资源管理:I/O端口与内存映射
上周调一块工控板系统启动后网卡死活不认。查了半天发现是I/O空间冲突——新加的FPGA逻辑占用了网卡的端口范围。这种问题在嵌入式开发里太常见了今天咱们就聊聊Linux驱动里怎么管好这些硬件资源。从一次真实踩坑说起那天看到内核日志里连续打印“eth0: probe failed”第一反应是硬件问题。用逻辑分析仪抓PCIe配置周期发现网卡BAR空间分配正常。接着查I/O访问才发现问题出在request_region失败。原来同事在FPGA驱动里写死了0x1000-0x1FFF的端口范围正好跟网卡的I/O窗口重叠。内核可不会主动告诉你“这两个驱动在抢地盘”它只是默默让后加载的驱动申请失败。I/O端口老派但必须懂x86架构保留着独立的I/O空间用专门的in/out指令访问。虽然现在很多外设都走内存映射了但串口、GPIO控制器这些老伙计还在用端口。/* 正确姿势先申请再使用 */if(!request_region(0x3F8,8,my_serial)){printk(端口被占用了查查谁干的\n);// 这里踩过坑不申请直接用的驱动都是耍流氓return-EBUSY;}/* 操作端口 */outb(0x80,0x3F8);// 设置波特率valinb(0x3F85);// 读线路状态/* 用完记得还 */release_region(0x3F8,8);/proc/ioports文件能看到系统里所有登记的端口范围。调试时先看这个能省两小时。曾经有个项目USB控制器突然不工作了最后发现是有人把DMA控制器的端口范围改大了4个字节刚好覆盖了USB的配置寄存器。内存映射现代外设的主流选择内存映射I/O把外设寄存器映射到物理内存空间CPU用普通访存指令就能操作。ARM、PowerPC这些架构压根没有独立的I/O空间全靠MMIO。/* 映射物理地址到内核虚拟地址 */void__iomem*regsioremap(0xFE000000,0x1000);if(!regs){printk(映射失败检查地址是不是太野了\n);return-ENOMEM;}/* 用专用接口读写 */u32 valioread32(regs0x10);iowrite32(0x12345678,regs0x20);/* 带屏障的版本确保执行顺序 */iowrite32_rep(regs,buffer,count);/* 取消映射 */iounmap(regs);这里有个细节ioremap返回的是void __iomem *类型编译器会阻止你直接用指针解引用。必须用ioread32这类接口为什么因为有些架构比如Alpha的I/O空间有特殊的内存序要求而且PCI设备可能有写合并限制。我见过有人用*(u32 *)regs直接操作在x86上跑得好好的一到PowerPC就数据错乱。资源管理框架内核提供了统一的资源管理机制把端口和内存映射都抽象成struct resourcestaticstructresourcemy_dev_resources[]{[0]{.start0xFE000000,.end0xFE000FFF,.namedev_regs,.flagsIORESOURCE_MEM,// 内存资源},[1]{.start0x1000,.end0x1007,.namedev_io,.flagsIORESOURCE_IO,// 端口资源}};/* 在probe函数里申请 */resplatform_get_resource(pdev,IORESOURCE_MEM,0);basedevm_ioremap_resource(pdev-dev,res);// 这个带错误处理和自动释放if(IS_ERR(base))returnPTR_ERR(base);用devm_开头的托管函数能自动释放资源驱动卸载或probe失败时不用手动清理。但注意devm_ioremap_resource会自己申请资源别在前面再调request_region。调试技巧cat /proc/iomem和cat /proc/ioports是首选工具一眼看出资源分配情况用/sys/kernel/debug/ioports能看到更详细的占用者信息怀疑资源冲突时临时注释掉可疑驱动的request_region看问题是否消失对于PCI设备lspci -vv显示的BAR空间信息比内核日志更直观去年调一个四网口板卡第三个网口时好时坏。最后发现是第二个网卡驱动在remove函数里漏了iounmap内核重启后物理地址被重复映射两个虚拟地址操作同一套寄存器时序全乱了。个人经验永远别写死资源地址用设备树或ACPI传递。我们有个项目换了芯片型号地址偏移变了改设备树比重新编译驱动方便多了先申请后使用哪怕你知道这个地址肯定没人用。有个客户在驱动里偷懒没申请后来系统升级加了个看门狗驱动正好地址重叠设备半年才死一次机查了三个月MMIO访问用屏障特别是多核处理器。曾经在ARM Cortex-A9上遇到寄存器配置不生效加了wmb()就好了释放资源要配对ioremap对iounmaprequest_region对release_region。最好用devm_托管让内核帮你记着查冲突从启动日志看起内核启动时打印的资源分配信息很有用特别是那句“resource conflict”资源管理就像开车系安全带平时觉得麻烦出事时能救命。好的驱动代码不仅要功能正确还要和其他驱动和睦相处。毕竟内核是大家的不能只顾自己跑得欢。