[spice-gtk,v4,00/13] CD sharing feature

Submitted by Christophe Fergeau on Sept. 21, 2018, 6:10 p.m.

Details

Message ID 20180921181008.GA20692@natto.ory.fergeau.eu
State New
Headers show

Not browsing as part of any series.

Patch hide | download patch | download mbox

diff --git a/src/Makefile.am b/src/Makefile.am
index 4dd657d7..250f2742 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -249,6 +249,8 @@  libspice_client_glib_2_0_la_SOURCES =			\
 	spice-uri-priv.h				\
 	usb-device-manager.c				\
 	usb-device-manager-priv.h			\
+	usb-backend-common.c				\
+	usb-backend.h					\
 	usbutil.c					\
 	usbutil.h					\
 	$(USB_ACL_HELPER_SRCS)				\
diff --git a/src/channel-usbredir-priv.h b/src/channel-usbredir-priv.h
index 17e9716a..fee95f7e 100644
--- a/src/channel-usbredir-priv.h
+++ b/src/channel-usbredir-priv.h
@@ -21,9 +21,8 @@ 
 #ifndef __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__
 #define __SPICE_CLIENT_USBREDIR_CHANNEL_PRIV_H__
 
-#include <libusb.h>
-#include <usbredirfilter.h>
 #include "spice-client.h"
+#include "usb-backend.h"
 
 G_BEGIN_DECLS
 
@@ -31,7 +30,7 @@  G_BEGIN_DECLS
    context should not be destroyed before the last device has been
    disconnected */
 void spice_usbredir_channel_set_context(SpiceUsbredirChannel *channel,
-                                        libusb_context       *context);
+                                        SpiceUsbBackend *context);
 
 void spice_usbredir_channel_disconnect_device_async(SpiceUsbredirChannel *channel,
                                                     GCancellable *cancellable,
@@ -46,7 +45,7 @@  gboolean spice_usbredir_channel_disconnect_device_finish(SpiceUsbredirChannel *c
    (through spice_channel_connect()), before calling this. */
 void spice_usbredir_channel_connect_device_async(
                                         SpiceUsbredirChannel *channel,
-                                        libusb_device        *device,
+                                        SpiceUsbBackendDevice  *device,
                                         SpiceUsbDevice       *spice_device,
                                         GCancellable         *cancellable,
                                         GAsyncReadyCallback   callback,
@@ -58,7 +57,7 @@  gboolean spice_usbredir_channel_connect_device_finish(
 
 void spice_usbredir_channel_disconnect_device(SpiceUsbredirChannel *channel);
 
-libusb_device *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel);
+SpiceUsbBackendDevice *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel);
 
 void spice_usbredir_channel_lock(SpiceUsbredirChannel *channel);
 
diff --git a/src/channel-usbredir.c b/src/channel-usbredir.c
index 1d9c380f..bf98f41f 100644
--- a/src/channel-usbredir.c
+++ b/src/channel-usbredir.c
@@ -23,7 +23,6 @@ 
 
 #ifdef USE_USBREDIR
 #include <glib/gi18n-lib.h>
-#include <usbredirhost.h>
 #ifdef USE_LZ4
 #include <lz4.h>
 #endif
@@ -66,15 +65,12 @@  enum SpiceUsbredirChannelState {
 };
 
 struct _SpiceUsbredirChannelPrivate {
-    libusb_device *device;
+    SpiceUsbBackendDevice *device;
     SpiceUsbDevice *spice_device;
-    libusb_context *context;
-    struct usbredirhost *host;
+    SpiceUsbBackend *context;
+    SpiceUsbBackendChannel *host;
     /* To catch usbredirhost error messages and report them as a GError */
     GError **catch_error;
-    /* Data passed from channel handle msg to the usbredirhost read cb */
-    const uint8_t *read_buf;
-    int read_buf_size;
     enum SpiceUsbredirChannelState state;
 #ifdef USE_POLKIT
     GTask *task;
@@ -90,18 +86,10 @@  static void spice_usbredir_channel_dispose(GObject *obj);
 static void spice_usbredir_channel_finalize(GObject *obj);
 static void usbredir_handle_msg(SpiceChannel *channel, SpiceMsgIn *in);
 
-static void usbredir_log(void *user_data, int level, const char *msg);
-static int usbredir_read_callback(void *user_data, uint8_t *data, int count);
+static void usbredir_log(void *user_data, const char *msg, gboolean error);
 static int usbredir_write_callback(void *user_data, uint8_t *data, int count);
-static void usbredir_write_flush_callback(void *user_data);
-#if USBREDIR_VERSION >= 0x000701
-static uint64_t usbredir_buffered_output_size_callback(void *user_data);
-#endif
-
-static void *usbredir_alloc_lock(void);
-static void usbredir_lock_lock(void *user_data);
-static void usbredir_unlock_lock(void *user_data);
-static void usbredir_free_lock(void *user_data);
+static gboolean usbredir_is_channel_ready(void *user_data);
+static uint64_t usbredir_get_queue_size(void *user_data);
 
 #else
 struct _SpiceUsbredirChannelPrivate {
@@ -128,7 +116,7 @@  static void _channel_reset_finish(SpiceUsbredirChannel *channel)
 
     spice_usbredir_channel_lock(channel);
 
-    usbredirhost_close(priv->host);
+    spice_usb_backend_channel_finalize(priv->host);
     priv->host = NULL;
 
     /* Call set_context to re-create the host */
@@ -228,7 +216,7 @@  static void spice_usbredir_channel_finalize(GObject *obj)
     SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(obj);
 
     if (channel->priv->host)
-        usbredirhost_close(channel->priv->host);
+        spice_usb_backend_channel_finalize(channel->priv->host);
 #ifdef USE_USBREDIR
     g_mutex_clear(&channel->priv->device_connect_mutex);
 #endif
@@ -253,32 +241,22 @@  static void channel_set_handlers(SpiceChannelClass *klass)
 
 G_GNUC_INTERNAL
 void spice_usbredir_channel_set_context(SpiceUsbredirChannel *channel,
-                                        libusb_context       *context)
+                                        SpiceUsbBackend *context)
 {
     SpiceUsbredirChannelPrivate *priv = channel->priv;
+    SpiceUsbBackendChannelInitData init_data;
+    init_data.user_data = channel;
+    init_data.get_queue_size = usbredir_get_queue_size;
+    init_data.is_channel_ready = usbredir_is_channel_ready;
+    init_data.log = usbredir_log;
+    init_data.write_callback = usbredir_write_callback;
+    init_data.debug = spice_util_get_debug();
 
     g_return_if_fail(priv->host == NULL);
 
     priv->context = context;
-    priv->host = usbredirhost_open_full(
-                                   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,
-                                   channel, PACKAGE_STRING,
-                                   spice_util_get_debug() ? usbredirparser_debug : usbredirparser_warning,
-                                   usbredirhost_fl_write_cb_owns_buffer);
-    if (!priv->host)
-        g_error("Out of memory allocating usbredirhost");
+    priv->host = spice_usb_backend_channel_initialize(context, &init_data);
 
-#if USBREDIR_VERSION >= 0x000701
-    usbredirhost_set_buffered_output_size_cb(priv->host, usbredir_buffered_output_size_callback);
-#endif
 #ifdef USE_LZ4
     spice_channel_set_capability(channel, SPICE_SPICEVMC_CAP_DATA_COMPRESS_LZ4);
 #endif
@@ -289,9 +267,8 @@  static gboolean spice_usbredir_channel_open_device(
 {
     SpiceUsbredirChannelPrivate *priv = channel->priv;
     SpiceSession *session;
-    libusb_device_handle *handle = NULL;
-    int rc, status;
     SpiceUsbDeviceManager *manager;
+    const char *msg = NULL;
 
     g_return_val_if_fail(priv->state == STATE_DISCONNECTED
 #ifdef USE_POLKIT
@@ -299,29 +276,28 @@  static gboolean spice_usbredir_channel_open_device(
 #endif
                          , FALSE);
 
-    rc = libusb_open(priv->device, &handle);
-    if (rc != 0) {
-        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                    "Could not open usb device: %s [%i]",
-                    spice_usbutil_libusb_strerror(rc), rc);
-        return FALSE;
-    }
-
     priv->catch_error = err;
-    status = usbredirhost_set_device(priv->host, handle);
+    if (!spice_usb_backend_channel_attach(priv->host, priv->device, &msg)) {
+        priv->catch_error = NULL;
+        if (*err == NULL) {
+            if (!msg) {
+                msg = "Exact error not reported";
+            }
+            g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                "Error attaching device: %s", msg);
+        }
+        return FALSE;
+    }
     priv->catch_error = NULL;
-    if (status != usb_redir_success) {
-        g_return_val_if_fail(err == NULL || *err != NULL, FALSE);
-        return FALSE;
-    }
 
     session = spice_channel_get_session(SPICE_CHANNEL(channel));
     manager = spice_usb_device_manager_get(session, NULL);
     g_return_val_if_fail(manager != NULL, FALSE);
 
     priv->usb_device_manager = g_object_ref(manager);
-    if (!spice_usb_device_manager_start_event_listening(priv->usb_device_manager, err)) {
-        usbredirhost_set_device(priv->host, NULL);
+    if (spice_usb_backend_device_need_thread(priv->device) &&
+        !spice_usb_device_manager_start_event_listening(priv->usb_device_manager, err)) {
+        spice_usb_backend_channel_attach(priv->host, NULL, NULL);
         return FALSE;
     }
 
@@ -352,8 +328,7 @@  static void spice_usbredir_channel_open_acl_cb(
         spice_usbredir_channel_open_device(channel, &err);
     }
     if (err) {
-        libusb_unref_device(priv->device);
-        priv->device = NULL;
+        g_clear_pointer(&priv->device, spice_usb_backend_device_release);
         g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
         priv->spice_device = NULL;
         priv->state  = STATE_DISCONNECTED;
@@ -384,8 +359,7 @@  _open_device_async_cb(GTask *task,
     spice_usbredir_channel_lock(channel);
 
     if (!spice_usbredir_channel_open_device(channel, &err)) {
-        libusb_unref_device(priv->device);
-        priv->device = NULL;
+        g_clear_pointer(&priv->device, spice_usb_backend_device_release);
         g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
         priv->spice_device = NULL;
     }
@@ -403,13 +377,16 @@  _open_device_async_cb(GTask *task,
 G_GNUC_INTERNAL
 void spice_usbredir_channel_connect_device_async(
                                           SpiceUsbredirChannel *channel,
-                                          libusb_device        *device,
+                                          SpiceUsbBackendDevice *device,
                                           SpiceUsbDevice       *spice_device,
                                           GCancellable         *cancellable,
                                           GAsyncReadyCallback   callback,
                                           gpointer              user_data)
 {
     SpiceUsbredirChannelPrivate *priv = channel->priv;
+#ifdef USE_POLKIT
+    const UsbDeviceInformation *info = spice_usb_backend_device_get_info(device);
+#endif
     GTask *task;
 
     g_return_if_fail(SPICE_IS_USBREDIR_CHANNEL(channel));
@@ -436,7 +413,8 @@  void spice_usbredir_channel_connect_device_async(
         goto done;
     }
 
-    priv->device = libusb_ref_device(device);
+    spice_usb_backend_device_acquire(device);
+    priv->device = device;
     priv->spice_device = g_boxed_copy(spice_usb_device_get_type(),
                                       spice_device);
 #ifdef USE_POLKIT
@@ -446,8 +424,8 @@  void spice_usbredir_channel_connect_device_async(
     g_object_set(spice_channel_get_session(SPICE_CHANNEL(channel)),
                  "inhibit-keyboard-grab", TRUE, NULL);
     spice_usb_acl_helper_open_acl_async(priv->acl_helper,
-                                        libusb_get_bus_number(device),
-                                        libusb_get_device_address(device),
+                                        info->bus,
+                                        info->address,
                                         cancellable,
                                         spice_usbredir_channel_open_acl_cb,
                                         channel);
@@ -501,13 +479,14 @@  void spice_usbredir_channel_disconnect_device(SpiceUsbredirChannel *channel)
          * libusb_handle_events call in the thread.
          */
         g_warn_if_fail(priv->usb_device_manager != NULL);
-        spice_usb_device_manager_stop_event_listening(priv->usb_device_manager);
+        if (spice_usb_backend_device_need_thread(priv->device)) {
+            spice_usb_device_manager_stop_event_listening(priv->usb_device_manager);
+        }
         g_clear_object(&priv->usb_device_manager);
 
         /* This also closes the libusb handle we passed from open_device */
-        usbredirhost_set_device(priv->host, NULL);
-        libusb_unref_device(priv->device);
-        priv->device = NULL;
+        spice_usb_backend_channel_attach(priv->host, NULL, NULL);
+        g_clear_pointer(&priv->device, spice_usb_backend_device_release);
         g_boxed_free(spice_usb_device_get_type(), priv->spice_device);
         priv->spice_device = NULL;
         priv->state  = STATE_DISCONNECTED;
@@ -558,7 +537,7 @@  spice_usbredir_channel_get_spice_usb_device(SpiceUsbredirChannel *channel)
 #endif
 
 G_GNUC_INTERNAL
-libusb_device *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel)
+SpiceUsbBackendDevice *spice_usbredir_channel_get_device(SpiceUsbredirChannel *channel)
 {
     return channel->priv->device;
 }
@@ -573,44 +552,37 @@  void spice_usbredir_channel_get_guest_filter(
 
     g_return_if_fail(priv->host != NULL);
 
-    usbredirhost_get_guest_filter(priv->host, rules_ret, rules_count_ret);
+    spice_usb_backend_channel_get_guest_filter(priv->host, rules_ret, rules_count_ret);
 }
 
 /* ------------------------------------------------------------------ */
 /* callbacks (any context)                                            */
 
-#if USBREDIR_VERSION >= 0x000701
-static uint64_t usbredir_buffered_output_size_callback(void *user_data)
+static uint64_t usbredir_get_queue_size(void *user_data)
 {
     g_return_val_if_fail(SPICE_IS_USBREDIR_CHANNEL(user_data), 0);
     return spice_channel_get_queue_size(SPICE_CHANNEL(user_data));
 }
-#endif
 
-/* Note that this function must be re-entrant safe, as it can get called
-   from both the main thread as well as from the usb event handling thread */
-static void usbredir_write_flush_callback(void *user_data)
+static gboolean usbredir_is_channel_ready(void *user_data)
 {
     SpiceUsbredirChannel *channel = SPICE_USBREDIR_CHANNEL(user_data);
     SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    if (spice_channel_get_state(SPICE_CHANNEL(channel)) !=
-            SPICE_CHANNEL_STATE_READY)
-        return;
-
+    if (spice_channel_get_state(SPICE_CHANNEL(channel)) != SPICE_CHANNEL_STATE_READY)
+        return FALSE;
     if (!priv->host)
-        return;
+        return FALSE;
 
-    usbredirhost_write_guest_data(priv->host);
+    return TRUE;
 }
 
-static void usbredir_log(void *user_data, int level, const char *msg)
+static void usbredir_log(void *user_data, const char *msg, gboolean error)
 {
     SpiceUsbredirChannel *channel = user_data;
     SpiceUsbredirChannelPrivate *priv = channel->priv;
 
-    if (priv->catch_error && level == usbredirparser_error) {
-        CHANNEL_DEBUG(channel, "%s", msg);
+    CHANNEL_DEBUG(channel, "%s", msg);
+    if (priv->catch_error && error) {
         /* Remove "usbredirhost: " prefix from usbredirhost messages */
         if (strncmp(msg, "usbredirhost: ", 14) == 0)
             g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
@@ -618,40 +590,8 @@  static void usbredir_log(void *user_data, int level, const char *msg)
         else
             g_set_error_literal(priv->catch_error, SPICE_CLIENT_ERROR,
                                 SPICE_CLIENT_ERROR_FAILED, msg);
-        return;
+        priv->catch_error = NULL;
     }
-
-    switch (level) {
-        case usbredirparser_error:
-            g_critical("%s", msg);
-            break;
-        case usbredirparser_warning:
-            g_warning("%s", msg);
-            break;
-        default:
-            CHANNEL_DEBUG(channel, "%s", msg);
-    }
-}
-
-static int usbredir_read_callback(void *user_data, uint8_t *data, int count)
-{
-    SpiceUsbredirChannel *channel = user_data;
-    SpiceUsbredirChannelPrivate *priv = channel->priv;
-
-    count = MIN(priv->read_buf_size, count);
-
-    if (count != 0) {
-        memcpy(data, priv->read_buf, count);
-    }
-
-    priv->read_buf_size -= count;
-    if (priv->read_buf_size) {
-        priv->read_buf += count;
-    } else {
-        priv->read_buf = NULL;
-    }
-
-    return count;
 }
 
 static void usbredir_free_write_cb_data(uint8_t *data, void *user_data)
@@ -659,7 +599,7 @@  static void usbredir_free_write_cb_data(uint8_t *data, void *user_data)
     SpiceUsbredirChannel *channel = user_data;
     SpiceUsbredirChannelPrivate *priv = channel->priv;
 
-    usbredirhost_free_write_buffer(priv->host, data);
+    spice_usb_backend_return_write_data(priv->host, data);
 }
 
 #ifdef USE_LZ4
@@ -731,7 +671,7 @@  static int usbredir_write_callback(void *user_data, uint8_t *data, int count)
 
 #ifdef USE_LZ4
     if (try_write_compress_LZ4(channel, data, count)) {
-        usbredirhost_free_write_buffer(channel->priv->host, data);
+        spice_usb_backend_return_write_data(channel->priv->host, data);
         return count;
     }
 #endif
@@ -744,15 +684,6 @@  static int usbredir_write_callback(void *user_data, uint8_t *data, int count)
     return count;
 }
 
-static void *usbredir_alloc_lock(void) {
-    GMutex *mutex;
-
-    mutex = g_new0(GMutex, 1);
-    g_mutex_init(mutex);
-
-    return mutex;
-}
-
 G_GNUC_INTERNAL
 void spice_usbredir_channel_lock(SpiceUsbredirChannel *channel)
 {
@@ -765,25 +696,6 @@  void spice_usbredir_channel_unlock(SpiceUsbredirChannel *channel)
     g_mutex_unlock(&channel->priv->device_connect_mutex);
 }
 
-static void usbredir_lock_lock(void *user_data) {
-    GMutex *mutex = user_data;
-
-    g_mutex_lock(mutex);
-}
-
-static void usbredir_unlock_lock(void *user_data) {
-    GMutex *mutex = user_data;
-
-    g_mutex_unlock(mutex);
-}
-
-static void usbredir_free_lock(void *user_data) {
-    GMutex *mutex = user_data;
-
-    g_mutex_clear(mutex);
-    g_free(mutex);
-}
-
 /* --------------------------------------------------------------------- */
 
 typedef struct device_error_data {
@@ -822,7 +734,7 @@  static void spice_usbredir_channel_up(SpiceChannel *c)
 
     g_return_if_fail(priv->host != NULL);
     /* Flush any pending writes */
-    usbredirhost_write_guest_data(priv->host);
+    spice_usb_backend_channel_up(priv->host);
 }
 
 static int try_handle_compressed_msg(SpiceMsgCompressedData *compressed_data_msg,
@@ -872,26 +784,20 @@  static void usbredir_handle_msg(SpiceChannel *c, SpiceMsgIn *in)
 
     g_return_if_fail(priv->host != NULL);
 
-    /* No recursion allowed! */
-    g_return_if_fail(priv->read_buf == NULL);
-
     if (spice_msg_in_type(in) == SPICE_MSG_SPICEVMC_COMPRESSED_DATA) {
         SpiceMsgCompressedData *compressed_data_msg = spice_msg_in_parsed(in);
         if (try_handle_compressed_msg(compressed_data_msg, &buf, &size)) {
-            priv->read_buf_size = size;
-            priv->read_buf = buf;
+            /* uncompressed ok*/
         } else {
-            r = usbredirhost_read_parse_error;
+            r = USB_REDIR_ERROR_READ_PARSE;
         }
     } else { /* Regular SPICE_MSG_SPICEVMC_DATA msg */
         buf = spice_msg_in_raw(in, &size);
-        priv->read_buf_size = size;
-        priv->read_buf = buf;
     }
 
     spice_usbredir_channel_lock(channel);
     if (r == 0)
-        r = usbredirhost_read_guest_data(priv->host);
+        r = spice_usb_backend_provide_read_data(priv->host, buf, size);
     if (r != 0) {
         SpiceUsbDevice *spice_device = priv->spice_device;
         device_error_data err_data;
@@ -905,16 +811,16 @@  static void usbredir_handle_msg(SpiceChannel *c, SpiceMsgIn *in)
 
         desc = spice_usb_device_get_description(spice_device, NULL);
         switch (r) {
-        case usbredirhost_read_parse_error:
+        case USB_REDIR_ERROR_READ_PARSE:
             err = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
                               _("usbredir protocol parse error for %s"), desc);
             break;
-        case usbredirhost_read_device_rejected:
+        case USB_REDIR_ERROR_DEV_REJECTED:
             err = g_error_new(SPICE_CLIENT_ERROR,
                               SPICE_CLIENT_ERROR_USB_DEVICE_REJECTED,
                               _("%s rejected by host"), desc);
             break;
-        case usbredirhost_read_device_lost:
+        case USB_REDIR_ERROR_DEV_LOST:
             err = g_error_new(SPICE_CLIENT_ERROR,
                               SPICE_CLIENT_ERROR_USB_DEVICE_LOST,
                               _("%s disconnected (fatal IO error)"), desc);
diff --git a/src/usb-backend-common.c b/src/usb-backend-common.c
new file mode 100644
index 00000000..6b80a02b
--- /dev/null
+++ b/src/usb-backend-common.c
@@ -0,0 +1,773 @@ 
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+    Copyright (C) 2018 Red Hat, Inc.
+
+    Red Hat Authors:
+    Yuri Benditovich<ybendito@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"
+
+#ifdef USE_USBREDIR
+
+#include <glib-object.h>
+#include <inttypes.h>
+#include <gio/gio.h>
+#include <errno.h>
+#include <libusb.h>
+#include <string.h>
+#include <fcntl.h>
+#include "usbredirhost.h"
+#include "usbredirparser.h"
+#include "spice-util.h"
+#include "usb-backend.h"
+#if defined(G_OS_WIN32)
+#include <windows.h>
+#include "win-usb-dev.h"
+#else
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+#endif
+
+//#define LOUD_DEBUG SPICE_DEBUG
+#define LOUD_DEBUG(x, ...)
+
+static void *g_mutex;
+
+struct _SpiceUsbBackendDevice
+{
+    union
+    {
+        void *libusb_device;
+        void *msc;
+    } d;
+    uint32_t isLibUsb   : 1;
+    uint32_t configured : 1;
+    int refCount;
+    void *mutex;
+    SpiceUsbBackendChannel *attached_to;
+    UsbDeviceInformation device_info;
+};
+
+struct _SpiceUsbBackend
+{
+    libusb_context *libusbContext;
+    usb_hot_plug_callback hp_callback;
+    void *hp_user_data;
+    libusb_hotplug_callback_handle hp_handle;
+    void *dev_change_user_data;
+    backend_device_change_callback dev_change_callback;
+};
+
+/* backend object for device change notification */
+static SpiceUsbBackend *notify_backend;
+
+struct BufferedBulkRead
+{
+    struct usb_redir_bulk_packet_header hout;
+    uint64_t id;
+};
+
+struct _SpiceUsbBackendChannel
+{
+    struct usbredirhost *usbredirhost;
+    struct usbredirhost *hiddenhost;
+    uint8_t *read_buf;
+    int read_buf_size;
+    struct usbredirfilter_rule *rules;
+    int rules_count;
+    uint32_t rejected          : 1;
+    SpiceUsbBackendDevice *attached;
+    SpiceUsbBackendChannelInitData data;
+};
+
+static const char *spice_usbutil_libusb_strerror(enum libusb_error error_code)
+{
+    switch (error_code) {
+    case LIBUSB_SUCCESS:
+        return "Success";
+    case LIBUSB_ERROR_IO:
+        return "Input/output error";
+    case LIBUSB_ERROR_INVALID_PARAM:
+        return "Invalid parameter";
+    case LIBUSB_ERROR_ACCESS:
+        return "Access denied (insufficient permissions)";
+    case LIBUSB_ERROR_NO_DEVICE:
+        return "No such device (it may have been disconnected)";
+    case LIBUSB_ERROR_NOT_FOUND:
+        return "Entity not found";
+    case LIBUSB_ERROR_BUSY:
+        return "Resource busy";
+    case LIBUSB_ERROR_TIMEOUT:
+        return "Operation timed out";
+    case LIBUSB_ERROR_OVERFLOW:
+        return "Overflow";
+    case LIBUSB_ERROR_PIPE:
+        return "Pipe error";
+    case LIBUSB_ERROR_INTERRUPTED:
+        return "System call interrupted (perhaps due to signal)";
+    case LIBUSB_ERROR_NO_MEM:
+        return "Insufficient memory";
+    case LIBUSB_ERROR_NOT_SUPPORTED:
+        return "Operation not supported or unimplemented on this platform";
+    case LIBUSB_ERROR_OTHER:
+        return "Other error";
+    }
+    return "Unknown error";
+}
+
+// lock functions for usbredirhost and usbredirparser
+static void *usbredir_alloc_lock(void) {
+    GMutex *mutex;
+
+    mutex = g_new0(GMutex, 1);
+    g_mutex_init(mutex);
+
+    return mutex;
+}
+
+static void usbredir_free_lock(void *user_data) {
+    GMutex *mutex = user_data;
+
+    g_mutex_clear(mutex);
+    g_free(mutex);
+}
+
+static void usbredir_lock_lock(void *user_data) {
+    GMutex *mutex = user_data;
+
+    g_mutex_lock(mutex);
+}
+
+static void usbredir_unlock_lock(void *user_data) {
+    GMutex *mutex = user_data;
+
+    g_mutex_unlock(mutex);
+}
+
+static gboolean fill_usb_info(SpiceUsbBackendDevice *bdev)
+{
+    UsbDeviceInformation *pi = &bdev->device_info;
+
+    if (bdev->isLibUsb)
+    {
+        struct libusb_device_descriptor desc;
+        libusb_device *libdev = bdev->d.libusb_device;
+        int res = libusb_get_device_descriptor(libdev, &desc);
+        pi->bus = libusb_get_bus_number(libdev);
+        pi->address = libusb_get_device_address(libdev);
+        if (res < 0) {
+            g_warning("cannot get device descriptor for (%p) %d.%d",
+                libdev, pi->bus, pi->address);
+            return FALSE;
+        }
+        pi->vid = desc.idVendor;
+        pi->pid = desc.idProduct;
+        pi->class = desc.bDeviceClass;
+        pi->subclass = desc.bDeviceSubClass;
+        pi->protocol = desc.bDeviceProtocol;
+        pi->isochronous = 0;
+    }
+    return TRUE;
+}
+
+/* Note that this function must be re-entrant safe, as it can get called
+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;
+    gboolean b = ch->data.is_channel_ready(ch->data.user_data);
+    if (b) {
+        if (ch->usbredirhost) {
+            SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch);
+            usbredirhost_write_guest_data(ch->usbredirhost);
+        }
+        else {
+            b = FALSE;
+        }
+    }
+
+    if (!b) {
+        SPICE_DEBUG("%s ch %p (not ready)", __FUNCTION__, ch);
+    }
+}
+
+SpiceUsbBackend *spice_usb_backend_initialize(void)
+{
+    SpiceUsbBackend *be;
+    SPICE_DEBUG("%s >>", __FUNCTION__);
+    if (!g_mutex) {
+        g_mutex = usbredir_alloc_lock();
+    }
+    be = (SpiceUsbBackend *)g_new0(SpiceUsbBackend, 1);
+    if (be) {
+        int rc;
+        rc = libusb_init(&be->libusbContext);
+        if (rc < 0) {
+            const char *desc = spice_usbutil_libusb_strerror(rc);
+            g_warning("Error initializing LIBUSB support: %s [%i]", desc, rc);
+        } else {
+#ifdef G_OS_WIN32
+#if LIBUSB_API_VERSION >= 0x01000106
+    libusb_set_option(be->libusbContext, LIBUSB_OPTION_USE_USBDK);
+#endif
+#endif
+        }
+    }
+    SPICE_DEBUG("%s <<", __FUNCTION__);
+    return be;
+}
+
+gboolean spice_usb_backend_handle_events(SpiceUsbBackend *be)
+{
+    SPICE_DEBUG("%s >>", __FUNCTION__);
+    gboolean b = TRUE;
+    if (be->libusbContext) {
+        SPICE_DEBUG("%s >> libusb", __FUNCTION__);
+        int res = libusb_handle_events(be->libusbContext);
+        if (res && res != LIBUSB_ERROR_INTERRUPTED) {
+            const char *desc = spice_usbutil_libusb_strerror(res);
+            g_warning("Error handling USB events: %s [%i]", desc, res);
+            b = FALSE;
+        }
+        SPICE_DEBUG("%s << libusb %d", __FUNCTION__, res);
+    }
+    else {
+        b = TRUE;
+        g_usleep(1000000);
+    }
+    SPICE_DEBUG("%s <<", __FUNCTION__);
+    return b;
+}
+
+static int LIBUSB_CALL hotplug_callback(libusb_context *ctx,
+    libusb_device *device,
+    libusb_hotplug_event event,
+    void *user_data)
+{
+    SpiceUsbBackend *be = (SpiceUsbBackend *)user_data;
+    if (be->hp_callback) {
+        SpiceUsbBackendDevice *d;
+        gboolean val = event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED;
+        d = g_new0(SpiceUsbBackendDevice, 1);
+        if (d) {
+            d->isLibUsb = 1;
+            d->refCount = 1;
+            d->mutex = g_mutex;
+            d->d.libusb_device = device;
+            if (fill_usb_info(d)) {
+                SPICE_DEBUG("created dev %p, usblib dev %p", d, device);
+                be->hp_callback(be->hp_user_data, d, val);
+            } else {
+                g_free(d);
+            }
+        }
+    }
+    return 0;
+}
+
+gboolean spice_usb_backend_handle_hotplug(SpiceUsbBackend *be,
+                                          void *user_data,
+                                          usb_hot_plug_callback proc)
+{
+    int rc;
+    if (!proc) {
+        if (be->hp_handle) {
+            libusb_hotplug_deregister_callback(be->libusbContext, be->hp_handle);
+            be->hp_handle = 0;
+        }
+        be->hp_callback = proc;
+        return TRUE;
+    }
+
+    be->hp_callback = proc;
+    be->hp_user_data = user_data;
+    if (!be->libusbContext) {
+        // it is acceptable if libusb is not available at all
+        return TRUE;
+    }
+    rc = libusb_hotplug_register_callback(be->libusbContext,
+        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+        LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY,
+        LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
+        hotplug_callback, be, &be->hp_handle);
+    if (rc != LIBUSB_SUCCESS) {
+        const char *desc = spice_usbutil_libusb_strerror(rc);
+        g_warning("Error initializing USB hotplug support: %s [%i]", desc, rc);
+        be->hp_callback = NULL;
+        return FALSE;
+    }
+    return TRUE;
+}
+
+void spice_usb_backend_set_device_change_callback(
+    SpiceUsbBackend *be, void *user_data, backend_device_change_callback proc)
+{
+    be->dev_change_user_data = user_data;
+    be->dev_change_callback = proc;
+    if (!notify_backend) {
+        notify_backend = be;
+    }
+}
+
+void spice_usb_backend_finalize(SpiceUsbBackend *be)
+{
+    SPICE_DEBUG("%s >>", __FUNCTION__);
+    if (be->libusbContext) {
+        libusb_exit(be->libusbContext);
+    }
+    g_free(be);
+    SPICE_DEBUG("%s <<", __FUNCTION__);
+}
+
+SpiceUsbBackendDevice **spice_usb_backend_get_device_list(SpiceUsbBackend *be)
+{
+    LOUD_DEBUG("%s >>", __FUNCTION__);
+    libusb_device **devlist = NULL, **dev;
+    SpiceUsbBackendDevice *d, **list;
+
+    int n = 0, index;
+
+    if (be->libusbContext) {
+        libusb_get_device_list(be->libusbContext, &devlist);
+    }
+
+    // add all the libusb device that not present in our list
+    for (dev = devlist; dev && *dev; dev++) {
+        n++;
+    }
+
+    list = g_new0(SpiceUsbBackendDevice*, n);
+    if (!list) {
+        libusb_free_device_list(devlist, 1);
+        return NULL;
+    }
+
+    index = 0;
+
+    for (dev = devlist; dev && *dev; dev++) {
+        d = g_new0(SpiceUsbBackendDevice, 1);
+        if (d) {
+            d->isLibUsb = 1;
+            d->refCount = 1;
+            d->mutex = g_mutex;
+            d->d.libusb_device = *dev;
+            if (index >= n || !fill_usb_info(d)) {
+                g_free(d);
+                libusb_unref_device(*dev);
+            }
+            else {
+                SPICE_DEBUG("created dev %p, usblib dev %p", d, *dev);
+                list[index++] = d;
+            }
+        }
+    }
+
+    if (devlist) {
+        libusb_free_device_list(devlist, 0);
+    }
+
+    LOUD_DEBUG("%s <<", __FUNCTION__);
+    return list;
+}
+
+gboolean spice_usb_backend_device_is_hub(SpiceUsbBackendDevice *dev)
+{
+    return dev->device_info.class == LIBUSB_CLASS_HUB;
+}
+
+static uint8_t is_libusb_isochronous(libusb_device *libdev)
+{
+    struct libusb_config_descriptor *conf_desc;
+    uint8_t isoc_found = FALSE;
+    gint i, j, k;
+
+    if (!libdev) {
+        SPICE_DEBUG("%s - unexpected libdev = 0", __FUNCTION__);
+        return 0;
+    }
+
+    if (libusb_get_active_config_descriptor(libdev, &conf_desc) != 0) {
+        SPICE_DEBUG("%s - no active configuration for libdev %p", __FUNCTION__, libdev);
+        return 0;
+    }
+
+    for (i = 0; !isoc_found && i < conf_desc->bNumInterfaces; i++) {
+        for (j = 0; !isoc_found && j < conf_desc->interface[i].num_altsetting; j++) {
+            for (k = 0; !isoc_found && k < conf_desc->interface[i].altsetting[j].bNumEndpoints;k++) {
+                gint attributes = conf_desc->interface[i].altsetting[j].endpoint[k].bmAttributes;
+                gint type = attributes & LIBUSB_TRANSFER_TYPE_MASK;
+                if (type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
+                    isoc_found = TRUE;
+            }
+        }
+    }
+
+    libusb_free_config_descriptor(conf_desc);
+    return isoc_found;
+}
+
+const UsbDeviceInformation*  spice_usb_backend_device_get_info(SpiceUsbBackendDevice *dev)
+{
+    dev->device_info.isochronous = dev->isLibUsb ? is_libusb_isochronous(dev->d.libusb_device) : 0;
+    return &dev->device_info;
+}
+
+gboolean spice_usb_backend_devices_same(SpiceUsbBackendDevice *dev1,
+                                        SpiceUsbBackendDevice *dev2)
+{
+    if (dev1->isLibUsb != dev2->isLibUsb) {
+        return FALSE;
+    }
+    if (dev1->isLibUsb) {
+        return dev1->d.libusb_device == dev2->d.libusb_device;
+    }
+    // assuming CD redir devices are static
+    return dev1 == dev2;
+}
+
+gconstpointer spice_usb_backend_device_get_libdev(SpiceUsbBackendDevice *dev)
+{
+    if (dev->isLibUsb) {
+        return dev->d.libusb_device;
+    }
+    return NULL;
+}
+
+void spice_usb_backend_free_device_list(SpiceUsbBackendDevice **devlist)
+{
+    LOUD_DEBUG("%s >>", __FUNCTION__);
+    SpiceUsbBackendDevice **dev;
+    for (dev = devlist; *dev; dev++) {
+        SpiceUsbBackendDevice *d = *dev;
+        spice_usb_backend_device_release(d);
+    }
+    g_free(devlist);
+    LOUD_DEBUG("%s <<", __FUNCTION__);
+}
+
+void spice_usb_backend_device_acquire(SpiceUsbBackendDevice *dev)
+{
+    void *mutex = dev->mutex;
+    LOUD_DEBUG("%s >> %p", __FUNCTION__, dev);
+    usbredir_lock_lock(mutex);
+    if (dev->isLibUsb) {
+        libusb_ref_device(dev->d.libusb_device);
+    }
+    dev->refCount++;
+    usbredir_unlock_lock(mutex);
+}
+
+void spice_usb_backend_device_release(SpiceUsbBackendDevice *dev)
+{
+    void *mutex = dev->mutex;
+    LOUD_DEBUG("%s >> %p(%d)", __FUNCTION__, dev, dev->refCount);
+    usbredir_lock_lock(mutex);
+    if (dev->isLibUsb) {
+        libusb_unref_device(dev->d.libusb_device);
+        dev->refCount--;
+        if (dev->refCount == 0) {
+            LOUD_DEBUG("%s freeing %p (libusb %p)", __FUNCTION__, dev, dev->d.libusb_device);
+            g_free(dev);
+        }
+    }
+    else {
+        dev->refCount--;
+    }
+    usbredir_unlock_lock(mutex);
+    LOUD_DEBUG("%s <<", __FUNCTION__);
+}
+
+gboolean spice_usb_backend_device_need_thread(SpiceUsbBackendDevice *dev)
+{
+    gboolean b = dev->isLibUsb != 0;
+    SPICE_DEBUG("%s << %d", __FUNCTION__, b);
+    return b;
+}
+
+int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev,
+                                          const struct usbredirfilter_rule *rules,
+                                          int count)
+{
+    if (dev->isLibUsb) {
+        return usbredirhost_check_device_filter(
+            rules, count, dev->d.libusb_device, 0);
+    }
+    g_assert_not_reached();
+}
+
+static int usbredir_read_callback(void *user_data, uint8_t *data, int count)
+{
+    SpiceUsbBackendChannel *ch = user_data;
+
+    count = MIN(ch->read_buf_size, count);
+
+    if (count != 0) {
+        memcpy(data, ch->read_buf, count);
+    }
+
+    ch->read_buf_size -= count;
+    if (ch->read_buf_size) {
+        ch->read_buf += count;
+    }
+    else {
+        ch->read_buf = NULL;
+    }
+    SPICE_DEBUG("%s ch %p, %d bytes", __FUNCTION__, ch, count);
+
+    return count;
+}
+
+static void usbredir_log(void *user_data, int level, const char *msg)
+{
+    SpiceUsbBackendChannel *ch = (SpiceUsbBackendChannel *)user_data;
+
+    switch (level) {
+    case usbredirparser_error:
+        g_critical("%s", msg);
+        ch->data.log(ch->data.user_data, msg, TRUE);
+        break;
+    case usbredirparser_warning:
+        g_warning("%s", msg);
+        ch->data.log(ch->data.user_data, msg, TRUE);
+        break;
+    default:
+        ch->data.log(ch->data.user_data, msg, FALSE);
+        break;
+    }
+}
+
+static int usbredir_write_callback(void *user_data, uint8_t *data, int count)
+{
+    SpiceUsbBackendChannel *ch = user_data;
+    int res;
+    SPICE_DEBUG("%s ch %p, %d bytes", __FUNCTION__, ch, count);
+    res = ch->data.write_callback(ch->data.user_data, data, count);
+    return res;
+}
+
+#if USBREDIR_VERSION >= 0x000701
+static uint64_t usbredir_buffered_output_size_callback(void *user_data)
+{
+    SpiceUsbBackendChannel *ch = user_data;
+    return ch->data.get_queue_size(ch->data.user_data);
+}
+#endif
+
+int spice_usb_backend_provide_read_data(SpiceUsbBackendChannel *ch, uint8_t *data, int count)
+{
+    int res = 0;
+    if (!ch->read_buf) {
+        typedef int(*readproc_t)(void *);
+        readproc_t fn = NULL;
+        void *param;
+        ch->read_buf = data;
+        ch->read_buf_size = count;
+        if (ch->usbredirhost) {
+            fn = (readproc_t)usbredirhost_read_guest_data;
+            param = ch->usbredirhost;
+        }
+        res = fn ? fn(param) : USB_REDIR_ERROR_IO;
+        switch (res)
+        {
+        case usbredirhost_read_io_error:
+            res = USB_REDIR_ERROR_IO;
+            break;
+        case usbredirhost_read_parse_error:
+            res = USB_REDIR_ERROR_READ_PARSE;
+            break;
+        case usbredirhost_read_device_rejected:
+            res = USB_REDIR_ERROR_DEV_REJECTED;
+            break;
+        case usbredirhost_read_device_lost:
+            res = USB_REDIR_ERROR_DEV_LOST;
+            break;
+        }
+        SPICE_DEBUG("%s ch %p, %d bytes, res %d", __FUNCTION__, ch, count, res);
+
+    } else {
+        res = USB_REDIR_ERROR_READ_PARSE;
+        SPICE_DEBUG("%s ch %p, %d bytes, already has data", __FUNCTION__, ch, count);
+    }
+    if (ch->rejected) {
+        ch->rejected = 0;
+        res = USB_REDIR_ERROR_DEV_REJECTED;
+    }
+    return res;
+}
+
+gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, SpiceUsbBackendDevice *dev, const char **msg)
+{
+    const char *dummy;
+    if (!msg) {
+        msg = &dummy;
+    }
+    SPICE_DEBUG("%s >> ch %p, dev %p (was attached %p)", __FUNCTION__, ch, dev, ch->attached);
+    gboolean b = FALSE;
+    if (dev && dev->isLibUsb) {
+        libusb_device_handle *handle = NULL;
+        int rc = libusb_open(dev->d.libusb_device, &handle);
+        b = rc == 0 && handle;
+        if (b) {
+            if (!ch->usbredirhost) {
+                ch->usbredirhost = ch->hiddenhost;
+            }
+            rc = usbredirhost_set_device(ch->usbredirhost, handle);
+            if (rc) {
+                SPICE_DEBUG("%s ch %p, dev %p usbredirhost error %d", __FUNCTION__, ch, dev, rc);
+                b = FALSE;
+            } else {
+                ch->attached = dev;
+                dev->attached_to = ch;
+            }
+        } else {
+            const char *desc = spice_usbutil_libusb_strerror(rc);
+            g_warning("Error libusb_open: %s [%i]", desc, rc);
+            *msg = desc;
+        }
+    } else if (!dev) {
+        SPICE_DEBUG("%s intentional sleep", __FUNCTION__);
+        g_usleep(100000);
+        if (ch->usbredirhost) {
+            // it will call libusb_close internally
+            usbredirhost_set_device(ch->usbredirhost, NULL);
+        }
+        SPICE_DEBUG("%s ch %p, detach done", __FUNCTION__, ch);
+        ch->attached->attached_to = NULL;
+        ch->attached = NULL;
+        b = TRUE;
+    }
+    return b;
+}
+
+void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data)
+{
+    typedef void(*retdata)(void *, void *);
+    retdata fn = NULL;
+    void *param;
+    if (ch->usbredirhost) {
+        fn = (retdata)usbredirhost_free_write_buffer;
+        param = ch->usbredirhost;
+    }
+    if (fn) {
+        SPICE_DEBUG("%s ch %p", __FUNCTION__, ch);
+        fn(param, data);
+    } else {
+        SPICE_DEBUG("%s ch %p - NOBODY TO CALL", __FUNCTION__, ch);
+    }
+}
+
+
+SpiceUsbBackendChannel *spice_usb_backend_channel_initialize(
+    SpiceUsbBackend *be,
+    const SpiceUsbBackendChannelInitData *init_data)
+{
+    SpiceUsbBackendChannel *ch = g_new0(SpiceUsbBackendChannel, 1);
+    SPICE_DEBUG("%s >>", __FUNCTION__);
+    gboolean ok = FALSE;
+    if (ch) {
+        ch->data = *init_data;
+        ch->hiddenhost = !be->libusbContext ? NULL :
+            usbredirhost_open_full(
+                be->libusbContext,
+                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,
+                init_data->debug ? usbredirparser_debug : usbredirparser_warning,
+                usbredirhost_fl_write_cb_owns_buffer);
+        ok = be->libusbContext == NULL || ch->hiddenhost != NULL;
+        if (ch->hiddenhost) {
+#if USBREDIR_VERSION >= 0x000701
+            usbredirhost_set_buffered_output_size_cb(ch->hiddenhost, usbredir_buffered_output_size_callback);
+#endif
+        }
+    }
+
+    if (ok) {
+        ch->usbredirhost = ch->hiddenhost;
+    }
+
+    if (ch && !ok) {
+        g_error("Out of memory allocating usbredir or parser");
+        if (ch->hiddenhost) {
+            usbredirhost_close(ch->hiddenhost);
+        }
+        g_free(ch);
+        ch = NULL;
+    }
+    SPICE_DEBUG("%s << %p", __FUNCTION__, ch);
+    return ch;
+}
+
+void spice_usb_backend_channel_up(SpiceUsbBackendChannel *ch)
+{
+    SPICE_DEBUG("%s %p, host %p", __FUNCTION__, ch, ch->usbredirhost);
+    if (ch->usbredirhost) {
+        usbredirhost_write_guest_data(ch->usbredirhost);
+    }}
+
+void spice_usb_backend_channel_finalize(SpiceUsbBackendChannel *ch)
+{
+    SPICE_DEBUG("%s >> %p", __FUNCTION__, ch);
+    if (ch->usbredirhost) {
+        usbredirhost_close(ch->usbredirhost);
+    }
+
+    if (ch->rules) {
+        // is it ok to g_free the memory that was allocated by parser?
+        g_free(ch->rules);
+    }
+
+    g_free(ch);
+    SPICE_DEBUG("%s << %p", __FUNCTION__, ch);
+}
+
+void spice_usb_backend_channel_get_guest_filter(
+    SpiceUsbBackendChannel *ch,
+    const struct usbredirfilter_rule **r,
+    int *count)
+{
+    int i;
+    *r = NULL;
+    *count = 0;
+    if (ch->usbredirhost) {
+        usbredirhost_get_guest_filter(ch->usbredirhost, r, count);
+    }
+    if (*r == NULL) {
+        *r = ch->rules;
+        *count = ch->rules_count;
+    }
+
+    if (*count) {
+        SPICE_DEBUG("%s ch %p: %d filters", __FUNCTION__, ch, *count);
+    }
+    for (i = 0; i < *count; i++) {
+        const struct usbredirfilter_rule *ra = *r;
+        SPICE_DEBUG("%s class %d, %X:%X",
+            ra[i].allow ? "allowed" : "denied", ra[i].device_class,
+            (uint32_t)ra[i].vendor_id, (uint32_t)ra[i].product_id);
+    }
+}
+
+#endif // USB_REDIR
diff --git a/src/usb-backend.h b/src/usb-backend.h
new file mode 100644
index 00000000..a0247b58
--- /dev/null
+++ b/src/usb-backend.h
@@ -0,0 +1,98 @@ 
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+    Copyright (C) 2018 Red Hat, Inc.
+
+    Red Hat Authors:
+    Yuri Benditovich<ybendito@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/>.
+*/
+
+#ifndef __SPICE_USB_BACKEND_H__
+#define __SPICE_USB_BACKEND_H__
+
+#include <usbredirfilter.h>
+#include "usb-device-manager.h"
+
+G_BEGIN_DECLS
+
+typedef struct _SpiceUsbBackend SpiceUsbBackend;
+typedef struct _SpiceUsbBackendDevice SpiceUsbBackendDevice;
+typedef struct _SpiceUsbBackendChannel SpiceUsbBackendChannel;
+
+typedef struct _UsbDeviceInformation
+{
+    uint16_t bus;
+    uint16_t address;
+    uint16_t vid;
+    uint16_t pid;
+    uint8_t class;
+    uint8_t subclass;
+    uint8_t protocol;
+    uint8_t isochronous;
+} UsbDeviceInformation;
+
+typedef struct _SpiceUsbBackendChannelInitData
+{
+    void *user_data;
+    void (*log)(void *user_data, const char *msg, gboolean error);
+    int (*write_callback)(void *user_data, uint8_t *data, int count);
+    int (*is_channel_ready)(void *user_data);
+    uint64_t (*get_queue_size)(void *user_data);
+    gboolean debug;
+} SpiceUsbBackendChannelInitData;
+
+typedef void(*usb_hot_plug_callback)(
+    void *user_data, SpiceUsbBackendDevice *dev, gboolean added);
+
+typedef void(*backend_device_change_callback)(
+    void *user_data, SpiceUsbBackendDevice *dev);
+
+enum {
+    USB_REDIR_ERROR_IO = -1,
+    USB_REDIR_ERROR_READ_PARSE = -2,
+    USB_REDIR_ERROR_DEV_REJECTED = -3,
+    USB_REDIR_ERROR_DEV_LOST = -4,
+};
+
+SpiceUsbBackend *spice_usb_backend_initialize(void);
+gboolean spice_usb_backend_handle_events(SpiceUsbBackend *);
+gboolean spice_usb_backend_handle_hotplug(SpiceUsbBackend *, void *user_data, usb_hot_plug_callback proc);
+void spice_usb_backend_set_device_change_callback(SpiceUsbBackend *, void *user_data, backend_device_change_callback proc);
+void spice_usb_backend_finalize(SpiceUsbBackend *context);
+// returns NULL-terminated array of SpiceUsbBackendDevice *
+SpiceUsbBackendDevice **spice_usb_backend_get_device_list(SpiceUsbBackend *backend);
+gboolean spice_usb_backend_device_is_hub(SpiceUsbBackendDevice *dev);
+gboolean spice_usb_backend_device_need_thread(SpiceUsbBackendDevice *dev);
+void spice_usb_backend_free_device_list(SpiceUsbBackendDevice **devlist);
+void spice_usb_backend_device_acquire(SpiceUsbBackendDevice *dev);
+void spice_usb_backend_device_release(SpiceUsbBackendDevice *dev);
+gboolean spice_usb_backend_devices_same(SpiceUsbBackendDevice *dev1, SpiceUsbBackendDevice *dev2);
+gconstpointer spice_usb_backend_device_get_libdev(SpiceUsbBackendDevice *dev);
+const UsbDeviceInformation *spice_usb_backend_device_get_info(SpiceUsbBackendDevice *dev);
+// returns 0 if the device passes the filter
+int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev, const struct usbredirfilter_rule *rules, int count);
+
+SpiceUsbBackendChannel *spice_usb_backend_channel_initialize(SpiceUsbBackend *context, const SpiceUsbBackendChannelInitData *init_data);
+// returns 0 for success or error code
+int spice_usb_backend_provide_read_data(SpiceUsbBackendChannel *ch, uint8_t *data, int count);
+gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, SpiceUsbBackendDevice *dev, const char **msg);
+void spice_usb_backend_channel_up(SpiceUsbBackendChannel *ch);
+void spice_usb_backend_channel_get_guest_filter(SpiceUsbBackendChannel *ch, const struct usbredirfilter_rule  **rules, int *count);
+void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data);
+void spice_usb_backend_channel_finalize(SpiceUsbBackendChannel *ch);
+
+G_END_DECLS
+
+#endif
diff --git a/src/usb-device-manager-priv.h b/src/usb-device-manager-priv.h
index 83884d7c..53149fb4 100644
--- a/src/usb-device-manager-priv.h
+++ b/src/usb-device-manager-priv.h
@@ -32,9 +32,12 @@  void spice_usb_device_manager_stop_event_listening(
     SpiceUsbDeviceManager *manager);
 
 #ifdef USE_USBREDIR
-#include <libusb.h>
+#include "usb-backend.h"
 void spice_usb_device_manager_device_error(
     SpiceUsbDeviceManager *manager, SpiceUsbDevice *device, GError *err);
+void spice_usb_device_manager_check_redir_on_connect(
+    SpiceUsbDeviceManager *manager, SpiceChannel *channel);
+
 
 guint8 spice_usb_device_get_busnum(const SpiceUsbDevice *device);
 guint8 spice_usb_device_get_devaddr(const SpiceUsbDevice *device);
diff --git a/src/usb-device-manager.c b/src/usb-device-manager.c
index 50fb4919..82418f63 100644
--- a/src/usb-device-manager.c
+++ b/src/usb-device-manager.c
@@ -24,10 +24,11 @@ 
 #include <glib-object.h>
 
 #ifdef USE_USBREDIR
+
 #include <errno.h>
-#include <libusb.h>
 
 #ifdef G_OS_WIN32
+#include <windows.h>
 #include "usbdk_api.h"
 #endif
 
@@ -41,8 +42,8 @@ 
 #endif
 
 #include "channel-usbredir-priv.h"
-#include "usbredirhost.h"
 #include "usbutil.h"
+
 #endif
 
 #include "spice-session-priv.h"
@@ -102,7 +103,7 @@  struct _SpiceUsbDeviceManagerPrivate {
     gchar *auto_connect_filter;
     gchar *redirect_on_connect;
 #ifdef USE_USBREDIR
-    libusb_context *context;
+    SpiceUsbBackend *context;
     int event_listeners;
     GThread *event_thread;
     gint event_thread_run;
@@ -112,10 +113,9 @@  struct _SpiceUsbDeviceManagerPrivate {
     int redirect_on_connect_rules_count;
 #ifdef USE_GUDEV
     GUdevClient *udev;
-    libusb_device **coldplug_list; /* Avoid needless reprobing during init */
+    SpiceUsbBackendDevice **coldplug_list; /* Avoid needless reprobing during init */
 #else
     gboolean redirecting; /* Handled by GUdevClient in the gudev case */
-    libusb_hotplug_callback_handle hp_handle;
 #endif
 #ifdef G_OS_WIN32
     usbdk_api_wrapper     *usbdk_api;
@@ -139,6 +139,7 @@  enum {
 
 #ifdef USE_USBREDIR
 
+// this is the structure behind SpiceUsbDevice
 typedef struct _SpiceUsbDeviceInfo {
     guint8  busnum;
     guint8  devaddr;
@@ -148,7 +149,7 @@  typedef struct _SpiceUsbDeviceInfo {
 #ifdef G_OS_WIN32
     guint8  state;
 #else
-    libusb_device *libdev;
+    SpiceUsbBackendDevice *bdev;
 #endif
     gint    ref;
 } SpiceUsbDeviceInfo;
@@ -166,15 +167,12 @@  static void spice_usb_device_manager_uevent_cb(GUdevClient     *client,
 static void spice_usb_device_manager_add_udev(SpiceUsbDeviceManager  *self,
                                               GUdevDevice            *udev);
 #else
-static int spice_usb_device_manager_hotplug_cb(libusb_context       *ctx,
-                                               libusb_device        *device,
-                                               libusb_hotplug_event  event,
-                                               void                 *data);
+static void spice_usb_device_manager_hotplug_cb(void *data,
+                                                SpiceUsbBackendDevice *bdev,
+                                                gboolean added);
 #endif
-static void spice_usb_device_manager_check_redir_on_connect(
-    SpiceUsbDeviceManager *self, SpiceChannel *channel);
 
-static SpiceUsbDeviceInfo *spice_usb_device_new(libusb_device *libdev);
+static SpiceUsbDeviceInfo *spice_usb_device_new(SpiceUsbBackendDevice *bdev);
 static SpiceUsbDevice *spice_usb_device_ref(SpiceUsbDevice *device);
 static void spice_usb_device_unref(SpiceUsbDevice *device);
 
@@ -183,12 +181,12 @@  static void _usbdk_hider_update(SpiceUsbDeviceManager *manager);
 static void _usbdk_hider_clear(SpiceUsbDeviceManager *manager);
 #endif
 
-static gboolean spice_usb_manager_device_equal_libdev(SpiceUsbDeviceManager *manager,
-                                                      SpiceUsbDevice *device,
-                                                      libusb_device *libdev);
-static libusb_device *
-spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
-                                          SpiceUsbDevice *device);
+static gboolean spice_usb_manager_device_equal_bdev(SpiceUsbDeviceManager *manager,
+                                                    SpiceUsbDevice *device,
+                                                    SpiceUsbBackendDevice *bdev);
+static SpiceUsbBackendDevice*
+spice_usb_device_manager_device_to_bdev(SpiceUsbDeviceManager *self,
+                                        SpiceUsbDevice *device);
 
 static void
 _spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
@@ -288,27 +286,16 @@  static gboolean spice_usb_device_manager_initable_init(GInitable  *initable,
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
     GList *list;
     GList *it;
-    int rc;
 #ifdef USE_GUDEV
     const gchar *const subsystems[] = {"usb", NULL};
 #endif
 
-    /* Initialize libusb */
-    rc = libusb_init(&priv->context);
-    if (rc < 0) {
-        const char *desc = spice_usbutil_libusb_strerror(rc);
-        g_warning("Error initializing USB support: %s [%i]", desc, rc);
-        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                    "Error initializing USB support: %s [%i]", desc, rc);
+    /* Initialize spice backend */
+    priv->context = spice_usb_backend_initialize();
+    if (!priv->context) {
         return FALSE;
     }
 
-#ifdef G_OS_WIN32
-#if LIBUSB_API_VERSION >= 0x01000106
-    libusb_set_option(priv->context, LIBUSB_OPTION_USE_USBDK);
-#endif
-#endif
-
     /* Start listening for usb devices plug / unplug */
 #ifdef USE_GUDEV
     priv->udev = g_udev_client_new(subsystems);
@@ -319,26 +306,20 @@  static gboolean spice_usb_device_manager_initable_init(GInitable  *initable,
     g_signal_connect(G_OBJECT(priv->udev), "uevent",
                      G_CALLBACK(spice_usb_device_manager_uevent_cb), self);
     /* Do coldplug (detection of already connected devices) */
-    libusb_get_device_list(priv->context, &priv->coldplug_list);
+    priv->coldplug_list = spice_usb_backend_get_device_list(priv->context);
     list = g_udev_client_query_by_subsystem(priv->udev, "usb");
     for (it = g_list_first(list); it; it = g_list_next(it)) {
         spice_usb_device_manager_add_udev(self, it->data);
         g_object_unref(it->data);
     }
     g_list_free(list);
-    libusb_free_device_list(priv->coldplug_list, 1);
+    spice_usb_backend_free_device_list(priv->coldplug_list);
     priv->coldplug_list = NULL;
 #else
-    rc = libusb_hotplug_register_callback(priv->context,
-        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
-        LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY,
-        LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
-        spice_usb_device_manager_hotplug_cb, self, &priv->hp_handle);
-    if (rc < 0) {
-        const char *desc = spice_usbutil_libusb_strerror(rc);
-        g_warning("Error initializing USB hotplug support: %s [%i]", desc, rc);
+    if (!spice_usb_backend_handle_hotplug(priv->context,
+                                          self, spice_usb_device_manager_hotplug_cb)) {
         g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
-                  "Error initializing USB hotplug support: %s [%i]", desc, rc);
+                    "Error initializing USB hotplug support");
         return FALSE;
     }
     spice_usb_device_manager_start_event_listening(self, NULL);
@@ -369,20 +350,20 @@  static void spice_usb_device_manager_dispose(GObject *gobject)
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
 
 #ifdef USE_LIBUSB_HOTPLUG
-    if (priv->hp_handle) {
-        spice_usb_device_manager_stop_event_listening(self);
-        if (g_atomic_int_get(&priv->event_thread_run)) {
-            /* Force termination of the event thread even if there were some
-             * mismatched spice_usb_device_manager_{start,stop}_event_listening
-             * calls. Otherwise, the usb event thread will be leaked, and will
-             * try to use the libusb context we destroy in finalize(), which would
-             * cause a crash */
-             g_warn_if_reached();
-             g_atomic_int_set(&priv->event_thread_run, FALSE);
-        }
-        /* This also wakes up the libusb_handle_events() in the event_thread */
-        libusb_hotplug_deregister_callback(priv->context, priv->hp_handle);
-        priv->hp_handle = 0;
+    // TODO: check in case the initial spice_usb_backend_handle_hotplug fails
+
+    spice_usb_device_manager_stop_event_listening(self);
+    if (g_atomic_int_get(&priv->event_thread_run)) {
+        /* Force termination of the event thread even if there were some
+            * mismatched spice_usb_device_manager_{start,stop}_event_listening
+            * calls. Otherwise, the usb event thread will be leaked, and will
+            * try to use the libusb context we destroy in finalize(), which would
+            * cause a crash */
+            g_warn_if_reached();
+            g_atomic_int_set(&priv->event_thread_run, FALSE);
+
+    /* This also wakes up the libusb_handle_events() in the event_thread */
+    spice_usb_backend_handle_hotplug(priv->context, NULL, NULL);
     }
 #endif
     if (priv->event_thread) {
@@ -411,8 +392,9 @@  static void spice_usb_device_manager_finalize(GObject *gobject)
     g_clear_object(&priv->udev);
 #endif
     g_return_if_fail(priv->event_thread == NULL);
-    if (priv->context)
-        libusb_exit(priv->context);
+    if (priv->context) {
+        spice_usb_backend_finalize(priv->context);
+    }
     free(priv->auto_conn_filter_rules);
     free(priv->redirect_on_connect_rules);
 #ifdef G_OS_WIN32
@@ -737,7 +719,7 @@  static void spice_usb_device_manager_class_init(SpiceUsbDeviceManagerClass *klas
 #ifdef USE_USBREDIR
 
 /* ------------------------------------------------------------------ */
-/* gudev / libusb Helper functions                                    */
+/* gudev / backend Helper functions                                    */
 
 #ifdef USE_GUDEV
 static gboolean spice_usb_device_manager_get_udev_bus_n_address(
@@ -761,40 +743,16 @@  static gboolean spice_usb_device_manager_get_udev_bus_n_address(
 }
 #endif
 
-static gboolean spice_usb_device_manager_get_device_descriptor(
-    libusb_device *libdev,
-    struct libusb_device_descriptor *desc)
-{
-    int errcode;
-    const gchar *errstr;
-
-    g_return_val_if_fail(libdev != NULL, FALSE);
-    g_return_val_if_fail(desc   != NULL, FALSE);
-
-    errcode = libusb_get_device_descriptor(libdev, desc);
-    if (errcode < 0) {
-        int bus, addr;
-
-        bus = libusb_get_bus_number(libdev);
-        addr = libusb_get_device_address(libdev);
-        errstr = spice_usbutil_libusb_strerror(errcode);
-        g_warning("cannot get device descriptor for (%p) %d.%d -- %s(%d)",
-                  libdev, bus, addr, errstr, errcode);
-        return FALSE;
-    }
-    return TRUE;
-}
-
 #endif // USE_USBREDIR
 
 /**
  * spice_usb_device_get_libusb_device:
- * @device: #SpiceUsbDevice to get the descriptor information of
+ * @device: #SpiceUsbDevice to get the libusb device of (if exists)
  *
  * Finds the %libusb_device associated with the @device.
  *
- * Returns: (transfer none): the %libusb_device associated to %SpiceUsbDevice.
- *
+ * Returns: (transfer none): the %libusb_device associated to %SpiceUsbDevice
+ *    or NULL (if the device does not have associated libusb device)
  * Since: 0.27
  **/
 gconstpointer
@@ -806,34 +764,13 @@  spice_usb_device_get_libusb_device(const SpiceUsbDevice *device G_GNUC_UNUSED)
 
     g_return_val_if_fail(info != NULL, FALSE);
 
-    return info->libdev;
+    return spice_usb_backend_device_get_libdev(info->bdev);
 #endif
 #endif
     return NULL;
 }
 
 #ifdef USE_USBREDIR
-static gboolean spice_usb_device_manager_get_libdev_vid_pid(
-    libusb_device *libdev, int *vid, int *pid)
-{
-    struct libusb_device_descriptor desc;
-
-    g_return_val_if_fail(libdev != NULL, FALSE);
-    g_return_val_if_fail(vid != NULL, FALSE);
-    g_return_val_if_fail(pid != NULL, FALSE);
-
-    *vid = *pid = 0;
-
-    if (!spice_usb_device_manager_get_device_descriptor(libdev, &desc)) {
-        return FALSE;
-    }
-    *vid = desc.idVendor;
-    *pid = desc.idProduct;
-
-    return TRUE;
-}
-
-/* ------------------------------------------------------------------ */
 /* callbacks                                                          */
 
 static void channel_new(SpiceSession *session, SpiceChannel *channel,
@@ -849,10 +786,8 @@  static void channel_new(SpiceSession *session, SpiceChannel *channel,
     spice_channel_connect(channel);
     g_ptr_array_add(self->priv->channels, channel);
 
-    spice_usb_device_manager_check_redir_on_connect(self, channel);
-
     /*
-     * add a reference to ourself, to make sure the libusb context is
+     * add a reference to ourself, to make sure the backend device context is
      * alive as long as the channel is.
      * TODO: moving to gusb could help here too.
      */
@@ -889,6 +824,7 @@  static void spice_usb_device_manager_auto_connect_cb(GObject      *gobject,
         g_signal_emit(self, signals[AUTO_CONNECT_FAILED], 0, device, err);
         g_error_free(err);
     }
+
     spice_usb_device_unref(device);
 }
 
@@ -902,12 +838,12 @@  spice_usb_device_manager_device_match(SpiceUsbDeviceManager *self, SpiceUsbDevic
 
 #ifdef USE_GUDEV
 static gboolean
-spice_usb_device_manager_libdev_match(SpiceUsbDeviceManager *self, libusb_device *libdev,
+spice_usb_device_manager_bdev_match(SpiceUsbDeviceManager *self, SpiceUsbBackendDevice *dev,
                                       const int bus, const int address)
 {
+    const UsbDeviceInformation* info = spice_usb_backend_device_get_info(dev);
     /* match functions for Linux/UsbDk -- match by bus.addr */
-    return (libusb_get_bus_number(libdev) == bus &&
-            libusb_get_device_address(libdev) == address);
+    return (info->bus == bus && info->address == address);
 }
 #endif
 
@@ -930,20 +866,16 @@  spice_usb_device_manager_find_device(SpiceUsbDeviceManager *self,
 }
 
 static void spice_usb_device_manager_add_dev(SpiceUsbDeviceManager  *self,
-                                             libusb_device          *libdev)
+                                             SpiceUsbBackendDevice  *bdev)
 {
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    struct libusb_device_descriptor desc;
     SpiceUsbDevice *device;
 
-    if (!spice_usb_device_manager_get_device_descriptor(libdev, &desc))
-        return;
-
     /* Skip hubs */
-    if (desc.bDeviceClass == LIBUSB_CLASS_HUB)
+    if (spice_usb_backend_device_is_hub(bdev))
         return;
 
-    device = (SpiceUsbDevice*)spice_usb_device_new(libdev);
+    device = (SpiceUsbDevice*)spice_usb_device_new(bdev);
     if (!device)
         return;
 
@@ -955,10 +887,9 @@  static void spice_usb_device_manager_add_dev(SpiceUsbDeviceManager  *self,
         can_redirect = spice_usb_device_manager_can_redirect_device(
                                         self, device, NULL);
 
-        auto_ok = usbredirhost_check_device_filter(
-                            priv->auto_conn_filter_rules,
-                            priv->auto_conn_filter_rules_count,
-                            libdev, 0) == 0;
+        auto_ok = spice_usb_backend_device_check_filter(bdev,
+                                                        priv->auto_conn_filter_rules,
+                                                        priv->auto_conn_filter_rules_count) == 0;
 
         if (can_redirect && auto_ok)
             spice_usb_device_manager_connect_device_async(self,
@@ -1005,7 +936,7 @@  static void spice_usb_device_manager_add_udev(SpiceUsbDeviceManager  *self,
                                               GUdevDevice            *udev)
 {
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    libusb_device *libdev = NULL, **dev_list = NULL;
+    SpiceUsbBackendDevice *devarrived = NULL, **dev_list = NULL;
     SpiceUsbDevice *device;
     const gchar *devtype;
     int i, bus, address;
@@ -1033,23 +964,23 @@  static void spice_usb_device_manager_add_udev(SpiceUsbDeviceManager  *self,
     if (priv->coldplug_list)
         dev_list = priv->coldplug_list;
     else
-        libusb_get_device_list(priv->context, &dev_list);
+        dev_list = spice_usb_backend_get_device_list(priv->context);
 
     for (i = 0; dev_list && dev_list[i]; i++) {
-        if (spice_usb_device_manager_libdev_match(self, dev_list[i], bus, address)) {
-            libdev = dev_list[i];
+        if (spice_usb_device_manager_bdev_match(self, dev_list[i], bus, address)) {
+            devarrived = dev_list[i];
             break;
         }
     }
 
-    if (libdev)
-        spice_usb_device_manager_add_dev(self, libdev);
+    if (devarrived)
+        spice_usb_device_manager_add_dev(self, devarrived);
     else
         g_warning("Could not find USB device to add " DEV_ID_FMT,
                   (guint) bus, (guint) address);
 
     if (!priv->coldplug_list)
-        libusb_free_device_list(dev_list, 1);
+        spice_usb_backend_free_device_list(dev_list);
 }
 
 static void spice_usb_device_manager_remove_udev(SpiceUsbDeviceManager  *self,
@@ -1078,8 +1009,8 @@  static void spice_usb_device_manager_uevent_cb(GUdevClient     *client,
 #else
 struct hotplug_idle_cb_args {
     SpiceUsbDeviceManager *self;
-    libusb_device *device;
-    libusb_hotplug_event event;
+    SpiceUsbBackendDevice *device;
+    gboolean device_added;
 };
 
 static gboolean spice_usb_device_manager_hotplug_idle_cb(gpointer user_data)
@@ -1087,36 +1018,33 @@  static gboolean spice_usb_device_manager_hotplug_idle_cb(gpointer user_data)
     struct hotplug_idle_cb_args *args = user_data;
     SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(args->self);
 
-    switch (args->event) {
-    case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
+    if (args->device_added) {
         spice_usb_device_manager_add_dev(self, args->device);
-        break;
-    case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
+    } else {
+        const UsbDeviceInformation *info = spice_usb_backend_device_get_info(args->device);
         spice_usb_device_manager_remove_dev(self,
-                                    libusb_get_bus_number(args->device),
-                                    libusb_get_device_address(args->device));
-        break;
+                                            info->bus,
+                                            info->address);
     }
-    libusb_unref_device(args->device);
+    spice_usb_backend_device_release(args->device);
     g_object_unref(self);
     g_free(args);
     return FALSE;
 }
 
 /* Can be called from both the main-thread as well as the event_thread */
-static int spice_usb_device_manager_hotplug_cb(libusb_context       *ctx,
-                                               libusb_device        *device,
-                                               libusb_hotplug_event  event,
-                                               void                 *user_data)
+static void spice_usb_device_manager_hotplug_cb(void *user_data,
+                                                SpiceUsbBackendDevice *bdev,
+                                                gboolean added)
 {
     SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
     struct hotplug_idle_cb_args *args = g_malloc0(sizeof(*args));
 
     args->self = g_object_ref(self);
-    args->device = libusb_ref_device(device);
-    args->event = event;
+    spice_usb_backend_device_acquire(bdev);
+    args->device_added = added;
+    args->device = bdev;
     g_idle_add(spice_usb_device_manager_hotplug_idle_cb, args);
-    return 0;
 }
 #endif // USE_USBREDIR
 
@@ -1143,13 +1071,9 @@  static gpointer spice_usb_device_manager_usb_ev_thread(gpointer user_data)
 {
     SpiceUsbDeviceManager *self = SPICE_USB_DEVICE_MANAGER(user_data);
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    int rc;
 
     while (g_atomic_int_get(&priv->event_thread_run)) {
-        rc = libusb_handle_events(priv->context);
-        if (rc && rc != LIBUSB_ERROR_INTERRUPTED) {
-            const char *desc = spice_usbutil_libusb_strerror(rc);
-            g_warning("Error handling USB events: %s [%i]", desc, rc);
+        if (!spice_usb_backend_handle_events(priv->context)) {
             break;
         }
     }
@@ -1194,13 +1118,13 @@  void spice_usb_device_manager_stop_event_listening(
         g_atomic_int_set(&priv->event_thread_run, FALSE);
 }
 
-static void spice_usb_device_manager_check_redir_on_connect(
+void spice_usb_device_manager_check_redir_on_connect(
     SpiceUsbDeviceManager *self, SpiceChannel *channel)
 {
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
     GTask *task;
     SpiceUsbDevice *device;
-    libusb_device *libdev;
+    SpiceUsbBackendDevice *dev;
     guint i;
 
     if (priv->redirect_on_connect == NULL)
@@ -1212,15 +1136,15 @@  static void spice_usb_device_manager_check_redir_on_connect(
         if (spice_usb_device_manager_is_device_connected(self, device))
             continue;
 
-        libdev = spice_usb_device_manager_device_to_libdev(self, device);
+        dev = spice_usb_device_manager_device_to_bdev(self, device);
 #ifdef G_OS_WIN32
-        if (libdev == NULL)
+        if (dev == NULL)
             continue;
 #endif
-        if (usbredirhost_check_device_filter(
-                            priv->redirect_on_connect_rules,
-                            priv->redirect_on_connect_rules_count,
-                            libdev, 0) == 0) {
+        if (spice_usb_backend_device_check_filter(
+            dev,
+            priv->redirect_on_connect_rules,
+            priv->redirect_on_connect_rules_count) == 0) {
             /* Note: re-uses spice_usb_device_manager_connect_device_async's
                completion handling code! */
             task = g_task_new(self,
@@ -1230,14 +1154,14 @@  static void spice_usb_device_manager_check_redir_on_connect(
 
             spice_usbredir_channel_connect_device_async(
                                SPICE_USBREDIR_CHANNEL(channel),
-                               libdev, device, NULL,
+                               dev, device, NULL,
                                spice_usb_device_manager_channel_connect_cb,
                                task);
-            libusb_unref_device(libdev);
+            spice_usb_backend_device_release(dev);
             return; /* We've taken the channel! */
         }
 
-        libusb_unref_device(libdev);
+        spice_usb_backend_device_release(dev);
     }
 }
 
@@ -1261,8 +1185,8 @@  static SpiceUsbredirChannel *spice_usb_device_manager_get_channel_for_dev(
     for (i = 0; i < priv->channels->len; i++) {
         SpiceUsbredirChannel *channel = g_ptr_array_index(priv->channels, i);
         spice_usbredir_channel_lock(channel);
-        libusb_device *libdev = spice_usbredir_channel_get_device(channel);
-        if (spice_usb_manager_device_equal_libdev(manager, device, libdev)) {
+        SpiceUsbBackendDevice *dev = spice_usbredir_channel_get_device(channel);
+        if (spice_usb_manager_device_equal_bdev(manager, device, dev)) {
             spice_usbredir_channel_unlock(channel);
             return channel;
         }
@@ -1319,13 +1243,13 @@  GPtrArray* spice_usb_device_manager_get_devices_with_filter(
         SpiceUsbDevice *device = g_ptr_array_index(priv->devices, i);
 
         if (rules) {
-            libusb_device *libdev =
-                spice_usb_device_manager_device_to_libdev(self, device);
+            SpiceUsbBackendDevice *bdev =
+                spice_usb_device_manager_device_to_bdev(self, device);
 #ifdef G_OS_WIN32
-            if (libdev == NULL)
+            if (bdev == NULL)
                 continue;
 #endif
-            if (usbredirhost_check_device_filter(rules, count, libdev, 0) != 0)
+            if (spice_usb_backend_device_check_filter(bdev, rules, count) != 0)
                 continue;
         }
         g_ptr_array_add(devices_copy, spice_usb_device_ref(device));
@@ -1399,7 +1323,7 @@  _spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
     task = g_task_new(self, cancellable, callback, user_data);
 
     SpiceUsbDeviceManagerPrivate *priv = self->priv;
-    libusb_device *libdev;
+    SpiceUsbBackendDevice *bdev;
     guint i;
 
     if (spice_usb_device_manager_is_device_connected(self, device)) {
@@ -1415,9 +1339,9 @@  _spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
         if (spice_usbredir_channel_get_device(channel))
             continue; /* Skip already used channels */
 
-        libdev = spice_usb_device_manager_device_to_libdev(self, device);
+        bdev = spice_usb_device_manager_device_to_bdev(self, device);
 #ifdef G_OS_WIN32
-        if (libdev == NULL) {
+        if (bdev == NULL) {
             /* Most likely, the device was plugged out at driver installation
              * time, and its remove-device event was ignored.
              * So remove the device now
@@ -1435,12 +1359,12 @@  _spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
         }
 #endif
         spice_usbredir_channel_connect_device_async(channel,
-                                 libdev,
+                                 bdev,
                                  device,
                                  cancellable,
                                  spice_usb_device_manager_channel_connect_cb,
                                  task);
-        libusb_unref_device(libdev);
+        spice_usb_backend_device_release(bdev);
         return;
     }
 
@@ -1702,20 +1626,20 @@  spice_usb_device_manager_can_redirect_device(SpiceUsbDeviceManager  *self,
 
     if (guest_filter_rules) {
         gboolean filter_ok;
-        libusb_device *libdev;
+        SpiceUsbBackendDevice *bdev;
 
-        libdev = spice_usb_device_manager_device_to_libdev(self, device);
+        bdev = spice_usb_device_manager_device_to_bdev(self, device);
 #ifdef G_OS_WIN32
-        if (libdev == NULL) {
+        if (bdev == NULL) {
             g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
                                 _("Some USB devices were not found"));
             return FALSE;
         }
 #endif
-        filter_ok = (usbredirhost_check_device_filter(
-                            guest_filter_rules, guest_filter_rules_count,
-                            libdev, 0) == 0);
-        libusb_unref_device(libdev);
+        filter_ok = (spice_usb_backend_device_check_filter(
+                            bdev,
+                            guest_filter_rules, guest_filter_rules_count) == 0);
+        spice_usb_backend_device_release(bdev);
         if (!filter_ok) {
             g_set_error_literal(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
                                 _("Some USB devices are blocked by host policy"));
@@ -1806,64 +1730,30 @@  gchar *spice_usb_device_get_description(SpiceUsbDevice *device, const gchar *for
 #endif
 }
 
-
-
 #ifdef USE_USBREDIR
-static gboolean probe_isochronous_endpoint(libusb_device *libdev)
-{
-    struct libusb_config_descriptor *conf_desc;
-    gboolean isoc_found = FALSE;
-    gint i, j, k;
-
-    g_return_val_if_fail(libdev != NULL, FALSE);
-
-    if (libusb_get_active_config_descriptor(libdev, &conf_desc) != 0) {
-        g_return_val_if_reached(FALSE);
-    }
-
-    for (i = 0; !isoc_found && i < conf_desc->bNumInterfaces; i++) {
-        for (j = 0; !isoc_found && j < conf_desc->interface[i].num_altsetting; j++) {
-            for (k = 0; !isoc_found && k < conf_desc->interface[i].altsetting[j].bNumEndpoints;k++) {
-                gint attributes = conf_desc->interface[i].altsetting[j].endpoint[k].bmAttributes;
-                gint type = attributes & LIBUSB_TRANSFER_TYPE_MASK;
-                if (type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
-                    isoc_found = TRUE;
-            }
-        }
-    }
-
-    libusb_free_config_descriptor(conf_desc);
-    return isoc_found;
-}
 
 /*
  * SpiceUsbDeviceInfo
  */
-static SpiceUsbDeviceInfo *spice_usb_device_new(libusb_device *libdev)
+static SpiceUsbDeviceInfo *spice_usb_device_new(SpiceUsbBackendDevice *bdev)
 {
     SpiceUsbDeviceInfo *info;
-    int vid, pid;
-    guint8 bus, addr;
+    const UsbDeviceInformation *devinfo;
 
-    g_return_val_if_fail(libdev != NULL, NULL);
-
-    bus = libusb_get_bus_number(libdev);
-    addr = libusb_get_device_address(libdev);
-
-    if (!spice_usb_device_manager_get_libdev_vid_pid(libdev, &vid, &pid)) {
-        return NULL;
-    }
+    g_return_val_if_fail(bdev != NULL, NULL);
+    devinfo = spice_usb_backend_device_get_info(bdev);
 
     info = g_new0(SpiceUsbDeviceInfo, 1);
 
-    info->busnum  = bus;
-    info->devaddr = addr;
-    info->vid = vid;
-    info->pid = pid;
+    info->busnum  = devinfo->bus;
+    info->devaddr = devinfo->address;
+    info->vid = devinfo->vid;
+    info->pid = devinfo->pid;
     info->ref = 1;
-    info->isochronous = probe_isochronous_endpoint(libdev);
+    info->isochronous = devinfo->isochronous;
 #ifndef G_OS_WIN32
-    info->libdev = libusb_ref_device(libdev);
+    info->bdev = bdev;
+    spice_usb_backend_device_acquire(bdev);
 #endif
 
     return info;
@@ -2001,49 +1891,51 @@  static void spice_usb_device_unref(SpiceUsbDevice *device)
     ref_count_is_0 = g_atomic_int_dec_and_test(&info->ref);
     if (ref_count_is_0) {
 #ifndef G_OS_WIN32
-        libusb_unref_device(info->libdev);
+        spice_usb_backend_device_release(info->bdev);
 #endif
+        info->vid = info->pid = 0;
+        SPICE_DEBUG("%s: deleting %p", __FUNCTION__, info);
         g_free(info);
     }
 }
 
 #ifndef G_OS_WIN32 /* Linux -- directly compare libdev */
 static gboolean
-spice_usb_manager_device_equal_libdev(SpiceUsbDeviceManager *manager,
-                                      SpiceUsbDevice *device,
-                                      libusb_device  *libdev)
+spice_usb_manager_device_equal_bdev(SpiceUsbDeviceManager *manager,
+                                    SpiceUsbDevice *device,
+                                    SpiceUsbBackendDevice *bdev)
 {
     SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
 
-    if ((device == NULL) || (libdev == NULL))
+    if ((device == NULL) || (bdev == NULL))
         return FALSE;
 
-    return info->libdev == libdev;
+    return spice_usb_backend_devices_same(info->bdev, bdev);
 }
 #else /* Windows -- compare vid:pid of device and libdev */
 static gboolean
-spice_usb_manager_device_equal_libdev(SpiceUsbDeviceManager *manager,
-                                      SpiceUsbDevice *device,
-                                      libusb_device  *libdev)
+spice_usb_manager_device_equal_bdev(SpiceUsbDeviceManager *manager,
+                                    SpiceUsbDevice *device,
+                                    SpiceUsbBackendDevice  *bdev)
 {
     int busnum, devaddr;
 
-    if ((device == NULL) || (libdev == NULL))
+    if ((device == NULL) || (bdev == NULL))
         return FALSE;
 
     busnum = spice_usb_device_get_busnum(device);
     devaddr = spice_usb_device_get_devaddr(device);
-    return spice_usb_device_manager_libdev_match(manager, libdev,
-                                                 busnum, devaddr);
+    return spice_usb_device_manager_bdev_match(manager, bdev,
+                                               busnum, devaddr);
 }
 #endif
 
 /*
- * Caller must libusb_unref_device the libusb_device returned by this function.
- * Returns a libusb_device, or NULL upon failure
+ * Caller must spice_usb_backend_device_release the SpiceUsbBackendDevice returned by this function.
+ * Returns a SpiceUsbBackendDevice, or NULL upon failure
  */
-static libusb_device *
-spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
+static SpiceUsbBackendDevice *
+spice_usb_device_manager_device_to_bdev(SpiceUsbDeviceManager *self,
                                           SpiceUsbDevice *device)
 {
 #ifdef G_OS_WIN32
@@ -2054,7 +1946,7 @@  spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
      * driver swap we do under windows invalidates the cached libdev.
      */
 
-    libusb_device *d, **devlist;
+    SpiceUsbBackendDevice *d, **devlist;
     int i;
 
     g_return_val_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self), NULL);
@@ -2062,18 +1954,18 @@  spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
     g_return_val_if_fail(self->priv != NULL, NULL);
     g_return_val_if_fail(self->priv->context != NULL, NULL);
 
-    libusb_get_device_list(self->priv->context, &devlist);
+    devlist = spice_usb_backend_get_device_list(self->priv->context);
     if (!devlist)
         return NULL;
 
     for (i = 0; (d = devlist[i]) != NULL; i++) {
-        if (spice_usb_manager_device_equal_libdev(self, device, d)) {
-            libusb_ref_device(d);
+        if (spice_usb_manager_device_equal_bdev(self, device, d)) {
+            spice_usb_backend_device_acquire(d);
             break;
         }
     }
 
-    libusb_free_device_list(devlist, 1);
+    spice_usb_backend_free_device_list(devlist);
 
     return d;
 
@@ -2081,7 +1973,8 @@  spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
     /* Simply return a ref to the cached libdev */
     SpiceUsbDeviceInfo *info = (SpiceUsbDeviceInfo *)device;
 
-    return libusb_ref_device(info->libdev);
+    spice_usb_backend_device_acquire(info->bdev);
+    return info->bdev;
 #endif
 }
 #endif /* USE_USBREDIR */
diff --git a/src/usb-device-manager.h b/src/usb-device-manager.h
index 773208f1..9ac38884 100644
--- a/src/usb-device-manager.h
+++ b/src/usb-device-manager.h
@@ -87,12 +87,13 @@  struct _SpiceUsbDeviceManagerClass
                                  SpiceUsbDevice *device, GError *error);
     void (*device_error) (SpiceUsbDeviceManager *manager,
                           SpiceUsbDevice *device, GError *error);
+
     /*< private >*/
     /*
      * If adding fields to this struct, remove corresponding
      * amount of padding to avoid changing overall struct size
      */
-    gchar _spice_reserved[SPICE_RESERVED_PADDING];
+    gchar _spice_reserved[SPICE_RESERVED_PADDING - 1 * sizeof(void *)];
 };
 
 GType spice_usb_device_get_type(void);
diff --git a/src/usbutil.c b/src/usbutil.c
index e96ab11f..5052ef36 100644
--- a/src/usbutil.c
+++ b/src/usbutil.c
@@ -58,42 +58,6 @@  static GMutex usbids_load_mutex;
 static int usbids_vendor_count = 0; /* < 0: failed, 0: empty, > 0: loaded */
 static usb_vendor_info *usbids_vendor_info = NULL;
 
-G_GNUC_INTERNAL
-const char *spice_usbutil_libusb_strerror(enum libusb_error error_code)
-{
-    switch (error_code) {
-    case LIBUSB_SUCCESS:
-        return "Success";
-    case LIBUSB_ERROR_IO:
-        return "Input/output error";
-    case LIBUSB_ERROR_INVALID_PARAM:
-        return "Invalid parameter";
-    case LIBUSB_ERROR_ACCESS:
-        return "Access denied (insufficient permissions)";
-    case LIBUSB_ERROR_NO_DEVICE:
-        return "No such device (it may have been disconnected)";
-    case LIBUSB_ERROR_NOT_FOUND:
-        return "Entity not found";
-    case LIBUSB_ERROR_BUSY:
-        return "Resource busy";
-    case LIBUSB_ERROR_TIMEOUT:
-        return "Operation timed out";
-    case LIBUSB_ERROR_OVERFLOW:
-        return "Overflow";
-    case LIBUSB_ERROR_PIPE:
-        return "Pipe error";
-    case LIBUSB_ERROR_INTERRUPTED:
-        return "System call interrupted (perhaps due to signal)";
-    case LIBUSB_ERROR_NO_MEM:
-        return "Insufficient memory";
-    case LIBUSB_ERROR_NOT_SUPPORTED:
-        return "Operation not supported or unimplemented on this platform";
-    case LIBUSB_ERROR_OTHER:
-        return "Other error";
-    }
-    return "Unknown error";
-}
-
 #ifdef __linux__
 /* <Sigh> libusb does not allow getting the manufacturer and product strings
    without opening the device, so grab them directly from sysfs */
diff --git a/src/win-usb-dev.c b/src/win-usb-dev.c
index 9a130a39..d5dd2d2a 100644
--- a/src/win-usb-dev.c
+++ b/src/win-usb-dev.c
@@ -23,11 +23,13 @@ 
 #include "config.h"
 
 #include <windows.h>
-#include <libusb.h>
 #include "win-usb-dev.h"
 #include "spice-marshal.h"
 #include "spice-util.h"
 #include "usbutil.h"
+#include "usb-backend.h"
+
+#define USB_CLASS_HUB   9
 
 enum {
     PROP_0,
@@ -35,7 +37,7 @@  enum {
 };
 
 struct _GUdevClientPrivate {
-    libusb_context *ctx;
+    SpiceUsbBackend *ctx;
     GList *udev_list;
     HWND hwnd;
     gboolean redirecting;
@@ -85,7 +87,7 @@  static GUdevClient *singleton = NULL;
 
 static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo);
 static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
-static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo);
+static gboolean get_usb_dev_info(SpiceUsbBackendDevice *dev, GUdevDeviceInfo *udevinfo);
 
 //uncomment to debug gudev device lists.
 //#define DEBUG_GUDEV_DEVICE_LISTS
@@ -122,8 +124,7 @@  static ssize_t
 g_udev_client_list_devices(GUdevClient *self, GList **devs,
                            GError **err, const gchar *name)
 {
-    gssize rc;
-    libusb_device **lusb_list, **dev;
+    SpiceUsbBackendDevice **lusb_list, **dev;
     GUdevClientPrivate *priv;
     GUdevDeviceInfo *udevinfo;
     GUdevDevice *udevice;
@@ -136,13 +137,8 @@  g_udev_client_list_devices(GUdevClient *self, GList **devs,
 
     g_return_val_if_fail(self->priv->ctx != NULL, -3);
 
-    rc = libusb_get_device_list(priv->ctx, &lusb_list);
-    if (rc < 0) {
-        const char *errstr = spice_usbutil_libusb_strerror(rc);
-        g_warning("%s: libusb_get_device_list failed - %s", name, errstr);
-        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
-                    "%s: Error getting device list from libusb: %s [%"G_GSSIZE_FORMAT"]",
-                    name, errstr, rc);
+    lusb_list = spice_usb_backend_get_device_list(priv->ctx);
+    if (!lusb_list) {
         return -4;
     }
 
@@ -158,7 +154,7 @@  g_udev_client_list_devices(GUdevClient *self, GList **devs,
         *devs = g_list_prepend(*devs, udevice);
         n++;
     }
-    libusb_free_device_list(lusb_list, 1);
+    spice_usb_backend_free_device_list(lusb_list);
 
     return n;
 }
@@ -180,7 +176,6 @@  g_udev_client_initable_init(GInitable *initable, GCancellable *cancellable,
     GUdevClient *self;
     GUdevClientPrivate *priv;
     WNDCLASS wcls;
-    int rc;
 
     g_return_val_if_fail(G_UDEV_IS_CLIENT(initable), FALSE);
     g_return_val_if_fail(cancellable == NULL, FALSE);
@@ -188,19 +183,10 @@  g_udev_client_initable_init(GInitable *initable, GCancellable *cancellable,
     self = G_UDEV_CLIENT(initable);
     priv = self->priv;
 
-    rc = libusb_init(&priv->ctx);
-    if (rc < 0) {
-        const char *errstr = spice_usbutil_libusb_strerror(rc);
-        g_warning("Error initializing USB support: %s [%i]", errstr, rc);
-        g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
-                    "Error initializing USB support: %s [%i]", errstr, rc);
+    priv->ctx = spice_usb_backend_initialize();
+    if (!priv->ctx) {
         return FALSE;
     }
-#ifdef G_OS_WIN32
-#if LIBUSB_API_VERSION >= 0x01000106
-    libusb_set_option(priv->ctx, LIBUSB_OPTION_USE_USBDK);
-#endif
-#endif
 
     /* get initial device list */
     if (g_udev_client_list_devices(self, &priv->udev_list, err, __FUNCTION__) < 0) {
@@ -267,7 +253,7 @@  static void g_udev_client_finalize(GObject *gobject)
 
     /* free libusb context initializing by libusb_init() */
     g_warn_if_fail(priv->ctx != NULL);
-    libusb_exit(priv->ctx);
+    spice_usb_backend_finalize(priv->ctx);
 
     /* Chain up to the parent class */
     if (G_OBJECT_CLASS(g_udev_client_parent_class)->finalize)
@@ -356,23 +342,18 @@  static void g_udev_client_class_init(GUdevClientClass *klass)
     g_object_class_install_property(gobject_class, PROP_REDIRECTING, pspec);
 }
 
-static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo)
+static gboolean get_usb_dev_info(SpiceUsbBackendDevice *dev, GUdevDeviceInfo *udevinfo)
 {
-    struct libusb_device_descriptor desc;
+    const UsbDeviceInformation* info = spice_usb_backend_device_get_info(dev);
 
     g_return_val_if_fail(dev, FALSE);
     g_return_val_if_fail(udevinfo, FALSE);
 
-    if (libusb_get_device_descriptor(dev, &desc) < 0) {
-        g_warning("cannot get device descriptor %p", dev);
-        return FALSE;
-    }
-
-    udevinfo->bus   = libusb_get_bus_number(dev);
-    udevinfo->addr  = libusb_get_device_address(dev);
-    udevinfo->class = desc.bDeviceClass;
-    udevinfo->vid   = desc.idVendor;
-    udevinfo->pid   = desc.idProduct;
+    udevinfo->bus = info->bus;
+    udevinfo->addr = info->address;
+    udevinfo->class = info->class;
+    udevinfo->vid   = info->vid;
+    udevinfo->pid   = info->pid;
     snprintf(udevinfo->sclass, sizeof(udevinfo->sclass), "%d", udevinfo->class);
     snprintf(udevinfo->sbus,   sizeof(udevinfo->sbus),   "%d", udevinfo->bus);
     snprintf(udevinfo->saddr,  sizeof(udevinfo->saddr),  "%d", udevinfo->addr);
@@ -573,7 +554,14 @@  static gboolean g_udev_skip_search(GUdevDevice *udev)
 #if defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x010000FF)
             (udevinfo->addr == 1) || /* root hub addr for libusbx >= 1.0.13 */
 #endif
-            (udevinfo->class == LIBUSB_CLASS_HUB) || /* hub*/
+            (udevinfo->class == USB_CLASS_HUB) || /* hub*/
             (udevinfo->addr == 0)); /* bad address */
     return skip;
 }
+
+void spice_usb_backend_indicate_dev_change(void)
+{
+    GUdevClient *client = g_udev_client_new(NULL);
+    GUdevClientPrivate *priv = client->priv;
+    PostMessage(priv->hwnd, WM_DEVICECHANGE, 0, 0);
+}
diff --git a/src/win-usb-dev.h b/src/win-usb-dev.h
index b5c4fce9..ff12aac3 100644
--- a/src/win-usb-dev.h
+++ b/src/win-usb-dev.h
@@ -104,6 +104,8 @@  typedef enum
     G_UDEV_CLIENT_WINAPI_FAILED
 } GUdevClientError;
 
+/* callback to indicate creation of new simulated device */
+void spice_usb_backend_indicate_dev_change(void);
 
 G_END_DECLS
 

Comments

Hi

On Fri, Sep 21, 2018 at 9:10 PM, Christophe Fergeau <cfergeau@redhat.com>
wrote:

> On Mon, Sep 17, 2018 at 04:22:50PM +0300, Yuri Benditovich wrote:
> > Changes from v3:
> > Single commit with all new files split to 12 patches
> > to make patch review easier
>
> What you did is not splitting at all, adding one new file per commit,
> and then all the modifications to the existing code in a final commit
> is not what we meant when we asked for split patches.
>
> You asked why this is needed, and actually, it turns out your patches
> introduce a regression in virt-viewer even when not exercising at all
> any of the new code. The series as it is is not bisectable in any way,
> so I basically have to look at these 9000 lines of code to try to figure
> out what broke...
>

IMO, the best thing to do in this case is to let me know about the
regression
(there is no written test procedure AFAIK, so I could miss something in my
tests)

Thanks,
Yuri


>
> If the series was split, and if we'd start with some refactoring
> (reworking existing code without introducing any behaviour change), and
> then
> the introduction of the new features, then git would at least be able to
> tell me if it's the refactoring which introduced the bug, or the new
> feature. And I'd have less code to look at to figure out what's going
> on...
>
> I've started to do some splitting, see attached patch (win32 not tested
> at all, a quick usb redirection test on linux was working). I'd probably
> go further though, and remove isLibUsb for now, and maybe also the
> checks for LibusbContext being non-NULL. I did not do it for now as it
> would create lots of conflicts :)
>
> While on the topic of things which would cause conflicts, I'd really
> prefer if we did not use so much one and two letter variable names (ch,
> be, b, ..), this makes the code harder to read than it should.
> g_new0 will never return NULL, so you can remove the extra indentations
> in code like:
> foo = g_new0()
> if (foo) {
> }
>
> I would also add a spice_usb_backend_channel_detach in addition to
> _attach. _attach is currently also doing the _detach, but it's making
> things much less readable. Same comment about spice_usb_backend_handle_
> hotplug
> which could be split in enable_hotplug/disable_hotplug
>
> All your SPICE_DEBUG calls include a __FUNCTION__, but it seems
> SPICE_DEBUG already does that for you?
>
> #define SPICE_DEBUG(fmt, ...)                                   \
>     do {                                                        \
>         if (G_UNLIKELY(spice_util_get_debug()))                 \
>             g_debug(G_STRLOC " " fmt, ## __VA_ARGS__);          \
>     } while (0)
>
> with
>
> #define G_STRLOC __FILE__ ":" G_STRINGIFY (__LINE__) ":"
> __PRETTY_FUNCTION__ "()"
>
> The "usbredirhost" stripping which is done in
> channel-usbredir.c:usbredir_log should be moved to the equivalent
> function in usb-backend-common.c
>
> Christophe
>
On Sat, Sep 22, 2018 at 12:14 AM, Yuri Benditovich <
yuri.benditovich@daynix.com> wrote:

> Hi
>
> On Fri, Sep 21, 2018 at 9:10 PM, Christophe Fergeau <cfergeau@redhat.com>
> wrote:
>
>> On Mon, Sep 17, 2018 at 04:22:50PM +0300, Yuri Benditovich wrote:
>> > Changes from v3:
>> > Single commit with all new files split to 12 patches
>> > to make patch review easier
>>
>> What you did is not splitting at all, adding one new file per commit,
>> and then all the modifications to the existing code in a final commit
>> is not what we meant when we asked for split patches.
>>
>> You asked why this is needed, and actually, it turns out your patches
>> introduce a regression in virt-viewer even when not exercising at all
>> any of the new code. The series as it is is not bisectable in any way,
>> so I basically have to look at these 9000 lines of code to try to figure
>> out what broke...
>>
>
BTW I'm sorry to write it, but for the record the patch is not 9000 lines,
if is less than 2500 lines including all.
I believe if you describe the kind of regression I am able to find root
cause
of it quickly.

Thanks,
Yuri



>
> IMO, the best thing to do in this case is to let me know about the
> regression
> (there is no written test procedure AFAIK, so I could miss something in my
> tests)
>
> Thanks,
> Yuri
>
>
>>
>> If the series was split, and if we'd start with some refactoring
>> (reworking existing code without introducing any behaviour change), and
>> then
>> the introduction of the new features, then git would at least be able to
>> tell me if it's the refactoring which introduced the bug, or the new
>> feature. And I'd have less code to look at to figure out what's going
>> on...
>>
>> I've started to do some splitting, see attached patch (win32 not tested
>> at all, a quick usb redirection test on linux was working). I'd probably
>> go further though, and remove isLibUsb for now, and maybe also the
>> checks for LibusbContext being non-NULL. I did not do it for now as it
>> would create lots of conflicts :)
>>
>> While on the topic of things which would cause conflicts, I'd really
>> prefer if we did not use so much one and two letter variable names (ch,
>> be, b, ..), this makes the code harder to read than it should.
>> g_new0 will never return NULL, so you can remove the extra indentations
>> in code like:
>> foo = g_new0()
>> if (foo) {
>> }
>>
>> I would also add a spice_usb_backend_channel_detach in addition to
>> _attach. _attach is currently also doing the _detach, but it's making
>> things much less readable. Same comment about
>> spice_usb_backend_handle_hotplug
>> which could be split in enable_hotplug/disable_hotplug
>>
>> All your SPICE_DEBUG calls include a __FUNCTION__, but it seems
>> SPICE_DEBUG already does that for you?
>>
>> #define SPICE_DEBUG(fmt, ...)                                   \
>>     do {                                                        \
>>         if (G_UNLIKELY(spice_util_get_debug()))                 \
>>             g_debug(G_STRLOC " " fmt, ## __VA_ARGS__);          \
>>     } while (0)
>>
>> with
>>
>> #define G_STRLOC __FILE__ ":" G_STRINGIFY (__LINE__) ":"
>> __PRETTY_FUNCTION__ "()"
>>
>> The "usbredirhost" stripping which is done in
>> channel-usbredir.c:usbredir_log should be moved to the equivalent
>> function in usb-backend-common.c
>>
>> Christophe
>>
>
>
On Sat, Sep 22, 2018 at 12:21:04AM +0300, Yuri Benditovich wrote:
> On Sat, Sep 22, 2018 at 12:14 AM, Yuri Benditovich <
> yuri.benditovich@daynix.com> wrote:
> 
> > Hi
> >
> > On Fri, Sep 21, 2018 at 9:10 PM, Christophe Fergeau <cfergeau@redhat.com>
> > wrote:
> >
> >> On Mon, Sep 17, 2018 at 04:22:50PM +0300, Yuri Benditovich wrote:
> >> > Changes from v3:
> >> > Single commit with all new files split to 12 patches
> >> > to make patch review easier
> >>
> >> What you did is not splitting at all, adding one new file per commit,
> >> and then all the modifications to the existing code in a final commit
> >> is not what we meant when we asked for split patches.
> >>
> >> You asked why this is needed, and actually, it turns out your patches
> >> introduce a regression in virt-viewer even when not exercising at all
> >> any of the new code. The series as it is is not bisectable in any way,
> >> so I basically have to look at these 9000 lines of code to try to figure
> >> out what broke...
> >>
> >
> BTW I'm sorry to write it, but for the record the patch is not 9000 lines,
> if is less than 2500 lines including all.

cd-scsi.c itself which I assume to be new code is 2700 lines, the new
widget is 2000 lines, usb-backend-common.c is 2000 lines. I'm taking the
9000 lines figure from your cover letter by the way..
https://lists.freedesktop.org/archives/spice-devel/2018-September/045576.html
 26 files changed, 9321 insertions(+), 520 deletions(-)


> I believe if you describe the kind of regression I am able to find root
> cause of it quickly.

I'm sure you can, but if you are the only one who can do that, that's a
maintainance concern. The issue is most likely caused by a leak of the
usb channel (or a leak from it). Start latest virt-viewer to connect to
a VM (probably with USB redir channels configured), exit it, and see it
hangs.

Christophe
On Mon, Sep 24, 2018 at 4:32 PM Christophe Fergeau <cfergeau@redhat.com>
wrote:

> On Sat, Sep 22, 2018 at 12:21:04AM +0300, Yuri Benditovich wrote:
> > On Sat, Sep 22, 2018 at 12:14 AM, Yuri Benditovich <
> > yuri.benditovich@daynix.com> wrote:
> >
> > > Hi
> > >
> > > On Fri, Sep 21, 2018 at 9:10 PM, Christophe Fergeau <
> cfergeau@redhat.com>
> > > wrote:
> > >
> > >> On Mon, Sep 17, 2018 at 04:22:50PM +0300, Yuri Benditovich wrote:
> > >> > Changes from v3:
> > >> > Single commit with all new files split to 12 patches
> > >> > to make patch review easier
> > >>
> > >> What you did is not splitting at all, adding one new file per commit,
> > >> and then all the modifications to the existing code in a final commit
> > >> is not what we meant when we asked for split patches.
> > >>
> > >> You asked why this is needed, and actually, it turns out your patches
> > >> introduce a regression in virt-viewer even when not exercising at all
> > >> any of the new code. The series as it is is not bisectable in any way,
> > >> so I basically have to look at these 9000 lines of code to try to
> figure
> > >> out what broke...
> > >>
> > >
> > BTW I'm sorry to write it, but for the record the patch is not 9000
> lines,
> > if is less than 2500 lines including all.
>
> cd-scsi.c itself which I assume to be new code is 2700 lines, the new
> widget is 2000 lines, usb-backend-common.c is 2000 lines. I'm taking the
> 9000 lines figure from your cover letter by the way..
>
> https://lists.freedesktop.org/archives/spice-devel/2018-September/045576.html
>  26 files changed, 9321 insertions(+), 520 deletions(-)
>
>
> > I believe if you describe the kind of regression I am able to find root
> > cause of it quickly.
>
> I'm sure you can, but if you are the only one who can do that, that's a
> maintainance concern. The issue is most likely caused by a leak of the
> usb channel (or a leak from it). Start latest virt-viewer to connect to
> a VM (probably with USB redir channels configured), exit it, and see it
> hangs.
>
>
Exit viewer or exit VM?
Usually I the test with built-in remote-viewer, running it from .libs
directory
with LD_LIBRARY_PATH=.

Nothing hangs.

Let's take it to separate mail thread, may be? Or all this process must be
piblic and here?


> Christophe
>
On Mon, Sep 24, 2018 at 06:56:59PM +0300, Yuri Benditovich wrote:
> On Mon, Sep 24, 2018 at 4:32 PM Christophe Fergeau <cfergeau@redhat.com>
> wrote:
> 
> > On Sat, Sep 22, 2018 at 12:21:04AM +0300, Yuri Benditovich wrote:
> > > On Sat, Sep 22, 2018 at 12:14 AM, Yuri Benditovich <
> > > yuri.benditovich@daynix.com> wrote:
> > >
> > > > Hi
> > > >
> > > > On Fri, Sep 21, 2018 at 9:10 PM, Christophe Fergeau <
> > cfergeau@redhat.com>
> > > > wrote:
> > > >
> > > >> On Mon, Sep 17, 2018 at 04:22:50PM +0300, Yuri Benditovich wrote:
> > > >> > Changes from v3:
> > > >> > Single commit with all new files split to 12 patches
> > > >> > to make patch review easier
> > > >>
> > > >> What you did is not splitting at all, adding one new file per commit,
> > > >> and then all the modifications to the existing code in a final commit
> > > >> is not what we meant when we asked for split patches.
> > > >>
> > > >> You asked why this is needed, and actually, it turns out your patches
> > > >> introduce a regression in virt-viewer even when not exercising at all
> > > >> any of the new code. The series as it is is not bisectable in any way,
> > > >> so I basically have to look at these 9000 lines of code to try to
> > figure
> > > >> out what broke...
> > > >>
> > > >
> > > BTW I'm sorry to write it, but for the record the patch is not 9000
> > lines,
> > > if is less than 2500 lines including all.
> >
> > cd-scsi.c itself which I assume to be new code is 2700 lines, the new
> > widget is 2000 lines, usb-backend-common.c is 2000 lines. I'm taking the
> > 9000 lines figure from your cover letter by the way..
> >
> > https://lists.freedesktop.org/archives/spice-devel/2018-September/045576.html
> >  26 files changed, 9321 insertions(+), 520 deletions(-)
> >
> >
> > > I believe if you describe the kind of regression I am able to find root
> > > cause of it quickly.
> >
> > I'm sure you can, but if you are the only one who can do that, that's a
> > maintainance concern. The issue is most likely caused by a leak of the
> > usb channel (or a leak from it). Start latest virt-viewer to connect to
> > a VM (probably with USB redir channels configured), exit it, and see it
> > hangs.
> >
> >
> Exit viewer or exit VM?
> Usually I the test with built-in remote-viewer, running it from .libs
> directory
> with LD_LIBRARY_PATH=.

exit virt-viewer (or remote-viewer). You need to have 65ef66e42 'Spice:
listen for new 'SpiceSession::disconnected' signal'.
Probably related to

==10758== 96 bytes in 2 blocks are definitely lost in loss record 13,528 of 16,001
==10758==    at 0x483A766: calloc (vg_replace_malloc.c:711)
==10758==    by 0x76E7FFD: g_malloc0 (gmem.c:129)
==10758==    by 0x5FF2F89: hotplug_callback (usb-backend-common.c:309)
==10758==    by 0x6F86804: libusb_hotplug_register_callback (hotplug.c:307)
==10758==    by 0x5FF35D0: spice_usb_backend_handle_hotplug (usb-backend-common.c:345)
==10758==    by 0x5FF164C: spice_usb_device_manager_initable_init (usb-device-manager.c:319)
==10758==    by 0x74FC3FE: g_initable_new_valist (ginitable.c:248)
==10758==    by 0x74FC4AC: g_initable_new (ginitable.c:162)
==10758==    by 0x5FCF374: spice_usb_device_manager_get (spice-session.c:2735)
==10758==    by 0x424686: create_spice_session (virt-viewer-session-spice.c:407)
==10758==    by 0x4249F8: virt_viewer_session_spice_constructed (virt-viewer-session-spice.c:224)
==10758==    by 0x7655B0A: g_object_new_internal (gobject.c:1845)
==10758==    by 0x765785D: g_object_new_valist (gobject.c:2128)
==10758==    by 0x7657BBC: g_object_new (gobject.c:1648)
==10758==    by 0x424CDB: virt_viewer_session_spice_new (virt-viewer-session-spice.c:1157)
==10758==    by 0x4186E2: virt_viewer_app_create_session (virt-viewer-app.c:1029)
==10758==    by 0x412BC1: virt_viewer_extract_connect_info (virt-viewer.c:559)
==10758==    by 0x412BC1: virt_viewer_update_display (virt-viewer.c:675)
==10758==    by 0x4140D4: virt_viewer_initial_connect (virt-viewer.c:931)
==10758==    by 0x418A59: virt_viewer_app_initial_connect (virt-viewer-app.c:1316)
==10758==    by 0x413987: virt_viewer_connect (virt-viewer.c:1107)
==10758==    by 0x413C41: virt_viewer_start (virt-viewer.c:1149)
==10758==    by 0x418B07: virt_viewer_app_start (virt-viewer-app.c:1680)
==10758==    by 0x41A504: virt_viewer_app_on_application_startup (virt-viewer-app.c:1818)
==10758==    by 0x764FC9C: g_closure_invoke (gclosure.c:810)
==10758==    by 0x76633A5: signal_emit_unlocked_R (gsignal.c:3565)
==10758==    by 0x766C329: g_signal_emit_valist (gsignal.c:3391)
==10758==    by 0x766C922: g_signal_emit (gsignal.c:3447)
==10758==    by 0x753C7C1: g_application_register (gapplication.c:2137)
==10758==    by 0x753D39A: g_application_run (gapplication.c:2444)
==10758==    by 0x414679: main (virt-viewer-main.c:41)


> 
> Nothing hangs.
> 
> Let's take it to separate mail thread, may be? Or all this process must be
> piblic and here?

Sure, feel free to reach out privately or on IRC.

Christophe
On Fri, Sep 21, 2018 at 08:10:08PM +0200, Christophe Fergeau wrote:
> 
> All your SPICE_DEBUG calls include a __FUNCTION__, but it seems
> SPICE_DEBUG already does that for you?
> 
> #define SPICE_DEBUG(fmt, ...)                                   \
>     do {                                                        \
>         if (G_UNLIKELY(spice_util_get_debug()))                 \
>             g_debug(G_STRLOC " " fmt, ## __VA_ARGS__);          \
>     } while (0)
> 
> with
> 
> #define G_STRLOC __FILE__ ":" G_STRINGIFY (__LINE__) ":" __PRETTY_FUNCTION__ "()"

I c&p'ed this from devhelp, but this is actually wrong, G_STRLOC is:

/* Provide a string identifying the current code position */
#if defined(__GNUC__) && (__GNUC__ < 3) && !defined(__cplusplus)
#define G_STRLOC    __FILE__ ":" G_STRINGIFY (__LINE__) ":" __PRETTY_FUNCTION__ "()"
#else
#define G_STRLOC    __FILE__ ":" G_STRINGIFY (__LINE__)
#endif

so no __PRETTY_FUNCTION__ on current GCC because of https://bugzilla.gnome.org/show_bug.cgi?id=69097

Christophe