#include #include #include #include #include #include #include #include "usb.h" #include "ringbuffer.h" extern ringbuffer *usb_to_uart_buf, *comm_in_buf; static void usb_reset_callback(void); static void usb_suspend_callback(void); /* 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 connected = 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) { connected = 1; } void usb_suspend_callback(void) { connected = 0; } bool usb_connected(void) { return connected; } 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_suspend_callback(g_usbd_dev, &usb_suspend_callback); usbd_register_set_config_callback(g_usbd_dev, cdcacm_set_config); } //void __attribute__((weak)) otg_fs_isr() { }