xref: /freebsd/usr.sbin/bluetooth/bthidd/hid.c (revision 62cfcf62f627e5093fb37026a6d8c98e4d2ef04c)
1 /*
2  * hid.c
3  */
4 
5 /*-
6  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
7  *
8  * Copyright (c) 2006 Maksim Yevmenkin <m_evmenkin@yahoo.com>
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $Id: hid.c,v 1.5 2006/09/07 21:06:53 max Exp $
33  * $FreeBSD$
34  */
35 
36 #include <sys/consio.h>
37 #include <sys/mouse.h>
38 #include <sys/queue.h>
39 #include <assert.h>
40 #define L2CAP_SOCKET_CHECKED
41 #include <bluetooth.h>
42 #include <dev/usb/usb.h>
43 #include <dev/usb/usbhid.h>
44 #include <errno.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <syslog.h>
49 #include <unistd.h>
50 #include <usbhid.h>
51 #include "bthid_config.h"
52 #include "bthidd.h"
53 #include "btuinput.h"
54 #include "kbd.h"
55 
56 /*
57  * Inoffical and unannounced report ids for Apple Mice and trackpad
58  */
59 #define TRACKPAD_REPORT_ID	0x28
60 #define AMM_REPORT_ID		0x29
61 #define BATT_STAT_REPORT_ID	0x30
62 #define BATT_STRENGTH_REPORT_ID	0x47
63 #define SURFACE_REPORT_ID	0x61
64 
65 /*
66  * Apple magic mouse (AMM) specific device state
67  */
68 #define AMM_MAX_BUTTONS 16
69 struct apple_state {
70 	int	y   [AMM_MAX_BUTTONS];
71 	int	button_state;
72 };
73 
74 #define MAGIC_MOUSE(D) (((D)->vendor_id == 0x5ac) && ((D)->product_id == 0x30d))
75 #define AMM_BASIC_BLOCK   5
76 #define AMM_FINGER_BLOCK  8
77 #define AMM_VALID_REPORT(L) (((L) >= AMM_BASIC_BLOCK) && \
78     ((L) <= 16*AMM_FINGER_BLOCK    + AMM_BASIC_BLOCK) && \
79     ((L)  % AMM_FINGER_BLOCK)     == AMM_BASIC_BLOCK)
80 #define AMM_WHEEL_SPEED 100
81 
82 /*
83  * Probe for per-device initialisation
84  */
85 void
86 hid_initialise(bthid_session_p s)
87 {
88 	hid_device_p hid_device = get_hid_device(&s->bdaddr);
89 
90 	if (hid_device && MAGIC_MOUSE(hid_device)) {
91 		/* Magic report to enable trackpad on Apple's Magic Mouse */
92 		static uint8_t rep[] = {0x53, 0xd7, 0x01};
93 
94 		if ((s->ctx = calloc(1, sizeof(struct apple_state))) == NULL)
95 			return;
96 		write(s->ctrl, rep, 3);
97 	}
98 }
99 
100 /*
101  * Process data from control channel
102  */
103 
104 int32_t
105 hid_control(bthid_session_p s, uint8_t *data, int32_t len)
106 {
107 	assert(s != NULL);
108 	assert(data != NULL);
109 	assert(len > 0);
110 
111 	switch (data[0] >> 4) {
112         case 0: /* Handshake (response to command) */
113 		if (data[0] & 0xf)
114 			syslog(LOG_ERR, "Got handshake message with error " \
115 				"response 0x%x from %s",
116 				data[0], bt_ntoa(&s->bdaddr, NULL));
117 		break;
118 
119 	case 1: /* HID Control */
120 		switch (data[0] & 0xf) {
121 		case 0: /* NOP */
122 			break;
123 
124 		case 1: /* Hard reset */
125 		case 2: /* Soft reset */
126 			syslog(LOG_WARNING, "Device %s requested %s reset",
127 				bt_ntoa(&s->bdaddr, NULL),
128 				((data[0] & 0xf) == 1)? "hard" : "soft");
129 			break;
130 
131 		case 3: /* Suspend */
132 			syslog(LOG_NOTICE, "Device %s requested Suspend",
133 				bt_ntoa(&s->bdaddr, NULL));
134 			break;
135 
136 		case 4: /* Exit suspend */
137 			syslog(LOG_NOTICE, "Device %s requested Exit Suspend",
138 				bt_ntoa(&s->bdaddr, NULL));
139 			break;
140 
141 		case 5: /* Virtual cable unplug */
142 			syslog(LOG_NOTICE, "Device %s unplugged virtual cable",
143 				bt_ntoa(&s->bdaddr, NULL));
144 			session_close(s);
145 			break;
146 
147 		default:
148 			syslog(LOG_WARNING, "Device %s sent unknown " \
149                                 "HID_Control message 0x%x",
150 				bt_ntoa(&s->bdaddr, NULL), data[0]);
151 			break;
152 		}
153 		break;
154 
155 	default:
156 		syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \
157 			"channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL));
158 		break;
159 	}
160 
161 	return (0);
162 }
163 
164 /*
165  * Process data from the interrupt channel
166  */
167 
168 int32_t
169 hid_interrupt(bthid_session_p s, uint8_t *data, int32_t len)
170 {
171 	hid_device_p	hid_device;
172 	hid_data_t	d;
173 	hid_item_t	h;
174 	int32_t		report_id, usage, page, val,
175 			mouse_x, mouse_y, mouse_z, mouse_t, mouse_butt,
176 			mevents, kevents, i;
177 
178 	assert(s != NULL);
179 	assert(s->srv != NULL);
180 	assert(data != NULL);
181 
182 	if (len < 3) {
183 		syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \
184 			"channel from %s", len, bt_ntoa(&s->bdaddr, NULL));
185 		return (-1);
186 	}
187 
188 	if (data[0] != 0xa1) {
189 		syslog(LOG_ERR, "Got unexpected message 0x%x on " \
190 			"Interrupt channel from %s",
191 			data[0], bt_ntoa(&s->bdaddr, NULL));
192 		return (-1);
193 	}
194 
195 	report_id = data[1];
196 	data ++;
197 	len --;
198 
199 	hid_device = get_hid_device(&s->bdaddr);
200 	assert(hid_device != NULL);
201 
202 	mouse_x = mouse_y = mouse_z = mouse_t = mouse_butt = 0;
203 	mevents = kevents = 0;
204 
205 	for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1);
206 	     hid_get_item(d, &h) > 0; ) {
207 		if ((h.flags & HIO_CONST) || (h.report_ID != report_id) ||
208 		    (h.kind != hid_input))
209 			continue;
210 
211 		page = HID_PAGE(h.usage);
212 		val = hid_get_data(data, &h);
213 
214 		/*
215 		 * When the input field is an array and the usage is specified
216 		 * with a range instead of an ID, we have to derive the actual
217 		 * usage by using the item value as an index in the usage range
218 		 * list.
219 		 */
220 		if ((h.flags & HIO_VARIABLE)) {
221 			usage = HID_USAGE(h.usage);
222 		} else {
223 			const uint32_t usage_offset = val - h.logical_minimum;
224 			usage = HID_USAGE(h.usage_minimum + usage_offset);
225 		}
226 
227 		switch (page) {
228 		case HUP_GENERIC_DESKTOP:
229 			switch (usage) {
230 			case HUG_X:
231 				mouse_x = val;
232 				mevents ++;
233 				break;
234 
235 			case HUG_Y:
236 				mouse_y = val;
237 				mevents ++;
238 				break;
239 
240 			case HUG_WHEEL:
241 				mouse_z = -val;
242 				mevents ++;
243 				break;
244 
245 			case HUG_SYSTEM_SLEEP:
246 				if (val)
247 					syslog(LOG_NOTICE, "Sleep button pressed");
248 				break;
249 			}
250 			break;
251 
252 		case HUP_KEYBOARD:
253 			kevents ++;
254 
255 			if (h.flags & HIO_VARIABLE) {
256 				if (val && usage < kbd_maxkey())
257 					bit_set(s->keys1, usage);
258 			} else {
259 				if (val && val < kbd_maxkey())
260 					bit_set(s->keys1, val);
261 
262 				for (i = 1; i < h.report_count; i++) {
263 					h.pos += h.report_size;
264 					val = hid_get_data(data, &h);
265 					if (val && val < kbd_maxkey())
266 						bit_set(s->keys1, val);
267 				}
268 			}
269 			break;
270 
271 		case HUP_BUTTON:
272 			if (usage != 0) {
273 				if (usage == 2)
274 					usage = 3;
275 				else if (usage == 3)
276 					usage = 2;
277 
278 				mouse_butt |= (val << (usage - 1));
279 				mevents ++;
280 			}
281 			break;
282 
283 		case HUP_CONSUMER:
284 			if (hid_device->keyboard && s->srv->uinput) {
285 				if (h.flags & HIO_VARIABLE) {
286 					uinput_rep_cons(s->ukbd, usage, !!val);
287 				} else {
288 					if (s->consk > 0)
289 						uinput_rep_cons(s->ukbd,
290 						    s->consk, 0);
291 					if (uinput_rep_cons(s->ukbd, val, 1)
292 					    == 0)
293 						s->consk = val;
294 				}
295 			}
296 
297 			if (!val)
298 				break;
299 
300 			switch (usage) {
301 			case HUC_AC_PAN:
302 				/* Horizontal scroll */
303 				mouse_t = val;
304 				mevents ++;
305 				val = 0;
306 				break;
307 
308 			case 0xb5: /* Scan Next Track */
309 				val = 0x19;
310 				break;
311 
312 			case 0xb6: /* Scan Previous Track */
313 				val = 0x10;
314 				break;
315 
316 			case 0xb7: /* Stop */
317 				val = 0x24;
318 				break;
319 
320 			case 0xcd: /* Play/Pause */
321 				val = 0x22;
322 				break;
323 
324 			case 0xe2: /* Mute */
325 				val = 0x20;
326 				break;
327 
328 			case 0xe9: /* Volume Up */
329 				val = 0x30;
330 				break;
331 
332 			case 0xea: /* Volume Down */
333 				val = 0x2E;
334 				break;
335 
336 			case 0x183: /* Media Select */
337 				val = 0x6D;
338 				break;
339 
340 			case 0x018a: /* Mail */
341 				val = 0x6C;
342 				break;
343 
344 			case 0x192: /* Calculator */
345 				val = 0x21;
346 				break;
347 
348 			case 0x194: /* My Computer */
349 				val = 0x6B;
350 				break;
351 
352 			case 0x221: /* WWW Search */
353 				val = 0x65;
354 				break;
355 
356 			case 0x223: /* WWW Home */
357 				val = 0x32;
358 				break;
359 
360 			case 0x224: /* WWW Back */
361 				val = 0x6A;
362 				break;
363 
364 			case 0x225: /* WWW Forward */
365 				val = 0x69;
366 				break;
367 
368 			case 0x226: /* WWW Stop */
369 				val = 0x68;
370 				break;
371 
372 			case 0x227: /* WWW Refresh */
373 				val = 0x67;
374 				break;
375 
376 			case 0x22a: /* WWW Favorites */
377 				val = 0x66;
378 				break;
379 
380 			default:
381 				val = 0;
382 				break;
383 			}
384 
385 			/* XXX FIXME - UGLY HACK */
386 			if (val != 0) {
387 				if (hid_device->keyboard) {
388 					int32_t	buf[4] = { 0xe0, val,
389 							   0xe0, val|0x80 };
390 
391 					assert(s->vkbd != -1);
392 					write(s->vkbd, buf, sizeof(buf));
393 				} else
394 					syslog(LOG_ERR, "Keyboard events " \
395 						"received from non-keyboard " \
396 						"device %s. Please report",
397 						bt_ntoa(&s->bdaddr, NULL));
398 			}
399 			break;
400 
401 		case HUP_MICROSOFT:
402 			switch (usage) {
403 			case 0xfe01:
404 				if (!hid_device->battery_power)
405 					break;
406 
407 				switch (val) {
408 				case 1:
409 					syslog(LOG_INFO, "Battery is OK on %s",
410 						bt_ntoa(&s->bdaddr, NULL));
411 					break;
412 
413 				case 2:
414 					syslog(LOG_NOTICE, "Low battery on %s",
415 						bt_ntoa(&s->bdaddr, NULL));
416 					break;
417 
418 				case 3:
419 					syslog(LOG_WARNING, "Very low battery "\
420                                                 "on %s",
421 						bt_ntoa(&s->bdaddr, NULL));
422 					break;
423                                 }
424 				break;
425 			}
426 			break;
427 		}
428 	}
429 	hid_end_parse(d);
430 
431 	/*
432 	 * Apple adheres to no standards and sends reports it does
433 	 * not introduce in its hid descriptor for its magic mouse.
434 	 * Handle those reports here.
435 	 */
436 	if (MAGIC_MOUSE(hid_device) && s->ctx) {
437 		struct apple_state *c = (struct apple_state *)s->ctx;
438 		int firm = 0, middle = 0;
439 		int16_t v;
440 
441 		data++, len--;		/* Chomp report_id */
442 
443 		if (report_id != AMM_REPORT_ID || !AMM_VALID_REPORT(len))
444 			goto check_middle_button;
445 
446 		/*
447 		 * The basics. When touches are detected, no normal mouse
448 		 * reports are sent. Collect clicks and dx/dy
449 		 */
450 		if (data[2] & 1)
451 			mouse_butt |= 0x1;
452 		if (data[2] & 2)
453 			mouse_butt |= 0x4;
454 
455 		if ((v = data[0] + ((data[2] & 0x0C) << 6)))
456 			mouse_x += ((int16_t)(v << 6)) >> 6, mevents++;
457 		if ((v = data[1] + ((data[2] & 0x30) << 4)))
458 			mouse_y += ((int16_t)(v << 6)) >> 6, mevents++;
459 
460 		/*
461 		 * The hard part: accumulate touch events and emulate middle
462 		 */
463 		for (data += AMM_BASIC_BLOCK,  len -= AMM_BASIC_BLOCK;
464 		     len >=  AMM_FINGER_BLOCK;
465 		     data += AMM_FINGER_BLOCK, len -= AMM_FINGER_BLOCK) {
466 			int x, y, z, force, id;
467 
468 			v = data[0] | ((data[1] & 0xf) << 8);
469 			x = ((int16_t)(v << 4)) >> 4;
470 
471 			v = (data[1] >> 4) | (data[2] << 4);
472 			y = -(((int16_t)(v << 4)) >> 4);
473 
474 			force = data[5] & 0x3f;
475 			id = 0xf & ((data[5] >> 6) | (data[6] << 2));
476 			z = (y - c->y[id]) / AMM_WHEEL_SPEED;
477 
478 			switch ((data[7] >> 4) & 0x7) {	/* Phase */
479 			case 3:	/* First touch */
480 				c->y[id] = y;
481 				break;
482 			case 4:	/* Touch dragged */
483 				if (z) {
484 					mouse_z += z;
485 					c->y[id] += z * AMM_WHEEL_SPEED;
486 					mevents++;
487 				}
488 				break;
489 			default:
490 				break;
491 			}
492 			/* Count firm touches vs. firm+middle touches */
493 			if (force >= 8 && ++firm && x > -350 && x < 350)
494 				++middle;
495 		}
496 
497 		/*
498 		 * If a new click is registered by mouse and there are firm
499 		 * touches which are all in center, make it a middle click
500 		 */
501 		if (mouse_butt && !c->button_state && firm && middle == firm)
502 			mouse_butt = 0x2;
503 
504 		/*
505 		 * If we're still clicking and have converted the click
506 		 * to a middle click, keep it middle clicking
507 		 */
508 check_middle_button:
509 		if (mouse_butt && c->button_state == 0x2)
510 			mouse_butt = 0x2;
511 
512 		if (mouse_butt != c->button_state)
513 			c->button_state = mouse_butt, mevents++;
514 	}
515 
516 	/*
517 	 * XXX FIXME Feed keyboard events into kernel.
518 	 * The code below works, bit host also needs to track
519 	 * and handle repeat.
520 	 *
521 	 * Key repeat currently works in X, but not in console.
522 	 */
523 
524 	if (kevents > 0) {
525 		if (hid_device->keyboard) {
526 			assert(s->vkbd != -1);
527 			kbd_process_keys(s);
528 		} else
529 			syslog(LOG_ERR, "Keyboard events received from " \
530 				"non-keyboard device %s. Please report",
531 				bt_ntoa(&s->bdaddr, NULL));
532 	}
533 
534 	/*
535 	 * XXX FIXME Feed mouse events into kernel.
536 	 * The code block below works, but it is not good enough.
537 	 * Need to track double-clicks etc.
538 	 *
539 	 * Double click currently works in X, but not in console.
540 	 */
541 
542 	if (mevents > 0) {
543 		struct mouse_info	mi;
544 
545 		memset(&mi, 0, sizeof(mi));
546 		mi.operation = MOUSE_ACTION;
547 		mi.u.data.buttons = mouse_butt;
548 
549 		/* translate T-axis into button presses */
550 		if (mouse_t != 0) {
551 			mi.u.data.buttons |= 1 << (mouse_t > 0 ? 6 : 5);
552 			if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
553 				syslog(LOG_ERR, "Could not process mouse " \
554 					"events from %s. %s (%d)",
555 					bt_ntoa(&s->bdaddr, NULL),
556 					strerror(errno), errno);
557 		}
558 
559 		mi.u.data.x = mouse_x;
560 		mi.u.data.y = mouse_y;
561 		mi.u.data.z = mouse_z;
562 		mi.u.data.buttons = mouse_butt;
563 
564 		if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
565 			syslog(LOG_ERR, "Could not process mouse events from " \
566 				"%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
567 				strerror(errno), errno);
568 
569 		if (hid_device->mouse && s->srv->uinput &&
570 		    uinput_rep_mouse(s->umouse, mouse_x, mouse_y, mouse_z,
571 					mouse_t, mouse_butt, s->obutt) < 0)
572 			syslog(LOG_ERR, "Could not process mouse events from " \
573 				"%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
574 				strerror(errno), errno);
575 		s->obutt = mouse_butt;
576 	}
577 
578 	return (0);
579 }
580