From d76129fb9ab60df696af6bc4911041f95b3a560b Mon Sep 17 00:00:00 2001
From: Julian Brown <julian@codesourcery.com>
Date: Tue, 1 Nov 2016 08:35:48 -0700
Subject: [PATCH 1/4] ARM BE8/BE32 semihosting and gdbstub support.
This patch improves support for semihosting and debugging with the
in-built gdbstub for ARM system-mode emulation in big-endian mode (either
BE8 or BE32), after the fairly recent changes to allow a single QEMU
binary to deal with each of LE, BE8 and BE32 modes in one. It's only
currently good for little-endian host systems. The relevant use case
is using QEMU as a "bare metal" instruction-set simulator, e.g. for
toolchain testing.
For semihosting, the softmmu-semi.h file is overridden with an
ARM-specific version that knows about byte-swapping target memory into
host order -- including that which has been byte-swapped at load time
for BE32 mode.
For the gdbstub, we'd like to be able to invoke QEMU from GDB like:
(gdb) target remote | arm-qemu-system -cpu=foo [options] /dev/null
(gdb) load
(gdb) ...
which unfortunately bypasses the probing of the loaded ELF file (since
it's just /dev/null) to determine whether to use BE8/BE32 mode. A
"cfgend" boolean parameter has been added for this scenario, mirroring
the configuration input on (some?) ARM cores, to choose a core-appropriate
big-endian mode at reset. (Use e.g. -cpu=arm926,cfgend=yes).
Signed-off-by: Julian Brown <julian@codesourcery.com>
---
hw/arm/boot.c | 16 ++++-
include/exec/softmmu-arm-semi.h | 148 ++++++++++++++++++++++++++++++++++++++++
target-arm/arm-semi.c | 2 +-
target-arm/cpu.c | 52 +++++++++++++-
target-arm/cpu.h | 12 ++++
target-arm/gdbstub.c | 42 ++++++++++++
6 files changed, 267 insertions(+), 5 deletions(-)
create mode 100644 include/exec/softmmu-arm-semi.h
@@ -894,7 +894,21 @@ static void arm_load_kernel_notify(Notifier *notifier, void *data)
entry = info->loader_start + kernel_load_offset;
kernel_size = load_image_targphys(info->kernel_filename, entry,
info->ram_size - kernel_load_offset);
- is_linux = 1;
+ if (kernel_size > 0) {
+ is_linux = 1;
+ } else {
+ /* We've been launched with a kernel of /dev/null or similar.
+ * Infer endianness from the reset value of the SCTLR for this
+ * CPU/board. (This can be altered using the cfgend parameter.)
+ */
+ if (!arm_feature(&cpu->env, ARM_FEATURE_V7) &&
+ (cpu->reset_sctlr & SCTLR_B) != 0)
+ info->endianness = ARM_ENDIANNESS_BE32;
+ else if ((cpu->reset_sctlr & SCTLR_EE) != 0)
+ info->endianness = ARM_ENDIANNESS_BE8;
+ else
+ info->endianness = ARM_ENDIANNESS_LE;
+ }
}
if (kernel_size < 0) {
fprintf(stderr, "qemu: could not load kernel '%s'\n",
new file mode 100644
@@ -0,0 +1,148 @@
+/*
+ * Helper routines to provide target memory access for ARM semihosting
+ * syscalls in system emulation mode.
+ *
+ * Copyright (c) 2007 CodeSourcery, (c) 2016 Mentor Graphics
+ *
+ * This code is licensed under the GPL
+ */
+
+#ifndef SOFTMMU_ARM_SEMI_H
+#define SOFTMMU_ARM_SEMI_H 1
+
+/* In BE32 system mode, the CPU-specific memory_rw_debug method will arrange to
+ * perform byteswapping on the target memory, so that it appears to the host as
+ * it appears to the emulated CPU. Memory is read verbatim in BE8 mode. (In
+ * other words, this function arranges so that BUF has the same format in both
+ * BE8 and BE32 system mode.)
+ */
+
+static inline int armsemi_memory_rw_debug(CPUState *cpu, target_ulong addr,
+ uint8_t *buf, int len, bool is_write)
+{
+ CPUClass *cc = CPU_GET_CLASS(cpu);
+
+ if (cc->memory_rw_debug) {
+ return cc->memory_rw_debug(cpu, addr, buf, len, is_write);
+ }
+ return cpu_memory_rw_debug(cpu, addr, buf, len, is_write);
+}
+
+/* In big-endian mode (either BE8 or BE32), values larger than a byte will be
+ * transferred to/from memory in big-endian format. Assuming we're on a
+ * little-endian host machine, such values will need to be byteswapped before
+ * and after the host processes them.
+ *
+ * This means that byteswapping will occur *twice* in BE32 mode for
+ * halfword/word reads/writes.
+ */
+
+static inline bool arm_bswap_needed(CPUARMState *env)
+{
+#ifdef HOST_WORDS_BIGENDIAN
+#error HOST_WORDS_BIGENDIAN is not supported for ARM semihosting at the moment.
+#else
+ return arm_sctlr_b(env) || arm_sctlr_ee(env);
+#endif
+}
+
+static inline uint64_t softmmu_tget64(CPUArchState *env, target_ulong addr)
+{
+ uint64_t val;
+
+ armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, (uint8_t *)&val, 8, 0);
+ if (arm_bswap_needed(env)) {
+ return bswap64(val);
+ } else {
+ return val;
+ }
+}
+
+static inline uint32_t softmmu_tget32(CPUArchState *env, target_ulong addr)
+{
+ uint32_t val;
+
+ armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, (uint8_t *)&val, 4, 0);
+ if (arm_bswap_needed(env)) {
+ return bswap32(val);
+ } else {
+ return val;
+ }
+}
+
+static inline uint32_t softmmu_tget8(CPUArchState *env, target_ulong addr)
+{
+ uint8_t val;
+ armsemi_memory_rw_debug(ENV_GET_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)
+{
+ if (arm_bswap_needed(env)) {
+ val = bswap64(val);
+ }
+ cpu_memory_rw_debug(ENV_GET_CPU(env), addr, (uint8_t *)&val, 8, 1);
+}
+
+static inline void softmmu_tput32(CPUArchState *env,
+ target_ulong addr, uint32_t val)
+{
+ if (arm_bswap_needed(env)) {
+ val = bswap32(val);
+ }
+ armsemi_memory_rw_debug(ENV_GET_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) {
+ armsemi_memory_rw_debug(ENV_GET_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 {
+ armsemi_memory_rw_debug(ENV_GET_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)
+{
+ uint8_t *pc = p;
+ if (len) {
+ armsemi_memory_rw_debug(ENV_GET_CPU(env), addr, p, len, 1);
+ }
+ free(p);
+}
+
+#define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len)
+
+#endif
@@ -114,7 +114,7 @@ static inline uint32_t set_swi_errno(CPUARMState *env, uint32_t code)
return code;
}
-#include "exec/softmmu-semi.h"
+#include "exec/softmmu-arm-semi.h"
#endif
static target_ulong arm_semi_syscall_len;
@@ -33,6 +33,7 @@
#include "sysemu/sysemu.h"
#include "sysemu/kvm.h"
#include "kvm_arm.h"
+#include "exec/cpu-common.h"
static void arm_cpu_set_pc(CPUState *cs, vaddr value)
{
@@ -497,6 +498,9 @@ static Property arm_cpu_rvbar_property =
static Property arm_cpu_has_el3_property =
DEFINE_PROP_BOOL("has_el3", ARMCPU, has_el3, true);
+static Property arm_cpu_cfgend_property =
+ DEFINE_PROP_BOOL("cfgend", ARMCPU, cfgend, false);
+
/* use property name "pmu" to match other archs and virt tools */
static Property arm_cpu_has_pmu_property =
DEFINE_PROP_BOOL("pmu", ARMCPU, has_pmu, true);
@@ -559,6 +563,18 @@ static void arm_cpu_post_init(Object *obj)
}
}
+ qdev_property_add_static(DEVICE(obj), &arm_cpu_cfgend_property,
+ &error_abort);
+
+ qdev_prop_set_globals(DEVICE(obj));
+
+ if (object_property_get_bool(obj, "cfgend", NULL)) {
+ if (arm_feature(&cpu->env, ARM_FEATURE_V7)) {
+ cpu->reset_sctlr |= SCTLR_EE;
+ } else {
+ cpu->reset_sctlr |= SCTLR_B;
+ }
+ }
}
static void arm_cpu_finalizefn(Object *obj)
@@ -758,6 +774,7 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
static ObjectClass *arm_cpu_class_by_name(const char *cpu_model)
{
ObjectClass *oc;
+ CPUClass *cc;
char *typename;
char **cpuname;
@@ -765,15 +782,20 @@ static ObjectClass *arm_cpu_class_by_name(const char *cpu_model)
return NULL;
}
- cpuname = g_strsplit(cpu_model, ",", 1);
+ cpuname = g_strsplit(cpu_model, ",", 2);
typename = g_strdup_printf("%s-" TYPE_ARM_CPU, cpuname[0]);
oc = object_class_by_name(typename);
- g_strfreev(cpuname);
- g_free(typename);
if (!oc || !object_class_dynamic_cast(oc, TYPE_ARM_CPU) ||
object_class_is_abstract(oc)) {
+ g_strfreev(cpuname);
+ g_free(typename);
return NULL;
}
+
+ cc = CPU_CLASS(oc);
+ cc->parse_features(typename, cpuname[1], &error_fatal);
+ g_strfreev(cpuname);
+
return oc;
}
@@ -1534,6 +1556,27 @@ static gchar *arm_gdb_arch_name(CPUState *cs)
return g_strdup("arm");
}
+#ifndef CONFIG_USER_ONLY
+static int arm_cpu_memory_rw_debug(CPUState *cpu, vaddr address,
+ uint8_t *buf, int len, bool is_write)
+{
+ target_ulong addr = address;
+ ARMCPU *armcpu = ARM_CPU(cpu);
+ CPUARMState *env = &armcpu->env;
+
+ if (arm_sctlr_b(env)) {
+ target_ulong i;
+ for (i = 0; i < len; i++) {
+ cpu_memory_rw_debug(cpu, (addr + i) ^ 3, &buf[i], 1, is_write);
+ }
+ } else {
+ cpu_memory_rw_debug(cpu, addr, buf, len, is_write);
+ }
+
+ return 0;
+}
+#endif
+
static void arm_cpu_class_init(ObjectClass *oc, void *data)
{
ARMCPUClass *acc = ARM_CPU_CLASS(oc);
@@ -1551,6 +1594,9 @@ static void arm_cpu_class_init(ObjectClass *oc, void *data)
cc->has_work = arm_cpu_has_work;
cc->cpu_exec_interrupt = arm_cpu_exec_interrupt;
cc->dump_state = arm_cpu_dump_state;
+#if !defined(CONFIG_USER_ONLY)
+ cc->memory_rw_debug = arm_cpu_memory_rw_debug;
+#endif
cc->set_pc = arm_cpu_set_pc;
cc->gdb_read_register = arm_cpu_gdb_read_register;
cc->gdb_write_register = arm_cpu_gdb_write_register;
@@ -657,6 +657,12 @@ struct ARMCPU {
uint32_t dcz_blocksize;
uint64_t rvbar;
+ /* Whether the cfgend input is high (i.e. this CPU should reset into
+ big-endian mode). This setting isn't used directly: instead it modifies
+ the reset_sctlr value to have SCTLR_B or SCTLR_EE set, depending on the
+ architecture version. */
+ bool cfgend;
+
ARMELChangeHook *el_change_hook;
void *el_change_hook_opaque;
};
@@ -2108,6 +2114,12 @@ static inline bool arm_sctlr_b(CPUARMState *env)
(env->cp15.sctlr_el[1] & SCTLR_B) != 0;
}
+static inline bool arm_sctlr_ee(CPUARMState *env)
+{
+ return arm_feature(env, ARM_FEATURE_V7) &&
+ (env->cp15.sctlr_el[1] & SCTLR_EE) != 0;
+}
+
/* Return true if the processor is in big-endian mode. */
static inline bool arm_cpu_data_is_big_endian(CPUARMState *env)
{
@@ -21,6 +21,7 @@
#include "qemu-common.h"
#include "cpu.h"
#include "exec/gdbstub.h"
+#include "exec/softmmu-arm-semi.h"
/* Old gdb always expect FPA registers. Newer (xml-aware) gdb only expect
whatever the target description contains. Due to a historical mishap
@@ -32,10 +33,22 @@ int arm_cpu_gdb_read_register(CPUState *cs, uint8_t *mem_buf, int n)
{
ARMCPU *cpu = ARM_CPU(cs);
CPUARMState *env = &cpu->env;
+#ifndef CONFIG_USER_ONLY
+ bool targ_bigendian = arm_bswap_needed(env);
+#endif
if (n < 16) {
/* Core integer register. */
+#ifdef CONFIG_USER_ONLY
return gdb_get_reg32(mem_buf, env->regs[n]);
+#else
+ if (targ_bigendian) {
+ stl_be_p(mem_buf, env->regs[n]);
+ } else {
+ stl_le_p(mem_buf, env->regs[n]);
+ }
+ return 4;
+#endif
}
if (n < 24) {
/* FPA registers. */
@@ -51,10 +64,28 @@ int arm_cpu_gdb_read_register(CPUState *cs, uint8_t *mem_buf, int n)
if (gdb_has_xml) {
return 0;
}
+#ifdef CONFIG_USER_ONLY
return gdb_get_reg32(mem_buf, 0);
+#else
+ if (targ_bigendian) {
+ stl_be_p(mem_buf, 0);
+ } else {
+ stl_le_p(mem_buf, 0);
+ }
+ return 4;
+#endif
case 25:
/* CPSR */
+#ifdef CONFIG_USER_ONLY
return gdb_get_reg32(mem_buf, cpsr_read(env));
+#else
+ if (targ_bigendian) {
+ stl_be_p(mem_buf, cpsr_read(env));
+ } else {
+ stl_le_p(mem_buf, cpsr_read(env));
+ }
+ return 4;
+#endif
}
/* Unknown register. */
return 0;
@@ -65,8 +96,19 @@ int arm_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n)
ARMCPU *cpu = ARM_CPU(cs);
CPUARMState *env = &cpu->env;
uint32_t tmp;
+#ifndef CONFIG_USER_ONLY
+ bool targ_bigendian = arm_bswap_needed(env);
+#endif
+#ifdef CONFIG_USER_ONLY
tmp = ldl_p(mem_buf);
+#else
+ if (targ_bigendian) {
+ tmp = ldl_be_p(mem_buf);
+ } else {
+ tmp = ldl_le_p(mem_buf);
+ }
+#endif
/* Mask out low bit of PC to workaround gdb bugs. This will probably
cause problems if we ever implement the Jazelle DBX extensions. */
--
1.9.1