/**
 * @file    boot_jump.c
 * @brief   Функции для перехода между бутлоадером и основным приложением,
 *          управление ключом BOOT и проверка прошивки.
 *
 *          Основные возможности:
 *          - Настройка вектора прерываний для запуска приложения
 *          - Управление ключом BOOT в Flash
 *          - Проверка корректности прошивки перед прыжком
 *          - Функции прыжка: Bootloader <-> Application
 */
#include "boot_jump.h"


/**
 * @brief  Инициализация приложения.
 *         Устанавливает вектор прерываний на начало основного приложения.
 */
void App_Init(void)
{	
  __disable_irq();
  SCB->VTOR = MAIN_APP_START_ADR;
  __enable_irq();
}

/**
 * @brief  Переход в бутлоадер.
 *         Сбрасывает ключ BOOT и выполняет системный сброс.
 */
void JumpToBootloader(void)
{
  // jump to boot
  ResetKey(); // сброс ключа (не erase, просто битый ключ)
  NVIC_SystemReset(); // сброс и переход в бутлоадер (т.к. нет ключа)
}

/**
 * @brief  Переход к основному приложению.
 *         Настраивает стек и переход к ResetHandler приложения.
 */
void JumpToApplocation(void)
{
  //Деинициализация HAL
  HAL_DeInit();
  
  //Перенос вектора прерываний на начало зашитой программы
  __disable_irq();
  __set_MSP(*((volatile uint32_t*)MAIN_APP_START_ADR));
  __enable_irq();
  
  //Переход к выполнению зашитой программы
  __ASM volatile(
      "ldr r0, [%0, #4]\n"  // r0 = *(MAIN_APP_START_ADR + 4)
      "bx  r0\n"            // переход по адресу в r0
      :
      : "r"(MAIN_APP_START_ADR)
      : "r0"
  );
  //Note: asm потому что при O0 компилятор делал локальные переменные, 
  //      из-за чего при смене стека он не мог получить адрес для прыжка
}

/**
 * @brief  Сброс ключа BOOT в Flash.
 *         Делает ключ «битым», чтобы MCU остался в бутлоадере при следующем перезапуске.
 */
void ResetKey(void)
{	
	HAL_FLASH_Unlock();
	
	HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BOOTLOADER_KEY_ADR, 0);
	
	HAL_FLASH_Lock();	
}

/**
 * @brief  Установка ключа BOOT в Flash.
 *         Указывает, что прошивка записана корректно.
 */
void SetKey(void)
{	
	HAL_FLASH_Unlock();
	
	HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, BOOTLOADER_KEY_ADR, BL_KEY_APP_WRITTEN);
	
	HAL_FLASH_Lock();	
}

/**
 * @brief  Чтение ключа BOOT из Flash.
 * @retval Значение ключа
 */
uint32_t ReadKey(void)
{	
	return (*(__IO uint32_t*)BOOTLOADER_KEY_ADR);
}

/**
 * @brief  Стирание ключа BOOT в Flash (одна страница).
 */
void EraseKey(void)
{	
	FLASH_EraseInitTypeDef EraseInitStruct;
	HAL_FLASH_Unlock();
	uint32_t PageError = 0x00;
	EraseInitStruct.TypeErase   = FLASH_TYPEERASE_PAGES;// erase pages
	EraseInitStruct.PageAddress = BOOTLOADER_KEY_ADR;   //address
	EraseInitStruct.NbPages     = 0x01;// num of erased  pages
	
	HAL_FLASHEx_Erase(&EraseInitStruct, &PageError);
	HAL_FLASH_Lock();
	
}

/**
 * @brief  Проверка валидности прошивки перед переходом к приложению.
 *         Проверяет MSP и ResetHandler.
 * @retval HAL_OK - прошивка валидна
 *         HAL_ERROR - прошивка повреждена или некорректна
 */
HAL_StatusTypeDef Verify_Firmware(void)
{
  uint32_t msp = *((volatile uint32_t*)(MAIN_APP_START_ADR));
  uint32_t reset = *((volatile uint32_t*)(MAIN_APP_START_ADR + 4));

  /* 1) Проверка MSP: должен быть указателем в SRAM */
  if ((msp < SRAM_START_ADR) || (msp > SRAM_END_ADR))
  {
    /* Некорректный стек — прошивка невалидна */
    return HAL_ERROR;
  }

  /* 2) Проверка reset handler:
       - бит0 должен быть 1 (Thumb)
       - адрес без бита0 должен лежать в пределах flash (MAIN_APP_START_ADR .. FLASH_END_ADR)
  */
  if ((reset & 0x1) == 0)
  {
    /* Не Thumb-при-старте — подозрительно */
    return HAL_ERROR;
  }

  uint32_t reset_addr = (reset & (~1U)); /* выравненный адрес */
  if ((reset_addr < FLASH_START_ADR) || (reset_addr > FLASH_END_ADR))
  {
    /* Reset handler вне flash */
    return HAL_ERROR;
  }
  return HAL_OK;
}
