]> mj.ucw.cz Git - home-hw.git/blobdiff - lib/dfu-bootloader.c
README
[home-hw.git] / lib / dfu-bootloader.c
index ce01a41c5173872c6c4dd45ec738a6e360fd8caa..3df8351aad66e50098d281f6f519eea1cc0a3737 100644 (file)
@@ -16,6 +16,7 @@
 #include <libopencm3/cm3/scb.h>
 #include <libopencm3/cm3/systick.h>
 #include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/crc.h>
 #include <libopencm3/stm32/gpio.h>
 #include <libopencm3/stm32/flash.h>
 #include <libopencm3/stm32/usart.h>
 #define DEBUG(x...) do { } while (0)
 #endif
 
-byte usbd_control_buffer[1024];
+// Offsets to firmware header fields (see tools/dfu-sign.c)
+#define HDR_LENGTH 0x1c
+#define HDR_FLASH_IN_PROGRESS 0x20
 
-static enum dfu_state usbdfu_state = STATE_DFU_IDLE;
+// DFU blocks should be equal to erase blocks of the flash
+#define BLOCK_SIZE 1024
+byte usbd_control_buffer[BLOCK_SIZE];
 
-struct prog_info {
+static enum dfu_state dfu_state = STATE_DFU_IDLE;
+static uint timeout;
+#define DEFAULT_TIMEOUT 5000   // ms
+#define DFU_TIMEOUT 2000
+
+static struct {
        byte buf[sizeof(usbd_control_buffer)];
        u16 blocknum;
        u16 len;
-};
-
-static struct prog_info prog_page;
-
-// The first page of application code is programmed last,
-// so that we are able to detect incompletely uploaded firmware.
-static struct prog_info prog_header;
+} prog;
 
 static char usb_serial_number[13];
 
@@ -83,7 +87,7 @@ const struct usb_dfu_descriptor dfu_function = {
        .bDescriptorType = DFU_FUNCTIONAL,
        .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH,
        .wDetachTimeout = 255,
-       .wTransferSize = 1024,
+       .wTransferSize = BLOCK_SIZE,
        .bcdDFUVersion = 0x0100,
 };
 
@@ -117,62 +121,92 @@ const struct usb_config_descriptor config = {
        .interface = ifaces,
 };
 
-static byte usbdfu_getstatus(usbd_device *usbd_dev UNUSED, u32 *bwPollTimeout)
+static inline u32 get_u32(u32 addr)
+{
+       return *(u32*)addr;
+}
+
+static inline u16 get_u16(u32 addr)
+{
+       return *(u16*)addr;
+}
+
+static bool verify_firmware(void)
+{
+       u32 len = get_u32(BOOTLOADER_APP_START + HDR_LENGTH);
+       u16 flash_in_progress = get_u16(BOOTLOADER_APP_START + HDR_FLASH_IN_PROGRESS);
+
+       // FIXME: Should check if len is reasonable
+
+       crc_reset();
+       u32 crc = crc_calculate_block((u32 *)BOOTLOADER_APP_START, len/4);
+       u32 want_crc = get_u32(BOOTLOADER_APP_START + len);
+       DEBUG("DFU: fip=%04x crc=%08x/%08x len=%u\n", (uint) flash_in_progress, (uint) crc, (uint) want_crc, (uint) len);
+       if (flash_in_progress || crc != want_crc) {
+               DEBUG("DFU: Bad firmware\n");
+               return 0;
+       }
+
+       return 1;
+}
+
+static byte dfu_getstatus(usbd_device *usbd_dev UNUSED, u32 *bwPollTimeout)
 {
-       switch (usbdfu_state) {
+       switch (dfu_state) {
        case STATE_DFU_DNLOAD_SYNC:
-               usbdfu_state = STATE_DFU_DNBUSY;
+               dfu_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;
+               dfu_state = STATE_DFU_MANIFEST;
                return DFU_STATUS_OK;
+       case STATE_DFU_ERROR:
+               return DFU_STATUS_ERR_VERIFY;
        default:
                return DFU_STATUS_OK;
        }
 }
 
-static void program_page(struct prog_info *prog, bool need_erase)
+static void dfu_getstatus_complete(usbd_device *usbd_dev UNUSED, struct usb_setup_data *req UNUSED)
 {
-       flash_unlock();
-       u32 baseaddr = BOOTLOADER_APP_START + prog->blocknum * dfu_function.wTransferSize;
-       DEBUG("DFU: Block %u -> %08x\n", prog->blocknum, (uint) baseaddr);
-       if (need_erase)
-               flash_erase_page(baseaddr);
-       for (uint i = 0; i < prog->len; i += 2) {
-               u16 data = *(u16 *)(prog->buf + i);
-               flash_program_half_word(baseaddr + i, data);
-       }
-       flash_lock();
-}
-
-static void usbdfu_getstatus_complete(usbd_device *usbd_dev UNUSED, struct usb_setup_data *req UNUSED)
-{
-       switch (usbdfu_state) {
+       switch (dfu_state) {
        case STATE_DFU_DNBUSY:
-               if (prog_page.blocknum == 0) {
-                       // The first block will be programmed last
-                       prog_header = prog_page;
-                       flash_unlock();
-                       flash_erase_page(BOOTLOADER_APP_START);
-                       flash_lock();
-               } else
-                       program_page(&prog_page, true);
-               usbdfu_state = STATE_DFU_DNLOAD_IDLE;
+               if (prog.blocknum == 0) {
+                       // The "flash in progress" word is programmed as 0xffff first and reset later
+                       *(u16*)(prog.buf + HDR_FLASH_IN_PROGRESS) = 0xffff;
+               }
+               u32 baseaddr = BOOTLOADER_APP_START + prog.blocknum * BLOCK_SIZE;
+               DEBUG("DFU: Block %u -> %08x + %u\n", prog.blocknum, (uint) baseaddr, prog.len);
+               flash_unlock();
+               flash_erase_page(baseaddr);
+               for (uint i = 0; i < prog.len; i += 2)
+                       flash_program_half_word(baseaddr + i, *(u16*)(prog.buf + i));
+               flash_lock();
+               for (uint i = 0; i < prog.len; i++) {
+                       if (*(byte *)(baseaddr + i) != prog.buf[i]) {
+                               DEBUG("DFU: Verification failed\n");
+                               dfu_state = STATE_DFU_ERROR;
+                       }
+               }
+               dfu_state = STATE_DFU_DNLOAD_IDLE;
                return;
        case STATE_DFU_MANIFEST:
-               // At the very end, program the first page
-               program_page(&prog_header, false);
-               /* USB device must detach, we just reset... */
-               scb_reset_system();
-               return; /* Will never return. */
+               // At the very end, re-flash the "flash in progress" word
+               flash_unlock();
+               flash_program_half_word(BOOTLOADER_APP_START + 0x20, 0);
+               flash_lock();
+               if (verify_firmware())
+                       dfu_state = STATE_DFU_MANIFEST_WAIT_RESET;
+               else
+                       dfu_state = STATE_DFU_ERROR;
+               return;
        default:
                return;
        }
 }
 
-static enum usbd_request_return_codes usbdfu_control_request(usbd_device *usbd_dev,
+static enum usbd_request_return_codes dfu_control_request(usbd_device *usbd_dev,
        struct usb_setup_data *req,
        byte **buf,
        u16 *len,
@@ -181,47 +215,48 @@ static enum usbd_request_return_codes usbdfu_control_request(usbd_device *usbd_d
        if ((req->bmRequestType & 0x7F) != 0x21)
                return USBD_REQ_NOTSUPP; /* Only accept class request. */
 
-       DEBUG("DFU: Request %02x in state %d\n", req->bRequest, usbdfu_state);
+       DEBUG("DFU: Request %02x in state %d\n", req->bRequest, dfu_state);
+       timeout = DFU_TIMEOUT;
 
        switch (req->bRequest) {
        case DFU_DNLOAD:
                if (len == NULL || *len == 0) {
-                       usbdfu_state = STATE_DFU_MANIFEST_SYNC;
+                       dfu_state = STATE_DFU_MANIFEST_SYNC;
                } else {
                        /* Copy download data for use on GET_STATUS. */
-                       prog_page.blocknum = req->wValue;
-                       prog_page.len = *len;
-                       memcpy(prog_page.buf, *buf, *len);
-                       usbdfu_state = STATE_DFU_DNLOAD_SYNC;
+                       prog.blocknum = req->wValue;
+                       prog.len = *len;
+                       memcpy(prog.buf, *buf, *len);
+                       dfu_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;
+               if (dfu_state == STATE_DFU_ERROR)
+                       dfu_state = STATE_DFU_IDLE;
                return USBD_REQ_HANDLED;
        case DFU_ABORT:
                /* Abort returns to dfuIDLE state. */
-               usbdfu_state = STATE_DFU_IDLE;
+               dfu_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)[0] = dfu_getstatus(usbd_dev, &bwPollTimeout);
                (*buf)[1] = bwPollTimeout & 0xFF;
                (*buf)[2] = (bwPollTimeout >> 8) & 0xFF;
                (*buf)[3] = (bwPollTimeout >> 16) & 0xFF;
-               (*buf)[4] = usbdfu_state;
+               (*buf)[4] = dfu_state;
                (*buf)[5] = 0; /* iString not used here */
                *len = 6;
-               *complete = usbdfu_getstatus_complete;
+               *complete = dfu_getstatus_complete;
                return USBD_REQ_HANDLED;
                }
        case DFU_GETSTATE:
                /* Return state with no state transition. */
-               *buf[0] = usbdfu_state;
+               *buf[0] = dfu_state;
                *len = 1;
                return USBD_REQ_HANDLED;
        }
@@ -229,34 +264,110 @@ static enum usbd_request_return_codes usbdfu_control_request(usbd_device *usbd_d
        return USBD_REQ_NOTSUPP;
 }
 
-static void usbdfu_set_config(usbd_device *usbd_dev, u16 wValue UNUSED)
+static void dfu_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);
+               usbd_dev,
+               USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
+               USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
+               dfu_control_request);
 }
 
-static void usbdfu_reset(void)
+static void dfu_reset(void)
 {
-       usbdfu_state = STATE_DFU_IDLE;
+       dfu_state = STATE_DFU_IDLE;
 }
 
-int main(void)
+/*
+ *  This is a modified version of rcc_clock_setup_in_hsi_out_48mhz(),
+ *  which properly turns off the PLL before setting its parameters.
+ */
+static void my_rcc_clock_setup_in_hsi_out_48mhz(void)
 {
-       usbd_device *usbd_dev;
+       /* Enable internal high-speed oscillator. */
+       rcc_osc_on(RCC_HSI);
+       rcc_wait_for_osc_ready(RCC_HSI);
+
+       /* Select HSI as SYSCLK source. */
+       rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSICLK);
+
+       // XXX: Disable PLL
+       rcc_osc_off(RCC_PLL);
+
+       /*
+        * Set prescalers for AHB, ADC, ABP1, ABP2.
+        * Do this before touching the PLL (TODO: why?).
+        */
+       rcc_set_hpre(RCC_CFGR_HPRE_SYSCLK_NODIV);       /*Set.48MHz Max.72MHz */
+       rcc_set_adcpre(RCC_CFGR_ADCPRE_PCLK2_DIV8);     /*Set. 6MHz Max.14MHz */
+       rcc_set_ppre1(RCC_CFGR_PPRE1_HCLK_DIV2);        /*Set.24MHz Max.36MHz */
+       rcc_set_ppre2(RCC_CFGR_PPRE2_HCLK_NODIV);       /*Set.48MHz Max.72MHz */
+       rcc_set_usbpre(RCC_CFGR_USBPRE_PLL_CLK_NODIV);  /*Set.48MHz Max.48MHz */
+
+       /*
+        * Sysclk runs with 48MHz -> 1 waitstates.
+        * 0WS from 0-24MHz
+        * 1WS from 24-48MHz
+        * 2WS from 48-72MHz
+        */
+       flash_set_ws(FLASH_ACR_LATENCY_1WS);
+
+       /*
+        * Set the PLL multiplication factor to 12.
+        * 8MHz (internal) * 12 (multiplier) / 2 (PLLSRC_HSI_CLK_DIV2) = 48MHz
+        */
+       rcc_set_pll_multiplication_factor(RCC_CFGR_PLLMUL_PLL_CLK_MUL12);
+
+       /* Select HSI/2 as PLL source. */
+       rcc_set_pll_source(RCC_CFGR_PLLSRC_HSI_CLK_DIV2);
+
+       /* Enable PLL oscillator and wait for it to stabilize. */
+       rcc_osc_on(RCC_PLL);
+       rcc_wait_for_osc_ready(RCC_PLL);
+
+       /* Select PLL as SYSCLK source. */
+       rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_PLLCLK);
+
+       /* Set the peripheral clock frequencies used */
+       rcc_ahb_frequency = 48000000;
+       rcc_apb1_frequency = 24000000;
+       rcc_apb2_frequency = 48000000;
+}
 
-       // Flash programming requires running on the internal oscillator
-       rcc_clock_setup_in_hsi_out_48mhz();
+static void clock_plain_hsi(void)
+{
+       // Select HSI as SYSCLK source
+       rcc_set_sysclk_source(RCC_CFGR_SW_SYSCLKSEL_HSICLK);
+
+       // Disable PLL
+       rcc_osc_off(RCC_PLL);
+
+       // Set prescalers for AHB, ADC, ABP1, ABP2, USB to defaults
+       rcc_set_hpre(RCC_CFGR_HPRE_SYSCLK_NODIV);
+       rcc_set_adcpre(RCC_CFGR_ADCPRE_PCLK2_DIV2);
+       rcc_set_ppre1(RCC_CFGR_PPRE1_HCLK_NODIV);
+       rcc_set_ppre2(RCC_CFGR_PPRE2_HCLK_NODIV);
+       rcc_set_usbpre(RCC_CFGR_USBPRE_PLL_VCO_CLK_DIV3);
+}
+
+static void reset_peripherals(void)
+{
+       // Turn off clock to all peripherals and reset them
+       RCC_AHBENR = 0x00000014;
+       RCC_APB1ENR = 0;
+       RCC_APB2ENR = 0;
+       RCC_APB1RSTR = 0x22fec9ff;
+       RCC_APB2RSTR = 0x0038fffd;
+       RCC_APB1RSTR = 0;
+       RCC_APB2RSTR = 0;
+}
 
+static void configure_hardware(void)
+{
        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);
+       rcc_periph_clock_enable(RCC_CRC);
 
 #ifdef DEBUG_USART
        // Currently, only USART1 is supported
@@ -284,57 +395,71 @@ int main(void)
        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
+static void usb_disconnect(void)
+{
        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<100; i++) {
                while (!systick_get_countflag())
                        ;
        }
+}
+
+int main(void)
+{
+       usbd_device *usbd_dev;
+
+       reset_peripherals();
+
+       // Flash programming requires running on the internal oscillator
+       my_rcc_clock_setup_in_hsi_out_48mhz();
+
+       configure_hardware();
+       desig_get_unique_id_as_dfu(usb_serial_number);
+
+       DEBUG("DFU: Started (SN %s)\n", usb_serial_number);
+
+       usb_disconnect();
 
        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);
+       usbd_register_reset_callback(usbd_dev, dfu_reset);
+       usbd_register_set_config_callback(usbd_dev, dfu_set_config);
 
 restart: ;
 
-       uint timeout = 5000;
-       while (timeout) {
+       timeout = DEFAULT_TIMEOUT;
+       while (timeout || (dfu_state != STATE_DFU_IDLE && dfu_state != STATE_DFU_MANIFEST_WAIT_RESET)) {
                usbd_poll(usbd_dev);
-               if (systick_get_countflag()) {
+               if (timeout && systick_get_countflag()) {
                        timeout--;
+                       // FIXME: Blink LED even after 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");
+       if (!verify_firmware())
                goto restart;
-       }
+
+       u32 sp = get_u32(BOOTLOADER_APP_START);
+       u32 pc = get_u32(BOOTLOADER_APP_START + 4);
+       DEBUG("DFU: Boot (sp=%08x pc=%08x)\n", (uint) sp, (uint) pc);
 
 #ifdef DEBUG_USART
-       while (!usart_get_flag(DEBUG_USART, USART_FLAG_TC))
-               ;
+       debug_flush();
 #endif
        debug_led(0);
-       cm_disable_interrupts();
+
+       reset_peripherals();
+       clock_plain_hsi();
 
        /* Set vector table base address. */
-       SCB_VTOR = BOOTLOADER_APP_START & 0xFFFF;
+       SCB_VTOR = BOOTLOADER_APP_START;
 
        /* Initialize master stack pointer. */
        asm volatile("msr msp, %0"::"g" (sp));