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编号intgpio_num=of_get_named_gpio(np,"reset-gpio",0);// 2. 申请GPIOif(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释放中断。
示例:
intirq_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(通用)structi2c_msgmsgs[]={// 第一步:读寄存器地址{.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 驱动中进行数据传输
// 构造传输结构体structspi_transfert={.tx_buf=tx_buf,// 发送缓冲区.rx_buf=rx_buf,// 接收缓冲区.len=4,// 传输长度.speed_hz=1000000,// 速率1MHz.cs_change=0,// 传输后不释放CS};structspi_messagemsg;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_系列接口解析:
structdevice_node*np=dev->of_node;// 1. 获取整型属性u32 reg_val;of_property_read_u32(np,"reg",®_val);// 2. 获取字符串属性charname[32];of_property_read_string(np,"name",name);// 3. 获取地址属性structresourceres;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编号intgpio=of_get_named_gpio(np,"reset-gpio",0);// 方式2:获取gpio_desc(推荐,支持更多特性)structgpio_desc*desc=gpiod_get_from_of_node(np,"reset-gpio",0,GPIOD_OUT_HIGH,NULL);// 操作GPIOgpiod_set_value(desc,0);// 释放gpiod_put(desc);七、驱动框架综合应用
1. 如何确保驱动资源的正确释放
设备管理接口(devm_*):优先使用
devm_kzalloc/devm_gpio_request/devm_request_irq,内核自动在设备卸载时释放资源,避免手动泄漏;remove/exit 函数:在驱动
remove(设备移除)/exit(模块卸载)函数中,释放未使用 devm 的资源(如手动分配的内存、注册的字符设备);错误回滚:probe 函数中,若某一步失败,回滚已分配的资源(如 goto 清理);
示例:
intmy_probe(...){void*ptr=devm_kzalloc(dev,size,GFP_KERNEL);intirq=devm_request_irq(dev,irq_num,handler,0,"irq",dev);if(irq<0)returnirq;// 自动释放ptr// devm资源无需手动回滚structcdev*cdev=cdev_alloc();if(!cdev){ret=-ENOMEM;gotoerr_cdev;}return0;err_cdev:// 手动释放非devm资源ret=-ENOMEM;}voidmy_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 不执行:检查总线是否注册、设备是否存在、资源是否冲突;
数据传输失败:检查硬件时序、地址、校验位,打印传输数据缓冲区。