Linux设备驱动编程模型之设备篇(二)
二、原理与源码解析
解决问题我还是习惯追根溯源,我们从内核启动对设备驱动模型初始化开始看起,设备驱动中sys/文件系统的初始化工作为start_kernel()->rest_init()->
1.static noinline void __init_refok rest_init(void)
2. __releases(kernel_lock)
3.{
4. ……
5. /*创建并启动内核线程kernel_init用于初始化一些
6. 内核部件工作*/
7. kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
8. ……
9.}
内核线程kernel_init()用于驱动的初始化
kernel_init()->do_basic_setup()->driver_init()
1./**
2. * driver_init - initialize driver model.
3. *
4. * Call the driver model init functions to initialize their
5. * subsystems. Called early from init/main.c.
6. */
7. /*在内核初始化时的线程中调用*/
8.void __init driver_init(void)
9.{
10. /* These are the core pieces */
11. /*devtmpfs 的功用是在 Linux 核心 启动早期建立一个初步的 /dev,
12. 令一般启动程序不用等待 udev,缩短 GNU/Linux 的开机时间。
13. devtmpfs 在 2009 年初被提出,并在同年年尾进出的 Linux 2.6.32 正式收录
14. */
15. devtmpfs_init();
16. /*设备相关初始化*/
17. devices_init();
18. /*bus初始化*/
19. buses_init();
20. /*class初始化*/
21. classes_init();
22. /*firmware初始化*/
23. firmware_init();
24. /*虚拟化平台hypervisor初始化。虚拟化 就是通过某种方式隐藏底层物理硬件
25. 的过程,从而让多个操作系统可以透明地使用和共享它。这种
26. 架构的另一个更常见的名称是平台虚拟化。在典型的分层架构
27. 中,提供平台虚拟化的层称为 hypervisor (有时称为虚拟机管理程
28. 序 或 VMM)。来宾操作系统称为虚拟机(VM),因为对这些 VM
29. 而言,硬件是专门针对它们虚拟化的。
30. */
31. hypervisor_init();
32.
33. /* These are also core pieces, but must come after the
34. * core core pieces.
35. */
36. platform_bus_init();
37. /*初始化system_bus,即在sys/devices目录下创建system目录*/
38. system_bus_init();
39. /*cpu_dev初始化*/
40. cpu_dev_init();
41. /*在/sys/devices/system目录下创建memory目录文件
42. 该函数需要定义的相关宏在本机器上没有定义
43. */
44. memory_dev_init();
45.}
1./*在sys文件系统中建立几个文件夹*/
2.int __init devices_init(void)
3.{
4. /*根目录sys下创建devices文件夹*/
5. devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
6. if (!devices_kset)
7. return -ENOMEM;
8. /*根目录下创建dev文件夹*/
9. dev_kobj = kobject_create_and_add("dev", NULL);
10. if (!dev_kobj)
11. goto dev_kobj_err;
12. /*在dev文件夹下创建block文件夹*/
13. sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
14. if (!sysfs_dev_block_kobj)
15. goto block_kobj_err;
16. /*在dev文件夹下创建char文件夹*/
17. sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
18. if (!sysfs_dev_char_kobj)
19. goto char_kobj_err;
20.
21. return 0;
22.
23. char_kobj_err:
24. kobject_put(sysfs_dev_block_kobj);
25. block_kobj_err:
26. kobject_put(dev_kobj);
27. dev_kobj_err:
28. kset_unregister(devices_kset);
29. return -ENOMEM;
30.}
上面代码执行后生成的目录如下图
/sys/下的dev/和devices/目录
/sys/dev/下的block/和char/目录
总线初始化
1.int __init buses_init(void)
2.{
3. /*在sys文件下创建bus文件夹*/
4. bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
5. if (!bus_kset)
6. return -ENOMEM;
7. return 0;
8.}
运行结果图,在/sys/目录下生成/bus/目录
Class初始化
1.int __init classes_init(void)
2.{
3. /*在sys文件夹下创建class文件夹*/
4. class_kset = kset_create_and_add("class", NULL, NULL);
5. if (!class_kset)
6. return -ENOMEM;
7. return 0;
8.}
运行结果,在/sys/目录下生成/class/目录
Firmware初始化
1.int __init firmware_init(void)
2.{
3. /*在sys文件夹下创建firmware文件夹*/
4. firmware_kobj = kobject_create_and_add("firmware", NULL);
5. if (!firmware_kobj)
6. return -ENOMEM;
7. return 0;
8.}
运行结果,在/sys/目录下生成/firmware/目录
Hypervisor初始化,该功能需要内核编译选项支持,该函数需要定义CONFIG_SYS_HYPERVISOR宏,在我机器上没有编译此功能。
Platform_bus初始化,下面会详细讲解
注册platform_bus设备 platform_bus_init()->device_register()
设备初始化
添加设备到设备树中,我们结合实际的实现结果分析 int device_add(struct device *dev) { struct device *parent = NULL; struct class_interface *class_intf;/*class接口*/ int error = -EINVAL; dev = get_device(dev);/*增加dev中kobject的引用计数*/ if (!dev) goto done; if (!dev->p) {/*如果设备对象的私有对象不存在,分配并初始化*/ error = device_private_init(dev); if (error) goto done; } /* * for statically allocated devices, which should all be converted * some day, we need to initialize the name. We prevent reading back * the name, and force the use of dev_name() */ if (dev->init_name) {/*将名称保存到kobj中*/ dev_set_name(dev, "%s", dev->init_name); dev->init_name = NULL; } if (!dev_name(dev))/*从kobj中获取上面设置的名称*/ goto name_error; pr_debug("device: '%s': %sn", dev_name(dev), __func__); /*得到设备的父设备,并增加其引用计数*/ parent = get_device(dev->parent); /*设置设备的obj对象树,下面详细讲*/ setup_parent(dev, parent); /* use parent numa_node */ if (parent) set_dev_node(dev, dev_to_node(parent)); /* first, register with generic layer. */ /* we require the name to be set before, and pass NULL */ /*将obj对象添加到对象树中,对于这里的意思即platform添加到sys目录下*/ error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); 实现效果如下图中的/sys/devices/目录下生成platform文件夹:
if (error) goto Error; /* notify platform of device entry */ if (platform_notify) platform_notify(dev); /*为文件创建dient对象(其实就是属性),并加入到dient对象树中*/ /*在对应设备下面创建uevent文件*/ error = device_create_file(dev, &uevent_attr); 如下图,在/sys/devices/platform下生成uevent文件 if (error) goto attrError; /*如果存在主设备号,在我们的情形中没有主设备号*/ if (MAJOR(dev->devt)) { /*创建dient对象,用指定的属性,即生成一个dev文件*/ error = device_create_file(dev, &devt_attr); 如果有主设备号,即在dev->kobj下生成dev文件,例如,/sys/devices/virtual/mem/random具有的主设备号为1,次设备号为8,在/sys/devices/virtual/mem/random目录下生成dev文件: 其中dev文件为文本文件,保存内容为major:minor,例子中为1:8:
if (error) goto ueventattrError; /*创建/dev符号链接(就是在dient树种创建符号链接树)*/ /*根据设备类型,在sys/dev/char或block或其属性指定的dev_obj目录下生成链接文件,其名字的样式为major:minor,如1:8,指向dev主目录*/ error = device_create_sys_dev_entry(dev);
继续上面的例子,在这里为在/sys/dev/char/下生成1:8/文件夹链接到/sys/devices/virtual/mem/random目录: if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
/*添加链接文件,dev的class存在时有效,在后面详细讲*/
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
/*实际负责device中的属性添加。也是几个部分的集合,
包括class中的dev_attrs,device_type中的groups,还有device本
身的groups。*/
error = device_add_attrs(dev);
if (error)
goto AttrsError;
/*add device to bus*/
/*添加device到bus,建立bus和device之间的联系,后面详细将*/
error = bus_add_device(dev);
if (error)
goto BusError;
/*在dev->kobj目录下生成power目录,用于电源管理*/
error = dpm_sysfs_add(dev);
在dev->kobj下会生成一个power文件夹用于电源的管理,例如我们这里会在platform下
if (error)
goto DPMError;
/*添加设备到PM链表*/
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysf_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
/*将驱动与设备关联,在后面详细讲解*/
bus_probe_device(dev);
/*下面为添加到系统中的各种链表中*/
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->class_mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->class_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->class_interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->class_mutex);
}
……
}
下面为一些辅助函数的实现 1.int device_private_init(struct device *dev) 2.{ 3. /*从cache中分配private的空间*/ 4. dev->p = kzalloc(sizeof(*dev->p), GFP_KERNEL); 5. if (!dev->p) 6. return -ENOMEM; 7. dev->p->device = dev;/*设置dev private属性的device为自身*/ 8. /*初始化klist_children属性,后面传入的两个参数为 9. get和put函数的实现,分别为对引用计数的增加和 10. 减少*/ 11. klist_init(&dev->p->klist_children, klist_children_get, 12. klist_children_put); 13. return 0; 14.} 1.static struct kobject *get_device_parent(struct device *dev, 2. struct device *parent) 3.{ 4. int retval; 5. 6. if (dev->class) {/*如果device存在class*/ 7. struct kobject *kobj = NULL; 8. struct kobject *parent_kobj; 9. struct kobject *k; 10. 11. /* 12. * If we have no parent, we live in "virtual". 13. * Class-devices with a non class-device as parent, live 14. * in a "glue" directory to prevent namespace collisions. 15. */ 16. if (parent == NULL)/*如果没有父设备,创建一个 17. 'virtual'的kobj,这里可以看出,当没有父设备 18. 时,class设备可以用'virtual'(即非class设备) 19. 作为父节点kobj的树 20. 而非class设备不行 21. */ 22. parent_kobj = virtual_device_parent(dev); 23. else if (parent->class)/*当父设备也是class设备时,直接返回其kobj*/ 24. return &parent->kobj; 25. else/*父设备存在,但是不是class设备*/ 26. parent_kobj = &parent->kobj; 27. 28. /* find our class-directory at the parent and reference it */ 29. spin_lock(&dev->class->p->class_dirs.list_lock); 30. list_for_each_entry(k, &dev->class->p->class_dirs.list, entry) 31. if (k->parent == parent_kobj) {/*遍历同类中的kobj,找到符合的*/ 32. kobj = kobject_get(k);/*递增其引用计数*/ 33. break; 34. } 35. spin_unlock(&dev->class->p->class_dirs.list_lock); 36. if (kobj) 37. return kobj; 38. 39. /* or create a new class-directory at the parent device */ 40. /*程序运行到这里表示没有在dev->class中找到 41. k->parent==parent_kobj的kobj,所以创建他*/ 42. k = kobject_create(); 43. if (!k) 44. return NULL; 45. /*初始化创建kobj的kset为dev中class->p->class_dirs*/ 46. k->kset = &dev->class->p->class_dirs; 47. /*添加kobj到其parent的kobj树中,parent字段设置为parent_kobj*/ 48. retval = kobject_add(k, parent_kobj, "%s", dev->class->name); 49. if (retval < 0) { 50. kobject_put(k); 51. return NULL; 52. } 53. /* do not emit an uevent for this simple "glue" directory */ 54. return k; 55. } 56. 57. if (parent)/*如果parent存在,返回父设备的kobj*/ 58. return &parent->kobj; 59. return NULL; 60.} 1.static struct kobject *virtual_device_parent(struct device *dev) 2.{ 3. static struct kobject *virtual_dir = NULL; 4. 5. if (!virtual_dir)/*创建名为virtual的kobj加入到设备的集合树中*/ 6. virtual_dir = kobject_create_and_add("virtual", 7. &devices_kset->kobj); 8. /*返回创建的kobj*/ 9. return virtual_dir; 10.} 我们看看具体在Linux下的相关目录: 设备与class关联
static int device_add_class_symlinks(struct device *dev)
{
int error;
/*没有class的情况直接返回*/
if (!dev->class)
return 0;
/*在dev->kobj目录下生成一个名为'subsystem'的链接文件,指向其所属的class的sys 的目录/sys/class/***,例如,接着上面的例子
*/
error = sysfs_create_link(&dev->kobj,
&dev->class->p->class_subsys.kobj,
"subsystem");
生成下图: ……
}
device关联bus
int bus_add_device(struct device *dev)
{
struct bus_type *bus = bus_get(dev->bus);
int error = 0;
/*如果bus存在,才执行下面部分*/
if (bus) {
pr_debug("bus: '%s': add device %sn", bus->name, dev_name(dev));
/*添加属性文件,该属性文件问bus的属性*/
error = device_add_attrs(bus, dev);
if (error)
goto out_put;
/*在bus的devices目录下生成到dev->kobj的链接,名称为dev->kobj的名称*/
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
例如,我们看/sys/devices/platform/devices/下的serial8250,为链接到/sys/devices/platform/serial8250 if (error)
goto out_id;
/*dev->kobj目录下subsystem为生成的到bus 下相关
目录的链接*/
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
我们看是考虑前面的serial8250文件,可见其subsystem目录链接到/sys/bus/platform/ if (error)
goto out_subsys;
/*dev->kobj目录下的bus生成到bus主目录的链接。
在我机器上没有定义相关的宏*/
error = make_deprecated_bus_links(dev);
if (error)
goto out_deprecated;
/*添加bus的设备链表到dev下的bus链表上*/
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0;
……
}
设备与驱动关联: 1./** 2. * bus_probe_device - probe drivers for a new device 3. * @dev: device to probe 4. * 5. * - Automatically probe for a driver if the bus allows it. 6. */ 7./*在设备关联的bus存在并且该bus支持驱动动态探测的情况下才有效*/ 8.void bus_probe_device(struct device *dev) 9.{ 10. struct bus_type *bus = dev->bus; 11. int ret; 12. 13. if (bus && bus->p->drivers_autoprobe) { 14. ret = device_attach(dev);/*关联驱动*/ 15. WARN_ON(ret < 0); 16. } 17.} Device_attach()函数最终调用driver_sysfs_add()函数实现实际工作
static int driver_sysfs_add(struct device *dev)
{
int ret;
/*驱动目录下dev->kobj目录链接到dev->kobj*/
ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
kobject_name(&dev->kobj));
我门仍然以serial8250为例子来说明,生成的链接如下图: if (ret == 0) {
/*在dev->kobj目录下的driver目录链接到其驱动目录*/
ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
"driver");
生成链接结果如下图所示: if (ret)
sysfs_remove_link(&dev->driver->p->kobj,
kobject_name(&dev->kobj));
}
return ret;
}
注册bus 1.struct bus_type platform_bus_type = { 2. .name = "platform",/*生成的为/sys/bus/platform文件夹*/ 3. .dev_attrs = platform_dev_attrs, 4. .match = platform_match, 5. .uevent = platform_uevent, 6. .pm = &platform_dev_pm_ops, 7.}; 1.int bus_register(struct bus_type *bus) 2.{ 3. int retval; 4. struct bus_type_private *priv; 5. /*为priv申请内存空间*/ 6. priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); 7. if (!priv) 8. return -ENOMEM; 9. /*添加与bus之间的联系*/ 10. priv->bus = bus; 11. bus->p = priv; 12. 13. BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); 14. /*设置priv的subsys的名称为bus的名字*/ 15. retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); 16. if (retval) 17. goto out; 18. /*设置kset 与ktype,同设备,这里的parent为kset的kobj属性*/ 19. priv->subsys.kobj.kset = bus_kset; 20. priv->subsys.kobj.ktype = &bus_ktype; 21. priv->drivers_autoprobe = 1; 22. /*注册对应的kset,将其添加到sd树中*/ 23. retval = kset_register(&priv->subsys); 24. if (retval) 25. goto out; 26. /*创建属性文件uevent*/ 27. retval = bus_create_file(bus, &bus_attr_uevent); 28. if (retval) 29. goto bus_uevent_fail; 30. /*在priv->subsys.kobj目录下生成devices目录*/ 31. priv->devices_kset = kset_create_and_add("devices", NULL, 32. &priv->subsys.kobj); 33. if (!priv->devices_kset) { 34. retval = -ENOMEM; 35. goto bus_devices_fail; 36. } 37. /*在priv->subsys.kobj目录下生成drivers文件目录*/ 38. priv->drivers_kset = kset_create_and_add("drivers", NULL, 39. &priv->subsys.kobj); 40. if (!priv->drivers_kset) { 41. retval = -ENOMEM; 42. goto bus_drivers_fail; 43. } 44. /*初始化priv的相关链接*/ 45. klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); 46. klist_init(&priv->klist_drivers, NULL, NULL); 47. /*在bus的目录下生成两个属性文件 48. drivers_probe和drivers_autoprobe*/ 49. retval = add_probe_files(bus); 50. if (retval) 51. goto bus_probe_files_fail; 52. /*如果该bus还有其他的属性,创建其对应的文件*/ 53. retval = bus_add_attrs(bus); 54. if (retval) 55. goto bus_attrs_fail; 56. 57. pr_debug("bus: '%s': registeredn", bus->name); 58. return 0; 59. 60.bus_attrs_fail: 61. remove_probe_files(bus); 62.bus_probe_files_fail: 63. kset_unregister(bus->p->drivers_kset); 64.bus_drivers_fail: 65. kset_unregister(bus->p->devices_kset); 66.bus_devices_fail: 67. bus_remove_file(bus, &bus_attr_uevent); 68.bus_uevent_fail: 69. kset_unregister(&bus->p->subsys); 70. kfree(bus->p); 71.out: 72. bus->p = NULL; 73. return retval; 74.} 实现结果如下图,在/sys/bus/platform目录下生成drivers和devices两个文件夹,uevent、drivers_probe、drivers_autoprobe三个属性
初始化system_bus,实现结果为在/sys/devices/下创建system文件夹 1.int __init system_bus_init(void) 2.{ 3. system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj); 4. if (!system_kset) 5. return -ENOMEM; 6. return 0; 7.} cpu_dev初始化 1.int __init cpu_dev_init(void) 2.{ 3. int err; 4. /*注册cpu,即在/sys/devices/system目录下创建cpu目录文件*/ 5. err = sysdev_class_register(&cpu_sysdev_class); 6. if (!err) 7. err = cpu_states_init(); 8. 9.#if defined(CONFIG_SCHED_MC) || defined(CONFIG_SCHED_SMT) 10. if (!err) 11. err = sched_create_sysfs_power_savings_entries(&cpu_sysdev_class); 12.#endif 13. 14. return err; 15.} 实现结果如下图,在/sys/devices/system下生成cpu文件夹 分析了内核怎样对设备驱动模型初始化后对驱动模型的理解应该有一个宏观的概念了,涉及到实际驱动的编写,每种设备有各自的一套机制,但原理上都大同小异
|