18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/* -*- linux-c -*- ------------------------------------------------------- *
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci *   Copyright (C) 1991, 1992 Linus Torvalds
58c2ecf20Sopenharmony_ci *   Copyright 2007-2008 rPath, Inc. - All Rights Reserved
68c2ecf20Sopenharmony_ci *   Copyright 2009 Intel Corporation; author H. Peter Anvin
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * ----------------------------------------------------------------------- */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci/*
118c2ecf20Sopenharmony_ci * Enable A20 gate (return -1 on failure)
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include "boot.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define MAX_8042_LOOPS	100000
178c2ecf20Sopenharmony_ci#define MAX_8042_FF	32
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic int empty_8042(void)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	u8 status;
228c2ecf20Sopenharmony_ci	int loops = MAX_8042_LOOPS;
238c2ecf20Sopenharmony_ci	int ffs   = MAX_8042_FF;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	while (loops--) {
268c2ecf20Sopenharmony_ci		io_delay();
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci		status = inb(0x64);
298c2ecf20Sopenharmony_ci		if (status == 0xff) {
308c2ecf20Sopenharmony_ci			/* FF is a plausible, but very unlikely status */
318c2ecf20Sopenharmony_ci			if (!--ffs)
328c2ecf20Sopenharmony_ci				return -1; /* Assume no KBC present */
338c2ecf20Sopenharmony_ci		}
348c2ecf20Sopenharmony_ci		if (status & 1) {
358c2ecf20Sopenharmony_ci			/* Read and discard input data */
368c2ecf20Sopenharmony_ci			io_delay();
378c2ecf20Sopenharmony_ci			(void)inb(0x60);
388c2ecf20Sopenharmony_ci		} else if (!(status & 2)) {
398c2ecf20Sopenharmony_ci			/* Buffers empty, finished! */
408c2ecf20Sopenharmony_ci			return 0;
418c2ecf20Sopenharmony_ci		}
428c2ecf20Sopenharmony_ci	}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	return -1;
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/* Returns nonzero if the A20 line is enabled.  The memory address
488c2ecf20Sopenharmony_ci   used as a test is the int $0x80 vector, which should be safe. */
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci#define A20_TEST_ADDR	(4*0x80)
518c2ecf20Sopenharmony_ci#define A20_TEST_SHORT  32
528c2ecf20Sopenharmony_ci#define A20_TEST_LONG	2097152	/* 2^21 */
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic int a20_test(int loops)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	int ok = 0;
578c2ecf20Sopenharmony_ci	int saved, ctr;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	set_fs(0x0000);
608c2ecf20Sopenharmony_ci	set_gs(0xffff);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	saved = ctr = rdfs32(A20_TEST_ADDR);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	while (loops--) {
658c2ecf20Sopenharmony_ci		wrfs32(++ctr, A20_TEST_ADDR);
668c2ecf20Sopenharmony_ci		io_delay();	/* Serialize and make delay constant */
678c2ecf20Sopenharmony_ci		ok = rdgs32(A20_TEST_ADDR+0x10) ^ ctr;
688c2ecf20Sopenharmony_ci		if (ok)
698c2ecf20Sopenharmony_ci			break;
708c2ecf20Sopenharmony_ci	}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	wrfs32(saved, A20_TEST_ADDR);
738c2ecf20Sopenharmony_ci	return ok;
748c2ecf20Sopenharmony_ci}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci/* Quick test to see if A20 is already enabled */
778c2ecf20Sopenharmony_cistatic int a20_test_short(void)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	return a20_test(A20_TEST_SHORT);
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci/* Longer test that actually waits for A20 to come on line; this
838c2ecf20Sopenharmony_ci   is useful when dealing with the KBC or other slow external circuitry. */
848c2ecf20Sopenharmony_cistatic int a20_test_long(void)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	return a20_test(A20_TEST_LONG);
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic void enable_a20_bios(void)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	struct biosregs ireg;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	initregs(&ireg);
948c2ecf20Sopenharmony_ci	ireg.ax = 0x2401;
958c2ecf20Sopenharmony_ci	intcall(0x15, &ireg, NULL);
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic void enable_a20_kbc(void)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	empty_8042();
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	outb(0xd1, 0x64);	/* Command write */
1038c2ecf20Sopenharmony_ci	empty_8042();
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	outb(0xdf, 0x60);	/* A20 on */
1068c2ecf20Sopenharmony_ci	empty_8042();
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	outb(0xff, 0x64);	/* Null command, but UHCI wants it */
1098c2ecf20Sopenharmony_ci	empty_8042();
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic void enable_a20_fast(void)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	u8 port_a;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	port_a = inb(0x92);	/* Configuration port A */
1178c2ecf20Sopenharmony_ci	port_a |=  0x02;	/* Enable A20 */
1188c2ecf20Sopenharmony_ci	port_a &= ~0x01;	/* Do not reset machine */
1198c2ecf20Sopenharmony_ci	outb(port_a, 0x92);
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci/*
1238c2ecf20Sopenharmony_ci * Actual routine to enable A20; return 0 on ok, -1 on failure
1248c2ecf20Sopenharmony_ci */
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci#define A20_ENABLE_LOOPS 255	/* Number of times to try */
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ciint enable_a20(void)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci       int loops = A20_ENABLE_LOOPS;
1318c2ecf20Sopenharmony_ci       int kbc_err;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci       while (loops--) {
1348c2ecf20Sopenharmony_ci	       /* First, check to see if A20 is already enabled
1358c2ecf20Sopenharmony_ci		  (legacy free, etc.) */
1368c2ecf20Sopenharmony_ci	       if (a20_test_short())
1378c2ecf20Sopenharmony_ci		       return 0;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	       /* Next, try the BIOS (INT 0x15, AX=0x2401) */
1408c2ecf20Sopenharmony_ci	       enable_a20_bios();
1418c2ecf20Sopenharmony_ci	       if (a20_test_short())
1428c2ecf20Sopenharmony_ci		       return 0;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	       /* Try enabling A20 through the keyboard controller */
1458c2ecf20Sopenharmony_ci	       kbc_err = empty_8042();
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	       if (a20_test_short())
1488c2ecf20Sopenharmony_ci		       return 0; /* BIOS worked, but with delayed reaction */
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	       if (!kbc_err) {
1518c2ecf20Sopenharmony_ci		       enable_a20_kbc();
1528c2ecf20Sopenharmony_ci		       if (a20_test_long())
1538c2ecf20Sopenharmony_ci			       return 0;
1548c2ecf20Sopenharmony_ci	       }
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	       /* Finally, try enabling the "fast A20 gate" */
1578c2ecf20Sopenharmony_ci	       enable_a20_fast();
1588c2ecf20Sopenharmony_ci	       if (a20_test_long())
1598c2ecf20Sopenharmony_ci		       return 0;
1608c2ecf20Sopenharmony_ci       }
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci       return -1;
1638c2ecf20Sopenharmony_ci}
164