xref: /qemu/semihosting/console.c (revision ead62c75f618c072a3a18221fd03ae99ae923cca)
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