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
Line 
1/*
2 * Copyright (c) 2025 Jiri Svoboda
3 * Copyright (c) 2011 Lubos Slovak, Vojtech Horky
4 * Copyright (c) 2018 Ondrej Hlavaty
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>
41#include <usb/hid/hid.h>
42#include <usb/hid/request.h>
43#include <usb/hid/usages/core.h>
44#include <errno.h>
45#include <async.h>
46#include <stdbool.h>
47#include <str_error.h>
48#include <ipc/mouseev.h>
49
50#include <ipc/kbdev.h>
51#include <io/keycode.h>
52
53#include "mousedev.h"
54#include "../usbhid.h"
55
56#define NAME "mouse"
57
58static void default_connection_handler(ddf_fun_t *, ipc_call_t *);
59
60static ddf_dev_ops_t ops = { .default_handler = default_connection_handler };
61
62const usb_endpoint_description_t usb_hid_mouse_poll_endpoint_description = {
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";
72const char *HID_MOUSE_CATEGORY = "mouse";
73
74/** Default idle rate for mouses. */
75static const uint8_t IDLE_RATE = 0;
76
77static const uint8_t USB_MOUSE_BOOT_REPORT_DESCRIPTOR[] = {
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 *
108 * @param fun Device function handling the call.
109 * @param icall Call data.
110 *
111 */
112static void default_connection_handler(ddf_fun_t *fun, ipc_call_t *icall)
113{
114 usb_mouse_t *mouse_dev = ddf_fun_data_get(fun);
115
116 if (mouse_dev == NULL) {
117 usb_log_debug("%s: Missing parameters.", __FUNCTION__);
118 async_answer_0(icall, EINVAL);
119 return;
120 }
121
122 usb_log_debug("%s: fun->name: %s", __FUNCTION__, ddf_fun_get_name(fun));
123 usb_log_debug("%s: mouse_sess: %p",
124 __FUNCTION__, mouse_dev->mouse_sess);
125
126 async_sess_t *sess =
127 async_callback_receive_start(EXCHANGE_SERIALIZE, icall);
128 if (sess != NULL) {
129 if (mouse_dev->mouse_sess == NULL) {
130 mouse_dev->mouse_sess = sess;
131 usb_log_debug("Console session to %s set ok (%p).",
132 ddf_fun_get_name(fun), sess);
133 async_answer_0(icall, EOK);
134 } else {
135 usb_log_error("Console session to %s already set.",
136 ddf_fun_get_name(fun));
137 async_answer_0(icall, ELIMIT);
138 async_hangup(sess);
139 }
140 } else {
141 usb_log_debug("%s: Invalid function.", __FUNCTION__);
142 async_answer_0(icall, EINVAL);
143 }
144}
145
146static const usb_hid_report_field_t *get_mouse_axis_move_field(uint8_t rid, usb_hid_report_t *report,
147 int32_t usage)
148{
149 usb_hid_report_path_t *path = usb_hid_report_path();
150 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_GENERIC_DESKTOP,
151 usage);
152
153 usb_hid_report_path_set_report_id(path, rid);
154
155 const usb_hid_report_field_t *field = usb_hid_report_get_sibling(
156 report, NULL, path, USB_HID_PATH_COMPARE_END,
157 USB_HID_REPORT_TYPE_INPUT);
158
159 usb_hid_report_path_free(path);
160
161 return field;
162}
163
164static void usb_mouse_process_report(usb_hid_dev_t *hid_dev,
165 usb_mouse_t *mouse_dev)
166{
167 assert(mouse_dev != NULL);
168
169 if (mouse_dev->mouse_sess == NULL)
170 return;
171
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);
178 const usb_hid_report_field_t *wheel = get_mouse_axis_move_field(
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.");
188 return;
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) {
209 async_exch_t *exch =
210 async_exchange_begin(mouse_dev->mouse_sess);
211 if (exch != NULL) {
212 async_msg_3(exch, MOUSEEV_MOVE_EVENT,
213 shift_x, shift_y, shift_z);
214 async_exchange_end(exch);
215 }
216 }
217
218 /* Buttons */
219 usb_hid_report_path_t *path = usb_hid_report_path();
220 if (path == NULL) {
221 usb_log_warning("Failed to create USB HID report path.");
222 return;
223 }
224 errno_t ret =
225 usb_hid_report_path_append_item(path, USB_HIDUT_PAGE_BUTTON, 0);
226 if (ret != EOK) {
227 usb_hid_report_path_free(path);
228 usb_log_warning("Failed to add buttons to report path.");
229 return;
230 }
231 usb_hid_report_path_set_report_id(path, hid_dev->report_id);
232
233 usb_hid_report_field_t *field = usb_hid_report_get_sibling(
234 &hid_dev->report, NULL, path, USB_HID_PATH_COMPARE_END |
235 USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY, USB_HID_REPORT_TYPE_INPUT);
236
237 while (field != NULL) {
238 usb_log_debug2(NAME " VALUE(%X) USAGE(%X)", field->value,
239 field->usage);
240 assert(field->usage > field->usage_minimum);
241 const unsigned index = field->usage - field->usage_minimum;
242 assert(index < mouse_dev->buttons_count);
243
244 if (mouse_dev->buttons[index] != field->value) {
245 async_exch_t *exch =
246 async_exchange_begin(mouse_dev->mouse_sess);
247 if (exch != NULL) {
248 async_req_2_0(exch, MOUSEEV_BUTTON_EVENT,
249 field->usage, (field->value != 0) ? 1 : 0);
250 async_exchange_end(exch);
251 mouse_dev->buttons[index] = field->value;
252 }
253 }
254
255 field = usb_hid_report_get_sibling(
256 &hid_dev->report, field, path, USB_HID_PATH_COMPARE_END |
257 USB_HID_PATH_COMPARE_USAGE_PAGE_ONLY,
258 USB_HID_REPORT_TYPE_INPUT);
259 }
260
261 usb_hid_report_path_free(path);
262}
263
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 " \
270 "will not be destroyed.\n", ddf_fun_get_name(fun)); \
271 } \
272} else (void) 0
273
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. */
293 while (true) {
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}
313
314static errno_t mouse_dev_init(usb_mouse_t *mouse_dev, usb_hid_dev_t *hid_dev)
315{
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]. */
322 mouse_dev->buttons_count = 1 + usb_mouse_get_highest_button(
323 &hid_dev->report, hid_dev->report_id);
324 mouse_dev->buttons = calloc(mouse_dev->buttons_count, sizeof(int32_t));
325
326 if (mouse_dev->buttons == NULL) {
327 usb_log_error(NAME ": out of memory, giving up on device!");
328 free(mouse_dev);
329 return ENOMEM;
330 }
331
332 // TODO: how to know if the device supports the request???
333 usbhid_req_set_idle(usb_device_get_default_pipe(hid_dev->usb_dev),
334 usb_device_get_iface_number(hid_dev->usb_dev), IDLE_RATE);
335 return EOK;
336}
337
338errno_t usb_mouse_init(usb_hid_dev_t *hid_dev, void **data)
339{
340 usb_log_debug("Initializing HID/Mouse structure...");
341
342 if (hid_dev == NULL) {
343 usb_log_error("Failed to init mouse structure: no structure"
344 " given.\n");
345 return EINVAL;
346 }
347
348 /* Create the exposed function. */
349 usb_log_debug("Creating DDF function %s...", HID_MOUSE_FUN_NAME);
350 ddf_fun_t *fun = usb_device_ddf_fun_create(hid_dev->usb_dev,
351 fun_exposed, HID_MOUSE_FUN_NAME);
352 if (fun == NULL) {
353 usb_log_error("Could not create DDF function node `%s'.",
354 HID_MOUSE_FUN_NAME);
355 return ENOMEM;
356 }
357
358 usb_mouse_t *mouse_dev = ddf_fun_data_alloc(fun, sizeof(usb_mouse_t));
359 if (mouse_dev == NULL) {
360 usb_log_error("Failed to alloc HID mouse device structure.");
361 ddf_fun_destroy(fun);
362 return ENOMEM;
363 }
364
365 errno_t ret = mouse_dev_init(mouse_dev, hid_dev);
366 if (ret != EOK) {
367 usb_log_error("Failed to init HID mouse device structure.");
368 return ret;
369 }
370
371 ddf_fun_set_ops(fun, &ops);
372
373 ret = ddf_fun_bind(fun);
374 if (ret != EOK) {
375 usb_log_error("Could not bind DDF function `%s': %s.",
376 ddf_fun_get_name(fun), str_error(ret));
377 ddf_fun_destroy(fun);
378 return ret;
379 }
380
381 usb_log_debug("Adding DDF function `%s' to category %s...",
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
393 /* Save the Mouse device structure into the HID device structure. */
394 *data = mouse_dev;
395
396 return EOK;
397}
398
399bool usb_mouse_polling_callback(usb_hid_dev_t *hid_dev, void *data)
400{
401 if (hid_dev == NULL || data == NULL) {
402 usb_log_error(
403 "Missing argument to the mouse polling callback.\n");
404 return false;
405 }
406
407 usb_mouse_t *mouse_dev = data;
408 usb_mouse_process_report(hid_dev, mouse_dev);
409
410 /* Continue polling until the device is about to be removed. */
411 return true;
412}
413
414void usb_mouse_deinit(usb_hid_dev_t *hid_dev, void *data)
415{
416 if (data == NULL)
417 return;
418
419 usb_mouse_t *mouse_dev = data;
420
421 /* Hangup session to the console */
422 if (mouse_dev->mouse_sess != NULL)
423 async_hangup(mouse_dev->mouse_sess);
424
425 free(mouse_dev->buttons);
426 FUN_UNBIND_DESTROY(mouse_dev->mouse_fun);
427}
428
429errno_t usb_mouse_set_boot_protocol(usb_hid_dev_t *hid_dev)
430{
431 errno_t rc = usb_hid_parse_report_descriptor(
432 &hid_dev->report, USB_MOUSE_BOOT_REPORT_DESCRIPTOR,
433 sizeof(USB_MOUSE_BOOT_REPORT_DESCRIPTOR));
434
435 if (rc != EOK) {
436 usb_log_error("Failed to parse boot report descriptor: %s",
437 str_error(rc));
438 return rc;
439 }
440
441 rc = usbhid_req_set_protocol(
442 usb_device_get_default_pipe(hid_dev->usb_dev),
443 usb_device_get_iface_number(hid_dev->usb_dev),
444 USB_HID_PROTOCOL_BOOT);
445
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 }
451
452 return EOK;
453}
454
455/**
456 * @}
457 */
Note: See TracBrowser for help on using the repository browser.