Linux 驱动核心知识
一、Linux 设备驱动模型
1. Linux 设备驱动模型的核心组件
- kobject:最基础对象,提供引用计数、sysfs 文件系统导出、对象生命周期管理;
- kset:kobject 的集合,按类别管理同类 kobject(如所有设备对象),支持统一事件处理;
- bus:总线抽象(如 platform/I2C/spi),是设备与驱动的连接媒介,定义匹配/解绑规则;
- device:表示硬件设备(物理/逻辑),存储设备属性、父/子设备关系,关联到具体总线;
- driver:驱动抽象,包含 probe(设备匹配后初始化)、remove(设备移除时清理)等核心接口;
- class:按功能归类设备(如 led/input),简化用户态访问(/sys/class/)。

2. 驱动和设备是如何匹配的
核心基于总线的匹配函数,分三类场景:
- 设备树匹配(主流):驱动的of_match_table中compatible属性,与设备树中设备节点的compatible字符串匹配;
- ID 匹配:驱动的 id_table(如 I2C 的 driver.id_table)与设备的 ID(如 I2C 的 client.addr)匹配;
- 平台总线匹配:平台设备(platform_device)的name与平台驱动(platform_driver)的driver.name完全匹配;
匹配成功后,总线调用驱动的 probe 函数完成设备初始化。

3. 设备树在驱动开发中的作用
- 硬件解耦:将硬件参数(寄存器地址、GPIO、中断号、时钟)从驱动代码中剥离,集中在设备树(.dts),无需修改驱动适配不同硬件;
- 动态配置:运行时通过设备树解析硬件信息,支持多平台复用同一驱动;
- 标准化:遵循 DTB 规范,统一硬件描述格式,简化驱动移植;
- 资源管理:驱动通过of_接口(如of_get_address)获取硬件资源,替代传统platform_get_resource。

二、GPIO 子系统
1. GPIO 子系统架构
- 底层:GPIO 控制器硬件驱动(如芯片级 GPIO 控制器),实现寄存器操作(方向/电平/中断);
- 核心层(gpiolib):提供统一的 GPIO 抽象接口(gpio_request/gpio_set_value),屏蔽不同控制器的硬件差异;
- 上层:驱动层调用 gpiolib 接口操作 GPIO,支持设备树解析、中断映射、sysfs 导出。

2. GPIO 子系统实现(核心逻辑)
- GPIO 控制器注册:驱动实现gpio_chip结构体(包含方向/电平/中断操作接口),通过gpiochip_add_data注册到 gpiolib;
- GPIO 编号管理:为每个 GPIO 分配全局编号(静态/base 或动态),关联到具体控制器;
- GPIO 请求/释放:驱动通过gpio_request申请 GPIO(防止冲突),gpio_free释放;
- 电平/方向操作:通过gpio_direction_input/output设置方向,gpio_get_value/set_value读写电平。

3. GPIO 子系统的主要组件
- gpio_chip:表示一个 GPIO 控制器,包含控制器的硬件操作接口(如方向设置、电平读写);
- gpio_desc:单个 GPIO 引脚的描述符,存储 GPIO 编号、方向、电平、中断属性、所属gpio_chip;
- gpiolib:核心库,提供 GPIO 申请、释放、电平操作、中断映射的统一接口;
- GPIO 控制器驱动:硬件层实现,对具体芯片的 GPIO 寄存器(如 NXP 高通 GPIO 控制器)操作。
4. 如何在驱动中使用 GPIO
// 1. 从设备树获取GPIO编号
int gpio_num = of_get_named_gpio(np, "reset-gpio", 0);
// 2. 申请GPIO
if (gpio_request(gpio_num, "reset-gpio") < 0) return –EBUSY;
// 3. 设置方向(输出)
gpio_direction_output(gpio_num, 1);
// 4. 操作电平
gpio_set_value(gpio_num, 0); // 拉低
// 5. 释放GPIO(驱动卸载时)
gpio_free(gpio_num);
进阶:使用devm_gpio_request(设备管理接口),自动释放 GPIO,避免内存泄漏。
5. GPIO 中断是如何实现
- 中断映射:通过gpio_to_irq将 GPIO 编号转换为中断号;
- 申请中断:调用request_irq注册中断处理函数,指定触发方式(上升沿/下降沿/双边沿);
- 硬件触发:GPIO 引脚电平变化触发控制器中断,内核调用注册的中断处理函数;
- 中断释放:驱动卸载时调用free_irq释放中断。
示例:
int irq_num = gpio_to_irq(gpio_num);
request_irq(irq_num, gpio_irq_handler, IRQF_TRIGGER_RISING, "gpio-irq", dev);
三、Pinctrl 子系统
1. Pinctrl 子系统架构
- 核心层:提供引脚复用、配置(上拉/下拉/速率)的统一接口;
- 硬件层:芯片级 pinctrl 驱动(如 pinctrl-s32k),实现寄存器级引脚配置;
- 设备树层:描述引脚组(pin group)、复用功能(function)、配置属性;
- 接口层:驱动通过pinctrl_get/pinctrl_select_state调用配置。

2. Pinctrl 子系统实现(核心步骤)
- pinctrl 驱动注册:实现pinctrl_desc结构体(包含引脚配置、复用接口),通过pinctrl_register注册;
- 设备树解析:解析设备节点的pinctrl-0/pinctrl-names属性,获取引脚状态(默认/休眠);
- 引脚配置:驱动通过pinctrl_select_state选择引脚状态,底层驱动修改寄存器配置复用/上下拉。

3. Pinctrl 子系统的作用
- 引脚复用:配置引脚功能(如同一引脚切换为 GPIO/I2C_SDA/SPI_CLK);
- 引脚配置:设置引脚的电气特性(上拉/下拉/驱动强度、速率、开漏/推挽);
- 引脚管理:统一管理系统所有引脚,避免引脚冲突;
- 状态切换:支持引脚在不同状态(默认/休眠/唤醒)下的配置切换,适配低功耗场景。
4. Pinctrl 与 GPIO 子系统的关系
- 依赖关系:GPIO 引脚必须先通过 Pinctrl 配置为 “GPIO 功能”,才能被 GPIO 子系统操作;
- 分工不同:Pinctrl 负责引脚的 “复用+电气配置”(硬件属性),GPIO 负责引脚的 “方向+电平+中断”(功能操作);
- 流程联动:驱动先通过 Pinctrl 将引脚设为 GPIO 模式,再调用 GPIO 子系统操作电平/中断。
5. 设备树中描述 Pinctrl 配置
// 1. 定义引脚组和配置
pinctrl@40014000 {
pinctrl_my_gpio: my-gpio-grp {
pinctrlx = <PIN_PA0L_GPIOx>; // 复用为GPIO
bias-pull-up; // 上拉
drive-strength = <20>; // 驱动强度20mA
};
};
// 2. 设备节点引用
my_device {
compatible = "vendor,my-device";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_my_gpio>; // 关联引脚配置
reset-gpio = <&gpioa 1 GPIO_ACTIVE_LOW>;
};
四、I2C 子系统
1. I2C 子系统架构
- 硬件层:I2C 控制器驱动(如 I2c-imx),实现总线时序(START/STOP/ACK)、寄存器操作;
- 核心层:I2C 核心(i2c-core),提供总线管理、设备/驱动注册、数据传输接口;
- 设备层:i2c_client(表示 I2C 设备),关联设备地址、总线号、设备树节点;
- 驱动层:i2c_driver(I2C 设备驱动),实现 probe/remove、数据传输接口。

2. I2C 子系统实现(核心逻辑)
- 控制器注册:I2C 控制器驱动通过i2c_add_adapter注册适配器(i2c_adapter);
- 设备注册:从设备树解析 I2C 设备节点,创建i2c_client并关联到适配器;
- 驱动匹配:i2c_driver的of_match_table与i2c_client的设备树节点匹配,调用 probe;
- 数据传输:驱动通过i2c_transfer/i2c_smbus_read/write完成数据收发。
3. I2C 子系统的主要组件
- i2c_adapter:表示 I2C 控制器(适配器),管理一条 I2C 总线,提供时序驱动;
- i2c_client:表示总线上的 I2C 设备,包含设备地址、所属 adapter、设备树信息;
- i2c_driver:I2C 设备驱动,包含匹配表、probe/remove、数据传输接口;
- i2c_msg:I2C 传输消息结构体,描述传输方向、地址、数据长度、数据缓冲区;
- i2c-core:核心层,负责 adapter/client/driver 的管理、匹配、传输调度。
4. I2C 设备驱动如何与设备匹配
- 设备树匹配(主流):i2c_driver的of_match_table中compatible与设备树 I2C 子节点的compatible匹配;
- ID 匹配:i2c_driver的id_table(包含设备名/地址)与i2c_client的name/addr匹配;
- 名称匹配:i2c_driver 的 driver.name 与 i2c_client 的 name 匹配;
匹配成功后,内核调用 i2c_driver 的 probe 函数,传入 i2c_client。
5. 如何在 I2C 驱动中进行数据传输
// 方式1:i2c_transfer(通用)
struct i2c_msg msgs[] = {
// 第一步:读寄存器地址
{ .addr = client->addr, .flags = 0, .len = 1, .buf = ®_addr },
// 第二步:读寄存器数据
{ .addr = client->addr, .flags = I2C_M_RD, .len = 2, .buf = data_buf },
};
i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
// 方式2:i2c_smbus(简化)
u16 data = i2c_smbus_read_word_data(client, reg_addr);


五、SPI 子系统
1. SPI 子系统架构
- 硬件层:SPI 控制器驱动(如 spi-imx),实现 SPI 时序(CLK/MOSI/MISO/CS)、寄存器操作;
- 核心层:SPI 核心(spi-core),管理适配器、设备、驱动,提供传输接口;
- 设备层:spi_device(SPI 设备),包含片选号、传输模式、速率、设备树信息;
- 驱动层:spi_driver(SPI 设备驱动),实现 probe/remove、数据传输逻辑。

2. SPI 子系统实现(核心逻辑)
- 控制器注册:SPI 控制器驱动通过spi_register_master注册主控制器(spi_master);
- 设备注册:从设备树解析 SPI 子节点,创建spi_device并关联到spi_master;
- 驱动匹配:spi_driver的of_match_table与spi_device的设备树节点匹配,调用 probe;
- 数据传输:驱动通过spi_transfer/spi_write/read完成数据收发。
3. SPI 子系统的主要组件
- spi_master:表示 SPI 控制器(主设备),管理一条 SPI 总线,提供时序驱动;
- spi_device:表示 SPI 从设备,包含片选号、传输速率、模式(CPOL/CPHA)、所属 master;
- spi_driver:SPI 设备驱动,包含匹配表、probe/remove、数据传输接口;
- spi_message:SPI 传输消息,包含多个spi_transfer,支持链式传输;
- spi_transfer:单个 SPI 传输单元,描述传输方向、数据长度、缓冲区、片选控制。
4. SPI 设备驱动如何与设备匹配
- 设备树匹配(主流):spi_driver的of_match_table中compatible与设备树 SPI 子节点的compatible匹配;
- ID 匹配:spi_driver的id_table(设备名)与spi_device的modalias匹配;
- 名称匹配:spi_driver 的 driver.name 与 spi_device 的 modalias 匹配;
匹配成功后,内核调用 spi_driver 的 probe 函数,传入 spi_device。
5. 如何在 SPI 驱动中进行数据传输
// 构造传输结构体
struct spi_transfer t = {
.tx_buf = tx_buf, // 发送缓冲区
.rx_buf = rx_buf, // 接收缓冲区
.len = 4, // 传输长度
.speed_hz = 1000000, // 速率1MHz
.cs_change = 0, // 传输后不释放CS
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&t, &msg);
// 执行传输
spi_sync(spi, &msg);
六、设备树与驱动匹配
1. 设备树中的 compatible 属性有什么作用?
- 核心作用:驱动与设备的匹配关键字,是设备树匹配的核心依据;
- 格式规则:字符串格式为 “厂商名,设备名”(如 “nxp,s32k344-i2c”),可包含多个字符串(兼容不同驱动);
- 匹配逻辑:驱动的of_match_table遍历compatible字符串列表,只要有一个匹配即触发 probe;
- 兼容性:支持 “向下兼容”(如新设备兼容旧驱动的compatible字符串)。
2. 驱动如何获取设备树中的属性
通过内核of_系列接口解析:
struct device_node *np = dev->of_node;
// 1. 获取整型属性
u32 reg_val;
of_property_read_u32(np, "reg", ®_val);
// 2. 获取字符串属性
char name[32];
of_property_read_string(np, "name", name);
// 3. 获取地址属性
struct resource res;
of_address_to_resource(np, 0, &res);
// 4. 获取数组属性
u32 arr[4];
of_property_read_u32_array(np, "data-arr", arr, 4);
3. 如何处理设备树中的 GPIO 描述
设备树描述格式:gpio = <&gpio控制器 引脚号 标志>;(如reset-gpio = <&gpioa 5 GPIO_ACTIVE_LOW>;);
驱动解析:
// 方式1:直接获取GPIO编号
int gpio = of_get_named_gpio(np, "reset-gpio", 0);
// 方式2:获取gpio_desc(推荐,支持更多特性)
struct gpio_desc *desc = gpiod_get_from_of_node(np, "reset-gpio", 0,
GPIOD_OUT_HIGH, NULL);
// 操作GPIO
gpiod_set_value(desc, 0);
// 释放
gpiod_put(desc);

七、驱动框架综合应用
1. 如何确保驱动资源的正确释放
- 设备管理接口(devm_*):优先使用devm_kzalloc/devm_gpio_request/devm_request_irq,内核自动在设备卸载时释放资源,避免手动泄漏;
- remove/exit 函数:在驱动remove(设备移除)/exit(模块卸载)函数中,释放未使用 devm 的资源(如手动分配的内存、注册的字符设备);
- 错误回滚:probe 函数中,若某一步失败,回滚已分配的资源(如 goto 清理);
示例:
int my_probe(...) {
void *ptr = devm_kzalloc(dev, size, GFP_KERNEL);
int irq = devm_request_irq(dev, irq_num, handler, 0, "irq", dev);
if (irq < 0) return irq; // 自动释放ptr
// devm资源无需手动回滚
struct cdev *cdev = cdev_alloc();
if (!cdev) {
ret = –ENOMEM;
goto err_cdev;
}
return 0;
err_cdev:
// 手动释放非devm资源
ret = –ENOMEM;
}
void my_remove(...) {
// 仅释放非devm资源
cdev_del(cdev);
}
2. 如何处理驱动的依赖关系
- 模块依赖:通过MODULE_DEPENDS声明依赖模块(如MODULE_DEPENDS("i2c-core")),或modprobe时指定依赖;
- 总线依赖:确保驱动依赖的总线(如 I2C/spi)已加载,可在 probe 中检查总线状态;
- 硬件依赖:通过设备树depends-on属性,确保依赖设备先初始化;
- 运行时依赖:使用devm_phandle_domain_attach/clk_prepare_enable等接口,确保时钟/电源/复位等依赖资源就绪后,再初始化驱动;
- 加载顺序:通过initcall_level(如module_init/late_initcall)调整驱动加载顺序,核心依赖先加载。
3. 如何调试 Linux 驱动问题
(1)内核日志调试
- 核心工具:dmesg/cat /var/log/kern.log,驱动中用dev_info/dev_err/pr_debug打印日志;
- 动态调试:开启CONFIG_DYNAMIC_DEBUG,通过echo "file my_driver.c +p" > /sys/kernel/debug/dynamic_debug/control打开调试日志。
(2)硬件调试
- 示波器/逻辑分析仪:抓取总线时序(I2C/SPI/GPIO),排查电平异常、时序不匹配;
- 万用表:测量电源/引脚电平,排查短路、供电异常。
(3)内核调试工具
- kgdb:内核远程调试,断点调试驱动代码;
- ftrace:跟踪函数调用(如 probe/remove)、中断执行时间;
- perf:分析驱动性能瓶颈(如 CPU 占用、函数耗时);
- sysfs/procfs:导出驱动状态(如/sys/class/gpio//proc/interrupts),查看资源占用。
(4)常见问题排查
- 匹配失败:检查设备树compatible与驱动of_match_table是否一致;
- probe 不执行:检查总线是否注册、设备是否存在、资源是否冲突;
- 数据传输失败:检查硬件时序、地址、校验位,打印传输数据缓冲区。
网硕互联帮助中心




评论前必须登录!
注册