一、 驱动初始化
当系统启动并识别到 TSI721 硬件后,驱动的初始化过程 (tsi721_probe 函数) 开始,为后续通信准备好所有必要的硬件和软件资源。
PCIe 基础配置:驱动首先使能 PCIe 设备,申请并映射设备的 BAR 空间。这些内存窗口是主机与 TSI721 芯片寄存器通信的通道。
DMA 引擎初始化:TSI721 驱动利用芯片的 BDMA 通道来高效生成 RapidIO 维护请求(包括对配置空间的访问)。驱动会初始化 DMA 描述符环等数据结构,为后续操作做准备。
中断系统设置:驱动使能 MSI 中断并注册中断服务程序。这是主机能够及时响应来自 RapidIO 网络事件的关键一步。
功能模块初始化:驱动会分别初始化 Port-Write 和门铃处理所需的特定资源,例如为 Port-Write 消息创建内核 FIFO 和工作队列。
二、事务处理全景图
下图直观地展示了 Port-Write 和门铃事务在 TSI721 驱动中从硬件中断到软件处理的完整路径:
三、Port-Write 事务的驱动处理
Port-Write 主要用于系统级的事件和错误报告,其处理流程强调可靠性和及时性。
中断触发:当 TSI721 从 RapidIO 端口收到一个 Port-Write 报文时,会生成一个 PCIe 中断。
中断服务程序:驱动注册的中断处理函数 tsi721_irqhandler 被调用。它会检查中断状态寄存器,识别出是 Port-Write 事件,然后调用专用的 tsi721_pw_handler。
捕获与排队:在 tsi721_pw_handler 中,驱动从 TSI721 的 Port-Write 捕获寄存器中读取完整的报文数据(通常是 4 个 32 位字)。然后,使用 kfifo_in 操作将这个报文存入一个预先分配好的内核 FIFO 中。如果 FIFO 已满,报文可能会被丢弃并计数。
延迟处理:为避免在中断上下文中执行耗时操作,驱动通过 schedule_work 函数调度一个延迟工作(如 tsi721_pw_dpc)。这个工作队列函数会在合适的时机运行,从 FIFO 中取出报文,解析出源设备 ID、端口信息以及 32 位的载荷数据,并最终通过内核事件机制或字符设备上报给等待的应用程序。
硬件初始化阶段
// 分配Port-Write消息FIFO和工作队列staticinttsi721_port_write_init(struct tsi721_device *priv){ priv->pw_discard_count = 0; INIT_WORK(&priv->pw_work, tsi721_pw_dpc); // 延迟处理工作队列 spin_lock_init(&priv->pw_fifo_lock); // 创建内核FIFO用于缓存Port-Write消息 if (kfifo_alloc(&priv->pw_fifo, TSI721_RIO_PW_MSG_SIZE * 32, GFP_KERNEL)) { tsi_err(&priv->pdev->dev, "PW FIFO allocation failed"); return -ENOMEM; } // 配置可靠Port-Write捕获模式 iowrite32(TSI721_RIO_PW_CTL_PWC_REL, priv->regs + TSI721_RIO_PW_CTL); return 0;}
中断触发与处理
// 主中断处理函数 - 检测Port-Write事件staticirqreturn_ttsi721_irqhandler(int irq, void *ptr){ struct tsi721_device *priv = (struct tsi721_device *)ptr; u32 dev_int = ioread32(priv->regs + TSI721_DEV_INT); if (dev_int & TSI721_DEV_INT_SRIO) { // 检查SRIO MAC中断状态 u32 intval = ioread32(priv->regs + TSI721_RIO_EM_INT_STAT); if (intval & TSI721_RIO_EM_INT_STAT_PW_RX) tsi721_pw_handler(priv); // 处理Port-Write if (intval & TSI721_RIO_EM_INT_STAT_PORT) tsi721_port_err_handler(priv); // 处理端口错误 } // ... 其他中断处理 return IRQ_HANDLED;}
staticinttsi721_pw_handler(struct tsi721_device *priv){ u32 pw_stat; u32 pw_buf[TSI721_RIO_PW_MSG_SIZE/sizeof(u32)]; // 读取Port-Write状态寄存器 pw_stat = ioread32(priv->regs + TSI721_RIO_PW_RX_STAT); // 检查Port-Write接收异常 if (pw_stat & 0x0000000E) tsi_err(&priv->pdev->dev, "PW_STAT: 0x%x\n", pw_stat); if (pw_stat & TSI721_RIO_PW_RX_STAT_PW_VAL) { // 从硬件捕获寄存器读取完整的Port-Write消息(4个32位字) pw_buf[0] = ioread32(priv->regs + TSI721_RIO_PW_RX_CAPT(0)); pw_buf[1] = ioread32(priv->regs + TSI721_RIO_PW_RX_CAPT(1)); pw_buf[2] = ioread32(priv->regs + TSI721_RIO_PW_RX_CAPT(2)); pw_buf[3] = ioread32(priv->regs + TSI721_RIO_PW_RX_CAPT(3)); // 线程安全地将消息存入FIFO spin_lock(&priv->pw_fifo_lock); if (kfifo_avail(&priv->pw_fifo) >= TSI721_RIO_PW_MSG_SIZE) kfifo_in(&priv->pw_fifo, pw_buf, TSI721_RIO_PW_MSG_SIZE); else priv->pw_discard_count++; // FIFO满时丢弃消息 spin_unlock(&priv->pw_fifo_lock); } // 清除中断状态 iowrite32(TSI721_RIO_PW_RX_STAT_PW_DISC | TSI721_RIO_PW_RX_STAT_PW_VAL, priv->regs + TSI721_RIO_PW_RX_STAT); // 调度工作队列进行异步处理 schedule_work(&priv->pw_work); return 0;}
异步消息处理
// 工作队列处理函数 - 在进程上下文中安全处理消息staticvoidtsi721_pw_dpc(struct work_struct *work){ struct tsi721_device *priv = container_of(work, struct tsi721_device, pw_work); union rio_pw_msg pwmsg; // 从FIFO中取出所有待处理消息 while (kfifo_out_spinlocked(&priv->pw_fifo, (unsigned char *)&pwmsg, TSI721_RIO_PW_MSG_SIZE, &priv->pw_fifo_lock)) { // 传递给RapidIO核心层进行进一步处理 rio_inb_pwrite_handler(&priv->mport, &pwmsg); }}
四、门铃事务的驱动处理
门铃事务更侧重于设备间的轻量级通信和同步,例如通知远端设备 DMA 数据传输已完成。
中断触发:当 TSI721 收到一个目标地址为本机的门铃事务时,会产生中断。
中断服务程序:中断处理函数同样先定位中断源。如果是一个入向门铃中断,它会读取 TSI721 的门铃状态寄存器,以确定是哪个门铃号被触发,并获取相关的 16 位信息码。
消息分发:驱动根据门铃号和信息码,执行预先注册的回调函数。例如,应用层可能为门铃号 1 注册了一个函数,用于处理“数据就绪”通知。驱动负责将这一硬件事件精准地传递给正确的处理逻辑。
应用层响应:在回调函数中,应用程序可以执行相应操作,比如开始处理一块已经通过 DMA 传输过来的数据。
门铃发送机制
// 发送门铃事务staticinttsi721_dsend(struct rio_mport *mport, int index, u16 destid, u16 data){ struct tsi721_device *priv = mport->priv; u32 offset, rval = 0; int i, rtry_cnt = 100; // 构建门铃事务包:目标设备ID + 16位信息码 offset = (((mport->sys_size) ? RIO_TT_CODE_16 : RIO_TT_CODE_8) << 18) | (destid << 2); // 写入门铃数据到ODB(Outbound Doorbell)寄存器 iowrite16be(data, priv->odb_base + offset); // 等待硬件确认传输完成 for (i = 0; i < 1000; i++) { udelay(1); if (ioread32(priv->regs + TSI721_ODB_CNT(0)) & TSI721_ODB_CNT_OK) return 0; // 成功发送 } // 错误处理和重试机制 // ... (省略错误处理代码) return -EAGAIN;}
门铃接收机制
硬件初始化
staticinttsi721_doorbell_init(struct tsi721_device *priv){ // 分配DMA一致性内存用于门铃队列 priv->idb_base = dma_alloc_coherent(&priv->pdev->dev, IDB_QSIZE * TSI721_IDB_ENTRY_SIZE, &priv->idb_dma, GFP_KERNEL); // 配置Inbound Doorbell队列 iowrite32(TSI721_IDQ_SIZE_VAL(IDB_QSIZE), priv->regs + TSI721_IDQ_SIZE(IDB_QUEUE)); iowrite32(((u64)priv->idb_dma >> 32), priv->regs + TSI721_IDQ_BASEU(IDB_QUEUE)); iowrite32(((u64)priv->idb_dma & TSI721_IDQ_BASEL_ADDR), priv->regs + TSI721_IDQ_BASEL(IDB_QUEUE)); // 使能所有门铃中断 iowrite32(0, priv->regs + TSI721_IDQ_MASK(IDB_QUEUE)); iowrite32(TSI721_IDQ_INIT, priv->regs + TSI721_IDQ_CTL(IDB_QUEUE)); return 0;}
中断处理
staticinttsi721_dbell_handler(struct tsi721_device *priv){ struct rio_mport *mport; struct rio_dbell *dbell; u32 wr_ptr, rd_ptr; u64 *idb_entry; union { u64 msg; u8 bytes[8]; } idb; mport = &priv->mport; rd_ptr = ioread32(priv->regs + TSI721_IDQ_RP(IDB_QUEUE)) % IDB_QSIZE; // 清除中断状态 iowrite32(TSI721_SR_CHINT_IDBQRCV, priv->regs + TSI721_SR_CHINT(IDB_QUEUE)); // 处理队列中所有待处理门铃 wr_ptr = ioread32(priv->regs + TSI721_IDQ_WP(IDB_QUEUE)) % IDB_QSIZE; while (wr_ptr != rd_ptr) { // 从队列中读取门铃消息 idb_entry = (u64 *)(priv->idb_base + (TSI721_IDB_ENTRY_SIZE * rd_ptr)); idb.msg = *idb_entry; *idb_entry = 0; // 清空队列条目 // 在注册的门铃列表中查找匹配项 list_for_each_entry(dbell, &mport->dbells, node) { if ((dbell->res->start <= DBELL_INF(idb.bytes)) && (dbell->res->end >= DBELL_INF(idb.bytes))) { // 调用上层注册的回调函数 dbell->dinb(mport, dbell->dev_id, DBELL_SID(idb.bytes), DBELL_TID(idb.bytes), DBELL_INF(idb.bytes)); break; } } rd_ptr = (rd_ptr + 1) % IDB_QSIZE; } // 更新读指针 iowrite32(rd_ptr, priv->regs + TSI721_IDQ_RP(IDB_QUEUE)); return 0;}
门铃处理注册流程
1. 用户空间初始化阶段
用户通过 ioctl 调用注册门铃过滤器:
case RIO_ENABLE_DOORBELL_RANGE: return rio_mport_add_db_filter(data, (void __user *)arg);
2. 驱动层过滤器注册
rio_mport_add_db_filter函数完成核心注册:
staticintrio_mport_add_db_filter(struct mport_cdev_priv *priv, void __user *arg){ // 从用户空间获取过滤器参数(门铃范围 low/high) // 调用底层 RapidIO 驱动注册门铃处理程序 ret = rio_request_inb_dbell(md->mport, md, filter.low, filter.high, rio_mport_doorbell_handler); // ...}
关键注册调用:rio_request_inb_dbell()将 rio_mport_doorbell_handler注册为门铃中断处理函数。
3. 硬件中断处理链
当硬件收到门铃时触发中断处理:
static void rio_mport_doorbell_handler(struct rio_mport *mport, void *dev_id, u16 src, u16 dst, u16 info){ // 遍历所有注册的门铃过滤器 list_for_each_entry(db_filter, &data->doorbells, data_node) { if (((db_filter->filter.rioid == RIO_INVALID_DESTID || db_filter->filter.rioid == src)) && info >= db_filter->filter.low && info <= db_filter->filter.high) { // 匹配成功,将事件加入对应客户端的事件队列 priv = db_filter->priv; rio_mport_add_event(priv, &event); handled = 1; } }}
4. 事件队列管理
事件添加到 FIFO 并唤醒等待进程:
staticintrio_mport_add_event(struct mport_cdev_priv *priv, struct rio_event *event){ // 检查事件掩码权限 if (!(priv->event_mask & event->header)) return -EACCES; spin_lock(&priv->fifo_lock); // 将事件存入环形缓冲区 overflow = kfifo_avail(&priv->event_fifo) < sizeof(*event) || kfifo_in(&priv->event_fifo, (unsigned char *)event, sizeof(*event)) != sizeof(*event); spin_unlock(&priv->fifo_lock); // 关键:唤醒等待队列中的进程 wake_up_interruptible(&priv->event_rx_wait); return overflow ? -EBUSY : 0;}
5. 用户空间事件读取机制
通过 poll 机制监控事件可用性:
static unsigned intmport_cdev_poll(structfile *filp, poll_table *wait){ struct mport_cdev_priv *priv = filp->private_data; // 注册等待队列到 poll_table poll_wait(filp, &priv->event_rx_wait, wait); // 检查是否有待读取事件 if (kfifo_len(&priv->event_fifo)) return POLLIN | POLLRDNORM; // 有数据可读 return 0;}
6. 文件操作接口集成
完整的 VFS 接口支持:
static const struct file_operations mport_fops = { .owner = THIS_MODULE, .open = mport_cdev_open, .release = mport_cdev_release, .poll = mport_cdev_poll, // 支持 poll/select .read = mport_read, // 读取事件数据 .write = mport_write, .mmap = mport_cdev_mmap, .fasync = mport_cdev_fasync, // 支持异步通知 .unlocked_ioctl = mport_cdev_ioctl // 包括门铃注册 ioctl};