Linux 平台总线 LED 驱动架构深度解析笔记
基于韦东山Linux驱动基础视频:LED模板驱动程序的改造
1. 总体架构概览
这套代码展示了一个标准的 Linux 平台总线 (Platform Bus) 设备驱动模型 。其核心设计思想是 分层 (Layering) 与 分离 (Separation)。
- 分离:将"硬件有哪些(资源)"与"硬件怎么用(驱动)"分开。
- 分层:将"通用的字符设备接口"与"底层的硬件操作逻辑"分层。
系统分层图解
我们可以将系统划分为三层,由上至下分别为:
- 上层(核心层 Core):
leddrv.c- 职责 :负责向内核注册字符设备,向应用层提供标准文件接口 (
open/write),管理/dev设备节点。它不关心底层具体怎么操作硬件。
- 职责 :负责向内核注册字符设备,向应用层提供标准文件接口 (
- 中层(驱动层 Driver):
chip_demo_gpio.c- 职责 :作为
platform_driver,负责从 Device 获取资源,并实现具体的 GPIO 操作逻辑。它是连接核心层与硬件资源的桥梁。
- 职责 :作为
- 下层(设备层 Device):
board_A_led.c- 职责 :作为
platform_device,负责"描述"当前板子上有哪些 LED,引脚编号是多少。
- 职责 :作为
2. 详细文件功能分析
A. 头文件 (协议与接口)
led_resource.h- 定义数据格式。利用位操作宏
GROUP(x),PIN(x)将 GPIO 的组号和引脚号打包成一个整数,方便在resource中传递。
- 定义数据格式。利用位操作宏
led_opr.h- 定义 硬件抽象层 (HAL) 接口
struct led_operations。包含init(初始化) 和ctl(控制) 两个函数指针。这使得上层不需要知道底层的具体实现。
- 定义 硬件抽象层 (HAL) 接口
leddrv.h- 核心层导出的 API 声明,供驱动层调用,如
led_class_create_device和register_led_operations。
- 核心层导出的 API 声明,供驱动层调用,如
B. leddrv.c (核心层 - 字符设备框架)
这是通用的 LED 驱动框架,不包含任何具体的硬件寄存器操作。
- 关键数据 :
p_led_opr: 指向底层注册进来的操作结构体。led_drv:file_operations结构体,定义open,write等系统调用对应的处理函数。
- 关键机制 :
- 注册接口 :
register_led_operations(struct led_operations *opr),供底层驱动将自己的init/ctl函数注册进来。 - 用户调用响应 :
led_drv_open: 根据次设备号 (minor) 调用p_led_opr->init(minor)。led_drv_write: 获取用户数据,调用p_led_opr->ctl(minor, status)。
- 设备节点创建 :
led_class_create_device封装了device_create,供底层在探测到硬件资源时动态创建/dev/100ask_ledX。
- 注册接口 :
C. board_A_led.c (设备层 - 资源描述)
模拟硬件板级信息,纯数据描述。
- 资源传递 Hack :利用
IORESOURCE_IRQ类型的资源字段来存储自定义的 GPIO 编码(由GROUP_PIN生成)。 - 平台设备 :定义
struct platform_device,名字为"100ask_led"。 - 作用:加载时,在平台总线上挂载一个名为 "100ask_led" 的设备,携带了具体的引脚数据。
D. chip_demo_gpio.c (驱动层 - 逻辑实现)
这是最繁忙的一层,负责承上启下。
- 平台驱动 :定义
struct platform_driver,名字也为"100ask_led"。注意:名字必须与 Device 层完全一致才能触发匹配。 chip_demo_gpio_probe(核心入口) :- 当 Driver 和 Device 匹配成功时被内核调用。
- 调用
platform_get_resource从 Device 获取 GPIO 引脚号。 - 记录引脚信息到
g_ledpins。 - 调用
leddrv.c的led_class_create_device创建设备节点。
chip_demo_gpio_drv_init(模块入口) :- 调用
register_led_operations向核心层注册操作函数。 - 注册平台驱动。
- 调用
3. 完整运行流程 (Workflow)
为了看清各部分如何协作,我们模拟从 模块加载 到 用户控制 的全过程:
阶段 1: 模块加载与匹配
- 加载
leddrv.ko:- 内核有了
register_led_operations等符号。
- 内核有了
- 加载
chip_demo_gpio.ko:- 初始化时调用
register_led_operations,leddrv.c中的p_led_opr指针被赋值,指向具体的 GPIO 操作函数。 - 注册平台驱动,开始在总线上守候。
- 初始化时调用
- 加载
board_A_led.ko:- 注册平台设备 "100ask_led"。
- Match:平台总线发现 Device 和 Driver 名字相同。
- Probe :触发
chip_demo_gpio_probe。读取引脚资源,创建/dev/100ask_led0。
阶段 2: 用户空间调用
假设用户执行命令:./ledtest /dev/100ask_led0 on
- App :
main()调用write(fd, &val, 1)。 - Kernel VFS : 找到字符设备,调用
leddrv.c->led_drv_write。 - Core Layer :
led_drv_write解析出次设备号 0,调用p_led_opr->ctl(0, 1)。 - Driver Layer : 跳转到
chip_demo_gpio.c->board_demo_led_ctl。 - Implementation : 查表
g_ledpins[0]找到引脚,打印 "set led on..." (模拟硬件操作)。
4. 模块依赖与加载顺序指南
由于代码之间存在 符号依赖 (Symbol Dependency),加载和卸载模块必须严格遵守顺序。
依赖关系
chip_demo_gpio.ko依赖leddrv.ko- 原因:
chip_demo_gpio.c调用了leddrv.c导出的EXPORT_SYMBOL(如register_led_operations)。
- 原因:
board_A_led.ko不依赖 具体符号,但依赖平台总线机制。
正确的加载顺序 (insmod)
必须先让"被依赖者"进入内核。
insmod leddrv.ko- (建立地基:提供注册函数和设备创建函数)
insmod chip_demo_gpio.ko- (建立驱动逻辑:依赖 leddrv 的符号,注册操作函数,等待设备)
insmod board_A_led.ko- (提供硬件资源:触发 Probe,生成 /dev 节点)
- 注:
board_A_led.ko其实可以在第1步之后任意时间加载,但通常最后加载它来触发实际工作。
正确的卸载顺序 (rmmod)
必须先卸载"依赖者",防止指针悬空或内核崩溃。
rmmod board_A_led(移除设备资源)rmmod chip_demo_gpio(移除驱动逻辑,它依赖 leddrv)rmmod leddrv(最后拆除地基)
5. 总结:这套代码好在哪里?
- 可扩展性 :
- 换板子?只需重写
board_A_led.c,无需动驱动逻辑。 - 换芯片?只需重写
chip_demo_gpio.c,无需动上层逻辑。
- 换板子?只需重写
- 维护性 :核心层
leddrv.c极其稳定,作为通用框架,一旦写好几乎不需要修改。
这份笔记涵盖了从代码细节到架构设计的全部核心内容。