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 资源管理器工作流程

  1. 驱动初始化: 调用 resmgr_attach() 注册 /dev/spi0,绑定连接与I/O回调表。
  2. 客户端打开: 应用调用 open("/dev/spi0", O_RDWR),触发 io_open 回调,分配会话上下文。
  3. 参数配置: 应用通过 devctl(fd, SPI_SET_CONFIG, &cfg) 设置模式、时钟、位宽等。
  4. 数据传输: 应用调用 write(fd, tx_buf, len) 或 read(fd, rx_buf, len),驱动内部启动PIO或DMA传输。
  5. 关闭释放: 应用调用 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 模型分层架构

dev-spi-master 驱动模型分层架构 应用层(Application) open / read / write / devctl / close 资源管理器层(Resource Manager) io_open / io_read / io_write / io_devctl 回调 SPI核心层(SPI Core) 传输队列管理 / DMA描述符编排 / 超时处理 硬件抽象层(HAL) 寄存器读写 / GPIO片选控制 / 时钟分频 / 中断处理 硬件层(Hardware) SPI控制器 / DMA控制器 / GPIO / 时钟

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 传输调度流程

SPI传输调度流程(资源管理器 + dev-spi-master) 客户端调用 write/devctl 资源管理器 io_write/io_devctl 构造 spi_message 入队 HAL层配置DMA描述符 启动DMA传输并等待完成 回调 complete,唤醒客户端

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 调试与验证方法

调试清单:
  1. 使用 pidin -p spi-driver 确认驱动进程运行状态。
  2. 使用 ls -l /dev/spi* 验证设备节点权限与存在性。
  3. 使用 ioctl -d /dev/spi0 SPI_IOC_SET_CONFIG 0x103 测试参数配置。
  4. 使用逻辑分析仪抓取SCLK、MOSI、MISO、CS信号,验证时序是否符合预期。
  5. 在 io_devctl 回调中添加 printf 或 slogf 输出,跟踪传输参数。