linux驱动模型--Apple的学习笔记
  2Nv1H5BMjysw 2023年11月19日 29 0

一,前言

既然是复习设备驱动,第一步当然是做一个最简单的基于设备树的驱动applechar,然后insmod和rmmod使用下,接着要回忆下driver和device是怎么match的,且把相关结构体复习下。

看了下结构体发现有点忘记了,另外match的函数也忘记了。有些东西不需要死记硬背,通过代码分析的方法论找到它即可。就是在我自己的驱动的probe函数打断点,看谁调用的,不是就都找到了嘛!

二,源码分析

  1. 首先回忆到的设备驱动模型是在do_initcalls把注册的driver遍历进行找设备,主要是driver和device match。先要match,然后就probe了。 我在qemu仿真vexpress开发板,自己的驱动中applechar_probe打断点,然后看到了调用情况
ret_from_fork
kernel_init
kernel_init_freeable
do_basic_setup
do_initcalls
do_initcall_level
do_one_initcall
applechar_drv_init
__platform_driver_register
driver_register
bus_add_driver
driver_attach
bus_for_each_dev
__driver_attach
device_driver_attach
driver_probe_device
really_probe
platform_drv_probe
applechar_probe

通过一个个看这些关键函数,找到__driver_attach里面有driver_match_device用来做匹配了。而至于probe,光从上面看really_probe后为什么要先调用platform_drv_probe而不是applechar_probe的原因是really_probe函数中的drv->probe的drv是device_driver类型,其实是在__platform_driver_register的时候赋值了platform_drv_probe,而在platform_drv_probe函数中drv->probe的drv是platform_driver,我添加的驱动代码中也是platform_driver类型的。

int __platform_driver_register(struct platform_driver *drv,
				struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &platform_bus_type;
	drv->driver.probe = platform_drv_probe;
	drv->driver.remove = platform_drv_remove;
	drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}

platform_driver_register是我添加在module_init中的,也就是do_one_initcall的时候遍历到的。

  1. 接下来分析match dts属性的具体代码位置。我一路跟进了代码,通过函数名字先找到位置。 调用路径
bus_add_driver
driver_attach
bus_for_each_dev
__driver_attach
driver_match_device
platform_match
of_driver_match_device

好了,找到关键函数了,接着自己继续看代码 of_driver_match_device->of_match_device->of_match_node->__of_match_node分析

static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
					   const struct device_node *node)
{
	const struct of_device_id *best_match = NULL;
	int score, best_score = 0;

	if (!matches)
		return NULL;

	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
		score = __of_device_is_compatible(node, matches->compatible,
						  matches->type, matches->name);
		if (score > best_score) {
			best_match = matches;
			best_score = score;
		}
	}

	return best_match;
}

看到细节的东东了,比如用户写的驱动,为什么of_device_id最后要加一个NULL,原因就是__of_match_node中的for循环的结束是根据NULL来判断的。

static const struct of_device_id apple_char[] = {
    { .compatible = "apple,char" },
    { },
};

继续再看,具体的属性匹配如下np->properties依次扫描compatible内容到最后,这就是dts中的compatible。

static int __of_device_is_compatible(const struct device_node *device,
				     const char *compat, const char *type, const char *name)
{
	struct property *prop;
	const char *cp;
	int index = 0, score = 0;

	/* Compatible match has highest priority */
	if (compat && compat[0]) {
		prop = __of_find_property(device, "compatible", NULL);
		for (cp = of_prop_next_string(prop, NULL); cp;
		     cp = of_prop_next_string(prop, cp), index++) {
			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
				score = INT_MAX/2 - (index << 2);
				break;
			}
		}
		if (!score)
			return 0;
	}

	/* Matching type is better than matching name */
	if (type && type[0]) {
		if (!__of_node_is_type(device, type))
			return 0;
		score += 2;
	}

	/* Matching name is a bit better than not */
	if (name && name[0]) {
		if (!of_node_name_eq(device, name))
			return 0;
		score++;
	}

	return score;
}

接着思考device_node结构体成员值怎么来的? 答:方法就是找这一路的函数,什么时候突然参数中出现device_node,找到如下dev->of_node语句就把传入的device变成了传入device_node,之后就可以在of_node中找dts中的compile属性了。

const struct of_device_id *of_match_device(const struct of_device_id *matches,
					   const struct device *dev)
{
	if ((!matches) || (!dev->of_node))
		return NULL;
	return of_match_node(matches, dev->of_node);  
}

再思考device结构体成员值怎么来的? 答:struct device从bus->p->klist_devices中来,明显klist_devices是一个device链表集合。

int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus || !bus->p)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while (!error && (dev = next_device(&i)))
		error = fn(dev, data);
	klist_iter_exit(&i);
	return error;
}

在搜索过程中发现了有相关的help文档bus.rst,于是看到了pci_bus_match,想起来了这些device和driver的match都是用的各个总线的match函数。

3. 好了我主要想复习的device和driver的match及probe找到了。接着要看一个我之前没关注的内容就是sysfs,对linux一切皆文件,所以sysfs才是关键,正好网上看了一篇比较好的blog,那么我需要关注kobject,keset和subsys_private。 从bus->p->klist_devices来分析,bus_type中有subsys_private成员指针。

struct subsys_private {
	struct kset subsys;
	struct kset *devices_kset;
	struct list_head interfaces;
	struct mutex mutex;

	struct kset *drivers_kset;
	struct klist klist_devices;
	struct klist klist_drivers;
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1;
	struct bus_type *bus;

	struct kset glue_dirs;
	struct class *class;
};

subsys_private继承了kset,kset还继承了kobject,通过kobject成员可以找到subsys_private。 然后搜索klist_devices可以找到klist_add_tail,就是为此list添加设备,函数名字是bus_add_device。 打个断点看看是否在do_initcalls前就调用了,确实是的,路径如下 do_basic_setup->driver_init->platform_bus_init->device_register->device_add->bus_add_device。 关于bus_add_device的含义看注释,先添加设备属性,再添加软链接,最后将设备添加到devices list中。

/**
 * bus_add_device - add device to bus
 * @dev: device being added
 *
 * - Add device's bus attributes.
 * - Create links to device's bus.
 * - Add the device to its bus's list of devices.
 */
int bus_add_device(struct device *dev)
{
	struct bus_type *bus = bus_get(dev->bus);
	int error = 0;

	if (bus) {

		error = device_add_groups(dev, bus->dev_groups);
        ...
		error = sysfs_create_link(&bus->p->devices_kset->kobj,
						&dev->kobj, dev_name(dev));
        ...
		klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
        ...
	}
	return 0;
    ...
}
  1. kobject.rst里面有kobject和kset相关api使用说明。 kobject是一个目录,里面需要设置属性,然后kset是kobjec的集合,还会处理热拔插。bus_register函数中创建了kset,创建了driver和device。
int kset_register(struct kset *k)
{
	int err;

	if (!k)
		return -EINVAL;

	kset_init(k);
	err = kobject_add_internal(&k->kobj);
	if (err)
		return err;
	kobject_uevent(&k->kobj, KOBJ_ADD);
	return 0;
}

kset_create_and_add里面会先kset_create也就是填充结构体,然后kset_register。如下就调用了热插拔的处理函数kobject_uevent。

int kset_register(struct kset *k)
{
	int err;

	if (!k)
		return -EINVAL;

	kset_init(k);
	err = kobject_add_internal(&k->kobj);
	if (err)
		return err;
	kobject_uevent(&k->kobj, KOBJ_ADD);
	return 0;
}

有如下各种event。搜索了下kobject_uevent在device_add,device_del等时候也会调用。

enum kobject_action {
	KOBJ_ADD,
	KOBJ_REMOVE,
	KOBJ_CHANGE,
	KOBJ_MOVE,
	KOBJ_ONLINE,
	KOBJ_OFFLINE,
	KOBJ_BIND,
	KOBJ_UNBIND,
};

三,关系图

针对match主要结构体我画了一个类图,platform的driver和device都是继承了driver和device,这也就是面向对象的设计方法。

linux驱动模型--Apple的学习笔记_设备驱动

linux驱动模型--Apple的学习笔记_设备驱动_02

四,小结

复习的时候我就突然觉得,其实没必要再看代码分析了,这些代码都不变的,早有人分析过,直接看结果是最快的,我这样花费时间主要目的是学习分析方法,直接拿来的结果和自己思考为什么推导出来的结果,过程完全是不同的,所以理解程度也会不同,复习效果也是不同的。我还是喜欢靠自己思考~



【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月19日 0

暂无评论

推荐阅读
2Nv1H5BMjysw