assemblyx86-16bootloaderbiosmbr

USB hard disk emulation cause a disk read to fail (BIOS int 13)?


Some background:

I'm working on a basic bootloader that reads a secondary bootloader into memory with the BIOS INT 13h AH=02h interrupt. I've got it working in the emulators (Virtualbox, Qemu, and Bochs).

Subsequently, I added a BPB (BIOS parameter block) to my bootloader, made a bootable USB, and tested it on my real machine with USB Floppy Emulation (which I set-up in the configuration screen of the BIOS of my real machine). It worked like a charm.

After testing the bootloader on my own machine, I tested it on another, newer machine. This new computer did not have a floppy emulation option in its BIOS configuration and therefore could not boot from the USB drive. So, following this osdev wikipage, I added a partition-table at the end of the MBR so that the newer machine could boot from the USB.

The Problem:

With the added partition table code, the bootloader fails to load the secondary bootloader into memory and the BIOS INT 13h fails. I have no clue why this might happen, as I've not changed any of the actual bootloader code. I just added the 64bit MBR partition table and reading data into memory fails instantly.

BPB (BIOS parameter block) & Disk accessing routine

bits    16 
org 0x7C00

jmp start
nop
;------------------------------------------;
;  Standard BIOS Parameter Block, "BPB".   ;
;------------------------------------------;
     bpbOEM         db  'MSDOS5.0'
     bpbSectSize    dw  512
     bpbClustSize   db  1
     bpbReservedSe  dw  1
     bpbFats        db  2
     bpbRootSize    dw  224
     bpbTotalSect   dw  2880
     bpbMedia       db  240
     bpbFatSize     dw  9
     bpbTrackSect   dw  18
     bpbHeads       dw  2
     bpbHiddenSect  dd  0
     bpbLargeSect   dd  0
     ;---------------------------------;
     ;  extended BPB for FAT12/FAT16   ;
     ;---------------------------------;
     bpbDriveNo     db  0
     bpbReserved    db  0
     bpbSignature   db  41            
     bpbID          dd  1
     bpbVolumeLabel db  'BOOT FLOPPY'
     bpbFileSystem  db  'FAT12   '

drive_n: db 0
start: 
    mov [drive_n], dl

    ; setup segments
    xor ax, ax
    mov ds, ax
    mov es, ax

    ; setup stack
    cli
    mov ss, ax
    mov sp, 0x7C00   ; stack will grow downward to lower adresses
    sti

    ; write start string
    mov si, start_str    ; start_str = pointer to "Bootloader Found..."
    call write_str       ; routine that prints string in si register to screen 

    ; read bootstrapper into memory
    mov dl, [drive_n]; drive number
    mov dh, 0x00    ; head (base = 0)
    mov ch, 0x00    ; track /cylinder = 0
    mov cl, 0x02    ; (1= bootloader, 2=start of bootstrapper
    mov bx, 0x7E00  ; location to load bootstrapper 
    mov si, 0x04    ; number of attempts

    ; attempt read 4 times 
  read_floppy:
    ; reset floppy disk
    xor ax, ax
    int 0x13

    ; check if attempts to read remain, if not, hlt system (jmp to fail_read)
    test    si, si
    je  fail_read   ; *** This jump happens only on real machines with 
    dec si          ; USB hard drive emulation ***

    ; attempt read 
    mov ah, 0x02    ; select read
    mov al, 0x0F    ; num sectors
    int     0x13
    jc  read_floppy

    ...             ; continue onward happily! (without any errors)

MBR Partition Table

; 0x1b4
db "12345678", 0x0, 0x0     ; 10 byte unique id

; 0x1be         ; Partition 1 -- create one big partition that spans the whole disk (2880 sectors, 1.44mb)
db 0x80         ; boot indicator flag = on

; start sector
db 0            ; starting head = 0
db 0b00000001   ; cyilinder = 0, sector = 1 (2 cylinder high bits, and sector. 00 000001 = high bits db 0x00)
db 0            ; 7-0 bits of cylinder (insgesamt 9 bits) 

; filesystem type
db 1            ; filesystem type = fat12

; end sector = 2880th sector (because a floppy disk is 1.44mb)
db 1            ; ending head = 1
db 18           ; cyilinder = 79, sector = 18 (2 cylinder high bits, and sector. 00 000001 = high bits db 0x00)
db 79           ; 7-0 bits of cylinder (insgesamt 9 bits) 

dd 0            ; 32 bit value of number of sectors between MBR and partition
dd 2880         ; 32 bit value of total number of sectors

; 0x1ce         ; Partition 2
times 16 db 0

; 0x1de         ; Partition 3
times 16 db 0

; 0x1ee         ; Parititon 4
times 16 db 0

; 0x1fe         ; Signature
dw  0xAA55

The Question

What causes a failure in reading the disk if and only if USB hard disk drive emulation is enabled in the BIOS? I've tried changing up the partition table and the BPB but nothing seems to work. I bet it has something to do with the difference in how the computer handles floppy vs. hard drive information but it's hard to find any info on that.

Any help would be greatly appreciated. I didn't intend for this question to be so long; it just accumulated.


Solution

  • TL;DR : In some situations the boot drive is not properly stored at label drive_n. This causes the disk read routine to fail on some hardware.


    I have a a Stackoverflow answer with a general set of bootloader tips. An important tip is this:

    When the BIOS jumps to your code you can't rely on CS,DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts. You can only be guaranteed that your bootloader will be loaded and run from physical address 0x00007c00 and that the boot drive number is loaded into the DL register.

    After your question was updated with more pertinent code with what happens before the read the issue becomes evident:

    drive_n: db 0
    start: 
        mov [drive_n], dl
    
        ; setup segments
        xor ax, ax
        mov ds, ax
        mov es, ax
    
        ; setup stack
        cli
        mov ss, ax
        mov sp, 0x7C00   ; stack will grow downward to lower adresses
        sti
    

    The problem is that mov [drive_n], dl is done before the segment registers are set up. mov [drive_n], dl is equivalent to mov [ds:drive_n], dl. The segment in DS matters. If the BIOS transfers control to your bootloader with a DS segment that isn't 0x0000 then mov [drive_n], dl will write the drive number to a memory location you don't expect.

    If the value of DS was not zero and the boot drive was something other than 0x00 then there was a good chance of failure. In cases where the real boot drive was stored to the wrong memory location, the initial value stored at the drive_n label would be used. In your case that was 0x00.

    In most cases you got lucky it worked. The resolution to this problem is simple. Ensure you write the value of DL to memory after you set up the segment registers (most notably DS). The code should look like:

    drive_n: db 0
    start: 
        ; setup segments
        xor ax, ax
        mov ds, ax
        mov es, ax
    
        ; setup stack
        cli
        mov ss, ax
        mov sp, 0x7C00   ; stack will grow downward to lower adresses
        sti
    
        mov [drive_n], dl