1 /* 2 * Semihosting Console Support 3 * 4 * Copyright (c) 2015 Imagination Technologies 5 * Copyright (c) 2019 Linaro Ltd 6 * 7 * This provides support for outputting to a semihosting console. 8 * 9 * While most semihosting implementations support reading and writing 10 * to arbitrary file descriptors we treat the console as something 11 * specifically for debugging interaction. This means messages can be 12 * re-directed to gdb (if currently being used to debug) or even 13 * re-directed elsewhere. 14 * 15 * SPDX-License-Identifier: GPL-2.0-or-later 16 */ 17 18 #include "qemu/osdep.h" 19 #include "cpu.h" 20 #include "semihosting/semihost.h" 21 #include "semihosting/console.h" 22 #include "exec/gdbstub.h" 23 #include "exec/exec-all.h" 24 #include "qemu/log.h" 25 #include "chardev/char.h" 26 #include "chardev/char-fe.h" 27 #include "qemu/main-loop.h" 28 #include "qapi/error.h" 29 #include "qemu/fifo8.h" 30 31 int qemu_semihosting_log_out(const char *s, int len) 32 { 33 Chardev *chardev = semihosting_get_chardev(); 34 if (chardev) { 35 return qemu_chr_write_all(chardev, (uint8_t *) s, len); 36 } else { 37 return write(STDERR_FILENO, s, len); 38 } 39 } 40 41 /* 42 * A re-implementation of lock_user_string that we can use locally 43 * instead of relying on softmmu-semi. Hopefully we can deprecate that 44 * in time. Copy string until we find a 0 or address error. 45 */ 46 static GString *copy_user_string(CPUArchState *env, target_ulong addr) 47 { 48 CPUState *cpu = env_cpu(env); 49 GString *s = g_string_sized_new(128); 50 uint8_t c; 51 52 do { 53 if (cpu_memory_rw_debug(cpu, addr++, &c, 1, 0) == 0) { 54 if (c) { 55 s = g_string_append_c(s, c); 56 } 57 } else { 58 qemu_log_mask(LOG_GUEST_ERROR, 59 "%s: passed inaccessible address " TARGET_FMT_lx, 60 __func__, addr); 61 break; 62 } 63 } while (c!=0); 64 65 return s; 66 } 67 68 static void semihosting_cb(CPUState *cs, target_ulong ret, target_ulong err) 69 { 70 if (ret == (target_ulong) -1) { 71 qemu_log("%s: gdb console output failed ("TARGET_FMT_ld")", 72 __func__, err); 73 } 74 } 75 76 int qemu_semihosting_console_outs(CPUArchState *env, target_ulong addr) 77 { 78 GString *s = copy_user_string(env, addr); 79 int out = s->len; 80 81 if (use_gdb_syscalls()) { 82 gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, s->len); 83 } else { 84 out = qemu_semihosting_log_out(s->str, s->len); 85 } 86 87 g_string_free(s, true); 88 return out; 89 } 90 91 void qemu_semihosting_console_outc(CPUArchState *env, target_ulong addr) 92 { 93 CPUState *cpu = env_cpu(env); 94 uint8_t c; 95 96 if (cpu_memory_rw_debug(cpu, addr, &c, 1, 0) == 0) { 97 if (use_gdb_syscalls()) { 98 gdb_do_syscall(semihosting_cb, "write,2,%x,%x", addr, 1); 99 } else { 100 qemu_semihosting_log_out((const char *) &c, 1); 101 } 102 } else { 103 qemu_log_mask(LOG_GUEST_ERROR, 104 "%s: passed inaccessible address " TARGET_FMT_lx, 105 __func__, addr); 106 } 107 } 108 109 #define FIFO_SIZE 1024 110 111 /* Access to this structure is protected by the BQL */ 112 typedef struct SemihostingConsole { 113 CharBackend backend; 114 GSList *sleeping_cpus; 115 bool got; 116 Fifo8 fifo; 117 } SemihostingConsole; 118 119 static SemihostingConsole console; 120 121 static int console_can_read(void *opaque) 122 { 123 SemihostingConsole *c = opaque; 124 int ret; 125 g_assert(qemu_mutex_iothread_locked()); 126 ret = (int) fifo8_num_free(&c->fifo); 127 return ret; 128 } 129 130 static void console_wake_up(gpointer data, gpointer user_data) 131 { 132 CPUState *cs = (CPUState *) data; 133 /* cpu_handle_halt won't know we have work so just unbung here */ 134 cs->halted = 0; 135 qemu_cpu_kick(cs); 136 } 137 138 static void console_read(void *opaque, const uint8_t *buf, int size) 139 { 140 SemihostingConsole *c = opaque; 141 g_assert(qemu_mutex_iothread_locked()); 142 while (size-- && !fifo8_is_full(&c->fifo)) { 143 fifo8_push(&c->fifo, *buf++); 144 } 145 g_slist_foreach(c->sleeping_cpus, console_wake_up, NULL); 146 c->sleeping_cpus = NULL; 147 } 148 149 target_ulong qemu_semihosting_console_inc(CPUArchState *env) 150 { 151 uint8_t ch; 152 SemihostingConsole *c = &console; 153 g_assert(qemu_mutex_iothread_locked()); 154 g_assert(current_cpu); 155 if (fifo8_is_empty(&c->fifo)) { 156 c->sleeping_cpus = g_slist_prepend(c->sleeping_cpus, current_cpu); 157 current_cpu->halted = 1; 158 current_cpu->exception_index = EXCP_HALTED; 159 cpu_loop_exit(current_cpu); 160 /* never returns */ 161 } 162 ch = fifo8_pop(&c->fifo); 163 return (target_ulong) ch; 164 } 165 166 void qemu_semihosting_console_init(void) 167 { 168 Chardev *chr = semihosting_get_chardev(); 169 170 if (chr) { 171 fifo8_create(&console.fifo, FIFO_SIZE); 172 qemu_chr_fe_init(&console.backend, chr, &error_abort); 173 qemu_chr_fe_set_handlers(&console.backend, 174 console_can_read, 175 console_read, 176 NULL, NULL, &console, 177 NULL, true); 178 } 179 } 180