1. 项目背景与核心挑战
最近在做一个老设备升级项目,需要把原本基于PCI9054的采集卡驱动移植到PCI9656芯片上。这两个都是PLX公司的桥接芯片,但内核驱动架构差异不小。折腾了两周终于跑通,记录下关键步骤和踩坑经验。
PCI9054是经典的32位33MHz PCI桥接芯片,在工业控制领域用了十几年。而PCI9656是它的升级版,支持64位66MHz总线,DMA性能更强。但内核驱动从9054切换到9656可不是改个设备ID那么简单,涉及到寄存器映射、DMA传输、中断处理等多个模块的适配。
2. 硬件差异分析
2.1 寄存器空间变化
PCI9054的配置空间是256字节,而PCI9656扩展到4KB。最要命的是关键寄存器偏移地址全变了:
- 9054的DMA控制寄存器在0x80
- 9656的同功能寄存器分散在0x200-0x300区域
- 中断使能位从PCI配置空间的0x4C移到了0x68
建议先用lspci -xxx查看设备的完整配置空间,对比芯片手册确认每个功能块的偏移量。我在初期直接照搬9054的寄存器地址,导致DMA根本无法启动。
2.2 DMA引擎差异
9656的DMA控制器比9054复杂得多:
- 支持8个DMA通道(9054只有4个)
- 每个通道有独立的描述符链表
- 新增了PACING控制寄存器(调节传输速率)
实测发现如果不初始化PACING寄存器,连续DMA传输会导致PCI总线挂死。正确的做法是在驱动初始化时设置:
c复制iowrite32(0x00000100, bar0 + DMAC_PACING_REG); // 限制突发长度为256B
3. 驱动移植关键步骤
3.1 设备识别与初始化
首先修改pci_device_id表,添加9656的设备ID:
c复制static const struct pci_device_id my_driver_ids[] = {
{ PCI_DEVICE(0x10b5, 0x9054), .driver_data = BOARD_9054 },
{ PCI_DEVICE(0x10b5, 0x9656), .driver_data = BOARD_9656 }, // 新增
{ 0 }
};
在probe函数中根据设备类型选择初始化路径:
c复制if (id->driver_data == BOARD_9656) {
ret = init_9656(pdev);
} else {
ret = init_9054(pdev);
}
3.2 中断处理适配
9656的中断状态寄存器布局不同:
c复制// 9054的中断检查
status = ioread32(bar0 + INTCSR);
// 9656需要这样读
status = ioread32(bar0 + PCI9656_INTCSR);
if (status & PCI9656_DMA_DONE) {
// 处理DMA完成中断
}
特别注意:9656要求先读MAIN_INT_REG确认中断源,再清除具体的中断位。顺序错了会导致丢失中断。
3.3 DMA传输改造
描述符结构需要调整:
c复制struct pci9656_dma_desc {
__le32 ctrl; // 控制字
__le32 local_addr; // 本地总线地址
__le32 pci_addr; // PCI地址
__le32 next_desc; // 下一个描述符地址
__le32 data_len; // 数据长度
};
启动DMA时多了通道选择步骤:
c复制// 选择通道2
iowrite32(0x2, bar0 + DMAC_CH_SEL);
// 写入描述符地址
iowrite32(desc_phys, bar0 + DMAC_DESC_ADDR);
// 启动传输
iowrite32(0x1, bar0 + DMAC_CTRL);
4. 调试技巧与常见问题
4.1 寄存器调试技巧
建议在驱动中添加调试节点,方便实时查看寄存器状态:
c复制// 在sysfs中暴露寄存器读取接口
static ssize_t reg_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
u32 val = ioread32(bar0 + offset);
return sprintf(buf, "0x%08x\n", val);
}
用watch命令监控关键寄存器变化:
bash复制watch -n 0.1 "cat /sys/class/misc/mydev/reg_dmac_status"
4.2 典型问题排查
问题1:DMA传输卡死
- 检查PCI9656的PACING寄存器是否设置
- 确认描述符的next_desc字段在链尾设为0
- 用lspci -vvv查看设备的PCI状态是否正常
问题2:中断不触发
- 确认MAIN_INT_REG是否显示有待处理中断
- 检查PCI配置空间的中断线(INTERRUPT_LINE)是否正确
- 测试硬件中断引脚是否正常拉高
问题3:性能不达预期
- 调整DMAC_PACING_REG的突发长度
- 检查是否启用64位DMA(需设置PCI9656_CNTRL寄存器bit5)
- 确认PCI设备的LNKCTL寄存器是否启用66MHz模式
5. 实测性能对比
在相同硬件平台(X86工控机)测试:
| 指标 | PCI9054 | PCI9656 |
|---|---|---|
| 单次DMA上限 | 4MB | 16MB |
| 持续传输速率 | 80MB/s | 210MB/s |
| CPU占用率 | 15% | 8% |
9656的改进主要来自:
- 64位总线带宽
- 更智能的DMA仲裁
- 更大的FIFO缓冲区
6. 移植后的优化方向
虽然基础功能已经移植完成,但还有几个优化点值得尝试:
- 利用多DMA通道:9656支持8个通道,可以给不同优先级的数据分配独立通道
- 实现描述符预取:设置DMAC_CFG寄存器的bit3可以提升连续传输性能
- 启用MSI中断:修改PCI配置空间的CAPPTR寄存器,改用MSI可以减少中断延迟
经过这次移植,最大的体会是:看似兼容的硬件,驱动层面可能有天壤之别。建议在早期就通过芯片手册对比关键差异点,能节省大量调试时间。