summaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/Makefile.objs1
-rw-r--r--hw/mips/mips_malta.c2
-rw-r--r--hw/semihosting/Kconfig3
-rw-r--r--hw/semihosting/Makefile.objs2
-rw-r--r--hw/semihosting/config.c186
-rw-r--r--hw/semihosting/console.c84
7 files changed, 278 insertions, 1 deletions
diff --git a/hw/Kconfig b/hw/Kconfig
index 88b9f15007..195f541e50 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -29,6 +29,7 @@ source pci/Kconfig
source rdma/Kconfig
source scsi/Kconfig
source sd/Kconfig
+source semihosting/Kconfig
source smbios/Kconfig
source ssi/Kconfig
source timer/Kconfig
diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index 82aa7fab8e..d770926ba9 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -36,6 +36,7 @@ devices-dirs-$(CONFIG_SOFTMMU) += watchdog/
devices-dirs-$(CONFIG_SOFTMMU) += xen/
devices-dirs-$(CONFIG_MEM_DEVICE) += mem/
devices-dirs-$(CONFIG_SOFTMMU) += smbios/
+devices-dirs-y += semihosting/
devices-dirs-y += core/
common-obj-y += $(devices-dirs-y)
obj-y += $(devices-dirs-y)
diff --git a/hw/mips/mips_malta.c b/hw/mips/mips_malta.c
index aff8464f2a..37ec89b07e 100644
--- a/hw/mips/mips_malta.c
+++ b/hw/mips/mips_malta.c
@@ -55,7 +55,7 @@
#include "qemu/error-report.h"
#include "hw/empty_slot.h"
#include "sysemu/kvm.h"
-#include "exec/semihost.h"
+#include "hw/semihosting/semihost.h"
#include "hw/mips/cps.h"
#define ENVP_ADDR 0x80002000l
diff --git a/hw/semihosting/Kconfig b/hw/semihosting/Kconfig
new file mode 100644
index 0000000000..efe0a30734
--- /dev/null
+++ b/hw/semihosting/Kconfig
@@ -0,0 +1,3 @@
+
+config SEMIHOSTING
+ bool
diff --git a/hw/semihosting/Makefile.objs b/hw/semihosting/Makefile.objs
new file mode 100644
index 0000000000..4ad47c05c0
--- /dev/null
+++ b/hw/semihosting/Makefile.objs
@@ -0,0 +1,2 @@
+obj-$(CONFIG_SEMIHOSTING) += config.o
+obj-$(CONFIG_SEMIHOSTING) += console.o
diff --git a/hw/semihosting/config.c b/hw/semihosting/config.c
new file mode 100644
index 0000000000..2a8e7e1045
--- /dev/null
+++ b/hw/semihosting/config.c
@@ -0,0 +1,186 @@
+/*
+ * Semihosting configuration
+ *
+ * Copyright (c) 2015 Imagination Technologies
+ * Copyright (c) 2019 Linaro Ltd
+ *
+ * This controls the configuration of semihosting for all guest
+ * targets that support it. Architecture specific handling is handled
+ * in target/HW/HW-semi.c
+ *
+ * Semihosting is sightly strange in that it is also supported by some
+ * linux-user targets. However in that use case no configuration of
+ * the outputs and command lines is supported.
+ *
+ * The config module is common to all softmmu targets however as vl.c
+ * needs to link against the helpers.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/option.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "hw/semihosting/semihost.h"
+#include "chardev/char.h"
+
+QemuOptsList qemu_semihosting_config_opts = {
+ .name = "semihosting-config",
+ .implied_opt_name = "enable",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_semihosting_config_opts.head),
+ .desc = {
+ {
+ .name = "enable",
+ .type = QEMU_OPT_BOOL,
+ }, {
+ .name = "target",
+ .type = QEMU_OPT_STRING,
+ }, {
+ .name = "chardev",
+ .type = QEMU_OPT_STRING,
+ }, {
+ .name = "arg",
+ .type = QEMU_OPT_STRING,
+ },
+ { /* end of list */ }
+ },
+};
+
+typedef struct SemihostingConfig {
+ bool enabled;
+ SemihostingTarget target;
+ Chardev *chardev;
+ const char **argv;
+ int argc;
+ const char *cmdline; /* concatenated argv */
+} SemihostingConfig;
+
+static SemihostingConfig semihosting;
+static const char *semihost_chardev;
+
+bool semihosting_enabled(void)
+{
+ return semihosting.enabled;
+}
+
+SemihostingTarget semihosting_get_target(void)
+{
+ return semihosting.target;
+}
+
+const char *semihosting_get_arg(int i)
+{
+ if (i >= semihosting.argc) {
+ return NULL;
+ }
+ return semihosting.argv[i];
+}
+
+int semihosting_get_argc(void)
+{
+ return semihosting.argc;
+}
+
+const char *semihosting_get_cmdline(void)
+{
+ if (semihosting.cmdline == NULL && semihosting.argc > 0) {
+ semihosting.cmdline = g_strjoinv(" ", (gchar **)semihosting.argv);
+ }
+ return semihosting.cmdline;
+}
+
+static int add_semihosting_arg(void *opaque,
+ const char *name, const char *val,
+ Error **errp)
+{
+ SemihostingConfig *s = opaque;
+ if (strcmp(name, "arg") == 0) {
+ s->argc++;
+ /* one extra element as g_strjoinv() expects NULL-terminated array */
+ s->argv = g_realloc(s->argv, (s->argc + 1) * sizeof(void *));
+ s->argv[s->argc - 1] = val;
+ s->argv[s->argc] = NULL;
+ }
+ return 0;
+}
+
+/* Use strings passed via -kernel/-append to initialize semihosting.argv[] */
+void semihosting_arg_fallback(const char *file, const char *cmd)
+{
+ char *cmd_token;
+
+ /* argv[0] */
+ add_semihosting_arg(&semihosting, "arg", file, NULL);
+
+ /* split -append and initialize argv[1..n] */
+ cmd_token = strtok(g_strdup(cmd), " ");
+ while (cmd_token) {
+ add_semihosting_arg(&semihosting, "arg", cmd_token, NULL);
+ cmd_token = strtok(NULL, " ");
+ }
+}
+
+Chardev *semihosting_get_chardev(void)
+{
+ return semihosting.chardev;
+}
+
+void qemu_semihosting_enable(void)
+{
+ semihosting.enabled = true;
+ semihosting.target = SEMIHOSTING_TARGET_AUTO;
+}
+
+int qemu_semihosting_config_options(const char *optarg)
+{
+ QemuOptsList *opt_list = qemu_find_opts("semihosting-config");
+ QemuOpts *opts = qemu_opts_parse_noisily(opt_list, optarg, false);
+
+ semihosting.enabled = true;
+
+ if (opts != NULL) {
+ semihosting.enabled = qemu_opt_get_bool(opts, "enable",
+ true);
+ const char *target = qemu_opt_get(opts, "target");
+ /* setup of chardev is deferred until they are initialised */
+ semihost_chardev = qemu_opt_get(opts, "chardev");
+ if (target != NULL) {
+ if (strcmp("native", target) == 0) {
+ semihosting.target = SEMIHOSTING_TARGET_NATIVE;
+ } else if (strcmp("gdb", target) == 0) {
+ semihosting.target = SEMIHOSTING_TARGET_GDB;
+ } else if (strcmp("auto", target) == 0) {
+ semihosting.target = SEMIHOSTING_TARGET_AUTO;
+ } else {
+ error_report("unsupported semihosting-config %s",
+ optarg);
+ return 1;
+ }
+ } else {
+ semihosting.target = SEMIHOSTING_TARGET_AUTO;
+ }
+ /* Set semihosting argument count and vector */
+ qemu_opt_foreach(opts, add_semihosting_arg,
+ &semihosting, NULL);
+ } else {
+ error_report("unsupported semihosting-config %s", optarg);
+ return 1;
+ }
+
+ return 0;
+}
+
+void qemu_semihosting_connect_chardevs(void)
+{
+ /* We had to defer this until chardevs were created */
+ if (semihost_chardev) {
+ Chardev *chr = qemu_chr_find(semihost_chardev);
+ if (chr == NULL) {
+ error_report("semihosting chardev '%s' not found",
+ semihost_chardev);
+ exit(1);
+ }
+ semihosting.chardev = chr;
+ }
+}
diff --git a/hw/semihosting/console.c b/hw/semihosting/console.c
new file mode 100644
index 0000000000..466ea6dade
--- /dev/null
+++ b/hw/semihosting/console.c
@@ -0,0 +1,84 @@
+/*
+ * Semihosting Console Support
+ *
+ * Copyright (c) 2015 Imagination Technologies
+ * Copyright (c) 2019 Linaro Ltd
+ *
+ * This provides support for outputting to a semihosting console.
+ *
+ * While most semihosting implementations support reading and writing
+ * to arbitrary file descriptors we treat the console as something
+ * specifically for debugging interaction. This means messages can be
+ * re-directed to gdb (if currently being used to debug) or even
+ * re-directed elsewhere.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "hw/semihosting/semihost.h"
+#include "hw/semihosting/console.h"
+#include "exec/gdbstub.h"
+#include "qemu/log.h"
+#include "chardev/char.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. We either copy len bytes if specified or until we find a NULL.
+ */
+static GString *copy_user_string(CPUArchState *env, target_ulong addr, int len)
+{
+ CPUState *cpu = ENV_GET_CPU(env);
+ GString *s = g_string_sized_new(len ? len : 128);
+ uint8_t c;
+ bool done;
+
+ do {
+ if (cpu_memory_rw_debug(cpu, addr++, &c, 1, 0) == 0) {
+ s = g_string_append_c(s, c);
+ done = len ? s->len == len : c == 0;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: passed inaccessible address " TARGET_FMT_lx,
+ __func__, addr);
+ done = true;
+ }
+ } while (!done);
+
+ 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_out(CPUArchState *env, target_ulong addr, int len)
+{
+ GString *s = copy_user_string(env, addr, len);
+ 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;
+}