/* * Copyright (c) 2022 Intel Corporation. * Copyright (c) 2025 Analog Devices, Inc. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "semihost_types.h" #define XTENSA_SEMIHOST_OPEN (-2) #define XTENSA_SEMIHOST_CLOSE (-3) #define XTENSA_SEMIHOST_READ (-4) #define XTENSA_SEMIHOST_WRITE (-5) #define XTENSA_SEMIHOST_LSEEK (-6) #define XTENSA_SEMIHOST_RENAME (-7) #define XTENSA_SEMIHOST_FSTAT (-10) enum semihost_open_flag { SEMIHOST_RDONLY = 0x0, SEMIHOST_WRONLY = 0x1, SEMIHOST_RDWR = 0x2, SEMIHOST_APPEND = 0x8, SEMIHOST_CREAT = 0x200, SEMIHOST_TRUNC = 0x400, SEMIHOST_EXCL = 0x800, }; uint32_t semihost_flags(enum semihost_open_mode mode) { uint32_t flags = 0; switch (mode) { case SEMIHOST_OPEN_R: case SEMIHOST_OPEN_RB: flags = SEMIHOST_RDONLY; break; case SEMIHOST_OPEN_R_PLUS: case SEMIHOST_OPEN_RB_PLUS: flags = SEMIHOST_RDWR; break; case SEMIHOST_OPEN_W: case SEMIHOST_OPEN_WB: flags = SEMIHOST_WRONLY | SEMIHOST_CREAT | SEMIHOST_TRUNC; break; case SEMIHOST_OPEN_W_PLUS: case SEMIHOST_OPEN_WB_PLUS: flags = SEMIHOST_RDWR | SEMIHOST_CREAT | SEMIHOST_TRUNC; break; case SEMIHOST_OPEN_A: case SEMIHOST_OPEN_AB: flags = SEMIHOST_WRONLY | SEMIHOST_CREAT | SEMIHOST_APPEND; break; case SEMIHOST_OPEN_A_PLUS: case SEMIHOST_OPEN_AB_PLUS: flags = SEMIHOST_RDWR | SEMIHOST_CREAT | SEMIHOST_APPEND; break; default: return -1; } return flags; } uint32_t semihost_mode(enum semihost_open_mode mode) { switch (mode) { case SEMIHOST_OPEN_W: case SEMIHOST_OPEN_WB: case SEMIHOST_OPEN_W_PLUS: case SEMIHOST_OPEN_WB_PLUS: case SEMIHOST_OPEN_A: case SEMIHOST_OPEN_AB: case SEMIHOST_OPEN_A_PLUS: case SEMIHOST_OPEN_AB_PLUS: /* Octal 0600, S_IRUSR | S_IWUSR */ return 0x180; default: return 0; } } static inline uintptr_t xtensa_semihost_call_4(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t call_id) { register uintptr_t a2 __asm__("%a2") = call_id; register uintptr_t a6 __asm__("%a6") = arg1; register uintptr_t a3 __asm__("%a3") = arg2; register uintptr_t a4 __asm__("%a4") = arg3; register uintptr_t a5 __asm__("%a5") = arg4; __asm__ volatile("break 1, 14\n\t" : "=r"(a2) : "r"(a2), "r"(a6), "r"(a3), "r"(a4), "r"(a5) : "memory"); return a2; } static inline uintptr_t xtensa_semihost_call_3(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t call_id) { register uintptr_t a2 __asm__("%a2") = call_id; register uintptr_t a6 __asm__("%a6") = arg1; register uintptr_t a3 __asm__("%a3") = arg2; register uintptr_t a4 __asm__("%a4") = arg3; __asm__ volatile("break 1, 14\n\t" : "=r"(a2) : "r"(a2), "r"(a6), "r"(a3), "r"(a4) : "memory"); return a2; } static inline uintptr_t xtensa_semihost_call_2(uintptr_t arg1, uintptr_t arg2, uintptr_t call_id) { register uintptr_t a2 __asm__("%a2") = call_id; register uintptr_t a6 __asm__("%a6") = arg1; register uintptr_t a3 __asm__("%a3") = arg2; __asm__ volatile("break 1, 14\n\t" : "=r"(a2) : "r"(a2), "r"(a6), "r"(a3) : "memory"); return a2; } static inline uintptr_t xtensa_semihost_call_1(uintptr_t arg1, uintptr_t call_id) { register uintptr_t a2 __asm__("%a2") = call_id; register uintptr_t a6 __asm__("%a6") = arg1; __asm__ volatile("break 1, 14\n\t" : "=r"(a2) : "r"(a2), "r"(a6) : "memory"); return a2; } long xtensa_semihost_open(struct semihost_open_args *args) { return xtensa_semihost_call_4((uintptr_t)args->path, semihost_flags(args->mode), semihost_mode(args->mode), args->path_len, XTENSA_SEMIHOST_OPEN); } long xtensa_semihost_close(long fd) { return xtensa_semihost_call_1(fd, XTENSA_SEMIHOST_CLOSE); } long xtensa_semihost_write(long fd, const char *buf, long len) { long ret; ret = (long)xtensa_semihost_call_3(fd, (uintptr_t)buf, len, XTENSA_SEMIHOST_WRITE); /* semihost_write assumes that data was written successfully if ret == 0. */ if (ret == len) { return 0; } return -1; } long xtensa_semihost_read(long fd, void *buf, long len) { long ret; ret = (long)xtensa_semihost_call_3(fd, (uintptr_t)buf, len, XTENSA_SEMIHOST_READ); /* semihost_read assumes that all bytes were read if ret == 0. * If ret == len, it means EOF was reached. */ if (ret == len) { return 0; } else if (ret <= 0) { return len; } else { return ret; } } long xtensa_semihost_read_char(long fd) { char c = 0; xtensa_semihost_call_3(fd, (uintptr_t)&c, 1, XTENSA_SEMIHOST_READ); return (long)c; } long xtensa_semihost_seek(struct semihost_seek_args *args) { long ret; ret = (long)xtensa_semihost_call_3(args->fd, args->offset, 0, XTENSA_SEMIHOST_LSEEK); if (ret == args->offset) { return 0; } return ret; } long xtensa_semihost_flen(long fd) { uint8_t buf[64] = {0}; long ret; ret = (long)xtensa_semihost_call_2(fd, (uintptr_t)buf, XTENSA_SEMIHOST_FSTAT); if (ret < 0) { return -1; } /* Struct stat is 64 bytes, bytes 28-35 correspond to st_size * field. 8-bytes cannot fit into long data type so return * only the lower 4 bytes. */ ret = *((long *)&buf[32]); return sys_be32_to_cpu(ret); } long semihost_exec(enum semihost_instr instr, void *args) { switch (instr) { case SEMIHOST_OPEN: return xtensa_semihost_open((struct semihost_open_args *)args); case SEMIHOST_CLOSE: return xtensa_semihost_close(((struct semihost_close_args *)args)->fd); case SEMIHOST_WRITEC: return xtensa_semihost_write(1, (char *)args, 1); case SEMIHOST_WRITE: return xtensa_semihost_write(((struct semihost_write_args *)args)->fd, ((struct semihost_write_args *)args)->buf, ((struct semihost_write_args *)args)->len); case SEMIHOST_READ: return xtensa_semihost_read(((struct semihost_read_args *)args)->fd, ((struct semihost_read_args *)args)->buf, ((struct semihost_read_args *)args)->len); case SEMIHOST_READC: return xtensa_semihost_read_char(((struct semihost_poll_in_args *)args)->zero); case SEMIHOST_SEEK: return xtensa_semihost_seek((struct semihost_seek_args *)args); case SEMIHOST_FLEN: return xtensa_semihost_flen(((struct semihost_flen_args *)args)->fd); default: return -1; } }