reactos/boot/freeldr/bootsect/extldr.S
Daniel Victor ddf55b3772
[BOOTSECT][FREELDR] Rewrite the Ext bootsector (#7544)
Rewrite the Ext bootsector because the older one was broken and had no compatibility with Ext4 extents.
Introduce a 3rd-stage bootsector for complex code.

CORE-14235

- Why was the previous bootsector broken?
  Because of hardcoded inode size, hardcoded freeldr base address, etc.

- Why is there a extldr.sys?
  The extldr.sys was introduced because the limited code space in the bootsector
  prevents adding new features, such as Ext4 full extents support.

- What is extldr.sys and What does it do?
  It is the helper file for the Ext bootsector and that is necessary for adding
  Ext4 support. It locates the freeldr.sys file, loads it into memory and runs it.
2025-03-18 21:53:47 +01:00

583 lines
14 KiB
ArmAsm

/*
* PROJECT: FreeLoader
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: EXTFS second stage loader
* COPYRIGHT: Copyright 2024-2025 Daniel Victor <ilauncherdeveloper@gmail.com>
*/
#define INCLUDED_ASM
#include "ext.S"
ExtLdrEntryPoint:
// Clear segment registers
xor eax, eax
mov ds, ax
mov es, ax
// Read root directory Inode
mov al, EXT_ROOT_INODE
call ExtReadInode
// Set file base address and search for freeldr
mov dword ptr BP_REL(ExtFileAddress), FREELDR_BASE
call ExtSetFileSegment
// Swap ES with DS segment and clear SI
call SwapESWithDS
xor si, si
// Set the directory entries size limit
mov eax, EXT_INODE_DATA_SIZE_LIMIT
// Get directory entries size
mov edx, dword ptr BP_REL(ExtFileSize)
// Set the buffer size with limit
cmp edx, eax
jbe .FLoop2
mov edx, eax
.FLoop2:
// Load directory Inode
mov eax, dword ptr ds:[si]
// If the buffer reached the end then abort the search
cmp si, dx
jae FreeLdrPrintFileNotFound
// Save SI
push si
// If the Inode is zero then skip this entry
test eax, eax
jz .FSkip2
mov di, offset FreeLdrFileName // Load target filename address
mov cx, offset FreeLdrFileNameEnd - FreeLdrFileName // Length of filename to compare
cmp byte ptr ds:[si + EXT_DIRECTORY_ENTRY_NAME_LENGTH_OFFSET], cl // Compare if both names have the same length
jnz .FSkip2 // Skip this entry if yes
add si, EXT_DIRECTORY_ENTRY_NAME_OFFSET // Move to filename in entry
repe cmpsb // Compare filenames
pop si // Restore SI
jz LoadFreeLdr // Found matching file
push si // Save SI
.FSkip2:
// Restore SI
pop si
// Move to next directory entry
add si, word ptr ds:[si + EXT_DIRECTORY_ENTRY_SIZE_OFFSET]
jmp .FLoop2
ExtFileIndirectPointer:
.long 0
ExtFileIndirectPointerDouble:
.long 0
SwapESWithDS:
// Swap ES and DS
push es
push ds
pop es
pop ds
ret
ExtSetFileSegment:
mov ebx, dword ptr BP_REL(ExtFileAddress) // Get the EXT file address
shr ebx, 4 // Shift four bits to the right to get segment
jmp .ExtSkip
ExtSetInodeSegment:
mov bx, EXT_INODE_ADDRESS / 16 // Get the EXT inode address
jmp .ExtSkip
ExtSetBlockSegment:
mov bx, EXT_BLOCK_ADDRESS / 16 // Get the EXT block address
jmp .ExtSkip
ExtSetBlock2Segment:
mov bx, EXT_BLOCK2_ADDRESS / 16 // Get the EXT second block address
jmp .ExtSkip
ExtSetBlock3Segment:
mov bx, EXT_BLOCK3_ADDRESS / 16 // Get the EXT third block address
jmp .ExtSkip
ExtSetBlock4Segment:
mov bx, EXT_BLOCK4_ADDRESS / 16 // Get the EXT fourth block address
jmp .ExtSkip
ExtSetBlock5Segment:
mov bx, EXT_BLOCK5_ADDRESS / 16 // Get the EXT fifth block address
jmp .ExtSkip
ExtSetBlock6Segment:
mov bx, EXT_BLOCK6_ADDRESS / 16 // Get the EXT sixth block address
jmp .ExtSkip
ExtSetBlock7Segment:
mov bx, EXT_BLOCK7_ADDRESS / 16 // Get the EXT seventh block address
.ExtSkip:
mov es, bx // Set ES
xor bx, bx // Clear BX
ret
ExtUpdateFileSize:
mov eax, dword ptr BP_REL(ExtBlockSizeInBytes)
ExtAdjustFileSize:
// Update the file size
sub dword ptr BP_REL(ExtFileSizeState), eax
add dword ptr BP_REL(ExtFileAddress), eax
ret
ExtInodeDetectExtentsFlag:
push eax
mov eax, es:[si + EXT_INODE_FLAGS_OFFSET]
test eax, EXT_INODE_FLAG_EXTENTS
pop eax
ret
ExtFileReadBlocks:
// If the block count is zero then do nothing
test bx, bx
jz .FRDone
// Read the block
call ExtFileReadBlock
// Go to the next block and decrement the block count
inc eax
dec bx
// Loop until all blocks are read
jmp ExtFileReadBlocks
.FRDone:
ret
ExtSafeReadBlock:
pushad
// If the block is zero then just clear the block
test eax, eax
jz .NullBlk
// Read the block safely
call ExtReadBlock
jmp .RBDone
.NullBlk:
// If the block is 0 then just clear the block
mov di, bx
xor ax, ax
mov cx, word ptr BP_REL(ExtBlockSizeInBytes)
rep stosb
.RBDone:
popad
ret
// Reads a block from the EXT file
// EAX has the block to read
// ExtFileAddress has the address to write to
ExtFileReadBlock:
push es
pushad
// Set the EXT file segment
call ExtSetFileSegment
// Read the block safely
call ExtSafeReadBlock
// Update the file size
call ExtUpdateFileSize
// Exit
popad
pop es
ret
ExtReadEntireFileDone:
push eax // Save EAX register
mov eax, dword ptr BP_REL(ExtBlockSizeInBytes) // Load block size in bytes into EAX
cmp dword ptr BP_REL(ExtFileSizeState), eax // Compare file size state with block size
pop eax // Restore EAX register
ret // Return from procedure
// Loops over the block pointers and reads the blocks
// until the file size state is zero
ExtReadEntireFileLoop:
call ExtReadEntireFileDone
jb .RDone
mov eax, dword ptr es:[si]
// Read the block safely
call ExtFileReadBlock
add si, EXT_POINTER_SIZE
loop ExtReadEntireFileLoop
.RDone:
ret
// Reads the double indirect block data
// This function is used to read the blocks that are not contiguous
// to the blocks that are pointed by the indirect block
ExtReadEntireFileIndirectDouble:
push es
pushad
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .RDone4
// Reset block offset
xor si, si
// Load the double indirect block address
mov eax, dword ptr BP_REL(ExtFileIndirectPointerDouble)
call ExtSetBlock2Segment
call ExtSafeReadBlock
// Load the number of pointers per block
mov ecx, dword ptr BP_REL(ExtPointersPerBlock)
.RLoop4:
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .RDone4
// Get the block address
mov eax, dword ptr es:[si]
// Read the indirect block
call ExtReadEntireFileIndirect
// Increment block offset
add si, EXT_POINTER_SIZE
// Loop until all blocks are read
loop .RLoop4
.RDone4:
popad
pop es
ret
// Reads the indirect block data
ExtReadEntireFileIndirect:
push es
pushad
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .RDone3
// Reset block offset
xor si, si
// Set the block segment
call ExtSetBlockSegment
// Read the block safely
call ExtSafeReadBlock
// Read the blocks
mov ecx, dword ptr BP_REL(ExtPointersPerBlock)
call ExtReadEntireFileLoop
.RDone3:
popad
pop es
ret
// Reads the direct block data
ExtReadEntireFileDirect:
push es
pushad
// If there is no more blocks to read then abort it
call ExtReadEntireFileDone
jb .RDone2
// Move to block pointer in Inode
add si, EXT_INODE_BLOCK_POINTER_OFFSET
xor ecx, ecx
mov cl, EXT_INODE_BLOCKS
// Set the correct segment
call ExtSetInodeSegment
// Read the blocks
call ExtReadEntireFileLoop
.RDone2:
popad
pop es
ret
ExtExtentsLevelSegmentFunctionTable:
.word ExtSetBlockSegment
.word ExtSetBlock2Segment
.word ExtSetBlock3Segment
.word ExtSetBlock4Segment
.word ExtSetBlock5Segment
.word ExtSetBlock6Segment
ExtExtentsLevelFunctionTable:
.word ExtReadEntireFileExtentsLevel0
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
.word ExtReadEntireFileExtentsLevelX
ExtReadEntireFileExtentsLevelX:
push es
pushad
// Load extent header entries
mov cx, word ptr es:[si + EXT_EXTENT_HEADER_ENTRIES_OFFSET]
.ExtentsLoopX:
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .ExtentsDoneX
// Go to next extent
add si, EXT_EXTENT_SIZE
// Save current state
push es
pushad
// Read the extent block
mov eax, dword ptr es:[si + EXT_EXTENT_INDEX_LEAF_OFFSET]
// Set block segment and read block safely
call ExtSetBlock7Segment
call ExtSafeReadBlock
// Reset SI for calculations
xor si, si
// Calculate function table offset
mov bp, word ptr es:[si + EXT_EXTENT_HEADER_DEPTH_OFFSET]
shl bp, 1
// Call the segment and level functions
call word ptr cs:ExtExtentsLevelSegmentFunctionTable[bp]
call ExtSafeReadBlock
call word ptr cs:ExtExtentsLevelFunctionTable[bp]
// Restore state and continue loop
popad
pop es
loop .ExtentsLoopX
.ExtentsDoneX:
popad
pop es
ret
ExtReadEntireFileExtentsLevel0:
push es
pushad
// Load extent header entries
mov cx, word ptr es:[si + EXT_EXTENT_HEADER_ENTRIES_OFFSET]
.ExtentsLoop0:
// Check if there is no more blocks to read
call ExtReadEntireFileDone
jb .ExtentsDone0
// Go to next extent
add si, EXT_EXTENT_SIZE
// Load extent length
mov bx, word ptr es:[si + EXT_EXTENT_LENGTH_OFFSET]
and ebx, HEX(FFFF)
// Check if extent is sparse
cmp bx, EXT_EXTENT_MAX_LENGTH
jbe .NotSparse
// Adjust sparse extent length
sub bx, EXT_EXTENT_MAX_LENGTH
push es
.SparseLoop:
// Check if the sparse blocks reached the end
test bx, bx
jz .SparseDone
xor eax, eax
call ExtFileReadBlock
// Decrement the block count
dec bx
jmp .SparseLoop
.SparseDone:
pop es
jmp .ExtentsSkip0
.NotSparse:
// Read blocks from extent start
mov eax, dword ptr es:[si + EXT_EXTENT_START_OFFSET]
call ExtFileReadBlocks
.ExtentsSkip0:
loop .ExtentsLoop0
.ExtentsDone0:
popad
pop es
ret
ExtReadEntireFileExtents:
push es
pushad
// Add the extent header offset
add si, EXT_INODE_BLOCK_POINTER_OFFSET
// Load extent header depth
mov ax, word ptr es:[si + EXT_EXTENT_HEADER_DEPTH_OFFSET]
// Check if the file has more than the maximum allowed levels
cmp ax, EXT_EXTENT_MAX_LEVEL
ja FreeLdrPrintFileBig
// If the extent header depth is zero then this is a level 0 extent
test ax, ax
jz .Level0
// Call the recursive function
.LevelX:
call ExtReadEntireFileExtentsLevelX
// Jump to the end of the function
jmp .FEDone
// Level 0 extent
.Level0:
// Call the level 0 extent function
call ExtReadEntireFileExtentsLevel0
// End of the function
.FEDone:
popad
pop es
ret
// Reads the entire file in EXTFS
ExtReadEntireFile:
// Set the correct Inode offset
mov si, word ptr BP_REL(ExtReadInodeIndexOffset)
// Set file size
mov ecx, dword ptr es:[si + EXT_INODE_SIZE_OFFSET]
mov dword ptr BP_REL(ExtFileSize), ecx
// If file size is zero then abort it
test ecx, ecx
jz FreeLdrPrintFileZero
// Set rounded up file size
add ecx, dword ptr BP_REL(ExtBlockSizeInBytes)
dec ecx
mov dword ptr BP_REL(ExtFileSizeState), ecx
// Use extents method if necessary
call ExtInodeDetectExtentsFlag
jz .NoExtents
// Call the extents method
call ExtReadEntireFileExtents
ret
.NoExtents:
// Set Indirect pointer
mov eax, dword ptr es:[si + (EXT_INODE_BLOCK_POINTER_OFFSET + (EXT_INODE_BLOCKS * EXT_POINTER_SIZE))]
mov dword ptr BP_REL(ExtFileIndirectPointer), eax
// Set Double indirect pointer
mov eax, dword ptr es:[si + (EXT_INODE_BLOCK_POINTER_OFFSET + (EXT_INODE_BLOCKS * EXT_POINTER_SIZE) + EXT_POINTER_SIZE)]
mov dword ptr BP_REL(ExtFileIndirectPointerDouble), eax
call ExtReadEntireFileDirect // Read the direct blocks
mov eax, dword ptr BP_REL(ExtFileIndirectPointer) // Load the simple indirect pointer
call ExtReadEntireFileIndirect // Read the simple indirect blocks
call ExtReadEntireFileIndirectDouble // Read the double indirect blocks
ret
LoadFreeLdr:
// Swap ES and DS
call SwapESWithDS
// Show output loading status
push si
mov si, offset msgLoadingFreeLdr
call PutChars
pop si
// Load Inode at directory entry
mov eax, dword ptr es:[si]
call ExtReadInode
mov si, word ptr BP_REL(ExtReadInodeIndexOffset)
// Get Inode type
mov ax, word ptr es:[si]
and ax, EXT_INODE_TYPE_MASK
// If it's not a regular file then abort it
cmp ax, EXT_INODE_TYPE_REGULAR
jnz FreeLdrPrintRegFileError
// Load Freeldr at FREELDR_BASE
mov dword ptr BP_REL(ExtFileAddress), FREELDR_BASE
call ExtReadEntireFile
// Restore the boot drive and partition
mov dl, byte ptr BP_REL(BootDrive)
mov dh, byte ptr BP_REL(BootPartition)
// Transfer execution to the bootloader
ljmp16 FREELDR_BASE / 16, 0
FreeLdrPrintFileBig:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdrBig
jmp DisplayItAndReboot
FreeLdrPrintFileZero:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdrZero
jmp DisplayItAndReboot
FreeLdrPrintFileNotFound:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdr
jmp DisplayItAndReboot
FreeLdrPrintRegFileError:
// Make DS correct, display it and reboot
call SwapESWithDS
mov si, offset msgFreeLdrNotRegularFile // FreeLdr not found message
jmp DisplayItAndReboot
FreeLdrFileName:
.ascii "freeldr.sys"
FreeLdrFileNameEnd:
msgFreeLdrZero:
.ascii "freeldr.sys size is zero", CR, LF, NUL
msgFreeLdrBig:
.ascii "freeldr.sys has many extent levels", CR, LF, NUL
msgFreeLdr:
.ascii "freeldr.sys not found", CR, LF, NUL
msgFreeLdrNotRegularFile:
.ascii "freeldr.sys is not a regular file", CR, LF, NUL
msgLoadingFreeLdr:
.ascii "Loading FreeLoader...", CR, LF, NUL
.org (EXTLDR_BOOTSECTOR_SIZE - 2)
.word HEX(AA55) // BootSector signature
.endcode16
END