ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PLX SDK for Linux V7.24驱动移植与PCIe DMA调试实战指南

PLX SDK for Linux V7.24驱动移植与PCIe DMA调试实战指南 简介PLX科技并入博通公司前发布的最后一版Linux开发套件主要面向需要编写PCIe设备驱动、优化高速I/O通路并调试系统性能的驱动工程师与底层开发者涵盖内核空间驱动与用户态接口。压缩包共8个文件包含4个网页格式的参考指南如API参考、发行说明、常见问题、3个PDF格式的用户手册与旧版API文档以及1个tar源码包整体约3.61MB网页文档便于快速检索PDF适合深度阅读tar包则提供可编译的源码框架。已有410人学习下载。借助其中完整的官方说明和示例工程开发者能够系统掌握设备初始化、DMA数据传输、中断处理等关键流程并通过源码快速移植适配新硬件或排查驱动兼容性问题从而减少底层开发中的试错成本。虽然该版本已停止更新但作为PLX被收购前的最终正式版本其文档完整性与历史参考价值依然突出。1. PLX SDK for Linux V7.24把 PCIe 桥片调好数据通路才有底做 PCIe 数据采集、FPGA 高速对传或者转接卡研发的工程师十有八九会和 PLX/PEX 桥片打交道。PLX SDK for Linux V7.24 是配合这些桥片的一整套“驱动 应用库 调试工具”它不是给你讲某颗芯片的原理课而是让你在 Linux 下把设备驱动编译装上、把寄存器读写通、把 DMA 做起来的一揽子工程。实际项目里我见过太多板子画得不错却因为驱动模块匹配不上内核而卡了一周的团队也见过 V7.24 的库文件在嵌入式 Linux 上跑通后整条数据采集链路被盘活的例子。这篇稿子从拿到 SDK 到移植排错按顺序写适合正在做板卡调测、或者第一次拿到 PLX SDK 想快速接线的工程师。2. 安装与编译V7.24 在 Linux 上跑通的最小闭环2.1 SDK 长什么样目录结构与安装脚本的关键件PLX SDK for Linux V7.24 的解压包不会给你一个现成的安装包。常见做法是把整套源代码放到工作目录你需要自己搞定内核模块和库的编译。以我手上的 SDK 习惯性目录来看一般会有Doc、Driver、PlxApi、PlxMon、Samples这几块Doc芯片手册、API 说明、版本说明都在这里调驱动前至少翻一遍版本说明。Driver/PlxLinux 内核驱动的源码和 Makefile这是整个 SDK 里最先要下手的地方。PlxApi应用层访问设备的 C 接口库V7.24 里它是一个大体量工程编译完可以生成静态库或动态库。PlxMon用来观察链路、寄存器和 DMA 状态的监控工具很多现场问题靠它一眼定位。Samples一批可以直接在命令行里跑的最小程序比如 DMA 测试、中断测试。拿到包之后别急着盲调。我一般会先打开Doc下的版本说明确认你手里的驱动编译基线用的内核是哪一代比如 V7.24 的 Linux 驱动原型是从 2.6 内核年代一直带过来的——这一点在你把同一份源码丢到 4.19 或 5.15 内核上编译时直接决定你会不会遇到编译中断。然后看一眼Driver/Plx/Makefile确认它是不是一个标准的 kernel module Makefile有没有把 SDK 的绝对路径写死在CFLAGS里。很多后续的编译报错根子就出在这个目录结构上。PLX 官方对 Linux 驱动的支持方式和 Windows 不太一样Windows 下驱动有安装包Linux 下更多是给你源码让内核模块随目标机内核去构建。这样做的好处是模块和内核的 API 版本完全匹配坏处是你的工具链和内核头文件版本必须准备好不然就会在 make 的一瞬间失败。所以第一个动手门槛不是 SDK 本身而是你手上 Linux 发行版的内核开发环境。2.2 编译配套库libPlxApi 为什么也要重编一次很多人以为驱动编译完就可以跑 SDK 示例了其实还差一步应用层的libPlxApi。V7.24 的包在你解压之后PlxApi/Lib下多半是源码而非现成的.so。原因很简单库要通过对应用层 ioctl 命令码访问驱动而驱动是你自己刚编出来的两者必须保持一致的接口定义。编译库的命令和内核模块不同它走的是普通 Makefile# 进入 PlxApi 的库工程目录V7.24 一般在这个位置 cd SDK_ROOT/PlxApi/Lib # 先清理掉可能的残留文件 make -f Makefile.linux clean # 直接编译静态库/动态库具体生成什么看 Makefile 目标 make -f Makefile.linux编译完成后PlxApi/Lib下应该会出现libPlxApi.a或libPlxApi.so。如果你的目标板是 x86 Linux直接链接动态库就行如果是嵌入式 Linux我更建议链接静态库省去部署时到处拷.so的麻烦。链接参数里要补上线程库因为库内部中断回调要起线程做事件分发gcc -o myapp myapp.c -ISDK_ROOT/PlxApi/Include \ -LSDK_ROOT/PlxApi/Lib -lPlxApi -lpthread这里的-I指向 SDK 的公共头文件目录-L指向刚才编出来的库文件。如果库是用-fPIC编成动态库运行时还要记得export LD_LIBRARY_PATH或者把.so拷到/usr/local/lib。不然程序起来会报找不到libPlxApi.so。2.3 编译内核模块最小步骤与 Makefile 缺省先把最小的一串命令列出来这是我每次拿到新板卡都会先跑一遍的“冒烟编译”# 先确认内核版本并确保内核头文件存在 uname -r sudo apt install linux-headers-$(uname -r) # 进入 V7.24 SDK 的驱动源码目录别从 SDK 根目录直接用 make cd SDK_ROOT/Driver/Plx # 用 Kbuild 的 -C ... M ... 方式编译当前目录下的模块 make -C /lib/modules/$(uname -r)/build M$(pwd) modules # 编译完成后看输出正常情况下会生成 plx.ko 或者类似名字的模块文件 ls -l *.ko如果你的 SDK 包在发布时已经把这个 Makefile 改造成了 PLX 自己的一键脚本那么上述命令会简化成直接执行./build或者make -f Makefile.linux。但无论脚本怎么包装底层逻辑都是上面这条。我用-C指定内核构建目录M$(pwd)让 Kbuild 知道要去当前目录编译外部模块这是 Linux 内核模块的标准做法PLX 驱动也不能例外。这里的参数有个容易忽略的地方/lib/modules/$(uname -r)/build必须真的存在。很多精简版 Linux 系统只装了运行内核没有装linux-headers这时候 make 会报“目录不存在”或者找不到Kconfig然后一堆新手就去改 Makefile 路径其实是头文件没装。正确做法是先执行apt install linux-headers-$(uname -r)装完再看/lib/modules/$(uname -r)/build是否指向/usr/src下的一个目录。另一个参数是交叉编译。如果是在 x86 主机上编译给 x86 目标机用不需要额外设置。但如果你打算把这块板卡挂到 ARM 的嵌入式 Linux 上需要在 make 的命令行里补上ARCHarm CROSS_COMPILEarm-linux-gnueabihf-并且-C指向的也必须是对应 ARM 架构的内核源码目录而不是uname -r对应的 x86 目录。这一点我会在最后一章展开编译时先记住uname -r只在本地宿主机编译时才适用。编译通过后用insmod加载模块然后立刻用dmesg看驱动有没有识别到 PLX 芯片的 PCI 设备。这一步是整条链路最直接的反馈sudo insmod ./plx.ko sudo dmesg | tail -30如果 dmesg 里出现类似“plx_pci: probe 0000:03:00.0 failed”的字样优先怀疑 PCIe 链路没有 up 或者芯片的 EEPROM 里配的 ID 没有被驱动匹配。如果一切正常你会看到驱动打印出它找到的 BAR 地址和中断号继续往下做设备节点。2.4 设备节点与权限先让 PlxMon 认识你的板卡PLX 驱动加载成功后用户态程序是通过/dev/plx0之类的字符设备去访问硬件的库函数把它包装成了PLX_DEVICE_OBJECT的句柄。在一个干净的 Linux 系统里这个设备文件不会自动出现除非你自己写了 udev 规则。V7.24 老派的做法是手动 mknod新派的做法是在/etc/udev/rules.d/下放一条规则。先看设备号从哪来cat /proc/devices | grep -i plx # 假设输出是: 245 plx # 主设备号 245次设备号从0开始分配 sudo mknod /dev/plx0 c 245 0 sudo chmod 666 /dev/plx0这里主设备号不是固定的必须以/proc/devices实际打印的为准。如果你在嵌入式 Linux 上做 rootfs 打包那需要在启动脚本里执行这段逻辑或者直接写成 udev 规则KERNELplx*, MODE0666把这条规则放到/etc/udev/rules.d/99-plx.rules后重新插拔设备或者触发 udev内核会自动为每个 plx 设备创建设备节点并放开权限。没有这一步你运行 SDK 自带的 PlxMon 或自己写的采样程序时PlxDeviceOpen会返回打不开设备的错误而且是不看日志很难意识到的权限问题。设备节点创建完后先用 SDK 的 PlxMon 工具连一下设备。如果 PlxMon 能读出芯片型号和 BAR 信息说明从驱动到设备节点的整条链路已经通了。这时候再跑lsmod、dmesg这些 linux 运维老命令去确认模块状态基本就没悬念了。3. 用 PlxApi 操作芯片寄存器、中断与 DMA 的调用路径3.1 设备句柄三步走注册、查找、打开拿到libPlxApi以后应用层代码看起来像是一个抽象的设备驱动。先把最经典的打开流程写出来这是 PLX 所有示例程序都有的骨架#include plx.h #include PlxApi.h PLX_STATUS MyOpenDevice(PLX_DEVICE_OBJECT **ppDev) { PLX_STATUS rc; U8 idx; PLX_DEVICE_KEY key; PLX_DEVICE_OBJECT *pDev; rc PlxInitialize(); if (rc ! PLX_STATUS_OK) { return rc; } /* 查找 PLX 芯片vendorId 一般填 0x10B5 */ idx 0; rc PlxPci_DeviceFind( 0x10B5, PCI_ANY_ID, idx, key); if (rc ! PLX_STATUS_OK) { PlxUninitialize(); return rc; } /* 打开设备句柄 */ rc PlxDeviceOpen(key, pDev); if (rc ! PLX_STATUS_OK) { PlxUninitialize(); return rc; } *ppDev pDev; return PLX_STATUS_OK; }这段代码把调用顺序说清楚了PlxInitialize做库内部状态和驱动的对接PlxPci_DeviceFind按 Vendor ID/Device ID 去 PCI 总线上扫设备。vendorId0x10B5是 PLX 公司自己的厂商号PCI_ANY_ID表示只要看到 PLX 的任意设备都行如果你板上有两颗不同的 PLX 芯片就要把deviceId显式填成具体的芯片型号不然每次idx0拿到的都是扫描到的第一颗。deviceId可以在PlxChip.h头文件里找到V7.24 的 SDK 会为每款芯片定义一个宏。查找和打开是两个阶段不要跳过PlxInitialize直接调用PlxDeviceOpen虽然库内部可能做了兼容但出问题时错误码会指向一个比较隐晦的位置。建议在每个调用之后都检查PLX_STATUSPLX 的错误码设计得比较细比如PLX_STATUS_DRIVER_NOT_FOUND、PLX_STATUS_DEVICE_NOT_FOUND、PLX_STATUS_PENDING看代号一眼能定位是库没对接上驱动还是总线上没扫到设备。设备打开之后程序结束前记得做反向清理PlxDeviceClose(pDev); PlxUninitialize();这个顺序别写反。先关设备再卸载库否则句柄还挂着库内部的状态机没有归零下次打开同一块板卡时可能报忙。一套驱动中多次开关如果出现第二次总是失败十有八九就是没有在退出路径上做对称清理。3.2 PlxRegRead / PlxRegWrite寄存器地址怎么换算寄存器读写是后面所有 DMA、中断调测的基础。PLX API 给应用层的接口很直接PLX_STATUS rc; U32 offset, val; offset 0x50; /* 例如某个芯片的控制寄存器偏移 */ rc PlxRegRead(pDev, offset, val); if (rc PLX_STATUS_OK) { printf(Reg[0x%X] 0x%08X\n, offset, val); } val val | (1U 3); /* 修改某一位 */ rc PlxRegWrite(pDev, offset, val);这里的offset到底是什么地址新手最容易搞混。PLX 芯片有两套寄存器空间一套是 PCI 配置空间一套是本地寄存器空间后者通过 PCI 的某个 BAR 映射上来。PlxRegRead里的 offset 按芯片手册里的本地寄存器偏移来填但不能直接拿手册偏移乱填因为应用层库在内部做过一次 BAR 基址的换算。你需要先确认驱动和库打开设备后默认选用的是哪个 BAR 空间。实际排错时我常用的办法是先读一个板上必有的寄存器比如 Vendor ID/Device ID 之外的状态寄存器把读回来的值和芯片手册对一下。读出来全是0xFFFFFFFF往往是 BAR 没有使能或者访问类型不对读出来是 0可能是寄存器偏移写错了更可能是访问到了只写的位。不要一上来就怀疑库有问题先用lspci -vvv看当前设备的 BAR 值再对照 SDK 文档里PlxRegRead的实现说明看它是在 BAR0 还是 BAR1 上做映射。PLX API 也提供了按名称读写的接口比如PlxRegisterRead、PlxRegisterWrite参数里给的是寄存器的枚举名。维护较长代码时我倾向于用名称接口因为过两个月回头看代码0x50这个数字已经失去上下文PLX_REG_OFFSET_CNTRL这种写法至少知道在操作哪个寄存器。V7.24 的PlxApi.h里把每个寄存器定义成了枚举和偏移宏严格说这不是运行时查表而是编译期的偏移量。读写还有一个容易被忽略的点PlxRegRead在 V7.24 的内部实现里不是每次直接 PIO 访问而是可能经过一个缓存窗口。如果你在代码里写完接着马上读最好加入一个小的边界时序或者调用PlxMmioRead强制走 MMIO。大部分场景不需要但在大量中断和 DMA 同时跑的时候窗口切换会导致地址回退这个细节会在性能验证时暴露出来。3.3 中断回调拿到事件请先置位DMA 和外部事件的中断是另一条主线。V7.24 的做法是向库注册一个回调函数驱动在中断上下文中把事件推到应用层。这里有一个很实在的教训回调函数里不能做重活只能置位、唤醒线程。static void MyIntHandler(PLX_DEVICE_OBJECT *pDev) { /* 标志位和事件对象在业务线程初始化 */ event_flag 1; sem_post(event_sem); } PLX_NOTIFY_OBJECT notify; memset(notify, 0, sizeof(notify)); notify.callback MyIntHandler; notify.context pDev; PlxNotificationRegister(pDev, notify); PlxInterruptEnable(pDev);这里的PLX_NOTIFY_OBJECT是库的通知对象callback会在驱动收到硬件中断时被调用。调用线程则阻塞在一个信号量上等sem_wait返回后去处理数据。不要在MyIntHandler里做寄存器回读、打印日志或者分配内存因为回调可能运行在原子上下文或者驱动的工作队列里一个耗时操作会把整个中断路径卡死丢中断是轻的严重时直接触发内核调度器报警。中断使能的顺序也有讲究。先注册通知对象再使能中断。如果顺序反过来硬件中断一来没有处理函数兜底驱动可能直接把它丢掉或者打印一个未处理中断的警告尤其 PLX 这类芯片中断经常是电平触发没处理完会一直挂着后续业务就全乱了。3.4 DMA 传输PlxDmaUserCmd 的典型参数与执行流程DMA 是 PLX SDK 应用价值最高的部分。PlxDmaUserCmd是应用层发起一次 DMA 传输的入口参数由一个PLX_DMA_USER_CMD结构体定义。下面是一个把 PCIe 端缓冲区拷到本地总线端地址的典型配置PLX_DMA_USER_CMD dma; PLX_STATUS rc; memset(dma, 0, sizeof(dma)); dma.DmaSize 4096; /* 传输字节数 */ dma.DmaAddr pBounce; /* 物理地址须是连续内存 */ dma.LocalAddr 0x00000000; /* 芯片本地侧地址 */ dma.LocalToPci 1; /* 0: 从设备读到主机 1: 从主机写到设备 */ dma.DmaChan 0; /* 使用第 0 个 DMA 通道 */ rc PlxDmaUserCmd(pDev, dma); if (PLX_STATUS_OK ! rc) { printf(DMA failed, status%d\n, rc); } else { printf(DMA done, actual%d\n, dma.DmaSize); }这段代码的关键参数有三处。DmaAddr必须是物理地址而不是用户态虚拟地址内核驱动在 ioctl 路径上会用它直接编程 DMA 描述符如果你传一个 malloc 出来的地址大概率传输数据全是乱码或者直接触发硬件错误。SDK 示例里多数用PlxUserDmaBufferAlloc来分配缓冲区这个接口确保分配的内存是物理连续且已做 DMA 映射。自己实现时也要走走类似dma_alloc_coherent加 ioctl 映射的路径。LocalAddr是给本地总线端看的地址不是 PCIe 系统的地址。FPGA 侧如果挂在本地总线上它天然是用这个地址对接的。所以具体填多少以你 FPGA 逻辑和芯片 Local Address Window 的映射为准不能想当然填 0。LocalToPci方向一旦设错了表现很毛糙写寄存器回读正常DMA 状态也显示完成但是数据对不上或者 DMA 结束中断根本不触发。原因是方向位和描述符里的源/目的地址需要配合方向一错硬件拿到的地址就成了从 LocalAddr 读到 LocalAddr 的循环搬运。调试时第一步永远是把方向和地址打印出来核对一遍。DMA 结束后还需要检查描述符状态位。PLX 芯片有 DMA Channel Status 寄存器里面包含完成位和错误位。应用层用法是U32 sts; /* 不同芯片寄存器偏移不同这里给一个占位示例 */ rc PlxRegRead(pDev, DMA_CHANNEL_STATUS_OFFSET, sts); if (sts DMA_STS_ABORT_BIT) printf(DMA aborted\n);不同芯片型号寄存器偏移不同V7.24 的 SDK 会在驱动层处理中断标志所以应用层不必每次手动清标志但调试时必须能看懂状态寄存器里 Done 位和 Abort 位的差别。只要看到 Abort 位被置上别重试先回头看地址映射重试往往让问题更难定位。4. EEPROM 与配置空间板卡识别的第一道关卡4.1 EEPROM为空和烧错板卡会以两种方式折磨你PLX 桥片出厂后不是空白的。芯片内部有一套默认的 PCI ID 和寄存器初始值少数芯片没有 EEPROM 也能工作但大多数板卡会把关键配置放到片外 EEPROM 里。SDK 里管理 EEPROM 的工具一般在 PlxMon 组件下也支持通过程序接口PlxEepromRead/PlxEepromWrite操作。先说一个基础但致命的问题EEPROM 为空和 EEPROM 内容损坏表现完全不同。EEPROM 为空芯片用内部默认 ID 和默认寄存器值驱动能正常 probe但是 BAR 空间大小、Local Bus 时序、厂商 ID 都是出厂默认值。如果你的板卡没有按这个默认设计设备能枚举但功能不对链路可能以 2.5GT/s 跑而不是你期望的 5GT/s。EEPROM 内容损坏校验和不过时芯片会按某个安全策略全部用默认值或拒绝加载。现象是驱动 probe 成功但后续寄存器访问返回异常甚至设备 ID 变成厂商保留值。SDK 提供的工具一般会在连接设备后显示当前 EEPROM 的容量和校验状态。拿到一块没有 EEPROM 或者被清空过的板卡我的习惯是先备份再改# 把当前 EEPROM 内容完整读出来保存为备份文件 plx_eeprom -r -o plx_backup.bin # 查看当前内容重点看 offset 0x00-0x1F 的 Vendor/Device ID 字段 plx_eeprom -v -o plx_backup.bin这里的plx_eeprom是 SDK 自带工具的习惯叫法不同版本可能叫PlxEeprom或EepromTool用法都是读取和写入两个方向。-r表示读-o指定输出文件。不要嫌备份这一步麻烦后面所有烧写错误都能靠这个文件兜底。关键的配置文件包括Vendor ID 和 Device ID、子系统 ID、各 BAR 的空间大小和类型、本地总线配置、DMA 通道默认使能。这些字段在 SDK 的PlxChip.h和 EEPROM 工具里都有字节偏移描述。用工具改完保存后需要做一次整板下电再上电因为 EEPROM 的内容是在芯片复位加载时被读取的软复位不一定触发重新加载。这是一个几乎必然踩中的细节写完后在系统里重启驱动没用必须断电上电。4.2 用 lspci 验证配置空间第一只手电筒Linux 下看 PCIe 设备状态最直接的命令是lspci不用装任何 PLX 工具就能拿到关键信息。我通常用这种组合lspci | grep -i PLX lspci -vvv -s 03:00.0第一行扫到 PLX 设备的总线和设备号第二行看完整配置空间。-vvv的输出里重点看这几段Region 0、Region 1显示 BAR 空间的物理地址和大小如果你打算在应用层直接 MMIO 访问注意这里的大小和你的 EEPROM 配置是否一致。LnkCap和LnkSta链路能力与当前协商状态如果LnkCap是 5GT/s 而LnkSta停在 2.5GT/s多半是连接器焊接或者 EEPROM 里没有开启高速档。Interrupt和MSI确认中断模式PCIe 的 MSI 和传统 INTx 在这里能看出差别。如果你的驱动已经加载lspci -vvv还能看到Kernel driver in use: plx这行出现意味着内核已经绑定了驱动。要是它是空的说明设备没有匹配上驱动一般情况下是 ID 对不上或没有调用modprobe。还有一种现场很诡异lspci能列出 PLX 设备但-vvv -s 03:00.0里 BAR 全为 0。这说明设备在枚举阶段就没有被分配资源常见原因是 PCIe Root Port 没有为这个外设开窗口或者主板 BIOS 的资源分配策略把总线上的空间吃光了。这种情况和 SDK 关系不大但你会从PlxRegRead的返回值里体会到灾难。先解决平台侧的资源分配再回来找 SDK。4.3 板卡识别失败的三种形态把经验中的三种失败形态列出来能帮你快速定位lspci里根本没有 PLX 设备。原因卡没插好、PCIe 链路 down 或 EEPROM 里的设备 ID 不被 Host 接受。解决先换插槽再看lspci链路 down 时大概率是焊接或金手指问题。lspci有设备但驱动不 probe。原因驱动里的 ID table 没有包含这个 device id或者内核编译时没把模块加载进 initramfs。解决查看Driver/Plx里的设备 PID 表确认你的芯片型号在不在不在就自己加一行然后重新编译。probe 成功但应用层打不开。原因设备节点没建、权限不对、或者驱动装载了不只一次。解决先用ls /dev/plx*确认节点再 cat/proc/devices核对主设备号最后dmesg查第二轮加载时是否有资源冲突。这节最想强调的还是备份 EEPROM。老板给你的板卡可能有别人刷过的痕迹你改坏了虽然可以重刷但如果没有原始备份你连恢复的参考都没有。我现在的习惯是每块板卡上电后第一件事就是读 EEPROM 存成.bin文件名带上板卡序列号和采集日期这二十秒操作省掉了后面无数个熬夜可能。5. 排错现场PLX V7.24 在 Linux 上最常见的 5 个卡点5.1 编译报错.ioctl字段找不到现象error: unknown field ioctl specified in initializer原因V7.24 的驱动源码里struct file_operations仍然用老的.ioctl字段而 Linux 内核从 2.6.36 开始把这个回调改成了.unlocked_ioctl旧字段被移除自然就编译不过。解决在驱动源码里找到struct file_operations的定义把回调字段名改掉static const struct file_operations plx_fops { .owner THIS_MODULE, .read plx_read, .write plx_write, .unlocked_ioctl plx_ioctl, /* 原来写作 .ioctl */ };同时把plx_ioctl的函数签名改成long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long)。V7.24 的 Linux 驱动如果原本就带compat_ioctl兼容层保留兼容层的写法只是主回调必须换名称。编译通过后再用一个最小测试程序验证 ioctl 路径因为改名过程中很容易把copy_to_user的指针类型弄混。5.2 加载驱动后/dev/plx0不见了现象insmod plx.ko ls /dev/plx0 # 提示 No such file or directory原因内核驱动只是创建设备不会自动生成/dev下的节点没有 udev 规则或者 init 脚本执行 mknod节点就是不存在。解决分两步排查先确认驱动是否注册了设备号cat /proc/devices | grep plx如果这里能看到主设备号再用对应的主设备号手动创建节点mknod /dev/plx0 c major 0 chmod 666 /dev/plx0如果是嵌入式 Linux直接把创建命令写进开机脚本里不要依赖交互式 shell。如果/proc/devices里也找不到说明驱动的register_chrdev_region没有执行成功回到dmesg看内核日志。5.3 寄存器读写全部返回0xFFFFFFFF现象PlxRegRead任何偏移读回来都是全F而且无论写多少次都不改变。原因这种值通常说明 CPU 访问没有命中有效资源。可能是 BAR 没有被系统分配也可能是 PLX 芯片本地侧的某个窗口没有使能还可能是板卡的物理链路断开设备已经在枚举阶段就掉了。解决先用lspci -vvv -s bus看Region值BAR 全 0 就是资源分配失败BAR 有值但访问仍全 F再看LnkSta是不是停在 Down 状态。如果 BAR 正常且链路 up回到 EEPROM 确认本地寄存器空间有没有被配置成不再映射。三类问题分别对应主板资源策略、物理链路、EEPROM 配置三个方向排查完基本能定位。5.4 DMA 传输第一包能过第二包起全部超时现象示例程序跑第一次 DMA数据和状态都对连续跑第二次开始卡在等待 DMA 完成上最后PlxDmaUserCmd直接返回超时。原因这是 PLX DMA 最典型的陷阱驱动在完成第一次传输后没有把 DMA 控制寄存器里的状态位清干净第二次传输的启动命令被硬件认为是非法状态而被忽略。另一个常见原因是用户态分配的 DMA 缓冲区在第一次传输后被释放但驱动的地址映射还在第二次 DMA 用了一个无效的物理地址。解决在每次启 DMA 之前显式读取并清除状态寄存器中的 Done、Abort 位再重新填写描述符。如果是自己的缓冲区管理把PlxUserDmaBufferAlloc改成整个进程生命周期内只分配一次不要每次传输前都重新申请。检查逻辑里优先看 DMA Channel Status 的 Abort 位它比 Done 位更能说明问题。5.5 64 位系统上 DMA 地址被截断现象在 64 位 Linux 上运行 SDK 示例小数据量正常搬到 2MB 以上缓冲区就开始出乱码或者 DMA 传输在运行中途 Read Request 失败。原因老一代 PLX 芯片的 DMA 引擎只支持 32 位地址描述符里的SysAddr是 32 位宽。如果你用dma_alloc_coherent分配缓冲区内核可能把 DMA 地址放在高 32 位所以高地址段直接超范围了。解决不要直接把大块物理地址填进描述符给这一组 DMA 专门分配 32 位可寻址的缓冲区或者把 PLX 芯片的 DMA 地址窗口映射到一个 32 位物理窗口内。具体到 V7.24 的 SDK应用层用到PlxUserDmaBufferAlloc时最好在分配后打印DmaAddr看到接近 4GB 边界就要警惕。这一节列出的五个卡点是我在不同项目里分别遇到的类型从编译期到运行期都有。按它们出现的频率排前三个几乎每一个刚接 PLX 的项目组都会遇到后两个则更多出现在做量产或者性能压测的阶段。对照现象解决之后整条数据通路才算真正站稳。6. 把 V7.24 移植到 ARM 嵌入式 Linux交叉编译与设备树最后一节谈嵌入式。很多使用 PLX 桥片的项目最终要跑到 ARM 平台或 SoC 上而不是 x86 服务器里这就涉及到交叉编译和平台资源分配。交叉编译命令和宿主机编译的差别主要在三处ARCH、CROSS_COMPILE和内核源码路径。一个可用的命令序列大概长这样export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- export KDIR/path/to/your/kernel/source cd SDK_ROOT/Driver/Plx make -C $(KDIR) M$(pwd) modules参数说明KDIR必须指向 ARM 架构的内核源码树而不是/lib/modules/...因为后者是宿主机的。PLX 驱动编译时依赖内核头文件里的 PCI 和 DMA 相关结构定义所以内核源码的版本要和目标板子的运行内核一致否则模块加载时会报version magic不匹配。设备树或平台代码里要确认 PCIe Root Port 的ranges属性把地址窗口和中断线映射出正确范围。ARM SoC 上 PCIe 控制器通常不是 x86 那种自动枚举的风格很多情况下需要显式配置ranges 0x42000000 0 0x40000000 0x...这样的窗口。PLX 芯片挂在 Root Port 下枚举顺序反而和 x86 区别不大但 Root Port 本身没有起来lspci 肯定看不到设备。另一个在嵌入式上容易出问题的点是 DMA 一致性。ARM 的 DMA 映射默认为非一致性而 PLX 老驱动有时候用pci_alloc_consistent分配有些内核版本把它改名为dma_alloc_coherent。代码编译时如果报找不到符号去驱动源码里搜索pci_alloc_consistent替换成对应头文件里的新 API。这是老 SDK 放在新内核上最常见的 ARM 移植问题。最后说一个我自己保存很久的习惯。做嵌入式板卡调测我收到一块板子后不是先跑例程而是先做三件登记EEPROM 备份、lspci -vvv保存、内核版本号记到板卡标签上。之后每次 SDK 移植、驱动更新都拿这三样对比一次。很多严重翻车现场事后看都是因为环境变了而没有留下“后悔药”比如板卡在测试机上调通了换到产线机器上驱动却加载不了回头一查内核版本差了三个小版本。PLX SDK for Linux V7.24 不是一套锁死的黑匣子它要求你对自己运行环境有清晰的认知。按这个顺序做完底层后面不管是调 FPGA 接口还是做性能数据通路基本就是水到渠成。希望这篇整理对你手里的板卡能有点用。本文还有配套的精品资源点击获取
返回列表