STM32F4DoorControl/usb.c

344 lines
11 KiB
C

#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/desig.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/usb/cdc.h>
#include <libopencm3/usb/usbd.h>
#include <stddef.h>
#include "usb.h"
#include "ringbuffer.h"
extern ringbuffer *usb_to_uart_buf, *comm_in_buf;
/* Number of serial devices we want to provide. */
#define NUM_SERIAL 2
static const uint8_t EP_COMM = 1 + 2 * ACM_COMM;
static const uint8_t EP_NFC = 1 + 2 * ACM_NFC;
/* CDC-ACM devices need a functional descriptor that looks like this */
struct usb_cdcacm_functional_descriptor {
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));
/* Allocate some space for our serial */
static char serial[24];
/*
These strings will be used as identifiers in some descriptors.
* In descriptors, indices are 1-based, and 0 is an empty string.
*/
static char const *usb_strings[] = {
"Imaginaerraum.de",
"DoorControl",
serial,
[3 + ACM_COMM] = "Comm",
[3 + ACM_NFC] = "RFID",
};
static int got_reset = 0;
usbd_device *g_usbd_dev;
/* The descriptor for our device. We need only one of these. */
static const struct usb_device_descriptor dev = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0200,
/*
* Devices with class 0xEF, subclass 2, and protocol 1 are interface
* associations that can contain multiple unrelated interfaces, like a
* keyboard and a video device, or multiple serial devices
*/
.bDeviceClass = 0xEF,
.bDeviceSubClass = 2,
.bDeviceProtocol = 1,
/* Maximum packet size for endpoint zero */
.bMaxPacketSize0 = 64,
/* Vendor and product ID are for ST's virtual modem */
.idVendor = 0x0483,
.idProduct = 0x5740,
/* Revision of the device: 2.00 */
.bcdDevice = 0x0200,
/* Indices into the string table (starts at 1 - 0 is none) */
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
/* We only have one configuration */
.bNumConfigurations = 1,
};
/* Each CDC-ACM device has... */
/* two data endpoints and one control endpoint */
static struct usb_endpoint_descriptor endpoints[3 * NUM_SERIAL];
/* one functional descriptor */
static struct usb_cdcacm_functional_descriptor
cdcacm_functional_descriptors[NUM_SERIAL];
/* a data interface combining the two data endpoints, and a control interface */
static struct usb_interface_descriptor interface_descriptors[2 * NUM_SERIAL];
/* instantiate the usb_interface struct needed by libopencm3 */
static struct usb_interface interfaces[sizeof(interface_descriptors) / sizeof(interface_descriptors[0])];
/* Create the one configuration we have */
static const struct usb_config_descriptor config = {
.bLength = USB_DT_CONFIGURATION_SIZE,
.bDescriptorType = USB_DT_CONFIGURATION,
.bNumInterfaces =
sizeof(interface_descriptors) / sizeof(interface_descriptors[0]),
/* this is filled in later by libopencm3, and also why we can't just create all of this in ROM */
.wTotalLength = 0,
/* first and only config */
.bConfigurationValue = 1,
/* name of the config (none here) */
.iConfiguration = 0,
/* 0x80 is required to be set */
.bmAttributes = 0x80,
/* multiply by 2 mA to get maximum power we request */
.bMaxPower = 100,
/* pointer to the interfaces declared above */
.interface = interfaces,
};
/* Buffer to be used for control requests. */
uint8_t usbd_control_buffer[512];
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)) {
(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.
*/
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;
}
static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
(void)ep;
char buf[64];
/* Read some data from the endpoint */
int len = usbd_ep_read_packet(usbd_dev, ep, buf, 64);
if (ep == EP_NFC) {
ringbuffer_add(usb_to_uart_buf, (void*)buf, len);
} else if (ep == EP_COMM) {
ringbuffer_add(comm_in_buf, (void*)buf, len);
}
}
int usb_write_cdcacm(uint8_t acm, void *data, size_t len, int tries) {
int i = 0;
while (usbd_ep_write_packet(g_usbd_dev, (1 + 2 * acm) | 0x80, data, len) == 0 && ++i < tries)
;
return i < tries;
}
static void cdcacm_set_config(usbd_device *usbd_dev, uint16_t wValue) {
(void)wValue;
for (int i = 0; i < NUM_SERIAL; i++) {
/* set up all the endpoints for each serial device */
usbd_ep_setup(usbd_dev, 0x01 + 2*i, USB_ENDPOINT_ATTR_BULK, 64,
cdcacm_data_rx_cb);
usbd_ep_setup(usbd_dev, 0x81 + 2*i, USB_ENDPOINT_ATTR_BULK, 64, NULL);
usbd_ep_setup(usbd_dev, 0x81 + 2*i + 1, 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);
}
void usb_reset_callback(void) {
got_reset = 1;
}
bool usb_got_reset(void) {
return got_reset;
}
void usb_setup() {
rcc_periph_clock_enable(RCC_GPIOA);
rcc_periph_clock_enable(RCC_OTGFS);
desig_get_unique_id_as_string(serial, sizeof serial);
gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO11 | GPIO12);
gpio_set_af(GPIOA, GPIO_AF10, GPIO11 | GPIO12);
for (uint8_t i = 0; i < NUM_SERIAL; i++) {
/*
* set up the endpoints Data endpoints are at addresses 1, 3, 5 (or 0x81,
* 0x83, 0x85) and so on.
*
* Control endpoints are at addresses 0x82, 0x84, 0x86...
*/
/* index of the first data endpoint in our array struct */
struct usb_endpoint_descriptor *data_ep = &endpoints[3 * i];
/* Index of the control endpoint */
struct usb_endpoint_descriptor *ctrl_ep = data_ep + 2;
/* Numbers of the data and control endpoints */
const uint8_t data_ep_num = 0x01 + 2*i;
const uint8_t ctrl_ep_num = data_ep_num + 1;
/* Interface numbers */
const uint8_t data_iface = 2*i;
const uint8_t control_iface = data_iface + 1;
/* OUT endpoint for data (host to device) */
data_ep[0] = (struct usb_endpoint_descriptor) {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = (uint8_t)(data_ep_num),
.bmAttributes = USB_ENDPOINT_ATTR_BULK,
.wMaxPacketSize = 64
};
/* IN endpoint (device to host) is the same except for the direction */
data_ep[1] = data_ep[0];
data_ep[1].bEndpointAddress |= 0x80;
/*
* The control endpoint is for interrupt transfers. Its address is one
* higher than the data endpoints, has smaller packets, and we don't really
* care about its interval (because we're not using it)
*/
*ctrl_ep = (struct usb_endpoint_descriptor) {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = (uint8_t)(0x80 + ctrl_ep_num),
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
.wMaxPacketSize = 16,
.bInterval = 255
};
cdcacm_functional_descriptors[i] = (struct usb_cdcacm_functional_descriptor) {
.header = (struct usb_cdc_header_descriptor) {
.bFunctionLength = sizeof (struct usb_cdc_header_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_HEADER,
/* Version of the standard */
.bcdCDC = 0x0110
},
.call_mgmt = (struct usb_cdc_call_management_descriptor) {
.bFunctionLength = sizeof (struct usb_cdc_call_management_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_CALL_MANAGEMENT,
/* We do no call management */
.bmCapabilities = 0,
/* Index of our data interface */
.bDataInterface = data_iface,
},
.acm = (struct usb_cdc_acm_descriptor){
.bFunctionLength = sizeof (struct usb_cdc_acm_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_ACM,
/* Nothing here either */
.bmCapabilities = 0,
},
.cdc_union = (struct usb_cdc_union_descriptor) {
.bFunctionLength = sizeof (struct usb_cdc_union_descriptor),
.bDescriptorType = CS_INTERFACE,
.bDescriptorSubtype = USB_CDC_TYPE_UNION,
/* Our interfaces */
.bControlInterface = control_iface,
.bSubordinateInterface0 = data_iface,
}
};
/* A plain data interface */
interface_descriptors[data_iface] = (struct usb_interface_descriptor) {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = data_iface,
/* This is the first alternate setting. There are no others. */
.bAlternateSetting = 0,
/* OUT and IN */
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_DATA,
/* No subclass or protocol */
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
/*
* A pointer to the first of its endpoints - the others must be
* contiguous.
*/
.endpoint = data_ep
};
/* Required by libopencm3 for some reason. */
interfaces[data_iface] = (struct usb_interface) {
.num_altsetting = 1, .altsetting = &interface_descriptors[data_iface]
};
/*
* The control interface gets the actual CDC ACM classes, and the string
* description. It also needs the CDC ACM functional descriptors.
*/
interface_descriptors[control_iface] = (struct usb_interface_descriptor) {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = control_iface,
.bAlternateSetting = 0,
/* Just one direction here */
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_CDC,
.bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
.bInterfaceProtocol = USB_CDC_PROTOCOL_AT,
/* The first three names are for manufacturer, product, and serial. */
.iInterface = (uint8_t)(4 + i),
/* Pointer to the single endpoint */
.endpoint = ctrl_ep,
/* Pointer and length for the CDC ACM functional descriptors */
.extra = &cdcacm_functional_descriptors[i],
.extralen = sizeof cdcacm_functional_descriptors[0]
};
interfaces[control_iface] = (struct usb_interface){
.num_altsetting = 1, .altsetting = &interface_descriptors[control_iface]};
}
/* Initialize the device... */
usb_reinit();
/* And use interrupts instead of polling. */
// nvic_enable_irq(NVIC_OTG_FS_IRQ);
}
void usb_reinit() {
g_usbd_dev = usbd_init(&otgfs_usb_driver, &dev, &config,
(char const * const *)usb_strings,
(sizeof usb_strings) / sizeof(usb_strings[0]),
usbd_control_buffer, sizeof(usbd_control_buffer));
usbd_register_reset_callback(g_usbd_dev, &usb_reset_callback);
usbd_register_set_config_callback(g_usbd_dev, cdcacm_set_config);
}
//void __attribute__((weak)) otg_fs_isr() { }