ata dma read problems

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
szhou42
Member
Member
Posts: 67
Joined: Thu Apr 28, 2016 12:40 pm
Contact:

ata dma read problems

Post by szhou42 »

I am experimenting reading hard disk using PCI Bus Mastering DMA.

Before this I've got PIO READ working, i think most of the procedure should be similar, but I just can't get it to read things.
I tried reading the Bus Master Status Register, the interrupt bit is 1, error bit is 0, active bit is 1, which means the transfer has not finished yet.
Why would this happen ? I've set up a prdt table containing only one entry and have set the EOT bit.

Also, any advice on how to debug this kind of problem(like looking at what register would tell me what's going on and why)

Code: Select all

char * ata_read_sector(ata_dev_t * dev, uint32_t lba) {
    char * buf = kmalloc(SECTOR_SIZE);

    // Reset bus master register's command register
    outportb(dev->BMR_COMMAND, 0);

    // Clear interrupt bit and error bit in status register
    outportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02);

    // Set prdt
    outportl(dev->BMR_prdt, (uint32_t)dev->prdt_phys);

    // Set DMA in Read mode
    //outportb(dev->BMR_COMMAND, 0x08);

    // Wait until ata device is ready
    wait_device_ready(dev);

    // Allow ata device interrupts
    outportb(dev->alt_status, 0);

    // Send 0xE0 for the "master" or 0xF0 for the "slave", ORed with the highest 4 bits of the LBA to port 0x1F6
    outportb(dev->drive, ((0xE + dev->slave) << 4) | ((lba >> 24) & 0xf) );
    outportb(dev->error, 0);
    outportb(dev->sector_count, 1); // ??
    outportb(dev->lba_lo, lba & 0xff);
    outportb(dev->lba_mid, (lba & 0xff00) >> 8);
    outportb(dev->lba_high, (lba & 0xff0000) >> 16);

/*
    Uncomment this to do PIO read
    outportb(dev->command, ATA_CMD_READ_PIO);
    int times = 0;
    unsigned short * sbuf = (void*)buf;
    while (times < 256) {
        unsigned short data = inports(dev->data);
        sbuf[times] = data;
        times++;
    }
    return buf;
*/

    printf("0x%p\n", dev->prdt_phys);
    printf("prdt[0] pbuffer: 0x%p vbuffer: 0x%p, size: %u, mark: %u\n", dev->prdt[0].buffer_phys, dev->mem_buffer, dev->prdt[0].transfer_size, dev->prdt[0].mark_end);
    printf("phys of 0xc0402000 is %p\n", virtual2phys(kpage_dir, (void*)0xc0402000));


    outportb(dev->command, 0xC8);
    inportb(dev->BMR_COMMAND);
    inportb(dev->BMR_STATUS);
    outportb(dev->BMR_COMMAND, 9);

    uint8_t t;
    while(1) {
        t = inportb(dev->BMR_STATUS);
        if(t & BMR_STATUS_INT)
            break;
    }
    printf("interrupt bit set\n");
    io_wait(dev);
    for(int i = 1000*1000; i >= 0; i--) {
        for(int j = 0; j < 10000; j++);
    }
    for(int i = 0; i < 100; i++) {
        int c = dev->mem_buffer[i];
        printf("%d ", c);
    }
    printf("\n");
    memcpy(buf, dev->mem_buffer, SECTOR_SIZE);
    return buf;

}
Here is ata.c and ata.h
https://github.com/szhou42/simpleos/blo ... /src/ata.c
https://github.com/szhou42/simpleos/blo ... lude/ata.h

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

Re: ata dma read problems

Post by BenLunt »

The only thing I can see so far is that if the drive supports the Packet Interface, you must use the packet interface for DMA transfers. You must not use ATA commands 0xC8 and 0xCA if the drive supports the Packet Interface.

Have you checked if it supports ATAPI yet? Have a look at the atapi cdrom read problem thread for instructions on how to detect if it supports ATAPI or not.

If that is not the case, is your PRDTable dword aligned? Is the count of bytes to read an even number? (why wouldn't it be?) The physical address for the transfer must not cross a 64k boundary.

Does this help you any?
Ben
http://www.fysnet.net/media_storage_devices.htm
szhou42
Member
Member
Posts: 67
Joined: Thu Apr 28, 2016 12:40 pm
Contact:

Re: ata dma read problems

Post by szhou42 »

BenLunt wrote:The only thing I can see so far is that if the drive supports the Packet Interface, you must use the packet interface for DMA transfers. You must not use ATA commands 0xC8 and 0xCA if the drive supports the Packet Interface.

Have you checked if it supports ATAPI yet? Have a look at the atapi cdrom read problem thread for instructions on how to detect if it supports ATAPI or not.

If that is not the case, is your PRDTable dword aligned? Is the count of bytes to read an even number? (why wouldn't it be?) The physical address for the transfer must not cross a 64k boundary.

Does this help you any?
Ben
http://www.fysnet.net/media_storage_devices.htm
Thanks Ben

I tried the method you describe, to detect atapi, but I got all zeros for sector_count, lba_lo, lba_mid and lba_hi. I think this means packet interface is not supported?(I am using qemu)

Code: Select all

void ata_device_detect(ata_dev_t * dev, int primary) {

    // Must init some register address before detection
    ata_device_init(dev, primary);

    software_reset(dev);
    io_wait(dev);
    // Select drive, send 0xA0 to master device, 0xB0 to slave device
    outportb(dev->drive, (0xA + dev->slave) << 4);
    // Set sector counts and LBAs to 0
    outportb(dev->sector_count, 0);
    outportb(dev->lba_lo, 0);
    outportb(dev->lba_mid, 0);
    outportb(dev->lba_high, 0);
    // Send identify command to command port
    outportb(dev->command, COMMAND_IDENTIFY);
    if(!inportb(dev->status)) {
        printf("ata_detect_device: device does not exist\n");
        return;
    }
    uint8_t sector_count = inportb(dev->sector_count);
    uint8_t lba_lo = inportb(dev->lba_lo);
    uint8_t lba_mid = inportb(dev->lba_mid);
    uint8_t lba_hi = inportb(dev->lba_high);
    printf("ata siganature: %d %d %d %d\n", sector_count, lba_lo, lba_mid, lba_hi);
    // print all zeros here
My prdt table is at physical address 0x401000, virtual address 0xc0401000, so it doesn't cross 64k boundary, and is dword-aligned.
The count of bytes is set to 512.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ata dma read problems

Post by BenLunt »

This tells me that you might have an ATA device, yet you need to read in the data after the command.
Once you send the command, rather than just checking the status register for zero, check the DRQ bit. If set, read 512 bytes (256 words).
You should delay for a small period after sending the command and before reading the status register, giving the drive and controller time to function.

To truly detect if ATA or ATAPI device, you need to complete the process. If there are 256 words to read, then you have an ATA. If the DRQ bit never becomes set, then you most likely have an ATAPI device, checking the ATA registers to be sure.

Now, to get back to the original code:

Code: Select all

// Allow ata device interrupts
    outportb(dev->alt_status, 0);
Shouldn't this be the Control Register? The Control Register is at the alternative base + 02h.

Then, even with DMA, after sending the command, you must wait for the DRQ bit to be set before starting the DMA.

Code: Select all

uint8_t t;
    while(1) {
        t = inportb(dev->BMR_STATUS);
        if(t & BMR_STATUS_INT)
            break;
    }
You should wait for an actual interrupt to fire, then check the Bus Master Status Register to see if the Interrupt bit is set. The IRQ will fire for other reasons or for another device. Checking the Bus Master Status Register is to verify that the IRQ was for this device and this channel.

Here is the process I use: (using DMA on ATA device with PCI Bus Mastering)
1. select the drive (using LBA's and HIGHNIBBLE(LBA))
2. setup the DMA
3. wait for the READY bit to be set (bit 6)
4. write the FEATURES register (0x00)
5. write the SECTOR_COUNT register (LOW_BYTE(count))
6. write the LOW_BYTE register (LOW_BYTE(LBA))
7. write the MID_BYTE register (MID_BYTE(LBA))
8. write the HIGH_BYTE register (HIGH_BYTE(LBA))
9. write the READ_DMA to the command register (0xC8)
10. wait for the DRQ bit to be set
11. start the DMA
12. wait for the interrupt...

Try that.

Also, make sure you are using the correct channel on the DMA Bus Master.

Hope this helps,
Ben
szhou42
Member
Member
Posts: 67
Joined: Thu Apr 28, 2016 12:40 pm
Contact:

Re: ata dma read problems

Post by szhou42 »

BenLunt wrote:This tells me that you might have an ATA device, yet you need to read in the data after the command.
Once you send the command, rather than just checking the status register for zero, check the DRQ bit. If set, read 512 bytes (256 words).
You should delay for a small period after sending the command and before reading the status register, giving the drive and controller time to function.

To truly detect if ATA or ATAPI device, you need to complete the process. If there are 256 words to read, then you have an ATA. If the DRQ bit never becomes set, then you most likely have an ATAPI device, checking the ATA registers to be sure.

Now, to get back to the original code:

Code: Select all

// Allow ata device interrupts
    outportb(dev->alt_status, 0);
Shouldn't this be the Control Register? The Control Register is at the alternative base + 02h.

Then, even with DMA, after sending the command, you must wait for the DRQ bit to be set before starting the DMA.

Code: Select all

uint8_t t;
    while(1) {
        t = inportb(dev->BMR_STATUS);
        if(t & BMR_STATUS_INT)
            break;
    }
You should wait for an actual interrupt to fire, then check the Bus Master Status Register to see if the Interrupt bit is set. The IRQ will fire for other reasons or for another device. Checking the Bus Master Status Register is to verify that the IRQ was for this device and this channel.

Here is the process I use: (using DMA on ATA device with PCI Bus Mastering)
1. select the drive (using LBA's and HIGHNIBBLE(LBA))
2. setup the DMA
3. wait for the READY bit to be set (bit 6)
4. write the FEATURES register (0x00)
5. write the SECTOR_COUNT register (LOW_BYTE(count))
6. write the LOW_BYTE register (LOW_BYTE(LBA))
7. write the MID_BYTE register (MID_BYTE(LBA))
8. write the HIGH_BYTE register (HIGH_BYTE(LBA))
9. write the READ_DMA to the command register (0xC8)
10. wait for the DRQ bit to be set
11. start the DMA
12. wait for the interrupt...

Try that.

Also, make sure you are using the correct channel on the DMA Bus Master.

Hope this helps,
Ben
Hi Ben, your advice is very helpful! I modified my code to closely follow your process, but it still doesn't work in qemu. However, it works perfectly fine with bochs, I can read a sector from hard disk with no problem.

What I observed is that, in qemu, after the ata interrupt is fired, the bus master status is 0x5(0b101). So the interrupt bit is 1, active bit is also 1, as usual.
In bochs, the interrupt bit remains 1, however, the active bit becomes 0.
That means in bochs all entries in the prdt table are processed and data sent to memory.

Does this mean anything to you ? I am just trying to gain some insight from this difference, but I can't think of anything because I have little experience programming the hardware.

ps: I think the control register use the same port number as the alternative status register(0x3F6 For primary), so this should be fine.

This is ata_read_sector() function:

Code: Select all

char * ata_read_sector(ata_dev_t * dev, uint32_t lba) {
    uint8_t t;
    char * buf = kmalloc(SECTOR_SIZE);

    // Select drive
    outportb(dev->drive, ((0xE + dev->slave) << 4) | ((lba >> 24) & 0xf) );

    // Reset bus master register's command register
    outportb(dev->BMR_COMMAND, 0);

    // Clear interrupt bit and error bit in status register
    outportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02);

    // Set prdt
    outportl(dev->BMR_prdt, (uint32_t)dev->prdt_phys);

    // Set DMA in Read mode
    outportb(dev->BMR_COMMAND, 0x08);

    // Wait until device is ready
    do {
        t = inportb(dev->status);
    }while(t & STATUS_RDY == 0);

    // Allow ata device interrupts
    outportb(dev->alt_status, 0);

    // Set feature/error register to 0
    outportb(dev->error, 0);

    // Set sector counts and LBAs
    outportb(dev->sector_count, 1); // ??
    outportb(dev->lba_lo, lba & 0xff);
    outportb(dev->lba_mid, (lba & 0xff00) >> 8);
    outportb(dev->lba_high, (lba & 0xff0000) >> 16);


/*
    //Uncomment this to do PIO read
    outportb(dev->command, ATA_CMD_READ_PIO);

    int times = 0;
    unsigned short * sbuf = (void*)buf;
    while (times < 256) {
        unsigned short data = inports(dev->data);
        sbuf[times] = data;
        times++;
    }
    return buf;
*/

    printf("0x%p\n", dev->prdt_phys);
    printf("prdt[0] pbuffer: 0x%p vbuffer: 0x%p, size: %u, mark: %u\n", dev->prdt[0].buffer_phys, dev->mem_buffer, dev->prdt[0].transfer_size, dev->prdt[0].mark_end);
    printf("phys of 0xc0402000 is %p\n", virtual2phys(kpage_dir, (void*)0xc0402000));

    // Write the READ_DMA to the command register (0xC8)
    outportb(dev->command, 0xC8);

    printf("Wait for DRQ bit to be set\n");
    do {
        t = inportb(dev->status);
    }while(t & STATUS_DRQ == 0);

    // Start DMA reading
    outportb(dev->BMR_COMMAND, 0x8 | 0x1);

    io_wait(dev);

    memcpy(buf, dev->mem_buffer, SECTOR_SIZE);
    return buf;

}
This is my ata interrupt handler

Code: Select all

void ata_handler(register_t * reg) {
    printf("ata interrupt arrived\n");
    uint32_t s1 = inportb(primary_master.status);
    uint32_t s2 = inportb(primary_master.BMR_STATUS);
    printf("s1 = %u s2 = %u\n", s1, s2);
    outportb(primary_master.BMR_COMMAND, BMR_COMMAND_DMA_STOP);

    for(int i = 0; i < 512; i++) {
        int c = primary_master.mem_buffer[i];
        printf("%d ", c);
    }
    printf("\n");
    irq_ack(14);
}
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ata dma read problems

Post by BenLunt »

szhou42 wrote:I modified my code to closely follow your process, but it still doesn't work in qemu. However, it works perfectly fine with bochs, I can read a sector from hard disk with no problem.

What I observed is that, in qemu, after the ata interrupt is fired, the bus master status is 0x5(0b101). So the interrupt bit is 1, active bit is also 1, as usual.
In bochs, the interrupt bit remains 1, however, the active bit becomes 0.
...
Does this mean anything to you ?
This tells me that QEMU might be buggy :-)

Glad that I could help.

Without looking over your code more thoroughly, and watching what actually happens, I couldn't tell you. I stopped using QEMU long ago when I found Bochs to be more of my taste. I have not had a look at QEMU since. Maybe it is time to go back and have a look.

Does QEMU have a bug reporting page? Work with your code in QEMU a little more, making sure that the active bit never clears. If so, then report your findings.
Some things to check:
1. wait a few mS then read the status again. Maybe QEMU takes a little while to clear it.
2. make sure you requested the exact same amount of bytes that the Bus Master is trying to transfer.
i.e: if you requested 514 bytes in the bus master, but only 512 are read from disk, it will wait for 2 more bytes before it clears the active bit.
3. detail your findings, and even give them code to reproduce the error.

Anyway, glad to help
Ben
szhou42
Member
Member
Posts: 67
Joined: Thu Apr 28, 2016 12:40 pm
Contact:

Re: ata dma read problems

Post by szhou42 »

BenLunt wrote:
szhou42 wrote:I modified my code to closely follow your process, but it still doesn't work in qemu. However, it works perfectly fine with bochs, I can read a sector from hard disk with no problem.

What I observed is that, in qemu, after the ata interrupt is fired, the bus master status is 0x5(0b101). So the interrupt bit is 1, active bit is also 1, as usual.
In bochs, the interrupt bit remains 1, however, the active bit becomes 0.
...
Does this mean anything to you ?
This tells me that QEMU might be buggy :-)

Glad that I could help.

Without looking over your code more thoroughly, and watching what actually happens, I couldn't tell you. I stopped using QEMU long ago when I found Bochs to be more of my taste. I have not had a look at QEMU since. Maybe it is time to go back and have a look.

Does QEMU have a bug reporting page? Work with your code in QEMU a little more, making sure that the active bit never clears. If so, then report your findings.
Some things to check:
1. wait a few mS then read the status again. Maybe QEMU takes a little while to clear it.
2. make sure you requested the exact same amount of bytes that the Bus Master is trying to transfer.
i.e: if you requested 514 bytes in the bus master, but only 512 are read from disk, it will wait for 2 more bytes before it clears the active bit.
3. detail your findings, and even give them code to reproduce the error.

Anyway, glad to help
Ben
I also noticed that in the ata interrupt handler, the DF(Device Fault) bit of the status byte is set.
I looked this up in the specs, this is what it tells me
If the device enters a condition where continued operation could affect user data integrity, the device shall
set the DF bit in the status register and no longer accept commands. This condition is only cleared by power
cycling the drive. Once the DF bit has been cleared it may remain clear until a command that could affect
user data integrity is received by the device. Examples of conditions that may cause the DF bit to be set by
a device are: Failure to spin-up properly, and no spares remaining for reallocation.
I think the first cause "Failure to spin-up properly" is more reasonable(since I don't understand the second at all :wink: )
Are there common beginner mistake that would cause to disk to not spin up properly ?
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ata dma read problems

Post by BenLunt »

szhou42 wrote:
If the device enters a condition where continued operation could affect user data integrity, the device shall
set the DF bit in the status register and no longer accept commands. This condition is only cleared by power
cycling the drive. Once the DF bit has been cleared it may remain clear until a command that could affect
user data integrity is received by the device. Examples of conditions that may cause the DF bit to be set by
a device are: Failure to spin-up properly, and no spares remaining for reallocation.
I think the first cause "Failure to spin-up properly" is more reasonable(since I don't understand the second at all :wink: )
Are there common beginner mistake that would cause to disk to not spin up properly ?
Outside of power saving services, sleep, etc., you have no control over whether the drive spins up properly. If the drive doesn't spin up properly, you have a faulty drive. As for the reallocation, without reading the specifications to give me the correct context, I would bet this means that it has ran out of spare sectors. If the drive finds a faulty sector, it will use a spare sector in its place. The OS has no idea of the spare sector, but the drive hardware knows to use that spare when the faulty one is requested. If it has no more spare sectors, it won't be able to save the disk from losing data due to faulty sectors.
szhou42
Member
Member
Posts: 67
Joined: Thu Apr 28, 2016 12:40 pm
Contact:

Re: ata dma read problems

Post by szhou42 »

BenLunt wrote:
szhou42 wrote:
If the device enters a condition where continued operation could affect user data integrity, the device shall
set the DF bit in the status register and no longer accept commands. This condition is only cleared by power
cycling the drive. Once the DF bit has been cleared it may remain clear until a command that could affect
user data integrity is received by the device. Examples of conditions that may cause the DF bit to be set by
a device are: Failure to spin-up properly, and no spares remaining for reallocation.
I think the first cause "Failure to spin-up properly" is more reasonable(since I don't understand the second at all :wink: )
Are there common beginner mistake that would cause to disk to not spin up properly ?
Outside of power saving services, sleep, etc., you have no control over whether the drive spins up properly. If the drive doesn't spin up properly, you have a faulty drive. As for the reallocation, without reading the specifications to give me the correct context, I would bet this means that it has ran out of spare sectors. If the drive finds a faulty sector, it will use a spare sector in its place. The OS has no idea of the spare sector, but the drive hardware knows to use that spare when the faulty one is requested. If it has no more spare sectors, it won't be able to save the disk from losing data due to faulty sectors.
Still can't get it working in qemu. But thank you so much Ben, your advice are very helpful, at least it works in bochs now.
szhou42
Member
Member
Posts: 67
Joined: Thu Apr 28, 2016 12:40 pm
Contact:

Re: ata dma read problems

Post by szhou42 »

I've eventually solved it by comparing my code with other's.

It turns out that it's because I didn't set the Bus Master bit(bit 2) in the pci command register.
(http://wiki.osdev.org/PCI#Detecting_Con ... hanism.2Fs, search keyword "bus master")

Since bus master bit is not set, any PCI access(like write my prdt table address to bar4 + 4) would fail.

In bochs, this bus master bit is not automatically set either, but it just works :roll:

easy fix:

Code: Select all

    uint32_t pci_command_reg = pci_read(ata_device, PCI_COMMAND);
    if(!(pci_command_reg & PCI_BUS_MASTER)) {
        pci_command_reg |= PCI_BUS_MASTER;
        pci_write(ata_device, PCI_COMMAND, pci_command_reg);
    }
I hope this helps someone having the same problem.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: ata dma read problems

Post by BenLunt »

Yep, you got it. Sorry, I didn't even think to mention it, though I should have. Sorry :-)

Glad that you were able to get it. I withdraw my comment about a buggy QEMU and may need to look into why Bochs has it set already whether it is by design or if it should be cleared initially.

Ben
Post Reply