mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #11085 from bergzand/pr/usb/cdcacm
usbus: Add CDC-ACM (Serial console) function
This commit is contained in:
commit
3f0dfc14ac
12
Makefile.dep
12
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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
178
sys/include/usb/usbus/cdc/acm.h
Normal file
178
sys/include/usb/usbus/cdc/acm.h
Normal file
@ -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 <dylan.laduranty@mesotic.com>
|
||||
* @author Koen Zandberg <koen@bergzand.net>
|
||||
*/
|
||||
|
||||
#ifndef USB_USBUS_CDC_ACM_H
|
||||
#define USB_USBUS_CDC_ACM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#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 */
|
||||
/** @} */
|
@ -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
|
||||
|
7
sys/usb/usbus/cdc/acm/Makefile
Normal file
7
sys/usb/usbus/cdc/acm/Makefile
Normal file
@ -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
|
338
sys/usb/usbus/cdc/acm/cdc_acm.c
Normal file
338
sys/usb/usbus/cdc/acm/cdc_acm.c
Normal file
@ -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 <dylan.laduranty@mesotic.com>
|
||||
* @author Koen Zandberg <koen@bergzand.net>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
74
sys/usb/usbus/cdc/acm/cdc_acm_stdio.c
Normal file
74
sys/usb/usbus/cdc/acm/cdc_acm_stdio.c
Normal file
@ -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 <stdio.h>
|
||||
|
||||
#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));
|
||||
}
|
27
tests/usbus_cdc_acm_stdio/Makefile
Normal file
27
tests/usbus_cdc_acm_stdio/Makefile
Normal file
@ -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
|
23
tests/usbus_cdc_acm_stdio/README.md
Normal file
23
tests/usbus_cdc_acm_stdio/README.md
Normal file
@ -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.
|
33
tests/usbus_cdc_acm_stdio/main.c
Normal file
33
tests/usbus_cdc_acm_stdio/main.c
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Koen Zandberg <koen@bergzand.net>
|
||||
*
|
||||
* 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 <koen@bergzand.net>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user