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
21isa_module_info *gIsa = NULL;
22bool gActiveMultiplexingEnabled = false;
23sem_id gControllerSem;
24
25static int32 sIgnoreInterrupts = 0;
26
27
28uint8
29ps2_read_ctrl(void)
30{
31	return gIsa->read_io_8(PS2_PORT_CTRL);
32}
33
34
35uint8
36ps2_read_data(void)
37{
38	return gIsa->read_io_8(PS2_PORT_DATA);
39}
40
41
42void
43ps2_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
51void
52ps2_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
60status_t
61ps2_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
73status_t
74ps2_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
89void
90ps2_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
113static status_t
114ps2_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
128static status_t
129ps2_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
156static status_t
157ps2_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
200no_support:
201	TRACE("ps2: active multiplexing not supported\n");
202	*enabled = false;
203
204done:
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
216fail:
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
225status_t
226ps2_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
276static int32
277ps2_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
333status_t
334ps2_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], 0xe6,
395				NULL, 0, NULL, 0, 100000) == B_TIMED_OUT) {
396			INFO("ps2: accessing multiplexed mouse port 0 timed out, ignoring it!\n");
397		} else {
398			ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE]);
399		}
400		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE + 1]);
401		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE + 2]);
402		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE + 3]);
403		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_KEYB]);
404	} else {
405		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE]);
406		ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_KEYB]);
407	}
408
409	TRACE("ps2: init done!\n");
410	return B_OK;
411
412err5:
413	remove_io_interrupt_handler(INT_PS2_MOUSE, &ps2_interrupt, NULL);
414err4:
415	remove_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL);
416err3:
417	ps2_service_exit();
418err2:
419	ps2_dev_exit();
420err1:
421	delete_sem(gControllerSem);
422	put_module(B_ISA_MODULE_NAME);
423	TRACE("ps2: init failed!\n");
424	return B_ERROR;
425}
426
427
428void
429ps2_uninit(void)
430{
431	TRACE("ps2: uninit\n");
432	remove_io_interrupt_handler(INT_PS2_MOUSE,    &ps2_interrupt, NULL);
433	remove_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL);
434	ps2_service_exit();
435	ps2_dev_exit();
436	delete_sem(gControllerSem);
437	put_module(B_ISA_MODULE_NAME);
438}
439