Android per sistemi embedded Real-Time (Seconda parte)

Android è un sistema operativo open source che, fin dalla nascita, è stato utilizzato nel mondo mobile tanto da rappresentarne la sua più tipica applicazione. In questo contesto rientra anche il segmento della telefonia dei tablet, un mercato ancora da esplorare completamente.

Introduzione

Il mondo embedded non è di certo immune: da diverso tempo, in effetti, si assiste a un uso, sempre più massiccio, di Android anche in questo particolare contesto applicativo. A rigore, è però necessario fare alcune precisazioni. Esistono diversi tipi di sistemi embedded caratterizzati da differenti specificità per via delle funzionalità offerte a riguardo agli obiettivi progettuali, di affidabilità e prevedibilità richieste. In questo contesto si passa dai sistemi di controllo per macchine industriali ai dispositivi di sicurezza delle automobili (ABS EBD e quant’altro), dai dispositivi per il networking (switch, access point) ai dispositivi mobili di ultima generazione. Android è un progetto nato e cresciuto attorno al segmento mobile embedded, ossia in tutte le applicazioni a microprocessore di dimensioni ridotte, dotate di una forma di alimentazione integrata che ne permetta il funzionamento fuori dalla rete elettrica e che possono presentare uno schermo e una o più modalità di acquisizione (tastiera o tecnologia touch). Al giorno d’oggi diventa sempre più necessario inserire Android anche in altri contesti operativi e tecnologici, tanto che diventa indispensabile portare il sistema operativo su architetture hardware differenti o anche inserire nuovi device per offrire sempre maggiori risposte al mercato e flessibilità.

ANDROID SU PIATTAFORMA HARDWARE

Portare Android su una qualsiasi piattaforma basata su processore ARM non è un’operazione complicata, anche se è necessario conoscere il sistema operativo e l’uso di una cross tool chain. Il processo di adattamento può essere diviso in due fasi: con la prima si deve portare il sistema operativo Linux e con l’altra è necessario svolgere i passi che sono indispensabili per integrare Android. La prima fase si può dividere in diverse attività; in altre parole occorre, ad esempio, prelevare le patch del kernel di Linux per il processore di riferimento e preparare la tool chain basata su ARM al fine di compilare il suo codice sorgente. In seguito è necessario definire e compilare il bootloader per il target di riferimento configurando, a questo proposito, il kernel di Linux per la piattaforma hardware. Infine, una volta compilato il codice sorgente di Linux, è necessario inserire l’immagine binaria ottenuta in formato compresso sulla piattaforma hardware. Conclusa questa fase è necessario lavorare direttamente su Android prendendo, per prima cosa, le sue patch e inserire tutti i moduli che si intendono modificare o utilizzare quali le cosiddette board specific components come codec, camera, audio, Wi-fi, power management, Bluetooth o quant’altro.

Terminata questa parte diventa necessario compilare il codice sorgente di Android e fare il burning dell’immagine ricavata per piattaforma hardware. A questo proposito la Figura 1 pone in evidenza i diversi passi indispensabili per modificare Android per la piattaforma in esame. Gli script di generazione dello stack risiedono nella directory build del repository Android dove è possibile trovare, inoltre, alcuni interessanti documenti che possono aiutarci a comprendere il cosiddetto Android Build System. Il building di Android ci permette di definire prodotti target differenti: definendo un prodotto target è possibile indicare quali moduli Android generare e installare (librerie e applicazioni), personalizzare le proprietà di sistema e indicare una lista di file da inserire staticamente nel file system generato per tale prodotto. Tipicamente una classica distribuzione Android è divisa in diversi folder, ognuno con una particolare funzionalità del tipo:

~/home$ ls
Makefile
bootable
external
kernel
system
Build
vendor
dalvik
frameworks
packages
bionic
development
hardware
prebuilt
Figura 1: passi necessari per portare Android

Figura 1: passi necessari per portare Android

Qualsiasi configurazione di ogni specifica piattaforma può essere aggiunta nella cartella “~/home/vendor”. All’interno della cartella vendor esistono le diverse configurazioni per ogni piattaforma commerciale o di lavoro esistente in commercio. A questo proposito la Figura 2 mostra il framework di Android per la piattaforma di lavoro di casa Marvell: Littleton. Questa soluzione si basa su processore PXA310 con architettura Intel della serie XScale e istruzioni ARM. La cartella Littleton contiene tutti i riferimenti specifici della piattaforma che intendiamo costruire, nel nostro caso Littleton, per l’appunto, tra cui anche il file BoardConfig.mk.

Figura 2: distribuzione Marvell.

Figura 2: distribuzione Marvell

Il Listato 1 mostra le diverse definizioni presenti: è bene ricordare che il file BoardConfig.mk è analizzato nella fase di configurazione del processo di build e definisce alcune variabili d’ambiente che influiscono nella generazione dei moduli.

# BoardConfig.mk
## Product-specific compile-time definitions.
#
# The generic product target doesn’t have any hardware-specific pieces.
TARGET_NO_BOOTLOADER := true
TARGET_NO_KERNEL := true
TARGET_NO_RADIOIMAGE := false
HAVE_HTC_AUDIO_DRIVER := false
BOARD_USES_GENERIC_AUDIO := false
USE_CAMERA_STUB := true
HAVE_MARVELL_WIFI_DRIVER := false
BOARD_HAVE_BLUETOOTH := false
Listato 1 – Littleton

Se ipotizzassimo che una società, PIPPO, voglia inserire, ad esempio, un nuovo device, PLUTO, allora la prima cosa necessaria, una volta acquisita una distribuzione di Android, è:

$ cd ~/android/aosp-v.v.x
$ . build/envsetup.sh
$ mkdir -p vendor/PIPPO/PLUTO
$ cd vendor/PIPPO/PLUTO

o, più genericamente:

mkdir venditore/xxx/yyy

dove xxx è al solito il nome del fornitore e yyy è il nome del prodotto, anche se, tuttavia, Google consiglia di utilizzare device. La prima cosa della quale abbiamo bisogno qui è un file AndroidProducts.mk per descrivere il nostro prodotto:

PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/full_PLUTO.mk

Ossia, in generale come

PRODUCT_MAKEFILES :=
$(LOCAL_DIR)/yyy.mk
Figura 3: envsetup.

Figura 3: envsetup

se il prodotto è identificato come yyy. Il file full_PLUTO.mk è descritto al Listato 4. Il listato pone in evidenza la soluzione adottata; in effetti, in questo lavoro si cerca di ereditare, attraverso la direttiva inherit-product, il lavoro già svolto per precedenti configurazioni hardware/software. Questa decisione ci consente di sfruttare i lavori precedenti allo scopo di costruire la nostra applicazione accelerando così il nostro lavoro. È necessario anche definire PRODUCT_PACKAGES, PRODUCT_NAME e  PRODUCT_DEVICE e chiamare ereditare-prodotto a ereditare da un makefile prodotto. In ambito x86 è preferibile utilizzare generic_x86.mk per ottenere tutte le regole comuni su architettura x86, come:

$(call inherit-product,$(SRC_TARGET_
DIR)/product/generic_x86.mk)

Il folder Product è il luogo dove si trovano i diversi prodotti di Marvell dove ogni board, nella terminologia Android, specifica un prodotto. Con il Listato 2 vediamo che il costruttore propone diverse alternative quali Littleton, traverb o saar. Il Listato 3 comprende tutte le definizioni utilizzate dal prodotto Littleton. Di seguito si riporta un esempio su come aggiungere una piattaforma ARM in ambito Android.

# AndroidProduct.mk
# This file should set PRODUCT_MAKEFILES to a list of product makefiles
# To expose to the build system. LOCAL_DIR will already be set to
# The directory containing this file.
##
# This file may not rely on the value of any variable other than
# LOCAL_DIR; do not use any conditionals, and do not look up the
# value of any variable that isn’t set in this file or in a file that
# It includes.
#
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/saar.mk \
$(LOCAL_DIR)/littleton.mk \
$(LOCAL_DIR)/tavorevb.mk
Listato 2 – AndroidProduct.mk
#littleton.mk
PRODUCT_PACKAGES := \
ApiDemos \
Development \
GPSEnable \
Quake \
AndroidTests \
Barcodes \
Email \
GlobalTime \
IM \
SoundRecorder \
Stk \
Term \
ToDoList \
Updater \
VoiceDialer \
WapBrowser \
WhackAMole \
TestHarness \
VideoPlayer \
SdkSetup
$(call inherit-product,
build/target/product/generic.mk)
PRODUCT_NAME := littleton
PRODUCT_DEVICE := littleton
Listato 3 – Littleton
$(call inherit-product, $(SRC_TARGET_DIR)/product/languages_full.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/full.mk)
DEVICE_PACKAGE_OVERLAYS :=
PRODUCT_PACKAGES +=
PRODUCT_COPY_FILES +=
PRODUCT_NAME := full_PLUTO
PRODUCT_DEVICE := PLUTO
PRODUCT_MODEL := Full Android on PLUTO, meep-meep
Listato 4 – full_PLUTO

Per prima cosa è necessario creare una cartella, ad esempio in android_root/device, con il nome desiderato.

mkdir [android_root]/device/arm

e successivamente possiamo creare il folder per il prodotto:

mkdir
[android_root]/device/arm
/products

In questo folder abbiamo la necessità di utilizzare un makefile “AndroidProducts.mk”, questo file contiene tutti i prodotti che risiedono sotto il folder ARM, quale:

PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/armboards_v7a.mk \

Ogni definizione deve contenere il riferimento al prodotto e al nome del device del tipo:

$(call inherit-product,
$(SRC_TARGET_DIR)/product
/generic.mk)
#
# Overrides
PRODUCT_NAME := [product_name]
PRODUCT_DEVICE := [board_name]

In questo caso con PRODUCT_NAME si identifica il nome del target utilizzato nel sistema di costruzione di Android, mentre con PRODUCT_DEVICE si definisce il nome della cartella che contiene i file che descrivono il device. La definizione di prodotto può essere effettuata sulla base di un prodotto padre ereditandone le impostazioni. Questa operazione viene effettuata con la stringa:

$(call inherit-product,
$(SRC_TARGET_DIR)/product/parentProduct.
mk)

In questo caso, oltre che a definire le variabili utili a identificare il prodotto, si aggiungono alle liste del prodotto padre i contenuti definiti per il prodotto desiderato. In seguito, dovranno essere sovrascritte le variabili di identificazione del nuovo prodotto. È chiaro che per inserire un nuovo file di definizione di prodotto nel processo di costruzione di Android questo deve essere inserito nella lista PRODUCT_MAKEFILES contenuta nel file build/target/product/AndroidProducts.mk
Nel nostro caso,

$(call inherit-product,
$(SRC_TARGET_DIR)/product
/generic.mk)
#
# Overrides
PRODUCT_NAME := armboard_v7a
PRODUCT_DEVICE := armboard_v7a

È opportuno ricordare che il valore di PRODUCT_NAME non deve essere lo stesso di PRODUCT_DEVICE; in effetti, si potrebbe anche sfruttare una definizione del tipo:

$(call inherit-product,
$(SRC_TARGET_DIR)/product
/generic.mk)
#
# Overrides
PRODUCT_NAME := vexpress
PRODUCT_DEVICE := cortex-a9

In questo modo i file che definiscono il nuovo device sono quindi contenuti nella cartella “cortex-a9”. Per generare un nuovo Android occorre lavorare almeno su due file di configurazione, ossia “AndroidProduct.mk” e “BoardConfig.mk”. Il primo è utilizzato come makefile del prodotto mentre il secondo si preoccupa di definire il prodotto o, come si preferisce dire, è il config file del prodotto. L’Android.mk potrebbe contenere la seguente definizione:

# make file for new hardware from
#
LOCAL_PATH := $(call my-dir)
#
# this is here to use
the pre-built kernel
ifeq
($(TARGET_PREBUILT_KERNEL),)
TARGET_PREBUILT_KERNEL :=
$(LOCAL_PATH)/kernel
endif
#
file :=
$(INSTALLED_KERNEL_TARGET)
ALL_PREBUILT += $(file)
$(file):
$(TARGET_PREBUILT_KERNEL) |$(ACP)
$(transform-prebuilt-to-target)
#
# no boot loader, so we don’t
need any of that stuff..
#
LOCAL_PATH :=
vendor/[company_name]/[board_name]
#
include $(CLEAR_VARS)
#
# include more board specific
stuff here? Such as Audio
parameters.
#

La porzione potrebbe essere adattata come segue:

# make file for ARMv7-A based SoC
#
LOCAL_PATH := $(call my-dir)
#
# this is here to use the prebuilt
kernel
ifeq
($(TARGET_PREBUILT_KERNEL),)
TARGET_PREBUILT_KERNEL :=
$(LOCAL_PATH)/kernel
endif
#
file :=
$(INSTALLED_KERNEL_TARGET)
ALL_PREBUILT += $(file)
$(file):
$(TARGET_PREBUILT_KERNEL) |
$(ACP)
$(transform-prebuilt-to-target)
#
# no boot loader, so we don’t
need any of that stuff..
#
LOCAL_PATH :=
device/arm/armboard_v7a
#
include $(CLEAR_VARS)
#
# include more board specific
stuff here? Such as Audio
parameters.
#
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/armboard_v7a.kl:syst
em/usr/keylayout/armboard_v7a.k

Si noti che la definizione PRODUCT_COPY_FILES contiene due locazioni separate dal carattere “:”. Il file BoardConfig.mk per il prodotto armboard_v7a è:

TARGET_CPU_ABI := armeabi-v7a
TARGET_CPU_ABI2 := armeabi
TARGET_NO_KERNEL := true
TARGET_ARCH_VARIANT := armv7-aneon
BOARD_USES_GENERIC_AUDIO :=
true
USE_CAMERA_STUB := true

A questo punto si è pronti per costruire il file system per Android per la nostra applicazione utilizzando make, come:

make PRODUCT-[PRODUCT_NAME]-eng

A un prodotto target sono associati determinati script di extraconfigurazione e generazione della piattaforma. Questi sono definiti nei file BoardConfig.mk e AndroidBoard.mk contenuti nella directory build/target/board/product_name/. Una volta applicate tutte le patch alla nostra applicazione è possibile costruire l’immagine Android attraverso il comando:

$ cd ~/mydroid
$ . build/envsetup.sh
e, successivamente:
$ make PRODUCT-littleton-user
$ make PRODUCT-littleton-eng

Utilizzando envsetup.sh si definisce l’ambiente di build che deve essere utilizzato. Come mostra l’esempio sopra riportato, ogni componente Android può esporre un tag che ne modifichi la presenza o meno nella versione del prodotto scelta attraverso la lista LOCAL_MODULE_TAGS. Con TARGET_BUILD_VARIANT è possibile selezionare i moduli da installare e ognuno di questi si suppone avere il relativo LOCAL_MODULE_TAGS impostato in Android.mk con almeno uno dei seguenti attributi: user, debug, eng, tests, optional o samples. Con il modificatore di default, eng, non si valuta il tag dei moduli per la loro inclusione nello stack generato, mentre con “make user” viene generata la versione “finale” dello stack e con “make userdebug” viene generata una versione simile a user contenente inoltre i moduli taggati come debug. Al termine, in seguito al processo di compilazione e linker, possiamo vedere che sono creati in “~/mydroid/out/target/product/Littleton/” i due file userdata.img (contiene una versione iniziale della partizione dedicata ai dati utente) e system.img (contiene tutti i file binari e di configurazione dello stack Android).

AGGIUNGIAMO UNA “APP”

Se volessimo aggiungere una default App sarà necessario, per prima cosa, aggiungere nel folder package una nuova cartella, “package/apps/”. Così, una volta definito il nostro piccolo programma utilizzando l’SDK con Eclipse, “CiaoGente”, possiamo copiare il nostro programma nella sua cartella di destinazione:

$ cp -a ~/workspace/CiaoGente
~/android/aospv.
v.x/packages/apps/

A questo punto dobbiamo creare il nostro Android.mk in aosp-rootpackages/apps/HelloWorld al fine di costruire la nostra app:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call alljava-
files-under, src)
LOCAL_PACKAGE_NAME := CiaoGente
include $(BUILD_PACKAGE)

Dalle definizioni utilizzate si desume che la nostra applicazione non sarà inclusa in Android per default visto che utilizziamo come optional il modulo LOCAL_MODULE_TAGS.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend