diff options
Diffstat (limited to 'hw')
-rw-r--r-- | hw/Kconfig | 1 | ||||
-rw-r--r-- | hw/Makefile.objs | 1 | ||||
-rw-r--r-- | hw/mips/mips_malta.c | 2 | ||||
-rw-r--r-- | hw/semihosting/Kconfig | 3 | ||||
-rw-r--r-- | hw/semihosting/Makefile.objs | 2 | ||||
-rw-r--r-- | hw/semihosting/config.c | 186 | ||||
-rw-r--r-- | hw/semihosting/console.c | 84 |
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; +} |