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