Page 1 of 1

ata - Busmaster DMA

Posted: Tue Jan 27, 2009 4:08 pm
by AntoineKaufmann
Hi all,

I'm currently working on getting PCI DMA in ata. I got it to work on qemu but on real hardware (some VIA-Chipset) nothing seems to happen when I issue the READ DMA command. No IRQ arrives and so the driver fails with an IRQ-Timeout.

The driver works well with PIO on real hardware. I allready looked ath the Wiki entry and the intel spec linked there.

Here are the (i think) relevant snippets of code:

Code: Select all

static int ata_request_dma_init(struct ata_request* request)
{
    struct ata_device* dev = request->dev;
    struct ata_controller* ctrl = dev->controller;
    uint64_t size = request->block_size * request->block_count;

    *ctrl->prdt_virt = (uint32_t) ctrl->dma_buf_phys;
    // Groesse nicht ueber 64K, 0 == 64K
    *ctrl->prdt_virt |= (size & (ATA_DMA_MAXSIZE - 1)) << 32L;
    // Letzter Eintrag in PRDT
    *ctrl->prdt_virt |= (uint64_t) 1L << 63L;

    // Die laufenden Transfers anhalten
    cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, 0);
    cdi_outb(ctrl->port_bmr_base + BMR_STATUS,
        cdi_inb(ctrl->port_bmr_base + BMR_STATUS) | 0x06);

    // Adresse der PRDT eintragen
    cdi_outl(ctrl->port_bmr_base + BMR_PRDT, (uint32_t) ctrl->prdt_phys);

    if (request->flags.direction != READ) {
        memcpy(ctrl->dma_buf_virt, request->buffer, size);
    }

    return 1;
}


static int ata_protocol_dma(struct ata_request* request)
{
    struct ata_device* dev = request->dev;
    struct ata_controller* ctrl = dev->controller;
    
    // Aktueller Status im Protokol
    enum {
        IRQ_WAIT,
        CHECK_STATUS,
        TRANSFER_DATA
    } state;

    cdi_inb(ctrl->port_bmr_base + BMR_COMMAND);
    cdi_inb(ctrl->port_bmr_base + BMR_STATUS);
    if (request->flags.direction != READ) {
        cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, 1 | (1 << 3));
    } else {
        cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, 1);
    }
    cdi_inb(ctrl->port_bmr_base + BMR_COMMAND);
    cdi_inb(ctrl->port_bmr_base + BMR_STATUS);

    state = IRQ_WAIT;
    while (1) {
        puts("a");
        switch (state) {
            case IRQ_WAIT:
                // Auf IRQ warten
                if (!ata_wait_irq(ctrl, ATA_IRQ_TIMEOUT)) {
                    request->error = IRQ_TIMEOUT;
                    DEBUG("pio_out IRQ-Timeout\n");
                    return 0;
                }

                state = CHECK_STATUS;
                break;

            default:
            case CHECK_STATUS: {
                uint8_t status = ata_reg_inb(ctrl, REG_STATUS);
                printf("\nstat=%x\n", status);

                // Transfer abgeschlossen
                if ((status & (STATUS_BSY | STATUS_DRQ)) == 0) {
                    cdi_inb(ctrl->port_bmr_base + BMR_STATUS);
                    cdi_outb(ctrl->port_bmr_base + BMR_COMMAND, 0);
                    goto out_success;
                }
                break;
            }
        }
    }

out_success:
    if (request->flags.direction == READ) {
        memcpy(request->buffer, ctrl->dma_buf_virt,
            request->block_size * request->block_count);
    }
    return 1;
}
The second function is called after the ata registers had been initialized.

Here you can have a look at the whole driver. The read is initiated from ata_drv_rw_sectors in ata.c.

Any ideas what I'm doing wrong? I tried to enable an mwdma-Mode first using the SET FEATURES command, but that didn't change anything.

Thanks in advance.

Re: ata - Busmaster DMA

Posted: Wed Jan 28, 2009 2:14 am
by Brendan
Hi,
AntoineKaufmann wrote:I'm currently working on getting PCI DMA in ata. I got it to work on qemu but on real hardware (some VIA-Chipset) nothing seems to happen when I issue the READ DMA command. No IRQ arrives and so the driver fails with an IRQ-Timeout.
I'm curious - did you try it on other real computers? There have been reports of problems with some VIA chipsets (like this one).

If you're lucky you might be able to make sure the driver detects if DMA fails and automatically switches to PIO, and then upgrade your BIOS to get DMA working.


Cheers,

Brendan

Re: ata - Busmaster DMA

Posted: Wed Jan 28, 2009 9:52 am
by AntoineKaufmann
Brendan wrote: I'm curious - did you try it on other real computers? There have been reports of problems with some VIA chipsets (like this one).
Good point, thank you. I think I hit that bug. Just tried another machine and it worked like a charm. ;-)

Okay, it's not as fast as I hoped... If I understand the wiki article correct, the BIOS should pick the fastest possible (U)DMA-Mode, or did I misunderstand that?

Re: ata - Busmaster DMA

Posted: Wed Jan 28, 2009 11:00 am
by AntoineKaufmann
OT: In general you may be right, but this is the community OS of the german osdev community and the german comments are one of our goals, because there isn't much german information about osdev. ;-)

Re: ata - Busmaster DMA

Posted: Wed Jan 28, 2009 6:53 pm
by Firestryke31
Or have bilingual comments: German, then English.

Re: ata - Busmaster DMA

Posted: Thu Jan 29, 2009 12:46 am
by bewing
AntoineKaufmann wrote: Okay, it's not as fast as I hoped... If I understand the wiki article correct, the BIOS should pick the fastest possible (U)DMA-Mode, or did I misunderstand that?
Important point:
The speed ratings that are given for all forms of disk transfer are maximum burst-mode transfer rates. In the real world, you will probably get half that transfer rate, at best -- if your DMA driver is well-written.

The BIOS tries to pick the fastest mode. But BIOSes have bugs.
The PCI disk controller chip has a maximum (U)DMA mode, and the disk drive has a different maximum. The BIOS is supposed to set both devices to the lower of the two -- the highest mode that both of them share.
The BIOS is also supposed to make sure that UDMA mode is actually selected on the drive.

It is easy to find the maximum mode for the disk -- it is in the Identify information. The flag for the "current UDMA mode" is also there.
If you can also get the data from the PCI disk controller device, then you can check for yourself what the proper UDMA mode should be, and see if it is set right, and do some large transfers, to see if you come close to the correct speed.