xref: /haiku/src/add-ons/kernel/bus_managers/ps2/ps2_common.cpp (revision 02354704729d38c3b078c696adc1bbbd33cbcf72)
1 /*
2  * Copyright 2004-2009 Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors (in chronological order):
6  *		Stefano Ceccherini (burton666@libero.it)
7  *		Axel Dörfler, axeld@pinc-software.de
8  *      Marcus Overhagen <marcus@overhagen.de>
9  */
10 
11 /*! PS/2 bus manager */
12 
13 
14 #include <string.h>
15 
16 #include "ps2_common.h"
17 #include "ps2_service.h"
18 #include "ps2_dev.h"
19 
20 
21 //#define TRACE_PS2_COMMON
22 #ifdef TRACE_PS2_COMMON
23 #	define TRACE(x...) dprintf(x)
24 #else
25 #	define TRACE(x...)
26 #endif
27 
28 
29 isa_module_info *gIsa = NULL;
30 bool gActiveMultiplexingEnabled = false;
31 sem_id gControllerSem;
32 
33 static int32 sIgnoreInterrupts = 0;
34 
35 
36 uint8
37 ps2_read_ctrl(void)
38 {
39 	return gIsa->read_io_8(PS2_PORT_CTRL);
40 }
41 
42 
43 uint8
44 ps2_read_data(void)
45 {
46 	return gIsa->read_io_8(PS2_PORT_DATA);
47 }
48 
49 
50 void
51 ps2_write_ctrl(uint8 ctrl)
52 {
53 	TRACE("ps2: ps2_write_ctrl 0x%02x\n", ctrl);
54 
55 	gIsa->write_io_8(PS2_PORT_CTRL, ctrl);
56 }
57 
58 
59 void
60 ps2_write_data(uint8 data)
61 {
62 	TRACE("ps2: ps2_write_data 0x%02x\n", data);
63 
64 	gIsa->write_io_8(PS2_PORT_DATA, data);
65 }
66 
67 
68 status_t
69 ps2_wait_read(void)
70 {
71 	int i;
72 	for (i = 0; i < PS2_CTRL_WAIT_TIMEOUT / 50; i++) {
73 		if (ps2_read_ctrl() & PS2_STATUS_OUTPUT_BUFFER_FULL)
74 			return B_OK;
75 		snooze(50);
76 	}
77 	return B_ERROR;
78 }
79 
80 
81 status_t
82 ps2_wait_write(void)
83 {
84 	int i;
85 	for (i = 0; i < PS2_CTRL_WAIT_TIMEOUT / 50; i++) {
86 		if (!(ps2_read_ctrl() & PS2_STATUS_INPUT_BUFFER_FULL))
87 			return B_OK;
88 		snooze(50);
89 	}
90 	return B_ERROR;
91 }
92 
93 
94 //	#pragma mark -
95 
96 
97 void
98 ps2_flush(void)
99 {
100 	int i;
101 
102 	acquire_sem(gControllerSem);
103 	atomic_add(&sIgnoreInterrupts, 1);
104 
105 	for (i = 0; i < 64; i++) {
106 		uint8 ctrl;
107 		uint8 data;
108 		ctrl = ps2_read_ctrl();
109 		if (!(ctrl & PS2_STATUS_OUTPUT_BUFFER_FULL))
110 			break;
111 		data = ps2_read_data();
112 		TRACE("ps2: ps2_flush: ctrl 0x%02x, data 0x%02x (%s)\n", ctrl, data, (ctrl & PS2_STATUS_AUX_DATA) ? "aux" : "keyb");
113 		snooze(100);
114 	}
115 
116 	atomic_add(&sIgnoreInterrupts, -1);
117 	release_sem(gControllerSem);
118 }
119 
120 
121 static status_t
122 ps2_selftest()
123 {
124 	status_t res;
125 	uint8 in;
126 	res = ps2_command(PS2_CTRL_SELF_TEST, NULL, 0, &in, 1);
127 	if (res != B_OK || in != 0x55) {
128 		INFO("ps2: controller self test failed, status 0x%08" B_PRIx32 ", data "
129 			"0x%02x\n", res, in);
130 		return B_ERROR;
131 	}
132 	return B_OK;
133 }
134 
135 
136 static status_t
137 ps2_setup_command_byte(bool interruptsEnabled)
138 {
139 	status_t res;
140 	uint8 cmdbyte;
141 
142 	res = ps2_command(PS2_CTRL_READ_CMD, NULL, 0, &cmdbyte, 1);
143 	TRACE("ps2: get command byte: res 0x%08" B_PRIx32 ", cmdbyte 0x%02x\n",
144 		res, cmdbyte);
145 	if (res != B_OK)
146 		cmdbyte = 0x47;
147 
148 	cmdbyte |= PS2_BITS_TRANSLATE_SCANCODES;
149 	cmdbyte &= ~(PS2_BITS_KEYBOARD_DISABLED | PS2_BITS_MOUSE_DISABLED);
150 
151 	if (interruptsEnabled)
152 		cmdbyte |= PS2_BITS_KEYBOARD_INTERRUPT | PS2_BITS_AUX_INTERRUPT;
153 	else
154 		cmdbyte &= ~(PS2_BITS_KEYBOARD_INTERRUPT | PS2_BITS_AUX_INTERRUPT);
155 
156 	res = ps2_command(PS2_CTRL_WRITE_CMD, &cmdbyte, 1, NULL, 0);
157 	TRACE("ps2: set command byte: res 0x%08" B_PRIx32 ", cmdbyte 0x%02x\n",
158 		res, cmdbyte);
159 
160 	return res;
161 }
162 
163 
164 static status_t
165 ps2_setup_active_multiplexing(bool *enabled)
166 {
167 	status_t res;
168 	uint8 in, out;
169 
170 	out = 0xf0;
171 	res = ps2_command(0xd3, &out, 1, &in, 1);
172 	if (res)
173 		goto fail;
174 	// Step 1, if controller is good, in does match out.
175 	// This test failes with MS Virtual PC.
176 	if (in != out)
177 		goto no_support;
178 
179 	out = 0x56;
180 	res = ps2_command(0xd3, &out, 1, &in, 1);
181 	if (res)
182 		goto fail;
183 	// Step 2, if controller is good, in does match out.
184 	if (in != out)
185 		goto no_support;
186 
187 	out = 0xa4;
188 	res = ps2_command(0xd3, &out, 1, &in, 1);
189 	if (res)
190 		goto fail;
191 	// Step 3, if the controller doesn't support active multiplexing,
192 	// then in data does match out data (0xa4), else it's version number.
193 	if (in == out)
194 		goto no_support;
195 
196 	// With some broken USB legacy emulation, it's 0xac, and with
197 	// MS Virtual PC, it's 0xa6. Since current active multiplexing
198 	// specification version is 1.1 (0x11), we validate the data.
199 	if (in > 0x9f) {
200 		TRACE("ps2: active multiplexing v%d.%d detected, but ignored!\n", (in >> 4), in & 0xf);
201 		goto no_support;
202 	}
203 
204 	INFO("ps2: active multiplexing v%d.%d enabled\n", (in >> 4), in & 0xf);
205 	*enabled = true;
206 	goto done;
207 
208 no_support:
209 	TRACE("ps2: active multiplexing not supported\n");
210 	*enabled = false;
211 
212 done:
213 	// Some controllers get upset by the d3 command and will continue data
214 	// loopback, thus we need to send a harmless command (enable keyboard
215 	// interface) next.
216 	// This fixes bug report #1175
217 	res = ps2_command(0xae, NULL, 0, NULL, 0);
218 	if (res != B_OK) {
219 		INFO("ps2: active multiplexing d3 workaround failed, status 0x%08"
220 			B_PRIx32 "\n", res);
221 	}
222 	return B_OK;
223 
224 fail:
225 	TRACE("ps2: testing for active multiplexing failed\n");
226 	*enabled = false;
227 	// this should revert the controller into legacy mode,
228 	// just in case it has switched to multiplexed mode
229 	return ps2_selftest();
230 }
231 
232 
233 status_t
234 ps2_command(uint8 cmd, const uint8 *out, int outCount, uint8 *in, int inCount)
235 {
236 	status_t res;
237 	int i;
238 
239 	acquire_sem(gControllerSem);
240 	atomic_add(&sIgnoreInterrupts, 1);
241 
242 #ifdef TRACE_PS2
243 	TRACE("ps2: ps2_command cmd 0x%02x, out %d, in %d\n", cmd, outCount, inCount);
244 	for (i = 0; i < outCount; i++)
245 		TRACE("ps2: ps2_command out 0x%02x\n", out[i]);
246 #endif
247 
248 	res = ps2_wait_write();
249 	if (res == B_OK)
250 		ps2_write_ctrl(cmd);
251 
252 	for (i = 0; res == B_OK && i < outCount; i++) {
253 		res = ps2_wait_write();
254 		if (res == B_OK)
255 			ps2_write_data(out[i]);
256 		else
257 			TRACE("ps2: ps2_command out byte %d failed\n", i);
258 	}
259 
260 	for (i = 0; res == B_OK && i < inCount; i++) {
261 		res = ps2_wait_read();
262 		if (res == B_OK)
263 			in[i] = ps2_read_data();
264 		else
265 			TRACE("ps2: ps2_command in byte %d failed\n", i);
266 	}
267 
268 #ifdef TRACE_PS2
269 	for (i = 0; i < inCount; i++)
270 		TRACE("ps2: ps2_command in 0x%02x\n", in[i]);
271 	TRACE("ps2: ps2_command result 0x%08" B_PRIx32 "\n", res);
272 #endif
273 
274 	atomic_add(&sIgnoreInterrupts, -1);
275 	release_sem(gControllerSem);
276 
277 	return res;
278 }
279 
280 
281 //	#pragma mark -
282 
283 
284 static int32
285 ps2_interrupt(void* cookie)
286 {
287 	uint8 ctrl;
288 	uint8 data;
289 	bool error;
290 	ps2_dev *dev;
291 
292 	ctrl = ps2_read_ctrl();
293 	if (!(ctrl & PS2_STATUS_OUTPUT_BUFFER_FULL)) {
294 		TRACE("ps2: ps2_interrupt unhandled, OBF bit unset, ctrl 0x%02x (%s)\n",
295 				ctrl, (ctrl & PS2_STATUS_AUX_DATA) ? "aux" : "keyb");
296 		return B_UNHANDLED_INTERRUPT;
297 	}
298 
299 	if (atomic_get(&sIgnoreInterrupts)) {
300 		TRACE("ps2: ps2_interrupt ignoring, ctrl 0x%02x (%s)\n", ctrl,
301 			(ctrl & PS2_STATUS_AUX_DATA) ? "aux" : "keyb");
302 		return B_HANDLED_INTERRUPT;
303 	}
304 
305 	data = ps2_read_data();
306 
307 	if ((ctrl & PS2_STATUS_AUX_DATA) != 0) {
308 		uint8 idx;
309 		if (gActiveMultiplexingEnabled) {
310 			idx = ctrl >> 6;
311 			error = (ctrl & 0x04) != 0;
312 			TRACE("ps2: ps2_interrupt ctrl 0x%02x, data 0x%02x (mouse %d)\n",
313 				ctrl, data, idx);
314 		} else {
315 			idx = 0;
316 			error = (ctrl & 0xC0) != 0;
317 			TRACE("ps2: ps2_interrupt ctrl 0x%02x, data 0x%02x (aux)\n", ctrl,
318 				data);
319 		}
320 		dev = &ps2_device[PS2_DEVICE_MOUSE + idx];
321 	} else {
322 		TRACE("ps2: ps2_interrupt ctrl 0x%02x, data 0x%02x (keyb)\n", ctrl,
323 			data);
324 
325 		dev = &ps2_device[PS2_DEVICE_KEYB];
326 		error = (ctrl & 0xC0) != 0;
327 	}
328 
329 	dev->history[1] = dev->history[0];
330 	dev->history[0].time = system_time();
331 	dev->history[0].data = data;
332 	dev->history[0].error = error;
333 
334 	return ps2_dev_handle_int(dev);
335 }
336 
337 
338 //	#pragma mark - driver interface
339 
340 
341 status_t
342 ps2_init(void)
343 {
344 	status_t status;
345 
346 	TRACE("ps2: init\n");
347 
348 	status = get_module(B_ISA_MODULE_NAME, (module_info **)&gIsa);
349 	if (status < B_OK)
350 		return status;
351 
352 	gControllerSem = create_sem(1, "ps/2 keyb ctrl");
353 
354 	ps2_flush();
355 
356 	status = ps2_dev_init();
357 	if (status < B_OK)
358 		goto err1;
359 
360 	status = ps2_service_init();
361 	if (status < B_OK)
362 		goto err2;
363 
364 	status = install_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt,
365 		NULL, 0);
366 	if (status)
367 		goto err3;
368 
369 	status = install_io_interrupt_handler(INT_PS2_MOUSE, &ps2_interrupt, NULL,
370 		0);
371 	if (status)
372 		goto err4;
373 
374 	// While this might have fixed bug #1185, we can't do this unconditionally
375 	// as it obviously messes up many controllers which couldn't reboot anymore
376 	// after that
377 	//ps2_selftest();
378 
379 	// Setup the command byte with disabled keyboard and AUX interrupts
380 	// to prevent interrupts storm on some KBCs during active multiplexing
381 	// activation procedure. Fixes #7635.
382 	status = ps2_setup_command_byte(false);
383 	if (status) {
384 		INFO("ps2: initial setup of command byte failed\n");
385 		goto err5;
386 	}
387 
388 	ps2_flush();
389 	status = ps2_setup_active_multiplexing(&gActiveMultiplexingEnabled);
390 	if (status) {
391 		INFO("ps2: setting up active multiplexing failed\n");
392 		goto err5;
393 	}
394 
395 	status = ps2_setup_command_byte(true);
396 	if (status) {
397 		INFO("ps2: setting up command byte with enabled interrupts failed\n");
398 		goto err5;
399 	}
400 
401 	if (gActiveMultiplexingEnabled) {
402 		if (ps2_dev_command_timeout(&ps2_device[PS2_DEVICE_MOUSE],
403 				PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0, 100000)
404 			== B_TIMED_OUT) {
405 			INFO("ps2: accessing multiplexed mouse port 0 timed out, ignoring it!\n");
406 		} else {
407 			ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE]);
408 		}
409 		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE + 1]);
410 		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE + 2]);
411 		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE + 3]);
412 		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_KEYB]);
413 	} else {
414 		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE]);
415 		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_KEYB]);
416 	}
417 
418 	TRACE("ps2: init done!\n");
419 	return B_OK;
420 
421 err5:
422 	remove_io_interrupt_handler(INT_PS2_MOUSE, &ps2_interrupt, NULL);
423 err4:
424 	remove_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL);
425 err3:
426 	ps2_service_exit();
427 err2:
428 	ps2_dev_exit();
429 err1:
430 	delete_sem(gControllerSem);
431 	put_module(B_ISA_MODULE_NAME);
432 	TRACE("ps2: init failed!\n");
433 	return B_ERROR;
434 }
435 
436 
437 void
438 ps2_uninit(void)
439 {
440 	TRACE("ps2: uninit\n");
441 	remove_io_interrupt_handler(INT_PS2_MOUSE,    &ps2_interrupt, NULL);
442 	remove_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL);
443 	ps2_service_exit();
444 	ps2_dev_exit();
445 	delete_sem(gControllerSem);
446 	put_module(B_ISA_MODULE_NAME);
447 }
448