【发布时间】: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