aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/semihosting/console.c79
-rw-r--r--include/hw/semihosting/console.h16
-rw-r--r--include/hw/semihosting/semihost.h4
-rw-r--r--linux-user/arm/semihost.c27
-rw-r--r--stubs/semihost.c4
-rw-r--r--target/arm/arm-semi.c3
-rw-r--r--vl.c3
7 files changed, 134 insertions, 2 deletions
diff --git a/hw/semihosting/console.c b/hw/semihosting/console.c
index b4b17c8afb..6346bd7f50 100644
--- a/hw/semihosting/console.c
+++ b/hw/semihosting/console.c
@@ -20,8 +20,15 @@
#include "hw/semihosting/semihost.h"
#include "hw/semihosting/console.h"
#include "exec/gdbstub.h"
+#include "exec/exec-all.h"
#include "qemu/log.h"
#include "chardev/char.h"
+#include <pthread.h>
+#include "chardev/char-fe.h"
+#include "sysemu/sysemu.h"
+#include "qemu/main-loop.h"
+#include "qapi/error.h"
+#include "qemu/fifo8.h"
int qemu_semihosting_log_out(const char *s, int len)
{
@@ -98,3 +105,75 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr)
__func__, addr);
}
}
+
+#define FIFO_SIZE 1024
+
+/* Access to this structure is protected by the BQL */
+typedef struct SemihostingConsole {
+ CharBackend backend;
+ GSList *sleeping_cpus;
+ bool got;
+ Fifo8 fifo;
+} SemihostingConsole;
+
+static SemihostingConsole console;
+
+static int console_can_read(void *opaque)
+{
+ SemihostingConsole *c = opaque;
+ int ret;
+ g_assert(qemu_mutex_iothread_locked());
+ ret = (int) fifo8_num_free(&c->fifo);
+ return ret;
+}
+
+static void console_wake_up(gpointer data, gpointer user_data)
+{
+ CPUState *cs = (CPUState *) data;
+ /* cpu_handle_halt won't know we have work so just unbung here */
+ cs->halted = 0;
+ qemu_cpu_kick(cs);
+}
+
+static void console_read(void *opaque, const uint8_t *buf, int size)
+{
+ SemihostingConsole *c = opaque;
+ g_assert(qemu_mutex_iothread_locked());
+ while (size-- && !fifo8_is_full(&c->fifo)) {
+ fifo8_push(&c->fifo, *buf++);
+ }
+ g_slist_foreach(c->sleeping_cpus, console_wake_up, NULL);
+ c->sleeping_cpus = NULL;
+}
+
+target_ulong qemu_semihosting_console_inc(CPUArchState *env)
+{
+ uint8_t ch;
+ SemihostingConsole *c = &console;
+ g_assert(qemu_mutex_iothread_locked());
+ g_assert(current_cpu);
+ 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);
+ /* never returns */
+ }
+ ch = fifo8_pop(&c->fifo);
+ return (target_ulong) ch;
+}
+
+void qemu_semihosting_console_init(void)
+{
+ Chardev *chr = semihosting_get_chardev();
+
+ if (chr) {
+ fifo8_create(&console.fifo, FIFO_SIZE);
+ qemu_chr_fe_init(&console.backend, chr, &error_abort);
+ qemu_chr_fe_set_handlers(&console.backend,
+ console_can_read,
+ console_read,
+ NULL, NULL, &console,
+ NULL, true);
+ }
+}
diff --git a/include/hw/semihosting/console.h b/include/hw/semihosting/console.h
index 9be9754bcd..0238f540f4 100644
--- a/include/hw/semihosting/console.h
+++ b/include/hw/semihosting/console.h
@@ -38,6 +38,22 @@ int qemu_semihosting_console_outs(CPUArchState *env, target_ulong s);
void qemu_semihosting_console_outc(CPUArchState *env, target_ulong c);
/**
+ * qemu_semihosting_console_inc:
+ * @env: CPUArchState
+ *
+ * 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
+ *
+ * Returns: character read OR cpu_loop_exit!
+ */
+target_ulong qemu_semihosting_console_inc(CPUArchState *env);
+
+/**
* qemu_semihosting_log_out:
* @s: pointer to string
* @len: length of string
diff --git a/include/hw/semihosting/semihost.h b/include/hw/semihosting/semihost.h
index 60fc42d851..b8ce5117ae 100644
--- a/include/hw/semihosting/semihost.h
+++ b/include/hw/semihosting/semihost.h
@@ -56,6 +56,9 @@ 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);
@@ -68,6 +71,7 @@ Chardev *semihosting_get_chardev(void);
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);
#endif /* CONFIG_USER_ONLY */
#endif /* SEMIHOST_H */
diff --git a/linux-user/arm/semihost.c b/linux-user/arm/semihost.c
index a16b525eec..a1f0f6050e 100644
--- a/linux-user/arm/semihost.c
+++ b/linux-user/arm/semihost.c
@@ -14,6 +14,7 @@
#include "cpu.h"
#include "hw/semihosting/console.h"
#include "qemu.h"
+#include <termios.h>
int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr)
{
@@ -47,3 +48,29 @@ void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr)
}
}
}
+
+/*
+ * For linux-user we can safely block. However as we want to return as
+ * soon as a character is read we need to tweak the termio to disable
+ * line buffering. We restore the old mode afterwards in case the
+ * program is expecting more normal behaviour. This is slow but
+ * nothing using semihosting console reading is expecting to be fast.
+ */
+target_ulong qemu_semihosting_console_inc(CPUArchState *env)
+{
+ uint8_t c;
+ struct termios old_tio, new_tio;
+
+ /* Disable line-buffering and echo */
+ tcgetattr(STDIN_FILENO, &old_tio);
+ new_tio = old_tio;
+ new_tio.c_lflag &= (~ICANON & ~ECHO);
+ tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
+
+ c = getchar();
+
+ /* restore config */
+ tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
+
+ return (target_ulong) c;
+}
diff --git a/stubs/semihost.c b/stubs/semihost.c
index f90589259c..1d8b37f7b2 100644
--- a/stubs/semihost.c
+++ b/stubs/semihost.c
@@ -69,3 +69,7 @@ void semihosting_arg_fallback(const char *file, const char *cmd)
void qemu_semihosting_connect_chardevs(void)
{
}
+
+void qemu_semihosting_console_init(void)
+{
+}
diff --git a/target/arm/arm-semi.c b/target/arm/arm-semi.c
index 6f7b6d801b..47d61f6fe1 100644
--- a/target/arm/arm-semi.c
+++ b/target/arm/arm-semi.c
@@ -802,8 +802,7 @@ target_ulong do_arm_semihosting(CPUARMState *env)
return guestfd_fns[gf->type].readfn(cpu, gf, arg1, len);
case TARGET_SYS_READC:
- qemu_log_mask(LOG_UNIMP, "%s: SYS_READC not implemented", __func__);
- return 0;
+ return qemu_semihosting_console_inc(env);
case TARGET_SYS_ISTTY:
GET_ARG(0);
diff --git a/vl.c b/vl.c
index 86474a55c9..b211921258 100644
--- a/vl.c
+++ b/vl.c
@@ -4238,6 +4238,9 @@ int main(int argc, char **argv, char **envp)
qemu_opts_foreach(qemu_find_opts("mon"),
mon_init_func, NULL, &error_fatal);
+ /* connect semihosting console input if requested */
+ qemu_semihosting_console_init();
+
if (foreach_device_config(DEV_SERIAL, serial_parse) < 0)
exit(1);
if (foreach_device_config(DEV_PARALLEL, parallel_parse) < 0)