343 lines
9.9 KiB
C
343 lines
9.9 KiB
C
#include <stdlib.h>
|
|
|
|
#include <libopencm3/cm3/nvic.h>
|
|
#include <libopencm3/usb/usbd.h>
|
|
#include <libopencm3/usb/cdc.h>
|
|
#include <libopencm3/stm32/desig.h>
|
|
|
|
#define UID_LEN (12 * 2 + 1) /* 12-byte, each byte turnned into 2-byte hex, then '\0'. */
|
|
|
|
#define DEV_VID 0x0483 /* ST Microelectronics */
|
|
#define DEV_PID 0x5740 /* STM32 */
|
|
#define DEV_VER 0x0009 /* 0.9 */
|
|
|
|
#define EP_INT 0x83
|
|
#define EP_OUT 0x82
|
|
#define EP_IN 0x01
|
|
|
|
#define STR_MAN 0x01
|
|
#define STR_PROD 0x02
|
|
#define STR_SER 0x03
|
|
#define STR_IFACE 0x04
|
|
|
|
#include "usbcdc.h"
|
|
|
|
static const struct usb_device_descriptor dev = {
|
|
.bLength = USB_DT_DEVICE_SIZE,
|
|
.bDescriptorType = USB_DT_DEVICE,
|
|
.bcdUSB = 0x0200,
|
|
.bDeviceClass = USB_CLASS_CDC,
|
|
.bDeviceSubClass = 0,
|
|
.bDeviceProtocol = 0,
|
|
.bMaxPacketSize0 = USBCDC_PKT_SIZE_DAT,
|
|
.idVendor = DEV_VID,
|
|
.idProduct = DEV_PID,
|
|
.bcdDevice = DEV_VER,
|
|
.iManufacturer = STR_MAN,
|
|
.iProduct = STR_PROD,
|
|
.iSerialNumber = STR_SER,
|
|
.bNumConfigurations = 1,
|
|
};
|
|
|
|
/*
|
|
* 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 comm_endp[] = {{
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = EP_INT,
|
|
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
|
|
.wMaxPacketSize = USBCDC_PKT_SIZE_INT,
|
|
.bInterval = 255,
|
|
}};
|
|
|
|
static const struct usb_endpoint_descriptor data_endp[] = {{
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = EP_IN,
|
|
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
|
|
.wMaxPacketSize = USBCDC_PKT_SIZE_DAT,
|
|
.bInterval = 1,
|
|
}, {
|
|
.bLength = USB_DT_ENDPOINT_SIZE,
|
|
.bDescriptorType = USB_DT_ENDPOINT,
|
|
.bEndpointAddress = EP_OUT,
|
|
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
|
|
.wMaxPacketSize = USBCDC_PKT_SIZE_DAT,
|
|
.bInterval = 1,
|
|
}};
|
|
|
|
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 comm_iface[] = {{
|
|
.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_AT,
|
|
.iInterface = STR_IFACE,
|
|
|
|
.endpoint = comm_endp,
|
|
|
|
.extra = &cdcacm_functional_descriptors,
|
|
.extralen = sizeof(cdcacm_functional_descriptors),
|
|
}};
|
|
|
|
static const struct usb_interface_descriptor data_iface[] = {{
|
|
.bLength = USB_DT_INTERFACE_SIZE,
|
|
.bDescriptorType = USB_DT_INTERFACE,
|
|
.bInterfaceNumber = 1,
|
|
.bAlternateSetting = 0,
|
|
.bNumEndpoints = 2,
|
|
.bInterfaceClass = USB_CLASS_DATA,
|
|
.bInterfaceSubClass = 0,
|
|
.bInterfaceProtocol = 0,
|
|
.iInterface = STR_IFACE,
|
|
|
|
.endpoint = data_endp,
|
|
}};
|
|
|
|
static const struct usb_interface ifaces[] = {{
|
|
.num_altsetting = 1,
|
|
.altsetting = comm_iface,
|
|
}, {
|
|
.num_altsetting = 1,
|
|
.altsetting = data_iface,
|
|
}};
|
|
|
|
static const struct usb_config_descriptor config = {
|
|
.bLength = USB_DT_CONFIGURATION_SIZE,
|
|
.bDescriptorType = USB_DT_CONFIGURATION,
|
|
.wTotalLength = 0,
|
|
.bNumInterfaces = 2,
|
|
.bConfigurationValue = 1,
|
|
.iConfiguration = 0,
|
|
.bmAttributes = 0x80,
|
|
.bMaxPower = 0x32,
|
|
|
|
.interface = ifaces,
|
|
};
|
|
|
|
/* Buffer to be used for control requests. */
|
|
static uint8_t usbd_control_buffer[128];
|
|
|
|
static enum usbd_request_return_codes 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)) {
|
|
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;
|
|
return USBD_REQ_HANDLED;
|
|
}
|
|
case USB_CDC_REQ_SET_LINE_CODING:
|
|
if (*len < sizeof(struct usb_cdc_line_coding))
|
|
return USBD_REQ_NOTSUPP;
|
|
return USBD_REQ_HANDLED;
|
|
}
|
|
return USBD_REQ_NOTSUPP;
|
|
}
|
|
|
|
volatile bool usb_ready = false;
|
|
|
|
static void cdcacm_reset(void) {
|
|
usb_ready = false;
|
|
}
|
|
|
|
static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue) {
|
|
usbd_ep_setup(usbd_dev, EP_IN , USB_ENDPOINT_ATTR_BULK, 64, NULL);
|
|
usbd_ep_setup(usbd_dev, EP_OUT, USB_ENDPOINT_ATTR_BULK, 64, NULL);
|
|
usbd_ep_setup(usbd_dev, EP_INT, USB_ENDPOINT_ATTR_INTERRUPT, 16, NULL);
|
|
|
|
usbd_register_control_callback(
|
|
usbd_dev,
|
|
USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
|
|
USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
|
|
cdcacm_control_request);
|
|
|
|
if (wValue > 0) {
|
|
usb_ready = true;
|
|
}
|
|
}
|
|
|
|
static usbd_device *usbd_dev; /* Just a pointer, need not to be volatile. */
|
|
|
|
static char serial[UID_LEN];
|
|
|
|
/* Vendor, device, version. */
|
|
static const char *usb_strings[] = {
|
|
"dword1511.info",
|
|
"STM32 virtual serprog for flashrom",
|
|
serial,
|
|
"serprog",
|
|
};
|
|
#define N_USB_STRS sizeof(usb_strings)/sizeof(usb_strings[0])
|
|
|
|
void usbcdc_init(void) {
|
|
desig_get_unique_id_as_string(serial, UID_LEN);
|
|
|
|
#ifdef STM32F0
|
|
usbd_dev = usbd_init(&st_usbfs_v2_usb_driver, &dev, &config, usb_strings, N_USB_STRS, usbd_control_buffer, sizeof(usbd_control_buffer));
|
|
#else
|
|
usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config, usb_strings, N_USB_STRS, usbd_control_buffer, sizeof(usbd_control_buffer));
|
|
#endif /* STM32F0 */
|
|
usbd_register_set_config_callback(usbd_dev, cdcacm_set_config);
|
|
usbd_register_reset_callback(usbd_dev, cdcacm_reset);
|
|
|
|
/* NOTE: Must be called after USB setup since this enables calling usbd_poll(). */
|
|
#ifdef STM32F0
|
|
nvic_enable_irq(NVIC_USB_IRQ);
|
|
#else
|
|
/* NVIC_USB_HP_CAN_TX_IRQ */
|
|
nvic_enable_irq(NVIC_USB_LP_CAN_RX0_IRQ);
|
|
nvic_enable_irq(NVIC_USB_WAKEUP_IRQ);
|
|
#endif /* STM32F0 */
|
|
}
|
|
|
|
/* Application-level functions */
|
|
uint16_t usbcdc_write(void *buf, size_t len) {
|
|
uint16_t ret;
|
|
|
|
/* Blocking write */
|
|
while (0 == (ret = usbd_ep_write_packet(usbd_dev, EP_OUT, buf, len)));
|
|
return ret;
|
|
}
|
|
|
|
uint16_t usbcdc_putc(char c) {
|
|
return usbcdc_write(&c, sizeof(c));
|
|
}
|
|
|
|
uint16_t usbcdc_putu32(uint32_t word) {
|
|
//uint32_t l = __builtin_bswap32(word);
|
|
//return usbcdc_write(&l, sizeof(word));
|
|
/* We are using little endian, so no bit swap. */
|
|
return usbcdc_write(&word, sizeof(word));
|
|
}
|
|
|
|
/* We need to maintain a RX user buffer since libopencm3 will throw rest of the packet away. */
|
|
char usbcdc_rxbuf[USBCDC_PKT_SIZE_DAT]; /* DMA needs access */
|
|
static uint8_t usbcdc_rxbuf_head = 0;
|
|
static uint8_t usbcdc_rxbuf_tail = 0; /* Indicates empty buffer */
|
|
|
|
uint16_t usbcdc_fetch_packet(void) {
|
|
uint16_t ret;
|
|
/* Blocking read. Assume RX user buffer is empty. TODO: consider setting a timeout */
|
|
while (0 == (ret = usbd_ep_read_packet(usbd_dev, EP_IN, usbcdc_rxbuf, USBCDC_PKT_SIZE_DAT)));
|
|
usbcdc_rxbuf_head = 0;
|
|
usbcdc_rxbuf_tail = ret;
|
|
return ret;
|
|
}
|
|
|
|
char usbcdc_getc(void) {
|
|
char c;
|
|
|
|
if (usbcdc_rxbuf_head >= usbcdc_rxbuf_tail) {
|
|
usbcdc_fetch_packet();
|
|
}
|
|
|
|
c = usbcdc_rxbuf[usbcdc_rxbuf_head];
|
|
usbcdc_rxbuf_head ++;
|
|
return c;
|
|
}
|
|
|
|
uint32_t usbcdc_getu24(void) {
|
|
uint32_t val = 0;
|
|
|
|
val = (uint32_t)usbcdc_getc() << 0;
|
|
val |= (uint32_t)usbcdc_getc() << 8;
|
|
val |= (uint32_t)usbcdc_getc() << 16;
|
|
|
|
return val;
|
|
}
|
|
|
|
uint32_t usbcdc_getu32(void) {
|
|
uint32_t val = 0;
|
|
|
|
val = (uint32_t)usbcdc_getc() << 0;
|
|
val |= (uint32_t)usbcdc_getc() << 8;
|
|
val |= (uint32_t)usbcdc_getc() << 16;
|
|
val |= (uint32_t)usbcdc_getc() << 24;
|
|
|
|
return val;
|
|
}
|
|
|
|
uint8_t usbcdc_get_remainder(char **bufpp) {
|
|
uint8_t len = usbcdc_rxbuf_tail - usbcdc_rxbuf_head;
|
|
|
|
*bufpp = &(usbcdc_rxbuf[usbcdc_rxbuf_head]);
|
|
usbcdc_rxbuf_head = usbcdc_rxbuf_tail; /* Mark as used. */
|
|
|
|
return len;
|
|
}
|
|
|
|
/* Interrupts */
|
|
|
|
static void usb_int_relay(void) {
|
|
/* Need to pass a parameter... otherwise just alias it directly. */
|
|
usbd_poll(usbd_dev);
|
|
}
|
|
|
|
#ifdef STM32F0
|
|
void usb_isr(void)
|
|
__attribute__ ((alias ("usb_int_relay")));
|
|
#else
|
|
void usb_wakeup_isr(void)
|
|
__attribute__ ((alias ("usb_int_relay")));
|
|
|
|
void usb_hp_can_tx_isr(void)
|
|
__attribute__ ((alias ("usb_int_relay")));
|
|
|
|
void usb_lp_can_rx0_isr(void)
|
|
__attribute__ ((alias ("usb_int_relay")));
|
|
#endif /* STM32F0 */
|