Commit 4b203cac authored by q3k's avatar q3k Committed by Serge Bazanski
Browse files

l0dables: mild refactor

parent 017ef3f9
#include "l0der/l0der.h"
#include <alloca.h>
#include <stdio.h>
#include <string.h>
#include <ff.h>
......@@ -8,6 +9,25 @@
#include "l0der/elf.h"
#include "modules/log.h"
/*
* l0der is, in reality, a boneless operating-system style ELF loader.
*
* To implement it, we parse an ELF file somewhat defensively, trying to
* not DoS ourselves by overallocating RAM (no heap allocations, no recursion).
*
* Currently we support only relocatable, PIE binaries. Adding support for
* static ELFs would be trivial, however we want to keep the possibility to
* shuffle around memory areas in future versions of card10 (possibly giving
* l0dables more RAM than 256k) without having to recompile all l0dables. We
* are also keeping the opportunity to have separate loading schemes in the
* future, for instance:
* - l0dables running next to pycardium, without unloading it
* - multiple l0dables running next to each other (TSR-style)
*
* Thus, we use PIE l0dables to keep these possibilities open and not write down
* a memory map in stone.
*/
/*
* Read an ELF header, check E_IDENT.
*/
......@@ -59,20 +79,31 @@ static int _read_elf_header(FIL *fp, Elf32_Ehdr *hdr)
}
/*
* Read an ELF program header header.
* Read bytes from file at a given offset.
*
* :param FIL* fp: file pointer to read from
* :param uint32_t address: address from which to read
* :param void *data: buffer into which to read
* :param size_t count: amount of bytes to read
* :returns: ``0`` on success or a negative value on error. Possible errors:
*
* - ``-EIO``: Could not read from FAT - address out of bounds of not enough bytes available.
*/
static int _read_program_header(FIL *fp, uint32_t phdr_addr, Elf32_Phdr *phdr)
{
static int _seek_and_read(FIL *fp, uint32_t address, void *data, size_t count) {
FRESULT fres;
if ((fres = f_lseek(fp, phdr_addr)) != FR_OK) {
LOG_ERR("l0der", "_read_program_header: could not seek to 0x%lx: %d", phdr_addr, fres);
if ((fres = f_lseek(fp, address)) != FR_OK) {
LOG_ERR("l0der", "_seek_and_read: could not seek to 0x%lx: %d", address, fres);
return -EIO;
}
unsigned int read;
if ((fres = f_read(fp, phdr, sizeof(Elf32_Phdr), &read)) != FR_OK || read < sizeof(Elf32_Phdr)) {
LOG_ERR("l0der", "_read_program_header: could not read phdr: %d", fres);
if ((fres = f_read(fp, data, count, &read)) != FR_OK || read < count) {
if (fres == FR_OK) {
LOG_ERR("l0der", "_seek_and_read: could not read: wanted %d bytes, got %d", count, read);
} else {
LOG_ERR("l0der", "_seek_and_read: could not read: %d", fres);
}
return -EIO;
}
......@@ -82,23 +113,17 @@ static int _read_program_header(FIL *fp, uint32_t phdr_addr, Elf32_Phdr *phdr)
/*
* Read an ELF program header header.
*/
static int _read_section_header(FIL *fp, uint32_t shdr_addr, Elf32_Shdr *shdr)
static int _read_program_header(FIL *fp, uint32_t phdr_addr, Elf32_Phdr *phdr)
{
FRESULT fres;
if ((fres = f_lseek(fp, shdr_addr)) != FR_OK) {
LOG_ERR("l0der", "_read_section_header: could not seek to 0x%lx: %d", shdr_addr, fres);
return -EIO;
}
unsigned int read;
if ((fres = f_read(fp, shdr, sizeof(Elf32_Shdr), &read)) != FR_OK || read < sizeof(Elf32_Shdr)) {
LOG_ERR("l0der", "_read_section_header: could not read shdr (0x%x bytes) at %08lx: %d, got 0x%x bytes",
sizeof(Elf32_Shdr), shdr_addr, fres, read);
return -EIO;
}
return _seek_and_read(fp, phdr_addr, phdr, sizeof(Elf32_Phdr));
}
return 0;
/*
* Read an ELF section header header.
*/
static int _read_section_header(FIL *fp, uint32_t shdr_addr, Elf32_Shdr *shdr)
{
return _seek_and_read(fp, shdr_addr, shdr, sizeof(Elf32_Shdr));
}
/*
......@@ -165,30 +190,25 @@ static int _check_section_header(FIL *fp, Elf32_Shdr *shdr) {
return 0;
}
/*
* Interpreter expected in l0dable PIE binaries.
*/
static const char *_interpreter = "card10-l0dable";
/*
* Check that the given INTERP program header contains the correct interpreter string.
*/
static int _check_interp(FIL *fp, Elf32_Phdr *phdr)
{
uint32_t buffer_size = 64;
char interp[buffer_size];
int res;
uint32_t buffer_size = strlen(_interpreter) + 1;
char *interp = alloca(buffer_size);
memset(interp, 0, buffer_size);
if (phdr->p_filesz > buffer_size) {
LOG_ERR("l0der", "_check_interp: interpreter size too large");
return -1;
}
FRESULT fres;
if ((fres = f_lseek(fp, phdr->p_offset)) != FR_OK) {
LOG_ERR("l0der", "_check_interp: could not seek to 0x%lx: %d", phdr->p_offset, fres);
return -1;
if ((res = _seek_and_read(fp, phdr->p_offset, interp, buffer_size)) != FR_OK) {
return res;
}
unsigned int read; // unused (we don't care if the read gets truncated)
if ((fres = f_read(fp, interp, buffer_size, &read)) != FR_OK) {
LOG_ERR("l0der", "_check_interp: could not read segment %d", fres);
return -1;
}
if (strncmp(interp, _interpreter, strlen(_interpreter)) != 0) {
LOG_ERR("l0der", "_check_interp: invalid interpreter, want card10-l0dable");
......@@ -198,6 +218,11 @@ static int _check_interp(FIL *fp, Elf32_Phdr *phdr)
return 0;
}
/*
* Calculate address at which binary should be loaded.
*
* Currently this means trying to fit it into core1 RAM.
*/
static int _get_load_addr(uint32_t image_start, uint32_t image_limit, void **load)
{
uint32_t image_size = image_limit - image_start;
......@@ -218,6 +243,11 @@ static int _get_load_addr(uint32_t image_start, uint32_t image_limit, void **loa
return 0;
}
/*
* Load a program segment into memory.
*
* Segment must be a LOAD segment.
*/
static int _load_segment(FIL *fp, void *image_load_addr, Elf32_Phdr *phdr)
{
uint32_t segment_start = (uint32_t)image_load_addr + phdr->p_vaddr;
......@@ -227,23 +257,17 @@ static int _load_segment(FIL *fp, void *image_load_addr, Elf32_Phdr *phdr)
segment_start, segment_limit, phdr->p_filesz);
memset((void *)segment_start, 0, phdr->p_memsz);
FRESULT fres;
unsigned int read;
if ((fres = f_lseek(fp, phdr->p_offset)) != FR_OK) {
LOG_ERR("l0der", "_load_segment: seek failed: %d", fres);
return -EIO;
}
if ((fres = f_read(fp, (void *)segment_start, phdr->p_filesz, &read)) != FR_OK || read != phdr->p_filesz) {
LOG_ERR("l0der", "_load_segment: read failed");
return -EIO;
}
return 0;
return _seek_and_read(fp, phdr->p_offset, (void*)segment_start, phdr->p_filesz);
}
static int _run_relocations(FIL *fp, void *load_addr, Elf32_Ehdr *hdr) {
/*
* Apply dynamic relocations from ELF.
*
* Currently, we only support R_ARM_RELATIVE relocations. These seem to be
* the only one used when making 'standard' PIE binaries on RAM. However, other
* kinds might have to be implemented in the future.
*/
static int _run_relocations(FIL *fp, void *load_addr, uint32_t image_start, uint32_t image_limit, Elf32_Ehdr *hdr) {
int res;
FRESULT fres;
Elf32_Shdr shdr;
......@@ -296,8 +320,12 @@ static int _run_relocations(FIL *fp, void *load_addr, Elf32_Ehdr *hdr) {
LOG_ERR("l0der", "_run_relocations: R_ARM_RELATIVE address must be 4-byte aligned");
return -ENOEXEC;
}
// TODO(q3k): check whether offset is contained in binary.
volatile uint32_t *addr = (uint32_t *)(rel.r_offset + load_addr);
if ((uint32_t)addr < image_start || (uint32_t)addr >= image_limit) {
LOG_ERR("l0der", "_run_relocations: R_ARM_RELATIVE address is outside image boundaries");
return -ENOEXEC;
}
*addr += (uint32_t)load_addr;
break;
default:
......@@ -310,6 +338,9 @@ static int _run_relocations(FIL *fp, void *load_addr, Elf32_Ehdr *hdr) {
return 0;
}
/*
* Load a l0dable PIE binary.
*/
static int _load_pie(FIL *fp, Elf32_Ehdr *hdr, struct l0dable_info *info)
{
int res;
......@@ -363,12 +394,14 @@ static int _load_pie(FIL *fp, Elf32_Ehdr *hdr, struct l0dable_info *info)
}
if (status_interp != 0) {
// Expected interpreter string was not found.
LOG_ERR("l0der", "_load_pie: not a card10 l0dable");
return -ENOEXEC;
}
if (image_limit < image_start) {
// We didn't find any LOAD segment.
LOG_ERR("l0der", "_load_pie: no loadable segments");
return -ENOEXEC;
}
......@@ -401,7 +434,7 @@ static int _load_pie(FIL *fp, Elf32_Ehdr *hdr, struct l0dable_info *info)
// Run relocations.
if ((res = _run_relocations(fp, load_addr, hdr)) != 0) {
if ((res = _run_relocations(fp, load_addr, image_start, image_limit, hdr)) != 0) {
return res;
}
......
......@@ -22,6 +22,11 @@ struct l0dable_info {
*
* :param const char *path: Path of l0dable on FAT filesystem.
* :param l0dable_info l0dable: Information about loaded l0dable.
* :returns: ``0`` on success or a negative value on error.
* :returns: ``0`` on success or a negative value on error. Possible errors:
*
* - ``-ENOENT``: l0dable not present at given path.
* - ``-EIO``: Read failed: l0dable corrupted or truncated.
* - ``-ENOEXEC``: Corrupted/invalid l0dable.
* - ``-ENOMEM``: l0dable too large to fit in RAM.
*/
int l0der_load_path(const char *path, struct l0dable_info *l0dable);
blinky
======
It works! Blink it!
This is a battery-hungry l0dable that shows you how to blink some LEDs while burning CPU time and battery.
#include "max32665.h"
#include "mxc_sys.h"
#include "gcr_regs.h"
#include "icc_regs.h"
#include "pwrseq_regs.h"
#include "epicardium.h"
uint32_t SystemCoreClock = HIRC_FREQ >> 1;
volatile uint32_t tombstone = 0;
#include <math.h>
void SystemCoreClockUpdate(void)
{
uint32_t base_freq, div, clk_src;
int levels[11] = {0};
int levels_display[11] = {0};
// Determine the clock source and frequency
clk_src = (MXC_GCR->clkcn & MXC_F_GCR_CLKCN_CLKSEL);
switch (clk_src)
{
case MXC_S_GCR_CLKCN_CLKSEL_HIRC:
base_freq = HIRC_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_XTAL32M:
base_freq = XTAL32M_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_LIRC8:
base_freq = LIRC8_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_HIRC96:
base_freq = HIRC96_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_HIRC8:
base_freq = HIRC8_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_XTAL32K:
base_freq = XTAL32K_FREQ;
break;
default:
// Values 001 and 111 are reserved, and should never be encountered.
base_freq = HIRC_FREQ;
break;
}
// Clock divider is retrieved to compute system clock
div = (MXC_GCR->clkcn & MXC_F_GCR_CLKCN_PSC) >> MXC_F_GCR_CLKCN_PSC_POS;
// From https://learn.adafruit.com/led-tricks-gamma-correction/the-quick-fix
const uint8_t gamma8[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
SystemCoreClock = base_freq >> div;
void fade() {
for (int i = 0; i < 11; i++) {
int level = gamma8[levels[i]];
if (levels_display[i] > 0) {
epic_leds_set(i, level, 0, 0);
if (level == 0) {
levels_display[i] = 0;
}
}
if (levels[i] > 0) {
levels[i]--;
}
}
}
/*
* main() is called when l0dable is loaded and executed.
*/
int main(void) {
tombstone = 0x10;
// Enable FPU.
SCB->CPACR |= SCB_CPACR_CP10_Msk | SCB_CPACR_CP11_Msk;
__DSB();
__ISB();
tombstone = 0x11;
// Enable ICache1 Clock
MXC_GCR->perckcn1 &= ~(1 << 22);
tombstone = 0x12;
// Invalidate cache and wait until ready
MXC_ICC1->invalidate = 1;
while (!(MXC_ICC1->cache_ctrl & MXC_F_ICC_CACHE_CTRL_CACHE_RDY));
tombstone = 0x13;
// Enable Cache
MXC_ICC1->cache_ctrl |= MXC_F_ICC_CACHE_CTRL_CACHE_EN;
tombstone = 0x14;
SystemCoreClockUpdate();
tombstone = 0x15;
/* TMR5 is used to notify on keyboard interrupt */
//NVIC_EnableIRQ(TMR5_IRQn);
tombstone = 0x16;
epic_leds_set(0, 255, 255, 255);
tombstone = 0x17;
for (;;) {}
// l0dables are running on a separate, exclusive-to-l0dables core.
// Busy-waiting will not block the main operating system on core0 from
// running - but it will drain batteries.
for (;;) {
for (int i = 0; i < 11; i++) {
levels[i] = 255;
levels_display[i] = 1;
for (int j = 0; j < 64; j++) {
fade();
}
}
for (int i = 9; i > 0; i--) {
levels[i] = 255;
levels_display[i] = 1;
for (int j = 0; j < 64; j++) {
fade();
}
}
}
}
......@@ -4,7 +4,7 @@ elf = executable(
name + '.elf',
'main.c',
build_by_default: true,
dependencies: [l0dable_startup, api_caller],
dependencies: [l0dable_startup, api_caller, periphdriver],
link_whole: [l0dable_startup_lib],
link_args: [
'-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
......
/*
* C Runtime for l0dable.
*
* Also known as a startup file.
*
* We provide the following to l0dables:
* - a 8k stack.
* - calling GCC initializers.
* - an ISR vector.
*/
.syntax unified
.arch armv7-m
/*
* ISR Vector.
*
* All of the following (apart from Reset_Handler, which calls main())
* are backed by weak referenced symbols, which you can override just
* by defining them in C code.
*/
.section .data
.align 2
.globl isr_vector
isr_vector:
.globl __isr_vector
__isr_vector:
.long CARD10_STACK_LIMIT /* Top of Stack */
.long Reset_Handler /* Reset Handler */
.long NMI_Handler /* NMI Handler */
.long HardFault_Handler /* Hard Fault Handler */
.long MemManage_Handler /* MPU Fault Handler */
.long BusFault_Handler /* Bus Fault Handler */
.long UsageFault_Handler /* Usage Fault Handler */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long SVC_Handler /* SVCall Handler */
.long 0 /* Reserved */ /* @TODO: Is this the Debug Montior Interrupt? */
.long 0 /* Reserved */
.long PendSV_Handler /* PendSV Handler */
.long SysTick_Handler /* SysTick Handler */
.long Reset_Handler /* Reset Handler */
.long NMI_Handler /* NMI Handler */
.long HardFault_Handler /* Hard Fault Handler */
.long MemManage_Handler /* MPU Fault Handler */
.long BusFault_Handler /* Bus Fault Handler */
.long UsageFault_Handler /* Usage Fault Handler */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long SVC_Handler /* SVCall Handler */
.long 0 /* Reserved */ /* @TODO: Is this the Debug Montior Interrupt? */
.long 0 /* Reserved */
.long PendSV_Handler /* PendSV Handler */
.long SysTick_Handler /* SysTick Handler */
/* Device-specific Interrupts */
.long PF_IRQHandler /* 0x10 0x0040 16: Power Fail */
......@@ -46,7 +64,7 @@ isr_vector:
.long ADC_IRQHandler /* 0x24 0x0090 36: ADC */
.long RSV21_IRQHandler /* 0x25 0x0094 37: Reserved */
.long RSV22_IRQHandler /* 0x26 0x0098 38: Reserved */
.long FLC0_IRQHandler /* 0x27 0x009C 39: Flash Controller */
.long FLC0_IRQHandler /* 0x27 0x009C 39: Flash Controller */
.long GPIO0_IRQHandler /* 0x28 0x00A0 40: GPIO0 */
.long GPIO1_IRQHandler /* 0x29 0x00A4 41: GPIO2 */
.long RSV26_IRQHandler /* 0x2A 0x00A8 42: GPIO3 */
......@@ -107,7 +125,7 @@ isr_vector:
.long WDT2_IRQHandler /* 0x61 0x0184 97: Watchdog Timer 2 */
.long ECC_IRQHandler /* 0x62 0x0188 98: Error Correction */
.long DVS_IRQHandler /* 0x63 0x018C 99: DVS Controller */
.long SIMO_IRQHandler /* 0x64 0x0190 100: SIMO Controller */
.long SIMO_IRQHandler /* 0x64 0x0190 100: SIMO Controller */
.long RPU_IRQHandler /* 0x65 0x0194 101: RPU */ /* @TODO: Is this correct? */
.long AUDIO_IRQHandler /* 0x66 0x0198 102: Audio subsystem */
.long FLC1_IRQHandler /* 0x67 0x019C 103: Flash Control 1 */
......@@ -119,28 +137,43 @@ isr_vector:
.long HTMR0_IRQHandler /* 0x6D 0x01B4 109: HTmr */
.long HTMR1_IRQHandler /* 0x6E 0x01B8 109: HTmr */
/*
* Reset_Handler, or, l0dable entrypoint.
*/
.text
.thumb
.thumb_func
.align 2
Reset_Handler:
// Set stack according to limits from linker script.
/* Set stack according to limits from linker script. */
ldr r0, =CARD10_STACK_LIMIT
mov sp, r0
// Jump to C code
/* Call system initialization from l0dables/lib/hardware.c. */
blx SystemInit
/* Call GCC constructors. */
ldr r0, =__libc_init_array
blx r0
/* Jump to C code */
ldr r0, =main
blx r0
// Spin
// TODO(q3k): let epicardium know we're done
/*
* Spin forever.
* TODO(q3k): let epicardium know we're done.
*/
.spin:
bl .spin
// Macro to define default handlers. Default handler
// will be weak symbol and just dead loops. They can be
// overwritten by other handlers.
.globl _init
_init:
bx lr
/* Macro to define default handlers. Default handler
* will be weak symbol and just dead loops. They can be
* overwritten by other handlers. */
.macro def_irq_handler handler_name
.align 1
.thumb_func
......@@ -151,9 +184,9 @@ Reset_Handler:
.size \handler_name, . - \handler_name
.endm
// Default ISRs.
/*
* Declare all default ISRs.
*/
def_irq_handler NMI_Handler
def_irq_handler HardFault_Handler
def_irq_handler MemManage_Handler
......
/*
* Hardware routines for l0dables.
*
* You shouldn't have to do much here. SystemInit/SystemCoreClockUpdate are
* called automatically before main(), and provide you with a sensible execution
* environment.
*
* However, if you wish, you can define your own SystemInit and take over the
* initialization before main() gets called.
*/
#include "max32665.h"
#include "mxc_sys.h"
#include "gcr_regs.h"
#include "icc_regs.h"
#include "pwrseq_regs.h"
uint32_t SystemCoreClock = HIRC_FREQ >> 1;
void SystemCoreClockUpdate(void)
{
uint32_t base_freq, div, clk_src;
// Determine the clock source and frequency
clk_src = (MXC_GCR->clkcn & MXC_F_GCR_CLKCN_CLKSEL);
switch (clk_src)
{
case MXC_S_GCR_CLKCN_CLKSEL_HIRC:
base_freq = HIRC_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_XTAL32M:
base_freq = XTAL32M_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_LIRC8:
base_freq = LIRC8_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_HIRC96:
base_freq = HIRC96_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_HIRC8:
base_freq = HIRC8_FREQ;
break;
case MXC_S_GCR_CLKCN_CLKSEL_XTAL32K:
base_freq = XTAL32K_FREQ;
break;
default:
// Values 001 and 111 are reserved, and should never be encountered.
base_freq = HIRC_FREQ;
break;
}
// Clock divider is retrieved to compute system clock
div = (MXC_GCR->clkcn & MXC_F_GCR_CLKCN_PSC) >> MXC_F_GCR_CLKCN_PSC_POS;
SystemCoreClock = base_freq >> div;
}