linux 驱动程序移植--总线设备驱动程序
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-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 embe
推荐阅读
-
在 Linux 内核中使用 spi 屏幕驱动程序 - fbtft
-
Linux MIPI DSI 驱动程序调试说明 - LCD 时序参数配置 (III)
-
RT-Thread 设备驱动程序开发指南高级版 - 手把手教你如何开发第一个 Oar LCD 外围设备驱动程序
-
Linux 音频驱动程序 - WAV 文件格式分析
-
Linux 内核 - 设备驱动程序 (III) 总线、设备、驱动程序模型探索 - I. 简介
-
Linux 内核驱动程序开发-006 内核计时器
-
Linux 驱动程序开发 -(IV)内核计时器
-
构建驱动程序模块 3 - 移植 STM32 NandFlash yaffs2 文件系统
-
Hi3559AV100 外置 UVC/MJPEG 摄像机实时图像采集设计(I):Linux USB 摄像机驱动程序分析
-
在 Windows 11 中,驱动程序无法在此设备上加载 ene.sys