[Spice-devel] [spice-gtk v1 1/2] usb-redirection: introduce usb backend layer

Yuri Benditovich yuri.benditovich at daynix.com
Mon Sep 24 08:43:54 UTC 2018


This layer communicates with libusb and libusbredir and
provides the API for USB redirection procedures.
In future all the modules of spice-gtk will communicate
only with usb backend instead of calling libusb and
usbredirhost directly. This is prerequisite of further
implementation of cd-sharing via USB redirection.

Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
---
 src/usb-backend-common.c | 809 +++++++++++++++++++++++++++++++++++++++++++++++
 src/usb-backend.h        |  97 ++++++
 2 files changed, 906 insertions(+)
 create mode 100644 src/usb-backend-common.c
 create mode 100644 src/usb-backend.h

diff --git a/src/usb-backend-common.c b/src/usb-backend-common.c
new file mode 100644
index 0000000..b3963ad
--- /dev/null
+++ b/src/usb-backend-common.c
@@ -0,0 +1,809 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+    Copyright (C) 2018 Red Hat, Inc.
+
+    Red Hat Authors:
+    Yuri Benditovich<ybendito at 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, ...)
+
+//#define INTERCEPT_LOG
+//#define INTERCEPT_LOG2FILE
+#ifdef INTERCEPT_LOG2FILE
+static FILE *fLog;
+#endif
+
+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;
+    uint32_t suppressed : 1;
+};
+
+/* backend object for device change notification */
+static SpiceUsbBackend *notify_backend;
+
+struct _SpiceUsbBackendChannel
+{
+    struct usbredirhost *usbredirhost;
+    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;
+        pi->max_luns = 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);
+    }
+}
+
+#ifdef INTERCEPT_LOG
+static void log_handler(
+    const gchar *log_domain,
+    GLogLevelFlags log_level,
+    const gchar *message,
+    gpointer user_data)
+{
+    GString *log_msg;
+    log_msg = g_string_new(NULL);
+    if (log_msg)
+    {
+        gchar *timestamp;
+        GThread *th = g_thread_self();
+        GDateTime *current_time = g_date_time_new_now_local();
+        gint micros = g_date_time_get_microsecond(current_time);
+        timestamp = g_date_time_format(current_time, "%H:%M:%S");
+        g_string_append_printf(log_msg, "[%p][%s.%03d]", th, timestamp, micros / 1000);
+        g_date_time_unref(current_time);
+        g_free(timestamp);
+        g_string_append(log_msg, message);
+#ifdef INTERCEPT_LOG2FILE
+        g_string_append(log_msg, "\n");
+        fwrite(log_msg->str, 1, strlen(log_msg->str), fLog);
+#else
+        g_log_default_handler(log_domain, log_level, log_msg->str, NULL);
+#endif
+        g_string_free(log_msg, TRUE);
+    }
+}
+#endif
+
+static void configure_log(void)
+{
+#ifdef INTERCEPT_LOG2FILE
+    fLog = fopen("remote-viewer.log", "w+t");
+#endif
+#ifdef INTERCEPT_LOG
+    g_log_set_default_handler(log_handler, NULL);
+#endif
+}
+
+SpiceUsbBackend *spice_usb_backend_initialize(void)
+{
+    SpiceUsbBackend *be;
+    SPICE_DEBUG("%s >>", __FUNCTION__);
+    if (!g_mutex) {
+        g_mutex = usbredir_alloc_lock();
+        configure_log();
+    }
+    be = (SpiceUsbBackend *)g_new0(SpiceUsbBackend, 1);
+#ifndef USE_CD_SHARING
+    be->suppressed = TRUE;
+#endif
+    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);
+        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_finalize(SpiceUsbBackend *be)
+{
+    SPICE_DEBUG("%s >>", __FUNCTION__);
+    if (be->libusbContext) {
+        libusb_exit(be->libusbContext);
+    }
+    if (be == notify_backend) {
+        notify_backend = NULL;
+    }
+    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 + 1);
+
+    index = 0;
+
+    for (dev = devlist; dev && *dev; dev++) {
+        d = g_new0(SpiceUsbBackendDevice, 1);
+        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);
+    }
+    return -1;
+}
+
+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 const char *strip_usbredir_prefix(const char *msg)
+{
+    if (strncmp(msg, "usbredirhost: ", 14) == 0) {
+        msg += 14;
+    }
+    return msg;
+}
+
+static void usbredir_log(void *user_data, int level, const char *msg)
+{
+    SpiceUsbBackendChannel *ch = (SpiceUsbBackendChannel *)user_data;
+    const char *stripped_msg = strip_usbredir_prefix(msg);
+    switch (level) {
+    case usbredirparser_error:
+        g_critical("%s", msg);
+        ch->data.log(ch->data.user_data, stripped_msg, TRUE);
+        break;
+    case usbredirparser_warning:
+        g_warning("%s", msg);
+        ch->data.log(ch->data.user_data, stripped_msg, TRUE);
+        break;
+    default:
+        ch->data.log(ch->data.user_data, stripped_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;
+}
+
+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);
+    }
+}
+
+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) {
+        return b;
+    }
+
+    if (dev->isLibUsb) {
+        libusb_device_handle *handle = NULL;
+        int rc = libusb_open(dev->d.libusb_device, &handle);
+        b = rc == 0 && handle;
+        if (b) {
+            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;
+        }
+    }
+
+    return b;
+}
+
+void spice_usb_backend_channel_detach(SpiceUsbBackendChannel *ch)
+{
+    SPICE_DEBUG("%s >> ch %p, was attached %p", __FUNCTION__, ch, ch->attached);
+    if (!ch->attached) {
+        SPICE_DEBUG("%s: nothing to detach", __FUNCTION__);
+        return;
+    }
+    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;
+}
+
+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;
+    ch->data = *init_data;
+    ch->usbredirhost = !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->usbredirhost != NULL;
+    if (ch->usbredirhost) {
+#if USBREDIR_VERSION >= 0x000701
+        usbredirhost_set_buffered_output_size_cb(ch->usbredirhost, usbredir_buffered_output_size_callback);
+#endif
+    }
+
+    if (!ok) {
+        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 0000000..f6a3604
--- /dev/null
+++ b/src/usb-backend.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+    Copyright (C) 2018 Red Hat, Inc.
+
+    Red Hat Authors:
+    Yuri Benditovich<ybendito at 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;
+    uint8_t max_luns;
+} 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);
+
+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_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);
+gboolean  spice_usb_backend_device_get_info_by_address(guint8 bus, guint8 addr, UsbDeviceInformation *info);
+// 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_detach(SpiceUsbBackendChannel *ch);
+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
-- 
2.9.4



More information about the Spice-devel mailing list