Page 1 of 1

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

Posted: Sun Feb 07, 2021 12:27 pm
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);
    }
}

Re: ATA driver fails to detect no drive

Posted: Sun Feb 07, 2021 5:20 pm
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.

Re: ATA driver fails to detect no drive

Posted: Mon Feb 08, 2021 12:36 am
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.

Re: ATA driver fails to detect no drive

Posted: Mon Feb 08, 2021 2:36 pm
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.

Re: ATA driver fails to detect no drive

Posted: Tue Feb 09, 2021 4:55 am
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.

Re: ATA driver fails to detect no drive

Posted: Tue Feb 09, 2021 4:06 pm
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.

Re: ATA driver fails to detect no drive

Posted: Wed Feb 10, 2021 4:13 am
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.

Re: ATA driver fails to detect no drive

Posted: Wed Feb 10, 2021 6:55 am
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.