Bootloader do some directory restructuring

This commit is contained in:
2023-11-18 13:59:45 +02:00
parent c9e9cfd361
commit cd646a1ab7
13 changed files with 20 additions and 19 deletions

View File

@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.26)
project(bootloader ASM)
set(BOOTLOADER_SOURCES
boot.S
command_line.S
disk.S
elf.S
ext2.S
framebuffer.S
memory_map.S
utils.S
)
add_executable(bootloader ${BOOTLOADER_SOURCES})
target_link_options(bootloader PRIVATE LINKER:-T,${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
target_link_options(bootloader PRIVATE -nostdlib)

170
bootloader/bios/boot.S Normal file
View File

@@ -0,0 +1,170 @@
.code16
#########################################
#
# STAGE 1 BOOTLOADER
#
# its sole purpose is to load stage2 from
# bios boot partition
#
#########################################
.section .stage1
.global stage1_main
stage1_main:
# setup segments
movw $0, %ax
movw %ax, %ds
movw %ax, %es
# setup stack
movw %ax, %ss
movl $0x7C00, %esp
# save boot disk number
call read_stage2_into_memory
jmp stage2_main
.global print_and_halt
print_and_halt:
call puts
halt:
hlt
jmp halt
#########################################
#
# STAGE 2 BOOTLOADER
#
#########################################
.section .stage2
stage2_main:
# clear screen and enter 80x25 text mode
movb $0x03, %al
movb $0x00, %ah
int $0x10
# print hello message
movw $hello_msg, %si
call puts; call print_newline
call enter_unreal_mode
movw $unreal_enter_msg, %si
call puts; call print_newline
call get_memory_map
call read_user_command_line
call vesa_find_video_mode
call print_newline
movw $start_kernel_load_msg, %si
call puts; call print_newline
call print_memory_map
call find_root_disk
call find_root_partition
call print_root_partition_info
call print_newline
call has_ext2_filesystem
testb %al, %al
jz print_and_halt
call ext2_find_kernel
movl $ext2_inode_read_bytes, %esi
call elf_read_kernel_to_memory
call vesa_set_target_mode
cli
# setup protected mode
movl %cr0, %ebx
orb $1, %bl
movl %ebx, %cr0
# jump to kernel in protected mode
ljmpl $0x18, $protected_mode
.code32
protected_mode:
movw $0x10, %bx
movw %bx, %ds
movw %bx, %es
movw %bx, %fs
movw %bx, %gs
movw %bx, %ss
movl %eax, %ecx
movl $0xD3C60CFF, %eax
movl $banan_boot_info, %ebx
xorl %edx, %edx
xorl %esi, %esi
xorl %edi, %edi
jmp *%ecx
.code16
enter_unreal_mode:
cli
pushw %ds
lgdt gdtr
movl %cr0, %eax
orb $1, %al
movl %eax, %cr0
ljmpl $0x8, $.enter_unreal_mode_pmode
.enter_unreal_mode_pmode:
movw $0x10, %bx
movw %bx, %ds
andb 0xFE, %al
movl %eax, %cr0
ljmpl $0x0, $.enter_unreal_mode_unreal
.enter_unreal_mode_unreal:
popw %ds
sti
ret
hello_msg:
.asciz "This is banan-os bootloader"
unreal_enter_msg:
.asciz "Entered unreal mode"
start_kernel_load_msg:
.asciz "Starting to load kernel"
gdt:
.quad 0x0000000000000000
.quad 0x00009A000000FFFF
.quad 0x00CF92000000FFFF
.quad 0x00CF9A000000FFFF
gdtr:
.short . - gdt - 1
.quad gdt
banan_boot_info:
boot_command_line:
.long command_line
boot_framebuffer:
.long framebuffer
boot_memory_map:
.long memory_map

View File

@@ -0,0 +1,83 @@
.code16
.section .stage2
# fills command line buffer
# NO REGISTERS SAVED
.global read_user_command_line
read_user_command_line:
# print initial command line
movw $command_line_enter_msg, %si
call puts
movw $command_line_buffer, %si
call puts
# prepare registers for input
movw $command_line_enter_msg, %si
movw $command_line_buffer, %di
.read_user_command_line_goto_end:
cmpb $0, (%di)
jz .read_user_command_line_loop
incw %di
jmp .read_user_command_line_goto_end
.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
pushw %ax
call isprint
testb %al, %al
jz .read_user_command_line_loop
popw %ax
# 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: "
.global command_line
command_line:
# 100 character command line
command_line_buffer:
.ascii "root=/dev/sda2"
.skip 100 - 28

521
bootloader/bios/disk.S Normal file
View File

@@ -0,0 +1,521 @@
# 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
# return:
# dl: drive number
# ecx: sector count (capped at 0xFFFFFFFF)
# bx:eax: first sector
.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)
jnc .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
# ebx:eax := last lba
movl (root_partition_entry + 44), %ebx
movl (root_partition_entry + 40), %eax
# ebx:eax -= first lba - 1
subl (root_partition_entry + 36), %ebx
movl (root_partition_entry + 32), %ecx;
decl %ecx
subl %ecx, %eax
jnc .find_root_partition_count_sub_no_carry
decl %ebx
.find_root_partition_count_sub_no_carry:
# ecx: min(partition count, 0xFFFFFFFF)
movl $0xFFFFFFFF, %edx
movl %eax, %ecx
testl %ebx, %ebx
cmovnzl %edx, %ecx
# ebx:eax := first lba
# FIXME: confirm ebx bits 16:31 are zero
movl (root_partition_entry + 36), %ebx
movl (root_partition_entry + 32), %eax
movb (root_disk_drive_number), %dl
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

222
bootloader/bios/elf.S Normal file
View File

@@ -0,0 +1,222 @@
.set SECTOR_SIZE, 512
# file header field offsets
.set e_type, 16
.set e_machine, 18
.set e_version, 20
.set e_entry, 24
.set e_phoff, 32
.set e_shoff, 40
.set e_flags, 48
.set e_ehsize, 52
.set e_phentsize, 54
.set e_phnum, 56
.set e_shentsize, 58
.set e_shnum, 60
.set e_shstrndx, 62
# e_ident offsets
.set EI_CLASS, 4
.set EI_DATA, 5
.set EI_VERSION, 6
# e_ident constants
.set ELFMAGIC, 0x464C457F
.set ELFCLASS64, 2
.set ELFDATA2LSB, 1
.set EV_CURRENT, 1
# e_type constants
.set ET_EXEC, 2
# program header field offsets
.set p_type, 0
.set p_flags, 4
.set p_offset, 8
.set p_vaddr, 16
.set p_paddr, 24
.set p_filesz, 32
.set p_memsz, 40
.set p_align, 48
# p_type constants
.set PT_NULL, 0
.set PT_LOAD, 1
.code16
.section .stage2
# Validate file header stored in elf_file_header
# returns only on success
elf_validate_file_header:
cmpl $ELFMAGIC, (elf_file_header)
jne .elf_validate_file_header_invalid_magic
cmpb $ELFCLASS64, (elf_file_header + EI_CLASS)
jne .elf_validate_file_header_only_64bit_supported
cmpb $ELFDATA2LSB, (elf_file_header + EI_DATA)
jne .elf_validate_file_header_only_little_endian_supported
cmpb $EV_CURRENT, (elf_file_header + EI_VERSION)
jne .elf_validate_file_header_not_current_version
cmpl $EV_CURRENT, (elf_file_header + e_version)
jne .elf_validate_file_header_not_current_version
cmpw $ET_EXEC, (elf_file_header + e_type)
jne .elf_validate_file_header_not_executable
ret
.elf_validate_file_header_invalid_magic:
movw $elf_validate_file_header_invalid_magic_msg, %si
jmp print_and_halt
.elf_validate_file_header_only_64bit_supported:
movw $elf_validate_file_header_only_64bit_supported_msg, %si
jmp print_and_halt
.elf_validate_file_header_only_little_endian_supported:
movw $elf_validate_file_header_only_little_endian_supported_msg, %si
jmp print_and_halt
.elf_validate_file_header_not_current_version:
movw $elf_validate_file_header_not_current_version_msg, %si
jmp print_and_halt
.elf_validate_file_header_not_executable:
movw $elf_validate_file_header_not_executable_msg, %si
jmp print_and_halt
# read callback format
# eax: first byte
# ecx: byte count
# edi: buffer
# returns only on success
# reads kernel to memory
# esi: callback for reading from kernel image
# return:
# eax: kernel entry address
.global elf_read_kernel_to_memory
elf_read_kernel_to_memory:
pushal
pushl %ebp
movl %esp, %ebp
subl $2, %esp
# read file header
movl $0, %eax
movl $64, %ecx
movl $elf_file_header, %edi
call *%esi
call elf_validate_file_header
cmpl $0, (elf_file_header + e_phoff + 4)
jnz .elf_read_kernel_to_memory_unsupported_offset
# current program header
movw $0, -2(%ebp)
.elf_read_kernel_to_memory_loop_program_headers:
movw -2(%ebp), %cx
cmpw (elf_file_header + e_phnum), %cx
jae .elf_read_kernel_to_memory_done
# eax := program_header_index * e_phentsize + e_phoff
xorl %eax, %eax
movw %cx, %ax
xorl %ebx, %ebx
movw (elf_file_header + e_phentsize), %bx
mull %ebx
addl (elf_file_header + e_phoff), %eax
jc .elf_read_kernel_to_memory_unsupported_offset
# setup program header size and address
movl $56, %ecx
movl $elf_program_header, %edi
# read the program header
call *%esi
# test if program header is empty
cmpl $PT_NULL, (elf_program_header + p_type)
je .elf_read_kernel_to_memory_null_program_header
# confirm that the program header is loadable
cmpl $PT_LOAD, (elf_program_header + p_type)
jne .elf_read_kernel_to_memory_not_loadable_header
# memset p_filesz -> p_memsz to 0
movl (elf_program_header + p_filesz), %ebx
movl (elf_program_header + p_vaddr), %edi
andl $0x7FFFFFFF, %edi
addl %ebx, %edi
movl (elf_program_header + p_memsz), %ecx
subl %ebx, %ecx
jz .elf_read_kernel_to_memory_memset_done
.elf_read_kernel_to_memory_memset:
movb $0, (%edi)
incl %edi
decl %ecx
jnz .elf_read_kernel_to_memory_memset
.elf_read_kernel_to_memory_memset_done:
# read file specified in program header to memory
movl (elf_program_header + p_offset), %eax
movl (elf_program_header + p_vaddr), %edi
andl $0x7FFFFFFF, %edi
movl (elf_program_header + p_filesz), %ecx
#call print_hex32; call print_newline
call *%esi
.elf_read_kernel_to_memory_null_program_header:
incw -2(%ebp)
jmp .elf_read_kernel_to_memory_loop_program_headers
.elf_read_kernel_to_memory_done:
leavel
popal
# set kernel entry address
movl (elf_file_header + e_entry), %eax
andl $0x7FFFFF, %eax
ret
.elf_read_kernel_to_memory_unsupported_offset:
movw $elf_read_kernel_to_memory_unsupported_offset_msg, %si
jmp print_and_halt
.elf_read_kernel_to_memory_not_loadable_header:
movw $elf_read_kernel_to_memory_not_loadable_header_msg, %si
jmp print_and_halt
elf_validate_file_header_invalid_magic_msg:
.asciz "ELF: file has invalid ELF magic"
elf_validate_file_header_only_64bit_supported_msg:
.asciz "ELF: file is not targettint 64 bit"
elf_validate_file_header_only_little_endian_supported_msg:
.asciz "ELF: file is not in little endian format"
elf_validate_file_header_not_current_version_msg:
.asciz "ELF: file is not in current ELF version"
elf_validate_file_header_not_executable_msg:
.asciz "ELF: file is not an executable"
elf_read_kernel_to_memory_unsupported_offset_msg:
.asciz "ELF: unsupported offset (only 32 bit offsets supported)"
elf_read_kernel_to_memory_not_loadable_header_msg:
.asciz "ELF: kernel contains non-loadable program header"
.section .bss
elf_file_header:
.skip 64
elf_program_header:
.skip 56

705
bootloader/bios/ext2.S Normal file
View File

@@ -0,0 +1,705 @@
# FIXME: don't assume 512 byte sectors
.set SECTOR_SHIFT, 9
.set SECTOR_SIZE, 1 << SECTOR_SHIFT
# FIXME: don't assume 1024 byte blocks
.set EXT2_BLOCK_SHIFT, 10
.set EXT2_BLOCK_SIZE, 1 << EXT2_BLOCK_SHIFT
.set EXT2_SUPERBLOCK_SIZE, 264
.set EXT2_BGD_SHIFT, 5
.set EXT2_BGD_SIZE, 1 << EXT2_BGD_SHIFT
.set EXT2_INODE_SIZE_MAX, 256
.set EXT2_ROOT_INO, 2
.set EXT2_GOOD_OLD_REV, 0
# inode types
.set EXT2_S_IMASK, 0xF000
.set EXT2_S_IFDIR, 0x4000
.set EXT2_S_IFREG, 0x8000
# superblock offsets
.set s_log_block_size, 24
.set s_inodes_per_group, 40
.set s_magic, 56
.set s_rev_level, 76
.set s_inode_size, 88
# block group descriptor offsets
.set bg_inode_table, 8
# inode offsets
.set i_mode, 0
.set i_size, 4
.set i_block, 40
.code16
.section .stage2
# checks whether partition contains ext2 filesystem.
# fills ext2_superblock_buffer
# dl: drive number
# ecx: sector count
# bx:eax: first sector
# return:
# al: 1 if is ext2, 0 otherwise
# si: error message on error
.global has_ext2_filesystem
has_ext2_filesystem:
pushl %ecx
pushw %bx
pushw %di
# fill ext2_partition_first_sector
movw $0, (ext2_partition_first_sector + 6)
movw %bx, (ext2_partition_first_sector + 4)
movl %eax, (ext2_partition_first_sector + 0)
# fill ext2_drive_number
movb %dl, (ext2_drive_number)
cmpl $3, %ecx
jb .has_ext2_filesystem_does_not_fit
# one sector
movw $1, %cx
# from byte offset 1024
addl $(1024 / SECTOR_SIZE), %eax
jnc .has_ext2_filesystem_no_overflow
incw %bx
.has_ext2_filesystem_no_overflow:
# into sector buffer
movw $ext2_block_buffer, %di
call read_from_disk
# copy superblock to its buffer
movw $ext2_block_buffer, %si
movw $ext2_superblock_buffer, %di
movw $EXT2_SUPERBLOCK_SIZE, %cx
rep movsb
# verify magic
cmpw $0xEF53, (ext2_superblock_buffer + s_magic)
jne .has_ext2_filesystem_invalid_magic
# verify block size
# verify shift fits in one byte
movl (ext2_superblock_buffer + s_log_block_size), %ecx
testl $0xFFFFFF00, %ecx
jnz .has_ext2_filesystem_unsupported_block_size
# verify 1024 << s_log_block_size == EXT2_BLOCK_SIZE
movl $1024, %eax
shll %cl, %eax
cmpl $EXT2_BLOCK_SIZE, %eax
jne .has_ext2_filesystem_unsupported_block_size
# fill inode size
movl $128, %eax
cmpl $EXT2_GOOD_OLD_REV, (ext2_superblock_buffer + s_rev_level)
cmovnel (ext2_superblock_buffer + s_inode_size), %eax
movl %eax, (ext2_inode_size)
movb $1, %al
jmp .has_ext2_filesystem_done
.has_ext2_filesystem_does_not_fit:
movw $root_partition_does_not_fit_ext2_filesystem_msg, %si
movb $0, %al
jmp .has_ext2_filesystem_done
.has_ext2_filesystem_invalid_magic:
movw $root_partition_has_invalid_ext2_magic_msg, %si
movb $0, %al
jmp .has_ext2_filesystem_done
.has_ext2_filesystem_unsupported_block_size:
movw $root_partition_has_unsupported_ext2_block_size_msg, %si
movb $0, %al
jmp .has_ext2_filesystem_done
.has_ext2_filesystem_done:
popw %di
popw %bx
popl %ecx
ret
# reads block in to ext2_block_buffer
# eax: block number
ext2_read_block:
pushl %eax
pushl %ebx
pushw %cx
pushl %edx
pushw %di
# NOTE: this assumes 1024 block size
# eax := (block * block_size) / sector_size := (eax << EXT2_BLOCK_SHIFT) >> SECTOR_SHIFT
xorl %edx, %edx
shll $EXT2_BLOCK_SHIFT, %eax
shrl $SECTOR_SHIFT, %eax
# ebx:eax := eax + (ext2_partition_first_sector)
movl (ext2_partition_first_sector + 4), %ebx
addl (ext2_partition_first_sector + 0), %eax
jnc .ext2_read_block_no_carry
incl %ebx
.ext2_read_block_no_carry:
# sectors per block
movw $(EXT2_BLOCK_SIZE / SECTOR_SIZE), %cx
movw $ext2_block_buffer, %di
movb (ext2_drive_number), %dl
call read_from_disk
popw %di
popl %edx
popw %cx
popl %ebx
popl %eax
ret
# reads block group descrtiptor into ext2_block_group_descriptor
# eax: block group
ext2_read_block_group_descriptor:
pushal
# eax := bgd_byte_offset := 2048 + EXT2_BGD_SIZE * eax := (eax << EXT2_BGD_SHIFT) + 2048
shll $EXT2_BGD_SHIFT, %eax
addl $2048, %eax
# eax: bgd_block := bgd_byte_offset / EXT2_BLOCK_SIZE
# ebx: bgd_offset := bgd_byte_offset % EXT2_BLOCK_SIZE
xorl %edx, %edx
movl $EXT2_BLOCK_SIZE, %ebx
divl %ebx
movl %edx, %ebx
call ext2_read_block
# esi := &ext2_block_buffer + bgd_offset := ebx + &ext2_block_buffer
# edi := &ext2_block_group_descriptor_buffer
movl %ebx, %esi
addl $ext2_block_buffer, %esi
movl $ext2_block_group_descriptor_buffer, %edi
movw $EXT2_BGD_SIZE, %cx
rep movsb
popal
ret
# reads inode into ext2_inode_buffer
# eax: ino
ext2_read_inode:
pushal
# eax := block_group = (ino - 1) / s_inodes_per_group
# ebx := inode_index = (ino - 1) % s_inodes_per_group
xorl %edx, %edx
decl %eax
movl (ext2_superblock_buffer + s_inodes_per_group), %ebx
divl %ebx
movl %edx, %ebx
call ext2_read_block_group_descriptor
# eax := inode_table_block := (inode_index * inode_size) / EXT2_BLOCK_SIZE
# ebx := inode_table_offset := (inode_index * inode_size) % EXT2_BLOCK_SIZE
xorl %edx, %edx
movl %ebx, %eax
movl (ext2_inode_size), %ebx
mull %ebx
movl $EXT2_BLOCK_SIZE, %ebx
divl %ebx
movl %edx, %ebx
# eax := file system block := eax + bg_inode_table
addl (ext2_block_group_descriptor_buffer + bg_inode_table), %eax
movb (ext2_drive_number), %dl
call ext2_read_block
# copy inode memory
# esi := inode_table_offset + ext2_block_buffer := edx + ext2_block_buffer
movl %ebx, %esi
addl $ext2_block_buffer, %esi
# edi := ext2_inode_buffer
movl $ext2_inode_buffer, %edi
# ecx := inode_size
movl (ext2_inode_size), %ecx
rep movsb
popal
ret
# gets block index from n'th data block in inode stored in ext2_inode_buffer
# eax: data block index
# return:
# eax: block index
ext2_data_block_index:
pushl %ebx
pushl %ecx
pushl %edx
pushl %esi
# calculate max data blocks
movl (ext2_inode_buffer + i_size), %ecx
addl (ext2_inode_size), %ecx
decl %ecx
shll $EXT2_BLOCK_SHIFT, %ecx
# verify data block is within bounds
cmpl %ecx, %eax
jae .ext2_data_block_index_out_of_bounds
# check if this is direct block access
cmpl $12, %eax
jb .ext2_data_block_index_direct
subl $12, %eax
# check if this is singly indirect block access
cmpl $(EXT2_BLOCK_SIZE / 4), %eax
jb .ext2_data_block_index_singly_indirect
subl $(EXT2_BLOCK_SIZE / 4), %eax
# check if this is doubly indirect block access
cmpl $((EXT2_BLOCK_SIZE / 4) * (EXT2_BLOCK_SIZE / 4)), %eax
jb .ext2_data_block_index_doubly_indirect
subl $((EXT2_BLOCK_SIZE / 4) * (EXT2_BLOCK_SIZE / 4)), %eax
# check if this is triply indirect block access
cmpl $((EXT2_BLOCK_SIZE / 4) * (EXT2_BLOCK_SIZE / 4) * (EXT2_BLOCK_SIZE / 4)), %eax
jb .ext2_data_block_index_triply_indirect
# otherwise this is invalid access
jmp .ext2_data_block_index_invalid
.ext2_data_block_index_direct:
movl $(ext2_inode_buffer + i_block), %esi
movl (%esi, %eax, 4), %eax
jmp .ext2_data_block_index_done
.ext2_data_block_index_singly_indirect:
movl %eax, %ebx
movl (ext2_inode_buffer + i_block + 12 * 4), %eax
movw $1, %cx
jmp .ext2_data_block_index_indirect
.ext2_data_block_index_doubly_indirect:
movl %eax, %ebx
movl (ext2_inode_buffer + i_block + 13 * 4), %eax
movw $2, %cx
jmp .ext2_data_block_index_indirect
.ext2_data_block_index_triply_indirect:
movl %eax, %ebx
movl (ext2_inode_buffer + i_block + 14 * 4), %eax
movw $3, %cx
jmp .ext2_data_block_index_indirect
# eax := current block
# ebx := index
# cx := depth
.ext2_data_block_index_indirect:
call ext2_read_block
# store depth and index
pushw %cx
pushl %ebx
cmpw $1, %cx
jbe .ext2_data_block_index_no_shift
# cl := shift
movb $(EXT2_BLOCK_SHIFT - 2), %al
decb %cl
mulb %cl
movb %al, %cl
# ebx := ebx >> cl
shrl %cl, %ebx
.ext2_data_block_index_no_shift:
# edx := index of next block
movl %ebx, %eax
xorl %edx, %edx
movl $(EXT2_BLOCK_SIZE / 4), %ebx
divl %ebx
# eax := next block
movl $ext2_block_buffer, %esi
movl (%esi, %edx, 4), %eax
# restore depth and index
popl %ebx
popw %cx
loop .ext2_data_block_index_indirect
jmp .ext2_data_block_index_done
.ext2_data_block_index_out_of_bounds:
movw $ext2_data_block_index_out_of_bounds_msg, %si
call puts; call print_newline
movl $0, %eax
jmp .ext2_data_block_index_done
.ext2_data_block_index_invalid:
movw $ext2_data_block_index_invalid_msg, %si
call puts; call print_newline
movl $0, %eax
jmp .ext2_data_block_index_done
.ext2_data_block_index_done:
popl %esi
popl %edx
popl %ecx
popl %ebx
ret
# read bytes from inode (implements read callback)
# eax: first byte
# ecx: byte count
# edi: buffer
# returns only on success
.global ext2_inode_read_bytes
ext2_inode_read_bytes:
pushal
pushl %ebp
movl %esp, %ebp
subl $8, %esp
# save read info
movl %eax, 0(%esp)
movl %ecx, 4(%esp)
# check if eax % EXT2_BLOCK_SIZE != 0,
# then we need to read a partial block starting from an offset
xorl %edx, %edx
movl $EXT2_BLOCK_SIZE, %ebx
divl %ebx
testl %edx, %edx
jz .ext2_inode_read_bytes_no_partial_start
# get data block index and read block
call ext2_data_block_index
call ext2_read_block
# ecx := byte count (min(block_size - edx, remaining_bytes))
movl $EXT2_BLOCK_SIZE, %ecx
subl %edx, %ecx
cmpl %ecx, 4(%esp)
cmovbl 4(%esp), %ecx
# update remaining read info
addl %ecx, 0(%esp)
subl %ecx, 4(%esp)
# esi := start sector data (block_buffer + index * SECTOR_SIZE)
movl $ext2_block_buffer, %esi
addl %edx, %esi
# very dumb memcpy with 32 bit addresses
movl $0, %ebx
.ext2_inode_read_bytes_memcpy_partial:
movb (%esi, %ebx), %al
movb %al, (%edi, %ebx)
incl %ebx
decl %ecx
jnz .ext2_inode_read_bytes_memcpy_partial
addl %ebx, %edi
# check if all sectors are read
cmpl $0, 4(%esp)
je .ext2_inode_read_bytes_done
.ext2_inode_read_bytes_no_partial_start:
# eax := data block index (byte_start / block_size)
movl 0(%esp), %eax
shrl $(EXT2_BLOCK_SHIFT), %eax
# get data block index and read block
call ext2_data_block_index
call ext2_read_block
# calculate bytes to copy (min(block_size, remaining_bytes))
movl $EXT2_BLOCK_SIZE, %ecx
cmpl %ecx, 4(%esp)
cmovbl 4(%esp), %ecx
# update remaining read info
addl %ecx, 0(%esp)
subl %ecx, 4(%esp)
# very dumb memcpy with 32 bit addresses
movl $ext2_block_buffer, %esi
movl $0, %ebx
.ext2_inode_read_bytes_memcpy:
movb (%esi, %ebx), %al
movb %al, (%edi, %ebx)
incl %ebx
decl %ecx
jnz .ext2_inode_read_bytes_memcpy
addl %ebx, %edi
# read next block if more sectors remaining
cmpl $0, 4(%esp)
jnz .ext2_inode_read_bytes_no_partial_start
.ext2_inode_read_bytes_done:
leavel
popal
ret
# find inode in inside directory inode stored in ext2_inode_buffer
# store the found inode in ext2_inode_buffer
# si: name string
# cx: name length
# return:
# eax: ino if inode was found, 0 otherwise
ext2_directory_find_inode:
pushl %ebx
pushw %cx
pushw %dx
pushw %si
pushw %di
pushl %ebp
movl %esp, %ebp
subl $8, %esp
# 0(%esp) := name length
movw %cx, 0(%esp)
# 2(%esp) := name string
movw %si, 2(%esp)
# verify that the name is <= 0xFF bytes
cmpw $0xFF, %cx
ja .ext2_directory_find_inode_not_found
# ebx := max data blocks: ceil(i_size / EXT2_BLOCK_SIZE)
movl (ext2_inode_buffer + i_size), %ebx
addl $EXT2_BLOCK_SHIFT, %ebx
decl %ebx
shrl $EXT2_BLOCK_SHIFT, %ebx
jz .ext2_directory_find_inode_not_found
# 4(%esp) := current block
movl $0, 4(%esp)
.ext2_directory_find_inode_block_read_loop:
# get next block index
movl 4(%esp), %eax
call ext2_data_block_index
test %eax, %eax
jz .ext2_directory_find_inode_next_block
# read current block
call ext2_read_block
# dx := current entry pointer
movw $ext2_block_buffer, %si
.ext2_directory_find_inode_loop_entries:
# temporarily store entry pointer in dx
movw %si, %dx
# check if name length matches
# cx := name length
movw 0(%esp), %cx
cmpb 6(%si), %cl
jne .ext2_directory_find_inode_next_entry
# si := entry name
addw $8, %si
# di := asked name
movw 2(%esp), %di
# check if name matches
call memcmp
test %al, %al
# NOTE: dx contains entry pointer
jnz .ext2_directory_find_inode_found
.ext2_directory_find_inode_next_entry:
# restore si
movw %dx, %si
# go to next entry if this block contains one
addw 4(%si), %si
cmpw $(ext2_block_buffer + EXT2_BLOCK_SIZE), %si
jb .ext2_directory_find_inode_loop_entries
.ext2_directory_find_inode_next_block:
incl 4(%esp)
cmpl %ebx, 4(%esp)
jb .ext2_directory_find_inode_block_read_loop
.ext2_directory_find_inode_not_found:
movb $0, %al
jmp .ext2_directory_find_inode_done
.ext2_directory_find_inode_found:
# extract ino and read it to ext2_inode_buffer
movw %dx, %si
movl 0(%si), %eax
call ext2_read_inode
.ext2_directory_find_inode_done:
leavel
popw %di
popw %si
popw %dx
popw %cx
popl %ebx
ret
# search for kernel file from filesystem
# returns only on success
.global ext2_find_kernel
ext2_find_kernel:
pushl %eax
pushw %cx
pushw %di
pushw %si
movl $EXT2_ROOT_INO, %eax
call ext2_read_inode
movw $kernel_path, %di
.ext2_find_kernel_loop:
movw (%di), %si
# check if this list is done
testw %si, %si
jz .ext2_find_kernel_loop_done
# check that current part is directory
movw (ext2_inode_buffer + i_mode), %ax
andw $EXT2_S_IMASK, %ax
cmpw $EXT2_S_IFDIR, %ax
jne .ext2_find_kernel_part_not_dir
# prepare registers for directory finding
movw 0(%si), %cx
addw $2, %si
# print search path
pushw %si
movw $ext2_looking_for_msg, %si
call puts
popw %si
call puts; call print_newline
# search current directory for this file
call ext2_directory_find_inode
testl %eax, %eax
jz .ext2_find_kernel_part_not_found
# loop to next part
addw $2, %di
jmp .ext2_find_kernel_loop
.ext2_find_kernel_loop_done:
# check that kernel is a regular file
movw (ext2_inode_buffer + i_mode), %ax
andw $EXT2_S_IMASK, %ax
cmpw $EXT2_S_IFREG, %ax
jne .ext2_find_kernel_not_reg
movw $ext2_kernel_found_msg, %si
call puts; call print_newline
popw %si
popw %di
popw %cx
popl %eax
ret
.ext2_find_kernel_part_not_dir:
movw $ext2_part_not_dir_msg, %si
jmp print_and_halt
.ext2_find_kernel_part_not_found:
movw $ext2_part_not_found_msg, %si
jmp print_and_halt
.ext2_find_kernel_not_reg:
movw $ext2_kernel_not_reg_msg, %si
jmp print_and_halt
kernel_path:
.short kernel_path1
.short kernel_path2
.short 0
kernel_path1:
.short 4
.asciz "boot"
kernel_path2:
.short 15
.asciz "banan-os.kernel"
root_partition_does_not_fit_ext2_filesystem_msg:
.asciz "Root partition is too small to contain ext2 filesystem"
root_partition_has_invalid_ext2_magic_msg:
.asciz "Root partition doesn't contain ext2 magic number"
root_partition_has_unsupported_ext2_block_size_msg:
.asciz "Root partition has unsupported ext2 block size (only 1024 supported)"
ext2_part_not_dir_msg:
.asciz "inode in root path is not directory"
ext2_part_not_found_msg:
.asciz " not found"
ext2_kernel_not_reg_msg:
.asciz "kernel is not a regular file"
ext2_kernel_found_msg:
.asciz "kernel found!"
ext2_data_block_index_out_of_bounds_msg:
.asciz "data block index out of bounds"
ext2_data_block_index_invalid_msg:
.asciz "data block index is invalid"
ext2_looking_for_msg:
.asciz "looking for "
.section .bss
ext2_block_buffer:
.skip EXT2_BLOCK_SIZE
ext2_partition_first_sector:
.skip 8
ext2_drive_number:
.skip 1
.skip 3 # padding
# NOTE: fits in 2 bytes
ext2_inode_size:
.skip 4
ext2_superblock_buffer:
.skip EXT2_SUPERBLOCK_SIZE
ext2_block_group_descriptor_buffer:
.skip EXT2_BGD_SIZE
ext2_inode_buffer:
.skip EXT2_INODE_SIZE_MAX

View File

@@ -0,0 +1,156 @@
.set TARGET_WIDTH, 800
.set TARGET_HEIGHT, 600
.set TARGET_BPP, 32
.code16
.section .stage2
# Find suitable video mode
# return:
# ax: video mode number if found, 0 otherwise
.global vesa_find_video_mode
vesa_find_video_mode:
pushw %ax
pushw %cx
pushw %di
pushl %esi
# clear target mode and frame buffer
movw $0, (vesa_target_mode)
movl $0, (framebuffer + 0)
movl $0, (framebuffer + 4)
movl $0, (framebuffer + 8)
movl $0, (framebuffer + 12)
movw $0, (framebuffer + 16)
# get vesa information
movw $0x4F00, %ax
movw $vesa_info_buffer, %di
int $0x10
cmpb $0x4F, %al; jne .vesa_unsupported
cmpb $0x00, %ah; jne .vesa_error
# confirm that response starts with 'VESA'
cmpl $0x41534556, (vesa_info_buffer)
jne .vesa_error
# confirm that version is atleast 2.0
cmpw $0x0200, (vesa_info_buffer + 0x04)
jb .vesa_unsupported_version
movl $(vesa_info_buffer + 0x0E), %esi
movl (%esi), %esi
.vesa_find_video_mode_loop_modes:
cmpw $0xFFFF, (%esi)
je .vesa_find_video_mode_loop_modes_done
# get info of next mode
movw $0x4F01, %ax
movw (%esi), %cx
movw $vesa_mode_info_buffer, %di
int $0x10
cmpb $0x4F, %al; jne .vesa_unsupported
cmpb $0x00, %ah; jne .vesa_error
# check whether in graphics mode
testb $0x10, (vesa_mode_info_buffer + 0)
jz .vesa_find_video_mode_next_mode
# compare mode's dimensions
cmpw $TARGET_WIDTH, (vesa_mode_info_buffer + 0x12)
jne .vesa_find_video_mode_next_mode
cmpw $TARGET_HEIGHT, (vesa_mode_info_buffer + 0x14)
jne .vesa_find_video_mode_next_mode
cmpb $TARGET_BPP, (vesa_mode_info_buffer + 0x19)
jne .vesa_find_video_mode_next_mode
movl (vesa_mode_info_buffer + 0x28), %esi
movl %esi, (framebuffer + 0)
movw (vesa_mode_info_buffer + 0x10), %ax
movw %ax, (framebuffer + 4)
movl $TARGET_WIDTH, (framebuffer + 8)
movl $TARGET_HEIGHT, (framebuffer + 12)
movb $TARGET_BPP, (framebuffer + 16)
movb $1, (framebuffer + 17)
movw %cx, (vesa_target_mode)
jmp .vesa_find_video_mode_loop_modes_done
.vesa_find_video_mode_next_mode:
addl $2, %esi
jmp .vesa_find_video_mode_loop_modes
.vesa_find_video_mode_loop_modes_done:
popl %esi
popw %di
popw %cx
popw %ax
ret
.vesa_unsupported:
movw $vesa_unsupported_msg, %si
jmp print_and_halt
.vesa_unsupported_version:
movw $vesa_unsupported_version_msg, %si
jmp print_and_halt
.vesa_error:
movw $vesa_error_msg, %si
jmp print_and_halt
# set mode found from vesa_find_video_mode. if no mode
# was found, set it to 80x25 text mode to clear the screen.
.global vesa_set_target_mode
vesa_set_target_mode:
pushw %ax
pushw %bx
movw (vesa_target_mode), %bx
testw %bx, %bx
jz .vesa_set_target_mode_generic
movw $0x4F02, %ax
orw $0x4000, %bx
int $0x10
jmp .set_video_done
.vesa_set_target_mode_generic:
movb $0x03, %al
movb $0x00, %ah
int $0x10
.set_video_done:
popw %bx
popw %ax
ret
vesa_error_msg:
.asciz "VESA error"
vesa_unsupported_msg:
.asciz "VESA unsupported"
vesa_unsupported_version_msg:
.asciz "VESA unsupported version"
vesa_success_msg:
.asciz "VESA success"
.section .bss
vesa_info_buffer:
.skip 512
vesa_mode_info_buffer:
.skip 256
vesa_target_mode:
.skip 2
.global framebuffer
framebuffer:
.skip 4 # address
.skip 4 # pitch
.skip 4 # width
.skip 4 # height
.skip 1 # bpp
.skip 1 # type

15
bootloader/bios/linker.ld Normal file
View File

@@ -0,0 +1,15 @@
ENTRY(stage1_main)
SECTIONS
{
. = 0x7C00;
.stage1 : { *(.stage1) }
. = ALIGN(512);
stage2_start = .;
.stage2 : { *(.stage2) }
stage2_end = .;
. = ALIGN(512);
.bss : { *(.bss) }
}

View File

@@ -0,0 +1,131 @@
.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
.global memory_map
memory_map:
memory_map_entry_count:
.skip 4
# 100 entries should be enough...
memory_map_entries:
.skip 20 * 100

280
bootloader/bios/utils.S Normal file
View File

@@ -0,0 +1,280 @@
.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
# prints 8 bit hexadecimal number to screen
# al: number to print
.global print_hex8
print_hex8:
pushw %ax
pushw %bx
pushw %cx
movw $16, %bx
movw $2, %cx
andw $0xFF, %ax
call print_number
popw %cx
popw %bx
popw %ax
ret
# prints 16 bit hexadecimal number to screen
# ax: number to print
.global print_hex16
print_hex16:
pushw %bx
pushw %cx
movw $16, %bx
movw $4, %cx
call print_number
popw %cx
popw %bx
ret
# prints 32 bit hexadecimal number to screen
# eax: number to print
.global print_hex32
print_hex32:
pushl %eax
pushw %dx
movw %ax, %dx
shrl $16, %eax;
call print_hex16
movw %dx, %ax
call print_hex16
popw %dx
popl %eax
ret
# prints 64 bit hexadecimal number to screen
# edx:eax: number to print
.global print_hex64
print_hex64:
xchgl %eax, %edx
call print_hex32
xchgl %eax, %edx
call print_hex32
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