-/main
+/out
+/OVMF_CODE.fd
+/OVMF_VARS.fd
-.PHONY: run
-run: main
- cat sys.f - | ./main
+.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 \
+ -drive format=raw,file=fat:rw:out \
+ -display type=gtk,zoom-to-fit=on
-main: main.asm impl.asm bootstrap.asm sys.f
- fasm $< $@
+# Assuming 'ovmf' package on Arch Linux is installed.
+OVMF_CODE.fd: /usr/share/ovmf/x64/OVMF_CODE.fd
+ cp $< $@
+OVMF_VARS.fd: /usr/share/ovmf/x64/OVMF_VARS.fd
+ cp $< $@
+
+out/main: main.asm impl.asm bootstrap.asm sys.f uefi.asm
+ mkdir -p out
+ fasm $< out/main
.PHONY: clean
clean:
- rm -f main
+ rm -rf out OVMF_CODE.fd OVMF_VARS.fd
;; vim: syntax=fasm
-segment readable executable
+section '.text' code readable executable
macro printlen msg, len {
push rsi
newline
sys_terminate 100
-segment readable writable
+section '.data' readable writable
find.search_length dq ?
find.search_buffer dq ?
;; vim: syntax=fasm
-format ELF64 executable
+include "uefi.asm"
;; "Syscalls" {{{
;;
;; Clobbers: RAX, RCX, R11, RDI, RSI
macro sys_print_string {
- mov rax, 1
- mov rdi, 1
- mov rsi, rcx
- syscall
+ push r8
+ push r9
+ push r10
+
+ call uefi_print_string
+
+ pop r10
+ pop r9
+ pop r8
}
;; Read a character from the user into the given buffer.
;;
;; Clobbers: RAX, RCX, R11, RDI, RSI, RDX
macro sys_read_char {
- mov rax, 0
- mov rdi, 0
- mov rdx, 1
- syscall
+ push rbx
+ push r8
+ push r9
+ push r10
+ push r15
+
+ mov rcx, rsi
+ call uefi_read_char
+
+ pop r15
+ pop r10
+ pop r9
+ pop r8
+ pop rbx
}
macro sys_terminate code {
- mov rax, $3C
- mov rdi, code
- syscall
+ mov rax, code
+ call uefi_terminate
}
;; }}}
.start:
}
-segment readable executable
-
-entry main
+section '.text' code readable executable
include "impl.asm" ; Misc. subroutines
include "bootstrap.asm" ; Forth words encoded in Assembly
cld ; Clear direction flag so LODSQ does the right thing.
mov rbp, return_stack_top ; Initialize return stack
+ call uefi_initialize
+
mov rax, MAIN
jmp qword [rax]
dq LIT, input_buffer_length
dq EXIT
-segment readable writable
+section '.data' readable writable
;; The LATEST variable holds a pointer to the word that was last added to the
;; dictionary. This pointer is updated as new words are added, and its value is
rq $2000
return_stack_top:
-segment readable
-
;; We store some Forth code in sys.f that defined common words that the user
;; would expect to have available at startup. To execute these words, we just
;; include the file directly in the binary, and then interpret it at startup.
--- /dev/null
+;; vim: syntax=fasm
+
+format pe64 dll efi
+entry main
+
+;; EFI struct definitions {{{
+
+EFI_NOT_READY = 0x8000_0000_0000_0000 or 6
+
+;; 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
+
+struc EFI_SIMPLE_TEXT_INPUT_PROTOCOL {
+ .Reset dq ? ; EFI_INPUT_RESET
+ .ReadKeyStroke dq ? ; EFI_INPUT_READ_KEY
+ ; ...
+}
+struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL
+
+struc EFI_INPUT_KEY {
+ .ScanCode dw ? ; UINT16
+ .UnicodeChar dw ? ; CHAR16
+ align 8
+}
+struct EFI_INPUT_KEY
+
+;; }}}
+
+section '.text' code executable readable
+
+uefi_initialize:
+ ; At program startup, RDX contains an EFI_SYSTEM_TABLE*.
+ mov [system_table], rdx
+ ret
+
+;; Print a string of the given length.
+;;
+;; Inputs:
+;; - RCX = String buffer
+;; - RDX = String length
+;;
+;; [TODO] Handle newlines correctly. (I.e. translate '\n' to '\r\n'.)
+uefi_print_string:
+ mov r8, rcx
+ mov r9, rdx
+
+ mov r10, r9
+ add r10, r10
+
+ ; We take an input string of bytes without any terminator. We need to turn
+ ; this string into a string of words, terminated by a null character.
+ mov rcx, 0
+ mov rsi, 0
+.copy_byte:
+ cmp rcx, r10
+ je .done
+
+ mov al, byte [r8 + rsi]
+ lea rdx, [.output_buffer + rcx]
+ mov byte [rdx], al
+ inc rcx
+ inc rsi
+
+ lea rdx, [.output_buffer + rcx]
+ mov byte [rdx], 0
+ inc rcx
+
+ jmp .copy_byte
+.done:
+ lea rdx, [.output_buffer + r10]
+ mov byte [rdx], 0
+
+ ; At this point we have our null-terminated word-string at .output_buffer. Now
+ ; we just need to print it.
+
+ mov rcx, [system_table] ; EFI_SYSTEM_TABLE* rcx
+ mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut] ; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* rcx
+ mov rdx, .output_buffer
+ mov rbx, [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString] ; EFI_TEXT_STRING rbx
+ sub rsp, 32
+ call rbx
+ add rsp, 32
+ ret
+
+;; Read a character as an ASCII byte into the given buffer.
+;;
+;; Inputs:
+;; - RCX = Character buffer (1 byte)
+;;
+;; [TODO] Show the user's input on screen while they are typing.
+;; [TODO] Handle enter key correctly (should return '\n').
+uefi_read_char:
+ mov r15, rcx
+.read_key:
+ mov rcx, [system_table] ; EFI_SYSTEM_TABLE* rcx
+ mov rcx, [rcx + EFI_SYSTEM_TABLE.ConIn] ; EFI_SIMPLE_TEXT_INPUT_PROTOCOL* rcx
+ mov rbx, [rcx + EFI_SIMPLE_TEXT_INPUT_PROTOCOL.ReadKeyStroke] ; EFI_INPUT_READ_KEY rbx
+ mov rdx, input_key ; EFI_INPUT_KEY* rdx
+ sub rsp, 32
+ call rbx
+ add rsp, 32
+
+ mov r8, EFI_NOT_READY
+ cmp rax, r8
+ je .read_key
+
+ mov ax, [input_key.UnicodeChar]
+ mov [r15], al
+
+ ret
+
+;; Terminate with the given error code.
+;;
+;; Inputs:
+;; - RCX = Error code
+uefi_terminate:
+ mov rcx, terminated_msg
+ mov rdx, terminated_msg.len
+ call uefi_print_string
+ jmp $
+
+section '.data' readable writable
+
+system_table dq ? ; EFI_SYSTEM_TABLE*
+
+terminated_msg db 0xD, 0xA, '(The program has terminated.)', 0xD, 0xA
+.len = $ - terminated_msg
+
+uefi_print_string.output_buffer rq 0x400
+
+char_buffer db ?
+
+input_key EFI_INPUT_KEY
+++ /dev/null
-/out
-/OVMF_CODE.fd
-/OVMF_VARS.fd
+++ /dev/null
-.PHONY: qemu
-qemu: out/main out/hello 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 \
- -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
- cp $< $@
-OVMF_VARS.fd: /usr/share/ovmf/x64/OVMF_VARS.fd
- cp $< $@
-
-out/main: main.asm
- mkdir -p out
- fasm $< out/main
-
-out/hello:
- mkdir -p out
- echo -e "Hello!\nThis is a test." > out/hello
-
-.PHONY: clean
-clean:
- rm -f main
+++ /dev/null
-;; vim: syntax=fasm
-
-format pe64 dll efi
-entry main
-
-;; [TODO] We need to provide the following:
-;; - [X] Print a string of a given length
-;; - [ ] Print a single character
-;; - [ ] Terminate the program (? - What should this do?)
-;; - [X] Read a single character
-;; - This should allow the user to type in a string, and then feed the
-;; buffer to us one character at a time.
-;; - [ ] We want to show the user's input on the screen while reading
-;; - [ ] Read a file that was bundled with the program
-;; - It looks like we can use EFI_LOAD_FILE_PROTOCOL.LoadFile() to load
-;; a file into a buffer. In order to be able to use this, we need to
-;; have some way of interpreting a static buffer instead of reading as
-;; we go.
-
-;; EFI struct definitions {{{
-
-EFI_NOT_READY = 0x8000_0000_0000_0000 or 6
-
-;; 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
-
-struc EFI_SIMPLE_TEXT_INPUT_PROTOCOL {
- .Reset dq ? ; EFI_INPUT_RESET
- .ReadKeyStroke dq ? ; EFI_INPUT_READ_KEY
- ; ...
-}
-struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL
-
-struc EFI_INPUT_KEY {
- .ScanCode dw ? ; UINT16
- .UnicodeChar dw ? ; CHAR16
- align 8
-}
-struct EFI_INPUT_KEY
-
-;; }}}
-
-section '.text' code executable readable
-
-main:
- ; At program startup, RDX contains an EFI_SYSTEM_TABLE*.
- mov [system_table], rdx
-
- mov rcx, hello_string
- mov rdx, hello_string.len
- call print_string
-
- mov rcx, char_buffer
- call read_char
-
- mov rcx, char_buffer
- mov rdx, 1
- call print_string
-
- mov rcx, hello_string
- mov rdx, hello_string.len
- call print_string
-
- ret
-
-;; Print a string of the given length.
-;;
-;; Inputs:
-;; - RCX = String buffer
-;; - RDX = String length
-print_string:
- mov r8, rcx
- mov r9, rdx
-
- mov r10, r9
- add r10, r10
-
- ; We take an input string of bytes without any terminator. We need to turn
- ; this string into a string of words, terminated by a null character.
- mov rcx, 0
- mov rsi, 0
-.copy_byte:
- cmp rcx, r10
- je .done
-
- mov al, byte [r8 + rsi]
- lea rdx, [.output_buffer + rcx]
- mov byte [rdx], al
- inc rcx
- inc rsi
-
- lea rdx, [.output_buffer + rcx]
- mov byte [rdx], 0
- inc rcx
-
- jmp .copy_byte
-.done:
- lea rdx, [.output_buffer + r10]
- mov byte [rdx], 0
-
- ; At this point we have our null-terminated word-string at .output_buffer. Now
- ; we just need to print it.
-
- mov rcx, [system_table] ; EFI_SYSTEM_TABLE* rcx
- mov rcx, [rcx + EFI_SYSTEM_TABLE.ConOut] ; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* rcx
- mov rdx, .output_buffer
- mov rbx, [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString] ; EFI_TEXT_STRING rbx
- sub rsp, 32
- call rbx
- add rsp, 32
- ret
-
-;; Read a character as an ASCII byte into the given buffer.
-;;
-;; Inputs:
-;; - RCX = Character buffer (1 byte)
-read_char:
- mov r15, rcx
-.read_key:
- mov rcx, [system_table] ; EFI_SYSTEM_TABLE* rcx
- mov rcx, [rcx + EFI_SYSTEM_TABLE.ConIn] ; EFI_SIMPLE_TEXT_INPUT_PROTOCOL* rcx
- mov rbx, [rcx + EFI_SIMPLE_TEXT_INPUT_PROTOCOL.ReadKeyStroke] ; EFI_INPUT_READ_KEY rbx
- mov rdx, input_key ; EFI_INPUT_KEY* rdx
- sub rsp, 32
- call rbx
- add rsp, 32
-
- mov r8, EFI_NOT_READY
- cmp rax, r8
- je .read_key
-
- mov ax, [input_key.UnicodeChar]
- mov [r15], al
-
- ret
-
-section '.data' readable writable
-
-system_table dq ? ; EFI_SYSTEM_TABLE*
-
-hello_string db 'Hello, world!', 0xD, 0xA, 'Here is some more text.', 0xD, 0xA
-.len = $ - hello_string
-
-print_string.output_buffer rq 0x400
-
-char_buffer db ?
-
-input_key EFI_INPUT_KEY