[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