From b0b39c56ba68f1e473719a0f93cb1c8088d64052 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Tue, 14 Nov 2023 03:26:03 +0200 Subject: [PATCH] Bootloader: Split bootloader into multiple files This cleans up the code since bootloader is starting to near 1k lines --- bootloader/boot.S | 824 +------------------------------------- bootloader/command_line.S | 69 ++++ bootloader/disk.S | 491 +++++++++++++++++++++++ bootloader/install.sh | 8 +- bootloader/linker.ld | 4 + bootloader/memory_map.S | 129 ++++++ bootloader/utils.S | 218 ++++++++++ 7 files changed, 922 insertions(+), 821 deletions(-) create mode 100644 bootloader/command_line.S create mode 100644 bootloader/disk.S create mode 100644 bootloader/memory_map.S create mode 100644 bootloader/utils.S diff --git a/bootloader/boot.S b/bootloader/boot.S index 689a909f..51e7e19a 100644 --- a/bootloader/boot.S +++ b/bootloader/boot.S @@ -1,10 +1,3 @@ -# FIXME: don't assume 512 byte sectors -.set SECTOR_SIZE_SHIFT, 9 -.set SECTOR_SIZE, 1 << SECTOR_SIZE_SHIFT - -.set SCREEN_WIDTH, 80 -.set SCREEN_HEIGHT, 25 - .code16 ######################################### @@ -18,166 +11,7 @@ .section .stage1 -stage1_start: - jmp stage1_main - -# prints character to screen -# al: ascii character to print -putc: - pusha - movb $0x0E, %ah - movb $0x00, %bh - int $0x10 - popa - ret - -# prints null terminated string to screen -# ds:si: string address -puts: - pushw %si - pushw %bx - pushw %ax - - movb $0x0E, %ah - movb $0x00, %bh - - .puts_loop: - lodsb - - test %al, %al - jz .puts_done - - int $0x10 - jmp .puts_loop - - .puts_done: - popw %ax - popw %bx - popw %si - ret - -# compares memory between addresses -# si: ptr1 -# di: ptr2 -# cx: bytes count -# return: -# al: 1 if equal, 0 otherwise -memcmp: - pushw %si - pushw %di - - cld - repe cmpsb - setzb %al - - popw %di - popw %si - ret - - -# 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 - push %si - - # prepare disk read packet - mov $disk_address_packet, %si - movb $0x10, 0x00(%si) # packet size - movb $0x00, 0x01(%si) # always 0 - movw %cx, 0x02(%si) # lba count - movw %di, 0x04(%si) # offset - movw %ds, 0x06(%si) # segment - movl %eax, 0x08(%si) # 32 bit lower lba - movw %bx, 0x0C(%si) # 16 bit upper lba - movw $0, 0x0E(%si) # zero - - # issue read command - clc - mov $0x42, %ah - int $0x13 - jc .read_failed - - pop %si - pop %ax - ret - - -# 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 - - +.global stage1_main stage1_main: # setup segments movw $0, %ax @@ -189,107 +23,17 @@ stage1_main: movl $0x7C00, %esp # save boot disk number - movb %dl, (boot_disk_number) + call read_stage2_into_memory - # FIXME: validate boot disk (needs size optizations) - - # confirm that int 13h extensions are available - clc - movb $0x41, %ah - movw $0x55AA, %bx - movb (boot_disk_number), %dl - int $0x13 - jc .no_int13h_ext - - # read gpt header - movl $1, %eax - movw $0, %bx - movw $1, %cx - movb (boot_disk_number), %dl - movw $gpt_header, %di - call read_from_disk - - call validate_gpt_header - - movw $bios_boot_guid, %si - movb boot_disk_number, %dl - call find_gpt_partition_entry - testb %al, %al - jz .no_bios_boot_partition - - # BIOS boot partiton found! - - # first lba - movl 32(%di), %eax - movw $0, %bx - - # count := last lba - first lba + 1 - movl 40(%di), %ecx - subl %eax, %ecx - addl $1, %ecx - - # calculate stage2 sector count - movw $((stage2_end - stage2_start + SECTOR_SIZE - 1) / SECTOR_SIZE), %cx - - movb (boot_disk_number), %dl - movw $stage2_start, %di - - call read_from_disk jmp stage2_main +.global print_and_halt print_and_halt: call puts halt: hlt jmp halt - .no_int13h_ext: - mov $no_int13_ext_msg, %si - jmp print_and_halt - - .read_failed: - mov $read_failed_msg, %si - jmp print_and_halt - - .not_gpt_partition: - mov $not_gpt_partition_msg, %si - jmp print_and_halt - - .no_bios_boot_partition: - mov $no_bios_boot_partition_msg, %si - jmp print_and_halt - - .too_gpt_big_entries: - mov $too_gpt_big_entries_msg, %si - jmp print_and_halt - - -# 21686148-6449-6E6F-744E-656564454649 -bios_boot_guid: - .long 0x21686148 # little endian - .word 0x6449 # little endian - .word 0x6E6F # little endian - .word 0x4E74 # big endian - .quad 0x494645646565 # big endian - -no_int13_ext_msg: - .asciz "no INT 13h ext" - -read_failed_msg: - .asciz "read error" - -not_gpt_partition_msg: - .asciz "not GPT" - -no_bios_boot_partition_msg: - .asciz "no bios boot partition" - -too_gpt_big_entries_msg: - .asciz "too big GPT array" - -boot_disk_number: - .skip 1 - ######################################### # @@ -299,477 +43,6 @@ boot_disk_number: .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 - - -# check if drive exists -# dl: drive number -# return: -# al: 1 if disk is usable, 0 otherwise -drive_exists: - pusha - - movb $0x48, %ah - movw $disk_drive_parameters, %si - movw $0x1A, (disk_drive_parameters) # set buffer size - - clc - int $0x13 - jc .drive_exists_nope - - popa - movb $1, %al - ret - - .drive_exists_nope: - popa - movb $0, %al - ret - - -# fills memory map data structure -# doesn't return on error -# NO REGISTERS SAVED -get_memory_map: - 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: - ret - - .get_memory_map_error: - movw $memory_map_error_msg, %si - jmp print_and_halt - - -# fills command line buffer -# NO REGISTERS SAVED -get_command_line: - 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 - - ret - - -# print memory map from memory_map_entries -# NO REGISTERS SAVED -print_memory_map: - 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 - - ret - - -# find root disk and populate root_disk_drive_number field -# NO REGISTERS SAVED -find_root_disk: - movb $0x80, %dl - - .find_root_disk_loop: - call drive_exists - testb %al, %al - jz .find_root_disk_not_found - - # read GPT header - xorw %bx, %bx - movl $1, %eax - movw $1, %cx - movw $gpt_header, %di - call read_from_disk - - # confirm header (starts with 'EFI PART') - cmpl $0x20494645, (gpt_header + 0) - jne .find_root_disk_next_disk - cmpl $0x54524150, (gpt_header + 4) - jne .find_root_disk_next_disk - - # compare disk GUID - movw $root_disk_guid, %si - movw $(gpt_header + 56), %di - movw $16, %cx - call memcmp - testb %al, %al - jz .find_root_disk_next_disk - - movb %dl, (root_disk_drive_number) - ret - - .find_root_disk_next_disk: - incb %dl - jmp .find_root_disk_loop - - .find_root_disk_not_found: - movw $no_root_disk_msg, %si - jmp print_and_halt - - -# finds root partition from root disk -# fills root_partition_entry data structure -# NOTE: assumes GPT header is in `gpt_header` -# NO REGISTERS SAVED -find_root_partition: - pushl %ebp - movl %esp, %ebp - subl $16, %esp - - # esp + 0: 8 byte entry array lba - movl (gpt_header + 72), %eax - movl %eax, 0(%esp) - movl (gpt_header + 76), %eax - movl %eax, 4(%esp) - # FIXME: check that bits 48-63 are zero - - # esp + 8: 4 byte entries per sector - xorl %edx, %edx - movl $SECTOR_SIZE, %eax - divl (gpt_header + 84) - movl %eax, 8(%esp) - - # esp + 12: 4 byte entries remaining - movl (gpt_header + 80), %eax - testl %eax, %eax - jz .find_root_partition_not_found - movl %eax, 12(%esp) - - .find_root_partition_read_entry_section: - movl 0(%esp), %eax - movl 4(%esp), %ebx - movw $1, %cx - movb (root_disk_drive_number), %dl - movw $sector_buffer, %di - call read_from_disk - - # ecx: min(entries per section, entries remaining) - movl 8(%esp), %ecx - cmpl 12(%esp), %ecx - jae .find_root_partition_got_entry_count - movl 12(%esp), %ecx - - .find_root_partition_got_entry_count: - # update entries remaining - subl %ecx, 12(%esp) - - # si: entry pointer - movw $sector_buffer, %si - - .find_root_partition_loop_entries: - # temporarily save cx in dx - movw %cx, %dx - - # check that entry is used - movw $16, %cx - movw $zero_guid, %di - call memcmp - test %al, %al - jnz .find_root_partition_next_entry - - # compare entry guid to root guid - movw $16, %cx - addw $16, %si - movw $root_partition_guid, %di - call memcmp - subw $16, %si - - testb %al, %al - jnz .find_root_partition_found - - .find_root_partition_next_entry: - - # restore cx - movw %dx, %cx - - # entry pointer += entry size - addw (gpt_header + 84), %si - loop .find_root_partition_loop_entries - - # entry not found in this sector - - # increment 8 byte entry array lba - incl 0(%esp) - jno .find_root_partition_no_overflow - incl 4(%esp) - - .find_root_partition_no_overflow: - # loop to read next section if entries remaining - cmpl $0, 12(%esp) - jnz .find_root_partition_read_entry_section - - .find_root_partition_not_found: - movw $no_root_partition_msg, %si - jmp print_and_halt - - .find_root_partition_found: - # copy entry to buffer - movw $root_partition_entry, %di - movw $128, %cx - rep movsb - - leavel - ret - - stage2_main: # clear screen and enter 80x25 text mode movb $0x03, %al @@ -781,7 +54,7 @@ stage2_main: call puts; call print_newline call get_memory_map - call get_command_line + call read_user_command_line call print_newline @@ -791,101 +64,14 @@ stage2_main: call print_memory_map call find_root_disk - movw $root_disk_found_msg, %si - call puts; call print_newline - call find_root_partition - movw $root_partition_found_msg, %si - call puts; call print_newline - movw $16, %bx - movw $2, %cx - movw (root_partition_entry + 38), %ax; call printnum - movw (root_partition_entry + 36), %ax; call printnum - movw (root_partition_entry + 34), %ax; call printnum - movw (root_partition_entry + 32), %ax; call printnum - - movb $'-', %al; call putc - movb $'>', %al; call putc - - movw (root_partition_entry + 46), %ax; call printnum - movw (root_partition_entry + 44), %ax; call printnum - movw (root_partition_entry + 42), %ax; call printnum - movw (root_partition_entry + 40), %ax; call printnum + call print_root_partition_info jmp halt - -# These will be patched during bootloader installation -root_disk_guid: - .ascii "root disk guid " -root_partition_guid: - .ascii "root part guid " -zero_guid: - .quad 0 - .quad 0 - 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" - -root_disk_found_msg: - .asciz "Root disk found!" -no_root_disk_msg: - .asciz "Root disk not found" - -root_partition_found_msg: - .asciz "Root partition found!" -no_root_partition_msg: - .asciz "Root partition not found" - -stage2_end: - - -.section .bss - -.align SECTOR_SIZE -gpt_header: - .skip SECTOR_SIZE -gpt_entry_data: - .skip SECTOR_SIZE - -sector_buffer: - .skip SECTOR_SIZE - -disk_drive_parameters: - .skip 0x1A - -disk_address_packet: - .skip 16 - -printnum_buffer: - .skip 10 - -root_disk_drive_number: - .skip 1 - -root_partition_entry: - .skip 128 - -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 diff --git a/bootloader/command_line.S b/bootloader/command_line.S new file mode 100644 index 00000000..cec0a5a7 --- /dev/null +++ b/bootloader/command_line.S @@ -0,0 +1,69 @@ +.code16 + +.section .stage2 + +# fills command line buffer +# NO REGISTERS SAVED +.global read_user_command_line +read_user_command_line: + movw $command_line_enter_msg, %si + call puts + + movw $command_line_buffer, %di + + .read_user_command_line_loop: + call getc + + cmpb $'\b', %al + je .read_user_command_line_backspace + + # Not sure if some BIOSes return '\n' as enter, but check it just in case + cmpb $'\r', %al + je .read_user_command_line_done + cmpb $'\n', %al + je .read_user_command_line_done + + call isprint + testb %al, %al + jnz .read_user_command_line_loop + + # put byte to buffer + movb %al, (%di) + incw %di + + # print byte + call putc + + jmp .read_user_command_line_loop + + .read_user_command_line_backspace: + # don't do anything if at the beginning + cmpw $command_line_buffer, %di + je .read_user_command_line_loop + + # decrement buffer pointer + decw %di + + # erase byte in display + call print_backspace + + jmp .read_user_command_line_loop + + .read_user_command_line_done: + # null terminate command line + movb $0, (%di) + + call print_newline + + ret + + +command_line_enter_msg: + .asciz "cmdline: " + + +.section .bss + +# 100 character command line +command_line_buffer: + .skip 100 diff --git a/bootloader/disk.S b/bootloader/disk.S new file mode 100644 index 00000000..c22f62bd --- /dev/null +++ b/bootloader/disk.S @@ -0,0 +1,491 @@ +# FIXME: don't assume 512 byte sectors +.set SECTOR_SIZE_SHIFT, 9 +.set SECTOR_SIZE, 1 << SECTOR_SIZE_SHIFT + +.code16 + +.section .stage1 + +.global stage2_start +.global stage2_end + +# check that drive has int13 ext +# dl: drive number +# returns only if drive does have the extension +drive_has_int13_ext: + pusha + + movb $0x41, %ah + movw $0x55AA, %bx + int $0x13 + jc .drive_has_int13_ext_no_int13_ext + + popa + ret + + .drive_has_int13_ext_no_int13_ext: + mov $no_int13_ext_msg, %si + jmp print_and_halt + + +# 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 +.global read_from_disk +read_from_disk: + pusha + + call drive_has_int13_ext + + # prepare disk read packet + mov $disk_address_packet, %si + movb $0x10, 0x00(%si) # packet size + movb $0x00, 0x01(%si) # always 0 + movw %cx, 0x02(%si) # lba count + movw %di, 0x04(%si) # offset + movw %ds, 0x06(%si) # segment + movl %eax, 0x08(%si) # 32 bit lower lba + movw %bx, 0x0C(%si) # 16 bit upper lba + movw $0, 0x0E(%si) # zero + + # issue read command + mov $0x42, %ah + int $0x13 + jc .read_from_disk_failed + + popa + ret + + .read_from_disk_failed: + mov $read_from_disk_msg, %si + jmp print_and_halt + + +# Reads GPT header into gpt_header buffer +# dl: drive number +# return: +# ax: 1 if has GPT header, 0 otherwise +.global read_gpt_header +read_gpt_header: + pushw %bx + pushw %cx + pushw %di + + xorw %bx, %bx + movl $1, %eax + movw $1, %cx + movw $gpt_header, %di + call read_from_disk + + xorw %bx, %bx + movw $1, %ax + + # check if header starts with 'EFI PART' + cmpl $0x20494645, (gpt_header + 0) + cmovnew %bx, %ax + cmpl $0x54524150, (gpt_header + 4) + cmovnew %bx, %ax + + popw %di + popw %cx + popw %bx + ret + + +# Find bios boot partition from boot drive +# returns: +# bx:eax: first lba +# cx: sector count +find_stage2_partition: + # read boot disk GPT header + movb (boot_disk_number), %dl + call read_gpt_header + + testb %al, %al + jz .find_stage2_partition_not_gpt + + # eax := entry_count + movl (gpt_header + 80), %eax + test %eax, %eax + jz .find_stage2_partition_not_found + + # edx:eax := eax * entry_size + mull (gpt_header + 84) + test %edx, %edx + jnz .find_stage2_partition_too_big_entries + + # 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 + movw $bios_boot_guid, %si + movb (boot_disk_number), %dl + + call read_from_disk + + # NOTE: 'only' 0xFFFF partitions supported, + # although read will fail with more than 0x80 + movw (gpt_header + 80), %cx + + .find_stage2_partition_loop_gpt_entries: + pushw %cx + movw $16, %cx + call memcmp + popw %cx + + testb %al, %al + jnz .find_stage2_partition_found + + # add entry size to entry pointer + addw (gpt_header + 84), %di + + loop .find_stage2_partition_loop_gpt_entries + + # fall through to not found case + .find_stage2_partition_not_found: + movw $no_bios_boot_partition_msg, %si + jmp print_and_halt + + .find_stage2_partition_not_gpt: + movw $not_gpt_partition_msg, %si + jmp print_and_halt + + .find_stage2_partition_too_big_entries: + movw $too_gpt_big_entries_msg, %si + jmp print_and_halt + + .find_stage2_partition_found: + # first lba + movl 32(%di), %eax + movw 36(%di), %bx + + # count := last lba - first lba + 1 + movl 40(%di), %ecx + subl %eax, %ecx + incl %ecx + + ret + +# reads stage2 into memory +# dl: boot drive number +# returns only on success +.global read_stage2_into_memory +read_stage2_into_memory: + movb %dl, (boot_disk_number) + + # push stage2 sector count + movl $stage2_end, %eax + subl $stage2_start, %eax + addl $(SECTOR_SIZE - 1), %eax + movl $SECTOR_SIZE, %ecx + xorl %edx, %edx + divl %ecx + pushl %eax + + call find_stage2_partition + + movb (boot_disk_number), %dl + popl %ecx # FIXME: validate that partition has enough sectors + movw $stage2_start, %di + call read_from_disk + + ret + +# 21686148-6449-6E6F-744E-656564454649 +.align 4 +bios_boot_guid: + .long 0x21686148 # little endian + .word 0x6449 # little endian + .word 0x6E6F # little endian + .word 0x4E74 # big endian + .quad 0x494645646565 # big endian + +boot_disk_number: + .skip 1 + +read_from_disk_msg: + .asciz "read error" + +no_int13_ext_msg: + .asciz "no INT13 ext" + +no_bios_boot_partition_msg: + .asciz "no bios boot" + +too_gpt_big_entries_msg: + .asciz "too big GPT array" + +not_gpt_partition_msg: + .asciz "not GPT" + + +.section .stage2 + +# check if drive exists +# dl: drive number +# return: +# al: 1 if disk is usable, 0 otherwise +drive_exists: + pusha + + movb $0x48, %ah + movw $disk_drive_parameters, %si + movw $0x1A, (disk_drive_parameters) # set buffer size + + int $0x13 + jc .drive_exists_nope + + popa + movb $1, %al + ret + + .drive_exists_nope: + popa + movb $0, %al + ret + +# find root disk and populate root_disk_drive_number field +# NO REGISTERS SAVED +.global find_root_disk +find_root_disk: + movb $0x80, %dl + + .find_root_disk_loop: + call drive_exists + testb %al, %al + jz .find_root_disk_not_found + + # read GPT header + xorw %bx, %bx + movl $1, %eax + movw $1, %cx + movw $gpt_header, %di + call read_from_disk + + # confirm header (starts with 'EFI PART') + cmpl $0x20494645, (gpt_header + 0) + jne .find_root_disk_next_disk + cmpl $0x54524150, (gpt_header + 4) + jne .find_root_disk_next_disk + + # compare disk GUID + movw $root_disk_guid, %si + movw $(gpt_header + 56), %di + movw $16, %cx + call memcmp + testb %al, %al + jz .find_root_disk_next_disk + + movw $root_disk_found_msg, %si + call puts; call print_newline + + movb %dl, (root_disk_drive_number) + ret + + .find_root_disk_next_disk: + incb %dl + jmp .find_root_disk_loop + + .find_root_disk_not_found: + movw $root_disk_not_found_msg, %si + jmp print_and_halt + + +# finds root partition from root disk +# fills root_partition_entry data structure +# NOTE: assumes GPT header is in `gpt_header` +# NO REGISTERS SAVED +.global find_root_partition +find_root_partition: + pushl %ebp + movl %esp, %ebp + subl $16, %esp + + # esp + 0: 8 byte entry array lba + movl (gpt_header + 72), %eax + movl %eax, 0(%esp) + movl (gpt_header + 76), %eax + movl %eax, 4(%esp) + # FIXME: check that bits 48-63 are zero + + # esp + 8: 4 byte entries per sector + xorl %edx, %edx + movl $SECTOR_SIZE, %eax + divl (gpt_header + 84) + movl %eax, 8(%esp) + + # esp + 12: 4 byte entries remaining + movl (gpt_header + 80), %eax + testl %eax, %eax + jz .find_root_partition_not_found + movl %eax, 12(%esp) + + .find_root_partition_read_entry_section: + movl 0(%esp), %eax + movl 4(%esp), %ebx + movw $1, %cx + movb (root_disk_drive_number), %dl + movw $sector_buffer, %di + call read_from_disk + + # ecx: min(entries per section, entries remaining) + movl 8(%esp), %ecx + cmpl 12(%esp), %ecx + jae .find_root_partition_got_entry_count + movl 12(%esp), %ecx + + .find_root_partition_got_entry_count: + # update entries remaining + subl %ecx, 12(%esp) + + # si: entry pointer + movw $sector_buffer, %si + + .find_root_partition_loop_entries: + # temporarily save cx in dx + movw %cx, %dx + + # check that entry is used + movw $16, %cx + movw $zero_guid, %di + call memcmp + test %al, %al + jnz .find_root_partition_next_entry + + # compare entry guid to root guid + movw $16, %cx + addw $16, %si + movw $root_partition_guid, %di + call memcmp + subw $16, %si + + testb %al, %al + jnz .find_root_partition_found + + .find_root_partition_next_entry: + + # restore cx + movw %dx, %cx + + # entry pointer += entry size + addw (gpt_header + 84), %si + loop .find_root_partition_loop_entries + + # entry not found in this sector + + # increment 8 byte entry array lba + incl 0(%esp) + jno .find_root_partition_no_overflow + incl 4(%esp) + + .find_root_partition_no_overflow: + # loop to read next section if entries remaining + cmpl $0, 12(%esp) + jnz .find_root_partition_read_entry_section + + .find_root_partition_not_found: + movw $root_partition_not_found_msg, %si + jmp print_and_halt + + .find_root_partition_found: + # copy entry to buffer + movw $root_partition_entry, %di + movw $128, %cx + rep movsb + + movw $root_partition_found_msg, %si + call puts; call print_newline + + leavel + ret + + +# print information about root partition +.global print_root_partition_info +print_root_partition_info: + pushw %ax + pushw %bx + pushw %cx + pushw %si + + movw $root_partition_info_start_msg, %si + call puts; + + movw $16, %bx + movw $2, %cx + movw (root_partition_entry + 38), %ax; call print_number + movw (root_partition_entry + 36), %ax; call print_number + movw (root_partition_entry + 34), %ax; call print_number + movw (root_partition_entry + 32), %ax; call print_number + + movb $'-', %al; call putc + movb $'>', %al; call putc + + movw (root_partition_entry + 46), %ax; call print_number + movw (root_partition_entry + 44), %ax; call print_number + movw (root_partition_entry + 42), %ax; call print_number + movw (root_partition_entry + 40), %ax; call print_number + + call print_newline + + popw %si + popw %cx + popw %bx + popw %ax + ret + + +# These will be patched during bootloader installation +root_disk_guid: + .ascii "root disk guid " +root_partition_guid: + .ascii "root part guid " +zero_guid: + .skip 16, 0 + +root_disk_found_msg: + .asciz "Root disk found!" +root_disk_not_found_msg: + .asciz "Root disk not found" + +root_partition_found_msg: + .asciz "Root partition found!" +root_partition_not_found_msg: + .asciz "Root partition not found" + +root_partition_info_start_msg: + .asciz "Root partition: " + +.section .bss + +.align SECTOR_SIZE +gpt_header: + .skip SECTOR_SIZE +gpt_entry_data: + .skip SECTOR_SIZE +sector_buffer: + .skip SECTOR_SIZE + +disk_address_packet: + .skip 16 + +disk_drive_parameters: + .skip 0x1A + .skip 2 # padding + +root_disk_drive_number: + .skip 1 + .skip 3 # padding + +root_partition_entry: + .skip 128 diff --git a/bootloader/install.sh b/bootloader/install.sh index b098764a..a5436b0d 100755 --- a/bootloader/install.sh +++ b/bootloader/install.sh @@ -30,10 +30,14 @@ make mkdir -p $BUILD_DIR echo compiling bootloader -x86_64-banan_os-as $CURRENT_DIR/boot.S -o $BUILD_DIR/bootloader.o +x86_64-banan_os-as $CURRENT_DIR/boot.S -o $BUILD_DIR/boot.o +x86_64-banan_os-as $CURRENT_DIR/command_line.S -o $BUILD_DIR/command_line.o +x86_64-banan_os-as $CURRENT_DIR/disk.S -o $BUILD_DIR/disk.o +x86_64-banan_os-as $CURRENT_DIR/memory_map.S -o $BUILD_DIR/memory_map.o +x86_64-banan_os-as $CURRENT_DIR/utils.S -o $BUILD_DIR/utils.o echo linking bootloader -x86_64-banan_os-ld -nostdlib -T $CURRENT_DIR/linker.ld $BUILD_DIR/bootloader.o -o $BUILD_DIR/bootloader +x86_64-banan_os-ld -nostdlib -T $CURRENT_DIR/linker.ld $BUILD_DIR/boot.o $BUILD_DIR/command_line.o $BUILD_DIR/disk.o $BUILD_DIR/memory_map.o $BUILD_DIR/utils.o -o $BUILD_DIR/bootloader echo installing bootloader to $INSTALLER_BUILD_DIR/x86_64-banan_os-bootloader-installer $BUILD_DIR/bootloader $BANAN_DISK_IMAGE_PATH $ROOT_PARTITION_GUID diff --git a/bootloader/linker.ld b/bootloader/linker.ld index 9351edd2..eecbf531 100644 --- a/bootloader/linker.ld +++ b/bootloader/linker.ld @@ -1,10 +1,14 @@ +ENTRY(stage1_main) + SECTIONS { . = 0x7C00; .stage1 : { *(.stage1) } . = ALIGN(512); + stage2_start = .; .stage2 : { *(.stage2) } + stage2_end = .; . = ALIGN(512); .bss : { *(.bss) } diff --git a/bootloader/memory_map.S b/bootloader/memory_map.S new file mode 100644 index 00000000..ca1908c5 --- /dev/null +++ b/bootloader/memory_map.S @@ -0,0 +1,129 @@ +.code16 + +.section .stage2 + +# fills memory map data structure +# doesn't return on error +# NO REGISTERS SAVED +.global get_memory_map +get_memory_map: + 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: + ret + + .get_memory_map_error: + movw $memory_map_error_msg, %si + jmp print_and_halt + + +# print memory map from memory_map_entries +# NO REGISTERS SAVED +.global print_memory_map +print_memory_map: + 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 print_number + movw 0x04(%si), %ax + call print_number + movw 0x02(%si), %ax + call print_number + movw 0x00(%si), %ax + call print_number + + movb $',', %al + call putc + movb $' ', %al + call putc + + movw 0x0E(%si), %ax + call print_number + movw 0x0C(%si), %ax + call print_number + movw 0x0A(%si), %ax + call print_number + movw 0x08(%si), %ax + call print_number + + movb $',', %al + call putc + movb $' ', %al + call putc + + movw 0x12(%si), %ax + call print_number + movw 0x10(%si), %ax + call print_number + + call print_newline + + addw $20, %si + + decl %edx + jnz .loop_memory_map + + ret + + +memory_map_msg: + .asciz "memmap:" +memory_map_error_msg: + .asciz "Failed to get memory map" + +.section .bss + +memory_map_entry_count: + .skip 4 +# 100 entries should be enough... +memory_map_entries: + .skip 20 * 100 diff --git a/bootloader/utils.S b/bootloader/utils.S new file mode 100644 index 00000000..5938fbad --- /dev/null +++ b/bootloader/utils.S @@ -0,0 +1,218 @@ +.set SCREEN_WIDTH, 80 +.set SCREEN_HEIGHT, 25 + +.code16 + +.section .stage1 + +# prints character to screen +# al: ascii character to print +.global putc +putc: + pushw %ax + pushw %bx + movb $0x0E, %ah + xorb %bh, %bh + int $0x10 + popw %bx + popw %ax + ret + +# prints null terminated string to screen +# ds:si: string address +.global puts +puts: + pushw %si + pushw %bx + pushw %ax + + movb $0x0E, %ah + xorb %bh, %bh + + .puts_loop: + lodsb + + test %al, %al + jz .puts_done + + int $0x10 + jmp .puts_loop + + .puts_done: + popw %ax + popw %bx + popw %si + ret + +# compares memory between addresses +# si: ptr1 +# di: ptr2 +# cx: bytes count +# return: +# al: 1 if equal, 0 otherwise +.global memcmp +memcmp: + # NOTE: using pusha + popa to save space + pusha + cld + repe cmpsb + popa + setzb %al + ret + + +.section .stage2 + +# read a character from keyboard +# return: +# al: ascii +# ah: bios scan code +.global getc +getc: + movb $0x00, %ah + int $0x16 + ret + +# prints newline to screen +.global print_newline +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 +.global print_backspace +print_backspace: + pushw %ax + pushw %bx + pushw %cx + pushw %dx + + # 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: + popw %dx + popw %cx + popw %bx + popw %ax + ret + +# print number to screen +# ax: number to print +# bx: number base +# cx: min width (zero pads if shorter) +.global print_number +print_number: + pusha + pushl %ebp + movl %esp, %ebp + + # save min width + subl $4, %esp + movw %cx, (%esp) + + movw $print_number_buffer, %si + xorw %cx, %cx + + .print_number_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 .print_number_fill_loop + + # check if zero pad is required + cmpw (%esp), %cx + jae .print_number_print_loop + + # dx: saved number count + # cx: zero pad count + movw %cx, %dx + movw (%esp), %cx + subw %dx, %cx + movb $'0', %al + + .print_number_pad_zeroes: + call putc + loop .print_number_pad_zeroes + + # restore number count + movw %dx, %cx + + .print_number_print_loop: + decw %si + movb (%si), %al + cmpb $10, %al + jae .print_number_hex + addb $'0', %al + jmp .print_number_do_print + .print_number_hex: + addb $('a' - 10), %al + .print_number_do_print: + call putc + loop .print_number_print_loop + + leavel + popa + ret + +# test if character is printable ascii +# al: character to test +# return: +# al: 1 if is printable, 0 otherwise +.global isprint +isprint: + subb $0x20, %al + cmpb $(0x7E - 0x20), %al + ja .isprint_not_printable + movb $1, %al + ret + .isprint_not_printable: + movb $0, %al + ret + +.section .bss + +# enough for base 2 printing +print_number_buffer: + .skip 16