; 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 SECTORS_PER_TRACK equ 0x04 NUMBER_OF_HEADS equ 0x08 BIOS_CHS_DRIVE_SIZE equ 0x0C LBA_SECTORS_READ equ 0x10 EXT2_ROOT_INO equ 2 EXT2_S_IFMT equ 0f0h EXT2_S_IFREG equ 080h org 7c00h segment .text bits 16 start: jmp short main nop BootDrive db 0x80 ;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 dd 263088 ; Start sector of the ext2 volume Ext2BlockSize dd 2 ; Block size in sectors Ext2BlockSizeInBytes dd 1024 ; Block size in bytes Ext2PointersPerBlock dd 256 ; Number of block pointers that can be contained in one block Ext2GroupDescPerBlock dd 32 ; Number of group descriptors per block Ext2FirstDataBlock dd 1 ; First data block (1 for 1024-byte blocks, 0 for bigger sizes) Ext2InodesPerGroup dd 2048 ; Number of inodes per group Ext2InodesPerBlock dd 8 ; Number of inodes per block Ext2ReadEntireFileLoadSegment: dw 0 Ext2InodeIndirectPointer: dd 0 Ext2InodeDoubleIndirectPointer: dd 0 Ext2BlocksLeftToRead: dd 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,7c00h mov sp,7b00h ; Setup a stack cmp BYTE [BYTE bp+BootDrive],BYTE 0xff ; If they have specified a boot drive then use it jne GetDriveParameters mov [BYTE bp+BootDrive],dl ; Save the boot drive GetDriveParameters: mov ah,08h mov dl,[BYTE bp+BootDrive] ; Get boot drive in dl int 13h ; 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,0ffffh 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,3fh ; 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 [BYTE bp-NUMBER_OF_HEADS],eax ; Save number of heads mov [BYTE 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 [BYTE 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,7e00h ; 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 [BYTE bp+Ext2GroupDescPerBlock] ; Group = (Group / Ext2GroupDescPerBlock) add eax,DWORD [BYTE 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,78000h 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 [BYTE 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 [BYTE bp+Ext2InodesPerGroup] ; Inode = (Inode / Ext2InodesPerGroup) mov ebx,eax ; EBX now has the inode group number mov eax,edx xor edx,edx div DWORD [BYTE 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 WORD 7000h pop es mov di,8008h pop eax ; Restore inode offset block number from stack add eax,DWORD [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,68000h 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 [BYTE bp+Ext2VolumeStartSector] ; Add the start of the volume cmp eax,DWORD [BYTE 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,0x41 ; AH = 41h mov bx,0x55aa ; BX = 55AAh mov dl,[BYTE bp+BootDrive] ; DL = drive (80h-FFh) int 13h ; IBM/MS INT 13 Extensions - INSTALLATION CHECK jc ReadSectorsCHS ; CF set on error (extensions not supported) cmp bx,0xaa55 ; 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,byte 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 [BYTE bp-LBA_SECTORS_READ],cx mov WORD [BYTE bp-LBA_SECTORS_READ+2],0 o32 push byte 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 byte 0x10 ; Set size of packet to 10h mov si,sp ; Setup disk address packet on stack mov dl,[BYTE bp+BootDrive] ; Drive number mov ah,42h ; Int 13h, AH = 42h - Extended Read int 13h ; Call BIOS jc PrintDiskError ; If the read failed then abort add sp,byte 0x10 ; Remove disk address packet from stack popad ; Restore sector count & logical sector number push bx mov ebx,DWORD [BYTE 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,[BYTE 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,DWORD [BYTE 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 [BYTE bp-NUMBER_OF_HEADS] ; Divide logical by number of heads mov dh,dl ; Head in DH mov dl,[BYTE 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,0201h int 13h ; 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,byte 20h ; 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 16h ; Wait for a keypress int 19h ; Reboot PutChars: lodsb or al,al jz short Done call PutCharsCallBios jmp short PutChars PutCharsCallBios: mov ah,0eh mov bx,07h int 10h retn Done: mov al,0dh call PutCharsCallBios mov al,0ah call PutCharsCallBios retn msgDiskError db 'Disk error',0 ; Sorry, need the space... ;msgAnyKey db 'Press any key to restart',0 msgAnyKey db 'Press any key',0 times 509-($-$$) db 0 ; Pad to 509 bytes BootPartition db 0 dw 0aa55h ; 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 WORD 6000h pop es mov di,8000h push di push es ; Save these for later ; Get root directory size from inode structure mov eax,DWORD [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,8000h ; 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,byte 8 ; Add the offset to the filename mov si,filename mov cl,11 rep 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 [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 bp+BootDrive] mov dh,[BYTE bp+BootPartition] push 0 ; push segment (0x0000) mov eax, [0x8000 + 0xA8] ; load the RVA of the EntryPoint into eax add eax, 0x8000 ; 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 [BYTE bp+Ext2ReadEntireFileLoadSegment],800h ; 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 [BYTE bp+Ext2BlockSizeInBytes] ; Get the block size in bytes push eax dec eax ; Ext2BlockSizeInBytes -= 1 add eax,DWORD [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,byte 0 jnz Ext2ReadEntireFile2 jmp PrintFileSizeError Ext2ReadEntireFile2: ; Save the indirect & double indirect pointers mov edx,DWORD [es:di+0x58] ; Get indirect pointer mov [BYTE bp+Ext2InodeIndirectPointer],edx ; Save indirect pointer mov edx,DWORD [es:di+0x5c] ; Get double indirect pointer mov [BYTE 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 WORD 7000h pop es pop ds mov si,8028h 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,byte 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 [BYTE bp+Ext2InodeIndirectPointer] ; Get the indirect block pointer push WORD 7000h 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 [BYTE 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,byte 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 [BYTE bp+Ext2BlocksLeftToRead],eax ; Save the total block count mov eax,DWORD [BYTE bp+Ext2InodeDoubleIndirectPointer] ; Get the double indirect block pointer push WORD 7800h 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,DWORD [es:di] ; Get indirect block pointer add di,BYTE 4 ; Update DI for next array index push es push di push WORD 7000h 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 [BYTE bp+Ext2BlocksLeftToRead] ; Restore the total block count mov ecx,DWORD [BYTE bp+Ext2PointersPerBlock] ; Get the number of block pointers that one block contains call Ext2ReadDirectBlockList mov [BYTE 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,byte 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 WORD 7000h pop es xor di,di ; Set ES:DI = 7000:0000 Ext2ReadDirectBlocksLoop: mov eax,[es:di] ; Get direct block pointer from array add di,BYTE 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,[BYTE bp+Ext2ReadEntireFileLoadSegment] xor bx,bx ; Setup load address for next read call Ext2ReadBlock ; Read the block (this updates ES for the next read) mov [BYTE 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 db 'freeldr.sys not found',0 msgFileSize db 'File size is 0',0 msgRegFile db 'freeldr.sys isnt a regular file',0 filename db 'freeldr.sys' msgLoading db 'Loading FreeLoader...',0 times 1022-($-$$) db 0 ; Pad to 1022 bytes dw 0aa55h ; BootSector signature