| >From 06ad256d2939aea4086428dcb5e5e7d5f86eb335 Mon Sep 17 00:00:00 2001 |
| From: Alon Levy <alevy@redhat.com> |
| Date: Tue, 22 Mar 2011 12:27:59 +0200 |
| Subject: [PATCH 13/17] spice-qemu-char.c: add throttling |
| |
| BZ: 672191 |
| |
| upstream: not submitted (explained below) |
| |
| Adds throttling support to spicevmc chardev. Uses a timer to avoid recursing: |
| 1. spice-server: reds.c: read_from_vdi_port |
| 2. qemu: spice-qemu-char.c: vmc_read |
| 3. chr_write_unblocked |
| (calls virtio_serial_throttle_port(port, false)) |
| 4. qemu: virtio ... |
| 5. qemu: spice-qemu-char.c: spice_chr_write |
| 6. qemu: spice-qemu-char.c: wakeup (calls into spice-server) |
| 7. spice-server: ... |
| 8. qemu: spice-qemu-char.c: vmc_read |
| |
| Instead, in vmc_read if we were throttled and we are just about to return |
| all the bytes we will set a timer to be triggered immediately to call |
| chr_write_unblocked. Then we return after 2 above, and 3 is called from the |
| timer callback. This also means we can later remove some ugly recursion protection |
| from spice-server. |
| |
| The other tricky point in this patch is not returning the leftover chunk twice. |
| When we throttle, by definition we have data that spice server didn't consume. |
| It is being kept by virtio-serial, and by us. The next vmc_read callback needs |
| to not return it, but just do unthrottling. Then virtio will give us the remaining |
| chunk as usual in spice_chr_write, and we will pass it to spice server in the |
| next vmc_read. |
| |
| This patch relies on Amit's series to expose throttling to chardev's, which |
| was not accepted upstream, and will not be accepted upstream until the mainloop |
| is reworked to use glib. |
| |
| spice-qemu-char.c | 39 +++++++++++++++++++++++++++++++++++---- |
| 1 files changed, 35 insertions(+), 4 deletions(-) |
| |
| diff --git a/spice-qemu-char.c b/spice-qemu-char.c |
| index 517f337..91467d5 100644 |
| |
| |
| @@ -1,4 +1,6 @@ |
| #include "config-host.h" |
| +#include "qemu-common.h" |
| +#include "qemu-timer.h" |
| #include "trace.h" |
| #include "ui/qemu-spice.h" |
| #include <spice.h> |
| @@ -25,6 +27,7 @@ typedef struct SpiceCharDriver { |
| uint8_t *datapos; |
| ssize_t bufsize, datalen; |
| uint32_t debug; |
| + QEMUTimer *unblock_timer; |
| } SpiceCharDriver; |
| |
| static int vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len) |
| @@ -51,6 +54,17 @@ static int vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len) |
| return out; |
| } |
| |
| +static void spice_chr_unblock(void *opaque) |
| +{ |
| + SpiceCharDriver *scd = opaque; |
| + |
| + if (scd->chr->chr_write_unblocked == NULL) { |
| + dprintf(scd, 1, "%s: backend doesn't support unthrottling.\n", __func__); |
| + return; |
| + } |
| + scd->chr->chr_write_unblocked(scd->chr->handler_opaque); |
| +} |
| + |
| static int vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len) |
| { |
| SpiceCharDriver *scd = container_of(sin, SpiceCharDriver, sin); |
| @@ -62,9 +76,16 @@ static int vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len) |
| scd->datapos += bytes; |
| scd->datalen -= bytes; |
| assert(scd->datalen >= 0); |
| - if (scd->datalen == 0) { |
| - scd->datapos = 0; |
| - } |
| + } |
| + if (scd->datalen == 0 && scd->chr->write_blocked) { |
| + dprintf(scd, 1, "%s: unthrottling (%d)\n", __func__, bytes); |
| + scd->chr->write_blocked = false; |
| + /* |
| + * set a timer instead of calling scd->chr->chr_write_unblocked directly, |
| + * because that will call back into spice_chr_write (see |
| + * virtio-console.c:chr_write_unblocked), which is unwanted. |
| + */ |
| + qemu_mod_timer(scd->unblock_timer, 0); |
| } |
| trace_spice_vmc_read(bytes, len); |
| return bytes; |
| @@ -107,6 +128,7 @@ static void vmc_unregister_interface(SpiceCharDriver *scd) |
| static int spice_chr_write(CharDriverState *chr, const uint8_t *buf, int len) |
| { |
| SpiceCharDriver *s = chr->opaque; |
| + int read_bytes; |
| |
| dprintf(s, 2, "%s: %d\n", __func__, len); |
| vmc_register_interface(s); |
| @@ -119,7 +141,15 @@ static int spice_chr_write(CharDriverState *chr, const uint8_t *buf, int len) |
| s->datapos = s->buffer; |
| s->datalen = len; |
| spice_server_char_device_wakeup(&s->sin); |
| - return len; |
| + read_bytes = len - s->datalen; |
| + if (read_bytes != len) { |
| + dprintf(s, 1, "%s: throttling: %d < %d (%zd)\n", __func__, |
| + read_bytes, len, s->bufsize); |
| + s->chr->write_blocked = true; |
| + /* We'll get passed in the unconsumed data with the next call */ |
| + s->datalen = 0; |
| + } |
| + return read_bytes; |
| } |
| |
| static void spice_chr_close(struct CharDriverState *chr) |
| @@ -183,6 +213,7 @@ CharDriverState *qemu_chr_open_spice(QemuOpts *opts) |
| chr->opaque = s; |
| chr->chr_write = spice_chr_write; |
| chr->chr_close = spice_chr_close; |
| + s->unblock_timer = qemu_new_timer(vm_clock, spice_chr_unblock, s); |
| |
| qemu_chr_generic_open(chr); |
| |
| -- |
| 1.7.3.2 |
| |