Detalhes do MediaTek: Partições e Pré-carregador

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.sexecuta as seguintes etapas para criar um estado limpo:

  1. Limpe todos os registros.
  2. Alterne para o modo privilegiado SVC32.
  3. Desativar interrupções.
  4. Configure os caches e outros detalhes menores (por exemplo, a pilha).
  5. Vá para o mainmé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_initplatform_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}.xlspelo 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.ce 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_BOOTserá 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 READYpara 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 Startsequê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):

ComandoByte de comandoFunção
CMD_GET_BL_VER0xfeObtenha a versão do Preloader (parece ser sempre “1”)
CMD_GET_HW_SW_VER0xfcRetornar subcódigo do hardware, versão do hardware e versão do software
CMD_GET_HW_CODE0xfdRetornar código e status de hardware
CMD_SEND_DA0xd7Envie um binário especial “Download Agent” para o SoC, assinado com uma chave.
CMD_JUMP_DA0xd5Defina o modo de inicialização DOWNLOAD_BOOTe inicie a execução do Download Agent enviado na etapa anterior.
CMD_GET_TARGET_CONFIG0xd8Obtenha sinalizadores de configuração do Preloader suportados
CMD_READ160xa2Ler dados da memória SoC (parâmetro de comprimento de 16 bits)
CMD_WRITE160xd2Gravar dados na memória SoC (parâmetro de comprimento de 16 bits)
CMD_READ320xd1Ler dados da memória SoC (parâmetro de comprimento de 32 bits)
CMD_WRITE320xd4Gravar dados na memória SoC (parâmetro de comprimento de 32 bits)
CMD_PWR_INIT0xc4Inicialize o controlador de gerenciamento de energia (efetivamente uma operação nula porque já está ativada)
CMD_PWR_DEINIT0xc5Desligue o controlador de gerenciamento de energia (efetivamente um nulo)
CMD_PWR_READ160xc6Leia 16 bits de dados da memória da interface do controlador de gerenciamento de energia
CMD_PWR_WRITE160xc7Grave 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 READYe 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 Startcomando 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:

ComandoCadeia de comandoFunção
SWITCH_MD_REQSWITCHMDProvavelmente deve mudar o modem para o modo de download de firmware, mas parece não fazer nada no MT6582?
ATCMD_NBOOT_REQAT+NBOOTMudar para o NORMAL_BOOTmodo
META_STR_REQMETAMETAMudar para o META_BOOTmodo
FACTORY_STR_REQFACTFACTMudar para o FACTORY_BOOTmodo
META_ADV_REQADVEMETAMudar para o ADVMETA_BOOTmodo
ATE_STR_REQFACTORYMMudar para o ATE_FACTORY_BOOTmodo
FB_STR_ACKFASTBOOTMudar para o FASTBOOTmodo

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 SECSTATICpartição
  • Validação de assinaturas de imagem criptográfica usando RSA / SHA1
  • UBOOTLOGOBOOTIMGRECOVERYANDROIDpartiçõ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 SignToolbiná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 UBOOTpartiçã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 UBOOTpartição.

Se você souber melhor e / ou algo mudou, me encontre no Launchpad.net ou no Freenode IRC e entre em contato!

Recursos

Deixe um comentário