Use fast in-situ test for gate A20 being set, to cut down on the
[gpxe.git] / src / arch / i386 / firmware / pcbios / gateA20.c
1 #include <stdio.h>
2 #include "realmode.h"
3 #include "timer.h"
4 #include "latch.h"
5 #include "bios.h"
6
7 #define K_RDWR          0x60            /* keyboard data & cmds (read/write) */
8 #define K_STATUS        0x64            /* keyboard status */
9 #define K_CMD           0x64            /* keybd ctlr command (write-only) */
10
11 #define K_OBUF_FUL      0x01            /* output buffer full */
12 #define K_IBUF_FUL      0x02            /* input buffer full */
13
14 #define KC_CMD_WIN      0xd0            /* read  output port */
15 #define KC_CMD_WOUT     0xd1            /* write output port */
16 #define KB_SET_A20      0xdf            /* enable A20,
17                                            enable output buffer full interrupt
18                                            enable data line
19                                            disable clock line */
20 #define KB_UNSET_A20    0xdd            /* enable A20,
21                                            enable output buffer full interrupt
22                                            enable data line
23                                            disable clock line */
24
25 enum { Disable_A20 = 0x2400, Enable_A20 = 0x2401, Query_A20_Status = 0x2402,
26         Query_A20_Support = 0x2403 };
27
28 #define CF ( 1 << 0 )
29
30 #ifndef IBM_L40
31 static void empty_8042 ( void ) {
32         unsigned long time;
33
34         time = currticks() + TICKS_PER_SEC;     /* max wait of 1 second */
35         while ( ( inb ( K_CMD ) & K_IBUF_FUL ) &&
36                 currticks() < time ) {
37                 /* Do nothing.  In particular, do *not* read from
38                  * K_RDWR, because that will drain the keyboard buffer
39                  * and lose keypresses.
40                  */
41         }
42 }
43 #endif  /* IBM_L40 */
44
45 /**
46  * Fast test to see if gate A20 is already set
47  *
48  * @ret set             Gate A20 is set
49  */
50 static int gateA20_is_set ( void ) {
51         static uint32_t test_pattern = 0xdeadbeef;
52         physaddr_t test_pattern_phys = virt_to_phys ( &test_pattern );
53         physaddr_t verify_pattern_phys = ( test_pattern_phys ^ 0x100000 );
54         userptr_t verify_pattern_user = phys_to_user ( verify_pattern_phys );
55         uint32_t verify_pattern;
56
57         /* Check for difference */
58         copy_from_user ( &verify_pattern, verify_pattern_user, 0,
59                          sizeof ( verify_pattern ) );
60         if ( verify_pattern != test_pattern )
61                 return 1;
62
63         /* Invert pattern and retest, just to be sure */
64         test_pattern ^= 0xffffffff;
65         copy_from_user ( &verify_pattern, verify_pattern_user, 0,
66                          sizeof ( verify_pattern ) );
67         if ( verify_pattern != test_pattern )
68                 return 1;
69
70         /* Pattern matched both times; gate A20 is not set */
71         return 0;
72 }
73
74 /*
75  * Gate A20 for high memory
76  *
77  * Note that this function gets called as part of the return path from
78  * librm's real_call, which is used to make the int15 call if librm is
79  * being used.  To avoid an infinite recursion, we make gateA20_set
80  * return immediately if it is already part of the call stack.
81  */
82 void gateA20_set ( void ) {
83         static char reentry_guard = 0;
84         unsigned int discard_a;
85
86         /* Avoid potential infinite recursion */
87         if ( reentry_guard )
88                 return;
89         reentry_guard = 1;
90
91         /* Fast check to see if gate A20 is already enabled */
92         if ( gateA20_is_set() )
93                 goto out;
94
95         /* Try INT 15 method first */
96         __asm__ __volatile__ ( REAL_CODE ( "int $0x15" )
97                                : "=a" ( discard_a )
98                                : "a" ( Enable_A20 ) );
99         if ( gateA20_is_set() )
100                 goto out;
101         
102         /* INT 15 method failed, try alternatives */
103 #ifdef  IBM_L40
104         outb(0x2, 0x92);
105 #else   /* IBM_L40 */
106         empty_8042();
107         outb(KC_CMD_WOUT, K_CMD);
108         empty_8042();
109         outb(KB_SET_A20, K_RDWR);
110         empty_8042();
111 #endif  /* IBM_L40 */
112         if ( gateA20_is_set() )
113                 goto out;
114
115         /* Better to die now than corrupt memory later */
116         printf ( "FATAL: Gate A20 stuck\n" );
117         while ( 1 ) {}
118
119  out:
120         reentry_guard = 0;
121 }
122
123 void gateA20_unset ( void ) {
124         /* Not currently implemented */
125 }