[FREELDR][BTRFS] Implemented BTRFS support in Free Loader. Now it supports case-insen...
[reactos.git] / boot / freeldr / bootsect / btrfs.S
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
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
15
16 FIRST_SUPERBLOCK_OFFSET = HEX(80) //in sectors
17
18 ROOT_TREE_OBJECTID = 1
19 EXTENT_TREE_OBJECTID = 2
20 CHUNK_TREE_OBJECTID = 3
21 DEV_TREE_OBJECTID = 4
22 FS_TREE_OBJECTID = 5
23 FIRST_CHUNK_TREE_OBJECTID = 256
24
25 DIR_ITEM_KEY = 84
26 EXTENT_DATA_KEY = 108
27 ROOT_ITEM_KEY = 132
28 EXTENT_ITEM_KEY = 168
29 CHUNK_ITEM_KEY = 228
30
31 BTRFS_FT_REG_FILE = 1
32
33 KEY_SIZE = 17
34 MAX_CHUNK_ITEMS = 255
35
36 start:
37 jmp short main
38 nop
39 // globals
40 ChunkMapSize:
41 .byte 0
42 BootDrive:
43 .byte 0
44 PartitionStartLBA:
45 .quad HEX(3f) // default value. Setup tool have to overwrite it upon installation
46
47 TreeRootAddress:
48 .quad 0
49 ChunkRootAddress:
50 .quad 0
51 FsRootAddress:
52 .quad 0
53
54 NodeSize:
55 .long 0
56 LeafSize:
57 .long 0
58 StripeSize:
59 .long 0
60 SysChunkSize:
61 .long 0
62
63 TreeRootLevel:
64 .byte 0
65 ChunkRootLevel:
66 .byte 0
67 FsRootLevel:
68 .byte 0
69
70 main:
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
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
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
91 LoadExtraBootCode:
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
123 SignatureOk:
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
145 ReadSysChunk:
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
172 ReadSectors:
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]
180 ReadSectors_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
187 ReadSectorsSetupDiskAddressPacket:
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
227 PrintDiskError:
228 mov si, msgDiskError // Bad boot disk message
229 PrintCustomError:
230 call PutChars // Display it
231
232 Reboot:
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
239 PutChars:
240 lodsb
241 or al,al
242 jz short Done
243 call PutCharsCallBios
244 jmp short PutChars
245 PutCharsCallBios:
246 mov ah, HEX(0e)
247 mov bx, HEX(07)
248 int HEX(10)
249 ret
250 Done:
251 mov al, HEX(0d)
252 call PutCharsCallBios
253 mov al, HEX(0a)
254 call PutCharsCallBios
255 ret
256
257
258 msgDiskError:
259 .asciz "Disk error"
260 msgAnyKey:
261 .asciz "Press any key to restart"
262
263 .org 510
264 .word HEX(aa55) // BootSector signature
265
266 SetTreeRoots:
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
326 ParseDirItem_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
337 ParseDirItem_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
345 FreeLdrNotFound:
346 lea si, [notFoundError]
347 call PrintCustomError
348
349 FreeLdrFound:
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
397 InsertChunk:
398 push bx
399 push ax
400 push es
401
402 xor ecx, ecx // index
403 InsertChunk_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
425 InsertChunk_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
488 SearchTree:
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
497 SearchTree_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
519 SearchTree_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)
534 SearchTree_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
551 SearchTree_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
559 SearchTree_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
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
579 mov si, di
580
581 leave
582 ret
583
584 SearchTree_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
597 ConvertAddress:
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
604 ConvertAddress_secondTry:
605 push ds
606 pop es
607 xor ecx, ecx // set index to 0
608 ConvertAddress_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
627 ConvertAddress_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
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]
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
669 ConvertAddress_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
703 CompareKeys:
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
742 CompareKeys_end:
743 cld
744 pop di
745 pop es
746 pop si
747 pop ds
748 ret
749
750
751
752 btrfsSignature:
753 .ascii "_BHRfS_M"
754
755 //.org 1022
756 // .word HEX(aa55)
757
758
759 wrongSignatureError:
760 .asciz "BTRFS read error"
761 MaxItemsError:
762 .asciz "Max items error"
763 filenameCrc:
764 .long HEX(68cba33d) // computed hashsum of "freeldr.sys"
765 freeldrFilename:
766 .ascii "freeldr.sys"
767 notFoundError:
768 .asciz "NFE"
769 .org 1534
770 .word HEX(aa55)
771 .endcode16
772
773 END