In the process of learning assembly, I am writing an OS. I have successfully written the code necessary for appending a second 512 byte sector to the initial 512 byte bootloader:
%define KBDINT 0x16
%define VIDINT 0x10
%define DISKINT 0x13
%define TTYOUT 0x0E
%define VIDMODE 0x0000
%define NUL 0x00
%define CR 0x0D
%define LF 0x0A
%define START 0x7C00
%macro PRINT 1
mov si, %1
call print
%endmacro
bits 16 ; 16 bit real mode
org START ; loader start in memory
start: jmp main
print: jmp .init
.loop: mov bx, VIDMODE
mov ah, TTYOUT
int VIDINT
inc si
.init: mov al, [si]
cmp al, NUL
jne .loop
ret
main: cli
xor ax, ax
mov ds, ax
mov es, ax
sti
PRINT welcome
mov ah, NUL
int DISKINT
mov al, 0x01 ; sector count
mov ah, 0x02 ; read function
mov bx, kernel
mov cl, 0x02
mov ch, 0x00 ; cylinder number
mov dh, 0x00 ; head number
int DISKINT
jc fail
jmp kernel
fail: PRINT failure
; jmp halt
halt: PRINT halting
cli
hlt
PRINT imprbbl
jmp halt
welcome db "moose os", CR, LF, NUL
failure db "failed disk load", CR, LF, NUL
halting db "halting", CR, LF, NUL
imprbbl db "but that's impossible!", CR, LF, NUL
times 0x0200 - ($ - $$) - 2 db 0x00
end dw 0xAA55
kernel: PRINT yay
yay db "kernel", CR, LF, NUL
jmp halt
times 0xFFFF db 0x00
I compile the file with: nasm -f bin -o boot.bin boot.asm && qemu boot.bin
:
I am curious how heads and cylinders are used:
- How are the sectors iterated through?
- How does iteration differ between emulation and direct execution?
To iterate over many sectors using CHS CylinderHeadSector notation, we first have to retrieve the actual limits on these parameters. BIOS has function 08h on
int 13h
that gives us the maximum values as well as some extra info that we don't need for now.The sector number in CL ranges from 1 to 63.
The head number in DH ranges from 0 to 255, although 255 is seldom used.
The cylinder number in CL ranges from 0 to 1023. Since this cannot be held in a single byte, the 2 highest bits of this 10-bit number are stored in bits 6 and 7 of the CL register!
How the iteration works
Think of the CHS notation as if it were some kind of number where C is the most significant part and S is the least significant part.
To get to the next sector on the disk we start our incrementation of this number at its least significant end.
If by incrementing the S part we overflow its range, we reset it to its smallest value (1) and start incrementing the next more significant part which is H in this case.
If by incrementing the H part we overflow its range, we reset it to its smallest value (0) and start incrementing the most significant part which is C in this case.
If by incrementing the C part we overflow its range, we reset it to its smallest value (0). This will make for a wraparound on the disk. If on input a correct SectorCount was given then normally at this point reading will have stopped.
A note on sector size
Although it's perfectly alright to have sectors that are not 512 bytes in length, it's a save assumption that they will be that size. After decades of programming I've never seen any disk that hadn't 512-byte sectors.
If you insisted on supporting different sizes, you can look at the 4th byte of the DisketteParameterTable for which you recieved a pointer in ES:DI from the BIOS function ReturnDiskDriveParameters.
I suppose that by direct execution you understand real hardware.
For real hardware BIOS will return you the geometry in CHS notation and the sectors exist ... well just because they are real!
Under emulation the emulator will do its best to also provide you will these geometry values but it will be up to you to make sure that enough sectors exist on the concerned drive. This is exactly what @Jester said when he asked you: "Do you even have that sector in the image file?" You resolved this issue by enlarging the image file using
times 0xFFFF db 0x00
Some extra advice
You didn't setup the stack. Since you load your kernel above the bootsector at 7C00h, I suggest you initialize SS:SP to 0000h:7C00h for a stack beneath the bootsector.
As @Fifoernik commented you would better put the
jmp halt
before theyay db "kernel", CR, LF, NUL
to prevent the execution of this data!