----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8根文件系统:busybox 1.25.0u-boot:2016.05----------------------------------------------------------------------------------------------------------------------------
在上一节我们介绍了总线设备驱动模型的由来,以及platform总线设备驱动模型,并介绍了如何编写platform设备驱动。
如果我们学过面向对象编程的话,我们可以将总线-设备-驱动看做基类,而platform总线-platform设备-platform驱动看做其中的一种实现。
在linux总线设备驱动模型中涉及到的概念有bus(总线)、calss(类)、device(设备)、device driver(设备驱动),这一节我们将会详细介绍这几个概念。
一、总线
linux认为总线是SOC和一个或多个设备之间信息交互的通道,而为了设备驱动模型的抽象,所有的设备都应该连接到总线上,无论是真实的物理总线I2C、SPI,USB还是虚拟的总线platform。下图是嵌入式系统常见的硬件的一个抽象拓扑:
需要注意:如果我们编写的驱动的程序并没有使用platform、I2C等总线,那么设备将不会连接到总线上。
1.1 总线定义
可以通过如下命令查看linux系统加载的所有总线:
root@zhengyang:/sys/bus# ls -l /sys/bus
总用量 0
drwxr-xr-x 4 root root 0 4月 11 21:38 ac97
drwxr-xr-x 4 root root 0 4月 11 21:38 acpi
drwxr-xr-x 4 root root 0 4月 11 21:38 clockevents
drwxr-xr-x 4 root root 0 4月 11 21:38 clocksource
drwxr-xr-x 4 root root 0 4月 11 21:38 container
drwxr-xr-x 4 root root 0 4月 11 21:38 cpu
drwxr-xr-x 4 root root 0 4月 11 21:38 edac
drwxr-xr-x 4 root root 0 4月 11 21:38 event_source
drwxr-xr-x 4 root root 0 4月 11 21:38 gameport
drwxr-xr-x 4 root root 0 4月 11 21:38 gpio
drwxr-xr-x 4 root root 0 4月 11 21:38 hid
drwxr-xr-x 4 root root 0 4月 11 21:38 i2c
drwxr-xr-x 4 root root 0 4月 11 21:38 isa
drwxr-xr-x 4 root root 0 4月 11 21:38 machinecheck
drwxr-xr-x 4 root root 0 4月 11 21:38 mdio_bus
drwxr-xr-x 4 root root 0 4月 11 21:38 memory
drwxr-xr-x 4 root root 0 4月 11 21:38 mipi-dsi
drwxr-xr-x 4 root root 0 4月 11 21:38 mmc
drwxr-xr-x 4 root root 0 4月 11 21:38 nd
drwxr-xr-x 4 root root 0 4月 11 21:38 node
drwxr-xr-x 4 root root 0 4月 11 21:38 nvmem
drwxr-xr-x 4 root root 0 4月 11 21:38 parport
drwxr-xr-x 5 root root 0 4月 11 21:38 pci
drwxr-xr-x 4 root root 0 4月 11 21:38 pci-epf
drwxr-xr-x 4 root root 0 4月 11 21:38 pci_express
drwxr-xr-x 4 root root 0 4月 11 21:38 platform
drwxr-xr-x 4 root root 0 4月 11 21:38 pnp
drwxr-xr-x 4 root root 0 4月 11 21:38 rapidio
drwxr-xr-x 4 root root 0 4月 11 21:38 scsi
drwxr-xr-x 4 root root 0 4月 11 21:38 sdio
drwxr-xr-x 4 root root 0 4月 11 21:38 serial
drwxr-xr-x 4 root root 0 4月 11 21:38 serio
drwxr-xr-x 4 root root 0 4月 11 21:38 snd_seq
drwxr-xr-x 4 root root 0 4月 11 21:38 spi
drwxr-xr-x 4 root root 0 4月 11 21:38 usb
drwxr-xr-x 4 root root 0 4月 11 21:38 virtio
drwxr-xr-x 4 root root 0 4月 11 21:38 vme
drwxr-xr-x 4 root root 0 4月 11 21:38 workqueue
drwxr-xr-x 4 root root 0 4月 11 21:38 xen
drwxr-xr-x 4 root root 0 4月 11 21:38 xen-backend
在Linux 设备模型中,总线由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.
* @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 positive value if the
* given device can be handled by the given driver and zero
* otherwise. It may also return error code if determining that
* the driver supports the device is not possible. In case of
* -EPROBE_DEFER it will queue the device for deferred probing.
* @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.
* @num_vf: Called to find out how many virtual functions a device on this
* bus supports.
* @dma_configure: Called to setup DMA configuration on a device on
* this bus.
* @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
* @need_parent_lock: When probing or removing a device on this bus, the
* device core should lock the device's parent.
*
* 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;
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);
int (*num_vf)(struct device *dev);
int (*dma_configure)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
bool need_parent_lock;
};
其中部分字段的含义如下:
- name:总线名称;
- dev_name:用于子系统枚举设备;
- dev_root:用作父设备使用的默认设备;
- bus_groups:总线属性;
- dev_groups:该总线上所有设备的默认属性;
- drv_groups:该总线上所有驱动的默认属性;
- match:当有新的设备或驱动添加到总线上时match函数被调用,如果设备和驱动可以匹配,返回0;
- uevent:当一个设备添加、移除或添加环境变量时,函数调用;
- probe:当有新设备或驱动添加时,probe函数调用,并且回调该驱动的probe函数来初始化相关联的设备;
- remove:设备移除时调用remove函数;
- shutdown:设备关机时调用shutdown函数;
- suspend:设备进入睡眠时调用suspend函数;
- resume:设备唤醒时调用resume函数;
- pm:总线的电源管理选项,并回调设备驱动的电源管理模块;
- p:驱动核心的私有数据,只有驱动核心才可以访问。使用struct subsys_private可以将struct bus_type中的部分细节屏蔽掉,利于外界使用bus_type;struct driver_private和struct device_private都有类似的功能。
1.1.1 subsys_private
struct subsys_private定义在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 subsystem's 'devices' directory
* @interfaces - list of subsystem interfaces associated
* @mutex - protect the devices, and interfaces lists.
*
* @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.
*
* @glue_dirs - "glue" directory to put in-between the parent device to
* avoid namespace conflicts
* @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 list_head interfaces; //链表头,存放接口比如由class_interface元素组成的链表
struct mutex mutex;
struct kset *drivers_kset; //代表bus目录下地devices子目录
struct klist klist_devices; //bus的设备链表
struct klist klist_drivers; //bus的驱动链表
struct blocking_notifier_head bus_notifier; //用于在总线上内容发送变化时调用特定的函数
unsigned int drivers_autoprobe:1; //标志定义是否允许device和driver自动匹配,如果允许会在device或者driver注册时就进行匹配工作,默认是1
struct bus_type *bus; //指针指向struct bus_type类型。
struct kset glue_dirs;
struct class *class;
}
这个结构就是集合了一些bus模块需要使用的私有数据,例如kset、klist等等,命名为bus_private会好点(就像device、driver模块一样),事实上早期版本确实是命名为bus_type_private。
不过为什么内核最终抛弃了呢呢?看看include/linux/device.h中的struct class结构就知道了,因为class结构中也包含了一个一模一样的struct subsys_private指针,由于class和bus很相似啊,所以在linux 3.x后期将class和bus的私有数据类型合并,统一使用subsys_private类型来表示了。
1.2 总线注册
在注册总线前,需要申明和初始化bus_type 结构体。只有很少的bus_type 成员需要初始化,大部分都由设备模型控制。但必须为总线指定名字及一些必要的方法。例如:
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
.uevent = ldd_uevent,
};
然后调用bus_register函数注册总线:
int bus_register(struct bus_type *bus)
该调用可能失败,所以必须始终检查返回值。
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
若成功,新的总线子系统将被添加进系统,之后可以向总线添加设备。
1.2.1 device_register
device_register函数定义在drivers/base/bus.c:
/**
* bus_register - register a driver-core subsystem
* @bus: bus to register
*
* Once we have that, we register the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the subsystem.
*/
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); // 分配子系统结构体
if (!priv)
return -ENOMEM;
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; //本总线上device/driver注册时是否自动匹配
retval = kset_register(&priv->subsys); //注册kset,即在/sys/bus下创建对应目录,目录名称为总线名称
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent); // 创建uevent属性文件
if (retval)
goto bus_uevent_fail;
/*bus目录下创建devices和drivers目录,将来绑定该总线的设备和驱动就会存放于此*/
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
// 初始化priv中设备的驱动的链表
INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);// 初始化设备链表,用于连接bus上的device
klist_init(&priv->klist_drivers, NULL, NULL); // 初始化驱动链表,用于连接bus上的device_driver
retval = add_probe_files(bus); // 在bus目录下添加drivers_probe和drivers_autoprobe文件
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(bus);
if (retval)
goto bus_attrs_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
bus_attrs_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
1.2.2 函数调用栈
下图是device_register函数的调用流程:
通过上图可以看到,它主要是在/sys/bus目录下面创建名字为bus_type.name的目录(这里名字举例为xxx-bus),然后会创建好devices和drivers给与它管理的设备和驱动进行注册,同时创建驱动的探测drivers_probe和drivers_autoprobe属性文件,以提供给用户触发去探测。
当然还少不了uevent事件文件的创建,最后还有一些bus自己特有的属性。注册完bus后,将会生成目录结构如下:
示例,以I2C总线注册为例:
1.3 总线卸载
当必须从系统中删除一个总线时,调用:
void bus_unregister(struct bus_type *bus);
二、类
在linux设备驱动模型中,class的作用就对具有相似功能或属性的设备进行分类,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。
因而从属于相同class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从class中继承即可。
2.1 类定义
struct class是class的抽象,它的定义include/linux/device.h,如下
/**
* struct class - device classes
* @name: Name of the class.
* @owner: The module owner.
* @class_groups: Default attributes of this class.
* @dev_groups: Default attributes of the devices that belong to the class.
* @dev_kobj: The kobject that represents this class and links it into the hierarchy.
* @dev_uevent: Called when a device is added, removed from this class, or a
* few other things that generate uevents to add the environment
* variables.
* @devnode: Callback to provide the devtmpfs.
* @class_release: Called to release this class.
* @dev_release: Called to release the device.
* @shutdown_pre: Called at shut-down time before driver shutdown.
* @ns_type: Callbacks so sysfs can detemine namespaces.
* @namespace: Namespace of the device belongs to this class.
* @get_ownership: Allows class to specify uid/gid of the sysfs directories
* for the devices belonging to the class. Usually tied to
* device's namespace.
* @pm: The default device power management operations of this class.
* @p: The private data of the driver core, no one other than the
* driver core can touch this.
*
* A class is a higher-level view of a device that abstracts out low-level
* implementation details. Drivers may see a SCSI disk or an ATA disk, but,
* at the class level, they are all simply disks. Classes allow user space
* to work with devices based on what they do, rather than how they are
* connected or how they work.
*/
struct class {
const char *name;
struct module *owner;
const struct attribute_group **class_groups;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*shutdown_pre)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
其中部分参数含义如下:
- name:class的名字,即在sys文件系统下/sys/class目录下创建的目录名称;
- owner:与模块相关;
- class_groups:class默认属性;
- dev_groups:所属该calss的设备的默认属性;
- dev_kobj:表示该class下的设备在/sys/dev/下的目录,现在一般有char和block两个,如果dev_kobj为NULL,则默认选择char;
- dev_uevent:上报事件的回调函数,当device加入class或从class移除,会调用该回调函数;
- dev_node:用于返回device设备节点的相对路径;
- class_release:class release时的回调函数;
- dev_release:device release时的回调函数;
- pm:class的电源管理选项;
- p:驱动核心的私有数据,只有驱动核心才可以访问。
2.2 类接口定义
类子系统有一个 linux设备驱动模型的其他部分找不到的附加概念,称为“接口”,可将它理解为一种设备加入或离开类时获得信息的触发机制,结构体如下:
struct class_interface {
struct list_head node; // 用于构建双向链表
struct class *class;
int (*add_dev) (struct device *, struct class_interface *);
void (*remove_dev) (struct device *, struct class_interface *);
};
注册或注销接口的函数:
int class_interface_register(struct class_interface *class_intf);
void class_interface_unregister(struct class_interface *class_intf);
一个类可注册多个接口。那么话说回来了,类接口有什么用呢?当所属calss的设备在添加或移除的时候,会调用类接口预先设置好的回调函数(add_dev、remove_dev)。
以device_add函数为例,其函数最后调用了class_intf->add_dev:
int device_add(struct device *dev)
{
...
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) // 遍历class私有变量p中指向的类接口列表,依次调用其add_dev方法
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
...
}
2.3 类创建
类的创建是通过class_create函数,定义在driver/base/class.c文件:
static void class_create_release(struct class *cls)
{
pr_debug("%s called for %s\n", __func__, cls->name);
kfree(cls);
}
/**
* class_create - create a struct class structure
* @owner: pointer to the module that is to "own" this struct class
* @name: pointer to a string for the name of this class.
* @key: the lock_class_key for this class; used by mutex lock debugging
*
* This is used to create a struct class pointer that can then be used
* in calls to device_create().
*
* Returns &struct class pointer on success, or ERR_PTR() on error.
*
* Note, the pointer created here is to be destroyed when finished by
* making a call to class_destroy().
*/
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;
retval = __class_register(cls, key);
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
紧接着call _class_register,__class_register是类注册函数。
2.4 类注册
类注册函数class_register函数实际也是调用的__class_register函数:
int class_register(struct class *cls);
如下:
int __class_register(struct class *cls, struct lock_class_key *key)
{
struct subsys_private *cp;
int error;
pr_debug("device class '%s': registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL); // 与bus类似,分配一个私有数据
if (!cp)
return -ENOMEM;
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put); // 初始化设备链表,用于连接calss上的device
INIT_LIST_HEAD(&cp->interfaces); // 初始化接口链表
kset_init(&cp->glue_dirs);
__mutex_init(&cp->mutex, "subsys mutex", key);
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
}
/* set the default /sys/dev directory for devices of this class */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj;
#if defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */
if (!sysfs_deprecated || cls != &block_class)
cp->subsys.kobj.kset = class_kset;
#else
cp->subsys.kobj.kset = class_kset;
#endif
cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;
error = kset_register(&cp->subsys); // 注册kset,在/sys/class下面创建一个目录,目录名称为class名称,同时发送一个uenvent事件给上层
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));
class_put(cls);
return error;
}
2.5 类卸载
void class_unregister(struct class *cls);
三、设备
3.1 设备定义
linux 系统中每一个设备都用 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.
* @driver_data: Private pointer for driver specific info.
* @links: Links to suppliers and consumers of this device.
* @power: For device power management.
* See Documentation/driver-api/pm/devices.rst 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/driver-api/pinctl.rst for details.
* @msi_list: Hosts MSI descriptors
* @msi_domain: The generic MSI domain this device is using.
* @numa_node: NUMA node this device is close to.
* @dma_ops: DMA mapping operations for this device.
* @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.
* @bus_dma_mask: Mask of an upstream bridge or bus which imposes a smaller DMA
* limit than the device itself supports.
* @dma_pfn_offset: offset of DMA memory range relatively of RAM
* @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.
* @fwnode: Associated device node supplied by platform firmware.
* @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.
* @iommu_fwspec: IOMMU-specific properties supplied by firmware.
*
* @offline_disabled: If set, the device is permanently online.
* @offline: Set after successful invocation of bus type's .offline().
* @of_node_reused: Set if the device-tree node is shared with an ancestor
* device.
* @dma_coherent: this particular device is dma coherent, even if the
* architecture supports non-coherent devices.
*
* 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 kobject kobj;
struct device *parent;
struct device_private *p;
const char *init_name; /* initial name of the device */
const struct device_type *type;
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 */
void *driver_data; /* Driver data, set and get with
dev_set_drvdata/dev_get_drvdata */
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct dev_links_info links;
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif
const struct dma_map_ops *dma_ops;
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. */
u64 bus_dma_mask; /* upstream dma_mask constraint */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
#ifdef CONFIG_DMA_DECLARE_COHERENT
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#endif
#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 fwnode_handle *fwnode; /* firmware device node */
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
bool dma_coherent:1;
#endif
};
其中部分参数含义如下:
- kobj:该数据结构对应的struct kobject;
- parent:指向设备的父设备,一般是该设备所属的bus、controller等设备;比如I2C从设备的父一般设置为I2C控制器;
- p:一个用于struct device的私有数据结构指针;驱动核心的私有数据,只有驱动核心才可以访问;
- init_name:设备的名字;
- type:指定设备类型;
- bus:该设备属于哪个总线;比如i2c_bus_type、platform_bus_type;
- driver:该设备对应的driver;
- platform_data:用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存到这里,需要使用的时候,再拿出来,因此设备模型并不关系该指针的实际意义;
- power、pm_domain:电源管理相关的参数;
- numa_node:NUMA功能;
- devt:dev_t是一个32位的整数,由主设备号和次设备号组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev/* 下的对应文件,如下:
- class:设备所属的类;
- groups:该设备的默认attribute集合。将会在设备注册时自动在sys文件系统中创建对应的文件;
3.2 设备创建
设备的创建是通过device_create函数,定义在driver/base/core.c文件:
/**
* device_create - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
核心函数是device_create_vargs,该函数新建device设备,并初始化其成员,最后调用device_register进行设备的注册:
/**
* device_create_vargs - creates a device and registers it with sysfs
* @class: pointer to the struct class that this device should be registered to
* @parent: pointer to the parent struct device of this new device, if any
* @devt: the dev_t for the char device to be added
* @drvdata: the data to be added to the device for callbacks
* @fmt: string for the device's name
* @args: va_list for the device's name
*
* This function can be used by char device classes. A struct device
* will be created in sysfs, registered to the specified class.
*
* A "dev" file will be created, showing the dev_t for the device, if
* the dev_t is not 0,0.
* If a pointer to a parent struct device is passed in, the newly created
* struct device will be a child of that device in sysfs.
* The pointer to the struct device will be returned from the call.
* Any further sysfs files that might be required can be created using this
* pointer.
*
* Returns &struct device pointer on success, or ERR_PTR() on error.
*
* Note: the struct class passed to this function must have previously
* been created with a call to class_create().
*/
struct device *device_create_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt,
va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV;
if (class == NULL || IS_ERR(class))
goto error;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); // 动态申请,dev
if (!dev) {
retval = -ENOMEM;
goto error;
}
dev->devt = devt; // 设备号
dev->class = class; // 所属calss
dev->parent = parent; // 父设备
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args); //kobject相关知识,暂且跳过,后续介绍
if (retval)
goto error;
retval = device_register(dev); // 设备注册
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
3.3 设备注册
在注册struct device 前,最少要设置parent, bus_id, bus, 和 release 成员,设备的函数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);
}
3.3.1 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都有公共的device_set,在系统初始化的时候动态申请的,具体的回掉函数show,store,release还是要由dev里的release或dev_type里的release或从属的class里的dev_release来清除
kobject_init(&dev->kobj, &device_ktype);// 初始化kobject
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);
}
3.3.2 device_add
/**
* 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); //增加设备的引用计数 dev->kobj->kref
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev); //私有数据没有的话申请并初始化,这个数据很重要,它是连接所属bus,parent,对应驱动等的重要连接点.
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()
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);//用dev的init_name初始化dev-kobject->name,实际就是目录名。
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)//dev的init_name不存在且dev-kobject->name也不存在,则使用bus的dev_name和dev_id来设置目录名
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) //如果上面几个步骤都还没找到可设的目录名,则失败返回,设备必须要放在某个目录下。
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent); //父节点引用计数加1
kobj = get_device_parent(dev, parent); //拿到父节点
if (kobj)
dev->kobj.parent = kobj; // 拿到的父节点赋值给本dev->kobj.parent,确定设备父子关系,也确定了sysfs中的目录关系
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));//设置该设备节点为-1,一般未注册前在device_initialize已经初始化为-1
/* 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);// 把内嵌的kobject注册到设备模型中将设备加入到kobject模型中,创建sys相关目录,目录名字为kobj->name
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
/*创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件,主要是在/sys/devices/.../中添加dev的uevent属性文件*/
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
/* 主设备号存在,则产生dev属性,并在/dev目录下产生设备节点文件 */
if (MAJOR(dev->devt)) { /*创建sys目录下设备的设备号属性,即major和minor /主要是在sys/devices/...中添加dev属性文件*/
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
/*在/sys/dev/char/或者/sys/dev/block/创建devt的属性的连接文件,形如10:45,由主设备号和次设备号构成,指向/sys/devices/.../的具体设备目录*/
error = device_create_sys_dev_entry(dev);//该链接文件只具备读属性,显示主设备号:次设备号,如10:45,用户空间udev响应uevent事件时,将根据设备号在/dev下创建节点文件
if (error)
goto devtattrError;
// 提交设备节点请求(创建/移除设备节点文件) mdev收到内核事件创建/dev/xx文件
devtmpfs_create_node(dev);
}
/*实际创建的kobject都是在device下面,其他class,bus之类的里面的具体设备都是device目录下设备的符号链接 */
error = device_add_class_symlinks(dev); // 在对应class的目录下,创建指向device的符号链接;在device的目录下,创建名称为subsystem、指向对应class目录的符号链接。
if (error)
goto SymlinkError;
error = device_add_attrs(dev); // 创建sys目录下设备其他属性文件(添加设备属性文件)
if (error)
goto AttrsError;
error = bus_add_device(dev); // 添加设备的总线属性,将设备加入到管理它的bus总线的设备连表上,创建subsystem链接文件,链接class下的具体的子系统文件夹 将设备添加到其总线的设备列表中。
if (error)
goto BusError;
error = dpm_sysfs_add(dev); //把设备增加到sysfs电源管理power目录(组)下,如果该设备设置电源管理相关的内容
if (error)
goto DPMError;
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);
/*产生一个内核uevent事件(这里是有设备加入),可以是helper,也可是通过netlink机制和用户空间通信该事件可以被内核以及应用层捕获,属于linux设备模型中热插拔机制*/
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);//给设备探测寻找相对应的驱动,在bus上找dev对应的drv,主要执行__device_attach,主要进行match,sys_add,执行probe函数和绑定等操作
if (parent)
klist_add_tail(&dev->p->knode_parent,//添加新设备到父设备的子列表中
&parent->p->klist_children);
if (dev->class) {//如果dev有所属类,则将dev的添加到类的设备列表里面
mutex_lock(&dev->class->p->mutex);//要使用class的互斥锁
/* tie the class to the device */
klist_add_tail(&dev->knode_class,//dev添加到class的klist_device链表(对driver也有klist_driver链表)
&dev->class->p->klist_devices);
/*通知有新设备加入,执行该dev的class_intf->add_dev(),好处是只有设备匹配注册成功了,才进行其它的注册工作(如字符设备的注册,生成/dev/***节点文件)以及部分初始化工作。*/
/* 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;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
devtmpfs_delete_node(dev);
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
3.3.3 函数调用栈
device_register函数的功能比较复杂,下图是device_register函数的调用流程(图中忽略了/dev/xxx创建的流程,/dev/xxx只有在device有主设备号的时候才会创建,一般只有在注册字符设备、块设备的时候才会指定主设备号):
通过过上图,可以看到设备的主要的初始化都是在/sys/devices/xxx/xxx下面创建一个名字为device.init_name的目录(例如xxx-device);
- 然后在里面创建自己的属性文件,比如dev、uevent文件;
- 同时它会创建一个subsystem的链接,指向bus或者class,表示它归属的类型,挂在bus下面意味着它是由某个bus管控着,如果挂在class下面,这只是一个视角问题,其实质也是表示它具备某些共同属性,乃至管理操作属性上的一致。
然后在/sys/bus或者/sys/class下面就没有必要创建重复的东西了,直接创建一个链接指向device,意味着从它们的目录去看,可以看到bus或者class都管着哪些设备。经过device_register后,创建的目录和链接关系如下:
这里的文件及目录的管理方式起本质是数据结构的管理思想,目录可以视为结构体,文件是数据结构成员,而链接文件是数据指针。上图实际上就是这几个数据结构除了自身的初始化外,然后就是建立结构之间的关联关系。
示例,比如我们通过class_create创建一个名字为"button"的 类,然后使用device_register注册一个名字为buttons的设备(该设备的父为null,class为button),那么将会在sys文件系统下创建/sys/class/button目录,并且目录下会创建一个buttons链接文件:
该链接文件指向了/sys/devices/virtual/button/buttons目录:
同时/sys/devices/virtual/button/buttons目录中的subsystem链接指向class也就是 /sys/class/button。
3.4 设备销毁
设备的注销函数为:
void device_unregister(struct device *dev);
四、驱动
4.1 驱动定义
linux 设备驱动模型中,之前内核直接叫做driver,后来改为device_driver,device和device_drvier 对应,device_driver 定义如下:
/**
* 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.
* @probe_type: Type of the probe (synchronous or asynchronous) to use.
* @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.
* @coredump: Called when sysfs entry is written to. The device driver
* is expected to call the dev_coredump API resulting in a
* uevent.
* @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 */
enum probe_type probe_type;
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;
void (*coredump) (struct device *dev);
struct driver_private *p;
};
其中部分参数含义如下:
- name:driver的名称;
- bus:该driver所驱动设备的总线设备。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化;
- owner、mod_name:内核module相关的变量;
- suppress_bind_attrs,是不在sysfs中启用bind和unbind attribute,如下:root@android:/storage/sdcard0 # ls /sys/bus/platform/drivers/switch-gpio/ ;
- probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备。
- shutdown、suspend、resume、pm:电源管理相关的参数;
- groups:和struct device结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sys文件系统中;
- p:一个用于struct device_driver的私有数据结构指针;驱动核心的私有数据,只有驱动核心才可以访问;
4.2 驱动注册
驱动注册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;
// driver的总线必须要有自己的subsys,因为这个才是整个bus连接device和driver的核心
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;
}
/* driver和bus两种都实现了下面函数,而实际最只能执行一个,所以告警说重复 */
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);
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
/* 把驱动加入总线的驱动链表 */
ret = bus_add_driver(drv);
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups); // 把驱动加入驱动的group
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD); // 向上报告增加事件
return ret;
}
下图是driver_register函数的调用流程:
driver_register如上图调用关系,可以看到它比device的注册做的事情少多了。
它主要是在/sys/bus/xxx-bus/drivers目录下创建一个名字为driver.name的目录(例如xxx-driver),然后在里面初始化驱动属性文件。
同时创建链接指向module,表示该驱动是由哪个内核模块提供功能,同样module也反向指向驱动,表示它提供的是什么样的驱动能力,当然只有驱动模块才会有指向驱动的链接。
经过driver_register后,其构成的目录和链接关系如下:
参考文章
[2]一张图掌握 Linux platform 平台设备驱动框架
[4]14.linux-platform机制实现驱动层分离(详解)
[6]Device & Driver -1- (Basic)
[7]Device & Driver -2- (Bus & Class)
[9]linux设备驱动模型(推荐)