You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
741 lines
18 KiB
741 lines
18 KiB
/* |
|
* 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_HID_LOG_LEVEL |
|
#include <logging/log.h> |
|
LOG_MODULE_REGISTER(usb_hid); |
|
|
|
#include <sys/byteorder.h> |
|
#include <usb_device.h> |
|
|
|
#include <usb_descriptor.h> |
|
#include <class/usb_hid.h> |
|
|
|
#include <stdlib.h> |
|
|
|
#define HID_INT_IN_EP_IDX 0 |
|
#define HID_INT_OUT_EP_IDX 1 |
|
|
|
struct usb_hid_class_subdescriptor { |
|
uint8_t bDescriptorType; |
|
uint16_t wDescriptorLength; |
|
} __packed; |
|
|
|
struct usb_hid_descriptor { |
|
uint8_t bLength; |
|
uint8_t bDescriptorType; |
|
uint16_t bcdHID; |
|
uint8_t bCountryCode; |
|
uint8_t bNumDescriptors; |
|
|
|
/* |
|
* Specification says at least one Class Descriptor needs to |
|
* be present (Report Descriptor). |
|
*/ |
|
struct usb_hid_class_subdescriptor subdesc[1]; |
|
} __packed; |
|
|
|
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_DESC_INTERFACE, \ |
|
.bInterfaceNumber = 0, \ |
|
.bAlternateSetting = 0, \ |
|
.bNumEndpoints = 1, \ |
|
.bInterfaceClass = USB_BCC_HID, \ |
|
.bInterfaceSubClass = 1, \ |
|
.bInterfaceProtocol = CONFIG_USB_HID_PROTOCOL_CODE, \ |
|
.iInterface = 0, \ |
|
} |
|
#else |
|
#define INITIALIZER_IF \ |
|
{ \ |
|
.bLength = sizeof(struct usb_if_descriptor), \ |
|
.bDescriptorType = USB_DESC_INTERFACE, \ |
|
.bInterfaceNumber = 0, \ |
|
.bAlternateSetting = 0, \ |
|
.bNumEndpoints = 1, \ |
|
.bInterfaceClass = USB_BCC_HID, \ |
|
.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_DESC_HID, \ |
|
.bcdHID = sys_cpu_to_le16(USB_SRN_1_1), \ |
|
.bCountryCode = 0, \ |
|
.bNumDescriptors = 1, \ |
|
.subdesc[0] = { \ |
|
.bDescriptorType = USB_DESC_HID_REPORT, \ |
|
.wDescriptorLength = 0, \ |
|
}, \ |
|
} |
|
|
|
#define INITIALIZER_IF_EP(addr, attr, mps) \ |
|
{ \ |
|
.bLength = sizeof(struct usb_ep_descriptor), \ |
|
.bDescriptorType = USB_DESC_ENDPOINT, \ |
|
.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(AUTO_EP_IN, \ |
|
USB_DC_EP_INTERRUPT, \ |
|
CONFIG_HID_INTERRUPT_EP_MPS), \ |
|
.if0_int_out_ep = \ |
|
INITIALIZER_IF_EP(AUTO_EP_OUT, \ |
|
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(AUTO_EP_IN, \ |
|
USB_DC_EP_INTERRUPT, \ |
|
CONFIG_HID_INTERRUPT_EP_MPS), \ |
|
} |
|
#endif |
|
|
|
struct hid_device_info { |
|
const uint8_t *report_desc; |
|
size_t report_size; |
|
const struct hid_ops *ops; |
|
#ifdef CONFIG_USB_DEVICE_SOF |
|
uint32_t sof_cnt[CONFIG_USB_HID_REPORTS]; |
|
bool idle_on; |
|
uint8_t idle_rate[CONFIG_USB_HID_REPORTS]; |
|
#endif |
|
#ifdef CONFIG_USB_HID_BOOT_PROTOCOL |
|
uint8_t protocol; |
|
#endif |
|
bool configured; |
|
bool suspended; |
|
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, int32_t *len, |
|
uint8_t **data) |
|
{ |
|
#ifdef CONFIG_USB_DEVICE_SOF |
|
uint8_t report_id = (uint8_t)setup->wValue; |
|
|
|
if (report_id > CONFIG_USB_HID_REPORTS) { |
|
LOG_ERR("Report id out of limit: %d", report_id); |
|
return -ENOTSUP; |
|
} |
|
|
|
LOG_DBG("Get Idle callback, report_id: %d", report_id); |
|
|
|
if (report_id == 0U) { |
|
/* |
|
* Common value, stored on Set Idle request with Report ID 0, |
|
* can be outdated because the duration can also |
|
* be set individually for each Report ID. |
|
*/ |
|
*data = &dev_data->idle_rate[0]; |
|
*len = sizeof(dev_data->idle_rate[0]); |
|
} else { |
|
*data = &dev_data->idle_rate[report_id - 1]; |
|
*len = sizeof(dev_data->idle_rate[report_id - 1]); |
|
} |
|
|
|
return 0; |
|
#else |
|
return -ENOTSUP; |
|
#endif |
|
} |
|
|
|
static int hid_on_get_report(struct hid_device_info *dev_data, |
|
struct usb_setup_packet *setup, int32_t *len, |
|
uint8_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, int32_t *len, |
|
uint8_t **data) |
|
{ |
|
#ifdef CONFIG_USB_HID_BOOT_PROTOCOL |
|
uint32_t size = sizeof(dev_data->protocol); |
|
|
|
if (setup->wValue) { |
|
LOG_ERR("wValue should be 0"); |
|
return -ENOTSUP; |
|
} |
|
|
|
LOG_DBG("Get 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, int32_t *len, |
|
uint8_t **data) |
|
{ |
|
#ifdef CONFIG_USB_DEVICE_SOF |
|
uint8_t rate = (uint8_t)(setup->wValue >> 8); |
|
uint8_t report_id = (uint8_t)setup->wValue; |
|
|
|
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); |
|
|
|
if (report_id == 0U) { |
|
for (uint16_t i = 0; i < CONFIG_USB_HID_REPORTS; i++) { |
|
dev_data->idle_rate[i] = rate; |
|
dev_data->sof_cnt[i] = 0U; |
|
} |
|
} else { |
|
dev_data->idle_rate[report_id - 1] = rate; |
|
dev_data->sof_cnt[report_id - 1] = 0U; |
|
} |
|
|
|
dev_data->idle_on = (bool)setup->wValue; |
|
|
|
return 0; |
|
#else |
|
return -ENOTSUP; |
|
#endif |
|
} |
|
|
|
static int hid_on_set_report(struct hid_device_info *dev_data, |
|
struct usb_setup_packet *setup, int32_t *len, |
|
uint8_t **data) |
|
{ |
|
LOG_DBG("Set Report callback"); |
|
|
|
/* TODO: Do something. */ |
|
|
|
return -ENOTSUP; |
|
} |
|
|
|
static int hid_on_set_protocol(const struct device *dev, |
|
struct hid_device_info *dev_data, |
|
struct usb_setup_packet *setup) |
|
{ |
|
#ifdef CONFIG_USB_HID_BOOT_PROTOCOL |
|
uint16_t protocol = setup->wValue; |
|
|
|
if (protocol > HID_PROTOCOL_REPORT) { |
|
LOG_ERR("Unsupported protocol: %u", protocol); |
|
return -ENOTSUP; |
|
} |
|
|
|
LOG_DBG("Set 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(dev, protocol); |
|
} |
|
} |
|
|
|
return 0; |
|
#else |
|
return -ENOTSUP; |
|
#endif |
|
} |
|
|
|
static void usb_set_hid_report_size(const struct usb_cfg_data *cfg, uint16_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, |
|
(uint8_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; |
|
for (uint16_t i = 0; i < CONFIG_USB_HID_REPORTS; i++) { |
|
dev_data->sof_cnt[i] = 0U; |
|
dev_data->idle_rate[i] = 0U; |
|
} |
|
} |
|
|
|
void hid_sof_handler(struct hid_device_info *dev_data) |
|
{ |
|
const struct device *dev = dev_data->common.dev; |
|
bool reported = false; |
|
|
|
if (dev_data->ops == NULL || dev_data->ops->on_idle == NULL) { |
|
return; |
|
} |
|
|
|
for (uint16_t i = 0; i < CONFIG_USB_HID_REPORTS; i++) { |
|
if (dev_data->idle_rate[i]) { |
|
dev_data->sof_cnt[i]++; |
|
} else { |
|
continue; |
|
} |
|
|
|
uint32_t diff = abs(dev_data->idle_rate[i] * 4U - |
|
dev_data->sof_cnt[i]); |
|
|
|
if (diff < 2 && reported == false) { |
|
dev_data->sof_cnt[i] = 0U; |
|
/* |
|
* We can submit only one report at a time |
|
* because we have only one endpoint and |
|
* there is no queue for the packets/reports. |
|
*/ |
|
reported = true; |
|
dev_data->ops->on_idle(dev, i + 1); |
|
} else if (diff == 0 && reported == true) { |
|
/* Delay it once until it has spread. */ |
|
dev_data->sof_cnt[i]--; |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
static void hid_do_status_cb(struct hid_device_info *dev_data, |
|
enum usb_dc_status_code status, |
|
const uint8_t *param) |
|
{ |
|
switch (status) { |
|
case USB_DC_ERROR: |
|
LOG_INF("Device error"); |
|
break; |
|
case USB_DC_RESET: |
|
LOG_INF("Device reset detected"); |
|
dev_data->configured = false; |
|
dev_data->suspended = false; |
|
#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_INF("Device connected"); |
|
break; |
|
case USB_DC_CONFIGURED: |
|
LOG_INF("Device configured"); |
|
dev_data->configured = true; |
|
break; |
|
case USB_DC_DISCONNECTED: |
|
LOG_INF("Device disconnected"); |
|
dev_data->configured = false; |
|
dev_data->suspended = false; |
|
break; |
|
case USB_DC_SUSPEND: |
|
LOG_INF("Device suspended"); |
|
dev_data->suspended = true; |
|
break; |
|
case USB_DC_RESUME: |
|
LOG_INF("Device resumed"); |
|
if (dev_data->suspended) { |
|
LOG_INF("from suspend"); |
|
dev_data->suspended = false; |
|
} else { |
|
LOG_DBG("Spurious resume event"); |
|
} |
|
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_INF("Unknown event"); |
|
break; |
|
} |
|
|
|
} |
|
|
|
static void hid_status_cb(struct usb_cfg_data *cfg, |
|
enum usb_dc_status_code status, |
|
const uint8_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); |
|
} |
|
|
|
static int hid_class_handle_req(struct usb_setup_packet *setup, |
|
int32_t *len, uint8_t **data) |
|
{ |
|
struct hid_device_info *dev_data; |
|
struct usb_dev_data *common; |
|
const struct device *dev; |
|
|
|
LOG_DBG("Class request:" |
|
"bRequest 0x%02x, bmRequestType 0x%02x len %d", |
|
setup->bRequest, setup->bmRequestType, *len); |
|
|
|
common = usb_get_dev_data_by_iface(&usb_hid_devlist, |
|
(uint8_t)setup->wIndex); |
|
if (common == NULL) { |
|
LOG_WRN("Device data not found for interface %u", |
|
setup->wIndex); |
|
return -ENODEV; |
|
} |
|
|
|
dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
|
dev = common->dev; |
|
|
|
if (usb_reqtype_is_to_host(setup)) { |
|
switch (setup->bRequest) { |
|
case USB_HID_GET_IDLE: |
|
return hid_on_get_idle(dev_data, setup, len, data); |
|
case USB_HID_GET_REPORT: |
|
if (dev_data->ops && dev_data->ops->get_report) { |
|
return dev_data->ops->get_report(dev, setup, |
|
len, data); |
|
} else { |
|
return hid_on_get_report(dev_data, setup, len, |
|
data); |
|
} |
|
break; |
|
case USB_HID_GET_PROTOCOL: |
|
return hid_on_get_protocol(dev_data, setup, len, data); |
|
default: |
|
LOG_ERR("Unhandled request 0x%02x", setup->bRequest); |
|
break; |
|
} |
|
} else { |
|
switch (setup->bRequest) { |
|
case USB_HID_SET_IDLE: |
|
return hid_on_set_idle(dev_data, setup, len, data); |
|
case USB_HID_SET_REPORT: |
|
if (dev_data->ops && dev_data->ops->set_report) { |
|
return dev_data->ops->set_report(dev, setup, |
|
len, data); |
|
} else { |
|
return hid_on_set_report(dev_data, setup, len, |
|
data); |
|
} |
|
break; |
|
case USB_HID_SET_PROTOCOL: |
|
return hid_on_set_protocol(dev, dev_data, setup); |
|
default: |
|
LOG_ERR("Unhandled request 0x%02x", setup->bRequest); |
|
break; |
|
} |
|
} |
|
|
|
return -ENOTSUP; |
|
} |
|
|
|
static int hid_custom_handle_req(struct usb_setup_packet *setup, |
|
int32_t *len, uint8_t **data) |
|
{ |
|
LOG_DBG("Standard request:" |
|
"bRequest 0x%02x, bmRequestType 0x%02x, len %d", |
|
setup->bRequest, setup->bmRequestType, setup->wLength); |
|
|
|
if (usb_reqtype_is_to_host(setup) && |
|
setup->RequestType.recipient == USB_REQTYPE_RECIPIENT_INTERFACE && |
|
setup->bRequest == USB_SREQ_GET_DESCRIPTOR) { |
|
uint8_t value = (uint8_t)(setup->wValue >> 8); |
|
uint8_t iface_num = (uint8_t)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", |
|
iface_num); |
|
return -EINVAL; |
|
} |
|
|
|
dev_data = CONTAINER_OF(common, struct hid_device_info, common); |
|
|
|
switch (value) { |
|
case USB_DESC_HID: |
|
cfg = common->dev->config; |
|
hid_desc = cfg->interface_descriptor; |
|
|
|
LOG_DBG("Return HID Descriptor"); |
|
|
|
*len = MIN(setup->wLength, hid_desc->if0_hid.bLength); |
|
*data = (uint8_t *)&hid_desc->if0_hid; |
|
break; |
|
case USB_DESC_HID_REPORT: |
|
LOG_DBG("Return Report Descriptor"); |
|
|
|
*len = MIN(setup->wLength, dev_data->report_size); |
|
*data = (uint8_t *)dev_data->report_desc; |
|
break; |
|
default: |
|
return -ENOTSUP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
return -EINVAL; |
|
} |
|
|
|
static void hid_int_in(uint8_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(common->dev); |
|
} |
|
|
|
#ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
|
static void hid_int_out(uint8_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(common->dev); |
|
} |
|
#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, AUTO_EP_IN), \ |
|
INITIALIZER_EP_DATA(hid_int_out, AUTO_EP_OUT), \ |
|
} |
|
#else |
|
#define DEFINE_HID_EP(x, _) \ |
|
static struct usb_ep_cfg_data hid_ep_data_##x[] = { \ |
|
INITIALIZER_EP_DATA(hid_int_in, AUTO_EP_IN), \ |
|
} |
|
#endif |
|
|
|
static void hid_interface_config(struct usb_desc_header *head, |
|
uint8_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 |
|
} |
|
|
|
#define DEFINE_HID_CFG_DATA(x, _) \ |
|
USBD_DEFINE_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, \ |
|
}, \ |
|
.num_endpoints = ARRAY_SIZE(hid_ep_data_##x), \ |
|
.endpoint = hid_ep_data_##x, \ |
|
} |
|
|
|
int usb_hid_set_proto_code(const struct device *dev, uint8_t proto_code) |
|
{ |
|
const struct usb_cfg_data *cfg = dev->config; |
|
struct usb_if_descriptor *if_desc = cfg->interface_descriptor; |
|
|
|
if (IS_ENABLED(CONFIG_USB_HID_BOOT_PROTOCOL)) { |
|
if_desc->bInterfaceProtocol = proto_code; |
|
return 0; |
|
} |
|
|
|
return -ENOTSUP; |
|
} |
|
|
|
int usb_hid_init(const struct device *dev) |
|
{ |
|
struct usb_cfg_data *cfg = (void *)dev->config; |
|
struct hid_device_info *dev_data = dev->data; |
|
|
|
LOG_DBG("Initializing HID Device: dev %p", dev); |
|
|
|
/* |
|
* Modify Report Descriptor Size |
|
*/ |
|
usb_set_hid_report_size(cfg, dev_data->report_size); |
|
|
|
return 0; |
|
} |
|
|
|
void usb_hid_register_device(const struct device *dev, const uint8_t *desc, |
|
size_t size, const struct hid_ops *ops) |
|
{ |
|
struct hid_device_info *dev_data = dev->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 uint8_t *data, uint32_t data_len, |
|
uint32_t *bytes_ret) |
|
{ |
|
const struct usb_cfg_data *cfg = dev->config; |
|
struct hid_device_info *hid_dev_data = dev->data; |
|
|
|
if (hid_dev_data->configured && !hid_dev_data->suspended) { |
|
return usb_write(cfg->endpoint[HID_INT_IN_EP_IDX].ep_addr, data, |
|
data_len, bytes_ret); |
|
} else { |
|
LOG_WRN("Device is not configured"); |
|
return -EAGAIN; |
|
} |
|
|
|
} |
|
|
|
int hid_int_ep_read(const struct device *dev, uint8_t *data, uint32_t max_data_len, |
|
uint32_t *ret_bytes) |
|
{ |
|
#ifdef CONFIG_ENABLE_HID_INT_OUT_EP |
|
const struct usb_cfg_data *cfg = dev->config; |
|
|
|
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(const struct device *dev) |
|
{ |
|
LOG_DBG("Init HID Device: dev %p (%s)", dev, dev->name); |
|
|
|
return 0; |
|
} |
|
|
|
#define DEFINE_HID_DEV_DATA(x, _) \ |
|
struct hid_device_info usb_hid_dev_data_##x |
|
|
|
#define DEFINE_HID_DEVICE(x, _) \ |
|
DEVICE_DEFINE(usb_hid_device_##x, \ |
|
CONFIG_USB_HID_DEVICE_NAME "_" #x, \ |
|
&usb_hid_device_init, \ |
|
NULL, \ |
|
&usb_hid_dev_data_##x, \ |
|
&hid_config_##x, POST_KERNEL, \ |
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ |
|
&hid_api) |
|
|
|
LISTIFY(CONFIG_USB_HID_DEVICE_COUNT, DEFINE_HID_DESCR, (;), _); |
|
LISTIFY(CONFIG_USB_HID_DEVICE_COUNT, DEFINE_HID_EP, (;), _); |
|
LISTIFY(CONFIG_USB_HID_DEVICE_COUNT, DEFINE_HID_CFG_DATA, (;), _); |
|
LISTIFY(CONFIG_USB_HID_DEVICE_COUNT, DEFINE_HID_DEV_DATA, (;), _); |
|
LISTIFY(CONFIG_USB_HID_DEVICE_COUNT, DEFINE_HID_DEVICE, (;), _);
|
|
|