总线无关设备访问¶
- 作者:
Matthew Wilcox
- 作者:
Alan Cox
简介¶
Linux 提供了一个 API,它抽象了跨所有总线和设备执行 IO 的操作,允许设备驱动程序独立于总线类型编写。
内存映射 IO¶
获取对设备的访问权限¶
最广泛支持的 IO 形式是内存映射 IO。也就是说,CPU 地址空间的一部分被解释为不是对内存的访问,而是对设备的访问。一些架构定义设备位于固定地址,但大多数架构都有一些发现设备的方法。PCI 总线扫描就是一个很好的例子。本文档不讨论如何接收这样的地址,而是假设你从一个地址开始。物理地址的类型为 unsigned long。
不应直接使用此地址。相反,为了获得适合传递给下面描述的访问器函数的地址,你应该调用 ioremap()
。将返回一个适合访问设备的地址。
在你完成使用设备后(例如,在你的模块的退出例程中),调用 iounmap() 以便将地址空间返回给内核。大多数架构每次调用 ioremap()
时都会分配新的地址空间,并且它们可能会耗尽,除非你调用 iounmap()。
访问设备¶
驱动程序最常用的接口部分是读取和写入设备上的内存映射寄存器。Linux 提供了读取和写入 8 位、16 位、32 位和 64 位数量的接口。由于历史原因,这些被称为字节、字、长字和四字访问。支持读取和写入访问;目前没有预取支持。
这些函数名为 readb()、readw()、readl()、readq()、readb_relaxed()、readw_relaxed()、readl_relaxed()、readq_relaxed()、writeb()、writew()、writel() 和 writeq()。
一些设备(例如帧缓冲区)希望一次使用大于 8 字节的传输。对于这些设备,提供了 memcpy_toio()、memcpy_fromio() 和 memset_io() 函数。不要在 IO 地址上使用 memset 或 memcpy;它们不能保证按顺序复制数据。
读取和写入函数被定义为有序的。也就是说,不允许编译器重新排序 I/O 序列。当可以编译器优化排序时,你可以使用 __readb() 及其朋友来指示宽松的排序。谨慎使用它。
虽然基本函数被定义为彼此同步且彼此有序,但设备所在的总线本身可能具有异步性。特别是,许多作者因 PCI 总线写入是异步发布的事实而受到困扰。驱动程序作者必须从同一设备发出读取,以确保写入发生在作者关心的特定情况下。这种属性不能在 API 中对驱动程序编写者隐藏。在某些情况下,用于刷新设备的读取可能会失败(例如,如果卡正在重置)。在这种情况下,应该从配置空间完成读取,如果卡不响应,配置空间保证软失败。
以下是一个示例,说明当驱动程序希望确保在继续执行之前写入的效果可见时,如何刷新对设备的写入
static inline void
qla1280_disable_intrs(struct scsi_qla_host *ha)
{
struct device_reg *reg;
reg = ha->iobase;
/* disable risc and host interrupts */
WRT_REG_WORD(®->ictrl, 0);
/*
* The following read will ensure that the above write
* has been received by the device before we return from this
* function.
*/
RD_REG_WORD(®->ictrl);
ha->flags.ints_enabled = 0;
}
PCI 排序规则还保证 PIO 读取响应在来自该总线的任何未完成的 DMA 写入之后到达,因为对于某些设备,readb() 调用的结果可能会向驱动程序发出 DMA 事务已完成的信号。然而,在许多情况下,驱动程序可能希望指示下一个 readb() 调用与设备执行的任何先前的 DMA 写入无关。驱动程序可以在这些情况下使用 readb_relaxed(),尽管只有某些平台会遵守宽松的语义。使用宽松的读取函数将在支持它的平台上提供显着的性能优势。qla2xxx 驱动程序提供了如何使用 readX_relaxed() 的示例。在许多情况下,驱动程序的大部分 readX() 调用可以安全地转换为 readX_relaxed() 调用,因为只有少数调用会指示或依赖 DMA 完成。
端口空间访问¶
端口空间说明¶
另一种常用支持的 IO 形式是端口空间。这是一系列与普通内存地址空间分开的地址。对这些地址的访问通常不如对内存映射地址的访问快,而且它也可能具有较小的地址空间。
与内存映射 IO 不同,访问端口空间不需要任何准备。
访问端口空间¶
通过一组允许 8 位、16 位和 32 位访问的函数来提供对该空间的访问;也称为字节、字和长字。这些函数是 inb()、inw()、inl()、outb()、outw() 和 outl()。
为这些函数提供了一些变体。一些设备要求降低对其端口的访问速度。此功能通过在函数末尾附加一个 _p
来提供。还有 memcpy 的等效项。ins() 和 outs() 函数将字节、字或长字复制到给定的端口。
__iomem 指针令牌¶
MMIO 地址的数据类型是 __iomem
限定指针,例如 void __iomem *reg
。在大多数架构上,它是一个指向虚拟内存地址的常规指针,可以偏移或解引用,但在可移植代码中,它必须仅从显式操作 __iomem
令牌的函数传递到这些函数,特别是 ioremap()
和 readl()/writel() 函数。“稀疏”语义代码检查器可用于验证是否正确完成此操作。
虽然在大多数架构上,ioremap()
为指向物理 MMIO 地址的未缓存虚拟地址创建页表条目,但某些架构需要用于 MMIO 的特殊指令,而 __iomem
指针仅编码物理地址或由 readl()/writel() 解释的可偏移的 cookie。
I/O 访问函数之间的差异¶
readq()、readl()、readw()、readb()、writeq()、writel()、writew()、writeb()
这些是最通用的访问器,提供针对其他 MMIO 访问和 DMA 访问的序列化,以及用于访问小端 PCI 设备和片上外围设备的固定字节序。可移植设备驱动程序通常应将这些用于对
__iomem
指针的任何访问。请注意,发布的写入与自旋锁并非严格排序,请参阅 对内存映射地址的 I/O 写入排序。
readq_relaxed()、readl_relaxed()、readw_relaxed()、readb_relaxed()、writeq_relaxed()、writel_relaxed()、writew_relaxed()、writeb_relaxed()
在需要昂贵的屏障来针对 DMA 进行序列化的架构上,这些 MMIO 访问器的“宽松”版本仅针对彼此进行序列化,但包含较便宜的屏障操作。设备驱动程序可能会在特别注重性能的快速路径中使用这些,并带有评论,解释为什么在特定位置使用这些是安全的,而无需额外的屏障。
有关非宽松版本和宽松版本的精确排序保证的更详细讨论,请参阅 memory-barriers.txt。
ioread64()、ioread32()、ioread16()、ioread8()、iowrite64()、iowrite32()、iowrite16()、iowrite8()
这些函数是普通 readl()/writel() 函数的替代品,它们的行为几乎相同,但它们也可以操作使用
pci_iomap()
或 ioport_map() 映射 PCI I/O 空间返回的__iomem
令牌。在需要特殊指令进行 I/O 端口访问的体系结构上,这会增加 lib/iomap.c 中实现的间接函数调用的少量开销,而在其他体系结构上,这些只是别名。
ioread64be(), ioread32be(), ioread16be() iowrite64be(), iowrite32be(), iowrite16be()
它们的行为与 ioread32()/iowrite32() 系列相同,但字节顺序相反,用于访问具有大端 MMIO 寄存器的设备。可以在大端或小端寄存器上操作的设备驱动程序可能需要实现一个自定义的包装函数,该函数根据找到的设备选择其中一个。
注意:在某些体系结构上,普通的 readl()/writel() 函数传统上假定设备的字节序与 CPU 相同,而在运行大端内核时,在 PCI 总线上使用硬件字节反转。以这种方式使用 readl()/writel() 的驱动程序通常不具备可移植性,但往往仅限于特定的 SoC。
hi_lo_readq(), lo_hi_readq(), hi_lo_readq_relaxed(), lo_hi_readq_relaxed(), ioread64_lo_hi(), ioread64_hi_lo(), ioread64be_lo_hi(), ioread64be_hi_lo(), hi_lo_writeq(), lo_hi_writeq(), hi_lo_writeq_relaxed(), lo_hi_writeq_relaxed(), iowrite64_lo_hi(), iowrite64_hi_lo(), iowrite64be_lo_hi(), iowrite64be_hi_lo()
某些设备驱动程序具有 64 位寄存器,这些寄存器在 32 位体系结构上无法原子访问,但允许进行两次连续的 32 位访问。由于必须首先访问两个半部分中的哪一个取决于特定设备,因此为每个 64 位访问器组合提供了辅助函数,这些访问器具有低/高或高/低字排序。设备驱动程序必须包含 <linux/io-64-nonatomic-lo-hi.h> 或 <linux/io-64-nonatomic-hi-lo.h> 才能获得函数定义以及辅助函数,这些辅助函数将架构上不提供 64 位原生访问的普通 readq()/writeq() 重定向到它们。
__raw_readq(), __raw_readl(), __raw_readw(), __raw_readb(), __raw_writeq(), __raw_writel(), __raw_writew(), __raw_writeb()
这些是低级 MMIO 访问器,没有屏障或字节顺序更改以及特定于体系结构的行为。访问通常是原子的,因为四字节的 __raw_readl() 不会拆分为单独的字节加载,但多个连续的访问可以在总线上组合。在可移植代码中,仅使用它们访问设备总线后面的内存是安全的,而不是 MMIO 寄存器,因为与其他 MMIO 访问甚至自旋锁没有排序保证。字节顺序通常与普通内存相同,因此与其它函数不同,这些函数可用于在内核内存和设备内存之间复制数据。
inl(), inw(), inb(), outl(), outw(), outb()
PCI I/O 端口资源传统上需要单独的辅助函数,因为它们是使用 x86 体系结构上的特殊指令实现的。在大多数其他体系结构上,这些在内部映射到 readl()/writel() 样式的访问器,通常指向虚拟内存中的固定区域。地址不是
__iomem
指针,而是 32 位整数令牌,用于标识端口号。PCI 要求 I/O 端口访问为非发布的,这意味着 outb() 必须在执行以下代码之前完成,而普通的 writeb() 可能仍在进行中。在正确实现此功能的体系结构上,I/O 端口访问因此针对自旋锁进行排序。然而,许多非 x86 PCI 主桥实现和 CPU 体系结构未能实现 PCI 上的非发布 I/O 空间,因此它们最终可能会在此类硬件上发布。在某些体系结构中,I/O 端口号空间与
__iomem
指针具有 1:1 映射,但这不建议使用,并且设备驱动程序不应依赖此功能来实现可移植性。同样,PCI 基地址寄存器中描述的 I/O 端口号可能与设备驱动程序看到的端口号不对应。可移植驱动程序需要读取内核提供的资源的端口号。没有直接的 64 位 I/O 端口访问器,但可以使用
pci_iomap()
与 ioread64/iowrite64 结合使用。
inl_p(), inw_p(), inb_p(), outl_p(), outw_p(), outb_p()
在需要特定计时的 ISA 设备上,I/O 访问器的 _p 版本会增加少量延迟。在没有 ISA 总线的体系结构上,这些是普通 inb/outb 辅助函数的别名。
readsq, readsl, readsw, readsb writesq, writesl, writesw, writesb ioread64_rep, ioread32_rep, ioread16_rep, ioread8_rep iowrite64_rep, iowrite32_rep, iowrite16_rep, iowrite8_rep insl, insw, insb, outsl, outsw, outsb
这些是多次访问同一地址的辅助函数,通常用于在内核内存字节流和 FIFO 缓冲区之间复制数据。与普通的 MMIO 访问器不同,这些函数不会在大端内核上执行字节交换,因此 FIFO 寄存器中的第一个字节对应于内存缓冲区中的第一个字节,而与体系结构无关。
设备内存映射模式¶
某些体系结构支持映射设备内存的多种模式。ioremap_*() 变体围绕这些特定于体系结构的模式提供了一个通用抽象,具有一组共享的语义。
ioremap()
是最常见的映射类型,适用于典型的设备内存(例如,I/O 寄存器)。如果体系结构支持,其他模式可以提供较弱或较强的保证。从最常见到最不常见,它们如下所示
ioremap()¶
默认模式,适用于大多数内存映射设备,例如控制寄存器。使用 ioremap()
映射的内存具有以下特性
未缓存 - 绕过 CPU 端缓存,所有读取和写入都由设备直接处理
无推测操作 - 除非在已提交的程序流中已到达执行该操作的指令,否则 CPU 可能不会对此内存发出读取或写入。
无重新排序 - CPU 不得重新排序对此内存映射的访问。在某些体系结构上,这依赖于 readl_relaxed()/writel_relaxed() 中的屏障。
无重复 - CPU 不得为单个程序指令发出多次读取或写入。
无写入合并 - 每个 I/O 操作都会导致向设备发出一个离散的读取或写入,并且多个写入不会合并为更大的写入。使用 __raw I/O 访问器或指针解引用时,可能强制执行也可能不强制执行此操作。
不可执行 - CPU 不允许从此内存推测指令执行(这也许是不言而喻的,但您也不允许跳转到设备内存中)。
在许多平台和总线(例如,PCI)上,通过 ioremap()
映射发出的写入是发布的,这意味着 CPU 不会等待写入实际到达目标设备才完成写入指令。
在许多平台上,I/O 访问必须与访问大小对齐;否则将导致异常或不可预测的结果。
ioremap_wc()¶
将 I/O 内存映射为具有写入合并的普通内存。与 ioremap()
不同,
CPU 可以推测性地发出程序实际上没有执行的设备读取,并且可以选择基本上读取它想要的任何内容。
只要结果从程序的角度来看是一致的,CPU 可以重新排序操作。
CPU 可能会多次写入到同一位置,即使程序发出单个写入。
CPU 可以将多个写入合并为单个更大的写入。
此模式通常用于视频帧缓冲区,在其中可以提高写入性能。它也可以用于设备中的其他内存块(例如,缓冲区或共享内存),但必须小心,因为在没有显式屏障的情况下,无法保证访问与普通 ioremap()
MMIO 寄存器访问的顺序。
在 PCI 总线上,通常可以安全地在标记为 IORESOURCE_PREFETCH
的 MMIO 区域上使用 ioremap_wc(),但它可能不能在没有该标志的区域上使用。对于片上设备,没有相应的标志,但驱动程序可以在已知安全的设备上使用 ioremap_wc()。
ioremap_wt()¶
将 I/O 内存映射为具有写透缓存的普通内存。与 ioremap_wc() 类似,但也
CPU 可以缓存发往设备的写入和从设备的读取,并从该缓存提供读取。
此模式有时用于视频帧缓冲区,在这些缓冲区中,驱动程序仍然希望写入及时到达设备(而不是卡在 CPU 缓存中),但为了效率,可以从缓存中提供读取。但是,它现在很少有用,因为帧缓冲区驱动程序通常只执行写入,对于写入,ioremap_wc() 更有效(因为它不会不必要地破坏缓存)。大多数驱动程序不应使用它。
ioremap_np()¶
与 ioremap()
类似,但显式请求非发布的写入语义。在某些体系结构和总线上,ioremap()
映射具有发布的写入语义,这意味着从 CPU 的角度来看,写入可能会“完成”,而写入的数据实际上并未到达目标设备。写入仍然与来自同一设备的写入和读取排序,但由于发布的写入语义,与其他设备不是这种情况。ioremap_np() 显式请求非发布语义,这意味着写入指令在设备接收到(并且在某种程度上已确认)写入数据之前不会出现完成。
此映射模式主要存在于为具有总线结构的平台提供服务,这些平台需要此特定映射模式才能正常工作。这些平台为需要 ioremap_np() 语义的资源设置 IORESOURCE_MEM_NONPOSTED
标志,并且可移植驱动程序应使用一个抽象,该抽象在适当的地方自动选择它(请参见下面的“更高级别的 ioremap 抽象”部分)。
裸 ioremap_np() 仅在某些体系结构上可用;在其他体系结构上,它始终返回 NULL。驱动程序通常不应使用它,除非它们是特定于平台的或它们从受支持的非发布写入中获益,并且可以在其他情况下回退到 ioremap()
。确保已发布的写入完成的通常方法是在写入后执行虚拟读取,如 访问设备 中所述,这在所有平台上都适用于 ioremap()
。
对于 PCI 驱动程序,绝不应该使用 ioremap_np()。即使在其他情况下实现 ioremap_np() 的架构上,PCI 内存空间写入也总是会发生后置写入。为 PCI BAR 使用 ioremap_np() 最好的结果是获得后置写入语义,最坏的结果是完全崩溃。
请注意,非后置写入语义与 CPU 端的顺序保证无关。CPU 仍然可以选择在非后置写入指令退出之前发出其他读取或写入。有关 CPU 端详细信息,请参阅前面关于 MMIO 访问函数的部分。
ioremap_uc()¶
ioremap_uc() 仅在具有 PAT 扩展的旧 x86-32 系统上,以及 ia64 上具有稍微非常规的 ioremap()
行为时才有意义,在其他所有地方,ioremap_uc() 默认返回 NULL。
可移植驱动程序应避免使用 ioremap_uc(),而应使用 ioremap()
。
ioremap_cache()¶
ioremap_cache() 有效地将 I/O 内存映射为普通 RAM。可以使用 CPU 回写缓存,并且 CPU 可以自由地将设备视为一块 RAM。对于具有任何副作用或读取时未返回先前写入的数据的设备内存,绝不应使用此功能。
它也不应用于实际 RAM,因为返回的指针是 __iomem
令牌。 memremap() 可用于将线性内核内存区域之外的普通 RAM 映射到常规指针。
可移植驱动程序应避免使用 ioremap_cache()。
架构示例¶
以下是在 ARM64 架构上上述模式如何映射到内存属性设置
API |
内存区域类型和可缓存性 |
ioremap_np() |
Device-nGnRnE |
Device-nGnRE |
|
ioremap_uc() |
(未实现) |
ioremap_wc() |
Normal-Non Cacheable(普通-不可缓存) |
ioremap_wt() |
(未实现;回退到 ioremap) |
ioremap_cache() |
Normal-Write-Back Cacheable(普通-回写可缓存) |
更高级别的 ioremap 抽象¶
鼓励驱动程序使用更高级别的 API,而不是使用上述原始 ioremap()
模式。 这些 API 可以实现特定于平台的逻辑,以在任何给定总线上自动选择适当的 ioremap 模式,从而使与平台无关的驱动程序可以在这些平台上工作而无需任何特殊情况。 在撰写本文时,以下 ioremap()
包装器具有此类逻辑
devm_ioremap_resource()
如果 struct resource 上设置了
IORESOURCE_MEM_NONPOSTED
标志,则可以根据平台要求自动选择 ioremap_np() 而不是ioremap()
。使用 devres 在驱动程序 probe() 函数失败或设备与其驱动程序解除绑定时自动取消映射资源。
为需要在特定总线上进行非后置写入的平台自动设置
IORESOURCE_MEM_NONPOSTED
标志(请参阅 nonposted-mmio 和 posted-mmio 设备树属性)。
映射设备树中
reg
属性中描述的资源,执行所有必需的转换。如上所述,根据平台要求自动选择 ioremap_np()。
pci_ioremap_bar(),pci_ioremap_wc_bar()
映射 PCI 基地址中描述的资源,而无需先提取物理地址。
类似于 pci_ioremap_bar()/pci_ioremap_bar(),但与 ioread32()/iowrite32() 和类似的访问器一起使用时也适用于 I/O 空间
类似于
pci_iomap()
,但是使用 devres 在驱动程序 probe() 函数失败或设备与其驱动程序解除绑定时自动取消映射资源
在某些对 I/O 内存映射有更严格规则的平台上,不使用这些包装器可能会使驱动程序无法使用。
概括对系统和 I/O 内存的访问¶
访问内存区域时,根据其位置,用户可能必须使用 I/O 操作或内存加载/存储操作来访问它。例如,复制到系统内存可以使用 memcpy()
完成,复制到 I/O 内存将使用 memcpy_toio() 完成。
void *vaddr = ...; // pointer to system memory
memcpy(vaddr, src, len);
void *vaddr_iomem = ...; // pointer to I/O memory
memcpy_toio(vaddr_iomem, src, len);
此类指针的用户可能没有关于该区域映射的信息,或者可能希望使用单个代码路径来处理该缓冲区上的操作,而无论它位于系统内存还是 IO 内存中。类型 struct iosys_map
及其帮助程序对此进行了抽象,因此可以将缓冲区传递给其他驱动程序,或者在同一个驱动程序中为分配、读取和写入操作设置单独的职责。
打开代码访问 struct iosys_map
被认为是不良的风格。 不要直接访问其字段,而应使用提供的帮助函数之一,或实现自己的函数。 例如,struct iosys_map
的实例可以使用 IOSYS_MAP_INIT_VADDR()
静态初始化,或使用 iosys_map_set_vaddr()
在运行时初始化。 这些帮助程序将在系统内存中设置地址。
struct iosys_map map = IOSYS_MAP_INIT_VADDR(0xdeadbeaf);
iosys_map_set_vaddr(&map, 0xdeadbeaf);
要在 I/O 内存中设置地址,请使用 IOSYS_MAP_INIT_VADDR_IOMEM()
或 iosys_map_set_vaddr_iomem()
。
struct iosys_map map = IOSYS_MAP_INIT_VADDR_IOMEM(0xdeadbeaf);
iosys_map_set_vaddr_iomem(&map, 0xdeadbeaf);
struct iosys_map
的实例不必清理,但可以使用 iosys_map_clear()
将其清除为 NULL。 清除的映射始终引用系统内存。
iosys_map_clear(&map);
使用 iosys_map_is_set()
或 iosys_map_is_null()
测试映射是否有效。
if (iosys_map_is_set(&map) != iosys_map_is_null(&map))
// always true
可以使用 iosys_map_is_equal()
比较 struct iosys_map
的实例是否相等。 指向不同内存空间(系统或 I/O)的映射永远不相等。 即使两个空间位于同一地址空间中、两个映射都包含相同的地址值或两个映射都引用 NULL,也是如此。
struct iosys_map sys_map; // refers to system memory
struct iosys_map io_map; // refers to I/O memory
if (iosys_map_is_equal(&sys_map, &io_map))
// always false
可以使用已设置的 struct iosys_map
实例来访问或操作缓冲内存。 根据内存的位置,提供的帮助程序将选择正确的操作。 可以使用 iosys_map_memcpy_to()
将数据复制到内存中。可以使用 iosys_map_incr()
操作地址。
const void *src = ...; // source buffer
size_t len = ...; // length of src
iosys_map_memcpy_to(&map, src, len);
iosys_map_incr(&map, len); // go to first byte after the memcpy
-
struct iosys_map¶
指向 IO/系统内存的指针
定义:
struct iosys_map {
union {
void __iomem *vaddr_iomem;
void *vaddr;
};
bool is_iomem;
};
成员
{unnamed_union}
匿名
vaddr_iomem
如果缓冲区位于 I/O 内存中,则为缓冲区的地址
vaddr
如果缓冲区位于系统内存中,则为缓冲区的地址
is_iomem
如果缓冲区位于 I/O 内存中,则为 True,否则为 False。
-
IOSYS_MAP_INIT_VADDR¶
IOSYS_MAP_INIT_VADDR (vaddr_)
将
struct iosys_map
初始化为系统内存中的地址
参数
vaddr_
一个系统内存地址
-
IOSYS_MAP_INIT_VADDR_IOMEM¶
IOSYS_MAP_INIT_VADDR_IOMEM (vaddr_iomem_)
将
struct iosys_map
初始化为 I/O 内存中的地址
参数
vaddr_iomem_
一个 I/O 内存地址
-
IOSYS_MAP_INIT_OFFSET¶
IOSYS_MAP_INIT_OFFSET (map_, offset_)
从另一个 iosys_map 初始化
struct iosys_map
参数
map_
要从中复制的 dma-buf 映射结构
offset_
要添加到另一个映射的偏移量
描述
根据另一个作为参数传递的结构,初始化一个新的 iosys_map 结构。 它对结构进行浅拷贝,因此可以更新后备存储而不会更改原始映射的指向位置。 它等效于执行
iosys_map map = other_map;
iosys_map_incr(&map, &offset);
用法示例
void foo(struct device *dev, struct iosys_map *base_map)
{
...
struct iosys_map map = IOSYS_MAP_INIT_OFFSET(base_map, FIELD_OFFSET);
...
}
使用初始化器而不是像上面那样使用 iosys_map_incr()
递增偏移量的优点是,新的映射在其作用域内始终指向缓冲区的正确位置。这降低了更新缓冲区错误部分且没有编译器警告的风险。如果忘记了对 IOSYS_MAP_INIT_OFFSET()
的赋值,编译器可以警告使用了未初始化的变量。
参数
struct iosys_map *map
iosys_map 结构
void *vaddr
一个系统内存地址
描述
设置地址并清除 I/O 内存标志。
-
void iosys_map_set_vaddr_iomem(struct iosys_map *map, void __iomem *vaddr_iomem)¶
将 iosys 映射结构设置为 I/O 内存中的地址
参数
struct iosys_map *map
iosys_map 结构
void __iomem *vaddr_iomem
一个 I/O 内存地址
描述
设置地址和 I/O 内存标志。
-
bool iosys_map_is_equal(const struct iosys_map *lhs, const struct iosys_map *rhs)¶
比较两个 iosys 映射结构的相等性
参数
const struct iosys_map *lhs
iosys_map 结构
const struct iosys_map *rhs
用于比较的 iosys_map 结构
描述
如果两个 iosys 映射结构都指向同一类型的内存以及该内存中的同一地址,则它们相等。
返回
如果两个结构相等,则为 True,否则为 false。
参数
const struct iosys_map *map
iosys_map 结构
描述
根据 struct iosys_map
.is_iomem 的状态,测试映射是否为 NULL。
返回
如果映射为 NULL,则为 True,否则为 false。
参数
const struct iosys_map *map
iosys_map 结构
描述
根据 struct iosys_map
.is_iomem 的状态,测试是否已设置映射。
返回
如果已设置映射,则为 True,否则为 false。
参数
struct iosys_map *map
iosys_map 结构
描述
将所有字段清零,包括 struct iosys_map
.is_iomem,因此设置为指向 I/O 内存的映射结构将被重置为系统内存。指针将清为 NULL。这是默认设置。
-
void iosys_map_memcpy_to(struct iosys_map *dst, size_t dst_offset, const void *src, size_t len)¶
将内存复制到 iosys_map 的偏移量处
参数
struct iosys_map *dst
iosys_map 结构
size_t dst_offset
要复制到的偏移量
const void *src
源缓冲区
size_t len
src 中的字节数
描述
将数据复制到带有偏移量的 iosys_map 中。源缓冲区位于系统内存中。根据缓冲区的位置,助手会选择正确的内存访问方法。
-
void iosys_map_memcpy_from(void *dst, const struct iosys_map *src, size_t src_offset, size_t len)¶
将内存从 iosys_map 复制到系统内存
参数
void *dst
系统内存中的目标位置
const struct iosys_map *src
iosys_map 结构
size_t src_offset
要复制到的偏移量
size_t len
src 中的字节数
描述
从带有偏移量的 iosys_map 复制数据。目标缓冲区位于系统内存中。根据映射位置,助手会选择正确的内存访问方法。
参数
struct iosys_map *map
iosys_map 结构
size_t incr
要递增的字节数
描述
递增 iosys 映射中存储的地址。根据缓冲区的位置,将更新正确的值。
-
void iosys_map_memset(struct iosys_map *dst, size_t offset, int value, size_t len)¶
Memset iosys_map
参数
struct iosys_map *dst
iosys_map 结构
size_t offset
从 dst 中开始设置值的偏移量
int value
要设置的值
size_t len
要在 dst 中设置的字节数
描述
在 iosys_map 中设置值。根据缓冲区的位置,助手会选择正确的内存访问方法。
-
iosys_map_rd¶
iosys_map_rd (map__, offset__, type__)
从 iosys_map 中读取 C 类型的值
参数
map__
iosys_map 结构
offset__
从中读取的偏移量
type__
正在读取的值的类型
描述
从 iosys_map 中读取 C 类型值 (u8, u16, u32 和 u64)。对于其他类型,或者如果指针可能未对齐(对于支持的架构来说存在问题),请使用 iosys_map_memcpy_from()
。
返回
从映射读取的值。
-
iosys_map_wr¶
iosys_map_wr (map__, offset__, type__, val__)
将 C 类型值写入 iosys_map
参数
map__
iosys_map 结构
offset__
要写入的映射的偏移量
type__
正在写入的值的类型
val__
要写入的值
描述
将 C 类型值 (u8、u16、u32 和 u64) 写入 iosys_map。对于其他类型,或者如果指针可能未对齐(对于支持的架构来说存在问题),请使用 iosys_map_memcpy_to()
-
iosys_map_rd_field¶
iosys_map_rd_field (map__, struct_offset__, struct_type__, field__)
从 iosys_map 中的结构体中读取成员
参数
map__
iosys_map 结构
struct_offset__
结构体在映射起始处的偏移量
struct_type__
描述映射布局的结构体
field__
要读取的结构体成员
描述
从iosys_map读取一个值,考虑到其布局由一个C结构体描述,该结构体从 struct_offset__ 开始。计算字段的偏移量和大小,并读取其值。如果字段访问会导致未对齐访问,则需要使用 iosys_map_memcpy_from()
或者架构必须支持它。 例如:假设有一个 struct foo 定义如下,并且需要从 iosys_map 读取值 foo.field2.inner2
struct foo {
int field1;
struct {
int inner1;
int inner2;
} field2;
int field3;
} __packed;
这是使用 iosys_map_rd_field()
时缓冲区的预期内存布局
地址 |
内容 |
---|---|
buffer + 0000 |
iosys_map 指向的 mmap 缓冲区的开始 |
... |
... |
buffer + |
|
... |
... |
buffer + wwww |
|
... |
... |
buffer + yyyy |
|
... |
... |
buffer + zzzz |
mmap 缓冲区的结束 |
此宏自动计算的值或不需要的值用 wwww、yyyy 和 zzzz 表示。以下是读取该值的代码
x = iosys_map_rd_field(&map, offset, struct foo, field2.inner2);
返回
从映射读取的值。
-
iosys_map_wr_field¶
iosys_map_wr_field (map__, struct_offset__, struct_type__, field__, val__)
将值写入到 iosys_map 中结构体的成员
参数
map__
iosys_map 结构
struct_offset__
结构体在映射起始处的偏移量
struct_type__
描述映射布局的结构体
field__
要读取的结构体成员
val__
要写入的值
描述
将一个值写入到 iosys_map,考虑到其布局由一个C结构体描述,该结构体从 struct_offset__ 开始。计算字段的偏移量和大小,并写入 val__。如果字段访问会导致未对齐访问,则需要使用 iosys_map_memcpy_to()
或者架构必须支持它。有关预期用法和内存布局,请参阅 iosys_map_rd_field()
。
提供的公共函数¶
-
phys_addr_t virt_to_phys(volatile void *address)¶
将虚拟地址映射到物理地址
参数
volatile void *address
要重新映射的地址
返回的物理地址是给定内存地址的物理(CPU)映射。仅对直接映射或通过 kmalloc 分配的地址使用此函数才有效。
此函数不提供 DMA 传输的总线映射。 在几乎所有可以想象的情况下,设备驱动程序都不应使用此函数
-
void *phys_to_virt(phys_addr_t address)¶
将物理地址映射到虚拟地址
参数
phys_addr_t address
要重新映射的地址
返回的虚拟地址是给定内存地址的当前 CPU 映射。仅对具有内核映射的地址使用此函数才有效
此函数不处理 DMA 传输的总线映射。在几乎所有可以想象的情况下,设备驱动程序都不应使用此函数
-
void __iomem *ioremap(resource_size_t offset, unsigned long size)¶
将总线内存映射到 CPU 空间
参数
resource_size_t offset
内存的总线地址
unsigned long size
要映射的资源大小
描述
ioremap 执行一个平台特定的操作序列,使总线内存可以通过 readb/readw/readl/writeb/ writew/writel 函数和其他 mmio 助手进行 CPU 访问。不保证返回的地址可以直接用作虚拟地址。
如果您尝试映射的区域是 PCI BAR,则应查看 pci_iomap()
。
-
void iosubmit_cmds512(void __iomem *dst, const void *src, size_t count)¶
以 512 位为单位将数据复制到单个 MMIO 位置
参数
void __iomem *dst
目标,位于 MMIO 空间中(必须是 512 位对齐的)
const void *src
源
size_t count
要提交的 512 位数量
描述
以 512 位为单位,一次性将数据从内核空间提交到 MMIO 空间。不保证访问顺序,也不保证之后执行内存屏障。
警告:除非您的驱动程序已检查平台是否支持 CPU 指令,否则请勿使用此助手。