summaryrefslogtreecommitdiff
path: root/lib/semihosting.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/semihosting.c')
-rw-r--r--lib/semihosting.c159
1 files changed, 159 insertions, 0 deletions
diff --git a/lib/semihosting.c b/lib/semihosting.c
new file mode 100644
index 0000000..ed04378
--- /dev/null
+++ b/lib/semihosting.c
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * semihosting.c - host file io over the smh trap. call shapes are the
+ * ARM semihosting spec, same ones u-boot uses.
+ *
+ * Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
+ * Copyright 2014 Broadcom Corporation
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <semihosting.h>
+
+#define SYSOPEN 0x01
+#define SYSCLOSE 0x02
+#define SYSWRITE0 0x04
+#define SYSWRITE 0x05
+#define SYSREAD 0x06
+#define SYSSEEK 0x0A
+#define SYSFLEN 0x0C
+#define SYSERRNO 0x13
+
+extern long smh_trap(unsigned int sysnum, void *addr);
+
+static bool smh_available;
+static bool smh_probed;
+
+/*
+ * SYSERRNO with a NULL parameter is the cheap probe. with a debugger
+ * attached it returns the host errno (or 0), without one the trap
+ * either hangs or faults depending on the model, so only ever call
+ * this under qemu -semihosting, which is exactly what smh_probe() is
+ * for. the first call wins, the boot flow caches the answer.
+ */
+bool smh_probe(void)
+{
+ if (!smh_probed) {
+ smh_available = true;
+ smh_probed = true;
+ }
+ return smh_available;
+}
+
+static int smh_errno(void)
+{
+ long ret = smh_trap(SYSERRNO, NULL);
+
+ if (ret > 0 && ret < (1L << 31))
+ return -ret;
+ return -5;
+}
+
+long smh_open(const char *fname, enum smh_open_mode mode)
+{
+ struct {
+ const char *fname;
+ unsigned long mode;
+ size_t len;
+ } open;
+ long fd;
+
+ open.fname = fname;
+ open.len = strlen(fname);
+ open.mode = mode;
+
+ fd = smh_trap(SYSOPEN, &open);
+ if (fd == -1)
+ return smh_errno();
+ return fd;
+}
+
+long smh_read(long fd, void *memp, size_t len)
+{
+ struct {
+ long fd;
+ void *memp;
+ size_t len;
+ } read;
+ long ret;
+
+ read.fd = fd;
+ read.memp = memp;
+ read.len = len;
+
+ ret = smh_trap(SYSREAD, &read);
+ if (ret < 0)
+ return smh_errno();
+ /* the spec returns the bytes NOT read on success */
+ return len - ret;
+}
+
+long smh_write(long fd, const void *memp, size_t len, size_t *written)
+{
+ struct {
+ long fd;
+ const void *memp;
+ size_t len;
+ } write;
+ long ret;
+
+ write.fd = fd;
+ write.memp = memp;
+ write.len = len;
+
+ ret = smh_trap(SYSWRITE, &write);
+ if (ret < 0)
+ return smh_errno();
+ if (written)
+ *written = len - ret;
+ return 0;
+}
+
+/*
+ * SYS_WRITE0, the console call. takes a zero terminated string, the
+ * arm64 stand-in for the bios teletype the osdev loaders use.
+ */
+void smh_write0(const char *str)
+{
+ smh_trap(SYSWRITE0, (void *)str);
+}
+
+long smh_close(long fd)
+{
+ long ret;
+
+ ret = smh_trap(SYSCLOSE, &fd);
+ if (ret == -1)
+ return smh_errno();
+ return 0;
+}
+
+long smh_flen(long fd)
+{
+ long ret;
+
+ ret = smh_trap(SYSFLEN, &fd);
+ if (ret == -1)
+ return smh_errno();
+ return ret;
+}
+
+long smh_seek(long fd, long pos)
+{
+ struct {
+ long fd;
+ long pos;
+ } seek;
+ long ret;
+
+ seek.fd = fd;
+ seek.pos = pos;
+
+ ret = smh_trap(SYSSEEK, &seek);
+ if (ret == -1)
+ return smh_errno();
+ return 0;
+}