void bootloader_enable(void)
{
#ifdef RBC_MESH_SERIAL
    mesh_aci_start();
#endif
    bl_cmd_t enable_cmd;
    enable_cmd.type = BL_CMD_TYPE_ENABLE;
    bl_cmd_handler(&enable_cmd);
    transport_start();

    /* Recover from broken state */
    if (dfu_mesh_app_is_valid())
    {
        set_timeout(TIMER_YIELD_TIMEOUT, TIMEOUT_ACTION_GO_TO_APP);
    }
    else
    {
#ifdef RTT_LOG
        __LOG(RTT_CTRL_TEXT_RED "APP is invalid.\n");
        bl_info_flags_t* p_flags = &bootloader_info_entry_get(BL_INFO_TYPE_FLAGS)->flags;
        bl_info_segment_t* p_seg = &bootloader_info_entry_get(BL_INFO_TYPE_SEGMENT_APP)->segment;
        __LOG("\tINTACT: SD: %d APP: %d BL: %d\n",
                p_flags->sd_intact,
                p_flags->app_intact,
                p_flags->bl_intact);
        if (*((uint32_t*) p_seg->start) == 0xFFFFFFFF)
        {
            __LOG("\tNo application at 0x%x\n", p_seg->start);
        }
#endif
        /* update the bootloader if a bank is available */
        if (dfu_bank_flash(DFU_TYPE_BOOTLOADER) == NRF_SUCCESS)
        {
            return;
        }

        dfu_type_t missing = dfu_mesh_missing_type_get();
        if (missing != DFU_TYPE_NONE && dfu_bank_flash(missing) == NRF_ERROR_NOT_FOUND)
        {
            fwid_union_t req_fwid;
            bl_info_entry_t* p_fwid_entry = bootloader_info_entry_get(BL_INFO_TYPE_VERSION);
            APP_ERROR_CHECK_BOOL(p_fwid_entry != NULL);

            switch (missing)
            {
                case DFU_TYPE_SD:
                    req_fwid.sd = p_fwid_entry->version.sd;
                    break;
                case DFU_TYPE_APP:
                    req_fwid.app = p_fwid_entry->version.app;
                    break;
                default:
                    APP_ERROR_CHECK(NRF_ERROR_INVALID_DATA);
            }

            dfu_mesh_req(missing, &req_fwid, (uint32_t*) 0xFFFFFFFF);
        }
    }
}
uint32_t dfu_bank_flash(dfu_type_t dfu_type)
{
    if (dfu_type != DFU_TYPE_SD &&
        dfu_type != DFU_TYPE_APP &&
        dfu_type != DFU_TYPE_BOOTLOADER)
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    if (mp_bank_entry != NULL)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    bl_info_entry_t* p_bank_entry = bootloader_info_entry_get((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + dfu_type));
    if (!p_bank_entry || *p_bank_entry->bank.p_bank_addr == 0xFFFFFFFF)
    {
        return NRF_ERROR_NOT_FOUND;
    }

    __LOG("Flash bank of type %d\n", dfu_type);
    m_dfu_type = dfu_type;
    mp_bank_entry = &p_bank_entry->bank;
    flash_bank_entry();

    return NRF_SUCCESS;
}
void dfu_bank_scan(bool* p_bank_flash_started)
{
    for (uint32_t i = 1; i <= 4; i <<= 1)
    {
        bl_info_entry_t* p_bank_entry = bootloader_info_entry_get((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + i));
        if (!p_bank_entry)
        {
            continue;
        }
        if (p_bank_entry->bank.state != BL_INFO_BANK_STATE_IDLE)
        {
            __LOG("Flash of bank of type %d already started (state 0x%x), continue where we left off!\n", i, p_bank_entry->bank.state);
            m_dfu_type = (dfu_type_t) i;
            mp_bank_entry = &p_bank_entry->bank;
            flash_bank_entry();
            if (p_bank_flash_started)
            {
                *p_bank_flash_started = true;
            }

            return;
        }
    }
    mp_bank_entry = NULL;
    if (p_bank_flash_started)
    {
        *p_bank_flash_started = false;
    }
}
/*****************************************************************************
* Interface Functions
*****************************************************************************/
void bootloader_init(void)
{
    mp_beacon = NULL;
    m_state = BL_STATE_FIND_FWID;
    m_transaction.transaction_id = 0;
    m_transaction.type = DFU_TYPE_NONE;
    memset(m_data_cache, 0xFF, DATA_CACHE_SIZE * sizeof(uint16_t));
    memset(m_req_cache, 0, REQ_CACHE_SIZE * sizeof(m_req_cache[0]));
    memset(m_tid_cache, 0, TRANSACTION_ID_CACHE_SIZE);
    m_tid_index = 0;
    m_data_index = 0;
    m_req_index = 0;

    /* fetch persistent entries */
    m_bl_info_pointers.p_flags              = &bootloader_info_entry_get((uint32_t*) BOOTLOADER_INFO_ADDRESS, BL_INFO_TYPE_FLAGS)->flags;
    m_bl_info_pointers.p_fwid               = &bootloader_info_entry_get((uint32_t*) BOOTLOADER_INFO_ADDRESS, BL_INFO_TYPE_VERSION)->version;
    m_bl_info_pointers.p_segment_app        = &bootloader_info_entry_get((uint32_t*) BOOTLOADER_INFO_ADDRESS, BL_INFO_TYPE_SEGMENT_APP)->segment;
    m_bl_info_pointers.p_segment_bl         = &bootloader_info_entry_get((uint32_t*) BOOTLOADER_INFO_ADDRESS, BL_INFO_TYPE_SEGMENT_BL)->segment;
    m_bl_info_pointers.p_segment_sd         = &bootloader_info_entry_get((uint32_t*) BOOTLOADER_INFO_ADDRESS, BL_INFO_TYPE_SEGMENT_SD)->segment;
    m_bl_info_pointers.p_ecdsa_public_key   = &bootloader_info_entry_get((uint32_t*) BOOTLOADER_INFO_ADDRESS, BL_INFO_TYPE_ECDSA_PUBLIC_KEY)->public_key[0];

    if (
        ((uint32_t) m_bl_info_pointers.p_flags              < BOOTLOADER_INFO_ADDRESS) ||
        ((uint32_t) m_bl_info_pointers.p_fwid               < BOOTLOADER_INFO_ADDRESS) ||
        ((uint32_t) m_bl_info_pointers.p_segment_app        < BOOTLOADER_INFO_ADDRESS) ||
        ((uint32_t) m_bl_info_pointers.p_segment_sd         < BOOTLOADER_INFO_ADDRESS) ||
        ((uint32_t) m_bl_info_pointers.p_segment_bl         < BOOTLOADER_INFO_ADDRESS)
       )
    {
        bootloader_abort(BL_END_ERROR_INVALID_PERSISTENT_STORAGE);
    }
}
void bootloader_abort(dfu_end_t end_reason)
{
    __LOG("ABORT...\n");
    bl_info_entry_t* p_segment_entry = bootloader_info_entry_get(BL_INFO_TYPE_SEGMENT_APP);
    switch (end_reason)
    {
        case DFU_END_SUCCESS:
        case DFU_END_ERROR_TIMEOUT:
        case DFU_END_FWID_VALID:
        case DFU_END_ERROR_MBR_CALL_FAILED:
            if (p_segment_entry && dfu_mesh_app_is_valid())
            {
                if (fifo_is_empty(&m_flash_fifo))
                {
                    interrupts_disable();

                    sd_mbr_command_t com = {SD_MBR_COMMAND_INIT_SD, };

                    uint32_t err_code = sd_mbr_command(&com);
                    APP_ERROR_CHECK(err_code);

                    err_code = sd_softdevice_vector_table_base_set(p_segment_entry->segment.start);
                    APP_ERROR_CHECK(err_code);
#ifdef DEBUG_LEDS
                    NRF_GPIO->OUTSET = (1 << 21) | (1 << 22) | (1 << 23) | (1 << 24);
#endif
                    bootloader_util_app_start(p_segment_entry->segment.start);
                }
                else
                {
                    __LOG("->Will go to app once flash is finished.\n");
                    m_go_to_app = true;
                }
            }
            else if (p_segment_entry)
            {
                __LOG("->App not valid.\n");
            }
            else
            {
                __LOG("->No segment entry found\n");
            }
            break;
        case DFU_END_ERROR_INVALID_PERSISTENT_STORAGE:
            APP_ERROR_CHECK_BOOL(false);
        default:
            __LOG(RTT_CTRL_TEXT_RED "SYSTEM RESET (reason 0x%x)\n", end_reason);
            __disable_irq();
            while(1);
            //NVIC_SystemReset();
    }
}
/** Interrupt handling Flash operations. */
void FLASH_HANDLER_IRQHandler(void)
{
    flash_queue_entry_t flash_entry;
    uint32_t op_count = 0;
    while (fifo_pop(&m_flash_fifo, &flash_entry) == NRF_SUCCESS)
    {
        op_count++;
        bl_cmd_t rsp_cmd;
        if (flash_entry.type == FLASH_OP_TYPE_WRITE)
        {
            APP_ERROR_CHECK_BOOL(IS_WORD_ALIGNED(flash_entry.op.write.start_addr));
            APP_ERROR_CHECK_BOOL(IS_WORD_ALIGNED(flash_entry.op.write.length));
            APP_ERROR_CHECK_BOOL(IS_WORD_ALIGNED(flash_entry.op.write.p_data));
            __LOG("WRITING to 0x%x.(len %d)\n", flash_entry.op.write.start_addr, flash_entry.op.write.length);
            if (flash_entry.op.write.start_addr >= 0x20000000)
            {
                uint8_t* p_dst = ((uint8_t*) flash_entry.op.write.start_addr);
                for (uint32_t i = 0; i < flash_entry.op.write.length; ++i, p_dst++)
                {
                    *p_dst = (*p_dst & flash_entry.op.write.p_data[i]);
                }
            }
            else
            {
                nrf_flash_store((uint32_t*) flash_entry.op.write.start_addr,
                                            flash_entry.op.write.p_data,
                                            flash_entry.op.write.length, 0);
            }

            rsp_cmd.type                      = BL_CMD_TYPE_FLASH_WRITE_COMPLETE;
            rsp_cmd.params.flash.write.p_data = flash_entry.op.write.p_data;
        }
        else
        {
            __LOG("ERASING 0x%x.\n", flash_entry.op.erase.start_addr);
            if (flash_entry.op.erase.start_addr >= 0x20000000)
            {
                memset((uint32_t*) flash_entry.op.erase.start_addr, 0xFF, flash_entry.op.erase.length);
            }
            else
            {
                nrf_flash_erase((uint32_t*) flash_entry.op.erase.start_addr,
                                            flash_entry.op.erase.length);
            }
            rsp_cmd.type                      = BL_CMD_TYPE_FLASH_ERASE_COMPLETE;
            rsp_cmd.params.flash.erase.p_dest = (uint32_t*) flash_entry.op.erase.start_addr;
        }
        bl_cmd_handler(&rsp_cmd);
    }

    if (op_count > 0)
    {
        bl_cmd_t idle_cmd;
        idle_cmd.type = BL_CMD_TYPE_FLASH_ALL_COMPLETE;
        bl_cmd_handler(&idle_cmd);
    }
    if (fifo_is_empty(&m_flash_fifo) && m_go_to_app)
    {
        bl_info_entry_t* p_segment_entry = bootloader_info_entry_get(BL_INFO_TYPE_SEGMENT_APP);
        bootloader_util_app_start(p_segment_entry->segment.start);
    }
}
static void flash_bank_entry(void)
{
    bl_info_bank_t* p_bank_entry = mp_bank_entry; /* make local copy to avoid race conditions */
    if (p_bank_entry == NULL)
    {
        return;
    }

    bl_info_entry_t bank_entry_replacement;
    memcpy(&bank_entry_replacement, p_bank_entry, sizeof(bl_info_bank_t));
    switch (p_bank_entry->state)
    {
        case BL_INFO_BANK_STATE_IDLE:
            {
                m_waiting_for_idle = true;
                bank_entry_replacement.bank.state = BL_INFO_BANK_STATE_FLASH_FW;
                bootloader_info_entry_overwrite((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + m_dfu_type), &bank_entry_replacement);

                /* Wait for this to take effect before moving on, as the
                   potential mbr commands in the flash_fw state may trigger
                   sudden reboots. */
                return;
            }

        case BL_INFO_BANK_STATE_FLASH_FW:
            switch (m_dfu_type)
            {
                case DFU_TYPE_BOOTLOADER:
                    /* Check to see if the bank transfer has been executed */
                    if (memcmp(p_bank_entry->p_bank_addr,
                                (uint32_t*) bootloader_info_entry_get(BL_INFO_TYPE_SEGMENT_BL)->segment.start,
                                p_bank_entry->length) != 0)
                    {
                        /* move the bank with MBR. BOOTLOADERADDR() must
                           have been set. */
                        sd_mbr_command_t sd_mbr_cmd;

                        sd_mbr_cmd.command               = SD_MBR_COMMAND_COPY_BL;
                        sd_mbr_cmd.params.copy_bl.bl_src = p_bank_entry->p_bank_addr;
                        sd_mbr_cmd.params.copy_bl.bl_len = p_bank_entry->length / sizeof(uint32_t);
                        APP_ERROR_CHECK(sd_mbr_command(&sd_mbr_cmd));
                        return; /* Can't be reached, only here for readability. */
                    }
                    else
                    {
                        bank_entry_replacement.bank.state = BL_INFO_BANK_STATE_FLASH_META;
                        bootloader_info_entry_overwrite(BL_INFO_TYPE_BANK_BL, &bank_entry_replacement);
                    }
                    break;

                case DFU_TYPE_SD:
                    /* Check to see if the bank transfer has been executed */
                    if (memcmp(p_bank_entry->p_bank_addr,
                                (uint32_t*) bootloader_info_entry_get(BL_INFO_TYPE_SEGMENT_SD)->segment.start,
                                p_bank_entry->length) != 0)
                    {
                        /* move the bank with MBR. */
                        sd_mbr_command_t sd_mbr_cmd;

                        sd_mbr_cmd.command               = SD_MBR_COMMAND_COPY_SD;
                        sd_mbr_cmd.params.copy_sd.src    = p_bank_entry->p_bank_addr;
                        sd_mbr_cmd.params.copy_sd.len    = p_bank_entry->length / sizeof(uint32_t);
                        sd_mbr_cmd.params.copy_sd.dst    = (uint32_t*) 0x1000;
                        APP_ERROR_CHECK(sd_mbr_command(&sd_mbr_cmd));
                        return; /* Can't be reached, only here for readability. */
                    }
                    else
                    {
                        bank_entry_replacement.bank.state = BL_INFO_BANK_STATE_FLASH_META;
                        bootloader_info_entry_overwrite((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + m_dfu_type), &bank_entry_replacement);
                    }
                    break;

                case DFU_TYPE_APP:
                    /* This nukes the call stack and any flash-callbacks on the
                       app side. If we're in the application, we have to jump
                       to bootloader. */
                    if (bootloader_is_in_application())
                    {
                        /* All paths leading to this call warns about this
                           reset. We'll come back to finalize the transfer
                           after the reset. */
                        __LOG("IN APP MODE. RESET!\n");

#if 1 //def SOFTDEVICE_PRESENT
                        sd_power_reset_reason_clr(0x0F000F);

#if NORDIC_SDK_VERSION >= 11
                        sd_power_gpregret_set(0, RBC_MESH_GPREGRET_CODE_GO_TO_APP);
#else
                        sd_power_gpregret_set(RBC_MESH_GPREGRET_CODE_GO_TO_APP);
#endif
                        sd_nvic_SystemReset();
#else
                        NRF_POWER->RESETREAS = 0x0F000F; /* erase reset-reason to avoid wrongful state-readout on reboot */
                        NRF_POWER->GPREGRET = RBC_MESH_GPREGRET_CODE_GO_TO_APP;
                        NVIC_SystemReset();
#endif
                    }
                    else
                    {
                        /* Erase, Flash the FW, flash FW flag, flash the signature, erase the bank entry. */
                        bl_info_entry_t* p_app_entry = bootloader_info_entry_get(BL_INFO_TYPE_SEGMENT_APP);

                        APP_ERROR_CHECK_BOOL(p_app_entry != NULL);
                        APP_ERROR_CHECK_BOOL(IS_PAGE_ALIGNED(p_app_entry->segment.start));

                        /* Erase existing FW */
                        bl_evt_t flash_evt;
                        flash_evt.type = BL_EVT_TYPE_FLASH_ERASE;
                        flash_evt.params.flash.erase.start_addr = p_app_entry->segment.start;
                        flash_evt.params.flash.erase.length = ((p_bank_entry->length + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1)); /* Pad the rest of the page */
                        if (bootloader_evt_send(&flash_evt) != NRF_SUCCESS)
                        {
                            m_waiting_for_idle = true;
                            return;
                        }

                        /* Flash bank */
                        flash_evt.type = BL_EVT_TYPE_FLASH_WRITE;
                        flash_evt.params.flash.write.p_data = (uint8_t*) p_bank_entry->p_bank_addr;
                        flash_evt.params.flash.write.length = p_bank_entry->length;
                        flash_evt.params.flash.write.start_addr = p_app_entry->segment.start;
                        if (bootloader_evt_send(&flash_evt) != NRF_SUCCESS)
                        {
                            m_waiting_for_idle = true;
                            return;
                        }

                        /* Update state */
                        bank_entry_replacement.bank.state = BL_INFO_BANK_STATE_FLASH_META;
                        bootloader_info_entry_overwrite((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + m_dfu_type), &bank_entry_replacement);
                    }
                    break;

                default:
                    APP_ERROR_CHECK(NRF_ERROR_INVALID_DATA);
                    break;
            }
            /* deliberate fallthrough */
        case BL_INFO_BANK_STATE_FLASH_META:
            {
                bl_info_entry_t fwid_entry;
                bl_info_type_t signature_type;
                bl_info_entry_t flags_entry;
                bl_info_entry_t* p_old_fwid_entry  = bootloader_info_entry_get(BL_INFO_TYPE_VERSION);
                bl_info_entry_t* p_old_flags_entry = bootloader_info_entry_get(BL_INFO_TYPE_FLAGS);
                APP_ERROR_CHECK_BOOL(p_old_fwid_entry);
                APP_ERROR_CHECK_BOOL(p_bank_entry);

                memcpy(&fwid_entry, p_old_fwid_entry, sizeof(bl_info_version_t));
                memcpy(&flags_entry, p_old_flags_entry, sizeof(bl_info_flags_t));
                switch (m_dfu_type)
                {
                    case DFU_TYPE_SD:
                        fwid_entry.version.sd = p_bank_entry->fwid.sd;
                        signature_type = BL_INFO_TYPE_SIGNATURE_SD;
                        flags_entry.flags.sd_intact = true;
                        break;
                    case DFU_TYPE_BOOTLOADER:
                        fwid_entry.version.bootloader.id  = p_bank_entry->fwid.bootloader.id;
                        fwid_entry.version.bootloader.ver = p_bank_entry->fwid.bootloader.ver;
                        signature_type = BL_INFO_TYPE_SIGNATURE_BL;
                        flags_entry.flags.bl_intact = true;
                        break;
                    case DFU_TYPE_APP:
                        fwid_entry.version.app.company_id   = p_bank_entry->fwid.app.company_id;
                        fwid_entry.version.app.app_id       = p_bank_entry->fwid.app.app_id;
                        fwid_entry.version.app.app_version  = p_bank_entry->fwid.app.app_version;
                        signature_type = BL_INFO_TYPE_SIGNATURE_APP;
                        flags_entry.flags.app_intact = true;
                        break;
                    default:
                        APP_ERROR_CHECK(NRF_ERROR_INVALID_DATA);
                        return;
                }
                if (!bootloader_info_entry_put(BL_INFO_TYPE_VERSION,
                            &fwid_entry,
                            BL_INFO_LEN_FWID))
                {
                    m_waiting_for_idle = true;
                    return;
                }
                if (p_bank_entry->has_signature)
                {
                    if (!bootloader_info_entry_put(signature_type,
                                (bl_info_entry_t*) p_bank_entry->signature,
                                BL_INFO_LEN_SIGNATURE))
                    {
                        m_waiting_for_idle = true;
                        return;
                    }
                }
                if (!bootloader_info_entry_put(BL_INFO_TYPE_FLAGS,
                            &flags_entry,
                            BL_INFO_LEN_FLAGS))
                {
                    m_waiting_for_idle = true;
                    return;
                }

                /* Update state */
                __LOG("Bank: Set state to FLASHED\n");
                bank_entry_replacement.bank.state = BL_INFO_BANK_STATE_FLASHED;
                bootloader_info_entry_overwrite((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + m_dfu_type), &bank_entry_replacement);

            }
            /* deliberate fallthrough */
        case BL_INFO_BANK_STATE_FLASHED:
            /* We may invalidate the bank entry in the device page now,
               it's all redundant. */
            __LOG("Bank: Invalidate.\n");
            if (bootloader_info_entry_invalidate((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + m_dfu_type)) == NRF_SUCCESS)
            {
                __LOG("Bank invalidated.\n");
                mp_bank_entry = NULL; /* reset the static bank pointer, as we no longer need it. */
            }
            else
            {
                m_waiting_for_idle = true;
            }
            break;

    }
}
bool dfu_bank_is_available(dfu_type_t dfu_type)
{
    bl_info_entry_t* p_entry = bootloader_info_entry_get((bl_info_type_t) (BL_INFO_TYPE_BANK_BASE + dfu_type));
    return (p_entry && p_entry->bank.state == BL_INFO_BANK_STATE_IDLE);
}