0x09 Bootloader - read disk parameters

When BIOS finishes its job, it finds a bootable disk and loads the first sector - the first 512 bytes. The magic 0xaa55 is the value marking bootable 510 bytes. But 510 bytes is not enough for a greedy programmer.

BIOS handle the first sector. the rest of them we must load ourselves. To do this one must understand parameters of BIOS INT 0x13 interrupt. Nowadays the commonly used scheme for locating blocks of data on storage devices is LBA (Logical Block Addressing). It replaced the CHS (Cylinder-Head-Sector) scheme. As you may see CHS expose some technical details. BIOS of cors uses CHS in basic interruptions and LBA in extended ones. Knowledge is knowledge, knowing where our data is stored and converting this to Cylinders, Heads and Sectors is good for understanding this topic.

BIOS leaves a little gift in dl register - the disk/drive index. To calculate proper values we must know disk parameters: how big is a sector, how many sectors are on track and heads in a cylinder. To get that information one may run ah = 0x08 or if available the extended version ah = 0x48 [WI13].

Lets try ah = 0x48, dl we have already, it leaves only a pointer (ds:si) to the structure to fill. If something goes bad cf register will be set and ah will have return code. Structure of the buffer has this informations:

 *----------*
 | 2 bytes  |  +0x00 size of this buffer = 0x1e   
 *----------*
 | 2 bytes  |  +0x02 flags
 *----------*
 | 4 bytes  |  +0x04 number of cylinders
 *----------*
 | 4 bytes  |  +0x08 number of heads/tracks
 *----------*
 | 4 bytes  |  +0x0c number of sectors per track
 *----------*
 | 8 bytes  |  +0x10 number of all sectors
 *----------*
 | 2 bytes  |  +0x18 sector size in bytes
 *----------*
 | 4 bytes  |  +0x1a pointer to EDD (Enhanced Disk Drives)
 *----------* 
 | 2 bytes  |  +0x1e 0x0BEDD - indicates presence of Device Path Information
 *----------*
 | 1 byte   |  +0x20 length of DPI with indicator
 *----------*
 | 1 byte   |  +0x21 reserved
 *----------*
 | 2 bytes  |  +0x22 reserved
 *----------*
 | 8 bytes  |  +0x24 host bus type ASCII
 *----------*
 | 8 bytes  |  +0x2c interface type ASCII
 *----------*
 | 8 bytes  |  +0x34 interface path
 *----------*
 | 8 bytes  |  +0x3c device path
 *----------*
 | 1 byte   |  +0x44 reserved
 *----------*
 | 1 byte   |  +0x45 checksum
 *----------*

More details one can find in EDD specifications [EDDS].

PoC code shows what I was looking for.

[bits 16]
[org 0x7c00]
; dl has drive number

    xor ax, ax
    mov ds, ax
    mov es, ax

    mov si, start_title
    call print

    mov si, drive_title
    call print

    xor ax, ax
    mov al, dl
    call print_value

    mov ah, 0x48
    mov si, dps
    int 0x13
    jc disk_error

    mov si, cylinders_title
    call print
    lea si, [dps+0x04]
    mov cx, 2
    call print_structure_part

    mov si, heads_title
    call print
    lea si, [dps+0x08]
    mov cx, 2
    call print_structure_part

    mov si, sectors_title
    call print
    lea si, [dps+0x0c]
    mov cx, 0x02
    call print_structure_part

    mov si, all_sectors_title
    call print
    lea si, [dps+0x10]
    mov cx, 4
    call print_structure_part

    mov si, sector_size_title
    call print
    mov ax, [dps+0x18]
    call print_value

    mov si, new_line
    call print
    lea si, [dps+0x24]
    call print

    jmp $

print_structure_part:
    mov dx, cx
    dec dx
    shl dx, 1
    add si, dx
    .loop:
    lodsw
    call print_value
    dec cx
    sub si, 0x04
    cmp cx, 0x00
    ja .loop
    ret

disk_error:
    push ax
    mov si, disk_error_msg
    call print
    pop ax
    call print_value
    jmp $

print:
    mov ah, 0x0e
    .repeat_print:
    lodsb
    cmp al, 0
    je .done_print
    int 0x10
    jmp .repeat_print
    .done_print:
    ret

print_value:
    push bx
    push cx
    push dx
    mov bh, 0
    mov bl, 15
    mov dx, ax
    mov cx, 0x10 ; we want to print 16-bit value
    .repeat_print_value:
    sub cx, 0x04
    mov ax, dx
    shr ax, cl
    and ax, 0x0f
    cmp ax, 0x0a
    jb .decimal_pritnt_value
    add al, char_A
    jmp .print_char_pritnt_value
    .decimal_pritnt_value:
    add al, char_0
    .print_char_pritnt_value:
    mov ah, 0x0e
    int 0x10
    cmp cx, 0
    je .done_print_value
    jmp .repeat_print_value
    .done_print_value:
    pop dx
    pop cx
    pop bx
    ret

char_0 equ 0x30
char_A equ 0x37

start_title         db "OS 0x03", 0xd, 0xa, 0x0
cylinders_title     db 0xd, 0xa, "Cylinders: ", 0x0
heads_title         db 0xd, 0xa, "Heads: ", 0x0
sectors_title       db 0xd, 0xa, "Sectors: ", 0x0
all_sectors_title   db 0xd, 0xa, "All sectors: ", 0x0
sector_size_title   db 0xd, 0xa, "Sector size: ", 0x0
drive_title         db 0xd, 0xa, "Drive num: ", 0x0
new_line            db 0xd, 0xa, 0x0
disk_error_msg      db "Disk error, could not read disk parameters", 0xd, 0xa, 0x0

dps     dw  0x0042,                         ; size of structure
        dw  0x0000,                         ; information flags
        dw  0x0000,0x0000,                  ; physical num of cylinders
        dw  0x0000,0x0000,                  ; physical num of heads
        dw  0x0000,0x0000,                  ; physical num of sectors
        dw  0x0000,0x0000,0x0000,0x0000,    ; absolute num of sectors
        dw  0x0000,                         ; bytes per sector
        dw  0x0000,0x0000                   ; optional pointer to EDD
        dw  0x0000                          ; 
        dw  0x0000,0x0000
        dw  0x0000,0x0000                   ; host bus type ASCII
        dw  0x0000,0x0000,0x0000,0x0000     ; interface type ASCII
        dw  0x0000,0x0000,0x0000,0x0000     ; interface path
        dw  0x0000,0x0000,0x0000,0x0000     ; device path
        dw  0x0000                          ; reserved + checksum

times (0x200 - 2 - ($ - $$)) db 0x00
dw 0xaa55

As always Makefile makes life easy. gdb_init_real_mode.txt I found at GitHub - mhugo/gdb_init_real_mode: GDB macros for real mode debugging.

all: os03.bin

os03.bin: os03.S
    yasm -f bin -o os03.bin os03.S

run: os03.bin
    qemu-system-i386 -drive format=raw,file=os03.bin

debug: os03.bin
    qemu-system-i386 -S -gdb tcp::8000 -drive format=raw,file=os03.bin &

    gdb \
        -ix gdb_init_real_mode.txt \
        -ex 'target remote localhost:8000' \
        -ex 'break *0x7c00' \
        -ex 'continue'

clean:
    rm -rf os03.bin

The bootloader produces this output:

OS 0x03

Drive num: 0080
Cylinders: 00000002
Heads: 00000010
Sectors: 0000003F
All sectors: 0000000000000001
Sector size: 0200
PCI ATA       

Calculating and conversion is nicely described at [WLBA].

References:

 

...SQUEAK!

Comments

Comments powered by Disqus