summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2017-11-28 16:49:24 -0500
committerJoseph Hunkeler <jhunkeler@gmail.com>2017-11-28 16:49:24 -0500
commit1dd05ebe61c95985b16d170bf2d3f081a02dfd7d (patch)
tree216d65aa993cba84c1b58e7152efd516b37121cc
downloadminos-1dd05ebe61c95985b16d170bf2d3f081a02dfd7d.tar.gz
Initial commit
-rw-r--r--.gitignore2
-rw-r--r--LICENSE.txt22
-rw-r--r--Makefile19
-rw-r--r--ascii.asm38
-rw-r--r--boot.asm246
-rw-r--r--console.asm284
-rw-r--r--constants.asm10
-rw-r--r--disk.asm85
-rw-r--r--kernel.asm77
-rw-r--r--keyboard.asm82
-rw-r--r--make.bat9
-rw-r--r--scancodes.asm9
-rw-r--r--stdio.asm28
-rw-r--r--string.asm174
14 files changed, 1085 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7cf5001
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.bin
+*.img
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..ff33a29
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2017 Joseph Hunkeler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6aa7a1d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+QEMU=qemu-system-i386
+
+all: system.img
+
+system.img: boot.bin kernel.bin
+ cat $^ > $@
+
+boot.bin: boot.asm
+ nasm -f bin -o $@ $<
+
+kernel.bin: kernel.asm
+ nasm -f bin -o $@ $<
+
+run: system.img
+ $(QEMU) -m 16 -fda $<
+
+clean:
+ rm *.bin *.img
+.PHONY: clean
diff --git a/ascii.asm b/ascii.asm
new file mode 100644
index 0000000..2616042
--- /dev/null
+++ b/ascii.asm
@@ -0,0 +1,38 @@
+%ifndef _ASCII_ASM
+%define _ASCII_ASM
+
+NUL equ 00h ; null
+SOH equ 01h ; start of heading
+STX equ 02h ; start of text
+ETX equ 03h ; end of text
+EOT equ 04h ; end of transmission
+ENQ equ 05h ; enquiry
+ACK equ 06h ; acknowledge
+BEL equ 07h ; bell (audible)
+BS equ 08h ; backspace
+TAB equ 09h ; horizontal tab
+LF equ 0Ah ; line feed
+VT equ 0Bh ; vertical tab
+FF equ 0Ch ; form feed
+CR equ 0Dh ; carriage return
+SHO equ 0Eh ; shift out
+SHI equ 0Fh ; shift in
+DLE equ 10h ; data link escape
+DC1 equ 11h ; device control 1
+DC2 equ 12h ; device control 2
+DC3 equ 13h ; device control 3
+DC4 equ 14h ; device control 4
+NAK equ 15h ; negative acknowledge
+SYN equ 16h ; synchronous idle
+ETB equ 17h ; end of transmission block
+CAN equ 18h ; cancel
+EM equ 19h ; end of medium
+SUBST equ 1Ah ; substitute
+ESC equ 1Bh ; escape
+FSEP equ 1Ch ; file separator
+GSEP equ 1Dh ; group separator
+RSEP equ 1Eh ; record separator
+USEP equ 1Fh ; unit separator
+SPC equ 20h ; space
+
+%endif
diff --git a/boot.asm b/boot.asm
new file mode 100644
index 0000000..d6ddfa1
--- /dev/null
+++ b/boot.asm
@@ -0,0 +1,246 @@
+bits 16
+
+jmp start
+
+CR equ 0Dh
+LF equ 0Ah
+K_CS_ADDR equ 007eh
+
+start:
+ mov ax, 07c0h
+ mov ds, ax ; set data segment
+ mov ax, 07e0h
+ mov ss, ax ; set stack segment
+ mov sp, 2000h ; 8192k
+
+ push bp ; set up stack frame
+ mov bp, sp
+ sub sp, 2 ; local storage
+
+ mov [drive0], dl ; save first detected drive
+
+ call cls ; clear screen
+
+ push 0 ;
+ call setcursor ; set cursor position
+
+ push banner ;
+ call puts ; print version
+ add sp, 2 ; clean up
+
+ push word [drive0]
+ call disk_reset
+ add sp, 2
+
+ xor cx, cx
+ mov ax, K_CS_ADDR
+ mov es, ax
+
+ push msg_loading
+ call puts
+ add sp, 2
+
+ mov bx, 0
+ mov di, 2 ; start at sector
+.loader:
+ mov al, 1 ; read one sector
+ mov cx, di ; track/cyl | sector number
+ mov dh, 0 ; head number
+ mov dl, [drive0] ; drive number
+ call disk_read
+
+ push '.'
+ call putc
+ add sp, 2
+
+ add bx, 200h ; increment address by 512 bytes
+ inc di ; increment sector read count
+ cmp di, 16 ; 8K (i'll make this smarter later)
+ jle .loader ; keep reading
+
+ push msg_done
+ call puts
+ add sp, 2
+
+ add sp, 2 ; remove local storage
+ mov sp, bp
+ pop bp
+
+ mov dx, [drive0] ; the kernel will need the boot drive number
+ jmp K_CS_ADDR:0000h ; jump to kernel address
+
+ cli ; disable interrupts
+ jmp $ ; hang
+
+panic:
+ ; Hang system with supplied error message
+ push bp
+ mov bp, sp
+
+ push error_msg_panic ; i.e. 'PANIC:'
+ call puts
+ add sp, 2
+
+ push word [bp + 4] ; address of error string buffer
+ call puts ; print error
+ add sp, 2
+
+ cli ; disable interrupts
+ jmp $ ; hang (no return)
+ ; stack is dead
+
+
+disk_reset:
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 00h ; reset disk
+ mov dl, [bp + 4] ; disk number
+ int 13h ; BIOS disk service
+ jnc .success
+
+ push error_msg_disk_reset
+ call panic
+
+.success:
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+
+disk_read:
+ push bp
+ mov bp, sp
+
+ push di
+ mov di, 3 ; retry counter
+.readloop:
+ push ax
+ push bx
+ push cx
+
+ mov ah, 02h ; BIOS - read disk sectors
+ int 13h ; BIOS disk service
+
+ jnc .success
+
+ push dx
+ call disk_reset
+ add sp, 2
+
+ pop cx
+ pop bx
+ pop ax
+
+ dec di
+ jnz .readloop
+
+ push error_msg_disk_read
+ call panic
+ add sp, 2
+
+.success:
+ pop di
+ mov sp, bp
+ pop bp
+ ret
+
+
+cls:
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 07h ; BIOS - scroll down
+ mov al, 00h ; lines to scroll (0 == entire screen)
+ mov bx, 0700h ; color white/black
+ ; & video page zero
+ mov cx, 0
+ mov dh, 24 ; rows
+ mov dl, 79 ; cols
+ int 10h ; BIOS video service
+ popa
+
+ mov sp, bp
+ pop bp
+ ret
+
+
+setcursor:
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 02h ; BIOS - set cursor position
+ mov bh, 0 ; video page zero
+ mov dx, [bp + 4] ; address of new cursor value
+ int 10h ; BIOS video service
+
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+
+putc:
+ ; Write single character at cursor position
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 0eh ; BIOS - teletype
+ mov al, [bp + 4] ; character
+ mov bx, 0 ; video page zero
+ int 10h ; BIOS video service
+
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+
+puts:
+ ; Write string buffer at cursor position
+ push bp
+ mov bp, sp
+ pusha
+
+ mov si, [bp + 4] ; address of string buffer
+ mov bx, 0000h ;
+ mov ah, 0eh ; BIOS - teletype
+
+.loop:
+ lodsb ; load byte at [si] into al
+ or al, 0 ; 0 | 0 = 0 (detect null terminator)
+ je .end
+ int 10h ; BIOS video service
+ jmp .loop
+.end:
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+
+
+; data
+drive0: dw 0
+banner: db "MINOS Bootloader", CR, LF, 0
+
+; General messages
+msg_loading: db "Loading", 0
+msg_done: db "done!", CR, LF, 0
+msg_disk_reset: db "Drive reset successful.", CR, LF, 0
+msg_disk_read: db "Sector read successful.", CR, LF, 0
+
+; Error messages
+error_msg_panic: db "PANIC: ", 0
+error_msg_disk_reset: db "Drive reset failed!", CR, LF, 0
+error_msg_disk_read: db "Drive read failed!", CR, LF, 0
+
+; boot signature
+times 510-($-$$) db 0
+dw 0xAA55
+
diff --git a/console.asm b/console.asm
new file mode 100644
index 0000000..f992705
--- /dev/null
+++ b/console.asm
@@ -0,0 +1,284 @@
+%ifndef _CONSOLE_ASM
+%define _CONSOLE_ASM
+
+%include "constants.asm"
+%include "stdio.asm"
+%include "string.asm"
+%include "keyboard.asm"
+
+MAX_ROWS equ 25
+MAX_COLS equ 80
+
+putc:
+ ; Write single character at cursor position
+ push bp
+ mov bp, sp
+ pusha
+
+ cmp al, 20h
+ jl .non_graphical
+
+ mov ah, 0ah ; BIOS - write character
+ mov bx, 00h ; video page zero
+ mov cx, 01h ; repeat character N times
+ int 10h ; BIOS video service
+
+.non_graphical:
+ push ax
+ call console_driver
+ add sp, 2
+
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+
+console_scroll_up:
+ cmp dh, MAX_ROWS - 1
+ jne .no_action
+
+ mov dh, MAX_ROWS - 2
+ push dx
+ call setcursor
+ add sp, 2
+
+ ; scroll window up:
+ mov ah, 06h ; scroll up function id.
+ mov al, 1 ; lines to scroll.
+ mov bx, 0700h ; attribute for new lines.
+ mov cl, 0 ; upper col.
+ mov ch, 0 ; upper row.
+ mov dl, MAX_COLS ; lower col.
+ mov dh, MAX_ROWS ; lower row.
+ int 10h
+.no_action:
+ ret
+
+
+console_driver:
+ push bp
+ mov bp, sp
+
+ call console_cursor_getpos
+ mov dh, [cursor_row]
+ mov dl, [cursor_col]
+
+ mov ax, [bp + 4]
+
+.do_fn:
+.do_scancode:
+ cmp al, 00h ; when AL is 00h, check scan-code
+ jne .do_ascii
+
+ cmp ah, SC_ARROW_LEFT
+ je .handle_sc_arrow_left
+
+ cmp ah, SC_ARROW_RIGHT
+ je .handle_sc_arrow_right
+
+
+.handle_sc_arrow_left:
+ dec dl
+ jmp .return
+
+.handle_sc_arrow_right:
+ inc dl
+ jmp .return
+
+.do_ascii:
+ ; ASCII control block
+ cmp al, SPC
+ jae .handle_SPC
+
+ cmp al, TAB
+ je .handle_TAB
+
+ cmp al, BS
+ je .handle_BS
+
+ cmp al, CR
+ je .handle_CR
+
+ cmp al, LF
+ je .handle_LF
+
+ ; etc...
+ jmp .return
+
+
+.handle_SPC:
+ inc dl
+ cmp dl, MAX_COLS
+ jge .handle_CR
+ jmp .return
+
+.handle_TAB:
+ add dl, 4
+ jmp .return
+
+.handle_BS:
+ dec dl
+ cmp dl, 0
+ jl .skip_bs
+
+ push dx
+ call setcursor
+ add sp, 2
+
+.skip_bs:
+ mov ah, 0ah
+ mov al, 20h
+ mov bx, 00h
+ mov cx, 1
+ int 10h
+ jmp .return_noupdate
+
+.handle_CR:
+ mov dl, 0 ; set column zero
+ ; fall through
+
+.handle_LF:
+ inc dh ; increment row
+ jmp .return
+
+
+.return:
+ push dx
+ call setcursor
+ add sp, 2
+
+
+.return_noupdate:
+ call console_scroll_up
+ mov sp, bp
+ pop bp
+ ret
+
+
+cls:
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 07h ; BIOS - scroll down
+ mov al, 00h ; lines to scroll (0 == entire screen)
+ mov bx, 0700h ; color white/black
+ ; & video page zero
+ mov cx, 0
+ mov dh, 24 ; rows
+ mov dl, 79 ; cols
+ int 10h ; BIOS video service
+ popa
+
+ mov sp, bp
+ pop bp
+ ret
+
+console_cursor_getpos:
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 03h ; BIOS - query cursor position and size
+ mov bh, 00h ; video page zero
+ int 10h
+
+ mov [cursor_sl_start], byte ch ; record data
+ mov [cursor_sl_end], byte cl
+ mov [cursor_row], byte dh
+ mov [cursor_col], byte dl
+
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+console_cursor_read:
+ push bp
+ mov bp, sp
+ push ax
+ push bx
+
+ mov ah, 08h ; BIOS - read character/attr at cursor
+ mov bh, 00h ; video page zero
+ int 10h
+
+ mov [cursor_attr], byte ah
+ mov [cursor_char], byte al
+
+ pop bx
+ pop ax
+ mov sp, bp
+ pop bp
+ ret
+
+console_cursor_read_last:
+ push bp
+ mov bp, sp
+ push dx
+
+ call console_cursor_getpos
+ mov dh, [cursor_row]
+ mov dl, [cursor_col]
+
+ cmp dh, 0 ; is this column zero?
+ je .finalize
+
+ sub dl, 1 ; previous column
+ js .prev_row ; column went negative
+ jmp .finalize
+
+.prev_row:
+ cmp dh, 0 ; is this row zero?
+ je .return
+
+ sub dh, 1 ; go up one row
+ add dl, 80 ; return to last column of row (-1 + 80 = 79)
+
+.finalize:
+ push dx
+ call setcursor
+ call console_cursor_getpos
+ call console_cursor_read
+
+ ; restore original cursor position
+ mov dh, [cursor_row]
+ mov dl, [cursor_col]
+ push dx
+ call setcursor
+.return:
+ add sp, 4
+ pop dx
+ mov sp, bp
+ pop bp
+ ret
+
+
+setcursor:
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 02h ; BIOS - set cursor position
+ mov bh, 0 ; video page zero
+ mov dx, [bp + 4] ; address of new cursor value
+ int 10h ; BIOS video service
+
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+
+; data
+cursor_sl_start: db 0
+cursor_sl_end: db 0
+cursor_row: db 0
+cursor_col: db 0
+cursor_row_vram: dw 0
+cursor_col_vram: dw 0
+cursor_vram: dw 0
+cursor_attr: db 0
+cursor_char: db 0
+%endif
diff --git a/constants.asm b/constants.asm
new file mode 100644
index 0000000..56c2496
--- /dev/null
+++ b/constants.asm
@@ -0,0 +1,10 @@
+%ifndef _CONSTANTS_ASM
+%define _CONSTANTS_ASM
+
+LENGTH_ROW equ 0A0h ; NOTE: length in bytes (80 * 2 = 160)
+
+%include "ascii.asm" ; ASCII control codes
+%include "scancodes.asm" ; Keyboard scancodes
+
+%endif
+
diff --git a/disk.asm b/disk.asm
new file mode 100644
index 0000000..3e43c48
--- /dev/null
+++ b/disk.asm
@@ -0,0 +1,85 @@
+%ifndef _DISK_ASM
+%define _DISK_ASM
+
+disk_lba_chs:
+ push bp
+ mov bp, sp
+
+ mov ax, word [bp - 2] ; LBA
+
+ mov sp, bp
+ pop bp
+ ret
+
+disk_reset:
+ push bp
+ mov bp, sp
+ pusha
+
+ mov ah, 00h ; reset disk
+ mov dl, [bp + 4] ; disk number
+ int 13h ; BIOS disk service
+ jnc .success
+
+ push error_msg_disk_reset
+ call panic
+
+.success:
+ push msg_disk_reset
+ call puts
+ add sp, 2
+
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+
+disk_read:
+ push bp
+ mov bp, sp
+
+ push di
+ mov di, 3 ; retry counter
+.readloop:
+ push ax
+ push bx
+ push cx
+
+ mov ah, 02h ; BIOS - read disk sectors
+ int 13h ; BIOS disk service
+
+ jnc .success
+
+ push dx
+ call disk_reset
+ add sp, 2
+
+ pop cx
+ pop bx
+ pop ax
+
+ dec di
+ jnz .readloop
+
+ push error_msg_disk_read
+ call panic
+ add sp, 2
+
+.success:
+ pop di
+ mov sp, bp
+ pop bp
+ ret
+
+
+; data
+drive0: dw 0
+
+msg_disk_reset: db "Drive reset successful.", CR, LF, 0
+msg_disk_read: db "Sector read successful.", CR, LF, 0
+
+error_msg_disk_reset: db "Drive reset failed!", CR, LF, 0
+error_msg_disk_read: db "Drive read failed!", CR, LF, 0
+
+%endif ; _DISK_ASM
diff --git a/kernel.asm b/kernel.asm
new file mode 100644
index 0000000..f613460
--- /dev/null
+++ b/kernel.asm
@@ -0,0 +1,77 @@
+bits 16
+
+jmp kmain
+
+%include "constants.asm"
+%include "string.asm"
+%include "disk.asm"
+%include "console.asm"
+%include "stdio.asm"
+
+
+kmain:
+ cli ; disable interrupts
+ mov ax, cs ; get code segment (i.e. far jump address in bootloader)
+ mov ds, ax ; set data segment
+ mov es, ax ; set extra segment
+ mov ax, 06000h
+ mov ss, ax ; set stack segment
+ mov sp, 0ffffh ; set stack pointer (~64k)
+ sti ; enable interrupts
+
+ mov [drive0], dx ; store bootloader's drive number
+
+ ; reset general purpose registers
+ xor ax, ax
+ xor bx, bx
+ xor cx, cx
+ xor dx, dx
+ xor di, di
+ xor si, si
+ xor bp, bp
+
+
+ push banner
+ call puts
+
+.mainloop:
+ call kbd_read
+ call putc
+
+ jmp .mainloop
+
+ cli
+ jmp $
+
+
+panic:
+ ; Hang system with supplied error message
+ push bp
+ mov bp, sp
+
+ push error_msg_panic ; i.e. 'PANIC:'
+ call puts
+ add sp, 2
+
+ push word [bp + 4] ; address of error string buffer
+ call puts ; print error
+ add sp, 2
+
+ cli ; disable interrupts
+ jmp $ ; hang (no return)
+ ; stack is dead
+
+
+; data
+kernel_address: dd 0 ; format DS:ADDR
+banner: db "+========================+", CR, LF
+ db "| Welcome to MINOS 0.0.1 |", CR, LF
+ db "+========================+", CR, LF
+ db CR, LF, 0
+
+; Error messages
+error_msg_panic: db "PANIC: ", 0
+
+
+times 512 * 16 db 0
+dw 0xefbe
diff --git a/keyboard.asm b/keyboard.asm
new file mode 100644
index 0000000..444a6cf
--- /dev/null
+++ b/keyboard.asm
@@ -0,0 +1,82 @@
+%ifndef _KEYBOARD_ASM
+%define _KEYBOARD_ASM
+
+; REFERENCE(s):
+; http://stanislavs.org/helppc/int_16-3.html
+
+kbd_read:
+ mov ah, 00h ; BIOS - read key (blocking)
+ int 16h ; BIOS keyboard service
+ mov [kbd_last_key], ax ; Record keypress
+ ; ah = scancode
+ ; al = ascii code
+ ret
+
+
+kbd_read_async:
+ mov ah, 01h ; BIOS - read key (non-blocking)
+ int 16h ; BIOS keyboard service
+ mov [kbd_last_key], ax ; Record keypress
+ ; ah = scancode
+ ; al = ascii code
+ ret
+
+
+kbd_status_shift:
+ mov ah, 02h ; BIOS - keyboard status
+ ; al = flags
+ ;
+ ; bit fields:
+ ; 7 = insert active
+ ; 6 = caps-lock active
+ ; 5 = num-lock active
+ ; 4 = scroll-lock active
+ ; 3 = ALT depresssed
+ ; 2 = CTRL depressed
+ ; 1 = left shift depressed
+ ; 0 = right shift depressed
+ mov [kbd_status_flags], byte al
+ int 16h ; BIOS keyboard service
+
+ ret
+
+
+kbd_set_rate:
+ push bp
+ mov bp, sp
+
+ mov ah, 03h ; BIOS - keyboard service rate/delay
+ mov al, 05h ; CONTROL
+ ; 00 - set typematic rate
+ ; 01 - increase delay
+ ; 02 - decrease rate by 0.5
+ ; 04 - disable typematic characters
+ ; 05 - set typematic rate & delay (used here)
+
+ mov bh, byte [bp + 4] ; REPEAT (per second)
+ ; 00 - 30.0 01 - 26.7 02 - 24.0 03 - 21.8
+ ; 04 - 20.0 05 - 18.5 06 - 17.1 07 - 16.0
+ ; 08 - 15.0 09 - 13.3 0A - 12.0 0B - 10.9
+ ; 0C - 10.0 0D - 9.2 0E - 8.6 0F - 8.0
+ ; 10 - 7.5 11 - 6.7 12 - 6.0 13 - 5.5
+ ; 14 - 5.0 15 - 4.6 16 - 4.3 17 - 4.0
+ ; 18 - 3.7 19 - 3.3 1A - 3.0 1B - 2.7
+ ; 1C - 2.5 1D - 2.3 1E - 2.1 1F - 2.0
+
+ mov bl, byte [bp + 6] ; DELAY
+ ; 00 - 250ms
+ ; 01 - 500ms
+ ; 02 - 750ms
+ ; 03 - 1000ms
+
+ int 16h ; BIOS keyboard service
+
+ mov sp, bp
+ pop bp
+ ret
+
+
+; data
+kbd_last_key: dw 0
+kbd_status_flags: db 0
+%endif
diff --git a/make.bat b/make.bat
new file mode 100644
index 0000000..48ef0e7
--- /dev/null
+++ b/make.bat
@@ -0,0 +1,9 @@
+del system.img *.bin
+
+nasm -f bin -o boot.bin boot.asm
+if %errorlevel% neq 0 exit /b %errorlevel%
+nasm -f bin -o kernel.bin kernel.asm
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+type boot.bin kernel.bin > system.img
+qemu-system-i386 system.img
diff --git a/scancodes.asm b/scancodes.asm
new file mode 100644
index 0000000..3a33183
--- /dev/null
+++ b/scancodes.asm
@@ -0,0 +1,9 @@
+%ifndef _SCANCODES_ASM
+%define _SCANCODES_ASM
+
+SC_ARROW_UP equ 48h
+SC_ARROW_DOWN equ 50h
+SC_ARROW_LEFT equ 4Bh
+SC_ARROW_RIGHT equ 4Dh
+
+%endif
diff --git a/stdio.asm b/stdio.asm
new file mode 100644
index 0000000..e6f7290
--- /dev/null
+++ b/stdio.asm
@@ -0,0 +1,28 @@
+%ifndef _STDIO_ASM
+%define _STDIO_ASM
+
+%include "console.asm"
+
+puts:
+ ; Write string buffer at cursor position
+ push bp
+ mov bp, sp
+ pusha
+
+ mov si, [bp + 4] ; address of string buffer
+ mov bx, 0000h ;
+ mov ah, 0eh ; BIOS - teletype
+
+.loop:
+ lodsb ; load byte at [si] into al
+ or al, 0 ; 0 | 0 = 0 (detect null terminator)
+ je .end
+ int 10h ; BIOS video service
+ jmp .loop
+.end:
+ popa
+ mov sp, bp
+ pop bp
+ ret
+
+%endif
diff --git a/string.asm b/string.asm
new file mode 100644
index 0000000..4000afb
--- /dev/null
+++ b/string.asm
@@ -0,0 +1,174 @@
+%ifndef _STRING_ASM
+%define _STRING_ASM
+
+; stack direction
+; ---------------
+; + = external
+; - = local
+;
+; ^ must be getting old D:
+;
+
+; calling conventions
+; push COUNT
+; push SOURCE
+; push DESTINATION
+; call FUNCTION
+; add sp, 6 (^ in the case of three WORDS)
+;
+; In C, for instance, this translate to:
+; FUNCTION (DESTINATION, SOURCE, COUNT);
+
+memset:
+ ; Set memory with byte value
+ push bp ; setup stack frame
+ mov bp, sp ; ...
+ push si
+ push di
+ push cx
+
+ mov di, [bp + 4] ; destination address
+ mov ax, [bp + 6] ; requested byte value
+ mov cx, [bp + 8] ; count
+
+ cld ; will decrement address ES:DI
+ rep stosb ; while cx > 0
+ ; store byte AL in ES:DI
+
+ pop cx
+ pop di
+ pop si
+ mov sp, bp
+ pop bp
+ ret
+
+
+memcpy:
+ push bp ; setup stack frame
+ mov bp, sp ; ...
+
+ mov di, [bp + 4] ; destination buffer
+ mov si, [bp + 6] ; source buffer
+ mov cx, [bp + 8] ; count of characters to move
+
+ cld
+ rep movsb
+
+ mov sp, bp
+ pop bp
+ ret
+
+
+strlen:
+ ; Determine length of null terminated string
+ ; NOTE: 64k limit imposed
+ push bp ; setup stack frame
+ mov bp, sp ; ...
+ push cx
+ push si
+
+ xor cx, cx ; cx is counter
+ xor ax, ax ; ax is return value
+ mov si, [bp + 2] ; string address
+.loop:
+ lodsb ; load byte from ES:SI into AL
+ cmp al, 0 ; zero?
+ je .return ; if so, we're done
+ inc cx ; if not, keep going
+ jc .crash ; if we roll over CX the carry flag will be set (that's bad)
+
+ jmp .loop
+
+.crash:
+ stc ; force carry flag on failure
+
+.return:
+ clc
+ mov ax, cx
+ pop si
+ pop cx
+ mov sp, bp
+ pop bp
+ ret
+
+
+strnchr:
+ ; Find first occurence of character in a string
+ push bp ; setup stack frame
+ mov bp, sp ; ...
+ push cx
+ push dx
+ push si
+
+ xor ax, ax ; ax is return value
+ mov si, [bp + 4] ; string address
+ mov dx, [bp + 6] ; needle character
+ mov cx, [bp + 8] ; counter
+.loop:
+ lodsb ; load byte at si
+ cmp al, dl ; same as needle?
+ je .return ; if true: return
+
+ dec cx ; decrement counter
+ jne .loop ; counter zero?
+
+.return:
+ mov ax, cx ; return index of character
+ pop si
+ pop dx
+ pop cx
+ mov sp, bp
+ pop bp
+ ret
+
+
+strncmp:
+ ; Determine difference between two strings
+ push bp ; setup stack frame
+ mov bp, sp ; ...
+ push bx ; save registers
+ push dx
+ push si
+ push di
+
+ mov di, [bp + 4] ; string2 address
+ mov si, [bp + 6] ; string1 address
+ mov cx, [bp + 8] ; limit to compare
+
+.loop:
+ mov bx, [si] ; string1
+ mov dx, [di] ; string2
+ inc si ; next address, string1
+ inc di ; next address, string2
+ dec cx ; decrease byte counter
+
+ cmp bx, dx ; compare bytes
+ jne .diff ; Just die if not-equal
+
+ cmp cx, 0 ; If equal, check for null termination
+ jne .loop
+
+.diff:
+ cmp bx, dx
+ jg .s1_larger
+ jl .s1_smaller
+ je .return
+
+.s1_smaller:
+ mov ax, -1
+ jmp .return
+
+.s1_larger:
+ mov ax, cx
+
+.return:
+ pop di ; cleanup stack
+ pop si
+ pop dx
+ pop bx
+ mov sp, bp
+ pop bp
+ ret
+
+
+%endif ; _STRING_ASM