/* 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 alone */ return 1; case 22: { /* argv: entry 0 is , as in zy(1) */ static const char lead[] = ""; 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); }