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