diff --git a/monitor.c b/monitor.c index 1e9b904bf5b2b35fa285f80da8c7fe982dd201c6..cd0663e937af30f99f28654304fec56a3f4f00c5 100644 --- a/monitor.c +++ b/monitor.c @@ -54,7 +54,8 @@ typedef struct term_cmd_t { const char *help; } term_cmd_t; -static CharDriverState *monitor_hd; +#define MAX_MON 4 +static CharDriverState *monitor_hd[MAX_MON]; static int hide_banner; static term_cmd_t term_cmds[]; @@ -69,8 +70,11 @@ CPUState *mon_cpu = NULL; void term_flush(void) { + int i; if (term_outbuf_index > 0) { - qemu_chr_write(monitor_hd, term_outbuf, term_outbuf_index); + for (i = 0; i < MAX_MON; i++) + if (monitor_hd[i] && monitor_hd[i]->focus == 0) + qemu_chr_write(monitor_hd[i], term_outbuf, term_outbuf_index); term_outbuf_index = 0; } } @@ -2452,9 +2456,25 @@ static void term_event(void *opaque, int event) monitor_start_input(); } +static int is_first_init = 1; + void monitor_init(CharDriverState *hd, int show_banner) { - monitor_hd = hd; + int i; + + if (is_first_init) { + for (i = 0; i < MAX_MON; i++) { + monitor_hd[i] = NULL; + } + is_first_init = 0; + } + for (i = 0; i < MAX_MON; i++) { + if (monitor_hd[i] == NULL) { + monitor_hd[i] = hd; + break; + } + } + hide_banner = !show_banner; qemu_chr_add_handlers(hd, term_can_read, term_read, term_event, NULL); @@ -2475,8 +2495,12 @@ static void monitor_readline_cb(void *opaque, const char *input) void monitor_readline(const char *prompt, int is_password, char *buf, int buf_size) { + int i; + if (is_password) { - qemu_chr_send_event(monitor_hd, CHR_EVENT_FOCUS); + for (i = 0; i < MAX_MON; i++) + if (monitor_hd[i] && monitor_hd[i]->focus == 0) + qemu_chr_send_event(monitor_hd[i], CHR_EVENT_FOCUS); } readline_start(prompt, is_password, monitor_readline_cb, NULL); monitor_readline_buf = buf; diff --git a/qemu-doc.texi b/qemu-doc.texi index a4c99708e946102370f2c08b51662b3abc826ee3..b539747dfe46c232a25cddbc1a2b508b72670966 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -610,6 +610,18 @@ A unix domain socket is used instead of a tcp socket. The option works the same as if you had specified @code{-serial tcp} except the unix domain socket @var{path} is used for connections. +@item mon:dev_string +This is a special option to allow the monitor to be multiplexed onto +another serial port. The monitor is accessed with key sequence of +@key{Control-a} and then pressing @key{c}. See monitor access +@ref{pcsys_keys} in the -nographic section for more keys. +@var{dev_string} should be any one of the serial devices specified +above. An example to multiplex the monitor onto a telnet server +listening on port 4444 would be: +@table @code +@item -serial mon:telnet::4444,server,nowait +@end table + @end table @item -parallel dev @@ -629,6 +641,19 @@ serial port). The default device is @code{vc} in graphical mode and @code{stdio} in non graphical mode. +@item -echr numeric_ascii_value +Change the escape character used for switching to the monitor when using +monitor and serial sharing. The default is @code{0x01} when using the +@code{-nographic} option. @code{0x01} is equal to pressing +@code{Control-a}. You can select a different character from the ascii +control keys where 1 through 26 map to Control-a through Control-z. For +instance you could use the either of the following to change the escape +character to Control-t. +@table @code +@item -echr 0x14 +@item -echr 20 +@end table + @item -s Wait gdb connection to port 1234 (@pxref{gdb_usage}). @item -p port @@ -711,6 +736,8 @@ Print this help Exit emulator @item Ctrl-a s Save disk data back to file (if -snapshot) +@item Ctrl-a t +toggle console timestamps @item Ctrl-a b Send break (magic sysrq in Linux) @item Ctrl-a c diff --git a/vl.c b/vl.c index 0a527a77a99412b609ad8790c286061181e75b24..de53b7219e72d1e14e981b39e65dd2c355183fad 100644 --- a/vl.c +++ b/vl.c @@ -1228,6 +1228,218 @@ static CharDriverState *qemu_chr_open_null(void) return chr; } +/* MUX driver for serial I/O splitting */ +static int term_timestamps; +static int64_t term_timestamps_start; +#define MAX_MUX 2 +typedef struct { + IOCanRWHandler *chr_can_read[MAX_MUX]; + IOReadHandler *chr_read[MAX_MUX]; + IOEventHandler *chr_event[MAX_MUX]; + void *ext_opaque[MAX_MUX]; + CharDriverState *drv; + int mux_cnt; + int term_got_escape; + int max_size; +} MuxDriver; + + +static int mux_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + MuxDriver *d = chr->opaque; + int ret; + if (!term_timestamps) { + ret = d->drv->chr_write(d->drv, buf, len); + } else { + int i; + + ret = 0; + for(i = 0; i < len; i++) { + ret += d->drv->chr_write(d->drv, buf+i, 1); + if (buf[i] == '\n') { + char buf1[64]; + int64_t ti; + int secs; + + ti = get_clock(); + if (term_timestamps_start == -1) + term_timestamps_start = ti; + ti -= term_timestamps_start; + secs = ti / 1000000000; + snprintf(buf1, sizeof(buf1), + "[%02d:%02d:%02d.%03d] ", + secs / 3600, + (secs / 60) % 60, + secs % 60, + (int)((ti / 1000000) % 1000)); + d->drv->chr_write(d->drv, buf1, strlen(buf1)); + } + } + } + return ret; +} + +static char *mux_help[] = { + "% h print this help\n\r", + "% x exit emulator\n\r", + "% s save disk data back to file (if -snapshot)\n\r", + "% t toggle console timestamps\n\r" + "% b send break (magic sysrq)\n\r", + "% c switch between console and monitor\n\r", + "% % sends %\n\r", + NULL +}; + +static int term_escape_char = 0x01; /* ctrl-a is used for escape */ +static void mux_print_help(CharDriverState *chr) +{ + int i, j; + char ebuf[15] = "Escape-Char"; + char cbuf[50] = "\n\r"; + + if (term_escape_char > 0 && term_escape_char < 26) { + sprintf(cbuf,"\n\r"); + sprintf(ebuf,"C-%c", term_escape_char - 1 + 'a'); + } else { + sprintf(cbuf,"\n\rEscape-Char set to Ascii: 0x%02x\n\r\n\r", term_escape_char); + } + chr->chr_write(chr, cbuf, strlen(cbuf)); + for (i = 0; mux_help[i] != NULL; i++) { + for (j=0; mux_help[i][j] != '\0'; j++) { + if (mux_help[i][j] == '%') + chr->chr_write(chr, ebuf, strlen(ebuf)); + else + chr->chr_write(chr, &mux_help[i][j], 1); + } + } +} + +static int mux_proc_byte(CharDriverState *chr, MuxDriver *d, int ch) +{ + if (d->term_got_escape) { + d->term_got_escape = 0; + if (ch == term_escape_char) + goto send_char; + switch(ch) { + case '?': + case 'h': + mux_print_help(chr); + break; + case 'x': + { + char *term = "QEMU: Terminated\n\r"; + chr->chr_write(chr,term,strlen(term)); + exit(0); + break; + } + case 's': + { + int i; + for (i = 0; i < MAX_DISKS; i++) { + if (bs_table[i]) + bdrv_commit(bs_table[i]); + } + } + break; + case 'b': + if (chr->chr_event) + chr->chr_event(chr->opaque, CHR_EVENT_BREAK); + break; + case 'c': + /* Switch to the next registered device */ + chr->focus++; + if (chr->focus >= d->mux_cnt) + chr->focus = 0; + break; + case 't': + term_timestamps = !term_timestamps; + term_timestamps_start = -1; + break; + } + } else if (ch == term_escape_char) { + d->term_got_escape = 1; + } else { + send_char: + return 1; + } + return 0; +} + +static int mux_chr_can_read(void *opaque) +{ + CharDriverState *chr = opaque; + MuxDriver *d = chr->opaque; + if (d->chr_can_read[chr->focus]) + return d->chr_can_read[chr->focus](d->ext_opaque[chr->focus]); + return 0; +} + +static void mux_chr_read(void *opaque, const uint8_t *buf, int size) +{ + CharDriverState *chr = opaque; + MuxDriver *d = chr->opaque; + int i; + for(i = 0; i < size; i++) + if (mux_proc_byte(chr, d, buf[i])) + d->chr_read[chr->focus](d->ext_opaque[chr->focus], &buf[i], 1); +} + +static void mux_chr_event(void *opaque, int event) +{ + CharDriverState *chr = opaque; + MuxDriver *d = chr->opaque; + int i; + + /* Send the event to all registered listeners */ + for (i = 0; i < d->mux_cnt; i++) + if (d->chr_event[i]) + d->chr_event[i](d->ext_opaque[i], event); +} + +static void mux_chr_update_read_handler(CharDriverState *chr) +{ + MuxDriver *d = chr->opaque; + + if (d->mux_cnt >= MAX_MUX) { + fprintf(stderr, "Cannot add I/O handlers, MUX array is full\n"); + return; + } + d->ext_opaque[d->mux_cnt] = chr->handler_opaque; + d->chr_can_read[d->mux_cnt] = chr->chr_can_read; + d->chr_read[d->mux_cnt] = chr->chr_read; + d->chr_event[d->mux_cnt] = chr->chr_event; + /* Fix up the real driver with mux routines */ + if (d->mux_cnt == 0) { + qemu_chr_add_handlers(d->drv, mux_chr_can_read, mux_chr_read, + mux_chr_event, chr); + } + chr->focus = d->mux_cnt; + d->mux_cnt++; +} + +CharDriverState *qemu_chr_open_mux(CharDriverState *drv) +{ + CharDriverState *chr; + MuxDriver *d; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + d = qemu_mallocz(sizeof(MuxDriver)); + if (!d) { + free(chr); + return NULL; + } + + chr->opaque = d; + d->drv = drv; + chr->focus = -1; + chr->chr_write = mux_chr_write; + chr->chr_update_read_handler = mux_chr_update_read_handler; + return chr; +} + + #ifdef _WIN32 static void socket_cleanup(void) @@ -1319,10 +1531,8 @@ typedef struct { int max_size; } FDCharDriver; -#define STDIO_MAX_CLIENTS 2 - -static int stdio_nb_clients; -static CharDriverState *stdio_clients[STDIO_MAX_CLIENTS]; +#define STDIO_MAX_CLIENTS 1 +static int stdio_nb_clients = 0; static int fd_chr_write(CharDriverState *chr, const uint8_t *buf, int len) { @@ -1435,162 +1645,45 @@ static CharDriverState *qemu_chr_open_pipe(const char *filename) /* for STDIO, we handle the case where several clients use it (nographic mode) */ -#define TERM_ESCAPE 0x01 /* ctrl-a is used for escape */ - #define TERM_FIFO_MAX_SIZE 1 -static int term_got_escape, client_index; static uint8_t term_fifo[TERM_FIFO_MAX_SIZE]; static int term_fifo_size; -static int term_timestamps; -static int64_t term_timestamps_start; - -void term_print_help(void) -{ - printf("\n" - "C-a h print this help\n" - "C-a x exit emulator\n" - "C-a s save disk data back to file (if -snapshot)\n" - "C-a b send break (magic sysrq)\n" - "C-a t toggle console timestamps\n" - "C-a c switch between console and monitor\n" - "C-a C-a send C-a\n" - ); -} - -/* called when a char is received */ -static void stdio_received_byte(int ch) -{ - if (term_got_escape) { - term_got_escape = 0; - switch(ch) { - case 'h': - term_print_help(); - break; - case 'x': - exit(0); - break; - case 's': - { - int i; - for (i = 0; i < MAX_DISKS; i++) { - if (bs_table[i]) - bdrv_commit(bs_table[i]); - } - } - break; - case 'b': - if (client_index < stdio_nb_clients) { - CharDriverState *chr; - FDCharDriver *s; - - chr = stdio_clients[client_index]; - s = chr->opaque; - qemu_chr_event(chr, CHR_EVENT_BREAK); - } - break; - case 'c': - client_index++; - if (client_index >= stdio_nb_clients) - client_index = 0; - if (client_index == 0) { - /* send a new line in the monitor to get the prompt */ - ch = '\r'; - goto send_char; - } - break; - case 't': - term_timestamps = !term_timestamps; - term_timestamps_start = -1; - break; - case TERM_ESCAPE: - goto send_char; - } - } else if (ch == TERM_ESCAPE) { - term_got_escape = 1; - } else { - send_char: - if (client_index < stdio_nb_clients) { - uint8_t buf[1]; - CharDriverState *chr; - - chr = stdio_clients[client_index]; - if (qemu_chr_can_read(chr) > 0) { - buf[0] = ch; - qemu_chr_read(chr, buf, 1); - } else if (term_fifo_size == 0) { - term_fifo[term_fifo_size++] = ch; - } - } - } -} static int stdio_read_poll(void *opaque) { - CharDriverState *chr; + CharDriverState *chr = opaque; - if (client_index < stdio_nb_clients) { - chr = stdio_clients[client_index]; - /* try to flush the queue if needed */ - if (term_fifo_size != 0 && qemu_chr_can_read(chr) > 0) { - qemu_chr_read(chr, term_fifo, 1); - term_fifo_size = 0; - } - /* see if we can absorb more chars */ - if (term_fifo_size == 0) - return 1; - else - return 0; - } else { - return 1; + /* try to flush the queue if needed */ + if (term_fifo_size != 0 && qemu_chr_can_read(chr) > 0) { + qemu_chr_read(chr, term_fifo, 1); + term_fifo_size = 0; } + /* see if we can absorb more chars */ + if (term_fifo_size == 0) + return 1; + else + return 0; } static void stdio_read(void *opaque) { int size; uint8_t buf[1]; - + CharDriverState *chr = opaque; + size = read(0, buf, 1); if (size == 0) { /* stdin has been closed. Remove it from the active list. */ qemu_set_fd_handler2(0, NULL, NULL, NULL, NULL); return; } - if (size > 0) - stdio_received_byte(buf[0]); -} - -static int stdio_write(CharDriverState *chr, const uint8_t *buf, int len) -{ - FDCharDriver *s = chr->opaque; - if (!term_timestamps) { - return unix_write(s->fd_out, buf, len); - } else { - int i; - char buf1[64]; - - for(i = 0; i < len; i++) { - unix_write(s->fd_out, buf + i, 1); - if (buf[i] == '\n') { - int64_t ti; - int secs; - - ti = get_clock(); - if (term_timestamps_start == -1) - term_timestamps_start = ti; - ti -= term_timestamps_start; - secs = ti / 1000000000; - snprintf(buf1, sizeof(buf1), - "[%02d:%02d:%02d.%03d] ", - secs / 3600, - (secs / 60) % 60, - secs % 60, - (int)((ti / 1000000) % 1000)); - unix_write(s->fd_out, buf1, strlen(buf1)); - } + if (size > 0) { + if (qemu_chr_can_read(chr) > 0) { + qemu_chr_read(chr, buf, 1); + } else if (term_fifo_size == 0) { + term_fifo[term_fifo_size++] = buf[0]; } - return len; } } @@ -1635,24 +1728,13 @@ static CharDriverState *qemu_chr_open_stdio(void) { CharDriverState *chr; - if (nographic) { - if (stdio_nb_clients >= STDIO_MAX_CLIENTS) - return NULL; - chr = qemu_chr_open_fd(0, 1); - chr->chr_write = stdio_write; - if (stdio_nb_clients == 0) - qemu_set_fd_handler2(0, stdio_read_poll, stdio_read, NULL, NULL); - client_index = stdio_nb_clients; - } else { - if (stdio_nb_clients != 0) - return NULL; - chr = qemu_chr_open_fd(0, 1); - } - stdio_clients[stdio_nb_clients++] = chr; - if (stdio_nb_clients == 1) { - /* set the terminal in raw mode */ - term_init(); - } + if (stdio_nb_clients >= STDIO_MAX_CLIENTS) + return NULL; + chr = qemu_chr_open_fd(0, 1); + qemu_set_fd_handler2(0, stdio_read_poll, stdio_read, NULL, chr); + stdio_nb_clients++; + term_init(); + return chr; } @@ -2815,6 +2897,16 @@ CharDriverState *qemu_chr_open(const char *filename) if (strstart(filename, "udp:", &p)) { return qemu_chr_open_udp(p); } else + if (strstart(filename, "mon:", &p)) { + CharDriverState *drv = qemu_chr_open(p); + if (drv) { + drv = qemu_chr_open_mux(drv); + monitor_init(drv, !nographic); + return drv; + } + printf("Unable to open driver: %s\n", p); + return 0; + } else #ifndef _WIN32 if (strstart(filename, "unix:", &p)) { return qemu_chr_open_tcp(p, 0, 1); @@ -6416,6 +6508,7 @@ enum { QEMU_OPTION_cirrusvga, QEMU_OPTION_g, QEMU_OPTION_std_vga, + QEMU_OPTION_echr, QEMU_OPTION_monitor, QEMU_OPTION_serial, QEMU_OPTION_parallel, @@ -6497,6 +6590,7 @@ const QEMUOption qemu_options[] = { #endif { "localtime", 0, QEMU_OPTION_localtime }, { "std-vga", 0, QEMU_OPTION_std_vga }, + { "echr", 1, QEMU_OPTION_echr }, { "monitor", 1, QEMU_OPTION_monitor }, { "serial", 1, QEMU_OPTION_serial }, { "parallel", 1, QEMU_OPTION_parallel }, @@ -6932,8 +7026,8 @@ int main(int argc, char **argv) } break; case QEMU_OPTION_nographic: - pstrcpy(monitor_device, sizeof(monitor_device), "stdio"); pstrcpy(serial_devices[0], sizeof(serial_devices[0]), "stdio"); + pstrcpy(monitor_device, sizeof(monitor_device), "stdio"); nographic = 1; break; case QEMU_OPTION_kernel: @@ -7094,6 +7188,14 @@ int main(int argc, char **argv) graphic_depth = depth; } break; + case QEMU_OPTION_echr: + { + char *r; + term_escape_char = strtol(optarg, &r, 0); + if (r == optarg) + printf("Bad argument to echr\n"); + break; + } case QEMU_OPTION_monitor: pstrcpy(monitor_device, sizeof(monitor_device), optarg); break; @@ -7384,12 +7486,27 @@ int main(int argc, char **argv) #endif } - monitor_hd = qemu_chr_open(monitor_device); - if (!monitor_hd) { - fprintf(stderr, "qemu: could not open monitor device '%s'\n", monitor_device); - exit(1); + /* Maintain compatibility with multiple stdio monitors */ + if (!strcmp(monitor_device,"stdio")) { + for (i = 0; i < MAX_SERIAL_PORTS; i++) { + if (!strcmp(serial_devices[i],"mon:stdio")) { + monitor_device[0] = '\0'; + break; + } else if (!strcmp(serial_devices[i],"stdio")) { + monitor_device[0] = '\0'; + pstrcpy(serial_devices[0], sizeof(serial_devices[0]), "mon:stdio"); + break; + } + } + } + if (monitor_device[0] != '\0') { + monitor_hd = qemu_chr_open(monitor_device); + if (!monitor_hd) { + fprintf(stderr, "qemu: could not open monitor device '%s'\n", monitor_device); + exit(1); + } + monitor_init(monitor_hd, !nographic); } - monitor_init(monitor_hd, !nographic); for(i = 0; i < MAX_SERIAL_PORTS; i++) { const char *devname = serial_devices[i]; diff --git a/vl.h b/vl.h index 79de55fa02e659f7d8ccd3a59109d6af28569902..bcaf004dd062fe8e505db68adaad84cd44bc8b74 100644 --- a/vl.h +++ b/vl.h @@ -303,6 +303,7 @@ typedef struct CharDriverState { void (*chr_send_event)(struct CharDriverState *chr, int event); void (*chr_close)(struct CharDriverState *chr); void *opaque; + int focus; QEMUBH *bh; } CharDriverState;