[TOC]
drivers/leds LED子系统(LED Subsystem) 内核统一的LED设备控制框架 历史与背景 这项技术是为了解决什么特定问题而诞生的? LED子系统的诞生是为了解决在Linux内核中缺乏一个统一、抽象的LED管理方式 的问题。在此框架出现之前,对LED的控制是零散且混乱的:
驱动代码重复 :每个需要控制LED的驱动程序(例如,网卡驱动、SD卡驱动)都必须自己去实现操作GPIO或特定硬件寄存器的代码,导致大量功能相似的代码被重复编写。
缺乏统一的用户接口 :用户无法以一种标准的方式来查看系统中有哪些LED,也无法在运行时改变它们的行为。控制LED的逻辑被硬编码在各自的驱动中。
逻辑与硬件强耦合 :LED的“物理”控制(如何点亮它)和“逻辑”行为(为什么点亮它,例如因为网络活动)被混杂在一起。这使得更换LED硬件或改变其用途变得非常困难。
LED子系统的核心目标就是创建一个清晰的框架,将LED的物理控制 与触发其亮灭的系统事件 彻底解耦,并为用户空间提供一个标准的控制接口。
它的发展经历了哪些重要的里程碑或版本迭代? LED子系统的发展核心是触发器(Trigger)机制 的引入和完善,这是其设计的精髓所在。
基础框架建立 :最初,子系统定义了led_classdev结构体,为所有LED设备提供了一个通用的注册接口和基于sysfs的属性(如brightness)。这解决了驱动代码重复和缺乏统一接口的问题。
触发器机制的引入 :这是最重要的里程碑。内核引入了led_trigger的概念,它代表一种可以触发LED状态变化的系统事件。例如,“硬盘活动”是一个触发器,“网络活动”是另一个。任何LED设备都可以通过sysfs在运行时动态地“挂接”到任何一个可用的触发器上。这完美地实现了逻辑与硬件的解耦。
常见触发器的标准化 :随着框架的成熟,社区逐步添加了许多通用的、可复用的触发器,如heartbeat(心跳,表示系统正常运行)、timer(定时闪烁)、disk-activity(磁盘活动)、netdev(网络设备活动)、input(响应输入事件,如大小写锁定键)等。
支持更复杂的LED :框架从最初只支持简单的亮/灭,扩展到支持多级亮度控制、RGB颜色控制等更复杂的LED硬件。
目前该技术的社区活跃度和主流应用情况如何? LED子系统是Linux内核中一个非常稳定、成熟且被广泛应用的基础设施。它在各种类型的Linux设备上都扮演着重要的角色。
主流应用 :
嵌入式设备和路由器 :用于显示电源、网络状态、Wi-Fi信号强度等。
服务器和NAS :用于显示硬盘托架的活动或故障状态。
单板计算机(如树莓派) :用于显示电源和SD卡活动状态。
笔记本电脑 :用于控制电源、Wi-Fi、大小写锁定等状态指示灯。
核心原理与设计 它的核心工作原理是什么? LED子系统的核心是设备驱动 和触发器 分离的生产者/消费者模型。
LED设备驱动(生产者) :
这是一个知道如何控制一个物理LED 的驱动程序。例如,leds-gpio驱动知道如何通过翻转GPIO电平来点亮/熄灭LED;leds-pca9532驱动知道如何通过I2C总线与一个LED控制器芯片通信。
它会填充并注册一个struct led_classdev实例。这个结构体中最核心的回调函数是.brightness_set(),LED核心层通过调用它来改变LED的亮度(0代表熄灭)。
LED核心层(协调者) :
这是位于drivers/leds/led-class.c等文件中的子系统核心。
它管理所有注册的led_classdev,并在sysfs中为它们创建目录(通常在/sys/class/leds/下)。
它还管理所有注册的led_trigger,并通过sysfs文件trigger让用户可以进行关联。
LED触发器(消费者/事件源) :
这是一个代表系统事件 的模块。例如,ledtrig-disk.c会在块设备层有I/O活动时被通知。
当一个触发器被关联到一个LED设备上,并且其代表的事件发生时,触发器会调用LED核心层的函数(如led_trigger_event())。
最终,LED核心层会调用具体LED设备驱动的.brightness_set()回调,完成对物理LED的控制。
用户空间接口(Sysfs) :
用户可以通过/sys/class/leds/下的目录来与LED交互。
cat brightness可以查看当前亮度。
echo 255 > brightness可以手动设置亮度。
cat trigger可以查看当前关联的触发器和所有可用的触发器(当前触发器会被方括号[]括起来)。
echo disk-activity > trigger可以将这个LED的行为模式切换为“硬盘活动指示灯”。
它的主要优势体现在哪些方面?
彻底解耦 :将“如何亮”和“为何亮”分开,极大地提高了代码的模块化和复用性。
用户可定制 :允许用户在系统运行时,通过简单的shell命令动态改变任何LED的功能,提供了极大的灵活性。
代码复用 :触发器(如heartbeat)和设备驱动(如leds-gpio)都可以被无限次复用。
标准化 :为所有LED提供了一致的接口和行为模式。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
不适用于复杂灯效 :该框架主要为简单的状态指示而设计。对于需要高速、同步、复杂动画效果的场景(如RGB电竞键盘/鼠标),它的sysfs接口性能不足,且缺乏复杂的模式编程能力。
非显示设备 :它不适用于需要显示复杂信息(如字符、数字)的设备,例如7段数码管。
与Backlight子系统的界限 :虽然LED子系统可以控制亮度,但对于屏幕或键盘背光这类用于“照明”而非“指示”的设备,有专门的Backlight子系统,它与电源管理和图形栈的集成更好。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。 LED子系统是任何需要在硬件上提供系统状态指示 的标准解决方案。
网络设备活动 :将一个以太网口的LED关联到netdev触发器。可以配置它在有网络流量(TX/RX)、或链接状态(Link Up)时闪烁/点亮。
系统“心跳” :在一个没有屏幕的嵌入式设备上,将一个LED关联到heartbeat触发器,使其有规律地闪烁。这可以直观地判断系统是否仍在正常运行,还是已经死机。
存储活动 :将服务器硬盘托架上的LED关联到disk-activity或具体的scsi_host触发器,以显示哪个硬盘正在进行读写操作。
按键状态 :笔记本上的大小写锁定(Caps Lock)灯。input子系统会生成事件,ledtrig-input触发器捕获这个事件并点亮/熄灭由leds-gpio控制的Caps Lock LED。
是否有不推荐使用该技术的场景?为什么?
RGB电竞外设 :不推荐。这些设备通常通过USB HID协议与用户空间的守护进程通信,以实现复杂的、可编程的灯光效果。
LCD/OLED屏幕背光 :不推荐。应使用Backlight子系统,因为它提供了更符合语义的接口,并且能更好地与系统的显示和电源管理策略(如空闲时自动调暗)集成。
对比分析 请将其 与 其他相似技术 进行详细对比。
特性
LED Subsystem
Backlight Subsystem
直接GPIO控制 (无框架)
核心功能
状态指示 (Indication) 。核心是“为什么亮”(触发器)。
照明 (Illumination) 。核心是“有多亮”(亮度级别)。
直接的物理控制 。
抽象层次
高 。将物理LED与逻辑事件完全分离。
中等 。抽象了不同背光硬件的亮度控制,但没有触发器概念。
无抽象 。驱动直接与硬件打交道。
用户接口
Sysfs (/sys/class/leds),提供亮度、触发器等丰富控制。
Sysfs (/sys/class/backlight),主要提供亮度和电源状态控制。
无标准用户接口。
典型设备
状态指示灯(电源、网络、磁盘活动、Caps Lock)。
LCD屏幕背光、键盘背光。
任何需要简单开关控制的场景,但只在没有更好框架时才应考虑。
灵活性
非常高 。用户可在运行时任意改变LED的功能。
中等 。用户可以调节亮度,但其功能是固定的。
极低 。功能在驱动中硬编码,无法更改。
适用场景
需要向用户传达系统状态信息 。
需要为用户提供视觉照明 。
在极简的、不需要任何抽象或用户控制的场景下(现代内核中几乎不存在)。
drivers/leds/led-core.c LED 亮度更新与设置 此代码片段包含了Linux LED子系统中两个核心的API函数, 用于获取和设置LED的亮度 。它们是LED驱动程序、触发器以及sysfs接口之间交互的中枢。其核心原理是提供一个统一的、健壮的接口来改变LED状态, 同时优雅地处理与软件控制的”闪烁”(blinking)功能之间的复杂交互 。
led_update_brightness: 同步软件状态与硬件现实这个函数提供了一个”拉”(pull)机制, 用于查询LED硬件的当前实际亮度, 并更新内核中对应的软件状态。
原理 : 某些LED硬件或控制器可能具有自主改变亮度的能力(例如, 硬件控制的”呼吸”效果), 或者其状态可能被其他方式改变。这个函数允许LED子系统在需要时(例如, 当用户通过sysfs读取亮度时)向底层硬件驱动查询最新的状态。这是一个可选功能, 只有当硬件驱动在led_classdev结构体中提供了brightness_get回调函数时, 此机制才会生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int led_update_brightness (struct led_classdev *led_cdev) { int ret; if (led_cdev->brightness_get) { ret = led_cdev->brightness_get(led_cdev); if (ret < 0 ) return ret; led_cdev->brightness = ret; } return 0 ; } EXPORT_SYMBOL_GPL(led_update_brightness);
LED 亮度设置核心逻辑 此代码片段揭示了Linux LED子系统中负责设置亮度的核心”引擎”。它由两个函数组成, 共同实现了一个健壮、高效且支持异步操作的亮度设置机制。其核心原理是采用”快速路径/慢速路径”(Fast Path/Slow Path)的设计模式: 优先尝试一个快速、非阻塞的操作; 如果该操作不可行(因为它可能需要睡眠), 则将任务安全地委托给一个内核工作队列(workqueue)进行异步处理 。
led_set_brightness_nosleep: 安全的亮度设置入口点这个函数是led_set_brightness的下一层实现, 充当了一个安全的”预处理器”和入口点。它负责在尝试改变硬件状态之前, 对输入值进行清理并检查设备的电源状态。
原理 :
输入值净化 : 它使用min()函数确保请求的亮度值不会超过硬件所能支持的最大亮度。这是一个重要的健壮性措施, 防止向底层驱动传递无效值。
电源状态检查 : 它会检查LED_SUSPENDED标志, 如果设备当前处于挂起(suspend)状态, 它会更新软件中缓存的亮度值, 但会跳过所有硬件操作 。这确保了在设备恢复(resume)后, 可以设置正确的亮度, 同时避免了在设备电源关闭时访问硬件寄存器。
分发 : 在通过所有检查后, 它调用led_set_brightness_nopm来执行真正的设置逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void led_set_brightness_nosleep (struct led_cdev *led_cdev, unsigned int value) { led_cdev->brightness = min(value, led_cdev->max_brightness); if (led_cdev->flags & LED_SUSPENDED) return ; led_set_brightness_nopm(led_cdev, led_cdev->brightness); } EXPORT_SYMBOL_GPL(led_set_brightness_nosleep);
led_set_brightness_nopm: 快速路径/慢速路径分发器这个函数是亮度设置的核心决策者。它决定是立即完成操作, 还是将其推迟到工作队列中。
原理 :
尝试快速路径 : 它首先调用__led_set_brightness。这个内部函数通常会调用硬件驱动提供的brightness_set_blocking回调, 该回调被保证不会睡眠 。如果硬件操作非常简单(例如, 写入一个内存映射的寄存器), 这个回调就会存在并成功返回0。此时, 任务完成, 函数直接返回。
切换到慢速路径 : 如果__led_set_brightness失败(返回非0), 这意味着硬件驱动的亮度设置操作可能会睡眠(例如, 通过I2C或SPI总线通信)。由于当前上下文可能不允许睡眠(例如, 在中断处理程序或持有自旋锁时), 必须采用异步方式。
准备工作 : 它将要设置的亮度值存入delayed_set_value。
设置信标 : 它使用原子操作(set_bit/clear_bit)来设置work_flags中的标志位。这些标志就像是给即将运行的工作队列任务的”指令”。它为”设置为非零值”和”设置为零(关闭)”这两种情况设置了不同的标志, 因为关闭LED可能需要额外处理(如停止硬件闪烁)。
调度任务 : 最后, 它调用queue_work将set_brightness_work任务放入leds_wq工作队列中。内核的工作队列线程会在稍后一个安全的、允许睡眠的上下文中执行这个任务, 任务会读取work_flags和delayed_set_value来完成实际的硬件操作。
(单核)系统上, smp_mb__before_atomic()主要作为编译器屏障 , 防止编译器对内存访问和原子操作进行不安全的重排序, 这对于保证在抢占式内核中的逻辑正确性仍然是必要的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 static int __led_set_brightness(struct led_classdev *led_cdev, unsigned int value){ if (!led_cdev->brightness_set) return -ENOTSUPP; led_cdev->brightness_set(led_cdev, value); return 0 ; } void led_set_brightness_nopm (struct led_cdev *led_cdev, unsigned int value) { if (!__led_set_brightness(led_cdev, value)) return ; led_cdev->delayed_set_value = value; smp_mb__before_atomic(); if (value) { set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); } else { clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); clear_bit(LED_SET_BLINK, &led_cdev->work_flags); set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags); } queue_work(led_cdev->wq, &led_cdev->set_brightness_work); } EXPORT_SYMBOL_GPL(led_set_brightness_nopm);
led_set_brightness: 设置LED亮度的主要API这个函数是设置LED亮度的标准入口点。它的设计非常巧妙, 充当了一个”分发器”(dispatcher), 能够正确处理普通设置和软件闪烁这两种截然不同的情况。
原理 : 软件闪烁是通过一个定时器和工作队列(workqueue)实现的, 它会周期性地改变LED的亮度。如果此时有一个新的亮度设置请求进来, 就会产生竞争条件: 新设置的亮度可能在下一个定时器滴答(tick)时被闪烁逻辑覆盖掉。此函数通过以下方式解决这个问题:
检查闪烁状态 : 它首先使用test_bit(LED_BLINK_SW, ...)原子地检查软件闪烁功能是否处于激活状态。
与闪烁逻辑协作 : 如果正在闪烁:
请求关闭闪烁 : 如果新亮度为0, 它不会直接关闭LED, 而是设置一个LED_BLINK_DISABLE标志, 然后调度工作队列任务。这相当于给正在运行的闪烁任务发送一个”请自行优雅关闭”的信号。这样做可以避免在中断等不允许睡眠的上下文中直接操作定时器。
请求改变闪烁亮度 : 如果新亮度非0, 它也不会直接设置, 而是设置一个LED_BLINK_BRIGHTNESS_CHANGE标志, 并将新亮度值存入new_blink_brightness。闪烁任务在下一次运行时会检查这个标志, 并自动采用新的亮度值作为其”亮”状态的电平。
直接设置 : 如果当前没有在进行软件闪烁, 函数会直接调用led_set_brightness_nosleep, 这将最终调用底层硬件驱动的.brightness_set()回调函数来实际改变硬件状态。
工作队列 : queue_work用于将可能引起睡眠的操作(如修改定时器)从中断上下文(Interrupt Context)推迟到进程上下文(Process Context)中执行, 这是编写健壮驱动程序的关键。
原子操作 : test_bit和set_bit是原子操作, 它们可以防止在检查和设置work_flags的过程中被中断或任务抢占, 从而避免了在单核抢占式系统上的竞态条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 void led_set_brightness (struct led_classdev *led_cdev, unsigned int brightness) { if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { if (!brightness) { set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); queue_work(led_cdev->wq, &led_cdev->set_brightness_work); } else { set_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags); led_cdev->new_blink_brightness = brightness; } return ; } led_set_brightness_nosleep(led_cdev, brightness); } EXPORT_SYMBOL_GPL(led_set_brightness);
led_stop_software_blink: 安全地终止LED的软件闪烁 此函数是一个核心辅助API, 其唯一的作用是安全、完整地停止由内核定时器驱动的LED软件闪爍功能 。当一个触发器被移除, 或者用户通过sysfs写入亮度0来关闭闪烁时, 就会调用此函数。
它的工作原理是执行一个严格的、有序的清理流程, 以确保将LED的状态从”定时器驱动”模式彻底转换回”手动控制”模式, 同时避免任何竞态条件。
同步删除定时器 : timer_delete_sync(&led_cdev->blink_timer)是此函数最关键的操作。blink_timer是周期性触发LED亮灭状态改变的内核定时器。timer_delete_sync函数会停用这个定时器。_sync后缀至关重要, 它保证了函数会等待定时器的处理函数(handler)执行完毕后才返回(如果它恰好正在运行)。这可以防止在处理函数仍在访问led_cdev数据时, 其他代码已经开始修改这些数据, 从而杜绝了”use-after-free”或数据竞争的风险。
状态复位 : 在确认定时器已完全停止后, 函数将与闪烁相关的状态变量blink_delay_on和blink_delay_off清零。这是一种良好的编程实践, 可以防止在未来重新启用闪烁时, 意外地使用了过时的、无效的延迟值。
清除标志位 : clear_bit(LED_BLINK_SW, &led_cdev->work_flags)以原子操作的方式清除LED_BLINK_SW状态标志。这个标志是其他函数(如led_set_brightness)用来判断是否应将亮度设置请求委托给闪烁逻辑的依据。清除此标志后, LED的行为会立即恢复到直接响应亮度设置请求的模式。
timer_delete_sync的同步特性依然重要, 它可以防止在定时器中断刚发生、其对应的软中断(softirq)即将执行时, 出现状态不一致的问题。
clear_bit的原子性保证了即使在读-修改-写work_flags的过程中发生任务抢占或中断, 也不会破坏该变量的完整性, 这对于编写健壮的内核代码至关重要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void led_stop_software_blink (struct led_classdev *led_cdev) { timer_delete_sync(&led_cdev->blink_timer); led_cdev->blink_delay_on = 0 ; led_cdev->blink_delay_off = 0 ; clear_bit(LED_BLINK_SW, &led_cdev->work_flags); } EXPORT_SYMBOL_GPL(led_stop_software_blink);
drivers/leds/led-triggers.c LED sysfs 触发器(Trigger)读取实现 此代码片段实现了用户通过sysfs文件系统读取一个LED可用”触发器”列表的功能, 对应于cat /sys/class/leds/.../trigger命令。其核心原理是动态地生成一个格式化的字符串, 该字符串列出了所有已注册且与当前LED相关的触发器, 并用方括号[]明确标识出当前正被激活的触发器 。
由于在某些极端情况下(如拥有数千个CPU核心的系统), “cpu”触发器的数量可能非常多, 导致列表的长度超过sysfs普通文本属性4KB的页面大小限制, 因此这里采用了**二进制属性(bin_attribute)**的实现方式。这是一种更灵活但不推荐常规使用的sysfs接口, 它允许读写任意长度的数据。
led_trigger_read: trigger文件的读操作回调这是当用户空间读取trigger文件时, 内核调用的主函数。它采用了一种健壮且高效的”两遍式”(two-pass)策略来生成和返回数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 ssize_t led_trigger_read (struct file *filp, struct kobject *kobj, const struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); struct led_classdev *led_cdev = dev_get_drvdata(dev); void *data; int len; down_read(&triggers_list_lock); down_read(&led_cdev->trigger_lock); len = led_trigger_format(NULL , 0 , led_cdev); data = kvmalloc(len + 1 , GFP_KERNEL); if (!data) { up_read(&led_cdev->trigger_lock); up_read(&triggers_list_lock); return -ENOMEM; } len = led_trigger_format(data, len + 1 , led_cdev); up_read(&led_cdev->trigger_lock); up_read(&triggers_list_lock); len = memory_read_from_buffer(buf, count, &pos, data, len); kvfree(data); return len; } EXPORT_SYMBOL_GPL(led_trigger_read);
LED sysfs 触发器(Trigger)写入实现 此代码片段实现了用户通过sysfs文件系统设置一个LED”触发器”的功能, 对应于echo "timer" > /sys/class/leds/.../trigger这样的命令。其核心原理是将用户写入的字符串与一个全局的、已注册的触发器列表进行匹配, 并在找到匹配项后, 调用该触发器的activate函数, 将LED的控制权移交给这个触发器 。
trigger_relevant: 触发器相关性过滤器这是一个小型的内联辅助函数, 它的作用是在庞大的触发器列表中, 筛选出那些与当前特定LED设备兼容或相关的触发器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static inline bool trigger_relevant (struct led_classdev *led_cdev, struct led_trigger *trig) { return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type; }
led_trigger_write: trigger文件的写操作回调这是当用户空间向trigger文件写入数据时, 内核调用的主函数。它负责解析用户的意图并执行相应的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 ssize_t led_trigger_write (struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_trigger *trig ; int ret = count; mutex_lock(&led_cdev->led_access); if (led_sysfs_is_disabled(led_cdev)) { ret = -EBUSY; goto unlock; } if (sysfs_streq(buf, "none" )) { led_trigger_remove(led_cdev); goto unlock; } if (sysfs_streq(buf, "default" )) { led_trigger_set_default(led_cdev); goto unlock; } down_read(&triggers_list_lock); list_for_each_entry(trig, &trigger_list, next_trig) { if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) { down_write(&led_cdev->trigger_lock); led_trigger_set(led_cdev, trig); up_write(&led_cdev->trigger_lock); up_read(&triggers_list_lock); goto unlock; } } ret = -EINVAL; up_read(&triggers_list_lock); unlock: mutex_unlock(&led_cdev->led_access); return ret; } EXPORT_SYMBOL_GPL(led_trigger_write);
这个辅助函数负责构建用户最终看到的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static int led_trigger_format (char *buf, size_t size, struct led_classdev *led_cdev) { struct led_trigger *trig ; int len = led_trigger_snprintf(buf, size, "%s" , led_cdev->trigger ? "none" : "[none]" ); if (led_cdev->default_trigger) len += led_trigger_snprintf(buf + len, size - len, " default" ); list_for_each_entry(trig, &trigger_list, next_trig) { bool hit; if (!trigger_relevant(led_cdev, trig)) continue ; hit = led_cdev->trigger && !strcmp (led_cdev->trigger->name, trig->name); len += led_trigger_snprintf(buf + len, size - len, " %s%s%s" , hit ? "[" : "" , trig->name, hit ? "]" : "" ); } len += led_trigger_snprintf(buf + len, size - len, "\n" ); return len; }
led_trigger_snprintf: 安全的格式化打印工具这是一个小型的辅助函数, 它是实现”两遍式”策略的关键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 __printf(3 , 4 ) static int led_trigger_snprintf (char *buf, ssize_t size, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); if (size <= 0 ) i = vsnprintf(NULL , 0 , fmt, args); else i = vscnprintf(buf, size, fmt, args); va_end(args); return i; }
LED 触发器(Trigger)管理核心 此代码片段是Linux内核LED子系统中负责动态管理”触发器”(Trigger)的核心逻辑。触发器是一种机制, 它允许一个内核子系统(如定时器、磁盘I/O、CPU负载)接管一个LED的控制权, 以自动地、有规律地改变其状态来反映系统事件。这些函数共同实现了设置、移除和恢复默认触发器 的完整生命周期管理。
其核心原理是通过一个”先拆后建”(Teardown-then-Setup)的原子操作, 安全地将LED的控制权从一个触发器转移到另一个, 同时处理好资源清理、sysfs接口更新以及与用户空间的通信 。
led_trigger_set: 核心引擎 - 设置或移除一个触发器这个函数是所有触发器切换操作的中心。它被设计为一个多功能的函数: 当传入一个有效的trig指针时, 它会激活该触发器; 当传入NULL时, 它会移除当前活动的触发器。
原理 - 停用(Teardown)阶段 : 如果当前已有触发器(led_cdev->trigger不为NULL), 此函数会执行一系列严格的清理步骤:
从列表中移除 : 使用RCU(Read-Copy-Update)安全地将LED设备从旧触发器的控制列表中移除。synchronize_rcu()确保了在继续之前, 所有可能正在读取该列表的代码都已经完成。
取消挂起工作 : cancel_work_sync()确保任何由用户空间写入brightness而排队的异步工作被取消, 防止旧的状态改变干扰新触发器。
停止软件闪烁 : 如果LED正由定时器控制闪烁, 停止它。
移除sysfs属性 : 如果旧触发器添加了自己特有的sysfs文件(例如, “timer”触发器的delay_on/delay_off), 则将它们移除。
调用deactivate回调 : 调用旧触发器自身的deactivate()函数, 让触发器有机会释放它所占用的资源。
状态复位 : 将LED的状态完全重置: trigger指针设为NULL, 关闭LED。
原理 - 激活(Setup)阶段 : 如果传入的新trig不为NULL, 则执行激活步骤:
添加到列表 : 使用RCU将LED设备添加到新触发器的控制列表中。
调用activate回调 : 调用新触发器的activate()函数, 这是将LED控制权正式移交的关键一步。触发器从此开始根据其内部逻辑控制LED。
添加sysfs属性 : 如果新触发器有特有的sysfs文件, 将它们添加到此LED的sysfs目录中。
健壮的错误处理 : 如果激活或添加sysfs属性的任何一步失败, 函数会执行一个完整的”回滚”操作, 即再次执行停用流程, 确保LED处于一个干净、无触发器的状态。
原理 - 通知用户空间 : 无论操作是设置还是移除, 最后都会调用kobject_uevent_env发送一个uevent事件。这会通知用户空间的守护进程(如udev), trigger文件的内容已经发生了变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 int led_trigger_set (struct led_classdev *led_cdev, struct led_trigger *trig) { char *event = NULL ; char *envp[2 ]; const char *name; int ret; if (!led_cdev->trigger && !trig) return 0 ; name = trig ? trig->name : "none" ; event = kasprintf(GFP_KERNEL, "TRIGGER=%s" , name); if (led_cdev->trigger) { spin_lock(&led_cdev->trigger->leddev_list_lock); list_del_rcu(&led_cdev->trig_list); spin_unlock(&led_cdev->trigger->leddev_list_lock); synchronize_rcu(); cancel_work_sync(&led_cdev->set_brightness_work); led_stop_software_blink(led_cdev); device_remove_groups(led_cdev->dev, led_cdev->trigger->groups); if (led_cdev->trigger->deactivate) led_cdev->trigger->deactivate(led_cdev); led_cdev->trigger = NULL ; led_cdev->trigger = NULL ; led_cdev->trigger_data = NULL ; led_cdev->activated = false ; led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; led_set_brightness(led_cdev, LED_OFF); } if (trig) { spin_lock(&trig->leddev_list_lock); list_add_tail_rcu(&led_cdev->trig_list, &trig->led_cdevs); spin_unlock(&trig->leddev_list_lock); led_cdev->trigger = trig; synchronize_rcu(); flush_work(&led_cdev->set_brightness_work); if (trig->activate) ret = trig->activate(led_cdev); ret = 0 ; if (trig->activate) ret = trig->activate(led_cdev); else led_set_brightness(led_cdev, trig->brightness); if (ret) goto err_activate; ret = device_add_groups(led_cdev->dev, trig->groups); if (ret) { dev_err(led_cdev->dev, "Failed to add trigger attributes\n" ); goto err_add_groups; } } if (event) { envp[0 ] = event; envp[1 ] = NULL ; if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp)) dev_err(led_cdev->dev, "%s: Error sending uevent\n" , __func__); kfree(event); } return 0 ; err_add_groups: if (trig->deactivate) trig->deactivate(led_cdev); err_activate: return ret; } EXPORT_SYMBOL_GPL(led_trigger_set);
led_trigger_remove 和 led_trigger_set_default: 便捷的API封装这两个函数是提供给其他内核代码使用的上层API, 它们通过加锁并调用led_trigger_set来简化操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 void led_trigger_remove (struct led_classdev *led_cdev) { down_write(&led_cdev->trigger_lock); led_trigger_set(led_cdev, NULL ); up_write(&led_cdev->trigger_lock); } EXPORT_SYMBOL_GPL(led_trigger_remove); static bool led_match_default_trigger (struct led_classdev *led_cdev, struct led_trigger *trig) { if (!strcmp (led_cdev->default_trigger, trig->name) && trigger_relevant(led_cdev, trig)) { led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; led_trigger_set(led_cdev, trig); return true ; } return false ; } void led_trigger_set_default (struct led_classdev *led_cdev) { struct led_trigger *trig ; bool found = false ; if (!led_cdev->default_trigger) return ; if (!strcmp (led_cdev->default_trigger, "none" )) { led_trigger_remove(led_cdev); return ; } down_read(&triggers_list_lock); down_write(&led_cdev->trigger_lock); list_for_each_entry(trig, &trigger_list, next_trig) { found = led_match_default_trigger(led_cdev, trig); if (found) break ; } up_write(&led_cdev->trigger_lock); up_read(&triggers_list_lock); if (!found) request_module_nowait("ledtrig:%s" , led_cdev->default_trigger); } EXPORT_SYMBOL_GPL(led_trigger_set_default);
LED子系统核心接口:亮度、闪烁与触发器事件处理 本代码片段摘自Linux内核的LED Class子系统,提供了控制LED设备行为的核心API。其主要功能包括:设置LED的亮度、控制LED的闪烁(包括异步处理和软件模拟闪烁),以及处理来自LED触发器(Trigger)的事件。这段代码是连接上层应用(通过sysfs)或内核其他子系统(通过API调用)与底层具体LED硬件驱动之间的关键桥梁。
实现原理分析 该代码的实现体现了Linux设备驱动模型中的分层和抽象思想,并巧妙地利用内核机制来处理并发和休眠上下文问题。
异步操作与工作队列 (Work Queue) : led_blink_set_nosleep 和 led_set_brightness 函数都可能需要执行可能休眠的操作(例如,调用一个底层驱动提供的brightness_set_blocking函数,或同步删除一个定时器timer_delete_sync)。为了让这些API可以安全地从中断等原子上下文中调用,它们采用了一种延迟执行 的策略。它们并不直接执行操作,而是设置一个标志位(work_flags),然后将一个预先准备好的工作项(set_brightness_work)添加到工作队列中。内核的工作队列线程稍后会在一个可以安全休眠的进程上下文中执行实际的工作。
软件闪烁 (Software Blink) : 对于没有硬件闪烁功能的LED,内核通过一个高精度定时器(blink_timer)来模拟闪烁。led_stop_software_blink 函数负责停止这种模拟。led_set_brightness 在软件闪烁激活时,不会立即改变亮度,而是更新一个目标亮度值,并设置标志位,由定时器的下一次回调函数来应用新的亮度,从而避免与定时器的闪烁逻辑产生冲突。
触发器 (Triggers) : led_trigger_event 函数实现了LED触发器机制。触发器是内核中定义的事件源(如磁盘活动、CPU负载、网络流量等)。一个或多个LED设备可以注册到同一个触发器上。当led_trigger_event被调用时,它会遍历所有注册到该触发器的LED设备,并统一设置它们的亮度。
RCU (Read-Copy-Update) : led_trigger_event 在遍历与触发器关联的LED设备列表时,使用了RCU机制(rcu_read_lock/unlock 和 list_for_each_entry_rcu)。RCU是一种高效的并发控制机制,它允许在没有任何锁的情况下安全地读取一个可能被其他CPU修改的链表。这对于性能敏感的事件(如网络包到达)触发LED闪烁的场景至关重要。
代码分析 LED闪烁与亮度设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 void led_blink_set_nosleep (struct led_classdev *led_cdev, unsigned long delay_on, unsigned long delay_off) { if (led_cdev->blink_set && led_cdev->brightness_set_blocking) { led_cdev->delayed_delay_on = delay_on; led_cdev->delayed_delay_off = delay_off; set_bit(LED_SET_BLINK, &led_cdev->work_flags); queue_work(led_cdev->wq, &led_cdev->set_brightness_work); return ; } led_blink_set(led_cdev, &delay_on, &delay_off); } EXPORT_SYMBOL_GPL(led_blink_set_nosleep); void led_stop_software_blink (struct led_classdev *led_cdev) { timer_delete_sync(&led_cdev->blink_timer); led_cdev->blink_delay_on = 0 ; led_cdev->blink_delay_off = 0 ; clear_bit(LED_BLINK_SW, &led_cdev->work_flags); } EXPORT_SYMBOL_GPL(led_stop_software_blink); void led_set_brightness (struct led_classdev *led_cdev, unsigned int brightness) { if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) { if (!brightness) { set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); queue_work(led_cdev->wq, &led_cdev->set_brightness_work); } else { set_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags); led_cdev->new_blink_brightness = brightness; } return ; } led_set_brightness_nosleep(led_cdev, brightness); } EXPORT_SYMBOL_GPL(led_set_brightness);
LED触发器事件处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void led_trigger_event (struct led_trigger *trig, enum led_brightness brightness) { struct led_classdev *led_cdev ; if (!trig) return ; trig->brightness = brightness; rcu_read_lock(); list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) led_set_brightness(led_cdev, brightness); rcu_read_unlock(); } EXPORT_SYMBOL_GPL(led_trigger_event);
drivers/leds/led-class.c LED sysfs 属性接口 此代码片段定义了所有注册到LED子系统的设备在sysfs中暴露给用户空间的文件接口。其核心原理是将用户对sysfs文件的读写操作, 通过一系列的回调函数, 转换成对具体LED设备(led_classdev)数据结构的操作, 并最终触发硬件动作 。这是Linux内核驱动模型中一个典型的、将内核功能暴露给用户空间的实现范例。
brightness 属性 (读/写)这是控制LED亮度最核心的接口, 对应sysfs中的brightness文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 static ssize_t brightness_show (struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); unsigned int brightness; mutex_lock(&led_cdev->led_access); led_update_brightness(led_cdev); brightness = led_cdev->brightness; mutex_unlock(&led_cdev->led_access); return sprintf (buf, "%u\n" , brightness); } static ssize_t brightness_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); unsigned long state; ssize_t ret; mutex_lock(&led_cdev->led_access); if (led_sysfs_is_disabled(led_cdev)) { ret = -EBUSY; goto unlock; } ret = kstrtoul(buf, 10 , &state); if (ret) goto unlock; if (state == LED_OFF) led_trigger_remove(led_cdev); led_set_brightness(led_cdev, state); ret = size; unlock: mutex_unlock(&led_cdev->led_access); return ret; } static DEVICE_ATTR_RW (brightness) ;
max_brightness 属性 (只读)这个接口用于查询LED硬件支持的最大亮度值, 对应sysfs中的max_brightness文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static ssize_t max_brightness_show (struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); unsigned int max_brightness; mutex_lock(&led_cdev->led_access); max_brightness = led_cdev->max_brightness; mutex_unlock(&led_cdev->led_access); return sprintf (buf, "%u\n" , max_brightness); } static DEVICE_ATTR_RO (max_brightness) ;
trigger 属性与属性组装这部分代码负责组装上述定义的所有属性, 并有条件地包含”触发器”(trigger)功能, 最后将它们打包成内核设备模型可以理解的格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #ifdef CONFIG_LEDS_TRIGGERS static const BIN_ATTR (trigger, 0644 , led_trigger_read, led_trigger_write, 0 ) ;static const struct bin_attribute *const led_trigger_bin_attrs [] = { &bin_attr_trigger, NULL , }; static const struct attribute_group led_trigger_group = { .bin_attrs = led_trigger_bin_attrs, }; #endif static struct attribute *led_class_attrs [] = { &dev_attr_brightness.attr, &dev_attr_max_brightness.attr, NULL , }; static const struct attribute_group led_group = { .attrs = led_class_attrs, }; static const struct attribute_group *led_groups [] = { &led_group, #ifdef CONFIG_LEDS_TRIGGERS &led_trigger_group, #endif NULL , };
LED类定义及模块安全交互 此代码片段定义了Linux内核中”leds”设备类的核心结构, 并提供了一个关键的辅助函数, 用于在与具体LED设备交互时, 安全地管理其父驱动模块的生命周期。
leds_class : 这是LED子系统的核心。它是一个struct class实例, 负责在sysfs中创建/sys/class/leds目录。它将所有LED设备驱动注册的设备聚合在一起, 并通过.dev_groups为每个设备提供一套标准的sysfs属性(如brightness, trigger等)。它还通过.pm指针关联了电源管理操作, 使得内核的电源管理核心可以统一地对所有LED设备进行挂起(suspend)和恢复(resume)操作。
SIMPLE_DEV_PM_OPS : 这是一个辅助宏, 用于快速定义一个简单的dev_pm_ops结构体。在这里, 它创建了leds_class_dev_pm_ops实例, 并将其中的.suspend和.resume回调分别指向了led_suspend和led_resume函数。这是一种简化驱动代码的常用技巧。
led_module_get : 这是一个至关重要的辅助函数, 其核心原理是防止因内核模块卸载而引发的”use-after-free”内核恐慌 。当用户通过sysfs与一个LED设备交互时, LED子系统需要调用其物理父设备(例如, 一个I2C IO扩展器)的驱动程序所提供的函数。如果在此时, 该父驱动模块正在被卸载(rmmod), 其代码和数据结构可能已经被释放。led_module_get通过调用try_module_get()来尝试增加父驱动模块的引用计数。如果成功, 它就获得了一个”锁”, 保证了父模块在此次操作完成前不会被卸载; 如果失败, 则说明父模块正在被卸载, 函数会安全地返回错误, 从而避免了对无效内存的访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 static SIMPLE_DEV_PM_OPS (leds_class_dev_pm_ops, led_suspend, led_resume) ;static struct led_classdev *led_module_get (struct device *led_dev) { struct led_classdev *led_cdev ; if (!led_dev) return ERR_PTR(-EPROBE_DEFER); led_cdev = dev_get_drvdata(led_dev); if (!try_module_get(led_cdev->dev->parent->driver->owner)) { put_device(led_cdev->dev); return ERR_PTR(-ENODEV); } return led_cdev; } static const struct class leds_class = { .name = "leds" , .dev_groups = led_groups, .pm = &leds_class_dev_pm_ops, };
LED子系统初始化与资源管理 此代码片段展示了Linux内核中LED类(Class)子系统的核心初始化、退出以及设备资源管理(devm)的相关实现。其核心原理是建立一个标准化的框架, 包括一个专用的工作队列和一个设备类, 使得所有不同类型的LED设备驱动都能以统一的方式注册到内核, 并向用户空间提供一致的控制接口 。
leds_init 和 leds_exit: 子系统的生命周期管理这两个函数负责在内核启动时建立LED子系统的基础架构, 并在内核关闭或模块卸载时安全地拆除它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 static int __init leds_init (void ) { leds_wq = alloc_ordered_workqueue("leds" , 0 ); if (!leds_wq) { pr_err("Failed to create LEDs ordered workqueue\n" ); return -ENOMEM; } return class_register(&leds_class); } static void __exit leds_exit (void ) { class_unregister(&leds_class); destroy_workqueue(leds_wq); } subsys_initcall(leds_init); module_exit(leds_exit);
devm_led_classdev_unregister 和 devm_led_classdev_match: 资源管理这两个函数是”设备资源管理”(devm)框架的一部分, 旨在简化驱动程序的资源清理工作。devm的核心思想是将资源的生命周期与设备的生命周期绑定 , 当设备被移除或驱动卸载时, 由devm框架自动释放所有已注册的资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static int devm_led_classdev_match (struct device *dev, void *res, void *data) { struct led_classdev **p = res; if (WARN_ON(!p || !*p)) return 0 ; return *p == data; } void devm_led_classdev_unregister (struct device *dev, struct led_classdev *led_cdev) { WARN_ON(devres_release(dev, devm_led_classdev_release, devm_led_classdev_match, led_cdev)); } EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
模块元数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 MODULE_AUTHOR("John Lenz, Richard Purdie" ); MODULE_LICENSE("GPL" ); MODULE_DESCRIPTION("LED Class Interface" );
GPIO LED 驱动:Probe Logic, Backward Compatibility, and Shutdown 本代码片段是 leds-gpio 驱动的核心部分,展示了其探测(probe)逻辑、强大的向后兼容性设计以及关机(shutdown)处理 。gpio_led_probe 函数作为驱动的入口点,智能地判断配置来源(现代的设备树 vs. 老旧的平台数据),并调用相应的创建流程。gpio_led_get_gpiod 是一个关键的辅助函数,它封装了从多种遗留(legacy)和现代方法中获取 GPIO 资源的复杂逻辑,是该驱动向后兼容性的基石。
实现原理分析 此代码是 Linux 驱动开发中处理不同硬件抽象层(板级文件 vs. 设备树)和 API 演进(整数 GPIO vs. gpiod)的优秀范例。
双重配置来源 (gpio_led_probe) :
probe 函数的核心是一个 if-else 结构,它实现了对两种不同配置风格的“分叉”处理: a. 平台数据路径 : if (pdata && pdata->num_leds) 检查是否存在平台数据。如果存在,驱动就进入“传统模式”,遍历 pdata->leds C 语言数组来获取每个 LED 的配置。这种方式常见于没有使用设备树的旧内核或架构。 b. 设备树路径 : else { priv = gpio_leds_create(dev); } 如果不存在平台数据,驱动就进入“现代模式”,调用 gpio_leds_create 函数(在上一段代码中分析过),该函数会去解析 probe 函数所绑定设备的设备树子节点。
优先级 : 这种设计明确了平台数据优先于 设备树。如果一个设备同时拥有 pdata 和设备树节点,只有 pdata 会被使用。
向后兼容的 GPIO 获取 (gpio_led_get_gpiod) :
这个函数是驱动兼容性的核心。它按照从新到旧的顺序,尝试用三种不同的方法来获取一个 GPIO: a. devm_gpiod_get_index_optional : 这是最现代的方法。它尝试根据索引 从设备获取一个预先声明的 GPIO 描述符 (gpiod)。这种方式通常与设备树或 ACPI 中的 GPIO 映射配合使用,允许板级配置文件通过描述符而不是全局 GPIO 编号来定义连接。 b. devm_gpio_request_one : 这是“遗留代码路径”的第一步。它使用 template->gpio 中提供的全局 GPIO 整数编号 。gpio_is_valid 首先检查这个编号是否合法。然后,devm_gpio_request_one 会请求并配置这个 GPIO。 c. gpio_to_desc : 在通过整数编号成功请求 GPIO 后,此函数将其转换为现代的 gpio_desc 描述符,从而统一了后续代码的处理方式。
极性处理 : if (template->active_low ^ gpiod_is_active_low(gpiod)) 这一行巧妙地处理了 GPIO 极性。它使用异或(XOR)操作来判断 pdata 中期望的极性(template->active_low)与 GPIO 控制器本身(或设备树中)定义的极性(gpiod_is_active_low)是否不一致。如果不一致,它会调用 gpiod_toggle_active_low 来“翻转”驱动对该 GPIO 的逻辑视图,从而确保无论底层物理极性如何,驱动写入 1 总是代表“亮”。
关机处理 (gpio_led_shutdown) :
这个回调函数在系统关机或重启 的过程中被调用。
它遍历该驱动管理的所有 LED。
if (!(led->cdev.flags & LED_RETAIN_AT_SHUTDOWN)): 它检查每个 LED 是否设置了“关机时保持状态”的标志。这个标志可以通过设备树或平台数据来配置。
如果没有 设置该标志,它就调用 gpio_led_set(&led->cdev, LED_OFF) 来明确地将 LED 关闭。
目的 : 这确保了在系统断电前,大部分非关键的指示灯会被关闭,这是一种良好的电源管理实践,也可以避免在重启序列中出现状态不明确的指示灯。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 static const struct of_device_id of_gpio_leds_match [] = { { .compatible = "gpio-leds" , }, {}, }; MODULE_DEVICE_TABLE(of, of_gpio_leds_match); static struct gpio_desc *gpio_led_get_gpiod (struct device *dev, int idx, const struct gpio_led *template) { struct gpio_desc *gpiod ; int ret; gpiod = devm_gpiod_get_index_optional(dev, NULL , idx, GPIOD_OUT_LOW); if (IS_ERR(gpiod)) return gpiod; if (gpiod) { gpiod_set_consumer_name(gpiod, template->name); return gpiod; } if (!gpio_is_valid(template->gpio)) return ERR_PTR(-ENOENT); ret = devm_gpio_request_one(dev, template->gpio, GPIOF_OUT_INIT_LOW, template->name); if (ret < 0 ) return ERR_PTR(ret); gpiod = gpio_to_desc(template->gpio); if (!gpiod) return ERR_PTR(-EINVAL); if (template->active_low ^ gpiod_is_active_low(gpiod)) gpiod_toggle_active_low(gpiod); return gpiod; } static int gpio_led_probe (struct platform_device *pdev) { struct device *dev = &pdev->dev; struct gpio_led_platform_data *pdata = dev_get_platdata(dev); struct gpio_leds_priv *priv ; int i, ret; if (pdata && pdata->num_leds) { priv = devm_kzalloc(dev, struct_size(priv, leds, pdata->num_leds), GFP_KERNEL); if (!priv) return -ENOMEM; priv->num_leds = pdata->num_leds; for (i = 0 ; i < priv->num_leds; i++) { const struct gpio_led *template = &pdata->leds[i]; struct gpio_led_data *led_dat = &priv->leds[i]; if (template->gpiod) led_dat->gpiod = template->gpiod; else led_dat->gpiod = gpio_led_get_gpiod(dev, i, template); if (IS_ERR(led_dat->gpiod)) { dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n" , template->gpio, template->name); continue ; } ret = create_gpio_led(template, led_dat, dev, NULL , pdata->gpio_blink_set); if (ret < 0 ) return ret; } } else { priv = gpio_leds_create(dev); if (IS_ERR(priv)) return PTR_ERR(priv); } platform_set_drvdata(pdev, priv); return 0 ; } static void gpio_led_shutdown (struct platform_device *pdev) { struct gpio_leds_priv *priv = platform_get_drvdata(pdev); int i; for (i = 0 ; i < priv->num_leds; i++) { struct gpio_led_data *led = &priv->leds[i]; if (!(led->cdev.flags & LED_RETAIN_AT_SHUTDOWN)) gpio_led_set(&led->cdev, LED_OFF); } } static struct platform_driver gpio_led_driver = { .probe = gpio_led_probe, .shutdown = gpio_led_shutdown, .driver = { .name = "leds-gpio" , .of_match_table = of_gpio_leds_match, }, }; module_platform_driver(gpio_led_driver); MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>" ); MODULE_DESCRIPTION("GPIO LED driver" ); MODULE_LICENSE("GPL" ); MODULE_ALIAS("platform:leds-gpio" );
GPIO LED 驱动:核心创建与回调逻辑 本代码片段展示了 leds-gpio 驱动的核心实现 ,包括单个 LED 设备的创建逻辑 (create_gpio_led)、设备树解析 (gpio_leds_create) 以及响应上层 LED 子系统请求的回调函数 (gpio_led_set, gpio_blink_set)。其主要功能是将从硬件抽象层(设备树、GPIO 子系统)获取的信息,精确地翻译和绑定到一个标准的 led_classdev 对象上,并提供实现该对象行为的具体方法 。
实现原理分析 此代码是 Linux 驱动模型中“翻译”和“实现”的典范。它将声明式的硬件描述(设备树)转化为一个功能性的内核对象。
设备树驱动的创建逻辑 (gpio_leds_create) :
此函数是驱动在“现代”(设备树)模式下的入口。
动态分配 : 它首先通过 device_get_child_node_count 计算出设备树父节点下有多少个子节点(即多少个 LED),然后使用 devm_kzalloc 和 struct_size 宏来动态地分配一个足够大的 gpio_leds_priv 结构,该结构包含一个灵活数组成员 leds。
子节点遍历 : device_for_each_child_node_scoped 是一个安全的宏,用于遍历所有子节点。对于每一个子节点 child: a. devm_fwnode_gpiod_get: 调用此函数从子节点的 gpios 属性中解析出 GPIO 描述符 (gpiod)。这是最关键的硬件资源获取步骤。 b. 属性解析 : 它读取子节点中的其他标准属性,如 retain-state-suspended、panic-indicator 等,并将它们填充到一个临时的 gpio_led 模板结构中。 c. create_gpio_led: 调用核心创建函数来完成该 LED 的实例化和注册。 d. gpiod_set_consumer_name: 这是一个很好的实践。在 LED 设备被成功注册并获得一个最终的名称(如 “user_led”)后,它将这个名称设置回 gpiod 的 consumer 字段。这在调试时(例如,在 /sys/kernel/debug/gpio 中)非常有帮助,可以清晰地看到哪个 GPIO 被哪个 LED 所使用。
核心创建与绑定 (create_gpio_led) :
此函数是连接 GPIO 子系统和 LED 子系统的核心桥梁 。
回调函数绑定 :
led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);: 这是一个关键的性能和正确性优化。它预先查询 GPIO 控制器是否需要睡眠(例如,通过 I2C/SPI 总线访问)。
根据查询结果,它将 led_dat->cdev.brightness_set(非阻塞)或 led_dat->cdev.brightness_set_blocking(阻塞)指向驱动内部的 gpio_led_set 函数。这确保了 LED 子系统在调用亮度设置接口时,会使用正确的、不会导致死锁的 GPIO API。
初始状态处理 : template->default_state == LEDS_GPIO_DEFSTATE_KEEP 这个逻辑允许设备树指定 LED 的初始状态应保持 GPIO 引脚上电时的状态,而不是强制设为开或关。
注册 : devm_led_classdev_register_ext 是最终的注册步骤,它将 led_classdev 对象“发布”到系统中,使其在 sysfs 中可见。
LED 操作实现 (gpio_led_set) :
这是用户通过 sysfs 写入 brightness 文件时最终被调用的函数。
翻译 : 它将 enum led_brightness(对于 GPIO LED,最大亮度为1)简单地翻译成 0(灭)或 1(亮)的 GPIO 电平。
状态处理 : 它包含一个 if (led_dat->blinking) 的检查。如果 LED 之前正处于硬件闪烁状态,再次设置亮度会隐式地停止闪烁。
_cansleep 调用 : 它根据 create_gpio_led 中预先确定的 can_sleep 标志,选择调用 gpiod_set_value_cansleep 或 gpiod_set_value,从而正确地处理可能需要睡眠的 GPIO 操作。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 struct gpio_led_data { struct led_classdev cdev ; struct gpio_desc *gpiod ; u8 can_sleep; u8 blinking; gpio_blink_set_t platform_gpio_blink_set; }; static void gpio_led_set (struct led_classdev *led_cdev, enum led_brightness value) { struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev); int level; if (value == LED_OFF) level = 0 ; else level = 1 ; if (led_dat->blinking) { led_dat->platform_gpio_blink_set(led_dat->gpiod, level, NULL , NULL ); led_dat->blinking = 0 ; } else { if (led_dat->can_sleep) gpiod_set_value_cansleep(led_dat->gpiod, level); else gpiod_set_value(led_dat->gpiod, level); } } static int create_gpio_led (const struct gpio_led *template, struct gpio_led_data *led_dat, struct device *parent, struct fwnode_handle *fwnode, gpio_blink_set_t blink_set) { led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod); if (!led_dat->can_sleep) led_dat->cdev.brightness_set = gpio_led_set; else led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking; ret = gpiod_direction_output(led_dat->gpiod, state); if (ret < 0 ) return ret; if (template->name) { led_dat->cdev.name = template->name; ret = devm_led_classdev_register(parent, &led_dat->cdev); } else { init_data.fwnode = fwnode; ret = devm_led_classdev_register_ext(parent, &led_dat->cdev, &init_data); } return ret; } static struct gpio_leds_priv *gpio_leds_create (struct device *dev) { device_for_each_child_node_scoped(dev, child) { struct gpio_led_data *led_dat = &priv->leds[used]; struct gpio_led led = {}; led.gpiod = devm_fwnode_gpiod_get(dev, child, NULL , GPIOD_ASIS, NULL ); led_dat->gpiod = led.gpiod; led.default_state = led_init_default_state_get(child); if (fwnode_property_present(child, "retain-state-suspended" )) led.retain_state_suspended = 1 ; ret = create_gpio_led(&led, led_dat, dev, child, NULL ); if (ret < 0 ) return ERR_PTR(ret); gpiod_set_consumer_name(led_dat->gpiod, led_dat->cdev.dev->kobj.name); used++; } }
led_classdev_register_ext / led_classdev_unregister / devm_led_classdev_register_ext / devm_led_classdev_release:LED classdev 的注册、注销与 devm 托管 led_classdev_register_ext:带初始化数据的 LED classdev 注册(命名合成、固件属性合并、设备创建、列表挂接与触发器初始化) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 int led_classdev_register_ext (struct device *parent, struct led_classdev *led_cdev, struct led_init_data *init_data) { char composed_name[LED_MAX_NAME_SIZE]; char final_name[LED_MAX_NAME_SIZE]; const char *proposed_name = composed_name; int ret; if (init_data) { if (init_data->devname_mandatory && !init_data->devicename) { dev_err(parent, "Mandatory device name is missing" ); return -EINVAL; } ret = led_compose_name(parent, init_data, composed_name); if (ret < 0 ) return ret; if (init_data->fwnode) { fwnode_property_read_string(init_data->fwnode, "linux,default-trigger" , &led_cdev->default_trigger); if (fwnode_property_present(init_data->fwnode, "retain-state-shutdown" )) led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; fwnode_property_read_u32(init_data->fwnode, "max-brightness" , &led_cdev->max_brightness); if (fwnode_property_present(init_data->fwnode, "color" )) fwnode_property_read_u32(init_data->fwnode, "color" , &led_cdev->color); } } else { proposed_name = led_cdev->name; } ret = led_classdev_next_name(proposed_name, final_name, sizeof (final_name)); if (ret < 0 ) return ret; else if (ret && led_cdev->flags & LED_REJECT_NAME_CONFLICT) return -EEXIST; else if (ret) dev_warn(parent, "Led %s renamed to %s due to name collision\n" , proposed_name, final_name); if (led_cdev->color >= LED_COLOR_ID_MAX) dev_warn(parent, "LED %s color identifier out of range\n" , final_name); mutex_init(&led_cdev->led_access); mutex_lock(&led_cdev->led_access); led_cdev->dev = device_create_with_groups(&leds_class, parent, 0 , led_cdev, led_cdev->groups, "%s" , final_name); if (IS_ERR(led_cdev->dev)) { mutex_unlock(&led_cdev->led_access); return PTR_ERR(led_cdev->dev); } if (init_data && init_data->fwnode) device_set_node(led_cdev->dev, init_data->fwnode); if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { ret = led_add_brightness_hw_changed(led_cdev); if (ret) { device_unregister(led_cdev->dev); led_cdev->dev = NULL ; mutex_unlock(&led_cdev->led_access); return ret; } } led_cdev->work_flags = 0 ; #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED led_cdev->brightness_hw_changed = -1 ; #endif down_write(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); up_write(&leds_list_lock); if (!led_cdev->max_brightness) led_cdev->max_brightness = LED_FULL; led_update_brightness(led_cdev); led_cdev->wq = leds_wq; led_init_core(led_cdev); #ifdef CONFIG_LEDS_TRIGGERS led_trigger_set_default(led_cdev); #endif mutex_unlock(&led_cdev->led_access); dev_dbg(parent, "Registered led device: %s\n" , led_cdev->name); return 0 ; } EXPORT_SYMBOL_GPL(led_classdev_register_ext);
led_classdev_unregister:LED classdev 注销(触发器解除、停止闪烁、按策略熄灭、回收设备节点与列表项) 作用与原理(关键点)
若启用触发器,需先在 trigger_lock 保护下解除触发器,避免触发器路径在注销后仍访问该 LED。
设置 LED_UNREGISTERING,并停止软件闪烁;若未设置 LED_RETAIN_AT_SHUTDOWN,则在注销时将 LED 置为灭。
flush_work(&set_brightness_work) 关键:保证异步亮度设置工作项不会在对象回收后继续运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void led_classdev_unregister (struct led_classdev *led_cdev) { if (IS_ERR_OR_NULL(led_cdev->dev)) return ; #ifdef CONFIG_LEDS_TRIGGERS down_write(&led_cdev->trigger_lock); if (led_cdev->trigger) led_trigger_set(led_cdev, NULL ); up_write(&led_cdev->trigger_lock); #endif led_cdev->flags |= LED_UNREGISTERING; led_stop_software_blink(led_cdev); if (!(led_cdev->flags & LED_RETAIN_AT_SHUTDOWN)) led_set_brightness(led_cdev, LED_OFF); flush_work(&led_cdev->set_brightness_work); if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) led_remove_brightness_hw_changed(led_cdev); device_unregister(led_cdev->dev); down_write(&leds_list_lock); list_del(&led_cdev->node); up_write(&leds_list_lock); mutex_destroy(&led_cdev->led_access); } EXPORT_SYMBOL_GPL(led_classdev_unregister);
devm_led_classdev_release:devres 释放回调(触发 LED 自动注销) 作用与原理(关键点)
这是 devres 资源项的释放函数:当 parent 设备解绑或资源回收时,自动调用 led_classdev_unregister(),从而把 “注册/注销” 与设备生命周期绑定。
1 2 3 4 static void devm_led_classdev_release (struct device *dev, void *res) { led_classdev_unregister(*(struct led_classdev **)res); }
devm_led_classdev_register_ext:对 led_classdev_register_ext 的 devm 封装(注册成功后把注销动作挂到 devres) 作用与原理(关键点)
devres_alloc() 分配一个资源项,资源项携带 devm_led_classdev_release() 作为析构回调。
若注册失败,释放资源项并返回;若成功,把 led_cdev 指针写入资源项并 devres_add() 到 parent,实现自动注销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int devm_led_classdev_register_ext (struct device *parent, struct led_classdev *led_cdev, struct led_init_data *init_data) { struct led_classdev **dr ; int rc; dr = devres_alloc(devm_led_classdev_release, sizeof (*dr), GFP_KERNEL); if (!dr) return -ENOMEM; rc = led_classdev_register_ext(parent, led_cdev, init_data); if (rc) { devres_free(dr); return rc; } *dr = led_cdev; devres_add(parent, dr); return 0 ; } EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
Linux 内核 GPIO LED 驱动全面解析(drivers/leds/leds-gpio.c) 介绍 leds-gpio 是一个通用驱动:把“由某个 GPIO 输出电平控制亮灭”的 LED 注册为 LED class 设备 。这样用户态可以通过 LED 子系统统一接口控制(brightness、trigger 等),而不需要直接操作 GPIO。
它的定位很清晰:开/关型指示灯 (通常 max_brightness = 1),用于状态指示、告警、活动灯等。
历史与背景 诞生要解决的问题
重要演进点(从当前常见主线实现的结构可观察)
社区活跃度与主流应用
属于主线通用驱动,广泛用于各类设备的状态灯/指示灯;
和 LED core、trigger、设备树绑定配合使用,是“标准做法”之一。
核心原理与设计 核心工作原理 可以按“probe → 创建每盏灯 → 回调控制”的链路理解:
probe:确定数据来源并枚举 LED
如果有 platform_data:按数组创建多个 LED;
否则:从固件节点(DT/ACPI)枚举子节点,每个子节点代表一盏灯。
单灯创建:获取 GPIO、解析属性、设置初始状态 典型会做这些事情:
获取 GPIO(优先走 gpiod/fwnode);
解析 default-state(on/off/keep):
keep:尽量保持当前硬件电平对应的状态;
on/off:按属性设置初始亮度;
配置 GPIO 为输出并输出初值(避免上电后 LED 状态不可控)。
注册 LED classdev
亮度设置路径
用户态写 brightness 或触发器驱动亮度变化时,最终调用驱动的 set 回调;
回调内部用 gpiod_set_value() 或 gpiod_set_value_cansleep() 输出 0/1。
关机/挂起语义(按绑定属性影响行为) 常见会支持类似策略(是否支持以你使用的内核版本为准):
retain-state-suspended:挂起/恢复时尽量不改变 LED 状态;
retain-state-shutdown:关机路径不强制关灯;
panic-indicator:允许在 panic 场景作为指示灯使用。
主要优势
统一接口 :天然获得 LED 子系统的 brightness/trigger 生态;
硬件描述解耦 :通过 DT/ACPI 描述多盏灯,驱动本身通用;
正确处理“GPIO 可能睡眠” :通过 blocking 回调避免在不允许睡眠的上下文误操作;
易维护 :通常大量使用 devm 资源管理,probe 失败/卸载路径更干净。
劣势、局限与不适用点
只能开/关 :不适合需要多级亮度或呼吸灯曲线的场景;
高精度/高频闪烁不合适 :GPIO 翻转受调度与总线影响,严格时序应交给 PWM/专用控制器;
硬件闪烁依赖平台能力 :没有硬件 blink 支持时,闪烁主要靠 trigger(软件/通用机制),实时性与功耗表现取决于系统。
使用场景 首选场景(举例)
设备状态灯:电源、运行、告警、网络/存储活动指示;
需要 trigger:如心跳、定时闪烁、磁盘活动等;
多灯板卡:用 DT/ACPI 一次性描述多个 LED。
不推荐场景(原因)
需要平滑调光/多级亮度:更适合 leds-pwm 或 I2C/SPI LED 控制器驱动;
需要严格波形输出:GPIO LED 驱动定位不是做波形发生器。
对比分析 下面按你关心的维度对比:leds-gpio vs leds-pwm vs 用户态直控 GPIO。
实现方式
leds-gpio:LED classdev → GPIO 输出 0/1(开关)。
leds-pwm:LED classdev → PWM 占空比(多级亮度)。
用户态直控 GPIO:应用自己申请 GPIO、自己实现策略/闪烁。
性能开销
leds-gpio:单次开关非常轻;触发器闪烁会引入定时/回调调度开销(通常可接受但不适合高频)。
leds-pwm:设置 PWM 状态相对更重,但亮度控制能力强,硬件侧更稳定。
用户态直控:频繁控制会有系统调用与上下文切换开销,且策略需要进程常驻。
资源占用
leds-gpio:每盏灯少量内核对象;通常不需要常驻用户进程。
leds-pwm:需要 PWM 控制器资源与状态对象。
用户态直控:需要用户进程与其定时器/事件循环资源。
隔离级别
leds-gpio / leds-pwm:由内核 LED 子系统统一对外导出,减少多进程争用与权限扩散。
用户态直控:GPIO 权限与资源仲裁更难,容易出现多个组件同时抢占。
启动速度
leds-gpio:GPIO 就绪即可点灯,通常很早能工作。
leds-pwm:取决于 PWM 控制器初始化与时钟资源。
用户态直控:取决于用户进程启动时机,早期阶段不稳定。
总结 关键特性
通用 GPIO 指示灯驱动:把 GPIO LED 纳入 LED 子系统;
典型为开关型(max_brightness=1),通过 gpiod_cansleep 区分阻塞/非阻塞设置路径;
支持 DT/ACPI 描述多灯,并可结合 trigger 做统一的状态指示策略。
gpio_led_probe / gpio_led_shutdown / gpio_led_driver: GPIO LED 平台驱动的探测初始化与关机收尾这个函数是 GPIO LED 平台驱动的设备级初始化入口 。它根据是否存在 platform_data(传统板级静态配置)走两条路径:
platform_data 路径 :按 pdata->num_leds 遍历模板数组,为每个 LED 获取 gpiod(优先复用模板里已给的 gpiod,否则调用 gpio_led_get_gpiod 动态获取),再调用 create_gpio_led 完成 LED classdev 注册与初始状态配置。
无 platform_data 路径 :调用 gpio_leds_create(通常对应设备树/固件描述)创建并初始化 priv 与 leds[]。
该函数体现的关键技巧是:
devm 管理内存 :devm_kzalloc 让 priv 生命周期绑定到 dev,probe 失败或设备卸载时自动释放,减少错误路径清理复杂度。
可变长度结构体分配 :struct_size(priv, leds, n) 计算 struct gpio_leds_priv + n 个 leds[] 的总大小,避免手工溢出。
弱失败策略 :GPIO 不可用时并不立即失败,而是 continue 跳过该 LED(仅在拿到 GPIO 后创建 LED 失败才返回错误)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 static int gpio_led_probe (struct platform_device *pdev) { struct device *dev = &pdev->dev; struct gpio_led_platform_data *pdata = dev_get_platdata(dev); struct gpio_leds_priv *priv ; int i, ret; if (pdata && pdata->num_leds) { priv = devm_kzalloc(dev, struct_size(priv, leds, pdata->num_leds), GFP_KERNEL); if (!priv) return -ENOMEM; priv->num_leds = pdata->num_leds; for (i = 0 ; i < priv->num_leds; i++) { const struct gpio_led *template = &pdata->leds[i]; struct gpio_led_data *led_dat = &priv->leds[i]; if (template->gpiod) led_dat->gpiod = template->gpiod; else led_dat->gpiod = gpio_led_get_gpiod(dev, i, template); if (IS_ERR(led_dat->gpiod)) { dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n" , template->gpio, template->name); continue ; } ret = create_gpio_led(template, led_dat, dev, NULL , pdata->gpio_blink_set); if (ret < 0 ) return ret; } } else { priv = gpio_leds_create(dev); if (IS_ERR(priv)) return PTR_ERR(priv); } platform_set_drvdata(pdev, priv); return 0 ; } static void gpio_led_shutdown (struct platform_device *pdev) { struct gpio_leds_priv *priv = platform_get_drvdata(pdev); int i; for (i = 0 ; i < priv->num_leds; i++) { struct gpio_led_data *led = &priv->leds[i]; if (!(led->cdev.flags & LED_RETAIN_AT_SHUTDOWN)) gpio_led_set(&led->cdev, LED_OFF); } } static const struct of_device_id of_gpio_leds_match [] = { { .compatible = "gpio-leds" , }, {}, }; MODULE_DEVICE_TABLE(of, of_gpio_leds_match); static struct platform_driver gpio_led_driver = { .probe = gpio_led_probe, .shutdown = gpio_led_shutdown, .driver = { .name = "leds-gpio" , .of_match_table = of_gpio_leds_match, }, }; module_platform_driver(gpio_led_driver);
gpio_leds_create / struct gpio_leds_priv: 从固件子节点批量创建 GPIO LED,并延迟补全 GPIO label这个函数是无 platform_data 分支 下的创建路径(通常对应设备树/ACPI 的固件描述)。它做的事情可以概括为:
用子节点数量决定最大分配规模 :先统计 device_get_child_node_count(dev),按“最多 count 个 LED”一次性分配 gpio_leds_priv + leds[count]。
逐子节点解析并创建 LED :每个子节点对应一个 LED;为它获取 GPIO 描述符、解析默认状态与若干布尔属性,然后调用 create_gpio_led 把 LED 注册进 LED 子系统。
注册后再设置 GPIO consumer name :因为在获取 gpiod 时 LED 的最终名字还没确定(注册 LED classdev 后才有 kobj.name),所以先以 NULL label 获取 gpiod,等注册完成后用 gpiod_set_consumer_name 写入最终名字。
关键技巧是第 3 点:先获取 GPIO(label 未定),后注册 LED(名字确定),再回填 GPIO label 。这能让调试与资源追踪(GPIO consumer 名称)与最终 LED 对象一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 struct gpio_leds_priv { int num_leds; struct gpio_led_data leds [] __counted_by (num_leds ); }; static struct gpio_leds_priv *gpio_leds_create (struct device *dev) { struct gpio_leds_priv *priv ; int count; int used; int ret; count = device_get_child_node_count(dev); if (!count) return ERR_PTR(-ENODEV); priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL); if (!priv) return ERR_PTR(-ENOMEM); priv->num_leds = count; used = 0 ; device_for_each_child_node_scoped(dev, child) { struct gpio_led_data *led_dat = &priv->leds[used]; struct gpio_led led = {}; led.gpiod = devm_fwnode_gpiod_get(dev, child, NULL , GPIOD_ASIS, NULL ); if (IS_ERR(led.gpiod)) { dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n" , child); return ERR_CAST(led.gpiod); } led_dat->gpiod = led.gpiod; led.default_state = led_init_default_state_get(child); if (fwnode_property_present(child, "retain-state-suspended" )) led.retain_state_suspended = 1 ; if (fwnode_property_present(child, "retain-state-shutdown" )) led.retain_state_shutdown = 1 ; if (fwnode_property_present(child, "panic-indicator" )) led.panic_indicator = 1 ; ret = create_gpio_led(&led, led_dat, dev, child, NULL ); if (ret < 0 ) return ERR_PTR(ret); gpiod_set_consumer_name(led_dat->gpiod, led_dat->cdev.dev->kobj.name); used++; } priv->num_leds = used; return priv; }
create_gpio_led:实例化并注册一个 GPIO LED(回调选择/默认状态/策略标志/pinctrl 默认态) 作用与实现原理(仅保留关键点) 函数把 template 的配置写入 led_dat->cdev,并根据 GPIO 访问特性选择合适的 LED 回调;随后确定初始电平、设置 LED core 行为标志,配置 GPIO 输出并注册到 LED 子系统;最后在 LED 设备注册完成后选择 pinctrl 默认态(可选)。
create_gpio_led 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 static int create_gpio_led (const struct gpio_led *template, struct gpio_led_data *led_dat, struct device *parent, struct fwnode_handle *fwnode, gpio_blink_set_t blink_set) { struct led_init_data init_data = {}; struct pinctrl *pinctrl ; int ret, state; led_dat->cdev.default_trigger = template->default_trigger; led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod); if (!led_dat->can_sleep) led_dat->cdev.brightness_set = gpio_led_set; else led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking; led_dat->blinking = 0 ; if (blink_set) { led_dat->platform_gpio_blink_set = blink_set; led_dat->cdev.blink_set = gpio_blink_set; } if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) { state = gpiod_get_value_cansleep(led_dat->gpiod); if (state < 0 ) return state; } else { state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); } led_dat->cdev.brightness = state; led_dat->cdev.max_brightness = 1 ; if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; if (template->panic_indicator) led_dat->cdev.flags |= LED_PANIC_INDICATOR; if (template->retain_state_shutdown) led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN; ret = gpiod_direction_output(led_dat->gpiod, state); if (ret < 0 ) return ret; if (template->name) { led_dat->cdev.name = template->name; ret = devm_led_classdev_register(parent, &led_dat->cdev); } else { init_data.fwnode = fwnode; ret = devm_led_classdev_register_ext(parent, &led_dat->cdev, &init_data); } if (ret) return ret; pinctrl = devm_pinctrl_get_select_default(led_dat->cdev.dev); ret = PTR_ERR_OR_ZERO(pinctrl); if (ret == -ENODEV) ret = 0 ; if (ret) { dev_warn(led_dat->cdev.dev, "Failed to select %pfw pinctrl: %d\n" , fwnode, ret); } return ret; }
gpio_led_set / gpio_led_set_blocking / gpio_blink_set:GPIO LED 亮度设置与硬件闪烁控制回调 gpio_led_set:LED core 的非阻塞亮度设置入口(同时负责终止硬件闪烁态) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 static void gpio_led_set (struct led_classdev *led_cdev, enum led_brightness value) { struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev); int level; if (value == LED_OFF) level = 0 ; else level = 1 ; if (led_dat->blinking) { led_dat->platform_gpio_blink_set(led_dat->gpiod, level, NULL , NULL ); led_dat->blinking = 0 ; } else { if (led_dat->can_sleep) gpiod_set_value_cansleep(led_dat->gpiod, level); else gpiod_set_value(led_dat->gpiod, level); } }
gpio_led_set_blocking:LED core 的可睡眠亮度设置入口(复用 gpio_led_set) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static int gpio_led_set_blocking (struct led_classdev *led_cdev, enum led_brightness value) { gpio_led_set(led_cdev, value); return 0 ; }
gpio_blink_set:LED core 请求闪烁时的入口(交给平台硬件闪烁实现) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int gpio_blink_set (struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev); led_dat->blinking = 1 ; return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK, delay_on, delay_off); }
drivers/leds/trigger/ledtrig-heartbeat.c led_heartbeat_function / led_invert_show / led_invert_store:心跳触发器的定时相位机与 invert 属性 作用与实现原理 led_heartbeat_function() 以 phase 作为相位状态机,周期性地产生“短亮-短灭-短亮-长灭”的四段式节奏。其 period 通过一个有界的分式函数随 1 分钟负载变化:负载越高,周期越短(心跳越快),且周期存在下界与上界,避免极端负载下出现不可控的过快或过慢。
函数还处理两类全局/异步控制:
panic_heartbeats 置位后,立即强制熄灭并停止继续调度(以减少 panic 期间的不确定行为)。
LED_BLINK_BRIGHTNESS_CHANGE 位触发“闪烁亮度”更新,将 new_blink_brightness 切换为 blink_brightness。
struct heartbeat_trig_data:触发器私有状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static int panic_heartbeats; struct heartbeat_trig_data { struct led_classdev *led_cdev ; unsigned int phase; unsigned int period; struct timer_list timer ; unsigned int invert; };
led_heartbeat_function:定时器回调,推进相位并设置 LED 亮度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 static void led_heartbeat_function (struct timer_list *t) { struct heartbeat_trig_data *heartbeat_data = timer_container_of(heartbeat_data, t, timer); struct led_classdev *led_cdev ; unsigned long brightness = LED_OFF; unsigned long delay = 0 ; led_cdev = heartbeat_data->led_cdev; if (unlikely(panic_heartbeats)) { led_set_brightness_nosleep(led_cdev, LED_OFF); return ; } if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags)) led_cdev->blink_brightness = led_cdev->new_blink_brightness; switch (heartbeat_data->phase) { case 0 : heartbeat_data->period = 300 + (6720 << FSHIFT) / (5 * avenrun[0 ] + (7 << FSHIFT)); heartbeat_data->period = msecs_to_jiffies(heartbeat_data->period); delay = msecs_to_jiffies(70 ); heartbeat_data->phase++; if (!heartbeat_data->invert) brightness = led_cdev->blink_brightness; break ; case 1 : delay = heartbeat_data->period / 4 - msecs_to_jiffies(70 ); heartbeat_data->phase++; if (heartbeat_data->invert) brightness = led_cdev->blink_brightness; break ; case 2 : delay = msecs_to_jiffies(70 ); heartbeat_data->phase++; if (!heartbeat_data->invert) brightness = led_cdev->blink_brightness; break ; default : delay = heartbeat_data->period - heartbeat_data->period / 4 - msecs_to_jiffies(70 ); heartbeat_data->phase = 0 ; if (heartbeat_data->invert) brightness = led_cdev->blink_brightness; break ; } led_set_brightness_nosleep(led_cdev, brightness); mod_timer(&heartbeat_data->timer, jiffies + delay); }
led_invert_show / led_invert_store:sysfs 属性 invert 的读写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static ssize_t led_invert_show (struct device *dev, struct device_attribute *attr, char *buf) { struct heartbeat_trig_data *heartbeat_data = led_trigger_get_drvdata(dev); return sprintf (buf, "%u\n" , heartbeat_data->invert); } static ssize_t led_invert_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct heartbeat_trig_data *heartbeat_data = led_trigger_get_drvdata(dev); unsigned long state; int ret; ret = kstrtoul(buf, 0 , &state); if (ret) return ret; heartbeat_data->invert = !!state; return size; } static DEVICE_ATTR (invert, 0644 , led_invert_show, led_invert_store) ;
heartbeat_trig_activate / heartbeat_trig_deactivate / heartbeat_trig_init / heartbeat_trig_exit:触发器生命周期管理与 reboot/panic 联动清理 heartbeat_trig_attrs / heartbeat_trig_groups:导出 invert 属性组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static struct attribute *heartbeat_trig_attrs [] = { &dev_attr_invert.attr, NULL }; ATTRIBUTE_GROUPS(heartbeat_trig);
heartbeat_trig_activate:触发器激活(分配私有数据、启动相位机定时器) 作用与原理
为每个绑定该触发器的 LED 分配 heartbeat_trig_data,并通过 led_set_trigger_data() 挂到 led_cdev。
timer_setup() 建立定时器回调为 led_heartbeat_function()。
若 blink_brightness 未设置,则用 max_brightness 作为默认闪烁亮度,保证触发器产生“可见输出”。
直接调用一次 led_heartbeat_function() 等效于“立即启动一次相位推进”,避免等待首个 tick 才开始闪烁。
设置 LED_BLINK_SW 表示进入软件闪烁路径(供 LED core 侧识别与协作)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static int heartbeat_trig_activate (struct led_classdev *led_cdev) { struct heartbeat_trig_data *heartbeat_data ; heartbeat_data = kzalloc(sizeof (*heartbeat_data), GFP_KERNEL); if (!heartbeat_data) return -ENOMEM; led_set_trigger_data(led_cdev, heartbeat_data); heartbeat_data->led_cdev = led_cdev; timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0 ); heartbeat_data->phase = 0 ; if (!led_cdev->blink_brightness) led_cdev->blink_brightness = led_cdev->max_brightness; led_heartbeat_function(&heartbeat_data->timer); set_bit(LED_BLINK_SW, &led_cdev->work_flags); return 0 ; }
heartbeat_trig_deactivate:触发器反激活(同步停止定时器、释放私有数据) 作用与原理
timer_shutdown_sync() 的关键语义是:确保定时器不再排队且回调不在执行中 ,从而保证随后 kfree() 不会引发 use-after-free。
清除 LED_BLINK_SW,使 LED core 不再把该 LED 视为软件闪烁控制对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static void heartbeat_trig_deactivate (struct led_classdev *led_cdev) { struct heartbeat_trig_data *heartbeat_data = led_get_trigger_data(led_cdev); timer_shutdown_sync(&heartbeat_data->timer); kfree(heartbeat_data); clear_bit(LED_BLINK_SW, &led_cdev->work_flags); }
heartbeat_led_trigger:触发器对象(名称、激活/反激活、属性组) 1 2 3 4 5 6 7 8 9 10 11 12 13 static struct led_trigger heartbeat_led_trigger = { .name = "heartbeat" , .activate = heartbeat_trig_activate, .deactivate = heartbeat_trig_deactivate, .groups = heartbeat_trig_groups, };
heartbeat_reboot_notifier / heartbeat_panic_notifier:系统事件回调 作用与原理
reboot 回调中注销触发器:在重启路径中尽早解除 LED 触发器与 sysfs/回调关系,降低重启阶段资源不一致风险。
panic 回调中仅置位 panic_heartbeats:把“停止心跳、强制灭灯”的策略留给定时器回调执行点统一处理,减少 panic 路径中复杂操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static int heartbeat_reboot_notifier (struct notifier_block *nb, unsigned long code, void *unused) { led_trigger_unregister(&heartbeat_led_trigger); return NOTIFY_DONE; } static int heartbeat_panic_notifier (struct notifier_block *nb, unsigned long code, void *unused) { panic_heartbeats = 1 ; return NOTIFY_DONE; }
heartbeat_reboot_nb / heartbeat_panic_nb:notifier_block 实例 1 2 3 4 5 6 7 8 9 10 11 12 13 static struct notifier_block heartbeat_reboot_nb = { .notifier_call = heartbeat_reboot_notifier, }; static struct notifier_block heartbeat_panic_nb = { .notifier_call = heartbeat_panic_notifier, };
heartbeat_trig_init / heartbeat_trig_exit:模块初始化与退出 作用与原理
init:注册触发器;若成功,再挂接 panic 通知链与 reboot notifier,确保系统关键事件能触发清理/停止策略。
exit:按与 init 相反的顺序注销 notifier 与触发器,避免退出后仍收到通知导致回调访问已卸载代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static int __init heartbeat_trig_init (void ) { int rc = led_trigger_register(&heartbeat_led_trigger); if (!rc) { atomic_notifier_chain_register(&panic_notifier_list, &heartbeat_panic_nb); register_reboot_notifier(&heartbeat_reboot_nb); } return rc; } static void __exit heartbeat_trig_exit (void ) { unregister_reboot_notifier(&heartbeat_reboot_nb); atomic_notifier_chain_unregister(&panic_notifier_list, &heartbeat_panic_nb); led_trigger_unregister(&heartbeat_led_trigger); } module_init(heartbeat_trig_init); module_exit(heartbeat_trig_exit); MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>" ); MODULE_DESCRIPTION("Heartbeat LED trigger" ); MODULE_LICENSE("GPL v2" );