1// EXT2.ASM 2// EXT2 Boot Sector 3// Copyright (c) 2002, 2003 Brian Palmer 4 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 9 10#include <asm.inc> 11.code16 12 13SECTORS_PER_TRACK = HEX(04) 14NUMBER_OF_HEADS = HEX(08) 15BIOS_CHS_DRIVE_SIZE = HEX(0C) 16LBA_SECTORS_READ = HEX(10) 17 18 19EXT2_ROOT_INO = 2 20EXT2_S_IFMT = HEX(0f0) 21EXT2_S_IFREG = HEX(080) 22 23 24//org 7c00h 25 26 27start: 28 jmp short main 29 nop 30 31BootDrive: 32 .byte HEX(80) 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] 38 39Ext2VolumeStartSector: 40 .long 263088 // Start sector of the ext2 volume 41Ext2BlockSize: 42 .long 2 // Block size in sectors 43Ext2BlockSizeInBytes: 44 .long 1024 // Block size in bytes 45Ext2PointersPerBlock: 46 .long 256 // Number of block pointers that can be contained in one block 47Ext2GroupDescPerBlock: 48 .long 32 // Number of group descriptors per block 49Ext2FirstDataBlock: 50 .long 1 // First data block (1 for 1024-byte blocks, 0 for bigger sizes) 51Ext2InodesPerGroup: 52 .long 2048 // Number of inodes per group 53Ext2InodesPerBlock: 54 .long 8 // Number of inodes per block 55 56Ext2ReadEntireFileLoadSegment: 57 .word 0 58Ext2InodeIndirectPointer: 59 .long 0 60Ext2InodeDoubleIndirectPointer: 61 .long 0 62Ext2BlocksLeftToRead: 63 .long 0 64 65main: 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 70 mov bp, HEX(7c00) 71 mov sp, HEX(7b00) // Setup a stack 72 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 76 77 mov [si],dl // Save the boot drive 78 79 80GetDriveParameters: 81 mov ah, 8 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 85 86 // If we get here then the call to the BIOS failed 87 // so just set CHS equal to the maximum addressable 88 // size 89 mov cx, HEX(0ffff) 90 mov dh,cl 91 92CalcDriveSize: 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] 109 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 113 114 115LoadExtraBootCode: 116 // First we have to load our extra boot code at 117 // sector 1 into memory at [0000:7e00h] 118 //mov eax,01h 119 xor eax,eax 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] 123 call ReadSectors 124 125 jmp LoadRootDirectory 126 127 128 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 133Ext2ReadGroupDesc: 134 shl eax,5 // Group = (Group * sizeof(GROUP_DESCRIPTOR) /* 32 */) 135 xor edx,edx 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 140 141 // Adjust the read offset so that the 142 // group descriptor is read to 7000:8000 143 mov ebx, HEX(78000) 144 sub ebx,edx 145 shr ebx,4 146 mov es,bx 147 xor bx,bx 148 149 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 153 154// Reads ext2 block into ES:[BX] 155// EAX has logical block number to read 156Ext2ReadBlock: 157 mov ecx, dword ptr [bp+Ext2BlockSize] 158 mul ecx 159 jmp ReadSectors 160 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 165Ext2ReadInode: 166 dec eax // Inode = Inode - 1 167 xor edx,edx 168 div dword ptr [bp+Ext2InodesPerGroup] // Inode = (Inode / Ext2InodesPerGroup) 169 mov ebx,eax // EBX now has the inode group number 170 mov eax,edx 171 xor edx,edx 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 176 177 // Save the inode values and put the group 178 // descriptor number in EAX and read it in 179 push edx 180 push eax 181 mov eax,ebx 182 call Ext2ReadGroupDesc 183 184 // Group descriptor has been read, now 185 // grab the inode table block number from it 186 push HEX(7000) 187 pop es 188 mov di, HEX(8008) 189 pop eax // Restore inode offset block number from stack 190 add eax, es:[di] // Add the inode table start block 191 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 195 mov ebx, HEX(68000) 196 sub ebx,edx 197 shr ebx,4 198 mov es,bx 199 xor bx,bx 200 201 call Ext2ReadBlock 202 ret 203 204 205// Reads logical sectors into ES:[BX] 206// EAX has logical sector number to read 207// CX has number of sectors to read 208ReadSectors: 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 214 215 pushad // Save logical sector number & sector count 216 217CheckInt13hExtensions: // 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 224 jne ReadSectorsCHS 225 test cl,1 // CX = API subset support bitmap 226 jz ReadSectorsCHS // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported 227 228 popad // Restore sector count & logical sector number 229 230ReadSectorsLBA: 231 pushad // Save logical sector number & sector count 232 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 236 237ReadSectorsSetupDiskAddressPacket: 238 mov [bp-LBA_SECTORS_READ],cx 239 mov word ptr [bp-LBA_SECTORS_READ+2],0 240 data32 push 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 247 248 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 253 254 add sp, 16 // Remove disk address packet from stack 255 256 popad // Restore sector count & logical sector number 257 258 push bx 259 mov ebx, [bp-LBA_SECTORS_READ] 260 add eax,ebx // Increment sector to read 261 shl ebx,5 262 mov dx,es 263 add dx,bx // Setup read buffer for next sector 264 mov es,dx 265 pop bx 266 267 sub cx,[bp-LBA_SECTORS_READ] 268 jnz ReadSectorsLBA // Read next sector 269 270 ret 271 272 273// Reads logical sectors into ES:[BX] 274// EAX has logical sector number to read 275// CX has number of sectors to read 276ReadSectorsCHS: 277 popad // Get logical sector number & sector count off stack 278 279ReadSectorsCHSLoop: 280 pushad 281 xor edx,edx 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 286 mov edx,eax 287 shr edx,16 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 295 mov ax, HEX(0201) 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 300 301 jc PrintDiskError // If the read failed then abort 302 303 popad 304 305 inc eax // Increment Sector to Read 306 307 mov dx,es 308 add dx, HEX(20) // Increment read buffer for next sector 309 mov es,dx 310 311 loop ReadSectorsCHSLoop // Read next sector 312 313 ret 314 315 316 317 318// Displays a disk error message 319// And reboots 320PrintDiskError: 321 mov si,msgDiskError // Bad boot disk message 322 call PutChars // Display it 323 324Reboot: 325 mov si,msgAnyKey // Press any key message 326 call PutChars // Display it 327 xor ax,ax 328 int HEX(16) // Wait for a keypress 329 int HEX(19) // Reboot 330 331PutChars: 332 lodsb 333 or al,al 334 jz short Done 335 call PutCharsCallBios 336 jmp short PutChars 337PutCharsCallBios: 338 mov ah, HEX(0e) 339 mov bx, HEX(07) 340 int HEX(10) 341 ret 342Done: 343 mov al, HEX(0d) 344 call PutCharsCallBios 345 mov al, HEX(0a) 346 call PutCharsCallBios 347 ret 348 349 350 351msgDiskError: 352 .ascii "Disk error", NUL 353// Sorry, need the space... 354//msgAnyKey db 'Press any key to restart',0 355msgAnyKey: 356 .ascii "Press key", NUL 357 358// times 509-($-$$) db 0 // Pad to 509 bytes 359 .org 509 360 361BootPartition: 362 .byte 0 363 364 .word HEX(0aa55) // BootSector signature 365 366 367// End of bootsector 368// 369// Now starts the extra boot code that we will store 370// at sector 1 on a EXT2 volume 371 372 373 374LoadRootDirectory: 375 376 mov eax,EXT2_ROOT_INO // Put the root directory inode number in EAX 377 call Ext2ReadInode // Read in the inode 378 379 // Point ES:DI to the inode structure at 6000:8000 380 push HEX(6000) 381 pop es 382 mov di, HEX(8000) 383 push di 384 push es // Save these for later 385 386 // Get root directory size from inode structure 387 mov eax, es:[di+4] 388 push eax 389 390 // Now that the inode has been read in load 391 // the root directory file data to 0000:8000 392 call Ext2ReadEntireFile 393 394 // Since the root directory was loaded to 0000:8000 395 // then add 8000h to the root directory's size 396 pop eax 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 399 400SearchRootDirectory: 401 push edx // Save current offset in root directory 402 push eax // Save the size of the root directory 403 404 // Now we have to convert the current offset 405 // in the root directory to a SEGMENT:OFFSET pair 406 mov eax,edx 407 xor edx,edx 408 mov ecx,16 409 div ecx // Now AX:DX has segment & offset 410 mov es,ax 411 mov di,dx 412 push di // Save the start of the directory entry 413 add di, 8 // Add the offset to the filename 414 mov si,filename 415 mov cl,11 416 repe cmpsb // Compare the file names 417 pop di 418 pop eax 419 pop edx 420 jz FoundFile 421 422 // Nope, didn't find it in this entry, keep looking 423 movzx ecx,word ptr es:[di+4] 424 add edx,ecx 425 426 // Check to see if we have reached the 427 // end of the root directory 428 cmp edx,eax 429 jb SearchRootDirectory 430 jmp PrintFileNotFound 431 432FoundFile: 433 mov eax,es:[di] // Get inode number from directory entry 434 call Ext2ReadInode // Read in the inode 435 436 // Point ES:DI to the inode structure at 6000:8000 437 pop es 438 pop di // These were saved earlier 439 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 443 je LoadFreeLoader 444 jmp PrintRegFileError 445 446LoadFreeLoader: 447 mov si,msgLoading // "Loading FreeLoader..." message 448 call PutChars // Display it 449 450 call Ext2ReadEntireFile // Read freeldr.sys to 0000:8000 451 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 459 460 461 462 463 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. 473Ext2ReadEntireFile: 474 475 // Reset the load segment 476 mov word ptr [bp+Ext2ReadEntireFileLoadSegment], HEX(800) 477 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 484 push eax 485 dec eax // Ext2BlockSizeInBytes -= 1 486 add eax, es:[di+4] // Add the file size 487 xor edx,edx 488 pop ecx // Divide by the block size in bytes 489 div ecx // EAX now contains the number of blocks to load 490 push eax 491 492 // Make sure the file size isn't zero 493 cmp eax, 0 494 jnz Ext2ReadEntireFile2 495 jmp PrintFileSizeError 496 497Ext2ReadEntireFile2: 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 503 504 // Now copy the direct pointers to 7000:0000 505 // so that we can call Ext2ReadDirectBlocks 506 push ds // Save DS 507 push es 508 push HEX(7000) 509 pop es 510 pop ds 511 mov si, HEX(8028) 512 xor di,di // DS:SI = 6000:8028 ES:DI = 7000:0000 513 mov cx,24 // Moving 24 words of data 514 rep movsw 515 pop ds // Restore DS 516 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 523 524 // Check to see if we actually have 525 // blocks left to read 526 cmp eax, 0 527 jz Ext2ReadEntireFileDone 528 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 534 push HEX(7000) 535 pop es 536 xor bx,bx // Set the load address to 7000:0000 537 call Ext2ReadBlock // Read the block 538 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 544 545 // Check to see if we actually have 546 // blocks left to read 547 cmp eax, 0 548 jz Ext2ReadEntireFileDone 549 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 557 push HEX(7800) 558 pop es 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 562 563 pop es // Put 7800h into ES (saved on the stack already) 564 xor di,di 565 566Ext2ReadIndirectBlock: 567 mov eax, es:[di] // Get indirect block pointer 568 add di, 4 // Update DI for next array index 569 push es 570 push di 571 572 push HEX(7000) 573 pop es 574 xor bx,bx // Set the load address to 7000:0000 575 call Ext2ReadBlock // Read the indirect block 576 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 583 pop di 584 pop es 585 586 // Check to see if we actually have 587 // blocks left to read 588 cmp eax, 0 589 jnz Ext2ReadIndirectBlock 590 591Ext2ReadEntireFileDone: 592 ret 593 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 599// On return: 600// EAX contians the new number of blocks left to read 601Ext2ReadDirectBlockList: 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 605 606CallExt2ReadDirectBlocks: 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 611 ret 612 613 614// Reads a specified number of blocks 615// from an array at 7000:0000 616// CX contains the number of blocks to read 617Ext2ReadDirectBlocks: 618 619 push HEX(7000) 620 pop es 621 xor di,di // Set ES:DI = 7000:0000 622 623Ext2ReadDirectBlocksLoop: 624 mov eax,es:[di] // Get direct block pointer from array 625 add di, 4 // Update DI for next array index 626 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 632 633 call Ext2ReadBlock // Read the block (this updates ES for the next read) 634 635 mov [bp+Ext2ReadEntireFileLoadSegment],es // Save updated ES 636 637 pop di // Restore the array offset 638 pop es // Restore the array segment 639 pop cx // Restore the number of blocks left 640 641 loop Ext2ReadDirectBlocksLoop 642 643 // At this point all the direct blocks should 644 // be loaded and ES (Ext2ReadEntireFileLoadSegment) 645 // should be ready for the next read. 646 ret 647 648 649 650// Displays a file not found error message 651// And reboots 652PrintFileNotFound: 653 mov si,msgFreeLdr // FreeLdr not found message 654 jmp short DisplayItAndReboot 655 656// Displays a file size is 0 error 657// And reboots 658PrintFileSizeError: 659 mov si,msgFileSize // Error message 660 jmp short DisplayItAndReboot 661 662// Displays a file is not a regular file error 663// And reboots 664PrintRegFileError: 665 mov si,msgRegFile // Error message 666DisplayItAndReboot: 667 call PutChars // Display it 668 jmp Reboot 669 670msgFreeLdr: 671 .ascii "freeldr.sys not found", NUL 672msgFileSize: 673 .ascii "File size 0", NUL 674msgRegFile: 675 .ascii "freeldr.sys isnt a regular file", NUL 676filename: 677 .ascii "freeldr.sys" 678msgLoading: 679 .ascii "Loading...", NUL 680 681// times 1022-($-$$) db 0 // Pad to 1022 bytes 682.org 1022 683 684 .word HEX(0aa55) // BootSector signature 685 686.endcode16 687 688END 689