Loading Sectors
Loading Sectors
by Daniel Rowell Faulkner
This tutorial is to show you how to load sectors from a floppy disk. Variations will be needed to get this code to work on most other disks.
In the bootloader code you can use a specific BIOS interrupt to load the sectors from the floppy disk for you. But unfortunatly it addresses the drive in the form of heads, cylinders and sectors.
Ideally we want to be able to refer to the floppy disk in terms of sectors only (chunks of 512bytes) the code to do that is in the LBA to CHS tutorial. Though to use that you need to first understand this tutorial.
Explaination of the CHS addressing system:
CHS simply stands for Cylinder, Head, Sector. This is the addressing system used by the low level BIOS functions and the likes.
The definitions of each are:
Sector = Chunk of data on the disk (normally 512bytes) - Segment of a cylinder(aka track)
Cylinder = This is a track on the disk (normally contains 18sectors)
Head = Which side of the disk (most floppys now are double sided, 2 heads)
So sectors 1-18 are on cylinder 1 on head 1 (or track 1 on side 1) but after that it gets complicated sectors 18+ are on varying cylinders and the head number alternates per cylinder.
This would be better explained with a diagram but that is yet to come I'm afraid though I may include one at a later date.
Explanation of the interrupt function we will be using:
We will be using interrupt 0x13 (anything starting in 0x or ending with h is a hexidecimal number)
Function number passed in ah (Most int's use ah to define a specific function) = 2 (Read sector)
The values to be passed in the rest of the registers:
al = Number of sectors to read (To be safe I wouldn't cross the cylinder boundary) dh = Head to read from (aka side) - Addressing registers eg: Town/City dl = Drive to read from. - Country ch = Cylinder (aka track) to read from - Street name cl = Sector to read - House number es = Segment to put loaded data into - Output address in eg: Street name bx = Offset to put loaded data into - House number in the street
An example to load the first sector of a floppy disk would be:
ah=2(Function number),al=1(1 sector to read),dh=1(First head)
dl=0(default for floppy drive),ch=1(First cylinder),cl=1(First sector)
es=1000h(Put the output at 1000h in memory), bx=0(Offset of 0)
To get beyound the 18th sector though you would have to change the head and cylinder values as appropriet.
Example of the code working:
; Code to load the second sector on the disk into memory location 0x2000:0x0000 mov bx, 0x2000 ; Segment location to read into (remember can't load direct to segment register) mov es, bx mov bx, 0 ; Offset to read into mov ah, 02 ; BIOS read sector function mov al, 01 ; read one sector mov ch, 01 ; Track to read mov cl, 02 ; Sector to read mov dh, 01 ; Head to read mov dl, 00 ; Drive to read int 0x13 ; Make the BIOS call (int 13h contains mainly BIOS drive functions)
I recommend using the LBA to CHS code from one of my other tutorials to get past the cylinder and head addressing problems. In order to use that you put the code into a loop and read one sector at a time like so:
Get and set output location in memory,get start location,get number of sectors to load.
Loop 'number of sectors to load' times:
Run LBA to CHS (to convert the sector number in a head and cylinder)
Run int 0x13 to load the sector from the LBA to CHS outputed data.
Increase bx by the number of bytes per sector (512) ready for next sector.
This code is often best put into a procedure and called as needed to load sectors off a floppy disk.
A complete example of such a procedure is:
; Load kernel procedure LoadKern: mov ah, 0x02 ; Read Disk Sectors mov al, 0x01 ; Read one sector only (512 bytes per sector) mov ch, 0x00 ; Track 0 mov cl, 0x02 ; Sector 2 mov dh, 0x00 ; Head 0 mov dl, 0x00 ; Drive 0 (Floppy 1) (This can be replaced with the value in BootDrv) mov bx, 0x2000 ; Segment 0x2000 mov es, bx ; again remember segments bust be loaded from non immediate data mov bx, 0x0000 ; Start of segment - offset value .readsector int 0x13 ; Call BIOS Read Disk Sectors function jc .readsector ; If there was an error, try again mov ax, 0x2000 ; Set the data segment register mov ds, ax ; to point to the kernel location in memory jmp 0x2000:0x0000 ; Jump to the kernel A complete example of a procedure including the LBA to CHS code (that procedure is in that tutorial for details on it, though this does use a different version of that procedure): ; Procedure ReadSectors - Reads sectors from the disk. ; Input: cx - Number of sectors; ax - Start position ; Output: Loaded file into: es:bx ReadSectors: .MAIN: ; Main Label mov di, 5 ; Loop 5 times max!!! .SECTORLOOP: push ax ; Save register values on the stack push bx push cx call LBAtoCHS ; Change the LBA addressing to CHS addressing ; The code to read a sector from the floppy drive mov ah, 02 ; BIOS read sector function mov al, 01 ; read one sector mov ch, BYTE [absoluteTrack] ; Track to read mov cl, BYTE [absoluteSector] ; Sector to read mov dh, BYTE [absoluteHead] ; Head to read mov dl, BYTE [BootDrv] ; Drive to read int 0x13 ; Make the BIOS call jnc .SUCCESS dec di ; Decrease the counter pop cx ; Restore the register values pop bx pop ax jnz .SECTORLOOP ; Try the command again incase the floppy drive is being annoying call ReadError ; Call the error command in case all else fails .SUCCESS pop cx ; Restore the register values pop bx pop ax add bx, WORD [BytesPerSector] ; Queue next buffer (Adjust output location so as to not over write the same area again with the next set of data) inc ax ; Queue next sector (Start at the next sector along from last time) ; I think I may add a status bar thing also. A # for each sector loaded or something. ; Shouldn't a test for CX go in here??? dec cx ; One less sector left to read jz .ENDREAD ; Jump to the end of the precedure loop .MAIN ; Read next sector (Back to the start) .ENDREAD: ; End of the read procedure ret ; Return to main program
I have loads of variations of this code as I slowly improved it over various versions of my bootloader. I suggest looking at some of my source if you want a more detailed explanation of the source and to see it in context.
Once you have the data loaded you have to transfer control to it. Now as you should know if you know asm well you can't modify the value in the IP register directly so you have to setup the data segment registers and then jump to the new location.
The jump command needed is normally:
jmp
eg: jmp 0x1000:0x0000
Normally for simple kernels you will leave the second part as 0x0000 and the first address should be equal to where you loaded the kernel in memory.
That is all I have had time to write I am afraid. Not being paid for this :( I will hopefully come back and write this in a more legible form but thats in the future some time.
All of my examples are cuts and pastes from various versions of my bootloader. Those is probably not ideal examples as I have implemented some things in odd ways. But my bootloaders does work which is the important thing. If you do use any of my code (no matter how small) I would appreciate being notified and my name mentioned with the source next to my code with a link to my website/details. To use any of my code in a commercial product requires my permission however!
I hope this has helpped you with loading sectors directly.
If this has helpped you please send me an e-mail saying so. (I like compliments)
If you want to see new things in here please say, if you want to translate this into an other language please send me the new version so I can host that as an alternative. (I can translate copy's of this if requested but the altavista/google/etc translaters aren't quite perfected for large documents like this, and I would rather spend my time working on something else)
This tutorial is here with the permission of Daniel Faulkner, if you wish to put this tutorial on another site, please contact Daniel for permission.