Windows mingw: usb: Dynamically install a libusb driver for USB devices

- Added win-usb-driver-install.[ch]
- Added win-usb-clerk.h

Operation (on Windows, spice-gtk point of view):
- After some sanity checks, just before redir'ing a USB device
  a libusb driver needs to be installed (before libusb can open the device)
- A connection (NamedPipe) is established with usb-clerk, a libusb
  driver installation service, and a request for driver installation
  is sent.
- Installation status is asynchronously read from the pipe, and
  spice_usb_drv_install_finished() is called.
- Upon a successful intallation, usbredir continues.

Linux operation is not changed.
This commit is contained in:
Uri Lublin 2012-07-05 23:43:58 +03:00
parent 5525ce9734
commit 714def3df0
5 changed files with 626 additions and 5 deletions

View File

@ -316,6 +316,9 @@ endif
WIN_USB_FILES= \
win-usb-dev.h \
win-usb-dev.c \
win-usb-clerk.h \
win-usb-driver-install.h \
win-usb-driver-install.c \
$(NULL)
if OS_WIN32

View File

@ -33,6 +33,7 @@
#include <gudev/gudev.h>
#elif defined(G_OS_WIN32)
#include "win-usb-dev.h"
#include "win-usb-driver-install.h"
#else
#warning "Expecting one of G_OS_WIN32 and USE_GUDEV to be defined"
#endif
@ -144,6 +145,13 @@ static libusb_device *
spice_usb_device_manager_device_to_libdev(SpiceUsbDeviceManager *self,
SpiceUsbDevice *device);
static void
_spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
SpiceUsbDevice *device,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device,
(GBoxedCopyFunc)spice_usb_device_ref,
(GBoxedFreeFunc)spice_usb_device_unref)
@ -154,6 +162,12 @@ G_DEFINE_BOXED_TYPE(SpiceUsbDevice, spice_usb_device, g_object_ref, g_object_unr
static void spice_usb_device_manager_initable_iface_init(GInitableIface *iface);
#ifdef G_OS_WIN32
static void spice_usb_device_manager_drv_install_cb(GObject *gobject,
GAsyncResult *res,
gpointer user_data);
#endif
static guint signals[LAST_SIGNAL] = { 0, };
G_DEFINE_TYPE_WITH_CODE(SpiceUsbDeviceManager, spice_usb_device_manager, G_TYPE_OBJECT,
@ -723,6 +737,87 @@ static void spice_usb_device_manager_channel_connect_cb(
g_object_unref(result);
}
#ifdef G_OS_WIN32
typedef struct _UsbInstallCbInfo {
SpiceUsbDeviceManager *manager;
SpiceUsbDevice *device;
SpiceWinUsbDriver *installer;
GCancellable *cancellable;
GAsyncReadyCallback callback;
gpointer user_data;
} UsbInstallCbInfo;
/**
* spice_usb_device_manager_drv_install_cb:
* @gobject: #SpiceWinUsbDriver in charge of installing the driver
* @res: #GAsyncResult of async win usb driver installation
* @user_data: #SpiceUsbDeviceManager requested the installation
*
* Called when an Windows libusb driver installation completed.
*
* If the driver installation was successful, continue with USB
* device redirection
*
* Always call _spice_usb_device_manager_connect_device_async.
* When installation fails, libusb_open fails too, but cleanup would be better.
*/
static void spice_usb_device_manager_drv_install_cb(GObject *gobject,
GAsyncResult *res,
gpointer user_data)
{
SpiceUsbDeviceManager *self;
SpiceWinUsbDriver *installer;
gint status;
GError *err = NULL;
SpiceUsbDevice *device;
UsbInstallCbInfo *cbinfo;
GCancellable *cancellable;
GAsyncReadyCallback callback;
SPICE_DEBUG("Win USB driver Installation finished");
g_return_if_fail(user_data != NULL);
cbinfo = user_data;
self = cbinfo->manager;
device = cbinfo->device;
installer = cbinfo->installer;
cancellable = cbinfo->cancellable;
callback = cbinfo->callback;
user_data = cbinfo->user_data;
g_free(cbinfo);
g_return_if_fail(SPICE_IS_USB_DEVICE_MANAGER(self));
g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(installer));
g_return_if_fail(device!= NULL);
status = spice_win_usb_driver_install_finish(installer, res, &err);
g_object_unref(installer);
spice_usb_device_unref(device);
if (err) {
g_warning("win usb driver installation failed -- %s",
err->message);
g_error_free(err);
}
if (!status) {
g_warning("failed to install win usb driver (status=0)");
}
/* device is already ref'ed */
_spice_usb_device_manager_connect_device_async(self,
device,
cancellable,
callback,
user_data);
}
#endif
/* ------------------------------------------------------------------ */
/* private api */
@ -903,11 +998,12 @@ gboolean spice_usb_device_manager_is_device_connected(SpiceUsbDeviceManager *sel
* @callback: a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: data to pass to callback
*/
void spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
SpiceUsbDevice *device,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
static void
_spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
SpiceUsbDevice *device,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
@ -958,6 +1054,38 @@ done:
g_object_unref(result);
}
void spice_usb_device_manager_connect_device_async(SpiceUsbDeviceManager *self,
SpiceUsbDevice *device,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
#if defined(USE_USBREDIR) && defined(G_OS_WIN32)
SpiceWinUsbDriver *installer;
UsbInstallCbInfo *cbinfo;
installer = spice_win_usb_driver_new();
cbinfo = g_new0(UsbInstallCbInfo, 1);
cbinfo->manager = self;
cbinfo->device = spice_usb_device_ref(device);
cbinfo->installer = installer;
cbinfo->cancellable = cancellable;
cbinfo->callback = callback;
cbinfo->user_data = user_data;
spice_win_usb_driver_install(installer, device, cancellable,
spice_usb_device_manager_drv_install_cb,
cbinfo);
#else
_spice_usb_device_manager_connect_device_async(self,
device,
cancellable,
callback,
user_data);
#endif
}
gboolean spice_usb_device_manager_connect_device_finish(
SpiceUsbDeviceManager *self, GAsyncResult *res, GError **err)
{

35
gtk/win-usb-clerk.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef _H_USBCLERK
#define _H_USBCLERK
#include <windows.h>
#define USB_CLERK_PIPE_NAME TEXT("\\\\.\\pipe\\usbclerkpipe")
#define USB_CLERK_MAGIC 0xDADA
#define USB_CLERK_VERSION 0x0002
typedef struct USBClerkHeader {
UINT16 magic;
UINT16 version;
UINT16 type;
UINT16 size;
} USBClerkHeader;
enum {
USB_CLERK_DRIVER_INSTALL = 1,
USB_CLERK_DRIVER_REMOVE,
USB_CLERK_REPLY,
USB_CLERK_END_MESSAGE,
};
typedef struct USBClerkDriverOp {
USBClerkHeader hdr;
UINT16 vid;
UINT16 pid;
} USBClerkDriverOp;
typedef struct USBClerkReply {
USBClerkHeader hdr;
UINT32 status;
} USBClerkReply;
#endif

View File

@ -0,0 +1,357 @@
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2011 Red Hat, Inc.
Red Hat Authors:
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/>.
*/
/*
* Some notes:
* Each installer (instance) opens a named-pipe to talk with win-usb-clerk.
* Each installer (instance) requests driver installation for a single device.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <windows.h>
#include <gio/gio.h>
#include <gio/gwin32inputstream.h>
#include <gio/gwin32outputstream.h>
#include "spice-util.h"
#include "win-usb-clerk.h"
#include "win-usb-driver-install.h"
#include "usb-device-manager-priv.h"
/* ------------------------------------------------------------------ */
/* gobject glue */
#define SPICE_WIN_USB_DRIVER_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverPrivate))
struct _SpiceWinUsbDriverPrivate {
USBClerkReply reply;
GSimpleAsyncResult *result;
GCancellable *cancellable;
HANDLE handle;
SpiceUsbDevice *device;
};
G_DEFINE_TYPE(SpiceWinUsbDriver, spice_win_usb_driver, G_TYPE_OBJECT);
static void spice_win_usb_driver_init(SpiceWinUsbDriver *self)
{
self->priv = SPICE_WIN_USB_DRIVER_GET_PRIVATE(self);
}
static void spice_win_usb_driver_close(SpiceWinUsbDriver *self)
{
if (self->priv->handle) {
CloseHandle(self->priv->handle);
self->priv->handle = 0;
}
}
static void spice_win_usb_driver_finalize(GObject *gobject)
{
SpiceWinUsbDriver *self = SPICE_WIN_USB_DRIVER(gobject);
SpiceWinUsbDriverPrivate *priv = self->priv;
spice_win_usb_driver_close(self);
g_clear_object(&priv->result);
}
static void spice_win_usb_driver_class_init(SpiceWinUsbDriverClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = spice_win_usb_driver_finalize;
g_type_class_add_private(klass, sizeof(SpiceWinUsbDriverPrivate));
}
/* ------------------------------------------------------------------ */
/* callbacks */
void win_usb_driver_handle_reply_cb(GObject *gobject,
GAsyncResult *read_res,
gpointer user_data)
{
SpiceWinUsbDriver *self;
SpiceWinUsbDriverPrivate *priv;
GInputStream *istream;
GError *err = NULL;
gssize bytes;
g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(user_data));
self = SPICE_WIN_USB_DRIVER(user_data);
priv = self->priv;
istream = G_INPUT_STREAM(gobject);
bytes = g_input_stream_read_finish(istream, read_res, &err);
SPICE_DEBUG("Finished reading reply-msg from usbclerk: bytes=%ld "
"err_exist?=%d", (long)bytes, err!=NULL);
g_warn_if_fail(g_input_stream_close(istream, NULL, NULL));
g_clear_object(&istream);
spice_win_usb_driver_close(self);
if (err) {
g_warning("failed to read reply from usbclerk (%s)", err->message);
g_simple_async_result_take_error(priv->result, err);
goto failed_reply;
}
if (bytes == 0) {
g_warning("unexpected EOF from usbclerk");
g_simple_async_result_set_error(priv->result,
SPICE_WIN_USB_DRIVER_ERROR,
SPICE_WIN_USB_DRIVER_ERROR_FAILED,
"unexpected EOF from usbclerk");
goto failed_reply;
}
if (bytes != sizeof(priv->reply)) {
g_warning("usbclerk size mismatch: read %d bytes, expected %d (header %d, size in header %d)",
bytes, sizeof(priv->reply), sizeof(priv->reply.hdr), priv->reply.hdr.size);
/* For now just warn, do not fail */
}
if (priv->reply.hdr.magic != USB_CLERK_MAGIC) {
g_warning("usbclerk magic mismatch: mine=0x%04x server=0x%04x",
USB_CLERK_MAGIC, priv->reply.hdr.magic);
g_simple_async_result_set_error(priv->result,
SPICE_WIN_USB_DRIVER_ERROR,
SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
"usbclerk magic mismatch");
goto failed_reply;
}
if (priv->reply.hdr.version != USB_CLERK_VERSION) {
g_warning("usbclerk version mismatch: mine=0x%04x server=0x%04x",
USB_CLERK_VERSION, priv->reply.hdr.version);
/* For now just warn, do not fail */
}
if (priv->reply.hdr.type != USB_CLERK_REPLY) {
g_warning("usbclerk message with unexpected type %d",
priv->reply.hdr.type);
g_simple_async_result_set_error(priv->result,
SPICE_WIN_USB_DRIVER_ERROR,
SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
"usbclerk message with unexpected type");
goto failed_reply;
}
if (priv->reply.hdr.size != bytes) {
g_warning("usbclerk message size mismatch: read %d bytes hdr.size=%d",
bytes, priv->reply.hdr.size);
g_simple_async_result_set_error(priv->result,
SPICE_WIN_USB_DRIVER_ERROR,
SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
"usbclerk message with unexpected size");
goto failed_reply;
}
failed_reply:
g_simple_async_result_complete_in_idle(priv->result);
g_clear_object(&priv->result);
}
/* ------------------------------------------------------------------ */
/* helper functions */
static
gboolean spice_win_usb_driver_send_request(SpiceWinUsbDriver *self, guint16 op,
guint16 vid, guint16 pid, GError **err)
{
USBClerkDriverOp req = {0,};
GOutputStream *ostream;
SpiceWinUsbDriverPrivate *priv;
gsize bytes;
gboolean ret;
SPICE_DEBUG("sending a request to usbclerk service (op=%d vid=0x%04x pid=0x%04x",
op, vid, pid);
g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), FALSE);
priv = self->priv;
req.hdr.magic = USB_CLERK_MAGIC;
req.hdr.version = USB_CLERK_VERSION;
req.hdr.type = op;
req.hdr.size = sizeof(req);
req.vid = vid;
req.pid = pid;
ostream = g_win32_output_stream_new(priv->handle, FALSE);
ret = g_output_stream_write_all(ostream, &req, sizeof(req), &bytes, NULL, err);
g_warn_if_fail(g_output_stream_close(ostream, NULL, NULL));
g_object_unref(ostream);
SPICE_DEBUG("write_all request returned %d written bytes %u expecting %u",
ret, bytes, sizeof(req));
return ret;
}
static
void spice_win_usb_driver_read_reply_async(SpiceWinUsbDriver *self)
{
SpiceWinUsbDriverPrivate *priv;
GInputStream *istream;
g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(self));
priv = self->priv;
SPICE_DEBUG("waiting for a reply from usbclerk");
istream = g_win32_input_stream_new(priv->handle, FALSE);
g_input_stream_read_async(istream, &priv->reply, sizeof(priv->reply),
G_PRIORITY_DEFAULT, priv->cancellable,
win_usb_driver_handle_reply_cb, self);
}
/* ------------------------------------------------------------------ */
/* private api */
G_GNUC_INTERNAL
SpiceWinUsbDriver *spice_win_usb_driver_new(void)
{
GObject *obj;
obj = g_object_new(SPICE_TYPE_WIN_USB_DRIVER, NULL);
return SPICE_WIN_USB_DRIVER(obj);
}
/**
* spice_win_usb_driver_install:
* Start libusb driver installation for @device
*
* A new NamedPipe is created for each request.
*
* Returns: TRUE if a request was sent to usbclerk
* FALSE upon failure to send a request.
*/
G_GNUC_INTERNAL
void spice_win_usb_driver_install(SpiceWinUsbDriver *self,
SpiceUsbDevice *device,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
guint16 vid, pid;
GError *err = NULL;
GSimpleAsyncResult *result;
SpiceWinUsbDriverPrivate *priv;
SPICE_DEBUG("Win usb driver installation started");
g_return_if_fail(SPICE_IS_WIN_USB_DRIVER(self));
g_return_if_fail(device != NULL);
priv = self->priv;
g_return_if_fail(priv->result == NULL);
result = g_simple_async_result_new(G_OBJECT(self), callback, user_data,
spice_win_usb_driver_install);
vid = spice_usb_device_get_vid(device);
pid = spice_usb_device_get_pid(device);
SPICE_DEBUG("win-usb-driver-install: connecting to usbclerk named pipe");
priv->handle = CreateFile(USB_CLERK_PIPE_NAME,
GENERIC_READ | GENERIC_WRITE,
0, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (priv->handle == INVALID_HANDLE_VALUE) {
DWORD errval = GetLastError();
gchar *errstr = g_win32_error_message(errval);
g_warning("failed to create a named pipe to usbclerk (%ld) %s",
errval,errstr);
g_simple_async_result_set_error(result,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to create named pipe (%ld) %s", errval, errstr);
goto failed_request;
}
if (!spice_win_usb_driver_send_request(self, USB_CLERK_DRIVER_INSTALL,
vid, pid, &err)) {
g_warning("failed to send a request to usbclerk %s", err->message);
g_simple_async_result_take_error(result, err);
goto failed_request;
}
/* set up for async read */
priv->result = result;
priv->device = device;
priv->cancellable = cancellable;
spice_win_usb_driver_read_reply_async(self);
return;
failed_request:
spice_win_usb_driver_close(self);
g_simple_async_result_complete_in_idle(result);
g_clear_object(&result);
}
/**
* Returns: currently returns 0 (failure) and 1 (success)
* possibly later we'll add error-codes
*/
G_GNUC_INTERNAL
gint spice_win_usb_driver_install_finish(SpiceWinUsbDriver *self,
GAsyncResult *res, GError **err)
{
GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(res);
g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), 0);
g_return_val_if_fail(g_simple_async_result_is_valid(res, G_OBJECT(self),
spice_win_usb_driver_install),
FALSE);
if (g_simple_async_result_propagate_error(result, err))
return 0;
return self->priv->reply.status;
}
G_GNUC_INTERNAL
SpiceUsbDevice *spice_win_usb_driver_get_device(SpiceWinUsbDriver *self)
{
g_return_val_if_fail(SPICE_IS_WIN_USB_DRIVER(self), 0);
return self->priv->device;
}
GQuark spice_win_usb_driver_error_quark(void)
{
return g_quark_from_static_string("spice-win-usb-driver-error-quark");
}

View File

@ -0,0 +1,98 @@
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2011 Red Hat, Inc.
Red Hat Authors:
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/>.
*/
#ifndef SPICE_WIN_USB_DRIVER_H
#define SPICE_WIN_USB_DRIVER_H
#include "usb-device-manager.h"
G_BEGIN_DECLS
GQuark win_usb_driver_error_quark(void);
#define SPICE_TYPE_WIN_USB_DRIVER (spice_win_usb_driver_get_type ())
#define SPICE_WIN_USB_DRIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriver))
#define SPICE_IS_WIN_USB_DRIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
SPICE_TYPE_WIN_USB_DRIVER))
#define SPICE_WIN_USB_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverClass))
#define SPICE_IS_WIN_USB_DRIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
SPICE_TYPE_WIN_USB_DRIVER))
#define SPICE_WIN_USB_DRIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
SPICE_TYPE_WIN_USB_DRIVER, SpiceWinUsbDriverClass))
typedef struct _SpiceWinUsbDriver SpiceWinUsbDriver;
typedef struct _SpiceWinUsbDriverClass SpiceWinUsbDriverClass;
typedef struct _SpiceWinUsbDriverPrivate SpiceWinUsbDriverPrivate;
struct _SpiceWinUsbDriver
{
GObject parent;
/*< private >*/
SpiceWinUsbDriverPrivate *priv;
/* Do not add fields to this struct */
};
struct _SpiceWinUsbDriverClass
{
GObjectClass parent_class;
};
GType spice_win_usb_driver_get_type(void);
SpiceWinUsbDriver *spice_win_usb_driver_new(void);
void spice_win_usb_driver_install(SpiceWinUsbDriver *self,
SpiceUsbDevice *device,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gint spice_win_usb_driver_install_finish(SpiceWinUsbDriver *self,
GAsyncResult *res, GError **err);
SpiceUsbDevice *spice_win_usb_driver_get_device(SpiceWinUsbDriver *self);
#define SPICE_WIN_USB_DRIVER_ERROR spice_win_usb_driver_error_quark()
/**
* SpiceWinUsbDriverError:
* @SPICE_WIN_USB_DRIVER_ERROR_FAILED: generic error code
* @SPICE_WIN_USB_DRIVER_ERROR_MESSAGE: bad message read from clerk
*
* Error codes returned by spice-client API.
*/
typedef enum
{
SPICE_WIN_USB_DRIVER_ERROR_FAILED,
SPICE_WIN_USB_DRIVER_ERROR_MESSAGE,
} SpiceWinUsbDriverError;
GQuark spice_win_usb_driver_error_quark(void);
G_END_DECLS
#endif /* SPICE_WIN_USB_DRIVER_H */