Search code examples
cblockingposix-select

Using select() to detect a block on a UIO device file


I'm working on an embedded processor running Yocto. I have a modified uio_pdrv_genirq.c UIO driver.

I am writing a library to control the DMA. There is one function which writes to the device file and initiates the DMA. A second function is intended to wait for the DMA to complete by calling select(). Whilst DMA is in progress the device file blocks. On completion the DMA controller issues an interrupt which releases the block on the device file.

I have the system working as expected using read() but I want to switch to select() so that I can include a time out. However, when I use select(), it doesn't seem to be recognising the block and always returns immediately (before the DMA has completed). I have included a simple version of the code:

int gannet_dma_interrupt_wait(dma_device_t *dma_device,
        dma_direction dma_transfer_direction) {

    fd_set rfds;
    struct timeval timeout;
    int select_res;

    /* Initialize the file descriptor set and add the device file */
    FD_ZERO(&rfds);
    FD_SET(dma_device->fd, &rfds);

    /* Set the timeout period. */
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    /* The device file will block until the DMA transfer has completed. */
    select_res = select(FD_SETSIZE, &rfds, NULL, NULL, &timeout);

    /* Reset the channel */
    gannet_dma_reset(dma_device, dma_transfer_direction);

    if (select_res == -1) {
        /* Select has encountered an error */
        perror("ERROR <Interrupt Select Failed>\n");
        exit(0);
    }
    else if (select_res == 1) {
        /* The device file descriptor block released */
        return 0;
    }
    else {
        /* The device file descriptor block exceeded timeout */
        return EINTR;
    }
}

Is there anything obviously wrong with my code? Or can anyone suggest an alternative to select?


Solution

  • It turns out that the UIO driver contains two counters. One records the number of events (event_count), the other records how many events the calling function is aware of (listener->event_count).

    When you do a read() on a UIO driver it returns the number of events and makes listener->event_count equal to event_count. ie. the listener is now up to date with all the events that have occurred.

    When you use poll() or select() on a UIO driver, it checks if these two numbers are different and returns if they are (if they are the same it waits until they differ and then returns). It does NOT update the listener->event_count.

    Clearly if you do not do a read() between calls to select() then the listener->event_count will not match the event_count and the second select() will return immediately. Therefore it is necessary to call read() in between calls to select().

    With hindsight it seems clear that select() should work in this way but it wasn't obvious to me at the time.