| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com> |
| * Copyright 2014 Broadcom Corporation |
| */ |
| |
| #include <common.h> |
| #include <log.h> |
| #include <semihosting.h> |
| |
| #define SYSOPEN 0x01 |
| #define SYSCLOSE 0x02 |
| #define SYSWRITEC 0x03 |
| #define SYSWRITE0 0x04 |
| #define SYSWRITE 0x05 |
| #define SYSREAD 0x06 |
| #define SYSREADC 0x07 |
| #define SYSISERROR 0x08 |
| #define SYSSEEK 0x0A |
| #define SYSFLEN 0x0C |
| #define SYSERRNO 0x13 |
| |
| /* |
| * Macro to force the compiler to *populate* memory (for an array or struct) |
| * before passing the pointer to an inline assembly call. |
| */ |
| #define USE_PTR(ptr) *(const char (*)[]) (ptr) |
| |
| #if defined(CONFIG_ARM64) |
| #define SMH_TRAP "hlt #0xf000" |
| #elif defined(CONFIG_CPU_V7M) |
| #define SMH_TRAP "bkpt #0xAB" |
| #elif defined(CONFIG_SYS_THUMB_BUILD) |
| #define SMH_TRAP "svc #0xab" |
| #else |
| #define SMH_TRAP "svc #0x123456" |
| #endif |
| |
| /* |
| * Call the handler |
| */ |
| static long smh_trap(unsigned int sysnum, void *addr) |
| { |
| register long result asm("r0"); |
| register void *_addr asm("r1") = addr; |
| |
| /* |
| * We need a memory clobber (aka compiler barrier) for two reasons: |
| * - The compiler needs to populate any data structures pointed to |
| * by "addr" *before* the trap instruction is called. |
| * - At least the SYSREAD function puts the result into memory pointed |
| * to by "addr", so the compiler must not use a cached version of |
| * the previous content, after the call has finished. |
| */ |
| asm volatile (SMH_TRAP |
| : "=r" (result) |
| : "0"(sysnum), "r"(USE_PTR(_addr)) |
| : "memory"); |
| |
| return result; |
| } |
| |
| #if CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK) |
| static bool _semihosting_enabled = true; |
| static bool try_semihosting = true; |
| |
| bool semihosting_enabled(void) |
| { |
| if (try_semihosting) { |
| smh_trap(SYSERRNO, NULL); |
| try_semihosting = false; |
| } |
| |
| return _semihosting_enabled; |
| } |
| |
| void disable_semihosting(void) |
| { |
| _semihosting_enabled = false; |
| } |
| #endif |
| |
| /** |
| * smh_errno() - Read the host's errno |
| * |
| * This gets the value of the host's errno and negates it. The host's errno may |
| * or may not be set, so only call this function if a previous semihosting call |
| * has failed. |
| * |
| * Return: a negative error value |
| */ |
| static int smh_errno(void) |
| { |
| long ret = smh_trap(SYSERRNO, NULL); |
| |
| if (ret > 0 && ret < INT_MAX) |
| return -ret; |
| return -EIO; |
| } |
| |
| long smh_open(const char *fname, enum smh_open_mode mode) |
| { |
| long fd; |
| struct smh_open_s { |
| const char *fname; |
| unsigned long mode; |
| size_t len; |
| } open; |
| |
| debug("%s: file \'%s\', mode \'%u\'\n", __func__, fname, mode); |
| |
| open.fname = fname; |
| open.len = strlen(fname); |
| open.mode = mode; |
| |
| /* Open the file on the host */ |
| fd = smh_trap(SYSOPEN, &open); |
| if (fd == -1) |
| return smh_errno(); |
| return fd; |
| } |
| |
| /** |
| * struct smg_rdwr_s - Arguments for read and write |
| * @fd: A file descriptor returned from smh_open() |
| * @memp: Pointer to a buffer of memory of at least @len bytes |
| * @len: The number of bytes to read or write |
| */ |
| struct smh_rdwr_s { |
| long fd; |
| void *memp; |
| size_t len; |
| }; |
| |
| long smh_read(long fd, void *memp, size_t len) |
| { |
| long ret; |
| struct smh_rdwr_s read; |
| |
| debug("%s: fd %ld, memp %p, len %zu\n", __func__, fd, memp, len); |
| |
| read.fd = fd; |
| read.memp = memp; |
| read.len = len; |
| |
| ret = smh_trap(SYSREAD, &read); |
| if (ret < 0) |
| return smh_errno(); |
| return len - ret; |
| } |
| |
| long smh_write(long fd, const void *memp, size_t len, ulong *written) |
| { |
| long ret; |
| struct smh_rdwr_s write; |
| |
| debug("%s: fd %ld, memp %p, len %zu\n", __func__, fd, memp, len); |
| |
| write.fd = fd; |
| write.memp = (void *)memp; |
| write.len = len; |
| |
| ret = smh_trap(SYSWRITE, &write); |
| *written = len - ret; |
| if (ret) |
| return smh_errno(); |
| return 0; |
| } |
| |
| long smh_close(long fd) |
| { |
| long ret; |
| |
| debug("%s: fd %ld\n", __func__, fd); |
| |
| ret = smh_trap(SYSCLOSE, &fd); |
| if (ret == -1) |
| return smh_errno(); |
| return 0; |
| } |
| |
| long smh_flen(long fd) |
| { |
| long ret; |
| |
| debug("%s: fd %ld\n", __func__, fd); |
| |
| ret = smh_trap(SYSFLEN, &fd); |
| if (ret == -1) |
| return smh_errno(); |
| return ret; |
| } |
| |
| long smh_seek(long fd, long pos) |
| { |
| long ret; |
| struct smh_seek_s { |
| long fd; |
| long pos; |
| } seek; |
| |
| debug("%s: fd %ld pos %ld\n", __func__, fd, pos); |
| |
| seek.fd = fd; |
| seek.pos = pos; |
| |
| ret = smh_trap(SYSSEEK, &seek); |
| if (ret) |
| return smh_errno(); |
| return 0; |
| } |
| |
| int smh_getc(void) |
| { |
| return smh_trap(SYSREADC, NULL); |
| } |
| |
| void smh_putc(char ch) |
| { |
| smh_trap(SYSWRITEC, &ch); |
| } |
| |
| void smh_puts(const char *s) |
| { |
| smh_trap(SYSWRITE0, (char *)s); |
| } |