mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice-gtk
synced 2026-02-02 02:34:54 +00:00
USB redirection flow on Windows includes a number of reset requests issued to the port that hosts the device deing redirected. Each port reset emulates device removal and reinsertion and produces corresponding hotplug events and a number of device list updates on different levels of USB stack and USB redirection engine. As a result, queriyng USB device list performed by spice-gtk's hotplug event handler may return inconsistent results if performed in parallel to redirection flow. This patch suppresses handling of USB hotplug events during redirection and injects a simulated hotplug event after redirection completion in order to properly process real device list changes in case they happened during the redirection flow. Signed-off-by: Kirill Moizik <kmoizik@redhat.com> Signed-off-by: Dmitry Fleytman <dfleytma@redhat.com> Acked-by: Jonathon Jongsma <jjongsma@redhat.com>
584 lines
17 KiB
C
584 lines
17 KiB
C
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
/*
|
|
Copyright (C) 2012 Red Hat, Inc.
|
|
|
|
Red Hat Authors:
|
|
Arnon Gilboa <agilboa@redhat.com>
|
|
Uri Lublin <uril@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"
|
|
|
|
#include <windows.h>
|
|
#include <libusb.h>
|
|
#include "win-usb-dev.h"
|
|
#include "spice-marshal.h"
|
|
#include "spice-util.h"
|
|
#include "usbutil.h"
|
|
|
|
#define G_UDEV_CLIENT_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE((obj), G_UDEV_TYPE_CLIENT, GUdevClientPrivate))
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_REDIRECTING
|
|
};
|
|
|
|
struct _GUdevClientPrivate {
|
|
libusb_context *ctx;
|
|
GList *udev_list;
|
|
HWND hwnd;
|
|
gboolean redirecting;
|
|
};
|
|
|
|
#define G_UDEV_CLIENT_WINCLASS_NAME TEXT("G_UDEV_CLIENT")
|
|
|
|
static void g_udev_client_initable_iface_init(GInitableIface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE(GUdevClient, g_udev_client, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, g_udev_client_initable_iface_init));
|
|
|
|
|
|
typedef struct _GUdevDeviceInfo GUdevDeviceInfo;
|
|
|
|
struct _GUdevDeviceInfo {
|
|
guint16 bus;
|
|
guint16 addr;
|
|
guint16 vid;
|
|
guint16 pid;
|
|
guint16 class;
|
|
gchar sclass[4];
|
|
gchar sbus[4];
|
|
gchar saddr[4];
|
|
gchar svid[8];
|
|
gchar spid[8];
|
|
};
|
|
|
|
struct _GUdevDevicePrivate
|
|
{
|
|
/* FixMe: move above fields to this structure and access them directly */
|
|
GUdevDeviceInfo *udevinfo;
|
|
};
|
|
|
|
G_DEFINE_TYPE(GUdevDevice, g_udev_device, G_TYPE_OBJECT)
|
|
|
|
|
|
enum
|
|
{
|
|
UEVENT_SIGNAL,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0, };
|
|
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);
|
|
|
|
//uncomment to debug gudev device lists.
|
|
//#define DEBUG_GUDEV_DEVICE_LISTS
|
|
|
|
#ifdef DEBUG_GUDEV_DEVICE_LISTS
|
|
static void g_udev_device_print_list(GList *l, const gchar *msg);
|
|
#else
|
|
static void g_udev_device_print_list(GList *l, const gchar *msg) {}
|
|
#endif
|
|
static void g_udev_device_print(GUdevDevice *udev, const gchar *msg);
|
|
|
|
static gboolean g_udev_skip_search(GUdevDevice *udev);
|
|
|
|
GQuark g_udev_client_error_quark(void)
|
|
{
|
|
return g_quark_from_static_string("win-gudev-client-error-quark");
|
|
}
|
|
|
|
GUdevClient *g_udev_client_new(const gchar* const *subsystems)
|
|
{
|
|
if (!singleton) {
|
|
singleton = g_initable_new(G_UDEV_TYPE_CLIENT, NULL, NULL, NULL);
|
|
return singleton;
|
|
} else {
|
|
return g_object_ref(singleton);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* devs [in,out] an empty devs list in, full devs list out
|
|
* Returns: number-of-devices, or a negative value on error.
|
|
*/
|
|
static ssize_t
|
|
g_udev_client_list_devices(GUdevClient *self, GList **devs,
|
|
GError **err, const gchar *name)
|
|
{
|
|
gssize rc;
|
|
libusb_device **lusb_list, **dev;
|
|
GUdevClientPrivate *priv;
|
|
GUdevDeviceInfo *udevinfo;
|
|
GUdevDevice *udevice;
|
|
ssize_t n;
|
|
|
|
g_return_val_if_fail(G_UDEV_IS_CLIENT(self), -1);
|
|
g_return_val_if_fail(devs != NULL, -2);
|
|
|
|
priv = self->priv;
|
|
|
|
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", name);
|
|
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);
|
|
return -4;
|
|
}
|
|
|
|
n = 0;
|
|
for (dev = lusb_list; *dev; dev++) {
|
|
udevinfo = g_new0(GUdevDeviceInfo, 1);
|
|
get_usb_dev_info(*dev, udevinfo);
|
|
udevice = g_udev_device_new(udevinfo);
|
|
if (g_udev_skip_search(udevice)) {
|
|
g_object_unref(udevice);
|
|
continue;
|
|
}
|
|
*devs = g_list_prepend(*devs, udevice);
|
|
n++;
|
|
}
|
|
libusb_free_device_list(lusb_list, 1);
|
|
|
|
return n;
|
|
}
|
|
|
|
static void g_udev_client_free_device_list(GList **devs)
|
|
{
|
|
g_return_if_fail(devs != NULL);
|
|
if (*devs) {
|
|
g_list_free_full(*devs, g_object_unref);
|
|
*devs = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean
|
|
g_udev_client_initable_init(GInitable *initable, GCancellable *cancellable,
|
|
GError **err)
|
|
{
|
|
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);
|
|
|
|
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);
|
|
return FALSE;
|
|
}
|
|
|
|
/* get initial device list */
|
|
if (g_udev_client_list_devices(self, &priv->udev_list, err, __FUNCTION__) < 0) {
|
|
goto g_udev_client_init_failed;
|
|
}
|
|
|
|
g_udev_device_print_list(priv->udev_list, "init: first list is: ");
|
|
|
|
/* create a hidden window */
|
|
memset(&wcls, 0, sizeof(wcls));
|
|
wcls.lpfnWndProc = wnd_proc;
|
|
wcls.lpszClassName = G_UDEV_CLIENT_WINCLASS_NAME;
|
|
if (!RegisterClass(&wcls)) {
|
|
DWORD e = GetLastError();
|
|
g_warning("RegisterClass failed , %ld", (long)e);
|
|
g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_WINAPI_FAILED,
|
|
"RegisterClass failed: %ld", (long)e);
|
|
goto g_udev_client_init_failed;
|
|
}
|
|
priv->hwnd = CreateWindow(G_UDEV_CLIENT_WINCLASS_NAME,
|
|
NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
|
|
if (!priv->hwnd) {
|
|
DWORD e = GetLastError();
|
|
g_warning("CreateWindow failed: %ld", (long)e);
|
|
g_set_error(err, G_UDEV_CLIENT_ERROR, G_UDEV_CLIENT_LIBUSB_FAILED,
|
|
"CreateWindow failed: %ld", (long)e);
|
|
goto g_udev_client_init_failed_unreg;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
g_udev_client_init_failed_unreg:
|
|
UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL);
|
|
g_udev_client_init_failed:
|
|
libusb_exit(priv->ctx);
|
|
priv->ctx = NULL;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void g_udev_client_initable_iface_init(GInitableIface *iface)
|
|
{
|
|
iface->init = g_udev_client_initable_init;
|
|
}
|
|
|
|
GList *g_udev_client_query_by_subsystem(GUdevClient *self, const gchar *subsystem)
|
|
{
|
|
GList *l = g_list_copy(self->priv->udev_list);
|
|
g_list_foreach(l, (GFunc)g_object_ref, NULL);
|
|
return l;
|
|
}
|
|
|
|
static void g_udev_client_init(GUdevClient *self)
|
|
{
|
|
self->priv = G_UDEV_CLIENT_GET_PRIVATE(self);
|
|
}
|
|
|
|
static void g_udev_client_finalize(GObject *gobject)
|
|
{
|
|
GUdevClient *self = G_UDEV_CLIENT(gobject);
|
|
GUdevClientPrivate *priv = self->priv;
|
|
|
|
singleton = NULL;
|
|
DestroyWindow(priv->hwnd);
|
|
UnregisterClass(G_UDEV_CLIENT_WINCLASS_NAME, NULL);
|
|
g_udev_client_free_device_list(&priv->udev_list);
|
|
|
|
/* free libusb context initializing by libusb_init() */
|
|
g_warn_if_fail(priv->ctx != NULL);
|
|
libusb_exit(priv->ctx);
|
|
|
|
/* Chain up to the parent class */
|
|
if (G_OBJECT_CLASS(g_udev_client_parent_class)->finalize)
|
|
G_OBJECT_CLASS(g_udev_client_parent_class)->finalize(gobject);
|
|
}
|
|
|
|
static void g_udev_client_get_property(GObject *gobject,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GUdevClient *self = G_UDEV_CLIENT(gobject);
|
|
GUdevClientPrivate *priv = self->priv;
|
|
|
|
switch (prop_id) {
|
|
case PROP_REDIRECTING:
|
|
g_value_set_boolean(value, priv->redirecting);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void handle_dev_change(GUdevClient *self);
|
|
|
|
static void g_udev_client_set_property(GObject *gobject,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GUdevClient *self = G_UDEV_CLIENT(gobject);
|
|
GUdevClientPrivate *priv = self->priv;
|
|
gboolean old_val;
|
|
|
|
switch (prop_id) {
|
|
case PROP_REDIRECTING:
|
|
old_val = priv->redirecting;
|
|
priv->redirecting = g_value_get_boolean(value);
|
|
if (old_val && !priv->redirecting) {
|
|
/* This is a redirection completion case.
|
|
Inject hotplug event in case we missed device changes
|
|
during redirection processing. */
|
|
handle_dev_change(self);
|
|
}
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void g_udev_client_class_init(GUdevClientClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
|
GParamSpec *pspec;
|
|
|
|
gobject_class->finalize = g_udev_client_finalize;
|
|
gobject_class->get_property = g_udev_client_get_property;
|
|
gobject_class->set_property = g_udev_client_set_property;
|
|
|
|
signals[UEVENT_SIGNAL] =
|
|
g_signal_new("uevent",
|
|
G_OBJECT_CLASS_TYPE(klass),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GUdevClientClass, uevent),
|
|
NULL, NULL,
|
|
g_cclosure_user_marshal_VOID__BOXED_BOXED,
|
|
G_TYPE_NONE,
|
|
2,
|
|
G_TYPE_STRING,
|
|
G_UDEV_TYPE_DEVICE);
|
|
|
|
/**
|
|
* GUdevClient::redirecting:
|
|
*
|
|
* This property indicates when a redirection operation
|
|
* is in progress on a device. It's set back to FALSE
|
|
* once the device is fully redirected to the guest.
|
|
*/
|
|
pspec = g_param_spec_boolean("redirecting", "Redirecting",
|
|
"USB redirection operation is in progress",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_property(gobject_class, PROP_REDIRECTING, pspec);
|
|
|
|
g_type_class_add_private(klass, sizeof(GUdevClientPrivate));
|
|
}
|
|
|
|
static gboolean get_usb_dev_info(libusb_device *dev, GUdevDeviceInfo *udevinfo)
|
|
{
|
|
struct libusb_device_descriptor desc;
|
|
|
|
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;
|
|
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);
|
|
snprintf(udevinfo->svid, sizeof(udevinfo->svid), "%d", udevinfo->vid);
|
|
snprintf(udevinfo->spid, sizeof(udevinfo->spid), "%d", udevinfo->pid);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Only vid:pid are compared */
|
|
static gint gudev_devices_differ(gconstpointer a, gconstpointer b)
|
|
{
|
|
GUdevDeviceInfo *ai, *bi;
|
|
gboolean same_vid;
|
|
gboolean same_pid;
|
|
|
|
ai = G_UDEV_DEVICE(a)->priv->udevinfo;
|
|
bi = G_UDEV_DEVICE(b)->priv->udevinfo;
|
|
|
|
same_vid = (ai->vid == bi->vid);
|
|
same_pid = (ai->pid == bi->pid);
|
|
|
|
return (same_pid && same_vid) ? 0 : -1;
|
|
}
|
|
|
|
static void notify_dev_state_change(GUdevClient *self,
|
|
GList *old_list,
|
|
GList *new_list,
|
|
const gchar *action)
|
|
{
|
|
GList *dev;
|
|
|
|
for (dev = g_list_first(old_list); dev != NULL; dev = g_list_next(dev)) {
|
|
if (g_list_find_custom(new_list, dev->data, gudev_devices_differ) == NULL) {
|
|
/* Found a device that changed its state */
|
|
g_udev_device_print(dev->data, action);
|
|
g_signal_emit(self, signals[UEVENT_SIGNAL], 0, action, dev->data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_dev_change(GUdevClient *self)
|
|
{
|
|
GUdevClientPrivate *priv = self->priv;
|
|
GError *err = NULL;
|
|
GList *now_devs = NULL;
|
|
|
|
if (priv->redirecting == TRUE) {
|
|
/* On Windows, querying USB device list may return inconsistent results
|
|
if performed in parallel to redirection flow.
|
|
A simulated hotplug event will be injected after redirection
|
|
completion in order to process real device list changes that may
|
|
had taken place during redirection process. */
|
|
return;
|
|
}
|
|
|
|
if(g_udev_client_list_devices(self, &now_devs, &err, __FUNCTION__) < 0) {
|
|
g_warning("could not retrieve device list");
|
|
return;
|
|
}
|
|
|
|
g_udev_device_print_list(now_devs, "handle_dev_change: current list:");
|
|
g_udev_device_print_list(priv->udev_list, "handle_dev_change: previous list:");
|
|
|
|
/* Unregister devices that are not present anymore */
|
|
notify_dev_state_change(self, priv->udev_list, now_devs, "remove");
|
|
|
|
/* Register newly inserted devices */
|
|
notify_dev_state_change(self, now_devs, priv->udev_list, "add");
|
|
|
|
/* keep most recent info: free previous list, and keep current list */
|
|
g_udev_client_free_device_list(&priv->udev_list);
|
|
priv->udev_list = now_devs;
|
|
}
|
|
|
|
static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
/* Only DBT_DEVNODES_CHANGED recieved */
|
|
if (message == WM_DEVICECHANGE) {
|
|
handle_dev_change(singleton);
|
|
}
|
|
return DefWindowProc(hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
/*** GUdevDevice ***/
|
|
|
|
static void g_udev_device_finalize(GObject *object)
|
|
{
|
|
GUdevDevice *device = G_UDEV_DEVICE(object);
|
|
|
|
g_free(device->priv->udevinfo);
|
|
if (G_OBJECT_CLASS(g_udev_device_parent_class)->finalize != NULL)
|
|
(* G_OBJECT_CLASS(g_udev_device_parent_class)->finalize)(object);
|
|
}
|
|
|
|
static void g_udev_device_class_init(GUdevDeviceClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
|
|
gobject_class->finalize = g_udev_device_finalize;
|
|
g_type_class_add_private (klass, sizeof(GUdevDevicePrivate));
|
|
}
|
|
|
|
static void g_udev_device_init(GUdevDevice *device)
|
|
{
|
|
device->priv = G_TYPE_INSTANCE_GET_PRIVATE(device, G_UDEV_TYPE_DEVICE, GUdevDevicePrivate);
|
|
}
|
|
|
|
static GUdevDevice *g_udev_device_new(GUdevDeviceInfo *udevinfo)
|
|
{
|
|
GUdevDevice *device;
|
|
|
|
g_return_val_if_fail(udevinfo != NULL, NULL);
|
|
|
|
device = G_UDEV_DEVICE(g_object_new(G_UDEV_TYPE_DEVICE, NULL));
|
|
device->priv->udevinfo = udevinfo;
|
|
return device;
|
|
}
|
|
|
|
const gchar *g_udev_device_get_property(GUdevDevice *udev, const gchar *property)
|
|
{
|
|
GUdevDeviceInfo* udevinfo;
|
|
|
|
g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL);
|
|
g_return_val_if_fail(property != NULL, NULL);
|
|
|
|
udevinfo = udev->priv->udevinfo;
|
|
g_return_val_if_fail(udevinfo != NULL, NULL);
|
|
|
|
if (g_strcmp0(property, "BUSNUM") == 0) {
|
|
return udevinfo->sbus;
|
|
} else if (g_strcmp0(property, "DEVNUM") == 0) {
|
|
return udevinfo->saddr;
|
|
} else if (g_strcmp0(property, "DEVTYPE") == 0) {
|
|
return "usb_device";
|
|
} else if (g_strcmp0(property, "VID") == 0) {
|
|
return udevinfo->svid;
|
|
} else if (g_strcmp0(property, "PID") == 0) {
|
|
return udevinfo->spid;
|
|
}
|
|
|
|
g_warn_if_reached();
|
|
return NULL;
|
|
}
|
|
|
|
const gchar *g_udev_device_get_sysfs_attr(GUdevDevice *udev, const gchar *attr)
|
|
{
|
|
GUdevDeviceInfo* udevinfo;
|
|
|
|
g_return_val_if_fail(G_UDEV_DEVICE(udev), NULL);
|
|
g_return_val_if_fail(attr != NULL, NULL);
|
|
|
|
udevinfo = udev->priv->udevinfo;
|
|
g_return_val_if_fail(udevinfo != NULL, NULL);
|
|
|
|
|
|
if (g_strcmp0(attr, "bDeviceClass") == 0) {
|
|
return udevinfo->sclass;
|
|
}
|
|
g_warn_if_reached();
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef DEBUG_GUDEV_DEVICE_LISTS
|
|
static void g_udev_device_print_list(GList *l, const gchar *msg)
|
|
{
|
|
GList *it;
|
|
|
|
for (it = g_list_first(l); it != NULL; it=g_list_next(it)) {
|
|
g_udev_device_print(it->data, msg);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void g_udev_device_print(GUdevDevice *udev, const gchar *msg)
|
|
{
|
|
GUdevDeviceInfo* udevinfo;
|
|
|
|
g_return_if_fail(G_UDEV_DEVICE(udev));
|
|
|
|
udevinfo = udev->priv->udevinfo;
|
|
g_return_if_fail(udevinfo != NULL);
|
|
|
|
SPICE_DEBUG("%s: %d.%d 0x%04x:0x%04x class %d", msg,
|
|
udevinfo->bus, udevinfo->addr,
|
|
udevinfo->vid, udevinfo->pid, udevinfo->class);
|
|
}
|
|
|
|
static gboolean g_udev_skip_search(GUdevDevice *udev)
|
|
{
|
|
GUdevDeviceInfo* udevinfo;
|
|
gboolean skip;
|
|
|
|
g_return_val_if_fail(G_UDEV_DEVICE(udev), FALSE);
|
|
|
|
udevinfo = udev->priv->udevinfo;
|
|
g_return_val_if_fail(udevinfo != NULL, FALSE);
|
|
|
|
skip = ((udevinfo->addr == 0xff) || /* root hub (HCD) */
|
|
#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->addr == 0)); /* bad address */
|
|
return skip;
|
|
}
|