// (C) 2020 Philip Endecott.
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENCE.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#include <cstdint>

#include "arm.hh"

// The following are defined in the linker script:
extern uint32_t _bss_start;
extern uint32_t _bss_end;
extern uint32_t _data_start;
extern uint32_t _mdata_start;
extern uint32_t _mdata_end;
extern uint32_t stacktop;
extern uint32_t irq_stacktop;
// Note that those are not variables containing the addresses of those things, 
// but rather they are actually AT those locations; take care to use &thing 
// when necessary.

extern int main(int argc, char* argv[]);


// Regarding global initialisation, there is some useful info here:
//   https://wiki.osdev.org/Calling_Global_Constructors
// This implementation is NOT based on that though.  It migh be wise to update 
// this to use that approach.  I'm not sure where my use of the .ctor and .dtor 
// section names came from (maybe JCN?).

typedef void (*void_func_ptr)();
extern void_func_ptr __init_array_start;
extern void_func_ptr __init_array_end;

// _start() is jumped to from the reset vector.

extern "C" {

__attribute__((naked))   // That causes the function prologue and epilogue to be 
                         // omitted; we need that because the normal prologue saves 
                         // registers on the stack, but we don't have a valid stack 
                         // pointer yet.
__attribute__((target("arm")))  // The reset vector jumps to _start in ARM mode, so we 
                                // need this to be ARM code, not thumb.  It also needs 
                                // to be ARM mode so that we can inline the cpsr functions.
void _start()
{
  // Set the stack pointer.
  // Useful reference is the newlib (libgloss) source:
  // https://sourceware.org/git/?p=newlib-cygwin.git;a=blob;f=libgloss/arm/crt0.S;hb=41ae84e6dcd810b9c94f1bbd5e00e6c8a28ccb94
  // We used to try this magic:
  //   volatile register uint32_t* stack asm("sp") = &stacktop;
  // That did work for a while, but then got optimised away.
  set_sp((uint32_t)&stacktop);

  // Set the stack IRQ-mode stack pointer.
  // We do this by changing to IRQ mode, setting sp, and changing back again.
  // We could do the same for the other modes as long as they allow us to 
  // change back - user mode doesn't (IIRC) so in that case there is a trick 
  // involving ldm^ to access the user-mode sp.  (Or a new trick, where you 
  // change mode to "system" which has the same registers as user.)
  // It's not at all clear if this would work if any of the following were 
  // function calls, due to both sp and lr changing.  They are always-inline.
  uint32_t cpsr = get_cpsr();
  auto irq_cpsr = (cpsr & ~0x1f) | 0x12;
  set_cpsr_c(irq_cpsr);
  set_sp((uint32_t)&irq_stacktop);
  set_cpsr_c(cpsr);

  // Newlib sets the "SL" register - stack limit? - to sp - 0x10000.  Do we have 
  // that?  I don't think so.

  // Zero the BSS
  for (uint32_t* ptr = &_bss_start; ptr<&_bss_end; ++ptr) {
    *ptr = 0;
  }

  // Copy mdata to data
  uint32_t* dptr = &_data_start;
  uint32_t* mptr = &_mdata_start;
  while (mptr < &_mdata_end) {
    *dptr = *mptr;
    ++dptr;
    ++mptr;
  }

  for (auto f = &__init_array_start; f < &__init_array_end; ++f) (*f)();

  main(0,0);
  // Behaviour is now undefined; main mustn't return.
}


};  // extern "C"

