Linux version: 4.14
Code link: Linux source code (v4.14) - Bootlin
1 devm_of_led_classdev_register 函数
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);
2 of_led_classdev_register 函数
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));if (ret < 0)return ret;led_cdev->dev = device_create_with_groups(leds_class, parent, 0,led_cdev, led_cdev->groups, "%s", name);if (IS_ERR(led_cdev->dev))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);return ret;}}led_cdev->work_flags = 0;
#ifdef CONFIG_LEDS_TRIGGERSinit_rwsem(&led_cdev->trigger_lock);
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGEDled_cdev->brightness_hw_changed = -1;
#endifmutex_init(&led_cdev->led_access);/* 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_TRIGGERSled_trigger_set_default(led_cdev);
#endifdev_dbg(parent, "Registered led device: %s\n",led_cdev->name);return 0;
}
EXPORT_SYMBOL_GPL(of_led_classdev_register);
(1) led_classdev_next_name
该函数决定 LED 设备在文件系统里面的名称。从设备树节点里面获取到的名称(label属性)作为初始的 name,然后遍历全局类 leds_class(class类型),跟里面的设备逐个对比,如果已经有同名的设备了,则在 name 后面添加 _x 形式的后缀,然后再次逐个对比,直到 leds_class 里面找不到同名的设备了,则表示该名称可以用于创建新设备了。该函数返回的 ret 为检测到命名冲突的次数。
static int led_classdev_next_name(const char *init_name, char *name,size_t len)
{unsigned int i = 0;int ret = 0;struct device *dev;strlcpy(name, init_name, len);while ((ret < len) &&(dev = class_find_device(leds_class, NULL, name, match_name))) {put_device(dev);ret = snprintf(name, len, "%s_%u", init_name, ++i);}if (ret >= len)return -ENOMEM;return i;
}
① class_find_device 函数
该函数遍历 class 中的 device ,返回符合 match 条件的 device 结构体变量。
struct device *class_find_device(struct class *class, struct device *start,const void *data,int (*match)(struct device *, const void *))
{struct class_dev_iter iter;struct device *dev;if (!class)return NULL;if (!class->p) {WARN(1, "%s called for class '%s' before it was initialized",__func__, class->name);return NULL;}class_dev_iter_init(&iter, class, start, NULL);while ((dev = class_dev_iter_next(&iter))) {if (match(dev, data)) {get_device(dev);break;}}class_dev_iter_exit(&iter);return dev;
}
EXPORT_SYMBOL_GPL(class_find_device);
② class_dev_iter_next 函数
该函数根据输入的 iter 类型的变量返回下一个 device 变量
struct device *class_dev_iter_next(struct class_dev_iter *iter)
{struct klist_node *knode;struct device *dev;while (1) {knode = klist_next(&iter->ki);if (!knode)return NULL;dev = container_of(knode, struct device, knode_class);if (!iter->type || iter->type == dev->type)return dev;}
}
EXPORT_SYMBOL_GPL(class_dev_iter_next);
(2)device_create_with_groups
该函数在全局类led_class下创建一个LED设备,这里参数parent为LED分组,所以在文件系统里面,该LED设备的节点会被创建在LED分组下面,而不是通常的/dev目录下面。
struct device *device_create_with_groups(struct class *class,struct device *parent, dev_t devt,void *drvdata,const struct attribute_group **groups,const char *fmt, ...)
{va_list vargs;struct device *dev;va_start(vargs, fmt);dev = device_create_groups_vargs(class, parent, devt, drvdata, groups,fmt, vargs);va_end(vargs);return dev;
}
EXPORT_SYMBOL_GPL(device_create_with_groups);
① device_create_groups_vargs 函数
static struct device *
device_create_groups_vargs(struct class *class, struct device *parent,dev_t devt, void *drvdata,const struct attribute_group **groups,const char *fmt, va_list args)
{struct device *dev = NULL;int retval = -ENODEV;if (class == NULL || IS_ERR(class))goto error;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev) {retval = -ENOMEM;goto error;}device_initialize(dev);dev->devt = devt;dev->class = class;dev->parent = parent;dev->groups = groups;dev->release = device_create_release;dev_set_drvdata(dev, drvdata);retval = kobject_set_name_vargs(&dev->kobj, fmt, args);if (retval)goto error;retval = device_add(dev);if (retval)goto error;return dev;error:put_device(dev);return ERR_PTR(retval);
}
② kobject_set_name_vargs
该函数设置kobject的名称
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,va_list vargs)
{const char *s;if (kobj->name && !fmt)return 0;s = kvasprintf_const(GFP_KERNEL, fmt, vargs);if (!s)return -ENOMEM;/** ewww... some of these buggers have '/' in the name ... If* that's the case, we need to make sure we have an actual* allocated copy to modify, since kvasprintf_const may have* returned something from .rodata.*/if (strchr(s, '/')) {char *t;t = kstrdup(s, GFP_KERNEL);kfree_const(s);if (!t)return -ENOMEM;strreplace(t, '/', '!');s = t;}kfree_const(kobj->name);kobj->name = s;return 0;
}
③ device_add
device_add就是将设备加入到Linux设备模型的关键,它的内部将找到它的bus,然后让它的bus给它找到它的driver,可参考:
八、device_add_device add_bit[7:4]_宁可一思进莫在一思停的博客-CSDN博客
(3)led_add_brightness_hw_changed
该函数创建brightness相关的文件
static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
{struct device *dev = led_cdev->dev;int ret;ret = device_create_file(dev, &dev_attr_brightness_hw_changed);if (ret) {dev_err(dev, "Error creating brightness_hw_changed\n");return ret;}led_cdev->brightness_hw_changed_kn =sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");if (!led_cdev->brightness_hw_changed_kn) {dev_err(dev, "Error getting brightness_hw_changed kn\n");device_remove_file(dev, &dev_attr_brightness_hw_changed);return -ENXIO;}return 0;
}
(4)led_update_brightness
该函数更新 led 的亮度状态
int led_update_brightness(struct led_classdev *led_cdev)
{int ret = 0;if (led_cdev->brightness_get) {ret = led_cdev->brightness_get(led_cdev);if (ret >= 0) {led_cdev->brightness = ret;return 0;}}return ret;
}
EXPORT_SYMBOL_GPL(led_update_brightness);
(5)led_init_core
初始化工作队列和定时器,处理led的闪烁
void led_init_core(struct led_classdev *led_cdev)
{INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);setup_timer(&led_cdev->blink_timer, led_timer_function,(unsigned long)led_cdev);
}
EXPORT_SYMBOL_GPL(led_init_core);
(6)led_trigger_set_default
该函数设置led的默认触发状态。
void led_trigger_set_default(struct led_classdev *led_cdev)
{struct led_trigger *trig;if (!led_cdev->default_trigger)return;down_read(&triggers_list_lock);down_write(&led_cdev->trigger_lock);list_for_each_entry(trig, &trigger_list, next_trig) {if (!strcmp(led_cdev->default_trigger, trig->name))led_trigger_set(led_cdev, trig);}up_write(&led_cdev->trigger_lock);up_read(&triggers_list_lock);
}
EXPORT_SYMBOL_GPL(led_trigger_set_default);
3 devres_add 函数
在驱动代码中我们经常会见到一些以devm开头的函数,这一类的函数都是和设备资源管理相关的
devm架构中代表资源的机构体是struct devres和struct devres_node
devres_add就是注册函数,他把devres加入到device的相关链表中
void devres_add(struct device *dev, void *res)
{struct devres *dr = container_of(res, struct devres, data);unsigned long flags;spin_lock_irqsave(&dev->devres_lock, flags);add_dr(dev, &dr->node);spin_unlock_irqrestore(&dev->devres_lock, flags);
}
EXPORT_SYMBOL_GPL(devres_add);
补充:
从 uboot 到 kernel 再到 /sys/class,然后注册 leds 类,再实例化一个 LED 灯。
/* uboot */
boot_jump_linux()announce_and_cleanup()printf("\nStarting kernel ...%s\n\n"); // printf() bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel");cleanup_before_linux()kernel_entry(0, machid, r2);/* kernel */
start_kernel()rest_init() // Do the rest non-__init'ed, we're now alivekernel_thread(kernel_init, NULL, CLONE_FS);kernel_init()kernel_init_freeable()/** Ok, the machine is now initialized. None of the devices* have been touched yet, but the CPU subsystem is up and* running, and memory and process management works.** Now we can finally start doing some real work..*/do_basic_setup()driver_init() // to initialize their subsystems.devtmpfs_init()devices_init()buses_init()classes_init()kset_create_and_add("class", NULL, NULL); // create a struct kset dynamically and add it to sysfskset_create()kobject_set_name()kset_register()kset_init()kobject_add_internal()kobject_get()kobj_kset_join()kset_get()list_add_tail()__list_add(){next->prev = new;new->next = next;new->prev = prev;}create_dir()firmware_init()hypervisor_init()platform_bus_init()cpu_dev_init()memory_dev_init()container_dev_init()of_core_init()subsys_initcall(leds_init);
leds_init() // 创建 leds 类,即 /sys/class/leds 目录class_create()__class_create()__class_register()kset_register()led_classdev_register()of_led_classdev_register() // register a new object(对象) of led_classdev class.led_classdev_next_name()device_create_with_groups()led_add_brightness_hw_changed()list_add_tail() // add to the list of ledsled_update_brightness()//led_trigger_set_default()
参考:
一叶知秋,一个 LED 就能入门 Linux 内核_device_create_with_groups_Li-Yongjun的博客-CSDN博客