mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
[bootloader] Compatibility with Omega 2.0
This commit is contained in:
19
.github/workflows/ci-workflow.yml
vendored
19
.github/workflows/ci-workflow.yml
vendored
@@ -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:
|
||||
|
||||
7
Makefile
7
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
|
||||
|
||||
@@ -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:
|
||||
|
||||
2
apps/external/app.h
vendored
2
apps/external/app.h
vendored
@@ -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];
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
21
bootloader/Makefile
Normal file
21
bootloader/Makefile
Normal file
@@ -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)))
|
||||
82
bootloader/boot.cpp
Normal file
82
bootloader/boot.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
#include <bootloader/boot.h>
|
||||
#include <bootloader/slot.h>
|
||||
#include <ion.h>
|
||||
#include <ion/src/device/shared/drivers/reset.h>
|
||||
#include <bootloader/interface.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
28
bootloader/boot.h
Normal file
28
bootloader/boot.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef BOOTLOADER_BOOT_H
|
||||
#define BOOTLOADER_BOOT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
83
bootloader/interface.cpp
Normal file
83
bootloader/interface.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <ion.h>
|
||||
|
||||
#include <bootloader/interface.h>
|
||||
#include <bootloader/slot.h>
|
||||
#include <bootloader/boot.h>
|
||||
|
||||
#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<uint8_t *>(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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
22
bootloader/interface.h
Normal file
22
bootloader/interface.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef BOOTLOADER_INTERFACE
|
||||
#define BOOTLOADER_INTERFACE
|
||||
|
||||
#include <stdint.h>
|
||||
#include <kandinsky/context.h>
|
||||
#include <escher/image.h>
|
||||
|
||||
namespace Bootloader {
|
||||
|
||||
class Interface {
|
||||
|
||||
private:
|
||||
static void drawImage(KDContext* ctx, const Image* image, int offset);
|
||||
|
||||
public:
|
||||
static void draw();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
9
bootloader/jump_to_firmware.s
Normal file
9
bootloader/jump_to_firmware.s
Normal file
@@ -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
|
||||
25
bootloader/kernel_header.cpp
Normal file
25
bootloader/kernel_header.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <bootloader/kernel_header.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
32
bootloader/kernel_header.h
Normal file
32
bootloader/kernel_header.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef BOOTLOADER_KERNEL_HEADER_H
|
||||
#define BOOTLOADER_KERNEL_HEADER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
42
bootloader/main.cpp
Normal file
42
bootloader/main.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
#include <ion.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <bootloader/boot.h>
|
||||
|
||||
__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();
|
||||
}
|
||||
33
bootloader/slot.cpp
Normal file
33
bootloader/slot.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <bootloader/slot.h>
|
||||
#include <ion/src/device/shared/drivers/board.h>
|
||||
|
||||
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(;;);
|
||||
}
|
||||
|
||||
}
|
||||
33
bootloader/slot.h
Normal file
33
bootloader/slot.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef BOOTLOADER_SLOT_H
|
||||
#define BOOTLOADER_SLOT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "kernel_header.h"
|
||||
#include "userland_header.h"
|
||||
|
||||
namespace Bootloader {
|
||||
|
||||
class Slot {
|
||||
|
||||
public:
|
||||
Slot(uint32_t address) :
|
||||
m_kernelHeader(reinterpret_cast<KernelHeader*>(address)),
|
||||
m_userlandHeader(reinterpret_cast<UserlandHeader*>(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
|
||||
42
bootloader/trampoline.cpp
Normal file
42
bootloader/trampoline.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <ion/src/device/shared/drivers/external_flash.h>
|
||||
#include <ion/src/device/n0110/drivers/power.h>
|
||||
|
||||
#include <bootloader/trampoline.h>
|
||||
#include <bootloader/boot.h>
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
}
|
||||
14
bootloader/trampoline.h
Normal file
14
bootloader/trampoline.h
Normal file
@@ -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
|
||||
12
bootloader/usb_desc.cpp
Normal file
12
bootloader/usb_desc.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace Ion {
|
||||
namespace Device {
|
||||
namespace USB {
|
||||
|
||||
const char* stringDescriptor() {
|
||||
return "@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
33
bootloader/userland_header.cpp
Normal file
33
bootloader/userland_header.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <bootloader/userland_header.h>
|
||||
|
||||
namespace Bootloader {
|
||||
|
||||
const UserlandHeader* s_UserlandHeaderA = reinterpret_cast<const struct UserlandHeader*>(0x90010000);
|
||||
const UserlandHeader* s_UserlandHeaderB = reinterpret_cast<const struct UserlandHeader*>(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;
|
||||
}
|
||||
|
||||
}
|
||||
50
bootloader/userland_header.h
Normal file
50
bootloader/userland_header.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef BOOTLOADER_USERLAND_HEADER_H
|
||||
#define BOOTLOADER_USERLAND_HEADER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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!")
|
||||
3
build/platform.device.bootloader.mak
Normal file
3
build/platform.device.bootloader.mak
Normal file
@@ -0,0 +1,3 @@
|
||||
TOOLCHAIN ?= arm-gcc-m7f
|
||||
ION_KEYBOARD_LAYOUT = layout_B3
|
||||
PCB_LATEST = 343 # PCB version 3.43
|
||||
@@ -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 \
|
||||
))
|
||||
|
||||
|
||||
56
build/targets.device.bootloader.mak
Normal file
56
build/targets.device.bootloader.mak
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
19
ion/src/device/bootloader/Makefile
Normal file
19
ion/src/device/bootloader/Makefile
Normal file
@@ -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 \
|
||||
)
|
||||
283
ion/src/device/bootloader/boot/rt0.cpp
Normal file
283
ion/src/device/bootloader/boot/rt0.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ion.h>
|
||||
#include <boot/isr.h>
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/rtc.h>
|
||||
#include <drivers/reset.h>
|
||||
#include <drivers/timing.h>
|
||||
#include <drivers/power.h>
|
||||
#include <drivers/wakeup.h>
|
||||
#include <drivers/battery.h>
|
||||
#include <drivers/usb.h>
|
||||
#include <drivers/led.h>
|
||||
#include <kandinsky.h>
|
||||
#include <regs/config/pwr.h>
|
||||
#include <regs/config/rcc.h>
|
||||
#include <regs/regs.h>
|
||||
|
||||
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;
|
||||
}
|
||||
28
ion/src/device/bootloader/bootloader.A.ld
Normal file
28
ion/src/device/bootloader/bootloader.A.ld
Normal file
@@ -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;
|
||||
28
ion/src/device/bootloader/bootloader.B.ld
Normal file
28
ion/src/device/bootloader/bootloader.B.ld
Normal file
@@ -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;
|
||||
128
ion/src/device/bootloader/bootloader_common.ld
Normal file
128
ion/src/device/bootloader/bootloader_common.ld
Normal file
@@ -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*)
|
||||
}
|
||||
}
|
||||
423
ion/src/device/bootloader/drivers/board.cpp
Normal file
423
ion/src/device/bootloader/drivers/board.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/cache.h>
|
||||
#include <drivers/internal_flash.h>
|
||||
#include <drivers/config/clocks.h>
|
||||
#include <drivers/config/internal_flash.h>
|
||||
#include <drivers/external_flash.h>
|
||||
#include <regs/regs.h>
|
||||
#include <ion.h>
|
||||
|
||||
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<const PCBVersion *>(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex)));
|
||||
}
|
||||
|
||||
void writePCBVersion(PCBVersion version) {
|
||||
uint8_t * destination = reinterpret_cast<uint8_t *>(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex));
|
||||
PCBVersion formattedVersion = ~version;
|
||||
InternalFlash::WriteMemory(destination, reinterpret_cast<uint8_t *>(&formattedVersion), sizeof(formattedVersion));
|
||||
}
|
||||
|
||||
void lockPCBVersion() {
|
||||
uint8_t * destination = reinterpret_cast<uint8_t *>(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex));
|
||||
uint8_t zero = 0;
|
||||
InternalFlash::WriteMemory(destination, &zero, sizeof(zero));
|
||||
}
|
||||
|
||||
bool pcbVersionIsLocked() {
|
||||
return *reinterpret_cast<const uint8_t *>(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
104
ion/src/device/bootloader/drivers/cache.cpp
Normal file
104
ion/src/device/bootloader/drivers/cache.cpp
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
46
ion/src/device/bootloader/drivers/cache.h
Normal file
46
ion/src/device/bootloader/drivers/cache.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef ION_DEVICE_N0110_CACHE_H
|
||||
#define ION_DEVICE_N0110_CACHE_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
25
ion/src/device/bootloader/drivers/config/backlight.h
Normal file
25
ion/src/device/bootloader/drivers/config/backlight.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_BACKLIGHT_H
|
||||
#define ION_DEVICE_N0110_CONFIG_BACKLIGHT_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
/* 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
|
||||
30
ion/src/device/bootloader/drivers/config/battery.h
Normal file
30
ion/src/device/bootloader/drivers/config/battery.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_BATTERY_H
|
||||
#define ION_DEVICE_N0110_CONFIG_BATTERY_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
/* 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
|
||||
68
ion/src/device/bootloader/drivers/config/clocks.h
Normal file
68
ion/src/device/bootloader/drivers/config/clocks.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_CLOCKS_H
|
||||
#define ION_DEVICE_N0110_CONFIG_CLOCKS_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
32
ion/src/device/bootloader/drivers/config/console.h
Normal file
32
ion/src/device/bootloader/drivers/config/console.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_CONSOLE_H
|
||||
#define ION_DEVICE_N0110_CONFIG_CONSOLE_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
38
ion/src/device/bootloader/drivers/config/display.h
Normal file
38
ion/src/device/bootloader/drivers/config/display.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_DISPLAY_H
|
||||
#define ION_DEVICE_N0110_CONFIG_DISPLAY_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
30
ion/src/device/bootloader/drivers/config/exam_mode.h
Normal file
30
ion/src/device/bootloader/drivers/config/exam_mode.h
Normal file
@@ -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
|
||||
45
ion/src/device/bootloader/drivers/config/external_flash.h
Normal file
45
ion/src/device/bootloader/drivers/config/external_flash.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H
|
||||
#define ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
/* 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
|
||||
31
ion/src/device/bootloader/drivers/config/internal_flash.h
Normal file
31
ion/src/device/bootloader/drivers/config/internal_flash.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H
|
||||
#define ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
75
ion/src/device/bootloader/drivers/config/keyboard.h
Normal file
75
ion/src/device/bootloader/drivers/config/keyboard.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_KEYBOARD_H
|
||||
#define ION_DEVICE_N0110_CONFIG_KEYBOARD_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
#include <ion/keyboard.h>
|
||||
|
||||
/* 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
|
||||
28
ion/src/device/bootloader/drivers/config/led.h
Normal file
28
ion/src/device/bootloader/drivers/config/led.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_LED_H
|
||||
#define ION_DEVICE_N0110_CONFIG_LED_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
18
ion/src/device/bootloader/drivers/config/serial_number.h
Normal file
18
ion/src/device/bootloader/drivers/config/serial_number.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H
|
||||
#define ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Device {
|
||||
namespace SerialNumber {
|
||||
namespace Config {
|
||||
|
||||
constexpr uint32_t UniqueDeviceIDAddress = 0x1FF07A10;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
24
ion/src/device/bootloader/drivers/config/swd.h
Normal file
24
ion/src/device/bootloader/drivers/config/swd.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_SWD_H
|
||||
#define ION_DEVICE_N0110_CONFIG_SWD_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
19
ion/src/device/bootloader/drivers/config/timing.h
Normal file
19
ion/src/device/bootloader/drivers/config/timing.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_TIMING_H
|
||||
#define ION_DEVICE_N0110_CONFIG_TIMING_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Device {
|
||||
namespace Timing {
|
||||
namespace Config {
|
||||
|
||||
constexpr static int LoopsPerMillisecond = 4811;
|
||||
constexpr static int LoopsPerMicrosecond = 38;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
31
ion/src/device/bootloader/drivers/config/usb.h
Normal file
31
ion/src/device/bootloader/drivers/config/usb.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef ION_DEVICE_N0110_CONFIG_USB_H
|
||||
#define ION_DEVICE_N0110_CONFIG_USB_H
|
||||
|
||||
#include <regs/regs.h>
|
||||
|
||||
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
|
||||
521
ion/src/device/bootloader/drivers/external_flash.cpp
Normal file
521
ion/src/device/bootloader/drivers/external_flash.cpp
Normal file
@@ -0,0 +1,521 @@
|
||||
#include <drivers/external_flash.h>
|
||||
#include <drivers/cache.h>
|
||||
#include <drivers/config/external_flash.h>
|
||||
#include <drivers/config/clocks.h>
|
||||
#include <ion/timing.h>
|
||||
|
||||
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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&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<uint8_t *>(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<QUADSPI::CCR::Size>(numberOfAltBytes - 1));
|
||||
QUADSPI.ABR()->set(altBytes);
|
||||
}
|
||||
if (address != reinterpret_cast<uint8_t *>(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
|
||||
ccr.setADMODE(operatingModes.addressOperatingMode());
|
||||
ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes);
|
||||
}
|
||||
ccr.setIMODE(operatingModes.instructionOperatingMode());
|
||||
ccr.setINSTRUCTION(static_cast<uint8_t>(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<uint8_t *>(FlashAddressSpaceSize)) {
|
||||
QUADSPI.AR()->set(reinterpret_cast<uint32_t>(address));
|
||||
}
|
||||
|
||||
if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) {
|
||||
for (size_t i=0; i<dataLength; i++) {
|
||||
QUADSPI.DR()->set(data[i]);
|
||||
}
|
||||
} else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) {
|
||||
for (size_t i=0; i<dataLength; i++) {
|
||||
data[i] = QUADSPI.DR()->get();
|
||||
}
|
||||
}
|
||||
|
||||
/* 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<float>(Clocks::Config::AHBFrequency)) / (static_cast<float>(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<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&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<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(¤tStatusRegister2), sizeof(currentStatusRegister2));
|
||||
statusRegister2.setQE(currentStatusRegister2.getQE());
|
||||
|
||||
uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()};
|
||||
send_write_command(Command::WriteStatusRegister, reinterpret_cast<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(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<uint8_t *>(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<uint8_t *>((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<uint8_t *>((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<uint32_t>(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<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&id), sizeof(id));
|
||||
*manufacturerID = id.manufacturerID;
|
||||
*memoryType = id.memoryType;
|
||||
*capacityType = id.capacityType;
|
||||
set_as_memory_mapped();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
386
ion/src/device/bootloader/drivers/external_flash_tramp.cpp
Normal file
386
ion/src/device/bootloader/drivers/external_flash_tramp.cpp
Normal file
@@ -0,0 +1,386 @@
|
||||
#include <drivers/external_flash.h>
|
||||
#include <drivers/cache.h>
|
||||
#include <drivers/config/external_flash.h>
|
||||
#include <drivers/config/clocks.h>
|
||||
#include <drivers/trampoline.h>
|
||||
#include <ion/timing.h>
|
||||
#include <assert.h>
|
||||
|
||||
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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&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<uint8_t *>(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<QUADSPI::CCR::Size>(numberOfAltBytes - 1));
|
||||
QUADSPI.ABR()->set(altBytes);
|
||||
}
|
||||
if (address != reinterpret_cast<uint8_t *>(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
|
||||
ccr.setADMODE(operatingModes.addressOperatingMode());
|
||||
ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes);
|
||||
}
|
||||
ccr.setIMODE(operatingModes.instructionOperatingMode());
|
||||
ccr.setINSTRUCTION(static_cast<uint8_t>(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<uint8_t *>(FlashAddressSpaceSize)) {
|
||||
QUADSPI.AR()->set(reinterpret_cast<uint32_t>(address));
|
||||
}
|
||||
|
||||
if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) {
|
||||
for (size_t i=0; i<dataLength; i++) {
|
||||
QUADSPI.DR()->set(data[i]);
|
||||
}
|
||||
} else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) {
|
||||
for (size_t i=0; i<dataLength; i++) {
|
||||
data[i] = QUADSPI.DR()->get();
|
||||
}
|
||||
}
|
||||
|
||||
/* 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<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(¤tStatusRegister2), sizeof(currentStatusRegister2));
|
||||
statusRegister2.setQE(currentStatusRegister2.getQE());
|
||||
|
||||
uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()};
|
||||
send_write_command(Command::WriteStatusRegister, reinterpret_cast<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(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<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&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<void(*)(uint8_t*, const uint8_t*, size_t)>(Ion::Device::Trampoline::address(Ion::Device::Trampoline::ExternalFlashWriteMemory))(destination, source, length);
|
||||
asm("cpsie if");
|
||||
}
|
||||
|
||||
void EraseSector(int i) {
|
||||
asm("cpsid if");
|
||||
reinterpret_cast<void(*)(int)>(Ion::Device::Trampoline::address(Ion::Device::Trampoline::ExternalFlashEraseSector))(i);
|
||||
asm("cpsie if");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
23
ion/src/device/bootloader/drivers/led.cpp
Normal file
23
ion/src/device/bootloader/drivers/led.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <ion/led.h>
|
||||
#include <ion/battery.h>
|
||||
#include <ion/usb.h>
|
||||
#include <ion/exam_mode.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
87
ion/src/device/bootloader/drivers/power.cpp
Normal file
87
ion/src/device/bootloader/drivers/power.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/power.h>
|
||||
#include <drivers/keyboard.h>
|
||||
#include <drivers/reset.h>
|
||||
#include <drivers/trampoline.h>
|
||||
#include <drivers/wakeup.h>
|
||||
#include <regs/regs.h>
|
||||
|
||||
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<void(*)(void)>(Ion::Device::Trampoline::address(Ion::Device::Trampoline::Suspend))();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
16
ion/src/device/bootloader/drivers/power.h
Normal file
16
ion/src/device/bootloader/drivers/power.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef ION_DEVICE_N0110_POWER_H
|
||||
#define ION_DEVICE_N0110_POWER_H
|
||||
|
||||
#include <ion/src/device/shared/drivers/power.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Device {
|
||||
namespace Power {
|
||||
|
||||
void standbyConfiguration();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
13
ion/src/device/bootloader/drivers/reset.cpp
Normal file
13
ion/src/device/bootloader/drivers/reset.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include <drivers/reset.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Device {
|
||||
namespace Reset {
|
||||
|
||||
void coreWhilePlugged() {
|
||||
core();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
14
ion/src/device/bootloader/drivers/trampoline.cpp
Normal file
14
ion/src/device/bootloader/drivers/trampoline.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <drivers/trampoline.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Device {
|
||||
namespace Trampoline {
|
||||
|
||||
uint32_t address(int index) {
|
||||
return 0x0020E000 + sizeof(void *) * index;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
22
ion/src/device/bootloader/drivers/trampoline.h
Normal file
22
ion/src/device/bootloader/drivers/trampoline.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef ION_DEVICE_BOOTLOADER_DRIVERS_TRAMPOLINE_H
|
||||
#define ION_DEVICE_BOOTLOADER_DRIVERS_TRAMPOLINE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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
|
||||
27
ion/src/device/bootloader/drivers/usb.cpp
Normal file
27
ion/src/device/bootloader/drivers/usb.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <drivers/usb.h>
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/config/usb.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
183
ion/src/device/bootloader/platform_info.cpp
Normal file
183
ion/src/device/bootloader/platform_info.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include <ion.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
6
ion/src/device/bootloader/regs/config/cortex.h
Normal file
6
ion/src/device/bootloader/regs/config/cortex.h
Normal file
@@ -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
|
||||
6
ion/src/device/bootloader/regs/config/crc.h
Normal file
6
ion/src/device/bootloader/regs/config/crc.h
Normal file
@@ -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
|
||||
6
ion/src/device/bootloader/regs/config/flash.h
Normal file
6
ion/src/device/bootloader/regs/config/flash.h
Normal file
@@ -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
|
||||
6
ion/src/device/bootloader/regs/config/pwr.h
Normal file
6
ion/src/device/bootloader/regs/config/pwr.h
Normal file
@@ -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
|
||||
7
ion/src/device/bootloader/regs/config/rcc.h
Normal file
7
ion/src/device/bootloader/regs/config/rcc.h
Normal file
@@ -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
|
||||
6
ion/src/device/bootloader/regs/config/syscfg.h
Normal file
6
ion/src/device/bootloader/regs/config/syscfg.h
Normal file
@@ -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
|
||||
12
ion/src/device/bootloader/regs/config/usart.h
Normal file
12
ion/src/device/bootloader/regs/config/usart.h
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
264
ion/src/device/n0100/boot/rt0.cpp
Normal file
264
ion/src/device/n0100/boot/rt0.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ion.h>
|
||||
#include <boot/isr.h>
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/rtc.h>
|
||||
#include <drivers/reset.h>
|
||||
#include <drivers/timing.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <drivers/power.h>
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/reset.h>
|
||||
#include <drivers/wakeup.h>
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
143
ion/src/device/n0100/platform_info.cpp
Normal file
143
ion/src/device/n0100/platform_info.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include <ion.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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() {
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
#include <drivers/battery.h>
|
||||
#include <drivers/cache.h>
|
||||
#include <drivers/external_flash.h>
|
||||
#include <drivers/led.h>
|
||||
#include <drivers/power.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <boot/isr.h>
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/rtc.h>
|
||||
#include <drivers/reset.h>
|
||||
#include <drivers/usb.h>
|
||||
#include <drivers/timing.h>
|
||||
#include <drivers/power.h>
|
||||
#include <drivers/wakeup.h>
|
||||
#include <drivers/battery.h>
|
||||
#include <drivers/usb.h>
|
||||
#include <drivers/led.h>
|
||||
#include <ion.h>
|
||||
#include <ion/battery.h>
|
||||
#include <ion/led.h>
|
||||
#include <ion/rtc.h>
|
||||
#include <ion/usb.h>
|
||||
#include <kandinsky.h>
|
||||
#include <regs/config/pwr.h>
|
||||
#include <regs/config/rcc.h>
|
||||
#include <regs/regs.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../drivers/board.h"
|
||||
#include "../drivers/reset.h"
|
||||
#include "../drivers/rtc.h"
|
||||
#include "../drivers/timing.h"
|
||||
#include "isr.h"
|
||||
|
||||
typedef void (*cxx_constructor)();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <drivers/board.h>
|
||||
#include <drivers/external_flash.h>
|
||||
#include <drivers/power.h>
|
||||
#include <drivers/keyboard.h>
|
||||
#include <drivers/reset.h>
|
||||
#include <drivers/wakeup.h>
|
||||
#include <regs/regs.h>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Device {
|
||||
namespace Power {
|
||||
|
||||
void standbyConfiguration();
|
||||
void internalFlashSuspend(bool isLEDActive);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
ion_device_src += $(addprefix ion/src/device/shared/boot/, \
|
||||
isr.c \
|
||||
rt0.cpp \
|
||||
)
|
||||
|
||||
@@ -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 *);
|
||||
|
||||
@@ -25,5 +25,6 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \
|
||||
swd.cpp \
|
||||
timing.cpp \
|
||||
usb.cpp \
|
||||
usb_desc.cpp \
|
||||
wakeup.cpp \
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Board {
|
||||
|
||||
void init();
|
||||
|
||||
void bootloaderMPU();
|
||||
void initFPU();
|
||||
void initClocks();
|
||||
void shutdownClocks(bool keepLEDAwake = false);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ void initGPIO();
|
||||
void shutdownGPIO();
|
||||
void initOTG();
|
||||
void shutdownOTG();
|
||||
const char* stringDescriptor();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
15
ion/src/device/shared/drivers/usb_desc.cpp
Normal file
15
ion/src/device/shared/drivers/usb_desc.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "usb.h"
|
||||
#include <ion/usb.h>
|
||||
#include <drivers/config/usb.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Device {
|
||||
namespace USB {
|
||||
|
||||
const char* stringDescriptor() {
|
||||
return Config::InterfaceStringDescriptor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 \
|
||||
)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include <drivers/usb.h>
|
||||
#include <drivers/config/usb.h>
|
||||
#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. */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <ion.h>
|
||||
#include <ion/usb.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
@@ -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
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <ion.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <ion/usb.h>
|
||||
#include <ion.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace USB {
|
||||
@@ -25,3 +25,7 @@ void disable() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Ion::updateSlotInfo() {
|
||||
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
141
ion/src/simulator/shared/platform_info.cpp
Normal file
141
ion/src/simulator/shared/platform_info.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <ion.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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() {
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user