I'm working on a simple ATA driver using PIO mode (no interrupts yet), and I'm running into a strange issue: the IDENTIFY command works and returns correct device info (serial, model, etc), but any attempt to read a sector results in a controller timeout.
Here’s what happens during a READ_SECTORS (0x20) command:
I issue the command after selecting drive and setting LBA and sector count
Then I wait for !BSY and DRQ in wait_controller()
But it never reaches DRQ, and the second loop times out
The final status = 0x41, error = 0x4
0x41 means:
BSY = 0
DRDY = 1
ERR = 1
0x4 in the error register indicates ABRT (Command Aborted).
However, as mentioned, IDENTIFY DEVICE (0xEC) works just fine, and returns good data. That suggests at least the drive is connected, responds to commands, and PIO reads in principle do work.
Here’s the relevant part of the log:
Code: Select all
status before command: 0x50
reading wait
wait1
wait2
KERNEL FAIL: ATA error: ATA controller timeout expired, status: 0x41, error: 0x4Code: Select all
static inline void wait_controller(size_t timeout) {
println((utf8)"wait1"); // TEMP
for (size_t i = 0; i < timeout; ++i) {
if (!(status() & BUSY_FLAG)) { // wait until controller is free
//if (error_status()) fail("ATA error: error bit set during busy wait, error: 0x%x", error());
break;
}
}
println((utf8)"wait2"); // TEMP
for (size_t i = 0; i < timeout; ++i) {
byte _status = status();
if (_status & DATA_REQUEST_FLAG) { // wait until controller is ready to transmit data
if (error_status(_status)) fail("ATA error: error bit set but data request is up, error: 0x%x", error());
return;
}
}
fail("ATA error: ATA controller timeout expired, status: 0x%x, error: 0x%x", status(), error());
}
Code: Select all
static inline void select(uint32_t lba, bool slave) {
byte high = getrange4(&lba, 24, 27);
byte drive_head = 0;
setbit(&drive_head, 7, 1);
setbit(&drive_head, 5, 1);
setbit(&drive_head, 4, slave);
drive_head |= high;
outb(DRIVE_SELECTOR_REGISTER, drive_head);
}
static inline void set_lba(uint32_t lba) {
outb(LBA_LOW, getrange4(&lba, 0, 7));
outb(LBA_MIDDLE, getrange4(&lba, 8, 15));
outb(LBA_HIGH, getrange4(&lba, 16, 23));
}
static inline byte status() {
return inb(STATUS_REGISTER);
}
static inline bool error_status(byte status) {
return status & ERROR_FLAG;
}
static inline byte error() {
return inb(ERROR_REGISTER);
}
static inline void io_wait() {
inb(0x3F6);
inb(0x3F6);
inb(0x3F6);
inb(0x3F6);
}Code: Select all
select(lba, false);
io_wait();
outb(SECTOR_COUNT_REGISTER, 1);
set_lba(lba);
io_wait();
outb(COMMAND_REGISTER, READ_IN_PIO_MODE_FLAG); // 0x20
wait_controller(CONTROLLER_TIMEOUT);Code: Select all
#define CONTROLLER_TIMEOUT 1000000
#define DATA_REGISTER 0x1F0
#define ERROR_REGISTER 0x1F1
#define SECTOR_COUNT_REGISTER 0x1F2
#define LBA_LOW 0x1F3
#define LBA_MIDDLE 0x1F4
#define LBA_HIGH 0x1F5
#define DRIVE_SELECTOR_REGISTER 0x1F6
#define STATUS_REGISTER 0x1F7
#define COMMAND_REGISTER 0x1F7
#define BUSY_FLAG 0x80
#define DATA_REQUEST_FLAG 0x08
#define ERROR_FLAG 0x01
// command flags
#define READ_IN_PIO_MODE_FLAG 0x20
#define WRITE_IN_PIO_MODE_FLAG 0x30
#define DEVICE_INFO_FLAG 0xECWhy would IDENTIFY work, but READ_SECTORS get aborted with ERR=1 and ABRT=1?
Does status=0x41 imply a command syntax issue or drive mode mismatch?
Could this be a problem with the select() logic or LBA high bits?
Let me know if more code or dumps are needed.
Thanks in advance!
