diff --git a/src/ata.c b/src/ata.c new file mode 100644 index 0000000..658b15c --- /dev/null +++ b/src/ata.c @@ -0,0 +1,199 @@ +#include "ata.h" +#include "ports.h" + +#define ataRegData 0x1F0 +#define ataRegError 0x1F1 +#define ataRegSectorCnt 0x1F2 +#define ataRegLbaLow 0x1F3 +#define ataRegLbaMid 0x1F4 +#define ataRegLbaHigh 0x1F5 +#define ataRegDriveHead 0x1F6 +#define ataRegStatus 0x1F7 +#define ataRegCommand 0x1F7 +#define ataRegControl 0x3F6 + +#define ataStatusErr 0x01 +#define ataStatusDrq 0x08 +#define ataStatusDf 0x20 +#define ataStatusBsy 0x80 + +#define ataCmdReadSectors 0x20 +#define ataCmdWriteSectors 0x30 +#define ataCmdCacheFlush 0xE7 +#define ataCmdIdentify 0xEC + +#define ataMaxSectorsPerCommand 255 +#define ataPollTimeout 1000000u + +static bool diskPresent; +static uint64 totalSectors; +static char modelBuffer[41]; + +static void ataIoDelay(void) +{ + /* Selecting a drive needs ~400ns to settle; reading the alternate + status register is a documented no-op read that burns exactly that + long without disturbing the main status register. */ + inb(ataRegControl); + inb(ataRegControl); + inb(ataRegControl); + inb(ataRegControl); +} + +static bool ataWaitClear(uint8 mask) +{ + for (uint32 i = 0; i < ataPollTimeout; i++) + if (!(inb(ataRegStatus) & mask)) + return true; + return false; +} + +static bool ataWaitDrqOrErr(bool* err) +{ + for (uint32 i = 0; i < ataPollTimeout; i++) { + uint8 status = inb(ataRegStatus); + if (status & (ataStatusErr | ataStatusDf)) { + *err = true; + return true; + } + if (status & ataStatusDrq) { + *err = false; + return true; + } + } + return false; +} + +static void ataSelect(uint64 lba, uint8 count) +{ + outb(ataRegDriveHead, (uint8)(0xE0 | ((lba >> 24) & 0x0F))); + ataIoDelay(); + outb(ataRegSectorCnt, count); + outb(ataRegLbaLow, (uint8)(lba & 0xFF)); + outb(ataRegLbaMid, (uint8)((lba >> 8) & 0xFF)); + outb(ataRegLbaHigh, (uint8)((lba >> 16) & 0xFF)); +} + +bool ataInit(void) +{ + diskPresent = false; + totalSectors = 0; + modelBuffer[0] = 0; + + outb(ataRegControl, 0x02); /* nIEN: this driver only ever polls */ + + outb(ataRegDriveHead, 0xA0); /* master, no chs/lba bits needed yet */ + ataIoDelay(); + outb(ataRegSectorCnt, 0); + outb(ataRegLbaLow, 0); + outb(ataRegLbaMid, 0); + outb(ataRegLbaHigh, 0); + outb(ataRegCommand, ataCmdIdentify); + + if (inb(ataRegStatus) == 0) + return false; /* floating bus: no drive wired up on this channel */ + + if (!ataWaitClear(ataStatusBsy)) + return false; + + /* A plain PATA hard disk leaves LBA mid/high at 0 after IDENTIFY; an + ATAPI or SATA-bridge device stamps a signature there instead. This + driver only supports the former. */ + if (inb(ataRegLbaMid) != 0 || inb(ataRegLbaHigh) != 0) + return false; + + bool err = false; + if (!ataWaitDrqOrErr(&err) || err) + return false; + + uint16 identifyWords[256]; + for (int i = 0; i < 256; i++) + identifyWords[i] = inw(ataRegData); + + totalSectors = (uint64)identifyWords[60] | ((uint64)identifyWords[61] << 16); + + for (int i = 0; i < 20; i++) { + uint16 word = identifyWords[27 + i]; + modelBuffer[i * 2] = (char)(word >> 8); + modelBuffer[i * 2 + 1] = (char)(word & 0xFF); + } + modelBuffer[40] = 0; + for (int i = 39; i >= 0 && modelBuffer[i] == ' '; i--) + modelBuffer[i] = 0; + + diskPresent = totalSectors > 0; + return diskPresent; +} + +bool ataPresent(void) +{ + return diskPresent; +} + +uint64 ataSectorCount(void) +{ + return totalSectors; +} + +const char* ataModelString(void) +{ + return modelBuffer; +} + +bool ataReadSectors(uint64 lba, uint32 count, void* buffer) +{ + if (count == 0) + return true; + if (!diskPresent || count > ataMaxSectorsPerCommand || lba + count > totalSectors) + return false; + + if (!ataWaitClear(ataStatusBsy)) + return false; + ataSelect(lba, (uint8)count); + outb(ataRegCommand, ataCmdReadSectors); + + uint16* dst = (uint16*)buffer; + for (uint32 s = 0; s < count; s++) { + if (!ataWaitClear(ataStatusBsy)) + return false; + bool err = false; + if (!ataWaitDrqOrErr(&err) || err) + return false; + for (int w = 0; w < 256; w++) + dst[w] = inw(ataRegData); + dst += 256; + } + return true; +} + +bool ataWriteSectors(uint64 lba, uint32 count, const void* buffer) +{ + if (count == 0) + return true; + if (!diskPresent || count > ataMaxSectorsPerCommand || lba + count > totalSectors) + return false; + + if (!ataWaitClear(ataStatusBsy)) + return false; + ataSelect(lba, (uint8)count); + outb(ataRegCommand, ataCmdWriteSectors); + + const uint16* src = (const uint16*)buffer; + for (uint32 s = 0; s < count; s++) { + if (!ataWaitClear(ataStatusBsy)) + return false; + bool err = false; + if (!ataWaitDrqOrErr(&err) || err) + return false; + for (int w = 0; w < 256; w++) + outw(ataRegData, src[w]); + src += 256; + ataIoDelay(); + } + + outb(ataRegCommand, ataCmdCacheFlush); + if (!ataWaitClear(ataStatusBsy)) + return false; + + return true; +}