; Copyright 2009 David Elliott. All rights reserved. ; ; License to be determined. ; ; Partition Boot Loader: boot1fat32 ; ; A boot sector for FAT32 volumes. ; ; This is a work in progress but the eventual plan is to successfully load an NTLDR-style ; flat binary just like Microsoft's bootsectors do. ; ; The FAT32 BPB definition comes from the TianoCore bs32.asm file. (FIXME: Change this, the names suck) ;-------------------------------------------------------------------------- ; ; Various constants. ; kBoot0Segment EQU 0x0000 kBoot0Stack EQU 0xFFF0 ; boot0 stack pointer kBoot0LoadAddr EQU 0x7C00 ; boot0 load address kBoot0RelocAddr EQU 0xE000 ; boot0 relocated address kBootSignature EQU 0xAA55 ; boot sector signature kFileBufferSeg EQU 0x2000 ; Load files at 2000:0h kFileBufferOffs EQU 0x0000 kFatSectorBuffer EQU 0x1000 ; Load FAT sector at 0:1000h ;-------------------------------------------------------------------------- ; ; Format of fdisk partition entry. ; ; The symbol 'part_size' is automatically defined as an `EQU' ; giving the size of the structure. ; struc part .bootid: resb 1 ; bootable or not .head: resb 1 ; starting head, sector, cylinder .sect: resb 1 ; .cyl: resb 1 ; .type: resb 1 ; partition type .endhead resb 1 ; ending head, sector, cylinder .endsect: resb 1 ; .endcyl: resb 1 ; .lba: resd 1 ; starting lba .sectors resd 1 ; size in sectors endstruc ;-------------------------------------------------------------------------- ; ; Format of unofficial LBA48 fdisk partition entry ; ; The symbol 'part_size' is automatically defined as an `EQU' ; giving the size of the structure. ; struc part48 .bootid: resb 1 ; bootable or not (0x81, 0x01) .resv1: resb 1 ; .lbaH resw 1 ; starting lba high word -- bits (47,32] .type: resb 1 ; partition type .resv2: resb 1 ; .sectorsH resw 1 ; size in sectors high word -- bits (47,32] .lba: resd 1 ; starting lba -- bits (31,0] .sectors resd 1 ; size in sectors -- bits (31,0] endstruc ;-------------------------------------------------------------------------- ; ; Format of FAT directory entry ; struc fatDIR .Name: resb 11 ; 8+3 name .Attr: resb 1 ; Attributes .NTRes: resb 1 ; Reserved for use by Windows NT .CrtTimeTenth: resb 1 ; 0-199 tenths of a second of creation time .CrtTime: resw 1 ; Creation time (word) .CrtDate: resw 1 ; Creation date (word) .LstAccDate resw 1 ; Last access date .FstClusHI: resw 1 ; High word of first cluster (0 for FAT 12/16) .WrtTime resw 1 ; Modify time .WrtDate resw 1 ; Modify date .FstClusLO: resw 1 ; Low word of first cluster .FileSize: resd 1 ; 32-bit file size endstruc ;-------------------------------------------------------------------------- ; Runtime data area struc bss .PartitionLBA: resq 1 ; .RootDirOffset: resd 1 ; Starting sector of root directory ; If RootDirOffset < DataOffset then this is FAT12/16 style. ; Otherwise, root directory is stored as a file allocated in the FAT .RootDirSectors: resd 1 ; Number of sectors in root directory .DataOffset: resd 1 ; Starting sector of data (BPB_RsvdSecCnt + BPB_NumFATs*BPB_FATSz16 + RootDirSectors) .CurrentFatSectorLBA resd 1 ; LBA of 1-sector portion of fat current loaded into the FAT buffer. .ExtentStart resd 1 ; Cluster number that starts the current extent .ExtentEnd resd 1 ; Cluster number that ends the current extent ; NOTE: ClusterMask, ClusterEOC, and NibblesPerFatEntry must be adjacent because we use stosd to set them .ClusterMask resd 1 .ClusterEOC resd 1 .NibblesPerFatEntry resd 1 endstruc ;-------------------------------------------------------------------------- ; Format of constant _fat_size_param_table struc fat_size_param_entry .cutoff resd 1 .NibblesPerFatEntry resw 1 endstruc ;-------------------------------------------------------------------------- ; Start of text segment. SEGMENT .text ORG 0x7C00 ; must match kBoot0RelocAddr ;-------------------------------------------------------------------------- ; Boot code is loaded at 0:7C00h. ; BS_jmpBoot: jmp short _real_start times 3-($-$$) nop BS_OEMName db 'DBL' times 8+3-($-$$) db ' ' BPB: ; Start of BPB portion of BS data BPB_BytsPerSec dw 0x0000 ; Sector Size - 16 bits BPB_SecPerClus db 0x00 ; Sector Per Cluster - 8 bits BPB_RsvdSecCnt dw 0x0000 ; Reserved Sectors - 16 bits BPB_NumFATs db 0x00 ; Number of FATs - 8 bits BPB_RootEntCnt dw 0x0000 ; Root Entries - 16 bits BPB_TotSec16 dw 0x0000 ; Number of Sectors - 16 bits BPB_Media db 0x00 ; Media - 8 bits - ignored BPB_FATSz16 dw 0x0000 ; Sectors Per FAT - 16 bits BPB_SecPerTrk dw 0x0000 ; Sectors Per Track - 16 bits - ignored BPB_NumHeads dw 0x0000 ; Heads - 16 bits - ignored BPB_HiddSec dd 0x00000000 ; Hidden Sectors - 32 bits - ignored BPB_TotSec32 dd 0x00000000 ; Large Sectors - 32 bits ;****************************************************************************** ; ;The structure for FAT32 starting at offset 36 of the boot sector. (At this point, ;the BPB/boot sector for FAT12 and FAT16 differs from the BPB/boot sector for FAT32.) ; ;****************************************************************************** BPB_FATSz32 dd 0x00000000 ; Sectors Per FAT for FAT32 - 4 bytes BPB_ExtFlags dw 0x0000 ; Mirror Flag - 2 bytes BPB_FSVer dw 0x0000 ; File System Version - 2 bytes BPB_RootClus dd 0x00000000 ; 1st Cluster Number of Root Dir - 4 bytes BPB_FSInfo dw 0x0000 ; Sector Number of FSINFO - 2 bytes BPB_BkBootSec dw 0x0000 ; Sector Number of Bk BootSector - 2 bytes BPB_Reserved times 12 db 0 ; Reserved Field - 12 bytes BS_DrvNum db 0x00 ; Physical Drive Number - 1 byte BS_Reserved1 db 0x00 ; Reserved Field - 1 byte BS_BootSig db 0x00 ; Extended Boot Signature - 1 byte BS_VolID dd 0x00000000 ; Volume Serial Number - 4 bytes BS_VolLab db "NO NAME " ; Volume Label - 11 bytes BS_FilSysType db "FAT32 " ; File System Type - 8 bytes _real_start: xor eax, eax ; We need the high bits of EAX to be zeroed mov ss, ax ; ss <- 0 mov sp, kBoot0Stack + 4 ; sp <- top of stack mov es, ax mov ds, ax ; Save DL to the BPB_DrvNum in the BPB (for now we just ignore what was in the BPB) mov byte [BS_DrvNum], dl ; Check that the sector size is 512, we don't support anything else. cmp word [BPB_BytsPerSec], 512 jne near print_bpb_error ; Initialize some BSS stuff to 0 mov dword [_bss + bss.CurrentFatSectorLBA], eax ; If DL < 0x80 then there's basically no way that DS:SI would've gotten set, so ignore it. ; optimization: if (dl & 0x80) == 0 goto use_hiddensectors test byte dl, 0x80 jz .use_hiddensectors cmp byte [si + part.bootid], 0x80 je .use_partition_lba cmp byte [si + part.bootid], 0x81 je .use_partition_lba48 .use_hiddensectors: ; Copy the hidden sectors field into EDX mov edx, dword [BPB_HiddSec] jmp short .do_setup_lba .use_partition_lba48: movzx eax, word [si + part48.lbaH] .use_partition_lba: ; Assume EAX = 0 or was just pulled from lbaH if using LBA48 mov edx, dword [si + part.lba] .do_setup_lba: ; Assume EAX = 0 or was set to the LBA high DWORD by use_partition_lba48 ; Assume EDX is the LBA or the LBA low DWORD mov dword [_bss + bss.PartitionLBA + 4], eax mov dword [_bss + bss.PartitionLBA], edx ; Calculate the amount of sectors taken up by the FATs ; This is (BPB_FATSz16!=0)?BPB_FATSz16:BPB_FATSz32 * BPB_NumFATs movzx dword eax, word [BPB_FATSz16] test eax, eax jnz .use_spf16 mov dword eax, dword [BPB_FATSz32] .use_spf16: ; EAX contains 32-bit sectors per fat movzx dword edx, byte [BPB_NumFATs] ; Stick number of fats in EDX mul dword edx ; EDX:EAX = EAX * EDX (ignore output EDX) ; Add in the reserved sectors movzx dword edx, word [BPB_RsvdSecCnt] add eax, edx ; Stash it for later use. mov dword [_bss + bss.RootDirOffset], eax ; Calculate the size of the root directory (only applicable to FAT12/16) ; RootDirSectors = BPB_RootEntCnt * 32 / 512 with division rounded up. ; This reduces to BPB_RootEntCnt / 16 with division rounded up ; Which is (BPB_RootEntCnt + 15) / 16 ; Which is (BPB_RootEntCnt + 15) >> 4 ; NOTE: BPB_RootEntCnt must be <= 65520 or the add 15 will overflow ; But we save two operand size override bytes using dx instead of edx which is nice. movzx dword edx, word [BPB_RootEntCnt] add dx, 15 shr dx, 4 ; Stash RootDirSectors for later use mov dword [_bss + bss.RootDirSectors], edx ; DataOffset = RootDirOffset + RootDirSectors add eax, edx mov dword [_bss + bss.DataOffset], eax ; Calculate number of data sectors ; DataSectors = TotalSectors - DataOffset ; Grab the 16 or 32-bit sector count (note: 16-bit sector count will never happen on FAT32) movzx dword edx, word [BPB_TotSec16] test edx,edx jnz .use_total_sec16 mov edx, dword [BPB_TotSec32] .use_total_sec16: ; Subtract DataOffset from TotalSectors neg eax add eax, edx ; EAX = (EDX - EAX) ; Calculate ClusterCount as DataSectors / BPB_SecPerClus ; Remainder is ignored (i.e. any trailing sectors aren't considered) movzx dword ecx, byte [BPB_SecPerClus] ; Grab the BPB_SecPerClus byte into ECX, zero extended xor edx, edx ; Zero EDX in preparation for divide since it uses EDX:EAX as implicit input div dword ecx ; EAX = EDX:EAX / ECX ; Determine the fat type. ; The algorithm is actually very straightforward when you think about it but even Microsoft's ; description of it complicates things by making the numbers seem as if they were arbitrarily ; chosen. So here's the deal: ; ; One single entry in a FAT table can be a 12-bit value, a 16-bit value, or a 32-bit value. ; ; Each entry refers to the next cluster number with certain numbers being special: ; * Entry numbers 0 and 1 are reserved and used for various purposes. ; * The last 8 numbers are used to indicate the end of chain. That is for FAT12 0xff8 through 0xfff ; indicate end of chain, for FAT16 it's 0xfff8 through 0xffff and for FAT32, which would ; probably more correctly be called FAT28 it's 0x0ffffff8 through 0x0fffffff ; * The ninth last valid number is the bad cluster mark (0xff7, 0xfff7, 0x0ffffff7) ; ; That means there are 11 numbers that can't be used which means the limit is 2 ** n - 11 ; Therefore ClusterCount must be LESS THAN that limit. Not less than or equal, less than. ; Instead of testing for FAT12 then 16 then 32 use the parameter table. ; Even though we have to indirectly access the table it's still less bytes ; to loop than to have a dword cmp for each of the 3 types. ; Start with SI at the beginning of the table mov si, _fat_size_param_table .next_fat_size_param_entry ; Is the sector count less than the cutoff? break out and process this entry cmp dword eax, dword [si + fat_size_param_entry.cutoff] jb .got_fat_size_param_entry ; Move on to the next entry if we haven't exhausted the table add si, fat_size_param_entry_size cmp si, _fat_size_param_table_end jb .next_fat_size_param_entry ; Too many clusters for FAT32. NOTE: Microsoft's spec is somewhat inconsistent on this point. ; In one place they claim that 0x0ffffff7 is a valid next cluster number in FAT32. ; But they also state that the top 4 bits of a cluster number are reserved as are the top ; 8 entries within the 28-bit cluster number just as they are for any other FAT type. ; So it _may_ be the case that 0x0ffffff6 should be used to account for the fact that ; apparently the bad cluster marker is no longer reserved. If someone actually has ; a FAT32 filesystem like this, I'd love to see it. jmp print_bpb_error .got_fat_size_param_entry ; Load the test constant to EAX (i.e 0x.....FF5 lodsd ; EAX = test constant ; SI += 4 ; Overwrite the last byte (so 0xF5 becomes 0xFF which is a suitable mask) mov al, 0xFF mov di, _bss + bss.ClusterMask stosd ; dword [_bss + bss.ClusterMask] = EAX ; DI += 4 ; Overwrite the last byte to 0xF8 which is the EOC marker mov al, 0xF8 stosd ; dword [_bss + bss.ClusterEOC] = EAX ; DI += 4 ; Zero all the high bits of EAX xor eax, eax ; Load the number of nibbles per entry into the BSS lodsw ; AX = [entry + fat_size_param_entry.NibblesPerFatEntry] ; SI += 2 stosd ; dword [_bss + bss.NibblesPerFatEntry] = EAX ; DI += 4 ; Make sure that there are at least 3 reserved sectors because otherwise we'd ; be loading some part of the FAT. cmp word [BPB_RsvdSecCnt], 3 jb print_bpb_error ; Load the third sector containing the remainder of the code since there is too much ; for one sector. mov di, continue_boot mov dword eax, 2 mov bl, 1 call read_part_lba jc print_read_error ; Check that it has the boot signature. cmp word [boot_sig_3], kBootSignature ; Continue on with the code from the third sector. je near continue_boot mov si, errorstr_badsec3 jmp short print_error print_bpb_error: mov si, errorstr_badbpb jmp short print_error print_read_error: mov si, errorstr_read print_error: lodsb mov bx, 1 mov ah, 0x0e int 0x10 test al,al jnz print_error error: mov ah, 0 int 0x16 int 0x18 ;-------------------------------------------------------------------------- ; read_part_lba - Read sectors from a partition using LBA addressing. ; ; Arguments: ; EAX = Starting LBA relative to partition (0 == bootsector) ; BPB_DrvNum (we used BH in GPT bootsector but we don't want to here) ; = drive number (0x80 + unit number) ; BL = number of 512-byte sectors to read (valid from 1-127). ; ES:DI = pointer to where the sectors should be stored. ; ; Returns: ; CF = 0 success ; 1 error ; read_part_lba: pushad _read_cluster_lba_entry: mov bp, sp ; Save the stack pointer ; Add the partition LBA offset to EAX to form EDX:EAX xor edx, edx add eax, dword [_bss + bss.PartitionLBA] adc edx, dword [_bss + bss.PartitionLBA + 4] push dword edx ; [12] High DWORD of LBA QWORD push dword eax ; [ 8] Low DWORD of LBA QWORD push word es ; destination buffer seg/off push word di ; [ 4] %if 0 mov dl, bh ; Put BH into DL for INT13 %else mov dl, byte [BS_DrvNum] %endif xor bh, bh push word bx ; [ 2] Push BL (BH has been zeroed) onto stack as a WORD push word 16 ; [ 0] Size of disk address packet mov si, sp ; DS:SI = SS:SP -> disk address packet mov ah, 0x42 int 0x13 ; INT 13/AH=42h mov sp, bp ; Restore the stack pointer popad ret errorstr_read: db 'Read Error', 0 errorstr_badbpb db 'Inconsistent BPB', 0 errorstr_badsec3 db 'Bad third sector', 0 align 2, db 0 _fat_size_param_table: istruc fat_size_param_entry at fat_size_param_entry.cutoff, dd 0x00000ff5 at fat_size_param_entry.NibblesPerFatEntry, dw (1 << 1) + 1 iend istruc fat_size_param_entry at fat_size_param_entry.cutoff, dd 0x0000fff5 at fat_size_param_entry.NibblesPerFatEntry, dw (2 << 1) + 0 iend istruc fat_size_param_entry at fat_size_param_entry.cutoff, dd 0x0ffffff5 at fat_size_param_entry.NibblesPerFatEntry, dw (4 << 1) + 0 ;db 0 iend _fat_size_param_table_end: times 510-11-($-$$) db 0 ldrfn: db 'DRWNLDR ' times 510-($-$$) db 0 ; Traditional 0x55 0xAA signature boot_sig dw kBootSignature _bss: ;-------------------------------------------------------------------------- ;-------------------------------------------------------------------------- ; FSInfo sector ; NOTE: Not loaded into memory at runtime FSInfo_Sector: ; From fatgen103.doc: FSI_LeadSig dd 0x41615252 FSI_Reserved1 times 480 db 0 FSI_StrucSig dd 0x61417272 FSI_Free_Count dd 0xFFFFFFFF ; Unknown free count FSI_Nxt_Free dd 0x00000002 ; Start free cluster search at first (2) FSI_Reserved2 times 12 db 0 FSI_TrailSig dd 0xAA550000 ;-------------------------------------------------------------------------- ;-------------------------------------------------------------------------- ; Second usable code sector (third sector) ; continue_boot: ; Reload ES w/ kFileBufferSeg push word kFileBufferSeg pop es ; Load the root entry count to DX mov dx, word [BPB_RootEntCnt] ; If there are 0 root entries then it must be FAT32 with a starting cluster cmp dx, 0 je .use_root_cluster ; Ahh, we have root directory entries.. presumably we're FAT12 or FAT16 ; Begin at the beginning of the directory mov eax, dword [_bss + bss.RootDirOffset] mov bx, word [_bss + bss.RootDirSectors] ; HACK: For now just assume there aren't too many root dir sectors cmp bx, 127 jbe .root_dir_ok mov si, errorstr_large_root_dir jmp print_error .root_dir_ok ; Read into the start of the file buffer xor di, di ; ES is already pointing at the file buffer call read_part_lba jc near print_read_error ; DX already has the number of entries call _find_dir_entry ; If we found the right entry, start loading the file. ja _load_found_entry ; Oops, we didn't find the entry mov si, errorstr_noloader jmp print_error .use_root_cluster ; It's FAT32 so we must find the root directory's starting cluster ; INIT Outer loop over clusters of the root directory mov eax, dword [BPB_RootClus] ; BEGIN Outer Loop over clusters of the root directory ; EAX = Cluster number ; .read_next_root_cluster: ; Point DI at start of buffer xor di,di call _read_cluster jc near print_read_error ; DX = number of entries within this cluster to be processed movzx dx, byte [BPB_SecPerClus] shl dx, 4 ; Ax *= 512 / 32 call _find_dir_entry ja _load_found_entry ; Ask for the next cluster number. call next_cluster ; If next cluster is < 0x0ffffff8 then loop jb .read_next_root_cluster ; END Outer loop ; We reached the end of the cluster chain.. this is an error mov si, errorstr_noloader jmp print_error ;-------------------------------------------------------------------------- ; _find_dir_entry ; Arguments ; ES:0 -> Directory entries ; DX = Number of entries to process ; Outputs ; DX = Number of entries left to process. ; Flags based on DX ?? 0. So if DX > 0 (i.e. ja) then an entry ; was found. Otherwise (if DX == 0) it was not found. ; Outputs if found: ; ES:BX -> Directory entry ; ES:DI -> Directory entry + 11 (since all 11 bytes matched if we are here) ; If not found (note: for reference, not guaranteed) ; ES:BX -> Past the end of the directory entries ; ES:DI -> Last processed dir entry + 11 ; Clobbers: ; flags, BX, DI, CX.. fuck.. everything. _find_dir_entry: ; INIT Inner loop over entries within the cluster ; Zero BX to point it at the start of the buffer xor bx, bx ; BEGIN Inner loop over entries within the cluster .check_next_dir_entry: ; DI -> dir entry mov di, bx ; Setup the compare to the 11-character constant. ; DS:SI -> ldrfn ; ES:DI -> dir entry we are processing ; CX = 11 bytes (characters) mov si, ldrfn mov cx, 11 repe cmpsb je .found_dir_entry ; Point BX at the next directory entry add bx, 32 ; Decrement DX by one directory entry sub dx, 1 ; Continue if DX > 0 ja .check_next_dir_entry ; END Inner loop .found_dir_entry: ; If we instead exhausted the loop this is merely a redundant cmp, no need ; to jump over it. cmp dx, 0 ret _load_found_entry: ; ES:BX -> Directory entry ; ES:DI -> Directory entry + 11 (since all 11 bytes matched if we are here) ; Set EAX = file cluster mov ax, word [es:bx + fatDIR.FstClusHI] shl dword eax, 16 mov ax, word [es:bx + fatDIR.FstClusLO] ; BEGIN Outer loop to start building extent .build_next_extent: mov dword [_bss + bss.ExtentStart], eax ; Save the starting cluster number of the extent ; BEGIN Inner loop to continue building extent .found_adjacent_cluster: inc dword eax mov dword [_bss + bss.ExtentEnd], eax ; Set the ending cluster to the next adjacent cluster dec dword eax ; Restore EAX back to the correct cluster number call next_cluster ; Get the next cluster number jnb cluster_read_done cmp dword eax, dword [_bss + bss.ExtentEnd] je .found_adjacent_cluster ; If the next cluster to read is adjacent then loop up ; END Inner loop .new_extent: ; EAX = Next cluster to read (to start a new extent) ; Extent info is filled out in BSS for read_extent ; Read the current extent call _read_extent jc near print_read_error jmp short .build_next_extent ; Loop to start the next extent ; END Outer loop ; Jump here when we need to read the last extent then boot cluster_read_done: call _read_extent jc near print_read_error mov dl, [BS_DrvNum] ; Put the drive number back into DL mov si, BPB ; NTLDR wants SI pointing to the BPB ; Start the booter jmp kFileBufferSeg:0 ;-------------------------------------------------------------------------- ; _read_extent: ; Arguments: ; Extents to read pulled from BSS ; Various disk information pulled from BPB ; ES:0 -> Buffer ; Returns ; CF. If set, read failed. ; If successful (not carry), ES:0 = where to read next extent _read_extent: pushad ; Calculate the maximum number of sectors that can be read. ; Set DX:AX to 64k xor word ax, ax ; Zero AX cbw ; Zero DX inc dx ; DX = 1, so DX:AX = 0001:0000h ; Put bytes per sector into EBX mov word bx, word [BPB_BytsPerSec] ; EBX = BPS div word bx ; AX = 64kB / BPS = # of sectors in one real-mode segment ; Limit AX to 127 sectors as that is the max by the EDD spec. cmp word ax, 127 jbe .sector_count_ok mov word ax, 127 .sector_count_ok: push word ax ; Save the sector count for later use. It will be popped into BX. ; Do the calculations on which sector to start reading at and how many ; sectors need to be read. ; Setup EBX = sectors per cluster, used by these calculations movzx dword ebx, byte [BPB_SecPerClus] ; EBX = BPS ; Figure out the ending sector number (relative to DataOffset) and stash it in ECX mov dword eax, [_bss + bss.ExtentEnd] ; Put the ending cluster number into EAX ; By "ending" cluster we mean that the ending cluster itself ; will not be read. That is, it's like a Python slice. dec dword eax dec dword eax ; Subtract 2 to turn it into a cluster offset in the data area mul dword ebx ; Multiply by SPC to turn it into a sector number within the data area. mov dword ecx, eax ; Stash it into ECX ; Figure out the starting sector number (relative to DataOffset) and stash it in EAX mov dword eax, [_bss + bss.ExtentStart] ; Load the starting cluster to read into EAX dec dword eax dec dword eax ; Subtract 2 to make it into a cluster offset in the data area. mul dword ebx ; Multiply by SPC to turn it into a sector number within the data area. ; Subtract the starting sector from the ending sector to get the count. sub dword ecx, eax ; ECX -= EAX so ECX is now the number of sectors that must be read ; Add DataOffset into EAX add eax, dword [_bss + bss.DataOffset] ; Add in the 32-bit DataOffset to the starting sector ; EAX is now relative to the start of the partition. ; Put max sector count (calculated above) into EBX pop word bx ; Restore the max sector count from above (pushed from AX) movzx dword ebx, word bx ; Zero extend it for easier comparison. ; Now actually read all the requested sectors. .read_next_block_within_extent: ; EAX = Starting sector to be read ; ECX = Number of sectors to read ; EBX = Maximum number of sectors that can be read (where high 16-bits guaranteed zero) cmp ecx, ebx jae .read_max_sectors ; If ECX >= EBX then read BX sectors ; ECX < EBX so set BX (number of sectors to read) = CX mov bx, cx .read_max_sectors: xor di, di call read_part_lba jc .read_extent_fail_return ; We just successfully read (E)BX sectors add dword eax, ebx ; Increment EAX (offset) by EBX sub dword ecx, ebx ; Decrement ECX (count) by EBX push dword eax ; Save EAX while we use MUL ; We just read (E)BX sectors of BPB_BytsPerSec size movzx dword eax, word [BPB_BytsPerSec] mul dword ebx ; EAX = BPB_BytsPerSec * EBX shr dword eax, 4 ; EAX = paragraphs read ; But actually, AX will be enough precision since we shifted right mov dx, es add dx, ax mov es, dx ; ES (via DX) += AX pop dword eax ; Restore EAX to the next sector number test dword ecx, ecx jnz .read_next_block_within_extent ; If ECX isn't 0, go read more sectors clc ; Clear the carry flag, we were successful in reading. .read_extent_fail_return popad ret ;-------------------------------------------------------------------------- ; next_cluster ; ; Arguments: ; EAX = Cluster number that was just read ; Returns: ; EAX = Next cluster number to read ; CF = 0 Returned cluster number is EOC ; = 1 Returned cluster number is _not_ EOC ; Rather than thinking of it in terms of CF, use jb/jnb or jb/jae ; Because the comparison is of EAX ??? EOC mark ; Clobbers: ; ... next_cluster: push dword edx push dword ecx push dword ebx ; Make a copy of EAX to EBX in case we need to do the nibbles calculation mov ebx, eax ; EBX = EAX ; Multiply the cluster number by the number of bytes per fat entry mov edx, dword [_bss + bss.NibblesPerFatEntry] ; EDX = Nibbles per fat entry shr edx, 1 ; EDX = Bytes per fat entry (nibbles / 2) mul edx ; EDX:EAX = EAX * EDX ; Clear CL which will be the odd shift if needed xor cl, cl ; See if we also want to add nibbles test byte [_bss + bss.NibblesPerFatEntry], 1 jz .even_nibbles ; Nibbles per fat entry is odd which means we lost cluster * 1/2 ; when we multiplied cluster * (nibbles / 2) ; So now we need to add back in what we lost. ; See if we need to set up the nibble shift test ebx, 1 jz .even_cluster ; Cluster number was odd so we'll be needing to shift the ; 16-bit value right by 4 bits before masking it off. mov cl, byte 4 .even_cluster: ; Calculate the nibbles as cluster / 2 shr ebx, 1 ; EBX = cluster / 2 ; Add the nibbles back in. Although at this point the real value is EDX:EAX we ; can be reasonably sure if we have to deal with nibbles it's FAT12 so EDX is ; already zero and EAX is low enough we do not risk causing a carry. add eax, ebx .even_nibbles: ; Now take our offset in EDX:EAX and turn it into a sector number and offset movzx dword ebx, word [BPB_BytsPerSec] div dword ebx ; EAX = EDX:EAX / BPB_BytsPerSec ; EDX = EDX:EAX % BPB_BytsPerSec ; Add the reserved sectors to get the partition-relative LBA movzx dword ebx, word [BPB_RsvdSecCnt] add dword eax, ebx cmp dword eax, dword [_bss + bss.CurrentFatSectorLBA] je .got_fat_sec mov dword [_bss + bss.CurrentFatSectorLBA], dword eax ; Mark this sector as being the current one. push es ; Save ES push ds ; Roundabout way to move DS to ES pop es mov bx, 2 ; HACK: Read 2 sectors in case we are FAT12 and spanning sectors. mov di, kFatSectorBuffer call read_part_lba pop es ; Restore ES .got_fat_sec: ; We can't use DX as a base register so mov to bx mov bx, dx mov eax, dword [kFatSectorBuffer + bx] ; CL = 0 unless we had nibbles (FAT12) _and_ it was an odd cluster number ; in which case CL = 4 shr eax, cl ; EAX = EAX >> CL ; Mask off the cluster for the type of FAT. Either 28 (for FAT32), 16, or 12. and eax, dword [_bss + bss.ClusterMask] ; Compare the cluster number we'll return in EAX to the EOC marker for the type of fat. ; This ensures all the flags are set so our callers don't have to do the cmp themselves. cmp dword eax, dword [_bss + bss.ClusterEOC] pop dword ebx ; Restore clobbered registers pop dword ecx pop dword edx ret ;-------------------------------------------------------------------------- ; read_cluster - Read cluster using the cluster number ; ; Arguments: ; EAX = Cluster number to be read (2 == First data cluster) ; ES:DI = pointer to where the sectors should be stored. ; ; Returns: ; CF = 0 success ; 1 error ; _read_cluster: pushad dec eax dec eax ; EAX -= 2 movzx dword ebx, byte [BPB_SecPerClus] ; EBX = BPB_SecPerClus, thus BL = BPB_SecPerClus mul ebx ; EDX:EAX = EAX * EBX add eax, dword [_bss + bss.DataOffset] ; Add in the 32-bit DataOffset ; "tail call" (kinda) jmp _read_cluster_lba_entry ; popad will occur at end of read_part_lba which we jump into ; following its own pushad errorstr_noloader: db 'Failed to find bootloader', 0 errorstr_large_root_dir: db 'Root directory too big', 0 times 512+512+510-($-$$) db 0 ; Traditional 0x55 0xAA signature boot_sig_3: dw kBootSignature