Linux设备驱动模型和sysfs文件系统
Linux内核在2.6版本中引入设备驱动模型,简化了驱动程序的编写。Linux设备驱动模型包含设备(device)、总线(bus)、类(class)和驱动(driver),它们之间相互关联。其中**设备(device)和驱动(driver)通过总线(bus)**绑定在一起。
Linux内核中,分别用bus_type
、device_driver
和device
结构来描述总线、驱动和设备,结构体定义详见linux/device.h
。设备和对应的驱动必须依附于同一种总线,因此device_driver
和device
结构中都包含struct bus_type
指针。
Linux sysfs是一个虚拟的文件系统,它把连接在系统上的设备和总线组织成为一个分级的文件,可以由用户空间存取,向用户空间导出内核数据结构以及它们的属性。
sysfs
展示出设备驱动模型中各个组件的层次关系,某个系统上的sysfs
顶层目录展示如下:
/sys$ ll
total 0
drwxr-xr-x 2 root root 0 Aug 20 15:27 block/
drwxr-xr-x 29 root root 0 Aug 20 15:27 bus/
drwxr-xr-x 61 root root 0 Aug 20 15:27 class/
drwxr-xr-x 4 root root 0 Aug 20 15:27 dev/
drwxr-xr-x 14 root root 0 Aug 20 15:27 devices/
drwxr-xr-x 4 root root 0 Aug 20 15:27 firmware/
drwxr-xr-x 8 root root 0 Aug 20 15:27 fs/
drwxr-xr-x 2 root root 0 Sep 2 17:08 hypervisor/
drwxr-xr-x 8 root root 0 Aug 20 15:27 kernel/
drwxr-xr-x 147 root root 0 Aug 20 15:27 module/
drwxr-xr-x 2 root root 0 Aug 20 15:27 power/
重要子目录介绍:
block
: 包含所有的块设备,如ram
,sda
等bus
: 包含系统中所有的总线类型,如pci
,usb
,i2c
等class
: 包含系统中的设备类型,如input
,pci_bus
,mmc_host
等dev
: 包含两个子目录:char
和block
,分别存放字符设备和块设备的主次设备号(major:minor),指向/sys/devices
目录下的设备devices
:包含系统所有的设备
sysfs
中显示的每一个对象都对应一个kobject
结构(完整定义位于linux/kobject.h
,结构内部包含一个parent
指针),而另一个相联系的结构为kset
。kset
是嵌入相同类型结构的kobject
对象的集合。
内核用kobject
、kset
和parent
之间的关系将各个对象连接起来组成一个分层的结构体系,从而与模型化的子系统相匹配。(有机会详细介绍)
sysfs
中能清晰地看出device
、driver
和bus
的相互联系,以某系统上pci
总线上的igb
驱动为例。
/sys/bus/pci/
下存在devices
和drivers
两个目录,分别包含了依附于pci
总线上的设备和驱动。进入igb
驱动目录,可以发现存在指向设备的链接。
/sys/bus/pci/drivers/igb$ ll
total 0
... 0 Sep 2 17:08 0000:07:00.0 -> ../../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.0/
... 0 Sep 2 17:08 0000:07:00.1 -> ../../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.1/
...
对应地,在/sys/devices/
目录下,可以看到设备存在一个指向igb
的driver
项:
/sys/devices/pci0000:00/0000:00:1c.4/0000:07:00.0$ ll
total 0
...
lrwxrwxrwx 1 root root 0 Aug 20 15:27 driver -> ../../../../bus/pci/drivers/igb/
...
同样地,/sys/bus/pci/devices
目录下可以找到指向同样设备的一个链接:
/sys/bus/pci/devices$ ll
total 0
...
... 0 Aug 20 15:27 0000:07:00.0 -> ../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.0/
... 0 Aug 20 15:27 0000:07:00.1 -> ../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.1/
...
对于早期的Linux内核(2.6版本以前)来说,通常在驱动代码中xxx_driver
注册过程中调用probe()
函数来对设备进行初始化。
引入Linux设备驱动模型下,设备和驱动可以分开注册,依赖总线完成相互绑定。系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,系统每注册一个驱动的时候,会寻找与之匹配的设备。这个过程中,设备和驱动的匹配工作由总线完成。
下文中将会用关键的内核源码(基于linux 5.2.14 Kernel)说明驱动和设备间匹配机制的实现,分析的过程中以platform
总线为例。
platform
总线是一种虚拟的总线,与之相对应的是PCI
、I2C
、SPI
等实体总线。引入虚拟platform
总线是为了解决某些设备无法直接依附在现有实体总线上的问题,例如SoC系统中集成的独立外设控制器,挂接在SoC内存空间的外设等等。
platform总线的注册
platform
总线作为Linux的基础总线,在内核启动阶段便完成了注册,注册的入口函数为platform_bus_init()
。内核启动阶段调用该函数的路径为:
start_kernel() --> arch_call_rest_init()[last step in start_kernel]
--> rest_init() --> kernel_init()
--> kernel_init_freeable() --> do_basic_setup()
--> driver_init() --> platform_bus_init()
Linux内核中定义了platform_bus_type
结构体来描述platform
总线,同时也定义了设备platform_bus
,用于管理所有挂载在platform
总线下的设备,定义如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
struct device platform_bus = {
.init_name = "platform",
};
platform_bus_init()
对platform
总线的注册主要分为两步:
device_register(&platform_bus)
bus_register(&platform_bus_type)
。
int __init platform_bus_init(void)
{
int error;
/* Clear up early_platform_device_list, then only remain head_list */
early_platform_cleanup();
/* register platform_bus device (platform_bus is also regarded as a device) */
error = device_register(&platform_bus);
if (error) {
put_device(&platform_bus);
return error;
}
/* Main process to register platform_bus */
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
device_register(&platform_bus)
/***** drivers/base/core.c *****/
int device_register(struct device *dev)
{
device_initialize(dev); // init device structure
return device_add(dev); // add device to device hierarchy
}
device_initialize()
:对struct device
中基本成员进行初始化,包括kobject
、struct device_private
、struct mutex
等。device_add(dev)
:将platform
总线也作为一个设备platform_bus
注册到驱动模型中,重要的函数包括device_create_file()
、device_add_class_symlinks()
、bus_add_device()
、bus_probe_device()
等,下文中对设备注册的介绍一节,将对这个函数做更详细的介绍。device_add(&platform_bus)
主要功能是完成/sys/devices/platform
目录的建立。
bus_register(&platform_bus_type)
/***** drivers/base/bus.c *****/
int bus_register(struct bus_type *bus)
{
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
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;
/* Register kset (subsys) */
retval = kset_register(&priv->subsys);
retval = bus_create_file(bus, &bus_attr_uevent);
/* Setup "devices" and "drivers" subfolder under "platform" */
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
priv->drivers_kset = kset_create_and_add("drivers", NULL,