NOTA: Esta é uma continuação do artigo anterior da série. As informações foram obtidas de várias fontes e, através da engenharia reversa, não tome como referência!
ATUALIZAÇÃO 03.07.2015: Adicionar referências do Download Agent.
Após a ROM de inicialização concluir a inicialização do hardware principal, ela carrega o primeiro bloco do flash eMMC na SRAM On-chip e inicia a execução. Normalmente, esse seria o local do firmware do carregador de inicialização do sistema operacional, mas nos SoCs do MediaTek, normalmente não é. Há um passo intermediário: o Preloader. É um software que abstrai um pouco entre a plataforma e o carregador de inicialização real e oferece alguns recursos adicionais, como a capacidade de inicializar a partir do MMC ou do NAND Flash ou de ler / gravar várias partes do flash via USB.
visão global
Quando o MediaTek envia um pacote fonte do kernel para um fabricante, o pacote também contém o código fonte do Preloader. Dependendo das alterações feitas pelo fabricante, o Preloader faz pequenas coisas diferentes em diferentes SoCs e placas, portanto, não é fácil criar uma descrição genérica que se adapte a todos os sistemas.
A análise a seguir é baseado no ThunderKernel código fonte para o MT6582 SoC. A distribuição é composta pelas seguintes partes:
- Uma parte específica da plataforma
mediatek/platform/${platform}/preloader
, contém a maior parte do código. - Uma peça personalizada (específica do fabricante) no
mediatek/custom/${platform}/preloader
. - Uma parte específica do dispositivo
mediatek/custom/${DEVICE}/preloader
.
Agora vamos analisar o código.
CPU Init
Quando o pré-carregador é iniciado, o SoC ainda não foi totalmente inicializado e muitas coisas estão em um estado aleatório. Uma parte do código do assembler mediatek/platform/${platform}/preloader/src/init/init.s
executa as seguintes etapas para criar um estado limpo:
- Limpe todos os registros.
- Alterne para o modo privilegiado SVC32.
- Desativar interrupções.
- Configure os caches e outros detalhes menores (por exemplo, a pilha).
- Vá para o
main
método no código C.
Agora a execução continua mediatek/platform/${platform}/preloader/src/core/main.c
, o que chama muitos outros métodos, que novamente chamam muitos outros métodos. Não faz sentido desabilitar todas as linhas de código deste artigo, portanto, darei uma visão de alto nível do que está acontecendo e explicarei alguns detalhes quando necessário.
Init da plataforma
O Preloader conta com alguns periféricos, por isso precisa inicializá-los. Isso é feito principalmente nos métodos platform_pre_init
e platform_init
. A lista de periféricos contém o timer, o relógio PLL, o controlador de memória DDR, o Watchdog, os pinos GPIO, o UART, a porta USB 1.1 e o circuito de gerenciamento de energia.
Há algo de especial aqui: após a inicialização do armazenamento flash, o Preloader oferece um modo de “download de emergência” precoce. O fabricante pode definir uma chave de hardware que, quando pressionada durante o init da plataforma Preloader, é reiniciada imediatamente na ROM de inicialização e aguarda o download.
Nesse ponto, o Preloader também registra o motivo pelo qual o sistema foi inicializado:
typedef enum {
BR_POWER_KEY = 0,
BR_USB,
BR_RTC,
BR_WDT,
BR_WDT_BY_PASS_PWK,
BR_TOOL_BY_PASS_PWK,
BR_2SEC_REBOOT,
BR_UNKNOWN
} boot_reason_t;
Partições
Depois de abrir a plataforma, o pré-carregador tem acesso total ao armazenamento interno. O MediaTek decidiu particionar o armazenamento, mas a tabela de partições está codificada. Ele é gerado durante a geração do Preloader a partir de um arquivo do Excel (sim, realmente) mediatek/build/tools/ptgen/${platform}/partition_table_${platform}.xls
pelo comando ./makeMtk -t ${device} ptgen
(aqui está um exemplo para o bq Aquaris E4.5 Ubuntu Edition):
A estrutura da tabela de partição gerada é armazenada out/target/product/krillin/obj/PRELOADER_OBJ/cust_part.c
e tem a seguinte aparência (exemplo para o bq Aquaris E4.5 Ubuntu Edition):
static part_t platform_parts[PART_MAX_COUNT] = {
{PART_PRELOADER, 0, PART_SIZE_PRELOADER, 0,PART_FLAG_NONE},
{PART_MBR, 0, PART_SIZE_MBR, 0,PART_FLAG_NONE},
{PART_EBR1, 0, PART_SIZE_EBR1, 0,PART_FLAG_NONE},
{PART_PRO_INFO, 0, PART_SIZE_PRO_INFO, 0,PART_FLAG_NONE},
{PART_NVRAM, 0, PART_SIZE_NVRAM, 0,PART_FLAG_NONE},
{PART_PROTECT_F, 0, PART_SIZE_PROTECT_F, 0,PART_FLAG_NONE},
{PART_PROTECT_S, 0, PART_SIZE_PROTECT_S, 0,PART_FLAG_NONE},
{PART_SECURE, 0, PART_SIZE_SECCFG, 0,PART_FLAG_NONE},
{PART_UBOOT, 0, PART_SIZE_UBOOT, 0,PART_FLAG_NONE},
{PART_BOOTIMG, 0, PART_SIZE_BOOTIMG, 0,PART_FLAG_NONE},
{PART_RECOVERY, 0, PART_SIZE_RECOVERY, 0,PART_FLAG_NONE},
{PART_SECSTATIC, 0, PART_SIZE_SEC_RO, 0,PART_FLAG_NONE},
{PART_MISC, 0, PART_SIZE_MISC, 0,PART_FLAG_NONE},
{PART_LOGO, 0, PART_SIZE_LOGO, 0,PART_FLAG_NONE},
{PART_EXPDB, 0, PART_SIZE_EXPDB, 0,PART_FLAG_NONE},
{PART_ANDSYSIMG, 0, PART_SIZE_ANDROID, 0,PART_FLAG_NONE},
{PART_CACHE, 0, PART_SIZE_CACHE, 0,PART_FLAG_NONE},
{PART_USER, 0, PART_SIZE_USRDATA, 0,PART_FLAG_NONE},
{NULL,0,0,0,PART_FLAG_END},
};
Primeiro estágio de inicialização segura
Após carregar todas as partições (e se o recurso estiver ativado), o Preloader inicializa o subsistema SecLib. O fornecedor do dispositivo fornece uma chave RSA de até 2048 bits de comprimento (embora as chaves que eu vi tenham apenas 1024 bits).
O que o SecLib faz exatamente é desconhecido. Ele pega os dados de configuração da partição SECURE (se existir) e a chave RSA e, em seguida, chama o blob binário mediatek/platform/${platform}/preloader/src/SecLib.a
.
Seleção do modo de inicialização
Após (opcionalmente) confirmar a inicialização segura, o Preloader decide qual modo de inicialização usar.
NORMAL_BOOT
será usado se a inicialização segura estiver desativada ou o módulo de inicialização segura não disser o contrário. Se o Modo de Download estiver ativado, este modo tentará entrar imediatamente.
Há uma longa lista de outros modos de inicialização possíveis, e nem todos são auto-explicativos:
typedef enum
{
NORMAL_BOOT = 0,
META_BOOT = 1,
RECOVERY_BOOT = 2,
SW_REBOOT = 3,
FACTORY_BOOT = 4,
ADVMETA_BOOT = 5,
ATE_FACTORY_BOOT = 6,
ALARM_BOOT = 7,
#if defined (MTK_KERNEL_POWER_OFF_CHARGING)
KERNEL_POWER_OFF_CHARGING_BOOT = 8,
LOW_POWER_OFF_CHARGING_BOOT = 9,
#endif
FASTBOOT = 99,
DOWNLOAD_BOOT = 100,
UNKNOWN_BOOT
} BOOTMODE;
Modo de download
Para poder entrar no modo Download, o Preloader precisa descobrir se um host está conectado via USB ou UART e executando a MTK SP Flash Tool. Isso é feito através da configuração de uma disciplina virtual do CDC ACM no USB, para que ambas as linhas sejam de fato portas seriais e se comportem da mesma forma.
A porta USB pressupõe que a ferramenta esteja conectada se receber uma mensagem CDC de “set line coding” (configura a taxa de transmissão etc.). Em seguida, envia a string READY
para a ferramenta e aguarda a recepção de um token de oito bytes.
Após a detecção bem-sucedida, a ferramenta pode enviar a Start
sequência de comandos especial ( 0xa0 0x0a 0x50 0x05
) para entrar em um modo especial disponível apenas via USB. Ele interpreta os seguintes comandos (deixei os marcados com “legado” de fora):
Comando | Byte de comando | Função |
---|---|---|
CMD_GET_BL_VER | 0xfe | Obtenha a versão do Preloader (parece ser sempre “1”) |
CMD_GET_HW_SW_VER | 0xfc | Retornar subcódigo do hardware, versão do hardware e versão do software |
CMD_GET_HW_CODE | 0xfd | Retornar código e status de hardware |
CMD_SEND_DA | 0xd7 | Envie um binário especial “Download Agent” para o SoC, assinado com uma chave. |
CMD_JUMP_DA | 0xd5 | Defina o modo de inicialização DOWNLOAD_BOOT e inicie a execução do Download Agent enviado na etapa anterior. |
CMD_GET_TARGET_CONFIG | 0xd8 | Obtenha sinalizadores de configuração do Preloader suportados |
CMD_READ16 | 0xa2 | Ler dados da memória SoC (parâmetro de comprimento de 16 bits) |
CMD_WRITE16 | 0xd2 | Gravar dados na memória SoC (parâmetro de comprimento de 16 bits) |
CMD_READ32 | 0xd1 | Ler dados da memória SoC (parâmetro de comprimento de 32 bits) |
CMD_WRITE32 | 0xd4 | Gravar dados na memória SoC (parâmetro de comprimento de 32 bits) |
CMD_PWR_INIT | 0xc4 | Inicialize o controlador de gerenciamento de energia (efetivamente uma operação nula porque já está ativada) |
CMD_PWR_DEINIT | 0xc5 | Desligue o controlador de gerenciamento de energia (efetivamente um nulo) |
CMD_PWR_READ16 | 0xc6 | Leia 16 bits de dados da memória da interface do controlador de gerenciamento de energia |
CMD_PWR_WRITE16 | 0xc7 | Grave 16 bits de dados na memória da interface do controlador de gerenciamento de energia |
A etapa Download Agent é necessária porque, dessa forma, a Flash Tool sempre pode enviar uma versão atual para a versão exata do hardware que está sendo usada.
O UART não tem possibilidade de detectar se a linha física está energizada; portanto, ele envia a string READY
e espera receber um token de oito bytes de volta. Nesse caso, assume que a ferramenta está presente.
Observe que os comandos especiais da tabela acima não estão disponíveis ao se comunicar pelo UART, provavelmente porque a ROM de inicialização já oferece a maioria desses comandos via UART.
Se o Start
comando especial não for emitido pelo host via USB, o Preloader entra em um modo comum no qual aceita os seguintes comandos por USB e UART:
Comando | Cadeia de comando | Função |
---|---|---|
SWITCH_MD_REQ | SWITCHMD | Provavelmente deve mudar o modem para o modo de download de firmware, mas parece não fazer nada no MT6582? |
ATCMD_NBOOT_REQ | AT+NBOOT | Mudar para o NORMAL_BOOT modo |
META_STR_REQ | METAMETA | Mudar para o META_BOOT modo |
FACTORY_STR_REQ | FACTFACT | Mudar para o FACTORY_BOOT modo |
META_ADV_REQ | ADVEMETA | Mudar para o ADVMETA_BOOT modo |
ATE_STR_REQ | FACTORYM | Mudar para o ATE_FACTORY_BOOT modo |
FB_STR_ACK | FASTBOOT | Mudar para o FASTBOOT modo |
Segundo estágio de inicialização segura
Novamente, não se sabe o que o SecLib faz nesse estágio, ele chama o blob binário na maioria das vezes.
As seguintes informações (questionáveis) foram obtidas examinando o wrapper C e despejando os símbolos e seqüências de caracteres da biblioteca:
- Os dados de segurança vêm da
SECSTATIC
partição - Validação de assinaturas de imagem criptográfica usando RSA / SHA1
- A
UBOOT
,LOGO
,BOOTIMG
,RECOVERY
eANDROID
partições parecem ser verificado em algum momento - O “nome do cliente” parece ter sido verificado de alguma forma, mas por quê?
As imagens assinadas necessárias provavelmente são geradas pelos SignTool
binários em mediatek/build/tools/SignTool
.
O fabricante do dispositivo pode adicionar medidas de segurança adicionais.
Carregar imagens principais de inicialização
Agora que o Preloader sabe que o sistema é seguro, ele pode carregar as imagens do firmware a partir do flash interno.
Este é um processo altamente especializado, porque cada imagem deve ser processada de maneira diferente. Por exemplo, o firmware do modem HSPA no MT6582 deve ser alimentado no modem usando registros e comandos especiais, enquanto o carregador de inicialização u-Boot pode ser copiado no endereço de memória correto. Nesta etapa, o Preloader também decidirá qual é o próximo componente que será executado após o término. Por padrão, essa será a imagem armazenada na UBOOT
partição.
Observe que nesta etapa apenas o firmware mais básico é carregado, geralmente esse é apenas o modem e o carregador de inicialização.
Plataforma pós init
Nesta etapa, a plataforma é colocada em um estado definido para o próximo componente do processo de inicialização (carregador de inicialização, Little Kernel). A etapa mais importante é transmitir os argumentos de inicialização que foram definidos durante a execução do Preloader. Esperamos que isso faça mais sentido, quando examinarmos o que acontece após o Preloader, todo o design do MediaTek é um pouco complicado.
A estrutura do argumento de inicialização no MT6852 é assim:
typedef struct {
u32 magic; // 0x504c504c
boot_mode_t mode; // Boot mode
u32 e_flag;
u32 log_port;
u32 log_baudrate;
u8 log_enable;
u8 part_num; // Number of partitions
u8 reserved[2];
u32 dram_rank_num;
u32 dram_rank_size[4];
u32 boot_reason; // Boot reason
u32 meta_com_type;
u32 meta_com_id;
u32 boot_time;
da_info_t da_info;
SEC_LIMIT sec_limit;
part_hdr_t *part_info; // Partition info
u8 md_type[4];
u32 ddr_reserve_enable;
u32 ddr_reserve_success;
u32 chip_ver;
} boot_arg_t;
Ele é colocado em um local de memória definido, onde “sobrevive” até que o próximo componente o pegue.
Inicialize o próximo componente
O último passo do Preloader é pular para o local do próximo componente, geralmente o “Little Kernel” carregado na UBOOT
partição.
Se você souber melhor e / ou algo mudou, me encontre no Launchpad.net ou no Freenode IRC e entre em contato!