ATA/IDE in PIO Mode
An ATA (IDE) drive accessed in PIO (Programmed I/O) mode, through the legacy ports 0x1F0–0x1F7, is still the most common starting point for a first storage driver, since it needs no DMA setup and no command-list structures at all, unlike the AHCI interface documented separately, which is what any drive exposed this way actually is on hardware built in the last two decades: there is no physical PIO-only controller left to target, only AHCI hardware a driver can additionally address through its own legacy-compatible PIO-style register mapping.
The register set
Section titled “The register set”The primary ATA bus exposes eight consecutive I/O ports starting at 0x1F0, each with a different meaning depending on whether it’s read or written.
#define ATA_DATA 0x1F0 // 16-bit data port#define ATA_ERROR 0x1F1 // read: error code from the last command#define ATA_SECCOUNT 0x1F2 // sector count for the next command#define ATA_LBA_LO 0x1F3#define ATA_LBA_MID 0x1F4#define ATA_LBA_HI 0x1F5#define ATA_DRIVE_HEAD 0x1F6 // drive select + LBA bits 24-27#define ATA_STATUS 0x1F7 // read: status; write: commandATA_DRIVE_HEAD selects which of the two drives a controller can address (master or slave, one bit in this same register) alongside the top four bits of a 28-bit LBA address, and ATA_STATUS doubles as the command register on write: sending a command byte here (0x20 for a PIO sector read, for instance) is what actually starts the drive acting on whatever the other registers were just filled in with.
IDENTIFY
Section titled “IDENTIFY”Before a driver can safely read or write anything, it needs to know what’s actually attached to a given drive slot at all, and whether that device is an ATA hard drive or an ATAPI device (a CD/DVD drive, most commonly) speaking a related but distinct command set through the same ports. The IDENTIFY command (0xEC) answers both questions at once: issued with ATA_SECCOUNT and the LBA registers all zeroed first, a subsequent read of ATA_STATUS returning zero means no drive is present at all, while ATA_LBA_MID/ATA_LBA_HI returning specific non-zero signature values (rather than the zero an ATA drive leaves there) identifies the device as ATAPI instead, at which point a driver should issue IDENTIFY PACKET DEVICE (0xA1) rather than continuing with the ATA-specific command. A successful IDENTIFY response is 256 words of data read from ATA_DATA, encoding the drive’s total addressable sector count, whether it supports 48-bit LBA addressing beyond the 28-bit register set’s own limit, and a human-readable model string, among many other fields a driver typically only needs a handful of.
Reading a sector: the polling cycle
Section titled “Reading a sector: the polling cycle”A PIO read has no interrupt-driven or DMA-driven completion signal by default; the driver instead polls ATA_STATUS in a tight loop, watching two specific bits: BSY (bit 7), set for as long as the drive is busy processing the command internally, and DRQ (bit 3), set once the drive has data ready and waiting in its own internal buffer for the host to actually read out.
void ata_wait_ready(void) { while (inb(ATA_STATUS) & 0x80) { } // wait for BSY to clear}
void ata_read_sector(uint32_t lba, uint16_t *buffer) { outb(ATA_DRIVE_HEAD, 0xE0 | ((lba >> 24) & 0x0F)); outb(ATA_SECCOUNT, 1); outb(ATA_LBA_LO, lba & 0xFF); outb(ATA_LBA_MID, (lba >> 8) & 0xFF); outb(ATA_LBA_HI, (lba >> 16) & 0xFF); outb(ATA_STATUS, 0x20); // READ SECTORS command
ata_wait_ready(); while (!(inb(ATA_STATUS) & 0x08)) { } // wait for DRQ
for (int i = 0; i < 256; i++) buffer[i] = inw(ATA_DATA); // 256 words = 512 bytes}Once DRQ sets, exactly 256 sixteen-bit words, one sector’s worth of data at the standard 512-byte sector size, have to be read from ATA_DATA in that same tight sequence, since the drive’s internal buffer expects to be drained completely before it will report readiness for whatever comes next: reading fewer than 256 words and moving on to a different command leaves the drive’s buffer state out of sync with what the driver believes has actually been transferred.
Implementation notes
Section titled “Implementation notes”Every register access in this scheme costs a full port I/O round trip, and every sector’s data additionally has to pass through the CPU one word at a time via ATA_DATA, rather than a DMA engine moving it directly between the drive and memory the way AHCI does; this is precisely the performance limitation that motivated AHCI’s DMA-based command model, and PIO remains useful mainly as a simple, dependency-free path to get a first read working rather than as a design a kernel is expected to keep using once basic disk I/O is confirmed correct. A driver targeting both the primary bus (0x1F0-range) and the secondary bus (conventionally 0x170-range, with an analogous register layout) has to track BSY/DRQ state independently per bus, since the two buses, and the up to two drives on each, don’t share status between them despite superficially identical register semantics.
References
Section titled “References”- ^ T13 Technical Committee, ATA/ATAPI Command Set (ACS): the formal specification for the command set and register layout described above.
See also
Section titled “See also”- AHCI: the DMA-based successor that solves PIO polling’s performance limitation directly, and the only interface modern hardware physically exposes.
- Networking: a contrasting driver model built around descriptor rings rather than per-command polling.
- Floppy Disk Controller: an even more legacy storage driver sharing this article’s polling-based, register-level approach.