// EXT2.ASM // EXT2 Boot Sector // Copyright (c) 2002, 2003 Brian Palmer // [bp-0x04] Here we will store the number of sectors per track // [bp-0x08] Here we will store the number of heads // [bp-0x0c] Here we will store the size of the disk as the BIOS reports in CHS form // [bp-0x10] Here we will store the number of LBA sectors read #include .code16 SECTORS_PER_TRACK = HEX(04) NUMBER_OF_HEADS = HEX(08) BIOS_CHS_DRIVE_SIZE = HEX(0C) LBA_SECTORS_READ = HEX(10) EXT2_ROOT_INO = 2 EXT2_S_IFMT = HEX(0f0) EXT2_S_IFREG = HEX(080) //org 7c00h start: jmp short main nop BootDrive: .byte HEX(80) //BootPartition db 0 // Moved to end of boot sector to have a standard format across all boot sectors //SectorsPerTrack db 63 // Moved to [bp-SECTORS_PER_TRACK] //NumberOfHeads dw 16 // Moved to [bp-NUMBER_OF_HEADS] //BiosCHSDriveSize dd (1024 * 1024 * 63) // Moved to [bp-BIOS_CHS_DRIVE_SIZE] //LBASectorsRead dd 0 // Moved to [bp-LBA_SECTORS_READ] Ext2VolumeStartSector: .long 263088 // Start sector of the ext2 volume Ext2BlockSize: .long 2 // Block size in sectors Ext2BlockSizeInBytes: .long 1024 // Block size in bytes Ext2PointersPerBlock: .long 256 // Number of block pointers that can be contained in one block Ext2GroupDescPerBlock: .long 32 // Number of group descriptors per block Ext2FirstDataBlock: .long 1 // First data block (1 for 1024-byte blocks, 0 for bigger sizes) Ext2InodesPerGroup: .long 2048 // Number of inodes per group Ext2InodesPerBlock: .long 8 // Number of inodes per block Ext2ReadEntireFileLoadSegment: .word 0 Ext2InodeIndirectPointer: .long 0 Ext2InodeDoubleIndirectPointer: .long 0 Ext2BlocksLeftToRead: .long 0 main: xor ax,ax // Setup segment registers mov ds,ax // Make DS correct mov es,ax // Make ES correct mov ss,ax // Make SS correct mov bp, HEX(7c00) mov sp, HEX(7b00) // Setup a stack mov si, offset BootDrive cmp byte ptr [si], HEX(0ff) // If they have specified a boot drive then use it jne GetDriveParameters mov [si],dl // Save the boot drive GetDriveParameters: mov ah, 8 mov dl,[si] // Get boot drive in dl int HEX(13) // Request drive parameters from the bios jnc CalcDriveSize // If the call succeeded then calculate the drive size // If we get here then the call to the BIOS failed // so just set CHS equal to the maximum addressable // size mov cx, HEX(0ffff) mov dh,cl CalcDriveSize: // Now that we have the drive geometry // lets calculate the drive size mov bl,ch // Put the low 8-bits of the cylinder count into BL mov bh,cl // Put the high 2-bits in BH shr bh,6 // Shift them into position, now BX contains the cylinder count and cl, HEX(3f) // Mask off cylinder bits from sector count // CL now contains sectors per track and DH contains head count movzx eax,dh // Move the heads into EAX movzx ebx,bx // Move the cylinders into EBX movzx ecx,cl // Move the sectors per track into ECX inc eax // Make it one based because the bios returns it zero based mov [bp-NUMBER_OF_HEADS],eax // Save number of heads mov [bp-SECTORS_PER_TRACK],ecx // Save number of sectors per track inc ebx // Make the cylinder count one based also mul ecx // Multiply heads with the sectors per track, result in edx:eax mul ebx // Multiply the cylinders with (heads * sectors) [stored in edx:eax already] // We now have the total number of sectors as reported // by the bios in eax, so store it in our variable mov [bp-BIOS_CHS_DRIVE_SIZE],eax LoadExtraBootCode: // First we have to load our extra boot code at // sector 1 into memory at [0000:7e00h] //mov eax,01h xor eax,eax inc eax // Read logical sector 1, EAX now = 1 mov cx,1 // Read one sector mov bx, HEX(7e00) // Read sector to [0000:7e00h] call ReadSectors jmp LoadRootDirectory // Reads ext2 group descriptor into [7000:8000] // We read it to this arbitrary location so // it will not cross a 64k boundary // EAX has group descriptor number to read Ext2ReadGroupDesc: shl eax,5 // Group = (Group * sizeof(GROUP_DESCRIPTOR) /* 32 */) xor edx,edx div dword ptr [bp+Ext2GroupDescPerBlock] // Group = (Group / Ext2GroupDescPerBlock) add eax, dword ptr [bp+Ext2FirstDataBlock] // Group = Group + Ext2FirstDataBlock + 1 inc eax // EAX now has the group descriptor block number // EDX now has the group descriptor offset in the block // Adjust the read offset so that the // group descriptor is read to 7000:8000 mov ebx, HEX(78000) sub ebx,edx shr ebx,4 mov es,bx xor bx,bx // Everything is now setup to call Ext2ReadBlock // Instead of using the call instruction we will // just put Ext2ReadBlock right after this routine // Reads ext2 block into ES:[BX] // EAX has logical block number to read Ext2ReadBlock: mov ecx, dword ptr [bp+Ext2BlockSize] mul ecx jmp ReadSectors // Reads ext2 inode into [6000:8000] // We read it to this arbitrary location so // it will not cross a 64k boundary // EAX has inode number to read Ext2ReadInode: dec eax // Inode = Inode - 1 xor edx,edx div dword ptr [bp+Ext2InodesPerGroup] // Inode = (Inode / Ext2InodesPerGroup) mov ebx,eax // EBX now has the inode group number mov eax,edx xor edx,edx div dword ptr [bp+Ext2InodesPerBlock] // Inode = (Inode / Ext2InodesPerBlock) shl edx,7 // FIXME: InodeOffset *= 128 (make the array index a byte offset) // EAX now has the inode offset block number from inode table // EDX now has the inode offset in the block // Save the inode values and put the group // descriptor number in EAX and read it in push edx push eax mov eax,ebx call Ext2ReadGroupDesc // Group descriptor has been read, now // grab the inode table block number from it push HEX(7000) pop es mov di, HEX(8008) pop eax // Restore inode offset block number from stack add eax, es:[di] // Add the inode table start block // Adjust the read offset so that the // inode we want is read to 6000:8000 pop edx // Restore inode offset in the block from stack mov ebx, HEX(68000) sub ebx,edx shr ebx,4 mov es,bx xor bx,bx call Ext2ReadBlock ret // Reads logical sectors into ES:[BX] // EAX has logical sector number to read // CX has number of sectors to read ReadSectors: add eax, dword ptr [bp+Ext2VolumeStartSector] // Add the start of the volume cmp eax, [bp-BIOS_CHS_DRIVE_SIZE] // Check if they are reading a sector outside CHS range jae ReadSectorsLBA // Yes - go to the LBA routine // If at all possible we want to use LBA routines because // They are optimized to read more than 1 sector per read pushad // Save logical sector number & sector count CheckInt13hExtensions: // Now check if this computer supports extended reads mov ah, HEX(41) // AH = 41h mov bx, HEX(55aa) // BX = 55AAh mov dl, byte ptr [bp+BootDrive] // DL = drive (80h-FFh) int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK jc ReadSectorsCHS // CF set on error (extensions not supported) cmp bx, HEX(0aa55) // BX = AA55h if installed jne ReadSectorsCHS test cl,1 // CX = API subset support bitmap jz ReadSectorsCHS // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported popad // Restore sector count & logical sector number ReadSectorsLBA: pushad // Save logical sector number & sector count cmp cx, 64 // Since the LBA calls only support 0x7F sectors at a time we will limit ourselves to 64 jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 65 sectors then just do the read mov cx,64 // Otherwise read only 64 sectors on this loop iteration ReadSectorsSetupDiskAddressPacket: mov [bp-LBA_SECTORS_READ],cx mov word ptr [bp-LBA_SECTORS_READ+2],0 data32 push 0 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 [bp+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 mov ebx, [bp-LBA_SECTORS_READ] add eax,ebx // Increment sector to read shl ebx,5 mov dx,es add dx,bx // Setup read buffer for next sector mov es,dx pop bx sub cx,[bp-LBA_SECTORS_READ] jnz ReadSectorsLBA // Read next sector ret // Reads logical sectors into ES:[BX] // EAX has logical sector number to read // CX has number of sectors to read ReadSectorsCHS: popad // Get logical sector number & sector count off stack ReadSectorsCHSLoop: pushad xor edx,edx mov ecx, [bp-SECTORS_PER_TRACK] div ecx // Divide logical by SectorsPerTrack inc dl // Sectors numbering starts at 1 not 0 mov cl,dl // Sector in CL mov edx,eax shr edx,16 div word ptr [bp-NUMBER_OF_HEADS] // Divide logical by number of heads mov dh,dl // Head in DH mov dl, byte ptr [bp+BootDrive] // Drive number in DL mov ch,al // Cylinder in CX ror ah,2 // Low 8 bits of cylinder in CH, high 2 bits // in CL shifted to bits 6 & 7 or cl,ah // Or with sector number mov ax, HEX(0201) int HEX(13) // DISK - READ SECTORS INTO MEMORY // AL = number of sectors to read, CH = track, CL = sector // DH = head, DL = drive, ES:BX -> buffer to fill // Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read jc PrintDiskError // If the read failed then abort popad inc eax // Increment Sector to Read mov dx,es add dx, HEX(20) // Increment read buffer for next sector mov es,dx loop ReadSectorsCHSLoop // Read next sector ret // Displays a disk error message // And reboots PrintDiskError: mov si,msgDiskError // Bad boot disk message call PutChars // Display it Reboot: mov 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: .ascii "Disk error", NUL // Sorry, need the space... //msgAnyKey db 'Press any key to restart',0 msgAnyKey: .ascii "Press key", NUL // times 509-($-$$) db 0 // Pad to 509 bytes .org 509 BootPartition: .byte 0 .word HEX(0aa55) // BootSector signature // End of bootsector // // Now starts the extra boot code that we will store // at sector 1 on a EXT2 volume LoadRootDirectory: mov eax,EXT2_ROOT_INO // Put the root directory inode number in EAX call Ext2ReadInode // Read in the inode // Point ES:DI to the inode structure at 6000:8000 push HEX(6000) pop es mov di, HEX(8000) push di push es // Save these for later // Get root directory size from inode structure mov eax, es:[di+4] push eax // Now that the inode has been read in load // the root directory file data to 0000:8000 call Ext2ReadEntireFile // Since the root directory was loaded to 0000:8000 // then add 8000h to the root directory's size pop eax mov edx, HEX(8000) // Set EDX to the current offset in the root directory add eax,edx // Initially add 8000h to the size of the root directory SearchRootDirectory: push edx // Save current offset in root directory push eax // Save the size of the root directory // Now we have to convert the current offset // in the root directory to a SEGMENT:OFFSET pair mov eax,edx xor edx,edx mov ecx,16 div ecx // Now AX:DX has segment & offset mov es,ax mov di,dx push di // Save the start of the directory entry add di, 8 // Add the offset to the filename mov si,filename mov cl,11 repe cmpsb // Compare the file names pop di pop eax pop edx jz FoundFile // Nope, didn't find it in this entry, keep looking movzx ecx,word ptr es:[di+4] add edx,ecx // Check to see if we have reached the // end of the root directory cmp edx,eax jb SearchRootDirectory jmp PrintFileNotFound FoundFile: mov eax,es:[di] // Get inode number from directory entry call Ext2ReadInode // Read in the inode // Point ES:DI to the inode structure at 6000:8000 pop es pop di // These were saved earlier mov cx, es:[di] // Get the file mode so we can make sure it's a regular file and ch,EXT2_S_IFMT // Mask off everything but the file type cmp ch,EXT2_S_IFREG // Make sure it's a regular file je LoadFreeLoader jmp PrintRegFileError LoadFreeLoader: mov si,msgLoading // "Loading FreeLoader..." message call PutChars // Display it call Ext2ReadEntireFile // Read freeldr.sys to 0000:8000 mov dl, byte ptr [bp+BootDrive] mov dh, byte ptr [bp+BootPartition] push 0 // push segment (0x0000) mov eax, [HEX(8000) + HEX(0A8)] // load the RVA of the EntryPoint into eax add eax, HEX(8000) // RVA -> VA push ax // push offset retf // Transfer control to FreeLoader // Reads ext2 file data into [0000:8000] // This function assumes that the file's // inode has been read in to 6000:8000 *and* // ES:DI points to 6000:8000 // This will load all the blocks up to // and including the double-indirect pointers. // This should be sufficient because it // allows for ~64MB which is much bigger // than we need for a boot loader. Ext2ReadEntireFile: // Reset the load segment mov word ptr [bp+Ext2ReadEntireFileLoadSegment], HEX(800) // Now we must calculate how // many blocks to read in // We will do this by rounding the // file size up to the next block // size and then dividing by the block size mov eax, dword ptr [bp+Ext2BlockSizeInBytes] // Get the block size in bytes push eax dec eax // Ext2BlockSizeInBytes -= 1 add eax, es:[di+4] // Add the file size xor edx,edx pop ecx // Divide by the block size in bytes div ecx // EAX now contains the number of blocks to load push eax // Make sure the file size isn't zero cmp eax, 0 jnz Ext2ReadEntireFile2 jmp PrintFileSizeError Ext2ReadEntireFile2: // Save the indirect & double indirect pointers mov edx, es:[di+ HEX(58)] // Get indirect pointer mov dword ptr [bp+Ext2InodeIndirectPointer], edx // Save indirect pointer mov edx, es:[di+ HEX(5c)] // Get double indirect pointer mov dword ptr [bp+Ext2InodeDoubleIndirectPointer],edx // Save double indirect pointer // Now copy the direct pointers to 7000:0000 // so that we can call Ext2ReadDirectBlocks push ds // Save DS push es push HEX(7000) pop es pop ds mov si, HEX(8028) xor di,di // DS:SI = 6000:8028 ES:DI = 7000:0000 mov cx,24 // Moving 24 words of data rep movsw pop ds // Restore DS // Now we have all the block pointers in the // right location so read them in pop eax // Restore the total number of blocks in this file xor ecx,ecx // Set the max count of blocks to read to 12 mov cl,12 // which is the number of direct block pointers in the inode call Ext2ReadDirectBlockList // Check to see if we actually have // blocks left to read cmp eax, 0 jz Ext2ReadEntireFileDone // Now we have read all the direct blocks in // the inode. So now we have to read the indirect // block and read all it's direct blocks push eax // Save the total block count mov eax, dword ptr [bp+Ext2InodeIndirectPointer] // Get the indirect block pointer push HEX(7000) pop es xor bx,bx // Set the load address to 7000:0000 call Ext2ReadBlock // Read the block // Now we have all the block pointers from the // indirect block in the right location so read them in pop eax // Restore the total block count mov ecx, dword ptr [bp+Ext2PointersPerBlock] // Get the number of block pointers that one block contains call Ext2ReadDirectBlockList // Check to see if we actually have // blocks left to read cmp eax, 0 jz Ext2ReadEntireFileDone // Now we have read all the direct blocks from // the inode's indirect block pointer. So now // we have to read the double indirect block // and read all it's indirect blocks // (whew, it's a good thing I don't support triple indirect blocks) mov dword ptr [bp+Ext2BlocksLeftToRead],eax // Save the total block count mov eax, dword ptr [bp+Ext2InodeDoubleIndirectPointer] // Get the double indirect block pointer push HEX(7800) pop es push es // Save an extra copy of this value on the stack xor bx,bx // Set the load address to 7000:8000 call Ext2ReadBlock // Read the block pop es // Put 7800h into ES (saved on the stack already) xor di,di Ext2ReadIndirectBlock: mov eax, es:[di] // Get indirect block pointer add di, 4 // Update DI for next array index push es push di push HEX(7000) pop es xor bx,bx // Set the load address to 7000:0000 call Ext2ReadBlock // Read the indirect block // Now we have all the block pointers from the // indirect block in the right location so read them in mov eax, dword ptr [bp+Ext2BlocksLeftToRead] // Restore the total block count mov ecx, dword ptr [bp+Ext2PointersPerBlock] // Get the number of block pointers that one block contains call Ext2ReadDirectBlockList mov dword ptr [bp+Ext2BlocksLeftToRead],eax // Save the total block count pop di pop es // Check to see if we actually have // blocks left to read cmp eax, 0 jnz Ext2ReadIndirectBlock Ext2ReadEntireFileDone: ret // Reads a maximum number of blocks // from an array at 7000:0000 // and updates the total count // ECX contains the max number of blocks to read // EAX contains the number of blocks left to read // On return: // EAX contians the new number of blocks left to read Ext2ReadDirectBlockList: cmp eax,ecx // Compare it to the maximum number of blocks to read ja CallExt2ReadDirectBlocks // If it will take more blocks then just read all of the blocks mov cx,ax // Otherwise adjust the block count accordingly CallExt2ReadDirectBlocks: sub eax,ecx // Subtract the number of blocks being read from the total count push eax // Save the new total count call Ext2ReadDirectBlocks pop eax // Restore the total count ret // Reads a specified number of blocks // from an array at 7000:0000 // CX contains the number of blocks to read Ext2ReadDirectBlocks: push HEX(7000) pop es xor di,di // Set ES:DI = 7000:0000 Ext2ReadDirectBlocksLoop: mov eax,es:[di] // Get direct block pointer from array add di, 4 // Update DI for next array index push cx // Save number of direct blocks left push es // Save array segment push di // Save array offset mov es,[bp+Ext2ReadEntireFileLoadSegment] xor bx,bx // Setup load address for next read call Ext2ReadBlock // Read the block (this updates ES for the next read) mov [bp+Ext2ReadEntireFileLoadSegment],es // Save updated ES pop di // Restore the array offset pop es // Restore the array segment pop cx // Restore the number of blocks left loop Ext2ReadDirectBlocksLoop // At this point all the direct blocks should // be loaded and ES (Ext2ReadEntireFileLoadSegment) // should be ready for the next read. ret // Displays a file not found error message // And reboots PrintFileNotFound: mov si,msgFreeLdr // FreeLdr not found message jmp short DisplayItAndReboot // Displays a file size is 0 error // And reboots PrintFileSizeError: mov si,msgFileSize // Error message jmp short DisplayItAndReboot // Displays a file is not a regular file error // And reboots PrintRegFileError: mov si,msgRegFile // Error message DisplayItAndReboot: call PutChars // Display it jmp Reboot msgFreeLdr: .ascii "freeldr.sys not found", NUL msgFileSize: .ascii "File size 0", NUL msgRegFile: .ascii "freeldr.sys isnt a regular file", NUL filename: .ascii "freeldr.sys" msgLoading: .ascii "Loading...", NUL // times 1022-($-$$) db 0 // Pad to 1022 bytes .org 1022 .word HEX(0aa55) // BootSector signature .endcode16 END