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

news2024/11/25 14:30:09

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,
            &priv->subsys.kobj);

    INIT_LIST_HEAD(&priv->interfaces);
    __mutex_init(&priv->mutex, "subsys mutex", key);
    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
    klist_init(&priv->klist_drivers, NULL, NULL);

    /* bus_create_file(bus, &bus_attr_drivers_probe);     BUS_ATTR_WO(drivers_probe)
     * bus_create_file(bus, &bus_attr_drivers_autoprobe); BUS_ATTR_RW(drivers_autoprobe)
     * Add two attribute files for current bus /sys/bus/platform 
     */
    retval = add_probe_files(bus);

    retval = bus_add_groups(bus, bus->bus_groups);

    return 0;
}

bus_register(&platform_bus_type)将总线platform注册到Linux的总线系统中,主要完成了subsystem的注册,对struct subsys_private结构进行了初始化,具体包括:

  • platform_bus_type->p->drivers_autoprobe = 1
  • struct kset类型成员subsys进行初始化,作为子系统中kobject对象的parentkset本身也包含kobject对象,在sysfs中也表现为一个目录,即/sys/bus/platform
  • 建立struct kset类型的drivers_ksetdevices_kset,作为总线下挂载的所有驱动和设备的集合,sysfs中表现为/sys/bus/platform/drivers/sys/bus/platform/devices
  • 初始化链表klist_driversklist_devices,将总线下的驱动和设备分别链接在一起。
  • 增加probe文件,对应/sys/bus/platform目录的文件drivers_autoprobedrivers_probe

注册完成后platform_bus_type结构重要的成员列举如下:

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,
    .p  (struct subsys_private) = {
        .bus        = &platform_bus_type,
        .subsys (struct kset) = {
            .kobj = {
                .name   = “platform”
                .kref->refcount->refs       = 1,    // kset_init()
                INIT_LIST_HEAD(.entry),
                .state_in_sysfs             = 0,
                .state_add_uevent_sent      = 1,    // kset_register()
                .state_remove_uevent_sent   = 0,
                .state_initialized          = 1,
                .kset   = bus_kset,                 // attached to /sys/bus/
                .ktype= bus_ktype,

                .parent = bus_kset->kobj,
                .sd (kernfs_node) = {               // create_dir, kobject_add_internal
                    .parent = bus_kset->kobj->sd,
                    .dir.root = bus_kset->kobj->sd->dir.root,
                    .ns = NULL,
                    .priv = .kobj
                }
            }
            INIT_LIST_HEAD(&k->list);
            spin_lock_init(&k->list_lock);
        }
        /* key point for driver to autoprobe device, set in bus_register() */
        . drivers_autoprobe = 1
        klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
        klist_init(&priv->klist_drivers, NULL, NULL);
        .devices_kset = kset_create_and_add("devices", NULL, &.p->subsys.kobj);
        /* .drivers_kset = kset_create_and_add("drivers", NULL, &.p->subsys.kobj) */
        .drivers_kset = {
            .kobj = {
                .name = “drivers”,
                .parent = &.subsys.kobj,
                .ktype = &kset_ktype,
                .kset = NULL,

                .kref->refcount->refs       = 1,    // kset_init
                INIT_LIST_HEAD(.entry),
                .state_in_sysfs             = 0,
                .state_add_uevent_sent      = 1,    // kset_register
                .state_remove_uevent_sent   = 0,
                .state_initialized          = 1,

                .sd = {                             // create_dir: /sys/bus/platform/drivers
                    /* kobject_add_internal */
                    .parent = &.subsys.kobj.sd,
                    .dir.root = = &.subsys.kobj.sd->dir.root
                        .ns = NULL,
                    .priv = .kobj
                }
            }
            INIT_LIST_HEAD(.list);
            spin_lock_init(.list_lock);
            .uevent_ops = NULL,
        }
    }
};

platform驱动的注册

Linux内核中对依赖于platform总线的驱动定义了platform_driver结构体,内部封装了前述的struct device_driver

struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
    bool prevent_deferred_probe;
};

为了更好地说明platform驱动的注册过程,以驱动globalfifo_driver为实例,globalfifo_driver结构成员定义如下:

static struct platform_driver globalfifo_driver = {
    .driver = {
        .name   = "globalfifo_platform",
        .owner  = THIS_MODULE,
    },
    .probe  = globalfifo_probe,
    .remove = globalfifo_remove,
};

globalfifo_driver注册的入口函数为platform_driver_register(&globalfifo_driver),具体实现为__platform_driver_register(&globalfifo_driver, THIS_MODULE)
该函数会对struct device_driverbusproberemove等回调函数进行初始化,紧接着调用driver_register(&globalfifo_driver->driver)

/*****  drivers/base/platform.c  *****/
/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
        struct module *owner)
{
    drv->driver.owner       = owner;
    drv->driver.bus         = &platform_bus_type;
    drv->driver.probe       = platform_drv_probe;
    drv->driver.remove      = platform_drv_remove;
    drv->driver.shutdown    = platform_drv_shutdown;

    return driver_register(&drv->driver);
}

driver_register(&(globalfifo_driver.driver))

/*****  drivers/base/driver.c  *****/
/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
    int ret;
    struct device_driver *other;

    if (!drv->bus->p) {
        pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
                drv->name, drv->bus->name);
        return -EINVAL;
    }

    if ((drv->bus->probe && drv->probe) ||
        (drv->bus->remove && drv->remove) ||
        (drv->bus->shutdown && drv->shutdown))
        printk(KERN_WARNING "Driver '%s' needs updating - please use "
                "bus_type methods\n", drv->name);

    other = driver_find(drv->name, drv->bus);

    ret = bus_add_driver(drv);

    ret = driver_add_groups(drv, drv->groups);

    kobject_uevent(&drv->p->kobj, KOBJ_ADD);

    return ret;
}

driver_register(&(globalfifo_driver.driver))主要的工作包括:

  • 确认驱动依附的总线platform_bus已经被注册并初始化(必要条件)。
  • proberemoveshutdown等回调函数初始化进行判断,保证总线和驱动上相应的函数只能存在一个。
  • driver_find()查找总线上是否已存在当前驱动的同名驱动。
  • bus_add_driver(&(globalfifo_driver.driver)),将驱动注册到总线上,下文详述。
  • 发起KOBJ_ADD类型uevent,指示驱动已经添加完成,TODO。

bus_add_driver(&(globalfifo_driver.driver))

/*****  drivers/base/bus.c  *****/
/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: driver.
 */
int bus_add_driver(struct device_driver *drv)
{
    struct bus_type *bus;
    struct driver_private *priv;
    int error = 0;

    bus = bus_get(drv->bus);

    priv = kzalloc(sizeof(*priv), GFP_KERNEL);

    klist_init(&priv->klist_devices, NULL, NULL);
    priv->driver = drv;
    drv->p = priv;
    priv->kobj.kset = bus->p->drivers_kset;
    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
            "%s", drv->name);

    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

    /* Entrance to match device: try to bind driver to devices */
    if (drv->bus->p->drivers_autoprobe) {
        error = driver_attach(drv);
    }
    module_add_driver(drv->owner, drv);

    error = driver_create_file(drv, &driver_attr_uevent);
    error = driver_add_groups(drv, bus->drv_groups);

    if (!drv->suppress_bind_attrs) {
        error = add_bind_files(drv);
    }
    return 0;
}

bus_add_driver(&(globalfifo_driver.driver))的主要工作包括:

  • struct device_driver中结构struct driver_private动态分配空间,并完成后者kobject对象初始化。对应地,在/sys/bus/platform/drivers下建立目录globalfifo_platform
  • 初始化klist_devices链表,用来维护驱动相关联的设备。对应sysfs中在每个驱动目录下关联的设备。
  • klist_add_tail()将当前驱动加入到总线对应的klist_drivers链表中。
  • 如果总线使能drivers_autoprobe,将调用driver_attach()尝试匹配设备。下文中将详述此过程。
  • module_add_driver(drv->owner, drv)通过sysfs_create_link(),在globalfifo_platform目录下新建module项指向/sys/module/globalfifo_platform。同时,也在/sys/module/globalfifo_platform/目录下新建driver目录,建立bus->name:drv->name 链接到/sys/bus/platform/drivers/globalfifo_platform
  • uevent设置。

初始化后globalfifo_driver结构主要的成员列举如下:

static struct platform_driver globalfifo_driver = {
    .driver = {
        .name       = "globalfifo_platform",
        .owner      = THIS_MODULE,
        .bus        = &platform_bus_type,
        .probe      = platform_drv_probe
        .remove     = platform_drv_remove,
        .shutdown   = platform_drv_shutdown,
        .p (struct driver_private)  = {
            .driver = & globalfifo_driver.driver,
            klist_init(&.klist_devices, NULL, NULL);
            klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

            .kobj = {
                .kset = platform_bus_type->p->drivers_kset,
                .ktype = driver_ktype,

                .kref->refcount->refs       = 1,    // kset_init
                INIT_LIST_HEAD(.entry),
                .state_in_sysfs             = 1,
                .state_add_uevent_sent      = 0,
                .state_remove_uevent_sent   = 0,
                .state_initialized          = 1,
                .name       = "globalfifo_platform",
                .parent     = platform_bus_type->p->drivers_kset->kobj,
            }
        }
    },
    .probe  = globalfifo_probe,
    .remove = globalfifo_remove,
};

driver_attach(&(globalfifo_driver.driver))

/*****  drivers/base/dd.c  *****/
/**
 * driver_attach - try to bind driver to devices.
 * @drv: driver.
 */
int driver_attach(struct device_driver *drv)
{
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

/*****  drivers/base/bus.c  *****/
/**
 * bus_for_each_dev - device iterator.
 * @bus: bus type.
 * @start: device to start iterating from.
 * @data: data for the callback.
 * @fn: function to be called for each device.
 */
int bus_for_each_dev(struct bus_type *bus, struct device *start,
        void *data, int (*fn)(struct device *, void *))
{
    struct klist_iter i;
    struct device *dev;
    int error = 0;

    if (!bus || !bus->p)
        return -EINVAL;

    klist_iter_init_node(&bus->p->klist_devices, &i,
            (start ? &start->p->knode_bus : NULL));
    while (!error && (dev = next_device(&i)))
        error = fn(dev, data);
    klist_iter_exit(&i);
    return error;
}

driver_attach()函数找到驱动依附的总线信息,遍历总线上链表klist_devices得到当前总线上存在的设备,然后调用__driver_attach(dev, drv)函数,尝试将驱动和设备绑定。
__driver_attach(dev, drv)函数包含两个主要的部分:

  • driver_match_device(drv, dev) : 尝试将驱动和设备匹配,返回值指示是否能匹配。
  • device_driver_attach(drv, dev): 将驱动和设备绑定。
static int __driver_attach(struct device *dev, void *data)
{
    struct device_driver *drv = data;
    int ret;

    /*
     * Lock device and try to bind to it. We drop the error
     * here and always return 0, because we need to keep trying
     * to bind to devices and some drivers will return an error
     * simply if it didn't support the device.
     *
     * driver_probe_device() will spit a warning if there
     * is an error.
     */
    ret = driver_match_device(drv, dev);
    if (ret == 0) {
        /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
        driver_deferred_probe_add(dev);
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;
    }   /* ret > 0 means positive match */

    ... ...

    device_driver_attach(drv, dev);
    return 0;
}

driver_match_device(drv, dev)

static inline int driver_match_device(struct device_driver *drv,
        struct device *dev)
{
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

driver_match_device(drv, dev)回调drv->bus->match()函数,对于platform_busplatform_match()
platform_match()函数会依次尝试如下几种方式:

  • driver_override有效时,尝试将驱动名字和driver_override匹配
  • 基于设备树风格的匹配
  • 基于ACPI风格的匹配
  • 匹配ID
  • 匹配platform_device设备名和驱动的名字
/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* When driver_override is set, only bind to the matching driver */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* Attempt an OF style match first */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}

device_driver_attach(drv, dev)

/**
 * device_driver_attach - attach a specific driver to a specific device
 * @drv: Driver to attach
 * @dev: Device to attach it to
 */
int device_driver_attach(struct device_driver *drv, struct device *dev)
{
    int ret = 0;

    __device_driver_lock(dev, dev->parent);

    /*
     * If device has been removed or someone has already successfully
     * bound a driver before us just skip the driver probe call.
     */
    if (!dev->p->dead && !dev->driver)
        ret = driver_probe_device(drv, dev);

    __device_driver_unlock(dev, dev->parent);

    return ret;
}

在驱动和设备匹配成功之后,便将驱动和设备进行绑定。调用driver_probe_device(drv, dev)完成此工作,进一步调用really_probe(dev, drv)

/**
 * driver_probe_device - attempt to bind device & driver together
 * @drv: driver to bind a device to
 * @dev: device to try to bind to the driver
 */
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    int ret = 0;

    if (!device_is_registered(dev))
        return -ENODEV;

    ... ...
    if (initcall_debug)
        ret = really_probe_debug(dev, drv);
    else
        ret = really_probe(dev, drv);
    ... ...
    return ret;
}

really_probe(dev, drv)

/*****  drivers/base/dd.c  *****/
static int really_probe(struct device *dev, struct device_driver *drv)
{
    dev->driver = drv;
    ... ...
    driver_sysfs_add(dev);
    ... ...
    /* Routine to probe device */
    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {
        ret = drv->probe(dev);
        if (ret)
            goto probe_failed;
    }
    ... ...
    driver_bound(dev);
}

really_probe(dev, drv)主要完成的工作包括:

  • 将设备struct devicedriver指针指向globalfifo_driver->driver
  • driver_sysfs_add(dev)完成sysfs中设备和驱动的链接,包括在驱动目录下建立到设备的链接,和在设备目录下建立到驱动的链接。
  • 设备probe函数的调用:优先使用platform_device->bus->probe函数,其次使用platform_driver->probe函数。对于globalfifo_driver,会回调globalfifo_probe(),完成设备的初始化。
  • driver_bound(dev)将设备添加到驱动维护的设备链表中,并发起KOBJ_BIND事件。

platform设备的注册

最后,对设备的注册过程进行简要梳理。
和驱动类似,Linux内核中对依赖于platform总线的设备也定义了特有的结构:platform_device,内部封装了struct device结构。

struct platform_device {
    const char  *name;
    int     id;
    bool        id_auto;
    struct device   dev;
    u32     num_resources;
    struct resource *resource;

    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

globalfifo_driver相对应,同样定义globalfifo_device结构体,成员定义如下:

static struct platform_device globalfifo_device = {
    .name       = "globalfifo_platform",
    .id         = -1,
};

对设备globalfifo_device进行注册的入口函数为platform_device_register(&globalfifo_device)

int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    arch_setup_pdev_archdata(pdev);
    return platform_device_add(pdev);
}

其中device_initialize(&pdev->dev)在第一节platform_bus注册中也提到过,主要对struct device中基本成员进行初始化,包括kobjectstruct device_privatestruct mutex等。着重介绍platform_device_add(pdev)

platform_device_add(&globalfifo_device)

int platform_device_add(struct platform_device *pdev)
{
    int i, ret;

    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus;

    pdev->dev.bus = &platform_bus_type;

    switch (pdev->id) {
        default:
            dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
            break;
        case PLATFORM_DEVID_NONE:
            dev_set_name(&pdev->dev, "%s", pdev->name);
            break;
        case PLATFORM_DEVID_AUTO:
            /*
             * Automatically allocated device ID. We mark it as such so
             * that we remember it must be freed, and we append a suffix
             * to avoid namespace collision with explicit IDs.
             */
            ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
            pdev->id = ret;
            pdev->id_auto = true;
            dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
            break;
    }
    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];
        if (r->name == NULL)
            r->name = dev_name(&pdev->dev);

        p = r->parent;
        if (!p) {
            if (resource_type(r) == IORESOURCE_MEM)
                p = &iomem_resource;
            else if (resource_type(r) == IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p) {
            ret = insert_resource(p, r);
        }
    }

    ret = device_add(&pdev->dev);
    if (ret == 0)
        return ret;
    ... ...
}

platform_device_add(&globalfifo_device)主要工作如下:

  • globalfifo_device.dev.parentglobalfifo_device->dev.bus初始化,分别指向platform_busplatform_bus_type
  • globalfifo_device.dev.kobj->name初始化为globalfifo_device.name (“globalfifo_platform”)。
  • 调用device_add(&globalfifo_device.dev)添加设备。

device_add(&globalfifo_device.dev)

int device_add(struct device *dev)
{
    struct device *parent;
    struct kobject *kobj;
    int error = -EINVAL;

    /* This will incr the ref_count */
    dev = get_device(dev);

    /*  Init dev->p->device = dev  */
    if (!dev->p)
        error = device_private_init(dev);

    /* if init_name exists, use it to initialize dev.kobj->name */
    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name);
        dev->init_name = NULL;
    }

    /* subsystems can specify simple device enumeration */
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

    /* Return ERROR if dev name is not specified */
    if (!dev_name(dev)) {
        error = -EINVAL;
        goto name_error;
    }

    ... ...
    parent = get_device(dev->parent);
    /* get_device_parent(dev, parent) --> platform_bus.kobj */
    kobj = get_device_parent(dev, parent);
    if (kobj)
        dev->kobj.parent = kobj;

    ... ...
    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);

    /* notify platform of device entry */
    error = device_platform_notify(dev, KOBJ_ADD);

    error = device_create_file(dev, &dev_attr_uevent);

    error = device_add_class_symlinks(dev);
    error = device_add_attrs(dev);

    /* Main Entrance to add device into existing bus */
    error = bus_add_device(dev);

    error = dpm_sysfs_add(dev);
    device_pm_add(dev);

    /* Create related node in devfs */
    if (MAJOR(dev->devt)) {
        error = device_create_file(dev, &dev_attr_dev);

        error = device_create_sys_dev_entry(dev);

        devtmpfs_create_node(dev);
    }
    ... ...

    kobject_uevent(&dev->kobj, KOBJ_ADD);

    /* Try to find driver to bind this device */
    bus_probe_device(dev);

    ... ...

}

主要工作如下:

  • globalfifo_device.dev.kobj.parent初始化为&platform_bus.kobj
  • kobject_add()函数初始化globalfifo_device.dev.kobj对象,在sysfs中建立相关的目录,例如/sys/devices/platform/globalfifo_platform
  • bus_add_device(&globalfifo_device.dev):将globalfifo_device注册到总线系统里,并建立sysfs的相关目录:总线系统中建立到设备的链接,同时也在设备目录下建立到总线的subsystem链接。
  • bus_probe_device(dev):尝试在总线上寻找可以绑定的驱动。下文详细介绍。

globalfifo_devices初步初始化后主要成员列举如下:

static struct platform_device globalfifo_device = {
    .name  = "globalfifo_platform",
    .id    = -1,
    .dev   = {
        .parent   = &platform_bus,
        .bus     = &platform_bus_type,
        .p = {
            .device = & globalfifo_device.dev,

            INIT_LIST_HEAD(.klist_children->k_list),
            spin_lock_init(.klist_children->k_lock),
            .klist_children->get    = klist_children_get,
            .klist_children->put    = klist_children_put,

            INIT_LIST_HEAD(&.deferred_probe)
        },

        .kobj       = {
            .name = "globalfifo_platform",
            .kref->refcount->refs       = 1,

            INIT_LIST_HEAD(.entry),
            .state_in_sysfs         = 0,
            .state_add_uevent_sent      = 0,
            .state_remove_uevent_sent   = 0,
            .state_initialized      = 1,
            .kset = devices_kset,
            .ktype = device_ktype,
            .name = "globalfifo_platform",

            .parent = & platform_bus.kobj,
            .sd = {                 //create_dir: /sys/devices/platform/globalfifo_platform
                .parent = platform_bus.kobj.sd,
                .dir.root = platform_bus.kobj.sd->dir.root,
                .ns = NULL,
                .priv = .kobj
            }
        },
        INIT_LIST_HEAD(.dma_pools),
        Mutex_init(.mutex),
        spin_lock_init(.devres_lock),
        INIT_LIST_HEAD(&dev->devres_head),
        device_pm_init(.),
        .numa_node  = -1,
        INIT_LIST_HEAD(&dev->msi_list),
        INIT_LIST_HEAD(&dev->links.consumers);
        INIT_LIST_HEAD(&dev->links.suppliers);
        dev->links.status = DL_DEV_NO_DRIVER;
    },
};

bus_probe_device(&globalfifo_device.dev)

/*****  drivers/base/bus.c  *****/
/**
 * bus_probe_device - probe drivers for a new device
 * @dev: device to probe
 *
 * - Automatically probe for a driver if the bus allows it.
 */
void bus_probe_device(struct device *dev)
{
    struct bus_type *bus = dev->bus;

    if (!bus)
        return;

    if (bus->p->drivers_autoprobe)
        device_initial_probe(dev);
    ... ... 
}

/*****  drivers/base/dd.c  *****/
void device_initial_probe(struct device *dev)
{
    __device_attach(dev, true);
}

static int __device_attach(struct device *dev, bool allow_async)
{
    ... ...
    ret = bus_for_each_drv(dev->bus, NULL, &data,
            __device_attach_driver);
    ... ...
}

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    struct device_attach_data *data = _data;
    struct device *dev = data->dev;
    bool async_allowed;
    int ret;

    ret = driver_match_device(drv, dev);
    if (ret == 0) {
        /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
        driver_deferred_probe_add(dev);
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d", ret);
        return ret;
    } /* ret > 0 means positive match */

    async_allowed = driver_allows_async_probing(drv);

    if (async_allowed)
        data->have_async = true;

    if (data->check_async && async_allowed != data->want_async)
        return 0;

    return driver_probe_device(drv, dev);
}

bus_probe_device(&globalfifo_device.dev)的执行函数路线分析如下所示,经过层层调用,最终又调用到driver_match_device()driver_probe_device()函数,查找总线上能和当前设备匹配的驱动,并将驱动和设备绑定在了一起。

    struct device *dev = &globalfifo_device.dev;
    struct device_attach_data *data = {
        .dev = dev,
        .check_async = allow_async,
        .want_async = false,
    };
    struct device_driver *drv;
---------------------------------------------------
        bus_probe_device(dev)
                |
                V
        device_initial_probe(dev)
                |
                V
        __device_attach(dev, true) 
                |
                V
        bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver)
                |
                V
        __device_attach_driver(drv, data)
                |
                V
        driver_match_device(drv, dev) / driver_probe_device(drv, dev)

总结

综上述分析,可以看到驱动注册的过程中,会尝试寻找总线上可以与之匹配的设备;同样地,设备注册的过程中,也会尝试寻找总线上可以与之绑定的驱动。整个过程中,总线、设备、驱动的关键注册函数分别为:

  • 总线注册:bus_register()
  • 驱动注册:platform_driver_register() --> driver_register() --> bus_add_driver()
  • 设备注册:platform_device_add() --> device_add() --> bus_add_device() / bus_probe_device()

sysfs的角度,可以清楚地看到platform_deviceplatform_driverplatform_bus之间的联系:

/sys/bus/platform/drivers/globalfifo_platform$ ll
total 0
     bind
     globalfifo_platform -> ../../../../devices/platform/globalfifo_platform/
     module -> ../../../../module/globalfifo_platform/
     uevent
     unbind

/sys/bus/platform/devices$ ll
total 0
... ... 
     globalfifo_platform -> ../../../devices/platform/globalfifo_platform/


/sys/devices/platform/globalfifo_platform$ ll
total 0
     driver -> ../../../bus/platform/drivers/globalfifo_platform/
     modalias
     power/
     subsystem -> ../../../bus/platform/
     uevent

/sys/module/globalfifo_platform/drivers$ ll
total 0
     platform:globalfifo_platform -> ../../../bus/platform/drivers/globalfifo_platform/

参考资料

[1] Linux设备驱动开发详解(基于最新的Linux4.0内核),宋宝华编著,2016年
[2] 知识整理–linux设备驱动模型:https://blog.csdn.net/TongxinV/article/details/54853122
[3] linux设备驱动模型:https://blog.csdn.net/qq_40732350/article/details/82992904

原文链接

  • Linux设备驱动模型简述(源码剖析)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1892973.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

检测水管缺水的好帮手-管道光电液位传感器

管道光电液位传感器是现代清水管道管理中的重要技术创新&#xff0c;不仅提高了检测液位的精确度&#xff0c;还解决了传统机械式和电容式传感器存在的诸多问题&#xff0c;成为检测管道缺水的可靠利器。 该传感器采用先进的光学感应原理&#xff0c;利用红外光学组件通过精密…

2024源代码加密软件评测丨保护企业源代码防泄密

为什么需要对源代码进行加密&#xff1f;因为源代码泄密可能会带来一系列严重的后果&#xff1a; 源代码泄密会导致企业的知识产权被盗窃&#xff0c;竞争对手可能会利用这些代码开发类似的产品&#xff0c;从而削弱公司的市场竞争力。 由于知识产权被盗&#xff0c;公司可能会…

【机器学习】机器学习与医疗健康在疾病预测中的融合应用与性能优化新探索

文章目录 引言第一章&#xff1a;机器学习在医疗健康中的应用1.1 数据预处理1.1.1 数据清洗1.1.2 数据归一化1.1.3 特征工程 1.2 模型选择1.2.1 逻辑回归1.2.2 决策树1.2.3 随机森林1.2.4 支持向量机1.2.5 神经网络 1.3 模型训练1.3.1 梯度下降1.3.2 随机梯度下降1.3.3 Adam优化…

【c++】C++ IO流

本专栏内容为&#xff1a;C学习专栏&#xff0c;分为初阶和进阶两部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握C。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&…

使用Vue CLI方式创建Vue3.0应用程序

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统。新版本的 Vue CLI 的包名由原来的 vue-cli 改成了 vue/cli。 在开发大型项目时&#xff0c;需要考虑项目的组织结构、项目构建和部署等问题。如果手动完成这些配置工作&#xff0c;工作效率会非常低。为此&#xff0c;Vue.…

嵌入式Linux系统编程 — 6.5 获取信号的描述信息

目录 1 strsignal()函数 2 psignal()函数 在 Linux 下&#xff0c;每个信号都有一串与之相对应的字符串描述信息&#xff0c;用于对该信号进行相应的描述。这些字符串位于 sys_siglist 数组中&#xff0c; sys_siglist 数组是一个 char *类型的数组&#xff0c;数组中的每一个…

《软件需求》读书笔记

商业的本质是供需和交换。软件行业也一样&#xff0c;生产别人所需要的软件并获得相应回报&#xff0c;就是成功。《软件需求》这本书是一本软件需求领域的工具书&#xff0c;很全面且具体&#xff0c;可以跳读。 在我所工作或了解的软件公司中&#xff0c;发现不论是初创企业…

一个R包完成单细胞基因集富集分析 (全代码)

singleseqgset是用于单细胞RNA-seq数据的基因集富集分析的软件包。它使用简单的基础统计量&#xff08;variance inflated Wilcoxon秩和检验&#xff09;来确定不同cluster中感兴趣的基因集的富集。 Installation library(devtools) install_github("arc85/singleseqgse…

heic文件怎么转换成jpg?苹果手机照片格式heic怎么改jpg?2024新软件!

HEIC作为一种苹果设备的特殊独有图片格式&#xff0c;以其高效节省存储空间的特性&#xff0c;迅速成为苹果手机用户的首选。然而&#xff0c;对于非苹果用户或需要在Windows系统上查看这些照片的用户来说&#xff0c;HEIC格式却带来了诸多不便。因此&#xff0c;本文将详细介绍…

MySQL的安装和环境配置

1.下载MySQL安装MySQL 选Custom选项为高级自定义模式 2.配置MySQL环境 安装好之后&#xff0c;在桌面右键点击我的电脑(有些是此电脑)&#xff0c;然后点击属性&#xff0c;进入系统信息设置&#xff0c;接着点击高级&#xff0c;进入环境变量界面&#xff0c;进入环境变量界面…

MySQL 如何实现将数据实时同步到 ES ?

引言&#xff1a;在现代应用程序开发中&#xff0c;通常会将数据存储在 MySQL 中&#xff0c;用于事务性处理和数据持久化。而 Elasticsearch&#xff08;ES&#xff09;则是一种专门用于全文搜索和分析的强大工具。将这两者结合使用的一个常见需求是实时将 MySQL 中的数据同步…

JAVA导出数据库字典到Excel

文章目录 1、查询某张表字段信息2、TableVo接收sql查询得到的数据3、excel导出4、导出案例 1、查询某张表字段信息 select column_name as columnName, -- 字段名 COLUMN_DEFAULT as colDefault, -- 默认值 column_key as columnKey, -- PRI-主键&#xff0c;UNI-唯一键&…

新能源组合灶,一灶两用(电燃灶+电陶炉),电生明火,无需燃料

在科技日新月异的今天&#xff0c;厨房电器的创新不断为我们的生活带来便捷与惊喜。华火新能源电燃灶&#xff0c;以其独特的设计和卓越的性能&#xff0c;成为未来厨房的首选&#xff0c;为您打造全新的烹饪体验。 中国人的烹饪文化源远流长&#xff0c;讲究火候的掌控和明火烹…

linux centos tomcat 不安全的HTTP请求方法

1、页面查看 2、在linux主机可使用此命令查看 curl -v -X OPTIONS http://实际地址 3、进入tomcat conf目录vim web.xml&#xff0c;增加以下内容 <!-- close insecure http methods --> <security-constraint><web-resource-collection><web-resource…

Java SpringBoot MongoPlus 使用MyBatisPlus的方式,优雅的操作MongoDB

Java SpringBoot MongoPlus 使用MyBatisPlus的方式&#xff0c;优雅的操作MongoDB 介绍特性安装新建SpringBoot工程引入依赖配置文件 使用新建实体类创建Service测试类进行测试新增方法查询方法 官方网站获取本项目案例代码 介绍 Mongo-Plus&#xff08;简称 MP&#xff09;是一…

AI写作神器大揭秘:五款你不可错过的AI写作工具

在现实生活中&#xff0c;除了专业的文字工作者&#xff0c;各行各业都避免不了需要写一些东西&#xff0c;比如策划案、论文、公文、讲话稿、总结计划……等等。而随着科技的进步&#xff0c;数字化时代的深入发展&#xff0c;AI已经成为日常工作中必不可少的工具了&#xff0…

Cesium 立式雷达扫描

Cesium 立式雷达扫描 自定义 Primitive 实现支持水平和垂直交替扫描

WebKey备受瞩目的Web3.0新叙事,硬件与加密生态完美融合特性成为数字世界的新入口

在当今迅速发展的科技领域&#xff0c;Web3.0正在引领一场颠覆性的变革。而作为这一变革的先锋&#xff0c;WebKey无疑是备受瞩目的创新项目。它不仅代表了一种全新的技术趋势&#xff0c;更是数字世界中硬件与加密生态完美融合的典范。 硬件与加密生态的完美融合 WebKey的核心…

海豚调度监控:新增依赖缺失巡检,上游改动再也不用担心了!

&#x1f4a1; 本系列文章是 DolphinScheduler 由浅入深的教程&#xff0c;涵盖搭建、二开迭代、核心原理解读、运维和管理等一系列内容。适用于想对 DolphinScheduler了解或想要加深理解的读者。 祝开卷有益:) 用过 DolphinScheduler 的小伙伴应该都知道&#xff0c;Dolphin…

Echarts中的折线图,多个Y轴集中在左侧(在Vue中使用多个Y轴的折线图)

简述&#xff1a;在 ECharts 中&#xff0c;创建一个带有多个 Y 轴的折线图&#xff0c;并且将这些 Y 轴都集中显示在图表的左侧&#xff0c;可以通过合理配置 yAxis 和 series 的属性来实现。简单记录 一. 函数代码 drawCarNumEcs() {// 初始化echarts图表,并绑定到id为"…