一,前言
既然是复习设备驱动,第一步当然是做一个最简单的基于设备树的驱动applechar,然后insmod和rmmod使用下,接着要回忆下driver和device是怎么match的,且把相关结构体复习下。
看了下结构体发现有点忘记了,另外match的函数也忘记了。有些东西不需要死记硬背,通过代码分析的方法论找到它即可。就是在我自己的驱动的probe函数打断点,看谁调用的,不是就都找到了嘛!
二,源码分析
- 首先回忆到的设备驱动模型是在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的时候遍历到的。
- 接下来分析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
接着思考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;
...
}
- 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,这也就是面向对象的设计方法。
四,小结
复习的时候我就突然觉得,其实没必要再看代码分析了,这些代码都不变的,早有人分析过,直接看结果是最快的,我这样花费时间主要目的是学习分析方法,直接拿来的结果和自己思考为什么推导出来的结果,过程完全是不同的,所以理解程度也会不同,复习效果也是不同的。我还是喜欢靠自己思考~
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 妙用Python集合求解啤酒问题(携程2016笔试题)
问题描述:一位酒商共有5桶葡萄酒和1桶啤酒,6个桶的容量分别为30升、32升、36升、38升、40升和62升,并且只卖整桶酒,不零卖。第一位顾客买走了2整桶葡萄酒,第二位顾客买走的葡萄酒是第一位顾客的2倍。那么,本来有多少升啤酒呢? 解析:由于该酒商只卖整桶酒…