Add ESP32C3 RISC-V stub
diff --git a/bin/jmk2 b/bin/jmk2
index 4bb9e1c..186344c 100755
--- a/bin/jmk2
+++ b/bin/jmk2
@@ -36,7 +36,7 @@
 set ld {$ld}
 set asm {$asm}
 set root {$root}
-set jmk_build_cmd {$0}
+set jmk_build_cmd {$0 $*}
 set jmk_build_dir {$(pwd)}
 cd {$outdir}
 array set options {$options}
diff --git a/etc/platforms/platforms.jmk b/etc/platforms/platforms.jmk
index f4bac15..9f1bd90 100644
--- a/etc/platforms/platforms.jmk
+++ b/etc/platforms/platforms.jmk
@@ -1,7 +1,8 @@
 # -*- tcl -*-
 
 isa x86_32
-device pc-generic x86_32
+device generic x86_32
 
 isa riscv32
 device esp32c3 riscv32
+# device generic riscv32
diff --git a/etc/platforms/riscv32/riscv32.jmk b/etc/platforms/riscv32/riscv32.jmk
new file mode 100644
index 0000000..f8249b9
--- /dev/null
+++ b/etc/platforms/riscv32/riscv32.jmk
@@ -0,0 +1,9 @@
+# -*- tcl -*-
+
+set ::ld "clang --target=riscv32"
+set ::cc "clang --target=riscv32"
+set ::asm "clang --target=riscv32"
+
+cflags -mcmodel=medany -march=rv32ima -mabi=ilp32
+asmflags -nostdlib -c
+ldflags $::cflags
diff --git a/etc/platforms/x86_32/x86_32.jmk b/etc/platforms/x86_32/x86_32.jmk
index e7cfbbb..9f8f4b0 100644
--- a/etc/platforms/x86_32/x86_32.jmk
+++ b/etc/platforms/x86_32/x86_32.jmk
@@ -1,3 +1,4 @@
 # -*- tcl -*-
 
-# nothing
\ No newline at end of file
+cflag -m32
+asmflag -felf32
diff --git a/include/kernel/serial.h b/include/kernel/serial.h
new file mode 100644
index 0000000..7e391db
--- /dev/null
+++ b/include/kernel/serial.h
@@ -0,0 +1,8 @@
+#pragma once
+
+/**
+ * Initialize the default serial device.
+ */
+void init_serial();
+void serial_put(char byte);
+void serial_write(char *string);
diff --git a/share/jmk/jmk.tcl b/share/jmk/jmk.tcl
index ae114dc..1a82ffb 100644
--- a/share/jmk/jmk.tcl
+++ b/share/jmk/jmk.tcl
@@ -220,7 +220,7 @@
 
 	rule .s.o {} {
 		log ASM $::first_src
-		asm "\ $::first_src -o $::target"
+		asm "$::asmflags $::first_src -o $::target"
 	}
 
 	rule clean {} {
@@ -242,23 +242,22 @@
 		cflags -O2
 	}
 	
-	proc 32 {} {
-		cflag -m32
-		asmflag -felf32
-	}
-
 	proc debug {} {
 		cflag -g
-		asmflag -Fdwarf
 	}
 
 	proc warn {} {
 		cflags -Wall -Wno-unused-function -Wno-unused-variable -Wno-incompatible-pointer-types -Wno-sign-compare
 	}
 
+	proc 32 {} {
+		cflag -m32
+		asmflag -felf32
+	}
+
 	proc nasm {} {
-		global asm
-		set asm nasm
+		set ::asm nasm
+		asmflag -Fdwarf
 	}
 }
 
diff --git a/share/jmk/multiplat.jmk b/share/jmk/multiplat.jmk
index 0638d8d..3111653 100644
--- a/share/jmk/multiplat.jmk
+++ b/share/jmk/multiplat.jmk
@@ -26,7 +26,7 @@
 }
 
 option ISA x86_32
-option DEVICE pc-generic
+option DEVICE generic
 
 proc source_if_exists {path} {
 	if {[file exists $path]} {
diff --git a/src/kernel/Jmk2 b/src/kernel/Jmk2
index 38e44b9..ffd134c 100644
--- a/src/kernel/Jmk2
+++ b/src/kernel/Jmk2
@@ -2,7 +2,7 @@
 
 init kernel kernel.elf
 
-presets freestanding debug 32 warn nasm
+presets freestanding debug warn
 
 cflags -I$root/include/kernel -I$root/include -I[pwd] -O0 -Wno-ignored-qualifiers -Wno-unused-params -Wno-sign-compare
 
diff --git a/src/kernel/riscv32/boot.s b/src/kernel/riscv32/boot.s
new file mode 100644
index 0000000..6578eba
--- /dev/null
+++ b/src/kernel/riscv32/boot.s
@@ -0,0 +1,21 @@
+	.equ STACK_SIZE, 8192
+
+	.global _start
+
+_start:
+	csrr t0, mhartid
+	slli t0, t0, 10 			# mhartid << 10
+	la sp, stacks + STACK_SIZE	# set up space for hart stacks
+	add sp, sp, t0				# sp += (mhartid << 10)
+
+	## For now, only run on hart 0
+	csrr a0, mhartid			# pass hart id to kmain
+	bnez a0, .end
+	j kmain
+
+.end:
+    wfi
+    j .end
+
+stacks:
+    .skip STACK_SIZE * 4		# Allocate stack space for each hart
diff --git a/src/kernel/riscv32/esp32c3.jmk b/src/kernel/riscv32/esp32c3.jmk
new file mode 100644
index 0000000..3125e01
--- /dev/null
+++ b/src/kernel/riscv32/esp32c3.jmk
@@ -0,0 +1,9 @@
+# -*- tcl -*-
+
+ldflags -T[pwd]/link-esp32c3.ld
+
+srcs serial-esp32c3.c
+
+rule kernel.bin kernel.elf {
+	shell {objcopy -O binary $^ $@}
+}
diff --git a/src/kernel/riscv32/generic.jmk b/src/kernel/riscv32/generic.jmk
new file mode 100644
index 0000000..f167787
--- /dev/null
+++ b/src/kernel/riscv32/generic.jmk
@@ -0,0 +1,3 @@
+ldflags -T[pwd]/link.ld
+
+srcs serial-generic.c
\ No newline at end of file
diff --git a/src/kernel/riscv32/link-esp32c3.ld b/src/kernel/riscv32/link-esp32c3.ld
new file mode 100644
index 0000000..34c639f
--- /dev/null
+++ b/src/kernel/riscv32/link-esp32c3.ld
@@ -0,0 +1,369 @@
+MEMORY
+{
+	irom (x): org = 0x42000000, len = 0x400000
+	drom (r): org = 0x3C000000, len = 0x400000
+	ram (rw): org = 0x3FC80000, len = 0x50000
+	rtc_ram (rx): org = 0x50000000, len = 0x2000
+}
+
+ENTRY(_start)
+
+SECTIONS
+{
+	.header : AT(0)
+	{
+		_irom_start = .;
+		LONG(0xaedb041d)
+		LONG(0xaedb041d)
+	} > irom
+
+	.text.entry ORIGIN(irom) + 8 :
+	{
+		KEEP(*(.text.entry))
+	} > irom
+
+	.text :
+	{
+		*(.text .stub .text.* .gnu.linkonce.t.*)
+		*(.gnu.warning)
+	}
+	. = ALIGN(4);
+	PROVIDE (__etext = .);
+	PROVIDE (_etext = .);
+	PROVIDE (etext = .);
+	_irom_size = . - _irom_start;
+	
+	_drom_start = ORIGIN(drom) + _irom_size;
+	.rodata  _drom_start : AT(_irom_size)
+	{
+		*(.rodata .rodata.* .gnu.linkonce.r.*)
+	} > drom
+
+	.rodata1 :
+	{
+		*(.rodata1)
+	}
+
+	.init_array :
+	{
+		PROVIDE_HIDDEN (__init_array_start = .);
+		KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
+		KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
+		PROVIDE_HIDDEN (__init_array_end = .);
+	} > drom
+
+	.fini_array :
+	{
+		PROVIDE_HIDDEN (__fini_array_start = .);
+		KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
+		KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
+		PROVIDE_HIDDEN (__fini_array_end = .);
+	} > drom
+
+	.ctors :
+	{
+		KEEP (*crtbegin.o(.ctors))
+		KEEP (*crtbegin?.o(.ctors))
+		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
+		KEEP (*(SORT(.ctors.*)))
+		KEEP (*(.ctors))
+	}
+
+	.dtors :
+	{
+		KEEP (*crtbegin.o(.dtors))
+		KEEP (*crtbegin?.o(.dtors))
+		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
+		KEEP (*(SORT(.dtors.*)))
+		KEEP (*(.dtors))
+	}
+
+	_drom_size = . - _drom_start;
+
+	.data ORIGIN(ram) : AT(_irom_size + _drom_size) 
+	{
+		_data_start = .;
+		__DATA_BEGIN__ = .;
+		*(.data .data.* .gnu.linkonce.d.*)
+		*(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*)
+		SORT(CONSTRUCTORS)
+	} > ram
+	.data1 :
+	{
+		*(.data1)
+	}
+	.sdata :
+	{
+		__SDATA_BEGIN__ = .;
+		*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*)
+		*(.sdata .sdata.* .gnu.linkonce.s.*)
+	}
+	. = ALIGN(4);
+	_edata = .; PROVIDE (edata = .);
+	_data_lma = ORIGIN(drom) + LOADADDR(.data);
+	_data_size = _edata - _data_start;
+
+	__bss_start = .;
+	.sbss           :
+	{
+		*(.dynsbss)
+		*(.sbss .sbss.* .gnu.linkonce.sb.*)
+		*(.scommon)
+	}
+	.bss            :
+	{
+		*(.dynbss)
+		*(.bss .bss.* .gnu.linkonce.b.*)
+		*(COMMON)
+	}
+	. = ALIGN(4);
+	__BSS_END__ = .;
+	__global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800,
+							MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800));
+	_end = .; PROVIDE (end = .);
+
+	/* Stack */
+	.stack :
+	{
+		__stack_bottom = .;
+		__stack_top = ORIGIN(ram) + LENGTH(ram);
+		__stack_size_min = 0x4000;
+		ASSERT(__stack_bottom + __stack_size_min < __stack_top, "Error: no space for stack");
+	}
+
+	/* Stabs debugging sections.  */
+	.stab          0 : { *(.stab) }
+	.stabstr       0 : { *(.stabstr) }
+	.stab.excl     0 : { *(.stab.excl) }
+	.stab.exclstr  0 : { *(.stab.exclstr) }
+	.stab.index    0 : { *(.stab.index) }
+	.stab.indexstr 0 : { *(.stab.indexstr) }
+	.comment       0 : { *(.comment) }
+	.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
+	/* DWARF debug sections.
+	Symbols in the DWARF debugging sections are relative to the beginning
+	of the section so we begin them at 0.  */
+	/* DWARF 1 */
+	.debug          0 : { *(.debug) }
+	.line           0 : { *(.line) }
+	/* GNU DWARF 1 extensions */
+	.debug_srcinfo  0 : { *(.debug_srcinfo) }
+	.debug_sfnames  0 : { *(.debug_sfnames) }
+	/* DWARF 1.1 and DWARF 2 */
+	.debug_aranges  0 : { *(.debug_aranges) }
+	.debug_pubnames 0 : { *(.debug_pubnames) }
+	/* DWARF 2 */
+	.debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
+	.debug_abbrev   0 : { *(.debug_abbrev) }
+	.debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
+	.debug_frame    0 : { *(.debug_frame) }
+	.debug_str      0 : { *(.debug_str) }
+	.debug_loc      0 : { *(.debug_loc) }
+	.debug_macinfo  0 : { *(.debug_macinfo) }
+	/* SGI/MIPS DWARF 2 extensions */
+	.debug_weaknames 0 : { *(.debug_weaknames) }
+	.debug_funcnames 0 : { *(.debug_funcnames) }
+	.debug_typenames 0 : { *(.debug_typenames) }
+	.debug_varnames  0 : { *(.debug_varnames) }
+	/* DWARF 3 */
+	.debug_pubtypes 0 : { *(.debug_pubtypes) }
+	.debug_ranges   0 : { *(.debug_ranges) }
+	/* DWARF Extension.  */
+	.debug_macro    0 : { *(.debug_macro) }
+	.debug_addr     0 : { *(.debug_addr) }
+	.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
+	/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+}
+
+/* ESP32C3 ROM functions from ./interface-esp32c3.yml */
+
+/* Functions */
+__absvdi2 = 0x40000764;
+__absvsi2 = 0x40000768;
+__adddf3 = 0x4000076c;
+__addsf3 = 0x40000770;
+__addvdi3 = 0x40000774;
+__addvsi3 = 0x40000778;
+__ashldi3 = 0x4000077c;
+__ashrdi3 = 0x40000780;
+__bswapdi2 = 0x40000784;
+__bswapsi2 = 0x40000788;
+__clear_cache = 0x4000078c;
+__clrsbdi2 = 0x40000790;
+__clrsbsi2 = 0x40000794;
+__clzdi2 = 0x40000798;
+__clzsi2 = 0x4000079c;
+__cmpdi2 = 0x400007a0;
+__ctzdi2 = 0x400007a4;
+__ctzsi2 = 0x400007a8;
+__divdc3 = 0x400007ac;
+__divdf3 = 0x400007b0;
+__divdi3 = 0x400007b4;
+__divsc3 = 0x400007b8;
+__divsf3 = 0x400007bc;
+__divsi3 = 0x400007c0;
+__eqdf2 = 0x400007c4;
+__eqsf2 = 0x400007c8;
+__extendsfdf2 = 0x400007cc;
+__ffsdi2 = 0x400007d0;
+__ffssi2 = 0x400007d4;
+__fixdfdi = 0x400007d8;
+__fixdfsi = 0x400007dc;
+__fixsfdi = 0x400007e0;
+__fixsfsi = 0x400007e4;
+__fixunsdfsi = 0x400007e8;
+__fixunssfdi = 0x400007ec;
+__fixunssfsi = 0x400007f0;
+__floatdidf = 0x400007f4;
+__floatdisf = 0x400007f8;
+__floatsidf = 0x400007fc;
+__floatsisf = 0x40000800;
+__floatundidf = 0x40000804;
+__floatundisf = 0x40000808;
+__floatunsidf = 0x4000080c;
+__floatunsisf = 0x40000810;
+__gcc_bcmp = 0x40000814;
+__gedf2 = 0x40000818;
+__gesf2 = 0x4000081c;
+__gtdf2 = 0x40000820;
+__gtsf2 = 0x40000824;
+__ledf2 = 0x40000828;
+__lesf2 = 0x4000082c;
+__lshrdi3 = 0x40000830;
+__ltdf2 = 0x40000834;
+__ltsf2 = 0x40000838;
+__moddi3 = 0x4000083c;
+__modsi3 = 0x40000840;
+__muldc3 = 0x40000844;
+__muldf3 = 0x40000848;
+__muldi3 = 0x4000084c;
+__mulsc3 = 0x40000850;
+__mulsf3 = 0x40000854;
+__mulsi3 = 0x40000858;
+__mulvdi3 = 0x4000085c;
+__mulvsi3 = 0x40000860;
+__nedf2 = 0x40000864;
+__negdf2 = 0x40000868;
+__negdi2 = 0x4000086c;
+__negsf2 = 0x40000870;
+__negvdi2 = 0x40000874;
+__negvsi2 = 0x40000878;
+__nesf2 = 0x4000087c;
+__paritysi2 = 0x40000880;
+__popcountdi2 = 0x40000884;
+__popcountsi2 = 0x40000888;
+__powidf2 = 0x4000088c;
+__powisf2 = 0x40000890;
+__subdf3 = 0x40000894;
+__subsf3 = 0x40000898;
+__subvdi3 = 0x4000089c;
+__subvsi3 = 0x400008a0;
+__truncdfsf2 = 0x400008a4;
+__ucmpdi2 = 0x400008a8;
+__udivdi3 = 0x400008ac;
+__udivmoddi4 = 0x400008b0;
+__udivsi3 = 0x400008b4;
+__udiv_w_sdiv = 0x400008b8;
+__umoddi3 = 0x400008bc;
+__umodsi3 = 0x400008c0;
+__unorddf2 = 0x400008c4;
+__unordsf2 = 0x400008c8;
+
+
+/***************************************
+Group newlib
+***************************************/
+
+/* Functions */
+memset = 0x40000354;
+memcpy = 0x40000358;
+memmove = 0x4000035c;
+memcmp = 0x40000360;
+strcpy = 0x40000364;
+strncpy = 0x40000368;
+strcmp = 0x4000036c;
+strncmp = 0x40000370;
+strlen = 0x40000374;
+strstr = 0x40000378;
+bzero = 0x4000037c;
+isalnum = 0x40000388;
+isalpha = 0x4000038c;
+isascii = 0x40000390;
+isblank = 0x40000394;
+iscntrl = 0x40000398;
+isdigit = 0x4000039c;
+islower = 0x400003a0;
+isgraph = 0x400003a4;
+isprint = 0x400003a8;
+ispunct = 0x400003ac;
+isspace = 0x400003b0;
+isupper = 0x400003b4;
+toupper = 0x400003b8;
+tolower = 0x400003bc;
+toascii = 0x400003c0;
+memccpy = 0x400003c4;
+memchr = 0x400003c8;
+memrchr = 0x400003cc;
+strcasecmp = 0x400003d0;
+strcasestr = 0x400003d4;
+strcat = 0x400003d8;
+strdup = 0x400003dc;
+strchr = 0x400003e0;
+strcspn = 0x400003e4;
+strcoll = 0x400003e8;
+strlcat = 0x400003ec;
+strlcpy = 0x400003f0;
+strlwr = 0x400003f4;
+strncasecmp = 0x400003f8;
+strncat = 0x400003fc;
+strndup = 0x40000400;
+strnlen = 0x40000404;
+strrchr = 0x40000408;
+strsep = 0x4000040c;
+strspn = 0x40000410;
+strtok_r = 0x40000414;
+strupr = 0x40000418;
+longjmp = 0x4000041c;
+setjmp = 0x40000420;
+abs = 0x40000424;
+div = 0x40000428;
+labs = 0x4000042c;
+ldiv = 0x40000430;
+qsort = 0x40000434;
+rand_r = 0x40000438;
+rand = 0x4000043c;
+srand = 0x40000440;
+utoa = 0x40000444;
+itoa = 0x40000448;
+atoi = 0x4000044c;
+atol = 0x40000450;
+strtol = 0x40000454;
+strtoul = 0x40000458;
+
+uart_tx_one_char = 0x40000068;
+uart_rx_one_char = 0x40000070;
+uart_rx_one_char_block = 0x40000074;
+uart_tx_flush = 0x40000080;
+
+/***************************************
+Group gpio
+***************************************/
+
+/* Functions */
+gpio_input_get = 0x4000059c;
+gpio_matrix_in = 0x400005a0;
+gpio_matrix_out = 0x400005a4;
+gpio_output_disable = 0x400005a8;
+gpio_output_enable = 0x400005ac;
+gpio_output_set = 0x400005b0;
+gpio_pad_hold = 0x400005b4;
+gpio_pad_input_disable = 0x400005b8;
+gpio_pad_input_enable = 0x400005bc;
+gpio_pad_pulldown = 0x400005c0;
+gpio_pad_pullup = 0x400005c4;
+gpio_pad_select_gpio = 0x400005c8;
+gpio_pad_set_drv = 0x400005cc;
+gpio_pad_unhold = 0x400005d0;
+gpio_pin_wakeup_disable = 0x400005d4;
+gpio_pin_wakeup_enable = 0x400005d8;
+gpio_bypass_matrix_in = 0x400005dc;
diff --git a/src/kernel/riscv32/link-old.ld b/src/kernel/riscv32/link-old.ld
new file mode 100644
index 0000000..eea882d
--- /dev/null
+++ b/src/kernel/riscv32/link-old.ld
@@ -0,0 +1,170 @@
+OUTPUT_ARCH( "riscv" )
+
+ENTRY( _start )
+
+SECTIONS
+{
+	.header : AT(0)
+	{
+		_irom_start = .;
+		LONG(0xaedb041d)
+		LONG(0xaedb041d)
+	} > irom
+
+	.text.entry ORIGIN(irom) + 8 :
+	{
+		KEEP(*(.text.entry))
+	} > irom
+
+	.text :
+	{
+		*(.text .stub .text.* .gnu.linkonce.t.*)
+		*(.gnu.warning)
+	}
+	. = ALIGN(4);
+	PROVIDE (__etext = .);
+	PROVIDE (_etext = .);
+	PROVIDE (etext = .);
+	_irom_size = . - _irom_start;
+
+	_drom_start = ORIGIN(drom) + _irom_size;
+	.rodata  _drom_start : AT(_irom_size)
+	{
+		*(.rodata .rodata.* .gnu.linkonce.r.*)
+	} > drom
+
+	.rodata1 :
+	{
+		*(.rodata1)
+	}
+
+	.init_array :
+	{
+		PROVIDE_HIDDEN (__init_array_start = .);
+		KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
+		KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
+		PROVIDE_HIDDEN (__init_array_end = .);
+	} > drom
+
+	.fini_array :
+	{
+		PROVIDE_HIDDEN (__fini_array_start = .);
+		KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
+		KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
+		PROVIDE_HIDDEN (__fini_array_end = .);
+	} > drom
+
+	.ctors :
+	{
+		KEEP (*crtbegin.o(.ctors))
+		KEEP (*crtbegin?.o(.ctors))
+		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
+		KEEP (*(SORT(.ctors.*)))
+		KEEP (*(.ctors))
+	}
+
+	.dtors :
+	{
+		KEEP (*crtbegin.o(.dtors))
+		KEEP (*crtbegin?.o(.dtors))
+		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
+		KEEP (*(SORT(.dtors.*)))
+		KEEP (*(.dtors))
+	}
+
+	_drom_size = . - _drom_start;
+
+	.data ORIGIN(ram) : AT(_irom_size + _drom_size) 
+	{
+		_data_start = .;
+		__DATA_BEGIN__ = .;
+		*(.data .data.* .gnu.linkonce.d.*)
+		*(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*)
+		SORT(CONSTRUCTORS)
+	} > ram
+	.data1 :
+	{
+		*(.data1)
+	}
+	.sdata :
+	{
+		__SDATA_BEGIN__ = .;
+		*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*)
+		*(.sdata .sdata.* .gnu.linkonce.s.*)
+	}
+	. = ALIGN(4);
+	_edata = .; PROVIDE (edata = .);
+	_data_lma = ORIGIN(drom) + LOADADDR(.data);
+	_data_size = _edata - _data_start;
+
+	__bss_start = .;
+	.sbss           :
+	{
+		*(.dynsbss)
+		*(.sbss .sbss.* .gnu.linkonce.sb.*)
+		*(.scommon)
+	}
+	.bss            :
+	{
+		*(.dynbss)
+		*(.bss .bss.* .gnu.linkonce.b.*)
+		*(COMMON)
+	}
+	. = ALIGN(4);
+	__BSS_END__ = .;
+	__global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800,
+							MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800));
+	_end = .; PROVIDE (end = .);
+
+	/* Stack */
+	.stack :
+	{
+		__stack_bottom = .;
+		__stack_top = ORIGIN(ram) + LENGTH(ram);
+		__stack_size_min = 0x4000;
+		ASSERT(__stack_bottom + __stack_size_min < __stack_top, "Error: no space for stack");
+	}
+
+	/* Stabs debugging sections.  */
+	.stab          0 : { *(.stab) }
+	.stabstr       0 : { *(.stabstr) }
+	.stab.excl     0 : { *(.stab.excl) }
+	.stab.exclstr  0 : { *(.stab.exclstr) }
+	.stab.index    0 : { *(.stab.index) }
+	.stab.indexstr 0 : { *(.stab.indexstr) }
+	.comment       0 : { *(.comment) }
+	.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
+	/* DWARF debug sections.
+	Symbols in the DWARF debugging sections are relative to the beginning
+	of the section so we begin them at 0.  */
+	/* DWARF 1 */
+	.debug          0 : { *(.debug) }
+	.line           0 : { *(.line) }
+	/* GNU DWARF 1 extensions */
+	.debug_srcinfo  0 : { *(.debug_srcinfo) }
+	.debug_sfnames  0 : { *(.debug_sfnames) }
+	/* DWARF 1.1 and DWARF 2 */
+	.debug_aranges  0 : { *(.debug_aranges) }
+	.debug_pubnames 0 : { *(.debug_pubnames) }
+	/* DWARF 2 */
+	.debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
+	.debug_abbrev   0 : { *(.debug_abbrev) }
+	.debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
+	.debug_frame    0 : { *(.debug_frame) }
+	.debug_str      0 : { *(.debug_str) }
+	.debug_loc      0 : { *(.debug_loc) }
+	.debug_macinfo  0 : { *(.debug_macinfo) }
+	/* SGI/MIPS DWARF 2 extensions */
+	.debug_weaknames 0 : { *(.debug_weaknames) }
+	.debug_funcnames 0 : { *(.debug_funcnames) }
+	.debug_typenames 0 : { *(.debug_typenames) }
+	.debug_varnames  0 : { *(.debug_varnames) }
+	/* DWARF 3 */
+	.debug_pubtypes 0 : { *(.debug_pubtypes) }
+	.debug_ranges   0 : { *(.debug_ranges) }
+	/* DWARF Extension.  */
+	.debug_macro    0 : { *(.debug_macro) }
+	.debug_addr     0 : { *(.debug_addr) }
+	.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
+	/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
+}
diff --git a/src/kernel/riscv32/link.ld b/src/kernel/riscv32/link.ld
new file mode 100644
index 0000000..abbd617
--- /dev/null
+++ b/src/kernel/riscv32/link.ld
@@ -0,0 +1,46 @@
+OUTPUT_ARCH( "riscv" )
+
+ENTRY( _start )
+
+MEMORY
+{
+  ram   (rw) : ORIGIN = 0x80000000, LENGTH = 128M
+}
+
+PHDRS
+{
+  text PT_LOAD;
+  data PT_LOAD;
+  bss PT_LOAD;
+}
+
+SECTIONS
+{
+  .text : {
+    PROVIDE(_text_start = .);
+    *(.text.init) *(.text .text.*)
+    PROVIDE(_text_end = .);
+  } >ram AT>ram :text
+
+  .rodata : {
+    PROVIDE(_rodata_start = .);
+    *(.rodata .rodata.*)
+    PROVIDE(_rodata_end = .);
+  } >ram AT>ram :text
+
+  .data : {
+    . = ALIGN(4096);
+    PROVIDE(_data_start = .);
+    *(.sdata .sdata.*) *(.data .data.*)
+    PROVIDE(_data_end = .);
+  } >ram AT>ram :data
+
+  .bss :{
+    PROVIDE(_bss_start = .);
+    *(.sbss .sbss.*) *(.bss .bss.*)
+    PROVIDE(_bss_end = .);
+  } >ram AT>ram :bss
+
+  PROVIDE(_memory_start = ORIGIN(ram));
+  PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
+}
diff --git a/src/kernel/riscv32/main.c b/src/kernel/riscv32/main.c
new file mode 100644
index 0000000..9603292
--- /dev/null
+++ b/src/kernel/riscv32/main.c
@@ -0,0 +1,13 @@
+#include "kint.h"
+#include "serial.h"
+
+void kmain(uint mhartid)
+{
+	init_serial();
+
+	serial_write("Hello from Bluejay!\r\n");
+	
+	while (true)
+	{
+	}
+}
diff --git a/src/kernel/riscv32/riscv32.jmk b/src/kernel/riscv32/riscv32.jmk
new file mode 100644
index 0000000..d90b47a
--- /dev/null
+++ b/src/kernel/riscv32/riscv32.jmk
@@ -0,0 +1,10 @@
+# -*- tcl -*-
+
+srcs boot.s \
+	main.c
+
+rule qemu "" {
+	shell "qemu-system-riscv32 -M ? | grep virt >/dev/null || exit"
+	shell "echo C-a X to exit qemu"
+	shell "qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -kernel kernel.elf"
+}
diff --git a/src/kernel/riscv32/serial-esp32c3.c b/src/kernel/riscv32/serial-esp32c3.c
new file mode 100644
index 0000000..32b8178
--- /dev/null
+++ b/src/kernel/riscv32/serial-esp32c3.c
@@ -0,0 +1,32 @@
+#include "serial.h"
+#include "kint.h"
+
+extern int uart_tx_one_char(char byte);
+
+/**
+ * @param c Pointer to input
+ * @returns OK or not
+ */
+extern bool uart_rx_one_char(char *c);
+
+/**
+ * @param uart_no UART number
+ */
+extern void uart_tx_flush(uchar uart_no);
+
+void init_serial()
+{
+}
+
+void serial_put(char byte)
+{
+	uart_tx_one_char(byte);
+}
+
+void serial_write(char *string)
+{
+	while (*string)
+	{
+		serial_put(*string++);
+	}
+}
diff --git a/src/kernel/riscv32/serial-generic.c b/src/kernel/riscv32/serial-generic.c
new file mode 100644
index 0000000..ea231d9
--- /dev/null
+++ b/src/kernel/riscv32/serial-generic.c
@@ -0,0 +1,35 @@
+#include "kint.h"
+#include "serial.h"
+
+/* Base address for QEMU */
+/* TODO: Abstract for ESP32C3 */
+#define UART_BASE (void *)0x10000000
+#define UART_LSR_EMPTY_MASK 0x40
+
+/* UART transmitter holding register */
+uchar *uart_thr = UART_BASE;
+
+/* UART line status register */
+uchar *uart_lsr = UART_BASE + 5;
+
+void init_serial()
+{
+}
+
+void serial_put(char byte)
+{
+	while (*uart_lsr ^ UART_LSR_EMPTY_MASK)
+	{
+		/* block until UART clear */
+	}
+
+	*uart_thr = byte;
+}
+
+void serial_write(char *string)
+{
+	while (*string)
+	{
+		serial_put(*string++);
+	}
+}
diff --git a/src/lisp/Jmk b/src/lisp/Jmk
deleted file mode 100644
index 022fcc0..0000000
--- a/src/lisp/Jmk
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- mode:makefile -*-
-
-init(lisp, lisp)
-
-option(PLAT, "`platform to build for: either linux or bluejay'", linux)
-
-# preset(optimize)
-preset(32)
-preset(debug)
-preset(warn)
-preset(nasm)
-
-archetype(c)
-archetype(asm)
-
-NO_READLINE ?= 0
-
-CFLAGS += -Ivendor/luajit/dynasm -Werror # -fsanitize=address
-ASMFLAGS += -felf -Fdwarf
-
-ifeq ($(NO_READLINE),1)
-CFLAGS += -DNO_READLINE
-else
-LDFLAGS += -lreadline
-CFLAGS += -lreadline
-endif
-
-OBJECTS = main.o \
-			lisp.o \
-			compiler.o \
-			lib/std.o \
-			plat/linux.o \
-			istream.o \
-			gc.o \
-			call_list.o \
-			error.o
-
-LUA = vendor/luajit/src/host/minilua
-
-$(LUA): vendor/luajit/src/host/minilua.c
-	status_log(CC, $<)
-	@$(CC) $< -o $@ -lm
-
-compiler.c: compiler.dasc | $(LUA)
-	status_log(DYNASM, $<)
-	@$(LUA) vendor/luajit/dynasm/dynasm.lua -o $@ $<
-
-type(executable)
-
-F ?= test.lisp
-lisp_libpath = $(ROOT)/lib/lisp
-
-run: lisp
-	status_log(LISP, $(F))
-	@LISP_LIBRARY_PATH="$(lisp_libpath)" ./lisp $(F)
-
-repl: lisp
-	status_log(LISP, repl)
-	@LISP_LIBRARY_PATH="$(lisp_libpath)" ./lisp $(ROOT)/lib/lisp/repl/repl.lisp
-
-leak-check: lisp
-	status_log(VALGRIND, lisp $(F))
-	@LISP_LIBRARY_PATH="$(lisp_libpath)" valgrind --leak-check=full ./lisp $(F)
-
-format:
-	status_log(FORMAT, *)
-	@clang-format -i *.c *.h *.dasc plat/* lib/*
-
-finish
diff --git a/src/lisp/Jmk2 b/src/lisp/Jmk2
index 1f4e6b1..5446f50 100644
--- a/src/lisp/Jmk2
+++ b/src/lisp/Jmk2
@@ -2,6 +2,10 @@
 
 init lisp
 
+presets 32 debug warn nasm
+cflags -Ivendor/luajit/dynasm -O0
+asmflags -felf32
+
 # Make this `readline', `edit', or `none'
 option READLINE readline
 
@@ -13,10 +17,6 @@
 	cflags -L/usr/lib/i386-linux-gnu -l$options(READLINE)
 }
 
-presets 32 debug warn nasm
-cflags -Ivendor/luajit/dynasm -O0
-asmflags -felf32
-
 set lua [pwd]/vendor/luajit/src/host/minilua
 
 rule $lua ${lua}.c {