ATA interrupt doesn't fire after sending READ_SECTORS command

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.
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

I am trying to implement a 28-bit ATA PIO driver right now but I am struggling with an issue. When I send the command 0x20, it doesn't raise any interrupts whatsoever. I checked if the IRQ 14 was masked but after unmasking the IRQ 14 and 2 it still doesn't work. I check whether the RDY bit is set and I send the commands according to ATA specifications but still, there aren't any interrupts. When I unmask IRQ 15 however, an interrupt happens before nothing else happening. Since the driver is a kernel-mode executable rather than a part of kernel, outb and inb is done by syscalls. Here is the code for anyone interested:

Code: Select all

int read_sector(int drive, int lba){
    int poll = sys_call(5, ATA_CONTROL_BASE, 0);
    while(!(poll & 0x40)) poll = sys_call(5, ATA_CONTROL_BASE, 0);
    int port_num = (drive & 1) ? ATA_SECONDARY_IO_BASE : ATA_IO_BASE;
    int command = (drive > 0xE1) ? 0xF0 : 0xE0;
    sys_call(6, port_num + 6, command | ((lba >> 24)));
    sys_call(6, port_num + 2, 0);
    sys_call(6, port_num + 3, lba & 0xFF);
    sys_call(6, port_num + 4, (lba >> 8) & 0xFF);
    sys_call(6, port_num + 5, (lba >> 16) & 0xFF);
    sys_call(6, port_num + 7, 0x20);
}
QEMU log:

Code: Select all

ide_status_read IDE PIO rd @ 0x3f6 (Alt Status); val 0x50; bus 0x555b63fa9ef0; IDEState 0x555b63fa9f78
ide_status_read IDE PIO rd @ 0x376 (Alt Status); val 0x00; bus 0x555b63faa7f0; IDEState 0x555b63faac50
ide_ioport_write IDE PIO wr @ 0x1f6 (Device/Head); val 0xa0; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f2 (Sector Count); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f3 (Sector Number); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f4 (Cylinder Low); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f5 (Cylinder High); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f7 (Command); val 0xec; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_bus_exec_cmd IDE exec cmd: bus 0x555b63fa9ef0; state 0x555b63fa9f78; cmd 0xec
ide_status_read IDE PIO rd @ 0x3f6 (Alt Status); val 0x58; bus 0x555b63fa9ef0; IDEState 0x555b63fa9f78
ide_status_read IDE PIO rd @ 0x3f6 (Alt Status); val 0x58; bus 0x555b63fa9ef0; IDEState 0x555b63fa9f78
ide_status_read IDE PIO rd @ 0x3f6 (Alt Status); val 0x58; bus 0x555b63fa9ef0; IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f6 (Device/Head); val 0xe0; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f2 (Sector Count); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f3 (Sector Number); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f4 (Cylinder Low); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f5 (Cylinder High); val 0x00; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_ioport_write IDE PIO wr @ 0x1f7 (Command); val 0x20; bus 0x555b63fa9ef0 IDEState 0x555b63fa9f78
ide_bus_exec_cmd IDE exec cmd: bus 0x555b63fa9ef0; state 0x555b63fa9f78; cmd 0x20
Thanks for any help
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by Octocontrabass »

yucarp wrote: Sat Jan 24, 2026 2:05 pmWhen I send the command 0x20, it doesn't raise any interrupts whatsoever.
I see you send command 0xEC before you send command 0x20. Did it raise any interrupts for command 0xEC?
yucarp wrote: Sat Jan 24, 2026 2:05 pmSince the driver is a kernel-mode executable rather than a part of kernel, outb and inb is done by syscalls.
Why? A kernel-mode executable doesn't need syscalls to use outb and inb.
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

Why? A kernel-mode executable doesn't need syscalls to use outb and inb.
Technically yes, but I haven't implemented relocatable executables properly yet so I can't leave a stub for inb() so this is the way I call functions from the main kernel executable.
I see you send command 0xEC before you send command 0x20. Did it raise any interrupts for command 0xEC?
No, it doesn't but the IDENTIFY command seems to work. Here is the code snippet for the IDENTIFY command:

Code: Select all

int send_identify(int port_num, int drive){
    sys_call(6, port_num + 6, drive);

    sys_call(6, port_num + 2, 0);
    sys_call(6, port_num + 3, 0);
    sys_call(6, port_num + 4, 0);
    sys_call(6, port_num + 5, 0);

    sys_call(6, port_num + 7, 0xEC);

    return sys_call(5, port_num, 0);
}
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by Octocontrabass »

yucarp wrote: Sat Jan 24, 2026 4:40 pmTechnically yes, but I haven't implemented relocatable executables properly yet so I can't leave a stub for inb() so this is the way I call functions from the main kernel executable.
Or you could make them inline functions so there's no function call at all.
No, it doesn't but the IDENTIFY command seems to work.
It's not working if it doesn't raise an interrupt. It should raise an interrupt when there's data available for you to read, exactly the same way command 0x20 should raise an interrupt when there's data available for you to read.
yucarp wrote: Sat Jan 24, 2026 4:40 pm

Code: Select all

    sys_call(6, port_num + 7, 0xEC);

    return sys_call(5, port_num, 0);
You need to wait for the drive to be ready before you try to read the 512-byte IDENTIFY DEVICE data. You need to use inw() to read that data from the drive.
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

There is a weird thing, after sending the IDENTIFY command, the interrupt comes from the 15th IRQ instead of the 14th IRQ. This really confuses me, I kept blocking the bit 15 as I didn't have any secondary bus in my emulator, but the interrupt is coming from the second bus...
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by Octocontrabass »

yucarp wrote: Sat Jan 24, 2026 5:22 pmThere is a weird thing, after sending the IDENTIFY command, the interrupt comes from the 15th IRQ instead of the 14th IRQ.
That sounds like a spurious interrupt. Spurious interrupts can happen if you don't wait for the interrupt to arrive before you do something that acknowledges the interrupt.
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

I tried everything but that interrupt keeps coming. I checked if the BSY bit is clear before sending the command but the interrupt happens directly after issuing the command. As it arrives as soon as I issue the command, there is nothing I can do to wait the right interrupt. Here is my new code for IDENTIFY

Code: Select all

int send_identify(int port_num, int drive){
    outb(port_num + 6, drive);

    outb(port_num + 2, 0);
    outb(port_num + 3, 0);
    outb(port_num + 4, 0);
    outb(port_num + 5, 0);

    uint8_t poll = inb(port_num + 7);
    while(poll & 0x80) poll = inb(port_num + 7);
    outb(port_num + 7, 0xEC);

    poll = inb(port_num + 7);

    if(!poll) return 0;

    while(poll & 0x80) poll = inb(port_num + 7);

    while(!(poll & 0x8)) poll = inb(port_num + 7);;

    for (int i = 0; i < 256; ++i){
        buffer[i] = inw(ATA_IO_BASE);
    }

    return 1;
}
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by Octocontrabass »

yucarp wrote: Sun Jan 25, 2026 6:51 am

Code: Select all

    outb(port_num + 7, 0xEC);

    poll = inb(port_num + 7);
Reading the status register acknowledges the interrupt. Reading the status register immediately after sending a command can acknowledge the interrupt so quickly that it causes a spurious interrupt.

What happens if you wait for an IRQ here instead of polling the status register? Do you still receive a spurious interrupt?
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

Yes, I still receive a spurious interrupt when I wait for an interrupt instead of reading the status register.
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by Octocontrabass »

Have you checked the PIC's ISR to see if it's really a spurious interrupt? I can't think of any reason why a spurious interrupt would arrive if you aren't accessing any of the drive's registers after sending the command.
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

It's doesn't come from IRQ 7, it comes from IRQ 15, which is the second ATA bus. The problem is I don't have any secondary ATA busses. I tried it on Bochs too and the same issue persists.
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

I can't believe that a single mistake cost me 1 day of debugging... When I initialized the IRQ, I wrote irq13, irq15 instead of irq13, irq14, irq15. Now everything is correct. However the same issue persists. After reading the first 512 bytes from the device no more interrupts happen. The QEMU log is like this:

Code: Select all

ide_data_readw IDE PIO rd @ 0x1f0 (Data: Word); val 0x0000; bus 0x5619f191bf20; IDEState 0x5619f191bfa8
ide_data_readw IDE PIO rd @ 0x1f0 (Data: Word); val 0x0000; bus 0x5619f191bf20; IDEState 0x5619f191bfa8
ide_ioport_read IDE PIO rd @ 0x1f7 (Status); val 0x50; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_ioport_read IDE PIO rd @ 0x1f7 (Status); val 0x50; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_ioport_write IDE PIO wr @ 0x1f6 (Device/Head); val 0xe0; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_ioport_write IDE PIO wr @ 0x1f2 (Sector Count); val 0x00; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_ioport_write IDE PIO wr @ 0x1f3 (Sector Number); val 0x00; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_ioport_write IDE PIO wr @ 0x1f4 (Cylinder Low); val 0x00; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_ioport_write IDE PIO wr @ 0x1f5 (Cylinder High); val 0x00; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_ioport_write IDE PIO wr @ 0x1f7 (Command); val 0x20; bus 0x5619f191bf20 IDEState 0x5619f191bfa8
ide_bus_exec_cmd IDE exec cmd: bus 0x5619f191bf20; state 0x5619f191bfa8; cmd 0x20
ide_sector_read sector=0 nsectors=1

Code: Select all

int read_sector(int drive, int lba){
    int port_num = (drive & 1) ? ATA_SECONDARY_IO_BASE : ATA_IO_BASE;
    int command = (drive > 0xE1) ? 0xF0 : 0xE0;
    sys_call(6, port_num + 6, command | ((lba >> 24)));
    sys_call(6, port_num + 2, 0);
    sys_call(6, port_num + 3, lba & 0xFF);
    sys_call(6, port_num + 4, (lba >> 8) & 0xFF);
    sys_call(6, port_num + 5, (lba >> 16) & 0xFF);
    sys_call(6, port_num + 7, 0x20);
}

void ata_handler(struct x86Registers *regs){
    for (int i = 0; i < 256; ++i){
        buffer[i] = inw(ATA_IO_BASE);
    }
    inb(ATA_IO_BASE + 7);
}
Octocontrabass
Member
Member
Posts: 6245
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by Octocontrabass »

yucarp wrote: Sun Jan 25, 2026 2:11 pmIt's doesn't come from IRQ 7, it comes from IRQ 15,
Spurious IRQ7 is more common than spurious IRQ15, but both may be spurious.
yucarp wrote: Sun Jan 25, 2026 6:15 pmAfter reading the first 512 bytes from the device no more interrupts happen.
I see you acknowledge the drive's interrupt, but do you also send EOI to the interrupt controllers?
yucarp wrote: Sun Jan 25, 2026 6:15 pm

Code: Select all

    for (int i = 0; i < 256; ++i){
        buffer[i] = inw(ATA_IO_BASE);
    }
    inb(ATA_IO_BASE + 7);
You should read the status register first so you can check for errors.
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

Spurious IRQ7 is more common than spurious IRQ15, but both may be spurious.
Thank you for that information, but IRQ15 was a mistake in my interrupt code rather than a spurious interrupt as far as I checked.
I see you acknowledge the drive's interrupt, but do you also send EOI to the interrupt controllers?
Oh god, I completely forgot about that... When I was setting interrupt code again I thought that all handlers automatically send EOI... #-o Now it works fine.
You should read the status register first so you can check for errors.
Thank you for your advice. I will consider that
yucarp
Member
Member
Posts: 39
Joined: Fri Dec 05, 2025 5:19 pm
Libera.chat IRC: yucarp

Re: ATA interrupt doesn't fire after sending READ_SECTORS command

Post by yucarp »

I have another question again... Does READ_SECTORS always read 256 sectors? The specification says that the byte that's send to 0x1F2 is the reverse of the sector count that will be read but when I set the sector count to 0xFF it still reads 256 sectors. The difference is it reads once by once instead of directly reading all sectors
Post Reply