source: mainline/uspace/drv/bus/usb/usbhid/mouse/mousedev.c@ 193da9d6

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 193da9d6 was 4093b14, checked in by Vojtech Horky <vojtechhorky@…>, 14 years ago

USB HID: mouse button count read from report (#349)

  • Property mode set to 100644
File size: 16.0 KB
Line 
1/*
2 * Copyright (c) 2011 Lubos Slovak, Vojtech Horky
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup drvusbhid
30 * @{
31 */
32/**
33 * @file
34 * USB Mouse driver API.
35 */
36
37#include <usb/debug.h>
38#include <usb/classes/classes.h>
39#include <usb/hid/hid.h>
40#include <usb/hid/request.h>
41#include <usb/hid/usages/core.h>
42#include <errno.h>
43#include <async.h>
44#include <str_error.h>
45#include <ipc/mouseev.h>
46#include <io/console.h>
47
48#include <ipc/kbdev.h>
49#include <io/keycode.h>
50
51#include "mousedev.h"
52#include "../usbhid.h"
53
54/** Number of simulated arrow-key presses for singel wheel step. */
55#define ARROWS_PER_SINGLE_WHEEL 3
56
57#define NAME "mouse"
58
59/*----------------------------------------------------------------------------*/
60
61usb_endpoint_description_t usb_hid_mouse_poll_endpoint_description = {
62 .transfer_type = USB_TRANSFER_INTERRUPT,
63 .direction = USB_DIRECTION_IN,
64 .interface_class = USB_CLASS_HID,
65 .interface_subclass = USB_HID_SUBCLASS_BOOT,
66 .interface_protocol = USB_HID_PROTOCOL_MOUSE,
67 .flags = 0
68};
69
70const char *HID_MOUSE_FUN_NAME = "mouse";
71const char *HID_MOUSE_WHEEL_FUN_NAME = "mouse-wheel";
72const char *HID_MOUSE_CATEGORY = "mouse";
73const char *HID_MOUSE_WHEEL_CATEGORY = "keyboard";
74
75/** Default idle rate for mouses. */
76static const uint8_t IDLE_RATE = 0;
77
78/*----------------------------------------------------------------------------*/
79
80enum {
81 USB_MOUSE_BOOT_REPORT_DESCRIPTOR_SIZE = 63
82};
83
84static const uint8_t USB_MOUSE_BOOT_REPORT_DESCRIPTOR[
85 USB_MOUSE_BOOT_REPORT_DESCRIPTOR_SIZE] = {
86 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
87 0x09, 0x02, // USAGE (Mouse)
88 0xa1, 0x01, // COLLECTION (Application)
89 0x09, 0x01, // USAGE (Pointer)
90 0xa1, 0x00, // COLLECTION (Physical)
91 0x95, 0x03, // REPORT_COUNT (3)
92 0x75, 0x01, // REPORT_SIZE (1)
93 0x05, 0x09, // USAGE_PAGE (Button)
94 0x19, 0x01, // USAGE_MINIMUM (Button 1)
95 0x29, 0x03, // USAGE_MAXIMUM (Button 3)
96 0x15, 0x00, // LOGICAL_MINIMUM (0)
97 0x25, 0x01, // LOGICAL_MAXIMUM (1)
98 0x81, 0x02, // INPUT (Data,Var,Abs)
99 0x95, 0x01, // REPORT_COUNT (1)
100 0x75, 0x05, // REPORT_SIZE (5)
101 0x81, 0x01, // INPUT (Cnst)
102 0x75, 0x08, // REPORT_SIZE (8)
103 0x95, 0x02, // REPORT_COUNT (2)
104 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
105 0x09, 0x30, // USAGE (X)
106 0x09, 0x31, // USAGE (Y)
107 0x15, 0x81, // LOGICAL_MINIMUM (-127)
108 0x25, 0x7f, // LOGICAL_MAXIMUM (127)
109 0x81, 0x06, // INPUT (Data,Var,Rel)
110 0xc0, // END_COLLECTION
111 0xc0 // END_COLLECTION
112};
113
114/*----------------------------------------------------------------------------*/
115
116/** Default handler for IPC methods not handled by DDF.
117 *
118 * @param fun Device function handling the call.
119 * @param icallid Call id.
120 * @param icall Call data.
121 */
122static void default_connection_handler(ddf_fun_t *fun,
123 ipc_callid_t icallid, ipc_call_t *icall)
124{
125 usb_mouse_t *mouse_dev = (usb_mouse_t *) fun->driver_data;
126
127 if (mouse_dev == NULL) {
128 usb_log_debug("default_connection_handler: Missing "
129 "parameters.\n");
130 async_answer_0(icallid, EINVAL);
131 return;
132 }
133
134 usb_log_debug("default_connection_handler: fun->name: %s\n",
135 fun->name);
136 usb_log_debug("default_connection_handler: mouse_sess: %p, "
137 "wheel_sess: %p\n", mouse_dev->mouse_sess, mouse_dev->wheel_sess);
138
139 async_sess_t **sess_ptr =
140 (str_cmp(fun->name, HID_MOUSE_FUN_NAME) == 0) ?
141 &mouse_dev->mouse_sess : &mouse_dev->wheel_sess;
142
143 async_sess_t *sess =
144 async_callback_receive_start(EXCHANGE_SERIALIZE, icall);
145 if (sess != NULL) {
146 if (*sess_ptr == NULL) {
147 *sess_ptr = sess;
148 usb_log_debug("Console session to mouse set ok (%p).\n",
149 sess);
150 async_answer_0(icallid, EOK);
151 } else {
152 usb_log_debug("default_connection_handler: Console "
153 "session to mouse already set.\n");
154 async_answer_0(icallid, ELIMIT);
155 }
156 } else {
157 usb_log_debug("default_connection_handler: Invalid function.\n");
158 async_answer_0(icallid, EINVAL);
159 }
160}
161
162/*----------------------------------------------------------------------------*/
163
164static usb_mouse_t *usb_mouse_new(void)
165{
166 usb_mouse_t *mouse = calloc(1, sizeof(usb_mouse_t));
167 if (mouse == NULL) {
168 return NULL;
169 }
170 mouse->mouse_sess = NULL;
171 mouse->wheel_sess = NULL;
172
173 return mouse;
174}
175
176/*----------------------------------------------------------------------------*/
177
178static void usb_mouse_destroy(usb_mouse_t *mouse_dev)
179{
180 assert(mouse_dev != NULL);
181
182 // hangup session to the console
183 if (mouse_dev->mouse_sess != NULL)
184 async_hangup(mouse_dev->mouse_sess);
185
186 if (mouse_dev->wheel_sess != NULL)
187 async_hangup(mouse_dev->wheel_sess);
188}
189
190/*----------------------------------------------------------------------------*/
191
192static void usb_mouse_send_wheel(const usb_mouse_t *mouse_dev, int wheel)
193{
194 unsigned int key = (wheel > 0) ? KC_UP : KC_DOWN;
195
196 if (mouse_dev->wheel_sess == NULL) {
197 usb_log_warning(
198 "Connection to console not ready, wheel roll discarded.\n");
199 return;
200 }
201
202 int count = ((wheel < 0) ? -wheel : wheel) * ARROWS_PER_SINGLE_WHEEL;
203 int i;
204
205 for (i = 0; i < count; i++) {
206 /* Send arrow press and release. */
207 usb_log_debug2("Sending key %d to the console\n", key);
208
209 async_exch_t *exch = async_exchange_begin(mouse_dev->wheel_sess);
210
211 async_msg_4(exch, KBDEV_EVENT, KEY_PRESS, key, 0, 0);
212 async_msg_4(exch, KBDEV_EVENT, KEY_RELEASE, key, 0, 0);
213
214 async_exchange_end(exch);
215 }
216}
217
218/*----------------------------------------------------------------------------*/
219
220static int get_mouse_axis_move_value(uint8_t rid, usb_hid_report_t *report,
221 int32_t usage)
222{
223 int result = 0;
224
225 usb_hid_report_path_t *path = usb_hid_report_path();
226 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_GENERIC_DESKTOP,
227 usage);
228
229 usb_hid_report_path_set_report_id(path, rid);
230
231 usb_hid_report_field_t *field = usb_hid_report_get_sibling(
232 report, NULL, path, USB_HID_PATH_COMPARE_END,
233 USB_HID_REPORT_TYPE_INPUT);
234
235 if (field != NULL) {
236 result = field->value;
237 }
238
239 usb_hid_report_path_free(path);
240
241 return result;
242}
243
244static bool usb_mouse_process_report(usb_hid_dev_t *hid_dev,
245 usb_mouse_t *mouse_dev)
246{
247 assert(mouse_dev != NULL);
248
249 if (mouse_dev->mouse_sess == NULL) {
250 usb_log_warning(NAME " No console session.\n");
251 return true;
252 }
253
254 int shift_x = get_mouse_axis_move_value(hid_dev->report_id,
255 hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_X);
256 int shift_y = get_mouse_axis_move_value(hid_dev->report_id,
257 hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_Y);
258 int wheel = get_mouse_axis_move_value(hid_dev->report_id,
259 hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_WHEEL);
260
261 if ((shift_x != 0) || (shift_y != 0)) {
262 async_exch_t *exch = async_exchange_begin(mouse_dev->mouse_sess);
263 async_req_2_0(exch, MOUSEEV_MOVE_EVENT, shift_x, shift_y);
264 async_exchange_end(exch);
265 }
266
267 if (wheel != 0)
268 usb_mouse_send_wheel(mouse_dev, wheel);
269
270 /*
271 * Buttons
272 */
273 usb_hid_report_path_t *path = usb_hid_report_path();
274 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_BUTTON, 0);
275 usb_hid_report_path_set_report_id(path, hid_dev->report_id);
276
277 usb_hid_report_field_t *field = usb_hid_report_get_sibling(
278 hid_dev->report, NULL, path, USB_HID_PATH_COMPARE_END
279 | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
280 USB_HID_REPORT_TYPE_INPUT);
281
282 while (field != NULL) {
283 usb_log_debug2(NAME " VALUE(%X) USAGE(%X)\n", field->value,
284 field->usage);
285
286 if (mouse_dev->buttons[field->usage - field->usage_minimum] == 0
287 && field->value != 0) {
288 async_exch_t *exch =
289 async_exchange_begin(mouse_dev->mouse_sess);
290 async_req_2_0(exch, MOUSEEV_BUTTON_EVENT, field->usage, 1);
291 async_exchange_end(exch);
292
293 mouse_dev->buttons[field->usage - field->usage_minimum]
294 = field->value;
295 } else if (mouse_dev->buttons[field->usage - field->usage_minimum] != 0
296 && field->value == 0) {
297 async_exch_t *exch =
298 async_exchange_begin(mouse_dev->mouse_sess);
299 async_req_2_0(exch, MOUSEEV_BUTTON_EVENT, field->usage, 0);
300 async_exchange_end(exch);
301
302 mouse_dev->buttons[field->usage - field->usage_minimum] =
303 field->value;
304 }
305
306 field = usb_hid_report_get_sibling(
307 hid_dev->report, field, path, USB_HID_PATH_COMPARE_END
308 | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
309 USB_HID_REPORT_TYPE_INPUT);
310 }
311
312 usb_hid_report_path_free(path);
313
314 return true;
315}
316
317/*----------------------------------------------------------------------------*/
318
319static int usb_mouse_create_function(usb_hid_dev_t *hid_dev, usb_mouse_t *mouse)
320{
321 assert(hid_dev != NULL);
322 assert(mouse != NULL);
323
324 /* Create the exposed function. */
325 usb_log_debug("Creating DDF function %s...\n", HID_MOUSE_FUN_NAME);
326 ddf_fun_t *fun = ddf_fun_create(hid_dev->usb_dev->ddf_dev, fun_exposed,
327 HID_MOUSE_FUN_NAME);
328 if (fun == NULL) {
329 usb_log_error("Could not create DDF function node.\n");
330 return ENOMEM;
331 }
332
333 fun->ops = &mouse->ops;
334 fun->driver_data = mouse;
335
336 int rc = ddf_fun_bind(fun);
337 if (rc != EOK) {
338 usb_log_error("Could not bind DDF function: %s.\n",
339 str_error(rc));
340 ddf_fun_destroy(fun);
341 return rc;
342 }
343
344 usb_log_debug("Adding DDF function to category %s...\n",
345 HID_MOUSE_CATEGORY);
346 rc = ddf_fun_add_to_category(fun, HID_MOUSE_CATEGORY);
347 if (rc != EOK) {
348 usb_log_error(
349 "Could not add DDF function to category %s: %s.\n",
350 HID_MOUSE_CATEGORY, str_error(rc));
351 ddf_fun_destroy(fun);
352 return rc;
353 }
354
355 /*
356 * Special function for acting as keyboard (wheel)
357 */
358 usb_log_debug("Creating DDF function %s...\n",
359 HID_MOUSE_WHEEL_FUN_NAME);
360 fun = ddf_fun_create(hid_dev->usb_dev->ddf_dev, fun_exposed,
361 HID_MOUSE_WHEEL_FUN_NAME);
362 if (fun == NULL) {
363 usb_log_error("Could not create DDF function node.\n");
364 return ENOMEM;
365 }
366
367 /*
368 * Store the initialized HID device and HID ops
369 * to the DDF function.
370 */
371 fun->ops = &mouse->ops;
372 fun->driver_data = mouse;
373
374 rc = ddf_fun_bind(fun);
375 if (rc != EOK) {
376 usb_log_error("Could not bind DDF function: %s.\n",
377 str_error(rc));
378 ddf_fun_destroy(fun);
379 return rc;
380 }
381
382 usb_log_debug("Adding DDF function to category %s...\n",
383 HID_MOUSE_WHEEL_CATEGORY);
384 rc = ddf_fun_add_to_category(fun, HID_MOUSE_WHEEL_CATEGORY);
385 if (rc != EOK) {
386 usb_log_error(
387 "Could not add DDF function to category %s: %s.\n",
388 HID_MOUSE_WHEEL_CATEGORY, str_error(rc));
389 ddf_fun_destroy(fun);
390 return rc;
391 }
392
393 return EOK;
394}
395
396/*----------------------------------------------------------------------------*/
397
398/** Get highest index of a button mentioned in given report.
399 *
400 * @param report HID report.
401 * @param report_id Report id we are interested in.
402 * @return Highest button mentioned in the report.
403 * @retval 1 No button was mentioned.
404 *
405 */
406static size_t usb_mouse_get_highest_button(usb_hid_report_t *report, uint8_t report_id)
407{
408 size_t highest_button = 0;
409
410 usb_hid_report_path_t *path = usb_hid_report_path();
411 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_BUTTON, 0);
412 usb_hid_report_path_set_report_id(path, report_id);
413
414 usb_hid_report_field_t *field = NULL;
415
416 /* Break from within. */
417 while (1) {
418 field = usb_hid_report_get_sibling(
419 report, field, path,
420 USB_HID_PATH_COMPARE_END | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
421 USB_HID_REPORT_TYPE_INPUT);
422 /* No more buttons? */
423 if (field == NULL) {
424 break;
425 }
426
427 size_t current_button = field->usage - field->usage_minimum;
428 if (current_button > highest_button) {
429 highest_button = current_button;
430 }
431 }
432
433 usb_hid_report_path_free(path);
434
435 return highest_button;
436}
437
438/*----------------------------------------------------------------------------*/
439
440int usb_mouse_init(usb_hid_dev_t *hid_dev, void **data)
441{
442 usb_log_debug("Initializing HID/Mouse structure...\n");
443
444 if (hid_dev == NULL) {
445 usb_log_error("Failed to init keyboard structure: no structure"
446 " given.\n");
447 return EINVAL;
448 }
449
450 usb_mouse_t *mouse_dev = usb_mouse_new();
451 if (mouse_dev == NULL) {
452 usb_log_error("Error while creating USB/HID Mouse device "
453 "structure.\n");
454 return ENOMEM;
455 }
456
457 // FIXME: This may not be optimal since stupid hardware vendor may
458 // use buttons 1, 2, 3 and 6000 and we would allocate array of
459 // 6001*4B and use only 4 items in it.
460 // Since I doubt that hardware producers would do that, I think
461 // that the current solution is good enough.
462 /* Adding 1 because we will be accessing buttons[highest]. */
463 mouse_dev->buttons_count = usb_mouse_get_highest_button(hid_dev->report,
464 hid_dev->report_id) + 1;
465 mouse_dev->buttons = calloc(mouse_dev->buttons_count, sizeof(int32_t));
466
467 if (mouse_dev->buttons == NULL) {
468 usb_log_error(NAME ": out of memory, giving up on device!\n");
469 free(mouse_dev);
470 return ENOMEM;
471 }
472
473
474 // save the Mouse device structure into the HID device structure
475 *data = mouse_dev;
476
477 // set handler for incoming calls
478 mouse_dev->ops.default_handler = default_connection_handler;
479
480 // TODO: how to know if the device supports the request???
481 usbhid_req_set_idle(&hid_dev->usb_dev->ctrl_pipe,
482 hid_dev->usb_dev->interface_no, IDLE_RATE);
483
484 int rc = usb_mouse_create_function(hid_dev, mouse_dev);
485 if (rc != EOK) {
486 usb_mouse_destroy(mouse_dev);
487 return rc;
488 }
489
490 return EOK;
491}
492
493/*----------------------------------------------------------------------------*/
494
495bool usb_mouse_polling_callback(usb_hid_dev_t *hid_dev, void *data)
496{
497 if (hid_dev == NULL || data == NULL) {
498 usb_log_error("Missing argument to the mouse polling callback."
499 "\n");
500 return false;
501 }
502
503 usb_mouse_t *mouse_dev = (usb_mouse_t *)data;
504
505 return usb_mouse_process_report(hid_dev, mouse_dev);
506}
507
508/*----------------------------------------------------------------------------*/
509
510void usb_mouse_deinit(usb_hid_dev_t *hid_dev, void *data)
511{
512 if (data != NULL) {
513 usb_mouse_destroy((usb_mouse_t *)data);
514 }
515}
516
517/*----------------------------------------------------------------------------*/
518
519int usb_mouse_set_boot_protocol(usb_hid_dev_t *hid_dev)
520{
521 int rc = usb_hid_parse_report_descriptor(hid_dev->report,
522 USB_MOUSE_BOOT_REPORT_DESCRIPTOR,
523 USB_MOUSE_BOOT_REPORT_DESCRIPTOR_SIZE);
524
525 if (rc != EOK) {
526 usb_log_error("Failed to parse boot report descriptor: %s\n",
527 str_error(rc));
528 return rc;
529 }
530
531 rc = usbhid_req_set_protocol(&hid_dev->usb_dev->ctrl_pipe,
532 hid_dev->usb_dev->interface_no, USB_HID_PROTOCOL_BOOT);
533
534 if (rc != EOK) {
535 usb_log_warning("Failed to set boot protocol to the device: "
536 "%s\n", str_error(rc));
537 return rc;
538 }
539
540 return EOK;
541}
542
543/**
544 * @}
545 */
Note: See TracBrowser for help on using the repository browser.