summaryrefslogtreecommitdiff
path: root/gpxe/src/arch/i386/firmware/pcbios/gateA20.c
blob: 1a71472dc2ebe115dee4fb6676494e343283b554 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
FILE_LICENCE ( GPL2_OR_LATER );

#include <stdio.h>
#include <realmode.h>
#include <bios.h>
#include <gpxe/io.h>
#include <gpxe/timer.h>

#define K_RDWR		0x60		/* keyboard data & cmds (read/write) */
#define K_STATUS	0x64		/* keyboard status */
#define K_CMD		0x64		/* keybd ctlr command (write-only) */

#define K_OBUF_FUL	0x01		/* output buffer full */
#define K_IBUF_FUL	0x02		/* input buffer full */

#define KC_CMD_WIN	0xd0		/* read  output port */
#define KC_CMD_WOUT	0xd1		/* write output port */
#define KC_CMD_NULL	0xff		/* null command ("pulse nothing") */
#define KB_SET_A20	0xdf		/* enable A20,
					   enable output buffer full interrupt
					   enable data line
					   disable clock line */
#define KB_UNSET_A20	0xdd		/* enable A20,
					   enable output buffer full interrupt
					   enable data line
					   disable clock line */

#define SCP_A		0x92		/* System Control Port A */

enum { Disable_A20 = 0x2400, Enable_A20 = 0x2401, Query_A20_Status = 0x2402,
	Query_A20_Support = 0x2403 };

enum a20_methods {
	A20_UNKNOWN = 0,
	A20_INT15,
	A20_KBC,
	A20_SCPA,
};

#define A20_MAX_RETRIES		32
#define A20_INT15_RETRIES	32
#define A20_KBC_RETRIES		(2^21)
#define A20_SCPA_RETRIES	(2^21)

/**
 * Drain keyboard controller
 */
static void empty_8042 ( void ) {
	unsigned long time;

	time = currticks() + TICKS_PER_SEC;	/* max wait of 1 second */
	while ( ( inb ( K_CMD ) & ( K_IBUF_FUL | K_OBUF_FUL ) ) &&
		currticks() < time ) {
		iodelay();
		( void ) inb_p ( K_RDWR );
		iodelay();
	}
}

/**
 * Fast test to see if gate A20 is already set
 *
 * @v retries		Number of times to retry before giving up
 * @ret set		Gate A20 is set
 */
static int gateA20_is_set ( int retries ) {
	static uint32_t test_pattern = 0xdeadbeef;
	physaddr_t test_pattern_phys = virt_to_phys ( &test_pattern );
	physaddr_t verify_pattern_phys = ( test_pattern_phys ^ 0x100000 );
	userptr_t verify_pattern_user = phys_to_user ( verify_pattern_phys );
	uint32_t verify_pattern;

	do {
		/* Check for difference */
		copy_from_user ( &verify_pattern, verify_pattern_user, 0,
				 sizeof ( verify_pattern ) );
		if ( verify_pattern != test_pattern )
			return 1;

		/* Avoid false negatives */
		test_pattern++;

		iodelay();

		/* Always retry at least once, to avoid false negatives */
	} while ( retries-- >= 0 );

	/* Pattern matched every time; gate A20 is not set */
	return 0;
}

/*
 * Gate A20 for high memory
 *
 * Note that this function gets called as part of the return path from
 * librm's real_call, which is used to make the int15 call if librm is
 * being used.  To avoid an infinite recursion, we make gateA20_set
 * return immediately if it is already part of the call stack.
 */
void gateA20_set ( void ) {
	static char reentry_guard = 0;
	static int a20_method = A20_UNKNOWN;
	unsigned int discard_a;
	unsigned int scp_a;
	int retries = 0;

	/* Avoid potential infinite recursion */
	if ( reentry_guard )
		return;
	reentry_guard = 1;

	/* Fast check to see if gate A20 is already enabled */
	if ( gateA20_is_set ( 0 ) )
		goto out;

	for ( ; retries < A20_MAX_RETRIES ; retries++ ) {
		switch ( a20_method ) {
		case A20_UNKNOWN:
		case A20_INT15:
			/* Try INT 15 method */
			__asm__ __volatile__ ( REAL_CODE ( "int $0x15" )
					       : "=a" ( discard_a )
					       : "a" ( Enable_A20 ) );
			if ( gateA20_is_set ( A20_INT15_RETRIES ) ) {
				DBG ( "Enabled gate A20 using BIOS\n" );
				a20_method = A20_INT15;
				goto out;
			}
			/* fall through */
		case A20_KBC:
			/* Try keyboard controller method */
			empty_8042();
			outb ( KC_CMD_WOUT, K_CMD );
			empty_8042();
			outb ( KB_SET_A20, K_RDWR );
			empty_8042();
			outb ( KC_CMD_NULL, K_CMD );
			empty_8042();
			if ( gateA20_is_set ( A20_KBC_RETRIES ) ) {
				DBG ( "Enabled gate A20 using "
				      "keyboard controller\n" );
				a20_method = A20_KBC;
				goto out;
			}
			/* fall through */
		case A20_SCPA:
			/* Try "Fast gate A20" method */
			scp_a = inb ( SCP_A );
			scp_a &= ~0x01; /* Avoid triggering a reset */
			scp_a |= 0x02; /* Enable A20 */
			iodelay();
			outb ( scp_a, SCP_A );
			iodelay();
			if ( gateA20_is_set ( A20_SCPA_RETRIES ) ) {
				DBG ( "Enabled gate A20 using "
				      "Fast Gate A20\n" );
				a20_method = A20_SCPA;
				goto out;
			}
		}
	}

	/* Better to die now than corrupt memory later */
	printf ( "FATAL: Gate A20 stuck\n" );
	while ( 1 ) {}

 out:
	if ( retries )
		DBG ( "%d attempts were required to enable A20\n",
		      ( retries + 1 ) );
	reentry_guard = 0;
}

void gateA20_unset ( void ) {
	/* Not currently implemented */
}