SPI 用户空间 API

SPI 设备的用户空间 API 功能有限,支持对 SPI 从设备进行基本的半双工 read() 和 write() 访问。通过 ioctl() 请求,也可以进行全双工传输和设备 I/O 配置。

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

您可能希望使用此编程接口的一些原因包括:

  • 在不易崩溃的环境中进行原型开发;用户空间中的野指针通常不会导致任何 Linux 系统崩溃。

  • 开发用于与充当 SPI 从设备功能的微控制器通信的简单协议,这些协议可能需要经常更改。

当然,有些驱动程序无法在用户空间中编写,因为它们需要访问用户空间无法访问的内核接口(例如中断请求处理程序或驱动程序堆栈的其他层)。

设备创建,驱动绑定

spidev 驱动程序包含支持不同硬件拓扑表示的 SPI 设备列表。

以下是 spidev 驱动程序支持的 SPI 设备表:

  • struct spi_device_id spidev_spi_ids[]:当使用带 .modalias 字段(与表中某个条目匹配)的 struct spi_board_info 定义设备时,可以绑定的设备列表。

  • struct of_device_id spidev_dt_ids[]:当使用兼容字符串与表中某个条目匹配的设备树节点定义设备时,可以绑定的设备列表。

  • struct acpi_device_id spidev_acpi_ids[]:当使用 _HID 与表中某个条目匹配的 ACPI 设备对象定义设备时,可以绑定的设备列表。

如果相关表中尚未包含您的 SPI 设备名称条目,我们鼓励您添加一个。为此,请向 linux-spi@vger.kernel.org 邮件列表提交一个针对 spidev 的补丁。

过去曾支持使用“spidev”名称定义 SPI 设备。例如,作为 .modalias = “spidev” 或 compatible = “spidev”。但 Linux 内核不再支持此功能,而是必须使用表中列出的真实 SPI 设备名称。

如果没有真实的 SPI 设备名称,将会打印错误并且 spidev 驱动程序将无法探测。

Sysfs 还支持用户空间驱动的绑定/解绑驱动程序到那些不通过上述任何表格自动绑定的设备。要使 spidev 驱动程序绑定到此类设备,请使用以下方法:

echo spidev > /sys/bus/spi/devices/spiB.C/driver_override
echo spiB.C > /sys/bus/spi/drivers/spidev/bind

当 spidev 驱动程序绑定到 SPI 设备时,该设备的 sysfs 节点将包含一个子设备节点,该节点带有一个“dev”属性,此属性将被 udev 或 mdev(BusyBox 中的 udev 替代品;功能较少,但通常足够用)理解。

对于总线 B 上带有片选 C 的 SPI 设备,您应该会看到:

/dev/spidevB.C ...

字符特殊设备,主设备号为 153,次设备号动态选择。这是用户空间程序将打开的节点,由“udev”或“mdev”创建。

/sys/devices/.../spiB.C ...

和往常一样,SPI 设备节点将是其 SPI 主控制器的一个子节点。

/sys/class/spidev/spidevB.C ...

当“spidev”驱动程序绑定到该设备时创建。(目录或符号链接,取决于您是否启用了“deprecated sysfs files” Kconfig 选项。)

不要尝试手动管理 /dev 字符设备特殊文件节点。这样做容易出错,并且您需要密切关注系统安全问题;udev/mdev 应该已经配置安全了。

如果您从该设备解绑“spidev”驱动程序,这两个“spidev”节点(在 sysfs 和 /dev 中)应自动删除(分别由内核和 udev/mdev 删除)。您可以通过删除“spidev”驱动模块来解绑,这将影响所有使用此驱动程序的设备。您还可以通过让内核代码删除 SPI 设备来解绑,这通常是通过删除其 SPI 控制器的驱动程序(从而使其 spi_master 消失)来实现。

尽管 spidev 驱动程序只是向用户空间公开了一个低级 API,但它是一个标准的 Linux 设备驱动程序,可以同时与任意数量的设备关联。只需为每个此类 SPI 设备提供一个 spi_board_info 记录,您就会为每个设备获得一个 /dev 设备节点。

基本字符设备 API

对 /dev/spidevB.D 文件执行正常的 open() 和 close() 操作,结果与您预期的一样。

标准的 read() 和 write() 操作显然是半双工的,并且在这些操作之间片选会被禁用。全双工访问以及无需片选禁用即可进行的复合操作,可通过 SPI_IOC_MESSAGE(N) 请求实现。

几个 ioctl() 请求允许您的驱动程序读取或覆盖设备的当前数据传输参数设置:

SPI_IOC_RD_MODE, SPI_IOC_WR_MODE ...

传入一个指向字节的指针,该字节将返回(RD)或分配(WR)SPI 传输模式。使用常量 SPI_MODE_0..SPI_MODE_3;或者,如果您愿意,可以组合 SPI_CPOL(时钟极性,如果设置则空闲高电平)或 SPI_CPHA(时钟相位,如果设置则在尾沿采样)标志。请注意,此请求仅限于适合单个字节的 SPI 模式标志。

SPI_IOC_RD_MODE32, SPI_IOC_WR_MODE32 ...

传入一个指向 uin32_t 的指针,该指针将返回(RD)或分配(WR)完整的 SPI 传输模式,不受限于单个字节中的位。

SPI_IOC_RD_LSB_FIRST, SPI_IOC_WR_LSB_FIRST ...

传入一个指向字节的指针,该字节将返回(RD)或分配(WR)用于传输 SPI 字的位对齐方式。零表示 MSB-first(最高有效位优先);其他值表示较不常见的 LSB-first(最低有效位优先)编码。在两种情况下,指定的值都在每个字中右对齐,因此未使用的(TX)或未定义的(RX)位位于 MSB 中。

SPI_IOC_RD_BITS_PER_WORD, SPI_IOC_WR_BITS_PER_WORD ...

传入一个指向字节的指针,该字节将返回(RD)或分配(WR)每个 SPI 传输字中的位数。值零表示八位。

SPI_IOC_RD_MAX_SPEED_HZ, SPI_IOC_WR_MAX_SPEED_HZ ...

传入一个指向 u32 的指针,该指针将返回(RD)或分配(WR)最大 SPI 传输速度,单位为赫兹(Hz)。控制器不一定能分配该特定的时钟速度。

注意事项

  • 目前没有异步 I/O 支持;所有操作都是纯同步的。

  • 目前没有办法报告用于将数据移入/移出给定设备的实际比特率。

  • 从用户空间,您目前无法更改片选极性;这可能会损坏与共享 SPI 总线的其他设备之间的传输。每个 SPI 设备在不活动使用时都会被取消选择,从而允许其他驱动程序与其他设备通信。

  • 每个 I/O 请求可以传输到 SPI 设备的字节数是有限制的。默认值为一页,但可以通过模块参数更改。

  • 因为 SPI 没有低级传输确认机制,所以当与不存在的设备通信时,您通常不会看到任何 I/O 错误。

全双工字符设备 API

请参阅 spidev_fdx.c 示例程序,了解使用全双工编程接口的一个示例。(尽管它并未执行全双工传输。)该模型与内核 spi_sync() 请求中使用的模型相同;单独的传输提供与内核驱动程序相同的功能(除了不是异步的)。

该示例展示了一个半双工 RPC 风格的请求和响应消息。这些请求通常要求在请求和响应之间不取消选择芯片。可以将多个此类请求链接到一个单独的内核请求中,甚至允许在每个响应后取消选择芯片。(其他协议选项包括为每个传输段更改字长和比特率。)

要发出全双工请求,请为同一传输提供 rx_buf 和 tx_buf。即使它们是同一个缓冲区也可以。