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 +#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 @@ -37,6 +37,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 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 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)