
Engenharia reversa da ROM de inicialização do TI AM3358
Já faz provavelmente dezoito meses desde que coloquei as mãos em algumas placas Beaglebone Black, salvas do lixo. Infelizmente, as placas não funcionaram de imediato. Bem, esta era a primeira vez que trabalhava com uma dessas placas, ou com qualquer computador de placa única, então não sabia se o problema era algo que eu estava fazendo ou algo errado com as próprias placas (talvez por isso estivessem indo para o lixo em primeiro lugar). Levei bastante tempo e um grande esforço para fazer essas placas realmente começarem a inicializar, mas, caramba, consegui. E aqui está o que aprendi ao longo do caminho.
Incluí alguns utilitários neste repositório, juntamente com um arquivo XML exportado do Ghidra que contém todos os símbolos que obtive até agora com a engenharia reversa. Usei este post para exportar sem o firmware real, evitando quaisquer problemas de direitos autorais, por precaução. Se você quiser depurar a boot ROM por conta própria, já terá o JTAG conectado, então poderá despejar a boot ROM (de 0x20000 a 0x2BFFF) você mesmo.
Para carregar os símbolos:
0x20000; você também pode definir o nome do bloco como bootrom.main(), ou para o handler de boot do cartão MMC/SD.Para começar, eu sabia que estas eram versões personalizadas do Beaglebone Black padrão, então desde cedo determinei que poderia ser algo faltando na própria placa, como um identificador de placa. O que eu via ao inicializar um cartão SD padrão formatado com balenaEtcher era simplesmente nada. Eu esperava que os LEDs da placa começassem a piscar e que conectar um cabo UART para USB me permitisse ver o processo do U-Boot. No entanto, a UART permanecia muda. Se eu removesse o cartão SD, ela emitia a letra C repetidamente, o que é um comportamento esperado para um boot UART/serial. Ela definitivamente estava tentando inicializar, e o cartão SD estava alterando esse comportamento, mas eu não tinha mais visibilidade. A maior parte da solução de problemas na web usava a saída do U-Boot como ponto de partida para diagnosticar problemas. Acho que eu não teria esse luxo.
Nesse ponto, imaginei que valeria a pena conectar uma sonda de depuração. Infelizmente, não tinha um conector compatível com o footprint existente, então fiz o meu próprio.
A placa Beaglebone tem um conector com designação P2 que expõe as conexões JTAG. Conectei alguns fios deste conector a um conector fêmea para poder me comunicar com ela por meio do meu J-Link.



Ao iniciar o Ozone (o depurador da Segger), configurei o J-Link e comecei apenas tentando encontrar o ponto de entrada. Eu pensava que um reset-halt me colocaria onde eu precisava, e foi assim que cheguei à suposição (incorreta) de que o ponto de entrada era 0x2148a, embora certamente tenha notado que isso não era consistente. Mais tarde, percebi que as placas AM335x não se dão muito bem com o reset-halt do J-Link, então havia na verdade um atraso de talvez algumas centenas de ciclos de clock, fazendo-me parar em algum lugar dentro de um handler de boot, de forma indeterminística. (Acabei contornando isso escrevendo um arquivo GEL para o Code Composer Studio da TI, que suporta depuração via J-Link — no reset, o registrador PC é definido para o handler de reset, os registradores são limpos e o modo de instrução é forçado para ARM.)
Em um tópico nos fóruns da TI (AM335x: Funcionários da TI, onde posso obter o código-fonte/símbolos do ROM Bootloader?) peguei alguns símbolos de depuração: SPI Initialize em 0x231e0, SPI ReadSectors em 0x23230, e 0x24bfa é uma rotina que executa uma leitura UART. Isso é uma boa ajuda, eu acho. Notei que o boot falhava ao cair em um loop infinito em 0x402f0440, um loop morto. Hmm, bem longe do restante da boot ROM, deve ser em RAM ou algo assim. Provavelmente é hora de ir para o manual de referência técnica (TRM)!
O Capítulo 26 do TRM contém uma tonelada de informações sobre boot. Temos a seguinte visão da boot ROM:

Descrição:
A arquitetura do Public ROM Code é mostrada na Figura 26-1. Ela é dividida em três camadas principais, em uma abordagem de cima para baixo: alto nível, drivers e camada de abstração de hardware (HAL). Uma camada se comunica com uma camada de nível inferior por meio de uma interface unificada. A camada de alto nível é responsável pelas principais tarefas do Public ROM Code: configuração do watchdog e dos clocks e a rotina principal de boot. A camada de drivers implementa os protocolos lógicos e de comunicação para qualquer dispositivo de boot, de acordo com a especificação da interface. Finalmente, a HAL implementa o código de nível mais baixo para interagir com os IPs da infraestrutura de hardware. Os dispositivos de boot finais são conectados aos pads de I/O do dispositivo.

A Figura 26-2 ilustra o fluxo de alto nível do procedimento de boot do Public ROM Code. Neste dispositivo, o Public ROM Code começa após a conclusão da inicialização segura (executada pelo Secure ROM Code). O ROM Code então realiza a configuração e inicialização da plataforma como parte do procedimento público de inicialização. A lista de dispositivos de boot é criada com base nos pinos SYSBOOT. Um dispositivo de boot pode ser um dispositivo de boot por memória (memória flash soldada ou um dispositivo de boot temporário, como um cartão de memória) ou uma interface periférica conectada a um host. O loop principal do procedimento de boot percorre a lista de dispositivos de boot e tenta procurar uma imagem no dispositivo de boot atualmente selecionado. Esse loop é encerrado se uma imagem de boot válida for encontrada e executada com sucesso, ou quando o watchdog expira. O procedimento de autenticação da imagem é realizado antes da execução da imagem em um dispositivo HS. A falha no procedimento de autenticação leva a um desvio para um "loop morto" na ROM Segura (aguardando um reset do watchdog).
Mapa de memória! Vetores de exceção! Fluxogramas! Muita informação nesta seção. Meu trabalho ficou muito mais fácil.
Nesse ponto, usei a sonda JTAG para baixar o firmware em alguns arquivos diferentes e comecei a carregar as coisas no Ghidra. Não parecia haver arquivos SVD ou outros mapeamentos de registradores disponíveis em um formato conveniente, o que é realmente lamentável, porque isso significa que eu precisava definir as regiões de memória, os registradores e tudo mais manualmente. Foi um processo tedioso, mas depois de um tempo eu tinha um script Python que podia usar para carregar símbolos no Ghidra para o AM3358. Menos uma coisa com que me preocupar!
Parece que os arquivos que tenho podem ser mapeados assim:
É interessante que o loop infinito em 0x402f_0440 esteja no topo da "imagem baixada" na SRAM interna, enquanto os vetores de exceção estão armazenados em outro lugar. Talvez isso seja uma pista importante mais adiante...
No reset, a boot ROM privada lida com as questões de segurança e desvia para 0x2 0000, que contém os vetores de reset. A primeira instrução é um desvio para 0x2 08d0, que deve ser o ponto de entrada. Não é uma instrução BX, então, presumivelmente, ainda estamos no modo ARM nesse ponto.
Este é o primeiro código executado, o que significa que não é exatamente uma "função" com parâmetros, mas sim um script de inicialização gerado por compilação. O primeiro bloco 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
Este bloco define o clock da RAM OCMC como habilitado:
1. Defina `CM_PER_OCMCRAM_CLKCTRL=0x2`
2. Verifique se o registrador foi definido; se não, continue fazendo polling
O registrador `CM_PER_OCMCRAM_CLKCTRL` usa os bits 0 e 1 para o campo `MODULEMODE`; definir `=0x2` habilita o clock para a RAM OCMC.
O próximo bloco básico:```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 bloco faz o seguinte:
(control_status & 0x700) >> 8 == 0x3, ignora se não forcontrol_status & 0x1f == 0x1f, se for, chama a função GPMC_init após carregar control_status em r6O próximo bloco configura o coprocessador:```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
Operações neste bloco:
1. Mova `11010011b` para o campo de controle do CPSR (`I=1`,`F=1`,`T=0`,`MODE=10011`)
1. `I` é a desabilitação de interrupção, `F` é a desabilitação de interrupção rápida (portanto, `I=F=1` significa que as interrupções estão desabilitadas)
2. `T` é o modo Thumb, definido como `0`
3. `MODE=10011` define o modo do processador para o 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. Veja [aqui](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 obter mais informações
2. Carregue o endereço do vetor de reset da ROM
3. Acesse o registrador de Extensões de Segurança `c12` do [coprocessador 15](https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers/Coprocessor-15) (coprocessador de controle do sistema) e carregue o vetor de reset da ROM no `VBAR` (registrador de endereço base do vetor)

4. Parecem saltos `nop`? Por que `bl` em vez de `b`?
5. Ative a previsão de desvio (defina o bit 11 do registrador de controle do sistema `SCTLR`)

*Registradores `c1` do CP15 (registradores de controle do sistema) na implementação VMSA*
Descrição do registrador `SCTLR`:
> O SCTLR fornece o controle de nível superior do sistema, incluindo seu sistema de memória.
> Este registrador faz parte do grupo funcional de registradores de controle de memória virtual.
Consulte a página B4-1687 do TRM. O bit 11 é o bit *branch prediction enable* (habilitação de previsão de desvio); defini-lo como habilitado significa que a [previsão de desvio](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/Common-Memory-System-Architecture-Features/Caches/Branch-predictors) está habilitada.
6. Chame uma função (por meio de um desvio para uma instrução de chamada)
### Função em `0x20894` (`__main`)
Esta função é alvo de um desvio de outra rotina inicial. Acredito que ela inicializa a pilha e, possivelmente, os temporizadores ou o watchdog, antes de chamar `FUN_0002889c` (que mais tarde se revela ser `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
As operações aqui são:
r0,r1,r2,r3,r4,lr para a pilha (pilha pública no mapa de memória)
load_stack_1 ramifica para uma função vazia bx lr, então retira r0,r1,r2,r3,r4,pc da pilha (basicamente coloca esses dados de volta nos registradores e coloca o que lr contém no pc para retornar)pc + 0x109 é ímpar; se for par, carregar 0x208bd em lr, caso contrário copiar pc para lrFUN_0002889cEsta deve ser a função __main() referida no fluxograma de boot:

Isso tornaria a próxima função a função main.
Conforme mostrado no topo da Figura 26-8, a CPU salta para o vetor de reset do Public ROM Code depois de concluir a inicialização segura de boot. Uma vez em modo público, ao iniciar o sistema, a CPU realiza a inicialização do lado público e a configuração da pilha (inicialização C gerada pelo compilador ou "scatter loading"). Então ela configura o watchdog timer 1 (definido para três minutos), realiza a configuração dos clocks do sistema. Finalmente, ela salta para a rotina de boot.
0x209b0)Quando main é chamada, o registrador SP aponta para 0x4030ce00. É aqui que a pilha começa, e ela cresce para baixo em direção a 0x4030 b800; e como estamos apontando para o endereço 0x4030 cdf0 após empurrar 4 registradores (uma diferença de 16 bytes ou 4 palavras), estamos usando uma pilha descendente completa, como no AAPCS. Ou seja, SP aponta para a palavra mais recente na pilha e cresce para baixo.
Aqui está a função 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; }
O mais interessante para meus propósitos é a função `run_booting_loop` em `0x20a10`.
### Notas sobre o X-Loader
Depois de percorrer a inicialização e chegar a esta parte com strings como "ISSW", "CHSETTINGS" e "X-LOADER", comecei a procurar em outros lugares onde essas strings poderiam aparecer em contextos relacionados ao U-Boot. Encontrei [este tópico](https://forum.xda-developers.com/t/discussion-on-the-boot-loader-cracked.1378886/) de pessoas fazendo engenharia reversa ou crackeando o firmware do Nook, e a [fonte do x-loader](https://github.com/joelagnel/x-loader/blob/f3c74bc9b01dac58e553393d6ec1041353f2f1f7/scripts/signGP.c) contém referências a coisas como `CHSETTINGS`. Pelo que pesquisei, "ISSW" [parece](https://github.com/u-boot/u-boot/blob/master/doc/README.ti-secure) se referir à inicialização a partir de dispositivos que não são de memória.
Lembre-se da documentação de inicialização, o código de alto nível:

De interesse:
- RNDIS
- FAR
- XMODEM
- BOOTP
- TFTP
- DFT
Talvez seja hora de tentar depuração ao vivo novamente. Tentar reverter todas aquelas structs provavelmente seria doloroso, considerando a grande quantidade de dados que não consigo entender...
Viva! A depuração ao vivo funciona ao definir o PC e o SP manualmente usando os vetores que encontrei:

A partir do código-fonte e dos vetores de rastreamento, consegui mapear as várias opções de inicialização e qual número de dispositivo é atribuído a cada uma. Isso mais tarde se tornou muito útil, pois tive que distinguir entre MMC0 (8) e MMCSD1 (9) ao definir breakpoints no manipulador de inicialização SD/MMC.
| Tipo | Dispositivo | ID do dispositivo |
| ---------- | ----------------- | ----------- |
| Memória | XIP (MUX2) | 1 |
| Memória | XIP w/WAIT (MUX2) | 2 |
| Memória | XIP (MUX1) | 3 |
| Memória | XIP w/WAIT (MUX1) | 4 |
| Memória | NAND | 5 |
| Memória | MMCSD1 | 7, 9 (eMMC) |
| Memória | NAND_I2C | 10 |
| Memória | MMC0 | 8, 12 (SD) |
| Periférico | UART0 | 16 |
| Periférico | USB | 20 |
| Periférico | GPGMAC0 | 22 |
Para informações sobre como o processador inicializa, consulte o TRM e [esta resposta no Stack Exchange](https://stackoverflow.com/a/31252989/8565545). Em resumo:
1. A ROM de boot identificou o arquivo MLO (Mmc LOader) no cartão SD e o copiou para a SRAM
2. Este é o carregador de programa secundário, um bootloader menor que inicializa a RAM completa e copia todo o binário do U-Boot para lá, a fim de executá-lo
3. Depois que o binário do U-Boot é executado, nós (ou melhor, o U-Boot) finalmente inicializamos o kernel
### `run_booting_loop()`
Este é o loop de boot principal. Ele roda infinitamente, ou até a execução desviar para outro bootloader que seria carregado na RAM.
Início do procedimento, sem atualizações de vetor de rastreamento:
- Consulta o tipo de dispositivo
- Se o tipo de dispositivo for 5 (dispositivo seguro), faz alguma outra inicialização
- Executa `build_boot_list(int,buffer[],data[],int)`
- `buffer[]` é inicializado com `0xff` e `data[]` inclui o tipo de dispositivo (provavelmente)```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 );
}
Descobri que havia uma função em 0x23d7a que chamei de boot_into_SRAM() e que era a última função chamada antes de saltar para a SRAM e, a partir daí, ir para o handler de exceção. Anteriormente, eu tinha um estado diferente da RAM salvo, mas ao depurar ao vivo mais uma vez (agora mais de um ano depois, julho de 2024), percebi o que devia estar acontecendo. A placa estava lendo dados do cartão SD com sucesso e estava executando código que havia carregado do cartão! Para verificar isso, tive que encontrar bytes na SRAM que fossem iguais aos que estavam no cartão SD. Acontece que, dentro de am335x-evm-linux-sdk-bin-.../board-support/prebuilt-images/, existe um arquivo binário chamado u-boot-spl.bin-am335x-evm, e o código nesse binário corresponde ao que aparece na SRAM. Chegamos ao uboot SPL!
Conseguimos inicializar na SRAM com sucesso; agora estou pensando no terminal UART, que deveria mostrar informações sobre o uboot. A conexão de hardware está abaixo.

Conectando o dispositivo com o CuteCom, 115200 @ 8-N-1, sem um cartão SD inserido, ele simplesmente exibe C repetidamente.

Mas quando o cartão SD está inserido, o UART não exibe nada. Sem mensagens, sem caracteres. A falha deve estar ocorrendo cedo demais no processo de boot? Mas agora que também sei qual código ele está executando (e tenho o código-fonte), devo ser capaz de criar alguns símbolos de depuração para ele e iniciar uma sessão de depuração adequada. Isso pode não ser trivial; preciso garantir que estou compilando o código da mesma forma, e pode levar algum tempo para aprender exatamente o que o SDK carregou no meu cartão SD e como compilá-lo.
Como o U-Boot estaria exibindo texto no UART, e não estou vendo nada, imagino que estejamos capturando uma exceção em algum lugar do SPL.
Nesse ponto, passei muitas e muitas horas fazendo engenharia reversa e limpando o código-fonte decompilado da ROM de boot no Ghidra, analisando structs e como cada membro de dados era usado entre funções, às vezes aninhados, causando todo tipo de estrago para mim. Enquanto isso estava em segundo plano, percebi que também era hora de começar a depurar e compilar meu próprio código. Afinal, estamos na RAM; por que não carregar os símbolos do SPL e ver o que está acontecendo?
Você pode depurar com o Ozone, o depurador da Segger para J-Link. Você também pode usar o Code Composer Studio (CCS) da própria TI, ou talvez também sua versão "leve" estilo VSCode, o CCS Theia. Consegui compilar tudo no SDK seguindo este vídeo: Sitara Linux Board Porting Series: Module 6. Há três componentes para compilar:
Segui o vídeo do Module 7 da série acima e consegui fazer as coisas funcionarem, com algumas observações:
s_init() não está mais presenteCom símbolos? Lindo. Agora posso ver o que está acontecendo na execução, começando pelo handler de reset reset(), e posso ver onde chegamos com nossa exceção. Para rastrear, defini um breakpoint no handler de exceção em 0x402f 0440 e verifiquei o link register, que ainda armazenava o endereço da função mais recente. Descobriu-se que era o endereço 0x402f 76ce, embora não pareça consistente. O que está causando o erro?
Nota: Para depuração, seguindo o vídeo do Module 7, execute até 0x402f 0400, então faça a parte Load Memory(). Isso deve ser feito a cada reinicialização.
Entramos na função device_probe() (0x402f 74c4), depois em algumas outras funções? Então, de do_setup_dpll() com ramificação em 0x402f 07fc, não saímos, então vamos continuar dentro dela. Avançando passo a passo, voltamos para _main() em crt0.S, localizado em 0x402f 14e0. Parece que podemos estar saindo de board_init_f() e seguindo para spl_relocate_stack_gd(). Essa chamada não retorna. Chegamos a dm_fixup_for_gd_move(). Ela contém uma instrução que falha, em 0x402f 76c2. Acho que é isto: está tentando acessar 0x81ff ff20. Sem sucesso, aparentemente. Tenho o palpite de que há um problema com a configuração da SDRAM. Rastreei todos os tópicos nos fóruns da TI relacionados a problemas semelhantes e encontrei meia dúzia que continham algumas dicas para me ajudar. Concluí que provavelmente estava relacionado a (a) ajuste de EMIF ou (b) nivelamento por software.
Minha placa é a mostrada abaixo.

A memória é da Micron, enquanto o esquemático do BeagleBone Black que tenho (rev C3) usa memória DDR3 da Kingston, especificamente a D2516EC4BXGGB. A DDR3 é a U12; podemos usar a página de decodificação de marcações da Micron para identificar o componente:
Vamos garantir que o componente esteja vivo; comecei simplesmente verificando se a alimentação estava presente. O datasheet especifica que deve ser 1.5V +/- 0.075V. Meço 1.506V em R6 na parte inferior da placa. Temos dois pontos de teste, TP1 e TP2.
Caso seja útil algum dia, aqui estão alguns dos pontos de teste.
O circuito da memória é descrito em detalhes na página de design de hardware.
Vamos verificar a linha de clock enable. Podemos verificar os dois lados de R96; um lado deve estar aterrado e o outro lado deve estar em nível alto.
Confirmado: 1.5V em CKE.
O próximo passo é verificar o sinal de clock. Fiz o que pude aqui, usando o tinySA com a antena conectada apontando aproximadamente na direção do chip de RAM. Fazendo esse tipo de "farejamento", sinto-me bastante seguro de que o clock está presente, pelo menos por enquanto.
Seguindo em frente, é hora de analisar a interface com a memória externa. Algo que aparece muito é o conceito de arquivo GEL. É uma linguagem interpretada desenvolvida pela Texas Instruments para o Code Composer Studio, e a sigla significa General Extension Language.
Um arquivo GEL está incluído na ferramenta de configuração de memória DDR.
Certo! Segui o procedimento de ajuste (da melhor forma que pude) e consegui encontrar valores ideais para o arquivo 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
Tentando ajustar coisas de RAM no Memory Browser... Eba! Funciona!
Então a memória certamente parece estar funcionando, mas o SPL ainda está falhando, então talvez haja algo acontecendo aqui em relação à forma como o SPL está tentando inicializar a SDRAM? Ah, certo, há mais no procedimento de ajuste, é claro! Você precisa realmente atualizar o SPL...
Estamos chegando mais perto agora. O arquivo `board.c` inicializa a DDR verificando que tipo de placa temos. Mas para esta placa, todas as funções (`board_is_evm_sk()`, `board_is_icev2()`, `board_is_bone_lt()`, etc) retornam false, então o padrão é `config_ddr(266, ...)`, onde 266 é a frequência do clock em MHz, e *deveria* ser 400 MHz. Isso certamente seria um problema.
`board_is_bone_lt` deve ser desviado para sempre retornar true. Eu fiz isso e avanço um pouco mais, mas algo está me incomodando. Carregar o novo arquivo MLO no cartão SD não funciona, mesmo que carregar o programa diretamente funcione perfeitamente. O que está acontecendo aqui? Posso dizer que o código que está sendo carregado na SRAM não é o mesmo código que compilei. Na verdade, cheguei a formatar o cartão SD, e um SPL padrão parece ser carregado na sram mesmo assim! Verifiquei que o processo de boot não tenta continuar quando se usa outro cartão SD. Portanto, o bootloader definitivamente procura a partição de boot no cartão SD, depois move a execução para a SRAM, mas ainda não copiou os dados? Ugh. De onde isso está vindo??
Esse problema me causou nada menos que uma boa dose de problemas. Apaguei todas as partições, zerei o MBR, zerei a partição de boot e tentei cartões SD diferentes, e somente o meu cartão ainda conseguia dar boot, então tinha que haver *alguns* dados inicializáveis ainda nele, armazenados em algum lugar. Finalmente, consegui pôr fim à loucura zerando o cartão *inteiro*.
Aprendi também, nesse ponto, sobre os **tracing vectors** que você pode acessar ao solucionar problemas da boot ROM. Isso também viria a ser extremamente útil para engenharia reversa, já que eu sabia de onde vinham todas as chamadas de rastreamento e podia então atribuir nomes de funções etc. com base nessas chamadas. Criei uma planilha para interpretar esses tracing vectors e a usei para entender rapidamente como o procedimento de boot mudava à medida que eu alterava os parâmetros do cartão. De fato, ele afirmava encontrar o CHSETTINGS repetidamente enquanto eu tentava formatar e reformatar o cartão SD, antes de eu, desesperadamente, zerar a coisa toda.
Para tentar ler o cartão da mesma forma que o processador TI faria, você pode usar `dd`. Use block size=512 e especifique o primeiro setor (tente usar o GParted para verificar qual é) pulando os primeiros `n`. Exemplo: o primeiro setor é 2048, o dispositivo é `sda`, vamos ler apenas o primeiro setor:```
sudo dd if=/dev/sda1 of=/home/sam/sector2048 bs=512 skip=2048 count=1
Usei isso para baixar imagens do MBR e do início da partição de boot diretamente do cartão SD, ambos seriam úteis mais tarde.
Meus esforços de engenharia reversa no Ghidra me levaram às funções do manipulador de boot do cartão SD, e agora eu podia ver funções enviando comandos SD para o cartão, e podia percorrer o código passo a passo e ver o que o cartão estava respondendo. Eu pensava que estava olhando no lugar certo e que o cartão estava retornando todos os zeros. Mais tarde, descobri que possivelmente estava percorrendo o manipulador eMMC (o mesmo manipulador, mas com um ID de dispositivo diferente), ou algo mais estava errado, porque não havia nada de errado com o cartão SD. Mesmo assim, decidi que era hora de aprender como esses cartões funcionam.
Eu estava curioso para ver por que o cartão continuava retornando todos os zeros durante cada solicitação de bloco. Claramente, o cartão funciona e o software consegue ler dele, pois isso já foi feito no passado. Mas ainda assim, era hora de conectar tudo e dar uma olhada com o analisador lógico. Um pouco de microsoldagem, segurando os fios 30awg com epóxi de cura UV, e conectando meu Saleae, e temos algo que funciona.

Usei este analisador para analisar os dados. Primeiro, tentei sem cartão inserido.

Para os primeiros comandos, a taxa de clock é de 120 kHz. Obviamente, o cartão não responde (ele não está lá).``` 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
Quando o cartão é realmente inserido, a frequência salta para cerca de 6 MHz após a configuração.

Depois de superar algumas falhas ao usar o modo SD (dica: mesmo para este cartão SD, você deve usar o modo MMC) e de verificar que o cartão estava fornecendo dados razoáveis, deixei isso de lado, pois redobrei meus esforços para entender o manipulador de inicialização do cartão SD e percebi que os dados corretos *estavam* sendo lidos, e eram os mesmos dados que eu havia obtido ao fazer `dd` manualmente no cartão! Bem, foi um desvio divertido e me ajudou a ficar confiante de que o cartão estava funcionando.
### Encontrando o Problema
Os dados vêm do endereço `0x4030c928` (uma variável de pilha, vetor de 512 bytes) após executar o branch em `0x25c2e` para o endereço `0x0000` e dispositivo `8` (veja os dados estáticos em `0x4030d00c`). Entrando no método que chamei de `MBR_detection`, o programa verifica os bytes mágicos `0xaa55`. Primeiro ele carrega os segundos dois bytes `0xaa`, depois o primeiro `0xaa`.```
r0 = data[0x1ff]
r1 = data[0x1fe]
orr r0,r1,r0,lsl #8
sub r1,r0,#0xaa00
subs r1,#0x55
bne <return FAIL>
Isto é bem-sucedido. A próxima verificação falha, no entanto:``` r0 = data[0xc] => 0 r1 = data[0xb] => 0 orr r0,r1,r0, lsl #8 cmp r0,#0x200 bne
Pseudocódigo do decompilador:```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;
}
This checks if the byte at 0xb = 11 is equal to 0x200, and checks whether the byte at 0xd is equal to a single-bit value. Both conditions must be met, or it returns a failure.
After the detection function returns a 1, the boot handler next tries to read it as an MBR and loads up the offset of the first partition to try and see if that is a bootable partition. Here's the procedure:```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;
}
}
Então, agora eu pulo para o ponto em que ele lê os dados em `0x800` e obtenho o dump correto. Mas o método de detecção do MBR ainda retorna 1, mesmo com os dados certos (e há algumas camadas de indireção ali que tive de seguir, grrr), então deve ser aí que está o problema.
Agora é a reta final. A primeira leitura de memória do cartão SD é o MBR, que possui até quatro entradas de tabela de partições; veja as Tabelas 26-20, 26-21 do TRM. A entrada da tabela de partições para a partição de boot diz que a partição contém `0x40000` setores. Mas o sistema de arquivos da partição (veja a Tabela 26-23 do TRM) diz que há apenas `0x3fff8`, por algum motivo, e a boot ROM detecta isso e falha.
Como experimento, pulei o desvio que estava causando problemas (o que pode acabar mal... cruzando os dedos...) e o programa certamente continuou, embora eu não tenha certeza de onde fui parar; parece lixo. Mas, ignorando isso, o dispositivo realmente inicializa! Definindo um breakpoint em `0x402f0400` (início da imagem carregada) e tudo está indo corretamente. Talvez seja hora de conectar a UART? A UART é ótima!
E quanto ao problema do cartão SD? Fiz uma [pergunta no Unix SE](https://unix.stackexchange.com/questions/781715/why-do-the-mbr-partition-entry-and-partition-filesystem-disagree-on-the-number-o/781755), mas não obtive muita ajuda no problema em questão (embora tenha conseguido boas informações mesmo assim). A partir daí: finalmente corrigi! Revisando os comandos `mkfs` para construir o sistema de arquivos FAT16, notei que [outra referência](https://blog.billvanleeuwen.ca/porting-u-boot-onto-the-beaglebone) usa a flag -a, que desativa o alinhamento. Essa foi a chave. Adicionando essa flag e recompilando, as contagens de setores concordam (`0x40000`) e o sistema inicializa. Acho que a boot ROM não suporta esse tipo de alinhamento.
Agora recebo as mensagens abaixo em um loop de boot, com o cartão conectado. Viva! Só preciso descobrir por que o kernel não está iniciando, e então estaremos feitos! Todo esse trabalho pode finalmente valer a pena com algumas placas beaglebone utilizáveis. Valeu a pena? Quem pode julgar.```
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 ...
Então há um problema com a árvore de dispositivos ("WARNING: Could not determine device tree to use"). Esta é a minha primeira experiência com o kernel e a inicialização do kernel, então não faço ideia do que isso significa.
"Inicializar o kernel" envolve o seguinte, pelo que entendo:
Um aspecto importante do processo de inicialização do kernel é a árvore de dispositivos. Ela é armazenada no arquivo .dtb (binário da árvore de dispositivos; compare com os arquivos-fonte .dts da árvore de dispositivos) para a placa.
Meu problema agora é que o U-Boot não está carregando a árvore de dispositivos da placa, já que não há mensagem reading /am335x-boneblack.dtb no log. Em vez disso, obtemos WARNING: Could not determine device tree to use. Então isso é uma boa evidência! Acredito que seja devido ao ID de placa EEPROM ausente.
Alguns detalhes sobre como ele obtém a placa são encontrados em este tópico nos fóruns da TI.
Então, como o U-Boot sabe como se configurar e inicializar corretamente? Dentro do código-fonte do U-Boot que compilamos, há uma pasta chamada configs/ que armazena arquivos defconfig para diversas placas diferentes. Esses arquivos definem os vários parâmetros de configuração do U-Boot, incluindo o comando de inicialização, que pode ser assim:```
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 qual configuração usar quando executamos o alvo `make <boardname>_config`. A função `findfdt` é usada para identificar a placa que estamos usando e configura a device tree adequadamente. Ela se parece com isto (definida em `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" \
Se quisermos o comportamento padrão, podemos simplesmente alterar a variável board_name, certo? Bem, talvez não, ou pelo menos não sei qual é o melhor lugar para alterá-la. Mas embora definir board_name não tenha funcionado por si só, na verdade eu também atualizei o arquivo .dtb padrão e isso funcionou!```
_____ _____ _ _
| _ |___ ___ ___ ___ | _ |___ ___ ||__ | |
| | _| .'| . | . | | | | . | | | -| _| _|
|||| |__,| || || || ||| ||||
|| |___|
Arago Project http://arago-project.org am335x-evm ttyS0
Arago 2021.09 am335x-evm ttyS0
am335x-evm login: root
root@am335x-evm:~#
At long last, we're at a terminal. My junk boards are alive!
### Corrigindo o ID de EEPROM ausente
Como última etapa, gravarei o ID de placa correto na EEPROM, o que é fácil de fazer a partir do espaço de usuário do Linux. No código-fonte do SPL, os vários IDs de placa são:
- `A335BONE` - placa Beaglebone
- `A335BNLT` - placa Beaglebone Black
- `A335PBGL`
- `A335X_SK`
- `A33515BB`
- `A335_ICE`
E também há uma revisão de placa opcional. Com base no esquema elétrico, a EEPROM está no I2C0, e o próprio chip (24LC32A no meu esquema, embora esteja rotulado como '256Kx8) fornece o endereço de dispositivo I2C de `0x50` (binário `b1010` seguido de `000` de endereço do chip, já que o encapsulamento de 5 pinos não possui pinos de endereço adicionais). Um último ponto: o pino WP é puxado para HIGH com um pull-up de 10k, portanto a proteção contra gravação fica habilitada por padrão; ele precisa ser ligado a LOW antes que qualquer gravação possa ocorrer, caso contrário, ele reconhecerá, mas simplesmente não gravará nada.
A EEPROM pode ser acessada por meio do kernel em `/sys/bus/i2c/devices/0-0050`, dentro do qual há um arquivo chamado `eeprom`. Portanto, com o pino WP conectado a LOW (amarre o TP4 na parte superior, perto do conector DC, ao terra), bastam algumas chamadas de `echo`. O Manual de Referência do Sistema Beaglebone Black traz o formato. Adaptei isso [daqui](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, e não devemos ter problemas em executar um cartão SD padrão a partir de agora. As alterações no SPL e na configuração do U-Boot podem ser revertidas, assim que todas as placas tiverem seus EEPROMs gravados.
| Região | Endereço inicial | Tamanho |
|---|
| Boot ROM (Pública) | 0x4002_0000 | 0xBFFF |
| Boot ROM (Pública, alias) | 0x0002_0000 | 0xBFFF |
| SRAM Interna | 0x402F_0400 | 0xFC00 |
| L3 OCM0 | 0x4030_0000 | 0x10000 |
| Ponto de Teste | Conexão | Folha do Esquemático | Lado da 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 |