4. QNX SPI驱动框架:资源管理器结构与dev-spi-master驱动模型
QNX SPI驱动采用资源管理器(Resource Manager)架构,将SPI控制器抽象为文件系统中的设备节点(如 /dev/spi0)。应用层通过标准的POSIX open()、read()、write()、devctl() 接口与SPI设备通信,驱动内部则通过 dev-spi-master 模型完成硬件控制与DMA传输调度。
4.1 资源管理器结构概述
资源管理器是QNX Neutrino微内核的核心I/O模型。SPI驱动作为一个用户态或内核态的资源管理器进程,向进程管理器注册路径前缀,并实现一组回调函数来处理客户端请求。
关键概念: 资源管理器将硬件设备映射为文件系统路径,使应用层无需了解底层硬件细节,通过统一API访问SPI、I2C、UART等外设。
4.1.1 资源管理器核心组件
| 组件 | 角色 | 说明 |
|---|---|---|
| 路径前缀 | 命名空间 | 如 /dev/spi0,驱动通过 resmgr_attach() 注册 |
| 连接回调 | 处理 open/unlink | io_open、io_unlink 等,建立客户端-驱动会话 |
| I/O回调 | 处理读写控制 | io_read、io_write、io_devctl 等,执行实际SPI传输 |
| 消息队列 | 请求排队 | 支持多客户端并发访问,通过优先级或FIFO调度 |
| 同步原语 | 互斥与条件变量 | 保护共享硬件寄存器与DMA描述符链 |
4.1.2 资源管理器工作流程
- 驱动初始化: 调用
resmgr_attach()注册/dev/spi0,绑定连接与I/O回调表。 - 客户端打开: 应用调用
open("/dev/spi0", O_RDWR),触发io_open回调,分配会话上下文。 - 参数配置: 应用通过
devctl(fd, SPI_SET_CONFIG, &cfg)设置模式、时钟、位宽等。 - 数据传输: 应用调用
write(fd, tx_buf, len)或read(fd, rx_buf, len),驱动内部启动PIO或DMA传输。 - 关闭释放: 应用调用
close(fd),触发io_close_ocb回调,释放DMA通道与缓冲区。
调试提示: 使用
ls -l /dev/spi* 查看已注册的SPI设备节点;使用 ioctl 调试工具(如 spi_test)验证驱动响应。
4.2 dev-spi-master驱动模型
dev-spi-master 是QNX官方提供的SPI主控制器驱动框架,位于 bsp/boards/<board>/src/hw/spi/ 目录下。它封装了资源管理器注册、SPI协议时序控制、DMA传输调度等通用逻辑,开发者只需实现底层硬件操作函数(HAL层)。
4.2.1 模型分层架构
4.2.2 核心数据结构
dev-spi-master 模型定义了以下关键结构体,驱动开发者需根据硬件填充:
// SPI主控制器设备结构
typedef struct spi_master {
uint32_t bus_num; // 总线编号
uint32_t max_speed_hz; // 最大时钟频率
uint32_t mode; // 默认模式(CPOL/CPHA)
uint32_t bits_per_word; // 数据位宽
uint32_t num_chipselect; // 片选数量
void *priv; // 私有数据(寄存器基址等)
int (*transfer)(struct spi_master *master,
struct spi_message *msg);
int (*setup)(struct spi_master *master,
struct spi_device *device);
} spi_master_t;
// SPI消息结构(单次传输单元)
typedef struct spi_message {
struct spi_transfer *transfers; // 传输段链表
uint32_t num_transfers; // 传输段数量
struct spi_device *spi; // 目标设备
int status; // 传输状态
void (*complete)(void *context);
void *context;
} spi_message_t;
// SPI传输段结构
typedef struct spi_transfer {
const void *tx_buf; // 发送缓冲区
void *rx_buf; // 接收缓冲区
uint32_t len; // 传输长度(字节)
uint32_t speed_hz; // 本次传输时钟频率
uint32_t bits_per_word; // 本次传输位宽
uint32_t cs_change; // 传输完成后是否切换片选
struct spi_transfer *next; // 链表下一节点
} spi_transfer_t;
常见错误: 在
transfer 回调中直接阻塞等待DMA完成,会导致资源管理器线程阻塞,影响其他客户端请求。应使用异步回调或工作线程处理DMA完成事件。
4.3 资源管理器与dev-spi-master的集成
实际驱动开发中,资源管理器回调函数会调用 dev-spi-master 提供的API。以下是一个典型的 io_devctl 处理流程:
int spi_io_devctl(resmgr_context_t *ctp, io_devctl_t *msg,
struct spi_master *master)
{
int status = EOK;
spi_ioc_transfer_t *ioc;
switch (msg->i.dcmd) {
case SPI_IOC_MESSAGE:
// 从用户空间拷贝传输参数
ioc = (spi_ioc_transfer_t *)msg->i.data;
// 构造 spi_message 并提交到传输队列
status = spi_master_transfer(master, ioc);
break;
case SPI_IOC_SET_CONFIG:
// 更新SPI模式、时钟等参数
status = spi_master_setup(master, &msg->i.data);
break;
default:
status = ENOTTY;
break;
}
// 返回结果给客户端
msg->o.ret_val = status;
return _RESMGR_PTR(ctp, &msg->o, sizeof(msg->o));
}
4.3.1 传输调度流程
4.4 关键回调函数实现要点
| 回调函数 | 触发时机 | 实现要点 |
|---|---|---|
io_open |
客户端调用 open() |
分配OCB(打开控制块),初始化设备上下文,检查硬件状态 |
io_read |
客户端调用 read() |
构造全双工传输(发送0xFF占位),等待DMA完成,拷贝数据到用户空间 |
io_write |
客户端调用 write() |
从用户空间拷贝发送数据,启动DMA发送,忽略接收数据 |
io_devctl |
客户端调用 devctl() |
解析命令码(如 SPI_IOC_MESSAGE),校验参数合法性,提交传输 |
io_close_ocb |
客户端调用 close() |
释放DMA通道、取消未完成传输、释放OCB内存 |
性能优化: 对于频繁的小数据量传输(如传感器寄存器读取),建议在
io_read/io_write 中直接使用PIO模式,避免DMA描述符构建开销。大数据块(>64字节)再切换到DMA。
4.5 多客户端并发访问策略
SPI总线是独占资源,多个客户端同时访问会导致数据错乱。dev-spi-master 模型通过以下机制保证原子性:
- 互斥锁: 在
transfer回调入口加锁,确保同一时刻只有一个传输在执行。 - 传输队列: 使用
pthread_mutex_t+pthread_cond_t实现生产者-消费者模型,客户端请求排队处理。 - 超时机制: 每个传输设置超时定时器,防止DMA挂死导致驱动永久阻塞。
- 优先级继承: 在锁操作中启用优先级继承协议,避免优先级反转。
// 传输队列加锁示例
static int spi_master_transfer_locked(struct spi_master *master,
struct spi_message *msg)
{
int ret;
pthread_mutex_lock(&master->lock);
// 将消息加入队列尾部
TAILQ_INSERT_TAIL(&master->msg_queue, msg, queue_entry);
// 如果当前没有活跃传输,启动队列处理
if (master->cur_msg == NULL) {
ret = spi_master_process_queue(master);
} else {
ret = EOK; // 等待当前传输完成
}
pthread_mutex_unlock(&master->lock);
return ret;
}
4.6 调试与验证方法
调试清单:
- 使用
pidin -p spi-driver确认驱动进程运行状态。 - 使用
ls -l /dev/spi*验证设备节点权限与存在性。 - 使用
ioctl -d /dev/spi0 SPI_IOC_SET_CONFIG 0x103测试参数配置。 - 使用逻辑分析仪抓取SCLK、MOSI、MISO、CS信号,验证时序是否符合预期。
- 在
io_devctl回调中添加printf或slogf输出,跟踪传输参数。