ESC
输入关键词搜索文章标题和内容

Linux驱动开发常见问题排查手册

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

Linux驱动开发常见问题排查手册

导读:做驱动开发,最怕的不是编译报错,而是内核panic、系统死机、驱动间歇性异常、卸载崩溃这类问题。本文整理驱动开发中的常见问题,覆盖构建、设备树、probe生命周期、并发、DMA、子系统、调试等核心领域,常见问题+完整解决方案,让排查有章可循。

使用方式:Ctrl+F搜索关键词(如vermagic、EPROBE_DEFER、dma_map_single、request_threaded_irq等)直接定位问题。


问题定位流程

遇到问题时的通用排查顺序:

flowchart LR A["📋 确认事实"] --> B["📝 查看日志"] B --> C["⚙️ 检查配置"] C --> D["✅ 验证资源"] D --> E["🔍 分析代码"] E --> F["🔧 上工具"] style A fill:#E3F2FD,stroke:#1565C0,stroke-width:2px,rx:8 style B fill:#E8F5E9,stroke:#2E7D32,stroke-width:2px,rx:8 style C fill:#FFF3E0,stroke:#E65100,stroke-width:2px,rx:8 style D fill:#E3F2FD,stroke:#1565C0,stroke-width:2px,rx:8 style E fill:#F3E5F5,stroke:#7B1FA2,stroke-width:2px,rx:8 style F fill:#E8F5E9,stroke:#2E7D32,stroke-width:2px,rx:8
  1. 确认事实:硬件原理图与DTS是否一致
  2. 查看日志:dmesg | grep -i "error\|defer"
  3. 检查配置:compatible、status、interrupt等DTS配置
  4. 验证资源:clk/regulator/reset/pinctrl获取结果
  5. 分析代码:probe流程、资源申请释放是否对称
  6. 上工具: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原因

来自 linuxros.cn · linuxROS
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
  • 区分上下文:中断不能睡眠
  • 善用工具:dmesg、ftrace、kasan

参考:
- Linux内核官方文档
- Dynamic DMA mapping Guide
- Generic IRQ handling
- Runtime PM Framework

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页