Index: qemu/vl.c =================================================================== --- qemu.orig/vl.c +++ qemu/vl.c @@ -1132,6 +1132,160 @@ CharDriverState *qemu_chr_open_null(void return chr; } +typedef struct { + IOCanRWHandler *fd_can_read; + IOReadHandler *fd_read; + void *ext_opaque; + int fd_in, fd_out; + void *mon_orig; + void *mon_opaque; + int max_size; +} MonitorDriver; + +#define TERM_ESCAPE 0x01 /* ctrl-a is used for escape */ +static MonitorDriver *mon_priv; /* pointer to current monitor console */ +static int term_got_escape = 0; +void monitor_print_help(void *opaque) +{ + CharDriverState *chr = opaque; + char *buf = "\n\r" + "C-a h print this help\n\r" + "C-a x exit emulator\n\r" + "C-a s save disk data back to file (if -snapshot)\n\r" + "C-a b send break (magic sysrq)\n\r" + "C-a c switch between console and monitor\n\r" + "C-a C-a send C-a\n\r"; + chr->chr_write(chr, buf, strlen(buf)); +} + +/* An I/O driver can choose to call this to get shared monitor + * support. The routine returns 1 if the caller should process the + * character else it returns 0 to indicate that the character was + * handled by this routine. + */ +static int proc_received_byte(int ch, void *opaque) +{ + CharDriverState *chr = opaque; + + if (!chr->monitorEnabled) + goto send_char; + if (term_got_escape) { + term_got_escape = 0; + switch(ch) { + case 'h': + monitor_print_help(opaque); + 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': + { + CharDriverState *chr; + /* Note that this is dependant on each driver which + * calls this proc_received_byte an opaque pointer at + * the same ofset in the structure. A some futre time this + * might have to be abstracted via opaque. + * + * IE: MonitorDriver, FDCharDriver, WinCharState + * --- + * IOCanRWHandler *can_read; + * IOReadHandler *read; + * void *opaque; + * --- + */ + MonitorDriver *s; + + chr = opaque; + s = chr->opaque; + if (chr->chr_event) + chr->chr_event(s->ext_opaque, CHR_EVENT_BREAK); + } + break; + case 'c': + /* swap the monitor in and out */ + if (mon_priv->mon_opaque == opaque) + { + mon_priv->mon_opaque = mon_priv->mon_orig; + } else { + mon_priv->mon_opaque = opaque; + /* send a new line in the monitor to get the prompt */ + ch = '\r'; + goto send_char; + } + break; + case TERM_ESCAPE: + goto send_char; + } + } else if (ch == TERM_ESCAPE) { + term_got_escape = 1; + } else { + send_char: + if (mon_priv && mon_priv->mon_opaque == opaque) { + uint8_t buf[1]; + /* Send characters to the monitor device */ + if (mon_priv->fd_can_read(mon_priv->mon_opaque) > 0) { + buf[0] = ch; + mon_priv->fd_read(mon_priv->mon_opaque, buf, 1); + } + return 0; + } + return 1; + } + return 0; +} + +static int monitor_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + MonitorDriver *d = chr->opaque; + if (d && d->mon_opaque) + { + CharDriverState *c = d->mon_opaque; + if (c->chr_write) + return c->chr_write(c, buf, len); + } + return len; +} + +static void monitor_chr_add_read_handler(CharDriverState *chr, + IOCanRWHandler *fd_can_read, + IOReadHandler *fd_read, void *opaque) +{ + MonitorDriver *d = chr->opaque; + d->ext_opaque = opaque; + d->fd_can_read = fd_can_read; + d->fd_read = fd_read; +} + +CharDriverState *qemu_chr_open_monitor(void) +{ + CharDriverState *chr; + MonitorDriver *d; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + d = qemu_mallocz(sizeof(MonitorDriver)); + if (!d) { + free(chr); + return NULL; + } + + chr->opaque = d; + d->mon_opaque = NULL; + chr->chr_write = monitor_chr_write; + chr->chr_add_read_handler = monitor_chr_add_read_handler; + return chr; +} + #ifdef _WIN32 static void socket_cleanup(void) @@ -1219,10 +1373,10 @@ void socket_set_nonblock(int fd) #ifndef _WIN32 typedef struct { - int fd_in, fd_out; IOCanRWHandler *fd_can_read; IOReadHandler *fd_read; void *fd_opaque; + int fd_in, fd_out; int max_size; } FDCharDriver; @@ -1328,91 +1482,12 @@ CharDriverState *qemu_chr_open_pipe(cons /* 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 int client_index; static uint8_t term_fifo[TERM_FIFO_MAX_SIZE]; int term_fifo_size; -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 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; - chr->chr_event(s->fd_opaque, 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 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; - FDCharDriver *s; - - chr = stdio_clients[client_index]; - s = chr->opaque; - if (s->fd_can_read(s->fd_opaque) > 0) { - buf[0] = ch; - s->fd_read(s->fd_opaque, buf, 1); - } else if (term_fifo_size == 0) { - term_fifo[term_fifo_size++] = ch; - } - } - } -} - static int stdio_read_poll(void *opaque) { CharDriverState *chr; @@ -1443,7 +1518,16 @@ static void stdio_read(void *opaque) size = read(0, buf, 1); if (size > 0) - stdio_received_byte(buf[0]); + { + if (proc_received_byte(buf[0], stdio_clients[client_index])) { + FDCharDriver *s = stdio_clients[client_index]->opaque; + if (s->fd_can_read(s->fd_opaque) > 0) { + s->fd_read(s->fd_opaque, buf, 1); + } else if (term_fifo_size == 0) { + term_fifo[term_fifo_size++] = buf[0]; + } + } + } } /* init terminal so that we can grab keys */ @@ -2106,14 +2190,207 @@ CharDriverState *qemu_chr_open_win_file_ } #endif +/***********************************************************/ +/* Net console */ + +typedef struct { + IOCanRWHandler *fd_can_read; + IOReadHandler *fd_read; + void *fd_opaque; + int fd_in, fd_out; + void *net_opaque; + char buf[1024]; + int bufcnt; + int bufptr; + int max_size; +} NetCharDriver; + +static int udp_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + NetCharDriver *c = chr->opaque; + struct sockaddr_in *s = c->net_opaque; + + return sendto(c->fd_in, buf, len, 0, + (struct sockaddr *)s, sizeof(struct sockaddr_in)); +} + +static int udp_chr_read_poll(void *opaque) +{ + CharDriverState *chr = opaque; + NetCharDriver *s = chr->opaque; + + s->max_size = s->fd_can_read(s->fd_opaque); + + /* If there were any stray characters in the queue process them + * first + */ + while (s->max_size > 0 && s->bufptr < s->bufcnt) { + if (proc_received_byte(s->buf[s->bufptr], opaque)) { + s->fd_read(s->fd_opaque, &s->buf[s->bufptr], 1); + } + s->bufptr++; + s->max_size = s->fd_can_read(s->fd_opaque); + } + return s->max_size; +} + +static void udp_chr_read(void *opaque) +{ + CharDriverState *chr = opaque; + NetCharDriver *s = chr->opaque; + + if (s->max_size == 0) + return; + s->bufcnt = recv(s->fd_in, s->buf, sizeof(s->buf), 0); + s->bufptr = s->bufcnt; + if (s->bufcnt <= 0) + return; + + s->bufptr = 0; + while (s->max_size > 0 && s->bufptr < s->bufcnt) { + if (proc_received_byte(s->buf[s->bufptr], opaque)) { + s->fd_read(s->fd_opaque, &s->buf[s->bufptr], 1); + } + s->bufptr++; + s->max_size = s->fd_can_read(s->fd_opaque); + } +} + +static void udp_chr_add_read_handler(CharDriverState *chr, + IOCanRWHandler *fd_can_read, + IOReadHandler *fd_read, void *opaque) +{ + NetCharDriver *s = chr->opaque; + + if (s->fd_in >= 0) { + s->fd_can_read = fd_can_read; + s->fd_read = fd_read; + s->fd_opaque = opaque; + qemu_set_fd_handler2(s->fd_in, udp_chr_read_poll, + udp_chr_read, NULL, chr); + } +} + +int parse_host_port(struct sockaddr_in *saddr, const char *str); + +CharDriverState *qemu_chr_open_udp(const char *def) +{ + CharDriverState *chr = NULL; + NetCharDriver *s = NULL; + struct sockaddr_in *net_opaque = NULL; + int fd = -1; + int con_type; + struct sockaddr_in addr; + const char *p, *r; + int port; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + goto return_err; + s = qemu_mallocz(sizeof(NetCharDriver)); + if (!s) + goto return_err; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + perror("socket(PF_INET, SOCK_DGRAM)"); + goto return_err; + } + net_opaque = qemu_mallocz(sizeof(struct sockaddr_in)); + if (!net_opaque) + goto return_err; + + /* There are three types of port definitions + * 1) udp:remote_port + * Juse use 0.0.0.0 for the IP and send to remote + * 2) udp:remote_host:port + * Use a IP and send traffic to remote + * 3) udp:local_port:remote_host:remote_port + * Use local_port as the originator + #2 + */ + con_type = 0; + p = def; + while ((p = strchr(p,':'))) { + p++; + con_type++; + } + + p = def; + memset(&addr,0,sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + net_opaque->sin_family = AF_INET; + net_opaque->sin_addr.s_addr = htonl(INADDR_ANY); + + switch (con_type) { + case 0: + port = strtol(p, (char **)&r, 0); + if (r == p) { + fprintf(stderr, "Error parsing port number\n"); + goto return_err; + } + net_opaque->sin_port = htons((short)port); + break; + case 2: + port = strtol(p, (char **)&r, 0); + if (r == p) { + fprintf(stderr, "Error parsing port number\n"); + goto return_err; + } + addr.sin_port = htons((short)port); + p = r + 1; + /* Fall through to case 1 now that we have the local port */ + case 1: + if (parse_host_port(net_opaque, p) < 0) { + fprintf(stderr, "Error parsing host name and port\n"); + goto return_err; + } + break; + default: + fprintf(stderr, "Too many ':' characters\n"); + goto return_err; + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + perror("bind"); + goto return_err; + } + + s->fd_in = fd; + s->fd_out = fd; + s->net_opaque = net_opaque; + s->bufcnt = 0; + s->bufptr = 0; + chr->opaque = s; + chr->chr_write = udp_chr_write; + chr->chr_add_read_handler = udp_chr_add_read_handler; + return chr; + +return_err: + if (chr) + free(chr); + if (s) + free(s); + if (net_opaque) + free(net_opaque); + if (fd >= 0) + closesocket(fd); + return NULL; +} + CharDriverState *qemu_chr_open(const char *filename) { const char *p; if (!strcmp(filename, "vc")) { return text_console_init(&display_state); + } else if (!strcmp(filename, "monitor")) { + return qemu_chr_open_monitor(); } else if (!strcmp(filename, "null")) { return qemu_chr_open_null(); + } else if (strstart(filename, "udp:", &p)) { + return qemu_chr_open_udp(p); } else #ifndef _WIN32 if (strstart(filename, "file:", &p)) { @@ -4763,6 +5040,7 @@ enum { QEMU_OPTION_g, QEMU_OPTION_std_vga, QEMU_OPTION_monitor, + QEMU_OPTION_mserial, QEMU_OPTION_serial, QEMU_OPTION_parallel, QEMU_OPTION_loadvm, @@ -4835,6 +5113,7 @@ const QEMUOption qemu_options[] = { { "localtime", 0, QEMU_OPTION_localtime }, { "std-vga", 0, QEMU_OPTION_std_vga }, { "monitor", 1, QEMU_OPTION_monitor }, + { "mserial", 1, QEMU_OPTION_mserial }, { "serial", 1, QEMU_OPTION_serial }, { "parallel", 1, QEMU_OPTION_parallel }, { "loadvm", HAS_ARG, QEMU_OPTION_loadvm }, @@ -5063,6 +5342,7 @@ int main(int argc, char **argv) CharDriverState *monitor_hd; char monitor_device[128]; char serial_devices[MAX_SERIAL_PORTS][128]; + int serial_device_monitor[MAX_SERIAL_PORTS]; int serial_device_index; char parallel_devices[MAX_PARALLEL_PORTS][128]; int parallel_device_index; @@ -5101,11 +5381,14 @@ int main(int argc, char **argv) #endif cyls = heads = secs = 0; translation = BIOS_ATA_TRANSLATION_AUTO; - pstrcpy(monitor_device, sizeof(monitor_device), "vc"); + monitor_device[0] = '\0'; pstrcpy(serial_devices[0], sizeof(serial_devices[0]), "vc"); for(i = 1; i < MAX_SERIAL_PORTS; i++) + { serial_devices[i][0] = '\0'; + serial_device_monitor[i] = 0; + } serial_device_index = 0; pstrcpy(parallel_devices[0], sizeof(parallel_devices[0]), "vc"); @@ -5222,7 +5505,6 @@ 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"); nographic = 1; break; @@ -5382,6 +5664,15 @@ int main(int argc, char **argv) case QEMU_OPTION_monitor: pstrcpy(monitor_device, sizeof(monitor_device), optarg); break; + case QEMU_OPTION_mserial: + if (serial_device_index >= MAX_SERIAL_PORTS) { + fprintf(stderr, "qemu: too many serial ports\n"); + exit(1); + } + serial_device_monitor[serial_device_index] = 1; + /* fall through to normal serial processing + * serial_devices[serial_device_index] + */ case QEMU_OPTION_serial: if (serial_device_index >= MAX_SERIAL_PORTS) { fprintf(stderr, "qemu: too many serial ports\n"); @@ -5638,13 +5929,7 @@ 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); - } - monitor_init(monitor_hd, !nographic); - + monitor_hd = NULL; for(i = 0; i < MAX_SERIAL_PORTS; i++) { if (serial_devices[i][0] != '\0') { serial_hds[i] = qemu_chr_open(serial_devices[i]); @@ -5655,9 +5940,50 @@ int main(int argc, char **argv) } if (!strcmp(serial_devices[i], "vc")) qemu_chr_printf(serial_hds[i], "serial%d console\n", i); + if (serial_device_monitor[i] || + (monitor_device[0] == '\0' && nographic && + strcmp(serial_devices[i], "stdio") == 0)) { + serial_hds[i]->monitorEnabled = 1; + if (!monitor_hd) { + monitor_hd = qemu_chr_open("monitor"); + if (!monitor_hd) { + fprintf(stderr, "qemu: failed internal monitor open"); + exit(1); + } + } + } } } + if (!monitor_hd) { + if (monitor_device[0] == '\0' && !nographic) { + /* Setup defaults because no monitor device was specificed */ + if (!nographic) + pstrcpy(monitor_device, sizeof(monitor_device), "vc"); + } + monitor_hd = qemu_chr_open(monitor_device); + if (!monitor_hd) { + fprintf(stderr, "qemu: could not open monitor device '%s'\n", monitor_device); + exit(1); + } + } else if (monitor_device[0] != '\0') { + /* Support a secondary monitor device */ + CharDriverState *tmp = qemu_chr_open(monitor_device); + if (!tmp) { + fprintf(stderr, "qemu: could not open 2nd monitor device '%s'\n", monitor_device); + exit(1); + } + /* Tell the monitor driver we are going to support sharing and + * have a default monitor to an individual terminal + */ + ((MonitorDriver *)monitor_hd->opaque)->mon_orig = tmp; + ((MonitorDriver *)monitor_hd->opaque)->mon_opaque = tmp; + monitor_init(tmp, 1); + } + + monitor_init(monitor_hd, !nographic); + mon_priv = monitor_hd->opaque; + for(i = 0; i < MAX_PARALLEL_PORTS; i++) { if (parallel_devices[i][0] != '\0') { parallel_hds[i] = qemu_chr_open(parallel_devices[i]); Index: qemu/monitor.c =================================================================== --- qemu.orig/monitor.c +++ qemu/monitor.c @@ -84,8 +84,19 @@ void term_puts(const char *str) break; term_outbuf[term_outbuf_index++] = c; if (term_outbuf_index >= sizeof(term_outbuf) || - c == '\n') + c == '\n') { term_flush(); + /* Tack on an extra \r when we get \n for correct terminal + * emulation mainly for telnet. Most of the time, the + * stdio is attached to some type of controlling terminal + * and the xterm and vt1xx emulation will automatically + * eat these '\r' chars. + */ + if (c == '\n') + { + term_outbuf[term_outbuf_index++] = '\r'; + } + } } } Index: qemu/vl.h =================================================================== --- qemu.orig/vl.h +++ qemu/vl.h @@ -253,6 +253,7 @@ typedef struct CharDriverState { void (*chr_send_event)(struct CharDriverState *chr, int event); void (*chr_close)(struct CharDriverState *chr); void *opaque; + int monitorEnabled; } CharDriverState; void qemu_chr_printf(CharDriverState *s, const char *fmt, ...); Index: qemu/qemu-doc.texi =================================================================== --- qemu.orig/qemu-doc.texi +++ qemu/qemu-doc.texi @@ -514,6 +514,12 @@ Write output to filename. No character c [Unix only] standard input/output @item pipe:filename [Unix only] name pipe @var{filename} +@item udp:remote_port +udp Net Console sent to locahost at remote_port +@item udp:remote_host:remote_port +udp Net Console sent to remote_host at remote_port +@item udp:src_port:remote_host:remote_port +udp Net Console sent from src_port to remote_host at the remote_port @end table The default device is @code{vc} in graphical mode and @code{stdio} in non graphical mode. @@ -521,6 +527,30 @@ non graphical mode. This option can be used several times to simulate up to 4 serials ports. +The upd:* sub options are primary intended for netconsole. If you just want a simple readonly console you can use @code{netcat} or @code{nc}, by starting qemu with: @code{-serial udp:4555} and nc as: @code{nc -u -l -p 4555}. Any time qemu writes something to that port it will appear in the netconsole session. + +If you plan to send characters back via netconsole or you want to stop and start qemu a lot of times, you should have qemu use the same source port each time by using something like @code{-serial udp:4556:localhost:4555} to qemu. Another approach is to use a patched version of netcat which can listen to a TCP port and send and receive characters via udp. If you have a patched version of netcat which activates telnet remote echo and single char transfer, then you can use the following options to step up a netcat redirector to allow telnet on port 5555 to access the qemu port. +@table @code +@item Qemu Options +-serial udp:4556:localhost:4555 +@item netcat options +-u -P 4555 -L localhost:4556 -t -p 5555 -I -T +@end table + + +@item -mserial dev +Redirect the virtual serial port to host device @var{dev}. This option alows multiplexing the monitor onto a device. This option only works with: +@table @code +@item stdio +@item udp:remote_port +@item udp:remote_host:remote_port +@item udp:src_port:remote_host:remote_port +@end table + +This option is more or less the same as -serial and has the same semantics, but you can switch between the monitor and the serial port. You do this with @key{Ctrl-a} @key{c}. See monitor accesc @ref{pcsys_keys} in the -nographic section for more keys. + +Additionaly this option will work in conjunction with a dedicated monitor specified with @code{-monitor}. When the monitor is active on the port with -mserial, it will be temporarily disabled on the dedicated port. + @item -parallel dev Redirect the virtual parallel port to host device @var{dev} (same devices as the serial port). On Linux hosts, @file{/dev/parportN} can