ION is the hardware abstraction layer. It does I/O.

It exposes a set of headers that are implemented for various hardwares (device, simulator).
It is also responsible for setting up the boot environment.

=====
2026 May: compile with make EPSILON_KERNEL=24 for Epsilon 24/25 kernel support
see link110 or EPSILON_KERNEL=19 for led support.

Makefile defines UPSILON_KERNEL for recent Epsilon versions (or use UPSILON_KERNEL19 for old kernels)
It adds kernel.cpp to the files to compile and removes including files in
src, because kernel.cpp redefines all the hardware functions from ion
Loader file is ion/flash110.ld
upsilonkernel.zip contains all modifications to current Upsilon, files are also listed in the archive shellscript.
Compile with ion/link110 or other variants.
Changes: rebased external app and api on khi (including exam mode check using sha256 signatures)
Bug: shift/alpha status not displayed

Notes taken during port
========================
USB
========================
epsilon23: src/device/shared/usb/dfu_xip.cpp
void DFU(DFUParameters parameters) {
  // Configure the kernel to avoid interrupting DFU protocole except on Back key
  Device::USB::willExecuteDFU();
  Device::USB::Calculator::PollAndReset(parameters);
  Device::USB::didExecuteDFU();
}

src/device/shared/drivers/usb_unprivilideged.cpp
#include <ion/usb.h>
#include <shared/regs/otg.h>

using namespace Ion::Device::Regs;

namespace Ion {
namespace USB {

void clearEnumerationInterrupt() { OTG.GINTSTS()->setENUMDNE(true); }

void enable() {
  // Get out of soft-disconnected state
  OTG.DCTL()->setSDIS(false);
}

void disable() { OTG.DCTL()->setSDIS(true); }

}  // namespace USB
}  // namespace Ion


Upsilon:
les fonctions ci-dessus forment une partie de src/device/shared/drivers/usb.cpp, à mettre dans usb_unprivilideged?
DFU ne doit probablement pas pouvoir appeler du code en RAM. Upsilon sans kernel appelle DFU de dfu_relocated.cpp qui copie en ram le code de dfu_xip.cpp et l'appelle. Au lieu d'exécuter du code en RAM, il utilise WriteMemory et EraseSector du bootloader en flash interne pour pouvoir effacer et écrire en flash externe?

========================
ion/src/shared/storage.cpp  __attribute__((weak)) uint32_t staticStorageArea[0xda70/sizeof(uint32_t)] = {0};

Problem: isr_vector_table should be at offset 0x30 in epsilon.A.bin but userland header of Upsilon is too long.
Fixed in Upsilon/ion/src/device/bootloader/platform_info.cpp by inserting the reset handler table into userland_header. ??? Will perhaps require changes in Upsilon boot sequence for N0110 (I hope not since it works like an Epsilon 2x kernel).

Verif: ? slot_info section at begin of ram.
Epsilon24
20000000 g       *ABS*	00000000 SRAMOrigin
20000000 g       *ABS*	00000000 UserlandSRAMOrigin
20000000 g       .data	00000000 _data_section_start_ram
20000000 l    d  .data	00000000 .data
20000000 l     O .data	00000010 Ion::Device::USB::slotInfo()::slotInformation
20000010 g     O .data	00000040 .hidden _ZL7sTurtle.lto_priv.0
20000050 g     O .data	00000001 .hidden Poincare::Internal::Render::s_font
20000051 g       .data	00000000 _data_section_end_ram
20000058 g       .bss	00000000 _bss_section_start_ram
20000058 l    d  .bss	00000000 .bss

Upsilon
20000000 l     O .slot_info	00000010 slotInfo()::slotInformation
20000000 l    d  .slot_info	00000000 .slot_info
20000200 g       .isr_vector_table	00000000 _isr_vector_table_start_ram
20000200 g     O .isr_vector_table	0000000c InitialisationVector
20000200 l    d  .isr_vector_table	00000000 .isr_vector_table
2000020c g       .isr_vector_table	00000000 _isr_vector_table_end_ram
20000210 g       .data	00000000 _data_section_start_ram


Exam mode changes: relocated to 0x903f0000 (offset 400), uses 16 bits instead of 8 (compat with Epsilon 2x if Upsilon is shifted in slotA above Epsilon userland)
*********************************************
Upsilon boot
répertoire bootloader (ne pas confondre avec le fait de compiler un userland pour bootloader)
Se termine par qqchose du genre
 if (Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi())) {
    Bootloader::Boot::bootSlot(Bootloader::Slot::Khi());

donc dans bootloader/boot.cpp, il faudra changer le test si on met Epsilon 90
(bootloader/slots/kernel_header.cpp: isAboveVersion16 du coup il faut probablemetn changer la version de Epsilon. Par exemple mettre 90? mais n'a pas l'air utilisé)
void Boot::bootSlot(Bootloader::Slot s) {
  config()->setSlot(&s);
  if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) {
    // We are trying to boot epsilon, so we check the version and show a warning if needed
    const char * version = s.userlandHeader()->version();
    const char * min = "24.10.1";
    int versionSum = Utility::versionSum(version, strlen(version));
    int minimalVersionTrigger = Utility::versionSum(min, strlen(min));
    if (versionSum >= minimalVersionTrigger) {
      WarningMenu menu = WarningMenu();
      menu.open();
      return;
    }
  }
  bootSelectedSlot();
}

bootloader/slots/kernel_header.cpp: isAboveVersion16 du coup il faut probablemetn changer la version de Epsilon. Par exemple mettre 90?
isValid pour un kernel: 
teste si le header d'un kernel est OK, ils n'ont pas changé.
kernel_header.h
  const uint32_t m_unknown; (en fait sécurité) //0
  const uint32_t m_signature; // 4
  const uint32_t m_header; // 8
  const char m_version[8]; // 0xc
  const char m_patchLevel[8]; // 0x14
  const uint32_t m_footer; // 0x18
  const uint32_t* m_stackPointer; // 0x20
  const void(*m_startPointer)(); // 0x24 reset_handler

userland_header: isValid ne teste que les 4 premiers octets
ensuite c'est trop différent selon les versions.
Omega et Upsilon se repèrent avec des mots de 4 octets à des positions spécifiques
Attention: storage est-il toujours à la même position?
slots/slot_exam_mode.h liste les différentes possibilités de secteur utilisé pour exammode
slot.cpp efface la copie du bootloader qui est en 0x90010000 (N.B. on pourrait s'en servir!!!), secteur 9 de la flash externe.
Au boot, dans bootloader/boot.cpp il y a un test isKernelPatched, qui concerne les noyaux Epsilon mais qui n'a pas l'air utilisé, de même que patchKernel??
  uint32_t origin_isr = s.address() + sizeof(Bootloader::KernelHeader) - sizeof(uint32_t) * 3; // c'est l'adresse de la table de reset du kernel offset 0x20
  if (*(uint32_t *)(origin_isr + sizeof(uint32_t) * 12) == (uint32_t)0x0) {
    // fake epsilon
    return true;
  }
OK, donc ion_main de bootloader/main.cpp commence par tester si un des slots est en mode examen, si oui il boote dessus, sinon il passe à 
__attribute__((noreturn)) void Boot::boot() {
  assert(mode() != BootMode::Unknown);

  Boot::config()->clearSlot();
  Boot::config()->setBooting(false);

  while (true) {
      HomeMenu menu = HomeMenu();
      menu.open(true);
  }
...
}

bootloader/interface/src/menu.cpp

void Bootloader::Menu::open(bool noreturn) {
  showMenu();

  uint64_t scan = 0;
  bool exit = false;

  postOpen();
  
  while(!exit && !m_forced_exit) {
    scan = Ion::Keyboard::scan();
    exit = !handleKey(scan);
    if (noreturn) {
      exit = false;
    }
  }
}


*********************************************
Startup: Upsilon start in kernel.cpp (instead of src/device/n0110/boot/rt0.cpp) -> ion_main in apps/main.cpp
-> AppsContainer::sharedAppsContainer()->run(); in apps/apps_container.cpp
now has circuitbreaker initialization/handling.
If launched on Epsilon, it is assumed that the circuitbreaker was unset in the launcher.
-> Container::run
-> apps/home/controller.cpp 
Le mode exam est géré ici et dans apps/global_preferences.cpp où j'ai modifié l'écriture du mode. Il faudra faire attention à compiler une version de Upsilon pour Epsilon 19 et compatibles (20.1/.2) et une version de Upsilon pour Epsilon 24 et compatibles.
********************************************
Epsilon24
start in epsilon24/ion/src/device/userland/boot/rt0.cpp 
void __attribute__((noinline)) start() {
  Ion::Device::Init::configureRAM(); // copy datasection + zero BSS + c++init
  Ion::Init();
// void Init() {
  Display::Context::SharedContext.init(); // ?
  Storage::FileSystem::sharedFileSystem.init(); // probablement remise a 0 du storage
}
  // Initialize slotInfo to be accessible to Kernel
  Ion::Device::USB::slotInfo();
// SlotInfo* slotInfo() {
  static SlotInfo __attribute__((used)) __attribute__((section(".slot_info")))
  slotInformation;
  return &slotInformation;
}

void SlotInfo::updateUserlandHeader() {
  m_userlandHeaderAddress = Board::userlandHeader();
}

  Ion::ExternalApps::deleteApps(Ion::ExamMode::get().isActive());
  ion_main(0, nullptr);
  abort();
}

apps/main.cpp
void ion_main(int argc, const char* const argv[]) {
  // Initialize TreePool::sharedPool and TreeStack::SharedTreeStack
  Poincare::Init();
  Escher::Init();
  Apps::Init();
  /* s_stackStart must be defined as early as possible to ensure that there
   * cannot be allocated memory pointers before. Otherwise, with MicroPython for
   * example, stack pointer could go backward after initialization and allocated
   * memory pointers could be overlooked during mark procedure. */
  volatile int stackTop;
  Ion::setStackStart((void*)(&stackTop));
  AppsContainer::sharedAppsContainer()->run(); 
  Poincare::Shutdown();
}


AppsContainer::run in Epsilon 2x apps/apps_container.cpp
void AppsContainer::run() {
  window()->setAbsoluteFrame(KDRectScreen);
  Preferences* poincarePreferences = Preferences::SharedPreferences();
  Poincare::ExamMode examMode = poincarePreferences->examMode();
  if (examMode.isActive()) {
    setExamMode(examMode, Poincare::ExamMode());
  } else {
    refreshPreferences();
  }
  Ion::Power::selectStandbyMode(false);
  Ion::Events::setSpinner(true);
  Ion::Display::setScreenshotCallback(ShowCursor);

  /* Setup the home checkpoint so that the exception chekpoint will be
   * reactivated on a home interrupt. This way, the main exception checkpoint
   * will keep the home checkpoint as parent. */
  bool homeInterruptOcurred;
  CircuitBreakerCheckpoint homeCheckpoint(
      Ion::CircuitBreaker::CheckpointType::Home);
  if (CircuitBreakerRun(homeCheckpoint)) {
    homeInterruptOcurred = false;
  } else {
    homeInterruptOcurred = true;
  }

  ExceptionCheckpoint exceptionCheckpoint;
  if (ExceptionRun(exceptionCheckpoint)) {
    if (homeInterruptOcurred) {
      /* Reset backlight and suspend timers here, because a keyboard event has
       * loaded the checkpoint and did not call AppsContainer::dispatchEvent. */
      m_backlightDimmingTimer.reset();
      m_suspendTimer.reset();
      Ion::Backlight::setBrightness(
          GlobalPreferences::SharedGlobalPreferences()->brightnessLevel());
      Ion::Events::setSpinner(true);
      Ion::Display::setScreenshotCallback(ShowCursor);
      m_dfuBetweenEvents = false;
      if (activeApp() && activeApp()->snapshot() == homeAppSnapshot()) {
        dispatchEvent(Ion::Events::Back);
      } else {
        switchToBuiltinApp(homeAppSnapshot());
      }
    } else {
      /* Normal execution. The exception checkpoint must be created before
       * switching to the first app, because the first app might create nodes on
       * the pool. */
      switchToBuiltinApp(initialAppSnapshot());
    }
  } else {
    /* We lock the Poincare pool until the application is destroyed (the pool
     * is then asserted empty). This prevents from allocating new handles
     * with the same identifiers as potential dangling handles (that have
     * lost their nodes in the exception). */
    TreePool::Lock();
    handleRunException();
    TreePool::Unlock();
    activeApp()->displayWarning(I18n::Message::PoolMemoryFull, true);
  }

  Container::run();
  switchToBuiltinApp(nullptr);
}

Epsilon 23 loader file
ion/src/device/userland/drivers/userland_header.cpp
0     m_header(Magic),
4     m_expectedEpsilonVersion{EPSILON_VERSION},
c     m_storageAddressRAM(&Ion::Storage::FileSystem::sharedFileSystem),
10    m_storageSizeRAM(Ion::Storage::FileSystem::k_storageSize),
14    m_externalAppsFlashStart(&_external_apps_flash_start),
18    m_externalAppsFlashEnd(&_external_apps_flash_end),
1c    m_externalAppsRAMStart(&_external_apps_RAM_start),
20    m_externalAppsRAMEnd(&_external_apps_RAM_end),
24    m_deviceNameFlashStart(&_device_name_sector_start),
28    m_deviceNameFlashEnd(&_device_name_sector_end),
2c    m_footer(Magic) {}
30 stack top F8 EF 03 20
34 reset_handler F9 44 07 90
38 draw_string 7D 78 0E 90

Upsilon/ion/src/device/bootloader/platform_info.cpp
class UserlandHeader {
...
  constexpr static uint32_t Magic = 0xDEC0EDFE;
  uint32_t m_header;  // 0
  const char m_expectedEpsilonVersion[8]; // 4
  void * m_storageAddressRAM; // 0xc
  size_t m_storageSizeRAM; // 0x10
  /* We store the range addresses of external apps memory because storing the
   * size is complicated due to c++11 constexpr. */
  uint32_t m_externalAppsFlashStart; // 0x14
  uint32_t m_externalAppsFlashEnd; // 0x18
  uint32_t m_externalAppsRAMStart; // 0x1c
  uint32_t m_externalAppsRAMEnd; // 0x20
  uint32_t m_footer; // 0x24
  uint32_t m_omegaMagicHeader;
  const char m_omegaVersion[16];
  const volatile char m_username[16];
  uint32_t m_omegaMagicFooter;
  uint32_t m_upsilonMagicHeader;
  const char m_UpsilonVersion[16];
  uint32_t m_osType;
  uint32_t m_upsilonMagicFooter;
  uint32_t m_upsilonExtraMagicHeader;
  uint32_t m_recoveryAddress;
  uint32_t m_extraVersion;
  uint32_t m_upsilonExtraMagicFooter;
};
