Implementation of USB backend interface. For platform-specific operations (like operations with local CD devices) see cd-device-* files. Signed-off-by: Yuri Benditovich <yuri.benditovich@xxxxxxxxxx> --- src/usb-backend-common.c | 2015 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2015 insertions(+) create mode 100644 src/usb-backend-common.c diff --git a/src/usb-backend-common.c b/src/usb-backend-common.c new file mode 100644 index 0000000..2907cf0 --- /dev/null +++ b/src/usb-backend-common.c @@ -0,0 +1,2015 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2018 Red Hat, Inc. + + Red Hat Authors: + Yuri Benditovich<ybendito@xxxxxxxxxx> + + 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" +#include "cd-usb-bulk-msd.h" +#include "cd-device.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 + +#define MAX_LUN_PER_DEVICE 1 + +#if MAX_LUN_PER_DEVICE > 1 +#define MAX_OWN_DEVICES 4 +#else +#define MAX_OWN_DEVICES 8 +#endif + +#define OWN_BUS_NUM 0xff +#define USB2_BCD 0x200 +// TODO: communicate usage of this VID/PID +#define CD_DEV_VID 0x2b23 +#define CD_DEV_PID 0xCDCD +#define CD_DEV_CLASS 8 +#define CD_DEV_SUBCLASS 6 +#define CD_DEV_PROTOCOL 0x50 +#define CD_DEV_BLOCK_SIZE 0x200 +#define CD_DEV_MAX_SIZE 737280000 +#define DVD_DEV_BLOCK_SIZE 0x800 +#define MAX_BULK_IN_REQUESTS 64 + +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; + SpiceCdLU units[MAX_LUN_PER_DEVICE]; +}; + +static struct OwnUsbDevices +{ + uint32_t active_devices; + SpiceUsbBackendDevice devices[MAX_OWN_DEVICES]; +} own_devices; + +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; + backend_device_change_callback dev_change_callback; + uint32_t suppressed : 1; +}; + +/* backend object for device change notification */ +static SpiceUsbBackend *notify_backend; + +struct BufferedBulkRead +{ + struct usb_redir_bulk_packet_header hout; + uint64_t id; +}; + +struct _SpiceUsbBackendChannel +{ + struct usbredirhost *usbredirhost; + struct usbredirhost *hiddenhost; + struct usbredirparser *parser; + struct usbredirparser *hiddenparser; + uint8_t *read_buf; + uint8_t *hello; + int read_buf_size; + int hello_size; + struct usbredirfilter_rule *rules; + int rules_count; + uint32_t host_caps; + uint32_t hello_done_host : 1; + uint32_t hello_done_parser : 1; + uint32_t hello_sent : 1; + uint32_t rejected : 1; + SpiceUsbBackendDevice *attached; + SpiceUsbBackendChannelInitData data; + uint32_t num_reads; + struct BufferedBulkRead read_bulk[MAX_BULK_IN_REQUESTS]; +}; + +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 void indicate_lun_change(SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev) +{ + if (be->dev_change_user_data && be->dev_change_callback) { + be->dev_change_callback(be->dev_change_user_data, bdev); + } +} + +static void indicate_device_presence(SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev, gboolean present) +{ +#ifdef G_OS_WIN32 + spice_usb_backend_indicate_dev_change(); +#else + if (be->hp_callback) { + be->hp_callback(be->hp_user_data, bdev, present); + } +#endif +} + +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; +} + +static gboolean open_stream(SpiceCdLU *unit, const char *filename) +{ + gboolean b = FALSE; + b = cd_device_open_stream(unit, filename) == 0; + return b; +} + +static void close_stream(SpiceCdLU *unit) +{ + if (unit->stream) { + g_object_unref(unit->stream); + unit->stream = NULL; + } + if (unit->file_object) { + g_object_unref(unit->file_object); + unit->file_object = NULL; + } +} + +/* 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 if (ch->parser) { + SPICE_DEBUG("%s ch %p -> usbredirparser", __FUNCTION__, ch); + usbredirparser_do_write(ch->parser); + } + else { + b = FALSE; + } + } + + if (!b) { + SPICE_DEBUG("%s ch %p (not ready)", __FUNCTION__, ch); + } +} + +void cd_usb_bulk_msd_read_complete(void *user_data, + uint8_t *data, uint32_t length, CdUsbBulkStatus status) +{ + SpiceUsbBackendDevice *d = (SpiceUsbBackendDevice *)user_data; + SpiceUsbBackendChannel *ch = d->attached_to; + if (ch && ch->attached == d && ch->parser) { + int nread; + uint32_t offset = 0; + + for (nread = 0; nread <ch->num_reads; nread++) { + uint32_t max_len; + max_len = (ch->read_bulk[nread].hout.length_high << 16) | + ch->read_bulk[nread].hout.length; + if (max_len > length) { + max_len = length; + ch->read_bulk[nread].hout.length = length; + ch->read_bulk[nread].hout.length_high = length >> 16; + } + + switch (status) { + case BULK_STATUS_GOOD: + ch->read_bulk[nread].hout.status = usb_redir_success; + break; + case BULK_STATUS_CANCELED: + ch->read_bulk[nread].hout.status = usb_redir_cancelled; + break; + case BULK_STATUS_ERROR: + ch->read_bulk[nread].hout.status = usb_redir_ioerror; + break; + case BULK_STATUS_STALL: + default: + ch->read_bulk[nread].hout.status = usb_redir_stall; + break; + } + + SPICE_DEBUG("%s: responding %" PRIu64 " with len %u out of %u, status %d", + __FUNCTION__, ch->read_bulk[nread].id, max_len, + length, ch->read_bulk[nread].hout.status); + usbredirparser_send_bulk_packet(ch->parser, ch->read_bulk[nread].id, + &ch->read_bulk[nread].hout, max_len ? (data + offset) : NULL, max_len); + + offset += max_len; + length -= max_len; + } + ch->num_reads = 0; + usbredir_write_flush_callback(ch); + + if (length) { + SPICE_DEBUG("%s: ERROR: %u bytes were not reported!", __FUNCTION__, length); + } + + } else { + SPICE_DEBUG("%s: broken device<->channel relationship!", __FUNCTION__); + } +} + +/* device reset completion callback */ +void cd_usb_bulk_msd_reset_complete(void *user_data, int status) +{ + // SpiceUsbBackendDevice *d = (SpiceUsbBackendDevice *)user_data; +} + +static gboolean load_lun(SpiceUsbBackendDevice *d, int unit, gboolean load) +{ + gboolean b = TRUE; + if (load && d->units[unit].device) { + // there is one possible problem in case our backend is the + // local CD device and it is ejected + cd_device_load(&d->units[unit], TRUE); + close_stream(&d->units[unit]); + if (cd_device_check(&d->units[unit]) || !open_stream(&d->units[unit], NULL)) { + return FALSE; + } + } + + if (load) { + CdScsiMediaParameters media_params = { 0 }; + + media_params.stream = d->units[unit].stream; + media_params.size = d->units[unit].size; + media_params.block_size = d->units[unit].blockSize; + if (media_params.block_size == CD_DEV_BLOCK_SIZE && + media_params.size % DVD_DEV_BLOCK_SIZE == 0) { + media_params.block_size = DVD_DEV_BLOCK_SIZE; + } + SPICE_DEBUG("%s: loading %s, size %" PRIu64 ", block %u", + __FUNCTION__, d->units[unit].filename, media_params.size, media_params.block_size); + + b = !cd_usb_bulk_msd_load(d->d.msc, unit, &media_params); + + d->units[unit].loaded = !!b; + + } else { + SPICE_DEBUG("%s: unloading %s", __FUNCTION__, d->units[unit].filename); + cd_usb_bulk_msd_unload(d->d.msc, unit); + d->units[unit].loaded = FALSE; +// I do not see why we need to eject the physical CD-ROM when the eject +// on VM requested. On Linux this may require root privilege, as this +// affects those who opened files on CD-ROM. I suppose this is user's +// responsibility to keep CD door close when it shares it with VM +#if DO_CD_DEVICE_EJECT + cd_device_load(&d->units[unit], FALSE); +#endif + } + return b; +} + +static gboolean lock_lun(SpiceUsbBackendDevice *d, int unit, gboolean lock) +{ + SPICE_DEBUG("%s: locking %s", __FUNCTION__, d->units[unit].filename); + return !cd_usb_bulk_msd_lock(d->d.msc, unit, lock); +} + +// called when a change happens on SCSI layer +void cd_usb_bulk_msd_lun_changed(void *user_data, uint32_t lun) +{ + SpiceUsbBackendDevice *d = (SpiceUsbBackendDevice *)user_data; + CdScsiDeviceInfo cd_info; + + if (!cd_usb_bulk_msd_get_info(d->d.msc, lun, &cd_info)) { + // load or unload command received from SCSI + if (d->units[lun].loaded != cd_info.loaded) { + if (!load_lun(d, lun, cd_info.loaded)) { + SPICE_DEBUG("%s: load failed, unloading unit", __FUNCTION__); + cd_usb_bulk_msd_unload(d->d.msc, lun); + } + } + } + + if (notify_backend) { + indicate_lun_change(notify_backend, d); + } +} + +static void process_default_parameters(CdScsiDeviceParameters *params, + const SpiceUsbDeviceLunInfo *info, int unit) +{ + if (!params->product || *params->product == 0) { + const char *name = strrchr(info->file_path, '\\'); + if (!name) name = strrchr(info->file_path, '/'); + params->product = name ? (name + 1) : info->file_path; + } + if (!params->version || *params->version == 0) { + static char s[8]; + g_snprintf(s, sizeof(s), "%d", unit); + params->version = s; + } + if (!params->vendor || *params->vendor == 0) { + params->vendor = "SPICE"; + } +} + +static gboolean activate_device(SpiceUsbBackendDevice *d, const SpiceUsbDeviceLunInfo *info, int unit) +{ + gboolean b = FALSE; + CdScsiDeviceParameters dev_params = { 0 }; + dev_params.vendor = info->vendor; + dev_params.product = info->product; + dev_params.version = info->revision; + + process_default_parameters(&dev_params, info, unit); + + if (!d->d.msc) { + d->d.msc = cd_usb_bulk_msd_alloc(d, MAX_LUN_PER_DEVICE); + if (!d->d.msc) { + return FALSE; + } + } + d->units[unit].blockSize = CD_DEV_BLOCK_SIZE; + b = !cd_usb_bulk_msd_realize(d->d.msc, unit, &dev_params); + if (b) { + b = open_stream(&d->units[unit], info->file_path); + if (b && info->loaded) { + b = load_lun(d, unit, TRUE); + if (b && info->locked) { + b = lock_lun(d, unit, TRUE); + } + } + if (!b) { + close_stream(&d->units[unit]); + cd_usb_bulk_msd_unrealize(d->d.msc, unit); + } + } + return b; +} + +static gboolean stop_device(SpiceUsbBackendDevice *d, int unit) +{ + int j; + gboolean empty = TRUE; + + cd_usb_bulk_msd_unrealize(d->d.msc, unit); + if (d->units[unit].filename) { + g_free(d->units[unit].filename); + d->units[unit].filename = NULL; + } + close_stream(&d->units[unit]); + + for (j = 0; empty && j < MAX_LUN_PER_DEVICE; ++j) { + if (d->units[j].filename) { + empty = FALSE; + } + } + if (empty) { + cd_usb_bulk_msd_free(d->d.msc); + d->d.msc = NULL; + d->configured = 0; + } + return empty; +} + +gboolean spice_usb_backend_add_cd_lun(SpiceUsbBackend *be, const SpiceUsbDeviceLunInfo *info) +{ + int i; + gboolean b = FALSE; + + if (be->suppressed) { + SPICE_DEBUG("%s: CD sharing is suppressed", __FUNCTION__); + return FALSE; + } + + for (i = 0; !b && i < MAX_OWN_DEVICES; i++) { + if ((1 << i) & ~own_devices.active_devices) { /* inactive usb device */ + SPICE_DEBUG("%s: add file %s to device %d (activate now) as lun 0", + __FUNCTION__, info->file_path, i); + + b = activate_device(&own_devices.devices[i], info, 0); + if (b) { + own_devices.active_devices |= 1 << i; + indicate_device_presence(be, &own_devices.devices[i], TRUE); + } + } else if (!own_devices.devices[i].attached_to) { + /* active unattached device, add as the next lun */ + int j; + for (j = 0; j < MAX_LUN_PER_DEVICE; j++) { + if (!own_devices.devices[i].units[j].stream) { + SPICE_DEBUG("%s: add file %s to device %d (already active) as lun %d", + __FUNCTION__, info->file_path, i, j); + + b = activate_device(&own_devices.devices[i], info, j); + if (!b) { + SPICE_DEBUG("%s: failed to add file %s to device %d (already active) as lun %d", + __FUNCTION__, info->file_path, i, j); + b = TRUE; /* exit outer loop */ + } else { + indicate_lun_change(be, &own_devices.devices[i]); + } + break; + } + } + } + } + if (!b) { + SPICE_DEBUG("can not create device %s", info->file_path); + } + return b; +} + +static gboolean check_device(SpiceUsbBackendDevice *bdev, guint lun, guint* index) +{ + int i; + + if (lun >= MAX_LUN_PER_DEVICE) { + return FALSE; + } + + for (i = 0; i < MAX_OWN_DEVICES; i++) { + if ((1 << i) & own_devices.active_devices) { /* active usb device */ + if (&own_devices.devices[i] == bdev) { + if (index) { + *index = i; + } + return TRUE; + } + } + } + + return FALSE; +} + +gboolean spice_usb_backend_remove_cd_lun(SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev, guint lun) +{ + char *name; + guint index; + if (!check_device(bdev, lun, &index)) { + return FALSE; + } + + name = bdev->units[lun].filename; + SPICE_DEBUG("%s: unshare %s, unit %u:%u", __FUNCTION__, name, index, lun); + if (stop_device(bdev, lun)) { + /* usb device does not have any active unit, + so we remove it */ + own_devices.active_devices &= ~(1 << index); + indicate_device_presence(be, bdev, FALSE); + } + else { + /* the device still contains active LUN(s) */ + indicate_lun_change(be, bdev); + } + return TRUE; +} + +uint32_t spice_usb_backend_get_cd_luns_bitmask(SpiceUsbBackendDevice *bdev) +{ + int i, value = 0; + + if (!check_device(bdev, 0, NULL)) { + return FALSE; + } + + for (i = 0; i < MAX_LUN_PER_DEVICE; i++) { + char *name = bdev->units[i].filename; + if (name) { + value |= 1 << i; + } + } + + return value; +} + +gboolean spice_usb_backend_get_cd_lun_info(SpiceUsbBackendDevice *bdev, + guint lun, SpiceUsbDeviceLunInfo *info) +{ + if (!check_device(bdev, lun, NULL)) { + return FALSE; + } + + if (bdev->units[lun].filename) { + CdScsiDeviceInfo cd_info; + if (!cd_usb_bulk_msd_get_info(bdev->d.msc, lun, &cd_info)) { + info->started = cd_info.started; + info->loaded = bdev->units[lun].loaded; + info->locked = cd_info.locked; + info->file_path = bdev->units[lun].filename; + info->vendor = cd_info.parameters.vendor; + info->product = cd_info.parameters.product; + info->revision = cd_info.parameters.version; + return TRUE; + } + } + + return FALSE; +} + +gboolean spice_usb_backend_load_cd_lun( + SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, + guint lun, + gboolean load) +{ + if (!check_device(bdev, lun, NULL)) { + return FALSE; + } + + if (bdev->units[lun].filename) { + gboolean b = load_lun(bdev, lun, load); + SPICE_DEBUG("%s: %sload %s", __FUNCTION__, + load ? "" : "un", b ? "succeeded" : "failed"); + if (b) { + indicate_lun_change(be, bdev); + } + return b; + } + + return FALSE; +} + +gboolean spice_usb_backend_lock_cd_lun( + SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, + guint lun, + gboolean lock) +{ + if (!check_device(bdev, lun, NULL)) { + return FALSE; + } + + if (bdev->units[lun].filename) { + gboolean b = lock_lun(bdev, lun, lock); + SPICE_DEBUG("%s: %slock %s", __FUNCTION__, + lock ? "" : "un", b ? "succeeded" : "failed"); + if (b) { + indicate_lun_change(be, bdev); + } + return b; + } + + return FALSE; +} + +gboolean spice_usb_backend_change_cd_lun( + SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, + guint lun, const SpiceUsbDeviceLunInfo *lun_info) +{ + gboolean b = FALSE; + + char *filename; + GFile *file_object; + GFileInputStream *stream; + + if (!check_device(bdev, lun, NULL)) { + return b; + } + + // if the CD is loaded, we need to unload it first + if (bdev->units[lun].loaded) { + SPICE_DEBUG("%s: the unit is loaded, unload it first", __FUNCTION__); + return b; + } + + // keep stream-related data (stream, file object) + filename = bdev->units[lun].filename; + bdev->units[lun].filename = NULL; + + stream = bdev->units[lun].stream; + bdev->units[lun].stream = NULL; + + file_object = bdev->units[lun].file_object; + bdev->units[lun].file_object = NULL; + + // now we can start the LUN with new parameters + b = open_stream(&bdev->units[lun], lun_info->file_path); + if (b) { + b = load_lun(bdev, lun, TRUE); + } + else { + close_stream(&bdev->units[lun]); + } + + if (!b) { + SPICE_DEBUG("%s: failed", __FUNCTION__); + // restore the state of unloaded unit + bdev->units[lun].filename = filename; + bdev->units[lun].stream = stream; + bdev->units[lun].file_object = file_object; + } + else { + SPICE_DEBUG("%s: succeeded", __FUNCTION__); + indicate_lun_change(be, bdev); + } + + return b; +} + +static void initialize_own_devices(void) +{ + int i; + // addresses 0 and 1 excluded as they are treated as + // not suitable for redirection + for (i = 0; i < MAX_OWN_DEVICES; i++) { + own_devices.devices[i].mutex = g_mutex; + own_devices.devices[i].device_info.bus = OWN_BUS_NUM; + own_devices.devices[i].device_info.address = i + 2; + own_devices.devices[i].device_info.vid = CD_DEV_VID; + own_devices.devices[i].device_info.pid = CD_DEV_PID; + own_devices.devices[i].device_info.class = 0; + own_devices.devices[i].device_info.subclass = 0; + own_devices.devices[i].device_info.protocol = 0; + own_devices.devices[i].device_info.max_luns = MAX_LUN_PER_DEVICE; + } +} + +#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(); + initialize_own_devices(); + configure_log(); + } + be = (SpiceUsbBackend *)g_new0(SpiceUsbBackend, 1); + if (be) { +#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); + if (d) { + 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_set_device_change_callback( + SpiceUsbBackend *be, void *user_data, backend_device_change_callback proc) +{ + be->dev_change_user_data = user_data; + be->dev_change_callback = proc; + if (!notify_backend) { + notify_backend = be; + } +} + +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, i, 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 + MAX_OWN_DEVICES); + if (!list) { + libusb_free_device_list(devlist, 1); + return NULL; + } + + for (i = 0; i < MAX_OWN_DEVICES; ++i) { + if (own_devices.active_devices & (1 << i)) { + n++; + } + } + + index = 0; + + for (dev = devlist; dev && *dev; dev++) { + d = g_new0(SpiceUsbBackendDevice, 1); + if (d) { + 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; + } + } + } + + usbredir_lock_lock(g_mutex); + + for (i = 0; i < MAX_OWN_DEVICES; ++i) { + d = &own_devices.devices[i]; + if ((own_devices.active_devices & (1 << i)) && index < n) { + list[index++] = d; + d->refCount++; + SPICE_DEBUG("found own %p, address %d", d, d->device_info.address); + } + } + usbredir_unlock_lock(g_mutex); + + 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); + } else { + uint8_t cls, subcls, proto; + cls = CD_DEV_CLASS; + subcls = CD_DEV_SUBCLASS; + proto = CD_DEV_PROTOCOL; + return usbredirfilter_check(rules, count, + dev->device_info.class, + dev->device_info.subclass, + dev->device_info.protocol, + &cls, &subcls, &proto, 1, dev->device_info.vid, + dev->device_info.pid, USB2_BCD, 0); + } +} + +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 void usbredir_log(void *user_data, int level, const char *msg) +{ + SpiceUsbBackendChannel *ch = (SpiceUsbBackendChannel *)user_data; + + switch (level) { + case usbredirparser_error: + g_critical("%s", msg); + ch->data.log(ch->data.user_data, msg, TRUE); + break; + case usbredirparser_warning: + g_warning("%s", msg); + ch->data.log(ch->data.user_data, msg, TRUE); + break; + default: + ch->data.log(ch->data.user_data, 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); + if (!ch->hello_sent) { + ch->hello_sent = 1; + if (count == 80) { + memcpy(&ch->host_caps, data + 76, 4); + SPICE_DEBUG("%s ch %p, sending first hello, caps %08X", + __FUNCTION__, ch, ch->host_caps); + } + } + 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->hello) { + ch->hello = g_malloc(count); + memcpy(ch->hello, data, count); + ch->hello_size = count; + if (ch->usbredirhost) { + ch->hello_done_host = 1; + } + if (ch->parser) { + ch->hello_done_parser = 1; + } + } + if (ch->usbredirhost) { + fn = (readproc_t)usbredirhost_read_guest_data; + param = ch->usbredirhost; + } + if (ch->parser) { + fn = (readproc_t)usbredirparser_do_read; + param = ch->parser; + } + 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 (ch->parser) { + fn = (retdata)usbredirhost_free_write_buffer; + param = ch->parser; + } + if (fn) { + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); + fn(param, data); + } else { + SPICE_DEBUG("%s ch %p - NOBODY TO CALL", __FUNCTION__, ch); + } +} + +#if 0 +struct usbredirparser { + /* app private data passed into all callbacks as the priv argument */ + void *priv; + /* non packet callbacks */ + usbredirparser_log log_func; + usbredirparser_read read_func; + usbredirparser_write write_func; + /* usb-redir-protocol v0.3 control packet complete callbacks */ + usbredirparser_device_connect device_connect_func; + usbredirparser_device_disconnect device_disconnect_func; + usbredirparser_reset reset_func; + usbredirparser_interface_info interface_info_func; + usbredirparser_ep_info ep_info_func; + usbredirparser_set_configuration set_configuration_func; + usbredirparser_get_configuration get_configuration_func; + usbredirparser_configuration_status configuration_status_func; + usbredirparser_set_alt_setting set_alt_setting_func; + usbredirparser_get_alt_setting get_alt_setting_func; + usbredirparser_alt_setting_status alt_setting_status_func; + usbredirparser_start_iso_stream start_iso_stream_func; + usbredirparser_stop_iso_stream stop_iso_stream_func; + usbredirparser_iso_stream_status iso_stream_status_func; + usbredirparser_start_interrupt_receiving start_interrupt_receiving_func; + usbredirparser_stop_interrupt_receiving stop_interrupt_receiving_func; + usbredirparser_interrupt_receiving_status interrupt_receiving_status_func; + usbredirparser_alloc_bulk_streams alloc_bulk_streams_func; + usbredirparser_free_bulk_streams free_bulk_streams_func; + usbredirparser_bulk_streams_status bulk_streams_status_func; + usbredirparser_cancel_data_packet cancel_data_packet_func; + /* usb-redir-protocol v0.3 data packet complete callbacks */ + usbredirparser_control_packet control_packet_func; + usbredirparser_bulk_packet bulk_packet_func; + usbredirparser_iso_packet iso_packet_func; + usbredirparser_interrupt_packet interrupt_packet_func; + /* usbredir 0.3.2 new non packet callbacks (for multi-thread locking) */ + usbredirparser_alloc_lock alloc_lock_func; + usbredirparser_lock lock_func; + usbredirparser_unlock unlock_func; + usbredirparser_free_lock free_lock_func; + /* usbredir 0.3.2 new control packet complete callbacks */ + usbredirparser_hello hello_func; + /* usbredir 0.4 new control packet complete callbacks */ + usbredirparser_filter_reject filter_reject_func; + usbredirparser_filter_filter filter_filter_func; + usbredirparser_device_disconnect_ack device_disconnect_ack_func; + /* usbredir 0.6 new control packet complete callbacks */ + usbredirparser_start_bulk_receiving start_bulk_receiving_func; + usbredirparser_stop_bulk_receiving stop_bulk_receiving_func; + usbredirparser_bulk_receiving_status bulk_receiving_status_func; + /* usbredir 0.6 new data packet complete callbacks */ + usbredirparser_buffered_bulk_packet buffered_bulk_packet_func; +}; +#endif + +static void *get_device_string(SpiceUsbBackendDevice *d, uint16_t index, uint8_t *len) +{ + static uint16_t s0[2] = { 0x304, 0x409 }; + static uint16_t s1[8] = { 0x310, 'R', 'e', 'd', ' ', 'H', 'a', 't' }; + static uint16_t s2[9] = { 0x312, 'S', 'p', 'i', 'c', 'e', ' ', 'C', 'D' }; + static uint16_t s3[4] = { 0x308, 'X', '0', '0' }; + void *p = NULL; + switch (index) + { + case 0: + p = s0; *len = sizeof(s0); break; + case 1: + p = s1; *len = sizeof(s1); break; + case 2: + p = s2; *len = sizeof(s2); break; + case 3: + s3[2] = '0' + d->device_info.address / 10; + s3[3] = '0' + d->device_info.address % 10; + p = s3; *len = sizeof(s3); break; + } + return p; +} + +static void get_device_descriptor(SpiceUsbBackendDevice *d, + struct usb_redir_control_packet_header *h) +{ + uint8_t *buffer = (uint8_t *)(h + 1); + const void *p = NULL; + uint8_t len = 0; + static const struct libusb_device_descriptor desc = + { + .bLength = 18, + .bDescriptorType = LIBUSB_DT_DEVICE, + .bcdUSB = USB2_BCD, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = CD_DEV_VID, + .idProduct = CD_DEV_PID, + .bcdDevice = 0x100, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1 + }; + static const uint8_t cfg[] = + { + 9, //len of cfg desc + LIBUSB_DT_CONFIG, // desc type + 0x20, // wlen + 0, + 1, // num if + 1, // cfg val + 0, // cfg name + 0x80, // bus powered + 0x32, // 100 ma + 9, // len of IF desc + LIBUSB_DT_INTERFACE, + 0, // num if + 0, // alt setting + 2, // num of endpoints + CD_DEV_CLASS, + CD_DEV_SUBCLASS, + CD_DEV_PROTOCOL, + 0, // if name + 7, + LIBUSB_DT_ENDPOINT, + 0x81, //->Direction : IN - EndpointID : 1 + 0x02, //->Bulk Transfer Type + 0, //wMaxPacketSize : 0x0200 = 0x200 max bytes + 2, + 0, //bInterval + 7, + LIBUSB_DT_ENDPOINT, + 0x02, //->Direction : OUT - EndpointID : 2 + 0x02, //->Bulk Transfer Type + 0, //wMaxPacketSize : 0x0200 = 0x200 max bytes + 2, + 0, //bInterval + }; + switch (h->value >> 8) { + case LIBUSB_DT_DEVICE: + p = &desc; + len = sizeof(desc); + break; + case LIBUSB_DT_CONFIG: + p = cfg; + len = sizeof(cfg); + break; + case LIBUSB_DT_STRING: + p = get_device_string(d, h->value & 0xff, &len); + break; + } + if (p && len) { + len = MIN(len, h->length); + memcpy(buffer, p, len); + h->length = len; + } else { + h->length = 0; + h->status = usb_redir_stall; + } +} + +static void usbredir_control_packet(void *priv, + uint64_t id, struct usb_redir_control_packet_header *h, + uint8_t *data, int data_len) +{ + SpiceUsbBackendChannel *ch = priv; + struct { + struct usb_redir_control_packet_header h; + uint8_t buffer[255]; + } response = { 0 }; + uint8_t reqtype = h->requesttype & 0x7f; + response.h = *h; + response.h.status = 0; + SPICE_DEBUG("%s %p: TRVIL %02X %02X %04X %04X %04X", + __FUNCTION__, + ch, h->requesttype, h->request, + h->value, h->index, h->length); + if (!ch->attached) { + // device already detached + response.h.status = usb_redir_ioerror; + response.h.length = 0; + } else if (reqtype == (LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE)) { + switch (h->request) { + case LIBUSB_REQUEST_GET_DESCRIPTOR: + get_device_descriptor(ch->attached, &response.h); + break; + default: + response.h.length = 0; + break; + } + } else if (reqtype == (LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_ENDPOINT)) { + // should be clear stall request + response.h.length = 0; + response.h.status = 0; + } else if (reqtype == (LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE)) { + response.h.length = 0; + response.h.status = 0; + switch (h->request) { + case 0xFF: + // mass-storage class request 'reset' + break; + case 0xFE: + // mass-storage class request 'get max lun' + // returning one byte + if (!h->length) { + response.h.status = usb_redir_stall; + } else { + response.h.length = 1; + response.buffer[0] = MAX_LUN_PER_DEVICE - 1; + } + break; + default: + break; + } + } + else { + response.h.length = 0; + response.h.status = usb_redir_stall; + } + SPICE_DEBUG("%s responding with payload of %02X, status %X", + __FUNCTION__, + response.h.length, response.h.status); + usbredirparser_send_control_packet(ch->parser, id, &response.h, + response.h.length ? response.buffer : NULL, response.h.length); + usbredir_write_flush_callback(ch); + usbredirparser_free_packet_data(ch->parser, data); +} + +static void usbredir_bulk_packet(void *priv, + uint64_t id, struct usb_redir_bulk_packet_header *h, + uint8_t *data, int data_len) +{ + SpiceUsbBackendChannel *ch = priv; + SpiceUsbBackendDevice *d = ch->attached; + struct usb_redir_bulk_packet_header hout = *h; + uint32_t len = (h->length_high << 16) | h->length; + SPICE_DEBUG("%s %p: ep %X, len %u, id %" PRIu64, __FUNCTION__, + ch, h->endpoint, len, id); + if (!d || !d->d.msc) { + SPICE_DEBUG("%s: device not attached or not realized", __FUNCTION__); + hout.status = usb_redir_ioerror; + hout.length = hout.length_high = 0; + SPICE_DEBUG("%s: responding (a) with ZLP status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, + &hout, NULL, 0); + } else if (h->endpoint & LIBUSB_ENDPOINT_IN) { + if (ch->num_reads < MAX_BULK_IN_REQUESTS) { + int res; + if (ch->num_reads) { + SPICE_DEBUG("%s: already has %u pending reads", __FUNCTION__, ch->num_reads); + } + ch->read_bulk[ch->num_reads].hout = *h; + ch->read_bulk[ch->num_reads].id = id; + ch->num_reads++; + res = cd_usb_bulk_msd_read(d->d.msc, len); + if (res) { + SPICE_DEBUG("%s: error on bulk read", __FUNCTION__); + ch->num_reads--; + hout.length = hout.length_high = 0; + hout.status = usb_redir_ioerror; + SPICE_DEBUG("%s: responding (b) with ZLP status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, &hout, NULL, 0); + } + + } else { + SPICE_DEBUG("%s: too many pending reads", __FUNCTION__); + hout.status = usb_redir_babble; + hout.length = hout.length_high = 0; + SPICE_DEBUG("%s: responding (a) with ZLP status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, &hout, NULL, 0); + } + } else { + cd_usb_bulk_msd_write(d->d.msc, data, data_len); + hout.status = usb_redir_success; + SPICE_DEBUG("%s: responding status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, &hout, NULL, 0); + } + + usbredirparser_free_packet_data(ch->parser, data); + usbredir_write_flush_callback(ch); +} + +static void usbredir_device_reset(void *priv) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); + if (ch->attached) { + cd_usb_bulk_msd_reset(ch->attached->d.msc); + } +} + +static void usbredir_interface_info(void *priv, + struct usb_redir_interface_info_header *interface_info) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s not implemented %p", __FUNCTION__, ch); +} + +static void usbredir_interface_ep_info(void *priv, + struct usb_redir_ep_info_header *ep_info) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s not implemented %p", __FUNCTION__, ch); +} + +static void usbredir_set_configuration(void *priv, + uint64_t id, struct usb_redir_set_configuration_header *set_configuration) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_configuration_status_header h; + h.status = 0; + h.configuration = set_configuration->configuration; + SPICE_DEBUG("%s ch %p, cfg %d", __FUNCTION__, ch, h.configuration); + if (ch->attached) { + ch->attached->configured = h.configuration != 0; + } + usbredirparser_send_configuration_status(ch->parser, id, &h); + usbredir_write_flush_callback(ch); +} + +static void usbredir_get_configuration(void *priv, uint64_t id) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_configuration_status_header h; + h.status = 0; + h.configuration = ch->attached && ch->attached->configured; + SPICE_DEBUG("%s ch %p, cfg %d", __FUNCTION__, ch, h.configuration); + usbredirparser_send_configuration_status(ch->parser, id, &h); + usbredir_write_flush_callback(ch); +} + +static void usbredir_set_alt_setting(void *priv, + uint64_t id, struct usb_redir_set_alt_setting_header *s) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_alt_setting_status_header sh; + sh.status = (!s->interface && !s->alt) ? 0 : usb_redir_stall; + sh.interface = s->interface; + sh.alt = s->alt; + SPICE_DEBUG("%s ch %p, %d:%d", __FUNCTION__, ch, s->interface, s->alt); + usbredirparser_send_alt_setting_status(ch->parser, id, &sh); + usbredir_write_flush_callback(ch); +} + +static void usbredir_get_alt_setting(void *priv, + uint64_t id, struct usb_redir_get_alt_setting_header *s) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_alt_setting_status_header sh; + sh.status = (s->interface == 0) ? 0 : usb_redir_stall; + sh.interface = s->interface; + sh.alt = 0; + SPICE_DEBUG("%s ch %p, if %d", __FUNCTION__, ch, s->interface); + usbredirparser_send_alt_setting_status(ch->parser, id, &sh); + usbredir_write_flush_callback(ch); +} + +static void usbredir_cancel_data(void *priv, uint64_t id) +{ + SpiceUsbBackendChannel *ch = priv; + SpiceUsbBackendDevice *d = ch->attached; + int nread, found = -1; + SPICE_DEBUG("%s ch %p id %" PRIu64 ", num_reads %u", + __FUNCTION__, ch, id, ch->num_reads); + + if (!d || !d->d.msc) { + SPICE_DEBUG("%s: device not attached or not realized", __FUNCTION__); + return; + } + + for (nread = 0; nread < ch->num_reads; nread++) { + if (ch->read_bulk[nread].id == id) { + found = nread; + break; + } + } + if (found >= 0) { + if (cd_usb_bulk_msd_cancel_read(d->d.msc)) { + cd_usb_bulk_msd_read_complete(d, NULL, 0, BULK_STATUS_CANCELED); + } + } else { + SPICE_DEBUG("%s: ERROR: no such id to cancel!", __FUNCTION__); + } +} + +static void usbredir_filter_reject(void *priv) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s %p", __FUNCTION__, ch); + ch->rejected = 1; +} + +/* Note that the ownership of the rules array is passed on to the callback. */ +static void usbredir_filter_filter(void *priv, + struct usbredirfilter_rule *r, int count) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s ch %p %d filters", __FUNCTION__, ch, count); + ch->rules = r; + ch->rules_count = count; + if (count) { + int i; + for (i = 0; i < count; i++) { + SPICE_DEBUG("%s class %d, %X:%X", + r[i].allow ? "allowed" : "denied", r[i].device_class, + (uint32_t)r[i].vendor_id, (uint32_t)r[i].product_id); + } + } +} + +static void usbredir_device_disconnect_ack(void *priv) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); + if (ch->parser) { + ch->parser = NULL; + SPICE_DEBUG("%s switch to usbredirhost", __FUNCTION__); + ch->usbredirhost = ch->hiddenhost; + } +} + +static void usbredir_hello(void *priv, + struct usb_redir_hello_header *hello) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_device_connect_header device_connect; + struct usb_redir_ep_info_header ep_info = { 0 }; + struct usb_redir_interface_info_header interface_info = { 0 }; + SPICE_DEBUG("%s %p %sattached %s", __FUNCTION__, ch, + ch->attached ? "" : "not ", hello ? "" : "(internal)"); + if (ch->attached) { + interface_info.interface_count = 1; + interface_info.interface_class[0] = CD_DEV_CLASS; + interface_info.interface_subclass[0] = CD_DEV_SUBCLASS; + interface_info.interface_protocol[0] = CD_DEV_PROTOCOL; + usbredirparser_send_interface_info(ch->parser, &interface_info); + + ep_info.type[0x11] = 2; + ep_info.max_packet_size[0x11] = 512; + ep_info.type[0x02] = 2; + ep_info.max_packet_size[0x02] = 512; + usbredirparser_send_ep_info(ch->parser, &ep_info); + + device_connect.device_class = 0; + device_connect.device_subclass = 0; + device_connect.device_protocol = 0; + device_connect.vendor_id = ch->attached->device_info.vid; + device_connect.product_id = ch->attached->device_info.pid; + device_connect.device_version_bcd = USB2_BCD; + device_connect.speed = usb_redir_speed_high; + usbredirparser_send_device_connect(ch->parser, &device_connect); + usbredir_write_flush_callback(ch); + } +} + +/* + We initialize the usbredirparser with HELLO enabled only in case + the libusb is not active and the usbredirhost does not function. + Then the parser sends session HELLO and receives server's response. + Otherwise (usbredirparser initialized with HELLO disabled): + - the usbredirhost sends session HELLO + - we look into it to know set of capabilities we shall initialize + the parser with + - we cache server's response to HELLO and provide it to parser on + first activation (attach of emulated device) to have it synchronized + with server's capabilities +*/ +static struct usbredirparser *create_parser(SpiceUsbBackendChannel *ch, gboolean bHello) +{ + struct usbredirparser *parser = usbredirparser_create(); + if (parser) { + uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0 }; + uint32_t flags = bHello ? 0 : usbredirparser_fl_no_hello; + flags |= usbredirparser_fl_write_cb_owns_buffer | + usbredirparser_fl_usb_host; + parser->priv = ch; + parser->log_func = usbredir_log; + parser->read_func = usbredir_read_callback; + parser->write_func = usbredir_write_callback; + parser->reset_func = usbredir_device_reset; + parser->interface_info_func = usbredir_interface_info; + parser->ep_info_func = usbredir_interface_ep_info; + parser->set_configuration_func = usbredir_set_configuration; + parser->get_configuration_func = usbredir_get_configuration; + parser->set_alt_setting_func = usbredir_set_alt_setting; + parser->get_alt_setting_func = usbredir_get_alt_setting; + parser->cancel_data_packet_func = usbredir_cancel_data; + parser->control_packet_func = usbredir_control_packet; + parser->bulk_packet_func = usbredir_bulk_packet; + parser->alloc_lock_func = usbredir_alloc_lock; + parser->lock_func = usbredir_lock_lock; + parser->unlock_func = usbredir_unlock_lock; + parser->free_lock_func = usbredir_free_lock; + parser->hello_func = usbredir_hello; + parser->filter_reject_func = usbredir_filter_reject; + parser->filter_filter_func = usbredir_filter_filter; + parser->device_disconnect_ack_func = usbredir_device_disconnect_ack; + + if (bHello) { + ch->hello_sent = 1; + ch->host_caps |= 1 << usb_redir_cap_connect_device_version; + ch->host_caps |= 1 << usb_redir_cap_device_disconnect_ack; + ch->host_caps |= 1 << usb_redir_cap_ep_info_max_packet_size; + ch->host_caps |= 1 << usb_redir_cap_64bits_ids; + ch->host_caps |= 1 << usb_redir_cap_32bits_bulk_length; + } + + if (ch->host_caps & (1 << usb_redir_cap_connect_device_version)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); + } + usbredirparser_caps_set_cap(caps, usb_redir_cap_filter); + if (ch->host_caps & (1 << usb_redir_cap_device_disconnect_ack)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_device_disconnect_ack); + } + if (ch->host_caps & (1 << usb_redir_cap_ep_info_max_packet_size)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); + } + if (ch->host_caps & (1 << usb_redir_cap_64bits_ids)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); + } + if (ch->host_caps & (1 << usb_redir_cap_32bits_bulk_length)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); + } + if (ch->host_caps & (1 << usb_redir_cap_bulk_streams)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams); + } + usbredirparser_init(parser, PACKAGE_STRING, caps, USB_REDIR_CAPS_SIZE, flags); + } + + return parser; +} + +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 && dev->isLibUsb) { + libusb_device_handle *handle = NULL; + int rc = libusb_open(dev->d.libusb_device, &handle); + b = rc == 0 && handle; + if (b) { + if (!ch->usbredirhost) { + ch->usbredirhost = ch->hiddenhost; + ch->parser = NULL; + } + 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; + if (ch->hello && !ch->hello_done_host) { + SPICE_DEBUG("%s sending cached hello to host", __FUNCTION__); + ch->hello_done_host = 1; + spice_usb_backend_provide_read_data(ch, ch->hello, ch->hello_size); + } + } + } else { + const char *desc = spice_usbutil_libusb_strerror(rc); + g_warning("Error libusb_open: %s [%i]", desc, rc); + *msg = desc; + } + } else if (!dev) { + SPICE_DEBUG("%s intentional sleep", __FUNCTION__); + g_usleep(100000); + if (ch->usbredirhost) { + // it will call libusb_close internally + usbredirhost_set_device(ch->usbredirhost, NULL); + } else { + // CD redir detach + usbredirparser_send_device_disconnect(ch->parser); + usbredir_write_flush_callback(ch); + } + SPICE_DEBUG("%s ch %p, detach done", __FUNCTION__, ch); + ch->attached->attached_to = NULL; + ch->attached = NULL; + b = TRUE; + } else { + // CD redir attach + b = TRUE; + ch->usbredirhost = NULL; + ch->parser = ch->hiddenparser; + ch->attached = dev; + dev->attached_to = ch; + if (ch->hello_done_parser) { + // send device info + usbredir_hello(ch, NULL); + } else if (ch->hello) { + SPICE_DEBUG("%s sending cached hello to parser", __FUNCTION__); + ch->hello_done_parser = 1; + spice_usb_backend_provide_read_data(ch, ch->hello, ch->hello_size); + usbredir_write_flush_callback(ch); + } + } + return b; +} + +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; + if (ch) { + ch->data = *init_data; + ch->hiddenhost = !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->hiddenhost != NULL; + if (ch->hiddenhost) { +#if USBREDIR_VERSION >= 0x000701 + usbredirhost_set_buffered_output_size_cb(ch->hiddenhost, usbredir_buffered_output_size_callback); +#endif + } + } + + if (ok) { + ch->usbredirhost = ch->hiddenhost; + } + + if (ch && !ok) { + g_error("Out of memory allocating usbredir or parser"); + if (ch->hiddenhost) { + usbredirhost_close(ch->hiddenhost); + } + 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); + ch->hiddenparser = create_parser(ch, FALSE); + } else if (!ch->hiddenparser) { + ch->hiddenparser = create_parser(ch, TRUE); + ch->parser = ch->hiddenparser; + usbredirparser_do_write(ch->parser); + } +} + +void spice_usb_backend_channel_finalize(SpiceUsbBackendChannel *ch) +{ + SPICE_DEBUG("%s >> %p", __FUNCTION__, ch); + if (ch->usbredirhost) { + usbredirhost_close(ch->usbredirhost); + } + if (ch->parser) { + usbredirparser_destroy(ch->parser); + } + if (ch->hello) { + g_free(ch->hello); + } + + 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); + } +} + +gboolean spice_usb_backend_device_get_info_by_address(guint8 bus, guint8 addr, UsbDeviceInformation *info) +{ + int i; + if (bus != OWN_BUS_NUM) { + return FALSE; + } + for (i = 0; i < MAX_OWN_DEVICES; i++) { + if (own_devices.devices[i].device_info.address == addr) { + *info = own_devices.devices[i].device_info; + return TRUE; + } + } + return FALSE; +} + +#endif // USB_REDIR -- 2.9.4 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel