
Reverse engineering the TI AM3358 boot ROM
Probablemente han pasado dieciocho meses desde que conseguí unas cuantas placas Beaglebone Black, salvadas de la basura. Desafortunadamente, las placas no funcionaron de inmediato. Ahora bien, era la primera vez que trabajaba con una de estas placas, o con cualquier computadora de placa única, para el caso, así que no estaba seguro de si el problema era algo que yo estaba haciendo o algo mal con las propias placas (quizás por eso estaban destinadas a la basura en primer lugar). Me tomó bastante tiempo y una gran cantidad de esfuerzo lograr que estas placas realmente comenzaran a arrancar, pero demonios, lo logré. Y esto es lo que aprendí en el camino.
He incluido algunas utilidades en este repositorio, junto con un archivo xml exportado desde Ghidra que tiene todos los símbolos que he obtenido hasta ahora mediante la ingeniería inversa. Usé esta publicación para exportar sin el firmware real, para evitar cualquier problema de derechos de autor, por si acaso. Si quieres depurar la ROM de arranque por ti mismo, ya tendrás el JTAG conectado, así que puedes volcar la ROM de arranque (de 0x20000 a 0x2BFFF) por tu cuenta.
Para cargar los símbolos:
0x20000; también puedes establecer el nombre del bloque como bootrom.main(), o al manejador de arranque por tarjeta MMC/SD.Para empezar, sabía que estas eran versiones personalizadas del beaglebone black estándar, así que al principio determiné que podría ser algo que faltaba en la propia placa, como un identificador de placa. Lo que vi al arrancar una tarjeta SD estándar formateada con balenaEtcher fue simplemente nada. Esperaba que los LEDs de la placa comenzaran a parpadear, y esperaba que conectar un cable UART a USB me permitiera ver el proceso de U-Boot. Sin embargo, la UART estaba en silencio. Si retiraba la tarjeta SD, emitía la letra C una y otra vez, lo cual es un comportamiento esperado para un arranque por UART/serie. Definitivamente estaba intentando arrancar, y la tarjeta SD estaba alterando este comportamiento, pero no tenía más visibilidad. La mayoría de las guías de solución de problemas en la web tomaban la salida de U-Boot como punto de partida para diagnosticar problemas. Supongo que no iba a tener ese lujo.
En ese punto, pensé que valdría la pena conectar una sonda de depuración. Desafortunadamente, no tenía un conector compatible con el footprint existente, así que hice uno propio.
La placa beaglebone tiene un conector con la designación P2 que expone las conexiones JTAG. Conecté algunos cables desde este a un conector hembra para poder comunicarme con él a través de mi J-Link.



Al lanzar Ozone (el depurador de Segger), configuré el J-Link y comencé simplemente intentando encontrar el punto de entrada. Había pensado que un reset-halt me colocaría donde necesitaba estar, que fue como llegué a la suposición (incorrecta) de que el punto de entrada era 0x2148a, aunque ciertamente noté que esto no era consistente. Más tarde, me di cuenta de que las placas AM335x no se llevan bien con el reset-halt del J-Link, por lo que en realidad había un retraso de quizás unos cientos de ciclos de reloj, aterrizando en algún lugar dentro de un manejador de arranque, de manera indeterminista. (Finalmente lo solucioné escribiendo un archivo GEL para Code Composer Studio de TI, que soporta depuración con J-Link: en el reset, el registro PC se establece en el manejador de reset, los registros se limpian y el modo de instrucción se fuerza a ARM).
De un hilo en los foros de TI (AM335x: empleados de TI, ¿dónde puedo obtener el código fuente/símbolos del ROM Bootloader?) obtuve un par de símbolos de depuración: SPI Initialize en 0x231e0, SPI ReadSectors en 0x23230, y 0x24bfa es una rutina que realiza una lectura por UART. Eso es una buena ayuda, supongo. Noté que el arranque fallaba terminando en un bucle infinito en 0x402f0440, un bucle sin salida. Mmm, bastante lejos del resto de la ROM de arranque; debe estar en la RAM o algo así. ¡Probablemente sea hora de acudir al manual de referencia técnica (TRM)!
El Capítulo 26 del TRM contiene muchísima información sobre el arranque. Obtenemos la siguiente vista de la ROM de arranque:

Descripción:
La arquitectura del código ROM público se muestra en la Figura 26-1. Está dividida en tres capas principales con un enfoque de arriba hacia abajo: alto nivel, controladores y capa de abstracción de hardware (HAL). Cada capa se comunica con una capa de nivel inferior a través de una interfaz unificada. La capa de alto nivel está a cargo de las tareas principales del código ROM público: configuración del watchdog y de los relojes, y la rutina principal de arranque. La capa de controladores implementa los protocolos lógicos y de comunicación para cualquier dispositivo de arranque de acuerdo con la especificación de la interfaz. Finalmente, la HAL implementa el código de más bajo nivel para interactuar con las IPs de infraestructura de hardware. Los dispositivos de arranque finales están conectados a los pads de E/S del dispositivo.

La Figura 26-2 ilustra el flujo de alto nivel del procedimiento de arranque del código ROM público. En este dispositivo, el código ROM público comienza al completarse el inicio seguro (realizado por el código ROM seguro). El código ROM realiza entonces la configuración e inicialización de la plataforma como parte del procedimiento de inicio público. La lista de dispositivos de arranque se crea basándose en los pines SYSBOOT. Un dispositivo de arranque puede ser un dispositivo de arranque por memoria (memoria flash soldada o un dispositivo de arranque temporal, como una tarjeta de memoria) o una interfaz periférica conectada a un host. El bucle principal del procedimiento de arranque recorre la lista de dispositivos de arranque e intenta buscar una imagen en el dispositivo de arranque seleccionado actualmente. El bucle se abandona si se encuentra una imagen de arranque válida y se ejecuta correctamente, o si vence el watchdog. El procedimiento de autenticación de imagen se realiza antes de la ejecución de la imagen en un dispositivo HS. Un fallo en el procedimiento de autenticación provoca una ramificación a un “bucle infinito” en la ROM segura (a la espera de un reset por watchdog).
¡Mapa de memoria! ¡Vectores de excepción! ¡Diagramas de flujo! Hay mucha información en esta sección. Mi trabajo se volvió mucho más fácil.
En este punto, usé la sonda JTAG para descargar el firmware en un par de archivos diferentes y comencé a cargar las cosas en Ghidra. No parecía haber archivos SVD ni otros mapeos de registros disponibles en un formato conveniente, lo cual es realmente desafortunado, porque eso significa que necesito definir manualmente las regiones de memoria, los registros y todo lo demás. Fue un proceso tedioso, pero después de un tiempo tenía un script de Python que podía usar para cargar símbolos en Ghidra para el AM3358. ¡Una cosa menos de la que preocuparme!
Parece que los archivos que tengo se pueden mapear así:
Es interesante que el bucle infinito en 0x402f_0440 esté al principio de la “imagen descargada” en la SRAM interna, mientras que los vectores de excepción se almacenan en otro lugar. Quizás esto sea una pista importante más adelante...
En el reset, la ROM de arranque privada maneja los asuntos de seguridad y salta a 0x2 0000, que contiene los vectores de reset. La primera instrucción es una bifurcación a 0x2 08d0, que debe ser el punto de entrada. No es una instrucción BX, por lo que, presumiblemente, todavía estamos en modo ARM en ese punto.
Este es el primer código que se ejecuta, lo que significa que no es exactamente una “función” con parámetros, sino más bien un script de inicio generado por compilación. El primer bloque básico:```arm ldr r4,[->Peripherals::CM_PER] mov r0,#0x2c ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r6,#0x2 str r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r0,#0x2c poll: ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL cmp r6,#0x2 bne poll
This block sets the OCMC RAM clock to enabled:
1. Set `CM_PER_OCMCRAM_CLKCTRL=0x2`
2. Check if register was set; if not, keep polling
The `CM_PER_OCMCRAM_CLKCTRL` register uses bits 0 and 1 for the `MODULEMODE` field, setting this `=0x2` enables the clock to the OCMC RAM.
The next basic block:```arm
ldr r0,[PTR_control_status]
ldr r0,[r0,#0x0]=>control_status
and r0,r0,#0x700
mov r0,r0, lsr #0x8
cmp r0,#0x3
bne skip
ldr r0,[PTR_control_status]
ldr r0,[r0,#0x0]=>control_status
cpy r6,r0
and r0,r0,#0x1f
cmp r0,#0x1f
bleq GPMIC_init
skip: ...
Este bloque hace lo siguiente:
(control_status & 0x700) >> 8 == 0x3, y omite si no es asícontrol_status & 0x1f == 0x1f, y si es así, llama a la función GPMC_init después de cargar control_status en r6El siguiente bloque configura el coprocesador:```arm msr cpsr_c,#0xd3 ldr r4,[->Exceptions::ROM_RESET_VECTOR] mcr p15,0x0,r4,cr12,cr0,0x0 bl LAB_00020934 bl LAB_00020938 bl LAB_0002093c bl LAB_00020940 bl LAB_00020944 bl LAB_00020948 bl LAB_0002094c bl LAB_00020950 mrc p15,0x0,r0,cr1,cr0,0x0 orr r0,r0,#0x800 mcr p15,0x0,r0,cr1,cr0,0x0 b LAB_000207f0
Operaciones en este bloque:
1. Mover `11010011b` al campo de control de CPSR (`I=1`,`F=1`,`T=0`,`MODE=10011`)
1. `I` es desactivación de interrupciones, `F` es desactivación de interrupciones rápidas (por lo que `I=F=1` significa que las interrupciones están desactivadas)
2. `T` es el modo Thumb, configurado a `0`
3. `MODE=10011` establece el modo de procesador en modo Supervisor ([ref](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/ARM-processor-modes?lang=en#CIHGHDGI))
4. Ver [aquí](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/Program-Status-Registers--PSRs-) para más información
2. Cargar la dirección del vector de reset de ROM
3. Acceder al [coprocesador 15](https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers/Coprocessor-15) (coproc. de control del sistema), registro de Security Extensions `c12`, y cargar el vector de reset de ROM en el `VBAR` (registro de dirección base de vectores)

4. ¿Parecen saltos `nop`? ¿Por qué `bl` en lugar de `b`?
5. Habilitar la predicción de saltos (establecer el bit 11 del registro de control del sistema `SCTLR`)

*Registros `c1` de CP15 (registros de control del sistema) en la implementación VMSA*
Descripción del registro `SCTLR`:
> El SCTLR proporciona el control de nivel superior del sistema, incluido su sistema de memoria.
> Este registro forma parte del grupo funcional de registros de control de memoria virtual.
Consulte la página B4-1687 del TRM. El bit 11 es el bit de *habilitación de predicción de saltos*; configurarlo como habilitado significa que la [predicción de saltos](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/Common-Memory-System-Architecture-Features/Caches/Branch-predictors) está habilitada.
6. Llamar a una función (mediante un salto a una instrucción de llamada)
### Función en `0x20894` (`__main`)
Esta función es el destino de un salto desde otra rutina temprana. Creo que inicializa la pila, y posiblemente los temporizadores o el watchdog, antes de llamar a `FUN_0002889c` (¡que más tarde se revela como `main()`!).```arm
ldr sp,[->RESERVED_EXCEPTION_BRANCH] ; 0x4030ce00
blx load_stack_1
ldr r12,[DWORD_1]
add r12,r12,pc
tst r12,#0x1
adrne lr,0x208bd
cpyeq lr,pc
bx r12 ;=>init_timers_maybe
adr r12,0x208bd
bx r12 ;=>LAB_000208bc
000208bc bl FUN_0002889c
000208c0 ddw 0x109
000208c4 addr RESERVED_EXCEPTION_BRANCH
...
RESERVED_EXCEPTION_BRANCH:
ldr pc=>LAB_00020090,[PTR_LAB_4030ce20]
; 20090 is a dead loop
Las operaciones aquí son:
r0,r1,r2,r3,r4,lr a la pila (pila pública en el mapa de memoria)
load_stack_1 salta a una función vacía bx lr, y luego extrae r0,r1,r2,r3,r4,pc de la pila (básicamente devuelve esos datos a los registros y pone lo que contenga lr en el pc para retornar)pc + 0x109 es impar; si es par, cargar 0x208bd en lr; de lo contrario, copiar pc en lrFUN_0002889cEsta debería ser la función __main() a la que se refiere el diagrama de flujo de arranque:

Esto convertiría a la siguiente función en la función main.
Como se muestra en la parte superior de la Figura 26-8, la CPU salta al vector de reset del Código ROM público una vez que ha completado la inicialización de arranque seguro. Una vez en modo público, al iniciar el sistema, la CPU realiza la inicialización del lado público y la configuración de la pila (inicialización C generada automáticamente por el compilador o "scatter loading"). Luego configura el temporizador watchdog 1 (ajustado a tres minutos), realiza la configuración de los relojes del sistema. Finalmente salta a la rutina de arranque.
0x209b0)Cuando se llama a main, el registro SP apunta a 0x4030ce00. Aquí es donde comienza la pila, y crece hacia abajo hacia 0x4030 b800; y dado que apuntamos a la dirección 0x4030 cdf0 tras empujar 4 registros (una diferencia de 16 bytes o 4 palabras), estamos usando una pila descendente completa, como en AAPCS. Es decir, SP apunta a la palabra más reciente de la pila y crece hacia abajo.
Aquí está la función main() descompilada:```c
int main()
{
uint local_10;
uint local_c;
local_c = 0; local_10 = 0; check_stack_prm(&local_10); update_coldreset_tracing_vector(local_10); update_current_tracing_vector(1); main_clock_init(6,0); watchdog_softreset(); watchdog_write_disable_seq_data2(); set_watchdog(300000); if ((local_10 & 1) != 0) { update_current_tracing_vector(2); local_c = local_c & 0xffff | 1; } timer_func_1(); clock_init_func_4(&local_c); run_booting_loop(&local_c,local_10 & 0xff); return 0; }
Lo más interesante para mis propósitos es la función `run_booting_loop` en `0x20a10`.
### Notas sobre X-Loader
Después de revisar el inicio y llegar a esta parte con cadenas como "ISSW", "CHSETTINGS" y "X-LOADER", empecé a buscar otros lugares donde estas cadenas pudieran aparecer en contextos relacionados con U-Boot. Me topé con [este hilo](https://forum.xda-developers.com/t/discussion-on-the-boot-loader-cracked.1378886/) de personas que hacían ingeniería inversa o crackeaban el firmware del Nook, y el [código fuente de x-loader](https://github.com/joelagnel/x-loader/blob/f3c74bc9b01dac58e553393d6ec1041353f2f1f7/scripts/signGP.c) contiene referencias a cosas como `CHSETTINGS`. Por lo que he visto, "ISSW" [parece](https://github.com/u-boot/u-boot/blob/master/doc/README.ti-secure) referirse al arranque desde dispositivos que no son de memoria.
Recuerden de la documentación de inicialización, el código de alto nivel:

De interés:
- RNDIS
- FAR
- XMODEM
- BOOTP
- TFTP
- DFT
Quizás sea hora de intentar de nuevo la depuración en vivo. Intentar aplicar ingeniería inversa a todas esas estructuras probablemente sería doloroso, considerando la gran cantidad de datos que no puedo entender...
¡Hurra! La depuración en vivo funciona al establecer el PC y el SP manualmente usando los vectores que he encontrado:

A partir del código fuente y de los vectores de rastreo, pude mapear las distintas opciones de arranque y el número de dispositivo que se les asigna. Esto luego resultó muy útil, ya que tuve que distinguir entre MMC0 (8) y MMCSD1 (9) al establecer puntos de interrupción en el manejador de arranque SD/MMC.
| Tipo | Dispositivo | ID de dispositivo |
| ---------- | ------------------ | ----------------- |
| Memoria | XIP (MUX2) | 1 |
| Memoria | XIP w/WAIT (MUX2) | 2 |
| Memoria | XIP (MUX1) | 3 |
| Memoria | XIP w/WAIT (MUX1) | 4 |
| Memoria | NAND | 5 |
| Memoria | MMCSD1 | 7, 9 (eMMC) |
| Memoria | NAND_I2C | 10 |
| Memoria | MMC0 | 8, 12 (SD) |
| Periférico | UART0 | 16 |
| Periférico | USB | 20 |
| Periférico | GPGMAC0 | 22 |
Para obtener información sobre cómo arranca el procesador, consulte el TRM y [esta respuesta en Stack Exchange](https://stackoverflow.com/a/31252989/8565545). En resumen:
1. La ROM de arranque ha identificado el archivo MLO (Mmc LOader) en la tarjeta SD y lo ha copiado a la SRAM
2. Este es el cargador de programas secundario, un cargador de arranque más pequeño que inicializa la RAM completa y copia allí el binario completo de U-Boot para su ejecución
3. Después de que el binario de U-Boot se ejecuta, nosotros (o más bien U-Boot) finalmente arrancamos el kernel
### `run_booting_loop()`
Este es el bucle de arranque principal. Se ejecuta indefinidamente, o hasta que la ejecución se bifurca a otro cargador de arranque que se cargaría en la RAM.
Inicio del procedimiento, sin actualizaciones de los vectores de rastreo:
- Consultar el tipo de dispositivo
- Si el tipo de dispositivo es 5 (dispositivo seguro), realizar alguna otra inicialización
- Ejecutar `build_boot_list(int,buffer[],data[],int)`
- `buffer[]` se inicializa a `0xff` y `data[]` incluye el tipo de dispositivo (probablemente)```c
void run_booting_loop(uint32_t *r0_config,undefined4 param_2,undefined4 param_3,
undefined4 default_list)
{
int iVar1;
uint j;
uint i;
int device_type;
byte alt_list [12];
undefined4 boot_status;
byte boot_list [8];
uint8_t local_buffer [8];
update_current_tracing_vector(3);
/* Device type is 3 */
lookup_device_type(&device_type);
if ((device_type == AM335X_HIGH_SECURITY) && (iVar1 = return_zero_4(), iVar1 != 0)) {
init_something_1_small(&STATIC_DATA_1);
}
/* param1 = 1
param2 = 4030 ebc4
param3 = 4030 ebb4 */
build_boot_list(*(ushort *)r0_config,boot_list,alt_list,default_list);
do {
i = 0;
local_buffer[0] = 0xff;
local_buffer[1] = 0xff;
local_buffer[2] = 0xff;
local_buffer[3] = 0xff;
do {
if (boot_list[i] - 1 < 12) {
update_current_tracing_vector(4);
/* No return unless there is an error */
boot_device_1(r0_config,boot_list[i],local_buffer);
}
else if (boot_list[i] - 65 < 8) {
update_current_tracing_vector(5);
watchdog_write_disable_seq_data2();
boot_status = 0xffffffff;
boot_device_2((uint32_t)r0_config,boot_list[i],&boot_status,local_buffer);
watchdog_write_enable_seq_data2();
if (boot_status != 0xffffffff) {
local_buffer[0] = (undefined)boot_status;
local_buffer[1] = boot_status._1_1_;
local_buffer[2] = boot_status._2_1_;
local_buffer[3] = boot_status._3_1_;
if ((boot_status & 0xffff00ff) == 0xf0030006) {
update_current_tracing_vector(9);
boot_list[i + 1] = (byte)(boot_status >> 8);
}
else if (boot_status != 0xf0030002) {
update_current_tracing_vector(8);
j = 0;
do {
if (63 < boot_list[j]) {
boot_list[j] = 0;
}
j = j + 1 & 0xff;
} while (j < 8);
}
}
}
i = i + 1 & 0xff;
} while (i < 8);
update_current_tracing_vector(6);
} while( true );
}
Descubrí que había una función en 0x23d7a a la que he llamado boot_into_SRAM(), que era la última función llamada antes de saltar a SRAM, y desde allí, pasar al manejador de excepciones. Anteriormente, tenía guardado un estado diferente de la RAM, pero al depurar en vivo una vez más (ahora más de un año después, julio de 2024), me di cuenta de lo que debía estar ocurriendo. La placa estaba leyendo correctamente datos de la tarjeta SD y ejecutando código que había cargado desde la tarjeta. Para verificarlo, tuve que encontrar bytes en la SRAM que coincidieran con los de la tarjeta SD. Resulta que dentro de am335x-evm-linux-sdk-bin-.../board-support/prebuilt-images/ hay un archivo binario llamado u-boot-spl.bin-am335x-evm, y el código de este binario coincide con lo que aparece en la SRAM. ¡Hemos llegado al SPL de uboot!
Hemos arrancado correctamente en la SRAM; ahora me pregunto sobre la terminal UART, que debería mostrar información sobre uboot. La conexión de hardware se muestra a continuación.

Conectando el dispositivo con CuteCom, 115200 @ 8-N-1, sin tarjeta SD insertada, simplemente emite C repetidamente.

Pero cuando la tarjeta SD está insertada, la UART no emite nada. Sin mensajes, sin caracteres. ¿El fallo debe estar apareciendo demasiado pronto en el proceso de arranque? Pero ahora que también sé qué código está ejecutando (y tengo su fuente), debería poder compilar algunos símbolos de depuración para él y poner en marcha una sesión de depuración adecuada. Esto puede no ser trivial; tengo que asegurarme de compilar el código de la misma manera, y puede que me lleve tiempo aprender exactamente qué ha cargado el SDK en mi tarjeta SD y cómo compilarlo.
Dado que U-Boot debería estar enviando texto a la UART y no veo nada, supongo que estamos capturando una excepción en algún lugar del SPL.
En este punto, pasé muchísimas horas haciendo ingeniería inversa y limpiando el código fuente descompilado de la boot ROM en Ghidra, examinando estructuras y cómo se usaba cada miembro de datos entre funciones, a veces anidadas, causándome todo tipo de estragos. Mientras eso quedaba en un segundo plano, pensé que también era hora de empezar a depurar y compilar mi propio código. Después de todo, estamos en RAM; ¿por qué no cargar los símbolos del SPL y ver qué está pasando?
Puedes depurar con Ozone, el depurador de Segger para J-Link. También puedes usar el Code Composer Studio (CCS) de TI o quizás su versión "ligera" estilo VSCode, CCS Theia. Conseguí compilar todo en el SDK siguiendo este vídeo: Serie de portado de placas Sitara Linux: Módulo 6. Hay tres componentes que compilar:
Seguí el vídeo del Módulo 7 de la serie anterior y logré que las cosas funcionaran, con un par de notas:
s_init() ya no está presente¿Tener símbolos? Hermoso. Ahora puedo ver qué está sucediendo en la ejecución, comenzando con un manejador de reinicio reset(), y puedo ver dónde terminamos con nuestra excepción. Para rastrearlo, puse un punto de interrupción en el manejador de excepciones en 0x402f 0440 y verifiqué el registro de enlace, que aún almacenaba la dirección de la función más reciente. Resultó ser la dirección 0x402f 76ce, aunque no parece consistente. ¿Qué está causando el error?
Nota: Para depurar, siguiendo el vídeo del Módulo 7, ejecuta hasta 0x402f 0400, luego haz la parte de Load Memory(). Esto debe hacerse en cada reinicio.
Entramos en la función device_probe() (0x402f 74c4), ¿luego un par de otras funciones? Después, desde do_setup_dpll() que se bifurcó en 0x402f 07fc, no salimos, así que sigamos entrando en eso. Avanzando paso a paso, volvemos a _main() en crt0.S, ubicado en 0x402f 14e0. Parece que podríamos estar saliendo de board_init_f() y dirigiéndonos a spl_relocate_stack_gd(). Esta llamada no sale. Llegamos a dm_fixup_for_gd_move(). Esto contiene una instrucción que falla, en 0x402f 76c2. Creo que esto es: está intentando acceder a 0x81ff ff20. Aparentemente no sirve. Tengo la corazonada de que hay un problema con la configuración de la SDRAM. Busqué todos los hilos en los foros de TI relacionados con problemas similares y encontré media docena que contenían algunas pistas que me ayudaron. Concluí que probablemente estaba relacionado con (a) el ajuste de EMIF, o (b) el nivelado por software.
Mi placa es la que se muestra a continuación.

La memoria es de Micron, mientras que el esquemático del BeagleBone Black que tengo (rev C3) utiliza memoria DDR3 de Kingston, específicamente la D2516EC4BXGGB. La DDR3 es U12; podemos usar la página de decodificador de marcas de Micron para encontrar la pieza:
Para asegurarme de que la cosa está viva, comencé simplemente comprobando que se suministrara la alimentación. La hoja de datos especifica que debe ser 1.5V +/- 0.075V. Mido 1.506V a través de R6 en la parte inferior de la placa. Tenemos dos puntos de prueba, TP1 y TP2.
Por si alguna vez resulta útil, aquí hay un par de puntos de prueba.
El circuito de la memoria se describe en detalle en la página de diseño de hardware.
Comprobemos la línea de habilitación del reloj. Podemos verificar ambos lados de R96; un lado debe estar conectado a tierra y el otro debe mantenerse en nivel alto.
Confirmado, 1.5V en CKE.
El siguiente paso es comprobar la señal de reloj. Hice lo que pude aquí, usando el tinySA con la antena conectada apuntando aproximadamente en dirección al chip de RAM. Haciendo este tipo de "olfateo", estoy bastante seguro de que el reloj está presente, al menos por ahora.
Continuando, es hora de ver la interfaz de memoria externa. Algo que aparece mucho es el concepto de un archivo GEL. Es un lenguaje interpretado desarrollado por Texas Instruments para Code Composer Studio; significa General Extension Language.
Un archivo GEL se incluye en la herramienta de configuración de memoria DDR.
¡Muy bien! Seguí el procedimiento de ajuste (lo mejor que pude) y logré encontrar valores óptimos para el archivo GEL.```
The Slave Ratio Search Program Values are...
PARAMETER MAX | MIN | OPTIMUM | RANGE
DATA_PHY_RD_DQS_SLAVE_RATIO 0x071 | 0x005 | 0x03b | 0x06c DATA_PHY_FIFO_WE_SLAVE_RATIO 0x1b3 | 0x046 | 0x0fc | 0x16d DATA_PHY_WR_DQS_SLAVE_RATIO 0x0f7 | 0x01a | 0x088 | 0x0dd DATA_PHY_WR_DATA_SLAVE_RATIO 0x137 | 0x05a | 0x0c8 | 0x0dd
Tengo que ajustar cosas de la RAM en el Memory Browser... ¡Uy! ¡Funciona!
Así que la memoria ciertamente parece estar funcionando, pero el SPL sigue fallando, así que quizás haya algo relacionado con la forma en que el SPL intenta inicializar la SDRAM. Ah, claro, ¡hay más en el procedimiento de ajuste, por supuesto! Tienes que actualizar realmente el SPL...
Estamos más cerca ahora. El archivo `board.c` inicializa la DDR comprobando qué tipo de placa tenemos. Pero para esta placa, todas las funciones (`board_is_evm_sk()`, `board_is_icev2()`, `board_is_bone_lt()`, etc.) devuelven falso, así que se usa por defecto `config_ddr(266, ...)` donde 266 es la frecuencia de reloj en MHz, y *debería* ser 400 MHz. Eso ciertamente sería un problema.
`board_is_bone_lt` debería evitarse haciendo que siempre devuelva verdadero. Hice esto y avanzo un poco más, pero algo me está molestando. Cargar el nuevo archivo MLO en la tarjeta SD no funciona, incluso aunque cargar el programa directamente funciona bien. ¿Qué está pasando aquí? Puedo decir que el código que se carga en la SRAM no es el mismo código que he compilado. De hecho, incluso formateé la tarjeta SD, ¡y parece que un SPL por defecto se está cargando en la SRAM de todos modos! Verifiqué que el proceso de arranque no intenta continuar cuando se usa otra tarjeta SD. Por lo tanto, el cargador de arranque definitivamente está buscando la partición de arranque en la tarjeta SD, luego mueve la ejecución a la SRAM, pero ¿todavía no ha copiado los datos? Uf. ¿De dónde viene?
Este problema me causó no pocos problemas. Eliminé todas las particiones, puse a cero el MBR, puse a cero la partición de arranque y probé diferentes tarjetas SD, y solo mi tarjeta aún podía arrancar, así que tenía que haber *algún* dato de arranque todavía en ella, almacenado en algún lugar. Finalmente, pude poner fin a la locura poniendo a cero la tarjeta *entera*.
También aprendí en este punto acerca de los **vectores de rastreo** a los que puedes acceder al solucionar problemas de la ROM de arranque. Esto también sería extremadamente útil para la ingeniería inversa, ya que sabía de dónde provenían todas las llamadas de rastreo y podía asignar nombres de funciones, etc., basándome en esas llamadas. Hice una hoja de cálculo para interpretar estos vectores de rastreo y la usé para entender rápidamente cómo cambiaba el procedimiento de arranque a medida que cambiaba los parámetros de la tarjeta. Y efectivamente, afirmaba encontrar el CHSETTINGS una y otra vez mientras intentaba formatear y reformatear la tarjeta SD, antes de poner a cero desesperadamente todo el asunto.
Para intentar leer la tarjeta de la manera en que lo haría el procesador TI, puedes usar `dd`. Usa block size=512 y especifica el primer sector (intenta usar GParted para comprobar cuál) saltando los primeros `n`. Ejemplo: El primer sector es 2048, el dispositivo es `sda`, leeremos solo el primer sector:```
sudo dd if=/dev/sda1 of=/home/sam/sector2048 bs=512 skip=2048 count=1
Usé esto para descargar imágenes del MBR y del inicio de la partición de arranque directamente desde la tarjeta SD, ambas cosas que resultarían útiles más adelante.
Mis esfuerzos de ingeniería inversa en Ghidra me habían llevado a las funciones del manejador de arranque de la tarjeta SD, y ahora podía ver funciones que enviaban comandos SD a la tarjeta, y podía avanzar paso a paso y ver con qué respondía la tarjeta. Pensé que estaba mirando en el lugar correcto y que la tarjeta devolvía todo ceros. Más tarde supe que posiblemente estaba avanzando por el manejador de eMMC (el mismo manejador pero con un ID de dispositivo diferente), o que algo más fallaba, porque no había nada malo con la tarjeta SD. Aun así, decidí que era hora de aprender cómo funcionan estas tarjetas.
Tenía curiosidad por ver por qué la tarjeta seguía devolviendo todos ceros durante cada solicitud de bloque. Claramente, las funciones de la tarjeta y el software pueden leer de ella porque ya lo han hecho antes. Pero aun así, era hora de cablear las cosas y echar un vistazo con el analizador lógico. Un poco de microsoldadura, sujetando los cables de 30awg con epoxi de curado UV, y conectando mi Saleae, y tenemos algo que funciona.

Usé este analizador para analizar los datos. Primero lo probé sin tarjeta insertada.

Durante los primeros comandos, la frecuencia de reloj es de 120 kHz. Obviamente, la tarjeta no responde (no está ahí).``` CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND ... CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND
Cuando la tarjeta está realmente insertada, la frecuencia salta a unos 6 MHz tras la configuración.

Después de superar algunos bloqueos por usar el modo SD (consejo: incluso para esta tarjeta SD deberías usar el modo MMC) y pude verificar que la tarjeta estaba proporcionando datos razonables. Lo dejé reposar después de esto porque redoblé mis esfuerzos para entender el manejador de arranque de la tarjeta SD y me di cuenta de que los datos correctos *sí* se estaban leyendo, y eran los mismos datos que había obtenido haciendo `dd` manualmente a la tarjeta. Vaya, fue un desvío divertido y me ayudó a sentirme seguro de que la tarjeta funcionaba.
### Encontrando el problema
Los datos provienen de la dirección `0x4030c928` (una variable de pila, array de 512 bytes) tras ejecutar la rama en `0x25c2e` para la dirección `0x0000` y el dispositivo `8` (ver datos estáticos en `0x4030d00c`). Al entrar en el método que he llamado `MBR_detection`, el programa comprueba los bytes mágicos `0xaa55`. Primero carga los dos segundos bytes `0xaa`, luego el primer `0xaa`.```
r0 = data[0x1ff]
r1 = data[0x1fe]
orr r0,r1,r0,lsl #8
sub r1,r0,#0xaa00
subs r1,#0x55
bne <return FAIL>
Esto funciona. Sin embargo, la siguiente comprobación falla:``` r0 = data[0xc] => 0 r1 = data[0xb] => 0 orr r0,r1,r0, lsl #8 cmp r0,#0x200 bne
Pseudocódigo del descompilador:```c
if (
data[0x1fe] != 0x55aa ||
data[0xb] != 0x200 ||
(data[0xd] != 1 && // bit 0
data[0xd] != 2 && // bit 1
data[0xd] != 4 && // bit 2
data[0xd] != 8 && // bit 3
data[0xd] != 0x10 && // bit 4
data[0xd] != 0x20 && // bit 5
data[0xd] != 0x40 && // bit 6
data[0xd] != 0x80) // bit 7
)
{
return 1;
}
Esto comprueba si el byte en 0xb = 11 es igual a 0x200, y verifica si el byte en 0xd es igual a un valor de un solo bit. Ambas condiciones deben cumplirse, o devuelve un fallo.
Después de que la función de detección devuelve un 1, el manejador de arranque intenta a continuación leerlo como un MBR y carga el desplazamiento de la primera partición para tratar de ver si esa es una partición de arranque. Aquí está el procedimiento:```C
// Call block read function
// mmc_block_read_something(boot_device *dev,blk_read_struct blk)
ret = ((code *)blockread_struct->block_read_func)(blockread_struct->device_ptr,&block_read_info);
if (ret != 0) {
return 1;
}
// Check if device doesn't use MBR
ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct);
if (ret != 0) {
// It uses MBR, try each partition in the partition entries for a bootable
// partition
ret = MBR_check_entries(block_data,blockread_struct);
if (ret != 0) {
return 1;
}
ret = MBR_parse_entries(block_data,&blockread_struct->part_entry);
if (ret != 0) {
return 1;
}
// Get bootable partition offset
block_read_info = (blk_read_struct *)(blockread_struct->part_entry).first_sect_pos;
uStack_220 = 1;
pbStack_21c = block_data;
// Call block read function
// mmc_block_read_something(boot_device *dev,blk_read_struct blk)
ret = ((code *)blockread_struct->block_read_func)
(blockread_struct->device_ptr,&block_read_info);
if (ret != 0) {
return 1;
}
// Try and verify bootable partition again
ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct);
if (ret != 0) {
return 1;
}
}
Entonces ahora salto al punto donde lee los datos en `0x800` y obtengo el volcado correcto. Pero el método de detección del MBR todavía devuelve 1, incluso con los datos correctos (y hay unas cuantas capas de indirección que tuve que seguir, grrr), así que ahí debe estar el problema.
Ya en la recta final. La primera lectura de memoria desde la tarjeta SD es el MBR, que tiene hasta cuatro entradas de tabla de particiones; ver Tablas TRM 26-20, 26-21. La entrada de la tabla de particiones para la partición de arranque dice que la partición contiene `0x40000` sectores. Pero el sistema de archivos de la partición (ver Tabla TRM 26-23) dice que solo hay `0x3fff8`, por alguna razón, y la ROM de arranque lo detecta y falla.
Como experimento, me salté el salto que causaba problemas (lo que podría salir mal... crucemos los dedos...) y el programa ciertamente continuó, aunque no estoy seguro de dónde acabé, parece basura. Pero ignorando eso, ¡el dispositivo realmente arranca! Al poner un punto de interrupción en `0x402f0400` (inicio de la imagen cargada), todo va correctamente. ¿Quizás es hora de conectar la UART? ¡La UART es buena!
¿En cuanto al problema de la tarjeta SD? Hice una [pregunta en Unix SE](https://unix.stackexchange.com/questions/781715/why-do-the-mbr-partition-entry-and-partition-filesystem-disagree-on-the-number-o/781755), pero no obtuve mucha ayuda con el problema en cuestión (aunque de todos modos obtuve buena información). A partir de ahí: ¡por fin lo arreglé! Al revisar los comandos `mkfs` para construir el sistema de archivos FAT16, noté que [otra referencia](https://blog.billvanleeuwen.ca/porting-u-boot-onto-the-beaglebone) usa la opción -a, que desactiva la alineación. Esta fue la clave. Al añadir esa opción y recompilar, los recuentos de sectores coinciden (`0x40000`) y el sistema arranca. Supongo que la ROM de arranque no soporta ese tipo de alineación.
Ahora recibo los mensajes de abajo en un bucle de arranque, con la tarjeta insertada. ¡Hurra! Solo necesito averiguar por qué el kernel no arranca, ¡y entonces estaremos listos! Todo ese trabajo podría finalmente dar sus frutos con unas cuantas placas beaglebone utilizables. ¿Vale la pena? Quién puede juzgarlo.```
U-Boot SPL 2021.01-00001-gc59bf25a382-dirty (Jul 24 2024 - 20:38:49 -0400)
Trying to boot from MMC1
U-Boot 2021.01-00001-gc59bf25a382-dirty (Jul 28 2024 - 20:36:46 -0400)
CPU : AM335X-GP rev 2.1
Model: TI AM335x BeagleBone Black
DRAM: 512 MiB
WDT: Started with servicing (60s timeout)
NAND: 0 MiB
MMC: OMAP SD/MMC: 0, OMAP SD/MMC: 1
Loading Environment from FAT... *** Warning - bad CRC, using default environment
<ethaddr> not set. Validating first E-fuse MAC
Net: eth2: ethernet@4a100000, eth3: usb_ether
Hit any key to stop autoboot: 2 <0x08><0x08><0x08> 1 <0x08><0x08><0x08> 0
WARNING: Could not determine device tree to use
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
Failed to load 'boot.scr'
Failed to load 'uEnv.txt'
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
<0x1b>7<0x1b>[r<0x1b>[999;999H<0x1b>[6n<0x1b>8Scanning disk [email protected]...
Scanning disk [email protected]...
** Unrecognized filesystem type **
Found 4 disks
No EFI system partition
BootOrder not defined
EFI boot manager: Cannot load any image
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
4997632 bytes read in 353 ms (13.5 MiB/s)
Failed to load '/boot/undefined'
Starting kernel ...
Entonces hay un problema con el árbol de dispositivos ("WARNING: Could not determine device tree to use"). Esta es mi primera experiencia con el kernel y el arranque del kernel, así que no tengo idea de qué significa eso.
"Arrancando el kernel" implica lo siguiente, según lo entiendo:
Un aspecto importante del proceso de arranque del kernel es el árbol de dispositivos. Este se almacena en el archivo .dtb (binario de árbol de dispositivos; compárese con los archivos fuente de árbol de dispositivos .dts) para la placa.
Ahora mi problema es que U-Boot no está cargando el árbol de dispositivos de la placa, ya que no hay ningún mensaje reading /am335x-boneblack.dtb en el registro. En su lugar, obtenemos WARNING: Could not determine device tree to use. ¡Así que esa es una buena evidencia! Supongo que se debe a la falta del ID de placa EEPROM.
Algunos detalles sobre cómo obtiene la placa se encuentran en este hilo en los foros de TI.
Entonces, ¿cómo sabe U-Boot cómo configurarse y arrancar correctamente? Dentro del código fuente de U-Boot que compilamos, hay una carpeta llamada configs/ que almacena archivos defconfig para varias placas diferentes. Estos archivos definen los diversos parámetros de configuración de U-Boot, incluido el comando de arranque, que podría tener este aspecto:```
if test ${boot_fit} -eq 1;
then run update_to_fit;
fi;
run findfdt;
run init_console;
run envboot;
run finduuid;
run distro_bootcmd
Definimos qué config usar cuando ejecutamos el objetivo `make <boardname>_config`. La función `findfdt` se utiliza para identificar la placa que estamos ejecutando y configura el árbol de dispositivos correctamente. Se ve así (definido en `am335x_evm.h`):```
"findfdt="\
"if test $board_name = A335BONE; then " \
"setenv fdtfile am335x-bone.dtb; fi; " \
"if test $board_name = A335BNLT; then " \
"setenv fdtfile am335x-boneblack.dtb; fi; " \
"if test $board_name = A335PBGL; then " \
"setenv fdtfile am335x-pocketbeagle.dtb; fi; " \
"if test $board_name = BBBW; then " \
"setenv fdtfile am335x-boneblack-wireless.dtb; fi; " \
"if test $board_name = BBG1; then " \
"setenv fdtfile am335x-bonegreen.dtb; fi; " \
"if test $board_name = BBGW; then " \
"setenv fdtfile am335x-bonegreen-wireless.dtb; fi; " \
"if test $board_name = BBBL; then " \
"setenv fdtfile am335x-boneblue.dtb; fi; " \
"if test $board_name = BBEN; then " \
"setenv fdtfile am335x-sancloud-bbe.dtb; fi; " \
"if test $board_name = A33515BB; then " \
"setenv fdtfile am335x-evm.dtb; fi; " \
"if test $board_name = A335X_SK; then " \
"setenv fdtfile am335x-evmsk.dtb; fi; " \
"if test $board_name = A335_ICE && test $ice_mii = rmii; then " \
"setenv fdtfile am335x-icev2.dtb; fi; " \
"if test $board_name = A335_ICE && test $ice_mii = mii; then " \
"setenv fdtfile am335x-icev2-prueth.dtb; fi; " \
"if test $fdtfile = undefined; then " \
"echo WARNING: Could not determine device tree to use; fi; \0" \
Si queremos el comportamiento predeterminado, podemos simplemente cambiar la variable board_name, ¿verdad? Bueno, quizás no, o al menos no sé cuál es el mejor lugar para cambiarla. Pero aunque establecer board_name no funcionó como tal, lo cierto es que también actualicé el archivo .dtb predeterminado y ¡eso funcionó!```
_____ _____ _ _
| _ |___ ___ ___ ___ | _ |___ ___ ||__ | |
| | _| .'| . | . | | | | . | | | -| _| _|
|||| |__,| || || || ||| ||||
|| |___|
Arago Project http://arago-project.org am335x-evm ttyS0
Arago 2021.09 am335x-evm ttyS0
am335x-evm login: root
root@am335x-evm:~#
¡Por fin estamos en una terminal! ¡Mis placas de desecho están vivas!
### Corrigiendo el ID de EEPROM faltante
Como último paso, escribiré el ID de placa correcto en la EEPROM, lo cual es fácil de hacer desde el espacio de usuario de Linux. De la fuente de SPL, los diversos ID de placa son:
- `A335BONE` - placa Beaglebone
- `A335BNLT` - placa Beaglebone Black
- `A335PBGL`
- `A335X_SK`
- `A33515BB`
- `A335_ICE`
También hay una revisión de placa opcional. Según el esquemático, la EEPROM está en I2C0, y el chip en sí (24LC32A en mi esquemático, aunque está etiquetado como '256Kx8) proporciona la dirección de dispositivo I2C `0x50` (binario `b1010` seguido de la dirección de chip `000`, ya que el encapsulado de 5 pines no tiene pines de dirección adicionales). Un último detalle: el pin WP está conectado a HIGH con una resistencia de pull-up de 10k, por lo que la protección contra escritura está habilitada por defecto; debe conectarse a LOW antes de que pueda realizarse cualquier escritura, o de lo contrario reconocerá (ACK) pero no escribirá nada.
Se puede acceder a la EEPROM a través del kernel en `/sys/bus/i2c/devices/0-0050`, dentro del cual hay un archivo llamado `eeprom`. Por lo tanto, con el pin WP conectado a LOW (conecta TP4 en la parte superior cerca del conector de CC a tierra), bastan unas pocas llamadas a `echo`. El Beaglebone Black System Reference Manual tiene el formato. Adapté esto [de aquí](https://groups.google.com/g/beagleboard/c/di5O5JCl4yw).```sh
root@am335x-evm:~# cat fix_eeprom.sh
#!/bin/bash
# Fix board ID EEPROM
EEPROM_FILE=/tmp/eeprom.tmp
EEPROM=/sys/bus/i2c/devices/0-0050/eeprom
# header bytes
echo -ne "\xaa\x55\x33\xee" > ${EEPROM_FILE}
# Board ID
echo -n "A335BNLT" >> ${EEPROM_FILE}
# serial number (I left this basically as the template)
echo -n "000C24wwBBoxxxx" >> ${EEPROM_FILE}
dd if=${EEPROM_FILE} of=${EEPROM}
Usando less para confirmar, y no deberíamos tener ningún problema para usar una tarjeta SD estándar de ahora en adelante. Los cambios en el SPL y la configuración de U-Boot se pueden revertir, una vez que todas las placas tengan sus EEPROM escritas.
| Región | Dirección de inicio | Longitud |
|---|
| Boot ROM (Public) | 0x4002_0000 | 0xBFFF |
| Boot ROM (Public, alias) | 0x0002_0000 | 0xBFFF |
| SRAM interna | 0x402F_0400 | 0xFC00 |
| L3 OCM0 | 0x4030_0000 | 0x10000 |
| Punto de prueba | Conexión | Hoja del esquemático | Lado de la placa |
|---|
| TP1 | DGND | 2 (D1) | Superior |
| TP2 | VDD_MPUON (VDD_MPU_MON) | 5 (C4) | Superior |
| TP3 | TESTOUT | 5 (B2) | Superior |
| TP4 | Board ID WP | 11 (B1) | Superior |