349 lines
8.4 KiB
NASM
349 lines
8.4 KiB
NASM
org 0x7C00 ; The boot code on a drive always gets loaded at address 0x7C00
|
|
bits 16 ; .bin defaults to 16 bits
|
|
|
|
WIDTH equ 0x4F ; 79
|
|
HEIGHT equ 0x18 ; 24
|
|
VIDEO_MODE equ 0x3 ; 80x25 screen, text mode
|
|
|
|
PIT_MODECOMMAND_PORT equ 0x43 ; programmable interval timer mode/command port (used for speaker)
|
|
PIT_CH2_DATA_PORT equ 0x42 ; PIT channel 2 data port
|
|
|
|
VIRTUAL_KBD_CHAR equ 0x09 ; character to detect if a command is comming from the virtual keyboard (htab)
|
|
|
|
;; see http://www.ctyme.com/intr/int-10.htm for interrupts
|
|
|
|
start:
|
|
|
|
mov sp, 0x2000 ; initialise stack pointer
|
|
|
|
;; Interrupt 0x14, ah=0
|
|
;; bit 7 - 5 baud rate
|
|
;; 0 0 0 110
|
|
;; 0 0 1 150
|
|
;; 0 1 0 300
|
|
;; 0 1 1 600
|
|
;; 1 0 0 1200
|
|
;; 1 0 1 2400
|
|
;; 1 1 0 4800
|
|
;; 1 1 1 9600 (used)
|
|
;; bit 4 - 3 parity
|
|
;; 0 0 none (used)
|
|
;; 0 1 odd
|
|
;; 1 1 even
|
|
;; bit 2 stop bits
|
|
;; 0 1
|
|
;; 1 2
|
|
;; bit 1 - 0 data bits
|
|
;; 1 0 7
|
|
;; 1 1 8
|
|
|
|
;; initialise serial port
|
|
mov ah, 0x0 ; initialise UART
|
|
mov al, 0b111_00_0_11 ; baud 9600, no parity, 1 stop bit, 8 data bits
|
|
mov dx, 0x0 ; COM1
|
|
int 0x14
|
|
|
|
;; Use color mode
|
|
mov ax, VIDEO_MODE
|
|
int 0x10
|
|
|
|
;; Clear screen
|
|
mov ah, 0x06 ; scroll screen up
|
|
mov al, 0x00 ; lines to scroll (0 = clear)
|
|
mov bh, 0x0F ; bg/fg colour
|
|
mov cx, 0x0000 ; upper row number, left col number (both 0)
|
|
mov dh, HEIGHT ; lower row number (24)
|
|
mov dl, WIDTH ; right col number (80)
|
|
int 0x10
|
|
|
|
;; Set cursor to bottom of screen
|
|
;; dh already contains HEIGHT
|
|
mov ah, 0x02 ; set cursor position
|
|
mov bh, 0x00 ; page number
|
|
mov dl, 0x00 ; col (leftmost col)
|
|
int 0x10
|
|
|
|
.loop:
|
|
;; read a character, ASCII in al
|
|
mov ah, 0x00 ; Read character
|
|
int 0x16
|
|
|
|
;; a special character will be sent before every command from the virtual keyboard
|
|
;; so if that character is detected go to the virtual_kbd_loop instead
|
|
cmp al, VIRTUAL_KBD_CHAR
|
|
je .virtual_kbd_loop_init
|
|
|
|
cmp al, 0x08 ; backspace
|
|
je change_color_mode
|
|
|
|
cmp al, 0x1B ; esc
|
|
je beep
|
|
|
|
;; print character to serial port
|
|
mov ah, 0x01 ; transmit char
|
|
mov dx, 0x00 ; COM1
|
|
int 0x14
|
|
|
|
cmp al, 0x0D ; enter (carriage return)
|
|
je .nl_lf
|
|
|
|
jmp nonewline ; all other characters
|
|
|
|
.nl_reset_state:
|
|
mov byte[is_from_virtual_kbd], 0x0 ; reset state variable
|
|
jmp .nl
|
|
|
|
;; BIOS keyboard routines read <enter> as CR which is usually not recognized
|
|
;; as actually beginning a new line, so anytime a CR is sent a LF is sent with
|
|
;; it
|
|
.nl_lf:
|
|
mov al, 0x0A ; line feed
|
|
mov ah, 0x01 ; transmit char
|
|
int 0x14
|
|
.nl:
|
|
call newline
|
|
jmp .loop
|
|
|
|
.virtual_kbd_loop_init:
|
|
mov byte[is_from_virtual_kbd], 0x1 ; update state variable
|
|
;; same as regular .loop except nothing is sent to serial
|
|
.virtual_kbd_loop:
|
|
;; read a character, ASCII in al
|
|
mov ah, 0x00
|
|
int 0x16
|
|
|
|
cmp al, 0x08 ; backspace
|
|
je change_color_mode
|
|
|
|
cmp al, 0x1B ; esc
|
|
je beep
|
|
|
|
;; an enter character signifies the end of a command
|
|
;; so a newline should get printed and then the program should resume
|
|
;; from the main .loop again
|
|
cmp al, 0x0D ; enter
|
|
je .nl_reset_state
|
|
|
|
jmp nonewline ; all other characters
|
|
|
|
;; Update the color_mode variable with a byte from the keyboard
|
|
;;
|
|
;; CLOBBERS
|
|
;; - ax
|
|
;; - bl
|
|
change_color_mode:
|
|
call readbyte ; get byte from keyboard (in al)
|
|
mov [color_mode], al ; store byte in color_mode variable
|
|
|
|
jmp compare_virtual_and_jump
|
|
|
|
;; Simulates going to a new line
|
|
;;
|
|
;; CLOBBERS
|
|
;; - ax
|
|
;; - bh
|
|
;; - cx
|
|
;; - dx
|
|
newline:
|
|
;; Scroll up window
|
|
mov ah, 0x06 ; scroll screen up
|
|
mov al, 0x01 ; lines to scroll (1)
|
|
mov bh, 0x0F ; bg/fg colour
|
|
mov cx, 0x0000 ; upper row num/left col num (both 0)
|
|
mov dh, HEIGHT ; lower row num (24)
|
|
mov dl, WIDTH ; right col num (80)
|
|
int 0x10
|
|
|
|
;; Move cursor to beginning of screen
|
|
;; dh already contains HEIGHT
|
|
mov ah, 0x02 ; set cursors position
|
|
mov bh, 0x00 ; page number
|
|
mov dl, 0x00 ; col
|
|
int 0x10
|
|
|
|
ret
|
|
|
|
;; Plays a note on the speaker for a given duration
|
|
;;
|
|
;; CLOBBERS
|
|
;; - ax
|
|
;; - bx
|
|
;; - cx
|
|
beep:
|
|
;; Read duration (0-16 supported)
|
|
mov ah, 0x00 ; read keypress
|
|
int 0x16
|
|
;; Read ASCII character is in al
|
|
;; the character must be normalised to a=1, b=2, ...
|
|
;; and not a=0, b=1, ... because during the waiting loop
|
|
;; see #NOTE_LOOP
|
|
;; the duration counter gets decremented BEFORE
|
|
;; it gets compared to zero
|
|
;; ('a' is 0x61)
|
|
sub al, 0x60 ; normalise (a=1, b=2, c=...)
|
|
|
|
;; interrupt 0x16, ah=0 returns the read scancode in ah
|
|
;; so ah must be reset
|
|
mov ah, 0
|
|
mov cx, ax ; save ax into cx temporarily
|
|
|
|
;; move 182 (0b10 11 011 0) into al
|
|
;; bit 7-6 = channel select (2, PC speaker)
|
|
;; bit 5-4 = access mode (lobyte/hibyte)
|
|
;; bit 3-1 = operating mode (square wave generator)
|
|
;; bit 0 = BCD/binary mode (16-bit binary)
|
|
mov al, 0b10_11_011_0
|
|
out PIT_MODECOMMAND_PORT, al ; write to PIT control port
|
|
|
|
;; Read byte and convert to a usable frequency
|
|
call readbyte ; read byte from keyboard (in al)
|
|
mov ah, 0 ; readbyte clobbers ax
|
|
sal ax, 5 ; multiply by 32
|
|
add ax, 1140
|
|
|
|
out PIT_CH2_DATA_PORT, al ; Output low byte.
|
|
mov al, ah
|
|
out PIT_CH2_DATA_PORT, al ; Output high byte
|
|
|
|
;; Port 0x61 does a whole lot but only the bottom 2 bits are relevant here
|
|
;; (speaker enable and speaker input select)
|
|
in al, 0x61 ; Preserve previous value
|
|
or al, 00000011b ; Set bits 1 and 0 (enable speaker, input = channel 2)
|
|
out 0x61, al ; Send new value
|
|
|
|
;; mov bx, 50000 ; Pause for duration of note.
|
|
|
|
;; multiply duration by 3125
|
|
;; (duration is in cx)
|
|
mov ax, 3125
|
|
mul cx ; multiply ax <- ax * cx
|
|
|
|
;; play note for a certain duration
|
|
.pause1:
|
|
mov ecx, 65535
|
|
.pause2:
|
|
dec cx ; count down
|
|
jne .pause2 ; if not zero keep counting down
|
|
|
|
;; #NOTE_LOOP
|
|
;; the duration gets decremented before being checked
|
|
;; hence why it was normalised to '`' and not 'a'
|
|
dec ax ; decrement duration counter
|
|
jne .pause1 ; keep doing that until it is 0
|
|
|
|
in al, 0x61 ; Preserve previous value
|
|
and al, 11111100b ; Clear bits 1 and 0 (disable speaker, clear channel select)
|
|
out 0x61, al ; Send new value
|
|
|
|
jmp compare_virtual_and_jump
|
|
|
|
;; Handles all other characters by writing them to the screen
|
|
;;
|
|
;; CLOBBERS
|
|
;; - ah
|
|
;; - bx
|
|
;; - cx
|
|
;; - dx
|
|
nonewline:
|
|
;; Write character
|
|
mov ah, 0x09 ; write character with attribute
|
|
mov bh, 0x00 ; page number
|
|
mov bl, [color_mode] ; BIOS colour code
|
|
mov cx, 0x01 ; amount of times to print character
|
|
int 0x10
|
|
|
|
;; Get current cursor position
|
|
;; bh already contains page number
|
|
;; returns dh = row, dl = col
|
|
mov ah, 0x03 ; get cursor position and shape
|
|
int 0x10
|
|
|
|
;; if the cursor is at the edge of the screen go to the next line
|
|
;; else move it forward once
|
|
cmp dl, WIDTH
|
|
jge .eol
|
|
jmp .noeol
|
|
|
|
.eol:
|
|
call newline
|
|
|
|
jmp compare_virtual_and_jump
|
|
|
|
.noeol:
|
|
;; Move cursor forward
|
|
;; bh already contains page number
|
|
;; dh already contains row
|
|
inc dl ; go to next col
|
|
mov ah, 0x02 ; set cursor position, dh (row) already set, dl (col) updated on prev line
|
|
int 0x10
|
|
|
|
jmp compare_virtual_and_jump
|
|
|
|
;; Compares the is_from_virtual_kbd state variable to 1 to check if the command
|
|
;; came from the virtual keyboard
|
|
;; if it did, jump to the virtual keyboard loop
|
|
;; if it didn't, jump to the main loop
|
|
compare_virtual_and_jump:
|
|
cmp byte[is_from_virtual_kbd], 0x01
|
|
je start.virtual_kbd_loop
|
|
jmp start.loop
|
|
|
|
;; Reads two characters from the keyboard buffer and converts them into a single byte
|
|
;;
|
|
;; The conversion happens as follows:
|
|
;; ASCII 'a' is subtracted from each character (so 'a' = 0, 'b' = 1, ...)
|
|
;; the first character is shifted left by 4
|
|
;; the shifted and non-shifted values are then or'ed together to form a byte
|
|
;;
|
|
;; RETURNS
|
|
;; A byte in al
|
|
;;
|
|
;; CLOBBERS
|
|
;; - ax
|
|
;; - bl
|
|
readbyte:
|
|
mov ah, 0x00 ; Read character
|
|
int 0x16
|
|
|
|
;; Read character is in al
|
|
sub al, 'a' ; Normalise
|
|
mov bl, al
|
|
shl bl, 0x04 ; First character is upper 4 bits
|
|
|
|
mov ah, 0x00 ; Read character
|
|
int 0x16
|
|
|
|
;; Read character is in al
|
|
sub al, 'a' ; Normalise
|
|
|
|
or al, bl ; Combine both characters
|
|
|
|
ret
|
|
|
|
;; Reads a duration from the keyboard
|
|
;;
|
|
;; RETURNS
|
|
;; A duration in al
|
|
;;
|
|
;; CLOBBERS
|
|
;; - ax
|
|
;read_duration:
|
|
; ;; Read duration (0-16 supported)
|
|
; mov ah, 0x00 ; read keypress
|
|
; int 0x16
|
|
;
|
|
; ;; Read character is in al
|
|
; sub al, 'a' ; normalise
|
|
;
|
|
; ret
|
|
|
|
;; Variable to store color mode information for writing to the screen
|
|
color_mode: db 0x02
|
|
;; State variable for detecting whether keystrokes are virtual or real
|
|
is_from_virtual_kbd: db 0x00
|
|
|
|
;; Magic number must be in the last 2 bytes of the sector so
|
|
;; fill the binary with zeroes until it is 510 bytes in size
|
|
;; (magic number will use up the remaining 2)
|
|
times 510 - ($ - $$) db 0
|
|
dw 0xAA55 ;; Magic number to mark sector as bootable
|