Linux 内核:设备驱动模型(2)driver-bus-device与probe

系列:Linux 内核:设备驱动模型 学习总结

参考:

背景

基于 Linux 3.14 来简单分析设备驱动模型。

前言

对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要:以后看具体的总线设备模型时会更加清晰。

建议先看了解:kobject、kset和ktype

Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述。

换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。

在内核里,有各种各样的总线,如 usb_bus_typespi_bus_typepci_bus_typeplatform_bus_typei2c_bus_type 等,内核通过总线将设备与驱动分离。

设备模型是层次的结构,层次的每一个节点都是通过kobject实现的,在文件上则体现在sysfs文件系统。

关于kobkect,如果不清楚请移步: http://blog.csdn.net/lizuobin2/article/details/51523693

关于 uevet 、mdev 前面也说过了,请参考: http://blog.csdn.net/lizuobin2/article/details/51534385

kobject 结构可能的层次结构如图:

对于整个 设备总线驱动模型 的样子,大概如下图吧,也并不复杂。

简单来说,bus 负责维护 注册进来的devcie driver ,每注册进来一个device 或者 driver 都会调用 Bus->match 函数 将devicedriver 进行配对,并将它们加入链表。

如果配对成功,调用Bus->probe或者driver->probe函数, 调用kobject_uevent函数设置环境变量(通知用户空间),mdev进行创建设备节点等操作。

我们从 Busdriverdevice三个部分进行详细的分析。

总线:

总线(bus)是linux发展过程中抽象出来的一种设备模型,为了统一管理所有的设备,内核中每个设备都会被挂载在总线上,这个bus可以是对应硬件的bus(i2c bus、spi bus)、可以是虚拟bus(platform bus)。

bus将所有挂在上面的具体设备抽象成两部分,device_driverdevice

driver与device:

driver实现了同类型设备的驱动程序实现,而device则向系统注册具体的设备需要的资源,每当添加一个新的driver(device)到bus中时,都将调用bus的match函数,试图寻找匹配的device(driver)。

如果匹配成功,就调用probe函数,在probe函数中实现设备的初始化、各种配置以及生成用户空间的文件接口。

probe函数是总线在匹配成功时调用的函数,bus->probedrv->probe中只会有一个起效,同时存在时使用bus->probe

初始化

driver_init

所有的bus都是在buses_init,kernel启动以后,进行初始化,最终执行到:

// init/main.c
kernel_init();
kernel_init_freeable();
do_basic_setup();
driver_init(); // 注意这个
do_initcalls();

看看driver_init做了什么:

// drivers/base/init.c
void __init driver_init(void)
{
/* These are the core pieces */
devtmpfs_init();
devices_init(); // 初始化device
buses_init(); // 初始化bus
classes_init();
firmware_init();
hypervisor_init(); /* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
cpu_dev_init();
memory_dev_init();
container_dev_init();
}

注意devices_initbuses_init这两个函数会创建一些对应的对象,我们能够在sysfs中看到这些对应的对象,在后续中会用到。。

devices_init

// driversbase/base.h
struct kset *devices_kset;
extern struct kset *devices_kset; // drivers/base/core.c
#include "base.h"
struct kset *devices_kset;
int __init devices_init(void)
{
// 创建 /sys/devices
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
// 创建 /sys/dev
dev_kobj = kobject_create_and_add("dev", NULL);
// 创建 /sys/dev/block
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
// 创建 /sys/dev/char
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj); return 0;
}

buses_init

// drivers/base/bus.c
static struct kset *system_kset;
static struct kset *bus_kset; int __init buses_init(void)
{
// 创建 /sys/bus
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM; // 创建 /sys/devices/system
system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
if (!system_kset)
return -ENOMEM; return 0;
}

同样是调用了kset_create_and_add,这里涉及到了ksetkobject这些概念。

kset_create_and_add

kobject_create_and_add这个函数首先会调用kobject_create来分配并初始化一个kobject对象,然后调用kobject_add函数在sysfs文件系统中为新生成的kobject对象建立一个新的目录。

// lib/kobject.c
/**
* kset_create_and_add - create a struct kset dynamically and add it to sysfs
*
* @name: the name for the kset
* @uevent_ops: a struct kset_uevent_ops for the kset
* @parent_kobj: the parent kobject of this kset, if any.
*
* This function creates a kset structure dynamically and registers it
* with sysfs. When you are finished with this structure, call
* kset_unregister() and the structure will be dynamically freed when it
* is no longer being used.
*
* If the kset was not able to be created, NULL will be returned.
*/
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int error; kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);

此后,其他bus通过 bus_register 进行注册,实际上会注册到 bus_kest 中。

bus

bus_type原型

// include/linux/device.h
/**
* struct bus_type - The bus type of the device
*
* @name: The name of the bus.
* @dev_name: Used for subsystems to enumerate devices like ("foo%u", dev->id).
* @dev_root: Default device to use as the parent.
* @dev_attrs: Default attributes of the devices on the bus.
* @bus_groups: Default attributes of the bus.
* @dev_groups: Default attributes of the devices on the bus.
* @drv_groups: Default attributes of the device drivers on the bus.
* @match: Called, perhaps multiple times, whenever a new device or driver
* is added for this bus. It should return a nonzero value if the
* given device can be handled by the given driver.
* @uevent: Called when a device is added, removed, or a few other things
* that generate uevents to add the environment variables.
* @probe: Called when a new device or driver add to this bus, and callback
* the specific driver's probe to initial the matched device.
* @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device.
*
* @online: Called to put the device back online (after offlining it).
* @offline: Called to put the device offline for hot-removal. May fail.
*
* @suspend: Called when a device on this bus wants to go to sleep mode.
* @resume: Called to bring a device on this bus out of sleep mode.
* @pm: Power management operations of this bus, callback the specific
* device driver's pm-ops.
* @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU
* driver implementations to a bus and allow the driver to do
* bus-specific setup
* @p: The private data of the driver core, only the driver core can
* touch this.
* @lock_key: Lock class key for use by the lock validator
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
* it is an internal, virtual, "platform" bus. Buses can plug into each other.
* A USB controller is usually a PCI device, for example. The device model
* represents the actual connections between buses and the devices they control.
* A bus is represented by the bus_type structure. It contains the name, the
* default attributes, the bus' methods, PM operations, and the driver core's
* private data.
*/
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups; int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev); int (*online)(struct device *dev);
int (*offline)(struct device *dev); int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev); const struct dev_pm_ops *pm; struct iommu_ops *iommu_ops; struct subsys_private *p;
struct lock_class_key lock_key;
};

注册bus:bus_register

/**
* bus_register - register a bus with the system.
* @bus: bus.
*
* Once we have that, we registered the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the bus.
*/
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv; priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
/* 1. bus 与 prv 相互建立联系 */
// 私有数据 .bus -> bus 本身
priv->bus = bus;
// bus->p 指向 priv
bus->p = priv;
// 内核通知链
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); /* 设置 bus->prv->subsys->kobj */
// 设置 priv->subsys.kobj.name = bus->name 对应于/sys/ 目录下的目录名
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
// 所有的 priv->subsys.kobj.kset 指向 bus_kse 对应于图中④与六的关系
priv->subsys.kobj.kset = bus_kset;
// 所有的priv->subsys.kobj.ktype 等于 bus_ktype
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1; /* 注册 kset (bus->prv->subsys priv->devices_kset priv->drivers_kset) */
// 注册 priv->subsys ,由于 priv->subsys.kobj.kset = bus_kset,所以会在 /sys/bus/目录下创建 目录 如/sys/bus/plateform
retval = kset_register(&priv->subsys);
// sysfs_create_file(&bus->p->subsys.kobj, &bus_attr_uevent->attr);
retval = bus_create_file(bus, &bus_attr_uevent); // 由于 priv->subsys.kobj.kset = bus_kset ,因此会创建 /sys/bus/XXX/devices 目录 如 /sys/bus/plateform/devices
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj); // 同理 创建 /sys/bus/XXX/devices 目录 如 /sys/bus/plateform/drivers
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj); // 初始化 klist_devices 并设置get put 函数 初始化 klist_drivers 不知为何没有get put ?
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL); retval = add_probe_files(bus); // 添加 bus->attrs 属性文件
retval = bus_add_attrs(bus); pr_debug("bus: '%s': registered\n", bus->name);
return 0;
}
EXPORT_SYMBOL_GPL(bus_register);

目前,能通过 bus_register 函数处理的工作有:

1、将 Bus 与 priv 相互建立联系,用于处理私有数据

2、注册通知链BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

3、设置bus->priv->subsys(kset).kobj的名字为 bus->name

4、设置 bus->priv->subsys(kset).kobj.kset 指向 bus_kset

5、设置 bus->priv->subsys(kset).kobj.ktype 为 bus_ktype ,提供 show store 函数

6、设置 bus->priv->drivers_autoprobe = 1;

7、注册 bus->priv->subsys(kset) :对应于图中④与⑥的关系

由于4,且没有指定bus->priv->subsys(kset).kobj.Parent,会将 bus_kest.kobj 设置为 bus->priv->subsys(kset).kobj.Parent

因此,会将bus->priv->subsys(kset).kobj.entry 加入bus_kest链表,且会在/sys/bus目录下创建相应的总线目录/sys/bus/$(bus->name),例如 /sys/bus/platform

8、创建 bus_attr_uevent->attr 属性文件

9、创建并注册 devices_kset devices_kset.kobj.parent = bus->priv->subsys.kobj ,名字为 device ,因此会创建 /sys/bus/$(bus->name)/devices

10、创建并注册 drivers_ksetdrivers_kset.kobj.parent = bus->priv->subsys.kobj ,名字为 drivers ,因此会创建 /sys/bus/$(bus->name)/drivers

11、初始化 bus->priv->klist_devices 链表

12、初始化 bus->priv->klist_drivers 链表

13、创建 bus->bus_attrs 属性文件

例子(bus)

下面来看个例子 ,修改自LDD3 。

lddbus.h

/*
* Definitions for the virtual LDD bus.
*
* lddbus.h
*/ extern struct device ldd_bus;
extern struct bus_type ldd_bus_type; /*
* The LDD driver type.
*/ struct ldd_driver {
char *version;
struct module *module;
struct device_driver driver;
struct driver_attribute version_attr;
}; #define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver); /*
* A device type for things "plugged" into the LDD bus.
*/ struct ldd_device {
char *name;
struct ldd_driver *driver;
struct device dev;
}; #define to_ldd_device(dev) container_of(dev, struct ldd_device, dev); extern int register_ldd_device(struct ldd_device *);
extern void unregister_ldd_device(struct ldd_device *);
extern int register_ldd_driver(struct ldd_driver *);
extern void unregister_ldd_driver(struct ldd_driver *);

lddbus.c

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include "lddbus.h" MODULE_AUTHOR("Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "$Revision: 1.9 $\n"; //--------------------------------- bus ---------------------------------------- static int ldd_match(struct device *dev, struct device_driver *drv)
{
struct ldd_device *pdev = to_ldd_device(dev); return !strncmp(pdev->name, drv->name, strlen(drv->name));
} struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
}; //--------------------------------- device -------------------------------------- static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, strlen(Version), "%s\n", Version);
} static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); // parent device
static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG "lddbus release\n");
}
static void ldd_dev_release(struct device *dev){ } struct device ldd_bus = {
.init_name = "ldd0", // ldd0 就是总线的名字,这里改成 ldd_bus 更恰当
.release = ldd_bus_release
}; int register_ldd_device(struct ldd_device *ldddev)
{ ldddev->dev.bus = &ldd_bus_type;
ldddev->dev.parent = &ldd_bus;
ldddev->dev.release = ldd_dev_release;
return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device); void unregister_ldd_device(struct ldd_device *ldddev)
{
device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device); //--------------------------------- driver -------------------------------------- static ssize_t show_version(struct device_driver *driver, char *buf)
{
struct ldd_driver *ldriver = to_ldd_driver(driver); sprintf(buf, "%s\n", ldriver->version);
return strlen(buf);
} int register_ldd_driver(struct ldd_driver *driver)
{
int ret; driver->driver.bus = &ldd_bus_type;
ret = driver_register(&driver->driver);
if (ret)
return ret;
driver->version_attr.attr.name = "version";
//driver->version_attr.attr.owner = driver->module;
driver->version_attr.attr.mode = S_IRUGO;
driver->version_attr.show = show_version;
driver->version_attr.store = NULL;
return driver_create_file(&driver->driver, &driver->version_attr);
} void unregister_ldd_driver(struct ldd_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver); //--------------------------------- bus ---------------------------------------- static int __init ldd_bus_init(void)
{
int ret;
device_register(&ldd_bus);
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Unable to create version attribute\n"); return ret;
} static void ldd_bus_exit(void)
{
bus_unregister(&ldd_bus_type);
} module_init(ldd_bus_init);
module_exit(ldd_bus_exit);

Makefile

EXTRA_CFLAGS += $(DEBFLAGS)
#EXTRA_CFLAGS += -I$(INCDIR) ########## change your module name here
MODULE = lddbus ########## change your obj file(s) here
$(MODULE)-objs:= lddbus.o
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#ARCH ?= arm ifneq ($(KERNELRELEASE), )
obj-m := $(MODULE).o else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd) all:
$(MAKE_BEGIN)
@echo
@if \
$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \
then $(MAKE_DONE);\
else \
$(MAKE_ERR);\
exit 1; \
fi endif show:
@echo "ARCH : ${ARCH}"
@echo "CC : ${CROSS_COMPILE}gcc"
@echo "KDIR : ${KERNELDIR}"
@echo "$(MODULE): $(ALLOBJS)"
clean:
$(CLEAN_BEGIN)
rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
$(CLEAN_END) .PHONY:all clean show
#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### nothing
#OFFSET=\e[21G # 21 col
COLOR1=\e[32m # all --> bule
COLOR2=\e[33m # clean --> brown
COLOR3=\e[31m # error --> red
RESET=\e[0m CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)" MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
### I do not forget "@", but it DOES NOT need "@"
MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"
### nothing end here
#$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=${CROSS_COMPILE} INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \ ############# Makefile end here

测试

[root@FriendlyARM /]# cd sys
[root@FriendlyARM /sys]# ls
block class devices fs module
bus dev firmare kernel
[root@FriendlyARM isys]# cd bus
[root@FriendlyARM bus]# ls
hid ldd platform sdio usb-serial i2c mmc scsi usb
[root@FriendlyARM bus]# cd ldd
[root@FriendlyARM ldd]# ls
devices drivers_autoprobe uevent
drivers drivers_probe version

insmod bus.ko 之后发现,/sys/bus 目录下多了一个 ldd目录,这个目录就是我们向内核注册的 总线 ldd 。

该目录下有一个devices 和 drivers目录,因为现在并没有向该总线注册任何的驱动和设备,因此这两个文件夹是空的。

[root@FriendlyARM ldd]#ls devices
[root@FriendlyARM ldd]#ls drivers
[root@FriendlyARM ldd]#

cat version 会调用show函数,显示我们在 Bus 中设置的属性。

[root@FriendlyARM ldd]#cat version
$Revision: 1.9

driver

整体流程

driver_register(drv) [core.c]
bus_add_driver(drv) [bus.c]
if (drv->bus->p->drivers_autoprobe)
driver_attach(dev)[dd.c]
bus_for_each_dev(dev->bus, NULL, drv,__driver_attach)
__driver_attach(dev, drv) [dd.c]
driver_match_device(drv, dev) [base.h]
drv-bus->match ? drv->bus-amatch(dev, drv) : 1
if false, return;
driver_probe_device(drv, dev) [dd.c]
really_probe(dev, drv) [dd.c]
dev-driver = drv;
if (dev-bus->probe)
dev->bus->probe(dev);
else if (drv->probe)
drv-aprobe(dev);
probe_failed:
dev->-driver = NULL;

device_driver原型

// include/linux/device.h
/**
* struct device_driver - The basic device driver structure
* @name: Name of the device driver.
* @bus: The bus which the device of this driver belongs to.
* @owner: The module owner.
* @mod_name: Used for built-in modules.
* @suppress_bind_attrs: Disables bind/unbind via sysfs.
* @of_match_table: The open firmware table.
* @acpi_match_table: The ACPI match table.
* @probe: Called to query the existence of a specific device,
* whether this driver can work with it, and bind the driver
* to a specific device.
* @remove: Called when the device is removed from the system to
* unbind a device from this driver.
* @shutdown: Called at shut-down time to quiesce the device.
* @suspend: Called to put the device to sleep mode. Usually to a
* low power state.
* @resume: Called to bring a device from sleep mode.
* @groups: Default attributes that get created by the driver core
* automatically.
* @pm: Power management operations of the device which matched
* this driver.
* @p: Driver core's private data, no one other than the driver
* core can touch this.
*
* The device driver-model tracks all of the drivers known to the system.
* The main reason for this tracking is to enable the driver core to match
* up drivers with new devices. Once drivers are known objects within the
* system, however, a number of other things become possible. Device drivers
* can export information and configuration variables that are independent
* of any specific device.
*/
struct device_driver {
const char *name;
struct bus_type *bus; struct module *owner;
const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table; int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups; const struct dev_pm_ops *pm; struct driver_private *p;
};

注册驱动并匹配:driver_register

// 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; // 判断是否被注册过了。
other = driver_find(drv->name, drv->bus);
if (other) {
return -EBUSY;
} // 1、添加驱动到bus中
ret = bus_add_driver(drv);
// 2、
ret = driver_add_groups(drv, drv->groups);
kobject_uevent(&drv->p->kobj, KOBJ_ADD); return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

driver_register做了这几件事情:

1、判断driver是否被注册过:通过名字查找总线中是否已经存在同名的对象

2、把驱动添加进bus中,

3、进行通知到用户空间。

在bus_add_driver中注册

// drivers/base/base.h
struct driver_private {
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_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; // 在/sys/bus/xxx/drivers 目录下创建目录
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name); // 1、匹配 dev
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
} // 将 driver 加入 Bus 的 drivers 链表中
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
// 如果设置了drv->mod_name 根据名字寻找模块
module_add_driver(drv->owner, drv); // 在/sys/bus/xxx/drivers/创建属性文件
error = driver_create_file(drv, &driver_attr_uevent); error = driver_add_attrs(bus, drv); if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
} kobject_uevent(&priv->kobj, KOBJ_ADD);
return 0;
}

在向Bus注册一个driver时,会调用到 driver_attch来寻找与之配对的 deivice

driver_attach

从逻辑上来说,一个驱动可以支持多个设备;一个设备只能绑定一个驱动。

因此,driver_attach最终一一遍历目前所有的驱动和设备,并绑定对应的设备。

// drivers/base/dd.c
/**
* driver_attach - try to bind driver to devices.
* @drv: driver.
*
* Walk the list of devices that the bus has on it and try to
* match the driver with each one. If driver_probe_device()
* returns 0 and the @dev->driver is set, we've found a
* compatible pair.
*/
int driver_attach(struct device_driver *drv)
{
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);
遍历bus_for_each_dev
/**
* 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.
*
* Iterate over @bus's list of devices, and call @fn for each,
* passing it @data. If @start is not NULL, we use that device to
* begin iterating from.
*
* We check the return of @fn each time. If it returns anything
* other than 0, we break out and return that value.
*
* NOTE: The device that returns a non-zero value is not retained
* in any way, nor is its refcount incremented. If the caller needs
* to retain this data, it should do so, and increment the reference
* count in the supplied callback.
*/
int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
// 迭代器,在这里用于遍历device
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 ((dev = next_device(&i)) && !error)
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);

根据名字我们应该能猜测出来,调用Bus的每一个 dev 与 driver 进行 __driver_attach

具体的做法是通过初始化一个迭代器指向链表的头部(在这里是bus->p->klist_devices),然后通过next_device进行遍历,并逐一执行fn方法。

关于迭代器的函数我这里贴出来,但是不再做深入的解释了。

// lib/klist.c
void klist_iter_init_node(struct klist *k, struct klist_iter *i,
struct klist_node *n)
{
i->i_klist = k;
i->i_cur = n;
if (n)
kref_get(&n->n_ref);
}
EXPORT_SYMBOL_GPL(klist_iter_init_node); // drivers/base/core.c
static struct device *next_device(struct klist_iter *i)
{
struct klist_node *n = klist_next(i);
struct device *dev = NULL;
struct device_private *p; if (n) {
p = to_device_private_parent(n);
dev = p->device;
}
return dev;
} // drivers/base/base.h
#define to_device_private_parent(obj) \
container_of(obj, struct device_private, knode_parent)

我们看看fn,在这里它执行的是__driver_attach

__driver_attach
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data; /*
* 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.
*/ // 1、匹配 现有的 drv 与 现在的 dev
if (!driver_match_device(drv, dev))
return 0; if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
// 2、从这里开始probe
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent); return 0;
}

在 __driver_attach 中,首先会调用到 driver_match_device 函数(return drv->bus->match ? drv->bus->match(dev, drv) : 1;)进行匹配:

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

match方法存在时,进行匹配,返回0代表成功。否则代表失败。

如果匹配成功,则继续调用 driver_probe_device(drv, dev)

// drivers/base/dd.c
/**
* 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
*
* This function returns -ENODEV if the device is not registered,
* 1 if the device is bound successfully and 0 otherwise.
*
* This function must be called with @dev lock held. When called for a
* USB interface, @dev->parent lock must be held as well.
*/
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0; if (!device_is_registered(dev))
return -ENODEV; pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name); pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_request_idle(dev); return ret;
}

在really_probe中probe

device原型
// include/linux/device.h
struct device {
struct device *parent; struct device_private *p; struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type; struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/ struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain; struct device_dma_parameters *dma_parms; struct list_head dma_pools; /* dma pools (if dma'ble) */ struct device_node *of_node; /* associated device tree node */
struct acpi_dev_node acpi_node; /* associated ACPI device node */ dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */ spinlock_t devres_lock;
struct list_head devres_head; struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev);
struct iommu_group *iommu_group; bool offline_disabled:1;
bool offline:1;
};
really_probe
// drivers/base/dd.c
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0; atomic_inc(&probe_count);
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head)); // 1、关联 dev 与 drv
dev->driver = drv; /* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed; // 2、更新 sysfs
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
} // 3、执行真正的probe
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;
} // 4、绑定
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done; probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL); if (ret == -EPROBE_DEFER) {
/* Driver requested deferred probing */
dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
} else if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
} else {
pr_debug("%s: probe of %s rejects match %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}

really_probe 中干了四件大事。

关联dev与drv

在 dev 中记录 driver :

dev->driver = drv;

已经match上了配对成功了嘛,所以可以将该device和driver关联起来: dev <- drv

然而device_driver中并没有device成员,因此并没有 drv <- dev

通知bus、更新sysfs
driver_sysfs_add(dev);

1、通知总线绑定了设备和驱动

2、创建两个symlink,更新sysfs

  • 在sysfs中该 dev.kobj 目录下创建与之匹配的driver的符号连接,名字为“driver”
  • 在sysfs中该 driver.kobj 目录下创建与之匹配的device的符号连接,名字为 kobject_name(&dev->kobj)
// drivers/base/dd.c
static int driver_sysfs_add(struct device *dev)
{
int ret; // 通知总线绑定了设备和驱动
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_BIND_DRIVER, dev);
/* 例如,
在/sys/bus/XXX/drivers/XXX 目录下建立symlink,链接名为 kobj->name,
链接指向 /sys/devices/platform/XXX
*/
ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
kobject_name(&dev->kobj));
if (ret == 0) {
/* 例如,
在/sys/devices/platform/XXX/下建立symlink,链接名为driver,
指向/sys/bus/xxx/drivers目录下的某个目录*/
ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
"driver");
if (ret)
sysfs_remove_link(&dev->driver->p->kobj,
kobject_name(&dev->kobj));
}
return ret;
}
执行真正的 probe 方法

多态

    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;
}

probe的规则是:如果BUS上实现了probe就用BUS的probe;否则才会用driver的probe。

绑定
driver_bound(dev);

将 device 放入 driver 链表中。

看来一个device只能有一个driver,但是driver可以支持多个device

// drivers/base/dd.c
static void driver_bound(struct device *dev)
{
// 判断是否绑定过
if (klist_node_attached(&dev->p->knode_driver)) {
printk(KERN_WARNING "%s: device %s already bound\n",
__func__, kobject_name(&dev->kobj));
return;
} // 将 device 放入 driver 链表中。
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices); /*
* Make sure the device is no longer in one of the deferred lists and
* kick off retrying all pending devices
*/
driver_deferred_probe_del(dev);
driver_deferred_probe_trigger(); if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_BOUND_DRIVER, dev);
} // lib/klist.c
/**
* klist_node_attached - Say whether a node is bound to a list or not.
* @n: Node that we're testing.
*/
int klist_node_attached(struct klist_node *n)
{
return (n->n_klist != NULL);
}
EXPORT_SYMBOL_GPL(klist_node_attached);

kobject_uevent通知用户空间

主要是在/sys/devices/.../中添加dev的uevent属性文件,先不说这个。

例子(drv)

ldd_drv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/io.h>
#include "lddbus.h" struct ldd_driver ldd_drv = {
.version = "version 1.0\n",
.driver = {
.name = "myldd",
},
}; static int ldd_drv_init(void){
register_ldd_driver(&ldd_drv);
return 0;
} static void ldd_drv_exit(void){ unregister_ldd_driver(&ldd_drv);
} module_init(ldd_drv_init);
module_exit(ldd_drv_exit);
MODULE_LICENSE("GPL");

测试

[root@FriendlyARM \]# insmod drv.ko
[root@FriendlyARM ]# cd sys /bus/ldd/drivers/
[root@FriendlyARM drivers]ls
myldd
[root@FriendlyARM drivers] cd myldd/
[root@FriendlyARM myldd]#ls
bind uevent unbind version
[root@FriendlyARM myldd]# cat version
version 1.0

insmod drv.ko 之后,我们会发现 /sys/bus/ldd/drivers 目录下多了一个 myldd 目录,这就是我们向内核注册的ldd总线上的myldd驱动程序。同样 cat version 会显示设定好的属性。

device

整体流程

device_register(dev)[core.c]
device_initialize()
device_add(dev) [core.c]
bus_add_device(dev)
bus_probe_device(dev) [bus.c]
if (dev->bus && dev->bus-op->drivers_autoprobe)
device_attach(dev) [dd.c]
if (dev->driver)
device_bind_driver(dev)
else // 从这里开始,与 driver一样
bus_for_each_dev(dev->bus, NULL, drv,__driver_attach)
__driver_attach(dev, drv) [dd.c]
driver_match_device(drv, dev) [base.h]
drv-bus->match ? drv->bus-amatch(dev, drv) : 1
if false, return;
driver_probe_device(drv, dev) [dd.c]
really_probe(dev, drv) [dd.c]
dev-driver = drv;
if (dev-bus->probe)
dev->bus->probe(dev);
else if (drv->probe)
drv-aprobe(dev);
probe_failed:
dev->-driver = NULL;

device原型

// include/linux/device.h
/**
* struct device - The basic device structure
* @parent: The device's "parent" device, the device to which it is attached.
* In most cases, a parent device is some sort of bus or host
* controller. If parent is NULL, the device, is a top-level device,
* which is not usually what you want.
* @p: Holds the private data of the driver core portions of the device.
* See the comment of the struct device_private for detail.
* @kobj: A top-level, abstract class from which other classes are derived.
* @init_name: Initial name of the device.
* @type: The type of device.
* This identifies the device type and carries type-specific
* information.
* @mutex: Mutex to synchronize calls to its driver.
* @bus: Type of bus device is on.
* @driver: Which driver has allocated this
* @platform_data: Platform data specific to the device.
* Example: For devices on custom boards, as typical of embedded
* and SOC based hardware, Linux often uses platform_data to point
* to board-specific structures describing devices and how they
* are wired. That can include what ports are available, chip
* variants, which GPIO pins act in what additional roles, and so
* on. This shrinks the "Board Support Packages" (BSPs) and
* minimizes board-specific #ifdefs in drivers.
* @power: For device power management.
* See Documentation/power/devices.txt for details.
* @pm_domain: Provide callbacks that are executed during system suspend,
* hibernation, system resume and during runtime PM transitions
* along with subsystem-level and driver-level callbacks.
* @pins: For device pin management.
* See Documentation/pinctrl.txt for details.
* @numa_node: NUMA node this device is close to.
* @dma_mask: Dma mask (if dma'ble device).
* @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
* hardware supports 64-bit addresses for consistent allocations
* such descriptors.
* @dma_parms: A low level driver may set these to teach IOMMU code about
* segment limitations.
* @dma_pools: Dma pools (if dma'ble device).
* @dma_mem: Internal for coherent mem override.
* @cma_area: Contiguous memory area for dma allocations
* @archdata: For arch-specific additions.
* @of_node: Associated device tree node.
* @acpi_node: Associated ACPI device node.
* @devt: For creating the sysfs "dev".
* @id: device instance
* @devres_lock: Spinlock to protect the resource of the device.
* @devres_head: The resources list of the device.
* @knode_class: The node used to add the device to the class list.
* @class: The class of the device.
* @groups: Optional attribute groups.
* @release: Callback to free the device after all references have
* gone away. This should be set by the allocator of the
* device (i.e. the bus driver that discovered the device).
* @iommu_group: IOMMU group the device belongs to.
*
* @offline_disabled: If set, the device is permanently online.
* @offline: Set after successful invocation of bus type's .offline().
*
* At the lowest level, every device in a Linux system is represented by an
* instance of struct device. The device structure contains the information
* that the device model core needs to model the system. Most subsystems,
* however, track additional information about the devices they host. As a
* result, it is rare for devices to be represented by bare device structures;
* instead, that structure, like kobject structures, is usually embedded within
* a higher-level representation of the device.
*/
struct device {
struct device *parent; struct device_private *p; struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type; struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/ struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain; #ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif #ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */ struct device_dma_parameters *dma_parms; struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata; struct device_node *of_node; /* associated device tree node */
struct acpi_dev_node acpi_node; /* associated ACPI device node */ dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */ spinlock_t devres_lock;
struct list_head devres_head; struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev);
struct iommu_group *iommu_group; bool offline_disabled:1;
bool offline:1;
};

device_register

/**
* device_register - register a device with the system.
* @dev: pointer to the device structure
*
* This happens in two clean steps - initialize the device
* and add it to the system. The two steps can be called
* separately, but this is the easiest and most common.
* I.e. you should only call the two helpers separately if
* have a clearly defined need to use and refcount the device
* before it is added to the hierarchy.
*
* For more information, see the kerneldoc for device_initialize()
* and device_add().
*
* NOTE: _Never_ directly free @dev after calling this function, even
* if it returned an error! Always use put_device() to give up the
* reference initialized in this function instead.
*/
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);

device_initialize

/**
* device_initialize - init device structure.
* @dev: device.
*
* This prepares the device for use by other layers by initializing
* its fields.
* It is the first half of device_register(), if called by
* that function, though it can also be called separately, so one
* may use @dev's fields. In particular, get_device()/put_device()
* may be used for reference counting of @dev after calling this
* function.
*
* All fields in @dev must be initialized by the caller to 0, except
* for those explicitly set to some other value. The simplest
* approach is to use kzalloc() to allocate the structure containing
* @dev.
*
* NOTE: Use put_device() to give up your reference instead of freeing
* @dev directly once you have called this function.
*/
void device_initialize(struct device *dev)
{
// 设置 dev->kobj.kset 为 devices_kset
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
EXPORT_SYMBOL_GPL(device_initialize);

做了一些设备有关的基本初始化。

device_add

// drviers/base/core.c
/**
* device_add - add device to device hierarchy.
* @dev: device.
*
* This is part 2 of device_register(), though may be called
* separately _iff_ device_initialize() has been called separately.
*
* This adds @dev to the kobject hierarchy via kobject_add(), adds it
* to the global and sibling lists for the device, then
* adds it to the other relevant subsystems of the driver model.
*
* Do not call this routine or device_register() more than once for
* any device structure. The driver model core is not designed to work
* with devices that get unregistered and then spring back to life.
* (Among other things, it's very hard to guarantee that all references
* to the previous incarnation of @dev have been dropped.) Allocate
* and register a fresh new struct device instead.
*
* NOTE: _Never_ directly free @dev after calling this function, even
* if it returned an error! Always use put_device() to give up your
* reference instead.
*/
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL; dev = get_device(dev);
if (!dev)
goto done; // 初始化 device的 私有数据
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
} /*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
/* 初始化设备内部的kobject的名字 */
// 如果初始名字(init_name)存在,则设为名字 为 init_name
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
} /* subsystems can specify simple device enumeration */
// 如果检查发现没有名字,但bus设置了设备名前缀,则以 类似foo%u的方式来设置设备的名字
// 例如 tty0
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id); pr_debug("device: '%s': %s\n", dev_name(dev), __func__); // 增加设备父设备并增加父设备引用计数,例如:csid的设备节点节v4l-subdev4的父设备是fd8c0000.qcom,msm-cam
parent = get_device(dev->parent);
// 获取v4l-subdev4设备目录的父目录是video4linux,video4linux的父目录是fd8c0000.qcom,msm-cam
kobj = get_device_parent(dev, parent);
// 在kobject层实现设备父子关系
if (kobj)
dev->kobj.parent = kobj; /* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
/*
把内嵌的kobject注册到设备模型中将设备加入到kobject模型中,
创建sys相关目录 ,目录名字为kobj->name
*/
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error; /* notify platform of device entry */
if (platform_notify)
platform_notify(dev); // 创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError; // 如果有主次设备号 创建dev 属性文件
if (MAJOR(dev->devt)) {
// 在 /sys/devices中创建设备节点
error = device_create_file(dev, &dev_attr_dev); /* 在/sys/dev/char/或者/sys/dev/block/创建devt的属性的连接文件,
形如10:45,由主设备号和次设备号构成,指向/sys/devices/.../的具体设备目录,
该链接文件只具备读属性,显示主设备号:次设备号,如10:45,用户空间udev相应uevent事件时,将根据设备号在/dev下创建节点文件
*/
error = device_create_sys_dev_entry(dev); devtmpfs_create_node(dev);
} // 创建类符号链接,相互创建dev和class之间的链接文件
error = device_add_class_symlinks(dev);
// 创建sys目录下设备其他属性文件
error = device_add_attrs(dev);
// 将设备加入到管理它的bus总线的设备链表上
// 创建subsystem链接文件,链接class下的具体的子系统文件夹
error = bus_add_device(dev); error = dpm_sysfs_add(dev); device_pm_add(dev); /* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
// 通知 添加设备 事件
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev); kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children); if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices); /* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error; }
EXPORT_SYMBOL_GPL(device_add);
bus_add_device
/**
* bus_add_device - add device to bus
* @dev: device being added
*
* - Add device's bus attributes.
* - Create links to device's bus.
* - Add the device to its bus's list of devices.
*/
int bus_add_device(struct device *dev)
{
/* 引用计数加一 */
struct bus_type *bus = bus_get(dev->bus);
int error = 0; if (bus) {
pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
/* 创建相应的属性文件 */
error = device_add_attrs(bus, dev); // 添加属性组。
error = device_add_groups(dev, bus->dev_groups); // 创建 /sys/bus/$(bus->name)/devices/$(dev->name) 到 /sys/devices/$(dev->name) 的软连接
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev)); // 创建 /sys/devices/$(dev->name)/subsystem 到 /sys/bus/$(bus->name)/devices/$(dev->name) 的软连接
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
if (error)
goto out_subsys;
// 将 dev 加入 bus 所管理的 devices 链表
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0; }

bus_probe_device

// 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;
struct subsys_interface *sif;
int ret; if (!bus)
return; if (bus->p->drivers_autoprobe) {
ret = device_attach(dev);
WARN_ON(ret < 0);
} mutex_lock(&bus->p->mutex);
list_for_each_entry(sif, &bus->p->interfaces, node)
if (sif->add_dev)
sif->add_dev(dev, sif);
mutex_unlock(&bus->p->mutex);
}
device_attach

从逻辑上来说,一个驱动可以支持多个设备;一个设备只能绑定一个驱动。

device_attach尝试为设备寻找到一个驱动;

因此,device_attach稍微与driver_attach不一样:调用driver_match_device匹配设备和驱动,成功就结束循环退出(而不是执行到循环);

// drivers/base/dd.c
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data; if (!driver_match_device(drv, dev))
return 0; return driver_probe_device(drv, dev);
} /**
* device_attach - try to attach device to a driver.
* @dev: device.
*
* Walk the list of drivers that the bus has and call
* driver_probe_device() for each pair. If a compatible
* pair is found, break out and return.
*
* Returns 1 if the device was bound to a driver(成功);
* 0 if no matching driver was found(失败);
* -ENODEV if the device is not registered(异常).
*
* When called for a USB interface, @dev->parent lock must be held.
*/
int device_attach(struct device *dev)
{
int ret = 0; device_lock(dev);
// 情况1:设备已经有驱动
if (dev->driver) {
if (klist_node_attached(&dev->p->knode_driver)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1; // 成功,并退出
else {
dev->driver = NULL;
ret = 0;
}
} else { // 情况2:设备没有驱动(通常情况)
// 遍历总线上的driver链表,进行匹配
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
pm_request_idle(dev);
}
out_unlock:
device_unlock(dev);
return ret;
}
EXPORT_SYMBOL_GPL(device_attach);

此后就是bus_for_each_drv,不再赘述。

例子(dev)

ldd_dev.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include "lddbus.h" static dev_t devid; static struct ldd_device ldd_dev = {
.name = "myldd",
.dev = {
.init_name = "myldd",
},
}; static int ldd_dev_init(void) { alloc_chrdev_region(&devid, 0, 1, "mylddtest");
//ldd_dev.dev.devt = devid;
register_ldd_device(&ldd_dev);
return 0;
} static void ldd_dev_exit(void) {
unregister_ldd_device(&ldd_dev);
} module_init(ldd_dev_init);
module_exit(ldd_dev_exit);
MODULE_LICENSE("GPL");

测试

[root@FriendlyARM myldd]#cd /sys/devices
[root@FriendlyARM devices] ls
1dd0 platform system virtual
[root@FriendlyARM devices] cd ldd0
[root@FriendlyARM lddo]# ls
myldd uevent
[root@FriendlyARM lddo] cd myldd
[root@FriendlyARM myldd] ls
driver subsystm uevent
[root@FriendlyARM myldd] ls -l
lrwxrwXrwx1 rootroot 0 Dec 3 01:37 driver -> ../../../bus /ldd/drivers/myldd
lrwXrwXrwX1 rootroot 0 Dec 301:37 subsystem -> ../../..7bus/ldd
-rw-r——r--1 root_root 4096 Dec 301:37 uevent
[root@FriendlyARM myldd]cd /sys/bus/ldd/devices/
[root@FriendlyARM devices]ls
myldd
[root@FriendlyARM devices]# ls -l
lrwxrwXrwx1 rootroot
0 Dec3 01:40 myldd -> ../../../ devices/ldd0/myldd
[root@FriendlyARM devices] cd myldd
[root@FriendlyARM myldd]# ls
driver subsystem uevent
[root@FriendlyARM myldd]# cd driver
[root@FriendlyARM myldd]# ls
bind myldd uevent unbind version
[root@FriendlyARM myldd] #cat version
' version 1.0

device 相对driver 要复杂一些,insmod dev.ko 之后,我们可以在/sys/devices 目录下看到新增了一个目录 ldd0(ldd_bus) ,在 ldd0 (ldd_bus)目录下看到我们向ldd总线注册的myldd设备(ldd0是 myldd 的父设备),在/sys/bus/ldd/devices/ 目录下同样可以看到 myldd , 因为这里的Myldd 是指向 /sys/devices/ldd0/myldd 的软连接。

/sys/devices/ldd0/myldd/driver 目录 与该设备匹配的驱动程序,我们在Bus->match中设置的匹配条件--名字相同。

我们并未看到属性文件 dev ,是因为我们没有指定Myldd设备的设备号,将 dev.c 代码中的 ldd_dev.dev.devt = devid 注释去掉,卸载原来驱动,重新加载。

[root@FriendlyARM myldd]#cd /sys/devices/ldd0/myldd
[root@FriendlyARM myldd]#ls
dev driver subsystem uevent
[root@FriendlyARM myldd]#cd /sys/bus/ldd/devices/myldd/
[root@FriendlyARM myldd]#ls
dev driver subsystem uevent
[root@FriendlyARM myldd]#cat dev
253:0
[root@FriendlyARM myldd]#ls -l /dev/my*
crw-rw---- 1 root root 253, 0 Dec 3 02:05 /dev/myldd

总结

无论是bus,还是class,还是我们会在后面看到的一些虚拟的子系统,它都构成了一个“子系统(sub-system)”;该子系统会包含形形色色的device或device_driver,就像一个独立的王国一样,存在于内核中。

而这些子系统的表现形式,就是/sys/bus(或/sys/class,或其它)目录下面的子目录,每一个子目录,都是一个子系统(如/sys/bus/spi/)。

附录:subsys_private的演化

参考:https://www.cnblogs.com/xinghuo123/p/12872026.html

按理说,subsys_private就是集合了一些bus模块需要使用的私有数据,例如ksetklist等等,命名为bus_private会好点(就像device、driver模块一样)

// drivers/base/base.h
/**
* struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
*
* @subsys - the struct kset that defines this subsystem
* @devices_kset - the list of devices associated
*
* @drivers_kset - the list of drivers associated
* @klist_devices - the klist to iterate over the @devices_kset
* @klist_drivers - the klist to iterate over the @drivers_kset
* @bus_notifier - the bus notifier list for anything that cares about things
* on this bus.
* @bus - pointer back to the struct bus_type that this structure is associated
* with.
*
* @class_interfaces - list of class_interfaces associated
* @glue_dirs - "glue" directory to put in-between the parent device to
* avoid namespace conflicts
* @class_mutex - mutex to protect the children, devices, and interfaces lists.
* @class - pointer back to the struct class that this structure is associated
* with.
*
* This structure is the one that is the actual kobject allowing struct
* bus_type/class to be statically allocated safely. Nothing outside of the
* driver core should ever touch these fields.
*/
struct subsys_private {
struct kset subsys; //代表bus在sysfs中的类型
struct kset *devices_kset; //代表bus目录下的drivers子目录
// ... struct kset *drivers_kset; //代表bus目录下地devices子目录
struct klist klist_devices; //bus的设备链表
struct klist klist_drivers; //bus的驱动链表
struct blocking_notifier_head bus_notifier; //用于在总线上内容发送变化时调用特定的函数 // 标志定义是否允许device和driver自动匹配,
// 如果允许会在device或者driver注册时就进行匹配工作,默认是1
unsigned int drivers_autoprobe:1; struct bus_type *bus; struct list_head class_interfaces;
struct kset glue_dirs;
struct mutex class_mutex;
struct class *class; // 指向关联的bus_type类型。
};

早期版本

事实上,早期版本确实是命名为bus_type_private。那个时候,class 的私有数据与 bus的私有数据是分开的,分别是class_privatebus_type_private

linux 2.6.35.7

struct class_private {
struct kset class_subsys;
struct klist class_devices;
struct list_head class_interfaces;
struct kset class_dirs;
struct mutex class_mutex;
struct class *class;
}; struct bus_type_private {
struct kset subsys;
struct kset *drivers_kset;
struct kset *devices_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
};

linux 3.x早期

bus因为需求升级为subsys_private ,同时为后面去掉class_private 做基础

struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
struct kset glue_dirs;
struct class *class;
};
struct class_private {
struct kset class_subsys;
struct klist class_devices;
struct list_head class_interfaces;
struct kset class_dirs;
struct mutex class_mutex;
struct class *class;
};

linux 3.x后期

两者完全统一用这个,class_private 在这个版本已经完全看不到了

struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus; //
struct kset glue_dirs;
struct class *class; //
};

附录:在bus中理解kobject的生命周期管理

回到kobject_put(),它通常被具体对象做一个简单包装,如:bus_put(),它直接调用kset_put(),然后调用到kobject_put()

// driver/base/bus.c
static void bus_put(struct bus_type *bus)
{
if (bus)
kset_put(&bus->p->subsys);
}

那对于这个bus_type对象而言,仅仅通过kobject_put(),如何来达到释放整个bus_type的目的呢?

这里就需要kobject另一个成员struct kobj_type * ktype来完成。

回到kobject_put()release()操作。当引用计数为0时,kobject核心会调用kobject_release(),最后会调用kobj_type->release(kobj)来完成对象的释放。可是具体对象的释放,最后却通过kobj->kobj_type->release()来释放,那这个release()函数,就必须得由具体的对象来指定。

还是拿bus_type举例:

在通过bus_register(struct bus_type *bus)进行总线注册时,该API内部会执行priv->subsys.kobj.ktype = &bus_ktype操作;

// driver/base/bus.c
int bus_register(struct bus_type *bus)
{
// ... priv->subsys.kobj.ktype = &bus_ktype; // ...
}

有了该操作,那么前面的bus_put()在执行bus_type->p-> subsys.kobj->ktype->release()时,就会执行注册的bus_ktype.release = bus_release函数。

// driver/base/bus.c
static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops,
.release = bus_release,
}; static void bus_release(struct kobject *kobj)
{
// 获取整个 具体的 bus子系统 对象
struct subsys_private *priv =
container_of(kobj, typeof(*priv), subsys.kobj);
struct bus_type *bus = priv->bus; // 释放资源
kfree(priv);
bus->p = NULL;
}

由于bus_release()函数由具体的bus子系统提供,它必定知道如何释放包括kobj在内的bus_type对象。

Linux 内核:设备驱动模型(2)driver-bus-device与probe的更多相关文章

  1. Linux总线设备驱动模型

    1. Linux2.6内核引入总线.设备.驱动模型来描述各种总线(PCI.USB.I2C.SPI)与外围设备及其驱动之间的关系. 2. 在Linux内核中,总线用bus_type结构来描述,定义于文件 ...

  2. Linux 字符设备驱动模型

    一.使用字符设备驱动程序 1. 编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块 2. 创建设备文件 通 ...

  3. Linux 内核设备驱动

    设备模型跟踪所有对系统已知的驱动. 这个跟踪的主要原因是使驱动核心能匹配驱动和新 设备. 一旦驱动在系统中是已知的对象, 但是, 许多其他的事情变得有可能. 设备驱动可 输出和任何特定设备无关的信息和 ...

  4. Linux内核驱动学习(四)Platform设备驱动模型

    Linux platform设备驱动模型 文章目录 Linux platform设备驱动模型 前言 框架 设备与驱动的分离 设备(device) 驱动(driver) 匹配(match) 参考 前言 ...

  5. Linux中总线设备驱动模型及平台设备驱动实例

    本文将简要地介绍Linux总线设备驱动模型及其实现方式,并不会过多地涉及其在内核中的具体实现,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程. 目录: 一.总线设备驱动模型总体介绍及其实现方 ...

  6. LINUX设备驱动模型之class

    转自 https://blog.csdn.net/qq_20678703/article/details/52754661 1.LINUX设备驱动模型中的bus.device.driver,.其中bu ...

  7. Linux的总线设备驱动模型

    裸机编写驱动比较自由,按照手册实现其功能即可,每个人写出来都有很大不同: 而Linux中还需要按照Linux的驱动模型来编写,也就是需要按照"模板"来写,写出来的驱动就比较统一. ...

  8. Linux混杂设备驱动

    1. Linux混杂设备驱动模型 ① 在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice).所有混杂设备形成一个链表, ...

  9. linux设备驱动模型

    尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要. Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统 ...

  10. Linux驱动之平台设备驱动模型简析(驱动分离分层概念的建立)

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而有一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...

随机推荐

  1. 前端关于获取网络时间的方法api

    淘宝 http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp 苏宁http://quan.suning.com/getSys ...

  2. redis系列02---缓存过期、穿透、击穿、雪崩

    一.缓存过期 问题产生的原由: 内存空间有限,给缓存设置过期时间,但有些键值运气比较好,每次都没有被我的随机算法选中,每次都能幸免于难,这可不行,这些长时间过期的数据一直霸占着不少的内存空间! 解决方 ...

  3. 羽夏壳世界—— PE 解析的实现

    写在前面   此系列是本人一个字一个字码出来的,包括代码实现和效果截图. 如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后 ...

  4. Golang、python中MD5、SHA512、base64编码等

    在GO中处理的话,比较方便. func main() { fmt.Println(md5Str("woGo")) fmt.Println(sha512Str("woGo& ...

  5. 好玩的vue组件

    https://gitee.com/zheng_yongtao/jyeontu-component-warehouse 推荐这个大佬,很厉害悬浮按钮 评论组件 词云 瀑布流照片容器 视频动态封面 3D ...

  6. NumPy 数组复制与视图详解

    NumPy 数组的复制与视图 NumPy 数组的复制和视图是两种不同的方式来创建新数组,它们之间存在着重要的区别. 复制 复制 会创建一个包含原始数组相同元素的新数组,但这两个数组拥有独立的内存空间. ...

  7. apisix~升级原始插件的方法

    扩展apisix原始插件 当apisix提供的插件不能满足我们要求时,我们可能需要将它的plugin进行个性化扩展,例如一个jwt认证插件jwt-auth,它本身具有验证jwt有效性功能,支持rs25 ...

  8. C# winfrom 局域网版多人成语接龙(一)

    在学习 springjdbc+c3p0时做了一个数据库版的获得给定词汇的成语接龙,这个做了之后,我突然就想做一个可供多人游戏的成语接龙游戏,由于自己根本不熟悉java的图形界面开发,感觉没有winfo ...

  9. MindSponge分子动力学模拟——多路径分子模拟(2024.05)

    技术背景 在前面的MindSponge教程系列博客中,我们已经介绍过MindSponge分子动力学模拟框架的基础功能使用方法,例如MindSponge的安装与使用.定义分子系统.计算单点能和迭代器等等 ...

  10. 安装centos7模板机[正常版]

    1. 安装centos 7模板机 准备好centos7的镜像 下载地址:http://mirrors.aliyun.com/centos/7/isos/x86_64/ 安装centos 自定义硬件: ...