[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH] riscv: Add semihosting support [v8]
From: |
Alistair Francis |
Subject: |
Re: [PATCH] riscv: Add semihosting support [v8] |
Date: |
Fri, 23 Oct 2020 17:22:45 -0700 |
On Fri, Oct 23, 2020 at 2:51 PM Keith Packard via <qemu-devel@nongnu.org> wrote:
>
> Adapt the arm semihosting support code for RISCV. This implementation
> is based on the standard for RISC-V semihosting version 0.2 as
> documented in
>
> https://github.com/riscv/riscv-semihosting-spec/releases/tag/0.2
>
> Signed-off-by: Keith Packard <keithp@keithp.com>
Hey Keith,
>
> ---
>
> v2:
> Update PC after exception is handled to follow
> change in the ARM version for SYS_READC
>
> v3:
> Disallow semihosting in user mode; report a regular
> breakpoint in that case.
>
> v4:
> Fix errors reported by checkpatch
>
> v5:
> Reference current RISC-V semihosting specification
>
> v6:
> Add support for semihosting in riscv64-linux-user and
> riscv32-linux-user
>
> v7:
> Add meson build support
>
> v8:
> Fix errors reported by checkpatch that crept in.
> ---
> MAINTAINERS | 1 +
> default-configs/devices/riscv32-softmmu.mak | 1 +
> default-configs/devices/riscv64-softmmu.mak | 1 +
> linux-user/meson.build | 1 +
> linux-user/qemu.h | 4 +-
> linux-user/riscv/meson.build | 2 +
> linux-user/riscv/semihost.c | 76 ++
> qemu-options.hx | 10 +-
> target/riscv/cpu.h | 7 +
> target/riscv/cpu_bits.h | 1 +
> target/riscv/cpu_helper.c | 9 +
> .../riscv/insn_trans/trans_privileged.c.inc | 24 +-
> target/riscv/meson.build | 1 +
> target/riscv/riscv-semi.c | 1086 +++++++++++++++++
> target/riscv/translate.c | 11 +
> 15 files changed, 1229 insertions(+), 6 deletions(-)
> create mode 100644 linux-user/riscv/meson.build
> create mode 100644 linux-user/riscv/semihost.c
> create mode 100644 target/riscv/riscv-semi.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 6a197bd358..8cf5b1e448 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -279,6 +279,7 @@ M: Palmer Dabbelt <palmer@dabbelt.com>
> M: Alistair Francis <Alistair.Francis@wdc.com>
> M: Sagar Karandikar <sagark@eecs.berkeley.edu>
> M: Bastian Koppelmann <kbastian@mail.uni-paderborn.de>
> +M: Keith Packard <keithp@keithp.com>
I don't think you should be a maintainer just yet. In general people
have to be actively reviewing patches to be listed as a maintainer.
> L: qemu-riscv@nongnu.org
> S: Supported
> F: target/riscv/
> diff --git a/default-configs/devices/riscv32-softmmu.mak
> b/default-configs/devices/riscv32-softmmu.mak
> index 94a236c9c2..e8a4eeebec 100644
> --- a/default-configs/devices/riscv32-softmmu.mak
> +++ b/default-configs/devices/riscv32-softmmu.mak
> @@ -3,6 +3,7 @@
> # Uncomment the following lines to disable these optional devices:
> #
> #CONFIG_PCI_DEVICES=n
> +CONFIG_SEMIHOSTING=y
>
> # Boards:
> #
> diff --git a/default-configs/devices/riscv64-softmmu.mak
> b/default-configs/devices/riscv64-softmmu.mak
> index 76b6195648..d8a87b7671 100644
> --- a/default-configs/devices/riscv64-softmmu.mak
> +++ b/default-configs/devices/riscv64-softmmu.mak
> @@ -3,6 +3,7 @@
> # Uncomment the following lines to disable these optional devices:
> #
> #CONFIG_PCI_DEVICES=n
> +CONFIG_SEMIHOSTING=y
>
> # Boards:
> #
> diff --git a/linux-user/meson.build b/linux-user/meson.build
> index 2b94e4ba24..31b5ed99b5 100644
> --- a/linux-user/meson.build
> +++ b/linux-user/meson.build
> @@ -29,6 +29,7 @@ subdir('microblaze')
> subdir('mips64')
> subdir('mips')
> subdir('ppc')
> +subdir('riscv')
> subdir('s390x')
> subdir('sh4')
> subdir('sparc64')
> diff --git a/linux-user/qemu.h b/linux-user/qemu.h
> index 941ca99722..faeaab9614 100644
> --- a/linux-user/qemu.h
> +++ b/linux-user/qemu.h
> @@ -105,6 +105,8 @@ typedef struct TaskState {
> /* FPA state */
> FPA11 fpa;
> # endif
> +#endif
> +#if defined(TARGET_ARM) || defined(TARGET_RISCV)
> int swi_errno;
> #endif
> #if defined(TARGET_I386) && !defined(TARGET_X86_64)
> @@ -118,7 +120,7 @@ typedef struct TaskState {
> #ifdef TARGET_M68K
> abi_ulong tp_value;
> #endif
> -#if defined(TARGET_ARM) || defined(TARGET_M68K)
> +#if defined(TARGET_ARM) || defined(TARGET_M68K) || defined(TARGET_RISCV)
> /* Extra fields for semihosted binaries. */
> abi_ulong heap_base;
> abi_ulong heap_limit;
> diff --git a/linux-user/riscv/meson.build b/linux-user/riscv/meson.build
> new file mode 100644
> index 0000000000..aee3179cdb
> --- /dev/null
> +++ b/linux-user/riscv/meson.build
> @@ -0,0 +1,2 @@
> +linux_user_ss.add(when: 'TARGET_RISCV64', if_true: files('semihost.c'))
> +linux_user_ss.add(when: 'TARGET_RISCV32', if_true: files('semihost.c'))
> diff --git a/linux-user/riscv/semihost.c b/linux-user/riscv/semihost.c
> new file mode 100644
> index 0000000000..2d35ebab68
> --- /dev/null
> +++ b/linux-user/riscv/semihost.c
> @@ -0,0 +1,76 @@
> +/*
> + * RISC-V Semihosting Console Support
> + *
> + * Copyright (c) 2019 Linaro Ltd
> + *
> + * Currently RISC-V and ARM are unique in having support for semihosting
> support
> + * in linux-user. So for now we implement the common console API but
> + * just for risc-v and arm linux-user.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "cpu.h"
> +#include "hw/semihosting/console.h"
> +#include "qemu.h"
> +#include <termios.h>
> +
> +int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr)
> +{
> + int len = target_strlen(addr);
> + void *s;
> + if (len < 0) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: passed inaccessible address " TARGET_FMT_lx,
> + __func__, addr);
> + return 0;
> + }
> + s = lock_user(VERIFY_READ, addr, (long)(len + 1), 1);
> + g_assert(s); /* target_strlen has already verified this will work */
> + len = write(STDERR_FILENO, s, len);
> + unlock_user(s, addr, 0);
> + return len;
> +}
> +
> +void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr)
> +{
> + char c;
> +
> + if (get_user_u8(c, addr)) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: passed inaccessible address " TARGET_FMT_lx,
> + __func__, addr);
> + } else {
> + if (write(STDERR_FILENO, &c, 1) != 1) {
> + qemu_log_mask(LOG_UNIMP, "%s: unexpected write to stdout
> failure",
> + __func__);
> + }
> + }
> +}
> +
> +/*
> + * For linux-user we can safely block. However as we want to return as
> + * soon as a character is read we need to tweak the termio to disable
> + * line buffering. We restore the old mode afterwards in case the
> + * program is expecting more normal behaviour. This is slow but
> + * nothing using semihosting console reading is expecting to be fast.
> + */
> +target_ulong qemu_semihosting_console_inc(CPUArchState *env)
> +{
> + uint8_t c;
> + struct termios old_tio, new_tio;
> +
> + /* Disable line-buffering and echo */
> + tcgetattr(STDIN_FILENO, &old_tio);
> + new_tio = old_tio;
> + new_tio.c_lflag &= (~ICANON & ~ECHO);
> + tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
> +
> + c = getchar();
> +
> + /* restore config */
> + tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
> +
> + return (target_ulong) c;
> +}
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 1da52a269c..0dcf15b480 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -4192,10 +4192,10 @@ ERST
> DEF("semihosting", 0, QEMU_OPTION_semihosting,
> "-semihosting semihosting mode\n",
> QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32 |
> - QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2)
> + QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2 | QEMU_ARCH_RISCV)
> SRST
> ``-semihosting``
> - Enable semihosting mode (ARM, M68K, Xtensa, MIPS, Nios II only).
> + Enable semihosting mode (ARM, M68K, Xtensa, MIPS, Nios II, RISC-V only).
>
> Note that this allows guest direct access to the host filesystem, so
> should only be used with a trusted guest OS.
> @@ -4207,10 +4207,10 @@ DEF("semihosting-config", HAS_ARG,
> QEMU_OPTION_semihosting_config,
> "-semihosting-config
> [enable=on|off][,target=native|gdb|auto][,chardev=id][,arg=str[,...]]\n" \
> " semihosting configuration\n",
> QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32 |
> -QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2)
> +QEMU_ARCH_MIPS | QEMU_ARCH_NIOS2 | QEMU_ARCH_RISCV)
> SRST
> ``-semihosting-config
> [enable=on|off][,target=native|gdb|auto][,chardev=id][,arg=str[,...]]``
> - Enable and configure semihosting (ARM, M68K, Xtensa, MIPS, Nios II
> + Enable and configure semihosting (ARM, M68K, Xtensa, MIPS, Nios II,
> RISC-V
> only).
>
> Note that this allows guest direct access to the host filesystem, so
> @@ -4225,6 +4225,8 @@ SRST
> open/read/write/seek/select. Tensilica baremetal libc for ISS and
> linux platform "sim" use this interface.
>
> + On RISC-V this implements the standard semihosting API, version 0.2.
> +
> ``target=native|gdb|auto``
> Defines where the semihosting calls will be addressed, to QEMU
> (``native``) or to GDB (``gdb``). The default is ``auto``, which
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index de275782e6..2ab5220964 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -460,4 +460,11 @@ void riscv_set_csr_ops(int csrno, riscv_csr_operations
> *ops);
>
> void riscv_cpu_register_gdb_regs_for_features(CPUState *cs);
>
> +typedef CPURISCVState CPUArchState;
> +typedef RISCVCPU ArchCPU;
> +
> +target_ulong do_riscv_semihosting(CPURISCVState *env);
> +
> +#include "exec/cpu-all.h"
This isn't used in the header so it shouldn't be here.
> +
> #endif /* RISCV_CPU_H */
> diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
> index bd36062877..26a60b9e27 100644
> --- a/target/riscv/cpu_bits.h
> +++ b/target/riscv/cpu_bits.h
> @@ -556,6 +556,7 @@
> #define RISCV_EXCP_INST_PAGE_FAULT 0xc /* since: priv-1.10.0 */
> #define RISCV_EXCP_LOAD_PAGE_FAULT 0xd /* since: priv-1.10.0 */
> #define RISCV_EXCP_STORE_PAGE_FAULT 0xf /* since: priv-1.10.0 */
> +#define RISCV_EXCP_SEMIHOST 0x10
I don't see this in the RISC-V spec, it seems to just be reserved, not
for semihosting.
> #define RISCV_EXCP_INST_GUEST_PAGE_FAULT 0x14
> #define RISCV_EXCP_LOAD_GUEST_ACCESS_FAULT 0x15
> #define RISCV_EXCP_VIRT_INSTRUCTION_FAULT 0x16
> diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
> index 904899054d..ddb751df26 100644
> --- a/target/riscv/cpu_helper.c
> +++ b/target/riscv/cpu_helper.c
> @@ -856,6 +856,15 @@ void riscv_cpu_do_interrupt(CPUState *cs)
> target_ulong htval = 0;
> target_ulong mtval2 = 0;
>
> + if (cause == RISCV_EXCP_SEMIHOST) {
> + if (env->priv >= PRV_S) {
> + env->gpr[xA0] = do_riscv_semihosting(env);
> + env->pc += 4;
> + return;
> + }
> + cause = RISCV_EXCP_BREAKPOINT;
> + }
> +
> if (!async) {
> /* set tval to badaddr for traps with address information */
> switch (cause) {
> diff --git a/target/riscv/insn_trans/trans_privileged.c.inc
> b/target/riscv/insn_trans/trans_privileged.c.inc
> index 2a61a853bf..8104f7b13c 100644
> --- a/target/riscv/insn_trans/trans_privileged.c.inc
> +++ b/target/riscv/insn_trans/trans_privileged.c.inc
> @@ -29,7 +29,29 @@ static bool trans_ecall(DisasContext *ctx, arg_ecall *a)
>
> static bool trans_ebreak(DisasContext *ctx, arg_ebreak *a)
> {
> - generate_exception(ctx, RISCV_EXCP_BREAKPOINT);
> + uint32_t pre = opcode_at(&ctx->base, ctx->base.pc_next - 4);
> + uint32_t ebreak = opcode_at(&ctx->base, ctx->base.pc_next);
> + uint32_t post = opcode_at(&ctx->base, ctx->base.pc_next + 4);
> +
> + /*
> + * The RISC-V semihosting spec specifies the following
> + * three-instruction sequence to flag a semihosting call:
> + *
> + * slli zero, zero, 0x1f 0x01f01013
> + * ebreak 0x00100073
> + * srai zero, zero, 0x7 0x40705013
> + *
> + * The two shift operations on the zero register are no-ops, used
> + * here to signify a semihosting exception, rather than a breakpoint.
> + *
> + * Uncompressed instructions are used so that the sequence is easy
> + * to validate.
> + */
> + if (pre == 0x01f01013 && ebreak == 0x00100073 && post == 0x40705013) {
> + generate_exception(ctx, RISCV_EXCP_SEMIHOST);
> + } else {
> + generate_exception(ctx, RISCV_EXCP_BREAKPOINT);
> + }
> exit_tb(ctx); /* no chaining */
> ctx->base.is_jmp = DISAS_NORETURN;
> return true;
> diff --git a/target/riscv/meson.build b/target/riscv/meson.build
> index abd647fea1..3d95282cb3 100644
> --- a/target/riscv/meson.build
> +++ b/target/riscv/meson.build
> @@ -22,6 +22,7 @@ riscv_ss.add(files(
> 'op_helper.c',
> 'vector_helper.c',
> 'translate.c',
> + 'riscv-semi.c'
> ))
>
> riscv_softmmu_ss = ss.source_set()
> diff --git a/target/riscv/riscv-semi.c b/target/riscv/riscv-semi.c
> new file mode 100644
> index 0000000000..ea705899ea
> --- /dev/null
> +++ b/target/riscv/riscv-semi.c
> @@ -0,0 +1,1086 @@
> +/*
> + * RISC-V semihosting syscalls
> + *
> + * Copyright (c) 2005, 2007 CodeSourcery.
> + * Copyright (c) 2019 Linaro
> + * Copyright © 2019 Keith Packard
> + * Written by Paul Brook.
> + * Adapted for RISC-V by Keith Packard
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + *
> + * ARM Semihosting is documented in:
> + * Semihosting for AArch32 and AArch64 Release 2.0
> + * https://static.docs.arm.com/100863/0200/semihosting.pdf
Maybe just point to the RISC-V doc instead.
> + */
> +
> +#include "qemu/osdep.h"
> +
> +#include "cpu.h"
> +#include "hw/semihosting/semihost.h"
> +#include "hw/semihosting/console.h"
> +#include "qemu/log.h"
> +#ifdef CONFIG_USER_ONLY
> +#include "qemu.h"
> +
> +#define RISCV_HEAP_SIZE (128 * 1024 * 1024)
> +#else
> +#include "exec/gdbstub.h"
> +#include "qemu/cutils.h"
> +#endif
> +
> +#define TARGET_SYS_OPEN 0x01
> +#define TARGET_SYS_CLOSE 0x02
> +#define TARGET_SYS_WRITEC 0x03
> +#define TARGET_SYS_WRITE0 0x04
> +#define TARGET_SYS_WRITE 0x05
> +#define TARGET_SYS_READ 0x06
> +#define TARGET_SYS_READC 0x07
> +#define TARGET_SYS_ISTTY 0x09
> +#define TARGET_SYS_SEEK 0x0a
> +#define TARGET_SYS_FLEN 0x0c
> +#define TARGET_SYS_TMPNAM 0x0d
> +#define TARGET_SYS_REMOVE 0x0e
> +#define TARGET_SYS_RENAME 0x0f
> +#define TARGET_SYS_CLOCK 0x10
> +#define TARGET_SYS_TIME 0x11
> +#define TARGET_SYS_SYSTEM 0x12
> +#define TARGET_SYS_ERRNO 0x13
> +#define TARGET_SYS_GET_CMDLINE 0x15
> +#define TARGET_SYS_HEAPINFO 0x16
> +#define TARGET_SYS_EXIT 0x18
> +#define TARGET_SYS_SYNCCACHE 0x19
> +#define TARGET_SYS_EXIT_EXTENDED 0x20
> +
> +/*
> + * ADP_Stopped_ApplicationExit is used for exit(0),
> + * anything else is implemented as exit(1)
> + */
> +#define ADP_Stopped_ApplicationExit (0x20026)
> +
> +#ifndef O_BINARY
> +#define O_BINARY 0
> +#endif
> +
> +#define GDB_O_RDONLY 0x000
> +#define GDB_O_WRONLY 0x001
> +#define GDB_O_RDWR 0x002
> +#define GDB_O_APPEND 0x008
> +#define GDB_O_CREAT 0x200
> +#define GDB_O_TRUNC 0x400
> +#define GDB_O_BINARY 0
> +
> +static int gdb_open_modeflags[12] = {
> + GDB_O_RDONLY,
> + GDB_O_RDONLY | GDB_O_BINARY,
> + GDB_O_RDWR,
> + GDB_O_RDWR | GDB_O_BINARY,
> + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC,
> + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY,
> + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC,
> + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY,
> + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND,
> + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY,
> + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND,
> + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY
> +};
> +
> +static int open_modeflags[12] = {
> + O_RDONLY,
> + O_RDONLY | O_BINARY,
> + O_RDWR,
> + O_RDWR | O_BINARY,
> + O_WRONLY | O_CREAT | O_TRUNC,
> + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
> + O_RDWR | O_CREAT | O_TRUNC,
> + O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
> + O_WRONLY | O_CREAT | O_APPEND,
> + O_WRONLY | O_CREAT | O_APPEND | O_BINARY,
> + O_RDWR | O_CREAT | O_APPEND,
> + O_RDWR | O_CREAT | O_APPEND | O_BINARY
> +};
> +
> +typedef enum GuestFDType {
> + GuestFDUnused = 0,
> + GuestFDHost = 1,
> + GuestFDGDB = 2,
> + GuestFDFeatureFile = 3,
> +} GuestFDType;
> +
> +/*
> + * Guest file descriptors are integer indexes into an array of
> + * these structures (we will dynamically resize as necessary).
> + */
> +typedef struct GuestFD {
> + GuestFDType type;
> + union {
> + int hostfd;
> + target_ulong featurefile_offset;
> + };
> +} GuestFD;
> +
> +static GArray *guestfd_array;
> +
> +#define FIRST_GUEST_FD 3
> +
> +/*
> + * Allocate a new guest file descriptor and return it; if we
> + * couldn't allocate a new fd then return -1.
> + * This is a fairly simplistic implementation because we don't
> + * expect that most semihosting guest programs will make very
> + * heavy use of opening and closing fds.
> + */
> +static int alloc_guestfd(void)
> +{
> + guint i;
> +
> + if (!guestfd_array) {
> + /* New entries zero-initialized, i.e. type GuestFDUnused */
> + guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD));
> + }
> +
> + for (i = FIRST_GUEST_FD; i < guestfd_array->len; i++) {
> + GuestFD *gf = &g_array_index(guestfd_array, GuestFD, i);
> +
> + if (gf->type == GuestFDUnused) {
> + return i;
> + }
> + }
> +
> + /* All elements already in use: expand the array */
> + g_array_set_size(guestfd_array, i + 1);
> + return i;
> +}
> +
> +/*
> + * Look up the guestfd in the data structure; return NULL
> + * for out of bounds, but don't check whether the slot is unused.
> + * This is used internally by the other guestfd functions.
> + */
> +static GuestFD *do_get_guestfd(int guestfd)
> +{
> + if (!guestfd_array) {
> + return NULL;
> + }
> +
> + if (guestfd < 0 || guestfd >= guestfd_array->len) {
> + return NULL;
> + }
> +
> + return &g_array_index(guestfd_array, GuestFD, guestfd);
> +}
> +
> +/*
> + * Associate the specified guest fd (which must have been
> + * allocated via alloc_fd() and not previously used) with
> + * the specified host/gdb fd.
> + */
> +static void associate_guestfd(int guestfd, int hostfd)
> +{
> + GuestFD *gf = do_get_guestfd(guestfd);
> +
> + assert(gf);
> + gf->type = use_gdb_syscalls() ? GuestFDGDB : GuestFDHost;
> + gf->hostfd = hostfd;
> +}
> +
> +/*
> + * Deallocate the specified guest file descriptor. This doesn't
> + * close the host fd, it merely undoes the work of alloc_fd().
> + */
> +static void dealloc_guestfd(int guestfd)
> +{
> + GuestFD *gf = do_get_guestfd(guestfd);
> +
> + assert(gf);
> + gf->type = GuestFDUnused;
> +}
> +
> +/*
> + * Given a guest file descriptor, get the associated struct.
> + * If the fd is not valid, return NULL. This is the function
> + * used by the various semihosting calls to validate a handle
> + * from the guest.
> + * Note: calling alloc_guestfd() or dealloc_guestfd() will
> + * invalidate any GuestFD* obtained by calling this function.
> + */
> +static GuestFD *get_guestfd(int guestfd)
> +{
> + GuestFD *gf = do_get_guestfd(guestfd);
> +
> + if (!gf || gf->type == GuestFDUnused) {
> + return NULL;
> + }
> + return gf;
> +}
> +
> +/*
> + * The semihosting API has no concept of its errno being thread-safe,
> + * as the API design predates SMP CPUs and was intended as a simple
> + * real-hardware set of debug functionality. For QEMU, we make the
> + * errno be per-thread in linux-user mode; in softmmu it is a simple
> + * global, and we assume that the guest takes care of avoiding any races.
> + */
> +#ifndef CONFIG_USER_ONLY
> +static target_ulong syscall_err;
> +
> +#include "exec/softmmu-semi.h"
> +#endif
> +
> +static inline uint32_t set_swi_errno(CPURISCVState *env, uint32_t code)
> +{
> + if (code == (uint32_t)-1) {
> +#ifdef CONFIG_USER_ONLY
> + CPUState *cs = env_cpu(env);
> + TaskState *ts = cs->opaque;
> +
> + ts->swi_errno = errno;
> +#else
> + syscall_err = errno;
> +#endif
> + }
> + return code;
> +}
> +
> +static inline uint32_t get_swi_errno(CPURISCVState *env)
> +{
> +#ifdef CONFIG_USER_ONLY
> + CPUState *cs = env_cpu(env);
> + TaskState *ts = cs->opaque;
> +
> + return ts->swi_errno;
> +#else
> + return syscall_err;
> +#endif
> +}
> +
> +static target_ulong riscv_semi_syscall_len;
Could we split all of the shared code out somewhere?
> +
> +static void riscv_semi_cb(CPUState *cs, target_ulong ret, target_ulong err)
> +{
> + RISCVCPU *cpu = RISCV_CPU(cs);
> + CPURISCVState *env = &cpu->env;
> + target_ulong reg0 = env->gpr[xA0];
> +
> + if (ret == (target_ulong)-1) {
> + errno = err;
> + set_swi_errno(env, -1);
> + reg0 = ret;
> + } else {
> + /* Fixup syscalls that use nonstardard return conventions. */
> + switch (reg0) {
> + case TARGET_SYS_WRITE:
> + case TARGET_SYS_READ:
> + reg0 = riscv_semi_syscall_len - ret;
> + break;
> + case TARGET_SYS_SEEK:
> + reg0 = 0;
> + break;
> + default:
> + reg0 = ret;
> + break;
> + }
> + }
> + env->gpr[xA0] = reg0;
> +}
> +
> +static target_ulong riscv_flen_buf(RISCVCPU *cpu)
> +{
> + /*
> + * Return an address in target memory of 64 bytes where the remote
> + * gdb should write its stat struct. (The format of this structure
> + * is defined by GDB's remote protocol and is not target-specific.)
> + * We put this on the guest's stack just below SP.
> + */
> + CPURISCVState *env = &cpu->env;
> + target_ulong sp;
> +
> + sp = env->gpr[xSP];
> +
> + return sp - 64;
> +}
> +
> +static void riscv_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong
> err)
> +{
> + RISCVCPU *cpu = RISCV_CPU(cs);
> + CPURISCVState *env = &cpu->env;
> + /*
> + * The size is always stored in big-endian order, extract
> + * the value. We assume the size always fit in 32 bits.
> + */
> + uint32_t size;
> + cpu_memory_rw_debug(cs, riscv_flen_buf(cpu) + 32, (uint8_t *)&size, 4,
> 0);
> + size = be32_to_cpu(size);
> + env->gpr[xA0] = size;
> + errno = err;
> + set_swi_errno(env, -1);
> +}
> +
> +static int riscv_semi_open_guestfd;
> +
> +static void riscv_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong
> err)
> +{
> + RISCVCPU *cpu = RISCV_CPU(cs);
> + CPURISCVState *env = &cpu->env;
> + if (ret == (target_ulong)-1) {
> + errno = err;
> + set_swi_errno(env, -1);
> + dealloc_guestfd(riscv_semi_open_guestfd);
> + } else {
> + associate_guestfd(riscv_semi_open_guestfd, ret);
> + ret = riscv_semi_open_guestfd;
> + }
> +
> + env->gpr[xA0] = ret;
> +}
> +
> +static target_ulong riscv_gdb_syscall(RISCVCPU *cpu, gdb_syscall_complete_cb
> cb,
> + const char *fmt, ...)
> +{
> + va_list va;
> + CPURISCVState *env = &cpu->env;
> +
> + va_start(va, fmt);
> + gdb_do_syscallv(cb, fmt, va);
> + va_end(va);
> +
> + /*
> + * FIXME: in softmmu mode, the gdbstub will schedule our callback
> + * to occur, but will not actually call it to complete the syscall
> + * until after this function has returned and we are back in the
> + * CPU main loop. Therefore callers to this function must not
> + * do anything with its return value, because it is not necessarily
> + * the result of the syscall, but could just be the old value of X0.
> + * The only thing safe to do with this is that the callers of
> + * do_riscv_semihosting() will write it straight back into X0.
> + * (In linux-user mode, the callback will have happened before
> + * gdb_do_syscallv() returns.)
> + *
> + * We should tidy this up so neither this function nor
> + * do_riscv_semihosting() return a value, so the mistake of
> + * doing something with the return value is not possible to make.
> + */
> +
> + return env->gpr[xA0];
> +}
> +
> +/*
> + * Types for functions implementing various semihosting calls
> + * for specific types of guest file descriptor. These must all
> + * do the work and return the required return value for the guest,
> + * setting the guest errno if appropriate.
> + */
> +typedef uint32_t sys_closefn(RISCVCPU *cpu, GuestFD *gf);
> +typedef uint32_t sys_writefn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len);
> +typedef uint32_t sys_readfn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len);
> +typedef uint32_t sys_isattyfn(RISCVCPU *cpu, GuestFD *gf);
> +typedef uint32_t sys_seekfn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong offset);
> +typedef uint32_t sys_flenfn(RISCVCPU *cpu, GuestFD *gf);
> +
> +static uint32_t host_closefn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + CPURISCVState *env = &cpu->env;
> +
> + return set_swi_errno(env, close(gf->hostfd));
> +}
> +
> +static uint32_t host_writefn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len)
> +{
> + uint32_t ret;
> + CPURISCVState *env = &cpu->env;
> + char *s = lock_user(VERIFY_READ, buf, len, 1);
> + if (!s) {
> + /* Return bytes not written on error */
> + return len;
> + }
> + ret = set_swi_errno(env, write(gf->hostfd, s, len));
> + unlock_user(s, buf, 0);
> + if (ret == (uint32_t)-1) {
> + ret = 0;
> + }
> + /* Return bytes not written */
> + return len - ret;
> +}
> +
> +static uint32_t host_readfn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len)
> +{
> + uint32_t ret;
> + CPURISCVState *env = &cpu->env;
> + char *s = lock_user(VERIFY_WRITE, buf, len, 0);
> + if (!s) {
> + /* return bytes not read */
> + return len;
> + }
> + do {
> + ret = set_swi_errno(env, read(gf->hostfd, s, len));
> + } while (ret == -1 && errno == EINTR);
> + unlock_user(s, buf, len);
> + if (ret == (uint32_t)-1) {
> + ret = 0;
> + }
> + /* Return bytes not read */
> + return len - ret;
> +}
> +
> +static uint32_t host_isattyfn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + return isatty(gf->hostfd);
> +}
> +
> +static uint32_t host_seekfn(RISCVCPU *cpu, GuestFD *gf, target_ulong offset)
> +{
> + CPURISCVState *env = &cpu->env;
> + uint32_t ret = set_swi_errno(env, lseek(gf->hostfd, offset, SEEK_SET));
> + if (ret == (uint32_t)-1) {
> + return -1;
> + }
> + return 0;
> +}
> +
> +static uint32_t host_flenfn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + CPURISCVState *env = &cpu->env;
> + struct stat buf;
> + uint32_t ret = set_swi_errno(env, fstat(gf->hostfd, &buf));
> + if (ret == (uint32_t)-1) {
> + return -1;
> + }
> + return buf.st_size;
> +}
> +
> +static uint32_t gdb_closefn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + return riscv_gdb_syscall(cpu, riscv_semi_cb, "close,%x", gf->hostfd);
> +}
> +
> +static uint32_t gdb_writefn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len)
> +{
> + riscv_semi_syscall_len = len;
> + return riscv_gdb_syscall(cpu, riscv_semi_cb, "write,%x,%x,%x",
> + gf->hostfd, buf, len);
> +}
> +
> +static uint32_t gdb_readfn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len)
> +{
> + riscv_semi_syscall_len = len;
> + return riscv_gdb_syscall(cpu, riscv_semi_cb, "read,%x,%x,%x",
> + gf->hostfd, buf, len);
> +}
> +
> +static uint32_t gdb_isattyfn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + return riscv_gdb_syscall(cpu, riscv_semi_cb, "isatty,%x", gf->hostfd);
> +}
> +
> +static uint32_t gdb_seekfn(RISCVCPU *cpu, GuestFD *gf, target_ulong offset)
> +{
> + return riscv_gdb_syscall(cpu, riscv_semi_cb, "lseek,%x,%x,0",
> + gf->hostfd, offset);
> +}
> +
> +static uint32_t gdb_flenfn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + return riscv_gdb_syscall(cpu, riscv_semi_flen_cb, "fstat,%x,%x",
> + gf->hostfd, riscv_flen_buf(cpu));
> +}
> +
> +#define SHFB_MAGIC_0 0x53
> +#define SHFB_MAGIC_1 0x48
> +#define SHFB_MAGIC_2 0x46
> +#define SHFB_MAGIC_3 0x42
> +
> +/* Feature bits reportable in feature byte 0 */
> +#define SH_EXT_EXIT_EXTENDED (1 << 0)
> +#define SH_EXT_STDOUT_STDERR (1 << 1)
> +
> +static const uint8_t featurefile_data[] = {
> + SHFB_MAGIC_0,
> + SHFB_MAGIC_1,
> + SHFB_MAGIC_2,
> + SHFB_MAGIC_3,
> + SH_EXT_EXIT_EXTENDED | SH_EXT_STDOUT_STDERR, /* Feature byte 0 */
> +};
> +
> +static void init_featurefile_guestfd(int guestfd)
> +{
> + GuestFD *gf = do_get_guestfd(guestfd);
> +
> + assert(gf);
> + gf->type = GuestFDFeatureFile;
> + gf->featurefile_offset = 0;
> +}
> +
> +static uint32_t featurefile_closefn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + /* Nothing to do */
> + return 0;
> +}
> +
> +static uint32_t featurefile_writefn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len)
> +{
> + /* This fd can never be open for writing */
> + CPURISCVState *env = &cpu->env;
> +
> + errno = EBADF;
> + return set_swi_errno(env, -1);
> +}
> +
> +static uint32_t featurefile_readfn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong buf, uint32_t len)
> +{
> + uint32_t i;
> +#ifndef CONFIG_USER_ONLY
> + CPURISCVState *env = &cpu->env;
> +#endif
> + char *s;
> +
> + s = lock_user(VERIFY_WRITE, buf, len, 0);
> + if (!s) {
> + return len;
> + }
> +
> + for (i = 0; i < len; i++) {
> + if (gf->featurefile_offset >= sizeof(featurefile_data)) {
> + break;
> + }
> + s[i] = featurefile_data[gf->featurefile_offset];
> + gf->featurefile_offset++;
> + }
> +
> + unlock_user(s, buf, len);
> +
> + /* Return number of bytes not read */
> + return len - i;
> +}
> +
> +static uint32_t featurefile_isattyfn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + return 0;
> +}
> +
> +static uint32_t featurefile_seekfn(RISCVCPU *cpu, GuestFD *gf,
> + target_ulong offset)
> +{
> + gf->featurefile_offset = offset;
> + return 0;
> +}
> +
> +static uint32_t featurefile_flenfn(RISCVCPU *cpu, GuestFD *gf)
> +{
> + return sizeof(featurefile_data);
> +}
> +
> +typedef struct GuestFDFunctions {
> + sys_closefn *closefn;
> + sys_writefn *writefn;
> + sys_readfn *readfn;
> + sys_isattyfn *isattyfn;
> + sys_seekfn *seekfn;
> + sys_flenfn *flenfn;
> +} GuestFDFunctions;
> +
> +static const GuestFDFunctions guestfd_fns[] = {
> + [GuestFDHost] = {
> + .closefn = host_closefn,
> + .writefn = host_writefn,
> + .readfn = host_readfn,
> + .isattyfn = host_isattyfn,
> + .seekfn = host_seekfn,
> + .flenfn = host_flenfn,
> + },
> + [GuestFDGDB] = {
> + .closefn = gdb_closefn,
> + .writefn = gdb_writefn,
> + .readfn = gdb_readfn,
> + .isattyfn = gdb_isattyfn,
> + .seekfn = gdb_seekfn,
> + .flenfn = gdb_flenfn,
> + },
> + [GuestFDFeatureFile] = {
> + .closefn = featurefile_closefn,
> + .writefn = featurefile_writefn,
> + .readfn = featurefile_readfn,
> + .isattyfn = featurefile_isattyfn,
> + .seekfn = featurefile_seekfn,
> + .flenfn = featurefile_flenfn,
> + },
> +};
> +
> +/*
> + * Read the input value from the argument block; fail the semihosting
> + * call if the memory read fails.
> + */
> +#define GET_ARG(n) do { \
> + if (get_user_ual(arg ## n, args + (n) * sizeof(target_ulong))) { \
> + errno = EFAULT; \
> + return set_swi_errno(env, -1); \
> + } \
> + } while (0)
> +
> +#define SET_ARG(n, val) \
> + put_user_ual(val, args + (n) * sizeof(target_ulong))
> +
> +/*
> + * Do a semihosting call.
> + *
> + * The specification always says that the "return register" either
> + * returns a specific value or is corrupted, so we don't need to
> + * report to our caller whether we are returning a value or trying to
> + * leave the register unchanged. We use 0xdeadbeef as the return value
> + * when there isn't a defined return value for the call.
> + */
> +target_ulong do_riscv_semihosting(CPURISCVState *env)
> +{
> + RISCVCPU *cpu = env_archcpu(env);
> + CPUState *cs = env_cpu(env);
> + target_ulong args;
> + target_ulong arg0, arg1, arg2, arg3;
> + char *s;
> + int nr;
> + uint32_t ret;
> + uint32_t len;
> + GuestFD *gf;
> +
> + nr = env->gpr[xA0] & 0xffffffffU;
> + args = env->gpr[xA1];
> +
> + switch (nr) {
> + case TARGET_SYS_OPEN:
> + {
> + int guestfd;
> +
> + GET_ARG(0);
> + GET_ARG(1);
> + GET_ARG(2);
> + s = lock_user_string(arg0);
> + if (!s) {
> + errno = EFAULT;
> + return set_swi_errno(env, -1);
> + }
> + if (arg1 >= 12) {
> + unlock_user(s, arg0, 0);
> + errno = EINVAL;
> + return set_swi_errno(env, -1);
> + }
> +
> + guestfd = alloc_guestfd();
> + if (guestfd < 0) {
> + unlock_user(s, arg0, 0);
> + errno = EMFILE;
> + return set_swi_errno(env, -1);
> + }
> +
> + if (strcmp(s, ":tt") == 0) {
> + int result_fileno;
> +
> + /*
> + * We implement SH_EXT_STDOUT_STDERR, so:
> + * open for read == stdin
> + * open for write == stdout
> + * open for append == stderr
> + */
> + if (arg1 < 4) {
> + result_fileno = STDIN_FILENO;
> + } else if (arg1 < 8) {
> + result_fileno = STDOUT_FILENO;
> + } else {
> + result_fileno = STDERR_FILENO;
> + }
> + associate_guestfd(guestfd, result_fileno);
> + unlock_user(s, arg0, 0);
> + return guestfd;
> + }
> + if (strcmp(s, ":semihosting-features") == 0) {
> + unlock_user(s, arg0, 0);
> + /* We must fail opens for modes other than 0 ('r') or 1 ('rb') */
> + if (arg1 != 0 && arg1 != 1) {
> + dealloc_guestfd(guestfd);
> + errno = EACCES;
> + return set_swi_errno(env, -1);
> + }
> + init_featurefile_guestfd(guestfd);
> + return guestfd;
> + }
> +
> + if (use_gdb_syscalls()) {
> + riscv_semi_open_guestfd = guestfd;
> + ret = riscv_gdb_syscall(cpu, riscv_semi_open_cb,
> + "open,%s,%x,1a4", arg0,
> + (int)arg2 + 1, gdb_open_modeflags[arg1]);
> + } else {
> + ret = set_swi_errno(env, open(s, open_modeflags[arg1], 0644));
> + if (ret == (uint32_t)-1) {
> + dealloc_guestfd(guestfd);
> + } else {
> + associate_guestfd(guestfd, ret);
> + ret = guestfd;
> + }
> + }
> + unlock_user(s, arg0, 0);
> + return ret;
> + }
> + case TARGET_SYS_CLOSE:
> + GET_ARG(0);
> +
> + gf = get_guestfd(arg0);
> + if (!gf) {
> + errno = EBADF;
> + return set_swi_errno(env, -1);
> + }
> +
> + ret = guestfd_fns[gf->type].closefn(cpu, gf);
> + dealloc_guestfd(arg0);
> + return ret;
> + case TARGET_SYS_WRITEC:
> + qemu_semihosting_console_outc(env, args);
> + return 0xdeadbeef;
> + case TARGET_SYS_WRITE0:
> + return qemu_semihosting_console_outs(env, args);
> + case TARGET_SYS_WRITE:
> + GET_ARG(0);
> + GET_ARG(1);
> + GET_ARG(2);
> + len = arg2;
> +
> + gf = get_guestfd(arg0);
> + if (!gf) {
> + errno = EBADF;
> + return set_swi_errno(env, -1);
> + }
> +
> + return guestfd_fns[gf->type].writefn(cpu, gf, arg1, len);
> + case TARGET_SYS_READ:
> + GET_ARG(0);
> + GET_ARG(1);
> + GET_ARG(2);
> + len = arg2;
> +
> + gf = get_guestfd(arg0);
> + if (!gf) {
> + errno = EBADF;
> + return set_swi_errno(env, -1);
> + }
> +
> + return guestfd_fns[gf->type].readfn(cpu, gf, arg1, len);
> + case TARGET_SYS_READC:
> + return qemu_semihosting_console_inc(env);
> + case TARGET_SYS_ISTTY:
> + GET_ARG(0);
> +
> + gf = get_guestfd(arg0);
> + if (!gf) {
> + errno = EBADF;
> + return set_swi_errno(env, -1);
> + }
> +
> + return guestfd_fns[gf->type].isattyfn(cpu, gf);
> + case TARGET_SYS_SEEK:
> + GET_ARG(0);
> + GET_ARG(1);
> +
> + gf = get_guestfd(arg0);
> + if (!gf) {
> + errno = EBADF;
> + return set_swi_errno(env, -1);
> + }
> +
> + return guestfd_fns[gf->type].seekfn(cpu, gf, arg1);
> + case TARGET_SYS_FLEN:
> + GET_ARG(0);
> +
> + gf = get_guestfd(arg0);
> + if (!gf) {
> + errno = EBADF;
> + return set_swi_errno(env, -1);
> + }
> +
> + return guestfd_fns[gf->type].flenfn(cpu, gf);
> + case TARGET_SYS_TMPNAM:
> + qemu_log_mask(LOG_UNIMP, "%s: SYS_TMPNAM not implemented", __func__);
> + return -1;
> + case TARGET_SYS_REMOVE:
> + GET_ARG(0);
> + GET_ARG(1);
> + if (use_gdb_syscalls()) {
> + ret = riscv_gdb_syscall(cpu, riscv_semi_cb, "unlink,%s",
> + arg0, (int)arg1 + 1);
> + } else {
> + s = lock_user_string(arg0);
> + if (!s) {
> + errno = EFAULT;
> + return set_swi_errno(env, -1);
> + }
> + ret = set_swi_errno(env, remove(s));
> + unlock_user(s, arg0, 0);
> + }
> + return ret;
> + case TARGET_SYS_RENAME:
> + GET_ARG(0);
> + GET_ARG(1);
> + GET_ARG(2);
> + GET_ARG(3);
> + if (use_gdb_syscalls()) {
> + return riscv_gdb_syscall(cpu, riscv_semi_cb, "rename,%s,%s",
> + arg0, (int)arg1 + 1, arg2, (int)arg3 + 1);
> + } else {
> + char *s2;
> + s = lock_user_string(arg0);
> + s2 = lock_user_string(arg2);
> + if (!s || !s2) {
> + errno = EFAULT;
> + ret = set_swi_errno(env, -1);
> + } else {
> + ret = set_swi_errno(env, rename(s, s2));
> + }
> + if (s2) {
> + unlock_user(s2, arg2, 0);
> + }
> + if (s) {
> + unlock_user(s, arg0, 0);
> + }
> + return ret;
> + }
> + case TARGET_SYS_CLOCK:
> + return clock() / (CLOCKS_PER_SEC / 100);
> + case TARGET_SYS_TIME:
> + return set_swi_errno(env, time(NULL));
> + case TARGET_SYS_SYSTEM:
> + GET_ARG(0);
> + GET_ARG(1);
> + if (use_gdb_syscalls()) {
> + return riscv_gdb_syscall(cpu, riscv_semi_cb, "system,%s",
> + arg0, (int)arg1 + 1);
> + } else {
> + s = lock_user_string(arg0);
> + if (!s) {
> + errno = EFAULT;
> + return set_swi_errno(env, -1);
> + }
> + ret = set_swi_errno(env, system(s));
> + unlock_user(s, arg0, 0);
> + return ret;
> + }
> + case TARGET_SYS_ERRNO:
> + return get_swi_errno(env);
> + case TARGET_SYS_GET_CMDLINE:
> + {
> + /*
> + * Build a command-line from the original argv.
> + *
> + * The inputs are:
> + * * arg0, pointer to a buffer of at least the size
> + * specified in arg1.
> + * * arg1, size of the buffer pointed to by arg0 in
> + * bytes.
> + *
> + * The outputs are:
> + * * arg0, pointer to null-terminated string of the
> + * command line.
> + * * arg1, length of the string pointed to by arg0.
> + */
> +
> + char *output_buffer;
> + size_t input_size;
> + size_t output_size;
> + int status = 0;
> +#if !defined(CONFIG_USER_ONLY)
> + const char *cmdline;
> +#else
> + TaskState *ts = cs->opaque;
> +#endif
> + GET_ARG(0);
> + GET_ARG(1);
> + input_size = arg1;
> + /* Compute the size of the output string. */
> +#if !defined(CONFIG_USER_ONLY)
> + cmdline = semihosting_get_cmdline();
> + if (cmdline == NULL) {
> + cmdline = ""; /* Default to an empty line. */
> + }
> + output_size = strlen(cmdline) + 1; /* Count terminating 0. */
> +#else
> + unsigned int i;
> +
> + output_size = ts->info->arg_end - ts->info->arg_start;
> + if (!output_size) {
> + /*
> + * We special-case the "empty command line" case (argc==0).
> + * Just provide the terminating 0.
> + */
> + output_size = 1;
> + }
> +#endif
> +
> + if (output_size > input_size) {
> + /* Not enough space to store command-line arguments. */
> + errno = E2BIG;
> + return set_swi_errno(env, -1);
> + }
> +
> + /* Adjust the command-line length. */
> + if (SET_ARG(1, output_size - 1)) {
> + /* Couldn't write back to argument block */
> + errno = EFAULT;
> + return set_swi_errno(env, -1);
> + }
> +
> + /* Lock the buffer on the RISC-V side. */
> + output_buffer = lock_user(VERIFY_WRITE, arg0, output_size, 0);
> + if (!output_buffer) {
> + errno = EFAULT;
> + return set_swi_errno(env, -1);
> + }
> +
> + /* Copy the command-line arguments. */
> +#if !defined(CONFIG_USER_ONLY)
> + pstrcpy(output_buffer, output_size, cmdline);
> +#else
> + if (output_size == 1) {
> + /* Empty command-line. */
> + output_buffer[0] = '\0';
> + goto out;
> + }
> +
> + if (copy_from_user(output_buffer, ts->info->arg_start,
> + output_size)) {
> + errno = EFAULT;
> + status = set_swi_errno(env, -1);
> + goto out;
> + }
> +
> + /* Separate arguments by white spaces. */
> + for (i = 0; i < output_size - 1; i++) {
> + if (output_buffer[i] == 0) {
> + output_buffer[i] = ' ';
> + }
> + }
> + out:
> +#endif
> + /* Unlock the buffer on the RISC-V side. */
> + unlock_user(output_buffer, arg0, output_size);
> +
> + return status;
> + }
> + case TARGET_SYS_HEAPINFO:
> + {
> + target_ulong retvals[4];
> + target_ulong limit;
> + int i;
> +#ifdef CONFIG_USER_ONLY
> + TaskState *ts = cs->opaque;
> +#endif
> +
> + GET_ARG(0);
> +
> +#ifdef CONFIG_USER_ONLY
> + /*
> + * Some C libraries assume the heap immediately follows .bss, so
> + * allocate it using sbrk.
> + */
> + if (!ts->heap_limit) {
> + abi_ulong ret;
> +
> + ts->heap_base = do_brk(0);
> + limit = ts->heap_base + RISCV_HEAP_SIZE;
> + /* Try a big heap, and reduce the size if that fails. */
> + for (;;) {
> + ret = do_brk(limit);
> + if (ret >= limit) {
> + break;
> + }
> + limit = (ts->heap_base >> 1) + (limit >> 1);
> + }
> + ts->heap_limit = limit;
> + }
> +
> + retvals[0] = ts->heap_base;
> + retvals[1] = ts->heap_limit;
> + retvals[2] = ts->stack_base;
> + retvals[3] = 0; /* Stack limit. */
> +#else
> + limit = ram_size;
> + /* TODO: Make this use the limit of the loaded application. */
> + retvals[0] = limit / 2;
> + retvals[1] = limit;
> + retvals[2] = limit; /* Stack base */
> + retvals[3] = 0; /* Stack limit. */
> +#endif
> +
> + for (i = 0; i < ARRAY_SIZE(retvals); i++) {
> + bool fail;
> +
> + fail = put_user_ual(retvals[i],
> + arg0 + i * sizeof(target_ulong));
> +
> + if (fail) {
> + /* Couldn't write back to argument block */
> + errno = EFAULT;
> + return set_swi_errno(env, -1);
> + }
> + }
> + return 0;
> + }
> + case TARGET_SYS_EXIT:
> + case TARGET_SYS_EXIT_EXTENDED:
> + if (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 0) {
> + /*
> + * The A64 version of SYS_EXIT takes a parameter block,
> + * so the application-exit type can return a subcode which
> + * is the exit status code from the application.
> + * SYS_EXIT_EXTENDED is an a new-in-v2.0 optional function
> + * which allows A32/T32 guests to also provide a status code.
> + */
> + GET_ARG(0);
> + GET_ARG(1);
> +
> + if (arg0 == ADP_Stopped_ApplicationExit) {
> + ret = arg1;
> + } else {
> + ret = 1;
> + }
> + } else {
> + /*
> + * The A32/T32 version of SYS_EXIT specifies only
> + * Stopped_ApplicationExit as normal exit, but does not
> + * allow the guest to specify the exit status code.
> + * Everything else is considered an error.
> + */
> + ret = (args == ADP_Stopped_ApplicationExit) ? 0 : 1;
> + }
> + gdb_exit(env, ret);
> + exit(ret);
> + case TARGET_SYS_SYNCCACHE:
> + /*
> + * Clean the D-cache and invalidate the I-cache for the specified
> + * virtual address range. This is a nop for us since we don't
> + * implement caches. This is only present on A64.
> + */
> + if (sizeof(target_ulong) == 8) {
> + return 0;
> + }
> + /* fall through -- invalid for A32/T32 */
> + default:
> + fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr);
> + cpu_dump_state(cs, stderr, 0);
> + abort();
> + }
> +}
> diff --git a/target/riscv/translate.c b/target/riscv/translate.c
> index 79dca2291b..ecf594babc 100644
> --- a/target/riscv/translate.c
> +++ b/target/riscv/translate.c
> @@ -63,6 +63,7 @@ typedef struct DisasContext {
> uint16_t vlen;
> uint16_t mlen;
> bool vl_eq_vlmax;
> + CPUState *cs;
I'm not sure we should do this.
> } DisasContext;
>
> #ifdef TARGET_RISCV64
> @@ -746,6 +747,15 @@ static bool gen_shift(DisasContext *ctx, arg_r *a,
> return true;
> }
>
> +static uint32_t opcode_at(DisasContextBase *dcbase, target_ulong pc)
> +{
> + DisasContext *ctx = container_of(dcbase, DisasContext, base);
> + CPUState *cpu = ctx->cs;
> + CPURISCVState *env = cpu->env_ptr;
> +
> + return cpu_ldl_code(env, pc);
@Richard Henderson is this ok?
Alistair
> +}
> +
> /* Include insn module translation function */
> #include "insn_trans/trans_rvi.c.inc"
> #include "insn_trans/trans_rvm.c.inc"
> @@ -812,6 +822,7 @@ static void riscv_tr_init_disas_context(DisasContextBase
> *dcbase, CPUState *cs)
> ctx->lmul = FIELD_EX32(tb_flags, TB_FLAGS, LMUL);
> ctx->mlen = 1 << (ctx->sew + 3 - ctx->lmul);
> ctx->vl_eq_vlmax = FIELD_EX32(tb_flags, TB_FLAGS, VL_EQ_VLMAX);
> + ctx->cs = cs;
> }
>
> static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu)
> --
> 2.28.0
>
>
>
Re: [PATCH] riscv: Add semihosting support [v8], Richard Henderson, 2020/10/26