专栏首页Linux内核深入分析Linux设备驱动模型-Device

Linux设备驱动模型-Device

前言

Linux将所有的设备统一抽象为struct device结构, 同时将所有的驱动统一抽象为struct device_driver结构。这样设计之后就方便驱动开发工程师编写驱动,只需要将具体的设备包含struct device结构,具体的驱动包含struct device_driver结构。最终会调用device_register和driver_register将驱动和设备注册到系统,表现出来就是在sys目录的device和driver目录下。本小节先分析device结构,以及相关API,以及如何注册到系统中,以及提供给上层的sys接口。

数据结构

Linux将所有的设备统一抽象为struct device结构。定义在<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 */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	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. */
	unsigned long	dma_pfn_offset;

	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结构体比较复杂,同时其中还包含了dma, numa, pintrl, pm相关的知识,本小节不做过多谈论。不过该结构体的注释是相当的详细,可以参考。

parent: 代表设备的parent节点,通常parent是bus或者controller。 如果此parent为NULL,则此设备就是顶层设备。

p: 代表device的私有数据的指针,指针类型为device_private。

kobj: kobject结构,用于层级关系。

init_name: 设备对象的名称,出现在sys目录下。

type: 指向device_type结构,代表了设备的特殊的信息。

mutex: 同步操作。

bus: 设备所属的总线。

driver: 设备所对应的驱动。

platfrorm_data: 设备所对应的平台数据。

driver_data: 保存设备所对于驱动的数据,一般使用get/set函数。

power/pm_domain: 电源管理相关的内容。

of_node: 该设备对应的设备树结构。

devt: 设备号,由主设备号和次设备号组成。

id: 设备索引号。

devres_head: 设备资源管理的链表。用于将设备使用的资源用链表管理。

class: 设备所属的class。

group: 设备默认的属性。

设备相关函数

  • devices_init

此函数主要是初始化devices_kset,以及初始化所有的dev_kset

int __init devices_init(void)
{
	devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);   
	if (!devices_kset)
		return -ENOMEM;
	dev_kobj = kobject_create_and_add("dev", NULL);
	if (!dev_kobj)
		goto dev_kobj_err;
	sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
	if (!sysfs_dev_block_kobj)
		goto block_kobj_err;
	sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
	if (!sysfs_dev_char_kobj)
		goto char_kobj_err;

	return 0;

 char_kobj_err:
	kobject_put(sysfs_dev_block_kobj);
 block_kobj_err:
	kobject_put(dev_kobj);
 dev_kobj_err:
	kset_unregister(devices_kset);
	return -ENOMEM;
}

内核中每一个设备都是struct device结构,同时内核将所有的设备使用devices_kset管理。同时为了统一方便管理又将设备分为block和char设备,生成的内核对象分别为sysfs_dev_block_kobj和sysfs_dev_char_kobj。当然这两个内核模块都是在dev内核模块之下的。

这个函数的操作实现最后表现到sys文件目录下,分别为/sys/devices, /sys/dev, /sys/dev/char, /sys/dev/block。

  • device_initialize(用于初始化一个device)
void device_initialize(struct device *dev)
{
	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);
}

主要是设置设备所属的kset,设置设备所属kset的ktype,初始化设备资源管理的链表,以及设备电源管理初始化。

  • device_add(添加一个设备到系统中)
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;

	if (!dev->p) {
		error = device_private_init(dev);            ---------------A
		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 = NULL;
	}

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

	if (!dev_name(dev)) {                                                  //设置device的name域,如果设置失败退出。
		error = -EINVAL;                                                   
		goto name_error;
	}                                              

	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

	parent = get_device(dev->parent);                                      //增加设备parent的引用计数
	kobj = get_device_parent(dev, parent);                                 //设置设备parent的内核对象,建立设备在sys下的层次结构。
	if (kobj)
		dev->kobj.parent = kobj;

	/* use parent numa_node */
	if (parent)
		set_dev_node(dev, dev_to_node(parent));

	/* 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_add将设备添加到sys中
	if (error)
		goto Error;

	/* notify platform of device entry */
	if (platform_notify)                                                 
		platform_notify(dev); 

	error = device_create_file(dev, &dev_attr_uevent);                 //创建设备的uevent属性
	if (error)
		goto attrError;

	if (MAJOR(dev->devt)) {
		error = device_create_file(dev, &dev_attr_dev);             //如果主设备号存在,创建dev属性
		if (error)
			goto ueventattrError;

		error = device_create_sys_dev_entry(dev);
		if (error)
			goto devtattrError;

		devtmpfs_create_node(dev);                                  //调用devtmpfs,自动创建设备节点
	}

	error = device_add_class_symlinks(dev);                             //创建链接文件
	if (error)
		goto SymlinkError;
	error = device_add_attrs(dev);                                      //添加设备的属性
	if (error)
		goto AttrsError;
	error = bus_add_device(dev);                                        //将此设备添加到一条总线上
	if (error)
		goto BusError;
	error = dpm_sysfs_add(dev);
	if (error)
		goto DPMError;
	device_pm_add(dev);                                                  //将设备添加到pm core链表中,会在suspend中使用到

	/* 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中
					     BUS_NOTIFY_ADD_DEVICE, dev);

	kobject_uevent(&dev->kobj, KOBJ_ADD);                     //使用uevent通知上层,这时候就会使用到device_uevent_ops中的函数
	bus_probe_device(dev);                                    //匹配bus下的设备与驱动
	if (parent)
		klist_add_tail(&dev->p->knode_parent,             //如果parent存在,将dev添加到parent链表中
			       &parent->p->klist_children);

	if (dev->class) {                                         //如果class存在,则将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);
	}
}

A: 如果设备的device_private不存在,就重现分配一个

int device_private_init(struct device *dev)
{
	dev->p = kzalloc(sizeof(*dev->p), GFP_KERNEL);                         //分配一个device_private结构
	if (!dev->p)
		return -ENOMEM;
	dev->p->device = dev;                                                  //设置device_private的device结构
	klist_init(&dev->p->klist_children, klist_children_get,         
		   klist_children_put);                                       //初始化设备下所有children链表
	INIT_LIST_HEAD(&dev->p->deferred_probe);
	return 0;
}

B: 主要分析下如何将设备添加到一条总线上,这是核心。

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);
		if (error)
			goto out_put;
		error = device_add_groups(dev, bus->dev_groups);
		if (error)
			goto out_groups;
		error = sysfs_create_link(&bus->p->devices_kset->kobj,
						&dev->kobj, dev_name(dev));
		if (error)
			goto out_id;
		error = sysfs_create_link(&dev->kobj,
				&dev->bus->p->subsys.kobj, "subsystem");
		if (error)
			goto out_subsys;
		klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
	}
	return 0;
}

如果此设备不属于任何总线,直接就返回,否则添加设备属性,创建链接文件,将此bus下的设备添加到klist_devices链表中。

C: 关于总线先设备与驱动的匹配是设备驱动模型的核心

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

一般总线都会支持自动匹配,当然可以修改driver_autoprobe的值,然后调用device_attach进行匹配。

int device_attach(struct device *dev)
{
	int ret = 0;

	device_lock(dev);                      //因为此操作只能运行一个dev进行,需要互斥保护。
	if (dev->driver) {                     //如果设备存在对应的driver,说明已经进行了匹配,只需要调用device_bind_driver在sys中建立关系。
		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 {
		ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);      //如果driver不存在,则需要遍历dev所在bus下的所有driver
		pm_request_idle(dev);
	}
out_unlock:
	device_unlock(dev);
	return ret;
}

当遍历driver下过程中,最终会调用到__device_attach函数。

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

函数match最终使用的匹配是

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

根据bus是否存在match函数,如果存在调用bus的match函数,如果不存在,直接返回1。如果匹配成功之后就会调用driver_probe_device进行绑定,最终会调用really_probe函数。

static int really_probe(struct device *dev, struct device_driver *drv)
{
	int ret = 0;
	int local_trigger_count = atomic_read(&deferred_trigger_count);

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

	dev->driver = drv;                                                 //设置设备的driver变量

	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto probe_failed;

	if (driver_sysfs_add(dev)) {                                       
		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
			__func__, dev_name(dev));
		goto probe_failed;
	}

	if (dev->bus->probe) {
		ret = dev->bus->probe(dev);                    //如果bus的probe存在,则调用bus的probe函数
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {                               //否则调用驱动的probe,这下知道驱动的probe函数是如何调用的。
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}

	driver_bound(dev);                                     //将设备所属的驱动加入到驱动链表中,使用通知链发出BOUND信息。
	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:               //probe失败之后,就会清空设备资源,设备driver设置为NULL,从sys中移除dev建立的driver链接。
	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);
		/* Did a trigger occur while probing? Need to re-trigger if yes */
		if (local_trigger_count != atomic_read(&deferred_trigger_count))
			driver_deferred_probe_trigger();
	} 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;
}
  • device_register(注册一个设备到系统中是上述两个函数的结合体)
  • device_unregister(将一个设备从系统中注销)
void device_unregister(struct device *dev)
{
	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
	device_del(dev);
	put_device(dev);
}

此函数分为两步走,第一步从系统中移除此设备,第二步将设备的引用计数减去1。主要的注销操作在device_del中实现,也就是register的反操作。

void device_del(struct device *dev)
{
	struct device *parent = dev->parent;
	struct class_interface *class_intf;

	/* Notify clients of device removal.  This call must come
	 * before dpm_sysfs_remove().
	 */
	if (dev->bus)      //如果bus总线存在,调用通知链删除设备
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_DEL_DEVICE, dev);
	dpm_sysfs_remove(dev); //电源管理函数,关掉设备电源
	if (parent)            //如果有父设备,将当前设备从父设备所属链表删除
		klist_del(&dev->p->knode_parent);
	if (MAJOR(dev->devt)) {     //如果主设备号存在, 动态删除设备节点,移除设备的属性
		devtmpfs_delete_node(dev);
		device_remove_sys_dev_entry(dev);
		device_remove_file(dev, &dev_attr_dev);
	}
	if (dev->class) {   //如果设备所属的class存在,移除设备的链接,调用remove_dev做移除,从class链表中删除该设备
		device_remove_class_symlinks(dev);

		mutex_lock(&dev->class->p->mutex);
		/* notify any interfaces that the device is now gone */
		list_for_each_entry(class_intf,
				    &dev->class->p->interfaces, node)
			if (class_intf->remove_dev)
				class_intf->remove_dev(dev, class_intf);
		/* remove the device from the class list */
		klist_del(&dev->knode_class);
		mutex_unlock(&dev->class->p->mutex);
	}
	device_remove_file(dev, &dev_attr_uevent);    //移除设备的uevent属性
	device_remove_attrs(dev);            
	bus_remove_device(dev);                       //从总线中移除设备,以及电源管理等。
	device_pm_remove(dev);
	driver_deferred_probe_del(dev);

	/* Notify the platform of the removal, in case they
	 * need to do anything...
	 */
	if (platform_notify_remove)
		platform_notify_remove(dev);
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_REMOVED_DEVICE, dev);
	kobject_uevent(&dev->kobj, KOBJ_REMOVE);
	cleanup_device_parent(dev);
	kobject_del(&dev->kobj);              //删除设备的内核模块
	put_device(parent);
}

设备属性

linux中使用device_attribute结构体表示一个设备的属性

struct device_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);
};

通常使用to_dev_attr宏定义得到device_attribute对象

#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)

同时为了方便定义设备的属性,内核提供了一系列相关的宏定义,用于初始化设备的属性。

#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DEVICE_ATTR_RW(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RO(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_WO(_name)

关于设备属性的调用过程,最终会调用到设备的show和store函数中,具体的流程分析可见Linux设备驱动模型-Ktype

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
			     char *buf)
{
	struct device_attribute *dev_attr = to_dev_attr(attr);
	struct device *dev = kobj_to_dev(kobj);
	ssize_t ret = -EIO;

	if (dev_attr->show)
		ret = dev_attr->show(dev, dev_attr, buf);
	if (ret >= (ssize_t)PAGE_SIZE) {
		print_symbol("dev_attr_show: %s returned bad count\n",
				(unsigned long)dev_attr->show);
	}
	return ret;
}

static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
			      const char *buf, size_t count)
{
	struct device_attribute *dev_attr = to_dev_attr(attr);
	struct device *dev = kobj_to_dev(kobj);
	ssize_t ret = -EIO;

	if (dev_attr->store)
		ret = dev_attr->store(dev, dev_attr, buf, count);
	return ret;
}

static const struct sysfs_ops dev_sysfs_ops = {
	.show	= dev_attr_show,
	.store	= dev_attr_store,
};

设备类型

在include/linux/device.h文件存在这样的结构体:

struct device_type {
	const char *name;
	const struct attribute_group **groups;
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	char *(*devnode)(struct device *dev, umode_t *mode,
			 kuid_t *uid, kgid_t *gid);
	void (*release)(struct device *dev);

	const struct dev_pm_ops *pm;
};

此结构代表设备类型。通常一个Bus下会存在各种设备的,比如:disks, mouse, event等。而此结构就表明此设备是何种类型的设备。

name: 代表设备的名称,在上报event的时候,会通过DEVTYPE设置设备的类型名称。

groups: 代码设备的属性,在添加设备的属性的时候,如果存在设备类型,也会添加设备类型的属性。

uevent: 在新增一个设备时,通过该函数上报设备类型的uevent。

devnode: 在创建一个设备节点的时候会使用到,获取设备的信息。

release: 在设备释放时候,如果存在设备类型会调用到。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android getevent,sendevent,input keyevent

    getevent和sendevent是Android系统自带的获取设备的收发事件和模拟设备事件进行自动话测试。而input keyevent也在自动话测试中有很...

    DragonKingZhu
  • Linux音频驱动-OSS和ALSA声音系统简介及其比较

    昨天想在Ubuntu上用一下HTK工具包来绘制语音信号的频谱图和提取MFCC的结果,但由于前段时间把Ubuntu升级到13.04,系统的声卡驱动是ALSA(Ad...

    DragonKingZhu
  • Linux电源管理-Suspend/Resume流程

    根据上一节linux电源管理-概述可知,linux电源管理存在的几种方式,如何查看这几种方式,以及最后的如何睡眠唤醒等。

    DragonKingZhu
  • Linux运维面试收藏

    mount.cifs //IP地址/server /mnt/server -o user=administrator,password=12...

    菲宇
  • VMware—Linux 5安装

    挂载点 装置 说明

    DataScience
  • Ubuntu安装Opencv记录(附人脸识别和人眼识别例子)

    木东居士
  • linux软raid制作记录

    domain0
  • mac电脑进行可见光通信实验要点

    俺踏月色而来
  • Ubuntu16.04安装Python3

    https://www.python.org/downloads/release/python-370/

    py3study
  • mac电脑进行可见光通信实验要点

    俺踏月色而来

扫码关注云+社区

领取腾讯云代金券