usb: device_next: add initial HID device support

Add initial HID device support. Unlike the existing HID implementation,
the new implementation uses a devicetree to instantiate a HID device.
To the user, the HID device appears as a normal Zephyr RTOS device.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
This commit is contained in:
Johann Fischer 2022-12-19 13:55:58 +01:00 committed by Anas Nashif
commit c0e8f0d96b
9 changed files with 2472 additions and 0 deletions

View file

@ -0,0 +1,58 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
description: Bindings for HID device
compatible: "zephyr,hid-device"
include: base.yaml
properties:
interface-name:
type: string
description: |
HID device name. When this property is present, a USB device will use it
as the string descriptor of the interface.
protocol-code:
type: string
description: |
This property corresponds to the protocol codes defined in Chapter 4.3
of the HID specification. Only boot devices are required to set one of
the protocols, keyboard or mouse. For non-boot devices, this property is
not required or can be set to none.
- none: Device does not support the boot interface
- keyboard: Device supports boot interface and keyboard protocol
- mouse: Device supports boot interface and mouse protocol
enum:
- none
- keyboard
- mouse
in-report-size:
type: int
required: true
description: |
The size of the longest input report that the HID device can generate.
This property is used to determine the buffer length used for transfers.
in-polling-rate:
type: int
required: true
description: |
Input or output type reports polling rate in microseconds. For USB full
speed this could be clamped to 1ms or 255ms depending on the value.
out-report-size:
type: int
description: |
The size of the longest output report that the HID device can generate.
When this property is present, a USB device will use out pipe for output
reports, otherwise control pipe will be used for output reports.
out-polling-rate:
type: int
description: |
Output type reports polling rate in microseconds. For USB full
speed this could be clamped to 1ms or 255ms depending on the value.
This option is only effective if the out-report-size property is defined.

View file

@ -0,0 +1,218 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief USBD HID device API header
*/
#ifndef ZEPHYR_INCLUDE_USBD_HID_CLASS_DEVICE_H_
#define ZEPHYR_INCLUDE_USBD_HID_CLASS_DEVICE_H_
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/usb/class/hid.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USBD HID Device API
* @defgroup usbd_hid_device USBD HID device API
* @ingroup usb
* @{
*/
/*
* HID Device overview:
*
* +---------------------+
* | |
* | |
* | HID Device |
* | User "top half" |
* | of the device that +-------+
* | deals with input | |
* | sampling | |
* | | |
* | | |
* | ------------------- | |
* | | |
* | HID Device user | |
* | callbacks | |
* | handlers | |
* +---------------------+ |
* ^ | HID Device Driver API:
* | |
* set_protocol() | | hid_device_register()
* get_report() | | hid_device_submit_report(
* .... | | ...
* v |
* +---------------------+ |
* | | |
* | HID Device | |
* | "bottom half" |<------+
* | USB HID class |
* | implementation |
* | |
* | |
* +---------------------+
* ^
* v
* +--------------------+
* | |
* | USB Device |
* | Support |
* | |
* +--------------------+
*/
/** HID report types
* Report types used in Get/Set Report requests.
*/
enum {
HID_REPORT_TYPE_INPUT = 1,
HID_REPORT_TYPE_OUTPUT,
HID_REPORT_TYPE_FEATURE,
};
/**
* @brief HID device user callbacks
*
* Each device depends on a user part that handles feature, input, and output
* report processing according to the device functionality described by the
* report descriptor. Which callbacks must be implemented depends on the device
* functionality. The USB device part of the HID device, cannot interpret
* device specific report descriptor and only handles USB specific parts,
* transfers and validation of requests, all reports are opaque to it.
* Callbacks are called from the USB device stack thread and must not block.
*/
struct hid_device_ops {
/**
* The interface ready callback is called with the ready argument set
* to true when the corresponding interface is part of the active
* configuration and the device can e.g. begin submitting input
* reports, and with the argument set to false when the interface is no
* longer active. This callback is optional.
*/
void (*iface_ready)(const struct device *dev, const bool ready);
/**
* This callback is called for the HID Get Report request to get a
* feature, input, or output report, which is specified by the argument
* type. If there is no report ID in the report descriptor, the id
* argument is zero. The callback implementation must check the
* arguments, such as whether the report type is supported, and return
* a nonzero value to indicate an unsupported type or an error.
*/
int (*get_report)(const struct device *dev,
const uint8_t type, const uint8_t id,
const uint16_t len, uint8_t *const buf);
/**
* This callback is called for the HID Set Report request to set a
* feature, input, or output report, which is specified by the argument
* type. If there is no report ID in the report descriptor, the id
* argument is zero. The callback implementation must check the
* arguments, such as whether the report type is supported, and return
* a nonzero value to indicate an unsupported type or an error.
*/
int (*set_report)(const struct device *dev,
const uint8_t type, const uint8_t id,
const uint16_t len, const uint8_t *const buf);
/**
* Notification to limit intput report frequency.
* The device should mute an input report submission until a new
* event occurs or until the time specified by the duration value has
* elapsed. If a report ID is used in the report descriptor, the
* device must store the duration and handle the specified report
* accordingly. Duration time resolution is in miliseconds.
*/
void (*set_idle)(const struct device *dev,
const uint8_t id, const uint32_t duration);
/**
* If a report ID is used in the report descriptor, the device
* must implement this callback and return the duration for the
* specified report ID. Duration time resolution is in miliseconds.
*/
uint32_t (*get_idle)(const struct device *dev, const uint8_t id);
/**
* Notification that the host has changed the protocol from
* Boot Protocol(0) to Report Protocol(1) or vice versa.
*/
void (*set_protocol)(const struct device *dev, const uint8_t proto);
/**
* Notification that input report submitted with
* hid_device_submit_report() has been sent.
* If the device does not use the callback, hid_device_submit_report()
* will be processed synchronously.
*/
void (*input_report_done)(const struct device *dev);
/**
* New output report callback. Callback will only be called for reports
* received through the optional interrupt OUT pipe. If there is no
* interrupt OUT pipe, output reports will be received using set_report().
* If a report ID is used in the report descriptor, the host places the ID
* in the buffer first, followed by the report data.
*/
void (*output_report)(const struct device *dev, const uint16_t len,
const uint8_t *const buf);
/**
* Optional Start of Frame (SoF) event callback.
* There will always be software and hardware dependent jitter and
* latency. This should be used very carefully, it should not block
* and the execution time should be quite short.
*/
void (*sof)(const struct device *dev);
};
/**
* @brief Register HID device report descriptor and user callbacks
*
* The device must register report descriptor and user callbacks before
* USB device support is initialized and enabled.
*
* @param[in] dev Pointer to HID device
* @param[in] rdesc Pointer to HID report descriptor
* @param[in] rsize Size of HID report descriptor
* @param[in] ops Pointer to HID device callbacks
*/
int hid_device_register(const struct device *dev,
const uint8_t *const rdesc, const uint16_t rsize,
const struct hid_device_ops *const ops);
/**
* @brief Submit new input report
*
* Submit a new input report to be sent via the interrupt IN pipe. If sync is
* true, the functions will block until the report is sent.
* If the device does not provide input_report_done() callback,
* hid_device_submit_report() will be processed synchronously.
*
* @param[in] dev Pointer to HID device
* @param[in] size Size of the input report
* @param[in] report Input report buffer. Report buffer must be aligned.
*
* @return 0 on success, negative errno code on fail.
*/
int hid_device_submit_report(const struct device *dev,
const uint16_t size, const uint8_t *const report);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_USBD_HID_CLASS_DEVICE_H_ */

View file

@ -62,4 +62,10 @@ zephyr_library_sources_ifdef(
class/usbd_uac2.c
)
zephyr_library_sources_ifdef(
CONFIG_USBD_HID_SUPPORT
class/usbd_hid.c
class/usbd_hid_api.c
)
zephyr_linker_sources(DATA_SECTIONS usbd_data.ld)

View file

@ -8,3 +8,4 @@ rsource "Kconfig.cdc_ecm"
rsource "Kconfig.bt"
rsource "Kconfig.msc"
rsource "Kconfig.uac2"
rsource "Kconfig.hid"

View file

@ -0,0 +1,37 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
menuconfig USBD_HID_SUPPORT
bool "USB Human Interface Device support"
default y
depends on DT_HAS_ZEPHYR_HID_DEVICE_ENABLED
help
Enables USB Human Interface Device support.
if USBD_HID_SUPPORT
config USBD_HID_IN_BUF_COUNT
int "Number of buffers in the IN pool"
range 1 256
default 2
help
Number of buffers in the IN pool per HID instance.
config USBD_HID_OUT_BUF_COUNT
int "Number of buffers in the OUT pool"
range 1 256
default 2
help
Number of buffers in the OUT pool per HID instance.
config USBD_HID_INIT_PRIORITY
int "HID device init priority"
default KERNEL_INIT_PRIORITY_DEVICE
help
HID device initialization priority
module = USBD_HID
module-str = usbd hid
source "subsys/logging/Kconfig.template.log_config"
endif # USBD_HID_SUPPORT

View file

@ -0,0 +1,744 @@
/*
* 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_data {
struct usbd_hid_descriptor *const desc;
struct usbd_class_data *c_data;
struct net_buf_pool *const pool_out;
struct net_buf_pool *const pool_in;
const struct hid_device_ops *ops;
const uint8_t *rdesc;
size_t rsize;
const struct usb_desc_header **const fs_desc;
const struct usb_desc_header **const hs_desc;
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)
{
const struct device *dev = usbd_class_get_private(c_data);
struct hid_device_data *ddata = dev->data;
struct usbd_hid_descriptor *desc = ddata->desc;
return desc->in_ep.bEndpointAddress;
}
static inline uint8_t hid_get_out_ep(struct usbd_class_data *const c_data)
{
const struct device *dev = usbd_class_get_private(c_data);
struct hid_device_data *ddata = dev->data;
struct usbd_hid_descriptor *desc = ddata->desc;
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_contex *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);
} 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;
switch (type) {
case HID_REPORT_TYPE_INPUT:
LOG_DBG("Get Report, Input Report ID %u", id);
errno = ops->get_report(dev, type, id, net_buf_tailroom(buf), buf->data);
break;
case HID_REPORT_TYPE_OUTPUT:
LOG_DBG("Get Report, Output Report ID %u", id);
errno = ops->get_report(dev, type, id, net_buf_tailroom(buf), buf->data);
break;
case HID_REPORT_TYPE_FEATURE:
LOG_DBG("Get Report, Feature Report ID %u", id);
errno = ops->get_report(dev, type, id, net_buf_tailroom(buf), buf->data);
break;
default:
errno = -ENOTSUP;
break;
}
return 0;
}
static int handle_set_protocol(const struct device *dev,
const struct usb_setup_packet *const setup)
{
struct hid_device_data *const ddata = dev->data;
struct usbd_hid_descriptor *const desc = ddata->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)
{
struct hid_device_data *const ddata = dev->data;
struct usbd_hid_descriptor *const desc = ddata->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)
{
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 = ddata->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);
struct hid_device_data *ddata = dev->data;
const struct hid_device_ops *const ops = ddata->ops;
struct usbd_hid_descriptor *const desc = ddata->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);
struct hid_device_data *ddata = dev->data;
if (speed == USBD_SPEED_HS) {
return ddata->hs_desc;
}
return ddata->fs_desc;
}
static int usbd_hid_init(struct usbd_class_data *const c_data)
{
LOG_DBG("HID class %s init", c_data->name);
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(struct hid_device_data *const ddata,
const uint16_t size, void *const data,
const uint8_t ep)
{
struct net_buf *buf = NULL;
struct udc_buf_info *bi;
buf = net_buf_alloc_with_data(ddata->pool_in, data, size, K_NO_WAIT);
if (!buf) {
return NULL;
}
bi = udc_get_buf_info(buf);
memset(bi, 0, sizeof(struct udc_buf_info));
bi->ep = ep;
return buf;
}
static struct net_buf *hid_buf_alloc(struct hid_device_data *const ddata,
const uint8_t ep)
{
struct net_buf *buf = NULL;
struct udc_buf_info *bi;
buf = net_buf_alloc(ddata->pool_out, K_NO_WAIT);
if (!buf) {
return NULL;
}
bi = udc_get_buf_info(buf);
memset(bi, 0, sizeof(struct udc_buf_info));
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);
struct usbd_class_data *c_data = ddata->c_data;
struct net_buf *buf;
if (!atomic_test_bit(&ddata->state, HID_DEV_CLASS_ENABLED)) {
return;
}
buf = hid_buf_alloc(ddata, 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)
{
struct hid_device_data *const ddata = dev->data;
const struct hid_device_ops *ops = ddata->ops;
struct usbd_class_data *c_data = ddata->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(ddata, 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)
{
struct hid_device_data *const ddata = dev->data;
struct usbd_hid_descriptor *const desc = ddata->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;
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) \
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); \
\
USBD_DEFINE_CLASS(hid_##n, \
&usbd_hid_api, \
(void *)DEVICE_DT_GET(DT_DRV_INST(n)), NULL); \
\
static struct hid_device_data hid_data_##n = { \
.desc = &hid_desc_##n, \
.c_data = &hid_##n, \
.pool_in = &hid_buf_pool_in_##n, \
.pool_out = HID_OUT_POOL_ADDR(n), \
.in_sem = Z_SEM_INITIALIZER(hid_data_##n.in_sem, 0, 1), \
.fs_desc = hid_fs_desc_##n, \
.hs_desc = hid_hs_desc_##n, \
}; \
\
DEVICE_DT_INST_DEFINE(n, hid_device_init, NULL, \
&hid_data_##n, NULL, \
POST_KERNEL, CONFIG_USBD_HID_INIT_PRIORITY, \
&hid_device_api);
DT_INST_FOREACH_STATUS_OKAY(USBD_HID_INSTANCE_DEFINE);

View file

@ -0,0 +1,216 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbd_hid_internal.h"
#include <stdint.h>
#include <assert.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/usb/class/usb_hid.h>
#include <zephyr/usb/class/usbd_hid.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(hid_api, CONFIG_USBD_HID_LOG_LEVEL);
int hid_device_submit_report(const struct device *dev,
const uint16_t size, const uint8_t *const report)
{
const struct hid_device_driver_api *api = dev->api;
return api->submit_report(dev, size, report);
}
int hid_device_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_driver_api *api = dev->api;
return api->dev_register(dev, rdesc, rsize, ops);
}
/* Legacy HID API wrapper below */
struct legacy_wrapper {
const struct device *dev;
const struct hid_ops *legacy_ops;
struct hid_device_ops *ops;
};
static struct hid_device_ops wrapper_ops;
#define DT_DRV_COMPAT zephyr_hid_device
#define USBD_HID_WRAPPER_DEFINE(n) \
{ \
.dev = DEVICE_DT_GET(DT_DRV_INST(n)), \
.ops = &wrapper_ops, \
},
static struct legacy_wrapper wrappers[] = {
DT_INST_FOREACH_STATUS_OKAY(USBD_HID_WRAPPER_DEFINE)
};
static const struct hid_ops *get_legacy_ops(const struct device *dev)
{
for (unsigned int i = 0; i < ARRAY_SIZE(wrappers); i++) {
if (wrappers[i].dev == dev) {
return wrappers[i].legacy_ops;
}
}
return NULL;
}
int wrapper_get_report(const struct device *dev,
const uint8_t type, const uint8_t id,
const uint16_t len, uint8_t *const buf)
{
const struct hid_ops *legacy_ops = get_legacy_ops(dev);
struct usb_setup_packet setup = {
.bmRequestType = 0,
.bRequest = 0,
.wValue = (type << 8) | id,
.wIndex = 0,
.wLength = len,
};
uint8_t *d = buf;
int l = len;
if (legacy_ops != NULL && legacy_ops->get_report != NULL) {
return legacy_ops->get_report(dev, &setup, &l, &d);
}
return -ENOTSUP;
}
int wrapper_set_report(const struct device *dev,
const uint8_t type, const uint8_t id,
const uint16_t len, const uint8_t *const buf)
{
const struct hid_ops *legacy_ops = get_legacy_ops(dev);
struct usb_setup_packet setup = {
.bmRequestType = 0,
.bRequest = 0,
.wValue = (type << 8) | id,
.wIndex = 0,
.wLength = len,
};
uint8_t *d = (void *)buf;
int l = len;
if (legacy_ops != NULL && legacy_ops->set_report != NULL) {
return legacy_ops->set_report(dev, &setup, &l, &d);
}
return -ENOTSUP;
}
void wrapper_set_idle(const struct device *dev,
const uint8_t id, const uint32_t duration)
{
if (id != 0U) {
LOG_ERR("Set Idle for %s ID %u duration %u cannot be propagated",
dev->name, id, duration);
}
}
void wrapper_set_protocol(const struct device *dev, const uint8_t proto)
{
const struct hid_ops *legacy_ops = get_legacy_ops(dev);
if (legacy_ops != NULL && legacy_ops->protocol_change != NULL) {
legacy_ops->protocol_change(dev, proto);
}
}
void wrapper_input_report_done(const struct device *dev)
{
const struct hid_ops *legacy_ops = get_legacy_ops(dev);
if (legacy_ops != NULL && legacy_ops->int_in_ready != NULL) {
legacy_ops->int_in_ready(dev);
}
}
void wrapper_output_report(const struct device *dev,
const uint16_t len, const uint8_t *const buf)
{
ARG_UNUSED(dev);
ARG_UNUSED(len);
ARG_UNUSED(buf);
__ASSERT(false, "Output report callback is not supported");
}
static struct hid_device_ops wrapper_ops = {
.get_report = wrapper_get_report,
.set_report = wrapper_set_report,
.set_idle = wrapper_set_idle,
.set_protocol = wrapper_set_protocol,
.input_report_done = wrapper_input_report_done,
.output_report = wrapper_output_report,
};
int hid_int_ep_write(const struct device *dev,
const uint8_t *data, uint32_t data_len, uint32_t *bytes_ret)
{
int ret;
ret = hid_device_submit_report(dev, data_len, data);
if (bytes_ret != NULL) {
*bytes_ret = ret == 0 ? data_len : 0;
}
return ret;
}
int hid_int_ep_read(const struct device *dev,
uint8_t *data, uint32_t max_data_len, uint32_t *ret_bytes)
{
ARG_UNUSED(dev);
ARG_UNUSED(data);
ARG_UNUSED(max_data_len);
ARG_UNUSED(ret_bytes);
LOG_ERR("Not supported");
return -ENOTSUP;
}
int usb_hid_set_proto_code(const struct device *dev, uint8_t proto_code)
{
ARG_UNUSED(dev);
ARG_UNUSED(proto_code);
LOG_WRN("Protocol code is set using DT property protocol-code");
return 0;
}
int usb_hid_init(const struct device *dev)
{
LOG_DBG("It does nothing for dev %s", dev->name);
return 0;
}
void usb_hid_register_device(const struct device *dev,
const uint8_t *desc, size_t size,
const struct hid_ops *ops)
{
for (unsigned int i = 0; i < ARRAY_SIZE(wrappers); i++) {
if (wrappers[i].dev == dev) {
wrappers[i].legacy_ops = ops;
if (hid_device_register(dev, desc, size, wrappers[i].ops)) {
LOG_ERR("Failed to register HID device");
}
}
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <zephyr/device.h>
#include <zephyr/usb/class/usbd_hid.h>
/*
* HID device driver API, we can keep internally as long as it is only used in
* USB.
*/
struct hid_device_driver_api {
int (*enable_output)(const struct device *dev, const bool enable);
int (*submit_report)(const struct device *dev,
const uint16_t size, const uint8_t *const report);
int (*dev_register)(const struct device *dev,
const uint8_t *const rdesc, const uint16_t rsize,
const struct hid_device_ops *const ops);
};

File diff suppressed because it is too large Load diff