A20地址线

来源:互联网 发布:java定义长整型 编辑:程序博客网 时间:2024/04/30 01:56

void go_to_protected_mode(void){/* Hook before leaving real mode, also disables interrupts */realmode_switch_hook();/* Move the kernel/setup to their final resting places */move_kernel_around();/* Enable the A20 gate */if (enable_a20()) {puts("A20 gate not responding, unable to boot...\n");die();}/* Reset coprocessor (IGNNE#) */reset_coprocessor();/* Mask all interrupts in the PIC */mask_all_interrupts();/* Actual transition to protected mode... */setup_idt();setup_gdt();protected_mode_jump(boot_params.hdr.code32_start,    (u32)&boot_params + (ds() << 4));}
上面是内核从实模式进入保护模式的代码,其中调用enable_a20()函数以打开A20地址线,A20地址线是什么呢?这是本文的重点。先了解一些历史。

8086/8088 PC中,地址总线只有20根,最大的寻址能力只有1M,在实模式下,内存寻址采用16位段和偏移量的组合来寻址(注意这里的最大寻址空间:FFFF0+FFFF=10FFEF),因为地址线只有20,所以如果寻址地址在100000以上时,就采用取模的方式,返回地址。到了80286的时候,地址总线变为24根,这样最大的寻址能力就有16M了,如果此时向下兼容,就会有一个问题:寻址在100000以上时,实际的物理内存是存在的,因为内存寻址最大都可以到16M,但在8086时,从0开始(取模)寻址。

如下图


为了解决这个问题,用第21根线(就是A20)来控制是否允许对100000以上的实际内存就行寻址。称为A20 Gate

  1. 如果A20 Gate开启,那么对0x100000-0x10FFEF之间的地址进行寻址时,系统访问实际的地址对应的物理内存。
  2. 如果A20 Gate被禁用,则对0x100000-0x10FFEF之间的地址进行寻址时,还是取模,从0开始访问内存。
系统开机CPU复位时,自动进入实地址模式,A23~A20自动置为0,以 A19~A0寻址1M的存储空间。就算是A20 Gate打开,也不能对100000以上的内存进行寻址,要对以上内存进行寻址,必须进入保护模式。现在准备进入保护模式,所以把A20 Gate打开。
int enable_a20(void){int loops = A20_ENABLE_LOOPS;#if defined(CONFIG_X86_ELAN)/* Elan croaks if we try to touch the KBC */enable_a20_fast();while (!a20_test_long());return 0;#elif defined(CONFIG_X86_VOYAGER)/* On Voyager, a20_test() is unsafe? */enable_a20_kbc();return 0;#elsewhile (loops--) {/* First, check to see if A20 is already enabled   (legacy free, etc.) */if (a20_test_short())return 0;/* Next, try the BIOS (INT 0x15, AX=0x2401) */enable_a20_bios();if (a20_test_short())return 0;/* Try enabling A20 through the keyboard controller */empty_8042();if (a20_test_short())return 0; /* BIOS worked, but with delayed reaction */enable_a20_kbc();if (a20_test_long())return 0;/* Finally, try enabling the "fast A20 gate" */enable_a20_fast();if (a20_test_long())return 0;}return -1;#endif}
这个函数具体的就不分析了,引用一个高人分析的结果:

如果A20 Gate被打开了,则在实模式下,程序员可以直接访问100000H~10FFEFH之间的内存,如果A20 Gate被禁止,则在实模式下,若程序员访问100000H~10FFEFH之间的内存,则会被硬件自动转换为0H~0FFEFH之间的内存,所以我a20_test函数就是利用这个差异来检测A20 Gate是否被打开。

 

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

 

67行进入循环,使ctr加1,具体等于多少不知道,把这个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()没有成功,试试142行enable_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}

 

嗯,没问题,调用BIOS的15号服务程序,打开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返回0,enable_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}

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

后面部分来源:http://blog.csdn.net/yunsongice/article/details/6110648