关于 x86 I/O 端口地址和 IN/OUT 指令的问题

Question About x86 I/O Port Addresses and IN/OUT Instructions

据我所知,这是 PC 总线系统的简化视图(它不包括我知道的网桥)。

如图所示:

据我所知,在现代 x86 CPU 中我们有一个 65536 字节 (0000-FFFF) 的地址 space。这些是 I/O 个地址。在 x86 中,IN 和 OUT 指令用于通过 I/O 端口与设备通信。

考虑到 PC (x86 cpus),以下是我的问题:

  1. 我可以在不使用 PCI 或任何其他总线的情况下向此 I/O 总线添加设备吗?我的意思是直接与 IN/OUT 沟通。如果是,I/O 地址是如何分配的?他们不冲突吗?

  2. 如果我的 CPU 支持 PCI 和 I²C(是的,有些支持!),CPU 如何区分它们的 I/O 地址?它如何知道一个 I/O 地址属于 PCI 或 I²C。

(顺便说一句,我不知道 I²C 地址是否只是合乎逻辑的,而不是关于 I/O 端口,但这是另一个我也需要答案的问题)

1) Can I add a device to this I/O bus without using PCI or any other bus?

理论上;是的。但是,对于现代系统,没有 shared/common 总线(现在是 "point to point links");内存控制器和 PCI 主机都内置在与 CPU 相同的芯片中,这意味着(除非你是像英特尔这样的 CPU 供应商的员工)你将不得不处理 high-speed serial links (Quick-path, Hyper-transport, PCIe or DMI) 并不便宜或易于接口(换句话说,它不像 1980 年代那样你有一个漂亮而缓慢的 ISA 总线可以连接)。

If yes how are I/O addresses assigned? Don't they conflict?

旧设备使用由 history/compatibility 分配的固定 IO 端口。其他一切都由一种 "IO port range allocator"(内置于固件 and/or 操作系统中)在 PCI 配置 space 中配置 BAR("Base Address Range" 寄存器)动态分配。有一些(现在 deprecated/non-existent)替代方案 - 通过 ISA 卡上的物理跳线或拨码开关手动分配,ISA "plug and play" 规范在许多设备支持它之前大部分被 PCI 取代,以及存在于用于动态资源分配的其他类型的总线(MCA 和 EISA)。当然,大多数现代设备根本不使用 IO 端口(而是使用内存映射寄存器)。

2-) If my CPU supports PCI and I²C (yes some do!), how does the cpu differentiate between their I/O addresses? How does it know that an I/O address belongs to PCI or I²C.

对于I²C,可能在某处有一对寄存器(在IO端口地址space或物理地址space)用于发送和接收一个字节to/from I²C 总线。 I²C 总线上的所有内容都将通过这 2 个寄存器访问,I²C 总线上的任何内容都无法访问不在 I²C 总线上的任何内容(包括无法访问任何 IO 端口和无法访问任何物理地址)。

大多数情况下(如以太网、视频、USB 等),您有一个控制器(具有 CPU 可以直接访问的寄存器)来控制 CPU 无法访问的内容直接(LAN、发送到显示器的信号、插入 USB 总线的 USB 设备,...)。

(简体)示例

让我们假设(由于 out dx,al 指令)CPU 在共享总线或 link 上发送消息“command = WRITE, space = IO port space, address = 0x1234, size = 1 byte, data = 0x56”。此消息可能会被 PCI 主机控制器拦截,它查看详细信息(哪个地址在哪个地址 space)并决定将消息转发到 PCI 总线上的 [​​=46=] 设备。当 "PCI to LPC bridge" 收到消息时,它可能会查看详细信息(哪个地址在哪个地址 space 中)并意识到它对应于一个 I²C 总线控制器并将其转发给 I²C 总线控制器。 I²C 总线控制器可能会解码消息并在 I²C 总线上发送字节 0x56(来自消息的 "data" 部分)。然后,可能正在侦听 I²C 总线的设备看到该 0x56 字节并通过将字节 0x78 通过 I²C 总线发送回 I²C 总线控制器进行响应,控制器将值 0x78 存储在内部缓冲寄存器中。

下一步; (因为 in al,dx 指令)CPU 可能会发送另一条消息“command = READ, space = IO port space, address = 0x1234, size = 1 byte”。此消息将遵循与前一个相同的路径(因为地址和地址 space 相同)并最终到达 I²C 总线控制器。 I²C 总线控制器可能会对消息进行解码,并意识到该消息要求从 I²C 总线控制器的内部缓冲寄存器(保存之前的值 0x78)中读取;所以 I²C 总线控制器发回消息“command = READ_REPLY, space = IO port space, address = 0x1234, size = 1 byte, data = 0x78”。此回复消息将返回到 CPU(再次使用相同的路径,但方向相反 - 例如,到 PCI 到 LPC 桥,然后到 PCI 主机控制器,然后到 CPU)。当 CPU 得到回复时,它可以完成导致它的原始指令(例如,将 al 设置为 "READ_reply" 消息中的值以完成 in al,dx 指令) .

这里的重点是 CPU 不能直接向 I²C 总线上的设备发送任何东西(CPU 只能与 I²C 总线控制器通信); I²C 总线上的任何设备都不能直接向 CPU 发送任何内容(它们也只能与 I²C 总线控制器通信)。 I²C 总线上的任何内容都不需要知道有关 IO 端口的任何信息; CPU 的 bus/links 不需要了解 I²C 总线上的任何字节。

还有; CPU 只是发送和接收消息。它不知道它们发送后会发生什么,也不知道(例如)“command = READ”消息将如何转发到哪里,也不知道哪个设备将发回“command = READ_REPLY”消息。路由主要取决于消息所采用路径中每个步骤的逻辑。例如,PCI 主机控制器可能被配置为将 0x0000 到 0x2000 范围内的 IO 端口访问的所有消息转发到 PCI 总线,而 PCI 到 LPC 桥可能被配置为转发 0x1234 范围内的 IO 端口访问的所有消息到 I²C 总线控制器的 0x1235。