-
前言
前面讨论已经知道, kset, kobject相当于c++中的基类,会有上层容器去继承他们,c语言里面的继承也就是包含其数据结构的意思,而bus(总线), device(设备),驱动(驱动), 他们三个壮汉继承了他们,从而引出了设备模型的高级部分,这里我们要搞明白以下几点
1.bus, device, driver的数据结构
2.三者间的关系
3.device与driver间的绑定
开搞! -
1.bus, device, driver的数据结构
-
1.1感性的认识什么是bus,device, driver
bus,即总线, 根据<<设备模型一>>文章内容叙述,总线可以是真实总线的抽象,如i2c,也可以是非真实 的总线,为了一致统一, 非真实统一也要跟真实总线一样被抽象,即虚拟总线
即 i2c(真实总线)---->抽象—>i2c_bus_type
虚拟总线—>抽象—>platform
所谓抽象,意思就是说把真实的总线(或虚拟总线)描述成一个数据结构
对于加入系统中的硬件或者驱动(写代码时会把硬件或者驱动所依赖的总线提前写好), 都会挂靠到总线上
那什么是挂靠到总线上呢?说白了就是建立三者之间的联系,比如device的数据结构中有一个指针指向bus跟driver,同理driver与bus也是一样的 -
1.2 sysfs上的体现
-
1.2.1在谈sysfs
sysfs文件系统, 根据前面文章所讲的,体现给用户bus,device,driver的关系层(一目了然)
大家都知道kset, kobject这些组成上层容器(bus, device, driver)的这些基类,他们的存在就是为了使这三者之间建立联系,并且通过sysfs向用户反映他们之间的联系,打个比方比如我插入Upan,内核检测到U盘插入后会调用device_add来添加设备,这里device_add会调用kobj_add会建立关系,所以你看。。 -
1.2.2 bus的由来
内核初始化的时候:
start_kernel–>kernel_init(启动的一个内核线程)–>do_basic_setup–>driver_init
在driver_init中:
devices_init();
buses_init();
classes_init();
好的,上面就是这三座大山的初始化, 注意,这里还有个class_init,关于class的讲解,在下一篇文章会讨论
这里简单看一下devices_init()
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;
这里不做过多解释,如果前面的文章都看了的话,相信这里的代码还是比较简单的
对于kset_create_and_add函数的作用前面有讲解:
三个init函数执行完之后,sysfs上的关系如下图所示:
摁,设备模型的骨架子基本上是出来了,之后我们看一下bus的数据结构
- 1.2.3 bus_type
上面讲到,在linux中真实总线与虚拟总线会被抽象出数据结构,即bus_type,下图会讲述一下bus_type中比较重要的参数
这里说一下bus_type中的match函数,对于每一个总线对应的match函数不同,比较简单的是platform总线
platform_bus_init–>bus_register(&platform_bus_type);
其中platform_bus_type
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match, //总线的match函数
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
platform_match函数如下
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0); //这里比较dev与drv的名字是否相等
}
上图中,重要的数据结构以及其作用已经很好的表达出来,接下来讨论下注册一个总线时都发生了什么
其实注册一个总线就是形成创建一个上图的关系网
- 1.2.4注册一个总线(虽然现在没有注册总线的机会,但是有必要小研究一下)→bus_register
我们采用这个方式,从代码中表示生成的步骤,之后用图来描述代码的步骤
bus_register(&platform_bus_type):
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); //1.malloc ksubsys_private结构
if (!priv)
return -ENOMEM;
priv->bus = bus; //2.确立关系
bus->p = priv; //2.与bus确立暧昧关系
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);//3.设置obj名字之后会在sysfs显示,因为sysfs显示的是obj的名字,所以用的bus->name,如上面显示, bus->name为platform
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;//4.确立与/sys/bus的关系
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;//5.为了加设备后自动添加驱动用
retval = kset_register(&priv->subsys); //6.前面的准备做好后,注册近kobj机制中
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);//7.在/sys/bus/platform生成文件 uevent
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj); //8.创建devices
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);//9.创建 drivers
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);//10.创建drivers_probe, drivers_autoprobe文件
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(bus);//11.如果bus中还定义了其他的属性文件,则创建
if (retval)
goto bus_attrs_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
bus_attrs_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
10来个步骤,还是比较清晰,下面跟一个图。。。(本人喜欢用图来说明问题,理清思路)
好了,对于bus的注册就讲完了, 下面开讲device_add这个函数,即,往总线添加一个设备都干嘛了,对于加入一个驱动的话同加入设备大同小异了,就不用再细说,老规矩,先分析代码,后根据上面的图进行延伸
- 1.2.5注册设备(device_add)
当然不同的平台对应不同的device结构,但是里面都会嵌入一个struct device结构
这里我们以platform_device为例子,给出数据结构,呜哈哈
上图中 dev结构体里面的platform_data是特殊针对,下面我们看一下如何注册一个platform device
static struct platform_device leds_gpio = {
.name = "xxx_platDev_ma", //定义设备名字
.id = -1, //id初始化
.dev = {
.platform_data = NULL,//啥都不说了,dev的一个变量
},
};
调用函数platform_device_register(&leds_gpio);
接下来,厉害了!重点介绍一下platform注册都发生了什么,想想都让人兴奋,GO!
当然,不能涉及到所有的细节,我们还是强调脉络的分析
大致的脉络已经体现出来了,至于其他的什么链接文件什么的,个人认为不是什么重点,所以在下面会从现象分析,东西实在太多,接下来我们还是跟据图还说明问题,形象的从图的角度看看都发生了什么,首先,来一个简单的例子
static struct platform_device leds_gpio = {
.name = "xx_platDev_ma",
.id = -1,
.dev = {
.platform_data = NULL
},
};
struct class *ma_class = NULL;
struct device *dev1;
struct device *dev2;
struct device *dev3;
struct device *dev4;
static int ma_class_init(void)
{
int err;
printk(KERN_ALERT "class_init\n");
err = platform_device_register(&leds_gpio); //非常简单,就是注册一个名字为xx_platDev_ma的platform设备
return 0;
}
static void ma_class_exit(void)
{
printk(KERN_ALERT "Goodbye, class_init\n");
platform_device_unregister(&leds_gpio);
}
module_init(ma_class_init);
module_exit(ma_class_exit);
我们结合上面的代码逻辑,给出小例子的图解分析
我们将例子insmod后,出现
我们看到/sys下的结构,跟上面分析的一致,摁。。基本上加入一个设备的分析到位了
还差个驱动的绑定,还的继续努力!
1.2.6驱动的绑定,关于这个方面,留个悬念,这里不想说的太多,老生长谈,大家有兴趣可以看下代码,对应的接口函数是bus_probe_device
在上面的1.2.3 中的图已经很清晰的介绍了绑定的流程顺序