| 1 |
/* ============================================================================ |
| 2 |
* Pactor64 — ATA PIO (Programmed I/O) Block Device Driver |
| 3 |
* Primary ATA controller: ports 0x1F0-0x1F7, 0x3F6 |
| 4 |
* ============================================================================ */ |
| 5 |
#include "../include/pactor64.h" |
| 6 |
|
| 7 |
/* === ATA I/O Ports (Primary Bus) === */ |
| 8 |
#define ATA_DATA 0x1F0 /* Data register (R/W, 16-bit) */ |
| 9 |
#define ATA_ERROR 0x1F1 /* Error register (R) */ |
| 10 |
#define ATA_FEATURES 0x1F1 /* Features register (W) */ |
| 11 |
#define ATA_SECCOUNT 0x1F2 /* Sector count register */ |
| 12 |
#define ATA_LBA_LO 0x1F3 /* LBA low byte */ |
| 13 |
#define ATA_LBA_MID 0x1F4 /* LBA mid byte */ |
| 14 |
#define ATA_LBA_HI 0x1F5 /* LBA high byte */ |
| 15 |
#define ATA_DRIVE_HEAD 0x1F6 /* Drive/Head register */ |
| 16 |
#define ATA_STATUS 0x1F7 /* Status register (R) */ |
| 17 |
#define ATA_COMMAND 0x1F7 /* Command register (W) */ |
| 18 |
#define ATA_ALT_STATUS 0x3F6 /* Alternate status (R) */ |
| 19 |
#define ATA_DEV_CTRL 0x3F6 /* Device control (W) */ |
| 20 |
|
| 21 |
/* === ATA Status Bits === */ |
| 22 |
#define ATA_SR_BSY 0x80 /* Busy */ |
| 23 |
#define ATA_SR_DRDY 0x40 /* Drive ready */ |
| 24 |
#define ATA_SR_DF 0x20 /* Drive write fault */ |
| 25 |
#define ATA_SR_DSC 0x10 /* Drive seek complete */ |
| 26 |
#define ATA_SR_DRQ 0x08 /* Data request ready */ |
| 27 |
#define ATA_SR_CORR 0x04 /* Corrected data */ |
| 28 |
#define ATA_SR_IDX 0x02 /* Index */ |
| 29 |
#define ATA_SR_ERR 0x01 /* Error */ |
| 30 |
|
| 31 |
/* === ATA Commands === */ |
| 32 |
#define ATA_CMD_IDENTIFY 0xEC |
| 33 |
#define ATA_CMD_READ 0x20 |
| 34 |
#define ATA_CMD_WRITE 0x30 |
| 35 |
|
| 36 |
/* === Global State === */ |
| 37 |
static int ata_drive_exists = 0; |
| 38 |
static char ata_model[41]; |
| 39 |
static char ata_serial[21]; |
| 40 |
static uint32_t ata_sectors = 0; |
| 41 |
|
| 42 |
/* === Helper: Wait for drive to be not busy === */ |
| 43 |
static void ata_wait_bsy(void) { |
| 44 |
while (inb(ATA_STATUS) & ATA_SR_BSY) |
| 45 |
; |
| 46 |
} |
| 47 |
|
| 48 |
/* === Helper: Wait for DRQ (data ready) === */ |
| 49 |
static int ata_wait_drq(void) { |
| 50 |
uint8_t status; |
| 51 |
/* Wait up to ~1 second (poll ~1M times) */ |
| 52 |
for (int i = 0; i < 1000000; i++) { |
| 53 |
status = inb(ATA_STATUS); |
| 54 |
if (status & ATA_SR_ERR) return -1; |
| 55 |
if (status & ATA_SR_DF) return -1; |
| 56 |
if (status & ATA_SR_DRQ) return 0; |
| 57 |
} |
| 58 |
return -1; /* timeout */ |
| 59 |
} |
| 60 |
|
| 61 |
/* === Helper: 400ns delay via reading alternate status === */ |
| 62 |
static void ata_delay_400ns(void) { |
| 63 |
inb(ATA_ALT_STATUS); |
| 64 |
inb(ATA_ALT_STATUS); |
| 65 |
inb(ATA_ALT_STATUS); |
| 66 |
inb(ATA_ALT_STATUS); |
| 67 |
} |
| 68 |
|
| 69 |
/* === Initialize ATA controller and detect drive === */ |
| 70 |
void ata_init(void) { |
| 71 |
kprintf("[ata] Initializing primary ATA controller...\n"); |
| 72 |
|
| 73 |
/* Select drive 0 (master) on primary bus */ |
| 74 |
outb(ATA_DRIVE_HEAD, 0xA0); /* LBA mode, drive 0 */ |
| 75 |
ata_delay_400ns(); |
| 76 |
|
| 77 |
/* Soft reset */ |
| 78 |
outb(ATA_DEV_CTRL, 0x04); /* Set SRST bit */ |
| 79 |
ata_delay_400ns(); |
| 80 |
outb(ATA_DEV_CTRL, 0x00); /* Clear SRST */ |
| 81 |
ata_delay_400ns(); |
| 82 |
|
| 83 |
/* Wait for drive to be ready */ |
| 84 |
ata_wait_bsy(); |
| 85 |
|
| 86 |
/* Check if drive exists by reading status */ |
| 87 |
uint8_t status = inb(ATA_STATUS); |
| 88 |
if (status == 0x00 || status == 0xFF) { |
| 89 |
kprintf("[ata] No drive detected (status=0x%x)\n", status); |
| 90 |
return; |
| 91 |
} |
| 92 |
|
| 93 |
kprintf("[ata] Drive detected, status=0x%x\n", status); |
| 94 |
|
| 95 |
/* Send IDENTIFY command */ |
| 96 |
outb(ATA_DRIVE_HEAD, 0xA0); /* Drive 0, LBA */ |
| 97 |
outb(ATA_SECCOUNT, 0); |
| 98 |
outb(ATA_LBA_LO, 0); |
| 99 |
outb(ATA_LBA_MID, 0); |
| 100 |
outb(ATA_LBA_HI, 0); |
| 101 |
outb(ATA_COMMAND, ATA_CMD_IDENTIFY); |
| 102 |
|
| 103 |
/* Wait for response */ |
| 104 |
ata_delay_400ns(); |
| 105 |
status = inb(ATA_STATUS); |
| 106 |
|
| 107 |
if (status == 0x00) { |
| 108 |
kprintf("[ata] Drive does not support IDENTIFY\n"); |
| 109 |
return; |
| 110 |
} |
| 111 |
|
| 112 |
/* Wait for BSY to clear */ |
| 113 |
ata_wait_bsy(); |
| 114 |
|
| 115 |
/* Check LBA mid/high for ATAPI detection */ |
| 116 |
if (inb(ATA_LBA_MID) != 0 || inb(ATA_LBA_HI) != 0) { |
| 117 |
kprintf("[ata] Not an ATA drive (ATAPI?)\n"); |
| 118 |
return; |
| 119 |
} |
| 120 |
|
| 121 |
/* Wait for DRQ */ |
| 122 |
if (ata_wait_drq() != 0) { |
| 123 |
kprintf("[ata] IDENTIFY timeout\n"); |
| 124 |
return; |
| 125 |
} |
| 126 |
|
| 127 |
/* Read 256 words (512 bytes) of IDENTIFY data */ |
| 128 |
uint16_t identify_data[256]; |
| 129 |
for (int i = 0; i < 256; i++) { |
| 130 |
identify_data[i] = inw(ATA_DATA); |
| 131 |
} |
| 132 |
|
| 133 |
/* Extract model string (words 27-46, 40 bytes) */ |
| 134 |
for (int i = 0; i < 20; i++) { |
| 135 |
ata_model[i * 2] = (char)(identify_data[27 + i] >> 8); |
| 136 |
ata_model[i * 2 + 1] = (char)(identify_data[27 + i] & 0xFF); |
| 137 |
} |
| 138 |
ata_model[40] = '\0'; |
| 139 |
|
| 140 |
/* Trim trailing spaces from model */ |
| 141 |
for (int i = 39; i >= 0 && ata_model[i] == ' '; i--) |
| 142 |
ata_model[i] = '\0'; |
| 143 |
|
| 144 |
/* Extract serial string (words 10-19, 20 bytes) */ |
| 145 |
for (int i = 0; i < 10; i++) { |
| 146 |
ata_serial[i * 2] = (char)(identify_data[10 + i] >> 8); |
| 147 |
ata_serial[i * 2 + 1] = (char)(identify_data[10 + i] & 0xFF); |
| 148 |
} |
| 149 |
ata_serial[20] = '\0'; |
| 150 |
|
| 151 |
/* Trim trailing spaces from serial */ |
| 152 |
for (int i = 19; i >= 0 && ata_serial[i] == ' '; i--) |
| 153 |
ata_serial[i] = '\0'; |
| 154 |
|
| 155 |
/* Capacity: words 60-61 contain total LBA28 sectors */ |
| 156 |
ata_sectors = ((uint32_t)identify_data[61] << 16) | identify_data[60]; |
| 157 |
uint32_t capacity_mb = (ata_sectors * 512) / (1024 * 1024); |
| 158 |
|
| 159 |
ata_drive_exists = 1; |
| 160 |
|
| 161 |
kprintf("[ata] Model: %s\n", ata_model); |
| 162 |
kprintf("[ata] Serial: %s\n", ata_serial); |
| 163 |
kprintf("[ata] Sectors: %d (%d MB)\n", ata_sectors, capacity_mb); |
| 164 |
kprintf("[ata] ATA PIO driver ready.\n"); |
| 165 |
} |
| 166 |
|
| 167 |
/* === Read sectors using LBA28 PIO === */ |
| 168 |
int ata_read_sectors(uint32_t lba, uint32_t count, void *buf) { |
| 169 |
if (!ata_drive_exists) return -1; |
| 170 |
|
| 171 |
uint16_t *ptr = (uint16_t *)buf; |
| 172 |
|
| 173 |
for (uint32_t i = 0; i < count; i++) { |
| 174 |
uint32_t cur_lba = lba + i; |
| 175 |
|
| 176 |
ata_wait_bsy(); |
| 177 |
|
| 178 |
/* Select drive + upper 4 bits of LBA */ |
| 179 |
outb(ATA_DRIVE_HEAD, 0xE0 | ((cur_lba >> 24) & 0x0F)); |
| 180 |
ata_delay_400ns(); |
| 181 |
|
| 182 |
/* Set parameters */ |
| 183 |
outb(ATA_SECCOUNT, 1); /* 1 sector at a time */ |
| 184 |
outb(ATA_LBA_LO, (uint8_t)(cur_lba & 0xFF)); |
| 185 |
outb(ATA_LBA_MID, (uint8_t)((cur_lba >> 8) & 0xFF)); |
| 186 |
outb(ATA_LBA_HI, (uint8_t)((cur_lba >> 16) & 0xFF)); |
| 187 |
|
| 188 |
/* Send READ SECTORS command */ |
| 189 |
outb(ATA_COMMAND, ATA_CMD_READ); |
| 190 |
|
| 191 |
/* Wait for data */ |
| 192 |
if (ata_wait_drq() != 0) { |
| 193 |
kprintf("[ata] Read error at LBA %d\n", cur_lba); |
| 194 |
return -1; |
| 195 |
} |
| 196 |
|
| 197 |
/* Read 256 words (512 bytes) */ |
| 198 |
for (int w = 0; w < 256; w++) { |
| 199 |
ptr[w] = inw(ATA_DATA); |
| 200 |
} |
| 201 |
ptr += 256; |
| 202 |
} |
| 203 |
|
| 204 |
return 0; |
| 205 |
} |
| 206 |
|
| 207 |
/* === Write sectors using LBA28 PIO === */ |
| 208 |
int ata_write_sectors(uint32_t lba, uint32_t count, const void *buf) { |
| 209 |
if (!ata_drive_exists) return -1; |
| 210 |
|
| 211 |
const uint16_t *ptr = (const uint16_t *)buf; |
| 212 |
|
| 213 |
for (uint32_t i = 0; i < count; i++) { |
| 214 |
uint32_t cur_lba = lba + i; |
| 215 |
|
| 216 |
ata_wait_bsy(); |
| 217 |
|
| 218 |
/* Select drive + upper 4 bits of LBA */ |
| 219 |
outb(ATA_DRIVE_HEAD, 0xE0 | ((cur_lba >> 24) & 0x0F)); |
| 220 |
ata_delay_400ns(); |
| 221 |
|
| 222 |
/* Set parameters */ |
| 223 |
outb(ATA_SECCOUNT, 1); |
| 224 |
outb(ATA_LBA_LO, (uint8_t)(cur_lba & 0xFF)); |
| 225 |
outb(ATA_LBA_MID, (uint8_t)((cur_lba >> 8) & 0xFF)); |
| 226 |
outb(ATA_LBA_HI, (uint8_t)((cur_lba >> 16) & 0xFF)); |
| 227 |
|
| 228 |
/* Send WRITE SECTORS command */ |
| 229 |
outb(ATA_COMMAND, ATA_CMD_WRITE); |
| 230 |
|
| 231 |
/* Wait for DRQ */ |
| 232 |
if (ata_wait_drq() != 0) { |
| 233 |
kprintf("[ata] Write error at LBA %d\n", cur_lba); |
| 234 |
return -1; |
| 235 |
} |
| 236 |
|
| 237 |
/* Write 256 words (512 bytes) */ |
| 238 |
for (int w = 0; w < 256; w++) { |
| 239 |
outw(ATA_DATA, ptr[w]); |
| 240 |
} |
| 241 |
ptr += 256; |
| 242 |
|
| 243 |
/* Flush: send CACHE FLUSH command */ |
| 244 |
ata_wait_bsy(); |
| 245 |
outb(ATA_COMMAND, 0xE7); /* CACHE FLUSH */ |
| 246 |
ata_wait_bsy(); |
| 247 |
} |
| 248 |
|
| 249 |
return 0; |
| 250 |
} |
| 251 |
|
| 252 |
/* === Check if drive exists === */ |
| 253 |
int ata_is_present(void) { |
| 254 |
return ata_drive_exists; |
| 255 |
} |
| 256 |
|
| 257 |
/* === Get drive info === */ |
| 258 |
uint32_t ata_get_sector_count(void) { |
| 259 |
return ata_sectors; |
| 260 |
} |
| 261 |
|
| 262 |
const char *ata_get_model(void) { |
| 263 |
return ata_model; |
| 264 |
} |
| 265 |
|
| 266 |
const char *ata_get_serial(void) { |
| 267 |
return ata_serial; |
| 268 |
} |
| 269 |
|
| 270 |
/* === Raw sector read (for ext2 block device callback) === */ |
| 271 |
/* Note: This wraps ata_read_sectors with LBA/sector conversion */ |
| 272 |
int ata_block_read(uint64_t lba, uint32_t count, void *buf) { |
| 273 |
return ata_read_sectors((uint32_t)lba, count, buf); |
| 274 |
} |
| 275 |
|