Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
a20.c
Go to the documentation of this file.
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 
21 static 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 
56 static 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 */
79 static 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. */
86 static int a20_test_long(void)
87 {
88  return a20_test(A20_TEST_LONG);
89 }
90 
91 static 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 
100 static 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 
114 static 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 
130 int 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 }