Linux led子系统分析2(基于Linux6.6)---软件架构介绍
一、led子系统实现说明
针对 Linux LED 子系统,其实现确实主要分为以下三大部分:led-trigger
、led-class
和 led-core
。每部分负责不同的功能和作用,下面是这三部分的详细说明:
1. led-trigger (LED 触发器)
LED 触发器(led-trigger
)是 LED 子系统中负责控制 LED 状态变化的机制。它可以根据不同的系统事件自动触发 LED 的状态改变。常见的触发器包括 CPU 活跃度、网络流量、输入事件等。
功能:
- 事件触发:通过特定的事件或条件(如 CPU 使用率、网络活动等),自动调整 LED 的状态(点亮、闪烁或关闭)。
- 触发器配置:通过 sysfs 接口,用户可以配置不同的触发器。
触发器类型:
none
:没有触发器,LED 手动控制。cpu
:基于 CPU 使用率变化触发。netdev
:基于网络接口的活动(如传输数据)触发。heartbeat
:定时闪烁,用于表示系统健康。input
:基于输入设备事件(例如按键输入)触发。
例子:
- 将 LED 配置为在网络活动时闪烁:
-
echo netdev > /sys/class/leds/my_led/trigger
2. led-class (LED 类)
led-class
是 LED 子系统的核心模块,它提供了对 LED 设备的统一管理和控制接口。在 Linux 中,每个 LED 设备都由一个 led_classdev
结构表示,该结构包含 LED 的属性、状态以及操作回调函数等。
功能:
- 设备注册与注销:
led-class
负责注册和注销 LED 设备,管理 LED 设备的生命周期。 - 统一接口:提供对 LED 的统一访问接口,包括设置亮度、控制闪烁等。
- Sysfs 接口:通过
led-class
提供的 sysfs 接口,用户空间可以对 LED 进行控制,例如设置亮度或触发器。
主要结构:
led_classdev
:每个 LED 设备对应一个led_classdev
结构,包含 LED 的名称、亮度设置、闪烁配置、触发器设置等。- 设备注册:使用
led_classdev_register()
函数注册 LED 设备,使用led_classdev_unregister()
注销设备。
例子:
- 设置 LED 亮度:
-
echo 255 > /sys/class/leds/my_led/brightness
3. led-core (LED 核心)
led-core
是 LED 子系统的底层实现,负责对 LED 设备的核心功能进行处理,包括亮度控制、闪烁控制等。它通过 led_classdev
与硬件接口进行交互。
功能:
- 控制 LED 亮度:
led-core
处理对 LED 亮度的设置,控制 LED 的点亮和熄灭。 - 闪烁控制:负责处理闪烁周期和频率的设置,通过定时器或 PWM 控制 LED 的闪烁模式。
- 硬件交互:
led-core
通过硬件接口(如 GPIO、PWM 等)实现对硬件 LED 的控制。
关键操作:
- 亮度设置:根据用户或触发器的设置,调整 LED 的亮度值。
- 闪烁管理:通过定时器或硬件计时器,周期性地改变 LED 的状态(点亮或熄灭)。
- 硬件控制:通过调用硬件驱动接口(如 GPIO 控制、PWM 信号等),将控制信号传递给硬件。
例子:
- 控制 LED 闪烁:
echo "1000" > /sys/class/leds/my_led/delay_on
echo "1000" > /sys/class/leds/my_led/delay_off
二、led子系统相关数据结构说明
其实针对led子系统而言,其本身实现的功能也比较简单,目标就是控制led的亮(包括亮度调节)、灭以及闪烁这三种情况,而针对led亮、灭、闪烁的控制策略依据业务会有所不同,因此基于这些要求,led子系统抽象了两个数据结构:struct led_classdev、struct led-trigger。下面我们首先从需求入手,分析所需要实现的功能,然后再和led子系统针对数据结构的定义进行比较。
led设备需要实现的功能:
- 需要提供亮度设置的接口,以便对led进行亮度设置;
- 需要支持led闪烁的功能;
- 能够针对具体的功能模块,提供相应的亮度调节等(以上两条是通用需求,而第三条则是可扩展的需求,以便让led实现不同的功能)。
led触发器所需要实现的功能:
- 提供一个led控制策略接口,该接口可实现对led功能的控制(如呼吸灯策略、backlight等)
而led子系统中关于struct led_classdev、struct led-trigger也就是实现上述的功能,下面我们分析一下。
2.1、struct led_classdev
如下即为led_classdev,所包含的内容:
- led亮度设置值以及最大亮度值
- led亮度设置与获取当前亮度的接口;
- 该led自行实现的闪烁接口;
- 为该led提供一个定时器,通过定时器实现通用的led闪烁功能(若该led不需要该定时器实现闪烁,则只需要实现上述3中所说的接口即可);
- 该led所需要绑定的led-trigger,即为该led绑定一个led控制方法;
- 该led定义了一个struct device类型的变量,用于将该led与led相关的class关联,并为应用程序提供了一个访问led-device的方式(通过向sysfs中创建文件,应用程序访问这些文件即可实现与led-device的交互)。
include/linux/leds.h
struct led_classdev {
const char *name;
unsigned int brightness;
unsigned int max_brightness;
unsigned int color;
int flags;
/* Lower 16 bits reflect status */
#define LED_SUSPENDED BIT(0)
#define LED_UNREGISTERING BIT(1)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME BIT(16)
#define LED_SYSFS_DISABLE BIT(17)
#define LED_DEV_CAP_FLASH BIT(18)
#define LED_HW_PLUGGABLE BIT(19)
#define LED_PANIC_INDICATOR BIT(20)
#define LED_BRIGHT_HW_CHANGED BIT(21)
#define LED_RETAIN_AT_SHUTDOWN BIT(22)
#define LED_INIT_DEFAULT_TRIGGER BIT(23)
/* set_brightness_work / blink_timer flags, atomic, private. */
unsigned long work_flags;
#define LED_BLINK_SW 0
#define LED_BLINK_ONESHOT 1
#define LED_BLINK_ONESHOT_STOP 2
#define LED_BLINK_INVERT 3
#define LED_BLINK_BRIGHTNESS_CHANGE 4
#define LED_BLINK_DISABLE 5
/* Brightness off also disables hw-blinking so it is a separate action */
#define LED_SET_BRIGHTNESS_OFF 6
#define LED_SET_BRIGHTNESS 7
#define LED_SET_BLINK 8
/* Set LED brightness level
* Must not sleep. Use brightness_set_blocking for drivers
* that can sleep while setting brightness.
*/
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/*
* Set LED brightness level immediately - it can block the caller for
* the time required for accessing a LED device register.
*/
int (*brightness_set_blocking)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
/*
* Activate hardware accelerated blink, delays are in milliseconds
* and if both are zero then a sensible default should be chosen.
* The call should adjust the timings in that case and if it can't
* match the values specified exactly.
* Deactivate blinking again when the brightness is set to LED_OFF
* via the brightness_set() callback.
* For led_blink_set_nosleep() the LED core assumes that blink_set
* implementations, of drivers which do not use brightness_set_blocking,
* will not sleep. Therefor if brightness_set_blocking is not set
* this function must not sleep!
*/
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off);
int (*pattern_set)(struct led_classdev *led_cdev,
struct led_pattern *pattern, u32 len, int repeat);
int (*pattern_clear)(struct led_classdev *led_cdev);
struct device *dev;
const struct attribute_group **groups;
struct list_head node; /* LED Device list */
const char *default_trigger; /* Trigger to use */
unsigned long blink_delay_on, blink_delay_off;
struct timer_list blink_timer;
int blink_brightness;
int new_blink_brightness;
void (*flash_resume)(struct led_classdev *led_cdev);
struct work_struct set_brightness_work;
int delayed_set_value;
unsigned long delayed_delay_on;
unsigned long delayed_delay_off;
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock;
struct led_trigger *trigger;
struct list_head trig_list;
void *trigger_data;
/* true if activated - deactivate routine uses it to do cleanup */
bool activated;
/* LEDs that have private triggers have this set */
struct led_hw_trigger_type *trigger_type;
/* Unique trigger name supported by LED set in hw control mode */
const char *hw_control_trigger;
/*
* Check if the LED driver supports the requested mode provided by the
* defined supported trigger to setup the LED to hw control mode.
*
* Return 0 on success. Return -EOPNOTSUPP when the passed flags are not
* supported and software fallback needs to be used.
* Return a negative error number on any other case for check fail due
* to various reason like device not ready or timeouts.
*/
int (*hw_control_is_supported)(struct led_classdev *led_cdev,
unsigned long flags);
/*
* Activate hardware control, LED driver will use the provided flags
* from the supported trigger and setup the LED to be driven by hardware
* following the requested mode from the trigger flags.
* Deactivate hardware blink control by setting brightness to LED_OFF via
* the brightness_set() callback.
*
* Return 0 on success, a negative error number on flags apply fail.
*/
int (*hw_control_set)(struct led_classdev *led_cdev,
unsigned long flags);
/*
* Get from the LED driver the current mode that the LED is set in hw
* control mode and put them in flags.
* Trigger can use this to get the initial state of a LED already set in
* hardware blink control.
*
* Return 0 on success, a negative error number on failing parsing the
* initial mode. Error from this function is NOT FATAL as the device
* may be in a not supported initial state by the attached LED trigger.
*/
int (*hw_control_get)(struct led_classdev *led_cdev,
unsigned long *flags);
/*
* Get the device this LED blinks in response to.
* e.g. for a PHY LED, it is the network device. If the LED is
* not yet associated to a device, return NULL.
*/
struct device *(*hw_control_get_device)(struct led_classdev *led_cdev);
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
int brightness_hw_changed;
struct kernfs_node *brightness_hw_changed_kn;
#endif
/* Ensures consistent access to the LED Flash Class device */
struct mutex led_access;
};
2.2、struct led-trigger
该数据结构的定义如下:
- 调用activate接口(当led-dev与led-trigger完成绑定时,即调用该接口),使能该led的控制策略;
- 调用deactivate接口(当led-dev与led-trigger解除绑定时,即调用该接口),关闭该led的控制策略;
include/linux/leds.h
struct led_trigger {
/* Trigger Properties */
const char *name;
int (*activate)(struct led_classdev *led_cdev);
void (*deactivate)(struct led_classdev *led_cdev);
/* LED-private triggers have this set */
struct led_hw_trigger_type *trigger_type;
/* LEDs under control by this trigger (for simple triggers) */
spinlock_t leddev_list_lock;
struct list_head led_cdevs;
/* Link to next registered trigger */
struct list_head next_trig;
const struct attribute_group **groups;
};
除了上述的数据结构之外,led子系统还提供了led-dev的注册与注销接口、led亮度设置接口、定时器实现的led闪烁接口、led-trigger的注册与注销接口等对外接口。
以上即是led_classdev、led_trigger以及led子系统提供接口的关联图,主要说明如下:
- 所有注册的led_classdev均会链接到leds_list;
- 所有注册的led_trigger均会链接到链表trigger_list上;
- 一个led_trigger可以与多个led_classdev关联,而一个led_classdev只能绑定一个led_trigger,即一个led只能同时使用一种led控制策略(这也符合我们的需求)。
- 每一个led_classdev均会提供一个定时器,用于实现led闪烁功能,但是否启用该软件定时器由绑定的led-trigger决定(当然也与led-trigger所要实现的功能关联,如ledtrigger-timer,则就是借助该软件定时器实现闪烁的(前提是该led-classdev未实现自己的闪烁接口))。
在 Linux 内核中,LED 子系统的设计和实现通过多个组件协作来控制 LED 设备的行为。led_classdev
、led_trigger
和 LED 子系统的其他接口密切配合。以下是各个组件的关联图示,帮助理解它们之间的关系。
LED 子系统组件及其接口
+------------------+
| User Space |
| |
| /sys/class/leds/ |
| |
+------------------+
|
+------------------------------+
| Sysfs Interface (User Control)|
+------------------------------+
|
+-------------------------------------------------------+
| LED Class Core |
| +-----------------------------------------------+ |
| | led_classdev structure | |
| | (per-LED device structure) | |
| +-----------------------------------------------+ |
| | |
| | Register LED Device |
| v |
| +-----------------------------------------------+ |
| | led_classdev_register() & led_classdev_unregister()| |
| +-----------------------------------------------+ |
| | |
| v |
| +-------------------------------------------+ |
| | LED Control: Brightness, Blink, etc. | |
| | led_set_brightness(), led_blink(), | |
| | led_trigger_set(), etc. | |
| +-------------------------------------------+ |
+-------------------------------------------------------+
|
v
+--------------------------------------+
| LED Trigger Mechanism |
| (led_trigger) |
| +--------------------------------+ |
| | Event-driven Trigger: | |
| | - CPU usage (cpu) | |
| | - Network activity (netdev) | |
| | - Heartbeat (heartbeat) | |
| | - User-defined triggers (input)| |
| +--------------------------------+ |
| | |
| v |
| +---------------------------------+ |
| | Event Handling (triggers LED) | |
| | led_trigger_register() | |
| +---------------------------------+ |
+--------------------------------------+
|
v
+-----------------------------+
| LED Hardware Interface |
| (GPIO, PWM, or other HW) |
| |
+-----------------------------+
|
v
+-------------------+
| LED Hardware (LED) |
+-------------------+
解释:
-
User Space (用户空间):
- 用户空间通过
/sys/class/leds/
路径来控制 LED。它与内核的 LED 子系统通过sysfs
接口交互,允许用户设置 LED 的亮度、触发器以及闪烁等行为。
- 用户空间通过
-
LED Class Core (LED 类核心):
led_classdev
结构体:这是每个 LED 设备的核心表示,包含了 LED 的属性和操作函数。每个 LED 设备会通过led_classdev_register()
函数注册到系统中,并通过led_classdev_unregister()
注销。- 该部分的主要任务是处理 LED 的控制命令,如亮度设置 (
led_set_brightness()
) 和闪烁管理(通过led_blink()
),并响应触发器的变化。
-
LED Trigger (LED 触发器):
-
LED 触发器是根据外部事件或系统状态自动控制 LED 状态的机制。常见的触发器包括:
- CPU 触发器 (
cpu
): 根据 CPU 活跃度改变 LED 状态。 - 网络活动触发器 (
netdev
): 当网络设备有数据传输时触发 LED 改变。 - 心跳触发器 (
heartbeat
): 周期性地闪烁 LED 表示系统正常。 - 用户定义的输入触发器 (
input
): 根据输入事件(如按键)来控制 LED。
- CPU 触发器 (
-
led_trigger_register()
函数用于注册触发器,而led_trigger_set()
则用于设置触发器的行为。当触发器事件发生时,LED 设备会相应地改变状态。
-
-
LED Hardware Interface (LED 硬件接口):
- LED 子系统通过底层的硬件接口(如 GPIO、PWM 等)控制 LED 的物理状态。具体的硬件驱动会通过
led_classdev
结构与硬件进行交互,控制 LED 的点亮、闪烁等。
- LED 子系统通过底层的硬件接口(如 GPIO、PWM 等)控制 LED 的物理状态。具体的硬件驱动会通过
关键函数和调用流程:
-
led_classdev_register()
:- 用于将一个新的 LED 设备注册到 LED 子系统。
- 这个函数将 LED 设备与
led_classdev
结构绑定,允许后续通过sysfs
控制 LED 的亮度、闪烁等。
-
led_trigger_register()
:- 注册一个 LED 触发器,使得该触发器可以根据事件自动控制 LED 的行为。
- 触发器会根据外部事件(如网络活动或 CPU 负载)改变 LED 的状态(亮起、闪烁等)。
-
led_set_brightness()
和led_blink()
:- 用于控制 LED 的亮度或闪烁模式。这些操作通常由用户通过
sysfs
或触发器控制。
- 用于控制 LED 的亮度或闪烁模式。这些操作通常由用户通过
-
硬件控制:
- 通过 GPIO 或 PWM 控制 LED 的电平,从而实现点亮、熄灭或闪烁效果。