Page 2 of 3

Re: ATA driver problem

Posted: Tue Dec 27, 2022 3:47 pm
by WinExperements
Octocontrabass wrote:You have to send a command that involves a data transfer before the drive will set DRQ.
Okay I send the IDENTIFY command and what command I should send or that enough?

Re: ATA driver problem

Posted: Tue Dec 27, 2022 3:55 pm
by Octocontrabass
That should be enough.

Check the other registers for the ATAPI signature. You might be dealing with an ATAPI drive that doesn't follow ATA correctly.

Re: ATA driver problem

Posted: Wed Dec 28, 2022 11:57 am
by WinExperements
So my problem is that my driver trying to send command to drive that still reseting after soft reset command, so my question is how I can correctly wait for the reset using only the disk IO ports or I must use the system sleep functions?

Re: ATA driver problem

Posted: Wed Dec 28, 2022 12:32 pm
by Octocontrabass
The status register will tell you when the drive is done resetting and ready to accept commands.

Re: ATA driver problem

Posted: Fri Dec 30, 2022 12:52 pm
by WinExperements
How I can identify hard drive in SATA IDE mode? My drive with my driver returns me device fault and error code is 0xff

Re: ATA driver problem

Posted: Fri Dec 30, 2022 1:07 pm
by Octocontrabass
Identifying a SATA drive on an IDE controller works the same as any other ATA drive on an IDE controller.

What is the value of the status register? The device fault bit only applies when the busy bit is clear.

Re: ATA driver problem

Posted: Fri Dec 30, 2022 1:22 pm
by WinExperements
Octocontrabass wrote:What is the value of the status register?

0x7f

Re: ATA driver problem

Posted: Fri Dec 30, 2022 1:37 pm
by Octocontrabass
That combination of status and error register values means there's no drive present.

Re: ATA driver problem

Posted: Sun Feb 04, 2024 12:24 pm
by WinExperements
So, hello again! I have some problems with reading the sectors, my driver is getting the base register address and ctrl register address from PCI as descripted in the wiki, when i run this code on bochs, virtualbox or qemu the code successfully read the sectors, but when i runed it on the real hardware i got strange behaivor, the register reports successfully sector reading, but the drive actually doesn't do that(the disk active led doesn't power on and only 0xff transfered from the port). On bochs also i got continiosly interrupt 0x27. Here is my read code:

Code: Select all

static void ata_vdev_readBlock(vfs_node_t *node,int blockNo,int how, void *buf) {
        ata_device_t *dev = node->device;
        if (dev->type == IDE_ATAPI) {
                kprintf("Currently doesn't supported!\n");
                return;
        }
    	//outb(dev->base+ATA_REG_CONTROL,2);
	   kprintf("ata: read: dev ctrl: 0x%x\n",dev->base);
        void *dd = buf;
        int doCopy = false;
        uint64_t lba = blockNo;
        uint16_t sectors = how/512;
        if (sectors == 0) {
                sectors = 1;
                buf = kmalloc(512);
                doCopy = 1;
        }
        kprintf("Read %d sectors at %d\r\n",sectors,lba);
        if (sectors == 0) sectors = 1;
        outb(dev->base+ATA_REG_HDDEVSEL,0x40);
        ata_io_wait(dev);
        outportb(dev->base + ATA_REG_SECCOUNT0, 0);
	    outportb(dev->base + ATA_REG_LBA0, (lba & 0xff000000) >> 24);
	    outportb(dev->base + ATA_REG_LBA1, (lba & 0xff00000000) >> 32);
	    outportb(dev->base + ATA_REG_LBA2, (lba & 0xff0000000000) >> 40);

	    outportb(dev->base + ATA_REG_SECCOUNT0, sectors);
	    outportb(dev->base + ATA_REG_LBA0, (lba & 0x000000ff) >>  0);
	    outportb(dev->base + ATA_REG_LBA1, (lba & 0x0000ff00) >>  8);
	    outportb(dev->base + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16);
        outb(dev->base+ATA_REG_COMMAND,ATA_CMD_READ_PIO_EXT); // ATA_CMD_READ_PIO_EXT
        ata_io_wait(dev);
	//kprintf("ata: command setted\n");
	//kprintf("ata: interrupts disabled\n");
        uint8_t i;
        // convert buffer
        uint8_t *bu = (uint8_t *)buf;
        //arch_sti();
	//kprintf("going to read loop\n");
        for (i = 0; i < sectors; i++) {
                while(1) {
                        uint8_t status = inb(dev->base+ATA_REG_STATUS);
			//kprintf("Waiting untill drive remove the ATA_SR_DRQ\n");
                        if (status & ATA_SR_DRQ) {
                                // Disk is ready to transfer data
                                break;
                        } else if (status & ATA_SR_ERR) {
                                kprintf("ATA: Disk error. Cancel\n");
                                return;
                        }
                }
		//kprintf("ata: sector readed\n");
                insw(dev->base,bu,256);
                bu+=256;
        }
	//kprintf("ata: sectors readed\n");
        if (doCopy) {
                memcpy(dd,buf,how);
                kfree(buf);
        }
	//kprintf("ata: goodbay\n");
}

Re: ATA driver problem

Posted: Sun Feb 04, 2024 5:36 pm
by Octocontrabass
WinExperements wrote:On bochs also i got continiosly interrupt 0x27.
Which devices are connected to that interrupt?
WinExperements wrote:

Code: Select all

        outb(dev->base+ATA_REG_COMMAND,ATA_CMD_READ_PIO_EXT); // ATA_CMD_READ_PIO_EXT
Drives smaller than about 128GB often don't support 48-bit LBA.
WinExperements wrote:

Code: Select all

                        uint8_t status = inb(dev->base+ATA_REG_STATUS);
			//kprintf("Waiting untill drive remove the ATA_SR_DRQ\n");
                        if (status & ATA_SR_DRQ) {
If BSY is set, none of the other bits in the status register are valid. You must wait until BSY is clear before trying to interpret the other bits. You should use interrupts instead of polling the status register.

Re: ATA driver problem

Posted: Tue Apr 09, 2024 11:28 am
by WinExperements
Hello again! So i have problem sending ATA Identify command to drive.
When i send the command the drive, the drive is raise the IRQ, i read the status register to indicate for the drive that we handle the interrupt, but drive won't stop send there IRQs. I receive infinite count of interrupts from ATA when send the Identify command even if i readed the data from data port.
Also i noticed that the bug occurs if i boot system in Legacy Mode or when i boot it on Legacy BIOS machine.
On virtual machines such as VirtualBox,QEMU all works fine. On Bochs i also got infinite amount of interrupts from ATA, but debug messages are empty.
What i did wrong?
Here is my drive detect code:

Code: Select all

bool ata_device_init(ata_device_t *dev,bool second) {
    arch_sti();
    ata_soft_reset(dev);
    ata_io_wait(dev);
    int bit = (second ? 0xB0 : 0xA0);
    outb(dev->base+ATA_REG_HDDEVSEL, bit | dev->slave << 4);
    ata_io_wait(dev);
    outb(dev->base+ATA_REG_SECCOUNT0,0);
    outb(dev->base+ATA_REG_LBA0,0);
    outb(dev->base+ATA_REG_LBA1,0);
    outb(dev->base+ATA_REG_LBA2,0);
    // Send drive identify command
    inter_ata = dev;
    dev->ident = 1;
    kprintf("ATA: sending command\r\n");
    outb(dev->base+ATA_REG_COMMAND,ATA_CMD_IDENTIFY);
    if (!inb(dev->base + ATA_REG_STATUS)) {
	    kprintf("ATA: no drive\r\n");
	    return false;
	}
    uint8_t lba1 = inb(dev->base+ATA_REG_LBA1);
    uint8_t lba2 = inb(dev->base+ATA_REG_LBA2);
    if (lba1 != 0 || lba2 != 0) {
	    kprintf("Not ATA device\r\n");
	    return false;
	}
    kprintf("Waiting until DRQ or ERR to be setted\r\n");
    uint8_t drq,err = 0;
    while(!drq && !err) {
		if (dev->ident == 0) break;
		//kprintf("DRQ: %d ERR: %d. STATUS: %d\r\n",drq,err,inb(dev->base+ATA_REG_STATUS));
	    drq = inb(dev->base + ATA_REG_STATUS) & ATA_SR_DRQ;
	    err = inb(dev->base + ATA_REG_STATUS) & ATA_SR_ERR;
	}
    if (err) {
	    kprintf("ATA: Drive error!\r\n");
	    return false;
	}
    kprintf("ATA: Reading using PIO mode\r\n");
    if (dev->ident) {
	    kprintf("ATA: No IRQ rised to read\r\n");

  //  kprintf("The device is ready and doesn't reported any errors, reading the identify structure...");
		uint16_t *buf = (uint16_t *)&dev->identify;
		for (int i = 0; i < 256; i++) {
			buf[i] = inw(dev->base);
		}
	}
  	kprintf("readed\n");
	uint8_t *ptr = (uint8_t *)&dev->identify.model;
	for (int i = 0; i < 39; i+=2) {
		uint8_t tmp = ptr[i+1];
		ptr[i+1] = ptr[i];
		ptr[i] = tmp;
	}
	return true;
}
And the IRQ handler code:

Code: Select all

void *ata_irq_handler(void *stack) {
	// ATA specifications
	kprintf("ATA IRQ!\r\n");
	inb(inter_ata->base+ATA_REG_STATUS);
	if (inter_ata->ident) {
		uint16_t *data = (uint16_t *)&inter_ata->identify;
		for (int i = 0; i < 256; i++) {
			data[i] = inw(inter_ata->base);
		}
		kprintf("Device identify readed!\r\n");
	inter_ata->ident = 0;
	}
    	//inb(inter_ata->base+ATA_REG_STATUS);
	if (inter_ata->busy) {
		inter_ata->busy = 0;
	}
    outb(inter_ata->bar4+4,0x00);
    /*kprintf("ATA IRQ handler!\n");
    kprintf("Status of drive(after sending there commands): 0x%x\n",inb(inter_ata->base+ATA_REG_STATUS));
    kprintf("Error register: 0x%x\n",inb(inter_ata->base+ATA_REG_ERROR));*/
    return stack;
}

Re: ATA driver problem

Posted: Tue Apr 09, 2024 7:53 pm
by Octocontrabass
WinExperements wrote:

Code: Select all

    int bit = (second ? 0xB0 : 0xA0);
    outb(dev->base+ATA_REG_HDDEVSEL, bit | dev->slave << 4);
Why do you have two different methods to set bit 4?
WinExperements wrote:

Code: Select all

    outb(dev->base+ATA_REG_COMMAND,ATA_CMD_IDENTIFY);
    if (!inb(dev->base + ATA_REG_STATUS)) {
This is a race condition. If you need to check the drive's status while you're waiting for an interrupt, use the alternate status register.
WinExperements wrote:

Code: Select all

    uint8_t lba1 = inb(dev->base+ATA_REG_LBA1);
    uint8_t lba2 = inb(dev->base+ATA_REG_LBA2);
You can't read these registers while the drive is busy, and the drive is probably going to be busy if you just sent a command.
WinExperements wrote:

Code: Select all

	    drq = inb(dev->base + ATA_REG_STATUS) & ATA_SR_DRQ;
	    err = inb(dev->base + ATA_REG_STATUS) & ATA_SR_ERR;
Again, you need to use the alternate status register to prevent a race condition. You must check the status of the BSY bit before trying to interpret other bits in the status register. You don't need to read the status register twice to read two bits.
WinExperements wrote:

Code: Select all

		uint16_t *buf = (uint16_t *)&dev->identify;
		for (int i = 0; i < 256; i++) {
			buf[i] = inw(dev->base);
You have code to read the buffer twice.
WinExperements wrote:

Code: Select all

	inb(inter_ata->base+ATA_REG_STATUS);
You shouldn't discard the value of the status register. It's probably fine if you're just testing, though.
WinExperements wrote:

Code: Select all

		uint16_t *data = (uint16_t *)&inter_ata->identify;
		for (int i = 0; i < 256; i++) {
			data[i] = inw(inter_ata->base);
You have code to read the buffer twice.
WinExperements wrote:

Code: Select all

    outb(inter_ata->bar4+4,0x00);
Which register is this supposed to be? You don't need to touch any DMA registers when you're doing a PIO transfer.

Re: ATA driver problem

Posted: Wed Apr 10, 2024 1:49 pm
by WinExperements
So, i rewrited the device init code using the Wiki code, but as soon as i enable the device interrupts using the ATA_REG_CONTROL and issue the ATA DMA read command i start to receiving endless count of interrupts from IDE controller.
Here is my new version of device init code:

Code: Select all

bool ata_device_init(ata_device_t *dev,int id) { 
	unsigned char err = 0,status;
	ide_write(dev,ATA_REG_CONTROL,2);
	ide_write(dev,ATA_REG_HDDEVSEL,0xA0 | (id << 4));
	ata_io_wait(dev);
	ide_write(dev,ATA_REG_COMMAND,ATA_CMD_IDENTIFY);
	ata_io_wait(dev);
	if (ide_read(dev,ATA_REG_STATUS) == 0) return false;
	kprintf("Waiting device to clear BSY state...");
	while(1) {
		status = ide_read(dev,ATA_REG_STATUS);
		if ((status & ATA_SR_ERR)) {
			// Looks like, we send wrong command?
			uint8_t t1 = ide_read(dev,ATA_REG_LBA1);
			uint8_t t2 = ide_read(dev,ATA_REG_LBA2);
			if (t1 == 0x14 && t2 == 0xEB) {
				// fault caused by WRONG command.
				kprintf("ATAPI device fault(normal,ignore)\r\n");
				dev->type = IDE_ATAPI;
				break;
			} else {
				kprintf("First device fault received 0x%x 0x%x\r\n",t1,t2);
				return false;
			}
		}
		if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break;
	}
	kprintf("cleared\r\n");
	if (dev->type == IDE_ATAPI) {
		// Selected by first fault handler.
		goto atapiDetect;
	}
	uint8_t ch = ide_read(dev,ATA_REG_LBA1);
	uint8_t cl = ide_read(dev,ATA_REG_LBA2);
	if (cl == 0x14 && ch == 0xEB) {
atapiDetect:
		kprintf("ATAPI device detected!\r\n");
		dev->type = IDE_ATAPI;
		ide_write(dev,ATA_REG_COMMAND,ATA_CMD_IDENTIFY_PACKET);
		ata_io_wait(dev);
		// Repeat process
		while(1) {
			status = ide_read(dev,ATA_REG_STATUS);
			if ((status & ATA_SR_ERR)) {
				kprintf("Device fault fallback!\r\n");
				return false;
			}
			if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break;
		}
	} else if (cl == 0x69 && ch == 0x96) {
		kprintf("ATA device!\r\n");
	} else if (cl == 0x0 && ch == 0) {
		kprintf("We skiped something?!\r\n");
	} else {
		kprintf("Unknown type\r\n");
		return false;
	}
	uint16_t *identf = (uint16_t *)&dev->identify;
	for (int i = 0; i < 256; i++) {
		identf[i] = inw(dev->base);
	}
	kprintf("readed\n");
	uint8_t *ptr = (uint8_t *)&dev->identify.model;
	for (int i = 0; i < 39; i+=2) {
		uint8_t tmp = ptr[i+1];
		ptr[i+1] = ptr[i];
		ptr[i] = tmp;
	}
	kprintf("ATA: Detected device name: %s, type: %s\r\n",dev->identify.model,dev->type == IDE_ATAPI ? "ATAPI" : "IDE");
	ata_create_device(dev->type == IDE_ATA,dev);
	dev->type = IDE_ATA;
	return true;
}
The DMA reading code itself:

Code: Select all

static void ata_vdev_readBlock(vfs_node_t *node,int blockNo,int how, void *buf) {
        ata_device_t *dev = node->device;
        if (dev->type == IDE_ATAPI) {
                kprintf("Currently doesn't supported!\n");
                return;
        }
        void *dd = buf;
        int doCopy = false;
        uint64_t lba = blockNo;
        uint16_t sectors = how/512;
        //kprintf("Read %d sectors at %d\r\n",sectors,lba);
        if (sectors == 0) sectors = 1;
	if (sectors > 255) {
		kprintf("ATA: Maximum count of sectors that able to read per one command is 255!\n");
		sectors = 254;
	}
	if (blockNo == 0) {
		goto readActual;
	}
	kprintf("%s: called\r\n",__func__);
	// Before issusing real READ DMA command, check if we don't readed the sectors previously
readActual:
	ide_write(dev,ATA_REG_CONTROL,0);
	kprintf("%s: actual reading begin\r\n",__func__);
	// DMA support!
	outb(dev->bar4,0x00);
	outl(dev->bar4 + 0x04, dev->dma_prdt_phys);
	outb(dev->bar4 + 0x2, inb(dev->bar4 + 0x02) | 0x04 | 0x02);
	outb(dev->bar4, 0x08);
	while (1) {
		uint8_t status = inb(dev->base + ATA_REG_STATUS);
		if (!(status & ATA_SR_BSY)) break;
	}
	int cache_sectors = sectors;
        outb(dev->base+ATA_REG_HDDEVSEL,0x40);
        ata_io_wait(dev);
        outb(dev->base + ATA_REG_FEATURES, 0x01);
        outb(dev->base+ATA_REG_SECCOUNT0,(cache_sectors >> 8) & 0xFF);
	outb(dev->base+ATA_REG_LBA0, (lba & 0xff000000) >> 24);
	outb(dev->base+ATA_REG_LBA1, (lba & 0xff000000) >> 32);
	outb(dev->base+ATA_REG_LBA2, (lba & 0xff000000) >> 48);
	outb(dev->base+ATA_REG_SECCOUNT0,cache_sectors & 0xFF);
	outb(dev->base+ATA_REG_LBA0,lba & 0xFF);
	outb(dev->base+ATA_REG_LBA1,(lba >> 8) & 0xFF);
	outb(dev->base+ATA_REG_LBA2,(lba >> 16) & 0xFF);
        uint8_t i;
        // convert buffer
        uint8_t *bu = (uint8_t *)buf;
        while (1) {
		uint8_t status = inb(dev->base + ATA_REG_STATUS);
		if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break;
	}
	outb(dev->base + ATA_REG_COMMAND, ATA_CMD_READ_DMA_EXT);
	ata_io_wait(dev);

	outb(dev->bar4, 0x08 | 0x01);
	inter_ata->busy = 1;
	arch_sti();
	/*while (1) {
		int status = inb(dev->bar4 + 0x02);
		int dstatus = inb(dev->base + ATA_REG_STATUS);
		if (!(status & 0x04)) {
			continue;
		}
		if (!(dstatus & ATA_SR_BSY)) {
			break;
		}
	}
*/
	while(inter_ata->busy);
	/* Inform device we are done. */
	outb(dev->bar4 + 0x2, inb(dev->bar4 + 0x02) | 0x04 | 0x02);
        if (doCopy) {
                memcpy(dd,buf,how);
                kfree(buf);
        }
	if (sectors > 255) {
		kprintf("ATA: Sectors count are bigger that 0xFF, repeat process\n");
		sectors-=255;
		ata_vdev_readBlock(node,blockNo+255,sectors*512,bu);
	}
	// Dump the PDRT
	if (how > 8192) {
		kprintf("WARRNING: DMA support only 8 sectors :). So we copy only 8 sectors\r\n");
	}
	if (buf == NULL) {
		//kprintf("Zero buffer!!!\r\n");
		dev->latestSector = blockNo;
		return;
	}
	memcpy(bu,dev->dma_start,(how > 8192) ? 8192 : how);
	kprintf("%s: ended\r\n",__func__);
}
Current version of device block reading code is for testing, so for example we don't read the real count of cache sectors.
Also i notice that the bug/problem shows only if system boots in Legacy Mode using the EFI legacy boot or by real legacy BIOS.
EDIT: Rewrite some parts of code like in the wiki, nothing changed.

Re: ATA driver problem

Posted: Wed Apr 10, 2024 7:16 pm
by Octocontrabass
WinExperements wrote:

Code: Select all

		if ((status & ATA_SR_ERR)) {
You must wait for the BSY bit to clear before you try to interpret any other status bits.
WinExperements wrote:

Code: Select all

	} else if (cl == 0x69 && ch == 0x96) {
SATA devices only return SATA-specific signatures after reset, not after an IDENTIFY DEVICE command. This signature indicates a SATA port multiplier.
WinExperements wrote:

Code: Select all

	} else if (cl == 0x0 && ch == 0) {
This signature indicates an IDE disk.
WinExperements wrote:

Code: Select all

		kprintf("ATA: Maximum count of sectors that able to read per one command is 255!\n");
No, the maximum is 65536 with READ DMA EXT (and 256 with READ DMA).
WinExperements wrote:

Code: Select all

	while (1) {
		uint8_t status = inb(dev->base + ATA_REG_STATUS);
		if (!(status & ATA_SR_BSY)) break;
	}
Why are you polling the status register? You should already know whether you're waiting for the drive to finish the last command you sent.
WinExperements wrote:

Code: Select all

        outb(dev->base+ATA_REG_HDDEVSEL,0x40);
I'm sure it makes no difference for READ DMA EXT, but it's a good idea to use 0xE0 instead of 0x40.
WinExperements wrote:

Code: Select all

        outb(dev->base + ATA_REG_FEATURES, 0x01);
READ DMA EXT doesn't use the features register.
WinExperements wrote:

Code: Select all

	outb(dev->base+ATA_REG_LBA1, (lba & 0xff000000) >> 32);
	outb(dev->base+ATA_REG_LBA2, (lba & 0xff000000) >> 48);
These values will always be zero instead of the correct LBA bits.
WinExperements wrote:

Code: Select all

        while (1) {
		uint8_t status = inb(dev->base + ATA_REG_STATUS);
		if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break;
	}
Why are you polling the status register again?
WinExperements wrote:

Code: Select all

		kprintf("WARRNING: DMA support only 8 sectors :). So we copy only 8 sectors\r\n");
DMA is not limited to 8 sectors, and 8192 bytes is either 16 sectors or 2 sectors depending on the sector size.

How does your interrupt handler acknowledge the interrupt? I could see you receiving an interrupt storm if that isn't done correctly.

Re: ATA driver problem

Posted: Thu Apr 11, 2024 9:40 am
by WinExperements
Octocontrabass wrote: How does your interrupt handler acknowledge the interrupt? I could see you receiving an interrupt storm if that isn't done correctly.
This is my IDE IRQ handler code:

Code: Select all

void *ata_irq_handler(void *stack) {
	// ATA specifications
	kprintf("ATA IRQ!\r\n");
	inb(inter_ata->base+ATA_REG_STATUS);
    	//inb(inter_ata->base+ATA_REG_STATUS);
	if (inter_ata->busy) {
		inter_ata->busy = 0;
	}
    outb(inter_ata->bar4+4,0x00);
    /*kprintf("ATA IRQ handler!\n");
    kprintf("Status of drive(after sending there commands): 0x%x\n",inb(inter_ata->base+ATA_REG_STATUS));
    kprintf("Error register: 0x%x\n",inb(inter_ata->base+ATA_REG_ERROR));*/
    return stack;
}
Currently inter_ata is setted by the ata_vdev_readBlock before writing the command to the command register.