/out
+/main
/OVMF_CODE.fd
/OVMF_VARS.fd
OVMF_VARS.fd: /usr/share/ovmf/x64/OVMF_VARS.fd
cp $< $@
-out/main: main.asm impl.asm bootstrap.asm sys.f uefi.asm
+out/main: main.asm impl.asm bootstrap.asm sys.f os/uefi.asm
mkdir -p out
- fasm $< out/main
+ OS_INCLUDE=os/uefi.asm fasm $< $@
+
+main: main.asm impl.asm bootstrap.asm sys.f os/linux.asm
+ OS_INCLUDE=os/linux.asm fasm $< $@
.PHONY: clean
clean:
-;; vim: syntax=fasm
-
-section '.text' code readable executable
+os_code_section
macro printlen msg, len {
push rsi
mov rcx, msg
mov rdx, len
- sys_print_string
+ call os_print_string
sub rsp, 8
pop rsi
pop rdi
printlen rdi, [.length]
newline
- sys_terminate 100
+ mov rax, 100
+ call os_terminate
-section '.data' readable writable
+os_data_section
find.search_length dq ?
find.search_buffer dq ?
;; vim: syntax=fasm
-include "uefi.asm"
-
-;; "Syscalls" {{{
-
-;; [NOTE] Volatile registers Linux (syscalls) vs UEFI
+;; At compile-time we load the module given by the environment variable
+;; OS_INCLUDE. All of the following these procedures should preserve the value
+;; of RSI and RSP. They may use other registers as they like.
;;
-;; Linux syscalls: RAX, RCX, R11
-;; UEFI: RAX, RCX, R11, RDX, R8, R9, R10
-
-;; We are in the process of replacing our dependency on Linux with a dependency
-;; on UEFI. The following macros attempt to isolate what would be syscalls in
-;; Linux; thus, we will be able to replace these with UEFI-based implementations,
-;; and in theory we should expect the program to work.
-
-;; Print a string of a given length.
+;; The module should provide the following:
;;
-;; Input:
-;; - RCX = Pointer to buffer
-;; - RDX = Buffer length
+;; os_code_section
+;; Macro to start the text segment.
;;
-;; Clobbers: RAX, RCX, R11, RDI, RSI
-macro sys_print_string {
- 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.
+;; os_data_section
+;; Macro to start the data segment.
;;
-;; Input:
-;; - RSI = Character buffer
+;; os_initialize
+;; Called at initialization.
;;
-;; Output:
-;; - BYTE [RSI] = Character
+;; os_print_string
+;; Takes a string buffer in RCX and the length in RDX, and prints the string
+;; to the console.
;;
-;; Clobbers: RAX, RCX, R11, RDI, RSI, RDX
-macro sys_read_char {
- 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, code
- call uefi_terminate
-}
-
-;; }}}
+;; os_read_char
+;; Wait for the user to type a key, and then put the corresponding ASCII byte
+;; into RAX.
+;;
+;; os_terminate
+;; Shut down the system, returning the error code given in RAX.
+include '%OS_INCLUDE%'
;; The code in this macro is placed at the end of each Forth word. When we are
;; executing a definition, this code is what causes execution to resume at the
.start:
}
-section '.text' code readable executable
-
include "impl.asm" ; Misc. subroutines
include "bootstrap.asm" ; Forth words encoded in Assembly
+os_code_section
+
main:
cld ; Clear direction flag so LODSQ does the right thing.
mov rbp, return_stack_top ; Initialize return stack
- call uefi_initialize
+ call os_initialize
mov rax, MAIN
jmp qword [rax]
lea rcx, [rsp]
mov rdx, 1
- sys_print_string
+ call os_print_string
add rsp, 8
popr rax
jne .from_buffer
;; Reading user input
- push rsi
- mov rsi, .buffer
- sys_read_char
- pop rsi
-
- movzx rax, byte [.buffer]
+ call os_read_char
ret
.from_buffer:
pop rdx ; Length
pop rcx ; Buffer
- sys_print_string
+ call os_print_string
popr rsi
popr rax
;; Exit the program cleanly.
forth_asm TERMINATE, 'TERMINATE'
- sys_terminate 0
+ mov rax, 0
+ call os_terminate
;; Duplicate a pair of elements.
forth_asm PAIRDUP, '2DUP'
;; Print the buffer
mov rcx, .buffer
mov rdx, [.printed_length]
- sys_print_string
+ call os_print_string
;; Restore RSI and continue execution
pop rsi
mov [.length], 0
.read_char:
- mov rsi, .char_buffer
- sys_read_char
-
- mov al, [.char_buffer]
+ call os_read_char
cmp al, '"'
je .done
dq LIT, input_buffer_length
dq EXIT
-section '.data' readable writable
+os_data_section
;; 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
;; 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.
-sysf file 'sys.f'
+sysf:
+file 'sys.f'
+file 'example.f'
sysf.len = $ - sysf
--- /dev/null
+format ELF64 executable
+entry main
+
+macro os_code_section {
+ segment readable executable
+}
+
+macro os_data_section {
+ segment readable writable
+}
+
+os_code_section
+
+os_initialize:
+ ret
+
+os_print_string:
+ push rsi
+ mov rax, 1
+ mov rdi, 1
+ mov rsi, rcx
+ syscall
+ pop rsi
+ ret
+
+os_read_char:
+ push rsi
+ mov rax, 0
+ mov rdi, 0
+ mov rsi, .buffer
+ mov rdx, 1
+ syscall
+ pop rsi
+ movzx rax, byte [.buffer]
+ ret
+
+os_terminate:
+ mov rdi, rax
+ mov rax, $3C
+ syscall
+
+os_data_section
+
+os_read_char.buffer db ?
+
--- /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
+
+;; }}}
+
+macro os_code_section {
+ section '.text' code readable executable
+}
+
+macro os_data_section {
+ section '.data' readable writable
+}
+
+section '.text' code executable readable
+
+os_initialize:
+ ; At program startup, RDX contains an EFI_SYSTEM_TABLE*.
+ mov [system_table], rdx
+ ret
+
+os_print_string:
+ ;; 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 rdi, .output_buffer ; Current location in output string
+
+.copy_byte:
+ ;; When there are no characters left in the input string, we are done.
+ cmp rdx, 0
+ je .done
+
+ ;; Load byte from input string
+ mov al, byte [rcx]
+
+ ;; Copy byte to output string
+
+ cmp al, $A
+ jne .not_newline
+.newline:
+ ;; It's a newline; replace it with '\r\n' in output string.
+ mov byte [rdi], $D
+ inc rdi
+ mov byte [rdi], 0
+ inc rdi
+ mov byte [rdi], $A
+ inc rdi
+ mov byte [rdi], 0
+ inc rdi
+ jmp .pop
+
+.not_newline:
+ ;; Not a newline, proceed as normal:
+ mov byte [rdi], al
+ inc rdi
+
+ ;; The output string has words rather than bytes for charactesr, so we need
+ ;; to add an extra zero:
+ mov byte [rdi], 0
+ inc rdi
+
+.pop:
+ ;; We finished copying character to output string, so pop it from the input
+ ;; string.
+ inc rcx
+ dec rdx
+
+ jmp .copy_byte
+.done:
+ ;; Append a final null-word:
+ mov word [rdi], 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
+
+os_read_char:
+.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
+
+ movzx rax, word [input_key.UnicodeChar]
+
+ ;; Special handling of enter (UEFI gives us '\r', but we want '\n'.)
+ cmp ax, $D
+ jne .no_enter
+ mov al, $A
+.no_enter:
+
+ push rax
+ ;; Print the character
+ mov [char_buffer], al
+ mov rcx, char_buffer
+ mov rdx, 1
+ call os_print_string
+ pop rax
+
+ ret
+
+;; Terminate with the given error code.
+;;
+;; Inputs:
+;; - RCX = Error code
+os_terminate:
+ mov rcx, terminated_msg
+ mov rdx, terminated_msg.len
+ call os_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
+
+os_print_string.output_buffer rq 0x400
+
+char_buffer db ?
+
+input_key EFI_INPUT_KEY
+++ /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
-uefi_print_string:
- ;; 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 rdi, .output_buffer ; Current location in output string
-
-.copy_byte:
- ;; When there are no characters left in the input string, we are done.
- cmp rdx, 0
- je .done
-
- ;; Load byte from input string
- mov al, byte [rcx]
-
- ;; Copy byte to output string
-
- cmp al, $A
- jne .not_newline
-.newline:
- ;; It's a newline; replace it with '\r\n' in output string.
- mov byte [rdi], $D
- inc rdi
- mov byte [rdi], 0
- inc rdi
- mov byte [rdi], $A
- inc rdi
- mov byte [rdi], 0
- inc rdi
- jmp .pop
-
-.not_newline:
- ;; Not a newline, proceed as normal:
- mov byte [rdi], al
- inc rdi
-
- ;; The output string has words rather than bytes for charactesr, so we need
- ;; to add an extra zero:
- mov byte [rdi], 0
- inc rdi
-
-.pop:
- ;; We finished copying character to output string, so pop it from the input
- ;; string.
- inc rcx
- dec rdx
-
- jmp .copy_byte
-.done:
- ;; Append a final null-word:
- mov word [rdi], 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)
-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
-
- ;; Special handling of enter (UEFI gives us '\r', but we want '\n'.)
- cmp ax, $D
- jne .no_enter
- mov byte [r15], $A
-.no_enter:
-
- ;; Print the character
- mov rcx, r15
- mov rdx, 1
- call uefi_print_string
-
- 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