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:
parent
92fc5316a4
commit
c0e8f0d96b
9 changed files with 2472 additions and 0 deletions
58
dts/bindings/usb/zephyr,hid-device.yaml
Normal file
58
dts/bindings/usb/zephyr,hid-device.yaml
Normal 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.
|
218
include/zephyr/usb/class/usbd_hid.h
Normal file
218
include/zephyr/usb/class/usbd_hid.h
Normal 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_ */
|
|
@ -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)
|
||||
|
|
|
@ -8,3 +8,4 @@ rsource "Kconfig.cdc_ecm"
|
|||
rsource "Kconfig.bt"
|
||||
rsource "Kconfig.msc"
|
||||
rsource "Kconfig.uac2"
|
||||
rsource "Kconfig.hid"
|
||||
|
|
37
subsys/usb/device_next/class/Kconfig.hid
Normal file
37
subsys/usb/device_next/class/Kconfig.hid
Normal 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
|
744
subsys/usb/device_next/class/usbd_hid.c
Normal file
744
subsys/usb/device_next/class/usbd_hid.c
Normal 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);
|
216
subsys/usb/device_next/class/usbd_hid_api.c
Normal file
216
subsys/usb/device_next/class/usbd_hid_api.c
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
23
subsys/usb/device_next/class/usbd_hid_internal.h
Normal file
23
subsys/usb/device_next/class/usbd_hid_internal.h
Normal 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);
|
||||
};
|
1169
subsys/usb/device_next/class/usbd_hid_macros.h
Normal file
1169
subsys/usb/device_next/class/usbd_hid_macros.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue