Linux version: 4.14
Code link: Linux source code (v4.14) - Bootlin
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);
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_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
led_cdev->brightness_hw_changed = -1;
#endif
mutex_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_TRIGGERS
led_trigger_set_default(led_cdev);
#endif
dev_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);
在驱动代码中我们经常会见到一些以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 alive
kernel_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 sysfs
kset_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 leds
led_update_brightness()
//led_trigger_set_default()
一叶知秋,一个 LED 就能入门 Linux 内核_device_create_with_groups_Li-Yongjun的博客-CSDN博客