* [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control @ 2010-04-15 8:16 Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 1/4] char: Let writers know how much data was written in case of errors Amit Shah 2010-04-15 12:04 ` [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control Paul Brook 0 siblings, 2 replies; 12+ messages in thread From: Amit Shah @ 2010-04-15 8:16 UTC (permalink / raw) To: qemu list; +Cc: Amit Shah, Gerd Hoffmann, paul Hello, This series lets interested callers ask for an -EAGAIN return from the chardev backends (only unix and tcp sockets as of now) to implement their own flow control. Support for other backend types is easy to add, I'll do that in a separate series if this is acceptable. Amit Shah (4): char: Let writers know how much data was written in case of errors char: Add ability to provide a callback when write won't return -EAGAIN virtio-console: Factor out common init between console and generic ports virtio-console: Throttle virtio-serial-bus if we can't consume any more guest data gdbstub.c | 2 +- hw/debugcon.c | 2 +- hw/escc.c | 3 +- hw/etraxfs_ser.c | 4 +- hw/mcf_uart.c | 2 +- hw/pl011.c | 2 +- hw/pxa2xx.c | 2 +- hw/serial.c | 2 +- hw/sh_serial.c | 2 +- hw/syborg_serial.c | 3 +- hw/usb-serial.c | 2 +- hw/virtio-console.c | 156 +++++++++++++++++++++++++++++++++++++++++++------- hw/xen_console.c | 7 +- hw/xilinx_uartlite.c | 5 +- monitor.c | 4 +- net/slirp.c | 2 +- net/socket.c | 4 +- qemu-char.c | 68 ++++++++++++++++++---- qemu-char.h | 6 ++ qemu_socket.h | 3 +- 20 files changed, 227 insertions(+), 54 deletions(-) ^ permalink raw reply [flat|nested] 12+ messages in thread
* [Qemu-devel] [PATCH v3 1/4] char: Let writers know how much data was written in case of errors 2010-04-15 8:16 [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control Amit Shah @ 2010-04-15 8:16 ` Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Amit Shah 2010-04-15 12:04 ` [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control Paul Brook 1 sibling, 1 reply; 12+ messages in thread From: Amit Shah @ 2010-04-15 8:16 UTC (permalink / raw) To: qemu list; +Cc: Amit Shah, Gerd Hoffmann, paul On writing errors, we just returned -1 even if some bytes were already written out. Ensure we return the number of bytes written before we return the error (on a subsequent call to qemu_chr_write()). Signed-off-by: Amit Shah <amit.shah@redhat.com> --- qemu-char.c | 9 +++++++-- 1 files changed, 7 insertions(+), 2 deletions(-) diff --git a/qemu-char.c b/qemu-char.c index 05df971..6aaa362 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -522,8 +522,13 @@ static int unix_write(int fd, const uint8_t *buf, int len1) while (len > 0) { ret = write(fd, buf, len); if (ret < 0) { - if (errno != EINTR && errno != EAGAIN) - return -1; + if (errno != EINTR && errno != EAGAIN) { + if (len1 - len) { + return len1 - len; + } else { + return -1; + } + } } else if (ret == 0) { break; } else { -- 1.6.2.5 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 1/4] char: Let writers know how much data was written in case of errors Amit Shah @ 2010-04-15 8:16 ` Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 3/4] virtio-console: Factor out common init between console and generic ports Amit Shah 2010-04-20 11:32 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Gerd Hoffmann 0 siblings, 2 replies; 12+ messages in thread From: Amit Shah @ 2010-04-15 8:16 UTC (permalink / raw) To: qemu list; +Cc: Amit Shah, Gerd Hoffmann, paul For nonblocking chardevs, we can return -EAGAIN to callers of qemu_chr_write() and let the caller know that the backend is ready to accept more data via a callback. Currently only unix and tcp sockets have the ability to return -EAGAIN and to provide such a callback. Update all callers of qemu chardevs to not register the callback handler so that the current behaviour of qemu_chr_write() not returning till the entire buffer is written out is maintained. Individual callers can update their code to add a callback handler and register the handler at the time of calling qemu_chr_add_handlers() if they wish to receive -EAGAIN notifications. Signed-off-by: Amit Shah <amit.shah@redhat.com> --- gdbstub.c | 2 +- hw/debugcon.c | 2 +- hw/escc.c | 3 +- hw/etraxfs_ser.c | 4 +- hw/mcf_uart.c | 2 +- hw/pl011.c | 2 +- hw/pxa2xx.c | 2 +- hw/serial.c | 2 +- hw/sh_serial.c | 2 +- hw/syborg_serial.c | 3 +- hw/usb-serial.c | 2 +- hw/virtio-console.c | 8 +++--- hw/xen_console.c | 7 +++-- hw/xilinx_uartlite.c | 5 ++- monitor.c | 4 +- net/slirp.c | 2 +- net/socket.c | 4 +- qemu-char.c | 59 ++++++++++++++++++++++++++++++++++++++++++------- qemu-char.h | 6 +++++ qemu_socket.h | 3 +- 20 files changed, 88 insertions(+), 36 deletions(-) diff --git a/gdbstub.c b/gdbstub.c index 93c4850..d6e5b23 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -2650,7 +2650,7 @@ int gdbserver_start(const char *device) if (!chr) return -1; - qemu_chr_add_handlers(chr, gdb_chr_can_receive, gdb_chr_receive, + qemu_chr_add_handlers(chr, gdb_chr_can_receive, gdb_chr_receive, NULL, gdb_chr_event, NULL); } diff --git a/hw/debugcon.c b/hw/debugcon.c index 5ee6821..4fa9189 100644 --- a/hw/debugcon.c +++ b/hw/debugcon.c @@ -73,7 +73,7 @@ static void debugcon_init_core(DebugconState *s) exit(1); } - qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, s); + qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, NULL, s); } static int debugcon_isa_initfn(ISADevice *dev) diff --git a/hw/escc.c b/hw/escc.c index 6d2fd36..94d1ba7 100644 --- a/hw/escc.c +++ b/hw/escc.c @@ -904,7 +904,8 @@ static int escc_init1(SysBusDevice *dev) s->chn[i].clock = s->frequency / 2; if (s->chn[i].chr) { qemu_chr_add_handlers(s->chn[i].chr, serial_can_receive, - serial_receive1, serial_event, &s->chn[i]); + serial_receive1, NULL, serial_event, + &s->chn[i]); } } s->chn[0].otherchn = &s->chn[1]; diff --git a/hw/etraxfs_ser.c b/hw/etraxfs_ser.c index e1f9615..5e592c9 100644 --- a/hw/etraxfs_ser.c +++ b/hw/etraxfs_ser.c @@ -176,8 +176,8 @@ static int etraxfs_ser_init(SysBusDevice *dev) s->chr = qdev_init_chardev(&dev->qdev); if (s->chr) qemu_chr_add_handlers(s->chr, - serial_can_receive, serial_receive, - serial_event, s); + serial_can_receive, serial_receive, NULL, + serial_event, s); return 0; } diff --git a/hw/mcf_uart.c b/hw/mcf_uart.c index d16bac7..5cea623 100644 --- a/hw/mcf_uart.c +++ b/hw/mcf_uart.c @@ -277,7 +277,7 @@ void *mcf_uart_init(qemu_irq irq, CharDriverState *chr) s->irq = irq; if (chr) { qemu_chr_add_handlers(chr, mcf_uart_can_receive, mcf_uart_receive, - mcf_uart_event, s); + NULL, mcf_uart_event, s); } mcf_uart_reset(s); return s; diff --git a/hw/pl011.c b/hw/pl011.c index 81de91e..73ae086 100644 --- a/hw/pl011.c +++ b/hw/pl011.c @@ -304,7 +304,7 @@ static int pl011_init(SysBusDevice *dev, const unsigned char *id) s->flags = 0x90; if (s->chr) { qemu_chr_add_handlers(s->chr, pl011_can_receive, pl011_receive, - pl011_event, s); + NULL, pl011_event, s); } register_savevm("pl011_uart", -1, 1, pl011_save, pl011_load, s); return 0; diff --git a/hw/pxa2xx.c b/hw/pxa2xx.c index 9095386..04dbda4 100644 --- a/hw/pxa2xx.c +++ b/hw/pxa2xx.c @@ -2012,7 +2012,7 @@ static PXA2xxFIrState *pxa2xx_fir_init(target_phys_addr_t base, if (chr) qemu_chr_add_handlers(chr, pxa2xx_fir_is_empty, - pxa2xx_fir_rx, pxa2xx_fir_event, s); + pxa2xx_fir_rx, NULL, pxa2xx_fir_event, s); register_savevm("pxa2xx_fir", 0, 0, pxa2xx_fir_save, pxa2xx_fir_load, s); diff --git a/hw/serial.c b/hw/serial.c index 90213c4..0320494 100644 --- a/hw/serial.c +++ b/hw/serial.c @@ -738,7 +738,7 @@ static void serial_init_core(SerialState *s) qemu_register_reset(serial_reset, s); - qemu_chr_add_handlers(s->chr, serial_can_receive1, serial_receive1, + qemu_chr_add_handlers(s->chr, serial_can_receive1, serial_receive1, NULL, serial_event, s); } diff --git a/hw/sh_serial.c b/hw/sh_serial.c index 93dc144..2c81798 100644 --- a/hw/sh_serial.c +++ b/hw/sh_serial.c @@ -403,7 +403,7 @@ void sh_serial_init (target_phys_addr_t base, int feat, if (chr) qemu_chr_add_handlers(chr, sh_serial_can_receive1, sh_serial_receive1, - sh_serial_event, s); + NULL, sh_serial_event, s); s->eri = eri_source; s->rxi = rxi_source; diff --git a/hw/syborg_serial.c b/hw/syborg_serial.c index cac00ea..9cf3591 100644 --- a/hw/syborg_serial.c +++ b/hw/syborg_serial.c @@ -327,7 +327,8 @@ static int syborg_serial_init(SysBusDevice *dev) s->chr = qdev_init_chardev(&dev->qdev); if (s->chr) { qemu_chr_add_handlers(s->chr, syborg_serial_can_receive, - syborg_serial_receive, syborg_serial_event, s); + syborg_serial_receive, NULL, + syborg_serial_event, s); } if (s->fifo_size <= 0) { fprintf(stderr, "syborg_serial: fifo too small\n"); diff --git a/hw/usb-serial.c b/hw/usb-serial.c index 69f0e44..c44d4b0 100644 --- a/hw/usb-serial.c +++ b/hw/usb-serial.c @@ -545,7 +545,7 @@ static int usb_serial_initfn(USBDevice *dev) USBSerialState *s = DO_UPCAST(USBSerialState, dev, dev); s->dev.speed = USB_SPEED_FULL; - qemu_chr_add_handlers(s->cs, usb_serial_can_read, usb_serial_read, + qemu_chr_add_handlers(s->cs, usb_serial_can_read, usb_serial_read, NULL, usb_serial_event, s); usb_serial_handle_reset(dev); return 0; diff --git a/hw/virtio-console.c b/hw/virtio-console.c index caea11f..2bccdd0 100644 --- a/hw/virtio-console.c +++ b/hw/virtio-console.c @@ -69,8 +69,8 @@ static int virtconsole_initfn(VirtIOSerialDevice *dev) port->is_console = true; if (vcon->chr) { - qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event, - vcon); + qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, + NULL, chr_event, vcon); port->info->have_data = flush_buf; } return 0; @@ -118,8 +118,8 @@ static int virtserialport_initfn(VirtIOSerialDevice *dev) port->info = dev->info; if (vcon->chr) { - qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event, - vcon); + qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, + NULL, chr_event, vcon); port->info->have_data = flush_buf; } return 0; diff --git a/hw/xen_console.c b/hw/xen_console.c index d2261f4..387c7ed 100644 --- a/hw/xen_console.c +++ b/hw/xen_console.c @@ -224,7 +224,7 @@ static int con_connect(struct XenDevice *xendev) xen_be_bind_evtchn(&con->xendev); if (con->chr) qemu_chr_add_handlers(con->chr, xencons_can_receive, xencons_receive, - NULL, con); + NULL, NULL, con); xen_be_printf(xendev, 1, "ring mfn %d, remote port %d, local port %d, limit %zd\n", con->ring_ref, @@ -238,8 +238,9 @@ static void con_disconnect(struct XenDevice *xendev) { struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); - if (con->chr) - qemu_chr_add_handlers(con->chr, NULL, NULL, NULL, NULL); + if (con->chr) { + qemu_chr_add_handlers(con->chr, NULL, NULL, NULL, NULL, NULL); + } xen_be_unbind_evtchn(&con->xendev); if (con->sring) { diff --git a/hw/xilinx_uartlite.c b/hw/xilinx_uartlite.c index adab759..b811167 100644 --- a/hw/xilinx_uartlite.c +++ b/hw/xilinx_uartlite.c @@ -205,8 +205,9 @@ static int xilinx_uartlite_init(SysBusDevice *dev) sysbus_init_mmio(dev, R_MAX * 4, uart_regs); s->chr = qdev_init_chardev(&dev->qdev); - if (s->chr) - qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, NULL, uart_event, s); + } return 0; } diff --git a/monitor.c b/monitor.c index 9c05981..e43a130 100644 --- a/monitor.c +++ b/monitor.c @@ -4590,10 +4590,10 @@ void monitor_init(CharDriverState *chr, int flags) if (monitor_ctrl_mode(mon)) { mon->mc = qemu_mallocz(sizeof(MonitorControl)); /* Control mode requires special handlers */ - qemu_chr_add_handlers(chr, monitor_can_read, monitor_control_read, + qemu_chr_add_handlers(chr, monitor_can_read, monitor_control_read, NULL, monitor_control_event, mon); } else { - qemu_chr_add_handlers(chr, monitor_can_read, monitor_read, + qemu_chr_add_handlers(chr, monitor_can_read, monitor_read, NULL, monitor_event, mon); } diff --git a/net/slirp.c b/net/slirp.c index b41c60a..2a915ab 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -634,7 +634,7 @@ static int slirp_guestfwd(SlirpState *s, const char *config_str, fwd->slirp = s->slirp; qemu_chr_add_handlers(fwd->hd, guestfwd_can_read, guestfwd_read, - NULL, fwd); + NULL, NULL, fwd); return 0; fail_syntax: diff --git a/net/socket.c b/net/socket.c index 1c4e153..8a401e6 100644 --- a/net/socket.c +++ b/net/socket.c @@ -56,8 +56,8 @@ static ssize_t net_socket_receive(VLANClientState *nc, const uint8_t *buf, size_ uint32_t len; len = htonl(size); - send_all(s->fd, (const uint8_t *)&len, sizeof(len)); - return send_all(s->fd, buf, size); + send_all(s->fd, (const uint8_t *)&len, sizeof(len), false); + return send_all(s->fd, buf, size, false); } static ssize_t net_socket_receive_dgram(VLANClientState *nc, const uint8_t *buf, size_t size) diff --git a/qemu-char.c b/qemu-char.c index 6aaa362..4964d35 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -194,16 +194,21 @@ void qemu_chr_send_event(CharDriverState *s, int event) void qemu_chr_add_handlers(CharDriverState *s, IOCanReadHandler *fd_can_read, IOReadHandler *fd_read, + IOHandler *fd_write_unblocked, IOEventHandler *fd_event, void *opaque) { s->chr_can_read = fd_can_read; s->chr_read = fd_read; + s->chr_write_unblocked = fd_write_unblocked; s->chr_event = fd_event; s->handler_opaque = opaque; if (s->chr_update_read_handler) s->chr_update_read_handler(s); + /* We'll set this at connect-time */ + s->nonblock = false; + /* We're connecting to an already opened device, so let's make sure we also get the open event */ if (s->opened) { @@ -456,7 +461,7 @@ static void mux_chr_update_read_handler(CharDriverState *chr) 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, + qemu_chr_add_handlers(d->drv, mux_chr_can_read, mux_chr_read, NULL, mux_chr_event, chr); } if (d->focus != -1) { @@ -490,7 +495,7 @@ static CharDriverState *qemu_chr_open_mux(CharDriverState *drv) #ifdef _WIN32 -int send_all(int fd, const void *buf, int len1) +int send_all(int fd, const void *buf, int len1, bool nonblock) { int ret, len; @@ -514,7 +519,7 @@ int send_all(int fd, const void *buf, int len1) #else -static int unix_write(int fd, const uint8_t *buf, int len1) +static int unix_write(int fd, const uint8_t *buf, int len1, bool nonblock) { int ret, len; @@ -522,6 +527,9 @@ static int unix_write(int fd, const uint8_t *buf, int len1) while (len > 0) { ret = write(fd, buf, len); if (ret < 0) { + if (errno == EAGAIN && nonblock) { + return -EAGAIN; + } if (errno != EINTR && errno != EAGAIN) { if (len1 - len) { return len1 - len; @@ -539,9 +547,9 @@ static int unix_write(int fd, const uint8_t *buf, int len1) return len1 - len; } -int send_all(int fd, const void *buf, int len1) +int send_all(int fd, const void *buf, int len1, bool nonblock) { - return unix_write(fd, buf, len1); + return unix_write(fd, buf, len1, nonblock); } #endif /* !_WIN32 */ @@ -558,7 +566,7 @@ static int stdio_nb_clients = 0; static int fd_chr_write(CharDriverState *chr, const uint8_t *buf, int len) { FDCharDriver *s = chr->opaque; - return send_all(s->fd_out, buf, len); + return send_all(s->fd_out, buf, len, chr->nonblock); } static int fd_chr_read_poll(void *opaque) @@ -870,7 +878,7 @@ static int pty_chr_write(CharDriverState *chr, const uint8_t *buf, int len) pty_chr_update_read_handler(chr); return 0; } - return send_all(s->fd, buf, len); + return send_all(s->fd, buf, len, chr->nonblock); } static int pty_chr_read_poll(void *opaque) @@ -1934,8 +1942,14 @@ static void tcp_chr_accept(void *opaque); static int tcp_chr_write(CharDriverState *chr, const uint8_t *buf, int len) { TCPCharDriver *s = chr->opaque; + int ret; + if (s->connected) { - return send_all(s->fd, buf, len); + ret = send_all(s->fd, buf, len, chr->nonblock); + if (ret == -EAGAIN) { + chr->write_blocked = true; + } + return ret; } else { /* XXX: indicate an error ? */ return len; @@ -2101,14 +2115,41 @@ static void tcp_chr_read(void *opaque) } } +static void tcp_chr_write_unblocked(void *opaque) +{ + CharDriverState *chr = opaque; + + if (chr->write_blocked && chr->chr_write_unblocked) { + chr->write_blocked = false; + chr->chr_write_unblocked(chr->handler_opaque); + } +} + static void tcp_chr_connect(void *opaque) { CharDriverState *chr = opaque; TCPCharDriver *s = chr->opaque; + IOHandler *write_cb; + int flags; + bool nonblock; + + flags = fcntl(s->fd, F_GETFL); + if (flags == -1) { + flags = 0; + } + nonblock = flags & O_NONBLOCK; + + write_cb = NULL; + chr->nonblock = false; + if (nonblock && chr->chr_write_unblocked) { + write_cb = chr->chr_write_unblocked; + chr->nonblock = true; + } + chr->write_blocked = false; s->connected = 1; qemu_set_fd_handler2(s->fd, tcp_chr_read_poll, - tcp_chr_read, NULL, chr); + tcp_chr_read, tcp_chr_write_unblocked, chr); qemu_chr_generic_open(chr); } diff --git a/qemu-char.h b/qemu-char.h index e3a0783..d65c31a 100644 --- a/qemu-char.h +++ b/qemu-char.h @@ -1,6 +1,8 @@ #ifndef QEMU_CHAR_H #define QEMU_CHAR_H +#include <stdbool.h> + #include "qemu-common.h" #include "qemu-queue.h" #include "qemu-option.h" @@ -59,6 +61,7 @@ struct CharDriverState { IOEventHandler *chr_event; IOCanReadHandler *chr_can_read; IOReadHandler *chr_read; + IOHandler *chr_write_unblocked; void *handler_opaque; void (*chr_send_event)(struct CharDriverState *chr, int event); void (*chr_close)(struct CharDriverState *chr); @@ -68,6 +71,8 @@ struct CharDriverState { char *label; char *filename; int opened; + bool nonblock; + bool write_blocked; QTAILQ_ENTRY(CharDriverState) next; }; @@ -82,6 +87,7 @@ void qemu_chr_send_event(CharDriverState *s, int event); void qemu_chr_add_handlers(CharDriverState *s, IOCanReadHandler *fd_can_read, IOReadHandler *fd_read, + IOHandler *fd_write_unblocked, IOEventHandler *fd_event, void *opaque); int qemu_chr_ioctl(CharDriverState *s, int cmd, void *arg); diff --git a/qemu_socket.h b/qemu_socket.h index 164ae3e..bdf878b 100644 --- a/qemu_socket.h +++ b/qemu_socket.h @@ -23,6 +23,7 @@ int inet_aton(const char *cp, struct in_addr *ia); #include <arpa/inet.h> #include <netdb.h> #include <sys/un.h> +#include <stdbool.h> #define socket_error() errno #define closesocket(s) close(s) @@ -35,7 +36,7 @@ int inet_aton(const char *cp, struct in_addr *ia); int qemu_socket(int domain, int type, int protocol); int qemu_accept(int s, struct sockaddr *addr, socklen_t *addrlen); void socket_set_nonblock(int fd); -int send_all(int fd, const void *buf, int len1); +int send_all(int fd, const void *buf, int len1, bool nonblock); /* New, ipv6-ready socket helper functions, see qemu-sockets.c */ int inet_listen_opts(QemuOpts *opts, int port_offset); -- 1.6.2.5 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [Qemu-devel] [PATCH v3 3/4] virtio-console: Factor out common init between console and generic ports 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Amit Shah @ 2010-04-15 8:16 ` Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 4/4] virtio-console: Throttle virtio-serial-bus if we can't consume any more guest data Amit Shah 2010-04-20 11:32 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Gerd Hoffmann 1 sibling, 1 reply; 12+ messages in thread From: Amit Shah @ 2010-04-15 8:16 UTC (permalink / raw) To: qemu list; +Cc: Amit Shah, Gerd Hoffmann, paul The initialisation for generic ports and console ports is similar. Factor out the parts that are the same in a different function that can be called from each of the initfns. Signed-off-by: Amit Shah <amit.shah@redhat.com> --- hw/virtio-console.c | 31 ++++++++++++++----------------- 1 files changed, 14 insertions(+), 17 deletions(-) diff --git a/hw/virtio-console.c b/hw/virtio-console.c index 2bccdd0..1552f47 100644 --- a/hw/virtio-console.c +++ b/hw/virtio-console.c @@ -58,24 +58,28 @@ static void chr_event(void *opaque, int event) } } -/* Virtio Console Ports */ -static int virtconsole_initfn(VirtIOSerialDevice *dev) +static int generic_port_init(VirtConsole *vcon, VirtIOSerialDevice *dev) { - VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); - VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); - - port->info = dev->info; - - port->is_console = true; + vcon->port.info = dev->info; if (vcon->chr) { qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, NULL, chr_event, vcon); - port->info->have_data = flush_buf; + vcon->port.info->have_data = flush_buf; } return 0; } +/* Virtio Console Ports */ +static int virtconsole_initfn(VirtIOSerialDevice *dev) +{ + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + + port->is_console = true; + return generic_port_init(vcon, dev); +} + static int virtconsole_exitfn(VirtIOSerialDevice *dev) { VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); @@ -115,14 +119,7 @@ static int virtserialport_initfn(VirtIOSerialDevice *dev) VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); - port->info = dev->info; - - if (vcon->chr) { - qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, - NULL, chr_event, vcon); - port->info->have_data = flush_buf; - } - return 0; + return generic_port_init(vcon, dev); } static VirtIOSerialPortInfo virtserialport_info = { -- 1.6.2.5 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* [Qemu-devel] [PATCH v3 4/4] virtio-console: Throttle virtio-serial-bus if we can't consume any more guest data 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 3/4] virtio-console: Factor out common init between console and generic ports Amit Shah @ 2010-04-15 8:16 ` Amit Shah 0 siblings, 0 replies; 12+ messages in thread From: Amit Shah @ 2010-04-15 8:16 UTC (permalink / raw) To: qemu list; +Cc: Amit Shah, Gerd Hoffmann, paul If the char device we're connected to is overwhelmed with data and it can't accept any more, signal to the virtio-serial-bus to stop sending us more data till we tell otherwise. If the current buffer being processed hasn't been completely written out to the char device, we have to keep it around and re-try sending it since the virtio-serial-bus code assumes we consume the entire buffer. Allow the chardev backends to return -EAGAIN; we're ready with a callback handler that will flush the remainder of the buffer. Also register with savevm so that we save/restore such a buffer across migration. Signed-off-by: Amit Shah <amit.shah@redhat.com> --- hw/virtio-console.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 123 insertions(+), 4 deletions(-) diff --git a/hw/virtio-console.c b/hw/virtio-console.c index 1552f47..1f8e695 100644 --- a/hw/virtio-console.c +++ b/hw/virtio-console.c @@ -13,18 +13,92 @@ #include "qemu-char.h" #include "virtio-serial.h" +typedef struct Buffer { + uint8_t *buf; + size_t rem_len; + size_t offset; +} Buffer; + typedef struct VirtConsole { VirtIOSerialPort port; CharDriverState *chr; + Buffer *unflushed_buf; } VirtConsole; +static void add_unflushed_buf(VirtConsole *vcon, const uint8_t *buf, size_t len) +{ + vcon->unflushed_buf = qemu_malloc(sizeof(Buffer)); + vcon->unflushed_buf->buf = qemu_malloc(len); + + memcpy(vcon->unflushed_buf->buf, buf, len); + vcon->unflushed_buf->rem_len = len; + vcon->unflushed_buf->offset = 0; +} + +static void free_unflushed_buf(VirtConsole *vcon) +{ + if (vcon->unflushed_buf) { + qemu_free(vcon->unflushed_buf->buf); + qemu_free(vcon->unflushed_buf); + vcon->unflushed_buf = NULL; + } +} + +static int buffered_write_to_chardev(VirtConsole *vcon, const uint8_t *buf, + size_t len) +{ + size_t written; + ssize_t ret; + + written = 0; + do { + ret = qemu_chr_write(vcon->chr, buf + written, len - written); + if (ret < 0) { + if (vcon->unflushed_buf) { + vcon->unflushed_buf->offset += written; + vcon->unflushed_buf->rem_len -= written; + } else { + virtio_serial_throttle_port(&vcon->port, true); + add_unflushed_buf(vcon, buf + written, len - written); + } + + return -EAGAIN; + } + + written += ret; + } while (written != len); + + return 0; +} + +/* Callback function called when the chardev can accept more data */ +static void chr_write_unblocked(void *opaque) +{ + VirtConsole *vcon = opaque; + + if (vcon->unflushed_buf) { + int ret; + + ret = buffered_write_to_chardev(vcon, vcon->unflushed_buf->buf + + vcon->unflushed_buf->offset, + vcon->unflushed_buf->rem_len); + if (ret < 0) { + return; + } + free_unflushed_buf(vcon); + } + virtio_serial_throttle_port(&vcon->port, false); +} /* Callback function that's called when the guest sends us data */ static void flush_buf(VirtIOSerialPort *port, const uint8_t *buf, size_t len) { VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); - qemu_chr_write(vcon->chr, buf, len); + /* If a previous write was incomplete, we should've been throttled. */ + assert(!vcon->unflushed_buf); + + buffered_write_to_chardev(vcon, buf, len); } /* Readiness of the guest to accept data on a port */ @@ -48,25 +122,69 @@ static void chr_event(void *opaque, int event) VirtConsole *vcon = opaque; switch (event) { - case CHR_EVENT_OPENED: { + case CHR_EVENT_OPENED: virtio_serial_open(&vcon->port); break; - } + case CHR_EVENT_CLOSED: + if (vcon->unflushed_buf) { + free_unflushed_buf(vcon); + } virtio_serial_close(&vcon->port); break; } } +static void virtio_console_port_save(QEMUFile *f, void *opaque) +{ + VirtConsole *vcon = opaque; + uint32_t have_buffer; + + have_buffer = vcon->unflushed_buf ? true : false; + + qemu_put_be32s(f, &have_buffer); + if (have_buffer) { + qemu_put_be64s(f, &vcon->unflushed_buf->rem_len); + qemu_put_buffer(f, vcon->unflushed_buf->buf + + vcon->unflushed_buf->offset, + vcon->unflushed_buf->rem_len); + } +} + +static int virtio_console_port_load(QEMUFile *f, void *opaque, int version_id) +{ + VirtConsole *vcon = opaque; + uint32_t have_buffer; + + if (version_id > 1) { + return -EINVAL; + } + + qemu_get_be32s(f, &have_buffer); + if (have_buffer) { + vcon->unflushed_buf = qemu_mallocz(sizeof(Buffer)); + + qemu_get_be64s(f, &vcon->unflushed_buf->rem_len); + vcon->unflushed_buf->buf = qemu_malloc(vcon->unflushed_buf->rem_len); + vcon->unflushed_buf->offset = 0; + + qemu_get_buffer(f, vcon->unflushed_buf->buf, + vcon->unflushed_buf->rem_len); + } + return 0; +} + static int generic_port_init(VirtConsole *vcon, VirtIOSerialDevice *dev) { vcon->port.info = dev->info; if (vcon->chr) { qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, - NULL, chr_event, vcon); + chr_write_unblocked, chr_event, vcon); vcon->port.info->have_data = flush_buf; } + register_savevm("virtio-console-ports", -1, 1, virtio_console_port_save, + virtio_console_port_load, vcon); return 0; } @@ -88,6 +206,7 @@ static int virtconsole_exitfn(VirtIOSerialDevice *dev) if (vcon->chr) { port->info->have_data = NULL; qemu_chr_close(vcon->chr); + free_unflushed_buf(vcon); } return 0; -- 1.6.2.5 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 3/4] virtio-console: Factor out common init between console and generic ports Amit Shah @ 2010-04-20 11:32 ` Gerd Hoffmann 2010-04-20 11:44 ` Amit Shah 1 sibling, 1 reply; 12+ messages in thread From: Gerd Hoffmann @ 2010-04-20 11:32 UTC (permalink / raw) To: Amit Shah; +Cc: qemu list, paul > static void tcp_chr_connect(void *opaque) > { > + chr->write_blocked = false; > s->connected = 1; > qemu_set_fd_handler2(s->fd, tcp_chr_read_poll, > - tcp_chr_read, NULL, chr); > + tcp_chr_read, tcp_chr_write_unblocked, chr); This is wrong. You want register tcp_chr_write_unblocked only for the chr->write_blocked == true (i.e. output buffers are full) case. Otherwise qemu will burn cpu calling tcp_chr_write_unblocked. Yes, you'll have to call qemu_set_fd_handler2 each time write_blocked changes state. Also implementing the whole logic at the individual chardev drivers level feels somewhat wrong as it will identical for most (all?) unix chardev drivers. cheers, Gerd ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN 2010-04-20 11:32 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Gerd Hoffmann @ 2010-04-20 11:44 ` Amit Shah 2010-04-20 12:28 ` Paul Brook 0 siblings, 1 reply; 12+ messages in thread From: Amit Shah @ 2010-04-20 11:44 UTC (permalink / raw) To: Gerd Hoffmann; +Cc: qemu list, paul On (Tue) Apr 20 2010 [13:32:18], Gerd Hoffmann wrote: >> static void tcp_chr_connect(void *opaque) >> { > >> + chr->write_blocked = false; >> s->connected = 1; >> qemu_set_fd_handler2(s->fd, tcp_chr_read_poll, >> - tcp_chr_read, NULL, chr); >> + tcp_chr_read, tcp_chr_write_unblocked, chr); > > This is wrong. You want register tcp_chr_write_unblocked only for the > chr->write_blocked == true (i.e. output buffers are full) case. > Otherwise qemu will burn cpu calling tcp_chr_write_unblocked. > > Yes, you'll have to call qemu_set_fd_handler2 each time write_blocked > changes state. Agreed. That's coming in the next round when I make it generic enough for all the backends to call the common code. > Also implementing the whole logic at the individual chardev drivers > level feels somewhat wrong as it will identical for most (all?) unix > chardev drivers. Yes, both these come together, I guess. I sent out this series as a "feeler" to see if the approach was acceptable. Paul didn't reply to my reply addressing his concern, so I take that as he's OK with the approach as well :-) Amit ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN 2010-04-20 11:44 ` Amit Shah @ 2010-04-20 12:28 ` Paul Brook 2010-04-20 12:39 ` Amit Shah 2010-04-20 18:59 ` Gerd Hoffmann 0 siblings, 2 replies; 12+ messages in thread From: Paul Brook @ 2010-04-20 12:28 UTC (permalink / raw) To: Amit Shah; +Cc: Gerd Hoffmann, qemu list > I sent out this series as a "feeler" to see if the approach was > acceptable. > > Paul didn't reply to my reply addressing his concern, so I take that as > he's OK with the approach as well :-) I'd probably exposed this as an asyncronous write rather than nonblocking operation. However both have their issues and I guess for character devices your approach makes sense (c.f. block devices where we want concurrent transfers). It would be useful to have a debugging mode where the chardev layer deliberately returns spurious EAGAIN and short writes. Otherwise you've got a lot of very poorly tested device fallback code. I have low confidence in getting this right first time :-) Paul ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN 2010-04-20 12:28 ` Paul Brook @ 2010-04-20 12:39 ` Amit Shah 2010-04-20 18:59 ` Gerd Hoffmann 1 sibling, 0 replies; 12+ messages in thread From: Amit Shah @ 2010-04-20 12:39 UTC (permalink / raw) To: Paul Brook; +Cc: Gerd Hoffmann, qemu list On (Tue) Apr 20 2010 [13:28:48], Paul Brook wrote: > > It would be useful to have a debugging mode where the chardev layer > deliberately returns spurious EAGAIN and short writes. Otherwise you've got a > lot of very poorly tested device fallback code. I have low confidence in > getting this right first time :-) Ah, hm, I'll see how I can do that. BTW I test this a couple of ways: 1. make the guest write till the host blocks because the chardev can't accept any more data. I ascertain this during development using some fprintfs. Any more data that gets written fills the guest's vq and then the write() in the guest starts blocking. Once this happens, my testcase succeeds. 2. Send a huge file from the guest to the host and compare the sha1sums at the end of the file transfer. This always results in the chardev returning -eagain a few times (confirm during testing via fprintfs again). The testcase passes if the sha1sums match. The test suite is at http://fedorapeople.org/gitweb?p=amitshah/public_git/test-virtserial.git Amit ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN 2010-04-20 12:28 ` Paul Brook 2010-04-20 12:39 ` Amit Shah @ 2010-04-20 18:59 ` Gerd Hoffmann 1 sibling, 0 replies; 12+ messages in thread From: Gerd Hoffmann @ 2010-04-20 18:59 UTC (permalink / raw) To: Paul Brook; +Cc: Amit Shah, qemu list On 04/20/10 14:28, Paul Brook wrote: >> I sent out this series as a "feeler" to see if the approach was >> acceptable. >> >> Paul didn't reply to my reply addressing his concern, so I take that as >> he's OK with the approach as well :-) > > I'd probably exposed this as an asyncronous write rather than nonblocking > operation. However both have their issues and I guess for character devices > your approach makes sense (c.f. block devices where we want concurrent > transfers). For chardevs async operation introduces ordering issues, I think supporting non-blocking writes is more useful here. > It would be useful to have a debugging mode where the chardev layer > deliberately returns spurious EAGAIN and short writes. Otherwise you've got a > lot of very poorly tested device fallback code. I have low confidence in > getting this right first time :-) It might be a good idea to have a different interface for it, i.e. qemu_chr_write() keeps current behavior and we'll add a new qemu_chr_write_nonblocking() for users which want (and can handle) the non-blocking behavior with short writes and -EAGAIN return values. We should also clearly define what qemu_chr_write_nonblocking() should do in case the underlying chardev backend doesn't support nonblocking operation. Option one is to fail and expect the caller handle the situation. Option two is fallback to blocking mode. I'd tend to pick option two, fallback to blocking mode is what the caller most likely will do anyway. cheers, Gerd ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control 2010-04-15 8:16 [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 1/4] char: Let writers know how much data was written in case of errors Amit Shah @ 2010-04-15 12:04 ` Paul Brook 2010-04-15 12:58 ` Amit Shah 1 sibling, 1 reply; 12+ messages in thread From: Paul Brook @ 2010-04-15 12:04 UTC (permalink / raw) To: qemu-devel; +Cc: Amit Shah, Gerd Hoffmann > This series lets interested callers ask for an -EAGAIN return from the > chardev backends (only unix and tcp sockets as of now) to implement > their own flow control. As mentioned previously, I think this is a bad idea. The device has no useful way of determining when to transmit the rest of the data. What you really want is an asynchronous transmit API. Paul ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control 2010-04-15 12:04 ` [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control Paul Brook @ 2010-04-15 12:58 ` Amit Shah 0 siblings, 0 replies; 12+ messages in thread From: Amit Shah @ 2010-04-15 12:58 UTC (permalink / raw) To: Paul Brook; +Cc: qemu-devel, Gerd Hoffmann On (Thu) Apr 15 2010 [13:04:40], Paul Brook wrote: > > This series lets interested callers ask for an -EAGAIN return from the > > chardev backends (only unix and tcp sockets as of now) to implement > > their own flow control. > > As mentioned previously, I think this is a bad idea. The device has no useful > way of determining when to transmit the rest of the data. The device gets a callback which indicates the chardev is ready to accept more data. Amit ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2010-04-20 18:59 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2010-04-15 8:16 [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 1/4] char: Let writers know how much data was written in case of errors Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 3/4] virtio-console: Factor out common init between console and generic ports Amit Shah 2010-04-15 8:16 ` [Qemu-devel] [PATCH v3 4/4] virtio-console: Throttle virtio-serial-bus if we can't consume any more guest data Amit Shah 2010-04-20 11:32 ` [Qemu-devel] [PATCH v3 2/4] char: Add ability to provide a callback when write won't return -EAGAIN Gerd Hoffmann 2010-04-20 11:44 ` Amit Shah 2010-04-20 12:28 ` Paul Brook 2010-04-20 12:39 ` Amit Shah 2010-04-20 18:59 ` Gerd Hoffmann 2010-04-15 12:04 ` [Qemu-devel] [PATCH v3 0/4] char: write callback, virtio-console: flow control Paul Brook 2010-04-15 12:58 ` Amit Shah
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).