Linux设备模型5

Linux设备模型5(基于Linux6.6)---device和device_driver介绍

 


一、device和device_driver概述

在 Linux 内核中,devicedevice_driver 是两个密切相关但又有所不同的概念,它们共同作用于硬件设备的管理和驱动。理解这两者的概念有助于深入理解 Linux 系统如何与硬件交互。

1.1 Device(设备)概述

在 Linux 中,device 代表一个硬件或虚拟设备(如磁盘、网卡、显示器、虚拟设备等)。每个设备都有其独特的功能和属性,内核使用设备模型来管理这些设备。device 在 Linux 内核中由 struct device 数据结构表示。

struct device

struct device 是 Linux 内核用于表示设备的主要结构体,它包含设备相关的信息,如设备的名称、状态、所属的总线类型、驱动程序等。具体定义可以在 include/linux/device.h 中找到。

设备的类型

Linux 支持各种类型的设备,包括:

  • 字符设备:如串口设备(tty)、键盘、鼠标等。通过字符设备,用户空间程序可以逐字节地与设备交互。
  • 块设备:如硬盘、闪存、USB 存储设备等。块设备通过缓冲区进行高效的块级数据读写。
  • 网络设备:如网卡等。
  • 输入设备:如触摸屏、键盘、鼠标等。

设备可以是物理设备(如硬盘、网卡)或虚拟设备(如内存设备、虚拟网络接口)。

设备与总线

设备通过总线(如 PCI、USB、I2C 等)与系统的其他部分进行连接。每种总线类型都有相应的驱动模型,用来管理设备的生命周期和与其他设备的交互。

1.2 Device Driver(设备驱动程序)概述

device driver(设备驱动程序)是操作系统内核中用于控制硬件设备的软件模块。设备驱动程序使操作系统能够与硬件进行交互,处理硬件设备的输入输出(I/O)操作,并提供对硬件资源的管理。每个设备都有一个或多个驱动程序来处理其特定的功能。

struct device_driver

设备驱动程序在 Linux 内核中由 struct device_driver 数据结构表示,它包含驱动程序的基本信息,如驱动程序的名称、设备匹配函数、操作接口等。

设备驱动程序的分类

设备驱动程序通常根据设备的类型和通信方式来分类,主要分为以下几种类型:

  • 字符设备驱动:管理字符设备(如串口、键盘、鼠标等)的驱动程序。
  • 块设备驱动:管理块设备(如硬盘、USB 存储设备)的驱动程序。
  • 网络设备驱动:管理网络设备(如网卡、Wi-Fi 接口等)的驱动程序。
  • 输入设备驱动:管理输入设备(如触摸屏、键盘、鼠标等)的驱动程序。

驱动程序的工作流程

设备驱动程序在操作系统中充当了硬件和内核之间的中介角色。其基本工作流程如下:

  1. 设备的注册:设备驱动程序首先通过 platform_driver_registerpci_register_driver 等函数注册自己,并告知内核该驱动支持哪些设备。
  2. 设备与驱动程序的匹配:当系统启动时,内核会根据硬件设备的标识信息(如设备 ID、总线类型等)将设备与适当的驱动程序进行匹配。匹配成功时,内核会调用驱动程序的 probe 函数来初始化设备。
  3. 设备的管理:驱动程序在 probe 函数中会对设备进行初始化,分配资源,并提供设备操作接口(如打开、读取、写入等)。
  4. 设备的卸载:当设备被移除或驱动程序卸载时,内核会调用 remove 函数清理设备资源。

1.3 设备和驱动程序的关系

  • 设备和驱动程序的绑定:一个设备通常由一个或多个驱动程序进行管理。在 Linux 中,设备和驱动程序是通过设备的 driver 字段来绑定的,驱动程序通过 probe 函数来初始化设备。

    设备和驱动程序的匹配通常是通过设备的 ID 信息来完成的。例如,在 PCI 总线下,设备通过其 PCI ID 和设备驱动程序进行匹配。

  • 设备树和驱动模型:Linux 采用设备树(Device Tree)模型来描述设备的层次结构。设备树通过描述设备的节点来表示硬件的层次和属性,而驱动程序则通过匹配设备树中的节点来操作对应的硬件。

1.4 Linux 设备驱动模型的工作流程

Linux 的设备驱动模型大体上遵循以下步骤:

  1. 注册设备:设备通过总线驱动(如 platform_driverpci_driver 等)注册到系统中。
  2. 设备匹配:内核根据设备的标识信息(如总线类型、设备 ID)与已注册的驱动程序进行匹配。
  3. 设备初始化(Probe):当设备和驱动程序成功匹配时,内核会调用驱动程序的 probe 函数进行设备的初始化工作。
  4. 设备使用:设备的驱动程序提供接口,用户空间可以通过文件系统或其他方式进行设备的读写操作。
  5. 设备移除(Remove):当设备被移除时,内核会调用驱动程序的 remove 函数,释放相关资源并注销设备。

device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备(device),所以kernel就为设备和驱动它的driver定义了两个数据结构,分别是device和device_driver。因此本文将会围绕这两个数据结构,介绍Linux设备模型的核心逻辑。

二、 struct device和struct device_driver

在阅读Linux内核源代码时,通过核心数据结构,即可理解某个模块60%以上的逻辑,设备模型部分尤为明显。

在include/linux/device.h中,Linux内核定义了设备模型中最重要的两个数据结构,struct device和struct device_driver。

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;

	const 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_ENERGY_MODEL
	struct em_perf_domain	*em_pd;
#endif

#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
	struct dev_msi_info	msi;
#ifdef CONFIG_DMA_OPS
	const struct dma_map_ops *dma_ops;
#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. */
	u64		bus_dma_limit;	/* upstream dma constraint */
	const struct bus_dma_region *dma_range_map;

	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
#ifdef CONFIG_SWIOTLB
	struct io_tlb_mem *dma_io_tlb_mem;
#endif
#ifdef CONFIG_SWIOTLB_DYNAMIC
	struct list_head dma_io_tlb_pools;
	spinlock_t dma_io_tlb_lock;
	bool dma_uses_io_tlb;
#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;

	const struct class	*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	struct dev_iommu	*iommu;

	struct device_physical_location *physical_location;

	enum device_removable	removable;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
	bool			state_synced:1;
	bool			can_match: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
#ifdef CONFIG_DMA_OPS_BYPASS
	bool			dma_ops_bypass : 1;
#endif
};

 说明:

parent,该设备的父设备,一般是该设备所从属的bus、controller等设备。

p,一个用于struct device的私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等,具体可查看源代码。

kobj,该数据结构对应的struct kobject。

init_name,该设备的名称。

type,struct device_type结构是新版本内核新引入的一个结构,它和struct device关系,非常类似stuct kobj_type和struct kobject之间的关系。

bus,该device属于哪个总线。

driver,该device对应的device driver。

platform_data,一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。

power、pm_domain,电源管理相关的逻辑,后续会由电源管理专题讲解。

pins,"PINCTRL”功能,暂不描述。

numa_node,"NUMA”功能,暂不描述。

dma_mask~archdata,DMA相关的功能,暂不描述。

devt,dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev/* 下的对应目录,如下:

class,该设备属于哪个class。

groups,该设备的默认attribute集合。将会在设备注册时自动在sysfs中创建对应的文件。

iommu_fwspec,固件提供的IOMMU特定属性

offline_disabled,如果设置,则设备永久在线

offline,成功调用总线类型的.offline()后设置

在最低级别,Linux系统中的每个设备都由一个结构设备的实例。 设备结构包含信息 设备模型核心需要为系统建模。 ‘

大多数子系统, 但是,请跟踪有关其托管设备的其他信息。 作为一个  结果,设备很少用裸设备结构表示;相反,该

结构,如kobject结构,通常嵌入其中 设备的更高级别表示。

  • struct device_driver
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;
 
	struct driver_private *p;
};

说明:

name设备驱动的名称。该名称通常是驱动模块的名称,用于在系统中标识这个驱动。通常与设备的名称匹配。
bus指向设备总线类型的指针。总线类型(例如,pci_bus_type、platform_bus_type 等)定义了设备如何连接和与内核交互。bus 字段告诉内核该驱动程序支持哪个总线的设备。
owner指向加载该驱动程序模块的 struct module 结构体的指针。这个字段指示该驱动程序所属的内核模块,有助于模块管理和引用计数。
mod_name用于内置模块的名称字段。这个字段通常在驱动程序直接内置到内核时使用,表示内置模块的名称。
suppress_bind_attrs如果设置为 true,则通过 sysfs 禁用设备的 bind 和 unbind 操作。这在某些特殊情况下可以防止设备与驱动程序的动态绑定和解绑。
probe_type设备驱动程序的探测类型。它指定了驱动程序的探测方法。通常有两种类型:PROBE_TYPE_NORMAL 和 PROBE_TYPE_SYNC,用来区分同步和异步的探测方式。
of_match_table指向设备树(Device Tree)匹配表的指针。of_match_table 定义了驱动程序支持哪些设备,并用于根据设备的设备树信息进行匹配。通常用于嵌入式系统中,设备的硬件信息通过设备树传递给内核。
acpi_match_table指向 ACPI 匹配表的指针。该表用于根据设备的 ACPI 信息与驱动程序进行匹配。ACPI(Advanced Configuration and Power Interface)是用于管理硬件配置和电源管理的标准。
probe设备驱动程序的初始化函数。当设备与驱动匹配时,内核会调用此函数来初始化设备。该函数返回值为 0 表示成功,非零值表示失败。
sync_state用于同步设备状态的回调函数。这个函数在设备驱动程序的同步操作时被调用,通常用于设备的状态更新。
remove驱动程序的移除函数。当设备被移除或驱动程序卸载时,内核会调用此函数以清理设备的资源。返回值为 0 表示成功,非零值表示失败。
shutdown驱动程序的关机函数。在系统关闭或设备关闭时调用。这个函数通常用于清理设备的电源或资源。
suspend驱动程序的挂起函数。当设备进入睡眠模式或被挂起时调用。pm_message_t 提供了关于挂起状态的信息。返回值为 0 表示成功,非零值表示失败。
resume驱动程序的恢复函数。当设备从挂起状态恢复时调用。返回值为 0 表示成功,非零值表示失败。
groups设备的属性组。属性组定义了设备通过 sysfs 暴露的属性。这些属性可以在 /sys 文件系统中被用户空间程序访问和修改。
dev_groups设备特有的属性组。这个字段用于指定与特定设备相关的属性组。
pm指向设备电源管理操作结构的指针。dev_pm_ops 包含有关设备的电源管理操作(如 suspend、resume 等)的函数指针。
coredump用于设备内核崩溃转储的回调函数。如果设备在运行过程中发生崩溃,可以使用此函数来处理崩溃转储信息。
p驱动程序私有数据指针,通常用于存储与驱动程序相关的额外私有数据。这个字段可以由驱动程序根据需要自定义,以存储非标准的驱动状态。

三、 设备模型框架下驱动开发

在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:

步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。

步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。

这两步完成后,内核会在合适的时机,调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。

以上两个步骤的补充说明:

1. 一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,本文并不会过多涉及device、device_driver等模块的实现细节)。

2. 内核提供很多struct device结构的操作接口(具体可以参考include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。

3. device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。

4. device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。

5. driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。

四、 设备驱动probe的时机

所谓的"probe”,是指在Linux内核中,如果存在相同名称的device和device_driver,内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作("remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似,就不再单独说明了)。

设备驱动prove的时机有如下几种(分为自动触发和手动触发):

  • 将struct device类型的变量注册到内核中时自动触发(device_register,device_add,device_create_vargs,device_create)
  • 将struct device_driver类型的变量注册到内核中时自动触发(driver_register)
  • 手动查找同一bus下的所有device_driver,如果有和指定device同名的driver,执行probe操作(device_attach)
  • 手动查找同一bus下的所有device,如果有和指定driver同名的device,执行probe操作(driver_attach)
  • 自行调用driver的probe接口,并在该接口中将该driver绑定到某个device结构中----即设置dev->driver(device_bind_driver)

五、其它杂项

5.1、 device_attribute和driver_attribute

在大多数时候,attribute文件的读写数据流为:vfs---->sysfs---->kobject---->attibute---->kobj_type---->sysfs_ops---->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上层数据结构实现。

Linux内核中关于该内容的例证到处都是,device也不无例外的提供了这种例子,如下

 drivers/base/core.c

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

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) {
		printk("dev_attr_show: %pS returned bad count\n",
				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,
};
 
 
static struct kobj_type device_ktype = {
	.release	= device_release,
	.sysfs_ops	= &dev_sysfs_ops,
	.namespace	= device_namespace,
};
 
 
struct sysfs_ops {
	ssize_t	(*show)(struct kobject *, struct attribute *,char *);
	ssize_t	(*store)(struct kobject *,struct attribute *,const char *, size_t);
};
 
 
/* interface for exporting device attributes */
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);
};

至于driver的attribute,则要简单的多,其数据流为:vfs---->sysfs---->kobject---->attribute---->driver_attribute,如下:

include/linux/device/driver.h 

/* sysfs interface for exporting driver attributes */

struct driver_attribute {
	struct attribute attr;
	ssize_t (*show)(struct device_driver *driver, char *buf);
	ssize_t (*store)(struct device_driver *driver, const char *buf,
			 size_t count);
};

#define DRIVER_ATTR_RW(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
	struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)

5.2、 device_type

device_type是内嵌在struct device结构中的一个数据结构,用于指明设备的类型,并提供一些额外的辅助功能。它的的形式如下:

 /*
 * The type of device, "struct device" is embedded in. A class
 * or bus can contain devices of different types
 * like "partitions" and "disks", "mouse" and "event".
 * This identifies the device type and carries type-specific
 * information, equivalent to the kobj_type of a kobject.
 * If "name" is specified, the uevent will contain it in
 * the DEVTYPE variable.
 */
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;
};

说明:
    const char *name:
        设备类型的名称。该字段通常包含一个描述设备类型的字符串,用于标识设备的类别。例如,你可以为一个特定硬件类型定义一个名字,作为设备的类别。

    const struct attribute_group **groups:
        设备的属性组,用于描述设备在 sysfs 中的属性。attribute_group 是一种包含多个 attribute 的结构体,sysfs 属性是设备与用户空间交互的一种机制。你可以通过 sysfs 文件系统读取或设置这些属性。
        该字段为一个指向 attribute_group 结构体数组的指针,用于定义一组设备属性。

    int (*uevent)(const struct device *dev, struct kobj_uevent_env *env):
        这个回调函数用于生成设备的 uevent(用户空间事件)。当设备状态发生变化时,内核会触发一个 uevent,通知用户空间。这些事件通常用于 udev 设备管理工具。
        例如,在设备连接或断开时,内核会通过此函数发送通知。

    char *(*devnode)(const struct device *dev, umode_t *mode, kuid_t *uid, kgid_t *gid):
        这个回调函数用于为设备生成设备节点文件。devnode 生成的设备节点文件通常位于 /dev 目录下,表示与设备相关的文件系统接口。
        它接收设备信息和一些权限信息(如文件模式、UID、GID),并返回设备节点的路径名。

    void (*release)(struct device *dev):
        这个回调函数在设备被释放时调用。它通常用于设备对象的清理工作,比如释放设备占用的资源、内存等。
        设备的 release 函数在设备生命周期的最后阶段被调用,当设备对象不再使用时,该函数会被触发来释放资源。

    const struct dev_pm_ops *pm:
        这是一个指向 dev_pm_ops 结构体的指针,用于指定设备的电源管理操作。dev_pm_ops 结构体定义了与电源管理相关的回调函数,如 suspend、resume 等。
        通过这个字段,设备可以定义如何处理电源管理(如挂起、恢复、关闭等)。

5.3、 root device

在sysfs中有这样一个目录:/sys/devices,系统中所有的设备,都归集在该目录下。有些设备,是通过device_register注册到Kernel并体现在/sys/devices/xxx/下。但有时候仅仅需要在/sys/devices/下注册一个目录,该目录不代表任何的实体设备,这时可以使用下面的接口:

include/linux/device.h 

/*
 * Root device objects for grouping under /sys/devices
 */
struct device *__root_device_register(const char *name, struct module *owner);

/* This is a macro to avoid include problems with THIS_MODULE */
#define root_device_register(name) \
	__root_device_register(name, THIS_MODULE)

void root_device_unregister(struct device *root);

该接口会调用device_register函数,向内核中注册一个设备,但是,没必要注册与之对应的driver。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值