This commit contains new source and header files, related to cd-sharing feature. Signed-off-by: Yuri Benditovich <yuri.benditovich@xxxxxxxxxx> --- src/cd-device-linux.c | 137 ++ src/cd-device-win.c | 193 +++ src/cd-device.h | 40 + src/cd-scsi-dev-params.h | 52 + src/cd-scsi.c | 2765 +++++++++++++++++++++++++++++++++++++++++ src/cd-scsi.h | 122 ++ src/cd-usb-bulk-msd.c | 555 +++++++++ src/cd-usb-bulk-msd.h | 134 ++ src/scsi-constants.h | 324 +++++ src/usb-backend-common.c | 2009 ++++++++++++++++++++++++++++++ src/usb-backend.h | 112 ++ src/usb-device-redir-widget.c | 2065 ++++++++++++++++++++++++++++++ 12 files changed, 8508 insertions(+) create mode 100644 src/cd-device-linux.c create mode 100644 src/cd-device-win.c create mode 100644 src/cd-device.h create mode 100644 src/cd-scsi-dev-params.h create mode 100644 src/cd-scsi.c create mode 100644 src/cd-scsi.h create mode 100644 src/cd-usb-bulk-msd.c create mode 100644 src/cd-usb-bulk-msd.h create mode 100644 src/scsi-constants.h create mode 100644 src/usb-backend-common.c create mode 100644 src/usb-backend.h create mode 100644 src/usb-device-redir-widget.c diff --git a/src/cd-device-linux.c b/src/cd-device-linux.c new file mode 100644 index 0000000..c97e574 --- /dev/null +++ b/src/cd-device-linux.c @@ -0,0 +1,137 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* +Copyright (C) 2018 Red Hat, Inc. + +Red Hat Authors: +Yuri Benditovich<ybendito@xxxxxxxxxx> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#include <glib-object.h> + +#ifndef G_OS_WIN32 +#ifdef USE_USBREDIR +#include <inttypes.h> +#include <gio/gio.h> +#include "cd-device.h" +#include "spice-client.h" +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <linux/fs.h> +#include <linux/cdrom.h> + +int cd_device_open_stream(SpiceCdLU *unit, const char *filename) +{ + int error = 0; + unit->device = 0; + + if (!unit->filename && !filename) { + SPICE_DEBUG("%s: file name not provided", __FUNCTION__); + return -1; // TODO + } + 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) { + 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) { + unit->file_object = g_file_new_for_path(unit->filename); + unit->stream = g_file_read(unit->file_object, NULL, NULL); + } + if (!unit->stream) { + SPICE_DEBUG("%s: can't open stream on %s", __FUNCTION__, unit->filename); + g_object_unref(unit->file_object); + unit->file_object = NULL; + error = -1; //TODO + } + } + else { + SPICE_DEBUG("%s: can't open file %s", __FUNCTION__, unit->filename); + error = -1; //TODO + } + + return error; +} + +int cd_device_load(SpiceCdLU *unit, gboolean load) +{ + int error; + if (!unit->device || !unit->filename) { + return -1; //TODO + } + int fd = open(unit->filename, O_RDONLY | O_NONBLOCK); + if (fd > 0) { + 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); + } else { + error = -1; //TODO + } + return error; +} + +int cd_device_check(SpiceCdLU *unit) +{ + int error; + if (!unit->device || !unit->filename) { + return -1; //TODO + } + int fd = open(unit->filename, O_RDONLY | O_NONBLOCK); + if (fd > 0) { + 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); + } + else { + error = -1; //TODO + } + return error; +} + +#endif +#endif diff --git a/src/cd-device-win.c b/src/cd-device-win.c new file mode 100644 index 0000000..de7f3f1 --- /dev/null +++ b/src/cd-device-win.c @@ -0,0 +1,193 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* +Copyright (C) 2018 Red Hat, Inc. + +Red Hat Authors: +Yuri Benditovich<ybendito@xxxxxxxxxx> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" +#include <glib-object.h> + +#ifdef G_OS_WIN32 +#ifdef USE_USBREDIR + +#include <inttypes.h> +#include <gio/gio.h> +#include <windows.h> +#include <ntddcdrm.h> +#include <ntddmmc.h> +#include "cd-device.h" +#include "spice-client.h" + +static gboolean is_device_name(const char *filename) +{ + gboolean b = strlen(filename) == 2 && filename[1] == ':'; + return b; +} + +static HANDLE open_file(const char *filename) +{ + HANDLE h = CreateFileA( + filename, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + 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); +} + +int cd_device_open_stream(SpiceCdLU *unit, const char *filename) +{ + int error = 0; + HANDLE h; + unit->device = 0; + if (!unit->filename && !filename) { + SPICE_DEBUG("%s: unnamed file", __FUNCTION__); + return -1; // TODO + } + 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) { + LARGE_INTEGER size = { 0 }; + if (!GetFileSizeEx(h, &size)) { + uint64_t buffer[256]; + unit->device = check_device(h); + SPICE_DEBUG("%s: CD device %srecognized on %s", + __FUNCTION__, unit->device ? "" : "NOT ", unit->filename); + uint32_t 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) { + unit->file_object = g_file_new_for_path(unit->filename); + unit->stream = g_file_read(unit->file_object, NULL, NULL); + } + if (!unit->stream) { + SPICE_DEBUG("%s: can't open stream on %s", __FUNCTION__, unit->filename); + g_object_unref(unit->file_object); + unit->file_object = NULL; + error = -1; //TODO + } + } else { + SPICE_DEBUG("%s: can't open file %s", __FUNCTION__, unit->filename); + error = -1; //TODO + } + return error; +} + +int cd_device_load(SpiceCdLU *unit, gboolean load) +{ + int error = 0; + HANDLE h; + if (!unit->device || !unit->filename) { + return -1; //TODO + } + 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; //TODO + } else { + SPICE_DEBUG("%s: device %s [%s]", + __FUNCTION__, load ? "loaded" : "ejected", unit->filename); + } + CloseHandle(h); + } + return error; +} + +int cd_device_check(SpiceCdLU *unit) +{ + int error = 0; + CDROM_DISK_DATA data; + HANDLE h; + if (!unit->device || !unit->filename) { + return -1; //TODO + } + 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; //TODO + } + CloseHandle(h); + } + return error; +} + +#endif +#endif diff --git a/src/cd-device.h b/src/cd-device.h new file mode 100644 index 0000000..050c7a1 --- /dev/null +++ b/src/cd-device.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* +Copyright (C) 2018 Red Hat, Inc. + +Red Hat Authors: +Yuri Benditovich<ybendito@xxxxxxxxxx> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CD_DEVICE_H__ +#define __CD_DEVICE_H__ + +typedef struct _SpiceCdLU +{ + char *filename; + GFile *file_object; + GFileInputStream *stream; + uint64_t size; + uint32_t blockSize; + uint32_t loaded : 1; + uint32_t device : 1; +} SpiceCdLU; + +int cd_device_open_stream(SpiceCdLU *unit, const char *filename); +int cd_device_load(SpiceCdLU *unit, gboolean load); +int cd_device_check(SpiceCdLU *unit); + +#endif diff --git a/src/cd-scsi-dev-params.h b/src/cd-scsi-dev-params.h new file mode 100644 index 0000000..cddce0a --- /dev/null +++ b/src/cd-scsi-dev-params.h @@ -0,0 +1,52 @@ +/* -*- 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _CD_SCSI_DEV_PARAMS_H_ +#define _CD_SCSI_DEV_PARAMS_H_ + +#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; + +#endif /* _CD_SCSI_DEV_PARAMS_H_ */ diff --git a/src/cd-scsi.c b/src/cd-scsi.c new file mode 100644 index 0000000..86baf97 --- /dev/null +++ b/src/cd-scsi.c @@ -0,0 +1,2765 @@ +/* -*- 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" +#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 MAX_LUNS 32 + +struct _CdScsiTarget; /* forward declaration */ +typedef struct _CdScsiTarget CdScsiTarget; + +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 num_luns; + uint32_t max_luns; + CdScsiLU units[MAX_LUNS]; +}; + +static gboolean cmd_names_initialized = FALSE; +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:%" G_GUINT32_FORMAT + " 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 */ + +static void cd_scsi_cmd_names_init(void) +{ + uint32_t opcode; + + if (cmd_names_initialized) { + return; + } + + 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"; + + cmd_names_initialized = TRUE; +} + +void *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:%" G_GUINT32_FORMAT, 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; + + cd_scsi_cmd_names_init(); + + return (void *)st; +} + +void cd_scsi_target_free(void *scsi_target) +{ + cd_scsi_target_reset(scsi_target); + g_free(scsi_target); +} + +/* SCSI Device */ + +static inline gboolean cd_scsi_target_lun_legal(CdScsiTarget *st, uint32_t lun) +{ + return (lun < st->max_luns) ? TRUE : FALSE; +} + +static inline gboolean cd_scsi_target_lun_realized(CdScsiTarget *st, uint32_t lun) +{ + return (st->num_luns == 0 || !st->units[lun].realized) ? FALSE : TRUE; +} + +int cd_scsi_dev_realize(void *scsi_target, uint32_t lun, + const CdScsiDeviceParameters *dev_params) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + CdScsiLU *dev; + + if (!cd_scsi_target_lun_legal(st, lun)) { + SPICE_ERROR("Realize, illegal lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + if (cd_scsi_target_lun_realized(st, lun)) { + SPICE_ERROR("Realize, already realized lun:%" G_GUINT32_FORMAT, 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); + + st->num_luns ++; + + SPICE_DEBUG("Realize lun:%" G_GUINT32_FORMAT " bs:%" G_GUINT32_FORMAT + " 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 */ + dev->stream = NULL; + dev->size = 0; + dev->block_size = 0; + dev->num_blocks = 0; +} + +int cd_scsi_dev_lock(void *scsi_target, uint32_t lun, gboolean lock) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + CdScsiLU *dev; + + if (!cd_scsi_target_lun_legal(st, lun)) { + SPICE_ERROR("Lock, illegal lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + if (!cd_scsi_target_lun_realized(st, lun)) { + SPICE_ERROR("Lock, unrealized lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + dev = &st->units[lun]; + dev->prevent_media_removal = lock; + SPICE_DEBUG("lun:%" G_GUINT32_FORMAT "%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 = 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; + } else { + dev->media_event = CD_MEDIA_EVENT_MEDIA_REMOVAL; + cd_scsi_lu_media_reset(dev); + } + dev->loaded = TRUE; +} + +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(void *scsi_target, uint32_t lun, + const CdScsiMediaParameters *media_params) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + CdScsiLU *dev; + + if (!cd_scsi_target_lun_legal(st, lun)) { + SPICE_ERROR("Load, illegal lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + if (!cd_scsi_target_lun_realized(st, lun)) { + SPICE_ERROR("Load, unrealized lun:%" G_GUINT32_FORMAT, 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:%" G_GUINT32_FORMAT " size:%" G_GUINT64_FORMAT + " blk_sz:%" G_GUINT32_FORMAT " num_blocks:%" G_GUINT32_FORMAT, + lun, dev->size, dev->block_size, dev->num_blocks); + return 0; +} + +int cd_scsi_dev_get_info(void *scsi_target, uint32_t lun, CdScsiDeviceInfo *lun_info) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + CdScsiLU *dev; + + if (!cd_scsi_target_lun_legal(st, lun)) { + SPICE_ERROR("Load, illegal lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + if (!cd_scsi_target_lun_realized(st, lun)) { + SPICE_ERROR("Load, unrealized lun:%" G_GUINT32_FORMAT, 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(void *scsi_target, uint32_t lun) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + CdScsiLU *dev; + + if (!cd_scsi_target_lun_legal(st, lun)) { + SPICE_ERROR("Unload, illegal lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + if (!cd_scsi_target_lun_realized(st, lun)) { + SPICE_ERROR("Unload, unrealized lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + dev = &st->units[lun]; + if (!dev->loaded) { + SPICE_ERROR("Unload, lun:%" G_GUINT32_FORMAT " not loaded yet", lun); + return 0; + } + if (dev->prevent_media_removal) { + SPICE_ERROR("Unload, lun:%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT, lun); + return 0; +} + +int cd_scsi_dev_unrealize(void *scsi_target, uint32_t lun) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + CdScsiLU *dev; + + if (!cd_scsi_target_lun_legal(st, lun)) { + SPICE_ERROR("Unrealize, illegal lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + if (!cd_scsi_target_lun_realized(st, lun)) { + SPICE_ERROR("Unrealize, absent lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + dev = &st->units[lun]; + + if (dev->vendor != NULL) { + free(dev->vendor); + dev->vendor = NULL; + } + if (dev->product != NULL) { + free(dev->product); + dev->product = NULL; + } + if (dev->version != NULL) { + free(dev->version); + dev->version = NULL; + } + if (dev->serial != NULL) { + free(dev->serial); + dev->serial = NULL; + } + + dev->loaded = FALSE; + dev->realized = FALSE; + dev->power_cond = CD_SCSI_POWER_STOPPED; + + st->num_luns --; + + SPICE_DEBUG("Unrealize lun:%" G_GUINT32_FORMAT, lun); + return 0; +} + +int cd_scsi_dev_reset(void *scsi_target, uint32_t lun) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + CdScsiLU *dev; + + if (!cd_scsi_target_lun_legal(st, lun)) { + SPICE_ERROR("Device reset, illegal lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + if (!cd_scsi_target_lun_realized(st, lun)) { + SPICE_ERROR("Device reset, absent lun:%" G_GUINT32_FORMAT, lun); + return -1; + } + dev = &st->units[lun]; + + 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:%" G_GUINT32_FORMAT, 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(void *scsi_target) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + + 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(scsi_target, 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 int scsi_cdb_length(uint8_t *cdb) +{ + 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 = -1; + } + return cdb_len; +} + +static uint64_t scsi_cdb_lba(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(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 = (req->req_len < sizeof(dev->fixed_sense)) ? + req->req_len : sizeof(dev->fixed_sense); + + if (dev->short_sense.key != NO_SENSE) { + SPICE_DEBUG("%s, lun:%" G_GUINT32_FORMAT " 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 num_luns = st->num_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; + + if (req->cdb[0] == 0x01) { + /* only well known logical units */ + num_luns = 0; + } + + out_buf[0] = (uint8_t)(num_luns >> 24); + out_buf[1] = (uint8_t)(num_luns >> 16); + out_buf[2] = (uint8_t)(num_luns >> 8); + out_buf[3] = (uint8_t)(num_luns); + memset(&out_buf[4], 0, 4); + + if (num_luns > 0) { + for (lun = 0; lun < num_luns; lun++) { + if (st->units[lun].realized) { + out_buf[buflen++] = (uint8_t)(num_luns >> 24); + out_buf[buflen++] = (uint8_t)(num_luns >> 16); + out_buf[buflen++] = (uint8_t)(num_luns >> 8); + out_buf[buflen++] = (uint8_t)(num_luns); + memset(&out_buf[buflen], 0, 4); + buflen += 4; + } + } + } + + 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 = (req->req_len < resp_len) ? req->req_len : resp_len; + + SPICE_DEBUG("inquiry_vpd, unsupported lun:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT " 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 = (req->req_len < resp_len) ? req->req_len : resp_len; + + SPICE_DEBUG("inquiry_standard, unsupported lun:%" G_GUINT32_FORMAT " perif_qual:0x%x " + "inquiry_len: %" G_GUINT32_FORMAT " 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 = (req->req_len < resp_len) ? req->req_len : resp_len; + + SPICE_DEBUG("inquiry_standard, lun:%" G_GUINT32_FORMAT + " inquiry_len: %" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT + " last_blk: %" G_GUINT32_FORMAT " blk_sz: %" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " 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 = (req->req_len < RDI_STANDARD_LEN) ? 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:%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT + " addr_type LBA: %" G_GUINT32_FORMAT + " invalid LBA: %" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " addr_type track: %" G_GUINT32_FORMAT + " invalid track: %" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " addr_type session: %" G_GUINT32_FORMAT + " invalid session: %" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + "invalid addr_type: %" G_GUINT32_FORMAT + " addr_num: %" G_GUINT32_FORMAT, + 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 = (req->req_len < TIB_LEN) ? 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:%" G_GUINT32_FORMAT + "addr_type: %" G_GUINT32_FORMAT " addr_num: %" G_GUINT32_FORMAT, + 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 = (req->req_len < READ_TOC_RESP_LEN) ? 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:%" G_GUINT32_FORMAT " 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] & 0xf; + + 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:%" G_GUINT32_FORMAT + " 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 = (req->req_len < resp_len) ? req->req_len : resp_len; + + SPICE_DEBUG("mode_sense_10, lun:%" G_GUINT32_FORMAT + " long_lba %d, dbd %d, page %d, sub_page %d, pc %d; " + "resp_len %" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " pf:%" G_GUINT32_FORMAT " sp:%" G_GUINT32_FORMAT + " list_len:%" G_GUINT32_FORMAT " exceeds data_len:%" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " pf:%" G_GUINT32_FORMAT " sp:%" G_GUINT32_FORMAT + " list_len:%" G_GUINT32_FORMAT " data_len:%" G_GUINT32_FORMAT + " mode_len:%" G_GUINT32_FORMAT " medium:%" G_GUINT32_FORMAT + " dev_param:%" G_GUINT32_FORMAT " blk_desc_len:%" G_GUINT32_FORMAT + " num_blocks:%" G_GUINT32_FORMAT " block_len:%" G_GUINT32_FORMAT + " page_num:%" G_GUINT32_FORMAT " page_len:%" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " pf:%" G_GUINT32_FORMAT " sp:%" G_GUINT32_FORMAT + " list_len:%" G_GUINT32_FORMAT " exceeds data_len:%" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " pf:%" G_GUINT32_FORMAT " sp:%" G_GUINT32_FORMAT + " list_len:%" G_GUINT32_FORMAT " data_len:%" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " invalid rt:%" G_GUINT32_FORMAT " start_f:%" G_GUINT32_FORMAT, + 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 = (req->req_len < resp_len) ? req->req_len : resp_len; + + SPICE_DEBUG("get_configuration, lun:%" G_GUINT32_FORMAT + " rt:%" G_GUINT32_FORMAT " start_f:%" G_GUINT32_FORMAT + " resp_len:%" G_GUINT32_FORMAT, + 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:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT + " imm:%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT + " imm:%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT + " imm:%" G_GUINT32_FORMAT " 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 = (req->req_len < resp_len) ? 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:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT + " 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:0x%" G_GUINT32_FORMAT + " immed:%" G_GUINT32_FORMAT " param_len:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT, 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:%" G_GUINT32_FORMAT, 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:0x%" G_GUINT32_FORMAT + " 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:0x%" G_GUINT32_FORMAT + " 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:0x%" G_GUINT32_FORMAT " 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:0x%" G_GUINT32_FORMAT " stopped", req->lun); + } else { /* start the unit */ + dev->power_cond = CD_SCSI_POWER_ACTIVE; + SPICE_DEBUG("start_stop_unit, lun:0x%" G_GUINT32_FORMAT " started", req->lun); + + if (load_eject) { /* load medium */ + SPICE_DEBUG("start_stop_unit, lun:0x%" G_GUINT32_FORMAT " 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:0x%" G_GUINT32_FORMAT " 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:0x%" G_GUINT32_FORMAT " 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:0x%" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT + " 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:%" G_GUINT32_FORMAT + " performance type:0x00 data_type:0x%x" + " except:0x%x write:0x%x tolerance:0x%x" + " max_num:%" G_GUINT32_FORMAT, + req->lun, data_type, except, write, + tolerance, max_num_descr); + + if (write) { + SPICE_DEBUG("get_performance, lun:%" G_GUINT32_FORMAT + " 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 = (req->req_len < resp_len) ? 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:%" G_GUINT32_FORMAT + " unsupported type:0x%x" + " data_type:0x%x max_num:%" G_GUINT32_FORMAT, + 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 = (req->req_len < resp_len) ? req->req_len : resp_len; + + SPICE_DEBUG("mechanism_status, lun:%" G_GUINT32_FORMAT, 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: %" G_GUINT32_FORMAT + " 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: %" G_GUINT32_FORMAT + " finished: %d bytes_read: %" G_GUINT64_FORMAT + " req: %" G_GUINT64_FORMAT, + req->lun, finished, (uint64_t)bytes_read, req->req_len); + + req->in_len = (bytes_read <= req->req_len) ? 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:%" G_GUINT32_FORMAT + " 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: %" G_GUINT32_FORMAT " 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: %" G_GUINT32_FORMAT " 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 = req->count * dev->block_size; + + cd_scsi_read_async_start(dev, req); +} + +void cd_scsi_dev_request_submit(void *scsi_target, CdScsiRequest *req) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + 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: %" G_GUINT32_FORMAT " 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:%" G_GUINT32_FORMAT, 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:%" G_GUINT32_FORMAT, 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: %" G_GUINT32_FORMAT + " op: 0x%02x %s, state: %s status: %" G_GUINT32_FORMAT " 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(void *scsi_target, CdScsiRequest *req) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + + if (st->cur_req == req) { + if (req->req_state == SCSI_REQ_RUNNING) { + SPICE_DEBUG("request_cancel: lun: %" G_GUINT32_FORMAT + " 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(void *scsi_target, CdScsiRequest *req) +{ + CdScsiTarget *st = (CdScsiTarget *)scsi_target; + + 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..b1d0ce2 --- /dev/null +++ b/src/cd-scsi.h @@ -0,0 +1,122 @@ +/* -*- 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CD_SCSI_H__ +#define __CD_SCSI_H__ + +#include "cd-scsi-dev-params.h" +#include "cd-usb-bulk-msd.h" +#include "scsi-constants.h" + +#define FIXED_SENSE_CURRENT 0x70 +#define FIXED_SENSE_LEN 18 + +#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 +#define SCSI_SENSE_BUF_SIZE 64 + +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 tag; + 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 */ + +void *cd_scsi_target_alloc(void *target_user_data, uint32_t max_luns); /* to be used in callbacks */ +void cd_scsi_target_free(void *scsi_target); + +int cd_scsi_dev_realize(void *scsi_target, uint32_t lun, const CdScsiDeviceParameters *dev_params); +int cd_scsi_dev_unrealize(void *scsi_target, uint32_t lun); + +int cd_scsi_dev_lock(void *scsi_target, uint32_t lun, gboolean lock); +int cd_scsi_dev_load(void *scsi_target, uint32_t lun, const CdScsiMediaParameters *media_params); +int cd_scsi_dev_get_info(void *scsi_target, uint32_t lun, CdScsiDeviceInfo *lun_info); +int cd_scsi_dev_unload(void *scsi_target, uint32_t lun); + +void cd_scsi_dev_request_submit(void *scsi_target, CdScsiRequest *request); +void cd_scsi_dev_request_cancel(void *scsi_target, CdScsiRequest *request); +void cd_scsi_dev_request_release(void *scsi_target, CdScsiRequest *request); + +int cd_scsi_dev_reset(void *scsi_target, uint32_t lun); + +int cd_scsi_target_reset(void *scsi_target); + +/* Callbacks */ + +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); + +#endif /* __CD_SCSI_H__ */ diff --git a/src/cd-usb-bulk-msd.c b/src/cd-usb-bulk-msd.c new file mode 100644 index 0000000..be0fee3 --- /dev/null +++ b/src/cd-usb-bulk-msd.c @@ -0,0 +1,555 @@ +/* -*- 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@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" +#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; + +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 */ +}; + +struct __attribute__((__packed__)) UsbCdCSW { + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t 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_tag; + 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; + void *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; +} + +void *cd_usb_bulk_msd_alloc(void *usb_user_data, uint32_t max_luns) +{ + UsbCdBulkMsdDevice *cd; + + cd = g_malloc0(sizeof(*cd)); + + 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); + 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:%" G_GUINT32_FORMAT, max_luns); + return cd; +} + +int cd_usb_bulk_msd_realize(void *device, uint32_t lun, + const CdScsiDeviceParameters *dev_params) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + 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:%" G_GUINT32_FORMAT, lun); + return rc; + } + + if (cd->state == USB_CD_STATE_INIT) { + 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:%" G_GUINT32_FORMAT, lun); + return 0; +} + +int cd_usb_bulk_msd_lock(void *device, uint32_t lun, gboolean lock) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + int rc; + + rc = cd_scsi_dev_lock(cd->scsi_target, lun, lock); + if (rc != 0) { + SPICE_ERROR("Failed to lock lun:%" G_GUINT32_FORMAT, lun); + return rc; + } + + SPICE_DEBUG("Lock OK lun:%" G_GUINT32_FORMAT, lun); + return 0; +} + +int cd_usb_bulk_msd_load(void *device, uint32_t lun, + const CdScsiMediaParameters *media_params) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + int rc; + + rc = cd_scsi_dev_load(cd->scsi_target, lun, media_params); + if (rc != 0) { + SPICE_ERROR("Failed to load lun:%" G_GUINT32_FORMAT, lun); + return rc; + } + + SPICE_DEBUG("Load OK lun:%" G_GUINT32_FORMAT, lun); + return 0; +} + +int cd_usb_bulk_msd_get_info(void *device, uint32_t lun, CdScsiDeviceInfo *lun_info) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + int rc; + + rc = cd_scsi_dev_get_info(cd->scsi_target, lun, lun_info); + if (rc != 0) { + SPICE_ERROR("Failed to get info lun:%" G_GUINT32_FORMAT, lun); + return rc; + } + + return 0; +} + +int cd_usb_bulk_msd_unload(void *device, uint32_t lun) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + int rc; + + rc = cd_scsi_dev_unload(cd->scsi_target, lun); + if (rc != 0) { + SPICE_ERROR("Failed to unload lun:%" G_GUINT32_FORMAT, lun); + return rc; + } + + SPICE_DEBUG("Unload OK lun:%" G_GUINT32_FORMAT, lun); + return 0; +} + +int cd_usb_bulk_msd_unrealize(void *device, uint32_t lun) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + int rc; + + rc = cd_scsi_dev_unrealize(cd->scsi_target, lun); + if (rc != 0) { + SPICE_ERROR("Unrealize lun:%" G_GUINT32_FORMAT, lun); + return rc; + } + + SPICE_DEBUG("Unrealize lun:%" G_GUINT32_FORMAT, lun); + return 0; +} + +void cd_usb_bulk_msd_free(void *device) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + + cd_scsi_target_free(cd->scsi_target); + g_free(cd); + + SPICE_DEBUG("Free"); +} + +int cd_usb_bulk_msd_reset(void *device) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + + 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 != 31) { + SPICE_ERROR("CMD: Bad CBW size:%" G_GUINT32_FORMAT, cbw_len); + return -1; + } + if (le32toh(cbw->sig) != 0x43425355) { /* MSD command signature */ + SPICE_ERROR("CMD: Bad CBW signature:%08x", le32toh(cbw->sig)); + return -1; + } + + usb_req->lun = cbw->lun; + usb_req->usb_tag = le32toh(cbw->tag); + 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 = ((uint32_t)cbw->cmd_len) & 0x1F; + g_assert(scsi_req->cdb_len <= sizeof(scsi_req->cdb)); + memcpy(scsi_req->cdb, cbw->cmd, scsi_req->cdb_len); + + scsi_req->tag = usb_req->usb_tag; + scsi_req->lun = usb_req->lun; + + SPICE_DEBUG("CMD lun:%" G_GUINT32_FORMAT " tag:0x%x flags:%08x " + "cdb_len:%" G_GUINT32_FORMAT " req_len:%" G_GUINT32_FORMAT, + usb_req->lun, usb_req->usb_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 = (avail_len <= max_len) ? avail_len : max_len; + + SPICE_DEBUG("Data-in cmd tag 0x%x, remains %" G_GUINT32_FORMAT + ", requested %" G_GUINT32_FORMAT ", send %" G_GUINT32_FORMAT, + 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 satisfiled */ + 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(void *device, uint32_t max_len) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + UsbCdBulkMsdRequest *usb_req = &cd->usb_req; + CdScsiRequest *scsi_req = &usb_req->scsi_req; + + SPICE_DEBUG("msd_read, state: %s, len %" G_GUINT32_FORMAT, + 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 %" G_GUINT32_FORMAT + " saved len %" G_GUINT32_FORMAT, + 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 %" G_GUINT32_FORMAT + " saved len %" G_GUINT32_FORMAT, + 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 %" G_GUINT32_FORMAT, + 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(void *device) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + 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(void *device, uint8_t *buf_out, uint32_t buf_out_len) +{ + UsbCdBulkMsdDevice *cd = (UsbCdBulkMsdDevice *)device; + + 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 %" G_GUINT32_FORMAT, + 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: %" G_GUINT32_FORMAT, + 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..1098a87 --- /dev/null +++ b/src/cd-usb-bulk-msd.h @@ -0,0 +1,134 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2018 Red Hat, Inc. + + Red Hat Authors: + Alexander Nezhinsky<anezhins@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CD_USB_BULK_MSD_H__ +#define __CD_USB_BULK_MSD_H__ + +G_BEGIN_DECLS + +#include <gio/gio.h> + +#include "cd-scsi-dev-params.h" + +typedef enum _CdUsbBulkStatus +{ + BULK_STATUS_GOOD = 0, + BULK_STATUS_ERROR, + BULK_STATUS_CANCELED, + BULK_STATUS_STALL, +} CdUsbBulkStatus; + +/* 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 */ +void *cd_usb_bulk_msd_alloc(void *user_data, uint32_t max_lun); + +/* free device descriptor */ +void cd_usb_bulk_msd_free(void *device); + +/* configure a new Logical Unit to be represented by the device + returns: error code +*/ +int cd_usb_bulk_msd_realize(void *device, uint32_t lun, + const CdScsiDeviceParameters *dev_params); + +/* lock the device, prevent unloading + returns: error code +*/ +int cd_usb_bulk_msd_lock(void *device, uint32_t lun, gboolean lock); + +/* load new media, if already loaded, simulate media change + returns: error code +*/ +int cd_usb_bulk_msd_load(void *device, uint32_t lun, + const CdScsiMediaParameters *media_params); + +/* query unit parameters and status + returns: error code +*/ +int cd_usb_bulk_msd_get_info(void *device, uint32_t lun, + CdScsiDeviceInfo *lun_info); + +/* unload the media + returns: error code +*/ +int cd_usb_bulk_msd_unload(void *device, uint32_t lun); + +/* detach a Logical Unit + returns: error code +*/ +int cd_usb_bulk_msd_unrealize(void *device, uint32_t lun); + +/* reset the device instance; cancel all IO ops, reset state + returns: error code +*/ +int cd_usb_bulk_msd_reset(void *device); + + +/* perform a write data bulk transfer + data_len - length of available data to write + returns: error code +*/ +int cd_usb_bulk_msd_write(void *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(void *device, uint32_t max_len); + +/* cancels pending read data bulk transfer + returns: error code +*/ +int cd_usb_bulk_msd_cancel_read(void *device); + +G_END_DECLS + +#endif /* __CD_USB_BULK_MSD_H__ */ diff --git a/src/scsi-constants.h b/src/scsi-constants.h new file mode 100644 index 0000000..31da750 --- /dev/null +++ b/src/scsi-constants.h @@ -0,0 +1,324 @@ +/* -*- 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/>. +*/ + +#ifndef __SCSI_CONSTANTS_H__ +#define __SCSI_CONSTANTS_H__ + +/* + * 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 + +#endif diff --git a/src/usb-backend-common.c b/src/usb-backend-common.c new file mode 100644 index 0000000..523ab65 --- /dev/null +++ b/src/usb-backend-common.c @@ -0,0 +1,2009 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2018 Red Hat, Inc. + + Red Hat Authors: + Yuri Benditovich<ybendito@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" + +#ifdef USE_USBREDIR + +#include <glib-object.h> +#include <inttypes.h> +#include <gio/gio.h> +#include <errno.h> +#include <libusb.h> +#include <string.h> +#include <fcntl.h> +#include "usbredirhost.h" +#include "usbredirparser.h" +#include "spice-util.h" +#include "usb-backend.h" +#include "cd-usb-bulk-msd.h" +#include "cd-device.h" +#if defined(G_OS_WIN32) +#include <windows.h> +#include "win-usb-dev.h" +#else +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <linux/fs.h> +#endif + +//#define LOUD_DEBUG SPICE_DEBUG +#define LOUD_DEBUG(x, ...) + +//#define INTERCEPT_LOG +//#define INTERCEPT_LOG2FILE +#ifdef INTERCEPT_LOG2FILE +static FILE *fLog; +#endif + +#define MAX_LUN_PER_DEVICE 1 + +#if MAX_LUN_PER_DEVICE > 1 +#define MAX_OWN_DEVICES 4 +#else +#define MAX_OWN_DEVICES 8 +#endif + +#define OWN_BUS_NUM 0xff +#define USB2_BCD 0x200 +// TODO: communicate usage of this VID/PID +#define CD_DEV_VID 0x2b23 +#define CD_DEV_PID 0xCDCD +#define CD_DEV_CLASS 8 +#define CD_DEV_SUBCLASS 6 +#define CD_DEV_PROTOCOL 0x50 +#define CD_DEV_BLOCK_SIZE 0x200 +#define CD_DEV_MAX_SIZE 737280000 +#define DVD_DEV_BLOCK_SIZE 0x800 +#define MAX_BULK_IN_REQUESTS 64 + +static void *g_mutex; + +struct _SpiceUsbBackendDevice +{ + union + { + void *libusb_device; + void *msc; + } d; + uint32_t isLibUsb : 1; + uint32_t configured : 1; + int refCount; + void *mutex; + SpiceUsbBackendChannel *attached_to; + UsbDeviceInformation device_info; + SpiceCdLU units[MAX_LUN_PER_DEVICE]; +}; + +static struct OwnUsbDevices +{ + uint32_t active_devices; + SpiceUsbBackendDevice devices[MAX_OWN_DEVICES]; +} own_devices; + +struct _SpiceUsbBackend +{ + libusb_context *libusbContext; + usb_hot_plug_callback hp_callback; + void *hp_user_data; + libusb_hotplug_callback_handle hp_handle; + void *dev_change_user_data; + backend_device_change_callback dev_change_callback; + uint32_t suppressed : 1; +}; + +/* backend object for device change notification */ +static SpiceUsbBackend *notify_backend; + +struct BufferedBulkRead +{ + struct usb_redir_bulk_packet_header hout; + uint64_t id; +}; + +struct _SpiceUsbBackendChannel +{ + struct usbredirhost *usbredirhost; + struct usbredirhost *hiddenhost; + struct usbredirparser *parser; + struct usbredirparser *hiddenparser; + uint8_t *read_buf; + uint8_t *hello; + int read_buf_size; + int hello_size; + struct usbredirfilter_rule *rules; + int rules_count; + uint32_t host_caps; + uint32_t hello_done_host : 1; + uint32_t hello_done_parser : 1; + uint32_t hello_sent : 1; + uint32_t rejected : 1; + SpiceUsbBackendDevice *attached; + SpiceUsbBackendChannelInitData data; + uint32_t num_reads; + struct BufferedBulkRead read_bulk[MAX_BULK_IN_REQUESTS]; +}; + +static const char *spice_usbutil_libusb_strerror(enum libusb_error error_code) +{ + switch (error_code) { + case LIBUSB_SUCCESS: + return "Success"; + case LIBUSB_ERROR_IO: + return "Input/output error"; + case LIBUSB_ERROR_INVALID_PARAM: + return "Invalid parameter"; + case LIBUSB_ERROR_ACCESS: + return "Access denied (insufficient permissions)"; + case LIBUSB_ERROR_NO_DEVICE: + return "No such device (it may have been disconnected)"; + case LIBUSB_ERROR_NOT_FOUND: + return "Entity not found"; + case LIBUSB_ERROR_BUSY: + return "Resource busy"; + case LIBUSB_ERROR_TIMEOUT: + return "Operation timed out"; + case LIBUSB_ERROR_OVERFLOW: + return "Overflow"; + case LIBUSB_ERROR_PIPE: + return "Pipe error"; + case LIBUSB_ERROR_INTERRUPTED: + return "System call interrupted (perhaps due to signal)"; + case LIBUSB_ERROR_NO_MEM: + return "Insufficient memory"; + case LIBUSB_ERROR_NOT_SUPPORTED: + return "Operation not supported or unimplemented on this platform"; + case LIBUSB_ERROR_OTHER: + return "Other error"; + } + return "Unknown error"; +} + +// lock functions for usbredirhost and usbredirparser +static void *usbredir_alloc_lock(void) { + GMutex *mutex; + + mutex = g_new0(GMutex, 1); + g_mutex_init(mutex); + + return mutex; +} + +static void usbredir_free_lock(void *user_data) { + GMutex *mutex = user_data; + + g_mutex_clear(mutex); + g_free(mutex); +} + +static void usbredir_lock_lock(void *user_data) { + GMutex *mutex = user_data; + + g_mutex_lock(mutex); +} + +static void usbredir_unlock_lock(void *user_data) { + GMutex *mutex = user_data; + + g_mutex_unlock(mutex); +} + +static void indicate_lun_change(SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev) +{ + if (be->dev_change_user_data && be->dev_change_callback) { + be->dev_change_callback(be->dev_change_user_data, bdev); + } +} + +static void indicate_device_presence(SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev, gboolean present) +{ +#ifdef G_OS_WIN32 + spice_usb_backend_indicate_dev_change(); +#else + if (be->hp_callback) { + be->hp_callback(be->hp_user_data, bdev, present); + } +#endif +} + +static gboolean fill_usb_info(SpiceUsbBackendDevice *bdev) +{ + UsbDeviceInformation *pi = &bdev->device_info; + + if (bdev->isLibUsb) + { + struct libusb_device_descriptor desc; + libusb_device *libdev = bdev->d.libusb_device; + int res = libusb_get_device_descriptor(libdev, &desc); + pi->bus = libusb_get_bus_number(libdev); + pi->address = libusb_get_device_address(libdev); + if (res < 0) { + g_warning("cannot get device descriptor for (%p) %d.%d", + libdev, pi->bus, pi->address); + return FALSE; + } + pi->vid = desc.idVendor; + pi->pid = desc.idProduct; + pi->class = desc.bDeviceClass; + pi->subclass = desc.bDeviceSubClass; + pi->protocol = desc.bDeviceProtocol; + pi->isochronous = 0; + pi->max_luns = 0; + } + return TRUE; +} + +static gboolean open_stream(SpiceCdLU *unit, const char *filename) +{ + gboolean b = FALSE; + b = cd_device_open_stream(unit, filename) == 0; + return b; +} + +static void close_stream(SpiceCdLU *unit) +{ + if (unit->stream) { + g_object_unref(unit->stream); + unit->stream = NULL; + } + if (unit->file_object) { + g_object_unref(unit->file_object); + unit->file_object = NULL; + } +} + +/* Note that this function must be re-entrant safe, as it can get called +from both the main thread as well as from the usb event handling thread */ +static void usbredir_write_flush_callback(void *user_data) +{ + SpiceUsbBackendChannel *ch = user_data; + gboolean b = ch->data.is_channel_ready(ch->data.user_data); + if (b) { + if (ch->usbredirhost) { + SPICE_DEBUG("%s ch %p -> usbredirhost", __FUNCTION__, ch); + usbredirhost_write_guest_data(ch->usbredirhost); + } + else if (ch->parser) { + SPICE_DEBUG("%s ch %p -> usbredirparser", __FUNCTION__, ch); + usbredirparser_do_write(ch->parser); + } + else { + b = FALSE; + } + } + + if (!b) { + SPICE_DEBUG("%s ch %p (not ready)", __FUNCTION__, ch); + } +} + +void cd_usb_bulk_msd_read_complete(void *user_data, + uint8_t *data, uint32_t length, CdUsbBulkStatus status) +{ + SpiceUsbBackendDevice *d = (SpiceUsbBackendDevice *)user_data; + SpiceUsbBackendChannel *ch = d->attached_to; + if (ch && ch->attached == d && ch->parser) { + int nread; + uint32_t offset = 0; + + for (nread = 0; nread <ch->num_reads; nread++) { + uint32_t max_len; + max_len = (ch->read_bulk[nread].hout.length_high << 16) | + ch->read_bulk[nread].hout.length; + if (max_len > length) { + max_len = length; + ch->read_bulk[nread].hout.length = length; + ch->read_bulk[nread].hout.length_high = length >> 16; + } + + switch (status) { + case BULK_STATUS_GOOD: + ch->read_bulk[nread].hout.status = usb_redir_success; + break; + case BULK_STATUS_CANCELED: + ch->read_bulk[nread].hout.status = usb_redir_cancelled; + break; + case BULK_STATUS_ERROR: + ch->read_bulk[nread].hout.status = usb_redir_ioerror; + break; + case BULK_STATUS_STALL: + default: + ch->read_bulk[nread].hout.status = usb_redir_stall; + break; + } + + SPICE_DEBUG("%s: responding %" PRIu64 " with len %u out of %u, status %d", + __FUNCTION__, ch->read_bulk[nread].id, max_len, + length, ch->read_bulk[nread].hout.status); + usbredirparser_send_bulk_packet(ch->parser, ch->read_bulk[nread].id, + &ch->read_bulk[nread].hout, max_len ? (data + offset) : NULL, max_len); + + offset += max_len; + length -= max_len; + } + ch->num_reads = 0; + usbredir_write_flush_callback(ch); + + if (length) { + SPICE_DEBUG("%s: ERROR: %u bytes were not reported!", __FUNCTION__, length); + } + + } else { + SPICE_DEBUG("%s: broken device<->channel relationship!", __FUNCTION__); + } +} + +/* device reset completion callback */ +void cd_usb_bulk_msd_reset_complete(void *user_data, int status) +{ + // SpiceUsbBackendDevice *d = (SpiceUsbBackendDevice *)user_data; +} + +static gboolean load_lun(SpiceUsbBackendDevice *d, int unit, gboolean load) +{ + gboolean b = TRUE; + if (load && d->units[unit].device) { + // there is one possible problem in case our backend is the + // local CD device and it is ejected + cd_device_load(&d->units[unit], TRUE); + close_stream(&d->units[unit]); + if (cd_device_check(&d->units[unit]) || !open_stream(&d->units[unit], NULL)) { + return FALSE; + } + } + + if (load) { + CdScsiMediaParameters media_params = { 0 }; + + media_params.stream = d->units[unit].stream; + media_params.size = d->units[unit].size; + media_params.block_size = d->units[unit].blockSize; + if (media_params.block_size == CD_DEV_BLOCK_SIZE && + media_params.size % DVD_DEV_BLOCK_SIZE == 0) { + media_params.block_size = DVD_DEV_BLOCK_SIZE; + } + SPICE_DEBUG("%s: loading %s, size %" PRIu64 ", block %u", + __FUNCTION__, d->units[unit].filename, media_params.size, media_params.block_size); + + b = !cd_usb_bulk_msd_load(d->d.msc, unit, &media_params); + + d->units[unit].loaded = !!b; + + } else { + SPICE_DEBUG("%s: unloading %s", __FUNCTION__, d->units[unit].filename); + cd_usb_bulk_msd_unload(d->d.msc, unit); + d->units[unit].loaded = FALSE; +// I do not see why we need to eject the physical CD-ROM when the eject +// on VM requested. On Linux this may require root privilege, as this +// affects those who opened files on CD-ROM. I suppose this is user's +// responsibility to keep CD door close when it shares it with VM +#if DO_CD_DEVICE_EJECT + cd_device_load(&d->units[unit], FALSE); +#endif + } + return b; +} + +static gboolean lock_lun(SpiceUsbBackendDevice *d, int unit, gboolean lock) +{ + SPICE_DEBUG("%s: locking %s", __FUNCTION__, d->units[unit].filename); + return !cd_usb_bulk_msd_lock(d->d.msc, unit, lock); +} + +// called when a change happens on SCSI layer +void cd_usb_bulk_msd_lun_changed(void *user_data, uint32_t lun) +{ + SpiceUsbBackendDevice *d = (SpiceUsbBackendDevice *)user_data; + CdScsiDeviceInfo cd_info; + + if (!cd_usb_bulk_msd_get_info(d->d.msc, lun, &cd_info)) { + // load or unload command received from SCSI + if (d->units[lun].loaded != cd_info.loaded) { + if (!load_lun(d, lun, cd_info.loaded && cd_info.loaded)) { + SPICE_DEBUG("%s: load failed, unloading unit", __FUNCTION__); + cd_usb_bulk_msd_unload(d->d.msc, lun); + } + } + } + + if (notify_backend) { + indicate_lun_change(notify_backend, d); + } +} + +static void process_default_parameters(CdScsiDeviceParameters *params, + const SpiceUsbDeviceLunInfo *info, int unit) +{ + if (!params->product || *params->product == 0) { + const char *name = strrchr(info->file_path, '\\'); + if (!name) name = strrchr(info->file_path, '/'); + params->product = name ? (name + 1) : info->file_path; + } + if (!params->version || *params->version == 0) { + static char s[8]; + g_snprintf(s, sizeof(s), "%d", unit); + params->version = s; + } + if (!params->vendor || *params->vendor == 0) { + params->vendor = "SPICE"; + } +} + +static gboolean activate_device(SpiceUsbBackendDevice *d, const SpiceUsbDeviceLunInfo *info, int unit) +{ + gboolean b = FALSE; + CdScsiDeviceParameters dev_params = { 0 }; + dev_params.vendor = info->vendor; + dev_params.product = info->product; + dev_params.version = info->revision; + + process_default_parameters(&dev_params, info, unit); + + if (!d->d.msc) { + d->d.msc = cd_usb_bulk_msd_alloc(d, MAX_LUN_PER_DEVICE); + if (!d->d.msc) { + return FALSE; + } + } + d->units[unit].blockSize = CD_DEV_BLOCK_SIZE; + b = !cd_usb_bulk_msd_realize(d->d.msc, unit, &dev_params); + if (b) { + b = open_stream(&d->units[unit], info->file_path); + if (b && info->loaded) { + b = load_lun(d, unit, TRUE); + if (b && info->locked) { + b = lock_lun(d, unit, TRUE); + } + } + if (!b) { + close_stream(&d->units[unit]); + cd_usb_bulk_msd_unrealize(d->d.msc, unit); + } + } + return b; +} + +static gboolean stop_device(SpiceUsbBackendDevice *d, int unit) +{ + int j; + gboolean empty = TRUE; + + cd_usb_bulk_msd_unrealize(d->d.msc, unit); + if (d->units[unit].filename) { + g_free(d->units[unit].filename); + d->units[unit].filename = NULL; + } + close_stream(&d->units[unit]); + + for (j = 0; empty && j < MAX_LUN_PER_DEVICE; ++j) { + if (d->units[j].filename) { + empty = FALSE; + } + } + if (empty) { + cd_usb_bulk_msd_free(d->d.msc); + d->d.msc = NULL; + d->configured = 0; + } + return empty; +} + +gboolean spice_usb_backend_add_cd_lun(SpiceUsbBackend *be, const SpiceUsbDeviceLunInfo *info) +{ + int i; + gboolean b = FALSE; + + if (be->suppressed) { + SPICE_DEBUG("%s: CD sharing is suppressed", __FUNCTION__); + return FALSE; + } + + for (i = 0; !b && i < MAX_OWN_DEVICES; i++) { + if ((1 << i) & ~own_devices.active_devices) { /* inactive usb device */ + SPICE_DEBUG("%s: add file %s to device %d (activate now) as lun 0", + __FUNCTION__, info->file_path, i); + + b = activate_device(&own_devices.devices[i], info, 0); + if (b) { + own_devices.active_devices |= 1 << i; + indicate_device_presence(be, &own_devices.devices[i], TRUE); + } + } else if (!own_devices.devices[i].attached_to) { + /* active unattached device, add as the next lun */ + int j; + for (j = 0; j < MAX_LUN_PER_DEVICE; j++) { + if (!own_devices.devices[i].units[j].stream) { + SPICE_DEBUG("%s: add file %s to device %d (already active) as lun %d", + __FUNCTION__, info->file_path, i, j); + + b = activate_device(&own_devices.devices[i], info, j); + if (!b) { + SPICE_DEBUG("%s: failed to add file %s to device %d (already active) as lun %d", + __FUNCTION__, info->file_path, i, j); + b = TRUE; /* exit outer loop */ + } else { + indicate_lun_change(be, &own_devices.devices[i]); + } + break; + } + } + } + } + if (!b) { + SPICE_DEBUG("can not create device %s", info->file_path); + } + return b; +} + +static gboolean check_device(SpiceUsbBackendDevice *bdev, guint lun, guint* index) +{ + int i; + + if (lun >= MAX_LUN_PER_DEVICE) { + return FALSE; + } + + for (i = 0; i < MAX_OWN_DEVICES; i++) { + if ((1 << i) & own_devices.active_devices) { /* active usb device */ + if (&own_devices.devices[i] == bdev) { + if (index) { + *index = i; + } + return TRUE; + } + } + } + + return FALSE; +} + +gboolean spice_usb_backend_remove_cd_lun(SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev, guint lun) +{ + char *name; + guint index; + if (!check_device(bdev, lun, &index)) { + return FALSE; + } + + name = bdev->units[lun].filename; + SPICE_DEBUG("%s: unshare %s, unit %u:%u", __FUNCTION__, name, index, lun); + if (stop_device(bdev, lun)) { + /* usb device does not have any active unit, + so we remove it */ + own_devices.active_devices &= ~(1 << index); + indicate_device_presence(be, bdev, FALSE); + } + else { + /* the device still contains active LUN(s) */ + indicate_lun_change(be, bdev); + } + return TRUE; +} + +uint32_t spice_usb_backend_get_cd_luns_bitmask(SpiceUsbBackendDevice *bdev) +{ + int i, value = 0; + + if (!check_device(bdev, 0, NULL)) { + return FALSE; + } + + for (i = 0; i < MAX_LUN_PER_DEVICE; i++) { + char *name = bdev->units[i].filename; + if (name) { + value |= 1 << i; + } + } + + return value; +} + +gboolean spice_usb_backend_get_cd_lun_info(SpiceUsbBackendDevice *bdev, + guint lun, SpiceUsbDeviceLunInfo *info) +{ + if (!check_device(bdev, lun, NULL)) { + return FALSE; + } + + if (bdev->units[lun].filename) { + CdScsiDeviceInfo cd_info; + if (!cd_usb_bulk_msd_get_info(bdev->d.msc, lun, &cd_info)) { + info->started = cd_info.started; + info->loaded = bdev->units[lun].loaded; + info->locked = cd_info.locked; + info->file_path = bdev->units[lun].filename; + info->vendor = cd_info.parameters.vendor; + info->product = cd_info.parameters.product; + info->revision = cd_info.parameters.version; + return TRUE; + } + } + + return FALSE; +} + +gboolean spice_usb_backend_load_cd_lun( + SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, + guint lun, + gboolean load) +{ + if (!check_device(bdev, lun, NULL)) { + return FALSE; + } + + if (bdev->units[lun].filename) { + gboolean b = load_lun(bdev, lun, load); + SPICE_DEBUG("%s: %sload %s", __FUNCTION__, + load ? "" : "un", b ? "succeeded" : "failed"); + if (b) { + indicate_lun_change(be, bdev); + } + return b; + } + + return FALSE; +} + +gboolean spice_usb_backend_lock_cd_lun( + SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, + guint lun, + gboolean lock) +{ + if (!check_device(bdev, lun, NULL)) { + return FALSE; + } + + if (bdev->units[lun].filename) { + gboolean b = lock_lun(bdev, lun, lock); + SPICE_DEBUG("%s: %slock %s", __FUNCTION__, + lock ? "" : "un", b ? "succeeded" : "failed"); + if (b) { + indicate_lun_change(be, bdev); + } + return b; + } + + return FALSE; +} + +gboolean spice_usb_backend_change_cd_lun( + SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, + guint lun, const SpiceUsbDeviceLunInfo *lun_info) +{ + gboolean b = FALSE; + + char *filename; + GFile *file_object; + GFileInputStream *stream; + + if (!check_device(bdev, lun, NULL)) { + return b; + } + + // if the CD is loaded, we need to unload it first + if (bdev->units[lun].loaded) { + SPICE_DEBUG("%s: the unit is loaded, unload it first", __FUNCTION__); + return b; + } + + // keep stream-related data (stream, file object) + filename = bdev->units[lun].filename; + bdev->units[lun].filename = NULL; + + stream = bdev->units[lun].stream; + bdev->units[lun].stream = NULL; + + file_object = bdev->units[lun].file_object; + bdev->units[lun].file_object = NULL; + + // now we can start the LUN with new parameters + b = open_stream(&bdev->units[lun], lun_info->file_path); + if (b) { + b = load_lun(bdev, lun, TRUE); + } + else { + close_stream(&bdev->units[lun]); + } + + if (!b) { + SPICE_DEBUG("%s: failed", __FUNCTION__); + // restore the state of unloaded unit + bdev->units[lun].filename = filename; + bdev->units[lun].stream = stream; + bdev->units[lun].file_object = file_object; + } + else { + SPICE_DEBUG("%s: succeeded", __FUNCTION__); + indicate_lun_change(be, bdev); + } + + return b; +} + +static void initialize_own_devices(void) +{ + int i; + // addresses 0 and 1 excluded as they are treated as + // not suitable for redirection + for (i = 0; i < MAX_OWN_DEVICES; i++) { + own_devices.devices[i].mutex = g_mutex; + own_devices.devices[i].device_info.bus = OWN_BUS_NUM; + own_devices.devices[i].device_info.address = i + 2; + own_devices.devices[i].device_info.vid = CD_DEV_VID; + own_devices.devices[i].device_info.pid = CD_DEV_PID; + own_devices.devices[i].device_info.class = 0; + own_devices.devices[i].device_info.subclass = 0; + own_devices.devices[i].device_info.protocol = 0; + own_devices.devices[i].device_info.max_luns = MAX_LUN_PER_DEVICE; + } +} + +#ifdef INTERCEPT_LOG +static void log_handler( + const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + GString *log_msg; + log_msg = g_string_new(NULL); + if (log_msg) + { + gchar *timestamp; + GThread *th = g_thread_self(); + GDateTime *current_time = g_date_time_new_now_local(); + gint micros = g_date_time_get_microsecond(current_time); + timestamp = g_date_time_format(current_time, "%H:%M:%S"); + g_string_append_printf(log_msg, "[%p][%s.%03d]", th, timestamp, micros / 1000); + g_date_time_unref(current_time); + g_free(timestamp); + g_string_append(log_msg, message); +#ifdef INTERCEPT_LOG2FILE + g_string_append(log_msg, "\n"); + fwrite(log_msg->str, 1, strlen(log_msg->str), fLog); +#else + g_log_default_handler(log_domain, log_level, log_msg->str, NULL); +#endif + g_string_free(log_msg, TRUE); + } +} +#endif + +static void configure_log(void) +{ +#ifdef INTERCEPT_LOG2FILE + fLog = fopen("remote-viewer.log", "w+t"); +#endif +#ifdef INTERCEPT_LOG + g_log_set_default_handler(log_handler, NULL); +#endif +} + +SpiceUsbBackend *spice_usb_backend_initialize(void) +{ + SpiceUsbBackend *be; + SPICE_DEBUG("%s >>", __FUNCTION__); + if (!g_mutex) { + g_mutex = usbredir_alloc_lock(); + initialize_own_devices(); + configure_log(); + } + be = (SpiceUsbBackend *)g_new0(SpiceUsbBackend, 1); + if (be) { +#ifndef USE_CD_SHARING + be->suppressed = TRUE; +#endif + int rc; + rc = libusb_init(&be->libusbContext); + if (rc < 0) { + const char *desc = spice_usbutil_libusb_strerror(rc); + g_warning("Error initializing LIBUSB support: %s [%i]", desc, rc); + } + } + SPICE_DEBUG("%s <<", __FUNCTION__); + return be; +} + +gboolean spice_usb_backend_handle_events(SpiceUsbBackend *be) +{ + SPICE_DEBUG("%s >>", __FUNCTION__); + gboolean b = TRUE; + if (be->libusbContext) { + SPICE_DEBUG("%s >> libusb", __FUNCTION__); + int res = libusb_handle_events(be->libusbContext); + if (res && res != LIBUSB_ERROR_INTERRUPTED) { + const char *desc = spice_usbutil_libusb_strerror(res); + g_warning("Error handling USB events: %s [%i]", desc, res); + b = FALSE; + } + SPICE_DEBUG("%s << libusb %d", __FUNCTION__, res); + } + else { + b = TRUE; + g_usleep(1000000); + } + SPICE_DEBUG("%s <<", __FUNCTION__); + return b; +} + +static int LIBUSB_CALL hotplug_callback(libusb_context *ctx, + libusb_device *device, + libusb_hotplug_event event, + void *user_data) +{ + SpiceUsbBackend *be = (SpiceUsbBackend *)user_data; + if (be->hp_callback) { + SpiceUsbBackendDevice *d; + gboolean val = event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED; + d = g_new0(SpiceUsbBackendDevice, 1); + if (d) { + d->isLibUsb = 1; + d->refCount = 1; + d->mutex = g_mutex; + d->d.libusb_device = device; + if (fill_usb_info(d)) { + SPICE_DEBUG("created dev %p, usblib dev %p", d, device); + be->hp_callback(be->hp_user_data, d, val); + } else { + g_free(d); + } + } + } + return 0; +} + +gboolean spice_usb_backend_handle_hotplug( + SpiceUsbBackend *be, + void *user_data, + usb_hot_plug_callback proc) +{ + int rc; + if (!proc) { + if (be->hp_handle) { + libusb_hotplug_deregister_callback(be->libusbContext, be->hp_handle); + be->hp_handle = 0; + } + be->hp_callback = proc; + return TRUE; + } + + be->hp_callback = proc; + be->hp_user_data = user_data; + if (!be->libusbContext) { + // it is acceptable if libusb is not available at all + return TRUE; + } + rc = libusb_hotplug_register_callback(be->libusbContext, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, + hotplug_callback, be, &be->hp_handle); + if (rc != LIBUSB_SUCCESS) { + const char *desc = spice_usbutil_libusb_strerror(rc); + g_warning("Error initializing USB hotplug support: %s [%i]", desc, rc); + be->hp_callback = NULL; + return FALSE; + } + return TRUE; +} + +void spice_usb_backend_set_device_change_callback( + SpiceUsbBackend *be, void *user_data, backend_device_change_callback proc) +{ + be->dev_change_user_data = user_data; + be->dev_change_callback = proc; + if (!notify_backend) { + notify_backend = be; + } +} + +void spice_usb_backend_finalize(SpiceUsbBackend *be) +{ + SPICE_DEBUG("%s >>", __FUNCTION__); + if (be->libusbContext) { + libusb_exit(be->libusbContext); + } + if (be == notify_backend) { + notify_backend = NULL; + } + g_free(be); + SPICE_DEBUG("%s <<", __FUNCTION__); +} + +SpiceUsbBackendDevice **spice_usb_backend_get_device_list(SpiceUsbBackend *be) +{ + LOUD_DEBUG("%s >>", __FUNCTION__); + libusb_device **devlist = NULL, **dev; + SpiceUsbBackendDevice *d, **list; + + int n = 0, i, index; + + if (be->libusbContext) { + libusb_get_device_list(be->libusbContext, &devlist); + } + + // add all the libusb device that not present in our list + for (dev = devlist; dev && *dev; dev++) { + n++; + } + + list = g_new0(SpiceUsbBackendDevice*, n + MAX_OWN_DEVICES); + if (!list) { + libusb_free_device_list(devlist, 1); + return NULL; + } + + for (i = 0; i < MAX_OWN_DEVICES; ++i) { + if (own_devices.active_devices & (1 << i)) { + n++; + } + } + + index = 0; + + for (dev = devlist; dev && *dev; dev++) { + d = g_new0(SpiceUsbBackendDevice, 1); + if (d) { + d->isLibUsb = 1; + d->refCount = 1; + d->mutex = g_mutex; + d->d.libusb_device = *dev; + if (index >= n || !fill_usb_info(d)) { + g_free(d); + libusb_unref_device(*dev); + } + else { + SPICE_DEBUG("created dev %p, usblib dev %p", d, *dev); + list[index++] = d; + } + } + } + + usbredir_lock_lock(g_mutex); + + for (i = 0; i < MAX_OWN_DEVICES; ++i) { + d = &own_devices.devices[i]; + if ((own_devices.active_devices & (1 << i)) && index < n) { + list[index++] = d; + d->refCount++; + SPICE_DEBUG("found own %p, address %d", d, d->device_info.address); + } + } + usbredir_unlock_lock(g_mutex); + + if (devlist) { + libusb_free_device_list(devlist, 0); + } + + LOUD_DEBUG("%s <<", __FUNCTION__); + return list; +} + +gboolean spice_usb_backend_device_is_hub(SpiceUsbBackendDevice *dev) +{ + return dev->device_info.class == LIBUSB_CLASS_HUB; +} + +static uint8_t is_libusb_isochronous(libusb_device *libdev) +{ + struct libusb_config_descriptor *conf_desc; + uint8_t isoc_found = FALSE; + gint i, j, k; + + if (!libdev) { + SPICE_DEBUG("%s - unexpected libdev = 0", __FUNCTION__); + return 0; + } + + if (libusb_get_active_config_descriptor(libdev, &conf_desc) != 0) { + SPICE_DEBUG("%s - no active configuration for libdev %p", __FUNCTION__, libdev); + return 0; + } + + for (i = 0; !isoc_found && i < conf_desc->bNumInterfaces; i++) { + for (j = 0; !isoc_found && j < conf_desc->interface[i].num_altsetting; j++) { + for (k = 0; !isoc_found && k < conf_desc->interface[i].altsetting[j].bNumEndpoints;k++) { + gint attributes = conf_desc->interface[i].altsetting[j].endpoint[k].bmAttributes; + gint type = attributes & LIBUSB_TRANSFER_TYPE_MASK; + if (type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + isoc_found = TRUE; + } + } + } + + libusb_free_config_descriptor(conf_desc); + return isoc_found; +} + +const UsbDeviceInformation* spice_usb_backend_device_get_info(SpiceUsbBackendDevice *dev) +{ + dev->device_info.isochronous = dev->isLibUsb ? is_libusb_isochronous(dev->d.libusb_device) : 0; + return &dev->device_info; +} + +gboolean spice_usb_backend_devices_same( + SpiceUsbBackendDevice *dev1, + SpiceUsbBackendDevice *dev2) +{ + if (dev1->isLibUsb != dev2->isLibUsb) { + return FALSE; + } + if (dev1->isLibUsb) { + return dev1->d.libusb_device == dev2->d.libusb_device; + } + // assuming CD redir devices are static + return dev1 == dev2; +} + +gconstpointer spice_usb_backend_device_get_libdev(SpiceUsbBackendDevice *dev) +{ + if (dev->isLibUsb) { + return dev->d.libusb_device; + } + return NULL; +} + +void spice_usb_backend_free_device_list(SpiceUsbBackendDevice **devlist) +{ + LOUD_DEBUG("%s >>", __FUNCTION__); + SpiceUsbBackendDevice **dev; + for (dev = devlist; *dev; dev++) { + SpiceUsbBackendDevice *d = *dev; + spice_usb_backend_device_release(d); + } + g_free(devlist); + LOUD_DEBUG("%s <<", __FUNCTION__); +} + +void spice_usb_backend_device_acquire(SpiceUsbBackendDevice *dev) +{ + void *mutex = dev->mutex; + LOUD_DEBUG("%s >> %p", __FUNCTION__, dev); + usbredir_lock_lock(mutex); + if (dev->isLibUsb) { + libusb_ref_device(dev->d.libusb_device); + } + dev->refCount++; + usbredir_unlock_lock(mutex); +} + +void spice_usb_backend_device_release(SpiceUsbBackendDevice *dev) +{ + void *mutex = dev->mutex; + LOUD_DEBUG("%s >> %p(%d)", __FUNCTION__, dev, dev->refCount); + usbredir_lock_lock(mutex); + if (dev->isLibUsb) { + libusb_unref_device(dev->d.libusb_device); + dev->refCount--; + if (dev->refCount == 0) { + LOUD_DEBUG("%s freeing %p (libusb %p)", __FUNCTION__, dev, dev->d.libusb_device); + g_free(dev); + } + } + else { + dev->refCount--; + } + usbredir_unlock_lock(mutex); + LOUD_DEBUG("%s <<", __FUNCTION__); +} + +gboolean spice_usb_backend_device_need_thread(SpiceUsbBackendDevice *dev) +{ + gboolean b = dev->isLibUsb != 0; + SPICE_DEBUG("%s << %d", __FUNCTION__, b); + return b; +} + +int spice_usb_backend_device_check_filter( + SpiceUsbBackendDevice *dev, + const struct usbredirfilter_rule *rules, + int count) +{ + if (dev->isLibUsb) { + return usbredirhost_check_device_filter( + rules, count, dev->d.libusb_device, 0); + } else { + uint8_t cls, subcls, proto; + cls = CD_DEV_CLASS; + subcls = CD_DEV_SUBCLASS; + proto = CD_DEV_PROTOCOL; + return usbredirfilter_check(rules, count, + dev->device_info.class, + dev->device_info.subclass, + dev->device_info.protocol, + &cls, &subcls, &proto, 1, dev->device_info.vid, + dev->device_info.pid, USB2_BCD, 0); + } +} + +static int usbredir_read_callback(void *user_data, uint8_t *data, int count) +{ + SpiceUsbBackendChannel *ch = user_data; + + count = MIN(ch->read_buf_size, count); + + if (count != 0) { + memcpy(data, ch->read_buf, count); + } + + ch->read_buf_size -= count; + if (ch->read_buf_size) { + ch->read_buf += count; + } + else { + ch->read_buf = NULL; + } + SPICE_DEBUG("%s ch %p, %d bytes", __FUNCTION__, ch, count); + + return count; +} + +static void usbredir_log(void *user_data, int level, const char *msg) +{ + SpiceUsbBackendChannel *ch = (SpiceUsbBackendChannel *)user_data; + + switch (level) { + case usbredirparser_error: + g_critical("%s", msg); + ch->data.log(ch->data.user_data, msg, TRUE); + break; + case usbredirparser_warning: + g_warning("%s", msg); + ch->data.log(ch->data.user_data, msg, TRUE); + break; + default: + ch->data.log(ch->data.user_data, msg, FALSE); + break; + } +} + +static int usbredir_write_callback(void *user_data, uint8_t *data, int count) +{ + SpiceUsbBackendChannel *ch = user_data; + int res; + SPICE_DEBUG("%s ch %p, %d bytes", __FUNCTION__, ch, count); + if (!ch->hello_sent) { + ch->hello_sent = 1; + if (count == 80) { + memcpy(&ch->host_caps, data + 76, 4); + SPICE_DEBUG("%s ch %p, sending first hello, caps %08X", + __FUNCTION__, ch, ch->host_caps); + } + } + res = ch->data.write_callback(ch->data.user_data, data, count); + return res; +} + +#if USBREDIR_VERSION >= 0x000701 +static uint64_t usbredir_buffered_output_size_callback(void *user_data) +{ + SpiceUsbBackendChannel *ch = user_data; + return ch->data.get_queue_size(ch->data.user_data); +} +#endif + +int spice_usb_backend_provide_read_data(SpiceUsbBackendChannel *ch, uint8_t *data, int count) +{ + int res = 0; + if (!ch->read_buf) { + typedef int(*readproc_t)(void *); + readproc_t fn = NULL; + void *param; + ch->read_buf = data; + ch->read_buf_size = count; + if (!ch->hello) { + ch->hello = g_malloc(count); + memcpy(ch->hello, data, count); + ch->hello_size = count; + if (ch->usbredirhost) { + ch->hello_done_host = 1; + } + if (ch->parser) { + ch->hello_done_parser = 1; + } + } + if (ch->usbredirhost) { + fn = (readproc_t)usbredirhost_read_guest_data; + param = ch->usbredirhost; + } + if (ch->parser) { + fn = (readproc_t)usbredirparser_do_read; + param = ch->parser; + } + res = fn ? fn(param) : USB_REDIR_ERROR_IO; + switch (res) + { + case usbredirhost_read_io_error: + res = USB_REDIR_ERROR_IO; + break; + case usbredirhost_read_parse_error: + res = USB_REDIR_ERROR_READ_PARSE; + break; + case usbredirhost_read_device_rejected: + res = USB_REDIR_ERROR_DEV_REJECTED; + break; + case usbredirhost_read_device_lost: + res = USB_REDIR_ERROR_DEV_LOST; + break; + } + SPICE_DEBUG("%s ch %p, %d bytes, res %d", __FUNCTION__, ch, count, res); + + } else { + res = USB_REDIR_ERROR_READ_PARSE; + SPICE_DEBUG("%s ch %p, %d bytes, already has data", __FUNCTION__, ch, count); + } + if (ch->rejected) { + ch->rejected = 0; + res = USB_REDIR_ERROR_DEV_REJECTED; + } + return res; +} + +void spice_usb_backend_return_write_data(SpiceUsbBackendChannel *ch, void *data) +{ + typedef void(*retdata)(void *, void *); + retdata fn = NULL; + void *param; + if (ch->usbredirhost) { + fn = (retdata)usbredirhost_free_write_buffer; + param = ch->usbredirhost; + } + if (ch->parser) { + fn = (retdata)usbredirhost_free_write_buffer; + param = ch->parser; + } + if (fn) { + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); + fn(param, data); + } else { + SPICE_DEBUG("%s ch %p - NOBODY TO CALL", __FUNCTION__, ch); + } +} + +#if 0 +struct usbredirparser { + /* app private data passed into all callbacks as the priv argument */ + void *priv; + /* non packet callbacks */ + usbredirparser_log log_func; + usbredirparser_read read_func; + usbredirparser_write write_func; + /* usb-redir-protocol v0.3 control packet complete callbacks */ + usbredirparser_device_connect device_connect_func; + usbredirparser_device_disconnect device_disconnect_func; + usbredirparser_reset reset_func; + usbredirparser_interface_info interface_info_func; + usbredirparser_ep_info ep_info_func; + usbredirparser_set_configuration set_configuration_func; + usbredirparser_get_configuration get_configuration_func; + usbredirparser_configuration_status configuration_status_func; + usbredirparser_set_alt_setting set_alt_setting_func; + usbredirparser_get_alt_setting get_alt_setting_func; + usbredirparser_alt_setting_status alt_setting_status_func; + usbredirparser_start_iso_stream start_iso_stream_func; + usbredirparser_stop_iso_stream stop_iso_stream_func; + usbredirparser_iso_stream_status iso_stream_status_func; + usbredirparser_start_interrupt_receiving start_interrupt_receiving_func; + usbredirparser_stop_interrupt_receiving stop_interrupt_receiving_func; + usbredirparser_interrupt_receiving_status interrupt_receiving_status_func; + usbredirparser_alloc_bulk_streams alloc_bulk_streams_func; + usbredirparser_free_bulk_streams free_bulk_streams_func; + usbredirparser_bulk_streams_status bulk_streams_status_func; + usbredirparser_cancel_data_packet cancel_data_packet_func; + /* usb-redir-protocol v0.3 data packet complete callbacks */ + usbredirparser_control_packet control_packet_func; + usbredirparser_bulk_packet bulk_packet_func; + usbredirparser_iso_packet iso_packet_func; + usbredirparser_interrupt_packet interrupt_packet_func; + /* usbredir 0.3.2 new non packet callbacks (for multi-thread locking) */ + usbredirparser_alloc_lock alloc_lock_func; + usbredirparser_lock lock_func; + usbredirparser_unlock unlock_func; + usbredirparser_free_lock free_lock_func; + /* usbredir 0.3.2 new control packet complete callbacks */ + usbredirparser_hello hello_func; + /* usbredir 0.4 new control packet complete callbacks */ + usbredirparser_filter_reject filter_reject_func; + usbredirparser_filter_filter filter_filter_func; + usbredirparser_device_disconnect_ack device_disconnect_ack_func; + /* usbredir 0.6 new control packet complete callbacks */ + usbredirparser_start_bulk_receiving start_bulk_receiving_func; + usbredirparser_stop_bulk_receiving stop_bulk_receiving_func; + usbredirparser_bulk_receiving_status bulk_receiving_status_func; + /* usbredir 0.6 new data packet complete callbacks */ + usbredirparser_buffered_bulk_packet buffered_bulk_packet_func; +}; +#endif + +static void *get_device_string(SpiceUsbBackendDevice *d, uint16_t index, uint8_t *len) +{ + static uint16_t s0[2] = { 0x304, 0x409 }; + static uint16_t s1[8] = { 0x310, 'R', 'e', 'd', ' ', 'H', 'a', 't' }; + static uint16_t s2[9] = { 0x312, 'S', 'p', 'i', 'c', 'e', ' ', 'C', 'D' }; + static uint16_t s3[4] = { 0x308, 'X', '0', '0' }; + void *p = NULL; + switch (index) + { + case 0: + p = s0; *len = sizeof(s0); break; + case 1: + p = s1; *len = sizeof(s1); break; + case 2: + p = s2; *len = sizeof(s2); break; + case 3: + s3[2] = '0' + d->device_info.address / 10; + s3[3] = '0' + d->device_info.address % 10; + p = s3; *len = sizeof(s3); break; + } + return p; +} + +static void get_device_descriptor(SpiceUsbBackendDevice *d, + struct usb_redir_control_packet_header *h) +{ + uint8_t *buffer = (uint8_t *)(h + 1); + const void *p = NULL; + uint8_t len = 0; + static const struct libusb_device_descriptor desc = + { + .bLength = 18, + .bDescriptorType = LIBUSB_DT_DEVICE, + .bcdUSB = USB2_BCD, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = CD_DEV_VID, + .idProduct = CD_DEV_PID, + .bcdDevice = 0x100, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1 + }; + static const uint8_t cfg[] = + { + 9, //len of cfg desc + LIBUSB_DT_CONFIG, // desc type + 0x20, // wlen + 0, + 1, // num if + 1, // cfg val + 0, // cfg name + 0x80, // bus powered + 0x32, // 100 ma + 9, // len of IF desc + LIBUSB_DT_INTERFACE, + 0, // num if + 0, // alt setting + 2, // num of endpoints + CD_DEV_CLASS, + CD_DEV_SUBCLASS, + CD_DEV_PROTOCOL, + 0, // if name + 7, + LIBUSB_DT_ENDPOINT, + 0x81, //->Direction : IN - EndpointID : 1 + 0x02, //->Bulk Transfer Type + 0, //wMaxPacketSize : 0x0200 = 0x200 max bytes + 2, + 0, //bInterval + 7, + LIBUSB_DT_ENDPOINT, + 0x02, //->Direction : OUT - EndpointID : 2 + 0x02, //->Bulk Transfer Type + 0, //wMaxPacketSize : 0x0200 = 0x200 max bytes + 2, + 0, //bInterval + }; + switch (h->value >> 8) { + case LIBUSB_DT_DEVICE: + p = &desc; + len = sizeof(desc); + break; + case LIBUSB_DT_CONFIG: + p = cfg; + len = sizeof(cfg); + break; + case LIBUSB_DT_STRING: + p = get_device_string(d, h->value & 0xff, &len); + break; + } + if (p && len) { + len = MIN(len, h->length); + memcpy(buffer, p, len); + h->length = len; + } else { + h->length = 0; + h->status = usb_redir_stall; + } +} + +static void usbredir_control_packet(void *priv, + uint64_t id, struct usb_redir_control_packet_header *h, + uint8_t *data, int data_len) +{ + SpiceUsbBackendChannel *ch = priv; + struct { + struct usb_redir_control_packet_header h; + uint8_t buffer[255]; + } response = { 0 }; + uint8_t reqtype = h->requesttype & 0x7f; + response.h = *h; + response.h.status = 0; + SPICE_DEBUG("%s %p: TRVIL %02X %02X %04X %04X %04X", + __FUNCTION__, + ch, h->requesttype, h->request, + h->value, h->index, h->length); + if (!ch->attached) { + // device already detached + response.h.status = usb_redir_ioerror; + response.h.length = 0; + } else if (reqtype == (LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_DEVICE)) { + switch (h->request) { + case LIBUSB_REQUEST_GET_DESCRIPTOR: + get_device_descriptor(ch->attached, &response.h); + break; + default: + response.h.length = 0; + break; + } + } else if (reqtype == (LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_ENDPOINT)) { + // should be clear stall request + response.h.length = 0; + response.h.status = 0; + } else if (reqtype == (LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE)) { + response.h.length = 0; + response.h.status = 0; + switch (h->request) { + case 0xFF: + // mass-storage class request 'reset' + break; + case 0xFE: + // mass-storage class request 'get max lun' + // returning one byte + if (!h->length) { + response.h.status = usb_redir_stall; + } else { + response.h.length = 1; + response.buffer[0] = MAX_LUN_PER_DEVICE - 1; + } + break; + default: + break; + } + } + else { + response.h.length = 0; + response.h.status = usb_redir_stall; + } + SPICE_DEBUG("%s responding with payload of %02X, status %X", + __FUNCTION__, + response.h.length, response.h.status); + usbredirparser_send_control_packet(ch->parser, id, &response.h, + response.h.length ? response.buffer : NULL, response.h.length); + usbredir_write_flush_callback(ch); + usbredirparser_free_packet_data(ch->parser, data); +} + +static void usbredir_bulk_packet(void *priv, + uint64_t id, struct usb_redir_bulk_packet_header *h, + uint8_t *data, int data_len) +{ + SpiceUsbBackendChannel *ch = priv; + SpiceUsbBackendDevice *d = ch->attached; + struct usb_redir_bulk_packet_header hout = *h; + uint32_t len = (h->length_high << 16) | h->length; + SPICE_DEBUG("%s %p: ep %X, len %u, id %" PRIu64, __FUNCTION__, + ch, h->endpoint, len, id); + if (!d || !d->d.msc) { + SPICE_DEBUG("%s: device not attached or not realized", __FUNCTION__); + hout.status = usb_redir_ioerror; + hout.length = hout.length_high = 0; + SPICE_DEBUG("%s: responding (a) with ZLP status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, + &hout, NULL, 0); + } else if (h->endpoint & LIBUSB_ENDPOINT_IN) { + if (ch->num_reads < MAX_BULK_IN_REQUESTS) { + int res; + if (ch->num_reads) { + SPICE_DEBUG("%s: already has %u pending reads", __FUNCTION__, ch->num_reads); + } + ch->read_bulk[ch->num_reads].hout = *h; + ch->read_bulk[ch->num_reads].id = id; + ch->num_reads++; + res = cd_usb_bulk_msd_read(d->d.msc, len); + if (res) { + SPICE_DEBUG("%s: error on bulk read", __FUNCTION__); + ch->num_reads--; + hout.length = hout.length_high = 0; + hout.status = usb_redir_ioerror; + SPICE_DEBUG("%s: responding (b) with ZLP status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, &hout, NULL, 0); + } + + } else { + SPICE_DEBUG("%s: too many pending reads", __FUNCTION__); + hout.status = usb_redir_babble; + hout.length = hout.length_high = 0; + SPICE_DEBUG("%s: responding (a) with ZLP status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, &hout, NULL, 0); + } + } else { + cd_usb_bulk_msd_write(d->d.msc, data, data_len); + hout.status = usb_redir_success; + SPICE_DEBUG("%s: responding status %d", __FUNCTION__, hout.status); + usbredirparser_send_bulk_packet(ch->parser, id, &hout, NULL, 0); + } + + usbredirparser_free_packet_data(ch->parser, data); + usbredir_write_flush_callback(ch); +} + +static void usbredir_device_reset(void *priv) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); + if (ch->attached) { + cd_usb_bulk_msd_reset(ch->attached->d.msc); + } +} + +static void usbredir_interface_info(void *priv, + struct usb_redir_interface_info_header *interface_info) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s not implemented %p", __FUNCTION__, ch); +} + +static void usbredir_interface_ep_info(void *priv, + struct usb_redir_ep_info_header *ep_info) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s not implemented %p", __FUNCTION__, ch); +} + +static void usbredir_set_configuration(void *priv, + uint64_t id, struct usb_redir_set_configuration_header *set_configuration) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_configuration_status_header h; + h.status = 0; + h.configuration = set_configuration->configuration; + SPICE_DEBUG("%s ch %p, cfg %d", __FUNCTION__, ch, h.configuration); + if (ch->attached) { + ch->attached->configured = h.configuration != 0; + } + usbredirparser_send_configuration_status(ch->parser, id, &h); + usbredir_write_flush_callback(ch); +} + +static void usbredir_get_configuration(void *priv, uint64_t id) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_configuration_status_header h; + h.status = 0; + h.configuration = ch->attached && ch->attached->configured; + SPICE_DEBUG("%s ch %p, cfg %d", __FUNCTION__, ch, h.configuration); + usbredirparser_send_configuration_status(ch->parser, id, &h); + usbredir_write_flush_callback(ch); +} + +static void usbredir_set_alt_setting(void *priv, + uint64_t id, struct usb_redir_set_alt_setting_header *s) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_alt_setting_status_header sh; + sh.status = (!s->interface && !s->alt) ? 0 : usb_redir_stall; + sh.interface = s->interface; + sh.alt = s->alt; + SPICE_DEBUG("%s ch %p, %d:%d", __FUNCTION__, ch, s->interface, s->alt); + usbredirparser_send_alt_setting_status(ch->parser, id, &sh); + usbredir_write_flush_callback(ch); +} + +static void usbredir_get_alt_setting(void *priv, + uint64_t id, struct usb_redir_get_alt_setting_header *s) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_alt_setting_status_header sh; + sh.status = (s->interface == 0) ? 0 : usb_redir_stall; + sh.interface = s->interface; + sh.alt = 0; + SPICE_DEBUG("%s ch %p, if %d", __FUNCTION__, ch, s->interface); + usbredirparser_send_alt_setting_status(ch->parser, id, &sh); + usbredir_write_flush_callback(ch); +} + +static void usbredir_cancel_data(void *priv, uint64_t id) +{ + SpiceUsbBackendChannel *ch = priv; + SpiceUsbBackendDevice *d = ch->attached; + int nread, found = -1; + SPICE_DEBUG("%s ch %p id %" PRIu64 ", num_reads %u", + __FUNCTION__, ch, id, ch->num_reads); + + if (!d || !d->d.msc) { + SPICE_DEBUG("%s: device not attached or not realized", __FUNCTION__); + return; + } + + for (nread = 0; nread < ch->num_reads; nread++) { + if (ch->read_bulk[nread].id == id) { + found = nread; + break; + } + } + if (found >= 0) { + if (cd_usb_bulk_msd_cancel_read(d->d.msc)) { + cd_usb_bulk_msd_read_complete(d, NULL, 0, BULK_STATUS_CANCELED); + } + } else { + SPICE_DEBUG("%s: ERROR: no such id to cancel!", __FUNCTION__); + } +} + +static void usbredir_filter_reject(void *priv) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s %p", __FUNCTION__, ch); + ch->rejected = 1; +} + +/* Note that the ownership of the rules array is passed on to the callback. */ +static void usbredir_filter_filter(void *priv, + struct usbredirfilter_rule *r, int count) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s ch %p %d filters", __FUNCTION__, ch, count); + ch->rules = r; + ch->rules_count = count; + if (count) { + int i; + for (i = 0; i < count; i++) { + SPICE_DEBUG("%s class %d, %X:%X", + r[i].allow ? "allowed" : "denied", r[i].device_class, + (uint32_t)r[i].vendor_id, (uint32_t)r[i].product_id); + } + } +} + +static void usbredir_device_disconnect_ack(void *priv) +{ + SpiceUsbBackendChannel *ch = priv; + SPICE_DEBUG("%s ch %p", __FUNCTION__, ch); + if (ch->parser) { + ch->parser = NULL; + SPICE_DEBUG("%s switch to usbredirhost", __FUNCTION__); + ch->usbredirhost = ch->hiddenhost; + } +} + +static void usbredir_hello(void *priv, + struct usb_redir_hello_header *hello) +{ + SpiceUsbBackendChannel *ch = priv; + struct usb_redir_device_connect_header device_connect; + struct usb_redir_ep_info_header ep_info = { 0 }; + struct usb_redir_interface_info_header interface_info = { 0 }; + SPICE_DEBUG("%s %p %sattached %s", __FUNCTION__, ch, + ch->attached ? "" : "not ", hello ? "" : "(internal)"); + if (ch->attached) { + interface_info.interface_count = 1; + interface_info.interface_class[0] = CD_DEV_CLASS; + interface_info.interface_subclass[0] = CD_DEV_SUBCLASS; + interface_info.interface_protocol[0] = CD_DEV_PROTOCOL; + usbredirparser_send_interface_info(ch->parser, &interface_info); + + ep_info.type[0x11] = 2; + ep_info.max_packet_size[0x11] = 512; + ep_info.type[0x02] = 2; + ep_info.max_packet_size[0x02] = 512; + usbredirparser_send_ep_info(ch->parser, &ep_info); + + device_connect.device_class = 0; + device_connect.device_subclass = 0; + device_connect.device_protocol = 0; + device_connect.vendor_id = ch->attached->device_info.vid; + device_connect.product_id = ch->attached->device_info.pid; + device_connect.device_version_bcd = USB2_BCD; + device_connect.speed = usb_redir_speed_high; + usbredirparser_send_device_connect(ch->parser, &device_connect); + usbredir_write_flush_callback(ch); + } +} + +/* + We initialize the usbredirparser with HELLO enabled only in case + the libusb is not active and the usbredirhost does not function. + Then the parser sends session HELLO and receives server's response. + Otherwise (usbredirparser initialized with HELLO disabled): + - the usbredirhost sends session HELLO + - we look into it to know set of capabilities we shall initialize + the parser with + - we cache server's response to HELLO and provide it to parser on + first activation (attach of emulated device) to have it synchronized + with server's capabilities +*/ +static struct usbredirparser *create_parser(SpiceUsbBackendChannel *ch, gboolean bHello) +{ + struct usbredirparser *parser = usbredirparser_create(); + if (parser) { + uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0 }; + uint32_t flags = bHello ? 0 : usbredirparser_fl_no_hello; + flags |= usbredirparser_fl_write_cb_owns_buffer | + usbredirparser_fl_usb_host; + parser->priv = ch; + parser->log_func = usbredir_log; + parser->read_func = usbredir_read_callback; + parser->write_func = usbredir_write_callback; + parser->reset_func = usbredir_device_reset; + parser->interface_info_func = usbredir_interface_info; + parser->ep_info_func = usbredir_interface_ep_info; + parser->set_configuration_func = usbredir_set_configuration; + parser->get_configuration_func = usbredir_get_configuration; + parser->set_alt_setting_func = usbredir_set_alt_setting; + parser->get_alt_setting_func = usbredir_get_alt_setting; + parser->cancel_data_packet_func = usbredir_cancel_data; + parser->control_packet_func = usbredir_control_packet; + parser->bulk_packet_func = usbredir_bulk_packet; + parser->alloc_lock_func = usbredir_alloc_lock; + parser->lock_func = usbredir_lock_lock; + parser->unlock_func = usbredir_unlock_lock; + parser->free_lock_func = usbredir_free_lock; + parser->hello_func = usbredir_hello; + parser->filter_reject_func = usbredir_filter_reject; + parser->filter_filter_func = usbredir_filter_filter; + parser->device_disconnect_ack_func = usbredir_device_disconnect_ack; + + if (bHello) { + ch->hello_sent = 1; + ch->host_caps |= 1 << usb_redir_cap_connect_device_version; + ch->host_caps |= 1 << usb_redir_cap_device_disconnect_ack; + ch->host_caps |= 1 << usb_redir_cap_ep_info_max_packet_size; + ch->host_caps |= 1 << usb_redir_cap_64bits_ids; + ch->host_caps |= 1 << usb_redir_cap_32bits_bulk_length; + } + + if (ch->host_caps & (1 << usb_redir_cap_connect_device_version)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); + } + usbredirparser_caps_set_cap(caps, usb_redir_cap_filter); + if (ch->host_caps & (1 << usb_redir_cap_device_disconnect_ack)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_device_disconnect_ack); + } + if (ch->host_caps & (1 << usb_redir_cap_ep_info_max_packet_size)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); + } + if (ch->host_caps & (1 << usb_redir_cap_64bits_ids)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); + } + if (ch->host_caps & (1 << usb_redir_cap_32bits_bulk_length)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); + } + if (ch->host_caps & (1 << usb_redir_cap_bulk_streams)) { + usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams); + } + usbredirparser_init(parser, PACKAGE_STRING, caps, USB_REDIR_CAPS_SIZE, flags); + } + + return parser; +} + +gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, SpiceUsbBackendDevice *dev, const char **msg) +{ + const char *dummy; + if (!msg) { + msg = &dummy; + } + SPICE_DEBUG("%s >> ch %p, dev %p (was attached %p)", __FUNCTION__, ch, dev, ch->attached); + gboolean b = FALSE; + if (dev && dev->isLibUsb) { + libusb_device_handle *handle = NULL; + int rc = libusb_open(dev->d.libusb_device, &handle); + b = rc == 0 && handle; + if (b) { + if (!ch->usbredirhost) { + ch->usbredirhost = ch->hiddenhost; + ch->parser = NULL; + } + rc = usbredirhost_set_device(ch->usbredirhost, handle); + if (rc) { + SPICE_DEBUG("%s ch %p, dev %p usbredirhost error %d", __FUNCTION__, ch, dev, rc); + b = FALSE; + } else { + ch->attached = dev; + dev->attached_to = ch; + if (ch->hello && !ch->hello_done_host) { + SPICE_DEBUG("%s sending cached hello to host", __FUNCTION__); + ch->hello_done_host = 1; + spice_usb_backend_provide_read_data(ch, ch->hello, ch->hello_size); + } + } + } else { + const char *desc = spice_usbutil_libusb_strerror(rc); + g_warning("Error libusb_open: %s [%i]", desc, rc); + *msg = desc; + } + } else if (!dev) { + SPICE_DEBUG("%s intentional sleep", __FUNCTION__); + g_usleep(100000); + if (ch->usbredirhost) { + // it will call libusb_close internally + usbredirhost_set_device(ch->usbredirhost, NULL); + } else { + // CD redir detach + usbredirparser_send_device_disconnect(ch->parser); + usbredir_write_flush_callback(ch); + } + SPICE_DEBUG("%s ch %p, detach done", __FUNCTION__, ch); + ch->attached->attached_to = NULL; + ch->attached = NULL; + b = TRUE; + } else { + // CD redir attach + b = TRUE; + ch->usbredirhost = NULL; + ch->parser = ch->hiddenparser; + ch->attached = dev; + dev->attached_to = ch; + if (ch->hello_done_parser) { + // send device info + usbredir_hello(ch, NULL); + } else if (ch->hello) { + SPICE_DEBUG("%s sending cached hello to parser", __FUNCTION__); + ch->hello_done_parser = 1; + spice_usb_backend_provide_read_data(ch, ch->hello, ch->hello_size); + usbredir_write_flush_callback(ch); + } + } + return b; +} + +SpiceUsbBackendChannel *spice_usb_backend_channel_initialize( + SpiceUsbBackend *be, + const SpiceUsbBackendChannelInitData *init_data) +{ + SpiceUsbBackendChannel *ch = g_new0(SpiceUsbBackendChannel, 1); + SPICE_DEBUG("%s >>", __FUNCTION__); + gboolean ok = FALSE; + if (ch) { + ch->data = *init_data; + ch->hiddenhost = !be->libusbContext ? NULL : + usbredirhost_open_full( + be->libusbContext, + NULL, + usbredir_log, + usbredir_read_callback, + usbredir_write_callback, + usbredir_write_flush_callback, + usbredir_alloc_lock, + usbredir_lock_lock, + usbredir_unlock_lock, + usbredir_free_lock, + ch, PACKAGE_STRING, + init_data->debug ? usbredirparser_debug : usbredirparser_warning, + usbredirhost_fl_write_cb_owns_buffer); + ok = be->libusbContext == NULL || ch->hiddenhost != NULL; + if (ch->hiddenhost) { +#if USBREDIR_VERSION >= 0x000701 + usbredirhost_set_buffered_output_size_cb(ch->hiddenhost, usbredir_buffered_output_size_callback); +#endif + } + } + + if (ok) { + ch->usbredirhost = ch->hiddenhost; + } + + if (ch && !ok) { + g_error("Out of memory allocating usbredir or parser"); + if (ch->hiddenhost) { + usbredirhost_close(ch->hiddenhost); + } + g_free(ch); + ch = NULL; + } + SPICE_DEBUG("%s << %p", __FUNCTION__, ch); + return ch; +} + +void spice_usb_backend_channel_up(SpiceUsbBackendChannel *ch) +{ + SPICE_DEBUG("%s %p, host %p", __FUNCTION__, ch, ch->usbredirhost); + if (ch->usbredirhost) { + usbredirhost_write_guest_data(ch->usbredirhost); + ch->hiddenparser = create_parser(ch, FALSE); + } else if (!ch->hiddenparser) { + ch->hiddenparser = create_parser(ch, TRUE); + ch->parser = ch->hiddenparser; + usbredirparser_do_write(ch->parser); + } +} + +void spice_usb_backend_channel_finalize(SpiceUsbBackendChannel *ch) +{ + SPICE_DEBUG("%s >> %p", __FUNCTION__, ch); + if (ch->usbredirhost) { + usbredirhost_close(ch->usbredirhost); + } + if (ch->parser) { + usbredirparser_destroy(ch->parser); + } + if (ch->hello) { + g_free(ch->hello); + } + + if (ch->rules) { + // is it ok to g_free the memory that was allocated by parser? + g_free(ch->rules); + } + + g_free(ch); + SPICE_DEBUG("%s << %p", __FUNCTION__, ch); +} + +void spice_usb_backend_channel_get_guest_filter( + SpiceUsbBackendChannel *ch, + const struct usbredirfilter_rule **r, + int *count) +{ + int i; + *r = NULL; + *count = 0; + if (ch->usbredirhost) { + usbredirhost_get_guest_filter(ch->usbredirhost, r, count); + } + if (*r == NULL) { + *r = ch->rules; + *count = ch->rules_count; + } + + if (*count) { + SPICE_DEBUG("%s ch %p: %d filters", __FUNCTION__, ch, *count); + } + for (i = 0; i < *count; i++) { + const struct usbredirfilter_rule *ra = *r; + SPICE_DEBUG("%s class %d, %X:%X", + ra[i].allow ? "allowed" : "denied", ra[i].device_class, + (uint32_t)ra[i].vendor_id, (uint32_t)ra[i].product_id); + } +} + +gboolean spice_usb_backend_device_get_info_by_address(guint8 bus, guint8 addr, UsbDeviceInformation *info) +{ + int i; + if (bus != OWN_BUS_NUM) { + return FALSE; + } + for (i = 0; i < MAX_OWN_DEVICES; i++) { + if (own_devices.devices[i].device_info.address == addr) { + *info = own_devices.devices[i].device_info; + return TRUE; + } + } + return FALSE; +} + +#endif // USB_REDIR diff --git a/src/usb-backend.h b/src/usb-backend.h new file mode 100644 index 0000000..47ec2b3 --- /dev/null +++ b/src/usb-backend.h @@ -0,0 +1,112 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2018 Red Hat, Inc. + + Red Hat Authors: + Yuri Benditovich<ybendito@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __SPICE_USB_BACKEND_H__ +#define __SPICE_USB_BACKEND_H__ + +#include <usbredirfilter.h> +#include "usb-device-manager.h" + +G_BEGIN_DECLS + +typedef struct _SpiceUsbBackend SpiceUsbBackend; +typedef struct _SpiceUsbBackendDevice SpiceUsbBackendDevice; +typedef struct _SpiceUsbBackendChannel SpiceUsbBackendChannel; + +typedef struct _UsbDeviceInformation +{ + uint16_t bus; + uint16_t address; + uint16_t vid; + uint16_t pid; + uint8_t class; + uint8_t subclass; + uint8_t protocol; + uint8_t isochronous; + uint8_t max_luns; +} UsbDeviceInformation; + +typedef struct _SpiceUsbBackendChannelInitData +{ + void *user_data; + void (*log)(void *user_data, const char *msg, gboolean error); + int (*write_callback)(void *user_data, uint8_t *data, int count); + int (*is_channel_ready)(void *user_data); + uint64_t (*get_queue_size)(void *user_data); + gboolean debug; +} SpiceUsbBackendChannelInitData; + +typedef void(*usb_hot_plug_callback)( + void *user_data, SpiceUsbBackendDevice *dev, gboolean added); + +typedef void(*backend_device_change_callback)( + void *user_data, SpiceUsbBackendDevice *dev); + +enum { + USB_REDIR_ERROR_IO = -1, + USB_REDIR_ERROR_READ_PARSE = -2, + USB_REDIR_ERROR_DEV_REJECTED = -3, + USB_REDIR_ERROR_DEV_LOST = -4, +}; + +SpiceUsbBackend *spice_usb_backend_initialize(void); +gboolean spice_usb_backend_handle_events(SpiceUsbBackend *); +gboolean spice_usb_backend_handle_hotplug(SpiceUsbBackend *, void *user_data, usb_hot_plug_callback proc); +void spice_usb_backend_set_device_change_callback(SpiceUsbBackend *, void *user_data, backend_device_change_callback proc); +void spice_usb_backend_finalize(SpiceUsbBackend *context); +// returns NULL-terminated array of SpiceUsbBackendDevice * +SpiceUsbBackendDevice **spice_usb_backend_get_device_list(SpiceUsbBackend *backend); +gboolean spice_usb_backend_device_is_hub(SpiceUsbBackendDevice *dev); +gboolean spice_usb_backend_device_need_thread(SpiceUsbBackendDevice *dev); +void spice_usb_backend_free_device_list(SpiceUsbBackendDevice **devlist); +void spice_usb_backend_device_acquire(SpiceUsbBackendDevice *dev); +void spice_usb_backend_device_release(SpiceUsbBackendDevice *dev); +gboolean spice_usb_backend_devices_same(SpiceUsbBackendDevice *dev1, SpiceUsbBackendDevice *dev2); +gconstpointer spice_usb_backend_device_get_libdev(SpiceUsbBackendDevice *dev); +const UsbDeviceInformation* spice_usb_backend_device_get_info(SpiceUsbBackendDevice *dev); +gboolean spice_usb_backend_device_get_info_by_address(guint8 bus, guint8 addr, UsbDeviceInformation *info); +// returns 0 if the device passes the filter +int spice_usb_backend_device_check_filter(SpiceUsbBackendDevice *dev, const struct usbredirfilter_rule *rules, int count); + +SpiceUsbBackendChannel *spice_usb_backend_channel_initialize(SpiceUsbBackend *context, const SpiceUsbBackendChannelInitData *init_data); +// returns 0 for success or error code +int spice_usb_backend_provide_read_data(SpiceUsbBackendChannel *ch, uint8_t *data, int count); +gboolean spice_usb_backend_channel_attach(SpiceUsbBackendChannel *ch, SpiceUsbBackendDevice *dev, const char **msg); +void spice_usb_backend_channel_up(SpiceUsbBackendChannel *ch); +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); +void spice_usb_backend_channel_finalize(SpiceUsbBackendChannel *ch); + +gboolean spice_usb_backend_add_cd_lun(SpiceUsbBackend *be, const SpiceUsbDeviceLunInfo *info); +gboolean spice_usb_backend_remove_cd_lun(SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev, guint lun); +uint32_t spice_usb_backend_get_cd_luns_bitmask(SpiceUsbBackendDevice *bdev); +gboolean spice_usb_backend_get_cd_lun_info(SpiceUsbBackendDevice *bdev, guint lun, SpiceUsbDeviceLunInfo *info); +gboolean spice_usb_backend_load_cd_lun(SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, guint lun, gboolean load); +gboolean spice_usb_backend_lock_cd_lun(SpiceUsbBackend *be, + SpiceUsbBackendDevice *bdev, guint lun, gboolean lock); +gboolean spice_usb_backend_change_cd_lun( + SpiceUsbBackend *be, SpiceUsbBackendDevice *bdev, + guint lun, const SpiceUsbDeviceLunInfo *info); + +G_END_DECLS + +#endif diff --git a/src/usb-device-redir-widget.c b/src/usb-device-redir-widget.c new file mode 100644 index 0000000..59a6043 --- /dev/null +++ b/src/usb-device-redir-widget.c @@ -0,0 +1,2065 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2012 Red Hat, Inc. + + Red Hat Authors: + Alexander Nezhinsky<anezhins@xxxxxxxxxx> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "config.h" +#ifndef USB_WIDGET_TEST + #include <glib/gi18n-lib.h> + #include "spice-client.h" + #include "spice-marshal.h" +#else + #include "spice-client.h" +#endif +#include "usb-device-widget.h" + +/* + Debugging note: + Logging from this module is not affected by --spice-debug + command line parameter + Use SPICE_DEBUG=1 environment varible to enable logs +*/ + +#ifdef USE_NEW_USB_WIDGET + +/** + * SECTION:usb-device-widget + * @short_description: USB device selection widget + * @title: Spice USB device selection widget + * @section_id: + * @see_also: + * @stability: Under development + * @include: spice-client-gtk.h + * + * #SpiceUsbDeviceWidget is a gtk widget which apps can use to easily + * add an UI to select USB devices to redirect (or unredirect). + */ + +struct _SpiceUsbDeviceWidget +{ + GtkBox parent; + + SpiceUsbDeviceWidgetPrivate *priv; +}; + +struct _SpiceUsbDeviceWidgetClass +{ + GtkBoxClass parent_class; + + /* signals */ + void (*connect_failed) (SpiceUsbDeviceWidget *widget, + SpiceUsbDevice *device, GError *error); +}; + +/* ------------------------------------------------------------------ */ +/* Prototypes for callbacks */ +static void device_added_cb(SpiceUsbDeviceManager *manager, + SpiceUsbDevice *device, gpointer user_data); +static void device_removed_cb(SpiceUsbDeviceManager *manager, + SpiceUsbDevice *device, gpointer user_data); +static void device_changed_cb(SpiceUsbDeviceManager *manager, + SpiceUsbDevice *device, gpointer user_data); +static void device_error_cb(SpiceUsbDeviceManager *manager, + SpiceUsbDevice *device, GError *err, gpointer user_data); +static gboolean spice_usb_device_widget_update_status(gpointer user_data); + +/* ------------------------------------------------------------------ */ +/* gobject glue */ + +#define SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_USB_DEVICE_WIDGET, \ + SpiceUsbDeviceWidgetPrivate)) + +enum { + PROP_0, + PROP_SESSION, + PROP_DEVICE_FORMAT_STRING, +}; + +enum { + CONNECT_FAILED, + LAST_SIGNAL, +}; + +typedef struct { + GtkTreeView *tree_view; + GtkTreeStore *tree_store; +} SpiceUsbDeviceWidgetTree; + +struct _SpiceUsbDeviceWidgetPrivate { + SpiceSession *session; + gchar *device_format_string; + SpiceUsbDeviceManager *manager; + GtkWidget *info_bar; + GtkWidget *label; + SpiceUsbDeviceWidgetTree cd_tree; + SpiceUsbDeviceWidgetTree usb_tree; + GdkPixbuf *icon_cd; + GdkPixbuf *icon_connected; + GdkPixbuf *icon_disconn; + GdkPixbuf *icon_warning; + GdkPixbuf *icon_info; + gchar *err_msg; + gsize device_count; +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE(SpiceUsbDeviceWidget, spice_usb_device_widget, GTK_TYPE_BOX); + +/* TREE */ + +enum column_id +{ + COL_REDIRECT = 0, + COL_ADDRESS, + COL_CONNECT_ICON, + COL_CD_ICON, + COL_VENDOR, + COL_PRODUCT, + COL_FILE, + COL_LOADED, + COL_LOCKED, + COL_IDLE, + /* internal columns */ + COL_REVISION, + COL_CD_DEV, + COL_LUN_ITEM, + COL_DEV_ITEM, + COL_ITEM_DATA, + COL_CONNECTED, + COL_CAN_REDIRECT, + COL_ROW_COLOR, + COL_ROW_COLOR_SET, + NUM_COLS, + + INVALID_COL +}; + +// there is a possibility to use different names +// for columns in USB device list and CD device list, if needed +// currently they are identical +static const char *column_names_cd[NUM_COLS]; +static const char *column_names_usb[NUM_COLS]; + +#define SET_COLUMN(n, s) column_names_cd[n] = column_names_usb[n] = s + +static void initialize_columns(void) +{ + SET_COLUMN(COL_REDIRECT, _("Redirect")); + SET_COLUMN(COL_ADDRESS, _("Address")); + SET_COLUMN(COL_CONNECT_ICON, _("Conn")); + SET_COLUMN(COL_CD_ICON, _("CD")); + SET_COLUMN(COL_VENDOR, _("Vendor")); + SET_COLUMN(COL_PRODUCT, _("Product")); + SET_COLUMN(COL_FILE, _("File/Device Path")); + SET_COLUMN(COL_LOADED, _("Loaded")); + SET_COLUMN(COL_LOCKED, _("Locked")); + SET_COLUMN(COL_IDLE, _("Idle")); + SET_COLUMN(COL_REVISION, "?Revision"); + SET_COLUMN(COL_CD_DEV, "?CD_DEV"); + SET_COLUMN(COL_LUN_ITEM, "?LUN_ITEM"); + SET_COLUMN(COL_DEV_ITEM, "?DEV_ITEM"); + SET_COLUMN(COL_ITEM_DATA, "?ITEM_DATA"); + SET_COLUMN(COL_CONNECTED, "?CONNECTED"); + SET_COLUMN(COL_CAN_REDIRECT, "?CAN_REDIRECT"); + SET_COLUMN(COL_ROW_COLOR, "?ROW_COLOR"); + SET_COLUMN(COL_ROW_COLOR_SET, "?ROW_COLOR_SET"); +}; + +static const char *column_name(enum column_id id, gboolean is_cd) +{ + const char **col_name = is_cd ? column_names_cd : column_names_usb; + return col_name[id]; +} + +typedef struct _UsbWidgetLunItem { + SpiceUsbDeviceManager *manager; + SpiceUsbDevice *device; + guint lun; + SpiceUsbDeviceLunInfo info; +} UsbWidgetLunItem; + +typedef struct _TreeFindUsbDev { + SpiceUsbDevice *usb_dev; + GtkTreeIter dev_iter; +} TreeFindUsbDev; + +typedef void (*tree_item_toggled_cb)(GtkCellRendererToggle *, gchar *, gpointer); + +static void usb_widget_add_device(SpiceUsbDeviceWidget *self, + SpiceUsbDevice *usb_device, + GtkTreeIter *old_dev_iter); + +static gchar *usb_device_description(SpiceUsbDeviceManager *manager, + SpiceUsbDevice *device, + const gchar *format) +{ + SpiceUsbDeviceDescription desc; + gchar *descriptor; + gchar *res; + spice_usb_device_get_info(manager, device, &desc); + descriptor = g_strdup_printf("[%04x:%04x]", desc.vendor_id, desc.product_id); + if (!format) { + format = _("%s %s %s at %d-%d"); + } + res = g_strdup_printf(format, desc.vendor, desc.product, descriptor, desc.bus, desc.address); + g_free(desc.vendor); + g_free(desc.product); + g_free(descriptor); + return res; +} + +static GtkTreeStore* usb_widget_create_tree_store(void) +{ + GtkTreeStore *tree_store; + + tree_store = gtk_tree_store_new(NUM_COLS, + G_TYPE_BOOLEAN, /* COL_REDIRECT */ + G_TYPE_STRING, /* COL_ADDRESS */ + GDK_TYPE_PIXBUF, /* COL_CONNECT_ICON */ + GDK_TYPE_PIXBUF, /* COL_CD_ICON */ + G_TYPE_STRING, /* COL_VENDOR */ + G_TYPE_STRING, /* COL_PRODUCT */ + G_TYPE_STRING, /* COL_FILE */ + G_TYPE_BOOLEAN, /* COL_LOADED */ + G_TYPE_BOOLEAN, /* COL_LOCKED */ + G_TYPE_BOOLEAN, /* COL_IDLE */ + /* internal columns */ + G_TYPE_STRING, /* COL_REVISION */ + G_TYPE_BOOLEAN, /* COL_CD_DEV */ + G_TYPE_BOOLEAN, /* COL_LUN_ITEM */ + G_TYPE_BOOLEAN, /* COL_DEV_ITEM */ + G_TYPE_POINTER, /* COL_ITEM_DATA */ + G_TYPE_BOOLEAN, /* COL_CONNECTED */ + G_TYPE_BOOLEAN, /* COL_CAN_REDIRECT */ + G_TYPE_STRING, /* COL_ROW_COLOR */ + G_TYPE_BOOLEAN /* COL_ROW_COLOR_SET */ ); + SPICE_DEBUG("tree store created"); + + return tree_store; +} + +static GdkPixbuf *get_named_icon(const gchar *name, gint size) +{ + GtkIconInfo *info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), name, size, 0); + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(info, NULL); + g_object_unref (info); + return pixbuf; +} + +static void select_widget_size(GtkWidget *wg) +{ + GdkDisplay *d = gtk_widget_get_display(wg); + int i, w = 2000, h = 1024; + int n = gdk_display_get_n_monitors(d); + for (i = 0; i < n; ++i) + { + GdkMonitor *m = gdk_display_get_monitor(d, i); + if (m) { + GdkRectangle area; + gdk_monitor_get_workarea(m, &area); + SPICE_DEBUG("monitor %d: %d x %d @( %d, %d)", + i, area.width, area.height, area.x, area.y ); + w = MIN(w, area.width); + h = MIN(h, area.height); + } + } + + w = (w * 3) / 4; + h = h / 2; + + SPICE_DEBUG("sizing widget as %d x %d", w, h); + gtk_widget_set_size_request(wg, w, h); +} + +static void usb_widget_add_device(SpiceUsbDeviceWidget *self, + SpiceUsbDevice *usb_device, + GtkTreeIter *old_dev_iter) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + SpiceUsbDeviceManager *usb_dev_mgr = priv->manager; + SpiceUsbDeviceWidgetTree *tree; + GtkTreeView *tree_view; + GtkTreeStore *tree_store; + GtkTreeIter new_dev_iter; + SpiceUsbDeviceDescription dev_info; + GtkTreePath *new_dev_path; + gboolean is_dev_redirected, is_dev_connected, is_dev_cd; + gchar *addr_str; + GArray *lun_array; + guint lun_index; + GError *error = NULL; + + is_dev_cd = spice_usb_device_manager_is_device_cd(usb_dev_mgr, usb_device); + tree = is_dev_cd ? &priv->cd_tree : &priv->usb_tree; + tree_view = tree->tree_view; + tree_store = tree->tree_store; + + if (old_dev_iter == NULL) { + gtk_tree_store_append(tree_store, &new_dev_iter, NULL); + } else { + gtk_tree_store_insert_after(tree_store, &new_dev_iter, NULL, old_dev_iter); + gtk_tree_store_remove(tree_store, old_dev_iter); + } + + spice_usb_device_get_info(usb_dev_mgr, usb_device, &dev_info); + addr_str = g_strdup_printf("%d:%d", (gint)dev_info.bus, (gint)dev_info.address); + is_dev_connected = spice_usb_device_manager_is_device_connected(usb_dev_mgr, usb_device); + is_dev_redirected = is_dev_connected; + SPICE_DEBUG("usb device a:[%s] p:[%s] m:[%s] conn:%d cd:%d", + addr_str, dev_info.vendor, dev_info.product, is_dev_connected, is_dev_cd); + + gtk_tree_store_set(tree_store, &new_dev_iter, + COL_REDIRECT, is_dev_redirected, + COL_ADDRESS, addr_str, + COL_CONNECT_ICON, is_dev_connected ? priv->icon_connected : priv->icon_disconn, + COL_CD_ICON, priv->icon_cd, + COL_VENDOR, dev_info.vendor, + COL_PRODUCT, dev_info.product, + COL_CD_DEV, is_dev_cd, + COL_LUN_ITEM, FALSE, /* USB device item */ + COL_DEV_ITEM, TRUE, /* USB device item */ + COL_ITEM_DATA, (gpointer)usb_device, + COL_CONNECTED, is_dev_connected, + COL_CAN_REDIRECT, spice_usb_device_manager_can_redirect_device(usb_dev_mgr, usb_device, &error), + COL_ROW_COLOR, "beige", + COL_ROW_COLOR_SET, TRUE, + -1); + g_clear_error(&error); + + priv->device_count++; + + /* get all the luns */ + lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, usb_device); + for (lun_index = 0; lun_index < lun_array->len; lun_index++) { + UsbWidgetLunItem *lun_item; + GtkTreeIter lun_iter; + gchar lun_str[8]; + + lun_item = g_malloc(sizeof(*lun_item)); + lun_item->manager = usb_dev_mgr; + lun_item->device = usb_device; + lun_item->lun = g_array_index(lun_array, guint, lun_index); + spice_usb_device_manager_device_lun_get_info(usb_dev_mgr, usb_device, lun_item->lun, &lun_item->info); + SPICE_DEBUG("lun %u v:[%s] p:[%s] r:[%s] file:[%s] lun_item:%p", + lun_index, lun_item->info.vendor, lun_item->info.product, + lun_item->info.revision, lun_item->info.file_path, lun_item); + g_snprintf(lun_str, 8, "↳%u", lun_item->lun); + + /* Append LUN as a child of USB device */ + gtk_tree_store_append(tree_store, &lun_iter, &new_dev_iter); + gtk_tree_store_set(tree_store, &lun_iter, + COL_ADDRESS, lun_str, + COL_VENDOR, lun_item->info.vendor, + COL_PRODUCT, lun_item->info.product, + COL_REVISION, lun_item->info.revision, + COL_FILE, lun_item->info.file_path, + COL_LOADED, lun_item->info.loaded, + COL_LOCKED, lun_item->info.locked, + COL_IDLE, !lun_item->info.started, + COL_CD_DEV, FALSE, + COL_LUN_ITEM, TRUE, /* LUN item */ + COL_DEV_ITEM, FALSE, /* LUN item */ + COL_ITEM_DATA, (gpointer)lun_item, + COL_CONNECTED, is_dev_connected, + COL_ROW_COLOR, "azure", + COL_ROW_COLOR_SET, TRUE, + -1); + } + g_array_unref(lun_array); + + new_dev_path = gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store), &new_dev_iter); + gtk_tree_view_expand_row(tree_view, new_dev_path, FALSE); + gtk_tree_path_free(new_dev_path); + + g_free(dev_info.vendor); + g_free(dev_info.product); + g_free(addr_str); +} + +static gboolean tree_item_is_lun(GtkTreeStore *tree_store, GtkTreeIter *iter) +{ + gboolean is_lun; + gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, COL_LUN_ITEM, &is_lun, -1); + return is_lun; +} + +static gboolean usb_widget_tree_store_find_usb_dev_foreach_cb(GtkTreeModel *tree_model, + GtkTreePath *path, GtkTreeIter *iter, + gpointer user_data) +{ + TreeFindUsbDev *find_info = (TreeFindUsbDev *)user_data; + SpiceUsbDevice *find_usb_device = find_info->usb_dev; + SpiceUsbDevice *usb_device; + gboolean is_lun_item; + + gtk_tree_model_get(tree_model, iter, + COL_LUN_ITEM, &is_lun_item, + COL_ITEM_DATA, (gpointer *)&usb_device, + -1); + if (!is_lun_item && usb_device == find_usb_device) { + find_info->dev_iter = *iter; + find_info->usb_dev = NULL; + SPICE_DEBUG("Usb dev found %p iter %p", usb_device, iter); + return TRUE; /* stop iterating */ + } else { + return FALSE; /* continue iterating */ + } +} + +static GtkTreeIter *usb_widget_tree_store_find_usb_device(GtkTreeStore *tree_store, + SpiceUsbDevice *usb_device) +{ + TreeFindUsbDev find_info = { .usb_dev = usb_device,.dev_iter = {} }; + GtkTreeIter *iter = NULL; + + gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store), + usb_widget_tree_store_find_usb_dev_foreach_cb, (gpointer)&find_info); + // the callback sets 'usb_dev' field to zero if it finds the device + if (!find_info.usb_dev) { + iter = g_malloc(sizeof(*iter)); + *iter = find_info.dev_iter; + } + return iter; +} + +static gboolean usb_widget_remove_device(SpiceUsbDeviceWidget *self, + SpiceUsbDevice *usb_device) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeIter *old_dev_iter; + GtkTreeStore *tree_store = priv->usb_tree.tree_store; + // on WIN32 it is possible the backend device is already removed + // from the USB device manager list and we can't know it was + // USB or CD, do we will try both lists + old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device); + if (old_dev_iter != NULL) { + SPICE_DEBUG("USB Device removed"); + gtk_tree_store_remove(tree_store, old_dev_iter); + priv->device_count--; + g_free(old_dev_iter); + return TRUE; + } + tree_store = priv->cd_tree.tree_store; + if (tree_store) { + old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device); + } + if (old_dev_iter != NULL) { + SPICE_DEBUG("CD Device removed"); + gtk_tree_store_remove(tree_store, old_dev_iter); + priv->device_count--; + g_free(old_dev_iter); + return TRUE; + } + SPICE_DEBUG("Device %p not found!", usb_device); + return FALSE; +} + +static GtkTreeViewColumn* view_add_toggle_column(SpiceUsbDeviceWidget *self, + enum column_id toggle_col_id, + enum column_id visible_col_id, + enum column_id sensitive_col_id, + tree_item_toggled_cb toggled_cb, + gboolean is_cd) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkCellRenderer *renderer; + GtkTreeViewColumn *view_col; + const char *col_name = column_name(toggle_col_id, is_cd); + + renderer = gtk_cell_renderer_toggle_new(); + + if (sensitive_col_id != INVALID_COL) { + view_col = gtk_tree_view_column_new_with_attributes( + col_name, + renderer, + "active", toggle_col_id, + "visible", visible_col_id, + "activatable", sensitive_col_id, + NULL); + } else { + view_col = gtk_tree_view_column_new_with_attributes( + col_name, + renderer, + "active", toggle_col_id, + "visible", visible_col_id, + NULL); + } + + gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_expand(view_col, FALSE); + gtk_tree_view_column_set_resizable(view_col, FALSE); + gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col); + + g_signal_connect(renderer, "toggled", G_CALLBACK(toggled_cb), self); + + SPICE_DEBUG("view added toggle column [%u : %s] visible when [%u : %s]", + toggle_col_id, col_name, + visible_col_id, column_name(visible_col_id, is_cd)); + return view_col; +} + +static GtkTreeViewColumn* view_add_read_only_toggle_column(SpiceUsbDeviceWidget *self, + enum column_id toggle_col_id, + enum column_id visible_col_id, + gboolean is_cd) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkCellRenderer *renderer; + GtkTreeViewColumn *view_col; + const char *col_name = column_name(toggle_col_id, is_cd); + + renderer = gtk_cell_renderer_toggle_new(); + + view_col = gtk_tree_view_column_new_with_attributes( + col_name, + renderer, + "active", toggle_col_id, + "visible", visible_col_id, + NULL); + + gtk_cell_renderer_toggle_set_activatable(GTK_CELL_RENDERER_TOGGLE(renderer), FALSE); + + gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_expand(view_col, FALSE); + gtk_tree_view_column_set_resizable(view_col, FALSE); + gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col); + + SPICE_DEBUG("view added read-only toggle column [%u : %s] visible when [%u : %s]", + toggle_col_id, col_name, + visible_col_id, column_name(visible_col_id, is_cd)); + return view_col; +} + +static GtkTreeViewColumn* view_add_text_column(SpiceUsbDeviceWidget *self, + enum column_id col_id, + gboolean expandable, + gboolean is_cd) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkCellRenderer *renderer; + GtkTreeViewColumn *view_col; + + renderer = gtk_cell_renderer_text_new(); + + view_col = gtk_tree_view_column_new_with_attributes( + column_name(col_id, is_cd), + renderer, + "text", col_id, + //"cell-background", COL_ROW_COLOR, + //"cell-background-set", COL_ROW_COLOR_SET, + NULL); + + gtk_tree_view_column_set_sizing(view_col, GTK_TREE_VIEW_COLUMN_GROW_ONLY); + gtk_tree_view_column_set_resizable(view_col, TRUE); + gtk_tree_view_column_set_expand(view_col, expandable); + + gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col); + + SPICE_DEBUG("view added text column [%u : %s]", col_id, column_name(col_id, is_cd)); + return view_col; +} + +static GtkTreeViewColumn* view_add_pixbuf_column(SpiceUsbDeviceWidget *self, + enum column_id col_id, + enum column_id visible_col_id, + gboolean is_cd) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkCellRenderer *renderer; + GtkTreeViewColumn *view_col; + const char *col_name = column_name(col_id, is_cd); + + renderer = gtk_cell_renderer_pixbuf_new(); + + if (visible_col_id == INVALID_COL) { + view_col = gtk_tree_view_column_new_with_attributes( + col_name, + renderer, + "pixbuf", col_id, + NULL); + SPICE_DEBUG("view added pixbuf col[%u : %s] visible always", col_id, col_name); + } else { + view_col = gtk_tree_view_column_new_with_attributes( + col_name, + renderer, + "pixbuf", col_id, + "visible", visible_col_id, + NULL); + SPICE_DEBUG("view added pixbuf col[%u : %s] visible when[%u : %s]", + col_id, col_name, visible_col_id, column_name(visible_col_id, is_cd)); + } + gtk_tree_view_append_column(is_cd ? priv->cd_tree.tree_view : priv->usb_tree.tree_view, view_col); + return view_col; +} + +/* Toggle handlers */ + +static gboolean tree_item_toggle_get_val(GtkTreeStore *tree_store, gchar *path_str, GtkTreeIter *iter, enum column_id col_id) +{ + gboolean toggle_val; + + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(tree_store), iter, path_str); + gtk_tree_model_get(GTK_TREE_MODEL(tree_store), iter, col_id, &toggle_val, -1); + + return toggle_val; +} + +static void tree_item_toggle_set(GtkTreeStore *tree_store, GtkTreeIter *iter, enum column_id col_id, gboolean new_val) +{ + gtk_tree_store_set(tree_store, iter, col_id, new_val, -1); +} + +typedef struct _connect_cb_data { + SpiceUsbDeviceWidget *self; + SpiceUsbDevice *usb_dev; +} connect_cb_data; + +static void connect_cb_data_free(connect_cb_data *user_data) +{ + spice_usb_device_widget_update_status(user_data->self); + g_object_unref(user_data->self); + g_boxed_free(spice_usb_device_get_type(), user_data->usb_dev); + g_free(user_data); +} + +static void usb_widget_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + connect_cb_data *cb_data = user_data; + SpiceUsbDeviceWidget *self = cb_data->self; + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + SpiceUsbDevice *usb_dev = cb_data->usb_dev; + GError *err = NULL; + GtkTreeIter *dev_iter; + gchar *desc; + gboolean finished; + gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev); + GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store; + + dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev); + if (!dev_iter) { + return; + } + + desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string); + SPICE_DEBUG("Connect cb: %p %s", usb_dev, desc); + + finished = spice_usb_device_manager_connect_device_finish(priv->manager, res, &err); + if (finished) { + gtk_tree_store_set(tree_store, dev_iter, + COL_CONNECT_ICON, priv->icon_connected, + COL_CONNECTED, TRUE, + -1); + } else { + gtk_tree_store_set(tree_store, dev_iter, + COL_REDIRECT, FALSE, + -1); + g_prefix_error(&err, "Device connect failed %s: ", desc); + if (err) { + SPICE_DEBUG("%s", err->message); + g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, err); + g_error_free(err); + } else { + g_signal_emit(self, signals[CONNECT_FAILED], 0, usb_dev, NULL); + } + + /* don't trigger a disconnect if connect failed */ + /* + g_signal_handlers_block_by_func(GTK_TOGGLE_BUTTON(user_data->check), + checkbox_clicked_cb, self); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(user_data->check), FALSE); + g_signal_handlers_unblock_by_func(GTK_TOGGLE_BUTTON(user_data->check), + checkbox_clicked_cb, self); + */ + } + g_free(desc); + g_free(dev_iter); + connect_cb_data_free(user_data); +} + +static void usb_widget_disconnect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + connect_cb_data *cb_data = user_data; + SpiceUsbDeviceWidget *self = cb_data->self; + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + SpiceUsbDevice *usb_dev = cb_data->usb_dev; + GError *err = NULL; + GtkTreeIter *dev_iter; + gchar *desc; + gboolean finished; + gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_dev); + GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store; + + dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_dev); + if (!dev_iter) { + return; + } + + desc = usb_device_description(priv->manager, usb_dev, priv->device_format_string); + SPICE_DEBUG("Disconnect cb: %p %s", usb_dev, desc); + + finished = spice_usb_device_manager_disconnect_device_finish(priv->manager, res, &err); + if (finished) { + gtk_tree_store_set(tree_store, dev_iter, + COL_CONNECT_ICON, priv->icon_disconn, + COL_CONNECTED, FALSE, + -1); + } else { + gtk_tree_store_set(tree_store, dev_iter, + COL_REDIRECT, TRUE, + -1); + g_prefix_error(&err, "Device disconnect failed %s: ", desc); + if (err) { + SPICE_DEBUG("%s", err->message); + g_error_free(err); + } + } + g_free(desc); + g_free(dev_iter); + connect_cb_data_free(user_data); +} + +static void tree_item_toggled_cb_redirect(GtkCellRendererToggle *cell, + gchar *path_str, + SpiceUsbDeviceWidget *self, + GtkTreeStore *tree_store) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + connect_cb_data *cb_data = g_new(connect_cb_data, 1); + SpiceUsbDevice *usb_dev; + GtkTreeIter iter; + gboolean new_redirect_val; + + new_redirect_val = !tree_item_toggle_get_val(tree_store, path_str, &iter, COL_REDIRECT); + SPICE_DEBUG("Redirect: %s", new_redirect_val ? "ON" : "OFF"); + tree_item_toggle_set(tree_store, &iter, COL_REDIRECT, new_redirect_val); + + gtk_tree_model_get(GTK_TREE_MODEL(tree_store), &iter, COL_ITEM_DATA, (gpointer *)&usb_dev, -1); + cb_data->self = g_object_ref(self); + cb_data->usb_dev = g_boxed_copy(spice_usb_device_get_type(), usb_dev); + + if (new_redirect_val) { + spice_usb_device_manager_connect_device_async(priv->manager, usb_dev, + NULL, /* cancellable */ + usb_widget_connect_cb, cb_data); + } else { + spice_usb_device_manager_disconnect_device_async(priv->manager, usb_dev, + NULL, /* cancellable */ + usb_widget_disconnect_cb, cb_data); + + } + spice_usb_device_widget_update_status(self); +} + +static void tree_item_toggled_cb_redirect_cd(GtkCellRendererToggle *cell, + gchar *path_str, + gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + tree_item_toggled_cb_redirect(cell, path_str, self, priv->cd_tree.tree_store); +} + +static void tree_item_toggled_cb_redirect_usb(GtkCellRendererToggle *cell, + gchar *path_str, + gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + tree_item_toggled_cb_redirect(cell, path_str, self, priv->usb_tree.tree_store); +} + +/* Signal handlers */ + +static void device_added_cb(SpiceUsbDeviceManager *usb_dev_mgr, + SpiceUsbDevice *usb_device, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + + SPICE_DEBUG("Signal: Device Added"); + + usb_widget_add_device(self, usb_device, NULL); + + spice_usb_device_widget_update_status(self); +} + +static void device_removed_cb(SpiceUsbDeviceManager *usb_dev_mgr, + SpiceUsbDevice *usb_device, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + gboolean dev_removed; + + SPICE_DEBUG("Signal: Device Removed"); + + dev_removed = usb_widget_remove_device(self, usb_device); + if (dev_removed) { + spice_usb_device_widget_update_status(self); + } +} + +static void device_changed_cb(SpiceUsbDeviceManager *usb_dev_mgr, + SpiceUsbDevice *usb_device, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeIter *old_dev_iter; + gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device); + GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store; + + SPICE_DEBUG("Signal: Device Changed"); + + old_dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device); + if (old_dev_iter != NULL) { + + usb_widget_add_device(self, usb_device, old_dev_iter); + + spice_usb_device_widget_update_status(self); + g_free(old_dev_iter); + } else { + SPICE_DEBUG("Device not found!"); + } +} + +static void device_error_cb(SpiceUsbDeviceManager *manager, + SpiceUsbDevice *usb_device, GError *err, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeIter *dev_iter; + gboolean is_cd = spice_usb_device_manager_is_device_cd(priv->manager, usb_device); + GtkTreeStore *tree_store = is_cd ? priv->cd_tree.tree_store : priv->usb_tree.tree_store; + + SPICE_DEBUG("Signal: Device Error"); + + dev_iter = usb_widget_tree_store_find_usb_device(tree_store, usb_device); + if (dev_iter != NULL) { + tree_item_toggle_set(tree_store, dev_iter, COL_REDIRECT, FALSE); + spice_usb_device_widget_update_status(self); + g_free(dev_iter); + } else { + SPICE_DEBUG("Device not found!"); + } +} + +/* Selection handler */ + +static void tree_selection_changed_cb(GtkTreeSelection *select, gpointer user_data) +{ + GtkTreeModel *tree_model; + GtkTreeIter iter; + GtkTreePath *path; + gboolean is_lun; + UsbWidgetLunItem *lun_item; + gchar *txt[3]; + + if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) { + gtk_tree_model_get(tree_model, &iter, + COL_VENDOR, &txt[0], + COL_PRODUCT, &txt[1], + COL_REVISION, &txt[2], + COL_LUN_ITEM, &is_lun, + COL_ITEM_DATA, (gpointer *)&lun_item, + -1); + path = gtk_tree_model_get_path(tree_model, &iter); + + SPICE_DEBUG("selected: %s,%s,%s [%s %s] [%s]", + txt[0], txt[1], + is_lun ? txt[2] : "--", + is_lun ? "LUN" : "USB-DEV", + is_lun ? lun_item->info.file_path : "--", + gtk_tree_path_to_string(path)); + + if (txt[0]) { + g_free(txt[0]); + } + if (txt[1]) { + g_free(txt[1]); + } + if (txt[2]) { + g_free(txt[2]); + } + gtk_tree_path_free(path); + } +} + +static GtkTreeSelection* set_selection_handler(GtkTreeView *tree_view) +{ + GtkTreeSelection *select; + + select = gtk_tree_view_get_selection(tree_view); + gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE); + + g_signal_connect(G_OBJECT(select), "changed", + G_CALLBACK(tree_selection_changed_cb), + NULL); + + SPICE_DEBUG("selection handler set"); + return select; +} + +static GtkWidget *create_image_button_box(const gchar *label_str, const gchar *icon_name, GtkWidget *parent) +{ + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + GtkWidget *icon = gtk_image_new_from_icon_name(icon_name, GTK_ICON_SIZE_MENU); + GtkWidget *label = gtk_accel_label_new(label_str); + GtkAccelGroup *accel_group = gtk_accel_group_new(); + guint accel_key; + + /* add icon */ + gtk_container_add(GTK_CONTAINER(box), icon); + + /* add label */ + gtk_label_set_xalign(GTK_LABEL(label), 0.0); + gtk_label_set_use_underline(GTK_LABEL(label), TRUE); + g_object_get(G_OBJECT(label), "mnemonic-keyval", &accel_key, NULL); + gtk_widget_add_accelerator(parent, "activate", accel_group, accel_key, + GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(label), parent); + gtk_box_pack_end(GTK_BOX(box), label, TRUE, TRUE, 0); + + /* add the new box to the parent widget */ + gtk_container_add(GTK_CONTAINER(parent), box); + + return box; +} + +/* LUN properties dialog */ + +typedef struct _lun_properties_dialog { + GtkWidget *dialog; + GtkWidget *advanced_grid; + gboolean advanced_shown; + + GtkWidget *file_entry; + GtkWidget *vendor_entry; + GtkWidget *product_entry; + GtkWidget *revision_entry; + GtkWidget *loaded_switch; + GtkWidget *locked_switch; +} lun_properties_dialog; + +#if 1 +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data) +{ + GtkWidget *file_entry = (GtkWidget *)user_data; + GtkFileChooserNative *native; + GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; + gint res; + + native = gtk_file_chooser_native_new("Choose File for USB CD", + GTK_WINDOW(gtk_widget_get_toplevel(file_entry)), + action, + "_Open", + "_Cancel"); + + res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(native)); + gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1); + gtk_entry_set_text(GTK_ENTRY(file_entry), filename); + g_free(filename); + } + else { + gtk_widget_grab_focus(button); + } + + g_object_unref(native); +} +#else +// to be removed +static void usb_cd_choose_file(GtkWidget *button, gpointer user_data) +{ + GtkWidget *file_entry = (GtkWidget *)user_data; + GtkWidget *dialog; + gint res; + + dialog = gtk_file_chooser_dialog_new ("Choose File for USB CD", + GTK_WINDOW(gtk_widget_get_toplevel(file_entry)), + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Ok", + GTK_RESPONSE_ACCEPT, + NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); + + res = gtk_dialog_run(GTK_DIALOG(dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + gtk_entry_set_alignment(GTK_ENTRY(file_entry), 1); + gtk_entry_set_text(GTK_ENTRY(file_entry), filename); + g_free(filename); + } + gtk_widget_destroy(dialog); +} +#endif + +static gboolean lun_properties_dialog_loaded_switch_cb(GtkWidget *widget, + gboolean state, gpointer user_data) +{ + lun_properties_dialog *lun_dialog = user_data; + + gtk_widget_set_sensitive(lun_dialog->locked_switch, state); + gtk_widget_set_can_focus(lun_dialog->locked_switch, state); + + return FALSE; /* call default signal handler */ +} + +static void lun_properties_dialog_toggle_advanced(GtkWidget *widget, gpointer user_data) +{ + lun_properties_dialog *lun_dialog = user_data; + + if (lun_dialog->advanced_shown) { + gtk_widget_hide(lun_dialog->advanced_grid); + lun_dialog->advanced_shown = FALSE; + } else { + gtk_widget_show_all(lun_dialog->advanced_grid); + lun_dialog->advanced_shown = TRUE; + } +} + +static void create_lun_properties_dialog(SpiceUsbDeviceWidget *self, + GtkWidget *parent_window, + SpiceUsbDeviceLunInfo *lun_info, + lun_properties_dialog *lun_dialog) +{ + // SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkWidget *dialog, *content_area; + GtkWidget *grid, *advanced_grid; + GtkWidget *file_entry, *choose_button; + GtkWidget *advanced_button, *advanced_icon; + GtkWidget *vendor_entry, *product_entry, *revision_entry; + GtkWidget *loaded_switch, *loaded_label; + GtkWidget *locked_switch, *locked_label; + gint nrow = 0; + + dialog = gtk_dialog_new_with_buttons (!lun_info ? "Add CD LUN" : "CD LUN Settings", + GTK_WINDOW(parent_window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, /* flags */ + !lun_info ? "Add" : "OK", GTK_RESPONSE_ACCEPT, + "Cancel", GTK_RESPONSE_REJECT, + NULL); + + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); + gtk_container_set_border_width(GTK_CONTAINER(dialog), 12); + gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog))), 12); + + content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + /* main grid - always visible */ + grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(grid), 12); + gtk_grid_set_column_homogeneous(GTK_GRID(grid), FALSE); + gtk_container_add(GTK_CONTAINER(content_area), grid); + + /* File path label */ + gtk_grid_attach(GTK_GRID(grid), + gtk_label_new("Select file or device"), + 0, nrow++, // left top + 7, 1); // width height + + /* file/device path entry */ + file_entry = gtk_entry_new(); + gtk_widget_set_hexpand(file_entry, TRUE); + if (!lun_info) { + gtk_entry_set_placeholder_text(GTK_ENTRY(file_entry), "file-path"); + } else { + gtk_entry_set_text(GTK_ENTRY(file_entry), lun_info->file_path); + if (lun_info->loaded) { + gtk_editable_set_editable(GTK_EDITABLE(file_entry), FALSE); + gtk_widget_set_can_focus(file_entry, FALSE); + } + } + gtk_grid_attach(GTK_GRID(grid), + file_entry, + 0, nrow, // left top + 6, 1); // width height + + /* choose button */ + choose_button = gtk_button_new_with_mnemonic("_Choose File"); + gtk_widget_set_hexpand(choose_button, FALSE); + g_signal_connect(GTK_BUTTON(choose_button), + "clicked", G_CALLBACK(usb_cd_choose_file), file_entry); + if (lun_info && lun_info->loaded) { + gtk_widget_set_sensitive(choose_button, FALSE); + gtk_widget_set_can_focus(choose_button, FALSE); + } + + gtk_grid_attach(GTK_GRID(grid), + choose_button, + 6, nrow++, // left top + 1, 1); // width height + + /* advanced button */ + advanced_button = gtk_button_new_with_label("Advanced"); + gtk_button_set_relief(GTK_BUTTON(advanced_button), GTK_RELIEF_NONE); + advanced_icon = gtk_image_new_from_icon_name("preferences-system", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(advanced_button), advanced_icon); + gtk_button_set_always_show_image(GTK_BUTTON(advanced_button), TRUE); + g_signal_connect(advanced_button, "clicked", G_CALLBACK(lun_properties_dialog_toggle_advanced), lun_dialog); + + gtk_grid_attach(GTK_GRID(grid), + advanced_button, + 0, nrow++, // left top + 1, 1); // width height + + /* advanced grid */ + advanced_grid = gtk_grid_new(); + gtk_grid_set_row_spacing(GTK_GRID(advanced_grid), 12); + gtk_grid_set_column_homogeneous(GTK_GRID(advanced_grid), FALSE); + gtk_container_add(GTK_CONTAINER(content_area), advanced_grid); + + /* horizontal separator */ + gtk_container_add(GTK_CONTAINER(content_area), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)); + + /* pack advanced grid */ + nrow = 0; + + /* horizontal separator */ + gtk_grid_attach(GTK_GRID(advanced_grid), + gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), + 0, nrow++, // left top + 7, 1); // width height + + /* product id labels */ + gtk_grid_attach(GTK_GRID(advanced_grid), + gtk_label_new("Vendor"), + 0, nrow, // left top + 2, 1); // width height + + gtk_grid_attach(GTK_GRID(advanced_grid), + gtk_label_new("Product"), + 2, nrow, // left top + 4, 1); // width height + + gtk_grid_attach(GTK_GRID(advanced_grid), + gtk_label_new("Revision"), + 6, nrow++, // left top + 1, 1); // width height + + /* vendor entry */ + vendor_entry = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(vendor_entry), 8); + if (lun_info) { + gtk_widget_set_sensitive(vendor_entry, FALSE); + gtk_widget_set_can_focus(vendor_entry, FALSE); + gtk_entry_set_text(GTK_ENTRY(vendor_entry), lun_info->vendor); + } else { + gtk_entry_set_placeholder_text(GTK_ENTRY(vendor_entry), "auto"); + } + gtk_grid_attach(GTK_GRID(advanced_grid), + vendor_entry, + 0, nrow, // left top + 2, 1); // width height + + /* product entry */ + product_entry = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(product_entry), 16); + if (lun_info) { + gtk_widget_set_sensitive(product_entry, FALSE); + gtk_widget_set_can_focus(product_entry, FALSE); + gtk_entry_set_text(GTK_ENTRY(product_entry), lun_info->product); + } else { + gtk_entry_set_placeholder_text(GTK_ENTRY(product_entry), "auto"); + } + gtk_grid_attach(GTK_GRID(advanced_grid), + product_entry, + 2, nrow, // left top + 4, 1); // width height + + /* revision entry */ + revision_entry = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(revision_entry), 4); + if (lun_info) { + gtk_widget_set_sensitive(revision_entry, FALSE); + gtk_widget_set_can_focus(revision_entry, FALSE); + gtk_entry_set_text(GTK_ENTRY(revision_entry), lun_info->revision); + } else { + gtk_entry_set_placeholder_text(GTK_ENTRY(revision_entry), "auto"); + } + gtk_grid_attach(GTK_GRID(advanced_grid), + revision_entry, + 6, nrow++, // left top + 1, 1); // width height + + /* horizontal separator */ + if (!lun_info) { + gtk_grid_attach(GTK_GRID(advanced_grid), + gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), + 0, nrow++, // left top + 7, 1); // width height + } + + /* initially loaded switch */ + loaded_label = gtk_label_new("Initially loaded:"); + gtk_grid_attach(GTK_GRID(advanced_grid), + loaded_label, + 0, nrow, // left top + 2, 1); // width height + + loaded_switch = gtk_switch_new(); + gtk_switch_set_state(GTK_SWITCH(loaded_switch), TRUE); + if (lun_info) { + gtk_widget_set_child_visible(loaded_switch, FALSE); + gtk_widget_set_child_visible(loaded_label, FALSE); + } else { + g_signal_connect(loaded_switch, "state-set", + G_CALLBACK(lun_properties_dialog_loaded_switch_cb), lun_dialog); + } + gtk_widget_set_halign(loaded_switch, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(advanced_grid), + loaded_switch, + 2, nrow++, // left top + 1, 1); // width height + + /* initially locked switch */ + locked_label = gtk_label_new("Initially locked:"); + gtk_grid_attach(GTK_GRID(advanced_grid), + locked_label, + 0, nrow, // left top + 2, 1); // width height + + locked_switch = gtk_switch_new(); + gtk_switch_set_state(GTK_SWITCH(locked_switch), FALSE); + gtk_widget_set_hexpand(locked_switch, FALSE); + if (lun_info) { + gtk_widget_set_child_visible(locked_switch, FALSE); + gtk_widget_set_child_visible(locked_label, FALSE); + } + gtk_widget_set_halign(locked_switch, GTK_ALIGN_START); + gtk_grid_attach(GTK_GRID(advanced_grid), + locked_switch, + 2, nrow++, // left top + 1, 1); // width height + + lun_dialog->dialog = dialog; + lun_dialog->advanced_grid = advanced_grid; + lun_dialog->advanced_shown = FALSE; + lun_dialog->file_entry = file_entry; + lun_dialog->vendor_entry = vendor_entry; + lun_dialog->product_entry = product_entry; + lun_dialog->revision_entry = revision_entry; + lun_dialog->loaded_switch = loaded_switch; + lun_dialog->locked_switch = locked_switch; + + gtk_widget_show_all(dialog); + gtk_widget_hide(advanced_grid); +} + +static void lun_properties_dialog_get_info(lun_properties_dialog *lun_dialog, + SpiceUsbDeviceLunInfo *lun_info) +{ + lun_info->file_path = gtk_entry_get_text(GTK_ENTRY(lun_dialog->file_entry)); + lun_info->vendor = gtk_entry_get_text(GTK_ENTRY(lun_dialog->vendor_entry)); + lun_info->product = gtk_entry_get_text(GTK_ENTRY(lun_dialog->product_entry)); + lun_info->revision = gtk_entry_get_text(GTK_ENTRY(lun_dialog->revision_entry)); + lun_info->loaded = gtk_switch_get_active(GTK_SWITCH(lun_dialog->loaded_switch)); + lun_info->locked = gtk_switch_get_active(GTK_SWITCH(lun_dialog->locked_switch)); +} + +/* Popup menu */ +static void view_popup_menu_on_eject(GtkWidget *menuitem, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view); + GtkTreeModel *tree_model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) { + if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) { + SpiceUsbDevice *usb_device; + gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1); + SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__); + } + else { + UsbWidgetLunItem *lun_item; + gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1); + spice_usb_device_manager_device_lun_load( + lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.loaded); + } + } + else { + SPICE_DEBUG("%s - failed to get selection", __FUNCTION__); + } +} + +static void view_popup_menu_on_lock(GtkWidget *menuitem, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view); + GtkTreeModel *tree_model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) { + if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) { + SpiceUsbDevice *usb_device; + gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1); + SPICE_DEBUG("%s - not applicable for USB device", __FUNCTION__); + } + else { + UsbWidgetLunItem *lun_item; + gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1); + spice_usb_device_manager_device_lun_lock( + lun_item->manager, lun_item->device, lun_item->lun, !lun_item->info.locked); + } + } + else { + SPICE_DEBUG("%s - failed to get selection", __FUNCTION__); + } +} + +static void view_popup_menu_on_remove(GtkWidget *menuitem, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view); + GtkTreeModel *tree_model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) { + if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) { + SpiceUsbDevice *usb_device; + gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&usb_device, -1); + SPICE_DEBUG("Remove USB device"); + } else { + UsbWidgetLunItem *lun_item; + gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1); + gtk_tree_selection_unselect_all(select); + spice_usb_device_manager_device_lun_remove(lun_item->manager, lun_item->device, lun_item->lun); + } + } else { + SPICE_DEBUG("Remove - failed to get selection"); + } +} + +static void view_popup_menu_on_settings(GtkWidget *menuitem, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeSelection *select = gtk_tree_view_get_selection(priv->cd_tree.tree_view); + GtkTreeModel *tree_model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected(select, &tree_model, &iter)) { + if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) { + SPICE_DEBUG("No settings for USB device yet"); + } else { + lun_properties_dialog lun_dialog; + UsbWidgetLunItem *lun_item; + gint resp; + + gtk_tree_model_get(tree_model, &iter, COL_ITEM_DATA, (gpointer *)&lun_item, -1); + gtk_tree_selection_unselect_all(select); + create_lun_properties_dialog(self, NULL, &lun_item->info, &lun_dialog); + + resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog)); + if (resp == GTK_RESPONSE_ACCEPT) { + SpiceUsbDeviceLunInfo lun_info; + SPICE_DEBUG("response is ACCEPT"); + lun_properties_dialog_get_info(&lun_dialog, &lun_info); + spice_usb_device_manager_device_lun_change_media( + priv->manager, lun_item->device, lun_item->lun, &lun_info); + } else { + SPICE_DEBUG("response is REJECT"); + } + gtk_widget_destroy(lun_dialog.dialog); + } + } else { + SPICE_DEBUG("Remove - failed to get selection"); + } +} + +static GtkWidget *view_popup_add_menu_item(GtkWidget *menu, + const gchar *label_str, + const gchar *icon_name, + GCallback cb_func, gpointer user_data) +{ + GtkWidget *menu_item = gtk_menu_item_new(); + create_image_button_box(label_str, icon_name, menu_item); + g_signal_connect(menu_item, "activate", cb_func, user_data); + + gtk_widget_show_all(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); + + return menu_item; +} + +static gboolean has_single_lun(SpiceUsbDeviceWidgetPrivate *priv, SpiceUsbDevice *device) +{ + gboolean result; + SpiceUsbDeviceManager *usb_dev_mgr = priv->manager; + GArray *lun_array; + lun_array = spice_usb_device_manager_get_device_luns(usb_dev_mgr, device); + result = lun_array && lun_array->len <= 1; + if (lun_array) { + g_array_unref(lun_array); + } + return result; +} + +static void view_popup_menu(GtkTreeView *tree_view, GdkEventButton *event, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeSelection *select; + GtkTreeModel *tree_model; + GtkTreeIter iter; + UsbWidgetLunItem *lun_item; + gboolean is_loaded, is_locked; + GtkTreeIter *usb_dev_iter; + gboolean is_dev_connected; + GtkWidget *menu; + + if (tree_view != priv->cd_tree.tree_view) { + SPICE_DEBUG("Not applicable for USB device"); + return; + } + + select = gtk_tree_view_get_selection(tree_view); + + if (!gtk_tree_selection_get_selected(select, &tree_model, &iter)) { + SPICE_DEBUG("No tree view row is selected"); + return; + } + if (!tree_item_is_lun(priv->cd_tree.tree_store, &iter)) { + SPICE_DEBUG("No settings for USB device yet"); + return; + } + + gtk_tree_model_get(tree_model, &iter, + COL_ITEM_DATA, (gpointer *)&lun_item, + COL_LOADED, &is_loaded, + COL_LOCKED, &is_locked, + -1); + + usb_dev_iter = usb_widget_tree_store_find_usb_device(priv->cd_tree.tree_store, lun_item->device); + if (usb_dev_iter != NULL) { + gtk_tree_model_get(tree_model, usb_dev_iter, + COL_CONNECTED, &is_dev_connected, + -1); + g_free(usb_dev_iter); + } else { + is_dev_connected = FALSE; + SPICE_DEBUG("Failed to find USB device for LUN: %s|%s", + lun_item->info.vendor, lun_item->info.product); + } + SPICE_DEBUG("Right-click on LUN: %s|%s, dev connected:%d, lun loaded:%d locked:%d", + lun_item->info.vendor, lun_item->info.product, + is_dev_connected, is_loaded, is_locked); + + /* Set up the menu */ + menu = gtk_menu_new(); + + view_popup_add_menu_item(menu, "_Settings", "preferences-system", + G_CALLBACK(view_popup_menu_on_settings), user_data); + if (is_loaded) { + if (!is_locked) { + view_popup_add_menu_item(menu, "_Lock", "system-lock-screen", + G_CALLBACK(view_popup_menu_on_lock), user_data); + view_popup_add_menu_item(menu, "_Eject", "media-eject", + G_CALLBACK(view_popup_menu_on_eject), user_data); + } else { + view_popup_add_menu_item(menu, "_Unlock", "system-lock-screen", + G_CALLBACK(view_popup_menu_on_lock), user_data); + } + } else { + view_popup_add_menu_item(menu, "_Load", "media-eject", + G_CALLBACK(view_popup_menu_on_eject), user_data); + } + + if (!is_dev_connected || has_single_lun(priv, lun_item->device)) { + view_popup_add_menu_item(menu, "_Remove", "edit-delete", + G_CALLBACK(view_popup_menu_on_remove), user_data); + } + + gtk_widget_show_all(menu); + gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL); +} + +static void treeview_select_current_row_by_pos(GtkTreeView *tree_view, gint x, gint y) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); + if (gtk_tree_selection_count_selected_rows(selection) <= 1) { + GtkTreePath *path; + /* Get tree path for row that was clicked */ + if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree_view), x, y, &path, NULL, NULL, NULL)) + { + gtk_tree_selection_unselect_all(selection); + gtk_tree_selection_select_path(selection, path); + gtk_tree_path_free(path); + } + } +} + +static gboolean treeview_on_right_button_pressed_cb(GtkWidget *view, GdkEventButton *event, gpointer user_data) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW(view); + /* single click with the right mouse button */ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + /* select the row that was clicked, it will also provide the context */ + treeview_select_current_row_by_pos(tree_view, (gint)event->x, (gint)event->y); + view_popup_menu(tree_view, event, user_data); + return TRUE; /* we handled this */ + } else { + return FALSE; /* we did not handle this */ + } +} + +static gboolean treeview_on_popup_key_pressed_cb(GtkWidget *view, gpointer user_data) +{ + view_popup_menu(GTK_TREE_VIEW(view), NULL, user_data); + return TRUE; /* we handled this */ +} + +/* Add LUN dialog */ + +static void add_cd_lun_button_clicked_cb(GtkWidget *add_cd_button, gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkWidget *parent_window = gtk_widget_get_toplevel(add_cd_button); + lun_properties_dialog lun_dialog; + gint resp; + + create_lun_properties_dialog(self, parent_window, NULL, &lun_dialog); + + resp = gtk_dialog_run(GTK_DIALOG(lun_dialog.dialog)); + if (resp == GTK_RESPONSE_ACCEPT) { + SpiceUsbDeviceLunInfo lun_info; + SPICE_DEBUG("response is ACCEPT"); + lun_properties_dialog_get_info(&lun_dialog, &lun_info); + spice_usb_device_manager_add_cd_lun(priv->manager, &lun_info); + } else { + SPICE_DEBUG("response is REJECT"); + } + gtk_widget_destroy(lun_dialog.dialog); +} + +static void spice_usb_device_widget_get_property(GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + + switch (prop_id) { + case PROP_SESSION: + g_value_set_object(value, priv->session); + break; + case PROP_DEVICE_FORMAT_STRING: + g_value_set_string(value, priv->device_format_string); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); + break; + } +} + +static void spice_usb_device_widget_set_property(GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(gobject); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + + switch (prop_id) { + case PROP_SESSION: + priv->session = g_value_dup_object(value); + break; + case PROP_DEVICE_FORMAT_STRING: + priv->device_format_string = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); + break; + } +} + +static void spice_usb_device_widget_hide_info_bar(SpiceUsbDeviceWidget *self) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + + if (priv->info_bar) { + gtk_widget_destroy(priv->info_bar); + priv->info_bar = NULL; + } +} + +static void +spice_usb_device_widget_show_info_bar(SpiceUsbDeviceWidget *self, + const gchar *message, + GtkMessageType message_type, + GdkPixbuf *icon_pixbuf) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkWidget *info_bar, *content_area, *hbox, *icon, *label; + + spice_usb_device_widget_hide_info_bar(self); + + info_bar = gtk_info_bar_new(); + gtk_info_bar_set_message_type(GTK_INFO_BAR(info_bar), message_type); + + content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar)); + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add(GTK_CONTAINER(content_area), hbox); + + icon = gtk_image_new_from_pixbuf(icon_pixbuf); + gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 10); + + label = gtk_label_new(message); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + + priv->info_bar = gtk_alignment_new(0.0, 0.0, 1.0, 0.0); + gtk_alignment_set_padding(GTK_ALIGNMENT(priv->info_bar), 0, 0, 0, 0); + gtk_container_add(GTK_CONTAINER(priv->info_bar), info_bar); + + gtk_box_pack_start(GTK_BOX(self), priv->info_bar, FALSE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(self), priv->info_bar, 1); /* put after the lable */ + gtk_widget_show_all(priv->info_bar); +} + +static void spice_usb_device_widget_create_tree_view(SpiceUsbDeviceWidget *self, gboolean is_cd) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + GtkTreeStore *tree_store = usb_widget_create_tree_store(); + GtkTreeView *tree_view = GTK_TREE_VIEW(gtk_tree_view_new()); + + if (is_cd) { + priv->cd_tree.tree_view = tree_view; + priv->cd_tree.tree_store = tree_store; + } else { + priv->usb_tree.tree_view = tree_view; + priv->usb_tree.tree_store = tree_store; + } + + gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(tree_store)); + g_object_unref(tree_store); /* destroy tree_store automatically with tree_view */ + + view_add_toggle_column(self, COL_REDIRECT, COL_DEV_ITEM, COL_CAN_REDIRECT, + is_cd ? tree_item_toggled_cb_redirect_cd : tree_item_toggled_cb_redirect_usb, is_cd); + + view_add_text_column(self, COL_ADDRESS, FALSE, is_cd); + + view_add_pixbuf_column(self, COL_CONNECT_ICON, COL_REDIRECT, is_cd); + if (is_cd) { + view_add_pixbuf_column(self, COL_CD_ICON, COL_CD_DEV, is_cd); + } + + view_add_text_column(self, COL_VENDOR, TRUE, is_cd); + view_add_text_column(self, COL_PRODUCT, TRUE, is_cd); + + if (is_cd) { + view_add_text_column(self, COL_FILE, TRUE, is_cd); + view_add_read_only_toggle_column(self, COL_LOADED, COL_LUN_ITEM, is_cd); + view_add_read_only_toggle_column(self, COL_LOCKED, COL_LUN_ITEM, is_cd); + // uncomment to show also 'idle' column for CD + //view_add_read_only_toggle_column(self, COL_IDLE, COL_LUN_ITEM, is_cd); + } + + gtk_tree_selection_set_mode( + gtk_tree_view_get_selection(tree_view), + GTK_SELECTION_NONE); + + if (is_cd) { + set_selection_handler(tree_view); + } +} + +static void spice_usb_device_widget_signals_connect(SpiceUsbDeviceWidget *self) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + + g_signal_connect(priv->manager, "device-added", + G_CALLBACK(device_added_cb), self); + g_signal_connect(priv->manager, "device-removed", + G_CALLBACK(device_removed_cb), self); + g_signal_connect(priv->manager, "device-changed", + G_CALLBACK(device_changed_cb), self); + // TODO: connect failed + g_signal_connect(priv->manager, "device-error", + G_CALLBACK(device_error_cb), self); + + g_signal_connect(priv->cd_tree.tree_view, "button-press-event", + G_CALLBACK(treeview_on_right_button_pressed_cb), self); + g_signal_connect(priv->cd_tree.tree_view, "popup-menu", + G_CALLBACK(treeview_on_popup_key_pressed_cb), self); +} + +static void create_tree_window(SpiceUsbDeviceWidget *self, GtkTreeView *tree_view) +{ + GtkWidget *sw; + /* scrolled window */ + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), + GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_set_hexpand(sw, TRUE); + gtk_widget_set_halign(sw, GTK_ALIGN_FILL); + gtk_widget_set_vexpand(sw, TRUE); + gtk_widget_set_valign(sw, GTK_ALIGN_FILL); + gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(tree_view)); + gtk_box_pack_start(GTK_BOX(self), sw, TRUE, TRUE, 0); +} + +static void spice_usb_device_widget_constructed(GObject *gobject) +{ + SpiceUsbDeviceWidget *self; + GtkRequisition min_size, natural_size; + SpiceUsbDeviceWidgetPrivate *priv; + GtkWidget *hbox, *dev_label; + GtkWidget *add_cd_button, *add_cd_icon; + GPtrArray *devices = NULL; + GError *err = NULL; + gchar *str; + guint i; + gboolean cd_sharing_enabled = TRUE; + #ifndef USE_CD_SHARING + cd_sharing_enabled = FALSE; + #endif + + self = SPICE_USB_DEVICE_WIDGET(gobject); + priv = self->priv; + if (!priv->session) + g_error("SpiceUsbDeviceWidget constructed without a session"); + + min_size.width = 600; + min_size.height = 300; + natural_size.width = 1200; + natural_size.height = 600; + gtk_widget_get_preferred_size(GTK_WIDGET(self), &min_size, &natural_size); + + priv->label = gtk_label_new(NULL); + str = g_strdup_printf("<b>%s</b>", _("Select USB devices to redirect")); + gtk_label_set_markup(GTK_LABEL(priv->label), str); + g_free(str); + gtk_box_pack_start(GTK_BOX(self), priv->label, FALSE, FALSE, 0); + + priv->icon_cd = get_named_icon("media-optical", GTK_ICON_SIZE_LARGE_TOOLBAR); + priv->icon_connected = get_named_icon("network-transmit-receive", GTK_ICON_SIZE_LARGE_TOOLBAR); + priv->icon_disconn = get_named_icon("network-offline", GTK_ICON_SIZE_LARGE_TOOLBAR); + priv->icon_warning = get_named_icon("dialog-warning", GTK_ICON_SIZE_LARGE_TOOLBAR); + priv->icon_info = get_named_icon("dialog-information", GTK_ICON_SIZE_LARGE_TOOLBAR); + + priv->manager = spice_usb_device_manager_get(priv->session, &err); + if (err) { + spice_usb_device_widget_show_info_bar(self, err->message, + GTK_MESSAGE_WARNING, priv->icon_warning); + g_clear_error(&err); + return; + } + + if (cd_sharing_enabled) { + spice_usb_device_widget_create_tree_view(self, TRUE); + } else { + priv->cd_tree.tree_store = NULL; + priv->cd_tree.tree_view = NULL; + } + spice_usb_device_widget_create_tree_view(self, FALSE); + + spice_usb_device_widget_signals_connect(self); + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0); + + /* "Available devices" label - in hbox */ + dev_label = gtk_label_new(_("Local USB devices")); + gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0); + create_tree_window(self, priv->usb_tree.tree_view); + + if (cd_sharing_enabled) { + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(self), hbox, FALSE, FALSE, 0); + dev_label = gtk_label_new(_("Shared CD devices")); + gtk_box_pack_start(GTK_BOX(hbox), dev_label, TRUE, FALSE, 0); + /* "Add CD" button - in hbox */ + add_cd_button = gtk_button_new_with_label(_("Add CD")); + gtk_button_set_always_show_image(GTK_BUTTON(add_cd_button), TRUE); + add_cd_icon = gtk_image_new_from_icon_name("list-add", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(add_cd_button), add_cd_icon); + + gtk_widget_set_halign(add_cd_button, GTK_ALIGN_END); + g_signal_connect(add_cd_button, "clicked", G_CALLBACK(add_cd_lun_button_clicked_cb), self); + gtk_box_pack_start(GTK_BOX(hbox), add_cd_button, FALSE, FALSE, 0); + create_tree_window(self, priv->cd_tree.tree_view); + } + devices = spice_usb_device_manager_get_devices(priv->manager); + if (!devices) + goto end; + + for (i = 0; i < devices->len; i++) { + SpiceUsbDevice *usb_device = g_ptr_array_index(devices, i); + usb_widget_add_device(self, usb_device, NULL); + } + g_ptr_array_unref(devices); + + select_widget_size(GTK_WIDGET(self)); + +end: + spice_usb_device_widget_update_status(self); +} + +static void spice_usb_device_widget_finalize(GObject *object) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(object); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + + if (priv->manager) { + g_signal_handlers_disconnect_by_func(priv->manager, + device_added_cb, self); + g_signal_handlers_disconnect_by_func(priv->manager, + device_removed_cb, self); + g_signal_handlers_disconnect_by_func(priv->manager, + device_changed_cb, self); + g_signal_handlers_disconnect_by_func(priv->manager, + device_error_cb, self); + } + g_object_unref(priv->session); + g_free(priv->device_format_string); + + if (G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize) + G_OBJECT_CLASS(spice_usb_device_widget_parent_class)->finalize(object); +} + +static void spice_usb_device_widget_class_init( + SpiceUsbDeviceWidgetClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *)klass; + GParamSpec *pspec; + + g_type_class_add_private (klass, sizeof (SpiceUsbDeviceWidgetPrivate)); + + gobject_class->constructed = spice_usb_device_widget_constructed; + gobject_class->finalize = spice_usb_device_widget_finalize; + gobject_class->get_property = spice_usb_device_widget_get_property; + gobject_class->set_property = spice_usb_device_widget_set_property; + + /** + * SpiceUsbDeviceWidget:session: + * + * #SpiceSession this #SpiceUsbDeviceWidget is associated with + * + **/ + pspec = g_param_spec_object("session", + "Session", + "SpiceSession", + SPICE_TYPE_SESSION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_property(gobject_class, PROP_SESSION, pspec); + + /** + * SpiceUsbDeviceWidget:device-format-string: + * + * Format string to pass to spice_usb_device_get_description() for getting + * the device USB descriptions. + */ + pspec = g_param_spec_string("device-format-string", + "Device format string", + "Format string for device description", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_property(gobject_class, PROP_DEVICE_FORMAT_STRING, + pspec); + + /** + * SpiceUsbDeviceWidget::connect-failed: + * @widget: The #SpiceUsbDeviceWidget that emitted the signal + * @device: #SpiceUsbDevice boxed object corresponding to the added device + * @error: #GError describing the reason why the connect failed + * + * The #SpiceUsbDeviceWidget::connect-failed signal is emitted whenever + * the user has requested for a device to be redirected and this has + * failed. + **/ + signals[CONNECT_FAILED] = + g_signal_new("connect-failed", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(SpiceUsbDeviceWidgetClass, connect_failed), + NULL, NULL, + g_cclosure_user_marshal_VOID__BOXED_BOXED, + G_TYPE_NONE, + 2, + SPICE_TYPE_USB_DEVICE, + G_TYPE_ERROR); +} + +static void spice_usb_device_widget_init(SpiceUsbDeviceWidget *self) +{ + self->priv = SPICE_USB_DEVICE_WIDGET_GET_PRIVATE(self); +} + +/* ------------------------------------------------------------------ */ +/* public api */ + +/** + * spice_usb_device_widget_new: + * @session: #SpiceSession for which to widget will control USB redirection + * @device_format_string: (allow-none): String passed to + * spice_usb_device_get_description() + * + * Creates a new widget to control USB redirection. + * + * Returns: a new #SpiceUsbDeviceWidget instance + */ +GtkWidget *spice_usb_device_widget_new(SpiceSession *session, + const gchar *device_format_string) +{ + static gboolean init_columns = TRUE; + spice_util_get_debug(); + if (init_columns) { + initialize_columns(); + init_columns = FALSE; + } + return g_object_new(SPICE_TYPE_USB_DEVICE_WIDGET, + "orientation", GTK_ORIENTATION_VERTICAL, + "session", session, + "device-format-string", device_format_string, + "spacing", 6, + NULL); +} + +/* ------------------------------------------------------------------ */ +/* callbacks */ + +static gboolean usb_widget_tree_store_check_redirect_foreach_cb(GtkTreeModel *tree_model, + GtkTreePath *path, + GtkTreeIter *iter, + SpiceUsbDeviceWidget *self, + GtkTreeStore *tree_store) +{ + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + + if (!tree_item_is_lun(tree_store, iter)) { + SpiceUsbDevice *usb_device; + gboolean can_redirect; + + gtk_tree_model_get(tree_model, iter, + COL_ITEM_DATA, (gpointer *)&usb_device, + -1); + + if (spice_usb_device_manager_is_redirecting(priv->manager)) { + can_redirect = FALSE; + } else { + GError *err = NULL; + + can_redirect = spice_usb_device_manager_can_redirect_device(priv->manager, + usb_device, &err); + + /* If we cannot redirect this device, append the error message to + err_msg, but only if it is *not* already there! */ + if (!can_redirect) { + if (priv->err_msg) { + if (!strstr(priv->err_msg, err->message)) { + gchar *old_err_msg = priv->err_msg; + priv->err_msg = g_strdup_printf("%s\n%s", priv->err_msg, + err->message); + g_free(old_err_msg); + } + } else { + priv->err_msg = g_strdup(err->message); + } + } + g_clear_error(&err); + } + gtk_tree_store_set(tree_store, iter, + COL_CAN_REDIRECT, can_redirect, + -1); + } + return FALSE; /* continue iterating */ +} + +static gboolean usb_widget_tree_store_check_redirect_foreach_cb_usb(GtkTreeModel *tree_model, + GtkTreePath *path, GtkTreeIter *iter, + gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + return usb_widget_tree_store_check_redirect_foreach_cb( + tree_model, path, iter, self, priv->usb_tree.tree_store); +} + +#ifdef USE_CD_SHARING +static gboolean usb_widget_tree_store_check_redirect_foreach_cb_cd(GtkTreeModel *tree_model, + GtkTreePath *path, GtkTreeIter *iter, + gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + return usb_widget_tree_store_check_redirect_foreach_cb( + tree_model, path, iter, self, priv->cd_tree.tree_store); +} +#endif + +static gboolean spice_usb_device_widget_update_status(gpointer user_data) +{ + SpiceUsbDeviceWidget *self = SPICE_USB_DEVICE_WIDGET(user_data); + SpiceUsbDeviceWidgetPrivate *priv = self->priv; + gchar *str, *markup_str; + const gchar *free_channels_str; + int free_channels; + + g_object_get(priv->manager, "free-channels", &free_channels, NULL); + free_channels_str = ngettext(_("Select USB devices to redirect (%d free channel)"), + _("Select USB devices to redirect (%d free channels)"), + free_channels); + str = g_strdup_printf(free_channels_str, free_channels); + markup_str = g_strdup_printf("<b>%s</b>", str); + gtk_label_set_markup(GTK_LABEL (priv->label), markup_str); + g_free(markup_str); + g_free(str); + + gtk_tree_model_foreach(GTK_TREE_MODEL(priv->usb_tree.tree_store), + usb_widget_tree_store_check_redirect_foreach_cb_usb, self); + gtk_widget_show_all(GTK_WIDGET(priv->usb_tree.tree_view)); + +#ifdef USE_CD_SHARING + gtk_tree_model_foreach(GTK_TREE_MODEL(priv->cd_tree.tree_store), + usb_widget_tree_store_check_redirect_foreach_cb_cd, self); + gtk_widget_show_all(GTK_WIDGET(priv->cd_tree.tree_view)); +#endif + + /* Show messages in the info, if necessary */ + if (priv->err_msg) { + spice_usb_device_widget_show_info_bar(self, priv->err_msg, + GTK_MESSAGE_INFO, priv->icon_warning); + g_free(priv->err_msg); + priv->err_msg = NULL; + } else if ( spice_usb_device_manager_is_redirecting(priv->manager)) { + spice_usb_device_widget_show_info_bar(self, _("Redirecting USB Device..."), + GTK_MESSAGE_INFO, priv->icon_info); + } else { + spice_usb_device_widget_hide_info_bar(self); + } + + if (priv->device_count == 0) + spice_usb_device_widget_show_info_bar(self, _("No USB devices detected"), + GTK_MESSAGE_INFO, priv->icon_info); + + return FALSE; +} + +#endif -- 2.9.4 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel