基于 iTOP-4412 POP 1G 精英板 + linux-4.19.323,学习一下 gpio-leds 驱动。
设备树(DTS)配置
1 | // arch/arm/boot/dts/exynos4412-itop-pop1g-elite.dts |
leds
节点描述了LED相关的硬件资源。compatible = "gpio-leds"
:这是最关键的特性,确保该节点由drivers/leds/leds-gpio.c
驱动处理gpios
属性:指定了 LED 连接的 GPIO 控制器(&gpx1
)、引脚号(0
)和有效电平(GPIO_ACTIVE_HIGH
或GPIO_ACTIVE_LOW
)。default-state = "off"
定义设备的默认状态为关闭。- 设备树源文件(
.dts
)会被编译成二进制格式(.dtb
),并由 Bootloader 在启动时加载到内存中传递给内核。
控制
LED 设备树节点的 label
属性决定了其在 /sys/class/leds/
下的目录名,以上配置可得:
1 | root@iTop4412_Ubuntu16:/home/easy$ ls /sys/class/leds |
通过 /sys 节点控制
写入 1
点亮,0
熄灭。
1 | echo 1 > /sys/class/leds/red:user/brightness |
通过 C 程序控制
1 |
|
以上 2 种控制方式均需要 root 权限操作 sysfs 文件。
代码分析
整体流程
- dts 被编译成 dtb,dtb 烧录到存储卡后,在 uboot 中被加载到内存,并将首地址通过参数传给 kernel。
- 解析 dtb,dtb 被解析为
device_node
节点。 - 初始化 bus,注册
platform_bus
和platform_bus_type
。 - 初始化 driver。
- 匹配 device。
1 | // @ init/main.c |
dtb 解析
获取 machine_desc
1 | // @ arch/arm/kernel/setup.c |
setup_machine_fdt(__atags_pointer);
根据设备树根节点的 compatible 属性匹配 machine_desc 结构体,确定硬件平台(如开发板型号)。__atags_pointer是设备树物理地址,由Bootloader传入。setup_machine_tags(__atags_pointer, __machine_arch_type);
若设备树解析失败,通过传统 tag 列表(如ATAGS)匹配 machine_desc(旧式启动传参)。- 还是失败,则输出当前的 machine_arch_type、atags_pointer 地址,分别存在在 r1、r2 寄存器。
转为 device_node tree
1 | // @ of.h |
初始化 bus
1 | // @ drivers/base/platform.c |
driver 注册过程
1 | // drivers/leds/leds-gpio.c |
module_platform_driver 展开
1 | // @ include/linux/platform_device.h |
所以 module_platform_driver(gpio_led_driver);
会被展开成:
1 | // 1 |
platform_driver_register 到 platform_match
注册平台驱动时(platform_driver_register(&gpio_led_driver);
),会触发 platform_match。
1 | // @ include/linux/platform_device.h |
以上代码的大致过程为:
driver_find
:查找 driver 是否已经存在于 bus,是则提示并结束注册流程。bus_add_driver
:kobject_init_and_add
:在Sysfs中创建对应的驱动对象 (kobject
),通常位于/sys/bus/<bus_name>/drivers/
下。klist_add_tail
:将驱动添加到总线维护的驱动程序链表 (klist_drivers
) 中。driver_attach
:遍历总线上的所有设备,尝试将当前驱动与每个设备进行匹配。若匹配成功,则调用驱动的probe
函数。module_add_driver
:增加该模块的引用计数,防止驱动在被设备使用时模块被意外卸载。driver_create_file
:这会在 sysfs 对应的目录下(例如/sys/bus/<bus_name>/drivers/<driver_name>/
)创建一个名为uevent
的文件。driver_add_groups
:将驱动程序本身定义的属性组(drv->groups
)以及总线类型可能要求的默认属性组(bus->drv_groups
)添加到 sysfs 中。add_bind_files
:在sysfs 目录下创建bind
和unbind
文件。用户空间可以通过向这些文件写入设备号来动态地将设备绑定到该驱动或从该驱动解绑,为设备的热插拔和动态配置提供了灵活性。
kobject_uevent
:发送KOBJ_ADD
事件,通知用户空间有新驱动添加。
platform_match 匹配过程
1 | // @ drivers/base/platform.c |
driver_probe_device
1 | // @ drivers/base/dd.c |
首先初始化 pin:
pinctrl_bind_pins(dev);
再初始化 dma:
dma_configure(dev);
初始化 sysfs:
driver_sysfs_add(dev)
- 执行:
drv->probe(dev);
,即为gpio_led_probe
。
gpio_led_probe
1 | // @ drivers/leds/leds-gpio.c |
注册成功后,内核会在 /sys/class/leds/
目录下为每个 LED 创建一个子目录。