Bootloader: Implement basic ext2 filesystem

This can search for files in an ext2 filesystem. Only 12 blocks
are currently supported.

Now only ELF loading is missing for loading the actual kernel!
This commit is contained in:
Bananymous 2023-11-15 16:36:53 +02:00
parent 8aab3a62cc
commit a9412aa741
4 changed files with 561 additions and 1 deletions

View File

@ -6,6 +6,7 @@ set(BOOTLOADER_SOURCES
boot.S
command_line.S
disk.S
ext2.S
memory_map.S
utils.S
)

View File

@ -67,6 +67,13 @@ stage2_main:
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
jmp halt

View File

@ -305,6 +305,10 @@ find_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
@ -385,7 +389,7 @@ find_root_partition:
# increment 8 byte entry array lba
incl 0(%esp)
jno .find_root_partition_no_overflow
jnc .find_root_partition_no_overflow
incl 4(%esp)
.find_root_partition_no_overflow:
@ -406,6 +410,32 @@ find_root_partition:
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

522
bootloader/ext2.S Normal file
View File

@ -0,0 +1,522 @@
# 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
# cx := inode_size
movw (ext2_inode_size), %cx
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 %ecx
# 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
jmp .ext2_data_block_index_unsupported
.ext2_data_block_index_direct:
movl $(ext2_inode_buffer + i_block), %ecx
addl %eax, %ecx
movl (%ecx), %eax
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_unsupported:
movw $ext2_data_block_index_unsupported_msg, %si
call puts; call print_newline
movl $0, %eax
jmp .ext2_data_block_index_done
.ext2_data_block_index_done:
popl %ecx
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
.global ext2_find_kernel
ext2_find_kernel:
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
1: jmp 1b
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_unsupported_msg:
.asciz "unsupported data block index"
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