resource 说明
数据结构
struct resource 的定义如下:
struct resource {
resource_size_t start; /* 描述设备实体在 CPU 总线上的线性起始物理地址 */
resource_size_t end; /* 描述设备实体在 CPU 总线上的线性结尾物理地址 */
const char *name; /* 描述这个设备实体的名称,这个名字开发人员可以随意起,但最好贴切 */
unsigned long flags; /* 描述这个设备实体的一些共性和特性的标志位 */
struct resource *parent, *sibling, *child;
};
与 resource 相关的另一个数据结构 struct resource_list:
struct resource_list {
struct resource_list *next;
struct resource *res;
struct pci_dev *dev;
};
flags
于资源,在 Linux 中有如下定义:
/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
#define IORESOURCE_READONLY 0x00004000
#define IORESOURCE_CACHEABLE 0x00008000
#define IORESOURCE_RANGELENGTH 0x00010000
#define IORESOURCE_SHADOWABLE 0x00020000
#define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */
#define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */
#define IORESOURCE_MEM_64 0x00100000
#define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */
#define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */
#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000 /* No address assigned yet */
#define IORESOURCE_AUTO 0x40000000
#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */
本篇我们主要来分析前三种资源,即 IO、MEM、REG。
几乎每一种外设都是通过读写设备上的寄存器来进行的,外设的寄存器通常被连续地编址。根据CPU体系结构的不同,CPU 对 IO 端口的编址方式有两种:
-
I/O映射方式(I/O-mapped)
典型地,如 x86 处理器为外设专门实现了一个单独的地址空间,称为"I/O 地址空间"或者"I/O 端口空间",CPU 通过专门的 I/O 指令(如x86的 IN 和 OUT 指令)来访问这一空间中的地址单元。
IO 地址空间,这个空间从 kernel 编程上来看,只能通过专门的接口函数才能访问。硬件层面上,CPU 需要用特殊指令才能访问或需要用特殊访问方式才能访问,不能直接用指针来寻址。
在嵌入式中一般使用 ARM 架构,而 ARM 架构采用的是 IO 统一编址,所以没有 IO address space。
-
内存映射方式(Memory-mapped)
RISC 指令系统的 CPU(如 MIPS ARM PowerPC等)通常只实现一个物理地址空间,像这种情况,外设的 I/O 端口的物理地址就被映射到内存地址空间中,外设I/O端口成为内存的一部分。此时,CPU 可以象访问一个内存单元那样访问外设 I/O 端口,而不需要设立专门的外设 I/O指 令。
若果搜索内核代码,会发现,只有极少量的寄存器是通过 IORESOURCE_REG 来表示的,绝大多数寄存器都是通过 IORESOURCE_IO 或IORESOURCE_MEM 来表示的。
我们的 ARM 平台对寄存的访问和内存一样,所以通常我们 ARM 中定义寄存器资源都是采用 IORESOURCE_MEM 来表示。
start、end 和 name
由上面分析,我们可知在 Linux 中,一个独立的挂接在 CPU 总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身。
Linux 是怎么管理所有的这些外部"物理地址范围段",进而给用户和 Linux 自身一个比较好的观察 4G 总线上挂接的一个个设备实体的简洁、统一级联视图的呢?
Linux 采用 struct resource 结构体来描述一个挂接在 CPU 总线上的设备实体(32位 CPU 的总线地址范围是0~4G):
- start: 这段资源的起始
- end:这段资源的结束
- name:这个资源的名字,方便用户查看
- flags:标记属于那种资源
例如:
static struct resource xxxx = {
/* addr */
.start = 0x04014000,
.end = 0x04014003,
.flags = IORESOURCE_MEM,
}
一般来说,在系统运行时,外设的I/O资源的物理地址是已知的,有硬件决定,查看手册即可知道。
但是 CPU 通常并没有为这些已知的外设 I/O 的物理地址分配虚拟地址,所以驱动程序并不能直接通过物理地址来访问 I/O 的地址资源,而必须将它们映射到核心虚拟地址空间(通过页表),然后才能根据映射所得到的核心虚拟地址范围,通过访问指令来访问这些 I/O 内存资源。
Linux 中在 io.h 头文件中申明了函数
ioremap()
,用来将 I/O 内存资源的物理地址映射到核心的虚拟地址空间。
一般我们,使用 I/O 内存首先要申请,然后才能映射,使用 I/O 端口首先要申请,或者叫请求,对 于I/O 端口的请求意思是让内核知道你要访问这个端口,这样内核知道了以后它就不会再让别人也访问这个端口了,不然两个用户同时访问一个硬件可能会有问题的。
因此,只需要了解一个设备实体的以上4项,Linux 就能够知晓这个挂接在 CPU 总线的上的设备实体的基本使用情况,也就是 [resource->start, resource->end] 这段物理地址现在是空闲着呢,还是被什么设备占用着。
Linux 会坚决避免将一个已经被一个设备实体使用的总线物理地址区间段 [resource->start, resource->end],再分配给另一个后来的也需要这个区间段或者区间段内部分地址的设备实体。进而避免设备之间出现对同一总线物理地址段的重复引用,而造成对唯一物理地址的设备实体二义性。
以上的4个属性仅仅用来描述一个设备实体自身,或者是设备实体可以用来自治的单元,但是这不是 Linux 所想的。
Linux 需要管理4G物理总线的所有空间,所以挂接到总线上的形形色色的各种设备实体,就需要链在一起,因此 resource 结构体提供了另外3个成员:指针 parent、sibling 和 child,分别为指向父亲、兄弟和子资源的指针。
资源树
parent、sibling 和 child 三根指针就构成了类似于下图所示的资源树结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3JrCgEcD-1658925942026)(images\Linux内核struct resource结构体\resource层次结构.png)]
内核中有两棵 resource 树,一棵是 iomem_resource, 另一棵是 ioport_resource,分别代表着两类不同性质的地址资源。两棵树的根也都是 resource 数据结构,不过这两个数据结构描述的并不是用于具体操作对象的地址资源,而是概念上的整个地址空间。
将主板上的 ROM 空间纳入 iomem_resource 树中;系统固有的 I/O 类资源则纳入 ioport_resource 树。
/usr/src/linux/kernel/resource.c
struct resource ioport_resource = {
.name = "PCI IO",
.start -= 0,
.end = IO_SPACE_LIMIT,
.flags -= IORESOURCE_IO,
};
struct resource iomem_resource = {
.name = "PCI mem",
.start -= 0,
.end = -1,
.flags -= IORESOURCE_MEM,
};
以平台总线中添加一段MEM资源为例,分析一下,资源的如何加入资源树。
当应用程序调用 platform_device_register 时,可以将设备注册进内核:
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
}
-
device_initialize 主要是对 dev 进初始化:
void device_initialize(struct device *dev) { /* 初始化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); }
-
arch_setup_pdev_archdata 调用框架相关的实现:
void __weak arch_setup_pdev_archdata(struct platform_device *pdev) { /* 预留给特殊架构的 */ }
-
重点在 platform_device_add 函数,下面我们对它进行详细分析。
platform_device_add 的定义如下:
int platform_device_add(struct platform_device *pdev)
{
int i, ret;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; /* 增加总线的父设备为平台设备 */
pdev->dev.bus = &platform_bus_type; /* 设备挂接在平台总线 */
switch (pdev->id) {
default: /* 自己设置设备标号 */
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE: /* -1 表示不需要设备标号 */
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO: /* 由总线分配设备标号 */
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}
/* 对该设备的资源插入资源树,如果已经有设备插入则会插入失败 */
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev); /* 如果资源没设置名字,则设置和设备名一样 */
p = r->parent;
if (!p) {
/* 如果没设置资源的父节点,
* 则检查是否是 IORESOURCE_MEM 或 IORESOURCE_IO 资源,
* 如果是,则把他们插入到这个资源所在的资源树中
*/
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource; /* iomem 资源树信息 */
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource; /* ioport 资源树信息 */
}
/* 这里p必须存在,即如果自己没设置,默认也只添加IO和MEM资源到资源树中 */
if (p && insert_resource(p, r)) {
dev_err(&pdev->dev, "failed to claim resource %d\n", i);
ret = -EBUSY;
goto failed;
}
}
/* ...省略... */
}
也就是说最终会调用 insert_resource 将资源添加到资源树上。
原文与参考
文章来源:http://gliethttp.cublog.cn
Linux下I/O资源管理(原理)_Linux教程_Linux公社-Linux系统门户网站 (linuxidc.com)