[Spice-commits] 12 commits - src/cd-scsi.c src/cd-scsi-dev-params.h src/cd-scsi.h src/cd-usb-bulk-msd.c src/cd-usb-bulk-msd.h src/channel-usbredir.c src/meson.build src/scsi-constants.h src/usb-backend.c src/usb-backend.h src/usb-device-cd.c src/usb-device-cd.h src/usb-device-manager.c src/usb-emulation.h

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon Sep 23 13:02:56 UTC 2019


 src/cd-scsi-dev-params.h |   46 
 src/cd-scsi.c            | 2742 +++++++++++++++++++++++++++++++++++++++++++++++
 src/cd-scsi.h            |  117 ++
 src/cd-usb-bulk-msd.c    |  543 +++++++++
 src/cd-usb-bulk-msd.h    |  131 ++
 src/channel-usbredir.c   |   31 
 src/meson.build          |   13 
 src/scsi-constants.h     |  321 +++++
 src/usb-backend.c        |  769 ++++++++++++-
 src/usb-backend.h        |    8 
 src/usb-device-cd.c      |  784 +++++++++++++
 src/usb-device-cd.h      |   34 
 src/usb-device-manager.c |   29 
 src/usb-emulation.h      |   88 +
 14 files changed, 5570 insertions(+), 86 deletions(-)

New commits:
commit 50953f236030484f3be998e239aee1f0d4a87e43
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Thu Sep 19 16:11:25 2019 +0200

    usb-redir: enable redirection of emulated CD drive
    
    Add implementation of emulated device to build.
    Now it is possible to create emulated CD devices.
    
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/meson.build b/src/meson.build
index 4d9215c..00eb277 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -157,6 +157,18 @@ elif spice_gtk_coroutine == 'winfiber'
   spice_client_glib_sources += 'coroutine_winfibers.c'
 endif
 
+if spice_gtk_has_usbredir
+  spice_client_glib_sources += [
+    'usb-device-cd.c',
+    'usb-device-cd.h',
+    'cd-scsi.c',
+    'cd-scsi.h',
+    'cd-scsi-dev-params.h',
+    'cd-usb-bulk-msd.c',
+    'cd-usb-bulk-msd.h',
+  ]
+endif
+
 if spice_gtk_has_usbredir and host_machine.system() == 'windows'
   spice_client_glib_sources += ['usbdk_api.c',
                                 'usbdk_api.h']
commit f771f701ad1d52097532415add599886cce60626
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Thu Sep 19 16:11:24 2019 +0200

    usb-redir: add implementation of emulated CD device
    
    This module contains implementation of emulated device
    interface for shared CD.
    
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/usb-device-cd.c b/src/usb-device-cd.c
new file mode 100644
index 0000000..2f421c4
--- /dev/null
+++ b/src/usb-device-cd.c
@@ -0,0 +1,784 @@
+/* -*- 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/>.
+*/
+
+#include "config.h"
+
+#ifdef USE_USBREDIR
+
+#include <glib-object.h>
+#include <inttypes.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <errno.h>
+#include <libusb.h>
+#include <fcntl.h>
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include <ntddcdrm.h>
+#include <ntddmmc.h>
+#else
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+#include <linux/cdrom.h>
+#endif
+
+#include "usb-emulation.h"
+#include "usb-device-cd.h"
+#include "cd-usb-bulk-msd.h"
+
+typedef struct SpiceCdLU {
+    char *filename;
+    GFileInputStream *stream;
+    uint64_t size;
+    uint32_t blockSize;
+    uint32_t loaded : 1;
+    uint32_t device : 1;
+} SpiceCdLU;
+
+#define MAX_LUN_PER_DEVICE              1
+#define USB2_BCD                        0x200
+/* Red Hat USB VID */
+#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 DVD_DEV_BLOCK_SIZE              0x800
+#define MAX_BULK_IN_REQUESTS            64
+
+struct BufferedBulkRead {
+    struct usb_redir_bulk_packet_header hout;
+    uint64_t id;
+};
+
+struct SpiceUsbEmulatedDevice {
+    UsbDeviceOps dev_ops;
+    SpiceUsbBackend *backend;
+    SpiceUsbBackendDevice *parent;
+    struct usbredirparser *parser;
+    UsbCdBulkMsdDevice* msc;
+    SpiceCdLU units[MAX_LUN_PER_DEVICE];
+    gboolean locked;
+    gboolean delete_on_eject;
+    gboolean deleting;
+    uint32_t num_reads;
+    struct BufferedBulkRead read_bulk[MAX_BULK_IN_REQUESTS];
+    /* according to USB MSC spec */
+    uint16_t serial[12];
+    uint8_t max_lun_index;
+};
+
+typedef struct SpiceUsbEmulatedDevice UsbCd;
+
+#ifndef G_OS_WIN32
+
+static int cd_device_open_stream(SpiceCdLU *unit, const char *filename)
+{
+    unit->device = 0;
+
+    if (!unit->filename && !filename) {
+        SPICE_DEBUG("%s: file name not provided", __FUNCTION__);
+        return -1;
+    }
+    if (unit->filename && filename) {
+        g_free(unit->filename);
+        unit->filename = NULL;
+    }
+    if (filename) {
+        unit->filename = g_strdup(filename);
+    }
+
+    int fd = open(unit->filename, O_RDONLY | O_NONBLOCK);
+    if (fd < 0) {
+        SPICE_DEBUG("%s: can't open file %s", __FUNCTION__, unit->filename);
+        return -1;
+    }
+
+    struct stat file_stat = { 0 };
+    if (fstat(fd, &file_stat) || file_stat.st_size == 0) {
+        file_stat.st_size = 0;
+        unit->device = 1;
+        if (!ioctl(fd, BLKGETSIZE64, &file_stat.st_size) &&
+            !ioctl(fd, BLKSSZGET, &unit->blockSize)) {
+        }
+    }
+    unit->size = file_stat.st_size;
+    close(fd);
+    if (unit->size) {
+        GFile *file_object = g_file_new_for_path(unit->filename);
+        unit->stream = g_file_read(file_object, NULL, NULL);
+        g_clear_object(&file_object);
+    }
+    if (!unit->stream) {
+        SPICE_DEBUG("%s: can't open stream on %s", __FUNCTION__, unit->filename);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int cd_device_load(SpiceCdLU *unit, gboolean load)
+{
+    int error;
+    if (!unit->device || !unit->filename) {
+        return -1;
+    }
+    int fd = open(unit->filename, O_RDONLY | O_NONBLOCK);
+    if (fd < 0) {
+        return -1;
+    }
+    if (load) {
+        error = ioctl(fd, CDROMCLOSETRAY, 0);
+    } else {
+        error = ioctl(fd, CDROM_LOCKDOOR, 0);
+        error = ioctl(fd, CDROMEJECT, 0);
+    }
+    if (error) {
+        // note that ejecting might be available only for root
+        // loading might be available also for regular user
+        SPICE_DEBUG("%s: can't %sload %s, res %d, errno %d",
+            __FUNCTION__, load ? "" : "un", unit->filename, error, errno);
+    }
+    close(fd);
+    return error;
+}
+
+static int cd_device_check(SpiceCdLU *unit)
+{
+    int error;
+    if (!unit->device || !unit->filename) {
+        return -1;
+    }
+    int fd = open(unit->filename, O_RDONLY | O_NONBLOCK);
+    if (fd < 0) {
+        return -1;
+    }
+    error = ioctl(fd, CDROM_DRIVE_STATUS, 0);
+    error = (error == CDS_DISC_OK) ? 0 : -1;
+    if (!error) {
+        error = ioctl(fd, CDROM_DISC_STATUS, 0);
+        error = (error == CDS_DATA_1) ? 0 : -1;
+    }
+    close(fd);
+    return error;
+}
+
+#else
+
+static gboolean is_device_name(const char *filename)
+{
+    return g_ascii_isalpha(filename[0]) && filename[1] == ':' &&
+        filename[2] == 0;
+}
+
+static HANDLE open_file(const char *filename)
+{
+    HANDLE h = CreateFileA(filename,
+                           GENERIC_READ,
+                           FILE_SHARE_READ,
+                           NULL, OPEN_EXISTING,
+                           0,
+                           NULL);
+    if (h == INVALID_HANDLE_VALUE) {
+        h = NULL;
+    }
+    return h;
+}
+
+static uint32_t ioctl_out(HANDLE h, uint32_t code, void *out_buffer, uint32_t out_size)
+{
+    uint32_t error;
+    DWORD ret;
+    BOOL b = DeviceIoControl(h, code, NULL, 0, out_buffer, out_size, &ret, NULL);
+    error = b ? 0 : GetLastError();
+    return error;
+}
+
+static uint32_t ioctl_none(HANDLE h, uint32_t code)
+{
+    return ioctl_out(h, code, NULL, 0);
+}
+
+static gboolean check_device(HANDLE h)
+{
+    GET_CONFIGURATION_IOCTL_INPUT cfgIn =
+        { FeatureCdRead, SCSI_GET_CONFIGURATION_REQUEST_TYPE_ALL, {} };
+    DWORD ret;
+    GET_CONFIGURATION_HEADER cfgOut;
+    return DeviceIoControl(h, IOCTL_CDROM_GET_CONFIGURATION,
+                           &cfgIn, sizeof(cfgIn), &cfgOut, sizeof(cfgOut),
+                           &ret, NULL);
+}
+
+static int cd_device_open_stream(SpiceCdLU *unit, const char *filename)
+{
+    HANDLE h;
+    unit->device = 0;
+    if (!unit->filename && !filename) {
+        SPICE_DEBUG("%s: unnamed file", __FUNCTION__);
+        return -1;
+    }
+    if (unit->filename && filename) {
+        g_free(unit->filename);
+        unit->filename = NULL;
+    }
+    if (!filename) {
+        // reopening the stream on existing file name
+    } else if (is_device_name(filename)) {
+        unit->filename = g_strdup_printf("\\\\.\\%s", filename);
+    } else {
+        unit->filename = g_strdup(filename);
+    }
+    h = open_file(unit->filename);
+    if (!h) {
+        SPICE_DEBUG("%s: can't open file %s", __FUNCTION__, unit->filename);
+        return -1;
+    }
+
+    LARGE_INTEGER size = { 0 };
+    if (!GetFileSizeEx(h, &size)) {
+        uint64_t buffer[256];
+        uint32_t res;
+        unit->device = check_device(h);
+        SPICE_DEBUG("%s: CD device %srecognized on %s",
+            __FUNCTION__, unit->device ? "" : "NOT ", unit->filename);
+        res = ioctl_out(h, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
+                        buffer, sizeof(buffer));
+        if (!res) {
+            DISK_GEOMETRY_EX *pg = (DISK_GEOMETRY_EX *)buffer;
+            unit->blockSize = pg->Geometry.BytesPerSector;
+            size = pg->DiskSize;
+        } else {
+            SPICE_DEBUG("%s: can't obtain size of %s (error %u)",
+                __FUNCTION__, unit->filename, res);
+        }
+    }
+    unit->size = size.QuadPart;
+    CloseHandle(h);
+    if (unit->size) {
+        GFile *file_object = g_file_new_for_path(unit->filename);
+        unit->stream = g_file_read(file_object, NULL, NULL);
+        g_clear_object(&file_object);
+    }
+    if (!unit->stream) {
+        SPICE_DEBUG("%s: can't open stream on %s", __FUNCTION__, unit->filename);
+        return -1;
+    }
+    return 0;
+}
+
+static int cd_device_load(SpiceCdLU *unit, gboolean load)
+{
+    int error = 0;
+    HANDLE h;
+    if (!unit->device || !unit->filename) {
+        return -1;
+    }
+    h = open_file(unit->filename);
+    if (h) {
+        uint32_t res = ioctl_none(h, load ? IOCTL_STORAGE_LOAD_MEDIA : IOCTL_STORAGE_EJECT_MEDIA);
+        if (res) {
+            SPICE_DEBUG("%s: can't %sload %s, win error %u",
+                        __FUNCTION__, load ? "" : "un", unit->filename, res);
+            error = -1;
+        } else {
+            SPICE_DEBUG("%s: device %s [%s]",
+                        __FUNCTION__, load ? "loaded" : "ejected", unit->filename);
+        }
+        CloseHandle(h);
+    }
+    return error;
+}
+
+static int cd_device_check(SpiceCdLU *unit)
+{
+    int error = 0;
+    CDROM_DISK_DATA data;
+    HANDLE h;
+    if (!unit->device || !unit->filename) {
+        return -1;
+    }
+    h = open_file(unit->filename);
+    if (h) {
+        uint32_t res = ioctl_none(h, IOCTL_STORAGE_CHECK_VERIFY);
+        if (!res) {
+            res = ioctl_out(h, IOCTL_CDROM_DISK_TYPE, &data, sizeof(data));
+        }
+        if (res != 0 || data.DiskData != CDROM_DISK_DATA_TRACK) {
+            error = -1;
+        }
+        CloseHandle(h);
+    }
+    return error;
+}
+
+#endif
+
+static gboolean open_stream(SpiceCdLU *unit, const char *filename)
+{
+    gboolean b;
+    b = cd_device_open_stream(unit, filename) == 0;
+    return b;
+}
+
+static void close_stream(SpiceCdLU *unit)
+{
+    g_clear_object(&unit->stream);
+}
+
+static gboolean load_lun(UsbCd *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 %" G_GUINT64_FORMAT ", block %u",
+                    __FUNCTION__, d->units[unit].filename, media_params.size, media_params.block_size);
+
+        b = !cd_usb_bulk_msd_load(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->msc, unit);
+        d->units[unit].loaded = FALSE;
+    }
+    return b;
+}
+
+static void usb_cd_unrealize(UsbCd *d)
+{
+    uint32_t unit = 0;
+    cd_usb_bulk_msd_unrealize(d->msc, unit);
+    g_clear_pointer(&d->units[unit].filename, g_free);
+    close_stream(&d->units[unit]);
+    g_clear_pointer(&d->msc, cd_usb_bulk_msd_free);
+    g_free(d);
+}
+
+static gboolean usb_cd_get_descriptor(UsbCd *d, uint8_t type, uint8_t index,
+                                      void **buffer, uint16_t *size)
+{
+    static 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 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
+    };
+    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' };
+
+    void *p = NULL;
+    uint16_t len = 0;
+
+    switch (type) {
+    case LIBUSB_DT_DEVICE:
+        p = &desc;
+        len = sizeof(desc);
+        break;
+    case LIBUSB_DT_CONFIG:
+        p = cfg;
+        len = sizeof(cfg);
+        break;
+    case LIBUSB_DT_STRING:
+        if (index == 0) {
+            p = s0; len = sizeof(s0);
+        } else if (index == 1) {
+            p = s1; len = sizeof(s1);
+        } else if (index == 2) {
+            p = s2; len = sizeof(s2);
+        } else if (index == 3) {
+            p = d->serial; len = sizeof(d->serial);
+        }
+        break;
+    }
+
+    if (p) {
+        *buffer = p;
+        *size = len;
+    }
+
+    return p != NULL;
+}
+
+static void usb_cd_attach(UsbCd *device, struct usbredirparser *parser)
+{
+    device->parser = parser;
+}
+
+static void usb_cd_detach(UsbCd *device)
+{
+    device->parser = NULL;
+}
+
+static void usb_cd_reset(UsbCd *device)
+{
+    cd_usb_bulk_msd_reset(device->msc);
+}
+
+static void usb_cd_control_request(UsbCd *device,
+                                   uint8_t *data, int data_len,
+                                   struct usb_redir_control_packet_header *h,
+                                   void **buffer)
+{
+    uint8_t reqtype = h->requesttype & 0x7f;
+    if (!device->msc) {
+        return;
+    }
+    if (reqtype == (LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_ENDPOINT)) {
+        // might be clear stall request
+        h->length = 0;
+        h->status = usb_redir_success;;
+        return;
+    }
+
+    if (reqtype == (LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE)) {
+        switch (h->request) {
+        case 0xFF:
+            // mass-storage class request 'reset'
+            usb_cd_reset(device);
+            h->length = 0;
+            h->status = usb_redir_success;
+            break;
+        case 0xFE:
+            // mass-storage class request 'get max lun'
+            // returning one byte
+            if (h->length) {
+                h->length = 1;
+                h->status = usb_redir_success;
+                *buffer = &device->max_lun_index;
+            }
+            break;
+        }
+    }
+}
+
+static void usb_cd_bulk_out_request(UsbCd *device,
+                                    uint8_t ep, uint8_t *data, int data_len,
+                                    uint8_t *status)
+{
+    if (!cd_usb_bulk_msd_write(device->msc, data, data_len)) {
+        *status = usb_redir_success;
+    }
+}
+
+void cd_usb_bulk_msd_read_complete(void *user_data,
+                                   uint8_t *data, uint32_t length, CdUsbBulkStatus status)
+{
+    UsbCd *d = (UsbCd *)user_data;
+
+    if (d->deleting) {
+        d->deleting = FALSE;
+        spice_usb_backend_device_eject(d->backend, d->parent);
+    }
+
+    if (d->parser) {
+        int nread;
+        uint32_t offset = 0;
+
+        for (nread = 0; nread < d->num_reads; nread++) {
+            uint32_t max_len;
+            max_len = (d->read_bulk[nread].hout.length_high << 16) |
+                        d->read_bulk[nread].hout.length;
+            if (max_len > length) {
+                max_len = length;
+                d->read_bulk[nread].hout.length = length;
+                d->read_bulk[nread].hout.length_high = length >> 16;
+            }
+
+            switch (status) {
+            case BULK_STATUS_GOOD:
+                d->read_bulk[nread].hout.status = usb_redir_success;
+                break;
+            case BULK_STATUS_CANCELED:
+                d->read_bulk[nread].hout.status = usb_redir_cancelled;
+                break;
+            case BULK_STATUS_ERROR:
+                d->read_bulk[nread].hout.status = usb_redir_ioerror;
+                break;
+            case BULK_STATUS_STALL:
+            default:
+                d->read_bulk[nread].hout.status = usb_redir_stall;
+                break;
+            }
+
+            SPICE_DEBUG("%s: responding %" G_GUINT64_FORMAT " with len %u out of %u, status %d",
+                        __FUNCTION__, d->read_bulk[nread].id, max_len,
+                        length, d->read_bulk[nread].hout.status);
+            usbredirparser_send_bulk_packet(d->parser, d->read_bulk[nread].id,
+                                            &d->read_bulk[nread].hout,
+                                            max_len ? (data + offset) : NULL,
+                                            max_len);
+            offset += max_len;
+            length -= max_len;
+        }
+        d->num_reads = 0;
+        usbredirparser_do_write(d->parser);
+
+        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)
+{
+    // UsbCd *d = (UsbCd *)user_data;
+}
+
+static gboolean usb_cd_bulk_in_request(UsbCd *d, uint64_t id,
+                                       struct usb_redir_bulk_packet_header *h)
+{
+    int res;
+    uint32_t len = (h->length_high << 16) | h->length;
+    if (d->num_reads >= MAX_BULK_IN_REQUESTS) {
+        h->length = h->length_high = 0;
+        SPICE_DEBUG("%s: too many pending reads", __FUNCTION__);
+        h->status = usb_redir_babble;
+        return FALSE;
+    }
+
+    if (d->num_reads) {
+        SPICE_DEBUG("%s: already has %u pending reads", __FUNCTION__, d->num_reads);
+    }
+
+    d->read_bulk[d->num_reads].hout = *h;
+    d->read_bulk[d->num_reads].id = id;
+    d->num_reads++;
+    res = cd_usb_bulk_msd_read(d->msc, len);
+    if (!res) {
+        return TRUE;
+    }
+
+    SPICE_DEBUG("%s: error on bulk read", __FUNCTION__);
+    d->num_reads--;
+    h->length = h->length_high = 0;
+    h->status = usb_redir_ioerror;
+
+    return FALSE;
+}
+
+static void usb_cd_cancel_request(UsbCd *d, uint64_t id)
+{
+    uint32_t nread;
+
+    for (nread = 0; nread < d->num_reads; nread++) {
+        if (d->read_bulk[nread].id == id) {
+            if (cd_usb_bulk_msd_cancel_read(d->msc)) {
+                cd_usb_bulk_msd_read_complete(d, NULL, 0, BULK_STATUS_CANCELED);
+            }
+            return;
+        }
+    }
+    SPICE_DEBUG("%s: ERROR: no such id to cancel!", __FUNCTION__);
+}
+
+// called when a change happens on SCSI layer
+void cd_usb_bulk_msd_lun_changed(void *user_data, uint32_t lun)
+{
+    UsbCd *d = (UsbCd *)user_data;
+    CdScsiDeviceInfo cd_info;
+
+    if (!cd_usb_bulk_msd_get_info(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->msc, lun);
+            }
+        }
+    }
+
+    if (d->delete_on_eject) {
+        d->delete_on_eject = FALSE;
+        d->deleting = TRUE;
+    } else {
+        spice_usb_backend_device_report_change(d->backend, d->parent);
+    }
+}
+
+static gchar *usb_cd_get_product_description(UsbCd *device)
+{
+    gchar *base_name = g_path_get_basename(device->units[0].filename);
+    gchar *res = g_strdup_printf("SPICE CD (%s)", base_name);
+    g_free(base_name);
+    return res;
+}
+
+static const UsbDeviceOps devops =
+{
+    .get_descriptor = usb_cd_get_descriptor,
+    .get_product_description = usb_cd_get_product_description,
+    .attach = usb_cd_attach,
+    .reset = usb_cd_reset,
+    .detach = usb_cd_detach,
+    .control_request = usb_cd_control_request,
+    .bulk_out_request = usb_cd_bulk_out_request,
+    .bulk_in_request = usb_cd_bulk_in_request,
+    .cancel_request = usb_cd_cancel_request,
+    .unrealize = usb_cd_unrealize,
+};
+
+static UsbCd* usb_cd_create(SpiceUsbBackend *be,
+                            SpiceUsbBackendDevice *parent,
+                            void *opaque_param,
+                            GError **err)
+{
+    CdEmulationParams *param = opaque_param;
+    int error = 0, i;
+    uint32_t unit = 0;
+    UsbCd *d = g_new0(UsbCd, 1);
+    CdScsiDeviceParameters dev_params = { 0 };
+    uint16_t address = spice_usb_backend_device_get_info(parent)->address;
+
+    d->dev_ops = devops;
+    d->backend = be;
+    d->parent  = parent;
+    d->delete_on_eject = !!param->delete_on_eject;
+    d->locked = !d->delete_on_eject;
+    d->serial[0] = 0x0300 + sizeof(d->serial);
+    d->serial[1] = '0' + address / 10;
+    d->serial[2] = '0' + address % 10;
+    for (i = 3; i < G_N_ELEMENTS(d->serial); ++i) {
+        d->serial[i] = '0';
+    }
+    d->max_lun_index = MAX_LUN_PER_DEVICE - 1;
+
+    dev_params.vendor = "Red Hat";
+    dev_params.product = "SPICE CD";
+    dev_params.version = "0";
+
+    d->msc = cd_usb_bulk_msd_alloc(d, MAX_LUN_PER_DEVICE);
+    if (!d->msc) {
+        g_clear_pointer(&d, g_free);
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    _("can't allocate device"));
+        return NULL;
+    }
+    d->units[unit].blockSize = CD_DEV_BLOCK_SIZE;
+    if (!cd_usb_bulk_msd_realize(d->msc, unit, &dev_params)) {
+        if (open_stream(&d->units[unit], param->filename) &&
+            load_lun(d, unit, TRUE)) {
+            if (d->locked) {
+                cd_usb_bulk_msd_lock(d->msc, unit, TRUE);
+            }
+        } else {
+            close_stream(&d->units[unit]);
+            cd_usb_bulk_msd_unrealize(d->msc, unit);
+            g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                        _("can't create device with %s"),
+                        param->filename);
+            error = 1;
+        }
+    } else {
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    _("can't allocate device"));
+        error = 1;
+    }
+    if (error) {
+        g_clear_pointer(&d->msc, cd_usb_bulk_msd_free);
+        g_clear_pointer(&d, g_free);
+        return NULL;
+    }
+
+    return d;
+}
+
+gboolean
+create_emulated_cd(SpiceUsbBackend *be,
+                   CdEmulationParams *param,
+                   GError **err)
+{
+    return spice_usb_backend_create_emulated_device(be, usb_cd_create, param, err);
+}
+
+#endif /* USE_USBREDIR */
diff --git a/src/usb-device-cd.h b/src/usb-device-cd.h
new file mode 100644
index 0000000..e985242
--- /dev/null
+++ b/src/usb-device-cd.h
@@ -0,0 +1,34 @@
+/* -*- 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/>.
+*/
+
+#pragma once
+
+#include "usb-backend.h"
+
+typedef struct CdEmulationParams {
+    const char *filename;
+    uint32_t delete_on_eject : 1;
+} CdEmulationParams;
+
+gboolean
+create_emulated_cd(SpiceUsbBackend *be,
+                   CdEmulationParams *param,
+                   GError **err);
commit 5bf98b3926e80fd1cc2e57375c38ffd5849e8757
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Thu Sep 19 16:11:23 2019 +0200

    cd-scsi: Do not export sense code constants
    
    They are used only inside the module.
    Use a macro to simplify declaration which is pretty long.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Victor Toso <victortoso at redhat.com>

diff --git a/src/cd-scsi.c b/src/cd-scsi.c
index ea682ef..942e589 100644
--- a/src/cd-scsi.c
+++ b/src/cd-scsi.c
@@ -114,133 +114,135 @@ struct CdScsiTarget {
 static const char* scsi_cmd_name[256];
 
 /* Predefined sense codes */
+#define SENSE_CODE(name) \
+    static const ScsiShortSense name G_GNUC_UNUSED
 
-const ScsiShortSense sense_code_NO_SENSE = {
+SENSE_CODE(sense_code_NO_SENSE) = {
     .key = NO_SENSE , .asc = 0x00 , .ascq = 0x00,
     .descr = ""
 };
 
-const ScsiShortSense sense_code_NOT_READY_CAUSE_NOT_REPORTABLE = {
+SENSE_CODE(sense_code_NOT_READY_CAUSE_NOT_REPORTABLE) = {
     .key = NOT_READY, .asc = 0x04, .ascq = 0x00,
     .descr = "CAUSE NOT REPORTABLE"
 };
 
-const ScsiShortSense sense_code_BECOMING_READY = {
+SENSE_CODE(sense_code_BECOMING_READY) = {
     .key = NOT_READY, .asc = 0x04, .ascq = 0x01,
     .descr = "IN PROCESS OF BECOMING READY"
 };
 
-const ScsiShortSense sense_code_INIT_CMD_REQUIRED = {
+SENSE_CODE(sense_code_INIT_CMD_REQUIRED) = {
     .key = NOT_READY, .asc = 0x04, .ascq = 0x02,
     .descr = "INITIALIZING COMMAND REQUIRED"
 };
 
-const ScsiShortSense sense_code_INTERVENTION_REQUIRED = {
+SENSE_CODE(sense_code_INTERVENTION_REQUIRED) = {
     .key = NOT_READY, .asc = 0x04, .ascq = 0x03,
     .descr = "MANUAL INTERVENTION REQUIRED"
 };
 
-const ScsiShortSense sense_code_NOT_READY_NO_MEDIUM = {
+SENSE_CODE(sense_code_NOT_READY_NO_MEDIUM) = {
     .key = NOT_READY, .asc = 0x3a, .ascq = 0x00,
     .descr = "MEDIUM NOT PRESENT"
 };
 
-const ScsiShortSense sense_code_NOT_READY_NO_MEDIUM_TRAY_CLOSED = {
+SENSE_CODE(sense_code_NOT_READY_NO_MEDIUM_TRAY_CLOSED) = {
     .key = NOT_READY, .asc = 0x3a, .ascq = 0x01,
     .descr = "MEDIUM NOT PRESENT - TRAY CLOSED"
 };
 
-const ScsiShortSense sense_code_NOT_READY_NO_MEDIUM_TRAY_OPEN = {
+SENSE_CODE(sense_code_NOT_READY_NO_MEDIUM_TRAY_OPEN) = {
     .key = NOT_READY, .asc = 0x3a, .ascq = 0x02,
     .descr = "MEDIUM NOT PRESENT - TRAY OPEN"
 };
 
-const ScsiShortSense sense_code_TARGET_FAILURE = {
+SENSE_CODE(sense_code_TARGET_FAILURE) = {
     .key = HARDWARE_ERROR, .asc = 0x44, .ascq = 0x00,
     .descr = "INTERNAL TARGET FAILURE"
 };
 
-const ScsiShortSense sense_code_INVALID_OPCODE = {
+SENSE_CODE(sense_code_INVALID_OPCODE) = {
     .key = ILLEGAL_REQUEST, .asc = 0x20, .ascq = 0x00,
     .descr = "INVALID COMMAND OPERATION CODE"
 };
 
-const ScsiShortSense sense_code_LBA_OUT_OF_RANGE = {
+SENSE_CODE(sense_code_LBA_OUT_OF_RANGE) = {
     .key = ILLEGAL_REQUEST, .asc = 0x21, .ascq = 0x00,
     .descr = "LOGICAL BLOCK ADDRESS OUT OF RANGE"
 };
 
-const ScsiShortSense sense_code_INVALID_CDB_FIELD = {
+SENSE_CODE(sense_code_INVALID_CDB_FIELD) = {
     .key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00,
     .descr = "INVALID FIELD IN CDB"
 };
 
-const ScsiShortSense sense_code_INVALID_PARAM_FIELD = {
+SENSE_CODE(sense_code_INVALID_PARAM_FIELD) = {
     .key = ILLEGAL_REQUEST, .asc = 0x26, .ascq = 0x00,
     .descr = "INVALID FIELD IN PARAMETER LIST"
 };
 
-const ScsiShortSense sense_code_INVALID_PARAM_LEN = {
+SENSE_CODE(sense_code_INVALID_PARAM_LEN) = {
     .key = ILLEGAL_REQUEST, .asc = 0x1a, .ascq = 0x00,
     .descr = "PARAMETER LIST LENGTH ERROR"
 };
 
-const ScsiShortSense sense_code_LUN_NOT_SUPPORTED = {
+SENSE_CODE(sense_code_LUN_NOT_SUPPORTED) = {
     .key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00,
     .descr = "LOGICAL UNIT NOT SUPPORTED"
 };
 
-const ScsiShortSense sense_code_SAVING_PARAMS_NOT_SUPPORTED = {
+SENSE_CODE(sense_code_SAVING_PARAMS_NOT_SUPPORTED) = {
     .key = ILLEGAL_REQUEST, .asc = 0x39, .ascq = 0x00,
     .descr = "SAVING PARAMETERS NOT SUPPORTED"
 };
 
-const ScsiShortSense sense_code_INCOMPATIBLE_MEDIUM = {
+SENSE_CODE(sense_code_INCOMPATIBLE_MEDIUM) = {
     .key = ILLEGAL_REQUEST, .asc = 0x30, .ascq = 0x00,
     .descr = "INCOMPATIBLE MEDIUM INSTALLED"
 };
 
-const ScsiShortSense sense_code_MEDIUM_REMOVAL_PREVENTED = {
+SENSE_CODE(sense_code_MEDIUM_REMOVAL_PREVENTED) = {
     .key = ILLEGAL_REQUEST, .asc = 0x53, .ascq = 0x02,
     .descr = "MEDIUM REMOVAL PREVENTED"
 };
 
-const ScsiShortSense sense_code_PARAMETERS_CHANGED = {
+SENSE_CODE(sense_code_PARAMETERS_CHANGED) = {
     .key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x00,
     .descr = "PARAMETERS CHANGED"
 };
 
-const ScsiShortSense sense_code_POWER_ON_RESET = {
+SENSE_CODE(sense_code_POWER_ON_RESET) = {
     .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00,
     .descr = "POWER ON, RESET, OR BUS DEVICE RESET"
 };
 
-const ScsiShortSense sense_code_SCSI_BUS_RESET = {
+SENSE_CODE(sense_code_SCSI_BUS_RESET) = {
     .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x02,
     .descr = "SCSI BUS RESET"
 };
 
-const ScsiShortSense sense_code_UA_NO_MEDIUM = {
+SENSE_CODE(sense_code_UA_NO_MEDIUM) = {
     .key = UNIT_ATTENTION, .asc = 0x3a, .ascq = 0x00,
     .descr = "MEDIUM NOT PRESENT"
 };
 
-const ScsiShortSense sense_code_MEDIUM_CHANGED = {
+SENSE_CODE(sense_code_MEDIUM_CHANGED) = {
     .key = UNIT_ATTENTION, .asc = 0x28, .ascq = 0x00,
     .descr = "MEDIUM CHANGED"
 };
 
-const ScsiShortSense sense_code_REPORTED_LUNS_CHANGED = {
+SENSE_CODE(sense_code_REPORTED_LUNS_CHANGED) = {
     .key = UNIT_ATTENTION, .asc = 0x3f, .ascq = 0x0e,
     .descr = "REPORTED LUNS CHANGED"
 };
 
-const ScsiShortSense sense_code_DEVICE_INTERNAL_RESET = {
+SENSE_CODE(sense_code_DEVICE_INTERNAL_RESET) = {
     .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x04,
     .descr = "DEVICE INTERNAL RESET"
 };
 
-const ScsiShortSense sense_code_UNIT_ATTENTION_MEDIUM_REMOVAL_REQUEST = {
+SENSE_CODE(sense_code_UNIT_ATTENTION_MEDIUM_REMOVAL_REQUEST) = {
     .key = UNIT_ATTENTION, .asc = 0x5a, .ascq = 0x01,
     .descr = "OPERATOR MEDIUM REMOVAL REQUEST"
 };
commit ef0bee2e56107e3545b42f4bd30ad9e6fb2baf94
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Thu Sep 19 16:11:22 2019 +0200

    usb-redir: add files for SCSI and USB MSC implementation
    
    Files added without including them in compilation.
    They contain implementation of SCSI commands for logical
    units of mass-storage device class and USB bulk-only
    mass-storage device protocol.
    
    Signed-off-by: Alexander Nezhinsky<anezhins at redhat.com>
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/cd-scsi-dev-params.h b/src/cd-scsi-dev-params.h
new file mode 100644
index 0000000..344f720
--- /dev/null
+++ b/src/cd-scsi-dev-params.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   CD SCSI device parameters
+
+   Copyright (C) 2018 Red Hat, Inc.
+
+   Red Hat Authors:
+   Alexander Nezhinsky<anezhins 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/>.
+*/
+
+#pragma once
+
+#include <gio/gio.h>
+
+typedef struct CdScsiDeviceParameters {
+    const char *vendor;
+    const char *product;
+    const char *version;
+    const char *serial;
+} CdScsiDeviceParameters;
+
+typedef struct CdScsiDeviceInfo {
+    CdScsiDeviceParameters parameters;
+    uint32_t started    : 1;
+    uint32_t locked     : 1;
+    uint32_t loaded     : 1;
+} CdScsiDeviceInfo;
+
+typedef struct CdScsiMediaParameters {
+    GFileInputStream *stream;
+    uint64_t size;
+    uint32_t block_size;
+} CdScsiMediaParameters;
diff --git a/src/cd-scsi.c b/src/cd-scsi.c
new file mode 100644
index 0000000..ea682ef
--- /dev/null
+++ b/src/cd-scsi.c
@@ -0,0 +1,2740 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   CD device emulation - SCSI engine
+
+   Copyright (C) 2018 Red Hat, Inc.
+
+   Red Hat Authors:
+   Alexander Nezhinsky<anezhins at redhat.com>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include "spice/types.h"
+#include "spice-common.h"
+#include "spice-util.h"
+#include "cd-scsi.h"
+
+#ifdef USE_USBREDIR
+
+#define SPICE_ERROR(fmt, ...) \
+    do { SPICE_DEBUG("dev-scsi error: " fmt , ## __VA_ARGS__); } while (0)
+
+#define FIXED_SENSE_CURRENT 0x70
+#define FIXED_SENSE_LEN 18
+
+#define MAX_LUNS   32
+
+typedef enum CdScsiPowerCondition {
+    CD_SCSI_POWER_STOPPED,
+    CD_SCSI_POWER_ACTIVE,
+    CD_SCSI_POWER_IDLE,
+    CD_SCSI_POWER_STANDBY
+} CdScsiPowerCondition;
+
+typedef struct ScsiShortSense {
+    uint8_t key;
+    uint8_t asc;
+    uint8_t ascq;
+    const char *descr;
+} ScsiShortSense;
+
+#define CD_MEDIA_EVENT_NO_CHANGE            0x0
+#define CD_MEDIA_EVENT_EJECT_REQ            0x1 /* user request (mechanical switch) to
+                                                 * eject the media */
+#define CD_MEDIA_EVENT_NEW_MEDIA            0x2 /* new media received */
+#define CD_MEDIA_EVENT_MEDIA_REMOVAL        0x3 /* media removed */
+#define CD_MEDIA_EVENT_MEDIA_CHANGED        0x4 /* user request to load new media */
+#define CD_MEDIA_EVENT_BG_FORMAT_COMPLETE   0x5
+#define CD_MEDIA_EVENT_BG_FORMAT_RESTART    0x6
+
+#define CD_POWER_EVENT_NO_CHANGE            0x0
+#define CD_POWER_EVENT_CHANGE_SUCCESS       0x1
+#define CD_POWER_EVENT_CHANGE_FALED         0x2
+
+typedef struct CdScsiLU {
+    CdScsiTarget *tgt;
+    uint32_t lun;
+
+    gboolean realized;
+    gboolean removable;
+    gboolean loaded;
+    gboolean prevent_media_removal;
+    gboolean cd_rom;
+
+    CdScsiPowerCondition power_cond;
+    uint32_t power_event;
+    uint32_t media_event;
+
+    uint32_t claim_version;
+
+    uint64_t size;
+    uint32_t block_size;
+    uint32_t num_blocks;
+
+    char *vendor;
+    char *product;
+    char *version;
+    char *serial;
+
+    GFileInputStream *stream;
+
+    ScsiShortSense short_sense; /* currently held sense of the scsi device */
+    uint8_t fixed_sense[FIXED_SENSE_LEN];
+} CdScsiLU;
+
+typedef enum CdScsiTargetState {
+    CD_SCSI_TGT_STATE_RUNNING,
+    CD_SCSI_TGT_STATE_RESET,
+} CdScsiTargetState;
+
+struct CdScsiTarget {
+    void *user_data;
+
+    CdScsiTargetState state;
+    CdScsiRequest *cur_req;
+    GCancellable *cancellable;
+
+    uint32_t max_luns;
+    CdScsiLU units[MAX_LUNS];
+};
+
+static const char* scsi_cmd_name[256];
+
+/* Predefined sense codes */
+
+const ScsiShortSense sense_code_NO_SENSE = {
+    .key = NO_SENSE , .asc = 0x00 , .ascq = 0x00,
+    .descr = ""
+};
+
+const ScsiShortSense sense_code_NOT_READY_CAUSE_NOT_REPORTABLE = {
+    .key = NOT_READY, .asc = 0x04, .ascq = 0x00,
+    .descr = "CAUSE NOT REPORTABLE"
+};
+
+const ScsiShortSense sense_code_BECOMING_READY = {
+    .key = NOT_READY, .asc = 0x04, .ascq = 0x01,
+    .descr = "IN PROCESS OF BECOMING READY"
+};
+
+const ScsiShortSense sense_code_INIT_CMD_REQUIRED = {
+    .key = NOT_READY, .asc = 0x04, .ascq = 0x02,
+    .descr = "INITIALIZING COMMAND REQUIRED"
+};
+
+const ScsiShortSense sense_code_INTERVENTION_REQUIRED = {
+    .key = NOT_READY, .asc = 0x04, .ascq = 0x03,
+    .descr = "MANUAL INTERVENTION REQUIRED"
+};
+
+const ScsiShortSense sense_code_NOT_READY_NO_MEDIUM = {
+    .key = NOT_READY, .asc = 0x3a, .ascq = 0x00,
+    .descr = "MEDIUM NOT PRESENT"
+};
+
+const ScsiShortSense sense_code_NOT_READY_NO_MEDIUM_TRAY_CLOSED = {
+    .key = NOT_READY, .asc = 0x3a, .ascq = 0x01,
+    .descr = "MEDIUM NOT PRESENT - TRAY CLOSED"
+};
+
+const ScsiShortSense sense_code_NOT_READY_NO_MEDIUM_TRAY_OPEN = {
+    .key = NOT_READY, .asc = 0x3a, .ascq = 0x02,
+    .descr = "MEDIUM NOT PRESENT - TRAY OPEN"
+};
+
+const ScsiShortSense sense_code_TARGET_FAILURE = {
+    .key = HARDWARE_ERROR, .asc = 0x44, .ascq = 0x00,
+    .descr = "INTERNAL TARGET FAILURE"
+};
+
+const ScsiShortSense sense_code_INVALID_OPCODE = {
+    .key = ILLEGAL_REQUEST, .asc = 0x20, .ascq = 0x00,
+    .descr = "INVALID COMMAND OPERATION CODE"
+};
+
+const ScsiShortSense sense_code_LBA_OUT_OF_RANGE = {
+    .key = ILLEGAL_REQUEST, .asc = 0x21, .ascq = 0x00,
+    .descr = "LOGICAL BLOCK ADDRESS OUT OF RANGE"
+};
+
+const ScsiShortSense sense_code_INVALID_CDB_FIELD = {
+    .key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00,
+    .descr = "INVALID FIELD IN CDB"
+};
+
+const ScsiShortSense sense_code_INVALID_PARAM_FIELD = {
+    .key = ILLEGAL_REQUEST, .asc = 0x26, .ascq = 0x00,
+    .descr = "INVALID FIELD IN PARAMETER LIST"
+};
+
+const ScsiShortSense sense_code_INVALID_PARAM_LEN = {
+    .key = ILLEGAL_REQUEST, .asc = 0x1a, .ascq = 0x00,
+    .descr = "PARAMETER LIST LENGTH ERROR"
+};
+
+const ScsiShortSense sense_code_LUN_NOT_SUPPORTED = {
+    .key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00,
+    .descr = "LOGICAL UNIT NOT SUPPORTED"
+};
+
+const ScsiShortSense sense_code_SAVING_PARAMS_NOT_SUPPORTED = {
+    .key = ILLEGAL_REQUEST, .asc = 0x39, .ascq = 0x00,
+    .descr = "SAVING PARAMETERS NOT SUPPORTED"
+};
+
+const ScsiShortSense sense_code_INCOMPATIBLE_MEDIUM = {
+    .key = ILLEGAL_REQUEST, .asc = 0x30, .ascq = 0x00,
+    .descr = "INCOMPATIBLE MEDIUM INSTALLED"
+};
+
+const ScsiShortSense sense_code_MEDIUM_REMOVAL_PREVENTED = {
+    .key = ILLEGAL_REQUEST, .asc = 0x53, .ascq = 0x02,
+    .descr = "MEDIUM REMOVAL PREVENTED"
+};
+
+const ScsiShortSense sense_code_PARAMETERS_CHANGED = {
+    .key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x00,
+    .descr = "PARAMETERS CHANGED"
+};
+
+const ScsiShortSense sense_code_POWER_ON_RESET = {
+    .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00,
+    .descr = "POWER ON, RESET, OR BUS DEVICE RESET"
+};
+
+const ScsiShortSense sense_code_SCSI_BUS_RESET = {
+    .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x02,
+    .descr = "SCSI BUS RESET"
+};
+
+const ScsiShortSense sense_code_UA_NO_MEDIUM = {
+    .key = UNIT_ATTENTION, .asc = 0x3a, .ascq = 0x00,
+    .descr = "MEDIUM NOT PRESENT"
+};
+
+const ScsiShortSense sense_code_MEDIUM_CHANGED = {
+    .key = UNIT_ATTENTION, .asc = 0x28, .ascq = 0x00,
+    .descr = "MEDIUM CHANGED"
+};
+
+const ScsiShortSense sense_code_REPORTED_LUNS_CHANGED = {
+    .key = UNIT_ATTENTION, .asc = 0x3f, .ascq = 0x0e,
+    .descr = "REPORTED LUNS CHANGED"
+};
+
+const ScsiShortSense sense_code_DEVICE_INTERNAL_RESET = {
+    .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x04,
+    .descr = "DEVICE INTERNAL RESET"
+};
+
+const ScsiShortSense sense_code_UNIT_ATTENTION_MEDIUM_REMOVAL_REQUEST = {
+    .key = UNIT_ATTENTION, .asc = 0x5a, .ascq = 0x01,
+    .descr = "OPERATOR MEDIUM REMOVAL REQUEST"
+};
+
+static inline gboolean cd_scsi_opcode_ua_supress(uint32_t opcode)
+{
+    switch (opcode) {
+    case INQUIRY:
+    case REPORT_LUNS:
+    case GET_CONFIGURATION:
+    case GET_EVENT_STATUS_NOTIFICATION:
+    case REQUEST_SENSE:
+        return TRUE;
+    default:
+        return FALSE;
+    }
+}
+
+static inline const char *CdScsiReqState_str(CdScsiReqState state)
+{
+    switch(state) {
+    case SCSI_REQ_IDLE:
+        return "IDLE";
+    case SCSI_REQ_RUNNING:
+        return "RUNNING";
+    case SCSI_REQ_COMPLETE:
+        return "COMPLETE";
+    case SCSI_REQ_CANCELED:
+        return "CANCELED";
+    default:
+        return "ILLEGAL";
+    }
+}
+
+static const char *cd_scsi_sense_key_descr(uint8_t sense_key)
+{
+    switch(sense_key) {
+    case NO_SENSE:
+        return "NO SENSE";
+    case RECOVERED_ERROR:
+        return "RECOVERED ERROR";
+    case NOT_READY:
+        return "LUN NOT READY";
+    case MEDIUM_ERROR:
+        return "MEDIUM ERROR";
+    case HARDWARE_ERROR:
+        return "HARDWARE ERROR";
+    case ILLEGAL_REQUEST:
+        return "ILLEGAL REQUEST";
+    case UNIT_ATTENTION:
+        return "UNIT ATTENTION";
+    case BLANK_CHECK:
+        return "BLANK CHECK";
+    case ABORTED_COMMAND:
+        return "ABORTED COMMAND";
+    default:
+        return "???";
+    }
+}
+static uint32_t cd_scsi_build_fixed_sense(uint8_t *buf, const ScsiShortSense *short_sense)
+{
+    memset(buf, 0, FIXED_SENSE_LEN);
+
+    buf[0] = FIXED_SENSE_CURRENT;
+    buf[2] = short_sense->key;
+    buf[7] = 10;
+    buf[12] = short_sense->asc;
+    buf[13] = short_sense->ascq;
+
+    return FIXED_SENSE_LEN;
+}
+
+static inline void cd_scsi_req_init(CdScsiRequest *req)
+{
+    req->req_state = SCSI_REQ_IDLE;
+    req->xfer_dir = SCSI_XFER_NONE;
+    req->priv_data = NULL;
+    req->in_len = 0;
+    req->status = GOOD;
+}
+
+static inline void cd_scsi_dev_sense_reset(CdScsiLU *dev)
+{
+    memset(&dev->short_sense, 0, sizeof(dev->short_sense));
+    cd_scsi_build_fixed_sense(dev->fixed_sense, &dev->short_sense);
+}
+
+static inline void cd_scsi_dev_sense_set(CdScsiLU *dev, const ScsiShortSense *short_sense)
+{
+    if (short_sense != NULL) {
+        /* copy short sense and generate full sense in fixed format */
+        dev->short_sense = *short_sense;
+        cd_scsi_build_fixed_sense(dev->fixed_sense, short_sense);
+    }
+}
+
+static inline void cd_scsi_dev_sense_set_power_on(CdScsiLU *dev)
+{
+    cd_scsi_dev_sense_set(dev, &sense_code_POWER_ON_RESET);
+}
+
+static void cd_scsi_cmd_complete_check_cond(CdScsiLU *dev, CdScsiRequest *req,
+                                            const ScsiShortSense *short_sense)
+{
+    req->req_state = SCSI_REQ_COMPLETE;
+    req->status = CHECK_CONDITION;
+    req->in_len = 0;
+
+    cd_scsi_dev_sense_set(dev, short_sense);
+
+    SPICE_DEBUG("CHECK_COND, request lun:%u"
+                " op: 0x%02x, pending sense: 0x%02x %02x %02x - %s, %s",
+                dev->lun, (uint32_t)req->cdb[0],
+                (uint32_t)dev->short_sense.key,
+                (uint32_t)dev->short_sense.asc,
+                (uint32_t)dev->short_sense.ascq,
+                cd_scsi_sense_key_descr(dev->short_sense.key),
+                dev->short_sense.descr);
+}
+
+static void cd_scsi_cmd_complete_good(CdScsiLU *dev, CdScsiRequest *req)
+{
+    req->req_state = SCSI_REQ_COMPLETE;
+    req->status = GOOD;
+}
+
+/* SCSI Target */
+
+SPICE_CONSTRUCTOR_FUNC(cd_scsi_cmd_names_init)
+{
+    uint32_t opcode;
+
+    for (opcode = 0; opcode < 256; opcode++) {
+        scsi_cmd_name[opcode] = "UNSUPPORTED";
+    }
+
+    scsi_cmd_name[REPORT_LUNS] = "REPORT LUNS";
+    scsi_cmd_name[TEST_UNIT_READY] = "TEST UNIT READY";
+    scsi_cmd_name[INQUIRY] = "INQUIRY";
+    scsi_cmd_name[REQUEST_SENSE] = "REQUEST SENSE";
+    scsi_cmd_name[READ_6] = "READ(6)";
+    scsi_cmd_name[READ_10] = "READ(10)";
+    scsi_cmd_name[READ_12] = "READ(12)";
+    scsi_cmd_name[READ_16] = "READ(16)";
+    scsi_cmd_name[READ_CAPACITY_10] = "READ CAPACITY(10)";
+    scsi_cmd_name[READ_TOC] = "READ TOC";
+    scsi_cmd_name[GET_EVENT_STATUS_NOTIFICATION] = "GET EVENT/STATUS NOTIFICATION";
+    scsi_cmd_name[READ_DISC_INFORMATION] = "READ DISC INFO";
+    scsi_cmd_name[READ_TRACK_INFORMATION] = "READ TRACK INFO";
+    scsi_cmd_name[MODE_SENSE_10] = "MODE SENSE(10)";
+    scsi_cmd_name[MODE_SELECT] = "MODE SELECT(6)";
+    scsi_cmd_name[MODE_SELECT_10] = "MODE SELECT(10)";
+    scsi_cmd_name[GET_CONFIGURATION] = "GET CONFIGURATION";
+    scsi_cmd_name[ALLOW_MEDIUM_REMOVAL] = "PREVENT ALLOW MEDIUM REMOVAL";
+    scsi_cmd_name[MMC_SEND_EVENT] = "SEND EVENT";
+    scsi_cmd_name[MMC_REPORT_KEY] = "REPORT KEY";
+    scsi_cmd_name[MMC_SEND_KEY] = "SEND_KEY";
+    scsi_cmd_name[START_STOP] = "START STOP UNIT";
+    scsi_cmd_name[MMC_GET_PERFORMANCE] = "GET PERFORMANCE";
+    scsi_cmd_name[MMC_MECHANISM_STATUS] = "MECHANISM STATUS";
+}
+
+CdScsiTarget *cd_scsi_target_alloc(void *target_user_data, uint32_t max_luns)
+{
+    CdScsiTarget *st;
+
+    if (max_luns == 0 || max_luns > MAX_LUNS) {
+        SPICE_ERROR("Alloc, illegal max_luns:%u", max_luns);
+        return NULL;
+    }
+
+    st = g_malloc0(sizeof(*st));
+
+    st->user_data = target_user_data;
+    st->state = CD_SCSI_TGT_STATE_RUNNING;
+    st->cur_req = NULL;
+    st->cancellable = g_cancellable_new();
+    st->max_luns = max_luns;
+
+    return st;
+}
+
+void cd_scsi_target_free(CdScsiTarget *st)
+{
+    uint32_t lun;
+
+    cd_scsi_target_reset(st);
+    for (lun = 0; lun < st->max_luns; lun++) {
+        CdScsiLU *unit = &st->units[lun];
+        if (unit->realized) {
+            cd_scsi_dev_unrealize(st, lun);
+        }
+        g_clear_object(&unit->stream);
+    }
+    g_clear_object(&st->cancellable);
+    g_free(st);
+}
+
+/* SCSI Device */
+
+static inline gboolean cd_scsi_target_lun_legal(const CdScsiTarget *st, uint32_t lun)
+{
+    return (lun < st->max_luns) ? TRUE : FALSE;
+}
+
+static inline gboolean cd_scsi_target_lun_realized(const CdScsiTarget *st, uint32_t lun)
+{
+    return st->units[lun].realized;
+}
+
+int cd_scsi_dev_realize(CdScsiTarget *st, uint32_t lun,
+                        const CdScsiDeviceParameters *dev_params)
+{
+    CdScsiLU *dev;
+
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("Realize, illegal lun:%u", lun);
+        return -1;
+    }
+    if (cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("Realize, already realized lun:%u", lun);
+        return -1;
+    }
+    dev = &st->units[lun];
+
+    memset(dev, 0, sizeof(*dev));
+    dev->tgt = st;
+    dev->lun = lun;
+
+    dev->realized = TRUE;
+    dev->removable = TRUE;
+    dev->loaded = FALSE;
+    dev->prevent_media_removal = FALSE;
+    dev->cd_rom = FALSE;
+
+    dev->power_cond = CD_SCSI_POWER_ACTIVE;
+    dev->power_event = CD_POWER_EVENT_NO_CHANGE;
+    dev->media_event = CD_MEDIA_EVENT_NO_CHANGE;
+
+    dev->claim_version = 3; /* 0 : none; 2,3,5 : SPC/MMC-x */
+
+    dev->vendor = g_strdup(dev_params->vendor);
+    dev->product = g_strdup(dev_params->product);
+    dev->version = g_strdup(dev_params->version);
+    dev->serial = g_strdup(dev_params->serial);
+
+    cd_scsi_dev_sense_set_power_on(dev);
+
+    SPICE_DEBUG("Realize lun:%u bs:%u VR:[%s] PT:[%s] ver:[%s] SN[%s]",
+                lun, dev->block_size, dev->vendor,
+                dev->product, dev->version, dev->serial);
+    return 0;
+}
+
+static void cd_scsi_lu_media_reset(CdScsiLU *dev)
+{
+    /* media_event is not set here, as it depends on the context */
+    g_clear_object(&dev->stream);
+    dev->size = 0;
+    dev->block_size = 0;
+    dev->num_blocks = 0;
+}
+
+int cd_scsi_dev_lock(CdScsiTarget *st, uint32_t lun, gboolean lock)
+{
+    CdScsiLU *dev;
+
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("Lock, illegal lun:%u", lun);
+        return -1;
+    }
+    if (!cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("Lock, unrealized lun:%u", lun);
+        return -1;
+    }
+    dev = &st->units[lun];
+    dev->prevent_media_removal = lock;
+    SPICE_DEBUG("lun:%u %slock", lun, lock ? "un" :"");
+    return 0;
+}
+
+static void cd_scsi_lu_load(CdScsiLU *dev,
+                            const CdScsiMediaParameters *media_params)
+{
+    if (media_params != NULL) {
+        dev->media_event = CD_MEDIA_EVENT_NEW_MEDIA;
+        dev->stream = g_object_ref(media_params->stream);
+        dev->size = media_params->size;
+        dev->block_size = media_params->block_size;
+        dev->num_blocks = media_params->size / media_params->block_size;
+        dev->loaded = TRUE;
+    } else {
+        dev->media_event = CD_MEDIA_EVENT_MEDIA_REMOVAL;
+        cd_scsi_lu_media_reset(dev);
+        dev->loaded = FALSE;
+    }
+}
+
+static void cd_scsi_lu_unload(CdScsiLU *dev)
+{
+    dev->media_event = CD_MEDIA_EVENT_MEDIA_REMOVAL;
+    cd_scsi_lu_media_reset(dev);
+    dev->loaded = FALSE;
+}
+
+int cd_scsi_dev_load(CdScsiTarget *st, uint32_t lun,
+                     const CdScsiMediaParameters *media_params)
+{
+    CdScsiLU *dev;
+
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("Load, illegal lun:%u", lun);
+        return -1;
+    }
+    if (!cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("Load, unrealized lun:%u", lun);
+        return -1;
+    }
+    dev = &st->units[lun];
+
+    cd_scsi_lu_load(dev, media_params);
+    dev->power_cond = CD_SCSI_POWER_ACTIVE;
+    dev->power_event = CD_POWER_EVENT_CHANGE_SUCCESS;
+
+    cd_scsi_dev_sense_set(dev, &sense_code_MEDIUM_CHANGED);
+
+    SPICE_DEBUG("Load lun:%u size:%" G_GUINT64_FORMAT
+                " blk_sz:%u num_blocks:%u",
+                lun, dev->size, dev->block_size, dev->num_blocks);
+    return 0;
+}
+
+int cd_scsi_dev_get_info(CdScsiTarget *st, uint32_t lun, CdScsiDeviceInfo *lun_info)
+{
+    CdScsiLU *dev;
+
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("Load, illegal lun:%u", lun);
+        return -1;
+    }
+    if (!cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("Load, unrealized lun:%u", lun);
+        return -1;
+    }
+    dev = &st->units[lun];
+
+    lun_info->started = dev->power_cond == CD_SCSI_POWER_ACTIVE;
+    lun_info->locked = dev->prevent_media_removal;
+    lun_info->loaded = dev->loaded;
+
+    lun_info->parameters.vendor = dev->vendor;
+    lun_info->parameters.product = dev->product;
+    lun_info->parameters.version = dev->version;
+    lun_info->parameters.serial = dev->serial;
+
+    return 0;
+}
+
+int cd_scsi_dev_unload(CdScsiTarget *st, uint32_t lun)
+{
+    CdScsiLU *dev;
+
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("Unload, illegal lun:%u", lun);
+        return -1;
+    }
+    if (!cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("Unload, unrealized lun:%u", lun);
+        return -1;
+    }
+    dev = &st->units[lun];
+    if (!dev->loaded) {
+        SPICE_ERROR("Unload, lun:%u not loaded yet", lun);
+        return 0;
+    }
+    if (dev->prevent_media_removal) {
+        SPICE_ERROR("Unload, lun:%u prevent_media_removal set", lun);
+        return -1;
+    }
+
+    cd_scsi_lu_unload(dev);
+    dev->power_cond = CD_SCSI_POWER_STOPPED;
+    dev->power_event = CD_POWER_EVENT_CHANGE_SUCCESS;
+
+    cd_scsi_dev_sense_set(dev, &sense_code_UA_NO_MEDIUM);
+
+    SPICE_DEBUG("Unload lun:%u", lun);
+    return 0;
+}
+
+int cd_scsi_dev_unrealize(CdScsiTarget *st, uint32_t lun)
+{
+    CdScsiLU *dev;
+
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("Unrealize, illegal lun:%u", lun);
+        return -1;
+    }
+    if (!cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("Unrealize, absent lun:%u", lun);
+        return -1;
+    }
+    dev = &st->units[lun];
+
+    g_clear_pointer(&dev->vendor, g_free);
+    g_clear_pointer(&dev->product, g_free);
+    g_clear_pointer(&dev->version, g_free);
+    g_clear_pointer(&dev->serial, g_free);
+
+    g_clear_object(&dev->stream);
+
+    dev->loaded = FALSE;
+    dev->realized = FALSE;
+    dev->power_cond = CD_SCSI_POWER_STOPPED;
+
+    SPICE_DEBUG("Unrealize lun:%u", lun);
+    return 0;
+}
+
+int cd_scsi_dev_reset(CdScsiTarget *st, uint32_t lun)
+{
+    CdScsiLU *dev;
+
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("Device reset, illegal lun:%u", lun);
+        return -1;
+    }
+    if (!cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("Device reset, absent lun:%u", lun);
+        return -1;
+    }
+    dev = &st->units[lun];
+
+    /* if we reset the 'prevent' flag we can't create
+     * the unit that is locked from the beginning, so
+     * we keep this flag as persistent over resets
+     */
+    /* dev->prevent_media_removal = FALSE; */
+    dev->power_cond = CD_SCSI_POWER_ACTIVE;
+    dev->power_event = CD_POWER_EVENT_CHANGE_SUCCESS;
+    cd_scsi_dev_sense_set_power_on(dev);
+
+    SPICE_DEBUG("Device reset lun:%u", lun);
+    return 0;
+}
+
+static void cd_scsi_target_do_reset(CdScsiTarget *st)
+{
+    uint32_t lun;
+
+    for (lun = 0; lun < st->max_luns; lun++) {
+        if (st->units[lun].realized) {
+            cd_scsi_dev_reset(st, lun);
+        }
+    }
+
+    SPICE_DEBUG("Target reset complete");
+    st->state = CD_SCSI_TGT_STATE_RUNNING;
+    cd_scsi_target_reset_complete(st->user_data);
+}
+
+int cd_scsi_target_reset(CdScsiTarget *st)
+{
+    if (st->state == CD_SCSI_TGT_STATE_RESET) {
+        SPICE_DEBUG("Target already in reset");
+        return -1;
+    }
+
+    st->state = CD_SCSI_TGT_STATE_RESET;
+
+    if (st->cur_req != NULL) {
+        cd_scsi_dev_request_cancel(st, st->cur_req);
+        if (st->cur_req != NULL) {
+            SPICE_DEBUG("Target reset in progress...");
+            return 0;
+        }
+    }
+
+    cd_scsi_target_do_reset(st);
+    return 0;
+}
+
+CdScsiReqState cd_scsi_get_req_state(CdScsiRequest *req)
+{
+    return req->req_state;
+}
+
+static void strpadcpy(char *buf, int buf_size, const char *str, char pad)
+{
+    int len = strnlen(str, buf_size);
+    memcpy(buf, str, len);
+    memset(buf + len, pad, buf_size - len);
+}
+
+/* SCSI CDB */
+
+static unsigned int scsi_cdb_length(const uint8_t *cdb)
+{
+    unsigned int cdb_len;
+
+    switch (cdb[0] >> 5) {
+    case 0:
+        cdb_len = 6;
+        break;
+    case 1:
+    case 2:
+        cdb_len = 10;
+        break;
+    case 4:
+        cdb_len = 16;
+        break;
+    case 5:
+        cdb_len = 12;
+        break;
+    default:
+        cdb_len = 0;
+    }
+    return cdb_len;
+}
+
+static uint64_t scsi_cdb_lba(const uint8_t *cdb, int cdb_len)
+{
+    uint64_t lba;
+
+    switch (cdb_len) {
+    case 6:
+        lba = (((uint64_t)(cdb[1] & 0x1f)) << 16) |
+              (((uint64_t)cdb[2]) << 8) |
+               ((uint64_t)cdb[3]);
+        break;
+    case 10:
+    case 12:
+        lba = (((uint64_t)cdb[2]) << 24) |
+              (((uint64_t)cdb[3]) << 16) |
+              (((uint64_t)cdb[4]) << 8)  |
+               ((uint64_t)cdb[5]);
+        break;
+    case 16:
+        lba = (((uint64_t)cdb[2]) << 56) |
+              (((uint64_t)cdb[3]) << 48) |
+              (((uint64_t)cdb[4]) << 40) |
+              (((uint64_t)cdb[5]) << 32) |
+              (((uint64_t)cdb[6]) << 24) |
+              (((uint64_t)cdb[7]) << 16) |
+              (((uint64_t)cdb[8]) << 8)  |
+               ((uint64_t)cdb[9]);
+        break;
+    default:
+        lba = 0;
+    }
+    return lba;
+}
+
+static uint32_t scsi_cdb_xfer_length(const uint8_t *cdb, int cdb_len)
+{
+    uint32_t len;
+
+    switch (cdb_len) {
+    case 6:
+        len = (uint32_t)cdb[4];
+        if (len == 0)
+            len = 256;
+        break;
+    case 10:
+        len = (((uint32_t)cdb[7]) << 8) |
+               ((uint32_t)cdb[8]);
+        break;
+    case 12:
+        len = (((uint32_t)cdb[6]) << 24) |
+              (((uint32_t)cdb[7]) << 16) |
+              (((uint32_t)cdb[8]) << 8)  |
+               ((uint32_t)cdb[9]);
+        break;
+    case 16:
+        len = (((uint32_t)cdb[10]) << 24) |
+              (((uint32_t)cdb[11]) << 16) |
+              (((uint32_t)cdb[12]) << 8)  |
+               ((uint32_t)cdb[13]);
+        break;
+    default:
+        len = 0;
+        break;
+    }
+    return len;
+}
+
+/* SCSI commands */
+
+static void cd_scsi_cmd_test_unit_ready(CdScsiLU *dev, CdScsiRequest *req)
+{
+    req->xfer_dir = SCSI_XFER_NONE;
+    req->in_len = 0;
+
+    if (dev->loaded) {
+        if (dev->power_cond != CD_SCSI_POWER_STOPPED) {
+            cd_scsi_cmd_complete_good(dev, req);
+        } else {
+            cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INIT_CMD_REQUIRED);
+        }
+    } else {
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_NOT_READY_NO_MEDIUM);
+    }
+}
+
+static void cd_scsi_cmd_request_sense(CdScsiLU *dev, CdScsiRequest *req)
+{
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    req->req_len = req->cdb[4];
+    req->in_len = MIN(req->req_len, sizeof(dev->fixed_sense));
+
+    if (dev->short_sense.key != NO_SENSE) {
+        SPICE_DEBUG("%s, lun:%u reported sense: 0x%02x %02x %02x - %s, %s",
+                    __FUNCTION__, req->lun,
+                    dev->short_sense.key, dev->short_sense.asc, dev->short_sense.ascq,
+                    cd_scsi_sense_key_descr(dev->short_sense.key),
+                    dev->short_sense.descr);
+    }
+    memcpy(req->buf, dev->fixed_sense, sizeof(dev->fixed_sense));
+    cd_scsi_dev_sense_reset(dev); /* clear reported sense */
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_cmd_report_luns(CdScsiTarget *st, CdScsiLU *dev,
+                                    CdScsiRequest *req)
+{
+    uint8_t *out_buf = req->buf;
+    uint32_t max_luns = st->max_luns;
+    uint32_t buflen = 8; /* header length */
+    uint32_t lun;
+
+    req->req_len = scsi_cdb_xfer_length(req->cdb, 12);
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    /* check SELECT REPORT field */
+    if (req->cdb[2] == 0x01) {
+        /* only well known logical units */
+        max_luns = 0;
+    }
+
+    memset(out_buf, 0, 8);
+
+    for (lun = 0; lun < max_luns; lun++) {
+        if (st->units[lun].realized) {
+            out_buf[buflen++] = 0;
+            out_buf[buflen++] = (uint8_t)(lun);
+            memset(&out_buf[buflen], 0, 6);
+            buflen += 6;
+        }
+    }
+
+    /* fill LUN LIST LENGTH */
+    out_buf[0] = (uint8_t)((buflen-8) >> 24);
+    out_buf[1] = (uint8_t)((buflen-8) >> 16);
+    out_buf[2] = (uint8_t)((buflen-8) >> 8);
+    out_buf[3] = (uint8_t)((buflen-8));
+
+    req->in_len = buflen;
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define SCSI_MAX_INQUIRY_LEN        256
+#define SCSI_MAX_MODE_LEN           256
+
+static void cd_scsi_cmd_inquiry_vpd_no_lun(CdScsiLU *dev, CdScsiRequest *req,
+                                           uint32_t perif_qual)
+{
+    uint8_t *outbuf = req->buf;
+    uint8_t page_code = req->cdb[2];
+    uint32_t resp_len = 4;
+
+    outbuf[0] = (perif_qual << 5) | TYPE_ROM;
+    outbuf[1] = page_code ; /* this page */
+    outbuf[2] = 0x00; /* page length MSB */
+    outbuf[3] = 0x00; /* page length LSB - no more data */
+
+    req->in_len = MIN(req->req_len, resp_len);
+
+    SPICE_DEBUG("inquiry_vpd, unsupported lun:%u"
+                " perif_qual:0x%x resp_len: %" G_GUINT64_FORMAT,
+                req->lun, perif_qual, req->in_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_cmd_inquiry_vpd(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint8_t page_code = req->cdb[2];
+    int buflen = 4;
+    int start = 4;
+
+    outbuf[0] = TYPE_ROM;
+    outbuf[1] = page_code ; /* this page */
+    outbuf[2] = 0x00; /* page length MSB */
+    outbuf[3] = 0x00; /* page length LSB, to write later */
+
+    switch (page_code) {
+    case 0x00: /* Supported page codes, mandatory */
+    {
+        outbuf[buflen++] = 0x00; // list of supported pages (this page)
+        if (dev->serial) {
+            outbuf[buflen++] = 0x80; // unit serial number
+        }
+        outbuf[buflen++] = 0x83; // device identification
+
+        SPICE_DEBUG("Inquiry EVPD[Supported pages] lun:%u"
+                    " req_len: %" G_GUINT64_FORMAT " resp_len: %d",
+                    req->lun, req->req_len, buflen);
+        break;
+    }
+    case 0x80: /* Device serial number, optional */
+    {
+        int serial_len;
+
+        serial_len = strlen(dev->serial);
+        if (serial_len > 36) {
+            serial_len = 36;
+        }
+        memcpy(outbuf+buflen, dev->serial, serial_len);
+        buflen += serial_len;
+
+        SPICE_DEBUG("Inquiry EVPD[Serial num] lun:%u"
+                    " req_len: %" G_GUINT64_FORMAT " resp_len: %d",
+                    req->lun, req->req_len, buflen);
+        break;
+    }
+    case 0x83: /* Device identification page, mandatory */
+    {
+        int serial_len = strlen(dev->serial);
+        int max_len = 20;
+
+        if (serial_len > max_len) {
+            serial_len = max_len;
+        }
+
+        outbuf[buflen++] = 0x2; // ASCII
+        outbuf[buflen++] = 0;   // not officially assigned
+        outbuf[buflen++] = 0;   // reserved
+        outbuf[buflen++] = serial_len; // length of data following
+
+        memcpy(outbuf+buflen, dev->serial, serial_len);
+        buflen += serial_len;
+
+        SPICE_DEBUG("Inquiry EVPD[Device id] lun:%u"
+                    " req_len: %" G_GUINT64_FORMAT " resp_len: %d",
+                    req->lun, req->req_len, buflen);
+        break;
+    }
+
+    default:
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        SPICE_DEBUG("inquiry_standard, lun:%u invalid page_code: %02x",
+                    req->lun, (int)page_code);
+        return;
+    }
+
+    /* done with EVPD */
+    g_assert(buflen - start <= 255);
+    outbuf[3] = buflen - start; /* page length LSB */
+
+    req->in_len = buflen;
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define INQUIRY_STANDARD_LEN_MIN            36
+#define INQUIRY_STANDARD_LEN                96
+#define INQUIRY_STANDARD_LEN_NO_VER         57
+
+#define PERIF_QUALIFIER_CONNECTED           0x00
+#define PERIF_QUALIFIER_NOT_CONNECTED       0x01
+#define PERIF_QUALIFIER_UNSUPPORTED         0x03
+
+#define INQUIRY_REMOVABLE_MEDIUM            0x80
+
+#define INQUIRY_VERSION_NONE                0x00
+#define INQUIRY_VERSION_SPC3                0x05
+
+/* byte 3 */
+#define INQUIRY_RESP_HISUP                  (0x01 << 4)
+#define INQUIRY_RESP_NORM_ACA               (0x01 << 5)
+#define INQUIRY_RESP_DATA_FORMAT_SPC3       0x02
+
+#define INQUIRY_VERSION_DESC_SAM2           0x040
+#define INQUIRY_VERSION_DESC_SPC3           0x300
+#define INQUIRY_VERSION_DESC_MMC3           0x2A0
+#define INQUIRY_VERSION_DESC_SBC2           0x320
+
+static void cd_scsi_cmd_inquiry_standard_no_lun(CdScsiLU *dev, CdScsiRequest *req,
+                                                uint32_t perif_qual)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t resp_len = INQUIRY_STANDARD_LEN_MIN;
+
+    memset(req->buf, 0, INQUIRY_STANDARD_LEN_MIN);
+
+    outbuf[0] = (perif_qual << 5) | TYPE_ROM;
+    outbuf[2] = INQUIRY_VERSION_NONE;
+    outbuf[3] = INQUIRY_RESP_DATA_FORMAT_SPC3;
+
+    outbuf[4] = resp_len - 4; /* additional length, after header */
+
+    req->in_len = MIN(req->req_len, resp_len);
+
+    SPICE_DEBUG("inquiry_standard, unsupported lun:%u perif_qual:0x%x "
+                "inquiry_len: %u resp_len: %" G_GUINT64_FORMAT,
+                req->lun, perif_qual, resp_len, req->in_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_cmd_inquiry_standard(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t resp_len =
+        (dev->claim_version == 0) ? INQUIRY_STANDARD_LEN_NO_VER : INQUIRY_STANDARD_LEN;
+
+    outbuf[0] = (PERIF_QUALIFIER_CONNECTED << 5) | TYPE_ROM;
+    outbuf[1] = (dev->removable) ? INQUIRY_REMOVABLE_MEDIUM : 0;
+    outbuf[2] = (dev->claim_version == 0) ? INQUIRY_VERSION_NONE : INQUIRY_VERSION_SPC3;
+    outbuf[3] = INQUIRY_RESP_NORM_ACA | INQUIRY_RESP_HISUP | INQUIRY_RESP_DATA_FORMAT_SPC3;
+
+    outbuf[4] = resp_len - 4; /* additional length, after header */
+
+    /* (outbuf[6,7] = 0) means also {BQue=0,CmdQue=0} - no queueing at all */
+
+    strpadcpy((char *) &outbuf[8], 8, dev->vendor, ' ');
+    strpadcpy((char *) &outbuf[16], 16, dev->product, ' ');
+    memcpy(&outbuf[32], dev->version, MIN(4, strlen(dev->version)));
+
+    if (dev->claim_version > 0) {
+        /* now supporting only 3 */
+        outbuf[58] = (INQUIRY_VERSION_DESC_SAM2 >> 8) & 0xff;
+        outbuf[59] = INQUIRY_VERSION_DESC_SAM2 & 0xff;
+
+        outbuf[60] = (INQUIRY_VERSION_DESC_SPC3 >> 8) & 0xff;
+        outbuf[61] = INQUIRY_VERSION_DESC_SPC3 & 0xff;
+
+        outbuf[62] = (INQUIRY_VERSION_DESC_MMC3 >> 8) & 0xff;
+        outbuf[63] = INQUIRY_VERSION_DESC_MMC3 & 0xff;
+
+        outbuf[64] = (INQUIRY_VERSION_DESC_SBC2 >> 8) & 0xff;
+        outbuf[65] = INQUIRY_VERSION_DESC_SBC2 & 0xff;
+    }
+
+    req->in_len = MIN(req->req_len, resp_len);
+
+    SPICE_DEBUG("inquiry_standard, lun:%u"
+                " inquiry_len: %u resp_len: %" G_GUINT64_FORMAT,
+                req->lun, resp_len, req->in_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define CD_INQUIRY_FLAG_EVPD                0x01
+#define CD_INQUIRY_FLAG_CMD_DT              0x02
+
+static void cd_scsi_cmd_inquiry(CdScsiLU *dev, CdScsiRequest *req)
+{
+    gboolean evpd, cmd_data;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    evpd = (req->cdb[1] & CD_INQUIRY_FLAG_EVPD) ? TRUE : FALSE;
+    cmd_data = (req->cdb[1] & CD_INQUIRY_FLAG_CMD_DT) ? TRUE : FALSE;
+
+    if (cmd_data) {
+        SPICE_DEBUG("inquiry, lun:%u CmdDT bit set - unsupported, "
+                    "cdb[1]:0x%02x cdb[1]:0x%02x",
+                    req->lun, (int)req->cdb[1], (int)req->cdb[2]);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+
+    req->req_len = req->cdb[4] | (req->cdb[3] << 8);
+    memset(req->buf, 0, req->req_len);
+
+    if (evpd) { /* enable vital product data */
+        cd_scsi_cmd_inquiry_vpd(dev, req);
+    } else { /* standard inquiry data */
+        if (req->cdb[2] != 0) {
+            SPICE_DEBUG("inquiry_standard, lun:%u non-zero page code: %02x",
+                        req->lun, (int)req->cdb[2]);
+            cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+            return;
+        }
+        cd_scsi_cmd_inquiry_standard(dev, req);
+    }
+}
+
+static void cd_scsi_cmd_read_capacity(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint32_t last_blk = dev->num_blocks - 1;
+    uint32_t blk_size = dev->block_size;
+    uint32_t *last_blk_out = (uint32_t *)req->buf;
+    uint32_t *blk_size_out = (uint32_t *)(req->buf + 4);
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+    req->req_len = 8;
+
+    *last_blk_out = htobe32(last_blk);
+    *blk_size_out = htobe32(blk_size);
+
+    SPICE_DEBUG("Read capacity, lun:%u last_blk: %u blk_sz: %u",
+                req->lun, last_blk, blk_size);
+
+    req->in_len = 8;
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define RDI_TYPE_STANDARD           0 /* Standard Disc Information */
+#define RDI_TYPE_TRACK_RESOURCES    1 /* Track Resources Information */
+#define RDI_TYPE_POW_RESOURCES      2 /* POW Resources Information */
+
+#define RDI_STANDARD_LEN            34
+
+#define RDI_ERAZABLE                (0x01 << 4)
+#define RDI_NON_ERAZABLE            (0x00 << 4)
+
+#define RDI_SESSION_EMPTY           (0x00 << 2)
+#define RDI_SESSION_INCOMPLETE      (0x01 << 2)
+#define RDI_SESSION_DAMAGED         (0x02 << 2)
+#define RDI_SESSION_COMPLETE        (0x03 << 2)
+
+#define RDI_DISC_EMPTY              0x00
+#define RDI_DISC_INCOMPLETE         0x01
+#define RDI_DISC_COMPLETE           0x02
+#define RDI_DISC_RANDOM_WR          0x03
+
+#define RDI_DISC_PMA_TYPE_CD_ROM    0x00
+#define RDI_DISC_PMA_TYPE_CDI       0x10
+#define RDI_DISC_PMA_TYPE_DDCD      0x20
+#define RDI_DISC_PMA_TYPE_UNDEFINED 0xFF
+
+static void cd_scsi_cmd_get_read_disc_information(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t data_type;
+    uint32_t first_track = 1;
+    uint32_t last_track = 1;
+    uint32_t num_sessions = 1;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    data_type = req->cdb[1] & 0x7;
+    if (data_type != RDI_TYPE_STANDARD) {
+        SPICE_DEBUG("read_disc_information, lun:%u"
+                    " unsupported data type: %02x",
+                    req->lun, data_type);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+
+    req->req_len = (req->cdb[7] << 8) | req->cdb[8];
+    req->in_len = MIN(req->req_len, RDI_STANDARD_LEN);
+
+    memset(outbuf, 0, RDI_STANDARD_LEN);
+    outbuf[1] = RDI_STANDARD_LEN - 2; /* length excluding the counter itself */
+    outbuf[2] = RDI_NON_ERAZABLE | RDI_SESSION_COMPLETE | RDI_DISC_COMPLETE;
+    outbuf[3] = first_track; /* on disk */
+    outbuf[4] = num_sessions & 0xff; /* lsb */
+    outbuf[5] = first_track & 0xff; /* in last sesson, lsb */
+    outbuf[6] = last_track & 0xff; /* in last sesson, lsb */
+    outbuf[8] = RDI_DISC_PMA_TYPE_CD_ROM;
+    outbuf[9] = (num_sessions >> 8) & 0xff; /* msb */
+    outbuf[10] = (first_track >> 8) & 0xff; /* in last sesson, lsb */
+    outbuf[11] = (last_track >> 8) & 0xff; /* in last sesson, lsb */
+
+    SPICE_DEBUG("read_disc_information, lun:%u len: %" G_GUINT64_FORMAT,
+                req->lun, req->in_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define RTI_ADDR_TYPE_LBA           0x00
+#define RTI_ADDR_TYPE_TRACK_NUM     0x01
+#define RTI_ADDR_TYPE_SESSION_NUM   0x02
+
+#define RTI_TRACK_NUM_LEAD_IN       0x00
+#define RTI_TRACK_NUM_INVISIBLE     0xff
+
+#define TIB_LEN                     0x36
+
+#define TIB_TRACK_MODE_CD           0x04
+#define TIB_DATA_MODE_ISO_10149     0x01
+
+#define TIB_LRA_VALID               (0x01 << 1)
+
+static void cd_scsi_cmd_get_read_track_information(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t track_size = dev->num_blocks;
+    uint32_t last_addr = track_size - 1;
+    uint32_t track_num = 1;
+    uint32_t session_num = 1;
+    uint32_t addr_type;
+    uint32_t addr_num;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    addr_type = req->cdb[1] & 0x3;
+    addr_num = (req->cdb[2] << 24) | (req->cdb[3] << 16) |
+               (req->cdb[4] << 8) | req->cdb[5];
+
+    switch (addr_type) {
+    case RTI_ADDR_TYPE_LBA:
+        if (addr_num > last_addr) {
+            SPICE_DEBUG("read_track_information, lun:%u"
+                        " addr_type LBA: %u"
+                        " invalid LBA: %u",
+                        req->lun, addr_type, addr_num);
+            cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+            return;
+        }
+        break;
+    case RTI_ADDR_TYPE_TRACK_NUM:
+        if (addr_num != track_num) {
+            SPICE_DEBUG("read_track_information, lun:%u"
+                        " addr_type track: %u"
+                        " invalid track: %u",
+                        req->lun, addr_type, addr_num);
+            cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+            return;
+        }
+        break;
+    case RTI_ADDR_TYPE_SESSION_NUM:
+        if (addr_num != session_num) {
+            SPICE_DEBUG("read_track_information, lun:%u"
+                        " addr_type session: %u"
+                        " invalid session: %u",
+                        req->lun, addr_type, addr_num);
+            cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+            return;
+        }
+        break;
+    default:
+        SPICE_DEBUG("read_track_information, lun:%u"
+                    " invalid addr_type: %u"
+                    " addr_num: %u",
+                    req->lun, addr_type, addr_num);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+
+    req->req_len = (req->cdb[7] << 8) | req->cdb[8];
+    req->in_len = MIN(req->req_len, TIB_LEN);
+
+    memset(outbuf, 0, TIB_LEN);
+    outbuf[1] = TIB_LEN - 2;
+    outbuf[2] = session_num;
+    outbuf[3] = track_num;
+    outbuf[5] = TIB_TRACK_MODE_CD & 0x0f;
+    outbuf[6] = TIB_DATA_MODE_ISO_10149 & 0x0f;
+    outbuf[7] = TIB_LRA_VALID;
+
+    /* Track size */
+    outbuf[24] = (track_size >> 24) & 0xff;
+    outbuf[25] = (track_size >> 16) & 0xff;
+    outbuf[26] = (track_size >> 8) & 0xff;
+    outbuf[27] = (track_size) & 0xff;
+
+    /* Last recorded address */
+    outbuf[28] = (last_addr >> 24) & 0xff;
+    outbuf[29] = (last_addr >> 16) & 0xff;
+    outbuf[30] = (last_addr >> 8) & 0xff;
+    outbuf[31] = (last_addr) & 0xff;
+
+    SPICE_DEBUG("read_track_information, lun:%u"
+                "addr_type: %u addr_num: %u",
+                req->lun, addr_type, addr_num);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define READ_TOC_TRACK_DESC_LEN     8
+#define READ_TOC_RESP_LEN           (4 + 2*READ_TOC_TRACK_DESC_LEN)
+
+static void cd_scsi_cmd_read_toc(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t msf, format, track_num;
+    uint32_t last_blk = dev->num_blocks - 1;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    msf = (req->cdb[1] >> 1) & 0x1;
+    format = req->cdb[2] & 0xf;
+    track_num = req->cdb[6];
+
+    req->req_len = (req->cdb[7] << 8) | req->cdb[8];
+    req->in_len = MIN(req->req_len, READ_TOC_RESP_LEN);
+
+    memset(outbuf, 0, READ_TOC_RESP_LEN);
+    outbuf[1] = READ_TOC_RESP_LEN - 2; /* length excluding the counter itself */
+    outbuf[2] = 1; /* first track/session */
+    outbuf[3] = 1; /* last track/session */
+
+    outbuf[5] = 0x04; /* Data CD, no Q-subchannel */
+    outbuf[6] = 0x01; /* Track number */
+    outbuf[10] = msf ? 0x02 : 0x0;
+
+    outbuf[13] = 0x04; /* Data CD, no Q-subchannel */
+    outbuf[14] = 0xaa; /* Track number */
+    if (msf) {
+        last_blk = 0xff300000;
+    }
+    outbuf[16] = last_blk >> 24;
+    outbuf[17] = last_blk >> 16;
+    outbuf[18] = last_blk >> 8;
+    outbuf[19] = last_blk;
+
+    SPICE_DEBUG("read_toc, lun:%u len: %" G_GUINT64_FORMAT
+                " msf: %x format: 0x%02x track/session: 0x%02x",
+                req->lun, req->in_len, msf, format, track_num);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define CD_MODE_PARAM_6_LEN_HEADER              4
+#define CD_MODE_PARAM_10_LEN_HEADER             8
+
+#define CD_MODE_PAGE_LEN_RW_ERROR               12
+
+static uint32_t cd_scsi_add_mode_page_rw_error_recovery(CdScsiLU *dev, uint8_t *outbuf)
+{
+    uint32_t page_len = CD_MODE_PAGE_LEN_RW_ERROR;
+
+    outbuf[0] = MODE_PAGE_R_W_ERROR;
+    outbuf[1] = CD_MODE_PAGE_LEN_RW_ERROR - 2;
+    outbuf[3] = 1; /* read retry count */
+
+    return page_len;
+}
+
+#define CD_MODE_PAGE_LEN_POWER                  12
+
+static uint32_t cd_scsi_add_mode_page_power_condition(CdScsiLU *dev, uint8_t *outbuf)
+{
+    uint32_t page_len = CD_MODE_PAGE_LEN_POWER;
+
+    outbuf[0] = MODE_PAGE_POWER;
+    outbuf[1] = CD_MODE_PAGE_LEN_POWER - 2;
+
+    return page_len;
+}
+
+#define CD_MODE_PAGE_LEN_FAULT_FAIL             12
+#define CD_MODE_PAGE_FAULT_FAIL_FLAG_PERF       0x80
+
+static uint32_t cd_scsi_add_mode_page_fault_reporting(CdScsiLU *dev, uint8_t *outbuf)
+{
+    uint32_t page_len = CD_MODE_PAGE_LEN_FAULT_FAIL;
+
+    outbuf[0] = MODE_PAGE_FAULT_FAIL;
+    outbuf[1] = CD_MODE_PAGE_LEN_FAULT_FAIL - 2;
+    outbuf[2] |= CD_MODE_PAGE_FAULT_FAIL_FLAG_PERF;
+
+    return page_len;
+}
+
+#define CD_MODE_PAGE_LEN_CAPS_MECH_STATUS_RO    26
+/* byte 2 */
+#define CD_MODE_PAGE_CAPS_CD_R_READ             0x01
+#define CD_MODE_PAGE_CAPS_CD_RW_READ            (0x01 << 1)
+#define CD_MODE_PAGE_CAPS_DVD_ROM_READ          (0x01 << 3)
+#define CD_MODE_PAGE_CAPS_DVD_R_READ            (0x01 << 4)
+#define CD_MODE_PAGE_CAPS_DVD_RAM_READ          (0x01 << 5)
+/* byte 6 */
+#define CD_MODE_PAGE_CAPS_LOCK_SUPPORT          (0x01)
+#define CD_MODE_PAGE_CAPS_LOCK_STATE            (0x01 << 1)
+#define CD_MODE_PAGE_CAPS_PREVENT_JUMPER        (0x01 << 2)
+#define CD_MODE_PAGE_CAPS_EJECT                 (0x01 << 3)
+#define CD_MODE_PAGE_CAPS_LOADING_TRAY          (0x01 << 5)
+
+static uint32_t cd_scsi_add_mode_page_caps_mech_status(CdScsiLU *dev, uint8_t *outbuf)
+{
+    uint32_t page_len = CD_MODE_PAGE_LEN_CAPS_MECH_STATUS_RO; /* no write */
+
+    outbuf[0] = MODE_PAGE_CAPS_MECH_STATUS;
+    outbuf[1] = page_len;
+    outbuf[2] = CD_MODE_PAGE_CAPS_CD_R_READ | CD_MODE_PAGE_CAPS_CD_RW_READ |
+                CD_MODE_PAGE_CAPS_DVD_ROM_READ | CD_MODE_PAGE_CAPS_DVD_R_READ |
+                CD_MODE_PAGE_CAPS_DVD_RAM_READ;
+    outbuf[6] = CD_MODE_PAGE_CAPS_LOADING_TRAY | CD_MODE_PAGE_CAPS_EJECT |
+                CD_MODE_PAGE_CAPS_LOCK_SUPPORT;
+    if (dev->prevent_media_removal) {
+        outbuf[6] |= CD_MODE_PAGE_CAPS_LOCK_STATE;
+    }
+
+    return page_len;
+}
+
+static void cd_scsi_cmd_mode_sense_10(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    int long_lba, dbd, page, sub_page, pc;
+    uint32_t resp_len = CD_MODE_PARAM_10_LEN_HEADER;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    long_lba = (req->cdb[1] >> 4) & 0x1;
+    dbd = (req->cdb[1] >> 3) & 0x1;
+    page = req->cdb[2] & 0x3f;
+    pc = req->cdb[2] >> 6;
+    sub_page = req->cdb[3];
+
+    req->req_len = (req->cdb[7] << 8) | req->cdb[8];
+
+    memset(outbuf, 0, req->req_len);
+    outbuf[2] =  0; /* medium type */
+
+    switch (page) {
+    case MODE_PAGE_R_W_ERROR:
+        /* Read/Write Error Recovery */
+        resp_len += cd_scsi_add_mode_page_rw_error_recovery(dev, outbuf + resp_len);
+        break;
+    case MODE_PAGE_POWER:
+        /* Power Condistions */
+        resp_len += cd_scsi_add_mode_page_power_condition(dev, outbuf + resp_len);
+        break;
+    case MODE_PAGE_FAULT_FAIL:
+        /* Fault / Failure Reporting Control */
+        resp_len += cd_scsi_add_mode_page_fault_reporting(dev, outbuf + resp_len);
+        break;
+    case MODE_PAGE_CAPS_MECH_STATUS:
+        resp_len += cd_scsi_add_mode_page_caps_mech_status(dev, outbuf + resp_len);
+        break;
+
+    /* not implemented */
+    case MODE_PAGE_WRITE_PARAMETER: /* Writer Parameters */
+    case MODE_PAGE_MRW:
+    case MODE_PAGE_MRW_VENDOR: /* MRW (Mount Rainier Re-writable Disks */
+    case MODE_PAGE_CD_DEVICE: /* CD Device parameters */
+    case MODE_PAGE_TO_PROTECT: /* Time-out and Protect */
+    default:
+        SPICE_DEBUG("mode_sense_10, lun:%u"
+                    " page 0x%x not implemented",
+                    req->lun, (unsigned)page);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+
+    outbuf[0] = ((resp_len - 2) >> 8) & 0xff;
+    outbuf[1] = (resp_len - 2) & 0xff;
+
+    req->in_len = MIN(req->req_len, resp_len);
+
+    SPICE_DEBUG("mode_sense_10, lun:%u"
+                " long_lba %d, dbd %d, page %d, sub_page %d, pc %d; "
+                "resp_len %u",
+                req->lun, long_lba, dbd, page, sub_page, pc, resp_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_cmd_mode_select_6(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *block_desc_data, *mode_data;
+    uint32_t page_format, save_pages, list_len; /* cdb */
+    uint32_t num_blocks = 0, block_len = 0; /* block descriptor */
+    uint32_t mode_len, medium_type, dev_param, block_desc_len; /* mode param header */
+    uint32_t page_num = 0, page_len = 0; /* mode page */
+
+    page_format = (req->cdb[1] >> 4) & 0x1;
+    save_pages = req->cdb[1] & 0x1;
+    list_len = req->cdb[4];
+
+    if (list_len > req->buf_len) {
+        SPICE_DEBUG("mode_select_6, lun:%u"
+                    " pf:%u sp:%u"
+                    " list_len:%u exceeds data_len:%u",
+                    req->lun, page_format, save_pages, list_len, req->buf_len);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_PARAM_LEN);
+        return;
+    }
+
+    mode_len = req->buf[0];
+    medium_type = req->buf[1];
+    dev_param = req->buf[2];
+    block_desc_len = req->buf[3];
+
+    if (block_desc_len) {
+        block_desc_data = &req->buf[CD_MODE_PARAM_6_LEN_HEADER];
+        num_blocks = (block_desc_data[3] << 16) | (block_desc_data[2] << 8) | block_desc_data[3];
+        block_len = (block_desc_data[5] << 16) | (block_desc_data[6] << 8) | block_desc_data[7];
+    }
+
+    if (mode_len) {
+        mode_data = &req->buf[CD_MODE_PARAM_6_LEN_HEADER];
+        if (block_desc_len) {
+            mode_data += block_desc_len;
+        }
+        page_num = mode_data[0] & 0x3f;
+        page_len = mode_data[1];
+    }
+
+    SPICE_DEBUG("mode_select_6, lun:%u"
+                " pf:%u sp:%u list_len:%u data_len:%u"
+                " mode_len:%u medium:%u dev_param:%u blk_desc_len:%u"
+                " num_blocks:%u block_len:%u"
+                " page_num:%u page_len:%u",
+                req->lun, page_format, save_pages, list_len, req->buf_len,
+                mode_len, medium_type, dev_param, block_desc_len,
+                num_blocks, block_len,
+                page_num, page_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_cmd_mode_select_10(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint32_t page_format, save_pages, list_len;
+
+    page_format = (req->cdb[1] >> 4) & 0x1;
+    save_pages = req->cdb[1] & 0x1;
+    list_len = (req->cdb[7] << 8) | req->cdb[8];
+
+    if (list_len > req->buf_len) {
+        SPICE_DEBUG("mode_select_10, lun:%u pf:%u sp:%u"
+                    " list_len:%u exceeds data_len:%u",
+                    req->lun, page_format, save_pages, list_len, req->buf_len);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_PARAM_LEN);
+        return;
+    }
+
+    SPICE_DEBUG("mode_select_10, lun:%u pf:%u sp:%u"
+                " list_len:%u data_len:%u",
+                req->lun, page_format, save_pages, list_len, req->buf_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define CD_FEATURE_HEADER_LEN               8
+#define CD_FEATURE_DESC_LEN                 4
+
+#define CD_PROFILE_DESC_LEN                 4
+#define CD_PROFILE_CURRENT                  0x01
+
+/* Profiles List */
+#define CD_FEATURE_NUM_PROFILES_LIST        0x00
+/* Core - Basic Functionality */
+#define CD_FEATURE_NUM_CORE                 0x01
+/* Morphing - The device changes its behavior due to external events */
+#define CD_FEATURE_NUM_MORPH                0x02
+/* Removable Medium - The medium may be removed from the device */
+#define CD_FEATURE_NUM_REMOVABLE            0x03
+/* Random Readable - PP=1 Read ability for storage devices with random addressing */
+#define CD_FEATURE_NUM_RANDOM_READ          0x10
+/* CD Read - The ability to read CD specific structures */
+#define CD_FEATURE_NUM_CD_READ              0x1E
+/* DVD Read - The ability to read DVD specific structures */
+#define CD_FEATURE_NUM_DVD_READ             0x1F
+/* Power Management - Initiator and device directed power management */
+#define CD_FEATURE_NUM_POWER_MNGT           0x100
+/* Timeout */
+#define CD_FEATURE_NUM_TIMEOUT              0x105
+
+#define CD_FEATURE_REQ_ALL                  0
+#define CD_FEATURE_REQ_CURRENT              1
+#define CD_FEATURE_REQ_SINGLE               2
+
+#define CD_FEATURE_CURRENT                  0x01
+#define CD_FEATURE_PERSISTENT               0x02
+
+#define CD_FEATURE_VERSION_1                (0x01 << 2)
+
+#define CD_FEATURE_PHYS_IF_SCSI             0x01
+
+#define CD_FEATURE_REMOVABLE_LOADING_TRAY   (0x01 << 5)
+#define CD_FEATURE_REMOVABLE_EJECT          (0x01 << 3)
+#define CD_FEATURE_REMOVABLE_NO_PRVNT_JMPR  (0x01 << 2)
+#define CD_FEATURE_REMOVABLE_LOCK           (0x01)
+
+static gboolean cd_scsi_feature_reportable(uint32_t feature, uint32_t start_feature,
+                                           uint32_t req_type)
+{
+    return (req_type == CD_FEATURE_REQ_SINGLE && start_feature == feature) ||
+           (feature >= start_feature);
+}
+
+static uint32_t cd_scsi_add_feature_profiles_list(CdScsiLU *dev, uint8_t *outbuf,
+                                                  uint32_t start_feature, uint32_t req_type)
+{
+    uint8_t *profile = outbuf + CD_FEATURE_DESC_LEN;
+    uint32_t feature_len = CD_FEATURE_DESC_LEN;
+    uint32_t add_len, profile_num;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_PROFILES_LIST, start_feature, req_type)) {
+        return 0;
+    }
+    /* feature descriptor header */
+    outbuf[0] = (CD_FEATURE_NUM_PROFILES_LIST >> 8) & 0xff;
+    outbuf[1] = CD_FEATURE_NUM_PROFILES_LIST & 0xff;
+    outbuf[2] = CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+
+    /* DVD-ROM profile descriptor */
+    add_len = CD_PROFILE_DESC_LEN; /* start with single profile, add later */
+    profile_num = MMC_PROFILE_DVD_ROM;
+
+    profile[0] = (profile_num >> 8) & 0xff; /* feature code */
+    profile[1] = profile_num & 0xff;
+    profile[2] = (!dev->cd_rom) ? CD_PROFILE_CURRENT : 0;
+
+    /* next profile */
+    add_len += CD_PROFILE_DESC_LEN;
+    profile += CD_PROFILE_DESC_LEN;
+
+    /* CD-ROM profile descriptor */
+    profile_num = MMC_PROFILE_CD_ROM;
+    profile[0] = (profile_num >> 8) & 0xff;
+    profile[1] = profile_num & 0xff;
+    profile[2] = dev->cd_rom ? CD_PROFILE_CURRENT : 0;
+
+    outbuf[3] = add_len;
+    feature_len += add_len;
+
+    return feature_len;
+}
+
+#define CD_FEATURE_CORE_PHYS_PROFILE_LEN    4
+
+static uint32_t cd_scsi_add_feature_core(CdScsiLU *dev, uint8_t *outbuf,
+                                         uint32_t start_feature, uint32_t req_type)
+{
+    uint8_t *profile = outbuf + CD_FEATURE_DESC_LEN;
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_CORE_PHYS_PROFILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_CORE, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[0] = (CD_FEATURE_NUM_CORE >> 8) & 0xff;
+    outbuf[1] = CD_FEATURE_NUM_CORE & 0xff;
+    outbuf[2] = CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_CORE_PHYS_PROFILE_LEN;
+
+    profile[3] = CD_FEATURE_PHYS_IF_SCSI;
+
+    return feature_len;
+}
+
+#define CD_FEATURE_MORPH_PROGILE_LEN    4
+#define CD_FEATURE_MORPH_ASYNC_EVENTS   0x01
+
+static uint32_t cd_scsi_add_feature_morph(CdScsiLU *dev, uint8_t *outbuf,
+                                          uint32_t start_feature, uint32_t req_type)
+{
+    uint8_t *profile = outbuf + CD_FEATURE_DESC_LEN;
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_MORPH_PROGILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_MORPH, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[1] = CD_FEATURE_NUM_MORPH;
+    outbuf[2] = CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_MORPH_PROGILE_LEN;
+
+    profile[0] = CD_FEATURE_MORPH_ASYNC_EVENTS;
+
+    return feature_len;
+}
+
+#define CD_FEATURE_REMOVABLE_PROFILE_LEN    4
+
+static uint32_t cd_scsi_add_feature_removable(CdScsiLU *dev, uint8_t *outbuf,
+                                              uint32_t start_feature, uint32_t req_type)
+{
+    uint8_t *profile = outbuf + CD_FEATURE_DESC_LEN;
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_REMOVABLE_PROFILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_REMOVABLE, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[1] = CD_FEATURE_NUM_REMOVABLE;
+    outbuf[2] = CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_REMOVABLE_PROFILE_LEN;
+
+    profile[0] = CD_FEATURE_REMOVABLE_NO_PRVNT_JMPR;
+    if (dev->removable) {
+        profile[0] |= (CD_FEATURE_REMOVABLE_LOADING_TRAY | CD_FEATURE_REMOVABLE_EJECT);
+    }
+
+    return feature_len;
+}
+
+#define CD_FEATURE_RANDOM_READ_PROFILE_LEN    8
+
+static uint32_t cd_scsi_add_feature_random_read(CdScsiLU *dev, uint8_t *outbuf,
+                                                uint32_t start_feature, uint32_t req_type)
+{
+    uint8_t *profile = outbuf + CD_FEATURE_DESC_LEN;
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_RANDOM_READ_PROFILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_RANDOM_READ, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[0] = (CD_FEATURE_NUM_RANDOM_READ >> 8) & 0xff;
+    outbuf[1] = CD_FEATURE_NUM_RANDOM_READ & 0xff;
+    outbuf[2] = CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_RANDOM_READ_PROFILE_LEN;
+
+    profile[0] = (dev->block_size >> 24) & 0xff;
+    profile[1] = (dev->block_size >> 16) & 0xff;
+    profile[2] = (dev->block_size >> 8) & 0xff;
+    profile[3] = (dev->block_size) & 0xff;
+    profile[5] = (dev->cd_rom) ? 0x01 : 0x10; /* logical blocks per readable unit */
+
+    return feature_len;
+}
+
+#define CD_FEATURE_CD_READ_PROFILE_LEN    4
+
+static uint32_t cd_scsi_add_feature_cd_read(CdScsiLU *dev, uint8_t *outbuf,
+                                            uint32_t start_feature, uint32_t req_type)
+{
+    uint8_t *profile = outbuf + CD_FEATURE_DESC_LEN;
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_CD_READ_PROFILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_CD_READ, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[0] = (CD_FEATURE_NUM_CD_READ >> 8) & 0xff;
+    outbuf[1] = (CD_FEATURE_NUM_CD_READ) & 0xff;
+    outbuf[2] = CD_FEATURE_VERSION_1 | CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_CD_READ_PROFILE_LEN;
+
+    profile[0] = 0; /* C2 Errors, CD-Text not supporte */
+
+    return feature_len;
+}
+
+#define CD_FEATURE_DVD_READ_PROFILE_LEN    0
+
+static uint32_t cd_scsi_add_feature_dvd_read(CdScsiLU *dev, uint8_t *outbuf,
+                                             uint32_t start_feature, uint32_t req_type)
+{
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_DVD_READ_PROFILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_CD_READ, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[0] = (CD_FEATURE_NUM_DVD_READ >> 8) & 0xff;
+    outbuf[1] = (CD_FEATURE_NUM_DVD_READ) & 0xff;
+    outbuf[2] = CD_FEATURE_VERSION_1 | CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_DVD_READ_PROFILE_LEN;
+
+    return feature_len;
+}
+
+#define CD_FEATURE_POWER_MNGT_PROFILE_LEN    0
+
+static uint32_t cd_scsi_add_feature_power_mgmt(CdScsiLU *dev, uint8_t *outbuf,
+                                               uint32_t start_feature, uint32_t req_type)
+{
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_POWER_MNGT_PROFILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_POWER_MNGT, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[0] = (CD_FEATURE_NUM_POWER_MNGT >> 8) & 0xff;
+    outbuf[1] = (CD_FEATURE_NUM_POWER_MNGT) & 0xff;
+    outbuf[2] = CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_POWER_MNGT_PROFILE_LEN;
+
+    return feature_len;
+}
+
+#define CD_FEATURE_TIMEOUT_PROFILE_LEN    0
+
+static uint32_t cd_scsi_add_feature_timeout(CdScsiLU *dev, uint8_t *outbuf,
+                                            uint32_t start_feature, uint32_t req_type)
+{
+    uint32_t feature_len = CD_FEATURE_DESC_LEN + CD_FEATURE_TIMEOUT_PROFILE_LEN;
+
+    if (!cd_scsi_feature_reportable(CD_FEATURE_NUM_TIMEOUT, start_feature, req_type)) {
+        return 0;
+    }
+    outbuf[0] = (CD_FEATURE_NUM_TIMEOUT >> 8) & 0xff;
+    outbuf[1] = CD_FEATURE_NUM_TIMEOUT & 0xff;
+    outbuf[2] = CD_FEATURE_PERSISTENT | CD_FEATURE_CURRENT;
+    outbuf[3] = CD_FEATURE_TIMEOUT_PROFILE_LEN;
+
+    return feature_len;
+}
+
+static void cd_scsi_cmd_get_configuration(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t profile_num = (!dev->cd_rom) ? MMC_PROFILE_DVD_ROM : MMC_PROFILE_CD_ROM;
+    uint32_t req_type, start_feature, resp_len;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    req_type = req->cdb[1] & 0x3;
+    start_feature = (req->cdb[2] << 8) | req->cdb[3];
+    req->req_len = (req->cdb[7] << 8) | req->cdb[8];
+
+    memset(outbuf, 0, req->req_len);
+
+    /* at least Feature Header should be present, to be filled later */
+    resp_len = CD_FEATURE_HEADER_LEN;
+
+    switch (req_type) {
+    case CD_FEATURE_REQ_ALL:
+    case CD_FEATURE_REQ_CURRENT:
+        resp_len += cd_scsi_add_feature_profiles_list(dev, outbuf + resp_len, start_feature,
+                                                      req_type);
+        resp_len += cd_scsi_add_feature_core(dev, outbuf + resp_len, start_feature, req_type);
+        resp_len += cd_scsi_add_feature_morph(dev, outbuf + resp_len, start_feature, req_type);
+        resp_len += cd_scsi_add_feature_removable(dev, outbuf + resp_len, start_feature, req_type);
+        resp_len += cd_scsi_add_feature_random_read(dev, outbuf + resp_len, start_feature,
+                                                    req_type);
+        resp_len += cd_scsi_add_feature_cd_read(dev, outbuf + resp_len, start_feature, req_type);
+        resp_len += cd_scsi_add_feature_dvd_read(dev, outbuf + resp_len, start_feature, req_type);
+        resp_len += cd_scsi_add_feature_power_mgmt(dev, outbuf + resp_len, start_feature, req_type);
+        resp_len += cd_scsi_add_feature_timeout(dev, outbuf + resp_len, start_feature, req_type);
+        break;
+    case CD_FEATURE_REQ_SINGLE:
+        switch (start_feature) {
+        case CD_FEATURE_NUM_CORE:
+            resp_len += cd_scsi_add_feature_core(dev, outbuf + resp_len, start_feature, req_type);
+            break;
+        case CD_FEATURE_NUM_MORPH:
+            resp_len += cd_scsi_add_feature_morph(dev, outbuf + resp_len, start_feature, req_type);
+            break;
+        case CD_FEATURE_NUM_REMOVABLE:
+            resp_len += cd_scsi_add_feature_removable(dev, outbuf + resp_len, start_feature,
+                                                      req_type);
+            break;
+        case CD_FEATURE_NUM_RANDOM_READ:
+            resp_len += cd_scsi_add_feature_random_read(dev, outbuf + resp_len, start_feature,
+                                                        req_type);
+            break;
+        case CD_FEATURE_NUM_CD_READ:
+            resp_len += cd_scsi_add_feature_cd_read(dev, outbuf + resp_len, start_feature,
+                                                    req_type);
+            break;
+        case CD_FEATURE_NUM_DVD_READ:
+            resp_len += cd_scsi_add_feature_dvd_read(dev, outbuf + resp_len, start_feature,
+                                                     req_type);
+            break;
+        case CD_FEATURE_NUM_POWER_MNGT:
+            resp_len += cd_scsi_add_feature_power_mgmt(dev, outbuf + resp_len, start_feature,
+                                                       req_type);
+            break;
+        case CD_FEATURE_NUM_TIMEOUT:
+            resp_len += cd_scsi_add_feature_timeout(dev, outbuf + resp_len, start_feature,
+                                                    req_type);
+            break;
+        default:
+            break;
+        }
+        break;
+
+    default:
+        SPICE_DEBUG("get_configuration, lun:%u invalid rt:%u start_f:%u",
+                    req->lun, req_type, start_feature);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+
+    /* set total data len */
+    outbuf[0] = (resp_len >> 24) & 0xff;
+    outbuf[1] = (resp_len >> 16) & 0xff;
+    outbuf[2] = (resp_len >> 8) & 0xff;
+    outbuf[3] = resp_len & 0xff;
+
+    /* report current profile num */
+    outbuf[6] = (profile_num >> 8) & 0xff;
+    outbuf[7] = profile_num & 0xff;
+
+    req->in_len = MIN(req->req_len, resp_len);
+
+    SPICE_DEBUG("get_configuration, lun:%u rt:%u start_f:%u resp_len:%u",
+                req->lun, req_type, start_feature, resp_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define CD_GET_EVENT_STATUS_IMMED            0x01
+
+#define CD_GET_EVENT_HEADER_NO_EVENT_AVAIL  (0x01 << 7)
+#define CD_GET_EVENT_HEADER_LEN             4
+
+#define CD_GET_EVENT_CLASS_NONE             (0x00)
+#define CD_GET_EVENT_CLASS_OPER_CHANGE      (0x01)
+#define CD_GET_EVENT_CLASS_POWER_MGMT       (0x02)
+#define CD_GET_EVENT_CLASS_EXTERNAL_REQ     (0x03)
+#define CD_GET_EVENT_CLASS_MEDIA            (0x04)
+#define CD_GET_EVENT_CLASS_MULTI_INITIATOR  (0x05)
+#define CD_GET_EVENT_CLASS_DEV_BUSY         (0x06)
+
+#define CD_GET_EVENT_LEN_MEDIA              4
+
+#define CD_MEDIA_STATUS_MEDIA_PRESENT       0x1
+#define CD_MEDIA_STATUS_TRAY_OPEN           0x2
+
+static uint32_t cd_scsi_cmd_get_event_resp_add_media(CdScsiLU *dev, uint8_t *outbuf)
+{
+    outbuf[0] = (uint8_t)dev->media_event & 0x0f;
+    outbuf[1] = (uint8_t)((dev->loaded ? 0 : CD_MEDIA_STATUS_TRAY_OPEN) |
+                          (dev->stream != NULL ? CD_MEDIA_STATUS_MEDIA_PRESENT : 0));
+
+    dev->media_event = CD_MEDIA_EVENT_NO_CHANGE; /* reset the event */
+    return CD_GET_EVENT_LEN_MEDIA;
+}
+
+#define CD_GET_EVENT_LEN_POWER              4
+
+#define CD_POWER_STATUS_ACTIVE              0x1
+#define CD_POWER_STATUS_IDLE                0x2
+
+static uint32_t cd_scsi_cmd_get_event_resp_add_power(CdScsiLU *dev, uint8_t *outbuf)
+{
+    outbuf[0] = (uint8_t)dev->power_event & 0x0f;
+    outbuf[1] = (uint8_t)((dev->power_cond == CD_SCSI_POWER_ACTIVE) ?
+                           CD_POWER_STATUS_ACTIVE : CD_POWER_STATUS_IDLE);
+
+    dev->power_event = CD_POWER_EVENT_NO_CHANGE; /* reset the event */
+    return CD_GET_EVENT_LEN_POWER;
+}
+
+static void cd_scsi_cmd_get_event_status_notification(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t resp_len = CD_GET_EVENT_HEADER_LEN;
+    const uint32_t power_class_mask = (0x01 << CD_GET_EVENT_CLASS_POWER_MGMT);
+    const uint32_t media_class_mask = (0x01 << CD_GET_EVENT_CLASS_MEDIA);
+    uint32_t classes_supported =  power_class_mask | media_class_mask;
+    uint32_t immed, classes_requested;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    immed = req->cdb[1] & CD_GET_EVENT_STATUS_IMMED;
+    classes_requested = req->cdb[4];
+    req->req_len = (req->cdb[7] << 8) | req->cdb[8];
+
+    if (!immed) {
+        SPICE_DEBUG("get_event_status_notification, lun:%u"
+                " imm:0 class_req:%02x, Non-immediate (async) mode unsupported",
+                req->lun, classes_requested);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+
+    memset(outbuf, 0, req->req_len);
+    if ((classes_supported & classes_requested) != 0) {
+        if (classes_requested & power_class_mask) {
+            outbuf[2] = CD_GET_EVENT_CLASS_POWER_MGMT;
+            outbuf[3] = (uint8_t)power_class_mask;
+
+            SPICE_DEBUG("get_event_status_notification, lun:%u"
+                        " imm:%u class_req:0x%02x class_sup:0x%02x"
+                        " power_event:0x%02x power_cond:0x%02x",
+                        req->lun, immed, classes_requested, classes_supported,
+                        dev->power_event, dev->power_cond);
+
+            resp_len += cd_scsi_cmd_get_event_resp_add_power(dev, outbuf + resp_len);
+        } else if (classes_requested & media_class_mask) {
+            outbuf[2] = CD_GET_EVENT_CLASS_MEDIA;
+            outbuf[3] = (uint8_t)media_class_mask;
+
+            SPICE_DEBUG("get_event_status_notification, lun:%u"
+                        " imm:%u class_req:0x%02x class_sup:0x%02x"
+                        " media_event:0x%02x loaded: %d",
+                        req->lun, immed, classes_requested, classes_supported,
+                        dev->media_event, dev->loaded);
+
+            resp_len += cd_scsi_cmd_get_event_resp_add_media(dev, outbuf + resp_len);
+        }
+    } else {
+        outbuf[2] = CD_GET_EVENT_HEADER_NO_EVENT_AVAIL | CD_GET_EVENT_CLASS_NONE;
+
+        SPICE_DEBUG("get_event_status_notification, lun:%u"
+                    " imm:%u class_req:0x%02x class_sup:0x%02x"
+                    " none of requested events supported",
+                    req->lun, immed, classes_requested, classes_supported);
+    }
+    outbuf[1] = (uint8_t)(resp_len - 2); /* Event Data Length LSB, length excluding the
+                                          * field itself */
+    outbuf[3] = (uint8_t)classes_supported;
+
+    req->in_len = MIN(req->req_len, resp_len);
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define CD_EXT_REQ_EVENT_FORMAT_NO_CHG          0x00
+#define CD_EXT_REQ_EVENT_FORMAT_LU_KEY_DOWN     0x01
+#define CD_EXT_REQ_EVENT_FORMAT_LU_KEY_UP       0x02
+#define CD_EXT_REQ_EVENT_FORMAT_REQ_NOTIFY      0x03
+
+#define CD_EXT_REQ_STATUS_READY                 0x00
+#define CD_EXT_REQ_STATUS_OTHER_PREVENT         0x01
+
+#define CD_EXT_REQ_CODE_NO_REQUEST              0x00
+#define CD_EXT_REQ_CODE_OVERRUN                 0x01
+#define CD_EXT_REQ_CODE_PLAY                    0x101
+#define CD_EXT_REQ_CODE_REWIND                  0x102
+#define CD_EXT_REQ_CODE_FAST_FW                 0x103
+#define CD_EXT_REQ_CODE_PAUSE                   0x104
+#define CD_EXT_REQ_CODE_STOP                    0x106
+#define CD_EXT_REQ_CODE_ASCII_BASE              0x200 /* SCSII value is LSB */
+
+static void cd_scsi_cmd_send_event(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *param, *event;
+    uint32_t immed, param_list_len;
+    uint32_t event_param_len, notification_class;
+    uint32_t ext_req_event, ext_req_status, pers_prevent, ext_req_code;
+
+    req->xfer_dir = SCSI_XFER_TO_DEV;
+
+    immed = req->cdb[1] & 0x01;
+    param_list_len = (req->cdb[8] << 8) | req->cdb[9];
+
+    if (req->buf_len < param_list_len) {
+        SPICE_DEBUG("send_event, lun:%u invalid param list len:0x%x, buf_len:0x%x",
+                    req->lun, param_list_len, req->buf_len);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_PARAM_LEN);
+        return;
+    }
+    param = req->buf;
+    event_param_len = (param[0] << 8) | param[1];
+
+    notification_class = param[2] & 0x07;
+    if (notification_class != CD_GET_EVENT_CLASS_EXTERNAL_REQ) {
+        SPICE_DEBUG("send_event, lun:%u invalid notification class:0x%x",
+                    req->lun, notification_class);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+
+    event = param + CD_GET_EVENT_HEADER_LEN;
+    ext_req_event = event[0] & 0xff;
+    ext_req_status = event[1] & 0x0f;
+    pers_prevent = event[1] & 0x80;
+    ext_req_code = (event[2] << 8) | event[3];
+
+    SPICE_DEBUG("send_event, lun:%u immed:%u param_len:%u"
+                " ext_req_event:0x%x ext_req_status:0x%x"
+                " pers_prevent:0x%x ext_req_code:0x%x",
+                req->lun, immed, event_param_len, ext_req_event,
+                ext_req_status, pers_prevent, ext_req_code);
+
+    /* ToDo: process the event */
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define CD_MEDIUM_REMOVAL_REQ_ALLOW                 0x00
+#define CD_MEDIUM_REMOVAL_REQ_PREVENT               0x01
+#define CD_MEDIUM_REMOVAL_REQ_ALLOW_CHANGER         0x02
+#define CD_MEDIUM_REMOVAL_REQ_PREVENT_CHANGER       0x03
+
+static void cd_scsi_cmd_allow_medium_removal(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint32_t prevent;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    prevent = req->cdb[4] & 0x03;
+    dev->prevent_media_removal = (prevent == CD_MEDIUM_REMOVAL_REQ_PREVENT ||
+                                  prevent == CD_MEDIUM_REMOVAL_REQ_PREVENT_CHANGER);
+    req->in_len = 0;
+
+    SPICE_DEBUG("allow_medium_removal, lun:%u prevent field::0x%02x flag:%d",
+                req->lun, prevent, dev->prevent_media_removal);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_cmd_report_key(CdScsiLU *dev, CdScsiRequest *req)
+{
+    SPICE_DEBUG("report_key - content protection unsupported, lun:%u", req->lun);
+    req->xfer_dir = SCSI_XFER_NONE;
+    cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_OPCODE);
+}
+
+static void cd_scsi_cmd_send_key(CdScsiLU *dev, CdScsiRequest *req)
+{
+    SPICE_DEBUG("send_key - content protection unsupported, lun:%u", req->lun);
+    req->xfer_dir = SCSI_XFER_NONE;
+    cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_OPCODE);
+}
+
+/* byte 1 */
+#define CD_START_STOP_FLAG_IMMED                    0x01
+
+/* byte 4 */
+#define CD_START_STOP_FLAG_START                    0x01
+#define CD_START_STOP_FLAG_LOEJ                     0x02
+
+/* POWER CONDITION field values */
+#define CD_START_STOP_POWER_COND_START_VALID        0x00
+#define CD_START_STOP_POWER_COND_ACTIVE             0x01
+#define CD_START_STOP_POWER_COND_IDLE               0x02
+#define CD_START_STOP_POWER_COND_STANDBY            0x03
+#define CD_START_STOP_POWER_COND_LU_CONTROL         0x07
+#define CD_START_STOP_POWER_COND_FORCE_IDLE_0       0x0a
+#define CD_START_STOP_POWER_COND_FORCE_STANDBY_0    0x0b
+
+static inline const char *cd_scsi_start_stop_power_cond_name(uint32_t power_cond)
+{
+    switch (power_cond) {
+    case CD_START_STOP_POWER_COND_START_VALID:
+        return "START_VALID";
+    case CD_START_STOP_POWER_COND_ACTIVE:
+        return "ACTIVE";
+    case CD_START_STOP_POWER_COND_IDLE:
+        return "IDLE";
+    case CD_START_STOP_POWER_COND_STANDBY:
+        return "STANDBY";
+    case CD_START_STOP_POWER_COND_LU_CONTROL:
+        return "LU_CONTROL";
+    case CD_START_STOP_POWER_COND_FORCE_IDLE_0:
+        return "FORCE_IDLE_0";
+    case CD_START_STOP_POWER_COND_FORCE_STANDBY_0:
+        return "FORCE_STANDBY_0";
+    default:
+        return "RESERVED";
+    }
+}
+
+static void cd_scsi_cmd_start_stop_unit(CdScsiLU *dev, CdScsiRequest *req)
+{
+    gboolean immed, start, load_eject;
+    uint32_t power_cond;
+
+    req->xfer_dir = SCSI_XFER_NONE;
+    req->in_len = 0;
+
+    immed = (req->cdb[1] & CD_START_STOP_FLAG_IMMED) ? TRUE : FALSE;
+    start = (req->cdb[4] & CD_START_STOP_FLAG_START) ? TRUE : FALSE;
+    load_eject = (req->cdb[4] & CD_START_STOP_FLAG_LOEJ) ? TRUE : FALSE;
+    power_cond = req->cdb[4] >> 4;
+
+    SPICE_DEBUG("start_stop_unit, lun:%u"
+                " immed:%d start:%d load_eject:%d power_cond:0x%x(%s)",
+                req->lun, immed, start, load_eject, power_cond,
+                cd_scsi_start_stop_power_cond_name(power_cond));
+
+    switch (power_cond) {
+    case CD_START_STOP_POWER_COND_START_VALID:
+        if (!start) { /* stop the unit */
+            if (load_eject) { /* eject medium */
+                if (dev->prevent_media_removal) {
+                    SPICE_DEBUG("start_stop_unit, lun:%u"
+                                " prevent_media_removal set, eject failed", req->lun);
+                    cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_MEDIUM_REMOVAL_PREVENTED);
+                    return;
+                }
+                SPICE_DEBUG("start_stop_unit, lun:%u eject", req->lun);
+                cd_scsi_lu_unload(dev);
+                cd_scsi_dev_changed(dev->tgt->user_data, req->lun);
+            }
+            dev->power_cond = CD_SCSI_POWER_STOPPED;
+            SPICE_DEBUG("start_stop_unit, lun:%u stopped", req->lun);
+        } else { /* start the unit */
+            dev->power_cond = CD_SCSI_POWER_ACTIVE;
+            SPICE_DEBUG("start_stop_unit, lun:%u started", req->lun);
+
+            if (load_eject) { /* load medium */
+                SPICE_DEBUG("start_stop_unit, lun:%u load with no media",
+                            req->lun);
+                cd_scsi_lu_load(dev, NULL);
+                cd_scsi_dev_changed(dev->tgt->user_data, req->lun);
+            }
+        }
+        break;
+    case CD_START_STOP_POWER_COND_ACTIVE:
+        /* not error to specify transition to the current power condition */
+        dev->power_cond = CD_SCSI_POWER_ACTIVE;
+        SPICE_DEBUG("start_stop_unit, lun:%u active", req->lun);
+        break;
+    case CD_START_STOP_POWER_COND_IDLE:
+    case CD_START_STOP_POWER_COND_FORCE_IDLE_0:
+        dev->power_cond = CD_SCSI_POWER_IDLE;
+        SPICE_DEBUG("start_stop_unit, lun:%u idle", req->lun);
+        break;
+    case CD_START_STOP_POWER_COND_STANDBY:
+    case CD_START_STOP_POWER_COND_FORCE_STANDBY_0:
+        dev->power_cond = CD_SCSI_POWER_STANDBY;
+        SPICE_DEBUG("start_stop_unit, lun:%u standby", req->lun);
+        break;
+    case CD_START_STOP_POWER_COND_LU_CONTROL:
+        break;
+    default:
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+#define CD_PERF_TYPE_PERFORMANCE                0x00
+#define CD_PERF_TYPE_UNUSABLE_AREA              0x01
+#define CD_PERF_TYPE_DEFECT_STATUS              0x02
+#define CD_PERF_TYPE_WRITE_SPEED                0x03
+
+#define CD_PERF_HEADER_LEN                      8
+
+#define CD_PERF_TYPE_PERFORMANCE_DESCR_LEN      16
+
+#define CD_PERF_TYPE_PERFORMANCE_REPORT_NOMINAL 0x00
+#define CD_PERF_TYPE_PERFORMANCE_REPORT_ALL     0x01
+#define CD_PERF_TYPE_PERFORMANCE_REPORT_EXCEPT  0x10
+
+
+static void cd_scsi_get_performance_resp_empty(CdScsiLU *dev, CdScsiRequest *req,
+                                               uint32_t type, uint32_t data_type,
+                                               uint32_t max_num_descr)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t write = (data_type >> 2) & 0x01;
+
+    memset(outbuf, 0, CD_PERF_HEADER_LEN);
+    if (write) {
+        outbuf[4] = 0x02;
+    }
+    req->in_len = CD_PERF_HEADER_LEN;
+
+    SPICE_DEBUG("get_performance, lun:%u"
+                " type:0x%x data_type:0x%x - sending empty response",
+                req->lun, type, data_type);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_get_performance_resp_performance(CdScsiLU *dev, CdScsiRequest *req,
+                                                     uint32_t start_lba,
+                                                     uint32_t data_type,
+                                                     uint32_t max_num_descr)
+{
+    uint8_t *outbuf = req->buf, *perf_desc;
+    uint32_t resp_len = CD_PERF_HEADER_LEN +
+                        CD_PERF_TYPE_PERFORMANCE_DESCR_LEN;
+    uint32_t perf_data_len = resp_len - 4; /* not incl. Perf Data Length */
+    uint32_t perf_kb = 10000;
+    uint32_t end_lba = dev->num_blocks - 1;
+    uint32_t except, write, tolerance;
+
+    except = data_type & 0x03;
+    if (except != CD_PERF_TYPE_PERFORMANCE_REPORT_ALL) {
+        start_lba = 0;
+    }
+    write = (data_type >> 2) & 0x01;
+    tolerance = (data_type >> 3) & 0x03;
+
+    SPICE_DEBUG("get_performance, lun:%u"
+                " performance type:0x00 data_type:0x%x"
+                " except:0x%x write:0x%x tolerance:0x%x"
+                " max_num:%u",
+                req->lun, data_type, except, write,
+                tolerance, max_num_descr);
+
+    if (write) {
+        SPICE_DEBUG("get_performance, lun:%u"
+                    " performance type:0x00 data_type:0x%x - write unsupported",
+                    req->lun, data_type);
+        cd_scsi_get_performance_resp_empty(dev, req, CD_PERF_TYPE_PERFORMANCE,
+                                           data_type, max_num_descr);
+        return;
+    }
+
+    memset(outbuf, 0, resp_len);
+
+    outbuf[0] = (perf_data_len >> 24) & 0xff;
+    outbuf[1] = (perf_data_len >> 16) & 0xff;
+    outbuf[2] = (perf_data_len >> 8) & 0xff;
+    outbuf[3] = perf_data_len & 0xff;
+
+    perf_desc = outbuf + CD_PERF_HEADER_LEN;
+
+    perf_desc[0] = (start_lba >> 24) & 0xff;
+    perf_desc[1] = (start_lba >> 16) & 0xff;
+    perf_desc[2] = (start_lba >> 8) & 0xff;
+    perf_desc[3] = start_lba & 0xff;
+
+    perf_desc[4] = (perf_kb >> 24) & 0xff;
+    perf_desc[5] = (perf_kb >> 16) & 0xff;
+    perf_desc[6] = (perf_kb >> 8) & 0xff;
+    perf_desc[7] = perf_kb & 0xff;
+
+    perf_desc[8] = (end_lba >> 24) & 0xff;
+    perf_desc[9] = (end_lba >> 16) & 0xff;
+    perf_desc[10] = (end_lba >> 8) & 0xff;
+    perf_desc[11] = end_lba & 0xff;
+
+    perf_desc[12] = (perf_kb >> 24) & 0xff;
+    perf_desc[13] = (perf_kb >> 16) & 0xff;
+    perf_desc[14] = (perf_kb >> 8) & 0xff;
+    perf_desc[15] = perf_kb & 0xff;
+
+    req->req_len = CD_PERF_HEADER_LEN +
+                   (max_num_descr * CD_PERF_TYPE_PERFORMANCE_DESCR_LEN);
+
+    req->in_len = MIN(req->req_len, resp_len);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_cmd_get_performance(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint32_t data_type, max_num_descr, start_lba, type;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    data_type = req->cdb[1] & 0x0f;
+    start_lba = (req->cdb[2] << 24) |
+                (req->cdb[3] << 16) |
+                (req->cdb[4] << 8) |
+                 req->cdb[5];
+    max_num_descr = (req->cdb[8] << 8) | req->cdb[9];
+    type = req->cdb[10];
+
+    switch (type) {
+    case CD_PERF_TYPE_PERFORMANCE:
+        cd_scsi_get_performance_resp_performance(dev, req, start_lba,
+                                                 data_type, max_num_descr);
+        break;
+    case CD_PERF_TYPE_UNUSABLE_AREA: /* not writable */
+    case CD_PERF_TYPE_DEFECT_STATUS: /* not restricted overwrite media */
+    case CD_PERF_TYPE_WRITE_SPEED: /* unsupported, irrelevant */
+    default:
+        SPICE_DEBUG("get_performance, lun:%u"
+                    " unsupported type:0x%x"
+                    " data_type:0x%x max_num:%u",
+                    req->lun, type, data_type, max_num_descr);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        return;
+    }
+}
+
+#define CD_MECHANISM_STATUS_HDR_LEN     8
+
+/* byte 0 */
+#define CD_CHANGER_FAULT_FLAG           0x80
+
+#define CD_CHANGER_READY                0x00
+#define CD_CHANGER_LOADING              0x01
+#define CD_CHANGER_UNLOADING            0x02
+#define CD_CHANGER_INITIALIZING         0x03
+
+/* byte 1 */
+#define CD_CHANGER_DOOR_OPEN_FLAG       0x10
+
+#define CD_MECHANISM_STATE_IDLE         0x00
+#define CD_MECHANISM_STATE_PLAYING      0x01
+#define CD_MECHANISM_STATE_SCANNING     0x02
+/* ACTIVE: with Initiator, Composite or Other ports
+  (i.e. READ, PLAY CD, SCAN during PLAY CD) */
+#define CD_MECHANISM_STATE_ACTIVE       0x03
+#define CD_MECHANISM_STATE_NO_INFO      0x07
+
+/* slots */
+#define CD_MECHANISM_STATUS_SLOT_LEN    4
+
+#define CD_MECHANISM_SLOT_DISK_CHANGED  0x01
+#define CD_MECHANISM_SLOT_DISK_PRESENT  0x80
+#define CD_MECHANISM_SLOT_DISK_CWP_V    0x02
+#define CD_MECHANISM_SLOT_DISK_CWP      0x01
+
+static void cd_scsi_cmd_mechanism_status(CdScsiLU *dev, CdScsiRequest *req)
+{
+    uint8_t *outbuf = req->buf;
+    uint32_t resp_len = CD_MECHANISM_STATUS_HDR_LEN;
+
+    req->xfer_dir = SCSI_XFER_FROM_DEV;
+
+    req->req_len = (req->cdb[8] << 8) | req->cdb[9];
+    memset(outbuf, 0, req->req_len);
+
+    /* For non-changer devices the curent slot number field is reserved, set only status */
+    outbuf[0] |= (CD_CHANGER_READY << 5);
+
+    outbuf[1] |= (!dev->loaded) ? CD_CHANGER_DOOR_OPEN_FLAG : 0;
+    outbuf[1] |= (dev->power_cond == CD_POWER_STATUS_ACTIVE) ?
+                 (CD_MECHANISM_STATE_ACTIVE << 5) : (CD_MECHANISM_STATE_IDLE << 5);
+
+    /* For non-changer devices the number of slot tables returned shall be zero, so we leave
+       both 'Number of Slots Available' and 'Length of Slot Table' fields as zeros */
+
+    req->in_len = MIN(req->req_len, resp_len);
+
+    SPICE_DEBUG("mechanism_status, lun:%u", req->lun);
+
+    cd_scsi_cmd_complete_good(dev, req);
+}
+
+static void cd_scsi_read_async_complete(GObject *src_object,
+                                        GAsyncResult *result,
+                                        gpointer user_data)
+{
+    GFileInputStream *stream = G_FILE_INPUT_STREAM(src_object);
+    CdScsiRequest *req = (CdScsiRequest *)user_data;
+    CdScsiTarget *st = (CdScsiTarget *)req->priv_data;
+    CdScsiLU *dev = &st->units[req->lun];
+    GError *error = NULL;
+    gsize bytes_read;
+    gboolean finished;
+
+    req->req_state = SCSI_REQ_COMPLETE;
+    req->cancel_id = 0;
+
+//    g_assert(stream == dev->stream);
+    if (stream != dev->stream) {
+        uint32_t opcode = (uint32_t)req->cdb[0];
+        SPICE_DEBUG("read_async_complete BAD STREAM, lun: %u"
+                    " req: %" G_GUINT64_FORMAT " op: 0x%02x",
+                    req->lun, req->req_len, opcode);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_TARGET_FAILURE);
+        cd_scsi_dev_request_complete(st->user_data, req);
+        return;
+    }
+
+    bytes_read = g_input_stream_read_finish(G_INPUT_STREAM(stream), result, &error);
+    finished = bytes_read > 0;
+    if (finished) {
+        SPICE_DEBUG("read_async_complete, lun: %u"
+                    " finished: %d bytes_read: %" G_GUINT64_FORMAT
+                    " req: %"  G_GUINT64_FORMAT,
+                    req->lun, finished, (uint64_t)bytes_read, req->req_len);
+
+        req->in_len = MIN(bytes_read, req->req_len);
+        req->status = GOOD;
+    } else {
+        if (error != NULL) {
+            SPICE_ERROR("g_input_stream_read_finish failed: %s", error->message);
+            g_clear_error (&error);
+        } else {
+            SPICE_ERROR("g_input_stream_read_finish failed (no err provided)");
+        }
+        req->in_len = 0;
+        req->status = GOOD;
+    }
+    cd_scsi_dev_request_complete(st->user_data, req);
+}
+
+static void cd_scsi_read_async_canceled(GCancellable *cancellable, gpointer user_data)
+{
+    CdScsiRequest *req = (CdScsiRequest *)user_data;
+    CdScsiTarget *st = (CdScsiTarget *)req->priv_data;
+
+    g_assert(cancellable == st->cancellable);
+    g_cancellable_disconnect(cancellable, req->cancel_id);
+    req->cancel_id = 0;
+
+    req->req_state =
+        (st->state == CD_SCSI_TGT_STATE_RUNNING) ? SCSI_REQ_CANCELED : SCSI_REQ_DISPOSED;
+    req->in_len = 0;
+    req->status = GOOD;
+
+    cd_scsi_dev_request_complete(st->user_data, req);
+}
+
+static int cd_scsi_read_async_start(CdScsiLU *dev, CdScsiRequest *req)
+{
+    CdScsiTarget *st = dev->tgt;
+    GFileInputStream *stream = dev->stream;
+
+    SPICE_DEBUG("read_async_start, lun:%u"
+                " lba: %" G_GUINT64_FORMAT " offset: %" G_GUINT64_FORMAT
+                " cnt: %" G_GUINT64_FORMAT " len: %" G_GUINT64_FORMAT,
+                req->lun, req->lba, req->offset, req->count, req->req_len);
+
+    req->cancel_id = g_cancellable_connect(st->cancellable,
+                                           G_CALLBACK(cd_scsi_read_async_canceled),
+                                           req, /* data */
+                                           NULL); /* data destroy cb */
+    if (req->cancel_id == 0) {
+        /* already canceled */
+        return -1;
+    }
+
+    g_seekable_seek(G_SEEKABLE(stream),
+                    req->offset,
+                    G_SEEK_SET,
+                    NULL, /* cancellable */
+                    NULL); /* error */
+
+    g_input_stream_read_async(G_INPUT_STREAM(stream),
+                              req->buf, /* buffer to fill */
+                              req->req_len,
+                              G_PRIORITY_DEFAULT,
+                              st->cancellable,
+                              cd_scsi_read_async_complete,
+                              (gpointer)req); /* callback argument */
+    return 0;
+}
+
+static void cd_scsi_cmd_read(CdScsiLU *dev, CdScsiRequest *req)
+{
+    if (dev->power_cond == CD_SCSI_POWER_STOPPED) {
+        SPICE_DEBUG("read, lun: %u is stopped", req->lun);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INIT_CMD_REQUIRED);
+        return;
+    } else if (!dev->loaded || dev->stream == NULL) {
+        SPICE_DEBUG("read, lun: %u is not loaded", req->lun);
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_NOT_READY_NO_MEDIUM);
+        return;
+    }
+
+    req->cdb_len = scsi_cdb_length(req->cdb);
+
+    req->lba = scsi_cdb_lba(req->cdb, req->cdb_len);
+    req->offset = req->lba * dev->block_size;
+
+    req->count = scsi_cdb_xfer_length(req->cdb, req->cdb_len); /* xfer in blocks */
+    req->req_len = (uint64_t) req->count * dev->block_size;
+
+    cd_scsi_read_async_start(dev, req);
+}
+
+void cd_scsi_dev_request_submit(CdScsiTarget *st, CdScsiRequest *req)
+{
+    uint32_t lun = req->lun;
+    uint32_t opcode = (uint32_t)req->cdb[0];
+    const char *cmd_name = scsi_cmd_name[opcode];
+    CdScsiLU *dev = &st->units[lun];
+
+    SPICE_DEBUG("request_submit, lun: %u op: 0x%02x %s", lun, opcode, cmd_name);
+
+    if (st->cur_req != NULL) {
+        SPICE_ERROR("request_submit, request not idle");
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_TARGET_FAILURE);
+        goto done;
+    }
+    if (req->req_state != SCSI_REQ_IDLE) {
+        SPICE_ERROR("request_submit, prev request outstanding");
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_TARGET_FAILURE);
+        goto done;
+    }
+    req->req_state = SCSI_REQ_RUNNING;
+    st->cur_req = req;
+
+    /* INQUIRY should send response even for non-existing LUNs */
+    if (!cd_scsi_target_lun_legal(st, lun)) {
+        SPICE_ERROR("request_submit, illegal lun:%u", lun);
+        if (opcode == INQUIRY) {
+            if (req->cdb[1] & 0x1) {
+                cd_scsi_cmd_inquiry_vpd_no_lun(dev, req, PERIF_QUALIFIER_UNSUPPORTED);
+            } else {
+                cd_scsi_cmd_inquiry_standard_no_lun(dev, req, PERIF_QUALIFIER_UNSUPPORTED);
+            }
+        } else {
+            cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_LUN_NOT_SUPPORTED);
+        }
+        goto done;
+    }
+    if (!cd_scsi_target_lun_realized(st, lun)) {
+        SPICE_ERROR("request_submit, absent lun:%u", lun);
+        if (opcode == INQUIRY) {
+            if (req->cdb[1] & 0x1) {
+                cd_scsi_cmd_inquiry_vpd_no_lun(dev, req, PERIF_QUALIFIER_NOT_CONNECTED);
+            } else {
+                cd_scsi_cmd_inquiry_standard_no_lun(dev, req, PERIF_QUALIFIER_NOT_CONNECTED);
+            }
+        } else {
+            cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_LUN_NOT_SUPPORTED);
+        }
+        goto done;
+    }
+
+    if (dev->short_sense.key != NO_SENSE) {
+        gboolean pending_sense = TRUE;
+        if (dev->short_sense.key == UNIT_ATTENTION) {
+            if (cd_scsi_opcode_ua_supress(opcode)) {
+                pending_sense = FALSE; /* UA supressed */
+            }
+        } else if (opcode == REQUEST_SENSE) {
+            pending_sense = FALSE; /* sense returned as data */
+        }
+        if (pending_sense) {
+            cd_scsi_cmd_complete_check_cond(dev, req, NULL); /* sense already set */
+            goto done;
+        }
+    }
+
+    /* save the target to be used in callbacks where only req is passed */
+    req->priv_data = (void *)st;
+
+    req->req_len = 0;
+
+    switch (opcode) {
+    case REPORT_LUNS:
+        cd_scsi_cmd_report_luns(st, dev, req);
+        break;
+    case TEST_UNIT_READY:
+        cd_scsi_cmd_test_unit_ready(dev, req);
+        break;
+    case INQUIRY:
+        cd_scsi_cmd_inquiry(dev, req);
+        break;
+    case REQUEST_SENSE:
+        cd_scsi_cmd_request_sense(dev, req);
+        break;
+    case READ_6:
+    case READ_10:
+    case READ_12:
+    case READ_16:
+        cd_scsi_cmd_read(dev, req);
+        break;
+    case READ_CAPACITY_10:
+        cd_scsi_cmd_read_capacity(dev, req);
+        break;
+    case READ_TOC:
+        cd_scsi_cmd_read_toc(dev, req);
+        break;
+    case GET_EVENT_STATUS_NOTIFICATION:
+        cd_scsi_cmd_get_event_status_notification(dev, req);
+        break;
+    case READ_DISC_INFORMATION:
+        cd_scsi_cmd_get_read_disc_information(dev, req);
+        break;
+    case READ_TRACK_INFORMATION:
+        cd_scsi_cmd_get_read_track_information(dev, req);
+        break;
+    case MODE_SENSE_10:
+        cd_scsi_cmd_mode_sense_10(dev, req);
+        break;
+    case MODE_SELECT:
+        cd_scsi_cmd_mode_select_6(dev, req);
+        break;
+    case MODE_SELECT_10:
+        cd_scsi_cmd_mode_select_10(dev, req);
+        break;
+    case GET_CONFIGURATION:
+        cd_scsi_cmd_get_configuration(dev, req);
+        break;
+    case ALLOW_MEDIUM_REMOVAL:
+        cd_scsi_cmd_allow_medium_removal(dev, req);
+        break;
+    case MMC_SEND_EVENT:
+        cd_scsi_cmd_send_event(dev, req);
+        break;
+    case MMC_REPORT_KEY:
+        cd_scsi_cmd_report_key(dev, req);
+        break;
+    case MMC_SEND_KEY:
+        cd_scsi_cmd_send_key(dev, req);
+        break;
+    case START_STOP:
+        cd_scsi_cmd_start_stop_unit(dev, req);
+        break;
+    case MMC_GET_PERFORMANCE:
+        cd_scsi_cmd_get_performance(dev, req);
+        break;
+    case MMC_MECHANISM_STATUS:
+        cd_scsi_cmd_mechanism_status(dev, req);
+        break;
+    default:
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_OPCODE);
+        break;
+    }
+
+    if (req->req_len > INT32_MAX) {
+        cd_scsi_cmd_complete_check_cond(dev, req, &sense_code_INVALID_CDB_FIELD);
+        goto done;
+    }
+
+done:
+    SPICE_DEBUG("request_submit done, lun: %u"
+                " op: 0x%02x %s, state: %s status: %u len: %" G_GUINT64_FORMAT,
+                lun, opcode, cmd_name, CdScsiReqState_str(req->req_state), req->status,
+                req->in_len);
+
+    if (req->req_state == SCSI_REQ_COMPLETE) {
+        cd_scsi_dev_request_complete(st->user_data, req);
+    }
+}
+
+void cd_scsi_dev_request_cancel(CdScsiTarget *st, CdScsiRequest *req)
+{
+    if (st->cur_req == req) {
+        if (req->req_state == SCSI_REQ_RUNNING) {
+            SPICE_DEBUG("request_cancel: lun: %u"
+                         " op: 0x%02x len: %" G_GUINT64_FORMAT,
+                        req->lun, (unsigned int)req->cdb[0], req->req_len);
+            g_cancellable_cancel(st->cancellable);
+        } else {
+            SPICE_DEBUG("request_cancel: request is not running");
+        }
+    } else {
+        SPICE_DEBUG("request_cancel: other request is outstanding");
+    }
+}
+
+void cd_scsi_dev_request_release(CdScsiTarget *st, CdScsiRequest *req)
+{
+    st->cur_req = NULL;
+    cd_scsi_req_init(req);
+
+    if (st->state == CD_SCSI_TGT_STATE_RESET) {
+        cd_scsi_target_do_reset(st);
+    }
+}
+
+#endif /* USE_USBREDIR */
diff --git a/src/cd-scsi.h b/src/cd-scsi.h
new file mode 100644
index 0000000..86e951b
--- /dev/null
+++ b/src/cd-scsi.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   CD device emulation - SCSI engine
+
+   Copyright (C) 2018 Red Hat, Inc.
+
+   Red Hat Authors:
+   Alexander Nezhinsky<anezhins 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/>.
+*/
+
+#pragma once
+
+#include "cd-scsi-dev-params.h"
+#include "cd-usb-bulk-msd.h"
+#include "scsi-constants.h"
+
+#if defined(G_OS_WIN32)
+#include <winsock2.h>
+#include <windows.h>
+/* Windows is always LE at the moment */
+#define le32toh(x)          (x)
+#define htole32(x)          (x)
+#define htobe32(x)          htonl(x)
+#endif
+
+typedef enum ScsiXferDir {
+    SCSI_XFER_NONE = 0,  /* TEST_UNIT_READY, ...           */
+    SCSI_XFER_FROM_DEV,  /* READ, INQUIRY, MODE_SENSE, ... */
+    SCSI_XFER_TO_DEV,    /* WRITE, MODE_SELECT, ...        */
+} ScsiXferDir;
+
+#define SCSI_CDB_BUF_SIZE   16
+
+typedef enum CdScsiReqState {
+    SCSI_REQ_IDLE = 0,
+    SCSI_REQ_RUNNING,
+    SCSI_REQ_COMPLETE,
+    SCSI_REQ_CANCELED,
+    SCSI_REQ_DISPOSED,
+} CdScsiReqState;
+
+typedef struct CdScsiRequest {
+    /* request */
+    uint8_t cdb[SCSI_CDB_BUF_SIZE];
+    uint32_t cdb_len;
+
+    uint32_t lun;
+
+    uint8_t *buf;
+    uint32_t buf_len;
+
+    /* internal */
+    CdScsiReqState req_state;
+    ScsiXferDir xfer_dir;
+    uint64_t cancel_id;
+    void *priv_data;
+
+    uint64_t lba; /* offset in logical blocks if relevant */
+    uint64_t count; /* count in logical blocks */
+
+    uint64_t offset; /* scsi cdb offset, normalized to bytes */
+    uint64_t req_len; /* scsi cdb request length, normalized to bytes */
+
+    /* result */
+    uint64_t in_len; /* length of data actually available after read */
+    uint32_t status; /* SCSI status code */
+
+} CdScsiRequest;
+
+CdScsiReqState cd_scsi_get_req_state(CdScsiRequest *req);
+
+/* SCSI target/device API */
+typedef struct CdScsiTarget CdScsiTarget;
+
+/* to be used in callbacks */
+CdScsiTarget *cd_scsi_target_alloc(void *target_user_data, uint32_t max_luns);
+void cd_scsi_target_free(CdScsiTarget *scsi_target);
+
+int cd_scsi_dev_realize(CdScsiTarget *scsi_target, uint32_t lun,
+                        const CdScsiDeviceParameters *dev_params);
+int cd_scsi_dev_unrealize(CdScsiTarget *scsi_target, uint32_t lun);
+
+int cd_scsi_dev_lock(CdScsiTarget *scsi_target, uint32_t lun, gboolean lock);
+int cd_scsi_dev_load(CdScsiTarget *scsi_target, uint32_t lun,
+                     const CdScsiMediaParameters *media_params);
+int cd_scsi_dev_get_info(CdScsiTarget *scsi_target, uint32_t lun, CdScsiDeviceInfo *lun_info);
+int cd_scsi_dev_unload(CdScsiTarget *scsi_target, uint32_t lun);
+
+void cd_scsi_dev_request_submit(CdScsiTarget *scsi_target, CdScsiRequest *request);
+void cd_scsi_dev_request_cancel(CdScsiTarget *scsi_target, CdScsiRequest *request);
+void cd_scsi_dev_request_release(CdScsiTarget *scsi_target, CdScsiRequest *request);
+
+int cd_scsi_dev_reset(CdScsiTarget *scsi_target, uint32_t lun);
+
+int cd_scsi_target_reset(CdScsiTarget *scsi_target);
+
+/* Callbacks
+ * These callbacks are used by upper layer to implement specific SCSI
+ * target devices.
+ */
+void cd_scsi_dev_request_complete(void *target_user_data, CdScsiRequest *request);
+void cd_scsi_dev_changed(void *target_user_data, uint32_t lun);
+void cd_scsi_dev_reset_complete(void *target_user_data, uint32_t lun);
+void cd_scsi_target_reset_complete(void *target_user_data);
diff --git a/src/cd-usb-bulk-msd.c b/src/cd-usb-bulk-msd.c
new file mode 100644
index 0000000..5d95dac
--- /dev/null
+++ b/src/cd-usb-bulk-msd.c
@@ -0,0 +1,543 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+    USB CD Device emulation - Data Bulk transfers - Mass Storage Device
+
+    Copyright (C) 2018 Red Hat, Inc.
+
+    Red Hat Authors:
+    Alexander Nezhinsky<anezhins at redhat.com>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+#include "spice/types.h"
+#include "spice-common.h"
+#include "spice-util.h"
+#include "cd-usb-bulk-msd.h"
+#include "cd-scsi.h"
+
+#ifdef USE_USBREDIR
+
+#define SPICE_ERROR(fmt, ...) \
+    do { SPICE_DEBUG("usb-msd error: " fmt , ## __VA_ARGS__); } while (0)
+
+typedef enum UsbCdState {
+    USB_CD_STATE_INIT, /* Not ready */
+    USB_CD_STATE_CBW, /* Waiting for Command Block */
+    USB_CD_STATE_DATAOUT, /* Transfer data to device */
+    USB_CD_STATE_DATAIN, /* Transfer data from device */
+    USB_CD_STATE_ZERO_DATAIN, /* Need to send zero bulk-in before status */
+    USB_CD_STATE_CSW, /* Send Command Status */
+    USB_CD_STATE_DEVICE_RESET, /* reset of a single device */
+    USB_CD_STATE_TARGET_RESET /* reset of entire target */
+} UsbCdState;
+
+/* USB MSD Command Block Wrapper */
+struct __attribute__((__packed__)) UsbCdCBW {
+    uint32_t sig;
+    uint32_t tag;
+    uint32_t exp_data_len; /* expected data xfer length for the request */
+    uint8_t flags;
+    uint8_t lun;
+    uint8_t cmd_len; /* actual length of the scsi command that follows */
+    uint8_t cmd[16]; /* scsi command to perform */
+};
+
+/* USB MSD Command Status Wrapper */
+struct __attribute__((__packed__)) UsbCdCSW {
+    uint32_t sig;
+    uint32_t tag;
+    uint32_t residue;
+    uint8_t status;
+};
+
+/* UsbCdCSW::status */
+typedef enum UsbMsdStatus {
+    USB_MSD_STATUS_GOOD = 0,
+    USB_MSD_STATUS_FAILED = 1,
+    USB_MSD_STATUS_PHASE_ERR = 2,
+} UsbMsdStatus;
+
+typedef struct UsbCdBulkMsdRequest {
+    CdScsiRequest scsi_req;
+
+    uint32_t lun;
+    uint32_t usb_req_len; /* length of data requested by usb */
+    uint32_t scsi_in_len; /* length of data returned by scsi limited by usb request */
+
+    uint32_t xfer_len; /* length of data transfered until now */
+    uint32_t bulk_in_len; /* length of the last postponed bulk-in request */
+
+    struct UsbCdCSW csw; /* usb status header */
+} UsbCdBulkMsdRequest;
+
+typedef struct UsbCdBulkMsdDevice {
+    UsbCdState state;
+    CdScsiTarget *scsi_target; /* scsi handle */
+    void *usb_user_data; /* used in callbacks to usb */
+    UsbCdBulkMsdRequest usb_req; /* now supporting a single cmd */
+    uint8_t *data_buf;
+    uint32_t data_buf_len;
+} UsbCdBulkMsdDevice;
+
+static inline const char *usb_cd_state_str(UsbCdState state)
+{
+    switch (state) {
+    case USB_CD_STATE_INIT:
+        return "INIT";
+    case USB_CD_STATE_CBW:
+        return "CBW";
+    case USB_CD_STATE_DATAOUT:
+        return "DATAOUT";
+    case USB_CD_STATE_DATAIN:
+        return "DATAIN";
+    case USB_CD_STATE_ZERO_DATAIN:
+        return "ZERO_DATAIN";
+    case USB_CD_STATE_CSW:
+        return "CSW";
+    case USB_CD_STATE_DEVICE_RESET:
+        return "DEV_RESET";
+    case USB_CD_STATE_TARGET_RESET:
+        return "TGT_RESET";
+    default:
+        return "ILLEGAL";
+    }
+}
+
+static void cd_usb_bulk_msd_set_state(UsbCdBulkMsdDevice *cd, UsbCdState state)
+{
+    SPICE_DEBUG("State %s -> %s", usb_cd_state_str(cd->state), usb_cd_state_str(state));
+    cd->state = state;
+}
+
+UsbCdBulkMsdDevice *cd_usb_bulk_msd_alloc(void *usb_user_data, uint32_t max_luns)
+{
+    UsbCdBulkMsdDevice *cd = g_new0(UsbCdBulkMsdDevice, 1);
+
+    cd->data_buf_len = 256 * 1024;
+    cd->data_buf = g_malloc(cd->data_buf_len);
+
+    cd->scsi_target = cd_scsi_target_alloc(cd, max_luns);
+    if (cd->scsi_target == NULL) {
+        g_free(cd->data_buf);
+        g_free(cd);
+        return NULL;
+    }
+    cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_INIT);
+    cd->usb_user_data = usb_user_data;
+
+    SPICE_DEBUG("Alloc, max_luns:%u", max_luns);
+    return cd;
+}
+
+int cd_usb_bulk_msd_realize(UsbCdBulkMsdDevice *cd, uint32_t lun,
+                            const CdScsiDeviceParameters *dev_params)
+{
+    CdScsiDeviceParameters scsi_dev_params;
+    int rc;
+
+    scsi_dev_params.vendor = dev_params->vendor ? : "SPICE";
+    scsi_dev_params.product = dev_params->product ? : "USB-CD";
+    scsi_dev_params.version = dev_params->version ? : "0.1";
+    scsi_dev_params.serial = dev_params->serial ? : "123456";
+
+    rc = cd_scsi_dev_realize(cd->scsi_target, lun, &scsi_dev_params);
+    if (rc != 0) {
+        SPICE_ERROR("Failed to realize lun:%u", lun);
+        return rc;
+    }
+
+    if (cd->state == USB_CD_STATE_INIT) {
+        /* wait next request */
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CBW);
+        cd_scsi_dev_request_release(cd->scsi_target, &cd->usb_req.scsi_req);
+    }
+
+    SPICE_DEBUG("Realize OK lun:%u", lun);
+    return 0;
+}
+
+int cd_usb_bulk_msd_lock(UsbCdBulkMsdDevice *cd, uint32_t lun, gboolean lock)
+{
+    int rc;
+
+    rc = cd_scsi_dev_lock(cd->scsi_target, lun, lock);
+    if (rc != 0) {
+        SPICE_ERROR("Failed to lock lun:%u", lun);
+        return rc;
+    }
+
+    SPICE_DEBUG("Lock OK lun:%u", lun);
+    return 0;
+}
+
+int cd_usb_bulk_msd_load(UsbCdBulkMsdDevice *cd, uint32_t lun,
+                         const CdScsiMediaParameters *media_params)
+{
+    int rc;
+
+    rc = cd_scsi_dev_load(cd->scsi_target, lun, media_params);
+    if (rc != 0) {
+        SPICE_ERROR("Failed to load lun:%u", lun);
+        return rc;
+    }
+
+    SPICE_DEBUG("Load OK lun:%u", lun);
+    return 0;
+}
+
+int cd_usb_bulk_msd_get_info(UsbCdBulkMsdDevice *cd, uint32_t lun, CdScsiDeviceInfo *lun_info)
+{
+    int rc;
+
+    rc = cd_scsi_dev_get_info(cd->scsi_target, lun, lun_info);
+    if (rc != 0) {
+        SPICE_ERROR("Failed to get info lun:%u", lun);
+        return rc;
+    }
+
+    return 0;
+}
+
+int cd_usb_bulk_msd_unload(UsbCdBulkMsdDevice *cd, uint32_t lun)
+{
+    int rc;
+
+    rc = cd_scsi_dev_unload(cd->scsi_target, lun);
+    if (rc != 0) {
+        SPICE_ERROR("Failed to unload lun:%u", lun);
+        return rc;
+    }
+
+    SPICE_DEBUG("Unload OK lun:%u", lun);
+    return 0;
+}
+
+int cd_usb_bulk_msd_unrealize(UsbCdBulkMsdDevice *cd, uint32_t lun)
+{
+    int rc;
+
+    rc = cd_scsi_dev_unrealize(cd->scsi_target, lun);
+    if (rc != 0) {
+        SPICE_ERROR("Unrealize lun:%u", lun);
+        return rc;
+    }
+
+    SPICE_DEBUG("Unrealize lun:%u", lun);
+    return 0;
+}
+
+void cd_usb_bulk_msd_free(UsbCdBulkMsdDevice *cd)
+{
+    cd_scsi_target_free(cd->scsi_target);
+    g_free(cd->data_buf);
+    g_free(cd);
+
+    SPICE_DEBUG("Free");
+}
+
+int cd_usb_bulk_msd_reset(UsbCdBulkMsdDevice *cd)
+{
+    cd_scsi_target_reset(cd->scsi_target);
+    cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CBW);
+
+    SPICE_DEBUG("Reset");
+    return 0;
+}
+
+static int parse_usb_msd_cmd(UsbCdBulkMsdDevice *cd, uint8_t *buf, uint32_t cbw_len)
+{
+    struct UsbCdCBW *cbw = (struct UsbCdCBW *)buf;
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+    CdScsiRequest *scsi_req = &usb_req->scsi_req;
+
+    if (cbw_len != sizeof(*cbw)) {
+        SPICE_ERROR("CMD: Bad CBW size:%u", cbw_len);
+        return -1;
+    }
+    if (le32toh(cbw->sig) != 0x43425355) { /* MSD command signature */
+        SPICE_ERROR("CMD: Bad CBW signature:%08x", le32toh(cbw->sig));
+        return -1;
+    }
+    const uint8_t cmd_len = cbw->cmd_len & 0x1F;
+    if (cmd_len < 1 || cmd_len > 16) {
+        SPICE_ERROR("CMD: Bad CBW command len:%08x", cmd_len);
+        return -1;
+    }
+
+    usb_req->lun = cbw->lun;
+    usb_req->usb_req_len = le32toh(cbw->exp_data_len);
+
+    usb_req->scsi_in_len = 0; /* no data from scsi yet */
+    usb_req->xfer_len = 0; /* no bulks transfered yet */
+    usb_req->bulk_in_len = 0; /* no bulk-in requests yet */
+
+    if (usb_req->usb_req_len == 0) {
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CSW); /* no data - return status */
+        scsi_req->buf = NULL;
+        scsi_req->buf_len = 0;
+    } else if (cbw->flags & 0x80) {
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_DATAIN); /* read command */
+        scsi_req->buf = cd->data_buf;
+        scsi_req->buf_len = cd->data_buf_len;
+    } else {
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_DATAOUT); /* write command */
+        scsi_req->buf = NULL;
+        scsi_req->buf_len = 0;
+    }
+
+    scsi_req->cdb_len = cmd_len;
+    g_assert(scsi_req->cdb_len <= sizeof(scsi_req->cdb));
+    memcpy(scsi_req->cdb, cbw->cmd, scsi_req->cdb_len);
+
+    scsi_req->lun = usb_req->lun;
+
+    SPICE_DEBUG("CMD lun:%u tag:%#x flags:%08x "
+                "cdb_len:%u req_len:%u",
+                usb_req->lun, le32toh(cbw->tag), cbw->flags,
+                scsi_req->cdb_len, usb_req->usb_req_len);
+
+    /* prepare status - CSW */
+    usb_req->csw.sig = htole32(0x53425355);
+    usb_req->csw.tag = cbw->tag;
+    usb_req->csw.residue = 0;
+    usb_req->csw.status = (uint8_t)USB_MSD_STATUS_GOOD;
+
+    return 0;
+}
+
+static void usb_cd_cmd_done(UsbCdBulkMsdDevice *cd)
+{
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+    CdScsiRequest *scsi_req = &usb_req->scsi_req;
+
+    cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CBW); /* Command next */
+    cd_scsi_dev_request_release(cd->scsi_target, scsi_req);
+}
+
+static void usb_cd_send_status(UsbCdBulkMsdDevice *cd)
+{
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+
+    SPICE_DEBUG("Command CSW tag:0x%x msd_status:%d len:%" G_GUINT64_FORMAT,
+                le32toh(usb_req->csw.tag), (int)usb_req->csw.status, sizeof(usb_req->csw));
+
+    usb_cd_cmd_done(cd);
+
+    g_assert(usb_req->csw.sig == htole32(0x53425355));
+    cd_usb_bulk_msd_read_complete(cd->usb_user_data,
+                                  (uint8_t *)&usb_req->csw, sizeof(usb_req->csw),
+                                  BULK_STATUS_GOOD);
+}
+
+static void usb_cd_send_canceled(UsbCdBulkMsdDevice *cd)
+{
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+
+    SPICE_DEBUG("Canceled cmd tag:0x%x, len:%" G_GUINT64_FORMAT,
+                le32toh(usb_req->csw.tag), sizeof(usb_req->csw));
+
+    usb_cd_cmd_done(cd);
+
+    cd_usb_bulk_msd_read_complete(cd->usb_user_data,
+                                  NULL, 0,
+                                  BULK_STATUS_CANCELED);
+}
+
+static void usb_cd_send_data_in(UsbCdBulkMsdDevice *cd, uint32_t max_len)
+{
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+    CdScsiRequest *scsi_req = &usb_req->scsi_req;
+    uint8_t *buf = ((uint8_t *)scsi_req->buf) + usb_req->xfer_len;
+    uint32_t avail_len = usb_req->scsi_in_len - usb_req->xfer_len;
+    uint32_t send_len = MIN(avail_len, max_len);
+
+    SPICE_DEBUG("Data-in cmd tag 0x%x, remains %u"
+                ", requested %u, send %u",
+                usb_req->csw.tag, avail_len, max_len, send_len);
+
+    g_assert(max_len <= usb_req->usb_req_len);
+
+    cd_usb_bulk_msd_read_complete(cd->usb_user_data,
+                                  buf, send_len,
+                                  BULK_STATUS_GOOD);
+
+    if (scsi_req->status == GOOD) {
+        usb_req->xfer_len += send_len;
+        if (usb_req->xfer_len == usb_req->scsi_in_len) {
+            /* all data for this bulk has been transferred */
+            if (usb_req->scsi_in_len == usb_req->usb_req_len || /* req fully satisfied */
+                send_len < max_len) { /* partial bulk - no more data */
+                cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CSW);
+            } else {
+                /* partial cmd data fullfilled entire vulk-in request */
+                cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_ZERO_DATAIN);
+            }
+        }
+    } else { /* cmd failed - no more data */
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CSW);
+    }
+}
+
+int cd_usb_bulk_msd_read(UsbCdBulkMsdDevice *cd, uint32_t max_len)
+{
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+    CdScsiRequest *scsi_req = &usb_req->scsi_req;
+
+    SPICE_DEBUG("msd_read, state: %s, len %u",
+                usb_cd_state_str(cd->state), max_len);
+
+    switch (cd->state) {
+    case USB_CD_STATE_CSW: /* Command Status */
+        if (max_len < 13) {
+            goto fail;
+        }
+        if (cd_scsi_get_req_state(scsi_req) == SCSI_REQ_COMPLETE) {
+            usb_cd_send_status(cd);
+        } else {
+            usb_req->bulk_in_len += max_len;
+            SPICE_DEBUG("msd_read CSW, req incomplete, added len %u"
+                        " saved len %u",
+                        max_len, usb_req->bulk_in_len);
+        }
+        break;
+
+    case USB_CD_STATE_DATAIN: /* Bulk Data-IN */
+        if (cd_scsi_get_req_state(scsi_req) == SCSI_REQ_COMPLETE) {
+            usb_cd_send_data_in(cd, max_len);
+        } else {
+            usb_req->bulk_in_len += max_len;
+            SPICE_DEBUG("msd_read DATAIN, req incomplete, added len %u saved len %u",
+                        max_len, usb_req->bulk_in_len);
+        }
+        break;
+
+    case USB_CD_STATE_ZERO_DATAIN:
+        cd_usb_bulk_msd_read_complete(cd->usb_user_data,
+                                      NULL, 0,
+                                      BULK_STATUS_GOOD);
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CSW); /* Status next */
+        break;
+
+    default:
+        SPICE_ERROR("Unexpected read state: %s, len %u",
+                    usb_cd_state_str(cd->state), max_len);
+        goto fail;
+    }
+    return 0;
+
+fail:
+    return -1;
+}
+
+void cd_scsi_dev_request_complete(void *target_user_data, CdScsiRequest *scsi_req)
+{
+    UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)target_user_data;
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+
+    g_assert(scsi_req == &usb_req->scsi_req);
+
+    if (scsi_req->req_state == SCSI_REQ_COMPLETE) {
+
+        usb_req->scsi_in_len = (scsi_req->in_len <= usb_req->usb_req_len) ?
+                                scsi_req->in_len : usb_req->usb_req_len;
+
+        /* prepare CSW */
+        if (usb_req->usb_req_len > usb_req->scsi_in_len) {
+            usb_req->csw.residue = htole32(usb_req->usb_req_len - usb_req->scsi_in_len);
+        }
+        if (scsi_req->status != GOOD) {
+            usb_req->csw.status = (uint8_t)USB_MSD_STATUS_FAILED;
+        }
+
+        if (usb_req->bulk_in_len) {
+            /* bulk-in request arrived while scsi was still running */
+            if (cd->state == USB_CD_STATE_DATAIN) {
+                usb_cd_send_data_in(cd, usb_req->bulk_in_len);
+            } else if (cd->state == USB_CD_STATE_CSW) {
+                usb_cd_send_status(cd);
+            }
+            usb_req->bulk_in_len = 0;
+        }
+    } else if (scsi_req->req_state == SCSI_REQ_CANCELED) {
+        usb_cd_send_canceled(cd);
+    } else {
+        g_assert(scsi_req->req_state == SCSI_REQ_DISPOSED);
+        SPICE_DEBUG("Disposed cmd tag:0x%x, len:%" G_GUINT64_FORMAT,
+                le32toh(usb_req->csw.tag), sizeof(usb_req->csw));
+        usb_cd_cmd_done(cd);
+    }
+}
+
+int cd_usb_bulk_msd_cancel_read(UsbCdBulkMsdDevice *cd)
+{
+    UsbCdBulkMsdRequest *usb_req = &cd->usb_req;
+    CdScsiRequest *scsi_req = &usb_req->scsi_req;
+
+    cd_scsi_dev_request_cancel(cd->scsi_target, scsi_req);
+    return 0;
+}
+
+int cd_usb_bulk_msd_write(UsbCdBulkMsdDevice *cd, uint8_t *buf_out, uint32_t buf_out_len)
+{
+    switch (cd->state) {
+    case USB_CD_STATE_CBW: /* Command Block */
+        parse_usb_msd_cmd(cd, buf_out, buf_out_len);
+        if (cd->state == USB_CD_STATE_DATAIN || cd->state == USB_CD_STATE_CSW) {
+            cd_scsi_dev_request_submit(cd->scsi_target, &cd->usb_req.scsi_req);
+        }
+        break;
+    case USB_CD_STATE_DATAOUT: /* Data-Out for a Write cmd */
+        cd->usb_req.scsi_req.buf = buf_out;
+        cd->usb_req.scsi_req.buf_len = buf_out_len;
+        cd_scsi_dev_request_submit(cd->scsi_target, &cd->usb_req.scsi_req);
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CSW); /* Status next */
+        break;
+    default:
+        SPICE_DEBUG("Unexpected write state: %s, len %u",
+                    usb_cd_state_str(cd->state), buf_out_len);
+        goto fail;
+    }
+    return 0;
+
+fail:
+    return -1;
+}
+
+void cd_scsi_dev_reset_complete(void *target_user_data, uint32_t lun)
+{
+    UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)target_user_data;
+
+    if (cd->state == USB_CD_STATE_DEVICE_RESET) {
+        cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_CBW);
+        cd_usb_bulk_msd_reset_complete(cd->usb_user_data, 0);
+    }
+}
+
+void cd_scsi_target_reset_complete(void *target_user_data)
+{
+    UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)target_user_data;
+    cd_usb_bulk_msd_set_state(cd, USB_CD_STATE_INIT);
+}
+
+void cd_scsi_dev_changed(void *target_user_data, uint32_t lun)
+{
+    UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)target_user_data;
+    SPICE_DEBUG("Device changed, state: %s lun: %u",
+                usb_cd_state_str(cd->state), lun);
+    cd_usb_bulk_msd_lun_changed(cd->usb_user_data, lun);
+}
+
+#endif /* USE_USBREDIR */
diff --git a/src/cd-usb-bulk-msd.h b/src/cd-usb-bulk-msd.h
new file mode 100644
index 0000000..c165c18
--- /dev/null
+++ b/src/cd-usb-bulk-msd.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+    Copyright (C) 2018 Red Hat, Inc.
+
+    Red Hat Authors:
+    Alexander Nezhinsky<anezhins 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/>.
+*/
+
+#pragma once
+
+#include <gio/gio.h>
+
+#include "cd-scsi-dev-params.h"
+
+G_BEGIN_DECLS
+
+typedef enum CdUsbBulkStatus {
+    BULK_STATUS_GOOD = 0,
+    BULK_STATUS_ERROR,
+    BULK_STATUS_CANCELED,
+    BULK_STATUS_STALL,
+} CdUsbBulkStatus;
+
+typedef struct UsbCdBulkMsdDevice UsbCdBulkMsdDevice;
+
+/* USB backend callbacks */
+
+/* called on completed read data bulk transfer
+ * user_data - user_data in unit parameters structure
+ * status - bulk status code
+ */
+void cd_usb_bulk_msd_read_complete(void *user_data,
+                                   uint8_t *data, uint32_t length,
+                                   CdUsbBulkStatus status);
+
+/* called when state of device's unit changed to signal GUI component
+ *  user_data - user_data in unit parameters structure
+ */
+void cd_usb_bulk_msd_lun_changed(void *user_data, uint32_t lun);
+
+/* called on completed device reset
+ * user_data - user_data in unit parameters structure
+ * status - error code
+ */
+void cd_usb_bulk_msd_reset_complete(void *user_data,
+                                    int status);
+
+/* MSD backend api */
+
+/* allocate new device descriptor */
+UsbCdBulkMsdDevice *cd_usb_bulk_msd_alloc(void *user_data, uint32_t max_lun);
+
+/* free device descriptor */
+void cd_usb_bulk_msd_free(UsbCdBulkMsdDevice *device);
+
+/* configure a new Logical Unit to be represen ted by the device
+ *  returns: error code
+ */
+int cd_usb_bulk_msd_realize(UsbCdBulkMsdDevice *device, uint32_t lun,
+                            const CdScsiDeviceParameters *dev_params);
+
+/* lock the device, prevent unloading
+ * returns: error code
+ */
+int cd_usb_bulk_msd_lock(UsbCdBulkMsdDevice *device, uint32_t lun, gboolean lock);
+
+/* load new media, if already loaded, simulate media change
+ * returns: error code
+ */
+int cd_usb_bulk_msd_load(UsbCdBulkMsdDevice *device, uint32_t lun,
+                         const CdScsiMediaParameters *media_params);
+
+/* query unit parameters and status
+ * returns: error code
+ */
+int cd_usb_bulk_msd_get_info(UsbCdBulkMsdDevice *device, uint32_t lun,
+                             CdScsiDeviceInfo *lun_info);
+
+/* unload the media
+ * returns: error code
+ */
+int cd_usb_bulk_msd_unload(UsbCdBulkMsdDevice *device, uint32_t lun);
+
+/* detach a Logical Unit
+ * returns: error code
+ */
+int cd_usb_bulk_msd_unrealize(UsbCdBulkMsdDevice *device, uint32_t lun);
+
+/* reset the device instance; cancel all IO ops, reset state
+ * returns: error code
+ */
+int cd_usb_bulk_msd_reset(UsbCdBulkMsdDevice *device);
+
+
+/* perform a write data bulk transfer
+ * data_len - length of available data to write
+ * returns: error code
+ */
+int cd_usb_bulk_msd_write(UsbCdBulkMsdDevice*device, uint8_t *buf, uint32_t data_len);
+
+
+/* perform a read data bulk transfer
+ * max_len - length of available buffer to fill
+ * If data available immediately, should call cd_usb_bulk_msd_read_complete()
+ *   and return success
+ * If fatal error detected immediately, should call cd_usb_bulk_msd_read_complete()
+ *   with error code and return success
+ *
+ * returns: 0 - success, -1 - error
+ */
+int cd_usb_bulk_msd_read(UsbCdBulkMsdDevice *device, uint32_t max_len);
+
+/* cancels pending read data bulk transfer
+ * returns: error code
+ */
+int cd_usb_bulk_msd_cancel_read(UsbCdBulkMsdDevice *device);
+
+G_END_DECLS
diff --git a/src/scsi-constants.h b/src/scsi-constants.h
new file mode 100644
index 0000000..a6d34a6
--- /dev/null
+++ b/src/scsi-constants.h
@@ -0,0 +1,321 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2018 Red Hat, Inc.
+   Based on the GLib header
+
+   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/>.
+*/
+
+#pragma once
+
+/*
+ *      SCSI opcodes
+ */
+
+#define TEST_UNIT_READY       0x00
+#define REWIND                0x01
+#define REQUEST_SENSE         0x03
+#define FORMAT_UNIT           0x04
+#define READ_BLOCK_LIMITS     0x05
+#define INITIALIZE_ELEMENT_STATUS 0x07
+#define REASSIGN_BLOCKS_      0x07
+#define READ_6                0x08
+#define WRITE_6               0x0a
+#define SET_CAPACITY          0x0b
+#define READ_REVERSE          0x0f
+#define WRITE_FILEMARKS       0x10
+#define SPACE                 0x11
+#define INQUIRY               0x12
+#define RECOVER_BUFFERED_DATA 0x14
+#define MODE_SELECT           0x15
+#define RESERVE               0x16
+#define RELEASE               0x17
+#define COPY                  0x18
+#define ERASE                 0x19
+#define MODE_SENSE            0x1a
+#define LOAD_UNLOAD           0x1b
+#define SCAN                  0x1b
+#define START_STOP            0x1b
+#define RECEIVE_DIAGNOSTIC    0x1c
+#define SEND_DIAGNOSTIC       0x1d
+#define ALLOW_MEDIUM_REMOVAL  0x1e
+#define SET_WINDOW            0x24
+#define READ_CAPACITY_10      0x25
+#define GET_WINDOW            0x25
+#define READ_10               0x28
+#define WRITE_10              0x2a
+#define SEND                  0x2a
+#define SEEK_10               0x2b
+#define LOCATE_10             0x2b
+#define POSITION_TO_ELEMENT   0x2b
+#define WRITE_VERIFY_10       0x2e
+#define VERIFY_10             0x2f
+#define SEARCH_HIGH           0x30
+#define SEARCH_EQUAL          0x31
+#define OBJECT_POSITION       0x31
+#define SEARCH_LOW            0x32
+#define SET_LIMITS            0x33
+#define PRE_FETCH             0x34
+#define READ_POSITION         0x34
+#define GET_DATA_BUFFER_STATUS 0x34
+#define SYNCHRONIZE_CACHE     0x35
+#define LOCK_UNLOCK_CACHE     0x36
+#define INITIALIZE_ELEMENT_STATUS_WITH_RANGE 0x37
+#define READ_DEFECT_DATA      0x37
+#define MEDIUM_SCAN           0x38
+#define COMPARE               0x39
+#define COPY_VERIFY           0x3a
+#define WRITE_BUFFER          0x3b
+#define READ_BUFFER           0x3c
+#define UPDATE_BLOCK          0x3d
+#define READ_LONG_10          0x3e
+#define WRITE_LONG_10         0x3f
+#define CHANGE_DEFINITION     0x40
+#define WRITE_SAME_10         0x41
+#define UNMAP                 0x42
+#define READ_TOC              0x43
+#define REPORT_DENSITY_SUPPORT 0x44
+#define GET_CONFIGURATION     0x46
+#define SANITIZE              0x48
+#define GET_EVENT_STATUS_NOTIFICATION 0x4a
+#define LOG_SELECT            0x4c
+#define LOG_SENSE             0x4d
+#define READ_DISC_INFORMATION 0x51
+#define READ_TRACK_INFORMATION 0x52
+#define RESERVE_TRACK         0x53
+#define MODE_SELECT_10        0x55
+#define RESERVE_10            0x56
+#define RELEASE_10            0x57
+#define MODE_SENSE_10         0x5a
+#define SEND_CUE_SHEET        0x5d
+#define PERSISTENT_RESERVE_IN 0x5e
+#define PERSISTENT_RESERVE_OUT 0x5f
+#define VARLENGTH_CDB         0x7f
+#define WRITE_FILEMARKS_16    0x80
+#define READ_REVERSE_16       0x81
+#define ALLOW_OVERWRITE       0x82
+#define EXTENDED_COPY         0x83
+#define ATA_PASSTHROUGH_16    0x85
+#define ACCESS_CONTROL_IN     0x86
+#define ACCESS_CONTROL_OUT    0x87
+#define READ_16               0x88
+#define COMPARE_AND_WRITE     0x89
+#define WRITE_16              0x8a
+#define WRITE_VERIFY_16       0x8e
+#define VERIFY_16             0x8f
+#define PRE_FETCH_16          0x90
+#define SPACE_16              0x91
+#define SYNCHRONIZE_CACHE_16  0x91
+#define LOCATE_16             0x92
+#define WRITE_SAME_16         0x93
+#define ERASE_16              0x93
+#define SERVICE_ACTION_IN_16  0x9e
+#define WRITE_LONG_16         0x9f
+#define REPORT_LUNS           0xa0
+#define ATA_PASSTHROUGH_12    0xa1
+#define MAINTENANCE_IN        0xa3
+#define MAINTENANCE_OUT       0xa4
+#define MOVE_MEDIUM           0xa5
+#define EXCHANGE_MEDIUM       0xa6
+#define SET_READ_AHEAD        0xa7
+#define READ_12               0xa8
+#define WRITE_12              0xaa
+#define SERVICE_ACTION_IN_12  0xab
+#define ERASE_12              0xac
+#define WRITE_VERIFY_12       0xae
+#define VERIFY_12             0xaf
+#define SEARCH_HIGH_12        0xb0
+#define SEARCH_EQUAL_12       0xb1
+#define SEARCH_LOW_12         0xb2
+#define READ_ELEMENT_STATUS   0xb8
+#define SEND_VOLUME_TAG       0xb6
+
+/* MMC-specific opcode assignment */
+#define MMC_SEND_EVENT          0xa2
+#define MMC_SEND_KEY            0xa3
+#define MMC_REPORT_KEY          0xa4
+#define MMC_GET_PERFORMANCE     0xac
+#define MMC_READ_DVD_STRUCTURE  0xad
+#define MMC_READ_DEFECT_DATA_12 0xb7
+#define MMC_SET_CD_SPEED        0xbb
+#define MMC_MECHANISM_STATUS    0xbd
+#define MMC_READ_CD             0xbe
+#define MMC_SEND_DVD_STRUCTURE  0xbf
+
+/*
+ * SERVICE ACTION IN subcodes
+ */
+#define SAI_READ_CAPACITY_16  0x10
+
+/*
+ * READ POSITION service action codes
+ */
+#define SHORT_FORM_BLOCK_ID  0x00
+#define SHORT_FORM_VENDOR_SPECIFIC 0x01
+#define LONG_FORM            0x06
+#define EXTENDED_FORM        0x08
+
+/*
+ *  SAM Status codes
+ */
+
+#define GOOD                 0x00
+#define CHECK_CONDITION      0x02
+#define CONDITION_GOOD       0x04
+#define BUSY                 0x08
+#define INTERMEDIATE_GOOD    0x10
+#define INTERMEDIATE_C_GOOD  0x14
+#define RESERVATION_CONFLICT 0x18
+#define COMMAND_TERMINATED   0x22
+#define TASK_SET_FULL        0x28
+#define ACA_ACTIVE           0x30
+#define TASK_ABORTED         0x40
+
+#define STATUS_MASK          0x3e
+
+/*
+ *  SENSE KEYS
+ */
+
+#define NO_SENSE            0x00
+#define RECOVERED_ERROR     0x01
+#define NOT_READY           0x02
+#define MEDIUM_ERROR        0x03
+#define HARDWARE_ERROR      0x04
+#define ILLEGAL_REQUEST     0x05
+#define UNIT_ATTENTION      0x06
+#define DATA_PROTECT        0x07
+#define BLANK_CHECK         0x08
+#define COPY_ABORTED        0x0a
+#define ABORTED_COMMAND     0x0b
+#define VOLUME_OVERFLOW     0x0d
+#define MISCOMPARE          0x0e
+
+
+/*
+ *  DEVICE TYPES
+ */
+
+#define TYPE_DISK           0x00
+#define TYPE_TAPE           0x01
+#define TYPE_PRINTER        0x02
+#define TYPE_PROCESSOR      0x03    /* HP scanners use this */
+#define TYPE_WORM           0x04    /* Treated as ROM by our system */
+#define TYPE_ROM            0x05
+#define TYPE_SCANNER        0x06
+#define TYPE_MOD            0x07    /* Magneto-optical disk -
+				     * - treated as TYPE_DISK */
+#define TYPE_MEDIUM_CHANGER 0x08
+#define TYPE_STORAGE_ARRAY  0x0c    /* Storage array device */
+#define TYPE_ENCLOSURE      0x0d    /* Enclosure Services Device */
+#define TYPE_RBC            0x0e    /* Simplified Direct-Access Device */
+#define TYPE_OSD            0x11    /* Object-storage Device */
+#define TYPE_WLUN           0x1e    /* Well known LUN */
+#define TYPE_NOT_PRESENT    0x1f
+#define TYPE_INACTIVE       0x20
+#define TYPE_NO_LUN         0x7f
+
+/* Mode page codes for mode sense/set */
+#define MODE_PAGE_R_W_ERROR                   0x01
+#define MODE_PAGE_MRW                         0x03
+#define MODE_PAGE_HD_GEOMETRY                 0x04
+#define MODE_PAGE_FLEXIBLE_DISK_GEOMETRY      0x05 /* SBC */
+#define MODE_PAGE_WRITE_PARAMETER             0x05 /* MMC */
+#define MODE_PAGE_CACHING                     0x08
+#define MODE_PAGE_CD_DEVICE                   0x0d
+#define MODE_PAGE_AUDIO_CTL                   0x0e
+#define MODE_PAGE_POWER                       0x1a
+#define MODE_PAGE_FAULT_FAIL                  0x1c
+#define MODE_PAGE_TO_PROTECT                  0x1d
+#define MODE_PAGE_CAPS_MECH_STATUS            0x2a
+#define MODE_PAGE_MRW_VENDOR                  0x2C
+#define MODE_PAGE_ALLS                        0x3f
+/* Not in Mt. Fuji, but in ATAPI 2.6 -- deprecated now in favor
+ * of MODE_PAGE_SENSE_POWER */
+#define MODE_PAGE_CDROM                       0x0d
+
+#define MODE_PAGE_CDROM                       0x0d
+
+/* Event notification classes for GET EVENT STATUS NOTIFICATION */
+#define GESN_NO_EVENTS                0
+#define GESN_OPERATIONAL_CHANGE       1
+#define GESN_POWER_MANAGEMENT         2
+#define GESN_EXTERNAL_REQUEST         3
+#define GESN_MEDIA                    4
+#define GESN_MULTIPLE_HOSTS           5
+#define GESN_DEVICE_BUSY              6
+
+/* Event codes for MEDIA event status notification */
+#define MEC_NO_CHANGE                 0
+#define MEC_EJECT_REQUESTED           1
+#define MEC_NEW_MEDIA                 2
+#define MEC_MEDIA_REMOVAL             3 /* only for media changers */
+#define MEC_MEDIA_CHANGED             4 /* only for media changers */
+#define MEC_BG_FORMAT_COMPLETED       5 /* MRW or DVD+RW b/g format completed */
+#define MEC_BG_FORMAT_RESTARTED       6 /* MRW or DVD+RW b/g format restarted */
+
+#define MS_TRAY_OPEN                  1
+#define MS_MEDIA_PRESENT              2
+
+/*
+ * Based on values from <linux/cdrom.h> but extending CD_MINS
+ * to the maximum common size allowed by the Orange's Book ATIP
+ *
+ * 90 and 99 min CDs are also available but using them as the
+ * upper limit reduces the effectiveness of the heuristic to
+ * detect DVDs burned to less than 25% of their maximum capacity
+ */
+
+/* Some generally useful CD-ROM information */
+#define CD_MINS                       80 /* max. minutes per CD */
+#define CD_SECS                       60 /* seconds per minute */
+#define CD_FRAMES                     75 /* frames per second */
+#define CD_FRAMESIZE                2048 /* bytes per frame, "cooked" mode */
+#define CD_MAX_BYTES       (CD_MINS * CD_SECS * CD_FRAMES * CD_FRAMESIZE)
+#define CD_MAX_SECTORS     (CD_MAX_BYTES / 512)
+
+/*
+ * The MMC values are not IDE specific and might need to be moved
+ * to a common header if they are also needed for the SCSI emulation
+ */
+
+/* Profile list from MMC-6 revision 1 table 91 */
+#define MMC_PROFILE_NONE                0x0000
+#define MMC_PROFILE_CD_ROM              0x0008
+#define MMC_PROFILE_CD_R                0x0009
+#define MMC_PROFILE_CD_RW               0x000A
+#define MMC_PROFILE_DVD_ROM             0x0010
+#define MMC_PROFILE_DVD_R_SR            0x0011
+#define MMC_PROFILE_DVD_RAM             0x0012
+#define MMC_PROFILE_DVD_RW_RO           0x0013
+#define MMC_PROFILE_DVD_RW_SR           0x0014
+#define MMC_PROFILE_DVD_R_DL_SR         0x0015
+#define MMC_PROFILE_DVD_R_DL_JR         0x0016
+#define MMC_PROFILE_DVD_RW_DL           0x0017
+#define MMC_PROFILE_DVD_DDR             0x0018
+#define MMC_PROFILE_DVD_PLUS_RW         0x001A
+#define MMC_PROFILE_DVD_PLUS_R          0x001B
+#define MMC_PROFILE_DVD_PLUS_RW_DL      0x002A
+#define MMC_PROFILE_DVD_PLUS_R_DL       0x002B
+#define MMC_PROFILE_BD_ROM              0x0040
+#define MMC_PROFILE_BD_R_SRM            0x0041
+#define MMC_PROFILE_BD_R_RRM            0x0042
+#define MMC_PROFILE_BD_RE               0x0043
+#define MMC_PROFILE_HDDVD_ROM           0x0050
+#define MMC_PROFILE_HDDVD_R             0x0051
+#define MMC_PROFILE_HDDVD_RAM           0x0052
+#define MMC_PROFILE_HDDVD_RW            0x0053
+#define MMC_PROFILE_HDDVD_R_DL          0x0058
+#define MMC_PROFILE_HDDVD_RW_DL         0x005A
+#define MMC_PROFILE_INVALID             0xFFFF
commit 3e20f17b90598e740c4e274b81d99f28187da800
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Thu Sep 19 16:11:21 2019 +0200

    usb-redir: extend USB backend to support emulated devices
    
    Redirection of emulated devices requires special approach, as
    usbredirhost can't be used for that (it works only with libusb
    devices). For emulated devices we create instance of usbredirparser
    that implements USB redirection protocol.  In order to work with the
    same set of protocol capabilities that usbredirhost sets up with
    remote side, the parser shall: - not send its own 'hello' to the
    server
    - initialize the same capabilities that usbredirhost
    - receive the same 'hello' response
    For that we analyze 'hello' sent by USB redir parser and extract set
    of capabilities from it and upon receive of 'hello' response we
    provide it also to the parser.  When libusb device is redirected via a
    channel, instance of usbredirhost serves it.  When emulated device is
    redirected via a channel, instance of usbredirparser does the job.
    
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/usb-backend.c b/src/usb-backend.c
index 169f1b0..a51c545 100644
--- a/src/usb-backend.c
+++ b/src/usb-backend.c
@@ -58,6 +58,7 @@ struct _SpiceUsbDevice
     UsbDeviceInformation device_info;
     bool cached_isochronous_valid;
     bool cached_isochronous;
+    gboolean edev_configured;
 };
 
 struct _SpiceUsbBackend
@@ -80,14 +81,23 @@ struct _SpiceUsbBackend
     uint32_t own_devices_mask;
 };
 
+typedef enum {
+    USB_CHANNEL_STATE_INITIALIZING,
+    USB_CHANNEL_STATE_HOST,
+    USB_CHANNEL_STATE_PARSER,
+} SpiceUsbBackendChannelState;
+
 struct _SpiceUsbBackendChannel
 {
     struct usbredirhost *usbredirhost;
     struct usbredirparser *parser;
+    SpiceUsbBackendChannelState state;
     uint8_t *read_buf;
     int read_buf_size;
     struct usbredirfilter_rule *rules;
     int rules_count;
+    uint32_t rejected          : 1;
+    uint32_t wait_disconnect_ack : 1;
     SpiceUsbBackendDevice *attached;
     SpiceUsbredirChannel *usbredir_channel;
     SpiceUsbBackend *backend;
@@ -407,13 +417,17 @@ 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;
-    if (!ch->usbredirhost) {
-        /* just to be on the safe side */
+    if (ch->parser == NULL) {
         return;
     }
     if (is_channel_ready(ch->usbredir_channel)) {
-        SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch);
-        usbredirhost_write_guest_data(ch->usbredirhost);
+        if (ch->state == USB_CHANNEL_STATE_HOST) {
+            SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch);
+            usbredirhost_write_guest_data(ch->usbredirhost);
+        } else {
+            SPICE_DEBUG("%s ch %p -> parser", __FUNCTION__, ch);
+            usbredirparser_do_write(ch->parser);
+        }
     } else {
         SPICE_DEBUG("%s ch %p (not ready)", __FUNCTION__, ch);
     }
@@ -568,13 +582,53 @@ void spice_usb_backend_device_unref(SpiceUsbBackendDevice *dev)
     }
 }
 
-G_GNUC_INTERNAL
+static int check_edev_device_filter(SpiceUsbBackendDevice *dev,
+                                    const struct usbredirfilter_rule *rules,
+                                    int count)
+{
+    SpiceUsbEmulatedDevice *edev = dev->edev;
+    uint8_t cls[32], subcls[32], proto[32], *cfg, ifnum = 0;
+    uint16_t size, offset = 0;
+
+    if (!device_ops(edev)->get_descriptor(edev, LIBUSB_DT_CONFIG, 0, (void **)&cfg, &size)) {
+        return -EINVAL;
+    }
+
+    while ((offset + 1) < size) {
+        uint8_t len  = cfg[offset];
+        uint8_t type = cfg[offset + 1];
+        if ((offset + len) > size) {
+            break;
+        }
+        if (type == LIBUSB_DT_INTERFACE) {
+            cls[ifnum] = cfg[offset + 5];
+            subcls[ifnum] = cfg[offset + 6];
+            proto[ifnum] = cfg[offset + 7];
+            ifnum++;
+        }
+        offset += len;
+    }
+
+    return usbredirfilter_check(rules, count,
+                                dev->device_info.class,
+                                dev->device_info.subclass,
+                                dev->device_info.protocol,
+                                cls, subcls, proto, ifnum,
+                                dev->device_info.vid,
+                                dev->device_info.pid,
+                                dev->device_info.bcdUSB, 0);
+}
+
 int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev,
-                                          const struct usbredirfilter_rule *rules,
-                                          int count)
+                                          const struct usbredirfilter_rule *rules, int count)
 {
-    g_return_val_if_fail(dev->libusb_device != NULL, -EINVAL);
-    return usbredirhost_check_device_filter(rules, count, dev->libusb_device, 0);
+    if (dev->libusb_device != NULL) {
+        return usbredirhost_check_device_filter(rules, count, dev->libusb_device, 0);
+    } else if (dev->edev != NULL) {
+        return check_edev_device_filter(dev, rules, count);
+    }
+    g_warn_if_reached();
+    return -EINVAL;
 }
 
 static int usbredir_read_callback(void *user_data, uint8_t *data, int count)
@@ -693,10 +747,33 @@ int spice_usb_backend_read_guest_data(SpiceUsbBackendChannel *ch, uint8_t *data,
 
     ch->read_buf = data;
     ch->read_buf_size = count;
-    if (ch->usbredirhost) {
+    if (SPICE_UNLIKELY(ch->state == USB_CHANNEL_STATE_INITIALIZING)) {
+        if (ch->usbredirhost != NULL) {
+            res = usbredirhost_read_guest_data(ch->usbredirhost);
+            if (res != 0) {
+                return res;
+            }
+            ch->state = USB_CHANNEL_STATE_HOST;
+
+            /* usbredirhost should consume hello response */
+            g_return_val_if_fail(ch->read_buf == NULL, USB_REDIR_ERROR_READ_PARSE);
+        } else {
+            ch->state = USB_CHANNEL_STATE_PARSER;
+        }
+
+        ch->read_buf = data;
+        ch->read_buf_size = count;
+        if (ch->attached && ch->attached->edev) {
+            /* case of CD sharing on connect */
+            ch->state = USB_CHANNEL_STATE_PARSER;
+            SPICE_DEBUG("%s: switch %p to parser", __FUNCTION__, ch);
+        }
+        return usbredirparser_do_read(ch->parser);
+    }
+    if (ch->state == USB_CHANNEL_STATE_HOST) {
         res = usbredirhost_read_guest_data(ch->usbredirhost);
     } else {
-        res = USB_REDIR_ERROR_IO;
+        res = usbredirparser_do_read(ch->parser);
     }
     switch (res)
     {
@@ -715,6 +792,11 @@ int spice_usb_backend_read_guest_data(SpiceUsbBackendChannel *ch, uint8_t *data,
     }
     SPICE_DEBUG("%s ch %p, %d bytes, res %d", __FUNCTION__, ch, count, res);
 
+    if (ch->rejected) {
+        ch->rejected = 0;
+        res = USB_REDIR_ERROR_DEV_REJECTED;
+    }
+
     return res;
 }
 
@@ -741,23 +823,115 @@ GError *spice_usb_backend_get_error_details(int error_code, gchar *desc)
     return err;
 }
 
+void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data)
+{
+    if (ch->state == USB_CHANNEL_STATE_HOST) {
+        SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch);
+        usbredirhost_free_write_buffer(ch->usbredirhost, data);
+    } else {
+        SPICE_DEBUG("%s ch %p -> parser", __FUNCTION__, ch);
+        usbredirparser_free_write_buffer(ch->parser, data);
+    }
+}
+
 static void
 usbredir_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *h,
                         uint8_t *data, int data_len)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    SpiceUsbBackendChannel *ch = priv;
+    SpiceUsbBackendDevice *d = ch->attached;
+    SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL;
+    struct usb_redir_control_packet_header response = *h;
+    uint8_t reqtype = h->requesttype & 0x7f;
+    gboolean done = FALSE;
+    void *out_buffer = NULL;
+
+    response.status = usb_redir_stall;
+    SPICE_DEBUG("%s %p: TRVIL %02X %02X %04X %04X %04X",
+                __FUNCTION__,
+                ch, h->requesttype, h->request,
+                h->value, h->index, h->length);
+
+    if (!edev) {
+        SPICE_DEBUG("%s: device not attached", __FUNCTION__);
+        response.status = usb_redir_ioerror;
+        done = TRUE;
+    } else if (reqtype == (LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE) &&
+               h->request == LIBUSB_REQUEST_GET_DESCRIPTOR) {
+        uint16_t size;
+        done = device_ops(edev)->get_descriptor(edev, h->value >> 8, h->value & 0xff,
+                                                &out_buffer, &size);
+        response.length = size;
+        if (done) {
+            response.status = 0;
+        }
+        done = TRUE;
+    }
+
+    if (!done) {
+        device_ops(edev)->control_request(edev, data, data_len, &response, &out_buffer);
+        done = TRUE;
+    }
+
+    if (response.status) {
+        response.length = 0;
+    } else if (response.length > h->length) {
+        response.length = h->length;
+    }
+
+    SPICE_DEBUG("%s responding with payload of %02X, status %X",
+                __FUNCTION__, response.length, response.status);
+    usbredirparser_send_control_packet(ch->parser, id, &response,
+                                       response.length ? out_buffer : NULL,
+                                       response.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)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    SpiceUsbBackendChannel *ch = priv;
+    SpiceUsbBackendDevice *d = ch->attached;
+    SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL;
+    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 %" G_GUINT64_FORMAT, __FUNCTION__,
+                ch, h->endpoint, len, id);
+
+    if (!edev) {
+        SPICE_DEBUG("%s: device not attached", __FUNCTION__);
+        hout.status = usb_redir_ioerror;
+        hout.length = hout.length_high = 0;
+        SPICE_DEBUG("%s: responding with ZLP status %d", __FUNCTION__, hout.status);
+    } else if (h->endpoint & LIBUSB_ENDPOINT_IN) {
+        if (device_ops(edev)->bulk_in_request(edev, id, &hout)) {
+            usbredirparser_free_packet_data(ch->parser, data);
+            /* completion is asynchronous */
+            return;
+        }
+    } else {
+        hout.status = usb_redir_stall;
+        device_ops(edev)->bulk_out_request(edev, h->endpoint, data, data_len, &hout.status);
+        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)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    SpiceUsbBackendChannel *ch = priv;
+    SpiceUsbBackendDevice *d = ch->attached;
+    SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL;
+    SPICE_DEBUG("%s ch %p", __FUNCTION__, ch);
+    if (edev) {
+        device_ops(edev)->reset(edev);
+    }
 }
 
 static void
@@ -776,34 +950,72 @@ static void
 usbredir_set_configuration(void *priv, uint64_t id,
                            struct usb_redir_set_configuration_header *set_configuration)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    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->edev_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)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    SpiceUsbBackendChannel *ch = priv;
+    struct usb_redir_configuration_status_header h;
+    h.status = 0;
+    h.configuration = ch->attached && ch->attached->edev_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)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    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)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    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)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    SpiceUsbBackendChannel *ch = priv;
+    SpiceUsbBackendDevice *d = ch->attached;
+    SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL;
+    if (!edev) {
+        SPICE_DEBUG("%s: device not attached", __FUNCTION__);
+        return;
+    }
+    device_ops(edev)->cancel_request(edev, id);
 }
 
 static void usbredir_filter_reject(void *priv)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    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. */
@@ -828,13 +1040,77 @@ usbredir_filter_filter(void *priv, struct usbredirfilter_rule *r, int count)
 
 static void usbredir_device_disconnect_ack(void *priv)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    SpiceUsbBackendChannel *ch = priv;
+    SPICE_DEBUG("%s ch %p", __FUNCTION__, ch);
+    if (ch->state == USB_CHANNEL_STATE_PARSER && ch->usbredirhost != NULL &&
+        ch->wait_disconnect_ack) {
+        ch->state = USB_CHANNEL_STATE_HOST;
+        SPICE_DEBUG("%s switch to usbredirhost", __FUNCTION__);
+    }
+    ch->wait_disconnect_ack = 0;
 }
 
 static void
 usbredir_hello(void *priv, struct usb_redir_hello_header *hello)
 {
-    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+    SpiceUsbBackendChannel *ch = priv;
+    SpiceUsbBackendDevice *d = ch->attached;
+    SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL;
+    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 };
+    uint8_t *cfg;
+    uint16_t size, offset = 0;
+    SPICE_DEBUG("%s %p %sattached %s", __FUNCTION__, ch,
+        edev ? "" : "not ",  hello ? "" : "(internal)");
+
+    if (!edev) {
+        return;
+    }
+    if (!device_ops(edev)->get_descriptor(edev, LIBUSB_DT_CONFIG, 0, (void **)&cfg, &size)) {
+        return;
+    }
+    while ((offset + 1) < size) {
+        uint8_t len  = cfg[offset];
+        uint8_t type = cfg[offset + 1];
+        if ((offset + len) > size) {
+            break;
+        }
+        if (type == LIBUSB_DT_INTERFACE) {
+            uint32_t i = interface_info.interface_count;
+            uint8_t class, subclass, protocol;
+            class = cfg[offset + 5];
+            subclass = cfg[offset + 6];
+            protocol = cfg[offset + 7];
+            interface_info.interface_class[i] = class;
+            interface_info.interface_subclass[i] = subclass;
+            interface_info.interface_protocol[i] = protocol;
+            interface_info.interface_count++;
+            SPICE_DEBUG("%s IF%d: %d/%d/%d", __FUNCTION__, i, class, subclass, protocol);
+        } else if (type == LIBUSB_DT_ENDPOINT) {
+            uint8_t address = cfg[offset + 2];
+            uint16_t max_packet_size = 0x100 * cfg[offset + 5] + cfg[offset + 4];
+            uint8_t index = address & 0xf;
+            if (address & 0x80) index += 0x10;
+            ep_info.type[index] = cfg[offset + 3] & 0x3;
+            ep_info.max_packet_size[index] = max_packet_size;
+            SPICE_DEBUG("%s EP[%02X]: %d/%d", __FUNCTION__, index, ep_info.type[index], max_packet_size);
+        }
+        offset += len;
+    }
+
+    usbredirparser_send_interface_info(ch->parser, &interface_info);
+    usbredirparser_send_ep_info(ch->parser, &ep_info);
+
+    device_connect.device_class = 0; //d->device_info.class;
+    device_connect.device_subclass = 0; //d->device_info.subclass;
+    device_connect.device_protocol = 0; //d->device_info.protocol;;
+    device_connect.vendor_id = d->device_info.vid;
+    device_connect.product_id = d->device_info.pid;
+    device_connect.device_version_bcd = d->device_info.bcdUSB;
+    device_connect.speed = usb_redir_speed_high;
+    usbredirparser_send_device_connect(ch->parser, &device_connect);
+    usbredir_write_flush_callback(ch);
 }
 
 static void initialize_parser(SpiceUsbBackendChannel *ch)
@@ -900,14 +1176,36 @@ static struct usbredirparser *create_parser(SpiceUsbBackendChannel *ch)
     return parser;
 }
 
-void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data)
+static gboolean attach_edev(SpiceUsbBackendChannel *ch,
+                            SpiceUsbBackendDevice *dev,
+                            GError **error)
 {
-    if (ch->usbredirhost) {
-        SPICE_DEBUG("%s ch %p", __FUNCTION__, ch);
-        usbredirhost_free_write_buffer(ch->usbredirhost, data);
+    if (!dev->edev) {
+        g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+           _("Failed to redirect device %d"), 1);
+        return FALSE;
+    }
+    if (ch->state == USB_CHANNEL_STATE_INITIALIZING) {
+        /*
+            we can't initialize parser until we see hello from usbredir
+            and the parser can't work until it sees the hello response.
+            this is case of autoconnect when emulated device is attached
+            before the channel is up
+        */
+        SPICE_DEBUG("%s waiting until the channel is ready", __FUNCTION__);
+
     } else {
-        SPICE_DEBUG("%s ch %p - NOBODY TO CALL", __FUNCTION__, ch);
+        ch->state = USB_CHANNEL_STATE_PARSER;
     }
+    ch->wait_disconnect_ack = 0;
+    ch->attached = dev;
+    dev->attached_to = ch;
+    device_ops(dev->edev)->attach(dev->edev, ch->parser);
+    if (ch->state == USB_CHANNEL_STATE_PARSER) {
+        /* send device info */
+        usbredir_hello(ch, NULL);
+    }
+    return TRUE;
 }
 
 gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch,
@@ -919,12 +1217,19 @@ gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch,
 
     g_return_val_if_fail(dev != NULL, FALSE);
 
+    if (!dev->libusb_device) {
+        return attach_edev(ch, dev, error);
+    }
+
     // no physical device enabled
     if (ch->usbredirhost == NULL) {
         return FALSE;
     }
 
     libusb_device_handle *handle = NULL;
+    if (ch->state != USB_CHANNEL_STATE_INITIALIZING) {
+        ch->state = USB_CHANNEL_STATE_HOST;
+    }
 
     /*
        Under Windows we need to avoid updating
@@ -958,18 +1263,32 @@ gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch,
 
 void spice_usb_backend_channel_detach(SpiceUsbBackendChannel *ch)
 {
+    SpiceUsbBackendDevice *d = ch->attached;
+    SpiceUsbEmulatedDevice *edev = d ? d->edev : NULL;
     SPICE_DEBUG("%s >> ch %p, was attached %p", __FUNCTION__, ch, ch->attached);
-    if (!ch->attached) {
+    if (!d) {
         SPICE_DEBUG("%s: nothing to detach", __FUNCTION__);
         return;
     }
-    if (ch->usbredirhost) {
+    if (ch->state == USB_CHANNEL_STATE_HOST) {
         /* it will call libusb_close internally */
         usbredirhost_set_device(ch->usbredirhost, NULL);
+    } else {
+        if (edev) {
+            device_ops(edev)->detach(edev);
+        }
+        usbredirparser_send_device_disconnect(ch->parser);
+        usbredir_write_flush_callback(ch);
+        ch->wait_disconnect_ack =
+            usbredirparser_peer_has_cap(ch->parser, usb_redir_cap_device_disconnect_ack);
+        if (!ch->wait_disconnect_ack && ch->usbredirhost != NULL) {
+            ch->state = USB_CHANNEL_STATE_HOST;
+        }
     }
     SPICE_DEBUG("%s ch %p, detach done", __FUNCTION__, ch);
     ch->attached->attached_to = NULL;
     ch->attached = NULL;
+    ch->rejected = 0;
 }
 
 SpiceUsbBackendChannel *
@@ -983,23 +1302,25 @@ spice_usb_backend_channel_new(SpiceUsbBackend *be,
     ch->usbredir_channel = usbredir_channel;
     if (be->libusb_context) {
         ch->backend = be;
-        ch->usbredirhost = usbredirhost_open_full(
-            be->libusb_context,
-            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,
-            spice_util_get_debug() ? usbredirparser_debug : usbredirparser_warning,
-            usbredirhost_fl_write_cb_owns_buffer);
+        ch->usbredirhost =
+            usbredirhost_open_full(be->libusb_context,
+                                   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,
+                                   spice_util_get_debug() ? usbredirparser_debug :
+                                        usbredirparser_warning,
+                                   usbredirhost_fl_write_cb_owns_buffer);
         g_warn_if_fail(ch->usbredirhost != NULL);
-        if (ch->usbredirhost) {
-            usbredirhost_set_buffered_output_size_cb(ch->usbredirhost, usbredir_buffered_output_size_callback);
+        if (ch->usbredirhost != NULL) {
+            usbredirhost_set_buffered_output_size_cb(ch->usbredirhost,
+                                                     usbredir_buffered_output_size_callback);
             // force flush of HELLO packet and creation of parser
             usbredirhost_write_guest_data(ch->usbredirhost);
         }
@@ -1023,9 +1344,11 @@ spice_usb_backend_channel_new(SpiceUsbBackend *be,
 
 void spice_usb_backend_channel_flush_writes(SpiceUsbBackendChannel *ch)
 {
-    SPICE_DEBUG("%s %p, host %p", __FUNCTION__, ch, ch->usbredirhost);
-    if (ch->usbredirhost) {
+    SPICE_DEBUG("%s %p is up", __FUNCTION__, ch);
+    if (ch->state != USB_CHANNEL_STATE_PARSER && ch->usbredirhost != NULL) {
         usbredirhost_write_guest_data(ch->usbredirhost);
+    } else {
+        usbredirparser_do_write(ch->parser);
     }
 }
 
commit 78c5a2e93383bd21a1215dd7123d1e00c14482f4
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Thu Sep 19 16:11:20 2019 +0200

    usb-backend: include usbredirparser
    
    This patch introduces the usage of usbredirparser in
    SpiceUsbBackendChannel.
    
    The focus of this patch is to the code path of real devices. As we
    don't know beforehand if a SpiceUsbBackendChannel will be used by real
    or emulated devices, an instance of usbredirparser must be initialized
    with the usbredirhost's first hello message.
    
    This is a must to the current design on supporting emulated devices.
    This will be extended in the next patch to match remote's usbredirhost
    capabilities and providing support to emulated devices.
    
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Signed-off-by: Victor Toso <victortoso at redhat.com>

diff --git a/src/usb-backend.c b/src/usb-backend.c
index c4d8e20..169f1b0 100644
--- a/src/usb-backend.c
+++ b/src/usb-backend.c
@@ -45,6 +45,7 @@
 #include "usbutil.h"
 
 #define LOUD_DEBUG(x, ...)
+#define USBREDIR_CALLBACK_NOT_IMPLEMENTED() spice_debug("%s not implemented - FIXME", __func__)
 
 struct _SpiceUsbDevice
 {
@@ -82,6 +83,7 @@ struct _SpiceUsbBackend
 struct _SpiceUsbBackendChannel
 {
     struct usbredirhost *usbredirhost;
+    struct usbredirparser *parser;
     uint8_t *read_buf;
     int read_buf_size;
     struct usbredirfilter_rule *rules;
@@ -631,11 +633,48 @@ static void usbredir_log(void *user_data, int level, const char *msg)
     }
 }
 
+static struct usbredirparser *create_parser(SpiceUsbBackendChannel *ch);
+
 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);
+
+    // handle first packet (HELLO) creating parser from capabilities
+    if (SPICE_UNLIKELY(ch->parser == NULL)) {
+        // Here we return 0 from this function to keep the packet in
+        // the queue. The packet will then be sent to the guest.
+        // We are initializing SpiceUsbBackendChannel, the
+        // SpiceUsbredirChannel is not ready to receive packets.
+
+        // we are still initializing the host
+        if (ch->usbredirhost == NULL) {
+            return 0;
+        }
+
+        ch->parser = create_parser(ch);
+        if (!ch->parser) {
+            return 0;
+        }
+
+        /* hello is short header (12) + hello struct (64) */
+        const int hello_size = 12 + sizeof(struct usb_redir_hello_header);
+        g_assert(count >= hello_size + 4);
+        g_assert(SPICE_ALIGNED_CAST(struct usb_redir_header *, data)->type == usb_redir_hello);
+
+        const uint32_t flags =
+            usbredirparser_fl_write_cb_owns_buffer | usbredirparser_fl_usb_host |
+            usbredirparser_fl_no_hello;
+
+        usbredirparser_init(ch->parser,
+                            PACKAGE_STRING,
+                            SPICE_ALIGNED_CAST(uint32_t *, data + hello_size),
+                            (count - hello_size) / sizeof(uint32_t),
+                            flags);
+
+        return 0;
+    }
     res = spice_usbredir_write(ch->usbredir_channel, data, count);
     return res;
 }
@@ -702,6 +741,165 @@ GError *spice_usb_backend_get_error_details(int error_code, gchar *desc)
     return err;
 }
 
+static void
+usbredir_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *h,
+                        uint8_t *data, int data_len)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void
+usbredir_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *h,
+                     uint8_t *data, int data_len)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void usbredir_device_reset(void *priv)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void
+usbredir_interface_info(void *priv, struct usb_redir_interface_info_header *interface_info)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void
+usbredir_interface_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void
+usbredir_set_configuration(void *priv, uint64_t id,
+                           struct usb_redir_set_configuration_header *set_configuration)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void usbredir_get_configuration(void *priv, uint64_t id)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void
+usbredir_set_alt_setting(void *priv, uint64_t id, struct usb_redir_set_alt_setting_header *s)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void
+usbredir_get_alt_setting(void *priv, uint64_t id, struct usb_redir_get_alt_setting_header *s)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void usbredir_cancel_data(void *priv, uint64_t id)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void usbredir_filter_reject(void *priv)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+/* 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);
+
+    free(ch->rules);
+    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)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void
+usbredir_hello(void *priv, struct usb_redir_hello_header *hello)
+{
+    USBREDIR_CALLBACK_NOT_IMPLEMENTED();
+}
+
+static void initialize_parser(SpiceUsbBackendChannel *ch)
+{
+    uint32_t flags, caps[USB_REDIR_CAPS_SIZE] = { 0 };
+
+    g_assert(ch->usbredirhost == NULL);
+
+    flags = usbredirparser_fl_write_cb_owns_buffer | usbredirparser_fl_usb_host;
+
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_filter);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_device_disconnect_ack);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving);
+    usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams);
+
+    usbredirparser_init(ch->parser, PACKAGE_STRING, caps, USB_REDIR_CAPS_SIZE, flags);
+}
+
+/*
+    We can 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
+    - when we receive server's response to HELLO we provide it also to
+      parser to let it synchronize with server's capabilities
+*/
+static struct usbredirparser *create_parser(SpiceUsbBackendChannel *ch)
+{
+    struct usbredirparser *parser = usbredirparser_create();
+
+    g_return_val_if_fail(parser != NULL, NULL);
+
+    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->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->device_disconnect_ack_func = usbredir_device_disconnect_ack;
+    parser->interface_info_func = usbredir_interface_info;
+    parser->ep_info_func = usbredir_interface_ep_info;
+    parser->filter_filter_func = usbredir_filter_filter;
+
+    return parser;
+}
+
 void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data)
 {
     if (ch->usbredirhost) {
@@ -800,11 +998,22 @@ spice_usb_backend_channel_new(SpiceUsbBackend *be,
             spice_util_get_debug() ? usbredirparser_debug : usbredirparser_warning,
             usbredirhost_fl_write_cb_owns_buffer);
         g_warn_if_fail(ch->usbredirhost != NULL);
-    }
-    if (ch->usbredirhost) {
-        usbredirhost_set_buffered_output_size_cb(ch->usbredirhost, usbredir_buffered_output_size_callback);
+        if (ch->usbredirhost) {
+            usbredirhost_set_buffered_output_size_cb(ch->usbredirhost, usbredir_buffered_output_size_callback);
+            // force flush of HELLO packet and creation of parser
+            usbredirhost_write_guest_data(ch->usbredirhost);
+        }
     } else {
-        g_free(ch);
+        // no physical device support, only emulated, create the
+        // parser
+        ch->parser = create_parser(ch);
+        if (ch->parser != NULL) {
+            initialize_parser(ch);
+        }
+    }
+
+    if (!ch->parser) {
+        spice_usb_backend_channel_delete(ch);
         ch = NULL;
     }
 
@@ -829,9 +1038,13 @@ void spice_usb_backend_channel_delete(SpiceUsbBackendChannel *ch)
     if (ch->usbredirhost) {
         usbredirhost_close(ch->usbredirhost);
     }
+    if (ch->parser) {
+        usbredirparser_destroy(ch->parser);
+    }
 
     if (ch->rules) {
-        g_free(ch->rules);
+        /* rules were allocated by usbredirparser */
+        free(ch->rules);
     }
 
     g_free(ch);
commit 6ee21ef76b122bfad2adfe74dbe62ee921cf9512
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Thu Sep 19 16:11:19 2019 +0200

    usb-backend: add safe check on attach()
    
    At this moment, spice_usb_backend_channel_attach() is only related to
    real devices and this call might happen on channel-up event that can
    possibly come before usbredirhost has been created.
    
    Signed-off-by: Victor Toso <victortoso at redhat.com>
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/usb-backend.c b/src/usb-backend.c
index 14429cf..c4d8e20 100644
--- a/src/usb-backend.c
+++ b/src/usb-backend.c
@@ -721,6 +721,11 @@ gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch,
 
     g_return_val_if_fail(dev != NULL, FALSE);
 
+    // no physical device enabled
+    if (ch->usbredirhost == NULL) {
+        return FALSE;
+    }
+
     libusb_device_handle *handle = NULL;
 
     /*
commit 4dd2eb519e27e089d995f386d6fa4b94175221a0
Author: Victor Toso <me at victortoso.com>
Date:   Thu Sep 19 16:11:18 2019 +0200

    usb-backend: add guard and doc to check_filter()
    
    * Add documentation on spice_usb_backend_device_check_filter()
    * Add guard on libusb_device
    * Adds G_GNUC_INTERNAL as this is only called internally in
      usb-device-manager.c
    * Changed the style a bit, still under 100 char in a single line
    
    This is a preparatory patch for extending usb-backend for emulated
    devices.
    
    Signed-off-by: Victor Toso <victortoso at redhat.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/usb-backend.c b/src/usb-backend.c
index a6b5f07..14429cf 100644
--- a/src/usb-backend.c
+++ b/src/usb-backend.c
@@ -566,13 +566,13 @@ void spice_usb_backend_device_unref(SpiceUsbBackendDevice *dev)
     }
 }
 
-int spice_usb_backend_device_check_filter(
-    SpiceUsbBackendDevice *dev,
-    const struct usbredirfilter_rule *rules,
-    int count)
+G_GNUC_INTERNAL
+int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev,
+                                          const struct usbredirfilter_rule *rules,
+                                          int count)
 {
-    return usbredirhost_check_device_filter(
-        rules, count, dev->libusb_device, 0);
+    g_return_val_if_fail(dev->libusb_device != NULL, -EINVAL);
+    return usbredirhost_check_device_filter(rules, count, dev->libusb_device, 0);
 }
 
 static int usbredir_read_callback(void *user_data, uint8_t *data, int count)
diff --git a/src/usb-backend.h b/src/usb-backend.h
index 46b742e..46713c1 100644
--- a/src/usb-backend.h
+++ b/src/usb-backend.h
@@ -70,7 +70,9 @@ void spice_usb_backend_device_unref(SpiceUsbBackendDevice *dev);
 gconstpointer spice_usb_backend_device_get_libdev(const SpiceUsbBackendDevice *dev);
 const UsbDeviceInformation* spice_usb_backend_device_get_info(const SpiceUsbBackendDevice *dev);
 gboolean spice_usb_backend_device_isoch(SpiceUsbBackendDevice *dev);
-/* returns 0 if the device passes the filter */
+
+/* returns 0 if the device passes the filter otherwise returns the error value from
+ * usbredirhost_check_device_filter() such as -EIO or -ENOMEM */
 int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev,
                                           const struct usbredirfilter_rule *rules, int count);
 
commit 9ca9095186f8b060dc2ec9c304235cdf7899533c
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Thu Sep 19 16:11:17 2019 +0200

    usb-backend: no emulated isoch devices
    
    SpiceUsbBackendDevice deals with real and emulated devices but there
    is no plans to implement emulated isochronous devices.
    
    This patch adds check to edev (emulated device) in the guard, fix the
    return value to FALSE instead of plain 0 and return early in case the
    code path is around emulated devices.
    
    This is a preparatory patch to extend emulated devices support.
    
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Signed-off-by: Victor Toso <victortoso at redhat.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/usb-backend.c b/src/usb-backend.c
index 5b52a40..a6b5f07 100644
--- a/src/usb-backend.c
+++ b/src/usb-backend.c
@@ -357,11 +357,17 @@ gboolean spice_usb_backend_device_isoch(SpiceUsbBackendDevice *dev)
     gint i, j, k;
     int rc;
 
+    g_return_val_if_fail(libdev != NULL || dev->edev != NULL, FALSE);
+
+    if (dev->edev != NULL) {
+        /* currently we do not emulate isoch devices */
+        return FALSE;
+    }
+
     if (dev->cached_isochronous_valid) {
         return dev->cached_isochronous;
     }
 
-    g_return_val_if_fail(libdev != NULL, 0);
 
     rc = libusb_get_active_config_descriptor(libdev, &conf_desc);
     if (rc) {
commit 5aec2ddd917c4db0eccfb002e13132f3a85a7693
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Mon Aug 12 12:57:24 2019 +0300

    usb-redir: do not use spice_usb_acl_helper for emulated devices
    
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/channel-usbredir.c b/src/channel-usbredir.c
index 0a711c2..e407bac 100644
--- a/src/channel-usbredir.c
+++ b/src/channel-usbredir.c
@@ -301,7 +301,6 @@ static void spice_usbredir_channel_open_acl_cb(
 }
 #endif
 
-#ifndef USE_POLKIT
 static void
 _open_device_async_cb(GTask *task,
                       gpointer object,
@@ -328,7 +327,6 @@ _open_device_async_cb(GTask *task,
         g_task_return_boolean(task, TRUE);
     }
 }
-#endif
 
 G_GNUC_INTERNAL
 void spice_usbredir_channel_connect_device_async(
@@ -373,21 +371,22 @@ void spice_usbredir_channel_connect_device_async(
     priv->spice_device = g_boxed_copy(spice_usb_device_get_type(),
                                       spice_device);
 #ifdef USE_POLKIT
-    priv->task = task;
-    priv->state  = STATE_WAITING_FOR_ACL_HELPER;
-    priv->acl_helper = spice_usb_acl_helper_new();
-    g_object_set(spice_channel_get_session(SPICE_CHANNEL(channel)),
-                 "inhibit-keyboard-grab", TRUE, NULL);
-    spice_usb_acl_helper_open_acl_async(priv->acl_helper,
-                                        info->bus,
-                                        info->address,
-                                        cancellable,
-                                        spice_usbredir_channel_open_acl_cb,
-                                        channel);
-    return;
-#else
-    g_task_run_in_thread(task, _open_device_async_cb);
+    if (info->bus != BUS_NUMBER_FOR_EMULATED_USB) {
+        priv->task = task;
+        priv->state  = STATE_WAITING_FOR_ACL_HELPER;
+        priv->acl_helper = spice_usb_acl_helper_new();
+        g_object_set(spice_channel_get_session(SPICE_CHANNEL(channel)),
+                    "inhibit-keyboard-grab", TRUE, NULL);
+        spice_usb_acl_helper_open_acl_async(priv->acl_helper,
+                                            info->bus,
+                                            info->address,
+                                            cancellable,
+                                            spice_usbredir_channel_open_acl_cb,
+                                            channel);
+        return;
+    }
 #endif
+    g_task_run_in_thread(task, _open_device_async_cb);
 
 done:
     g_object_unref(task);
commit ddb62340a17a5df4efeabe981d85ae3803837cf1
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Mon Aug 12 12:57:23 2019 +0300

    usb-redir: move implementation of device description to USB backend
    
    For local USB device the USB backend returns the same device
    description as spice-usb-manager did, for emulated devices it
    uses the description provided by device's implementation.
    
    Signed-off-by: Yuri Benditovich <yuri.benditovich at daynix.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/usb-backend.c b/src/usb-backend.c
index cb41f71..5b52a40 100644
--- a/src/usb-backend.c
+++ b/src/usb-backend.c
@@ -42,6 +42,7 @@
 #include "usb-emulation.h"
 #include "channel-usbredir-priv.h"
 #include "spice-channel-priv.h"
+#include "usbutil.h"
 
 #define LOUD_DEBUG(x, ...)
 
@@ -852,6 +853,46 @@ spice_usb_backend_channel_get_guest_filter(SpiceUsbBackendChannel *ch,
     }
 }
 
+gchar *spice_usb_backend_device_get_description(SpiceUsbBackendDevice *dev,
+                                                const gchar *format)
+{
+    guint16 bus, address, vid, pid;
+    gchar *description, *descriptor, *manufacturer = NULL, *product = NULL;
+
+    g_return_val_if_fail(dev != NULL, NULL);
+
+    bus     = dev->device_info.bus;
+    address = dev->device_info.address;
+    vid     = dev->device_info.vid;
+    pid     = dev->device_info.pid;
+
+    if ((vid > 0) && (pid > 0)) {
+        descriptor = g_strdup_printf("[%04x:%04x]", vid, pid);
+    } else {
+        descriptor = g_strdup("");
+    }
+
+    if (dev->libusb_device) {
+        spice_usb_util_get_device_strings(bus, address, vid, pid,
+                                          &manufacturer, &product);
+    } else {
+        product = device_ops(dev->edev)->get_product_description(dev->edev);
+    }
+
+    if (!format) {
+        format = _("%s %s %s at %d-%d");
+    }
+
+    description = g_strdup_printf(format, manufacturer ? manufacturer : "",
+                                  product, descriptor, bus, address);
+
+    g_free(manufacturer);
+    g_free(descriptor);
+    g_free(product);
+
+    return description;
+}
+
 void spice_usb_backend_device_report_change(SpiceUsbBackend *be,
                                             SpiceUsbBackendDevice *dev)
 {
diff --git a/src/usb-backend.h b/src/usb-backend.h
index 83319dc..46b742e 100644
--- a/src/usb-backend.h
+++ b/src/usb-backend.h
@@ -90,5 +90,6 @@ void spice_usb_backend_channel_get_guest_filter(SpiceUsbBackendChannel *ch,
                                                 const struct usbredirfilter_rule  **rules,
                                                 int *count);
 void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data);
+gchar *spice_usb_backend_device_get_description(SpiceUsbBackendDevice *dev, const gchar *format);
 
 G_END_DECLS
diff --git a/src/usb-device-manager.c b/src/usb-device-manager.c
index 17bca09..3a9542a 100644
--- a/src/usb-device-manager.c
+++ b/src/usb-device-manager.c
@@ -32,7 +32,6 @@
 #endif
 
 #include "channel-usbredir-priv.h"
-#include "usbutil.h"
 #endif
 
 #include "spice-session-priv.h"
@@ -1432,35 +1431,9 @@ spice_usb_device_manager_can_redirect_device(SpiceUsbDeviceManager  *self,
 gchar *spice_usb_device_get_description(SpiceUsbDevice *device, const gchar *format)
 {
 #ifdef USE_USBREDIR
-    guint16 bus, address, vid, pid;
-    gchar *description, *descriptor, *manufacturer = NULL, *product = NULL;
-
     g_return_val_if_fail(device != NULL, NULL);
 
-    bus     = spice_usb_device_get_busnum(device);
-    address = spice_usb_device_get_devaddr(device);
-    vid     = spice_usb_device_get_vid(device);
-    pid     = spice_usb_device_get_pid(device);
-
-    if ((vid > 0) && (pid > 0)) {
-        descriptor = g_strdup_printf("[%04x:%04x]", vid, pid);
-    } else {
-        descriptor = g_strdup("");
-    }
-
-    spice_usb_util_get_device_strings(bus, address, vid, pid,
-                                      &manufacturer, &product);
-
-    if (!format)
-        format = _("%s %s %s at %d-%d");
-
-    description = g_strdup_printf(format, manufacturer, product, descriptor, bus, address);
-
-    g_free(manufacturer);
-    g_free(descriptor);
-    g_free(product);
-
-    return description;
+    return spice_usb_backend_device_get_description(device, format);
 #else
     return NULL;
 #endif
commit 822e855887090949955de245d3ff2f51d7e5a28f
Author: Yuri Benditovich <yuri.benditovich at daynix.com>
Date:   Mon Aug 12 12:57:21 2019 +0300

    usb-redir: define interfaces to support emulated devices
    
    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. Implementation module will define
    constructor for specific 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>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/src/meson.build b/src/meson.build
index dac85a7..4d9215c 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 e021d43..cb41f71 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"
 
@@ -46,7 +47,10 @@
 
 struct _SpiceUsbDevice
 {
+    /* Pointer to device. Either real device (libusb_device)
+     * or emulated one (edev) */
     libusb_device *libusb_device;
+    SpiceUsbEmulatedDevice *edev;
     gint ref_count;
     SpiceUsbBackendChannel *attached_to;
     UsbDeviceInformation device_info;
@@ -68,6 +72,10 @@ struct _SpiceUsbBackend
     libusb_device **libusb_device_list;
     gint redirecting;
 #endif
+
+    /* Mask of allocated device, a specific bit set to 1 to indicate that the device at
+     * that address is allocated */
+    uint32_t own_devices_mask;
 };
 
 struct _SpiceUsbBackendChannel
@@ -422,6 +430,8 @@ SpiceUsbBackend *spice_usb_backend_new(GError **error)
         libusb_set_option(be->libusb_context, LIBUSB_OPTION_USE_USBDK);
 #endif
 #endif
+        /* exclude addresses 0 (reserved) and 1 (root hub) */
+        be->own_devices_mask = 3;
     }
     SPICE_DEBUG("%s <<", __FUNCTION__);
     return be;
@@ -538,8 +548,13 @@ 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);
+        }
+        if (dev->edev) {
+            device_ops(dev->edev)->unrealize(dev->edev);
+        }
         g_free(dev);
     }
 }
@@ -837,4 +852,88 @@ spice_usb_backend_channel_get_guest_filter(SpiceUsbBackendChannel *ch,
     }
 }
 
+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);
+}
+
+void spice_usb_backend_device_eject(SpiceUsbBackend *be, SpiceUsbBackendDevice *dev)
+{
+    g_return_if_fail(dev);
+
+    if (dev->edev) {
+        be->own_devices_mask &= ~(1 << dev->device_info.address);
+    }
+    if (be->hotplug_callback) {
+        be->hotplug_callback(be->hotplug_user_data, dev, FALSE);
+    }
+}
+
+gboolean
+spice_usb_backend_create_emulated_device(SpiceUsbBackend *be,
+                                         SpiceUsbEmulatedDeviceCreate create_proc,
+                                         void *create_params,
+                                         GError **err)
+{
+    SpiceUsbEmulatedDevice *edev;
+    SpiceUsbBackendDevice *dev;
+    struct libusb_device_descriptor *desc;
+    uint16_t device_desc_size;
+    uint8_t address = 0;
+
+    if (be->own_devices_mask == 0xffffffff) {
+        g_set_error(err, 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);
+    dev->device_info.bus = BUS_NUMBER_FOR_EMULATED_USB;
+    dev->device_info.address = address;
+    dev->ref_count = 1;
+
+    dev->edev = edev = create_proc(be, dev, create_params, err);
+    if (edev == NULL) {
+        spice_usb_backend_device_unref(dev);
+        return FALSE;
+    }
+
+    if (!device_ops(edev)->get_descriptor(edev, LIBUSB_DT_DEVICE, 0,
+                                          (void **)&desc, &device_desc_size)
+        || device_desc_size != sizeof(*desc)) {
+
+        spice_usb_backend_device_unref(dev);
+        g_set_error(err, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
+                    _("can't create device - internal error"));
+        return FALSE;
+    }
+
+    be->own_devices_mask |= 1 << 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;
+
+    if (be->hotplug_callback) {
+        be->hotplug_callback(be->hotplug_user_data, dev, TRUE);
+    }
+    spice_usb_backend_device_unref(dev);
+
+    return TRUE;
+}
+
 #endif /* USB_REDIR */
diff --git a/src/usb-backend.h b/src/usb-backend.h
index 7558807..83319dc 100644
--- a/src/usb-backend.h
+++ b/src/usb-backend.h
@@ -30,12 +30,15 @@ typedef struct _SpiceUsbBackend SpiceUsbBackend;
 typedef struct _SpiceUsbDevice SpiceUsbBackendDevice;
 typedef struct _SpiceUsbBackendChannel SpiceUsbBackendChannel;
 
+#define BUS_NUMBER_FOR_EMULATED_USB G_MAXUINT16
+
 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;
diff --git a/src/usb-emulation.h b/src/usb-emulation.h
new file mode 100644
index 0000000..ac3d8e0
--- /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/>.
+*/
+
+#pragma once
+
+#include "usbredirparser.h"
+#include "usb-backend.h"
+
+typedef struct SpiceUsbEmulatedDevice SpiceUsbEmulatedDevice;
+typedef SpiceUsbEmulatedDevice*
+(*SpiceUsbEmulatedDeviceCreate)(SpiceUsbBackend *be,
+                                SpiceUsbBackendDevice *parent,
+                                void *create_params,
+                                GError **err);
+
+/*
+    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 (*unrealize)(SpiceUsbEmulatedDevice *device);
+} UsbDeviceOps;
+
+static inline const UsbDeviceOps *device_ops(SpiceUsbEmulatedDevice *dev)
+{
+    return (const UsbDeviceOps *)dev;
+}
+
+gboolean
+spice_usb_backend_create_emulated_device(SpiceUsbBackend *be,
+                                         SpiceUsbEmulatedDeviceCreate create_proc,
+                                         void *create_params,
+                                         GError **err);
+void spice_usb_backend_device_eject(SpiceUsbBackend *be, SpiceUsbBackendDevice *device);
+void spice_usb_backend_device_report_change(SpiceUsbBackend *be, SpiceUsbBackendDevice *device);


More information about the Spice-commits mailing list