A20地址线

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开始(取模)寻址。

如下图

A20地址线

为了解决这个问题,用第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;
#else
	while (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{

93struct biosregs ireg;

94

95initregs(&ireg);

96ireg.ax = 0x2401;

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

98}

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

18#define MAX_8042_LOOPS100000

19#define MAX_8042_FF32

20

21static int empty_8042(void)

22{

23u8 status;

24int loops = MAX_8042_LOOPS;

25int ffs= MAX_8042_FF;

26

27while (loops--) {

28io_delay();

29

30status = inb(0x64);

31if (status == 0xff) {

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

33if (!--ffs)

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

35}

36if (status & 1) {

37/* Read and discard input data */

38io_delay();

39(void)inb(0x60);

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

41/* Buffers empty, finished! */

42return 0;

43}

44}

45

46return -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{

102empty_8042();

103

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

105empty_8042();

106

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

108empty_8042();

109

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

111empty_8042();

112}


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

114static void enable_a20_fast(void)

115{

116u8 port_a;

117

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

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

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

121outb(port_a, 0x92);

122}

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

你可能感兴趣的:(a)