diff --git a/sys/Makefile.dep b/sys/Makefile.dep index dc40d4f584..af860973f8 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -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 diff --git a/sys/include/usb/hid.h b/sys/include/usb/hid.h new file mode 100644 index 0000000000..d5cbd5370c --- /dev/null +++ b/sys/include/usb/hid.h @@ -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 + */ + +#ifndef USB_HID_H +#define USB_HID_H + +#include + +#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 */ +/** @} */ diff --git a/sys/include/usb/usbus/hid.h b/sys/include/usb/usbus/hid.h new file mode 100644 index 0000000000..e99e6931a7 --- /dev/null +++ b/sys/include/usb/usbus/hid.h @@ -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 + */ + +#ifndef USB_USBUS_HID_H +#define USB_USBUS_HID_H + +#include + +#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 */ +/** @} */ diff --git a/sys/usb/usbus/Makefile b/sys/usb/usbus/Makefile index 0c54ad8c63..c33c0aed0d 100644 --- a/sys/usb/usbus/Makefile +++ b/sys/usb/usbus/Makefile @@ -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 diff --git a/sys/usb/usbus/hid/Makefile b/sys/usb/usbus/hid/Makefile new file mode 100644 index 0000000000..9bc1576d47 --- /dev/null +++ b/sys/usb/usbus/hid/Makefile @@ -0,0 +1,3 @@ +MODULE = usbus_hid + +include $(RIOTBASE)/Makefile.base diff --git a/sys/usb/usbus/hid/hid.c b/sys/usb/usbus/hid/hid.c new file mode 100644 index 0000000000..0e520fc3fa --- /dev/null +++ b/sys/usb/usbus/hid/hid.c @@ -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 + * @} + */ + +#define USB_H_USER_IS_RIOT_INTERNAL + +#include + +#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); + } +} diff --git a/sys/usb/usbus/hid/hid_io.c b/sys/usb/usbus/hid/hid_io.c new file mode 100644 index 0000000000..8e0a34a091 --- /dev/null +++ b/sys/usb/usbus/hid/hid_io.c @@ -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 + * @} + */ + +#define USB_H_USER_IS_RIOT_INTERNAL + +#include + +#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); +} diff --git a/tests/usbus_hid/Makefile b/tests/usbus_hid/Makefile new file mode 100644 index 0000000000..92adda2678 --- /dev/null +++ b/tests/usbus_hid/Makefile @@ -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 diff --git a/tests/usbus_hid/README.md b/tests/usbus_hid/README.md new file mode 100644 index 0000000000..7fbf18a4fc --- /dev/null +++ b/tests/usbus_hid/README.md @@ -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. \ No newline at end of file diff --git a/tests/usbus_hid/main.c b/tests/usbus_hid/main.c new file mode 100644 index 0000000000..508390dcd7 --- /dev/null +++ b/tests/usbus_hid/main.c @@ -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 + */ + +#include +#include + +#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"); + } +}