打开A20地址线

3.4.2 打开A20地址线

回到go_to_protected_mode()110行,调用一个enable_a20()函数。这里又用到“PC汇编及BIOS编程”的知识了。PC及其兼容机的第21根地址线(A20)较特殊,这就是“Intel 80286工作模式” 提到的PC中安排的一个“门”控制该地址线是否有效。到了80286,系统的地址总线有原来的20根发展为24根,这样能够访问的内存可以达到2^24=16MIntel在设计80286时提出的目标是向下兼容。所以,在实模式下,系统所表现的行为应该和8086/8088所表现的完全一样,也就是说,在实模式下,80286以及后续系列,应该和8086/8088完全兼容。但最终,80286芯片却存在一个BUG:因为有了80286A20线,如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存,而不是象8086/8088一样从0开始。我们来看一副图:

打开A20地址线_第1张图片

为了解决上述兼容性问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根) 的有效性,被称为A20 Gate

 

1 如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;

 

2 如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式(8086仿真)。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能。

 

上面所述的内存访问模式都是实模式,在80286以及更高系列的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。

 

那么,如何打开这个“门”呢?这就是enable_a20()函数干的事情,来自

linux/arch/x86/boot/a20.c

 

/*

 125 * Actual routine to enable A20; return 0 on ok, -1 on failure

 126 */

 127

 128#define A20_ENABLE_LOOPS 255    /* Number of times to try */

 129

 130int enable_a20(void)

 131{

 132       int loops = A20_ENABLE_LOOPS;

 133       int kbc_err;

 134

 135       while (loops--) {

 136               /* First, check to see if A20 is already enabled

 137                  (legacy free, etc.) */

 138               if (a20_test_short())

 139                       return 0;

 140              

 141               /* Next, try the BIOS (INT 0x15, AX=0x2401) */

 142               enable_a20_bios();

 143               if (a20_test_short())

 144                       return 0;

 145              

 146               /* Try enabling A20 through the keyboard controller */

 147               kbc_err = empty_8042();

 148

 149               if (a20_test_short())

 150                       return 0; /* BIOS worked, but with delayed reaction */

 151       

 152               if (!kbc_err) {

 153                       enable_a20_kbc();

 154                       if (a20_test_long())

 155                               return 0;

 156               }

 157              

 158               /* Finally, try enabling the "fast A20 gate" */

 159               enable_a20_fast();

 160               if (a20_test_long())

 161                       return 0;

 162       }

 163      

 164       return -1;

 165}

 

enable_a20()函数返回0,则直接到go_to_protected_mode116行,否则重启。下面重点来分析一下enable_a20()函数。函数首先进行一个最大循环为255次的有限循环中。然后不断调用探测函数a20_test_short()a20_test_long()

 

/* Returns nonzero if the A20 line is enabled.  The memory address

  50   used as a test is the int $0x80 vector, which should be safe. */

  51

  52#define A20_TEST_ADDR   (4*0x80)

  53#define A20_TEST_SHORT  32

  54#define A20_TEST_LONG   2097152 /* 2^21 */

  55

  56static int a20_test(int loops)

  57{

  58        int ok = 0;

  59        int saved, ctr;

  60

  61        set_fs(0x0000);

  62        set_gs(0xffff);

  63

  64        saved = ctr = rdfs32(A20_TEST_ADDR);

  65

  66        while (loops--) {

  67                wrfs32(++ctr, A20_TEST_ADDR);

  68                io_delay();     /* Serialize and make delay constant */

  69                ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;

  70                if (ok)

  71                        break;

  72        }

  73

  74        wrfs32(saved, A20_TEST_ADDR);

  75        return ok;

  76}

  77

  78/* Quick test to see if A20 is already enabled */

  79static int a20_test_short(void)

  80{

  81        return a20_test(A20_TEST_SHORT);

  82}

  83

  84/* Longer test that actually waits for A20 to come on line; this

  85   is useful when dealing with the KBC or other slow external circuitry. */

  86static int a20_test_long(void)

  87{

  88        return a20_test(A20_TEST_LONG);

  89}

 

一路走来的同志们看这些代码应该不难,就是用fs配合gs来测试A20 Gate是否被打开。我们在之前已经提到,如果A20 Gate被打开了,则在实模式下,程序员可以直接访问100000H~10FFEFH之间的内存,如果A20 Gate被禁止,则在实模式下,若程序员访问100000H~10FFEFH之间的内存,则会被硬件自动转换为0H~0FFEFH之间的内存,所以我a20_test函数就是利用这个差异来检测A20 Gate是否被打开。

 

首先,6162行,把fsgs的值分别设置为0x00000xffff。然后64行调用rdfs32函数得到0000: 4*0x80的内容并存放到32位的临时变量savedctr中。4*0x80=0x200,所以savedctr中存放的是内存地址0x200处的值。对应内核映像解压缩后的内存布局,我们知道这个地址属于BIOS的一些数据存放的区域,虽然我不知道这个地址到底是存的什么数据,但是可以肯定的是,这个数据可以随意修改,来做我们的A20测试的。

 

67行进入循环,使ctr1,具体等于多少不知道,把这个32位的值写入0000: 4*0x80对应的内存单元中。69行,“^”是按位异或运算符,即如果ffff: 4*0x80+0x10内存单元存放的值与ctr相等,则说明有可能刚刚写入的ctr其实是写入的0000: 4*0x80内存单元。注意,ffff: 4*0x80+0x10换算实模式地址就是ffff0+4*0x80+0x10=0x100200,不过也不排除偶然的情况,这两个内存单元相等。所以继续循环,修改一下ctr的值,多试几次。

 

74行,跳出循环后,你刚才给人家0x200的地址对应的内存修改了数据,得给人家改回去啊,最后返回ok。如果执行了loops次这个ok都是0,就说明A20肯定没有打开。

 

回到enable_a20函数,如果bootloader没有已经关闭了A20的话(grub是肯定关闭了的),也就是第一个a20_test_short()没有成功,试试142enable_a20_bios()

 

  91static void enable_a20_bios(void)

  92{

  93        struct biosregs ireg;

  94

  95        initregs(&ireg);

  96        ireg.ax = 0x2401;

  97        intcall(0x15, &ireg, NULL);

  98}

 

嗯,没问题,调用BIOS15号服务程序,打开A20。如果没成功,执行147行代码:

 

  18#define MAX_8042_LOOPS  100000

  19#define MAX_8042_FF     32

  20

  21static int empty_8042(void)

  22{

  23        u8 status;

  24        int loops = MAX_8042_LOOPS;

  25        int ffs   = MAX_8042_FF;

  26

  27        while (loops--) {

  28                io_delay();

  29

  30                status = inb(0x64);

  31                if (status == 0xff) {

  32                        /* FF is a plausible, but very unlikely status */

  33                        if (!--ffs)

  34                                return -1; /* Assume no KBC present */

  35                }

  36                if (status & 1) {

  37                        /* Read and discard input data */

  38                        io_delay();

  39                        (void)inb(0x60);

  40                } else if (!(status & 2)) {

  41                        /* Buffers empty, finished! */

  42                        return 0;

  43                }

  44        }

  45

  46        return -1;

  47}

 

0x64号端口号对应的是键盘控制器(keyboard controller, KBC)的状态寄存器,首先30行获得该控制器的状态值,保存到内部变量status中。status不能为0xff,否则出错返回。其次,status的最低位D0如果被设置,则说明键盘缓存中还有数据,则通过inb(0x60)从键盘缓存对应的端口读出来,并且置空。

 

介绍一下键盘控制器:主板的一个芯片。通过LPC总线和南桥相连。一般作用:键盘控制、LCD明暗度的调节、低级电源的管理、风扇、蓝牙等一些小功能。

 

注意这个逻辑关系,如果键盘缓存中没有内容,并且status的次低位D1不为1,则说明键盘缓存是空的,这时候empty_8042返回0enable_a20再一次检测一下BIOS然后来到了153行,调用enable_a20_kbc()函数,利用键盘控制器来尝试打开A20

 

100static void enable_a20_kbc(void)

 101{

 102        empty_8042();

 103

 104        outb(0xd1, 0x64);       /* Command write */

 105        empty_8042();

 106

 107        outb(0xdf, 0x60);       /* A20 on */

 108        empty_8042();

 109

 110        outb(0xff, 0x64);       /* Null command, but UHCI wants it */

 111        empty_8042();

 112}

 

如果还打不开A20,没辙了,就来到enable_a20()159行进行最后的努力,调用enable_a20_fast()函数。

 

114static void enable_a20_fast(void)

 115{

 116        u8 port_a;

 117

 118        port_a = inb(0x92);     /* Configuration port A */

 119        port_a |=  0x02;        /* Enable A20 */

 120        port_a &= ~0x01;        /* Do not reset machine */

 121        outb(port_a, 0x92);

 122}

 

这个函数是个熟面孔,在我们的博文“保护模式编程实例”中,那个EnableA20汇编宏就是干的这么一个事情,将0x92端口的D1位置位。

 

注意这个enable_a20_fast,基于x86的主板都提供0x92端口来作为一个主板控制寄存器,所以,如果这个函数执行后都还是打不开A20的话,那也就没辙了,只好放弃。

你可能感兴趣的:(汇编,IBM,command,keyboard,loops,delay)