From b26a10e085d192f1efd841c21cbae448141632be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Sat, 15 Apr 2017 13:57:45 +0200 Subject: [PATCH] add DFU runtime to USB CDC ACM profile --- lib/usb_cdcacm.c | 351 +++++++++++++++++++++++++++++------------------ lib/usb_cdcacm.h | 8 +- 2 files changed, 218 insertions(+), 141 deletions(-) diff --git a/lib/usb_cdcacm.c b/lib/usb_cdcacm.c index 2dd1bc6..9c79afa 100644 --- a/lib/usb_cdcacm.c +++ b/lib/usb_cdcacm.c @@ -15,7 +15,7 @@ /** library for USB CDC ACM communication (code) * @file usb_cdcacm.c * @author King Kévin - * @date 2016 + * @date 2016-2017 */ /* standard libraries */ @@ -32,59 +32,63 @@ #include // USB library #include // USB CDC library #include // synchronisation utilities +#include // DFU definitions #include "global.h" // global utilities #include "usb_cdcacm.h" // USB CDC ACM header and definitions +static uint8_t usbd_control_buffer[128] = {0}; /**< buffer to be used for control requests */ +static usbd_device *usb_device = NULL; /**< structure holding all the info related to the USB device */ + /** USB CDC ACM device descriptor * @note as defined in USB CDC specification section 5 */ -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 = 0, // unused - .bDeviceProtocol = 0, // unused - .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_device_descriptor usb_cdcacm_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 = 0, /**< unused */ + .bDeviceProtocol = 0, /**< unused */ + .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 */ }; /** USB CDC ACM data endpoints * @note as defined in USB CDC specification section 5 */ -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 +static const struct usb_endpoint_descriptor usb_cdcacm_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 = 0x02, /**< OUT (from host) direction (0<<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 */ },{ - .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 + .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 */ }}; /** USB CDC ACM communication endpoints * @note 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 usb_endpoint_descriptor usb_cdcacm_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 = 0x81, /**< IN (to host) direction (1<<7), endpoint 1 */ + .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 */ }}; /** USB CDC ACM functional descriptor @@ -96,7 +100,7 @@ static const struct { struct usb_cdc_call_management_descriptor call_mgmt; /**< call management descriptor */ struct usb_cdc_acm_descriptor acm; /**< descriptor */ struct usb_cdc_union_descriptor cdc_union; /**< descriptor */ -} __attribute__((packed)) cdcacm_functional_descriptors = { +} __attribute__((packed)) usb_cdcacm_functional_descriptors = { .header = { .bFunctionLength = sizeof(struct usb_cdc_header_descriptor), /**< descriptor length */ .bDescriptorType = CS_INTERFACE, /**< descriptor type */ @@ -128,7 +132,7 @@ static const struct { /** USB CDC interface descriptor * @note as defined in USB CDC specification section 5.1.3 */ -static const struct usb_interface_descriptor communication_interface[] = {{ +static const struct usb_interface_descriptor usb_cdcacm_communication_interface = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, @@ -139,16 +143,16 @@ static const struct usb_interface_descriptor communication_interface[] = {{ .bInterfaceProtocol = USB_CDC_PROTOCOL_NONE, .iInterface = 0, - .endpoint = communication_endpoints, + .endpoint = usb_cdcacm_communication_endpoints, - .extra = &cdcacm_functional_descriptors, - .extralen = sizeof(cdcacm_functional_descriptors), -}}; + .extra = &usb_cdcacm_functional_descriptors, + .extralen = sizeof(usb_cdcacm_functional_descriptors), +}; /** USB CDC ACM data class interface descriptor * @note as defined in USB CDC specification section 5.1.3 */ -static const struct usb_interface_descriptor data_interface[] = {{ +static const struct usb_interface_descriptor usb_cdcacm_data_interface = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 1, @@ -159,30 +163,62 @@ static const struct usb_interface_descriptor data_interface[] = {{ .bInterfaceProtocol = 0, .iInterface = 0, - .endpoint = data_endpoints, -}}; + .endpoint = usb_cdcacm_data_endpoints, +}; + +/** USB DFU functional descriptor + * @note as defined in USB Device Firmware Upgrade specification section 4.2.4 + */ +static const struct usb_dfu_descriptor usb_dfu_functional = { + .bLength = sizeof(struct usb_dfu_descriptor), /**< provide own size */ + .bDescriptorType = DFU_FUNCTIONAL, /**< functional descriptor type */ + .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH, /**< this DFU can download and will detach after download */ + .wDetachTimeout = 200, /**< maximum time (in milliseconds) needed to detach (else resume normal operation) */ + .wTransferSize = sizeof(usbd_control_buffer), /**< set max transfer size */ + .bcdDFUVersion = 0x0110, /**< DFU specification version 1.1 used */ +}; + +/** USB DFU interface descriptor + * @note as defined in USB Device Firmware Upgrade specification section 4.2.3 + */ +static const struct usb_interface_descriptor usb_dfu_interface = { + .bLength = USB_DT_INTERFACE_SIZE, /**< size of descriptor in byte */ + .bDescriptorType = USB_DT_INTERFACE, /**< interface descriptor type */ + .bInterfaceNumber = 2, /**< interface number in the list */ + .bAlternateSetting = 0, /**< no alternative settings */ + .bNumEndpoints = 0, /**< only the control pipe at endpoint 0 is used */ + .bInterfaceClass = 0xFE, /**< DFU interface class (not defined in libopencm3 dfu lib) */ + .bInterfaceSubClass = 1, /**< DFU interface subclass (not defined in libopencm3 dfu lib) */ + .bInterfaceProtocol = 1, /**< runtime protocol (not defined in libopencm3 dfu lib) */ + .iInterface = 4, /**< the index of the string in the string table that represents interface description */ + .extra = &usb_dfu_functional, /**< point to functional descriptor */ + .extralen = sizeof(usb_dfu_functional), /**< size of functional descriptor */ +}; /** USB CDC ACM interface descriptor */ -static const struct usb_interface interfaces[] = {{ +static const struct usb_interface usb_cdcacm_interfaces[] = {{ .num_altsetting = 1, - .altsetting = communication_interface, + .altsetting = &usb_cdcacm_communication_interface, }, { .num_altsetting = 1, - .altsetting = data_interface, + .altsetting = &usb_cdcacm_data_interface, +}, { + .num_altsetting = 1, + .altsetting = &usb_dfu_interface, }}; /** USB CDC ACM configuration descriptor */ -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 (0<<5) - .bMaxPower = 0x32, // the maximum amount of current that this device will draw in 2mA units +static const struct usb_config_descriptor usb_cdcacm_configuration_descriptor = { + .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 = LENGTH(usb_cdcacm_interfaces), /**< 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, /**< bus powered (1<<7) */ + .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 + .interface = usb_cdcacm_interfaces, /**< pointer to an array of interfaces */ }; /** USB string table @@ -190,14 +226,11 @@ static const struct usb_config_descriptor config = { */ static const char *usb_strings[] = { "CuVoodoo", - "CDC-ACM", "STM32F1", + "CDC-ACM", + "CuVoodoo DFU bootloader (runtime mode)", }; -static uint8_t usbd_control_buffer[128] = {0}; /**< buffer to be used for control requests */ -static usbd_device *usb_device = NULL; /**< structure holding all the info related to the USB device */ -static bool connected = false; /**< is the USB device is connected to a host */ - /* input and output ring buffer, indexes, and available memory */ static uint8_t rx_buffer[CDCACM_BUFFER] = {0}; /**< ring buffer for received data */ static volatile uint8_t rx_i = 0; /**< current position of read received data */ @@ -207,21 +240,13 @@ static uint8_t tx_buffer[CDCACM_BUFFER] = {0}; /**< ring buffer for data to tran static volatile uint8_t tx_i = 0; /**< current position if transmitted data */ static volatile uint8_t tx_used = 0; /**< how much data needs to be transmitted */ mutex_t tx_lock = MUTEX_UNLOCKED; /**< lock to update tx_i or tx_used */ -volatile uint8_t cdcacm_received = 0; // same as rx_used, but since the user can write this variable we don't rely on it +volatile uint8_t usb_cdcacm_received = 0; // same as rx_used, but since the user can write this variable we don't rely on it +static bool connected = false; /**< is the USB device is connected to a host */ /** disconnect USB by pulling down D+ to for re-enumerate */ static void usb_disconnect(void) { - /* short USB disconnect to force re-enumerate */ -#if defined(SYSTEM_BOARD) || defined(BLUE_PILL) - // pull USB D+ low for a short while - rcc_periph_clock_enable(RCC_GPIOA); - gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO12); - gpio_clear(GPIOA, GPIO12); - for (uint32_t i = 0; i < 0x2000; i++) { - __asm__("nop"); - } -#elif defined(MAPLE_MINI) +#if defined(MAPLE_MINI) // disconnect USB D+ using dedicated DISC line/circuit on PB9 rcc_periph_clock_enable(RCC_GPIOB); gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO9); @@ -230,64 +255,115 @@ static void usb_disconnect(void) __asm__("nop"); } gpio_clear(GPIOB, GPIO9); +#else + // pull USB D+ low for a short while + rcc_periph_clock_enable(RCC_GPIOA); + gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO12); + gpio_clear(GPIOA, GPIO12); + for (uint32_t i = 0; i < 0x2000; i++) { + __asm__("nop"); + } #endif } +/** disconnect USB and perform system reset + * @param[in] usbd_dev USB device (unused) + * @param[in] req USB request (unused) + */ +static void usb_reset(usbd_device *usbd_dev, struct usb_setup_data *req) +{ + (void)usbd_dev; // variable not used + (void)req; // variable not used + usb_disconnect(); // USB detach (disconnect to force re-enumeration) + scb_reset_system(); // reset device + while (true); // wait for the reset to happen +} + +/** DFU detach (disconnect USB and perform core reset) + * @param[in] usbd_dev USB device (unused) + * @param[in] req USB request (unused) + */ +static void usb_dfu_detach(usbd_device *usbd_dev, struct usb_setup_data *req) +{ + (void)usbd_dev; // variable not used + (void)req; // variable not used + RCC_CSR |= RCC_CSR_RMVF; // clear reset flag for the bootloader to detect the core reset + usb_disconnect(); // USB detach (disconnect to force re-enumeration) + scb_reset_core(); // reset device (only the core, to the peripheral stay configured) + while (true); // wait for the reset to happen +} + /** incoming USB CDC ACM control request * @param[in] usbd_dev USB device descriptor * @param[in] req control request information * @param[in] buf control request data * @param[in] len control request data length - * @param[in] complete not used + * @param[in] complete function to run after request completed * @return 0 if succeeded, error else * @note resets device when configured with 5 bits */ -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)) +static int usb_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: - connected = req->wValue ? true : false; // check if terminal is open - //bool dtr = (req->wValue & (1 << 0)) ? true : false; - //bool rts = (req->wValue & (1 << 1)) ? true : false; - /* 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. - */ - uint8_t reply[10] = {0}; - struct usb_cdc_notification *notif = (void *)reply; - /* 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; - reply[8] = req->wValue & 3; - reply[9] = 0; - usbd_ep_write_packet(usbd_dev, 0x83, reply, LENGTH(reply)); - break; - case USB_CDC_REQ_SET_LINE_CODING: - // ignore if length is wrong - if (*len < sizeof(struct usb_cdc_line_coding)) { + if (usb_dfu_interface.bInterfaceNumber==req->wIndex) { // check if request is for DFU + switch (req->bRequest) { + case DFU_DETACH: // USB detach requested + *complete = usb_dfu_detach; // detach after reply + break; + case DFU_GETSTATUS: // get status + (*buf)[0] = DFU_STATUS_OK;; // set OK status + (*buf)[1] = 0; // set null poll timeout + (*buf)[2] = 0; // set null poll timeout + (*buf)[3] = 0; // set null poll timeout + (*buf)[4] = STATE_APP_IDLE; // application is running + (*buf)[5] = 0; // string not used + *len = 6; // set length of buffer to return + break; + default: // other requests are not supported return 0; - } - // get the line coding - struct usb_cdc_line_coding *coding = (struct usb_cdc_line_coding *)*buf; - /* reset device is the data bits is set to 5 - * this is used to allowing rebooting the device in DFU mode for reflashing - * to reset the device from the host you can use stty --file /dev/ttyACM0 115200 raw cs5 - */ - if (coding->bDataBits==5) { - usb_disconnect(); // force re-enumerate after reset - scb_reset_system(); // reset device - while (true); // wait for the reset to happen - } - break; - default: - return 0; + } + } else { + switch (req->bRequest) { + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: + connected = req->wValue ? true : false; // check if terminal is open + //bool dtr = (req->wValue & (1 << 0)) ? true : false; + //bool rts = (req->wValue & (1 << 1)) ? true : false; + /* 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. + */ + uint8_t reply[10] = {0}; + struct usb_cdc_notification *notif = (void *)reply; + /* 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; + reply[8] = req->wValue & 3; + reply[9] = 0; + usbd_ep_write_packet(usbd_dev, 0x81, reply, LENGTH(reply)); + break; + case USB_CDC_REQ_SET_LINE_CODING: + // ignore if length is wrong + if (*len < sizeof(struct usb_cdc_line_coding)) { + return 0; + } + // get the line coding + struct usb_cdc_line_coding *coding = (struct usb_cdc_line_coding *)*buf; + /* reset device is the data bits is set to 5 + * to reset the device from the host you can use stty --file /dev/ttyACM0 raw cs5 + */ + if (coding->bDataBits==5) { + *complete = usb_reset; // perform reset after reply + } + break; + default: + return 0; + } } return 1; } @@ -296,7 +372,7 @@ static int cdcacm_control_request(usbd_device *usbd_dev, struct usb_setup_data * * @param[in] usbd_dev USB device descriptor * @param[in] ep endpoint where data came in */ -static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) +static void usb_cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) { (void)ep; @@ -304,13 +380,13 @@ static void cdcacm_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) 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)); + usb_length = usbd_ep_read_packet(usbd_dev, 0x02, usb_data, sizeof(usb_data)); if (usb_length) { // copy received data for (uint16_t i=0; i