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