资源分配及设备总线

资源分配

I/O端口和I/O内存事两种概念上的方法,用以支持设备驱动程序和设备之间的通信;为使得各种不同的驱动程序彼此互不干扰,有必要事先为驱动程序分配端口和I/O内存范围;这确保不同设备驱动程序不会试图访问同样的资源;

资源管理

需要了解用于资源管理的数据结构和函数;

树数据结构

Linux提供了一个通用构架,用于在内存中构建数据结构;这些结构描述了系统中可用的资源,使得内核代码能够管理和分配资源;其中,关键的数据结构是resource,定义如下:

/*
 * Resources are tree-like, allowing
 * nesting etc..
 */
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};
  • startend,指定了一个一般性的区域;
  • name,存储了一个字符串,以便给资源赋予一个有意义的名字;资源名称实际上与内核无关,但在以可读形式输出资源列表(proc文件系统中)时比较有用;
  • flags,用于更准确地描述资源及其当前状态;
  • parentsiblingchild,分别表示当前resource节点的父节点、兄弟节点、子节点;

resource节点是通过树结构来进行组织的;下图说明了parent、sibling、child在树型结构中的组织方式;

用于连接parent、sibling、child的规则如下:

- 每个子节点只有一个父节点;
- 一个父节点可以有任意数目的子节点;
- 同一个父节点的所有子节点,会连接到兄弟节点链表上;
- 尽管每个子节点都有一个指针指向父节点;但父节点只有一个指向子节点的指针,指向第一个子节点;其它子节点都通过兄弟节点链表访问;
- 指向父节点的指针也可以为NULL;这种情况下,说明已经没有更上层的节点了;

该层次结构用来管理I/O内存、I/O端口等资源;后面I/O内存、I/O端口的章节中,可以看到是如何使用该层次结构进行管理的;

请求和释放资源

为确保可靠地配置资源(无论何种类型),内核必须提供一种机制来分配和释放资源;资源一旦分配,则不能由任何其它驱动程序使用;

请求和释放资源,就是从资源树中进行添加和删除操作;

请求资源

内核提供了__request_resource函数,用于请求一个资源区域;该函数需要一系列参数,包括一个指向父节点的指针,所请求资源区域的起始地址和结束地址,该区域的名称;

它连续地扫描现存的资源,将新资源添加到正确的位置;如果所请求资源区域与已经分配区域有冲突,则分配失败;完成上述工作,需要遍历兄弟节点的链表;如果请求的资源区域是空闲的,则插入新的resource实例,完成资源请求;如果该区域不是空闲的,则分配失败;

它只扫描给定父节点的子节点,只会在一个层次上扫描兄弟节点链表;内核不会扫描更底层的子节点的链表;

如果资源无法配置,驱动程序自然知道该资源已经分配,因而目前是不可用的;

/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
    resource_size_t start = new->start;
    resource_size_t end = new->end;
    struct resource *tmp, **p;

    if (end < start)
        return root;
    if (start < root->start)
        return root;
    if (end > root->end)
        return root;
    p = &root->child;
    for (;;) {
        tmp = *p;
        if (!tmp || tmp->start > end) {
            new->sibling = tmp;
            *p = new;
            new->parent = root;
            return NULL;
        }
        p = &tmp->sibling;
        if (tmp->end < start)
            continue;
        return tmp;
    }
}

代码逻辑:

  1. 初始检查:
resource_size_t start = new->start;
resource_size_t end = new->end;

if (end < start)
    return root; // 错误情况:结束地址小于开始地址
if (start < root->start)
    return root; // 请求的开始地址小于根资源的开始地址
if (end > root->end)
    return root; // 请求的结束地址大于根资源的结束地址

  • 首先,获取新资源的开始和结束地址。
  • 检查这些地址的有效性。如果无效,返回根资源指针。
  1. 插入逻辑:
p = &root->child; // 指向当前节点的第一个子节点
for (;;) {
    tmp = *p; // 获取当前子节点
    if (!tmp || tmp->start > end) {
        new->sibling = tmp; // 将新资源作为当前子节点的兄弟
        *p = new; // 插入新资源
        new->parent = root; // 设置新资源的父节点
        return NULL; // 成功插入,返回 NULL
    }
    p = &tmp->sibling; // 继续查找下一个兄弟节点
    if (tmp->end < start)
        continue; // 如果当前节点结束地址小于请求的开始地址,继续查找
    return tmp; // 找到冲突的资源,返回
}

  1. 冲突检测与处理:
  • 代码通过一个循环遍历当前资源树中的子节点,判断 new 资源是否可以安全插入。

  • 如果找到的子节点 tmp 的起始地址大于 new 资源的结束地址,说明 new 可以插入。

  • 如果 tmp 的结束地址大于等于 new 的起始地址,说明发生了冲突,返回该冲突的资源 tmp

  • __request_resource 函数的作用是请求一个新的资源,并检查是否与现有资源发生冲突。如果没有冲突,则将新资源插入资源树中;如果有冲突,返回引发冲突的资源;

  • 该函数是设备管理和资源分配中的关键部分,确保每个设备请求的地址范围不会重叠;

I/O内存

设备通常会提供一组寄存器来控制设备、读写设备和获取设备状态,即控制寄存器、数据寄存器和状

态寄存器。这些寄存器可能位于I/O空间中,也可能位于内存空间中。当位于I/O空间时,通常被称为I/O端

口;当位于内存空间时,对应的内存空间被称为I/O内存。

I/O内存、I/O端口都使用上述都使用上述资源管理方式;

I/O内存不仅包括与扩展设备通信直接使用的内存区域,还包括系统中可用的物理内存和ROM存储器,以及包含在资源列表中的内存(可以使用proc文件系统中iomem文件,显示所有的I/O内存);

所有分配的I/O内存地址,都通过一棵资源树管理,树的根节点是全局内核变量iomem_resource;上图的输出中,每个缩紧表示一个子节点层次;具有相同的缩进层次的所有项是兄弟节点,会通过链表联系起来;

在使用I/O内存时,分配内存区域并不是所需的唯一操作;取决于总线系统和处理器类型,可能必须将扩展设备的地址空间映射到内核地址空间(虚拟地址空间)后,才能访问该设备(称之为软件I/O映射);这是通过ioremap内核函数适当设置系统页表而实现的,该函数的定义是体系结构相关的;同样地,还提供了特定于体系结构的iounmap函数来解除映射;

将一个物理地址映射到处理器的虚拟地址空间中,使得内核可以使用该地址;就设备驱动程序而言,这意味着扩展总线的地址空间映射到CPU的地址空间中,使得嫩个够用普通内存访问函数操作总线/设备;

设备地址空间映射到内核地址空间的过程

将扩展设备的地址空间映射到内核地址空间的过程通常涉及几个步骤,主要是通过内存映射(memory mapping)机制将设备的物理地址空间映射到内核可以访问的虚拟地址空间。以下是关键的过程:

1. 设备地址空间的分配

在系统启动或者设备插入时,设备会在其配置空间中注册自己的地址需求,例如某些内存地址空间或 I/O 端口。系统会根据设备的 Base Address Registers (BAR) 分配适当的物理地址,并将该地址配置到设备的 BAR 寄存器中。

2. 内核识别设备地址空间

在设备驱动初始化过程中,内核通过 PCI 或其他总线接口(如 USB、I2C)识别设备并获取该设备的资源信息。对于 PCI 设备,内核会读取设备配置空间中的 BAR 寄存器以获取分配给设备的物理地址范围。

3. 使用 ioremap() 函数映射物理地址

设备的地址空间通常是物理地址。为了在内核中访问这些地址,设备驱动程序会调用 ioremap() 函数,将设备的物理地址空间映射到内核的虚拟地址空间。

c


复制代码
void __iomem *mapped_addr = ioremap(device_phys_addr, size);
  • 参数:
    • device_phys_addr:设备的物理地址,通常通过总线接口获取。
    • size:需要映射的地址空间大小。
  • 返回值ioremap() 返回一个虚拟地址 mapped_addr,内核可以通过这个地址直接访问设备寄存器。
4. 访问设备地址空间

ioremap() 返回的虚拟地址允许内核访问设备的寄存器。访问时通常使用内核提供的 I/O 访问函数,如 readl()writel()iowrite8() 等来确保正确的读写行为。

c


复制代码
writel(value, mapped_addr + offset);  // 写入值
value = readl(mapped_addr + offset);  // 读取值
5. 取消映射(可选)

在驱动卸载或不再需要设备地址空间时,调用 iounmap() 函数取消映射:

c


复制代码
iounmap(mapped_addr);
6. 示例流程

设备驱动的初始化可能类似如下伪代码:

c


复制代码
int my_device_probe(struct pci_dev *pdev, const struct pci_device_id *ent) {
    // 1. 启用 PCI 设备
    pci_enable_device(pdev);

    // 2. 获取设备地址的 BAR
    phys_addr = pci_resource_start(pdev, BAR_INDEX);

    // 3. 映射到内核地址空间
    void __iomem *mapped_addr = ioremap(phys_addr, size);

    // 4. 使用映射的地址访问设备寄存器
    writel(value, mapped_addr + offset);

    // ... 其他初始化操作 ...

    return 0;
}

// 驱动卸载时取消映射
void my_device_remove(struct pci_dev *pdev) {
    iounmap(mapped_addr);
    pci_disable_device(pdev);
}
总结

这种映射机制通过将设备的物理地址映射到内核地址空间,让内核能够直接访问设备的寄存器,从而进行数据交换。

I/O端口

I/O端口是一种与设备和总线通信的流行方法,特别是在IA-32平台上;

kernel/resource.c中的ioport_resource充当资源树的根节点;proc文件系统中的ioports文件可以显示已经分配的端口地址;

struct resource ioport_resource = {
	.name	= "PCI IO",
	.start	= 0,
	.end	= IO_SPACE_LIMIT,
	.flags	= IORESOURCE_IO,
};
EXPORT_SYMBOL(ioport_resource);

总线系统

资源管理依赖总线系统完成设备的发现、资源分配、配置控制以及电源管理等任务。总线系统通过标准接口帮助资源管理系统与设备交互,实现设备的配置与控制,并确保资源的安全、有效使用。因此,总线系统是资源管理功能的基础,良好的总线管理和设备配置可以提升系统的稳定性和效率。

内核支持大量总线,我们主要讨论PCI总线和USB总线;

通用驱动程序模型

现代总线系统在布局和结构的细节上可能有所不同,但也有许多共通之处;内核使用一个通用驱动程序模型,所有总线共有的属性封装到可以用通用方法处理的数据结构中;

设备的表示

驱动程序模型采用一种特殊数据结构来表示几乎所有总线类型通用的设备属性;该结构直接嵌入到特定于总线的数据结构中,而不是通过指针引用;其定义如下:

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
	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. */
	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;
};
  • parent,指向父设备的指针。一般来说,一个设备的父设备是其连接的总线或控制器。
  • <font style="color:#0e0e0e;">p</font>,指向设备私有数据的指针。包含内核的设备模型核心所需的内部信息。
  • <font style="color:#0e0e0e;">kobj</font>,内核对象,用于设备的内核模型管理,可以使设备与 sysfs(文件系统)关联。
  • <font style="color:#0e0e0e;">init_name</font>,设备的初始名称。
  • <font style="color:#0e0e0e;">type</font>,设备类型,用于标识设备类别。
  • <font style="color:#0e0e0e;">mutex</font>,设备的互斥锁,用于同步对驱动程序的调用。
  • <font style="color:#0e0e0e;">bus</font>,设备所在的总线类型。
  • <font style="color:#0e0e0e;">driver</font>,指向已分配此设备的驱动程序。
  • <font style="color:#0e0e0e;">platform_data</font>,平台特定数据,通常用于嵌入式系统中描述特定板级设备的结构(如端口、引脚设置等)。
  • <font style="color:#0e0e0e;">driver_data</font>,驱动程序专用的数据,存储在设备中以便驱动程序使用。
  • <font style="color:#0e0e0e;">power</font><font style="color:#0e0e0e;">pm_domain</font>,用于电源管理,包括设备的电源状态及回调接口,用于处理挂起、恢复等系统电源事件。
  • <font style="color:#0e0e0e;">dma_mask</font><font style="color:#0e0e0e;">coherent_dma_mask</font>,用于 DMA(直接内存访问)操作的掩码设置。
  • <font style="color:#0e0e0e;">of_node</font><font style="color:#0e0e0e;">fwnode</font>,指向设备树节点和固件节点,用于设备树及平台固件信息关联。
  • <font style="color:#0e0e0e;">release</font>,回调函数,用于在设备所有引用被释放后清理资源。
  • <font style="color:#0e0e0e;">iommu_group</font><font style="color:#0e0e0e;">iommu_fwspec</font>,与 IOMMU(输入输出内存管理单元)相关的设备属性。
  • <font style="color:#0e0e0e;">offline_disabled</font><font style="color:#0e0e0e;">offline</font>,标识设备的在线或离线状态。

struct device 是设备模型的核心结构体,包含了设备的各种属性和与驱动、总线、设备树、DMA 等组件的关联。通过 struct device,内核能够统一管理不同类型的设备,实现设备的发现、初始化、挂载、访问、断开及释放等操作。

关联使用

  1. 与总线和驱动模型关联:通过 bus、driver 等成员字段,设备与总线和驱动程序相互关联;
  2. 与电源管理系统关联:电源管理相关成员(如 power 和 pm_domain)用于支持设备的节能和电源事件响应。
  3. 与 DMA 和 IOMMU:通过 dma_mask、iommu_group 等成员,设备支持直接内存访问和内存管理单元(IOMMU)。

struct device 作为 Linux 内核设备模型的核心结构,使内核可以统一、灵活地管理各种设备。

内核提供标准函数device_register,用于将一个新设备添加到内核的数据结构;

驱动程序的表示

通用驱动程序模型也为设备驱动程序单独设计了一种数据结构;

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;
};
  • <font style="color:#0e0e0e;">name</font>,驱动程序的名称。
  • <font style="color:#0e0e0e;">bus</font>,驱动程序所在的总线类型(struct bus_type),用于确定驱动程序可以管理的设备类型。
  • <font style="color:#0e0e0e;">owner</font>,指向模块的所有者,通常用于驱动程序模块管理,确保驱动在使用时不会被卸载。
  • <font style="color:#0e0e0e;">mod_name</font>,用于内核内置模块的名称。
  • <font style="color:#0e0e0e;">suppress_bind_attrs</font>,禁止通过 sysfs 绑定或解绑设备和驱动程序。
  • <font style="color:#0e0e0e;">probe_type</font>,探测类型,可以是同步或异步,用于决定驱动程序在设备发现时的初始化方式。
  • <font style="color:#0e0e0e;">of_match_table</font>,设备树匹配表,用于在设备树中寻找与驱动程序匹配的设备。
  • <font style="color:#0e0e0e;">acpi_match_table</font>,ACPI 匹配表,用于基于 ACPI 的设备自动匹配。
  • <font style="color:#0e0e0e;">probe</font>,用于在系统中查询特定设备是否存在,并尝试绑定驱动程序和设备。如果设备和驱动程序匹配,probe 函数会初始化设备。
  • <font style="color:#0e0e0e;">remove</font>,用于设备从系统中移除时解除驱动程序的绑定并清理资源。
  • <font style="color:#0e0e0e;">shutdown</font>,系统关机时调用,用于安全地停止设备操作。
  • <font style="color:#0e0e0e;">suspend </font><font style="color:#0e0e0e;">resume</font>,电源管理回调函数,用于将设备置于低功耗状态或从低功耗状态恢复。
  • <font style="color:#0e0e0e;">groups</font>,驱动程序核心自动创建的默认属性组。
  • <font style="color:#0e0e0e;">pm</font>,指向设备的电源管理操作结构(struct dev_pm_ops),包含与设备电源管理相关的回调。
  • <font style="color:#0e0e0e;">p</font>,驱动核心的私有数据,仅供驱动核心使用,其他模块无法访问。

功能

  • struct device_driver 提供了一个驱动程序注册到内核、处理特定类型设备的方法。
  • 通过 probe、remove 等回调函数,驱动程序可以在设备连接、移除时响应,完成设备初始化和清理。
  • 结合 bus、of_match_table 等字段,驱动程序可以与特定类型的设备自动匹配,从而实现自动化管理。

使用场景

struct device_driver 被广泛用于定义和实现各种设备驱动程序,使得内核可以自动化设备与驱动的匹配、管理和控制,包括通用的字符设备、块设备及网络设备等。

驱动程序使用内核的标准函数driver_rigister注册到系统;

总线的表示

通用驱动程序模型使用以下数据结构表示总线:

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	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);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

字段解析

  • <font style="color:#0e0e0e;">name</font>,总线的名称,用于标识此总线的唯一名称。
  • <font style="color:#0e0e0e;">dev_name</font>,设备名称的前缀,用于自动生成设备的名称(如 usb、pci 等)。
  • <font style="color:#0e0e0e;">dev_root</font>,指向此总线的根设备。通常,所有属于此总线的设备都会挂载在 dev_root 下。
  • <font style="color:#0e0e0e;">dev_attrs</font>,描述设备属性的结构体。此字段被标记为过时,应使用 dev_groups 代替。
  • <font style="color:#0e0e0e;">bus_groups</font>,用于表示总线的属性组,可以通过 sysfs 文件系统进行导出。
  • <font style="color:#0e0e0e;">dev_groups</font>,表示设备的属性组,用于将设备的属性导出到 sysfs 中,以便用户空间访问。
  • <font style="color:#0e0e0e;">drv_groups</font>,表示驱动程序的属性组,使驱动程序的属性可以在 sysfs 中暴露出来。

回调函数

  • <font style="color:#0e0e0e;">match</font>,匹配设备和驱动的函数。此函数根据设备和驱动的属性进行匹配,如果匹配成功,则返回非零值。
  • <font style="color:#0e0e0e;">uevent</font>,当设备或驱动在总线上注册时调用,用于生成 uevent,以便用户空间可以捕获设备的插拔事件。
  • <font style="color:#0e0e0e;">probe</font>,在驱动与设备成功匹配后调用,初始化设备并绑定到驱动程序。用于设备初始化操作。
  • <font style="color:#0e0e0e;">remove</font>,在设备被卸载时调用,通常执行设备清理操作。
  • <font style="color:#0e0e0e;">shutdown</font>,当系统关机或重启时调用,用于执行设备的关闭操作。
  • <font style="color:#0e0e0e;">online</font>**<font style="color:#0e0e0e;"> </font>**/ <font style="color:#0e0e0e;">offline</font>,用于设备上线和下线的操作。
  • <font style="color:#0e0e0e;">suspend</font> / <font style="color:#0e0e0e;">resume</font>,用于电源管理。suspend 负责将设备进入低功耗状态,resume 负责唤醒设备。
  • <font style="color:#0e0e0e;">num_vf</font>,获取设备的虚拟功能数量,主要用于支持 SR-IOV 技术的设备。

作用

struct bus_type 提供了标准接口,使得各种类型的总线可以在 Linux 内核中以统一的方式进行管理。通过注册总线、定义设备和驱动的回调函数,Linux 内核可以高效地管理设备的插拔、驱动程序加载、设备电源管理等操作。

注册过程

首先需要注册总线,之后进行设备和设备驱动的注册;

注册总线

在可以注册设备及其驱动之前,需要有总线;通过bus_register,向系统添加一个新总线;

/**
 * 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);
    ...
	priv->devices_kset = kset_create_and_add("devices", NULL,
						 &priv->subsys.kobj);


	priv->drivers_kset = kset_create_and_add("drivers", NULL,
						 &priv->subsys.kobj);
    ...
}
    
  • bus_register 函数用于在 Linux 内核中注册一个新的总线,并将总线的设备和驱动信息通过 ksetklist 结构与内核的设备模型框架连接起来。
  • 它创建了一个与总线相关的 subsys_private 结构,其中包含了总线的设备集合、驱动集合以及相关的锁和通知机制。
注册设备

注册设备包括两个步骤:

  1. 初始化设备对象 dev,准备设备的内部结构。

通常包括:

- 初始化设备的状态。
- 设定设备的各种默认值(例如设备的父设备、设备的总线类型等)。
- 设备的一些默认属性和初始化工作。

这一步并没有将设备添加到设备模型中,而只是准备好设备。

  1. 将设备添加到内核设备模型中,并完成实际的设备注册;

具体来说,这一步做了以下几件事:

- 将设备插入到总线系统或父设备的设备列表中。
- 创建设备的 sysfs 文件节点(如果设备支持 sysfs)。
- 如果设备需要与驱动程序进行绑定,内核会开始匹配驱动程序。
- 触发设备的 `uevent`,通知用户空间。

device_add 实际上是设备注册过程的关键步骤,负责将设备与内核设备模型整合。

int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}
注册设备驱动程序

通过driver_register函数将设备驱动程序注册到其对应的总线上,从而将设备、驱动程序和总线模型结合起来;

主要步骤如下:

  • 检查是否重复注册。
  • 添加到总线,关联设备和驱动。
  • 创建 sysfs 文件节点,使驱动信息可被用户空间访问。
  • 通知用户空间新设备或驱动的添加。

这使得设备驱动模型在内核和用户空间的管理和交互更加系统化。

int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

	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);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}

PCI总线

PCI是peripheral component interconnect的缩写,是英特尔公司开发的一种标准总线,它迅速在系

统组件和体系结构厂商中间确立了自身的地位,成为一种非常流行的总线。PCI 总线为高性能局部总线,主要解决外部设备之间以及外部设备与主机之间高速数据传输。在数字图形、图像等处理,以及高速实时数据采集与处理等对数据传输率要求高的应用中,采用PCI 总线进行数据传输。

内核对于PCI也有对应的数据结构:

struct pci_bus,PCI总线;

struct pci_dev,PCI设备;

struct pci_driver,PCI设备驱动程序;

内核通过这几个数据结构及通用驱动模型的数据结构,对PCI进行管理;

USB总线

参考文献

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer;
  2. Linux设备驱动开发详解,宋宝华;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值