/* -*- linux-c -*- ------------------------------------------------------- *1*2* Copyright (C) 1991, 1992 Linus Torvalds3* Copyright 2007-2008 rPath, Inc. - All Rights Reserved4* Copyright 2009 Intel Corporation; author H. Peter Anvin5*6* This file is part of the Linux kernel, and is made available under7* the terms of the GNU General Public License version 2.8*9* ----------------------------------------------------------------------- */1011/*12* Enable A20 gate (return -1 on failure)13*/1415#include "boot.h"1617#define MAX_8042_LOOPS 10000018#define MAX_8042_FF 321920static int empty_8042(void)21{22u8 status;23int loops = MAX_8042_LOOPS;24int ffs = MAX_8042_FF;2526while (loops--) {27io_delay();2829status = inb(0x64);30if (status == 0xff) {31/* FF is a plausible, but very unlikely status */32if (!--ffs)33return -1; /* Assume no KBC present */34}35if (status & 1) {36/* Read and discard input data */37io_delay();38(void)inb(0x60);39} else if (!(status & 2)) {40/* Buffers empty, finished! */41return 0;42}43}4445return -1;46}4748/* Returns nonzero if the A20 line is enabled. The memory address49used as a test is the int $0x80 vector, which should be safe. */5051#define A20_TEST_ADDR (4*0x80)52#define A20_TEST_SHORT 3253#define A20_TEST_LONG 2097152 /* 2^21 */5455static int a20_test(int loops)56{57int ok = 0;58int saved, ctr;5960set_fs(0x0000);61set_gs(0xffff);6263saved = ctr = rdfs32(A20_TEST_ADDR);6465while (loops--) {66wrfs32(++ctr, A20_TEST_ADDR);67io_delay(); /* Serialize and make delay constant */68ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;69if (ok)70break;71}7273wrfs32(saved, A20_TEST_ADDR);74return ok;75}7677/* Quick test to see if A20 is already enabled */78static int a20_test_short(void)79{80return a20_test(A20_TEST_SHORT);81}8283/* Longer test that actually waits for A20 to come on line; this84is useful when dealing with the KBC or other slow external circuitry. */85static int a20_test_long(void)86{87return a20_test(A20_TEST_LONG);88}8990static void enable_a20_bios(void)91{92struct biosregs ireg;9394initregs(&ireg);95ireg.ax = 0x2401;96intcall(0x15, &ireg, NULL);97}9899static void enable_a20_kbc(void)100{101empty_8042();102103outb(0xd1, 0x64); /* Command write */104empty_8042();105106outb(0xdf, 0x60); /* A20 on */107empty_8042();108109outb(0xff, 0x64); /* Null command, but UHCI wants it */110empty_8042();111}112113static void enable_a20_fast(void)114{115u8 port_a;116117port_a = inb(0x92); /* Configuration port A */118port_a |= 0x02; /* Enable A20 */119port_a &= ~0x01; /* Do not reset machine */120outb(port_a, 0x92);121}122123/*124* Actual routine to enable A20; return 0 on ok, -1 on failure125*/126127#define A20_ENABLE_LOOPS 255 /* Number of times to try */128129int enable_a20(void)130{131int loops = A20_ENABLE_LOOPS;132int kbc_err;133134while (loops--) {135/* First, check to see if A20 is already enabled136(legacy free, etc.) */137if (a20_test_short())138return 0;139140/* Next, try the BIOS (INT 0x15, AX=0x2401) */141enable_a20_bios();142if (a20_test_short())143return 0;144145/* Try enabling A20 through the keyboard controller */146kbc_err = empty_8042();147148if (a20_test_short())149return 0; /* BIOS worked, but with delayed reaction */150151if (!kbc_err) {152enable_a20_kbc();153if (a20_test_long())154return 0;155}156157/* Finally, try enabling the "fast A20 gate" */158enable_a20_fast();159if (a20_test_long())160return 0;161}162163return -1;164}165166167