Add "Hello world" UEFI application
authorJonas Hvid <mail@johv.dk>
Fri, 6 Mar 2020 17:15:15 +0000 (18:15 +0100)
committerJonas Hvid <mail@johv.dk>
Fri, 6 Mar 2020 17:15:15 +0000 (18:15 +0100)
README.md
uefi/.gitignore
uefi/Makefile
uefi/main.asm [new file with mode: 0644]

index fe54d8021a55fe04dba6dc0cc868fedf02b8cee7..8d3d026f4a288c28e39e2e9e53f7070126bae49e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -30,6 +30,10 @@ To run a UEFI shell inside qemu, cd to `uefi/` and run:
 
     $ make run
 
+### Running on read hardware
+
+* [ ] This is not supported yet
+
 # Notes on implementation
 
 This is my summary of the most important parts of
@@ -182,9 +186,20 @@ about how this functionality is implemented.
 
 ## Packaging and testing the image
 
-* [ ] What should the image look like?
-* [ ] How to build the image (which programs, commands, etc.)
-* [ ] How do we run the application in QEMU
+UEFI expects a UEFI application to be stored in a FAT32 file system on a
+GPT-partitioned disk.
+
+Luckily, QEMU has a convenient way of making a subdirectory availabe as a
+FAT-formatted disk (see [the relevant section in the QEMU User
+Documentation](https://qemu.weilnetz.de/doc/qemu-doc.html#disk_005fimages_005ffat_005fimages)
+for more information):
+
+    $ qemu-sytem-x86_64 ... -hda fat:/some/directory
+
+We use this to easily test the image in QEMU; see the Makefile for more information.
+
+* [ ] How to build the image for real hardware (what should the image look like,
+  which programs, commands, etc.)
 
 ## Interfacing with UEFI
 
@@ -210,10 +225,73 @@ drivers. Eventually, we would like to add some basic graphical drawing
 capabilities to `JONASFORTH`, and it's my impression that this would be possible
 using what is provided to us by UEFI.
 
-* [ ] How to register as a UEFI application
-* [ ] How to use UEFI provided functions
+A UEFI images is basically a windows EXE without symbol tables. There are three
+types of UEFI images; we use the EFI application, which has subsystem `10`. It
+is an x68-64 image, which has value `0x8664`.
+
+UEFI applications use [Microsoft's 64-bit calling convention](https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention) for x68-64 functions. See the linked article for a full description. Here is the short version:
+
+* Integer or pointer arguments are given in RCX, RDX, R8 and R9.
+* Additional arguments are pushed onto the stack from right to left.
+* Integer or pointer values are returned in RAX.
+* An integer-sized struct is passed directly; non-integer-sized structs are passed as pointers.
+* The caller must allocate 32 bytes of "shadow space" on the stack immediately
+  before calling the function, regardless of the number of parameters used, and
+  the caller is responsible for popping the stack afterwards.
+* The following registers are volatile (caller-saved): RAX, RCX, RDX, R8, R9, R10, R11
+* The following registers are nonvolatile (callee-saved): RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15
+
+When the application is loaded, RCX contains a firmware allocated `EFI_HANDLE`
+for the UEFI image, RDX contains a `EFI_SYSTEM_TABLE*` pointer to the EFI system
+table and RSP contains the return address. For more infromation about how a UEFI
+application is entered, see "4 - EFI System Table" in [the latest UEFI
+specification as of March 2020 (PDF)](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf).
+
+**Sources:**
+
+* [UEFI applications in detail - OSDev Wiki](https://wiki.osdev.org/UEFI#UEFI_applications_in_detail)
+* [Microsoft x64 calling convention](https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention)
+* [UEFI Specifications](https://uefi.org/specifications)
+
+### UEFI with FASM
+
+We might want to consider using something like this: https://wiki.osdev.org/Uefi.inc)
+
+FASM can generate UEFI application binaries by default. Use the following
+template to output a 64-bit UEFI application:
+
+    format pe64 dll efi
+    entry main
+
+    section '.text' code executable readable
+
+    main:
+       ;; ...
+       ret
+
+    section '.data' data readable writable
+
+    ;; ...
+
+Use `objdump -x` to inspect the assembled application binary.
+
+### UEFI documentation
+
+* [Latest specification as of March 2020 (PDF)](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf)
+
+Notable sections:
+
+* 2\. Overview (14)
+* 4\. EFI System Table (89)
+* 7\. Services - Boot Services (140)
+* 8\. Services - Runtime Services (228)
+* 12\. Protocols - Console Support (429)
+* Appendix B - Console (2201)
+* Appendix D - Status Codes (2211)
+
 
 ## Resources
 
-* https://wiki.osdev.org/UEFI
-* https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface
+* [UEFI - OSDev Wiki](https://wiki.osdev.org/UEFI)
+* [Unified Extensible Firmware Interface (Wikipedia)](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)
+* [UEFI Specifications](https://uefi.org/specifications)
index 8dbe266a02bff42f91990a7b5ae1c5cf52137097..5557c762e3873e91cb01d4cca24df6bcc8110247 100644 (file)
@@ -1,2 +1,3 @@
+/out
 /OVMF_CODE.fd
 /OVMF_VARS.fd
index 0837d394cf263731e39d0bd9c0e96ce6aa79f05b..1d55f083bbf67785f262e70297cb2fb0c91601c3 100644 (file)
@@ -1,10 +1,12 @@
-.PHONY: run
-run: OVMF_CODE.fd OVMF_VARS.fd
+.PHONY: qemu
+qemu: out/main OVMF_CODE.fd OVMF_VARS.fd
        # Based on https://wiki.osdev.org/UEFI#Emulation_with_QEMU_and_OVMF
        qemu-system-x86_64 -cpu qemu64 \
                -drive if=pflash,format=raw,unit=0,file=OVMF_CODE.fd,readonly=on \
                -drive if=pflash,format=raw,unit=1,file=OVMF_VARS.fd \
-               -net none
+               -net none \
+               -drive format=raw,file=fat:rw:out \
+               -display type=gtk,zoom-to-fit=on
 
 # Assuming 'ovmf' package on Arch Linux is installed.
 OVMF_CODE.fd: /usr/share/ovmf/x64/OVMF_CODE.fd
@@ -12,6 +14,10 @@ OVMF_CODE.fd: /usr/share/ovmf/x64/OVMF_CODE.fd
 OVMF_VARS.fd: /usr/share/ovmf/x64/OVMF_VARS.fd
        cp $< $@
 
+out/main: main.asm
+       mkdir -p out
+       fasm $< out/main
+
 .PHONY: clean
 clean:
        rm -f main
diff --git a/uefi/main.asm b/uefi/main.asm
new file mode 100644 (file)
index 0000000..aa34ddc
--- /dev/null
@@ -0,0 +1,65 @@
+format pe64 dll efi
+entry main
+
+;; #region Structs
+
+;; Based on https://wiki.osdev.org/Uefi.inc
+macro struct name {
+  virtual at 0
+    name name
+  end virtual
+}
+
+struc EFI_TABLE_HEADER {
+  dq ?
+  dd ?
+  dd ?
+  dd ?
+  dd ?
+}
+
+struc EFI_SYSTEM_TABLE {
+  .Hdr EFI_TABLE_HEADER
+  .FirmwareVendor dq ? ; CHAR16*
+  .FirmwareRevision dd ? ; UINT32
+  align 8
+  .ConsoleInHandle dq ? ; EFI_HANDLE
+  .ConIn dq ? ; EFI_SIMPLE_TEXT_INPUT_PROTOCOL*
+  .ConsoleOutHandle dq ? ; EFI_HANDLE
+  .ConOut dq ? ; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL*
+  ; ...
+}
+struct EFI_SYSTEM_TABLE
+
+struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
+  .Reset dq ? ; EFI_TEXT_RESET
+  .OutputString dq ? ; EFI_TEXT_STRING
+  ; ...
+}
+struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
+
+;; #endregion
+
+section '.text' code executable readable
+
+main:
+  ; At program startup, RDX contains an EFI_SYSTEM_TABLE*.
+  mov [system_table], rdx
+
+  mov rcx, [system_table] ; EFI_SYSTEM_TABLE* rcx
+  mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut] ; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* rcx
+  mov rdx, hello_world_string
+  ; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* rcx, CHAR16* rdx)
+  mov rbx, [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString] ; EFI_TEXT_STIRNG rbx
+  sub rsp, 32
+  call rbx
+  add rsp, 32
+
+  mov rax, 0
+  ret
+
+section '.data' readable writable
+
+system_table dq ?  ; EFI_SYSTEM_TABLE*
+
+hello_world_string du 'Hello world!', 0xC, 0xA, 0