/* 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 * Copyright 2014 Broadcom Corporation * Copyright (C) 2026 Bradley Morgan */ #include #include #include #include #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; }