summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Storage/UsbMsd.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Storage/UsbMsd.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Storage/UsbMsd.cpp')
-rw-r--r--src/VBox/Devices/Storage/UsbMsd.cpp2416
1 files changed, 2416 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/UsbMsd.cpp b/src/VBox/Devices/Storage/UsbMsd.cpp
new file mode 100644
index 00000000..80e7f94d
--- /dev/null
+++ b/src/VBox/Devices/Storage/UsbMsd.cpp
@@ -0,0 +1,2416 @@
+/* $Id: UsbMsd.cpp $ */
+/** @file
+ * UsbMSD - USB Mass Storage Device Emulation.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_USB_MSD
+#include <VBox/vmm/pdmusb.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/scsi.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name USB MSD string IDs
+ * @{ */
+#define USBMSD_STR_ID_MANUFACTURER 1
+#define USBMSD_STR_ID_PRODUCT_HD 2
+#define USBMSD_STR_ID_PRODUCT_CDROM 3
+/** @} */
+
+/** @name USB MSD vendor and product IDs
+ * @{ */
+#define VBOX_USB_VENDOR 0x80EE
+#define USBMSD_PID_HD 0x0030
+#define USBMSD_PID_CD 0x0031
+/** @} */
+
+/** Saved state version. */
+#define USB_MSD_SAVED_STATE_VERSION 2
+/** Saved state vesion before the cleanup. */
+#define USB_MSD_SAVED_STATE_VERSION_PRE_CLEANUP 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * USB MSD Command Block Wrapper or CBW. The command block
+ * itself (CBWCB) contains protocol-specific data (here SCSI).
+ */
+#pragma pack(1)
+typedef struct USBCBW
+{
+ uint32_t dCBWSignature;
+#define USBCBW_SIGNATURE UINT32_C(0x43425355)
+ uint32_t dCBWTag;
+ uint32_t dCBWDataTransferLength;
+ uint8_t bmCBWFlags;
+#define USBCBW_DIR_MASK RT_BIT(7)
+#define USBCBW_DIR_OUT 0
+#define USBCBW_DIR_IN RT_BIT(7)
+ uint8_t bCBWLun;
+ uint8_t bCBWCBLength;
+ uint8_t CBWCB[16];
+} USBCBW;
+#pragma pack()
+AssertCompileSize(USBCBW, 31);
+/** Pointer to a Command Block Wrapper. */
+typedef USBCBW *PUSBCBW;
+/** Pointer to a const Command Block Wrapper. */
+typedef const USBCBW *PCUSBCBW;
+
+/**
+ * USB MSD Command Status Wrapper or CSW.
+ */
+#pragma pack(1)
+typedef struct USBCSW
+{
+ uint32_t dCSWSignature;
+#define USBCSW_SIGNATURE UINT32_C(0x53425355)
+ uint32_t dCSWTag;
+ uint32_t dCSWDataResidue;
+#define USBCSW_STATUS_OK UINT8_C(0)
+#define USBCSW_STATUS_FAILED UINT8_C(1)
+#define USBCSW_STATUS_PHASE_ERROR UINT8_C(2)
+ uint8_t bCSWStatus;
+} USBCSW;
+#pragma pack()
+AssertCompileSize(USBCSW, 13);
+/** Pointer to a Command Status Wrapper. */
+typedef USBCSW *PUSBCSW;
+/** Pointer to a const Command Status Wrapper. */
+typedef const USBCSW *PCUSBCSW;
+
+
+/**
+ * The USB MSD request state.
+ */
+typedef enum USBMSDREQSTATE
+{
+ /** Invalid status. */
+ USBMSDREQSTATE_INVALID = 0,
+ /** Ready to receive a new SCSI command. */
+ USBMSDREQSTATE_READY,
+ /** Waiting for the host to supply data. */
+ USBMSDREQSTATE_DATA_FROM_HOST,
+ /** The SCSI request is being executed by the driver. */
+ USBMSDREQSTATE_EXECUTING,
+ /** Have (more) data for the host. */
+ USBMSDREQSTATE_DATA_TO_HOST,
+ /** Waiting to supply status information to the host. */
+ USBMSDREQSTATE_STATUS,
+ /** Destroy the request upon completion.
+ * This is set when the SCSI request doesn't complete before for the device or
+ * mass storage reset operation times out. USBMSD::pReq will be set to NULL
+ * and the only reference to this request will be with DrvSCSI. */
+ USBMSDREQSTATE_DESTROY_ON_COMPLETION,
+ /** The end of the valid states. */
+ USBMSDREQSTATE_END,
+ /** 32bit blow up hack. */
+ USBMSDREQSTATE_32BIT_HACK = 0x7fffffff
+} USBMSDREQSTATE;
+
+
+/**
+ * A pending USB MSD request.
+ */
+typedef struct USBMSDREQ
+{
+ /** The state of the request. */
+ USBMSDREQSTATE enmState;
+ /** The I/O requesthandle .*/
+ PDMMEDIAEXIOREQ hIoReq;
+ /** The size of the data buffer. */
+ uint32_t cbBuf;
+ /** Pointer to the data buffer. */
+ uint8_t *pbBuf;
+ /** Current buffer offset. */
+ uint32_t offBuf;
+ /** The current Cbw when we're in the pending state. */
+ USBCBW Cbw;
+ /** The status of a completed SCSI request. */
+ uint8_t iScsiReqStatus;
+} USBMSDREQ;
+/** Pointer to a USB MSD request. */
+typedef USBMSDREQ *PUSBMSDREQ;
+
+
+/**
+ * Endpoint status data.
+ */
+typedef struct USBMSDEP
+{
+ bool fHalted;
+} USBMSDEP;
+/** Pointer to the endpoint status. */
+typedef USBMSDEP *PUSBMSDEP;
+
+
+/**
+ * A URB queue.
+ */
+typedef struct USBMSDURBQUEUE
+{
+ /** The head pointer. */
+ PVUSBURB pHead;
+ /** Where to insert the next entry. */
+ PVUSBURB *ppTail;
+} USBMSDURBQUEUE;
+/** Pointer to a URB queue. */
+typedef USBMSDURBQUEUE *PUSBMSDURBQUEUE;
+/** Pointer to a const URB queue. */
+typedef USBMSDURBQUEUE const *PCUSBMSDURBQUEUE;
+
+
+/**
+ * The USB MSD instance data.
+ */
+typedef struct USBMSD
+{
+ /** Pointer back to the PDM USB Device instance structure. */
+ PPDMUSBINS pUsbIns;
+ /** Critical section protecting the device state. */
+ RTCRITSECT CritSect;
+
+ /** The current configuration.
+ * (0 - default, 1 - the only, i.e configured.) */
+ uint8_t bConfigurationValue;
+ /** Endpoint 0 is the default control pipe, 1 is the host->dev bulk pipe and 2
+ * is the dev->host one. */
+ USBMSDEP aEps[3];
+ /** The current request. */
+ PUSBMSDREQ pReq;
+
+ /** Pending to-host queue.
+ * The URBs waiting here are pending the completion of the current request and
+ * data or status to become available.
+ */
+ USBMSDURBQUEUE ToHostQueue;
+
+ /** Done queue
+ * The URBs stashed here are waiting to be reaped. */
+ USBMSDURBQUEUE DoneQueue;
+ /** Signalled when adding an URB to the done queue and fHaveDoneQueueWaiter
+ * is set. */
+ RTSEMEVENT hEvtDoneQueue;
+ /** Someone is waiting on the done queue. */
+ bool fHaveDoneQueueWaiter;
+
+ /** Whether to signal the reset semaphore when the current request completes. */
+ bool fSignalResetSem;
+ /** Semaphore usbMsdUsbReset waits on when a request is executing at reset
+ * time. Only signalled when fSignalResetSem is set. */
+ RTSEMEVENTMULTI hEvtReset;
+ /** The reset URB.
+ * This is waiting for SCSI request completion before finishing the reset. */
+ PVUSBURB pResetUrb;
+ /** Indicates that PDMUsbHlpAsyncNotificationCompleted should be called when
+ * the MSD is entering the idle state. */
+ volatile bool fSignalIdle;
+
+ /** Indicates that this device is a CD-ROM. */
+ bool fIsCdrom;
+
+ /**
+ * LUN\#0 data.
+ */
+ struct
+ {
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ /** The media port interface fo LUN\#0. */
+ PDMIMEDIAPORT IMediaPort;
+ /** The extended media port interface for LUN\#0 */
+ PDMIMEDIAEXPORT IMediaExPort;
+
+ /** The base interface for the SCSI driver connected to LUN\#0. */
+ PPDMIBASE pIBase;
+ /** The media interface for th SCSI drver conected to LUN\#0. */
+ PPDMIMEDIA pIMedia;
+ /** The extended media inerface for the SCSI driver connected to LUN\#0. */
+ PPDMIMEDIAEX pIMediaEx;
+ } Lun0;
+
+} USBMSD;
+/** Pointer to the USB MSD instance data. */
+typedef USBMSD *PUSBMSD;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const PDMUSBDESCCACHESTRING g_aUsbMsdStrings_en_US[] =
+{
+ { USBMSD_STR_ID_MANUFACTURER, "VirtualBox" },
+ { USBMSD_STR_ID_PRODUCT_HD, "USB Harddisk" },
+ { USBMSD_STR_ID_PRODUCT_CDROM, "USB CD-ROM" }
+};
+
+static const PDMUSBDESCCACHELANG g_aUsbMsdLanguages[] =
+{
+ { 0x0409, RT_ELEMENTS(g_aUsbMsdStrings_en_US), g_aUsbMsdStrings_en_US }
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbMsdEndpointDescsFS[2] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 64 /* maximum possible */,
+ /* .bInterval = */ 0 /* not applicable for bulk EP */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ },
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x02 /* ep=2, out */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 64 /* maximum possible */,
+ /* .bInterval = */ 0 /* not applicable for bulk EP */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ }
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbMsdEndpointDescsHS[2] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 512 /* HS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ },
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x02 /* ep=2, out */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 512 /* HS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ NULL,
+ /* .cbSsepc = */ 0
+ }
+};
+
+static const VUSBDESCSSEPCOMPANION g_aUsbMsdEpCompanionSS =
+{
+ /* .bLength = */ sizeof(VUSBDESCSSEPCOMPANION),
+ /* .bDescriptorType = */ VUSB_DT_SS_ENDPOINT_COMPANION,
+ /* .bMaxBurst = */ 15 /* we can burst all the way */,
+ /* .bmAttributes = */ 0 /* no streams */,
+ /* .wBytesPerInterval = */ 0 /* not a periodic endpoint */
+};
+
+static const VUSBDESCENDPOINTEX g_aUsbMsdEndpointDescsSS[2] =
+{
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x81 /* ep=1, in */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 1024 /* SS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ &g_aUsbMsdEpCompanionSS,
+ /* .cbSsepc = */ sizeof(g_aUsbMsdEpCompanionSS)
+ },
+ {
+ {
+ /* .bLength = */ sizeof(VUSBDESCENDPOINT),
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0x02 /* ep=2, out */,
+ /* .bmAttributes = */ 2 /* bulk */,
+ /* .wMaxPacketSize = */ 1024 /* SS bulk packet size */,
+ /* .bInterval = */ 0 /* no NAKs */
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ /* .pvSsepc = */ &g_aUsbMsdEpCompanionSS,
+ /* .cbSsepc = */ sizeof(g_aUsbMsdEpCompanionSS)
+ }
+};
+
+static const VUSBDESCINTERFACEEX g_UsbMsdInterfaceDescFS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 2,
+ /* .bInterfaceClass = */ 8 /* Mass Storage */,
+ /* .bInterfaceSubClass = */ 6 /* SCSI transparent command set */,
+ /* .bInterfaceProtocol = */ 0x50 /* Bulk-Only Transport */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ &g_aUsbMsdEndpointDescsFS[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBDESCINTERFACEEX g_UsbMsdInterfaceDescHS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 2,
+ /* .bInterfaceClass = */ 8 /* Mass Storage */,
+ /* .bInterfaceSubClass = */ 6 /* SCSI transparent command set */,
+ /* .bInterfaceProtocol = */ 0x50 /* Bulk-Only Transport */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ &g_aUsbMsdEndpointDescsHS[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBDESCINTERFACEEX g_UsbMsdInterfaceDescSS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCINTERFACE),
+ /* .bDescriptorType = */ VUSB_DT_INTERFACE,
+ /* .bInterfaceNumber = */ 0,
+ /* .bAlternateSetting = */ 0,
+ /* .bNumEndpoints = */ 2,
+ /* .bInterfaceClass = */ 8 /* Mass Storage */,
+ /* .bInterfaceSubClass = */ 6 /* SCSI transparent command set */,
+ /* .bInterfaceProtocol = */ 0x50 /* Bulk-Only Transport */,
+ /* .iInterface = */ 0
+ },
+ /* .pvMore = */ NULL,
+ /* .pvClass = */ NULL,
+ /* .cbClass = */ 0,
+ &g_aUsbMsdEndpointDescsSS[0],
+ /* .pIAD = */ NULL,
+ /* .cbIAD = */ 0
+};
+
+static const VUSBINTERFACE g_aUsbMsdInterfacesFS[] =
+{
+ { &g_UsbMsdInterfaceDescFS, /* .cSettings = */ 1 },
+};
+
+static const VUSBINTERFACE g_aUsbMsdInterfacesHS[] =
+{
+ { &g_UsbMsdInterfaceDescHS, /* .cSettings = */ 1 },
+};
+
+static const VUSBINTERFACE g_aUsbMsdInterfacesSS[] =
+{
+ { &g_UsbMsdInterfaceDescSS, /* .cSettings = */ 1 },
+};
+
+static const VUSBDESCCONFIGEX g_UsbMsdConfigDescFS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbMsdInterfacesFS),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbMsdInterfacesFS[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCCONFIGEX g_UsbMsdConfigDescHS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbMsdInterfacesHS),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbMsdInterfacesHS[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCCONFIGEX g_UsbMsdConfigDescSS =
+{
+ {
+ /* .bLength = */ sizeof(VUSBDESCCONFIG),
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .wTotalLength = */ 0 /* recalculated on read */,
+ /* .bNumInterfaces = */ RT_ELEMENTS(g_aUsbMsdInterfacesSS),
+ /* .bConfigurationValue =*/ 1,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ RT_BIT(7),
+ /* .MaxPower = */ 50 /* 100mA */
+ },
+ NULL, /* pvMore */
+ NULL, /* pvClass */
+ 0, /* cbClass */
+ &g_aUsbMsdInterfacesSS[0],
+ NULL /* pvOriginal */
+};
+
+static const VUSBDESCDEVICE g_UsbMsdDeviceDesc20 =
+{
+ /* .bLength = */ sizeof(g_UsbMsdDeviceDesc20),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x200, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 64,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_HD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_HD,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbCdDeviceDesc20 =
+{
+ /* .bLength = */ sizeof(g_UsbCdDeviceDesc20),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x200, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 64,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_CD,
+ /* .bcdDevice = */ 0x0100, /* 1.0 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_CDROM,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbMsdDeviceDesc30 =
+{
+ /* .bLength = */ sizeof(g_UsbMsdDeviceDesc30),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x300, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 9 /* 512, the only option for USB3. */,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_HD,
+ /* .bcdDevice = */ 0x0110, /* 1.10 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_HD,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDESCDEVICE g_UsbCdDeviceDesc30 =
+{
+ /* .bLength = */ sizeof(g_UsbCdDeviceDesc30),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE,
+ /* .bcdUsb = */ 0x300, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 9 /* 512, the only option for USB3. */,
+ /* .idVendor = */ VBOX_USB_VENDOR,
+ /* .idProduct = */ USBMSD_PID_CD,
+ /* .bcdDevice = */ 0x0110, /* 1.10 */
+ /* .iManufacturer = */ USBMSD_STR_ID_MANUFACTURER,
+ /* .iProduct = */ USBMSD_STR_ID_PRODUCT_CDROM,
+ /* .iSerialNumber = */ 0,
+ /* .bNumConfigurations = */ 1
+};
+
+static const VUSBDEVICEQUALIFIER g_UsbMsdDeviceQualifier =
+{
+ /* .bLength = */ sizeof(g_UsbMsdDeviceQualifier),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE_QUALIFIER,
+ /* .bcdUsb = */ 0x200, /* USB 2.0 */
+ /* .bDeviceClass = */ 0 /* Class specified in the interface desc. */,
+ /* .bDeviceSubClass = */ 0 /* Subclass specified in the interface desc. */,
+ /* .bDeviceProtocol = */ 0 /* Protocol specified in the interface desc. */,
+ /* .bMaxPacketSize0 = */ 64,
+ /* .bNumConfigurations = */ 1,
+ /* .bReserved = */ 0
+};
+
+static const struct {
+ VUSBDESCBOS bos;
+ VUSBDESCSSDEVCAP sscap;
+} g_UsbMsdBOS =
+{
+ {
+ /* .bLength = */ sizeof(g_UsbMsdBOS.bos),
+ /* .bDescriptorType = */ VUSB_DT_BOS,
+ /* .wTotalLength = */ sizeof(g_UsbMsdBOS),
+ /* .bNumDeviceCaps = */ 1
+ },
+ {
+ /* .bLength = */ sizeof(VUSBDESCSSDEVCAP),
+ /* .bDescriptorType = */ VUSB_DT_DEVICE_CAPABILITY,
+ /* .bDevCapabilityType = */ VUSB_DCT_SUPERSPEED_USB,
+ /* .bmAttributes = */ 0 /* No LTM. */,
+ /* .wSpeedsSupported = */ 0xe /* Any speed is good. */,
+ /* .bFunctionalitySupport = */ 2 /* Want HS at least. */,
+ /* .bU1DevExitLat = */ 0, /* We are blazingly fast. */
+ /* .wU2DevExitLat = */ 0
+ }
+};
+
+static const PDMUSBDESCCACHE g_UsbMsdDescCacheFS =
+{
+ /* .pDevice = */ &g_UsbMsdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescFS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbCdDescCacheFS =
+{
+ /* .pDevice = */ &g_UsbCdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescFS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbMsdDescCacheHS =
+{
+ /* .pDevice = */ &g_UsbMsdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescHS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbCdDescCacheHS =
+{
+ /* .pDevice = */ &g_UsbCdDeviceDesc20,
+ /* .paConfigs = */ &g_UsbMsdConfigDescHS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbMsdDescCacheSS =
+{
+ /* .pDevice = */ &g_UsbMsdDeviceDesc30,
+ /* .paConfigs = */ &g_UsbMsdConfigDescSS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+static const PDMUSBDESCCACHE g_UsbCdDescCacheSS =
+{
+ /* .pDevice = */ &g_UsbCdDeviceDesc30,
+ /* .paConfigs = */ &g_UsbMsdConfigDescSS,
+ /* .paLanguages = */ g_aUsbMsdLanguages,
+ /* .cLanguages = */ RT_ELEMENTS(g_aUsbMsdLanguages),
+ /* .fUseCachedDescriptors = */ true,
+ /* .fUseCachedStringsDescriptors = */ true
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int usbMsdHandleBulkDevToHost(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb);
+
+
+/**
+ * Initializes an URB queue.
+ *
+ * @param pQueue The URB queue.
+ */
+static void usbMsdQueueInit(PUSBMSDURBQUEUE pQueue)
+{
+ pQueue->pHead = NULL;
+ pQueue->ppTail = &pQueue->pHead;
+}
+
+
+
+/**
+ * Inserts an URB at the end of the queue.
+ *
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to insert.
+ */
+DECLINLINE(void) usbMsdQueueAddTail(PUSBMSDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ pUrb->Dev.pNext = NULL;
+ *pQueue->ppTail = pUrb;
+ pQueue->ppTail = &pUrb->Dev.pNext;
+}
+
+
+/**
+ * Unlinks the head of the queue and returns it.
+ *
+ * @returns The head entry.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(PVUSBURB) usbMsdQueueRemoveHead(PUSBMSDURBQUEUE pQueue)
+{
+ PVUSBURB pUrb = pQueue->pHead;
+ if (pUrb)
+ {
+ PVUSBURB pNext = pUrb->Dev.pNext;
+ pQueue->pHead = pNext;
+ if (!pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ else
+ pUrb->Dev.pNext = NULL;
+ }
+ return pUrb;
+}
+
+
+/**
+ * Removes an URB from anywhere in the queue.
+ *
+ * @returns true if found, false if not.
+ * @param pQueue The URB queue.
+ * @param pUrb The URB to remove.
+ */
+DECLINLINE(bool) usbMsdQueueRemove(PUSBMSDURBQUEUE pQueue, PVUSBURB pUrb)
+{
+ PVUSBURB pCur = pQueue->pHead;
+ if (pCur == pUrb)
+ pQueue->pHead = pUrb->Dev.pNext;
+ else
+ {
+ while (pCur)
+ {
+ if (pCur->Dev.pNext == pUrb)
+ {
+ pCur->Dev.pNext = pUrb->Dev.pNext;
+ break;
+ }
+ pCur = pCur->Dev.pNext;
+ }
+ if (!pCur)
+ return false;
+ }
+ if (!pUrb->Dev.pNext)
+ pQueue->ppTail = &pQueue->pHead;
+ return true;
+}
+
+
+#ifdef VBOX_STRICT
+/**
+ * Checks if the queue is empty or not.
+ *
+ * @returns true if it is, false if it isn't.
+ * @param pQueue The URB queue.
+ */
+DECLINLINE(bool) usbMsdQueueIsEmpty(PCUSBMSDURBQUEUE pQueue)
+{
+ return pQueue->pHead == NULL;
+}
+#endif /* VBOX_STRICT */
+
+
+/**
+ * Links an URB into the done queue.
+ *
+ * @param pThis The MSD instance.
+ * @param pUrb The URB.
+ */
+static void usbMsdLinkDone(PUSBMSD pThis, PVUSBURB pUrb)
+{
+ usbMsdQueueAddTail(&pThis->DoneQueue, pUrb);
+
+ if (pThis->fHaveDoneQueueWaiter)
+ {
+ int rc = RTSemEventSignal(pThis->hEvtDoneQueue);
+ AssertRC(rc);
+ }
+}
+
+
+
+
+/**
+ * Allocates a new request and does basic init.
+ *
+ * @returns Pointer to the new request. NULL if we're out of memory.
+ * @param pThis The MSD instance.
+ */
+static PUSBMSDREQ usbMsdReqAlloc(PUSBMSD pThis)
+{
+ PUSBMSDREQ pReq = NULL;
+ PDMMEDIAEXIOREQ hIoReq = NULL;
+
+ int rc = pThis->Lun0.pIMediaEx->pfnIoReqAlloc(pThis->Lun0.pIMediaEx, &hIoReq, (void **)&pReq,
+ 0 /* uTag */, PDMIMEDIAEX_F_DEFAULT);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->hIoReq = hIoReq;
+ pReq->enmState = USBMSDREQSTATE_READY;
+ pReq->iScsiReqStatus = 0xff;
+ }
+ else
+ LogRel(("usbMsdReqAlloc: Out of memory (%Rrc)\n", rc));
+
+ return pReq;
+}
+
+
+/**
+ * Frees a request.
+ *
+ * @param pThis The MSD instance.
+ * @param pReq The request.
+ */
+static void usbMsdReqFree(PUSBMSD pThis, PUSBMSDREQ pReq)
+{
+ /*
+ * Check the input.
+ */
+ AssertReturnVoid( pReq->enmState > USBMSDREQSTATE_INVALID
+ && pReq->enmState != USBMSDREQSTATE_EXECUTING
+ && pReq->enmState < USBMSDREQSTATE_END);
+ PPDMUSBINS pUsbIns = pThis->pUsbIns;
+ AssertPtrReturnVoid(pUsbIns);
+ AssertReturnVoid(PDM_VERSION_ARE_COMPATIBLE(pUsbIns->u32Version, PDM_USBINS_VERSION));
+
+ /*
+ * Invalidate it and free the associated resources.
+ */
+ pReq->enmState = USBMSDREQSTATE_INVALID;
+ pReq->cbBuf = 0;
+ pReq->offBuf = 0;
+
+ if (pReq->pbBuf)
+ {
+ PDMUsbHlpMMHeapFree(pUsbIns, pReq->pbBuf);
+ pReq->pbBuf = NULL;
+ }
+
+ int rc = pThis->Lun0.pIMediaEx->pfnIoReqFree(pThis->Lun0.pIMediaEx, pReq->hIoReq);
+ AssertRC(rc);
+}
+
+
+/**
+ * Prepares a request for execution or data buffering.
+ *
+ * @param pReq The request.
+ * @param pCbw The SCSI command block wrapper.
+ */
+static void usbMsdReqPrepare(PUSBMSDREQ pReq, PCUSBCBW pCbw)
+{
+ /* Copy the CBW */
+ uint8_t bCBWLen = RT_MIN(pCbw->bCBWCBLength, sizeof(pCbw->CBWCB));
+ size_t cbCopy = RT_UOFFSETOF_DYN(USBCBW, CBWCB[bCBWLen]);
+ memcpy(&pReq->Cbw, pCbw, cbCopy);
+ memset((uint8_t *)&pReq->Cbw + cbCopy, 0, sizeof(pReq->Cbw) - cbCopy);
+
+ /* Setup the SCSI request. */
+ pReq->offBuf = 0;
+ pReq->iScsiReqStatus = 0xff;
+}
+
+
+/**
+ * Makes sure that there is sufficient buffer space available.
+ *
+ * @returns Success indicator (true/false)
+ * @param pThis The MSD instance.
+ * @param pReq The request.
+ * @param cbBuf The required buffer space.
+ */
+static int usbMsdReqEnsureBuffer(PUSBMSD pThis, PUSBMSDREQ pReq, uint32_t cbBuf)
+{
+ if (RT_LIKELY(pReq->cbBuf >= cbBuf))
+ RT_BZERO(pReq->pbBuf, cbBuf);
+ else
+ {
+ PDMUsbHlpMMHeapFree(pThis->pUsbIns, pReq->pbBuf);
+ pReq->cbBuf = 0;
+
+ cbBuf = RT_ALIGN_Z(cbBuf, 0x1000);
+ pReq->pbBuf = (uint8_t *)PDMUsbHlpMMHeapAllocZ(pThis->pUsbIns, cbBuf);
+ if (!pReq->pbBuf)
+ return false;
+
+ pReq->cbBuf = cbBuf;
+ }
+ return true;
+}
+
+
+/**
+ * Completes the URB with a stalled state, halting the pipe.
+ */
+static int usbMsdCompleteStall(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb, const char *pszWhy)
+{
+ RT_NOREF(pszWhy);
+ Log(("usbMsdCompleteStall/#%u: pUrb=%p:%s: %s\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, pszWhy));
+
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+
+ /** @todo figure out if the stall is global or pipe-specific or both. */
+ if (pEp)
+ pEp->fHalted = true;
+ else
+ {
+ pThis->aEps[1].fHalted = true;
+ pThis->aEps[2].fHalted = true;
+ }
+
+ usbMsdLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Completes the URB with a OK state.
+ */
+static int usbMsdCompleteOk(PUSBMSD pThis, PVUSBURB pUrb, size_t cbData)
+{
+ Log(("usbMsdCompleteOk/#%u: pUrb=%p:%s cbData=%#zx\n", pThis->pUsbIns->iInstance, pUrb, pUrb->pszDesc, cbData));
+
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->cbData = (uint32_t)cbData;
+
+ usbMsdLinkDone(pThis, pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reset worker for usbMsdUsbReset, usbMsdUsbSetConfiguration and
+ * usbMsdUrbHandleDefaultPipe.
+ *
+ * @returns VBox status code.
+ * @param pThis The MSD instance.
+ * @param pUrb Set when usbMsdUrbHandleDefaultPipe is the
+ * caller.
+ * @param fSetConfig Set when usbMsdUsbSetConfiguration is the
+ * caller.
+ */
+static int usbMsdResetWorker(PUSBMSD pThis, PVUSBURB pUrb, bool fSetConfig)
+{
+ /*
+ * Wait for the any command currently executing to complete before
+ * resetting. (We cannot cancel its execution.) How we do this depends
+ * on the reset method.
+ */
+ PUSBMSDREQ pReq = pThis->pReq;
+ if ( pReq
+ && pReq->enmState == USBMSDREQSTATE_EXECUTING)
+ {
+ /* Don't try to deal with the set config variant nor multiple build-only
+ mass storage resets. */
+ if (pThis->pResetUrb && (pUrb || fSetConfig))
+ {
+ Log(("usbMsdResetWorker: pResetUrb is already %p:%s - stalling\n", pThis->pResetUrb, pThis->pResetUrb->pszDesc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "pResetUrb");
+ }
+
+ /* Bulk-Only Mass Storage Reset: Complete the reset on request completion. */
+ if (pUrb)
+ {
+ pThis->pResetUrb = pUrb;
+ Log(("usbMsdResetWorker: Setting pResetUrb to %p:%s\n", pThis->pResetUrb, pThis->pResetUrb->pszDesc));
+ return VINF_SUCCESS;
+ }
+
+ /* Device reset: Wait for up to 10 ms. If it doesn't work, ditch
+ whole the request structure. We'll allocate a new one when needed. */
+ Log(("usbMsdResetWorker: Waiting for completion...\n"));
+ Assert(!pThis->fSignalResetSem);
+ pThis->fSignalResetSem = true;
+ RTSemEventMultiReset(pThis->hEvtReset);
+ RTCritSectLeave(&pThis->CritSect);
+
+ int rc = RTSemEventMultiWait(pThis->hEvtReset, 10 /*ms*/);
+
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->fSignalResetSem = false;
+ if ( RT_FAILURE(rc)
+ || pReq->enmState == USBMSDREQSTATE_EXECUTING)
+ {
+ Log(("usbMsdResetWorker: Didn't complete, ditching the current request (%p)!\n", pReq));
+ Assert(pReq == pThis->pReq);
+ pReq->enmState = USBMSDREQSTATE_DESTROY_ON_COMPLETION;
+ pThis->pReq = NULL;
+ pReq = NULL;
+ }
+ }
+
+ /*
+ * Reset the request and device state.
+ */
+ if (pReq)
+ {
+ pReq->enmState = USBMSDREQSTATE_READY;
+ pReq->iScsiReqStatus = 0xff;
+ }
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aEps); i++)
+ pThis->aEps[i].fHalted = false;
+
+ if (!pUrb && !fSetConfig) /* (only device reset) */
+ pThis->bConfigurationValue = 0; /* default */
+
+ /*
+ * Ditch all pending URBs.
+ */
+ PVUSBURB pCurUrb;
+ while ((pCurUrb = usbMsdQueueRemoveHead(&pThis->ToHostQueue)) != NULL)
+ {
+ pCurUrb->enmStatus = VUSBSTATUS_CRC;
+ usbMsdLinkDone(pThis, pCurUrb);
+ }
+
+ pCurUrb = pThis->pResetUrb;
+ if (pCurUrb)
+ {
+ pThis->pResetUrb = NULL;
+ pCurUrb->enmStatus = VUSBSTATUS_CRC;
+ usbMsdLinkDone(pThis, pCurUrb);
+ }
+
+ if (pUrb)
+ return usbMsdCompleteOk(pThis, pUrb, 0);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Process a completed request.
+ *
+ * @param pThis The MSD instance.
+ * @param pReq The request.
+ * @param rcReq The completion status.
+ */
+static void usbMsdReqComplete(PUSBMSD pThis, PUSBMSDREQ pReq, int rcReq)
+{
+ RT_NOREF1(rcReq);
+
+ Log(("usbMsdLun0IoReqCompleteNotify: pReq=%p dCBWTag=%#x iScsiReqStatus=%u \n", pReq, pReq->Cbw.dCBWTag, pReq->iScsiReqStatus));
+ RTCritSectEnter(&pThis->CritSect);
+
+ if (pReq->enmState != USBMSDREQSTATE_DESTROY_ON_COMPLETION)
+ {
+ Assert(pReq->enmState == USBMSDREQSTATE_EXECUTING);
+ Assert(pThis->pReq == pReq);
+
+ /*
+ * Advance the state machine. The state machine is not affected by
+ * SCSI errors.
+ */
+ if ((pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_OUT)
+ {
+ pReq->enmState = USBMSDREQSTATE_STATUS;
+ Log(("usbMsdLun0IoReqCompleteNotify: Entering STATUS\n"));
+ }
+ else
+ {
+ pReq->enmState = USBMSDREQSTATE_DATA_TO_HOST;
+ Log(("usbMsdLun0IoReqCompleteNotify: Entering DATA_TO_HOST\n"));
+ }
+
+ /*
+ * Deal with pending to-host URBs.
+ */
+ for (;;)
+ {
+ PVUSBURB pUrb = usbMsdQueueRemoveHead(&pThis->ToHostQueue);
+ if (!pUrb)
+ break;
+
+ /* Process it the normal way. */
+ usbMsdHandleBulkDevToHost(pThis, &pThis->aEps[1], pUrb);
+ }
+ }
+ else
+ {
+ Log(("usbMsdLun0IoReqCompleteNotify: freeing %p\n", pReq));
+ usbMsdReqFree(pThis, pReq);
+ }
+
+ if (pThis->fSignalResetSem)
+ RTSemEventMultiSignal(pThis->hEvtReset);
+
+ if (pThis->pResetUrb)
+ {
+ pThis->pResetUrb = NULL;
+ usbMsdResetWorker(pThis, pThis->pResetUrb, false /*fSetConfig*/);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf}
+ */
+static DECLCALLBACK(int) usbMsdLun0IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF2(pInterface, hIoReq);
+ int rc = VINF_SUCCESS;
+ PUSBMSDREQ pReq = (PUSBMSDREQ)pvIoReqAlloc;
+
+ if (RT_UNLIKELY(offDst + cbCopy > pReq->cbBuf))
+ rc = VERR_PDM_MEDIAEX_IOBUF_OVERFLOW;
+ else
+ {
+ size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, pReq->pbBuf + offDst, cbCopy);
+ Assert(cbCopied == cbCopy); RT_NOREF(cbCopied);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf}
+ */
+static DECLCALLBACK(int) usbMsdLun0IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf,
+ size_t cbCopy)
+{
+ RT_NOREF2(pInterface, hIoReq);
+ int rc = VINF_SUCCESS;
+ PUSBMSDREQ pReq = (PUSBMSDREQ)pvIoReqAlloc;
+
+ if (RT_UNLIKELY(offSrc + cbCopy > pReq->cbBuf))
+ rc = VERR_PDM_MEDIAEX_IOBUF_UNDERRUN;
+ else
+ {
+ size_t cbCopied = RTSgBufCopyFromBuf(pSgBuf, pReq->pbBuf + offSrc, cbCopy);
+ Assert(cbCopied == cbCopy); RT_NOREF(cbCopied);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify}
+ */
+static DECLCALLBACK(int) usbMsdLun0IoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, int rcReq)
+{
+ RT_NOREF1(hIoReq);
+ PUSBMSD pThis = RT_FROM_MEMBER(pInterface, USBMSD, Lun0.IMediaExPort);
+ PUSBMSDREQ pReq = (PUSBMSDREQ)pvIoReqAlloc;
+
+ usbMsdReqComplete(pThis, pReq, rcReq);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged}
+ */
+static DECLCALLBACK(void) usbMsdLun0IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq,
+ void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState)
+{
+ RT_NOREF4(pInterface, hIoReq, pvIoReqAlloc, enmState);
+ AssertLogRelMsgFailed(("This should not be hit because I/O requests should not be suspended\n"));
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected}
+ */
+static DECLCALLBACK(void) usbMsdLun0MediumEjected(PPDMIMEDIAEXPORT pInterface)
+{
+ RT_NOREF1(pInterface); /** @todo */
+}
+
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) usbMsdLun0QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PUSBMSD pThis = RT_FROM_MEMBER(pInterface, USBMSD, Lun0.IMediaPort);
+ PPDMUSBINS pUsbIns = pThis->pUsbIns;
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pUsbIns->pReg->szName;
+ *piInstance = pUsbIns->iInstance;
+ *piLUN = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) usbMsdLun0QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PUSBMSD pThis = RT_FROM_MEMBER(pInterface, USBMSD, Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->Lun0.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pThis->Lun0.IMediaPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pThis->Lun0.IMediaExPort);
+ return NULL;
+}
+
+
+/**
+ * Checks if all asynchronous I/O is finished.
+ *
+ * Used by usbMsdVMReset, usbMsdVMSuspend and usbMsdVMPowerOff.
+ *
+ * @returns true if quiesced, false if busy.
+ * @param pUsbIns The USB device instance.
+ */
+static bool usbMsdAllAsyncIOIsFinished(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ if ( RT_VALID_PTR(pThis->pReq)
+ && pThis->pReq->enmState == USBMSDREQSTATE_EXECUTING)
+ return false;
+
+ return true;
+}
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY,
+ * Callback employed by usbMsdVMSuspend and usbMsdVMPowerOff.}
+ */
+static DECLCALLBACK(bool) usbMsdIsAsyncSuspendOrPowerOffDone(PPDMUSBINS pUsbIns)
+{
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ return false;
+
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ return true;
+}
+
+/**
+ * Common worker for usbMsdVMSuspend and usbMsdVMPowerOff.
+ */
+static void usbMsdSuspendOrPowerOff(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ PDMUsbHlpSetAsyncNotification(pUsbIns, usbMsdIsAsyncSuspendOrPowerOffDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ if (pThis->pReq)
+ {
+ usbMsdReqFree(pThis, pThis->pReq);
+ pThis->pReq = NULL;
+ }
+ }
+
+ if (pThis->Lun0.pIMediaEx)
+ pThis->Lun0.pIMediaEx->pfnNotifySuspend(pThis->Lun0.pIMediaEx);
+}
+
+
+/* -=-=-=-=- Saved State -=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNSSMUSBSAVEPREP}
+ */
+static DECLCALLBACK(int) usbMsdSavePrep(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+#ifdef VBOX_STRICT
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ Assert(usbMsdAllAsyncIOIsFinished(pUsbIns));
+ Assert(usbMsdQueueIsEmpty(&pThis->ToHostQueue));
+ Assert(usbMsdQueueIsEmpty(&pThis->DoneQueue));
+#else
+ RT_NOREF(pUsbIns);
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBLOADPREP}
+ */
+static DECLCALLBACK(int) usbMsdLoadPrep(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+#ifdef VBOX_STRICT
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ Assert(usbMsdAllAsyncIOIsFinished(pUsbIns));
+ Assert(usbMsdQueueIsEmpty(&pThis->ToHostQueue));
+ Assert(usbMsdQueueIsEmpty(&pThis->DoneQueue));
+#else
+ RT_NOREF(pUsbIns);
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBLIVEEXEC}
+ */
+static DECLCALLBACK(int) usbMsdLiveExec(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ /* config. */
+ pHlp->pfnSSMPutBool(pSSM, pThis->Lun0.pIBase != NULL);
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBSAVEEXEC}
+ */
+static DECLCALLBACK(int) usbMsdSaveExec(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ /* The config */
+ int rc = usbMsdLiveExec(pUsbIns, pSSM, SSM_PASS_FINAL);
+ AssertRCReturn(rc, rc);
+
+ pHlp->pfnSSMPutU8(pSSM, pThis->bConfigurationValue);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aEps[0].fHalted);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aEps[1].fHalted);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aEps[2].fHalted);
+ pHlp->pfnSSMPutBool(pSSM, pThis->pReq != NULL);
+
+ if (pThis->pReq)
+ {
+ PUSBMSDREQ pReq = pThis->pReq;
+
+ pHlp->pfnSSMPutU32(pSSM, pReq->enmState);
+ pHlp->pfnSSMPutU32(pSSM, pReq->cbBuf);
+ if (pReq->cbBuf)
+ {
+ AssertPtr(pReq->pbBuf);
+ pHlp->pfnSSMPutMem(pSSM, pReq->pbBuf, pReq->cbBuf);
+ }
+
+ pHlp->pfnSSMPutU32(pSSM, pReq->offBuf);
+ pHlp->pfnSSMPutMem(pSSM, &pReq->Cbw, sizeof(pReq->Cbw));
+ pHlp->pfnSSMPutU8(pSSM, pReq->iScsiReqStatus);
+ }
+
+ return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+/**
+ * @callback_method_impl{FNSSMUSBLOADEXEC}
+ */
+static DECLCALLBACK(int) usbMsdLoadExec(PPDMUSBINS pUsbIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ if (uVersion > USB_MSD_SAVED_STATE_VERSION)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Verify config. */
+ bool fInUse;
+ int rc = pHlp->pfnSSMGetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != (pThis->Lun0.pIBase != NULL))
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
+ N_("The %s VM is missing a USB mass storage device. Please make sure the source and target VMs have compatible storage configurations"),
+ fInUse ? "target" : "source");
+
+ if (uPass == SSM_PASS_FINAL)
+ {
+ /* Restore data. */
+ Assert(!pThis->pReq);
+
+ pHlp->pfnSSMGetU8(pSSM, &pThis->bConfigurationValue);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aEps[0].fHalted);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aEps[1].fHalted);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aEps[2].fHalted);
+ bool fReqAlloc = false;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fReqAlloc);
+ AssertRCReturn(rc, rc);
+ if (fReqAlloc)
+ {
+ PUSBMSDREQ pReq = usbMsdReqAlloc(pThis);
+ AssertReturn(pReq, VERR_NO_MEMORY);
+ pThis->pReq = pReq;
+
+ AssertCompile(sizeof(pReq->enmState) == sizeof(uint32_t));
+ pHlp->pfnSSMGetU32(pSSM, (uint32_t *)&pReq->enmState);
+
+ uint32_t cbBuf = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cbBuf);
+ AssertRCReturn(rc, rc);
+ if (cbBuf)
+ {
+ if (usbMsdReqEnsureBuffer(pThis, pReq, cbBuf))
+ {
+ AssertPtr(pReq->pbBuf);
+ Assert(cbBuf == pReq->cbBuf);
+ pHlp->pfnSSMGetMem(pSSM, pReq->pbBuf, pReq->cbBuf);
+ }
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ pHlp->pfnSSMGetU32(pSSM, &pReq->offBuf);
+ pHlp->pfnSSMGetMem(pSSM, &pReq->Cbw, sizeof(pReq->Cbw));
+
+ if (uVersion > USB_MSD_SAVED_STATE_VERSION_PRE_CLEANUP)
+ rc = pHlp->pfnSSMGetU8(pSSM, &pReq->iScsiReqStatus);
+ else
+ {
+ int32_t iScsiReqStatus;
+
+ /* Skip old fields which are unused now or can be determined from the CBW. */
+ pHlp->pfnSSMSkip(pSSM, 4 * 4 + 64);
+ rc = pHlp->pfnSSMGetS32(pSSM, &iScsiReqStatus);
+ pReq->iScsiReqStatus = (uint8_t)iScsiReqStatus;
+ }
+ AssertRCReturn(rc, rc);
+ }
+
+ uint32_t u32;
+ rc = pHlp->pfnSSMGetU32(pSSM, &u32);
+ AssertRCReturn(rc, rc);
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbReap}
+ */
+static DECLCALLBACK(PVUSBURB) usbMsdUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUrbReap/#%u: cMillies=%u\n", pUsbIns->iInstance, cMillies));
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ PVUSBURB pUrb = usbMsdQueueRemoveHead(&pThis->DoneQueue);
+ if (!pUrb && cMillies)
+ {
+ /* Wait */
+ pThis->fHaveDoneQueueWaiter = true;
+ RTCritSectLeave(&pThis->CritSect);
+
+ RTSemEventWait(pThis->hEvtDoneQueue, cMillies);
+
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->fHaveDoneQueueWaiter = false;
+
+ pUrb = usbMsdQueueRemoveHead(&pThis->DoneQueue);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ if (pUrb)
+ Log(("usbMsdUrbReap/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc));
+ return pUrb;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnWakeup}
+ */
+static DECLCALLBACK(int) usbMsdWakeup(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUrbReap/#%u:\n", pUsbIns->iInstance));
+
+ return RTSemEventSignal(pThis->hEvtDoneQueue);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbCancel}
+ */
+static DECLCALLBACK(int) usbMsdUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUrbCancel/#%u: pUrb=%p:%s\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Remove the URB from the to-host queue and move it onto the done queue.
+ */
+ if (usbMsdQueueRemove(&pThis->ToHostQueue, pUrb))
+ usbMsdLinkDone(pThis, pUrb);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Wrapper around PDMISCSICONNECTOR::pfnSCSIRequestSend that deals with
+ * SCSI_REQUEST_SENSE.
+ *
+ * @returns VBox status code.
+ * @param pThis The MSD instance data.
+ * @param pReq The MSD request.
+ * @param pszCaller Where we're called from.
+ */
+static int usbMsdSubmitScsiCommand(PUSBMSD pThis, PUSBMSDREQ pReq, const char *pszCaller)
+{
+ RT_NOREF(pszCaller);
+ Log(("%s: Entering EXECUTING (dCBWTag=%#x).\n", pszCaller, pReq->Cbw.dCBWTag));
+ Assert(pReq == pThis->pReq);
+ pReq->enmState = USBMSDREQSTATE_EXECUTING;
+
+ PDMMEDIAEXIOREQSCSITXDIR enmTxDir = pReq->Cbw.dCBWDataTransferLength == 0
+ ? PDMMEDIAEXIOREQSCSITXDIR_NONE
+ : (pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_OUT
+ ? PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE
+ : PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE;
+
+ return pThis->Lun0.pIMediaEx->pfnIoReqSendScsiCmd(pThis->Lun0.pIMediaEx, pReq->hIoReq, pReq->Cbw.bCBWLun,
+ &pReq->Cbw.CBWCB[0], pReq->Cbw.bCBWCBLength, enmTxDir, NULL,
+ pReq->Cbw.dCBWDataTransferLength, NULL, 0, NULL,
+ &pReq->iScsiReqStatus, 20 * RT_MS_1SEC);
+}
+
+
+/**
+ * Handle requests sent to the outbound (to device) bulk pipe.
+ */
+static int usbMsdHandleBulkHostToDev(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb)
+{
+ /*
+ * Stall the request if the pipe is halted.
+ */
+ if (RT_UNLIKELY(pEp->fHalted))
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Halted pipe");
+
+ /*
+ * Deal with the URB according to the current state.
+ */
+ PUSBMSDREQ pReq = pThis->pReq;
+ USBMSDREQSTATE enmState = pReq ? pReq->enmState : USBMSDREQSTATE_READY;
+ switch (enmState)
+ {
+ case USBMSDREQSTATE_STATUS:
+ LogFlow(("usbMsdHandleBulkHostToDev: Skipping pending status.\n"));
+ pReq->enmState = USBMSDREQSTATE_READY;
+ RT_FALL_THRU();
+
+ /*
+ * We're ready to receive a command. Start off by validating the
+ * incoming request.
+ */
+ case USBMSDREQSTATE_READY:
+ {
+ PCUSBCBW pCbw = (PUSBCBW)&pUrb->abData[0];
+ if (pUrb->cbData < RT_UOFFSETOF(USBCBW, CBWCB[1]))
+ {
+ Log(("usbMsd: Bad CBW: cbData=%#x < min=%#x\n", pUrb->cbData, RT_UOFFSETOF(USBCBW, CBWCB[1]) ));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "BAD CBW");
+ }
+ if (pCbw->dCBWSignature != USBCBW_SIGNATURE)
+ {
+ Log(("usbMsd: CBW: Invalid dCBWSignature value: %#x\n", pCbw->dCBWSignature));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ Log(("usbMsd: CBW: dCBWTag=%#x dCBWDataTransferLength=%#x bmCBWFlags=%#x bCBWLun=%#x bCBWCBLength=%#x cbData=%#x fShortNotOk=%RTbool\n",
+ pCbw->dCBWTag, pCbw->dCBWDataTransferLength, pCbw->bmCBWFlags, pCbw->bCBWLun, pCbw->bCBWCBLength, pUrb->cbData, pUrb->fShortNotOk));
+ if (pCbw->bmCBWFlags & ~USBCBW_DIR_MASK)
+ {
+ Log(("usbMsd: CBW: Bad bmCBWFlags value: %#x\n", pCbw->bmCBWFlags));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+
+ }
+ if (pCbw->bCBWLun != 0)
+ {
+ Log(("usbMsd: CBW: Bad bCBWLun value: %#x\n", pCbw->bCBWLun));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ if ((pCbw->bCBWCBLength == 0) || (pCbw->bCBWCBLength > sizeof(pCbw->CBWCB)))
+ {
+ Log(("usbMsd: CBW: Bad bCBWCBLength value: %#x\n", pCbw->bCBWCBLength));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ if (pUrb->cbData < RT_UOFFSETOF_DYN(USBCBW, CBWCB[pCbw->bCBWCBLength]))
+ {
+ Log(("usbMsd: CBW: Mismatching cbData and bCBWCBLength values: %#x vs. %#x (%#x)\n",
+ pUrb->cbData, RT_UOFFSETOF_DYN(USBCBW, CBWCB[pCbw->bCBWCBLength]), pCbw->bCBWCBLength));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad CBW");
+ }
+ if (pCbw->dCBWDataTransferLength > _1M)
+ {
+ Log(("usbMsd: CBW: dCBWDataTransferLength is too large: %#x (%u)\n",
+ pCbw->dCBWDataTransferLength, pCbw->dCBWDataTransferLength));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Too big transfer");
+ }
+
+ /*
+ * Make sure we've got a request and a sufficient buffer space.
+ *
+ * Note! This will make sure the buffer is ZERO as well, thus
+ * saving us the trouble of clearing the output buffer on
+ * failure later.
+ */
+ if (!pReq)
+ {
+ pReq = usbMsdReqAlloc(pThis);
+ if (!pReq)
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Request allocation failure");
+ pThis->pReq = pReq;
+ }
+ if (!usbMsdReqEnsureBuffer(pThis, pReq, pCbw->dCBWDataTransferLength))
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Buffer allocation failure");
+
+ /*
+ * Prepare the request. Kick it off right away if possible.
+ */
+ usbMsdReqPrepare(pReq, pCbw);
+
+ if ( pReq->Cbw.dCBWDataTransferLength == 0
+ || (pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_IN)
+ {
+ int rc = usbMsdSubmitScsiCommand(pThis, pReq, "usbMsdHandleBulkHostToDev");
+ if (RT_SUCCESS(rc) && rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ usbMsdReqComplete(pThis, pReq, rc);
+ else if (RT_FAILURE(rc))
+ {
+ Log(("usbMsd: Failed sending SCSI request to driver: %Rrc\n", rc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "SCSI Submit #1");
+ }
+ }
+ else
+ {
+ Log(("usbMsdHandleBulkHostToDev: Entering DATA_FROM_HOST.\n"));
+ pReq->enmState = USBMSDREQSTATE_DATA_FROM_HOST;
+ }
+
+ return usbMsdCompleteOk(pThis, pUrb, pUrb->cbData);
+ }
+
+ /*
+ * Stuff the data into the buffer.
+ */
+ case USBMSDREQSTATE_DATA_FROM_HOST:
+ {
+ uint32_t cbData = pUrb->cbData;
+ uint32_t cbLeft = pReq->Cbw.dCBWDataTransferLength - pReq->offBuf;
+ if (cbData > cbLeft)
+ {
+ Log(("usbMsd: Too much data: cbData=%#x offBuf=%#x dCBWDataTransferLength=%#x cbLeft=%#x\n",
+ cbData, pReq->offBuf, pReq->Cbw.dCBWDataTransferLength, cbLeft));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Too much data");
+ }
+ memcpy(&pReq->pbBuf[pReq->offBuf], &pUrb->abData[0], cbData);
+ pReq->offBuf += cbData;
+
+ if (pReq->offBuf == pReq->Cbw.dCBWDataTransferLength)
+ {
+ int rc = usbMsdSubmitScsiCommand(pThis, pReq, "usbMsdHandleBulkHostToDev");
+ if (RT_SUCCESS(rc) && rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ usbMsdReqComplete(pThis, pReq, rc);
+ else if (RT_FAILURE(rc))
+ {
+ Log(("usbMsd: Failed sending SCSI request to driver: %Rrc\n", rc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "SCSI Submit #2");
+ }
+ }
+ return usbMsdCompleteOk(pThis, pUrb, cbData);
+ }
+
+ /*
+ * Bad state, stall.
+ */
+ case USBMSDREQSTATE_DATA_TO_HOST:
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state H2D: DATA_TO_HOST");
+
+ case USBMSDREQSTATE_EXECUTING:
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state H2D: EXECUTING");
+
+ default:
+ AssertMsgFailed(("enmState=%d\n", enmState));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state (H2D)");
+ }
+}
+
+
+/**
+ * Handle requests sent to the inbound (to host) bulk pipe.
+ */
+static int usbMsdHandleBulkDevToHost(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb)
+{
+ /*
+ * Stall the request if the pipe is halted OR if there is no
+ * pending request yet.
+ */
+ PUSBMSDREQ pReq = pThis->pReq;
+ if (RT_UNLIKELY(pEp->fHalted || !pReq))
+ return usbMsdCompleteStall(pThis, NULL, pUrb, pEp->fHalted ? "Halted pipe" : "No request");
+
+ /*
+ * Deal with the URB according to the state.
+ */
+ switch (pReq->enmState)
+ {
+ /*
+ * We've data left to transfer to the host.
+ */
+ case USBMSDREQSTATE_DATA_TO_HOST:
+ {
+ uint32_t cbData = pUrb->cbData;
+ uint32_t cbCopy = pReq->Cbw.dCBWDataTransferLength - pReq->offBuf;
+ if (cbData <= cbCopy)
+ cbCopy = cbData;
+ else if (pUrb->fShortNotOk)
+ {
+ Log(("usbMsd: Requested more data that we've got; cbData=%#x offBuf=%#x dCBWDataTransferLength=%#x cbLeft=%#x\n",
+ cbData, pReq->offBuf, pReq->Cbw.dCBWDataTransferLength, cbCopy));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Data underrun");
+ }
+ memcpy(&pUrb->abData[0], &pReq->pbBuf[pReq->offBuf], cbCopy);
+ pReq->offBuf += cbCopy;
+
+ if (pReq->offBuf == pReq->Cbw.dCBWDataTransferLength)
+ {
+ Log(("usbMsdHandleBulkDevToHost: Entering STATUS\n"));
+ pReq->enmState = USBMSDREQSTATE_STATUS;
+ }
+ return usbMsdCompleteOk(pThis, pUrb, cbCopy);
+ }
+
+ /*
+ * Status transfer.
+ */
+ case USBMSDREQSTATE_STATUS:
+ {
+ if ((pUrb->cbData < sizeof(USBCSW)) || (pUrb->cbData > sizeof(USBCSW) && pUrb->fShortNotOk))
+ {
+ Log(("usbMsd: Unexpected status request size: %#x (expected %#x), fShortNotOK=%RTbool\n", pUrb->cbData, sizeof(USBCSW), pUrb->fShortNotOk));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Invalid CSW size");
+ }
+
+ /* Enter a CSW into the URB data buffer. */
+ PUSBCSW pCsw = (PUSBCSW)&pUrb->abData[0];
+ pCsw->dCSWSignature = USBCSW_SIGNATURE;
+ pCsw->dCSWTag = pReq->Cbw.dCBWTag;
+ pCsw->bCSWStatus = pReq->iScsiReqStatus == SCSI_STATUS_OK
+ ? USBCSW_STATUS_OK
+ : pReq->iScsiReqStatus < 0xff
+ ? USBCSW_STATUS_FAILED
+ : USBCSW_STATUS_PHASE_ERROR;
+ /** @todo the following is not always accurate; VSCSI needs
+ * to implement residual counts properly! */
+ if ((pReq->Cbw.bmCBWFlags & USBCBW_DIR_MASK) == USBCBW_DIR_OUT)
+ pCsw->dCSWDataResidue = pCsw->bCSWStatus == USBCSW_STATUS_OK
+ ? 0
+ : pReq->Cbw.dCBWDataTransferLength;
+ else
+ pCsw->dCSWDataResidue = pCsw->bCSWStatus == USBCSW_STATUS_OK
+ ? 0
+ : pReq->Cbw.dCBWDataTransferLength;
+ Log(("usbMsd: CSW: dCSWTag=%#x bCSWStatus=%d dCSWDataResidue=%#x\n",
+ pCsw->dCSWTag, pCsw->bCSWStatus, pCsw->dCSWDataResidue));
+
+ Log(("usbMsdHandleBulkDevToHost: Entering READY\n"));
+ pReq->enmState = USBMSDREQSTATE_READY;
+ return usbMsdCompleteOk(pThis, pUrb, sizeof(*pCsw));
+ }
+
+ /*
+ * Status request before we've received all (or even any) data.
+ * Linux 2.4.31 does this sometimes. The recommended behavior is to
+ * to accept the current data amount and execute the request. (The
+ * alternative behavior is to stall.)
+ */
+ case USBMSDREQSTATE_DATA_FROM_HOST:
+ {
+ if (pUrb->cbData != sizeof(USBCSW))
+ {
+ Log(("usbMsdHandleBulkDevToHost: DATA_FROM_HOST; cbData=%#x -> stall\n", pUrb->cbData));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Invalid CSW size");
+ }
+
+ int rc = usbMsdSubmitScsiCommand(pThis, pReq, "usbMsdHandleBulkDevToHost");
+ if (RT_SUCCESS(rc) && rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS)
+ usbMsdReqComplete(pThis, pReq, rc);
+ else if (RT_FAILURE(rc))
+ {
+ Log(("usbMsd: Failed sending SCSI request to driver: %Rrc\n", rc));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "SCSI Submit #3");
+ }
+ }
+ RT_FALL_THRU();
+
+ /*
+ * The SCSI command is still pending, queue the URB awaiting its
+ * completion.
+ */
+ case USBMSDREQSTATE_EXECUTING:
+ usbMsdQueueAddTail(&pThis->ToHostQueue, pUrb);
+ LogFlow(("usbMsdHandleBulkDevToHost: Added %p:%s to the to-host queue\n", pUrb, pUrb->pszDesc));
+ return VINF_SUCCESS;
+
+ /*
+ * Bad states, stall.
+ */
+ case USBMSDREQSTATE_READY:
+ Log(("usbMsdHandleBulkDevToHost: enmState=READ(%d) (cbData=%#x)\n", pReq->enmState, pUrb->cbData));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Bad state D2H: READY");
+
+ default:
+ Log(("usbMsdHandleBulkDevToHost: enmState=%d cbData=%#x\n", pReq->enmState, pUrb->cbData));
+ return usbMsdCompleteStall(pThis, NULL, pUrb, "Really bad state (D2H)!");
+ }
+}
+
+
+/**
+ * Handles request send to the default control pipe.
+ */
+static int usbMsdHandleDefaultPipe(PUSBMSD pThis, PUSBMSDEP pEp, PVUSBURB pUrb)
+{
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0];
+ AssertReturn(pUrb->cbData >= sizeof(*pSetup), VERR_VUSB_FAILED_TO_QUEUE_URB);
+
+ if ((pSetup->bmRequestType & VUSB_REQ_MASK) == VUSB_REQ_STANDARD)
+ {
+ switch (pSetup->bRequest)
+ {
+ case VUSB_REQ_GET_DESCRIPTOR:
+ {
+ if (pSetup->bmRequestType != (VUSB_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_DIR_TO_HOST))
+ {
+ Log(("usbMsd: Bad GET_DESCRIPTOR req: bmRequestType=%#x\n", pSetup->bmRequestType));
+ return usbMsdCompleteStall(pThis, pEp, pUrb, "Bad GET_DESCRIPTOR");
+ }
+
+ switch (pSetup->wValue >> 8)
+ {
+ uint32_t cbCopy;
+
+ case VUSB_DT_STRING:
+ Log(("usbMsd: GET_DESCRIPTOR DT_STRING wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ break;
+ case VUSB_DT_DEVICE_QUALIFIER:
+ Log(("usbMsd: GET_DESCRIPTOR DT_DEVICE_QUALIFIER wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, sizeof(g_UsbMsdDeviceQualifier));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &g_UsbMsdDeviceQualifier, cbCopy);
+ return usbMsdCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ case VUSB_DT_BOS:
+ Log(("usbMsd: GET_DESCRIPTOR DT_BOS wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ /* Returned data is written after the setup message. */
+ cbCopy = pUrb->cbData - sizeof(*pSetup);
+ cbCopy = RT_MIN(cbCopy, sizeof(g_UsbMsdBOS));
+ memcpy(&pUrb->abData[sizeof(*pSetup)], &g_UsbMsdBOS, cbCopy);
+ return usbMsdCompleteOk(pThis, pUrb, cbCopy + sizeof(*pSetup));
+ default:
+ Log(("usbMsd: GET_DESCRIPTOR, huh? wValue=%#x wIndex=%#x\n", pSetup->wValue, pSetup->wIndex));
+ break;
+ }
+ break;
+ }
+
+ case VUSB_REQ_CLEAR_FEATURE:
+ break;
+ }
+
+ /** @todo implement this. */
+ Log(("usbMsd: Implement standard request: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+
+ usbMsdCompleteStall(pThis, pEp, pUrb, "TODO: standard request stuff");
+ }
+ /* 3.1 Bulk-Only Mass Storage Reset */
+ else if ( pSetup->bmRequestType == (VUSB_REQ_CLASS | VUSB_TO_INTERFACE)
+ && pSetup->bRequest == 0xff
+ && !pSetup->wValue
+ && !pSetup->wLength
+ && pSetup->wIndex == 0)
+ {
+ Log(("usbMsdHandleDefaultPipe: Bulk-Only Mass Storage Reset\n"));
+ return usbMsdResetWorker(pThis, pUrb, false /*fSetConfig*/);
+ }
+ /* 3.2 Get Max LUN, may stall if we like (but we don't). */
+ else if ( pSetup->bmRequestType == (VUSB_REQ_CLASS | VUSB_TO_INTERFACE | VUSB_DIR_TO_HOST)
+ && pSetup->bRequest == 0xfe
+ && !pSetup->wValue
+ && pSetup->wLength == 1
+ && pSetup->wIndex == 0)
+ {
+ *(uint8_t *)(pSetup + 1) = 0; /* max lun is 0 */
+ usbMsdCompleteOk(pThis, pUrb, 1);
+ }
+ else
+ {
+ Log(("usbMsd: Unknown control msg: bmRequestType=%#x bRequest=%#x wValue=%#x wIndex=%#x wLength=%#x\n",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+ return usbMsdCompleteStall(pThis, pEp, pUrb, "Unknown control msg");
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbMsdQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdQueue/#%u: pUrb=%p:%s EndPt=%#x\n", pUsbIns->iInstance, pUrb, pUrb->pszDesc, pUrb->EndPt));
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Parse on a per end-point basis.
+ */
+ int rc;
+ switch (pUrb->EndPt)
+ {
+ case 0:
+ rc = usbMsdHandleDefaultPipe(pThis, &pThis->aEps[0], pUrb);
+ break;
+
+ case 0x81:
+ AssertFailed();
+ RT_FALL_THRU();
+ case 0x01:
+ rc = usbMsdHandleBulkDevToHost(pThis, &pThis->aEps[1], pUrb);
+ break;
+
+ case 0x02:
+ rc = usbMsdHandleBulkHostToDev(pThis, &pThis->aEps[2], pUrb);
+ break;
+
+ default:
+ AssertMsgFailed(("EndPt=%d\n", pUrb->EndPt));
+ rc = VERR_VUSB_FAILED_TO_QUEUE_URB;
+ break;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbClearHaltedEndpoint}
+ */
+static DECLCALLBACK(int) usbMsdUsbClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbClearHaltedEndpoint/#%u: uEndpoint=%#x\n", pUsbIns->iInstance, uEndpoint));
+
+ if ((uEndpoint & ~0x80) < RT_ELEMENTS(pThis->aEps))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->aEps[(uEndpoint & ~0x80)].fHalted = false;
+ RTCritSectLeave(&pThis->CritSect);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetInterface}
+ */
+static DECLCALLBACK(int) usbMsdUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
+{
+ RT_NOREF(pUsbIns, bInterfaceNumber, bAlternateSetting);
+ LogFlow(("usbMsdUsbSetInterface/#%u: bInterfaceNumber=%u bAlternateSetting=%u\n", pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting));
+ Assert(bAlternateSetting == 0);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetConfiguration}
+ */
+static DECLCALLBACK(int) usbMsdUsbSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue,
+ const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc)
+{
+ RT_NOREF(pvOldCfgDesc, pvOldIfState, pvNewCfgDesc);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbSetConfiguration/#%u: bConfigurationValue=%u\n", pUsbIns->iInstance, bConfigurationValue));
+ Assert(bConfigurationValue == 1);
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * If the same config is applied more than once, it's a kind of reset.
+ */
+ if (pThis->bConfigurationValue == bConfigurationValue)
+ usbMsdResetWorker(pThis, NULL, true /*fSetConfig*/); /** @todo figure out the exact difference */
+ pThis->bConfigurationValue = bConfigurationValue;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache}
+ */
+static DECLCALLBACK(PCPDMUSBDESCCACHE) usbMsdUsbGetDescriptorCache(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbGetDescriptorCache/#%u:\n", pUsbIns->iInstance));
+ if (pThis->pUsbIns->enmSpeed == VUSB_SPEED_SUPER)
+ return pThis->fIsCdrom ? &g_UsbCdDescCacheSS : &g_UsbMsdDescCacheSS;
+ else if (pThis->pUsbIns->enmSpeed == VUSB_SPEED_HIGH)
+ return pThis->fIsCdrom ? &g_UsbCdDescCacheHS : &g_UsbMsdDescCacheHS;
+ else
+ return pThis->fIsCdrom ? &g_UsbCdDescCacheFS : &g_UsbMsdDescCacheFS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbReset}
+ */
+static DECLCALLBACK(int) usbMsdUsbReset(PPDMUSBINS pUsbIns, bool fResetOnLinux)
+{
+ RT_NOREF(fResetOnLinux);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdUsbReset/#%u:\n", pUsbIns->iInstance));
+ RTCritSectEnter(&pThis->CritSect);
+
+ int rc = usbMsdResetWorker(pThis, NULL, false /*fSetConfig*/);
+
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnVMSuspend}
+ */
+static DECLCALLBACK(void) usbMsdVMSuspend(PPDMUSBINS pUsbIns)
+{
+ LogFlow(("usbMsdVMSuspend/#%u:\n", pUsbIns->iInstance));
+ usbMsdSuspendOrPowerOff(pUsbIns);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnVMSuspend}
+ */
+static DECLCALLBACK(void) usbMsdVMPowerOff(PPDMUSBINS pUsbIns)
+{
+ LogFlow(("usbMsdVMPowerOff/#%u:\n", pUsbIns->iInstance));
+ usbMsdSuspendOrPowerOff(pUsbIns);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDriverAttach}
+ */
+static DECLCALLBACK(int) usbMsdDriverAttach(PPDMUSBINS pUsbIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ LogFlow(("usbMsdDriverAttach/#%u:\n", pUsbIns->iInstance));
+
+ AssertMsg(iLUN == 0, ("UsbMsd: No other LUN than 0 is supported\n"));
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("UsbMsd: Device does not support hotplugging\n"));
+
+ /* the usual paranoia */
+ AssertRelease(!pThis->Lun0.pIBase);
+ AssertRelease(!pThis->Lun0.pIMedia);
+ AssertRelease(!pThis->Lun0.pIMediaEx);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ int rc = PDMUsbHlpDriverAttach(pUsbIns, iLUN, &pThis->Lun0.IBase, &pThis->Lun0.pIBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get media and extended media interface. */
+ pThis->Lun0.pIMedia = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIA);
+ AssertMsgReturn(pThis->Lun0.pIMedia, ("Missing media interface below\n"), VERR_PDM_MISSING_INTERFACE);
+ pThis->Lun0.pIMediaEx = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIAEX);
+ AssertMsgReturn(pThis->Lun0.pIMediaEx, ("Missing extended media interface below\n"), VERR_PDM_MISSING_INTERFACE);
+
+ rc = pThis->Lun0.pIMediaEx->pfnIoReqAllocSizeSet(pThis->Lun0.pIMediaEx, sizeof(USBMSDREQ));
+ AssertMsgRCReturn(rc, ("MSD failed to set I/O request size!\n"), VERR_PDM_MISSING_INTERFACE);
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pThis->Lun0.pIBase = NULL;
+ pThis->Lun0.pIMedia = NULL;
+ pThis->Lun0.pIMediaEx = NULL;
+ }
+
+ pThis->fIsCdrom = false;
+ PDMMEDIATYPE enmType = pThis->Lun0.pIMedia->pfnGetType(pThis->Lun0.pIMedia);
+ /* Anything else will be reported as a hard disk. */
+ if (enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD)
+ pThis->fIsCdrom = true;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDriverDetach}
+ */
+static DECLCALLBACK(void) usbMsdDriverDetach(PPDMUSBINS pUsbIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(iLUN, fFlags);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ LogFlow(("usbMsdDriverDetach/#%u:\n", pUsbIns->iInstance));
+
+ AssertMsg(iLUN == 0, ("UsbMsd: No other LUN than 0 is supported\n"));
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("UsbMsd: Device does not support hotplugging\n"));
+
+ if (pThis->pReq)
+ {
+ usbMsdReqFree(pThis, pThis->pReq);
+ pThis->pReq = NULL;
+ }
+
+ /*
+ * Zero some important members.
+ */
+ pThis->Lun0.pIBase = NULL;
+ pThis->Lun0.pIMedia = NULL;
+ pThis->Lun0.pIMediaEx = NULL;
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDEVASYNCNOTIFY,
+ * Callback employed by usbMsdVMReset.}
+ */
+static DECLCALLBACK(bool) usbMsdIsAsyncResetDone(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ return false;
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+
+ int rc = usbMsdResetWorker(pThis, NULL, false /*fSetConfig*/);
+ AssertRC(rc);
+ return true;
+}
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnVMReset}
+ */
+static DECLCALLBACK(void) usbMsdVMReset(PPDMUSBINS pUsbIns)
+{
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+
+ ASMAtomicWriteBool(&pThis->fSignalIdle, true);
+ if (!usbMsdAllAsyncIOIsFinished(pUsbIns))
+ PDMUsbHlpSetAsyncNotification(pUsbIns, usbMsdIsAsyncResetDone);
+ else
+ {
+ ASMAtomicWriteBool(&pThis->fSignalIdle, false);
+ int rc = usbMsdResetWorker(pThis, NULL, false /*fSetConfig*/);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnDestruct}
+ */
+static DECLCALLBACK(void) usbMsdDestruct(PPDMUSBINS pUsbIns)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ LogFlow(("usbMsdDestruct/#%u:\n", pUsbIns->iInstance));
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ RTCritSectLeave(&pThis->CritSect);
+ RTCritSectDelete(&pThis->CritSect);
+ }
+
+ if (pThis->hEvtDoneQueue != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThis->hEvtDoneQueue);
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ }
+
+ if (pThis->hEvtReset != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(pThis->hEvtReset);
+ pThis->hEvtReset = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) usbMsdConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
+{
+ RT_NOREF(pCfgGlobal);
+ PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns);
+ PUSBMSD pThis = PDMINS_2_DATA(pUsbIns, PUSBMSD);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ Log(("usbMsdConstruct/#%u:\n", iInstance));
+
+ /*
+ * Perform the basic structure initialization first so the destructor
+ * will not misbehave.
+ */
+ pThis->pUsbIns = pUsbIns;
+ pThis->hEvtDoneQueue = NIL_RTSEMEVENT;
+ pThis->hEvtReset = NIL_RTSEMEVENTMULTI;
+ pThis->Lun0.IBase.pfnQueryInterface = usbMsdLun0QueryInterface;
+ pThis->Lun0.IMediaPort.pfnQueryDeviceLocation = usbMsdLun0QueryDeviceLocation;
+ pThis->Lun0.IMediaExPort.pfnIoReqCompleteNotify = usbMsdLun0IoReqCompleteNotify;
+ pThis->Lun0.IMediaExPort.pfnIoReqCopyFromBuf = usbMsdLun0IoReqCopyFromBuf;
+ pThis->Lun0.IMediaExPort.pfnIoReqCopyToBuf = usbMsdLun0IoReqCopyToBuf;
+ pThis->Lun0.IMediaExPort.pfnIoReqQueryDiscardRanges = NULL;
+ pThis->Lun0.IMediaExPort.pfnIoReqStateChanged = usbMsdLun0IoReqStateChanged;
+ pThis->Lun0.IMediaExPort.pfnMediumEjected = usbMsdLun0MediumEjected;
+ usbMsdQueueInit(&pThis->ToHostQueue);
+ usbMsdQueueInit(&pThis->DoneQueue);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventCreate(&pThis->hEvtDoneQueue);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventMultiCreate(&pThis->hEvtReset);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Validate and read the configuration.
+ */
+ rc = pHlp->pfnCFGMValidateConfig(pCfg, "/", "", "", "UsbMsd", iInstance);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach the SCSI driver.
+ */
+ rc = PDMUsbHlpDriverAttach(pUsbIns, 0 /*iLun*/, &pThis->Lun0.IBase, &pThis->Lun0.pIBase, "SCSI Port");
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("MSD failed to attach SCSI driver"));
+ pThis->Lun0.pIMedia = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIA);
+ if (!pThis->Lun0.pIMedia)
+ return PDMUsbHlpVMSetError(pUsbIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS,
+ N_("MSD failed to query the PDMIMEDIA from the driver below it"));
+ pThis->Lun0.pIMediaEx = PDMIBASE_QUERY_INTERFACE(pThis->Lun0.pIBase, PDMIMEDIAEX);
+ if (!pThis->Lun0.pIMediaEx)
+ return PDMUsbHlpVMSetError(pUsbIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS,
+ N_("MSD failed to query the PDMIMEDIAEX from the driver below it"));
+
+ /*
+ * Find out what kind of device we are.
+ */
+ pThis->fIsCdrom = false;
+ PDMMEDIATYPE enmType = pThis->Lun0.pIMedia->pfnGetType(pThis->Lun0.pIMedia);
+ /* Anything else will be reported as a hard disk. */
+ if (enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD)
+ pThis->fIsCdrom = true;
+
+ rc = pThis->Lun0.pIMediaEx->pfnIoReqAllocSizeSet(pThis->Lun0.pIMediaEx, sizeof(USBMSDREQ));
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS, N_("MSD failed to set I/O request size!"));
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMUsbHlpSSMRegister(pUsbIns, USB_MSD_SAVED_STATE_VERSION, sizeof(*pThis),
+ NULL, usbMsdLiveExec, NULL,
+ usbMsdSavePrep, usbMsdSaveExec, NULL,
+ usbMsdLoadPrep, usbMsdLoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return PDMUsbHlpVMSetError(pUsbIns, rc, RT_SRC_POS,
+ N_("MSD failed to register SSM save state handlers"));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The USB Mass Storage Device (MSD) registration record.
+ */
+const PDMUSBREG g_UsbMsd =
+{
+ /* u32Version */
+ PDM_USBREG_VERSION,
+ /* szName */
+ "Msd",
+ /* pszDescription */
+ "USB Mass Storage Device, one LUN.",
+ /* fFlags */
+ PDM_USBREG_HIGHSPEED_CAPABLE | PDM_USBREG_SUPERSPEED_CAPABLE
+ | PDM_USBREG_SAVED_STATE_SUPPORTED,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(USBMSD),
+ /* pfnConstruct */
+ usbMsdConstruct,
+ /* pfnDestruct */
+ usbMsdDestruct,
+ /* pfnVMInitComplete */
+ NULL,
+ /* pfnVMPowerOn */
+ NULL,
+ /* pfnVMReset */
+ usbMsdVMReset,
+ /* pfnVMSuspend */
+ usbMsdVMSuspend,
+ /* pfnVMResume */
+ NULL,
+ /* pfnVMPowerOff */
+ usbMsdVMPowerOff,
+ /* pfnHotPlugged */
+ NULL,
+ /* pfnHotUnplugged */
+ NULL,
+ /* pfnDriverAttach */
+ usbMsdDriverAttach,
+ /* pfnDriverDetach */
+ usbMsdDriverDetach,
+ /* pfnQueryInterface */
+ NULL,
+ /* pfnUsbReset */
+ usbMsdUsbReset,
+ /* pfnUsbGetCachedDescriptors */
+ usbMsdUsbGetDescriptorCache,
+ /* pfnUsbSetConfiguration */
+ usbMsdUsbSetConfiguration,
+ /* pfnUsbSetInterface */
+ usbMsdUsbSetInterface,
+ /* pfnUsbClearHaltedEndpoint */
+ usbMsdUsbClearHaltedEndpoint,
+ /* pfnUrbNew */
+ NULL/*usbMsdUrbNew*/,
+ /* pfnQueue */
+ usbMsdQueue,
+ /* pfnUrbCancel */
+ usbMsdUrbCancel,
+ /* pfnUrbReap */
+ usbMsdUrbReap,
+ /* pfnWakeup */
+ usbMsdWakeup,
+ /* u32TheEnd */
+ PDM_USBREG_VERSION
+};
+