diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2173e152392..7f8c4832175 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -117,6 +117,25 @@ jobs: with: name: epsilon-binpack-n0110.tgz path: output/release/device/n0110/binpack-n0110.tgz + bootloader: + runs-on: ubuntu-latest + steps: + - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config + - uses: numworks/setup-arm-toolchain@2020-q2 + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - run: make -j2 bootloader.dfu + - run: make MODEL=bootloader -j2 epsilon.A.dfu epsilon.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.A.dfu epsilon.onboarding.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.update.A.dfu epsilon.onboarding.update.B.dfu + - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.A.dfu epsilon.onboarding.beta.B.dfu + - run: make -j2 binpack + - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz + - uses: actions/upload-artifact@master + with: + name: epsilon-binpack-bootloader.tgz + path: output/release/device/bootloader/binpack-bootloader.tgz windows: runs-on: windows-latest defaults: diff --git a/Makefile b/Makefile index f83a5922f12..67f626c9a71 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,14 @@ include build/toolchain.$(TOOLCHAIN).mak include build/variants.mak include build/helpers.mk -ifeq (${MODEL}, n0110) +ifeq (${MODEL},n0110) apps_list = ${EPSILON_APPS} else - apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) + ifeq (${MODEL},bootloader) + apps_list = ${EPSILON_APPS} + else + apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) + endif endif ifdef FORCE_EXTERNAL @@ -114,6 +118,7 @@ include poincare/Makefile include python/Makefile include escher/Makefile # Executable Makefiles +include bootloader/Makefile include apps/Makefile include build/struct_layout/Makefile include build/scenario/Makefile diff --git a/apps/code/app.h b/apps/code/app.h index 494f5c0f8c7..41f542eb4d4 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -75,7 +75,7 @@ class App : public Shared::InputEventHandlerDelegateApp { VariableBoxController * variableBoxController() { return &m_variableBoxController; } - static constexpr int k_pythonHeapSize = 100000; + static constexpr int k_pythonHeapSize = 99000; private: /* Python delegate: diff --git a/apps/external/app.h b/apps/external/app.h index 8077289f5c2..d1d38487b9f 100644 --- a/apps/external/app.h +++ b/apps/external/app.h @@ -29,7 +29,7 @@ class App : public ::App { MainController m_mainController; StackViewController m_stackViewController; Window * m_window; - static constexpr int k_externalHeapSize = 100000; + static constexpr int k_externalHeapSize = 99000; char m_externalHeap[k_externalHeapSize]; }; diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 00000000000..519a847cda7 --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,21 @@ + +bootloader_src += $(addprefix bootloader/,\ + boot.cpp \ + main.cpp \ + kernel_header.cpp \ + userland_header.cpp \ + slot.cpp \ + interface.cpp \ + jump_to_firmware.s \ + trampoline.cpp \ + usb_desc.cpp \ +) + +bootloader_images = $(addprefix bootloader/, \ + cable.png \ + computer.png \ +) + +bootloader_src += $(filter-out ion/src/device/shared/drivers/usb_desc.cpp,$(ion_src)) $(kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) + +$(eval $(call depends_on_image,bootloader/interface.cpp,$(bootloader_images))) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp new file mode 100644 index 00000000000..1ec899b4a2c --- /dev/null +++ b/bootloader/boot.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +#include + +namespace Bootloader { + +BootMode Boot::mode() { + // We use the exam mode driver as storage for the boot mode + uint8_t mode = Ion::ExamMode::FetchExamMode(); + + if (mode > 3) + return Unknown; + + return (BootMode) mode; +} + +void Boot::setMode(BootMode mode) { + BootMode currentMode = Boot::mode(); + if (currentMode == mode) + return; + + assert(mode != BootMode::Unknown); + int8_t deltaMode = (int8_t)mode - (int8_t)currentMode; + deltaMode = deltaMode < 0 ? deltaMode + 4 : deltaMode; + assert(deltaMode > 0); + Ion::ExamMode::IncrementExamMode(deltaMode); +} + +__attribute__((noreturn)) void Boot::boot() { + assert(mode() != BootMode::Unknown); + + if (!Slot::A().kernelHeader()->isValid() && !Slot::B().kernelHeader()->isValid()) { + // Bootloader if both invalid + bootloader(); + } else if (!Slot::A().kernelHeader()->isValid()) { + // If slot A is invalid and B valid, boot B + setMode(BootMode::SlotB); + Slot::B().boot(); + } else if (!Slot::B().kernelHeader()->isValid()) { + // If slot B is invalid and A valid, boot A + setMode(BootMode::SlotA); + Slot::A().boot(); + } else { + // Both valid, boot the selected one + if (mode() == BootMode::SlotA) { + Slot::A().boot(); + } else if (mode() == BootMode::SlotB) { + Slot::B().boot(); + } + } + + // Achivement unlocked: How Did We Get Here? + bootloader(); +} + +__attribute__ ((noreturn)) void Boot::bootloader() { + for(;;) { + // Draw the interfaces and infos + Bootloader::Interface::draw(); + + // Enable USB + Ion::USB::enable(); + + // Wait for the device to be enumerated + do { + // If we pressed back while waiting, reset. + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::Device::Reset::core(); + } + } while (!Ion::USB::isEnumerated()); + + // Launch the DFU stack, allowing to press Back to quit and reset + Ion::USB::DFU(true); + } +} + +} diff --git a/bootloader/boot.h b/bootloader/boot.h new file mode 100644 index 00000000000..bb5df17c1b1 --- /dev/null +++ b/bootloader/boot.h @@ -0,0 +1,28 @@ +#ifndef BOOTLOADER_BOOT_H +#define BOOTLOADER_BOOT_H + +#include + +namespace Bootloader { + +enum BootMode: uint8_t { + SlotA = 0, + SlotB = 1, + // These modes exists so that you can launch the bootloader from a running slot + // They mean "Launch bootloader then go back to slot X" + SlotABootloader = 2, + SlotBBootloader = 3, + Unknown +}; + +class Boot { +public: + static BootMode mode(); + static void setMode(BootMode mode); + __attribute__ ((noreturn)) static void boot(); + __attribute__ ((noreturn)) static void bootloader(); +}; + +} + +#endif \ No newline at end of file diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp new file mode 100644 index 00000000000..672f86b7e6f --- /dev/null +++ b/bootloader/interface.cpp @@ -0,0 +1,83 @@ + +#include +#include + +#include +#include +#include + +#include "computer.h" +#include "cable.h" + +namespace Bootloader { + +void Interface::drawImage(KDContext* ctx, const Image* image, int offset) { + const uint8_t* data; + size_t size; + size_t pixelBufferSize; + + if (image != nullptr) { + data = image->compressedPixelData(); + size = image->compressedPixelDataSize(); + pixelBufferSize = image->width() * image->height(); + } else { + return; + } + + KDColor pixelBuffer[4000]; + assert(pixelBufferSize <= 4000); + assert(Ion::stackSafe()); // That's a VERY big buffer we're allocating on the stack + + Ion::decompress( + data, + reinterpret_cast(pixelBuffer), + size, + pixelBufferSize * sizeof(KDColor) + ); + + KDRect bounds((320 - image->width()) / 2, offset, image->width(), image->height()); + + ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); +} + +void Interface::draw() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0,0,320,240), KDColorBlack); + drawImage(ctx, ImageStore::Computer, 70); + drawImage(ctx, ImageStore::Cable, 172); + + ctx->drawString("Slot A:", KDPoint(0, 0), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString("Slot B:", KDPoint(0, 13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString("Current:", KDPoint(0, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); + + if (Boot::mode() == BootMode::SlotA) { + ctx->drawString("Slot A", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else if (Boot::mode() == BootMode::SlotB) { + ctx->drawString("Slot B", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } + + Slot slots[2] = {Slot::A(), Slot::B()}; + + for(uint8_t i = 0; i < 2; i++) { + Slot slot = slots[i]; + + if (slot.kernelHeader()->isValid() && slot.userlandHeader()->isValid()) { + if (slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon()) { + ctx->drawString("Upsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString(slot.userlandHeader()->upsilonVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else if (slot.userlandHeader()->isOmega()) { + ctx->drawString("Omega", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString(slot.userlandHeader()->omegaVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else { + ctx->drawString("Epsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + ctx->drawString(slot.userlandHeader()->version(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } + ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(168, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } else { + ctx->drawString("Invalid", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); + } + } + +} + +} diff --git a/bootloader/interface.h b/bootloader/interface.h new file mode 100644 index 00000000000..0a98c2b57b6 --- /dev/null +++ b/bootloader/interface.h @@ -0,0 +1,22 @@ +#ifndef BOOTLOADER_INTERFACE +#define BOOTLOADER_INTERFACE + +#include +#include +#include + +namespace Bootloader { + +class Interface { + +private: + static void drawImage(KDContext* ctx, const Image* image, int offset); + +public: + static void draw(); + +}; + +} + +#endif \ No newline at end of file diff --git a/bootloader/jump_to_firmware.s b/bootloader/jump_to_firmware.s new file mode 100644 index 00000000000..25a52171341 --- /dev/null +++ b/bootloader/jump_to_firmware.s @@ -0,0 +1,9 @@ + +.syntax unified +.section .text.jump_to_firmware +.align 2 +.thumb +.global jump_to_firmware +jump_to_firmware: + msr msp, r0 + bx r1 diff --git a/bootloader/kernel_header.cpp b/bootloader/kernel_header.cpp new file mode 100644 index 00000000000..7dac97a06b5 --- /dev/null +++ b/bootloader/kernel_header.cpp @@ -0,0 +1,25 @@ +#include + +namespace Bootloader { + +const char * KernelHeader::version() const { + return m_version; +} + +const char * KernelHeader::patchLevel() const { + return m_patchLevel; +} + +const bool KernelHeader::isValid() const { + return m_header == Magic && m_footer == Magic; +} + +const uint32_t* KernelHeader::stackPointer() const { + return m_stackPointer; +} + +const void(*KernelHeader::startPointer() const)() { + return m_startPointer; +} + +} diff --git a/bootloader/kernel_header.h b/bootloader/kernel_header.h new file mode 100644 index 00000000000..017036b62a8 --- /dev/null +++ b/bootloader/kernel_header.h @@ -0,0 +1,32 @@ +#ifndef BOOTLOADER_KERNEL_HEADER_H +#define BOOTLOADER_KERNEL_HEADER_H + +#include + +namespace Bootloader { + +class KernelHeader { +public: + const char * version() const; + const char * patchLevel() const; + const bool isValid() const; + + const uint32_t* stackPointer() const; + const void(*startPointer() const)(); + +private: + KernelHeader(); + constexpr static uint32_t Magic = 0xDEC00DF0; + const uint32_t m_unknown; + const uint32_t m_signature; + const uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + const uint32_t m_footer; + const uint32_t* m_stackPointer; + const void(*m_startPointer)(); +}; + +} + +#endif diff --git a/bootloader/main.cpp b/bootloader/main.cpp new file mode 100644 index 00000000000..8e07dc8a1c5 --- /dev/null +++ b/bootloader/main.cpp @@ -0,0 +1,41 @@ + +#include +#include + +#include + +__attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { + // Clear the screen + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorBlack); + // Initialize the backlight + Ion::Backlight::init(); + + // Set the mode to slot A if undefined + if (Bootloader::Boot::mode() == Bootloader::BootMode::Unknown) + Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); + + // Handle rebooting to bootloader + if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotABootloader) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); + Bootloader::Boot::bootloader(); + } else if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotBBootloader) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); + Bootloader::Boot::bootloader(); + } + + uint64_t scan = Ion::Keyboard::scan(); + + // Reset+4 => Launch bootloader + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Four)) { + Bootloader::Boot::bootloader(); + // Reset+1 => Launch slot A + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One)) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); + // Reset+2 => Launch slot B + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two)) { + Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); + } + + // Boot the firmware + Bootloader::Boot::boot(); +} diff --git a/bootloader/slot.cpp b/bootloader/slot.cpp new file mode 100644 index 00000000000..b23655aedef --- /dev/null +++ b/bootloader/slot.cpp @@ -0,0 +1,33 @@ +#include +#include + +extern "C" void jump_to_firmware(const uint32_t* stackPtr, const void(*startPtr)(void)); + +namespace Bootloader { + +const Slot Slot::A() { + return Slot(0x90000000); +} + +const Slot Slot::B() { + return Slot(0x90400000); +} + +const KernelHeader* Slot::kernelHeader() const { + return m_kernelHeader; +} + +const UserlandHeader* Slot::userlandHeader() const { + return m_userlandHeader; +} + +[[ noreturn ]] void Slot::boot() const { + // Configure the MPU for the booted firmware + Ion::Device::Board::bootloaderMPU(); + + // Jump + jump_to_firmware(kernelHeader()->stackPointer(), kernelHeader()->startPointer()); + for(;;); +} + +} diff --git a/bootloader/slot.h b/bootloader/slot.h new file mode 100644 index 00000000000..15a883f3948 --- /dev/null +++ b/bootloader/slot.h @@ -0,0 +1,33 @@ +#ifndef BOOTLOADER_SLOT_H +#define BOOTLOADER_SLOT_H + +#include + +#include "kernel_header.h" +#include "userland_header.h" + +namespace Bootloader { + +class Slot { + +public: + Slot(uint32_t address) : + m_kernelHeader(reinterpret_cast(address)), + m_userlandHeader(reinterpret_cast(address + 64 * 1024)) { } + + const KernelHeader* kernelHeader() const; + const UserlandHeader* userlandHeader() const; + [[ noreturn ]] void boot() const; + + static const Slot A(); + static const Slot B(); + +private: + const KernelHeader* m_kernelHeader; + const UserlandHeader* m_userlandHeader; + +}; + +} + +#endif \ No newline at end of file diff --git a/bootloader/trampoline.cpp b/bootloader/trampoline.cpp new file mode 100644 index 00000000000..4d1013f3491 --- /dev/null +++ b/bootloader/trampoline.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +#include +#include + +namespace Bootloader { + +void __attribute__((noinline)) suspend() { + Ion::Device::Power::internalFlashSuspend(true); +} + +void* Trampolines[TRAMPOLINES_COUNT] + __attribute__((section(".trampolines_table"))) + __attribute__((used)) + = { + (void*) Bootloader::suspend, // Suspend + (void*) Ion::Device::ExternalFlash::EraseSector, // External erase + (void*) Ion::Device::ExternalFlash::WriteMemory, // External write + (void*) memcmp, + (void*) memcpy, + (void*) memmove, + (void*) memset, + (void*) strchr, + (void*) strcmp, + (void*) strlcat, + (void*) strlcpy, + (void*) strlen, + (void*) strncmp +}; + +void* CustomTrampolines[CUSTOM_TRAMPOLINES_COUNT] + __attribute__((section(".custom_trampolines_table"))) + __attribute__((used)) + = { + (void*) Bootloader::Boot::mode, + (void*) Bootloader::Boot::setMode +}; + +} diff --git a/bootloader/trampoline.h b/bootloader/trampoline.h new file mode 100644 index 00000000000..0bff7a6d284 --- /dev/null +++ b/bootloader/trampoline.h @@ -0,0 +1,14 @@ +#ifndef BOOTLOADER_TRAMPOLINE_H +#define BOOTLOADER_TRAMPOLINE_H + +namespace Bootloader { + +#define TRAMPOLINES_COUNT 13 +extern void* Trampolines[TRAMPOLINES_COUNT]; + +#define CUSTOM_TRAMPOLINES_COUNT 2 +extern void* CustomTrampolines[CUSTOM_TRAMPOLINES_COUNT]; + +} + +#endif \ No newline at end of file diff --git a/bootloader/usb_desc.cpp b/bootloader/usb_desc.cpp new file mode 100644 index 00000000000..be49306000b --- /dev/null +++ b/bootloader/usb_desc.cpp @@ -0,0 +1,12 @@ + +namespace Ion { +namespace Device { +namespace USB { + +const char* stringDescriptor() { + return "@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg"; +} + +} +} +} diff --git a/bootloader/userland_header.cpp b/bootloader/userland_header.cpp new file mode 100644 index 00000000000..5108b187dfa --- /dev/null +++ b/bootloader/userland_header.cpp @@ -0,0 +1,33 @@ +#include + +namespace Bootloader { + +const UserlandHeader* s_UserlandHeaderA = reinterpret_cast(0x90010000); +const UserlandHeader* s_UserlandHeaderB = reinterpret_cast(0x90410000); + +const char * UserlandHeader::version() const { + return m_expectedEpsilonVersion; +} + +const bool UserlandHeader::isValid() const { + return m_header == Magic && m_footer == Magic; +} + +const bool UserlandHeader::isOmega() const { + return m_ohm_header == OmegaMagic && m_ohm_footer == OmegaMagic; +} + + +const char * UserlandHeader::omegaVersion() const { + return m_omegaVersion; +} + +const bool UserlandHeader::isUpsilon() const { + return m_ups_header == UpsilonMagic && m_ups_footer == UpsilonMagic; +} + +const char * UserlandHeader::upsilonVersion() const { + return m_UpsilonVersion; +} + +} diff --git a/bootloader/userland_header.h b/bootloader/userland_header.h new file mode 100644 index 00000000000..97f4ff5e647 --- /dev/null +++ b/bootloader/userland_header.h @@ -0,0 +1,50 @@ +#ifndef BOOTLOADER_USERLAND_HEADER_H +#define BOOTLOADER_USERLAND_HEADER_H + +#include +#include +#include + +namespace Bootloader { + +class UserlandHeader { +public: + const char * version() const; + const bool isValid() const; + const bool isOmega() const; + const char * omegaVersion() const; + const bool isUpsilon() const; + const char * upsilonVersion() const; + +private: + UserlandHeader(); + constexpr static uint32_t Magic = 0xDEC0EDFE; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + uint32_t m_header; + const char m_expectedEpsilonVersion[8]; + void * m_storageAddressRAM; + size_t m_storageSizeRAM; + /* We store the range addresses of external apps memory because storing the + * size is complicated due to c++11 constexpr. */ + uint32_t m_externalAppsFlashStart; + uint32_t m_externalAppsFlashEnd; + uint32_t m_externalAppsRAMStart; + uint32_t m_externalAppsRAMEnd; + uint32_t m_footer; + uint32_t m_ohm_header; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_ohm_footer; + uint32_t m_ups_header; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_ups_footer; +}; + +extern const UserlandHeader* s_userlandHeaderA; +extern const UserlandHeader* s_userlandHeaderB; + +} + +#endif diff --git a/build/config.mak b/build/config.mak index ee15ea61cf1..502989a91a1 100644 --- a/build/config.mak +++ b/build/config.mak @@ -5,7 +5,7 @@ DEBUG ?= 0 HOME_DISPLAY_EXTERNALS ?= 1 EPSILON_VERSION ?= 15.5.0 -OMEGA_VERSION ?= 1.23.0 +OMEGA_VERSION ?= 2.0.0 # OMEGA_USERNAME ?= N/A OMEGA_STATE ?= public EPSILON_APPS ?= calculation rpn graph code statistics probability solver atomic sequence regression settings external diff --git a/build/platform.device.bootloader.mak b/build/platform.device.bootloader.mak new file mode 100644 index 00000000000..256dd180d59 --- /dev/null +++ b/build/platform.device.bootloader.mak @@ -0,0 +1,3 @@ +TOOLCHAIN ?= arm-gcc-m7f +ION_KEYBOARD_LAYOUT = layout_B3 +PCB_LATEST = 343 # PCB version 3.43 diff --git a/build/rules.mk b/build/rules.mk index 050c4b805c1..e6faac45035 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -32,13 +32,13 @@ $(eval $(call rule_for, \ $(eval $(call rule_for, \ OBJCOPY, %.hex, %.elf, \ - $$(OBJCOPY) -O ihex $$< $$@, \ + $$(OBJCOPY) -R .slot_info -O ihex $$< $$@, \ local \ )) $(eval $(call rule_for, \ OBJCOPY, %.bin, %.elf, \ - $$(OBJCOPY) -O binary $$< $$@, \ + $$(OBJCOPY) -R .slot_info -O binary $$< $$@, \ local \ )) diff --git a/build/targets.device.bootloader.mak b/build/targets.device.bootloader.mak new file mode 100644 index 00000000000..48466b778c3 --- /dev/null +++ b/build/targets.device.bootloader.mak @@ -0,0 +1,55 @@ + +epsilon_flavors_bootloader = $(foreach floavor,$(epsilon_flavors),$(floavor).A $(floavor).B) + +define rule_for_epsilon_flavor_bootloader +$$(BUILD_DIR)/epsilon.$(1).A.$$(EXE): $$(call flavored_object_for,$$(epsilon_src),$(1)) +$$(BUILD_DIR)/epsilon.$(1).A.$$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.A.ld +$$(BUILD_DIR)/epsilon.$(1).B.$$(EXE): $$(call flavored_object_for,$$(epsilon_src),$(1)) +$$(BUILD_DIR)/epsilon.$(1).B.$$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.B.ld +$$(BUILD_DIR)/epsilon.$(1).bin: $$(BUILD_DIR)/epsilon.$(1).A.bin $$(BUILD_DIR)/epsilon.$(1).B.bin + @echo "COMBINE $$@" + $(Q) cat $$(BUILD_DIR)/epsilon.$(1).A.bin >> $$(BUILD_DIR)/epsilon.$(1).bin + $(Q) truncate -s 4MiB $$(BUILD_DIR)/epsilon.$(1).bin + $(Q) cat $$(BUILD_DIR)/epsilon.$(1).B.bin >> $$(BUILD_DIR)/epsilon.$(1).bin + $(Q) truncate -s 8MiB $$(BUILD_DIR)/epsilon.$(1).bin +endef + +$(BUILD_DIR)/epsilon.A.$(EXE): $(call flavored_object_for,$(epsilon_src)) +$(BUILD_DIR)/epsilon.A.$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.A.ld + +$(BUILD_DIR)/epsilon.B.$(EXE): $(call flavored_object_for,$(epsilon_src)) +$(BUILD_DIR)/epsilon.B.$(EXE): LDSCRIPT = ion/src/device/bootloader/bootloader.B.ld + +$(BUILD_DIR)/epsilon.bin: $(BUILD_DIR)/epsilon.A.bin $(BUILD_DIR)/epsilon.B.bin + @echo "COMBINE $@" + $(Q) cat $(BUILD_DIR)/epsilon.A.bin >> $(BUILD_DIR)/epsilon.bin + $(Q) truncate -s 4MiB $(BUILD_DIR)/epsilon.bin + $(Q) cat $(BUILD_DIR)/epsilon.B.bin >> $(BUILD_DIR)/epsilon.bin + $(Q) truncate -s 8MiB $(BUILD_DIR)/epsilon.bin + +$(foreach flavor,$(epsilon_flavors),$(eval $(call rule_for_epsilon_flavor_bootloader,$(flavor)))) + + +HANDY_TARGETS = $(foreach flavor,$(epsilon_flavors_bootloader),epsilon.$(flavor)) +HANDY_TARGETS += epsilon.A epsilon.B + +.PHONY: epsilon +epsilon: $(BUILD_DIR)/epsilon.onboarding.bin +.DEFAULT_GOAL := epsilon + +.PHONY: %_flash +%_flash: $(BUILD_DIR)/%.dfu + @echo "DFU $@" + @echo "INFO About to flash your device. Please plug your device to your computer" + @echo " using an USB cable and press at the same time the 6 key and the RESET" + @echo " button on the back of your device." + $(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done + $(Q) $(PYTHON) build/device/dfu.py -u $(word 1,$^) + +.PHONY: binpack +binpack: $(BUILD_DIR)/epsilon.onboarding.bin + rm -rf $(BUILD_DIR)/binpack + mkdir -p $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.bin $(BUILD_DIR)/binpack + cd $(BUILD_DIR) && for binary in epsilon.onboarding.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* diff --git a/build/targets.device.mak b/build/targets.device.mak index 9498371c430..891b1d097ad 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -54,21 +54,3 @@ $(BUILD_DIR)/bench.ram.$(EXE): LDFLAGS += -Lion/src/$(PLATFORM)/bench $(BUILD_DIR)/bench.ram.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld $(BUILD_DIR)/bench.flash.$(EXE): $(call flavored_object_for,$(bench_src),consoleuart usbxip) $(BUILD_DIR)/bench.flash.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/internal_flash.ld - -.PHONY: %.two_binaries -%.two_binaries: %.elf - @echo "Building an internal and an external binary for $<" - $(Q) $(OBJCOPY) -O binary -j .text.external -j .rodata.external -j .exam_mode_buffer $< $(basename $<).external.bin - $(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin - @echo "Padding $(basename $<).external.bin and $(basename $<).internal.bin" - $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin - $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin - -.PHONY: binpack -binpack: $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/epsilon.onboarding.two_binaries - rm -rf $(BUILD_DIR)/binpack - mkdir -p $(BUILD_DIR)/binpack - cp $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/binpack - cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack - cd $(BUILD_DIR) && for binary in flasher.light.bin epsilon.onboarding.internal.bin epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done - cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* diff --git a/build/targets.device.n0100.mak b/build/targets.device.n0100.mak index aff59a280fc..ae70afbdbf2 100644 --- a/build/targets.device.n0100.mak +++ b/build/targets.device.n0100.mak @@ -5,3 +5,18 @@ @echo " using an USB cable and press the RESET button the back of your device." $(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done $(Q) $(PYTHON) build/device/dfu.py -m -u $< + +.PHONY: %.two_binaries +%.two_binaries: %.elf + @echo "Building an internal binary for $<" + $(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin + @echo "Padding $(basename $<).internal.bin" + $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin + +.PHONY: binpack +binpack: $(BUILD_DIR)/epsilon.onboarding.two_binaries + rm -rf $(BUILD_DIR)/binpack + mkdir -p $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/binpack + cd $(BUILD_DIR) && for binary in epsilon.onboarding.internal.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index 77bc1bf8ce8..687fb7a589f 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,21 +1,38 @@ -HANDY_TARGETS += test.external_flash.write test.external_flash.read +HANDY_TARGETS += test.external_flash.write test.external_flash.read bootloader $(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src) $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src)) $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src)) +.PHONY: bootloader +bootloader: $(BUILD_DIR)/bootloader.bin +$(BUILD_DIR)/bootloader.$(EXE): $(call flavored_object_for,$(bootloader_src),usbxip) +$(BUILD_DIR)/bootloader.$(EXE): LDSCRIPT = ion/src/device/n0110/internal_flash.ld + .PHONY: %_flash -%_flash: $(BUILD_DIR)/%.dfu $(BUILD_DIR)/flasher.light.dfu +%_flash: $(BUILD_DIR)/%.dfu @echo "DFU $@" @echo "INFO About to flash your device. Please plug your device to your computer" @echo " using an USB cable and press at the same time the 6 key and the RESET" @echo " button on the back of your device." $(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done - $(eval DFU_SLAVE := $(shell $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11")) - $(Q) if expr "$(DFU_SLAVE)" : ".*0483:df11.*" > /dev/null; \ - then \ - $(PYTHON) build/device/dfu.py -u $(word 2,$^); \ - sleep 2; \ - fi $(Q) $(PYTHON) build/device/dfu.py -u $(word 1,$^) + +.PHONY: %.two_binaries +%.two_binaries: %.elf + @echo "Building an internal and an external binary for $<" + $(Q) $(OBJCOPY) -O binary -j .text.external -j .rodata.external -j .exam_mode_buffer $< $(basename $<).external.bin + $(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin + @echo "Padding $(basename $<).external.bin and $(basename $<).internal.bin" + $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin + $(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin + +.PHONY: binpack +binpack: $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/epsilon.onboarding.two_binaries + rm -rf $(BUILD_DIR)/binpack + mkdir -p $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack + cd $(BUILD_DIR) && for binary in flasher.light.bin epsilon.onboarding.internal.bin epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* diff --git a/ion/Makefile b/ion/Makefile index 45461e1c793..8bcef599e72 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -22,7 +22,7 @@ include ion/src/shared/tools/Makefile # char test[4]= "ab"; is valid and should initialize test to 'a','b',0,0). # Older versions of GCC are not conformant so we resort to an initializer list. initializer_list = $(shell echo $(1) | sed "s/\(.\)/'\1',/g")0 -$(call object_for,ion/src/shared/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))" +$(call object_for,ion/src/simulator/platform_info.cpp ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp ion/src/simulator/shared/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))" ion_src += $(addprefix ion/src/shared/, \ console_line.cpp \ @@ -31,7 +31,6 @@ ion_src += $(addprefix ion/src/shared/, \ events.cpp \ events_keyboard.cpp \ events_modifier.cpp \ - platform_info.cpp \ rtc.cpp \ stack_position.cpp \ storage.cpp \ diff --git a/ion/include/ion.h b/ion/include/ion.h index 854c3c5394f..ce44f84a36c 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -39,6 +39,7 @@ const char * omegaVersion(); const char * patchLevel(); const char * fccId(); const char * pcbVersion(); +void updateSlotInfo(); // CRC32 : non xor-ed, non reversed, direct, polynomial 4C11DB7 uint32_t crc32Word(const uint32_t * data, size_t length); // Only accepts whole 32bit values diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 9636904406f..cc5594242de 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -4,7 +4,7 @@ include ion/src/device/bench/Makefile include ion/src/device/flasher/Makefile include ion/src/device/$(MODEL)/Makefile -$(call object_for,ion/src/shared/platform_info.cpp): SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" +$(call object_for,ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp): SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" ifeq ($(EPSILON_TELEMETRY),1) ion_src += ion/src/shared/telemetry_console.cpp diff --git a/ion/src/device/bootloader/Makefile b/ion/src/device/bootloader/Makefile new file mode 100644 index 00000000000..c5d7208f7ab --- /dev/null +++ b/ion/src/device/bootloader/Makefile @@ -0,0 +1,19 @@ + +ion_device_src += $(addprefix ion/src/device/bootloader/drivers/, \ + board.cpp \ + cache.cpp \ + external_flash_tramp.cpp \ + led.cpp \ + power.cpp \ + reset.cpp \ + trampoline.cpp \ + usb.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/bootloader/boot/, \ + rt0.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/bootloader/, \ + platform_info.cpp \ +) diff --git a/ion/src/device/bootloader/boot/rt0.cpp b/ion/src/device/bootloader/boot/rt0.cpp new file mode 100644 index 00000000000..a631356bbab --- /dev/null +++ b/ion/src/device/bootloader/boot/rt0.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*cxx_constructor)(); + +extern "C" { + extern char _data_section_start_flash; + extern char _data_section_start_ram; + extern char _data_section_end_ram; + extern char _bss_section_start_ram; + extern char _bss_section_end_ram; + extern cxx_constructor _init_array_start; + extern cxx_constructor _init_array_end; + + extern char _isr_vector_table_start_flash; + extern char _isr_vector_table_start_ram; + extern char _isr_vector_table_end_ram; +} + +void __attribute__((noinline)) abort() { +#ifdef NDEBUG + Ion::Device::Reset::core(); +#else + while (1) { + } +#endif +} + +/* In order to ensure that this method is execute from the external flash, we + * forbid inlining it.*/ + +static void __attribute__((noinline)) external_flash_start() { + /* Init the peripherals. We do not initialize the backlight in case there is + * an on boarding app: indeed, we don't want the user to see the LCD tests + * happening during the on boarding app. The backlight will be initialized + * after the Power-On Self-Test if there is one or before switching to the + * home app otherwise. */ + Ion::Device::Board::initPeripherals(false); + + return ion_main(0, nullptr); +} + +/* This additional function call 'jump_to_external_flash' serves two purposes: + * - By default, the compiler is free to inline any function call he wants. If + * the compiler decides to inline some functions that make use of VFP + * registers, it will need to push VFP them onto the stack in calling + * function's prologue. + * Problem: in start()'s prologue, we would never had a chance to enable the + * FPU since this function is the first thing called after reset. + * We can safely assume that neither memcpy, memset, nor any Ion::Device::init* + * method will use floating-point numbers, but ion_main very well can. + * To make sure ion_main's potential usage of VFP registers doesn't bubble-up to + * start(), we isolate it in its very own non-inlined function call. + * - To avoid jumping on the external flash when it is shut down, we ensure + * there is no symbol references from the internal flash to the external + * flash except this jump. In order to do that, we isolate this + * jump in a symbol that we link in a special section separated from the + * internal flash section. We can than forbid cross references from the + * internal flash to the external flash. */ + +static void __attribute__((noinline)) jump_to_external_flash() { + external_flash_start(); +} + +/* When 'start' is executed, the external flash is supposed to be shutdown. We + * thus forbid inlining to prevent executing this code from external flash + * (just in case 'start' was to be called from the external flash). */ + +void __attribute__((noinline)) start() { + /* This is where execution starts after reset. + * Many things are not initialized yet so the code here has to pay attention. */ + + /* Initialize the FPU as early as possible. + * For example, static C++ objects are very likely to manipulate float values */ + Ion::Device::Board::initFPU(); + + /* Copy data section to RAM + * The data section is R/W but its initialization value matters. It's stored + * in Flash, but linked as if it were in RAM. Now's our opportunity to copy + * it. Note that until then the data section (e.g. global variables) contains + * garbage values and should not be used. */ + size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram); + memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength); + + /* Zero-out the bss section in RAM + * Until we do, any uninitialized global variable will be unusable. */ + size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram); + memset(&_bss_section_start_ram, 0, bssSectionLength); + + /* Call static C++ object constructors + * The C++ compiler creates an initialization function for each static object. + * The linker then stores the address of each of those functions consecutively + * between _init_array_start and _init_array_end. So to initialize all C++ + * static objects we just have to iterate between theses two addresses and + * call the pointed function. */ +#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 +#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS + for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { + (*c)(); + } +#else + /* In practice, static initialized objects are a terrible idea. Since the init + * order is not specified, most often than not this yields the dreaded static + * init order fiasco. How about bypassing the issue altogether? */ + if (&_init_array_start != &_init_array_end) { + abort(); + } +#endif + + /* Copy isr_vector_table section to RAM + * The isr table must be within the memory mapped by the microcontroller (it + * can't live in the external flash). */ + size_t isrSectionLength = (&_isr_vector_table_end_ram - &_isr_vector_table_start_ram); + memcpy(&_isr_vector_table_start_ram, &_isr_vector_table_start_flash, isrSectionLength); + + Ion::Device::Board::init(); + + /* At this point, we initialized clocks and the external flash but no other + * peripherals. */ + + jump_to_external_flash(); + + abort(); +} + +void __attribute__((interrupt, noinline)) isr_systick() { + auto t = Ion::Device::Timing::MillisElapsed; + t++; + Ion::Device::Timing::MillisElapsed = t; +} diff --git a/ion/src/device/bootloader/bootloader.A.ld b/ion/src/device/bootloader/bootloader.A.ld new file mode 100644 index 00000000000..3e1188476d2 --- /dev/null +++ b/ion/src/device/bootloader/bootloader.A.ld @@ -0,0 +1,28 @@ +/* Linker script + * The role of this script is to take all the object files built by the compiler + * and produce a single binary suitable for execution. + * Without an explicit linker script, the linker will produce a binary file that + * would not match some of our requirements (for example, we want the code to be + * written at a specific address (in Flash ROM) and the data at another. */ + +/* Let's instruct the linker about our memory layout. + * This will let us use shortcuts such as ">FLASH" to ask for a given section to + * be stored in Flash. */ + +MEMORY { + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + FLASH (rx) : ORIGIN = 0x90000000, LENGTH = 4M + /* + ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + SRAM1 (rwx) : ORIGIN = 0x20010000, LENGTH = 176K + SRAM2 (rwx) : ORIGIN = 0x2003C000, LENGTH = 16K + */ +} + +STACK_SIZE = 32K; +FIRST_FLASH_SECTOR_SIZE = 4K; +SIGNED_PAYLOAD_LENGTH = 8; +USERLAND_OFFSET = 64K; + +INCLUDE ion/src/device/bootloader/bootloader_common.ld; diff --git a/ion/src/device/bootloader/bootloader.B.ld b/ion/src/device/bootloader/bootloader.B.ld new file mode 100644 index 00000000000..f89ebca2f21 --- /dev/null +++ b/ion/src/device/bootloader/bootloader.B.ld @@ -0,0 +1,28 @@ +/* Linker script + * The role of this script is to take all the object files built by the compiler + * and produce a single binary suitable for execution. + * Without an explicit linker script, the linker will produce a binary file that + * would not match some of our requirements (for example, we want the code to be + * written at a specific address (in Flash ROM) and the data at another. */ + +/* Let's instruct the linker about our memory layout. + * This will let us use shortcuts such as ">FLASH" to ask for a given section to + * be stored in Flash. */ + +MEMORY { + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + FLASH (rx) : ORIGIN = 0x90400000, LENGTH = 4M + /* + ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + SRAM1 (rwx) : ORIGIN = 0x20010000, LENGTH = 176K + SRAM2 (rwx) : ORIGIN = 0x2003C000, LENGTH = 16K + */ +} + +STACK_SIZE = 32K; +FIRST_FLASH_SECTOR_SIZE = 4K; +SIGNED_PAYLOAD_LENGTH = 8; +USERLAND_OFFSET = 64K; + +INCLUDE ion/src/device/bootloader/bootloader_common.ld; diff --git a/ion/src/device/bootloader/bootloader_common.ld b/ion/src/device/bootloader/bootloader_common.ld new file mode 100644 index 00000000000..e8f0c9bbc3f --- /dev/null +++ b/ion/src/device/bootloader/bootloader_common.ld @@ -0,0 +1,128 @@ + +SECTIONS { + .signed_payload_prefix ORIGIN(FLASH) : { + FILL(0xFF); + BYTE(0xFF) + . = ORIGIN(FLASH) + SIGNED_PAYLOAD_LENGTH; + } >FLASH + + .kernel_header : { + KEEP(*(.kernel_header)) + } >FLASH + + .slot_info : { + *(.slot_info*) + } >SRAM + + .isr_vector_table ORIGIN(SRAM) + 512 : AT(ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.kernel_header)) { + /* When booting, the STM32F412 fetches the content of address 0x0, and + * extracts from it various key infos: the initial value of the PC register + * (program counter), the initial value of the stack pointer, and various + * entry points to interrupt service routines. This data is called the ISR + * vector table. + * + * Note that address 0x0 is always an alias. It points to the beginning of + * Flash, SRAM, or integrated bootloader depending on the boot mode chosen. + * (This mode is chosen by setting the BOOTn pins on the chip). + * + * We're generating the ISR vector table in code because it's very + * convenient: using function pointers, we can easily point to the service + * routine for each interrupt. */ + _isr_vector_table_start_flash = LOADADDR(.isr_vector_table); + _isr_vector_table_start_ram = .; + KEEP(*(.isr_vector_table)) + _isr_vector_table_end_ram = .; + } >SRAM + + .exam_mode_buffer ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.kernel_header) + SIZEOF(.isr_vector_table) : { + . = ALIGN(4K); + _exam_mode_buffer_start = .; + KEEP(*(.exam_mode_buffer)) + /* Note: We don't increment "." here, we set it. */ + . = . + FIRST_FLASH_SECTOR_SIZE; + _exam_mode_buffer_end = .; + } >FLASH + + /* External flash memory */ + .userland_header : { + . = ORIGIN(FLASH) + USERLAND_OFFSET; + KEEP(*(.userland_header)); + } > FLASH + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >FLASH + + .rodata : { + *(.rodata) + *(.rodata.*) + } >FLASH + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >FLASH + + .data : { + /* The data section is written to Flash but linked as if it were in RAM. + * + * This is required because its initial value matters (so it has to be in + * persistant memory in the first place), but it is a R/W area of memory + * so it will have to live in RAM upon execution (in linker lingo, that + * translates to the data section having a LMA in Flash and a VMA in RAM). + * + * This means we'll have to copy it from Flash to RAM on initialization. + * To do this, we'll need to know the source location of the data section + * (in Flash), the target location (in RAM), and the size of the section. + * That's why we're defining three symbols that we'll use in the initial- + * -ization routine. */ + . = ALIGN(4); + _data_section_start_flash = LOADADDR(.data); + _data_section_start_ram = .; + *(.data) + *(.data.*) + _data_section_end_ram = .; + } >SRAM AT> FLASH + + .bss : { + /* The bss section contains data for all uninitialized variables + * So like the .data section, it will go in RAM, but unlike the data section + * we don't care at all about an initial value. + * + * Before execution, crt0 will erase that section of memory though, so we'll + * need pointers to the beginning and end of this section. */ + . = ALIGN(4); + _bss_section_start_ram = .; + *(.bss) + *(.bss.*) + /* The compiler may choose to allocate uninitialized global variables as + * COMMON blocks. This can be disabled with -fno-common if needed. */ + *(COMMON) + _bss_section_end_ram = .; + } >SRAM + + .heap : { + _heap_start = .; + /* Note: We don't increment "." here, we set it. */ + . = (ORIGIN(SRAM) + LENGTH(SRAM) - STACK_SIZE); + _heap_end = .; + } >SRAM + + .stack : { + . = ALIGN(8); + _stack_end = .; + . += (STACK_SIZE - 8); + . = ALIGN(8); + _stack_start = .; + } >SRAM + + /DISCARD/ : { + /* exidx and extab are needed for unwinding, which we don't use */ + *(.ARM.exidx*) + *(.ARM.extab*) + } +} diff --git a/ion/src/device/bootloader/drivers/board.cpp b/ion/src/device/bootloader/drivers/board.cpp new file mode 100644 index 00000000000..03e47bc2cdb --- /dev/null +++ b/ion/src/device/bootloader/drivers/board.cpp @@ -0,0 +1,423 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void(*ISR)(void); +extern ISR InitialisationVector[]; + +// Public Ion methods + +const char * Ion::fccId() { + return "2ALWP-N0110"; +} + +// Private Ion::Device methods + +namespace Ion { +namespace Device { +namespace Board { + +using namespace Regs; + +void initMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.2 Disable fault exceptions + CORTEX.SHCRS()->setMEMFAULTENA(false); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + // 2. MPU settings + // 2.1 Configure a MPU region for the FMC memory area + /* This is needed for interfacing with the LCD + * We define the whole FMC memory bank 1 as strongly ordered, non-executable + * and not accessible. We define the FMC command and data addresses as + * writeable non-cachable, non-buffereable and non shareable. */ + int sector = 0; + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000+0x20000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + // 2.2 Configure MPU regions for the QUADSPI peripheral + /* L1 Cache can issue speculative reads to any memory address. But, when the + * Quad-SPI is in memory-mapped mode, if an access is made to an address + * outside of the range defined by FSIZE but still within the 256Mbytes range, + * then an AHB error is given (AN4760). To prevent this to happen, we + * configure the MPU to define the whole Quad-SPI addressable space as + * strongly ordered, non-executable and not accessible. Plus, we define the + * Quad-SPI region corresponding to the Expternal Chip as executable and + * fully accessible (AN4861). */ + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_8MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setXN(false); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(1); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + /* 2.3 Empty sector + * We have to override the sectors configured by the bootloader. */ + while(sector < 8) { + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0); + MPU.RASR()->setENABLE(0); + } + + /* We assert that all sectors have been initialized. Otherwise, the bootloader + * configuration is still active on the last sectors when their configuration + * should be reset. */ + assert(sector == 8); + + // 2.4 Enable MPU + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::dsb(); + Cache::isb(); +} + +void init() { + initMPU(); + initClocks(); + + // Ensure right location of interrupt vectors + CORTEX.VTOR()->setVTOR((void*)&InitialisationVector); + + // Initiate L1 cache after initiating the external flash + Cache::enable(); +} + +void initClocks() { + /* System clock + * Configure the CPU at 192 MHz and USB at 48 MHz. */ + + /* After reset, the device is using the high-speed internal oscillator (HSI) + * as a clock source, which runs at a fixed 16 MHz frequency. The HSI is not + * accurate enough for reliable USB operation, so we need to use the external + * high-speed oscillator (HSE). */ + + // Enable the HSI and wait for it to be ready + RCC.CR()->setHSION(true); + while(!RCC.CR()->getHSIRDY()) { + } + + // Enable the HSE and wait for it to be ready + RCC.CR()->setHSEON(true); + while(!RCC.CR()->getHSERDY()) { + } + + // Enable PWR peripheral clock + RCC.APB1ENR()->setPWREN(true); + + /* To pass electromagnetic compatibility tests, we activate the Spread + * Spectrum clock generation, which adds jitter to the PLL clock in order to + * "lower peak-energy on the central frequency" and its harmonics. + * It must be done before enabling the PLL. */ + class RCC::SSCGR sscgr(0); // Reset value + sscgr.setMODPER(Clocks::Config::SSCG_MODPER); + sscgr.setINCSTEP(Clocks::Config::SSCG_INCSTEP); + sscgr.setSPREADSEL(RCC::SSCGR::SPREADSEL::CenterSpread); + sscgr.setSSCGEN(true); + RCC.SSCGR()->set(sscgr); + + /* Given the crystal used on our device, the HSE will oscillate at 8 MHz. By + * piping it through a phase-locked loop (PLL) we can derive other frequencies + * for use in different parts of the system. */ + + // Configure the PLL ratios and use HSE as a PLL input + RCC.PLLCFGR()->setPLLM(Clocks::Config::PLL_M); + RCC.PLLCFGR()->setPLLN(Clocks::Config::PLL_N); + RCC.PLLCFGR()->setPLLQ(Clocks::Config::PLL_Q); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + + // Enable Over-drive + PWR.CR()->setODEN(true); + while(!PWR.CSR()->getODRDY()) { + } + + PWR.CR()->setODSWEN(true); + while(!PWR.CSR()->getODSWRDY()) { + } + + // Choose Voltage scale 1 + PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); + while (!PWR.CSR()->getVOSRDY()) {} + + /* After reset the Flash runs as fast as the CPU. When we clock the CPU faster + * the flash memory cannot follow and therefore flash memory accesses need to + * wait a little bit. + * The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */ + FLASH.ACR()->setLATENCY(7); + + /* Enable prefetching flash instructions */ + /* Fetching instructions increases slightly the power consumption but the + * increase is negligible compared to the screen consumption. */ + FLASH.ACR()->setPRFTEN(true); + + /* Enable the ART */ + FLASH.ACR()->setARTEN(true); + + // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz + RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerReg); + // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz + RCC.CFGR()->setPPRE2(Clocks::Config::APB2PrescalerReg); + + while(!RCC.CR()->getPLLRDY()) { + } + + // Use the PLL output as a SYSCLK source + RCC.CFGR()->setSW(RCC::CFGR::SW::PLL); + while (RCC.CFGR()->getSWS() != RCC::CFGR::SW::PLL) { + } + + // Now that we don't need use it anymore, turn the HSI off + RCC.CR()->setHSION(false); + + // Peripheral clocks + + // AHB1 bus + // Our peripherals are using GPIO A, B, C, D and E. + // We're not using the CRC nor DMA engines. + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + ahb1enr.setGPIOAEN(true); + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + ahb1enr.setGPIODEN(true); + ahb1enr.setGPIOEEN(true); + ahb1enr.setDMA2EN(true); + RCC.AHB1ENR()->set(ahb1enr); + + // AHB2 bus + RCC.AHB2ENR()->setOTGFSEN(true); + + // AHB3 bus + RCC.AHB3ENR()->setFSMCEN(true); + + // APB1 bus + // We're using TIM3 for the LEDs + RCC.APB1ENR()->setTIM3EN(true); + RCC.APB1ENR()->setPWREN(true); + RCC.APB1ENR()->setRTCAPB(true); + + // APB2 bus + class RCC::APB2ENR apb2enr(0); // Reset value + apb2enr.setADC1EN(true); + apb2enr.setSYSCFGEN(true); + apb2enr.setUSART6EN(true); // TODO required if building bench target only? + RCC.APB2ENR()->set(apb2enr); + + // Configure clocks in sleep mode + // AHB1 peripheral clock enable in low-power mode register + class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value + ahb1lpenr.setGPIOALPEN(true); // Enable IO port A for Charging/USB plug/Keyboard pins + ahb1lpenr.setGPIOBLPEN(true); // Enable IO port B for LED pins + ahb1lpenr.setGPIOCLPEN(true); // Enable IO port C for LED/Keyboard pins + ahb1lpenr.setGPIODLPEN(false); // Disable IO port D (LCD...) + ahb1lpenr.setGPIOELPEN(true); // Enable IO port E for Keyboard/Battery pins + ahb1lpenr.setGPIOFLPEN(false); // Disable IO port F + ahb1lpenr.setGPIOGLPEN(false); // Disable IO port G + ahb1lpenr.setGPIOHLPEN(false); // Disable IO port H + ahb1lpenr.setGPIOILPEN(false); // Disable IO port I + ahb1lpenr.setCRCLPEN(false); + ahb1lpenr.setFLITFLPEN(false); + ahb1lpenr.setSRAM1LPEN(false); + ahb1lpenr.setDMA1LPEN(false); + ahb1lpenr.setDMA2LPEN(false); + ahb1lpenr.setAXILPEN(false); + ahb1lpenr.setSRAM2LPEN(false); + ahb1lpenr.setBKPSRAMLPEN(false); + ahb1lpenr.setDTCMLPEN(false); + ahb1lpenr.setOTGHSLPEN(false); + ahb1lpenr.setOTGHSULPILPEN(false); + RCC.AHB1LPENR()->set(ahb1lpenr); + + // AHB2 peripheral clock enable in low-power mode register + class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value + ahb2lpenr.setOTGFSLPEN(false); + ahb2lpenr.setRNGLPEN(false); + ahb2lpenr.setAESLPEN(false); + RCC.AHB2LPENR()->set(ahb2lpenr); + + // AHB3 peripheral clock enable in low-power mode register + class RCC::AHB3LPENR ahb3lpenr(0x00000003); // Reset value + ahb3lpenr.setFMCLPEN(false); + ahb3lpenr.setQSPILPEN(false); + RCC.AHB3LPENR()->set(ahb3lpenr); + + // APB1 peripheral clock enable in low-power mode register + class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value + apb1lpenr.setTIM2LPEN(false); + apb1lpenr.setTIM3LPEN(true); // Enable TIM3 in sleep mode for LEDs + apb1lpenr.setTIM4LPEN(false); + apb1lpenr.setTIM5LPEN(false); + apb1lpenr.setTIM6LPEN(false); + apb1lpenr.setTIM7LPEN(false); + apb1lpenr.setTIM12LPEN(false); + apb1lpenr.setTIM13LPEN(false); + apb1lpenr.setTIM14LPEN(false); + apb1lpenr.setRTCAPBLPEN(false); + apb1lpenr.setWWDGLPEN(false); + apb1lpenr.setSPI2LPEN(false); + apb1lpenr.setSPI3LPEN(false); + apb1lpenr.setUSART2LPEN(false); + apb1lpenr.setUSART3LPEN(false); + apb1lpenr.setI2C1LPEN(false); + apb1lpenr.setI2C2LPEN(false); + apb1lpenr.setI2C3LPEN(false); + apb1lpenr.setCAN1LPEN(false); + apb1lpenr.setPWRLPEN(false); + apb1lpenr.setLPTIM1LPEN(false); + apb1lpenr.setUSART4LPEN(false); + apb1lpenr.setUSART5LPEN(false); + apb1lpenr.setOTGHSLPEN(false); + apb1lpenr.setOTGHSULPILPEN(false); + RCC.APB1LPENR()->set(apb1lpenr); + + // APB2 peripheral clock enable in low-power mode register + class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value + apb2lpenr.setTIM1LPEN(false); + apb2lpenr.setTIM8LPEN(false); + apb2lpenr.setUSART1LPEN(false); + apb2lpenr.setUSART6LPEN(false); + apb2lpenr.setADC1LPEN(false); + apb2lpenr.setSPI1LPEN(false); + apb2lpenr.setSPI4LPEN(false); + apb2lpenr.setSYSCFGLPEN(false); + apb2lpenr.setTIM9LPEN(false); + apb2lpenr.setTIM10LPEN(false); + apb2lpenr.setTIM11LPEN(false); + apb2lpenr.setSPI5LPEN(false); + apb2lpenr.setSDMMC2LPEN(false); + apb2lpenr.setADC2LPEN(false); + apb2lpenr.setADC3LPEN(false); + apb2lpenr.setSAI1LPEN(false); + apb2lpenr.setSAI2LPEN(false); + RCC.APB2LPENR()->set(apb2lpenr); +} + +void shutdownClocks(bool keepLEDAwake) { + // APB2 bus + RCC.APB2ENR()->set(0); // Reset value + + // AHB2 bus + RCC.AHB2ENR()->set(0); // Reset value + + // AHB3 bus + class RCC::AHB3ENR ahb3enr(0); // Reset value + // Required by external flash + ahb3enr.setQSPIEN(true); + RCC.AHB3ENR()->set(ahb3enr); // Reset value + + // APB1 + class RCC::APB1ENR apb1enr(0); // Reset value + // AHB1 bus + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + // GPIO B, C, D, E are used the by external flash + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + ahb1enr.setGPIODEN(true); + ahb1enr.setGPIOEEN(true); + if (keepLEDAwake) { + apb1enr.setTIM3EN(true); + ahb1enr.setGPIOBEN(true); + } + RCC.APB1ENR()->set(apb1enr); + RCC.AHB1ENR()->set(ahb1enr); +} + +constexpr int k_pcbVersionOTPIndex = 0; + +/* As we want the PCB versions to be in ascending order chronologically, and + * because the OTP are initialized with 1s, we store the bitwise-not of the + * version number. This way, devices with blank OTP are considered version 0. */ + +PCBVersion pcbVersion() { +#if IN_FACTORY + /* When flashing for the first time, we want all systems that depend on the + * PCB version to function correctly before flashing the PCB version. This + * way, flashing the PCB version can be done last. */ + return PCB_LATEST; +#else + PCBVersion version = readPCBVersionInMemory(); + return (version == k_alternateBlankVersion ? 0 : version); +#endif +} + +PCBVersion readPCBVersionInMemory() { + return ~(*reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex))); +} + +void writePCBVersion(PCBVersion version) { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex)); + PCBVersion formattedVersion = ~version; + InternalFlash::WriteMemory(destination, reinterpret_cast(&formattedVersion), sizeof(formattedVersion)); +} + +void lockPCBVersion() { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)); + uint8_t zero = 0; + InternalFlash::WriteMemory(destination, &zero, sizeof(zero)); +} + +bool pcbVersionIsLocked() { + return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/cache.cpp b/ion/src/device/bootloader/drivers/cache.cpp new file mode 100644 index 00000000000..0d16f5261ea --- /dev/null +++ b/ion/src/device/bootloader/drivers/cache.cpp @@ -0,0 +1,104 @@ +#include "cache.h" + +namespace Ion { +namespace Device { +namespace Cache { + +using namespace Regs; + +void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable) { + // Select Level 1 data cache + CORTEX.CSSELR()->set(0); + dsb(); + + // Disable D-Cache + if (disable) { + CORTEX.CCR()->setDC(false); + dsb(); + } + + // Pick the right DC??SW register according to invalidate/disable parameters + volatile CORTEX::DCSW * target = nullptr; + if (clean && invalidate) { + target = CORTEX.DCCISW(); + } else if (clean) { + target = CORTEX.DCCSW(); + } else { + assert(invalidate); + target = CORTEX.DCISW(); + } + + class CORTEX::CCSIDR ccsidr = CORTEX.CCSIDR()->get(); + uint32_t sets = ccsidr.getNUMSETS(); + uint32_t ways = ccsidr.getASSOCIATIVITY(); + + for (int set = sets; set >= 0; set--) { + for (int way = ways; way >= 0; way--) { + class CORTEX::DCSW dcsw; + dcsw.setSET(set); + dcsw.setWAY(way); + target->set(dcsw); + } + } + + dsb(); + isb(); +} + +void enable() { + enableICache(); + enableDCache(); +} + +void disable() { + disableICache(); + disableDCache(); +} + +void invalidateDCache() { + privateCleanInvalidateDisableDCache(false, true, false); +} + +void cleanDCache() { + privateCleanInvalidateDisableDCache(true, false, false); +} + +void enableDCache() { + invalidateDCache(); + CORTEX.CCR()->setDC(true); // Enable D-cache + dsb(); + isb(); +} + +void disableDCache() { + privateCleanInvalidateDisableDCache(true, true, true); +} + +void invalidateICache() { + dsb(); + isb(); + CORTEX.ICIALLU()->set(0); // Invalidate I-cache + dsb(); + isb(); +} + +void enableICache() { + invalidateICache(); + CORTEX.CCR()->setIC(true); // Enable I-cache + dsb(); + isb(); +} + +void disableICache() { + dsb(); + isb(); + CORTEX.CCR()->setIC(false); // Disable I-cache + CORTEX.ICIALLU()->set(0); // Invalidate I-cache + dsb(); + isb(); +} + + +} +} +} diff --git a/ion/src/device/bootloader/drivers/cache.h b/ion/src/device/bootloader/drivers/cache.h new file mode 100644 index 00000000000..cc047743b30 --- /dev/null +++ b/ion/src/device/bootloader/drivers/cache.h @@ -0,0 +1,46 @@ +#ifndef ION_DEVICE_N0110_CACHE_H +#define ION_DEVICE_N0110_CACHE_H + +#include + +namespace Ion { +namespace Device { +namespace Cache { + +/* Data memory barrier + * Ensures that all explicit memory accesses that appear in program order before + * the DMB instruction are observed before any explicit memory accesses that + * appear in program order after the DMB instruction */ +inline void dmb() { + asm volatile("dmb 0xF":::"memory"); +} + +/* Data synchronisation barrier + * Ensures that the processor stalls until the memory write is complete */ +inline void dsb() { + asm volatile("dsb 0xF":::"memory"); +} + +/* Instructions synchronisation barrier + * Ensures that the subsequent instructions are loaded in the new context */ +inline void isb() { + asm volatile("isb 0xF":::"memory"); +} + +void enable(); +void disable(); + +void invalidateDCache(); +void cleanDCache(); +void enableDCache(); +void disableDCache(); + +void invalidateICache(); +void enableICache(); +void disableICache(); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/backlight.h b/ion/src/device/bootloader/drivers/config/backlight.h new file mode 100644 index 00000000000..6f94838693c --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/backlight.h @@ -0,0 +1,25 @@ +#ifndef ION_DEVICE_N0110_CONFIG_BACKLIGHT_H +#define ION_DEVICE_N0110_CONFIG_BACKLIGHT_H + +#include + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PE0 | Backlight Enable | Output | + */ + +namespace Ion { +namespace Device { +namespace Backlight { +namespace Config { + +using namespace Regs; + +constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0); + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/battery.h b/ion/src/device/bootloader/drivers/config/battery.h new file mode 100644 index 00000000000..07851149b33 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/battery.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_N0110_CONFIG_BATTERY_H +#define ION_DEVICE_N0110_CONFIG_BATTERY_H + +#include + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PE3 | BAT_CHRG | Input, pulled up | Low = charging, high = full + * PB1 | VBAT_SNS | Analog | ADC1_1 + */ + +namespace Ion { +namespace Device { +namespace Battery { +namespace Config { + +using namespace Regs; + +constexpr static GPIOPin ChargingPin = GPIOPin(GPIOE, 3); +constexpr static GPIOPin ADCPin = GPIOPin(GPIOB, 1); +constexpr uint8_t ADCChannel = 9; +constexpr float ADCReferenceVoltage = 2.8f; +constexpr float ADCDividerBridgeRatio = 2.0f; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/clocks.h b/ion/src/device/bootloader/drivers/config/clocks.h new file mode 100644 index 00000000000..0cc551cb03b --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/clocks.h @@ -0,0 +1,68 @@ +#ifndef ION_DEVICE_N0110_CONFIG_CLOCKS_H +#define ION_DEVICE_N0110_CONFIG_CLOCKS_H + +#include + +namespace Ion { +namespace Device { +namespace Clocks { +namespace Config { + +/* If you want to considerably slow down the whole machine uniformely, which + * can be very useful to diagnose performance issues, change the PLL + * configuration to: + * PLL_M = 8 + * PLL_N = 192 + * PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP8 + * PLL_Q = 4 + * + * SYSCLK and HCLK will be set to 24 MHz. + * Note that even booting takes a few seconds, so don't be surprised + * if the screen is black for a short while upon booting. */ + +constexpr static int HSE = 8; +constexpr static int PLL_M = 8; +constexpr static int PLL_N = 384; +constexpr static Regs::RCC::PLLCFGR::PLLP PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP2; +constexpr static int PLL_P = ((int)PLL_P_Reg | 1) << 1; +constexpr static int PLL_Q = 8; +constexpr static int SYSCLKFrequency = ((HSE/PLL_M)*PLL_N)/PLL_P; +constexpr static int AHBPrescaler = 1; +/* To slow down the whole system, we prescale the AHB clock. + * We could divide the system clock by 512. However, the HCLK clock + * frequency must be >= 14.2MHz and <=216 MHz which forces the + * AHBPrescaler to be below 192MHz/14.2MHz~13.5. */ +constexpr static Regs::RCC::CFGR::AHBPrescaler AHBLowFrequencyPrescalerReg = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy8; +constexpr static int AHBLowFrequencyPrescaler = 8; +constexpr static int HCLKFrequency = SYSCLKFrequency/AHBPrescaler; +static_assert(HCLKFrequency == 192, "HCLK frequency changed!"); +constexpr static int HCLKLowFrequency = SYSCLKFrequency/AHBLowFrequencyPrescaler; +constexpr static int AHBFrequency = HCLKFrequency; +//constexpr static int AHBLowFrequency = HCLKLowFrequency; +constexpr static Regs::RCC::CFGR::APBPrescaler APB1PrescalerReg = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy4; +constexpr static int APB1Prescaler = 4; +//constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler; +constexpr static int APB1LowFrequency = HCLKLowFrequency/APB1Prescaler; +//constexpr static int APB1TimerFrequency = 2*APB1Frequency; +constexpr static int APB1TimerLowFrequency = 2*APB1LowFrequency; + +constexpr static Regs::RCC::CFGR::APBPrescaler APB2PrescalerReg = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2; + +/* According to AN4850 about Spread Spectrum clock generation + * MODPER = round[HSE/(4 x fMOD)] with fMOD the target modulation frequency. */ +constexpr static int fMod = 8; // in KHz. Must be <= 10KHz +constexpr static uint32_t SSCG_MODPER = HSE*1000/(4*fMod); // *1000 to put HSE in KHz +/* According to the USB specification 2, "For full-speed only functions, the + * required data-rate when transmitting (TFDRATE) is 12.000 Mb/s ±0.25%". */ +constexpr static double modulationDepth = 0.25; // Must be (0.25% <= md <= 2%) +// INCSTEP = round[(2^15 -1)xmdxPLLN)/(100x5xMODPER) +constexpr static uint32_t SSCG_INCSTEP = (32767*modulationDepth*PLL_N)/(1.0*100*5*SSCG_MODPER); +static_assert(SSCG_MODPER == 250, "SSCG_MODPER changed"); +static_assert(SSCG_INCSTEP == 25, "SSCG_INCSTEP changed"); +static_assert(SSCG_INCSTEP * SSCG_MODPER < 32767, "Wrong values for the Spread spectrun clock generator"); +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/console.h b/ion/src/device/bootloader/drivers/config/console.h new file mode 100644 index 00000000000..58a527201ab --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/console.h @@ -0,0 +1,32 @@ +#ifndef ION_DEVICE_N0110_CONFIG_CONSOLE_H +#define ION_DEVICE_N0110_CONFIG_CONSOLE_H + +#include + +namespace Ion { +namespace Device { +namespace Console { +namespace Config { + +using namespace Regs; + +constexpr static USART Port = USART(6); +constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 7); +constexpr static GPIOPin TxPin = GPIOPin(GPIOC, 6); +constexpr static GPIO::AFR::AlternateFunction AlternateFunction = GPIO::AFR::AlternateFunction::AF8; + +/* The baud rate of the UART is set by the following equation: + * BaudRate = f/USARTDIV, where f is the clock frequency and USARTDIV a divider. + * In other words, USARTDIV = f/BaudRate. All frequencies in Hz. + * + * In our case, we configure the minicom to use a 115200 BaudRate and + * f = fAPB2 = 96 MHz, so USARTDIV = 833.333 */ +constexpr static int USARTDIVValue = 833; + + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/display.h b/ion/src/device/bootloader/drivers/config/display.h new file mode 100644 index 00000000000..c14f0d21bd5 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/display.h @@ -0,0 +1,38 @@ +#ifndef ION_DEVICE_N0110_CONFIG_DISPLAY_H +#define ION_DEVICE_N0110_CONFIG_DISPLAY_H + +#include + +namespace Ion { +namespace Device { +namespace Display { +namespace Config { + +using namespace Regs; + +constexpr static GPIOPin FSMCPins[] = { + GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), GPIOPin(GPIOD, 5), + GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 8), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10), + GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 7), + GPIOPin(GPIOE, 8), GPIOPin(GPIOE, 9), GPIOPin(GPIOE, 10), GPIOPin(GPIOE, 11), + GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14), GPIOPin(GPIOE, 15), +}; + +constexpr static GPIOPin PowerPin = GPIOPin(GPIOC, 8); +constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 1); +constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOD, 6); +constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 11); + +constexpr static DMA DMAEngine = DMA2; +constexpr static int DMAStream = 0; + +constexpr static int HCLKFrequencyInMHz = 192; + +constexpr static bool DisplayInversion = true; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/exam_mode.h b/ion/src/device/bootloader/drivers/config/exam_mode.h new file mode 100644 index 00000000000..e2a2e2abae5 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/exam_mode.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_N0110_CONFIG_EXAM_MODE_H +#define ION_DEVICE_N0110_CONFIG_EXAM_MODE_H + +namespace Ion { +namespace ExamMode { +namespace Config { + +// TODO: factorize the macro with equivalent macro on N100 + +#define byte4 0xFF, 0xFF, 0xFF, 0xFF +#define byte8 byte4, byte4 +#define byte16 byte8, byte8 +#define byte32 byte16, byte16 +#define byte64 byte32, byte32 +#define byte128 byte64, byte64 +#define byte256 byte128, byte128 +#define byte512 byte256, byte256 +#define byte1K byte512, byte512 +#define byte2K byte1K, byte1K +#define byte4K byte2K, byte2K + +#define EXAM_BUFFER_CONTENT byte4K + +constexpr static int ExamModeBufferSize = 4*1024; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/external_flash.h b/ion/src/device/bootloader/drivers/config/external_flash.h new file mode 100644 index 00000000000..f245dca2000 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/external_flash.h @@ -0,0 +1,45 @@ +#ifndef ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H +#define ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H + +#include + +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PE2 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI_BK1_IO2 + * PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI_BK1_IO0 + * PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI_BK1_IO1 + * PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI_BK1_IO3 + */ + +namespace Ion { +namespace Device { +namespace ExternalFlash { +namespace Config { + +using namespace Regs; + +constexpr static uint32_t StartAddress = 0x90000000; +constexpr static uint32_t EndAddress = 0x90800000; + +constexpr static int NumberOf4KSectors = 8; +constexpr static int NumberOf32KSectors = 1; +constexpr static int NumberOf64KSectors = 128 - 1; +constexpr static int NumberOfSectors = NumberOf4KSectors + NumberOf32KSectors + NumberOf64KSectors; + +constexpr static AFGPIOPin Pins[] = { + AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), + AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast), +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/internal_flash.h b/ion/src/device/bootloader/drivers/config/internal_flash.h new file mode 100644 index 00000000000..9fcbeed145d --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/internal_flash.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H +#define ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H + +#include + +namespace Ion { +namespace Device { +namespace InternalFlash { +namespace Config { + +constexpr static uint32_t StartAddress = 0x08000000; +constexpr static uint32_t EndAddress = 0x08010000; +constexpr static int NumberOfSectors = 4; +constexpr static uint32_t SectorAddresses[NumberOfSectors+1] = { + 0x08000000, 0x08004000, 0x08008000, 0x0800C000, + 0x08010000 +}; + +constexpr static uint32_t OTPStartAddress = 0x1FF07800; +constexpr static uint32_t OTPLocksAddress = 0x1FF07A00; +constexpr static int NumberOfOTPBlocks = 16; +constexpr static uint32_t OTPBlockSize = 0x20; +constexpr uint32_t OTPAddress(int block) { return OTPStartAddress + block * OTPBlockSize; }; +constexpr uint32_t OTPLockAddress(int block) { return OTPLocksAddress + block; } + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/keyboard.h b/ion/src/device/bootloader/drivers/config/keyboard.h new file mode 100644 index 00000000000..2c98f84a3e3 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/keyboard.h @@ -0,0 +1,75 @@ +#ifndef ION_DEVICE_N0110_CONFIG_KEYBOARD_H +#define ION_DEVICE_N0110_CONFIG_KEYBOARD_H + +#include +#include + +/* Pin | Role | Mode + * -----+-------------------+-------------------- + * PC0 | Keyboard column 1 | Input, pulled up + * PC1 | Keyboard column 2 | Input, pulled up + * PC2 | Keyboard column 3 | Input, pulled up + * PC3 | Keyboard column 4 | Input, pulled up + * PC4 | Keyboard column 5 | Input, pulled up + * PC5 | Keyboard column 6 | Input, pulled up + * PA1 | Keyboard row A | Output, open drain + * PA0 | Keyboard row B | Output, open drain + * PA2 | Keyboard row C | Output, open drain + * PA3 | Keyboard row D | Output, open drain + * PA4 | Keyboard row E | Output, open drain + * PA5 | Keyboard row F | Output, open drain + * PA6 | Keyboard row G | Output, open drain + * PA7 | Keyboard row H | Output, open drain + * PA8 | Keyboard row I | Output, open drain + * + * The keyboard is a matrix that is laid out as follow: + * + * -+------+------+------+------+------+------+ + * | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | + * -+------+------+------+------+------+------+ + * | K_B1 | | K_B3 | | | | + * -+------+------+------+------+------+------+ + * | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | + * -+------+------+------+------+------+------+ + * | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | + * -+------+------+------+------+------+------+ + * | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | + * -+------+------+------+------+------+------+ + * | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | + * -+------+------+------+------+------+------+ + * | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | + * -+------+------+------+------+------+------+ + * | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | + * -+------+------+------+------+------+------+ + * | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | + * -+------+------+------+------+------+------| + */ + +namespace Ion { +namespace Device { +namespace Keyboard { +namespace Config { + +using namespace Regs; + +constexpr GPIO RowGPIO = GPIOA; +constexpr uint8_t numberOfRows = 9; +constexpr uint8_t RowPins[numberOfRows] = {1, 0, 2, 3, 4, 5, 6, 7, 8}; + +constexpr GPIO ColumnGPIO = GPIOC; +constexpr uint8_t numberOfColumns = 6; +constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5}; + +/* Undefined keys numbers are: 7, 9, 10, 11, 35, 41, 47 and 53 + * Therefore we want to make sure those bits are forced to zero in + * whatever value we return. */ +inline uint64_t ValidKeys(uint64_t state) { + return state & 0x1F7DF7FFFFF17F; +} + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/led.h b/ion/src/device/bootloader/drivers/config/led.h new file mode 100644 index 00000000000..1fea36d4a82 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/led.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_N0110_CONFIG_LED_H +#define ION_DEVICE_N0110_CONFIG_LED_H + +#include + +namespace Ion { +namespace Device { +namespace LED { +namespace Config { + +using namespace Regs; + +static constexpr int RedChannel = 1; +static constexpr int GreenChannel = 2; +static constexpr int BlueChannel = 3; + +constexpr static AFGPIOPin RGBPins[] = { + AFGPIOPin(GPIOB, 4, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low), // RED + AFGPIOPin(GPIOB, 5, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low), // GREEN + AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low) // BLUE +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/serial_number.h b/ion/src/device/bootloader/drivers/config/serial_number.h new file mode 100644 index 00000000000..c5ad127c304 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/serial_number.h @@ -0,0 +1,18 @@ +#ifndef ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H +#define ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H + +#include + +namespace Ion { +namespace Device { +namespace SerialNumber { +namespace Config { + +constexpr uint32_t UniqueDeviceIDAddress = 0x1FF07A10; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/swd.h b/ion/src/device/bootloader/drivers/config/swd.h new file mode 100644 index 00000000000..1b9fcaa20aa --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/swd.h @@ -0,0 +1,24 @@ +#ifndef ION_DEVICE_N0110_CONFIG_SWD_H +#define ION_DEVICE_N0110_CONFIG_SWD_H + +#include + +namespace Ion { +namespace Device { +namespace SWD { +namespace Config { + +using namespace Regs; + +constexpr static AFGPIOPin Pins[] = { + AFGPIOPin(GPIOA, 13, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOA, 14, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 3, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/timing.h b/ion/src/device/bootloader/drivers/config/timing.h new file mode 100644 index 00000000000..fa4502000af --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/timing.h @@ -0,0 +1,19 @@ +#ifndef ION_DEVICE_N0110_CONFIG_TIMING_H +#define ION_DEVICE_N0110_CONFIG_TIMING_H + +#include + +namespace Ion { +namespace Device { +namespace Timing { +namespace Config { + +constexpr static int LoopsPerMillisecond = 4811; +constexpr static int LoopsPerMicrosecond = 38; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/config/usb.h b/ion/src/device/bootloader/drivers/config/usb.h new file mode 100644 index 00000000000..61c1e739d31 --- /dev/null +++ b/ion/src/device/bootloader/drivers/config/usb.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_N0110_CONFIG_USB_H +#define ION_DEVICE_N0110_CONFIG_USB_H + +#include + +namespace Ion { +namespace Device { +namespace USB { +namespace Config { + +using namespace Regs; + +/* On the STM32F730, PA9 does not actually support alternate function 10. + * However, because of the wiring of the USB connector on old N0110, detection + * of when the device is plugged required the use of this undocumented setting. + * After the revision of the USB connector and ESD protection, we can now + * follow the specification and configure the Vbus pin as a floating-input GPIO. + */ +constexpr static AFGPIOPin VbusPin = AFGPIOPin(GPIOA, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast); + +constexpr static AFGPIOPin DmPin = AFGPIOPin(GPIOA, 11, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast); +constexpr static AFGPIOPin DpPin = AFGPIOPin(GPIOA, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast); + +constexpr static const char * InterfaceStringDescriptor = "@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg"; + +} +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/external_flash.cpp b/ion/src/device/bootloader/drivers/external_flash.cpp new file mode 100644 index 00000000000..1c7686cd45e --- /dev/null +++ b/ion/src/device/bootloader/drivers/external_flash.cpp @@ -0,0 +1,521 @@ +#include +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace ExternalFlash { + +using namespace Regs; + +/* The external flash and the Quad-SPI peripheral support several operating + * modes, corresponding to different numbers of signals used to communicate + * during each phase of the command sequence. + * + * Mode name for | Number of signals used during each phase: + * external flash | Instruction | Address | Alt. bytes | Data + * ----------------+-------------+---------+------------+------ + * Standard SPI | 1 | 1 | 1 | 1 + * Dual-Output SPI | 1 | 1 | 1 | 2 + * Dual-I/O SPI | 1 | 2 | 2 | 2 + * Quad-Output SPI | 1 | 1 | 1 | 4 + * Quad-I/O SPI | 1 | 4 | 4 | 4 + * QPI | 4 | 4 | 4 | 4 + * + * The external flash supports clock frequencies up to 104MHz for all + * instructions, except for Read Data (0x03) which is supported up to 50Mhz. + * + * + * Quad-SPI block diagram + * + * +----------------------+ +------------+ + * | Quad-SPI | | | + * | peripheral | | External | + * | | read | flash | + * AHB <-- | data <-- 32-byte | <-- | memory | + * matrix --> | register --> FIFO | --> | | + * +----------------------+ write +------------+ + * + * Any data transmitted to or from the external flash memory go through a + * 32-byte FIFO. + * + * Read or write operations are performed in burst mode, that is, after any data + * byte is transmitted between the Quad-SPI and the flash memory, the latter + * automatically increments the specified address and the next byte to read or + * write is respectively pushed in or popped from the FIFO. + * And so on, as long as the clock continues. + * + * If the FIFO gets full in a read operation or + * if the FIFO gets empty in a write operation, + * the operation stalls and CLK stays low until firmware services the FIFO. + * + * If the FIFO gets full in a write operation, the operation is stalled until + * the FIFO has enough space to accept the amount of data being written. + * If the FIFO does not have as many bytes as requested by the read operation + * and if BUSY=1, the operation is stalled until enough data is present or until + * the transfer is complete, whichever happens first. */ + +enum class Command : uint8_t { + WriteStatusRegister = 0x01, + PageProgram = 0x02, // Program previously erased memory areas as being "0" + ReadData = 0x03, + ReadStatusRegister1 = 0x05, + WriteEnable = 0x06, + Erase4KbyteBlock = 0x20, + WriteStatusRegister2 = 0x31, + QuadPageProgramW25Q64JV = 0x32, + QuadPageProgramAT25F641 = 0x33, + ReadStatusRegister2 = 0x35, + Erase32KbyteBlock = 0x52, + EnableReset = 0x66, + Reset = 0x99, + ReadJEDECID = 0x9F, + ReleaseDeepPowerDown = 0xAB, + DeepPowerDown = 0xB9, + ChipErase = 0xC7, // Erase the whole chip or a 64-Kbyte block as being "1" + Erase64KbyteBlock = 0xD8, + FastReadQuadIO = 0xEB +}; + +static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +static constexpr uint8_t NumberOfAddressBitsIn32KbyteBlock = 15; +static constexpr uint8_t NumberOfAddressBitsIn4KbyteBlock = 12; + +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD(QE, 1); + }; +}; + +class OperatingModes { +public: + constexpr OperatingModes( + QUADSPI::CCR::OperatingMode instruction, + QUADSPI::CCR::OperatingMode address, + QUADSPI::CCR::OperatingMode data) : + m_instructionOperatingMode(instruction), + m_addressOperatingMode(address), + m_dataOperatingMode(data) + {} + QUADSPI::CCR::OperatingMode instructionOperatingMode() const { return m_instructionOperatingMode; } + QUADSPI::CCR::OperatingMode addressOperatingMode() const { return m_addressOperatingMode; } + QUADSPI::CCR::OperatingMode dataOperatingMode() const { return m_dataOperatingMode; } +private: + QUADSPI::CCR::OperatingMode m_instructionOperatingMode; + QUADSPI::CCR::OperatingMode m_addressOperatingMode; + QUADSPI::CCR::OperatingMode m_dataOperatingMode; +}; + +/* W25Q64JV does not implement QPI-4-4-4, so we always send the instructions on + * one wire only.*/ +static constexpr OperatingModes sOperatingModes100(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes101(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes110(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes111(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes114(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad); +static constexpr OperatingModes sOperatingModes144(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad, QUADSPI::CCR::OperatingMode::Quad); + +static QUADSPI::CCR::OperatingMode sOperatingMode = QUADSPI::CCR::OperatingMode::Single; + +static constexpr int ClockFrequencyDivisor = 2; // F(QUADSPI) = F(AHB) / ClockFrequencyDivisor +static constexpr int FastReadQuadIODummyCycles = 4; // Must be 4 for W25Q64JV (Fig 24.A page 34) and for AT25F641 (table 7.19 page 28) +/* According to datasheets, the CS signal should stay high (deselect the device) + * for t_SHSL = 50ns at least. + * -> Max of 30ns (see AT25F641 Sections 8.7 and 8.8), + * 10ns and 50ns (see W25Q64JV Section 9.6). */ +static constexpr float ChipSelectHighTimeInNanoSeconds = 50.0f; + +static void send_command_full( + QUADSPI::CCR::FunctionalMode functionalMode, + OperatingModes operatingModes, + Command c, + uint8_t * address, + uint32_t altBytes, + size_t numberOfAltBytes, + uint8_t dummyCycles, + uint8_t * data, + size_t dataLength); + +static inline void send_command(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + sOperatingModes100, + c, + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0); +} + +static inline void send_write_command(Command c, uint8_t * address, const uint8_t * data, size_t dataLength, OperatingModes operatingModes) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingModes, + c, + address, + 0, 0, + 0, + const_cast(data), dataLength); +} + +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes101, + c, + address, + 0, 0, + 0, + data, dataLength); +} + +static inline void wait() { + /* The DSB instruction guarantees the completion of a write operation before + * polling the status register. */ + Cache::dsb(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + do { + send_read_command(Command::ReadStatusRegister1, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1)); + } while (statusRegister1.getBUSY()); +} + +static void set_as_memory_mapped() { + /* In memory-mapped mode, all AHB masters may access the external flash memory as an internal one: + * the programmed instruction is sent automatically whenever an AHB master reads in the Quad-SPI flash bank area. + * (The QUADSPI_DLR register has no meaning and any access to QUADSPI_DR returns zero.) + * + * To anticipate sequential reads, the nCS signal is maintained low so as to + * keep the read operation active and prefetch the subsequent bytes in the FIFO. + * + * It goes low, only if the low-power timeout counter is enabled. + * (Flash memories tend to consume more when nCS is held low.) */ + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + sOperatingModes144, + Command::FastReadQuadIO, + reinterpret_cast(FlashAddressSpaceSize), + 0xA0, 1, + FastReadQuadIODummyCycles, + nullptr, 0 + ); +} + +static void unset_memory_mapped_mode() { + /* Reset Continuous Read Mode Bits before issuing normal instructions. */ + uint8_t dummyData; + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes144, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + FastReadQuadIODummyCycles, + &dummyData, 1 + ); +} + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, OperatingModes operatingModes, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { + /* According to ST's Errata Sheet ES0360, "Wrong data can be read in + * memory-mapped after an indirect mode operation". This is the workaround. */ + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI::CCR::FunctionalMode previousMode = QUADSPI.CCR()->getFMODE(); + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectWrite || previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Reset the address register + QUADSPI.AR()->set(0); // No write to DR should be done after this + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Make an abort request to stop the reading and clear the busy bit + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + } + } else if (QUADSPI.CCR()->getFMODE() == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + /* "BUSY goes high as soon as the first memory-mapped access occurs. Because + * of the prefetch operations, BUSY does not fall until there is a timeout, + * there is an abort, or the peripheral is disabled". (From the Reference + * Manual) + * If we are leaving memory-mapped mode, we send an abort to clear BUSY. */ + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + + assert(QUADSPI.CCR()->getFMODE() != QUADSPI::CCR::FunctionalMode::MemoryMapped || QUADSPI.SR()->getBUSY() == 0); + + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingModes.dataOperatingMode()); + } + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } + ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingModes.addressOperatingMode()); // Seems to always be the same as address mode + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingModes.addressOperatingMode()); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + } + ccr.setIMODE(operatingModes.instructionOperatingMode()); + ccr.setINSTRUCTION(static_cast(c)); + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setSIOO(true); + /* If the SIOO bit is set, the instruction is sent only for the first command following a write to QUADSPI_CCR. + * Subsequent command sequences skip the instruction phase, until there is a write to QUADSPI_CCR. */ + } + QUADSPI.CCR()->set(ccr); + if (address != reinterpret_cast(FlashAddressSpaceSize)) { + QUADSPI.AR()->set(reinterpret_cast(address)); + } + + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* Wait for the command to be sent. + * "When configured in memory-mapped mode, because of the prefetch operations, + * BUSY does not fall until there is a timeout, there is an abort, or the + * peripheral is disabled.", so we do not wait if the device is in + * memory-mapped mode. */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } + } +} + +static void initGPIO() { + for(const AFGPIOPin & p : Config::Pins) { + p.init(); + } +} + +static void initQSPI() { + // Enable QUADSPI AHB3 peripheral clock + RCC.AHB3ENR()->setQSPIEN(true); + + // Configure controller for target device + class QUADSPI::DCR dcr(0); + dcr.setFSIZE(NumberOfAddressBitsInChip - 1); + constexpr int ChipSelectHighTimeCycles = (ChipSelectHighTimeInNanoSeconds * static_cast(Clocks::Config::AHBFrequency)) / (static_cast(ClockFrequencyDivisor) * 1000.0f) + 1.0f; + dcr.setCSHT(ChipSelectHighTimeCycles - 1); + dcr.setCKMODE(true); + QUADSPI.DCR()->set(dcr); + class QUADSPI::CR cr(0); + cr.setPRESCALER(ClockFrequencyDivisor - 1); + cr.setEN(true); + QUADSPI.CR()->set(cr); +} + +static void initChip() { + // Release sleep deep + send_command(Command::ReleaseDeepPowerDown); + Timing::usleep(3); + + /* The chip initially expects commands in SPI mode. We need to use SPI to tell + * it to switch to QuadSPI/QPI. */ + if (sOperatingMode == QUADSPI::CCR::OperatingMode::Single) { + send_command(Command::WriteEnable); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + statusRegister2.setQE(true); + wait(); + send_write_command(Command::WriteStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister2), sizeof(statusRegister2), sOperatingModes101); + wait(); + sOperatingMode = QUADSPI::CCR::OperatingMode::Quad; + } + set_as_memory_mapped(); +} + +void init() { + if (Config::NumberOfSectors == 0) { + return; + } + initGPIO(); + initQSPI(); + initChip(); +} + +static void shutdownGPIO() { + for(const AFGPIOPin & p : Config::Pins) { + p.group().OSPEEDR()->setOutputSpeed(p.pin(), GPIO::OSPEEDR::OutputSpeed::Low); + p.group().MODER()->setMode(p.pin(), GPIO::MODER::Mode::Analog); + p.group().PUPDR()->setPull(p.pin(), GPIO::PUPDR::Pull::None); + } +} + +static void shutdownChip() { + unset_memory_mapped_mode(); + // Reset + send_command(Command::EnableReset); + send_command(Command::Reset); + sOperatingMode = QUADSPI::CCR::OperatingMode::Single; + Timing::usleep(30); + + // Sleep deep + send_command(Command::DeepPowerDown); + Timing::usleep(3); +} + +static void shutdownQSPI() { + // Reset the controller + RCC.AHB3RSTR()->setQSPIRST(true); + RCC.AHB3RSTR()->setQSPIRST(false); + + RCC.AHB3ENR()->setQSPIEN(false); // TODO: move in Device::shutdownClocks +} + +void shutdown() { + if (Config::NumberOfSectors == 0) { + return; + } + shutdownChip(); + shutdownQSPI(); + shutdownGPIO(); +} + +int SectorAtAddress(uint32_t address) { + /* WARNING: this code assumes that the flash sectors are of increasing size: + * first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */ + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i > Config::NumberOf64KSectors) { + return -1; + } + if (i >= 1) { + return Config::NumberOf4KSectors + Config::NumberOf32KSectors + i - 1; + } + i = address >> NumberOfAddressBitsIn32KbyteBlock; + if (i >= 1) { + i = Config::NumberOf4KSectors + i - 1; + assert(i >= 0 && i <= Config::NumberOf32KSectors); + return i; + } + i = address >> NumberOfAddressBitsIn4KbyteBlock; + assert(i <= Config::NumberOf4KSectors); + return i; +} + +void unlockFlash() { + // Warning: unset_memory_mapped_mode must be called before + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); +} + +void MassErase() { + if (Config::NumberOfSectors == 0) { + return; + } + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + send_command(Command::ChipErase); + wait(); + set_as_memory_mapped(); +} + +void __attribute__((noinline)) EraseSector(int i) { + assert(i >= 0 && i < Config::NumberOfSectors); + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + /* WARNING: this code assumes that the flash sectors are of increasing size: + * first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */ + if (i < Config::NumberOf4KSectors) { + send_write_command(Command::Erase4KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn4KbyteBlock), nullptr, 0, sOperatingModes110); + } else if (i < Config::NumberOf4KSectors + Config::NumberOf32KSectors) { + /* If the sector is the number Config::NumberOf4KSectors, we want to write + * at the address 1 << NumberOfAddressBitsIn32KbyteBlock, hence the formula + * (i - Config::NumberOf4KSectors + 1). */ + send_write_command(Command::Erase32KbyteBlock, reinterpret_cast((i - Config::NumberOf4KSectors + 1) << NumberOfAddressBitsIn32KbyteBlock), nullptr, 0, sOperatingModes110); + } else { + /* If the sector is the number + * Config::NumberOf4KSectors - Config::NumberOf32KSectors, we want to write + * at the address 1 << NumberOfAddressBitsIn32KbyteBlock, hence the formula + * (i - Config::NumberOf4KSectors - Config::NumberOf32KSectors + 1). */ + send_write_command(Command::Erase64KbyteBlock, reinterpret_cast((i - Config::NumberOf4KSectors - Config::NumberOf32KSectors + 1) << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0, sOperatingModes110); + } + wait(); + set_as_memory_mapped(); +} + +void __attribute__((noinline)) WriteMemory(uint8_t * destination, const uint8_t * source, size_t length) { + if (Config::NumberOfSectors == 0) { + return; + } + destination -= ExternalFlash::Config::StartAddress; + unset_memory_mapped_mode(); + /* Each 256-byte page of the external flash memory (contained in a previously erased area) + * may be programmed in burst mode with a single Page Program instruction. + * However, when the end of a page is reached, the addressing wraps to the beginning. + * Hence a Page Program instruction must be issued for each page. */ + static constexpr size_t PageSize = 256; + uint8_t offset = reinterpret_cast(destination) & (PageSize - 1); + size_t lengthThatFitsInPage = PageSize - offset; + while (length > 0) { + if (lengthThatFitsInPage > length) { + lengthThatFitsInPage = length; + } + send_command(Command::WriteEnable); + wait(); + + /* Some chips implement 0x32 only, others 0x33 only, we call both. This does + * not seem to affect the writing. */ + send_write_command(Command::QuadPageProgramAT25F641, destination, source, lengthThatFitsInPage, sOperatingModes144); + send_write_command(Command::QuadPageProgramW25Q64JV, destination, source, lengthThatFitsInPage, sOperatingModes114); + + length -= lengthThatFitsInPage; + destination += lengthThatFitsInPage; + source += lengthThatFitsInPage; + lengthThatFitsInPage = PageSize; + wait(); + } + set_as_memory_mapped(); +} + +void JDECid(uint8_t * manufacturerID, uint8_t * memoryType, uint8_t * capacityType) { + unset_memory_mapped_mode(); + struct JEDECId { + uint8_t manufacturerID; + uint8_t memoryType; + uint8_t capacityType; + }; + JEDECId id; + send_read_command(Command::ReadJEDECID, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&id), sizeof(id)); + *manufacturerID = id.manufacturerID; + *memoryType = id.memoryType; + *capacityType = id.capacityType; + set_as_memory_mapped(); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/external_flash_tramp.cpp b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp new file mode 100644 index 00000000000..29a2bf03e0d --- /dev/null +++ b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp @@ -0,0 +1,386 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace ExternalFlash { + +using namespace Regs; + +/* The external flash and the Quad-SPI peripheral support several operating + * modes, corresponding to different numbers of signals used to communicate + * during each phase of the command sequence. + * + * Mode name for | Number of signals used during each phase: + * external flash | Instruction | Address | Alt. bytes | Data + * ----------------+-------------+---------+------------+------ + * Standard SPI | 1 | 1 | 1 | 1 + * Dual-Output SPI | 1 | 1 | 1 | 2 + * Dual-I/O SPI | 1 | 2 | 2 | 2 + * Quad-Output SPI | 1 | 1 | 1 | 4 + * Quad-I/O SPI | 1 | 4 | 4 | 4 + * QPI | 4 | 4 | 4 | 4 + * + * The external flash supports clock frequencies up to 104MHz for all + * instructions, except for Read Data (0x03) which is supported up to 50Mhz. + * + * + * Quad-SPI block diagram + * + * +----------------------+ +------------+ + * | Quad-SPI | | | + * | peripheral | | External | + * | | read | flash | + * AHB <-- | data <-- 32-byte | <-- | memory | + * matrix --> | register --> FIFO | --> | | + * +----------------------+ write +------------+ + * + * Any data transmitted to or from the external flash memory go through a + * 32-byte FIFO. + * + * Read or write operations are performed in burst mode, that is, after any data + * byte is transmitted between the Quad-SPI and the flash memory, the latter + * automatically increments the specified address and the next byte to read or + * write is respectively pushed in or popped from the FIFO. + * And so on, as long as the clock continues. + * + * If the FIFO gets full in a read operation or + * if the FIFO gets empty in a write operation, + * the operation stalls and CLK stays low until firmware services the FIFO. + * + * If the FIFO gets full in a write operation, the operation is stalled until + * the FIFO has enough space to accept the amount of data being written. + * If the FIFO does not have as many bytes as requested by the read operation + * and if BUSY=1, the operation is stalled until enough data is present or until + * the transfer is complete, whichever happens first. */ + +enum class Command : uint8_t { + WriteStatusRegister = 0x01, + PageProgram = 0x02, // Program previously erased memory areas as being "0" + ReadData = 0x03, + ReadStatusRegister1 = 0x05, + WriteEnable = 0x06, + Erase4KbyteBlock = 0x20, + WriteStatusRegister2 = 0x31, + QuadPageProgramW25Q64JV = 0x32, + QuadPageProgramAT25F641 = 0x33, + ReadStatusRegister2 = 0x35, + Erase32KbyteBlock = 0x52, + EnableReset = 0x66, + Reset = 0x99, + ReadJEDECID = 0x9F, + ReleaseDeepPowerDown = 0xAB, + DeepPowerDown = 0xB9, + ChipErase = 0xC7, // Erase the whole chip or a 64-Kbyte block as being "1" + Erase64KbyteBlock = 0xD8, + FastReadQuadIO = 0xEB +}; + +static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +static constexpr uint8_t NumberOfAddressBitsIn32KbyteBlock = 15; +static constexpr uint8_t NumberOfAddressBitsIn4KbyteBlock = 12; + +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : public Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD(QE, 1); + }; +}; + +class OperatingModes { +public: + constexpr OperatingModes( + QUADSPI::CCR::OperatingMode instruction, + QUADSPI::CCR::OperatingMode address, + QUADSPI::CCR::OperatingMode data) : + m_instructionOperatingMode(instruction), + m_addressOperatingMode(address), + m_dataOperatingMode(data) + {} + QUADSPI::CCR::OperatingMode instructionOperatingMode() const { return m_instructionOperatingMode; } + QUADSPI::CCR::OperatingMode addressOperatingMode() const { return m_addressOperatingMode; } + QUADSPI::CCR::OperatingMode dataOperatingMode() const { return m_dataOperatingMode; } +private: + QUADSPI::CCR::OperatingMode m_instructionOperatingMode; + QUADSPI::CCR::OperatingMode m_addressOperatingMode; + QUADSPI::CCR::OperatingMode m_dataOperatingMode; +}; + +/* W25Q64JV does not implement QPI-4-4-4, so we always send the instructions on + * one wire only.*/ +static constexpr OperatingModes sOperatingModes100(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes101(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes110(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData); +static constexpr OperatingModes sOperatingModes111(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single); +static constexpr OperatingModes sOperatingModes114(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad); +static constexpr OperatingModes sOperatingModes144(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad, QUADSPI::CCR::OperatingMode::Quad); + +// static QUADSPI::CCR::OperatingMode sOperatingMode = QUADSPI::CCR::OperatingMode::Single; + +static constexpr int ClockFrequencyDivisor = 2; // F(QUADSPI) = F(AHB) / ClockFrequencyDivisor +static constexpr int FastReadQuadIODummyCycles = 4; // Must be 4 for W25Q64JV (Fig 24.A page 34) and for AT25F641 (table 7.19 page 28) +/* According to datasheets, the CS signal should stay high (deselect the device) + * for t_SHSL = 50ns at least. + * -> Max of 30ns (see AT25F641 Sections 8.7 and 8.8), + * 10ns and 50ns (see W25Q64JV Section 9.6). */ +static constexpr float ChipSelectHighTimeInNanoSeconds = 50.0f; + +static void send_command_full( + QUADSPI::CCR::FunctionalMode functionalMode, + OperatingModes operatingModes, + Command c, + uint8_t * address, + uint32_t altBytes, + size_t numberOfAltBytes, + uint8_t dummyCycles, + uint8_t * data, + size_t dataLength); + +static inline void send_command(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + sOperatingModes100, + c, + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0); +} + +static inline void send_write_command(Command c, uint8_t * address, const uint8_t * data, size_t dataLength, OperatingModes operatingModes) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingModes, + c, + address, + 0, 0, + 0, + const_cast(data), dataLength); +} + +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes101, + c, + address, + 0, 0, + 0, + data, dataLength); +} + +static inline void wait() { + /* The DSB instruction guarantees the completion of a write operation before + * polling the status register. */ + Cache::dsb(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + do { + send_read_command(Command::ReadStatusRegister1, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1)); + } while (statusRegister1.getBUSY()); +} + +static void set_as_memory_mapped() { + /* In memory-mapped mode, all AHB masters may access the external flash memory as an internal one: + * the programmed instruction is sent automatically whenever an AHB master reads in the Quad-SPI flash bank area. + * (The QUADSPI_DLR register has no meaning and any access to QUADSPI_DR returns zero.) + * + * To anticipate sequential reads, the nCS signal is maintained low so as to + * keep the read operation active and prefetch the subsequent bytes in the FIFO. + * + * It goes low, only if the low-power timeout counter is enabled. + * (Flash memories tend to consume more when nCS is held low.) */ + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + sOperatingModes144, + Command::FastReadQuadIO, + reinterpret_cast(FlashAddressSpaceSize), + 0xA0, 1, + FastReadQuadIODummyCycles, + nullptr, 0 + ); +} + +static void unset_memory_mapped_mode() { + /* Reset Continuous Read Mode Bits before issuing normal instructions. */ + uint8_t dummyData; + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + sOperatingModes144, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + FastReadQuadIODummyCycles, + &dummyData, 1 + ); +} + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, OperatingModes operatingModes, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { + /* According to ST's Errata Sheet ES0360, "Wrong data can be read in + * memory-mapped after an indirect mode operation". This is the workaround. */ + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI::CCR::FunctionalMode previousMode = QUADSPI.CCR()->getFMODE(); + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectWrite || previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Reset the address register + QUADSPI.AR()->set(0); // No write to DR should be done after this + if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + // Make an abort request to stop the reading and clear the busy bit + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + } + } else if (QUADSPI.CCR()->getFMODE() == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + /* "BUSY goes high as soon as the first memory-mapped access occurs. Because + * of the prefetch operations, BUSY does not fall until there is a timeout, + * there is an abort, or the peripheral is disabled". (From the Reference + * Manual) + * If we are leaving memory-mapped mode, we send an abort to clear BUSY. */ + QUADSPI.CR()->setABORT(true); + while (QUADSPI.CR()->getABORT()) { + } + } + + assert(QUADSPI.CCR()->getFMODE() != QUADSPI::CCR::FunctionalMode::MemoryMapped || QUADSPI.SR()->getBUSY() == 0); + + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingModes.dataOperatingMode()); + } + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } + ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingModes.addressOperatingMode()); // Seems to always be the same as address mode + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingModes.addressOperatingMode()); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + } + ccr.setIMODE(operatingModes.instructionOperatingMode()); + ccr.setINSTRUCTION(static_cast(c)); + if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setSIOO(true); + /* If the SIOO bit is set, the instruction is sent only for the first command following a write to QUADSPI_CCR. + * Subsequent command sequences skip the instruction phase, until there is a write to QUADSPI_CCR. */ + } + QUADSPI.CCR()->set(ccr); + if (address != reinterpret_cast(FlashAddressSpaceSize)) { + QUADSPI.AR()->set(reinterpret_cast(address)); + } + + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* Wait for the command to be sent. + * "When configured in memory-mapped mode, because of the prefetch operations, + * BUSY does not fall until there is a timeout, there is an abort, or the + * peripheral is disabled.", so we do not wait if the device is in + * memory-mapped mode. */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } + } +} + +void init() { + assert(false); +} + +void shutdown() { + assert(false); +} + +int SectorAtAddress(uint32_t address) { + /* WARNING: this code assumes that the flash sectors are of increasing size: + * first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */ + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i > Config::NumberOf64KSectors) { + return -1; + } + if (i >= 1) { + return Config::NumberOf4KSectors + Config::NumberOf32KSectors + i - 1; + } + i = address >> NumberOfAddressBitsIn32KbyteBlock; + if (i >= 1) { + i = Config::NumberOf4KSectors + i - 1; + assert(i >= 0 && i <= Config::NumberOf32KSectors); + return i; + } + i = address >> NumberOfAddressBitsIn4KbyteBlock; + assert(i <= Config::NumberOf4KSectors); + return i; +} + +void unlockFlash() { + // Warning: unset_memory_mapped_mode must be called before + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); +} + +void JDECid(uint8_t * manufacturerID, uint8_t * memoryType, uint8_t * capacityType) { + unset_memory_mapped_mode(); + struct JEDECId { + uint8_t manufacturerID; + uint8_t memoryType; + uint8_t capacityType; + }; + JEDECId id; + send_read_command(Command::ReadJEDECID, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&id), sizeof(id)); + *manufacturerID = id.manufacturerID; + *memoryType = id.memoryType; + *capacityType = id.capacityType; + set_as_memory_mapped(); +} + +void MassErase() { + // Mass erase is not enabled on kernel + assert(false); +} + +void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length) { + asm("cpsid if"); + reinterpret_cast(Ion::Device::Trampoline::address(Ion::Device::Trampoline::ExternalFlashWriteMemory))(destination, source, length); + asm("cpsie if"); +} + +void EraseSector(int i) { + asm("cpsid if"); + reinterpret_cast(Ion::Device::Trampoline::address(Ion::Device::Trampoline::ExternalFlashEraseSector))(i); + asm("cpsie if"); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/led.cpp b/ion/src/device/bootloader/drivers/led.cpp new file mode 100644 index 00000000000..676464827bf --- /dev/null +++ b/ion/src/device/bootloader/drivers/led.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +namespace Ion { +namespace LED { + +KDColor updateColorWithPlugAndCharge() { + KDColor ledColor = getColor(); + if (ExamMode::FetchExamMode() == 0) { // If exam mode is on, we do not update the LED with the plugged/charging state + if (USB::isPlugged()) { + ledColor = Battery::isCharging() ? KDColorOrange : KDColorGreen; + } else { + ledColor = KDColorBlack; + } + setColor(ledColor); + } + return ledColor; +} + +} +} diff --git a/ion/src/device/bootloader/drivers/power.cpp b/ion/src/device/bootloader/drivers/power.cpp new file mode 100644 index 00000000000..f38f9e9786b --- /dev/null +++ b/ion/src/device/bootloader/drivers/power.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace Ion { +namespace Power { + +/* We isolate the standby code that needs to be executed from the internal + * flash (because the external flash is then shut down). We forbid inlining to + * avoid inlining these instructions in the external flash. */ + +void standby() { + Device::Power::waitUntilOnOffKeyReleased(); + Device::Power::standbyConfiguration(); + Device::Board::shutdownPeripherals(); + Device::Power::internalFlashStandby(); +} + +} +} + +namespace Ion { +namespace Device { +namespace Power { + +void configWakeUp() { + Device::WakeUp::onOnOffKeyDown(); + Device::WakeUp::onUSBPlugging(); + Device::WakeUp::onChargingEvent(); +} + +// Public Power methods +using namespace Device::Regs; + +void standbyConfiguration() { + PWR.CR()->setPPDS(true); // Select standby when the CPU enters deepsleep + PWR.CR()->setCSBF(true); // Clear Standby flag + PWR.CSR()->setBRE(false); // Unable back up RAM (lower power consumption in standby) + PWR.CSR()->setEIWUP(false); // Unable RTC (lower power consumption in standby) + + /* The pin A0 is about to be configured as a wakeup pin. However, the matrix + * keyboard connects pin A0 (row B) with other pins (column 1, column 3...). + * We thus shutdown this pins to avoid the potential pull-up on pin A0 due to + * a keyboard event. For example, if the "Home" key is down, pin A0 is + * pulled-up so enabling it as the wake up pin would trigger a wake up flag + * instantly. */ + Device::Keyboard::shutdown(); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + PWR.CSR2()->setEWUP1(true); // Enable PA0 as wakeup pin + PWR.CR2()->setWUPP1(false); // Define PA0 (wakeup) pin polarity (rising edge) + PWR.CR2()->setCWUPF1(true); // Clear wakeup pin flag for PA0 (if device has already been in standby and woke up) +#endif + + CORTEX.SCR()->setSLEEPDEEP(true); // Allow Cortex-M7 deepsleep state +} + +void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { + // Shutdown all clocks (except the ones used by LED if active and the one used by the flash) + Device::Board::shutdownClocks(isLEDActive); + + Device::Power::enterLowPowerMode(); + + /* A hardware event triggered a wake up, we determine if the device should + * wake up. We wake up when: + * - only the power key was down + * - the unplugged device was plugged + * - the battery stopped charging */ + Device::Board::initClocks(); +} + +void __attribute__((noinline)) internalFlashStandby() { + Device::Board::shutdownClocks(); + Device::Power::enterLowPowerMode(); + Device::Reset::coreWhilePlugged(); +} + +void enterLowPowerMode() { + reinterpret_cast(Ion::Device::Trampoline::address(Ion::Device::Trampoline::Suspend))(); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/power.h b/ion/src/device/bootloader/drivers/power.h new file mode 100644 index 00000000000..a3d142bc1c2 --- /dev/null +++ b/ion/src/device/bootloader/drivers/power.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_N0110_POWER_H +#define ION_DEVICE_N0110_POWER_H + +#include + +namespace Ion { +namespace Device { +namespace Power { + +void standbyConfiguration(); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/reset.cpp b/ion/src/device/bootloader/drivers/reset.cpp new file mode 100644 index 00000000000..f8c30d7e6be --- /dev/null +++ b/ion/src/device/bootloader/drivers/reset.cpp @@ -0,0 +1,13 @@ +#include + +namespace Ion { +namespace Device { +namespace Reset { + +void coreWhilePlugged() { + core(); +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/trampoline.cpp b/ion/src/device/bootloader/drivers/trampoline.cpp new file mode 100644 index 00000000000..be12ea05e81 --- /dev/null +++ b/ion/src/device/bootloader/drivers/trampoline.cpp @@ -0,0 +1,14 @@ +#include +#include + +namespace Ion { +namespace Device { +namespace Trampoline { + +uint32_t address(int index) { + return 0x0020E000 + sizeof(void *) * index; +} + +} +} +} diff --git a/ion/src/device/bootloader/drivers/trampoline.h b/ion/src/device/bootloader/drivers/trampoline.h new file mode 100644 index 00000000000..db10ce4bc8f --- /dev/null +++ b/ion/src/device/bootloader/drivers/trampoline.h @@ -0,0 +1,22 @@ +#ifndef ION_DEVICE_BOOTLOADER_DRIVERS_TRAMPOLINE_H +#define ION_DEVICE_BOOTLOADER_DRIVERS_TRAMPOLINE_H + +#include + +namespace Ion { +namespace Device { +namespace Trampoline { + +constexpr int Suspend = 0; +constexpr int ExternalFlashEraseSector = 1; +constexpr int ExternalFlashWriteMemory = 2; + +// TODO: Use the other available trampolines instead of liba's functions + +uint32_t address(int index); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/drivers/usb.cpp b/ion/src/device/bootloader/drivers/usb.cpp new file mode 100644 index 00000000000..d56cee4752b --- /dev/null +++ b/ion/src/device/bootloader/drivers/usb.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +namespace Ion { +namespace Device { + +using namespace Regs; + +namespace USB { + +bool useAlternateFunctionVbus() { + return Board::pcbVersion() == 0; +} + +void initVbus() { + if (useAlternateFunctionVbus()) { + Config::VbusPin.init(); + } else { + Config::VbusPin.group().MODER()->setMode(Config::VbusPin.pin(), GPIO::MODER::Mode::Input); + Config::VbusPin.group().PUPDR()->setPull(Config::VbusPin.pin(), GPIO::PUPDR::Pull::None); + } +} + +} +} +} diff --git a/ion/src/device/bootloader/platform_info.cpp b/ion/src/device/bootloader/platform_info.cpp new file mode 100644 index 00000000000..c1f83e36a7f --- /dev/null +++ b/ion/src/device/bootloader/platform_info.cpp @@ -0,0 +1,156 @@ +#include +#include + +#ifndef PATCH_LEVEL +#error This file expects PATCH_LEVEL to be defined +#endif + +#ifndef EPSILON_VERSION +#error This file expects EPSILON_VERSION to be defined +#endif + +#ifndef OMEGA_VERSION +#error This file expects OMEGA_VERSION to be defined +#endif + +namespace Ion { +extern char staticStorageArea[]; +} +constexpr void * storageAddress = &(Ion::staticStorageArea); + +class KernelHeader { +public: + constexpr KernelHeader() : + m_header(Magic), + m_version{EPSILON_VERSION}, + m_patchLevel{PATCH_LEVEL}, + m_footer(Magic) { } + const char * version() const { + assert(m_header == Magic); + assert(m_footer == Magic); + return m_version; + } + const char * patchLevel() const { + assert(m_header == Magic); + assert(m_footer == Magic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + uint32_t m_footer; +}; + +const KernelHeader __attribute__((section(".kernel_header"), used)) k_kernelHeader; + +class UserlandHeader { +public: + constexpr UserlandHeader(): + m_header(Magic), + m_expectedEpsilonVersion{EPSILON_VERSION}, + m_storageAddressRAM(storageAddress), + m_storageSizeRAM(Ion::Storage::k_storageSize), + m_externalAppsFlashStart(0xFFFFFFFF), + m_externalAppsFlashEnd(0xFFFFFFFF), + m_externalAppsRAMStart(0xFFFFFFFF), + m_externalAppsRAMEnd(0xFFFFFFFF), + m_footer(Magic), + m_ohm_header(OmegaMagic), + m_omegaVersion{OMEGA_VERSION}, +#ifdef OMEGA_USERNAME + m_username{OMEGA_USERNAME}, +#else + m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, +#endif + m_ohm_footer(OmegaMagic) { } + + const char * omegaVersion() const { + assert(m_storageAddressRAM != nullptr); + assert(m_storageSizeRAM != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_omegaVersion; + } + const volatile char * username() const volatile { + assert(m_storageAddressRAM != nullptr); + assert(m_storageSizeRAM != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_username; + } + +private: + constexpr static uint32_t Magic = 0xDEC0EDFE; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + uint32_t m_header; + const char m_expectedEpsilonVersion[8]; + void * m_storageAddressRAM; + size_t m_storageSizeRAM; + /* We store the range addresses of external apps memory because storing the + * size is complicated due to c++11 constexpr. */ + uint32_t m_externalAppsFlashStart; + uint32_t m_externalAppsFlashEnd; + uint32_t m_externalAppsRAMStart; + uint32_t m_externalAppsRAMEnd; + uint32_t m_footer; + uint32_t m_ohm_header; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_ohm_footer; +}; + +const UserlandHeader __attribute__((section(".userland_header"), used)) k_userlandHeader; + +class SlotInfo { + +public: + SlotInfo() : + m_header(Magic), + m_footer(Magic) {} + + void update() { + m_header = Magic; + m_kernelHeaderAddress = &k_kernelHeader; + m_userlandHeaderAddress = &k_userlandHeader; + m_footer = Magic; + } + +private: + constexpr static uint32_t Magic = 0xEFEEDBBA; + uint32_t m_header; + const KernelHeader * m_kernelHeaderAddress; + const UserlandHeader * m_userlandHeaderAddress; + uint32_t m_footer; + +}; + +const char * Ion::omegaVersion() { + return k_userlandHeader.omegaVersion(); +} + +const volatile char * Ion::username() { + return k_userlandHeader.username(); +} + +const char * Ion::softwareVersion() { + return k_kernelHeader.version(); +} + +const char * Ion::patchLevel() { + return k_kernelHeader.patchLevel(); +} + +SlotInfo * slotInfo() { + static SlotInfo __attribute__((used)) __attribute__((section(".slot_info"))) slotInformation; + return &slotInformation; +} + +void Ion::updateSlotInfo() { + slotInfo()->update(); +} diff --git a/ion/src/device/bootloader/regs/config/cortex.h b/ion/src/device/bootloader/regs/config/cortex.h new file mode 100644 index 00000000000..faee2a9ced9 --- /dev/null +++ b/ion/src/device/bootloader/regs/config/cortex.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_CORTEX_H +#define ION_DEVICE_N0110_REGS_CONFIG_CORTEX_H + +#define REGS_CORTEX_CONFIG_CACHE 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/crc.h b/ion/src/device/bootloader/regs/config/crc.h new file mode 100644 index 00000000000..faa0a263b0a --- /dev/null +++ b/ion/src/device/bootloader/regs/config/crc.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_CRC_H +#define ION_DEVICE_N0110_REGS_CONFIG_CRC_H + +#define REGS_CRC_CONFIG_BYTE_ACCESS 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/flash.h b/ion/src/device/bootloader/regs/config/flash.h new file mode 100644 index 00000000000..770fcc37ad1 --- /dev/null +++ b/ion/src/device/bootloader/regs/config/flash.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_FLASH_H +#define ION_DEVICE_N0110_REGS_CONFIG_FLASH_H + +#define REGS_FLASH_CONFIG_ART 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/pwr.h b/ion/src/device/bootloader/regs/config/pwr.h new file mode 100644 index 00000000000..85f7c0e9d1b --- /dev/null +++ b/ion/src/device/bootloader/regs/config/pwr.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_PWR_H +#define ION_DEVICE_N0110_REGS_CONFIG_PWR_H + +#define REGS_PWR_CONFIG_ADDITIONAL_FIELDS 1 + +#endif diff --git a/ion/src/device/bootloader/regs/config/rcc.h b/ion/src/device/bootloader/regs/config/rcc.h new file mode 100644 index 00000000000..54db5f2116e --- /dev/null +++ b/ion/src/device/bootloader/regs/config/rcc.h @@ -0,0 +1,7 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_RCC_H +#define ION_DEVICE_N0110_REGS_CONFIG_RCC_H + +#define REGS_RCC_CONFIG_F730 1 +#define REGS_RCC_CONFIG_F412 0 + +#endif diff --git a/ion/src/device/bootloader/regs/config/syscfg.h b/ion/src/device/bootloader/regs/config/syscfg.h new file mode 100644 index 00000000000..1165be7c976 --- /dev/null +++ b/ion/src/device/bootloader/regs/config/syscfg.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_SYSCFG_H +#define ION_DEVICE_N0110_REGS_CONFIG_SYSCFG_H + +#define REGS_SYSCFG_CONFIG_F412 0 + +#endif diff --git a/ion/src/device/bootloader/regs/config/usart.h b/ion/src/device/bootloader/regs/config/usart.h new file mode 100644 index 00000000000..5de196af22a --- /dev/null +++ b/ion/src/device/bootloader/regs/config/usart.h @@ -0,0 +1,12 @@ +#ifndef ION_DEVICE_N0110_REGS_CONFIG_USART_H +#define ION_DEVICE_N0110_REGS_CONFIG_USART_H + +#define REGS_USART_SR_OFFSET 0x1C +#define REGS_USART_RDR_OFFSET 0x24 +#define REGS_USART_TDR_OFFSET 0x28 +#define REGS_USART_BRR_OFFSET 0x0C +#define REGS_USART_CR1_OFFSET 0x00 + +#define REGS_USART_CR1_UE_BIT 0 + +#endif diff --git a/ion/src/device/n0100/Makefile b/ion/src/device/n0100/Makefile index ccacc84ab6f..83d810c1029 100644 --- a/ion/src/device/n0100/Makefile +++ b/ion/src/device/n0100/Makefile @@ -7,4 +7,12 @@ ion_device_src += $(addprefix ion/src/device/n0100/drivers/, \ usb.cpp \ ) +ion_device_src += $(addprefix ion/src/device/n0100/boot/, \ + rt0.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/n0100/, \ + platform_info.cpp \ +) + LDSCRIPT ?= ion/src/device/n0100/flash.ld diff --git a/ion/src/device/shared/boot/rt0.cpp b/ion/src/device/n0100/boot/rt0.cpp similarity index 97% rename from ion/src/device/shared/boot/rt0.cpp rename to ion/src/device/n0100/boot/rt0.cpp index 4cb671496b7..454d41c7e19 100644 --- a/ion/src/device/shared/boot/rt0.cpp +++ b/ion/src/device/n0100/boot/rt0.cpp @@ -1,11 +1,11 @@ -#include "isr.h" #include #include #include -#include "../drivers/board.h" -#include "../drivers/rtc.h" -#include "../drivers/reset.h" -#include "../drivers/timing.h" +#include +#include +#include +#include +#include typedef void (*cxx_constructor)(); diff --git a/ion/src/device/n0100/drivers/power.cpp b/ion/src/device/n0100/drivers/power.cpp index 4f7a5b8fdf8..e14012b7116 100644 --- a/ion/src/device/n0100/drivers/power.cpp +++ b/ion/src/device/n0100/drivers/power.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace Ion { @@ -29,6 +30,38 @@ void configWakeUp() { Device::WakeUp::onUSBPlugging(); } +void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { + // Shutdown all clocks (except the ones used by LED if active) + Device::Board::shutdownClocks(isLEDActive); + + Device::Power::enterLowPowerMode(); + + /* A hardware event triggered a wake up, we determine if the device should + * wake up. We wake up when: + * - only the power key was down + * - the unplugged device was plugged + * - the battery stopped charging */ + Device::Board::initClocks(); +} + +void __attribute__((noinline)) internalFlashStandby() { + Device::Board::shutdownClocks(); + Device::Power::enterLowPowerMode(); + Device::Reset::coreWhilePlugged(); +} + +void enterLowPowerMode() { + /* To enter sleep, we need to issue a WFE instruction, which waits for the + * event flag to be set and then clears it. However, the event flag might + * already be on. So the safest way to make sure we actually wait for a new + * event is to force the event flag to on (SEV instruction), use a first WFE + * to clear it, and then a second WFE to wait for a _new_ event. */ + asm("sev"); + asm("wfe"); + asm("nop"); + asm("wfe"); +} + } } } diff --git a/ion/src/device/n0100/platform_info.cpp b/ion/src/device/n0100/platform_info.cpp new file mode 100644 index 00000000000..90d205352ba --- /dev/null +++ b/ion/src/device/n0100/platform_info.cpp @@ -0,0 +1,113 @@ +#include +#include + +#ifndef PATCH_LEVEL +#error This file expects PATCH_LEVEL to be defined +#endif + +#ifndef EPSILON_VERSION +#error This file expects EPSILON_VERSION to be defined +#endif + +#ifndef OMEGA_VERSION +#error This file expects OMEGA_VERSION to be defined +#endif + +#ifndef HEADER_SECTION +#define HEADER_SECTION +#endif + +namespace Ion { +extern char staticStorageArea[]; +} +constexpr void * storageAddress = &(Ion::staticStorageArea); + +class PlatformInfo { +public: + constexpr PlatformInfo() : + m_header(Magic), + m_version{EPSILON_VERSION}, + m_patchLevel{PATCH_LEVEL}, + m_storageAddress(storageAddress), + m_storageSize(Ion::Storage::k_storageSize), + m_footer(Magic), + m_ohm_header(OmegaMagic), + m_omegaVersion{OMEGA_VERSION}, +#ifdef OMEGA_USERNAME + m_username{OMEGA_USERNAME}, +#else + m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, +#endif + m_ohm_footer(OmegaMagic) { } + const char * version() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_version; + } + const char * omegaVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_omegaVersion; + } + const volatile char * username() const volatile { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_username; + } + const char * patchLevel() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + void * m_storageAddress; + size_t m_storageSize; + uint32_t m_footer; + uint32_t m_ohm_header; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_ohm_footer; +}; + +const PlatformInfo HEADER_SECTION platform_infos; + +const char * Ion::softwareVersion() { + return platform_infos.version(); +} + +const char * Ion::omegaVersion() { + return platform_infos.omegaVersion(); +} + +const volatile char * Ion::username() { + return platform_infos.username(); +} + +const char * Ion::patchLevel() { + return platform_infos.patchLevel(); +} + +void Ion::updateSlotInfo() { + +} \ No newline at end of file diff --git a/ion/src/device/n0110/Makefile b/ion/src/device/n0110/Makefile index 3e6297f6aaf..11c3b224f94 100644 --- a/ion/src/device/n0110/Makefile +++ b/ion/src/device/n0110/Makefile @@ -8,4 +8,12 @@ ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ usb.cpp \ ) +ion_device_src += $(addprefix ion/src/device/n0110/boot/, \ + rt0.cpp \ +) + +ion_device_src += $(addprefix ion/src/device/n0110/, \ + platform_info.cpp \ +) + LDSCRIPT ?= ion/src/device/n0110/flash.ld diff --git a/ion/src/device/n0110/boot/rt0.cpp b/ion/src/device/n0110/boot/rt0.cpp new file mode 100644 index 00000000000..454d41c7e19 --- /dev/null +++ b/ion/src/device/n0110/boot/rt0.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*cxx_constructor)(); + +extern "C" { + extern char _data_section_start_flash; + extern char _data_section_start_ram; + extern char _data_section_end_ram; + extern char _bss_section_start_ram; + extern char _bss_section_end_ram; + extern cxx_constructor _init_array_start; + extern cxx_constructor _init_array_end; +} + +void __attribute__((noinline)) abort() { +#ifdef NDEBUG + Ion::Device::Reset::core(); +#else + while (1) { + } +#endif +} + +/* In order to ensure that this method is execute from the external flash, we + * forbid inlining it.*/ + +static void __attribute__((noinline)) external_flash_start() { + /* Init the peripherals. We do not initialize the backlight in case there is + * an on boarding app: indeed, we don't want the user to see the LCD tests + * happening during the on boarding app. The backlight will be initialized + * after the Power-On Self-Test if there is one or before switching to the + * home app otherwise. */ + Ion::Device::Board::initPeripherals(false); + + return ion_main(0, nullptr); +} + +/* This additional function call 'jump_to_external_flash' serves two purposes: + * - By default, the compiler is free to inline any function call he wants. If + * the compiler decides to inline some functions that make use of VFP + * registers, it will need to push VFP them onto the stack in calling + * function's prologue. + * Problem: in start()'s prologue, we would never had a chance to enable the + * FPU since this function is the first thing called after reset. + * We can safely assume that neither memcpy, memset, nor any Ion::Device::init* + * method will use floating-point numbers, but ion_main very well can. + * To make sure ion_main's potential usage of VFP registers doesn't bubble-up to + * start(), we isolate it in its very own non-inlined function call. + * - To avoid jumping on the external flash when it is shut down, we ensure + * there is no symbol references from the internal flash to the external + * flash except this jump. In order to do that, we isolate this + * jump in a symbol that we link in a special section separated from the + * internal flash section. We can than forbid cross references from the + * internal flash to the external flash. */ + +static void __attribute__((noinline)) jump_to_external_flash() { + external_flash_start(); +} + +/* When 'start' is executed, the external flash is supposed to be shutdown. We + * thus forbid inlining to prevent executing this code from external flash + * (just in case 'start' was to be called from the external flash). */ + +void __attribute__((noinline)) start() { + /* This is where execution starts after reset. + * Many things are not initialized yet so the code here has to pay attention. */ + + /* Copy data section to RAM + * The data section is R/W but its initialization value matters. It's stored + * in Flash, but linked as if it were in RAM. Now's our opportunity to copy + * it. Note that until then the data section (e.g. global variables) contains + * garbage values and should not be used. */ + size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram); + memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength); + + /* Zero-out the bss section in RAM + * Until we do, any uninitialized global variable will be unusable. */ + size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram); + memset(&_bss_section_start_ram, 0, bssSectionLength); + + /* Initialize the FPU as early as possible. + * For example, static C++ objects are very likely to manipulate float values */ + Ion::Device::Board::initFPU(); + + /* Call static C++ object constructors + * The C++ compiler creates an initialization function for each static object. + * The linker then stores the address of each of those functions consecutively + * between _init_array_start and _init_array_end. So to initialize all C++ + * static objects we just have to iterate between theses two addresses and + * call the pointed function. */ +#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 +#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS + for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { + (*c)(); + } +#else + /* In practice, static initialized objects are a terrible idea. Since the init + * order is not specified, most often than not this yields the dreaded static + * init order fiasco. How about bypassing the issue altogether? */ + if (&_init_array_start != &_init_array_end) { + abort(); + } +#endif + + Ion::Device::Board::init(); + + /* At this point, we initialized clocks and the external flash but no other + * peripherals. */ + + jump_to_external_flash(); + + abort(); +} + +void __attribute__((interrupt, noinline)) isr_systick() { + auto t = Ion::Device::Timing::MillisElapsed; + t++; + Ion::Device::Timing::MillisElapsed = t; +} diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index 0ed24868c2c..23579c5a262 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -24,6 +24,28 @@ namespace Board { using namespace Regs; +void bootloaderMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + MPU.RNR()->setREGION(7); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setXN(false); + MPU.RASR()->setENABLE(true); + + // 2.3 Enable MPU + MPU.CTRL()->setENABLE(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::disable(); + Cache::dsb(); + Cache::isb(); +} + void initMPU() { // 1. Disable the MPU // 1.1 Memory barrier diff --git a/ion/src/device/n0110/drivers/external_flash.cpp b/ion/src/device/n0110/drivers/external_flash.cpp index 34de8b591a6..612efc01796 100644 --- a/ion/src/device/n0110/drivers/external_flash.cpp +++ b/ion/src/device/n0110/drivers/external_flash.cpp @@ -405,7 +405,7 @@ int SectorAtAddress(uint32_t address) { i = address >> NumberOfAddressBitsIn32KbyteBlock; if (i >= 1) { i = Config::NumberOf4KSectors + i - 1; - assert(i >= 0 && i <= Config::NumberOf32KSectors); + assert(i >= Config::NumberOf4KSectors && i <= Config::NumberOf4KSectors + Config::NumberOf32KSectors); return i; } i = address >> NumberOfAddressBitsIn4KbyteBlock; @@ -471,6 +471,7 @@ void __attribute__((noinline)) WriteMemory(uint8_t * destination, const uint8_t if (Config::NumberOfSectors == 0) { return; } + destination -= ExternalFlash::Config::StartAddress; unset_memory_mapped_mode(); /* Each 256-byte page of the external flash memory (contained in a previously erased area) * may be programmed in burst mode with a single Page Program instruction. diff --git a/ion/src/device/n0110/drivers/power.cpp b/ion/src/device/n0110/drivers/power.cpp index f8f657dea07..1286bc88363 100644 --- a/ion/src/device/n0110/drivers/power.cpp +++ b/ion/src/device/n0110/drivers/power.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include @@ -56,6 +58,43 @@ void standbyConfiguration() { CORTEX.SCR()->setSLEEPDEEP(true); // Allow Cortex-M7 deepsleep state } +void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { + // Shutdown the external flash + Device::ExternalFlash::shutdown(); + // Shutdown all clocks (except the ones used by LED if active) + Device::Board::shutdownClocks(isLEDActive); + + Device::Power::enterLowPowerMode(); + + /* A hardware event triggered a wake up, we determine if the device should + * wake up. We wake up when: + * - only the power key was down + * - the unplugged device was plugged + * - the battery stopped charging */ + Device::Board::initClocks(); + // Init external flash + Device::ExternalFlash::init(); +} + +void __attribute__((noinline)) internalFlashStandby() { + Device::ExternalFlash::shutdown(); + Device::Board::shutdownClocks(); + Device::Power::enterLowPowerMode(); + Device::Reset::coreWhilePlugged(); +} + +void enterLowPowerMode() { + /* To enter sleep, we need to issue a WFE instruction, which waits for the + * event flag to be set and then clears it. However, the event flag might + * already be on. So the safest way to make sure we actually wait for a new + * event is to force the event flag to on (SEV instruction), use a first WFE + * to clear it, and then a second WFE to wait for a _new_ event. */ + asm("sev"); + asm("wfe"); + asm("nop"); + asm("wfe"); +} + } } } diff --git a/ion/src/device/n0110/drivers/power.h b/ion/src/device/n0110/drivers/power.h index a3d142bc1c2..1fbc55acf93 100644 --- a/ion/src/device/n0110/drivers/power.h +++ b/ion/src/device/n0110/drivers/power.h @@ -8,6 +8,7 @@ namespace Device { namespace Power { void standbyConfiguration(); +void internalFlashSuspend(bool isLEDActive); } } diff --git a/ion/src/device/n0110/internal_flash.ld b/ion/src/device/n0110/internal_flash.ld index 76089e337a2..924c67f7d01 100644 --- a/ion/src/device/n0110/internal_flash.ld +++ b/ion/src/device/n0110/internal_flash.ld @@ -1,11 +1,15 @@ /* Same as flash.ld but everything is linked in internal flash */ MEMORY { - INTERNAL_FLASH (rx) : ORIGIN = 0x00200000, LENGTH = 64K + INTERNAL_FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K } STACK_SIZE = 32K; +TRAMPOLINES_OFFSET = 0xE000; +CUSTOM_TRAMPOLINES_OFFSET = 64K - 64; +FLASH_SECOND_SECTOR_OFFSET = 16K; +FLASH_SECOND_SECTOR_SIZE = 16K; SECTIONS { .isr_vector_table ORIGIN(INTERNAL_FLASH) : { @@ -29,6 +33,20 @@ SECTIONS { KEEP(*(.header)) } >INTERNAL_FLASH + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >INTERNAL_FLASH + + .exam_mode_buffer ORIGIN(INTERNAL_FLASH) + FLASH_SECOND_SECTOR_OFFSET : { + _exam_mode_buffer_start = .; + KEEP(*(.exam_mode_buffer)) + /* Note: We don't increment "." here, we set it. */ + . = ORIGIN(INTERNAL_FLASH) + FLASH_SECOND_SECTOR_OFFSET + FLASH_SECOND_SECTOR_SIZE; + _exam_mode_buffer_end = .; + } >INTERNAL_FLASH + .text : { . = ALIGN(4); *(.text) @@ -42,12 +60,6 @@ SECTIONS { _init_array_end = .; } >INTERNAL_FLASH - .rodata : { - . = ALIGN(4); - *(.rodata) - *(.rodata.*) - } >INTERNAL_FLASH - .data : { /* The data section is written to Flash but linked as if it were in RAM. * @@ -69,6 +81,16 @@ SECTIONS { _data_section_end_ram = .; } >SRAM AT> INTERNAL_FLASH + .trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + TRAMPOLINES_OFFSET; + KEEP(*(.trampolines_table)); + } > INTERNAL_FLASH + + .custom_trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + CUSTOM_TRAMPOLINES_OFFSET; + KEEP(*(.custom_trampolines_table)); + } > INTERNAL_FLASH + .bss : { /* The bss section contains data for all uninitialized variables * So like the .data section, it will go in RAM, but unlike the data section diff --git a/ion/src/device/n0110/platform_info.cpp b/ion/src/device/n0110/platform_info.cpp new file mode 100644 index 00000000000..90d205352ba --- /dev/null +++ b/ion/src/device/n0110/platform_info.cpp @@ -0,0 +1,113 @@ +#include +#include + +#ifndef PATCH_LEVEL +#error This file expects PATCH_LEVEL to be defined +#endif + +#ifndef EPSILON_VERSION +#error This file expects EPSILON_VERSION to be defined +#endif + +#ifndef OMEGA_VERSION +#error This file expects OMEGA_VERSION to be defined +#endif + +#ifndef HEADER_SECTION +#define HEADER_SECTION +#endif + +namespace Ion { +extern char staticStorageArea[]; +} +constexpr void * storageAddress = &(Ion::staticStorageArea); + +class PlatformInfo { +public: + constexpr PlatformInfo() : + m_header(Magic), + m_version{EPSILON_VERSION}, + m_patchLevel{PATCH_LEVEL}, + m_storageAddress(storageAddress), + m_storageSize(Ion::Storage::k_storageSize), + m_footer(Magic), + m_ohm_header(OmegaMagic), + m_omegaVersion{OMEGA_VERSION}, +#ifdef OMEGA_USERNAME + m_username{OMEGA_USERNAME}, +#else + m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, +#endif + m_ohm_footer(OmegaMagic) { } + const char * version() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_version; + } + const char * omegaVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_omegaVersion; + } + const volatile char * username() const volatile { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_username; + } + const char * patchLevel() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_ohm_header == OmegaMagic); + assert(m_ohm_footer == OmegaMagic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + uint32_t m_header; + const char m_version[8]; + const char m_patchLevel[8]; + void * m_storageAddress; + size_t m_storageSize; + uint32_t m_footer; + uint32_t m_ohm_header; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_ohm_footer; +}; + +const PlatformInfo HEADER_SECTION platform_infos; + +const char * Ion::softwareVersion() { + return platform_infos.version(); +} + +const char * Ion::omegaVersion() { + return platform_infos.omegaVersion(); +} + +const volatile char * Ion::username() { + return platform_infos.username(); +} + +const char * Ion::patchLevel() { + return platform_infos.patchLevel(); +} + +void Ion::updateSlotInfo() { + +} \ No newline at end of file diff --git a/ion/src/device/shared/boot/Makefile b/ion/src/device/shared/boot/Makefile index 2bb8d22d76b..17dc4a4adbd 100644 --- a/ion/src/device/shared/boot/Makefile +++ b/ion/src/device/shared/boot/Makefile @@ -1,4 +1,3 @@ ion_device_src += $(addprefix ion/src/device/shared/boot/, \ isr.c \ - rt0.cpp \ ) diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 2db043c5fa4..67bfb6ff5ad 100644 --- a/ion/src/device/shared/drivers/Makefile +++ b/ion/src/device/shared/drivers/Makefile @@ -25,5 +25,6 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ swd.cpp \ timing.cpp \ usb.cpp \ + usb_desc.cpp \ wakeup.cpp \ ) diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h index 61b63b2b31a..8d1d1bbb6fa 100644 --- a/ion/src/device/shared/drivers/board.h +++ b/ion/src/device/shared/drivers/board.h @@ -9,6 +9,7 @@ namespace Board { void init(); +void bootloaderMPU(); void initFPU(); void initClocks(); void shutdownClocks(bool keepLEDAwake = false); diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 6f569cd8929..692bd4df8ba 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -46,7 +46,7 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { if (SectorAtAddress((uint32_t)destination) < InternalFlash::Config::NumberOfSectors) { InternalFlash::WriteMemory(destination, source, length); } else { - ExternalFlash::WriteMemory(destination - ExternalFlash::Config::StartAddress, source, length); + ExternalFlash::WriteMemory(destination, source, length); } } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 0e5b35267b5..bba6444c33d 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -100,31 +100,6 @@ namespace Power { // Public Power methods using namespace Device::Regs; -void __attribute__((noinline)) internalFlashSuspend(bool isLEDActive) { - // Shutdown the external flash - Device::ExternalFlash::shutdown(); - // Shutdown all clocks (except the ones used by LED if active) - Device::Board::shutdownClocks(isLEDActive); - - Device::Power::enterLowPowerMode(); - - /* A hardware event triggered a wake up, we determine if the device should - * wake up. We wake up when: - * - only the power key was down - * - the unplugged device was plugged - * - the battery stopped charging */ - Device::Board::initClocks(); - // Init external flash - Device::ExternalFlash::init(); -} - -void __attribute__((noinline)) internalFlashStandby() { - Device::ExternalFlash::shutdown(); - Device::Board::shutdownClocks(); - Device::Power::enterLowPowerMode(); - Device::Reset::coreWhilePlugged(); -} - void stopConfiguration() { PWR.CR()->setMRUDS(true); // Main regulator in Low Voltage and Flash memory in Deep Sleep mode when the device is in Stop mode PWR.CR()->setLPUDS(true); // Low-power regulator in under-drive mode if LPDS bit is set and Flash memory in power-down when the device is in Stop under-drive mode @@ -163,18 +138,6 @@ void waitUntilOnOffKeyReleased() { Timing::msleep(100); } -void enterLowPowerMode() { - /* To enter sleep, we need to issue a WFE instruction, which waits for the - * event flag to be set and then clears it. However, the event flag might - * already be on. So the safest way to make sure we actually wait for a new - * event is to force the event flag to on (SEV instruction), use a first WFE - * to clear it, and then a second WFE to wait for a _new_ event. */ - asm("sev"); - asm("wfe"); - asm("nop"); - asm("wfe"); -} - } } } diff --git a/ion/src/device/shared/drivers/usb.h b/ion/src/device/shared/drivers/usb.h index df27513e7d2..598aa4e9f16 100644 --- a/ion/src/device/shared/drivers/usb.h +++ b/ion/src/device/shared/drivers/usb.h @@ -12,6 +12,7 @@ void initGPIO(); void shutdownGPIO(); void initOTG(); void shutdownOTG(); +const char* stringDescriptor(); } } diff --git a/ion/src/device/shared/drivers/usb_desc.cpp b/ion/src/device/shared/drivers/usb_desc.cpp new file mode 100644 index 00000000000..34ea8527ae8 --- /dev/null +++ b/ion/src/device/shared/drivers/usb_desc.cpp @@ -0,0 +1,15 @@ +#include "usb.h" +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +const char* stringDescriptor() { + return Config::InterfaceStringDescriptor; +} + +} +} +} \ No newline at end of file diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index a79991d7459..02e931b6a21 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -66,6 +66,7 @@ ion_device_dfu_src += $(addprefix ion/src/device/shared/drivers/, \ swd.cpp \ timing.cpp \ usb.cpp \ + usb_desc.cpp \ wakeup.cpp \ ) diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index cf083860c8b..e5eaba3a467 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -3,6 +3,7 @@ #include #include +#include #include #include "dfu_interface.h" #include "stack/device.h" @@ -94,7 +95,7 @@ class Calculator : public Device { m_manufacturerStringDescriptor("NumWorks"), m_productStringDescriptor("NumWorks Calculator"), m_serialNumberStringDescriptor(serialNumber), - m_interfaceStringDescriptor(Config::InterfaceStringDescriptor), + m_interfaceStringDescriptor(stringDescriptor()), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index 82868a88c1d..fb001cef1c4 100644 --- a/ion/src/device/shared/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -14,6 +15,7 @@ namespace USB { typedef void (*PollFunctionPointer)(bool exitWithKeyboard); void DFU(bool exitWithKeyboard) { + Ion::updateSlotInfo(); /* DFU transfers can serve two purposes: * - Transfering RAM data between the machine and a host, e.g. Python scripts diff --git a/ion/src/device/shared/usb/dfu_xip.cpp b/ion/src/device/shared/usb/dfu_xip.cpp index 753948f73a7..fd0447c3268 100644 --- a/ion/src/device/shared/usb/dfu_xip.cpp +++ b/ion/src/device/shared/usb/dfu_xip.cpp @@ -1,9 +1,11 @@ +#include #include "calculator.h" namespace Ion { namespace USB { void DFU(bool exitWithKeyboard) { + Ion::updateSlotInfo(); Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard); } diff --git a/ion/src/shared/dummy/usb.cpp b/ion/src/shared/dummy/usb.cpp index ce3c0fd2cb4..dfe12752e7e 100644 --- a/ion/src/shared/dummy/usb.cpp +++ b/ion/src/shared/dummy/usb.cpp @@ -1,3 +1,4 @@ +#include #include namespace Ion { @@ -25,3 +26,7 @@ void disable() { } } + +void Ion::updateSlotInfo() { + +} diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index f98981af2c5..caa9c1028a8 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -25,6 +25,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ keyboard.cpp \ layout.cpp \ main.cpp \ + platform_info.cpp \ random.cpp \ timing.cpp \ window.cpp \ diff --git a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenevents.c b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenevents.c index 64a399bde4e..66328af32b8 100644 --- a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenevents.c +++ b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenevents.c @@ -587,24 +587,13 @@ Emscripten_HandleResize(int eventType, const EmscriptenUiEvent *uiEvent, void *u double w = window_data->window->w; double h = window_data->window->h; - if(window_data->external_size) { - emscripten_get_element_css_size(window_data->canvas_id, &w, &h); - } - - emscripten_set_canvas_element_size(window_data->canvas_id, w * window_data->pixel_ratio, h * window_data->pixel_ratio); - - /* set_canvas_size unsets this */ - if (!window_data->external_size && window_data->pixel_ratio != 1.0f) { - emscripten_set_element_css_size(window_data->canvas_id, w, h); - } - if (force) { /* force the event to trigger, so pixel ratio changes can be handled */ window_data->window->w = 0; window_data->window->h = 0; } - SDL_SendWindowEvent(window_data->window, SDL_WINDOWEVENT_RESIZED, w, h); + SDL_SendWindowEvent(window_data->window, SDL_WINDOWEVENT_RESIZED, 320, 240); } } diff --git a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c index e20d7a98518..ad9b33cbcb4 100644 --- a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c +++ b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c @@ -197,38 +197,12 @@ Emscripten_CreateWindow(_THIS, SDL_Window * window) } wdata->canvas_id = SDL_strdup("#canvas"); + wdata->pixel_ratio = 320 / 240; - if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { - wdata->pixel_ratio = emscripten_get_device_pixel_ratio(); - } else { - wdata->pixel_ratio = 1.0f; - } - - scaled_w = SDL_floor(window->w * wdata->pixel_ratio); - scaled_h = SDL_floor(window->h * wdata->pixel_ratio); - - /* set a fake size to check if there is any CSS sizing the canvas */ - emscripten_set_canvas_element_size(wdata->canvas_id, 1, 1); - emscripten_get_element_css_size(wdata->canvas_id, &css_w, &css_h); - - wdata->external_size = SDL_floor(css_w) != 1 || SDL_floor(css_h) != 1; - - if ((window->flags & SDL_WINDOW_RESIZABLE) && wdata->external_size) { - /* external css has resized us */ - scaled_w = css_w * wdata->pixel_ratio; - scaled_h = css_h * wdata->pixel_ratio; + window->w = 320; + window->h = 240; - SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, css_w, css_h); - } - emscripten_set_canvas_element_size(wdata->canvas_id, scaled_w, scaled_h); - - /* if the size is not being controlled by css, we need to scale down for hidpi */ - if (!wdata->external_size) { - if (wdata->pixel_ratio != 1.0f) { - /*scale canvas down*/ - emscripten_set_element_css_size(wdata->canvas_id, window->w, window->h); - } - } + emscripten_set_canvas_element_size(wdata->canvas_id, 320, 240); #if SDL_VIDEO_OPENGL_EGL if (window->flags & SDL_WINDOW_OPENGL) { diff --git a/ion/src/shared/platform_info.cpp b/ion/src/simulator/shared/platform_info.cpp similarity index 100% rename from ion/src/shared/platform_info.cpp rename to ion/src/simulator/shared/platform_info.cpp diff --git a/themes/icons.json b/themes/icons.json index 3dc1ae52da1..c3349673dd7 100644 --- a/themes/icons.json +++ b/themes/icons.json @@ -40,5 +40,8 @@ "apps/probability/images/normal_icon.png" : "probability/normal_icon.png", "apps/probability/images/poisson_icon.png" : "probability/poisson_icon.png", "apps/probability/images/student_icon.png" : "probability/student_icon.png", - "apps/probability/images/uniform_icon.png" : "probability/uniform_icon.png" + "apps/probability/images/uniform_icon.png" : "probability/uniform_icon.png", + + "bootloader/cable.png": "bootloader/cable.png", + "bootloader/computer.png": "bootloader/computer.png" } diff --git a/themes/themes/local/epsilon_dark/bootloader/cable.png b/themes/themes/local/epsilon_dark/bootloader/cable.png new file mode 100644 index 00000000000..ac0996ca746 Binary files /dev/null and b/themes/themes/local/epsilon_dark/bootloader/cable.png differ diff --git a/themes/themes/local/epsilon_dark/bootloader/computer.png b/themes/themes/local/epsilon_dark/bootloader/computer.png new file mode 100644 index 00000000000..d30a99af2af Binary files /dev/null and b/themes/themes/local/epsilon_dark/bootloader/computer.png differ diff --git a/themes/themes/local/epsilon_light/bootloader/cable.png b/themes/themes/local/epsilon_light/bootloader/cable.png new file mode 100644 index 00000000000..ac0996ca746 Binary files /dev/null and b/themes/themes/local/epsilon_light/bootloader/cable.png differ diff --git a/themes/themes/local/epsilon_light/bootloader/computer.png b/themes/themes/local/epsilon_light/bootloader/computer.png new file mode 100644 index 00000000000..d30a99af2af Binary files /dev/null and b/themes/themes/local/epsilon_light/bootloader/computer.png differ diff --git a/themes/themes/local/omega_dark/bootloader/cable.png b/themes/themes/local/omega_dark/bootloader/cable.png new file mode 100644 index 00000000000..cf2c2f8455e Binary files /dev/null and b/themes/themes/local/omega_dark/bootloader/cable.png differ diff --git a/themes/themes/local/omega_dark/bootloader/computer.png b/themes/themes/local/omega_dark/bootloader/computer.png new file mode 100644 index 00000000000..0b5a578e696 Binary files /dev/null and b/themes/themes/local/omega_dark/bootloader/computer.png differ diff --git a/themes/themes/local/omega_light/bootloader/cable.png b/themes/themes/local/omega_light/bootloader/cable.png new file mode 100644 index 00000000000..cf2c2f8455e Binary files /dev/null and b/themes/themes/local/omega_light/bootloader/cable.png differ diff --git a/themes/themes/local/omega_light/bootloader/computer.png b/themes/themes/local/omega_light/bootloader/computer.png new file mode 100644 index 00000000000..0b5a578e696 Binary files /dev/null and b/themes/themes/local/omega_light/bootloader/computer.png differ