diff options
Diffstat (limited to 'lib/semihosting.c')
| -rw-r--r-- | lib/semihosting.c | 159 |
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; +} |
