Be more aggressive in attempts to enable A20, now that we have 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 #define SCP_A           0x92            /* System Control Port A */
26
27 #define A20_MAX_RETRIES 32
28
29 enum { Disable_A20 = 0x2400, Enable_A20 = 0x2401, Query_A20_Status = 0x2402,
30         Query_A20_Support = 0x2403 };
31
32 /**
33  * Drain keyboard controller
34  */
35 static void empty_8042 ( void ) {
36         unsigned long time;
37
38         time = currticks() + TICKS_PER_SEC;     /* max wait of 1 second */
39         while ( ( inb ( K_CMD ) & ( K_IBUF_FUL | K_OBUF_FUL ) ) &&
40                 currticks() < time ) {
41                 SLOW_DOWN_IO;
42                 ( void ) inb ( K_RDWR );
43                 SLOW_DOWN_IO;
44         }
45 }
46
47 /**
48  * Fast test to see if gate A20 is already set
49  *
50  * @ret set             Gate A20 is set
51  */
52 static int gateA20_is_set ( void ) {
53         static uint32_t test_pattern = 0xdeadbeef;
54         physaddr_t test_pattern_phys = virt_to_phys ( &test_pattern );
55         physaddr_t verify_pattern_phys = ( test_pattern_phys ^ 0x100000 );
56         userptr_t verify_pattern_user = phys_to_user ( verify_pattern_phys );
57         uint32_t verify_pattern;
58
59         /* Check for difference */
60         copy_from_user ( &verify_pattern, verify_pattern_user, 0,
61                          sizeof ( verify_pattern ) );
62         if ( verify_pattern != test_pattern )
63                 return 1;
64
65         /* Invert pattern and retest, just to be sure */
66         test_pattern ^= 0xffffffff;
67         copy_from_user ( &verify_pattern, verify_pattern_user, 0,
68                          sizeof ( verify_pattern ) );
69         if ( verify_pattern != test_pattern )
70                 return 1;
71
72         /* Pattern matched both times; gate A20 is not set */
73         return 0;
74 }
75
76 /*
77  * Gate A20 for high memory
78  *
79  * Note that this function gets called as part of the return path from
80  * librm's real_call, which is used to make the int15 call if librm is
81  * being used.  To avoid an infinite recursion, we make gateA20_set
82  * return immediately if it is already part of the call stack.
83  */
84 void gateA20_set ( void ) {
85         static char reentry_guard = 0;
86         unsigned int discard_a;
87         unsigned int scp_a;
88         int retries = 0;
89
90         /* Avoid potential infinite recursion */
91         if ( reentry_guard )
92                 return;
93         reentry_guard = 1;
94
95         /* Fast check to see if gate A20 is already enabled */
96         if ( gateA20_is_set() )
97                 goto out;
98
99         for ( ; retries < A20_MAX_RETRIES ; retries++ ) {
100
101                 /* Try INT 15 method first */
102                 __asm__ __volatile__ ( REAL_CODE ( "int $0x15" )
103                                        : "=a" ( discard_a )
104                                        : "a" ( Enable_A20 ) );
105                 if ( gateA20_is_set() ) {
106                         DBG ( "Enabled gate A20 using BIOS\n" );
107                         goto out;
108                 }
109
110                 /* Try keyboard controller method */
111                 empty_8042();
112                 outb ( KC_CMD_WOUT, K_CMD );
113                 empty_8042();
114                 outb ( KB_SET_A20, K_RDWR );
115                 empty_8042();
116                 if ( gateA20_is_set() ) {
117                         DBG ( "Enabled gate A20 using keyboard controller\n" );
118                         goto out;
119                 }
120
121                 /* Try "Fast gate A20" method */
122                 scp_a = inb ( SCP_A );
123                 scp_a &= ~0x01; /* Avoid triggering a reset */
124                 scp_a |= 0x02; /* Enable A20 */
125                 SLOW_DOWN_IO;
126                 outb ( scp_a, SCP_A );
127                 SLOW_DOWN_IO;
128                 if ( gateA20_is_set() ) {
129                         DBG ( "Enabled gate A20 using Fast Gate A20\n" );
130                         goto out;
131                 }
132         }
133
134         /* Better to die now than corrupt memory later */
135         printf ( "FATAL: Gate A20 stuck\n" );
136         while ( 1 ) {}
137
138  out:
139         if ( retries )
140                 DBG ( "%d attempts were required to enable A20\n",
141                       ( retries + 1 ) );
142         reentry_guard = 0;
143 }
144
145 void gateA20_unset ( void ) {
146         /* Not currently implemented */
147 }