commit d3727ac45a0773a291326f3056a42a60c508f479
parent 5cb64f840aa50d32c02ff15576715d75a35fdff5
Author: Christian Ermann <christianermann@gmail.com>
Date:   Wed, 22 Nov 2023 16:29:29 -0500
Add EFI print function
Diffstat:
| A | Dockerfile | | | 23 | +++++++++++++++++++++++ | 
| A | Makefile | | | 4 | ++++ | 
| A | efi.asm | | | 285 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | forth.asm | | | 29 | +++++++++++++++++++++++++++++ | 
| A | run.sh | | | 9 | +++++++++ | 
5 files changed, 350 insertions(+), 0 deletions(-)
diff --git a/Dockerfile b/Dockerfile
@@ -0,0 +1,23 @@
+FROM debian:stable-slim
+
+RUN apt-get update && apt-get -y upgrade && \
+    apt-get --no-install-recommends -y install \
+        qemu-system-x86 \
+        ovmf \
+        fasm \
+        mtools
+
+COPY forth.asm /
+COPY efi.asm /
+COPY run.sh /
+
+RUN fasm forth.asm BOOTX64.EFI
+
+RUN dd if=/dev/zero of=boot.img bs=1M count=512 && \
+    mformat -i boot.img :: && \
+    mmd -i boot.img ::/EFI && \
+    mmd -i boot.img ::/EFI/BOOT && \
+    mcopy -i boot.img BOOTX64.EFI ::/EFI/BOOT
+
+ENTRYPOINT ["/run.sh"]
+
diff --git a/Makefile b/Makefile
@@ -0,0 +1,4 @@
+all:
+	docker build . --tag forth
+	docker image prune -f
+	docker run --interactive --rm forth
diff --git a/efi.asm b/efi.asm
@@ -0,0 +1,285 @@
+; This file is adapted from `uefi.inc` hosted on osdev.org. It's purpose is
+; to provide an easy-to-use interface to the EFI protocol. I've stripped a lot
+; of stuff out as I don't need it at the moment. I'll probably have to add some
+; of it back at a later date.
+;
+
+; These definitions are for portability across 32- and 64-bit systems and
+; provide automatic alignment in structure definitions.
+struc int8 {
+    . db ?
+}
+struc int16 {
+    align 2
+    . dw ?
+}
+struc int32 {
+    align 4
+    . dd ?
+}
+struc int64 {
+    align 8
+    . dq ?
+}
+struc intn {
+    align 8
+    . dq ?
+}
+struc dptr {
+    align 8
+    . dq ?
+}
+
+; Definitions of status codes returned by various EFI calls.
+EFIERR = 0x8000000000000000
+EFI_SUCCESS           = 0
+EFI_LOAD_ERROR        = EFIERR or 1
+EFI_INVALID_PARAMETER = EFIERR or 2
+EFI_UNSUPPORTED       = EFIERR or 3
+EFI_BAD_BUFFER_SIZE   = EFIERR or 4
+EFI_BUFFER_TOO_SMALL  = EFIERR or 5
+EFI_NOT_READY         = EFIERR or 6
+EFI_DEVICE_ERROR      = EFIERR or 7
+EFI_WRITE_PROTECTED   = EFIERR or 8
+EFI_OUT_OF_RESOURCES  = EFIERR or 9
+EFI_VOLUME_CORRUPTED  = EFIERR or 10
+EFI_VOLUME_FULL       = EFIERR or 11
+EFI_NO_MEDIA          = EFIERR or 12
+EFI_MEDIA_CHANGED     = EFIERR or 13
+EFI_NOT_FOUND         = EFIERR or 14
+EFI_ACCESS_DENIED     = EFIERR or 15
+EFI_NO_RESPONSE       = EFIERR or 16
+EFI_NO_MAPPING        = EFIERR or 17
+EFI_TIMEOUT           = EFIERR or 18
+EFI_NOT_STARTED       = EFIERR or 19
+EFI_ALREADY_STARTED   = EFIERR or 20
+EFI_ABORTED           = EFIERR or 21
+EFI_ICMP_ERROR        = EFIERR or 22
+EFI_TFTP_ERROR        = EFIERR or 23
+EFI_PROTOCOL_ERROR    = EFIERR or 24
+
+
+; Helper macro for definition of relative structure member offsets.
+macro struct name
+{
+    virtual at 0
+        name name
+    end virtual
+}
+
+; Definitions of EFI structures
+
+struc EFI_TABLE_HEADER {
+    .Signature  int64
+    .Revision   int32
+    .HeaderSize int32
+    .CRC32      int32
+    .Reserved   int32
+}
+struct EFI_TABLE_HEADER
+
+struc EFI_SYSTEM_TABLE {
+    .Hdr                  EFI_TABLE_HEADER
+    .FirmwareVendor       dptr
+    .FirmwareRevision     int32
+    .ConsoleInHandle      dptr
+    .ConIn                dptr
+    .ConsoleOutHandle     dptr
+    .ConOut               dptr
+    .StandardErrorHandle  dptr
+    .StdErr               dptr
+    .RuntimeServices      dptr
+    .BootServices         dptr
+    .NumberOfTableEntries intn
+    .ConfigurationTable   dptr
+}
+struct EFI_SYSTEM_TABLE
+
+struc SIMPLE_TEXT_OUTPUT_INTERFACE {
+    .Reset             dptr
+    .OutputString      dptr
+    .TestString        dptr
+    .QueryMode         dptr
+    .SetMode           dptr
+    .SetAttribute      dptr
+    .ClearScreen       dptr
+    .SetCursorPosition dptr
+    .EnableCursor      dptr
+    .Mode              dptr
+}
+struct SIMPLE_TEXT_OUTPUT_INTERFACE
+
+struc SIMPLE_INPUT_INTERFACE {
+    .Reset         dptr
+    .ReadKeyStroke dptr
+    .WaitForKey    dptr
+}
+struct SIMPLE_INPUT_INTERFACE
+
+struc INPUT_KEY {
+    .ScanCode    dw ?
+    .UnicodeChar dw ?
+    align 8
+}
+struct INPUT_KEY
+
+sys_initialize:
+; Store the address of the EFI system table in the `system_table` variable.
+;
+; This function should be called ASAP after program start to ensure that the
+; RDX register still contains the address of the system table.
+;
+; `system_table` is defined as an 8-byte variable in the data section at the
+; bottom of this file.
+;
+    mov [system_table], rdx
+    ret
+
+sys_clear_screen:
+; Clear the screen.
+;
+; This function will clobber the RCX and RBX registers.
+;
+    mov rcx, [system_table]
+    mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut]
+    mov rbx, [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.ClearScreen]
+    sub rsp, 32
+    call rbx
+    add rsp, 32
+    ret
+
+sys_print_string:
+; Print an ascii string to stdout.
+;
+; This function uses the `.output_buffer` local variable to store the UTF-16
+; null-terminated version of the input string. 1024 bytes (512 characters) are
+; reserved for this buffer in the data section at the bottom of this file.
+;
+; This function will clobber the RDI, RAX, RBX, RCX, and RDX registers.
+;
+; Args:
+;    RCX: The address of an ASCII string. The string does not need to be
+;         null-terminated.
+;    RDX: The length of the string.
+;
+    mov rdi, .output_buffer
+
+.copy_char:
+; Copy a character from the ASCII string into the AL register.
+;
+; If there are no characters left, print the converted UTF-16 string.
+;
+    cmp rdx, 0
+    je .print_string
+
+    mov al, byte [rcx]
+    cmp al, 0xA
+    jne .not_newline
+
+.is_newline:
+; Convert "\n" (ASCII) to "\r\n" (UTF-16).
+;
+; The converted character is stored in `.output_buffer`.
+;
+    mov byte [rdi], 0xD
+    inc rdi
+    mov byte [rdi], 0
+    inc rdi
+    mov byte [rdi], 0xA
+    inc rdi
+    mov byte [rdi], 0
+    inc rdi
+    jmp .next_char
+
+.not_newline:
+; Convert an ASCII character into a UTF-16 character.
+;
+; The converted character is stored in `.output_buffer`.
+;
+    mov byte [rdi], al
+    inc rdi
+    mov byte [rdi], 0
+    inc rdi
+
+.next_char:
+; Process the next character in the ASCII string.
+;
+    inc rcx
+    dec rdx
+    jmp .copy_char
+
+.print_string:
+; Print a UTF-16 string to stdout.
+;
+; The string is null-terminated before being passed to the EFI OutputString
+; function in the RDX register.
+;
+    mov word [rdi], 0
+    mov rdx, .output_buffer
+    mov rcx, [system_table]
+    mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut]
+    mov rbx, [rcx + SIMPLE_TEXT_OUTPUT_INTERFACE.OutputString]
+    sub rsp, 32
+    call rbx
+    add rsp, 32
+    ret
+
+sys_read_char:
+; Read an ASCII character from stdin.
+;
+; The EFI ReadKeyStroke function actually returns a UTF-16 character but I only
+; want to deal with ASCII characters for now.
+;
+    mov rcx, [system_table]
+    mov rcx, [rcx + EFI_SYSTEM_TABLE.ConIn]
+    mov rbx, [rcx + SIMPLE_INPUT_INTERFACE.ReadKeyStroke]
+    mov rdx, .input_key
+
+.read_key:
+; Call the EFI ReadKeyStroke function until a key is pressed.
+;
+; The status code is returned in the RAX register. If a key is successfully
+; read, it is stored in the `.input_key` local variable through a pointer in
+; the RDX register.
+;
+    sub rsp, 32
+    call rbx
+    add rsp, 32
+
+    mov r8, EFI_NOT_READY
+    cmp rax, r8
+    je .read_key
+
+.process_key:
+; Move the character read from stdin into the RAX register.
+;
+; Carriage returns ("\r") are replaced with newlines ("\n").
+;
+    movzx rax, word [.input_key.UnicodeChar]
+    cmp ax, 0xD
+    jne .no_enter
+    mov rax, 0xA
+
+.no_enter:
+; Echo the character onto stdout and return.
+;
+; The echo will likely be removed in the future but is here currently for
+; debugging purposes.
+;
+    push rax
+    mov [.char_buffer], al
+    mov rcx, .char_buffer
+    mov rdx, 1
+    call sys_print_string
+    pop rax
+    ret
+
+section '.data' data readable writeable
+
+system_table dq ?
+
+sys_print_string.output_buffer rq 0x400
+
+sys_read_char.input_key INPUT_KEY
+sys_read_char.char_buffer db ?
+
diff --git a/forth.asm b/forth.asm
@@ -0,0 +1,29 @@
+; An in-progress Forth compiler and tutorial for UEFI x86_64 systems.
+;
+; Written By Christian Ermann
+;
+
+format pe64 dll efi
+entry main
+
+section '.text' code executable readable
+
+include 'efi.asm'
+
+main:
+; Initialize the system and print a version string.
+;
+; Later on, this will start the interpreter as well.
+;
+    call sys_initialize
+    call sys_clear_screen
+    mov rcx, version_string
+    mov rdx, version_string.length
+    call sys_print_string
+    call sys_read_char
+
+section '.data' readable writable
+
+version_string db 'soup forth v0.1', 0xA
+.length = $ - version_string
+
diff --git a/run.sh b/run.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+exec qemu-system-x86_64 \
+         -nographic \
+         -serial mon:stdio \
+         -net none \
+         -bios /usr/share/ovmf/OVMF.fd \
+         -drive file=boot.img,format=raw
+