diff --git a/epicardium/l0der/elf.h b/epicardium/l0der/elf.h new file mode 100644 index 0000000000000000000000000000000000000000..9deb88ea8faae7487bb2e943e8bdb89b81329fde --- /dev/null +++ b/epicardium/l0der/elf.h @@ -0,0 +1,111 @@ +#pragma once + +/* + * 32-bit ELF structures. + * + * ref: Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification + * Version 1.2, May 1995 + * http://refspecs.linuxbase.org/elf/elf.pdf + * + * ref: ELF for the ARM Architecture + * ARM IHI 0044F, current through ABI release 2.10, 24th November 2015 + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044f/IHI0044F_aaelf.pdf + * + */ + +#include <stdint.h> + +typedef uint32_t Elf32_Addr; +typedef uint16_t Elf32_Half; +typedef uint32_t Elf32_Off; +typedef int32_t Elf32_Sword; +typedef uint32_t Elf32_Word; + +#define EI_NIDENT 16 + +typedef struct { + unsigned char e_ident[EI_NIDENT]; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; + Elf32_Off e_phoff; + Elf32_Off e_shoff; + Elf32_Word e_flags; + Elf32_Half e_ehsize; + Elf32_Half e_phentsize; + Elf32_Half e_phnum; + Elf32_Half e_shentsize; + Elf32_Half e_shnum; + Elf32_Half e_shstrndx; +} Elf32_Ehdr; + +#define ET_DYN 3 // Shared object file or PIE binary + +#define EM_ARM 40 + +#define EV_CURRENT 1 + +#define ELFMAG0 0x7f +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' + +#define ELFCLASS32 1 + +#define ELFDATA2LSB 1 + +typedef struct { + Elf32_Word sh_name; + Elf32_Word sh_type; + Elf32_Word sh_flags; + Elf32_Addr sh_addr; + Elf32_Off sh_offset; + Elf32_Word sh_size; + Elf32_Word sh_link; + Elf32_Word sh_info; + Elf32_Word sh_addralign; + Elf32_Word sh_entsize; +} Elf32_Shdr; + +#define SHT_RELA 4 +#define SHT_REL 9 +#define SHT_DYNSYM 11 + +typedef struct { + Elf32_Word st_name; + Elf32_Addr st_value; + Elf32_Word st_size; + unsigned char st_info; + unsigned char st_other; + Elf32_Half st_shndx; +} Elf32_Sym; + +#define ELF32_ST_BIND(i) ((i)>>4) +#define ELF32_ST_TYPE(i) ((i)&0xf) + +#define STB_WEAK 2 + +typedef struct { + Elf32_Addr r_offset; + Elf32_Word r_info; +} Elf32_Rel; + +#define ELF32_R_SYM(i) ((i)>>8) +#define ELF32_R_TYPE(i) ((unsigned char)(i)) + +#define R_ARM_RELATIVE 0x17 + +typedef struct { + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; +} Elf32_Phdr; + +#define PT_LOAD 1 +#define PT_INTERP 3 diff --git a/epicardium/l0der/l0der.c b/epicardium/l0der/l0der.c new file mode 100644 index 0000000000000000000000000000000000000000..bf02ad2dcfcb4cd18c7edc7d7510b2927afc5bb6 --- /dev/null +++ b/epicardium/l0der/l0der.c @@ -0,0 +1,724 @@ +#include "l0der/l0der.h" + +#include <alloca.h> +#include <stdio.h> +#include <string.h> + +#include "epicardium.h" +#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. + */ + +#define WEAK_SYMBOL_MAX 128 + +struct _pie_load_info { + /// Populated by _load_pie + // Addresses within ELF file. + uint32_t image_start; + uint32_t image_limit; + // Highest alignment request for a segment. + uint32_t strictest_alignment; + + /// Populated by _parse_dynamic_symbols + // List of weak symbols for which relocations can be ignored. + uint32_t weak_symbols[WEAK_SYMBOL_MAX]; + uint32_t weak_symbol_count; + + /// Populated by _get_load_addr + // Load address of ELF file. + uint32_t load_address; + // Addresses within memory of ELF file. + uint32_t image_load_start; + uint32_t image_load_limit; + // Stack top. + uint32_t stack_top; +}; + +/* + * Read an ELF header, check E_IDENT. + */ +static int _read_elf_header(int fd, Elf32_Ehdr *hdr) +{ + int res; + + epic_file_seek(fd, 0, SEEK_SET); + + if ((res = epic_file_read(fd, hdr, sizeof(Elf32_Ehdr))) != + sizeof(Elf32_Ehdr)) { + LOG_ERR("l0der", "_read_elf_header: read failed: %d", res); + return res; + } + + if (hdr->e_ident[0] != ELFMAG0 || hdr->e_ident[1] != ELFMAG1 || + hdr->e_ident[2] != ELFMAG2 || hdr->e_ident[3] != ELFMAG3) { + LOG_ERR("l0der", "_read_elf_header: not an ELF file"); + return -ENOEXEC; + } + + if (hdr->e_ident[4] != ELFCLASS32) { + LOG_ERR("l0der", "_read_elf_header: not a 32-bit ELF"); + return -ENOEXEC; + } + + if (hdr->e_ident[5] != ELFDATA2LSB) { + LOG_ERR("l0der", "_read_elf_header: not a little-endian ELF"); + return -ENOEXEC; + } + + if (hdr->e_ident[6] != EV_CURRENT) { + LOG_ERR("l0der", "_read_elf_header: not a v1 ELF"); + return -ENOEXEC; + } + + if (hdr->e_ehsize < sizeof(Elf32_Ehdr)) { + LOG_ERR("l0der", "_raed_elf_header: header too small"); + return -ENOEXEC; + } + + return 0; +} + +/* + * Read bytes from file at a given offset. + * + * :param int fd: 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 _seek_and_read(int fd, uint32_t address, void *data, size_t count) +{ + int res; + + if ((res = epic_file_seek(fd, address, SEEK_SET)) != 0) { + LOG_ERR("l0der", + "_seek_and_read: could not seek to 0x%lx: %d", + address, + res); + return res; + } + + if ((res = epic_file_read(fd, data, count)) != count) { + LOG_ERR("l0der", "_seek_and_read: could not read: %d", res); + return res; + } + + return 0; +} + +/* + * Read an ELF program header header. + */ +static int _read_program_header(int fd, uint32_t phdr_addr, Elf32_Phdr *phdr) +{ + return _seek_and_read(fd, phdr_addr, phdr, sizeof(Elf32_Phdr)); +} + +/* + * Read an ELF section header header. + */ +static int _read_section_header(int fd, uint32_t shdr_addr, Elf32_Shdr *shdr) +{ + return _seek_and_read(fd, shdr_addr, shdr, sizeof(Elf32_Shdr)); +} + +/* + * Check an ELF program header. + * + * This function ensures basic memory sanity of a program header / segment. + * It ensures that it points to a file region that is contained within the file fully. + */ +static int _check_program_header(int fd, int size, Elf32_Phdr *phdr) +{ + // Check file size/offset. + uint32_t file_start = phdr->p_offset; + uint32_t file_limit = phdr->p_offset + phdr->p_filesz; + if (file_limit < file_start) { + LOG_ERR("l0der", "_check_program_header: file size overflow"); + return -ENOEXEC; + } + if (file_limit > size) { + LOG_ERR("l0der", + "_check_program_header: extends past end of file"); + return -ENOEXEC; + } + + if (phdr->p_type == PT_LOAD) { + // Check mem/file size. + if (phdr->p_filesz > phdr->p_memsz) { + LOG_ERR("l0der", + "_check_program_header: file size larger than memory size"); + return -ENOEXEC; + } + + uint32_t mem_start = phdr->p_vaddr; + uint32_t mem_limit = phdr->p_vaddr + phdr->p_memsz; + + if (mem_limit < mem_start) { + LOG_ERR("l0der", + "_check_program_header: mem size overflow"); + return -ENOEXEC; + } + } + + return 0; +} + +/* + * Check an ELF section header. + * + * This function ensures basic memory sanity of a section header. + * It ensures that it points to a file region that is contained within the file fully. + */ +static int _check_section_header(int fd, int size, Elf32_Shdr *shdr) +{ + // Check file size/offset. + uint32_t file_start = shdr->sh_offset; + uint32_t file_limit = shdr->sh_offset + shdr->sh_size; + if (file_limit < file_start) { + LOG_ERR("l0der", "_check_section_header: file size overflow"); + return -ENOEXEC; + } + if (file_limit > size) { + LOG_ERR("l0der", + "_check_section_header: extends past end of file"); + return -ENOEXEC; + } + + 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(int fd, Elf32_Phdr *phdr) +{ + int res; + uint32_t buffer_size = strlen(_interpreter) + 1; + char *interp = alloca(buffer_size); + memset(interp, 0, buffer_size); + + if ((res = _seek_and_read(fd, phdr->p_offset, interp, buffer_size)) != + 0) { + return res; + } + + if (strncmp(interp, _interpreter, strlen(_interpreter)) != 0) { + LOG_ERR("l0der", + "_check_interp: invalid interpreter, want card10-l0dable"); + return -1; + } + + 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(struct _pie_load_info *li) +{ + uint32_t image_size = li->image_limit - li->image_start; + + // ref: Documentation/memorymap.rst + uint32_t core1_mem_start = 0x20040000; + uint32_t core1_mem_limit = 0x20080000; + uint32_t core1_mem_size = core1_mem_limit - core1_mem_start; + + if (image_size > core1_mem_size) { + LOG_ERR("l0der", + "_get_load_addr: image too large (need 0x%08lx bytes, have %08lx", + image_size, + core1_mem_size); + return -ENOMEM; + } + + // Place image at bottom of core1 memory range. + li->load_address = core1_mem_start; + li->image_load_start = li->load_address + li->image_start; + li->image_load_limit = li->load_address + li->image_limit; + + // Ensure within alignment requests. + if ((li->load_address % li->strictest_alignment) != 0) { + LOG_ERR("l0der", + "_get_load_addr: too strict alignment request for %ld bytes", + li->strictest_alignment); + return -ENOEXEC; + } + + // Place stack at top of core1 memory range. + li->stack_top = core1_mem_limit; + + // Check that there is enough stack space. + uint32_t stack_space = li->stack_top - li->image_load_limit; + if (stack_space < 8192) { + LOG_WARN( + "l0der", + "_get_load_addr: low stack space (%ld bytes)", + stack_space + ); + } else if (stack_space < 256) { + LOG_ERR("l0der", + "_get_load_addr: low stack space (%ld bytes), cannot continue", + stack_space); + return -ENOMEM; + } + + LOG_INFO( + "l0der", + "Stack at %08lx, %ld bytes available", + li->stack_top, + stack_space + ); + + return 0; +} + +/* + * Load a program segment into memory. + * + * Segment must be a LOAD segment. + */ +static int _load_segment(int fd, struct _pie_load_info *li, Elf32_Phdr *phdr) +{ + uint32_t segment_start = li->load_address + phdr->p_vaddr; + uint32_t segment_limit = segment_start + phdr->p_memsz; + + LOG_INFO( + "l0der", + "Segment %08lx-%08lx: 0x%lx bytes from file", + segment_start, + segment_limit, + phdr->p_filesz + ); + memset((void *)segment_start, 0, phdr->p_memsz); + + return _seek_and_read( + fd, phdr->p_offset, (void *)segment_start, phdr->p_filesz + ); +} + +/* + * Parse dynamic symbol sections. + */ +static int _parse_dynamic_symbols( + int fd, int size, struct _pie_load_info *li, Elf32_Ehdr *hdr +) { + int res; + Elf32_Shdr shdr; + Elf32_Sym sym; + + // Go through all dynamic symbol sections. + for (int i = 0; i < hdr->e_shnum; i++) { + uint32_t shdr_addr = hdr->e_shoff + (i * hdr->e_shentsize); + if ((res = _read_section_header(fd, shdr_addr, &shdr)) != 0) { + return res; + } + + if (shdr.sh_type != SHT_DYNSYM) { + continue; + } + + if ((res = _check_section_header(fd, size, &shdr)) != 0) { + return res; + } + + if ((shdr.sh_size % sizeof(Elf32_Sym)) != 0) { + LOG_ERR("l0der", + "_parse_dynamic_symbols: SHT_DYN section with invalid size: %ld", + shdr.sh_size); + return -EIO; + } + uint32_t sym_count = shdr.sh_size / sizeof(Elf32_Sym); + + // Read symbols one by one. + if ((res = epic_file_seek(fd, shdr.sh_offset, SEEK_SET)) != 0) { + LOG_ERR("l0der", + "_parse_dynamic_symbols: seek to first relocation (at 0x%lx) failed", + shdr.sh_offset); + return res; + } + + for (int j = 0; j < sym_count; j++) { + if ((res = epic_file_read( + fd, &sym, sizeof(Elf32_Sym))) != + sizeof(Elf32_Sym)) { + LOG_ERR("l0der", + "__parse_dynamic_symbols: symbol read failed: %d", + res); + return res; + } + + uint32_t bind = ELF32_ST_BIND(sym.st_info); + if (bind != STB_WEAK) { + continue; + } + + if (li->weak_symbol_count >= WEAK_SYMBOL_MAX) { + LOG_ERR("l0der", + "__parse_dynamic_symbols: too many weak symbols (limit: %d)", + WEAK_SYMBOL_MAX); + return -ENOMEM; + } + + li->weak_symbols[li->weak_symbol_count++] = j; + } + } + + return 0; +} + +/* + * 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(int fd, int size, struct _pie_load_info *li, Elf32_Ehdr *hdr) +{ + int res; + Elf32_Shdr shdr; + Elf32_Rel rel; + + // Go through all relocation sections. + for (int i = 0; i < hdr->e_shnum; i++) { + uint32_t shdr_addr = hdr->e_shoff + (i * hdr->e_shentsize); + if ((res = _read_section_header(fd, shdr_addr, &shdr)) != 0) { + return res; + } + + // We don't support RELA (relocation with addend) sections (yet?). + if (shdr.sh_type == SHT_RELA) { + LOG_ERR("l0der", + "_run_relocations: found unsupported SHT_RELA section, bailing"); + return -ENOEXEC; + } + + if (shdr.sh_type != SHT_REL) { + continue; + } + + if ((res = _check_section_header(fd, size, &shdr)) != 0) { + return res; + } + + if ((shdr.sh_size % sizeof(Elf32_Rel)) != 0) { + LOG_ERR("l0der", + "_run_relocations: SHT_REL section with invalid size: %ld", + shdr.sh_size); + return -ENOEXEC; + } + uint32_t reloc_count = shdr.sh_size / sizeof(Elf32_Rel); + + // Read relocations one by one. + if ((res = epic_file_seek(fd, shdr.sh_offset, SEEK_SET)) != 0) { + LOG_ERR("l0der", + "_run_relocations: seek to first relocation (at 0x%lx) failed", + shdr.sh_offset); + return res; + } + + for (int j = 0; j < reloc_count; j++) { + if ((res = epic_file_read( + fd, &rel, sizeof(Elf32_Rel))) != + sizeof(Elf32_Rel)) { + LOG_ERR("l0der", + "_run_relocations: relocation read failed: %d", + res); + return res; + } + + uint32_t sym = ELF32_R_SYM(rel.r_info); + uint8_t type = ELF32_R_TYPE(rel.r_info); + + // Skip relocations that are for weak symbols. + // (ie., do not resolve relocation - they default to a safe NULL) + uint8_t skip = 0; + if (sym != 0) { + for (int k = 0; k < li->weak_symbol_count; + k++) { + if (li->weak_symbols[k] == sym) { + skip = 1; + break; + } + } + } + if (skip) { + continue; + } + + switch (type) { + case R_ARM_RELATIVE: + // Relocate. + if ((rel.r_offset % 4) != 0) { + LOG_ERR("l0der", + "_run_relocations: R_ARM_RELATIVE address must be 4-byte aligned"); + return -ENOEXEC; + } + volatile uint32_t *addr = + (uint32_t + *)(rel.r_offset + li->load_address); + if ((uint32_t)addr < li->image_load_start || + (uint32_t)addr >= li->image_load_limit) { + LOG_ERR("l0der", + "_run_relocations: R_ARM_RELATIVE address (%08lx) is outside image boundaries", + (uint32_t)addr); + return -ENOEXEC; + } + + *addr += li->load_address; + break; + default: + LOG_ERR("l0der", + "_run_relocations: unsupported relocation type %d", + type); + return -ENOEXEC; + } + } + } + + return 0; +} + +/* + * Load a l0dable PIE binary. + */ +static int +_load_pie(int fd, int size, Elf32_Ehdr *hdr, struct l0dable_info *info) +{ + int res; + struct _pie_load_info li = { 0 }; + + // First pass over program headers: sanity check sizes, calculate image + // size bounds, check alignment. + + li.image_start = 0xffffffff; + li.image_limit = 0x0; + + Elf32_Phdr phdr; + + int status_interp = -1; + + for (int i = 0; i < hdr->e_phnum; i++) { + uint32_t phdr_addr = hdr->e_phoff + (i * hdr->e_phentsize); + if ((res = _read_program_header(fd, phdr_addr, &phdr)) != 0) { + return res; + } + + if ((res = _check_program_header(fd, size, &phdr)) != 0) { + return res; + } + + if (phdr.p_type == PT_INTERP) { + status_interp = _check_interp(fd, &phdr); + continue; + } + + if (phdr.p_type == PT_LOAD) { + // Check alignment request. + if ((phdr.p_vaddr % phdr.p_align) != 0) { + LOG_ERR("l0der", + "_load_pie: phdr %d alignment too strict", + i); + return -ENOEXEC; + } + if (phdr.p_align > li.strictest_alignment) { + li.strictest_alignment = phdr.p_align; + } + + uint32_t mem_start = phdr.p_vaddr; + uint32_t mem_limit = phdr.p_vaddr + phdr.p_memsz; + + // Record memory usage. + if (mem_start < li.image_start) { + li.image_start = mem_start; + } + if (mem_limit > li.image_limit) { + li.image_limit = mem_limit; + } + } + } + + if (status_interp != 0) { + // Expected interpreter string was not found. + LOG_ERR("l0der", "_load_pie: not a card10 l0dable"); + return -ENOEXEC; + } + + if (li.image_limit < li.image_start) { + // We didn't find any LOAD segment. + LOG_ERR("l0der", "_load_pie: no loadable segments"); + return -ENOEXEC; + } + + LOG_INFO( + "l0der", + "Image bounds %08lx - %08lx", + li.image_start, + li.image_limit + ); + + if ((res = _get_load_addr(&li)) != 0) { + return res; + } + + LOG_INFO("l0der", "Loading at %08lx", li.load_address); + + // Second pass through program headers: load all LOAD segments. + + for (int i = 0; i < hdr->e_phnum; i++) { + uint32_t phdr_addr = hdr->e_phoff + (i * hdr->e_phentsize); + if ((res = _read_program_header(fd, phdr_addr, &phdr)) != 0) { + return res; + } + + if (phdr.p_type != PT_LOAD) { + continue; + } + + if ((res = _load_segment(fd, &li, &phdr)) != 0) { + return res; + } + } + + // Load dynamic symbols. + if ((res = _parse_dynamic_symbols(fd, size, &li, hdr)) != 0) { + return res; + } + + // Run relocations. + if ((res = _run_relocations(fd, size, &li, hdr)) != 0) { + return res; + } + + uint32_t image_entrypoint = li.load_address + hdr->e_entry; + LOG_INFO("l0der", "Entrypoint (ISR Vector) at %08lx", image_entrypoint); + + // Setup stack + uint32_t *isr = (uint32_t *)image_entrypoint; + isr[0] = li.stack_top; + + info->isr_vector = (void *)image_entrypoint; + return 0; +} + +int l0der_load_path(const char *path, struct l0dable_info *info) +{ + int fd, res; + if ((fd = epic_file_open(path, "rb")) < 0) { + LOG_ERR("l0der", + "l0der_load_path: could not open ELF file %s: %d", + path, + fd); + return fd; + } + + if ((res = epic_file_seek(fd, 0, SEEK_END)) != 0) { + return res; + } + + int size = epic_file_tell(fd); + + if ((res = epic_file_seek(fd, 0, SEEK_SET)) != 0) { + return res; + } + + // Load ELF header and ensure it's somewhat sane. + + Elf32_Ehdr hdr; + if ((res = _read_elf_header(fd, &hdr)) != 0) { + goto done; + } + + // Sanitize segments. + + uint32_t ph_start = hdr.e_phoff; + uint32_t ph_limit = hdr.e_phoff + (hdr.e_phnum * hdr.e_phentsize); + if (ph_limit < ph_start) { + LOG_ERR("l0der", + "l0der_load_path: invalid program header count/size: overflow"); + return -ENOEXEC; + } + if (ph_limit - ph_start == 0) { + LOG_ERR("l0der", "l0der_load_path: no segments"); + return -ENOEXEC; + } + if (ph_limit > size) { + LOG_ERR("l0der", + "l0der_load_path: program header table extends past end of file"); + return -ENOEXEC; + } + if (hdr.e_phentsize < sizeof(Elf32_Phdr)) { + LOG_ERR("l0der", + "l0der_load_path: invalid program header table entry size"); + return -ENOEXEC; + } + + // Sanitize sections. + + uint32_t sh_start = hdr.e_shoff; + uint32_t sh_limit = hdr.e_shoff + (hdr.e_shnum + hdr.e_shentsize); + if (sh_limit < sh_start) { + LOG_ERR("l0der", + "l0der_load_path: invalid section header count/size: overflow"); + return -ENOEXEC; + } + if (sh_limit > size) { + LOG_ERR("l0der", + "l0der_load_path: section header table extends past end of file"); + return -ENOEXEC; + } + if (hdr.e_shentsize < sizeof(Elf32_Shdr)) { + LOG_ERR("l0der", + "l0der_load_path: invalid section header table entry size"); + return -ENOEXEC; + } + + // Check whether it's something that we can load. + + if (hdr.e_type == ET_DYN && hdr.e_machine == EM_ARM && + hdr.e_version == EV_CURRENT) { + LOG_INFO("l0der", "Loading PIE l0dable %s ...", path); + res = _load_pie(fd, size, &hdr, info); + goto done; + } else { + LOG_ERR("l0der", + "l0der_load_path: %s: not an ARM PIE, cannot load.", + path); + res = -ENOEXEC; + goto done; + } + +done: + epic_file_close(fd); + return res; +} diff --git a/epicardium/l0der/l0der.h b/epicardium/l0der/l0der.h new file mode 100644 index 0000000000000000000000000000000000000000..d7f6b462c80694bc4304fa4baab68a206b21760a --- /dev/null +++ b/epicardium/l0der/l0der.h @@ -0,0 +1,32 @@ +#pragma once + +/* + * l0der, the l0dable loader. + * + * l0der is the ELF loader responsible for retrieving a l0dable from FAT and + * into memory for core1 to execute. + * + * l0dables are PIE ELF binaries. They can be loaded anywhere into memory, + * although for now we load them at a static address (but that might change + * with address space evolution and/or multi-app / resident app support. + * + */ + +struct l0dable_info { + /** The address of the entry ISR vector. */ + void *isr_vector; +}; + +/** + * Load a l0dable into memory. + * + * :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. 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); diff --git a/epicardium/l0der/meson.build b/epicardium/l0der/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..e727bcfa223a0a6b2fd4d9dd0bc1f1edc780d269 --- /dev/null +++ b/epicardium/l0der/meson.build @@ -0,0 +1,3 @@ +l0der_sources = files( + 'l0der.c', +) diff --git a/epicardium/main.c b/epicardium/main.c index 75bb9c79537a82de067d2618d1f3071348d2b032..c90d008248e0562c184701099be9ff59d7639b60 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -1,6 +1,8 @@ #include <stdio.h> #include <stdlib.h> +#include <ff.h> + #include "max32665.h" #include "uart.h" #include "cdcacm.h" @@ -9,6 +11,7 @@ #include "pmic.h" #include "leds.h" #include "api/dispatcher.h" +#include "l0der/l0der.h" #include "modules/modules.h" #include "modules/log.h" #include "modules/stream.h" @@ -92,15 +95,38 @@ int main(void) NULL, tskIDLE_PRIORITY + 2, &dispatcher_task_id) != pdPASS) { - LOG_CRIT("startup", "Failed to create %s task!", "API Dispatcher"); + LOG_CRIT( + "startup", + "Failed to create %s task!", + "API Dispatcher" + ); abort(); } LOG_INFO("startup", "Initializing dispatcher ..."); api_dispatcher_init(); - LOG_INFO("startup", "Starting core1 payload ..."); - core1_start(); + /* + * See if there's a l0dable.elf to run. If not, run pycardium. + * This is temporary until epicardium gets a l0dable API from pycardium. + */ + const char *l0dable = "l0dable.elf"; + if (f_stat(l0dable, NULL) == FR_OK) { + LOG_INFO("startup", "Running %s ...", l0dable); + struct l0dable_info info; + int res = l0der_load_path(l0dable, &info); + if (res != 0) { + LOG_ERR("startup", "l0der failed: %d\n", res); + } else { + LOG_INFO( + "startup", "Starting %s on core1 ...", l0dable + ); + core1_start(info.isr_vector); + } + } else { + LOG_INFO("startup", "Starting pycardium on core1 ..."); + core1_start((void *)0x10080000); + } LOG_INFO("startup", "Starting FreeRTOS ..."); vTaskStartScheduler(); diff --git a/epicardium/meson.build b/epicardium/meson.build index ce584e64f6b54b6ae4f32ec19e81e52b98f42be4..c5dc701d6164c79246e9a6aa8bc761c01626b32e 100644 --- a/epicardium/meson.build +++ b/epicardium/meson.build @@ -66,12 +66,15 @@ freertos = static_library( subdir('modules/') +subdir('l0der/') + elf = executable( name + '.elf', 'cdcacm.c', 'main.c', 'support.c', module_sources, + l0der_sources, dependencies: [libcard10, max32665_startup_core0, maxusb, libff13], link_with: [api_dispatcher_lib, freertos], link_whole: [max32665_startup_core0_lib, board_card10_lib, newlib_heap_lib], diff --git a/hw-tests/dual-core/main.c b/hw-tests/dual-core/main.c index adc1076c6b0ff468da44cdf2fc98efb7a44efeb1..ca91f9d63161412f52271c7797e172c5e7be20be 100644 --- a/hw-tests/dual-core/main.c +++ b/hw-tests/dual-core/main.c @@ -18,40 +18,47 @@ int main(void) { - card10_init(); - card10_diag(); - - Paint_DrawImage(Heart, 0, 0, 160, 80); - LCD_Update(); - - for(int i=0; i<11; i++) { - leds_set_dim(i, 1); - } - - int h = 0; - - // Release core1 - core1_start(); - - while (1) { - #define NUM 15 - for(int i=0; i<NUM; i++) { - if(i < 12) { - leds_set_hsv(i, (h + 360/NUM * i) % 360, 1., 1./8); - } else { - leds_set_hsv(i, (h + 360/NUM * i) % 360, 1., 1.); - } - } - - leds_update(); - TMR_Delay(MXC_TMR0, MSEC(10), 0); - h++; - - // Send a txev using `sev` every once in a while to wake up core1 - // and let it do something - if (h % 100 == 0) { - printf("core0: Triggering core1 using SEV ...\n"); - __asm volatile("sev"); - } - } + card10_init(); + card10_diag(); + + Paint_DrawImage(Heart, 0, 0, 160, 80); + LCD_Update(); + + for (int i = 0; i < 11; i++) { + leds_set_dim(i, 1); + } + + int h = 0; + + // Release core1 + core1_start((void *)0x10080000); + + while (1) { +#define NUM 15 + for (int i = 0; i < NUM; i++) { + if (i < 12) { + leds_set_hsv( + i, + (h + 360 / NUM * i) % 360, + 1., + 1. / 8 + ); + } else { + leds_set_hsv( + i, (h + 360 / NUM * i) % 360, 1., 1. + ); + } + } + + leds_update(); + TMR_Delay(MXC_TMR0, MSEC(10), 0); + h++; + + // Send a txev using `sev` every once in a while to wake up core1 + // and let it do something + if (h % 100 == 0) { + printf("core0: Triggering core1 using SEV ...\n"); + __asm volatile("sev"); + } + } } diff --git a/l0dables/blinky/README.md b/l0dables/blinky/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9b13b5104a1d717acef4f3af5592deb7b182948b --- /dev/null +++ b/l0dables/blinky/README.md @@ -0,0 +1,6 @@ +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. diff --git a/l0dables/blinky/main.c b/l0dables/blinky/main.c new file mode 100644 index 0000000000000000000000000000000000000000..14237da94cf93c587aad585e19b7a83126b211a9 --- /dev/null +++ b/l0dables/blinky/main.c @@ -0,0 +1,71 @@ +#include "epicardium.h" + +#include <math.h> + +int levels[11] = { 0 }; +int levels_display[11] = { 0 }; + +// 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 +}; + +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) +{ + // 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] = 128; + levels_display[i] = 1; + for (int j = 0; j < 32; j++) { + fade(); + } + } + for (int i = 9; i > 0; i--) { + levels[i] = 128; + levels_display[i] = 1; + for (int j = 0; j < 32; j++) { + fade(); + } + } + } +} diff --git a/l0dables/blinky/meson.build b/l0dables/blinky/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..9a5c199dcaf503aafb2b966256b56938ae22be4e --- /dev/null +++ b/l0dables/blinky/meson.build @@ -0,0 +1,13 @@ +name = 'blinky' + +elf = executable( + name + '.elf', + 'main.c', + build_by_default: true, + dependencies: [l0dable_startup, api_caller], + link_whole: [l0dable_startup_lib], + link_args: [ + '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map', + ], + pie: true, +) diff --git a/l0dables/lib/crt.s b/l0dables/lib/crt.s new file mode 100644 index 0000000000000000000000000000000000000000..b811c4e6cb0c732b0de7091e596da94fce9bd1ca --- /dev/null +++ b/l0dables/lib/crt.s @@ -0,0 +1,295 @@ + /* + * C Runtime for l0dable. + * + * Also known as a startup file. + * + * We provide the following to l0dables: + * - calling GCC initializers. + * - an ISR vector. + * + * The stack is provided by l0der. + */ + + .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: + .long 0 /* Top of Stack, overriden by l0der at load time */ + .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 */ + .long WDT0_IRQHandler /* 0x11 0x0044 17: Watchdog 0 */ + .long DefaultHandler /* 0x12 0x0048 18: USB, used by core0, unoverridable */ + .long RTC_IRQHandler /* 0x13 0x004C 19: RTC */ + .long TRNG_IRQHandler /* 0x14 0x0050 20: True Random Number Generator */ + .long TMR0_IRQHandler /* 0x15 0x0054 21: Timer 0 */ + .long TMR1_IRQHandler /* 0x16 0x0058 22: Timer 1 */ + .long TMR2_IRQHandler /* 0x17 0x005C 23: Timer 2 */ + .long TMR3_IRQHandler /* 0x18 0x0060 24: Timer 3*/ + .long TMR4_IRQHandler /* 0x19 0x0064 25: Timer 4*/ + .long TMR5_IRQHandler /* 0x1A 0x0068 26: Timer 5 */ + .long RSV11_IRQHandler /* 0x1B 0x006C 27: Reserved */ + .long RSV12_IRQHandler /* 0x1C 0x0070 28: Reserved */ + .long I2C0_IRQHandler /* 0x1D 0x0074 29: I2C0 */ + .long DefaultHandler /* 0x1E 0x0078 30: UART 0, used by core0, unoverridable */ + .long UART1_IRQHandler /* 0x1F 0x007C 31: UART 1 */ + .long SPI1_IRQHandler /* 0x20 0x0080 32: SPI1 */ + .long SPI2_IRQHandler /* 0x21 0x0084 33: SPI2 */ + .long RSV18_IRQHandler /* 0x22 0x0088 34: Reserved */ + .long RSV19_IRQHandler /* 0x23 0x008C 35: Reserved */ + .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 DefaultHandler /* 0x28 0x00A0 40: GPIO0, used by core0, unoverridable */ + .long DefaultHandler /* 0x29 0x00A4 41: GPIO2, used by core0, unoverridable */ + .long RSV26_IRQHandler /* 0x2A 0x00A8 42: GPIO3 */ + .long TPU_IRQHandler /* 0x2B 0x00AC 43: Crypto */ + .long DMA0_IRQHandler /* 0x2C 0x00B0 44: DMA0 */ + .long DMA1_IRQHandler /* 0x2D 0x00B4 45: DMA1 */ + .long DMA2_IRQHandler /* 0x2E 0x00B8 46: DMA2 */ + .long DMA3_IRQHandler /* 0x2F 0x00BC 47: DMA3 */ + .long RSV32_IRQHandler /* 0x30 0x00C0 48: Reserved */ + .long RSV33_IRQHandler /* 0x31 0x00C4 49: Reserved */ + .long UART2_IRQHandler /* 0x32 0x00C8 50: UART 2 */ + .long RSV35_IRQHandler /* 0x33 0x00CC 51: Reserved */ + .long I2C1_IRQHandler /* 0x34 0x00D0 52: I2C1 */ + .long RSV37_IRQHandler /* 0x35 0x00D4 53: Reserved */ + .long SPIXFC_IRQHandler /* 0x36 0x00D8 54: SPI execute in place */ + .long BTLE_TX_DONE_IRQHandler /* 0x37 0x00DC 55: BTLE TX Done */ + .long BTLE_RX_RCVD_IRQHandler /* 0x38 0x00E0 56: BTLE RX Recived */ + .long BTLE_RX_ENG_DET_IRQHandler /* 0x39 0x00E4 57: BTLE RX Energy Dectected */ + .long BTLE_SFD_DET_IRQHandler /* 0x3A 0x00E8 58: BTLE SFD Detected */ + .long BTLE_SFD_TO_IRQHandler /* 0x3B 0x00EC 59: BTLE SFD Timeout*/ + .long BTLE_GP_EVENT_IRQHandler /* 0x3C 0x00F0 60: BTLE Timestamp*/ + .long BTLE_CFO_IRQHandler /* 0x3D 0x00F4 61: BTLE CFO Done */ + .long BTLE_SIG_DET_IRQHandler /* 0x3E 0x00F8 62: BTLE Signal Detected */ + .long BTLE_AGC_EVENT_IRQHandler /* 0x3F 0x00FC 63: BTLE AGC Event */ + .long BTLE_RFFE_SPIM_IRQHandler /* 0x40 0x0100 64: BTLE RFFE SPIM Done */ + .long BTLE_TX_AES_IRQHandler /* 0x41 0x0104 65: BTLE TX AES Done */ + .long BTLE_RX_AES_IRQHandler /* 0x42 0x0108 66: BTLE RX AES Done */ + .long BTLE_INV_APB_ADDR_IRQHandler /* 0x43 0x010C 67: BTLE Invalid APB Address*/ + .long BTLE_IQ_DATA_VALID_IRQHandler /* 0x44 0x0110 68: BTLE IQ Data Valid */ + .long WUT_IRQHandler /* 0x45 0x0114 69: WUT Wakeup */ + .long GPIOWAKE_IRQHandler /* 0x46 0x0118 70: GPIO Wakeup */ + .long RSV55_IRQHandler /* 0x47 0x011C 71: Reserved */ + .long SPI0_IRQHandler /* 0x48 0x0120 72: SPI AHB */ + .long WDT1_IRQHandler /* 0x49 0x0124 73: Watchdog 1 */ + .long RSV58_IRQHandler /* 0x4A 0x0128 74: Reserved */ + .long PT_IRQHandler /* 0x4B 0x012C 75: Pulse train */ + .long SDMA0_IRQHandler /* 0x4C 0x0130 76: Smart DMA 0 */ + .long RSV61_IRQHandler /* 0x4D 0x0134 77: Reserved */ + .long I2C2_IRQHandler /* 0x4E 0x0138 78: I2C 2 */ + .long RSV63_IRQHandler /* 0x4F 0x013C 79: Reserved */ + .long RSV64_IRQHandler /* 0x50 0x0140 80: Reserved */ + .long RSV65_IRQHandler /* 0x51 0x0144 81: Reserved */ + .long SDHC_IRQHandler /* 0x52 0x0148 82: SDIO/SDHC */ + .long OWM_IRQHandler /* 0x53 0x014C 83: One Wire Master */ + .long DMA4_IRQHandler /* 0x54 0x0150 84: DMA4 */ + .long DMA5_IRQHandler /* 0x55 0x0154 85: DMA5 */ + .long DMA6_IRQHandler /* 0x56 0x0158 86: DMA6 */ + .long DMA7_IRQHandler /* 0x57 0x015C 87: DMA7 */ + .long DMA8_IRQHandler /* 0x58 0x0160 88: DMA8 */ + .long DMA9_IRQHandler /* 0x59 0x0164 89: DMA9 */ + .long DMA10_IRQHandler /* 0x5A 0x0168 90: DMA10 */ + .long DMA11_IRQHandler /* 0x5B 0x016C 91: DMA11 */ + .long DMA12_IRQHandler /* 0x5C 0x0170 92: DMA12 */ + .long DMA13_IRQHandler /* 0x5D 0x0174 93: DMA13 */ + .long DMA14_IRQHandler /* 0x5E 0x0178 94: DMA14 */ + .long DMA15_IRQHandler /* 0x5F 0x017C 95: DMA15 */ + .long USBDMA_IRQHandler /* 0x60 0x0180 96: USB DMA */ + .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 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 */ + .long RSV88_IRQHandler /* 0x68 0x01A0 104: UART 3 */ + .long RSV89_IRQHandler /* 0x69 0x01A4 105: UART 4 */ + .long RSV90_IRQHandler /* 0x6A 0x01A8 106: UART 5 */ + .long RSV91_IRQHandler /* 0x6B 0x01AC 107: Camera IF */ + .long RSV92_IRQHandler /* 0x6C 0x01B0 108: I3C */ + .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: + /* 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 + + /* + * C code done, spin forever. + * TODO(q3k): let epicardium know we're done. + */ +.spin: + bl .spin + + /* + * Used by __libc_init_array. + */ + .globl _init +_init: + bx lr + + /* + * The default handler for all IRQs just spins forwever. + * TODO(q3k): let epicardium know we've reached an infinite loop due to + * an exception and/or unhandled IRQ, perhaps by splitting + * DefaultHandler into multiple handlers that report different + * error conditions to epicardium (eg. unhandled IRQ, fault, ...) + */ + .thumb_func + .type DefaultHandler, %function +DefaultHandler: + b . + + .macro def_irq_handler handler_name + .weakref \handler_name, DefaultHandler + .endm + + /* + * Declare all default ISRs. + */ + def_irq_handler NMI_Handler + def_irq_handler HardFault_Handler + def_irq_handler MemManage_Handler + def_irq_handler BusFault_Handler + def_irq_handler UsageFault_Handler + def_irq_handler SVC_Handler + def_irq_handler DebugMon_Handler + def_irq_handler PendSV_Handler + + def_irq_handler PF_IRQHandler + def_irq_handler WDT0_IRQHandler + def_irq_handler RTC_IRQHandler + def_irq_handler TRNG_IRQHandler + def_irq_handler TMR0_IRQHandler + def_irq_handler TMR1_IRQHandler + def_irq_handler TMR2_IRQHandler + def_irq_handler TMR3_IRQHandler + def_irq_handler TMR4_IRQHandler + def_irq_handler RSV11_IRQHandler + def_irq_handler RSV12_IRQHandler + def_irq_handler I2C0_IRQHandler + def_irq_handler UART1_IRQHandler + def_irq_handler SPI1_IRQHandler + def_irq_handler SPI2_IRQHandler + def_irq_handler RSV18_IRQHandler + def_irq_handler RSV19_IRQHandler + def_irq_handler ADC_IRQHandler + def_irq_handler RSV21_IRQHandler + def_irq_handler RSV22_IRQHandler + def_irq_handler FLC0_IRQHandler + def_irq_handler RSV26_IRQHandler + def_irq_handler TPU_IRQHandler + def_irq_handler DMA0_IRQHandler + def_irq_handler DMA1_IRQHandler + def_irq_handler DMA2_IRQHandler + def_irq_handler DMA3_IRQHandler + def_irq_handler RSV32_IRQHandler + def_irq_handler RSV33_IRQHandler + def_irq_handler UART2_IRQHandler + def_irq_handler RSV35_IRQHandler + def_irq_handler I2C1_IRQHandler + def_irq_handler RSV37_IRQHandler + def_irq_handler SPIXFC_IRQHandler + def_irq_handler BTLE_TX_DONE_IRQHandler + def_irq_handler BTLE_RX_RCVD_IRQHandler + def_irq_handler BTLE_RX_ENG_DET_IRQHandler + def_irq_handler BTLE_SFD_DET_IRQHandler + def_irq_handler BTLE_SFD_TO_IRQHandler + def_irq_handler BTLE_GP_EVENT_IRQHandler + def_irq_handler BTLE_CFO_IRQHandler + def_irq_handler BTLE_SIG_DET_IRQHandler + def_irq_handler BTLE_AGC_EVENT_IRQHandler + def_irq_handler BTLE_RFFE_SPIM_IRQHandler + def_irq_handler BTLE_TX_AES_IRQHandler + def_irq_handler BTLE_RX_AES_IRQHandler + def_irq_handler BTLE_INV_APB_ADDR_IRQHandler + def_irq_handler BTLE_IQ_DATA_VALID_IRQHandler + def_irq_handler WUT_IRQHandler + def_irq_handler GPIOWAKE_IRQHandler + def_irq_handler RSV55_IRQHandler + def_irq_handler SPI0_IRQHandler + def_irq_handler WDT1_IRQHandler + def_irq_handler RSV58_IRQHandler + def_irq_handler PT_IRQHandler + def_irq_handler SDMA0_IRQHandler + def_irq_handler RSV61_IRQHandler + def_irq_handler I2C2_IRQHandler + def_irq_handler RSV63_IRQHandler + def_irq_handler RSV64_IRQHandler + def_irq_handler RSV65_IRQHandler + def_irq_handler SDHC_IRQHandler + def_irq_handler OWM_IRQHandler + def_irq_handler DMA4_IRQHandler + def_irq_handler DMA5_IRQHandler + def_irq_handler DMA6_IRQHandler + def_irq_handler DMA7_IRQHandler + def_irq_handler DMA8_IRQHandler + def_irq_handler DMA9_IRQHandler + def_irq_handler DMA10_IRQHandler + def_irq_handler DMA11_IRQHandler + def_irq_handler DMA12_IRQHandler + def_irq_handler DMA13_IRQHandler + def_irq_handler DMA14_IRQHandler + def_irq_handler DMA15_IRQHandler + def_irq_handler USBDMA_IRQHandler + def_irq_handler WDT2_IRQHandler + def_irq_handler ECC_IRQHandler + def_irq_handler DVS_IRQHandler + def_irq_handler SIMO_IRQHandler + def_irq_handler RPU_IRQHandler + def_irq_handler AUDIO_IRQHandler + def_irq_handler FLC1_IRQHandler + def_irq_handler RSV88_IRQHandler + def_irq_handler RSV89_IRQHandler + def_irq_handler RSV90_IRQHandler + def_irq_handler RSV91_IRQHandler + def_irq_handler RSV92_IRQHandler + def_irq_handler HTMR0_IRQHandler + def_irq_handler HTMR1_IRQHandler + + .section .cinterp + .asciz "card10-l0dable" + .byte diff --git a/l0dables/lib/hardware.c b/l0dables/lib/hardware.c new file mode 100644 index 0000000000000000000000000000000000000000..209ee97ad24059860b437e2284f0704f928ae02e --- /dev/null +++ b/l0dables/lib/hardware.c @@ -0,0 +1,130 @@ +/* + * 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 <stddef.h> +#include "epicardium.h" + +#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; +} + +__weak void SystemInit() +{ + // Enable FPU. + SCB->CPACR |= SCB_CPACR_CP10_Msk | SCB_CPACR_CP11_Msk; + __DSB(); + __ISB(); + + // Enable ICache1 Clock + MXC_GCR->perckcn1 &= ~(1 << 22); + + // Invalidate cache and wait until ready + MXC_ICC1->invalidate = 1; + while (!(MXC_ICC1->cache_ctrl & MXC_F_ICC_CACHE_CTRL_CACHE_RDY)) + ; + + // Enable Cache + MXC_ICC1->cache_ctrl |= MXC_F_ICC_CACHE_CTRL_CACHE_EN; + + SystemCoreClockUpdate(); + + // Enable API interrupt. + NVIC_EnableIRQ(TMR5_IRQn); +} + +// newlib syscall to allow printf to work. +long _write(int fd, const char *buf, size_t cnt) +{ + // Only print one line at a time. Insert `\r` between lines so + // they are properly displayed on the serial console. + size_t i, last = 0; + for (i = 0; i < cnt; i++) { + if (buf[i] == '\n') { + epic_uart_write_str(&buf[last], i - last); + epic_uart_write_str("\r", 1); + last = i; + } + } + epic_uart_write_str(&buf[last], cnt - last); + return cnt; +} + +// newlib syscall to allow for a heap +extern uint32_t __heap_start; +uint32_t _sbrk(int incr) +{ + static char *brk = NULL; + if (brk == NULL) { + brk = (char *)&__heap_start; + } + + // Ensure we don't overflow the heap by checking agsinst the current stack + // pointer (the heap grows towards the stack, and vice-versa). + // + // This is only a last-ditch attempt at saving ourselves from memory + // corruption. It doesn't prevent the stack from growing into the heap + // first. + + void *sp; + __asm__ __volatile__("mov %0, sp" : "=r"(sp)); + + // Require a 'safe margin' of 4k between the heap and stack. + uint32_t stack_bottom = (uint32_t)sp - 4096; + + if (((uint32_t)brk + incr) > stack_bottom) { + errno = ENOMEM; + return (uint32_t)-1; + } + + char *prev_brk = brk; + brk += incr; + return (uint32_t)prev_brk; +} diff --git a/l0dables/lib/l0dable.ld b/l0dables/lib/l0dable.ld new file mode 100644 index 0000000000000000000000000000000000000000..31fbb773de00115fecb9a49d7a449a38cbfc02b6 --- /dev/null +++ b/l0dables/lib/l0dable.ld @@ -0,0 +1,91 @@ +ENTRY(__isr_vector); + +/* + * Segment in the output l0dable. + * + * They are mostly standard, but we define them explicitely so that we can + * target them in sections. + */ +PHDRS +{ + header PT_PHDR PHDRS ; + interp PT_INTERP ; + text PT_LOAD FILEHDR PHDRS ; + data PT_LOAD ; +} + +/* + * ELF sections. + */ +SECTIONS { + . = SIZEOF_HEADERS; + + /* + * Customer card10-l0dable INTERP/intepreter path. + * + * We nuke the original one (.interp) provided by gcc/ld, and inject out + * own. This section is populated in l0dable.ld. + */ + .cinterp : + { + *(.cinterp); + } :interp :text + + .text : + { + *(.text*) + *(.rodata*) + + KEEP(*(.init)) + KEEP(*(.fini)) + } :text + + .data : + { + . = ALIGN(4); + *(.data*) + + . = ALIGN(4); + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP(*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + + + . = ALIGN(4); + PROVIDE_HIDDEN (__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + + . = ALIGN(4); + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP(*(SORT(.fini_array.*))) + KEEP(*(.fini_array)) + PROVIDE_HIDDEN (__fini_array_end = .); + } :data + + .bss : + { + . = ALIGN(4); + *(.bss*) + *(COMMON) + } :data + + /* Used by hardware.c as start of heap. */ + __heap_start = .; + + /* Limit based on current limitations of l0dable setup - only uses core1 RAM. */ + ASSERT(. < 0x40000, "Exceeded available RAM") + + /DISCARD/ : + { + /* Compiler version - nuke. */ + *(.comment) + /* ARM attributes - nuke. */ + *(.ARM.attributes) + /* Original interpreter path from gcc/ld - nuke. */ + *(.interp) + /* Dynamic linking section - nuke, we're not a .so and nothing is going to link against us. */ + *(.dynamic) + } +} diff --git a/l0dables/lib/meson.build b/l0dables/lib/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..f2f16295ae51a3d5ba0680ae14c220911dd540d1 --- /dev/null +++ b/l0dables/lib/meson.build @@ -0,0 +1,18 @@ +l0dable_startup_lib = static_library( + 'l0dable-startup', + 'crt.s', + 'hardware.c', + dependencies: [api_caller], + pic: true, +) + +l0dable_startup = declare_dependency( + link_args: [ + '-nostdlib', '-n', + '-T', meson.current_source_dir() + 'l0dable.ld', + ], + compile_args: [ + '-fPIE', '-pie', + ], +) + diff --git a/l0dables/meson.build b/l0dables/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..f4be10b507d2e028d204202e14fa68bdf195a6ac --- /dev/null +++ b/l0dables/meson.build @@ -0,0 +1,3 @@ +subdir('lib/') + +subdir('blinky/') diff --git a/lib/card10/card10.c b/lib/card10/card10.c index 009a69d629e9015ad3f75ba615149249474b3af7..b423704f3c969103157a3064fc63c9830ac70506 100644 --- a/lib/card10/card10.c +++ b/lib/card10/card10.c @@ -212,10 +212,9 @@ void card10_diag(void) #endif } -void core1_start(void) +void core1_start(void *isr) { - //MXC_GCR->gp0 = (uint32_t)(&__isr_vector_core1); - MXC_GCR->gp0 = 0x10080000; + MXC_GCR->gp0 = (uint32_t)isr; MXC_GCR->perckcn1 &= ~MXC_F_GCR_PERCKCN1_CPU1; } diff --git a/lib/card10/card10.h b/lib/card10/card10.h index 488575a104f4c289998deaabfea14626d533de8c..853a27be90980deda9df88d1c3156a704ada648e 100644 --- a/lib/card10/card10.h +++ b/lib/card10/card10.h @@ -9,7 +9,7 @@ extern const gpio_cfg_t bhi_interrupt_pin; void card10_init(void); void card10_diag(void); -void core1_start(void); +void core1_start(void *isr); void core1_stop(void); void card10_poll(void); diff --git a/meson.build b/meson.build index 20b7040b216e7d4294c9a911230e71bd3b96e257..e58eb189d3fcc084bad0acc72c8e030a97771d5a 100644 --- a/meson.build +++ b/meson.build @@ -37,3 +37,5 @@ subdir('epicardium/') subdir('pycardium/') subdir('hw-tests/') + +subdir('l0dables/')