|
做驱动开发,最怕的不是编译报错,而是内核panic、系统死机、驱动间歇性异常、卸载崩溃这类问题。本文整理驱动开发中的常见问题,覆盖构建、设备树、probe生命周期、并发、DMA、子系统、调试等核心领域,常见问题+完整解决方案,让排查有章可循。 使用方式:Ctrl+F搜索关键词(如vermagic、EPROBE_DEFER、dma_map_single、request_threaded_irq等)直接定位问题。 --- 问题定位流程 遇到问题时的通用排查顺序: 确认事实:硬件原理图与DTS是否一致 查看日志:dmesg | grep -i "error\|defer" 检查配置:compatible、status、interrupt等DTS配置 验证资源:clk/regulator/reset/pinctrl获取结果 分析代码:probe流程、资源申请释放是否对称 上工具:ftrace、lockdep、kasan定位 --- 构建与版本匹配 ① 内核版本不匹配导致vermagic错误 表现:insmod报错invalid module format 原因: .ko编译时使用的内核头/配置与目标机不一致 编译器版本差异 内核配置选项差异(CONFIG_SMP、CONFIG_PREEMPT) 命令: modinfo foo.ko | grep vermagicuname -rcat /proc/config.gz 2>/dev/null | zcat解决: 使用与目标机一致的内核源码和.config 统一工具链版本 编译外部模块: make -C /lib/modules/$(uname -r)/build M=$PWD modules--- ️ ② unknown symbol导出符号缺失 表现:dmesg显示unknown symbol bar (err 0) 原因: 依赖模块未加载或顺序错误 符号以EXPORT_SYMBOL_GPL导出但本模块非GPL 外部模块构建未携带Module.symvers 命令: grep symbol /proc/kallsymsmodprobe --show-depends foo解决: MODULE_LICENSE("GPL")# Makefile中指定依赖模块的Module.symversKBUILD_EXTRA_SYMBOLS := /path/to/dep_module/Module.symvers注意:modules_prepare不会生成Module.symvers,需要完整内核编译 --- ③ 交叉编译环境设置错误 表现:ABI不符、链接错误或目标机无法加载 原因:ARCH/CROSS_COMPILE设置错误 命令: # 32位ARMexport ARCH=armexport CROSS_COMPILE=arm-none-linux-gnueabihf-# 64位ARMexport ARCH=arm64export CROSS_COMPILE=aarch64-linux-gnu-file foo.koreadelf -h foo.ko--- ④ MODULE_LICENSE与GPL接口 原因: EXPORT_SYMBOL_GPL的符号必须配合MODULE_LICENSE("GPL") 非GPL模块使用GPL符号会导致内核taint --- 设备树与资源绑定 ① compatible不匹配导致驱动不probe 表现:节点存在但/sys无设备 排查: cat /sys/firmware/devicetree/base/xxx/compatible解决: /* DTS:具体→通用排序 */compatible = "vendor,chip-v2", "vendor,chip";/* 驱动 */static const struct of_device_id foo_of_match[] = { { .compatible = "vendor,chip-v2" }, { .compatible = "vendor,chip" }, { }};--- ② status = "disabled"或父节点未使能 排查: cat /sys/firmware/devicetree/base/xxx/status解决:节点和父节点的status都设为"okay" --- ③ interrupts映射失败 表现:platform_get_irq()返回负值 排查: dmesg | grep -i "irq\|interrupt"解决:核对interrupt-parent、触发类型、GPIO-IRQ需要interrupt-controller --- ️ ④ 时钟未使能或频率错误 表现:外设不工作/速率异常 排查: cat /sys/kernel/debug/clk/clk_summary解决: clk = devm_clk_get(&pdev->dev, "xclk");if (IS_ERR(clk)) return PTR_ERR(clk);ret = clk_prepare_enable(clk);if (ret) return ret;--- ⑤ resets未释放/复位域未拉起 表现:寄存器读常量/写不生效 解决: rst = devm_reset_control_get(&pdev->dev, NULL);if (IS_ERR(rst)) return PTR_ERR(rst);reset_control_deassert(rst);usleep_range(10000, 20000);--- ⑥ Regulator供电未就绪 表现:访问超时/电平错误 解决: vdd = devm_regulator_get(&pdev->dev, "vdd");if (IS_ERR(vdd)) return PTR_ERR(vdd);regulator_enable(vdd);usleep_range(10000, 20000);--- ⑦ DMA缓存一致性问题 表现:DMA数据错乱、花屏 排查: dmesg | grep -i "dma\|coherent"解决: /* CPU→设备 */dma_sync_single_for_device(dev, dma_addr, size, DMA_TO_DEVICE);/* 设备→CPU */dma_sync_single_for_cpu(dev, dma_addr, size, DMA_FROM_DEVICE);注意:DMA_TO_DEVICE=数据从CPU到设备,DMA_FROM_DEVICE=数据从设备到CPU --- 驱动probe()与生命周期 ① probe/remove不对称,资源泄漏 表现:驱动卸载后重新加载失败 原因:probe申请资源,remove未释放;probe中途失败未回滚 解决:使用devm_*简化资源管理 static int foo_probe(struct platform_device *pdev){ struct foo_dev *dev; dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; ret = devm_request_threaded_irq(&pdev->dev, irq, foo_isr, foo_thread, IRQF_ONESHOT, dev_name(&pdev->dev), dev); if (ret < 0) return ret; pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); return 0;}/* remove中无需手动释放,devm_*自动处理 */--- ️ ② -EPROBE_DEFER反复延迟探测 表现:probe反复被调用,设备无法就绪 排查: dmesg | grep -i defer解决:使用dev_err_probe自动打印defer原因 clk = devm_clk_get(&pdev->dev, NULL);if (IS_ERR(clk)) return dev_err_probe(&pdev->dev, PTR_ERR(clk), "get clk failed");--- ③ devm_*资源管理误用 注意: devm_*随设备生命周期释放,勿手动释放 不要混用devm和非devm资源 长寿对象用devm_add_action_or_reset收口 --- ④ 中断处理函数使用不当 表现:中断不触发或死锁 原因: 复杂逻辑放线程化中断 顶半部只清中断源、唤醒底半部 核对触发类型 解决: static irqreturn_t foo_isr(int irq, void *dev_id){ u32 status = readl(base + IRQ_STATUS); if (!(status & IRQ_DATA_READY)) return IRQ_NONE; writel(status, base + IRQ_STATUS); return IRQ_WAKE_THREAD;}static irqreturn_t foo_thread(int irq, void *dev_id){ struct foo_dev *dev = dev_id; mutex_lock(&dev->lock); process_data(dev); mutex_unlock(&dev->lock); return IRQ_HANDLED;}--- ️ ⑤ 中断风暴/虚假中断 表现:/proc/interrupts计数疯狂增长 排查: cat /proc/interrupts解决:检查中断源清除、触发类型 --- ⑥ pm_runtime未启用导致访问死机 表现:访问寄存器死机 解决: pm_runtime_enable(&pdev->dev);pm_runtime_get_sync(&pdev->dev);writel(0x1234, base);--- ️ ⑦ suspend/resume恢复失败 原因: 有向依赖(电源→时钟→外设) 时序敏感操作放noirq/late阶段 需配合pm_runtime 正确顺序: static int foo_suspend(struct device *dev){ pm_runtime_put(dev); /* 保存上下文 */ return 0;}static int foo_resume(struct device *dev){ pm_runtime_get_sync(dev); /* 恢复上下文 */ return 0;}恢复顺序:硬件初始化→时钟使能→外设配置→pm_runtime同步 --- 同步并发与内存安全 ① 中断上下文睡眠导致panic 表现:BUG: scheduling while atomic 原因:中断上下文调用了msleep、mutex_lock等睡眠函数 解决:使用线程化中断 static irqreturn_t foo_isr(int irq, void *dev_id){ writel(1, base + IRQ_CLEAR); return IRQ_WAKE_THREAD;}static irqreturn_t foo_thread(int irq, void *dev_id){ msleep(10); // 可睡眠 mutex_lock(&dev->lock); mutex_unlock(&dev->lock); return IRQ_HANDLED;}--- ② 自旋锁/互斥锁误用 要点: 中断上下文用spinlock(spin_lock_irqsave) 进程上下文用mutex 锁获取顺序一致,避免死锁 --- ③ 竞态条件导致数据错乱 表现:多线程/中断并发访问,数据错乱 解决:加锁保护 DEFINE_SPINLOCK(xxx_lock);spin_lock_irqsave(&xxx_lock, flags);/* 临界区 */spin_unlock_irqrestore(&xxx_lock, flags);--- ④ copy_to_user越界 解决: if (!access_ok(uarg, sizeof(*uarg))) return -EFAULT;if (copy_from_user(&karg, uarg, sizeof(karg))) return -EFAULT;--- ⑤ 内存检测工具 排查: CONFIG_KASAN=y CONFIG_KCSAN=y CONFIG_UBSAN=yecho scan > /sys/kernel/debug/kmemleakcat /sys/kernel/debug/kmemleak--- DMA/IOMMU/缓存一致性 ① dma_map_*与dma_alloc_*混用 要点: dma_alloc_coherent返回一致性缓冲,无需sync 流式缓冲需要dma_map/unmap与方向 --- ② 忘记dma_sync造成数据错乱 排查:dmesg | grep -i "dma\|coherent" 解决: /* CPU→设备 */dma_sync_single_for_device(dev, addr, size, DMA_TO_DEVICE);/* 设备→CPU */dma_sync_single_for_cpu(dev, addr, size, DMA_FROM_DEVICE);--- ③ DMA位宽不匹配 解决: ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40));if (ret) ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));--- ④ CMA不足 解决:bootargs设置cma=256M --- 子系统专项 ① I²C探测不到从设备 表现:-ENXIO/-ETIMEDOUT 排查: i2cdetect -y 0原因:地址错误、上拉不足、引脚未复用、总线被拉低 --- ② SPI模式/极性错误 表现:读回全0/全1、错位 排查: spidev_test -D /dev/spidevX.Y -s 1000000 -p "\xaa\x55"解决:确认mode0~3、CPOL/CPHA、片选极性 --- ③ UART ttyS不出现/波特率漂移 排查: ls /sys/class/tty/ttyS*stty -F /dev/ttyS0 115200 -a--- 调试与可观测性 ① oops/panic分析 排查: dmesgaddr2line -e vmlinux 0xffffff8008123456--- ② 动态调试dyndbg 使用: echo 'file drivers/foo/*.c +p' > /sys/kernel/debug/dynamic_debug/controlcat /sys/kernel/debug/dynamic_debug/control--- ③ ftrace使用 使用: cat /sys/kernel/debug/tracing/available_tracersecho function_graph > /sys/kernel/debug/tracing/current_tracertrace-cmd record -p function_graph -g foo_function -l 1trace-cmd report--- 崩溃与回溯 ① kdump/vmcore分析 配置:crashkernel=256M 使用: crash vmlinux vmcorecrash> btcrash> log--- 可维护性与兼容 ① 内核版本兼容 #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) /* 6.1+特定代码 */#else /* 旧版本代码 */#endif--- ② MODULE_FIRMWARE使用 要点:probe前后异步拉取、容错回退、版本管理 --- 热插拔与移除 ① remove资源回收 表现:驱动卸载后重新加载失败 解决: static int foo_remove(struct platform_device *pdev){ pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return 0;}--- 常用调试命令 # 内核日志dmesg | grep -i "error\|defer"# 模块lsmodmodinfo foo.ko# 设备树ls /sys/firmware/devicetree/base/# 中断cat /proc/interrupts# 时钟cat /sys/kernel/debug/clk/clk_summary--- 总结 资源对称:devm_*简化生命周期,申请释放配对 竞态加锁:中断用spinlock,进程用mutex 区分上下文:中断不能睡眠 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |