diff --git a/epicardium/l0der/elf.h b/epicardium/l0der/elf.h
new file mode 100644
index 0000000000000000000000000000000000000000..ce90fc270aef8b145d583c15cd93a4eafeadee40
--- /dev/null
+++ b/epicardium/l0der/elf.h
@@ -0,0 +1,96 @@
+#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
+
+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 23
+
+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..93a0ecba597bcaa8cef0af29a0362fddb71cdf87
--- /dev/null
+++ b/epicardium/l0der/l0der.c
@@ -0,0 +1,491 @@
+#include "l0der/l0der.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ff.h>
+
+#include "epicardium.h"
+#include "l0der/elf.h"
+#include "modules/log.h"
+
+/*
+ * Read an ELF header, check E_IDENT.
+ */
+static int _read_elf_header(FIL *fp, Elf32_Ehdr *hdr)
+{
+	f_lseek(fp, 0);
+
+	unsigned int read;
+	FRESULT fres = f_read(fp, hdr, sizeof(Elf32_Ehdr), &read);
+	if (fres != FR_OK) {
+		LOG_ERR("l0der", "_read_elf_header: f_read failed: %d", fres);
+		return -1;
+	}
+
+	if (read != sizeof(Elf32_Ehdr)) {
+		LOG_ERR("l0der", "_read_elf_header: file truncated");
+		return -1;
+	}
+
+	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 -1;
+	}
+
+	if (hdr->e_ident[4] != ELFCLASS32) {
+		LOG_ERR("l0der", "_read_elf_header: not a 32-bit ELF");
+		return -1;
+	}
+
+	if (hdr->e_ident[5] != ELFDATA2LSB) {
+		LOG_ERR("l0der", "_read_elf_header: not a little-endian ELF");
+		return -1;
+	}
+
+	if (hdr->e_ident[6] != EV_CURRENT) {
+		LOG_ERR("l0der", "_read_elf_header: not a v1 ELF");
+		return -1;
+	}
+
+	if (hdr->e_ehsize < sizeof(Elf32_Ehdr)) {
+		LOG_ERR("l0der", "_raed_elf_header: header too small");
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Read an ELF program header header.
+ */
+static int _read_program_header(FIL *fp, uint32_t phdr_addr, Elf32_Phdr *phdr)
+{
+	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);
+		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);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/*
+ * Read an ELF program header header.
+ */
+static int _read_section_header(FIL *fp, uint32_t shdr_addr, Elf32_Shdr *shdr)
+{
+	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 0;
+}
+
+/*
+ * 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(FIL *fp, Elf32_Phdr *phdr) {
+	size_t size = f_size(fp);
+
+	// 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(FIL *fp, Elf32_Shdr *shdr) {
+	size_t size = f_size(fp);
+
+	// 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;
+}
+
+static const char *_interpreter = "card10-l0dable";
+
+static int _check_interp(FIL *fp, Elf32_Phdr *phdr)
+{
+	uint32_t buffer_size = 64;
+	char interp[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;
+	}
+
+	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");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int _get_load_addr(uint32_t image_start, uint32_t image_limit, void **load)
+{
+	uint32_t image_size = image_limit - 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;
+	}
+
+	*load = (void *)core1_mem_start;
+
+	return 0;
+}
+
+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;
+	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);
+
+	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;
+}
+
+static int _run_relocations(FIL *fp, void *load_addr, Elf32_Ehdr *hdr) {
+	int res;
+	FRESULT fres;
+	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(fp, 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(fp, &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 -EIO;
+		}
+		uint32_t reloc_count = shdr.sh_size / sizeof(Elf32_Rel);
+
+		// Read relocations one by one.
+		if ((fres = f_lseek(fp, shdr.sh_offset)) != FR_OK) {
+			LOG_ERR("l0der", "_run_relocations: seek to first relocation (at 0x%lx) failed", shdr.sh_offset);
+			return -EIO;
+		}
+
+		for (int j = 0; j < reloc_count; j++) {
+			unsigned int read;
+			if ((fres = f_read(fp, &rel, sizeof(Elf32_Rel), &read)) != FR_OK || read != sizeof(Elf32_Rel)) {
+				LOG_ERR("l0der", "_run_relocations: relocation read failed: %d", fres);
+				return -EIO;
+			}
+
+			uint8_t type = ELF32_R_TYPE(rel.r_info);
+			switch (type) {
+				case R_ARM_RELATIVE:
+					if ((rel.r_offset % 4) != 0) {
+						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);
+					*addr += (uint32_t)load_addr;
+					break;
+				default:
+					LOG_ERR("l0der", "_run_relocations: unsupported relocation type %d", type);
+					return -ENOEXEC;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int _load_pie(FIL *fp, Elf32_Ehdr *hdr, struct l0dable_info *info)
+{
+	int res;
+
+	// First pass over program headers: sanity check sizes and calculate
+	// memory image bounds. l0der currently only supports loading the image into
+	// the core1 address space, that is from 0x1008_0000 to 0x1010_0000. Thus,
+	// we need to ensure that all the LOADable segments can fit within this
+	// range.
+	
+	uint32_t image_start = 0xFFFFFFFF;
+	uint32_t 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(fp, phdr_addr, &phdr)) != 0) {
+			return res;
+		}
+
+		if ((res = _check_program_header(fp, &phdr)) != 0) {
+			return res;
+		}
+
+		if (phdr.p_type == PT_INTERP) {
+			status_interp = _check_interp(fp, &phdr);
+			continue;
+		}
+
+		if (phdr.p_type == PT_LOAD) {
+			// Check alignment request.
+			if (phdr.p_align > 4) {
+				LOG_ERR("l0der", "_load_pie: phdr %d alignment too strict", i);
+				return -ENOEXEC;
+			}
+
+			uint32_t mem_start = phdr.p_vaddr;
+			uint32_t mem_limit = phdr.p_vaddr + phdr.p_memsz;
+
+			// Record memory usage.
+			if (mem_start < image_start) {
+				image_start = mem_start;
+			}
+			if (mem_limit > image_limit) {
+				image_limit = mem_limit;
+			}
+		}
+	}
+
+	if (status_interp != 0) {
+		LOG_ERR("l0der", "_load_pie: not a card10 l0dable");
+		return -ENOEXEC;
+	}
+
+
+	if (image_limit < image_start) {
+		LOG_ERR("l0der", "_load_pie: no loadable segments");
+		return -ENOEXEC;
+	}
+
+	LOG_INFO("l0der", "Image bounds %08lx - %08lx", image_start, image_limit);
+
+	void *load_addr;
+	if ((res = _get_load_addr(image_start, image_limit, &load_addr)) != 0) {
+		return res;
+	}
+
+	LOG_INFO("l0der", "Loading at %08lx", (uint32_t)load_addr);
+
+	// 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(fp, phdr_addr, &phdr)) != 0) {
+			return res;
+		}
+
+		if (phdr.p_type != PT_LOAD) {
+			continue;
+		}
+
+		if ((res = _load_segment(fp, load_addr, &phdr)) != 0) {
+			return res;
+		}
+	}
+
+	// Run relocations.
+	
+	if ((res = _run_relocations(fp, load_addr, hdr)) != 0) {
+		return res;
+	}
+
+	uint32_t image_entrypoint = (uint32_t)load_addr + hdr->e_entry;
+	LOG_INFO("l0der", "Entrypoint (ISR Vector) at %08lx", image_entrypoint);
+
+	info->isr_vector = (void *)image_entrypoint;
+
+	return 0;
+}
+
+int l0der_load_path(const char *path, struct l0dable_info *info)
+{
+	FIL fh;
+
+	FRESULT fres = f_open(&fh, path, FA_OPEN_EXISTING|FA_READ);
+	if (fres != FR_OK) {
+		LOG_ERR("l0der", "l0der_load_path: could not open ELF file %s: %d", path, fres);
+		return -ENOENT;
+	}
+
+	int size = f_size(&fh);
+
+	int res = 0;
+
+	// Load ELF header and ensure it's somewhat sane.
+
+	Elf32_Ehdr hdr;
+	if (_read_elf_header(&fh, &hdr) != 0) {
+		res = -EINVAL;
+		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(&fh, &hdr, info);
+		goto done;
+	} else {
+		LOG_ERR("l0der", "l0der_load_path: %s: not an ARM PIE, cannot load.", path);
+		res = -ENOEXEC;
+		goto done;
+	}
+
+done:
+	f_close(&fh);
+	return res;
+}
diff --git a/epicardium/l0der/l0der.h b/epicardium/l0der/l0der.h
new file mode 100644
index 0000000000000000000000000000000000000000..80ce3242dc1f239a7e9c5fa3eef50e53fac2868b
--- /dev/null
+++ b/epicardium/l0der/l0der.h
@@ -0,0 +1,27 @@
+#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.
+ */
+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..a44f73d81e15b04882c8e27e8394585b7f055d2d 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -9,6 +9,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"
@@ -99,8 +100,16 @@ int main(void)
 	LOG_INFO("startup", "Initializing dispatcher ...");
 	api_dispatcher_init();
 
-	LOG_INFO("startup", "Starting core1 payload ...");
-	core1_start();
+	LOG_INFO("startup", "Testing l0der ...");
+	struct l0dable_info info;
+	int res = l0der_load_path("blinky.elf", &info);
+	if (res != 0) {
+		LOG_ERR("startup", "l0der failed: %d\n", res);
+	} else {
+		LOG_INFO("startup", "Starting core1 payload ...");
+		core1_start(info.isr_vector);
+	}
+
 
 	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..bee6db9fbbd6ab8f37e75b8cafe1893a664be7bf 100644
--- a/hw-tests/dual-core/main.c
+++ b/hw-tests/dual-core/main.c
@@ -31,7 +31,7 @@ int main(void)
     int h = 0;
 
     // Release core1
-    core1_start();
+    core1_start((void *)0x10080000);
 
     while (1) {
         #define NUM     15
diff --git a/l0dables/blinky/main.c b/l0dables/blinky/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d9c1e10002bbc24496818321fe2f3021e40f905
--- /dev/null
+++ b/l0dables/blinky/main.c
@@ -0,0 +1,80 @@
+#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;
+
+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;
+}
+
+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 (;;) {}
+}
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/core1.ld b/l0dables/core1.ld
new file mode 100644
index 0000000000000000000000000000000000000000..4a7ff28de2bc564c5840f8ef7041d8f095567bc0
--- /dev/null
+++ b/l0dables/core1.ld
@@ -0,0 +1,91 @@
+/*MEMORY {
+    SPIX (rx)  : ORIGIN = 0x08000000, LENGTH = 128M
+    FLASH (rx) : ORIGIN = 0x10080000, LENGTH = 512k
+    SRAM (rwx) : ORIGIN = 0x20040000, LENGTH = 256k
+    SPID (r)   : ORIGIN = 0x80000000, LENGTH = 512M
+}*/
+
+ENTRY(isr_vector);
+
+CARD10_CORE1_START = 0x20040000;
+CARD10_CORE1_LIMIT = 0x20080000;
+
+PHDRS
+{
+    header PT_PHDR PHDRS ;
+    interp PT_INTERP ;
+    text PT_LOAD FILEHDR PHDRS ;
+    data PT_LOAD ;
+    dynamic PT_DYNAMIC ;
+}
+
+SECTIONS {
+    /* . = CARD10_CORE1_START; */
+
+    . = SIZEOF_HEADERS;
+
+    .cinterp :
+    {
+        *(.cinterp);
+    } :interp :text
+
+    .text :
+    {
+        *(.text*)
+        *(.rodata*)
+        
+        KEEP(*(.init))
+        KEEP(*(.fini))
+    } :text
+
+    .data :
+    {
+        . = ALIGN(4);
+        *(.data*)
+
+        . = 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
+
+    .dynamic :
+    {
+        *(.dynamic)
+    } :data :dynamic
+
+    .bss :
+    {
+        . = ALIGN(4);
+        CARD10_BSS_OFF_START = .;
+        *(.bss*)
+        *(COMMON)
+        CARD10_BSS_LIMIT = .;
+    } :data
+
+    . = ALIGN(4096);
+    .stack : 
+    {
+        CARD10_STACK_START = .;
+        . += 4096;
+        CARD10_STACK_LIMIT = .;
+    } :data
+
+    /DISCARD/ :
+    {
+        /* *(*) */
+        /* *(.symtab) */
+        /* *(.strtab) */
+        /* *(.shstrtab) */
+        /**(.interp)*/
+        *(.comment)
+        *(.interp)
+    }
+}
diff --git a/l0dables/crt.s b/l0dables/crt.s
new file mode 100644
index 0000000000000000000000000000000000000000..732c9c988ceee5640348ba9d92a356ab6728f806
--- /dev/null
+++ b/l0dables/crt.s
@@ -0,0 +1,265 @@
+		.syntax unified
+		.arch armv7-m
+
+		.section .data
+		.align 2
+		.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 */
+		
+		/* Device-specific Interrupts */
+		.long    PF_IRQHandler                 /* 0x10  0x0040  16: Power Fail */
+		.long    WDT0_IRQHandler               /* 0x11  0x0044  17: Watchdog 0 */
+		.long    USB_IRQHandler                /* 0x12  0x0048  18: USB */
+		.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    UART0_IRQHandler              /* 0x1E  0x0078  30: UART 0 */
+		.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    GPIO0_IRQHandler              /* 0x28  0x00A0  40: GPIO0 */
+		.long    GPIO1_IRQHandler              /* 0x29  0x00A4  41: GPIO2 */
+		.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 */
+
+
+		.text
+		.thumb
+		.thumb_func
+		.align 2
+Reset_Handler:
+		// Set stack according to limits from linker script.
+		ldr r0, =CARD10_STACK_LIMIT
+		mov sp, r0
+
+		// Jump to C code
+		ldr r0, =main
+		blx r0
+
+		// Spin
+		// 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.
+		.macro    def_irq_handler    handler_name
+		.align 1
+		.thumb_func
+		.weak    \handler_name
+		.type    \handler_name, %function
+\handler_name :
+		b    .
+		.size    \handler_name, . - \handler_name
+		.endm
+
+
+		// 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    Default_Handler
+
+		def_irq_handler    PF_IRQHandler
+		def_irq_handler    WDT0_IRQHandler
+		def_irq_handler    USB_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    TMR5_IRQHandler
+		def_irq_handler    RSV11_IRQHandler
+		def_irq_handler    RSV12_IRQHandler
+		def_irq_handler    I2C0_IRQHandler
+		def_irq_handler    UART0_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    GPIO0_IRQHandler
+		def_irq_handler    GPIO1_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/meson.build b/l0dables/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..12bb45b3db79d8f0952ce4b7ee7897a4f8051967
--- /dev/null
+++ b/l0dables/meson.build
@@ -0,0 +1,17 @@
+l0dable_startup_lib = static_library(
+  'l0dable-startup',
+  'crt.s',
+  pic: true,
+)
+
+l0dable_startup = declare_dependency(
+  link_args: [
+    '-nostdlib', '-n',
+    '-T', meson.current_source_dir() + 'core1.ld',
+  ],
+  compile_args: [
+    '-fPIE', '-pie',
+  ],
+)
+
+subdir('blinky/')
diff --git a/lib/card10/card10.c b/lib/card10/card10.c
index 92ddee1be65cda57b9c7f8b8459e301dda328bbf..de332612850c6a4cd076d8cb0fa5b45016542f7f 100644
--- a/lib/card10/card10.c
+++ b/lib/card10/card10.c
@@ -199,11 +199,9 @@ void card10_diag(void)
 #endif
 }
 
-void core1_start(void)
-{
-	//MXC_GCR->gp0 = (uint32_t)(&__isr_vector_core1);
-	MXC_GCR->gp0 = 0x10080000;
-	MXC_GCR->perckcn1 &= ~MXC_F_GCR_PERCKCN1_CPU1;
+void core1_start(void *isr) {
+    MXC_GCR->gp0 = (uint32_t)isr;
+    MXC_GCR->perckcn1 &= ~MXC_F_GCR_PERCKCN1_CPU1;
 }
 
 void core1_stop(void)
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/')