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:
A sector is the smallest physical storage unit on the disk, and on most file systems it is fixed at 512 bytes in size. A cluster can consist of one or more consecutive sectors – commonly, a cluster will have four or eight sectors.
Storing the Data Tracks are concentric circles that surround the entire platter. Sectors are wedges that, when grouped together, form a track. Sectors contain a fixed number of bytes – usually 256 or 512 – and are grouped into clusters. Before a drive can be used, it must be properly formatted.
There can be more than a thousand tracks on a 3.5-inch hard disk. Sections within each track are called sectors. A sector is the smallest physical storage unit on a disk, and is almost always 512 bytes (0.5 kB) in size. The figure below shows a hard disk with two platters.
Each sector stores a fixed amount of user data, traditionally 512 bytes for hard disk drives. But because of better data integrity at higher densities and robust error correction capabilities newer HDDs now store 4096 bytes (4K) in each sector.
•How are the sectors iterated through?
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!
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.
; INPUT: DL=Drive
; CH=Cylinder
; DH=Head
; CL=Sector
; AX=SectorCount
; ES:BX=Buffer
; OUTPUT: CF=0 AH = 0
; CH,DH,CL = CHS of following sector
; CF=1 AH = Error status
; CH,DH,CL = CHS of problem sector
ReadDiskSectors:
push es
push di
push bp
mov bp,sp ;Local variables:
push ax ;[bp-2]=SectorCount
push cx ;[bp-4]=MaxSector
push dx ;[bp-6]=MaxHead
push bx ;[bp-8]=MaxCylinder
push es
mov ah,08h
int 13h ;ReturnDiskDriveParameters
pop es
jc NOK
mov bx,cx ;10-bit cylinder info -> BX
xchg bl,bh
shr bh,6
xchg [bp-8],bx ;Store MaxCylinder and get input BX back
movzx dx,dh ;8-bit head info -> DX
xchg [bp-6],dx ;Store MaxHead and get input DX back
and cx,003Fh ;6-bit sector info -> CX
xchg [bp-4],cx ;Store MaxSector and get input CX back
ReadNext:
mov di,5 ;Max 5 tries per sector
ReadAgain:
mov ax,0201h ;Read 1 sector
int 13h ;ReadDiskSectors
jnc OK
push ax ;Save error status byte in AH
mov ah,00h
int 13h ;ResetDiskSystem
pop ax
dec di
jnz ReadAgain
stc
jmp NOK
OK:
dec word ptr [bp-2] ;SectorCount
jz Ready
call NextCHS
mov ax,es ;Move buffer 512 bytes up
add ax,512/16
mov es,ax
jmp ReadNext
Ready:
call NextCHS ;Return useful CHS values to support reads
xor ah,ah ; -> CF=0 ... that are longer than memory
NOK:
mov sp,bp
pop bp
pop di
pop es
ret
NextCHS:
mov al,cl ;Calculate the 6-bit sector number
and al,00111111b
cmp al,[bp-4] ;MaxSector
jb NextSector
cmp dh,[bp-6] ;MaxHead
jb NextHead
mov ax,cx ;Calculate the 10-bit cylinder number
xchg al,ah
shr ah,6
cmp ax,[bp-8] ;MaxCylinder
jb NextCylinder
DiskWrap:
mov cx,1 ;Wraparound to very first sector on disk
mov dh,0
ret
NextCylinder:
inc ax
shl ah,6 ;Split 10-bit cylinder number over CL and CH
xchg al,ah
mov cx,ax
mov dh,0
inc cl
ret
NextHead:
inc dh
and cl,11000000b
NextSector:
inc cl
ret
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.
•How does iteration differ between emulation and direct execution?
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
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.
main:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 7C00h
sti
PRINT welcome
As @Fifoernik commented you would better put the jmp halt
before the yay db "kernel", CR, LF, NUL
to prevent the execution of this data!
kernel:
PRINT yay
jmp halt
yay db "kernel", CR, LF, NUL
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With