Bootloader: Continue work on bootloader

Bootloader can now get the memory map and read cmdline from user.

Now 'just' video mode query, ext2 and ELF parsing are needed :D
This commit is contained in:
Bananymous 2023-11-11 22:49:00 +02:00
parent cfc7313451
commit 381cfdad77
3 changed files with 469 additions and 89 deletions

View File

@ -1,10 +1,9 @@
# FIXME: don't assume 512 byte sectors
.set SECTOR_SIZE, 512
.set GPT_HEADER_ADDR, free_memory_start
.set GPT_ENTRY_ADDR, free_memory_start + SECTOR_SIZE
.set SECTOR_SIZE_SHIFT, 9
.set SECTOR_SIZE, 1 << SECTOR_SIZE_SHIFT
.set SCREEN_WIDTH, 80
.set SCREEN_HEIGHT, 25
.code16
@ -20,42 +19,49 @@
.section .stage1
stage1_start:
jmp main
jmp stage1_main
# al: character to print
# prints character to screen
# al: ascii character to print
putc:
mov $0x0E, %ah
pusha
movb $0x0E, %ah
movb $0x00, %bh
int $0x10
popa
ret
# ds:si: null terminated string to print
# prints null terminated string to screen
# ds:si: string address
puts:
push %si
push %ax
pushw %si
pushw %bx
pushw %ax
1:
movb $0x0E, %ah
movb $0x00, %bh
.puts_loop:
lodsb
test %al, %al
jz 2f
jz .puts_done
call putc
jmp 1b
int $0x10
jmp .puts_loop
2:
mov $'\r', %al
call putc
mov $'\n', %al
call putc
pop %ax
pop %si
.puts_done:
popw %ax
popw %bx
popw %si
ret
# compares memory between addresses
# si: ptr1
# di: ptr2
# cx: count
# return: 1 if equal, 0 otherwise
# cx: bytes count
# return:
# al: 1 if equal, 0 otherwise
memcmp:
pushw %si
pushw %di
@ -70,12 +76,10 @@ memcmp:
# read sectors from disk
#
# bx:eax: lba start
# cx: lba count (has to less than 0x80)
# dl: drive number
# ds:di: physical address
#
# returns only on success
read_from_disk:
push %ax
@ -103,7 +107,78 @@ read_from_disk:
ret
main:
# Validates that GPT header is valid and usable
# doesn't return on invalid header
validate_gpt_header:
# confirm header (starts with 'EFI PART')
cmpl $0x20494645, (gpt_header + 0)
jne .not_gpt_partition
cmpl $0x54524150, (gpt_header + 4)
jne .not_gpt_partition
ret
# Find partition entry with type guid
# si: address of type guid
# dl: drive number
# returns:
# al: non-zero if found
# di: address of partition entry
find_gpt_partition_entry:
# eax := entry_count
movl (gpt_header + 80), %eax
test %eax, %eax
jz .no_gpt_partition_found
# edx:eax := eax * entry_size
pushw %dx
mull (gpt_header + 84)
test %edx, %edx
jnz .too_gpt_big_entries
popw %dx
# FIXME: read one entry array section at a time
# sector count := (arr_size + SECTOR_SIZE - 1) / SECTOR_SIZE
movl %eax, %ecx
shrl $SECTOR_SIZE_SHIFT, %ecx
# start lba
movl (gpt_header + 72), %eax
movw (gpt_header + 76), %bx
movw $gpt_entry_data, %di
call read_from_disk
# NOTE: 'only' 0xFFFF partitions supported,
# although read will fail with more than 0x80
movw (gpt_header + 80), %cx
.loop_gpt_entries:
pushw %cx
movw $16, %cx
call memcmp
popw %cx
testb %al, %al
jnz .gpt_partition_found
# add entry size to entry pointer
addw (gpt_header + 84), %di
loop .loop_gpt_entries
.no_gpt_partition_found:
movb $0, %al
ret
.gpt_partition_found:
movb $1, %al
ret
stage1_main:
# setup segments
movw $0, %ax
movw %ax, %ds
@ -129,61 +204,19 @@ main:
movw $0, %bx
movw $1, %cx
movb (boot_disk_number), %dl
movw $GPT_HEADER_ADDR, %di
movw $gpt_header, %di
call read_from_disk
# confirm header (starts with 'EFI PART')
cmpl $0x20494645, (GPT_HEADER_ADDR + 0)
jne .not_gpt_partition
cmpl $0x54524150, (GPT_HEADER_ADDR + 4)
jne .not_gpt_partition
call validate_gpt_header
# eax := entry_count
movl (GPT_HEADER_ADDR + 80), %eax
test %eax, %eax
movw $bios_boot_guid, %si
movb boot_disk_number, %dl
call find_gpt_partition_entry
testb %al, %al
jz .no_bios_boot_partition
# edx:eax := eax * entry_size
mull (GPT_HEADER_ADDR + 84)
test %edx, %edx
jnz .too_gpt_big_entries
# BIOS boot partiton found!
# sector count := (arr_size + SECTOR_SIZE - 1) / SECTOR_SIZE
pushl %eax
addl $(SECTOR_SIZE - 1), %eax
movl $SECTOR_SIZE, %ecx
divl %ecx
movl %eax, %ecx
popl %eax
# start lba
movl (GPT_HEADER_ADDR + 72), %eax
movw (GPT_HEADER_ADDR + 76), %bx
movb (boot_disk_number), %dl
movw $GPT_ENTRY_ADDR, %di
call read_from_disk
# NOTE: 'only' 0xFFFF partitions supported
movw (GPT_HEADER_ADDR + 80), %cx
.loop_entries:
push %cx
movw $16, %cx
movw $bios_boot_guid, %si
call memcmp
pop %cx
testb %al, %al
jnz .bios_boot_found
# add entry size to entry pointer
addw (GPT_HEADER_ADDR + 84), %di
loop .loop_entries
jmp .no_bios_boot_partition
.bios_boot_found:
# first lba
movl 32(%di), %eax
movw $0, %bx
@ -200,7 +233,7 @@ main:
movw $stage2_start, %di
call read_from_disk
jmp stage2_start
jmp stage2_main
print_and_halt:
call puts
@ -229,7 +262,6 @@ halt:
jmp print_and_halt
# 21686148-6449-6E6F-744E-656564454649
bios_boot_guid:
.long 0x21686148 # little endian
@ -245,7 +277,7 @@ read_failed_msg:
.asciz "read error"
not_gpt_partition_msg:
.asciz "not gpt"
.asciz "not GPT"
no_bios_boot_partition_msg:
.asciz "no bios boot partition"
@ -256,10 +288,6 @@ too_gpt_big_entries_msg:
boot_disk_number:
.skip 1
disk_address_packet:
.skip 16
#########################################
#
@ -268,7 +296,247 @@ disk_address_packet:
#########################################
.section .stage2
stage2_start:
# read a character from keyboard
# return:
# al: ascii
# ah: bios scan code
getc:
movb $0x00, %ah
int $0x16
ret
# prints newline to screen
print_newline:
pushw %ax
movb $'\r', %al
call putc
movb $'\n', %al
call putc
pop %ax
ret
# prints backspace to screen, can go back a line
print_backspace:
pusha
# get cursor position
movb $0x03, %ah
movb $0x00, %bh
int $0x10
# don't do anyting if on first row
testb %dh, %dh
jz .print_backspace_done
# go one line up if on first column
test %dl, %dl
jz .print_backspace_go_line_up
# otherwise decrease column
decb %dl
jmp .print_backspace_do_print
.print_backspace_go_line_up:
# decrease row and set column to the last one
decb %dh
movb $(SCREEN_WIDTH - 1), %dl
.print_backspace_do_print:
# set cursor position
movb $0x02, %ah
int $0x10
# print 'empty' character (space)
mov $' ', %al
call putc
# set cursor position
movb $0x02, %ah
int $0x10
.print_backspace_done:
popa
ret
# print number to screen
# ax: number to print
# bx: number base
# cx: min width (zero pads if shorter)
printnum:
pusha
pushw %cx
movw $printnum_buffer, %si
xorw %cx, %cx
.printnum_fill_loop:
# fill buffer with all remainders ax % bx
xorw %dx, %dx
divw %bx
movb %dl, (%si)
incw %si
incw %cx
testw %ax, %ax
jnz .printnum_fill_loop
# check if zero pad is required
popw %dx
cmpw %cx, %dx
jbe .printnum_print_loop
xchgw %dx, %cx
subw %dx, %cx
movb $'0', %al
.printnum_pad_zeroes:
call putc
loop .printnum_pad_zeroes
movw %dx, %cx
.printnum_print_loop:
decw %si
movb (%si), %al
cmpb $10, %al
jae 1f
addb $'0', %al
jmp 2f
1: addb $('a' - 10), %al
2: call putc
loop .printnum_print_loop
popa
ret
# test if character is printable ascii
# al: character to test
# sets ZF if is printable
isprint:
cmpb $0x20, %al
jb .isprint_done
cmpb $0x7E, %al
ja .isprint_done
cmpb %al, %al
.isprint_done:
ret
# fills memory map data structure
# doesn't return on error
get_memory_map:
pusha
movl $0, (memory_map_entry_count)
movl $0x0000E820, %eax
movl $0x534D4150, %edx
xorl %ebx, %ebx
movl $20, %ecx
movw $memory_map_entries, %di
clc
int $0x15
# If first call returs with CF set, the call failed
jc .get_memory_map_error
.get_memory_map_rest:
cmpl $0x534D4150, %eax
jne .get_memory_map_error
# FIXME: don't assume BIOS to always return 20 bytes
cmpl $20, %ecx
jne .get_memory_map_error
# increment entry count
incl (memory_map_entry_count)
# increment entry pointer
addw %cx, %di
# BIOS can indicate end of list by 0 in ebx
testl %ebx, %ebx
jz .get_memory_map_done
movl $0x0000E820, %eax
movl $0x534D4150, %edx
clc
int $0x15
# BIOS can indicate end of list by setting CF
jnc .get_memory_map_rest
.get_memory_map_done:
popa
ret
.get_memory_map_error:
movw $memory_map_error_msg, %si
jmp print_and_halt
# fills command line buffer
get_command_line:
pusha
movw $command_line_enter_msg, %si
call puts
movw $command_line_buffer, %di
.get_command_line_loop:
call getc
cmpb $'\b', %al
je .get_command_line_backspace
# Not sure if some BIOSes return '\n' as enter, but check it just in case
cmpb $'\r', %al
je .get_command_line_done
cmpb $'\n', %al
je .get_command_line_done
call isprint
jnz .get_command_line_loop
# put byte to buffer
movb %al, (%di)
incw %di
# print byte
call putc
jmp .get_command_line_loop
.get_command_line_backspace:
# don't do anything if at the beginning
cmpw $command_line_buffer, %di
je .get_command_line_loop
# decrement buffer pointer
decw %di
# erase byte in display
call print_backspace
jmp .get_command_line_loop
.get_command_line_done:
# null terminate command line
movb $0, (%di)
call print_newline
popa
ret
stage2_main:
# clear screen and enter 80x25 text mode
movb $0x03, %al
movb $0x00, %ah
@ -277,11 +545,123 @@ stage2_start:
# print hello message
movw $hello_msg, %si
call puts
call print_newline
call get_memory_map
call get_command_line
call print_newline
movw $start_kernel_load_msg, %si
call puts
call print_newline
movw $command_line_msg, %si
call puts
movw $command_line_buffer, %si
call puts
call print_newline
movw $memory_map_msg, %si
call puts
call print_newline
movl (memory_map_entry_count), %edx
movw $memory_map_entries, %si
movw $16, %bx
movw $4, %cx
.loop_memory_map:
movb $' ', %al
call putc
call putc
call putc
call putc
movw 0x06(%si), %ax
call printnum
movw 0x04(%si), %ax
call printnum
movw 0x02(%si), %ax
call printnum
movw 0x00(%si), %ax
call printnum
movb $',', %al
call putc
movb $' ', %al
call putc
movw 0x0E(%si), %ax
call printnum
movw 0x0C(%si), %ax
call printnum
movw 0x0A(%si), %ax
call printnum
movw 0x08(%si), %ax
call printnum
movb $',', %al
call putc
movb $' ', %al
call putc
movw 0x12(%si), %ax
call printnum
movw 0x10(%si), %ax
call printnum
call print_newline
addw $20, %si
decl %edx
jnz .loop_memory_map
jmp halt
1:
jmp 1b
hello_msg:
.asciz "This is banan-os bootloader"
command_line_msg:
command_line_enter_msg:
.asciz "cmdline: "
memory_map_msg:
.asciz "memmap:"
memory_map_error_msg:
.asciz "Failed to get memory map"
start_kernel_load_msg:
.asciz "Starting to load kernel"
stage2_end:
.section .bss
.align SECTOR_SIZE
gpt_header:
.skip SECTOR_SIZE
gpt_entry_data:
.skip SECTOR_SIZE
disk_address_packet:
.skip 16
printnum_buffer:
.skip 10
memory_map_entry_count:
.skip 4
# 100 entries should be enough...
memory_map_entries:
.skip 20 * 100
# 100 character command line
command_line_buffer:
.skip 100

View File

@ -1,11 +1,11 @@
SECTIONS
{
. = 0x7C00;
.stage1 : { *(.stage1*) }
.stage1 : { *(.stage1) }
. = ALIGN(512);
.stage2 : { *(.stage2) }
. = ALIGN(512);
free_memory_start = .;
.bss : { *(.bss) }
}

View File

@ -42,4 +42,4 @@ if [ "$1" == "debug" ] ; then
fi
echo running qemu
qemu-system-x86_64 $QEMU_FLAGS --drive format=raw,file=test.img
env BANAN_DISK_IMAGE_PATH=${DISK_IMAGE_PATH} BANAN_ARCH=x86_64 ../script/qemu.sh $QEMU_FLAGS