aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--accel/accel-common.c8
-rw-r--r--accel/stubs/tcg-stub.c7
-rw-r--r--configs/targets/aarch64-linux-user.mak1
-rw-r--r--configs/targets/aarch64_be-linux-user.mak1
-rw-r--r--configs/targets/arm-linux-user.mak1
-rw-r--r--configs/targets/armeb-linux-user.mak1
-rw-r--r--configs/targets/riscv32-linux-user.mak1
-rw-r--r--configs/targets/riscv64-linux-user.mak1
-rw-r--r--docs/conf.py2
-rw-r--r--docs/system/arm/cpu-features.rst56
-rw-r--r--gdbstub.c38
-rw-r--r--hw/arm/virt.c10
-rw-r--r--include/exec/gdbstub.h64
-rw-r--r--include/exec/softmmu-semi.h101
-rw-r--r--include/qemu/accel.h1
-rw-r--r--include/semihosting/common-semi.h (renamed from semihosting/common-semi.h)2
-rw-r--r--include/semihosting/console.h65
-rw-r--r--include/semihosting/guestfd.h91
-rw-r--r--include/semihosting/semihost.h14
-rw-r--r--include/semihosting/softmmu-uaccess.h59
-rw-r--r--include/semihosting/syscalls.h75
-rw-r--r--linux-user/aarch64/cpu_loop.c2
-rw-r--r--linux-user/arm/cpu_loop.c2
-rw-r--r--linux-user/m68k/cpu_loop.c5
-rw-r--r--linux-user/main.c9
-rw-r--r--linux-user/riscv/cpu_loop.c2
-rw-r--r--linux-user/semihost.c48
-rw-r--r--semihosting/arm-compat-semi.c995
-rw-r--r--semihosting/config.c17
-rw-r--r--semihosting/console.c147
-rw-r--r--semihosting/guestfd.c160
-rw-r--r--semihosting/meson.build6
-rw-r--r--semihosting/syscalls.c978
-rw-r--r--semihosting/uaccess.c91
-rw-r--r--softmmu/vl.c6
-rw-r--r--stubs/semihost.c6
-rw-r--r--target/arm/common-semi-target.h62
-rw-r--r--target/arm/cpregs.h5
-rw-r--r--target/arm/cpu.c32
-rw-r--r--target/arm/cpu.h103
-rw-r--r--target/arm/cpu64.c205
-rw-r--r--target/arm/helper-sme.h21
-rw-r--r--target/arm/helper.c217
-rw-r--r--target/arm/helper.h1
-rw-r--r--target/arm/internals.h4
-rw-r--r--target/arm/kvm64.c2
-rw-r--r--target/arm/m_helper.c2
-rw-r--r--target/arm/machine.c34
-rw-r--r--target/arm/meson.build1
-rw-r--r--target/arm/ptw.c26
-rw-r--r--target/arm/sme_helper.c61
-rw-r--r--target/arm/syndrome.h14
-rw-r--r--target/arm/translate-a64.c46
-rw-r--r--target/arm/translate-a64.h38
-rw-r--r--target/arm/translate-sve.c36
-rw-r--r--target/arm/translate.h6
-rw-r--r--target/m68k/m68k-semi.c137
-rw-r--r--target/m68k/meson.build6
-rw-r--r--target/mips/cpu.h3
-rw-r--r--target/mips/tcg/exception.c1
-rw-r--r--target/mips/tcg/micromips_translate.c.inc6
-rw-r--r--target/mips/tcg/mips16e_translate.c.inc2
-rw-r--r--target/mips/tcg/nanomips_translate.c.inc4
-rw-r--r--target/mips/tcg/sysemu/mips-semi.c85
-rw-r--r--target/mips/tcg/sysemu/tlb_helper.c4
-rw-r--r--target/mips/tcg/sysemu_helper.h.inc2
-rw-r--r--target/mips/tcg/tcg-internal.h2
-rw-r--r--target/mips/tcg/translate.c12
-rw-r--r--target/nios2/meson.build4
-rw-r--r--target/nios2/nios2-semi.c106
-rw-r--r--target/riscv/common-semi-target.h50
-rw-r--r--target/riscv/cpu_helper.c2
72 files changed, 2962 insertions, 1453 deletions
diff --git a/accel/accel-common.c b/accel/accel-common.c
index 7b8ec7e0f7..50035bda55 100644
--- a/accel/accel-common.c
+++ b/accel/accel-common.c
@@ -49,6 +49,14 @@ AccelClass *accel_find(const char *opt_name)
return ac;
}
+/* Return the name of the current accelerator */
+const char *current_accel_name(void)
+{
+ AccelClass *ac = ACCEL_GET_CLASS(current_accel());
+
+ return ac->name;
+}
+
static void accel_init_cpu_int_aux(ObjectClass *klass, void *opaque)
{
CPUClass *cc = CPU_CLASS(klass);
diff --git a/accel/stubs/tcg-stub.c b/accel/stubs/tcg-stub.c
index ea4a0dd2fb..6ce8a34228 100644
--- a/accel/stubs/tcg-stub.c
+++ b/accel/stubs/tcg-stub.c
@@ -21,6 +21,13 @@ void tlb_set_dirty(CPUState *cpu, target_ulong vaddr)
{
}
+int probe_access_flags(CPUArchState *env, target_ulong addr,
+ MMUAccessType access_type, int mmu_idx,
+ bool nonfault, void **phost, uintptr_t retaddr)
+{
+ g_assert_not_reached();
+}
+
void *probe_access(CPUArchState *env, target_ulong addr, int size,
MMUAccessType access_type, int mmu_idx, uintptr_t retaddr)
{
diff --git a/configs/targets/aarch64-linux-user.mak b/configs/targets/aarch64-linux-user.mak
index d0c603c54e..db552f1839 100644
--- a/configs/targets/aarch64-linux-user.mak
+++ b/configs/targets/aarch64-linux-user.mak
@@ -2,4 +2,5 @@ TARGET_ARCH=aarch64
TARGET_BASE_ARCH=arm
TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml
TARGET_HAS_BFLT=y
+CONFIG_SEMIHOSTING=y
CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
diff --git a/configs/targets/aarch64_be-linux-user.mak b/configs/targets/aarch64_be-linux-user.mak
index 7794424745..dc78044fb1 100644
--- a/configs/targets/aarch64_be-linux-user.mak
+++ b/configs/targets/aarch64_be-linux-user.mak
@@ -3,4 +3,5 @@ TARGET_BASE_ARCH=arm
TARGET_BIG_ENDIAN=y
TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml
TARGET_HAS_BFLT=y
+CONFIG_SEMIHOSTING=y
CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
diff --git a/configs/targets/arm-linux-user.mak b/configs/targets/arm-linux-user.mak
index 3e10d6b15d..7f5d65794c 100644
--- a/configs/targets/arm-linux-user.mak
+++ b/configs/targets/arm-linux-user.mak
@@ -3,4 +3,5 @@ TARGET_SYSTBL_ABI=common,oabi
TARGET_SYSTBL=syscall.tbl
TARGET_XML_FILES= gdb-xml/arm-core.xml gdb-xml/arm-vfp.xml gdb-xml/arm-vfp3.xml gdb-xml/arm-vfp-sysregs.xml gdb-xml/arm-neon.xml gdb-xml/arm-m-profile.xml gdb-xml/arm-m-profile-mve.xml
TARGET_HAS_BFLT=y
+CONFIG_SEMIHOSTING=y
CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
diff --git a/configs/targets/armeb-linux-user.mak b/configs/targets/armeb-linux-user.mak
index a249cc2e29..943d0d87bf 100644
--- a/configs/targets/armeb-linux-user.mak
+++ b/configs/targets/armeb-linux-user.mak
@@ -4,4 +4,5 @@ TARGET_SYSTBL=syscall.tbl
TARGET_BIG_ENDIAN=y
TARGET_XML_FILES= gdb-xml/arm-core.xml gdb-xml/arm-vfp.xml gdb-xml/arm-vfp3.xml gdb-xml/arm-vfp-sysregs.xml gdb-xml/arm-neon.xml gdb-xml/arm-m-profile.xml gdb-xml/arm-m-profile-mve.xml
TARGET_HAS_BFLT=y
+CONFIG_SEMIHOSTING=y
CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
diff --git a/configs/targets/riscv32-linux-user.mak b/configs/targets/riscv32-linux-user.mak
index bd2f1fd497..9761618e67 100644
--- a/configs/targets/riscv32-linux-user.mak
+++ b/configs/targets/riscv32-linux-user.mak
@@ -2,4 +2,5 @@ TARGET_ARCH=riscv32
TARGET_BASE_ARCH=riscv
TARGET_ABI_DIR=riscv
TARGET_XML_FILES= gdb-xml/riscv-32bit-cpu.xml gdb-xml/riscv-32bit-fpu.xml gdb-xml/riscv-64bit-fpu.xml gdb-xml/riscv-32bit-virtual.xml
+CONFIG_SEMIHOSTING=y
CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
diff --git a/configs/targets/riscv64-linux-user.mak b/configs/targets/riscv64-linux-user.mak
index 4aca7662ce..cfd1fd382f 100644
--- a/configs/targets/riscv64-linux-user.mak
+++ b/configs/targets/riscv64-linux-user.mak
@@ -2,4 +2,5 @@ TARGET_ARCH=riscv64
TARGET_BASE_ARCH=riscv
TARGET_ABI_DIR=riscv
TARGET_XML_FILES= gdb-xml/riscv-64bit-cpu.xml gdb-xml/riscv-32bit-fpu.xml gdb-xml/riscv-64bit-fpu.xml gdb-xml/riscv-64bit-virtual.xml
+CONFIG_SEMIHOSTING=y
CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
diff --git a/docs/conf.py b/docs/conf.py
index 49dab44cca..e33cf3d381 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -126,7 +126,7 @@ finally:
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
-language = None
+language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst
index 3e626c4b68..3fd76fa0b4 100644
--- a/docs/system/arm/cpu-features.rst
+++ b/docs/system/arm/cpu-features.rst
@@ -372,6 +372,31 @@ verbose command lines. However, the recommended way to select vector
lengths is to explicitly enable each desired length. Therefore only
example's (1), (4), and (6) exhibit recommended uses of the properties.
+SME CPU Property Examples
+-------------------------
+
+ 1) Disable SME::
+
+ $ qemu-system-aarch64 -M virt -cpu max,sme=off
+
+ 2) Implicitly enable all vector lengths for the ``max`` CPU type::
+
+ $ qemu-system-aarch64 -M virt -cpu max
+
+ 3) Only enable the 256-bit vector length::
+
+ $ qemu-system-aarch64 -M virt -cpu max,sme256=on
+
+ 3) Enable the 256-bit and 1024-bit vector lengths::
+
+ $ qemu-system-aarch64 -M virt -cpu max,sme256=on,sme1024=on
+
+ 4) Disable the 512-bit vector length. This results in all the other
+ lengths supported by ``max`` defaulting to enabled
+ (128, 256, 1024 and 2048)::
+
+ $ qemu-system-aarch64 -M virt -cpu max,sve512=off
+
SVE User-mode Default Vector Length Property
--------------------------------------------
@@ -387,3 +412,34 @@ length supported by QEMU is 256.
If this property is set to ``-1`` then the default vector length
is set to the maximum possible length.
+
+SME CPU Properties
+==================
+
+The SME CPU properties are much like the SVE properties: ``sme`` is
+used to enable or disable the entire SME feature, and ``sme<N>`` is
+used to enable or disable specific vector lengths. Finally,
+``sme_fa64`` is used to enable or disable ``FEAT_SME_FA64``, which
+allows execution of the "full a64" instruction set while Streaming
+SVE mode is enabled.
+
+SME is not supported by KVM at this time.
+
+At least one vector length must be enabled when ``sme`` is enabled,
+and all vector lengths must be powers of 2. The maximum vector
+length supported by qemu is 2048 bits. Otherwise, there are no
+additional constraints on the set of vector lengths supported by SME.
+
+SME User-mode Default Vector Length Property
+--------------------------------------------
+
+For qemu-aarch64, the cpu propery ``sme-default-vector-length=N`` is
+defined to mirror the Linux kernel parameter file
+``/proc/sys/abi/sme_default_vector_length``. The default length, ``N``,
+is in units of bytes and must be between 16 and 8192.
+If not specified, the default vector length is 32.
+
+As with ``sve-default-vector-length``, if the default length is larger
+than the maximum vector length enabled, the actual vector length will
+be reduced. If this property is set to ``-1`` then the default vector
+length is set to the maximum possible length.
diff --git a/gdbstub.c b/gdbstub.c
index 88a34c8f52..cf869b10e3 100644
--- a/gdbstub.c
+++ b/gdbstub.c
@@ -1878,14 +1878,46 @@ static void handle_read_all_regs(GArray *params, void *user_ctx)
static void handle_file_io(GArray *params, void *user_ctx)
{
if (params->len >= 1 && gdbserver_state.current_syscall_cb) {
- target_ulong ret, err;
+ uint64_t ret;
+ int err;
- ret = (target_ulong)get_param(params, 0)->val_ull;
+ ret = get_param(params, 0)->val_ull;
if (params->len >= 2) {
- err = (target_ulong)get_param(params, 1)->val_ull;
+ err = get_param(params, 1)->val_ull;
} else {
err = 0;
}
+
+ /* Convert GDB error numbers back to host error numbers. */
+#define E(X) case GDB_E##X: err = E##X; break
+ switch (err) {
+ case 0:
+ break;
+ E(PERM);
+ E(NOENT);
+ E(INTR);
+ E(BADF);
+ E(ACCES);
+ E(FAULT);
+ E(BUSY);
+ E(EXIST);
+ E(NODEV);
+ E(NOTDIR);
+ E(ISDIR);
+ E(INVAL);
+ E(NFILE);
+ E(MFILE);
+ E(FBIG);
+ E(NOSPC);
+ E(SPIPE);
+ E(ROFS);
+ E(NAMETOOLONG);
+ default:
+ err = EINVAL;
+ break;
+ }
+#undef E
+
gdbserver_state.current_syscall_cb(gdbserver_state.c_cpu, ret, err);
gdbserver_state.current_syscall_cb = NULL;
}
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 097238faa7..5502aa60c8 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -2010,15 +2010,7 @@ static void machvirt_init(MachineState *machine)
cpuobj = object_new(possible_cpus->cpus[0].type);
armcpu = ARM_CPU(cpuobj);
- if (object_property_get_bool(cpuobj, "aarch64", NULL)) {
- pa_bits = arm_pamax(armcpu);
- } else if (arm_feature(&armcpu->env, ARM_FEATURE_LPAE)) {
- /* v7 with LPAE */
- pa_bits = 40;
- } else {
- /* Anything else */
- pa_bits = 32;
- }
+ pa_bits = arm_pamax(armcpu);
object_unref(cpuobj);
diff --git a/include/exec/gdbstub.h b/include/exec/gdbstub.h
index c35d7334b4..f667014888 100644
--- a/include/exec/gdbstub.h
+++ b/include/exec/gdbstub.h
@@ -10,11 +10,71 @@
#define GDB_WATCHPOINT_READ 3
#define GDB_WATCHPOINT_ACCESS 4
+/* For gdb file i/o remote protocol open flags. */
+#define GDB_O_RDONLY 0
+#define GDB_O_WRONLY 1
+#define GDB_O_RDWR 2
+#define GDB_O_APPEND 8
+#define GDB_O_CREAT 0x200
+#define GDB_O_TRUNC 0x400
+#define GDB_O_EXCL 0x800
+
+/* For gdb file i/o remote protocol errno values */
+#define GDB_EPERM 1
+#define GDB_ENOENT 2
+#define GDB_EINTR 4
+#define GDB_EBADF 9
+#define GDB_EACCES 13
+#define GDB_EFAULT 14
+#define GDB_EBUSY 16
+#define GDB_EEXIST 17
+#define GDB_ENODEV 19
+#define GDB_ENOTDIR 20
+#define GDB_EISDIR 21
+#define GDB_EINVAL 22
+#define GDB_ENFILE 23
+#define GDB_EMFILE 24
+#define GDB_EFBIG 27
+#define GDB_ENOSPC 28
+#define GDB_ESPIPE 29
+#define GDB_EROFS 30
+#define GDB_ENAMETOOLONG 91
+#define GDB_EUNKNOWN 9999
+
+/* For gdb file i/o remote protocol lseek whence. */
+#define GDB_SEEK_SET 0
+#define GDB_SEEK_CUR 1
+#define GDB_SEEK_END 2
+
+/* For gdb file i/o stat/fstat. */
+typedef uint32_t gdb_mode_t;
+typedef uint32_t gdb_time_t;
+
+struct gdb_stat {
+ uint32_t gdb_st_dev; /* device */
+ uint32_t gdb_st_ino; /* inode */
+ gdb_mode_t gdb_st_mode; /* protection */
+ uint32_t gdb_st_nlink; /* number of hard links */
+ uint32_t gdb_st_uid; /* user ID of owner */
+ uint32_t gdb_st_gid; /* group ID of owner */
+ uint32_t gdb_st_rdev; /* device type (if inode device) */
+ uint64_t gdb_st_size; /* total size, in bytes */
+ uint64_t gdb_st_blksize; /* blocksize for filesystem I/O */
+ uint64_t gdb_st_blocks; /* number of blocks allocated */
+ gdb_time_t gdb_st_atime; /* time of last access */
+ gdb_time_t gdb_st_mtime; /* time of last modification */
+ gdb_time_t gdb_st_ctime; /* time of last change */
+} QEMU_PACKED;
+
+struct gdb_timeval {
+ gdb_time_t tv_sec; /* second */
+ uint64_t tv_usec; /* microsecond */
+} QEMU_PACKED;
+
#ifdef NEED_CPU_H
#include "cpu.h"
-typedef void (*gdb_syscall_complete_cb)(CPUState *cpu,
- target_ulong ret, target_ulong err);
+typedef void (*gdb_syscall_complete_cb)(CPUState *cpu, uint64_t ret, int err);
/**
* gdb_do_syscall:
diff --git a/include/exec/softmmu-semi.h b/include/exec/softmmu-semi.h
deleted file mode 100644
index fbcae88f4b..0000000000
--- a/include/exec/softmmu-semi.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Helper routines to provide target memory access for semihosting
- * syscalls in system emulation mode.
- *
- * Copyright (c) 2007 CodeSourcery.
- *
- * This code is licensed under the GPL
- */
-
-#ifndef SOFTMMU_SEMI_H
-#define SOFTMMU_SEMI_H
-
-#include "cpu.h"
-
-static inline uint64_t softmmu_tget64(CPUArchState *env, target_ulong addr)
-{
- uint64_t val;
-
- cpu_memory_rw_debug(env_cpu(env), addr, (uint8_t *)&val, 8, 0);
- return tswap64(val);
-}
-
-static inline uint32_t softmmu_tget32(CPUArchState *env, target_ulong addr)
-{
- uint32_t val;
-
- cpu_memory_rw_debug(env_cpu(env), addr, (uint8_t *)&val, 4, 0);
- return tswap32(val);
-}
-
-static inline uint32_t softmmu_tget8(CPUArchState *env, target_ulong addr)
-{
- uint8_t val;
-
- cpu_memory_rw_debug(env_cpu(env), addr, &val, 1, 0);
- return val;
-}
-
-#define get_user_u64(arg, p) ({ arg = softmmu_tget64(env, p); 0; })
-#define get_user_u32(arg, p) ({ arg = softmmu_tget32(env, p) ; 0; })
-#define get_user_u8(arg, p) ({ arg = softmmu_tget8(env, p) ; 0; })
-#define get_user_ual(arg, p) get_user_u32(arg, p)
-
-static inline void softmmu_tput64(CPUArchState *env,
- target_ulong addr, uint64_t val)
-{
- val = tswap64(val);
- cpu_memory_rw_debug(env_cpu(env), addr, (uint8_t *)&val, 8, 1);
-}
-
-static inline void softmmu_tput32(CPUArchState *env,
- target_ulong addr, uint32_t val)
-{
- val = tswap32(val);
- cpu_memory_rw_debug(env_cpu(env), addr, (uint8_t *)&val, 4, 1);
-}
-#define put_user_u64(arg, p) ({ softmmu_tput64(env, p, arg) ; 0; })
-#define put_user_u32(arg, p) ({ softmmu_tput32(env, p, arg) ; 0; })
-#define put_user_ual(arg, p) put_user_u32(arg, p)
-
-static void *softmmu_lock_user(CPUArchState *env,
- target_ulong addr, target_ulong len, int copy)
-{
- uint8_t *p;
- /* TODO: Make this something that isn't fixed size. */
- p = malloc(len);
- if (p && copy) {
- cpu_memory_rw_debug(env_cpu(env), addr, p, len, 0);
- }
- return p;
-}
-#define lock_user(type, p, len, copy) softmmu_lock_user(env, p, len, copy)
-static char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr)
-{
- char *p;
- char *s;
- uint8_t c;
- /* TODO: Make this something that isn't fixed size. */
- s = p = malloc(1024);
- if (!s) {
- return NULL;
- }
- do {
- cpu_memory_rw_debug(env_cpu(env), addr, &c, 1, 0);
- addr++;
- *(p++) = c;
- } while (c);
- return s;
-}
-#define lock_user_string(p) softmmu_lock_user_string(env, p)
-static void softmmu_unlock_user(CPUArchState *env, void *p, target_ulong addr,
- target_ulong len)
-{
- if (len) {
- cpu_memory_rw_debug(env_cpu(env), addr, p, len, 1);
- }
- free(p);
-}
-#define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len)
-
-#endif
diff --git a/include/qemu/accel.h b/include/qemu/accel.h
index 4f4c283f6f..be56da1b99 100644
--- a/include/qemu/accel.h
+++ b/include/qemu/accel.h
@@ -68,6 +68,7 @@ typedef struct AccelClass {
AccelClass *accel_find(const char *opt_name);
AccelState *current_accel(void);
+const char *current_accel_name(void);
void accel_init_interfaces(AccelClass *ac);
diff --git a/semihosting/common-semi.h b/include/semihosting/common-semi.h
index 0bfab1c669..0a91db7c41 100644
--- a/semihosting/common-semi.h
+++ b/include/semihosting/common-semi.h
@@ -34,6 +34,6 @@
#ifndef COMMON_SEMI_H
#define COMMON_SEMI_H
-target_ulong do_common_semihosting(CPUState *cs);
+void do_common_semihosting(CPUState *cs);
#endif /* COMMON_SEMI_H */
diff --git a/include/semihosting/console.h b/include/semihosting/console.h
index 0238f540f4..61b0cb3a94 100644
--- a/include/semihosting/console.h
+++ b/include/semihosting/console.h
@@ -12,46 +12,33 @@
#include "cpu.h"
/**
- * qemu_semihosting_console_outs:
- * @env: CPUArchState
- * @s: host address of null terminated guest string
+ * qemu_semihosting_console_read:
+ * @cs: CPUState
+ * @buf: host buffer
+ * @len: buffer size
*
- * Send a null terminated guest string to the debug console. This may
- * be the remote gdb session if a softmmu guest is currently being
- * debugged.
+ * Receive at least one character from debug console. As this call may
+ * block if no data is available we suspend the CPU and will re-execute the
+ * instruction when data is there. Therefore two conditions must be met:
*
- * Returns: number of bytes written.
- */
-int qemu_semihosting_console_outs(CPUArchState *env, target_ulong s);
-
-/**
- * qemu_semihosting_console_outc:
- * @env: CPUArchState
- * @s: host address of null terminated guest string
- *
- * Send single character from guest memory to the debug console. This
- * may be the remote gdb session if a softmmu guest is currently being
- * debugged.
+ * - CPUState is synchronized before calling this function
+ * - pc is only updated once the character is successfully returned
*
- * Returns: nothing
+ * Returns: number of characters read, OR cpu_loop_exit!
*/
-void qemu_semihosting_console_outc(CPUArchState *env, target_ulong c);
+int qemu_semihosting_console_read(CPUState *cs, void *buf, int len);
/**
- * qemu_semihosting_console_inc:
- * @env: CPUArchState
+ * qemu_semihosting_console_write:
+ * @buf: host buffer
+ * @len: buffer size
*
- * Receive single character from debug console. This may be the remote
- * gdb session if a softmmu guest is currently being debugged. As this
- * call may block if no data is available we suspend the CPU and will
- * re-execute the instruction when data is there. Therefore two
- * conditions must be met:
- * - CPUState is synchronized before calling this function
- * - pc is only updated once the character is successfully returned
+ * Write len bytes from buf to the debug console.
*
- * Returns: character read OR cpu_loop_exit!
+ * Returns: number of bytes written -- this should only ever be short
+ * on some sort of i/o error.
*/
-target_ulong qemu_semihosting_console_inc(CPUArchState *env);
+int qemu_semihosting_console_write(void *buf, int len);
/**
* qemu_semihosting_log_out:
@@ -66,4 +53,20 @@ target_ulong qemu_semihosting_console_inc(CPUArchState *env);
*/
int qemu_semihosting_log_out(const char *s, int len);
+/*
+ * qemu_semihosting_console_block_until_ready:
+ * @cs: CPUState
+ *
+ * If no data is available we suspend the CPU and will re-execute the
+ * instruction when data is available.
+ */
+void qemu_semihosting_console_block_until_ready(CPUState *cs);
+
+/**
+ * qemu_semihosting_console_ready:
+ *
+ * Return true if characters are available for read; does not block.
+ */
+bool qemu_semihosting_console_ready(void);
+
#endif /* SEMIHOST_CONSOLE_H */
diff --git a/include/semihosting/guestfd.h b/include/semihosting/guestfd.h
new file mode 100644
index 0000000000..3d426fedab
--- /dev/null
+++ b/include/semihosting/guestfd.h
@@ -0,0 +1,91 @@
+/*
+ * Hosted file support for semihosting syscalls.
+ *
+ * Copyright (c) 2005, 2007 CodeSourcery.
+ * Copyright (c) 2019 Linaro
+ * Copyright © 2020 by Keith Packard <keithp@keithp.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SEMIHOSTING_GUESTFD_H
+#define SEMIHOSTING_GUESTFD_H
+
+typedef enum GuestFDType {
+ GuestFDUnused = 0,
+ GuestFDHost,
+ GuestFDGDB,
+ GuestFDStatic,
+ GuestFDConsole,
+} 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;
+ struct {
+ const uint8_t *data;
+ size_t len;
+ size_t off;
+ } staticfile;
+ };
+} GuestFD;
+
+/*
+ * For ARM semihosting, we have a separate structure for routing
+ * data for the console which is outside the guest fd address space.
+ */
+extern GuestFD console_in_gf;
+extern GuestFD console_out_gf;
+
+/**
+ * alloc_guestfd:
+ *
+ * Allocate an unused GuestFD index. The associated guestfd index
+ * will still be GuestFDUnused until it is initialized.
+ */
+int alloc_guestfd(void);
+
+/**
+ * dealloc_guestfd:
+ * @guestfd: GuestFD index
+ *
+ * Deallocate a GuestFD index. The associated GuestFD structure
+ * will be recycled for a subsequent allocation.
+ */
+void dealloc_guestfd(int guestfd);
+
+/**
+ * get_guestfd:
+ * @guestfd: GuestFD index
+ *
+ * Return the GuestFD structure associated with an initialized @guestfd,
+ * or NULL if it has not been allocated, or hasn't been initialized.
+ */
+GuestFD *get_guestfd(int guestfd);
+
+/**
+ * associate_guestfd:
+ * @guestfd: GuestFD index
+ * @hostfd: host file descriptor
+ *
+ * Initialize the GuestFD for @guestfd to GuestFDHost using @hostfd.
+ */
+void associate_guestfd(int guestfd, int hostfd);
+
+/**
+ * staticfile_guestfd:
+ * @guestfd: GuestFD index
+ * @data: data to be read
+ * @len: length of @data
+ *
+ * Initialize the GuestFD for @guestfd to GuestFDStatic.
+ * The @len bytes at @data will be returned to the guest on reads.
+ */
+void staticfile_guestfd(int guestfd, const uint8_t *data, size_t len);
+
+#endif /* SEMIHOSTING_GUESTFD_H */
diff --git a/include/semihosting/semihost.h b/include/semihosting/semihost.h
index 0c55ade3ac..93a3c21b44 100644
--- a/include/semihosting/semihost.h
+++ b/include/semihosting/semihost.h
@@ -51,14 +51,6 @@ static inline const char *semihosting_get_cmdline(void)
{
return NULL;
}
-
-static inline Chardev *semihosting_get_chardev(void)
-{
- return NULL;
-}
-static inline void qemu_semihosting_console_init(void)
-{
-}
#else /* !CONFIG_USER_ONLY */
bool semihosting_enabled(void);
SemihostingTarget semihosting_get_target(void);
@@ -66,12 +58,12 @@ const char *semihosting_get_arg(int i);
int semihosting_get_argc(void);
const char *semihosting_get_cmdline(void);
void semihosting_arg_fallback(const char *file, const char *cmd);
-Chardev *semihosting_get_chardev(void);
/* for vl.c hooks */
void qemu_semihosting_enable(void);
int qemu_semihosting_config_options(const char *opt);
-void qemu_semihosting_connect_chardevs(void);
-void qemu_semihosting_console_init(void);
+void qemu_semihosting_chardev_init(void);
+void qemu_semihosting_console_init(Chardev *);
#endif /* CONFIG_USER_ONLY */
+void qemu_semihosting_guestfd_init(void);
#endif /* SEMIHOST_H */
diff --git a/include/semihosting/softmmu-uaccess.h b/include/semihosting/softmmu-uaccess.h
new file mode 100644
index 0000000000..4f08dfc098
--- /dev/null
+++ b/include/semihosting/softmmu-uaccess.h
@@ -0,0 +1,59 @@
+/*
+ * Helper routines to provide target memory access for semihosting
+ * syscalls in system emulation mode.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+
+#ifndef SEMIHOSTING_SOFTMMU_UACCESS_H
+#define SEMIHOSTING_SOFTMMU_UACCESS_H
+
+#include "cpu.h"
+
+#define get_user_u64(val, addr) \
+ ({ uint64_t val_ = 0; \
+ int ret_ = cpu_memory_rw_debug(env_cpu(env), (addr), \
+ &val_, sizeof(val_), 0); \
+ (val) = tswap64(val_); ret_; })
+
+#define get_user_u32(val, addr) \
+ ({ uint32_t val_ = 0; \
+ int ret_ = cpu_memory_rw_debug(env_cpu(env), (addr), \
+ &val_, sizeof(val_), 0); \
+ (val) = tswap32(val_); ret_; })
+
+#define get_user_u8(val, addr) \
+ ({ uint8_t val_ = 0; \
+ int ret_ = cpu_memory_rw_debug(env_cpu(env), (addr), \
+ &val_, sizeof(val_), 0); \
+ (val) = val_; ret_; })
+
+#define get_user_ual(arg, p) get_user_u32(arg, p)
+
+#define put_user_u64(val, addr) \
+ ({ uint64_t val_ = tswap64(val); \
+ cpu_memory_rw_debug(env_cpu(env), (addr), &val_, sizeof(val_), 1); })
+
+#define put_user_u32(val, addr) \
+ ({ uint32_t val_ = tswap32(val); \
+ cpu_memory_rw_debug(env_cpu(env), (addr), &val_, sizeof(val_), 1); })
+
+#define put_user_ual(arg, p) put_user_u32(arg, p)
+
+void *softmmu_lock_user(CPUArchState *env, target_ulong addr,
+ target_ulong len, bool copy);
+#define lock_user(type, p, len, copy) softmmu_lock_user(env, p, len, copy)
+
+char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr);
+#define lock_user_string(p) softmmu_lock_user_string(env, p)
+
+void softmmu_unlock_user(CPUArchState *env, void *p,
+ target_ulong addr, target_ulong len);
+#define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len)
+
+ssize_t softmmu_strlen_user(CPUArchState *env, target_ulong addr);
+#define target_strlen(p) softmmu_strlen_user(env, p)
+
+#endif /* SEMIHOSTING_SOFTMMU_UACCESS_H */
diff --git a/include/semihosting/syscalls.h b/include/semihosting/syscalls.h
new file mode 100644
index 0000000000..3a5ec229eb
--- /dev/null
+++ b/include/semihosting/syscalls.h
@@ -0,0 +1,75 @@
+/*
+ * Syscall implementations for semihosting.
+ *
+ * Copyright (c) 2022 Linaro
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SEMIHOSTING_SYSCALLS_H
+#define SEMIHOSTING_SYSCALLS_H
+
+/*
+ * Argument loading from the guest is performed by the caller;
+ * results are returned via the 'complete' callback.
+ *
+ * String operands are in address/len pairs. The len argument may be 0
+ * (when the semihosting abi does not already provide the length),
+ * or non-zero (where it should include the terminating zero).
+ */
+
+typedef struct GuestFD GuestFD;
+
+void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ int gdb_flags, int mode);
+
+void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd);
+
+void semihost_sys_read(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, target_ulong buf, target_ulong len);
+
+void semihost_sys_read_gf(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len);
+
+void semihost_sys_write(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, target_ulong buf, target_ulong len);
+
+void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len);
+
+void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, int64_t off, int gdb_whence);
+
+void semihost_sys_isatty(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd);
+
+void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb,
+ gdb_syscall_complete_cb flen_cb,
+ int fd, target_ulong fstat_addr);
+
+void semihost_sys_fstat(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, target_ulong addr);
+
+void semihost_sys_stat(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ target_ulong addr);
+
+void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len);
+
+void semihost_sys_rename(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong oname, target_ulong oname_len,
+ target_ulong nname, target_ulong nname_len);
+
+void semihost_sys_system(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong cmd, target_ulong cmd_len);
+
+void semihost_sys_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong tv_addr, target_ulong tz_addr);
+
+void semihost_sys_poll_one(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, GIOCondition cond, int timeout);
+
+#endif /* SEMIHOSTING_SYSCALLS_H */
diff --git a/linux-user/aarch64/cpu_loop.c b/linux-user/aarch64/cpu_loop.c
index 3b273f6299..f7ef36cd9f 100644
--- a/linux-user/aarch64/cpu_loop.c
+++ b/linux-user/aarch64/cpu_loop.c
@@ -154,7 +154,7 @@ void cpu_loop(CPUARMState *env)
force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->pc);
break;
case EXCP_SEMIHOST:
- env->xregs[0] = do_common_semihosting(cs);
+ do_common_semihosting(cs);
env->pc += 4;
break;
case EXCP_YIELD:
diff --git a/linux-user/arm/cpu_loop.c b/linux-user/arm/cpu_loop.c
index d950409d5b..c0790f3246 100644
--- a/linux-user/arm/cpu_loop.c
+++ b/linux-user/arm/cpu_loop.c
@@ -449,7 +449,7 @@ void cpu_loop(CPUARMState *env)
}
break;
case EXCP_SEMIHOST:
- env->regs[0] = do_common_semihosting(cs);
+ do_common_semihosting(cs);
env->regs[15] += env->thumb ? 2 : 4;
break;
case EXCP_INTERRUPT:
diff --git a/linux-user/m68k/cpu_loop.c b/linux-user/m68k/cpu_loop.c
index 3d3033155f..caead1cb74 100644
--- a/linux-user/m68k/cpu_loop.c
+++ b/linux-user/m68k/cpu_loop.c
@@ -36,11 +36,6 @@ void cpu_loop(CPUM68KState *env)
process_queued_cpu_work(cs);
switch(trapnr) {
- case EXCP_HALT_INSN:
- /* Semihosing syscall. */
- env->pc += 4;
- do_m68k_semihosting(env, env->dregs[0]);
- break;
case EXCP_ILLEGAL:
case EXCP_LINEA:
case EXCP_LINEF:
diff --git a/linux-user/main.c b/linux-user/main.c
index 651e32f5f2..e44bdb17b8 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -54,6 +54,10 @@
#include "loader.h"
#include "user-mmap.h"
+#ifdef CONFIG_SEMIHOSTING
+#include "semihosting/semihost.h"
+#endif
+
#ifndef AT_FLAGS_PRESERVE_ARGV0
#define AT_FLAGS_PRESERVE_ARGV0_BIT 0
#define AT_FLAGS_PRESERVE_ARGV0 (1 << AT_FLAGS_PRESERVE_ARGV0_BIT)
@@ -906,6 +910,11 @@ int main(int argc, char **argv, char **envp)
}
gdb_handlesig(cpu, 0);
}
+
+#ifdef CONFIG_SEMIHOSTING
+ qemu_semihosting_guestfd_init();
+#endif
+
cpu_loop(env);
/* never exits */
return 0;
diff --git a/linux-user/riscv/cpu_loop.c b/linux-user/riscv/cpu_loop.c
index 29084c1421..bffca7db12 100644
--- a/linux-user/riscv/cpu_loop.c
+++ b/linux-user/riscv/cpu_loop.c
@@ -81,7 +81,7 @@ void cpu_loop(CPURISCVState *env)
force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->pc);
break;
case RISCV_EXCP_SEMIHOST:
- env->gpr[xA0] = do_common_semihosting(cs);
+ do_common_semihosting(cs);
env->pc += 4;
break;
default:
diff --git a/linux-user/semihost.c b/linux-user/semihost.c
index 17f074ac56..cee62a365c 100644
--- a/linux-user/semihost.c
+++ b/linux-user/semihost.c
@@ -16,39 +16,6 @@
#include "user-internals.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
@@ -56,21 +23,28 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr)
* 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)
+int qemu_semihosting_console_read(CPUState *cs, void *buf, int len)
{
- uint8_t c;
+ int ret;
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);
+ new_tio.c_cc[VMIN] = 1;
+ new_tio.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
- c = getchar();
+ ret = fread(buf, 1, len, stdin);
/* restore config */
tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
- return (target_ulong) c;
+ return ret;
+}
+
+int qemu_semihosting_console_write(void *buf, int len)
+{
+ return fwrite(buf, 1, len, stderr);
}
diff --git a/semihosting/arm-compat-semi.c b/semihosting/arm-compat-semi.c
index b6ddaf863a..1a1e2a6960 100644
--- a/semihosting/arm-compat-semi.c
+++ b/semihosting/arm-compat-semi.c
@@ -32,12 +32,14 @@
*/
#include "qemu/osdep.h"
-
+#include "qemu/timer.h"
+#include "exec/gdbstub.h"
#include "semihosting/semihost.h"
#include "semihosting/console.h"
#include "semihosting/common-semi.h"
-#include "qemu/timer.h"
-#include "exec/gdbstub.h"
+#include "semihosting/guestfd.h"
+#include "semihosting/syscalls.h"
+
#ifdef CONFIG_USER_ONLY
#include "qemu.h"
@@ -45,9 +47,6 @@
#else
#include "qemu/cutils.h"
#include "hw/loader.h"
-#ifdef TARGET_ARM
-#include "hw/arm/boot.h"
-#endif
#include "hw/boards.h"
#endif
@@ -85,65 +84,21 @@
#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_RDONLY,
GDB_O_RDWR,
- GDB_O_RDWR | GDB_O_BINARY,
+ GDB_O_RDWR,
+ GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC,
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_RDWR | GDB_O_CREAT | GDB_O_TRUNC,
+ GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND,
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
+ GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND,
};
-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;
-
#ifndef CONFIG_USER_ONLY
/**
@@ -210,155 +165,31 @@ static LayoutInfo common_semi_find_bases(CPUState *cs)
#endif
-#ifdef TARGET_ARM
-static inline target_ulong
-common_semi_arg(CPUState *cs, int argno)
-{
- ARMCPU *cpu = ARM_CPU(cs);
- CPUARMState *env = &cpu->env;
- if (is_a64(env)) {
- return env->xregs[argno];
- } else {
- return env->regs[argno];
- }
-}
-
-static inline void
-common_semi_set_ret(CPUState *cs, target_ulong ret)
-{
- ARMCPU *cpu = ARM_CPU(cs);
- CPUARMState *env = &cpu->env;
- if (is_a64(env)) {
- env->xregs[0] = ret;
- } else {
- env->regs[0] = ret;
- }
-}
-
-static inline bool
-common_semi_sys_exit_extended(CPUState *cs, int nr)
-{
- return (nr == TARGET_SYS_EXIT_EXTENDED || is_a64(cs->env_ptr));
-}
-
-#endif /* TARGET_ARM */
-
-#ifdef TARGET_RISCV
-static inline target_ulong
-common_semi_arg(CPUState *cs, int argno)
-{
- RISCVCPU *cpu = RISCV_CPU(cs);
- CPURISCVState *env = &cpu->env;
- return env->gpr[xA0 + argno];
-}
-
-static inline void
-common_semi_set_ret(CPUState *cs, target_ulong ret)
-{
- RISCVCPU *cpu = RISCV_CPU(cs);
- CPURISCVState *env = &cpu->env;
- env->gpr[xA0] = ret;
-}
-
-static inline bool
-common_semi_sys_exit_extended(CPUState *cs, int nr)
-{
- return (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 8);
-}
-
-#endif
-
-/*
- * 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));
- }
-
- /* SYS_OPEN should return nonzero handle on success. Start guestfd from 1 */
- for (i = 1; 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;
-}
+#include "common-semi-target.h"
/*
- * Deallocate the specified guest file descriptor. This doesn't
- * close the host fd, it merely undoes the work of alloc_fd().
+ * Read the input value from the argument block; fail the semihosting
+ * call if the memory read fails. Eventually we could use a generic
+ * CPUState helper function here.
*/
-static void dealloc_guestfd(int guestfd)
-{
- GuestFD *gf = do_get_guestfd(guestfd);
- assert(gf);
- gf->type = GuestFDUnused;
-}
+#define GET_ARG(n) do { \
+ if (is_64bit_semihosting(env)) { \
+ if (get_user_u64(arg ## n, args + (n) * 8)) { \
+ goto do_fault; \
+ } \
+ } else { \
+ if (get_user_u32(arg ## n, args + (n) * 4)) { \
+ goto do_fault; \
+ } \
+ } \
+} while (0)
-/*
- * 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);
+#define SET_ARG(n, val) \
+ (is_64bit_semihosting(env) ? \
+ put_user_u64(val, args + (n) * 8) : \
+ put_user_u32(val, args + (n) * 4))
- if (!gf || gf->type == GuestFDUnused) {
- return NULL;
- }
- return gf;
-}
/*
* The semihosting API has no concept of its errno being thread-safe,
@@ -370,22 +201,8 @@ static GuestFD *get_guestfd(int guestfd)
#ifndef CONFIG_USER_ONLY
static target_ulong syscall_err;
-#include "exec/softmmu-semi.h"
-#endif
-
-static inline uint32_t set_swi_errno(CPUState *cs, uint32_t code)
-{
- if (code == (uint32_t)-1) {
-#ifdef CONFIG_USER_ONLY
- TaskState *ts = cs->opaque;
-
- ts->swi_errno = errno;
-#else
- syscall_err = errno;
+#include "semihosting/softmmu-uaccess.h"
#endif
- }
- return code;
-}
static inline uint32_t get_swi_errno(CPUState *cs)
{
@@ -398,254 +215,116 @@ static inline uint32_t get_swi_errno(CPUState *cs)
#endif
}
-static target_ulong common_semi_syscall_len;
-
-static void common_semi_cb(CPUState *cs, target_ulong ret, target_ulong err)
-{
- target_ulong reg0 = common_semi_arg(cs, 0);
-
- if (ret == (target_ulong)-1) {
- errno = err;
- set_swi_errno(cs, -1);
- reg0 = ret;
- } else {
- /* Fixup syscalls that use nonstardard return conventions. */
- switch (reg0) {
- case TARGET_SYS_WRITE:
- case TARGET_SYS_READ:
- reg0 = common_semi_syscall_len - ret;
- break;
- case TARGET_SYS_SEEK:
- reg0 = 0;
- break;
- default:
- reg0 = ret;
- break;
- }
- }
- common_semi_set_ret(cs, reg0);
-}
-
-static target_ulong common_semi_flen_buf(CPUState *cs)
+static void common_semi_cb(CPUState *cs, uint64_t ret, int err)
{
- target_ulong sp;
-#ifdef TARGET_ARM
- /* 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.
- */
- ARMCPU *cpu = ARM_CPU(cs);
- CPUARMState *env = &cpu->env;
-
- if (is_a64(env)) {
- sp = env->xregs[31];
- } else {
- sp = env->regs[13];
- }
-#endif
-#ifdef TARGET_RISCV
- RISCVCPU *cpu = RISCV_CPU(cs);
- CPURISCVState *env = &cpu->env;
-
- sp = env->gpr[xSP];
+ if (err) {
+#ifdef CONFIG_USER_ONLY
+ TaskState *ts = cs->opaque;
+ ts->swi_errno = err;
+#else
+ syscall_err = err;
#endif
-
- return sp - 64;
-}
-
-static void
-common_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err)
-{
- /* 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, common_semi_flen_buf(cs) + 32,
- (uint8_t *)&size, 4, 0);
- size = be32_to_cpu(size);
- common_semi_set_ret(cs, size);
- errno = err;
- set_swi_errno(cs, -1);
-}
-
-static int common_semi_open_guestfd;
-
-static void
-common_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong err)
-{
- if (ret == (target_ulong)-1) {
- errno = err;
- set_swi_errno(cs, -1);
- dealloc_guestfd(common_semi_open_guestfd);
- } else {
- associate_guestfd(common_semi_open_guestfd, ret);
- ret = common_semi_open_guestfd;
}
common_semi_set_ret(cs, ret);
}
-static target_ulong
-common_semi_gdb_syscall(CPUState *cs, gdb_syscall_complete_cb cb,
- const char *fmt, ...)
+/*
+ * Use 0xdeadbeef as the return value when there isn't a defined
+ * return value for the call.
+ */
+static void common_semi_dead_cb(CPUState *cs, uint64_t ret, int err)
{
- va_list va;
-
- 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_common_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_common_semihosting() return a value, so the mistake of
- * doing something with the return value is not possible to make.
- */
-
- return common_semi_arg(cs, 0);
+ common_semi_set_ret(cs, 0xdeadbeef);
}
/*
- * 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.
+ * SYS_READ and SYS_WRITE always return the number of bytes not read/written.
+ * There is no error condition, other than returning the original length.
*/
-typedef uint32_t sys_closefn(CPUState *cs, GuestFD *gf);
-typedef uint32_t sys_writefn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len);
-typedef uint32_t sys_readfn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len);
-typedef uint32_t sys_isattyfn(CPUState *cs, GuestFD *gf);
-typedef uint32_t sys_seekfn(CPUState *cs, GuestFD *gf,
- target_ulong offset);
-typedef uint32_t sys_flenfn(CPUState *cs, GuestFD *gf);
-
-static uint32_t host_closefn(CPUState *cs, GuestFD *gf)
+static void common_semi_rw_cb(CPUState *cs, uint64_t ret, int err)
{
- /*
- * Only close the underlying host fd if it's one we opened on behalf
- * of the guest in SYS_OPEN.
- */
- if (gf->hostfd == STDIN_FILENO ||
- gf->hostfd == STDOUT_FILENO ||
- gf->hostfd == STDERR_FILENO) {
- return 0;
+ /* Recover the original length from the third argument. */
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ target_ulong args = common_semi_arg(cs, 1);
+ target_ulong arg2;
+ GET_ARG(2);
+
+ if (err) {
+ do_fault:
+ ret = 0; /* error: no bytes transmitted */
}
- return set_swi_errno(cs, close(gf->hostfd));
+ common_semi_set_ret(cs, arg2 - ret);
}
-static uint32_t host_writefn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len)
+/*
+ * Convert from Posix ret+errno to Arm SYS_ISTTY return values.
+ * With gdbstub, err is only ever set for protocol errors to EIO.
+ */
+static void common_semi_istty_cb(CPUState *cs, uint64_t ret, int err)
{
- CPUArchState *env = cs->env_ptr;
- uint32_t ret;
- char *s = lock_user(VERIFY_READ, buf, len, 1);
- (void) env; /* Used in arm softmmu lock_user implicitly */
- if (!s) {
- /* Return bytes not written on error */
- return len;
+ if (err) {
+ ret = (err == ENOTTY ? 0 : -1);
}
- ret = set_swi_errno(cs, write(gf->hostfd, s, len));
- unlock_user(s, buf, 0);
- if (ret == (uint32_t)-1) {
- ret = 0;
- }
- /* Return bytes not written */
- return len - ret;
+ common_semi_cb(cs, ret, err);
}
-static uint32_t host_readfn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len)
+/*
+ * SYS_SEEK returns 0 on success, not the resulting offset.
+ */
+static void common_semi_seek_cb(CPUState *cs, uint64_t ret, int err)
{
- CPUArchState *env = cs->env_ptr;
- uint32_t ret;
- char *s = lock_user(VERIFY_WRITE, buf, len, 0);
- (void) env; /* Used in arm softmmu lock_user implicitly */
- if (!s) {
- /* return bytes not read */
- return len;
- }
- do {
- ret = set_swi_errno(cs, read(gf->hostfd, s, len));
- } while (ret == -1 && errno == EINTR);
- unlock_user(s, buf, len);
- if (ret == (uint32_t)-1) {
+ if (!err) {
ret = 0;
}
- /* Return bytes not read */
- return len - ret;
-}
-
-static uint32_t host_isattyfn(CPUState *cs, GuestFD *gf)
-{
- return isatty(gf->hostfd);
+ common_semi_cb(cs, ret, err);
}
-static uint32_t host_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset)
+/*
+ * 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.
+ */
+static target_ulong common_semi_flen_buf(CPUState *cs)
{
- uint32_t ret = set_swi_errno(cs, lseek(gf->hostfd, offset, SEEK_SET));
- if (ret == (uint32_t)-1) {
- return -1;
- }
- return 0;
+ target_ulong sp = common_semi_stack_bottom(cs);
+ return sp - 64;
}
-static uint32_t host_flenfn(CPUState *cs, GuestFD *gf)
-{
- struct stat buf;
- uint32_t ret = set_swi_errno(cs, fstat(gf->hostfd, &buf));
- if (ret == (uint32_t)-1) {
- return -1;
+static void
+common_semi_flen_fstat_cb(CPUState *cs, uint64_t ret, int err)
+{
+ if (!err) {
+ /* The size is always stored in big-endian order, extract the value. */
+ uint64_t size;
+ if (cpu_memory_rw_debug(cs, common_semi_flen_buf(cs) +
+ offsetof(struct gdb_stat, gdb_st_size),
+ &size, 8, 0)) {
+ ret = -1, err = EFAULT;
+ } else {
+ size = be64_to_cpu(size);
+ if (ret != size) {
+ ret = -1, err = EOVERFLOW;
+ }
+ }
}
- return buf.st_size;
-}
-
-static uint32_t gdb_closefn(CPUState *cs, GuestFD *gf)
-{
- return common_semi_gdb_syscall(cs, common_semi_cb, "close,%x", gf->hostfd);
-}
-
-static uint32_t gdb_writefn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len)
-{
- common_semi_syscall_len = len;
- return common_semi_gdb_syscall(cs, common_semi_cb, "write,%x,%x,%x",
- gf->hostfd, buf, len);
-}
-
-static uint32_t gdb_readfn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len)
-{
- common_semi_syscall_len = len;
- return common_semi_gdb_syscall(cs, common_semi_cb, "read,%x,%x,%x",
- gf->hostfd, buf, len);
-}
-
-static uint32_t gdb_isattyfn(CPUState *cs, GuestFD *gf)
-{
- return common_semi_gdb_syscall(cs, common_semi_cb, "isatty,%x", gf->hostfd);
+ common_semi_cb(cs, ret, err);
}
-static uint32_t gdb_seekfn(CPUState *cs, GuestFD *gf, target_ulong offset)
+static void
+common_semi_readc_cb(CPUState *cs, uint64_t ret, int err)
{
- return common_semi_gdb_syscall(cs, common_semi_cb, "lseek,%x,%x,0",
- gf->hostfd, offset);
-}
+ if (!err) {
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ uint8_t ch;
-static uint32_t gdb_flenfn(CPUState *cs, GuestFD *gf)
-{
- return common_semi_gdb_syscall(cs, common_semi_flen_cb, "fstat,%x,%x",
- gf->hostfd, common_semi_flen_buf(cs));
+ if (get_user_u8(ch, common_semi_stack_bottom(cs) - 1)) {
+ ret = -1, err = EFAULT;
+ } else {
+ ret = ch;
+ }
+ }
+ common_semi_cb(cs, ret, err);
}
#define SHFB_MAGIC_0 0x53
@@ -665,157 +344,15 @@ static const uint8_t featurefile_data[] = {
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(CPUState *cs, GuestFD *gf)
-{
- /* Nothing to do */
- return 0;
-}
-
-static uint32_t featurefile_writefn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len)
-{
- /* This fd can never be open for writing */
-
- errno = EBADF;
- return set_swi_errno(cs, -1);
-}
-
-static uint32_t featurefile_readfn(CPUState *cs, GuestFD *gf,
- target_ulong buf, uint32_t len)
-{
- CPUArchState *env = cs->env_ptr;
- uint32_t i;
- char *s;
-
- (void) env; /* Used in arm softmmu lock_user implicitly */
- 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(CPUState *cs, GuestFD *gf)
-{
- return 0;
-}
-
-static uint32_t featurefile_seekfn(CPUState *cs, GuestFD *gf,
- target_ulong offset)
-{
- gf->featurefile_offset = offset;
- return 0;
-}
-
-static uint32_t featurefile_flenfn(CPUState *cs, 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. Eventually we could use a generic
- * CPUState helper function here.
- */
-static inline bool is_64bit_semihosting(CPUArchState *env)
-{
-#if defined(TARGET_ARM)
- return is_a64(env);
-#elif defined(TARGET_RISCV)
- return riscv_cpu_mxl(env) != MXL_RV32;
-#else
-#error un-handled architecture
-#endif
-}
-
-
-#define GET_ARG(n) do { \
- if (is_64bit_semihosting(env)) { \
- if (get_user_u64(arg ## n, args + (n) * 8)) { \
- errno = EFAULT; \
- return set_swi_errno(cs, -1); \
- } \
- } else { \
- if (get_user_u32(arg ## n, args + (n) * 4)) { \
- errno = EFAULT; \
- return set_swi_errno(cs, -1); \
- } \
- } \
-} while (0)
-
-#define SET_ARG(n, val) \
- (is_64bit_semihosting(env) ? \
- put_user_u64(val, args + (n) * 8) : \
- put_user_u32(val, args + (n) * 4))
-
-
/*
* 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.
+ * leave the register unchanged.
*/
-target_ulong do_common_semihosting(CPUState *cs)
+void do_common_semihosting(CPUState *cs)
{
CPUArchState *env = cs->env_ptr;
target_ulong args;
@@ -824,43 +361,31 @@ target_ulong do_common_semihosting(CPUState *cs)
char * s;
int nr;
uint32_t ret;
- uint32_t len;
- GuestFD *gf;
int64_t elapsed;
- (void) env; /* Used implicitly by arm lock_user macro */
nr = common_semi_arg(cs, 0) & 0xffffffffU;
args = common_semi_arg(cs, 1);
switch (nr) {
case TARGET_SYS_OPEN:
{
- int guestfd;
+ int ret, err = 0;
+ int hostfd;
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
s = lock_user_string(arg0);
if (!s) {
- errno = EFAULT;
- return set_swi_errno(cs, -1);
+ goto do_fault;
}
if (arg1 >= 12) {
unlock_user(s, arg0, 0);
- errno = EINVAL;
- return set_swi_errno(cs, -1);
- }
-
- guestfd = alloc_guestfd();
- if (guestfd < 0) {
- unlock_user(s, arg0, 0);
- errno = EMFILE;
- return set_swi_errno(cs, -1);
+ common_semi_cb(cs, -1, EINVAL);
+ break;
}
if (strcmp(s, ":tt") == 0) {
- int result_fileno;
-
/*
* We implement SH_EXT_STDOUT_STDERR, so:
* open for read == stdin
@@ -868,206 +393,160 @@ target_ulong do_common_semihosting(CPUState *cs)
* open for append == stderr
*/
if (arg1 < 4) {
- result_fileno = STDIN_FILENO;
+ hostfd = STDIN_FILENO;
} else if (arg1 < 8) {
- result_fileno = STDOUT_FILENO;
+ hostfd = STDOUT_FILENO;
} else {
- result_fileno = STDERR_FILENO;
+ hostfd = 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);
+ ret = alloc_guestfd();
+ associate_guestfd(ret, hostfd);
+ } else if (strcmp(s, ":semihosting-features") == 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(cs, -1);
- }
- init_featurefile_guestfd(guestfd);
- return guestfd;
- }
-
- if (use_gdb_syscalls()) {
- common_semi_open_guestfd = guestfd;
- ret = common_semi_gdb_syscall(cs, common_semi_open_cb,
- "open,%s,%x,1a4", arg0, (int)arg2 + 1,
- gdb_open_modeflags[arg1]);
- } else {
- ret = set_swi_errno(cs, open(s, open_modeflags[arg1], 0644));
- if (ret == (uint32_t)-1) {
- dealloc_guestfd(guestfd);
+ ret = -1;
+ err = EACCES;
} else {
- associate_guestfd(guestfd, ret);
- ret = guestfd;
+ ret = alloc_guestfd();
+ staticfile_guestfd(ret, featurefile_data,
+ sizeof(featurefile_data));
}
+ } else {
+ unlock_user(s, arg0, 0);
+ semihost_sys_open(cs, common_semi_cb, arg0, arg2 + 1,
+ gdb_open_modeflags[arg1], 0644);
+ break;
}
unlock_user(s, arg0, 0);
- return ret;
+ common_semi_cb(cs, ret, err);
+ break;
}
+
case TARGET_SYS_CLOSE:
GET_ARG(0);
+ semihost_sys_close(cs, common_semi_cb, arg0);
+ break;
- gf = get_guestfd(arg0);
- if (!gf) {
- errno = EBADF;
- return set_swi_errno(cs, -1);
- }
-
- ret = guestfd_fns[gf->type].closefn(cs, gf);
- dealloc_guestfd(arg0);
- return ret;
case TARGET_SYS_WRITEC:
- qemu_semihosting_console_outc(cs->env_ptr, args);
- return 0xdeadbeef;
+ /*
+ * FIXME: the byte to be written is in a target_ulong slot,
+ * which means this is wrong for a big-endian guest.
+ */
+ semihost_sys_write_gf(cs, common_semi_dead_cb,
+ &console_out_gf, args, 1);
+ break;
+
case TARGET_SYS_WRITE0:
- return qemu_semihosting_console_outs(cs->env_ptr, args);
+ {
+ ssize_t len = target_strlen(args);
+ if (len < 0) {
+ common_semi_dead_cb(cs, -1, EFAULT);
+ } else {
+ semihost_sys_write_gf(cs, common_semi_dead_cb,
+ &console_out_gf, args, len);
+ }
+ }
+ break;
+
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(cs, -1);
- }
+ semihost_sys_write(cs, common_semi_rw_cb, arg0, arg1, arg2);
+ break;
- return guestfd_fns[gf->type].writefn(cs, 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(cs, -1);
- }
+ semihost_sys_read(cs, common_semi_rw_cb, arg0, arg1, arg2);
+ break;
- return guestfd_fns[gf->type].readfn(cs, gf, arg1, len);
case TARGET_SYS_READC:
- return qemu_semihosting_console_inc(cs->env_ptr);
+ semihost_sys_read_gf(cs, common_semi_readc_cb, &console_in_gf,
+ common_semi_stack_bottom(cs) - 1, 1);
+ break;
+
case TARGET_SYS_ISERROR:
GET_ARG(0);
- return (target_long) arg0 < 0 ? 1 : 0;
+ common_semi_set_ret(cs, (target_long)arg0 < 0);
+ break;
+
case TARGET_SYS_ISTTY:
GET_ARG(0);
+ semihost_sys_isatty(cs, common_semi_istty_cb, arg0);
+ break;
- gf = get_guestfd(arg0);
- if (!gf) {
- errno = EBADF;
- return set_swi_errno(cs, -1);
- }
-
- return guestfd_fns[gf->type].isattyfn(cs, gf);
case TARGET_SYS_SEEK:
GET_ARG(0);
GET_ARG(1);
+ semihost_sys_lseek(cs, common_semi_seek_cb, arg0, arg1, GDB_SEEK_SET);
+ break;
- gf = get_guestfd(arg0);
- if (!gf) {
- errno = EBADF;
- return set_swi_errno(cs, -1);
- }
-
- return guestfd_fns[gf->type].seekfn(cs, gf, arg1);
case TARGET_SYS_FLEN:
GET_ARG(0);
+ semihost_sys_flen(cs, common_semi_flen_fstat_cb, common_semi_cb,
+ arg0, common_semi_flen_buf(cs));
+ break;
- gf = get_guestfd(arg0);
- if (!gf) {
- errno = EBADF;
- return set_swi_errno(cs, -1);
- }
-
- return guestfd_fns[gf->type].flenfn(cs, gf);
case TARGET_SYS_TMPNAM:
+ {
+ int len;
+ char *p;
+
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
- if (asprintf(&s, "/tmp/qemu-%x%02x", getpid(),
- (int) (arg1 & 0xff)) < 0) {
- return -1;
- }
- ul_ret = (target_ulong) -1;
-
+ len = asprintf(&s, "/tmp/qemu-%x%02x", getpid(), (int)arg1 & 0xff);
/* Make sure there's enough space in the buffer */
- if (strlen(s) < arg2) {
- char *output = lock_user(VERIFY_WRITE, arg0, arg2, 0);
- strcpy(output, s);
- unlock_user(output, arg0, arg2);
- ul_ret = 0;
+ if (len < 0 || len >= arg2) {
+ common_semi_set_ret(cs, -1);
+ break;
}
+ p = lock_user(VERIFY_WRITE, arg0, len, 0);
+ if (!p) {
+ goto do_fault;
+ }
+ memcpy(p, s, len + 1);
+ unlock_user(p, arg0, len);
free(s);
- return ul_ret;
+ common_semi_set_ret(cs, 0);
+ break;
+ }
+
case TARGET_SYS_REMOVE:
GET_ARG(0);
GET_ARG(1);
- if (use_gdb_syscalls()) {
- ret = common_semi_gdb_syscall(cs, common_semi_cb, "unlink,%s",
- arg0, (int)arg1 + 1);
- } else {
- s = lock_user_string(arg0);
- if (!s) {
- errno = EFAULT;
- return set_swi_errno(cs, -1);
- }
- ret = set_swi_errno(cs, remove(s));
- unlock_user(s, arg0, 0);
- }
- return ret;
+ semihost_sys_remove(cs, common_semi_cb, arg0, arg1 + 1);
+ break;
+
case TARGET_SYS_RENAME:
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
GET_ARG(3);
- if (use_gdb_syscalls()) {
- return common_semi_gdb_syscall(cs, common_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(cs, -1);
- } else {
- ret = set_swi_errno(cs, rename(s, s2));
- }
- if (s2)
- unlock_user(s2, arg2, 0);
- if (s)
- unlock_user(s, arg0, 0);
- return ret;
- }
+ semihost_sys_rename(cs, common_semi_cb, arg0, arg1 + 1, arg2, arg3 + 1);
+ break;
+
case TARGET_SYS_CLOCK:
- return clock() / (CLOCKS_PER_SEC / 100);
+ common_semi_set_ret(cs, clock() / (CLOCKS_PER_SEC / 100));
+ break;
+
case TARGET_SYS_TIME:
- return set_swi_errno(cs, time(NULL));
+ ul_ret = time(NULL);
+ common_semi_cb(cs, ul_ret, ul_ret == -1 ? errno : 0);
+ break;
+
case TARGET_SYS_SYSTEM:
GET_ARG(0);
GET_ARG(1);
- if (use_gdb_syscalls()) {
- return common_semi_gdb_syscall(cs, common_semi_cb, "system,%s",
- arg0, (int)arg1 + 1);
- } else {
- s = lock_user_string(arg0);
- if (!s) {
- errno = EFAULT;
- return set_swi_errno(cs, -1);
- }
- ret = set_swi_errno(cs, system(s));
- unlock_user(s, arg0, 0);
- return ret;
- }
+ semihost_sys_system(cs, common_semi_cb, arg0, arg1 + 1);
+ break;
+
case TARGET_SYS_ERRNO:
- return get_swi_errno(cs);
+ common_semi_set_ret(cs, get_swi_errno(cs));
+ break;
+
case TARGET_SYS_GET_CMDLINE:
{
/* Build a command-line from the original argv.
@@ -1118,22 +597,20 @@ target_ulong do_common_semihosting(CPUState *cs)
if (output_size > input_size) {
/* Not enough space to store command-line arguments. */
- errno = E2BIG;
- return set_swi_errno(cs, -1);
+ common_semi_cb(cs, -1, E2BIG);
+ break;
}
/* 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(cs, -1);
+ goto do_fault;
}
/* Lock the buffer on the ARM side. */
output_buffer = lock_user(VERIFY_WRITE, arg0, output_size, 0);
if (!output_buffer) {
- errno = EFAULT;
- return set_swi_errno(cs, -1);
+ goto do_fault;
}
/* Copy the command-line arguments. */
@@ -1148,9 +625,8 @@ target_ulong do_common_semihosting(CPUState *cs)
if (copy_from_user(output_buffer, ts->info->arg_strings,
output_size)) {
- errno = EFAULT;
- status = set_swi_errno(cs, -1);
- goto out;
+ unlock_user(output_buffer, arg0, 0);
+ goto do_fault;
}
/* Separate arguments by white spaces. */
@@ -1163,9 +639,10 @@ target_ulong do_common_semihosting(CPUState *cs)
#endif
/* Unlock the buffer on the ARM side. */
unlock_user(output_buffer, arg0, output_size);
-
- return status;
+ common_semi_cb(cs, status, 0);
}
+ break;
+
case TARGET_SYS_HEAPINFO:
{
target_ulong retvals[4];
@@ -1222,12 +699,13 @@ target_ulong do_common_semihosting(CPUState *cs)
if (fail) {
/* Couldn't write back to argument block */
- errno = EFAULT;
- return set_swi_errno(cs, -1);
+ goto do_fault;
}
}
- return 0;
+ common_semi_set_ret(cs, 0);
}
+ break;
+
case TARGET_SYS_EXIT:
case TARGET_SYS_EXIT_EXTENDED:
if (common_semi_sys_exit_extended(cs, nr)) {
@@ -1257,6 +735,7 @@ target_ulong do_common_semihosting(CPUState *cs)
}
gdb_exit(ret);
exit(ret);
+
case TARGET_SYS_ELAPSED:
elapsed = get_clock() - clock_start;
if (sizeof(target_ulong) == 8) {
@@ -1265,28 +744,32 @@ target_ulong do_common_semihosting(CPUState *cs)
SET_ARG(0, (uint32_t) elapsed);
SET_ARG(1, (uint32_t) (elapsed >> 32));
}
- return 0;
+ common_semi_set_ret(cs, 0);
+ break;
+
case TARGET_SYS_TICKFREQ:
/* qemu always uses nsec */
- return 1000000000;
+ common_semi_set_ret(cs, 1000000000);
+ break;
+
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.
*/
-#ifdef TARGET_ARM
- if (is_a64(cs->env_ptr)) {
- return 0;
+ if (common_semi_has_synccache(env)) {
+ common_semi_set_ret(cs, 0);
+ break;
}
-#endif
-#ifdef TARGET_RISCV
- return 0;
-#endif
- /* fall through -- invalid for A32/T32 */
+ /* fall through */
default:
fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr);
cpu_dump_state(cs, stderr, 0);
abort();
+
+ do_fault:
+ common_semi_cb(cs, -1, EFAULT);
+ break;
}
}
diff --git a/semihosting/config.c b/semihosting/config.c
index 3afacf54ab..e171d4d6bc 100644
--- a/semihosting/config.c
+++ b/semihosting/config.c
@@ -51,7 +51,6 @@ QemuOptsList qemu_semihosting_config_opts = {
typedef struct SemihostingConfig {
bool enabled;
SemihostingTarget target;
- Chardev *chardev;
char **argv;
int argc;
const char *cmdline; /* concatenated argv */
@@ -122,11 +121,6 @@ void semihosting_arg_fallback(const char *file, const char *cmd)
}
}
-Chardev *semihosting_get_chardev(void)
-{
- return semihosting.chardev;
-}
-
void qemu_semihosting_enable(void)
{
semihosting.enabled = true;
@@ -172,16 +166,19 @@ int qemu_semihosting_config_options(const char *optarg)
return 0;
}
-void qemu_semihosting_connect_chardevs(void)
+/* We had to defer this until chardevs were created */
+void qemu_semihosting_chardev_init(void)
{
- /* We had to defer this until chardevs were created */
+ Chardev *chr = NULL;
+
if (semihost_chardev) {
- Chardev *chr = qemu_chr_find(semihost_chardev);
+ chr = qemu_chr_find(semihost_chardev);
if (chr == NULL) {
error_report("semihosting chardev '%s' not found",
semihost_chardev);
exit(1);
}
- semihosting.chardev = chr;
}
+
+ qemu_semihosting_console_init(chr);
}
diff --git a/semihosting/console.c b/semihosting/console.c
index ef6958d844..cda7cf1905 100644
--- a/semihosting/console.c
+++ b/semihosting/console.c
@@ -27,89 +27,10 @@
#include "qapi/error.h"
#include "qemu/fifo8.h"
-int qemu_semihosting_log_out(const char *s, int len)
-{
- Chardev *chardev = semihosting_get_chardev();
- if (chardev) {
- return qemu_chr_write_all(chardev, (uint8_t *) s, len);
- } else {
- return write(STDERR_FILENO, s, len);
- }
-}
-
-/*
- * A re-implementation of lock_user_string that we can use locally
- * instead of relying on softmmu-semi. Hopefully we can deprecate that
- * in time. Copy string until we find a 0 or address error.
- */
-static GString *copy_user_string(CPUArchState *env, target_ulong addr)
-{
- CPUState *cpu = env_cpu(env);
- GString *s = g_string_sized_new(128);
- uint8_t c;
-
- do {
- if (cpu_memory_rw_debug(cpu, addr++, &c, 1, 0) == 0) {
- if (c) {
- s = g_string_append_c(s, c);
- }
- } else {
- qemu_log_mask(LOG_GUEST_ERROR,
- "%s: passed inaccessible address " TARGET_FMT_lx,
- __func__, addr);
- break;
- }
- } while (c!=0);
-
- return s;
-}
-
-static void semihosting_cb(CPUState *cs, target_ulong ret, target_ulong err)
-{
- if (ret == (target_ulong) -1) {
- qemu_log("%s: gdb console output failed ("TARGET_FMT_ld")",
- __func__, err);
- }
-}
-
-int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr)
-{
- GString *s = copy_user_string(env, addr);
- int out = s->len;
-
- if (use_gdb_syscalls()) {
- gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, s->len);
- } else {
- out = qemu_semihosting_log_out(s->str, s->len);
- }
-
- g_string_free(s, true);
- return out;
-}
-
-void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr)
-{
- CPUState *cpu = env_cpu(env);
- uint8_t c;
-
- if (cpu_memory_rw_debug(cpu, addr, &c, 1, 0) == 0) {
- if (use_gdb_syscalls()) {
- gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, 1);
- } else {
- qemu_semihosting_log_out((const char *) &c, 1);
- }
- } else {
- qemu_log_mask(LOG_GUEST_ERROR,
- "%s: passed inaccessible address " TARGET_FMT_lx,
- __func__, addr);
- }
-}
-
-#define FIFO_SIZE 1024
-
/* Access to this structure is protected by the BQL */
typedef struct SemihostingConsole {
CharBackend backend;
+ Chardev *chr;
GSList *sleeping_cpus;
bool got;
Fifo8 fifo;
@@ -117,6 +38,17 @@ typedef struct SemihostingConsole {
static SemihostingConsole console;
+int qemu_semihosting_log_out(const char *s, int len)
+{
+ if (console.chr) {
+ return qemu_chr_write_all(console.chr, (uint8_t *) s, len);
+ } else {
+ return write(STDERR_FILENO, s, len);
+ }
+}
+
+#define FIFO_SIZE 1024
+
static int console_can_read(void *opaque)
{
SemihostingConsole *c = opaque;
@@ -145,27 +77,58 @@ static void console_read(void *opaque, const uint8_t *buf, int size)
c->sleeping_cpus = NULL;
}
-target_ulong qemu_semihosting_console_inc(CPUArchState *env)
+bool qemu_semihosting_console_ready(void)
+{
+ SemihostingConsole *c = &console;
+
+ g_assert(qemu_mutex_iothread_locked());
+ return !fifo8_is_empty(&c->fifo);
+}
+
+void qemu_semihosting_console_block_until_ready(CPUState *cs)
{
- uint8_t ch;
SemihostingConsole *c = &console;
+
g_assert(qemu_mutex_iothread_locked());
- g_assert(current_cpu);
+
+ /* Block if the fifo is completely empty. */
if (fifo8_is_empty(&c->fifo)) {
- c->sleeping_cpus = g_slist_prepend(c->sleeping_cpus, current_cpu);
- current_cpu->halted = 1;
- current_cpu->exception_index = EXCP_HALTED;
- cpu_loop_exit(current_cpu);
+ c->sleeping_cpus = g_slist_prepend(c->sleeping_cpus, cs);
+ cs->halted = 1;
+ cs->exception_index = EXCP_HALTED;
+ cpu_loop_exit(cs);
/* never returns */
}
- ch = fifo8_pop(&c->fifo);
- return (target_ulong) ch;
}
-void qemu_semihosting_console_init(void)
+int qemu_semihosting_console_read(CPUState *cs, void *buf, int len)
{
- Chardev *chr = semihosting_get_chardev();
+ SemihostingConsole *c = &console;
+ int ret = 0;
+
+ qemu_semihosting_console_block_until_ready(cs);
+
+ /* Read until buffer full or fifo exhausted. */
+ do {
+ *(char *)(buf + ret) = fifo8_pop(&c->fifo);
+ ret++;
+ } while (ret < len && !fifo8_is_empty(&c->fifo));
+
+ return ret;
+}
+int qemu_semihosting_console_write(void *buf, int len)
+{
+ if (console.chr) {
+ return qemu_chr_write_all(console.chr, (uint8_t *)buf, len);
+ } else {
+ return fwrite(buf, 1, len, stderr);
+ }
+}
+
+void qemu_semihosting_console_init(Chardev *chr)
+{
+ console.chr = chr;
if (chr) {
fifo8_create(&console.fifo, FIFO_SIZE);
qemu_chr_fe_init(&console.backend, chr, &error_abort);
@@ -175,4 +138,6 @@ void qemu_semihosting_console_init(void)
NULL, NULL, &console,
NULL, true);
}
+
+ qemu_semihosting_guestfd_init();
}
diff --git a/semihosting/guestfd.c b/semihosting/guestfd.c
new file mode 100644
index 0000000000..b05c52f26f
--- /dev/null
+++ b/semihosting/guestfd.c
@@ -0,0 +1,160 @@
+/*
+ * Hosted file support for semihosting syscalls.
+ *
+ * Copyright (c) 2005, 2007 CodeSourcery.
+ * Copyright (c) 2019 Linaro
+ * Copyright © 2020 by Keith Packard <keithp@keithp.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "exec/gdbstub.h"
+#include "semihosting/semihost.h"
+#include "semihosting/guestfd.h"
+#ifdef CONFIG_USER_ONLY
+#include "qemu.h"
+#else
+#include "semihosting/softmmu-uaccess.h"
+#include CONFIG_DEVICES
+#endif
+
+static GArray *guestfd_array;
+
+#ifdef CONFIG_ARM_COMPATIBLE_SEMIHOSTING
+GuestFD console_in_gf;
+GuestFD console_out_gf;
+#endif
+
+void qemu_semihosting_guestfd_init(void)
+{
+ /* New entries zero-initialized, i.e. type GuestFDUnused */
+ guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD));
+
+#ifdef CONFIG_ARM_COMPATIBLE_SEMIHOSTING
+ /* For ARM-compat, the console is in a separate namespace. */
+ if (use_gdb_syscalls()) {
+ console_in_gf.type = GuestFDGDB;
+ console_in_gf.hostfd = 0;
+ console_out_gf.type = GuestFDGDB;
+ console_out_gf.hostfd = 2;
+ } else {
+ console_in_gf.type = GuestFDConsole;
+ console_out_gf.type = GuestFDConsole;
+ }
+#else
+ /* Otherwise, the stdio file descriptors apply. */
+ guestfd_array = g_array_set_size(guestfd_array, 3);
+#ifndef CONFIG_USER_ONLY
+ if (!use_gdb_syscalls()) {
+ GuestFD *gf = &g_array_index(guestfd_array, GuestFD, 0);
+ gf[0].type = GuestFDConsole;
+ gf[1].type = GuestFDConsole;
+ gf[2].type = GuestFDConsole;
+ return;
+ }
+#endif
+ associate_guestfd(0, 0);
+ associate_guestfd(1, 1);
+ associate_guestfd(2, 2);
+#endif
+}
+
+/*
+ * 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.
+ */
+int alloc_guestfd(void)
+{
+ guint i;
+
+ /* SYS_OPEN should return nonzero handle on success. Start guestfd from 1 */
+ for (i = 1; 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;
+}
+
+static void do_dealloc_guestfd(GuestFD *gf)
+{
+ gf->type = GuestFDUnused;
+}
+
+/*
+ * 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 < 0 || guestfd >= guestfd_array->len) {
+ return NULL;
+ }
+
+ return &g_array_index(guestfd_array, GuestFD, guestfd);
+}
+
+/*
+ * 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.
+ */
+GuestFD *get_guestfd(int guestfd)
+{
+ GuestFD *gf = do_get_guestfd(guestfd);
+
+ if (!gf || gf->type == GuestFDUnused) {
+ return NULL;
+ }
+ return gf;
+}
+
+/*
+ * Associate the specified guest fd (which must have been
+ * allocated via alloc_fd() and not previously used) with
+ * the specified host/gdb fd.
+ */
+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;
+}
+
+void staticfile_guestfd(int guestfd, const uint8_t *data, size_t len)
+{
+ GuestFD *gf = do_get_guestfd(guestfd);
+
+ assert(gf);
+ gf->type = GuestFDStatic;
+ gf->staticfile.data = data;
+ gf->staticfile.len = len;
+ gf->staticfile.off = 0;
+}
+
+/*
+ * Deallocate the specified guest file descriptor. This doesn't
+ * close the host fd, it merely undoes the work of alloc_fd().
+ */
+void dealloc_guestfd(int guestfd)
+{
+ GuestFD *gf = do_get_guestfd(guestfd);
+
+ assert(gf);
+ do_dealloc_guestfd(gf);
+}
diff --git a/semihosting/meson.build b/semihosting/meson.build
index ea8090abe3..8057db5494 100644
--- a/semihosting/meson.build
+++ b/semihosting/meson.build
@@ -1,6 +1,12 @@
specific_ss.add(when: 'CONFIG_SEMIHOSTING', if_true: files(
+ 'guestfd.c',
+ 'syscalls.c',
+))
+
+specific_ss.add(when: ['CONFIG_SEMIHOSTING', 'CONFIG_SOFTMMU'], if_true: files(
'config.c',
'console.c',
+ 'uaccess.c',
))
specific_ss.add(when: ['CONFIG_ARM_COMPATIBLE_SEMIHOSTING'],
diff --git a/semihosting/syscalls.c b/semihosting/syscalls.c
new file mode 100644
index 0000000000..4847f66c02
--- /dev/null
+++ b/semihosting/syscalls.c
@@ -0,0 +1,978 @@
+/*
+ * Syscall implementations for semihosting.
+ *
+ * Copyright (c) 2022 Linaro
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "exec/gdbstub.h"
+#include "semihosting/guestfd.h"
+#include "semihosting/syscalls.h"
+#include "semihosting/console.h"
+#ifdef CONFIG_USER_ONLY
+#include "qemu.h"
+#else
+#include "semihosting/softmmu-uaccess.h"
+#endif
+
+
+/*
+ * Validate or compute the length of the string (including terminator).
+ */
+static int validate_strlen(CPUState *cs, target_ulong str, target_ulong tlen)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char c;
+
+ if (tlen == 0) {
+ ssize_t slen = target_strlen(str);
+
+ if (slen < 0) {
+ return -EFAULT;
+ }
+ if (slen >= INT32_MAX) {
+ return -ENAMETOOLONG;
+ }
+ return slen + 1;
+ }
+ if (tlen > INT32_MAX) {
+ return -ENAMETOOLONG;
+ }
+ if (get_user_u8(c, str + tlen - 1)) {
+ return -EFAULT;
+ }
+ if (c != 0) {
+ return -EINVAL;
+ }
+ return tlen;
+}
+
+static int validate_lock_user_string(char **pstr, CPUState *cs,
+ target_ulong tstr, target_ulong tlen)
+{
+ int ret = validate_strlen(cs, tstr, tlen);
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char *str = NULL;
+
+ if (ret > 0) {
+ str = lock_user(VERIFY_READ, tstr, ret, true);
+ ret = str ? 0 : -EFAULT;
+ }
+ *pstr = str;
+ return ret;
+}
+
+/*
+ * TODO: Note that gdb always stores the stat structure big-endian.
+ * So far, that's ok, as the only two targets using this are also
+ * big-endian. Until we do something with gdb, also produce the
+ * same big-endian result from the host.
+ */
+static int copy_stat_to_user(CPUState *cs, target_ulong addr,
+ const struct stat *s)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ struct gdb_stat *p;
+
+ if (s->st_dev != (uint32_t)s->st_dev ||
+ s->st_ino != (uint32_t)s->st_ino) {
+ return -EOVERFLOW;
+ }
+
+ p = lock_user(VERIFY_WRITE, addr, sizeof(struct gdb_stat), 0);
+ if (!p) {
+ return -EFAULT;
+ }
+
+ p->gdb_st_dev = cpu_to_be32(s->st_dev);
+ p->gdb_st_ino = cpu_to_be32(s->st_ino);
+ p->gdb_st_mode = cpu_to_be32(s->st_mode);
+ p->gdb_st_nlink = cpu_to_be32(s->st_nlink);
+ p->gdb_st_uid = cpu_to_be32(s->st_uid);
+ p->gdb_st_gid = cpu_to_be32(s->st_gid);
+ p->gdb_st_rdev = cpu_to_be32(s->st_rdev);
+ p->gdb_st_size = cpu_to_be64(s->st_size);
+#ifdef _WIN32
+ /* Windows stat is missing some fields. */
+ p->gdb_st_blksize = 0;
+ p->gdb_st_blocks = 0;
+#else
+ p->gdb_st_blksize = cpu_to_be64(s->st_blksize);
+ p->gdb_st_blocks = cpu_to_be64(s->st_blocks);
+#endif
+ p->gdb_st_atime = cpu_to_be32(s->st_atime);
+ p->gdb_st_mtime = cpu_to_be32(s->st_mtime);
+ p->gdb_st_ctime = cpu_to_be32(s->st_ctime);
+
+ unlock_user(p, addr, sizeof(struct gdb_stat));
+ return 0;
+}
+
+/*
+ * GDB semihosting syscall implementations.
+ */
+
+static gdb_syscall_complete_cb gdb_open_complete;
+
+static void gdb_open_cb(CPUState *cs, uint64_t ret, int err)
+{
+ if (!err) {
+ int guestfd = alloc_guestfd();
+ associate_guestfd(guestfd, ret);
+ ret = guestfd;
+ }
+ gdb_open_complete(cs, ret, err);
+}
+
+static void gdb_open(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ int gdb_flags, int mode)
+{
+ int len = validate_strlen(cs, fname, fname_len);
+ if (len < 0) {
+ complete(cs, -1, -len);
+ return;
+ }
+
+ gdb_open_complete = complete;
+ gdb_do_syscall(gdb_open_cb, "open,%s,%x,%x",
+ fname, len, (target_ulong)gdb_flags, (target_ulong)mode);
+}
+
+static void gdb_close(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf)
+{
+ gdb_do_syscall(complete, "close,%x", (target_ulong)gf->hostfd);
+}
+
+static void gdb_read(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ gdb_do_syscall(complete, "read,%x,%x,%x",
+ (target_ulong)gf->hostfd, buf, len);
+}
+
+static void gdb_write(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ gdb_do_syscall(complete, "write,%x,%x,%x",
+ (target_ulong)gf->hostfd, buf, len);
+}
+
+static void gdb_lseek(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, int64_t off, int gdb_whence)
+{
+ gdb_do_syscall(complete, "lseek,%x,%lx,%x",
+ (target_ulong)gf->hostfd, off, (target_ulong)gdb_whence);
+}
+
+static void gdb_isatty(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf)
+{
+ gdb_do_syscall(complete, "isatty,%x", (target_ulong)gf->hostfd);
+}
+
+static void gdb_fstat(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong addr)
+{
+ gdb_do_syscall(complete, "fstat,%x,%x", (target_ulong)gf->hostfd, addr);
+}
+
+static void gdb_stat(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ target_ulong addr)
+{
+ int len = validate_strlen(cs, fname, fname_len);
+ if (len < 0) {
+ complete(cs, -1, -len);
+ return;
+ }
+
+ gdb_do_syscall(complete, "stat,%s,%x", fname, len, addr);
+}
+
+static void gdb_remove(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len)
+{
+ int len = validate_strlen(cs, fname, fname_len);
+ if (len < 0) {
+ complete(cs, -1, -len);
+ return;
+ }
+
+ gdb_do_syscall(complete, "unlink,%s", fname, len);
+}
+
+static void gdb_rename(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong oname, target_ulong oname_len,
+ target_ulong nname, target_ulong nname_len)
+{
+ int olen, nlen;
+
+ olen = validate_strlen(cs, oname, oname_len);
+ if (olen < 0) {
+ complete(cs, -1, -olen);
+ return;
+ }
+ nlen = validate_strlen(cs, nname, nname_len);
+ if (nlen < 0) {
+ complete(cs, -1, -nlen);
+ return;
+ }
+
+ gdb_do_syscall(complete, "rename,%s,%s", oname, olen, nname, nlen);
+}
+
+static void gdb_system(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong cmd, target_ulong cmd_len)
+{
+ int len = validate_strlen(cs, cmd, cmd_len);
+ if (len < 0) {
+ complete(cs, -1, -len);
+ return;
+ }
+
+ gdb_do_syscall(complete, "system,%s", cmd, len);
+}
+
+static void gdb_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong tv_addr, target_ulong tz_addr)
+{
+ gdb_do_syscall(complete, "gettimeofday,%x,%x", tv_addr, tz_addr);
+}
+
+/*
+ * Host semihosting syscall implementations.
+ */
+
+static void host_open(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ int gdb_flags, int mode)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char *p;
+ int ret, host_flags;
+
+ ret = validate_lock_user_string(&p, cs, fname, fname_len);
+ if (ret < 0) {
+ complete(cs, -1, -ret);
+ return;
+ }
+
+ if (gdb_flags & GDB_O_WRONLY) {
+ host_flags = O_WRONLY;
+ } else if (gdb_flags & GDB_O_RDWR) {
+ host_flags = O_RDWR;
+ } else {
+ host_flags = O_RDONLY;
+ }
+ if (gdb_flags & GDB_O_CREAT) {
+ host_flags |= O_CREAT;
+ }
+ if (gdb_flags & GDB_O_TRUNC) {
+ host_flags |= O_TRUNC;
+ }
+ if (gdb_flags & GDB_O_EXCL) {
+ host_flags |= O_EXCL;
+ }
+
+ ret = open(p, host_flags, mode);
+ if (ret < 0) {
+ complete(cs, -1, errno);
+ } else {
+ int guestfd = alloc_guestfd();
+ associate_guestfd(guestfd, ret);
+ complete(cs, guestfd, 0);
+ }
+ unlock_user(p, fname, 0);
+}
+
+static void host_close(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf)
+{
+ /*
+ * Only close the underlying host fd if it's one we opened on behalf
+ * of the guest in SYS_OPEN.
+ */
+ if (gf->hostfd != STDIN_FILENO &&
+ gf->hostfd != STDOUT_FILENO &&
+ gf->hostfd != STDERR_FILENO &&
+ close(gf->hostfd) < 0) {
+ complete(cs, -1, errno);
+ } else {
+ complete(cs, 0, 0);
+ }
+}
+
+static void host_read(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ void *ptr = lock_user(VERIFY_WRITE, buf, len, 0);
+ ssize_t ret;
+
+ if (!ptr) {
+ complete(cs, -1, EFAULT);
+ return;
+ }
+ do {
+ ret = read(gf->hostfd, ptr, len);
+ } while (ret == -1 && errno == EINTR);
+ if (ret == -1) {
+ complete(cs, -1, errno);
+ unlock_user(ptr, buf, 0);
+ } else {
+ complete(cs, ret, 0);
+ unlock_user(ptr, buf, ret);
+ }
+}
+
+static void host_write(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ void *ptr = lock_user(VERIFY_READ, buf, len, 1);
+ ssize_t ret;
+
+ if (!ptr) {
+ complete(cs, -1, EFAULT);
+ return;
+ }
+ ret = write(gf->hostfd, ptr, len);
+ complete(cs, ret, ret == -1 ? errno : 0);
+ unlock_user(ptr, buf, 0);
+}
+
+static void host_lseek(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, int64_t off, int whence)
+{
+ /* So far, all hosts use the same values. */
+ QEMU_BUILD_BUG_ON(GDB_SEEK_SET != SEEK_SET);
+ QEMU_BUILD_BUG_ON(GDB_SEEK_CUR != SEEK_CUR);
+ QEMU_BUILD_BUG_ON(GDB_SEEK_END != SEEK_END);
+
+ off_t ret = off;
+ int err = 0;
+
+ if (ret == off) {
+ ret = lseek(gf->hostfd, ret, whence);
+ if (ret == -1) {
+ err = errno;
+ }
+ } else {
+ ret = -1;
+ err = EINVAL;
+ }
+ complete(cs, ret, err);
+}
+
+static void host_isatty(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf)
+{
+ int ret = isatty(gf->hostfd);
+ complete(cs, ret, ret ? 0 : errno);
+}
+
+static void host_flen(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf)
+{
+ struct stat buf;
+
+ if (fstat(gf->hostfd, &buf) < 0) {
+ complete(cs, -1, errno);
+ } else {
+ complete(cs, buf.st_size, 0);
+ }
+}
+
+static void host_fstat(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong addr)
+{
+ struct stat buf;
+ int ret;
+
+ ret = fstat(gf->hostfd, &buf);
+ if (ret) {
+ complete(cs, -1, errno);
+ return;
+ }
+ ret = copy_stat_to_user(cs, addr, &buf);
+ complete(cs, ret ? -1 : 0, ret ? -ret : 0);
+}
+
+static void host_stat(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ target_ulong addr)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ struct stat buf;
+ char *name;
+ int ret, err;
+
+ ret = validate_lock_user_string(&name, cs, fname, fname_len);
+ if (ret < 0) {
+ complete(cs, -1, -ret);
+ return;
+ }
+
+ ret = stat(name, &buf);
+ if (ret) {
+ err = errno;
+ } else {
+ ret = copy_stat_to_user(cs, addr, &buf);
+ err = 0;
+ if (ret < 0) {
+ err = -ret;
+ ret = -1;
+ }
+ }
+ complete(cs, ret, err);
+ unlock_user(name, fname, 0);
+}
+
+static void host_remove(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char *p;
+ int ret;
+
+ ret = validate_lock_user_string(&p, cs, fname, fname_len);
+ if (ret < 0) {
+ complete(cs, -1, -ret);
+ return;
+ }
+
+ ret = remove(p);
+ complete(cs, ret, ret ? errno : 0);
+ unlock_user(p, fname, 0);
+}
+
+static void host_rename(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong oname, target_ulong oname_len,
+ target_ulong nname, target_ulong nname_len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char *ostr, *nstr;
+ int ret;
+
+ ret = validate_lock_user_string(&ostr, cs, oname, oname_len);
+ if (ret < 0) {
+ complete(cs, -1, -ret);
+ return;
+ }
+ ret = validate_lock_user_string(&nstr, cs, nname, nname_len);
+ if (ret < 0) {
+ unlock_user(ostr, oname, 0);
+ complete(cs, -1, -ret);
+ return;
+ }
+
+ ret = rename(ostr, nstr);
+ complete(cs, ret, ret ? errno : 0);
+ unlock_user(ostr, oname, 0);
+ unlock_user(nstr, nname, 0);
+}
+
+static void host_system(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong cmd, target_ulong cmd_len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char *p;
+ int ret;
+
+ ret = validate_lock_user_string(&p, cs, cmd, cmd_len);
+ if (ret < 0) {
+ complete(cs, -1, -ret);
+ return;
+ }
+
+ ret = system(p);
+ complete(cs, ret, ret == -1 ? errno : 0);
+ unlock_user(p, cmd, 0);
+}
+
+static void host_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong tv_addr, target_ulong tz_addr)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ struct gdb_timeval *p;
+ int64_t rt;
+
+ /* GDB fails on non-null TZ, so be consistent. */
+ if (tz_addr != 0) {
+ complete(cs, -1, EINVAL);
+ return;
+ }
+
+ p = lock_user(VERIFY_WRITE, tv_addr, sizeof(struct gdb_timeval), 0);
+ if (!p) {
+ complete(cs, -1, EFAULT);
+ return;
+ }
+
+ /* TODO: Like stat, gdb always produces big-endian results; match it. */
+ rt = g_get_real_time();
+ p->tv_sec = cpu_to_be32(rt / G_USEC_PER_SEC);
+ p->tv_usec = cpu_to_be64(rt % G_USEC_PER_SEC);
+ unlock_user(p, tv_addr, sizeof(struct gdb_timeval));
+}
+
+#ifndef CONFIG_USER_ONLY
+static void host_poll_one(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, GIOCondition cond, int timeout)
+{
+ /*
+ * Since this is only used by xtensa in system mode, and stdio is
+ * handled through GuestFDConsole, and there are no semihosting
+ * system calls for sockets and the like, that means this descriptor
+ * must be a normal file. Normal files never block and are thus
+ * always ready.
+ */
+ complete(cs, cond & (G_IO_IN | G_IO_OUT), 0);
+}
+#endif
+
+/*
+ * Static file semihosting syscall implementations.
+ */
+
+static void staticfile_read(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ target_ulong rest = gf->staticfile.len - gf->staticfile.off;
+ void *ptr;
+
+ if (len > rest) {
+ len = rest;
+ }
+ ptr = lock_user(VERIFY_WRITE, buf, len, 0);
+ if (!ptr) {
+ complete(cs, -1, EFAULT);
+ return;
+ }
+ memcpy(ptr, gf->staticfile.data + gf->staticfile.off, len);
+ gf->staticfile.off += len;
+ complete(cs, len, 0);
+ unlock_user(ptr, buf, len);
+}
+
+static void staticfile_lseek(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, int64_t off, int gdb_whence)
+{
+ int64_t ret;
+
+ switch (gdb_whence) {
+ case GDB_SEEK_SET:
+ ret = off;
+ break;
+ case GDB_SEEK_CUR:
+ ret = gf->staticfile.off + off;
+ break;
+ case GDB_SEEK_END:
+ ret = gf->staticfile.len + off;
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+ if (ret >= 0 && ret <= gf->staticfile.len) {
+ gf->staticfile.off = ret;
+ complete(cs, ret, 0);
+ } else {
+ complete(cs, -1, EINVAL);
+ }
+}
+
+static void staticfile_flen(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf)
+{
+ complete(cs, gf->staticfile.len, 0);
+}
+
+/*
+ * Console semihosting syscall implementations.
+ */
+
+static void console_read(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char *ptr;
+ int ret;
+
+ ptr = lock_user(VERIFY_WRITE, buf, len, 0);
+ if (!ptr) {
+ complete(cs, -1, EFAULT);
+ return;
+ }
+ ret = qemu_semihosting_console_read(cs, ptr, len);
+ complete(cs, ret, 0);
+ unlock_user(ptr, buf, ret);
+}
+
+static void console_write(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ CPUArchState *env G_GNUC_UNUSED = cs->env_ptr;
+ char *ptr = lock_user(VERIFY_READ, buf, len, 1);
+ int ret;
+
+ if (!ptr) {
+ complete(cs, -1, EFAULT);
+ return;
+ }
+ ret = qemu_semihosting_console_write(ptr, len);
+ complete(cs, ret ? ret : -1, ret ? 0 : EIO);
+ unlock_user(ptr, buf, ret);
+}
+
+static void console_fstat(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong addr)
+{
+ static const struct stat tty_buf = {
+ .st_mode = 020666, /* S_IFCHR, ugo+rw */
+ .st_rdev = 5, /* makedev(5, 0) -- linux /dev/tty */
+ };
+ int ret;
+
+ ret = copy_stat_to_user(cs, addr, &tty_buf);
+ complete(cs, ret ? -1 : 0, ret ? -ret : 0);
+}
+
+#ifndef CONFIG_USER_ONLY
+static void console_poll_one(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, GIOCondition cond, int timeout)
+{
+ /* The semihosting console does not support urgent data or errors. */
+ cond &= G_IO_IN | G_IO_OUT;
+
+ /*
+ * Since qemu_semihosting_console_write never blocks, we can
+ * consider output always ready -- leave G_IO_OUT alone.
+ * All that remains is to conditionally signal input ready.
+ * Since output ready causes an immediate return, only block
+ * for G_IO_IN alone.
+ *
+ * TODO: Implement proper timeout. For now, only support
+ * indefinite wait or immediate poll.
+ */
+ if (cond == G_IO_IN && timeout < 0) {
+ qemu_semihosting_console_block_until_ready(cs);
+ /* We returned -- input must be ready. */
+ } else if ((cond & G_IO_IN) && !qemu_semihosting_console_ready()) {
+ cond &= ~G_IO_IN;
+ }
+
+ complete(cs, cond, 0);
+}
+#endif
+
+/*
+ * Syscall entry points.
+ */
+
+void semihost_sys_open(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ int gdb_flags, int mode)
+{
+ if (use_gdb_syscalls()) {
+ gdb_open(cs, complete, fname, fname_len, gdb_flags, mode);
+ } else {
+ host_open(cs, complete, fname, fname_len, gdb_flags, mode);
+ }
+}
+
+void semihost_sys_close(CPUState *cs, gdb_syscall_complete_cb complete, int fd)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (!gf) {
+ complete(cs, -1, EBADF);
+ return;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ gdb_close(cs, complete, gf);
+ break;
+ case GuestFDHost:
+ host_close(cs, complete, gf);
+ break;
+ case GuestFDStatic:
+ case GuestFDConsole:
+ complete(cs, 0, 0);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ dealloc_guestfd(fd);
+}
+
+void semihost_sys_read_gf(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ /*
+ * Bound length for 64-bit guests on 32-bit hosts, not overlowing ssize_t.
+ * Note the Linux kernel does this with MAX_RW_COUNT, so it's not a bad
+ * idea to do this unconditionally.
+ */
+ if (len > INT32_MAX) {
+ len = INT32_MAX;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ gdb_read(cs, complete, gf, buf, len);
+ break;
+ case GuestFDHost:
+ host_read(cs, complete, gf, buf, len);
+ break;
+ case GuestFDStatic:
+ staticfile_read(cs, complete, gf, buf, len);
+ break;
+ case GuestFDConsole:
+ console_read(cs, complete, gf, buf, len);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void semihost_sys_read(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, target_ulong buf, target_ulong len)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (gf) {
+ semihost_sys_read_gf(cs, complete, gf, buf, len);
+ } else {
+ complete(cs, -1, EBADF);
+ }
+}
+
+void semihost_sys_write_gf(CPUState *cs, gdb_syscall_complete_cb complete,
+ GuestFD *gf, target_ulong buf, target_ulong len)
+{
+ /*
+ * Bound length for 64-bit guests on 32-bit hosts, not overlowing ssize_t.
+ * Note the Linux kernel does this with MAX_RW_COUNT, so it's not a bad
+ * idea to do this unconditionally.
+ */
+ if (len > INT32_MAX) {
+ len = INT32_MAX;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ gdb_write(cs, complete, gf, buf, len);
+ break;
+ case GuestFDHost:
+ host_write(cs, complete, gf, buf, len);
+ break;
+ case GuestFDConsole:
+ console_write(cs, complete, gf, buf, len);
+ break;
+ case GuestFDStatic:
+ /* Static files are never open for writing: EBADF. */
+ complete(cs, -1, EBADF);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void semihost_sys_write(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, target_ulong buf, target_ulong len)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (gf) {
+ semihost_sys_write_gf(cs, complete, gf, buf, len);
+ } else {
+ complete(cs, -1, EBADF);
+ }
+}
+
+void semihost_sys_lseek(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, int64_t off, int gdb_whence)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (!gf) {
+ complete(cs, -1, EBADF);
+ return;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ gdb_lseek(cs, complete, gf, off, gdb_whence);
+ return;
+ case GuestFDHost:
+ host_lseek(cs, complete, gf, off, gdb_whence);
+ break;
+ case GuestFDStatic:
+ staticfile_lseek(cs, complete, gf, off, gdb_whence);
+ break;
+ case GuestFDConsole:
+ complete(cs, -1, ESPIPE);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void semihost_sys_isatty(CPUState *cs, gdb_syscall_complete_cb complete, int fd)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (!gf) {
+ complete(cs, 0, EBADF);
+ return;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ gdb_isatty(cs, complete, gf);
+ break;
+ case GuestFDHost:
+ host_isatty(cs, complete, gf);
+ break;
+ case GuestFDStatic:
+ complete(cs, 0, ENOTTY);
+ break;
+ case GuestFDConsole:
+ complete(cs, 1, 0);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void semihost_sys_flen(CPUState *cs, gdb_syscall_complete_cb fstat_cb,
+ gdb_syscall_complete_cb flen_cb, int fd,
+ target_ulong fstat_addr)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (!gf) {
+ flen_cb(cs, -1, EBADF);
+ return;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ gdb_fstat(cs, fstat_cb, gf, fstat_addr);
+ break;
+ case GuestFDHost:
+ host_flen(cs, flen_cb, gf);
+ break;
+ case GuestFDStatic:
+ staticfile_flen(cs, flen_cb, gf);
+ break;
+ case GuestFDConsole:
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void semihost_sys_fstat(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, target_ulong addr)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (!gf) {
+ complete(cs, -1, EBADF);
+ return;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ gdb_fstat(cs, complete, gf, addr);
+ break;
+ case GuestFDHost:
+ host_fstat(cs, complete, gf, addr);
+ break;
+ case GuestFDConsole:
+ console_fstat(cs, complete, gf, addr);
+ break;
+ case GuestFDStatic:
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void semihost_sys_stat(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len,
+ target_ulong addr)
+{
+ if (use_gdb_syscalls()) {
+ gdb_stat(cs, complete, fname, fname_len, addr);
+ } else {
+ host_stat(cs, complete, fname, fname_len, addr);
+ }
+}
+
+void semihost_sys_remove(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong fname, target_ulong fname_len)
+{
+ if (use_gdb_syscalls()) {
+ gdb_remove(cs, complete, fname, fname_len);
+ } else {
+ host_remove(cs, complete, fname, fname_len);
+ }
+}
+
+void semihost_sys_rename(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong oname, target_ulong oname_len,
+ target_ulong nname, target_ulong nname_len)
+{
+ if (use_gdb_syscalls()) {
+ gdb_rename(cs, complete, oname, oname_len, nname, nname_len);
+ } else {
+ host_rename(cs, complete, oname, oname_len, nname, nname_len);
+ }
+}
+
+void semihost_sys_system(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong cmd, target_ulong cmd_len)
+{
+ if (use_gdb_syscalls()) {
+ gdb_system(cs, complete, cmd, cmd_len);
+ } else {
+ host_system(cs, complete, cmd, cmd_len);
+ }
+}
+
+void semihost_sys_gettimeofday(CPUState *cs, gdb_syscall_complete_cb complete,
+ target_ulong tv_addr, target_ulong tz_addr)
+{
+ if (use_gdb_syscalls()) {
+ gdb_gettimeofday(cs, complete, tv_addr, tz_addr);
+ } else {
+ host_gettimeofday(cs, complete, tv_addr, tz_addr);
+ }
+}
+
+#ifndef CONFIG_USER_ONLY
+void semihost_sys_poll_one(CPUState *cs, gdb_syscall_complete_cb complete,
+ int fd, GIOCondition cond, int timeout)
+{
+ GuestFD *gf = get_guestfd(fd);
+
+ if (!gf) {
+ complete(cs, G_IO_NVAL, 1);
+ return;
+ }
+ switch (gf->type) {
+ case GuestFDGDB:
+ complete(cs, G_IO_NVAL, 1);
+ break;
+ case GuestFDHost:
+ host_poll_one(cs, complete, gf, cond, timeout);
+ break;
+ case GuestFDConsole:
+ console_poll_one(cs, complete, gf, cond, timeout);
+ break;
+ case GuestFDStatic:
+ default:
+ g_assert_not_reached();
+ }
+}
+#endif
diff --git a/semihosting/uaccess.c b/semihosting/uaccess.c
new file mode 100644
index 0000000000..8018828069
--- /dev/null
+++ b/semihosting/uaccess.c
@@ -0,0 +1,91 @@
+/*
+ * Helper routines to provide target memory access for semihosting
+ * syscalls in system emulation mode.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+
+#include "qemu/osdep.h"
+#include "exec/exec-all.h"
+#include "semihosting/softmmu-uaccess.h"
+
+void *softmmu_lock_user(CPUArchState *env, target_ulong addr,
+ target_ulong len, bool copy)
+{
+ void *p = malloc(len);
+ if (p && copy) {
+ if (cpu_memory_rw_debug(env_cpu(env), addr, p, len, 0)) {
+ free(p);
+ p = NULL;
+ }
+ }
+ return p;
+}
+
+ssize_t softmmu_strlen_user(CPUArchState *env, target_ulong addr)
+{
+ int mmu_idx = cpu_mmu_index(env, false);
+ size_t len = 0;
+
+ while (1) {
+ size_t left_in_page;
+ int flags;
+ void *h;
+
+ /* Find the number of bytes remaining in the page. */
+ left_in_page = TARGET_PAGE_SIZE - (addr & ~TARGET_PAGE_MASK);
+
+ flags = probe_access_flags(env, addr, MMU_DATA_LOAD,
+ mmu_idx, true, &h, 0);
+ if (flags & TLB_INVALID_MASK) {
+ return -1;
+ }
+ if (flags & TLB_MMIO) {
+ do {
+ uint8_t c;
+ if (cpu_memory_rw_debug(env_cpu(env), addr, &c, 1, 0)) {
+ return -1;
+ }
+ if (c == 0) {
+ return len;
+ }
+ addr++;
+ len++;
+ if (len > INT32_MAX) {
+ return -1;
+ }
+ } while (--left_in_page != 0);
+ } else {
+ char *p = memchr(h, 0, left_in_page);
+ if (p) {
+ len += p - (char *)h;
+ return len <= INT32_MAX ? (ssize_t)len : -1;
+ }
+ addr += left_in_page;
+ len += left_in_page;
+ if (len > INT32_MAX) {
+ return -1;
+ }
+ }
+ }
+}
+
+char *softmmu_lock_user_string(CPUArchState *env, target_ulong addr)
+{
+ ssize_t len = softmmu_strlen_user(env, addr);
+ if (len < 0) {
+ return NULL;
+ }
+ return softmmu_lock_user(env, addr, len + 1, true);
+}
+
+void softmmu_unlock_user(CPUArchState *env, void *p,
+ target_ulong addr, target_ulong len)
+{
+ if (len) {
+ cpu_memory_rw_debug(env_cpu(env), addr, p, len, 1);
+ }
+ free(p);
+}
diff --git a/softmmu/vl.c b/softmmu/vl.c
index 54e920ada1..b24772841d 100644
--- a/softmmu/vl.c
+++ b/softmmu/vl.c
@@ -1917,8 +1917,7 @@ static void qemu_create_late_backends(void)
exit(1);
/* now chardevs have been created we may have semihosting to connect */
- qemu_semihosting_connect_chardevs();
- qemu_semihosting_console_init();
+ qemu_semihosting_chardev_init();
}
static void qemu_resolve_machine_memdev(void)
@@ -2271,8 +2270,7 @@ static void configure_accelerators(const char *progname)
}
if (init_failed && !qtest_chrdev) {
- AccelClass *ac = ACCEL_GET_CLASS(current_accel());
- error_report("falling back to %s", ac->name);
+ error_report("falling back to %s", current_accel_name());
}
if (icount_enabled() && !tcg_enabled()) {
diff --git a/stubs/semihost.c b/stubs/semihost.c
index 4bf2cf71b9..f486651afb 100644
--- a/stubs/semihost.c
+++ b/stubs/semihost.c
@@ -65,10 +65,6 @@ void semihosting_arg_fallback(const char *file, const char *cmd)
{
}
-void qemu_semihosting_connect_chardevs(void)
-{
-}
-
-void qemu_semihosting_console_init(void)
+void qemu_semihosting_chardev_init(void)
{
}
diff --git a/target/arm/common-semi-target.h b/target/arm/common-semi-target.h
new file mode 100644
index 0000000000..629d75ca5a
--- /dev/null
+++ b/target/arm/common-semi-target.h
@@ -0,0 +1,62 @@
+/*
+ * Target-specific parts of semihosting/arm-compat-semi.c.
+ *
+ * Copyright (c) 2005, 2007 CodeSourcery.
+ * Copyright (c) 2019, 2022 Linaro
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TARGET_ARM_COMMON_SEMI_TARGET_H
+#define TARGET_ARM_COMMON_SEMI_TARGET_H
+
+#ifndef CONFIG_USER_ONLY
+#include "hw/arm/boot.h"
+#endif
+
+static inline target_ulong common_semi_arg(CPUState *cs, int argno)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+ if (is_a64(env)) {
+ return env->xregs[argno];
+ } else {
+ return env->regs[argno];
+ }
+}
+
+static inline void common_semi_set_ret(CPUState *cs, target_ulong ret)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+ if (is_a64(env)) {
+ env->xregs[0] = ret;
+ } else {
+ env->regs[0] = ret;
+ }
+}
+
+static inline bool common_semi_sys_exit_extended(CPUState *cs, int nr)
+{
+ return (nr == TARGET_SYS_EXIT_EXTENDED || is_a64(cs->env_ptr));
+}
+
+static inline bool is_64bit_semihosting(CPUArchState *env)
+{
+ return is_a64(env);
+}
+
+static inline target_ulong common_semi_stack_bottom(CPUState *cs)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+ return is_a64(env) ? env->xregs[31] : env->regs[13];
+}
+
+static inline bool common_semi_has_synccache(CPUArchState *env)
+{
+ /* Ok for A64, invalid for A32/T32 */
+ return is_a64(env);
+}
+
+#endif
diff --git a/target/arm/cpregs.h b/target/arm/cpregs.h
index d9b678c2f1..d30758ee71 100644
--- a/target/arm/cpregs.h
+++ b/target/arm/cpregs.h
@@ -113,6 +113,11 @@ enum {
ARM_CP_EL3_NO_EL2_UNDEF = 1 << 16,
ARM_CP_EL3_NO_EL2_KEEP = 1 << 17,
ARM_CP_EL3_NO_EL2_C_NZ = 1 << 18,
+ /*
+ * Flag: Access check for this sysreg is constrained by the
+ * ARM pseudocode function CheckSMEAccess().
+ */
+ ARM_CP_SME = 1 << 19,
};
/*
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index 1b5d535788..bb44ad45aa 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -39,6 +39,7 @@
#include "hw/boards.h"
#endif
#include "sysemu/tcg.h"
+#include "sysemu/qtest.h"
#include "sysemu/hw_accel.h"
#include "kvm_arm.h"
#include "disas/capstone.h"
@@ -1122,11 +1123,13 @@ static void arm_cpu_initfn(Object *obj)
#ifdef CONFIG_USER_ONLY
# ifdef TARGET_AARCH64
/*
- * The linux kernel defaults to 512-bit vectors, when sve is supported.
- * See documentation for /proc/sys/abi/sve_default_vector_length, and
- * our corresponding sve-default-vector-length cpu property.
+ * The linux kernel defaults to 512-bit for SVE, and 256-bit for SME.
+ * These values were chosen to fit within the default signal frame.
+ * See documentation for /proc/sys/abi/{sve,sme}_default_vector_length,
+ * and our corresponding cpu property.
*/
cpu->sve_default_vq = 4;
+ cpu->sme_default_vq = 2;
# endif
#else
/* Our inbound IRQ and FIQ lines */
@@ -1421,6 +1424,7 @@ void arm_cpu_finalize_features(ARMCPU *cpu, Error **errp)
{
Error *local_err = NULL;
+#ifdef TARGET_AARCH64
if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) {
arm_cpu_sve_finalize(cpu, &local_err);
if (local_err != NULL) {
@@ -1428,6 +1432,12 @@ void arm_cpu_finalize_features(ARMCPU *cpu, Error **errp)
return;
}
+ arm_cpu_sme_finalize(cpu, &local_err);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
arm_cpu_pauth_finalize(cpu, &local_err);
if (local_err != NULL) {
error_propagate(errp, local_err);
@@ -1440,6 +1450,7 @@ void arm_cpu_finalize_features(ARMCPU *cpu, Error **errp)
return;
}
}
+#endif
if (kvm_enabled()) {
kvm_arm_steal_time_finalize(cpu, &local_err);
@@ -1490,25 +1501,32 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
}
}
- if (kvm_enabled()) {
+ if (!tcg_enabled() && !qtest_enabled()) {
/*
+ * We assume that no accelerator except TCG (and the "not really an
+ * accelerator" qtest) can handle these features, because Arm hardware
+ * virtualization can't virtualize them.
+ *
* Catch all the cases which might cause us to create more than one
* address space for the CPU (otherwise we will assert() later in
* cpu_address_space_init()).
*/
if (arm_feature(env, ARM_FEATURE_M)) {
error_setg(errp,
- "Cannot enable KVM when using an M-profile guest CPU");
+ "Cannot enable %s when using an M-profile guest CPU",
+ current_accel_name());
return;
}
if (cpu->has_el3) {
error_setg(errp,
- "Cannot enable KVM when guest CPU has EL3 enabled");
+ "Cannot enable %s when guest CPU has EL3 enabled",
+ current_accel_name());
return;
}
if (cpu->tag_memory) {
error_setg(errp,
- "Cannot enable KVM when guest CPUs has MTE enabled");
+ "Cannot enable %s when guest CPUs has MTE enabled",
+ current_accel_name());
return;
}
}
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index df677b2d5d..4a4342f262 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -205,14 +205,8 @@ typedef struct {
#ifdef TARGET_AARCH64
# define ARM_MAX_VQ 16
-void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp);
-void arm_cpu_pauth_finalize(ARMCPU *cpu, Error **errp);
-void arm_cpu_lpa2_finalize(ARMCPU *cpu, Error **errp);
#else
# define ARM_MAX_VQ 1
-static inline void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp) { }
-static inline void arm_cpu_pauth_finalize(ARMCPU *cpu, Error **errp) { }
-static inline void arm_cpu_lpa2_finalize(ARMCPU *cpu, Error **errp) { }
#endif
typedef struct ARMVectorReg {
@@ -258,6 +252,7 @@ typedef struct CPUArchState {
* nRW (also known as M[4]) is kept, inverted, in env->aarch64
* DAIF (exception masks) are kept in env->daif
* BTYPE is kept in env->btype
+ * SM and ZA are kept in env->svcr
* all other bits are stored in their correct places in env->pstate
*/
uint32_t pstate;
@@ -292,6 +287,7 @@ typedef struct CPUArchState {
uint32_t condexec_bits; /* IT bits. cpsr[15:10,26:25]. */
uint32_t btype; /* BTI branch type. spsr[11:10]. */
uint64_t daif; /* exception masks, in the bits they are in PSTATE */
+ uint64_t svcr; /* PSTATE.{SM,ZA} in the bits they are in SVCR */
uint64_t elr_el[4]; /* AArch64 exception link regs */
uint64_t sp_el[4]; /* AArch64 banked stack pointers */
@@ -474,6 +470,7 @@ typedef struct CPUArchState {
};
uint64_t tpidr_el[4];
};
+ uint64_t tpidr2_el0;
/* The secure banks of these registers don't map anywhere */
uint64_t tpidrurw_s;
uint64_t tpidrprw_s;
@@ -666,8 +663,8 @@ typedef struct CPUArchState {
float_status standard_fp_status;
float_status standard_fp_status_f16;
- /* ZCR_EL[1-3] */
- uint64_t zcr_el[4];
+ uint64_t zcr_el[4]; /* ZCR_EL[1-3] */
+ uint64_t smcr_el[4]; /* SMCR_EL[1-3] */
} vfp;
uint64_t exclusive_addr;
uint64_t exclusive_val;
@@ -691,6 +688,28 @@ typedef struct CPUArchState {
} keys;
uint64_t scxtnum_el[4];
+
+ /*
+ * SME ZA storage -- 256 x 256 byte array, with bytes in host word order,
+ * as we do with vfp.zregs[]. This corresponds to the architectural ZA
+ * array, where ZA[N] is in the least-significant bytes of env->zarray[N].
+ * When SVL is less than the architectural maximum, the accessible
+ * storage is restricted, such that if the SVL is X bytes the guest can
+ * see only the bottom X elements of zarray[], and only the least
+ * significant X bytes of each element of the array. (In other words,
+ * the observable part is always square.)
+ *
+ * The ZA storage can also be considered as a set of square tiles of
+ * elements of different sizes. The mapping from tiles to the ZA array
+ * is architecturally defined, such that for tiles of elements of esz
+ * bytes, the Nth row (or "horizontal slice") of tile T is in
+ * ZA[T + N * esz]. Note that this means that each tile is not contiguous
+ * in the ZA storage, because its rows are striped through the ZA array.
+ *
+ * Because this is so large, keep this toward the end of the reset area,
+ * to keep the offsets into the rest of the structure smaller.
+ */
+ ARMVectorReg zarray[ARM_MAX_VQ * 16];
#endif
#if defined(CONFIG_USER_ONLY)
@@ -782,6 +801,19 @@ typedef enum ARMPSCIState {
typedef struct ARMISARegisters ARMISARegisters;
+/*
+ * In map, each set bit is a supported vector length of (bit-number + 1) * 16
+ * bytes, i.e. each bit number + 1 is the vector length in quadwords.
+ *
+ * While processing properties during initialization, corresponding init bits
+ * are set for bits in sve_vq_map that have been set by properties.
+ *
+ * Bits set in supported represent valid vector lengths for the CPU type.
+ */
+typedef struct {
+ uint32_t map, init, supported;
+} ARMVQMap;
+
/**
* ARMCPU:
* @env: #CPUARMState
@@ -1028,23 +1060,11 @@ struct ArchCPU {
#ifdef CONFIG_USER_ONLY
/* Used to set the default vector length at process start. */
uint32_t sve_default_vq;
+ uint32_t sme_default_vq;
#endif
- /*
- * In sve_vq_map each set bit is a supported vector length of
- * (bit-number + 1) * 16 bytes, i.e. each bit number + 1 is the vector
- * length in quadwords.
- *
- * While processing properties during initialization, corresponding
- * sve_vq_init bits are set for bits in sve_vq_map that have been
- * set by properties.
- *
- * Bits set in sve_vq_supported represent valid vector lengths for
- * the CPU type.
- */
- uint32_t sve_vq_map;
- uint32_t sve_vq_init;
- uint32_t sve_vq_supported;
+ ARMVQMap sve_vq;
+ ARMVQMap sme_vq;
/* Generic timer counter frequency, in Hz */
uint64_t gt_cntfrq_hz;
@@ -1093,8 +1113,7 @@ int aarch64_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg);
void aarch64_sve_narrow_vq(CPUARMState *env, unsigned vq);
void aarch64_sve_change_el(CPUARMState *env, int old_el,
int new_el, bool el0_a64);
-void aarch64_add_sve_properties(Object *obj);
-void aarch64_add_pauth_properties(Object *obj);
+void arm_reset_sve_state(CPUARMState *env);
/*
* SVE registers are encoded in KVM's memory in an endianness-invariant format.
@@ -1125,7 +1144,6 @@ static inline void aarch64_sve_narrow_vq(CPUARMState *env, unsigned vq) { }
static inline void aarch64_sve_change_el(CPUARMState *env, int o,
int n, bool a)
{ }
-static inline void aarch64_add_sve_properties(Object *obj) { }
#endif
void aarch64_sync_32_to_64(CPUARMState *env);
@@ -1133,15 +1151,21 @@ void aarch64_sync_64_to_32(CPUARMState *env);
int fp_exception_el(CPUARMState *env, int cur_el);
int sve_exception_el(CPUARMState *env, int cur_el);
+int sme_exception_el(CPUARMState *env, int cur_el);
/**
- * sve_vqm1_for_el:
+ * sve_vqm1_for_el_sm:
* @env: CPUARMState
* @el: exception level
+ * @sm: streaming mode
*
- * Compute the current SVE vector length for @el, in units of
+ * Compute the current vector length for @el & @sm, in units of
* Quadwords Minus 1 -- the same scale used for ZCR_ELx.LEN.
+ * If @sm, compute for SVL, otherwise NVL.
*/
+uint32_t sve_vqm1_for_el_sm(CPUARMState *env, int el, bool sm);
+
+/* Likewise, but using @sm = PSTATE.SM. */
uint32_t sve_vqm1_for_el(CPUARMState *env, int el);
static inline bool is_a64(CPUARMState *env)
@@ -1426,6 +1450,14 @@ FIELD(CPTR_EL3, TCPAC, 31, 1)
#define PSTATE_MODE_EL1t 4
#define PSTATE_MODE_EL0t 0
+/* PSTATE bits that are accessed via SVCR and not stored in SPSR_ELx. */
+FIELD(SVCR, SM, 0, 1)
+FIELD(SVCR, ZA, 1, 1)
+
+/* Fields for SMCR_ELx. */
+FIELD(SMCR, LEN, 0, 4)
+FIELD(SMCR, FA64, 31, 1)
+
/* Write a new value to v7m.exception, thus transitioning into or out
* of Handler mode; this may result in a change of active stack pointer.
*/
@@ -3147,6 +3179,10 @@ FIELD(TBFLAG_A64, ATA, 15, 1)
FIELD(TBFLAG_A64, TCMA, 16, 2)
FIELD(TBFLAG_A64, MTE_ACTIVE, 18, 1)
FIELD(TBFLAG_A64, MTE0_ACTIVE, 19, 1)
+FIELD(TBFLAG_A64, SMEEXC_EL, 20, 2)
+FIELD(TBFLAG_A64, PSTATE_SM, 22, 1)
+FIELD(TBFLAG_A64, PSTATE_ZA, 23, 1)
+FIELD(TBFLAG_A64, SVL, 24, 4)
/*
* Helpers for using the above.
@@ -3192,6 +3228,17 @@ static inline int sve_vq(CPUARMState *env)
return EX_TBFLAG_A64(env->hflags, VL) + 1;
}
+/**
+ * sme_vq
+ * @env: the cpu context
+ *
+ * Return the SVL cached within env->hflags, in units of quadwords.
+ */
+static inline int sme_vq(CPUARMState *env)
+{
+ return EX_TBFLAG_A64(env->hflags, SVL) + 1;
+}
+
static inline bool bswap_code(bool sctlr_b)
{
#ifdef CONFIG_USER_ONLY
diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c
index 15665c962b..19188d6cc2 100644
--- a/target/arm/cpu64.c
+++ b/target/arm/cpu64.c
@@ -355,8 +355,8 @@ void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp)
* any of the above. Finally, if SVE is not disabled, then at least one
* vector length must be enabled.
*/
- uint32_t vq_map = cpu->sve_vq_map;
- uint32_t vq_init = cpu->sve_vq_init;
+ uint32_t vq_map = cpu->sve_vq.map;
+ uint32_t vq_init = cpu->sve_vq.init;
uint32_t vq_supported;
uint32_t vq_mask = 0;
uint32_t tmp, vq, max_vq = 0;
@@ -369,14 +369,14 @@ void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp)
*/
if (kvm_enabled()) {
if (kvm_arm_sve_supported()) {
- cpu->sve_vq_supported = kvm_arm_sve_get_vls(CPU(cpu));
- vq_supported = cpu->sve_vq_supported;
+ cpu->sve_vq.supported = kvm_arm_sve_get_vls(CPU(cpu));
+ vq_supported = cpu->sve_vq.supported;
} else {
assert(!cpu_isar_feature(aa64_sve, cpu));
vq_supported = 0;
}
} else {
- vq_supported = cpu->sve_vq_supported;
+ vq_supported = cpu->sve_vq.supported;
}
/*
@@ -487,8 +487,13 @@ void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp)
"using only sve<N> properties.\n");
} else {
error_setg(errp, "cannot enable sve%d", vq * 128);
- error_append_hint(errp, "This CPU does not support "
- "the vector length %d-bits.\n", vq * 128);
+ if (vq_supported) {
+ error_append_hint(errp, "This CPU does not support "
+ "the vector length %d-bits.\n", vq * 128);
+ } else {
+ error_append_hint(errp, "SVE not supported by KVM "
+ "on this host\n");
+ }
}
return;
} else {
@@ -529,7 +534,7 @@ void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp)
/* From now on sve_max_vq is the actual maximum supported length. */
cpu->sve_max_vq = max_vq;
- cpu->sve_vq_map = vq_map;
+ cpu->sve_vq.map = vq_map;
}
static void cpu_max_get_sve_max_vq(Object *obj, Visitor *v, const char *name,
@@ -574,31 +579,34 @@ static void cpu_max_set_sve_max_vq(Object *obj, Visitor *v, const char *name,
}
/*
- * Note that cpu_arm_get/set_sve_vq cannot use the simpler
- * object_property_add_bool interface because they make use
- * of the contents of "name" to determine which bit on which
- * to operate.
+ * Note that cpu_arm_{get,set}_vq cannot use the simpler
+ * object_property_add_bool interface because they make use of the
+ * contents of "name" to determine which bit on which to operate.
*/
-static void cpu_arm_get_sve_vq(Object *obj, Visitor *v, const char *name,
- void *opaque, Error **errp)
+static void cpu_arm_get_vq(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
{
ARMCPU *cpu = ARM_CPU(obj);
+ ARMVQMap *vq_map = opaque;
uint32_t vq = atoi(&name[3]) / 128;
+ bool sve = vq_map == &cpu->sve_vq;
bool value;
- /* All vector lengths are disabled when SVE is off. */
- if (!cpu_isar_feature(aa64_sve, cpu)) {
+ /* All vector lengths are disabled when feature is off. */
+ if (sve
+ ? !cpu_isar_feature(aa64_sve, cpu)
+ : !cpu_isar_feature(aa64_sme, cpu)) {
value = false;
} else {
- value = extract32(cpu->sve_vq_map, vq - 1, 1);
+ value = extract32(vq_map->map, vq - 1, 1);
}
visit_type_bool(v, name, &value, errp);
}
-static void cpu_arm_set_sve_vq(Object *obj, Visitor *v, const char *name,
- void *opaque, Error **errp)
+static void cpu_arm_set_vq(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
{
- ARMCPU *cpu = ARM_CPU(obj);
+ ARMVQMap *vq_map = opaque;
uint32_t vq = atoi(&name[3]) / 128;
bool value;
@@ -606,14 +614,8 @@ static void cpu_arm_set_sve_vq(Object *obj, Visitor *v, const char *name,
return;
}
- if (value && kvm_enabled() && !kvm_arm_sve_supported()) {
- error_setg(errp, "cannot enable %s", name);
- error_append_hint(errp, "SVE not supported by KVM on this host\n");
- return;
- }
-
- cpu->sve_vq_map = deposit32(cpu->sve_vq_map, vq - 1, 1, value);
- cpu->sve_vq_init |= 1 << (vq - 1);
+ vq_map->map = deposit32(vq_map->map, vq - 1, 1, value);
+ vq_map->init |= 1 << (vq - 1);
}
static bool cpu_arm_get_sve(Object *obj, Error **errp)
@@ -637,13 +639,85 @@ static void cpu_arm_set_sve(Object *obj, bool value, Error **errp)
cpu->isar.id_aa64pfr0 = t;
}
-#ifdef CONFIG_USER_ONLY
-/* Mirror linux /proc/sys/abi/sve_default_vector_length. */
-static void cpu_arm_set_sve_default_vec_len(Object *obj, Visitor *v,
- const char *name, void *opaque,
- Error **errp)
+void arm_cpu_sme_finalize(ARMCPU *cpu, Error **errp)
+{
+ uint32_t vq_map = cpu->sme_vq.map;
+ uint32_t vq_init = cpu->sme_vq.init;
+ uint32_t vq_supported = cpu->sme_vq.supported;
+ uint32_t vq;
+
+ if (vq_map == 0) {
+ if (!cpu_isar_feature(aa64_sme, cpu)) {
+ cpu->isar.id_aa64smfr0 = 0;
+ return;
+ }
+
+ /* TODO: KVM will require limitations via SMCR_EL2. */
+ vq_map = vq_supported & ~vq_init;
+
+ if (vq_map == 0) {
+ vq = ctz32(vq_supported) + 1;
+ error_setg(errp, "cannot disable sme%d", vq * 128);
+ error_append_hint(errp, "All SME vector lengths are disabled.\n");
+ error_append_hint(errp, "With SME enabled, at least one "
+ "vector length must be enabled.\n");
+ return;
+ }
+ } else {
+ if (!cpu_isar_feature(aa64_sme, cpu)) {
+ vq = 32 - clz32(vq_map);
+ error_setg(errp, "cannot enable sme%d", vq * 128);
+ error_append_hint(errp, "SME must be enabled to enable "
+ "vector lengths.\n");
+ error_append_hint(errp, "Add sme=on to the CPU property list.\n");
+ return;
+ }
+ /* TODO: KVM will require limitations via SMCR_EL2. */
+ }
+
+ cpu->sme_vq.map = vq_map;
+}
+
+static bool cpu_arm_get_sme(Object *obj, Error **errp)
+{
+ ARMCPU *cpu = ARM_CPU(obj);
+ return cpu_isar_feature(aa64_sme, cpu);
+}
+
+static void cpu_arm_set_sme(Object *obj, bool value, Error **errp)
{
ARMCPU *cpu = ARM_CPU(obj);
+ uint64_t t;
+
+ t = cpu->isar.id_aa64pfr1;
+ t = FIELD_DP64(t, ID_AA64PFR1, SME, value);
+ cpu->isar.id_aa64pfr1 = t;
+}
+
+static bool cpu_arm_get_sme_fa64(Object *obj, Error **errp)
+{
+ ARMCPU *cpu = ARM_CPU(obj);
+ return cpu_isar_feature(aa64_sme, cpu) &&
+ cpu_isar_feature(aa64_sme_fa64, cpu);
+}
+
+static void cpu_arm_set_sme_fa64(Object *obj, bool value, Error **errp)
+{
+ ARMCPU *cpu = ARM_CPU(obj);
+ uint64_t t;
+
+ t = cpu->isar.id_aa64smfr0;
+ t = FIELD_DP64(t, ID_AA64SMFR0, FA64, value);
+ cpu->isar.id_aa64smfr0 = t;
+}
+
+#ifdef CONFIG_USER_ONLY
+/* Mirror linux /proc/sys/abi/{sve,sme}_default_vector_length. */
+static void cpu_arm_set_default_vec_len(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ uint32_t *ptr_default_vq = opaque;
int32_t default_len, default_vq, remainder;
if (!visit_type_int32(v, name, &default_len, errp)) {
@@ -652,7 +726,7 @@ static void cpu_arm_set_sve_default_vec_len(Object *obj, Visitor *v,
/* Undocumented, but the kernel allows -1 to indicate "maximum". */
if (default_len == -1) {
- cpu->sve_default_vq = ARM_MAX_VQ;
+ *ptr_default_vq = ARM_MAX_VQ;
return;
}
@@ -664,7 +738,11 @@ static void cpu_arm_set_sve_default_vec_len(Object *obj, Visitor *v,
* and is the maximum architectural width of ZCR_ELx.LEN.
*/
if (remainder || default_vq < 1 || default_vq > 512) {
- error_setg(errp, "cannot set sve-default-vector-length");
+ ARMCPU *cpu = ARM_CPU(obj);
+ const char *which =
+ (ptr_default_vq == &cpu->sve_default_vq ? "sve" : "sme");
+
+ error_setg(errp, "cannot set %s-default-vector-length", which);
if (remainder) {
error_append_hint(errp, "Vector length not a multiple of 16\n");
} else if (default_vq < 1) {
@@ -676,22 +754,23 @@ static void cpu_arm_set_sve_default_vec_len(Object *obj, Visitor *v,
return;
}
- cpu->sve_default_vq = default_vq;
+ *ptr_default_vq = default_vq;
}
-static void cpu_arm_get_sve_default_vec_len(Object *obj, Visitor *v,
- const char *name, void *opaque,
- Error **errp)
+static void cpu_arm_get_default_vec_len(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
{
- ARMCPU *cpu = ARM_CPU(obj);
- int32_t value = cpu->sve_default_vq * 16;
+ uint32_t *ptr_default_vq = opaque;
+ int32_t value = *ptr_default_vq * 16;
visit_type_int32(v, name, &value, errp);
}
#endif
-void aarch64_add_sve_properties(Object *obj)
+static void aarch64_add_sve_properties(Object *obj)
{
+ ARMCPU *cpu = ARM_CPU(obj);
uint32_t vq;
object_property_add_bool(obj, "sve", cpu_arm_get_sve, cpu_arm_set_sve);
@@ -699,15 +778,41 @@ void aarch64_add_sve_properties(Object *obj)
for (vq = 1; vq <= ARM_MAX_VQ; ++vq) {
char name[8];
sprintf(name, "sve%d", vq * 128);
- object_property_add(obj, name, "bool", cpu_arm_get_sve_vq,
- cpu_arm_set_sve_vq, NULL, NULL);
+ object_property_add(obj, name, "bool", cpu_arm_get_vq,
+ cpu_arm_set_vq, NULL, &cpu->sve_vq);
}
#ifdef CONFIG_USER_ONLY
/* Mirror linux /proc/sys/abi/sve_default_vector_length. */
object_property_add(obj, "sve-default-vector-length", "int32",
- cpu_arm_get_sve_default_vec_len,
- cpu_arm_set_sve_default_vec_len, NULL, NULL);
+ cpu_arm_get_default_vec_len,
+ cpu_arm_set_default_vec_len, NULL,
+ &cpu->sve_default_vq);
+#endif
+}
+
+static void aarch64_add_sme_properties(Object *obj)
+{
+ ARMCPU *cpu = ARM_CPU(obj);
+ uint32_t vq;
+
+ object_property_add_bool(obj, "sme", cpu_arm_get_sme, cpu_arm_set_sme);
+ object_property_add_bool(obj, "sme_fa64", cpu_arm_get_sme_fa64,
+ cpu_arm_set_sme_fa64);
+
+ for (vq = 1; vq <= ARM_MAX_VQ; vq <<= 1) {
+ char name[8];
+ sprintf(name, "sme%d", vq * 128);
+ object_property_add(obj, name, "bool", cpu_arm_get_vq,
+ cpu_arm_set_vq, NULL, &cpu->sme_vq);
+ }
+
+#ifdef CONFIG_USER_ONLY
+ /* Mirror linux /proc/sys/abi/sme_default_vector_length. */
+ object_property_add(obj, "sme-default-vector-length", "int32",
+ cpu_arm_get_default_vec_len,
+ cpu_arm_set_default_vec_len, NULL,
+ &cpu->sme_default_vq);
#endif
}
@@ -751,7 +856,7 @@ static Property arm_cpu_pauth_property =
static Property arm_cpu_pauth_impdef_property =
DEFINE_PROP_BOOL("pauth-impdef", ARMCPU, prop_pauth_impdef, false);
-void aarch64_add_pauth_properties(Object *obj)
+static void aarch64_add_pauth_properties(Object *obj)
{
ARMCPU *cpu = ARM_CPU(obj);
@@ -975,10 +1080,12 @@ static void aarch64_max_initfn(Object *obj)
cpu->dcz_blocksize = 7; /* 512 bytes */
#endif
- cpu->sve_vq_supported = MAKE_64BIT_MASK(0, ARM_MAX_VQ);
+ cpu->sve_vq.supported = MAKE_64BIT_MASK(0, ARM_MAX_VQ);
+ cpu->sme_vq.supported = SVE_VQ_POW2_MAP;
aarch64_add_pauth_properties(obj);
aarch64_add_sve_properties(obj);
+ aarch64_add_sme_properties(obj);
object_property_add(obj, "sve-max-vq", "uint32", cpu_max_get_sve_max_vq,
cpu_max_set_sve_max_vq, NULL, NULL);
qdev_property_add_static(DEVICE(obj), &arm_cpu_lpa2_property);
@@ -1024,7 +1131,7 @@ static void aarch64_a64fx_initfn(Object *obj)
/* The A64FX supports only 128, 256 and 512 bit vector lengths */
aarch64_add_sve_properties(obj);
- cpu->sve_vq_supported = (1 << 0) /* 128bit */
+ cpu->sve_vq.supported = (1 << 0) /* 128bit */
| (1 << 1) /* 256bit */
| (1 << 3); /* 512bit */
diff --git a/target/arm/helper-sme.h b/target/arm/helper-sme.h
new file mode 100644
index 0000000000..3bd48c235f
--- /dev/null
+++ b/target/arm/helper-sme.h
@@ -0,0 +1,21 @@
+/*
+ * AArch64 SME specific helper definitions
+ *
+ * Copyright (c) 2022 Linaro, Ltd
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+DEF_HELPER_FLAGS_2(set_pstate_sm, TCG_CALL_NO_RWG, void, env, i32)
+DEF_HELPER_FLAGS_2(set_pstate_za, TCG_CALL_NO_RWG, void, env, i32)
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 6457e6301c..f6dcb1a115 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -5879,6 +5879,8 @@ static void define_arm_vh_e2h_redirects_aliases(ARMCPU *cpu)
*/
{ K(3, 0, 1, 2, 0), K(3, 4, 1, 2, 0), K(3, 5, 1, 2, 0),
"ZCR_EL1", "ZCR_EL2", "ZCR_EL12", isar_feature_aa64_sve },
+ { K(3, 0, 1, 2, 6), K(3, 4, 1, 2, 6), K(3, 5, 1, 2, 6),
+ "SMCR_EL1", "SMCR_EL2", "SMCR_EL12", isar_feature_aa64_sme },
{ K(3, 0, 5, 6, 0), K(3, 4, 5, 6, 0), K(3, 5, 5, 6, 0),
"TFSR_EL1", "TFSR_EL2", "TFSR_EL12", isar_feature_aa64_mte },
@@ -6219,25 +6221,92 @@ int sve_exception_el(CPUARMState *env, int el)
}
/*
+ * Return the exception level to which exceptions should be taken for SME.
+ * C.f. the ARM pseudocode function CheckSMEAccess.
+ */
+int sme_exception_el(CPUARMState *env, int el)
+{
+#ifndef CONFIG_USER_ONLY
+ if (el <= 1 && !el_is_in_host(env, el)) {
+ switch (FIELD_EX64(env->cp15.cpacr_el1, CPACR_EL1, SMEN)) {
+ case 1:
+ if (el != 0) {
+ break;
+ }
+ /* fall through */
+ case 0:
+ case 2:
+ return 1;
+ }
+ }
+
+ if (el <= 2 && arm_is_el2_enabled(env)) {
+ /* CPTR_EL2 changes format with HCR_EL2.E2H (regardless of TGE). */
+ if (env->cp15.hcr_el2 & HCR_E2H) {
+ switch (FIELD_EX64(env->cp15.cptr_el[2], CPTR_EL2, SMEN)) {
+ case 1:
+ if (el != 0 || !(env->cp15.hcr_el2 & HCR_TGE)) {
+ break;
+ }
+ /* fall through */
+ case 0:
+ case 2:
+ return 2;
+ }
+ } else {
+ if (FIELD_EX64(env->cp15.cptr_el[2], CPTR_EL2, TSM)) {
+ return 2;
+ }
+ }
+ }
+
+ /* CPTR_EL3. Since ESM is negative we must check for EL3. */
+ if (arm_feature(env, ARM_FEATURE_EL3)
+ && !FIELD_EX64(env->cp15.cptr_el[3], CPTR_EL3, ESM)) {
+ return 3;
+ }
+#endif
+ return 0;
+}
+
+/*
* Given that SVE is enabled, return the vector length for EL.
*/
-uint32_t sve_vqm1_for_el(CPUARMState *env, int el)
+uint32_t sve_vqm1_for_el_sm(CPUARMState *env, int el, bool sm)
{
ARMCPU *cpu = env_archcpu(env);
- uint32_t len = cpu->sve_max_vq - 1;
+ uint64_t *cr = env->vfp.zcr_el;
+ uint32_t map = cpu->sve_vq.map;
+ uint32_t len = ARM_MAX_VQ - 1;
+
+ if (sm) {
+ cr = env->vfp.smcr_el;
+ map = cpu->sme_vq.map;
+ }
if (el <= 1 && !el_is_in_host(env, el)) {
- len = MIN(len, 0xf & (uint32_t)env->vfp.zcr_el[1]);
+ len = MIN(len, 0xf & (uint32_t)cr[1]);
}
if (el <= 2 && arm_feature(env, ARM_FEATURE_EL2)) {
- len = MIN(len, 0xf & (uint32_t)env->vfp.zcr_el[2]);
+ len = MIN(len, 0xf & (uint32_t)cr[2]);
}
if (arm_feature(env, ARM_FEATURE_EL3)) {
- len = MIN(len, 0xf & (uint32_t)env->vfp.zcr_el[3]);
+ len = MIN(len, 0xf & (uint32_t)cr[3]);
+ }
+
+ map &= MAKE_64BIT_MASK(0, len + 1);
+ if (map != 0) {
+ return 31 - clz32(map);
}
- len = 31 - clz32(cpu->sve_vq_map & MAKE_64BIT_MASK(0, len + 1));
- return len;
+ /* Bit 0 is always set for Normal SVE -- not so for Streaming SVE. */
+ assert(sm);
+ return ctz32(cpu->sme_vq.map);
+}
+
+uint32_t sve_vqm1_for_el(CPUARMState *env, int el)
+{
+ return sve_vqm1_for_el_sm(env, el, FIELD_EX64(env->svcr, SVCR, SM));
}
static void zcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
@@ -6279,6 +6348,120 @@ static const ARMCPRegInfo zcr_reginfo[] = {
.writefn = zcr_write, .raw_writefn = raw_write },
};
+#ifdef TARGET_AARCH64
+static CPAccessResult access_tpidr2(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
+ int el = arm_current_el(env);
+
+ if (el == 0) {
+ uint64_t sctlr = arm_sctlr(env, el);
+ if (!(sctlr & SCTLR_EnTP2)) {
+ return CP_ACCESS_TRAP;
+ }
+ }
+ /* TODO: FEAT_FGT */
+ if (el < 3
+ && arm_feature(env, ARM_FEATURE_EL3)
+ && !(env->cp15.scr_el3 & SCR_ENTP2)) {
+ return CP_ACCESS_TRAP_EL3;
+ }
+ return CP_ACCESS_OK;
+}
+
+static CPAccessResult access_esm(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
+ /* TODO: FEAT_FGT for SMPRI_EL1 but not SMPRIMAP_EL2 */
+ if (arm_current_el(env) < 3
+ && arm_feature(env, ARM_FEATURE_EL3)
+ && !FIELD_EX64(env->cp15.cptr_el[3], CPTR_EL3, ESM)) {
+ return CP_ACCESS_TRAP_EL3;
+ }
+ return CP_ACCESS_OK;
+}
+
+static void svcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ helper_set_pstate_sm(env, FIELD_EX64(value, SVCR, SM));
+ helper_set_pstate_za(env, FIELD_EX64(value, SVCR, ZA));
+ arm_rebuild_hflags(env);
+}
+
+static void smcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ int cur_el = arm_current_el(env);
+ int old_len = sve_vqm1_for_el(env, cur_el);
+ int new_len;
+
+ QEMU_BUILD_BUG_ON(ARM_MAX_VQ > R_SMCR_LEN_MASK + 1);
+ value &= R_SMCR_LEN_MASK | R_SMCR_FA64_MASK;
+ raw_write(env, ri, value);
+
+ /*
+ * Note that it is CONSTRAINED UNPREDICTABLE what happens to ZA storage
+ * when SVL is widened (old values kept, or zeros). Choose to keep the
+ * current values for simplicity. But for QEMU internals, we must still
+ * apply the narrower SVL to the Zregs and Pregs -- see the comment
+ * above aarch64_sve_narrow_vq.
+ */
+ new_len = sve_vqm1_for_el(env, cur_el);
+ if (new_len < old_len) {
+ aarch64_sve_narrow_vq(env, new_len + 1);
+ }
+}
+
+static const ARMCPRegInfo sme_reginfo[] = {
+ { .name = "TPIDR2_EL0", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 3, .crn = 13, .crm = 0, .opc2 = 5,
+ .access = PL0_RW, .accessfn = access_tpidr2,
+ .fieldoffset = offsetof(CPUARMState, cp15.tpidr2_el0) },
+ { .name = "SVCR", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 3, .crn = 4, .crm = 2, .opc2 = 2,
+ .access = PL0_RW, .type = ARM_CP_SME,
+ .fieldoffset = offsetof(CPUARMState, svcr),
+ .writefn = svcr_write, .raw_writefn = raw_write },
+ { .name = "SMCR_EL1", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 0, .crn = 1, .crm = 2, .opc2 = 6,
+ .access = PL1_RW, .type = ARM_CP_SME,
+ .fieldoffset = offsetof(CPUARMState, vfp.smcr_el[1]),
+ .writefn = smcr_write, .raw_writefn = raw_write },
+ { .name = "SMCR_EL2", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 4, .crn = 1, .crm = 2, .opc2 = 6,
+ .access = PL2_RW, .type = ARM_CP_SME,
+ .fieldoffset = offsetof(CPUARMState, vfp.smcr_el[2]),
+ .writefn = smcr_write, .raw_writefn = raw_write },
+ { .name = "SMCR_EL3", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 6, .crn = 1, .crm = 2, .opc2 = 6,
+ .access = PL3_RW, .type = ARM_CP_SME,
+ .fieldoffset = offsetof(CPUARMState, vfp.smcr_el[3]),
+ .writefn = smcr_write, .raw_writefn = raw_write },
+ { .name = "SMIDR_EL1", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 1, .crn = 0, .crm = 0, .opc2 = 6,
+ .access = PL1_R, .accessfn = access_aa64_tid1,
+ /*
+ * IMPLEMENTOR = 0 (software)
+ * REVISION = 0 (implementation defined)
+ * SMPS = 0 (no streaming execution priority in QEMU)
+ * AFFINITY = 0 (streaming sve mode not shared with other PEs)
+ */
+ .type = ARM_CP_CONST, .resetvalue = 0, },
+ /*
+ * Because SMIDR_EL1.SMPS is 0, SMPRI_EL1 and SMPRIMAP_EL2 are RES 0.
+ */
+ { .name = "SMPRI_EL1", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 0, .crn = 1, .crm = 2, .opc2 = 4,
+ .access = PL1_RW, .accessfn = access_esm,
+ .type = ARM_CP_CONST, .resetvalue = 0 },
+ { .name = "SMPRIMAP_EL2", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 4, .crn = 1, .crm = 2, .opc2 = 5,
+ .access = PL2_RW, .accessfn = access_esm,
+ .type = ARM_CP_CONST, .resetvalue = 0 },
+};
+#endif /* TARGET_AARCH64 */
+
void hw_watchpoint_update(ARMCPU *cpu, int n)
{
CPUARMState *env = &cpu->env;
@@ -8440,6 +8623,9 @@ void register_cp_regs_for_features(ARMCPU *cpu)
}
#ifdef TARGET_AARCH64
+ if (cpu_isar_feature(aa64_sme, cpu)) {
+ define_arm_cp_regs(cpu, sme_reginfo);
+ }
if (cpu_isar_feature(aa64_pauth, cpu)) {
define_arm_cp_regs(cpu, pauth_reginfo);
}
@@ -10329,13 +10515,13 @@ static void handle_semihosting(CPUState *cs)
qemu_log_mask(CPU_LOG_INT,
"...handling as semihosting call 0x%" PRIx64 "\n",
env->xregs[0]);
- env->xregs[0] = do_common_semihosting(cs);
+ do_common_semihosting(cs);
env->pc += 4;
} else {
qemu_log_mask(CPU_LOG_INT,
"...handling as semihosting call 0x%x\n",
env->regs[0]);
- env->regs[0] = do_common_semihosting(cs);
+ do_common_semihosting(cs);
env->regs[15] += env->thumb ? 2 : 4;
}
}
@@ -11165,6 +11351,19 @@ static CPUARMTBFlags rebuild_hflags_a64(CPUARMState *env, int el, int fp_el,
}
DP_TBFLAG_A64(flags, SVEEXC_EL, sve_el);
}
+ if (cpu_isar_feature(aa64_sme, env_archcpu(env))) {
+ int sme_el = sme_exception_el(env, el);
+
+ DP_TBFLAG_A64(flags, SMEEXC_EL, sme_el);
+ if (sme_el == 0) {
+ /* Similarly, do not compute SVL if SME is disabled. */
+ DP_TBFLAG_A64(flags, SVL, sve_vqm1_for_el_sm(env, el, true));
+ }
+ if (FIELD_EX64(env->svcr, SVCR, SM)) {
+ DP_TBFLAG_A64(flags, PSTATE_SM, 1);
+ }
+ DP_TBFLAG_A64(flags, PSTATE_ZA, FIELD_EX64(env->svcr, SVCR, ZA));
+ }
sctlr = regime_sctlr(env, stage1);
diff --git a/target/arm/helper.h b/target/arm/helper.h
index 07d45faf49..3a8ce42ab0 100644
--- a/target/arm/helper.h
+++ b/target/arm/helper.h
@@ -1022,6 +1022,7 @@ DEF_HELPER_FLAGS_6(gvec_bfmlal_idx, TCG_CALL_NO_RWG,
#ifdef TARGET_AARCH64
#include "helper-a64.h"
#include "helper-sve.h"
+#include "helper-sme.h"
#endif
#include "helper-mve.h"
diff --git a/target/arm/internals.h b/target/arm/internals.h
index 6f94f3019d..c66f74a0db 100644
--- a/target/arm/internals.h
+++ b/target/arm/internals.h
@@ -1288,6 +1288,10 @@ int arm_gdb_get_svereg(CPUARMState *env, GByteArray *buf, int reg);
int arm_gdb_set_svereg(CPUARMState *env, uint8_t *buf, int reg);
int aarch64_fpu_gdb_get_reg(CPUARMState *env, GByteArray *buf, int reg);
int aarch64_fpu_gdb_set_reg(CPUARMState *env, uint8_t *buf, int reg);
+void arm_cpu_sve_finalize(ARMCPU *cpu, Error **errp);
+void arm_cpu_sme_finalize(ARMCPU *cpu, Error **errp);
+void arm_cpu_pauth_finalize(ARMCPU *cpu, Error **errp);
+void arm_cpu_lpa2_finalize(ARMCPU *cpu, Error **errp);
#endif
#ifdef CONFIG_USER_ONLY
diff --git a/target/arm/kvm64.c b/target/arm/kvm64.c
index ff8f65da22..d16d4ea250 100644
--- a/target/arm/kvm64.c
+++ b/target/arm/kvm64.c
@@ -820,7 +820,7 @@ uint32_t kvm_arm_sve_get_vls(CPUState *cs)
static int kvm_arm_sve_set_vls(CPUState *cs)
{
ARMCPU *cpu = ARM_CPU(cs);
- uint64_t vls[KVM_ARM64_SVE_VLS_WORDS] = { cpu->sve_vq_map };
+ uint64_t vls[KVM_ARM64_SVE_VLS_WORDS] = { cpu->sve_vq.map };
struct kvm_one_reg reg = {
.id = KVM_REG_ARM64_SVE_VLS,
.addr = (uint64_t)&vls[0],
diff --git a/target/arm/m_helper.c b/target/arm/m_helper.c
index a740c3e160..308610f6b4 100644
--- a/target/arm/m_helper.c
+++ b/target/arm/m_helper.c
@@ -2373,7 +2373,7 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
"...handling as semihosting call 0x%x\n",
env->regs[0]);
#ifdef CONFIG_TCG
- env->regs[0] = do_common_semihosting(cs);
+ do_common_semihosting(cs);
#else
g_assert_not_reached();
#endif
diff --git a/target/arm/machine.c b/target/arm/machine.c
index 285e387d2c..54c5c62433 100644
--- a/target/arm/machine.c
+++ b/target/arm/machine.c
@@ -167,6 +167,39 @@ static const VMStateDescription vmstate_sve = {
VMSTATE_END_OF_LIST()
}
};
+
+static const VMStateDescription vmstate_vreg = {
+ .name = "vreg",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64_ARRAY(d, ARMVectorReg, ARM_MAX_VQ * 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool za_needed(void *opaque)
+{
+ ARMCPU *cpu = opaque;
+
+ /*
+ * When ZA storage is disabled, its contents are discarded.
+ * It will be zeroed when ZA storage is re-enabled.
+ */
+ return FIELD_EX64(cpu->env.svcr, SVCR, ZA);
+}
+
+static const VMStateDescription vmstate_za = {
+ .name = "cpu/sme",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = za_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(env.zarray, ARMCPU, ARM_MAX_VQ * 16, 0,
+ vmstate_vreg, ARMVectorReg),
+ VMSTATE_END_OF_LIST()
+ }
+};
#endif /* AARCH64 */
static bool serror_needed(void *opaque)
@@ -884,6 +917,7 @@ const VMStateDescription vmstate_arm_cpu = {
&vmstate_m_security,
#ifdef TARGET_AARCH64
&vmstate_sve,
+ &vmstate_za,
#endif
&vmstate_serror,
&vmstate_irq_line_state,
diff --git a/target/arm/meson.build b/target/arm/meson.build
index ac571fc45d..43dc600547 100644
--- a/target/arm/meson.build
+++ b/target/arm/meson.build
@@ -47,6 +47,7 @@ arm_ss.add(when: 'TARGET_AARCH64', if_true: files(
'mte_helper.c',
'pauth_helper.c',
'sve_helper.c',
+ 'sme_helper.c',
'translate-a64.c',
'translate-sve.c',
))
diff --git a/target/arm/ptw.c b/target/arm/ptw.c
index 4d97a24808..da478104f0 100644
--- a/target/arm/ptw.c
+++ b/target/arm/ptw.c
@@ -36,15 +36,29 @@ static const uint8_t pamax_map[] = {
/* The cpu-specific constant value of PAMax; also used by hw/arm/virt. */
unsigned int arm_pamax(ARMCPU *cpu)
{
- unsigned int parange =
- FIELD_EX64(cpu->isar.id_aa64mmfr0, ID_AA64MMFR0, PARANGE);
+ if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) {
+ unsigned int parange =
+ FIELD_EX64(cpu->isar.id_aa64mmfr0, ID_AA64MMFR0, PARANGE);
+
+ /*
+ * id_aa64mmfr0 is a read-only register so values outside of the
+ * supported mappings can be considered an implementation error.
+ */
+ assert(parange < ARRAY_SIZE(pamax_map));
+ return pamax_map[parange];
+ }
/*
- * id_aa64mmfr0 is a read-only register so values outside of the
- * supported mappings can be considered an implementation error.
+ * In machvirt_init, we call arm_pamax on a cpu that is not fully
+ * initialized, so we can't rely on the propagation done in realize.
*/
- assert(parange < ARRAY_SIZE(pamax_map));
- return pamax_map[parange];
+ if (arm_feature(&cpu->env, ARM_FEATURE_LPAE) ||
+ arm_feature(&cpu->env, ARM_FEATURE_V7VE)) {
+ /* v7 with LPAE */
+ return 40;
+ }
+ /* Anything else */
+ return 32;
}
/*
diff --git a/target/arm/sme_helper.c b/target/arm/sme_helper.c
new file mode 100644
index 0000000000..b215725594
--- /dev/null
+++ b/target/arm/sme_helper.c
@@ -0,0 +1,61 @@
+/*
+ * ARM SME Operations
+ *
+ * Copyright (c) 2022 Linaro, Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "internals.h"
+#include "exec/helper-proto.h"
+
+/* ResetSVEState */
+void arm_reset_sve_state(CPUARMState *env)
+{
+ memset(env->vfp.zregs, 0, sizeof(env->vfp.zregs));
+ /* Recall that FFR is stored as pregs[16]. */
+ memset(env->vfp.pregs, 0, sizeof(env->vfp.pregs));
+ vfp_set_fpcr(env, 0x0800009f);
+}
+
+void helper_set_pstate_sm(CPUARMState *env, uint32_t i)
+{
+ if (i == FIELD_EX64(env->svcr, SVCR, SM)) {
+ return;
+ }
+ env->svcr ^= R_SVCR_SM_MASK;
+ arm_reset_sve_state(env);
+}
+
+void helper_set_pstate_za(CPUARMState *env, uint32_t i)
+{
+ if (i == FIELD_EX64(env->svcr, SVCR, ZA)) {
+ return;
+ }
+ env->svcr ^= R_SVCR_ZA_MASK;
+
+ /*
+ * ResetSMEState.
+ *
+ * SetPSTATE_ZA zeros on enable and disable. We can zero this only
+ * on enable: while disabled, the storage is inaccessible and the
+ * value does not matter. We're not saving the storage in vmstate
+ * when disabled either.
+ */
+ if (i) {
+ memset(env->zarray, 0, sizeof(env->zarray));
+ }
+}
diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h
index c105f9e6ba..73df5e3793 100644
--- a/target/arm/syndrome.h
+++ b/target/arm/syndrome.h
@@ -48,6 +48,7 @@ enum arm_exception_class {
EC_AA64_SMC = 0x17,
EC_SYSTEMREGISTERTRAP = 0x18,
EC_SVEACCESSTRAP = 0x19,
+ EC_SMETRAP = 0x1d,
EC_INSNABORT = 0x20,
EC_INSNABORT_SAME_EL = 0x21,
EC_PCALIGNMENT = 0x22,
@@ -68,6 +69,13 @@ enum arm_exception_class {
EC_AA64_BKPT = 0x3c,
};
+typedef enum {
+ SME_ET_AccessTrap,
+ SME_ET_Streaming,
+ SME_ET_NotStreaming,
+ SME_ET_InactiveZA,
+} SMEExceptionType;
+
#define ARM_EL_EC_SHIFT 26
#define ARM_EL_IL_SHIFT 25
#define ARM_EL_ISV_SHIFT 24
@@ -207,6 +215,12 @@ static inline uint32_t syn_sve_access_trap(void)
return EC_SVEACCESSTRAP << ARM_EL_EC_SHIFT;
}
+static inline uint32_t syn_smetrap(SMEExceptionType etype, bool is_16bit)
+{
+ return (EC_SMETRAP << ARM_EL_EC_SHIFT)
+ | (is_16bit ? 0 : ARM_EL_IL) | etype;
+}
+
static inline uint32_t syn_pactrap(void)
{
return EC_PACTRAP << ARM_EL_EC_SHIFT;
diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c
index 4c64546090..c86b97b1d4 100644
--- a/target/arm/translate-a64.c
+++ b/target/arm/translate-a64.c
@@ -1188,6 +1188,22 @@ bool sve_access_check(DisasContext *s)
}
/*
+ * Check that SME access is enabled, raise an exception if not.
+ * Note that this function corresponds to CheckSMEAccess and is
+ * only used directly for cpregs.
+ */
+static bool sme_access_check(DisasContext *s)
+{
+ if (s->sme_excp_el) {
+ gen_exception_insn_el(s, s->pc_curr, EXCP_UDEF,
+ syn_smetrap(SME_ET_AccessTrap, false),
+ s->sme_excp_el);
+ return false;
+ }
+ return true;
+}
+
+/*
* This utility function is for doing register extension with an
* optional shift. You will likely want to pass a temporary for the
* destination register. See DecodeRegExtend() in the ARM ARM.
@@ -1746,6 +1762,30 @@ static void handle_msr_i(DisasContext *s, uint32_t insn,
}
break;
+ case 0x1b: /* SVCR* */
+ if (!dc_isar_feature(aa64_sme, s) || crm < 2 || crm > 7) {
+ goto do_unallocated;
+ }
+ if (sme_access_check(s)) {
+ bool i = crm & 1;
+ bool changed = false;
+
+ if ((crm & 2) && i != s->pstate_sm) {
+ gen_helper_set_pstate_sm(cpu_env, tcg_constant_i32(i));
+ changed = true;
+ }
+ if ((crm & 4) && i != s->pstate_za) {
+ gen_helper_set_pstate_za(cpu_env, tcg_constant_i32(i));
+ changed = true;
+ }
+ if (changed) {
+ gen_rebuild_hflags(s);
+ } else {
+ s->base.is_jmp = DISAS_NEXT;
+ }
+ }
+ break;
+
default:
do_unallocated:
unallocated_encoding(s);
@@ -1958,6 +1998,8 @@ static void handle_sys(DisasContext *s, uint32_t insn, bool isread,
return;
} else if ((ri->type & ARM_CP_SVE) && !sve_access_check(s)) {
return;
+ } else if ((ri->type & ARM_CP_SME) && !sme_access_check(s)) {
+ return;
}
if ((tb_cflags(s->base.tb) & CF_USE_ICOUNT) && (ri->type & ARM_CP_IO)) {
@@ -14603,7 +14645,9 @@ static void aarch64_tr_init_disas_context(DisasContextBase *dcbase,
dc->align_mem = EX_TBFLAG_ANY(tb_flags, ALIGN_MEM);
dc->pstate_il = EX_TBFLAG_ANY(tb_flags, PSTATE__IL);
dc->sve_excp_el = EX_TBFLAG_A64(tb_flags, SVEEXC_EL);
+ dc->sme_excp_el = EX_TBFLAG_A64(tb_flags, SMEEXC_EL);
dc->vl = (EX_TBFLAG_A64(tb_flags, VL) + 1) * 16;
+ dc->svl = (EX_TBFLAG_A64(tb_flags, SVL) + 1) * 16;
dc->pauth_active = EX_TBFLAG_A64(tb_flags, PAUTH_ACTIVE);
dc->bt = EX_TBFLAG_A64(tb_flags, BT);
dc->btype = EX_TBFLAG_A64(tb_flags, BTYPE);
@@ -14611,6 +14655,8 @@ static void aarch64_tr_init_disas_context(DisasContextBase *dcbase,
dc->ata = EX_TBFLAG_A64(tb_flags, ATA);
dc->mte_active[0] = EX_TBFLAG_A64(tb_flags, MTE_ACTIVE);
dc->mte_active[1] = EX_TBFLAG_A64(tb_flags, MTE0_ACTIVE);
+ dc->pstate_sm = EX_TBFLAG_A64(tb_flags, PSTATE_SM);
+ dc->pstate_za = EX_TBFLAG_A64(tb_flags, PSTATE_ZA);
dc->vec_len = 0;
dc->vec_stride = 0;
dc->cp_regs = arm_cpu->cp_regs;
diff --git a/target/arm/translate-a64.h b/target/arm/translate-a64.h
index dbc917ee65..f0970c6b8c 100644
--- a/target/arm/translate-a64.h
+++ b/target/arm/translate-a64.h
@@ -107,6 +107,44 @@ static inline int vec_full_reg_size(DisasContext *s)
return s->vl;
}
+/*
+ * Return the offset info CPUARMState of the predicate vector register Pn.
+ * Note for this purpose, FFR is P16.
+ */
+static inline int pred_full_reg_offset(DisasContext *s, int regno)
+{
+ return offsetof(CPUARMState, vfp.pregs[regno]);
+}
+
+/* Return the byte size of the whole predicate register, VL / 64. */
+static inline int pred_full_reg_size(DisasContext *s)
+{
+ return s->vl >> 3;
+}
+
+/*
+ * Round up the size of a register to a size allowed by
+ * the tcg vector infrastructure. Any operation which uses this
+ * size may assume that the bits above pred_full_reg_size are zero,
+ * and must leave them the same way.
+ *
+ * Note that this is not needed for the vector registers as they
+ * are always properly sized for tcg vectors.
+ */
+static inline int size_for_gvec(int size)
+{
+ if (size <= 8) {
+ return 8;
+ } else {
+ return QEMU_ALIGN_UP(size, 16);
+ }
+}
+
+static inline int pred_gvec_reg_size(DisasContext *s)
+{
+ return size_for_gvec(pred_full_reg_size(s));
+}
+
bool disas_sve(DisasContext *, uint32_t);
void gen_gvec_rax1(unsigned vece, uint32_t rd_ofs, uint32_t rn_ofs,
diff --git a/target/arm/translate-sve.c b/target/arm/translate-sve.c
index 67761bf2cc..62b5f3040c 100644
--- a/target/arm/translate-sve.c
+++ b/target/arm/translate-sve.c
@@ -100,42 +100,6 @@ static inline int msz_dtype(DisasContext *s, int msz)
* Implement all of the translator functions referenced by the decoder.
*/
-/* Return the offset info CPUARMState of the predicate vector register Pn.
- * Note for this purpose, FFR is P16.
- */
-static inline int pred_full_reg_offset(DisasContext *s, int regno)
-{
- return offsetof(CPUARMState, vfp.pregs[regno]);
-}
-
-/* Return the byte size of the whole predicate register, VL / 64. */
-static inline int pred_full_reg_size(DisasContext *s)
-{
- return s->vl >> 3;
-}
-
-/* Round up the size of a register to a size allowed by
- * the tcg vector infrastructure. Any operation which uses this
- * size may assume that the bits above pred_full_reg_size are zero,
- * and must leave them the same way.
- *
- * Note that this is not needed for the vector registers as they
- * are always properly sized for tcg vectors.
- */
-static int size_for_gvec(int size)
-{
- if (size <= 8) {
- return 8;
- } else {
- return QEMU_ALIGN_UP(size, 16);
- }
-}
-
-static int pred_gvec_reg_size(DisasContext *s)
-{
- return size_for_gvec(pred_full_reg_size(s));
-}
-
/* Invoke an out-of-line helper on 2 Zregs. */
static bool gen_gvec_ool_zz(DisasContext *s, gen_helper_gvec_2 *fn,
int rd, int rn, int data)
diff --git a/target/arm/translate.h b/target/arm/translate.h
index 88dc18a034..22fd882368 100644
--- a/target/arm/translate.h
+++ b/target/arm/translate.h
@@ -42,7 +42,9 @@ typedef struct DisasContext {
bool ns; /* Use non-secure CPREG bank on access */
int fp_excp_el; /* FP exception EL or 0 if enabled */
int sve_excp_el; /* SVE exception EL or 0 if enabled */
+ int sme_excp_el; /* SME exception EL or 0 if enabled */
int vl; /* current vector length in bytes */
+ int svl; /* current streaming vector length in bytes */
bool vfp_enabled; /* FP enabled via FPSCR.EN */
int vec_len;
int vec_stride;
@@ -96,6 +98,10 @@ typedef struct DisasContext {
bool align_mem;
/* True if PSTATE.IL is set */
bool pstate_il;
+ /* True if PSTATE.SM is set. */
+ bool pstate_sm;
+ /* True if PSTATE.ZA is set. */
+ bool pstate_za;
/* True if MVE insns are definitely not predicated by VPR or LTPSIZE */
bool mve_no_pred;
/*
diff --git a/target/m68k/m68k-semi.c b/target/m68k/m68k-semi.c
index 37343d47e2..d0697ddbd1 100644
--- a/target/m68k/m68k-semi.c
+++ b/target/m68k/m68k-semi.c
@@ -21,13 +21,8 @@
#include "cpu.h"
#include "exec/gdbstub.h"
-#if defined(CONFIG_USER_ONLY)
-#include "qemu.h"
-#define SEMIHOSTING_HEAP_SIZE (128 * 1024 * 1024)
-#else
-#include "exec/softmmu-semi.h"
+#include "semihosting/softmmu-uaccess.h"
#include "hw/boards.h"
-#endif
#include "qemu/log.h"
#define HOSTED_EXIT 0
@@ -45,38 +40,6 @@
#define HOSTED_ISATTY 12
#define HOSTED_SYSTEM 13
-typedef uint32_t gdb_mode_t;
-typedef uint32_t gdb_time_t;
-
-struct m68k_gdb_stat {
- uint32_t gdb_st_dev; /* device */
- uint32_t gdb_st_ino; /* inode */
- gdb_mode_t gdb_st_mode; /* protection */
- uint32_t gdb_st_nlink; /* number of hard links */
- uint32_t gdb_st_uid; /* user ID of owner */
- uint32_t gdb_st_gid; /* group ID of owner */
- uint32_t gdb_st_rdev; /* device type (if inode device) */
- uint64_t gdb_st_size; /* total size, in bytes */
- uint64_t gdb_st_blksize; /* blocksize for filesystem I/O */
- uint64_t gdb_st_blocks; /* number of blocks allocated */
- gdb_time_t gdb_st_atime; /* time of last access */
- gdb_time_t gdb_st_mtime; /* time of last modification */
- gdb_time_t gdb_st_ctime; /* time of last change */
-} QEMU_PACKED;
-
-struct gdb_timeval {
- gdb_time_t tv_sec; /* second */
- uint64_t tv_usec; /* microsecond */
-} QEMU_PACKED;
-
-#define GDB_O_RDONLY 0x0
-#define GDB_O_WRONLY 0x1
-#define GDB_O_RDWR 0x2
-#define GDB_O_APPEND 0x8
-#define GDB_O_CREAT 0x200
-#define GDB_O_TRUNC 0x400
-#define GDB_O_EXCL 0x800
-
static int translate_openflags(int flags)
{
int hf;
@@ -98,11 +61,13 @@ static int translate_openflags(int flags)
static void translate_stat(CPUM68KState *env, target_ulong addr, struct stat *s)
{
- struct m68k_gdb_stat *p;
+ struct gdb_stat *p;
- if (!(p = lock_user(VERIFY_WRITE, addr, sizeof(struct m68k_gdb_stat), 0)))
+ p = lock_user(VERIFY_WRITE, addr, sizeof(struct gdb_stat), 0);
+ if (!p) {
/* FIXME - should this return an error code? */
return;
+ }
p->gdb_st_dev = cpu_to_be32(s->st_dev);
p->gdb_st_ino = cpu_to_be32(s->st_ino);
p->gdb_st_mode = cpu_to_be32(s->st_mode);
@@ -122,11 +87,14 @@ static void translate_stat(CPUM68KState *env, target_ulong addr, struct stat *s)
p->gdb_st_atime = cpu_to_be32(s->st_atime);
p->gdb_st_mtime = cpu_to_be32(s->st_mtime);
p->gdb_st_ctime = cpu_to_be32(s->st_ctime);
- unlock_user(p, addr, sizeof(struct m68k_gdb_stat));
+ unlock_user(p, addr, sizeof(struct gdb_stat));
}
-static void m68k_semi_return_u32(CPUM68KState *env, uint32_t ret, uint32_t err)
+static void m68k_semi_u32_cb(CPUState *cs, uint64_t ret, int err)
{
+ M68kCPU *cpu = M68K_CPU(cs);
+ CPUM68KState *env = &cpu->env;
+
target_ulong args = env->dregs[1];
if (put_user_u32(ret, args) ||
put_user_u32(err, args + 4)) {
@@ -140,8 +108,11 @@ static void m68k_semi_return_u32(CPUM68KState *env, uint32_t ret, uint32_t err)
}
}
-static void m68k_semi_return_u64(CPUM68KState *env, uint64_t ret, uint32_t err)
+static void m68k_semi_u64_cb(CPUState *cs, uint64_t ret, int err)
{
+ M68kCPU *cpu = M68K_CPU(cs);
+ CPUM68KState *env = &cpu->env;
+
target_ulong args = env->dregs[1];
if (put_user_u32(ret >> 32, args) ||
put_user_u32(ret, args + 4) ||
@@ -152,25 +123,6 @@ static void m68k_semi_return_u64(CPUM68KState *env, uint64_t ret, uint32_t err)
}
}
-static int m68k_semi_is_fseek;
-
-static void m68k_semi_cb(CPUState *cs, target_ulong ret, target_ulong err)
-{
- M68kCPU *cpu = M68K_CPU(cs);
- CPUM68KState *env = &cpu->env;
-
- if (m68k_semi_is_fseek) {
- /*
- * FIXME: We've already lost the high bits of the fseek
- * return value.
- */
- m68k_semi_return_u64(env, ret, err);
- m68k_semi_is_fseek = 0;
- } else {
- m68k_semi_return_u32(env, ret, err);
- }
-}
-
/*
* Read the input value from the argument block; fail the semihosting
* call if the memory read fails.
@@ -185,6 +137,7 @@ static void m68k_semi_cb(CPUState *cs, target_ulong ret, target_ulong err)
void do_m68k_semihosting(CPUM68KState *env, int nr)
{
+ CPUState *cs = env_cpu(env);
uint32_t args;
target_ulong arg0, arg1, arg2, arg3;
void *p;
@@ -203,7 +156,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(2);
GET_ARG(3);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "open,%s,%x,%x", arg0, (int)arg1,
+ gdb_do_syscall(m68k_semi_u32_cb, "open,%s,%x,%x", arg0, (int)arg1,
arg2, arg3);
return;
} else {
@@ -224,7 +177,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
int fd = arg0;
if (fd > 2) {
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "close,%x", arg0);
+ gdb_do_syscall(m68k_semi_u32_cb, "close,%x", arg0);
return;
} else {
result = close(fd);
@@ -240,7 +193,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(2);
len = arg2;
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "read,%x,%x,%x",
+ gdb_do_syscall(m68k_semi_u32_cb, "read,%x,%x,%x",
arg0, arg1, len);
return;
} else {
@@ -260,7 +213,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(2);
len = arg2;
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "write,%x,%x,%x",
+ gdb_do_syscall(m68k_semi_u32_cb, "write,%x,%x,%x",
arg0, arg1, len);
return;
} else {
@@ -283,12 +236,11 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(3);
off = (uint32_t)arg2 | ((uint64_t)arg1 << 32);
if (use_gdb_syscalls()) {
- m68k_semi_is_fseek = 1;
- gdb_do_syscall(m68k_semi_cb, "fseek,%x,%lx,%x",
+ gdb_do_syscall(m68k_semi_u64_cb, "fseek,%x,%lx,%x",
arg0, off, arg3);
} else {
off = lseek(arg0, off, arg3);
- m68k_semi_return_u64(env, off, errno);
+ m68k_semi_u64_cb(cs, off, errno);
}
return;
}
@@ -298,7 +250,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(2);
GET_ARG(3);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "rename,%s,%s",
+ gdb_do_syscall(m68k_semi_u32_cb, "rename,%s,%s",
arg0, (int)arg1, arg2, (int)arg3);
return;
} else {
@@ -318,7 +270,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "unlink,%s",
+ gdb_do_syscall(m68k_semi_u32_cb, "unlink,%s",
arg0, (int)arg1);
return;
} else {
@@ -337,7 +289,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(1);
GET_ARG(2);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "stat,%s,%x",
+ gdb_do_syscall(m68k_semi_u32_cb, "stat,%s,%x",
arg0, (int)arg1, arg2);
return;
} else {
@@ -359,7 +311,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "fstat,%x,%x",
+ gdb_do_syscall(m68k_semi_u32_cb, "fstat,%x,%x",
arg0, arg1);
return;
} else {
@@ -374,7 +326,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "gettimeofday,%x,%x",
+ gdb_do_syscall(m68k_semi_u32_cb, "gettimeofday,%x,%x",
arg0, arg1);
return;
} else {
@@ -395,7 +347,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
case HOSTED_ISATTY:
GET_ARG(0);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "isatty,%x", arg0);
+ gdb_do_syscall(m68k_semi_u32_cb, "isatty,%x", arg0);
return;
} else {
result = isatty(arg0);
@@ -405,7 +357,7 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
- gdb_do_syscall(m68k_semi_cb, "system,%s",
+ gdb_do_syscall(m68k_semi_u32_cb, "system,%s",
arg0, (int)arg1);
return;
} else {
@@ -420,48 +372,17 @@ void do_m68k_semihosting(CPUM68KState *env, int nr)
}
break;
case HOSTED_INIT_SIM:
-#if defined(CONFIG_USER_ONLY)
- {
- CPUState *cs = env_cpu(env);
- TaskState *ts = cs->opaque;
- /* Allocate the heap using sbrk. */
- if (!ts->heap_limit) {
- abi_ulong ret;
- uint32_t size;
- uint32_t base;
-
- base = do_brk(0);
- size = SEMIHOSTING_HEAP_SIZE;
- /* Try a big heap, and reduce the size if that fails. */
- for (;;) {
- ret = do_brk(base + size);
- if (ret >= (base + size)) {
- break;
- }
- size >>= 1;
- }
- ts->heap_limit = base + size;
- }
- /*
- * This call may happen before we have writable memory, so return
- * values directly in registers.
- */
- env->dregs[1] = ts->heap_limit;
- env->aregs[7] = ts->stack_base;
- }
-#else
/*
* FIXME: This is wrong for boards where RAM does not start at
* address zero.
*/
env->dregs[1] = current_machine->ram_size;
env->aregs[7] = current_machine->ram_size;
-#endif
return;
default:
cpu_abort(env_cpu(env), "Unsupported semihosting syscall %d\n", nr);
result = 0;
}
failed:
- m68k_semi_return_u32(env, result, errno);
+ m68k_semi_u32_cb(cs, result, errno);
}
diff --git a/target/m68k/meson.build b/target/m68k/meson.build
index 05cd9fbd1e..27d2d7ba87 100644
--- a/target/m68k/meson.build
+++ b/target/m68k/meson.build
@@ -4,14 +4,16 @@ m68k_ss.add(files(
'fpu_helper.c',
'gdbstub.c',
'helper.c',
- 'm68k-semi.c',
'op_helper.c',
'softfloat.c',
'translate.c',
))
m68k_softmmu_ss = ss.source_set()
-m68k_softmmu_ss.add(files('monitor.c'))
+m68k_softmmu_ss.add(files(
+ 'm68k-semi.c',
+ 'monitor.c'
+))
target_arch += {'m68k': m68k_ss}
target_softmmu_arch += {'m68k': m68k_softmmu_ss}
diff --git a/target/mips/cpu.h b/target/mips/cpu.h
index 42efa989e4..0a085643a3 100644
--- a/target/mips/cpu.h
+++ b/target/mips/cpu.h
@@ -1252,8 +1252,9 @@ enum {
EXCP_MSAFPE,
EXCP_TLBXI,
EXCP_TLBRI,
+ EXCP_SEMIHOST,
- EXCP_LAST = EXCP_TLBRI,
+ EXCP_LAST = EXCP_SEMIHOST,
};
/*
diff --git a/target/mips/tcg/exception.c b/target/mips/tcg/exception.c
index 0b21e0872b..2bd77a61de 100644
--- a/target/mips/tcg/exception.c
+++ b/target/mips/tcg/exception.c
@@ -125,6 +125,7 @@ static const char * const excp_names[EXCP_LAST + 1] = {
[EXCP_TLBRI] = "TLB read-inhibit",
[EXCP_MSADIS] = "MSA disabled",
[EXCP_MSAFPE] = "MSA floating point",
+ [EXCP_SEMIHOST] = "Semihosting",
};
const char *mips_exception_name(int32_t exception)
diff --git a/target/mips/tcg/micromips_translate.c.inc b/target/mips/tcg/micromips_translate.c.inc
index fc6ede75b8..274caf2c3c 100644
--- a/target/mips/tcg/micromips_translate.c.inc
+++ b/target/mips/tcg/micromips_translate.c.inc
@@ -826,7 +826,7 @@ static void gen_pool16c_insn(DisasContext *ctx)
break;
case SDBBP16:
if (is_uhi(extract32(ctx->opcode, 0, 4))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
/*
* XXX: not clear which exception should be raised
@@ -942,7 +942,7 @@ static void gen_pool16c_r6_insn(DisasContext *ctx)
case R6_SDBBP16:
/* SDBBP16 */
if (is_uhi(extract32(ctx->opcode, 6, 4))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
if (ctx->hflags & MIPS_HFLAG_SBRI) {
generate_exception(ctx, EXCP_RI);
@@ -1311,7 +1311,7 @@ static void gen_pool32axf(CPUMIPSState *env, DisasContext *ctx, int rt, int rs)
break;
case SDBBP:
if (is_uhi(extract32(ctx->opcode, 16, 10))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
check_insn(ctx, ISA_MIPS_R1);
if (ctx->hflags & MIPS_HFLAG_SBRI) {
diff --git a/target/mips/tcg/mips16e_translate.c.inc b/target/mips/tcg/mips16e_translate.c.inc
index f57e0a5f2a..0a3ba252e4 100644
--- a/target/mips/tcg/mips16e_translate.c.inc
+++ b/target/mips/tcg/mips16e_translate.c.inc
@@ -952,7 +952,7 @@ static int decode_ase_mips16e(CPUMIPSState *env, DisasContext *ctx)
break;
case RR_SDBBP:
if (is_uhi(extract32(ctx->opcode, 5, 6))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
/*
* XXX: not clear which exception should be raised
diff --git a/target/mips/tcg/nanomips_translate.c.inc b/target/mips/tcg/nanomips_translate.c.inc
index c0ba2bf1b1..ecb0ebed57 100644
--- a/target/mips/tcg/nanomips_translate.c.inc
+++ b/target/mips/tcg/nanomips_translate.c.inc
@@ -3695,7 +3695,7 @@ static int decode_nanomips_32_48_opc(CPUMIPSState *env, DisasContext *ctx)
break;
case NM_SDBBP:
if (is_uhi(extract32(ctx->opcode, 0, 19))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
if (ctx->hflags & MIPS_HFLAG_SBRI) {
gen_reserved_instruction(ctx);
@@ -4634,7 +4634,7 @@ static int decode_isa_nanomips(CPUMIPSState *env, DisasContext *ctx)
break;
case NM_SDBBP16:
if (is_uhi(extract32(ctx->opcode, 0, 3))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
if (ctx->hflags & MIPS_HFLAG_SBRI) {
gen_reserved_instruction(ctx);
diff --git a/target/mips/tcg/sysemu/mips-semi.c b/target/mips/tcg/sysemu/mips-semi.c
index b4a383ae90..67c35fe7f9 100644
--- a/target/mips/tcg/sysemu/mips-semi.c
+++ b/target/mips/tcg/sysemu/mips-semi.c
@@ -20,10 +20,10 @@
#include "qemu/osdep.h"
#include "cpu.h"
#include "qemu/log.h"
-#include "exec/helper-proto.h"
-#include "exec/softmmu-semi.h"
+#include "semihosting/softmmu-uaccess.h"
#include "semihosting/semihost.h"
#include "semihosting/console.h"
+#include "internal.h"
typedef enum UHIOp {
UHI_exit = 1,
@@ -74,6 +74,46 @@ enum UHIOpenFlags {
UHIOpen_EXCL = 0x800
};
+enum UHIErrno {
+ UHI_EACCESS = 13,
+ UHI_EAGAIN = 11,
+ UHI_EBADF = 9,
+ UHI_EBADMSG = 77,
+ UHI_EBUSY = 16,
+ UHI_ECONNRESET = 104,
+ UHI_EEXIST = 17,
+ UHI_EFBIG = 27,
+ UHI_EINTR = 4,
+ UHI_EINVAL = 22,
+ UHI_EIO = 5,
+ UHI_EISDIR = 21,
+ UHI_ELOOP = 92,
+ UHI_EMFILE = 24,
+ UHI_EMLINK = 31,
+ UHI_ENAMETOOLONG = 91,
+ UHI_ENETDOWN = 115,
+ UHI_ENETUNREACH = 114,
+ UHI_ENFILE = 23,
+ UHI_ENOBUFS = 105,
+ UHI_ENOENT = 2,
+ UHI_ENOMEM = 12,
+ UHI_ENOSPC = 28,
+ UHI_ENOSR = 63,
+ UHI_ENOTCONN = 128,
+ UHI_ENOTDIR = 20,
+ UHI_ENXIO = 6,
+ UHI_EOVERFLOW = 139,
+ UHI_EPERM = 1,
+ UHI_EPIPE = 32,
+ UHI_ERANGE = 34,
+ UHI_EROFS = 30,
+ UHI_ESPIPE = 29,
+ UHI_ETIMEDOUT = 116,
+ UHI_ETXTBSY = 26,
+ UHI_EWOULDBLOCK = 11,
+ UHI_EXDEV = 18,
+};
+
static int errno_mips(int host_errno)
{
/* Errno values taken from asm-mips/errno.h */
@@ -142,8 +182,8 @@ static int get_open_flags(target_ulong target_flags)
return open_flags;
}
-static int write_to_file(CPUMIPSState *env, target_ulong fd, target_ulong vaddr,
- target_ulong len, target_ulong offset)
+static int write_to_file(CPUMIPSState *env, target_ulong fd,
+ target_ulong vaddr, target_ulong len)
{
int num_of_bytes;
void *dst = lock_user(VERIFY_READ, vaddr, len, 1);
@@ -152,23 +192,14 @@ static int write_to_file(CPUMIPSState *env, target_ulong fd, target_ulong vaddr,
return -1;
}
- if (offset) {
-#ifdef _WIN32
- num_of_bytes = 0;
-#else
- num_of_bytes = pwrite(fd, dst, len, offset);
-#endif
- } else {
- num_of_bytes = write(fd, dst, len);
- }
+ num_of_bytes = write(fd, dst, len);
unlock_user(dst, vaddr, 0);
return num_of_bytes;
}
static int read_from_file(CPUMIPSState *env, target_ulong fd,
- target_ulong vaddr, target_ulong len,
- target_ulong offset)
+ target_ulong vaddr, target_ulong len)
{
int num_of_bytes;
void *dst = lock_user(VERIFY_WRITE, vaddr, len, 0);
@@ -177,15 +208,7 @@ static int read_from_file(CPUMIPSState *env, target_ulong fd,
return -1;
}
- if (offset) {
-#ifdef _WIN32
- num_of_bytes = 0;
-#else
- num_of_bytes = pread(fd, dst, len, offset);
-#endif
- } else {
- num_of_bytes = read(fd, dst, len);
- }
+ num_of_bytes = read(fd, dst, len);
unlock_user(dst, vaddr, len);
return num_of_bytes;
@@ -238,7 +261,7 @@ static int copy_argn_to_target(CPUMIPSState *env, int arg_num,
unlock_user(p, gpr, 0); \
} while (0)
-void helper_do_semihosting(CPUMIPSState *env)
+void mips_semihosting(CPUMIPSState *env)
{
target_ulong *gpr = env->active_tc.gpr;
const UHIOp op = gpr[25];
@@ -272,11 +295,11 @@ void helper_do_semihosting(CPUMIPSState *env)
gpr[3] = errno_mips(errno);
break;
case UHI_read:
- gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], 0);
+ gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6]);
gpr[3] = errno_mips(errno);
break;
case UHI_write:
- gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], 0);
+ gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6]);
gpr[3] = errno_mips(errno);
break;
case UHI_lseek:
@@ -342,14 +365,6 @@ void helper_do_semihosting(CPUMIPSState *env)
FREE_TARGET_STRING(p, gpr[4]);
abort();
break;
- case UHI_pread:
- gpr[2] = read_from_file(env, gpr[4], gpr[5], gpr[6], gpr[7]);
- gpr[3] = errno_mips(errno);
- break;
- case UHI_pwrite:
- gpr[2] = write_to_file(env, gpr[4], gpr[5], gpr[6], gpr[7]);
- gpr[3] = errno_mips(errno);
- break;
#ifndef _WIN32
case UHI_link:
GET_TARGET_STRINGS_2(p, gpr[4], p2, gpr[5]);
diff --git a/target/mips/tcg/sysemu/tlb_helper.c b/target/mips/tcg/sysemu/tlb_helper.c
index 73254d1929..57ffad2902 100644
--- a/target/mips/tcg/sysemu/tlb_helper.c
+++ b/target/mips/tcg/sysemu/tlb_helper.c
@@ -1053,6 +1053,10 @@ void mips_cpu_do_interrupt(CPUState *cs)
}
offset = 0x180;
switch (cs->exception_index) {
+ case EXCP_SEMIHOST:
+ cs->exception_index = EXCP_NONE;
+ mips_semihosting(env);
+ return;
case EXCP_DSS:
env->CP0_Debug |= 1 << CP0DB_DSS;
/*
diff --git a/target/mips/tcg/sysemu_helper.h.inc b/target/mips/tcg/sysemu_helper.h.inc
index 4353a966f9..af585b5d9c 100644
--- a/target/mips/tcg/sysemu_helper.h.inc
+++ b/target/mips/tcg/sysemu_helper.h.inc
@@ -9,8 +9,6 @@
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
-DEF_HELPER_1(do_semihosting, void, env)
-
/* CP0 helpers */
DEF_HELPER_1(mfc0_mvpcontrol, tl, env)
DEF_HELPER_1(mfc0_mvpconf0, tl, env)
diff --git a/target/mips/tcg/tcg-internal.h b/target/mips/tcg/tcg-internal.h
index 993720b00c..1d27fa2ff9 100644
--- a/target/mips/tcg/tcg-internal.h
+++ b/target/mips/tcg/tcg-internal.h
@@ -62,6 +62,8 @@ bool mips_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
MMUAccessType access_type, int mmu_idx,
bool probe, uintptr_t retaddr);
+void mips_semihosting(CPUMIPSState *env);
+
#endif /* !CONFIG_USER_ONLY */
#endif
diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c
index 5f460fb687..d9d7692765 100644
--- a/target/mips/tcg/translate.c
+++ b/target/mips/tcg/translate.c
@@ -12094,14 +12094,6 @@ static inline bool is_uhi(int sdbbp_code)
#endif
}
-#ifdef CONFIG_USER_ONLY
-/* The above should dead-code away any calls to this..*/
-static inline void gen_helper_do_semihosting(void *env)
-{
- g_assert_not_reached();
-}
-#endif
-
void gen_ldxs(DisasContext *ctx, int base, int index, int rd)
{
TCGv t0 = tcg_temp_new();
@@ -13910,7 +13902,7 @@ static void decode_opc_special_r6(CPUMIPSState *env, DisasContext *ctx)
break;
case R6_OPC_SDBBP:
if (is_uhi(extract32(ctx->opcode, 6, 20))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
if (ctx->hflags & MIPS_HFLAG_SBRI) {
gen_reserved_instruction(ctx);
@@ -14322,7 +14314,7 @@ static void decode_opc_special2_legacy(CPUMIPSState *env, DisasContext *ctx)
break;
case OPC_SDBBP:
if (is_uhi(extract32(ctx->opcode, 6, 20))) {
- gen_helper_do_semihosting(cpu_env);
+ generate_exception_end(ctx, EXCP_SEMIHOST);
} else {
/*
* XXX: not clear which exception should be raised
diff --git a/target/nios2/meson.build b/target/nios2/meson.build
index 2bd60ba306..c6e2243cc3 100644
--- a/target/nios2/meson.build
+++ b/target/nios2/meson.build
@@ -1,7 +1,6 @@
nios2_ss = ss.source_set()
nios2_ss.add(files(
'cpu.c',
- 'nios2-semi.c',
'op_helper.c',
'translate.c',
))
@@ -10,7 +9,8 @@ nios2_softmmu_ss = ss.source_set()
nios2_softmmu_ss.add(files(
'helper.c',
'monitor.c',
- 'mmu.c'
+ 'mmu.c',
+ 'nios2-semi.c',
))
target_arch += {'nios2': nios2_ss}
diff --git a/target/nios2/nios2-semi.c b/target/nios2/nios2-semi.c
index ec88474a73..55061bb2dc 100644
--- a/target/nios2/nios2-semi.c
+++ b/target/nios2/nios2-semi.c
@@ -22,14 +22,9 @@
*/
#include "qemu/osdep.h"
-
#include "cpu.h"
#include "exec/gdbstub.h"
-#if defined(CONFIG_USER_ONLY)
-#include "qemu.h"
-#else
-#include "exec/softmmu-semi.h"
-#endif
+#include "semihosting/softmmu-uaccess.h"
#include "qemu/log.h"
#define HOSTED_EXIT 0
@@ -47,38 +42,6 @@
#define HOSTED_ISATTY 12
#define HOSTED_SYSTEM 13
-typedef uint32_t gdb_mode_t;
-typedef uint32_t gdb_time_t;
-
-struct nios2_gdb_stat {
- uint32_t gdb_st_dev; /* device */
- uint32_t gdb_st_ino; /* inode */
- gdb_mode_t gdb_st_mode; /* protection */
- uint32_t gdb_st_nlink; /* number of hard links */
- uint32_t gdb_st_uid; /* user ID of owner */
- uint32_t gdb_st_gid; /* group ID of owner */
- uint32_t gdb_st_rdev; /* device type (if inode device) */
- uint64_t gdb_st_size; /* total size, in bytes */
- uint64_t gdb_st_blksize; /* blocksize for filesystem I/O */
- uint64_t gdb_st_blocks; /* number of blocks allocated */
- gdb_time_t gdb_st_atime; /* time of last access */
- gdb_time_t gdb_st_mtime; /* time of last modification */
- gdb_time_t gdb_st_ctime; /* time of last change */
-} QEMU_PACKED;
-
-struct gdb_timeval {
- gdb_time_t tv_sec; /* second */
- uint64_t tv_usec; /* microsecond */
-} QEMU_PACKED;
-
-#define GDB_O_RDONLY 0x0
-#define GDB_O_WRONLY 0x1
-#define GDB_O_RDWR 0x2
-#define GDB_O_APPEND 0x8
-#define GDB_O_CREAT 0x200
-#define GDB_O_TRUNC 0x400
-#define GDB_O_EXCL 0x800
-
static int translate_openflags(int flags)
{
int hf;
@@ -110,9 +73,9 @@ static int translate_openflags(int flags)
static bool translate_stat(CPUNios2State *env, target_ulong addr,
struct stat *s)
{
- struct nios2_gdb_stat *p;
+ struct gdb_stat *p;
- p = lock_user(VERIFY_WRITE, addr, sizeof(struct nios2_gdb_stat), 0);
+ p = lock_user(VERIFY_WRITE, addr, sizeof(struct gdb_stat), 0);
if (!p) {
return false;
@@ -136,14 +99,16 @@ static bool translate_stat(CPUNios2State *env, target_ulong addr,
p->gdb_st_atime = cpu_to_be32(s->st_atime);
p->gdb_st_mtime = cpu_to_be32(s->st_mtime);
p->gdb_st_ctime = cpu_to_be32(s->st_ctime);
- unlock_user(p, addr, sizeof(struct nios2_gdb_stat));
+ unlock_user(p, addr, sizeof(struct gdb_stat));
return true;
}
-static void nios2_semi_return_u32(CPUNios2State *env, uint32_t ret,
- uint32_t err)
+static void nios2_semi_u32_cb(CPUState *cs, uint64_t ret, int err)
{
+ Nios2CPU *cpu = NIOS2_CPU(cs);
+ CPUNios2State *env = &cpu->env;
target_ulong args = env->regs[R_ARG1];
+
if (put_user_u32(ret, args) ||
put_user_u32(err, args + 4)) {
/*
@@ -156,10 +121,12 @@ static void nios2_semi_return_u32(CPUNios2State *env, uint32_t ret,
}
}
-static void nios2_semi_return_u64(CPUNios2State *env, uint64_t ret,
- uint32_t err)
+static void nios2_semi_u64_cb(CPUState *cs, uint64_t ret, int err)
{
+ Nios2CPU *cpu = NIOS2_CPU(cs);
+ CPUNios2State *env = &cpu->env;
target_ulong args = env->regs[R_ARG1];
+
if (put_user_u32(ret >> 32, args) ||
put_user_u32(ret, args + 4) ||
put_user_u32(err, args + 8)) {
@@ -169,25 +136,6 @@ static void nios2_semi_return_u64(CPUNios2State *env, uint64_t ret,
}
}
-static int nios2_semi_is_lseek;
-
-static void nios2_semi_cb(CPUState *cs, target_ulong ret, target_ulong err)
-{
- Nios2CPU *cpu = NIOS2_CPU(cs);
- CPUNios2State *env = &cpu->env;
-
- if (nios2_semi_is_lseek) {
- /*
- * FIXME: We've already lost the high bits of the lseek
- * return value.
- */
- nios2_semi_return_u64(env, ret, err);
- nios2_semi_is_lseek = 0;
- } else {
- nios2_semi_return_u32(env, ret, err);
- }
-}
-
/*
* Read the input value from the argument block; fail the semihosting
* call if the memory read fails.
@@ -202,6 +150,7 @@ static void nios2_semi_cb(CPUState *cs, target_ulong ret, target_ulong err)
void do_nios2_semihosting(CPUNios2State *env)
{
+ CPUState *cs = env_cpu(env);
int nr;
uint32_t args;
target_ulong arg0, arg1, arg2, arg3;
@@ -222,7 +171,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(2);
GET_ARG(3);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "open,%s,%x,%x", arg0, (int)arg1,
+ gdb_do_syscall(nios2_semi_u32_cb, "open,%s,%x,%x", arg0, (int)arg1,
arg2, arg3);
return;
} else {
@@ -243,7 +192,7 @@ void do_nios2_semihosting(CPUNios2State *env)
int fd = arg0;
if (fd > 2) {
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "close,%x", arg0);
+ gdb_do_syscall(nios2_semi_u32_cb, "close,%x", arg0);
return;
} else {
result = close(fd);
@@ -259,7 +208,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(2);
len = arg2;
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "read,%x,%x,%x",
+ gdb_do_syscall(nios2_semi_u32_cb, "read,%x,%x,%x",
arg0, arg1, len);
return;
} else {
@@ -279,7 +228,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(2);
len = arg2;
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "write,%x,%x,%x",
+ gdb_do_syscall(nios2_semi_u32_cb, "write,%x,%x,%x",
arg0, arg1, len);
return;
} else {
@@ -302,12 +251,11 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(3);
off = (uint32_t)arg2 | ((uint64_t)arg1 << 32);
if (use_gdb_syscalls()) {
- nios2_semi_is_lseek = 1;
- gdb_do_syscall(nios2_semi_cb, "lseek,%x,%lx,%x",
+ gdb_do_syscall(nios2_semi_u64_cb, "lseek,%x,%lx,%x",
arg0, off, arg3);
} else {
off = lseek(arg0, off, arg3);
- nios2_semi_return_u64(env, off, errno);
+ nios2_semi_u64_cb(cs, off, errno);
}
return;
}
@@ -317,7 +265,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(2);
GET_ARG(3);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "rename,%s,%s",
+ gdb_do_syscall(nios2_semi_u32_cb, "rename,%s,%s",
arg0, (int)arg1, arg2, (int)arg3);
return;
} else {
@@ -337,7 +285,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "unlink,%s",
+ gdb_do_syscall(nios2_semi_u32_cb, "unlink,%s",
arg0, (int)arg1);
return;
} else {
@@ -356,7 +304,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(1);
GET_ARG(2);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "stat,%s,%x",
+ gdb_do_syscall(nios2_semi_u32_cb, "stat,%s,%x",
arg0, (int)arg1, arg2);
return;
} else {
@@ -379,7 +327,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "fstat,%x,%x",
+ gdb_do_syscall(nios2_semi_u32_cb, "fstat,%x,%x",
arg0, arg1);
return;
} else {
@@ -395,7 +343,7 @@ void do_nios2_semihosting(CPUNios2State *env)
/* Only the tv parameter is used. tz is assumed NULL. */
GET_ARG(0);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "gettimeofday,%x,%x",
+ gdb_do_syscall(nios2_semi_u32_cb, "gettimeofday,%x,%x",
arg0, 0);
return;
} else {
@@ -416,7 +364,7 @@ void do_nios2_semihosting(CPUNios2State *env)
case HOSTED_ISATTY:
GET_ARG(0);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "isatty,%x", arg0);
+ gdb_do_syscall(nios2_semi_u32_cb, "isatty,%x", arg0);
return;
} else {
result = isatty(arg0);
@@ -426,7 +374,7 @@ void do_nios2_semihosting(CPUNios2State *env)
GET_ARG(0);
GET_ARG(1);
if (use_gdb_syscalls()) {
- gdb_do_syscall(nios2_semi_cb, "system,%s",
+ gdb_do_syscall(nios2_semi_u32_cb, "system,%s",
arg0, (int)arg1);
return;
} else {
@@ -446,5 +394,5 @@ void do_nios2_semihosting(CPUNios2State *env)
result = 0;
}
failed:
- nios2_semi_return_u32(env, result, errno);
+ nios2_semi_u32_cb(cs, result, errno);
}
diff --git a/target/riscv/common-semi-target.h b/target/riscv/common-semi-target.h
new file mode 100644
index 0000000000..7c8a59e0cc
--- /dev/null
+++ b/target/riscv/common-semi-target.h
@@ -0,0 +1,50 @@
+/*
+ * Target-specific parts of semihosting/arm-compat-semi.c.
+ *
+ * Copyright (c) 2005, 2007 CodeSourcery.
+ * Copyright (c) 2019, 2022 Linaro
+ * Copyright © 2020 by Keith Packard <keithp@keithp.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TARGET_RISCV_COMMON_SEMI_TARGET_H
+#define TARGET_RISCV_COMMON_SEMI_TARGET_H
+
+static inline target_ulong common_semi_arg(CPUState *cs, int argno)
+{
+ RISCVCPU *cpu = RISCV_CPU(cs);
+ CPURISCVState *env = &cpu->env;
+ return env->gpr[xA0 + argno];
+}
+
+static inline void common_semi_set_ret(CPUState *cs, target_ulong ret)
+{
+ RISCVCPU *cpu = RISCV_CPU(cs);
+ CPURISCVState *env = &cpu->env;
+ env->gpr[xA0] = ret;
+}
+
+static inline bool common_semi_sys_exit_extended(CPUState *cs, int nr)
+{
+ return (nr == TARGET_SYS_EXIT_EXTENDED || sizeof(target_ulong) == 8);
+}
+
+static inline bool is_64bit_semihosting(CPUArchState *env)
+{
+ return riscv_cpu_mxl(env) != MXL_RV32;
+}
+
+static inline target_ulong common_semi_stack_bottom(CPUState *cs)
+{
+ RISCVCPU *cpu = RISCV_CPU(cs);
+ CPURISCVState *env = &cpu->env;
+ return env->gpr[xSP];
+}
+
+static inline bool common_semi_has_synccache(CPUArchState *env)
+{
+ return true;
+}
+
+#endif
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index 4a6700c890..be28615e23 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -1347,7 +1347,7 @@ void riscv_cpu_do_interrupt(CPUState *cs)
if (cause == RISCV_EXCP_SEMIHOST) {
if (env->priv >= PRV_S) {
- env->gpr[xA0] = do_common_semihosting(cs);
+ do_common_semihosting(cs);
env->pc += 4;
return;
}