【问题标题】:Can I select() on a /dev/spidev file descriptor?我可以在 /dev/spidev 文件描述符上选择()吗?
【发布时间】:2016-08-06 04:34:59
【问题描述】:

我正在维护一些通过 SPI 与 FPGA 通信的用户空间代码。现在正在轮询是否有数据可以采取行动,我对此并不感到兴奋。通信线程的(高度简化的)结构如下所示:

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
        }
    }
}

真的更喜欢基于事件的解决方案而不是轮询和睡眠。首先想到的是在 spidev 文件描述符上执行 select(),而不是每 X 秒检查一次寄存器,类似于

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);
}

问题是我找不到任何这样处理 SPI 的人的例子,我想知道这是否有充分的理由。 /dev/spidev 可以这样使用吗? 它会做一些愚蠢的事情,比如总是/从不“准备好阅读”?可以制作按照我想要的方式行事吗?它依赖于硬件吗?如果有必要,我不反对对内核驱动程序进行一点点黑客攻击,但我不确定是否/在哪里需要寻找。

【问题讨论】:

  • select() 应该可以工作。一旦内核缓冲区中有一个字节准备好,数据就准备好读取。但是,我不能保证设备驱动的作者没有偷工减料。
  • 如果驱动程序正常,那么select() 应该可以工作。虽然您注意到这些问题将是编写合适测试的好时机 - 即使一切都在您现在定位的设备上运行,如果您稍后尝试为设备或驱动程序构建,您将感谢测试它失败了。
  • “我真的更喜欢基于事件的解决方案” -- 如果 SPI 驱动程序因为不使用中断而强制您进行轮询,那么就没有什么神奇的了例行公事,将改变这种情况。使用 select() (可能不适用于 user-space SPI 驱动程序)只会将轮询移出您的代码,并隐藏在 libc 调用后面。如果你想要事件驱动的 I/O,那么你必须使用/编写一个驱动程序来生成和服务中断。
  • 什么是ioctl_read_some_fpga_register_that_says_if_theres_data()?听起来这是问题所在,而不是 SPI。 select 将如何帮助您?告诉是否有数据要读取的不是 SPI,而是一些 FPGA 寄存器。那个 FPGA 寄存器是否支持select?这就是你在等待的,而不是 SPI。
  • 确保您了解 SPI 的 Linux 驱动程序模型。见spi summary。 TI 文档适用于 SPI 主控制器;它的中断不是你想要的。 spidev 驱动程序是一个用户空间的 SPI 协议驱动程序,即 SPI 从设备的驱动程序,即您的 FPGA。如果您的 FPGA 可以产生中断,那么您可能会将其连接到 GPIO 以触发中断..

标签: c asynchronous embedded linux-device-driver spi


【解决方案1】:

在这可能成为 Linux SPI 驱动程序问题之前,您必须了解如何从 FPGA 获取状态信息。

除非 FPGA 正在执行诸如驱动中断或注意线之类的操作,否则 SPI 主机(可能连接到 CPU)将不得不执行 SPI 操作来轮询 FPGA。因此,除非或直到您在内核空间中有代码定期进行轮询,否则驱动程序中没有可用的信息供用户空间有意义地选择()。

如果您从 FPGA 将注意力信号返回到处理器(这取决于是否有其他东西共享它,可能就像不按顺序驱动 MISO 一样简单),那么您大概可以将其监控为中断在内核 SPI 驱动程序中,或者单独使用可以选择()的用户空间中断接口。

如果没有,您将不得不评估通过 SPI 将状态轮询移动到自定义内核驱动程序与将其留在用户空间中的权衡。

【讨论】:

    【解决方案2】:

    我可以在 /dev/spidev 文件描述符上选择()吗?

    没有。
    spidev documentation 状态

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

    更重要的是spidev driver 不支持轮询文件操作。 select() 系统调用要求设备驱动程序支持 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 };
    

    【讨论】:

    • 如果它既没有异步 I/O 支持,也没有任何方式阻塞,直到设备准备好提供数据,那是相当可悲的。 :(
    • @DavidSchwartz -- spidev 驱动程序并非旨在成为生产质量驱动程序。它是一个用户空间组件,允许在 SPI 驱动程序模型中快速开发和测试协议驱动程序。如果需要注意 SPI 从设备,通常会有硬件故障,例如 GPIO 中断。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-25
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    • 2020-10-17
    • 1970-01-01
    相关资源
    最近更新 更多