home bbs files messages ]

Forums before death by AOL, social media and spammers... "We can't have nice things"

   alt.os.development      Operating system development chatter      4,255 messages   

[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]

   Message 3,974 of 4,255   
   James Harris to All   
   UEFI boot basics   
   24 Nov 23 17:59:47   
   
   From: james.harris.1@gmail.com   
      
   Here are some notes on getting UEFI to boot one's own code.   
      
   To be clear about where the code below is intended to fit into an OS   
   boot process the idea is to boot the same OS by various means such as,   
   in simple terms,   
      
      BIOS --> my BIOS bootloader \   
      PXE --> my PXE bootloader    } my OS   
      UEFI --> my UEFI bootloader /   
      
   and this thread is intended to be about the UEFI bootloader part.   
      
   Furthermore, while I can add some comments later about a development   
   environment and accessing a boot filesystem but this post is just about   
   getting started with a UEFI Hello World. There's already plenty in here   
   to make the post over-long as it is.   
      
   There are specs for UEFI (and ACPI) at   
      
      https://uefi.org/specifications   
      
   I'll refer to sections of what is currently the latest spec, i.e. 2.10.   
      
   UEFI documents are not easy reading. The basic spec, alone, is something   
   like 2,000 pages and obscure.   
      
   To make things worse, I've seen tutorials which add further libraries.   
   Such libraries are intended 'to make things easier' but they give a   
   programmer more to learn and make it hard to tell what is UEFI and what   
   is an added library. I wanted to find out about the fundamentals of UEFI   
   itself so what I'll describe is a relatively low-level approach.   
      
   Perhaps the best place to start is the video at   
      
      https://youtu.be/XHH4VnHbsVc   
      
   and other videos by the same (very patient!) content creator.   
      
   At least in my test environment I found that even persuading UEFI to run   
   code was a major undertaking. For too long the boot process led to   
   nothing happening at all. No output, no error message, no response of   
   any kind. For a long while I couldn't tell whether my code had even been   
   booted. So getting to the point where one can output text is crucial:   
   once one can write text to the display then one has a chance of   
   developing and debugging.   
      
   With that said here's UEFI code (in Nasm assembly syntax) to write a   
   Hello message to the screen. I'll walk through it, below.   
      
   efi_main is:   
      
      
      
   Called with x64 ms abi   
      RCX = image   
      RDX = systab   
      
   global efi_main   
   efi_main:   
      
      push rbp   
      mov rbp, rsp   
      sub rsp, 32   
      mov r10, rdi ;image   
      mov r11, rsi ;systab   
      
      lea rdx, [rel msg_hello]   
      mov rcx, [r11 + efi_systab.conout]   
      mov rax, [rcx + efi_stop.OutputString]   
      call rax   
      
      add rsp, 32   
      pop rbp   
      ret   
      
   section .data   
      
   msg_hello: dw __?utf16?__("Hello 64 UEFI..."), 13, 10, 0   
      
      
      
   The first thing to say is that the design of UEFI appears to be heavily   
   influenced by Microsoft. As such, the string to be printed needs CR as   
   well as LF to go to the start of a new line. Also, the file which we   
   want UEFI to boot needs to be in PE format. There's no option of flat   
   binary or ELF etc.   
      
   However, once one's own bootloader is running it can load the OS from   
   whatever type of file one wants. In other words, the OS loader has to be   
   in PE format but what it loads can be of some other format as required.   
      
   When UEFI calls our code and when we call UEFI routines we naturally   
   have to stick to the expected calling conventions. They have to match   
   the CPU and mode. For 64-bit x86 booting see section 2.3.4. which is   
   about x64 Platforms.   
      
   The spec says that a caller must always call with the stack 16-byte   
   aligned. One thing I haven't found out is whether that means the stack   
   should be 16-byte aligned before or after the call instruction.   
   (Naturally, the call instruction will add 8 bytes to the stack.) The   
   above code assumes 'before'. So pushing RBP will realign the stack. But   
   that needs to be revisited.   
      
   One thing UEFI gives the booted code is a pointer to the System Table.   
   The table is documented in section 4. It starts with the following   
   fields. (I included only as far as I needed.)   
      
   struc efi_systab ;UEFI system table   
      .hdr:      resb efi_hdr_size   
      .fvend:    resq 1 ;FirmwareVendor   
      .frev:     resd 1 ;FirmwareRevision   
                 resd 1 ;padding   
      .coninh:   resq 1 ;ConsoleInHandle   
      .conin:    resq 1 ;ConIn   
      .conouth:  resq 1 ;ConsoleOutHandle   
      .conout:   resq 1 ;ConOut   
   endstruc   
      
   The header is   
      
      
   struc efi_hdr ;Generic header   
      .Signature:    resq 1   
      .Revision:     resd 1   
      .HeaderSize:   resd 1   
      .CRC32:        resd 1   
                     resd 1  ;reserved   
   endstruc   
      
   A note for anyone not familiar with C: where the spec in section 4.3.1 says   
      
      CHAR16                           *FirmwareVendor;   
      UINT32                           FirmwareRevision;   
      
   whereas FirmwareRevision in the second line is uint32, as you might   
   expect, the asterisk on the first line indicates that FirmwareVendor is   
   a /pointer/ to the stated type, char16, and so in this case the field is   
   64-bit.   
      
   Going back to the code, the System Table's conout field is a pointer to   
   a 'simple text output' (STO) table for the console, i.e. the screen at   
   boot time. An STO table includes the following fields.   
      
   struc efi_stop ;UEFI simple text output protocol   
      .Reset:                   resq 1   
      .OutputString:            resq 1   
      .TestString:              resq 1   
      .QueryMode:               resq 1   
      .SetMode:                 resq 1   
      .SetAttribute:            resq 1   
      .ClearScreen:             resq 1   
      .SetCursorPosition:       resq 1   
      .EnableCursor:            resq 1   
      .Mode:                    resq 1   
   endstruc   
      
   In that, the OutputString field holds the address of the UEFI function   
   which can be called to write a string of 16-bit chars to the screen. It   
   is passed   
      
      ECX = the STO table for the console (i.e. conout)   
      EDX = the string to print   
      
   Lo and behold, text appears! At least it does if one has everything   
   right. I can say more about that if anyone wants but for now I'll stop   
   here and not make this post any longer.   
      
   Feel free to add comments, questions and corrections.   
      
      
   --   
   James Harris   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   

[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]


(c) 1994,  bbs@darkrealms.ca