From 3d02764ca96b5fa7f9fdbef19086a3db97e7f528 Mon Sep 17 00:00:00 2001 From: Matheus Date: Tue, 12 May 2026 00:25:45 -0300 Subject: [PATCH] usb: initial hid implementation --- kernel-src/include/kernel/usb_hid.h | 100 +++ kernel-src/io/usb/class/hid.c | 309 ++++++++ kernel-src/io/usb/class/hid_parser.c | 1011 ++++++++++++++++++++++++++ 3 files changed, 1420 insertions(+) create mode 100644 kernel-src/include/kernel/usb_hid.h create mode 100644 kernel-src/io/usb/class/hid.c create mode 100644 kernel-src/io/usb/class/hid_parser.c diff --git a/kernel-src/include/kernel/usb_hid.h b/kernel-src/include/kernel/usb_hid.h new file mode 100644 index 00000000..d8e25ce9 --- /dev/null +++ b/kernel-src/include/kernel/usb_hid.h @@ -0,0 +1,100 @@ +#ifndef _USB_HID_H +#define _USB_HID_H + +#include +#include +#include + +#define HID_USAGE_PAGE_GENERIC_DESKTOP 0x01 +#define HID_USAGE_PAGE_SIMULATION 0x02 +#define HID_USAGE_PAGE_VR 0x03 +#define HID_USAGE_PAGE_SPORT 0x04 +#define HID_USAGE_PAGE_GAME 0x05 +#define HID_USAGE_PAGE_GENERIC_DEVICE 0x06 +#define HID_USAGE_PAGE_KEYBOARD_KEYPAD 0x07 +#define HID_USAGE_PAGE_LED 0x08 +#define HID_USAGE_PAGE_BUTTON 0x09 +#define HID_USAGE_PAGE_ORDINAL 0x0a +#define HID_USAGE_PAGE_TELEPHONY 0x0b +#define HID_USAGE_PAGE_CONSUMER 0x0c +#define HID_USAGE_PAGE_DIGITIZER 0x0d +#define HID_USAGE_PAGE_PID 0x0f +#define HID_USAGE_PAGE_UNICODE 0x10 +#define HID_USAGE_PAGE_SENSOR 0x20 +#define HID_USAGE_PAGE_VENDOR_MIN 0xff00 +#define HID_USAGE_PAGE_VENDOR_MAX 0xffff + +#define HID_GD_POINTER 0x01 +#define HID_GD_MOUSE 0x02 +#define HID_GD_JOYSTICK 0x04 +#define HID_GD_GAMEPAD 0x05 +#define HID_GD_KEYBOARD 0x06 +#define HID_GD_KEYPAD 0x07 +#define HID_GD_MULTI_AXIS_CONTROLLER 0x08 + +typedef struct { + uint32_t usage; + uint32_t minimum; + uint32_t maximum; +} hid_usage_t; + +#define USAGE_PAGE(x) (((x) >> 16) & 0xffff) +#define USAGE(x) ((x) & 0xffff) + +#define HID_INPUT_CONSTANT (1u << 0) +#define HID_INPUT_VARIABLE (1u << 1) +#define HID_INPUT_RELATIVE (1u << 2) +#define HID_INPUT_WRAP (1u << 3) +#define HID_INPUT_NON_LINEAR (1u << 4) +#define HID_INPUT_NO_PREFERRED (1u << 5) +#define HID_INPUT_NULL_STATE (1u << 6) +#define HID_INPUT_VOLATILE (1u << 7) +#define HID_INPUT_BUFFERED_BYTES (1u << 8) + +typedef struct { + unsigned int application_id; + + hid_usage_t *usages; + size_t usage_count; + long logical_maximum; + long logical_minimum; + uint16_t flags; + + size_t bit_offset; + size_t bit_size; + size_t count; +} hid_input_t; + +typedef struct { + unsigned int report_id; + hid_input_t *inputs; + size_t input_count; + size_t bit_size; +} hid_report_t; + +typedef struct { + uint32_t usage; + input_device_t *input_device; + uint64_t pressed_keys[4]; + uint64_t pressed_buttons; +} hid_application_t; + +typedef struct { + union { + hid_report_t *reports; + hid_input_t *inputs; + }; + size_t report_count; + size_t input_count; + size_t input_bit_size; + + hid_application_t *applications; + size_t application_count; +} hid_parser_t; + +int hid_parser_init(hid_parser_t *parser, void *data, size_t size); +hid_report_t *hid_get_report_from_id(hid_parser_t *parser, unsigned int report_id); +void hid_parse_report(hid_parser_t *parser, hid_report_t *report, uint8_t *data, size_t size); +void hid_advertise_events(hid_parser_t *parser); + +#endif diff --git a/kernel-src/io/usb/class/hid.c b/kernel-src/io/usb/class/hid.c new file mode 100644 index 00000000..76a84248 --- /dev/null +++ b/kernel-src/io/usb/class/hid.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include + +// TODO proper cleanup + +#define USB_INTERFACE_CLASS_HID 0x03 + +#define USB_DESC_TYPE_HID 0x21 +#define USB_DESC_TYPE_HID_REPORT 0x22 +#define USB_DESC_TYPE_HID_PHYSICAL 0x23 + +typedef struct { + uint8_t bDescriptorType; + uint16_t wDescriptorLength; +} __attribute__((packed)) usb_hid_class_desc_t; + +typedef struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHID; + uint8_t bCountryCode; + uint8_t bNumDescriptors; + usb_hid_class_desc_t descriptors[]; +} __attribute__((packed)) usb_hid_desc_t; + + +typedef struct { + uint8_t *report_descriptor; + size_t report_descriptor_length; + hid_parser_t parser; + usb_endpoint_t *interrupt_in_endpoint; + size_t interrupt_in_packet_size; + void *data_buffer; +} hid_driver_t; + +#define HID_GD_POINTER 0x01 +#define HID_GD_MOUSE 0x02 +#define HID_GD_JOYSTICK 0x04 +#define HID_GD_GAMEPAD 0x05 +#define HID_GD_KEYBOARD 0x06 +#define HID_GD_KEYPAD 0x07 +#define HID_GD_MULTI_AXIS_CONTROLLER 0x08 + +static inline const char *get_pretty_name_gd(uint32_t usage) { + switch (USAGE(usage)) { + case HID_GD_POINTER: + return "HID Pointer"; + case HID_GD_MOUSE: + return "HID Mouse"; + case HID_GD_JOYSTICK: + return "HID Joystick"; + case HID_GD_GAMEPAD: + return "HID Gamepad"; + case HID_GD_KEYBOARD: + return "HID Keyboard"; + case HID_GD_KEYPAD: + return "HID Keypad"; + case HID_GD_MULTI_AXIS_CONTROLLER: + return "HID Multi Axis Controller"; + } + + return "HID Device (GD)"; +} + +static usb_hid_desc_t *hid_find_descriptor(usb_device_t *dev, usb_interface_t *interface) { + bool in_interface = false; + + usb_for_each_descriptor(dev->config_desc, desc) { + USB_FOR_EACH_DESCRIPTOR_CHECK(dev->config_desc, desc) { + return NULL; + } + + if (desc == (usb_desc_hdr_t *)interface->desc) { + in_interface = true; + continue; + } + + if (!in_interface) + continue; + + if (desc->bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE) + break; + + if (desc->bDescriptorType != USB_DESC_TYPE_HID) + continue; + + if (desc->bLength < sizeof(usb_hid_desc_t)) + return NULL; + + usb_hid_desc_t *usb_hid_desc = (usb_hid_desc_t *)desc; + size_t expected_length = sizeof(usb_hid_desc_t) + usb_hid_desc->bNumDescriptors * sizeof(usb_hid_class_desc_t); + if (desc->bLength < expected_length) + return NULL; + + return usb_hid_desc; + } + + return NULL; +} + +hid_report_t *hid_get_report_from_id(hid_parser_t *parser, unsigned int report_id) { + hid_report_t *report = NULL; + + for (size_t i = 0; i < parser->report_count; ++i) { + if (parser->reports[i].report_id == report_id) { + report = &parser->reports[i]; + break; + } + } + + return report; +} + +static void interrupt_in_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + hid_driver_t *hid_driver = ctx; + + if (status != USB_STATUS_SUCCESS && status != USB_STATUS_SHORT_PACKET) { + printf("hid: interrupt transfer failed. retrying\n"); + } else { + if (transferred == 0) + goto resend; + + uint8_t *data = hid_driver->data_buffer; + hid_report_t *report = NULL; + if (hid_driver->parser.report_count) { + report = hid_get_report_from_id(&hid_driver->parser, data[0]); + if (report == NULL) { + printf("hid: got unknown report. ignoring\n"); + goto resend; + } + + ++data; + --transferred; + } + + hid_parse_report(&hid_driver->parser, report, data, transferred); + } + +resend: + usb_xfer_t xfer = { + .ep = hid_driver->interrupt_in_endpoint, + .flags = USB_XFER_FLAG_TO_HOST, + .type = USB_XFER_TYPE_INTERRUPT, + .data = hid_driver->data_buffer, + .data_length = hid_driver->interrupt_in_packet_size, + .completion = interrupt_in_callback, + .completion_ctx = hid_driver + }; + + int error = usb_submit_xfer(dev, &xfer); + if (error) { + printf("hid: failed to submit HID interrupt xfer: %s\n", strerror(error)); + } +} + +static void get_report_descriptor_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) { + if (status != USB_STATUS_SUCCESS) { + printf("hid: failed to get report descriptor\n"); + return; + } + + usb_interface_t *interface = ctx; + hid_driver_t *hid_driver = interface->driver_data; + + if (transferred != hid_driver->report_descriptor_length) { + printf("hid: transferred != hid_driver->report_descriptor_length\n"); + return; + } + + for (int i = 0; i < interface->desc->bNumEndpoints; ++i) { + if (interface->endpoints[i].desc && (interface->endpoints[i].desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_DIR_IN) && (interface->endpoints[i].desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK) == USB_ENDPOINT_ATTRIB_TYPE_INTR) + hid_driver->interrupt_in_endpoint = &interface->endpoints[i]; + } + + if (hid_driver->interrupt_in_endpoint == NULL) { + printf("hid: no IN interrupt endpoint found\n"); + return; + } + + int error = hid_parser_init(&hid_driver->parser, hid_driver->report_descriptor, hid_driver->report_descriptor_length); + if (error) { + printf("hid: failed to parse report descriptor\n"); + return; + } + + printf("hid: %lu applications with %lu %s%s\n", + hid_driver->parser.application_count, + hid_driver->parser.report_count ? hid_driver->parser.report_count : hid_driver->parser.input_count, + hid_driver->parser.report_count ? "report" : "anonymous input", + (hid_driver->parser.report_count + hid_driver->parser.input_count != 1) ? "s" : "" + ); + + hid_driver->interrupt_in_packet_size = usb_endpoint_max_packet_size(hid_driver->interrupt_in_endpoint->desc); + hid_driver->data_buffer = alloc(hid_driver->interrupt_in_packet_size); + if (hid_driver->data_buffer == NULL) { + printf("hid: out of memory\n"); + return; + } + + for (size_t i = 0; i < hid_driver->parser.application_count; ++i) { + hid_application_t *application = &hid_driver->parser.applications[i]; + const char *pretty_name = "HID Device"; + if (USAGE_PAGE(application->usage) == HID_USAGE_PAGE_GENERIC_DESKTOP) + pretty_name = get_pretty_name_gd(application->usage); + + application->input_device = input_new(); + if (application->input_device == NULL) { + printf("hid: failed to create new input device\n"); + return; + } + + application->input_device->id_bus = INPUT_DEVICE_BUS_USB; + application->input_device->id_vendor = dev->desc.idVendor; + application->input_device->id_product = dev->desc.idProduct; + snprintf(application->input_device->name, sizeof(application->input_device->name), pretty_name); + application->input_device->id_version = 1; + application->input_device->ver_major = 1; + application->input_device->ver_minor = 0; + application->input_device->ver_patch = 0; + } + + hid_advertise_events(&hid_driver->parser); + + usb_xfer_t xfer = { + .ep = hid_driver->interrupt_in_endpoint, + .flags = USB_XFER_FLAG_TO_HOST, + .type = USB_XFER_TYPE_INTERRUPT, + .data = hid_driver->data_buffer, + .data_length = hid_driver->interrupt_in_packet_size, + .completion = interrupt_in_callback, + .completion_ctx = hid_driver + }; + + error = usb_submit_xfer(dev, &xfer); + if (error) { + printf("hid: failed to submit initial hid xfer: %s\n", strerror(error)); + } +} + +static int hid_attach(usb_device_t *dev, usb_interface_t *interface) { + usb_hid_desc_t *usb_hid_desc = hid_find_descriptor(dev, interface); + if (usb_hid_desc == NULL) { + printf("hid: invalid interface descriptor\n"); + return EINVAL; + } + + usb_hid_class_desc_t *usb_hid_class_desc = NULL; + for (int i = 0; i < usb_hid_desc->bNumDescriptors; ++i) { + if (usb_hid_desc->descriptors[i].bDescriptorType != USB_DESC_TYPE_HID_REPORT) + continue; + + usb_hid_class_desc = &usb_hid_desc->descriptors[i]; + break; + } + + if (usb_hid_class_desc == NULL) { + printf("hid: no report descriptor\n"); + return EINVAL; + } + + hid_driver_t *hid_driver = alloc(sizeof(hid_driver_t)); + if (hid_driver == NULL) { + printf("hid: out of memory\n"); + return ENOMEM; + } + + hid_driver->report_descriptor_length = usb_hid_class_desc->wDescriptorLength; + if (hid_driver->report_descriptor_length == 0) { + printf("hid: invalid report descriptor length\n"); + free(hid_driver); + return EINVAL; + } + + hid_driver->report_descriptor = alloc(hid_driver->report_descriptor_length); + if (hid_driver->report_descriptor == NULL) { + printf("hid: out of memory\n"); + free(hid_driver); + return ENOMEM; + } + + interface->driver_data = hid_driver; + + int error = usb_get_interface_descriptor(dev, interface->desc->bInterfaceNumber, USB_DESC_TYPE_HID_REPORT, 0, hid_driver->report_descriptor, hid_driver->report_descriptor_length, get_report_descriptor_callback, interface); + if (error) { + interface->driver_data = NULL; + free(hid_driver->report_descriptor); + free(hid_driver); + } + + return error; +} + +static int hid_probe(usb_device_t *, usb_interface_t *interface) { + + if (interface->desc->bInterfaceClass == USB_INTERFACE_CLASS_HID) + return USB_DRIVER_SCORE_CLASS_MATCH; + + return USB_DRIVER_SCORE_NONE; +} + +DEFINE_USB_CLASS_DRIVER(hid_driver, + .name = "USB Human Interface Device", + .probe = hid_probe, + .attach = hid_attach + // TODO detach +); diff --git a/kernel-src/io/usb/class/hid_parser.c b/kernel-src/io/usb/class/hid_parser.c new file mode 100644 index 00000000..0880ee82 --- /dev/null +++ b/kernel-src/io/usb/class/hid_parser.c @@ -0,0 +1,1011 @@ +#include +#include +#include +#include + +#define HID_ITEM_LONG_PREFIX 0xfe + +#define HID_ITEM_TYPE_MAIN 0x00 +#define HID_ITEM_TYPE_GLOBAL 0x01 +#define HID_ITEM_TYPE_LOCAL 0x02 +#define HID_ITEM_TYPE_RESERVED 0x03 + +#define HID_MAIN_ITEM_INPUT 0x08 +#define HID_MAIN_ITEM_OUTPUT 0x09 +#define HID_MAIN_ITEM_COLLECTION 0x0a +#define HID_MAIN_ITEM_FEATURE 0x0b +#define HID_MAIN_ITEM_END_COLLECTION 0x0c + +#define HID_GLOBAL_ITEM_USAGE_PAGE 0x00 +#define HID_GLOBAL_ITEM_LOGICAL_MIN 0x01 +#define HID_GLOBAL_ITEM_LOGICAL_MAX 0x02 +#define HID_GLOBAL_ITEM_PHYSICAL_MIN 0x03 +#define HID_GLOBAL_ITEM_PHYSICAL_MAX 0x04 +#define HID_GLOBAL_ITEM_UNIT_EXPONENT 0x05 +#define HID_GLOBAL_ITEM_UNIT 0x06 +#define HID_GLOBAL_ITEM_REPORT_SIZE 0x07 +#define HID_GLOBAL_ITEM_REPORT_ID 0x08 +#define HID_GLOBAL_ITEM_REPORT_COUNT 0x09 +#define HID_GLOBAL_ITEM_PUSH 0x0a +#define HID_GLOBAL_ITEM_POP 0x0b + +#define HID_LOCAL_ITEM_USAGE 0x00 +#define HID_LOCAL_ITEM_USAGE_MIN 0x01 +#define HID_LOCAL_ITEM_USAGE_MAX 0x02 +#define HID_LOCAL_ITEM_DESIGNATOR_INDEX 0x03 +#define HID_LOCAL_ITEM_DESIGNATOR_MIN 0x04 +#define HID_LOCAL_ITEM_DESIGNATOR_MAX 0x05 +#define HID_LOCAL_ITEM_STRING_INDEX 0x07 +#define HID_LOCAL_ITEM_STRING_MIN 0x08 +#define HID_LOCAL_ITEM_STRING_MAX 0x09 +#define HID_LOCAL_ITEM_DELIMITER 0x0a + +#define HID_COLLECTION_PHYSICAL 0x00 +#define HID_COLLECTION_APPLICATION 0x01 +#define HID_COLLECTION_LOGICAL 0x02 +#define HID_COLLECTION_REPORT 0x03 +#define HID_COLLECTION_NAMED_ARRAY 0x04 +#define HID_COLLECTION_USAGE_SWITCH 0x05 +#define HID_COLLECTION_USAGE_MODIFIER 0x06 + +#define HID_GD_X 0x30 +#define HID_GD_Y 0x31 +#define HID_GD_Z 0x32 +#define HID_GD_RX 0x33 +#define HID_GD_RY 0x34 +#define HID_GD_RZ 0x35 +#define HID_GD_SLIDER 0x36 +#define HID_GD_DIAL 0x37 +#define HID_GD_WHEEL 0x38 +#define HID_GD_HAT_SWITCH 0x39 + +#define HID_BUTTON_PRIMARY 0x01 +#define HID_BUTTON_SECONDARY 0x02 +#define HID_BUTTON_TERTIARY 0x03 +#define HID_BUTTON_SIDE 0x04 +#define HID_BUTTON_EXTRA 0x05 +#define HID_BUTTON_MAX HID_BUTTON_EXTRA + +#define HID_KEYBOARD_KEYPAD_MAX 0xe7 + +static inline void decode_item(uint8_t item, uint8_t *tag, uint8_t *type, uint8_t *size) { + *size = item & 0x3; + *type = (item >> 2) & 0x3; + *tag = (item >> 4) & 0xf; +} + +static inline uint32_t get_data_u(uint8_t *data, size_t size) { + switch (size) { + case 0: + return 0; + case 1: + return data[0]; + case 2: + return (uint32_t)data[0] | + ((uint32_t)data[1] << 8); + case 4: + return (uint32_t)data[0] | + ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | + ((uint32_t)data[3] << 24); + default: + __assert(!"Corruption in HID driver"); + } +} + +static inline int32_t get_data_i(uint8_t *data, size_t size) { + switch (size) { + case 0: + return 0; + case 1: + return (int32_t)(int8_t)data[0]; + case 2: + return (int32_t)(int16_t)((uint16_t)data[0] | + ((uint16_t)data[1] << 8)); + case 4: + return (int32_t)((uint32_t)data[0] | + ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16) | + ((uint32_t)data[3] << 24)); + default: + __assert(!"Corruption in HID driver"); + } +} + +static inline size_t translate_size(uint8_t size) { + switch (size) { + case 0: + case 1: + case 2: + return size; + case 3: + return 4; + default: + __assert(!"Corruption in HID driver"); + } +} + +typedef struct { + uint32_t usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + uint32_t unit; + uint32_t report_size; + uint32_t report_id; + uint32_t report_count; +} hid_global_state_t; + +static int handle_global(hid_global_state_t *global_state, uint8_t tag, uint8_t *item, size_t actual_size) { + uint32_t udata = get_data_u(item + 1, actual_size); + int32_t idata = get_data_i(item + 1, actual_size); + + switch (tag) { + case HID_GLOBAL_ITEM_USAGE_PAGE: + global_state->usage_page = udata; + break; + case HID_GLOBAL_ITEM_LOGICAL_MIN: + global_state->logical_minimum = idata; + break; + case HID_GLOBAL_ITEM_LOGICAL_MAX: + global_state->logical_maximum = idata; + break; + case HID_GLOBAL_ITEM_PHYSICAL_MIN: + global_state->physical_minimum = idata; + break; + case HID_GLOBAL_ITEM_PHYSICAL_MAX: + global_state->physical_maximum = idata; + break; + case HID_GLOBAL_ITEM_UNIT_EXPONENT: + global_state->unit_exponent = idata; + break; + case HID_GLOBAL_ITEM_UNIT: + global_state->unit = udata; + break; + case HID_GLOBAL_ITEM_REPORT_SIZE: + global_state->report_size = udata; + break; + case HID_GLOBAL_ITEM_REPORT_ID: + if (udata == 0 || udata > 0xff) + return EINVAL; + + global_state->report_id = udata; + break; + case HID_GLOBAL_ITEM_REPORT_COUNT: + global_state->report_count = udata; + break; + default: + return EINVAL; + } + + return 0; +} +#define HID_LOCAL_USAGE_CAPACITY 128 + +typedef struct { + hid_usage_t usages[HID_LOCAL_USAGE_CAPACITY]; + size_t usage_count; + + bool has_usage_minimum; + uint32_t usage_minimum; + + bool has_designator_index; + bool has_designator_minimum; + bool has_designator_maximum; + uint32_t designator_index; + uint32_t designator_minimum; + uint32_t designator_maximum; + + bool has_string_index; + bool has_string_minimum; + bool has_string_maximum; + uint32_t string_index; + uint32_t string_minimum; + uint32_t string_maximum; +} hid_local_state_t; + +static inline hid_usage_t hid_make_usage(uint32_t current_page, uint32_t raw_usage, size_t actual_size) { + hid_usage_t usage = { + .usage = actual_size <= 2 ? ((current_page << 16) | raw_usage) : raw_usage, + + }; + + return usage; +} + +static inline hid_usage_t hid_make_usage_range(hid_global_state_t *global_state, uint32_t min, uint32_t max) { + hid_usage_t usage = { + .minimum = min, + .maximum = max, + .usage = global_state->usage_page << 16 + }; + + return usage; +} + +static int hid_local_add_usage(hid_local_state_t *local_state, hid_usage_t usage) { + if (local_state->usage_count >= HID_LOCAL_USAGE_CAPACITY) + return E2BIG; + + local_state->usages[local_state->usage_count++] = usage; + + return 0; +} + +static int handle_local(hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t tag, uint8_t *item, size_t actual_size) { + uint32_t udata = get_data_u(item + 1, actual_size); + + switch (tag) { + case HID_LOCAL_ITEM_USAGE: { + hid_usage_t usage = hid_make_usage(global_state->usage_page, udata, actual_size); + return hid_local_add_usage(local_state, usage); + } + case HID_LOCAL_ITEM_USAGE_MIN: + local_state->has_usage_minimum = true; + local_state->usage_minimum = udata; + break; + case HID_LOCAL_ITEM_USAGE_MAX: + if (local_state->has_usage_minimum == false) + return EINVAL; + + hid_usage_t usage = hid_make_usage_range(global_state, local_state->usage_minimum, udata); + local_state->has_usage_minimum = false; + return hid_local_add_usage(local_state, usage); + case HID_LOCAL_ITEM_DESIGNATOR_INDEX: + local_state->has_designator_index = true; + local_state->designator_index = udata; + break; + case HID_LOCAL_ITEM_DESIGNATOR_MIN: + local_state->has_designator_minimum = true; + local_state->designator_minimum = udata; + break; + case HID_LOCAL_ITEM_DESIGNATOR_MAX: + local_state->has_designator_maximum = true; + local_state->designator_maximum = udata; + break; + case HID_LOCAL_ITEM_STRING_INDEX: + local_state->has_string_index = true; + local_state->string_index = udata; + break; + case HID_LOCAL_ITEM_STRING_MIN: + local_state->has_string_minimum = true; + local_state->string_minimum = udata; + break; + case HID_LOCAL_ITEM_STRING_MAX: + local_state->has_string_maximum = true; + local_state->string_maximum = udata; + break; + case HID_LOCAL_ITEM_DELIMITER: + return ENOTSUP; + } + + return 0; +} + +typedef struct { + unsigned int current_depth; +} hid_collection_state_t; + +static int handle_collection(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t type) { + // only accept root application collections + if (collection_state->current_depth == 0 && type != HID_COLLECTION_APPLICATION) + return EINVAL; + + // currently ignore all non-root collections + if (collection_state->current_depth++ != 0) + return 0; + + // check if we have valid usage state before proceeding + if (local_state->usage_count != 1) + return EINVAL; + + // add a new application to the parser + void *p; + if (parser->applications == NULL) + p = alloc(sizeof(hid_application_t)); + else + p = realloc(parser->applications, (parser->application_count + 1) * sizeof(hid_application_t)); + + if (p == NULL) + return ENOMEM; + + parser->applications = p; + parser->applications[parser->application_count].usage = local_state->usages[0].usage; + ++parser->application_count; + return 0; +} + +static int handle_collection_end(hid_collection_state_t *collection_state) { + if (collection_state->current_depth == 0) + return EINVAL; + + --collection_state->current_depth; + + return 0; +} + +static int insert_report(hid_parser_t *parser, unsigned int id, hid_report_t **report) { + void *p = parser->reports ? realloc(parser->reports, sizeof(hid_report_t) * (parser->report_count + 1)) : alloc(sizeof(hid_report_t)); + if (p == NULL) + return ENOMEM; + + parser->reports = p; + *report = &parser->reports[parser->report_count++]; + + (*report)->report_id = id; + return 0; +} + +static int insert_input(hid_parser_t *parser, hid_input_t **inputp, size_t *input_count, size_t *report_bit_size, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint16_t flags) { + size_t field_bit_size = global_state->report_size * global_state->report_count; + if (global_state->report_count && field_bit_size / global_state->report_count != global_state->report_size) + return EOVERFLOW; + if (*report_bit_size + field_bit_size < *report_bit_size) + return EOVERFLOW; + + void *p = *inputp ? realloc(*inputp, sizeof(hid_input_t) * (*input_count + 1)) : alloc(sizeof(hid_input_t)); + if (p == NULL) + return ENOMEM; + + *inputp = p; + hid_input_t *input = &(*inputp)[(*input_count)++]; + input->application_id = parser->application_count - 1; + input->bit_offset = *report_bit_size; + if (local_state->usage_count) { + input->usages = alloc(local_state->usage_count * sizeof(hid_usage_t)); + if (input->usages == NULL) { + // TODO proper cleanup here + return ENOMEM; + } + memcpy(input->usages, local_state->usages, local_state->usage_count * sizeof(hid_usage_t)); + input->usage_count = local_state->usage_count; + } + input->logical_maximum = global_state->logical_maximum; + input->logical_minimum = global_state->logical_minimum; + input->bit_size = global_state->report_size; + input->count = global_state->report_count; + input->flags = flags; + *report_bit_size += field_bit_size; + + return 0; +} + +static int handle_input(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint16_t flags) { + if (collection_state->current_depth == 0) + return EINVAL; + + if (global_state->report_id == 0) { + // add it to the input list without being gated behind a report id + if (parser->report_count) + return EINVAL; + + return insert_input(parser, &parser->inputs, &parser->input_count, &parser->input_bit_size, collection_state, global_state, local_state, flags); + } + + if (parser->input_count) + return EINVAL; + + hid_report_t *report = hid_get_report_from_id(parser, global_state->report_id); + if (report == NULL) { + // we are guaranteed to have a valid report_id here + int error = insert_report(parser, global_state->report_id, &report); + if (error) + return error; + } + + return insert_input(parser, &report->inputs, &report->input_count, &report->bit_size, collection_state, global_state, local_state, flags); +} + +static int handle_main(hid_parser_t *parser, hid_collection_state_t *collection_state, hid_global_state_t *global_state, hid_local_state_t *local_state, uint8_t tag, uint8_t *item, size_t actual_size) { + uint32_t udata = get_data_u(item + 1, actual_size); + + int error = 0; + switch (tag) { + case HID_MAIN_ITEM_INPUT: + error = handle_input(parser, collection_state, global_state, local_state, udata); + break; + case HID_MAIN_ITEM_COLLECTION: + error = handle_collection(parser, collection_state, global_state, local_state, udata); + break; + case HID_MAIN_ITEM_END_COLLECTION: + error = handle_collection_end(collection_state); + break; + } + + memset(local_state, 0, sizeof(hid_local_state_t)); + return error; +} + +int hid_parser_init(hid_parser_t *parser, void *data, size_t data_size) { + uint8_t *item = data; + size_t current_offset = 0; + uint8_t tag, type, size; + + hid_global_state_t globals = {0}; + hid_local_state_t locals = {0}; + hid_collection_state_t collection_state = {0}; + + // TODO support PUSH and POP operations + while (current_offset < data_size) { + // TODO: if item 0xfe, it is a long item and will be ignored for now. + decode_item(item[current_offset], &tag, &type, &size); + size_t actual_size = translate_size(size); + if (current_offset + 1 + actual_size > data_size) { + printf("hid: short item exceeds report descriptor length\n"); + return EINVAL; + } + + int error = 0; + switch (type) { + case HID_ITEM_TYPE_GLOBAL: + error = handle_global(&globals, tag, &item[current_offset], actual_size); + break; + case HID_ITEM_TYPE_LOCAL: + error = handle_local(&globals, &locals, tag, &item[current_offset], actual_size); + break; + case HID_ITEM_TYPE_MAIN: + error = handle_main(parser, &collection_state, &globals, &locals, tag, &item[current_offset], actual_size); + break; + } + + if (error) { + // TODO eventual cleanup + printf("hid: parsing returned an error: %s\n", strerror(error)); + return error; + } + + current_offset += 1 + actual_size; + } + + if (collection_state.current_depth != 0) { + printf("hid: unclosed collection in report descriptor\n"); + return EINVAL; + } + + return 0; +} + +#define EVENT_EMISSION_LIMIT 32 + +static const uint16_t hid_keyboard_keypad_to_input_key_table[HID_KEYBOARD_KEYPAD_MAX + 1] = { + [0x04] = INPUT_KEY_A, + [0x05] = INPUT_KEY_B, + [0x06] = INPUT_KEY_C, + [0x07] = INPUT_KEY_D, + [0x08] = INPUT_KEY_E, + [0x09] = INPUT_KEY_F, + [0x0a] = INPUT_KEY_G, + [0x0b] = INPUT_KEY_H, + [0x0c] = INPUT_KEY_I, + [0x0d] = INPUT_KEY_J, + [0x0e] = INPUT_KEY_K, + [0x0f] = INPUT_KEY_L, + [0x10] = INPUT_KEY_M, + [0x11] = INPUT_KEY_N, + [0x12] = INPUT_KEY_O, + [0x13] = INPUT_KEY_P, + [0x14] = INPUT_KEY_Q, + [0x15] = INPUT_KEY_R, + [0x16] = INPUT_KEY_S, + [0x17] = INPUT_KEY_T, + [0x18] = INPUT_KEY_U, + [0x19] = INPUT_KEY_V, + [0x1a] = INPUT_KEY_W, + [0x1b] = INPUT_KEY_X, + [0x1c] = INPUT_KEY_Y, + [0x1d] = INPUT_KEY_Z, + [0x1e] = INPUT_KEY_1, + [0x1f] = INPUT_KEY_2, + [0x20] = INPUT_KEY_3, + [0x21] = INPUT_KEY_4, + [0x22] = INPUT_KEY_5, + [0x23] = INPUT_KEY_6, + [0x24] = INPUT_KEY_7, + [0x25] = INPUT_KEY_8, + [0x26] = INPUT_KEY_9, + [0x27] = INPUT_KEY_0, + [0x28] = INPUT_KEY_ENTER, + [0x29] = INPUT_KEY_ESC, + [0x2a] = INPUT_KEY_BACKSPACE, + [0x2b] = INPUT_KEY_TAB, + [0x2c] = INPUT_KEY_SPACE, + [0x2d] = INPUT_KEY_MINUS, + [0x2e] = INPUT_KEY_EQUAL, + [0x2f] = INPUT_KEY_LEFTBRACE, + [0x30] = INPUT_KEY_RIGHTBRACE, + [0x31] = INPUT_KEY_BACKSLASH, + [0x33] = INPUT_KEY_SEMICOLON, + [0x34] = INPUT_KEY_APOSTROPHE, + [0x35] = INPUT_KEY_GRAVE, + [0x36] = INPUT_KEY_COMMA, + [0x37] = INPUT_KEY_DOT, + [0x38] = INPUT_KEY_SLASH, + [0x39] = INPUT_KEY_CAPSLOCK, + [0x3a] = INPUT_KEY_F1, + [0x3b] = INPUT_KEY_F2, + [0x3c] = INPUT_KEY_F3, + [0x3d] = INPUT_KEY_F4, + [0x3e] = INPUT_KEY_F5, + [0x3f] = INPUT_KEY_F6, + [0x40] = INPUT_KEY_F7, + [0x41] = INPUT_KEY_F8, + [0x42] = INPUT_KEY_F9, + [0x43] = INPUT_KEY_F10, + [0x44] = INPUT_KEY_F11, + [0x45] = INPUT_KEY_F12, + [0x47] = INPUT_KEY_SCROLLLOCK, + [0x49] = INPUT_KEY_INSERT, + [0x4a] = INPUT_KEY_HOME, + [0x4b] = INPUT_KEY_PAGEUP, + [0x4c] = INPUT_KEY_DELETE, + [0x4d] = INPUT_KEY_END, + [0x4e] = INPUT_KEY_PAGEDOWN, + [0x4f] = INPUT_KEY_RIGHT, + [0x50] = INPUT_KEY_LEFT, + [0x51] = INPUT_KEY_DOWN, + [0x52] = INPUT_KEY_UP, + [0x53] = INPUT_KEY_NUMLOCK, + [0x54] = INPUT_KEY_KPSLASH, + [0x55] = INPUT_KEY_KPASTERISK, + [0x56] = INPUT_KEY_KPMINUS, + [0x57] = INPUT_KEY_KPPLUS, + [0x58] = INPUT_KEY_KPENTER, + [0x59] = INPUT_KEY_KP1, + [0x5a] = INPUT_KEY_KP2, + [0x5b] = INPUT_KEY_KP3, + [0x5c] = INPUT_KEY_KP4, + [0x5d] = INPUT_KEY_KP5, + [0x5e] = INPUT_KEY_KP6, + [0x5f] = INPUT_KEY_KP7, + [0x60] = INPUT_KEY_KP8, + [0x61] = INPUT_KEY_KP9, + [0x62] = INPUT_KEY_KP0, + [0x63] = INPUT_KEY_KPDOT, + [0x68] = INPUT_KEY_F13, + [0x69] = INPUT_KEY_F14, + [0x6a] = INPUT_KEY_F15, + [0x6b] = INPUT_KEY_F16, + [0x6c] = INPUT_KEY_F17, + [0x6d] = INPUT_KEY_F18, + [0x6e] = INPUT_KEY_F19, + [0x6f] = INPUT_KEY_F20, + [0x70] = INPUT_KEY_F21, + [0x71] = INPUT_KEY_F22, + [0x72] = INPUT_KEY_F23, + [0x73] = INPUT_KEY_F24, + [0xe0] = INPUT_KEY_LEFTCTRL, + [0xe1] = INPUT_KEY_LEFTSHIFT, + [0xe2] = INPUT_KEY_LEFTALT, + [0xe3] = INPUT_KEY_LEFTMETA, + [0xe4] = INPUT_KEY_RIGHTCTRL, + [0xe5] = INPUT_KEY_RIGHTSHIFT, + [0xe6] = INPUT_KEY_RIGHTALT, + [0xe7] = INPUT_KEY_RIGHTMETA +}; + +static uint16_t hid_keyboard_keypad_to_input_key(uint32_t hid_key) { + if (hid_key > HID_KEYBOARD_KEYPAD_MAX) + return INPUT_KEY_RESERVED; + + return hid_keyboard_keypad_to_input_key_table[hid_key]; +} + +static void push_event(input_event_t event, input_event_t *events, size_t *events_emitted) { + if (*events_emitted >= EVENT_EMISSION_LIMIT) + return; + + events[*events_emitted] = event; + *events_emitted += 1; +} + +static inline void hid_key_set(uint64_t *keys, uint32_t usage) { + if (usage > HID_KEYBOARD_KEYPAD_MAX) + return; + + keys[usage / 64] |= 1ull << (usage % 64); +} + +static inline bool hid_key_is_modifier(uint32_t usage) { + return usage >= 0xe0 && usage <= 0xe7; +} + +static inline int64_t hid_input_value_signed(hid_input_t *input, uint64_t value) { + if (input->logical_minimum >= 0 || input->bit_size == 0 || input->bit_size >= 64) + return (int64_t)value; + + uint64_t sign_bit = 1ull << (input->bit_size - 1); + if (value & sign_bit) + value |= ~((1ull << input->bit_size) - 1); + + return (int64_t)value; +} + +static void hid_emit_event_keyboard_keypad(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (input->flags & HID_INPUT_VARIABLE) { + if (value == 0) + return; + + if (usage->minimum || usage->maximum) { + value = usage->minimum + idx; + if (value > usage->maximum) + return; + } else { + value = USAGE(usage->usage); + } + } + + hid_key_set(application->pressed_keys, value); +} + +static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage); + +static void hid_emit_event_generic_desktop(hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if ((input->flags & HID_INPUT_RELATIVE) == 0) + return; + + uint32_t hid_usage = USAGE(usage->usage); + if (usage->minimum || usage->maximum) { + hid_usage = usage->minimum + idx; + if (hid_usage > usage->maximum) + return; + } + + uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); + if (code > INPUT_REL_MAX) + return; + + int64_t signed_value = hid_input_value_signed(input, value); + if (signed_value == 0) + return; + + input_event_t event = {0}; + event.type = INPUT_EV_REL; + event.code = code; + event.value = signed_value; + push_event(event, events, events_emitted); +} + +static inline void hid_button_set(hid_application_t *application, uint32_t button) { + if (button < HID_BUTTON_PRIMARY || button > HID_BUTTON_MAX) + return; + + application->pressed_buttons |= 1ull << (button - HID_BUTTON_PRIMARY); +} + +static void hid_emit_event_button(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (input->flags & HID_INPUT_VARIABLE) { + if (value == 0) + return; + + if (usage->minimum || usage->maximum) { + uint32_t button = usage->minimum + idx; + if (button > usage->maximum) + return; + + hid_button_set(application, button); + } else { + hid_button_set(application, USAGE(usage->usage)); + } + } else if (value) { + hid_button_set(application, value); + } +} + +static void hid_emit_event(hid_application_t *application, hid_input_t *input, hid_usage_t *usage, size_t idx, uint64_t value, input_event_t *events, size_t *events_emitted) { + switch (USAGE_PAGE(usage->usage)) { + case HID_USAGE_PAGE_GENERIC_DESKTOP: + hid_emit_event_generic_desktop(input, usage, idx, value, events, events_emitted); + break; + case HID_USAGE_PAGE_KEYBOARD_KEYPAD: + hid_emit_event_keyboard_keypad(application, input, usage, idx, value, events, events_emitted); + break; + case HID_USAGE_PAGE_BUTTON: + hid_emit_event_button(application, input, usage, idx, value); + break; + } +} + +static inline uint64_t get_bitfield_data_at_offset(uint8_t *data, size_t bit_count, size_t bit_offset) { + uint64_t value = 0; + + if (bit_count == 0) + return 0; + + __assert(bit_count <= 64); + + for (size_t i = 0; i < bit_count; i++) { + size_t src_bit = bit_offset + i; + uint8_t byte = data[src_bit / 8]; + uint8_t bit = (byte >> (src_bit % 8)) & 1; + + value |= (uint64_t)bit << i; + } + + return value; +} + +static void do_input(hid_parser_t *parser, hid_input_t *input, uint8_t *data) { + hid_application_t *application = &parser->applications[input->application_id]; + + if (input->bit_size > 64) { + printf("hid: bit sizes above 64-bit are currently a TODO.\n"); + return; + } + + input_event_t events[EVENT_EMISSION_LIMIT]; + size_t events_emitted = 0; + hid_usage_t *usage = NULL; + for (size_t i = 0; i < input->count; ++i) { + if (i < input->usage_count) + usage = &input->usages[i]; + + // no point in handling this if we are not gonna have an use for it + if (usage == NULL) + break; + + uint64_t value = get_bitfield_data_at_offset(data, input->bit_size, input->bit_offset + input->bit_size * i); + hid_emit_event(application, input, usage, i, value, events, &events_emitted); + } + + if (events_emitted) + input_queue_packet(application->input_device, events, events_emitted); +} + +typedef struct { + hid_input_t *inputs; + size_t input_count; + unsigned int next_input; +} parse_state_t; + +static bool consume_next_input(parse_state_t *parse_state, hid_input_t **current_input) { + if (parse_state->next_input >= parse_state->input_count) + return false; + + *current_input = &parse_state->inputs[parse_state->next_input]; + + ++parse_state->next_input; + return true; +} + +static void hid_handle_key_changes(hid_application_t *application, input_event_t *events, size_t *events_emitted, uint64_t old_keys, size_t offset, bool modifiers) { + uint64_t changes = old_keys ^ application->pressed_keys[offset]; + while (changes) { + int bit = __builtin_ctzll(changes); + uint32_t value = offset * 64 + bit; + + if (hid_key_is_modifier(value) != modifiers) { + changes &= ~(1llu << bit); + continue; + } + + input_event_t event = {0}; + event.type = INPUT_EV_KEY; + event.code = hid_keyboard_keypad_to_input_key(value); + if (event.code != INPUT_KEY_RESERVED) { + event.value = (application->pressed_keys[offset] & (1ull << bit)) ? 1 : 0; + push_event(event, events, events_emitted); + } + + changes &= ~(1llu << bit); + } +} + +static uint16_t hid_button_to_input_key(uint32_t usage); + +static void hid_handle_button_changes(hid_application_t *application, input_event_t *events, size_t *events_emitted, uint64_t old_buttons) { + uint64_t changes = old_buttons ^ application->pressed_buttons; + while (changes) { + int bit = __builtin_ctzll(changes); + uint32_t button = HID_BUTTON_PRIMARY + bit; + + input_event_t event = {0}; + event.type = INPUT_EV_KEY; + event.code = hid_button_to_input_key(button); + if (event.code != INPUT_KEY_RESERVED) { + event.value = (application->pressed_buttons & (1ull << bit)) ? 1 : 0; + push_event(event, events, events_emitted); + } + + changes &= ~(1llu << bit); + } +} + +static void do_report_parse(hid_parser_t *parser, hid_input_t *inputs, size_t input_count, uint8_t *data) { + parse_state_t parse_state = { + .inputs = inputs, + .input_count = input_count + }; + + hid_input_t *current_input; + + __assert(parser->application_count <= 64); + uint64_t applications_handled = 0; + uint64_t old_keys[4 * parser->application_count]; + uint64_t old_buttons[parser->application_count]; + while (consume_next_input(&parse_state, ¤t_input)) { + if ((applications_handled & (1llu << current_input->application_id)) == 0) { + memcpy(&old_keys[current_input->application_id * 4], parser->applications[current_input->application_id].pressed_keys, sizeof(parser->applications[current_input->application_id].pressed_keys)); + memset(parser->applications[current_input->application_id].pressed_keys, 0, sizeof(parser->applications[current_input->application_id].pressed_keys)); + old_buttons[current_input->application_id] = parser->applications[current_input->application_id].pressed_buttons; + parser->applications[current_input->application_id].pressed_buttons = 0; + applications_handled |= (1llu << current_input->application_id); + } + + do_input(parser, current_input, data); + } + + for (size_t i = 0; i < parser->application_count; ++i) { + if ((applications_handled & (1llu << i)) == 0) + continue; + + input_event_t events[EVENT_EMISSION_LIMIT]; + size_t events_emitted = 0; + + for (int j = 0; j < 4; ++j) + if (old_keys[i * 4 + j] ^ parser->applications[i].pressed_keys[j]) + hid_handle_key_changes(&parser->applications[i], events, &events_emitted, old_keys[i * 4 + j], j, true); + + for (int j = 0; j < 4; ++j) + if (old_keys[i * 4 + j] ^ parser->applications[i].pressed_keys[j]) + hid_handle_key_changes(&parser->applications[i], events, &events_emitted, old_keys[i * 4 + j], j, false); + + if (old_buttons[i] ^ parser->applications[i].pressed_buttons) + hid_handle_button_changes(&parser->applications[i], events, &events_emitted, old_buttons[i]); + + if (events_emitted) + input_queue_packet(parser->applications[i].input_device, events, events_emitted); + } +} + +void hid_parse_report(hid_parser_t *parser, hid_report_t *report, uint8_t *data, size_t size) { + if (report) { + // separated by reports + if (ROUND_UP(report->bit_size, 8) / 8 > size) + return; + + do_report_parse(parser, report->inputs, report->input_count, data); + } else { + // only anonymous inputs + if (ROUND_UP(parser->input_bit_size, 8) / 8 > size) + return; + + do_report_parse(parser, parser->inputs, parser->input_count, data); + } +} + +static void hid_advertise_keyboard_keypad(input_device_t *device, hid_input_t *input, hid_usage_t *usage) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (usage->minimum == 0 && usage->maximum == 0) { + uint16_t code = hid_keyboard_keypad_to_input_key(USAGE(usage->usage)); + if (code != INPUT_KEY_RESERVED) + bitmap_set(&device->key_bits, code, 1); + } + + for (uint32_t hid_key = usage->minimum; hid_key <= usage->maximum && hid_key <= HID_KEYBOARD_KEYPAD_MAX; ++hid_key) { + uint16_t code = hid_keyboard_keypad_to_input_key(hid_key); + if (code != INPUT_KEY_RESERVED) + bitmap_set(&device->key_bits, code, 1); + } +} + +static uint16_t hid_generic_desktop_to_input_rel(uint32_t usage) { + switch (usage) { + case HID_GD_X: + return INPUT_REL_X; + case HID_GD_Y: + return INPUT_REL_Y; + case HID_GD_WHEEL: + return INPUT_REL_WHEEL; + default: + return INPUT_REL_MAX + 1; + } +} + +static void hid_advertise_generic_desktop(input_device_t *device, hid_input_t *input, hid_usage_t *usage) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if ((input->flags & HID_INPUT_RELATIVE) == 0) + return; + + if (usage->minimum == 0 && usage->maximum == 0) { + uint16_t code = hid_generic_desktop_to_input_rel(USAGE(usage->usage)); + if (code <= INPUT_REL_MAX) { + bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); + bitmap_set(&device->rel_bits, code, 1); + } + } + + for (uint32_t hid_usage = usage->minimum; hid_usage <= usage->maximum && hid_usage <= HID_GD_WHEEL; ++hid_usage) { + uint16_t code = hid_generic_desktop_to_input_rel(hid_usage); + if (code <= INPUT_REL_MAX) { + bitmap_set(&device->ev_bits, INPUT_EV_REL, 1); + bitmap_set(&device->rel_bits, code, 1); + } + } +} + +static uint16_t hid_button_to_input_key(uint32_t usage) { + switch (usage) { + case HID_BUTTON_PRIMARY: + return INPUT_KEY_BTN_LEFT; + case HID_BUTTON_SECONDARY: + return INPUT_KEY_BTN_RIGHT; + case HID_BUTTON_TERTIARY: + return INPUT_KEY_BTN_MIDDLE; + case HID_BUTTON_SIDE: + return INPUT_KEY_BTN_SIDE; + case HID_BUTTON_EXTRA: + return INPUT_KEY_BTN_EXTRA; + default: + return INPUT_KEY_RESERVED; + } +} + +static void hid_advertise_button(input_device_t *device, hid_input_t *input, hid_usage_t *usage) { + if (input->flags & HID_INPUT_CONSTANT) + return; + + if (usage->minimum == 0 && usage->maximum == 0) { + uint16_t code = hid_button_to_input_key(USAGE(usage->usage)); + if (code != INPUT_KEY_RESERVED) { + bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1); + bitmap_set(&device->key_bits, code, 1); + } + } + + for (uint32_t button = usage->minimum; button <= usage->maximum && button <= HID_BUTTON_MAX; ++button) { + uint16_t code = hid_button_to_input_key(button); + if (code != INPUT_KEY_RESERVED) { + bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1); + bitmap_set(&device->key_bits, code, 1); + } + } +} + +static void hid_advertise_input(hid_parser_t *parser, hid_input_t *input) { + hid_application_t *application = &parser->applications[input->application_id]; + input_device_t *device = application->input_device; + + for (size_t i = 0; i < input->usage_count; ++i) { + hid_usage_t *usage = &input->usages[i]; + switch (USAGE_PAGE(usage->usage)) { + case HID_USAGE_PAGE_GENERIC_DESKTOP: + hid_advertise_generic_desktop(device, input, usage); + break; + case HID_USAGE_PAGE_KEYBOARD_KEYPAD: + bitmap_set(&device->ev_bits, INPUT_EV_KEY, 1); + hid_advertise_keyboard_keypad(device, input, usage); + break; + case HID_USAGE_PAGE_BUTTON: + hid_advertise_button(device, input, usage); + break; + } + } +} + +void hid_advertise_events(hid_parser_t *parser) { + if (parser->report_count) { + for (size_t i = 0; i < parser->report_count; ++i) { + hid_report_t *report = &parser->reports[i]; + for (size_t j = 0; j < report->input_count; ++j) + hid_advertise_input(parser, &report->inputs[j]); + } + } else { + for (size_t i = 0; i < parser->input_count; ++i) + hid_advertise_input(parser, &parser->inputs[i]); + } +}