Can I select() on a /dev/spidev file descriptor?
Asked Answered
G

2

6

I'm maintaining some userspace code that talks to a FPGA via SPI. Right now it's polling to see if there's data to act on, which I'm not thrilled about. The (heavily-simplified) structure of the comm thread looks like this:

int spi_fd;

void do_transfer(char *buf, int len)
{
    struct spi_ioc_transfer xfer;
    memset(xfer, 0, sizeof(xfer));

    ioctl_tell_some_fpga_register_heads_up();

    xfer[0].len = len;
    xfer[0].tx_buf = NULL;
    xfer[0].rx_buf = buf;
    ioctl(spi_fd, SPI_IOC_MESSAGE(1), xfer);

    ioctl_tell_some_fpga_register_were_done();
}

void *comm_thread(void arg)
{
    uint8_t config = SPI_MODE_3;
    __u32 speed = 4000000;
    char buffer[5120];

    spi_fd = open("/dev/spidev1.0", O_RDWR);
    ioctl(spi_fd, SPI_IOC_WR_MODE, &config);
    ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    while(1) {
        sleep(2); //ugh
        if(ioctl_read_some_fpga_register_that_says_if_theres_data())
        {
            do_transfer(buffer, some_small_number_of_bytes());
            do_stuff(buffer); //you get the picture
        }
    }
}

I'd really prefer an event-based solution over poll-and-sleep. The first thing that came to mind was doing a select() on the spidev file descriptor instead of checking some register every X seconds, something like

fd_set myset;

while(1) {
    FD_ZERO(&myset);
    FD_SET(spi_fd, &myset);
    select(spi_fd + 1, &myset, NULL, NULL, NULL);
    do_transfer(buffer, some_small_number_of_bytes());
    do_stuff(buffer);
}

Thing is I can't find any examples of people handling SPI like that, and I'm wondering if maybe there's a good reason for it. Can /dev/spidev be used this way? Will it do something goofy like always/never being "ready to read"? Can it be made to behave the way I want? Is it hardware dependent? I'm not averse to a little kernel driver hacking if it's necessary, but I'm not really sure if/where I need to be looking.

Goldiegoldilocks answered 14/4, 2016 at 14:29 Comment(7)
select() should work. Data is ready to read as soon as there is a single byte ready in the kernel's buffer. However, I cannot guarantee that the author of the device driver didn't cut any corners.Gaze
If the driver is sound then select() should work. While you're mindful of these issues would be a good time write a suitable test -- even if everything works on the device you're now targeting, you will be thankful for the test if you later try to build for a device or driver on which it fails.Bogusz
"I'd really prefer an event-based solution" -- If the SPI driver is forcing you to poll because it doesn't use interrupts, then there's no magical routine that will transform the situation. Using select() (which may not work with the user-space SPI driver) would only move the polling out of your code, and hide in behind a libc call. If you want event-driven I/O, then you have to use/write a driver that generates & services interrupts.Cuspidor
What is ioctl_read_some_fpga_register_that_says_if_theres_data()? It sounds like that's the issue, not SPI. How would select even help you? It's not SPI that tells whether there's data to read or not but some FPGA register. Does that FPGA register support select? That's what you're waiting for, not SPI.Pragmatic
Thanks. I'm still getting familiar with theory behind SPI interfaces. I've spent a little while reading the appropriate TI technical document for my SPI device (and a few other resources) and I'm increasingly certain that I either need to keep polling or see if my FPGA has some interrupt I can enable & service when there's data to process. Just to be sure - the Receive Interrupt in 2.7.1.2 is not what I'm looking for, right? That would only fire after I'd already lit up the bus, right?Goldiegoldilocks
I can't be sure with just what you've shown, but probably so. Try using a blocking read and see if it works.Pragmatic
Be sure you understand the Linux driver model for SPI. See spi summary. The TI doc would be for the SPI master controller; its interrupts are not want you want. The spidev driver is a user-space SPI protocol driver, i.e. a driver for the SPI slave device, your FPGA.. If your FPGA can generate an interrupt, then you'd probably connect it to a GPIO to trigger an interrupt..Cuspidor
C
2

Can I select() on a /dev/spidev file descriptor?

No.
The spidev documentation states

At this time there is no async I/O support; everything is purely synchronous.

More importantly the spidev driver does not support a poll file operation. The select() syscall requires the device driver to support a poll fops.

670 static const struct file_operations spidev_fops = {
671         .owner =        THIS_MODULE,
672         /* REVISIT switch to aio primitives, so that userspace
673          * gets more complete API coverage.  It'll simplify things
674          * too, except for the locking.
675          */
676         .write =        spidev_write,
677         .read =         spidev_read,
678         .unlocked_ioctl = spidev_ioctl,
679         .compat_ioctl = spidev_compat_ioctl,
680         .open =         spidev_open,
681         .release =      spidev_release,
682         .llseek =       no_llseek,
683 };
Cuspidor answered 14/4, 2016 at 18:24 Comment(2)
If it has neither async I/O support nor any way to block until the device is ready to provide data, that's pretty sad. :(Pragmatic
@DavidSchwartz -- The spidev driver was not intended to be a production-quality driver. It's a user-space kludge to allow rapid development and testing of a protocol driver in the SPI driver model. If the SPI slave device needs attention, there's typically a HW kludge such as a GPIO interrupt.Cuspidor
M
1

Even before this can be a Linux SPI driver question, you have to look at how status information can be obtained from the FPGA.

Unless the FPGA is doing something like driving an interrupt or attention line, the SPI master (presumably attached to the CPU) is going to have to do an SPI operation to poll the FPGA. So unless or until you have code in kernel space which periodically does that polling, there's no information available within the driver for userspace to meaningfully select() upon.

If you get an attention signal from the FPGA back to the processor (which depending on if anything else is sharing it, could be as simple as driving MISO out-of-turn) then you could presumably monitor that as an interrupt either in a kernel SPI driver, or by separately using a userspace interrupt interface on which you can select().

If not, you'll have to evaluate the tradeoffs of moving the polling of status via SPI into a custom kernel driver, vs. leaving it in userspace.

Malita answered 18/4, 2016 at 15:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.