4. SPI 消息与传输:核心结构与传输机制
SPI 子系统将一次完整的设备交互抽象为 消息 (Message),而消息内部则由一个或多个 传输段 (Transfer) 组成。理解 spi_message 与 spi_transfer 的结构及其队列机制,是掌握 SPI 驱动开发的基础。
4.1 spi_message 结构体:事务的容器
spi_message 代表一次完整的 SPI 事务,它包含多个传输段,并携带完成回调与状态信息。驱动开发者通过填充该结构体来定义一次通信的语义。
| 成员 | 类型 | 描述 |
|---|---|---|
transfers |
struct list_head |
挂载该消息下所有 spi_transfer 的链表头 |
spi |
struct spi_device * |
指向目标 SPI 从设备 |
is_dma_mapped |
unsigned |
标记缓冲区是否已由调用者完成 DMA 映射 |
complete |
void (*)(void *) |
消息完成时的回调函数 |
context |
void * |
传递给回调函数的上下文指针 |
status |
int |
消息执行后的状态码(0 成功,负值错误) |
queue |
struct list_head |
用于将消息挂入控制器消息队列的链表节点 |
state |
void * |
控制器驱动私有状态指针 |
关键设计:
spi_message 通过 transfers 链表串联多个传输段,使得一次片选有效期间可以执行多个不同方向、不同长度的数据交换,无需重新发起片选信号。
4.2 spi_transfer 结构体:数据传输的原子单元
spi_transfer 描述一段连续的数据传输,是 SPI 通信的最小执行单元。每个传输段可以独立指定发送/接收缓冲区、传输长度、时钟频率、字长和延迟。
struct spi_transfer {
/* 数据缓冲区 */
const void *tx_buf; // 发送缓冲区(可为 NULL)
void *rx_buf; // 接收缓冲区(可为 NULL)
unsigned len; // 传输字节数
/* 时序与配置覆盖 */
unsigned speed_hz; // 本次传输的时钟频率(0 表示使用设备默认值)
u16 bits_per_word; // 每字位数(0 表示使用设备默认值)
u8 bits_per_word; // 字长(旧版本兼容字段)
u16 delay_usecs; // 传输完成后的延迟(微秒)
u32 speed_hz; // 速度(旧版本兼容字段)
/* 控制标志 */
u8 cs_change; // 传输完成后是否切换片选
u8 tx_nbits; // 发送线宽(1/2/4 线)
u8 rx_nbits; // 接收线宽(1/2/4 线)
u8 word_delay_usecs; // 字间延迟(微秒)
/* 内部使用 */
struct list_head transfer_list; // 挂入 spi_message 的链表节点
};
缓冲区规则: 若
tx_buf 为 NULL,则发送全 0;若 rx_buf 为 NULL,则丢弃接收数据。至少一个缓冲区必须非 NULL,且 len 必须大于 0。
4.3 消息队列机制:异步流水线
SPI 核心层维护一个 消息队列,驱动通过 spi_async() 将消息提交到队列尾部,控制器按 FIFO 顺序处理。队列机制实现了发送者与硬件操作的解耦,支持高吞吐场景。
4.4 同步传输与异步传输
SPI 子系统提供两种传输模式,满足不同场景需求:
| 特性 | 同步传输 (spi_sync) | 异步传输 (spi_async) |
|---|---|---|
| 核心函数 | spi_sync() / spi_write_then_read() |
spi_async() |
| 阻塞行为 | 调用进程休眠,等待消息完成 | 立即返回,不阻塞调用进程 |
| 完成通知 | 函数返回即表示完成 | 通过 complete 回调通知 |
| 上下文要求 | 可在进程上下文调用 | 可在中断/进程上下文调用 |
| 适用场景 | 简单设备读写、配置寄存器 | 高吞吐、DMA、实时性要求高 |
| 内部实现 | 封装 spi_async + 等待队列 |
直接提交到控制器消息队列 |
注意事项: 在中断上下文或持有自旋锁时,禁止调用
spi_sync(),否则会导致内核死锁。此时必须使用 spi_async() 并确保回调函数在安全上下文中执行。
4.5 消息构建与提交示例
以下代码展示如何构建一个包含两个传输段的消息,并通过同步方式提交:
#include <linux/spi/spi.h>
static int spi_example_transfer(struct spi_device *spi)
{
struct spi_message msg;
struct spi_transfer xfer[2];
u8 tx_buf[] = { 0xAA, 0xBB, 0xCC };
u8 rx_buf[3] = { 0 };
/* 初始化消息 */
spi_message_init(&msg);
/* 配置传输段 1:发送命令 */
memset(&xfer[0], 0, sizeof(xfer[0]));
xfer[0].tx_buf = tx_buf;
xfer[0].len = 3;
xfer[0].speed_hz = 1000000; // 1 MHz
spi_message_add_tail(&xfer[0], &msg);
/* 配置传输段 2:接收响应 */
memset(&xfer[1], 0, sizeof(xfer[1]));
xfer[1].rx_buf = rx_buf;
xfer[1].len = 3;
xfer[1].delay_usecs = 10; // 段间延迟 10us
spi_message_add_tail(&xfer[1], &msg);
/* 同步提交 */
return spi_sync(spi, &msg);
}
最佳实践: 使用
spi_message_init_with_transfers() 辅助函数可以一步完成消息和传输段的初始化,减少样板代码。对于只发送或只接收的简单操作,优先使用 spi_write()、spi_read() 封装函数。
4.6 传输段间的片选控制
通过 cs_change 标志可以精细控制片选信号的行为:
- cs_change = 0:当前传输完成后保持片选有效,后续传输段继续使用同一片选。
- cs_change = 1:当前传输完成后释放片选,下一个传输段重新拉低片选。
- 最后一个传输段:无论
cs_change为何值,消息完成后都会释放片选。
4.7 传输完成后的清理与状态检查
消息执行完毕后,必须检查 msg.status 以确认传输是否成功:
/* 异步传输回调示例 */
static void spi_complete_callback(void *context)
{
struct spi_message *msg = (struct spi_message *)context;
if (msg->status < 0) {
pr_err("SPI transfer failed: %d\n", msg->status);
/* 执行错误恢复或重试逻辑 */
} else {
pr_info("SPI transfer succeeded, %d bytes transferred\n",
msg->actual_length);
}
/* 注意:不要在回调中长时间阻塞 */
complete(&msg->context); // 若需要同步等待
}
性能提示: 对于批量小数据块传输,尽量合并到同一个
spi_message 的多个 spi_transfer 中,避免多次提交消息带来的队列调度开销。每次消息提交都会触发一次完整的队列入队和调度流程。