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