source: mainline/uspace/drv/hid/usbhid/mouse/mousedev.c

Last change on this file was 21cd0c8, checked in by Jiri Svoboda <jiri@…>, 8 weeks ago

Avoid flooding log with mouse warnings during system startup.

  • Property mode set to 100644
File size: 13.9 KB
RevLine 
[e9f0348]1/*
[21cd0c8]2 * Copyright (c) 2025 Jiri Svoboda
[e9f0348]3 * Copyright (c) 2011 Lubos Slovak, Vojtech Horky
[e0a5d4c]4 * Copyright (c) 2018 Ondrej Hlavaty
[e9f0348]5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * - The name of the author may not be used to endorse or promote products
17 * derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/** @addtogroup drvusbhid
32 * @{
33 */
34/**
35 * @file
36 * USB Mouse driver API.
37 */
38
39#include <usb/debug.h>
40#include <usb/classes/classes.h>
[faa44e58]41#include <usb/hid/hid.h>
42#include <usb/hid/request.h>
43#include <usb/hid/usages/core.h>
[e9f0348]44#include <errno.h>
[79ae36dd]45#include <async.h>
[76d0981d]46#include <stdbool.h>
[e9f0348]47#include <str_error.h>
[1875a0c]48#include <ipc/mouseev.h>
[f8e549b]49
[5f88293]50#include <ipc/kbdev.h>
[f8e549b]51#include <io/keycode.h>
[e9f0348]52
53#include "mousedev.h"
54#include "../usbhid.h"
55
[e04182d]56#define NAME "mouse"
[30710035]57
[984a9ba]58static void default_connection_handler(ddf_fun_t *, ipc_call_t *);
[2b621cb]59
60static ddf_dev_ops_t ops = { .default_handler = default_connection_handler };
[e9f0348]61
[b803845]62const usb_endpoint_description_t usb_hid_mouse_poll_endpoint_description = {
[e9f0348]63 .transfer_type = USB_TRANSFER_INTERRUPT,
64 .direction = USB_DIRECTION_IN,
65 .interface_class = USB_CLASS_HID,
66 .interface_subclass = USB_HID_SUBCLASS_BOOT,
67 .interface_protocol = USB_HID_PROTOCOL_MOUSE,
68 .flags = 0
69};
70
71const char *HID_MOUSE_FUN_NAME = "mouse";
[1dc4a5e]72const char *HID_MOUSE_CATEGORY = "mouse";
[e9f0348]73
74/** Default idle rate for mouses. */
75static const uint8_t IDLE_RATE = 0;
76
[e3c78efc]77static const uint8_t USB_MOUSE_BOOT_REPORT_DESCRIPTOR[] = {
[e9f0348]78 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
79 0x09, 0x02, // USAGE (Mouse)
80 0xa1, 0x01, // COLLECTION (Application)
81 0x09, 0x01, // USAGE (Pointer)
82 0xa1, 0x00, // COLLECTION (Physical)
83 0x95, 0x03, // REPORT_COUNT (3)
84 0x75, 0x01, // REPORT_SIZE (1)
85 0x05, 0x09, // USAGE_PAGE (Button)
86 0x19, 0x01, // USAGE_MINIMUM (Button 1)
87 0x29, 0x03, // USAGE_MAXIMUM (Button 3)
88 0x15, 0x00, // LOGICAL_MINIMUM (0)
89 0x25, 0x01, // LOGICAL_MAXIMUM (1)
90 0x81, 0x02, // INPUT (Data,Var,Abs)
91 0x95, 0x01, // REPORT_COUNT (1)
92 0x75, 0x05, // REPORT_SIZE (5)
93 0x81, 0x01, // INPUT (Cnst)
94 0x75, 0x08, // REPORT_SIZE (8)
95 0x95, 0x02, // REPORT_COUNT (2)
96 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
97 0x09, 0x30, // USAGE (X)
98 0x09, 0x31, // USAGE (Y)
99 0x15, 0x81, // LOGICAL_MINIMUM (-127)
100 0x25, 0x7f, // LOGICAL_MAXIMUM (127)
101 0x81, 0x06, // INPUT (Data,Var,Rel)
102 0xc0, // END_COLLECTION
103 0xc0 // END_COLLECTION
104};
105
106/** Default handler for IPC methods not handled by DDF.
107 *
[984a9ba]108 * @param fun Device function handling the call.
109 * @param icall Call data.
110 *
[e9f0348]111 */
[984a9ba]112static void default_connection_handler(ddf_fun_t *fun, ipc_call_t *icall)
[e9f0348]113{
[56fd7cf]114 usb_mouse_t *mouse_dev = ddf_fun_data_get(fun);
[cc29622]115
[65b458c4]116 if (mouse_dev == NULL) {
[a1732929]117 usb_log_debug("%s: Missing parameters.", __FUNCTION__);
[984a9ba]118 async_answer_0(icall, EINVAL);
[e9f0348]119 return;
120 }
[cc29622]121
[a1732929]122 usb_log_debug("%s: fun->name: %s", __FUNCTION__, ddf_fun_get_name(fun));
123 usb_log_debug("%s: mouse_sess: %p",
[70172dc4]124 __FUNCTION__, mouse_dev->mouse_sess);
[cc29622]125
[5da7199]126 async_sess_t *sess =
127 async_callback_receive_start(EXCHANGE_SERIALIZE, icall);
128 if (sess != NULL) {
[70172dc4]129 if (mouse_dev->mouse_sess == NULL) {
130 mouse_dev->mouse_sess = sess;
[a1732929]131 usb_log_debug("Console session to %s set ok (%p).",
[56fd7cf]132 ddf_fun_get_name(fun), sess);
[984a9ba]133 async_answer_0(icall, EOK);
[5da7199]134 } else {
[a1732929]135 usb_log_error("Console session to %s already set.",
[56fd7cf]136 ddf_fun_get_name(fun));
[984a9ba]137 async_answer_0(icall, ELIMIT);
[70172dc4]138 async_hangup(sess);
[e9f0348]139 }
[5da7199]140 } else {
[a1732929]141 usb_log_debug("%s: Invalid function.", __FUNCTION__);
[984a9ba]142 async_answer_0(icall, EINVAL);
[e9f0348]143 }
144}
[76fbd9a]145
[ae303ad]146static const usb_hid_report_field_t *get_mouse_axis_move_field(uint8_t rid, usb_hid_report_t *report,
[a3a2fdb]147 int32_t usage)
[e9f0348]148{
[30710035]149 usb_hid_report_path_t *path = usb_hid_report_path();
[a3a2fdb]150 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_GENERIC_DESKTOP,
151 usage);
[e9f0348]152
[a3a2fdb]153 usb_hid_report_path_set_report_id(path, rid);
[e9f0348]154
[ae303ad]155 const usb_hid_report_field_t *field = usb_hid_report_get_sibling(
[a3a2fdb]156 report, NULL, path, USB_HID_PATH_COMPARE_END,
[30710035]157 USB_HID_REPORT_TYPE_INPUT);
[e9f0348]158
[30710035]159 usb_hid_report_path_free(path);
160
[ae303ad]161 return field;
[a3a2fdb]162}
[5cc9eba]163
[970f6e1]164static void usb_mouse_process_report(usb_hid_dev_t *hid_dev,
[a3a2fdb]165 usb_mouse_t *mouse_dev)
166{
167 assert(mouse_dev != NULL);
[cc29622]168
[21cd0c8]169 if (mouse_dev->mouse_sess == NULL)
[970f6e1]170 return;
[30710035]171
[ae303ad]172 const usb_hid_report_field_t *move_x = get_mouse_axis_move_field(
173 hid_dev->report_id, &hid_dev->report,
174 USB_HIDUT_USAGE_GENERIC_DESKTOP_X);
175 const usb_hid_report_field_t *move_y = get_mouse_axis_move_field(
176 hid_dev->report_id, &hid_dev->report,
177 USB_HIDUT_USAGE_GENERIC_DESKTOP_Y);
[18b6a88]178 const usb_hid_report_field_t *wheel = get_mouse_axis_move_field(
[ae303ad]179 hid_dev->report_id, &hid_dev->report,
180 USB_HIDUT_USAGE_GENERIC_DESKTOP_WHEEL);
181
182 bool absolute_x = move_x && !USB_HID_ITEM_FLAG_RELATIVE(move_x->item_flags);
183 bool absolute_y = move_y && !USB_HID_ITEM_FLAG_RELATIVE(move_y->item_flags);
184
185 /* Tablet shall always report both X and Y */
186 if (absolute_x != absolute_y) {
187 usb_log_error(NAME " cannot handle mix of absolute and relative mouse move.");
[970f6e1]188 return;
[ae303ad]189 }
190
191 int shift_x = move_x ? move_x->value : 0;
192 int shift_y = move_y ? move_y->value : 0;
193 int shift_z = wheel ? wheel->value : 0;
194
195 if (absolute_x && absolute_y) {
196 async_exch_t *exch =
197 async_exchange_begin(mouse_dev->mouse_sess);
198 if (exch != NULL) {
199 async_msg_4(exch, MOUSEEV_ABS_MOVE_EVENT,
200 shift_x, shift_y, move_x->logical_maximum, move_y->logical_maximum);
201 async_exchange_end(exch);
202 }
203
204 // Even if we move the mouse absolutely, we need to resolve wheel
205 shift_x = shift_y = 0;
206 }
207
208 if (shift_x || shift_y || shift_z) {
[a8c4e871]209 async_exch_t *exch =
210 async_exchange_begin(mouse_dev->mouse_sess);
[e3e0953]211 if (exch != NULL) {
[edb3cf2]212 async_msg_3(exch, MOUSEEV_MOVE_EVENT,
[ae303ad]213 shift_x, shift_y, shift_z);
[e3e0953]214 async_exchange_end(exch);
215 }
[e9f0348]216 }
[cc29622]217
[e3e0953]218 /* Buttons */
[a3a2fdb]219 usb_hid_report_path_t *path = usb_hid_report_path();
[e3e0953]220 if (path == NULL) {
[a1732929]221 usb_log_warning("Failed to create USB HID report path.");
[970f6e1]222 return;
[e3e0953]223 }
[5a6cc679]224 errno_t ret =
[18b6a88]225 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_BUTTON, 0);
[e3e0953]226 if (ret != EOK) {
227 usb_hid_report_path_free(path);
[a1732929]228 usb_log_warning("Failed to add buttons to report path.");
[970f6e1]229 return;
[e3e0953]230 }
[65c3794]231 usb_hid_report_path_set_report_id(path, hid_dev->report_id);
[cc29622]232
[a3a2fdb]233 usb_hid_report_field_t *field = usb_hid_report_get_sibling(
[18b6a88]234 &hid_dev->report, NULL, path, USB_HID_PATH_COMPARE_END |
235 USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY, USB_HID_REPORT_TYPE_INPUT);
[30710035]236
[310c4df]237 while (field != NULL) {
[a1732929]238 usb_log_debug2(NAME " VALUE(%X) USAGE(%X)", field->value,
[30710035]239 field->usage);
[5723ae49]240 assert(field->usage > field->usage_minimum);
241 const unsigned index = field->usage - field->usage_minimum;
242 assert(index < mouse_dev->buttons_count);
[e3e0953]243
[295f658]244 if (mouse_dev->buttons[index] != field->value) {
[5da7199]245 async_exch_t *exch =
246 async_exchange_begin(mouse_dev->mouse_sess);
[e3e0953]247 if (exch != NULL) {
248 async_req_2_0(exch, MOUSEEV_BUTTON_EVENT,
[295f658]249 field->usage, (field->value != 0) ? 1 : 0);
[e3e0953]250 async_exchange_end(exch);
251 mouse_dev->buttons[index] = field->value;
252 }
[1875a0c]253 }
[a8c4e871]254
[30710035]255 field = usb_hid_report_get_sibling(
[18b6a88]256 &hid_dev->report, field, path, USB_HID_PATH_COMPARE_END |
257 USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
[30710035]258 USB_HID_REPORT_TYPE_INPUT);
[e9f0348]259 }
[cc29622]260
[30710035]261 usb_hid_report_path_free(path);
[e9f0348]262}
[76fbd9a]263
[0f12c17]264#define FUN_UNBIND_DESTROY(fun) \
265if (fun) { \
266 if (ddf_fun_unbind((fun)) == EOK) { \
267 ddf_fun_destroy((fun)); \
268 } else { \
269 usb_log_error("Could not unbind function `%s', it " \
[56fd7cf]270 "will not be destroyed.\n", ddf_fun_get_name(fun)); \
[0f12c17]271 } \
[58563585]272} else (void) 0
[76fbd9a]273
[4093b14]274/** Get highest index of a button mentioned in given report.
275 *
276 * @param report HID report.
277 * @param report_id Report id we are interested in.
278 * @return Highest button mentioned in the report.
279 * @retval 1 No button was mentioned.
280 *
281 */
282static size_t usb_mouse_get_highest_button(usb_hid_report_t *report, uint8_t report_id)
283{
284 size_t highest_button = 0;
285
286 usb_hid_report_path_t *path = usb_hid_report_path();
287 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_BUTTON, 0);
288 usb_hid_report_path_set_report_id(path, report_id);
289
290 usb_hid_report_field_t *field = NULL;
291
292 /* Break from within. */
[76d0981d]293 while (true) {
[4093b14]294 field = usb_hid_report_get_sibling(
295 report, field, path,
296 USB_HID_PATH_COMPARE_END | USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
297 USB_HID_REPORT_TYPE_INPUT);
298 /* No more buttons? */
299 if (field == NULL) {
300 break;
301 }
302
303 size_t current_button = field->usage - field->usage_minimum;
304 if (current_button > highest_button) {
305 highest_button = current_button;
306 }
307 }
308
309 usb_hid_report_path_free(path);
310
311 return highest_button;
312}
[76fbd9a]313
[5a6cc679]314static errno_t mouse_dev_init(usb_mouse_t *mouse_dev, usb_hid_dev_t *hid_dev)
[e9f0348]315{
[4093b14]316 // FIXME: This may not be optimal since stupid hardware vendor may
317 // use buttons 1, 2, 3 and 6000 and we would allocate array of
318 // 6001*4B and use only 4 items in it.
319 // Since I doubt that hardware producers would do that, I think
320 // that the current solution is good enough.
321 /* Adding 1 because we will be accessing buttons[highest]. */
[a8c4e871]322 mouse_dev->buttons_count = 1 + usb_mouse_get_highest_button(
323 &hid_dev->report, hid_dev->report_id);
[4093b14]324 mouse_dev->buttons = calloc(mouse_dev->buttons_count, sizeof(int32_t));
[cc29622]325
[30710035]326 if (mouse_dev->buttons == NULL) {
[a1732929]327 usb_log_error(NAME ": out of memory, giving up on device!");
[30710035]328 free(mouse_dev);
329 return ENOMEM;
330 }
[4093b14]331
[e9f0348]332 // TODO: how to know if the device supports the request???
[c39e9fb]333 usbhid_req_set_idle(usb_device_get_default_pipe(hid_dev->usb_dev),
[8e10ef4]334 usb_device_get_iface_number(hid_dev->usb_dev), IDLE_RATE);
[38b4a25]335 return EOK;
336}
[cc29622]337
[5a6cc679]338errno_t usb_mouse_init(usb_hid_dev_t *hid_dev, void **data)
[38b4a25]339{
[a1732929]340 usb_log_debug("Initializing HID/Mouse structure...");
[38b4a25]341
342 if (hid_dev == NULL) {
[58563585]343 usb_log_error("Failed to init mouse structure: no structure"
[38b4a25]344 " given.\n");
345 return EINVAL;
346 }
347
348 /* Create the exposed function. */
[a1732929]349 usb_log_debug("Creating DDF function %s...", HID_MOUSE_FUN_NAME);
[6785b538]350 ddf_fun_t *fun = usb_device_ddf_fun_create(hid_dev->usb_dev,
351 fun_exposed, HID_MOUSE_FUN_NAME);
[38b4a25]352 if (fun == NULL) {
[a1732929]353 usb_log_error("Could not create DDF function node `%s'.",
[38b4a25]354 HID_MOUSE_FUN_NAME);
355 return ENOMEM;
[31cfee16]356 }
[cc29622]357
[38b4a25]358 usb_mouse_t *mouse_dev = ddf_fun_data_alloc(fun, sizeof(usb_mouse_t));
359 if (mouse_dev == NULL) {
[a1732929]360 usb_log_error("Failed to alloc HID mouse device structure.");
[38b4a25]361 ddf_fun_destroy(fun);
362 return ENOMEM;
363 }
364
[5a6cc679]365 errno_t ret = mouse_dev_init(mouse_dev, hid_dev);
[38b4a25]366 if (ret != EOK) {
[a1732929]367 usb_log_error("Failed to init HID mouse device structure.");
[38b4a25]368 return ret;
369 }
370
371 ddf_fun_set_ops(fun, &ops);
372
373 ret = ddf_fun_bind(fun);
374 if (ret != EOK) {
[a1732929]375 usb_log_error("Could not bind DDF function `%s': %s.",
[38b4a25]376 ddf_fun_get_name(fun), str_error(ret));
377 ddf_fun_destroy(fun);
378 return ret;
379 }
380
[a1732929]381 usb_log_debug("Adding DDF function `%s' to category %s...",
[38b4a25]382 ddf_fun_get_name(fun), HID_MOUSE_CATEGORY);
383 ret = ddf_fun_add_to_category(fun, HID_MOUSE_CATEGORY);
384 if (ret != EOK) {
385 usb_log_error(
386 "Could not add DDF function to category %s: %s.\n",
387 HID_MOUSE_CATEGORY, str_error(ret));
388 FUN_UNBIND_DESTROY(fun);
389 return ret;
390 }
391 mouse_dev->mouse_fun = fun;
392
[5723ae49]393 /* Save the Mouse device structure into the HID device structure. */
394 *data = mouse_dev;
395
[e9f0348]396 return EOK;
397}
[76fbd9a]398
[4d3c13e]399bool usb_mouse_polling_callback(usb_hid_dev_t *hid_dev, void *data)
[e9f0348]400{
[65b458c4]401 if (hid_dev == NULL || data == NULL) {
[e3e0953]402 usb_log_error(
403 "Missing argument to the mouse polling callback.\n");
[e9f0348]404 return false;
405 }
[cc29622]406
[5723ae49]407 usb_mouse_t *mouse_dev = data;
[970f6e1]408 usb_mouse_process_report(hid_dev, mouse_dev);
[5723ae49]409
[970f6e1]410 /* Continue polling until the device is about to be removed. */
[91173333]411 return true;
[e9f0348]412}
[76fbd9a]413
[65b458c4]414void usb_mouse_deinit(usb_hid_dev_t *hid_dev, void *data)
[e9f0348]415{
[5723ae49]416 if (data == NULL)
417 return;
418
419 usb_mouse_t *mouse_dev = data;
420
421 /* Hangup session to the console */
[622e7c9]422 if (mouse_dev->mouse_sess != NULL)
423 async_hangup(mouse_dev->mouse_sess);
[5723ae49]424
425 free(mouse_dev->buttons);
[38b4a25]426 FUN_UNBIND_DESTROY(mouse_dev->mouse_fun);
[e9f0348]427}
[76fbd9a]428
[5a6cc679]429errno_t usb_mouse_set_boot_protocol(usb_hid_dev_t *hid_dev)
[e9f0348]430{
[5a6cc679]431 errno_t rc = usb_hid_parse_report_descriptor(
[a8c4e871]432 &hid_dev->report, USB_MOUSE_BOOT_REPORT_DESCRIPTOR,
[e3c78efc]433 sizeof(USB_MOUSE_BOOT_REPORT_DESCRIPTOR));
[cc29622]434
[e9f0348]435 if (rc != EOK) {
[a1732929]436 usb_log_error("Failed to parse boot report descriptor: %s",
[e9f0348]437 str_error(rc));
438 return rc;
439 }
[cc29622]440
[c39e9fb]441 rc = usbhid_req_set_protocol(
442 usb_device_get_default_pipe(hid_dev->usb_dev),
[8e10ef4]443 usb_device_get_iface_number(hid_dev->usb_dev),
444 USB_HID_PROTOCOL_BOOT);
[cc29622]445
[e9f0348]446 if (rc != EOK) {
447 usb_log_warning("Failed to set boot protocol to the device: "
448 "%s\n", str_error(rc));
449 return rc;
450 }
[cc29622]451
[e9f0348]452 return EOK;
453}
454
455/**
456 * @}
457 */
Note: See TracBrowser for help on using the repository browser.