xref: /haiku/src/add-ons/kernel/bus_managers/ps2/ps2_dev.cpp (revision 17889a8c70dbb3d59c1412f6431968753c767bab)
1 /*
2  * Copyright 2005-2014 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors (in chronological order):
6  *		Marcus Overhagen (marcus@overhagen.de)
7  *		Clemens Zeidler (haiku@clemens-zeidler.de)
8  */
9 
10 
11 /*!	PS/2 bus manager */
12 
13 #include "ps2_dev.h"
14 #include "ps2_service.h"
15 
16 #include "ps2_alps.h"
17 #include "ps2_elantech.h"
18 #include "ps2_standard_mouse.h"
19 #include "ps2_synaptics.h"
20 #include "ps2_trackpoint.h"
21 
22 #include <fs/devfs.h>
23 
24 #include <string.h>
25 
26 
27 //#define TRACE_PS2_DEV
28 #ifdef TRACE_PS2_DEV
29 #	define TRACE(x...) dprintf(x)
30 #else
31 #	define TRACE(x...)
32 #endif
33 
34 
35 ps2_dev ps2_device[PS2_DEVICE_COUNT];
36 
37 
38 status_t
39 ps2_reset_mouse(ps2_dev* dev)
40 {
41 	uint8 data[2];
42 	status_t status;
43 
44 	TRACE("ps2: ps2_reset_mouse\n");
45 
46 	status = ps2_dev_command(dev, PS2_CMD_RESET, NULL, 0, data, 2);
47 
48 	if (status == B_OK && data[0] == 0xFE && data[1] == 0xAA) {
49 		// workaround for HP/Compaq KBCs timeout condition. #2867 #3594 #4315
50 		TRACE("ps2: KBC has timed out the mouse reset request. "
51 			"Response was: 0x%02x 0x%02x. Requesting the answer data.\n",
52 			data[0], data[1]);
53 		status = ps2_dev_command(dev, PS2_CMD_RESEND, NULL, 0, data, 2);
54 	}
55 
56 	if (status == B_OK && (data[0] != 0xAA || data[1] != 0x00)) {
57 		TRACE("ps2: reset mouse failed, response was: 0x%02x 0x%02x\n",
58 			data[0], data[1]);
59 		status = B_ERROR;
60 	} else if (status != B_OK) {
61 		TRACE("ps2: reset mouse failed\n");
62 	} else {
63 		TRACE("ps2: reset mouse success\n");
64 	}
65 
66 	return status;
67 }
68 
69 
70 status_t
71 ps2_dev_detect_pointing(ps2_dev* dev, device_hooks** hooks)
72 {
73 	status_t status = ps2_reset_mouse(dev);
74 	if (status != B_OK) {
75 		INFO("ps2_dev: reset failed: %s\n", strerror(status));
76 		return B_ERROR;
77 	}
78 
79 	// probe devices
80 	// the probe function has to set the dev name and the dev packet size
81 
82 	status = probe_trackpoint(dev);
83 	if (status == B_OK) {
84 		*hooks = &gStandardMouseDeviceHooks;
85 		goto dev_found;
86 	}
87 
88 	status = probe_synaptics(dev);
89 	if (status == B_OK) {
90 		*hooks = &gSynapticsDeviceHooks;
91 		goto dev_found;
92 	}
93 
94 	status = probe_alps(dev);
95 	if (status == B_OK) {
96 		*hooks = &gALPSDeviceHooks;
97 		goto dev_found;
98 	}
99 
100 	status = probe_elantech(dev);
101 	if (status == B_OK) {
102 		*hooks = &gElantechDeviceHooks;
103 		goto dev_found;
104 	}
105 
106 	// reset the mouse for the case that the previous probes left the mouse in
107 	// a undefined state
108 	status = ps2_reset_mouse(dev);
109 	if (status != B_OK) {
110 		INFO("ps2: reset after probe failed\n");
111 		return B_ERROR;
112 	}
113 
114 	status = probe_standard_mouse(dev);
115 	if (status == B_OK) {
116 		*hooks = &gStandardMouseDeviceHooks;
117 		goto dev_found;
118 	}
119 
120 	return B_ERROR;
121 
122 dev_found:
123 	if (dev == &(ps2_device[PS2_DEVICE_SYN_PASSTHROUGH]))
124 		synaptics_pass_through_set_packet_size(dev, dev->packet_size);
125 
126 	return B_OK;
127 }
128 
129 
130 status_t
131 ps2_dev_init(void)
132 {
133 	ps2_device[0].name = "input/mouse/ps2/0";
134 	ps2_device[0].active = false;
135 	ps2_device[0].idx = 0;
136 	ps2_device[0].result_sem = -1;
137 	ps2_device[0].command = standard_command_timeout;
138 
139 	ps2_device[1].name = "input/mouse/ps2/1";
140 	ps2_device[1].active = false;
141 	ps2_device[1].idx = 1;
142 	ps2_device[1].result_sem = -1;
143 	ps2_device[1].command = standard_command_timeout;
144 
145 	ps2_device[2].name = "input/mouse/ps2/2";
146 	ps2_device[2].active = false;
147 	ps2_device[2].idx = 2;
148 	ps2_device[2].result_sem = -1;
149 	ps2_device[2].command = standard_command_timeout;
150 
151 	ps2_device[3].name = "input/mouse/ps2/3";
152 	ps2_device[3].active = false;
153 	ps2_device[3].idx = 3;
154 	ps2_device[3].result_sem = -1;
155 	ps2_device[3].command = standard_command_timeout;
156 
157 	ps2_device[4].name = "input/mouse/ps2/synaptics_passthrough";
158 	ps2_device[4].active = false;
159 	ps2_device[4].result_sem = -1;
160 	ps2_device[4].command = passthrough_command;
161 
162 	ps2_device[5].name = "input/keyboard/at/0";
163 	ps2_device[5].active = false;
164 	ps2_device[5].result_sem = -1;
165 	ps2_device[5].flags = PS2_FLAG_KEYB;
166 	ps2_device[5].command = standard_command_timeout;
167 
168 	int i;
169 	for (i = 0; i < PS2_DEVICE_COUNT; i++) {
170 		ps2_dev* dev = &ps2_device[i];
171 		dev->result_sem = create_sem(0, "ps2 result");
172 		if (dev->result_sem < 0)
173 			goto err;
174 	}
175 	return B_OK;
176 
177 err:
178 	ps2_dev_exit();
179 
180 	return B_ERROR;
181 }
182 
183 
184 void
185 ps2_dev_exit(void)
186 {
187 	for (int i = 0; i < PS2_DEVICE_COUNT; i++) {
188 		ps2_dev* dev = &ps2_device[i];
189 		if (dev->result_sem >= 0) {
190 			delete_sem(dev->result_sem);
191 			dev->result_sem = -1;
192 		}
193 	}
194 }
195 
196 
197 void
198 ps2_dev_publish(ps2_dev* dev)
199 {
200 	status_t status = B_OK;
201 	TRACE("ps2: ps2_dev_publish %s\n", dev->name);
202 
203 	if (dev->active)
204 		return;
205 
206 	if (atomic_get(&dev->flags) & PS2_FLAG_KEYB) {
207 		status = devfs_publish_device(dev->name, &gKeyboardDeviceHooks);
208 	} else {
209 		// Check if this is the "pass-through" device and wait until
210 		// the parent_dev goes to enabled state. It is required to prevent
211 		// from messing up the Synaptics command sequences in synaptics_open.
212 		if (dev->parent_dev) {
213 			const bigtime_t timeout = 2000000;
214 			bigtime_t start = system_time();
215 			while (!(atomic_get(&dev->parent_dev->flags) & PS2_FLAG_ENABLED)) {
216 				if ((system_time() - start) > timeout) {
217 					status = B_BUSY;
218 					break;
219 				}
220 				snooze(timeout / 20);
221 			}
222 			TRACE("ps2: publishing %s: parent %s is %s; wait time %" B_PRId64
223 				"\n", dev->name, dev->parent_dev->name,
224 				status == B_OK ? "enabled" : "busy", system_time() - start);
225 		}
226 
227 		if (status == B_OK) {
228 			device_hooks* hooks;
229 			status = ps2_dev_detect_pointing(dev, &hooks);
230 			if (status == B_OK) {
231 				status = devfs_publish_device(dev->name, hooks);
232 			}
233 		}
234 	}
235 
236 	dev->active = true;
237 
238 	INFO("ps2: devfs_publish_device %s, status = 0x%08" B_PRIx32 "\n",
239 		dev->name, status);
240 }
241 
242 
243 void
244 ps2_dev_unpublish(ps2_dev* dev)
245 {
246 	status_t status;
247 	TRACE("ps2: ps2_dev_unpublish %s\n", dev->name);
248 
249 	if (!dev->active)
250 		return;
251 
252 	dev->active = false;
253 
254 	status = devfs_unpublish_device(dev->name, true);
255 
256 	if ((dev->flags & PS2_FLAG_ENABLED) && dev->disconnect)
257 		dev->disconnect(dev);
258 
259 	INFO("ps2: devfs_unpublish_device %s, status = 0x%08" B_PRIx32 "\n",
260 		dev->name, status);
261 }
262 
263 
264 int32
265 ps2_dev_handle_int(ps2_dev* dev)
266 {
267 	const uint8 data = dev->history[0].data;
268 	uint32 flags;
269 
270 	flags = atomic_get(&dev->flags);
271 
272 	if (flags & PS2_FLAG_CMD) {
273 		if ((flags & (PS2_FLAG_ACK | PS2_FLAG_NACK)) == 0) {
274 			int cnt = 1;
275 			if (data == PS2_REPLY_ACK) {
276 				atomic_or(&dev->flags, PS2_FLAG_ACK);
277 			} else if (data == PS2_REPLY_RESEND || data == PS2_REPLY_ERROR) {
278 				atomic_or(&dev->flags, PS2_FLAG_NACK);
279 			} else if ((flags & PS2_FLAG_GETID)
280 				&& (data == 0 || data == 3 || data == 4)) {
281 				// workaround for broken mice that don't ack the "get id"
282 				// command
283 				TRACE("ps2: ps2_dev_handle_int: mouse didn't ack the 'get id' "
284 					"command\n");
285 				atomic_or(&dev->flags, PS2_FLAG_ACK);
286 				if (dev->result_buf_cnt) {
287 					dev->result_buf[dev->result_buf_idx] = data;
288 					dev->result_buf_idx++;
289 					dev->result_buf_cnt--;
290 					if (dev->result_buf_cnt == 0) {
291 						atomic_and(&dev->flags, ~PS2_FLAG_CMD);
292 						cnt++;
293 					}
294 				}
295 			} else if ((flags & PS2_FLAG_RESEND)) {
296 				TRACE("ps2: ps2_dev_handle_int: processing RESEND request\n");
297 				atomic_or(&dev->flags, PS2_FLAG_ACK);
298 				if (dev->result_buf_cnt) {
299 					dev->result_buf[dev->result_buf_idx] = data;
300 					dev->result_buf_idx++;
301 					dev->result_buf_cnt--;
302 					if (dev->result_buf_cnt == 0) {
303 						atomic_and(&dev->flags, ~PS2_FLAG_CMD);
304 						cnt++;
305 					}
306 				}
307 			} else {
308 //				TRACE("ps2: ps2_dev_handle_int unexpected data 0x%02x while "
309 //					"waiting for ack\n", data);
310 				TRACE("ps2: int1 %02x\n", data);
311 				goto pass_to_handler;
312 			}
313 			release_sem_etc(dev->result_sem, cnt, B_DO_NOT_RESCHEDULE);
314 			return B_INVOKE_SCHEDULER;
315 		} else if (dev->result_buf_cnt) {
316 			dev->result_buf[dev->result_buf_idx] = data;
317 			dev->result_buf_idx++;
318 			dev->result_buf_cnt--;
319 			if (dev->result_buf_cnt == 0) {
320 				atomic_and(&dev->flags, ~PS2_FLAG_CMD);
321 				release_sem_etc(dev->result_sem, 1, B_DO_NOT_RESCHEDULE);
322 				return B_INVOKE_SCHEDULER;
323 			}
324 		} else {
325 //			TRACE("ps2: ps2_dev_handle_int unexpected data 0x%02x during "
326 //				"command processing\n", data);
327 			TRACE("ps2: int2 %02x\n", data);
328 			goto pass_to_handler;
329 		}
330 		return B_HANDLED_INTERRUPT;
331 	}
332 
333 pass_to_handler:
334 
335 	if ((flags & PS2_FLAG_KEYB) == 0) {
336 		if (dev->history[0].error && data == 0xfd) {
337 			INFO("ps2: hot removal of %s\n", dev->name);
338 			ps2_service_notify_device_removed(dev);
339 			return B_INVOKE_SCHEDULER;
340 		}
341 		if (data == 0x00 && dev->history[1].data == 0xaa
342 			&& (dev->history[0].time - dev->history[1].time) < 50000) {
343 			INFO("ps2: hot plugin of %s\n", dev->name);
344 			if (dev->active) {
345 				INFO("ps2: device %s still active, republishing...\n",
346 					dev->name);
347 				ps2_service_notify_device_republish(dev);
348 			} else {
349 				ps2_service_notify_device_added(dev);
350 			}
351 			return B_INVOKE_SCHEDULER;
352 		}
353 	}
354 
355 	if (!dev->active) {
356 		TRACE("ps2: %s not active, data 0x%02x dropped\n", dev->name, data);
357 		if (gSetupComplete && data != 0x00 && data != 0xaa) {
358 			INFO("ps2: possibly a hot plugin of %s\n", dev->name);
359 			ps2_service_notify_device_added(dev);
360 			return B_INVOKE_SCHEDULER;
361 		}
362 		return B_HANDLED_INTERRUPT;
363 	}
364 
365 	if ((flags & PS2_FLAG_ENABLED) == 0) {
366 		TRACE("ps2: %s not enabled, data 0x%02x dropped\n", dev->name, data);
367 		return B_HANDLED_INTERRUPT;
368 	}
369 
370 	return dev->handle_int(dev);
371 }
372 
373 
374 status_t
375 standard_command_timeout(ps2_dev* dev, uint8 cmd, const uint8* out,
376 	int out_count, uint8* in, int in_count, bigtime_t timeout)
377 {
378 	status_t res;
379 #ifdef TRACE_PS2_DEV
380 	bigtime_t start;
381 #endif
382 	int32 sem_count;
383 	int i;
384 
385 	TRACE("ps2: ps2_dev_command cmd 0x%02x, out-count %d, in-count %d, "
386 		"dev %s\n", cmd, out_count, in_count, dev->name);
387 	for (i = 0; i < out_count; i++)
388 		TRACE("ps2: ps2_dev_command tx: 0x%02x\n", out[i]);
389 
390 	res = get_sem_count(dev->result_sem, &sem_count);
391 	if (res == B_OK && sem_count != 0) {
392 		TRACE("ps2: ps2_dev_command: sem_count %" B_PRId32 ", fixing!\n",
393 			sem_count);
394 		if (sem_count > 0)
395 			acquire_sem_etc(dev->result_sem, sem_count, 0, 0);
396 		else
397 			release_sem_etc(dev->result_sem, -sem_count, 0);
398 	}
399 
400 	dev->result_buf_cnt = in_count;
401 	dev->result_buf_idx = 0;
402 	dev->result_buf = in;
403 
404 	res = B_OK;
405 	for (i = -1; res == B_OK && i < out_count; i++) {
406 
407 		atomic_and(&dev->flags,
408 			~(PS2_FLAG_ACK | PS2_FLAG_NACK | PS2_FLAG_GETID | PS2_FLAG_RESEND));
409 
410 		acquire_sem(gControllerSem);
411 
412 		if (!(atomic_get(&dev->flags) & PS2_FLAG_KEYB)) {
413 			uint8 prefix_cmd;
414 			if (gActiveMultiplexingEnabled)
415 				prefix_cmd = 0x90 + dev->idx;
416 			else
417 				prefix_cmd = 0xd4;
418 			res = ps2_wait_write();
419 			if (res == B_OK)
420 				ps2_write_ctrl(prefix_cmd);
421 		}
422 
423 		res = ps2_wait_write();
424 		if (res == B_OK) {
425 			if (i == -1) {
426 				if (cmd == PS2_CMD_GET_DEVICE_ID)
427 					atomic_or(&dev->flags, PS2_FLAG_CMD | PS2_FLAG_GETID);
428 				else if (cmd == PS2_CMD_RESEND)
429 					atomic_or(&dev->flags, PS2_FLAG_CMD | PS2_FLAG_RESEND);
430 				else
431 					atomic_or(&dev->flags, PS2_FLAG_CMD);
432 				ps2_write_data(cmd);
433 			} else {
434 				ps2_write_data(out[i]);
435 			}
436 		}
437 
438 		release_sem(gControllerSem);
439 #ifdef TRACE_PS2_DEV
440 		start = system_time();
441 #endif
442 		res = acquire_sem_etc(dev->result_sem, 1, B_RELATIVE_TIMEOUT, timeout);
443 
444 		if (res != B_OK)
445 			atomic_and(&dev->flags, ~PS2_FLAG_CMD);
446 
447 #ifdef TRACE_PS2_DEV
448 		TRACE("ps2: ps2_dev_command wait for ack res 0x%08" B_PRIx32 ", "
449 			"wait-time %" B_PRId64 "\n", res, system_time() - start);
450 #endif
451 		if (atomic_get(&dev->flags) & PS2_FLAG_ACK) {
452 			TRACE("ps2: ps2_dev_command got ACK\n");
453 		}
454 
455 		if (atomic_get(&dev->flags) & PS2_FLAG_NACK) {
456 			atomic_and(&dev->flags, ~PS2_FLAG_CMD);
457 			res = B_IO_ERROR;
458 			TRACE("ps2: ps2_dev_command got NACK\n");
459 		}
460 
461 		if (res != B_OK)
462 			break;
463 	}
464 
465 	if (res == B_OK) {
466 		if (in_count == 0) {
467 			atomic_and(&dev->flags, ~PS2_FLAG_CMD);
468 		} else {
469 #ifdef TRACE_PS2_DEV
470 			start = system_time();
471 #endif
472 			res = acquire_sem_etc(dev->result_sem, 1, B_RELATIVE_TIMEOUT,
473 				timeout);
474 
475 			atomic_and(&dev->flags, ~PS2_FLAG_CMD);
476 
477 			if (dev->result_buf_cnt != 0) {
478 				TRACE("ps2: ps2_dev_command error: %d rx bytes not received\n",
479 					dev->result_buf_cnt);
480 				in_count -= dev->result_buf_cnt;
481 				dev->result_buf_cnt = 0;
482 				res = B_IO_ERROR;
483 			}
484 
485 #ifdef TRACE_PS2_DEV
486 			TRACE("ps2: ps2_dev_command wait for input res 0x%08" B_PRIx32 ", "
487 				"wait-time %" B_PRId64 "\n", res, system_time() - start);
488 			for (i = 0; i < in_count; i++)
489 				TRACE("ps2: ps2_dev_command rx: 0x%02x\n", in[i]);
490 #endif
491 		}
492 	}
493 
494 	TRACE("ps2: ps2_dev_command result: %s\n", strerror(res));
495 
496 	return res;
497 }
498 
499 
500 status_t
501 ps2_dev_command(ps2_dev* dev, uint8 cmd, const uint8* out, int out_count,
502 	uint8* in, int in_count)
503 {
504 	return ps2_dev_command_timeout(dev, cmd, out, out_count, in, in_count,
505 		4000000);
506 }
507 
508 
509 status_t
510 ps2_dev_command_timeout(ps2_dev* dev, uint8 cmd, const uint8* out,
511 	int out_count, uint8* in, int in_count, bigtime_t timeout)
512 {
513 	return dev->command(dev, cmd, out, out_count, in, in_count, timeout);
514 }
515 
516 
517 status_t
518 ps2_dev_sliced_command(ps2_dev* dev, uint8 cmd)
519 {
520 	uint8 val;
521 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_SCALE11) != B_OK)
522 		return B_ERROR;
523 
524 	val = (cmd >> 6) & 3;
525 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK)
526 		return B_ERROR;
527 
528 	val = (cmd >> 4) & 3;
529 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK)
530 		return B_ERROR;
531 
532 	val = (cmd >> 2) & 3;
533 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK)
534 		return B_ERROR;
535 
536 	val = cmd & 3;
537 	if (ps2_dev_command(dev, PS2_CMD_MOUSE_SET_RES, &val, 1) != B_OK)
538 		return B_ERROR;
539 
540 	return B_OK;
541 }
542