source: mainline/uspace/drv/bus/usb/usbhid/mouse/mousedev.c@ 555499da

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 555499da was 0f12c17, checked in by Jan Vesely <jano.vesely@…>, 14 years ago

usbhid, mouse: Even better error handling.

  • Property mode set to 100644
File size: 17.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
61const usb_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 = fun->driver_data;
126
127 if (mouse_dev == NULL) {
128 usb_log_debug("%s: Missing parameters.\n", __FUNCTION__);
129 async_answer_0(icallid, EINVAL);
130 return;
131 }
132
133 usb_log_debug("%s: fun->name: %s\n", __FUNCTION__, fun->name);
134 usb_log_debug("%s: mouse_sess: %p, wheel_sess: %p\n",
135 __FUNCTION__, mouse_dev->mouse_sess, mouse_dev->wheel_sess);
136
137 async_sess_t **sess_ptr = (fun == mouse_dev->mouse_fun) ?
138 &mouse_dev->mouse_sess : &mouse_dev->wheel_sess;
139
140 async_sess_t *sess =
141 async_callback_receive_start(EXCHANGE_SERIALIZE, icall);
142 if (sess != NULL) {
143 if (*sess_ptr == NULL) {
144 *sess_ptr = sess;
145 usb_log_debug("Console session to %s set ok (%p).\n",
146 fun->name, sess);
147 async_answer_0(icallid, EOK);
148 } else {
149 usb_log_error("Console session to %s already set.\n",
150 fun->name);
151 async_answer_0(icallid, ELIMIT);
152 }
153 } else {
154 usb_log_debug("%s: Invalid function.\n", __FUNCTION__);
155 async_answer_0(icallid, EINVAL);
156 }
157}
158
159/*----------------------------------------------------------------------------*/
160
161static void usb_mouse_send_wheel(const usb_mouse_t *mouse_dev, int wheel)
162{
163 unsigned int key = (wheel > 0) ? KC_UP : KC_DOWN;
164
165 if (mouse_dev->wheel_sess == NULL) {
166 usb_log_warning(
167 "Connection to console not ready, wheel roll discarded.\n");
168 return;
169 }
170
171 const unsigned count =
172 ((wheel < 0) ? -wheel : wheel) * ARROWS_PER_SINGLE_WHEEL;
173 for (unsigned i = 0; i < count; i++) {
174 /* Send arrow press and release. */
175 usb_log_debug2("Sending key %d to the console\n", key);
176
177 async_exch_t *exch = async_exchange_begin(mouse_dev->wheel_sess);
178
179 async_msg_4(exch, KBDEV_EVENT, KEY_PRESS, key, 0, 0);
180 async_msg_4(exch, KBDEV_EVENT, KEY_RELEASE, key, 0, 0);
181
182 async_exchange_end(exch);
183 }
184}
185
186/*----------------------------------------------------------------------------*/
187
188static int get_mouse_axis_move_value(uint8_t rid, usb_hid_report_t *report,
189 int32_t usage)
190{
191 int result = 0;
192
193 usb_hid_report_path_t *path = usb_hid_report_path();
194 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_GENERIC_DESKTOP,
195 usage);
196
197 usb_hid_report_path_set_report_id(path, rid);
198
199 usb_hid_report_field_t *field = usb_hid_report_get_sibling(
200 report, NULL, path, USB_HID_PATH_COMPARE_END,
201 USB_HID_REPORT_TYPE_INPUT);
202
203 if (field != NULL) {
204 result = field->value;
205 }
206
207 usb_hid_report_path_free(path);
208
209 return result;
210}
211
212static bool usb_mouse_process_report(usb_hid_dev_t *hid_dev,
213 usb_mouse_t *mouse_dev)
214{
215 assert(mouse_dev != NULL);
216
217 if (mouse_dev->mouse_sess == NULL) {
218 usb_log_warning(NAME " No console session.\n");
219 return true;
220 }
221
222 const int shift_x = get_mouse_axis_move_value(hid_dev->report_id,
223 &hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_X);
224 const int shift_y = get_mouse_axis_move_value(hid_dev->report_id,
225 &hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_Y);
226 const int wheel = get_mouse_axis_move_value(hid_dev->report_id,
227 &hid_dev->report, USB_HIDUT_USAGE_GENERIC_DESKTOP_WHEEL);
228
229 if ((shift_x != 0) || (shift_y != 0)) {
230 async_exch_t *exch =
231 async_exchange_begin(mouse_dev->mouse_sess);
232 if (exch != NULL) {
233 async_req_2_0(exch, MOUSEEV_MOVE_EVENT, shift_x, shift_y);
234 async_exchange_end(exch);
235 }
236 }
237
238 if (wheel != 0)
239 usb_mouse_send_wheel(mouse_dev, wheel);
240
241 /* Buttons */
242 usb_hid_report_path_t *path = usb_hid_report_path();
243 if (path == NULL) {
244 usb_log_warning("Failed to create USB HID report path.\n");
245 return true;
246 }
247 int ret =
248 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_BUTTON, 0);
249 if (ret != EOK) {
250 usb_hid_report_path_free(path);
251 usb_log_warning("Failed to add buttons to report path.\n");
252 return true;
253 }
254 usb_hid_report_path_set_report_id(path, hid_dev->report_id);
255
256 usb_hid_report_field_t *field = usb_hid_report_get_sibling(
257 &hid_dev->report, NULL, path, USB_HID_PATH_COMPARE_END
258 | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY, USB_HID_REPORT_TYPE_INPUT);
259
260 while (field != NULL) {
261 usb_log_debug2(NAME " VALUE(%X) USAGE(%X)\n", field->value,
262 field->usage);
263 assert(field->usage > field->usage_minimum);
264 const unsigned index = field->usage - field->usage_minimum;
265 assert(index < mouse_dev->buttons_count);
266
267 if (mouse_dev->buttons[index] == 0 && field->value != 0) {
268 async_exch_t *exch =
269 async_exchange_begin(mouse_dev->mouse_sess);
270 if (exch != NULL) {
271 async_req_2_0(exch, MOUSEEV_BUTTON_EVENT,
272 field->usage, 1);
273 async_exchange_end(exch);
274 mouse_dev->buttons[index] = field->value;
275 }
276
277 } else if (mouse_dev->buttons[index] != 0 && field->value == 0) {
278 async_exch_t *exch =
279 async_exchange_begin(mouse_dev->mouse_sess);
280 if (exch != NULL) {
281 async_req_2_0(exch, MOUSEEV_BUTTON_EVENT,
282 field->usage, 0);
283 async_exchange_end(exch);
284 mouse_dev->buttons[index] = field->value;
285 }
286 }
287
288 field = usb_hid_report_get_sibling(
289 &hid_dev->report, field, path, USB_HID_PATH_COMPARE_END
290 | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
291 USB_HID_REPORT_TYPE_INPUT);
292 }
293
294 usb_hid_report_path_free(path);
295
296 return true;
297}
298/*----------------------------------------------------------------------------*/
299#define FUN_UNBIND_DESTROY(fun) \
300if (fun) { \
301 if (ddf_fun_unbind((fun)) == EOK) { \
302 (fun)->driver_data = NULL; \
303 ddf_fun_destroy((fun)); \
304 } else { \
305 usb_log_error("Could not unbind function `%s', it " \
306 "will not be destroyed.\n", (fun)->name); \
307 } \
308} else (void)0
309/*----------------------------------------------------------------------------*/
310static int usb_mouse_create_function(usb_hid_dev_t *hid_dev, usb_mouse_t *mouse)
311{
312 assert(hid_dev != NULL);
313 assert(mouse != NULL);
314
315 /* Create the exposed function. */
316 usb_log_debug("Creating DDF function %s...\n", HID_MOUSE_FUN_NAME);
317 ddf_fun_t *fun = ddf_fun_create(hid_dev->usb_dev->ddf_dev, fun_exposed,
318 HID_MOUSE_FUN_NAME);
319 if (fun == NULL) {
320 usb_log_error("Could not create DDF function node `%s'.\n",
321 HID_MOUSE_FUN_NAME);
322 return ENOMEM;
323 }
324
325 fun->ops = &mouse->ops;
326 fun->driver_data = mouse;
327
328 int rc = ddf_fun_bind(fun);
329 if (rc != EOK) {
330 usb_log_error("Could not bind DDF function `%s': %s.\n",
331 fun->name, str_error(rc));
332 fun->driver_data = NULL;
333 ddf_fun_destroy(fun);
334 return rc;
335 }
336
337 usb_log_debug("Adding DDF function `%s' to category %s...\n",
338 fun->name, HID_MOUSE_CATEGORY);
339 rc = ddf_fun_add_to_category(fun, HID_MOUSE_CATEGORY);
340 if (rc != EOK) {
341 usb_log_error(
342 "Could not add DDF function to category %s: %s.\n",
343 HID_MOUSE_CATEGORY, str_error(rc));
344 FUN_UNBIND_DESTROY(fun);
345 return rc;
346 }
347 mouse->mouse_fun = fun;
348
349 /*
350 * Special function for acting as keyboard (wheel)
351 */
352 usb_log_debug("Creating DDF function %s...\n",
353 HID_MOUSE_WHEEL_FUN_NAME);
354 fun = ddf_fun_create(hid_dev->usb_dev->ddf_dev, fun_exposed,
355 HID_MOUSE_WHEEL_FUN_NAME);
356 if (fun == NULL) {
357 usb_log_error("Could not create DDF function node `%s'.\n",
358 HID_MOUSE_WHEEL_FUN_NAME);
359 FUN_UNBIND_DESTROY(mouse->mouse_fun);
360 mouse->mouse_fun = NULL;
361 return ENOMEM;
362 }
363
364 /*
365 * Store the initialized HID device and HID ops
366 * to the DDF function.
367 */
368 fun->ops = &mouse->ops;
369 fun->driver_data = mouse;
370
371 rc = ddf_fun_bind(fun);
372 if (rc != EOK) {
373 usb_log_error("Could not bind DDF function `%s': %s.\n",
374 fun->name, str_error(rc));
375 FUN_UNBIND_DESTROY(mouse->mouse_fun);
376 mouse->mouse_fun = NULL;
377
378 fun->driver_data = NULL;
379 ddf_fun_destroy(fun);
380 return rc;
381 }
382
383 usb_log_debug("Adding DDF function to category %s...\n",
384 HID_MOUSE_WHEEL_CATEGORY);
385 rc = ddf_fun_add_to_category(fun, HID_MOUSE_WHEEL_CATEGORY);
386 if (rc != EOK) {
387 usb_log_error(
388 "Could not add DDF function to category %s: %s.\n",
389 HID_MOUSE_WHEEL_CATEGORY, str_error(rc));
390
391 FUN_UNBIND_DESTROY(mouse->mouse_fun);
392 mouse->mouse_fun = NULL;
393 FUN_UNBIND_DESTROY(fun);
394 return rc;
395 }
396 mouse->wheel_fun = fun;
397
398 return EOK;
399}
400
401/*----------------------------------------------------------------------------*/
402
403/** Get highest index of a button mentioned in given report.
404 *
405 * @param report HID report.
406 * @param report_id Report id we are interested in.
407 * @return Highest button mentioned in the report.
408 * @retval 1 No button was mentioned.
409 *
410 */
411static size_t usb_mouse_get_highest_button(usb_hid_report_t *report, uint8_t report_id)
412{
413 size_t highest_button = 0;
414
415 usb_hid_report_path_t *path = usb_hid_report_path();
416 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_BUTTON, 0);
417 usb_hid_report_path_set_report_id(path, report_id);
418
419 usb_hid_report_field_t *field = NULL;
420
421 /* Break from within. */
422 while (1) {
423 field = usb_hid_report_get_sibling(
424 report, field, path,
425 USB_HID_PATH_COMPARE_END | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
426 USB_HID_REPORT_TYPE_INPUT);
427 /* No more buttons? */
428 if (field == NULL) {
429 break;
430 }
431
432 size_t current_button = field->usage - field->usage_minimum;
433 if (current_button > highest_button) {
434 highest_button = current_button;
435 }
436 }
437
438 usb_hid_report_path_free(path);
439
440 return highest_button;
441}
442/*----------------------------------------------------------------------------*/
443int usb_mouse_init(usb_hid_dev_t *hid_dev, void **data)
444{
445 usb_log_debug("Initializing HID/Mouse structure...\n");
446
447 if (hid_dev == NULL) {
448 usb_log_error("Failed to init keyboard structure: no structure"
449 " given.\n");
450 return EINVAL;
451 }
452
453 usb_mouse_t *mouse_dev = calloc(1, sizeof(usb_mouse_t));
454 if (mouse_dev == NULL) {
455 usb_log_error("Error while creating USB/HID Mouse device "
456 "structure.\n");
457 return ENOMEM;
458 }
459
460 // FIXME: This may not be optimal since stupid hardware vendor may
461 // use buttons 1, 2, 3 and 6000 and we would allocate array of
462 // 6001*4B and use only 4 items in it.
463 // Since I doubt that hardware producers would do that, I think
464 // that the current solution is good enough.
465 /* Adding 1 because we will be accessing buttons[highest]. */
466 mouse_dev->buttons_count = 1 + usb_mouse_get_highest_button(
467 &hid_dev->report, hid_dev->report_id);
468 mouse_dev->buttons = calloc(mouse_dev->buttons_count, sizeof(int32_t));
469
470 if (mouse_dev->buttons == NULL) {
471 usb_log_error(NAME ": out of memory, giving up on device!\n");
472 free(mouse_dev);
473 return ENOMEM;
474 }
475
476 // set handler for incoming calls
477 mouse_dev->ops.default_handler = default_connection_handler;
478
479 // TODO: how to know if the device supports the request???
480 usbhid_req_set_idle(&hid_dev->usb_dev->ctrl_pipe,
481 hid_dev->usb_dev->interface_no, IDLE_RATE);
482
483 int rc = usb_mouse_create_function(hid_dev, mouse_dev);
484 if (rc != EOK) {
485 free(mouse_dev->buttons);
486 free(mouse_dev);
487 return rc;
488 }
489
490 /* Save the Mouse device structure into the HID device structure. */
491 *data = mouse_dev;
492
493 return EOK;
494}
495/*----------------------------------------------------------------------------*/
496bool usb_mouse_polling_callback(usb_hid_dev_t *hid_dev, void *data)
497{
498 if (hid_dev == NULL || data == NULL) {
499 usb_log_error(
500 "Missing argument to the mouse polling callback.\n");
501 return false;
502 }
503
504 usb_mouse_t *mouse_dev = data;
505
506 return usb_mouse_process_report(hid_dev, mouse_dev);
507}
508/*----------------------------------------------------------------------------*/
509void usb_mouse_deinit(usb_hid_dev_t *hid_dev, void *data)
510{
511 if (data == NULL)
512 return;
513
514 usb_mouse_t *mouse_dev = data;
515
516 /* Hangup session to the console */
517 if (mouse_dev->mouse_sess != NULL) {
518 const int ret = async_hangup(mouse_dev->mouse_sess);
519 if (ret != EOK)
520 usb_log_warning("Failed to hang up mouse session: "
521 "%p, %s.\n", mouse_dev->mouse_sess, str_error(ret));
522 }
523
524 if (mouse_dev->wheel_sess != NULL) {
525 const int ret = async_hangup(mouse_dev->wheel_sess);
526 if (ret != EOK)
527 usb_log_warning("Failed to hang up wheel session: "
528 "%p, %s.\n", mouse_dev->wheel_sess, str_error(ret));
529 }
530
531 FUN_UNBIND_DESTROY(mouse_dev->mouse_fun);
532 FUN_UNBIND_DESTROY(mouse_dev->wheel_fun);
533
534 free(mouse_dev->buttons);
535 free(mouse_dev);
536}
537/*----------------------------------------------------------------------------*/
538int usb_mouse_set_boot_protocol(usb_hid_dev_t *hid_dev)
539{
540 int rc = usb_hid_parse_report_descriptor(
541 &hid_dev->report, USB_MOUSE_BOOT_REPORT_DESCRIPTOR,
542 USB_MOUSE_BOOT_REPORT_DESCRIPTOR_SIZE);
543
544 if (rc != EOK) {
545 usb_log_error("Failed to parse boot report descriptor: %s\n",
546 str_error(rc));
547 return rc;
548 }
549
550 rc = usbhid_req_set_protocol(&hid_dev->usb_dev->ctrl_pipe,
551 hid_dev->usb_dev->interface_no, USB_HID_PROTOCOL_BOOT);
552
553 if (rc != EOK) {
554 usb_log_warning("Failed to set boot protocol to the device: "
555 "%s\n", str_error(rc));
556 return rc;
557 }
558
559 return EOK;
560}
561
562/**
563 * @}
564 */
Note: See TracBrowser for help on using the repository browser.