Ejecutando Linux bajo Android

El ODROID-N2 parece un sistema de TV Android perfecto: todo funciona sin problemas y convierte un televisor normal en un televisor inteligente muy valioso. Todo funciona perfecto si eso es lo único que quieres hacer con tu ODROID-N2, pero para mí, no es suficiente. Quería usar el ODROID-N2 para reemplazar un ODROID-C2 que ejecutaba Linux, tenía algunos sensores (temperatura y PIR) conectados a sus pines GPIO y también ejecutaba Kodi. Podría haber usado Linux en el ODROID-N2, pero la experiencia de Android TV es mejor como centro multimedia.

Así que me embarqué en un nuevo viaje para aprender cómo hacer que Android y Linux coexistieran en el mismo hardware y así poder transferir fácilmente mis scripts de Linux que maneja los datos del sensor del ODROID-C2 al ODROID-N2.

Si estás empezando tu proyecto desde cero, deberías analizar primero la implementación de la API de Android Things de Hardkernel  (https://magazine.odroid.com/article/android-things/) y crear aplicaciones nativas de Android que usen los pines GPIO, pero en mi caso ya disponía de algunos scripts de Python que leían datos de temperatura y movimiento, de modo que quería reutilizarlos (https://bit.ly/2xhnGdH).

Instalar Linux bajo Android TV

Para exportar scripts existentes a Android es mejor instalar un entorno Linux en un chroot. La mejor aplicación para trabajar es Linux Deploy (https://bit.ly/2UbeKPV) que puedes encontrar en Play Store, pero desafortunadamente no está disponible para Android TV. Deberás descargar el apk de una fuente diferente (utilicé apkpure https://bit.ly/2wuiHGq) e instalarlo en ATV. Para instalar un paquete apk tienes dos opciones:

  • Copia el archivo apk en una unidad USB, conéctala al ATV y usa un explorador de archivos (hay algunas opciones disponibles en Play Store) para instalarlo.
  • O instálalo a través de adb (ya sea con un cable usb o a través de la red):

$ adb connect odroid-ip
$ adb install ~/downloads/Linux\ Deploy_v2.6.0_apkpure.com.apk
Yo estoy usando la imagen ATV de voodik (https://bit.ly/3bkj3OI) con adb habilitado en red.

Una vez instalado Linux Deploy, usa un ratón/teclado (no está diseñado para ATV y la entrada del mando a distancia no cubre todas las opciones) para configurar e implementar la distribución la Linux que elijas en el ODROID. Si no tienes acceso a un dispositivo de entrada adecuado, puede instalar scrcpy (https://github.com/Genymobile/scrcpy) y acceder a Android de forma similar a un escritorio remoto desde tu ordenador. También es posible que necesites instalar un launcher adicional para ver aplicaciones que no sean ATV, como el Sideload Launcher (https://play.google.com/store/apps/details?id=eu.chainfire.tv.sideloadlauncher&hl=en).

Inicia la aplicación y selecciona el icono de propiedades y configura tu entorno. Ten en cuenta que requiere acceso root, así que asegúrate de otorgarle root. Aquí puede elegir tu distribución favorita (yo usé Ubuntu 18.04), puede elegir el tamaño de la imagen rootfs (yo elegí 8G) y configurar las credenciales por defecto

Figura 1 - Propiedades de configuración de implementación de Linux

En la sección INIT, asegúrate de habilitar un sistema de inicialización. Yo opté por sys-v que usa scripts en /etc/rc3.d/.

Bajo las opciones de montaje, quería mostrar algunas configuraciones de Android hacia la imagen invitada de Linux, así que asigné / y /sdcard como puntos de montaje internos en Linux. Afortunadamente, /dev y /sys ya están compartidos.

Figura 2: Puntos de montaje de la implementación de Linux

En teoría, puedes instalar en un directorio y beneficiarte de todo el espacio del host, pero en mi caso esa opción no funcionaba

Cuando estés listo, puedes seleccionar Iniciar y el programa empezará a descargar los paquetes necesarios y creará una imagen mínima. Esto puede llevar un tiempo, pero cuando esté listo, puede continuar y empezar con esa imagen. Si has habilitado ssh, ahora deberías poder iniciar sesión en la IP de tu ODROID con ssh en el puerto 22. Ahora puedes usar apt para actualizar la lista de paquetes e instalar los paquetes que necesites.

En mi caso, necesitaba python3 y un montón de módulos de python, así que los instalé con:

# apt-get install python3-pip python3-paho-mqtt
Como necesito usar los pines GPIO, quería instalar la rama wiringpi de Hardkernel para el ODROID-N2. La forma más sencilla de hacerlo (sin compilar nada) es tomarlo del PPA de Hardkernel tal y como se describe en la wiki en https://wiki.odroid.com/odroid-n2/application_note/gpio/wiringpi:
# apt install software-properties-common
# add-apt-repository ppa:hardkernel/ppa
# apt update
# apt install odroid-wiringpi odroid-wiringpi-python
Una vez instalado, verifica que wiringpi pueda acceder a los pines GPIO y leer su estado
# gpio readall
Si todo está en su lugar, transfiere tus scripts y conecta el hardware. En mi caso, usr un sensor de temperatura DS18B20 (https://www.adafruit.com/product/381) que utiliza un cable para la comunicación y un sensor PIR HC-SR501 (https://www.hardkernel.com/shop/motion-detector-hc-sr501/) El sensor PIR es el más sencillo para trabajar, ya que requiere un pin GPIO de entrada que te permite leer su estado. Yo estoy usando el pin físico 12, que corresponde al pin wiringpi 1 si echas un vistazo a la distribución (https://wiki.odroid.com/odroid-n2/hardware/expansion_connectors).

El sensor de temperatura es algo más complicado porque necesita soporte de un único hilo de ejecución en el kernel. En mi caso, el kernel ATV en este momento no tenía habilitado el soporte de único hilo:

$ zcat /proc/config.gz | grep -i W1
# CONFIG_W1 is not set
Echa un vistazo a la próxima sección sobre compilación del kernel y flasheo.

Una vez que tus programas se ejecutan correctamente, debes iniciarlos cuando se inicia Linux (Linux Deploy se encarga de iniciarlos cuando Android se inicia si activas el inicio automático en la configuración de Linux Deploy). Desafortunadamente, puesto que Linux Deploy se ejecuta en un chroot si usas el comando service en Linux para iniciar y detener servicios, recibirás un aviso de que se está ejecutando en un chroot y no hará nada. En mi caso, necesitaba únicamente dos scripts, así que tuve que crear sys-v para mis procesos. Puedes obtener una plantilla de los script en https://github.com/fhd/init-script-template. Crea /etc/init.d/pir-mqtt-agent, configúralo para que apunte a tu script, hazlo ejecutable y vincúlalo a /etc/rc3.d:

# chmod a+x /etc/init.d/pir-mqtt-agent
# cd /etc/rc3.d/
# ln -s ../init.d/pir-mqtt-agent S10pir-mqtt-agent
SYS-V puede parecer arcaico, pero ha funcionado muy bien durante los últimos 20 años, y es más fácil de entender que systemd. Sin embargo, ¿qué sucede si los scripts fallan por alguna razón (por ejemplo, el sensor no está, la red está offline, etc.)? Sin systemd para hacer el servicio de mantenimiento, no hay nada que reinicia tus scripts muertos. Opté por la ruta rápida y chapucera y configuré cron para reiniciar periódicamente los scripts (incluso si no están activos). Esto funciona y cubre mis necesidades, aunque hay que tenerlo en cuenta a la hora crear un sistema de gestión de procesos más robusto.
# crontab -l
0 * * * * /usr/sbin/service pir-mqtt-service restart

Compilar el kernel de Android

Compilar el kernel de Android es casi lo mismo que compilar el kernel de Linux, aunque tiene algunas peculiaridades. Algunos de los cambios más importantes son:

  • La compilación se realiza en un sistema x86_64 (compilación cruzada)
  • zImage necesita combinarse con initrd y empaquetarse en una imagen de arranque de 16M
  • La imagen de arranque debe actualizarse a la partición de arranque de Android
  • El dtb vive en una partición dtbs y necesita ser flasheado también en caso de que le estés haciendo cambios.

Primero deberás configurar tu entorno de compilación cruzada. Suponiendo que estés en un sistema Ubuntu 18.04 x86_64, puede:

$ sudo apt-get -y install bc curl gcc git libssl-dev \
  libncurses5-dev lzop make u-boot-tools device-tree-compiler
$ mkdir ~/toolchain
$ cd ~/toolchain
$ wget https://releases.linaro.org/components/toolchain/binaries/6.3-2017.02/aarch64-linux-gnu/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu.tar.xz
$ unxz gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu.tar.xz
$ tar xvf gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu.tar
A continuación, descárgate la fuente del kernel de Android:
$ mkdir ~/development
$ cd ~/development
$ git clone --depth 1 https://github.com/hardkernel/linux.git -b odroidn2-4.9.y-android
$ cd linux
Ahora, ajusta algunas variables de entorno para que el entorno de compilación sepa que necesita usar el compilador cruzado. Deberás volver a ejecutar estos comandos cada vez que inicies un nuevo terminal (no se mantienen):
$ export ARCH=arm64
$ export CROSS_COMPILE=aarch64-linux-gnu-
$ export PATH=~/toolchain/gcc-linaro-6.3.1-2017.02-x86_64_aarch64-linux-gnu/bin/:$PATH
Ahora puedes empezar desde una configuración base, generalmente dentro del directorio de Linux:
$ make odroidn2_android_defconfig
Si deseas empezar con una configuración diferente, puedes extraer la configuración actual del kernel del sistema Android en ejecución desde /proc/config.gz, comprimirla y guardarla como ~ /development/linux/.config.

Ahora puedes ejecutar

$ make menuconfig
Figura 3 - make menuconfig

Aquí puedes buscar los elementos que necesitas y habilitarlos. Por razones de eficiencia, puede localizar una opción de configuración en el menú escribiendo '/' junto con el nombre de la opción (por ejemplo, en mi caso W1 - abreviatura de onewire). El resultado te indicará dónde puede encontrar esa opción (en mi caso, en drivers de dispositivo), cuál es su estado actual y si depende de otras opciones (y cuáles son sus estados). Si no puedes encontrar una opción en el menú, lo más probable es que esté oculta porque no se cumplen sus dependencias. Usando la función de búsqueda puede encontrar la dependencia que falta.

Figura 4: Buscando opciones de kernel

Una vez que llegue al menú, puede encontrar el elemento presionando ALT y la letra de acceso directo resaltada y deberías navegar a través de todas las opciones en el menú visible que comparte el mismo acceso directo.

Cuando habilitas una opción, generalmente tiene dos elecciones: integrada o como módulo. Tradicionalmente, Linux usa módulos y tiene la capacidad de cargar automáticamente los módulos a medida que se necesiten, pero Android es un poco diferente y, aunque puede usar módulos, no los carga automáticamente. La opción más fácil es compilarlo, pero si necesitas demasiadas opciones, puede hacer que el kernel crezca demasiado y no se ajuste (junto con el initrd) a la partición de arranque de 16M.

Si va a compilar módulos, deberás ubicarlo manualmente en el entorno de compilación (por ejemplo, drivers/w1/masters/w1-gpio.ko) y copiarlo en /vendor/lib/modules/ en Android. Ten en cuenta que la partición /vendor se monta con solo lectura y debe volver a montarse antes de copiar:

:/ # mount -o remount,rw /vendor $ adb push drivers/w1/masters/w1-gpio.ko /vendor/lib/modules/

Para cargar nuevos módulos al inicio deberá editar init.odroidn2.system.rc tal y como se describe en:https://github.com/codewalkerster/android_device_hardkernel_odroidn2/blob/s922_9.0.0_master/init.odroidn2.system.rc#L96

Para cubrir mis necesidades (soporte onewire) necesitaba habilitar los siguientes módulos (y tenerlos integrados):

CONFIG_W1
CONFIG_W1_MASTER_GPIO
CONFIG_W1_SLAVE_THERM
Ok, ahora que la configuración del kernel está lista, puedes guardarla y comenzar a compilar el kernel:
$ make -j$(nproc)
La compilación debería llevar un tiempo la primera vez, pero las ejecuciones posteriores deberían compilar únicamente las diferencias. Echa un vistazo a cualquier error, si se compila correctamente, debería obtener una ~/development/linux/arch/arm64/boot/Image.gz.
$ ls -l ~/development/linux/arch/arm64/boot/Image.gz
Si necesitas realizar cambios en el dtb (por ejemplo, habilitar o cambiar la configuración), puede usar fdtput (parte del paquete del compilador del árbol de dispositivos) para realizar cambios en el dtb. En mi caso, necesitaba habilitar explícitamente el soporte para un único hilo de ejecución en el dtb, de lo contrario el controlador no haría nada:
$ fdtput -t s ~/development/linux/arch/arm64/boot/dts/amlogic/meson64_odroidn2_android.dtb /onewire status okay
Una vez que el kernel y el dtb estén en orden, debemos preparar el bootimg. Para hacer esto, primero extrae la partición de arranque de la versión ATV en funcionamiento, para que podamos mantener el initrd de la imagen original. A continuación, instalamos bootimg, que es una herramienta para empaquetar y desempaquetar imágenes de arranque de Android.
$ adb shell
# if=/dev/block/boot of=/dev/shm/boot.img
# exit
$ mkdir ~/development/bootimg
$ cd ~/development/bootimg
$ adb pull /dev/shm/boot.img
$ sudo apt-get install abootimg
Ahora podemos inspeccionar boot.img y extraer el initrd y la configuración:
$ file boot.img
boot.img: Android bootimg, kernel (0x1080000), ramdisk (0x1000000), page size: 2048, cmdline (otg_device=1 buildvariant=userdebug)
$ abootimg -x boot.img
El último paso es crear el nuevo bootimg con el nuevo kernel:
$ abootimg --create boot-new.img -f bootimg.cfg -k ~/development/linux/arch/arm64/boot/Image.gz -r initrd.img
Ok, ahora estás listo para grabar el nuevo kernel y dtb:
$ adb push boot-new.img /dev/shm
$ adb push ~/development/linux/arch/arm64/boot/dts/amlogic/meson64_odroidn2_android.dtb /dev/shm
$ adb shell
# dd if=/dev/shm/boot-new.img of=/dev/block/boot
# dd if=/dev/shm/meson64_odroidn2_android.dtb of=/dev/block/dtbs
Una vez que reinicies, tu nuevo kernel debería hacerse cargo del sistema, puedes verificar que cuenta con la funcionalidad que necesitas. Si no se reinicia, deberás recuperar la placa al actualizar el boot.img anterior mediante el modo fastboot. ¡Disfruta de tu servidor Linux con una GUI de Android TV!

Be the first to comment

Leave a Reply