1*97873a3dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
296ae6ea0SThomas Gleixner /* -*- linux-c -*- ------------------------------------------------------- *
396ae6ea0SThomas Gleixner *
496ae6ea0SThomas Gleixner * Copyright (C) 1991, 1992 Linus Torvalds
5aa60d13fSH. Peter Anvin * Copyright 2007-2008 rPath, Inc. - All Rights Reserved
6df7699c5SH. Peter Anvin * Copyright 2009 Intel Corporation; author H. Peter Anvin
796ae6ea0SThomas Gleixner *
896ae6ea0SThomas Gleixner * ----------------------------------------------------------------------- */
996ae6ea0SThomas Gleixner
1096ae6ea0SThomas Gleixner /*
1196ae6ea0SThomas Gleixner * Enable A20 gate (return -1 on failure)
1296ae6ea0SThomas Gleixner */
1396ae6ea0SThomas Gleixner
1496ae6ea0SThomas Gleixner #include "boot.h"
1596ae6ea0SThomas Gleixner
1696ae6ea0SThomas Gleixner #define MAX_8042_LOOPS 100000
173bd323a1SH. Peter Anvin #define MAX_8042_FF 32
1896ae6ea0SThomas Gleixner
empty_8042(void)1996ae6ea0SThomas Gleixner static int empty_8042(void)
2096ae6ea0SThomas Gleixner {
2196ae6ea0SThomas Gleixner u8 status;
2296ae6ea0SThomas Gleixner int loops = MAX_8042_LOOPS;
233bd323a1SH. Peter Anvin int ffs = MAX_8042_FF;
2496ae6ea0SThomas Gleixner
2596ae6ea0SThomas Gleixner while (loops--) {
2696ae6ea0SThomas Gleixner io_delay();
2796ae6ea0SThomas Gleixner
2896ae6ea0SThomas Gleixner status = inb(0x64);
293bd323a1SH. Peter Anvin if (status == 0xff) {
303bd323a1SH. Peter Anvin /* FF is a plausible, but very unlikely status */
313bd323a1SH. Peter Anvin if (!--ffs)
323bd323a1SH. Peter Anvin return -1; /* Assume no KBC present */
333bd323a1SH. Peter Anvin }
3496ae6ea0SThomas Gleixner if (status & 1) {
3596ae6ea0SThomas Gleixner /* Read and discard input data */
3696ae6ea0SThomas Gleixner io_delay();
3796ae6ea0SThomas Gleixner (void)inb(0x60);
3896ae6ea0SThomas Gleixner } else if (!(status & 2)) {
3996ae6ea0SThomas Gleixner /* Buffers empty, finished! */
4096ae6ea0SThomas Gleixner return 0;
4196ae6ea0SThomas Gleixner }
4296ae6ea0SThomas Gleixner }
4396ae6ea0SThomas Gleixner
4496ae6ea0SThomas Gleixner return -1;
4596ae6ea0SThomas Gleixner }
4696ae6ea0SThomas Gleixner
4796ae6ea0SThomas Gleixner /* Returns nonzero if the A20 line is enabled. The memory address
4896ae6ea0SThomas Gleixner used as a test is the int $0x80 vector, which should be safe. */
4996ae6ea0SThomas Gleixner
5096ae6ea0SThomas Gleixner #define A20_TEST_ADDR (4*0x80)
5196ae6ea0SThomas Gleixner #define A20_TEST_SHORT 32
5296ae6ea0SThomas Gleixner #define A20_TEST_LONG 2097152 /* 2^21 */
5396ae6ea0SThomas Gleixner
a20_test(int loops)5496ae6ea0SThomas Gleixner static int a20_test(int loops)
5596ae6ea0SThomas Gleixner {
5696ae6ea0SThomas Gleixner int ok = 0;
5796ae6ea0SThomas Gleixner int saved, ctr;
5896ae6ea0SThomas Gleixner
5996ae6ea0SThomas Gleixner set_fs(0x0000);
6096ae6ea0SThomas Gleixner set_gs(0xffff);
6196ae6ea0SThomas Gleixner
6296ae6ea0SThomas Gleixner saved = ctr = rdfs32(A20_TEST_ADDR);
6396ae6ea0SThomas Gleixner
6496ae6ea0SThomas Gleixner while (loops--) {
6596ae6ea0SThomas Gleixner wrfs32(++ctr, A20_TEST_ADDR);
6696ae6ea0SThomas Gleixner io_delay(); /* Serialize and make delay constant */
6796ae6ea0SThomas Gleixner ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;
6896ae6ea0SThomas Gleixner if (ok)
6996ae6ea0SThomas Gleixner break;
7096ae6ea0SThomas Gleixner }
7196ae6ea0SThomas Gleixner
7296ae6ea0SThomas Gleixner wrfs32(saved, A20_TEST_ADDR);
7396ae6ea0SThomas Gleixner return ok;
7496ae6ea0SThomas Gleixner }
7596ae6ea0SThomas Gleixner
7696ae6ea0SThomas Gleixner /* Quick test to see if A20 is already enabled */
a20_test_short(void)7796ae6ea0SThomas Gleixner static int a20_test_short(void)
7896ae6ea0SThomas Gleixner {
7996ae6ea0SThomas Gleixner return a20_test(A20_TEST_SHORT);
8096ae6ea0SThomas Gleixner }
8196ae6ea0SThomas Gleixner
8296ae6ea0SThomas Gleixner /* Longer test that actually waits for A20 to come on line; this
8396ae6ea0SThomas Gleixner is useful when dealing with the KBC or other slow external circuitry. */
a20_test_long(void)8496ae6ea0SThomas Gleixner static int a20_test_long(void)
8596ae6ea0SThomas Gleixner {
8696ae6ea0SThomas Gleixner return a20_test(A20_TEST_LONG);
8796ae6ea0SThomas Gleixner }
8896ae6ea0SThomas Gleixner
enable_a20_bios(void)8996ae6ea0SThomas Gleixner static void enable_a20_bios(void)
9096ae6ea0SThomas Gleixner {
91df7699c5SH. Peter Anvin struct biosregs ireg;
92df7699c5SH. Peter Anvin
93df7699c5SH. Peter Anvin initregs(&ireg);
94df7699c5SH. Peter Anvin ireg.ax = 0x2401;
95df7699c5SH. Peter Anvin intcall(0x15, &ireg, NULL);
9696ae6ea0SThomas Gleixner }
9796ae6ea0SThomas Gleixner
enable_a20_kbc(void)9896ae6ea0SThomas Gleixner static void enable_a20_kbc(void)
9996ae6ea0SThomas Gleixner {
10096ae6ea0SThomas Gleixner empty_8042();
10196ae6ea0SThomas Gleixner
10296ae6ea0SThomas Gleixner outb(0xd1, 0x64); /* Command write */
10396ae6ea0SThomas Gleixner empty_8042();
10496ae6ea0SThomas Gleixner
10596ae6ea0SThomas Gleixner outb(0xdf, 0x60); /* A20 on */
10696ae6ea0SThomas Gleixner empty_8042();
107aa60d13fSH. Peter Anvin
108aa60d13fSH. Peter Anvin outb(0xff, 0x64); /* Null command, but UHCI wants it */
109aa60d13fSH. Peter Anvin empty_8042();
11096ae6ea0SThomas Gleixner }
11196ae6ea0SThomas Gleixner
enable_a20_fast(void)11296ae6ea0SThomas Gleixner static void enable_a20_fast(void)
11396ae6ea0SThomas Gleixner {
11496ae6ea0SThomas Gleixner u8 port_a;
11596ae6ea0SThomas Gleixner
11696ae6ea0SThomas Gleixner port_a = inb(0x92); /* Configuration port A */
11796ae6ea0SThomas Gleixner port_a |= 0x02; /* Enable A20 */
11896ae6ea0SThomas Gleixner port_a &= ~0x01; /* Do not reset machine */
11996ae6ea0SThomas Gleixner outb(port_a, 0x92);
12096ae6ea0SThomas Gleixner }
12196ae6ea0SThomas Gleixner
12296ae6ea0SThomas Gleixner /*
12396ae6ea0SThomas Gleixner * Actual routine to enable A20; return 0 on ok, -1 on failure
12496ae6ea0SThomas Gleixner */
12596ae6ea0SThomas Gleixner
12696ae6ea0SThomas Gleixner #define A20_ENABLE_LOOPS 255 /* Number of times to try */
12796ae6ea0SThomas Gleixner
enable_a20(void)12896ae6ea0SThomas Gleixner int enable_a20(void)
12996ae6ea0SThomas Gleixner {
13052aaa12fSManish Katiyar int loops = A20_ENABLE_LOOPS;
1313bd323a1SH. Peter Anvin int kbc_err;
1323bd323a1SH. Peter Anvin
13396ae6ea0SThomas Gleixner while (loops--) {
13496ae6ea0SThomas Gleixner /* First, check to see if A20 is already enabled
13596ae6ea0SThomas Gleixner (legacy free, etc.) */
13696ae6ea0SThomas Gleixner if (a20_test_short())
13796ae6ea0SThomas Gleixner return 0;
13896ae6ea0SThomas Gleixner
13996ae6ea0SThomas Gleixner /* Next, try the BIOS (INT 0x15, AX=0x2401) */
14096ae6ea0SThomas Gleixner enable_a20_bios();
14196ae6ea0SThomas Gleixner if (a20_test_short())
14296ae6ea0SThomas Gleixner return 0;
14396ae6ea0SThomas Gleixner
14496ae6ea0SThomas Gleixner /* Try enabling A20 through the keyboard controller */
1453bd323a1SH. Peter Anvin kbc_err = empty_8042();
1463bd323a1SH. Peter Anvin
14796ae6ea0SThomas Gleixner if (a20_test_short())
14896ae6ea0SThomas Gleixner return 0; /* BIOS worked, but with delayed reaction */
14996ae6ea0SThomas Gleixner
1503bd323a1SH. Peter Anvin if (!kbc_err) {
15196ae6ea0SThomas Gleixner enable_a20_kbc();
15296ae6ea0SThomas Gleixner if (a20_test_long())
15396ae6ea0SThomas Gleixner return 0;
1543bd323a1SH. Peter Anvin }
15596ae6ea0SThomas Gleixner
15696ae6ea0SThomas Gleixner /* Finally, try enabling the "fast A20 gate" */
15796ae6ea0SThomas Gleixner enable_a20_fast();
15896ae6ea0SThomas Gleixner if (a20_test_long())
15996ae6ea0SThomas Gleixner return 0;
16096ae6ea0SThomas Gleixner }
16196ae6ea0SThomas Gleixner
16296ae6ea0SThomas Gleixner return -1;
16396ae6ea0SThomas Gleixner }
164