/* * 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) { if (cdcacm->state != USBUS_CDC_ACM_LINE_STATE_DISCONNECTED) { return tsrb_add(&cdcacm->tsrb, buf, len); } /* stuff as much data as possible into tsrb, discarding the oldest */ size_t n = tsrb_free(&cdcacm->tsrb); if (len > n) { n += tsrb_drop(&cdcacm->tsrb, len - n); buf += len - n; } else { n = len; } tsrb_add(&cdcacm->tsrb, buf, n); /* behave as if everything has been written correctly */ return 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: initialization\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; } }