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 *   Copyright 2009 Intel Corporation; author H. Peter Anvin
   6 *
   7 *   This file is part of the Linux kernel, and is made available under
   8 *   the terms of the GNU General Public License version 2.
   9 *
  10 * ----------------------------------------------------------------------- */
  11
  12/*
  13 * Enable A20 gate (return -1 on failure)
  14 */
  15
  16#include "boot.h"
  17
  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}
  48
  49/* 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}
  90
  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}
  99
 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}
 113
 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}
 123
 124/*
 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}
 166