Skip to content

Commit

Permalink
Make it run on UEFI!
Browse files Browse the repository at this point in the history
The program is now usable, albeit barely, running inside QEMU, with the only
dependency being UEFI! Very cool!

Right now, no output is printed to the screen when you type characters, newlines
aren't printed correctly, and the program doesn't accept Enter as valid
whitespace when reading input.
  • Loading branch information
Jonas committed Mar 9, 2020
1 parent 78d7fb7 commit 6280c68
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 97 deletions.
4 changes: 3 additions & 1 deletion .gitignore
@@ -1 +1,3 @@
/main
/out
/OVMF_CODE.fd
/OVMF_VARS.fd
25 changes: 19 additions & 6 deletions Makefile
@@ -1,10 +1,23 @@
.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
4 changes: 2 additions & 2 deletions impl.asm
@@ -1,6 +1,6 @@
;; vim: syntax=fasm

segment readable executable
section '.text' code readable executable

macro printlen msg, len {
push rsi
Expand Down Expand Up @@ -231,7 +231,7 @@ parse_number:
newline
sys_terminate 100

segment readable writable
section '.data' readable writable

find.search_length dq ?
find.search_buffer dq ?
Expand Down
48 changes: 30 additions & 18 deletions main.asm
@@ -1,6 +1,6 @@
;; vim: syntax=fasm

format ELF64 executable
include "uefi.asm"

;; "Syscalls" {{{

Expand All @@ -22,10 +22,15 @@ format ELF64 executable
;;
;; 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.
Expand All @@ -38,16 +43,25 @@ macro sys_print_string {
;;
;; 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
}

;; }}}
Expand Down Expand Up @@ -109,9 +123,7 @@ macro forth_asm label, name, immediate {
.start:
}

segment readable executable

entry main
section '.text' code readable executable

include "impl.asm" ; Misc. subroutines
include "bootstrap.asm" ; Forth words encoded in Assembly
Expand All @@ -120,6 +132,8 @@ main:
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]

Expand Down Expand Up @@ -597,7 +611,7 @@ forth INPUT_LENGTH, 'INPUT-LENGTH'
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
Expand Down Expand Up @@ -637,8 +651,6 @@ here_top rq $4000
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.
Expand Down
65 changes: 25 additions & 40 deletions uefi/main.asm → uefi.asm
Expand Up @@ -3,20 +3,6 @@
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
Expand Down Expand Up @@ -74,33 +60,19 @@ struct EFI_INPUT_KEY

section '.text' code executable readable

main:
uefi_initialize:
; 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:
;;
;; [TODO] Handle newlines correctly. (I.e. translate '\n' to '\r\n'.)
uefi_print_string:
mov r8, rcx
mov r9, rdx

Expand Down Expand Up @@ -146,13 +118,16 @@ print_string:
;;
;; Inputs:
;; - RCX = Character buffer (1 byte)
read_char:
;;
;; [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 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
mov rdx, input_key ; EFI_INPUT_KEY* rdx
sub rsp, 32
call rbx
add rsp, 32
Expand All @@ -166,14 +141,24 @@ read_char:

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*
system_table dq ? ; EFI_SYSTEM_TABLE*

hello_string db 'Hello, world!', 0xD, 0xA, 'Here is some more text.', 0xD, 0xA
.len = $ - hello_string
terminated_msg db 0xD, 0xA, '(The program has terminated.)', 0xD, 0xA
.len = $ - terminated_msg

print_string.output_buffer rq 0x400
uefi_print_string.output_buffer rq 0x400

char_buffer db ?

Expand Down
3 changes: 0 additions & 3 deletions uefi/.gitignore

This file was deleted.

27 changes: 0 additions & 27 deletions uefi/Makefile

This file was deleted.

0 comments on commit 6280c68

Please sign in to comment.