| /* |
| * Human Interface Device (HID) USB class core |
| * |
| * Copyright (c) 2018 Intel Corporation |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define LOG_LEVEL CONFIG_USB_DEVICE_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(usb_hid); |
| |
| #include <misc/byteorder.h> |
| #include <usb_device.h> |
| #include <usb_common.h> |
| |
| #include <usb_descriptor.h> |
| #include <class/usb_hid.h> |
| |
| #include <stdlib.h> |
| |
| #define HID_INT_IN_EP_ADDR 0x81 |
| #define HID_INT_OUT_EP_ADDR 0x01 |
| |
| #define HID_INT_IN_EP_IDX 0 |
| #define HID_INT_OUT_EP_IDX 1 |
| |
| struct usb_hid_config { |
| struct usb_if_descriptor if0; |
| struct usb_hid_descriptor if0_hid; |
| struct usb_ep_descriptor if0_int_in_ep; |
| #ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
| struct usb_ep_descriptor if0_int_out_ep; |
| #endif |
| } __packed; |
| |
| #if defined(CONFIG_USB_HID_BOOT_PROTOCOL) |
| #define INITIALIZER_IF \ |
| { \ |
| .bLength = sizeof(struct usb_if_descriptor), \ |
| .bDescriptorType = USB_INTERFACE_DESC, \ |
| .bInterfaceNumber = 0, \ |
| .bAlternateSetting = 0, \ |
| .bNumEndpoints = 1, \ |
| .bInterfaceClass = HID_CLASS, \ |
| .bInterfaceSubClass = 1, \ |
| .bInterfaceProtocol = CONFIG_USB_HID_PROTOCOL_CODE, \ |
| .iInterface = 0, \ |
| } |
| #else |
| #define INITIALIZER_IF \ |
| { \ |
| .bLength = sizeof(struct usb_if_descriptor), \ |
| .bDescriptorType = USB_INTERFACE_DESC, \ |
| .bInterfaceNumber = 0, \ |
| .bAlternateSetting = 0, \ |
| .bNumEndpoints = 1, \ |
| .bInterfaceClass = HID_CLASS, \ |
| .bInterfaceSubClass = 0, \ |
| .bInterfaceProtocol = 0, \ |
| .iInterface = 0, \ |
| } |
| #endif |
| |
| /* Descriptor length needs to be set after initialization */ |
| #define INITIALIZER_IF_HID \ |
| { \ |
| .bLength = sizeof(struct usb_hid_descriptor), \ |
| .bDescriptorType = USB_HID_DESC, \ |
| .bcdHID = sys_cpu_to_le16(USB_1_1), \ |
| .bCountryCode = 0, \ |
| .bNumDescriptors = 1, \ |
| .subdesc[0] = { \ |
| .bDescriptorType = USB_HID_REPORT_DESC, \ |
| .wDescriptorLength = 0, \ |
| }, \ |
| } |
| |
| #define INITIALIZER_IF_EP(addr, attr, mps) \ |
| { \ |
| .bLength = sizeof(struct usb_ep_descriptor), \ |
| .bDescriptorType = USB_ENDPOINT_DESC, \ |
| .bEndpointAddress = addr, \ |
| .bmAttributes = attr, \ |
| .wMaxPacketSize = sys_cpu_to_le16(mps), \ |
| .bInterval = CONFIG_USB_HID_POLL_INTERVAL_MS, \ |
| } |
| |
| #ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
| #define DEFINE_HID_DESCR(x) \ |
| USBD_CLASS_DESCR_DEFINE(primary, x) \ |
| struct usb_hid_config hid_cfg_##x = { \ |
| /* Interface descriptor */ \ |
| .if0 = INITIALIZER_IF, \ |
| .if0_hid = INITIALIZER_IF_HID, \ |
| .if0_int_in_ep = \ |
| INITIALIZER_IF_EP(HID_INT_IN_EP_ADDR, \ |
| USB_DC_EP_INTERRUPT, \ |
| CONFIG_HID_INTERRUPT_EP_MPS), \ |
| .if0_int_out_ep = \ |
| INITIALIZER_IF_EP(HID_INT_OUT_EP_ADDR, \ |
| USB_DC_EP_INTERRUPT, \ |
| CONFIG_HID_INTERRUPT_EP_MPS), \ |
| } |
| #else |
| #define DEFINE_HID_DESCR(x) \ |
| USBD_CLASS_DESCR_DEFINE(primary, x) \ |
| struct usb_hid_config hid_cfg_##x = { \ |
| /* Interface descriptor */ \ |
| .if0 = INITIALIZER_IF, \ |
| .if0_hid = INITIALIZER_IF_HID, \ |
| .if0_int_in_ep = \ |
| INITIALIZER_IF_EP(HID_INT_IN_EP_ADDR, \ |
| USB_DC_EP_INTERRUPT, \ |
| CONFIG_HID_INTERRUPT_EP_MPS), \ |
| } |
| #endif |
| |
| struct hid_device_info { |
| const u8_t *report_desc; |
| size_t report_size; |
| const struct hid_ops *ops; |
| #ifdef CONFIG_USB_DEVICE_SOF |
| u32_t sof_cnt[CONFIG_USB_HID_REPORTS + 1]; |
| bool idle_on; |
| bool idle_id_report; |
| u8_t idle_rate[CONFIG_USB_HID_REPORTS + 1]; |
| #endif |
| #ifdef CONFIG_USB_HID_BOOT_PROTOCOL |
| u8_t protocol; |
| #endif |
| struct usb_dev_data common; |
| }; |
| |
| static sys_slist_t usb_hid_devlist; |
| |
| static int hid_on_get_idle(struct hid_device_info *dev_data, |
| struct usb_setup_packet *setup, s32_t *len, |
| u8_t **data) |
| { |
| #ifdef CONFIG_USB_DEVICE_SOF |
| u8_t report_id = sys_le16_to_cpu(setup->wValue) & 0xFF; |
| |
| if (report_id > CONFIG_USB_HID_REPORTS) { |
| LOG_ERR("Report id out of limit: %d", report_id); |
| return -ENOTSUP; |
| } |
| |
| u32_t size = sizeof(dev_data->idle_rate[report_id]); |
| |
| LOG_DBG("Get Idle callback, report_id: %d", report_id); |
| |
| *data = &dev_data->idle_rate[report_id]; |
| len = &size; |
| return 0; |
| #else |
| return -ENOTSUP; |
| #endif |
| } |
| |
| static int hid_on_get_report(struct hid_device_info *dev_data, |
| struct usb_setup_packet *setup, s32_t *len, |
| u8_t **data) |
| { |
| LOG_DBG("Get Report callback"); |
| |
| /* TODO: Do something. */ |
| |
| return -ENOTSUP; |
| } |
| |
| static int hid_on_get_protocol(struct hid_device_info *dev_data, |
| struct usb_setup_packet *setup, s32_t *len, |
| u8_t **data) |
| { |
| #ifdef CONFIG_USB_HID_BOOT_PROTOCOL |
| if (setup->wValue) { |
| LOG_ERR("wValue should be 0"); |
| return -ENOTSUP; |
| } |
| |
| u32_t size = sizeof(dev_data->protocol); |
| |
| LOG_DBG("Get Protocol callback, protocol: %d", dev_data->protocol); |
| |
| *data = &dev_data->protocol; |
| len = &size; |
| return 0; |
| #else |
| return -ENOTSUP; |
| #endif |
| } |
| |
| static int hid_on_set_idle(struct hid_device_info *dev_data, |
| struct usb_setup_packet *setup, s32_t *len, |
| u8_t **data) |
| { |
| #ifdef CONFIG_USB_DEVICE_SOF |
| u8_t rate = ((sys_le16_to_cpu(setup->wValue) & 0xFF00) >> 8); |
| u8_t report_id = sys_le16_to_cpu(setup->wValue) & 0xFF; |
| |
| if (report_id > CONFIG_USB_HID_REPORTS) { |
| LOG_ERR("Report id out of limit: %d", report_id); |
| return -ENOTSUP; |
| } |
| |
| LOG_DBG("Set Idle callback, rate: %d, report_id: %d", rate, report_id); |
| |
| dev_data->idle_rate[report_id] = rate; |
| |
| if (rate == 0) { |
| /* Clear idle */ |
| bool clear = true; |
| |
| for (u16_t i = 1; i <= CONFIG_USB_HID_REPORTS; i++) { |
| if (dev_data->idle_rate[i] != 0) { |
| /* Report with non-zero id has idle rate. */ |
| clear = false; |
| break; |
| } |
| } |
| if (clear) { |
| dev_data->idle_id_report = false; |
| LOG_DBG("Non-zero report idle rate OFF."); |
| |
| if (dev_data->idle_rate[0] == 0) { |
| dev_data->idle_on = false; |
| LOG_DBG("Idle rate OFF."); |
| } |
| } |
| } else { |
| /* Set idle */ |
| dev_data->idle_on = true; |
| LOG_DBG("Idle rate ON."); |
| if (report_id != 0) { |
| /* Report with non-zero id has idle rate set now. */ |
| dev_data->idle_id_report = true; |
| LOG_DBG("Non-zero report idle rate ON."); |
| } |
| } |
| return 0; |
| #else |
| return -ENOTSUP; |
| #endif |
| } |
| |
| static int hid_on_set_report(struct hid_device_info *dev_data, |
| struct usb_setup_packet *setup, s32_t *len, |
| u8_t **data) |
| { |
| LOG_DBG("Set Report callback"); |
| |
| /* TODO: Do something. */ |
| |
| return -ENOTSUP; |
| } |
| |
| static int hid_on_set_protocol(struct hid_device_info *dev_data, |
| struct usb_setup_packet *setup, s32_t *len, |
| u8_t **data) |
| { |
| #ifdef CONFIG_USB_HID_BOOT_PROTOCOL |
| u16_t protocol = sys_le16_to_cpu(setup->wValue); |
| |
| if (protocol > HID_PROTOCOL_REPORT) { |
| LOG_ERR("Unsupported protocol: %u", protocol); |
| return -ENOTSUP; |
| } |
| |
| LOG_DBG("Set Protocol callback, protocol: %u", protocol); |
| |
| if (dev_data->protocol != protocol) { |
| dev_data->protocol = protocol; |
| |
| if (dev_data->ops && dev_data->ops->protocol_change) { |
| dev_data->ops->protocol_change(protocol); |
| } |
| } |
| |
| return 0; |
| #else |
| return -ENOTSUP; |
| #endif |
| } |
| |
| static void usb_set_hid_report_size(const struct usb_cfg_data *cfg, u16_t size) |
| { |
| struct usb_if_descriptor *if_desc = (void *)cfg->interface_descriptor; |
| struct usb_hid_config *desc = |
| CONTAINER_OF(if_desc, struct usb_hid_config, if0); |
| |
| LOG_DBG("if_desc %p desc %p size %u", if_desc, desc, size); |
| |
| sys_put_le16(size, |
| (u8_t *)&(desc->if0_hid.subdesc[0].wDescriptorLength)); |
| } |
| |
| #ifdef CONFIG_USB_DEVICE_SOF |
| void hid_clear_idle_ctx(struct hid_device_info *dev_data) |
| { |
| dev_data->idle_on = false; |
| dev_data->idle_id_report = false; |
| for (u16_t i = 0; i <= CONFIG_USB_HID_REPORTS; i++) { |
| dev_data->sof_cnt[i] = 0; |
| dev_data->idle_rate[i] = 0; |
| } |
| } |
| |
| void hid_sof_handler(struct hid_device_info *dev_data) |
| { |
| for (u16_t i = 0; i <= CONFIG_USB_HID_REPORTS; i++) { |
| if (dev_data->idle_rate[i]) { |
| dev_data->sof_cnt[i]++; |
| } |
| |
| u32_t diff = abs((dev_data->idle_rate[i] * 4) |
| - dev_data->sof_cnt[i]); |
| |
| if (diff < (2 + (dev_data->idle_rate[i] / 10))) { |
| dev_data->sof_cnt[i] = 0; |
| if (dev_data->ops && dev_data->ops->on_idle) { |
| dev_data->ops->on_idle(i); |
| } |
| } |
| |
| if (!dev_data->idle_id_report) { |
| /* Only report with 0 id has idle rate. |
| * No need to check the whole array. |
| */ |
| break; |
| } |
| } |
| } |
| #endif |
| |
| static void hid_do_status_cb(struct hid_device_info *dev_data, |
| enum usb_dc_status_code status, |
| const u8_t *param) |
| { |
| switch (status) { |
| case USB_DC_ERROR: |
| LOG_DBG("USB device error"); |
| break; |
| case USB_DC_RESET: |
| LOG_DBG("USB device reset detected"); |
| #ifdef CONFIG_USB_HID_BOOT_PROTOCOL |
| dev_data->protocol = HID_PROTOCOL_REPORT; |
| #endif |
| #ifdef CONFIG_USB_DEVICE_SOF |
| hid_clear_idle_ctx(dev_data); |
| #endif |
| break; |
| case USB_DC_CONNECTED: |
| LOG_DBG("USB device connected"); |
| break; |
| case USB_DC_CONFIGURED: |
| LOG_DBG("USB device configured"); |
| break; |
| case USB_DC_DISCONNECTED: |
| LOG_DBG("USB device disconnected"); |
| break; |
| case USB_DC_SUSPEND: |
| LOG_DBG("USB device suspended"); |
| break; |
| case USB_DC_RESUME: |
| LOG_DBG("USB device resumed"); |
| break; |
| case USB_DC_SOF: |
| #ifdef CONFIG_USB_DEVICE_SOF |
| if (dev_data->idle_on) { |
| hid_sof_handler(dev_data); |
| } |
| #endif |
| break; |
| case USB_DC_UNKNOWN: |
| default: |
| LOG_DBG("USB unknown state"); |
| break; |
| } |
| |
| if (dev_data->ops && dev_data->ops->status_cb) { |
| dev_data->ops->status_cb(status, param); |
| } |
| } |
| |
| #ifdef CONFIG_USB_COMPOSITE_DEVICE |
| static void hid_status_composite_cb(struct usb_cfg_data *cfg, |
| enum usb_dc_status_code status, |
| const u8_t *param) |
| { |
| struct hid_device_info *dev_data; |
| struct usb_dev_data *common; |
| |
| LOG_DBG("cfg %p status %d", cfg, status); |
| |
| common = usb_get_dev_data_by_cfg(&usb_hid_devlist, cfg); |
| if (common == NULL) { |
| LOG_WRN("Device data not found for cfg %p", cfg); |
| return; |
| } |
| |
| dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
| |
| hid_do_status_cb(dev_data, status, param); |
| } |
| #else |
| static void hid_status_cb(enum usb_dc_status_code status, const u8_t *param) |
| { |
| struct hid_device_info *dev_data; |
| struct usb_dev_data *common; |
| |
| /* Should be the only one element in the list */ |
| common = CONTAINER_OF(sys_slist_peek_head(&usb_hid_devlist), |
| struct usb_dev_data, node); |
| if (common == NULL) { |
| LOG_WRN("Device data not found"); |
| return; |
| } |
| |
| dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
| |
| hid_do_status_cb(dev_data, status, param); |
| } |
| #endif |
| |
| static int hid_class_handle_req(struct usb_setup_packet *setup, |
| s32_t *len, u8_t **data) |
| { |
| struct hid_device_info *dev_data; |
| struct usb_dev_data *common; |
| |
| LOG_DBG("Class request: bRequest 0x%x bmRequestType 0x%x len %d", |
| setup->bRequest, setup->bmRequestType, *len); |
| |
| common = usb_get_dev_data_by_iface(&usb_hid_devlist, |
| sys_le16_to_cpu(setup->wIndex)); |
| if (common == NULL) { |
| LOG_WRN("Device data not found for interface %u", |
| sys_le16_to_cpu(setup->wIndex)); |
| return -ENODEV; |
| } |
| |
| dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
| |
| if (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST) { |
| switch (setup->bRequest) { |
| case HID_GET_IDLE: |
| if (dev_data->ops && dev_data->ops->get_idle) { |
| return dev_data->ops->get_idle(setup, len, |
| data); |
| } else { |
| return hid_on_get_idle(dev_data, setup, len, |
| data); |
| } |
| break; |
| case HID_GET_REPORT: |
| if (dev_data->ops && dev_data->ops->get_report) { |
| return dev_data->ops->get_report(setup, len, |
| data); |
| } else { |
| return hid_on_get_report(dev_data, setup, len, |
| data); |
| } |
| break; |
| case HID_GET_PROTOCOL: |
| if (dev_data->ops && dev_data->ops->get_protocol) { |
| return dev_data->ops->get_protocol(setup, len, |
| data); |
| } else { |
| return hid_on_get_protocol(dev_data, setup, len, |
| data); |
| } |
| break; |
| default: |
| LOG_ERR("Unhandled request 0x%x", setup->bRequest); |
| break; |
| } |
| } else { |
| switch (setup->bRequest) { |
| case HID_SET_IDLE: |
| if (dev_data->ops && dev_data->ops->set_idle) { |
| return dev_data->ops->set_idle(setup, len, |
| data); |
| } else { |
| return hid_on_set_idle(dev_data, setup, len, |
| data); |
| } |
| break; |
| case HID_SET_REPORT: |
| if (dev_data->ops && dev_data->ops->set_report) { |
| return dev_data->ops->set_report(setup, len, |
| data); |
| } else { |
| return hid_on_set_report(dev_data, setup, len, |
| data); |
| } |
| break; |
| case HID_SET_PROTOCOL: |
| if (dev_data->ops && dev_data->ops->set_protocol) { |
| return dev_data->ops->set_protocol(setup, len, |
| data); |
| } else { |
| return hid_on_set_protocol(dev_data, setup, len, |
| data); |
| } |
| break; |
| default: |
| LOG_ERR("Unhandled request 0x%x", setup->bRequest); |
| break; |
| } |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static int hid_custom_handle_req(struct usb_setup_packet *setup, |
| s32_t *len, u8_t **data) |
| { |
| LOG_DBG("Standard request: bRequest 0x%x bmRequestType 0x%x len %d", |
| setup->bRequest, setup->bmRequestType, *len); |
| |
| if (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST && |
| REQTYPE_GET_RECIP(setup->bmRequestType) == |
| REQTYPE_RECIP_INTERFACE && |
| setup->bRequest == REQ_GET_DESCRIPTOR) { |
| u8_t value = sys_le16_to_cpu(setup->wValue) >> 8; |
| u8_t iface_num = sys_le16_to_cpu(setup->wIndex); |
| struct hid_device_info *dev_data; |
| struct usb_dev_data *common; |
| const struct usb_cfg_data *cfg; |
| const struct usb_hid_config *hid_desc; |
| |
| common = usb_get_dev_data_by_iface(&usb_hid_devlist, iface_num); |
| if (common == NULL) { |
| LOG_WRN("Device data not found for interface %u", |
| sys_le16_to_cpu(setup->wIndex)); |
| return -ENODEV; |
| } |
| |
| dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
| |
| switch (value) { |
| case HID_CLASS_DESCRIPTOR_HID: |
| cfg = common->dev->config->config_info; |
| hid_desc = cfg->interface_descriptor; |
| |
| LOG_DBG("Return HID Descriptor"); |
| |
| *len = min(*len, hid_desc->if0_hid.bLength); |
| *data = (u8_t *)&hid_desc->if0_hid; |
| break; |
| case HID_CLASS_DESCRIPTOR_REPORT: |
| LOG_DBG("Return Report Descriptor"); |
| |
| /* Some buggy system may be pass a larger wLength when |
| * it try read HID report descriptor, although we had |
| * already tell it the right descriptor size. |
| * So truncated wLength if it doesn't match. */ |
| if (*len != dev_data->report_size) { |
| LOG_WRN("len %d doesn't match " |
| "Report Descriptor size", *len); |
| *len = min(*len, dev_data->report_size); |
| } |
| *data = (u8_t *)dev_data->report_desc; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static void hid_int_in(u8_t ep, enum usb_dc_ep_cb_status_code ep_status) |
| { |
| struct hid_device_info *dev_data; |
| struct usb_dev_data *common; |
| |
| common = usb_get_dev_data_by_ep(&usb_hid_devlist, ep); |
| if (common == NULL) { |
| LOG_WRN("Device data not found for endpoint %u", ep); |
| return; |
| } |
| |
| dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
| |
| if (ep_status != USB_DC_EP_DATA_IN || dev_data->ops == NULL || |
| dev_data->ops->int_in_ready == NULL) { |
| return; |
| } |
| |
| dev_data->ops->int_in_ready(); |
| } |
| |
| #ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
| static void hid_int_out(u8_t ep, enum usb_dc_ep_cb_status_code ep_status) |
| { |
| struct hid_device_info *dev_data; |
| struct usb_dev_data *common; |
| |
| common = usb_get_dev_data_by_ep(&usb_hid_devlist, ep); |
| if (common == NULL) { |
| LOG_WRN("Device data not found for endpoint %u", ep); |
| return; |
| } |
| |
| dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
| |
| if (ep_status != USB_DC_EP_DATA_OUT || dev_data->ops == NULL || |
| dev_data->ops->int_out_ready == NULL) { |
| return; |
| } |
| |
| dev_data->ops->int_out_ready(); |
| } |
| #endif |
| |
| #define INITIALIZER_EP_DATA(cb, addr) \ |
| { \ |
| .ep_cb = cb, \ |
| .ep_addr = addr, \ |
| } |
| |
| /* Describe Endpoints configuration */ |
| #ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
| #define DEFINE_HID_EP(x) \ |
| static struct usb_ep_cfg_data hid_ep_data_##x[] = { \ |
| INITIALIZER_EP_DATA(hid_int_in, HID_INT_IN_EP_ADDR), \ |
| INITIALIZER_EP_DATA(hid_int_out, HID_INT_OUT_EP_ADDR), \ |
| } |
| #else |
| #define DEFINE_HID_EP(x) \ |
| static struct usb_ep_cfg_data hid_ep_data_##x[] = { \ |
| INITIALIZER_EP_DATA(hid_int_in, HID_INT_IN_EP_ADDR), \ |
| } |
| #endif |
| |
| static void hid_interface_config(struct usb_desc_header *head, |
| u8_t bInterfaceNumber) |
| { |
| struct usb_if_descriptor *if_desc = (struct usb_if_descriptor *)head; |
| struct usb_hid_config *desc = |
| CONTAINER_OF(if_desc, struct usb_hid_config, if0); |
| |
| LOG_DBG(""); |
| |
| desc->if0.bInterfaceNumber = bInterfaceNumber; |
| #ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
| desc->if0.bNumEndpoints = 2; |
| #endif |
| } |
| |
| #ifdef CONFIG_USB_COMPOSITE_DEVICE |
| #define DEFINE_HID_CFG_DATA(x) \ |
| USBD_CFG_DATA_DEFINE(hid) \ |
| struct usb_cfg_data hid_config_##x = { \ |
| .usb_device_description = NULL, \ |
| .interface_config = hid_interface_config, \ |
| .interface_descriptor = &hid_cfg_##x.if0, \ |
| .cb_usb_status_composite = hid_status_composite_cb, \ |
| .interface = { \ |
| .class_handler = hid_class_handle_req, \ |
| .custom_handler = hid_custom_handle_req, \ |
| .payload_data = NULL, \ |
| }, \ |
| .num_endpoints = ARRAY_SIZE(hid_ep_data_##x), \ |
| .endpoint = hid_ep_data_##x, \ |
| } |
| #else |
| #define DEFINE_HID_CFG_DATA(x) \ |
| USBD_CFG_DATA_DEFINE(hid) \ |
| struct usb_cfg_data hid_config_##x = { \ |
| .usb_device_description = NULL, \ |
| .interface_config = hid_interface_config, \ |
| .interface_descriptor = &hid_cfg_##x.if0, \ |
| .cb_usb_status = hid_status_cb, \ |
| .interface = { \ |
| .class_handler = hid_class_handle_req, \ |
| .custom_handler = hid_custom_handle_req, \ |
| .payload_data = NULL, \ |
| }, \ |
| .num_endpoints = ARRAY_SIZE(hid_ep_data_##x), \ |
| .endpoint = hid_ep_data_##x, \ |
| } |
| #endif |
| |
| #if !defined(CONFIG_USB_COMPOSITE_DEVICE) |
| static u8_t interface_data[CONFIG_USB_HID_MAX_PAYLOAD_SIZE]; |
| #endif |
| |
| int usb_hid_init(const struct device *dev) |
| { |
| struct usb_cfg_data *cfg = (void *)dev->config->config_info; |
| struct hid_device_info *dev_data = dev->driver_data; |
| |
| LOG_DBG("Initializing HID Device: dev %p", dev); |
| |
| /* |
| * Modify Report Descriptor Size |
| */ |
| usb_set_hid_report_size(cfg, dev_data->report_size); |
| |
| #ifndef CONFIG_USB_COMPOSITE_DEVICE |
| int ret; |
| |
| cfg->interface.payload_data = interface_data; |
| cfg->usb_device_description = usb_get_device_descriptor(); |
| |
| /* Initialize the USB driver with the right configuration */ |
| ret = usb_set_config(cfg); |
| if (ret < 0) { |
| LOG_ERR("Failed to config USB"); |
| return ret; |
| } |
| |
| /* Enable USB driver */ |
| ret = usb_enable(cfg); |
| if (ret < 0) { |
| LOG_ERR("Failed to enable USB"); |
| return ret; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| void usb_hid_register_device(struct device *dev, const u8_t *desc, |
| size_t size, const struct hid_ops *ops) |
| { |
| struct hid_device_info *dev_data = dev->driver_data; |
| |
| dev_data->report_desc = desc; |
| dev_data->report_size = size; |
| |
| dev_data->ops = ops; |
| dev_data->common.dev = dev; |
| |
| sys_slist_append(&usb_hid_devlist, &dev_data->common.node); |
| |
| LOG_DBG("Added dev_data %p dev %p to devlist %p", dev_data, dev, |
| &usb_hid_devlist); |
| } |
| |
| int hid_int_ep_write(const struct device *dev, const u8_t *data, u32_t data_len, |
| u32_t *bytes_ret) |
| { |
| const struct usb_cfg_data *cfg = dev->config->config_info; |
| |
| return usb_write(cfg->endpoint[HID_INT_IN_EP_IDX].ep_addr, data, |
| data_len, bytes_ret); |
| } |
| |
| int hid_int_ep_read(const struct device *dev, u8_t *data, u32_t max_data_len, |
| u32_t *ret_bytes) |
| { |
| #ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
| const struct usb_cfg_data *cfg = dev->config->config_info; |
| |
| return usb_read(cfg->endpoint[HID_INT_OUT_EP_IDX].ep_addr, |
| data, max_data_len, ret_bytes); |
| #else |
| return -ENOTSUP; |
| #endif |
| } |
| |
| static const struct usb_hid_device_api { |
| void (*init)(void); |
| } hid_api; |
| |
| static int usb_hid_device_init(struct device *dev) |
| { |
| LOG_DBG("Init HID Device: dev %p (%s)", dev, dev->config->name); |
| |
| return 0; |
| } |
| |
| #define DEFINE_HID_DEV_DATA(x) \ |
| struct hid_device_info usb_hid_dev_data_##x |
| |
| #define DEFINE_HID_DEVICE(x) \ |
| DEVICE_AND_API_INIT(usb_hid_device_##x, \ |
| CONFIG_USB_HID_DEVICE_NAME_##x, \ |
| &usb_hid_device_init, \ |
| &usb_hid_dev_data_##x, \ |
| &hid_config_##x, POST_KERNEL, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ |
| &hid_api) |
| |
| DEFINE_HID_DESCR(0); |
| DEFINE_HID_EP(0); |
| DEFINE_HID_CFG_DATA(0); |
| DEFINE_HID_DEV_DATA(0); |
| DEFINE_HID_DEVICE(0); |
| |
| #ifdef CONFIG_USB_HID_DEVICE_1 |
| DEFINE_HID_DESCR(1); |
| DEFINE_HID_EP(1); |
| DEFINE_HID_CFG_DATA(1); |
| DEFINE_HID_DEV_DATA(1); |
| DEFINE_HID_DEVICE(1); |
| #endif |