From: Martin Mares Date: Mon, 24 Feb 2020 14:28:38 +0000 (+0100) Subject: Library: Generic DFU boot-loader X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=45da07326dab3a36988c45bf3e9bf6e5a0c5b9e1;p=home-hw.git Library: Generic DFU boot-loader --- diff --git a/USB-IDS b/USB-IDS index 4c4a6ca..5beefcb 100644 --- a/USB-IDS +++ b/USB-IDS @@ -3,5 +3,6 @@ USB IDs used by our gadgets 4242:0001 Experiments with clock 4242:0002 SSR module 4242:0003 BSB gateway +4242:0004 BSB gateway bootloader cafe:cafe KSP Space Alert thermometer cafe:caff KSP Space Alert accelerometer diff --git a/bsb/bootloader/Makefile b/bsb/bootloader/Makefile index 4c9abf3..1f14b90 100644 --- a/bsb/bootloader/Makefile +++ b/bsb/bootloader/Makefile @@ -1,6 +1,6 @@ ROOT=../.. BINARY=bootloader -OBJS=main.o -LIB_OBJS=util-debug.o +OBJS= +LIB_OBJS=util-debug.o dfu-bootloader.o include $(ROOT)/mk/bluepill.mk diff --git a/bsb/bootloader/config.h b/bsb/bootloader/config.h index dcb9ac7..232fd4a 100644 --- a/bsb/bootloader/config.h +++ b/bsb/bootloader/config.h @@ -12,3 +12,13 @@ #define DEBUG_USART USART1 #define DEBUG_LED_BLUEPILL + +// Bootloader settings + +#define BOOTLOADER_DEBUG +#define BOOTLOADER_APP_START 0x08002000 +#define BOOTLOADER_MFG_ID 0x4242 +#define BOOTLOADER_DEV_ID 0x0004 +#define BOOTLOADER_DEV_VERSION 0x0100 +#define BOOTLOADER_MFG_NAME "United Computer Wizards" +#define BOOTLOADER_DEV_NAME "BSB Interface (boot-loader)" diff --git a/bsb/bootloader/main.c b/bsb/bootloader/main.c deleted file mode 100644 index 5071122..0000000 --- a/bsb/bootloader/main.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Bootloader for the BSB Interface - * - * (c) 2020 Martin Mareš - * - * Based on example code from the libopencm3 project, which is - * Copyright (C) 2010 Gareth McMullin - * - * Licensed under the GNU LGPL v3 or any later version. - */ - -#include "util.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#define DEBUG_BOOTLOADER - -#ifdef DEBUG_BOOTLOADER -#define DEBUG(x...) debug_printf(x) -#else -#define DEBUG(x...) do { } while (0) -#endif - -#define APP_ADDRESS 0x08002000 - -byte usbd_control_buffer[1024]; - -static enum dfu_state usbdfu_state = STATE_DFU_IDLE; - -static struct { - byte buf[sizeof(usbd_control_buffer)]; - u16 len; - u32 addr; - u16 blocknum; -} prog; - -static char usb_serial_number[13]; - -enum usb_string { - STR_MANUFACTURER = 1, - STR_DEVICE, - STR_SERIAL, -}; - -static const char *usb_strings[] = { - "United Computer Wizards", - "Boot Loader", - usb_serial_number, -}; - -const struct usb_device_descriptor dev = { - .bLength = USB_DT_DEVICE_SIZE, - .bDescriptorType = USB_DT_DEVICE, - .bcdUSB = 0x0200, - .bDeviceClass = 0, - .bDeviceSubClass = 0, - .bDeviceProtocol = 0, - .bMaxPacketSize0 = 64, - .idVendor = 0x4242, - .idProduct = 0xdead, - .bcdDevice = 0x0100, - .iManufacturer = STR_MANUFACTURER, - .iProduct = STR_DEVICE, - .iSerialNumber = STR_SERIAL, - .bNumConfigurations = 1, -}; - -const struct usb_dfu_descriptor dfu_function = { - .bLength = sizeof(struct usb_dfu_descriptor), - .bDescriptorType = DFU_FUNCTIONAL, - .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH, - .wDetachTimeout = 255, - .wTransferSize = 1024, - .bcdDFUVersion = 0x0100, -}; - -const struct usb_interface_descriptor iface = { - .bLength = USB_DT_INTERFACE_SIZE, - .bDescriptorType = USB_DT_INTERFACE, - .bInterfaceNumber = 0, - .bAlternateSetting = 0, - .bNumEndpoints = 0, - .bInterfaceClass = 0xFE, /* Device Firmware Upgrade */ - .bInterfaceSubClass = 1, - .bInterfaceProtocol = 2, - .extra = &dfu_function, - .extralen = sizeof(dfu_function), -}; - -const struct usb_interface ifaces[] = {{ - .num_altsetting = 1, - .altsetting = &iface, -}}; - -const struct usb_config_descriptor config = { - .bLength = USB_DT_CONFIGURATION_SIZE, - .bDescriptorType = USB_DT_CONFIGURATION, - .wTotalLength = 0, - .bNumInterfaces = 1, - .bConfigurationValue = 1, - .iConfiguration = 0, - .bmAttributes = USB_CONFIG_ATTR_DEFAULT, // bus-powered - .bMaxPower = 50, // multiplied by 2 mA - .interface = ifaces, -}; - -static byte usbdfu_getstatus(usbd_device *usbd_dev UNUSED, u32 *bwPollTimeout) -{ - switch (usbdfu_state) { - case STATE_DFU_DNLOAD_SYNC: - usbdfu_state = STATE_DFU_DNBUSY; - *bwPollTimeout = 100; - return DFU_STATUS_OK; - case STATE_DFU_MANIFEST_SYNC: - /* Device will reset when read is complete. */ - usbdfu_state = STATE_DFU_MANIFEST; - return DFU_STATUS_OK; - default: - return DFU_STATUS_OK; - } -} - -static void usbdfu_getstatus_complete(usbd_device *usbd_dev UNUSED, struct usb_setup_data *req UNUSED) -{ - switch (usbdfu_state) { - case STATE_DFU_DNBUSY: { - flash_unlock(); - u32 baseaddr = APP_ADDRESS + prog.blocknum * dfu_function.wTransferSize; - DEBUG("DFU: Block %u -> %08x\n", prog.blocknum, (uint) baseaddr); - flash_erase_page(baseaddr); - for (uint i = 0; i < prog.len; i += 2) { - u16 *dat = (u16 *)(prog.buf + i); - flash_program_half_word(baseaddr + i, *dat); - } - flash_lock(); - usbdfu_state = STATE_DFU_DNLOAD_IDLE; - return; - } - case STATE_DFU_MANIFEST: - /* USB device must detach, we just reset... */ - scb_reset_system(); - return; /* Will never return. */ - default: - return; - } -} - -static enum usbd_request_return_codes usbdfu_control_request(usbd_device *usbd_dev, - struct usb_setup_data *req, - byte **buf, - u16 *len, - void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)) -{ - if ((req->bmRequestType & 0x7F) != 0x21) - return USBD_REQ_NOTSUPP; /* Only accept class request. */ - - DEBUG("DFU: req %02x in state %d\n", req->bRequest, usbdfu_state); - - switch (req->bRequest) { - case DFU_DNLOAD: - if (len == NULL || *len == 0) { - usbdfu_state = STATE_DFU_MANIFEST_SYNC; - } else { - /* Copy download data for use on GET_STATUS. */ - prog.blocknum = req->wValue; - prog.len = *len; - memcpy(prog.buf, *buf, *len); - usbdfu_state = STATE_DFU_DNLOAD_SYNC; - } - return USBD_REQ_HANDLED; - case DFU_CLRSTATUS: - /* Clear error and return to dfuIDLE. */ - if (usbdfu_state == STATE_DFU_ERROR) - usbdfu_state = STATE_DFU_IDLE; - return USBD_REQ_HANDLED; - case DFU_ABORT: - /* Abort returns to dfuIDLE state. */ - usbdfu_state = STATE_DFU_IDLE; - return USBD_REQ_HANDLED; - case DFU_UPLOAD: - /* Upload not supported for now. */ - return USBD_REQ_NOTSUPP; - case DFU_GETSTATUS: { - u32 bwPollTimeout = 0; /* 24-bit number of milliseconds */ - (*buf)[0] = usbdfu_getstatus(usbd_dev, &bwPollTimeout); - (*buf)[1] = bwPollTimeout & 0xFF; - (*buf)[2] = (bwPollTimeout >> 8) & 0xFF; - (*buf)[3] = (bwPollTimeout >> 16) & 0xFF; - (*buf)[4] = usbdfu_state; - (*buf)[5] = 0; /* iString not used here */ - *len = 6; - *complete = usbdfu_getstatus_complete; - return USBD_REQ_HANDLED; - } - case DFU_GETSTATE: - /* Return state with no state transition. */ - *buf[0] = usbdfu_state; - *len = 1; - return USBD_REQ_HANDLED; - } - - return USBD_REQ_NOTSUPP; -} - -static void usbdfu_set_config(usbd_device *usbd_dev, u16 wValue UNUSED) -{ - usbd_register_control_callback( - usbd_dev, - USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, - USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, - usbdfu_control_request); -} - -static void usbdfu_reset(void) -{ - usbdfu_state = STATE_DFU_IDLE; -} - -int main(void) -{ - usbd_device *usbd_dev; - - // Flash programming requires running on the internal oscillator - rcc_clock_setup_in_hsi_out_48mhz(); - - rcc_periph_clock_enable(RCC_GPIOA); - rcc_periph_clock_enable(RCC_GPIOC); - rcc_periph_clock_enable(RCC_USB); - - rcc_periph_reset_pulse(RST_GPIOA); - rcc_periph_reset_pulse(RST_GPIOC); - rcc_periph_reset_pulse(RST_USB); - -#ifdef DEBUG_BOOTLOADER - rcc_periph_clock_enable(RCC_USART1); - rcc_periph_reset_pulse(RST_USART1); - gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9); - - usart_set_baudrate(USART1, 115200); - usart_set_databits(USART1, 8); - usart_set_stopbits(USART1, USART_STOPBITS_1); - usart_set_mode(USART1, USART_MODE_TX); - usart_set_parity(USART1, USART_PARITY_NONE); - usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE); - - usart_enable(USART1); -#endif - - // BluePill LED - gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13); - debug_led(1); - - // Systick: set to overflow in 1 ms, will use only the overflow flag, no interrupts - systick_set_frequency(1000, CPU_CLOCK_MHZ * 1000000); - systick_clear(); - systick_counter_enable(); - - desig_get_unique_id_as_dfu(usb_serial_number); - - DEBUG("Entered boot-loader\n"); - - // Simulate USB disconnect - gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12); - gpio_clear(GPIOA, GPIO11 | GPIO12); - for (uint i=0; i<1000; i++) { - while (!systick_get_countflag()) - ; - } - - DEBUG("DFU: Ready\n"); - debug_led(0); - - usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config, usb_strings, ARRAY_SIZE(usb_strings), usbd_control_buffer, sizeof(usbd_control_buffer)); - usbd_register_reset_callback(usbd_dev, usbdfu_reset); - usbd_register_set_config_callback(usbd_dev, usbdfu_set_config); - -restart: ; - - uint timeout = 5000; - while (timeout) { - usbd_poll(usbd_dev); - if (systick_get_countflag()) { - timeout--; - if (!(timeout & 0x3f)) - debug_led_toggle(); - } - } - - volatile u32 *app = (volatile u32 *) APP_ADDRESS; - u32 sp = app[0]; - u32 pc = app[1]; - - DEBUG("DFU: Starting application (sp=%08x, pc=%08x)\n", (uint) sp, (uint) pc); - if (sp & 0x2ffe0000 != 0x20000000) { - DEBUG("DFU: Suspicious SP, refusing to start\n"); - goto restart; - } - - while (!usart_get_flag(DEBUG_USART, USART_FLAG_TC)) - ; - debug_led(0); - cm_disable_interrupts(); - - /* Set vector table base address. */ - SCB_VTOR = APP_ADDRESS & 0xFFFF; - - /* Initialize master stack pointer. */ - asm volatile("msr msp, %0"::"g" (sp)); - - /* Jump to application. */ - ((void (*)(void)) pc)(); -} diff --git a/lib/dfu-bootloader.c b/lib/dfu-bootloader.c new file mode 100644 index 0000000..4f792ce --- /dev/null +++ b/lib/dfu-bootloader.c @@ -0,0 +1,325 @@ +/* + * Generic DFU Bootloader + * + * (c) 2020 Martin Mareš + * + * Based on example code from the libopencm3 project, which is + * Copyright (C) 2010 Gareth McMullin + * + * Licensed under the GNU LGPL v3 or any later version. + */ + +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef BOOTLOADER_DEBUG +#define DEBUG(x...) debug_printf(x) +#else +#define DEBUG(x...) do { } while (0) +#endif + +byte usbd_control_buffer[1024]; + +static enum dfu_state usbdfu_state = STATE_DFU_IDLE; + +static struct { + byte buf[sizeof(usbd_control_buffer)]; + u16 len; + u32 addr; + u16 blocknum; +} prog; + +static char usb_serial_number[13]; + +enum usb_string { + STR_MANUFACTURER = 1, + STR_DEVICE, + STR_SERIAL, +}; + +static const char *usb_strings[] = { + BOOTLOADER_MFG_NAME, + BOOTLOADER_DEV_NAME, + usb_serial_number, +}; + +const struct usb_device_descriptor dev = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = BOOTLOADER_MFG_ID, + .idProduct = BOOTLOADER_DEV_ID, + .bcdDevice = BOOTLOADER_DEV_VERSION, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_DEVICE, + .iSerialNumber = STR_SERIAL, + .bNumConfigurations = 1, +}; + +const struct usb_dfu_descriptor dfu_function = { + .bLength = sizeof(struct usb_dfu_descriptor), + .bDescriptorType = DFU_FUNCTIONAL, + .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH, + .wDetachTimeout = 255, + .wTransferSize = 1024, + .bcdDFUVersion = 0x0100, +}; + +const struct usb_interface_descriptor iface = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = 0xFE, /* Device Firmware Upgrade */ + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 2, + .extra = &dfu_function, + .extralen = sizeof(dfu_function), +}; + +const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = &iface, +}}; + +const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = USB_CONFIG_ATTR_DEFAULT, // bus-powered + .bMaxPower = 50, // multiplied by 2 mA + .interface = ifaces, +}; + +static byte usbdfu_getstatus(usbd_device *usbd_dev UNUSED, u32 *bwPollTimeout) +{ + switch (usbdfu_state) { + case STATE_DFU_DNLOAD_SYNC: + usbdfu_state = STATE_DFU_DNBUSY; + *bwPollTimeout = 100; + return DFU_STATUS_OK; + case STATE_DFU_MANIFEST_SYNC: + /* Device will reset when read is complete. */ + usbdfu_state = STATE_DFU_MANIFEST; + return DFU_STATUS_OK; + default: + return DFU_STATUS_OK; + } +} + +static void usbdfu_getstatus_complete(usbd_device *usbd_dev UNUSED, struct usb_setup_data *req UNUSED) +{ + switch (usbdfu_state) { + case STATE_DFU_DNBUSY: { + flash_unlock(); + u32 baseaddr = BOOTLOADER_APP_START + prog.blocknum * dfu_function.wTransferSize; + DEBUG("DFU: Block %u -> %08x\n", prog.blocknum, (uint) baseaddr); + flash_erase_page(baseaddr); + for (uint i = 0; i < prog.len; i += 2) { + u16 *dat = (u16 *)(prog.buf + i); + flash_program_half_word(baseaddr + i, *dat); + } + flash_lock(); + usbdfu_state = STATE_DFU_DNLOAD_IDLE; + return; + } + case STATE_DFU_MANIFEST: + /* USB device must detach, we just reset... */ + scb_reset_system(); + return; /* Will never return. */ + default: + return; + } +} + +static enum usbd_request_return_codes usbdfu_control_request(usbd_device *usbd_dev, + struct usb_setup_data *req, + byte **buf, + u16 *len, + void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)) +{ + if ((req->bmRequestType & 0x7F) != 0x21) + return USBD_REQ_NOTSUPP; /* Only accept class request. */ + + DEBUG("DFU: req %02x in state %d\n", req->bRequest, usbdfu_state); + + switch (req->bRequest) { + case DFU_DNLOAD: + if (len == NULL || *len == 0) { + usbdfu_state = STATE_DFU_MANIFEST_SYNC; + } else { + /* Copy download data for use on GET_STATUS. */ + prog.blocknum = req->wValue; + prog.len = *len; + memcpy(prog.buf, *buf, *len); + usbdfu_state = STATE_DFU_DNLOAD_SYNC; + } + return USBD_REQ_HANDLED; + case DFU_CLRSTATUS: + /* Clear error and return to dfuIDLE. */ + if (usbdfu_state == STATE_DFU_ERROR) + usbdfu_state = STATE_DFU_IDLE; + return USBD_REQ_HANDLED; + case DFU_ABORT: + /* Abort returns to dfuIDLE state. */ + usbdfu_state = STATE_DFU_IDLE; + return USBD_REQ_HANDLED; + case DFU_UPLOAD: + /* Upload not supported for now. */ + return USBD_REQ_NOTSUPP; + case DFU_GETSTATUS: { + u32 bwPollTimeout = 0; /* 24-bit number of milliseconds */ + (*buf)[0] = usbdfu_getstatus(usbd_dev, &bwPollTimeout); + (*buf)[1] = bwPollTimeout & 0xFF; + (*buf)[2] = (bwPollTimeout >> 8) & 0xFF; + (*buf)[3] = (bwPollTimeout >> 16) & 0xFF; + (*buf)[4] = usbdfu_state; + (*buf)[5] = 0; /* iString not used here */ + *len = 6; + *complete = usbdfu_getstatus_complete; + return USBD_REQ_HANDLED; + } + case DFU_GETSTATE: + /* Return state with no state transition. */ + *buf[0] = usbdfu_state; + *len = 1; + return USBD_REQ_HANDLED; + } + + return USBD_REQ_NOTSUPP; +} + +static void usbdfu_set_config(usbd_device *usbd_dev, u16 wValue UNUSED) +{ + usbd_register_control_callback( + usbd_dev, + USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + usbdfu_control_request); +} + +static void usbdfu_reset(void) +{ + usbdfu_state = STATE_DFU_IDLE; +} + +int main(void) +{ + usbd_device *usbd_dev; + + // Flash programming requires running on the internal oscillator + rcc_clock_setup_in_hsi_out_48mhz(); + + rcc_periph_clock_enable(RCC_GPIOA); + rcc_periph_clock_enable(RCC_GPIOC); + rcc_periph_clock_enable(RCC_USB); + + rcc_periph_reset_pulse(RST_GPIOA); + rcc_periph_reset_pulse(RST_GPIOC); + rcc_periph_reset_pulse(RST_USB); + +#ifdef DEBUG_USART + // Currently, only USART1 is supported + rcc_periph_clock_enable(RCC_USART1); + rcc_periph_reset_pulse(RST_USART1); + gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9); + + usart_set_baudrate(USART1, 115200); + usart_set_databits(USART1, 8); + usart_set_stopbits(USART1, USART_STOPBITS_1); + usart_set_mode(USART1, USART_MODE_TX); + usart_set_parity(USART1, USART_PARITY_NONE); + usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE); + + usart_enable(USART1); +#endif + +#ifdef DEBUG_LED_BLUEPILL + // BluePill LED + gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13); + debug_led(1); +#endif + + // Systick: set to overflow in 1 ms, will use only the overflow flag, no interrupts + systick_set_frequency(1000, CPU_CLOCK_MHZ * 1000000); + systick_clear(); + systick_counter_enable(); + + desig_get_unique_id_as_dfu(usb_serial_number); + + DEBUG("DFU: Entered boot-loader (SN %s)\n", usb_serial_number); + + // Simulate USB disconnect + gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO11 | GPIO12); + gpio_clear(GPIOA, GPIO11 | GPIO12); + for (uint i=0; i<1000; i++) { + while (!systick_get_countflag()) + ; + } + + DEBUG("DFU: Ready\n"); + debug_led(0); + + usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config, usb_strings, ARRAY_SIZE(usb_strings), usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_reset_callback(usbd_dev, usbdfu_reset); + usbd_register_set_config_callback(usbd_dev, usbdfu_set_config); + +restart: ; + + uint timeout = 5000; + while (timeout) { + usbd_poll(usbd_dev); + if (systick_get_countflag()) { + timeout--; + if (!(timeout & 0x3f)) + debug_led_toggle(); + } + } + + volatile u32 *app = (volatile u32 *) BOOTLOADER_APP_START; + u32 sp = app[0]; + u32 pc = app[1]; + + DEBUG("DFU: Starting application (sp=%08x, pc=%08x)\n", (uint) sp, (uint) pc); + if (sp & 0x2ffe0000 != 0x20000000) { + DEBUG("DFU: Suspicious SP, refusing to start\n"); + goto restart; + } + +#ifdef DEBUG_USART + while (!usart_get_flag(DEBUG_USART, USART_FLAG_TC)) + ; +#endif + debug_led(0); + cm_disable_interrupts(); + + /* Set vector table base address. */ + SCB_VTOR = BOOTLOADER_APP_START & 0xFFFF; + + /* Initialize master stack pointer. */ + asm volatile("msr msp, %0"::"g" (sp)); + + /* Jump to application. */ + ((void (*)(void)) pc)(); +}