aboutsummaryrefslogtreecommitdiff
path: root/src/baremetal/port.c
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-12 08:46:23 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-12 08:46:23 +0330
commitecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch)
tree70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /src/baremetal/port.c
a
Diffstat (limited to 'src/baremetal/port.c')
-rw-r--r--src/baremetal/port.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/baremetal/port.c b/src/baremetal/port.c
new file mode 100644
index 0000000..636c1ea
--- /dev/null
+++ b/src/baremetal/port.c
@@ -0,0 +1,339 @@
+/* UART port for the microcore. No libc. BARE_RISCV is the 16550 at
+ 0x10000000 on qemu's riscv virt. BARE_AARCH64 is the PL011 at 0x09000000
+ on qemu's aarch64 virt.
+
+ BARE_INPUT=0 (a program baked into the image, nothing typed): eof is
+ true and getc is -1. BARE_INPUT=1: bytes come from the UART. getc and
+ eof both wait until a byte is there. 0x04 (EOT) is end of input and is
+ not delivered. A gap is not the end, so a stream longer than the UART
+ FIFO still echoes until EOT. The compiler's backtick still ends a unit;
+ the byte after it is the program's first input, or EOT when the program
+ reads nothing.
+
+ Syscalls, same numbers as the hosted ports:
+ 1 open 2 close 3 read 4 write a small RAM file store
+ 5 getenv BOARD is "riscv" or "aarch64"
+ 6 time the hardware counter, not unix seconds
+ 7 exit(status)
+ 8 random
+ 9 memory 0 micro_mem_words(), 1 HEAP_LO, 2 HEAP_HI, 3 micro_mem_words()*8
+ 10 bopen 11 bread 12 bwrite 13 bgetenv
+ 10..13 use packed bytes. Anything else is -1. */
+#include "micro_port.h"
+#include "bops.h"
+
+#ifndef BARE_INPUT
+#define BARE_INPUT 0
+#endif
+
+#if defined(BARE_RISCV)
+static volatile unsigned char *const uart = (volatile unsigned char *)0x10000000;
+#define UART_LSR 5
+#define LSR_RX 0x01
+#define LSR_TX 0x20
+
+static void uart_init(void)
+{
+ /* QEMU's 16550 already speaks 8N1. Rewriting LCR or FCR after
+ bytes have arrived drops the first one, and the source is
+ already in the FIFO by the time this runs. */
+}
+
+static int rx_ready(void) { return uart[UART_LSR] & LSR_RX; }
+static void uart_write(unsigned char c)
+{
+ while ((uart[UART_LSR] & LSR_TX) == 0) ;
+ uart[0] = c;
+}
+static int uart_read(void)
+{
+ while (!rx_ready()) ;
+ return uart[0];
+}
+static unsigned long timer(void)
+{
+ unsigned long t;
+ __asm__ volatile("rdtime %0" : "=r"(t));
+ return t;
+}
+static void exit_status(int code)
+{
+ volatile unsigned *test = (volatile unsigned *)0x100000;
+ /* PASS exits 0. FAIL's high half is the status (16 bits). */
+ if (code == 0) *test = 0x5555;
+ else *test = ((unsigned)(code & 0xffff) << 16) | 0x3333;
+ for (;;) ;
+}
+#elif defined(BARE_AARCH64)
+static volatile unsigned *const uart = (volatile unsigned *)0x09000000;
+#define UART_FR 6 /* 0x18 / 4 */
+#define FR_RXFE 0x10
+#define FR_TXFF 0x20
+
+static void uart_init(void) { }
+static int rx_ready(void) { return (uart[UART_FR] & FR_RXFE) == 0; }
+static void uart_write(unsigned char c)
+{
+ while (uart[UART_FR] & FR_TXFF) ;
+ uart[0] = c;
+}
+static int uart_read(void)
+{
+ while (!rx_ready()) ;
+ return (int)uart[0];
+}
+static unsigned long timer(void)
+{
+ unsigned long t;
+ __asm__ volatile("mrs %0, cntvct_el0" : "=r"(t));
+ return t;
+}
+static void exit_status(int code)
+{
+ /* SYS_EXIT. x1 points at {ADP_Stopped_ApplicationExit, status}.
+ QEMU has to be started with -semihosting; PSCI SYSTEM_OFF cannot
+ carry a status. */
+ unsigned long blk[2];
+ register unsigned long x0 __asm__("x0") = 0x18;
+ register unsigned long x1 __asm__("x1");
+ blk[0] = 0x20026ul;
+ blk[1] = (unsigned long)code;
+ x1 = (unsigned long)blk;
+ __asm__ volatile("hlt #0xf000" :: "r"(x0), "r"(x1) : "memory");
+ for (;;) ;
+}
+#else
+#error define BARE_RISCV or BARE_AARCH64
+#endif
+
+static int saw_end, have_peek, peekc;
+
+void micro_putc(int c) { uart_write((unsigned char)c); }
+
+void micro_msg(const char *s)
+{
+ while (*s) uart_write((unsigned char)*s++);
+}
+
+/* the machine's memory on this board: the arena below, which is the board's RAM as
+ memmap.h sizes it. a board's RAM is a fixed amount of hardware, so this is the one
+ place the port has a size; it returns NULL past it. */
+/* physical backing is separate from the vm's configurable growth limit. */
+#ifndef MICRO_PORT_WORDS
+#define MICRO_PORT_WORDS (HEAP_HI > PW ? HEAP_HI : PW)
+#endif
+static Cell arena[MICRO_PORT_WORDS];
+
+Cell *micro_mem_alloc(UCell words)
+{
+ if (words > MICRO_PORT_WORDS) return 0;
+ return arena;
+}
+
+static int inmem(Cell a) { return (UCell)a < (UCell)micro_mem_words(); }
+
+static int zcstr(Cell *mem, Cell addr, char *dst, int max)
+{
+ int i = 0, c;
+ if (max < 1) return -1;
+ if (!inmem(addr)) { dst[0] = 0; return -1; }
+ while (i < max - 1 && inmem(addr + i)) {
+ c = (int)mem[addr + i];
+ if (!c) break;
+ dst[i++] = (char)(c & 255);
+ }
+ dst[i] = 0;
+ return i;
+}
+
+static int streq(const char *a, const char *b)
+{
+ int i = 0;
+ while (a[i] && a[i] == b[i]) i++;
+ return a[i] == b[i];
+}
+
+#define NFILES 4
+#define FCAP 128
+#define NLEN 32
+static struct {
+ char name[NLEN];
+ unsigned char data[FCAP];
+ int len, pos;
+} ramf[NFILES];
+
+static int ram_slot(const char *name, int create)
+{
+ int i, free_i = -1, n;
+ for (i = 0; i < NFILES; i++) {
+ if (!ramf[i].name[0]) { if (free_i < 0) free_i = i; continue; }
+ if (streq(ramf[i].name, name)) return i;
+ }
+ if (!create || free_i < 0) return -1;
+ for (n = 0; name[n] && n < NLEN - 1; n++) ramf[free_i].name[n] = name[n];
+ ramf[free_i].name[n] = 0;
+ ramf[free_i].len = 0;
+ ramf[free_i].pos = 0;
+ return free_i;
+}
+
+static UCell rng;
+static Cell rnd(void)
+{
+ if (!rng) rng = timer() ^ 0x9E3779B97F4A7C15ull;
+ if (!rng) rng = 0x9E3779B97F4A7C15ull;
+ rng ^= rng << 13;
+ rng ^= rng >> 7;
+ rng ^= rng << 17;
+ if ((Cell)rng == -1) rng ^= 0x100;
+ return (Cell)rng;
+}
+
+static void board_name(char *dst)
+{
+#if defined(BARE_RISCV)
+ dst[0] = 'r'; dst[1] = 'i'; dst[2] = 's'; dst[3] = 'c'; dst[4] = 'v'; dst[5] = 0;
+#else
+ dst[0] = 'a'; dst[1] = 'a'; dst[2] = 'r'; dst[3] = 'c'; dst[4] = 'h';
+ dst[5] = '6'; dst[6] = '4'; dst[7] = 0;
+#endif
+}
+
+Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
+{
+ char path[NLEN];
+ int fd, n, i;
+ switch ((int)op) {
+ case 1:
+ if (zcstr(mem, a, path, NLEN) < 0 || !path[0]) return -1;
+ fd = ram_slot(path, b == 1);
+ if (fd < 0) return -1;
+ if (b == 1) { ramf[fd].len = 0; ramf[fd].pos = 0; }
+ else ramf[fd].pos = 0;
+ return fd;
+ case 2:
+ if ((UCell)a >= NFILES || !ramf[a].name[0]) return -1;
+ ramf[a].pos = 0;
+ return 0;
+ case 3:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c && ramf[a].pos < ramf[a].len && inmem(b + n)) {
+ mem[b + n] = ramf[a].data[ramf[a].pos++];
+ n++;
+ }
+ return n;
+ case 4:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c && ramf[a].len < FCAP && inmem(b + n)) {
+ ramf[a].data[ramf[a].len++] = (unsigned char)mem[b + n];
+ n++;
+ }
+ ramf[a].pos = ramf[a].len;
+ return n;
+ case 5: {
+ char board[8], *e;
+ if (zcstr(mem, a, path, NLEN) < 0) return -1;
+ board_name(board);
+ e = streq(path, "BOARD") ? board : 0;
+ if (!e) return -1;
+ if (c < 1 || !inmem(b)) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
+ if (inmem(b + i)) mem[b + i] = 0;
+ return i;
+ }
+ case 6:
+ return (Cell)timer();
+ case 7:
+ exit_status((int)a);
+ return 0;
+ case 8:
+ return rnd();
+ case 9:
+ if (a == 0) return micro_mem_words();
+ if (a == 1) return HEAP_LO;
+ if (a == 2) return HEAP_HI;
+ if (a == 3) return (Cell)micro_mem_words() * 8;
+ return -1;
+ case 10:
+ if (bop_cstr(mem, a, path, NLEN, micro_mem_words()) < 0 || !path[0]) return -1;
+ fd = ram_slot(path, b == 1);
+ if (fd < 0) return -1;
+ if (b == 1) { ramf[fd].len = 0; ramf[fd].pos = 0; }
+ else ramf[fd].pos = 0;
+ return fd;
+ case 11:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c && ramf[a].pos < ramf[a].len && bop_ok(b + n, 1, micro_mem_words())) {
+ bop_put1(mem, b + n, ramf[a].data[ramf[a].pos++]);
+ n++;
+ }
+ return n;
+ case 12:
+ if ((UCell)a >= NFILES || !ramf[a].name[0] || c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c && ramf[a].len < FCAP && bop_ok(b + n, 1, micro_mem_words())) {
+ ramf[a].data[ramf[a].len++] = (unsigned char)bop_load(mem, b + n, 1);
+ n++;
+ }
+ ramf[a].pos = ramf[a].len;
+ return n;
+ case 13: {
+ char board[8], *e;
+ if (bop_cstr(mem, a, path, NLEN, micro_mem_words()) < 0) return -1;
+ board_name(board);
+ e = streq(path, "BOARD") ? board : 0;
+ if (!e) return -1;
+ if (c < 1 || !bop_ok(b, 1, micro_mem_words())) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && bop_ok(b + i, 1, micro_mem_words())) { bop_put1(mem, b + i, (unsigned char)e[i]); i++; }
+ if (bop_ok(b + i, 1, micro_mem_words())) bop_put1(mem, b + i, 0);
+ return i;
+ }
+ case 21: /* argc: a board has no command line, so <eval> alone */
+ return 1;
+ case 22: { /* argv: entry 0 is <eval>, as in zy(1) */
+ static const char lead[] = "<eval>";
+ if (a != 0) return -1;
+ if (!b) return 6;
+ if (c < 7 || !inmem(b) || !inmem(b + 6)) return -1;
+ for (i = 0; i < 6; i++) mem[b + i] = (unsigned char)lead[i];
+ mem[b + 6] = 0;
+ return 6;
+ }
+ case 23: /* envc: a board has no environment */
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+int micro_getc(void)
+{
+ int ch;
+ if (!BARE_INPUT || saw_end) return -1;
+ if (have_peek) { have_peek = 0; ch = peekc; }
+ else ch = uart_read();
+ if (ch == 4) { saw_end = 1; return -1; }
+ return ch;
+}
+
+int micro_eof(void)
+{
+ if (!BARE_INPUT || saw_end) return 1;
+ if (!have_peek) {
+ peekc = uart_read(); /* wait: an empty FIFO is not the end */
+ have_peek = 1;
+ }
+ if (peekc == 4) { saw_end = 1; have_peek = 0; return 1; }
+ return 0;
+}
+
+void bare_main(void)
+{
+ uart_init();
+ exit_status(micro_run() != 0);
+}