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,

最低0.47元/天 解锁文章
1010

被折叠的 条评论
为什么被折叠?



