[solved] ATA driver fails to detect no drive on real hardwar

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
Ankeraout
Member
Member
Posts: 25
Joined: Tue Feb 28, 2017 10:22 am

[solved] ATA driver fails to detect no drive on real hardwar

Post by Ankeraout »

Hello,

I am currently developing an ATA driver and I ran into some problems when trying it on real hardware.
My test bench is a very old laptop that has a PCI IDE controller with 2 channels:
  • The first one (I/O ports 0x1f0 and 0x3f6) is controlling the hard drive as master
  • The second one (I/O ports 0x170 and 0x376) is controlling the optical drive as master
I know for sure there are no other IDE drives on this computer (according to hdparm actually).

However, during the detection phase of my driver, I noticed that there is a weird thing after sending the IDENTIFY command to the slave drive of the first channel.
It looks like even though there is no drive, the status byte is not set to zero after 400ns.
My driver therefore thinks there is a drive, and waits for the BSY bit to be cleared, and then for the DRQ bit to be set.

I searched the whole day for information about this, I tried adding wait time, etc... But nothing solved my problem.
Does anybody have any information about this?

Here is my code:

Code: Select all

static void ata_identify(ata_channel_t *channel, int drive) {
    ata_identify_t identify;

    outb(channel->commandIoBase + ATA_CMDREG_DRIVE_HEAD, 0xa0 | ((drive & 1) << 4));

    ata_wait(channel); // Unnecessary?

    outb(channel->commandIoBase + ATA_CMDREG_SECTOR_COUNT, 0);
    outb(channel->commandIoBase + ATA_CMDREG_LBA_0_7, 0);
    outb(channel->commandIoBase + ATA_CMDREG_LBA_8_15, 0);
    outb(channel->commandIoBase + ATA_CMDREG_LBA_16_23, 0);
    outb(channel->commandIoBase + ATA_CMDREG_COMMAND, ATA_COMMAND_IDENTIFY);
    
    ata_wait(channel); // Unnecessary?

    if(!inb(channel->commandIoBase + ATA_CMDREG_STATUS)) {
        printf("ata: no %s drive detected on this channel\n", drive ? "slave" : "master");
        return;
    } else {
        printf("ata: detected drive\n");
    }

    printf("ata: waiting for BUSY bit to clear...\n");
    ata_waitBsyEnd(channel);
    printf("ata: BUSY bit cleared\n");

    if(inb(channel->commandIoBase + ATA_CMDREG_STATUS) & ATA_STATUS_ERROR) {
        uint8_t firstByte = inb(channel->commandIoBase + ATA_CMDREG_CYLINDER_LOW);
        uint8_t secondByte = inb(channel->commandIoBase + ATA_CMDREG_CYLINDER_HIGH);

        if(firstByte == 0x00 && secondByte == 0x00) {
            printf("ata: detected PATA device\n");
        } else if(firstByte == 0x14 && secondByte == 0xeb) {
            printf("ata: detected PATAPI device\n");
        } else if(firstByte == 0x3c && secondByte == 0xc3) {
            printf("ata: detected SATA device\n");
        } else if(firstByte == 0x69 && secondByte == 0x96) {
            printf("ata: detected SATAPI device\n");
        } else {
            printf("ata: unknown device identification: %#02x %#02x\n", firstByte, secondByte);
        }
        
        return;
    }

    printf("ata: waiting for DRQ bit to be set...\n");
    ata_waitDrq(channel);
    printf("ata: DRQ bit is set.\n");

    ata_receive(channel, &identify);

    printf("ata: device identification: %.40s\n", identify.modelNumber);
}

static void ata_waitBsyEnd(ata_channel_t *channel) {
    while(inb(channel->commandIoBase + ATA_CMDREG_STATUS) & ATA_STATUS_BUSY);
}

static void ata_waitDrq(ata_channel_t *channel) {
    while(!(inb(channel->commandIoBase + ATA_CMDREG_STATUS) & ATA_STATUS_DATA_REQUEST_READY));
}

static void ata_receive(ata_channel_t *channel, void *buffer) {
    for(int i = 0; i < 256; i++) {
        uint16_t tmp = inw(channel->commandIoBase + ATA_CMDREG_DATA);
        ((uint16_t *)buffer)[i] = (tmp >> 8) | (tmp << 8);
    }
}

static void ata_wait(ata_channel_t *channel) {
    for(int i = 0; i < 4; i++) {
        inb(channel->commandIoBase + ATA_CMDREG_STATUS);
    }
}
Attachments
20210207_185518.jpg
Last edited by Ankeraout on Wed Feb 10, 2021 4:14 am, edited 1 time in total.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA driver fails to detect no drive

Post by Octocontrabass »

Are you sure the drives aren't both attached to the same channel? The wiki says some ATAPI drives don't set ERR when they're supposed to, so you might end up waiting forever.
Ankeraout
Member
Member
Posts: 25
Joined: Tue Feb 28, 2017 10:22 am

Re: ATA driver fails to detect no drive

Post by Ankeraout »

Hello,

Yes, I am sure that the optical drive is on the second channel because I can detect it if I only check the second channel.
Something that I forgot to mention also is that when checking only the second channel, my code detects the presence of an ATAPI drive and detects correctly that there is no drive on the slave slot.

Also if I ignore the DRQ bit and start reading 512 bytes of data when identifying drives, the "ghost" drive on the first channel seems to return only zeroes, whereas the hard drive returns its identification data correctly.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA driver fails to detect no drive

Post by Octocontrabass »

What value is the status byte when you're expecting it to be zero?

Linux seems to think many different nonstandard responses are possible.
Ankeraout
Member
Member
Posts: 25
Joined: Tue Feb 28, 2017 10:22 am

Re: ATA driver fails to detect no drive

Post by Ankeraout »

I was reading 0x80, and then shortly after 0x00 (BSY bit was clearing).

So I decided to wait for 50ms using a sleep() function, instead of reading the status register 5 times as recommended on the Wiki.
This time, the driver detects the drives correctly and does not detect any "ghost" drive.
Attachments
screenshot.jpg
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: ATA driver fails to detect no drive

Post by Octocontrabass »

Ankeraout wrote:instead of reading the status register 5 times as recommended on the Wiki.
The information on the wiki seems to be very old. Reading the status register takes at least 100ns on a 10MHz bus, but the parallel ATA bus supports speeds up to 33MHz, which means reading a register could take only 30ns. You would have to read the status register 14 times to ensure a 400ns delay.
Ankeraout
Member
Member
Posts: 25
Joined: Tue Feb 28, 2017 10:22 am

Re: ATA driver fails to detect no drive

Post by Ankeraout »

I can confirm that it is working now. I have changed ata_wait() to read the status port 14 times.
I noticed that I also had to wait after sending the IDENTIFY command.
I thought that I only had to wait when changing drives.
thewrongchristian
Member
Member
Posts: 426
Joined: Tue Apr 03, 2018 2:44 am

Re: ATA driver fails to detect no drive

Post by thewrongchristian »

Ankeraout wrote:I can confirm that it is working now. I have changed ata_wait() to read the status port 14 times.
I noticed that I also had to wait after sending the IDENTIFY command.
I thought that I only had to wait when changing drives.
You're better off writing a general purpose time based thread sleep. Busy polling status ports is not only unreliable, but also a waste of CPU and bus resources. Not a problem in a hobby OS in the throws of being written and initializing, but eventually, you will need a general purpose way of sleeping for some specified amount of time.
Post Reply