summaryrefslogtreecommitdiff
path: root/code/fe310/gloss
diff options
context:
space:
mode:
Diffstat (limited to 'code/fe310/gloss')
-rw-r--r--code/fe310/gloss/Makefile17
-rw-r--r--code/fe310/gloss/crt0.S246
-rw-r--r--code/fe310/gloss/sys_access.c8
-rw-r--r--code/fe310/gloss/sys_chdir.c8
-rw-r--r--code/fe310/gloss/sys_chmod.c9
-rw-r--r--code/fe310/gloss/sys_chown.c9
-rw-r--r--code/fe310/gloss/sys_close.c8
-rw-r--r--code/fe310/gloss/sys_execve.c8
-rw-r--r--code/fe310/gloss/sys_exit.c7
-rw-r--r--code/fe310/gloss/sys_faccessat.c8
-rw-r--r--code/fe310/gloss/sys_fork.c8
-rw-r--r--code/fe310/gloss/sys_fstat.c9
-rw-r--r--code/fe310/gloss/sys_fstatat.c9
-rw-r--r--code/fe310/gloss/sys_ftime.c9
-rw-r--r--code/fe310/gloss/sys_getcwd.c8
-rw-r--r--code/fe310/gloss/sys_getpid.c7
-rw-r--r--code/fe310/gloss/sys_isatty.c7
-rw-r--r--code/fe310/gloss/sys_kill.c8
-rw-r--r--code/fe310/gloss/sys_link.c7
-rw-r--r--code/fe310/gloss/sys_lseek.c9
-rw-r--r--code/fe310/gloss/sys_lstat.c8
-rw-r--r--code/fe310/gloss/sys_open.c8
-rw-r--r--code/fe310/gloss/sys_openat.c8
-rw-r--r--code/fe310/gloss/sys_read.c23
-rw-r--r--code/fe310/gloss/sys_sbrk.c39
-rw-r--r--code/fe310/gloss/sys_stat.c9
-rw-r--r--code/fe310/gloss/sys_sysconf.c16
-rw-r--r--code/fe310/gloss/sys_unlink.c8
-rw-r--r--code/fe310/gloss/sys_utime.c9
-rw-r--r--code/fe310/gloss/sys_wait.c7
-rw-r--r--code/fe310/gloss/sys_write.c22
31 files changed, 566 insertions, 0 deletions
diff --git a/code/fe310/gloss/Makefile b/code/fe310/gloss/Makefile
new file mode 100644
index 0000000..a937a53
--- /dev/null
+++ b/code/fe310/gloss/Makefile
@@ -0,0 +1,17 @@
+include ../common.mk
+CFLAGS += -I../include
+
+src = sys_access.c sys_chdir.c sys_chmod.c sys_chown.c sys_close.c sys_execve.c sys_exit.c sys_faccessat.c sys_fork.c sys_fstat.c sys_fstatat.c sys_ftime.c sys_getcwd.c sys_getpid.c \
+ sys_isatty.c sys_kill.c sys_link.c sys_lseek.c sys_lstat.c sys_open.c sys_openat.c sys_read.c sys_sbrk.c sys_stat.c sys_sysconf.c sys_unlink.c sys_utime.c sys_wait.c sys_write.c
+obj = crt0.o $(src:.c=.o)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c $<
+
+%.o: %.S
+ $(CC) $(CFLAGS) -c $<
+
+all: $(obj)
+
+clean:
+ rm -f *.o
diff --git a/code/fe310/gloss/crt0.S b/code/fe310/gloss/crt0.S
new file mode 100644
index 0000000..920ee4b
--- /dev/null
+++ b/code/fe310/gloss/crt0.S
@@ -0,0 +1,246 @@
+/* Copyright (c) 2017-2018 SiFive Inc. All rights reserved.
+
+ This copyrighted material is made available to anyone wishing to use,
+ modify, copy, or redistribute it subject to the terms and conditions
+ of the FreeBSD License. This program is distributed in the hope that
+ it will be useful, but WITHOUT ANY WARRANTY expressed or implied,
+ including the implied warranties of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. A copy of this license is available at
+ http://www.opensource.org/licenses.
+*/
+
+/* crt0.S: Entry point for RISC-V METAL programs. */
+
+.section .text.libgloss.start
+.global _start
+.type _start, @function
+
+ /* _start is defined by the METAL to have been called with the following
+ * arguments:
+ * a0: the hart ID of the currently executing hart. Harts can start at
+ * any arbitrary point, it's the C library's job to ensure the code is
+ * safe.
+ * a1: a pointer to a description of the machine on which this code is
+ * currently executing. This is probably 0 on an embedded system
+ * because they tend to not be dynamically portable. As such, newlib
+ * ignores this argument.
+ * a2: a pointer to a function that must be run after the envirnoment has
+ * been initialized, but before user code can be expected to be run.
+ * If this is 0 then there is no function to be run. */
+_start:
+.cfi_startproc
+.cfi_undefined ra
+
+ /* This is a bit funky: it's not usually sane for _start to return, but in
+ * this case we actually want to in order to signal an error to the METAL. */
+ mv s0, ra
+
+ /* Before doing anything we must initialize the global pointer, as we cannot
+ * safely perform any access that may be relaxed without GP being set. This
+ * is done with relaxation disabled to avoid relaxing the address calculation
+ * to just "addi gp, gp, 0". */
+.option push
+.option norelax
+ la gp, __global_pointer$
+.option pop
+
+ /* The METAL is designed for a bare-metal environment and therefor is expected
+ * to define its own stack pointer. We also align the stack pointer here
+ * because the only RISC-V ABI that's currently defined mandates 16-byte
+ * stack alignment. */
+ la sp, _sp
+
+ /* Increment by hartid number of stack sizes */
+ li t0, 0
+ la t1, __stack_size
+1:
+ beq t0, a0, 1f
+ add sp, sp, t1
+ addi t0, t0, 1
+ j 1b
+1:
+ andi sp, sp, -16
+
+ /* If we're not hart 0, skip the initialization work */
+ la t0, __metal_boot_hart
+ bne a0, t0, _skip_init
+
+ /* Embedded systems frequently require relocating the data segment before C
+ * code can be run -- for example, the data segment may exist in flash upon
+ * boot and then need to get relocated into a non-persistant writable memory
+ * before C code can execute. If this is the case we do so here. This step
+ * is optional: if the METAL provides an environment in which this relocation
+ * is not necessary then it must simply set metal_segment_data_source_start to
+ * be equal to metal_segment_data_target_start. */
+ la t0, metal_segment_data_source_start
+ la t1, metal_segment_data_target_start
+ la t2, metal_segment_data_target_end
+
+ beq t0, t1, 2f
+ bge t1, t2, 2f
+
+1:
+#if __riscv_xlen == 32
+ lw a0, 0(t0)
+ addi t0, t0, 4
+ sw a0, 0(t1)
+ addi t1, t1, 4
+ blt t1, t2, 1b
+#else
+ ld a0, 0(t0)
+ addi t0, t0, 8
+ sd a0, 0(t1)
+ addi t1, t1, 8
+ blt t1, t2, 1b
+#endif
+2:
+
+ /* Copy the ITIM section */
+ la t0, metal_segment_itim_source_start
+ la t1, metal_segment_itim_target_start
+ la t2, metal_segment_itim_target_end
+
+ beq t0, t1, 2f
+ bge t1, t2, 2f
+
+1:
+#if __riscv_xlen == 32
+ lw a0, 0(t0)
+ addi t0, t0, 4
+ sw a0, 0(t1)
+ addi t1, t1, 4
+ blt t1, t2, 1b
+#else
+ ld a0, 0(t0)
+ addi t0, t0, 8
+ sd a0, 0(t1)
+ addi t1, t1, 8
+ blt t1, t2, 1b
+#endif
+2:
+
+ /* Fence all subsequent instruction fetches until after the ITIM writes
+ complete */
+ fence.i
+
+ /* Zero the BSS segment. */
+ la t1, metal_segment_bss_target_start
+ la t2, metal_segment_bss_target_end
+
+ bge t1, t2, 2f
+
+1:
+#if __riscv_xlen == 32
+ sw x0, 0(t1)
+ addi t1, t1, 4
+ blt t1, t2, 1b
+#else
+ sd x0, 0(t1)
+ addi t1, t1, 8
+ blt t1, t2, 1b
+#endif
+2:
+
+ /* At this point we're in an environment that can execute C code. The first
+ * thing to do is to make the callback to the parent environment if it's been
+ * requested to do so. */
+ beqz a2, 1f
+ jalr a2
+1:
+
+ /* The RISC-V port only uses new-style constructors and destructors. */
+ la a0, __libc_fini_array
+ call atexit
+ call __libc_init_array
+
+_skip_init:
+
+ /* Synchronize harts so that secondary harts wait until hart 0 finishes
+ initializing */
+ call __metal_synchronize_harts
+
+ /* Check RISC-V isa and enable FS bits if Floating Point architecture. */
+ csrr a5, misa
+ li a4, 0x10028
+ and a5, a5, a4
+ beqz a5, 1f
+ csrr a5, mstatus
+ lui a4, 0x2
+ or a5, a5, a4
+ csrw mstatus, a5
+ csrwi fcsr, 0
+1:
+
+ /* This is a C runtime, so main() is defined to have some arguments. Since
+ * there's nothing sane the METAL can pass we don't bother with that but
+ * instead just setup as close to a NOP as we can. */
+ li a0, 1 /* argc=1 */
+ la a1, argv /* argv = {"libgloss", NULL} */
+ la a2, envp /* envp = {NULL} */
+ call secondary_main
+
+ /* Call exit to handle libc's cleanup routines. Under normal contains this
+ * shouldn't even get called, but I'm still not using a tail call here
+ * because returning to the METAL is the right thing to do in pathological
+ * situations. */
+ call exit
+
+ /* And here's where we return. Again, it's a bit odd but the METAL defines
+ * this as a bad idea (ie, as opposed to leaving it undefined) and at this
+ * point it's really the only thing left to do. */
+ mv ra, s0
+ ret
+
+.cfi_endproc
+
+/* RISC-V systems always use __libc_{init,fini}_array, but for compatibility we
+ * define _{init,fini} to do nothing. */
+.global _init
+.type _init, @function
+.global _fini
+.type _fini, @function
+_init:
+_fini:
+ ret
+.size _init, .-_init
+.size _fini, .-_fini
+
+/* By default, secondary_main will cause secondary harts to spin forever.
+ * Users can redefine secondary_main themselves to run code on secondary harts */
+.weak secondary_main
+.global secondary_main
+.type secondary_main, @function
+
+secondary_main:
+ addi sp, sp, -16
+#if __riscv_xlen == 32
+ sw ra, 4(sp)
+#else
+ sd ra, 8(sp)
+#endif
+ csrr t0, mhartid
+ la t1, __metal_boot_hart
+ beq t0, t1, 2f
+1:
+ wfi
+ j 1b
+2:
+ call main
+#if __riscv_xlen == 32
+ lw ra, 4(sp)
+#else
+ ld ra, 8(sp)
+#endif
+ addi sp, sp, 16
+ ret
+
+/* This shim allows main() to be passed a set of arguments that can satisfy the
+ * requirements of the C API. */
+.section .rodata.libgloss.start
+argv:
+.dc.a name
+envp:
+.dc.a 0
+name:
+.asciz "libgloss"
+
diff --git a/code/fe310/gloss/sys_access.c b/code/fe310/gloss/sys_access.c
new file mode 100644
index 0000000..c0bc153
--- /dev/null
+++ b/code/fe310/gloss/sys_access.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_access(const char *file, int mode)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_chdir.c b/code/fe310/gloss/sys_chdir.c
new file mode 100644
index 0000000..f33d26a
--- /dev/null
+++ b/code/fe310/gloss/sys_chdir.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_chdir(const char *path)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_chmod.c b/code/fe310/gloss/sys_chmod.c
new file mode 100644
index 0000000..67412bf
--- /dev/null
+++ b/code/fe310/gloss/sys_chmod.c
@@ -0,0 +1,9 @@
+#include <errno.h>
+#include <sys/types.h>
+
+int
+_chmod(const char *path, mode_t mode)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_chown.c b/code/fe310/gloss/sys_chown.c
new file mode 100644
index 0000000..302952e
--- /dev/null
+++ b/code/fe310/gloss/sys_chown.c
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <errno.h>
+
+int
+_chown(const char *path, uid_t owner, gid_t group)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_close.c b/code/fe310/gloss/sys_close.c
new file mode 100644
index 0000000..26dd6a5
--- /dev/null
+++ b/code/fe310/gloss/sys_close.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_close(int file)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_execve.c b/code/fe310/gloss/sys_execve.c
new file mode 100644
index 0000000..9ae9f7e
--- /dev/null
+++ b/code/fe310/gloss/sys_execve.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_execve(const char *name, char *const argv[], char *const env[])
+{
+ errno = ENOMEM;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_exit.c b/code/fe310/gloss/sys_exit.c
new file mode 100644
index 0000000..1502b3e
--- /dev/null
+++ b/code/fe310/gloss/sys_exit.c
@@ -0,0 +1,7 @@
+#include <stdlib.h>
+
+__attribute__ ((noreturn)) void
+_exit(int st) { while (1); }
+
+int
+atexit(void (*f)(void)) { return 0; }
diff --git a/code/fe310/gloss/sys_faccessat.c b/code/fe310/gloss/sys_faccessat.c
new file mode 100644
index 0000000..873d52c
--- /dev/null
+++ b/code/fe310/gloss/sys_faccessat.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_faccessat(int dirfd, const char *file, int mode, int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_fork.c b/code/fe310/gloss/sys_fork.c
new file mode 100644
index 0000000..64e6756
--- /dev/null
+++ b/code/fe310/gloss/sys_fork.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_fork()
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_fstat.c b/code/fe310/gloss/sys_fstat.c
new file mode 100644
index 0000000..fedc289
--- /dev/null
+++ b/code/fe310/gloss/sys_fstat.c
@@ -0,0 +1,9 @@
+#include <errno.h>
+#include <sys/stat.h>
+
+int
+_fstat(int file, struct stat *st)
+{
+ errno = -ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_fstatat.c b/code/fe310/gloss/sys_fstatat.c
new file mode 100644
index 0000000..f2f43bd
--- /dev/null
+++ b/code/fe310/gloss/sys_fstatat.c
@@ -0,0 +1,9 @@
+#include <errno.h>
+#include <sys/stat.h>
+
+int
+_fstatat(int dirfd, const char *file, struct stat *st, int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_ftime.c b/code/fe310/gloss/sys_ftime.c
new file mode 100644
index 0000000..65c1563
--- /dev/null
+++ b/code/fe310/gloss/sys_ftime.c
@@ -0,0 +1,9 @@
+#include <errno.h>
+#include <sys/timeb.h>
+
+int
+_ftime(struct timeb *tp)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_getcwd.c b/code/fe310/gloss/sys_getcwd.c
new file mode 100644
index 0000000..82e8404
--- /dev/null
+++ b/code/fe310/gloss/sys_getcwd.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+char *
+_getcwd(char *buf, size_t size)
+{
+ errno = -ENOSYS;
+ return NULL;
+}
diff --git a/code/fe310/gloss/sys_getpid.c b/code/fe310/gloss/sys_getpid.c
new file mode 100644
index 0000000..589ad11
--- /dev/null
+++ b/code/fe310/gloss/sys_getpid.c
@@ -0,0 +1,7 @@
+#include <errno.h>
+
+int
+_getpid()
+{
+ return 1;
+}
diff --git a/code/fe310/gloss/sys_isatty.c b/code/fe310/gloss/sys_isatty.c
new file mode 100644
index 0000000..70aec43
--- /dev/null
+++ b/code/fe310/gloss/sys_isatty.c
@@ -0,0 +1,7 @@
+#include <unistd.h>
+
+int
+_isatty(int fd)
+{
+ return ((fd == STDIN_FILENO) || (fd == STDOUT_FILENO) || (fd == STDERR_FILENO));
+}
diff --git a/code/fe310/gloss/sys_kill.c b/code/fe310/gloss/sys_kill.c
new file mode 100644
index 0000000..9003f26
--- /dev/null
+++ b/code/fe310/gloss/sys_kill.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_kill(int pid, int sig)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_link.c b/code/fe310/gloss/sys_link.c
new file mode 100644
index 0000000..40d5912
--- /dev/null
+++ b/code/fe310/gloss/sys_link.c
@@ -0,0 +1,7 @@
+#include <errno.h>
+
+int _link(const char *old_name, const char *new_name)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_lseek.c b/code/fe310/gloss/sys_lseek.c
new file mode 100644
index 0000000..d28a781
--- /dev/null
+++ b/code/fe310/gloss/sys_lseek.c
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <errno.h>
+
+off_t
+_lseek(int file, off_t ptr, int dir)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_lstat.c b/code/fe310/gloss/sys_lstat.c
new file mode 100644
index 0000000..97a4585
--- /dev/null
+++ b/code/fe310/gloss/sys_lstat.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+#include <sys/stat.h>
+
+int _lstat(const char *file, struct stat *st)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_open.c b/code/fe310/gloss/sys_open.c
new file mode 100644
index 0000000..a59f627
--- /dev/null
+++ b/code/fe310/gloss/sys_open.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_open(const char *name, int flags, int mode)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_openat.c b/code/fe310/gloss/sys_openat.c
new file mode 100644
index 0000000..206de3b
--- /dev/null
+++ b/code/fe310/gloss/sys_openat.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_openat(int dirfd, const char *name, int flags, int mode)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_read.c b/code/fe310/gloss/sys_read.c
new file mode 100644
index 0000000..1857fc6
--- /dev/null
+++ b/code/fe310/gloss/sys_read.c
@@ -0,0 +1,23 @@
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "platform.h"
+
+/* Read from a file. */
+ssize_t
+_read(int fd, void *ptr, size_t len)
+{
+ if (fd != STDIN_FILENO) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ char *current = ptr;
+ for (size_t i = 0; i < len; i++) {
+ volatile uint32_t r;
+ while ((r = UART0_REG(UART_REG_RXFIFO)) & 0x80000000);
+ current[i] = r & 0xFF;
+ }
+ return len;
+}
diff --git a/code/fe310/gloss/sys_sbrk.c b/code/fe310/gloss/sys_sbrk.c
new file mode 100644
index 0000000..cc01c8f
--- /dev/null
+++ b/code/fe310/gloss/sys_sbrk.c
@@ -0,0 +1,39 @@
+#include <sys/types.h>
+
+/* brk is handled entirely within the C library. This limits METAL programs that
+ * use the C library to be disallowed from dynamically allocating memory
+ * without talking to the C library, but that sounds like a sane way to go
+ * about it. Note that there is no error checking anywhere in this file, users
+ * will simply get the relevant error when actually trying to use the memory
+ * that's been allocated. */
+extern char metal_segment_heap_target_start;
+extern char metal_segment_heap_target_end;
+static char *brk = &metal_segment_heap_target_start;
+
+int
+_brk(void *addr)
+{
+ brk = addr;
+ return 0;
+}
+
+char *
+_sbrk(ptrdiff_t incr)
+{
+ char *old = brk;
+
+ /* If __heap_size == 0, we can't allocate memory on the heap */
+ if(&metal_segment_heap_target_start == &metal_segment_heap_target_end) {
+ return (void *)-1;
+ }
+
+ /* Don't move the break past the end of the heap */
+ if ((brk + incr) < &metal_segment_heap_target_end) {
+ brk += incr;
+ } else {
+ brk = &metal_segment_heap_target_end;
+ return (void *)-1;
+ }
+
+ return old;
+}
diff --git a/code/fe310/gloss/sys_stat.c b/code/fe310/gloss/sys_stat.c
new file mode 100644
index 0000000..3c2e419
--- /dev/null
+++ b/code/fe310/gloss/sys_stat.c
@@ -0,0 +1,9 @@
+#include <errno.h>
+#include <sys/stat.h>
+
+int
+_stat(const char *file, struct stat *st)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_sysconf.c b/code/fe310/gloss/sys_sysconf.c
new file mode 100644
index 0000000..452a252
--- /dev/null
+++ b/code/fe310/gloss/sys_sysconf.c
@@ -0,0 +1,16 @@
+#include <unistd.h>
+#include <time.h>
+
+/* Get configurable system variables. */
+
+long
+_sysconf(int name)
+{
+ switch (name)
+ {
+ case _SC_CLK_TCK:
+ return CLOCKS_PER_SEC;
+ }
+
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_unlink.c b/code/fe310/gloss/sys_unlink.c
new file mode 100644
index 0000000..b369d20
--- /dev/null
+++ b/code/fe310/gloss/sys_unlink.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+int
+_unlink(const char *name)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_utime.c b/code/fe310/gloss/sys_utime.c
new file mode 100644
index 0000000..33d557a
--- /dev/null
+++ b/code/fe310/gloss/sys_utime.c
@@ -0,0 +1,9 @@
+#include <errno.h>
+struct utimbuf;
+
+int
+_utime(const char *path, const struct utimbuf *times)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_wait.c b/code/fe310/gloss/sys_wait.c
new file mode 100644
index 0000000..9d459f1
--- /dev/null
+++ b/code/fe310/gloss/sys_wait.c
@@ -0,0 +1,7 @@
+#include <errno.h>
+
+int _wait(int *status)
+{
+ errno = ENOSYS;
+ return -1;
+}
diff --git a/code/fe310/gloss/sys_write.c b/code/fe310/gloss/sys_write.c
new file mode 100644
index 0000000..2095940
--- /dev/null
+++ b/code/fe310/gloss/sys_write.c
@@ -0,0 +1,22 @@
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "platform.h"
+
+/* Write to a file. */
+ssize_t
+_write(int fd, const void *ptr, size_t len)
+{
+ if ((fd != STDOUT_FILENO) && (fd != STDERR_FILENO)) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ const char *current = ptr;
+ for (size_t i = 0; i < len; i++) {
+ while (UART0_REG(UART_REG_TXFIFO) & 0x80000000);
+ UART0_REG(UART_REG_TXFIFO) = current[i];
+ }
+ return len;
+}