3 // Copyright (c) 2002, 2003 Brian Palmer
5 // [bp-0x04] Here we will store the number of sectors per track
6 // [bp-0x08] Here we will store the number of heads
7 // [bp-0x0c] Here we will store the size of the disk as the BIOS reports in CHS form
8 // [bp-0x10] Here we will store the number of LBA sectors read
13 SECTORS_PER_TRACK = HEX(04)
14 NUMBER_OF_HEADS = HEX(08)
15 BIOS_CHS_DRIVE_SIZE = HEX(0C)
16 LBA_SECTORS_READ = HEX(10)
20 EXT2_S_IFMT = HEX(0f0)
21 EXT2_S_IFREG = HEX(080)
33 //BootPartition db 0 // Moved to end of boot sector to have a standard format across all boot sectors
34 //SectorsPerTrack db 63 // Moved to [bp-SECTORS_PER_TRACK]
35 //NumberOfHeads dw 16 // Moved to [bp-NUMBER_OF_HEADS]
36 //BiosCHSDriveSize dd (1024 * 1024 * 63) // Moved to [bp-BIOS_CHS_DRIVE_SIZE]
37 //LBASectorsRead dd 0 // Moved to [bp-LBA_SECTORS_READ]
39 Ext2VolumeStartSector:
40 .long 263088 // Start sector of the ext2 volume
42 .long 2 // Block size in sectors
44 .long 1024 // Block size in bytes
46 .long 256 // Number of block pointers that can be contained in one block
47 Ext2GroupDescPerBlock:
48 .long 32 // Number of group descriptors per block
50 .long 1 // First data block (1 for 1024-byte blocks, 0 for bigger sizes)
52 .long 2048 // Number of inodes per group
54 .long 8 // Number of inodes per block
56 Ext2ReadEntireFileLoadSegment:
58 Ext2InodeIndirectPointer:
60 Ext2InodeDoubleIndirectPointer:
66 xor ax,ax // Setup segment registers
67 mov ds,ax // Make DS correct
68 mov es,ax // Make ES correct
69 mov ss,ax // Make SS correct
71 mov sp, HEX(7b00) // Setup a stack
73 mov si, offset BootDrive
74 cmp byte ptr [si], HEX(0ff) // If they have specified a boot drive then use it
75 jne GetDriveParameters
77 mov [si],dl // Save the boot drive
82 mov dl,[si] // Get boot drive in dl
83 int HEX(13) // Request drive parameters from the bios
84 jnc CalcDriveSize // If the call succeeded then calculate the drive size
86 // If we get here then the call to the BIOS failed
87 // so just set CHS equal to the maximum addressable
93 // Now that we have the drive geometry
94 // lets calculate the drive size
95 mov bl,ch // Put the low 8-bits of the cylinder count into BL
96 mov bh,cl // Put the high 2-bits in BH
97 shr bh,6 // Shift them into position, now BX contains the cylinder count
98 and cl, HEX(3f) // Mask off cylinder bits from sector count
99 // CL now contains sectors per track and DH contains head count
100 movzx eax,dh // Move the heads into EAX
101 movzx ebx,bx // Move the cylinders into EBX
102 movzx ecx,cl // Move the sectors per track into ECX
103 inc eax // Make it one based because the bios returns it zero based
104 mov [bp-NUMBER_OF_HEADS],eax // Save number of heads
105 mov [bp-SECTORS_PER_TRACK],ecx // Save number of sectors per track
106 inc ebx // Make the cylinder count one based also
107 mul ecx // Multiply heads with the sectors per track, result in edx:eax
108 mul ebx // Multiply the cylinders with (heads * sectors) [stored in edx:eax already]
110 // We now have the total number of sectors as reported
111 // by the bios in eax, so store it in our variable
112 mov [bp-BIOS_CHS_DRIVE_SIZE],eax
116 // First we have to load our extra boot code at
117 // sector 1 into memory at [0000:7e00h]
120 inc eax // Read logical sector 1, EAX now = 1
121 mov cx,1 // Read one sector
122 mov bx, HEX(7e00) // Read sector to [0000:7e00h]
125 jmp LoadRootDirectory
129 // Reads ext2 group descriptor into [7000:8000]
130 // We read it to this arbitrary location so
131 // it will not cross a 64k boundary
132 // EAX has group descriptor number to read
134 shl eax,5 // Group = (Group * sizeof(GROUP_DESCRIPTOR) /* 32 */)
136 div dword ptr [bp+Ext2GroupDescPerBlock] // Group = (Group / Ext2GroupDescPerBlock)
137 add eax, dword ptr [bp+Ext2FirstDataBlock] // Group = Group + Ext2FirstDataBlock + 1
138 inc eax // EAX now has the group descriptor block number
139 // EDX now has the group descriptor offset in the block
141 // Adjust the read offset so that the
142 // group descriptor is read to 7000:8000
150 // Everything is now setup to call Ext2ReadBlock
151 // Instead of using the call instruction we will
152 // just put Ext2ReadBlock right after this routine
154 // Reads ext2 block into ES:[BX]
155 // EAX has logical block number to read
157 mov ecx, dword ptr [bp+Ext2BlockSize]
161 // Reads ext2 inode into [6000:8000]
162 // We read it to this arbitrary location so
163 // it will not cross a 64k boundary
164 // EAX has inode number to read
166 dec eax // Inode = Inode - 1
168 div dword ptr [bp+Ext2InodesPerGroup] // Inode = (Inode / Ext2InodesPerGroup)
169 mov ebx,eax // EBX now has the inode group number
172 div dword ptr [bp+Ext2InodesPerBlock] // Inode = (Inode / Ext2InodesPerBlock)
173 shl edx,7 // FIXME: InodeOffset *= 128 (make the array index a byte offset)
174 // EAX now has the inode offset block number from inode table
175 // EDX now has the inode offset in the block
177 // Save the inode values and put the group
178 // descriptor number in EAX and read it in
182 call Ext2ReadGroupDesc
184 // Group descriptor has been read, now
185 // grab the inode table block number from it
189 pop eax // Restore inode offset block number from stack
190 add eax, es:[di] // Add the inode table start block
192 // Adjust the read offset so that the
193 // inode we want is read to 6000:8000
194 pop edx // Restore inode offset in the block from stack
205 // Reads logical sectors into ES:[BX]
206 // EAX has logical sector number to read
207 // CX has number of sectors to read
209 add eax, dword ptr [bp+Ext2VolumeStartSector] // Add the start of the volume
210 cmp eax, [bp-BIOS_CHS_DRIVE_SIZE] // Check if they are reading a sector outside CHS range
211 jae ReadSectorsLBA // Yes - go to the LBA routine
212 // If at all possible we want to use LBA routines because
213 // They are optimized to read more than 1 sector per read
215 pushad // Save logical sector number & sector count
217 CheckInt13hExtensions: // Now check if this computer supports extended reads
218 mov ah, HEX(41) // AH = 41h
219 mov bx, HEX(55aa) // BX = 55AAh
220 mov dl, byte ptr [bp+BootDrive] // DL = drive (80h-FFh)
221 int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK
222 jc ReadSectorsCHS // CF set on error (extensions not supported)
223 cmp bx, HEX(0aa55) // BX = AA55h if installed
225 test cl,1 // CX = API subset support bitmap
226 jz ReadSectorsCHS // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
228 popad // Restore sector count & logical sector number
231 pushad // Save logical sector number & sector count
233 cmp cx, 64 // Since the LBA calls only support 0x7F sectors at a time we will limit ourselves to 64
234 jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 65 sectors then just do the read
235 mov cx,64 // Otherwise read only 64 sectors on this loop iteration
237 ReadSectorsSetupDiskAddressPacket:
238 mov [bp-LBA_SECTORS_READ],cx
239 mov word ptr [bp-LBA_SECTORS_READ+2],0
241 push eax // Put 64-bit logical block address on stack
242 push es // Put transfer segment on stack
243 push bx // Put transfer offset on stack
244 push cx // Set transfer count
245 push 16 // Set size of packet to 10h
246 mov si,sp // Setup disk address packet on stack
249 mov dl, byte ptr [bp+BootDrive] // Drive number
250 mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read
251 int HEX(13) // Call BIOS
252 jc PrintDiskError // If the read failed then abort
254 add sp, 16 // Remove disk address packet from stack
256 popad // Restore sector count & logical sector number
259 mov ebx, [bp-LBA_SECTORS_READ]
260 add eax,ebx // Increment sector to read
263 add dx,bx // Setup read buffer for next sector
267 sub cx,[bp-LBA_SECTORS_READ]
268 jnz ReadSectorsLBA // Read next sector
273 // Reads logical sectors into ES:[BX]
274 // EAX has logical sector number to read
275 // CX has number of sectors to read
277 popad // Get logical sector number & sector count off stack
282 mov ecx, [bp-SECTORS_PER_TRACK]
283 div ecx // Divide logical by SectorsPerTrack
284 inc dl // Sectors numbering starts at 1 not 0
285 mov cl,dl // Sector in CL
288 div word ptr [bp-NUMBER_OF_HEADS] // Divide logical by number of heads
289 mov dh,dl // Head in DH
290 mov dl, byte ptr [bp+BootDrive] // Drive number in DL
291 mov ch,al // Cylinder in CX
292 ror ah,2 // Low 8 bits of cylinder in CH, high 2 bits
293 // in CL shifted to bits 6 & 7
294 or cl,ah // Or with sector number
296 int HEX(13) // DISK - READ SECTORS INTO MEMORY
297 // AL = number of sectors to read, CH = track, CL = sector
298 // DH = head, DL = drive, ES:BX -> buffer to fill
299 // Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read
301 jc PrintDiskError // If the read failed then abort
305 inc eax // Increment Sector to Read
308 add dx, HEX(20) // Increment read buffer for next sector
311 loop ReadSectorsCHSLoop // Read next sector
318 // Displays a disk error message
321 mov si,msgDiskError // Bad boot disk message
322 call PutChars // Display it
325 mov si,msgAnyKey // Press any key message
326 call PutChars // Display it
328 int HEX(16) // Wait for a keypress
329 int HEX(19) // Reboot
335 call PutCharsCallBios
344 call PutCharsCallBios
346 call PutCharsCallBios
352 .ascii "Disk error", NUL
353 // Sorry, need the space...
354 //msgAnyKey db 'Press any key to restart',0
356 .ascii "Press key", NUL
358 // times 509-($-$$) db 0 // Pad to 509 bytes
364 .word HEX(0aa55) // BootSector signature
369 // Now starts the extra boot code that we will store
370 // at sector 1 on a EXT2 volume
376 mov eax,EXT2_ROOT_INO // Put the root directory inode number in EAX
377 call Ext2ReadInode // Read in the inode
379 // Point ES:DI to the inode structure at 6000:8000
384 push es // Save these for later
386 // Get root directory size from inode structure
390 // Now that the inode has been read in load
391 // the root directory file data to 0000:8000
392 call Ext2ReadEntireFile
394 // Since the root directory was loaded to 0000:8000
395 // then add 8000h to the root directory's size
397 mov edx, HEX(8000) // Set EDX to the current offset in the root directory
398 add eax,edx // Initially add 8000h to the size of the root directory
401 push edx // Save current offset in root directory
402 push eax // Save the size of the root directory
404 // Now we have to convert the current offset
405 // in the root directory to a SEGMENT:OFFSET pair
409 div ecx // Now AX:DX has segment & offset
412 push di // Save the start of the directory entry
413 add di, 8 // Add the offset to the filename
416 repe cmpsb // Compare the file names
422 // Nope, didn't find it in this entry, keep looking
423 movzx ecx,word ptr es:[di+4]
426 // Check to see if we have reached the
427 // end of the root directory
429 jb SearchRootDirectory
430 jmp PrintFileNotFound
433 mov eax,es:[di] // Get inode number from directory entry
434 call Ext2ReadInode // Read in the inode
436 // Point ES:DI to the inode structure at 6000:8000
438 pop di // These were saved earlier
440 mov cx, es:[di] // Get the file mode so we can make sure it's a regular file
441 and ch,EXT2_S_IFMT // Mask off everything but the file type
442 cmp ch,EXT2_S_IFREG // Make sure it's a regular file
444 jmp PrintRegFileError
447 mov si,msgLoading // "Loading FreeLoader..." message
448 call PutChars // Display it
450 call Ext2ReadEntireFile // Read freeldr.sys to 0000:8000
452 mov dl, byte ptr [bp+BootDrive]
453 mov dh, byte ptr [bp+BootPartition]
454 push 0 // push segment (0x0000)
455 mov eax, [HEX(8000) + HEX(0A8)] // load the RVA of the EntryPoint into eax
456 add eax, HEX(8000) // RVA -> VA
457 push ax // push offset
458 retf // Transfer control to FreeLoader
464 // Reads ext2 file data into [0000:8000]
465 // This function assumes that the file's
466 // inode has been read in to 6000:8000 *and*
467 // ES:DI points to 6000:8000
468 // This will load all the blocks up to
469 // and including the double-indirect pointers.
470 // This should be sufficient because it
471 // allows for ~64MB which is much bigger
472 // than we need for a boot loader.
475 // Reset the load segment
476 mov word ptr [bp+Ext2ReadEntireFileLoadSegment], HEX(800)
478 // Now we must calculate how
479 // many blocks to read in
480 // We will do this by rounding the
481 // file size up to the next block
482 // size and then dividing by the block size
483 mov eax, dword ptr [bp+Ext2BlockSizeInBytes] // Get the block size in bytes
485 dec eax // Ext2BlockSizeInBytes -= 1
486 add eax, es:[di+4] // Add the file size
488 pop ecx // Divide by the block size in bytes
489 div ecx // EAX now contains the number of blocks to load
492 // Make sure the file size isn't zero
494 jnz Ext2ReadEntireFile2
495 jmp PrintFileSizeError
498 // Save the indirect & double indirect pointers
499 mov edx, es:[di+ HEX(58)] // Get indirect pointer
500 mov dword ptr [bp+Ext2InodeIndirectPointer], edx // Save indirect pointer
501 mov edx, es:[di+ HEX(5c)] // Get double indirect pointer
502 mov dword ptr [bp+Ext2InodeDoubleIndirectPointer],edx // Save double indirect pointer
504 // Now copy the direct pointers to 7000:0000
505 // so that we can call Ext2ReadDirectBlocks
512 xor di,di // DS:SI = 6000:8028 ES:DI = 7000:0000
513 mov cx,24 // Moving 24 words of data
517 // Now we have all the block pointers in the
518 // right location so read them in
519 pop eax // Restore the total number of blocks in this file
520 xor ecx,ecx // Set the max count of blocks to read to 12
521 mov cl,12 // which is the number of direct block pointers in the inode
522 call Ext2ReadDirectBlockList
524 // Check to see if we actually have
525 // blocks left to read
527 jz Ext2ReadEntireFileDone
529 // Now we have read all the direct blocks in
530 // the inode. So now we have to read the indirect
531 // block and read all it's direct blocks
532 push eax // Save the total block count
533 mov eax, dword ptr [bp+Ext2InodeIndirectPointer] // Get the indirect block pointer
536 xor bx,bx // Set the load address to 7000:0000
537 call Ext2ReadBlock // Read the block
539 // Now we have all the block pointers from the
540 // indirect block in the right location so read them in
541 pop eax // Restore the total block count
542 mov ecx, dword ptr [bp+Ext2PointersPerBlock] // Get the number of block pointers that one block contains
543 call Ext2ReadDirectBlockList
545 // Check to see if we actually have
546 // blocks left to read
548 jz Ext2ReadEntireFileDone
550 // Now we have read all the direct blocks from
551 // the inode's indirect block pointer. So now
552 // we have to read the double indirect block
553 // and read all it's indirect blocks
554 // (whew, it's a good thing I don't support triple indirect blocks)
555 mov dword ptr [bp+Ext2BlocksLeftToRead],eax // Save the total block count
556 mov eax, dword ptr [bp+Ext2InodeDoubleIndirectPointer] // Get the double indirect block pointer
559 push es // Save an extra copy of this value on the stack
560 xor bx,bx // Set the load address to 7000:8000
561 call Ext2ReadBlock // Read the block
563 pop es // Put 7800h into ES (saved on the stack already)
566 Ext2ReadIndirectBlock:
567 mov eax, es:[di] // Get indirect block pointer
568 add di, 4 // Update DI for next array index
574 xor bx,bx // Set the load address to 7000:0000
575 call Ext2ReadBlock // Read the indirect block
577 // Now we have all the block pointers from the
578 // indirect block in the right location so read them in
579 mov eax, dword ptr [bp+Ext2BlocksLeftToRead] // Restore the total block count
580 mov ecx, dword ptr [bp+Ext2PointersPerBlock] // Get the number of block pointers that one block contains
581 call Ext2ReadDirectBlockList
582 mov dword ptr [bp+Ext2BlocksLeftToRead],eax // Save the total block count
586 // Check to see if we actually have
587 // blocks left to read
589 jnz Ext2ReadIndirectBlock
591 Ext2ReadEntireFileDone:
594 // Reads a maximum number of blocks
595 // from an array at 7000:0000
596 // and updates the total count
597 // ECX contains the max number of blocks to read
598 // EAX contains the number of blocks left to read
600 // EAX contians the new number of blocks left to read
601 Ext2ReadDirectBlockList:
602 cmp eax,ecx // Compare it to the maximum number of blocks to read
603 ja CallExt2ReadDirectBlocks // If it will take more blocks then just read all of the blocks
604 mov cx,ax // Otherwise adjust the block count accordingly
606 CallExt2ReadDirectBlocks:
607 sub eax,ecx // Subtract the number of blocks being read from the total count
608 push eax // Save the new total count
609 call Ext2ReadDirectBlocks
610 pop eax // Restore the total count
614 // Reads a specified number of blocks
615 // from an array at 7000:0000
616 // CX contains the number of blocks to read
617 Ext2ReadDirectBlocks:
621 xor di,di // Set ES:DI = 7000:0000
623 Ext2ReadDirectBlocksLoop:
624 mov eax,es:[di] // Get direct block pointer from array
625 add di, 4 // Update DI for next array index
627 push cx // Save number of direct blocks left
628 push es // Save array segment
629 push di // Save array offset
630 mov es,[bp+Ext2ReadEntireFileLoadSegment]
631 xor bx,bx // Setup load address for next read
633 call Ext2ReadBlock // Read the block (this updates ES for the next read)
635 mov [bp+Ext2ReadEntireFileLoadSegment],es // Save updated ES
637 pop di // Restore the array offset
638 pop es // Restore the array segment
639 pop cx // Restore the number of blocks left
641 loop Ext2ReadDirectBlocksLoop
643 // At this point all the direct blocks should
644 // be loaded and ES (Ext2ReadEntireFileLoadSegment)
645 // should be ready for the next read.
650 // Displays a file not found error message
653 mov si,msgFreeLdr // FreeLdr not found message
654 jmp short DisplayItAndReboot
656 // Displays a file size is 0 error
659 mov si,msgFileSize // Error message
660 jmp short DisplayItAndReboot
662 // Displays a file is not a regular file error
665 mov si,msgRegFile // Error message
667 call PutChars // Display it
671 .ascii "freeldr.sys not found", NUL
673 .ascii "File size 0", NUL
675 .ascii "freeldr.sys isnt a regular file", NUL
679 .ascii "Loading...", NUL
681 // times 1022-($-$$) db 0 // Pad to 1022 bytes
684 .word HEX(0aa55) // BootSector signature