ATA not firing IRQ

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
kemosparc
Member
Member
Posts: 207
Joined: Tue Oct 29, 2013 1:13 pm

ATA not firing IRQ

Post by kemosparc »

Hi,

I am having a problem trying to make an ATA disk fire an interrupt.

I can detect all the disks and extract fw, model, serial, and disk size correctly.

When I try to write to one of the attached detected disks the interrupt does not fire. Here is the code that I expect to fire the interrupt:

Code: Select all

// io_port = 0xf0
// drv_selector = 0xB0
// ATA_PRIMARY_DCR_AS = 0x3F6
// addr is the sector number and I use the value 100.

    Ports::outportb(io_port + ATA_REG_HDDEVSEL,drv_selector); // select disk
    Ports::outportb(ATA_PRIMARY_DCR_AS,0b00001000);    // clearing nIEN bit
    
    Ports::outportb(io_port + 1, 0x00);
    Ports::outportb(io_port + 2, 0x01);
    Ports::outportb(io_port + 3, (uint8_t)addr);
    Ports::outportb(io_port + 4, (uint8_t)(addr >> 8));
    Ports::outportb(io_port + 5, (uint8_t)(addr >> 16));
    Ports::outportb(io_port + 6, 0xE0+slavebit | (slavebit << 4) | ((addr >> 24) & 0x0F));
    Ports::outportb(io_port + 7, 0x30);
I expect that at the end of the above code an interrupt fires.

The IVT handler for the primary and the secondary (14 and 15) is called once at the time I identify the disks and then no interrupts fires again.

A copy of the same interrupt handler works fine wit my network driver (E1000) and send EOI at the end of the ATA interrupt as well. I also read the status register at the end of the interrupt.

Also it is worth mentioning that I have tried this code from with and system call so it occurs within an interrupt, and from the main body of my kernel avoiding executing the code from within an interrupt.


Any ideas what might be the problem?

Thanks.
Karim.
User avatar
BrightLight
Member
Member
Posts: 901
Joined: Sat Dec 27, 2014 9:11 am
Location: Maadi, Cairo, Egypt
Contact:

Re: ATA not firing IRQ

Post by BrightLight »

kemosparc wrote:

Code: Select all

Ports::outportb(ATA_PRIMARY_DCR_AS,0b00001000);
Why are you setting bit 3? What does this bit do?
BTW, I usually reset the ATA bus by setting then clearing bit 2, and then enable interrupts by clearing bit 1. You should do this before every ATA command, or at least I do and it works on emulators and real HW.
You know your OS is advanced when you stop using the Intel programming guide as a reference.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ATA not firing IRQ

Post by BenLunt »

kemosparc wrote: The IVT handler for the primary and the secondary (14 and 15) is called once at the time I identify the disks and then no interrupts fires again.

A copy of the same interrupt handler works fine wit my network driver (E1000) and send EOI at the end of the ATA interrupt as well. I also read the status register at the end of the interrupt.
Just a few thoughts.
- You have acknowledged the primary *and* secondary PIC's after the interrupt? (first the secondary, then the primary)
- You have read the STATUS register, not the ALT_STATUS register?
- You actually wait long enough for the interrupt to happen before checking to see if it did?

>> Ports::outportb(io_port + 6, 0xE0+slavebit | (slavebit << 4) | ((addr >> 24) & 0x0F));

Using the "+ slavebit | (slavebit << 4)" code, you actually are setting bit 0 in the [LBA27:24] if you are on the slave drive.
Should you change the above to:

>> Ports::outportb(io_port + 6, 0xE0 | (slavebit << 4) | ((addr >> 24) & 0x0F));

I don't recommend resetting of the controller each time you send a command, since a proper reset sequence can take up to 7mS. This is a 7mS wait every time you wish to send a command. If you can only read 256 sectors at a time, this is 7mS between reads. A lot can happen in 7mS. :-)

Ben
http://www.fysnet.net/media_storage_devices.htm
kemosparc
Member
Member
Posts: 207
Joined: Tue Oct 29, 2013 1:13 pm

Re: ATA not firing IRQ

Post by kemosparc »

Hi,

Okay I have managed to get the IRQ fire but with some symptoms that I cannot explain.

The main problem was that I do select drive before issuing the write commands.

Here is the code I have right now for the write:

Code: Select all


    Ports::outportb(io_port + 6, 0xE0 | (slavebit << 4) | ((addr >> 24) & 0x0F));
    Ports::outportb(io_port + 1, 0x00);
    Ports::outportb(io_port + 2, 0x01);
    Ports::outportb(io_port + 3, (uint8_t)addr);
    Ports::outportb(io_port + 4, (uint8_t)(addr >> 8));
    Ports::outportb(io_port + 5, (uint8_t)(addr >> 16));
    Ports::outportb(io_port + 7, 0x30);
    Ports::outportb(io_port + 7, 0xE7);
 
    if (Ports::inportb(io_port + ATA_REG_STATUS))
    {
        for ( int i = 0 ; ; i++)
        {
            uint8_t status = Ports::inportb(io_port+ATA_REG_STATUS);
            if ( status & ATA_SR_DRQ ) break;
            else if(status & ATA_SR_ERR) 
            {
                video->setPosition(30,5);
                video->putString("Error on Write Sector",COLOR_BLUE,COLOR_LIGHT_BROWN);
                return; 
            }
        }
    }
// if I remove this part the IRQ does not fire for the write
    for (int idx = 0; idx < 256; idx++)
    {
        uint16_t writeword = p_buffer[idx * 2] | (p_buffer[idx * 2 + 1] << 8);
        Ports::outportw(io_port, writeword);
        Ports::outportb(io_port + 7, 0xE7);
    }
// if I remove this part the IRQ does not fire for the write
    for (int idx = 0; idx < 10; idx++)
        ide_400ns_delay();

The problem that I can not understand is that if I remove the code for the sector data writing the interrupt does not appear, so what is the purpose of the interrupt anyway.

I was targeting to put the code loop for writing the sector in the interrupt handler.

Any help is much appreciated.

Thanks,
Karim.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ATA not firing IRQ

Post by BenLunt »

I think you have misunderstood where the IRQ should fire. The IRQ only fires *after* a sector has been read or written, a whole sector.

The IRQ won't fire after you have written the parameter registers, only after you have written 256 16-bit words.

Also, the IRQ will not fire immediately after the last word written, it will take a few nS before it does.

And don't write the Flush Cache command until after the whole sector has been written, in fact at the moment, until you
get a working read/write routine, I would forget about flushing the cache.

Here is what I would do (please see my notes afterward):

Code: Select all

    Ports::outportb(io_port + 6, 0xE0 | (slavebit << 4) | ((addr >> 24) & 0x0F));
    Ports::inportb(io_port + ATA_REG_STATUS));
    Ports::outportb(io_port + 1, 0x00);
    Ports::outportb(io_port + 2, 0x01);
    Ports::outportb(io_port + 3, (uint8_t)addr);
    Ports::outportb(io_port + 4, (uint8_t)(addr >> 8));
    Ports::outportb(io_port + 5, (uint8_t)(addr >> 16));
    Ports::outportb(io_port + 7, 0x30);
    
    if (Ports::inportb(io_port + ATA_REG_STATUS))
    {
        // for ( int i = 0 ; ; i++)   // why increment 'i'?  Unneeded instruction
        for (;;)
        {
            uint8_t status = Ports::inportb(io_port+ATA_REG_STATUS);
            if ( status & ATA_SR_DRQ ) break;
            else if(status & ATA_SR_ERR)
            {
                video->setPosition(30,5);
                video->putString("Error on Write Sector",COLOR_BLUE,COLOR_LIGHT_BROWN);
                return;
            }
        }
    }
    for (int idx = 0; idx < 256; idx++)
    {
        uint16_t writeword = p_buffer[idx * 2] | (p_buffer[idx * 2 + 1] << 8);
        Ports::outportw(io_port, writeword);
    }

    // here is where the IRQ should fire.
Here are some notes about the ATA interface.
  • - You should only select the drive once. For example, don't select the same drive again for the next sector. Only write to the DRV_HEAD register if the value to write is different from the last time. The ATA controller takes a considerable amount of time to 'select' itself. (400ns is a long time, really) :-)
    - You should read the status register after selecting the drive, giving it enough time to actually select itself.
    - If you cast your p_buffer from a byte buffer to a word buffer, you can do something like:

    Code: Select all

    uint16_t *ptr = (uint16_t *) p_buffer;
    for (int idx = 0; idx < 256; idx++)
      Ports::outportw(io_port, *ptr++);
Here is a condensed version of my "transfer a sector" function from my book:

Code: Select all

  // select drive
  // (reads the status register to clear any pending interrupts)
  ata_select_drv(ata->cntrlr->base, ata->drv, ATA_DH_ISLBA, (bit8u) ((bit64u) (lba & (bit64u) 0x00000F000000) >> 24));
  
  // wait for the controller to not be busy (i.e: wait for it to be ready)
  if (ata_wait(ata, ATA_STATUS_RDY, wait)) {
    outportb(ata->cntrlr->base + ATA_FEATURES, (features & 0xFF));
    outportb(ata->cntrlr->base + ATA_SECTOR_COUNT, ((buflen / 512) & 0xFF));
    outportb(ata->cntrlr->base + ATA_LBA_LOW_BYTE,  (bit8u) (((bit32u) lba & 0x000000FF) >>  0));
    outportb(ata->cntrlr->base + ATA_LBA_MID_BYTE,  (bit8u) (((bit32u) lba & 0x0000FF00) >>  8));
    outportb(ata->cntrlr->base + ATA_LBA_HIGH_BYTE, (bit8u) (((bit32u) lba & 0x00FF0000) >> 16));
  } else
    return FALSE;
  
  // send the command
  outportb(ata->cntrlr->base + ATA_COMMAND, command);
  // wait for the drive to be ready for the transfer
  if (ata_wait(ata, ATA_STATUS_DRQ, wait)) {
    if (ttype == ATA_TRNS_TYPE_PIO) {
      // All sector transfers on an ATA call are 512 byte sectors...
      bit32u addr = (bit32u) buf;
      while (buflen > 0) {
        if (ata->dword_io) {
          bit32u *ptr = (bit32u *) addr;
          j = ((buflen > 512) ? 512 : buflen) / sizeof(bit32u);
          for (i=0; i<j; i++) {
            if (dir == ATA_DIR_RECV)
              *ptr++ = inportl(ata->cntrlr->base + ATA_DATA);
            else
              outportl(ata->cntrlr->base + ATA_DATA, *ptr++);
          }
        } else {
          bit16u *ptr = (bit16u *) addr;
          j = ((buflen > 512) ? 512 : buflen) / sizeof(bit16u);
          for (i=0; i<j; i++) {
            if (dir == ATA_DIR_RECV)
              *ptr++ = inportw(ata->cntrlr->base + ATA_DATA);
            else
              outportw(ata->cntrlr->base + ATA_DATA, *ptr++);
          }
        }
        buflen -= 512;
        addr += 512;
        // wait for the drive to be ready for the next transfer
        if ((buflen > 0) && !ata_wait(ata, ATA_STATUS_DRQ, wait))
          return FALSE;
      }
    } else {
       // is DMA, do the DMA code
       //  not included for this post.
    }
  }
Please note that this is a stripped down version and allows transfers less than 512 bytes, since the same code is used to send 12- and 16-byte packets.

Hope this helps.
Ben
http://www.fysnet.net/media_storage_devices.htm
kemosparc
Member
Member
Posts: 207
Joined: Tue Oct 29, 2013 1:13 pm

Re: ATA not firing IRQ

Post by kemosparc »

Hi,

Thank you very very much for your thorough reply. I really appreciate it.

Two verification questions though:
1.You said that the IRQ fires when the whole sector is transferred to the disk which explains the symptom I got, but in that case what is the need for the interrupt? what I should handle in the interrupt? I mean in case of writing sectors polling is obviously mandated? For me there is no need for the interrupt.
2. In case of reading what happens is that my IRQ fires as soon as I issue "Ports::outportb(io_port + 7, 0x20)" followed by reading the status, so in that case I should do the sector reading within the interrupt handler, right? So in reading it does not wait until the whole sector is read, or am I getting something wrong?

On another note, I am planning to start on ATA/DMA, do you have any easy source for that which can explain the subject from scratch (without assuming that some issues are clear by themselves :))) ) and with important implementation details. Most of the online material tackles it in a general form, that will suite DMA with any device and without going into implementation details, but I need documentation on ATA/DMA in specific with some examples and tutorial approach (something like James Molloy tutorial)

Thanks a lot again for the help,
Karim.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ATA not firing IRQ

Post by BenLunt »

When transferring using PIO, yes, there is really no need to use interrupts. You must poll for the DRQ bit anyway.

Section 9.5 of ATAPI 6 states that you will receive an interrupt just after sending the READ command, and then another after each sector read. You do not receive an interrupt after sending the write command.

As far as using DMA, if the drive is ATAPI compatible, i.e.: uses Packets, you cannot use the same commands as PIO and DMA transfers. You must use the packet interface for all DMA transfers.

Sending packets are a little different. You first send the packet to the drive just like you would if you are writing sectors, though you use the PACKET command and send 12 to 16 bytes only. Then you wait for the drive ready, if using PIO, or wait for an interrupt if using DMA.

Give it a try and then if you get stuck, let us know.

Ben
http://www.fysnet.net/media_storage_devices.htm
kemosparc
Member
Member
Posts: 207
Joined: Tue Oct 29, 2013 1:13 pm

Re: ATA not firing IRQ [DETECTNG PCI]

Post by kemosparc »

Hi Ben,

I got your book on kindle and I started reading it; so I am going step by step.

Now I am trying to detect my drives PCI configuration.

I already had a module that reads all PCI, and loads their descriptors into data structure.

When I print them all I cannot find any PCI device with class code = 0x1.

I run my OS on qemu and vbox and both printed a list of devices and non of them has a class code = 0x1.

Does this mean that the disks are configured as ISA and not PCI.

In that case how can I force qemu and vbox to show my disks as PCI.

Thanks,
Karim.
kemosparc
Member
Member
Posts: 207
Joined: Tue Oct 29, 2013 1:13 pm

Re: ATA not firing IRQ [ANSWERING MYSELF]

Post by kemosparc »

Okay,

I am answering my one post.

I think that that default in both hypervisors is ISA.

For example I used to start my qemu machine like this:

Code: Select all

qemu-system-x86_64 -m 4096 -smp 2 -hda $(IMAGE)/boot.flp -hdb $(IMAGE)/hdd1.qcow2 -hdc $(IMAGE)/hdd2.qcow2 -hdd $(IMAGE)/hdd3.qcow2 -net nic,vlan=0,model=e1000,macaddr=52:54:00:91:46:f6 -net tap,ifname=virbr0_1,script=/etc/cloudsparc/net_scripts/virbr0 --enable-kvm
When I changed it to the following the PCI was detected:

Code: Select all

qemu-system-x86_64 -m 4096 -smp 2 -drive file=/home/kmsobh/NetBeansProjects/BOSML/images/boot.flp,if=ide,index=0,media=disk -drive file=/home/kmsobh/NetBeansProjects/BOSML/images/hdd1.qcow2,if=ide,index=1,media=disk -drive file=/home/kmsobh/NetBeansProjects/BOSML/images/hdd2.qcow2,if=ide,index=2,media=disk -drive file=/home/kmsobh/NetBeansProjects/BOSML/images/hdd3.qcow2,if=ide,index=3,media=disk  -net nic,vlan=0,model=e1000,macaddr=52:54:00:91:46:f6 -net tap,ifname=virbr0_1,script=/etc/cloudsparc/net_scripts/virbr0 --enable-kvm
Okay, so using the short hand -hd[a|b|c|d] will start the machine with ISA (according to my interpretation; might be wrong), and defining the drives using the "drive" switch will make it work as PCI.

In vbox, I had to add a SATA controller and move my disks under it.

I thought that this might be of benefit to other who may go through the same route.

Thanks,
Karim.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ATA not firing IRQ

Post by BenLunt »

Yes, most emulators, at least the ones I use, default to ISA compatible ATA controllers.

Please note though, that an ATA controller can be in PCI mode and still be compatible with ISA drivers. This is where the Native and Compatibility bits come into play. Please read the section starting on page 2-9.

SATA controllers, have two capable modes. SATA IDE and SATA AHCI. These are PCI(e) only.

SATA IDE work (almost) exactly as PATA (Parallel ATA). They have the same interface between the software and hardware, though the interface between the hardware and the drive is much different.

SATA AHCI is quite a bit different that the PATA or SATA IDE interface and needs a lot more work. However, it now allows 32 drives to be attached, with
many more if you use multipliers (hubs in the USB sense of the word).

Not all SATA controllers can be AHCI and not all SATA controllers can be IDE, but if a controller is AHCI it most likely can be IDE too.

Luckily for you, my book and the CDROM ISO I sent you explains all of these modes. (smile)

Also, for a bonus, the book explains the FDC too. As with recent statements in other posts, floppies are a thing of the past. However, I like hardware from the past. It shows the innovations used to accomplish something that was yet to be a common place. One of the things I work on, sporadically, is to find out the sequence described by https://blogs.msdn.microsoft.com/oldnew ... 0/?p=18643

Good luck,
Ben
kemosparc
Member
Member
Posts: 207
Joined: Tue Oct 29, 2013 1:13 pm

Re: ATA not firing IRQ

Post by kemosparc »

Thanks a lot for your explanation,

I would like to add that I was not fully about my last post.

In addition to the qemu switches my problem was that I did not read all the functions within every bus, only the first one, and that was the second problem by not detecting my drives correctly.

I just wanted to add this as documentation for others whom my have the same problem, and I refer to this OSDEV forum post http://forum.osdev.org/viewtopic.php?f=1&t=30125 which helped me discover the problem.

Now it works well and I am in the process of detecting the DMA and setting it up.

The book is very good so far, and I am following it for the DMA initialization and set up steps :)

Will let you know when I get it running; probably in a couple of days,

Thanks again for the help.

Karim.
Kevin
Member
Member
Posts: 1071
Joined: Sun Feb 01, 2009 6:11 am
Location: Germany
Contact:

Re: ATA not firing IRQ [ANSWERING MYSELF]

Post by Kevin »

kemosparc wrote:Okay, so using the short hand -hd[a|b|c|d] will start the machine with ISA (according to my interpretation; might be wrong), and defining the drives using the "drive" switch will make it work as PCI.
You must have changed something else, too, because -hd[abcd] is exactly the same as your -drive options. It is even implemented this way in qemu, the option is first expanded into an explicit -drive option. And I never had problems finding the controller on the PCI bus when using -hda.
Developer of tyndur - community OS of Lowlevel (German)
Post Reply