After this part of the boot sector explanation, we need to put it all together, assemble and install it into a floppy.
Then, we need to create a simple test kernel with base address 0 and with 16-bit Un/Real Mode Code, to see if booting it really works as it should in all machines.
8. Reading the File
For FAT, we have a file/directory entry that contains a cluster number. For a new FAT12 filesystem floppy, that cluster will normally be 2.
So FAT dictates that we first read the file contents at Cluster 2, and then inspect the entry that corresponds to Cluster 2 in the FAT table.
From the FAT table, we must get another value, and once we get it and clean it up (getting rid of 4 extra bytes of the following cluster, by ANDing the highest 4 bits or right-shifting the lowest 4 bits), we must repeat the process to read the contents of the actual cluster and then inspect that cluster entry in the FAT table to follow the file's cluster chain.
Code: Select all
;8. Read file contents:
;;
;;INIT: Final processes and pass control to the kernel
;;INIT: Final processes and pass control to the kernel
;;INIT: Final processes and pass control to the kernel
.FileReadLoop:
;>> ECX = 0000????
;> [SP] = Next cluster of file
;> ESI = 0000????
;> EDX = 0000????
;> ES:EDI = Destination address
;> EBP = LBA of cluster 2
;> DS = 0
;;
xor ax,ax
mov ax,[_CurrFileClust] ;Get the most recent cluster number
cmp ax,0xFF8 ;Valid cluster?
jae short eof ;No: assume end of file
;Yes: (c-bit set)
;Read file sector here
;;
movzx edi,byte[_0Dh_sectsPerClust] ;File data sectors to read
push es
push word[_FileBuffSegment]
pop es
;Convert cluster number (which starts from 2) into LBA sector.
;The cluster area normally starts at the 34th sector (sector 33
;counting from 0). Cluster numbers start from 2, but to get their LBA address
;we must substract 2 again from the start cluster number, as set
;by the FAT algorithm. It looks like the minimum usable sector we can get
;for clusters as LBA is 33.
;
;It seems as if every major area was seen as a cluster within FAT, so
;the boot sector doesn't have a cluster number assigned,
;the area of FATs had special cluster number 0, and
;the root directory area had special cluster number 1,
;so normal cluster numbers start at 2, but we need to substract that 2
;to then, now obviously, get the correct LBA number.
;;
add ax,[_ClustAreaSect] ;Get first sector of cluster area
dec ax
dec ax ;Remove the artifact of starting cluster numbers from 2
; Input:
; EAX = LBA
; DI = sector count
; ES = segment
; Output:
; EBX high half cleared
; DL = drive # number
; EDX high half cleared
; ESI = 0
; Clobbered:
; BX, CX, DH, BP
;;
call read_sectors ;CALL to AHEAD address
mov cx,di ;Copy sectors per cluster
dec cx ;Turn it into 0-based count for shift multiply
mov di,[_0Bh_bytesPerSect] ;Get bytes per sector
shl di,cl ;Get total bytes in cluster
shr di,4 ;Convert to Real Mode segment
add [_FileBuffSegment],di ;Advance the base copy segment
pop es
;Start getting next cluster:
mov ax,[_CurrFileClust]
push ax ;Save original cluster
mov si,ax
shr si,1 ;Divide SI by 2. Now we have 0.5 of its cluster value
add si,ax ;Add original cluster value, now we have 1.5 of its value
push si ;Save multiplied value
;Read 2 FAT sectors:
mov ax,si ;Get multiplied cluster for actual sector offset
shr ax,9 ;Get the sector number, shift divide by 512
add ax,[_0Eh_reservedSects] ;Get to first actual FAT sector in LBA
xor di,di
inc di
inc di ;Specify 2 sectors to read, in the correct FAT sector, load at
;segment 0x7E0
; Input:
; EAX = LBA
; DI = sector count
; ES = segment
; Output:
; EBX high half cleared
; DL = drive # number
; EDX high half cleared
; ESI = 0
; Clobbered:
; BX, CX, DH, BP
;;
call read_sectors ;CALL to AHEAD address
;Get 2 raw cluster bytes:
pop si ;Get multiplied cluster
pop ax ;Get original cluster
and si,111111111b ;Limit SI buffer offset to first 512 bytes
mov si,[es:si] ;Access 1024-byte buffer with limitation above
test ax,1 ;See if original cluster number is even or odd
jz .evenClustNum
shr si,4 ;If odd, just keep the higher 4 bits (discard first 4 bits)
jmp short .DoneAdjustClustNum
.evenClustNum:
and si,0x0FFF ;If even, just keep the lower/first 12 bits
.DoneAdjustClustNum:
;Save the new cleaned-up cluster value:
;;
mov [_CurrFileClust],si
jmp short .FileReadLoop
eof:
;;END: Final processes and pass control to the kernel
;;END: Final processes and pass control to the kernel
;;END: Final processes and pass control to the kernel
___________________________________________________
___________________________________________________
___________________________________________________
9. Start the Bootloader with a Simple Far Jump
We will still be running in Un/Real Mode after jumping to our "kernel", and we loaded it to physical address 0x700, but we want to use offsets with base address of 0, so it's the segment that contains the value 0x700 | 700h.
Code: Select all
;9. Jump to the 16-bit Real Mode bootup image
; (it's intended to jump to 70h:0000h or 700h physical):
;;
;Now jump to the kernel image we loaded into
;address 0x500, 0x600 or 0x700 physical (just like DOS):
;;
jmp _kern16seg:0
___________________________________________________
___________________________________________________
___________________________________________________
___________________________________________________
13. Program Variables
Most of the program variables are defined as a pointer and not as an actual reserved set of bytes.
The exception is _FileBuffSegment, because it needs to be initialized with the value 0x70, since it's more efficient in size to initialize it in this way than at run time, and the same goes for the other variables.
Code: Select all
;13. Program variables here:
;;
;All of the variables below are 16-bit.
;
;They are outside of the boot code to make
;more room and only _FileBuffSegment is better off
;if we define and initialize it:
;;
_kern16seg equ 0x70
_FileBuffSegment dw _kern16seg
_RootDirSect equ 8200h+0
_RootDirSectCount equ 8200h+2
_CurrFileClust equ 8200h+4
_ClustAreaSect equ 8200h+8