+ free(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);
+}
+
+
+uint32_t spice_char_device_client_num_tokens_get(SpiceCharDeviceState *dev,
+ RedClient *client)
+{
+ SpiceCharDeviceClientState *dev_client;
+
+ dev_client = spice_char_device_client_find(dev, client);
+
+ if (!dev_client) {
+ spice_error("client wasn't found");
+ return 0;
+ }
+
+ return dev_client->num_client_tokens;
+}
+
+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 bdb32ae..e3cd52d 100644
--- a/server/char_device.h
+++ b/server/char_device.h
@@ -2,11 +2,209 @@
#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;
+ RedClient *client_origin; /* 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;
+
+ 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);
--
1.7.7.6
_______________________________________________
Spice-devel mailing list
Spice-devel@xxxxxxxxxxxxxxxxxxxxx
http://lists.freedesktop.org/mailman/listinfo/spice-devel