diff --git a/Makefile.dep b/Makefile.dep index faa290be4b..1339dbca5c 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -401,7 +401,7 @@ ifneq (,$(filter newlib,$(USEMODULE))) ifeq (,$(filter newlib_syscalls_%,$(USEMODULE))) USEMODULE += newlib_syscalls_default endif - ifeq (,$(filter stdio_rtt,$(USEMODULE))) + ifeq (,$(filter stdio_rtt stdio_cdc_acm,$(USEMODULE))) USEMODULE += stdio_uart endif endif @@ -414,6 +414,11 @@ ifneq (,$(filter posix_sockets,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter stdio_cdc_acm,$(USEMODULE))) + USEMODULE += usbus_cdc_acm + USEMODULE += isrpipe +endif + ifneq (,$(filter stdio_rtt,$(USEMODULE))) USEMODULE += xtimer endif @@ -880,6 +885,11 @@ ifneq (,$(filter usbus,$(USEMODULE))) USEMODULE += event endif +ifneq (,$(filter usbus_cdc_acm,$(USEMODULE))) + USEMODULE += tsrb + USEMODULE += usbus +endif + ifneq (,$(filter usbus_cdc_ecm,$(USEMODULE))) USEMODULE += iolist USEMODULE += fmt diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 818839dbe5..e9462ecac8 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -80,6 +80,7 @@ PSEUDOMODULES += sock_tcp PSEUDOMODULES += sock_udp PSEUDOMODULES += stdin PSEUDOMODULES += stdio_ethos +PSEUDOMODULES += stdio_cdc_acm PSEUDOMODULES += stdio_uart_rx PSEUDOMODULES += sock_dtls diff --git a/sys/auto_init/usb/auto_init_usb.c b/sys/auto_init/usb/auto_init_usb.c index 6d85d3622b..dfaf2d848e 100644 --- a/sys/auto_init/usb/auto_init_usb.c +++ b/sys/auto_init/usb/auto_init_usb.c @@ -28,6 +28,9 @@ #include "usb/usbus/cdc/ecm.h" usbus_cdcecm_device_t cdcecm; #endif +#ifdef MODULE_USBUS_CDC_ACM +#include "usb/usbus/cdc/acm.h" +#endif static char _stack[USBUS_STACKSIZE]; static usbus_t usbus; @@ -42,6 +45,11 @@ void auto_init_usb(void) usbus_init(&usbus, usbdev); /* USBUS function handlers initialization */ +#ifdef MODULE_STDIO_CDC_ACM + void usb_cdc_acm_stdio_init(usbus_t *usbus); + usb_cdc_acm_stdio_init(&usbus); +#endif + #ifdef MODULE_USBUS_CDC_ECM usbus_cdcecm_init(&usbus, &cdcecm); #endif diff --git a/sys/include/usb/cdc.h b/sys/include/usb/cdc.h index 51d2e71464..e7de020f36 100644 --- a/sys/include/usb/cdc.h +++ b/sys/include/usb/cdc.h @@ -90,6 +90,21 @@ extern "C" { * @{ */ +/** + * @brief Set line character formatting properties + */ +#define USB_CDC_MGNT_REQUEST_SET_LINE_CODING (0x20) + +/** + * @brief Request the currently configured line coding + */ +#define USB_CDC_MGNT_REQUEST_GET_LINE_CODING (0x21) + +/** + * @brief Set the control line state + */ +#define USB_CDC_MGNT_REQUEST_SET_CONTROL_LINE_STATE (0x22) + /** * @brief Set ethernet multicast filter request */ @@ -116,6 +131,22 @@ extern "C" { #define USB_CDC_MGNT_REQUEST_GET_ETH_STATISTICS 0x44 /** @} */ +/** + * @name USB CDC ACM control line state flags + * @{ + */ + +/** + * @brief DTE (e.g. a PC) is present and listening + */ +#define USB_CDC_ACM_CONTROL_LINE_DTE (0x01) + +/** + * @brief Activate carrier control for half duplex modems + */ +#define USB_CDC_ACM_CONTROL_LINE_CARRIER (0x02) +/** @} */ + /** * @name USB CDC management notifications * @{ @@ -238,6 +269,36 @@ typedef struct __attribute__((packed)) { uint32_t up; /**< Uplink bit rate */ } usb_desc_cdcecm_speed_t; +/** + * @name USB CDC ACM line coding setup defines + * @{ + */ + +/** + * @brief USB CDC ACM line coding setup content + * @see USB CDC 1.2 PSTN subclass spec section 6.3.11 + */ +typedef struct __attribute__((packed)) { + uint32_t baud; /**< Requested baud rate */ + uint8_t format; /**< Stop bits settings */ + uint8_t parity; /**< Parity settings */ + uint8_t databits; /**< Number of data bits (5, 6, 7, 8 or 16) */ +} usb_req_cdcacm_coding_t; + +#define USB_CDC_ACM_CODING_STOP_BITS_1 0 /**< 1 stop bit */ +#define USB_CDC_ACM_CODING_STOP_BITS_1_5 1 /**< 1.5 stop bits */ +#define USB_CDC_ACM_CODING_STOP_BITS_2 2 /**< 2 stop bits */ + +#define USB_CDC_ACM_CODING_PARITY_NONE 0 /**< No parity bit */ +#define USB_CDC_ACM_CODING_PARITY_ODD 1 /**< Odd parity */ +#define USB_CDC_ACM_CODING_PARITY_EVEN 2 /**< Even parity */ +#define USB_CDC_ACM_CODING_PARITY_MARK 3 /**< Mark parity */ +#define USB_CDC_ACM_CODING_PARITY_SPACE 4 /**< Space parity */ +/** @} */ + + +/** @} */ + #ifdef __cplusplus } #endif diff --git a/sys/include/usb/usbus/cdc/acm.h b/sys/include/usb/usbus/cdc/acm.h new file mode 100644 index 0000000000..63080463a4 --- /dev/null +++ b/sys/include/usb/usbus/cdc/acm.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2018 Mesotic SAS + * + * 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_cdc_acm USBUS CDC ACM - USBUS CDC abstract control model + * @ingroup usb + * @brief USBUS CDC ACM interface module + * + * @{ + * + * @file + * @brief Interface and definitions for USB CDC ACM 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 Dylan Laduranty + * @author Koen Zandberg + */ + +#ifndef USB_USBUS_CDC_ACM_H +#define USB_USBUS_CDC_ACM_H + +#include +#include "usb/cdc.h" +#include "usb/usbus.h" +#include "tsrb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Buffer size for STDIN and STDOUT data to and from USB when using + * the USBUS_CDC_ACM_STDIO module + */ +#ifndef USBUS_CDC_ACM_STDIO_BUF_SIZE +#define USBUS_CDC_ACM_STDIO_BUF_SIZE (128) +#endif + +/** + * @brief USB CDC ACM bulk endpoint size + */ +#ifndef USBUS_CDC_ACM_BULK_EP_SIZE +#define USBUS_CDC_ACM_BULK_EP_SIZE (64) +#endif + +/** + * @brief USBUS CDC ACM interrupt endpoint size. + */ +#define USBUS_CDC_ACM_INT_EP_SIZE (8) + +/** + * @brief CDC ACM line state as reported by the host computer + */ +typedef enum { + /** + * @brief No DTE connected + */ + USBUS_CDC_ACM_LINE_STATE_DISCONNECTED, + + /** + * @brief DTE (e.g. a personal computer) is present and connected + */ + USBUS_CDC_ACM_LINE_STATE_DTE +} usbus_cdcacm_line_state_t; + +/** + * @brief USBUS CDC ACM context struct forward declaration + */ +typedef struct usbus_cdcacm_device usbus_cdcacm_device_t; + +/** + * @brief CDC ACM data callback. + * + * Callback for received data from the USB host + * + * @param[in] cdcacm CDC ACM handler context + * @param[in] data ptr to the data + * @param[in] len Length of the received data + */ +typedef void (*usbus_cdcacm_cb_t)(usbus_cdcacm_device_t *cdcacm, + uint8_t *data, size_t len); + +/** + * @brief CDC ACM line coding callback. + * + * Callback for received line coding request from the USB host + * + * @param[in] cdcacm CDC ACM handler context + * @param[in] baud requested baud rate + * @param[in] bits requested number of data bits + * @param[in] parity requested parity + * @param[in] stop requested number of stop bits + * + * @return 0 when the mode is available + * @return negative if the mode is not available + */ +typedef int (*usbus_cdcacm_coding_cb_t)(usbus_cdcacm_device_t *cdcacm, + uint32_t baud, uint8_t bits, + uint8_t parity, uint8_t stop); + +/** + * @brief USBUS CDC ACM context struct + */ +struct usbus_cdcacm_device { + usbus_handler_t handler_ctrl; /**< control handler */ + usbus_interface_t iface_ctrl; /**< CDC control interface */ + usbus_interface_t iface_data; /**< CDC data interface */ + usbus_hdr_gen_t cdcacm_hdr; /**< CDC header generator */ + usbus_cdcacm_cb_t cb; /**< Callback for data handlers */ + usbus_cdcacm_coding_cb_t coding_cb; /**< Callback for ACM coding changes */ + tsrb_t tsrb; /**< TSRB for data to the host */ + usbus_t *usbus; /**< USBUS reference */ + size_t occupied; /**< Number of bytes for the host */ + usbus_cdcacm_line_state_t state; /**< Current line state */ + event_t flush; /**< device2host forced flush event */ + usb_req_cdcacm_coding_t coding; /**< Current coding configuration */ +}; + +/** + * @brief Initialize an USBUS CDC ACM interface + * + * @param[in] usbus USBUS context to register with + * @param[in] cdcacm USBUS CDC ACM handler + * @param[in] cb Callback for data from the USB interface + * @param[in] coding_cb Callback for control settings + * @param[in] buf Buffer for data to the USB interface + * @param[in] len Size in bytes of the buffer + */ +void usbus_cdc_acm_init(usbus_t *usbus, usbus_cdcacm_device_t *cdcacm, + usbus_cdcacm_cb_t cb, + usbus_cdcacm_coding_cb_t coding_cb, + uint8_t *buf, size_t len); + +/** + * @brief Submit bytes to the CDC ACM handler + * + * @param[in] cdcacm USBUS CDC ACM handler context + * @param[in] buf buffer to submit + * @param[in] len length of the submitted buffer + * + * @return Number of bytes added to the CDC ACM ring buffer + */ +size_t usbus_cdc_acm_submit(usbus_cdcacm_device_t *cdcacm, + const uint8_t *buf, size_t len); + +/** + * @brief Flush the buffer to the USB host + * + * @param[in] cdcacm USBUS CDC ACM handler context + */ +void usbus_cdc_acm_flush(usbus_cdcacm_device_t *cdcacm); + +/** + * @brief Set the callback for control settings + * + * Interrupts are disabled during update to ensure thread safety + * + * @param[in] cdcacm USBUS CDC ACM handler context + * @param[in] coding_cb Callback for control settings + */ +void usbus_cdc_acm_set_coding_cb(usbus_cdcacm_device_t *cdcacm, + usbus_cdcacm_coding_cb_t coding_cb); + +#ifdef __cplusplus +} +#endif + +#endif /* USB_USBUS_CDC_ACM_H */ +/** @} */ diff --git a/sys/usb/usbus/Makefile b/sys/usb/usbus/Makefile index 8ee522ad25..0c54ad8c63 100644 --- a/sys/usb/usbus/Makefile +++ b/sys/usb/usbus/Makefile @@ -4,4 +4,7 @@ SRCS += usbus_hdrs.c ifneq (,$(filter usbus_cdc_ecm,$(USEMODULE))) DIRS += cdc/ecm endif +ifneq (,$(filter usbus_cdc_acm,$(USEMODULE))) + DIRS += cdc/acm +endif include $(RIOTBASE)/Makefile.base diff --git a/sys/usb/usbus/cdc/acm/Makefile b/sys/usb/usbus/cdc/acm/Makefile new file mode 100644 index 0000000000..dac9843796 --- /dev/null +++ b/sys/usb/usbus/cdc/acm/Makefile @@ -0,0 +1,7 @@ +MODULE = usbus_cdc_acm +SRC = cdc_acm.c + +ifneq (,$(filter stdio_cdc_acm,$(USEMODULE))) + SRC += cdc_acm_stdio.c +endif +include $(RIOTBASE)/Makefile.base diff --git a/sys/usb/usbus/cdc/acm/cdc_acm.c b/sys/usb/usbus/cdc/acm/cdc_acm.c new file mode 100644 index 0000000000..f67dfb7e43 --- /dev/null +++ b/sys/usb/usbus/cdc/acm/cdc_acm.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2018 Dylan Laduranty + * + * 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_cdc_acm + * @{ + * @file + * + * @author Dylan Laduranty + * @author Koen Zandberg + * @} + */ + +#include + +#include "tsrb.h" +#include "usb/descriptor.h" +#include "usb/cdc.h" +#include "usb/descriptor.h" +#include "usb/usbus.h" +#include "usb/usbus/cdc/acm.h" +#include "usb/usbus/control.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_flush(event_t *ev); + +static const usbus_handler_driver_t cdc_driver = { + .init = _init, + .event_handler = _event_handler, + .control_handler = _control_handler, + .transfer_handler = _transfer_handler, +}; + +static size_t _gen_full_acm_descriptor(usbus_t *usbus, void *arg); + +/* Descriptors */ +static const usbus_hdr_gen_funcs_t _cdcacm_descriptor = { + .get_header = _gen_full_acm_descriptor, + .len = { + .fixed_len = sizeof(usb_desc_cdc_t) + + sizeof(usb_desc_acm_t) + + sizeof(usb_desc_union_t) + + sizeof(usb_desc_call_mngt_t), + }, + .len_type = USBUS_HDR_LEN_FIXED, +}; + +static size_t _gen_mngt_descriptor(usbus_t *usbus, usbus_cdcacm_device_t *cdcacm) +{ + usb_desc_call_mngt_t mngt; + /* functional call management descriptor */ + mngt.length = sizeof(usb_desc_call_mngt_t); + mngt.type = USB_TYPE_DESCRIPTOR_CDC; + mngt.subtype = USB_CDC_DESCR_SUBTYPE_CALL_MGMT; + mngt.capabalities = 0; + mngt.data_if = cdcacm->iface_data.idx; + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&mngt, sizeof(mngt)); + return sizeof(usb_desc_call_mngt_t); +} + +static size_t _gen_union_descriptor(usbus_t *usbus, + usbus_cdcacm_device_t *cdcacm) +{ + usb_desc_union_t uni; + /* functional union descriptor */ + uni.length = sizeof(usb_desc_union_t); + uni.type = USB_TYPE_DESCRIPTOR_CDC; + uni.subtype = USB_CDC_DESCR_SUBTYPE_UNION; + uni.master_if = cdcacm->iface_ctrl.idx; + uni.slave_if = cdcacm->iface_data.idx; + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&uni, sizeof(uni)); + return sizeof(usb_desc_union_t); +} + +static size_t _gen_acm_descriptor(usbus_t *usbus) +{ + usb_desc_acm_t acm; + /* functional cdc acm descriptor */ + acm.length = sizeof(usb_desc_acm_t); + acm.type = USB_TYPE_DESCRIPTOR_CDC; + acm.subtype = USB_CDC_DESCR_SUBTYPE_ACM; + /* Support for Set/Get_Line_coding, Control_State, and Serial_State notif */ + acm.capabalities = 0x02; + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&acm, sizeof(acm)); + return sizeof(usb_desc_acm_t); +} + +static size_t _gen_cdc_descriptor(usbus_t *usbus) +{ + usb_desc_cdc_t cdc; + /* functional cdc descriptor */ + cdc.length = sizeof(usb_desc_cdc_t); + cdc.bcd_cdc = USB_CDC_VERSION_BCD; + cdc.type = USB_TYPE_DESCRIPTOR_CDC; + cdc.subtype = USB_CDC_DESCR_SUBTYPE_FUNCTIONAL; + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&cdc, sizeof(cdc)); + return sizeof(usb_desc_cdc_t); +} + +static size_t _gen_full_acm_descriptor(usbus_t *usbus, void *arg) +{ + usbus_cdcacm_device_t *cdcacm = (usbus_cdcacm_device_t*)arg; + size_t total_len = 0; + total_len += _gen_cdc_descriptor(usbus); + total_len += _gen_acm_descriptor(usbus); + total_len += _gen_union_descriptor(usbus, cdcacm); + total_len += _gen_mngt_descriptor(usbus, cdcacm); + return total_len; +} + +/* Submit (ACM interface in) */ +size_t usbus_cdc_acm_submit(usbus_cdcacm_device_t *cdcacm, const uint8_t *buf, size_t len) +{ + return tsrb_add(&cdcacm->tsrb, buf, len); +} + +void usbus_cdc_acm_set_coding_cb(usbus_cdcacm_device_t *cdcacm, + usbus_cdcacm_coding_cb_t coding_cb) +{ + irq_disable(); + cdcacm->coding_cb = coding_cb; + irq_enable(); +} + +/* flush event */ +void usbus_cdc_acm_flush(usbus_cdcacm_device_t *cdcacm) +{ + if (cdcacm->usbus) { + usbus_event_post(cdcacm->usbus, &cdcacm->flush); + } +} + +void usbus_cdc_acm_init(usbus_t *usbus, usbus_cdcacm_device_t *cdcacm, + usbus_cdcacm_cb_t cb, usbus_cdcacm_coding_cb_t coding_cb, + uint8_t *buf, size_t len) +{ + memset(cdcacm, 0, sizeof(usbus_cdcacm_device_t)); + cdcacm->usbus = usbus; + tsrb_init(&cdcacm->tsrb, buf, len); + cdcacm->handler_ctrl.driver = &cdc_driver; + cdcacm->cb = cb; + cdcacm->coding_cb = coding_cb; + cdcacm->state = USBUS_CDC_ACM_LINE_STATE_DISCONNECTED; + usbus_register_event_handler(usbus, &cdcacm->handler_ctrl); +} + +static void _init(usbus_t *usbus, usbus_handler_t *handler) +{ + DEBUG("CDC_ACM: intialization\n"); + usbus_cdcacm_device_t *cdcacm = (usbus_cdcacm_device_t*)handler; + + cdcacm->flush.handler = _handle_flush; + + cdcacm->cdcacm_hdr.next = NULL; + cdcacm->cdcacm_hdr.funcs = &_cdcacm_descriptor; + cdcacm->cdcacm_hdr.arg = cdcacm; + + /* Configure Interface 0 as control interface */ + cdcacm->iface_ctrl.class = USB_CLASS_CDC_CONTROL ; + cdcacm->iface_ctrl.subclass = USB_CDC_SUBCLASS_ACM; + cdcacm->iface_ctrl.protocol = USB_CDC_PROTOCOL_NONE; + cdcacm->iface_ctrl.hdr_gen = &cdcacm->cdcacm_hdr; + cdcacm->iface_ctrl.handler = handler; + /* Configure second interface to handle data endpoint */ + cdcacm->iface_data.class = USB_CLASS_CDC_DATA ; + cdcacm->iface_data.subclass = USB_CDC_SUBCLASS_NONE; + cdcacm->iface_data.protocol = USB_CDC_PROTOCOL_NONE; + cdcacm->iface_data.hdr_gen = NULL; + cdcacm->iface_data.handler = handler; + + /* Create required endpoints */ + usbus_endpoint_t *ep = usbus_add_endpoint(usbus, &cdcacm->iface_ctrl, + USB_EP_TYPE_INTERRUPT, + USB_EP_DIR_IN, 8); + ep->interval = 255; /* Max interval */ + usbus_enable_endpoint(ep); + ep = usbus_add_endpoint(usbus, &cdcacm->iface_data, + USB_EP_TYPE_BULK, USB_EP_DIR_IN, + USBUS_CDC_ACM_BULK_EP_SIZE); + ep->interval = 0; /* Interval is not used with bulk endpoints */ + usbus_enable_endpoint(ep); + ep = usbus_add_endpoint(usbus, &cdcacm->iface_data, + USB_EP_TYPE_BULK, USB_EP_DIR_OUT, + USBUS_CDC_ACM_BULK_EP_SIZE); + ep->interval = 0; /* Interval is not used with bulk endpoints */ + usbus_enable_endpoint(ep); + usbdev_ep_ready(ep->ep, 0); + + /* Add interfaces to the stack */ + usbus_add_interface(usbus, &cdcacm->iface_ctrl); + usbus_add_interface(usbus, &cdcacm->iface_data); + + usbus_handler_set_flag(handler, USBUS_HANDLER_FLAG_RESET); +} + +static int _control_handler(usbus_t *usbus, usbus_handler_t *handler, + usbus_control_request_state_t state, + usb_setup_t *setup) +{ + (void)state; + usbus_cdcacm_device_t *cdcacm = (usbus_cdcacm_device_t*)handler; + switch(setup->request) { + case USB_CDC_MGNT_REQUEST_SET_LINE_CODING: + if ((state == USBUS_CONTROL_REQUEST_STATE_OUTDATA) && + (setup->length == sizeof(usb_req_cdcacm_coding_t))) { + size_t len = 0; + usb_req_cdcacm_coding_t *coding = + (usb_req_cdcacm_coding_t*)usbus_control_get_out_data(usbus, + &len); + + if (len != sizeof(usb_req_cdcacm_coding_t)) { + DEBUG("CDCACM: incorrect length of LINE_CODING set request" + ", expected: %u, got: %u", + sizeof(usb_req_cdcacm_coding_t), len); + return -1; + } + if (cdcacm->coding_cb) { + DEBUG("Setting line coding to baud rate %" PRIu32 ", " + "%u data bits, parity value %u, stop bit value %u\n", + coding->baud, coding->databits, coding->parity, + coding->format); + if (cdcacm->coding_cb(cdcacm, coding->baud, + coding->databits, coding->parity, + coding->format) < 0) { + return -1; + } + } + memcpy(&cdcacm->coding, coding, + sizeof(usb_req_cdcacm_coding_t)); + } + break; + case USB_CDC_MGNT_REQUEST_GET_LINE_CODING: + usbus_control_slicer_put_bytes(usbus, (uint8_t*)&cdcacm->coding, + sizeof(usb_req_cdcacm_coding_t)); + break; + case USB_CDC_MGNT_REQUEST_SET_CONTROL_LINE_STATE: + if (setup->value & USB_CDC_ACM_CONTROL_LINE_DTE) { + DEBUG("CDC ACM: DTE enabled on interface %u\n", setup->index); + cdcacm->state = USBUS_CDC_ACM_LINE_STATE_DTE; + usbus_cdc_acm_flush(cdcacm); + } + else { + cdcacm->state = USBUS_CDC_ACM_LINE_STATE_DISCONNECTED; + DEBUG("CDC ACM: DTE disabled on interface %u\n", setup->index); + } + break; + default: + DEBUG("unhandled USB setup request:0x%x\n", setup->request); + return -1; + } + return 1; +} + +static void _handle_in(usbus_cdcacm_device_t *cdcacm, + usbdev_ep_t *ep) +{ + if ((cdcacm->usbus->state != USBUS_STATE_CONFIGURED) || + (cdcacm->state != USBUS_CDC_ACM_LINE_STATE_DTE)) { + return; + } + while (!tsrb_empty(&cdcacm->tsrb)) { + int c = tsrb_get_one(&cdcacm->tsrb); + ep->buf[cdcacm->occupied++] = (uint8_t)c; + if (cdcacm->occupied >= USBUS_CDC_ACM_BULK_EP_SIZE) { + break; + } + } + usbdev_ep_ready(ep, cdcacm->occupied); +} + +static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler, + usbdev_ep_t *ep, usbus_event_transfer_t event) +{ + (void)usbus; + (void)event; /* Only receives TR_COMPLETE events */ + usbus_cdcacm_device_t *cdcacm = (usbus_cdcacm_device_t*)handler; + if ((ep->dir == USB_EP_DIR_OUT) && (ep->type == USB_EP_TYPE_BULK)) { + size_t len; + /* Retrieve incoming data */ + usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t)); + if (len > 0) { + cdcacm->cb(cdcacm, ep->buf, len); + } + usbdev_ep_ready(ep, 0); + } + if ((ep->dir == USB_EP_DIR_IN) && (ep->type == USB_EP_TYPE_BULK)) { + cdcacm->occupied = 0; + if (!tsrb_empty(&cdcacm->tsrb)) { + return _handle_in(cdcacm, ep); + } + } +} + +static void _handle_flush(event_t *ev) +{ + usbus_cdcacm_device_t *cdcacm = container_of(ev, usbus_cdcacm_device_t, + flush); + if (cdcacm->occupied == 0) { + _handle_in(cdcacm, cdcacm->iface_data.ep->next->ep); + } +} + +static void _handle_reset(usbus_handler_t *handler) +{ + usbus_cdcacm_device_t *cdcacm = (usbus_cdcacm_device_t *)handler; + DEBUG("CDC ACM: Reset notification received\n"); + + cdcacm->state = USBUS_CDC_ACM_LINE_STATE_DISCONNECTED; +} + +static void _event_handler(usbus_t *usbus, usbus_handler_t *handler, usbus_event_usb_t event) +{ + (void)usbus; + switch(event) { + case USBUS_EVENT_USB_RESET: + _handle_reset(handler); + break; + + default: + DEBUG("Unhandled event :0x%x\n", event); + break; + } +} diff --git a/sys/usb/usbus/cdc/acm/cdc_acm_stdio.c b/sys/usb/usbus/cdc/acm/cdc_acm_stdio.c new file mode 100644 index 0000000000..c61af0b223 --- /dev/null +++ b/sys/usb/usbus/cdc/acm/cdc_acm_stdio.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 Koen Zandberg + * + * 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 sys + * @{ + * + * @file + * @brief CDC ACM stdio implementation for USBUS CDC ACM + * + * This file implements a USB CDC ACM callback and read/write functions. + * + * + * @} + */ + +#include + +#include "isrpipe.h" + +#include "usb/usbus.h" +#include "usb/usbus/cdc/acm.h" + +#if MODULE_VFS +#include "vfs.h" +#endif + +static usbus_cdcacm_device_t cdcacm; +static uint8_t _cdc_tx_buf_mem[USBUS_CDC_ACM_STDIO_BUF_SIZE]; +static uint8_t _cdc_rx_buf_mem[USBUS_CDC_ACM_STDIO_BUF_SIZE]; +static isrpipe_t _cdc_stdio_isrpipe = ISRPIPE_INIT(_cdc_rx_buf_mem); + +void stdio_init(void) +{ + /* Initialize this side of the CDC ACM pipe */ +#if MODULE_VFS + vfs_bind_stdio(); +#endif +} + +ssize_t stdio_read(void* buffer, size_t len) +{ + (void)buffer; + (void)len; + return isrpipe_read(&_cdc_stdio_isrpipe, buffer, len); +} + +ssize_t stdio_write(const void* buffer, size_t len) +{ + usbus_cdc_acm_submit(&cdcacm, buffer, len); + usbus_cdc_acm_flush(&cdcacm); + /* Use tsrb and flush */ + return len; +} + +static void _cdc_acm_rx_pipe(usbus_cdcacm_device_t *cdcacm, + uint8_t *data, size_t len) +{ + (void)cdcacm; + for (size_t i = 0; i < len; i++) { + isrpipe_write_one(&_cdc_stdio_isrpipe, data[i]); + } +} + +void usb_cdc_acm_stdio_init(usbus_t *usbus) +{ + usbus_cdc_acm_init(usbus, &cdcacm, _cdc_acm_rx_pipe, NULL, + _cdc_tx_buf_mem, sizeof(_cdc_tx_buf_mem)); +} diff --git a/tests/usbus_cdc_acm_stdio/Makefile b/tests/usbus_cdc_acm_stdio/Makefile new file mode 100644 index 0000000000..711f6a2ea6 --- /dev/null +++ b/tests/usbus_cdc_acm_stdio/Makefile @@ -0,0 +1,27 @@ +BOARD ?= samr21-xpro +include ../Makefile.tests_common + +USEMODULE += auto_init_usbus +USEMODULE += stdio_cdc_acm +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps + +# USB device vendor and product ID +DEFAULT_VID = 1209 +DEFAULT_PID = 0001 +USB_VID ?= $(DEFAULT_VID) +USB_PID ?= $(DEFAULT_PID) + +CFLAGS += -DUSB_CONFIG_VID=0x$(USB_VID) -DUSB_CONFIG_PID=0x$(USB_PID) + +include $(RIOTBASE)/Makefile.include + +.PHONY: usb_id_check +usb_id_check: + @if [ $(USB_VID) = $(DEFAULT_VID) ] || [ $(USB_PID) = $(DEFAULT_PID) ] ; then \ + $(COLOR_ECHO) "$(COLOR_RED)Private testing pid.codes USB VID/PID used!, do not use it outside of test environments!$(COLOR_RESET)" 1>&2 ; \ + $(COLOR_ECHO) "$(COLOR_RED)MUST NOT be used on any device redistributed, sold or manufactured, VID/PID is not unique!$(COLOR_RESET)" 1>&2 ; \ + fi + +all: | usb_id_check diff --git a/tests/usbus_cdc_acm_stdio/README.md b/tests/usbus_cdc_acm_stdio/README.md new file mode 100644 index 0000000000..2c22dc52bf --- /dev/null +++ b/tests/usbus_cdc_acm_stdio/README.md @@ -0,0 +1,23 @@ +Expected result +=============== + +A second USB serial console (ttyACMx) appears when plugging the USB peripheral +into a host computer. When opening the serial device it should show the RIOT +shell. Basic command interaction must work. + +The test should work on Linux, MacOS and Windows. Putty is known to work on +Windows. + +Changing the baud rate, bit mode and parity mode is accepted by the device and +reflected back. However, changing these should not affect shell operation. + +Note that when testing with this firmware, the regular USB serial console from +the attached debugger is not functional. + +Background +========== + +This test application can be used to verify the USBUS CDC ACM implementation. +Assuming drivers available, the board under test should show up on the host +computer as an USB CDC Abstract Control Management device (ttyACMx on Linux). +Drivers are available for Linux, macOS and Windows. diff --git a/tests/usbus_cdc_acm_stdio/main.c b/tests/usbus_cdc_acm_stdio/main.c new file mode 100644 index 0000000000..e342013afd --- /dev/null +++ b/tests/usbus_cdc_acm_stdio/main.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * + * 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. + */ + +/** + * @file + * @brief Basic test for USB CDC ACM functionality. When plugged into a + * USB port, the peripheral should show up as a serial modem USB + * peripheral (/dev/ttyACMx on Linux) and should present the RIOT + * shell over this serial device. + * + * @author Koen Zandberg + * + */ + +#include + +#include "shell.h" +#include "shell_commands.h" + +int main(void) +{ + (void) puts("RIOT USB CDC ACM shell test"); + + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + + return 0; +}