/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
/* Copyright (c) 2016 King Kévin */
/* this library handles the USB CDC ACM */
/* standard libraries */
#include // standard integer types
#include // standard I/O facilities
#include // general utilities
/* STM32 (including CM3) libraries */
#include // real-time control clock library
#include // general purpose input output library
#include // interrupt handler
#include // Cortex M3 utilities
#include // USB library
#include // USB CDC library
#include "usb_cdcacm.h" // USB CDC ACM header and definitions
/* USB descriptor */
static const struct usb_device_descriptor device_descriptor = {
.bLength = USB_DT_DEVICE_SIZE, // the size of this header in bytes, 18
.bDescriptorType = USB_DT_DEVICE, // a value of 1 indicates that this is a device descriptor
.bcdUSB = 0x0200, // this device supports USB 2.0
.bDeviceClass = USB_CLASS_CDC, // use the CDC device class
.bDeviceSubClass = USB_CDC_SUBCLASS_ACM, // use the ACM sub-class
.bDeviceProtocol = USB_CDC_PROTOCOL_NONE, // use no specific protocol
.bMaxPacketSize0 = 64, // packet size for endpoint zero in bytes
.idVendor = 0xc440, // Vendor ID (CuVo...)
.idProduct = 0x0d00, // product ID within the Vendor ID space (...odoo)
.bcdDevice = 0x0100, // version number for the device
.iManufacturer = 1, // the index of the string in the string table that represents the name of the manufacturer of this device.
.iProduct = 2, // the index of the string in the string table that represents the name of the product
.iSerialNumber = 3, // the index of the string in the string table that represents the serial number of this item in string form.
.bNumConfigurations = 1, // the number of possible configurations this device has
};
static const struct usb_endpoint_descriptor data_endpoints[] = {{
.bLength = USB_DT_ENDPOINT_SIZE, // the size of the endpoint descriptor in bytes
.bDescriptorType = USB_DT_ENDPOINT, // a value of 5 indicates that this describes an endpoint
.bEndpointAddress = 0x01, // OUT (from host) direction (0<<7), endpoint 1
.bmAttributes = USB_ENDPOINT_ATTR_BULK, // bulk mode
.wMaxPacketSize = 64, // maximum packet size
.bInterval = 1, // the frequency, in number of frames, that we're going to be sending data
},{
.bLength = USB_DT_ENDPOINT_SIZE, // the size of the endpoint descriptor in bytes
.bDescriptorType = USB_DT_ENDPOINT, // a value of 5 indicates that this describes an endpoint
.bEndpointAddress = 0x82, // IN (to host) direction (1<<7), endpoint 2
.bmAttributes = USB_ENDPOINT_ATTR_BULK, // bulk mode
.wMaxPacketSize = 64, // maximum packet size
.bInterval = 1, // the frequency, in number of frames, that we're going to be sending data
}};
/* This notification endpoint isn't implemented. According to CDC spec its
* optional, but its absence causes a NULL pointer dereference in Linux
* cdc_acm driver.
*/
static const struct usb_endpoint_descriptor communication_endpoints[] = {{
.bLength = USB_DT_ENDPOINT_SIZE, // the size of the endpoint descriptor in bytes
.bDescriptorType = USB_DT_ENDPOINT, // a value of 5 indicates that this describes an endpoint
.bEndpointAddress = 0x83, // IN (to host) direction (1<<7), endpoint 3
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, // interrupt mode
.wMaxPacketSize = 16, // maximum packet size
.bInterval = 255, // the frequency, in number of frames, that we're going to be sending data
}};
static const struct {
struct usb_cdc_header_descriptor header;
struct usb_cdc_call_management_descriptor call_mgmt;
struct usb_cdc_acm_descriptor acm;
struct usb_cdc_union_descriptor cdc_union;
} __attribute__((packed)) cdcacm_functional_descriptors = {
.header = {
.bFunctionLength = sizeof(struct usb_cdc_header_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_HEADER,
.bcdCDC = 0x0110,
},
.call_mgmt = {
.bFunctionLength =
sizeof(struct usb_cdc_call_management_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT,
.bmCapabilities = 0,
.bDataInterface = 1,
},
.acm = {
.bFunctionLength = sizeof(struct usb_cdc_acm_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_ACM,
.bmCapabilities = 0,
},
.cdc_union = {
.bFunctionLength = sizeof(struct usb_cdc_union_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_UNION,
.bControlInterface = 0,
.bSubordinateInterface0 = 1,
},
};
static const struct usb_interface_descriptor communication_interface[] = {{
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_CDC,
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
.bInterfaceProtocol = USB_CDC_PROTOCOL_NONE,
.iInterface = 0,
.endpoint = communication_endpoints,
.extra = &cdcacm_functional_descriptors,
.extralen = sizeof(cdcacm_functional_descriptors),
}};
static const struct usb_interface_descriptor data_interface[] = {{
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 1,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_DATA,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
.endpoint = data_endpoints,
}};
static const struct usb_interface interfaces[] = {{
.num_altsetting = 1,
.altsetting = communication_interface,
}, {
.num_altsetting = 1,
.altsetting = data_interface,
}};
static const struct usb_config_descriptor config = {
.bLength = USB_DT_CONFIGURATION_SIZE, // the length of this header in bytes
.bDescriptorType = USB_DT_CONFIGURATION, // a value of 2 indicates that this is a configuration descriptor
.wTotalLength = 0, // this should hold the total size of the configuration descriptor including all sub interfaces. it is automatically filled in by the USB stack in libopencm3
.bNumInterfaces = 2, // the number of interfaces in this configuration
.bConfigurationValue = 1, // the index of this configuration
.iConfiguration = 0, // a string index describing this configuration (zero means not provided)
.bmAttributes = 0x80, // self powered (0<<6), supports remote wakeup (5<<0)
.bMaxPower = 0x32, // the maximum amount of current that this device will draw in 2mA units
// end of header
.interface = interfaces, // pointer to an array of interfaces
};
/* string table (starting with index 1) */
const char *usb_strings[] = {
"CuVoodoo",
"CDC-ACM",
"STM32F1",
};
/* buffer to be used for control requests */
static uint8_t usbd_control_buffer[128];
/* structure holding all the info related to the USB device */
static usbd_device *usb_device;
/* input and output ring buffer, indexes, and available memory */
static uint8_t rx_buffer[CDCACM_BUFFER] = {0};
static volatile uint8_t rx_i = 0;
static volatile uint8_t rx_used = 0;
/* show the user how much data received over USB is ready */
volatile uint8_t cdcacm_received = 0; // same as rx_used, but since the user can write this variable we don't rely on it
static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req))
{
(void)complete;
(void)buf;
(void)usbd_dev;
switch (req->bRequest) {
case USB_CDC_REQ_SET_CONTROL_LINE_STATE: {
/*
* This Linux cdc_acm driver requires this to be implemented
* even though it's optional in the CDC spec, and we don't
* advertise it in the ACM functional descriptor.
*/
char local_buf[10];
struct usb_cdc_notification *notif = (void *)local_buf;
/* We echo signals back to host as notification. */
notif->bmRequestType = 0xA1;
notif->bNotification = USB_CDC_NOTIFY_SERIAL_STATE;
notif->wValue = 0;
notif->wIndex = 0;
notif->wLength = 2;
local_buf[8] = req->wValue & 3;
local_buf[9] = 0;
// usbd_ep_write_packet(0x83, buf, 10);
return 1;
}
case USB_CDC_REQ_SET_LINE_CODING:
if (*len < sizeof(struct usb_cdc_line_coding))
return 0;
return 1;
}
return 0;
}
static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void)ep;
(void)usbd_dev;
char usb_data[64] = {0}; // buffer to read data
uint16_t usb_length = 0; // length of incoming data
/* receive data */
usb_length = usbd_ep_read_packet(usbd_dev, 0x01, usb_data, sizeof(usb_data));
if (usb_length) { // copy received data
for (uint16_t i=0; i