From a9412aa741ba03ef8b2792f50f47a4a484acd4cd Mon Sep 17 00:00:00 2001 From: Bananymous Date: Wed, 15 Nov 2023 16:36:53 +0200 Subject: [PATCH] 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! --- bootloader/CMakeLists.txt | 1 + bootloader/boot.S | 7 + bootloader/disk.S | 32 ++- bootloader/ext2.S | 522 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 bootloader/ext2.S diff --git a/bootloader/CMakeLists.txt b/bootloader/CMakeLists.txt index d57c93f4..f2fbbaca 100644 --- a/bootloader/CMakeLists.txt +++ b/bootloader/CMakeLists.txt @@ -6,6 +6,7 @@ set(BOOTLOADER_SOURCES boot.S command_line.S disk.S + ext2.S memory_map.S utils.S ) diff --git a/bootloader/boot.S b/bootloader/boot.S index 51e7e19a..f99ede3b 100644 --- a/bootloader/boot.S +++ b/bootloader/boot.S @@ -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 diff --git a/bootloader/disk.S b/bootloader/disk.S index c22f62bd..9ab1f60e 100644 --- a/bootloader/disk.S +++ b/bootloader/disk.S @@ -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 diff --git a/bootloader/ext2.S b/bootloader/ext2.S new file mode 100644 index 00000000..eaecac28 --- /dev/null +++ b/bootloader/ext2.S @@ -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