『7x24小时有问必答』
做驱动开发,最怕的不是编译报错,而是内核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 vermagic

uname  -r

cat  /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/kallsyms

modprobe --show-depends foo

解决:
MODULE_LICENSE("GPL")

# Makefile中指定依赖模块的Module.symvers

KBUILD_EXTRA_SYMBOLS := /path/to/dep_module/Module.symvers

注意:modules_prepare不会生成Module.symvers,需要完整内核编译

---

③ 交叉编译环境设置错误

表现:ABI不符、链接错误或目标机无法加载
原因:ARCH/CROSS_COMPILE设置错误
命令:
# 32位ARM

export  ARCH=arm

export  CROSS_COMPILE=arm-none-linux-gnueabihf-

# 64位ARM

export  ARCH=arm64

export  CROSS_COMPILE=aarch64-linux-gnu-

file foo.ko

readelf -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=y

echo  scan > /sys/kernel/debug/kmemleak

cat  /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分析

排查:
dmesg

addr2line -e vmlinux 0xffffff8008123456

---

② 动态调试dyndbg

使用:
echo  'file drivers/foo/*.c +p'  > /sys/kernel/debug/dynamic_debug/control

cat  /sys/kernel/debug/dynamic_debug/control

---

③ ftrace使用

使用:
cat  /sys/kernel/debug/tracing/available_tracers

echo  function_graph > /sys/kernel/debug/tracing/current_tracer

trace-cmd record -p function_graph -g foo_function -l 1

trace-cmd report

---

崩溃与回溯

① kdump/vmcore分析

配置:crashkernel=256M
使用:
crash vmlinux vmcore

crash> bt

crash>  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"

# 模块

lsmod

modinfo foo.ko

# 设备树

ls  /sys/firmware/devicetree/base/

# 中断

cat  /proc/interrupts

# 时钟

cat  /sys/kernel/debug/clk/clk_summary

---

总结

资源对称:devm_*简化生命周期,申请释放配对
竞态加锁:中断用spinlock,进程用mutex
区分上下文:中断不能睡眠

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码