First, make sure the I/O APIC is configured correctly. Since there is no interrupt overrides for the ATA IRQs, make sure that the I/O APIC entry for them is configured to use active high polarity, and edge trigger mode, which are the defaults for ISA IRQs if the firmware doesn't indicate otherwise. The
I/O APIC Wiki entry has more information on this.
Next, make sure the IRQs from the ATA channels are enabled. Do this by clearing bit 1 of the device alternate control register (normally at I/O port 0x3F6, but could be different, see below.)
Next, make sure the I/O ports that are being used are correct. This is unlikely to happen on emulators, but will hit you when you use real hardware. Basically, scan the PCI bus for a PCI IDE controller. If you find one, BAR0 contains the base I/O port of the primary channel, BAR1 contains the base I/O port of the primary channel alternate control, BAR2 contains the base I/O port of the secondary channel and BAR3 contains the base I/O port of the secondary channel alternate control. If any of these values are zero or one, you can assume the defaults; 0x1F0, 0x3F6, 0x170 and 0x376. The IRQ being used should also be read from the PCI configuration space, and you should not assume IRQs 14 and 15.
Since you are using DMA transfers anyway, you are already using the PCI IDE controller. Make sure that it is allowed to access I/O port space, bus-master DMA and send interrupts, by setting/clearing appropriate bits in the PCI command register.
EDIT: In general, using PCI IRQs without a proper ACPI AML interpreter will give you very fragile code, that works by chance on some PCs and won't work on others. As such, if you're going to use PCI IRQs in any way, don't use the I/O APIC for those, and stick to the legacy PIC. An alternative (and easier) option, that I myself made, is to make all PCI device drivers rely on polling, and not IRQs, because it reduces the need of an AML interpreter. But if you feel like writing one, then go ahead. It
would be inspiring.