2.1 Linux驱动设备模型

非markdown版本

Linux设备模型大概是最难的linux知识点之一了。晦涩难懂的kobject、kset概念,云里雾里的sys文件系统层次,搞得你不知所云。下面我们就尝试对这块硬骨头进行剖析。

本文使用的是linux-2.6.16.54内核,最新内核中的细节有些改变请注意。

1、Linux驱动设备模型的实现

Linux设备模型需要完成的任务:

  • 1、现在的linux系统中充斥着数量众多的设备,你需要使用一种组织方法将其管理起来。linux设备模型使用树形的组织结构来解决;
  • 2、linux设备树形的组织结构需要呈现给用户,并和用户交互。linux设备模型使用sysfs文件系统来解决。Sysfs文件系统中的文件夹和文件对应不同的设备对象,文件层次结构对应设备模型中的树形结构,对文件的操作对应对设备对象的操作。
  • 3、而在完成不同的任务时,我们又需要以不同的视角去组织。比如在完成电源管理任务时我们需要从总线的角度来遍历设备,在下电的时候,我们需要先下电总线上最外围的设备,再下电端口,再下电核心,上电顺序反之。而另一种情况下我们需要从功能的角度去划分设备,比如我们需要使用打印机,有usb打印机、并口打印机、网络打印机,这里又把这些不同总线的设备归为一类。另外还有驱动和设备、设备和驱动适配的关系。linux的同一设备需要在多种视角的树结构中呈现,linux设备模型使用符号链接来解决。

  • Linux设备模型还有一个重要的思想,就是类的思想。首先使用kobject、kset实现了树形结构的基类,然后在基类之上派生出了通用设备模型的类bus_type、device、device_driver、class,进一步在通用设备类的基础上派生出了pci的bus、device、driver,usb的bus、device、driver等等具体的设备模型。

1.1、第一层类kobject、kset、subsys

Kobject的功能最初是维护一个引用计数。后续给它加上了组织层次结构、sysfs表述、热插拔处理的功能。

1.1.1、kobject定义

这里写图片描述

kobject结构中,重要的数据成员有:

  • 1、引用计数kref;
  • 2、层次结构相关entry、kset、parent;
  • 3、属性和属性的操作方法ktype。

1.1.2、kset定义

这里写图片描述

kset结构中,重要的数据成员有:

  • 1、内嵌的kobject成员kobj;
  • 2、层次结构相关的subsys、list;
  • 3、kset自己的属性和操作函数ktype;
  • 4、kset的热插拔处理函数。

1.1.3、subsys定义

这里写图片描述

可以看到subsys结构中就是简单两个成员:kset和信号量rwsem。
Subsys的功能和kset基本一致,鉴于kset完全可以取代subsys,在最新的内核中已经取消subsystem类型了。

1.1.4、功能1:引用计数

kobject、kset的基本功能就是提供引用计数。
在使用对象之前,你需要调用get函数增加对象的引用计数:

这里写图片描述

在使用完对象之后,你需要调用put函数减少对象的引用计数。值得注意的是,如果对象检测到无人引用,会调用ktype->release函数释放对象:

这里写图片描述

1.1.5、功能2:构造层次结构

kobject、kset的另一个基本功能就是构造对象之间树形的层次结构。有两种基本的层次结构,kobject和kset的集合树形结构,kobject和kobject的父子树形结构。
具体的代码实现见kobject_register函数。

1.1.5.1、kobject和kset的集合结构

这里写图片描述

1.1.5.2、kobject和kobject的父子树形结构

这里写图片描述

1.1.6、功能3:sysfs表述

其实所谓的sysfs本质就是一个内存文件系统,可以create/remove文件和文件夹,可以open/close/read/write其中的文件。而不一定要和kobject、kset有什么关系。
但是我们创建sysfs的目的就是为了对kobject、kset进行用户表述,用户使用查看sysfs文件层次的方法可以看到内核kobject、kset对象的层次关系,用户对sysfs文件的read/write操作可以操作到kobject、kset对象的相关属性。
所以对sysfs文件系统的文件文件夹及操作和kobject、kset对象和操作建立起了对应关系:

这里写图片描述

在内核代码中,我们可以看到这种对应关系的具体实现。

1.1.6.1、sysfs dir/file

linuxsrc/lib/kobject.c:
在kobject、kset对象的注册函数kobject_register()、kset_register()中,会调用sysfs_create_dir()创建kobject、kset对应的sysfs文件夹,并且调用sysfs_create_file()在文件夹下创建kobject、kset属性对应的sysfs文件。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

在kobject、kset对象注销函数kobject_unregister()、kset_unregister()中,我们也会看到删除对应的sysfs文件夹和文件的动作。sysfs_remove_dir()。

1.1.6.2、sysfs file read/write

kobject、kset的普通属性对应sysfs的普通文件,使用sysfs_create_file()、sysfs_remove_file()函数创建和删除sysfs中的普通文件。普通文件提供小于一个PAGE_SIZE的内容处理。

这里写图片描述

在sysfs普通文件的read/write函数的实现中linuxsrc/fs/sysfs/file.c,可以看到read/write最终调用到了kobject、kset属性的show/store函数。

这里写图片描述
这里写图片描述
这里写图片描述

1.1.6.3、sysfs bin file read/write

kobject、kse中二进制处理需要用二进制属性,对应sysfs的二进制文件,使用sysfs_create_bin_file()、sysfs_remove_bin_file()函数创建和删除sysfs中的二进制文件。二进制属性的操作函数也不再使用ktype->sysfs_ops->show/store函数,而使用自己的read/write函数。

这里写图片描述

在sysfs二进制文件的read/write函数的实现中linuxsrc/fs/sysfs/bin.c,可以看到read/write最终调用到了kobject、kset二进制属性的read/write函数。
具体的过程类似于普通文件,可以自行查看源代码。

1.1.7、功能4:热插拔事件处理

在前面kset的结构定义中可以看到,kset中有一个uevent_ops成员来处理kobject和kset对象的热插拔事件。uevent_ops包含3个成员函数,filter、name、uevent。

这里写图片描述

在系统上报热插拔事件上报函数kobject_uevent()中,可以看到uevent_ops所起到的作用。linuxsrc/lib/kobject_uevent.c:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

1.2、第二层类bus_type、device、device_driver、class

根据第一层类kobject、kset、subsys提供的基本功能,在此之上派生出了linux设备模型的基本模型。基本分为bus_type、device、device_driver、class对应设备管理的不同元素和不同视角,以完成linux设备管理的不同任务。
这四种元素,在sysfs中的呈现就是/sys/bus 、/sys/devices/ 、/sys/bus/xxx/drivers/ 、/sys/class/路径下的各种文件和文件夹。

这里写图片描述

另外需要声明的是,kobject、kset及其对应的sysfs呈现的基类,并不是仅仅提供给设备模型使用的,内核的其他部分也可以使用sysfs。在/sys文件夹下,除去上述的文件夹,其他的文件就是设备管理以外的内核模块使用的。

1.2.1、bus_type

1.2.1.1、bus_type定义

31

1.2.1.2、bus根目录/sys/bus

linux系统在初始化的时候,调用buses_init函数,给bus_type类型的对象创建一个根节点“/sys/bus”。

32

1.2.1.3、bus_register()

可以调用bus_register()函数注册新的总线类型,其会在“/sys/bus”下面创建“/sys/bus/bus_name”、“/sys/bus/bus_name/devices”、“/sys/bus/bus_name/drivers”文件夹,以及 “/sys/bus/bus_name/bus_attr_file”文件。

33*2

1.2.1.4、bus sysfs层次结构

经过bus_register()函数注册以后,一种新类型bus在sysfs中的层次结构为:

34

1.2.1.5、bus sysfs表述

从上节的层次图中可以看到,bus_register()注册一个新的bus_type类型对象会在“/sys/bus”下面创建如下的sys文件:

  • “/sys/bus/bus_name”文件夹 —–> bus_type.subsys
  • “/sys/bus/bus_name/devices” 文件夹 —–> bus_type.devices
  • “/sys/bus/bus_name/drivers” 文件夹 —–> bus_type.drivers
  • “/sys/bus/bus_name/bus_attr_file”文件 —–> bus_type->bus_attrs
1.2.1.6、bus属性处理函数

bus_type类型对象的属性,即为“/sys/bus/bus_name/bus_attr_file”文件。因为“/sys/bus/bus_name”和“/sys/bus”是kset集合的树形关系,所以对“/sys/bus/bus_name”属性文件的操作会引用“/sys/bus/bus_name”->set的属性操作,即“/sys/bus”的属性操作函数。在buses_init时,注册的“/sys/bus”属性操作函数是bus_attr_show()、bus_attr_store()。
查看bus_attr_show()、bus_attr_store()的实现:

35

从kobject、kset的kobj_type定义中可以看到,kobject、kset属性可以是多个,但属性处理函数是同一个kobject->ktype->sysfs_ops。
看bus_attribute的结构定义,每个属性都有一个自己的属性处理函数。所以bus_attr_show()、bus_attr_store()的实际作用就是充当一个总入口,将每个属性的处理,交给各自属性自己的处理函数。

1.2.1.7、bus热插拔处理

bus_type 3个对象(“/sys/bus/bus_name”、“/sys/bus/bus_name/devices”、“/sys/bus/bus_name/drivers”)的注册都没有初始化热插拔处理函数kset->uevent_ops。
所以bus_type对象的热插拔,都没有自定义处理,使用的是系统默认的热插拔处理方法。

1.2.1.8、bus_type的klist_devices链表

总线上挂接的所有device设备的链表,在device设备注册时,会把设备加入所在总线的bus_type->klist_devices链表。

36

1.2.1.9、bus_type的klist_drivers链表

总线上挂接的所有device_driver驱动的链表,在device_driver驱动注册时,会把设备加入所在总线的bus_type->klist_drivers链表。

37

1.2.1.10、bus_type的bus_attrs属性

bus对象自己的bus相关属性,如果bus_type->bus_attrs存在的话,bus_register()时会在“/sys/bus_name”文件夹下创建相应的属性文件。

38

1.2.1.11、bus_type的dev_attrs属性

device对象的bus相关属性,如果bus_type->dev_attrs存在的话,device_register()时会在“/sys/devices/dev_name”文件夹下创建相应的属性文件。

39

1.2.1.12、bus_type的drv_attrs属性

device_driver对象的bus相关属性,如果bus_type->dev_attrs存在的话,driver_register()时会在“/sys/bus_name/drivers/drv_name”文件夹下创建相应的属性文件。

40

1.2.1.13、bus_type的match()函数

在device设备或者是device_driver驱动注册的时候,总线会尝试给设备找一个适配的驱动,给驱动找到所有适配的设备。这个过程中就会调用到bus_type->match()函数。

1.2.1.14、bus_type的uevent ()函数

在device设备的热插拔处理函数中,如果设备所在总线的热插拔处理函数存在的话,会加入bus->uevent的处理。

41

1.2.1.15、bus_type的probe ()、remove ()函数

在驱动和设备的注册过程中,如果驱动和设备match适配成功,接下来就会调用初始化probe函数。driver_probe_device的实现中可以看到,如果bus_type->probe()函数存在,就会调用bus的probe函数,否则调用设备的device_driver->probe函数。
一般的总线bus_type->probe()函数也不做具体初始化,最终也是调用device_driver->probe函数来进行初始化的。

42

同理,在驱动卸载或者设备移除时,会调用remove函数。

1.2.1.16、bus_type的shutdown()、suspend ()、resume ()函数

电源管理的相关函数,对应关机、待机、恢复上电操作。应该是由acpi程序统一调用。

1.2.2、device

1.2.2.1、device定义

43

1.2.2.2、device根目录/sys/devices

linux系统在初始化的时候,调用devices_init函数,给device类型的对象创建一个根节点“/sys/device”。

44

1.2.2.3、device_register()

45
46
47
48
49
50
51

1.2.2.4、device sysfs层次结构

经过device_register()函数注册以后,一种新类型device在sysfs中的层次结构为:

52

注意device在注册的时候存在两种层次结构:一种在device_register时没有指定parent,则会把“/sys/devices”指定为kset和parent;如果指定了parent,则parent是指定的kobject,而kset任然是“/sys/devices”。
1.2.2.5 device sysfs表述
从上节的层次图中可以看到,device_register()注册一个新的bus_type类型对象会在“/sys/devices”下面创建如下的sys文件:

  • “/sys/devices/dev_name”文件夹 —–> device.kobj
  • “/sys/ devices /dev_name/uevent” 文件 —–> device.uevent_attr
  • “/sys/ devices /dev_name / bus->dev_attrs” 文件 —–> device-> bus->dev_attrs
1.2.2.6、device属性处理函数

所有注册的device都是kobject对象,所有device的kest都是“/sys/devices”,所以对device对象的属性操作都会调用到“/sys/devices”的属相操作函数,这个在“/sys/devices”注册的是dev_attr_show ()、dev_attr_store()。
和bus_type的属性处理函数一样,device的属性处理函数也是一个总入口,其作用就是把属性处理的工作交给device_attribute自己的处理函数。

53

1.2.2.7、device热插拔处理

所有device的kest都是“/sys/devices”,所以对device对象的热插拔处理都会调用到“/sys/devices”的热插拔处理函数,这个在“/sys/devices”注册的是dev_uevent_filter ()、dev_uevent_name ()、dev_uevent()。

54
54-1

1.2.2.8、device对象的uevent属性

每个device对象在device_register注册以后,都会创建一个uevent属性。在device_register的代码分析中可以看到其具体实现。
Uevent的具体作用,在用户往uevent文件里面发生一个写操作,不管写任何内容,都会触发对象发出一条设备增加的热插拔消息。
这个特性是给udev使用的,udev中的udevtrigger就是利用了这个原理。

55

1.2.3、device_driver

1.2.3.1、device_driver定义

56

1.2.3.2、device_driver根目录

device_driver是依附于总线的,他并没有独立的根目录。driver_register()会注册驱动对象到“/sys/bus/bus_name/drivers/”目录。而不像device_register()在“/sys/bus/bus_name/devices/”目录下创建的都是符号链接。

1.2.3.3、driver_register ()

57
58
59
60

1.2.3.4、device_driver sysfs层次结构

经过driver_register()函数注册以后,一种新类型driver_register在sysfs中的层次结构为:

61

1.2.3.5、device_driver sysfs表述

从上节的层次图中可以看到,driver_register()注册一个新的device_driver类型对象会在“/sys/bus/bus_name/drivers”下面创建如下的sys文件:

  • “/sys/bus/bus_name/drivers /drv_name”文件夹 —–> device_driver.kobj
  • “/sys/bus/bus_name/drivers /drv_name/bind” 文件 —–> driver_attr_bind
  • “/sys/bus/bus_name/drivers /drv_name/unbind” 文件 —–> driver_attr_unbind
  • “/sys/bus/bus_name/drivers/drv_name / bus->drv_attrs” 文件 —–> device_driver -> bus->drv_attrs
1.2.3.6、device_driver属性处理函数

device_driver结构是kobject类型的,从层次如上可以看到其kset是“/sys/bus/bus_name/drivers”,所以它的属性处理函数就是“/sys/bus/bus_name/drivers”的属性处理函数。
“/sys/bus/bus_name/drivers”在注册的时候指定了ktype_driver为其属性处理函数。可以看到device_driver属性处理函数drv_attr_show()、drv_attr_store()和bus_type即device的属性函数类似,只是一个总入口,将属性处理的工作交给driver_attribute属性本身的处理函数。

62

1.2.3.7、device_driver热插拔处理

device_driver的kset是“/sys/bus/bus_name/drivers”,它没有注册热插拔处理函数,所以device_driver对象没有自定义的热插拔处理函数。

1.2.3.8、device_driver对象的bind、unbind属性

每个device_driver对象在driver_register注册以后,都会创建一个bind属性和一个unbind属性。bind的作用就是将driver和bus_id等于写入的内容的device进行绑定,unbind的作用就是将driver和bus_id等于写入的内容的device进行解除绑定。

63
64
65

1.2.3.9、device_driver的klist_devices链表

驱动上挂接的所有device设备的链表,一个驱动可以适配多个设备。在device设备和device_driver驱动绑定时,会把设备加入所在总线的device_driver->klist_devices链表。

66

1.2.4、class

1.2.4.1、class定义

67

1.2.4.2、class根目录/sys/class

linux系统在初始化的时候,调用classes_init函数,给class类型的对象创建一个根节点“/sys/class”。

68

1.2.4.3、class_create()

69

1.2.4.4、class sysfs层次结构

经过class_create ()函数注册以后,一种新类型class在sysfs中的层次结构为:

70

1.2.4.5、class sysfs表述

从上节的层次图中可以看到,class_create()注册一个新的class类型对象会在“/sys/class/”下面创建如下的sys文件:

  • “/sys/class/class_name”文件夹 —–> class. subsys.kset.kobj
  • “/sys/class/class_name / class->class_attrs” 文件 —–> class->class_attrs
1.2.4.6、class属性处理函数

因为“/sys/class/class_name”的kset是“/sys/class/”,所以class_create()创建的对象的属性处理函数就是“/sys/class/”的属性处理函数。
“/sys/class/” class_subsys在注册的时候属性处理函数指定的是class_attr_show()、class_attr_store(),这两个函数的主要作用也是提供一个总入口,将属性处理传递给class_attr自己的属性处理函数。

71

1.2.4.7、class热插拔处理

因为“/sys/class/class_name”的kset是“/sys/class/”,所以class_create()创建的对象的热插拔处理函数就是“/sys/class/”的热插拔处理函数。
class_subsys在注册的时候热插拔处理函数没有指定,所以class对象没有自定义热插拔函数,使用系统默认的热插拔处理。

1.2.4.8、class的class_attrs属性

class对象本身的class相关属性,如果class->class_attrs存在的话,class_ register()时会在“/sys/class/class_name”文件夹下创建相应的属性文件。

72

1.2.4.9、class的class_dev_attrs属性

class_device对象的class相关属性,如果class->class_attrs存在的话,class_device_ register()时会在“/sys/class/class_name/class_dev”文件夹下创建相应的属性文件。

73

1.2.4.10、class的uevent ()函数

在class_device设备的热插拔处理函数中,如果设备所在class的热插拔处理函数存在的话,会加入class->uevent的处理。

74

1.2.5、class_device

1.2.5.1、class_device定义

75

1.2.5.2、class_device_create()

76
77
78
79
80

1.2.5.3、class_device sysfs层次结构

81

1.2.5.4、class_device sysfs表述

从上节的层次图中可以看到,class_device_create ()注册一个新的class_device类型对象会在“/sys/class/”下面创建如下的sys文件:

  • “/sys/class/class_name/class_dev”文件夹 —–> class_device. kobject
  • “/sys/class/class_name/class_dev /uevent” 文件 —–> class_device的uevent属性
  • “/sys/class/class_name/class_dev /dev” 文件 —–> class_device的dev属性
  • “/sys/class/class_name/class_dev /class->class_dev_attrs” 文件 —–> class->class_dev_attrs
1.2.5.5、class_device属性处理函数

因为“/sys/class/class_name/class_dev”的kset是class_obj_subsys,所以class_device_create()创建的对象的属性处理函数就是class_obj_subsys的属性处理函数。
class_obj_subsys在注册的时候属性处理函数指定的是class_device_attr_show ()、class_device_attr_store (),这两个函数的主要作用也是提供一个总入口,将属性处理传递给class_device_attribute自己的属性处理函数。

82

1.2.5.6、class_device热插拔处理

因为“/sys/class/class_name/class_dev”的kset是class_obj_subsys,所以class_device_create()创建的对象的热插拔处理函数就是class_obj_subsys的热插拔处理函数。
class_obj_subsys在注册的时候热插拔处理函数指定的是class_uevent_filter ()、class_uevent_name ()、class_uevent()。

83
84
85

1.2.5.7、class_device对象的uevent属性

class_device对象的uevent属性和device对象的uevent属性作用是一样的,都是在写uevent文件时产生一次热插拔的add事件。给udev使用。

86

1.2.5.8、class_device对象的dev属性

class_device对象的dev属性是根据class_device->devt创建的,他的作用是用来把字符设备/块设备的主设备号和从设备号通过dev文件呈现给用户态。用户态udev程序根据这个dev文件和热插拔消息来动态的给设备创建和删除设备节点文件。

87

1.3、第三层类pci_bus_type、pci_dev、pci_driver

1.3.1、pci_bus_type

注意pci_bus_type是一个标准的bus_type类型变量,而不是一个类型。

1.3.1.1、pci_bus_type定义

88
89

1.3.1.2、pci_bus_type根目录/sys/bus/pci

pci总线初始化函数pci_driver_init直接调用bus_register注册了一个pci总线。创建了“/sys/bus/pci”、“/sys/bus/pci/devices”、“/sys/bus/pci/drivers”目录。

90

1.3.1.3、pci_bus_type sysfs层次结构

91

1.3.1.4、pci_bus_type sysfs表述
  • “/sys/bus/ pci”文件夹 —–> pci_bus_type.subsys
  • “/sys/bus/ pci /devices” 文件夹 —–> pci_bus_type.devices
  • “/sys/bus/ pci /drivers” 文件夹 —–> pci_bus_type.drivers
1.3.1.5、pci_bus_type属性处理函数

pci_bus_type是标准的bus_type对象,pci_bus_type的属性处理请参见bus_type的属性处理。

1.3.1.6、pci_bus_type热插拔处理

pci_bus_type是标准的bus_type对象,pci_bus_type的热插拔处理请参见bus_type的热插拔处理。

1.3.1.7、pci_bus_type的dev_attrs属性

device对象的bus相关属性,如果bus_type->dev_attrs存在的话,device_register()时会在“/sys/devices/dev_name”文件夹下创建相应的属性文件。

92

对应pci设备配置寄存器和其他的一些属性。

1.3.1.8、pci_bus_type的pci_match()函数

pci驱动和设备的适配函数。

93
94

1.3.1.9、pci_bus_type的pci_uevent ()函数

pci bus的热插拔处理函数。在pci device设备的热插拔处理函数中,如果设备所在总线的热插拔处理函数存在的话,会加入bus->uevent的处理。
如果不支持pci热插拔该函数为空,如果支持pci热插拔,该函数为以下的实现:

95
96

1.3.1.10、pci_bus_type的pci_probe ()、pci_remove()函数

在驱动和设备的注册过程中,如果驱动和设备match适配成功,接下来就会调用初始化probe函数。driver_probe_device的实现中可以看到,如果bus_type->probe()函数存在,就会调用bus的probe函数,否则调用设备的device_driver->probe函数。
一般的总线bus_type->probe()函数也不做具体初始化,最终也是调用device_driver->probe函数来进行初始化的。
同理,在驱动卸载或者设备移除时,会调用remove函数。

97
98
99
100

1.3.1.11、pci_bus_type的pci_suspend ()、pci_resume ()函数

101

1.3.2、pci_dev

1.3.2.1、pci_dev定义

102
103

1.3.2.2、pci_dev根目录/sys/devices/pci0000:00

在扫描pci根总线设备时,会先创建pci设备的跟挂载节点“/sys/devices/pci0000:00”。在pci_create_bus()函数中创建的。

104

注意pci_dev根目录“/sys/devices/pci0000:00”是一个device结构,而不是pci_dev结构。

1.3.2.3、pci_scan_single_device ()

扫描并初始化pci_dev设备的函数为pci_scan_single_device()。真正注册pci_dev的函数为pci_bus_add_devices()。

105
106
107

1.3.2.4、pci_dev层次结构

108

1.3.2.5、pci_dev表述

从上节的层次图中可以看到,注册一个新的pci_dev类型对象会在“/sys/devices/pci0000 :00”下面创建如下的sys文件:
“/sys/devices/pci0000 :00/0000 :00 :00.0”文件夹 —–> pci_dev.dev.kobj

1.3.2.6、pci_dev属性处理函数

pci_dev的属性处理请参见device的属性处理。

1.3.2.7、pci_dev热插拔处理

pci_dev的热插拔处理请参见device的热插拔处理。

1.3.3、pci_driver

1.3.3.1、pci_driver定义

109

1.3.3.2、pci_driver根目录

device_driver是依附于总线的,他并没有独立的根目录。driver_register()会注册驱动对象到“/sys/bus/bus_name/drivers/”目录。而不像device_register()在“/sys/bus/bus_name/devices/”目录下创建的都是符号链接。
所以pci_driver的根目录为“/sys/bus/pci/drivers/”。

1.3.3.3、pci_register_driver ()

110

1.3.3.4、pci_driver sysfs层次结构

111

1.3.3.5、pci_driver sysfs表述

从上节的层次图中可以看到,通过pci_register_driver()注册一个新的pci_driver类型对象会在“/sys/bus/pci/drivers”下面创建如下的sys文件:

  • “/sys/bus/pci/drivers/pci_drv_name”文件夹 —–> pci_driver.driver.kobj
1.3.3.6、pci_driver属性处理函数

pci_driver的属性处理请参见device_driver的属性处理。

1.3.3.7、pci_driver热插拔处理

pci_driver的热插拔处理请参见device_driver的热插拔处理。

1.3.3.8、device_driver对象的bind、unbind属性

pci_driver的bind、unbind属性处理请参见device_driver的bind、unbind属性处理。

1.3.3.9、device_driver对象的newid属性

pci_driver的newid属性是为了支持pci_driver的动态设备id表。在驱动运行时,可以动态添加驱动支持的新的设备ID。在pci_register_driver ()注册驱动时,调用pci_create_newid_file()函数创建“newid”属性文件。

112
113

1.3.3.10、pci_driver的静态设备id表id_table

pci总线的驱动和适配函数pci_match(),就是判断设备的pci id是否和pci_driver所支持的设备是否适配。pci_driver所支持的设备由静态设备id表pci_driver->id_table和动态设备id表pci_driver->dynids。
静态设备id表在驱动编译时指定。

114

1.3.3.11、pci_driver的动态设备id表dynids

动态设备id表在驱动运行时,通过驱动的“newid”文件动态进行添加。通过类似“echo ‘10ec 8140’ > /sys/bus/pci/drivers/8139too/new_id ”的操作,可以动态添加设备到驱动。

1.3.4、pci_bus

1.3.4.1、pci_bus定义

115

1.3.4.2、pci_bus根目录/sys/class/pci_bus

pci总线初始化函数pcibus_class_init直接调用class_register注册了一个pci_bus类。创建了“/sys/class/pci_bus”目录。

116

1.3.4.3、pci_create_bus ()

创建根总线在pci_create_bus()中实现。

117
118

1.3.4.4、pci_add_new_bus ()

扫描总线时,扫描到子总线设备的创建子总线对象,在pci_add_new_bus()中实现。

119
120

1.3.4.5、pci_bus sysfs层次结构

121

1.3.4.6、pci_bus sysfs表述

从上节的层次图中可以看到,注册一个新的pci_bus类型对象会在“/sys/class/pci_bus”下面创建如下的sys文件:

  • “/sys/class/pci_bus /0000 :00”文件夹 —–> pci_bus.class_dev.kobj
1.3.4.7、pci_bus属性处理函数

pci_bus的属性处理请参见class_device的属性处理。

1.3.4.8、pci_bus热插拔处理

pci_bus的热插拔处理请参见class_device的热插拔处理。

1.3.4.9、pci_bus的cpuaffinity属性

122

1.4、第四层类pcie_port_bus_type、pcie_device、pcie_port_service_driver

在pci的数据模型之上,进一步包装了pcie port驱动。注意是pcie端口驱动,而不是pcie设备驱动。针对pcie桥设备的四种能力:PCIE_PORT_SERVICE_PME(Power Management Event)、PCIE_PORT_SERVICE_AER (Advanced Error Reporting)、PCIE_PORT_SERVICE_HP(Native Hotplug)、PCIE_PORT_SERVICE_VC (Virtual Channel)。把桥设备的四种能力虚拟出四种pcie端口设备来。并且可以对这四种设备注册相应的驱动。

1.4.1、pcie_port_bus_type

1.4.1.1、pcie_port_bus_type定义

123

pcie_port_bus_type一个标准bus_type类型的变量。

1.4.1.2、pcie_port_bus_type根目录/sys/bus/pci_express/

为pcie桥设备的创建了一个新的总线类型“/sys/bus/pci_express/”。

124

1.4.1.3、pcie_port_bus_type sysfs层次结构

125

1.4.1.4、pcie_port_bus_type sysfs表述

从上节的层次图中可以看到,pcie_port_bus_type注册一个新的bus_type类型对象会在“/sys/bus”下面创建如下的sys文件:

  • “/sys/bus/ pci_express”文件夹 —–> pcie_port_bus_type.subsys
  • “/sys/bus/ pci_express /devices” 文件夹 —–> pcie_port_bus_type.devices
  • “/sys/bus/ pci_express /drivers” 文件夹 —–> pcie_port_bus_type.drivers
1.4.1.5、pcie_port_bus_type属性处理函数

pcie_port_bus_type是标准的bus_type对象,pcie_port_bus_type的属性处理请参见bus_type的属性处理。

1.4.1.6、pcie_port_bus_type热插拔处理

pcie_port_bus_type是标准的bus_type对象,pcie_port_bus_type的热插拔处理请参见bus_type的热插拔处理。

1.4.1.7、pcie_port_bus_type的pcie_port_bus_match ()函数

126

1.4.2、pcie_device

1.4.2.1、pcie_device定义

127

1.4.2.2、pcie_device入口/sys/bus/pci/drivers/pcieport-driver/

为了把桥设备的四种能力虚拟出四种pcie端口设备来。首先注册一个标准的pci_driver即pcie_portdrv。pcie_portdrv是所有pci桥设备的驱动,在pcie_portdrv提供的probe函数中,探测桥是否为pcie桥,再探测pcie桥的四种能力,为每个存在的能力创建一个pcie设备。
pcie_portdrv驱动注册以后会在sys文件系统创建“/sys/bus/pci/drivers/pcieport-driver/”文件夹。

128
129

1.4.2.3、pcie_portdrv_probe ()

pcie_portdrv_probe()函数是pcie_portdrv的probe函数。pcie_portdrv_probe的目的不是为pci桥设备提供驱动,它的目的是为了探测pci桥设备的pcie能力,为pcie能力创建pcie_device对象。

130
131
132
133
134
135
136

1.4.2.4、pcie_device sysfs层次结构

137

1.4.2.5、pcie_device sysfs表述

从上节的层次图中可以看到,注册一个新的pcie_device类型对象会在“/sys/devices/pci0000:00/0000:00:02.0”下面创建如下的sys文件:

  • “/sys/devices/pci0000:00/0000:00:02.0/0000:00:02.0:pcie00”文件夹 —–> pcie_device.device.kobj
1.4.2.6、pcie_device属性处理函数

pcie_device的属性处理请参见device的属性处理。

1.4.2.7、pcie_device热插拔处理

pcie_device的热插拔处理请参见device的热插拔处理。

1.4.3、pcie_port_service_driver

1.4.3.1、pcie_port_service_driver定义

138

1.4.3.2、pcie_port_service_driver根目录

pcie_port_service_driver是依附于总线的,他并没有独立的根目录。pcie_port_service_register ()会注册驱动对象到“/sys/bus/pci_express/drivers/”目录。
所以pcie_port_service_driver的根目录为“/sys/bus/pci_express/drivers/”。

1.4.3.3、pcie_port_service_register ()

139

1.4.3.4、pcie_port_service_driver sysfs层次结构

140

1.4.3.5、pcie_port_service_driver sysfs表述

从上节的层次图中可以看到,通过pcie_port_service_register ()注册一个新的pcie_port_service_register类型对象会在“/sys/bus/ pci_express/drivers”下面创建如下的sys文件:

  • “/sys/bus/ pci_express/drivers/ drv_name”文件夹 —–> pcie_port_service_driver.driver.kobj
1.4.3.6、pcie_port_service_driver属性处理函数

pcie_port_service_driver的属性处理请参见device_driver的属性处理。

1.4.3.7、pcie_port_service_driver热插拔处理

pcie_port_service_driver的热插拔处理请参见device_driver的热插拔处理。

1.4.3.8、pcie_port_service_driver->driver的pcie_port_probe_service()、pcie_port_remove_service()函数

在驱动和设备的注册过程中,如果驱动和设备match适配成功,接下来就会调用初始化probe函数。driver_probe_device的实现中可以看到,如果bus_type->probe()函数存在,就会调用bus的probe函数,否则调用设备的device_driver->probe函数。
pcie_port_bus_type总线没有实现bus_type->probe()函数,所以调用device_driver->probe函数来进行初始化的。device_driver->probe最终会调用到pcie_port_service_driver->probe函数。

141
142

1.4.3.9、pcie_port_service_driver->driver的pcie_port_shutdown_service ()、pcie_port_suspend_service ()、pcie_port_resume_service()函数

和probe、remove函数一样,最终调用到pcie_port_service_driver本身的相应函数。

2、pci总线的初始化流程

2.1、linux初始化顺序说明

内核中的很多函数,使用了xxx_initcall()的宏进行定义。xxx_initcall()定义的函数是在linux内核初始化的过程中执行的,不同的xxx_initcall()定义了不同的初始化顺序。

143

可以看到,xxx_initcall(fn)宏就是在section(.initcallx.init)中定义了一个函数指针变量,变量的值指向fn。

144

在vmlinux.lds.S链接脚本中可以看到section(.initcallx.init)的定义,它的头是__initcall_start,尾是__initcall_end。对section(.initcallx.init)中这些初始化函数指针,是在start_kernel() -> rest_init() -> init() ->do_basic_setup-> do_initcalls()中执行的。

145

2.2、linux驱动模型初始化driver_init()

在linux初始化的时候,linux设备模型的subsystem根节点需要优先建立。bus_type对应的“/sys/bus”, device对应的“/sys/devices”, class对应的“/sys/class”。
这些根节点的创建函数是driver_init()。driver_init()是被do_basic_setup调用,调用顺序在do_initcalls之前。

146
147
148

2.3、pci总线初始化

pci总线 的相关sysfs挂载点“/sys/bus/pci”、“/sys/class/pci_bus”也需要在扫描设备之前创建。

2.3.1、pci bus_type初始化“/sys/bus/pci”

149

2.3.2、pci_bus class初始化“/sys/class/pci_bus”

150

2.4、pci配置空间访问方式的探测

x86架构中pci配置空间的访问有4种方式:pci_bios、pci_conf1、pci_conf2、pci_mmcfg。最优的方式是mmcfg,这需要bios配置,把pci配置空间映射到cpu mem空间;pci_conf1、pci_conf2方式是通过io指针间接访问的;pci_bios方式应该是调用bios提供的服务进程进行访问。
在linux初始化的时候,需要给驱动程序选择一种最优的访问方式。

2.4.1、pci_bios访问方式

151

2.4.2、pci_conf1、pci_conf2访问方式

152

2.4.3、pci_mmcfg访问方式

153

2.4.4、pci 配置访问的使用

154
155
156
157

2.5、pci设备扫描与创建

2.5.1、pci总线和设备扫描pci_scan_bus_parented()

pci总线和设备扫描的实现在linuxsrc/driver/pci/probe.c中pci_scan_bus_parented()函数中,pci_scan_bus_parented()函数会被pcibios_scan_root()和pcibios_scan_root()调用。我们来看看pci_scan_bus_parented()的具体扫描过程。

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180

2.5.2、pci总线和设备的层次结构

181

2.5.3、pci设备的注册pci_bus_add_devices()

pci_scan_bus_parented()函数完成以后,pci总线上的各级设备个各级总线桥都被扫描了出来。其中总线相关的pci_bus数据结构也都在sysfs系统中注册。
但是还有些事情没有做:pci设备对应的pci_dev结构都只是挂接在各级总线的pci_bus->devices链表上,都未向sysfs注册;另外各个设备的bar空间只是进行了扫描,并未进行统一配置。
pci_bus_add_devices()所做的工作就是对扫描完成的pci设备对应的pci_dev进行注册。

182
183
184

2.5.4、pci设备bar资源的统一分配和配置

扫描完设备,注册完设备以后,还需要对所有的pci设备bar空间进行统一编址,还需要配置pci桥设备的mem/io访问窗口。
这种统一编址类似如pci设备的扫描,需要递归的深入各个叶子节点设备进行计算。但是资料上说,intel架构这部分工作是由bios做的,linux内核需要做的就是对bios配置的统一编址进行校验,并注册进数据结构。

2.5.4.1、pcibios_resource_survey()

pcibios_resource_survey()所做的工作就是把pci扫描得到的bios配置的bar地址进行校验和注册。
pcibios_resource_survey()被pcibios_init()调用,启动阶段在subsys_initcall()。

185
186
187
188
189

2.5.4.2、pcibios_assign_resources()

pcibios_assign_resources()检查资源的对齐状况,对没对齐的资源进行调整。pcibios_assign_resources()的启动阶段在fs_initcall。
PCI I/O和PCI内存必须以自然对齐方式分配给每个设备。比如如果一个设备要求0xB0大小的PCI I/O空间则它必须和一个0xB0倍数的地址对齐。除此以外,对于任何指定桥接器,其PCI I/O和PCI内存基址必须以在1M字节边界上以4K字节方式对齐。

190
191

2.6、pci search功能

在扫描pci总线设备的时候,将顶层总线加入了pci_root_buses链表,将所有的pci设备加入了pci_devices链表。如是就有了支持各种pci_bus、pci_dev查找的函数。

2.6.1、根据bus号查找pci_bus :pci_find_bus()

192
193
194

2.6.2、根据bus、devfn号查找pci_dev : pci_find_slot()

195

2.6.3、在制定pci_bus上查找pci_dev : pci_get_slot()

196

2.6.4、根据Vid、Did查找pci_dev : pci_find_device ()、pci_get_device ()

197

pci_get_device ()和pci_find_device ()功能基本一致,就是多了一个pci_dev的get操作pci_dev_get()。

3、pcie port热插拔

3.1、pcie热插拔的处理流程

198

可以看到热插拔流程的过程就是,用户通知内核要进行插拔动作,内核执行软件层的移除/添加动作,完成后通知用户软件层动作已经完成,可以进行下一步的手工动作。

3.2、pcie热插拔的扳手检测、指示灯、电源管理

这几部分的原理都很直白,具体可以自行看具体单板的实现,这里不做详细分析。

3.3、pcie热插拔的内核处理

在热插拔时,最核心的内容其实就是内核需要把设备数据结构从系统中移除或添加到系统中。在拔出时:内核需要调用驱动的清理函数remove函数,再把设备的数据结构从系统中释放;在插入时:内核需要配置新设备的bar资源,给新设备分配数据结构,并且帮其查找合适的驱动并调用驱动初始化函数probe函数。

3.3.1、pcie热插拔驱动的注册

pcie热插拔驱动,注册成上述驱动设备模型中的pcie_port_service_driver类型。当pcie-port是支持pcie热插拔时,pcie热插拔驱动便有效。
这里看一个pcie热插拔驱动注册的例子:

199
200

3.3.2、内核移除pci设备pciehp_unconfigure_device()

在pcie设备拔出时:内核需要调用驱动的清理函数remove函数,再把设备的数据结构从系统中释放;调用的就是pciehp_unconfigure_device()函数。我们看其具体的实现过程。

201
202
203

3.3.3、内核添加pci设备pciehp_configure_device()

在pcie设备插入时:内核需要配置新设备的bar资源,给新设备分配数据结构,并且帮其查找合适的驱动并调用驱动初始化函数probe函数。

204

4、sysfs的应用udev

4.1、udev

4.1.1、udev功能简介

在前面设备模型的章节,可以看到sysfs设备在注册、移除即其他动作时,会向用户空间发出热插拔消息。同一份热插拔消息,在2.6.16.54内核中使用了两种方式发送,一种是通过netlink的广播消息发送,另一种是使用call_usermodehelper在内核态调用用户空间程序来处理消息。

205

Udev就是在用户空间接收内核sysfs netlink热插拔消息的程序,而内核态调用用户空间程序的方式调用的是“/sbin/hotplug”,后一种方式已经被淘汰。
用户空间对热插拔消息的处理有几类动作:

  • 1、创建或者移除设备的设备节点;如果设备的devt属性,即“/sys/class/” 路径下包含“dev”文件属性的内核设备,发生增加或移除操作时,udev会帮其在用户空间“/dev”路径下增加或移除设备节点。
  • 2、根据规则文件,给设备改名、创建符号链接等。
  • 3、根据规则文件,调用外部程序。例如,调用modprobe插入驱动。

4.1.2、udev bin文件

安装完udev软件包udev-085.tar.bz2以后,udev提供了以下有用的udev相关bin文件:

  • Udevd:udev的后台守护进程,接收内核的热插拔消息,并根据规则文件进行处理。
  • Udevmonitor:热插拔消息的调试监控程序,打开它可以看到具体的热插拔消息。
  • Udevcontrol:控制udevd的相关动作。
  • Udevtrigger:因为udev起来以后,已经错过了已经建立起来的设备的插入消息,调用udevtrigger想所有设备的“uevent”文件写操作,触发内核再发一次设备的插入消息。
  • Udevinfo:udev的数据库查询程序,udev将sysfs的结构存入自己的数据库中,udevinfo用来查询这些信息。
  • Udevsettle:等待udev队列中所有的消息处理完成后,才退出。主要目的是在启动udevd时,等待udevd处理完Udevtrigger产生的热插拔消息,才继续执行启动脚本,避免产生新的热插拔消息,造成消息丢失。

4.1.3、udev规则文件

udev的动作是根据“/etc/udev/rules.d”文件夹下的规则文件*.rules来定义的。有关规则文件的说明可以参考udevd的man说明和“Writing udev rules”文档。

4.2、/sbin/hotplug

4.2.1、hotplug功能简介

hotplug就是内核发送内核热插拔消息中的后一种方式,内核会直接调用用户态的“/sbin/hotplug ”程序来处理热插拔消息。
在早期的2.6内核版本中,udev和hotplug是并存的,udev负责处理设备节点创建,hotplug负责给设备插入驱动。在2.6.15内核版本以后,hotplug被淘汰了,上述两件事被udev一个人全做了。

4.2.2、hotplug调用流程

内核使用格式“/sbin/hotplug subsystem”调用,热插拔相关信息存储在环境变量envp中。

206

5、Proc文件系统

和sysfs文件系统一样,proc文件系统也可以实现用户和内核空间的通讯。Proc也有相应的创建文件夹和文件的接口函数,其文件的读写访问有两种接口read_proc/ write_proc和get_info。

5.1、Proc文件系统创建文件夹

207

5.2、Proc文件系统创建文件,read_proc/ write_proc读写接口

208
209

5.3、Proc文件系统创建文件夹,get_info读接口

210

5.4、Proc文件读写的实现原理

我们可以在proc文件系统的读写函数proc_file_read()、proc_file_write()的实际实现,即怎么调用到节点的read_proc、 write_proc、get_info接口。

211
212
213

6、参考资料

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值