Merge pull request #14629 from Ollrogge/usb_hid_pr

USB HID minimal implementation
This commit is contained in:
Dylan Laduranty 2021-01-11 20:17:52 +01:00 committed by GitHub
commit 916f554d2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 689 additions and 0 deletions

View File

@ -971,6 +971,11 @@ ifneq (,$(filter usbus_cdc_ecm,$(USEMODULE)))
USEMODULE += luid
endif
ifneq (,$(filter usbus_hid,$(USEMODULE)))
USEMODULE += isrpipe_read_timeout
USEMODULE += usbus
endif
ifneq (,$(filter uuid,$(USEMODULE)))
USEMODULE += hashes
USEMODULE += random

111
sys/include/usb/hid.h Normal file
View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2020 Nils Ollrogge
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*/
/**
* @defgroup usb_hid HID - USB communications device class
* @ingroup usb
* @brief Generic USB HID defines and helpers
*
* @{
*
* @file
* @brief Definition for USB HID interfaces
*
* @author Nils Ollrogge <nils-ollrogge@outlook.de>
*/
#ifndef USB_HID_H
#define USB_HID_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USB HID type descriptor
*/
#define USB_TYPE_DESCRIPTOR_HID 0x21
/**
* @brief USB HID version in BCD
*/
#define USB_HID_VERSION_BCD 0x0110
/**
* @name USB HID subclass types
* @{
*/
#define USB_HID_SUBCLASS_NONE 0x0
#define USB_HID_SUBCLASS_BOOT 0x1
/** @} */
/**
* @name USB HID protocol types
* @{
*/
#define USB_HID_PROTOCOL_NONE 0x0
#define USB_HID_PROTOCOL_KEYBOARD 0x1
#define USB_HID_PROTOCOL_MOUSE 0x2
/** @} */
/**
* @name USB HID descriptor types
* @{
*/
#define USB_HID_DESCR_HID 0x21
#define USB_HID_DESCR_REPORT 0x22
#define USB_HID_DESCR_PHYSICAL 0x23
/** @} */
/**
* @brief USB HID country codes
*/
#define USB_HID_COUNTRY_CODE_NOTSUPPORTED 0x00
/**
* @name USB HID class specific control requests
* @{
*/
#define USB_HID_REQUEST_GET_REPORT 0x01
#define USB_HID_REQUEST_GET_IDLE 0x02
#define USB_HID_REQUEST_GET_PROTOCOL 0x03
#define USB_HID_REQUEST_SET_REPORT 0x09
#define USB_HID_REQUEST_SET_IDLE 0x0a
#define USB_HID_REQUEST_SET_PROTOCOL 0x0b
/** @} */
/**
* @brief USB HID descriptor struct
*
* @see USB HID 1.11 spec section 6.2.1
*/
typedef struct __attribute__((packed)){
uint8_t length; /**< Numeric expression that is the total size of
the HID descriptor */
uint8_t desc_type; /**< Constant name specifying type of HID
descriptor.*/
uint16_t bcd_hid; /**< Numeric expression identifying the HID Class
Specification release */
uint8_t country_code; /**< Numeric expression identifying country code of
the localized hardware. */
uint8_t num_descrs; /**< Numeric expression specifying the number of
class descriptors */
uint8_t report_type; /**< Type of HID class report. */
uint16_t report_length; /**< the total size of the Report descriptor. */
} usb_desc_hid_t;
#ifdef __cplusplus
}
#endif
#endif /* USB_HID_H */
/** @} */

119
sys/include/usb/usbus/hid.h Normal file
View File

@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 Nils Ollrogge
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*/
/**
* @defgroup usbus_hid USBUS HID
* @ingroup usb
* @brief USBUS HID interface module
*
* @{
*
* @file
* @brief Interface and definitions for USB HID type interfaces in
* USBUS.
*
* The functionality provided here only implements the USB
* specific handling. A different module is required to provide
* functional handling of the data e.g. UART or STDIO integration.
*
* @author Nils Ollrogge <nils-ollrogge@outlook.de>
*/
#ifndef USB_USBUS_HID_H
#define USB_USBUS_HID_H
#include <stdint.h>
#include "usb/usbus.h"
#include "usb/hid.h"
#include "mutex.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USB HID interrupt endpoint size
*/
#ifndef CONFIG_USBUS_HID_INTERRUPT_EP_SIZE
#define CONFIG_USBUS_HID_INTERRUPT_EP_SIZE 0x40
#endif
/**
* @brief USBUS HID context struct forward declaration
*/
typedef struct usbus_hid_device usbus_hid_device_t;
/**
* @brief HID data callback.
*
* Callback for received data from the USB host
*
* @param[in] hid HID handler context
* @param[in] data ptr to the data
* @param[in] len Length of the received data
*/
typedef void (*usbus_hid_cb_t)(usbus_hid_device_t *hid, uint8_t *data,
size_t len);
/**
* @brief USBUS HID context struct
*/
struct usbus_hid_device {
usbus_handler_t handler_ctrl; /**< control handler */
usbus_interface_t iface; /**< HID interface */
usbus_endpoint_t *ep_out; /**< OUT endpoint */
usbus_endpoint_t *ep_in; /**< IN endpoint */
usbus_descr_gen_t hid_descr; /**< HID descriptor generator */
const uint8_t *report_desc; /**< report descriptor reference */
size_t report_desc_size; /**< report descriptor size */
usbus_t *usbus; /**< USBUS reference */
size_t occupied; /**< Number of bytes for the host */
usbus_hid_cb_t cb; /**< Callback for data handlers */
event_t tx_ready; /**< Transmit ready event */
mutex_t in_lock; /**< mutex used for locking hid send */
};
/**
* @brief Initialize an USBUS HID interface
*
* @param[in] usbus USBUS context to register with
* @param[in] hid USBUS HID handler
* @param[in] cb Callback for data from the USB interface
* @param[in] report_desc USB_HID report descriptor
* @param[in] report_desc_size Size of USB_HID report descriptor
*/
void usbus_hid_init(usbus_t *usbus, usbus_hid_device_t *hid,
usbus_hid_cb_t cb, const uint8_t *report_desc,
size_t report_desc_size);
/**
* @brief Submit bytes to the HID handler
*
* @param[in] hid USBUS HID handler context
* @param[in] buf buffer to submit
* @param[in] len length of the submitted buffer
*
* @return Number of bytes added to the HID ring buffer
*/
size_t usbus_hid_submit(usbus_hid_device_t *hid, const uint8_t *buf,
size_t len);
/**
* @brief Flush the buffer to the USB host
*
* @param[in] hid USBUS HID handler context
*/
void usbus_hid_flush(usbus_hid_device_t *hid);
#ifdef __cplusplus
}
#endif
#endif /* USB_USBUS_HID_H */
/** @} */

View File

@ -7,4 +7,7 @@ endif
ifneq (,$(filter usbus_cdc_acm,$(USEMODULE)))
DIRS += cdc/acm
endif
ifneq (,$(filter usbus_hid,$(USEMODULE)))
DIRS += hid
endif
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,3 @@
MODULE = usbus_hid
include $(RIOTBASE)/Makefile.base

229
sys/usb/usbus/hid/hid.c Normal file
View File

@ -0,0 +1,229 @@
/*
* Copyright (C) 2020 Nils Ollrogge
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup usbus_hid
* @{
* @file
*
* @author Nils Ollrogge <nils-ollrogge@outlook.de>
* @}
*/
#define USB_H_USER_IS_RIOT_INTERNAL
#include <string.h>
#include "usb/usbus.h"
#include "usb/usbus/control.h"
#include "usb/usbus/hid.h"
#include "tsrb.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
static void _init(usbus_t *usbus, usbus_handler_t *handler);
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event);
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup);
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event);
static void _handle_tx_ready(event_t *ev);
static const usbus_handler_driver_t hid_driver = {
.init = _init,
.event_handler = _event_handler,
.control_handler = _control_handler,
.transfer_handler = _transfer_handler
};
static size_t _gen_hid_descriptor(usbus_t *usbus, void *arg);
static const usbus_descr_gen_funcs_t _hid_descriptor = {
.fmt_post_descriptor = _gen_hid_descriptor,
.len = {
.fixed_len = sizeof(usb_desc_hid_t)
},
.len_type = USBUS_DESCR_LEN_FIXED
};
static size_t _gen_hid_descriptor(usbus_t *usbus, void *arg)
{
usbus_hid_device_t *hid_dev = arg;
usb_desc_hid_t hid_desc;
hid_desc.length = sizeof(usb_desc_hid_t);
hid_desc.desc_type = USB_HID_DESCR_HID;
hid_desc.bcd_hid = USB_HID_VERSION_BCD;
hid_desc.country_code = USB_HID_COUNTRY_CODE_NOTSUPPORTED;
hid_desc.num_descrs = 0x01;
hid_desc.report_type = USB_HID_DESCR_REPORT;
hid_desc.report_length = hid_dev->report_desc_size;
usbus_control_slicer_put_bytes(usbus, (uint8_t *)&hid_desc,
sizeof(hid_desc));
return sizeof(usb_desc_hid_t);
}
static void _handle_tx_ready(event_t *ev)
{
usbus_hid_device_t *hid = container_of(ev, usbus_hid_device_t, tx_ready);
usbdev_ep_ready(hid->ep_in->ep, hid->occupied);
}
void usbus_hid_init(usbus_t *usbus, usbus_hid_device_t *hid, usbus_hid_cb_t cb,
const uint8_t *report_desc, size_t report_desc_size)
{
memset(hid, 0, sizeof(usbus_hid_device_t));
hid->usbus = usbus;
mutex_init(&hid->in_lock);
hid->handler_ctrl.driver = &hid_driver;
hid->report_desc = report_desc;
hid->report_desc_size = report_desc_size;
hid->cb = cb;
DEBUG("hid_init: %d %d \n", report_desc_size, report_desc[0]);
usbus_register_event_handler(usbus, &hid->handler_ctrl);
}
static void _init(usbus_t *usbus, usbus_handler_t *handler)
{
DEBUG("USB_HID: initialization\n");
usbus_hid_device_t *hid = (usbus_hid_device_t *)handler;
hid->tx_ready.handler = _handle_tx_ready;
hid->hid_descr.next = NULL;
hid->hid_descr.funcs = &_hid_descriptor;
hid->hid_descr.arg = hid;
/*
Configure Interface as USB_HID interface, choosing NONE for subclass and
protocol in order to represent a generic I/O device
*/
hid->iface.class = USB_CLASS_HID;
hid->iface.subclass = USB_HID_SUBCLASS_NONE;
hid->iface.protocol = USB_HID_PROTOCOL_NONE;
hid->iface.descr_gen = &hid->hid_descr;
hid->iface.handler = handler;
/* IN endpoint to send data to host */
hid->ep_in = usbus_add_endpoint(usbus, &hid->iface,
USB_EP_TYPE_INTERRUPT,
USB_EP_DIR_IN,
CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
/* interrupt endpoint polling rate in ms */
hid->ep_in->interval = 0x05;
usbus_enable_endpoint(hid->ep_in);
/* OUT endpoint to receive data from host */
hid->ep_out = usbus_add_endpoint(usbus, &hid->iface,
USB_EP_TYPE_INTERRUPT, USB_EP_DIR_OUT,
CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
/* interrupt endpoint polling rate in ms */
hid->ep_out->interval = 0x05;
usbus_enable_endpoint(hid->ep_out);
/* signal that INTERRUPT OUT is ready to receive data */
usbdev_ep_ready(hid->ep_out->ep, 0);
usbus_add_interface(usbus, &hid->iface);
}
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event)
{
(void)usbus;
(void)handler;
switch (event) {
default:
DEBUG("USB HID unhandled event: 0x%x\n", event);
break;
}
}
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup)
{
usbus_hid_device_t *hid = (usbus_hid_device_t *)handler;
DEBUG("USB_HID: request: %d type: %d value: %d length: %d state: %d \n",
setup->request, setup->type, setup->value >> 8, setup->length, state);
/* Requests defined in USB HID 1.11 spec section 7 */
switch (setup->request) {
case USB_SETUP_REQ_GET_DESCRIPTOR: {
uint8_t desc_type = setup->value >> 8;
if (desc_type == USB_HID_DESCR_REPORT) {
usbus_control_slicer_put_bytes(usbus, hid->report_desc,
hid->report_desc_size);
}
else if (desc_type == USB_HID_DESCR_HID) {
_gen_hid_descriptor(usbus, NULL);
}
break;
}
case USB_HID_REQUEST_GET_REPORT:
break;
case USB_HID_REQUEST_GET_IDLE:
break;
case USB_HID_REQUEST_GET_PROTOCOL:
break;
case USB_HID_REQUEST_SET_REPORT:
if ((state == USBUS_CONTROL_REQUEST_STATE_OUTDATA)) {
size_t size = 0;
uint8_t *data = usbus_control_get_out_data(usbus, &size);
if (size > 0) {
hid->cb(hid, data, size);
}
}
break;
case USB_HID_REQUEST_SET_IDLE:
break;
case USB_HID_REQUEST_SET_PROTOCOL:
break;
default:
DEBUG("USB_HID: unknown request %d \n", setup->request);
return -1;
}
return 1;
}
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event)
{
(void)usbus;
(void)event;
DEBUG("USB_HID: transfer_handler\n");
usbus_hid_device_t *hid = (usbus_hid_device_t *)handler;
if ((ep->dir == USB_EP_DIR_IN) && (ep->type == USB_EP_TYPE_INTERRUPT)) {
mutex_unlock(&hid->in_lock);
hid->occupied = 0;
}
else if ((ep->dir == USB_EP_DIR_OUT) &&
(ep->type == USB_EP_TYPE_INTERRUPT)) {
size_t len;
usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t));
if (len > 0) {
hid->cb(hid, ep->buf, len);
}
usbdev_ep_ready(ep, 0);
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 Nils Ollrogge
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup usbus_hid
* @{
* @file
* @brief This file implements a USB HID callback and read/write functions.
*
* @author Nils Ollrogge <nils-ollrogge@outlook.de>
* @}
*/
#define USB_H_USER_IS_RIOT_INTERNAL
#include <string.h>
#include "isrpipe.h"
#include "isrpipe/read_timeout.h"
#include "usb/usbus.h"
#include "usb/usbus/hid.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
static usbus_hid_device_t hid;
static uint8_t _hid_rx_buf_mem[CONFIG_USBUS_HID_INTERRUPT_EP_SIZE];
static isrpipe_t _hid_stdio_isrpipe = ISRPIPE_INIT(_hid_rx_buf_mem);
int usb_hid_io_read(void *buffer, size_t size)
{
return isrpipe_read(&_hid_stdio_isrpipe, buffer, size);
}
int usb_hid_io_read_timeout(void *buffer, size_t size, uint32_t timeout)
{
return isrpipe_read_timeout(&_hid_stdio_isrpipe, buffer, size, timeout);
}
void usb_hid_io_write(const void *buffer, size_t len)
{
uint8_t* buffer_ep = hid.ep_in->ep->buf;
uint16_t max_size = hid.ep_in->maxpacketsize;
size_t offset = 0;
while (len) {
mutex_lock(&hid.in_lock);
if (len > max_size) {
memmove(buffer_ep + offset, (uint8_t *)buffer + offset, max_size);
offset += max_size;
hid.occupied = max_size;
len -= max_size;
}
else {
memmove(buffer_ep + offset, (uint8_t *)buffer + offset, len);
offset += len;
hid.occupied = len;
len = 0;
}
usbus_event_post(hid.usbus, &hid.tx_ready);
}
}
static void _hid_rx_pipe(usbus_hid_device_t *hid, uint8_t *data, size_t len)
{
(void)hid;
for (size_t i = 0; i < len; i++) {
isrpipe_write_one(&_hid_stdio_isrpipe, data[i]);
}
}
void usb_hid_io_init(usbus_t *usbus, uint8_t *report_desc,
size_t report_desc_size)
{
usbus_hid_init(usbus, &hid, _hid_rx_pipe, report_desc, report_desc_size);
}

12
tests/usbus_hid/Makefile Normal file
View File

@ -0,0 +1,12 @@
BOARD ?= nrf52840dk
include ../Makefile.tests_common
USEMODULE += usbus_hid
DISABLE_MODULE += auto_init_usbus
USB_VID ?= ${USB_VID_TESTING}
USB_PID ?= ${USB_PID_TESTING}
include $(RIOTBASE)/Makefile.include

40
tests/usbus_hid/README.md Normal file
View File

@ -0,0 +1,40 @@
Expected result
===============
Connect you computer to the USB interface directly for the SoC and the
USB interface for power and debug.
Flash the device.
After flashing the device executing the command:
```
dmesg
```
should contain logs stating that a new USB HID device was found.
The output should look like the following:
```
[18579.559436] usb 1-9: new full-speed USB device number 7 using xhci_hcd
[18579.701474] usb 1-9: New USB device found, idVendor=1915, idProduct=521f, bcdDevice= 0.00
[18579.701481] usb 1-9: New USB device strings: Mfr=3, Product=2, SerialNumber=0
[18579.701484] usb 1-9: Product: Usb Hid Test Device
[18579.701487] usb 1-9: Manufacturer: RIOT-os.org
[18579.704613] hid-generic 0003:1915:521F.0008: hiddev0,hidraw6: USB HID v1.10 Device [RIOT-os.org Usb Hid Test Device] on usb-0000:00:14.0-9/input0
```
Note, that the endpoint (in this case hidraw6) might differ.
After successful initialization of USB HID one should be able to communicate
via USB HID simply by echoing input to the device.
Based on the screenshot above, an example command:
```
echo "Test" > /dev/hidraw6
```
The input string "Test" should be read by the test application and
printed to stdout.

85
tests/usbus_hid/main.c Normal file
View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2020 Nils Ollrogge
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @brief
* @{
*
* @brief Tests for USB HID
*
* @author Nils Ollrogge <nils-ollrogge@outlook.de>
*/
#include <stdio.h>
#include <stdlib.h>
#include "usb/usbus.h"
#include "xtimer.h"
#include "usb/usbus/hid.h"
/*
this descriptor is used, because the basic usb_hid interface was developed in
conjunction with FIDO2. Descriptor is taken from CTAP2 specification
(version 20190130) section 8.1.8.2
*/
static uint8_t report_desc_ctap[] = {
0x06, 0xD0, 0xF1, /* HID_UsagePage ( FIDO_USAGE_PAGE ) */
0x09, 0x01, /* HID_Usage ( FIDO_USAGE_CTAPHID ) */
0xA1, 0x01, /* HID_Collection ( HID_Application ) */
0x09, 0x20, /* HID_Usage ( FIDO_USAGE_DATA_IN ) */
0x15, 0x00, /* HID_LogicalMin ( 0 ) */
0x26, 0xFF, 0x00, /* HID_LogicalMaxS ( 0xff ) */
0x75, 0x08, /* HID_ReportSize ( 8 ) */
0x95, 0x40, /* HID_ReportCount ( HID_INPUT_REPORT_BYTES ) */
0x81, 0x02, /* HID_Input ( HID_Data | HID_Absolute | HID_Variable ) */
0x09, 0x21, /* HID_Usage ( FIDO_USAGE_DATA_OUT ) */
0x15, 0x00, /* HID_LogicalMin ( 0 ) */
0x26, 0xFF, 0x00, /* HID_LogicalMaxS ( 0xff ) */
0x75, 0x08, /* HID_ReportSize ( 8 ) */
0x95, 0x40, /* HID_ReportCount ( HID_OUTPUT_REPORT_BYTES ) */
0x91, 0x02, /* HID_Output ( HID_Data | HID_Absolute | HID_Variable ) */
0xC0, /* HID_EndCollection */
};
static usbus_t usbus;
static char _stack[USBUS_STACKSIZE];
void usb_hid_io_init(usbus_t* usbus, uint8_t* report_desc,
size_t report_desc_size);
ssize_t usb_hid_io_read(void* buffer, size_t len);
void init(void)
{
usbdev_t *usbdev = usbdev_get_ctx(0);
usbus_init(&usbus, usbdev);
usb_hid_io_init(&usbus, report_desc_ctap, sizeof(report_desc_ctap));
usbus_create(_stack, USBUS_STACKSIZE, USBUS_PRIO, USBUS_TNAME, &usbus);
}
int main(void)
{
/* sleep to wait for Pyterm attaching in order to see puts messages */
xtimer_sleep(3);
init();
puts("RIOT USB HID echo test");
puts("Write input to the hidraw device under /dev/hidrawX, and see if it gets echoed here");
uint8_t buffer[CONFIG_USBUS_HID_INTERRUPT_EP_SIZE];
for (;;) {
ssize_t len = usb_hid_io_read(buffer, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
printf("Msg received via USB HID: ");
for (int i = 0; i < len; i++) {
putc(buffer[i], stdout);
}
printf("\n");
}
}