谁来决定 I/O 映射和内存映射 I/O (x86)

Tom*_*erg 6 x86 assembly cpu-architecture

在 x86 架构中,我们对 I/O 映射 I/O 使用 I/O 指令,如 IN 和 OUT。据我所知,我们在内存映射 I/O 中使用像 MOV 这样的内存指令。这一切都很好,但谁来决定使用哪种 I/O 方法?如果我想建立自己的设备(一个外设),我可以自由选择使用 I/O 映射或内存映射 I/O 与 PC 通信吗?还是所有设备都必须支持两者?

无法理解谁决定用于与设备通信的 I/O 方法。

Mar*_*oom 8

正如迈克尔佩奇在他的评论中所说,它是制造商,但它并不总是有完全的自由。
标准和规范可以强制使用地址空间,一些标准是通用的(例如 OHCI、USB 1.0,指的是“不可缓存的地址空间”,在 x86 上可以是 IO 或 MMIO)其他不是(例如 PC 客户端TPM 规范根据使用的 MMIO 区域按位置映射 TPM 寄存器)。


据我所知,就我们在这个答案中所关注的而言,随着 PCI 1的出现,MMIO 采用成为主流。
PCI BAR(基地址寄存器)有一种特殊的格式,允许软件知道卡正在使用哪个地址空间(以及需要多少地址空间):

PCI BAR 格式

位 0 是只读的(由制造商设置)并指示卡正在使用哪个地址空间。

IO 空间相对于 MMIO 的优势在于不需要任何设置,MMIO 需要虚拟到物理映射和正确的缓存类型。
但是IO空间只有64KiB+3B,非常小。
事实上,PCI 2.2 将单个 BAR 使用的最大 IO 空间限制为 256 字节。

每个 BAR 256 IO 字节

抱歉图片,从 PDF 规范复制给了我胡言乱语

此外,指针在 IO 空间中不起作用,一些设备使用指针(例如 USB 控制器、GBe 等)。

IO 肯定用于传统设备(在 MMIO 出现之前)。
我曾经认为 IO 用于具有少量寄存器的设备,但并不总是如此,例如 PCH(芯片组)的电源管理器控制寄存器是 IO 映射的,占用 128B。

有时,设备同时支持 IO 和 MMIO。这需要两个 BAR,一个例子是 PCH 的 SMBus 控制器:

SMBus 控制器的 BAR

它有两个 BAR(注意默认值,一个用于 IO,另一个用于 MMIO)控制同一组寄存器。
文档指定两者都可以使用。

我无法给出何时使用 IO 与 MMIO 的确切规则。
我认为性能上没有区别,区别只是PCIe链路层发送的TLP数据包中的一点。
但是我从来没有调查过这个问题,IO 指令是序列化的,所以在软件级别会有性能损失。

我的经验法则是,如果以下任何一项为真,IO 是/可以使用的:

  • 该设备是传统设备(此处确实没有选择的自由)。
  • 您的设备没有使用指针(因为 IO 没有指针)并且寄存器集很小。
  • 寄存器主要用于控制和报告设备或整个系统的状态(因为 IO 指令是串行化的)。

这些只是经验法则,根据我的阅读和记忆,有很多例外和反例。
今天的趋势是使用 MMIO,这可能需要更多的解码逻辑(更多的地址线进行解码),但 PCI 规范通过允许设备将其解码四舍五入到 4KiB 来简化它。
一个例子是 PCIe 配置空间,在 PCI 中它是 IO 访问的(使用一种类似于寄存器堆栈的技术,例如在 VGA 控制器中使用),但现在是内存映射。

无需考虑其他总线,因为 PCIe 是现代 PC 上的主要总线,其他一切都通过 PCIe 设备(例如 USB 使用 xHCI PCI 设备)。
唯一的例外是非核心设备(例如 LAPIC、TXT 寄存器),这些是通过内存映射 IO 访问的,因为我认为它的性能更高,这种访问不会进入系统代理(这些设备是靠近它们的核心并且无论如何都在 CPU 包内)所以使用(序列化)IO 指令会显着影响它们。
此外,在 4GiB 的顶部还有一个不错的地方,英特尔可以在那里回收内存,而不会对其他设备造成太大压力。
有趣的事实:端口 0xf8-0xff 是保留的,因为当时 FPU 是协处理器 (x87),CPU 使用这些端口与它进行通信。


1在此之前,其他两种PnP总线都已经可用(例如PnP ISA和 MCA),但解码内存访问主要是为了提供对 ROM 和卡上 RAM 的访问。将寄存器映射到内存还不是我猜的事情。