0
LBA HDD Access via PIO
Every operating system will eventually find a need for reliable, long-term storage. There are only a handful of commonly used storage devices:- Floppy
- Flash media
- CD-ROM
- Hard drive
To read a sector using LBA28:
- Send a NULL byte to port 0x1F1: outb(0x1F1, 0x00);
- Send a sector count to port 0x1F2: outb(0x1F2, 0x01);
- Send the low 8 bits of the block address to port 0x1F3: outb(0x1F3, (unsigned char)addr);
- Send the next 8 bits of the block address to port 0x1F4: outb(0x1F4, (unsigned char)(addr >> 8);
- Send the next 8 bits of the block address to port 0x1F5: outb(0x1F5, (unsigned char)(addr >> 16);
- Send the drive indicator, some magic bits, and highest 4 bits of the block address to port 0x1F6: outb(0x1F6, 0xE0 | (drive << 4) | ((addr >> 24) & 0x0F));
- Send the command (0x20) to port 0x1F7: outb(0x1F7, 0x20);
Do all the same as above, but send 0x30 for the command byte instead of 0x20: outb(0x1F7, 0x30);
To read a sector using LBA48:
- Send two NULL bytes to port 0x1F1: outb(0x1F1, 0x00); outb(0x1F1, 0x00);
- Send a 16-bit sector count to port 0x1F2: outb(0x1F2, 0x00); outb(0x1F2, 0x01);
- Send bits 24-31 to port 0x1F3: outb(0x1F3, (unsigned char)(addr >> 24));
- Send bits 0-7 to port 0x1F3: outb(0x1F3, (unsigned char)addr);
- Send bits 32-39 to port 0x1F4: outb(0x1F4, (unsigned char)(addr >> 32));
- Send bits 8-15 to port 0x1F4: outb(0x1F4, (unsigned char)(addr >> 8));
- Send bits 40-47 to port 0x1F5: outb(0x1F5, (unsigned char)(addr >> 40));
- Send bits 16-23 to port 0x1F5: outb(0x1F5, (unsigned char)(addr >> 16));
- Send the drive indicator and some magic bits to port 0x1F6: outb(0x1F6, 0x40 | (drive << 4));
- Send the command (0x24) to port 0x1F7: outb(0x1F7, 0x24);
Do all the same as above, but send 0x34 for the command byte, instead of 0x24: outb(0x1F7, 0x34);
Once you've done all this, you just have to wait for the drive to signal that it's ready:
while (!(inb(0x1F7) & 0x08)) {}
And then read/write your data from/to port 0x1F0:
// for read:
for (idx = 0; idx < 256; idx++)
{
tmpword = inw(0x1F0);
buffer[idx * 2] = (unsigned char)tmpword;
buffer[idx * 2 + 1] = (unsigned char)(tmpword >> 8);
}
// for write:
for (idx = 0; idx < 256; idx++)
{
tmpword = buffer[8 + idx * 2] | (buffer[8 + idx * 2 + 1] << 8);
outw(0x1F0, tmpword);
}
Of course, all of this is useless if you don't know what drives you actually have hooked up. Each IDE controller can handle 2 drives, and most computers have 2 IDE controllers. The primary controller, which is the one I have been dealing with thus-far has its registers located from port 0x1F0 to port 0x1F7. The secondary controller has its registers in ports 0x170-0x177. Detecting whether controllers are present is fairly easy:
- Write a magic value to the low LBA port for that controller (0x1F3 for the primary controller, 0x173 for the secondary): outb(0x1F3, 0x88);
- Read back from the same port, and see if what you read is what you wrote. If it is, that controller exists.
outb(0x1F6, 0xA0); // use 0xB0 instead of 0xA0 to test the second drive on the controller
sleep(1); // wait 1/250th of a second
tmpword = inb(0x1F7); // read the status port
if (tmpword & 0x40) // see if the busy bit is set
{
printf("Primary master exists\n");
}
And that about wraps it up. Note that I haven't actually tested my LBA48 code, because I'm stuck with Bochs, which only supports LBA28. It should work, according to the ATA specification.
0Awesome Comments!