[bootloader] Compatibility with Omega 2.0

This commit is contained in:
Laury
2022-03-17 20:05:20 +01:00
parent 19e5562228
commit 6dc31401fe
112 changed files with 4372 additions and 147 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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
View File

@@ -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];
};

View File

@@ -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
View 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
View 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
View 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
View 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
View 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

View 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

View 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;
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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";
}
}
}
}

View 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;
}
}

View 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

View File

@@ -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

View File

@@ -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!")

View File

@@ -0,0 +1,3 @@
TOOLCHAIN ?= arm-gcc-m7f
ION_KEYBOARD_LAYOUT = layout_B3
PCB_LATEST = 343 # PCB version 3.43

View File

@@ -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 \
))

View 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

View File

@@ -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

View File

@@ -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/*

View File

@@ -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

View File

@@ -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 \

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View 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 \
)

View 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;
}

View 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;

View 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;

View 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*)
}
}

View 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;
}
}
}
}

View 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();
}
}
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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 *>(&currentStatusRegister2), 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();
}
}
}
}

View 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 *>(&currentStatusRegister2), 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");
}
}
}
}

View 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;
}
}
}

View 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))();
}
}
}
}

View 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

View File

@@ -0,0 +1,13 @@
#include <drivers/reset.h>
namespace Ion {
namespace Device {
namespace Reset {
void coreWhilePlugged() {
core();
}
}
}
}

View 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;
}
}
}
}

View 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

View 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);
}
}
}
}
}

View 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();
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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;
}

View File

@@ -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");
}
}
}
}

View 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() {
}

View File

@@ -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

View File

@@ -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)();

View File

@@ -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

View File

@@ -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.

View File

@@ -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");
}
}
}
}

View File

@@ -8,6 +8,7 @@ namespace Device {
namespace Power {
void standbyConfiguration();
void internalFlashSuspend(bool isLEDActive);
}
}

View File

@@ -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

View File

@@ -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() {
}

View File

@@ -1,4 +1,3 @@
ion_device_src += $(addprefix ion/src/device/shared/boot/, \
isr.c \
rt0.cpp \
)

View File

@@ -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 *);

View File

@@ -25,5 +25,6 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \
swd.cpp \
timing.cpp \
usb.cpp \
usb_desc.cpp \
wakeup.cpp \
)

View File

@@ -9,6 +9,7 @@ namespace Board {
void init();
void bootloaderMPU();
void initFPU();
void initClocks();
void shutdownClocks(bool keepLEDAwake = false);

View File

@@ -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);
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -12,6 +12,7 @@ void initGPIO();
void shutdownGPIO();
void initOTG();
void shutdownOTG();
const char* stringDescriptor();
}
}

View 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;
}
}
}
}

View File

@@ -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 \
)

View File

@@ -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. */

View File

@@ -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,

View File

@@ -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

View File

@@ -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);
}

View File

@@ -1,4 +1,4 @@
#include <ion/usb.h>
#include <ion.h>
namespace Ion {
namespace USB {
@@ -25,3 +25,7 @@ void disable() {
}
}
void Ion::updateSlotInfo() {
}

View File

@@ -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 \

View 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