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
10 SECTORS_PER_TRACK equ 0x04
11 NUMBER_OF_HEADS equ 0x08
12 BIOS_CHS_DRIVE_SIZE equ 0x0C
13 LBA_SECTORS_READ equ 0x10
32 ;BootPartition db 0 ; Moved to end of boot sector to have a standard format across all boot sectors
33 ;SectorsPerTrack db 63 ; Moved to [bp-SECTORS_PER_TRACK]
34 ;NumberOfHeads dw 16 ; Moved to [bp-NUMBER_OF_HEADS]
35 ;BiosCHSDriveSize dd (1024 * 1024 * 63) ; Moved to [bp-BIOS_CHS_DRIVE_SIZE]
36 ;LBASectorsRead dd 0 ; Moved to [bp-LBA_SECTORS_READ]
38 Ext2VolumeStartSector dd 263088 ; Start sector of the ext2 volume
39 Ext2BlockSize dd 2 ; Block size in sectors
40 Ext2BlockSizeInBytes dd 1024 ; Block size in bytes
41 Ext2PointersPerBlock dd 256 ; Number of block pointers that can be contained in one block
42 Ext2GroupDescPerBlock dd 32 ; Number of group descriptors per block
43 Ext2FirstDataBlock dd 1 ; First data block (1 for 1024-byte blocks, 0 for bigger sizes)
44 Ext2InodesPerGroup dd 2048 ; Number of inodes per group
45 Ext2InodesPerBlock dd 8 ; Number of inodes per block
47 Ext2ReadEntireFileLoadSegment:
49 Ext2InodeIndirectPointer:
51 Ext2InodeDoubleIndirectPointer:
57 xor ax,ax ; Setup segment registers
58 mov ds,ax ; Make DS correct
59 mov es,ax ; Make ES correct
60 mov ss,ax ; Make SS correct
62 mov sp,7b00h ; Setup a stack
65 cmp BYTE [BYTE bp+BootDrive],BYTE 0xff ; If they have specified a boot drive then use it
66 jne GetDriveParameters
68 mov [BYTE bp+BootDrive],dl ; Save the boot drive
73 mov dl,[BYTE bp+BootDrive] ; Get boot drive in dl
74 int 13h ; Request drive parameters from the bios
75 jnc CalcDriveSize ; If the call succeeded then calculate the drive size
77 ; If we get here then the call to the BIOS failed
78 ; so just set CHS equal to the maximum addressable
84 ; Now that we have the drive geometry
85 ; lets calculate the drive size
86 mov bl,ch ; Put the low 8-bits of the cylinder count into BL
87 mov bh,cl ; Put the high 2-bits in BH
88 shr bh,6 ; Shift them into position, now BX contains the cylinder count
89 and cl,3fh ; Mask off cylinder bits from sector count
90 ; CL now contains sectors per track and DH contains head count
91 movzx eax,dh ; Move the heads into EAX
92 movzx ebx,bx ; Move the cylinders into EBX
93 movzx ecx,cl ; Move the sectors per track into ECX
94 inc eax ; Make it one based because the bios returns it zero based
95 mov [BYTE bp-NUMBER_OF_HEADS],eax ; Save number of heads
96 mov [BYTE bp-SECTORS_PER_TRACK],ecx ; Save number of sectors per track
97 inc ebx ; Make the cylinder count one based also
98 mul ecx ; Multiply heads with the sectors per track, result in edx:eax
99 mul ebx ; Multiply the cylinders with (heads * sectors) [stored in edx:eax already]
101 ; We now have the total number of sectors as reported
102 ; by the bios in eax, so store it in our variable
103 mov [BYTE bp-BIOS_CHS_DRIVE_SIZE],eax
107 ; First we have to load our extra boot code at
108 ; sector 1 into memory at [0000:7e00h]
111 inc eax ; Read logical sector 1, EAX now = 1
112 mov cx,1 ; Read one sector
113 mov bx,7e00h ; Read sector to [0000:7e00h]
116 jmp LoadRootDirectory
120 ; Reads ext2 group descriptor into [7000:8000]
121 ; We read it to this arbitrary location so
122 ; it will not cross a 64k boundary
123 ; EAX has group descriptor number to read
125 shl eax,5 ; Group = (Group * sizeof(GROUP_DESCRIPTOR) /* 32 */)
127 div DWORD [BYTE bp+Ext2GroupDescPerBlock] ; Group = (Group / Ext2GroupDescPerBlock)
128 add eax,DWORD [BYTE bp+Ext2FirstDataBlock] ; Group = Group + Ext2FirstDataBlock + 1
129 inc eax ; EAX now has the group descriptor block number
130 ; EDX now has the group descriptor offset in the block
132 ; Adjust the read offset so that the
133 ; group descriptor is read to 7000:8000
141 ; Everything is now setup to call Ext2ReadBlock
142 ; Instead of using the call instruction we will
143 ; just put Ext2ReadBlock right after this routine
145 ; Reads ext2 block into [ES:BX]
146 ; EAX has logical block number to read
148 mov ecx,DWORD [BYTE bp+Ext2BlockSize]
152 ; Reads ext2 inode into [6000:8000]
153 ; We read it to this arbitrary location so
154 ; it will not cross a 64k boundary
155 ; EAX has inode number to read
157 dec eax ; Inode = Inode - 1
159 div DWORD [BYTE bp+Ext2InodesPerGroup] ; Inode = (Inode / Ext2InodesPerGroup)
160 mov ebx,eax ; EBX now has the inode group number
163 div DWORD [BYTE bp+Ext2InodesPerBlock] ; Inode = (Inode / Ext2InodesPerBlock)
164 shl edx,7 ; FIXME: InodeOffset *= 128 (make the array index a byte offset)
165 ; EAX now has the inode offset block number from inode table
166 ; EDX now has the inode offset in the block
168 ; Save the inode values and put the group
169 ; descriptor number in EAX and read it in
173 call Ext2ReadGroupDesc
175 ; Group descriptor has been read, now
176 ; grab the inode table block number from it
180 pop eax ; Restore inode offset block number from stack
181 add eax,DWORD [es:di] ; Add the inode table start block
183 ; Adjust the read offset so that the
184 ; inode we want is read to 6000:8000
185 pop edx ; Restore inode offset in the block from stack
196 ; Reads logical sectors into [ES:BX]
197 ; EAX has logical sector number to read
198 ; CX has number of sectors to read
200 add eax,DWORD [BYTE bp+Ext2VolumeStartSector] ; Add the start of the volume
201 cmp eax,DWORD [BYTE bp-BIOS_CHS_DRIVE_SIZE] ; Check if they are reading a sector outside CHS range
202 jae ReadSectorsLBA ; Yes - go to the LBA routine
203 ; If at all possible we want to use LBA routines because
204 ; They are optimized to read more than 1 sector per read
206 pushad ; Save logical sector number & sector count
208 CheckInt13hExtensions: ; Now check if this computer supports extended reads
209 mov ah,0x41 ; AH = 41h
210 mov bx,0x55aa ; BX = 55AAh
211 mov dl,[BYTE bp+BootDrive] ; DL = drive (80h-FFh)
212 int 13h ; IBM/MS INT 13 Extensions - INSTALLATION CHECK
213 jc ReadSectorsCHS ; CF set on error (extensions not supported)
214 cmp bx,0xaa55 ; BX = AA55h if installed
216 test cl,1 ; CX = API subset support bitmap
217 jz ReadSectorsCHS ; Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
219 popad ; Restore sector count & logical sector number
222 pushad ; Save logical sector number & sector count
224 cmp cx,byte 64 ; Since the LBA calls only support 0x7F sectors at a time we will limit ourselves to 64
225 jbe ReadSectorsSetupDiskAddressPacket ; If we are reading less than 65 sectors then just do the read
226 mov cx,64 ; Otherwise read only 64 sectors on this loop iteration
228 ReadSectorsSetupDiskAddressPacket:
229 mov [BYTE bp-LBA_SECTORS_READ],cx
230 mov WORD [BYTE bp-LBA_SECTORS_READ+2],0
232 push eax ; Put 64-bit logical block address on stack
233 push es ; Put transfer segment on stack
234 push bx ; Put transfer offset on stack
235 push cx ; Set transfer count
236 push byte 0x10 ; Set size of packet to 10h
237 mov si,sp ; Setup disk address packet on stack
240 mov dl,[BYTE bp+BootDrive] ; Drive number
241 mov ah,42h ; Int 13h, AH = 42h - Extended Read
243 jc PrintDiskError ; If the read failed then abort
245 add sp,byte 0x10 ; Remove disk address packet from stack
247 popad ; Restore sector count & logical sector number
250 mov ebx,DWORD [BYTE bp-LBA_SECTORS_READ]
251 add eax,ebx ; Increment sector to read
254 add dx,bx ; Setup read buffer for next sector
258 sub cx,[BYTE bp-LBA_SECTORS_READ]
259 jnz ReadSectorsLBA ; Read next sector
264 ; Reads logical sectors into [ES:BX]
265 ; EAX has logical sector number to read
266 ; CX has number of sectors to read
268 popad ; Get logical sector number & sector count off stack
273 mov ecx,DWORD [BYTE bp-SECTORS_PER_TRACK]
274 div ecx ; Divide logical by SectorsPerTrack
275 inc dl ; Sectors numbering starts at 1 not 0
276 mov cl,dl ; Sector in CL
279 div WORD [BYTE bp-NUMBER_OF_HEADS] ; Divide logical by number of heads
280 mov dh,dl ; Head in DH
281 mov dl,[BYTE bp+BootDrive] ; Drive number in DL
282 mov ch,al ; Cylinder in CX
283 ror ah,2 ; Low 8 bits of cylinder in CH, high 2 bits
284 ; in CL shifted to bits 6 & 7
285 or cl,ah ; Or with sector number
287 int 13h ; DISK - READ SECTORS INTO MEMORY
288 ; AL = number of sectors to read, CH = track, CL = sector
289 ; DH = head, DL = drive, ES:BX -> buffer to fill
290 ; Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read
292 jc PrintDiskError ; If the read failed then abort
296 inc eax ; Increment Sector to Read
299 add dx,byte 20h ; Increment read buffer for next sector
302 loop ReadSectorsCHSLoop ; Read next sector
309 ; Displays a disk error message
312 mov si,msgDiskError ; Bad boot disk message
313 call PutChars ; Display it
316 mov si,msgAnyKey ; Press any key message
317 call PutChars ; Display it
319 int 16h ; Wait for a keypress
326 call PutCharsCallBios
335 call PutCharsCallBios
337 call PutCharsCallBios
342 msgDiskError db 'Disk error',0
343 ; Sorry, need the space...
344 ;msgAnyKey db 'Press any key to restart',0
345 msgAnyKey db 'Press any key',0
347 times 509-($-$$) db 0 ; Pad to 509 bytes
351 dw 0aa55h ; BootSector signature
356 ; Now starts the extra boot code that we will store
357 ; at sector 1 on a EXT2 volume
363 mov eax,EXT2_ROOT_INO ; Put the root directory inode number in EAX
364 call Ext2ReadInode ; Read in the inode
366 ; Point ES:DI to the inode structure at 6000:8000
371 push es ; Save these for later
373 ; Get root directory size from inode structure
374 mov eax,DWORD [es:di+4]
377 ; Now that the inode has been read in load
378 ; the root directory file data to 0000:8000
379 call Ext2ReadEntireFile
381 ; Since the root directory was loaded to 0000:8000
382 ; then add 8000h to the root directory's size
384 mov edx,8000h ; Set EDX to the current offset in the root directory
385 add eax,edx ; Initially add 8000h to the size of the root directory
388 push edx ; Save current offset in root directory
389 push eax ; Save the size of the root directory
391 ; Now we have to convert the current offset
392 ; in the root directory to a SEGMENT:OFFSET pair
396 div ecx ; Now AX:DX has segment & offset
399 push di ; Save the start of the directory entry
400 add di,byte 8 ; Add the offset to the filename
403 rep cmpsb ; Compare the file names
409 ; Nope, didn't find it in this entry, keep looking
410 movzx ecx,WORD [es:di+4]
413 ; Check to see if we have reached the
414 ; end of the root directory
416 jb SearchRootDirectory
417 jmp PrintFileNotFound
420 mov eax,[es:di] ; Get inode number from directory entry
421 call Ext2ReadInode ; Read in the inode
423 ; Point ES:DI to the inode structure at 6000:8000
425 pop di ; These were saved earlier
427 mov cx,[es:di] ; Get the file mode so we can make sure it's a regular file
428 and ch,EXT2_S_IFMT ; Mask off everything but the file type
429 cmp ch,EXT2_S_IFREG ; Make sure it's a regular file
431 jmp PrintRegFileError
434 mov si,msgLoading ; "Loading FreeLoader..." message
435 call PutChars ; Display it
437 call Ext2ReadEntireFile ; Read freeldr.sys to 0000:8000
439 mov dl,[BYTE bp+BootDrive]
440 mov dh,[BYTE bp+BootPartition]
441 push 0 ; push segment (0x0000)
442 mov eax, [0x8000 + 0xA8] ; load the RVA of the EntryPoint into eax
443 add eax, 0x8000 ; RVA -> VA
444 push ax ; push offset
445 retf ; Transfer control to FreeLoader
451 ; Reads ext2 file data into [0000:8000]
452 ; This function assumes that the file's
453 ; inode has been read in to 6000:8000 *and*
454 ; ES:DI points to 6000:8000
455 ; This will load all the blocks up to
456 ; and including the double-indirect pointers.
457 ; This should be sufficient because it
458 ; allows for ~64MB which is much bigger
459 ; than we need for a boot loader.
462 ; Reset the load segment
463 mov WORD [BYTE bp+Ext2ReadEntireFileLoadSegment],800h
465 ; Now we must calculate how
466 ; many blocks to read in
467 ; We will do this by rounding the
468 ; file size up to the next block
469 ; size and then dividing by the block size
470 mov eax,DWORD [BYTE bp+Ext2BlockSizeInBytes] ; Get the block size in bytes
472 dec eax ; Ext2BlockSizeInBytes -= 1
473 add eax,DWORD [es:di+4] ; Add the file size
475 pop ecx ; Divide by the block size in bytes
476 div ecx ; EAX now contains the number of blocks to load
479 ; Make sure the file size isn't zero
481 jnz Ext2ReadEntireFile2
482 jmp PrintFileSizeError
485 ; Save the indirect & double indirect pointers
486 mov edx,DWORD [es:di+0x58] ; Get indirect pointer
487 mov [BYTE bp+Ext2InodeIndirectPointer],edx ; Save indirect pointer
488 mov edx,DWORD [es:di+0x5c] ; Get double indirect pointer
489 mov [BYTE bp+Ext2InodeDoubleIndirectPointer],edx ; Save double indirect pointer
491 ; Now copy the direct pointers to 7000:0000
492 ; so that we can call Ext2ReadDirectBlocks
499 xor di,di ; DS:SI = 6000:8028 ES:DI = 7000:0000
500 mov cx,24 ; Moving 24 words of data
504 ; Now we have all the block pointers in the
505 ; right location so read them in
506 pop eax ; Restore the total number of blocks in this file
507 xor ecx,ecx ; Set the max count of blocks to read to 12
508 mov cl,12 ; which is the number of direct block pointers in the inode
509 call Ext2ReadDirectBlockList
511 ; Check to see if we actually have
512 ; blocks left to read
514 jz Ext2ReadEntireFileDone
516 ; Now we have read all the direct blocks in
517 ; the inode. So now we have to read the indirect
518 ; block and read all it's direct blocks
519 push eax ; Save the total block count
520 mov eax,DWORD [BYTE bp+Ext2InodeIndirectPointer] ; Get the indirect block pointer
523 xor bx,bx ; Set the load address to 7000:0000
524 call Ext2ReadBlock ; Read the block
526 ; Now we have all the block pointers from the
527 ; indirect block in the right location so read them in
528 pop eax ; Restore the total block count
529 mov ecx,DWORD [BYTE bp+Ext2PointersPerBlock] ; Get the number of block pointers that one block contains
530 call Ext2ReadDirectBlockList
532 ; Check to see if we actually have
533 ; blocks left to read
535 jz Ext2ReadEntireFileDone
537 ; Now we have read all the direct blocks from
538 ; the inode's indirect block pointer. So now
539 ; we have to read the double indirect block
540 ; and read all it's indirect blocks
541 ; (whew, it's a good thing I don't support triple indirect blocks)
542 mov [BYTE bp+Ext2BlocksLeftToRead],eax ; Save the total block count
543 mov eax,DWORD [BYTE bp+Ext2InodeDoubleIndirectPointer] ; Get the double indirect block pointer
546 push es ; Save an extra copy of this value on the stack
547 xor bx,bx ; Set the load address to 7000:8000
548 call Ext2ReadBlock ; Read the block
550 pop es ; Put 7800h into ES (saved on the stack already)
553 Ext2ReadIndirectBlock:
554 mov eax,DWORD [es:di] ; Get indirect block pointer
555 add di,BYTE 4 ; Update DI for next array index
561 xor bx,bx ; Set the load address to 7000:0000
562 call Ext2ReadBlock ; Read the indirect block
564 ; Now we have all the block pointers from the
565 ; indirect block in the right location so read them in
566 mov eax,DWORD [BYTE bp+Ext2BlocksLeftToRead] ; Restore the total block count
567 mov ecx,DWORD [BYTE bp+Ext2PointersPerBlock] ; Get the number of block pointers that one block contains
568 call Ext2ReadDirectBlockList
569 mov [BYTE bp+Ext2BlocksLeftToRead],eax ; Save the total block count
573 ; Check to see if we actually have
574 ; blocks left to read
576 jnz Ext2ReadIndirectBlock
578 Ext2ReadEntireFileDone:
581 ; Reads a maximum number of blocks
582 ; from an array at 7000:0000
583 ; and updates the total count
584 ; ECX contains the max number of blocks to read
585 ; EAX contains the number of blocks left to read
587 ; EAX contains the new number of blocks left to read
588 Ext2ReadDirectBlockList:
589 cmp eax,ecx ; Compare it to the maximum number of blocks to read
590 ja CallExt2ReadDirectBlocks ; If it will take more blocks then just read all of the blocks
591 mov cx,ax ; Otherwise adjust the block count accordingly
593 CallExt2ReadDirectBlocks:
594 sub eax,ecx ; Subtract the number of blocks being read from the total count
595 push eax ; Save the new total count
596 call Ext2ReadDirectBlocks
597 pop eax ; Restore the total count
601 ; Reads a specified number of blocks
602 ; from an array at 7000:0000
603 ; CX contains the number of blocks to read
604 Ext2ReadDirectBlocks:
608 xor di,di ; Set ES:DI = 7000:0000
610 Ext2ReadDirectBlocksLoop:
611 mov eax,[es:di] ; Get direct block pointer from array
612 add di,BYTE 4 ; Update DI for next array index
614 push cx ; Save number of direct blocks left
615 push es ; Save array segment
616 push di ; Save array offset
617 mov es,[BYTE bp+Ext2ReadEntireFileLoadSegment]
618 xor bx,bx ; Setup load address for next read
620 call Ext2ReadBlock ; Read the block (this updates ES for the next read)
622 mov [BYTE bp+Ext2ReadEntireFileLoadSegment],es ; Save updated ES
624 pop di ; Restore the array offset
625 pop es ; Restore the array segment
626 pop cx ; Restore the number of blocks left
628 loop Ext2ReadDirectBlocksLoop
630 ; At this point all the direct blocks should
631 ; be loaded and ES (Ext2ReadEntireFileLoadSegment)
632 ; should be ready for the next read.
637 ; Displays a file not found error message
640 mov si,msgFreeLdr ; FreeLdr not found message
641 jmp short DisplayItAndReboot
643 ; Displays a file size is 0 error
646 mov si,msgFileSize ; Error message
647 jmp short DisplayItAndReboot
649 ; Displays a file is not a regular file error
652 mov si,msgRegFile ; Error message
654 call PutChars ; Display it
657 msgFreeLdr db 'freeldr.sys not found',0
658 msgFileSize db 'File size is 0',0
659 msgRegFile db 'freeldr.sys isnt a regular file',0
660 filename db 'freeldr.sys'
661 msgLoading db 'Loading FreeLoader...',0
663 times 1022-($-$$) db 0 ; Pad to 1022 bytes
665 dw 0aa55h ; BootSector signature