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
31isa_module_info *gIsa = NULL;
32bool gActiveMultiplexingEnabled = false;
33bool gSetupComplete = false;
34sem_id gControllerSem;
35
36static int32 sIgnoreInterrupts = 0;
37
38
39uint8
40ps2_read_ctrl(void)
41{
42	return gIsa->read_io_8(PS2_PORT_CTRL);
43}
44
45
46uint8
47ps2_read_data(void)
48{
49	return gIsa->read_io_8(PS2_PORT_DATA);
50}
51
52
53void
54ps2_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
62void
63ps2_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
71status_t
72ps2_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
84status_t
85ps2_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
100void
101ps2_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
124static status_t
125ps2_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
139static status_t
140ps2_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
167static status_t
168ps2_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
214no_support:
215	TRACE("ps2: active multiplexing not supported\n");
216	*enabled = false;
217
218done:
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
230fail:
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
239status_t
240ps2_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
290static int32
291ps2_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
347status_t
348ps2_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
433err5:
434	remove_io_interrupt_handler(INT_PS2_MOUSE, &ps2_interrupt, NULL);
435err4:
436	remove_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL);
437err3:
438	ps2_service_exit();
439err2:
440	ps2_dev_exit();
441err1:
442	delete_sem(gControllerSem);
443	put_module(B_ISA_MODULE_NAME);
444	TRACE("ps2: init failed!\n");
445	return B_ERROR;
446}
447
448
449void
450ps2_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