My first ATA implementation was poorly designed, written when I could barely comprehend the standard's language. My goal then was solely to get it working. It could only address the first 256 sectors of the hard drive, wrapping around to the first sector, limiting me to 128 KB.
Now that I understand the standard, taking the time to piece it apart, I've designed a proper implementation that handles each step correctly. This page will document how the addressing is implemented.
LBA treats the disk as an array of 512 byte sectors with 28 bit indexes. These indexes are passed to the disk controller as separate 8 bit numbers.
There are 4 ports (device registers mapped to the io space) relevant to LBAs, each of which are 8 bits wide.
Note: a byte is 8 bits while a nibble is 4 bits.
| Name | Port | 7 | 6 | 5 | 4 | 3-0 | |||
|---|---|---|---|---|---|---|---|---|---|
| Sector Number | 1F3 | lowest byte of LBA | |||||||
| Cylinder Low | 1F4 | 2nd lowest byte of LBA | |||||||
| Cylinder High | 1F5 | 3rd lowest byte of LBA | |||||||
| Device/Head | 1F6 | _ | mode | _ | device | highest nibble of LBA | |||
The device/head port high nibble has two relevant bits: mode and device. The mode bit is always set to 1 for LBA mode, while "device" selects one of the 2 ATA devices, usually 0 when you only have one HDD.
While 28 bits are available, in real mode I can only pass around 16 bit numbers. But this can be alleviated by choosing a denser block size than 512 bytes. I prefer 1024 bytes (1k) because it makes estimating usage a trivial task and its a nice number to work with. 16 bits can index 64 MB (64k * 1k), easily doubling the addressable space in contrast to using sectors (32 MB).
I'm far more than content with only 64 MB of disk space, so 17 bit sector addressing to support 16 bit block addressing will be perfect. This will only necessitate updating three of the device registers.
: SECTOR! 1F3 out ; : LOW! 1F4 out ; : HIGH! 1F5 out ; : BLOCK! dup 2* SECTOR! 7 >> dup LOW! 8 >> HIGH! ;
This alone does have a limitation exposed if the driver needs to index the second sector of a block. But I solved this with an extra word to increment the Sector Number port.
: 2nd 1F3 in 1+ SECTOR! ;
From what I've read of the standard and testing with my previous driver, the address used in either of the read/write commands remains in the registers and can be reused, even when an error occurs.