[bootloader] Make work with Numworks workshop

This commit is contained in:
M4x1m3
2022-02-26 19:33:18 +01:00
parent f2c17121d2
commit a63cbcf0c2
11 changed files with 260 additions and 256 deletions

View File

@@ -34,7 +34,7 @@ HANDY_TARGETS += epsilon.A epsilon.B
.PHONY: %.two_binaries
%.two_binaries: %.elf
@echo "Building an external binary for $<"
$(Q) $(OBJCOPY) -O binary $< $(basename $<).external.bin
$(Q) $(OBJCOPY) -O binary -R .slot_info $< $(basename $<).external.bin
@echo "Padding $(basename $<).external.bin"
$(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin

View File

@@ -39,6 +39,7 @@ const char * omegaVersion();
const char * patchLevel();
const char * fccId();
const char * pcbVersion();
void updateSlotInfo();
// CRC32 : non xor-ed, non reversed, direct, polynomial 4C11DB7
uint32_t crc32Word(const uint32_t * data, size_t length); // Only accepts whole 32bit values

View File

@@ -23,122 +23,6 @@ MEMORY {
STACK_SIZE = 32K;
FIRST_FLASH_SECTOR_SIZE = 4K;
SIGNED_PAYLOAD_LENGTH = 8;
USERLAND_OFFSET = 64K;
SECTIONS {
.signed_payload_prefix ORIGIN(FLASH) : {
FILL(0xFF);
BYTE(0xFF)
. = ORIGIN(FLASH) + SIGNED_PAYLOAD_LENGTH;
} >FLASH
.header : {
KEEP(*(.header))
} >FLASH
.isr_vector_table : AT(ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.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(.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 */
.text ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header) + SIZEOF(.isr_vector_table) + SIZEOF(.exam_mode_buffer) : {
. = 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*)
}
}
INCLUDE ion/src/device/bootloader/bootloader_common.ld;

View File

@@ -23,122 +23,6 @@ MEMORY {
STACK_SIZE = 32K;
FIRST_FLASH_SECTOR_SIZE = 4K;
SIGNED_PAYLOAD_LENGTH = 8;
USERLAND_OFFSET = 64K;
SECTIONS {
.signed_payload_prefix ORIGIN(FLASH) : {
FILL(0xFF);
BYTE(0xFF)
. = ORIGIN(FLASH) + SIGNED_PAYLOAD_LENGTH;
} >FLASH
.header : {
KEEP(*(.header))
} >FLASH
.isr_vector_table : AT(ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.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(.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 */
.text ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header) + SIZEOF(.isr_vector_table) + SIZEOF(.exam_mode_buffer) : {
. = 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*)
}
}
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

@@ -106,7 +106,20 @@ void initMPU() {
MPU.RASR()->setB(0);
MPU.RASR()->setENABLE(true);
// 2.3 Enable MPU
/* 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);

View File

@@ -13,18 +13,14 @@
#error This file expects OMEGA_VERSION to be defined
#endif
#ifndef HEADER_SECTION
#define HEADER_SECTION
#endif
namespace Ion {
extern char staticStorageArea[];
}
constexpr void * storageAddress = &(Ion::staticStorageArea);
class PlatformInfo {
class KernelHeader {
public:
constexpr PlatformInfo() :
constexpr KernelHeader() :
m_header(Magic),
m_version{EPSILON_VERSION},
m_patchLevel{PATCH_LEVEL},
@@ -41,34 +37,120 @@ public:
}
private:
constexpr static uint32_t Magic = 0xDEC00DF0;
constexpr static uint32_t OmegaMagic = 0xEFBEADDE;
uint32_t m_header;
const char m_version[8];
const char m_patchLevel[8];
uint32_t m_footer;
};
const PlatformInfo HEADER_SECTION platform_infos;
const KernelHeader __attribute__((section(".kernel_header"), used)) k_kernelHeader;
const char * Ion::softwareVersion() {
return platform_infos.version();
}
static const char s_omegaVersion[16] = {OMEGA_VERSION};
class UserlandHeader {
public:
constexpr UserlandHeader():
m_header(Magic),
m_expectedEpsilonVersion{EPSILON_VERSION},
m_storageAddressRAM(storageAddress),
m_storageSizeRAM(Ion::Storage::k_storageSize),
m_externalAppsFlashStart(0xFFFFFFFF),
m_externalAppsFlashEnd(0xFFFFFFFF),
m_externalAppsRAMStart(0xFFFFFFFF),
m_externalAppsRAMEnd(0xFFFFFFFF),
m_footer(Magic),
m_ohm_header(OmegaMagic),
m_omegaVersion{OMEGA_VERSION},
#ifdef OMEGA_USERNAME
static const char s_username[16] = {OMEGA_USERNAME};
m_username{OMEGA_USERNAME},
#else
static const char s_username[16] = {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"};
m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"},
#endif
m_ohm_footer(OmegaMagic) { }
const char * omegaVersion() const {
assert(m_storageAddressRAM != nullptr);
assert(m_storageSizeRAM != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_omegaVersion;
}
const volatile char * username() const volatile {
assert(m_storageAddressRAM != nullptr);
assert(m_storageSizeRAM != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_username;
}
private:
constexpr static uint32_t Magic = 0xDEC0EDFE;
constexpr static uint32_t OmegaMagic = 0xEFBEADDE;
uint32_t m_header;
const char m_expectedEpsilonVersion[8];
void * m_storageAddressRAM;
size_t m_storageSizeRAM;
/* We store the range addresses of external apps memory because storing the
* size is complicated due to c++11 constexpr. */
uint32_t m_externalAppsFlashStart;
uint32_t m_externalAppsFlashEnd;
uint32_t m_externalAppsRAMStart;
uint32_t m_externalAppsRAMEnd;
uint32_t m_footer;
uint32_t m_ohm_header;
const char m_omegaVersion[16];
const volatile char m_username[16];
uint32_t m_ohm_footer;
};
const UserlandHeader __attribute__((section(".userland_header"), used)) k_userlandHeader;
class SlotInfo {
public:
SlotInfo() :
m_header(Magic),
m_footer(Magic) {}
void update() {
m_header = Magic;
m_kernelHeaderAddress = &k_kernelHeader;
m_userlandHeaderAddress = &k_userlandHeader;
m_footer = Magic;
}
private:
constexpr static uint32_t Magic = 0xEFEEDBBA;
uint32_t m_header;
const KernelHeader * m_kernelHeaderAddress;
const UserlandHeader * m_userlandHeaderAddress;
uint32_t m_footer;
};
const char * Ion::omegaVersion() {
return s_omegaVersion;
return k_userlandHeader.omegaVersion();
}
const volatile char * Ion::username() {
return s_username;
return k_userlandHeader.username();
}
const char * Ion::softwareVersion() {
return k_kernelHeader.version();
}
const char * Ion::patchLevel() {
return platform_infos.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

@@ -107,3 +107,7 @@ const volatile char * Ion::username() {
const char * Ion::patchLevel() {
return platform_infos.patchLevel();
}
void Ion::updateSlotInfo() {
}

View File

@@ -107,3 +107,7 @@ const volatile char * Ion::username() {
const char * Ion::patchLevel() {
return platform_infos.patchLevel();
}
void Ion::updateSlotInfo() {
}

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);
void DFU(bool exitWithKeyboard) {
Ion::updateSlotInfo();
/* DFU transfers can serve two purposes:
* - Transfering RAM data between the machine and a host, e.g. Python scripts

View File

@@ -1,9 +1,11 @@
#include <ion.h>
#include "calculator.h"
namespace Ion {
namespace USB {
void DFU(bool exitWithKeyboard) {
Ion::updateSlotInfo();
Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard);
}