[Spice-devel] [spice-gtk 1/9] usb-redir: define interfaces to support emulated devices

Victor Toso victortoso at redhat.com
Thu Jul 25 08:08:51 UTC 2019


Hi,

On Wed, Jul 24, 2019 at 01:53:43PM +0300, Yuri Benditovich wrote:
> SpiceUsbBackendDevice structure is extended to support
> additional kind of device that is emulated by Spice-GTK
> and not present locally (and does not have libusb_device),
> such device has instead pointer to SpiceUsbEmulatedDevice
> abstract structure. Specific implementation of such device
> depends on its device type (currently only 'CD device'
> defined). Implementation module registers constructor for
> this device type. Device structure is abstract but always
> starts from table of virtual functions required to redirect
> such virtual device.
> 
> Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
> ---
>  src/meson.build     |   1 +
>  src/usb-backend.c   | 128 +++++++++++++++++++++++++++++++++++++++++++-
>  src/usb-backend.h   |  36 ++++++++++++-
>  src/usb-emulation.h |  88 ++++++++++++++++++++++++++++++
>  4 files changed, 250 insertions(+), 3 deletions(-)
>  create mode 100644 src/usb-emulation.h
> 
> diff --git a/src/meson.build b/src/meson.build
> index adcfaec..49fec52 100644
> --- a/src/meson.build
> +++ b/src/meson.build
> @@ -122,6 +122,7 @@ spice_client_glib_sources = [
>    'usbutil.c',
>    'usbutil.h',
>    'usb-backend.c',
> +  'usb-emulation.h',
>    'usb-backend.h',
>    'vmcstream.c',
>    'vmcstream.h',
> diff --git a/src/usb-backend.c b/src/usb-backend.c
> index 9ac8595..0bf2ecc 100644
> --- a/src/usb-backend.c
> +++ b/src/usb-backend.c
> @@ -39,6 +39,7 @@
>  #include "usbredirparser.h"
>  #include "spice-util.h"
>  #include "usb-backend.h"
> +#include "usb-emulation.h"
>  #include "channel-usbredir-priv.h"
>  #include "spice-channel-priv.h"
>  
> @@ -47,6 +48,7 @@
>  struct _SpiceUsbBackendDevice
>  {
>      libusb_device *libusb_device;
> +    SpiceUsbEmulatedDevice *edev;
>      gint ref_count;
>      SpiceUsbBackendChannel *attached_to;
>      UsbDeviceInformation device_info;
> @@ -66,6 +68,9 @@ struct _SpiceUsbBackend
>      libusb_device **libusb_device_list;
>      gint redirecting;
>  #endif
> +
> +    SpiceUsbEmulatedDeviceCreate dev_init[USB_DEV_TYPE_MAX];
> +    uint32_t own_devices_mask;
>  };
>  
>  struct _SpiceUsbBackendChannel
> @@ -408,6 +413,7 @@ SpiceUsbBackend *spice_usb_backend_new(GError **error)
>          libusb_set_option(be->libusb_context, LIBUSB_OPTION_USE_USBDK);
>  #endif
>  #endif
> +        be->own_devices_mask = 3; /* exclude addresses 0 and 1 */
>      }
>      SPICE_DEBUG("%s <<", __FUNCTION__);
>      return be;
> @@ -524,8 +530,12 @@ void spice_usb_backend_device_unref(SpiceUsbBackendDevice *dev)
>  {
>      LOUD_DEBUG("%s >> %p(%d)", __FUNCTION__, dev, dev->ref_count);
>      if (g_atomic_int_dec_and_test(&dev->ref_count)) {
> -        libusb_unref_device(dev->libusb_device);
> -        LOUD_DEBUG("%s freeing %p (libusb %p)", __FUNCTION__, dev, dev->libusb_device);
> +        if (dev->libusb_device) {
> +            libusb_unref_device(dev->libusb_device);
> +            LOUD_DEBUG("%s freeing %p (libusb %p)", __FUNCTION__, dev, dev->libusb_device);
> +        } else if (dev->edev) {
> +            device_ops(dev->edev)->delete(dev->edev);
> +        }
>          g_free(dev);
>      }
>  }
> @@ -824,4 +834,118 @@ spice_usb_backend_channel_get_guest_filter(SpiceUsbBackendChannel *ch,
>      }
>  }
>  
> +gchar * spice_usb_backend_device_description(SpiceUsbBackendDevice *dev,
> +                                             const gchar *format)

Style: extra space after asterisk

> +{
> +    if (!dev->edev) {
> +        return g_strdup("Not available for libusb devices");
> +    }
> +    gchar *description, *descriptor, *product;
> +    descriptor = g_strdup_printf("[%04x:%04x]", dev->device_info.vid, dev->device_info.pid);
> +    product = device_ops(dev->edev)->get_product_description(dev->edev);
> +    description = g_strdup_printf(format, "", product, descriptor,
> +                                  dev->device_info.bus, dev->device_info.address);
> +    g_free(descriptor);
> +    g_free(product);
> +    return description;
> +}
> +
> +void spice_usb_backend_register_device_type(SpiceUsbBackend *be,
> +                                            UsbEmulatedDeviceType dev_type,
> +                                            SpiceUsbEmulatedDeviceCreate init_proc)
> +{
> +    if (dev_type >= USB_DEV_TYPE_MAX) {
> +        g_return_if_reached();
> +    }

g_return_if_fail(dev_type < USB_DEV_TYPE_MAX); ?

> +    be->dev_init[dev_type] = init_proc;
> +}
> +
> +void spice_usb_backend_device_report_change(SpiceUsbBackend *be,
> +                                            SpiceUsbBackendDevice *dev)
> +{
> +    gchar *desc;
> +    g_return_if_fail(dev && dev->edev);
> +
> +    desc = device_ops(dev->edev)->get_product_description(dev->edev);
> +    SPICE_DEBUG("%s: %s", __FUNCTION__, desc);
> +    g_free(desc);

Curious about this function, looking at the whole set this is
called on cd_usb_bulk_msd_lun_changed() which has the comment
"called when a change happens on SCSI layer"

What kind of change can it happen on the emulated device?

> +}
> +
> +void spice_usb_backend_device_eject(SpiceUsbBackend *be, SpiceUsbBackendDevice *dev)
> +{
> +    g_return_if_fail(dev && dev->edev);
> +
> +    if (be->hotplug_callback) {

Wondering if this should not be a
g_return_if_fail(be->hotplug_callback != NULL);

This callback is unset only on
spice_usb_backend_deregister_hotplug() so it should be somewhat
programming error having spice_usb_backend_device_eject() being
called after that, no? That could also make not wanted unref to
dev

> +        be->hotplug_callback(be->hotplug_user_data, dev, FALSE);
> +    }
> +    spice_usb_backend_device_unref(dev);
> +}
> +
> +gboolean spice_usb_backend_create_device(SpiceUsbBackend *be,
> +                                         UsbEmulatedDeviceType dev_type,
> +                                         UsbCreateDeviceParameters *param)
> +{
> +    SpiceUsbEmulatedDevice *edev = NULL;
> +    SpiceUsbBackendDevice  *dev;
> +    struct libusb_device_descriptor *desc;
> +    uint16_t device_desc_size;
> +    uint8_t address = 0;
> +
> +    if (dev_type >= USB_DEV_TYPE_MAX || !be->dev_init[dev_type]) {
> +        param->error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                                   _("can't create device - not supported"));
> +        return FALSE;
> +    }
> +
> +    if (be->own_devices_mask == 0xffffffff) {
> +        param->error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                                   _("can't create device - limit reached"));
> +        return FALSE;
> +    }
> +    for (address = 0; address < 32; ++address) {
> +        if (~be->own_devices_mask & (1 << address)) {
> +            break;
> +        }
> +    }
> +
> +    dev = g_new0(SpiceUsbBackendDevice, 1);
> +
> +    param->address = address;
> +    if (be->dev_init[dev_type](be, dev, param, &edev)) {
> +        g_free(dev);
> +        return FALSE;
> +    }
> +
> +    if (!device_ops(edev)->get_descriptor(edev, LIBUSB_DT_DEVICE, 0,
> +                                          (void **)&desc, &device_desc_size)
> +        || device_desc_size != sizeof(*desc)) {
> +
> +        device_ops(edev)->delete(edev);
> +        g_free(dev);
> +        param->error = g_error_new(SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
> +                                   _("can't create device - internal error"));
> +        return FALSE;
> +    }
> +
> +    be->own_devices_mask |= 1 << address;

I see the bit is set to 1 but shouldn't it be there a place where
the bit is set to 0, let's say, when device is disconnected?

> +
> +    dev->device_info.bus = BUS_NUMBER_FOR_EMULATED_USB;
> +    dev->device_info.address = address;
> +    dev->device_info.vid = desc->idVendor;
> +    dev->device_info.pid = desc->idProduct;
> +    dev->device_info.bcdUSB = desc->bcdUSB;
> +    dev->device_info.class = desc->bDeviceClass;
> +    dev->device_info.subclass = desc->bDeviceSubClass;
> +    dev->device_info.protocol = desc->bDeviceProtocol;
> +    dev->device_info.device_type = dev_type;
> +    dev->ref_count = 1;
> +    dev->edev = edev;
> +
> +    if (be->hotplug_callback) {
> +        be->hotplug_callback(be->hotplug_user_data, dev, TRUE);
> +    }
> +
> +    return TRUE;
> +}
> +
>  #endif /* USB_REDIR */
> diff --git a/src/usb-backend.h b/src/usb-backend.h
> index 69a490b..8a04ed0 100644
> --- a/src/usb-backend.h
> +++ b/src/usb-backend.h
> @@ -31,15 +31,24 @@ typedef struct _SpiceUsbBackend SpiceUsbBackend;
>  typedef struct _SpiceUsbBackendDevice SpiceUsbBackendDevice;
>  typedef struct _SpiceUsbBackendChannel SpiceUsbBackendChannel;
>  
> +typedef enum
> +{
> +    USB_DEV_TYPE_NONE,
> +    USB_DEV_TYPE_CD,
> +    USB_DEV_TYPE_MAX
> +} UsbEmulatedDeviceType;
> +
>  typedef struct UsbDeviceInformation
>  {
>      uint16_t bus;
>      uint16_t address;
>      uint16_t vid;
>      uint16_t pid;
> +    uint16_t bcdUSB;
>      uint8_t class;
>      uint8_t subclass;
>      uint8_t protocol;
> +    uint8_t device_type; /* UsbEmulatedDeviceType */
>  } UsbDeviceInformation;
>  
>  typedef void(*usb_hot_plug_callback)(void *user_data, SpiceUsbBackendDevice *dev, gboolean added);
> @@ -71,7 +80,6 @@ gboolean spice_usb_backend_device_isoch(SpiceUsbBackendDevice *dev);
>  /* returns 0 if the device passes the filter */
>  int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev,
>                                            const struct usbredirfilter_rule *rules, int count);
> -
>  /* Spice USB backend channel API */
>  SpiceUsbBackendChannel *spice_usb_backend_channel_new(SpiceUsbBackend *context,
>                                                        void            *user_data);
> @@ -89,6 +97,32 @@ void spice_usb_backend_channel_get_guest_filter(SpiceUsbBackendChannel *ch,
>                                                  int *count);
>  void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data);
>  
> +#define BUS_NUMBER_FOR_EMULATED_USB         255

Is there a specific reason for it to be 255 (like standard)? If
yes, I'd add a small comment above it.

> +
> +typedef struct UsbCreateDeviceParameters
> +{
> +    GError *error;
> +    uint32_t    address;
> +    uint32_t    reserved;
> +    union
> +    {
> +        struct
> +        {
> +            const char *filename;
> +            uint32_t   delete_on_eject  : 1;
> +        } create_cd;
> +    } device_param;
> +
> +} UsbCreateDeviceParameters;
> +
> +/* API related to emulated devices */
> +gboolean spice_usb_backend_create_device(SpiceUsbBackend *be,
> +                                         UsbEmulatedDeviceType dev_type,
> +                                         UsbCreateDeviceParameters *param);
> +gchar *spice_usb_backend_device_description(SpiceUsbBackendDevice *dev, const gchar *format);
> +void spice_usb_backend_device_eject(SpiceUsbBackend *be, SpiceUsbBackendDevice *device);
> +void spice_usb_backend_device_report_change(SpiceUsbBackend *be, SpiceUsbBackendDevice *device);
> +
>  G_END_DECLS
>  
>  #endif
> diff --git a/src/usb-emulation.h b/src/usb-emulation.h
> new file mode 100644
> index 0000000..4473361
> --- /dev/null
> +++ b/src/usb-emulation.h
> @@ -0,0 +1,88 @@
> +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
> +/*
> +    Copyright (C) 2019 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_EMULATION_H__
> +#define __SPICE_USB_EMULATION_H__
> +
> +#include "usbredirparser.h"
> +#include "usb-backend.h"
> +
> +typedef struct _SpiceUsbEmulatedDevice SpiceUsbEmulatedDevice;
> +typedef int (*SpiceUsbEmulatedDeviceCreate)(SpiceUsbBackend *be,
> +                                            SpiceUsbBackendDevice *parent,
> +                                            UsbCreateDeviceParameters *param,
> +                                            SpiceUsbEmulatedDevice **device);
> +
> +/*
> +    function table for emulated USB device
> +    must be first member of device structure
> +    all functions are mandatory for implementation
> +*/
> +typedef struct UsbDeviceOps
> +{
> +    gboolean (*get_descriptor)(SpiceUsbEmulatedDevice *device,
> +                               uint8_t type, uint8_t index,
> +                               void **buffer, uint16_t *size);
> +    gchar * (*get_product_description)(SpiceUsbEmulatedDevice *device);
> +    void (*attach)(SpiceUsbEmulatedDevice *device, struct usbredirparser *parser);
> +    void (*reset)(SpiceUsbEmulatedDevice *device);
> +    /*
> +        processing is synchronous, default = stall:
> +        - return success without data: set status to 0
> +        - return error - set status to error
> +        - return success with data - set status to 0,
> +                                    set buffer to some buffer
> +                                    set length to out len
> +                                    truncation is automatic
> +    */
> +    void (*control_request)(SpiceUsbEmulatedDevice *device,
> +                            uint8_t *data, int data_len,
> +                            struct usb_redir_control_packet_header *h,
> +                            void **buffer);
> +    /*
> +        processing is synchronous:
> +        - set h->status to resulting status, default = stall
> +    */
> +    void (*bulk_out_request)(SpiceUsbEmulatedDevice *device,
> +                             uint8_t ep, uint8_t *data, int data_len,
> +                             uint8_t *status);
> +    /*
> +        if returns true, processing is asynchronous
> +        otherwise header contains error status
> +    */
> +    gboolean (*bulk_in_request)(SpiceUsbEmulatedDevice *device, uint64_t id,
> +                            struct usb_redir_bulk_packet_header *bulk_header);
> +    void (*cancel_request)(SpiceUsbEmulatedDevice *device, uint64_t id);
> +    void (*detach)(SpiceUsbEmulatedDevice *device);
> +    void (*delete)(SpiceUsbEmulatedDevice *device);
> +} UsbDeviceOps;

Ok,

> +
> +static inline const UsbDeviceOps *device_ops(SpiceUsbEmulatedDevice *dev) {
> +    return (const UsbDeviceOps *)dev;
> +}
> +
> +void spice_usb_backend_register_device_type(SpiceUsbBackend *be,
> +                                            UsbEmulatedDeviceType dev_type,
> +                                            SpiceUsbEmulatedDeviceCreate init_proc);
> +/* supported device types */
> +void spice_usb_device_register_cd(SpiceUsbBackend *be);

Should be added in later patches.

Cheers,
Victor
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/spice-devel/attachments/20190725/1fad3f80/attachment-0001.sig>


More information about the Spice-devel mailing list