banan-os/bootloader/bios/ext2.S

750 lines
16 KiB
ArmAsm

# FIXME: don't assume 512 byte sectors
.set SECTOR_SHIFT, 9
.set SECTOR_SIZE, 1 << SECTOR_SHIFT
.set EXT2_MAX_BLOCK_SIZE, 4096
.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_first_data_block, 20
.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
adcw $0, %bx
# 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_MAX_BLOCK_SIZE
movl $1024, %eax
shll %cl, %eax
cmpl $EXT2_MAX_BLOCK_SIZE, %eax
ja .has_ext2_filesystem_unsupported_block_size
# fill block size and shift
movl %eax, (ext2_block_size)
addl $10, %ecx
movl %ecx, (ext2_block_shift)
# 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:
pushal
# ecx := sectors_per_block := block_size / sector_size
movl (ext2_block_size), %ecx
shrl $SECTOR_SHIFT, %ecx
# ebx:eax := block * sectors_per_block + (ext2_partition_first_sector)
xorl %ebx, %ebx
mull %ecx
addl (ext2_partition_first_sector + 0), %eax
adcl (ext2_partition_first_sector + 4), %ebx
movw $ext2_block_buffer, %di
movb (ext2_drive_number), %dl
call read_from_disk
popal
ret
# reads block group descrtiptor into ext2_block_group_descriptor
# eax: block group
ext2_read_block_group_descriptor:
pushal
# ebx := bgd_block_byte_offset := (s_first_data_block + 1) * block_size
# := (s_first_data_block + 1) << ext2_block_shift
movl (ext2_superblock_buffer + s_first_data_block), %ebx
incl %ebx
movb (ext2_block_shift), %cl
shll %cl, %ebx
# eax := bgd_byte_offset := bgd_block_byte_offset + EXT2_BGD_SIZE * block_group;
# := bgd_block_byte_offset + (block_group << EXT2_BGD_SHIFT)
movb $EXT2_BGD_SHIFT, %cl
shll %cl, %eax
addl %ebx, %eax
# eax: bgd_block := bgd_byte_offset / block_size
# ebx: bgd_offset := bgd_byte_offset % block_size
xorl %edx, %edx
divl (ext2_block_size)
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
divl (ext2_superblock_buffer + s_inodes_per_group)
movl %edx, %ebx
call ext2_read_block_group_descriptor
# eax := inode_table_block := (inode_index * inode_size) / block_size
# ebx := inode_table_offset := (inode_index * inode_size) % block_size
movl %ebx, %eax
mull (ext2_inode_size)
divl (ext2_block_size)
movl %edx, %ebx
# eax := filesystem_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
# reset indirect cache to zero
movl $0, (ext2_inode_indirect_number)
.ext2_read_inode_done:
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
pushl %edi
# ebx := max_data_blocks := (file_size + block_size - 1) / block_size
# := (i_size + ext2_block_size - 1) >> ext2_block_shift
# cl := ext2_block_shift
movl (ext2_inode_buffer + i_size), %ebx
addl (ext2_block_size), %ebx
decl %ebx
movb (ext2_block_shift), %cl
shrl %cl, %ebx
# verify data block is within bounds
cmpl %ebx, %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
# cl := indices_per_block_shift := ext2_block_shift - 2
# ebx := comp
subb $2, %cl
movl $1, %ebx
shll %cl, %ebx
# check if this is singly indirect block access
cmpl %ebx, %eax
jb .ext2_data_block_index_singly_indirect
subl %ebx, %eax
shll %cl, %ebx
# check if this is doubly indirect block access
cmpl %ebx, %eax
jb .ext2_data_block_index_doubly_indirect
subl %ebx, %eax
shll %cl, %ebx
# check if this is triply indirect block access
cmpl %ebx, %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:
# edx := cache index := (index & ~(block_size / 4 - 1)) | depth
# := (index & -(block_size >> 2)) | depth
movl (ext2_block_size), %edx
shrl $2, %edx
negl %edx
andl %ebx, %edx
orw %cx, %dx
# check whether this block is already cached
cmpl $0, (ext2_inode_indirect_number)
je .ext2_data_block_index_indirect_no_cache
cmpl %edx, (ext2_inode_indirect_number)
je .ext2_data_block_index_indirect_cached
.ext2_data_block_index_indirect_no_cache:
# update cache block number, will be cached when found
movl %edx, (ext2_inode_indirect_number)
# eax := current block
# ebx := index
# cx := depth
.ext2_data_block_index_indirect_loop:
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), %al
subb $2, %al
decb %cl
mulb %cl
movb %al, %cl
# ebx := ebx >> shift
shrl %cl, %ebx
.ext2_data_block_index_no_shift:
# edx := index of next block (ebx & (block_size / 4 - 1))
movl (ext2_block_size), %edx
shrl $2, %edx
decl %edx
andl %ebx, %edx
# 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_loop
# cache last read block
movw $ext2_block_buffer, %si
movw $ext2_inode_indirect_buffer, %di
movw (ext2_block_size), %cx
rep movsb
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_indirect_cached:
movl $ext2_inode_indirect_buffer, %esi
movl (ext2_block_size), %edx
shrl $2, %edx
decl %edx
andl %edx, %ebx
movl (%esi, %ebx, 4), %eax
.ext2_data_block_index_done:
popl %edi
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)
# eax := first_byte / block_size
# edx := first_byte % block_size
# when edx == 0, no partial read needed
xorl %edx, %edx
divl (ext2_block_size)
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
call memcpy32
# 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
movb (ext2_block_shift), %cl
shrl %cl, %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)
movl $ext2_block_buffer, %esi
call memcpy32
# 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 / block_size)
movl (ext2_inode_buffer + i_size), %ebx
addl (ext2_block_size), %ebx
decl %ebx
movb (ext2_block_shift), %cl
shrl %cl, %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
movw $ext2_block_buffer, %di
addw (ext2_block_size), %di
cmpw %di, %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:
xorb %al, %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
.section .data
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 (1 KiB, 2 KiB and 4 KiB are 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
.align SECTOR_SIZE
ext2_block_buffer:
.skip EXT2_MAX_BLOCK_SIZE
ext2_inode_indirect_buffer:
.skip EXT2_MAX_BLOCK_SIZE
ext2_inode_indirect_number:
.skip 4
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_block_size:
.skip 4
ext2_block_shift:
.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