文章目录
- 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
 
点赞+关注,永远不迷路
 

![CodeForces..李华和迷宫.[简单].[找规律]](https://img-blog.csdnimg.cn/193c1f0bc179472ea655e347c4fd71c3.png)













![[比赛简介]ICR - Identifying Age-Related Conditions](https://img-blog.csdnimg.cn/5b2e3e4152e646979669babb6095055e.png)



