> > From: Yuri Benditovich <yuri.benditovich@xxxxxxxxxx> > > Redirection of emulated devices requires special approach, as > usbredirhost can't be used for that (it works only with libusb > devices). For emulated devices we create instance of usbredirparser > that implements USB redirection protocol. In order to work with the > same set of protocol capabilities that usbredirhost sets up with > remote side, the parser shall: - not send its own 'hello' to the > server > - initialize the same capabilities that usbredirhost > - receive the same 'hello' response > For that we analyze 'hello' sent by USB redir parser and extract set > of capabilities from it and upon receive of 'hello' response we > provide it also to the parser. When libusb device is redirected via a > channel, instance of usbredirhost serves it. When emulated device is > redirected via a channel, instance of usbredirparser does the job. > > Signed-off-by: Yuri Benditovich <yuri.benditovich@xxxxxxxxxx> Same comments for previous patch. Otherwise fine for me. Christophe did multiple comments but I think were all addressed before. > --- > src/usb-backend.c | 417 ++++++++++++++++++++++++++++++++++++++++------ > 1 file changed, 370 insertions(+), 47 deletions(-) > > diff --git a/src/usb-backend.c b/src/usb-backend.c > index ef29901..81d7c9e 100644 > --- a/src/usb-backend.c > +++ b/src/usb-backend.c > @@ -58,6 +58,7 @@ struct _SpiceUsbDevice > UsbDeviceInformation device_info; > bool cached_isochronous_valid; > bool cached_isochronous; > + gboolean edev_configured; > }; > > struct _SpiceUsbBackend > @@ -80,14 +81,23 @@ struct _SpiceUsbBackend > uint32_t own_devices_mask; > }; > > +typedef enum { > + USB_CHANNEL_STATE_INITIALIZING, > + USB_CHANNEL_STATE_HOST, > + USB_CHANNEL_STATE_PARSER, > +} SpiceUsbBackendChannelState; > + > struct _SpiceUsbBackendChannel > { > struct usbredirhost *usbredirhost; > struct usbredirparser *parser; > + SpiceUsbBackendChannelState state; > uint8_t *read_buf; > int read_buf_size; > struct usbredirfilter_rule *rules; > int rules_count; > + uint32_t rejected : 1; > + uint32_t wait_disconnect_ack : 1; > SpiceUsbBackendDevice *attached; > SpiceUsbredirChannel *usbredir_channel; > SpiceUsbBackend *backend; > @@ -406,13 +416,17 @@ from both the main thread as well as from the usb event > handling thread */ > static void usbredir_write_flush_callback(void *user_data) > { > SpiceUsbBackendChannel *ch = user_data; > - if (!ch->usbredirhost) { > - /* just to be on the safe side */ > + if (ch->parser == NULL) { > return; > } > if (is_channel_ready(ch->usbredir_channel)) { > - SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch); > - usbredirhost_write_guest_data(ch->usbredirhost); > + if (ch->state == USB_CHANNEL_STATE_HOST) { > + SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch); > + usbredirhost_write_guest_data(ch->usbredirhost); > + } else { > + SPICE_DEBUG("%s ch %p -> parser", __FUNCTION__, ch); > + usbredirparser_do_write(ch->parser); > + } > } else { > SPICE_DEBUG("%s ch %p (not ready)", __FUNCTION__, ch); > } > @@ -567,13 +581,53 @@ void > spice_usb_backend_device_unref(SpiceUsbBackendDevice *dev) > } > } > > -G_GNUC_INTERNAL > +static int check_edev_device_filter(SpiceUsbBackendDevice *dev, > + const struct usbredirfilter_rule *rules, > + int count) > +{ > + SpiceUsbEmulatedDevice *edev = dev->edev; > + uint8_t cls[32], subcls[32], proto[32], *cfg, ifnum = 0; > + uint16_t size, offset = 0; > + > + if (!device_ops(edev)->get_descriptor(edev, LIBUSB_DT_CONFIG, 0, (void > **)&cfg, &size)) { > + return -EINVAL; > + } > + > + while ((offset + 1) < size) { > + uint8_t len = cfg[offset]; > + uint8_t type = cfg[offset + 1]; > + if ((offset + len) > size) { > + break; > + } > + if (type == LIBUSB_DT_INTERFACE) { > + cls[ifnum] = cfg[offset + 5]; > + subcls[ifnum] = cfg[offset + 6]; > + proto[ifnum] = cfg[offset + 7]; > + ifnum++; > + } > + offset += len; > + } > + > + return usbredirfilter_check(rules, count, > + dev->device_info.class, > + dev->device_info.subclass, > + dev->device_info.protocol, > + cls, subcls, proto, ifnum, > + dev->device_info.vid, > + dev->device_info.pid, > + dev->device_info.bcdUSB, 0); > +} > + > int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev, > - const struct usbredirfilter_rule > *rules, > - int count) > + const struct usbredirfilter_rule > *rules, int count) > { > - g_return_val_if_fail(dev->libusb_device != NULL, -EINVAL); > - return usbredirhost_check_device_filter(rules, count, > dev->libusb_device, 0); > + if (dev->libusb_device != NULL) { > + return usbredirhost_check_device_filter(rules, count, > dev->libusb_device, 0); > + } else if (dev->edev != NULL) { > + return check_edev_device_filter(dev, rules, count); > + } > + g_warn_if_reached(); > + return -EINVAL; > } > > static int usbredir_read_callback(void *user_data, uint8_t *data, int count) > @@ -692,10 +746,33 @@ int > spice_usb_backend_read_guest_data(SpiceUsbBackendChannel *ch, uint8_t *data, > > ch->read_buf = data; > ch->read_buf_size = count; > - if (ch->usbredirhost) { > + if (SPICE_UNLIKELY(ch->state == USB_CHANNEL_STATE_INITIALIZING)) { > + if (ch->usbredirhost != NULL) { > + res = usbredirhost_read_guest_data(ch->usbredirhost); > + if (res != 0) { > + return res; > + } > + ch->state = USB_CHANNEL_STATE_HOST; > + > + /* usbredirhost should consume hello response */ > + g_return_val_if_fail(ch->read_buf == NULL, > USB_REDIR_ERROR_READ_PARSE); > + } else { > + ch->state = USB_CHANNEL_STATE_PARSER; > + } > + > + ch->read_buf = data; > + ch->read_buf_size = count; > + if (ch->attached && ch->attached->edev) { > + /* case of CD sharing on connect */ > + ch->state = USB_CHANNEL_STATE_PARSER; > + SPICE_DEBUG("%s: switch %p to parser", __FUNCTION__, ch); > + } > + return usbredirparser_do_read(ch->parser); > + } > + if (ch->state == USB_CHANNEL_STATE_HOST) { > res = usbredirhost_read_guest_data(ch->usbredirhost); > } else { > - res = USB_REDIR_ERROR_IO; > + res = usbredirparser_do_read(ch->parser); > } > switch (res) > { > @@ -714,6 +791,11 @@ int > spice_usb_backend_read_guest_data(SpiceUsbBackendChannel *ch, uint8_t *data, > } > SPICE_DEBUG("%s ch %p, %d bytes, res %d", __FUNCTION__, ch, count, res); > > + if (ch->rejected) { > + ch->rejected = 0; > + res = USB_REDIR_ERROR_DEV_REJECTED; > + } > + > return res; > } > > @@ -740,23 +822,115 @@ GError *spice_usb_backend_get_error_details(int > error_code, gchar *desc) > return err; > } > > +void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void > *data) > +{ > + if (ch->state == USB_CHANNEL_STATE_HOST) { > + SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch); > + usbredirhost_free_write_buffer(ch->usbredirhost, data); > + } else { > + SPICE_DEBUG("%s ch %p -> parser", __FUNCTION__, ch); > + usbredirparser_free_write_buffer(ch->parser, data); > + } > +} > + > static void > usbredir_control_packet(void *priv, uint64_t id, struct > usb_redir_control_packet_header *h, > uint8_t *data, int data_len) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + SpiceUsbBackendDevice *d = ch->attached; > + SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL; > + struct usb_redir_control_packet_header response = *h; > + uint8_t reqtype = h->requesttype & 0x7f; > + gboolean done = FALSE; > + void *out_buffer = NULL; > + > + response.status = usb_redir_stall; > + SPICE_DEBUG("%s %p: TRVIL %02X %02X %04X %04X %04X", > + __FUNCTION__, > + ch, h->requesttype, h->request, > + h->value, h->index, h->length); > + > + if (!edev) { > + SPICE_DEBUG("%s: device not attached", __FUNCTION__); > + response.status = usb_redir_ioerror; > + done = TRUE; > + } else if (reqtype == (LIBUSB_REQUEST_TYPE_STANDARD | > LIBUSB_RECIPIENT_DEVICE) && > + h->request == LIBUSB_REQUEST_GET_DESCRIPTOR) { > + uint16_t size; > + done = device_ops(edev)->get_descriptor(edev, h->value >> 8, > h->value & 0xff, > + &out_buffer, &size); > + response.length = size; > + if (done) { > + response.status = 0; > + } > + done = TRUE; > + } > + > + if (!done) { > + device_ops(edev)->control_request(edev, data, data_len, &response, > &out_buffer); > + done = TRUE; > + } > + > + if (response.status) { > + response.length = 0; > + } else if (response.length > h->length) { > + response.length = h->length; > + } > + > + SPICE_DEBUG("%s responding with payload of %02X, status %X", > + __FUNCTION__, response.length, response.status); > + usbredirparser_send_control_packet(ch->parser, id, &response, > + response.length ? out_buffer : NULL, > + response.length); > + > + usbredir_write_flush_callback(ch); > + usbredirparser_free_packet_data(ch->parser, data); > } > > static void > usbredir_bulk_packet(void *priv, uint64_t id, struct > usb_redir_bulk_packet_header *h, > uint8_t *data, int data_len) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + SpiceUsbBackendDevice *d = ch->attached; > + SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL; > + struct usb_redir_bulk_packet_header hout = *h; > + uint32_t len = (h->length_high << 16) | h->length; > + SPICE_DEBUG("%s %p: ep %X, len %u, id %" G_GUINT64_FORMAT, __FUNCTION__, > + ch, h->endpoint, len, id); > + > + if (!edev) { > + SPICE_DEBUG("%s: device not attached", __FUNCTION__); > + hout.status = usb_redir_ioerror; > + hout.length = hout.length_high = 0; > + SPICE_DEBUG("%s: responding with ZLP status %d", __FUNCTION__, > hout.status); > + } else if (h->endpoint & LIBUSB_ENDPOINT_IN) { > + if (device_ops(edev)->bulk_in_request(edev, id, &hout)) { > + usbredirparser_free_packet_data(ch->parser, data); > + /* completion is asynchronous */ > + return; > + } > + } else { > + hout.status = usb_redir_stall; > + device_ops(edev)->bulk_out_request(edev, h->endpoint, data, > data_len, &hout.status); > + SPICE_DEBUG("%s: responding status %d", __FUNCTION__, hout.status); > + } > + > + usbredirparser_send_bulk_packet(ch->parser, id, &hout, NULL, 0); > + usbredirparser_free_packet_data(ch->parser, data); > + usbredir_write_flush_callback(ch); > } > > static void usbredir_device_reset(void *priv) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + SpiceUsbBackendDevice *d = ch->attached; > + SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL; > + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); > + if (edev) { > + device_ops(edev)->reset(edev); > + } > } > > static void > @@ -775,34 +949,72 @@ static void > usbredir_set_configuration(void *priv, uint64_t id, > struct usb_redir_set_configuration_header > *set_configuration) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + struct usb_redir_configuration_status_header h; > + h.status = 0; > + h.configuration = set_configuration->configuration; > + SPICE_DEBUG("%s ch %p, cfg %d", __FUNCTION__, ch, h.configuration); > + if (ch->attached) { > + ch->attached->edev_configured = h.configuration != 0; > + } > + usbredirparser_send_configuration_status(ch->parser, id, &h); > + usbredir_write_flush_callback(ch); > } > > static void usbredir_get_configuration(void *priv, uint64_t id) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + struct usb_redir_configuration_status_header h; > + h.status = 0; > + h.configuration = ch->attached && ch->attached->edev_configured; > + SPICE_DEBUG("%s ch %p, cfg %d", __FUNCTION__, ch, h.configuration); > + usbredirparser_send_configuration_status(ch->parser, id, &h); > + usbredir_write_flush_callback(ch); > } > > static void > usbredir_set_alt_setting(void *priv, uint64_t id, struct > usb_redir_set_alt_setting_header *s) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + struct usb_redir_alt_setting_status_header sh; > + sh.status = (!s->interface && !s->alt) ? 0 : usb_redir_stall; > + sh.interface = s->interface; > + sh.alt = s->alt; > + SPICE_DEBUG("%s ch %p, %d:%d", __FUNCTION__, ch, s->interface, s->alt); > + usbredirparser_send_alt_setting_status(ch->parser, id, &sh); > + usbredir_write_flush_callback(ch); > } > > static void > usbredir_get_alt_setting(void *priv, uint64_t id, struct > usb_redir_get_alt_setting_header *s) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + struct usb_redir_alt_setting_status_header sh; > + sh.status = (s->interface == 0) ? 0 : usb_redir_stall; > + sh.interface = s->interface; > + sh.alt = 0; > + SPICE_DEBUG("%s ch %p, if %d", __FUNCTION__, ch, s->interface); > + usbredirparser_send_alt_setting_status(ch->parser, id, &sh); > + usbredir_write_flush_callback(ch); > } > > static void usbredir_cancel_data(void *priv, uint64_t id) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + SpiceUsbBackendDevice *d = ch->attached; > + SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL; > + if (!edev) { > + SPICE_DEBUG("%s: device not attached", __FUNCTION__); > + return; > + } > + device_ops(edev)->cancel_request(edev, id); > } > > static void usbredir_filter_reject(void *priv) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + SPICE_DEBUG("%s %p", __FUNCTION__, ch); > + ch->rejected = 1; > } > > /* Note that the ownership of the rules array is passed on to the callback. > */ > @@ -827,13 +1039,77 @@ usbredir_filter_filter(void *priv, struct > usbredirfilter_rule *r, int count) > > static void usbredir_device_disconnect_ack(void *priv) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); > + if (ch->state == USB_CHANNEL_STATE_PARSER && ch->usbredirhost != NULL && > + ch->wait_disconnect_ack) { > + ch->state = USB_CHANNEL_STATE_HOST; > + SPICE_DEBUG("%s switch to usbredirhost", __FUNCTION__); > + } > + ch->wait_disconnect_ack = 0; > } > > static void > usbredir_hello(void *priv, struct usb_redir_hello_header *hello) > { > - USBREDIR_CALLBACK_NOT_IMPLEMENTED(); > + SpiceUsbBackendChannel *ch = priv; > + SpiceUsbBackendDevice *d = ch->attached; > + SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL; > + struct usb_redir_device_connect_header device_connect; > + struct usb_redir_ep_info_header ep_info = { 0 }; > + struct usb_redir_interface_info_header interface_info = { 0 }; > + uint8_t *cfg; > + uint16_t size, offset = 0; > + SPICE_DEBUG("%s %p %sattached %s", __FUNCTION__, ch, > + edev ? "" : "not ", hello ? "" : "(internal)"); > + > + if (!edev) { > + return; > + } > + if (!device_ops(edev)->get_descriptor(edev, LIBUSB_DT_CONFIG, 0, (void > **)&cfg, &size)) { > + return; > + } > + while ((offset + 1) < size) { > + uint8_t len = cfg[offset]; > + uint8_t type = cfg[offset + 1]; > + if ((offset + len) > size) { > + break; > + } > + if (type == LIBUSB_DT_INTERFACE) { > + uint32_t i = interface_info.interface_count; > + uint8_t class, subclass, protocol; > + class = cfg[offset + 5]; > + subclass = cfg[offset + 6]; > + protocol = cfg[offset + 7]; > + interface_info.interface_class[i] = class; > + interface_info.interface_subclass[i] = subclass; > + interface_info.interface_protocol[i] = protocol; > + interface_info.interface_count++; > + SPICE_DEBUG("%s IF%d: %d/%d/%d", __FUNCTION__, i, class, > subclass, protocol); > + } else if (type == LIBUSB_DT_ENDPOINT) { > + uint8_t address = cfg[offset + 2]; > + uint16_t max_packet_size = 0x100 * cfg[offset + 5] + cfg[offset > + 4]; > + uint8_t index = address & 0xf; > + if (address & 0x80) index += 0x10; > + ep_info.type[index] = cfg[offset + 3] & 0x3; > + ep_info.max_packet_size[index] = max_packet_size; > + SPICE_DEBUG("%s EP[%02X]: %d/%d", __FUNCTION__, index, > ep_info.type[index], max_packet_size); > + } > + offset += len; > + } > + > + usbredirparser_send_interface_info(ch->parser, &interface_info); > + usbredirparser_send_ep_info(ch->parser, &ep_info); > + > + device_connect.device_class = 0; //d->device_info.class; > + device_connect.device_subclass = 0; //d->device_info.subclass; > + device_connect.device_protocol = 0; //d->device_info.protocol;; > + device_connect.vendor_id = d->device_info.vid; > + device_connect.product_id = d->device_info.pid; > + device_connect.device_version_bcd = d->device_info.bcdUSB; > + device_connect.speed = usb_redir_speed_high; > + usbredirparser_send_device_connect(ch->parser, &device_connect); > + usbredir_write_flush_callback(ch); > } > > static void initialize_parser(SpiceUsbBackendChannel *ch) > @@ -899,14 +1175,36 @@ static struct usbredirparser > *create_parser(SpiceUsbBackendChannel *ch) > return parser; > } > > -void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void > *data) > +static gboolean attach_edev(SpiceUsbBackendChannel *ch, > + SpiceUsbBackendDevice *dev, > + GError **error) > { > - if (ch->usbredirhost) { > - SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); > - usbredirhost_free_write_buffer(ch->usbredirhost, data); > + if (!dev->edev) { > + g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, > + _("Failed to redirect device %d"), 1); > + return FALSE; > + } > + if (ch->state == USB_CHANNEL_STATE_INITIALIZING) { > + /* > + we can't initialize parser until we see hello from usbredir > + and the parser can't work until it sees the hello response. > + this is case of autoconnect when emulated device is attached > + before the channel is up > + */ > + SPICE_DEBUG("%s waiting until the channel is ready", __FUNCTION__); > + > } else { > - SPICE_DEBUG("%s ch %p - NOBODY TO CALL", __FUNCTION__, ch); > + ch->state = USB_CHANNEL_STATE_PARSER; > } > + ch->wait_disconnect_ack = 0; > + ch->attached = dev; > + dev->attached_to = ch; > + device_ops(dev->edev)->attach(dev->edev, ch->parser); > + if (ch->state == USB_CHANNEL_STATE_PARSER) { > + /* send device info */ > + usbredir_hello(ch, NULL); > + } > + return TRUE; > } > > gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, > @@ -918,12 +1216,19 @@ gboolean > spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, > > g_return_val_if_fail(dev != NULL, FALSE); > > + if (!dev->libusb_device) { > + return attach_edev(ch, dev, error); > + } > + > // no physical device enabled > if (ch->usbredirhost == NULL) { > return FALSE; > } > > libusb_device_handle *handle = NULL; > + if (ch->state != USB_CHANNEL_STATE_INITIALIZING) { > + ch->state = USB_CHANNEL_STATE_HOST; > + } > > /* > Under Windows we need to avoid updating > @@ -957,18 +1262,32 @@ gboolean > spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, > > void spice_usb_backend_channel_detach(SpiceUsbBackendChannel *ch) > { > + SpiceUsbBackendDevice *d = ch->attached; > + SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL; > SPICE_DEBUG("%s >> ch %p, was attached %p", __FUNCTION__, ch, > ch->attached); > - if (!ch->attached) { > + if (!d) { > SPICE_DEBUG("%s: nothing to detach", __FUNCTION__); > return; > } > - if (ch->usbredirhost) { > + if (ch->state == USB_CHANNEL_STATE_HOST) { > /* it will call libusb_close internally */ > usbredirhost_set_device(ch->usbredirhost, NULL); > + } else { > + if (edev) { > + device_ops(edev)->detach(edev); > + } > + usbredirparser_send_device_disconnect(ch->parser); > + usbredir_write_flush_callback(ch); > + ch->wait_disconnect_ack = > + usbredirparser_peer_has_cap(ch->parser, > usb_redir_cap_device_disconnect_ack); > + if (!ch->wait_disconnect_ack && ch->usbredirhost != NULL) { > + ch->state = USB_CHANNEL_STATE_HOST; > + } > } > SPICE_DEBUG("%s ch %p, detach done", __FUNCTION__, ch); > ch->attached->attached_to = NULL; > ch->attached = NULL; > + ch->rejected = 0; > } > > SpiceUsbBackendChannel * > @@ -982,23 +1301,25 @@ spice_usb_backend_channel_new(SpiceUsbBackend *be, > ch->usbredir_channel = usbredir_channel; > if (be->libusb_context) { > ch->backend = be; > - ch->usbredirhost = usbredirhost_open_full( > - be->libusb_context, > - NULL, > - usbredir_log, > - usbredir_read_callback, > - usbredir_write_callback, > - usbredir_write_flush_callback, > - usbredir_alloc_lock, > - usbredir_lock_lock, > - usbredir_unlock_lock, > - usbredir_free_lock, > - ch, PACKAGE_STRING, > - spice_util_get_debug() ? usbredirparser_debug : > usbredirparser_warning, > - usbredirhost_fl_write_cb_owns_buffer); > + ch->usbredirhost = > + usbredirhost_open_full(be->libusb_context, > + NULL, > + usbredir_log, > + usbredir_read_callback, > + usbredir_write_callback, > + usbredir_write_flush_callback, > + usbredir_alloc_lock, > + usbredir_lock_lock, > + usbredir_unlock_lock, > + usbredir_free_lock, > + ch, PACKAGE_STRING, > + spice_util_get_debug() ? > usbredirparser_debug : > + usbredirparser_warning, > + usbredirhost_fl_write_cb_owns_buffer); > g_warn_if_fail(ch->usbredirhost != NULL); > - if (ch->usbredirhost) { > - usbredirhost_set_buffered_output_size_cb(ch->usbredirhost, > usbredir_buffered_output_size_callback); > + if (ch->usbredirhost != NULL) { > + usbredirhost_set_buffered_output_size_cb(ch->usbredirhost, > + > usbredir_buffered_output_size_callback); > // force flush of HELLO packet and creation of parser > usbredirhost_write_guest_data(ch->usbredirhost); > } > @@ -1022,9 +1343,11 @@ spice_usb_backend_channel_new(SpiceUsbBackend *be, > > void spice_usb_backend_channel_flush_writes(SpiceUsbBackendChannel *ch) > { > - SPICE_DEBUG("%s %p, host %p", __FUNCTION__, ch, ch->usbredirhost); > - if (ch->usbredirhost) { > + SPICE_DEBUG("%s %p is up", __FUNCTION__, ch); > + if (ch->state != USB_CHANNEL_STATE_PARSER && ch->usbredirhost != NULL) { > usbredirhost_write_guest_data(ch->usbredirhost); > + } else { > + usbredirparser_do_write(ch->parser); > } > } > Frediano _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel