source: mainline/uspace/drv/bus/usb/usbhid/mouse/mousedev.c@ 3f8f09f

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

usb: Make endpoint descriptions const.

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