reactos/boot/freeldr/bootsect/btrfs.S

774 lines
19 KiB
ArmAsm
Raw Normal View History

/*
* PROJECT: FreeLoader
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
* PURPOSE: BTRFS volume boot sector for FreeLoader
* COPYRIGHT: Copyright 2018 Victor Perevertkin (victor@perevertkin.ru)
*/
#include <asm.inc>
#include <freeldr/include/arch/pc/x86common.h>
.code16
CHUNK_MAP_OFFSET = HEX(8200) // max - 256 chunks (chunk is 24 bytes)
DATA_BUFFER_SEGMENT = HEX(9a0) // here we will store FS structures read from disk
FIRST_SUPERBLOCK_OFFSET = HEX(80) //in sectors
ROOT_TREE_OBJECTID = 1
EXTENT_TREE_OBJECTID = 2
CHUNK_TREE_OBJECTID = 3
DEV_TREE_OBJECTID = 4
FS_TREE_OBJECTID = 5
FIRST_CHUNK_TREE_OBJECTID = 256
DIR_ITEM_KEY = 84
EXTENT_DATA_KEY = 108
ROOT_ITEM_KEY = 132
EXTENT_ITEM_KEY = 168
CHUNK_ITEM_KEY = 228
BTRFS_FT_REG_FILE = 1
KEY_SIZE = 17
MAX_CHUNK_ITEMS = 255
start:
jmp short main
nop
// globals
ChunkMapSize:
.byte 0
BootDrive:
.byte 0
PartitionStartLBA:
.quad HEX(3f) // default value. Setup tool have to overwrite it upon installation
TreeRootAddress:
.quad 0
ChunkRootAddress:
.quad 0
FsRootAddress:
.quad 0
NodeSize:
.long 0
LeafSize:
.long 0
StripeSize:
.long 0
SysChunkSize:
.long 0
TreeRootLevel:
.byte 0
ChunkRootLevel:
.byte 0
FsRootLevel:
.byte 0
main:
xor eax, eax // Setup segment registers
mov ds, ax // Make DS correct
mov es, ax // Make ES correct
mov ss, ax // Make SS correct
mov sp, HEX(7c00) // Setup a stack
mov bp, sp
mov byte ptr [BootDrive], dl
// Now check if this computer supports extended reads. This boot sector will not work without it
CheckInt13hExtensions:
mov ah, HEX(41) // AH = 41h
mov bx, HEX(55aa) // BX = 55AAh
int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK
jc PrintDiskError // CF set on error (extensions not supported)
cmp bx, HEX(aa55) // BX = AA55h if installed
jne PrintDiskError
test cl, 1 // si = API subset support bitmap
jz PrintDiskError // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
LoadExtraBootCode:
// First we have to load our extra boot code into into memory at [0000:7e00h]
xor edx, edx
mov eax, 1 // read from the second sector - the first was already read
mov cx, 2
mov bx, HEX(7e00) // Read sector to [0000:7e00h]
call ReadSectors
mov ax, DATA_BUFFER_SEGMENT
mov es, ax
// reading superblock
xor edx, edx
mov eax, FIRST_SUPERBLOCK_OFFSET
mov cx, 8 // superblock is 0x1000 bytes - 8 sectors
xor bx, bx
call ReadSectors
push es // swapping segments for memory operations with superblock
push ds // ds must be DATA_BUFFER_SEGMENT
pop es
pop ds
mov si, HEX(40)
lea di, [btrfsSignature]
mov cx, 4 // magic string size (words)
repe cmpsw
je SignatureOk
mov si, wrongSignatureError
call PrintCustomError
SignatureOk:
// signature is ok - reading superblock data
add si, 8 // logical address of the root tree root
lea di, [TreeRootAddress]
mov cx, 8 // read both root tree root and chunk tree root
rep movsw
// read sizes
add si, HEX(34) // now pointing to nodesize field
lea di, [NodeSize] // read all 4 values
mov cx, 8
rep movsw
// read both levels
add si, 34
movsw // di is on the right place
push es
push ds
pop es
pop ds
// read sys chunk array
mov ax, HEX(32b) // sys_chunk_start
ReadSysChunk:
mov bx, ax
add bx, KEY_SIZE
push bx // start of the chunk
call InsertChunk
pop si
mov bx, word ptr es:[si+44] // number of stripes
shl bx, 5 // one stripe entry is 32 bytes
lea si, [si+bx+48] // 48 - size of the chunk
mov ax, si // save for next iteration
sub si, HEX(32b)
cmp si, word ptr [SysChunkSize] // the size cannot be more than 0xFFFF here so use word ptr
jb ReadSysChunk
jmp SetTreeRoots
// Reads logical sectors from disk
// INPUT:
// - ES:[BX] address at which the data will be stored
// - EDX:EAX logical sector number from which to read
// - CX number of sectors to read
// OUTPUT:
// - 512*CX bytes of memory at ES:[BX]
// LOCALS:
// - [bp-2] - LBASectorsRead
ReadSectors:
push bp
mov bp, sp
sub sp, 2
push es // we will spoil es register here
add eax, dword ptr [PartitionStartLBA]
adc edx, dword ptr [PartitionStartLBA+4]
ReadSectors_loop:
pushad // Save logical sector number & sector count
cmp cx, 8 // Maximum sectors per call is 0x7F, but VirtualBox correctly works with only 8
jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 9 sectors then just do the read
mov cx, 8 // Otherwise read only 8 sectors on this loop iteration
ReadSectorsSetupDiskAddressPacket:
mov word ptr [bp-2], cx
push edx
push eax // Put 64-bit logical block address on stack
push es // Put transfer segment on stack
push bx // Put transfer offset on stack
push cx // Set transfer count
push 16 // Set size of packet to 10h
mov si, sp // Setup disk address packet on stack
mov dl, byte ptr [BootDrive] // Drive number
mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read
int HEX(13) // Call BIOS
jc PrintDiskError // If the read failed then abort
add sp, 16 // Remove disk address packet from stack
popad // Restore sector count & logical sector number
push bx
movzx ebx, word ptr [bp-2]
add eax, ebx // Increment sector to read
adc edx, 0
shl ebx, 5 // Multiplying by 512=2^9 here.
// Shifting only by 5, because it goes to segment
// (segment will be shifter by another 4 when converted to real addr)
mov si, es
add si, bx // Setup read buffer for next sector
mov es, si
pop bx
sub cx, word ptr [bp-2]
jnz ReadSectors_loop // Read next sector
pop es
leave
ret
// Displays a disk error message
// And reboots
PrintDiskError:
mov si, msgDiskError // Bad boot disk message
PrintCustomError:
call PutChars // Display it
Reboot:
lea si, [msgAnyKey] // Press any key message
call PutChars // Display it
xor ax,ax
int HEX(16) // Wait for a keypress
int HEX(19) // Reboot
PutChars:
lodsb
or al,al
jz short Done
call PutCharsCallBios
jmp short PutChars
PutCharsCallBios:
mov ah, HEX(0e)
mov bx, HEX(07)
int HEX(10)
ret
Done:
mov al, HEX(0d)
call PutCharsCallBios
mov al, HEX(0a)
call PutCharsCallBios
ret
msgDiskError:
.asciz "Disk error"
msgAnyKey:
.asciz "Press any key to restart"
.org 510
.word HEX(aa55) // BootSector signature
SetTreeRoots:
// converting sizes to sectors. We dont need this sizes in bytes
shr dword ptr [NodeSize], 9
shr dword ptr [LeafSize], 9 // leafsize is deprecated
// finding FS_TREE root
// put the key on stack
xor eax, eax
push eax
push eax
dec sp
mov byte ptr [esp], ROOT_ITEM_KEY
push eax
push 0
push FS_TREE_OBJECTID
mov eax, dword ptr [TreeRootAddress]
mov edx, dword ptr [TreeRootAddress+4]
mov cl, byte ptr [TreeRootLevel]
call SearchTree
add sp, 17 // setting stack back
// bx - pointer to ROOT_ITEM
mov al, byte ptr es:[bx+238] // moving level
mov byte ptr [FsRootLevel], al
cld
mov eax, dword ptr es:[bx+176]
mov dword ptr [FsRootAddress], eax
mov eax, dword ptr es:[bx+176+4]
mov dword ptr [FsRootAddress+4], eax
// now we need to find DIR_ITEM_KEY with freeldr.sys hash
xor eax, eax
push eax
push dword ptr [filenameCrc]
dec sp
mov byte ptr [esp], DIR_ITEM_KEY
push eax
push 0
push 256 // root dir objectid
mov eax, dword ptr [FsRootAddress]
mov edx, dword ptr [FsRootAddress+4]
mov cl, byte ptr [FsRootLevel]
call SearchTree
add sp, 17 // setting stack back
// parsing DIR_ITEM
// bx - item addr
// cx - item length
mov ax, cx
push ds
push es
pop ds
pop es
ParseDirItem_loop:
cmp byte ptr [bx+29], BTRFS_FT_REG_FILE // checking dir_item type
jne ParseDirItem_loop_2
cmp word ptr [bx+27], 11 // checking name length
jne ParseDirItem_loop_2
lea si, [bx+30]
lea di, [freeldrFilename]
mov cx, 11
repe cmpsb
je FreeLdrFound
ParseDirItem_loop_2:
mov dx, 30
add dx, word ptr [bx+27]
cmp dx, ax
jae FreeLdrNotFound
add bx, dx
jmp ParseDirItem_loop
FreeLdrNotFound:
lea si, [notFoundError]
call PrintCustomError
FreeLdrFound:
// freeldr objectid is the first qword of DIR_ITEM structure
xor eax, eax
push eax
push eax
dec sp
mov byte ptr [esp], EXTENT_DATA_KEY
push dword ptr [bx+4]
push dword ptr [bx]
push es
pop ds
mov eax, dword ptr [FsRootAddress]
mov edx, dword ptr [FsRootAddress+4]
mov cl, byte ptr [FsRootLevel]
call SearchTree
add sp, 17
// here we have an EXTENT_ITEM with freeldr.sys
mov eax, dword ptr es:[bx+29]
shr eax, 9 // getting the number of clusters
mov cx, ax
push cx
lea di, [bx+21]
call ConvertAddress
pop cx
xor bx, bx
mov ds, bx
mov es, bx
mov bx, FREELDR_BASE
call ReadSectors
mov dl, byte ptr [BootDrive] // Load boot drive into DL
//mov dh, 0 // Load boot partition into DH (not needed, FreeLbr detects it itself)
/* Transfer execution to the bootloader */
ljmp16 0, FREELDR_BASE
// Insert chunk into chunk map (located at DS:[CHUNK_MAP_OFFSET])
// INPUT:
// - ES:[AX] chunk key address
// - ES:[BX] chunk item address
// TODO: add max items checking
InsertChunk:
push bx
push ax
push es
xor ecx, ecx // index
InsertChunk_loop:
std // numbers are little-engian, going right-to-left
mov si, CHUNK_MAP_OFFSET
lea si, [esi+ecx*8]
lea si, [esi+ecx*8]
lea si, [esi+ecx*8] // shift by 24 bytes
inc cx
cmp cl, byte ptr [ChunkMapSize]
ja InsertChunk_insert
lea si, [si+4] // set to the high dword of the 8-byte number
lea di, [eax+KEY_SIZE-4] // set to high dword of key's offset field (offset=logical addr)
cmpsd
jb InsertChunk_loop
cmpsd
jb InsertChunk_loop
lea si, [si+4] // set back to the beginning of key
// numbers are greater - need to insert Here
// shifting all to right by one element
InsertChunk_insert:
push si // here we will store new chunk
dec cx // because we increased it before comparison
mov dx, ds
mov es, dx
movzx eax, byte ptr [ChunkMapSize] // number of elements
mov si, CHUNK_MAP_OFFSET
lea si, [esi+eax*8]
lea si, [esi+eax*8]
lea si, [esi+eax*8-4] // setting to the high dword of the last element
mov di, si
add di, 20 // last byte of the last + 1 element (-4 bytes)
sub ax, cx
mov bx, 6
mul bx // 24/4 because of dwords
mov cx, ax // number of elements to shift
rep movsd
// finally we can write the element
cld
pop di // here we will store new chunk
pop ds // key-to-insert address segment
pop si // key-to-insert address
add si, 9 // move to 'offset' field
movsd
movsd // logical address loaded!
// time for stripes
pop si // chunk item address, length is the first field
movsd
movsd
add si, 48 // move to offset of the first stripe (we read only first one)
movsd
movsd
push es // swapping segments back to original
push ds
pop es
pop ds
inc byte ptr [ChunkMapSize]
ret
// Searches a key in a BTRFS tree
// INPUT:
// - [bp+4] BTRFS key to find
// - EDX:EAX logical address of root header
// - CL leaf node level
// OUTPUT:
// - ES:[SI] pointer to found item's key
// - ES:[BX] pointer to found item
// - ECX item length
// LOCALS:
// - [bp-8] - block number/current node offset
// - [bp-9] - current level
SearchTree:
push bp
mov bp, sp
sub sp, 9 // set stack frame
mov dword ptr [bp-4], edx
mov dword ptr [bp-8], eax
mov byte ptr [bp-9], cl
SearchTree_readHeader:
push ss
pop es
lea di, [bp-8]
call ConvertAddress
// LBA is in edx:eax now
mov bx, DATA_BUFFER_SEGMENT
mov es, bx
xor bx, bx
mov cl, byte ptr [bp-9]
test cl, cl
jz SearchTree_readLeaf
// read node
mov cx, word ptr [NodeSize] // word btr because we cannot read more than 65536 bytes yet
call ReadSectors // reading node to the memory
// every node begins with header
//mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf
mov cx, -1 // index
// finding the key
SearchTree_findLoop_1:
inc cx
cmp word ptr es:[HEX(60)], cx
je SearchTree_findLoop_1_equal // we are at the end - taking the last element
lea si, [bp+4] // key to find
mov di, HEX(65) // first key in leaf
mov ax, 33
call CompareKeys
jb SearchTree_findLoop_1
je SearchTree_findLoop_1_equal
sub di, 33 // setting to previous element
// we are here if key is equal or greater
// (si points to the start of the key)
SearchTree_findLoop_1_equal:
push ds
push es
pop ds
pop es
lea si, [di+17]
lea di, [bp-8]
movsd
movsd
push es
pop ds
dec byte ptr [bp-9] // decrement level
jmp SearchTree_readHeader
SearchTree_readLeaf:
mov cx, word ptr [LeafSize]
call ReadSectors
// every node begins with header
//mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf
mov cx, -1 // index
// finding the key
SearchTree_findLoop_2:
inc cx
cmp word ptr es:[HEX(60)], cx
je SearchTree_foundEqual
lea si, [bp+4] // key to find
mov di, HEX(65) // first key in leaf
mov ax, 25
call CompareKeys
jb SearchTree_findLoop_2
je SearchTree_foundEqual
// set pointer to previous element if greater
sub di, 25
SearchTree_foundEqual:
// found equal or greater key
mov bx, word ptr es:[di+17] // data offset relative to end of header
mov cx, word ptr es:[di+21] // data size
add bx, HEX(65) // end of header
mov si, di
leave
ret
SearchTree_notFound:
xor ecx, ecx // return ecx=0 if nothing found
leave
ret
// Converts logical address into physical LBA addr using chunk map
// INPUT:
// - ES:[DI] pointer to logical addr
// OUTPUT:
// - EDX:EAX target LBA
// - sets ZF on failure
ConvertAddress:
// NOTE: SearchTree will overwrite data buffer area and our logical addr will be erased!
// so putting it on stack
push dword ptr es:[di+4]
push dword ptr es:[di]
mov bl, 1 // indicates first try. On second try BL must be set to 0
ConvertAddress_secondTry:
push ds
pop es
xor ecx, ecx // set index to 0
ConvertAddress_loop:
cmp cl, byte ptr [ChunkMapSize]
jae ConvertAddress_checkInclusion // greater chunk is not found in chunk map - checking the last one
std // numbers are little-engian, going right-to-left
mov si, CHUNK_MAP_OFFSET
lea si, [esi+ecx*8]
lea si, [esi+ecx*8]
lea si, [esi+ecx*8] // shift by 24 bytes
lea di, [esp+4] // set to the second dword the 8-byte number
lea si, [si+4]
inc cl
cmpsd
jb ConvertAddress_loop
cmpsd
jb ConvertAddress_loop
ConvertAddress_checkInclusion:
dec cl
cld
// found chunk map item, checking inclusion with length
mov si, CHUNK_MAP_OFFSET
lea si, [esi+ecx*8]
lea si, [esi+ecx*8]
lea si, [esi+ecx*8] // shift by 24 bytes
// logical_addr + length
mov eax, dword ptr [si] // low dword of address
mov edx, dword ptr [si+4] // high dword of address
add eax, dword ptr [si+8] // low dword of length
adc edx, dword ptr [si+12] // high dword of length
// edx:eax is the end of the chunk
// (logical_addr + length) - addr_to_find
cmp edx, dword ptr [esp+4]
ja ConvertAddress_found
cmp eax, dword ptr [esp]
ja ConvertAddress_found // address is greater than end of the chunk
test bl, bl
jnz ConvertAddress_notFound
ret 8
ConvertAddress_found:
// found chunk. Calculating the address
// addr_to_find - logical_addr
pop eax // low dword of addr_to_find
pop edx // high dword of addr_to_find
sub eax, dword ptr [si]
sbb edx, dword ptr [si+4]
// (addr_to_find - logical_addr) + physical_addr
add eax, dword ptr [si+16]
adc edx, dword ptr [si+20]
// edx:eax is physical address. Converting to LBA (shifting by 9 bits)
shrd eax, edx, 9
shr edx, 9
inc bl // clears ZF (bl is 0 or 1 here)
ret
ConvertAddress_notFound:
// offset is alredy on stack
//push dword ptr [esp+4]
//push dword ptr [esp]
dec sp
mov byte ptr [esp], CHUNK_ITEM_KEY
data32 push 0
push 0
push FIRST_CHUNK_TREE_OBJECTID
mov eax, dword ptr [ChunkRootAddress]
mov edx, dword ptr [ChunkRootAddress+4]
mov cl, byte ptr [ChunkRootLevel]
call SearchTree
add sp, 9 // setting stack back
mov ax, si // ES:[SI] - found key pointer
call InsertChunk
mov bl, 0 // indicates second try
jmp ConvertAddress_secondTry
// Compare key (key1) with key in array identified by base and index (key2)
// INPUT:
// - DS:[SI] key1 pointer
// - ES:[DI] start of the array
// - AX size of one key in array
// - CX key index
// OUTPUT:
// - DS:[SI] key1 pointer
// - ES:[DI] key2 pointer
// - sets flags according to comparison
CompareKeys:
//xchg si, di
mul cx
add di, ax
push ds
push si
push es
push di
// must be replaced for proper flags after cmps
push ds
push es
pop ds
pop es
xchg si, di
lea si, [si+4] // key in array
lea di, [di+4] // searchable key
std
// comparing objectid
cmpsd
jne CompareKeys_end
cmpsd
jne CompareKeys_end
// comparing type
lea si, [si+12]
lea di, [di+12]
cmpsb
jne CompareKeys_end
// comparing offset
lea si, [si+1+1+4]
lea di, [di+1+1+4]
cmpsd
jne CompareKeys_end
cmpsd
CompareKeys_end:
cld
pop di
pop es
pop si
pop ds
ret
btrfsSignature:
.ascii "_BHRfS_M"
//.org 1022
// .word HEX(aa55)
wrongSignatureError:
.asciz "BTRFS read error"
MaxItemsError:
.asciz "Max items error"
filenameCrc:
.long HEX(68cba33d) // computed hashsum of "freeldr.sys"
freeldrFilename:
.ascii "freeldr.sys"
notFoundError:
.asciz "NFE"
.org 1534
.word HEX(aa55)
.endcode16
END