兼顾工作和自身学习的需要,想尽可能多的弄懂 linux内核驱动的“四大天王”—— BUS、 DEVICE、 DEVICE-DRIVER、 CLASS。通过几天跟踪代码、参考牛人的一些 BLOG和自己 DEMO一些例子之后,才发现确实有难度。正如某人说的“懂八成,理解五成,融会贯通三成,能用出来就只剩一成了,而就这一成要往死里用才行”,何况,我更没有弄懂八成,要写成文档更让我迷茫了。没办法,反反复复地看,反反复复地试,反反复复地想。但效果还是不明显,但 Deadline又近了,只能窃取牛人的成果,加入自己的一些理解,写写这几天多所看的,所想的。权宜当个抛砖引玉的作用,如果有什么错误和纰漏,欢迎大家指正。
I、目标:
到底要达到怎样还真不好说,其实我自己现在也很混沌,那就一起开始学习吧,至少,完成这个系列之后,应该要知道“ Driver和 Device是怎样匹配的?”、“ Bus是不是 Device?”、“一个 Driver只匹配一个 Device还是多个?”、“在系统中,是先加 Device还是先加 Driver还是没要求?”等等。
II、规划:
驱动模型应该讲Kobject/Kset。Kobject/Kset相当于是LinuxKernel对所有设备驱动的抽象类,是基类。Linux Kernle的设备驱动大概有Bus、 Device、 Device-driver,这个东东是紧贴Kobject/Kset这一层,那么就基于这个实现自己的bus/device/device_driver,来理解Kobject/Kset。
1 什么是BUS
BUS是主机和外设的连接通道,也可以说是“红娘”,因为它提供了 Device和 Driver找对象匹配的场所。有些总线是比较规范的,形成了很多协议。如 PCI,USB,IIC等。我们也在内核添加自己定义的总线。任何设备都可以选择合适的总线连接到主机。
如下图,查看 /sys/目录下,有一个 Bus的文件夹,没错,我们就是要这个 Bus。
再进去Bus看看,
看到没有,有我们非常熟悉的 i2c总线。其实其它也是总线,分别用于接通不同的外设。也包括“ platform”这条虚拟的平台总线。
2 我从哪里来
跟踪代码,我们先找出 bus的定义
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct bus_type_private *p;
};
还就是Bus一些重要的方法:
int bus_register(struct bus_type *bus)
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
struct bus_type也有几个很重要的函数指针,比如
int (*match)(struct device *dev, struct device_driver *drv);
int (*probe)(struct device *dev);
注册一条总线就是实现这个 bus_type的结构体,好,查找一下 i2c总线是怎样实现的。
跟踪代码,果然找到了这个:
.name = "i2c"就说明在/sys/bus/ 下有一个i2c的目录,同理,反过来,我要找 /sys/bus/hid对应的 bus_type,那么一定有个 struct bus_type hid_bus_type,
找找看,
果然,其它的也一样。
再深入一点,接下来分析一下 int bus_register(struct bus_type *bus)这个函数,为什么通过使用它能在 /sys/bus/目录下建一个相关的文件夹。
先给出原型如下
int bus_register(struct bus_type *bus)
{
int retval;
struct bus_type_private *priv;
priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
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);
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(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);
kfree(bus->p);
out:
bus->p = NULL;
return retval;
}
太长了,唉,看不下去,避重就轻地看一些我们关心的。简化之后就这样。
int bus_register(struct bus_type *bus)
{
retval = kset_register(&priv->subsys);
retval = bus_create_file(bus, &bus_attr_uevent);
priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);
priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);
}
先看kset_register()这个方法,跟踪代码可以发现会调用 kobject_add_internal()这个函数,如下图
继续跟进代码,进去看看 kobject_add_internal()这个函数做了什么事情。其中里面有个这个东西
,不用进去看做了什么事,看一下函数名大概就知道是创建一个目录。好吧就这样,它会在 sysfs/里面创建相应的文件夹。具体怎么创建,怎么组织这些文件和文件夹那就是设备模型的精髓 Kobject、 Kset、 Ktype。里面有很多东西,回头认真研读一下光哥关于设备模型这个文档。
好,回到bus_register()这个函数,接下来
bus_create_file(bus, &bus_attr_uevent);
kset_create_and_add("devices", NULL, &priv->subsys.kobj);
kset_create_and_add("drivers", NULL, &priv->subsys.kobj);
这三个函数是在你定义的 Bus主文件夹下创建一些 Kset和属性文件,这个不详细说,在下面的例子再展开分析,
一条Bus上会有很多设备,也会有很多驱动。暂且这样说吧, Bus就是《非诚勿扰》栏目,它为多个男( Driver)女( Device)提供约会配对的场所。多个“男” “女”是怎样放在一起的呢?没错,就是下面这两个函数 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put) 和klist_init(&priv->klist_drivers, NULL, NULL); 通过两个内核链表,把它们组织起来的。
还要说的就是那个 match()方法。也就是说《非诚勿扰》在怎样在孟非老师主持下进行配对的。具体怎样配对,可以看看 i2c BUS是怎样实现的。
继续深入,看看这个函数到是怎样进行 id match的。
没错,是通过比较 name这个字符串来匹配的。
写一个小例子来增强一下我们的理解,我们要在 /sys/bus/下创建一条 BUS,暂且命名为 kf-bus,它将是和i2c在同一个目录里的 BUS。
>>>kf_bus.c文件如下
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>
static int kf_bus_match(struct device *dev, struct device_driver *drv)
{
return 0;
}
static struct bus_type kf_bus_type = {
.name = "kf-bus",
.match = kf_bus_match,
};
static int __init kf_bus_init()
{
printk(">>>kf_bus_init successed!!\n");
return bus_register(&kf_bus_type);
}
static void __exit kf_bus_exit()
{
printk(">>>kf_bus_exit successed!!\n");
bus_unregister(&kf_bus_type);
}
module_init(kf_bus_init);
module_exit(kf_bus_exit);
MODULE_AUTHOR("Koffuxu");
MODULE_LICENSE("GPL");
>>>Makefile文件如下,
obj-m += kf_bus.o
KERNELDIR = ../../../kernel_rk29sdk2
PWD := $(shell pwd)
all:
make -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o* *.ko* *.mod.c* .cmd *.symvers .tmp_version.*.cmd
放到合适的目录, make一下,就会在当面目录生成一个 kf_bus.ko, copy到 MID上,
1 检查下内核已有的模块
2 加载我们的模块
通过lsmod我们已经成功加载自己的 "kf_bus"。
接下来我们去 /sys/bus/下面去看看是否有猜想的 kf_bus文件夹。
果然,得瑟一下,“ so easy, 妈妈再也不用担心我的学习了”。好了,我们再进去看看里有什么东东。
怎么多出来这么多东西?接下来,我们就来一一找出来这五个文件(目录也是文件)是怎样产生的。
回头看看我们的代码,首先定义了一个 bus_type的结构体。名字就是 "kf-bus",按照我们之前的分析,会在 /sys/bus/下产生一个 kf-bus的文件夹,这个我们已经验证,接下来,我们调用了 bus_register()这个函数,玄机就是只在这个函数里面。其实这个函数我们之前也有初步分析了一下,我们简化为这样:
int bus_register(struct bus_type *bus)
{
retval = kset_register(&priv->subsys);
retval = bus_create_file(bus, &bus_attr_uevent);
priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);
priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);
}
kset_register()上文已经讲过,接下来就是
这个函数,进去看看,这个函数调用了
,唉,没办法,基础差,只能望文生义,就是在 sys文件系统里建一个属性文件,不妨进去看
从注释也能看出来,是这样的。那么
就是在自己定义的bus下,那一个 bus_aat_uevent这个属性文件,没错就是对应我们要分析的五个文件的第一个文件 uevent,它是与 bus_attr通过一个连接符号组成了。
好第一个已经出来了,继续看代码: priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);这个函数貌似是个/sys/bus/kf-bus/devices/这个文件夹相关,空说无凭,进去代码看看。
这个函数首先调用 kset_create()创建一个name的kset,再把这个 kset传给 kse_register(),注册进内核。是不是看到老朋友,这个函数我们上面分析过,它最后会调用create_dir()去创建一个目录。所以就有了我们devices目录。同理, drivers文件夹是由 priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj)这个函数产生的。接下来就是 drivers_probe和 drivers_autoprobe两个文件了,我们一定能找得出来的,果然, add_probe_files(bus);进去这个函数
就两个我们很熟悉的方法 bus_create_fle(),这里产生了最后的两个文件了。