X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;ds=inline;f=lib%2Fdfu-bootloader.c;h=3df8351aad66e50098d281f6f519eea1cc0a3737;hb=553dc382661879e33c4970b4fb5babfc630432c9;hp=ce01a41c5173872c6c4dd45ec738a6e360fd8caa;hpb=fbd3fa1ca9ff01dd7f67502b9549da62e5f8279f;p=home-hw.git diff --git a/lib/dfu-bootloader.c b/lib/dfu-bootloader.c index ce01a41..3df8351 100644 --- a/lib/dfu-bootloader.c +++ b/lib/dfu-bootloader.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -31,21 +32,24 @@ #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));