// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

//                               !!!WARNING!!!
//
// Some of the code in this file is run without static initialization expected
// by C/C++. Any accesses to statically initialized objects/variables before
// memory is initialized will result in undefined values and violates the C
// specification. Only code run after memory initialization is complete will be
// compliant and truly safe to run. In general, make early initialization code
// run AFTER memory initialization has completed unless it is ABSOLUTELY
// NECESSARY to modify the way memory is initialized.
//
// When execution begins due to SoC power-on (or the device is reset), three
// key things must happen to properly enter C++ execution context:
//   1. Static variables must be loaded from flash to RAM.
//   2. Zero-initialized variables must be zero-initialized.
//   3. Statically allocated objects must have their constructors run.
// The SoC doesn't inherently have a notion of how to do this, so this is
// handled in StaticMemoryInit();
//
// Following this, execution is handed over to pw_PreMainInit() to facilitate
// platform, project, or application pre-main initialization. When
// pw_PreMainInit() returns, main() is executed.
//
// The simple flow is as follows:
//   1. Power on
//   2. PC (and maybe SP) set (from vector_table by SoC, or by bootloader)
//   3. pw_boot_Entry()
//     3.0. Initialize critical registers (VTOR, SP)
//     3.1. pw_boot_PreStaticMemoryInit();
//     3.2. Static-init memory (.data, .bss)
//     3.3. pw_boot_PreStaticConstructorInit();
//     3.4. Static C++ constructors
//     3.5. pw_boot_PreMainInit()
//     3.6. main()
//     3.7. pw_boot_PostMain()

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "pw_boot/boot.h"
#include "pw_boot_cortex_m/boot.h"
#include "pw_preprocessor/arch.h"
#include "pw_preprocessor/compiler.h"

#if !_PW_ARCH_ARM_CORTEX_M
#error You can only build this for ARM Cortex-M architectures. If you are \
       trying to do this and are still seeing this error, see \
       pw_preprocessor/arch.h
#endif  // !_PW_ARCH_ARM_CORTEX_M

#if !_PW_ARCH_ARM_V6M && !_PW_ARCH_ARM_V7M && !_PW_ARCH_ARM_V7EM && \
    !_PW_ARCH_ARM_V8M_MAINLINE && !_PW_ARCH_ARM_V8_1M_MAINLINE
#error "Your selected Cortex-M arch is not yet supported by this module."
#endif

// Extern symbols provided by linker script.
// These symbols tell us where various memory sections start and end.
extern uint8_t _pw_static_init_ram_start;
extern uint8_t _pw_static_init_ram_end;
extern uint8_t _pw_static_init_flash_start;
extern uint8_t _pw_zero_init_ram_start;
extern uint8_t _pw_zero_init_ram_end;

// Functions called as part of firmware initialization.
void __libc_init_array(void);

// WARNING: Be EXTREMELY careful when running code before this function
// completes. The context before this function violates the C spec
// (Section 6.7.8, paragraph 10 for example, which requires uninitialized static
// values to be zero-initialized).
void StaticMemoryInit(void) {
  // Static-init RAM (load static values into ram, .data section init).
  memcpy(&_pw_static_init_ram_start,
         &_pw_static_init_flash_start,
         &_pw_static_init_ram_end - &_pw_static_init_ram_start);

  // Zero-init RAM (.bss section init).
  memset(&_pw_zero_init_ram_start,
         0,
         &_pw_zero_init_ram_end - &_pw_zero_init_ram_start);
}

// WARNING: This code is run immediately upon boot, and performs initialization
// of RAM. Note that code running before this function finishes memory
// initialization will violate the C spec (Section 6.7.8, paragraph 10 for
// example, which requires uninitialized static values to be zero-initialized).
// Be EXTREMELY careful when running code before this function finishes RAM
// initialization.
//
// This function runs immediately at boot because it is at index 1 of the
// interrupt vector table.
//
// This initial stage is written in assembly in a function with no
// prologue/epilogue because it cannot assume a valid stack pointer has been
// set up.
PW_NO_PROLOGUE
void pw_boot_Entry(void) {
  // No C code allowed here due to PW_NO_PROLOGUE, and don't use extended
  // asm that might try to spill a register to the stack. See
  // https://gcc.gnu.org/onlinedocs/gcc-14.1.0/gcc/ARM-Function-Attributes.html
  asm volatile(
      // Disable interrupts.
      //
      // Until pw_boot_PreStaticMemoryInit() has completed, depending on the
      // bootloader (or lack thereof), there is no guarantee that the vector
      // table has been correctly set up, so it's not safe to run interrupts
      // until after this function returns.
      //
      // Until StaticMemoryInit() has completed, interrupt handlers cannot use
      // either statically initialized RAM or zero initialized RAM. Since most
      // interrupt handlers need one or the other to change system state, it's
      // not safe to run handlers until after this function returns.
      "cpsid i                           \n"

#if _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
      // Set VTOR to the location of the vector table.
      //
      // Devices with a bootloader will often set VTOR after parsing the loaded
      // binary and prior to launching it. When no bootloader is present, or if
      // launched directly from memory after a reset, VTOR will be zero and must
      // be assigned the correct value.
      "ldr r0, =0xE000ED08               \n"
      "ldr r1, =pw_boot_vector_table_addr\n"
      "str r1, [r0]                      \n"

      // Configure MSP and MSPLIM.
      "ldr r0, =pw_boot_stack_high_addr  \n"
      "msr msp, r0                       \n"

      "ldr r0, =pw_boot_stack_low_addr   \n"
      "msr msplim, r0                    \n"
#endif  // _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE

      // We have a stack; proceed to actual C code.
      "b _pw_boot_Entry                  \n");
}

// C continuation of pw_boot_Entry, this cannot be marked static because
// it is only referenced by asm and may be optimized away.
void _pw_boot_Entry(void) {
  // Run any init that must be done before static init of RAM which preps the
  // .data (static values not yet loaded into ram) and .bss sections (not yet
  // zero-initialized).
  pw_boot_PreStaticMemoryInit();

  // Note that code running before this function finishes memory
  // initialization will violate the C spec (Section 6.7.8, paragraph 10 for
  // example, which requires uninitialized static values to be
  // zero-initialized). Be EXTREMELY careful when running code before this
  // function finishes static memory initialization.
  StaticMemoryInit();

  // Reenable interrupts.
  //
  // Care is still required since C++ static constructors have not yet been
  // initialized, however it's a lot less likely that an interrupt handler
  // (which are small and focused) will have an issue there.
  asm volatile("cpsie i");

  // Run any init that must be done before C++ static constructors.
  pw_boot_PreStaticConstructorInit();

  // Call static constructors.
  __libc_init_array();

  // This function is not provided by pw_boot_cortex_m, a platform layer,
  // project, or application is expected to implement it.
  pw_boot_PreMainInit();

  // Run main.
  main();

  // In case main() returns, invoke this hook.
  pw_boot_PostMain();

  PW_UNREACHABLE;
}
