3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: BTRFS volume boot sector for FreeLoader
5 * COPYRIGHT: Copyright 2018 Victor Perevertkin (victor@perevertkin.ru)
9 #include <freeldr/include/arch/pc/x86common.h>
13 CHUNK_MAP_OFFSET = HEX(8200) // max - 256 chunks (chunk is 24 bytes)
14 DATA_BUFFER_SEGMENT = HEX(9a0) // here we will store FS structures read from disk
16 FIRST_SUPERBLOCK_OFFSET = HEX(80) //in sectors
18 ROOT_TREE_OBJECTID = 1
19 EXTENT_TREE_OBJECTID = 2
20 CHUNK_TREE_OBJECTID = 3
23 FIRST_CHUNK_TREE_OBJECTID = 256
45 .quad HEX(3f) // default value. Setup tool have to overwrite it upon installation
71 xor eax, eax // Setup segment registers
72 mov ds, ax // Make DS correct
73 mov es, ax // Make ES correct
74 mov ss, ax // Make SS correct
75 mov sp, HEX(7c00) // Setup a stack
78 mov byte ptr [BootDrive], dl
80 // Now check if this computer supports extended reads. This boot sector will not work without it
81 CheckInt13hExtensions:
82 mov ah, HEX(41) // AH = 41h
83 mov bx, HEX(55aa) // BX = 55AAh
84 int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK
85 jc PrintDiskError // CF set on error (extensions not supported)
86 cmp bx, HEX(aa55) // BX = AA55h if installed
88 test cl, 1 // si = API subset support bitmap
89 jz PrintDiskError // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
92 // First we have to load our extra boot code into into memory at [0000:7e00h]
94 mov eax, 1 // read from the second sector - the first was already read
96 mov bx, HEX(7e00) // Read sector to [0000:7e00h]
99 mov ax, DATA_BUFFER_SEGMENT
102 // reading superblock
104 mov eax, FIRST_SUPERBLOCK_OFFSET
105 mov cx, 8 // superblock is 0x1000 bytes - 8 sectors
109 push es // swapping segments for memory operations with superblock
110 push ds // ds must be DATA_BUFFER_SEGMENT
115 lea di, [btrfsSignature]
116 mov cx, 4 // magic string size (words)
120 mov si, wrongSignatureError
121 call PrintCustomError
124 // signature is ok - reading superblock data
125 add si, 8 // logical address of the root tree root
126 lea di, [TreeRootAddress]
127 mov cx, 8 // read both root tree root and chunk tree root
130 add si, HEX(34) // now pointing to nodesize field
131 lea di, [NodeSize] // read all 4 values
136 movsw // di is on the right place
143 // read sys chunk array
144 mov ax, HEX(32b) // sys_chunk_start
148 push bx // start of the chunk
152 mov bx, word ptr es:[si+44] // number of stripes
153 shl bx, 5 // one stripe entry is 32 bytes
154 lea si, [si+bx+48] // 48 - size of the chunk
155 mov ax, si // save for next iteration
157 cmp si, word ptr [SysChunkSize] // the size cannot be more than 0xFFFF here so use word ptr
163 // Reads logical sectors from disk
165 // - ES:[BX] address at which the data will be stored
166 // - EDX:EAX logical sector number from which to read
167 // - CX number of sectors to read
169 // - 512*CX bytes of memory at ES:[BX]
171 // - [bp-2] - LBASectorsRead
176 push es // we will spoil es register here
178 add eax, dword ptr [PartitionStartLBA]
179 adc edx, dword ptr [PartitionStartLBA+4]
181 pushad // Save logical sector number & sector count
183 cmp cx, 8 // Maximum sectors per call is 0x7F, but VirtualBox correctly works with only 8
184 jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 9 sectors then just do the read
185 mov cx, 8 // Otherwise read only 8 sectors on this loop iteration
187 ReadSectorsSetupDiskAddressPacket:
188 mov word ptr [bp-2], cx
190 push eax // Put 64-bit logical block address on stack
191 push es // Put transfer segment on stack
192 push bx // Put transfer offset on stack
193 push cx // Set transfer count
194 push 16 // Set size of packet to 10h
195 mov si, sp // Setup disk address packet on stack
197 mov dl, byte ptr [BootDrive] // Drive number
198 mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read
199 int HEX(13) // Call BIOS
200 jc PrintDiskError // If the read failed then abort
202 add sp, 16 // Remove disk address packet from stack
204 popad // Restore sector count & logical sector number
207 movzx ebx, word ptr [bp-2]
208 add eax, ebx // Increment sector to read
210 shl ebx, 5 // Multiplying by 512=2^9 here.
211 // Shifting only by 5, because it goes to segment
212 // (segment will be shifter by another 4 when converted to real addr)
214 add si, bx // Setup read buffer for next sector
218 sub cx, word ptr [bp-2]
219 jnz ReadSectors_loop // Read next sector
225 // Displays a disk error message
228 mov si, msgDiskError // Bad boot disk message
230 call PutChars // Display it
233 lea si, [msgAnyKey] // Press any key message
234 call PutChars // Display it
236 int HEX(16) // Wait for a keypress
237 int HEX(19) // Reboot
243 call PutCharsCallBios
252 call PutCharsCallBios
254 call PutCharsCallBios
261 .asciz "Press any key to restart"
264 .word HEX(aa55) // BootSector signature
267 // converting sizes to sectors. We dont need this sizes in bytes
268 shr dword ptr [NodeSize], 9
269 shr dword ptr [LeafSize], 9 // leafsize is deprecated
271 // finding FS_TREE root
273 // put the key on stack
278 mov byte ptr [esp], ROOT_ITEM_KEY
281 push FS_TREE_OBJECTID
283 mov eax, dword ptr [TreeRootAddress]
284 mov edx, dword ptr [TreeRootAddress+4]
285 mov cl, byte ptr [TreeRootLevel]
288 add sp, 17 // setting stack back
290 // bx - pointer to ROOT_ITEM
291 mov al, byte ptr es:[bx+238] // moving level
292 mov byte ptr [FsRootLevel], al
295 mov eax, dword ptr es:[bx+176]
296 mov dword ptr [FsRootAddress], eax
297 mov eax, dword ptr es:[bx+176+4]
298 mov dword ptr [FsRootAddress+4], eax
300 // now we need to find DIR_ITEM_KEY with freeldr.sys hash
303 push dword ptr [filenameCrc]
305 mov byte ptr [esp], DIR_ITEM_KEY
308 push 256 // root dir objectid
310 mov eax, dword ptr [FsRootAddress]
311 mov edx, dword ptr [FsRootAddress+4]
312 mov cl, byte ptr [FsRootLevel]
315 add sp, 17 // setting stack back
327 cmp byte ptr [bx+29], BTRFS_FT_REG_FILE // checking dir_item type
328 jne ParseDirItem_loop_2
329 cmp word ptr [bx+27], 11 // checking name length
330 jne ParseDirItem_loop_2
332 lea di, [freeldrFilename]
339 add dx, word ptr [bx+27]
343 jmp ParseDirItem_loop
346 lea si, [notFoundError]
347 call PrintCustomError
350 // freeldr objectid is the first qword of DIR_ITEM structure
355 mov byte ptr [esp], EXTENT_DATA_KEY
356 push dword ptr [bx+4]
362 mov eax, dword ptr [FsRootAddress]
363 mov edx, dword ptr [FsRootAddress+4]
364 mov cl, byte ptr [FsRootLevel]
369 // here we have an EXTENT_ITEM with freeldr.sys
370 mov eax, dword ptr es:[bx+29]
371 shr eax, 9 // getting the number of clusters
385 mov dl, byte ptr [BootDrive] // Load boot drive into DL
386 //mov dh, 0 // Load boot partition into DH (not needed, FreeLbr detects it itself)
388 /* Transfer execution to the bootloader */
389 ljmp16 0, FREELDR_BASE
392 // Insert chunk into chunk map (located at DS:[CHUNK_MAP_OFFSET])
394 // - ES:[AX] chunk key address
395 // - ES:[BX] chunk item address
396 // TODO: add max items checking
402 xor ecx, ecx // index
404 std // numbers are little-engian, going right-to-left
405 mov si, CHUNK_MAP_OFFSET
408 lea si, [esi+ecx*8] // shift by 24 bytes
411 cmp cl, byte ptr [ChunkMapSize]
412 ja InsertChunk_insert
414 lea si, [si+4] // set to the high dword of the 8-byte number
415 lea di, [eax+KEY_SIZE-4] // set to high dword of key's offset field (offset=logical addr)
421 lea si, [si+4] // set back to the beginning of key
423 // numbers are greater - need to insert Here
424 // shifting all to right by one element
426 push si // here we will store new chunk
427 dec cx // because we increased it before comparison
432 movzx eax, byte ptr [ChunkMapSize] // number of elements
434 mov si, CHUNK_MAP_OFFSET
437 lea si, [esi+eax*8-4] // setting to the high dword of the last element
440 add di, 20 // last byte of the last + 1 element (-4 bytes)
444 mul bx // 24/4 because of dwords
445 mov cx, ax // number of elements to shift
448 // finally we can write the element
450 pop di // here we will store new chunk
452 pop ds // key-to-insert address segment
453 pop si // key-to-insert address
454 add si, 9 // move to 'offset' field
456 movsd // logical address loaded!
459 pop si // chunk item address, length is the first field
463 add si, 48 // move to offset of the first stripe (we read only first one)
467 push es // swapping segments back to original
472 inc byte ptr [ChunkMapSize]
476 // Searches a key in a BTRFS tree
478 // - [bp+4] BTRFS key to find
479 // - EDX:EAX logical address of root header
480 // - CL leaf node level
482 // - ES:[SI] pointer to found item's key
483 // - ES:[BX] pointer to found item
486 // - [bp-8] - block number/current node offset
487 // - [bp-9] - current level
491 sub sp, 9 // set stack frame
493 mov dword ptr [bp-4], edx
494 mov dword ptr [bp-8], eax
495 mov byte ptr [bp-9], cl
497 SearchTree_readHeader:
503 // LBA is in edx:eax now
504 mov bx, DATA_BUFFER_SEGMENT
508 mov cl, byte ptr [bp-9]
510 jz SearchTree_readLeaf
512 mov cx, word ptr [NodeSize] // word btr because we cannot read more than 65536 bytes yet
513 call ReadSectors // reading node to the memory
515 // every node begins with header
516 //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf
519 SearchTree_findLoop_1:
521 cmp word ptr es:[HEX(60)], cx
522 je SearchTree_findLoop_1_equal // we are at the end - taking the last element
524 lea si, [bp+4] // key to find
525 mov di, HEX(65) // first key in leaf
528 jb SearchTree_findLoop_1
529 je SearchTree_findLoop_1_equal
530 sub di, 33 // setting to previous element
532 // we are here if key is equal or greater
533 // (si points to the start of the key)
534 SearchTree_findLoop_1_equal:
548 dec byte ptr [bp-9] // decrement level
549 jmp SearchTree_readHeader
552 mov cx, word ptr [LeafSize]
555 // every node begins with header
556 //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf
559 SearchTree_findLoop_2:
561 cmp word ptr es:[HEX(60)], cx
562 je SearchTree_foundEqual
564 lea si, [bp+4] // key to find
565 mov di, HEX(65) // first key in leaf
568 jb SearchTree_findLoop_2
569 je SearchTree_foundEqual
571 // set pointer to previous element if greater
574 SearchTree_foundEqual:
575 // found equal or greater key
576 mov bx, word ptr es:[di+17] // data offset relative to end of header
577 mov cx, word ptr es:[di+21] // data size
578 add bx, HEX(65) // end of header
585 xor ecx, ecx // return ecx=0 if nothing found
591 // Converts logical address into physical LBA addr using chunk map
593 // - ES:[DI] pointer to logical addr
595 // - EDX:EAX target LBA
596 // - sets ZF on failure
598 // NOTE: SearchTree will overwrite data buffer area and our logical addr will be erased!
599 // so putting it on stack
600 push dword ptr es:[di+4]
601 push dword ptr es:[di]
603 mov bl, 1 // indicates first try. On second try BL must be set to 0
604 ConvertAddress_secondTry:
607 xor ecx, ecx // set index to 0
609 cmp cl, byte ptr [ChunkMapSize]
610 jae ConvertAddress_checkInclusion // greater chunk is not found in chunk map - checking the last one
612 std // numbers are little-engian, going right-to-left
613 mov si, CHUNK_MAP_OFFSET
616 lea si, [esi+ecx*8] // shift by 24 bytes
618 lea di, [esp+4] // set to the second dword the 8-byte number
623 jb ConvertAddress_loop
625 jb ConvertAddress_loop
627 ConvertAddress_checkInclusion:
630 // found chunk map item, checking inclusion with length
631 mov si, CHUNK_MAP_OFFSET
634 lea si, [esi+ecx*8] // shift by 24 bytes
636 // logical_addr + length
637 mov eax, dword ptr [si] // low dword of address
638 mov edx, dword ptr [si+4] // high dword of address
639 add eax, dword ptr [si+8] // low dword of length
640 adc edx, dword ptr [si+12] // high dword of length
641 // edx:eax is the end of the chunk
643 // (logical_addr + length) - addr_to_find
644 cmp edx, dword ptr [esp+4]
645 ja ConvertAddress_found
646 cmp eax, dword ptr [esp]
647 ja ConvertAddress_found // address is greater than end of the chunk
649 jnz ConvertAddress_notFound
652 ConvertAddress_found:
653 // found chunk. Calculating the address
654 // addr_to_find - logical_addr
655 pop eax // low dword of addr_to_find
656 pop edx // high dword of addr_to_find
657 sub eax, dword ptr [si]
658 sbb edx, dword ptr [si+4]
659 // (addr_to_find - logical_addr) + physical_addr
660 add eax, dword ptr [si+16]
661 adc edx, dword ptr [si+20]
663 // edx:eax is physical address. Converting to LBA (shifting by 9 bits)
666 inc bl // clears ZF (bl is 0 or 1 here)
669 ConvertAddress_notFound:
670 // offset is alredy on stack
671 //push dword ptr [esp+4]
672 //push dword ptr [esp]
674 mov byte ptr [esp], CHUNK_ITEM_KEY
677 push FIRST_CHUNK_TREE_OBJECTID
679 mov eax, dword ptr [ChunkRootAddress]
680 mov edx, dword ptr [ChunkRootAddress+4]
681 mov cl, byte ptr [ChunkRootLevel]
684 add sp, 9 // setting stack back
686 mov ax, si // ES:[SI] - found key pointer
689 mov bl, 0 // indicates second try
690 jmp ConvertAddress_secondTry
693 // Compare key (key1) with key in array identified by base and index (key2)
695 // - DS:[SI] key1 pointer
696 // - ES:[DI] start of the array
697 // - AX size of one key in array
700 // - DS:[SI] key1 pointer
701 // - ES:[DI] key2 pointer
702 // - sets flags according to comparison
713 // must be replaced for proper flags after cmps
720 lea si, [si+4] // key in array
721 lea di, [di+4] // searchable key
724 // comparing objectid
760 .asciz "BTRFS read error"
762 .asciz "Max items error"
764 .long HEX(68cba33d) // computed hashsum of "freeldr.sys"