总线无关设备访问

作者:

Matthew Wilcox

作者:

Alan Cox

简介

Linux 提供了一个 API,用于抽象在所有总线和设备上执行 I/O 操作,从而允许设备驱动程序独立于总线类型编写。

内存映射 I/O

获取设备访问权限

最广泛支持的 I/O 形式是内存映射 I/O。也就是说,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() 函数。不要在 I/O 地址上使用 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(&reg->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(&reg->ictrl);
    ha->flags.ints_enabled = 0;
}

PCI 排序规则还保证 PIO 读响应在来自该总线的任何未完成 DMA 写操作之后到达,因为对于某些设备,readb() 调用的结果可能向驱动程序指示 DMA 事务已完成。然而,在许多情况下,驱动程序可能希望指示下一个 readb() 调用与设备执行的任何先前的 DMA 写操作无关。驱动程序可以使用 readb_relaxed() 处理这些情况,尽管只有一些平台会遵守宽松的语义。使用宽松的读函数将在支持它的平台上提供显著的性能优势。qla2xxx 驱动程序提供了如何使用 readX_relaxed() 的示例。在许多情况下,驱动程序的大多数 readX() 调用可以安全地转换为 readX_relaxed() 调用,因为只有少数会指示或依赖 DMA 完成。

端口空间访问

端口空间解释

另一种常用的 I/O 形式是端口空间。这是一段与正常内存地址空间分离的地址范围。访问这些地址通常不如访问内存映射地址快,并且其地址空间也可能更小。

与内存映射 I/O 不同,访问端口空间无需准备。

访问端口空间

对该空间的访问通过一组函数提供,这些函数允许 8 位、16 位和 32 位访问;也称为字节、字和长字。这些函数是 inb()、inw()、inl()、outb()、outw() 和 outl()。

为这些函数提供了一些变体。有些设备要求对其端口的访问进行减速。此功能通过在函数末尾附加 _p 来提供。还有 memcpy 的等效函数。ins() 和 outs() 函数将字节、字或长字复制到给定端口。

__iomem 指针标记

MMIO 地址的数据类型是 __iomem 限定的指针,例如 void __iomem *reg。在大多数架构上,它是一个指向虚拟内存地址的常规指针,可以进行偏移或解引用,但在可移植代码中,它只能从显式操作 __iomem 标记的函数传入和传出,特别是 ioremap() 和 readl()/writel() 函数。可以使用 ‘sparse’ 语义代码检查器来验证此操作是否正确。

虽然在大多数架构上,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()

ioremap_np() 绝不应用于 PCI 驱动程序。PCI 内存空间写操作总是提交式的,即使在其他方面实现了 ioremap_np() 的架构上也是如此。将 ioremap_np() 用于 PCI BARs 最好会产生提交写语义,最坏会导致完全损坏。

请注意,非提交写语义与 CPU 端的排序保证是正交的。CPU 仍然可以选择在非提交写指令退出之前发出其他读写操作。有关 CPU 方面的详细信息,请参阅上一节中的 MMIO 访问函数。

ioremap_uc()

ioremap_uc() 仅在具有 PAT 扩展的旧 x86-32 系统以及具有稍微非常规 ioremap() 行为的 ia64 上有意义,其他所有地方 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

ioremap()

Device-nGnRE

ioremap_uc()

(未实现)

ioremap_wc()

普通-不可缓存

ioremap_wt()

(未实现;回退到 ioremap)

ioremap_cache()

普通-回写缓存

高级 ioremap 抽象

驱动程序应鼓励使用更高级别的 API,而不是使用上述原始的 ioremap() 模式。这些 API 可以实现平台特定的逻辑,以在任何给定总线上自动选择适当的 ioremap 模式,从而允许平台无关的驱动程序在这些平台上工作而无需任何特殊情况。在撰写本文时,以下 ioremap() 包装器具有此类逻辑

devm_ioremap_resource()

如果结构体 resource 上设置了 IORESOURCE_MEM_NONPOSTED 标志,则可以根据平台要求自动选择 ioremap_np() 而非 ioremap()。当驱动程序的 probe() 函数失败或设备与驱动程序解绑时,使用 devres 自动解除映射资源。

记录在 Devres - 管理设备资源 中。

of_address_to_resource()

自动为需要特定总线非提交写操作的平台设置 IORESOURCE_MEM_NONPOSTED 标志(请参阅 nonposted-mmio 和 posted-mmio 设备树属性)。

of_iomap()

映射设备树中 reg 属性中描述的资源,执行所有必要的转换。根据平台要求自动选择 ioremap_np(),如上所述。

pci_ioremap_bar(), pci_ioremap_wc_bar()

映射 PCI 基址中描述的资源,无需首先提取物理地址。

pci_iomap(), pci_iomap_wc()

类似于 pci_ioremap_bar()/pci_ioremap_bar(),但与 ioread32()/iowrite32() 及类似访问器一起使用时,也可在 I/O 空间上工作

pcim_iomap()

类似于 pci_iomap(),但当驱动程序的 probe() 函数失败或设备与驱动程序解绑时,使用 devres 自动解除映射资源

记录在 Devres - 管理设备资源 中。

不使用这些包装器可能会导致驱动程序在某些对映射 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);

此类指针的用户可能没有关于该区域映射的信息,或者可能希望有一个单一的代码路径来处理该缓冲区上的操作,无论它位于系统内存还是 I/O 内存中。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

struct iosys_map 的实例可以使用 iosys_map_is_equal() 进行相等性比较。指向不同内存空间(系统或 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

指向 I/O/系统内存的指针

定义:

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 结构体。它对结构体进行浅拷贝,因此可以在不改变原始映射指向位置的情况下更新后端存储。这等同于执行

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() 进行赋值,编译器可以警告使用未初始化变量。

void iosys_map_set_vaddr(struct iosys_map *map, void *vaddr)

将 iosys 映射结构体设置为系统内存中的地址

参数

struct iosys_map *map

iosys_map 结构体

void *vaddr

一个系统内存地址

描述

设置地址并清除 I/O 内存标志。

void __iomem *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。

bool iosys_map_is_null(const struct iosys_map *map)

测试 iosys 映射是否为 NULL

参数

const struct iosys_map *map

iosys_map 结构体

描述

根据 struct iosys_map.is_iomem 的状态,测试映射是否为 NULL。

返回

如果映射为 NULL,则为 True,否则为 False。

bool iosys_map_is_set(const struct iosys_map *map)

测试 iosys 映射是否已设置

参数

const struct iosys_map *map

iosys_map 结构体

描述

根据 struct iosys_map.is_iomem 的状态,测试映射是否已设置。

返回

如果映射已设置,则为 True,否则为 False。

void iosys_map_clear(struct iosys_map *map)

清除 iosys 映射结构体

参数

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 复制数据。目标缓冲区位于系统内存中。根据映射位置,辅助函数会选择正确的内存访问方法。

void iosys_map_incr(struct iosys_map *map, size_t incr)

增加 iosys 映射中存储的地址

参数

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)

设置 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__)

向 iosys_map 写入一个 C 类型值

参数

map__

iosys_map 结构体

offset__

写入映射的偏移量

type__

正在写入的值的类型

val__

要写入的值

描述

向 iosys_map 写入一个 C 类型值(u8、u16、u32 和 u64)。对于其他类型或如果指针可能未对齐(并且对所支持的架构有问题),请使用 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 读取一个值,假设其布局由从 **struct_offset__** 开始的 C 结构体描述。计算字段的偏移量和大小并读取其值。如果字段访问会导致未对齐访问,则需要使用 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 指向的内存映射缓冲区的起始

...

...

buffer + struct_offset__

struct foo 的起始

...

...

buffer + wwww

foo.field2.inner2

...

...

buffer + yyyy

struct foo 的结束

...

...

buffer + zzzz

内存映射缓冲区的结束

此宏自动计算或不需要的值由 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 写入一个值,假设其布局由从 **struct_offset__** 开始的 C 结构体描述。计算字段的偏移量和大小,并写入 **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 __iomem *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 位数量

描述

将数据从内核空间提交到 MMIO 空间,每次以 512 位为单位。不保证访问顺序,之后也不执行内存屏障。

警告:除非您的驱动程序已检查 CPU 指令在平台上受支持,否则请勿使用此辅助函数。