linux/arch/x86/boot/a20.c
<<
>>
Prefs
   1/* -*- linux-c -*- ------------------------------------------------------- *
   2 *
   3 *   Copyright (C) 1991, 1992 Linus Torvalds
   4 *   Copyright 2007-2008 rPath, Inc. - All Rights Reserved
   5 *
   6 *   This file is part of the Linux kernel, and is made available under
   7 *   the terms of the GNU General Public License version 2.
   8 *
   9 * ----------------------------------------------------------------------- */
  10
  11/*
  12 * Enable A20 gate (return -1 on failure)
  13 */
  14
  15#include "boot.h"
  16
  17#define MAX_8042_LOOPS  100000
  18
  19static int empty_8042(void)
  20{
  21        u8 status;
  22        int loops = MAX_8042_LOOPS;
  23
  24        while (loops--) {
  25                io_delay();
  26
  27                status = inb(0x64);
  28                if (status & 1) {
  29                        /* Read and discard input data */
  30                        io_delay();
  31                        (void)inb(0x60);
  32                } else if (!(status & 2)) {
  33                        /* Buffers empty, finished! */
  34                        return 0;
  35                }
  36        }
  37
  38        return -1;
  39}
  40
  41/* Returns nonzero if the A20 line is enabled.  The memory address
  42   used as a test is the int $0x80 vector, which should be safe. */
  43
  44#define A20_TEST_ADDR   (4*0x80)
  45#define A20_TEST_SHORT  32
  46#define A20_TEST_LONG   2097152 /* 2^21 */
  47
  48static int a20_test(int loops)
  49{
  50        int ok = 0;
  51        int saved, ctr;
  52
  53        set_fs(0x0000);
  54        set_gs(0xffff);
  55
  56        saved = ctr = rdfs32(A20_TEST_ADDR);
  57
  58        while (loops--) {
  59                wrfs32(++ctr, A20_TEST_ADDR);
  60                io_delay();     /* Serialize and make delay constant */
  61                ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;
  62                if (ok)
  63                        break;
  64        }
  65
  66        wrfs32(saved, A20_TEST_ADDR);
  67        return ok;
  68}
  69
  70/* Quick test to see if A20 is already enabled */
  71static int a20_test_short(void)
  72{
  73        return a20_test(A20_TEST_SHORT);
  74}
  75
  76/* Longer test that actually waits for A20 to come on line; this
  77   is useful when dealing with the KBC or other slow external circuitry. */
  78static int a20_test_long(void)
  79{
  80        return a20_test(A20_TEST_LONG);
  81}
  82
  83static void enable_a20_bios(void)
  84{
  85        asm volatile("pushfl; int $0x15; popfl"
  86                     : : "a" ((u16)0x2401));
  87}
  88
  89static void enable_a20_kbc(void)
  90{
  91        empty_8042();
  92
  93        outb(0xd1, 0x64);       /* Command write */
  94        empty_8042();
  95
  96        outb(0xdf, 0x60);       /* A20 on */
  97        empty_8042();
  98
  99        outb(0xff, 0x64);       /* Null command, but UHCI wants it */
 100        empty_8042();
 101}
 102
 103static void enable_a20_fast(void)
 104{
 105        u8 port_a;
 106
 107        port_a = inb(0x92);     /* Configuration port A */
 108        port_a |=  0x02;        /* Enable A20 */
 109        port_a &= ~0x01;        /* Do not reset machine */
 110        outb(port_a, 0x92);
 111}
 112
 113/*
 114 * Actual routine to enable A20; return 0 on ok, -1 on failure
 115 */
 116
 117#define A20_ENABLE_LOOPS 255    /* Number of times to try */
 118
 119int enable_a20(void)
 120{
 121#if defined(CONFIG_X86_ELAN)
 122        /* Elan croaks if we try to touch the KBC */
 123        enable_a20_fast();
 124        while (!a20_test_long())
 125                ;
 126        return 0;
 127#elif defined(CONFIG_X86_VOYAGER)
 128        /* On Voyager, a20_test() is unsafe? */
 129        enable_a20_kbc();
 130        return 0;
 131#else
 132       int loops = A20_ENABLE_LOOPS;
 133        while (loops--) {
 134                /* First, check to see if A20 is already enabled
 135                   (legacy free, etc.) */
 136                if (a20_test_short())
 137                        return 0;
 138
 139                /* Next, try the BIOS (INT 0x15, AX=0x2401) */
 140                enable_a20_bios();
 141                if (a20_test_short())
 142                        return 0;
 143
 144                /* Try enabling A20 through the keyboard controller */
 145                empty_8042();
 146                if (a20_test_short())
 147                        return 0; /* BIOS worked, but with delayed reaction */
 148
 149                enable_a20_kbc();
 150                if (a20_test_long())
 151                        return 0;
 152
 153                /* Finally, try enabling the "fast A20 gate" */
 154                enable_a20_fast();
 155                if (a20_test_long())
 156                        return 0;
 157        }
 158
 159        return -1;
 160#endif
 161}
 162