Programación bare metal de un SoC: Prueba de concepto sobre la Orange Pi Zero Plus 
Los SoCs están diseñados para ejecutar sistemas operativos completos (Linux, Android, etc.). La programación bare metal de este tipo de chips es una tarea complicada y poco agradecida (normalmente no se justifica el uso de un SoC sin sistema operativo, para eso están los microcontroladores), sin embargo estos proyectos brindan una oportunidad única para conocer los entresijos del chip y de paso entender mejor cómo funcionan los SoCs en general.

Orange Pi Zero Plus

El corazón de la placa Orange Pi Zero Plus (la que se ha utilizado para esta prueba de concepto) es un SoC H5 de la marca AllWinner. Se trata de un ARM Cortex-A53 de cuadruple núcleo que implementa la arquitectura ARMv8-A (64 bits). En el arranque, todos los ARM de 64 bits arrancan en modo 32 bits, así que por simplicidad se ha decidido que la prueba de concepto se haga en el modo de arranque compatible ARMv7-A (32 bits, sin pasar a modo 64 bits) y utilizando sólo el primer núcleo (en el arranque sólo está operativo el núcleo 0, los núcleos 1, 2 y 3 están desactivados).

La secuencia de arranque del H5

El H5 implementa varias formas y modos de arranque, sin embargo, en la placa Orange Pi Zero Plus el modo de arranque que se usa es el que busca en la tarjeta de memoria (MicroSD) el bootloader. En el caso habitual, para cargar un sistema operativo, dicho bootloader sera el U-Boot u otro similar. Aunque en el manual de usuario del H5 se especifica de forma más detallada, se puede simplificar diciendo que al arrancar el H5 ejecuta una "boot ROM" (BROM) que no es modificable y que se encuentra cableada dentro del chip. Esta ROM se encarga de inicializar la tarjeta de memoria, de cargar el SPL (Second Program Loader) desde la tarjeta de memoria en la RAM y de ejecutar dicho código una vez está cargado en RAM. En terminología H5 este SPL hace las veces de boot loader.

En http://linux-sunxi.org/Bootable_SD_card#SD_Card_Layout se especifica la distribución de los datos en la tarjeta de memoria, dónde debe estar alojado el SPL (offset 8192) y lo que puede ocupar como máximo (32 KBytes). Según la documentación oficial (http://linux-sunxi.org/BROM#U-Boot_SPL_limitations), este SPL no puede ser código tal cual, sino que debe tener un formato y una especie de firma digital especial. Dicho formato se encuentra documentado y un programador desarrolló hace tiempo una pequeña utilidad llamada "mksunxiboot", open source, programada en C, que, a partir de un binario estándar, genera un binario firmado y reconocible por parte del SoC como un SPL válido (la firma no deja de ser una estructura de datos en la cabecera más un checksum). El código fuente de dicha utilidad (es un único fichero en C) se puede encontrar en https://github.com/amery/mksunxiboot/.

Haciendo nuestro propio SPL

Para hacer la prueba de concepto bare metal bastará con hacer un pequeño programa que haga de SPL. En este caso se ha optado por hacer el típico blinker que actúe sobre una de las salidas GPIO de la placa a la que conectaremos un led para comprobar que el invento funciona. Usaremos la salida GPIO12 (se podría usar cualquier otra) que se corresponde con el pin 3 del puerto de expansión de la Orange Pi Zero Plus (http://linux-sunxi.org/Orange_Pi_Zero_P ... nsion_Port).



A continuación necesitaremos cualquier toolchain "arm-none-eabi" que tengamos a mano, puede ser tanto descargada de algún repositorio como compilada por nosotros mismos (ver post en este mismo blog). Se trata de una toolchain diseñada para hacer programas bare metal para arquitecturas ARM de 32 bits. En principio hay dos formas de hacerlo: la elegante y lenta y la "sucia" y rápida. La forma elegante y lenta obliga a escribir un linker script que nos permita pasar casi cualquier programa que queramos a un SPL, sin embargo la forma sucia y rápida, aunque no da tanta libertad, sí que nos permite hacer la prueba de concepto de forma rápida.

#include <stdint.h>

void spl() __attribute__ ((section(".spl")));

void spl() {
    volatile uint64_t n;
    const uint64_t WAIT = 20000ULL;
    const uint32_t CCU_BASE = 0x01C20000;
    *((volatile uint32_t *) (CCU_BASE + 0x0068)) |= 0x00000020;   // PIO clock enable
    const uint32_t PIO_BASE = 0x01C20800;
    *((volatile uint32_t *) (PIO_BASE + 0x0004)) = 0x77717777;  // PA12 como pin de salida
    while (true) {
        *((volatile uint32_t *) (PIO_BASE + 0x0010)) = 0x00001000;   // PA12 := 1
        for (n = 0; n < WAIT; n++)
            ;
        *((volatile uint32_t *) (PIO_BASE + 0x0010)) = 0x00000000;   // PA12 := 0
        for (n = 0; n < WAIT; n++)
            ;
    }
}


Como se puede apreciar, escribimos el código en una función a la que podemos ponerle el nombre que queramos y, mediante atributos del compilador, le decimos que debe estar en la sección ".spl" (esta etiqueta es arbitraria, la sección podría llamarse ".pepejuan"). Nótese que no estamos usando variables globales y no estamos referenciando nada que esté fuera de la propia función en sí (todo el código está autocontenido). Esta limitación es importante, pues, como se verá más adelante, sólo usaremos como código SPL lo que esté dentro de la sección ".spl" que se ha definido en tiempo de compilación.

A la hora de escribir el código se recurrió al manual de usuario oficial del H5 (https://linux-sunxi.org/File:Allwinner_H5_Manual_v1.0.pdf): en la sección "CCU" (sub sección "Gating and reset") se explica cómo habilitar el reloj para el módulo PIO (el encargado de controlar los pines GPIO), mientras que en la sección "Port Controller (CPUx-PORT)" se explica como habilitar y usar los pines GPIO. Para compilar y generar el fichero binario que transferiremos a la tarjeta MicroSD haremos lo siguiente:

arm-none-eabi-g++ -mtune=cortex-a7 -fno-exceptions -fno-rtti -nostartfiles -c -o spl.o spl.cc
arm-none-eabi-objcopy -O binary -j .spl spl.o spl.bin
mksunxiboot spl.bin spl_with_signature.bin


Primero se genera el fichero "spl.o", a continuación usando la utilidad objcopy extraemos en forma binaria el código que se encuentra en la sección ".spl" desde dentro de "spl.o" hacia "spl.bin" y, como tercer paso, invocamos la utilidad "mksunxiboot" para generar, a partir de "spl.bin", un "spl_with_signature.bin" que sí puede ser transferido tal cual a la tarjeta MicroSD. Como se puede apreciar, se le dice al compilador que genere código compatible Cortex-A7 (para que genere código siguiendo la arquitectura ARMv7-A). Para los que tengan curiosidad por el código que ha generado el compilador, se puede desensamblar dicho código mediante el siguiente comando:

arm-none-eabi-objdump -D spl.o


A continuación cogemos el fichero "spl_with_signature.bin" que acabamos de generar, lo copiamos tal cual a partir del offset 8192 de la tarjeta de memoria y arrancamos la Orange Pi Zero Plus con dicha tarjeta de memoria insertada:

dd if=spl_with_signature.img of=/dev/sdb bs=1024 seek=8

Et voilà :



Todo el código fuente está disponible en la sección soft.

[ añadir comentario ] ( 241 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 794 )

<Anterior | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Siguiente> >>