devm_of_led_classdev_register 函数

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_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);

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 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博客

你可能感兴趣的:(linux自带的LED驱动分析,linux)