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