ATA PIO read fails with timeout (status 0x41, error 0x4), but IDENTIFY works fine

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
ArtemOkhrimenko
Posts: 1
Joined: Sun Aug 03, 2025 1:33 am
Libera.chat IRC: Artem Okhrimenko

ATA PIO read fails with timeout (status 0x41, error 0x4), but IDENTIFY works fine

Post by ArtemOkhrimenko »

Hi everyone,
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: 0x4
And this is the read function and wait logic I use:

Code: 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);
}
And this is how I issue the read:

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);
And these are macros what I use

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 0xEC
Questions:

Why 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!
Octocontrabass
Member
Member
Posts: 6275
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA PIO read fails with timeout (status 0x41, error 0x4), but IDENTIFY works fine

Post by Octocontrabass »

ArtemOkhrimenko wrote: ↑Sun Aug 03, 2025 2:02 am

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);
}
You told the drive you're using CHS, but you're actually using LBA. I would guess the drive is aborting the command because it thinks you've specified an invalid CHS address.
Post Reply