diff --git a/server/Makefile.am b/server/Makefile.am index 47b3c10b..e7b49775 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -43,6 +43,7 @@ libspice_server_la_LIBADD = \ libspice_server_la_SOURCES = \ agent-msg-filter.c \ agent-msg-filter.h \ + char_device.c \ char_device.h \ demarshallers.h \ glz_encoder.c \ diff --git a/server/char_device.c b/server/char_device.c new file mode 100644 index 00000000..f4939566 --- /dev/null +++ b/server/char_device.c @@ -0,0 +1,752 @@ +/* spice-server char device flow control code + + Copyright (C) 2012 Red Hat, Inc. + + Red Hat Authors: + Yonit Halperin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ + + +#include "char_device.h" +#include "red_channel.h" +#include "reds.h" + +#define CHAR_DEVICE_WRITE_TO_TIMEOUT 100 +#define SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT 30000 + +typedef struct SpiceCharDeviceClientState SpiceCharDeviceClientState; +struct SpiceCharDeviceClientState { + RingItem link; + SpiceCharDeviceState *dev; + RedClient *client; + int do_flow_control; + uint64_t num_client_tokens; + uint64_t num_client_tokens_free; /* client messages that were consumed by the device */ + uint64_t num_send_tokens; /* send to client */ + SpiceTimer *wait_for_tokens_timer; + int wait_for_tokens_started; + Ring send_queue; + uint32_t send_queue_size; + uint32_t max_send_queue_size; +}; + +enum { + WRITE_BUFFER_ORIGIN_NONE, + WRITE_BUFFER_ORIGIN_CLIENT, + WRITE_BUFFER_ORIGIN_SERVER, +}; + +/* Holding references for avoiding access violation if the char device was + * destroyed during a callback */ +static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev); +static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev); + +static void spice_char_dev_write_retry(void *opaque); + +typedef struct SpiceCharDeviceMsgToClientItem { + RingItem link; + SpiceCharDeviceMsgToClient *msg; +} SpiceCharDeviceMsgToClientItem; + +static void write_buffers_queue_free(Ring *write_queue) +{ + while (!ring_is_empty(write_queue)) { + RingItem *item = ring_get_tail(write_queue); + SpiceCharDeviceWriteBuffer *buf; + + ring_remove(item); + buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + free(buf->buf); + free(buf); + } +} + +static void spice_char_device_write_buffer_pool_add(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *buf) +{ + buf->buf_used = 0; + buf->origin = WRITE_BUFFER_ORIGIN_NONE; + buf->client = NULL; + ring_add(&dev->write_bufs_pool, &buf->link); +} + +static void spice_char_device_client_send_queue_free(SpiceCharDeviceState *dev, + SpiceCharDeviceClientState *dev_client) +{ + spice_debug("send_queue_empty %d", ring_is_empty(&dev_client->send_queue)); + while (!ring_is_empty(&dev_client->send_queue)) { + RingItem *item = ring_get_tail(&dev_client->send_queue); + SpiceCharDeviceMsgToClientItem *msg_item = SPICE_CONTAINEROF(item, + SpiceCharDeviceMsgToClientItem, + link); + + ring_remove(item); + dev->cbs.unref_msg_to_client(msg_item->msg, dev->opaque); + free(msg_item); + } + dev_client->num_send_tokens += dev_client->send_queue_size; + dev_client->send_queue_size = 0; +} + +static void spice_char_device_client_free(SpiceCharDeviceState *dev, + SpiceCharDeviceClientState *dev_client) +{ + RingItem *item, *next; + + if (dev_client->wait_for_tokens_timer) { + core->timer_remove(dev_client->wait_for_tokens_timer); + } + + spice_char_device_client_send_queue_free(dev, dev_client); + + /* remove write buffers that are associated with the client */ + spice_debug("write_queue_is_empty %d", ring_is_empty(&dev->write_queue) && !dev->cur_write_buf); + RING_FOREACH_SAFE(item, next, &dev->write_queue) { + SpiceCharDeviceWriteBuffer *write_buf; + + write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && + write_buf->client == dev_client->client) { + ring_remove(item); + spice_char_device_write_buffer_pool_add(dev, write_buf); + } + } + + if (dev->cur_write_buf && dev->cur_write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && + dev->cur_write_buf->client == dev_client->client) { + dev->cur_write_buf->origin = WRITE_BUFFER_ORIGIN_NONE; + dev->cur_write_buf->client = NULL; + } + + dev->num_clients--; + ring_remove(&dev_client->link); + free(dev_client); +} + +static void spice_char_device_handle_client_overflow(SpiceCharDeviceClientState *dev_client) +{ + SpiceCharDeviceState *dev = dev_client->dev; + spice_printerr("dev %p client %p ", dev, dev_client); + dev->cbs.remove_client(dev_client->client, dev->opaque); +} + +static SpiceCharDeviceClientState *spice_char_device_client_find(SpiceCharDeviceState *dev, + RedClient *client) +{ + RingItem *item; + + RING_FOREACH(item, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + if (dev_client->client == client) { + return dev_client; + } + } + return NULL; +} + +/*************************** + * Reading from the device * + **************************/ + +static void device_client_wait_for_tokens_timeout(void *opaque) +{ + SpiceCharDeviceClientState *dev_client = opaque; + + spice_char_device_handle_client_overflow(dev_client); +} + +static int spice_char_device_can_send_to_client(SpiceCharDeviceClientState *dev_client) +{ + return !dev_client->do_flow_control || dev_client->num_send_tokens; +} + +static uint64_t spice_char_device_max_send_tokens(SpiceCharDeviceState *dev) +{ + RingItem *item; + uint64_t max = 0; + + RING_FOREACH(item, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + + if (!dev_client->do_flow_control) { + max = ~0; + break; + } + + if (dev_client->num_send_tokens > max) { + max = dev_client->num_send_tokens; + } + } + return max; +} + +static void spice_char_device_add_msg_to_client_queue(SpiceCharDeviceClientState *dev_client, + SpiceCharDeviceMsgToClient *msg) +{ + SpiceCharDeviceState *dev = dev_client->dev; + SpiceCharDeviceMsgToClientItem *msg_item; + + if (dev_client->send_queue_size >= dev_client->max_send_queue_size) { + spice_char_device_handle_client_overflow(dev_client); + return; + } + + msg_item = spice_new0(SpiceCharDeviceMsgToClientItem, 1); + msg_item->msg = dev->cbs.ref_msg_to_client(msg, dev->opaque); + ring_add(&dev_client->send_queue, &msg_item->link); + dev_client->send_queue_size++; + if (!dev_client->wait_for_tokens_started) { + core->timer_start(dev_client->wait_for_tokens_timer, + SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT); + dev_client->wait_for_tokens_started = TRUE; + } +} + +static void spice_char_device_send_msg_to_clients(SpiceCharDeviceState *dev, + SpiceCharDeviceMsgToClient *msg) +{ + RingItem *item, *next; + + RING_FOREACH_SAFE(item, next, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + if (spice_char_device_can_send_to_client(dev_client)) { + dev_client->num_send_tokens--; + spice_assert(ring_is_empty(&dev_client->send_queue)); + dev->cbs.send_msg_to_client(msg, dev_client->client, dev->opaque); + + /* don't refer to dev_client anymore, it may have been released */ + } else { + spice_char_device_add_msg_to_client_queue(dev_client, msg); + } + } +} + +static int spice_char_device_read_from_device(SpiceCharDeviceState *dev) +{ + uint64_t max_send_tokens; + int did_read = FALSE; + + if (!dev->running) { + return FALSE; + } + + /* There are 2 scenarios where we can get called recursively: + * 1) spice-vmc vmc_read triggering flush of throttled data, recalling wakeup + * (virtio) + * 2) in case of sending messages to the client, and unreferencing the + * msg, we trigger another read. + */ + if (dev->during_read_from_device++ > 0) { + return FALSE; + } + + max_send_tokens = spice_char_device_max_send_tokens(dev); + spice_char_device_state_ref(dev); + /* + * Reading from the device only in case at least one of the clients have a free token. + * All messages will be discarded if no client is attached to the device + */ + while ((max_send_tokens || ring_is_empty(&dev->clients)) && dev->running) { + SpiceCharDeviceMsgToClient *msg; + + msg = dev->cbs.read_one_msg_from_device(dev->sin, dev->opaque); + if (!msg) { + if (dev->during_read_from_device > 1) { + dev->during_read_from_device = 1; + continue; /* a wakeup might have been called during the read - + make sure it doesn't get lost */ + } + break; + } + did_read = TRUE; + spice_char_device_send_msg_to_clients(dev, msg); + dev->cbs.unref_msg_to_client(msg, dev->opaque); + max_send_tokens--; + } + dev->during_read_from_device = 0; + spice_char_device_state_unref(dev); + return did_read; +} + +static void spice_char_device_client_send_queue_push(SpiceCharDeviceClientState *dev_client) +{ + RingItem *item; + while ((item = ring_get_tail(&dev_client->send_queue)) && + spice_char_device_can_send_to_client(dev_client)) { + SpiceCharDeviceMsgToClientItem *msg_item; + + msg_item = SPICE_CONTAINEROF(item, SpiceCharDeviceMsgToClientItem, link); + ring_remove(item); + + dev_client->num_send_tokens--; + dev_client->dev->cbs.send_msg_to_client(msg_item->msg, + dev_client->client, + dev_client->dev->opaque); + dev_client->dev->cbs.unref_msg_to_client(msg_item->msg, dev_client->dev->opaque); + dev_client->send_queue_size--; + free(msg_item); + } +} + +static void spice_char_device_send_to_client_tokens_absorb(SpiceCharDeviceClientState *dev_client, + uint32_t tokens) +{ + dev_client->num_send_tokens += tokens; + + if (dev_client->send_queue_size) { + spice_assert(dev_client->num_send_tokens == tokens); + spice_char_device_client_send_queue_push(dev_client); + } + + if (spice_char_device_can_send_to_client(dev_client)) { + core->timer_cancel(dev_client->wait_for_tokens_timer); + dev_client->wait_for_tokens_started = FALSE; + spice_char_device_read_from_device(dev_client->dev); + } else if (dev_client->send_queue_size) { + core->timer_start(dev_client->wait_for_tokens_timer, + SPICE_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT); + dev_client->wait_for_tokens_started = TRUE; + } +} + +void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens) +{ + SpiceCharDeviceClientState *dev_client; + + dev_client = spice_char_device_client_find(dev, client); + + if (!dev_client) { + spice_error("client wasn't found dev %p client %p", dev, client); + return; + } + spice_char_device_send_to_client_tokens_absorb(dev_client, tokens); +} + +void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens) +{ + SpiceCharDeviceClientState *dev_client; + + dev_client = spice_char_device_client_find(dev, client); + + if (!dev_client) { + spice_error("client wasn't found dev %p client %p", dev, client); + return; + } + + dev_client->num_send_tokens = 0; + spice_char_device_send_to_client_tokens_absorb(dev_client, tokens); +} + +/************************** + * Writing to the device * +***************************/ + +static void spice_char_device_client_token_add(SpiceCharDeviceState *dev, + SpiceCharDeviceClientState *dev_client) +{ + if (!dev_client->do_flow_control) { + return; + } + if (++dev_client->num_client_tokens_free == dev->client_tokens_interval) { + dev_client->num_client_tokens += dev->client_tokens_interval; + dev_client->num_client_tokens_free = 0; + dev->cbs.send_tokens_to_client(dev_client->client, + dev->client_tokens_interval, + dev->opaque); + } +} + +static int spice_char_device_write_to_device(SpiceCharDeviceState *dev) +{ + SpiceCharDeviceInterface *sif; + int total = 0; + int n; + + if (!dev->running) { + return 0; + } + + spice_char_device_state_ref(dev); + core->timer_cancel(dev->write_to_dev_timer); + + sif = SPICE_CONTAINEROF(dev->sin->base.sif, SpiceCharDeviceInterface, base); + while (1) { + uint32_t write_len; + + if (!dev->cur_write_buf) { + RingItem *item = ring_get_tail(&dev->write_queue); + if (!item) { + break; + } + dev->cur_write_buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + dev->cur_write_buf_pos = dev->cur_write_buf->buf; + ring_remove(item); + } + + write_len = dev->cur_write_buf->buf + dev->cur_write_buf->buf_used - + dev->cur_write_buf_pos; + n = sif->write(dev->sin, dev->cur_write_buf_pos, write_len); + if (!dev->running) { + break; + } + if (n <= 0) { + break; + } + total += n; + write_len -= n; + if (!write_len) { + SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf; + dev->cur_write_buf = NULL; + spice_char_device_write_buffer_release(dev, release_buf); + continue; + } + dev->cur_write_buf_pos += n; + } + /* retry writing as long as the write queue is not empty */ + if (dev->cur_write_buf) { + core->timer_start(dev->write_to_dev_timer, CHAR_DEVICE_WRITE_TO_TIMEOUT); + } else { + spice_assert(ring_is_empty(&dev->write_queue)); + } + spice_char_device_state_unref(dev); + return total; +} + +static void spice_char_dev_write_retry(void *opaque) +{ + SpiceCharDeviceState *dev = opaque; + + core->timer_cancel(dev->write_to_dev_timer); + spice_char_device_write_to_device(dev); +} + +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev, + RedClient *client, int size) +{ + RingItem *item; + SpiceCharDeviceWriteBuffer *ret; + + if (!client && !dev->num_self_tokens) { + spice_printerr("internal buf is not available"); + return NULL; + } + + if ((item = ring_get_tail(&dev->write_bufs_pool))) { + ret = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + ring_remove(item); + } else { + ret = spice_new0(SpiceCharDeviceWriteBuffer, 1); + } + + spice_assert(!ret->buf_used); + + if (ret->buf_size < size) { + ret->buf = spice_realloc(ret->buf, size); + ret->buf_size = size; + } + + if (client) { + SpiceCharDeviceClientState *dev_client = spice_char_device_client_find(dev, client); + if (dev_client) { + if (dev_client->do_flow_control && !dev_client->num_client_tokens) { + spice_printerr("token violation: dev %p client %p", dev, client); + spice_char_device_handle_client_overflow(dev_client); + goto error; + } + ret->origin = WRITE_BUFFER_ORIGIN_CLIENT; + ret->client = client; + if (dev_client->do_flow_control) { + dev_client->num_client_tokens--; + } + } else { + /* it is possible that the client was removed due to send tokens underflow, but + * the caller still receive messages from the client */ + spice_printerr("client not found: dev %p client %p", dev, client); + goto error; + } + } else { + ret->origin = WRITE_BUFFER_ORIGIN_SERVER; + dev->num_self_tokens--; + } + + return ret; +error: + ring_add(&dev->write_bufs_pool, &ret->link); + return NULL; +} + +void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf) +{ + spice_assert(dev); + /* caller shouldn't add buffers for client that was removed */ + if (write_buf->origin == WRITE_BUFFER_ORIGIN_CLIENT && + !spice_char_device_client_find(dev, write_buf->client)) { + spice_printerr("client not found: dev %p client %p", dev, write_buf->client); + spice_char_device_write_buffer_pool_add(dev, write_buf); + return; + } + + ring_add(&dev->write_queue, &write_buf->link); + spice_char_device_write_to_device(dev); +} + +void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf) +{ + int buf_origin = write_buf->origin; + RedClient *client = write_buf->client; + + spice_assert(!ring_item_is_linked(&write_buf->link)); + if (!dev) { + spice_printerr("no device. write buffer is freed"); + free(write_buf->buf); + free(write_buf); + return; + } + + spice_assert(dev->cur_write_buf != write_buf); + + spice_char_device_write_buffer_pool_add(dev, write_buf); + if (buf_origin == WRITE_BUFFER_ORIGIN_CLIENT) { + SpiceCharDeviceClientState *dev_client; + + spice_assert(client); + dev_client = spice_char_device_client_find(dev, client); + /* when a client is removed, we remove all the buffers that are associated with it */ + spice_assert(dev_client); + spice_char_device_client_token_add(dev, dev_client); + } else if (buf_origin == WRITE_BUFFER_ORIGIN_SERVER) { + dev->num_self_tokens++; + if (dev->cbs.on_free_self_token) { + dev->cbs.on_free_self_token(dev->opaque); + } + } + +} + + +/******************************** + * char_device_state management * + ********************************/ + +SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin, + uint32_t client_tokens_interval, + uint32_t self_tokens, + SpiceCharDeviceCallbacks *cbs, + void *opaque) +{ + SpiceCharDeviceState *char_dev; + + spice_assert(sin); + spice_assert(cbs->read_one_msg_from_device && cbs->ref_msg_to_client && + cbs->unref_msg_to_client && cbs->send_msg_to_client && + cbs->send_tokens_to_client && cbs->remove_client); + + char_dev = spice_new0(SpiceCharDeviceState, 1); + char_dev->sin = sin; + char_dev->cbs = *cbs; + char_dev->opaque = opaque; + char_dev->client_tokens_interval = client_tokens_interval; + char_dev->num_self_tokens = self_tokens; + + ring_init(&char_dev->write_queue); + ring_init(&char_dev->write_bufs_pool); + ring_init(&char_dev->clients); + + char_dev->write_to_dev_timer = core->timer_add(spice_char_dev_write_retry, char_dev); + if (!char_dev->write_to_dev_timer) { + spice_error("failed creating char dev write timer"); + } + char_dev->refs = 1; + sin->st = char_dev; + spice_debug("sin %p dev_state %p", sin, char_dev); + return char_dev; +} + +void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *state, + SpiceCharDeviceInstance *sin) +{ + spice_debug("sin %p dev_state %p", sin, state); + state->sin = sin; + sin->st = state; +} + +void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev) +{ + return dev->opaque; +} + +static void spice_char_device_state_ref(SpiceCharDeviceState *char_dev) +{ + char_dev->refs++; +} + +static void spice_char_device_state_unref(SpiceCharDeviceState *char_dev) +{ + /* The refs field protects the char_dev from being deallocated in + * case spice_char_device_state_destroy has been called + * during a callabck, and we might still access the char_dev afterwards. + * spice_char_device_state_unref is always coupled with a preceding + * spice_char_device_state_ref. Here, refs can turn 0 + * only when spice_char_device_state_destroy is called in between + * the calls to spice_char_device_state_ref and spice_char_device_state_unref.*/ + if (!--char_dev->refs) { + free(char_dev); + } +} + +void spice_char_device_state_destroy(SpiceCharDeviceState *char_dev) +{ + core->timer_remove(char_dev->write_to_dev_timer); + write_buffers_queue_free(&char_dev->write_queue); + write_buffers_queue_free(&char_dev->write_bufs_pool); + + while (!ring_is_empty(&char_dev->clients)) { + RingItem *item = ring_get_tail(&char_dev->clients); + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(item, SpiceCharDeviceClientState, link); + spice_char_device_client_free(char_dev, dev_client); + } + char_dev->running = FALSE; + + spice_char_device_state_unref(char_dev); +} + +void spice_char_device_client_add(SpiceCharDeviceState *dev, + RedClient *client, + int do_flow_control, + uint32_t max_send_queue_size, + uint32_t num_client_tokens, + uint32_t num_send_tokens) +{ + SpiceCharDeviceClientState *dev_client; + + spice_assert(dev); + spice_assert(client); + + spice_debug("dev_state %p client %p", dev, client); + dev_client = spice_new0(SpiceCharDeviceClientState, 1); + dev_client->dev = dev; + dev_client->client = client; + ring_init(&dev_client->send_queue); + dev_client->send_queue_size = 0; + dev_client->max_send_queue_size = max_send_queue_size; + dev_client->do_flow_control = do_flow_control; + if (do_flow_control) { + dev_client->wait_for_tokens_timer = core->timer_add(device_client_wait_for_tokens_timeout, + dev_client); + if (!dev_client->wait_for_tokens_timer) { + spice_error("failed to create wait for tokens timer"); + } + dev_client->num_client_tokens = num_client_tokens; + dev_client->num_send_tokens = num_send_tokens; + } else { + dev_client->num_client_tokens = ~0; + dev_client->num_send_tokens = ~0; + } + ring_add(&dev->clients, &dev_client->link); + dev->num_clients++; + /* Now that we have a client, forward any pending device data */ + spice_char_device_wakeup(dev); +} + +void spice_char_device_client_remove(SpiceCharDeviceState *dev, + RedClient *client) +{ + SpiceCharDeviceClientState *dev_client; + + spice_debug("dev_state %p client %p", dev, client); + dev_client = spice_char_device_client_find(dev, client); + + if (!dev_client) { + spice_error("client wasn't found"); + return; + } + + spice_char_device_client_free(dev, dev_client); +} + +int spice_char_device_client_exists(SpiceCharDeviceState *dev, + RedClient *client) +{ + return (spice_char_device_client_find(dev, client) != NULL); +} + +void spice_char_device_start(SpiceCharDeviceState *dev) +{ + spice_debug("dev_state %p", dev); + dev->running = TRUE; + spice_char_device_state_ref(dev); + while (spice_char_device_write_to_device(dev) || + spice_char_device_read_from_device(dev)); + spice_char_device_state_unref(dev); +} + +void spice_char_device_stop(SpiceCharDeviceState *dev) +{ + spice_debug("dev_state %p", dev); + dev->running = FALSE; + core->timer_cancel(dev->write_to_dev_timer); +} + +void spice_char_device_reset(SpiceCharDeviceState *dev) +{ + RingItem *client_item; + spice_char_device_stop(dev); + + spice_debug("dev_state %p", dev); + while (!ring_is_empty(&dev->write_queue)) { + RingItem *item = ring_get_tail(&dev->write_queue); + SpiceCharDeviceWriteBuffer *buf; + + ring_remove(item); + buf = SPICE_CONTAINEROF(item, SpiceCharDeviceWriteBuffer, link); + /* tracking the tokens */ + spice_char_device_write_buffer_release(dev, buf); + } + if (dev->cur_write_buf) { + SpiceCharDeviceWriteBuffer *release_buf = dev->cur_write_buf; + + dev->cur_write_buf = NULL; + spice_char_device_write_buffer_release(dev, release_buf); + } + + RING_FOREACH(client_item, &dev->clients) { + SpiceCharDeviceClientState *dev_client; + + dev_client = SPICE_CONTAINEROF(client_item, SpiceCharDeviceClientState, link); + spice_char_device_client_send_queue_free(dev, dev_client); + } + dev->sin = NULL; +} + +void spice_char_device_wakeup(SpiceCharDeviceState *dev) +{ + spice_char_device_read_from_device(dev); +} + diff --git a/server/char_device.h b/server/char_device.h index bdb32ae9..db27bece 100644 --- a/server/char_device.h +++ b/server/char_device.h @@ -2,11 +2,212 @@ #define __CHAR_DEVICE_H__ #include "spice.h" +#include "red_channel.h" + +/* + * Shared code for char devices, mainly for flow control. + * + * How to use the api: + * ================== + * device attached: call spice_char_device_state_create + * device detached: call spice_char_device_state_destroy/reset + * + * client connected and assoicated with a device: spice_char_device_client_add + * client disconnected: spice_char_device_client_remove + * + * Writing to the device + * --------------------- + * Write the data into SpiceCharDeviceWriteBuffer: + * call spice_char_device_write_buffer_get in order to get an appropriate buffer. + * call spice_char_device_write_buffer_add in order to push the buffer to the write queue. + * If you choose not to push the buffer to the device, call + * spice_char_device_write_buffer_release + * + * reading from the device + * ----------------------- + * The callback read_one_msg_from_device (see below) should be implemented + * (using sif->read). + * When the device is ready, this callback is called, and is expected to + * return one message which is addressed to the client, or NULL if the read + * hasn't completed. + * + * calls triggered from the device (qemu): + * -------------------------------------- + * spice_char_device_start + * spice_char_device_stop + * spice_char_device_wakeup (for reading from the device) + */ + +/* + * Note about multiple-clients: + * Multiclients are currently not supported in any of the character devices: + * spicevmc does not allow more than one client (and at least for usb, it should stay this way). + * smartcard code is not compatible with more than one reader. + * The server and guest agent code doesn't distinguish messages from different clients. + * In addition, its current flow control code (e.g., tokens handling) is wrong and doesn't + * take into account the different clients. + * + * Nonetheless, the following code introduces some support for multiple-clients: + * We track the number of tokens for all the clients, and we read from the device + * if one of the clients have enough tokens. For the clients that don't have tokens, + * we queue the messages, till they receive tokens, or till a timeout. + * + * TODO: + * At least for the agent, not all the messages from the device will be directed to all + * the clients (e.g., copy from guest to a specific client). Thus, support for + * client-specific-messages should be added. + * In addition, we should have support for clients that are being connected + * in the middle of a message transfer from the agent to the clients. + * + * */ + +/* buffer that is used for writing to the device */ +typedef struct SpiceCharDeviceWriteBuffer { + RingItem link; + int origin; + RedClient *client; /* The client that sent the message to the device. + NULL if the server created the message */ + + uint8_t *buf; + uint32_t buf_size; + uint32_t buf_used; +} SpiceCharDeviceWriteBuffer; + +typedef void SpiceCharDeviceMsgToClient; + +typedef struct SpiceCharDeviceCallbacks { + /* + * Messages that are addressed to the client can be queued in case we have + * multiple clients and some of them don't have enough tokens. + */ + + /* reads from the device till reaching a msg that should be sent to the client, + * or till the reading fails */ + SpiceCharDeviceMsgToClient* (*read_one_msg_from_device)(SpiceCharDeviceInstance *sin, + void *opaque); + SpiceCharDeviceMsgToClient* (*ref_msg_to_client)(SpiceCharDeviceMsgToClient *msg, + void *opaque); + void (*unref_msg_to_client)(SpiceCharDeviceMsgToClient *msg, + void *opaque); + void (*send_msg_to_client)(SpiceCharDeviceMsgToClient *msg, + RedClient *client, + void *opaque); /* after this call, the message is unreferenced */ + + /* The cb is called when a predefined number of write buffers were consumed by the + * device */ + void (*send_tokens_to_client)(RedClient *client, uint32_t tokens, void *opaque); + + /* The cb is called when a server (self) message that was addressed to the device, + * has been completely written to it */ + void (*on_free_self_token)(void *opaque); + + /* This cb is called if it is recommanded that a client will be removed + * due to slow flow or due to some other error. + * The called instance should disconnect the client, or at least the corresponding channel */ + void (*remove_client)(RedClient *client, void *opaque); +} SpiceCharDeviceCallbacks; + +typedef struct SpiceCharDeviceState SpiceCharDeviceState; struct SpiceCharDeviceState { + int running; + uint32_t refs; + + Ring write_queue; + Ring write_bufs_pool; + SpiceCharDeviceWriteBuffer *cur_write_buf; + uint8_t *cur_write_buf_pos; + SpiceTimer *write_to_dev_timer; + uint64_t num_self_tokens; + + Ring clients; + uint32_t num_clients; + + uint64_t client_tokens_interval; /* frequency of returning tokens to the client */ + SpiceCharDeviceInstance *sin; + + int during_read_from_device; + + SpiceCharDeviceCallbacks cbs; + void *opaque; + /* tmp till all spice char devices will employ the new SpiceCharDeviceState + * implementation. Then, SpiceCharDeviceState will be moved to char_device.c and + * this callback will be removed */ void (*wakeup)(SpiceCharDeviceInstance *sin); }; + +SpiceCharDeviceState *spice_char_device_state_create(SpiceCharDeviceInstance *sin, + uint32_t client_tokens_interval, + uint32_t self_tokens, + SpiceCharDeviceCallbacks *cbs, + void *opaque); + +void spice_char_device_state_reset_dev_instance(SpiceCharDeviceState *dev, + SpiceCharDeviceInstance *sin); +void spice_char_device_state_destroy(SpiceCharDeviceState *dev); + +void *spice_char_device_state_opaque_get(SpiceCharDeviceState *dev); + + +/* + * Resets write/read queues, and moves that state to being stopped. + * This routine is a workaround for a bad tokens management in the vdagent + * protocol: + * The client tokens' are set only once, when the main channel is initialized. + * Instead, it would have been more appropriate to reset them upon AGEN_CONNECT. + * The client tokens are tracked as part of the SpiceCharDeviceClientState. Thus, + * in order to be backwartd compatible with the client, we need to track the tokens + * event when the agent is detached. We don't destroy the the char_device state, and + * instead we just reset it. + * In addition, there is a misshandling of AGENT_TOKENS message in spice-gtk: it + * overrides the amount of tokens, instead of adding the given amount. + * + * todo: change AGENT_CONNECT msg to contain tokens count. + */ +void spice_char_device_reset(SpiceCharDeviceState *dev); + +/* max_send_queue_size = how many messages we can read from the device and enqueue for this client, + * when we have tokens for other clients and no tokens for this one */ +void spice_char_device_client_add(SpiceCharDeviceState *dev, + RedClient *client, + int do_flow_control, + uint32_t max_send_queue_size, + uint32_t num_client_tokens, + uint32_t num_send_tokens); + +void spice_char_device_client_remove(SpiceCharDeviceState *dev, + RedClient *client); +int spice_char_device_client_exists(SpiceCharDeviceState *dev, + RedClient *client); + +void spice_char_device_start(SpiceCharDeviceState *dev); +void spice_char_device_stop(SpiceCharDeviceState *dev); + +/** Read from device **/ + +void spice_char_device_wakeup(SpiceCharDeviceState *dev); + +void spice_char_device_send_to_client_tokens_add(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens); + + +void spice_char_device_send_to_client_tokens_set(SpiceCharDeviceState *dev, + RedClient *client, + uint32_t tokens); +/** Write to device **/ + +SpiceCharDeviceWriteBuffer *spice_char_device_write_buffer_get(SpiceCharDeviceState *dev, + RedClient *client, int size); +/* Either add the buffer to the write queue or release it */ +void spice_char_device_write_buffer_add(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf); +void spice_char_device_write_buffer_release(SpiceCharDeviceState *dev, + SpiceCharDeviceWriteBuffer *write_buf); + +/* api for specific char devices */ + void spicevmc_device_connect(SpiceCharDeviceInstance *sin, uint8_t channel_type); void spicevmc_device_disconnect(SpiceCharDeviceInstance *char_device);