文章目录
- 1、LED驱动初始化流程
- 1.1 LED驱动匹配以及设备的创建
- 1.1.1 gpio_led_probe
- 1.1.2 gpio_leds_create
- 1.1.3 create_gpio_led
- 1.2、LED设备的注册
- 1.2.1 devm_of_led_classdev_register
- 1.2.2 of_led_classdev_register
- 1.3、leds class 创建
- 1.3.1 leds_init
- 1.3.2 led_groups
- 2、LED读写流程
- 2.1 用户空间的读写操作
- 2.2 属性定义
- 2.2.1 DEVICE_ATTR_RW
- 2.3 读属性
- 2.4 写属性
上文,我们了解了
LED
子系统核心的数据结构以及之间的关联,下面我们来看一下其详细的实现流程。
分析LED
子系统有两个线路进行分析,一条是从驱动和设备匹配后,注册进入LED
子系统,即LED驱动初始化,另一条是LED
子系统框架的初始化,即**leds class
创建**。
1、LED驱动初始化流程
1.1 LED驱动匹配以及设备的创建
一般我们分析驱动,都是先从
probe
入手,也就是驱动的入口函数。
1.1.1 gpio_led_probe
static int gpio_led_probe(struct platform_device *pdev)
{
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); // 获取平台数据
struct gpio_leds_priv *priv;
int i, ret = 0;
if (pdata && pdata->num_leds) {
priv = devm_kzalloc(&pdev->dev,
sizeof_gpio_leds_priv(pdata->num_leds),
GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->num_leds = pdata->num_leds;
for (i = 0; i < priv->num_leds; i++) {
ret = create_gpio_led(&pdata->leds[i], &priv->leds[i],
&pdev->dev, NULL,
pdata->gpio_blink_set);
if (ret < 0)
return ret;
}
} else {
priv = gpio_leds_create(pdev); // 创建LED
if (IS_ERR(priv))
return PTR_ERR(priv);
}
platform_set_drvdata(pdev, priv);
return 0;
}
函数介绍:gpio_led_probe
是LED
驱动的入口函数,也是LED
子系统中,硬件设备和驱动程序匹配后,第一个执行的函数。
实现思路:
- 通过
dev_get_platdata
检索设备的平台数据,如果平台数据中的LED
数量大于零,则使用devm_kzalloc
为其分配内存空间,并且使用create_gpio_led
进行初始化 - 如果平台数据不存在或
LED
的数量为零,则使用gpio_leds_create
创建LED。 - 最后,设置驱动程序数据,并返回0,表示操作成功。
1.1.2 gpio_leds_create
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *child;
struct gpio_leds_priv *priv;
int count, ret;
count = device_get_child_node_count(dev); // 获取子节点数量
if (!count)
return ERR_PTR(-ENODEV);
priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL); // 根据数量分配内存空间
if (!priv)
return ERR_PTR(-ENOMEM);
device_for_each_child_node(dev, child) { // 遍历每个LED设备树节点
struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
struct gpio_led led = {};
const char *state = NULL;
struct device_node *np = to_of_node(child);
ret = fwnode_property_read_string(child, "label", &led.name); // 获取设备树属性
if (ret && IS_ENABLED(CONFIG_OF) && np)
led.name = np->name;
if (!led.name) {
fwnode_handle_put(child);
return ERR_PTR(-EINVAL);
}
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
GPIOD_ASIS,
led.name);
if (IS_ERR(led.gpiod)) {
fwnode_handle_put(child);
return ERR_CAST(led.gpiod);
}
fwnode_property_read_string(child, "linux,default-trigger",
&led.default_trigger);
if (!fwnode_property_read_string(child, "default-state",
&state)) {
if (!strcmp(state, "keep"))
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
else if (!strcmp(state, "on"))
led.default_state = LEDS_GPIO_DEFSTATE_ON;
else
led.default_state = LEDS_GPIO_DEFSTATE_OFF;
}
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, np, NULL); // 创建LED设备
if (ret < 0) {
fwnode_handle_put(child);
return ERR_PTR(ret);
}
led_dat->cdev.dev->of_node = np;
priv->num_leds++;
}
return priv;
}
函数介绍:gpio_leds_create
主要用于创建LED
设备。
实现思路:
- 通过
device_get_child_node_count
获取设备树中LED
子节点的数量,根据获取到的子节点数量,分配LED
设备对应的内存空间 - 通过
device_for_each_child_node
遍历每个子节点,并为每个子节点创建对应的LED
设备 - 对于每个子节点,使用
fwnode_property_read_string
接口,读取设备树中相关的属性信息,如:label
、linux,default-trigger
等,将这些信息赋值给gpio_led
结构体中 - 最后将遍历的每个
LED
,调用create_gpio_led
进行设备的创建
1.1.3 create_gpio_led
static int create_gpio_led(const struct gpio_led *template,
struct gpio_led_data *led_dat, struct device *parent,
struct device_node *np, gpio_blink_set_t blink_set)
{
int ret, state;
led_dat->gpiod = template->gpiod;
if (!led_dat->gpiod) {
/*
* This is the legacy code path for platform code that
* still uses GPIO numbers. Ultimately we would like to get
* rid of this block completely.
*/
unsigned long flags = GPIOF_OUT_INIT_LOW;
/* skip leds that aren't available */
if (!gpio_is_valid(template->gpio)) { // 判断是否gpio合法
dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
template->gpio, template->name);
return 0;
}
if (template->active_low)
flags |= GPIOF_ACTIVE_LOW;
ret = devm_gpio_request_one(parent, template->gpio, flags,
template->name);
if (ret < 0)
return ret;
led_dat->gpiod = gpio_to_desc(template->gpio); // 获取gpio组
if (!led_dat->gpiod)
return -EINVAL;
}
led_dat->cdev.name = template->name; // 赋值一些属性信息
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_FULL : LED_OFF;
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;
return devm_of_led_classdev_register(parent, np, &led_dat->cdev); // 将LED设备注册到子系统中
}
函数介绍:create_gpio_led
创建LED
设备的核心函数
实现思路:
- 先通过
gpio_is_valid
接口,判断GPIO
是否合法 - 将上层从设备树解析出来的信息,填充到
gpio_led_data
字段中,并且初始化部分字段,如:led_classdev
、gpio_desc
等 - 最后调用
devm_of_led_classdev_register
接口,将LED
设备注册到LED
框架之中。
1.2、LED设备的注册
1.2.1 devm_of_led_classdev_register
/**
* devm_of_led_classdev_register - resource managed led_classdev_register()
*
* @parent: parent of LED device
* @led_cdev: the led_classdev structure for this device.
*/
int devm_of_led_classdev_register(struct device *parent,
struct device_node *np,
struct led_classdev *led_cdev)
{
struct led_classdev **dr;
int rc;
dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
if (!dr)
return -ENOMEM;
rc = of_led_classdev_register(parent, np, led_cdev); // 注册到子系统
if (rc) {
devres_free(dr);
return rc;
}
*dr = led_cdev;
devres_add(parent, dr);
return 0;
}
EXPORT_SYMBOL_GPL(devm_of_led_classdev_register);
函数介绍:devm_of_led_classdev_register
是of_led_classdev_register
函数的资源管理版本。即:在of_led_classdev_register
之上,进行了资源的管理。
实现思路:
- 先通过
struct led_classdev **dr
创建一个新对象,并将其与给定的设备节点关联 - 该函数分配了一个
devres
结构来管理led_classdev
对象的生命周期。 - 如果注册成功,则
led_classdev
对象将存储在devres
结构中,并与父设备关联。
1.2.2 of_led_classdev_register
/**
* of_led_classdev_register - register a new object of led_classdev class.
*
* @parent: parent of LED device
* @led_cdev: the led_classdev structure for this device.
* @np: DT node describing this LED
*/
int of_led_classdev_register(struct device *parent, struct device_node *np,
struct led_classdev *led_cdev)
{
char name[LED_MAX_NAME_SIZE];
int ret;
ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); // 将leds设备注册进sysfs文件系统中
if (ret < 0)
return ret;
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", name); // 添加属性文件
if (IS_ERR(led_cdev->dev)) {
mutex_unlock(&led_cdev->led_access);
return PTR_ERR(led_cdev->dev);
}
led_cdev->dev->of_node = np;
if (ret)
dev_warn(parent, "Led %s renamed to %s due to name collision",
led_cdev->name, dev_name(led_cdev->dev));
if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
ret = led_add_brightness_hw_changed(led_cdev);
if (ret) {
device_unregister(led_cdev->dev);
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
/* add to the list of leds */
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_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;
}
函数介绍:of_led_classdev_register
注册一个新的led_classdev
类。
实现思路:
- 通过
led_classdev_next_name
来对LED
名字添加序号,生成唯一名称 - 使用
device_create_with_groups
接口,并使用定义好的属性组,创建一个新的设备 - 使用
led_init_core
接口,初始化了LED
核心并为设备设置了默认触发器。
1.3、leds class 创建
在上面,我们直接注册了led_classdev
,但是在实际情况中,每一个设备都会去创建对应的class
,leds
也不例外。
1.3.1 leds_init
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds"); // 创建leds文件节点
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->pm = &leds_class_dev_pm_ops;
leds_class->dev_groups = led_groups;
return 0;
}
static void __exit leds_exit(void)
{
class_destroy(leds_class);
}
subsys_initcall(leds_init);
module_exit(leds_exit);
MODULE_AUTHOR("John Lenz, Richard Purdie");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED Class Interface");
函数介绍:leds_init
该函数在内核在加载的时候执行,主要用于初始化leds_class
类,对应与sysfs
文件系统下的leds
目录。
实现思路:
- 调用
class_create
接口,创建leds_class
类 leds_class_dev_pm_ops
配置电源管理接口led_groups
配置文件属性
1.3.2 led_groups
#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
// 属性文件
static struct attribute *led_trigger_attrs[] = {
&dev_attr_trigger.attr,
NULL,
};
static const struct attribute_group led_trigger_group = {
.attrs = led_trigger_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_groups
来配置相关文件属性,这里有三个属性:max_brightness
、brightness
、trigger
,
这里仅仅是配置3个属性文件,但是还没有创建,真正的创建在于device
注册之后。
综上,LED
初始化流程如下:
leds_init(drivers\leds\led-class.c) // 系统开始就初始化
|--> class_create // 创建leds_class类
gpio_led_probe(drivers/leds/leds-gpio.c)
|--> gpio_leds_create
|--> create_gpio_led // 创建LED设备
|--> devm_of_led_classdev_register
|--> of_led_classdev_register // 注册到框架中
|--> device_create_with_groups // 添加属性
|--> led_init_core // 初始化LED核心层
最后创建的文件结构如下:
一些属性文件是我们配置的,如max_brightness
、brightness
、trigger
,一些链接文件是自动创建的。
2、LED读写流程
2.1 用户空间的读写操作
在上文,我们知道,在Linux
用户空间下,生成的属性文件,我们一般在用户空间通过两种方式访问:
- 以C库或者系统调用来读写文件:相关接口有
fopen
或者open
,fclose
或者close
,fwrite
或者write
,fread
或者read
- 以命令行直接读写文件:相关命令有
echo
,cat
这两种方式在内核中的接口都是一致的,下面我们来分析一下驱动代码。
在文件
led-class.c
中,是如何配置相关属性文件的读写的呢?
在上面介绍,我们在初始化leds_class
的时候,配置过相关属性文件,如下
static struct attribute *led_class_attrs[] = {
&dev_attr_brightness.attr,
&dev_attr_max_brightness.attr,
NULL,
};
那么这些属性在哪里定义的呢?
2.2 属性定义
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
/* no lock needed for this */
led_update_brightness(led_cdev);
return sprintf(buf, "%u\n", led_cdev->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);
上面定义了两个函数(brightness_show
,brightness_store
)和一个属性名称(brightness
),那么在哪里将这两个函数与这个属性联系起来的呢?
关键在于DEVICE_ATTR_RW
宏定义!
2.2.1 DEVICE_ATTR_RW
// static DEVICE_ATTR_RW(brightness);
#define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
// static struct device_attribute dev_attr_brightness = __ATTR_RW(brightness)
#define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
// static struct device_attribute dev_attr_brightness = __ATTR(brightness, 0644, brightness_show, brightness_store)
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
// static struct device_attribute dev_attr_brightness = {
// .attr = {
// .name = __stringify(brightness),
// .mode = VERIFY_OCTAL_PERMISSIONS(0644),
// }
// .show = brightness_show,
// .store = brightness_store,
//}
上面屏蔽的内容就是static DEVICE_ATTR_RW(brightness)
展开的原貌,这样就与上面的两个函数(brightness_show
,brightness_store
)关联了起来!
2.3 读属性
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
/* no lock needed for this */
led_update_brightness(led_cdev);
return sprintf(buf, "%u\n", led_cdev->brightness);
}
函数介绍:将亮度值读出到用户空间
实现思路:
- 调用
dev_get_drvdata
接口,获取led_classdev
结构体的地址 - 调用
led_update_brightness
接口更新亮度值,如果led_cdev->brightness_get
自定义的接口实现的话,就调用该接口,如果未实现,则直接返回led_cdev->brightness
2.4 写属性
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;
}
函数介绍:设置LED
亮度值
实现思路:
- 调用
dev_get_drvdata
接口,获取led_classdev
结构体的地址 - 调用接口
led_sysfs_is_disabled
判断是否有写权限 - 调用
led_set_brightness
接口,设置亮度
详细的写流程如下:
// 写亮度值
brightness_store(drivers/leds/led-class.c) // 写亮度值
|--> led_sysfs_is_disabled // 判断是否有权限
|--> led_set_brightness
|--> led_set_brightness_nosleep // 设置亮度
|--> led_set_brightness_nopm
|--> schedule_work // 加入到队列中
|--> set_brightness_delayed // 设置亮度值
|--> __led_set_brightness
|--> led_cdev->brightness_set(/home/donge/Develop/I_MX/source/kernel/drivers/leds/led-core.c)
|--> gpio_led_set
|--> gpiod_set_value
点赞+关注,永远不迷路