xref: /openbmc/linux/arch/x86/boot/a20.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
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