spice/server/char-device.cpp
Frediano Ziglio 66dacecda9 char-device: Add a read method to simplify reading data
Avoid to pass throught SpiceCharDeviceInterface.
When data is read from device the device get marked as active.
This is coherent with what is done in red_char_device_read_from_device.

Signed-off-by: Frediano Ziglio <freddy77@gmail.com>
Acked-by: Julien Ropé <jrope@gmail.com>
2020-06-23 12:49:26 +01:00

961 lines
32 KiB
C++

/* spice-server char device flow control code
Copyright (C) 2012-2015 Red Hat, Inc.
Red Hat Authors:
Yonit Halperin <yhalperi@redhat.com>
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 <http:www.gnu.org/licenses/>.
*/
#include <config.h>
#include <inttypes.h>
#include "char-device.h"
#include "reds.h"
#define CHAR_DEVICE_WRITE_TO_TIMEOUT 100
#define RED_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT 30000
typedef enum {
WRITE_BUFFER_ORIGIN_NONE,
WRITE_BUFFER_ORIGIN_CLIENT,
WRITE_BUFFER_ORIGIN_SERVER,
WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN,
} WriteBufferOrigin;
struct RedCharDeviceWriteBufferPrivate {
RedCharDeviceClientOpaque *client; /* The client that sent the message to the device.
NULL if the server created the message */
WriteBufferOrigin origin;
uint32_t token_price;
uint32_t refs;
};
typedef struct RedCharDeviceClient RedCharDeviceClient;
struct RedCharDeviceClient {
RedCharDevice *dev;
RedCharDeviceClientOpaque *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;
GQueue *send_queue;
uint32_t max_send_queue_size;
};
struct RedCharDevicePrivate {
SPICE_CXX_GLIB_ALLOCATOR
int running;
int active; /* has read/write been performed since the device was started */
int wait_for_migrate_data;
GQueue write_queue;
RedCharDeviceWriteBuffer *cur_write_buf;
uint8_t *cur_write_buf_pos;
SpiceTimer *write_to_dev_timer;
uint64_t num_self_tokens;
GList *clients; /* list of RedCharDeviceClient */
uint64_t client_tokens_interval; /* frequency of returning tokens to the client */
SpiceCharDeviceInstance *sin;
int during_read_from_device;
int during_write_to_device;
SpiceServer *reds;
};
static void red_char_device_write_buffer_unref(RedCharDeviceWriteBuffer *write_buf);
void
RedCharDevice::send_tokens_to_client(RedCharDeviceClientOpaque *client, uint32_t tokens)
{
g_warn_if_reached();
}
static void red_char_device_write_buffer_free(RedCharDeviceWriteBuffer *buf)
{
if (buf) {
g_free(buf->priv);
}
/* NOTE: do not free buf. buf was contained into a larger structure
* which contained both private and public part and was freed above */
}
static void write_buffers_queue_free(GQueue *write_queue)
{
RedCharDeviceWriteBuffer *buf;
while ((buf = (RedCharDeviceWriteBuffer *) g_queue_pop_tail(write_queue)))
red_char_device_write_buffer_free(buf);
}
static void red_char_device_client_free(RedCharDevice *dev,
RedCharDeviceClient *dev_client)
{
GList *l, *next;
red_timer_remove(dev_client->wait_for_tokens_timer);
dev_client->wait_for_tokens_timer = NULL;
g_queue_free_full(dev_client->send_queue, (GDestroyNotify)red_pipe_item_unref);
/* remove write buffers that are associated with the client */
spice_debug("write_queue_is_empty %d", g_queue_is_empty(&dev->priv->write_queue) && !dev->priv->cur_write_buf);
l = g_queue_peek_head_link(&dev->priv->write_queue);
while (l) {
RedCharDeviceWriteBuffer *write_buf = (RedCharDeviceWriteBuffer *) l->data;
next = l->next;
if (write_buf->priv->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
write_buf->priv->client == dev_client->client) {
g_queue_delete_link(&dev->priv->write_queue, l);
red_char_device_write_buffer_unref(write_buf);
}
l = next;
}
if (dev->priv->cur_write_buf && dev->priv->cur_write_buf->priv->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
dev->priv->cur_write_buf->priv->client == dev_client->client) {
dev->priv->cur_write_buf->priv->origin = WRITE_BUFFER_ORIGIN_NONE;
dev->priv->cur_write_buf->priv->client = NULL;
}
dev->priv->clients = g_list_remove(dev->priv->clients, dev_client);
g_free(dev_client);
}
static void red_char_device_handle_client_overflow(RedCharDeviceClient *dev_client)
{
RedCharDevice *dev = dev_client->dev;
dev->remove_client(dev_client->client);
}
static RedCharDeviceClient *red_char_device_client_find(RedCharDevice *dev,
RedCharDeviceClientOpaque *client)
{
RedCharDeviceClient *dev_client;
GLIST_FOREACH(dev->priv->clients, RedCharDeviceClient, dev_client) {
if (dev_client->client == client) {
return dev_client;
}
}
return NULL;
}
/***************************
* Reading from the device *
**************************/
static void device_client_wait_for_tokens_timeout(RedCharDeviceClient *dev_client)
{
red_char_device_handle_client_overflow(dev_client);
}
static int red_char_device_can_send_to_client(RedCharDeviceClient *dev_client)
{
return !dev_client->do_flow_control || dev_client->num_send_tokens;
}
static uint64_t red_char_device_max_send_tokens(RedCharDevice *dev)
{
RedCharDeviceClient *dev_client;
uint64_t max = 0;
GLIST_FOREACH(dev->priv->clients, RedCharDeviceClient, dev_client) {
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 red_char_device_add_msg_to_client_queue(RedCharDeviceClient *dev_client,
RedPipeItem *msg)
{
if (g_queue_get_length(dev_client->send_queue) >= dev_client->max_send_queue_size) {
red_char_device_handle_client_overflow(dev_client);
return;
}
red_pipe_item_ref(msg);
g_queue_push_head(dev_client->send_queue, msg);
if (!dev_client->wait_for_tokens_started) {
red_timer_start(dev_client->wait_for_tokens_timer,
RED_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT);
dev_client->wait_for_tokens_started = TRUE;
}
}
static void red_char_device_send_msg_to_clients(RedCharDevice *dev,
RedPipeItem *msg)
{
RedCharDeviceClient *dev_client;
GLIST_FOREACH(dev->priv->clients, RedCharDeviceClient, dev_client) {
if (red_char_device_can_send_to_client(dev_client)) {
dev_client->num_send_tokens--;
spice_assert(g_queue_is_empty(dev_client->send_queue));
dev->send_msg_to_client(msg, dev_client->client);
/* don't refer to dev_client anymore, it may have been released */
} else {
red_char_device_add_msg_to_client_queue(dev_client, msg);
}
}
}
static bool red_char_device_read_from_device(RedCharDevice *dev)
{
uint64_t max_send_tokens;
int did_read = FALSE;
if (!dev->priv->running || dev->priv->wait_for_migrate_data || !dev->priv->sin) {
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->priv->during_read_from_device++ > 0) {
return FALSE;
}
max_send_tokens = red_char_device_max_send_tokens(dev);
red::shared_ptr<RedCharDevice> hold_dev(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 || (dev->priv->clients == NULL)) && dev->priv->running) {
RedPipeItem *msg;
msg = dev->read_one_msg_from_device(dev->priv->sin);
if (!msg) {
if (dev->priv->during_read_from_device > 1) {
dev->priv->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;
red_char_device_send_msg_to_clients(dev, msg);
red_pipe_item_unref(msg);
max_send_tokens--;
}
dev->priv->during_read_from_device = 0;
if (dev->priv->running) {
dev->priv->active = dev->priv->active || did_read;
}
return did_read;
}
static void red_char_device_client_send_queue_push(RedCharDeviceClient *dev_client)
{
while (!g_queue_is_empty(dev_client->send_queue) &&
red_char_device_can_send_to_client(dev_client)) {
RedPipeItem *msg = (RedPipeItem *) g_queue_pop_tail(dev_client->send_queue);
g_assert(msg != NULL);
dev_client->num_send_tokens--;
dev_client->dev->send_msg_to_client(msg, dev_client->client);
red_pipe_item_unref(msg);
}
}
static void
red_char_device_send_to_client_tokens_absorb(RedCharDevice *dev,
RedCharDeviceClientOpaque *client,
uint32_t tokens,
bool reset)
{
RedCharDeviceClient *dev_client;
dev_client = red_char_device_client_find(dev, client);
if (!dev_client) {
spice_error("client wasn't found dev %p client %p", dev, client);
return;
}
if (reset) {
dev_client->num_send_tokens = 0;
}
dev_client->num_send_tokens += tokens;
if (g_queue_get_length(dev_client->send_queue)) {
spice_assert(dev_client->num_send_tokens == tokens);
red_char_device_client_send_queue_push(dev_client);
}
if (red_char_device_can_send_to_client(dev_client)) {
red_timer_cancel(dev_client->wait_for_tokens_timer);
dev_client->wait_for_tokens_started = FALSE;
red_char_device_read_from_device(dev_client->dev);
} else if (!g_queue_is_empty(dev_client->send_queue)) {
red_timer_start(dev_client->wait_for_tokens_timer,
RED_CHAR_DEVICE_WAIT_TOKENS_TIMEOUT);
dev_client->wait_for_tokens_started = TRUE;
}
}
void RedCharDevice::send_to_client_tokens_add(RedCharDeviceClientOpaque *client,
uint32_t tokens)
{
red_char_device_send_to_client_tokens_absorb(this, client, tokens, false);
}
void RedCharDevice::send_to_client_tokens_set(RedCharDeviceClientOpaque *client,
uint32_t tokens)
{
red_char_device_send_to_client_tokens_absorb(this, client, tokens, true);
}
/**************************
* Writing to the device *
***************************/
static void red_char_device_client_tokens_add(RedCharDevice *dev,
RedCharDeviceClient *dev_client,
uint32_t num_tokens)
{
if (!dev_client->do_flow_control) {
return;
}
if (num_tokens > 1) {
spice_debug("#tokens > 1 (=%u)", num_tokens);
}
dev_client->num_client_tokens_free += num_tokens;
if (dev_client->num_client_tokens_free >= dev->priv->client_tokens_interval) {
uint32_t tokens = dev_client->num_client_tokens_free;
dev_client->num_client_tokens += dev_client->num_client_tokens_free;
dev_client->num_client_tokens_free = 0;
dev->send_tokens_to_client(dev_client->client, tokens);
}
}
int RedCharDevice::write_to_device()
{
SpiceCharDeviceInterface *sif;
int total = 0;
int n;
if (!priv->running || priv->wait_for_migrate_data || !priv->sin) {
return 0;
}
/* protect against recursion with red_char_device_wakeup */
if (priv->during_write_to_device++ > 0) {
return 0;
}
red::shared_ptr<RedCharDevice> hold_dev(this);
if (priv->write_to_dev_timer) {
red_timer_cancel(priv->write_to_dev_timer);
}
sif = spice_char_device_get_interface(priv->sin);
while (priv->running) {
uint32_t write_len;
if (!priv->cur_write_buf) {
priv->cur_write_buf = (RedCharDeviceWriteBuffer *) g_queue_pop_tail(&priv->write_queue);
if (!priv->cur_write_buf)
break;
priv->cur_write_buf_pos = priv->cur_write_buf->buf;
}
write_len = priv->cur_write_buf->buf + priv->cur_write_buf->buf_used -
priv->cur_write_buf_pos;
n = sif->write(priv->sin, priv->cur_write_buf_pos, write_len);
if (n <= 0) {
if (priv->during_write_to_device > 1) {
priv->during_write_to_device = 1;
continue; /* a wakeup might have been called during the write -
make sure it doesn't get lost */
}
break;
}
total += n;
write_len -= n;
if (!write_len) {
write_buffer_release(&priv->cur_write_buf);
continue;
}
priv->cur_write_buf_pos += n;
}
/* retry writing as long as the write queue is not empty */
if (priv->running) {
if (priv->cur_write_buf) {
if (priv->write_to_dev_timer) {
red_timer_start(priv->write_to_dev_timer,
CHAR_DEVICE_WRITE_TO_TIMEOUT);
}
} else {
spice_assert(g_queue_is_empty(&priv->write_queue));
}
priv->active = priv->active || total;
}
priv->during_write_to_device = 0;
return total;
}
void RedCharDevice::write_retry(RedCharDevice *dev)
{
if (dev->priv->write_to_dev_timer) {
red_timer_cancel(dev->priv->write_to_dev_timer);
}
dev->write_to_device();
}
static RedCharDeviceWriteBuffer *
red_char_device_write_buffer_get(RedCharDevice *dev, RedCharDeviceClientOpaque *client, int size,
WriteBufferOrigin origin, int migrated_data_tokens)
{
RedCharDeviceWriteBuffer *ret;
if (origin == WRITE_BUFFER_ORIGIN_SERVER && !dev->priv->num_self_tokens) {
return NULL;
}
struct RedCharDeviceWriteBufferFull {
RedCharDeviceWriteBufferPrivate priv;
RedCharDeviceWriteBuffer buffer;
} *write_buf;
write_buf = (struct RedCharDeviceWriteBufferFull* )
g_malloc(sizeof(struct RedCharDeviceWriteBufferFull) + size);
memset(write_buf, 0, sizeof(*write_buf));
write_buf->priv.refs = 1;
ret = &write_buf->buffer;
ret->buf_size = size;
ret->priv = &write_buf->priv;
spice_assert(!ret->buf_used);
ret->priv->origin = origin;
if (origin == WRITE_BUFFER_ORIGIN_CLIENT) {
spice_assert(client);
RedCharDeviceClient *dev_client = red_char_device_client_find(dev, client);
if (dev_client) {
if (!migrated_data_tokens &&
dev_client->do_flow_control && !dev_client->num_client_tokens) {
g_warning("token violation: dev %p client %p", dev, client);
red_char_device_handle_client_overflow(dev_client);
goto error;
}
ret->priv->client = client;
if (!migrated_data_tokens && 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 */
g_warning("client not found: dev %p client %p", dev, client);
goto error;
}
} else if (origin == WRITE_BUFFER_ORIGIN_SERVER) {
dev->priv->num_self_tokens--;
}
ret->priv->token_price = migrated_data_tokens ? migrated_data_tokens : 1;
ret->priv->refs = 1;
return ret;
error:
red_char_device_write_buffer_free(ret);
return NULL;
}
RedCharDeviceWriteBuffer *RedCharDevice::write_buffer_get_client(RedCharDeviceClientOpaque *client,
int size)
{
spice_assert(client);
return red_char_device_write_buffer_get(this, client, size, WRITE_BUFFER_ORIGIN_CLIENT, 0);
}
RedCharDeviceWriteBuffer *RedCharDevice::write_buffer_get_server(int size,
bool use_token)
{
WriteBufferOrigin origin =
use_token ? WRITE_BUFFER_ORIGIN_SERVER : WRITE_BUFFER_ORIGIN_SERVER_NO_TOKEN;
return red_char_device_write_buffer_get(this, NULL, size, origin, 0);
}
static RedCharDeviceWriteBuffer *red_char_device_write_buffer_ref(RedCharDeviceWriteBuffer *write_buf)
{
spice_assert(write_buf);
write_buf->priv->refs++;
return write_buf;
}
static void red_char_device_write_buffer_unref(RedCharDeviceWriteBuffer *write_buf)
{
spice_assert(write_buf);
write_buf->priv->refs--;
if (write_buf->priv->refs == 0)
red_char_device_write_buffer_free(write_buf);
}
void RedCharDevice::write_buffer_add(RedCharDeviceWriteBuffer *write_buf)
{
/* caller shouldn't add buffers for client that was removed */
if (write_buf->priv->origin == WRITE_BUFFER_ORIGIN_CLIENT &&
!red_char_device_client_find(this, write_buf->priv->client)) {
g_warning("client not found: this %p client %p", this, write_buf->priv->client);
red_char_device_write_buffer_unref(write_buf);
return;
}
g_queue_push_head(&priv->write_queue, write_buf);
write_to_device();
}
void RedCharDevice::write_buffer_release(RedCharDevice *dev,
RedCharDeviceWriteBuffer **p_write_buf)
{
RedCharDeviceWriteBuffer *write_buf = *p_write_buf;
if (!write_buf) {
return;
}
*p_write_buf = NULL;
WriteBufferOrigin buf_origin = write_buf->priv->origin;
uint32_t buf_token_price = write_buf->priv->token_price;
RedCharDeviceClientOpaque *client = write_buf->priv->client;
if (!dev) {
g_warning("no device. write buffer is freed");
red_char_device_write_buffer_free(write_buf);
return;
}
spice_assert(dev->priv->cur_write_buf != write_buf);
red_char_device_write_buffer_unref(write_buf);
if (buf_origin == WRITE_BUFFER_ORIGIN_CLIENT) {
RedCharDeviceClient *dev_client;
spice_assert(client);
dev_client = red_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);
red_char_device_client_tokens_add(dev, dev_client, buf_token_price);
} else if (buf_origin == WRITE_BUFFER_ORIGIN_SERVER) {
dev->priv->num_self_tokens++;
dev->on_free_self_token();
}
}
/********************************
* char_device_state management *
********************************/
void RedCharDevice::reset_dev_instance(SpiceCharDeviceInstance *sin)
{
spice_debug("sin %p, char device %p", sin, this);
priv->sin = sin;
if (sin) {
sin->st = this;
}
if (priv->reds) {
init_device_instance();
}
}
static RedCharDeviceClient *
red_char_device_client_new(RedsState *reds,
RedCharDeviceClientOpaque *client,
int do_flow_control,
uint32_t max_send_queue_size,
uint32_t num_client_tokens,
uint32_t num_send_tokens)
{
RedCharDeviceClient *dev_client;
dev_client = g_new0(RedCharDeviceClient, 1);
dev_client->client = client;
dev_client->send_queue = g_queue_new();
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 =
reds_core_timer_add(reds, 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;
}
return dev_client;
}
bool RedCharDevice::client_add(RedCharDeviceClientOpaque *client,
int do_flow_control,
uint32_t max_send_queue_size,
uint32_t num_client_tokens,
uint32_t num_send_tokens,
int wait_for_migrate_data)
{
RedCharDeviceClient *dev_client;
spice_assert(client);
if (wait_for_migrate_data && (priv->clients != NULL || priv->active)) {
spice_warning("can't restore device %p from migration data. The device "
"has already been active", this);
return FALSE;
}
priv->wait_for_migrate_data = wait_for_migrate_data;
spice_debug("char device %p, client %p", this, client);
dev_client = red_char_device_client_new(priv->reds,
client,
do_flow_control,
max_send_queue_size,
num_client_tokens,
num_send_tokens);
dev_client->dev = this;
priv->clients = g_list_prepend(priv->clients, dev_client);
/* Now that we have a client, forward any pending device data */
wakeup();
return TRUE;
}
void RedCharDevice::client_remove(RedCharDeviceClientOpaque *client)
{
RedCharDeviceClient *dev_client;
spice_debug("char device %p, client %p", this, client);
dev_client = red_char_device_client_find(this, client);
if (!dev_client) {
spice_error("client wasn't found");
return;
}
red_char_device_client_free(this, dev_client);
if (priv->wait_for_migrate_data) {
spice_assert(priv->clients == NULL);
priv->wait_for_migrate_data = FALSE;
red_char_device_read_from_device(this);
}
}
int RedCharDevice::client_exists(RedCharDeviceClientOpaque *client)
{
return (red_char_device_client_find(this, client) != NULL);
}
void RedCharDevice::start()
{
spice_debug("char device %p", this);
priv->running = TRUE;
red::shared_ptr<RedCharDevice> hold_dev(this);
while (write_to_device() ||
red_char_device_read_from_device(this));
}
void RedCharDevice::stop()
{
spice_debug("char device %p", this);
priv->running = FALSE;
priv->active = FALSE;
if (priv->write_to_dev_timer) {
red_timer_cancel(priv->write_to_dev_timer);
}
}
void RedCharDevice::reset()
{
RedCharDeviceClient *dev_client;
RedCharDeviceWriteBuffer *buf;
priv->wait_for_migrate_data = FALSE;
spice_debug("char device %p", this);
while ((buf = (RedCharDeviceWriteBuffer *) g_queue_pop_tail(&priv->write_queue))) {
write_buffer_release(&buf);
}
write_buffer_release(&priv->cur_write_buf);
GLIST_FOREACH(priv->clients, RedCharDeviceClient, dev_client) {
spice_debug("send_queue_empty %d", g_queue_is_empty(dev_client->send_queue));
dev_client->num_send_tokens += g_queue_get_length(dev_client->send_queue);
g_queue_free_full(dev_client->send_queue, (GDestroyNotify)red_pipe_item_unref);
dev_client->send_queue = g_queue_new();
/* If device is reset, we must reset the tokens counters as well as we
* don't hold any data from client and upon agent's reconnection we send
* SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS with all free tokens we have */
dev_client->num_client_tokens += dev_client->num_client_tokens_free;
dev_client->num_client_tokens_free = 0;
}
}
void RedCharDevice::wakeup()
{
write_to_device();
red_char_device_read_from_device(this);
}
/*************
* Migration *
* **********/
void RedCharDevice::migrate_data_marshall_empty(SpiceMarshaller *m)
{
SpiceMigrateDataCharDevice *mig_data;
spice_debug("trace");
mig_data = (SpiceMigrateDataCharDevice *)spice_marshaller_reserve_space(m,
sizeof(*mig_data));
memset(mig_data, 0, sizeof(*mig_data));
mig_data->version = SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION;
mig_data->connected = FALSE;
}
static void migrate_data_marshaller_write_buffer_free(uint8_t *data, void *opaque)
{
RedCharDeviceWriteBuffer *write_buf = (RedCharDeviceWriteBuffer *)opaque;
red_char_device_write_buffer_unref(write_buf);
}
void RedCharDevice::migrate_data_marshall(SpiceMarshaller *m)
{
RedCharDeviceClient *dev_client;
GList *item;
uint8_t *write_to_dev_sizes_ptr;
uint32_t write_to_dev_size;
uint32_t write_to_dev_tokens;
SpiceMarshaller *m2;
/* multi-clients are not supported */
spice_assert(g_list_length(priv->clients) == 1);
dev_client = (RedCharDeviceClient *) g_list_last(priv->clients)->data;
/* FIXME: if there were more than one client before the marshalling,
* it is possible that the send_queue length > 0, and the send data
* should be migrated as well */
spice_assert(g_queue_is_empty(dev_client->send_queue));
spice_marshaller_add_uint32(m, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION);
spice_marshaller_add_uint8(m, 1); /* connected */
spice_marshaller_add_uint32(m, dev_client->num_client_tokens);
spice_marshaller_add_uint32(m, dev_client->num_send_tokens);
write_to_dev_sizes_ptr = spice_marshaller_reserve_space(m, sizeof(uint32_t)*2);
write_to_dev_size = 0;
write_to_dev_tokens = 0;
m2 = spice_marshaller_get_ptr_submarshaller(m);
if (priv->cur_write_buf) {
uint32_t buf_remaining = priv->cur_write_buf->buf + priv->cur_write_buf->buf_used -
priv->cur_write_buf_pos;
spice_marshaller_add_by_ref_full(m2, priv->cur_write_buf_pos, buf_remaining,
migrate_data_marshaller_write_buffer_free,
red_char_device_write_buffer_ref(priv->cur_write_buf)
);
write_to_dev_size += buf_remaining;
if (priv->cur_write_buf->priv->origin == WRITE_BUFFER_ORIGIN_CLIENT) {
spice_assert(priv->cur_write_buf->priv->client == dev_client->client);
write_to_dev_tokens += priv->cur_write_buf->priv->token_price;
}
}
for (item = g_queue_peek_tail_link(&priv->write_queue); item != NULL; item = item->prev) {
RedCharDeviceWriteBuffer *write_buf = (RedCharDeviceWriteBuffer *) item->data;
spice_marshaller_add_by_ref_full(m2, write_buf->buf, write_buf->buf_used,
migrate_data_marshaller_write_buffer_free,
red_char_device_write_buffer_ref(write_buf)
);
write_to_dev_size += write_buf->buf_used;
if (write_buf->priv->origin == WRITE_BUFFER_ORIGIN_CLIENT) {
spice_assert(write_buf->priv->client == dev_client->client);
write_to_dev_tokens += write_buf->priv->token_price;
}
}
spice_debug("migration data dev %p: write_queue size %u tokens %u",
this, write_to_dev_size, write_to_dev_tokens);
spice_marshaller_set_uint32(m, write_to_dev_sizes_ptr, write_to_dev_size);
spice_marshaller_set_uint32(m, write_to_dev_sizes_ptr + sizeof(uint32_t), write_to_dev_tokens);
}
bool RedCharDevice::restore(SpiceMigrateDataCharDevice *mig_data)
{
RedCharDeviceClient *dev_client;
uint32_t client_tokens_window;
spice_assert(g_list_length(priv->clients) == 1 &&
priv->wait_for_migrate_data);
dev_client = (RedCharDeviceClient *) g_list_last(priv->clients)->data;
if (mig_data->version > SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION) {
spice_error("dev %p error: migration data version %u is bigger than self %u",
this, mig_data->version, SPICE_MIGRATE_DATA_CHAR_DEVICE_VERSION);
return FALSE;
}
spice_assert(!priv->cur_write_buf && g_queue_is_empty(&priv->write_queue));
spice_assert(mig_data->connected);
client_tokens_window = dev_client->num_client_tokens; /* initial state of tokens */
dev_client->num_client_tokens = mig_data->num_client_tokens;
/* assumption: client_tokens_window stays the same across severs */
dev_client->num_client_tokens_free = client_tokens_window -
mig_data->num_client_tokens -
mig_data->write_num_client_tokens;
dev_client->num_send_tokens = mig_data->num_send_tokens;
if (mig_data->write_size > 0) {
if (mig_data->write_num_client_tokens) {
priv->cur_write_buf =
red_char_device_write_buffer_get(this, dev_client->client,
mig_data->write_size, WRITE_BUFFER_ORIGIN_CLIENT,
mig_data->write_num_client_tokens);
} else {
priv->cur_write_buf =
red_char_device_write_buffer_get(this, NULL,
mig_data->write_size, WRITE_BUFFER_ORIGIN_SERVER, 0);
}
/* the first write buffer contains all the data that was saved for migration */
memcpy(priv->cur_write_buf->buf,
((uint8_t *)mig_data) + mig_data->write_data_ptr - sizeof(SpiceMigrateDataHeader),
mig_data->write_size);
priv->cur_write_buf->buf_used = mig_data->write_size;
priv->cur_write_buf_pos = priv->cur_write_buf->buf;
}
priv->wait_for_migrate_data = FALSE;
write_to_device();
red_char_device_read_from_device(this);
return TRUE;
}
SpiceServer* RedCharDevice::get_server()
{
return priv->reds;
}
SpiceCharDeviceInterface *spice_char_device_get_interface(SpiceCharDeviceInstance *instance)
{
return SPICE_UPCAST(SpiceCharDeviceInterface, instance->base.sif);
}
void RedCharDevice::init_device_instance()
{
SpiceCharDeviceInterface *sif;
g_return_if_fail(priv->reds);
red_timer_remove(priv->write_to_dev_timer);
priv->write_to_dev_timer = NULL;
if (priv->sin == NULL) {
return;
}
sif = spice_char_device_get_interface(priv->sin);
if (sif->base.minor_version <= 2 ||
!(sif->flags & SPICE_CHAR_DEVICE_NOTIFY_WRITABLE)) {
priv->write_to_dev_timer = reds_core_timer_add(priv->reds,
RedCharDevice::write_retry,
this);
if (!priv->write_to_dev_timer) {
spice_error("failed creating char dev write timer");
}
}
priv->sin->st = this;
}
RedCharDevice::~RedCharDevice()
{
red_timer_remove(priv->write_to_dev_timer);
priv->write_to_dev_timer = NULL;
write_buffers_queue_free(&priv->write_queue);
red_char_device_write_buffer_free(priv->cur_write_buf);
priv->cur_write_buf = NULL;
while (priv->clients != NULL) {
RedCharDeviceClient *dev_client = (RedCharDeviceClient *) priv->clients->data;
red_char_device_client_free(this, dev_client);
}
priv->running = FALSE;
}
void
RedCharDevice::port_event(uint8_t event)
{
}
SPICE_GNUC_VISIBLE void spice_server_port_event(SpiceCharDeviceInstance *sin, uint8_t event)
{
if (sin->st == NULL) {
spice_warning("no RedCharDevice attached to instance %p", sin);
return;
}
sin->st->port_event(event);
}
SpiceCharDeviceInstance *RedCharDevice::get_device_instance()
{
return priv->sin;
}
RedCharDevice::RedCharDevice(RedsState *reds, SpiceCharDeviceInstance *sin,
uint64_t client_tokens_interval, uint64_t num_self_tokens)
{
priv->reds = reds;
priv->client_tokens_interval = client_tokens_interval;
priv->num_self_tokens = num_self_tokens;
reset_dev_instance(sin);
g_queue_init(&priv->write_queue);
}
int RedCharDevice::read(uint8_t *buf, int len)
{
auto sif = spice_char_device_get_interface(priv->sin);
int ret = sif->read(priv->sin, buf, len);
if (ret > 0) {
priv->active = true;
}
return ret;
}