From 6dc31401fe4fd1072478237e10174f08c8fe588f Mon Sep 17 00:00:00 2001 From: Laury Date: Thu, 17 Mar 2022 20:05:20 +0100 Subject: [PATCH] [bootloader] Compatibility with Omega 2.0 --- .github/workflows/ci-workflow.yml | 19 + Makefile | 7 +- apps/code/app.h | 2 +- apps/external/app.h | 2 +- apps/settings/sub_menu/about_controller.cpp | 12 +- bootloader/Makefile | 21 + bootloader/boot.cpp | 82 +++ bootloader/boot.h | 28 + bootloader/interface.cpp | 83 +++ bootloader/interface.h | 22 + bootloader/jump_to_firmware.s | 9 + bootloader/kernel_header.cpp | 25 + bootloader/kernel_header.h | 32 ++ bootloader/main.cpp | 42 ++ bootloader/slot.cpp | 33 ++ bootloader/slot.h | 33 ++ bootloader/trampoline.cpp | 42 ++ bootloader/trampoline.h | 14 + bootloader/usb_desc.cpp | 12 + bootloader/userland_header.cpp | 33 ++ bootloader/userland_header.h | 50 ++ build/config.mak | 2 +- build/device/secure_ext.py | 13 +- build/platform.device.bootloader.mak | 3 + build/rules.mk | 4 +- build/targets.device.bootloader.mak | 56 ++ build/targets.device.mak | 19 - build/targets.device.n0100.mak | 15 + build/targets.device.n0110.mak | 34 +- ion/Makefile | 3 +- ion/include/ion.h | 5 +- ion/include/ion/storage.h | 2 +- ion/src/device/Makefile | 4 +- ion/src/device/bootloader/Makefile | 19 + ion/src/device/bootloader/boot/rt0.cpp | 283 ++++++++++ ion/src/device/bootloader/bootloader.A.ld | 28 + ion/src/device/bootloader/bootloader.B.ld | 28 + .../device/bootloader/bootloader_common.ld | 128 +++++ ion/src/device/bootloader/drivers/board.cpp | 423 ++++++++++++++ ion/src/device/bootloader/drivers/cache.cpp | 104 ++++ ion/src/device/bootloader/drivers/cache.h | 46 ++ .../bootloader/drivers/config/backlight.h | 25 + .../bootloader/drivers/config/battery.h | 30 + .../device/bootloader/drivers/config/clocks.h | 68 +++ .../bootloader/drivers/config/console.h | 32 ++ .../bootloader/drivers/config/display.h | 38 ++ .../bootloader/drivers/config/exam_mode.h | 30 + .../drivers/config/external_flash.h | 45 ++ .../drivers/config/internal_flash.h | 31 ++ .../bootloader/drivers/config/keyboard.h | 75 +++ .../device/bootloader/drivers/config/led.h | 28 + .../bootloader/drivers/config/serial_number.h | 18 + .../device/bootloader/drivers/config/swd.h | 24 + .../device/bootloader/drivers/config/timing.h | 19 + .../device/bootloader/drivers/config/usb.h | 31 ++ .../bootloader/drivers/external_flash.cpp | 521 ++++++++++++++++++ .../drivers/external_flash_tramp.cpp | 386 +++++++++++++ ion/src/device/bootloader/drivers/led.cpp | 23 + ion/src/device/bootloader/drivers/power.cpp | 87 +++ ion/src/device/bootloader/drivers/power.h | 16 + ion/src/device/bootloader/drivers/reset.cpp | 13 + .../device/bootloader/drivers/trampoline.cpp | 14 + .../device/bootloader/drivers/trampoline.h | 22 + ion/src/device/bootloader/drivers/usb.cpp | 27 + ion/src/device/bootloader/platform_info.cpp | 183 ++++++ .../device/bootloader/regs/config/cortex.h | 6 + ion/src/device/bootloader/regs/config/crc.h | 6 + ion/src/device/bootloader/regs/config/flash.h | 6 + ion/src/device/bootloader/regs/config/pwr.h | 6 + ion/src/device/bootloader/regs/config/rcc.h | 7 + .../device/bootloader/regs/config/syscfg.h | 6 + ion/src/device/bootloader/regs/config/usart.h | 12 + ion/src/device/n0100/Makefile | 8 + ion/src/device/n0100/boot/rt0.cpp | 264 +++++++++ ion/src/device/n0100/drivers/power.cpp | 33 ++ ion/src/device/n0100/platform_info.cpp | 143 +++++ ion/src/device/n0110/Makefile | 8 + ion/src/device/{shared => n0110}/boot/rt0.cpp | 28 +- ion/src/device/n0110/drivers/board.cpp | 22 + .../device/n0110/drivers/external_flash.cpp | 3 +- ion/src/device/n0110/drivers/power.cpp | 39 ++ ion/src/device/n0110/drivers/power.h | 1 + ion/src/device/n0110/internal_flash.ld | 36 +- .../n0110}/platform_info.cpp | 54 +- ion/src/device/shared/boot/Makefile | 1 - ion/src/device/shared/boot/isr.h | 1 - ion/src/device/shared/drivers/Makefile | 1 + ion/src/device/shared/drivers/board.h | 1 + ion/src/device/shared/drivers/flash.cpp | 2 +- ion/src/device/shared/drivers/power.cpp | 37 -- ion/src/device/shared/drivers/usb.h | 1 + ion/src/device/shared/drivers/usb_desc.cpp | 15 + ion/src/device/shared/usb/Makefile | 1 + ion/src/device/shared/usb/calculator.h | 3 +- ion/src/device/shared/usb/dfu_interface.cpp | 1 + ion/src/device/shared/usb/dfu_relocated.cpp | 2 + ion/src/device/shared/usb/dfu_xip.cpp | 2 + ion/src/shared/dummy/usb.cpp | 6 +- ion/src/simulator/Makefile | 1 + ion/src/simulator/shared/platform_info.cpp | 141 +++++ kandinsky/Makefile | 2 +- themes/icons.json | 5 +- .../local/epsilon_dark/bootloader/cable.png | Bin 0 -> 179 bytes .../epsilon_dark/bootloader/computer.png | Bin 0 -> 357 bytes .../local/epsilon_light/bootloader/cable.png | Bin 0 -> 179 bytes .../epsilon_light/bootloader/computer.png | Bin 0 -> 357 bytes .../local/omega_dark/bootloader/cable.png | Bin 0 -> 5586 bytes .../local/omega_dark/bootloader/computer.png | Bin 0 -> 10338 bytes .../local/omega_light/bootloader/cable.png | Bin 0 -> 5586 bytes .../local/omega_light/bootloader/computer.png | Bin 0 -> 10338 bytes .../local/upsilon_light/bootloader/cable.png | Bin 0 -> 179 bytes .../upsilon_light/bootloader/computer.png | Bin 0 -> 357 bytes 112 files changed, 4372 insertions(+), 147 deletions(-) create mode 100644 bootloader/Makefile create mode 100644 bootloader/boot.cpp create mode 100644 bootloader/boot.h create mode 100644 bootloader/interface.cpp create mode 100644 bootloader/interface.h create mode 100644 bootloader/jump_to_firmware.s create mode 100644 bootloader/kernel_header.cpp create mode 100644 bootloader/kernel_header.h create mode 100644 bootloader/main.cpp create mode 100644 bootloader/slot.cpp create mode 100644 bootloader/slot.h create mode 100644 bootloader/trampoline.cpp create mode 100644 bootloader/trampoline.h create mode 100644 bootloader/usb_desc.cpp create mode 100644 bootloader/userland_header.cpp create mode 100644 bootloader/userland_header.h create mode 100644 build/platform.device.bootloader.mak create mode 100644 build/targets.device.bootloader.mak create mode 100644 ion/src/device/bootloader/Makefile create mode 100644 ion/src/device/bootloader/boot/rt0.cpp create mode 100644 ion/src/device/bootloader/bootloader.A.ld create mode 100644 ion/src/device/bootloader/bootloader.B.ld create mode 100644 ion/src/device/bootloader/bootloader_common.ld create mode 100644 ion/src/device/bootloader/drivers/board.cpp create mode 100644 ion/src/device/bootloader/drivers/cache.cpp create mode 100644 ion/src/device/bootloader/drivers/cache.h create mode 100644 ion/src/device/bootloader/drivers/config/backlight.h create mode 100644 ion/src/device/bootloader/drivers/config/battery.h create mode 100644 ion/src/device/bootloader/drivers/config/clocks.h create mode 100644 ion/src/device/bootloader/drivers/config/console.h create mode 100644 ion/src/device/bootloader/drivers/config/display.h create mode 100644 ion/src/device/bootloader/drivers/config/exam_mode.h create mode 100644 ion/src/device/bootloader/drivers/config/external_flash.h create mode 100644 ion/src/device/bootloader/drivers/config/internal_flash.h create mode 100644 ion/src/device/bootloader/drivers/config/keyboard.h create mode 100644 ion/src/device/bootloader/drivers/config/led.h create mode 100644 ion/src/device/bootloader/drivers/config/serial_number.h create mode 100644 ion/src/device/bootloader/drivers/config/swd.h create mode 100644 ion/src/device/bootloader/drivers/config/timing.h create mode 100644 ion/src/device/bootloader/drivers/config/usb.h create mode 100644 ion/src/device/bootloader/drivers/external_flash.cpp create mode 100644 ion/src/device/bootloader/drivers/external_flash_tramp.cpp create mode 100644 ion/src/device/bootloader/drivers/led.cpp create mode 100644 ion/src/device/bootloader/drivers/power.cpp create mode 100644 ion/src/device/bootloader/drivers/power.h create mode 100644 ion/src/device/bootloader/drivers/reset.cpp create mode 100644 ion/src/device/bootloader/drivers/trampoline.cpp create mode 100644 ion/src/device/bootloader/drivers/trampoline.h create mode 100644 ion/src/device/bootloader/drivers/usb.cpp create mode 100644 ion/src/device/bootloader/platform_info.cpp create mode 100644 ion/src/device/bootloader/regs/config/cortex.h create mode 100644 ion/src/device/bootloader/regs/config/crc.h create mode 100644 ion/src/device/bootloader/regs/config/flash.h create mode 100644 ion/src/device/bootloader/regs/config/pwr.h create mode 100644 ion/src/device/bootloader/regs/config/rcc.h create mode 100644 ion/src/device/bootloader/regs/config/syscfg.h create mode 100644 ion/src/device/bootloader/regs/config/usart.h create mode 100644 ion/src/device/n0100/boot/rt0.cpp create mode 100644 ion/src/device/n0100/platform_info.cpp rename ion/src/device/{shared => n0110}/boot/rt0.cpp (97%) rename ion/src/{shared => device/n0110}/platform_info.cpp (72%) create mode 100644 ion/src/device/shared/drivers/usb_desc.cpp create mode 100644 ion/src/simulator/shared/platform_info.cpp create mode 100644 themes/themes/local/epsilon_dark/bootloader/cable.png create mode 100644 themes/themes/local/epsilon_dark/bootloader/computer.png create mode 100644 themes/themes/local/epsilon_light/bootloader/cable.png create mode 100644 themes/themes/local/epsilon_light/bootloader/computer.png create mode 100644 themes/themes/local/omega_dark/bootloader/cable.png create mode 100644 themes/themes/local/omega_dark/bootloader/computer.png create mode 100644 themes/themes/local/omega_light/bootloader/cable.png create mode 100644 themes/themes/local/omega_light/bootloader/computer.png create mode 100644 themes/themes/local/upsilon_light/bootloader/cable.png create mode 100644 themes/themes/local/upsilon_light/bootloader/computer.png diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 0f3d3a77f..2dc7f1be6 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -153,6 +153,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 1a424b3ef..be01a126e 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,11 @@ endif 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 @@ -137,6 +141,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 68692a135..4e2ffc9ae 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -75,7 +75,7 @@ public: VariableBoxController * variableBoxController() { return &m_variableBoxController; } - static constexpr int k_pythonHeapSize = 67000; + static constexpr int k_pythonHeapSize = 70000; private: /* Python delegate: diff --git a/apps/external/app.h b/apps/external/app.h index 8077289f5..d1d38487b 100644 --- a/apps/external/app.h +++ b/apps/external/app.h @@ -29,7 +29,7 @@ private: 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/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 812862ab9..a95879f52 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -66,20 +66,20 @@ bool AboutController::handleEvent(Ion::Events::Event event) { } if (childLabel == I18n::Message::UpsilonVersion) { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); - if (strcmp(myCell->accessoryText(), Ion::UpsilonVersion()) == 0) { + if (strcmp(myCell->accessoryText(), Ion::upsilonVersion()) == 0) { myCell->setAccessoryText(MP_STRINGIFY(OMEGA_STATE)); //Change for public/dev return true; } - myCell->setAccessoryText(Ion::UpsilonVersion()); + myCell->setAccessoryText(Ion::upsilonVersion()); return true; } if (childLabel == I18n::Message::OmegaVersion) { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); - if (strcmp(myCell->accessoryText(), Ion::OmegaVersion()) == 0) { + if (strcmp(myCell->accessoryText(), Ion::omegaVersion()) == 0) { myCell->setAccessoryText(MP_STRINGIFY(OMEGA_STATE)); //Change for public/dev return true; } - myCell->setAccessoryText(Ion::OmegaVersion()); + myCell->setAccessoryText(Ion::omegaVersion()); return true; } if (childLabel == I18n::Message::MemUse) { @@ -206,8 +206,8 @@ void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { static const char * messages[] = { (const char*) Ion::username(), - Ion::UpsilonVersion(), - Ion::OmegaVersion(), + Ion::upsilonVersion(), + Ion::omegaVersion(), Ion::softwareVersion(), mpVersion, batteryLevel, diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 000000000..83f835a36 --- /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)) $(simple_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 000000000..fa8d8282e --- /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 000000000..bb5df17c1 --- /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 000000000..e9e87e504 --- /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 000000000..c163ddc4c --- /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 000000000..25a521713 --- /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 000000000..7dac97a06 --- /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 000000000..017036b62 --- /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 000000000..70c632136 --- /dev/null +++ b/bootloader/main.cpp @@ -0,0 +1,42 @@ + +#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 000000000..b23655aed --- /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 000000000..15a883f39 --- /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 000000000..4d1013f34 --- /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 000000000..0bff7a6d2 --- /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 000000000..be4930600 --- /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 000000000..349d7708a --- /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_omegaMagicHeader == OmegaMagic && m_omegaMagicFooter == OmegaMagic; +} + + +const char * UserlandHeader::omegaVersion() const { + return m_omegaVersion; +} + +const bool UserlandHeader::isUpsilon() const { + return m_upsilonMagicHeader == UpsilonMagic && m_upsilonMagicFooter == 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 000000000..dbcfb4539 --- /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_omegaMagicHeader; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; +}; + +extern const UserlandHeader* s_userlandHeaderA; +extern const UserlandHeader* s_userlandHeaderB; + +} + +#endif diff --git a/build/config.mak b/build/config.mak index eaa8c1db1..53d37a8c1 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.22.1 +OMEGA_VERSION ?= 2.0.0 UPSILON_VERSION ?= 1.0.0-dev # OMEGA_USERNAME ?= N/A OMEGA_STATE ?= public diff --git a/build/device/secure_ext.py b/build/device/secure_ext.py index e1939a542..13c6acc17 100644 --- a/build/device/secure_ext.py +++ b/build/device/secure_ext.py @@ -11,15 +11,16 @@ if len(sys.argv) > 1: print("Error: File not found!") sys.exit(-1) file = open(ext_path, "r+b") - first_packet = bytearray(file.read(2048)) - for b in first_packet: - if b != 255: + file.read(MAGIK_POS) + packet = bytearray(file.read(4)) + for b in packet: + if b != 0: print("Error: Invalid file! (maybe already patched?)") sys.exit(-1) for i in range(4): - first_packet[MAGIK_POS + i] = MAGIK_CODE[i] + packet[i] = MAGIK_CODE[i] - file.seek(0) - file.write(first_packet) + file.seek(MAGIK_POS) + file.write(packet) print("External bin Patched!") \ No newline at end of file diff --git a/build/platform.device.bootloader.mak b/build/platform.device.bootloader.mak new file mode 100644 index 000000000..256dd180d --- /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 050c4b805..e6faac450 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 000000000..52e509e2c --- /dev/null +++ b/build/targets.device.bootloader.mak @@ -0,0 +1,56 @@ + +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/* + $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.bin diff --git a/build/targets.device.mak b/build/targets.device.mak index 97293a6cd..f11b11f86 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -54,22 +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/* - $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.external.bin diff --git a/build/targets.device.n0100.mak b/build/targets.device.n0100.mak index aff59a280..ae70afbdb 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 c71f80aae..570ae0b31 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,21 +1,39 @@ -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) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relegated_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/* + $(PYTHON) build/device/secure_ext.py $(BUILD_DIR)/epsilon.onboarding.external.bin diff --git a/ion/Makefile b/ion/Makefile index 1b04baf69..4182a9224 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))" -DUPSILON_VERSION="$(call initializer_list,$(UPSILON_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))" -DUPSILON_VERSION="$(call initializer_list,$(UPSILON_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 490e6a28b..e6072db26 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -35,11 +35,12 @@ namespace Ion { const char * serialNumber(); const volatile char * username(); const char * softwareVersion(); -const char * UpsilonVersion(); -const char * OmegaVersion(); +const char * upsilonVersion(); +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/include/ion/storage.h b/ion/include/ion/storage.h index f1a091d49..fe9f3364a 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -17,7 +17,7 @@ class Storage { public: typedef uint16_t record_size_t; - constexpr static size_t k_storageSize = 64000; + constexpr static size_t k_storageSize = 61000; static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); static Storage * sharedStorage(); diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 963690440..37943dbe2 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -4,13 +4,13 @@ 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 endif -ion_src += ion/src/shared/collect_registers.cpp +ion_device_src += ion/src/shared/collect_registers.cpp IN_FACTORY ?= 0 diff --git a/ion/src/device/bootloader/Makefile b/ion/src/device/bootloader/Makefile new file mode 100644 index 000000000..c5d7208f7 --- /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 000000000..3e5037572 --- /dev/null +++ b/ion/src/device/bootloader/boot/rt0.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} + +/* 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(); +} + +void __attribute__((noinline)) abort_init() { + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::Board::initPeripherals(false); + Ion::Timing::msleep(100); + Ion::Backlight::init(); + Ion::LED::setColor(KDColorRed); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_economy() { + int brightness = Ion::Backlight::brightness(); + bool plugged = Ion::USB::isPlugged(); + while (brightness > 0) { + brightness--; + Ion::Backlight::setBrightness(brightness); + Ion::Timing::msleep(50); + if(plugged || (!plugged && Ion::USB::isPlugged())){ + Ion::Backlight::setBrightness(180); + return; + } + } + Ion::Backlight::shutdown(); + while (1) { + Ion::Device::Power::sleepConfiguration(); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + }; + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + Ion::Backlight::init(); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_sleeping() { + if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { + return; + } + // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal + Ion::Device::Board::shutdownPeripherals(true); + bool plugged = Ion::USB::isPlugged(); + while (1) { + Ion::Device::Battery::initGPIO(); + Ion::Device::USB::initGPIO(); + Ion::Device::LED::init(); + Ion::Device::Power::sleepConfiguration(); + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + Ion::Device::USB::initGPIO(); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + } + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + abort_init(); +} + +void __attribute__((noinline)) abort_core(const char * text) { + Ion::Timing::msleep(100); + int counting; + while (true) { + counting = 0; + if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { + abort_sleeping(); + abort_screen(text); + } + Ion::USB::enable(); + Ion::Battery::Charge previous_state = Ion::Battery::level(); + while (!Ion::USB::isEnumerated()) { + if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { + if (previous_state != Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::Charge::LOW; + counting = 0; + } + Ion::Timing::msleep(500); + if (counting >= 20) { + abort_sleeping(); + abort_screen(text); + counting = -1; + } + counting++; + } else { + if (previous_state == Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::level(); + counting = 0; + } + Ion::Timing::msleep(100); + if (counting >= 300) { + abort_economy(); + counting = -1; + } + counting++; + } + } + Ion::USB::DFU(false, false, 0); + } +} + +void __attribute__((noinline)) abort_screen(const char * text){ + KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); + KDContext* ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); + ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); +} + +void __attribute__((noinline)) abort() { + abort_init(); + abort_screen("HARDFAULT"); + abort_core("HARDFAULT"); +} + +void __attribute__((noinline)) nmi_abort() { + abort_init(); + abort_screen("NMIFAULT"); + abort_core("NMIFAULT"); +} + +void __attribute__((noinline)) bf_abort() { + abort_init(); + abort_screen("BUSFAULT"); + abort_core("BUSFAULT"); +} + +void __attribute__((noinline)) uf_abort() { + abort_init(); + abort_screen("USAGEFAULT"); + abort_core("USAGEFAULT"); +} + +/* 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 000000000..3e1188476 --- /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 000000000..f89ebca2f --- /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 000000000..e8f0c9bbc --- /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 000000000..03e47bc2c --- /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 000000000..0d16f5261 --- /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 000000000..cc047743b --- /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 000000000..6f9483869 --- /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 000000000..07851149b --- /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 000000000..0cc551cb0 --- /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 000000000..58a527201 --- /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 000000000..c14f0d21b --- /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 000000000..e2a2e2aba --- /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 000000000..f245dca20 --- /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 000000000..9fcbeed14 --- /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 000000000..2c98f84a3 --- /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 000000000..1fea36d4a --- /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 000000000..c5ad127c3 --- /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 000000000..1b9fcaa20 --- /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 000000000..fa4502000 --- /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 000000000..61c1e739d --- /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 000000000..1c7686cd4 --- /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 000000000..29a2bf03e --- /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 000000000..676464827 --- /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 000000000..f38f9e978 --- /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 000000000..a3d142bc1 --- /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 000000000..f8c30d7e6 --- /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 000000000..be12ea05e --- /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 000000000..db10ce4bc --- /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 000000000..d56cee475 --- /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 000000000..b3b9a2a00 --- /dev/null +++ b/ion/src/device/bootloader/platform_info.cpp @@ -0,0 +1,183 @@ +#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 UPSILON_VERSION +#error This file expects UPSILON_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_omegaMagicHeader(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_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_UpsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } + + const char * omegaVersion() const { + assert(m_storageAddressRAM != nullptr); + assert(m_storageSizeRAM != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_omegaVersion; + } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_UpsilonVersion; + } + const volatile char * username() const volatile { + assert(m_storageAddressRAM != nullptr); + assert(m_storageSizeRAM != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_username; + } + +private: + constexpr static uint32_t Magic = 0xDEC0EDFE; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; + 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_omegaMagicHeader; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; +}; + +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 char * Ion::upsilonVersion() { + return k_userlandHeader.upsilonVersion(); +} + +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 000000000..faee2a9ce --- /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 000000000..faa0a263b --- /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 000000000..770fcc37a --- /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 000000000..85f7c0e9d --- /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 000000000..54db5f211 --- /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 000000000..1165be7c9 --- /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 000000000..5de196af2 --- /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 ccacc84ab..83d810c10 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/n0100/boot/rt0.cpp b/ion/src/device/n0100/boot/rt0.cpp new file mode 100644 index 000000000..d9b47cf51 --- /dev/null +++ b/ion/src/device/n0100/boot/rt0.cpp @@ -0,0 +1,264 @@ +#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; +} + +/* 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(); +} + +void __attribute__((noinline)) abort_init() { + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::Board::initPeripherals(false); + Ion::Timing::msleep(100); + Ion::Backlight::init(); + Ion::LED::setColor(KDColorRed); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_economy() { + int brightness = Ion::Backlight::brightness(); + bool plugged = Ion::USB::isPlugged(); + while (brightness > 0) { + brightness--; + Ion::Backlight::setBrightness(brightness); + Ion::Timing::msleep(50); + if(plugged || (!plugged && Ion::USB::isPlugged())){ + Ion::Backlight::setBrightness(180); + return; + } + } + Ion::Backlight::shutdown(); + while (1) { + Ion::Device::Power::sleepConfiguration(); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + }; + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + Ion::Backlight::init(); + Ion::Backlight::setBrightness(180); +} + +void __attribute__((noinline)) abort_sleeping() { + if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { + return; + } + // we don't use Ion::Power::suspend because we don't want to move the exam buffer into the internal + Ion::Device::Board::shutdownPeripherals(true); + bool plugged = Ion::USB::isPlugged(); + while (1) { + Ion::Device::Battery::initGPIO(); + Ion::Device::USB::initGPIO(); + Ion::Device::LED::init(); + Ion::Device::Power::sleepConfiguration(); + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::WakeUp::onUSBPlugging(); + Ion::Device::WakeUp::onChargingEvent(); + Ion::Device::Power::internalFlashSuspend(true); + Ion::Device::USB::initGPIO(); + if (!plugged && Ion::USB::isPlugged()) { + break; + } + plugged = Ion::USB::isPlugged(); + } + Ion::Device::Board::setStandardFrequency(Ion::Device::Board::Frequency::High); + abort_init(); +} + +void __attribute__((noinline)) abort_core(const char * text) { + Ion::Timing::msleep(100); + int counting; + while (true) { + counting = 0; + if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { + abort_sleeping(); + abort_screen(text); + } + + Ion::USB::enable(); + Ion::Battery::Charge previous_state = Ion::Battery::level(); + while (!Ion::USB::isEnumerated()) { + if (Ion::Battery::level() == Ion::Battery::Charge::LOW) { + if (previous_state != Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::Charge::LOW; + counting = 0; + } + Ion::Timing::msleep(500); + if (counting >= 20) { + abort_sleeping(); + abort_screen(text); + counting = -1; + } + counting++; + + } else { + if (previous_state == Ion::Battery::Charge::LOW) { + previous_state = Ion::Battery::level(); + counting = 0; + } + Ion::Timing::msleep(100); + if (counting >= 300) { + abort_economy(); + counting = -1; + } + counting++; + } + } + Ion::USB::DFU(false, false, 0); + } +} + +void __attribute__((noinline)) abort_screen(const char * text){ + KDRect screen = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), KDColor::RGB24(0xffffff)); + KDContext* ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("UPSILON CRASH", KDPoint(90, 10), KDFont::LargeFont, KDColorRed, KDColor::RGB24(0xffffff)); + ctx->drawString("An error occurred", KDPoint(10, 30), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("If you have some important data, please", KDPoint(10, 45), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("use bit.ly/upsiBackup to backup them.", KDPoint(10, 60), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("YOU WILL LOSE ALL YOUR DATA", KDPoint(10, 85), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ You can try to reboot by presssing the", KDPoint(10, 110), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("reset button at the back of the calculator", KDPoint(10, 125), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("→ If Upsilon keeps crashing, you can connect", KDPoint(10, 140), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("the calculator to a computer or a phone", KDPoint(10, 160), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString("and try to reinstall Upsilon", KDPoint(10, 175), KDFont::SmallFont, KDColorBlack, KDColor::RGB24(0xffffff)); + ctx->drawString(text, KDPoint(220, 200), KDFont::SmallFont, KDColorRed, KDColor::RGB24(0xffffff)); +} + +void __attribute__((noinline)) abort() { + abort_init(); + abort_screen("HARDFAULT"); + abort_core("HARDFAULT"); +} + +void __attribute__((noinline)) nmi_abort() { + abort_init(); + abort_screen("NMIFAULT"); + abort_core("NMIFAULT"); +} + +void __attribute__((noinline)) bf_abort() { + abort_init(); + abort_screen("BUSFAULT"); + abort_core("BUSFAULT"); +} +void __attribute__((noinline)) uf_abort() { + abort_init(); + abort_screen("USAGEFAULT"); + abort_core("USAGEFAULT"); +} + +/* 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/n0100/drivers/power.cpp b/ion/src/device/n0100/drivers/power.cpp index 4f7a5b8fd..e14012b71 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 000000000..28a9b04dc --- /dev/null +++ b/ion/src/device/n0100/platform_info.cpp @@ -0,0 +1,143 @@ +#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 UPSILON_VERSION +#error This file expects UPSILON_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_omegaMagicHeader(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_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_upsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } + const char * version() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_version; + } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + assert(m_upsilonMagicHeader == UpsilonMagic); + assert(m_upsilonMagicFooter == UpsilonMagic); + return m_upsilonVersion; + } + const char * omegaVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == 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_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == 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_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; + 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_omegaMagicHeader; + const char m_omegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_upsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; + +}; + +const PlatformInfo HEADER_SECTION platform_infos; + +const char * Ion::softwareVersion() { + return platform_infos.version(); +} + +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); +} + +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() { + +} diff --git a/ion/src/device/n0110/Makefile b/ion/src/device/n0110/Makefile index 3e6297f6a..11c3b224f 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/shared/boot/rt0.cpp b/ion/src/device/n0110/boot/rt0.cpp similarity index 97% rename from ion/src/device/shared/boot/rt0.cpp rename to ion/src/device/n0110/boot/rt0.cpp index a2949b3c0..4e2f22a56 100644 --- a/ion/src/device/shared/boot/rt0.cpp +++ b/ion/src/device/n0110/boot/rt0.cpp @@ -1,28 +1,20 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include #include -#include -#include -#include -#include #include #include #include #include -#include -#include - -#include "../drivers/board.h" -#include "../drivers/reset.h" -#include "../drivers/rtc.h" -#include "../drivers/timing.h" -#include "isr.h" typedef void (*cxx_constructor)(); diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index 24b8cfc36..ae6b8ac06 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 34de8b591..612efc017 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 f8f657dea..1286bc883 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 a3d142bc1..1fbc55acf 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 2e1ce78bb..2094a6469 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/shared/platform_info.cpp b/ion/src/device/n0110/platform_info.cpp similarity index 72% rename from ion/src/shared/platform_info.cpp rename to ion/src/device/n0110/platform_info.cpp index b5ab46842..5edbb65db 100644 --- a/ion/src/shared/platform_info.cpp +++ b/ion/src/device/n0110/platform_info.cpp @@ -35,43 +35,43 @@ public: m_storageAddress(storageAddress), m_storageSize(Ion::Storage::k_storageSize), m_footer(Magic), - m_ohm_header(OmegaMagic), + m_omegaMagicHeader(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), - m_ups_header(UpsilonMagic), + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), m_UpsilonVersion{UPSILON_VERSION}, m_osType(OSType), - m_ups_footer(UpsilonMagic) { } + m_upsilonMagicFooter(UpsilonMagic) { } 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); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_version; } - const char * UpsilonVersion() const { + const char * upsilonVersion() 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); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_UpsilonVersion; - } - const char * OmegaVersion() const { + } + 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); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_OmegaVersion; } const volatile char * username() const volatile { @@ -79,8 +79,8 @@ public: assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_username; } const char * patchLevel() const { @@ -88,8 +88,8 @@ public: assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } private: @@ -103,14 +103,14 @@ private: void * m_storageAddress; size_t m_storageSize; uint32_t m_footer; - uint32_t m_ohm_header; + uint32_t m_omegaMagicHeader; const char m_OmegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; - uint32_t m_ups_header; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; const char m_UpsilonVersion[16]; uint32_t m_osType; - uint32_t m_ups_footer; + uint32_t m_upsilonMagicFooter; }; @@ -120,12 +120,12 @@ const char * Ion::softwareVersion() { return platform_infos.version(); } -const char * Ion::UpsilonVersion() { - return platform_infos.UpsilonVersion(); +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); } -const char * Ion::OmegaVersion() { - return platform_infos.OmegaVersion(); +const char * Ion::omegaVersion() { + return platform_infos.omegaVersion(); } const volatile char * Ion::username() { @@ -135,3 +135,7 @@ const volatile char * Ion::username() { const char * Ion::patchLevel() { return platform_infos.patchLevel(); } + +void Ion::updateSlotInfo() { + +} diff --git a/ion/src/device/shared/boot/Makefile b/ion/src/device/shared/boot/Makefile index 2bb8d22d7..17dc4a4ad 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/boot/isr.h b/ion/src/device/shared/boot/isr.h index cec396388..05812cd60 100644 --- a/ion/src/device/shared/boot/isr.h +++ b/ion/src/device/shared/boot/isr.h @@ -8,7 +8,6 @@ extern "C" { void bf_abort(); void uf_abort(); void nmi_abort(); -// Here and below, we are doing operations on the abort handler, not the opposite void abort_init(); void abort_core(const char *); void abort_screen(const char *); diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 2db043c5f..67bfb6ff5 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 61b63b2b3..8d1d1bbb6 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 6f569cd89..692bd4df8 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 0e5b35267..bba6444c3 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 df27513e7..598aa4e9f 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 000000000..3d87d72fe --- /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; +} + +} +} +} diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index a79991d74..02e931b6a 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 5a3d898ef..a2a3581ea 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 @@ public: 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_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index a473e1422..836153d4a 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -296,6 +296,7 @@ void DFUInterface::writeOnMemory() { leaveDFUAndReset(false); return; } + m_largeBuffer[k_externalMagicAddress + i] = 0; } } // We only check the first packet because there is some predictable data in there, diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index 9b68244c4..77433ee4c 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, bool unlocked, int level); void DFU(bool exitWithKeyboard, bool unlocked, int level) { + 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 dce014cd2..b94a77bd5 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, bool unlocked, int level) { + Ion::updateSlotInfo(); Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard, unlocked, level); } diff --git a/ion/src/shared/dummy/usb.cpp b/ion/src/shared/dummy/usb.cpp index 4f3554dff..367321584 100644 --- a/ion/src/shared/dummy/usb.cpp +++ b/ion/src/shared/dummy/usb.cpp @@ -1,4 +1,4 @@ -#include +#include namespace Ion { namespace USB { @@ -25,3 +25,7 @@ void disable() { } } + +void Ion::updateSlotInfo() { + +} diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index f98981af2..caa9c1028 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/shared/platform_info.cpp b/ion/src/simulator/shared/platform_info.cpp new file mode 100644 index 000000000..5edbb65db --- /dev/null +++ b/ion/src/simulator/shared/platform_info.cpp @@ -0,0 +1,141 @@ +#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 UPSILON_VERSION +#error This file expects UPSILON_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_omegaMagicHeader(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_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_UpsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } + const char * version() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_version; + } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_UpsilonVersion; + } + const char * omegaVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == 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_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == 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_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_patchLevel; + } +private: + constexpr static uint32_t Magic = 0xDEC00DF0; + constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; + 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_omegaMagicHeader; + const char m_OmegaVersion[16]; + const volatile char m_username[16]; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; + +}; + +const PlatformInfo HEADER_SECTION platform_infos; + +const char * Ion::softwareVersion() { + return platform_infos.version(); +} + +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); +} + +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() { + +} diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 88cb6885a..3b3f45628 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -94,8 +94,8 @@ kandinsky_src += $(addprefix kandinsky/fonts/, \ SmallFont.ttf \ ) -simple_kandinsky_src = $(kandinsky_src) default_kandinsky_src = $(kandinsky_src) +simple_kandinsky_src = $(kandinsky_src) $(eval $(call raster_font,SmallFont,SmallFontSimple,0,12,7,14)) $(eval $(call raster_font,LargeFont,LargeFontSimple,0,16,10,18)) diff --git a/themes/icons.json b/themes/icons.json index ee6985c86..953bca5cf 100644 --- a/themes/icons.json +++ b/themes/icons.json @@ -41,5 +41,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 0000000000000000000000000000000000000000..ac0996ca74685d68c185ee0bbc4cc3a05cdb6aec GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Y(VV7!2~3A?JidUsVYww$B>FS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d30a99af2af41be5ed19fed9df7ea01d38eb3acb GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5SyhFS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d30a99af2af41be5ed19fed9df7ea01d38eb3acb GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5Syhk!b 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0b5a578e6962685f14dc8c43866fec08c1ac2c71 GIT binary patch literal 10338 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 Vj8me+;?_!lmYTk5g|bcf{{Zqm*T(<= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cf2c2f8455e68d2fcd19efeee26dd2f7f32b4891 GIT binary patch literal 5586 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0b5a578e6962685f14dc8c43866fec08c1ac2c71 GIT binary patch literal 10338 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 Vj8me+;?_!lmYTk5g|bcf{{Zqm*T(<= literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/bootloader/cable.png b/themes/themes/local/upsilon_light/bootloader/cable.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0996ca74685d68c185ee0bbc4cc3a05cdb6aec GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^Y(VV7!2~3A?JidUsVYww$B>FS$tej5D^{!s2nb9_ zNT?`y?&$8$F3il#+`^x3V3@$52{5#SsAJA*S1~lk!zb;$B{EN4o7dwGqi2xG= YtES2ht{+eQfv#ZiboFyt=akR{0N5@#iU0rr literal 0 HcmV?d00001 diff --git a/themes/themes/local/upsilon_light/bootloader/computer.png b/themes/themes/local/upsilon_light/bootloader/computer.png new file mode 100644 index 0000000000000000000000000000000000000000..d30a99af2af41be5ed19fed9df7ea01d38eb3acb GIT binary patch literal 357 zcmeAS@N?(olHy`uVBq!ia0vp^!9c9f!2~1)3=-cmFfi(Px;TbZ%z1lDvFNaYK7}4lH07&=AltcwxxJ!ozZ6At1t`aiZjyXlCbA z&vN@|r8b}I^4L0=aSo2B8^(o^QSwf_z?GwA>L^kc)t8E*qE z?Lzwar#^Om`XhiyW<9qeLqm|(|0z@NMCHYLyMC&+;D45|Yv1WjTJwYJz4UTqj=%aE zU;F*L_~++mPA$J=slN11l}}h{tTDrbIj`ql%a5}2R%Chp=eqg(ke-W{>CvzE{%0x` XaGfW8;hGOH_!vB0{an^LB{Ts5Syh