Linux设备驱动模型简述(源码剖析)
1. 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
drwxr-xr-x root root Aug : block/
drwxr-xr-x root root Aug : bus/
drwxr-xr-x root root Aug : class/
drwxr-xr-x root root Aug : dev/
drwxr-xr-x root root Aug : devices/
drwxr-xr-x root root Aug : firmware/
drwxr-xr-x root root Aug : fs/
drwxr-xr-x root root Sep : hypervisor/
drwxr-xr-x root root Aug : kernel/
drwxr-xr-x root root Aug : module/
drwxr-xr-x root root Aug : 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
... Sep : ::00.0 -> ../../../../devices/pci0000:/::1c./::00.0/
... Sep : ::00.1 -> ../../../../devices/pci0000:/::1c./::00.1/
...
对应地,在 /sys/devices/ 目录下,可以看到设备存在一个指向igb的 driver 项:
/sys/devices/pci0000:/::1c./::00.0$ ll
total
...
lrwxrwxrwx root root Aug : driver -> ../../../../bus/pci/drivers/igb/
...
同样地, /sys/bus/pci/devices 目录下可以找到指向同样设备的一个链接:
/sys/bus/pci/devices$ ll
total
...
... Aug : ::00.0 -> ../../../devices/pci0000:/::1c./::00.0/
... Aug : ::00.1 -> ../../../devices/pci0000:/::1c./::00.1/
...
对于早期的Linux内核(2.6版本以前)来说,通常在驱动代码中xxx_driver
注册过程中调用 probe() 函数来对设备进行初始化。
引入Linux设备驱动模型下,设备和驱动可以分开注册,依赖总线完成相互绑定。系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,系统每注册一个驱动的时候,会寻找与之匹配的设备。这个过程中,设备和驱动的匹配工作由总线完成。
下文中将会用关键的内核源码说明驱动和设备间匹配机制的实现,分析的过程中以platform总线为例。
platform总线是一种虚拟的总线,与之相对应的是PCI、I2C、SPI等实体总线。引入虚拟platform总线是为了解决某些设备无法直接依附在现有实体总线上的问题,例如SoC系统中集成的独立外设控制器,挂接在SoC内存空间的外设等等。
2. 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;
}
2.1 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 目录的建立。
2.2 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 = ; /* 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 ;
}
bus_register(&platform_bus_type) 将总线 platform 注册到 Linux 的总线系统中,主要完成了 subsystem 的注册,对 struct subsys_private 结构进行了初始化,具体包括:
- platform_bus_type->p->drivers_autoprobe =
- 对 struct kset 类型成员sysfs进行初始化,作为子系统中 kobject 对象的 parent 。 kset 本身也包含 kobject 对象,在sysfs中也表现为一个目录,即 /sys/bus/platform 。
- 建立 struct kset 类型的 drivers_kset 和 devices_kset ,作为总线下挂载的所有驱动和设备的集合,sysfs中表现为 /sys/bus/platform/drivers 和 /sys/bus/platform/devices 。
- 初始化链表 klist_drivers 和 klist_devices ,将总线下的驱动和设备分别链接在一起。
- 增加 probe 文件,对应 /sys/bus/platform 目录的文件 drivers_autoprobe 和 drivers_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 = , // kset_init()
INIT_LIST_HEAD(.entry),
.state_in_sysfs = ,
.state_add_uevent_sent = , // kset_register()
.state_remove_uevent_sent = ,
.state_initialized = ,
.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 =
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 = , // kset_init
INIT_LIST_HEAD(.entry),
.state_in_sysfs = ,
.state_add_uevent_sent = , // kset_register
.state_remove_uevent_sent = ,
.state_initialized = , .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,
}
}
};
3. 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_driver 的bus、probe、remove等回调函数进行初始化,紧接着调用 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);
}
3.1 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 已经被注册并初始化(必要条件)。
- 对bus、probe、remove等回调函数初始化进行判断,保证总线和驱动上相应的函数只能存在一个。
- driver_find() 查找总线上是否已存在当前驱动的同名驱动。
- bus_add_driver(&(globalfifo_driver.driver)) ,将驱动注册到总线上,下文详述。
- 发起 KOBJ_ADD 类型uevent,指示驱动已经添加完成,TODO。
3.2 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 = ; 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 ;
}
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 = , // kset_init
INIT_LIST_HEAD(.entry),
.state_in_sysfs = ,
.state_add_uevent_sent = ,
.state_remove_uevent_sent = ,
.state_initialized = ,
.name = "globalfifo_platform",
.parent = platform_bus_type->p->drivers_kset->kobj,
}
}
},
.probe = globalfifo_probe,
.remove = globalfifo_remove,
};
3.3 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 = ; 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 == ) {
/* no match */
return ;
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
driver_deferred_probe_add(dev);
} else if (ret < ) {
dev_dbg(dev, "Bus failed to match device: %d", ret);
return ret;
} /* ret > 0 means positive match */ ... ... device_driver_attach(drv, dev);
return ;
}
3.4 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) : ;
}
driver_match_device(drv, dev) 回调 drv->bus->match() 函数,对于platform_bus为 platform_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 ; /* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return ; /* 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) == );
}
3.5 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 = ; __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 = ; if (!device_is_registered(dev))
return -ENODEV; ... ...
if (initcall_debug)
ret = really_probe_debug(dev, drv);
else
ret = really_probe(dev, drv);
... ...
return ret;
}
3.6 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 device 中 driver 指针指向 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 事件。
4. 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 = -,
};
对设备 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 中基本成员进行初始化,包括 kobject 、 struct device_private 、 struct mutex 等。着重介绍 platform_device_add(pdev) 。
4.1 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, , , GFP_KERNEL);
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}
for (i = ; 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 == )
return ret;
... ...
}
platform_device_add(&globalfifo_device) 主要工作如下:
- 对 globalfifo_device.dev.parent 和 globalfifo_device->dev.bus 初始化,分别指向 platform_bus 和 platform_bus_type 。
- globalfifo_device.dev.kobj->name 初始化为 globalfifo_device.name (“globalfifo_platform”)。
- 调用 device_add(&globalfifo_device.dev) device_add(&globalfifo_device.dev) 添加设备。
4.2 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_device 初步初始化后主要成员列举如下:
static struct platform_device globalfifo_device = {
.name = "globalfifo_platform",
.id = -,
.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 = , INIT_LIST_HEAD(.entry),
.state_in_sysfs = ,
.state_add_uevent_sent = ,
.state_remove_uevent_sent = ,
.state_initialized = ,
.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 = -,
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;
},
};
4.3 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 == ) {
/* no match */
return ;
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
driver_deferred_probe_add(dev);
} else if (ret < ) {
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 ; 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)
5. 总结
综上述分析,可以看到驱动注册的过程中,会尝试寻找总线上可以与之匹配的设备;同样地,设备注册的过程中,也会尝试寻找总线上可以与之绑定的驱动。整个过程中,总线、设备、驱动的关键注册函数分别为:
- 总线注册: bus_register()
- 驱动注册: platform_driver_register()
--> driver_register() --> bus_add_driver()
- 设备注册: platform_device_add()
--> device_add() --> bus_add_device() / bus_probe_device()
从sysfs的角度,可以清楚地看到 platform_device 、 platform_driver 、 platform_bus 之间的联系:
/sys/bus/platform/drivers/globalfifo_platform$ ll
total
bind
globalfifo_platform -> ../../../../devices/platform/globalfifo_platform/
module -> ../../../../module/globalfifo_platform/
uevent
unbind /sys/bus/platform/devices$ ll
total
... ...
globalfifo_platform -> ../../../devices/platform/globalfifo_platform/ /sys/devices/platform/globalfifo_platform$ ll
total
driver -> ../../../bus/platform/drivers/globalfifo_platform/
modalias
power/
subsystem -> ../../../bus/platform/
uevent /sys/module/globalfifo_platform/drivers$ ll
total
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设备驱动模型简述(源码剖析)的更多相关文章
- Linux设备驱动模型之platform(平台)总线详解
/********************************************************/ 内核版本:2.6.35.7 运行平台:三星s5pv210 /*********** ...
- LINUX设备驱动模型之class
转自 https://blog.csdn.net/qq_20678703/article/details/52754661 1.LINUX设备驱动模型中的bus.device.driver,.其中bu ...
- Linux设备驱动模型底层架构及组织方式
1.什么是设备驱动模型? 设备驱动模型,说实话这个概念真的不好解释,他是一个比较抽象的概念,我在网上也是没有找到关于设备驱动模型的一个定义,那么今天就我所学.所了解 到的,我对设备驱动模型的一个理解: ...
- 探究linux设备驱动模型之——platform虚拟总线(一)
说在前面的话 : 设备驱动模型系列的文章主要依据的内核版本是2.6.32的,因为我装的Linux系统差不多就是这个版本的(实际上我用的fedora 14的内核版本是2.6.35.13的.) ...
- Linux 设备驱动模型
Linux系统将设备和驱动归一到设备驱动模型中了来管理 设备驱动程序功能: 1,对硬件设备初始化和释放 2,对设备进行管理,包括实参设置,以及提供对设备的统一操作接口 3,读取应用程序传递给设备文件的 ...
- linux设备驱动模型之Kobject、kobj_type、kset【转】
本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/74838165 版权声明:本文为博主原创文章,转载请注明http://blog.c ...
- linux设备驱动模型
尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要. Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统 ...
- Linux设备驱动模型【转】
本文转载自:http://blog.csdn.net/xiahouzuoxin/article/details/8943863 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+ ...
- linux设备驱动模型(kobject与kset)
Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...
随机推荐
- 为什么你的 64-bit 程序可能占用巨大的虚拟空间
出于很多目的,我从最新的 Go 系统内核开发源码复制了一份代码,在一个正常的运行环境中构建(和重新构建)它,在构建版本基础上周期性地重新构建 Go 程序.近期我在用 ps 查看我的一个程序的内存使用情 ...
- DRF内置认证组件之自定义认证系统
自定义token认证 我们知道,在django项目中不管路由以及对应的视图类是如何写的,都会走到 dispatch 方法,进行路由分发, 在阅读 APIView类中的dispatch 方法的源码中,有 ...
- SVN的基本使用
2020年7月6日 为什么需要版本控制? 需要清晰地保存某些文件的不同修订版本 控制文件的发屐过程,找出导致 BUG 的原因 轻松将项目或文件恢复到指定版本 极大方便团队之间协同开发,防止出现混乱 在 ...
- 【EFCore】利用Entityframework Core创建数据库模型
利用Entityframework Core创建数据库模型 本文中Entityframework Core版本为v3.1.6 简介 Entity Framework (EF) Core 是微软轻量化. ...
- Socket原理及实践(Java/C/C++)
原理 基本概念 什么是TCP/IP.UDP? 详细的可以看一下这个:学习计算机网络知识只要一篇就够了! TCP/IP(Transmission Control Protocol/Internet Pr ...
- Scala中做简易wordCount
使用foldLeft函数,实现简易的wordCount import scala.collection.mutable object Demo_019 { def main(args: Array[S ...
- 根据appid跳到App Store某个APP的详情页
需求 本手机是否装了某个APP 示例百度appid 382201985 scheme BaiduSSO:// 1.是,直接打开百度APP 2.否,跳到App Store百度APP的详情页 NSStr ...
- window下git多账户管理
前言 一般情况下,我们都是一台电脑配置一个Git账号,使用如下命令: git config --global user.name "your name" git config -- ...
- Python 为什么没有 void 关键字?
void 是编程语言中最常见的关键字之一,从字面上理解,它是"空的.空集.空白"的意思,最常用于 表示函数的一种返回值类型. 维基百科上有一个定义: The void type, ...
- golang 判断IPv4 or IPv6 address
import strings func IsIPv4(address string) bool { return strings.Count(address, ":") < ...