Weโve covered the mechanisms in the abstract; now a real device โ the IDE disk โ makes them concrete.
36.8 Case Study: A Simple IDE Disk Driver
An IDE disk exposes four kinds of register (control, command-block,
status, error), read and written with x86 in/out at fixed I/O
addresses. The status register at 0x1F7 packs the driveโs state into
eight bits:
The IDE status register (0x1F7): one byte, eight state bits. BUSY and DRQ drive the protocol; ERROR flags a fault.
The command-block registers (also I/O addresses) carry the request:
| address | what it does | |
|---|---|---|
| Data | 0x1F0 | the data port โ read/write the sector bytes |
| Error / Features | 0x1F1 | error details (read when the STATUS ERROR bit is set) |
| Sector Count | 0x1F2 | how many sectors to transfer |
| LBA low / mid / hi | 0x1F3โ0x1F5 | the logical block address (which sectors) โ 3 bytes |
| Drive / head | 0x1F6 | drive select (master/slave) + top LBA bits |
| Command / Status | 0x1F7 | WRITE a command here to start; READ it for status |
| Control | 0x3F6 | reset the drive; enable/disable interrupts |
The protocol strings these together โ wait ready, write the parameters, start the command, and let an interrupt signal completion:
A poll: spin until the drive can accept a command. (This brief poll is fine even in an interrupt-driven driver โ the drive is ready almost at once.)
The xv6 driver implements exactly this in four functions (Fig 36.6):
// spin until the drive is ready to accept a command
static int ide_wait_ready() {
while (((int r = inb(0x1f7)) & IDE_BSY) || !(r & IDE_DRDY))
; // loop until not-busy and ready
}
// issue a request: write the registers (and data, if a write)
static void ide_start_request(struct buf *b) {
ide_wait_ready();
outb(0x3f6, 0); // enable interrupt
outb(0x1f2, 1); // one sector
outb(0x1f3, b->sector & 0xff); // LBA low ...
outb(0x1f4, (b->sector >> 8) & 0xff); // ... mid ...
outb(0x1f5, (b->sector >> 16) & 0xff);// ... hi
outb(0x1f6, 0xe0 | ((b->dev & 1) << 4) | ((b->sector >> 24) & 0x0f));
if (b->flags & B_DIRTY) {
outb(0x1f7, IDE_CMD_WRITE); // WRITE command
outsl(0x1f0, b->data, 512/4); // ...stream the data out
} else {
outb(0x1f7, IDE_CMD_READ); // READ command (no data yet)
}
}
// queue a request; sleep until it completes
void ide_rw(struct buf *b) {
acquire(&ide_lock);
/* ...append b to ide_queue... */
if (ide_queue == b) // if the queue was empty,
ide_start_request(b); // issue it now
while ((b->flags & (B_VALID|B_DIRTY)) != B_VALID)
sleep(b, &ide_lock); // block until the interrupt marks it done
release(&ide_lock);
}
// the interrupt handler: finish the request, wake the waiter, start the next
void ide_intr() {
acquire(&ide_lock);
if (!(b->flags & B_DIRTY) && ide_wait_ready() >= 0)
insl(0x1f0, b->data, 512/4); // if a READ, pull the data in
b->flags |= B_VALID; b->flags &= ~B_DIRTY;
wakeup(b); // wake the process in ide_rw
if ((ide_queue = b->qnext) != 0) // if more queued,
ide_start_request(ide_queue); // launch the next
release(&ide_lock);
}
Notice the shape: ide_rw queues and sleeps (using the concurrency
primitives from Part II โ a lock plus sleep/wakeup); ide_intr runs on
the hardware interrupt, does the finishing work, and wakes the
sleeper. Interrupts and DMA-style overlap, made real.
36.9 Historical Notes
Interrupts and DMA are ancient. Interrupt vectoring appears on the early-1950s UNIVAC; DMA-like designs on the DYSEAC (a โmobileโ machine haulable in a trailer) and IBM SAGE; vectored interrupts perhaps on the Lincoln Labs TX-2. The exact โwho first?โ is murky and maybe beside the point โ the ideas are obvious consequences of fast CPUs and slow devices: of course youโd let the CPU do other work while a slow I/O is pending. If youโd been there, you might have thought of them too.
36.10 Summary
You now know how an OS interacts with a device: two efficiency techniques โ the interrupt (overlap computation with I/O) and DMA (offload data copying) โ and two ways to reach device registers โ explicit I/O instructions and memory-mapped I/O. And the device driver encapsulates each deviceโs low-level specifics so the rest of the OS stays device-neutral. Next: the device this driver has been commanding all along โ the hard disk drive.
Check yourself: the IDE driver
1.Before issuing a command, ide_wait_ready() spins on the status register. What is it waiting for?
2.In ide_start_request, what does writing the command register (0x1F7 = READ or WRITE) accomplish?
3.What does ide_rw() do, and how does it use Part II's concurrency tools?
4.What does the interrupt handler ide_intr() do when the disk finishes?
5.How does the driver detect and diagnose an error?
6.The chapter summary lists two efficiency techniques and two register-access methods. What are they?