Linux 数据采集设备的驱动程序和 API 架构

Linux Driver and API architecture for a data acquisition device

我们正在尝试为自定义数据采集设备编写一个 driver/API,它可以捕获多个 "channels" 数据。为了便于讨论,我们假设这是一个多通道视频捕获设备。该设备通过 8xPCIe Gen-1 link 连接到系统,理论吞吐量为 16Gbps。我们的实际数据速率约为 2.8Gbps(~350MB/秒)。

由于数据速率要求,我们认为我们必须小心 driver/API 架构。我们已经实现了基于描述符的 DMA 机制和相关的驱动程序。例如,我们可以从设备启动 256KB 的 DMA 事务并成功完成。然而,在这个实现中,我们只是在内核驱动程序中捕获数据,然后将其删除,我们根本没有将数据流式传输给 user-space。本质上,这只是一个小型的 DMA 测试实现。

我们认为我们必须将问题分为三个部分:1. 内核驱动程序 2. 用户space API 3. 用户代码

采集设备在PCIe地址space有一个寄存器,表示是否有数据可以从设备读取任何通道。所以,我们的内核驱动程序必须轮询这个位向量。当内核驱动程序看到此位已设置时,它会启动 DMA 事务。然而,用户应用程序不需要知道所有这些 DMA 事务和数据,直到整个数据块准备就绪(例如,假设设备为我们提供每个事务的 16 行视频数据,但我们需要通知仅在整个视频帧准备就绪时使用)。我们只需要将整个帧传输到用户应用程序。

这是我们的第一次尝试:

  1. 我们的用户端 API 允许用户应用程序为 "channel".
  2. 注册函数回调
  3. 用户端API有一个"start"函数,可以被用户应用程序调用,它使用ioctl向内核驱动程序发送启动消息。
  4. 在内核驱动程序中,在收到启动消息后,我们启动了一个内核线程,它持续监视 "data ready" 位向量,当它看到新数据时,将其复制到驱动程序分配的(kmalloc) 缓冲区。它一直这样做,直到收集的数据的大小达到 "frame size".
  5. 此时自定义 linux 信号(类似于 SIGINT、SIGHUP 等)被发送到 运行 驱动程序的进程。我们的 API 捕获此信号,然后回调相应的用户回调函数。
  6. 用户回调函数调用API(transfer_data)中的一个函数,该函数使用ioctl调用向内核发送一个用户space缓冲区地址,内核通过对用户 space.
  7. 执行通道帧数据的 copy_to_user 来完成数据传输

除了性能很差之外,以上所有都工作正常。我们只能达到大约 2MB/秒的传输率。我们需要完全重写它,我们愿意接受任何建议或示例指针。

其他说明:

你现在可能已经过去了,但如果还没有,这就是我的 2p。

  1. 很难相信你的卡在以下情况下不能产生中断 它已传输数据。它有一个 DMA 引擎,它可以处理 'descriptors',大概是分散-聚集的元素 列表。我假设它可以生成一个 PCIe 'interrupt'; YMMV.
  2. 不要费心在内核中搜索现有的类似驱动程序。你 可能会走运,但我怀疑不会。

您需要编写一个阻塞读取,为其提供一个大内存缓冲区。驱动程序读取操作 (a) 获取用户缓冲区的用户页面列表并将它们锁定在内存中 (get_user_pages); (b) 用 pci_map_sg 创建一个散点列表; (c) 遍历列表 (for_each_sg); (d) 对于每个条目,将相应的物理总线地址和数据长度写入 DMA 控制器,我假设您正在调用 'descriptor'。

卡现在有一个描述符列表,它对应于你的大用户缓冲区的物理总线地址。当数据到达卡时,它直接将其写入用户 space,写入您的用户缓冲区,而您的用户级读取仍然被阻止。当它完成描述符列表时,卡必须能够中断,否则就没用了。驱动程序响应中断并解锁您的用户级读取。

就是这样。当然,细节很糟糕,而且文档也很少,但这应该是基本架构。如果你真的没有中断你可以在内核中设置一个定时器来轮询传输是否完成,但如果它真的是一张定制卡你应该收回你的钱。