/* Copyright (c) 2013 Nordic Semiconductor. All Rights Reserved. * * The information contained herein is property of Nordic Semiconductor ASA. * Terms and conditions of usage are described in detail in NORDIC * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. * * Licensees are granted free, non-transferable use of the information. NO * WARRANTY of ANY KIND is provided. This heading must NOT be removed from * the file. * */ /**@file * * @defgroup ble_sdk_app_bootloader_main main.c * @{ * @ingroup dfu_bootloader_api * @brief Bootloader project main file. * * -# Receive start data packet. * -# Based on start packet, prepare NVM area to store received data. * -# Receive data packet. * -# Validate data packet. * -# Write Data packet to NVM. * -# If not finished - Wait for next packet. * -# Receive stop data packet. * -# Activate Image, boot application. * */ #include #include #include #include #include "nordic_common.h" #include "sdk_common.h" #include "dfu_transport.h" #include "bootloader.h" #include "bootloader_util.h" #include "nrf.h" #include "nrf_soc.h" #include "nrf_nvic.h" #include "app_error.h" #include "nrf_gpio.h" #include "ble.h" #include "nrf.h" #include "ble_hci.h" #include "app_scheduler.h" #include "app_timer.h" #include "nrf_error.h" #include "boards.h" #include "pstorage_platform.h" #include "nrf_mbr.h" #include "nrf_wdt.h" #include "pstorage.h" #ifdef NRF52840_XXAA #include "nrf_usbd.h" #include "tusb.h" #include "usb/msc_uf2.h" void usb_init(void); void usb_teardown(void); /* tinyusb function that handles power event (detected, ready, removed) * We must call it within SD's SOC event handler, or set it as power event handler if SD is not enabled. */ extern void tusb_hal_nrf_power_event(uint32_t event); // TODO fully move to nrfx enum { NRFX_POWER_USB_EVT_DETECTED, /**< USB power detected on the connector (plugged in). */ NRFX_POWER_USB_EVT_REMOVED, /**< USB power removed from the connector. */ NRFX_POWER_USB_EVT_READY /**< USB power regulator ready. */ }; #else #define usb_init() #define usb_teardown() #endif #define BOOTLOADER_VERSION_REGISTER NRF_TIMER2->CC[0] #define LED_BLINK_INTERVAL 100 #define BOOTLOADER_STARTUP_DFU_INTERVAL 1000 /* Magic that written to NRF_POWER->GPREGRET by application when it wish to go into DFU * - BOOTLOADER_DFU_OTA_MAGIC used by BLEDfu service : SD is already init * - BOOTLOADER_DFU_OTA_FULLRESET_MAGIC entered by soft reset : SD is not init * - BOOTLOADER_DFU_SERIAL_MAGIC entered by soft reset : SD is not init * * Note: for BOOTLOADER_DFU_OTA_MAGIC Softdevice should not initialized. In other case SD must be initialized */ #define BOOTLOADER_DFU_OTA_MAGIC BOOTLOADER_DFU_START // 0xB1 #define BOOTLOADER_DFU_OTA_FULLRESET_MAGIC 0xA8 #define BOOTLOADER_DFU_SERIAL_MAGIC 0x4e #define BOOTLOADER_BUTTON BUTTON_1 // Button used to enter SW update mode. #define FRESET_BUTTON BUTTON_2 // Button used in addition to DFU button, to force OTA DFU #define APP_TIMER_PRESCALER 0 /**< Value of the RTC1 PRESCALER register. */ #define APP_TIMER_OP_QUEUE_SIZE 4 /**< Size of timer operation queues. */ #define SCHED_MAX_EVENT_DATA_SIZE sizeof(app_timer_event_t) /**< Maximum size of scheduler events. */ #define SCHED_QUEUE_SIZE 30 /**< Maximum number of events in the scheduler queue. */ // Helper function #define memclr(buffer, size) memset(buffer, 0, size) #define varclr(_var) memclr(_var, sizeof(*(_var))) #define arrclr(_arr) memclr(_arr, sizeof(_arr)) #define arrcount(_arr) ( sizeof(_arr) / sizeof(_arr[0]) ) // These value must be the same with one in dfu_transport_ble.c #define BLEGAP_EVENT_LENGTH 6 #define BLEGATT_ATT_MTU_MAX 247 enum { BLE_CONN_CFG_HIGH_BANDWIDTH = 1 }; // Adafruit for factory reset #define APPDATA_ADDR_START (BOOTLOADER_REGION_START-DFU_APP_DATA_RESERVED) #ifdef NRF52840_XXAA STATIC_ASSERT( APPDATA_ADDR_START == 0xED000); #else STATIC_ASSERT( APPDATA_ADDR_START == 0x6D000); #endif void adafruit_factory_reset(void); volatile bool _freset_erased_complete = false; // Adafruit for Blink pattern bool isBlinkFast = false; bool isOTAConnected = false; APP_TIMER_DEF( blinky_timer_id ); // true if ble, false if serial bool _ota_update = false; bool is_ota(void) { return _ota_update; } static void button_pin_init(uint32_t pin) { nrf_gpio_cfg_sense_input(pin, BUTTON_PULL, NRF_GPIO_PIN_SENSE_LOW); } bool button_pressed(uint32_t pin) { return (nrf_gpio_pin_read(pin) == 0) ? true : false; } static void led_pin_init(uint32_t pin) { nrf_gpio_cfg_output(pin); led_off(pin); } void led_control(uint32_t pin, bool state) { nrf_gpio_pin_write(pin, state ? LED_STATE_ON : (1-LED_STATE_ON)); } /* * Blinking function, there are a few patterns * - DFU Serial : LED Status blink * - DFU OTA : LED Status & Conn blink at the same time * - DFU Flashing : LED Status blink 2x fast * - Factory Reset : LED Status blink 2x fast * - Fatal Error : LED Status & Conn blink one after another */ static void blinky_handler(void * p_context) { static uint8_t state = 0; static uint32_t count = 0; count++; // if not uploading then blink slow (interval/2) if ( !isBlinkFast && count%2 ) return; state = 1-state; led_control(LED_RED, state); // Blink LED BLUE if OTA mode and not connected if (is_ota() && !isOTAConnected) { led_control(LED_BLUE, state); } // Feed all Watchdog just in case application enable it (WDT last through a soft reboot to bootloader) if ( nrf_wdt_started() ) { for (uint8_t i=0; i<8; i++) nrf_wdt_reload_request_set(i); } } void blinky_fast_set(bool isFast) { isBlinkFast = isFast; } void blinky_ota_connected(void) { isOTAConnected = true; } void blinky_ota_disconneted(void) { isOTAConnected = false; } void board_init(void) { button_pin_init(BOOTLOADER_BUTTON); button_pin_init(FRESET_BUTTON); NRFX_DELAY_US(100); // wait for the pin state is stable led_pin_init(LED_RED); led_pin_init(LED_BLUE); // Init scheduler and timer (use scheduler) APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE); app_timer_init(); } /**@brief Function for initializing the BLE stack. * * @details Initializes the SoftDevice and the BLE event interrupt. * * @param[in] init_softdevice true if SoftDevice should be initialized. The SoftDevice must only * be initialized if a chip reset has occured. Soft reset from * application must not reinitialize the SoftDevice. */ static uint32_t ble_stack_init(bool init_softdevice) { if (init_softdevice) { sd_mbr_command_t com = { .command = SD_MBR_COMMAND_INIT_SD }; APP_ERROR_CHECK( sd_mbr_command(&com) ); } APP_ERROR_CHECK( sd_softdevice_vector_table_base_set(BOOTLOADER_REGION_START) ); // Enable Softdevice nrf_clock_lf_cfg_t clock_cfg = { #if 0 .source = NRF_CLOCK_LF_SRC_RC, .rc_ctiv = 16, .rc_temp_ctiv = 2, .accuracy = NRF_CLOCK_LF_ACCURACY_20_PPM #else .source = NRF_CLOCK_LF_SRC_XTAL, .rc_ctiv = 0, .rc_temp_ctiv = 0, .accuracy = NRF_CLOCK_LF_ACCURACY_20_PPM #endif }; APP_ERROR_CHECK( sd_softdevice_enable(&clock_cfg, app_error_fault_handler) ); sd_nvic_EnableIRQ(SD_EVT_IRQn); /*------------- Configure BLE params -------------*/ extern uint32_t __data_start__[]; // defined in linker uint32_t ram_start = (uint32_t) __data_start__; ble_cfg_t blecfg; // Configure the maximum number of connections. varclr(&blecfg); blecfg.gap_cfg.role_count_cfg.periph_role_count = 1; blecfg.gap_cfg.role_count_cfg.central_role_count = 0; blecfg.gap_cfg.role_count_cfg.central_sec_count = 0; APP_ERROR_CHECK( sd_ble_cfg_set(BLE_GAP_CFG_ROLE_COUNT, &blecfg, ram_start) ); // NRF_DFU_BLE_REQUIRES_BONDS varclr(&blecfg); blecfg.gatts_cfg.service_changed.service_changed = 1; APP_ERROR_CHECK( sd_ble_cfg_set(BLE_GATTS_CFG_SERVICE_CHANGED, &blecfg, ram_start) ); // ATT MTU varclr(&blecfg); blecfg.conn_cfg.conn_cfg_tag = BLE_CONN_CFG_HIGH_BANDWIDTH; blecfg.conn_cfg.params.gatt_conn_cfg.att_mtu = BLEGATT_ATT_MTU_MAX; APP_ERROR_CHECK( sd_ble_cfg_set(BLE_CONN_CFG_GATT, &blecfg, ram_start) ); // Event Length + HVN queue + WRITE CMD queue setting affecting bandwidth varclr(&blecfg); blecfg.conn_cfg.conn_cfg_tag = BLE_CONN_CFG_HIGH_BANDWIDTH; blecfg.conn_cfg.params.gap_conn_cfg.conn_count = 1; blecfg.conn_cfg.params.gap_conn_cfg.event_length = BLEGAP_EVENT_LENGTH; APP_ERROR_CHECK( sd_ble_cfg_set(BLE_CONN_CFG_GAP, &blecfg, ram_start) ); // Enable BLE stack. APP_ERROR_CHECK( sd_ble_enable(&ram_start) ); return NRF_SUCCESS; } /** * @brief Function for bootloader main entry. */ int main(void) { // SD is already Initialized in case of BOOTLOADER_DFU_OTA_MAGIC bool sd_inited = (NRF_POWER->GPREGRET == BOOTLOADER_DFU_OTA_MAGIC); // Start Bootloader in BLE OTA mode _ota_update = (NRF_POWER->GPREGRET == BOOTLOADER_DFU_OTA_MAGIC) || (NRF_POWER->GPREGRET == BOOTLOADER_DFU_OTA_FULLRESET_MAGIC); // start bootloader either serial or ble bool dfu_start = _ota_update || (NRF_POWER->GPREGRET == BOOTLOADER_DFU_SERIAL_MAGIC); // Clear GPREGRET if it is our values if (dfu_start) NRF_POWER->GPREGRET = 0; // Save bootloader version to pre-defined register, retrieved by application BOOTLOADER_VERSION_REGISTER = (BOOTLOADER_VERSION); // This check ensures that the defined fields in the bootloader corresponds with actual // setting in the chip. APP_ERROR_CHECK_BOOL(*((uint32_t *)NRF_UICR_BOOT_START_ADDRESS) == BOOTLOADER_REGION_START); board_init(); /* Initialize a blinky timer to show that we're in bootloader */ (void) app_timer_create(&blinky_timer_id, APP_TIMER_MODE_REPEATED, blinky_handler); // Init bootloader (void) bootloader_init(); if (bootloader_dfu_sd_in_progress()) { APP_ERROR_CHECK( bootloader_dfu_sd_update_continue() ); ble_stack_init(!sd_inited); app_timer_start(blinky_timer_id, APP_TIMER_TICKS(LED_BLINK_INTERVAL), NULL); APP_ERROR_CHECK( bootloader_dfu_sd_update_finalize() ); } else { ble_stack_init(!sd_inited); app_timer_start(blinky_timer_id, APP_TIMER_TICKS(LED_BLINK_INTERVAL), NULL); } usb_init(); /*------------- Determine DFU mode (Serial, OTA, FRESET or normal) -------------*/ // DFU button pressed dfu_start = dfu_start || button_pressed(BOOTLOADER_BUTTON); // DFU + FRESET are pressed --> OTA _ota_update = _ota_update || ( button_pressed(BOOTLOADER_BUTTON) && button_pressed(FRESET_BUTTON) ) ; if (dfu_start || (!bootloader_app_is_valid(DFU_BANK_0_REGION_START))) { // Initiate an update of the firmware. APP_ERROR_CHECK( bootloader_dfu_start(_ota_update, 0) ); } /*------------- Adafruit Factory reset -------------*/ bool is_freset = ( !button_pressed(BOOTLOADER_BUTTON) && button_pressed(FRESET_BUTTON) ); if (is_freset) { adafruit_factory_reset(); } /*------------- Stop timer and jump to application -------------*/ app_timer_stop(blinky_timer_id); led_off(LED_BLUE); led_off(LED_RED); if (bootloader_app_is_valid(DFU_BANK_0_REGION_START) && !bootloader_dfu_sd_in_progress()) { // Stop RTC1 NVIC_DisableIRQ(RTC1_IRQn); NRF_RTC1->EVTENCLR = RTC_EVTEN_COMPARE0_Msk; NRF_RTC1->INTENCLR = RTC_INTENSET_COMPARE0_Msk; NRF_RTC1->TASKS_STOP = 1; NRF_RTC1->TASKS_CLEAR = 1; usb_teardown(); // Select a bank region to use as application region. // @note: Only applications running from DFU_BANK_0_REGION_START is supported. bootloader_app_start(DFU_BANK_0_REGION_START); } NVIC_SystemReset(); } /*------------------------------------------------------------------*/ /* FACTORY RESET *------------------------------------------------------------------*/ /** * Pstorage callback, fired after erased Application Data */ static void appdata_pstorage_cb(pstorage_handle_t * p_handle, uint8_t op_code, uint32_t result, uint8_t * p_data, uint32_t data_len) { if ( op_code == PSTORAGE_CLEAR_OP_CODE) { _freset_erased_complete = true; } } void freset_erase_and_wait(pstorage_handle_t* hdl, uint32_t addr, uint32_t size) { _freset_erased_complete = false; // set address and start erasing hdl->block_id = addr; pstorage_clear(hdl, size); // Time to erase a page is 100 ms max // It is better to force a timeout to prevent lock-up uint32_t timeout_tck = (size/CODE_PAGE_SIZE)*100; timeout_tck = APP_TIMER_TICKS(timeout_tck); uint32_t start_tck = app_timer_cnt_get(); while(!_freset_erased_complete) { sd_app_evt_wait(); app_sched_execute(); uint32_t now_tck = app_timer_cnt_get(); if ( (now_tck - start_tck) > timeout_tck ) break; } } /** * Perform factory reset to erase Application + Data */ void adafruit_factory_reset(void) { // Blink fast RED and turn on BLUE when erasing blinky_fast_set(true); led_on(LED_BLUE); static pstorage_handle_t freset_handle = { .block_id = APPDATA_ADDR_START } ; pstorage_module_param_t storage_params = { .cb = appdata_pstorage_cb}; pstorage_register(&storage_params, &freset_handle); // clear all App Data freset_erase_and_wait(&freset_handle, APPDATA_ADDR_START, DFU_APP_DATA_RESERVED); // Only need to erase the 1st page of Application code to make it invalid freset_erase_and_wait(&freset_handle, DFU_BANK_0_REGION_START, CODE_PAGE_SIZE); // back to normal blinky_fast_set(false); led_off(LED_BLUE); } //--------------------------------------------------------------------+ // Error Handler //--------------------------------------------------------------------+ void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info) { volatile uint32_t* ARM_CM_DHCSR = ((volatile uint32_t*) 0xE000EDF0UL); /* Cortex M CoreDebug->DHCSR */ if ( (*ARM_CM_DHCSR) & 1UL ) __asm("BKPT #0\n"); /* Only halt mcu if debugger is attached */ NVIC_SystemReset(); } void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name) { app_error_handler(0xDEADBEEF, line_num, p_file_name); } uint32_t tusb_hal_millis(void) { return ( ( ((uint64_t)app_timer_cnt_get())*1000*(APP_TIMER_CONFIG_RTC_FREQUENCY+1)) / APP_TIMER_CLOCK_FREQ ); } /*------------------------------------------------------------------*/ /* SoftDevice Event handler *------------------------------------------------------------------*/ uint32_t proc_ble(void) { __ALIGN(4) uint8_t ev_buf[ BLE_EVT_LEN_MAX(BLEGATT_ATT_MTU_MAX) ]; uint16_t ev_len = BLE_EVT_LEN_MAX(BLEGATT_ATT_MTU_MAX); // Get BLE Event uint32_t err = sd_ble_evt_get(ev_buf, &ev_len); // Handle valid event, ignore error if( NRF_SUCCESS == err) { // from dfu_transport_ble extern void ble_evt_dispatch(ble_evt_t * p_ble_evt); ble_evt_t* evt = (ble_evt_t*) ev_buf; ble_evt_dispatch(evt); } return err; } uint32_t proc_soc(void) { uint32_t soc_evt; uint32_t err = sd_evt_get(&soc_evt); if (NRF_SUCCESS == err) { pstorage_sys_event_handler(soc_evt); #ifdef NRF52840_XXAA /*------------- usb power event handler -------------*/ int32_t usbevt = (soc_evt == NRF_EVT_POWER_USB_DETECTED ) ? NRFX_POWER_USB_EVT_DETECTED: (soc_evt == NRF_EVT_POWER_USB_POWER_READY) ? NRFX_POWER_USB_EVT_READY : (soc_evt == NRF_EVT_POWER_USB_REMOVED ) ? NRFX_POWER_USB_EVT_REMOVED : -1; if ( usbevt >= 0) tusb_hal_nrf_power_event(usbevt); #endif } return err; } void ada_sd_task(void* evt_data, uint16_t evt_size) { (void) evt_data; (void) evt_size; // process BLE and SOC until there is no more events while( (NRF_ERROR_NOT_FOUND != proc_ble()) || (NRF_ERROR_NOT_FOUND != proc_soc()) ) { } } void SD_EVT_IRQHandler(void) { // Use App Scheduler to defer handling code in non-isr context app_sched_event_put(NULL, 0, ada_sd_task); }