Escrevendo um Bootloader UEFI
Bootloaders já cobre escrever um setor de boot legado de 512 bytes e carregamento em estágios; uma aplicação UEFI é um caminho inteiramente diferente, carregado e executado diretamente pelo firmware sem GRUB algum, sem setor de boot legado algum, e sem o handoff em estágios do tipo que aquele artigo descreve, já que o próprio firmware UEFI já fornece a maior parte do que os estágios iniciais de um bootloader legado existem para construir do zero.
Um binário PE32+, não ELF bruto
Seção intitulada “Um binário PE32+, não ELF bruto”O firmware UEFI só sabe carregar e executar o formato PE32+, o mesmo formato executável que o Windows usa, não ELF, que é o que o próprio binário de um kernel é tipicamente construído como. Um kernel visando UEFI diretamente portanto precisa de seu estágio de bootloader construído e linkado como um executável PE32+ genuíno (-target x86_64-pc-win32-coff e um linker script apropriado sob uma toolchain GNU, ou nativamente sob uma toolchain ciente de EFI), exportando uma assinatura de ponto de entrada específica que o firmware espera chamar diretamente.
EFI_STATUS EFIAPI efi_main(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table) { // system_table dá acesso a Boot Services, Runtime Services, // e ao console, desde a primeiríssima instrução ...}system_table é o portal do ponto de entrada para tudo que o UEFI oferece: Boot Services (alocação de memória, busca de protocolo, o mapa de memória), Runtime Services (um conjunto menor que continua chamável mesmo depois de o kernel ter assumido, como ler o relógio de tempo real), e um console básico em modo texto utilizável para saída de diagnóstico inicial antes de qualquer framebuffer ter sido localizado.
Boot Services: o mapa de memória e um framebuffer
Seção intitulada “Boot Services: o mapa de memória e um framebuffer”Antes de transferir controle para o kernel propriamente dito, um bootloader UEFI tipicamente precisa de duas coisas dos Boot Services: o mapa de memória, obtido através de GetMemoryMap, descrevendo quais regiões físicas são utilizáveis, reservadas, ou já ocupadas pelo firmware ou pelas próprias alocações do bootloader, e um framebuffer, localizado através do Graphics Output Protocol (GOP), o mesmo protocolo que VGA & Framebuffers já menciona de passagem como o equivalente UEFI da VBE.
EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;system_table->BootServices->LocateProtocol(&gop_guid, NULL, (void**)&gop);
uint32_t width = gop->Mode->Info->HorizontalResolution;uint32_t height = gop->Mode->Info->VerticalResolution;uint64_t fb_base = gop->Mode->FrameBufferBase;Os dois são obtidos da mesma forma que essencialmente toda facilidade UEFI é: localizando um protocolo por seu GUID através dos Boot Services, depois chamando funções através da própria tabela de ponteiros de função daquele protocolo, um padrão consistente entre serviços de memória, gráficos, I/O de disco, e tudo mais que os Boot Services expõem, em vez de cada facilidade ter sua própria convenção de chamada não relacionada.
ExitBootServices: o ponto sem volta
Seção intitulada “ExitBootServices: o ponto sem volta”ExitBootServices é a chamada individual mais consequente de toda a sequência de boot: depois que ela tem sucesso, todo ponteiro de função de Boot Services se torna inválido, chamar qualquer um deles produz comportamento indefinido, e o kernel fica inteiramente por conta própria para gerenciamento de memória, descoberta adicional de hardware, e tudo mais que os Boot Services estavam fornecendo até aquele momento.
EFI_MEMORY_DESCRIPTOR *map;UINTN map_size, map_key, desc_size;UINT32 desc_version;
system_table->BootServices->GetMemoryMap(&map_size, map, &map_key, &desc_size, &desc_version);// ... aloca um buffer de map_size e chama GetMemoryMap de novo para preenchê-lo ...
system_table->BootServices->ExitBootServices(image_handle, map_key);// a partir daqui, nenhuma chamada de Boot Services é válidaA chamada exige exatamente o map_key da chamada de GetMemoryMap mais recente, e falha se qualquer alocação intermediária (mesmo uma feita pelo próprio código do bootloader) mudou o mapa de memória desde que essa chave foi obtida, motivo pelo qual o padrão convencional chama GetMemoryMap de novo, imediatamente antes de ExitBootServices, especificamente para garantir que a chave corresponda ao mapa exatamente como existe naquele instante. Runtime Services continuam chamáveis depois desse ponto (o kernel não fica inteiramente sem assistência alguma do firmware), mas Boot Services, e o console, a descoberta de framebuffer, e as funções de alocação de memória que vieram com eles, não.
Notas de implementação
Seção intitulada “Notas de implementação”Como o próprio alocador de memória do UEFI fica indisponível depois de ExitBootServices, um kernel precisa de seu próprio alocador de frames, já coberto separadamente, pronto para assumir usando exatamente o mapa de memória obtido logo antes dessa chamada, já que nenhum mapa adicional pode ser requisitado depois para preencher o que quer que tenha sido perdido. Um bootloader UEFI também precisa ser colocado num caminho reconhecido pelo firmware numa EFI System Partition formatada em FAT32 (convencionalmente \EFI\BOOT\BOOTX64.EFI como o padrão de fallback que BIOS vs. UEFI já nomeia) em vez de embutido num setor de boot da forma que um bootloader legado é, o que é em si uma partição e sistema de arquivos que o firmware UEFI já sabe ler sem driver algum que o kernel forneça.
Referências
Seção intitulada “Referências”- ^ UEFI Forum, UEFI Specification, Capítulo 4 (Boot Services), Capítulo 8 (Runtime Services), e Capítulo 12 (Graphics Output Protocol).
Ver também
Seção intitulada “Ver também”- BIOS vs. UEFI: a comparação de alto nível sob a qual o caminho de boot deste artigo se encaixa.
- VGA & Framebuffers: a descoberta de framebuffer via GOP mencionada ali numa frase, coberta aqui por completo do lado do fluxo de boot.
- Physical Memory: o alocador de frames que precisa assumir usando o mapa de memória obtido logo antes do ExitBootServices.