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.
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:
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
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:
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.
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.
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...
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.
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
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?
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.
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.
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:
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
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... Now it works fine.
You should read the status register first so you can check for errors.
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