IDE DMA driver

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.
solarius
Posts: 20
Joined: Mon Sep 27, 2010 4:45 am

IDE DMA driver

Post by solarius »

Hello everybody,

I'm writting a little OS with a friend, and I used a lot OSDev wiki. Now, I'm working on an IDE/DMA driver and I have some problems.

The context: We're writting an OS for x86 processors. It is multitask, use one CPU, doesn't have virtual (paged) memory. We run it in VirtualBox (sometimes QEMU or real hardware).

The problem: I try to read something once on the first disk of the first controller, using a DMA method. When I get the corresponding interrupt (IRQ14) the Busmaster status register has the value 0xFF, the IDE status byte is 0x00. If I try a second call, no interrupt happens.

The most weird is that the behaviour depends on configuration in VirtualBox. I mean if I put only one disk on the controller, I get nothing (no interruption after the first read). The PCI configuration space gives me "Int Line = 0x00". But if I put two disks on the controller, I get the behaviour described above and "Int Line = 0xd" :shock:. In QEMU, the behaviour is the above's one for one or two disks.

Informations on the method I use:
I read the PCI configuration space to detect IDE controllers. Then, I detect ATA IDE disks (I ignore ATAPI and SATA drives) using the method described in the wiki (http://wiki.osdev.org/ATA_PIO_Mode#Dete ... ialization). Finally I call a method which does the DMA and the ATA commands to read the disk.

I probably forgot a configuration step for the controller or the ATA drive but I don't found it. Can you help me ?

Thank you.

[edit] My DMA code here:

Code: Select all

/* Physical Descriptor structure */
typedef union _prd_t {
        uint64_t qword;
        struct {
                unsigned int phy_mem_add:32;        
                unsigned int byte_count:16;        
                unsigned int reserved:15;           
                unsigned int last:1;           
        } details __attribute__ ((packed));
} prd_t;

/* DMA registers */
typedef enum _dma_reg_t {
        DMA_CMD     = 0x0,
        DMA_STATUS  = 0x2,
        DMA_PRDT0   = 0x4,
        DMA_PRDT1   = 0x5,
        DMA_PRDT2   = 0x6,
        DMA_PRDT3   = 0x7,
} dma_reg_t;

/* DMA commands*/
#define DMA_STOP                0x00
#define DMA_START               0x01
#define DMA_WRITE               (1 << 3)
#define DMA_READ                0x00

/* One prdt for the tests */
prd_t essai __attribute__ ((aligned (4)));
drive_t *current = NULL;

/* Traitant d'interruptions */
void ide_handler(void) {

        uint8_t reg;

        /* Acquitter les interruptions des PICs maitres et esclaves */
        ACK_IT_S;
        ACK_IT_M;

        printf("Interruption:\n");

        if (!current) {
                printf("No request !\n");
                return;
        }
        
        /* Interruption came from the disk ? */
        reg = inb(current->dma_reg + DMA_STATUS);
        outb(inb(current->dma_reg + DMA_STATUS)&~1, current->dma_reg + DMA_STATUS);  /* Stop DMA */

        printf("\tDMA_STATUS = 0x%x\n", reg);

        current = NULL;
}

/*
 * Initialize DMA giving a prdt and a READ/WRITE command.
 */
static void ide_dma_init(drive_t *drive, int count, char *buffer) {

        uint8_t tmp_dma_reg;

        /* Construire l'entrée PRD */
        essai.details.last = 1;
        essai.details.byte_count = (unsigned int) count * 512;
        essai.details.phy_mem_add = (unsigned int) buffer;
        essai.details.reserved = 0;

        // TODO Create a PRDT.

        /* Put controller in stad-by */
        outb(DMA_STOP, drive->dma_reg + DMA_CMD);
        tmp_dma_reg = (uint8_t) inb(drive->dma_reg + DMA_STATUS); /* Effacer interrupt et error */
        outb(tmp_dma_reg | 0x6, drive->dma_reg + DMA_STATUS);

        /* Load PRDT in DMA register */
        outl((uint32_t)&essai, drive->dma_reg + DMA_PRDT0);

        /* Start DMA transfert */
        outb(DMA_START | DMA_READ, drive->dma_reg + DMA_CMD);
}

/* Send a LBA28 request */
static void ide_dma_lba28_cmd(drive_t *drive, uint64_t block, uint16_t count, uint8_t cmd) {
    
        /* Select MASTER or SLAVE */
        outb(drive->level << 4, drive->base_reg + ATA_REG_HDDEVSEL);
        ATA_WAIT_READY(drive);

        /* Wait for the disk to be ready
         * TODO: get and work with the return value */
        ide_wait_drive_ready(drive);

        /* Select the drive, send high part of LBA */
        outb(0xE0 | (drive->level << 4) | ((count & 0xF000000) >> 24), drive->base_reg + ATA_REG_HDDEVSEL);
        ide_wait_drive_ready(drive);

        /* Activate interruption in controller "control register" */
        outb(0, drive->dcr_reg);

        /* Send sector count */
        outb((uint8_t)count, drive->base_reg + ATA_REG_SECCOUNT);

        /* Send LBA address */
        outb((uint8_t)(block), drive->base_reg + ATA_REG_LBA0);
        outb((uint8_t)(block >> 8), drive->base_reg + ATA_REG_LBA1);
        outb((uint8_t)(block >> 16), drive->base_reg + ATA_REG_LBA2);

        /* Send command */
        outb(cmd, drive->base_reg + ATA_REG_COMMAND);
        ATA_WAIT_READY(drive);
}

int ide_dma_read(int drive_num, uint64_t block, uint16_t count, char *buffer)
{
        drive_t *drive;

        if (UNLIKELY(buffer == NULL)) {
                return RET_ERROR;
        }
        if (UNLIKELY(count == 0)) /* Nothing to do, go out */
                return RET_OK;

        /* Get the drive structure */
        drive = drives[drive_num];
        if (UNLIKELY(drive == NULL)) {
                return RET_ERROR;
        }

        /* Block while previous read is not finished
         * TODO Block this processus with the scheduler not with its bad way */
        while (current != NULL) hlt();
        current = drive;

        /* Configure the DMA */
        ide_dma_init(drive, count, buffer);

        /* Perform IDE request */
        if (drive->add_mode == LBA28) {
                ide_dma_lba28_cmd(drive, block, count, ATA_CMD_READ_DMA_LBA28);
        } else if (drive->add_mode == LBA48) {
                ide_dma_lba48_cmd(drive, block, count, ATA_CMD_READ_DMA_LBA48);
        } else
                return RET_ERROR;


        return RET_OK;
}
solarius
Posts: 20
Joined: Mon Sep 27, 2010 4:45 am

Re: IDE DMA driver

Post by solarius »

Nobody can help me ? :cry:
jal
Member
Member
Posts: 1385
Joined: Wed Oct 31, 2007 9:09 am

Re: IDE DMA driver

Post by jal »

solarius wrote:Nobody can help me?
Apparently. Most people use PIO, not DMA, since it is aparently easier to do so. That's not to say you should abandon DMA of course, as it should give you better throughput.


JAL
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Re: IDE DMA driver

Post by hrniels »

I do it a bit differently. At first I set up the ATA-command, i.e. ide_dma_lba28_cmd() in your case. Afterwards I set up the DMA-transfer. Additionally before and after the DMA-start-command I read the command- and status-register once. I'm not sure if thats necessary, but I've seen that in some other OSs and apparently it doesn't hurt :)
After the DMA-transfer has been started, I wait for an interrupt. As soon as I got one, I check the ATA-status-register and controller-status-register and finally I write a zero to the controller-command-register.

This way ATA- and ATAPI-DMA works for me in bochs, qemu, virtualbox and vmware. Unfortunatly only on 1 of 3 of my real machines. So it seems that it isn't completely correct yet. But perhaps it helps you anyway :)
jal
Member
Member
Posts: 1385
Joined: Wed Oct 31, 2007 9:09 am

Re: IDE DMA driver

Post by jal »

hrniels wrote:Unfortunatly only on 1 of 3 of my real machines.
Did you find out where it malfunctions on those machines? On the same point in all of them? On different points?


JAL
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Re: IDE DMA driver

Post by hrniels »

jal wrote:
hrniels wrote:Unfortunatly only on 1 of 3 of my real machines.
Did you find out where it malfunctions on those machines? On the same point in all of them? On different points?
I've just checked it out again. On one machine it works now if I enable all my debugging-outputs (otherwise I get a timeout while waiting for an interrupt). So it seems to a timing-issue somewhere. It showed definitely a different behaviour last time. Don't ask me what happened :D I have to analyze that more detailed again.
Anyway, on the other machine its like last time I checked it. I don't get an interrupt after the transfer has been started and when I check the status-register the value is 0xd0 (and doesn't change), which means according to the ATA-standard BSY is set and DRDY is set. Perhaps its even the same issue as on the other machine, but only shows up differently. I don't know..
solarius
Posts: 20
Joined: Mon Sep 27, 2010 4:45 am

Re: IDE DMA driver

Post by solarius »

hrniels wrote:I do it a bit differently. At first I set up the ATA-command, i.e. ide_dma_lba28_cmd() in your case. Afterwards I set up the DMA-transfer. Additionally before and after the DMA-start-command I read the command- and status-register once. I'm not sure if thats necessary, but I've seen that in some other OSs and apparently it doesn't hurt :)
After the DMA-transfer has been started, I wait for an interrupt. As soon as I got one, I check the ATA-status-register and controller-status-register and finally I write a zero to the controller-command-register.
I did that and it changed the situation. I get two interruptions for one read request. If I send 0 to the controller command register in first, I got a 0x41 value in the status register, if I do it after reading the status register, this one is set to 0x58.

Here is my handler code:

Code: Select all

/* Interrupt handler */
void ide_handler(void) {

        uint8_t reg;

        /* ACK interruptions */
        ACK_IT_S;
        ACK_IT_M;

        printf("Interrupt handler:\n");

        if (!current) {
                printf("No request !\n");
                return;
        }
        
        reg = inb(current->base_reg + ATA_REG_STATUS);
        printf("\nATA STATUS: 0x%x\n", reg);
        reg = inb(current->dcr_reg);            /* Device control register */
        printf("\tDCR = 0x%x\n", reg);

        outb(0, current->base_reg + ATA_REG_COMMAND);

        current = NULL;
}
I'll probably work on this its week end. I'll keep you informed.
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Re: IDE DMA driver

Post by hrniels »

solarius wrote:I did that and it changed the situation. I get two interruptions for one read request. If I send 0 to the controller command register in first, I got a 0x41 value in the status register, if I do it after reading the status register, this one is set to 0x58.
It seems to be a better strategie to do it like that:

Code: Select all

static volatile int gotInterrupt = 0;
void ide_handler(void) {
  gotInterrupt = 1;
}

while(!gotInterrupt)
  wait();
At least, I get multiple interrupts sometimes as well.

To the status-codes: 0x41 contains the ERR-bit.
After I got the interrupt, I wait (if necessary) until BSY and DRQ in the status-register cleared. Afterwards I read the controller-status-register and write a 0 to the controller-command-register.
solarius
Posts: 20
Joined: Mon Sep 27, 2010 4:45 am

Re: IDE DMA driver

Post by solarius »

hrniels wrote: To the status-codes: 0x41 contains the ERR-bit.
After I got the interrupt, I wait (if necessary) until BSY and DRQ in the status-register cleared. Afterwards I read the controller-status-register and write a 0 to the controller-command-register.
I try your method, it seems to be better. The first interruption return a status code of 0x88 (BSY & DRQ) but they never clears. If I try to recall the read procedure, I get the interruptions too, but the status register are: 0x65 (ERR & DF & RDY)
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Re: IDE DMA driver

Post by hrniels »

solarius wrote:I try your method, it seems to be better. The first interruption return a status code of 0x88 (BSY & DRQ) but they never clears. If I try to recall the read procedure, I get the interruptions too, but the status register are: 0x65 (ERR & DF & RDY)
Have you checked bochs's debugging-outputs? Perhaps they give you a hint whats wrong. At least I would try to get it working on bochs and qemu first, because that should be the easiest task :)
xyzzy
Member
Member
Posts: 391
Joined: Wed Jul 25, 2007 8:45 am
Libera.chat IRC: aejsmith
Location: London, UK
Contact:

Re: IDE DMA driver

Post by xyzzy »

Your definitions of DMA_WRITE and DMA_READ are the wrong way round. You must set bit 3 in the command register for a read from the device, clear it for a write.

Edit: I noticed that the wiki article didn't mention this at all, I've just added a bit about it.
solarius
Posts: 20
Joined: Mon Sep 27, 2010 4:45 am

Re: IDE DMA driver

Post by solarius »

AlexExtreme wrote:Your definitions of DMA_WRITE and DMA_READ are the wrong way round. You must set bit 3 in the command register for a read from the device, clear it for a write.

Edit: I noticed that the wiki article didn't mention this at all, I've just added a bit about it.
I try it, but nothing change. But, I'm not OK with that, in this http://www.osdever.net/downloads/docs/idems100.zip documentation (from the "ATA/ATAPI using DMA" article on the wiki) I found the contrary. If your code work with that, I'll take it :wink:
hrniels wrote:Have you checked bochs's debugging-outputs? Perhaps they give you a hint whats wrong. At least I would try to get it working on bochs and qemu first, because that should be the easiest task :)
My error codes were wrong, on virtual box, I get 0x58 first, and then 0x41 and I'm able to throw interrupts without problems. The wait function expire just before the 0x58, the bits don't clear themselves. On QEMU, I get 0xd8 and I can only throw 2 interrupts.
Last edited by solarius on Tue Oct 05, 2010 6:19 am, edited 1 time in total.
xyzzy
Member
Member
Posts: 391
Joined: Wed Jul 25, 2007 8:45 am
Libera.chat IRC: aejsmith
Location: London, UK
Contact:

Re: IDE DMA driver

Post by xyzzy »

My code works with what I said, and it is also what Linux does. As far as I'm aware, what is meant by "bus master read" in the spec is a read from the computer's memory, to send data to the device (i.e. writing to the device).
solarius
Posts: 20
Joined: Mon Sep 27, 2010 4:45 am

Re: IDE DMA driver

Post by solarius »

I run my code into Bochs and I get the following debugging output:

Code: Select all

07627126927d[HD   ] 8-bit write to 01f6 = 00 {DISK}
07627126927d[HD   ] IO write 0x01f6 (00): not 1x1xxxxxb
07627126942d[HD   ] 8-bit read from 03f6 = 50 {DISK}
07627126959d[HD   ] 8-bit read from 03f6 = 50 {DISK}
07627126976d[HD   ] 8-bit read from 03f6 = 50 {DISK}
07627127001d[HD   ] 8-bit read from 01f7 = 50 {DISK}
07627127043d[HD   ] 8-bit write to 01f6 = e0 {DISK}
07627127043d[HD   ] enabling LBA mode
07627127061d[HD   ] 8-bit write to 03f6 = 00 {DISK}
07627127061d[HD   ] ata0: adapter control reg: reset controller = 0
07627127061d[HD   ] ata0: adapter control reg: disable irq = 0
07627127061d[HD   ] ata0: adapter control reg: disable irq = 0
07627127083d[HD   ] 8-bit write to 01f2 = 00 {DISK}
07627127083d[HD   ] sector count = 0 {DISK}
07627127107d[HD   ] 8-bit write to 01f3 = 00 {DISK}
07627127107d[HD   ] sector number = 0 {DISK}
07627127131d[HD   ] 8-bit write to 01f4 = 00 {DISK}
07627127131d[HD   ] cylinder low = 00h {DISK}
07627127156d[HD   ] 8-bit write to 01f5 = 00 {DISK}
07627127156d[HD   ] cylinder high = 00h {DISK}
07627127177d[HD   ] 8-bit write to 01f2 = 01 {DISK}
07627127177d[HD   ] sector count = 1 {DISK}
07627127198d[HD   ] 8-bit write to 01f3 = 01 {DISK}
07627127198d[HD   ] sector number = 1 {DISK}
07627127222d[HD   ] 8-bit write to 01f4 = 00 {DISK}
07627127222d[HD   ] cylinder low = 00h {DISK}
07627127246d[HD   ] 8-bit write to 01f5 = 00 {DISK}
07627127246d[HD   ] cylinder high = 00h {DISK}
07627127266d[HD   ] 8-bit write to 01f7 = 25 {DISK}
07627127281d[HD   ] 8-bit read from 03f6 = 58 {DISK}
07627127298d[HD   ] 8-bit read from 03f6 = 58 {DISK}
07627127315d[HD   ] 8-bit read from 03f6 = 58 {DISK}
The problem I see is that Bochs create a LBA48 disk, so I can't compare with the other virtual machines. In this result I don't see anything wrong. I have a doubt, must I transform the LBA address to CHS when I'm in LBA mode ?
hrniels
Member
Member
Posts: 53
Joined: Wed Nov 05, 2008 5:18 am
Location: Marburg, Germany

Re: IDE DMA driver

Post by hrniels »

I've just checked what bochs logs for me:

Code: Select all

00152743142d[HD   ] 8-bit write to 01f6 = 40 {DISK}
00152743142d[HD   ] IO write 0x01f6 (40): not 1x1xxxxxb
00152743143d[CPU0 ] page walk for address 0x9ffffe5c
00152743149d[CPU0 ] page walk for address 0x9ffffe90
00152743162d[IOAP ] set_irq_level(): INTIN14: level=0
00152743162d[HD   ] 8-bit read from 01f7 = 50 {DISK}
00152743173d[IOAP ] set_irq_level(): INTIN14: level=0
00152743173d[HD   ] 8-bit read from 01f7 = 50 {DISK}
00152743184d[IOAP ] set_irq_level(): INTIN14: level=0
00152743184d[HD   ] 8-bit read from 01f7 = 50 {DISK}
00152743195d[IOAP ] set_irq_level(): INTIN14: level=0
00152743195d[HD   ] 8-bit read from 01f7 = 50 {DISK}
00152743242d[FDD  ] write access to port 0x03f6, value=0x00
00152743242d[FDD  ] io_write: reserved register 0x3f6 unsupported
00152743242d[HD   ] 8-bit write to 03f6 = 00 {DISK}
00152743242d[HD   ] ata0: adapter control reg: reset controller = 0
00152743242d[HD   ] ata0: adapter control reg: disable irq = 0
00152743242d[HD   ] ata0: adapter control reg: disable irq = 0
00152743286d[HD   ] 8-bit write to 01f2 = 00 {DISK}
00152743286d[HD   ] sector count = 0 {DISK}
00152743322d[HD   ] 8-bit write to 01f3 = 00 {DISK}
00152743322d[HD   ] sector number = 0 {DISK}
00152743358d[HD   ] 8-bit write to 01f4 = 00 {DISK}
00152743358d[HD   ] cylinder low = 00h {DISK}
00152743395d[HD   ] 8-bit write to 01f5 = 00 {DISK}
00152743395d[HD   ] cylinder high = 00h {DISK}
00152743428d[HD   ] 8-bit write to 01f2 = 02 {DISK}
00152743428d[HD   ] sector count = 2 {DISK}
00152743461d[HD   ] 8-bit write to 01f3 = a5 {DISK}
00152743461d[HD   ] sector number = 165 {DISK}
00152743497d[HD   ] 8-bit write to 01f4 = 59 {DISK}
00152743497d[HD   ] cylinder low = 59h {DISK}
00152743533d[HD   ] 8-bit write to 01f5 = 00 {DISK}
00152743533d[HD   ] cylinder high = 00h {DISK}
00152743567d[HD   ] 8-bit write to 01f7 = 25 {DISK}
00152743567d[IOAP ] set_irq_level(): INTIN14: level=0
00152743644d[PIDE ] BM-DMA write command register, channel 0, value = 0x00
00152743667d[PIDE ] BM-DMA read status register, channel 0, value = 0x04
00152743701d[PIDE ] BM-DMA write status register, channel 0, value = 0x06
00152743729d[PIDE ] BM-DMA write DTP register, channel 0, value = 0x2d4000
00152743756d[PIDE ] BM-DMA read command register, channel 0, value = 0x00
00152743779d[PIDE ] BM-DMA read status register, channel 0, value = 0x00
00152743811d[PIDE ] BM-DMA write command register, channel 0, value = 0x09
00152743835d[PIDE ] BM-DMA read command register, channel 0, value = 0x09
00152743858d[PIDE ] BM-DMA read status register, channel 0, value = 0x01
00152743887d[CPU0 ] interrupt(): vector = 30, TYPE = 4, EXT = 0, EBP=9ffffe98
...
00152763811d[HD   ] raising interrupt 14 {DISK}
I see the following differences:
  • You read the io-port 0x3f6 3 times for a short delay, I guess. I use the status-register of the affected controller.
  • After issuing the command, you read 0x3f6 again 3 times. I don't.
Though, I'm not sure if its necessary to do it like I do it. So I wouldn't change yours, if it does not get it working.

Edit:
The problem I see is that Bochs create a LBA48 disk, so I can't compare with the other virtual machines. In this result I don't see anything wrong. I have a doubt, must I transform the LBA address to CHS when I'm in LBA mode ?
No, in LBA-mode simply use the LBA :)
Post Reply