This commit resolves a bug where the USB Endpoint provided by the HID driver was incorrect when in High Speed mode. Signed-off-by: Victor Brzeski <vbrzeski@gmail.com>
807 lines
22 KiB
C
807 lines
22 KiB
C
/*
|
|
* Copyright (c) 2023 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT zephyr_hid_device
|
|
|
|
#include "usbd_hid_internal.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/usb/usbd.h>
|
|
#include <zephyr/usb/class/usbd_hid.h>
|
|
#include <zephyr/drivers/usb/udc.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(usbd_hid, CONFIG_USBD_HID_LOG_LEVEL);
|
|
|
|
#define HID_GET_IDLE_DURATION(wValue) ((wValue) >> 8)
|
|
#define HID_GET_IDLE_ID(wValue) (wValue)
|
|
#define HID_GET_REPORT_TYPE(wValue) ((wValue) >> 8)
|
|
#define HID_GET_REPORT_ID(wValue) (wValue)
|
|
|
|
#define HID_SUBORDINATE_DESC_NUM 1
|
|
|
|
struct subordinate_info {
|
|
uint8_t bDescriptorType;
|
|
uint16_t wDescriptorLength;
|
|
} __packed;
|
|
|
|
/* See HID spec. 6.2 Class-Specific Descriptors */
|
|
struct hid_descriptor {
|
|
uint8_t bLength;
|
|
uint8_t bDescriptorType;
|
|
uint16_t bcdHID;
|
|
uint8_t bCountryCode;
|
|
uint8_t bNumDescriptors;
|
|
/* At least report subordinate descriptor is required. */
|
|
struct subordinate_info sub[HID_SUBORDINATE_DESC_NUM];
|
|
} __packed;
|
|
|
|
struct usbd_hid_descriptor {
|
|
struct usb_if_descriptor if0;
|
|
struct hid_descriptor hid;
|
|
struct usb_ep_descriptor in_ep;
|
|
struct usb_ep_descriptor hs_in_ep;
|
|
struct usb_ep_descriptor out_ep;
|
|
struct usb_ep_descriptor hs_out_ep;
|
|
|
|
struct usb_if_descriptor if0_1;
|
|
struct usb_ep_descriptor alt_hs_in_ep;
|
|
struct usb_ep_descriptor alt_hs_out_ep;
|
|
};
|
|
|
|
enum {
|
|
HID_DEV_CLASS_ENABLED,
|
|
};
|
|
|
|
struct hid_device_config {
|
|
struct usbd_hid_descriptor *desc;
|
|
struct usbd_class_data *c_data;
|
|
struct net_buf_pool *pool_out;
|
|
struct net_buf_pool *pool_in;
|
|
struct usbd_desc_node *const if_desc_data;
|
|
const struct usb_desc_header **fs_desc;
|
|
const struct usb_desc_header **hs_desc;
|
|
};
|
|
|
|
struct hid_device_data {
|
|
const struct device *dev;
|
|
const struct hid_device_ops *ops;
|
|
const uint8_t *rdesc;
|
|
size_t rsize;
|
|
atomic_t state;
|
|
struct k_sem in_sem;
|
|
struct k_work output_work;
|
|
uint8_t idle_rate;
|
|
uint8_t protocol;
|
|
};
|
|
|
|
static inline uint8_t hid_get_in_ep(struct usbd_class_data *const c_data)
|
|
{
|
|
struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct usbd_hid_descriptor *desc = dcfg->desc;
|
|
|
|
if (USBD_SUPPORTS_HIGH_SPEED && usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) {
|
|
return desc->hs_in_ep.bEndpointAddress;
|
|
}
|
|
|
|
return desc->in_ep.bEndpointAddress;
|
|
}
|
|
|
|
static inline uint8_t hid_get_out_ep(struct usbd_class_data *const c_data)
|
|
{
|
|
struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct usbd_hid_descriptor *desc = dcfg->desc;
|
|
|
|
if (USBD_SUPPORTS_HIGH_SPEED && usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) {
|
|
return desc->hs_out_ep.bEndpointAddress;
|
|
}
|
|
|
|
return desc->out_ep.bEndpointAddress;
|
|
}
|
|
|
|
static int usbd_hid_request(struct usbd_class_data *const c_data,
|
|
struct net_buf *const buf, const int err)
|
|
{
|
|
struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
struct hid_device_data *ddata = dev->data;
|
|
const struct hid_device_ops *ops = ddata->ops;
|
|
struct udc_buf_info *bi;
|
|
|
|
bi = udc_get_buf_info(buf);
|
|
|
|
if (bi->ep == hid_get_out_ep(c_data)) {
|
|
if (ops->output_report != NULL) {
|
|
if (err == 0) {
|
|
ops->output_report(dev, buf->len, buf->data);
|
|
}
|
|
|
|
k_work_submit(&ddata->output_work);
|
|
}
|
|
}
|
|
|
|
if (bi->ep == hid_get_in_ep(c_data)) {
|
|
if (ops->input_report_done != NULL) {
|
|
ops->input_report_done(dev, buf->__buf);
|
|
} else {
|
|
k_sem_give(&ddata->in_sem);
|
|
}
|
|
}
|
|
|
|
return usbd_ep_buf_free(uds_ctx, buf);
|
|
}
|
|
|
|
static int handle_set_idle(const struct device *dev,
|
|
const struct usb_setup_packet *const setup)
|
|
{
|
|
const uint32_t duration = HID_GET_IDLE_DURATION(setup->wValue);
|
|
const uint8_t id = HID_GET_IDLE_ID(setup->wValue);
|
|
struct hid_device_data *const ddata = dev->data;
|
|
const struct hid_device_ops *ops = ddata->ops;
|
|
|
|
if (id == 0U) {
|
|
/* Only the common idle rate is stored. */
|
|
ddata->idle_rate = duration;
|
|
}
|
|
|
|
if (ops->set_idle != NULL) {
|
|
ops->set_idle(dev, id, duration * 4UL);
|
|
} else {
|
|
errno = -ENOTSUP;
|
|
}
|
|
|
|
LOG_DBG("Set Idle, Report ID %u Duration %u", id, duration);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_get_idle(const struct device *dev,
|
|
const struct usb_setup_packet *const setup,
|
|
struct net_buf *const buf)
|
|
{
|
|
const uint8_t id = HID_GET_IDLE_ID(setup->wValue);
|
|
struct hid_device_data *const ddata = dev->data;
|
|
const struct hid_device_ops *ops = ddata->ops;
|
|
uint32_t duration;
|
|
|
|
if (setup->wLength != 1U) {
|
|
errno = -ENOTSUP;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* There is no Get Idle callback in the leagacy API, do not issue a
|
|
* protocol error if no callback is provided but ID is 0.
|
|
*/
|
|
if (id != 0U && ops->get_idle == NULL) {
|
|
errno = -ENOTSUP;
|
|
return 0;
|
|
}
|
|
|
|
if (id == 0U) {
|
|
/* Only the common idle rate is stored. */
|
|
duration = ddata->idle_rate;
|
|
} else {
|
|
duration = ops->get_idle(dev, id) / 4UL;
|
|
}
|
|
|
|
LOG_DBG("Get Idle, Report ID %u Duration %u", id, duration);
|
|
net_buf_add_u8(buf, duration);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_set_report(const struct device *dev,
|
|
const struct usb_setup_packet *const setup,
|
|
const struct net_buf *const buf)
|
|
{
|
|
const uint8_t type = HID_GET_REPORT_TYPE(setup->wValue);
|
|
const uint8_t id = HID_GET_REPORT_ID(setup->wValue);
|
|
struct hid_device_data *const ddata = dev->data;
|
|
const struct hid_device_ops *ops = ddata->ops;
|
|
|
|
if (ops->set_report == NULL) {
|
|
errno = -ENOTSUP;
|
|
LOG_DBG("Set Report not supported");
|
|
return 0;
|
|
}
|
|
|
|
switch (type) {
|
|
case HID_REPORT_TYPE_INPUT:
|
|
LOG_DBG("Set Report, Input Report ID %u", id);
|
|
errno = ops->set_report(dev, type, id, buf->len, buf->data);
|
|
break;
|
|
case HID_REPORT_TYPE_OUTPUT:
|
|
LOG_DBG("Set Report, Output Report ID %u", id);
|
|
errno = ops->set_report(dev, type, id, buf->len, buf->data);
|
|
break;
|
|
case HID_REPORT_TYPE_FEATURE:
|
|
LOG_DBG("Set Report, Feature Report ID %u", id);
|
|
errno = ops->set_report(dev, type, id, buf->len, buf->data);
|
|
break;
|
|
default:
|
|
errno = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_get_report(const struct device *dev,
|
|
const struct usb_setup_packet *const setup,
|
|
struct net_buf *const buf)
|
|
{
|
|
const uint8_t type = HID_GET_REPORT_TYPE(setup->wValue);
|
|
const uint8_t id = HID_GET_REPORT_ID(setup->wValue);
|
|
struct hid_device_data *const ddata = dev->data;
|
|
const struct hid_device_ops *ops = ddata->ops;
|
|
const size_t size = setup->wLength;
|
|
int ret = 0;
|
|
|
|
switch (type) {
|
|
case HID_REPORT_TYPE_INPUT:
|
|
LOG_DBG("Get Report, Input Report ID %u", id);
|
|
ret = ops->get_report(dev, type, id, size, buf->data);
|
|
break;
|
|
case HID_REPORT_TYPE_OUTPUT:
|
|
LOG_DBG("Get Report, Output Report ID %u", id);
|
|
ret = ops->get_report(dev, type, id, size, buf->data);
|
|
break;
|
|
case HID_REPORT_TYPE_FEATURE:
|
|
LOG_DBG("Get Report, Feature Report ID %u", id);
|
|
ret = ops->get_report(dev, type, id, size, buf->data);
|
|
break;
|
|
default:
|
|
errno = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
if (ret > 0) {
|
|
__ASSERT(ret <= net_buf_tailroom(buf),
|
|
"Buffer overflow in the HID driver");
|
|
net_buf_add(buf, MIN(net_buf_tailroom(buf), ret));
|
|
} else {
|
|
errno = ret ? ret : -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_set_protocol(const struct device *dev,
|
|
const struct usb_setup_packet *const setup)
|
|
{
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct hid_device_data *const ddata = dev->data;
|
|
struct usbd_hid_descriptor *const desc = dcfg->desc;
|
|
const struct hid_device_ops *const ops = ddata->ops;
|
|
const uint16_t protocol = setup->wValue;
|
|
|
|
if (protocol > HID_PROTOCOL_REPORT) {
|
|
/* Can only be 0 (Boot Protocol) or 1 (Report Protocol). */
|
|
errno = -ENOTSUP;
|
|
return 0;
|
|
}
|
|
|
|
if (desc->if0.bInterfaceSubClass == 0) {
|
|
/*
|
|
* The device does not support the boot protocol and we will
|
|
* not notify it.
|
|
*/
|
|
errno = -ENOTSUP;
|
|
return 0;
|
|
}
|
|
|
|
LOG_DBG("Set Protocol: %s", protocol ? "Report" : "Boot");
|
|
|
|
if (ddata->protocol != protocol) {
|
|
ddata->protocol = protocol;
|
|
|
|
if (ops->set_protocol) {
|
|
ops->set_protocol(dev, protocol);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_get_protocol(const struct device *dev,
|
|
const struct usb_setup_packet *const setup,
|
|
struct net_buf *const buf)
|
|
{
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct hid_device_data *const ddata = dev->data;
|
|
struct usbd_hid_descriptor *const desc = dcfg->desc;
|
|
|
|
if (setup->wValue != 0 || setup->wLength != 1) {
|
|
errno = -ENOTSUP;
|
|
return 0;
|
|
}
|
|
|
|
if (desc->if0.bInterfaceSubClass == 0) {
|
|
/* The device does not support the boot protocol */
|
|
errno = -ENOTSUP;
|
|
return 0;
|
|
}
|
|
|
|
LOG_DBG("Get Protocol: %s", ddata->protocol ? "Report" : "Boot");
|
|
net_buf_add_u8(buf, ddata->protocol);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_get_descriptor(const struct device *dev,
|
|
const struct usb_setup_packet *const setup,
|
|
struct net_buf *const buf)
|
|
{
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct hid_device_data *const ddata = dev->data;
|
|
uint8_t desc_type = USB_GET_DESCRIPTOR_TYPE(setup->wValue);
|
|
uint8_t desc_idx = USB_GET_DESCRIPTOR_INDEX(setup->wValue);
|
|
struct usbd_hid_descriptor *const desc = dcfg->desc;
|
|
|
|
switch (desc_type) {
|
|
case USB_DESC_HID_REPORT:
|
|
LOG_DBG("Get descriptor report");
|
|
net_buf_add_mem(buf, ddata->rdesc, MIN(ddata->rsize, setup->wLength));
|
|
break;
|
|
case USB_DESC_HID:
|
|
LOG_DBG("Get descriptor HID");
|
|
net_buf_add_mem(buf, &desc->hid, MIN(desc->hid.bLength, setup->wLength));
|
|
break;
|
|
case USB_DESC_HID_PHYSICAL:
|
|
LOG_DBG("Get descriptor physical %u", desc_idx);
|
|
errno = -ENOTSUP;
|
|
break;
|
|
default:
|
|
errno = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usbd_hid_ctd(struct usbd_class_data *const c_data,
|
|
const struct usb_setup_packet *const setup,
|
|
const struct net_buf *const buf)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
int ret = 0;
|
|
|
|
switch (setup->bRequest) {
|
|
case USB_HID_SET_IDLE:
|
|
ret = handle_set_idle(dev, setup);
|
|
break;
|
|
case USB_HID_SET_REPORT:
|
|
ret = handle_set_report(dev, setup, buf);
|
|
break;
|
|
case USB_HID_SET_PROTOCOL:
|
|
ret = handle_set_protocol(dev, setup);
|
|
break;
|
|
default:
|
|
errno = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int usbd_hid_cth(struct usbd_class_data *const c_data,
|
|
const struct usb_setup_packet *const setup,
|
|
struct net_buf *const buf)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
int ret = 0;
|
|
|
|
switch (setup->bRequest) {
|
|
case USB_HID_GET_IDLE:
|
|
ret = handle_get_idle(dev, setup, buf);
|
|
break;
|
|
case USB_HID_GET_REPORT:
|
|
ret = handle_get_report(dev, setup, buf);
|
|
break;
|
|
case USB_HID_GET_PROTOCOL:
|
|
ret = handle_get_protocol(dev, setup, buf);
|
|
break;
|
|
case USB_SREQ_GET_DESCRIPTOR:
|
|
ret = handle_get_descriptor(dev, setup, buf);
|
|
break;
|
|
default:
|
|
errno = -ENOTSUP;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void usbd_hid_sof(struct usbd_class_data *const c_data)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
struct hid_device_data *ddata = dev->data;
|
|
const struct hid_device_ops *const ops = ddata->ops;
|
|
|
|
if (ops->sof) {
|
|
ops->sof(dev);
|
|
}
|
|
}
|
|
|
|
static void usbd_hid_enable(struct usbd_class_data *const c_data)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct hid_device_data *ddata = dev->data;
|
|
const struct hid_device_ops *const ops = ddata->ops;
|
|
struct usbd_hid_descriptor *const desc = dcfg->desc;
|
|
|
|
atomic_set_bit(&ddata->state, HID_DEV_CLASS_ENABLED);
|
|
ddata->protocol = HID_PROTOCOL_REPORT;
|
|
if (ops->iface_ready) {
|
|
ops->iface_ready(dev, true);
|
|
}
|
|
|
|
if (desc->out_ep.bLength != 0U) {
|
|
k_work_submit(&ddata->output_work);
|
|
}
|
|
|
|
LOG_DBG("Configuration enabled");
|
|
}
|
|
|
|
static void usbd_hid_disable(struct usbd_class_data *const c_data)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
struct hid_device_data *ddata = dev->data;
|
|
const struct hid_device_ops *const ops = ddata->ops;
|
|
|
|
atomic_clear_bit(&ddata->state, HID_DEV_CLASS_ENABLED);
|
|
if (ops->iface_ready) {
|
|
ops->iface_ready(dev, false);
|
|
}
|
|
|
|
LOG_DBG("Configuration disabled");
|
|
}
|
|
|
|
static void usbd_hid_suspended(struct usbd_class_data *const c_data)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
|
|
LOG_DBG("Configuration suspended, device %s", dev->name);
|
|
}
|
|
|
|
static void usbd_hid_resumed(struct usbd_class_data *const c_data)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
|
|
LOG_DBG("Configuration resumed, device %s", dev->name);
|
|
}
|
|
|
|
static void *usbd_hid_get_desc(struct usbd_class_data *const c_data,
|
|
const enum usbd_speed speed)
|
|
{
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
|
|
if (USBD_SUPPORTS_HIGH_SPEED && speed == USBD_SPEED_HS) {
|
|
return dcfg->hs_desc;
|
|
}
|
|
|
|
return dcfg->fs_desc;
|
|
}
|
|
|
|
static int usbd_hid_init(struct usbd_class_data *const c_data)
|
|
{
|
|
struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data);
|
|
const struct device *dev = usbd_class_get_private(c_data);
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct usbd_hid_descriptor *const desc = dcfg->desc;
|
|
|
|
LOG_DBG("HID class %s init", c_data->name);
|
|
|
|
if (dcfg->if_desc_data != NULL && desc->if0.iInterface == 0) {
|
|
if (usbd_add_descriptor(uds_ctx, dcfg->if_desc_data)) {
|
|
LOG_ERR("Failed to add interface string descriptor");
|
|
} else {
|
|
desc->if0.iInterface = usbd_str_desc_get_idx(dcfg->if_desc_data);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void usbd_hid_shutdown(struct usbd_class_data *const c_data)
|
|
{
|
|
LOG_DBG("HID class %s shutdown", c_data->name);
|
|
}
|
|
|
|
static struct net_buf *hid_buf_alloc_ext(const struct hid_device_config *const dcfg,
|
|
const uint16_t size, void *const data,
|
|
const uint8_t ep)
|
|
{
|
|
struct net_buf *buf = NULL;
|
|
struct udc_buf_info *bi;
|
|
|
|
__ASSERT(IS_UDC_ALIGNED(data), "Application provided unaligned buffer");
|
|
|
|
buf = net_buf_alloc_with_data(dcfg->pool_in, data, size, K_NO_WAIT);
|
|
if (!buf) {
|
|
return NULL;
|
|
}
|
|
|
|
bi = udc_get_buf_info(buf);
|
|
bi->ep = ep;
|
|
|
|
return buf;
|
|
}
|
|
|
|
static struct net_buf *hid_buf_alloc(const struct hid_device_config *const dcfg,
|
|
const uint8_t ep)
|
|
{
|
|
struct net_buf *buf = NULL;
|
|
struct udc_buf_info *bi;
|
|
|
|
buf = net_buf_alloc(dcfg->pool_out, K_NO_WAIT);
|
|
if (!buf) {
|
|
return NULL;
|
|
}
|
|
|
|
bi = udc_get_buf_info(buf);
|
|
bi->ep = ep;
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void hid_dev_output_handler(struct k_work *work)
|
|
{
|
|
struct hid_device_data *ddata = CONTAINER_OF(work,
|
|
struct hid_device_data,
|
|
output_work);
|
|
const struct device *dev = ddata->dev;
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct usbd_class_data *c_data = dcfg->c_data;
|
|
struct net_buf *buf;
|
|
|
|
if (!atomic_test_bit(&ddata->state, HID_DEV_CLASS_ENABLED)) {
|
|
return;
|
|
}
|
|
|
|
buf = hid_buf_alloc(dcfg, hid_get_out_ep(c_data));
|
|
if (buf == NULL) {
|
|
LOG_ERR("Failed to allocate buffer");
|
|
return;
|
|
}
|
|
|
|
if (usbd_ep_enqueue(c_data, buf)) {
|
|
net_buf_unref(buf);
|
|
LOG_ERR("Failed to enqueue buffer");
|
|
}
|
|
}
|
|
|
|
static int hid_dev_submit_report(const struct device *dev,
|
|
const uint16_t size, const uint8_t *const report)
|
|
{
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct hid_device_data *const ddata = dev->data;
|
|
const struct hid_device_ops *ops = ddata->ops;
|
|
struct usbd_class_data *c_data = dcfg->c_data;
|
|
struct net_buf *buf;
|
|
int ret;
|
|
|
|
__ASSERT(IS_ALIGNED(report, sizeof(void *)), "Report buffer is not aligned");
|
|
|
|
if (!atomic_test_bit(&ddata->state, HID_DEV_CLASS_ENABLED)) {
|
|
return -EACCES;
|
|
}
|
|
|
|
buf = hid_buf_alloc_ext(dcfg, size, (void *)report, hid_get_in_ep(c_data));
|
|
if (buf == NULL) {
|
|
LOG_ERR("Failed to allocate net_buf");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = usbd_ep_enqueue(c_data, buf);
|
|
if (ret) {
|
|
net_buf_unref(buf);
|
|
return ret;
|
|
}
|
|
|
|
if (ops->input_report_done == NULL) {
|
|
k_sem_take(&ddata->in_sem, K_FOREVER);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hid_dev_register(const struct device *dev,
|
|
const uint8_t *const rdesc, const uint16_t rsize,
|
|
const struct hid_device_ops *const ops)
|
|
{
|
|
const struct hid_device_config *dcfg = dev->config;
|
|
struct hid_device_data *const ddata = dev->data;
|
|
struct usbd_hid_descriptor *const desc = dcfg->desc;
|
|
|
|
if (atomic_test_bit(&ddata->state, HID_DEV_CLASS_ENABLED)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
/* Get Report is required for all HID device types. */
|
|
if (ops == NULL || ops->get_report == NULL) {
|
|
LOG_ERR("get_report callback is missing");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Set Report is required when an output report is declared. */
|
|
if (desc->out_ep.bLength && ops->set_report == NULL) {
|
|
LOG_ERR("set_report callback is missing");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Get/Set Protocol are required when device supports boot interface.
|
|
* Get Protocol is handled internally, no callback is required.
|
|
*/
|
|
if (desc->if0.bInterfaceSubClass && ops->set_protocol == NULL) {
|
|
LOG_ERR("set_protocol callback is missing");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ddata->rdesc = rdesc;
|
|
ddata->rsize = rsize;
|
|
ddata->ops = ops;
|
|
|
|
sys_put_le16(ddata->rsize, (uint8_t *)&(desc->hid.sub[0].wDescriptorLength));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hid_device_init(const struct device *dev)
|
|
{
|
|
struct hid_device_data *const ddata = dev->data;
|
|
|
|
ddata->dev = dev;
|
|
|
|
k_sem_init(&ddata->in_sem, 0, 1);
|
|
|
|
k_work_init(&ddata->output_work, hid_dev_output_handler);
|
|
LOG_DBG("HID device %s init", dev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct usbd_class_api usbd_hid_api = {
|
|
.request = usbd_hid_request,
|
|
.update = NULL,
|
|
.sof = usbd_hid_sof,
|
|
.enable = usbd_hid_enable,
|
|
.disable = usbd_hid_disable,
|
|
.suspended = usbd_hid_suspended,
|
|
.resumed = usbd_hid_resumed,
|
|
.control_to_dev = usbd_hid_ctd,
|
|
.control_to_host = usbd_hid_cth,
|
|
.get_desc = usbd_hid_get_desc,
|
|
.init = usbd_hid_init,
|
|
.shutdown = usbd_hid_shutdown,
|
|
};
|
|
|
|
static const struct hid_device_driver_api hid_device_api = {
|
|
.submit_report = hid_dev_submit_report,
|
|
.dev_register = hid_dev_register,
|
|
};
|
|
|
|
#include "usbd_hid_macros.h"
|
|
|
|
#define USBD_HID_INTERFACE_SIMPLE_DEFINE(n) \
|
|
static struct usbd_hid_descriptor hid_desc_##n = { \
|
|
.if0 = HID_INTERFACE_DEFINE(n, 0), \
|
|
.hid = HID_DESCRIPTOR_DEFINE(n), \
|
|
.in_ep = HID_IN_EP_DEFINE(n, false, true), \
|
|
.hs_in_ep = HID_IN_EP_DEFINE(n, true, true), \
|
|
.out_ep = HID_OUT_EP_DEFINE_OR_ZERO(n, false, true), \
|
|
.hs_out_ep = HID_OUT_EP_DEFINE_OR_ZERO(n, true, true), \
|
|
}; \
|
|
\
|
|
const static struct usb_desc_header *hid_fs_desc_##n[] = { \
|
|
(struct usb_desc_header *) &hid_desc_##n.if0, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hid, \
|
|
(struct usb_desc_header *) &hid_desc_##n.in_ep, \
|
|
(struct usb_desc_header *) &hid_desc_##n.out_ep, \
|
|
NULL, \
|
|
}; \
|
|
\
|
|
const static struct usb_desc_header *hid_hs_desc_##n[] = { \
|
|
(struct usb_desc_header *) &hid_desc_##n.if0, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hid, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hs_in_ep, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hs_out_ep, \
|
|
NULL, \
|
|
}
|
|
|
|
#define USBD_HID_INTERFACE_ALTERNATE_DEFINE(n) \
|
|
static struct usbd_hid_descriptor hid_desc_##n = { \
|
|
.if0 = HID_INTERFACE_DEFINE(n, 0), \
|
|
.hid = HID_DESCRIPTOR_DEFINE(n), \
|
|
.in_ep = HID_IN_EP_DEFINE(n, false, false), \
|
|
.hs_in_ep = HID_IN_EP_DEFINE(n, true, false), \
|
|
.out_ep = HID_OUT_EP_DEFINE_OR_ZERO(n, false, false), \
|
|
.hs_out_ep = HID_OUT_EP_DEFINE_OR_ZERO(n, true, false), \
|
|
.if0_1 = HID_INTERFACE_DEFINE(n, 1), \
|
|
.alt_hs_in_ep = HID_IN_EP_DEFINE(n, true, true), \
|
|
.alt_hs_out_ep = HID_OUT_EP_DEFINE_OR_ZERO(n, true, true), \
|
|
}; \
|
|
\
|
|
const static struct usb_desc_header *hid_fs_desc_##n[] = { \
|
|
(struct usb_desc_header *) &hid_desc_##n.if0, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hid, \
|
|
(struct usb_desc_header *) &hid_desc_##n.in_ep, \
|
|
(struct usb_desc_header *) &hid_desc_##n.out_ep, \
|
|
NULL, \
|
|
}; \
|
|
\
|
|
const static struct usb_desc_header *hid_hs_desc_##n[] = { \
|
|
(struct usb_desc_header *) &hid_desc_##n.if0, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hid, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hs_in_ep, \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, out_report_size), \
|
|
((struct usb_desc_header *) &hid_desc_##n.hs_out_ep,), ()) \
|
|
(struct usb_desc_header *)&hid_desc_##n.if0_1, \
|
|
(struct usb_desc_header *) &hid_desc_##n.hid, \
|
|
(struct usb_desc_header *) &hid_desc_##n.alt_hs_in_ep, \
|
|
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, out_report_size), \
|
|
((struct usb_desc_header *) &hid_desc_##n.alt_hs_out_ep,), ()) \
|
|
NULL, \
|
|
}
|
|
|
|
#define USBD_HID_INTERFACE_DEFINE(n) \
|
|
COND_CODE_1(HID_ALL_MPS_LESS_65(n), \
|
|
(USBD_HID_INTERFACE_SIMPLE_DEFINE(n)), \
|
|
(USBD_HID_INTERFACE_ALTERNATE_DEFINE(n)))
|
|
|
|
#define USBD_HID_INSTANCE_DEFINE(n) \
|
|
HID_VERIFY_REPORT_SIZES(n); \
|
|
\
|
|
NET_BUF_POOL_DEFINE(hid_buf_pool_in_##n, \
|
|
CONFIG_USBD_HID_IN_BUF_COUNT, 0, \
|
|
sizeof(struct udc_buf_info), NULL); \
|
|
\
|
|
HID_OUT_POOL_DEFINE(n); \
|
|
USBD_HID_INTERFACE_DEFINE(n); \
|
|
\
|
|
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, label), ( \
|
|
USBD_DESC_STRING_DEFINE(hid_if_desc_data_##n, \
|
|
DT_INST_PROP(n, label), \
|
|
USBD_DUT_STRING_INTERFACE); \
|
|
)) \
|
|
\
|
|
USBD_DEFINE_CLASS(hid_##n, \
|
|
&usbd_hid_api, \
|
|
(void *)DEVICE_DT_GET(DT_DRV_INST(n)), NULL); \
|
|
\
|
|
static const struct hid_device_config hid_config_##n = { \
|
|
.desc = &hid_desc_##n, \
|
|
.c_data = &hid_##n, \
|
|
.pool_in = &hid_buf_pool_in_##n, \
|
|
.pool_out = HID_OUT_POOL_ADDR(n), \
|
|
.fs_desc = hid_fs_desc_##n, \
|
|
.hs_desc = hid_hs_desc_##n, \
|
|
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, label), ( \
|
|
.if_desc_data = &hid_if_desc_data_##n, \
|
|
)) \
|
|
}; \
|
|
\
|
|
static struct hid_device_data hid_data_##n; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, hid_device_init, NULL, \
|
|
&hid_data_##n, &hid_config_##n, \
|
|
POST_KERNEL, CONFIG_USBD_HID_INIT_PRIORITY, \
|
|
&hid_device_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(USBD_HID_INSTANCE_DEFINE);
|