1

I’m trying to develop a bootloader that simply scans the root directory of its own boot medium (Floppy with FAT16) for a file and jumps to it. I finally ran into a problem that I found nowhere online and I feel like I’ve done something wrong: At the start of my code, when I read the root directory of the drive using INT 0x13, the carry flag is being set and after I made it print out the error code that comes in AH, I got 0x80 which seems to correspond to a disk timeout. I already tried hardcoding DL to 0x00 (Floppy #1 ‒ same as before), 0x01 (Floppy #2 ‒ AH=0x01 Illegal function) and 0x80 (Hard disk #1 ‒ there actually was data, but as expected, not the one from the floppy image). I also tried hardcoding the calculating of parameters and I tried only reading one sector. Below is the code that the error seems to be happening in:

    BITS 16

    jmp short bootload
    nop

    ; Drive parameters

bootload:
    ; Segment registers
    mov ax, 0x07C0+544
    cli
        mov ss, ax
        mov sp, 4096
    sti

    mov ax, 0x07C0
    mov ds, ax
    mov es, ax

    ; Boot device
    mov [bootdev], dl

    ; Calculations (I just hardcoded them in this example to make it easier to understand)
    mov byte [rootdirsize], 14
    mov byte [rootdirchssec], 1
    mov word [rootdirchstrack], 1

    ; Read sectors
    mov ah, 0x02                        ; Read sectors
    mov al, byte [rootdirsize]          ; The amount of sectors needed by the root dir entries
                                        ; (RootDirEntries / 16)
    mov dl, byte [bootdev]
    mov dh, 0                           ; Heads are ignored... yet
    mov cl, byte [rootdirchssec]        ; Sector number of the root dir in CHS
    and cx, 0b0000_0000_0011_1111       ; Sector only uses bits 0-5 of CX
    mov bx, word [rootdirchstrack]      ; Track number of the root dir in CHS
    shl bx, 6                           ; Track uses bits 6-15 of CX
    or cx, bx                           ; Transfer to CX
    mov bx, 0x0100                      ; Segment where it is loaded
    mov es, bx
    mov bx, 0                           ; Offset = 0
    int 0x13

    jc disk_error                       ; CF = error

    jmp $                               ; the rest of the bootloader

disk_error:
    mov al, ah                          ; AH is the error code
    mov ah, 0x0E                        ; print it
    int 0x10                            ; returns 'Ç' = 0x80 = timeout

    jmp $

data:
    bootdev         db 0
    rootdirsec      dw 0
    rootdirchssec   db 0
    rootdirchstrack dw 0
    rootdirsize     db 0

    times 510-($-$$) db 0
    dw 0xAA55

The real code is of course much longer, I tried to write only the parts essential to the problem. Other details that may help:

  • I’m using NASM
  • I’m testing on VMWare Workstation with a virtual floppy
  • Other code works fine (for example, printing stuff or interacting with the keyboard)
  • I did multiple snapshots to inspect the virtual memory with a hex editor, the disk data (apart from the bootcode) was never loaded into memory
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
qqa12
  • 13
  • 4
  • 2
    Did you try single-stepping through your code with a debugger? The BOCHS emulator has a debugger built-in; some others let you attach GDB. If your setup doesn't make debugging possible, get a new setup; a debugger makes it vastly easier to catch silly mistakes and notice wrong assumptions without having to think of them first and write debug-prints to check. – Peter Cordes Apr 30 '20 at 17:28
  • mov al, [dont read any sectors whatsoever]. If your loading to 100:0, thats the same as 0:1000. When you read dl=2, and you saw the data "loaded" was it not just the data loaded by bios at POST – Stephen Duffy Apr 30 '20 at 18:48
  • @MichaelPetch thank you, this actually solved the problem! The reason I messed this up is probably that you can think of a border between CH and CL, and I assumed this border was just off by two bits to allow a higher value on one side. Would you mind posting this as the answer so I can accept it? – qqa12 May 01 '20 at 15:39

1 Answers1

1

You are correct that the Cylinders (Tracks) can be a 10-bit number, and the sector number to read is 6 bits. Both are packed into a 16-bit register (CX) for the int 0x13 BIOS disk read call. 10-bit Cylinder numbers only apply to hard disk type media (or anything emulated as as a hard drive). With floppy media Cylinders are limited to 8-bit values (0-255) and the sector number is still limited to values between 1-63 (6 bits).

You load a 16-bit word containing the cylinders into BX to perform calculations. You shift BX left by 6 bits. This places the lower 2 bits of the Cylinder count into the upper 2 bits of BL and the upper 8 bits into the BH register. This isn't how the Cylinder number is encoded. The documentation for INT 13h/AH=2 says:

CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)

This says that it is the upper 2 bits of the Cylinder number that must be stored in the upper 2-bits of CL, and the sector number is the lower 6 bits of CL. CH contains the lower 8 bits of the Cylinder number.

To fix this you could change these lines:

mov bx, word [rootdirchstrack]  ; Track number of the root dir in CHS
shl bx, 6                       ; Track uses bits 6-15 of CX
or cx, bx                       ; Transfer to CX

to something similar to:

mov bx, word [rootdirchstrack]  ; Track*Cylinder) number of the root dir in CHS    
xchg bl, bh                     ; Place lower 8 bits of Cylinder in BH
                                ; Upper 2 bits of Cylinder are now the lower 2 bits of BL
ror bl, 2                       ; Rotate the lower 2 bits into the upper 2 bits of BL
or cx, bx                       ; Transfer to CX already containing sec # in lower 6 bits
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • The "high two bits of cylinder (bits 6-7, hard disk only)" are hard disk only. This is for floppy (no partitions, not using "int 0xx13 extensions") so those bits don't exist. You can literally do `mov cl,[rootdirchssec]` and `mov ch,[rootdirchstrack]` if it's a single-sided floppy disk. For double sided floppy disk you probably need to get the lowest bit of `[rootdirchstrack]` shifted into `dh` (like maybe `mov cx,[rootdirchstrack]`, `mov dh,cl`, `shl cx,8-1`, `and dl,1`). – Brendan May 02 '20 at 00:10
  • @Brendan, that is entirely true. In fact I relooked at the question and only noticed "floppy" after your comment. However there is still value in this if he tries to move to a medium that is not a floppy (using something like FAT16). – Michael Petch May 02 '20 at 00:13
  • For boot loaders like this, you have to rewrite them for anything else (all hard disks have partitions and almost all need "int 0x13 extensions" using LBA because they're too big for CHS to work). I should probably also mention that on real floppy drives you want to retry 3+ times if there's errors because often there's "tried to read before disk is spinning at right speed" problems, and the media is relatively unreliable so retrying a few extra times if there's read errors can help. – Brendan May 02 '20 at 00:20
  • @Brendan I have an answer [elsewhere on here](https://stackoverflow.com/a/57732963/3857942) that discusses real floppies and retries, which is outside of the scope of what ails this particular question. The OP pointed out in their question that they had stripped down their code to produce an [mcve] so I'm not really concerned beyond the actual primary bug causing their issue because they are providing only test code to reproduce. – Michael Petch May 02 '20 at 00:22