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

Hard drives are by far the most widely used mechanism
for data storage, and this tutorial will familiarize you with a
practical method for accessing them. In the past, a method known as
CHS was used. With CHS, you specified the cylinder, head, and sector
where your data was located. The problem with this method is that the
number of cylinders that could be addressed was rather limited. To
solve this problem, a new method for accessing hard drives was
created: Linear Block Addressing (LBA). With LBA, you simply specify
the address of the block you want to access. Blocks are 512-byte
chunks of data, so the first 512 bytes of data on the disk are in
block 0, the next 512 bytes are in block 1, etc. This is clearly
superior to having to calculate and specify three separate bits of
information, as with CHS. However, there is one hitch with LBA. There
are two forms of LBA, which are slightly different: LBA28 and LBA48.
LBA28 uses 28 bits to specify the block address, and LBA48 uses 48
bits. Most drives support LBA28, but not all drives support LBA48. In
particular, the Bochs emulator supports LBA28, and not LBA48. This
isn't a serious problem, but something to be aware of.(Bochs 2.3.7 added support for LBA48 - noted on 2011-01-28) Now that you
know how LBA works, it's time to see the actual methods involved.

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.

Now, you have to detect which drives are present on
each controller. To do this, you simply select the appropriate drive
with the drive/head select register (0x1F6 for the primary
controller, 0x176 for the secondary controller), wait a small amount
of time (I wait 1/250th of a second), and then read the
status register and see if the busy bit is set:

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.