device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备,所以kernel就为设备和驱动它的driver定义了两个数据结构,分别是device和device_driver。
1. struct device
c
/**
* 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.
* @power: For device power management.
* See Documentation/power/devices.txt for details.
* @pm_domain: Provide callbacks that are executed during system suspend,
* hibernation, system resume and during runtime PM transitions
* along with subsystem-level and driver-level callbacks.
* @pins: For device pin management.
* See Documentation/pinctrl.txt for details.
* @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_mask: Dma mask (if dma'ble device).
* @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
* hardware supports 64-bit addresses for consistent allocations
* such descriptors.
* @dma_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().
*
* At the lowest level, every device in a Linux system is represented by an
* instance of struct device. The device structure contains the information
* that the device model core needs to model the system. Most subsystems,
* however, track additional information about the devices they host. As a
* result, it is rare for devices to be represented by bare device structures;
* instead, that structure, like kobject structures, is usually embedded within
* a higher-level representation of the device.
*/
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
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
#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 fwnode_handle *fwnode; /* firmware 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;
struct iommu_fwspec *iommu_fwspec;
bool offline_disabled:1;
bool offline:1;
};
struct device
是 Linux 设备模型中用于描述设备的基础结构体,包含设备驱动程序中几乎所有的关键信息。这个结构体的每个成员都代表设备的不同属性或控制与管理功能。
主要成员变量
struct device *parent
- 设备的父设备指针。大多数情况下,父设备是某种总线或控制器(如 PCI 控制器)。如果为
NULL
,则表示该设备是顶层设备。
- 设备的父设备指针。大多数情况下,父设备是某种总线或控制器(如 PCI 控制器)。如果为
struct device_private *p
- 保存设备私有数据的指针,用于设备模型核心管理设备的内部数据。通常开发者不直接操作这个字段。
struct kobject kobj
- kobject 是 Linux 内核中的一种对象管理机制,
kobj
用于在/sys
文件系统中表示设备,通过它可以导出设备的属性到用户空间。
- kobject 是 Linux 内核中的一种对象管理机制,
const char *init_name
- 设备的初始名称,用于在注册时给设备指定名称。
- 注1:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据"bus name + device ID"的方式创造。
const struct device_type *type
- 设备类型指针,定义了设备的类型及其类型特定信息。
struct mutex mutex
- 设备互斥锁,用于同步对该设备的操作,避免多个线程同时访问设备驱动的共享资源时引发冲突。
struct bus_type *bus
- 指向设备所在的总线类型(如 PCI、I2C、SPI 等)的指针,用于描述设备属于哪个总线。这里要留意一下,后面会讲到平台总线
struct device_driver *driver
- 指向分配该设备的驱动程序结构体的指针,表示当前哪个驱动程序在控制这个设备。
void *platform_data
- 平台相关的数据指针。对于嵌入式或 SoC 硬件,
platform_data
通常指向特定设备的硬件配置数据,例如可用的端口、芯片变体等。它帮助减少驱动程序中的板级特定代码。
- 平台相关的数据指针。对于嵌入式或 SoC 硬件,
void *driver_data
- 驱动程序的私有数据,驱动可以通过
dev_set_drvdata()
和dev_get_drvdata()
来设置和获取该字段。
- 驱动程序的私有数据,驱动可以通过
struct dev_links_info links
- 存储与设备之间的拓扑关系信息,如设备间的依赖性。
struct dev_pm_info power
- 设备电源管理信息。包含电源状态、挂起、恢复等操作相关的字段,涉及设备的电源管理。
struct dev_pm_domain *pm_domain
- 设备电源管理域的指针,定义系统挂起、休眠、恢复时需要执行的回调函数。
struct irq_domain *msi_domain
- 当设备使用 Message Signaled Interrupts (MSI) 时,指向管理设备 MSI 中断的域。
struct dev_pin_info *pins
- 引脚管理信息,设备在引脚控制方面的数据结构,用于 GPIO 和 pinctrl 子系统。
struct list_head msi_list
- 保存设备的 MSI 描述符的链表头。
int numa_node
- 设备所在的 NUMA(Non-Uniform Memory Access)节点。
u64 *dma_mask
- DMA 掩码,用于描述设备支持的 DMA 地址范围。设备使用 DMA 时,用来表示设备能够访问的物理内存地址空间的最大范围。
u64 coherent_dma_mask
- 与
dma_mask
类似,但专门用于一致性内存的 DMA 掩码。
- 与
unsigned long dma_pfn_offset
- DMA 内存与物理内存之间的偏移量。
struct device_dma_parameters *dma_parms
- DMA 参数,低层驱动可以通过它告知 IOMMU 关于段限制等特性。
struct list_head dma_pools
- 设备的 DMA 池列表头,表示设备的 DMA 内存池。
struct dma_coherent_mem *dma_mem
-
- 用于管理一致性 DMA 内存的内部数据结构。
struct cma *cma_area
- 用于 DMA 分配的连续内存区域指针。
struct dev_archdata archdata
- 设备的体系结构特定数据。不同平台可以利用这个字段扩展额外的体系结构相关的信息。
struct device_node *of_node
- 指向与设备相关的设备树节点。对于使用设备树的系统,
of_node
记录设备在设备树中的描述信息。
- 指向与设备相关的设备树节点。对于使用设备树的系统,
struct fwnode_handle *fwnode
- 设备的固件节点,通常用于指向 ACPI 或设备树中的设备描述。
dev_t devt
- 设备的
dev_t
类型,设备号,表示设备的主设备号和次设备号,用于创建设备节点。
- 设备的
u32 id
- 设备实例 ID,用于标识同类设备的不同实例。
spinlock_t devres_lock
- 设备资源锁,用于保护设备的资源列表。
struct list_head devres_head
- 设备资源链表头,记录与设备相关的资源(如内存、I/O 端口等)。
struct klist_node knode_class
- 用于将设备添加到设备类链表中的节点。
struct class *class
- 设备的类,用于分类设备(如输入设备、网络设备等)。设备类定义设备的行为及其属性。
const struct attribute_group **groups
- 可选的属性组,用于定义设备在
/sys
文件系统中暴露的额外属性。
- 可选的属性组,用于定义设备在
void (*release)(struct device *dev)
- 设备的释放回调函数,当所有对设备的引用都释放后,调用该函数释放设备占用的资源。
struct iommu_group *iommu_group
- 设备所属的 IOMMU 组指针。IOMMU 组用于管理设备之间的内存隔离。
struct iommu_fwspec *iommu_fwspec
- 由固件提供的 IOMMU 特定属性。
状态标志位
bool offline_disabled:1
- 标志位,指示设备是否永久在线,如果设置为
true
,设备无法进入离线状态。
- 标志位,指示设备是否永久在线,如果设置为
bool offline:1
- 标志位,指示设备是否离线。
struct device
是 Linux 设备模型中非常重要的结构体,负责设备的管理与控制。它包含了设备在总线上的位置信息、与驱动程序的关联、DMA 信息、电源管理、IOMMU 等多种设备相关的属性。在实际使用时,struct device
通常是被嵌入到更高级的设备结构中,用于表示具体的硬件设备,比如平台platfrom_device结构体中。其实这些成员内核开发者也给了详细的注解。
2. struct device_driver
c
/**
* 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.
* @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;
struct driver_private *p;
};
struct device_driver
是 Linux 内核中用于描述设备驱动程序的基本结构体,它用于表示设备驱动的属性和操作。该结构体负责跟踪系统中已知的所有驱动程序,并为新设备和驱动程序之间进行匹配提供基础。
主要成员变量
const char *name
- 驱动程序的名称。在设备和驱动之间进行匹配时,设备的名称和驱动的名称会进行比较。
struct bus_type *bus
- 驱动程序所依附的总线类型。每个驱动程序都属于某个总线类型,例如 PCI、I2C、SPI 等。
struct module *owner
- 模块的所有者,指向实现该驱动的模块(内核模块)结构体。它确保驱动所在模块在使用期间不会被卸载。
const char *mod_name
- 模块的名称。对于内建模块(built-in modules),这个字段用于指明模块的名字。
bool suppress_bind_attrs
- 控制是否通过 sysfs 禁止手动绑定/解绑设备。如果设置为
true
,则无法通过/sys
文件系统中的 bind 和 unbind 文件手动进行设备和驱动的绑定/解绑。
- 控制是否通过 sysfs 禁止手动绑定/解绑设备。如果设置为
enum probe_type probe_type
- 控制驱动程序的
probe
操作的类型,可以是同步的(synchronous)或异步的(asynchronous)。它决定了驱动程序在处理设备时,probe
函数是阻塞执行还是非阻塞执行。
- 控制驱动程序的
const struct of_device_id *of_match_table
- 用于设备树(Device Tree)的匹配表。驱动程序可以通过该表与设备树中的设备节点进行匹配。
const struct acpi_device_id *acpi_match_table
- 用于 ACPI(高级配置与电源接口)的匹配表。驱动程序通过该表与系统的 ACPI 表进行匹配,从而识别设备。
驱动操作接口
**int (*probe)(struct device *dev)**
** **probe
函数用于在设备检测到时调用,它用于检查该驱动是否可以支持指定的设备,并在可以支持时绑定设备和驱动。返回0
表示成功,非零表示失败。- 所谓的"probe",是指在Linux内核中,如果存在相同名称的device和device_driver(注:还存在其它方式),内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作。
- 这里先不讲,在bus中再提
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
- 驱动程序导出的默认属性组。内核会自动创建这些属性组,使驱动程序可以将配置或状态信息通过
/sys
文件系统提供给用户空间。
- 驱动程序导出的默认属性组。内核会自动创建这些属性组,使驱动程序可以将配置或状态信息通过
const struct dev_pm_ops *pm
- 设备电源管理操作的指针。通过该指针,驱动程序可以定义设备的电源管理操作(如挂起、恢复、系统睡眠等)。
struct driver_private *p
- 驱动程序核心的私有数据字段。只有驱动程序核心可以访问该字段,通常开发者不需要直接使用这个字段。
struct device_driver
是 Linux 驱动模型中用于描述设备驱动程序的核心结构体,它定义了驱动程序的各种操作接口(如 probe
、remove
、shutdown
、suspend
、resume
等),并包含了一些驱动程序的元数据(如名称、所属总线类型、匹配表等)。通过这个结构体,驱动程序可以被内核识别,并通过合适的接口与设备进行绑定、控制和管理。
3. 驱动开发的基本步骤
设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:
步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。
步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。
完成这两步,内核会在何时的时机对他们进行匹配,当然匹配的前提是这两个结构体中的name是相同的(同一bus的情况下)
但是,一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,并不会过多涉及device、device_driver等模块的实现细节)。
就比如platform device,很多情况下不需要我们自己去填充,只需要在设备树(后续会讲)中去定义好设备的相关信息(寄存器?中断资源?引脚?),平台就会自己帮我们注册号platform device。而platform其实依靠的也是bus总线,关于总线和平台设备具体的会另开文章讲解。