当前位置:首页>Linux>TSI721 Linux 驱动源码分析1:驱动处理port write 和门铃事务中断处理过程

TSI721 Linux 驱动源码分析1:驱动处理port write 和门铃事务中断处理过程

  • 2026-08-22 22:52:39
TSI721 Linux 驱动源码分析1:驱动处理port write 和门铃事务中断处理过程

一、 驱动初始化

当系统启动并识别到 TSI721 硬件后,驱动的初始化过程 (tsi721_probe 函数) 开始,为后续通信准备好所有必要的硬件和软件资源。

  1. PCIe 基础配置:驱动首先使能 PCIe 设备,申请并映射设备的 BAR 空间。这些内存窗口是主机与 TSI721 芯片寄存器通信的通道。

  2. DMA 引擎初始化:TSI721 驱动利用芯片的 BDMA 通道来高效生成 RapidIO 维护请求(包括对配置空间的访问)。驱动会初始化 DMA 描述符环等数据结构,为后续操作做准备。

  3. 中断系统设置:驱动使能 MSI 中断并注册中断服务程序。这是主机能够及时响应来自 RapidIO 网络事件的关键一步。

  4. 功能模块初始化:驱动会分别初始化 Port-Write 和门铃处理所需的特定资源,例如为 Port-Write 消息创建内核 FIFO 和工作队列。

二、事务处理全景图

下图直观地展示了 Port-Write 和门铃事务在 TSI721 驱动中从硬件中断到软件处理的完整路径:

三、Port-Write 事务的驱动处理

Port-Write 主要用于系统级的事件和错误报告,其处理流程强调可靠性和及时性。

  1. 中断触发:当 TSI721 从 RapidIO 端口收到一个 Port-Write 报文时,会生成一个 PCIe 中断。

  2. 中断服务程序:驱动注册的中断处理函数 tsi721_irqhandler 被调用。它会检查中断状态寄存器,识别出是 Port-Write 事件,然后调用专用的 tsi721_pw_handler。

  3. 捕获与排队:在 tsi721_pw_handler 中,驱动从 TSI721 的 Port-Write 捕获寄存器中读取完整的报文数据(通常是 4 个 32 位字)。然后,使用 kfifo_in 操作将这个报文存入一个预先分配好的内核 FIFO 中。如果 FIFO 已满,报文可能会被丢弃并计数。

  4. 延迟处理:为避免在中断上下文中执行耗时操作,驱动通过 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;}
Port-Write消息捕获
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 数据传输已完成。

  1. 中断触发:当 TSI721 收到一个目标地址为本机的门铃事务时,会产生中断。

  2. 中断服务程序:中断处理函数同样先定位中断源。如果是一个入向门铃中断,它会读取 TSI721 的门铃状态寄存器,以确定是哪个门铃号被触发,并获取相关的 16 位信息码。

  3. 消息分发:驱动根据门铃号和信息码,执行预先注册的回调函数。例如,应用层可能为门铃号 1 注册了一个函数,用于处理“数据就绪”通知。驱动负责将这一硬件事件精准地传递给正确的处理逻辑。

  4. 应用层响应:在回调函数中,应用程序可以执行相应操作,比如开始处理一块已经通过 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};

最新文章

随机文章