/***************************************************************************//**
 * @file
 * @brief Application interface to the bootloader.
 *******************************************************************************
 * # License
 * <b>Copyright 2021 Silicon Laboratories Inc. www.silabs.com</b>
 *******************************************************************************
 *
 * The licensor of this software is Silicon Laboratories Inc.  Your use of this
 * software is governed by the terms of Silicon Labs Master Software License
 * Agreement (MSLA) available at
 * www.silabs.com/about-us/legal/master-software-license-agreement.  This
 * software is distributed to you in Source Code format and is governed by the
 * sections of the MSLA applicable to Source Code.
 *
 ******************************************************************************/

#include "btl_interface.h"
#include "em_core.h"
#include "btl_interface_cfg.h"
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif

#if defined(SL_COMPONENT_CATALOG_PRESENT)
#include "sl_component_catalog.h"
#endif

#if defined(__GNUC__)
extern uint32_t __ResetReasonStart__;
#elif defined(__ICCARM__)
#pragma section = "BOOTLOADER_RESET_REASON"
#endif

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)

// -----------------------------------------------------------------------------
// Configurations

#if ((!defined(BOOTLOADER_DISABLE_NVM3_FAULT_HANDLING) \
  || (defined(BOOTLOADER_DISABLE_NVM3_FAULT_HANDLING)  \
  && (BOOTLOADER_DISABLE_NVM3_FAULT_HANDLING == 0))) && defined(SL_CATALOG_NVM3_PRESENT))
#define BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING
#endif // BOOTLOADER_DISABLE_NVM3_FAULT_HANDLING && SL_CATALOG_NVM3_PRESENT

#if !defined(BOOTLOADER_MANUAL_OVERRIDE_SECURITY_STATE)  \
  || (defined(BOOTLOADER_MANUAL_OVERRIDE_SECURITY_STATE) \
  && (BOOTLOADER_MANUAL_OVERRIDE_SECURITY_STATE == 0))
#define BOOTLOADER_ENABLE_USART_AUTO_DETECTION
#endif // BOOTLOADER_MANUAL_OVERRIDE_SECURITY_STATE

#if (defined(BOOTLOADER_DISABLE_OLD_BOOTLOADER_MITIGATION) \
  && (BOOTLOADER_DISABLE_OLD_BOOTLOADER_MITIGATION == 1))
#define BOOTLOADER_DISABLE_MULTI_TIERED_FALLBACK
#undef BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING
#undef BOOTLOADER_ENABLE_USART_AUTO_DETECTION
#endif // BOOTLOADER_DISABLE_OLD_BOOTLOADER_MITIGATION

#if defined(SL_TRUSTZONE_SECURE)
#undef BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING
#endif // SL_TRUSTZONE_SECURE

// -----------------------------------------------------------------------------
// Configuration spesifics

#if defined(BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING)
// Allocated range of NVM3 IDs for bootloader usage */
#define BL_NVM3_RESERVED_ID  (0x87100UL)

#include <string.h>
#include "nvm3_default.h"

static bool blPPUSATDnStateCacheSet = false;
static uint32_t blPPUSATDnStateCache[2] = { 0 };
#endif // BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING

#if defined(BOOTLOADER_ENABLE_USART_AUTO_DETECTION)
static void preConfigureUsartPPUSATD(void);
static void storeUsartInUse(void);
static int32_t usartNumberSpi = -1;
#endif // BOOTLOADER_ENABLE_USART_AUTO_DETECTION

#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

// -----------------------------------------------------------------------------
// Static variables

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
#if !defined(BOOTLOADER_TEST_UNPRIVILEGED_ACCESS)
static CORE_DECLARE_IRQ_STATE;
#endif
static Bootloader_PPUSATDnCLKENnState_t blPPUSATDnCLKENnState = { 0 };

typedef enum {
  IDLE, SAVE, TIERED
} ppusatdConfigurationState_t;

static ppusatdConfigurationState_t bootloader_ppusatdConfigstate = IDLE;
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

// -----------------------------------------------------------------------------
// Enums

typedef enum {
  RESET, INITIALIZED, DEINITIALIZED
} initState_t;

static initState_t bootloader_InitState = RESET;

// -----------------------------------------------------------------------------
// Test helpers

#if defined(BOOTLOADER_TEST_UNPRIVILEGED_ACCESS)
extern bool enabled;
extern void enterUnprivilegedMode(bool);
extern void exitUnprivilegedMode(void);
#endif // BOOTLOADER_TEST_UNPRIVILEGED_ACCESS

// -----------------------------------------------------------------------------
// Functions

void bootloader_getInfo(BootloaderInformation_t *info)
{
#if defined(BOOTLOADER_HAS_FIRST_STAGE)
  if (!bootloader_pointerToFirstStageValid(firstBootloaderTable)
      || !bootloader_pointerValid(mainBootloaderTable)) {
    // No bootloader is present (first stage or main stage invalid)
    info->type = NO_BOOTLOADER;
    info->capabilities = 0;
  } else if ((firstBootloaderTable->header.type == BOOTLOADER_MAGIC_FIRST_STAGE)
             && (mainBootloaderTable->header.type == BOOTLOADER_MAGIC_MAIN)) {
    info->type = SL_BOOTLOADER;
    info->version = mainBootloaderTable->header.version;
    info->capabilities = mainBootloaderTable->capabilities;
  } else {
    info->type = NO_BOOTLOADER;
    info->capabilities = 0;
  }
#else
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    // No bootloader is present (first stage or main stage invalid)
    info->type = NO_BOOTLOADER;
    info->capabilities = 0;
  } else if (mainBootloaderTable->header.type == BOOTLOADER_MAGIC_MAIN) {
    info->type = SL_BOOTLOADER;
    info->version = mainBootloaderTable->header.version;
    info->capabilities = mainBootloaderTable->capabilities;
  } else {
    info->type = NO_BOOTLOADER;
    info->capabilities = 0;
  }
#endif
}

int32_t bootloader_init(void)
{
  int32_t retVal;
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return BOOTLOADER_ERROR_INIT_TABLE;
  }
  if (mainBootloaderTable->header.type != BOOTLOADER_MAGIC_MAIN) {
    return BOOTLOADER_ERROR_INIT_TABLE;
  }

  if (bootloader_InitState == RESET || bootloader_InitState == DEINITIALIZED) {
#if defined(SL_TRUSTZONE_SECURE)
    if (bootloader_InitState == RESET) {
      // Enable SMU bus clock at the start-up of the TZ secure application
      // to make it possible to configure the SMU peripheral. Since the CMU address
      // is known. Otherwise, delegate the SMU bus clock enablement to the NS application.
#if defined(_CMU_CLKEN1_SMU_MASK)
      CMU->CLKEN1_SET = CMU_CLKEN1_SMU;
#endif // _CMU_CLKEN1_SMU_MASK
    }
#endif // SL_TRUSTZONE_SECURE

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
    bootloader_ppusatdnSaveReconfigureState(&blPPUSATDnCLKENnState);
#endif

#if defined(BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING)
    NVIC_ClearPendingIRQ(SMU_SECURE_IRQn);
    SMU->IF_CLR = SMU_IEN_PPUSEC;
    NVIC_EnableIRQ(SMU_SECURE_IRQn);
    SMU->IEN_SET = SMU_IEN_PPUSEC;
#endif
    retVal = mainBootloaderTable->init();

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
    bootloader_ppusatdnRestoreState(&blPPUSATDnCLKENnState);
#endif

    if (retVal == BOOTLOADER_OK) {
      bootloader_InitState = INITIALIZED;
    }
  } else {
    retVal = BOOTLOADER_OK;
  }
  return retVal;
}

int32_t bootloader_deinit(void)
{
  int32_t retVal;
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return BOOTLOADER_ERROR_INIT_TABLE;
  }
  if (mainBootloaderTable->header.type != BOOTLOADER_MAGIC_MAIN) {
    return BOOTLOADER_ERROR_INIT_TABLE;
  }

  if (bootloader_InitState == INITIALIZED) {
#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
    bootloader_ppusatdnSaveReconfigureState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

#if defined(BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING)
    SMU->IF_CLR = SMU_IEN_PPUSEC;
    SMU->IEN_CLR = SMU_IEN_PPUSEC;
    NVIC_ClearPendingIRQ(SMU_SECURE_IRQn);
    NVIC_DisableIRQ(SMU_SECURE_IRQn);
#endif

    retVal = mainBootloaderTable->deinit();
    if (retVal == BOOTLOADER_OK) {
      bootloader_InitState = DEINITIALIZED;
    }
#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
    bootloader_ppusatdnRestoreState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE
  } else {
    retVal = BOOTLOADER_OK;
  }
  return retVal;
}

BootloaderResetCause_t bootloader_getResetReason(void)
{
#if defined(__GNUC__)
  uint32_t resetReasonBase = (uint32_t)&__ResetReasonStart__;
#elif defined(__ICCARM__)
  void *resetReasonBase =   __section_begin("BOOTLOADER_RESET_REASON");
#endif

  volatile BootloaderResetCause_t *resetCause = (BootloaderResetCause_t *)(resetReasonBase);
  return *resetCause;
}

void bootloader_rebootAndInstall(void)
{
  // Set reset reason to bootloader entry
#if defined(__GNUC__)
  uint32_t resetReasonBase = (uint32_t)&__ResetReasonStart__;
#elif defined(__ICCARM__)
  void *resetReasonBase =   __section_begin("BOOTLOADER_RESET_REASON");
#endif
  BootloaderResetCause_t *resetCause = (BootloaderResetCause_t *) (resetReasonBase);
  resetCause->reason = BOOTLOADER_RESET_REASON_BOOTLOAD;
  resetCause->signature = BOOTLOADER_RESET_SIGNATURE_VALID;
#if defined(RMU_PRESENT)
  // Clear resetcause
  RMU->CMD = RMU_CMD_RCCLR;
  // Trigger a software system reset
  RMU->CTRL = (RMU->CTRL & ~_RMU_CTRL_SYSRMODE_MASK) | RMU_CTRL_SYSRMODE_FULL;
#endif
  NVIC_SystemReset();
}

int32_t bootloader_initParser(BootloaderParserContext_t *context,
                              size_t                    contextSize)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return BOOTLOADER_ERROR_PARSE_FAILED;
  }

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnSaveReconfigureState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  int32_t retVal = mainBootloaderTable->initParser(context, contextSize);

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnRestoreState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  return retVal;
}

int32_t bootloader_parseBuffer(BootloaderParserContext_t   *context,
                               BootloaderParserCallbacks_t *callbacks,
                               uint8_t                     data[],
                               size_t                      numBytes)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return BOOTLOADER_ERROR_PARSE_FAILED;
  }

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnSaveReconfigureState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  int32_t retVal = mainBootloaderTable->parseBuffer(context, callbacks, data, numBytes);

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnRestoreState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  return retVal;
}

int32_t bootloader_parseImageInfo(BootloaderParserContext_t *context,
                                  uint8_t                   data[],
                                  size_t                    numBytes,
                                  ApplicationData_t         *appInfo,
                                  uint32_t                  *bootloaderVersion)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return BOOTLOADER_ERROR_PARSE_FAILED;
  }

  BootloaderInformation_t info = { .type = SL_BOOTLOADER, .version = 0U, .capabilities = 0U };
  bootloader_getInfo(&info);

  uint32_t blMajorVersion = ((info.version & BOOTLOADER_VERSION_MAJOR_MASK)
                             >> BOOTLOADER_VERSION_MAJOR_SHIFT);
  uint32_t blMinorVersion = ((info.version & BOOTLOADER_VERSION_MINOR_MASK)
                             >> BOOTLOADER_VERSION_MINOR_SHIFT);

  if ((blMajorVersion < 1UL) || (blMajorVersion == 1UL && blMinorVersion < 11UL)) {
    return BOOTLOADER_ERROR_PARSE_FAILED;
  }

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnSaveReconfigureState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  int32_t retVal = mainBootloaderTable->parseImageInfo(context, data, numBytes, appInfo, bootloaderVersion);

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnRestoreState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  return retVal;
}

uint32_t bootloader_parserContextSize(void)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return 0UL;
  }

  BootloaderInformation_t info = { .type = SL_BOOTLOADER, .version = 0U, .capabilities = 0U };
  bootloader_getInfo(&info);

  uint32_t blMajorVersion = ((info.version & BOOTLOADER_VERSION_MAJOR_MASK)
                             >> BOOTLOADER_VERSION_MAJOR_SHIFT);
  uint32_t blMinorVersion = ((info.version & BOOTLOADER_VERSION_MINOR_MASK)
                             >> BOOTLOADER_VERSION_MINOR_SHIFT);

  if (blMajorVersion < 1UL) {
    return 384UL;
  }

  if (blMajorVersion == 1UL && blMinorVersion < 11UL) {
#if defined(_SILICON_LABS_32B_SERIES_2)
    if (blMinorVersion == 10UL) {
      return 524UL;
    } else {
      return 384UL;
    }
#else
    return 384UL;
#endif
  }

  return (uint32_t)mainBootloaderTable->parserContextSize();
}

bool bootloader_verifyApplication(uint32_t startAddress)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return false;
  }

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnSaveReconfigureState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  bool retVal = mainBootloaderTable->verifyApplication(startAddress);

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
  bootloader_ppusatdnRestoreState(&blPPUSATDnCLKENnState);
#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE

  return retVal;
}

bool bootloader_secureBootEnforced(void)
{
  BootloaderInformation_t info = { .type = SL_BOOTLOADER, .version = 0U, .capabilities = 0U };
  bootloader_getInfo(&info);

  if (info.capabilities & BOOTLOADER_CAPABILITY_ENFORCE_SECURE_BOOT) {
    return true;
  }
  return false;
}

bool bootloader_getUpgradeLocation(uint32_t *location)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return false;
  }

  BootloaderInformation_t info = { .type = SL_BOOTLOADER, .version = 0U, .capabilities = 0U };
  bootloader_getInfo(&info);

  uint32_t blMajorVersion = ((info.version & BOOTLOADER_VERSION_MAJOR_MASK)
                             >> BOOTLOADER_VERSION_MAJOR_SHIFT);
  uint32_t blMinorVersion = ((info.version & BOOTLOADER_VERSION_MINOR_MASK)
                             >> BOOTLOADER_VERSION_MINOR_SHIFT);

  if (blMajorVersion > 2UL || (blMajorVersion == 2UL && blMinorVersion >= 1UL)) {
    *location = mainBootloaderTable->getUpgradeLocation();
    return true;
  }

  return false;
}

#if !defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
uint32_t bootloader_remainingApplicationUpgrades(void)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return 0UL;
  }

  BootloaderInformation_t info = { .type = SL_BOOTLOADER, .version = 0U, .capabilities = 0U };
  bootloader_getInfo(&info);

  uint32_t blMajorVersion = ((info.version & BOOTLOADER_VERSION_MAJOR_MASK)
                             >> BOOTLOADER_VERSION_MAJOR_SHIFT);
  uint32_t blMinorVersion = ((info.version & BOOTLOADER_VERSION_MINOR_MASK)
                             >> BOOTLOADER_VERSION_MINOR_SHIFT);

  if ((blMajorVersion < 1UL) || (blMajorVersion == 1UL && blMinorVersion < 11UL)) {
    return 0UL;
  }

  return (uint32_t)mainBootloaderTable->remainingApplicationUpgrades();
}
#endif

#if defined(_SILICON_LABS_32B_SERIES_2)
bool bootloader_getCertificateVersion(uint32_t *version)
{
  // Access word 13 to read sl_app_properties of the bootloader.
  ApplicationProperties_t *blProperties =
    (ApplicationProperties_t *)(*(uint32_t *)(BTL_MAIN_STAGE_BASE + 52UL));

  if (!bootloader_pointerValid(blProperties)) {
    return false;
  }

  // Compatibility check of the application properties struct.
  if (((blProperties->structVersion & APPLICATION_PROPERTIES_VERSION_MAJOR_MASK)
       >> APPLICATION_PROPERTIES_VERSION_MAJOR_SHIFT) < 1UL) {
    return false;
  }
  if (((blProperties->structVersion & APPLICATION_PROPERTIES_VERSION_MINOR_MASK)
       >> APPLICATION_PROPERTIES_VERSION_MINOR_SHIFT) < 1UL) {
    return false;
  }

  if (blProperties->cert == NULL) {
    return false;
  }

  *version = blProperties->cert->version;
  return true;
}
#endif // _SILICON_LABS_32B_SERIES_2

#if defined(BOOTLOADER_INTERFACE_TRUSTZONE_AWARE)
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
void bootloader_getPeripheralList(uint32_t *ppusatd0, uint32_t *ppusatd1, uint32_t *ppusatd2)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return;
  }

  BootloaderInformation_t info = { .type = SL_BOOTLOADER, .version = 0U, .capabilities = 0U };
  bootloader_getInfo(&info);

  if (info.capabilities & BOOTLOADER_CAPABILITY_PERIPHERAL_LIST) {
    mainBootloaderTable->getPeripheralList(ppusatd0, ppusatd1, ppusatd2);
  }
}
#else
void bootloader_getPeripheralList(uint32_t *ppusatd0, uint32_t *ppusatd1)
{
  if (!bootloader_pointerValid(mainBootloaderTable)) {
    return;
  }

  BootloaderInformation_t info = { .type = SL_BOOTLOADER, .version = 0U, .capabilities = 0U };
  bootloader_getInfo(&info);

  if (info.capabilities & BOOTLOADER_CAPABILITY_PERIPHERAL_LIST) {
    mainBootloaderTable->getPeripheralList(ppusatd0, ppusatd1);
  }
}
#endif

void bootloader_ppusatdnSaveReconfigureState(Bootloader_PPUSATDnCLKENnState_t *ctx)
{
  if (bootloader_ppusatdConfigstate != IDLE) {
    // This function is called from a bootloader callback function
    bootloader_ppusatdConfigstate = TIERED;
    return;
  }
  uint32_t ppusatd0 = 0u;
  uint32_t ppusatd1 = 0u;
  #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  uint32_t ppusatd2 = 0u;
  #endif

  if (ctx == NULL) {
    return;
  }

  bootloader_ppusatdConfigstate = SAVE;

  // Enter ATOMIC section. The ATOMIC section is exited when
  // the restore function is called.
#if !defined(BOOTLOADER_TEST_UNPRIVILEGED_ACCESS)
  CORE_ENTER_ATOMIC();
#endif

  sli_bootloader_preHook();

#if defined(BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING)
  // Read the stored PPUSATD state from NVM3
  if (!blPPUSATDnStateCacheSet) {
    Ecode_t status;
    status = nvm3_initDefault();
    if (status == ECODE_NVM3_OK) {
      nvm3_ObjectKey_t object_id = BL_NVM3_RESERVED_ID;
      status = nvm3_readData(nvm3_defaultHandle,
                             object_id,
                             blPPUSATDnStateCache,
                             sizeof(blPPUSATDnStateCache));
      if (status == ECODE_NVM3_OK) {
        blPPUSATDnStateCacheSet = true;
      }
    }
  }
#endif // BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING

  // Store the CLKENn states
#if defined(_CMU_CLKEN0_MASK)
  ctx->CLKEN0 = CMU->CLKEN0;
  ctx->CLKEN1 = CMU->CLKEN1;
#endif

#if defined(_CMU_CLKEN1_SMU_MASK)
  CMU->CLKEN1_SET = CMU_CLKEN1_SMU;
#endif // _CMU_CLKEN1_SMU_MASK
  ctx->SMU_STATUS = SMU->STATUS;

  // Unlock SMU before re-configuration
  SMU->LOCK = SMU_LOCK_SMULOCKKEY_UNLOCK;

  // Store the PPUSATDn states
  ctx->PPUSATD0 = SMU->PPUSATD0;
  ctx->PPUSATD1 = SMU->PPUSATD1;
  #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  ctx->PPUSATD2 = SMU->PPUSATD2;
  #endif
  ctx->BMPUSATD0 = SMU->BMPUSATD0;

#if defined(SMU_NS_CFGNS_BASE)
  // Store the PPUPATDn states
  ctx->PPUPATD0 = SMU->PPUPATD0;
  ctx->PPUPATD1 = SMU->PPUPATD1;
  #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  ctx->PPUPATD2 = SMU->PPUPATD2;
  #endif
  SMU->PPUPATD0 = SMU_NS_CFGNS->PPUNSPATD0;
  SMU->PPUPATD1 = SMU_NS_CFGNS->PPUNSPATD1;
  #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  SMU->PPUPATD2 = SMU_NS_CFGNS->PPUNSPATD2;
  #endif
#endif // SMU_NS_CFGNS_BASE

#if defined(CMU_CLKEN0_LDMA)
  CMU->CLKEN0_SET = CMU_CLKEN0_LDMA;
#endif // CMU_CLKEN0_LDMA
#if defined(CRYPTOACC_PRESENT)
  CMU->CLKEN1_SET = CMU_CLKEN1_CRYPTOACC;
#endif

  // Wait for any active transition of other busmasters to finish
  if (SMU->PPUSATD0 & SMU_PPUSATD0_LDMA) {
    while (LDMA_S->STATUS & LDMA_STATUS_ANYBUSY) ;
  } else {
    while (LDMA_NS->STATUS & LDMA_STATUS_ANYBUSY) ;
  }
#if defined(CRYPTOACC_PRESENT)
  if (SMU->PPUSATD1 & SMU_PPUSATD1_CRYPTOACC) {
    #if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_230)
    while (CRYPTOACC_S->DMACTRL_STATUS & (CRYPTOACC_DMACTRL_STATUS_FETCH_BUSY | CRYPTOACC_DMACTRL_STATUS_PUSH_BUSY | CRYPTOACC_DMACTRL_STATUS_SOFT_RST_BUSY)) ;
    #else
    while (CRYPTOACC_S->STATUS & (CRYPTOACC_STATUS_FETCHERBSY | CRYPTOACC_STATUS_PUSHERBSY | CRYPTOACC_STATUS_SOFTRSTBSY)) ;
    #endif
  } else {
    #if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_230)
    while (CRYPTOACC_NS->DMACTRL_STATUS & (CRYPTOACC_DMACTRL_STATUS_FETCH_BUSY | CRYPTOACC_DMACTRL_STATUS_PUSH_BUSY | CRYPTOACC_DMACTRL_STATUS_SOFT_RST_BUSY)) ;
    #else
    while (CRYPTOACC_NS->STATUS & (CRYPTOACC_STATUS_FETCHERBSY | CRYPTOACC_STATUS_PUSHERBSY | CRYPTOACC_STATUS_SOFTRSTBSY)) ;
    #endif
  }
#endif // CRYPTOACC_PRESENT

  // Configure the peripheral secure access state before calling into the bootloader.
#if !defined(BOOTLOADER_DISABLE_MULTI_TIERED_FALLBACK)
#if defined(BOOTLOADER_ENABLE_USART_AUTO_DETECTION)
  // If we do not know which USART is used by the bootloader,
  // and the bootloader is not initialized,
  // Configure PPUSATDn bits for all the USART not in use.
  preConfigureUsartPPUSATD();
#else
  SMU->PPUSATD0_SET = BOOTLOADER_PPUSATD0_MASK;
  SMU->PPUSATD1_SET = BOOTLOADER_PPUSATD1_MASK;
#endif // BOOTLOADER_ENABLE_USART_AUTO_DETECTION

  SMU->PPUSATD0_SET = SMU_PPUSATD0_CMU;
  SMU->PPUSATD0_SET = SMU_PPUSATD0_MSC;
  if (bootloader_getAllocatedDMAChannel() != -1
      && bootloader_getAllocatedDMAChannel() != BOOTLOADER_ERROR_INIT_STORAGE) {
    SMU->PPUSATD0_SET = SMU_PPUSATD0_LDMA;
    SMU->PPUSATD0_SET = SMU_PPUSATD0_LDMAXBAR;
    SMU->BMPUSATD0_SET = SMU_BMPUSATD0_LDMA;
  }

  SMU->PPUSATD0_SET = SMU_PPUSATD0_HFRCO0;
#if !defined(_SILICON_LABS_32B_SERIES_2_CONFIG_5) && !defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6) \
  && !defined(_SILICON_LABS_32B_SERIES_2_CONFIG_8) && !defined(_SILICON_LABS_32B_SERIES_2_CONFIG_9)
  SMU->PPUSATD0_SET = SMU_PPUSATD0_GPIO;
#endif
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  SMU->PPUSATD1_SET = SMU_PPUSATD1_GPCRC;
#else
  SMU->PPUSATD0_SET = SMU_PPUSATD0_GPCRC;
#endif
#if defined(SMU_PPUSATD1_CRYPTOACC)
  SMU->PPUSATD1_SET = SMU_PPUSATD1_CRYPTOACC;
#endif // SMU_PPUSATD1_CRYPTOACC
#if defined(SMU_PPUSATD1_SEMAILBOX)
  SMU->PPUSATD1_SET = SMU_PPUSATD1_SEMAILBOX;
#elif defined(SMU_PPUSATD2_SEMAILBOX)
  SMU->PPUSATD2_SET = SMU_PPUSATD2_SEMAILBOX;
#endif // SMU_PPUSATD1_SEMAILBOX
#elif defined(SL_TRUSTZONE_SECURE)
  #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_5)
  SMU->BMPUSATD0_SET = SMU_BMPUSATD0_LDMA0;
  SMU->BMPUSATD0_SET = SMU_BMPUSATD0_LDMA1;
  #else
  SMU->BMPUSATD0_SET = SMU_BMPUSATD0_LDMA;
  #endif
#endif // BOOTLOADER_DISABLE_MULTI_TIERED_FALLBACK

#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  bootloader_getPeripheralList(&ppusatd0, &ppusatd1, &ppusatd2);
  SMU->PPUSATD0_SET = ppusatd0;
  SMU->PPUSATD1_SET = ppusatd1;
  SMU->PPUSATD2_SET = ppusatd2;
#else
  bootloader_getPeripheralList(&ppusatd0, &ppusatd1);
  SMU->PPUSATD0_SET = ppusatd0;
  SMU->PPUSATD1_SET = ppusatd1;
#endif

#if defined(BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING)
  // Update the peripheral secure access state of
  // the "unknown" peripheral that triggered a fault earlier
  if (blPPUSATDnStateCacheSet == true) {
    SMU->PPUSATD0_SET = blPPUSATDnStateCache[0];
    SMU->PPUSATD1_SET = blPPUSATDnStateCache[1];
  }
#endif // BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING
#if defined(BOOTLOADER_TEST_UNPRIVILEGED_ACCESS)
  enterUnprivilegedMode(enabled);
#endif
}

void bootloader_ppusatdnRestoreState(Bootloader_PPUSATDnCLKENnState_t *ctx)
{
#if defined(BOOTLOADER_TEST_UNPRIVILEGED_ACCESS)
  exitUnprivilegedMode();
#endif

  if (bootloader_ppusatdConfigstate != SAVE) {
    // This function is called from a bootloader callback function
    bootloader_ppusatdConfigstate = SAVE;
    return;
  }

  if (ctx == NULL) {
    return;
  }

  // Wait for any active transition of other busmasters to finish
  if (bootloader_getAllocatedDMAChannel() != -1
      && bootloader_getAllocatedDMAChannel() != BOOTLOADER_ERROR_INIT_STORAGE) {
#if defined(CMU_CLKEN0_LDMA)
    CMU_S->CLKEN0_SET = CMU_CLKEN0_LDMA;
#endif // CMU_CLKEN0_LDMA
    while (LDMA_S->STATUS & LDMA_STATUS_ANYBUSY) ;
  }
#if defined(CRYPTOACC_PRESENT)
  if (SMU->PPUSATD0 & SMU_PPUSATD0_CMU) {
    CMU_S->CLKEN1_SET = CMU_CLKEN1_CRYPTOACC;
  } else {
    CMU_NS->CLKEN1_SET = CMU_CLKEN1_CRYPTOACC;
  }
  #if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_230)
  while (CRYPTOACC_S->DMACTRL_STATUS & (CRYPTOACC_DMACTRL_STATUS_FETCH_BUSY | CRYPTOACC_DMACTRL_STATUS_PUSH_BUSY | CRYPTOACC_DMACTRL_STATUS_SOFT_RST_BUSY)) ;
    #else
  while (CRYPTOACC_S->STATUS & (CRYPTOACC_STATUS_FETCHERBSY | CRYPTOACC_STATUS_PUSHERBSY | CRYPTOACC_STATUS_SOFTRSTBSY)) ;
    #endif
#endif // CRYPTOACC_PRESENT

  SMU->PPUSATD0 = ctx->PPUSATD0;
  SMU->PPUSATD1 = ctx->PPUSATD1;
  #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  SMU->PPUSATD2 = ctx->PPUSATD2;
  #endif
  SMU->BMPUSATD0 = ctx->BMPUSATD0;
#if defined(SMU_NS_CFGNS_BASE)
  SMU->PPUPATD0 = ctx->PPUPATD0;
  SMU->PPUPATD1 = ctx->PPUPATD1;
  #if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_6)
  SMU->PPUPATD2 = ctx->PPUPATD2;
  #endif
#endif // SMU_NS_CFGNS_BASE
  if (ctx->SMU_STATUS & SMU_STATUS_SMULOCK) {
    SMU->LOCK = 0u;
  }

#if defined(BOOTLOADER_ENABLE_USART_AUTO_DETECTION)
  storeUsartInUse();
#endif // BOOTLOADER_ENABLE_USART_AUTO_DETECTION

  // Restore the CLKENn states
#if defined(_CMU_CLKEN0_MASK)
  CMU->CLKEN0 = ctx->CLKEN0;
  CMU->CLKEN1 = ctx->CLKEN1;
#endif

  sli_bootloader_postHook();

#if !defined(BOOTLOADER_TEST_UNPRIVILEGED_ACCESS)
  CORE_EXIT_ATOMIC();
#endif

  // Update the state after the critical section has been
  // exited to ensure that the SMU interrupt gets fired,
  // before the state change happens.
  bootloader_ppusatdConfigstate = IDLE;
}

__attribute__ ((weak)) void sli_bootloader_preHook(void)
{
}

__attribute__ ((weak)) void sli_bootloader_postHook(void)
{
}

#if defined(BOOTLOADER_ENABLE_USART_AUTO_DETECTION)
static void preConfigureUsartPPUSATD(void)
{
  if (usartNumberSpi != -1) {
    #if defined(SMU_PPUSATD1_USART0)
    SMU->PPUSATD1_SET = (SMU_PPUSATD1_USART0 << usartNumberSpi);
    #else
    SMU->PPUSATD0_SET = (SMU_PPUSATD0_USART0 << usartNumberSpi);
    #endif
    return;
  }

  if (bootloader_InitState != RESET) {
    // The USART taken by the bootloader
    // is still unknown, do nothing.
    return;
  }
#if defined(USART0_BASE)
#if defined(CMU_CLKEN0_USART0)
  CMU->CLKEN0_SET = CMU_CLKEN0_USART0;
#endif // CMU_CLKEN0_USART0
#if defined(SL_TRUSTZONE_SECURE)
  if (USART0_NS->EN == _USART_EN_RESETVALUE) {
#else
  if (USART0->EN == _USART_EN_RESETVALUE) {
#endif // SL_TRUSTZONE_SECURE
    #if defined(SMU_PPUSATD1_USART0)
    SMU->PPUSATD1_SET = SMU_PPUSATD1_USART0;
    #else
    SMU->PPUSATD0_SET = SMU_PPUSATD0_USART0;
    #endif
  }
#endif // USART0_BASE

#if defined(USART1_BASE)
#if defined(CMU_CLKEN0_USART1)
  CMU->CLKEN0_SET = CMU_CLKEN0_USART1;
#elif defined(CMU_CLKEN2_USART1)
  CMU->CLKEN2_SET = CMU_CLKEN2_USART1;
#endif
#if defined(SL_TRUSTZONE_SECURE)
  if (USART1_NS->EN == _USART_EN_RESETVALUE) {
#else
  if (USART1->EN == _USART_EN_RESETVALUE) {
#endif // SL_TRUSTZONE_SECURE
    #if defined(SMU_PPUSATD1_USART1)
    SMU->PPUSATD1_SET = SMU_PPUSATD1_USART1;
    #else
    SMU->PPUSATD0_SET = SMU_PPUSATD0_USART1;
    #endif
  }
#endif // USART1_BASE

#if defined(USART2_BASE)
#if defined(CMU_CLKEN0_USART2)
  CMU->CLKEN0_SET = CMU_CLKEN0_USART2;
#elif defined(CMU_CLKEN2_USART2)
  CMU->CLKEN2_SET = CMU_CLKEN2_USART2;
#endif
#if defined(SL_TRUSTZONE_SECURE)
  if (USART2_NS->EN == _USART_EN_RESETVALUE) {
#else
  if (USART2->EN == _USART_EN_RESETVALUE) {
#endif // SL_TRUSTZONE_SECURE
    #if defined(SMU_PPUSATD1_USART2)
    SMU->PPUSATD1_SET = SMU_PPUSATD1_USART2;
    #else
    SMU->PPUSATD0_SET = SMU_PPUSATD0_USART2;
    #endif
  }
#endif // USART2_BASE
}

static void storeUsartInUse(void)
{
  if ((usartNumberSpi != -1) || (bootloader_InitState != RESET)) {
    return;
  }
#if defined(USART0_BASE)
#if defined(SL_TRUSTZONE_SECURE)
  if (USART0_NS->EN & USART_EN_EN) {
#else
  if (USART0->EN & USART_EN_EN) {
#endif // SL_TRUSTZONE_SECURE
    usartNumberSpi = 0;
  }
#endif // USART0_BASE

#if defined(USART1_BASE)
#if defined(SL_TRUSTZONE_SECURE)
  if (USART1_NS->EN & USART_EN_EN) {
#else
  if (USART1->EN & USART_EN_EN) {
#endif // SL_TRUSTZONE_SECURE
    usartNumberSpi = 1;
  }
#endif // USART1_BASE

#if defined(USART2_BASE)
#if defined(SL_TRUSTZONE_SECURE)
  if (USART2_NS->EN & USART_EN_EN) {
#else
  if (USART2->EN & USART_EN_EN) {
#endif // SL_TRUSTZONE_SECURE
    usartNumberSpi = 2;
  }
#endif // USART2_BASE
}
#endif // BOOTLOADER_ENABLE_USART_AUTO_DETECTION

#if defined(BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING)
void SMU_SECURE_IRQHandler(void)
{
#if !defined(BOOTLOADER_TEST_FAULT_HANDLING)
  if (bootloader_ppusatdConfigstate == IDLE) {
    // If none of the bootloader interface operations are active,
    // the fault is not caused by the bootloader. Just park in the while loop.
    while (true) ;
  }
#endif // BOOTLOADER_TEST_FAULT_HANDLING

  Ecode_t status;
  uint32_t PPUSATDn_state[2] = { 0 };
  nvm3_ObjectKey_t object_id = BL_NVM3_RESERVED_ID;

  // First read the pre-existing configuration
  status = nvm3_readData(nvm3_defaultHandle, object_id, PPUSATDn_state, sizeof(PPUSATDn_state));
  if (status == ECODE_NVM3_OK) {
    if (SMU->PPUFS > 31) {
      PPUSATDn_state[1] |= 1 << (SMU->PPUFS - 32);
    } else {
      PPUSATDn_state[0] |= 1 << SMU->PPUFS;
    }
    // Nothing to do to recover if this fails, just continue and perform a reset.
    (void)nvm3_writeData(nvm3_defaultHandle, object_id, PPUSATDn_state, sizeof(PPUSATDn_state));
  } else if (status == ECODE_NVM3_ERR_KEY_NOT_FOUND) {
    if (SMU->PPUFS > 31) {
      PPUSATDn_state[1] = 1 << (SMU->PPUFS - 32);
    } else {
      PPUSATDn_state[0] = 1 << SMU->PPUFS;
    }
    (void)nvm3_writeData(nvm3_defaultHandle, object_id, PPUSATDn_state, sizeof(PPUSATDn_state));
  } else {
    // Do nothing. If NVM3 read fails for an unknown reason,
    // perform a reset to recover from the failure state.
  }

#if defined(__GNUC__)
  uint32_t resetReasonBase = (uint32_t)&__ResetReasonStart__;
#elif defined(__ICCARM__)
  void *resetReasonBase =   __section_begin("BOOTLOADER_RESET_REASON");
#endif
  BootloaderResetCause_t *resetCause = (BootloaderResetCause_t *) (resetReasonBase);
  resetCause->reason = BOOTLOADER_RESET_REASON_FAULT;
  resetCause->signature = BOOTLOADER_RESET_SIGNATURE_VALID;
  NVIC_SystemReset();
}
#endif // BOOTLOADER_ENABLE_NVM3_FAULT_HANDLING

#endif // BOOTLOADER_INTERFACE_TRUSTZONE_AWARE
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
