底层软件 | Linux设备驱动模型和sysfs文件系统

Linux设备驱动模型和sysfs文件系统

Linux内核在2.6版本中引入设备驱动模型,简化了驱动程序的编写。Linux设备驱动模型包含设备(device)总线(bus)类(class)驱动(driver),它们之间相互关联。其中**设备(device)驱动(driver)通过总线(bus)**绑定在一起。

Linux内核中,分别用bus_typedevice_driverdevice结构来描述总线、驱动和设备,结构体定义详见linux/device.h。设备和对应的驱动必须依附于同一种总线,因此device_driverdevice结构中都包含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: 包含所有的块设备,如ramsda
  • bus: 包含系统中所有的总线类型,如pciusbi2c
  • class: 包含系统中的设备类型,如inputpci_busmmc_host
  • dev: 包含两个子目录:charblock,分别存放字符设备和块设备的主次设备号(major:minor),指向/sys/devices目录下的设备
  • devices:包含系统所有的设备

sysfs中显示的每一个对象都对应一个kobject结构(完整定义位于linux/kobject.h,结构内部包含一个parent指针),而另一个相联系的结构为ksetkset是嵌入相同类型结构的kobject对象的集合。
内核用kobjectksetparent之间的关系将各个对象连接起来组成一个分层的结构体系,从而与模型化的子系统相匹配。(有机会详细介绍)

sysfs中能清晰地看出devicedriverbus的相互联系,以某系统上pci总线上的igb驱动为例。
/sys/bus/pci/下存在devicesdrivers两个目录,分别包含了依附于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/目录下,可以看到设备存在一个指向igbdriver项:

/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总线是一种虚拟的总线,与之相对应的是PCII2CSPI等实体总线。引入虚拟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中基本成员进行初始化,包括kobjectstruct device_privatestruct 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,
       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值