A20 Gate
作者:解琛
时间:2020 年 9 月 4 日
Intel 早期的 8086 CPU 提供了 20 根地址线,可寻址空间范围即 0 2 2 0 0~2^20 0 220 (00000H ~ FFFFFH) 的 1MB 内存空间。
但 8086 的数据处理位宽位 16 位,无法直接寻址 1MB 内存空间,所以 8086 提供了段地址加偏移地址的地址转换机制。
PC 机的寻址结构是 segment:offset,segment 和 offset 都是 16 位的寄存器,最大值是 0ffffh。
换算成物理地址的计算方法是把 segment 左移 4 位,再加上 offset,所以 segment:offset 所能表达的寻址空间最大应为 0ffff0h + 0ffffh = 10ffefh。
前面的 0ffffh 是 segment = 0ffffh 并向左移动 4 位的结果,后面的 0ffffh 是可能的最大 offset。
这个计算出的 10ffefh 是多大呢?大约是 1088KB,就是说,segment:offset 的地址表示能力,超过了 20 位地址线的物理寻址能力。
所以当寻址到超过 1MB 的内存时,会发生“回卷”(不会发生异常)。
下一代的基于 Intel 80286 CPU 的 PC AT 计算机系统提供了 24 根地址线,这样 CPU 的寻址范围变为 2 2 4 = 16 M 2^24=16M 224=16M,同时也提供了保护模式,可以访问到 1MB 以上的内存了,此时如果遇到“寻址超过 1MB”的情况,系统不会再“回卷”了,这就造成了向下不兼容。
为了保持完全的向下兼容性,IBM 决定在 PC AT 计算机系统上加个硬件逻辑,来模仿以上的回绕特征,于是出现了 A20 Gate。
他们的方法就是把 A20 地址线控制和键盘控制器的一个输出进行 AND 操作,这样来控制 A20 地址线的打开(使能)和关闭(屏蔽\禁止)。
一开始时 A20 地址线控制是被屏蔽的(总为 0),直到系统软件通过一定的 IO 操作去打开它。
在实模式下要访问高端内存区,这个开关必须打开。
在保护模式下,由于使用 32 位地址线,如果 A20 恒等于0,那么系统只能访问奇数兆的内存,即只能访问 0-1M、2-3M、4-5M…,这显然是不行的,所以在保护模式下,这个开关也必须打开。
当 A20 地址线控制禁止时,则程序就像在 8086 中运行,1MB 以上的地址是不可访问的。
在保护模式下 A20 地址线控制是要打开的。
为了使能所有地址位的寻址能力,必须向键盘控制器 8042 发送一个命令。
键盘控制器 8042 将会将它的的某个输出引脚的输出置高电平,作为 A20 地址线控制的输入。
一旦设置成功之后,内存将不会再被绕回(memory wrapping),这样我们就可以寻址整个 286 的 16M 内存,或者是寻址 80386 级别机器的所有 4G 内存了。
键盘控制器 8042 的逻辑结构图如下所示。
早期的 PC 机,控制键盘有一个单独的单片机 8042,现如今这个芯片已经给集成到了其它大片子中,但其功能和使用方法还是一样。
当 PC 机刚刚出现 A20 Gate 的时候,为节省硬件设计成本等考虑,工程师使用这个 8042 键盘控制器来控制 A20 Gate,但 A20 Gate 与键盘管理没有一点关系。
8042 键盘控制器的 IO 端口是 0x60~0x6f,实际上 IBM PC/AT 使用的只有 0x60 和 0x64 两个端口(0x61、0x62 和 0x63 用于与 XT 兼容目的)。
8042 通过这些端口给键盘控制器或键盘发送命令或读取状态。
输出端口 P2 用于特定目的。
位 0(P20引脚)用于实现 CPU 复位操作,位 1(P21 引脚)用户控制 A20 信号线的开启与否。
系统向输入缓冲(端口 0x64)写入一个字节,即发送一个键盘控制器命令,可以带一个参数。
参数是通过 0x60 端口发送的。 命令的返回值也从端口 0x60 去读。
8042 有 4 个寄存器:
- 1 个 8-bit 长的 Input buffer,Write-Only;
- 1 个 8-bit 长的 Output buffer,Read-Only;
- 1 个 8-bit 长的 Status Register,Read-Only;
- 1 个 8-bit 长的 Control Register,Read/Write。
有两个端口地址,60h 和 64h,有关对它们的读写操作描述如下:
- 读 60h 端口,读 output buffer;
- 写 60h 端口,写 input buffer;
- 读 64h 端口,读 Status Register;
- 操作 Control Register。
- 要向 64h 端口写一个命令(20h 为读命令,60h 为写命令);
- 根据命令从 60h 端口读出 Control Register 的数据;
- 或向 60h 端口写入 Control Register 的数据(64h 端口还可以接受许多其它的命令)。
Status Register 的定义(要用 bit 0 和 bit 1):
bit | meaning |
---|---|
0 | output register (60h) 中有数据 |
1 | input register (60h/64h) 有数据 |
2 | 系统标志(上电复位后被置为0) |
3 | data in input register is command (1) or data (0) |
4 | 1=keyboard enabled, 0=keyboard disabled (via switch) |
5 | 1=transmit timeout (data transmit not complete) |
6 | 1=receive timeout (data transmit not complete) |
7 | 1=even parity rec’d, 0=odd parity rec’d (should be odd) |
8042 还有 3 个内部端口:Input Port、Outport Port 和 Test Port。
这三个端口的操作都是通过向 64h 发送命令,然后在 60h 进行读写的方式完成,其中本文要操作的 A20 Gate 被定义在 Output Port 的 bit 1 上,所以有必要对 Outport Port 的操作及端口定义做一个说明。
- 读 Output Port:向 64h 发送 0d0h 命令,然后从 60h 读取 Output Port 的内容;
- 写 Output Port:向 64h 发送 0d1h 命令,然后向 60h 写入 Output Port 的数据;
- 禁止键盘操作命令:向 64h 发送 0adh;
- 打开键盘操作命令:向 64h 发送 0aeh。
有了这些命令和知识,就可以实现操作 A20 Gate 来从实模式切换到保护模式了。
理论上讲,我们只要操作 8042 芯片的输出端口(64h)的bit 1,就可以控制 A20 Gate。
实际上,当你准备向 8042 的输入缓冲区里写数据时,可能里面还有其它数据没有处理,所以,我们要首先禁止键盘操作,同时等待数据缓冲区中没有数据以后,才能真正地去操作 8042 打开或者关闭 A20 Gate。
打开 A20 Gate 的具体步骤大致如下:
- 等待 8042 Input buffer 为空;
- 发送 Write 8042 Output Port (P2)命令到8042 Input buffer;
- 等待 8042 Input buffer 为空;
- 将 8042 Output Port(P2)得到字节的第 2 位置 1,然后写入 8042 Input buffer。