[PATCH] RISC-V: Add semihosting support

Craig Blackmore <[email protected]>
Newsgroups gmane.comp.lib.newlib
Message-ID <[email protected]>
Add a new library called libsemihost which implements syscalls using
semihosting syscalls. This is intended to be used instead of libgloss
when semihosting support is required. A new semihost.specs file has
been added which will link with -lsemihost instead -lgloss.

The library is implemented following the RISC-V Semihosting
specification available at:

  https://github.com/riscv/riscv-semihosting-spec

Tested on the gcc testsuite on rv32imc and rv64imc using spike and
openocd.

---
 libgloss/riscv/Makefile.in                | 66 +++++++++++++++++-
 libgloss/riscv/machine/syscall.h          | 25 +++++++
 libgloss/riscv/semihost-sys_close.c       | 24 +++++++
 libgloss/riscv/semihost-sys_exit.c        | 19 ++++++
 libgloss/riscv/semihost-sys_fdtable.c     | 81 +++++++++++++++++++++++
 libgloss/riscv/semihost-sys_fstat.c       | 15 +++++
 libgloss/riscv/semihost-sys_ftime.c       | 12 ++++
 libgloss/riscv/semihost-sys_isatty.c      | 17 +++++
 libgloss/riscv/semihost-sys_link.c        |  5 ++
 libgloss/riscv/semihost-sys_lseek.c       | 66 ++++++++++++++++++
 libgloss/riscv/semihost-sys_open.c        | 58 ++++++++++++++++
 libgloss/riscv/semihost-sys_read.c        | 28 ++++++++
 libgloss/riscv/semihost-sys_sbrk.c        | 22 ++++++
 libgloss/riscv/semihost-sys_stat.c        | 32 +++++++++
 libgloss/riscv/semihost-sys_stat_common.c | 32 +++++++++
 libgloss/riscv/semihost-sys_unlink.c      | 11 +++
 libgloss/riscv/semihost-sys_write.c       | 28 ++++++++
 libgloss/riscv/semihost.specs             | 10 +++
 libgloss/riscv/semihost_fdtable.h         | 17 +++++
 libgloss/riscv/semihost_stat.h            | 10 +++
 libgloss/riscv/semihost_syscall.h         | 43 ++++++++++++
 21 files changed, 620 insertions(+), 1 deletion(-)
 create mode 100644 libgloss/riscv/semihost-sys_close.c
 create mode 100644 libgloss/riscv/semihost-sys_exit.c
 create mode 100644 libgloss/riscv/semihost-sys_fdtable.c
 create mode 100644 libgloss/riscv/semihost-sys_fstat.c
 create mode 100644 libgloss/riscv/semihost-sys_ftime.c
 create mode 100644 libgloss/riscv/semihost-sys_isatty.c
 create mode 100644 libgloss/riscv/semihost-sys_link.c
 create mode 100644 libgloss/riscv/semihost-sys_lseek.c
 create mode 100644 libgloss/riscv/semihost-sys_open.c
 create mode 100644 libgloss/riscv/semihost-sys_read.c
 create mode 100644 libgloss/riscv/semihost-sys_sbrk.c
 create mode 100644 libgloss/riscv/semihost-sys_stat.c
 create mode 100644 libgloss/riscv/semihost-sys_stat_common.c
 create mode 100644 libgloss/riscv/semihost-sys_unlink.c
 create mode 100644 libgloss/riscv/semihost-sys_write.c
 create mode 100644 libgloss/riscv/semihost.specs
 create mode 100644 libgloss/riscv/semihost_fdtable.h
 create mode 100644 libgloss/riscv/semihost_stat.h
 create mode 100644 libgloss/riscv/semihost_syscall.h

diff --git a/libgloss/riscv/Makefile.in b/libgloss/riscv/Makefile.in
index 579dd9554..185b6e6d2 100644
--- a/libgloss/riscv/Makefile.in
+++ b/libgloss/riscv/Makefile.in
@@ -40,8 +40,38 @@ gloss_srcs = \
 	sys_wait.c \
 	sys_write.c 
 
+# libsemihost reuses some of the libgloss minimal implementations
+
+semihost_srcs = \
+	nanosleep.c \
+	sys_chdir.c \
+	sys_chmod.c \
+	sys_chown.c \
+	sys_execve.c \
+	sys_fork.c \
+	sys_getcwd.c \
+	sys_getpid.c \
+	sys_kill.c \
+	sys_utime.c \
+	sys_wait.c \
+	semihost-sys_close.c \
+	semihost-sys_exit.c \
+	semihost-sys_fdtable.c \
+	semihost-sys_fstat.c \
+	semihost-sys_ftime.c \
+	semihost-sys_isatty.c \
+	semihost-sys_link.c \
+	semihost-sys_lseek.c \
+	semihost-sys_open.c \
+	semihost-sys_read.c \
+	semihost-sys_sbrk.c \
+	semihost-sys_stat.c \
+	semihost-sys_stat_common.c \
+	semihost-sys_unlink.c \
+	semihost-sys_write.c
+
 gloss_specs = \
-	nano.specs sim.specs
+	nano.specs sim.specs semihost.specs
 
 # Extra files
 
@@ -134,6 +164,17 @@ sim_objs += $(sim_c_objs)
 deps += $(sim_c_deps)
 junk += $(sim_c_deps) $(sim_c_objs)
 
+semihost_c_srcs = $(filter %.c, $(semihost_srcs))
+semihost_c_objs = $(patsubst %.c, semihost-%.o, $(notdir $(semihost_c_srcs)))
+semihost_c_deps = $(patsubst %.c, semihost-%.d, $(notdir $(semihost_c_srcs)))
+
+$(semihost_c_objs): semihost-%.o : %.c
+	$(COMPILE) -c -o $@ $<
+
+semihost_objs += $(semihost_c_objs)
+deps += $(semihost_c_deps)
+junk += $(semihost_c_deps) $(semihost_c_objs)
+
 #-------------------------------------------------------------------------
 # Build Object Files from Assembly Source
 #-------------------------------------------------------------------------
@@ -159,6 +200,16 @@ sim_objs += $(sim_asm_objs)
 deps += $(sim_asm_deps)
 junk += $(sim_asm_deps) $(sim_asm_objs)
 
+semihost_asm_objs = $(patsubst %.S, semihost-%.o, $(notdir $(gloss_asm_srcs)))
+semihost_asm_deps = $(patsubst %.S, semihost-%.d, $(notdir $(gloss_asm_srcs)))
+
+$(semihost_asm_objs) : semihost-%.o : %.S
+	$(COMPILE) -c -DUSING_SEMIHOST_SPECS -o $@ $<
+
+semihost_objs += $(semihost_asm_objs)
+deps += $(semihost_asm_deps)
+junk += $(semihost_asm_deps) $(semihost_asm_objs)
+
 #-------------------------------------------------------------------------
 # Build libgloss.a
 #-------------------------------------------------------------------------
@@ -187,6 +238,19 @@ junk += $(sim_lib)
 
 install_libs += $(sim_lib)
 
+#-------------------------------------------------------------------------
+# Build libsemihost.a
+#-------------------------------------------------------------------------
+
+semihost_lib  = libsemihost.a
+$(semihost_lib) : $(semihost_objs)
+	$(AR) rcv $@ $^
+	$(RANLIB) $@
+
+junk += $(semihost_lib)
+
+install_libs += $(semihost_lib)
+
 #-------------------------------------------------------------------------
 # Build crt0.o
 #-------------------------------------------------------------------------
diff --git a/libgloss/riscv/machine/syscall.h b/libgloss/riscv/machine/syscall.h
index 5cd15b848..88b9fdfda 100644
--- a/libgloss/riscv/machine/syscall.h
+++ b/libgloss/riscv/machine/syscall.h
@@ -54,4 +54,29 @@
 #define SYS_time 1062
 #define SYS_getmainvars 2011
 
+/* Semihosting operations.  */
+#define SEMIHOST_clock 0x10
+#define SEMIHOST_close 0x02
+#define SEMIHOST_elapsed 0x30
+#define SEMIHOST_errno 0x13
+#define SEMIHOST_exit 0x18
+#define SEMIHOST_exit_extended 0x20
+#define SEMIHOST_flen 0x0C
+#define SEMIHOST_get_cmdline 0x15
+#define SEMIHOST_heapinfo 0x16
+#define SEMIHOST_iserror 0x08
+#define SEMIHOST_istty 0x09
+#define SEMIHOST_open 0x01
+#define SEMIHOST_read 0x06
+#define SEMIHOST_readc 0x07
+#define SEMIHOST_remove 0x0E
+#define SEMIHOST_rename 0x0F
+#define SEMIHOST_seek 0x0A
+#define SEMIHOST_system 0x12
+#define SEMIHOST_tickfreq 0x31
+#define SEMIHOST_time 0x11
+#define SEMIHOST_tmpnam 0x0D
+#define SEMIHOST_write 0x05
+#define SEMIHOST_writec 0x03
+#define SEMIHOST_write0 0x04
 #endif
diff --git a/libgloss/riscv/semihost-sys_close.c b/libgloss/riscv/semihost-sys_close.c
new file mode 100644
index 000000000..214052185
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_close.c
@@ -0,0 +1,24 @@
+#include <machine/syscall.h>
+#include "semihost_syscall.h"
+#include "semihost_fdtable.h"
+
+/* Close a file.  */
+int
+_close (int file)
+{
+  long res;
+  struct fdentry *fd =__get_fdentry (file);
+  long data_block[1];
+
+  if (fd == NULL)
+    return -1;
+
+  data_block[0] = fd->handle;
+  res = syscall_errno (SEMIHOST_close, data_block);
+
+  if (res != 0)
+    return res;
+
+  __remove_fdentry (file);
+  return 0;
+}
diff --git a/libgloss/riscv/semihost-sys_exit.c b/libgloss/riscv/semihost-sys_exit.c
new file mode 100644
index 000000000..774ec847e
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_exit.c
@@ -0,0 +1,19 @@
+#include <machine/syscall.h>
+#include "semihost_syscall.h"
+
+#define ADP_Stopped_ApplicationExit 0x20026
+
+/* Exit a program without cleaning up files.  */
+void
+_exit (int exit_status)
+{
+#if __riscv_xlen == 32
+  syscall_errno (SEMIHOST_exit, (long *) ADP_Stopped_ApplicationExit);
+#else
+  /* The semihosting exit operation only allows 64-bit targets to report the
+     exit code.  */
+  long data_block[] = {ADP_Stopped_ApplicationExit, exit_status};
+  syscall_errno (SEMIHOST_exit, data_block);
+#endif
+  while (1);
+}
diff --git a/libgloss/riscv/semihost-sys_fdtable.c b/libgloss/riscv/semihost-sys_fdtable.c
new file mode 100644
index 000000000..c9454224e
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_fdtable.c
@@ -0,0 +1,81 @@
+#include "semihost_fdtable.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifndef RISCV_MAX_OPEN_FILES
+#define RISCV_MAX_OPEN_FILES 16
+#endif
+
+extern int errno;
+extern int _open (const char *, int, ...);
+
+/* fdtable keeps track of the position of each file and is used to map stdin,
+   stdout and stderr to STDIN_FILENO, STDOUT_FILENO and STDERR_FILENO.  */
+
+static struct fdentry fdtable[RISCV_MAX_OPEN_FILES];
+
+/* Initialize fdtable.  A handle of -1 denotes an empty entry.  */
+
+void __attribute__ ((constructor))
+init_semihosting ()
+{
+  int handle;
+
+  for (int i=0; i<RISCV_MAX_OPEN_FILES; i++)
+    fdtable[i].handle = -1;
+
+  /* Set up std streams.  */
+  /* stdin.  */
+  handle = _open (":tt", O_RDONLY);
+  fdtable[STDIN_FILENO].handle = handle;
+  fdtable[STDIN_FILENO].pos = 0;
+
+  /* stdout.  */
+  handle = _open (":tt", O_WRONLY|O_CREAT|O_TRUNC);
+  fdtable[STDOUT_FILENO].handle = handle;
+  fdtable[STDOUT_FILENO].pos = 0;
+
+  /* stderr.  */
+  handle = _open (":tt", O_WRONLY|O_CREAT|O_APPEND);
+  fdtable[STDERR_FILENO].handle = handle;
+  fdtable[STDERR_FILENO].pos = 0;
+}
+
+/* Add entry to fdtable.  */
+
+int
+__add_fdentry (int handle)
+{
+  for (int i=0; i<RISCV_MAX_OPEN_FILES; i++)
+    if (fdtable[i].handle == -1)
+      {
+	fdtable[i].handle = handle;
+	fdtable[i].pos = 0;
+	return i;
+      }
+  /* Too many open files.  */
+  errno = ENFILE;
+  return -1;
+}
+
+/* Return the fdentry for file or NULL if not found.  */
+
+struct fdentry *
+__get_fdentry (int file)
+{
+  if (file<0 || file>RISCV_MAX_OPEN_FILES || fdtable[file].handle == -1)
+    {
+      errno = EBADF;
+      return NULL;
+    }
+  return &fdtable[file];
+}
+
+/* Remove entry from fdtable.  */
+
+void
+__remove_fdentry (int file)
+{
+  fdtable[file].handle = -1;
+}
diff --git a/libgloss/riscv/semihost-sys_fstat.c b/libgloss/riscv/semihost-sys_fstat.c
new file mode 100644
index 000000000..0c5a8d857
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_fstat.c
@@ -0,0 +1,15 @@
+#include <string.h>
+#include <sys/stat.h>
+#include "semihost_stat.h"
+
+/* Status of an open file.  The sys/stat.h header file required is
+   distributed in the include subdirectory for this C library.  */
+
+int
+_fstat (int file, struct stat *st)
+{
+  /* Initialize st as not all fields will be set.  */
+  memset (st, 0, sizeof (*st));
+
+  return __stat_common (file, st);
+}
diff --git a/libgloss/riscv/semihost-sys_ftime.c b/libgloss/riscv/semihost-sys_ftime.c
new file mode 100644
index 000000000..c8d9a9133
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_ftime.c
@@ -0,0 +1,12 @@
+#include <machine/syscall.h>
+#include <sys/timeb.h>
+#include "semihost_syscall.h"
+
+/* Get the current time.  */
+int
+_ftime (struct timeb *tp)
+{
+  tp->time = syscall_errno (SEMIHOST_time, 0);
+  tp->millitm = 0;
+  return 0;
+}
diff --git a/libgloss/riscv/semihost-sys_isatty.c b/libgloss/riscv/semihost-sys_isatty.c
new file mode 100644
index 000000000..ed1376600
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_isatty.c
@@ -0,0 +1,17 @@
+#include <machine/syscall.h>
+#include <sys/stat.h>
+#include "semihost_syscall.h"
+#include "semihost_fdtable.h"
+
+int
+_isatty (int file)
+{
+  struct fdentry *fd =__get_fdentry (file);
+  long data_block[1];
+
+  if (fd == NULL)
+    return -1;
+
+  data_block[0] = fd->handle;
+  return syscall_errno (SEMIHOST_istty, data_block);
+}
diff --git a/libgloss/riscv/semihost-sys_link.c b/libgloss/riscv/semihost-sys_link.c
new file mode 100644
index 000000000..d65852e7e
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_link.c
@@ -0,0 +1,5 @@
+/* Establish a new name for an existing file.  */
+int _link (const char *old_name, const char *new_name)
+{
+  return -1;
+}
diff --git a/libgloss/riscv/semihost-sys_lseek.c b/libgloss/riscv/semihost-sys_lseek.c
new file mode 100644
index 000000000..121769243
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_lseek.c
@@ -0,0 +1,66 @@
+#include <machine/syscall.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "semihost_syscall.h"
+#include "semihost_fdtable.h"
+
+extern int errno;
+
+/* Set position in a file.  */
+off_t
+_lseek (int file, off_t offset, int dir)
+{
+  long data_block[2];
+  long flen;
+  long res;
+  struct fdentry *fd;
+  off_t abs_pos;
+
+  fd =__get_fdentry (file);
+  if (fd == NULL)
+    {
+      errno = EBADF;
+      return -1;
+    }
+
+  if (dir == SEEK_CUR && offset == 0)
+    return fd->pos;
+
+  data_block[0] = fd->handle;
+
+  switch (dir)
+    {
+      case SEEK_SET:
+	abs_pos = offset;
+	break;
+      case SEEK_CUR:
+	abs_pos = fd->pos + offset;
+	break;
+      case SEEK_END:
+	data_block[1] = 0;
+	flen = syscall_errno (SEMIHOST_flen, data_block);
+	if (flen == -1)
+	  return -1;
+	abs_pos = flen + offset;
+	break;
+      default:
+	errno = EINVAL;
+	return -1;
+    }
+
+  if (abs_pos < 0)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  data_block[1] = abs_pos;
+  res = syscall_errno (SEMIHOST_seek, data_block);
+  if (res == 0)
+    {
+      fd->pos = abs_pos;
+      return abs_pos;
+    }
+  return res;
+}
diff --git a/libgloss/riscv/semihost-sys_open.c b/libgloss/riscv/semihost-sys_open.c
new file mode 100644
index 000000000..9d456a51e
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_open.c
@@ -0,0 +1,58 @@
+#include <machine/syscall.h>
+#include "semihost_syscall.h"
+#include "semihost_fdtable.h"
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+extern int errno;
+
+#define SEMIHOST_MODE_R 0
+#define SEMIHOST_MODE_RPLUS 2
+#define SEMIHOST_MODE_W 4
+#define SEMIHOST_MODE_WPLUS 6
+#define SEMIHOST_MODE_A 8
+#define SEMIHOST_MODE_APLUS 10
+
+/* Open a file.  */
+int
+_open (const char *name, int flags, ...)
+{
+  int fh;
+  int mode;
+  long data_block[3];
+
+  /* Work out mode from flags.  */
+  if ((flags & (O_RDONLY | O_WRONLY | O_RDWR)) == O_RDONLY)
+    mode = SEMIHOST_MODE_R;
+  else if ((flags & (O_WRONLY | O_CREAT | O_TRUNC))
+	   == (O_WRONLY | O_CREAT | O_TRUNC))
+    mode = SEMIHOST_MODE_W;
+  else if ((flags & (O_WRONLY | O_CREAT | O_APPEND))
+	   == (O_WRONLY | O_CREAT | O_APPEND))
+    mode = SEMIHOST_MODE_A;
+  else if ((flags & (O_RDWR | O_CREAT | O_TRUNC))
+	   == (O_RDWR | O_CREAT | O_TRUNC))
+    mode = SEMIHOST_MODE_WPLUS;
+  else if ((flags & (O_RDWR | O_CREAT | O_APPEND))
+	   == (O_RDWR | O_CREAT | O_APPEND))
+    mode = SEMIHOST_MODE_APLUS;
+  else if (flags & O_RDWR)
+    mode = SEMIHOST_MODE_RPLUS;
+  else
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  data_block[0] = (long) name;
+  data_block[1] = mode;
+  data_block[2] = strlen (name);
+  fh = syscall_errno (SEMIHOST_open, data_block);
+  /* Failed to open file.  */
+  if (fh == -1)
+    return -1;
+
+  /* Register the file in the fdtable.  */
+  return __add_fdentry (fh);
+}
diff --git a/libgloss/riscv/semihost-sys_read.c b/libgloss/riscv/semihost-sys_read.c
new file mode 100644
index 000000000..b76193301
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_read.c
@@ -0,0 +1,28 @@
+#include <machine/syscall.h>
+#include <errno.h>
+#include <sys/types.h>
+#include "semihost_syscall.h"
+#include "semihost_fdtable.h"
+
+/* Read from a file.  */
+ssize_t _read (int file, void *ptr, size_t len)
+{
+  struct fdentry *fd =__get_fdentry (file);
+  long data_block[3];
+  long res;
+
+  if (fd == NULL)
+    return -1;
+
+  data_block[0] = fd->handle;
+  data_block[1] = (long) ptr;
+  data_block[2] = len;
+  res = syscall_errno (SEMIHOST_read, data_block);
+  if (res >= 0)
+    {
+      ssize_t bytes_read = len - res;
+      fd->pos += bytes_read;
+      return bytes_read;
+    }
+  return -1;
+}
diff --git a/libgloss/riscv/semihost-sys_sbrk.c b/libgloss/riscv/semihost-sys_sbrk.c
new file mode 100644
index 000000000..0d7cf3754
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_sbrk.c
@@ -0,0 +1,22 @@
+/* Semihosting requires that sbrk be implemented without a syscall.  */
+extern char _end[];               /* _end is set in the linker command file.  */
+char *heap_ptr;
+
+/*
+ * sbrk -- changes heap size size.  Get nbytes more
+ *         RAM.  We just increment a pointer in what's
+ *         left of memory on the board.
+ */
+char *
+_sbrk (nbytes)
+     int nbytes;
+{
+  char *base;
+
+  if (!heap_ptr)
+    heap_ptr = (char *)&_end;
+  base = heap_ptr;
+  heap_ptr += nbytes;
+
+  return base;
+}
diff --git a/libgloss/riscv/semihost-sys_stat.c b/libgloss/riscv/semihost-sys_stat.c
new file mode 100644
index 000000000..09039b6e9
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_stat.c
@@ -0,0 +1,32 @@
+#include <machine/syscall.h>
+#include <string.h>
+#include <fcntl.h>
+#include "semihost_stat.h"
+
+/* Status of a file (by name).  */
+
+int
+_stat (const char *name, struct stat *st)
+{
+  int file;
+  int res;
+
+  /* Initialize st as not all fields will be set.  */
+  memset (st, 0, sizeof (*st));
+
+  /* Try to open file.  */
+  file = _open (name, O_RDONLY);
+  if (file == -1)
+    /* _open should have already set errno.  */
+    return -1;
+
+  /* File opened successfully, infer read permission for owner and assume it is
+     a regular file.  */
+  st->st_mode |= S_IREAD | S_IFREG;
+
+  /* Fill in more info.  */
+  res = __stat_common (file, st);
+
+  _close (file);
+  return res;
+}
diff --git a/libgloss/riscv/semihost-sys_stat_common.c b/libgloss/riscv/semihost-sys_stat_common.c
new file mode 100644
index 000000000..aa7e716c2
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_stat_common.c
@@ -0,0 +1,32 @@
+#include <machine/syscall.h>
+#include "semihost_syscall.h"
+#include <sys/stat.h>
+#include "semihost_fdtable.h"
+
+/* Used by _fstat and _stat to fill in some common details.  */
+
+int
+__stat_common (int file, struct stat *st)
+{
+  int flen;
+  struct fdentry *fd =__get_fdentry (file);
+  long data_block[1];
+
+  if (fd == NULL)
+    return -1;
+
+  data_block[0] = fd->handle;
+
+  /* Assume character device and default block size of 4096.  */
+  st->st_mode |= S_IFCHR;
+  st->st_blksize = 4096;
+
+  /* Attempt to get length of file.  */
+  flen = syscall_errno (SEMIHOST_flen, data_block);
+  if (flen == -1)
+    return -1;
+
+  st->st_size = flen;
+
+  return 0;
+}
diff --git a/libgloss/riscv/semihost-sys_unlink.c b/libgloss/riscv/semihost-sys_unlink.c
new file mode 100644
index 000000000..dd0d2e9b1
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_unlink.c
@@ -0,0 +1,11 @@
+#include <machine/syscall.h>
+#include "semihost_syscall.h"
+#include <string.h>
+
+/* Remove a file's directory entry.  */
+int
+_unlink (const char *name)
+{
+  long data_block[] = {(long) name, strlen (name)};
+  return syscall_errno (SEMIHOST_remove, data_block);
+}
diff --git a/libgloss/riscv/semihost-sys_write.c b/libgloss/riscv/semihost-sys_write.c
new file mode 100644
index 000000000..0a249c2b6
--- /dev/null
+++ b/libgloss/riscv/semihost-sys_write.c
@@ -0,0 +1,28 @@
+#include <machine/syscall.h>
+#include <sys/types.h>
+#include "semihost_syscall.h"
+#include "semihost_fdtable.h"
+
+/* Write to a file.  */
+ssize_t
+_write (int file, const void *ptr, size_t len)
+{
+  struct fdentry *fd =__get_fdentry (file);
+  long data_block[3];
+  long res;
+
+  if (fd == NULL)
+    return -1;
+
+  data_block[0] = fd->handle;
+  data_block[1] = (long) ptr;
+  data_block[2] = len;
+  res = syscall_errno (SEMIHOST_write, data_block);
+  if (res >= 0)
+    {
+      ssize_t bytes_written = len - res;
+      fd->pos += bytes_written;
+      return bytes_written;
+    }
+  return -1;
+}
diff --git a/libgloss/riscv/semihost.specs b/libgloss/riscv/semihost.specs
new file mode 100644
index 000000000..1c86c67e4
--- /dev/null
+++ b/libgloss/riscv/semihost.specs
@@ -0,0 +1,10 @@
+# Spec file for semihosting syscalls.
+
+%rename lib	semihost_lib
+%rename link	semihost_link
+
+*lib:
+--start-group -lc -lsemihost --end-group
+
+*link:
+%(semihost_link) %:replace-outfile(-lgloss -lsemihost)
diff --git a/libgloss/riscv/semihost_fdtable.h b/libgloss/riscv/semihost_fdtable.h
new file mode 100644
index 000000000..0fa62dd2d
--- /dev/null
+++ b/libgloss/riscv/semihost_fdtable.h
@@ -0,0 +1,17 @@
+#include <sys/types.h>
+
+#ifndef RISCV_SEMIHOST_FDTABLE_H
+#define RISCV_SEMIHOST_FDTABLE_H
+
+extern void __attribute__ ((constructor)) init_semihosting ();
+extern int __add_fdentry (int);
+extern struct fdentry * __get_fdentry (int);
+extern void __remove_fdentry (int);
+
+struct fdentry
+{
+  int handle;
+  off_t pos;
+};
+
+#endif
diff --git a/libgloss/riscv/semihost_stat.h b/libgloss/riscv/semihost_stat.h
new file mode 100644
index 000000000..2030c7ab2
--- /dev/null
+++ b/libgloss/riscv/semihost_stat.h
@@ -0,0 +1,10 @@
+#include <sys/types.h>
+
+#ifndef RISCV_SEMIHOST_STAT_H
+#define RISCV_SEMIHOST_STAT_H
+
+extern int __stat_common (int, struct stat *);
+extern int _open (const char *, int, ...);
+extern int _close (int);
+
+#endif
diff --git a/libgloss/riscv/semihost_syscall.h b/libgloss/riscv/semihost_syscall.h
new file mode 100644
index 000000000..c8ae9ffaf
--- /dev/null
+++ b/libgloss/riscv/semihost_syscall.h
@@ -0,0 +1,43 @@
+#ifndef _INTERNAL_SYSCALL_H
+#define _INTERNAL_SYSCALL_H
+
+extern int errno;
+
+static inline long
+__semihost_syscall (long id, long *data_block)
+{
+  register long a0 asm ("a0") = id;
+  register long a1 asm ("a1") = (long) data_block;
+
+  /* RISC-V semihosting trap sequence.  Must be uncompressed and must not
+     cross page boundary.  */
+  asm volatile (
+    ".balign 16             \n"
+    ".option push           \n"
+    ".option norvc          \n"
+    "slli zero, zero, 0x1f  \n"
+    "ebreak                 \n"
+    "srai zero, zero, 0x7   \n"
+    ".option pop            \n"
+      : "+r"(a0) : "r"(a1) : "memory");
+
+  return a0;
+}
+
+static inline long
+__syscall_error ()
+{
+  errno = __semihost_syscall (SEMIHOST_errno, 0);
+  return -1;
+}
+
+static inline long
+syscall_errno (long id, long *data_block)
+{
+  long res = __semihost_syscall (id, data_block);
+  if (res < 0)
+    return __syscall_error ();
+  return res;
+}
+
+#endif
-- 
2.17.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.