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
+