Reading data from HDD without BIOS interrupts DRQ bit not setting

1.2k Views Asked by At

IMPORTANT: this code will render media unusable without formatting or recovery. Use unused, empty, or unneeded media.

It should be straightforward to replicate BIOS interrupt functions' behavior but the information about it online is lackluster on the real life use scenarios. So I wrote code to read 2.5" hdd drive identification info, which is a function of int 13h. I get a hang at the stage of checking for the DRQ (data ready for query?) bit of the 0x1f7 status register. When I comment out the check-DRQ loop the code proceeds but the data is all 0s, possibly because it was not read. I've tried varying slave and master bit, and the primary/secondary bus... The secondary bus goes through the DRQ check but the data seems to be all 1s. Importantly, int 13h on the same drive and the same machine works fine. Does anyone know if there's a way to: a) know ide location (primary/secondary, master/slave) of the hdd that is booting, and then b) test it's there without reading from it, and c) figure out how to get the DRQ bit to green? Will error register be meaningful before DRQ ones out?

Interestingly, BIOS shows me 3 HDDs aside from USB, DVD, and LAN entries, but I only have two slots for hard drives. And I can never plug-in my HDD so that it's HDD1. Bizarre. Could this be related?

Here's my bootable code to read from ATA HDD primary master using control registers and to print several bytes of it on screen as binary numbers:

[bits 16]
[org 0x7c00]
xor ax, ax
cli
mov dx, 0x1f7
m1:
in al, dx
test al, 010000000b
jnz m1
mov dx, 0x1f6
mov al, 0xE0       ; LBA mode - not needed for this?
out dx,al
mov dx, 0x1f7
m2:
in al, dx
test al, 010000000b     ; wait for BSY to be 0
jnz m2
test al, 001000000b     ; wait for DRDY to be 1
jz m2
mov dx, 0x1f7
mov al, 0xEC          ; identify drive command
out dx, al
mov dx, 0x1f7
m3:
in al, dx
test al, 010000000b
jnz m3
mov dx, 0x1f7
m4:
in al, dx
test al, 000001000b    ; this test for DRQ never turns 1
jz m4
mov ax, 0
mov es, ax
mov di, 0x7e00       ;save the identification to 0x7e00
mov dx, 0x1f0
mov cx, 256
rep insw
mov ax, 0xb800     ; display in table of binary bytes
mov es, ax
mov di, 0
mov ax, 0
mov ds, ax
mov si, 0x7e00     ;starting RAM address to read
mov cx, 0
mov dl, 20      ;how many lines to print
morelines:
mov bh, 7      ;how many bytes per line
morebytes:      
mov ah, [ds:si]
mov bl, 8       ;counter for bits of each byte to print
morebits:
shl ah, 1
mov al, 48
jnc zero
mov al, 49
zero:
mov [es:di], al
inc di
inc di
dec bl
jnz morebits
mov al, 32
mov [es:di], al
inc di
inc di
inc si
dec bh
jnz morebytes
add cx, 80*2
mov di, cx
dec dl
jnz morelines
times 510 - ($ - $$) db 0
dw 0xaa55
2

There are 2 best solutions below

7
On

You may be working with the DVD drive plugged in the SATA0 port instead of HDD. In this case, command 0xA1 (IDENTIFY PACKET DEVICE) will return the identification data.

For SATA1 - SATA3 ports, try setting bit 4 (Drive Select) of register 0x1F6, or using registers at 0x170 - 0x177 as suggested by @rcgldr.

6
On

It should be straightforward to replicate BIOS interrupt functions' behavior but the information about it online is lackluster on the real life use scenarios.

No, definitely not.

For example, to recreate the (relatively horrible and broken due to historical limitations and "waste CPU time polling for IO completion when there's better things CPU can do" design idiocy) int 0x13 interface you will need code for:

  • legacy/ISA floppy controllers, floppy drives, tape drives
  • legacy/ISA "parallel ATA" controllers, ATA disk drives and ATAPI CD-ROMs
  • about 50 different legacy/ISA "parallel ATA" RAID controllers
  • 80 different legacy/ISA SCSI controllers
  • 3 different USB controllers, USB floppy drives, USB mass storage devices
  • SATA/AHCI
  • about 20 different PCI SCSI controllers
  • about 30 different PCI (SATA, SAS) RAID controllers
  • NVMe
  • a bunch of device enumeration code (e.g. to scan PCI buses and figure out which devices there are, etc)
  • everything all of the above depends on (e.g. memory management, IRQ handling, etc)

This adds up to more code than will fit in a 512 byte boot loader, which begs the question "If you can use BIOS to load the code needed to handle the device, why can't you use BIOS to load the code needed to start a kernel and normal device drivers that don't suck?".

Note that for most of these cases (e.g. all SCSI, all RAID) the BIOS just uses a ROM provided by the device manufacturer and included in their ISA/PCI card so that the BIOS itself doesn't have any code of its own for device.