A20 Line

来自osdev
跳到导航 跳到搜索

A20地址线是任何内存访问的第21位 (从0开始计数到数字20) 物理表示。(译者注:A20 Address Line启用设置是一种为了兼容而保留的技术,一般使用需要设置1启用) 当IBM-AT(Intel 286)推出时,它能够访问多达16兆字节的内存(而不是8086的1兆字节)。 但为了保持与8086的兼容性,必须在AT中复制8086体系结构中的一个怪癖(内存地址折回-memory wraparound)。 为此,默认情况下,地址总线上的A20线被禁用。

这是因为8086只能访问1兆字节的内存, 但由于分段内存模型,它可以有效地寻址高达1M字节和64K字节(减去16字节)。 因为8086上有20个地址线 (A0到A19),所以1兆字节标记以上的任何地址都折回为零。 出于某种原因,一些目光短浅的程序员会编写实际使用这种折回行为的程序 (而不是在内存底部的正常位置直接寻址内存)。 因此,为了在新处理器上支持这些8086时代的程序,必须在IBM AT及其兼容性上模拟这种折回; 这最初是通过锁存器(latch)实现的,默认情况下,锁存器将A20线设置为零。(译者注:也就是保持和原来旧CPU的内存地址溢出后的归零折回一样的特性) 后来,486将逻辑添加到处理器中,并引入了A20M引脚来控制它。

对于操作系统开发人员 (或 Bootloader 开发人员),这意味着必须启用A20线路,以便可以访问所有内存。 这一开始只是一个简单的黑客技巧,但最后又加入更简单的方法来应对它, 导致编写肯定会启用它的代码变得更加困难,而编写肯定会禁用它的代码也变得更加困难。

键盘控制器

A20线使能的传统方法是直接探测键盘控制器。 原因是英特尔的8042键盘控制器有一个备用引脚,他们决定通过该引脚控制A20线路。 考虑到它们(内存和键盘)的设计目的毫无关系,这在现在看来似乎是愚蠢的,但是当时计算机还没有那么标准化。 键盘控制器通常是8042芯片。 通过对该芯片进行精确编程,你可以启用或禁用地址总线上的位#20。

当你的PC启动时,A20 gate通常被禁用,但是一些BIOS (和仿真器,如QEMU) 确实为你启用了它,一些高内存管理器 (HIMEM.SYS) 或引导加载器 (GRUB) 也会这么做。

测试A20线

在使用下面介绍的任何方法启用A20之前,最好测试A20地址线是否已由BIOS启用。 这可以通过将位于地址0000:7DFE处的引导扇区标识符 (0xAA55) 与位于地址FFFF:7E0E处的值更高的1MIB进行比较来实现。 当两个值不同时,这意味着A20已启用,否则如果值相同,则必须排除看看这是不是偶然。 因此,需要更改引导扇区标识符,例如通过将其向左旋转8位,并再次与FFFF:7E0E处的16位字进行比较。 当它们仍然相同时,A20地址线是被禁用的,否则是被启用的。

下面的代码执行检查(不像上面描述的那样——更直接)。

; 以下代码是公共领域许可的

[bits 16]

; Function: check_a20
;
; 目的:以完全独立的状态保存方式检查a20线路的状态。
;          如果不需要完全的自包含(self-containment),可以根据需要对该函数进行修改
;          删除开头的push和结尾的pop。
;
; 如果a20线被禁用(内存出现折回),则返回: ax中的0
; 如果启用了A20线路,则为AX中的1(内存不折回)

check_a20:
    pushf
    push ds
    push es
    push di
    push si

    cli

    xor ax, ax ; ax = 0
    mov es, ax

    not ax ; ax = 0xFFFF
    mov ds, ax

    mov di, 0x0500
    mov si, 0x0510

    mov al, byte [es:di]
    push ax

    mov al, byte [ds:si]
    push ax

    mov byte [es:di], 0x00
    mov byte [ds:si], 0xFF

    cmp byte [es:di], 0xFF

    pop ax
    mov byte [ds:si], al

    pop ax
    mov byte [es:di], al

    mov ax, 0
    je check_a20__exit

    mov ax, 1

check_a20__exit:
    pop si
    pop di
    pop es
    pop ds
    popf

    ret

从保护模式测试A20线路

在受保护模式下,测试A20更容易,因为你可以使用任何奇数兆字节地址访问A20的设置内存地址,并将其与偶数兆字节的邻居进行比较。

[bits 32]

; 检查A20线路设置
; 如果A20 gate被清除0,则返回给调用者。
; 如果设置了A20行,则继续到A20_on运行。
; 作者Elad Ashkcenazi 

is_A20_on?:   

pushad
mov edi,0x112345  ;odd megabyte address.
mov esi,0x012345  ;even megabyte address.
mov [esi],esi     ;确保两个地址都包含不同的值。
mov [edi],edi     ;( 如果清除了A20线路设置为0,则两个指针将指向包含0x112345 (edi) 的地址0x012345) 
cmpsd             ;比较地址以查看它们是否相同。
popad
jne A20_on        ;如果不等效,则设置A20行。
ret               ;如果等效,A20线路已被清除为0。

A20_on:
;*此处是你的代码*

启用

有几个使能A20的方法来源,通常每一个输入或合在一起以形成A20使能信号。 但这也意味着使用一种方法 (如果芯片组支持) 也许就足以启用A20。 而如果要禁用A20,可能必须禁用所有现有源。 始终通过如上所述测试线路来确保A20具有请求的状态。

键盘控制器

对于最初启用A20线路的方法,需要一些使用键盘控制器芯片(8042芯片)的硬件IO。

void init_A20(void)
{
   uint8_t   a;

   disable_ints();

   kyb_wait_until_done();
   kyb_send_command(0xAD);         // disable keyboard

   kyb_wait_until_done();
   a=kyb_get_data();

   kyb_wait_until_done();
   kyb_send_command(0xD1);         // Write to output

   kyb_wait_until_done();
   kyb_send_data(a|2);

   kyb_wait_until_done();
   kyb_send_command(0xAE);         // enable keyboard

   enable_ints();
}

或在汇编

;;
;; NASM 32bit assembler
;;

[bits 32]
[section .text]

enable_A20:
        cli

        call    a20wait
        mov     al,0xAD
        out     0x64,al

        call    a20wait
        mov     al,0xD0
        out     0x64,al

        call    a20wait2
        in      al,0x60
        push    eax

        call    a20wait
        mov     al,0xD1
        out     0x64,al

        call    a20wait
        pop     eax
        or      al,2
        out     0x60,al

        call    a20wait
        mov     al,0xAE
        out     0x64,al

        call    a20wait
        sti
        ret

a20wait:
        in      al,0x64
        test    al,2
        jnz     a20wait
        ret


a20wait2:
        in      al,0x64
        test    al,1
        jz      a20wait2
        ret

Fast A20 Gate

在大多数以IBM PS/2开始的较新计算机上,芯片组有一个FAST A20选项,可以快速启用A20线路设置。 如果以这种方式启用A20,则不需要延迟循环或轮询,只需3条简单的指令。

in al, 0x92
or al, 2
out 0x92, al

正如在 参见站点中提到的,最好仅在必要时进行写入,并确保位0为0,因为它用于快速重置。 下面是一个例子:

in al, 0x92
test al, 2
jnz after
or al, 2
and al, 0xFE
out 0x92, al
after:

然而,并非所有地方都支持Fast A20方法,而且没有可靠的方法来判断它是否会对给定的系统产生一些影响。 更糟糕的是,在某些系统上,它实际上可能会做其他事情,例如清空屏幕,因此只有在 BIOS 报告FAST A20可用后才应使用它。 还需要为缺乏FAST A20支持的系统编写代码,因此不鼓励只依赖这种方法。 此外,在某些芯片组上,你可能需要在BIOS配置屏幕中启用Fast A20支持。

INT 15

另一种方法是使用BIOS。

;FASM
use16
mov     ax,2403h                ;--- A20-Gate Support ---
int     15h
jb      a20_ns                  ;INT 15h is not supported
cmp     ah,0
jnz     a20_ns                  ;INT 15h is not supported

mov     ax,2402h                ;--- A20-Gate Status ---
int     15h
jb      a20_failed              ;couldn't get status
cmp     ah,0
jnz     a20_failed              ;couldn't get status

cmp     al,1
jz      a20_activated           ;A20 is already activated

mov     ax,2401h                ;--- A20-Gate Activate ---
int     15h
jb      a20_failed              ;couldn't activate the gate
cmp     ah,0
jnz     a20_failed              ;couldn't activate the gate

a20_activated:                  ;go on

如果有一个中断失败,你将不得不使用另一种方法。(见下文。)

访问0xee

在某些系统上,读取IO端口 0xee启用A20,而写入则禁用A20。 (或者,有时,此操作仅在启用IO端口 0xee时发生。) 类似的情况也适用于IO端口 0xef和Reset(写入会导致重置)。 来自i386SL/i486SL文档:

以下端口仅在启用时可见,对这些端口的任何写入都会导致对应名称的操作。

Name of Register     Address   Default Value  Where placed    Size
FAST CPU RESET         EFh          N/A         82360SL         8
FAST A20 GATE          EEh          N/A         82360SL         8  

启用A20:

in al,0xee

禁用A20:

out 0xee,al

注意 写入时AL包含什么并不重要,读取时AL未定义(端口0xee)

推荐方法

因为有几种不同的方法可能被支持,也可能不被支持,而且其中一些方法会在某些计算机上引发问题; 推荐的方法是按“风险最小”的顺序尝试所有这些方法,直到其中一种方法起作用。 本质上:

  • 测试A20是否已启用——如果已启用,则根本不需要执行任何操作
  • 尝试使用BIOS功能。 忽略返回状态。
  • 测试A20是否启用(查看BIOS功能是否实际工作)
  • 尝试键盘控制器方法。
  • 测试是否在循环中启用了A20并具有超时等待-(因为键盘控制器方法可能工作缓慢)
  • 最后尝试Fast A20方法
  • 测试A20是否在超时循环中启用(因为FAST A20方法可能工作缓慢)
  • 如果以上都不起作用,请放弃

另见

外部链接

de:A20-Gate