xref: /reactos/boot/freeldr/bootsect/btrfs.S (revision 45fd48bd)
1/*
2 * PROJECT:     FreeLoader
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)
6 */
7
8#include <asm.inc>
9#include <freeldr/include/arch/pc/x86common.h>
10
11.code16
12
13CHUNK_MAP_OFFSET = HEX(8200) // max - 256 chunks (chunk is 24 bytes)
14DATA_BUFFER_SEGMENT = HEX(9a0) // here we will store FS structures read from disk
15
16FIRST_SUPERBLOCK_OFFSET = HEX(80) //in sectors
17
18ROOT_TREE_OBJECTID = 1
19EXTENT_TREE_OBJECTID = 2
20CHUNK_TREE_OBJECTID = 3
21DEV_TREE_OBJECTID = 4
22FS_TREE_OBJECTID = 5
23FIRST_CHUNK_TREE_OBJECTID = 256
24
25DIR_ITEM_KEY = 84
26EXTENT_DATA_KEY = 108
27ROOT_ITEM_KEY = 132
28EXTENT_ITEM_KEY = 168
29CHUNK_ITEM_KEY = 228
30
31BTRFS_FT_REG_FILE = 1
32
33KEY_SIZE = 17
34MAX_CHUNK_ITEMS = 255
35
36start:
37    jmp short main
38    nop
39// globals
40ChunkMapSize:
41    .byte 0
42BootDrive:
43    .byte 0
44PartitionStartLBA:
45    .quad HEX(3f) // default value. Setup tool have to overwrite it upon installation
46
47TreeRootAddress:
48    .quad 0
49ChunkRootAddress:
50    .quad 0
51FsRootAddress:
52    .quad 0
53
54NodeSize:
55    .long 0
56LeafSize:
57    .long 0
58StripeSize:
59    .long 0
60SysChunkSize:
61    .long 0
62
63TreeRootLevel:
64    .byte 0
65ChunkRootLevel:
66    .byte 0
67FsRootLevel:
68    .byte 0
69
70main:
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
76    mov bp, sp
77
78    mov byte ptr [BootDrive], dl
79
80// Now check if this computer supports extended reads. This boot sector will not work without it
81CheckInt13hExtensions:
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
87    jne  PrintDiskError
88    test cl, 1                              // si = API subset support bitmap
89    jz   PrintDiskError                     // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
90
91LoadExtraBootCode:
92    // First we have to load our extra boot code into into memory at [0000:7e00h]
93    xor edx, edx
94    mov eax, 1 // read from the second sector - the first was already read
95    mov  cx, 2
96    mov  bx, HEX(7e00) // Read sector to [0000:7e00h]
97    call ReadSectors
98
99    mov ax, DATA_BUFFER_SEGMENT
100    mov es, ax
101
102// reading superblock
103    xor edx, edx
104    mov eax, FIRST_SUPERBLOCK_OFFSET
105    mov cx, 8 // superblock is 0x1000 bytes - 8 sectors
106    xor bx, bx
107    call ReadSectors
108
109    push es // swapping segments for memory operations with superblock
110    push ds // ds must be DATA_BUFFER_SEGMENT
111    pop es
112    pop ds
113
114    mov si, HEX(40)
115    lea di, [btrfsSignature]
116    mov cx, 4 // magic string size (words)
117    repe cmpsw
118    je SignatureOk
119
120    mov si, wrongSignatureError
121    call PrintCustomError
122
123SignatureOk:
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
128    rep movsw
129// read sizes
130    add si, HEX(34) // now pointing to nodesize field
131    lea di, [NodeSize] // read all 4 values
132    mov cx, 8
133    rep movsw
134// read both levels
135    add si, 34
136    movsw // di is on the right place
137
138    push es
139    push ds
140    pop es
141    pop ds
142
143// read sys chunk array
144    mov ax, HEX(32b) // sys_chunk_start
145ReadSysChunk:
146    mov bx, ax
147    add bx, KEY_SIZE
148    push bx // start of the chunk
149    call InsertChunk
150
151    pop si
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
156    sub si, HEX(32b)
157    cmp si, word ptr [SysChunkSize] // the size cannot be more than 0xFFFF here so use word ptr
158    jb ReadSysChunk
159
160    jmp SetTreeRoots
161
162
163// Reads logical sectors from disk
164// INPUT:
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
168// OUTPUT:
169// - 512*CX bytes of memory at ES:[BX]
170// LOCALS:
171// - [bp-2] - LBASectorsRead
172ReadSectors:
173    push bp
174    mov bp, sp
175    sub sp, 2
176    push es // we will spoil es register here
177
178    add eax, dword ptr [PartitionStartLBA]
179    adc edx, dword ptr [PartitionStartLBA+4]
180ReadSectors_loop:
181    pushad                                  // Save logical sector number & sector count
182
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
186
187ReadSectorsSetupDiskAddressPacket:
188    mov  word ptr [bp-2], cx
189    push edx
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
196
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
201
202    add  sp, 16                             // Remove disk address packet from stack
203
204    popad                                   // Restore sector count & logical sector number
205
206    push bx
207    movzx ebx, word ptr [bp-2]
208    add  eax, ebx                            // Increment sector to read
209    adc  edx, 0
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)
213    mov  si, es
214    add  si, bx                              // Setup read buffer for next sector
215    mov  es, si
216    pop  bx
217
218    sub  cx, word ptr [bp-2]
219    jnz  ReadSectors_loop                        // Read next sector
220
221    pop es
222    leave
223    ret
224
225// Displays a disk error message
226// And reboots
227PrintDiskError:
228    mov  si, msgDiskError            // Bad boot disk message
229PrintCustomError:
230    call PutChars                   // Display it
231
232Reboot:
233    lea  si, [msgAnyKey]             // Press any key message
234    call PutChars                   // Display it
235    xor ax,ax
236    int HEX(16)                         // Wait for a keypress
237    int HEX(19)                         // Reboot
238
239PutChars:
240    lodsb
241    or al,al
242    jz short Done
243    call PutCharsCallBios
244    jmp short PutChars
245PutCharsCallBios:
246    mov ah, HEX(0e)
247    mov bx, HEX(07)
248    int HEX(10)
249    ret
250Done:
251    mov al, HEX(0d)
252    call PutCharsCallBios
253    mov al, HEX(0a)
254    call PutCharsCallBios
255    ret
256
257
258msgDiskError:
259    .asciz "Disk error"
260msgAnyKey:
261    .asciz "Press any key to restart"
262
263.org 510
264    .word HEX(aa55)       // BootSector signature
265
266SetTreeRoots:
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
270
271    // finding FS_TREE root
272
273    // put the key on stack
274    xor eax, eax
275    push eax
276    push eax
277    dec sp
278    mov byte ptr [esp], ROOT_ITEM_KEY
279    push eax
280    push 0
281    push FS_TREE_OBJECTID
282
283    mov eax, dword ptr [TreeRootAddress]
284    mov edx, dword ptr [TreeRootAddress+4]
285    mov cl, byte ptr [TreeRootLevel]
286
287    call SearchTree
288    add sp, 17 // setting stack back
289
290    // bx - pointer to ROOT_ITEM
291    mov al, byte ptr es:[bx+238] // moving level
292    mov byte ptr [FsRootLevel], al
293    cld
294
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
299
300    // now we need to find DIR_ITEM_KEY with freeldr.sys hash
301    xor eax, eax
302    push eax
303    push dword ptr [filenameCrc]
304    dec sp
305    mov byte ptr [esp], DIR_ITEM_KEY
306    push eax
307    push 0
308    push 256 // root dir objectid
309
310    mov eax, dword ptr [FsRootAddress]
311    mov edx, dword ptr [FsRootAddress+4]
312    mov cl, byte ptr [FsRootLevel]
313
314    call SearchTree
315    add sp, 17 // setting stack back
316
317    // parsing DIR_ITEM
318    // bx - item addr
319    // cx - item length
320    mov ax, cx
321
322    push ds
323    push es
324    pop ds
325    pop es
326ParseDirItem_loop:
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
331    lea si, [bx+30]
332    lea di, [freeldrFilename]
333    mov cx, 11
334    repe cmpsb
335    je FreeLdrFound
336
337ParseDirItem_loop_2:
338    mov dx, 30
339    add dx, word ptr [bx+27]
340    cmp dx, ax
341    jae FreeLdrNotFound
342    add bx, dx
343    jmp ParseDirItem_loop
344
345FreeLdrNotFound:
346    lea si, [notFoundError]
347    call PrintCustomError
348
349FreeLdrFound:
350    // freeldr objectid is the first qword of DIR_ITEM structure
351    xor eax, eax
352    push eax
353    push eax
354    dec sp
355    mov byte ptr [esp], EXTENT_DATA_KEY
356    push dword ptr [bx+4]
357    push dword ptr [bx]
358
359    push es
360    pop ds
361
362    mov eax, dword ptr [FsRootAddress]
363    mov edx, dword ptr [FsRootAddress+4]
364    mov cl, byte ptr [FsRootLevel]
365
366    call SearchTree
367    add sp, 17
368
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
372    mov cx, ax
373    push cx
374
375    lea di, [bx+21]
376    call ConvertAddress
377    pop cx
378
379    xor bx, bx
380    mov ds, bx
381    mov es, bx
382    mov bx, FREELDR_BASE
383    call ReadSectors
384
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)
387
388    /* Transfer execution to the bootloader */
389    ljmp16 0, FREELDR_BASE
390
391
392// Insert chunk into chunk map (located at DS:[CHUNK_MAP_OFFSET])
393// INPUT:
394// - ES:[AX] chunk key address
395// - ES:[BX] chunk item address
396// TODO: add max items checking
397InsertChunk:
398    push bx
399    push ax
400    push es
401
402    xor ecx, ecx // index
403InsertChunk_loop:
404    std  // numbers are little-engian, going right-to-left
405    mov si, CHUNK_MAP_OFFSET
406    lea si, [esi+ecx*8]
407    lea si, [esi+ecx*8]
408    lea si, [esi+ecx*8]   // shift by 24 bytes
409
410    inc cx
411    cmp cl, byte ptr [ChunkMapSize]
412    ja InsertChunk_insert
413
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)
416
417    cmpsd
418    jb InsertChunk_loop
419    cmpsd
420    jb InsertChunk_loop
421    lea si, [si+4] // set back to the beginning of key
422
423    // numbers are greater - need to insert Here
424    // shifting all to right by one element
425InsertChunk_insert:
426    push si // here we will store new chunk
427    dec cx // because we increased it before comparison
428
429    mov dx, ds
430    mov es, dx
431
432    movzx eax, byte ptr [ChunkMapSize] // number of elements
433
434    mov si, CHUNK_MAP_OFFSET
435    lea si, [esi+eax*8]
436    lea si, [esi+eax*8]
437    lea si, [esi+eax*8-4] // setting to the high dword of the last element
438
439    mov di, si
440    add di, 20 // last byte of the last + 1 element (-4 bytes)
441
442    sub ax, cx
443    mov bx, 6
444    mul bx // 24/4 because of dwords
445    mov cx, ax // number of elements to shift
446    rep movsd
447
448    // finally we can write the element
449    cld
450    pop di // here we will store new chunk
451
452    pop ds // key-to-insert address segment
453    pop si // key-to-insert address
454    add si, 9 // move to 'offset' field
455    movsd
456    movsd // logical address loaded!
457
458    // time for stripes
459    pop si // chunk item address, length is the first field
460    movsd
461    movsd
462
463    add si, 48 // move to offset of the first stripe (we read only first one)
464    movsd
465    movsd
466
467    push es // swapping segments back to original
468    push ds
469    pop es
470    pop ds
471
472    inc byte ptr [ChunkMapSize]
473    ret
474
475
476// Searches a key in a BTRFS tree
477// INPUT:
478// - [bp+4] BTRFS key to find
479// - EDX:EAX logical address of root header
480// - CL leaf node level
481// OUTPUT:
482// - ES:[SI] pointer to found item's key
483// - ES:[BX] pointer to found item
484// - ECX item length
485// LOCALS:
486// - [bp-8] - block number/current node offset
487// - [bp-9] - current level
488SearchTree:
489    push bp
490    mov bp, sp
491    sub sp, 9 // set stack frame
492
493    mov dword ptr [bp-4], edx
494    mov dword ptr [bp-8], eax
495    mov byte ptr [bp-9], cl
496
497SearchTree_readHeader:
498    push ss
499    pop es
500
501    lea di, [bp-8]
502    call ConvertAddress
503    // LBA is in edx:eax now
504    mov bx, DATA_BUFFER_SEGMENT
505    mov es, bx
506    xor bx, bx
507
508    mov cl, byte ptr [bp-9]
509    test cl, cl
510    jz SearchTree_readLeaf
511// read node
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
514
515    // every node begins with header
516    //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf
517    mov cx, -1 // index
518    // finding the key
519SearchTree_findLoop_1:
520    inc cx
521    cmp word ptr es:[HEX(60)], cx
522    je SearchTree_findLoop_1_equal // we are at the end - taking the last element
523
524    lea si, [bp+4] // key to find
525    mov di, HEX(65) // first key in leaf
526    mov ax, 33
527    call CompareKeys
528    jb SearchTree_findLoop_1
529    je SearchTree_findLoop_1_equal
530    sub di, 33 // setting to previous element
531
532    // we are here if key is equal or greater
533    // (si points to the start of the key)
534SearchTree_findLoop_1_equal:
535    push ds
536    push es
537    pop ds
538    pop es
539
540    lea si, [di+17]
541    lea di, [bp-8]
542    movsd
543    movsd
544
545    push es
546    pop ds
547
548    dec byte ptr [bp-9] // decrement level
549    jmp SearchTree_readHeader
550
551SearchTree_readLeaf:
552    mov cx, word ptr [LeafSize]
553    call ReadSectors
554
555    // every node begins with header
556    //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf
557    mov cx, -1 // index
558    // finding the key
559SearchTree_findLoop_2:
560    inc cx
561    cmp word ptr es:[HEX(60)], cx
562    je SearchTree_foundEqual
563
564    lea si, [bp+4] // key to find
565    mov di, HEX(65) // first key in leaf
566    mov ax, 25
567    call CompareKeys
568    jb SearchTree_findLoop_2
569    je SearchTree_foundEqual
570
571    // set pointer to previous element if greater
572    sub di, 25
573
574SearchTree_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
579    mov si, di
580
581    leave
582    ret
583
584SearchTree_notFound:
585    xor ecx, ecx // return ecx=0 if nothing found
586
587    leave
588    ret
589
590
591// Converts logical address into physical LBA addr using chunk map
592// INPUT:
593// - ES:[DI] pointer to logical addr
594// OUTPUT:
595// - EDX:EAX target LBA
596// - sets ZF on failure
597ConvertAddress:
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]
602
603    mov bl, 1 // indicates first try. On second try BL must be set to 0
604ConvertAddress_secondTry:
605    push ds
606    pop es
607    xor ecx, ecx // set index to 0
608ConvertAddress_loop:
609    cmp cl, byte ptr [ChunkMapSize]
610    jae ConvertAddress_checkInclusion // greater chunk is not found in chunk map - checking the last one
611
612    std  // numbers are little-engian, going right-to-left
613    mov si, CHUNK_MAP_OFFSET
614    lea si, [esi+ecx*8]
615    lea si, [esi+ecx*8]
616    lea si, [esi+ecx*8]   // shift by 24 bytes
617
618    lea di, [esp+4]      // set to the second dword the 8-byte number
619    lea si, [si+4]
620    inc cl
621
622    cmpsd
623    jb ConvertAddress_loop
624    cmpsd
625    jb ConvertAddress_loop
626
627ConvertAddress_checkInclusion:
628    dec cl
629    cld
630    // found chunk map item, checking inclusion with length
631    mov si, CHUNK_MAP_OFFSET
632    lea si, [esi+ecx*8]
633    lea si, [esi+ecx*8]
634    lea si, [esi+ecx*8]   // shift by 24 bytes
635
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
642
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
648    test bl, bl
649    jnz ConvertAddress_notFound
650    ret 8
651
652ConvertAddress_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]
662
663    // edx:eax is physical address. Converting to LBA (shifting by 9 bits)
664    shrd eax, edx, 9
665    shr  edx, 9
666    inc bl // clears ZF (bl is 0 or 1 here)
667    ret
668
669ConvertAddress_notFound:
670    // offset is alredy on stack
671    //push dword ptr [esp+4]
672    //push dword ptr [esp]
673    dec sp
674    mov byte ptr [esp], CHUNK_ITEM_KEY
675    data32 push 0
676    push 0
677    push FIRST_CHUNK_TREE_OBJECTID
678
679    mov eax, dword ptr [ChunkRootAddress]
680    mov edx, dword ptr [ChunkRootAddress+4]
681    mov cl, byte ptr [ChunkRootLevel]
682
683    call SearchTree
684    add sp, 9 // setting stack back
685
686    mov ax, si // ES:[SI] - found key pointer
687    call InsertChunk
688
689    mov bl, 0 // indicates second try
690    jmp ConvertAddress_secondTry
691
692
693// Compare key (key1) with key in array identified by base and index (key2)
694// INPUT:
695// - DS:[SI] key1 pointer
696// - ES:[DI] start of the array
697// - AX size of one key in array
698// - CX key index
699// OUTPUT:
700// - DS:[SI] key1 pointer
701// - ES:[DI] key2 pointer
702// - sets flags according to comparison
703CompareKeys:
704    //xchg si, di
705
706    mul cx
707    add di, ax
708    push ds
709    push si
710    push es
711    push di
712
713    // must be replaced for proper flags after cmps
714    push ds
715    push es
716    pop ds
717    pop es
718    xchg si, di
719
720    lea si, [si+4] // key in array
721    lea di, [di+4] // searchable key
722    std
723
724    // comparing objectid
725    cmpsd
726    jne CompareKeys_end
727    cmpsd
728    jne CompareKeys_end
729    // comparing type
730    lea si, [si+12]
731    lea di, [di+12]
732
733    cmpsb
734    jne CompareKeys_end
735    // comparing offset
736    lea si, [si+1+1+4]
737    lea di, [di+1+1+4]
738
739    cmpsd
740    jne CompareKeys_end
741    cmpsd
742CompareKeys_end:
743    cld
744    pop di
745    pop es
746    pop si
747    pop ds
748    ret
749
750
751
752btrfsSignature:
753    .ascii "_BHRfS_M"
754
755//.org 1022
756//    .word HEX(aa55)
757
758
759wrongSignatureError:
760    .asciz "BTRFS read error"
761MaxItemsError:
762    .asciz "Max items error"
763filenameCrc:
764    .long HEX(68cba33d)          // computed hashsum of "freeldr.sys"
765freeldrFilename:
766    .ascii "freeldr.sys"
767notFoundError:
768    .asciz "NFE"
769.org 1534
770    .word HEX(aa55)
771.endcode16
772
773END
774