1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

usbus: Add cdc acm function

This commit is contained in:
Koen Zandberg 2019-02-28 21:37:01 +01:00
parent 802012cbda
commit 852b7c8d0a
No known key found for this signature in database
GPG Key ID: 0895A893E6D2985B
5 changed files with 528 additions and 0 deletions

View File

@ -874,6 +874,11 @@ ifneq (,$(filter tlsf-malloc,$(USEMODULE)))
USEPKG += tlsf
endif
ifneq (,$(filter usbus_cdc_acm,$(USEMODULE)))
USEMODULE += tsrb
USEMODULE += usbus
endif
ifneq (,$(filter usbus,$(USEMODULE)))
FEATURES_REQUIRED += periph_usbdev
USEMODULE += core_thread_flags

View 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 */
/** @} */

View File

@ -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

View File

@ -0,0 +1,4 @@
MODULE = usbus_cdc_acm
SRC = cdc_acm.c
include $(RIOTBASE)/Makefile.base

View 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;
}
}