diff options
Diffstat (limited to 'src/VBox/Devices/Storage/DevVirtioSCSI.cpp')
-rw-r--r-- | src/VBox/Devices/Storage/DevVirtioSCSI.cpp | 2772 |
1 files changed, 2772 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp new file mode 100644 index 00000000..4190ee00 --- /dev/null +++ b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp @@ -0,0 +1,2772 @@ +/* $Id: DevVirtioSCSI.cpp $ */ +/** @file + * VBox storage devices - Virtio SCSI Driver + * + * Log-levels used: + * - Level 1: The most important (but usually rare) things to note + * - Level 2: SCSI command logging + * - Level 3: Vector and I/O transfer summary (shows what client sent an expects and fulfillment) + * - Level 6: Device <-> Guest Driver negotation, traffic, notifications and state handling + * - Level 12: Brief formatted hex dumps of I/O data + */ + +/* + * Copyright (C) 2006-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_DRV_SCSI +#define LOG_GROUP LOG_GROUP_DEV_VIRTIO + +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmstorageifs.h> +#include <VBox/vmm/pdmcritsect.h> +#include <VBox/AssertGuest.h> +#include <VBox/msi.h> +#include <VBox/version.h> +#include <VBox/log.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <VBox/sup.h> +#include "../build/VBoxDD.h" +#include <VBox/scsi.h> +#ifdef IN_RING3 +# include <iprt/alloc.h> +# include <iprt/memcache.h> +# include <iprt/semaphore.h> +# include <iprt/sg.h> +# include <iprt/param.h> +# include <iprt/uuid.h> +#endif +#include "../VirtIO/VirtioCore.h" + +#include "VBoxSCSI.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The current saved state version. */ +#define VIRTIOSCSI_SAVED_STATE_VERSION UINT32_C(1) + + +#define LUN0 0 +/** @name VirtIO 1.0 SCSI Host feature bits (See VirtIO 1.0 specification, Section 5.6.3) + * @{ */ +#define VIRTIO_SCSI_F_INOUT RT_BIT_64(0) /** Request is device readable AND writeable */ +#define VIRTIO_SCSI_F_HOTPLUG RT_BIT_64(1) /** Host allows hotplugging SCSI LUNs & targets */ +#define VIRTIO_SCSI_F_CHANGE RT_BIT_64(2) /** Host LUNs chgs via VIRTIOSCSI_T_PARAM_CHANGE evt */ +#define VIRTIO_SCSI_F_T10_PI RT_BIT_64(3) /** Add T10 port info (DIF/DIX) in SCSI req hdr */ +/** @} */ + + +#define VIRTIOSCSI_HOST_SCSI_FEATURES_ALL \ + (VIRTIO_SCSI_F_INOUT | VIRTIO_SCSI_F_HOTPLUG | VIRTIO_SCSI_F_CHANGE | VIRTIO_SCSI_F_T10_PI) + +#define VIRTIOSCSI_HOST_SCSI_FEATURES_NONE 0 + +#define VIRTIOSCSI_HOST_SCSI_FEATURES_OFFERED VIRTIOSCSI_HOST_SCSI_FEATURES_NONE + +#define VIRTIOSCSI_REQ_VIRTQ_CNT 4 /**< T.B.D. Consider increasing */ +#define VIRTIOSCSI_VIRTQ_CNT (VIRTIOSCSI_REQ_VIRTQ_CNT + 2) +#define VIRTIOSCSI_MAX_TARGETS 256 /**< T.B.D. Figure out a a good value for this. */ +#define VIRTIOSCSI_MAX_LUN 1 /**< VirtIO specification, section 5.6.4 */ +#define VIRTIOSCSI_MAX_COMMANDS_PER_LUN 128 /**< T.B.D. What is a good value for this? */ +#define VIRTIOSCSI_MAX_SEG_COUNT 126 /**< T.B.D. What is a good value for this? */ +#define VIRTIOSCSI_MAX_SECTORS_HINT 0x10000 /**< VirtIO specification, section 5.6.4 */ +#define VIRTIOSCSI_MAX_CHANNEL_HINT 0 /**< VirtIO specification, section 5.6.4 should be 0 */ + +#define PCI_DEVICE_ID_VIRTIOSCSI_HOST 0x1048 /**< Informs guest driver of type of VirtIO device */ +#define PCI_CLASS_BASE_MASS_STORAGE 0x01 /**< PCI Mass Storage device class */ +#define PCI_CLASS_SUB_SCSI_STORAGE_CONTROLLER 0x00 /**< PCI SCSI Controller subclass */ +#define PCI_CLASS_PROG_UNSPECIFIED 0x00 /**< Programming interface. N/A. */ +#define VIRTIOSCSI_PCI_CLASS 0x01 /**< Base class Mass Storage? */ + +#define VIRTIOSCSI_SENSE_SIZE_DEFAULT 96 /**< VirtIO 1.0: 96 on reset, guest can change */ +#define VIRTIOSCSI_SENSE_SIZE_MAX 4096 /**< Picked out of thin air by bird. */ +#define VIRTIOSCSI_CDB_SIZE_DEFAULT 32 /**< VirtIO 1.0: 32 on reset, guest can change */ +#define VIRTIOSCSI_CDB_SIZE_MAX 255 /**< Picked out of thin air by bird. */ +#define VIRTIOSCSI_PI_BYTES_IN 1 /**< Value TBD (see section 5.6.6.1) */ +#define VIRTIOSCSI_PI_BYTES_OUT 1 /**< Value TBD (see section 5.6.6.1) */ +#define VIRTIOSCSI_DATA_OUT 512 /**< Value TBD (see section 5.6.6.1) */ + +/** + * VirtIO SCSI Host Device device-specific queue indicies. + * (Note: # of request queues is determined by virtio_scsi_config.num_queues. VirtIO 1.0, 5.6.4) + */ +#define CONTROLQ_IDX 0 /**< VirtIO Spec-defined Index of control queue */ +#define EVENTQ_IDX 1 /**< VirtIO Spec-defined Index of event queue */ +#define VIRTQ_REQ_BASE 2 /**< VirtIO Spec-defined base index of req. queues */ + +#define VIRTQNAME(uVirtqNbr) (pThis->aszVirtqNames[uVirtqNbr]) /**< Macro to get queue name from its index */ +#define CBVIRTQNAME(uVirtqNbr) RTStrNLen(VIRTQNAME(uVirtqNbr), sizeof(VIRTQNAME(uVirtqNbr))) + +#define IS_REQ_VIRTQ(uVirtqNbr) (uVirtqNbr >= VIRTQ_REQ_BASE && uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT) + +#define VIRTIO_IS_IN_DIRECTION(pMediaExTxDirEnumValue) \ + ((pMediaExTxDirEnumValue) == PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE) + +#define VIRTIO_IS_OUT_DIRECTION(pMediaExTxDirEnumValue) \ + ((pMediaExTxDirEnumValue) == PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE) + +#define IS_VIRTQ_EMPTY(pDevIns, pVirtio, uVirtqNbr) \ + (virtioCoreVirtqAvailBufCount(pDevIns, pVirtio, uVirtqNbr) == 0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * VirtIO SCSI Host Device device-specific configuration (see VirtIO 1.0, section 5.6.4) + * VBox VirtIO core issues callback to this VirtIO device-specific implementation to handle + * MMIO accesses to device-specific configuration parameters. + */ +typedef struct virtio_scsi_config +{ + uint32_t uNumVirtqs; /**< num_queues \# of req q's exposed by dev */ + uint32_t uSegMax; /**< seg_max Max \# of segs allowed in cmd */ + uint32_t uMaxSectors; /**< max_sectors Hint to guest max xfer to use */ + uint32_t uCmdPerLun; /**< cmd_per_lun Max \# of link cmd sent per lun */ + uint32_t uEventInfoSize; /**< event_info_size Fill max, evtq bufs */ + uint32_t uSenseSize; /**< sense_size Max sense data size dev writes */ + uint32_t uCdbSize; /**< cdb_size Max CDB size driver writes */ + uint16_t uMaxChannel; /**< max_channel Hint to guest driver */ + uint16_t uMaxTarget; /**< max_target Hint to guest driver */ + uint32_t uMaxLun; /**< max_lun Hint to guest driver */ +} VIRTIOSCSI_CONFIG_T, PVIRTIOSCSI_CONFIG_T; + +/** @name VirtIO 1.0 SCSI Host Device device specific control types + * @{ */ +#define VIRTIOSCSI_T_NO_EVENT 0 +#define VIRTIOSCSI_T_TRANSPORT_RESET 1 +#define VIRTIOSCSI_T_ASYNC_NOTIFY 2 /**< Asynchronous notification */ +#define VIRTIOSCSI_T_PARAM_CHANGE 3 +/** @} */ + +/** + * Device operation: eventq + */ +#define VIRTIOSCSI_T_EVENTS_MISSED UINT32_C(0x80000000) +typedef struct virtio_scsi_event +{ + // Device-writable part + uint32_t uEvent; /**< event */ + uint8_t abVirtioLun[8]; /**< lun */ + uint32_t uReason; /**< reason */ +} VIRTIOSCSI_EVENT_T, *PVIRTIOSCSI_EVENT_T; + +/** @name VirtIO 1.0 SCSI Host Device device specific event types + * @{ */ +#define VIRTIOSCSI_EVT_RESET_HARD 0 /**< */ +#define VIRTIOSCSI_EVT_RESET_RESCAN 1 /**< */ +#define VIRTIOSCSI_EVT_RESET_REMOVED 2 /**< */ +/** @} */ + +/** + * Device operation: reqestq + */ +#pragma pack(1) +typedef struct REQ_CMD_HDR_T +{ + uint8_t abVirtioLun[8]; /**< lun */ + uint64_t uId; /**< id */ + uint8_t uTaskAttr; /**< task_attr */ + uint8_t uPrio; /**< prio */ + uint8_t uCrn; /**< crn */ +} REQ_CMD_HDR_T; +#pragma pack() +AssertCompileSize(REQ_CMD_HDR_T, 19); + +typedef struct REQ_CMD_PI_T +{ + uint32_t uPiBytesOut; /**< pi_bytesout */ + uint32_t uPiBytesIn; /**< pi_bytesin */ +} REQ_CMD_PI_T; +AssertCompileSize(REQ_CMD_PI_T, 8); + +typedef struct REQ_RESP_HDR_T +{ + uint32_t cbSenseLen; /**< sense_len */ + uint32_t uResidual; /**< residual */ + uint16_t uStatusQualifier; /**< status_qualifier */ + uint8_t uStatus; /**< status SCSI status code */ + uint8_t uResponse; /**< response */ +} REQ_RESP_HDR_T; +AssertCompileSize(REQ_RESP_HDR_T, 12); + +#pragma pack(1) +typedef struct VIRTIOSCSI_REQ_CMD_T +{ + /** Device-readable section + * @{ */ + REQ_CMD_HDR_T ReqHdr; + uint8_t uCdb[1]; /**< cdb */ + + REQ_CMD_PI_T piHdr; /**< T10 Pi block integrity (optional feature) */ + uint8_t uPiOut[1]; /**< pi_out[] T10 pi block integrity */ + uint8_t uDataOut[1]; /**< dataout */ + /** @} */ + + /** @name Device writable section + * @{ */ + REQ_RESP_HDR_T respHdr; + uint8_t uSense[1]; /**< sense */ + uint8_t uPiIn[1]; /**< pi_in[] T10 Pi block integrity */ + uint8_t uDataIn[1]; /**< detain; */ + /** @} */ +} VIRTIOSCSI_REQ_CMD_T, *PVIRTIOSCSI_REQ_CMD_T; +#pragma pack() +AssertCompileSize(VIRTIOSCSI_REQ_CMD_T, 19+8+12+6); + +/** @name VirtIO 1.0 SCSI Host Device Req command-specific response values + * @{ */ +#define VIRTIOSCSI_S_OK 0 /**< control, command */ +#define VIRTIOSCSI_S_OVERRUN 1 /**< control */ +#define VIRTIOSCSI_S_ABORTED 2 /**< control */ +#define VIRTIOSCSI_S_BAD_TARGET 3 /**< control, command */ +#define VIRTIOSCSI_S_RESET 4 /**< control */ +#define VIRTIOSCSI_S_BUSY 5 /**< control, command */ +#define VIRTIOSCSI_S_TRANSPORT_FAILURE 6 /**< control, command */ +#define VIRTIOSCSI_S_TARGET_FAILURE 7 /**< control, command */ +#define VIRTIOSCSI_S_NEXUS_FAILURE 8 /**< control, command */ +#define VIRTIOSCSI_S_FAILURE 9 /**< control, command */ +#define VIRTIOSCSI_S_INCORRECT_LUN 12 /**< command */ +/** @} */ + +/** @name VirtIO 1.0 SCSI Host Device command-specific task_attr values + * @{ */ +#define VIRTIOSCSI_S_SIMPLE 0 /**< */ +#define VIRTIOSCSI_S_ORDERED 1 /**< */ +#define VIRTIOSCSI_S_HEAD 2 /**< */ +#define VIRTIOSCSI_S_ACA 3 /**< */ +/** @} */ + +/** + * VirtIO 1.0 SCSI Host Device Control command before we know type (5.6.6.2) + */ +typedef struct VIRTIOSCSI_CTRL_T +{ + uint32_t uType; +} VIRTIOSCSI_CTRL_T, *PVIRTIOSCSI_CTRL_T; + +/** @name VirtIO 1.0 SCSI Host Device command-specific TMF values + * @{ */ +#define VIRTIOSCSI_T_TMF 0 /**< */ +#define VIRTIOSCSI_T_TMF_ABORT_TASK 0 /**< */ +#define VIRTIOSCSI_T_TMF_ABORT_TASK_SET 1 /**< */ +#define VIRTIOSCSI_T_TMF_CLEAR_ACA 2 /**< */ +#define VIRTIOSCSI_T_TMF_CLEAR_TASK_SET 3 /**< */ +#define VIRTIOSCSI_T_TMF_I_T_NEXUS_RESET 4 /**< */ +#define VIRTIOSCSI_T_TMF_LOGICAL_UNIT_RESET 5 /**< */ +#define VIRTIOSCSI_T_TMF_QUERY_TASK 6 /**< */ +#define VIRTIOSCSI_T_TMF_QUERY_TASK_SET 7 /**< */ +/** @} */ + +#pragma pack(1) +typedef struct VIRTIOSCSI_CTRL_TMF_T +{ + uint32_t uType; /**< type */ + uint32_t uSubtype; /**< subtype */ + uint8_t abScsiLun[8]; /**< lun */ + uint64_t uId; /**< id */ +} VIRTIOSCSI_CTRL_TMF_T, *PVIRTIOSCSI_CTRL_TMF_T; +#pragma pack() +AssertCompileSize(VIRTIOSCSI_CTRL_TMF_T, 24); + +/** VirtIO 1.0 section 5.6.6.2, CTRL TMF response is an 8-bit status */ + +/** @name VirtIO 1.0 SCSI Host Device device specific tmf control response values + * @{ */ +#define VIRTIOSCSI_S_FUNCTION_COMPLETE 0 /**< */ +#define VIRTIOSCSI_S_FUNCTION_SUCCEEDED 10 /**< */ +#define VIRTIOSCSI_S_FUNCTION_REJECTED 11 /**< */ +/** @} */ + +#define VIRTIOSCSI_T_AN_QUERY 1 /**< Asynchronous notification query */ +#define VIRTIOSCSI_T_AN_SUBSCRIBE 2 /**< Asynchronous notification subscription */ + +#pragma pack(1) +typedef struct VIRTIOSCSI_CTRL_AN_T +{ + uint32_t uType; /**< type */ + uint8_t abScsiLun[8]; /**< lun */ + uint32_t fEventsRequested; /**< event_requested */ +} VIRTIOSCSI_CTRL_AN_T, *PVIRTIOSCSI_CTRL_AN_T; +#pragma pack() +AssertCompileSize(VIRTIOSCSI_CTRL_AN_T, 16); + +/** VirtIO 1.0, Section 5.6.6.2, CTRL AN response is 4-byte evt mask + 8-bit status */ + +typedef union VIRTIO_SCSI_CTRL_UNION_T +{ + VIRTIOSCSI_CTRL_T Type; + VIRTIOSCSI_CTRL_TMF_T Tmf; + VIRTIOSCSI_CTRL_AN_T AsyncNotify; + uint8_t ab[24]; +} VIRTIO_SCSI_CTRL_UNION_T, *PVIRTIO_SCSI_CTRL_UNION_T; +AssertCompile(sizeof(VIRTIO_SCSI_CTRL_UNION_T) == 24); /* VIRTIOSCSI_CTRL_T forces 4 byte alignment, the other two are byte packed. */ + +/** @name VirtIO 1.0 SCSI Host Device device specific tmf control response values + * @{ */ +#define VIRTIOSCSI_EVT_ASYNC_OPERATIONAL_CHANGE 2 /**< */ +#define VIRTIOSCSI_EVT_ASYNC_POWER_MGMT 4 /**< */ +#define VIRTIOSCSI_EVT_ASYNC_EXTERNAL_REQUEST 8 /**< */ +#define VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE 16 /**< */ +#define VIRTIOSCSI_EVT_ASYNC_MULTI_HOST 32 /**< */ +#define VIRTIOSCSI_EVT_ASYNC_DEVICE_BUSY 64 /**< */ +/** @} */ + +#define SUBSCRIBABLE_EVENTS \ + ( VIRTIOSCSI_EVT_ASYNC_OPERATIONAL_CHANGE \ + | VIRTIOSCSI_EVT_ASYNC_POWER_MGMT \ + | VIRTIOSCSI_EVT_ASYNC_EXTERNAL_REQUEST \ + | VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE \ + | VIRTIOSCSI_EVT_ASYNC_MULTI_HOST \ + | VIRTIOSCSI_EVT_ASYNC_DEVICE_BUSY ) + +#define SUPPORTED_EVENTS 0 /* TBD */ + +/** + * Worker thread context, shared state. + */ +typedef struct VIRTIOSCSIWORKER +{ + SUPSEMEVENT hEvtProcess; /**< handle of associated sleep/wake-up semaphore */ + bool volatile fSleeping; /**< Flags whether worker thread is sleeping or not */ + bool volatile fNotified; /**< Flags whether worker thread notified */ +} VIRTIOSCSIWORKER; +/** Pointer to a VirtIO SCSI worker. */ +typedef VIRTIOSCSIWORKER *PVIRTIOSCSIWORKER; + +/** + * Worker thread context, ring-3 state. + */ +typedef struct VIRTIOSCSIWORKERR3 +{ + R3PTRTYPE(PPDMTHREAD) pThread; /**< pointer to worker thread's handle */ + uint16_t auRedoDescs[VIRTQ_SIZE];/**< List of previously suspended reqs to re-submit */ + uint16_t cRedoDescs; /**< Number of redo desc chain head desc idxes in list */ +} VIRTIOSCSIWORKERR3; +/** Pointer to a VirtIO SCSI worker. */ +typedef VIRTIOSCSIWORKERR3 *PVIRTIOSCSIWORKERR3; + +/** + * State of a target attached to the VirtIO SCSI Host + */ +typedef struct VIRTIOSCSITARGET +{ + /** The ring-3 device instance so we can easily get our bearings. */ + PPDMDEVINSR3 pDevIns; + + /** Pointer to attached driver's base interface. */ + R3PTRTYPE(PPDMIBASE) pDrvBase; + + /** Target number (PDM LUN) */ + uint32_t uTarget; + + /** Target Description */ + R3PTRTYPE(char *) pszTargetName; + + /** Target base interface. */ + PDMIBASE IBase; + + /** Flag whether device is present. */ + bool fPresent; + + /** Media port interface. */ + PDMIMEDIAPORT IMediaPort; + + /** Pointer to the attached driver's media interface. */ + R3PTRTYPE(PPDMIMEDIA) pDrvMedia; + + /** Extended media port interface. */ + PDMIMEDIAEXPORT IMediaExPort; + + /** Pointer to the attached driver's extended media interface. */ + R3PTRTYPE(PPDMIMEDIAEX) pDrvMediaEx; + + /** Status LED interface */ + PDMILEDPORTS ILed; + + /** The status LED state for this device. */ + PDMLED led; + +} VIRTIOSCSITARGET, *PVIRTIOSCSITARGET; + +/** + * VirtIO Host SCSI device state, shared edition. + * + * @extends VIRTIOCORE + */ +typedef struct VIRTIOSCSI +{ + /** The core virtio state. */ + VIRTIOCORE Virtio; + + /** VirtIO Host SCSI device runtime configuration parameters */ + VIRTIOSCSI_CONFIG_T virtioScsiConfig; + + bool fBootable; + bool afPadding0[3]; + + /** Number of targets in paTargetInstances. */ + uint32_t cTargets; + + /** Per device-bound virtq worker-thread contexts (eventq slot unused) */ + VIRTIOSCSIWORKER aWorkers[VIRTIOSCSI_VIRTQ_CNT]; + + /** Instance name */ + char szInstance[16]; + + /** Device-specific spec-based VirtIO VIRTQNAMEs */ + char aszVirtqNames[VIRTIOSCSI_VIRTQ_CNT][VIRTIO_MAX_VIRTQ_NAME_SIZE]; + + /** Track which VirtIO queues we've attached to */ + bool afVirtqAttached[VIRTIOSCSI_VIRTQ_CNT]; + + /** Set if events missed due to lack of bufs avail on eventq */ + bool fEventsMissed; + + /** Explicit alignment padding. */ + bool afPadding1[2]; + + /** Mask of VirtIO Async Event types this device will deliver */ + uint32_t fAsyncEvtsEnabled; + + /** Total number of requests active across all targets */ + volatile uint32_t cActiveReqs; + + + /** True if the guest/driver and VirtIO framework are in the ready state */ + uint32_t fVirtioReady; + + /** True if VIRTIO_SCSI_F_T10_PI was negotiated */ + uint32_t fHasT10pi; + + /** True if VIRTIO_SCSI_F_HOTPLUG was negotiated */ + uint32_t fHasHotplug; + + /** True if VIRTIO_SCSI_F_INOUT was negotiated */ + uint32_t fHasInOutBufs; + + /** True if VIRTIO_SCSI_F_CHANGE was negotiated */ + uint32_t fHasLunChange; + + /** True if in the process of resetting */ + uint32_t fResetting; + +} VIRTIOSCSI; +/** Pointer to the shared state of the VirtIO Host SCSI device. */ +typedef VIRTIOSCSI *PVIRTIOSCSI; + + +/** + * VirtIO Host SCSI device state, ring-3 edition. + * + * @extends VIRTIOCORER3 + */ +typedef struct VIRTIOSCSIR3 +{ + /** The core virtio ring-3 state. */ + VIRTIOCORER3 Virtio; + + /** Array of per-target data. */ + R3PTRTYPE(PVIRTIOSCSITARGET) paTargetInstances; + + /** Per device-bound virtq worker-thread contexts (eventq slot unused) */ + VIRTIOSCSIWORKERR3 aWorkers[VIRTIOSCSI_VIRTQ_CNT]; + + /** Device base interface. */ + PDMIBASE IBase; + + /** Pointer to the device instance. + * @note Only used in interface callbacks. */ + PPDMDEVINSR3 pDevIns; + + /** Status Target: LEDs port interface. */ + PDMILEDPORTS ILeds; + + /** IMediaExPort: Media ejection notification */ + R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify; + + /** Virtq to send tasks to R3. - HC ptr */ + R3PTRTYPE(PPDMQUEUE) pNotifierVirtqR3; + + /** True if in the process of quiescing I/O */ + uint32_t fQuiescing; + + /** For which purpose we're quiescing. */ + VIRTIOVMSTATECHANGED enmQuiescingFor; + +} VIRTIOSCSIR3; +/** Pointer to the ring-3 state of the VirtIO Host SCSI device. */ +typedef VIRTIOSCSIR3 *PVIRTIOSCSIR3; + + +/** + * VirtIO Host SCSI device state, ring-0 edition. + */ +typedef struct VIRTIOSCSIR0 +{ + /** The core virtio ring-0 state. */ + VIRTIOCORER0 Virtio; +} VIRTIOSCSIR0; +/** Pointer to the ring-0 state of the VirtIO Host SCSI device. */ +typedef VIRTIOSCSIR0 *PVIRTIOSCSIR0; + + +/** + * VirtIO Host SCSI device state, raw-mode edition. + */ +typedef struct VIRTIOSCSIRC +{ + /** The core virtio raw-mode state. */ + VIRTIOCORERC Virtio; +} VIRTIOSCSIRC; +/** Pointer to the ring-0 state of the VirtIO Host SCSI device. */ +typedef VIRTIOSCSIRC *PVIRTIOSCSIRC; + + +/** @typedef VIRTIOSCSICC + * The instance data for the current context. */ +typedef CTX_SUFF(VIRTIOSCSI) VIRTIOSCSICC; +/** @typedef PVIRTIOSCSICC + * Pointer to the instance data for the current context. */ +typedef CTX_SUFF(PVIRTIOSCSI) PVIRTIOSCSICC; + + +/** + * Request structure for IMediaEx (Associated Interfaces implemented by DrvSCSI) + * @note cbIn, cbOUt, cbDataOut mostly for debugging + */ +typedef struct VIRTIOSCSIREQ +{ + PDMMEDIAEXIOREQ hIoReq; /**< Handle of I/O request */ + PVIRTIOSCSITARGET pTarget; /**< Target */ + uint16_t uVirtqNbr; /**< Index of queue this request arrived on */ + PVIRTQBUF pVirtqBuf; /**< Prepared desc chain pulled from virtq avail ring */ + size_t cbDataIn; /**< size of datain buffer */ + size_t cbDataOut; /**< size of dataout buffer */ + uint16_t uDataInOff; /**< Fixed size of respHdr + sense (precede datain) */ + uint16_t uDataOutOff; /**< Fixed size of reqhdr + cdb (precede dataout) */ + uint32_t cbSenseAlloc; /**< Size of sense buffer */ + size_t cbSenseLen; /**< Receives \# bytes written into sense buffer */ + uint8_t *pbSense; /**< Pointer to R3 sense buffer */ + PDMMEDIAEXIOREQSCSITXDIR enmTxDir; /**< Receives transfer direction of I/O req */ + uint8_t uStatus; /**< SCSI status code */ +} VIRTIOSCSIREQ; +typedef VIRTIOSCSIREQ *PVIRTIOSCSIREQ; + + +/** + * callback_method_impl{VIRTIOCORER0,pfnVirtqNotified} + * @todo this causes burn if I prefix with at-sign. This callback is in VIRTIOCORER0 and VIRTIOCORER3 + */ +static DECLCALLBACK(void) virtioScsiNotified(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr) +{ + + RT_NOREF(pVirtio); + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + + AssertReturnVoid(uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT); + PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[uVirtqNbr]; + +#if defined (IN_RING3) && defined (LOG_ENABLED) + RTLogFlush(NULL); +#endif + + if (uVirtqNbr == CONTROLQ_IDX || IS_REQ_VIRTQ(uVirtqNbr)) + { + Log6Func(("%s has available data\n", VIRTQNAME(uVirtqNbr))); + /* Wake queue's worker thread up if sleeping */ + if (!ASMAtomicXchgBool(&pWorker->fNotified, true)) + { + if (ASMAtomicReadBool(&pWorker->fSleeping)) + { + Log6Func(("waking %s worker.\n", VIRTQNAME(uVirtqNbr))); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pWorker->hEvtProcess); + AssertRC(rc); + } + } + } + else if (uVirtqNbr == EVENTQ_IDX) + { + Log3Func(("Driver queued buffer(s) to %s\n", VIRTQNAME(uVirtqNbr))); +// if (ASMAtomicXchgBool(&pThis->fEventsMissed, false)) +// virtioScsiR3ReportEventsMissed(pDevIns, pThis, 0); + } + else + LogFunc(("Unexpected queue idx (ignoring): %d\n", uVirtqNbr)); +} + + +#ifdef IN_RING3 /* spans most of the file, at the moment. */ + + +DECLINLINE(void) virtioScsiSetVirtqNames(PVIRTIOSCSI pThis) +{ + RTStrCopy(pThis->aszVirtqNames[CONTROLQ_IDX], VIRTIO_MAX_VIRTQ_NAME_SIZE, "controlq"); + RTStrCopy(pThis->aszVirtqNames[EVENTQ_IDX], VIRTIO_MAX_VIRTQ_NAME_SIZE, "eventq"); + for (uint16_t uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTQ_REQ_BASE + VIRTIOSCSI_REQ_VIRTQ_CNT; uVirtqNbr++) + RTStrPrintf(pThis->aszVirtqNames[uVirtqNbr], VIRTIO_MAX_VIRTQ_NAME_SIZE, + "requestq<%d>", uVirtqNbr - VIRTQ_REQ_BASE); +} + +#ifdef LOG_ENABLED + + +DECLINLINE(const char *) virtioGetTxDirText(uint32_t enmTxDir) +{ + switch (enmTxDir) + { + case PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN: return "<UNKNOWN>"; + case PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE: return "<DEV-TO-GUEST>"; + case PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE: return "<GUEST-TO-DEV>"; + case PDMMEDIAEXIOREQSCSITXDIR_NONE: return "<NONE>"; + default: return "<BAD ENUM>"; + } +} + +DECLINLINE(const char *) virtioGetTMFTypeText(uint32_t uSubType) +{ + switch (uSubType) + { + case VIRTIOSCSI_T_TMF_ABORT_TASK: return "ABORT TASK"; + case VIRTIOSCSI_T_TMF_ABORT_TASK_SET: return "ABORT TASK SET"; + case VIRTIOSCSI_T_TMF_CLEAR_ACA: return "CLEAR ACA"; + case VIRTIOSCSI_T_TMF_CLEAR_TASK_SET: return "CLEAR TASK SET"; + case VIRTIOSCSI_T_TMF_I_T_NEXUS_RESET: return "I T NEXUS RESET"; + case VIRTIOSCSI_T_TMF_LOGICAL_UNIT_RESET: return "LOGICAL UNIT RESET"; + case VIRTIOSCSI_T_TMF_QUERY_TASK: return "QUERY TASK"; + case VIRTIOSCSI_T_TMF_QUERY_TASK_SET: return "QUERY TASK SET"; + default: return "<unknown>"; + } +} + +DECLINLINE(const char *) virtioGetReqRespText(uint32_t vboxRc) +{ + switch (vboxRc) + { + case VIRTIOSCSI_S_OK: return "OK/COMPLETE"; + case VIRTIOSCSI_S_OVERRUN: return "OVERRRUN"; + case VIRTIOSCSI_S_ABORTED: return "ABORTED"; + case VIRTIOSCSI_S_BAD_TARGET: return "BAD TARGET"; + case VIRTIOSCSI_S_RESET: return "RESET"; + case VIRTIOSCSI_S_TRANSPORT_FAILURE: return "TRANSPORT FAILURE"; + case VIRTIOSCSI_S_TARGET_FAILURE: return "TARGET FAILURE"; + case VIRTIOSCSI_S_NEXUS_FAILURE: return "NEXUS FAILURE"; + case VIRTIOSCSI_S_BUSY: return "BUSY"; + case VIRTIOSCSI_S_FAILURE: return "FAILURE"; + case VIRTIOSCSI_S_INCORRECT_LUN: return "INCORRECT LUN"; + case VIRTIOSCSI_S_FUNCTION_SUCCEEDED: return "FUNCTION SUCCEEDED"; + case VIRTIOSCSI_S_FUNCTION_REJECTED: return "FUNCTION REJECTED"; + default: return "<unknown>"; + } +} + +DECLINLINE(void) virtioGetControlAsyncMaskText(char *pszOutput, uint32_t cbOutput, uint32_t fAsyncTypesMask) +{ + RTStrPrintf(pszOutput, cbOutput, "%s%s%s%s%s%s", + fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_OPERATIONAL_CHANGE ? "CHANGE_OPERATION " : "", + fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_POWER_MGMT ? "POWER_MGMT " : "", + fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_EXTERNAL_REQUEST ? "EXTERNAL_REQ " : "", + fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE ? "MEDIA_CHANGE " : "", + fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_MULTI_HOST ? "MULTI_HOST " : "", + fAsyncTypesMask & VIRTIOSCSI_EVT_ASYNC_DEVICE_BUSY ? "DEVICE_BUSY " : ""); +} + +static uint8_t virtioScsiEstimateCdbLen(uint8_t uCmd, uint8_t cbMax) +{ + if (uCmd < 0x1f) + return RT_MIN(6, cbMax); + if (uCmd >= 0x20 && uCmd < 0x60) + return RT_MIN(10, cbMax); + if (uCmd >= 0x60 && uCmd < 0x80) + return cbMax; + if (uCmd >= 0x80 && uCmd < 0xa0) + return RT_MIN(16, cbMax); + if (uCmd >= 0xa0 && uCmd < 0xc0) + return RT_MIN(12, cbMax); + return cbMax; +} + +#endif /* LOG_ENABLED */ + + +/* + * @todo Figure out how to implement this with R0 changes. Not used by current linux driver + */ + +#if 0 +static int virtioScsiR3SendEvent(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget, uint32_t uEventType, uint32_t uReason) +{ + switch (uEventType) + { + case VIRTIOSCSI_T_NO_EVENT: + Log6Func(("(target=%d, LUN=%d): Warning event info guest queued is shorter than configured\n", uTarget, LUN0)); + break; + case VIRTIOSCSI_T_NO_EVENT | VIRTIOSCSI_T_EVENTS_MISSED: + Log6Func(("(target=%d, LUN=%d): Warning driver that events were missed\n", uTarget, LUN0)); + break; + case VIRTIOSCSI_T_TRANSPORT_RESET: + switch (uReason) + { + case VIRTIOSCSI_EVT_RESET_REMOVED: + Log6Func(("(target=%d, LUN=%d): Target or LUN removed\n", uTarget, LUN0)); + break; + case VIRTIOSCSI_EVT_RESET_RESCAN: + Log6Func(("(target=%d, LUN=%d): Target or LUN added\n", uTarget, LUN0)); + break; + case VIRTIOSCSI_EVT_RESET_HARD: + Log6Func(("(target=%d, LUN=%d): Target was reset\n", uTarget, LUN0)); + break; + } + break; + case VIRTIOSCSI_T_ASYNC_NOTIFY: + { +#ifdef LOG_ENABLED + char szTypeText[128]; + virtioGetControlAsyncMaskText(szTypeText, sizeof(szTypeText), uReason); + Log6Func(("(target=%d, LUN=%d): Delivering subscribed async notification %s\n", uTarget, LUN0, szTypeText)); +#endif + break; + } + case VIRTIOSCSI_T_PARAM_CHANGE: + LogFunc(("(target=%d, LUN=%d): PARAM_CHANGE sense code: 0x%x sense qualifier: 0x%x\n", + uTarget, LUN0, uReason & 0xff, (uReason >> 8) & 0xff)); + break; + default: + Log6Func(("(target=%d, LUN=%d): Unknown event type: %d, ignoring\n", uTarget, LUN0, uEventType)); + return VINF_SUCCESS; + } + + PVIRTQBUF pVirtqBuf = NULL; + int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, EVENTQ_IDX, &pVirtqBuf, true); + if (rc == VERR_NOT_AVAILABLE) + { + LogFunc(("eventq is empty, events missed (driver didn't preload queue)!\n")); + ASMAtomicWriteBool(&pThis->fEventsMissed, true); + return VINF_SUCCESS; + } + AssertRCReturn(rc, rc); + + VIRTIOSCSI_EVENT_T Event; + Event.uEvent = uEventType; + Event.uReason = uReason; + Event.abVirtioLun[0] = 1; + Event.abVirtioLun[1] = uTarget; + Event.abVirtioLun[2] = (LUN0 >> 8) & 0x40; + Event.abVirtioLun[3] = LUN0 & 0xff; + Event.abVirtioLun[4] = 0; + Event.abVirtioLun[5] = 0; + Event.abVirtioLun[6] = 0; + Event.abVirtioLun[7] = 0; + + RTSGSEG aReqSegs[1]; + aReqSegs[0].pvSeg = &Event; + aReqSegs[0].cbSeg = sizeof(Event); + + RTSGBUF ReqSgBuf; + RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs)); + + rc = virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, EVENTQ_IDX, &ReqSgBuf, pVirtqBuf, true /*fFence*/); + if (rc == VINF_SUCCESS) + virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, EVENTQ_IDX, false); + else + LogRel(("Error writing control message to guest\n")); + virtioCoreR3VirtqBufRelease(&pThis->Virtio, pVirtqBuf); + + return rc; +} +#endif + +/** + * Releases one reference from the given controller instances active request counter. + * + * @param pDevIns The device instance. + * @param pThis VirtIO SCSI shared instance data. + * @param pThisCC VirtIO SCSI ring-3 instance data. + */ +DECLINLINE(void) virtioScsiR3Release(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC) +{ + Assert(pThis->cActiveReqs); + + if (!ASMAtomicDecU32(&pThis->cActiveReqs) && pThisCC->fQuiescing) + PDMDevHlpAsyncNotificationCompleted(pDevIns); +} + +/** + * Retains one reference for the given controller instances active request counter. + * + * @param pThis VirtIO SCSI shared instance data. + */ +DECLINLINE(void) virtioScsiR3Retain(PVIRTIOSCSI pThis) +{ + ASMAtomicIncU32(&pThis->cActiveReqs); +} + +/** Internal worker. */ +static void virtioScsiR3FreeReq(PVIRTIOSCSITARGET pTarget, PVIRTIOSCSIREQ pReq) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pTarget->pDevIns, PVIRTIOSCSI); + RTMemFree(pReq->pbSense); + pReq->pbSense = NULL; + virtioCoreR3VirtqBufRelease(&pThis->Virtio, pReq->pVirtqBuf); + pReq->pVirtqBuf = NULL; + pTarget->pDrvMediaEx->pfnIoReqFree(pTarget->pDrvMediaEx, pReq->hIoReq); +} + +/** + * This is called to complete a request immediately + * + * @param pDevIns The device instance. + * @param pThis VirtIO SCSI shared instance data. + * @param uVirtqNbr Virtq index + * @param pVirtqBuf Pointer to pre-processed descriptor chain pulled from virtq + * @param pRespHdr Response header + * @param pbSense Pointer to sense buffer or NULL if none. + * @param cbSenseCfg The configured sense buffer size. + * + * @returns VINF_SUCCESS + */ +static int virtioScsiR3ReqErr(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uVirtqNbr, + PVIRTQBUF pVirtqBuf, REQ_RESP_HDR_T *pRespHdr, uint8_t *pbSense, + size_t cbSenseCfg) +{ + Log2Func((" status: %s response: %s\n", + SCSIStatusText(pRespHdr->uStatus), virtioGetReqRespText(pRespHdr->uResponse))); + + RTSGSEG aReqSegs[2]; + + /* Segment #1: Response header*/ + aReqSegs[0].pvSeg = pRespHdr; + aReqSegs[0].cbSeg = sizeof(*pRespHdr); + + /* Segment #2: Sense data. */ + uint8_t abSenseBuf[VIRTIOSCSI_SENSE_SIZE_MAX]; + AssertCompile(VIRTIOSCSI_SENSE_SIZE_MAX <= 4096); + Assert(cbSenseCfg <= sizeof(abSenseBuf)); + + RT_ZERO(abSenseBuf); + if (pbSense && pRespHdr->cbSenseLen) + memcpy(abSenseBuf, pbSense, RT_MIN(pRespHdr->cbSenseLen, sizeof(abSenseBuf))); + else + pRespHdr->cbSenseLen = 0; + + aReqSegs[1].pvSeg = abSenseBuf; + aReqSegs[1].cbSeg = cbSenseCfg; + + /* Init S/G buffer. */ + RTSGBUF ReqSgBuf; + RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs)); + + if (pThis->fResetting) + pRespHdr->uResponse = VIRTIOSCSI_S_RESET; + + virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, uVirtqNbr, &ReqSgBuf, pVirtqBuf, true /* fFence */); + virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, uVirtqNbr); + + Log2(("---------------------------------------------------------------------------------\n")); + + return VINF_SUCCESS; +} + + +/** + * Variant of virtioScsiR3ReqErr that takes four (4) REQ_RESP_HDR_T member + * fields rather than a pointer to an initialized structure. + * + * @param pDevIns The device instance. + * @param pThis VirtIO SCSI shared instance data. + * @param uVirtqNbr Virtq index + * @param pVirtqBuf Pointer to pre-processed descriptor chain pulled from virtq + * @param cbResidual The number of residual bytes or something like that. + * @param bStatus The SCSI status code. + * @param bResponse The virtio SCSI response code. + * @param pbSense Pointer to sense buffer or NULL if none. + * @param cbSense The number of bytes of sense data. Zero if none. + * @param cbSenseCfg The configured sense buffer size. + * + * @returns VINF_SUCCESS + */ +static int virtioScsiR3ReqErr4(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uVirtqNbr, + PVIRTQBUF pVirtqBuf, size_t cbResidual, uint8_t bStatus, uint8_t bResponse, + uint8_t *pbSense, size_t cbSense, size_t cbSenseCfg) +{ + REQ_RESP_HDR_T RespHdr; + RespHdr.cbSenseLen = cbSense & UINT32_MAX; + RespHdr.uResidual = cbResidual & UINT32_MAX; + RespHdr.uStatusQualifier = 0; + RespHdr.uStatus = bStatus; + RespHdr.uResponse = bResponse; + + return virtioScsiR3ReqErr(pDevIns, pThis, uVirtqNbr, pVirtqBuf, &RespHdr, pbSense, cbSenseCfg); +} + +static void virtioScsiR3SenseKeyToVirtioResp(REQ_RESP_HDR_T *respHdr, uint8_t uSenseKey) +{ + switch (uSenseKey) + { + case SCSI_SENSE_ABORTED_COMMAND: + respHdr->uResponse = VIRTIOSCSI_S_ABORTED; + break; + case SCSI_SENSE_COPY_ABORTED: + respHdr->uResponse = VIRTIOSCSI_S_ABORTED; + break; + case SCSI_SENSE_UNIT_ATTENTION: + respHdr->uResponse = VIRTIOSCSI_S_TARGET_FAILURE; + break; + case SCSI_SENSE_HARDWARE_ERROR: + respHdr->uResponse = VIRTIOSCSI_S_TARGET_FAILURE; + break; + case SCSI_SENSE_NOT_READY: + /* Not sure what to return for this. See choices at VirtIO 1.0, 5.6.6.1.1 */ + respHdr->uResponse = VIRTIOSCSI_S_FAILURE; + /* respHdr->uResponse = VIRTIOSCSI_S_BUSY; */ /* BUSY is VirtIO's 'retryable' response */ + break; + default: + respHdr->uResponse = VIRTIOSCSI_S_FAILURE; + break; + } +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify} + */ +static DECLCALLBACK(int) virtioScsiR3IoReqFinish(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, int rcReq) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort); + PPDMDEVINS pDevIns = pTarget->pDevIns; + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + PPDMIMEDIAEX pIMediaEx = pTarget->pDrvMediaEx; + PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc; + + size_t cbResidual = 0; + int rc = pIMediaEx->pfnIoReqQueryResidual(pIMediaEx, hIoReq, &cbResidual); + AssertRC(rc); + + size_t cbXfer = 0; + rc = pIMediaEx->pfnIoReqQueryXferSize(pIMediaEx, hIoReq, &cbXfer); + AssertRC(rc); + + /* Masking deals with data type size discrepancies between + * The APIs (virtio and VBox). Windows C-compiler complains otherwise */ + Assert(!(cbXfer & 0xffffffff00000000)); + uint32_t cbXfer32 = cbXfer & 0xffffffff; + REQ_RESP_HDR_T respHdr = { 0 }; + respHdr.cbSenseLen = pReq->pbSense[2] == SCSI_SENSE_NONE ? 0 : (uint32_t)pReq->cbSenseLen; + AssertMsg(!(cbResidual & 0xffffffff00000000), + ("WARNING: Residual size larger than sizeof(uint32_t), truncating")); + respHdr.uResidual = (uint32_t)(cbResidual & 0xffffffff); + respHdr.uStatus = pReq->uStatus; + + /* VirtIO 1.0 spec 5.6.6.1.1 says device MUST return a VirtIO response byte value. + * Some are returned during the submit phase, and a few are not mapped at all, + * wherein anything that can't map specifically gets mapped to VIRTIOSCSI_S_FAILURE + */ + if (pThis->fResetting) + respHdr.uResponse = VIRTIOSCSI_S_RESET; + else + { + switch (rcReq) + { + case SCSI_STATUS_OK: + { + if (pReq->uStatus != SCSI_STATUS_CHECK_CONDITION) + respHdr.uResponse = VIRTIOSCSI_S_OK; + else + virtioScsiR3SenseKeyToVirtioResp(&respHdr, pReq->pbSense[2]); + break; + } + case SCSI_STATUS_CHECK_CONDITION: + virtioScsiR3SenseKeyToVirtioResp(&respHdr, pReq->pbSense[2]); + break; + + default: + respHdr.uResponse = VIRTIOSCSI_S_FAILURE; + break; + } + } + + Log2Func(("status: (%d) %s, response: (%d) %s\n", pReq->uStatus, SCSIStatusText(pReq->uStatus), + respHdr.uResponse, virtioGetReqRespText(respHdr.uResponse))); + + if (RT_FAILURE(rcReq)) + Log2Func(("rcReq: %Rrc\n", rcReq)); + + if (LogIs3Enabled()) + { + LogFunc(("cbDataIn = %u, cbDataOut = %u (cbIn = %u, cbOut = %u)\n", + pReq->cbDataIn, pReq->cbDataOut, pReq->pVirtqBuf->cbPhysReturn, pReq->pVirtqBuf->cbPhysSend)); + LogFunc(("xfer = %lu, residual = %u\n", cbXfer, cbResidual)); + LogFunc(("xfer direction: %s, sense written = %d, sense size = %d\n", + virtioGetTxDirText(pReq->enmTxDir), respHdr.cbSenseLen, pThis->virtioScsiConfig.uSenseSize)); + } + + if (respHdr.cbSenseLen && LogIs2Enabled()) + { + LogFunc(("Sense: %s\n", SCSISenseText(pReq->pbSense[2]))); + LogFunc(("Sense Ext3: %s\n", SCSISenseExtText(pReq->pbSense[12], pReq->pbSense[13]))); + } + + if ( (VIRTIO_IS_IN_DIRECTION(pReq->enmTxDir) && cbXfer32 > pReq->cbDataIn) + || (VIRTIO_IS_OUT_DIRECTION(pReq->enmTxDir) && cbXfer32 > pReq->cbDataOut)) + { + Log2Func((" * * * * Data overrun, returning sense\n")); + uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED, + 0, SCSI_SENSE_ILLEGAL_REQUEST, 0, 0, 0, 0, 10, 0, 0, 0 }; + respHdr.cbSenseLen = sizeof(abSense); + respHdr.uStatus = SCSI_STATUS_CHECK_CONDITION; + respHdr.uResponse = VIRTIOSCSI_S_OVERRUN; + respHdr.uResidual = pReq->cbDataIn & UINT32_MAX; + + virtioScsiR3ReqErr(pDevIns, pThis, pReq->uVirtqNbr, pReq->pVirtqBuf, &respHdr, abSense, + RT_MIN(pThis->virtioScsiConfig.uSenseSize, VIRTIOSCSI_SENSE_SIZE_MAX)); + } + else + { + Assert(pReq->pbSense != NULL); + + /* req datain bytes already in guest phys mem. via virtioScsiIoReqCopyFromBuf() */ + RTSGSEG aReqSegs[2]; + + aReqSegs[0].pvSeg = &respHdr; + aReqSegs[0].cbSeg = sizeof(respHdr); + + aReqSegs[1].pvSeg = pReq->pbSense; + aReqSegs[1].cbSeg = pReq->cbSenseAlloc; /* VirtIO 1.0 spec 5.6.4/5.6.6.1 */ + + RTSGBUF ReqSgBuf; + RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs)); + + size_t cbReqSgBuf = RTSgBufCalcTotalLength(&ReqSgBuf); + /** @todo r=bird: Returning here looks a little bogus... */ + AssertMsgReturn(cbReqSgBuf <= pReq->pVirtqBuf->cbPhysReturn, + ("Guest expected less req data (space needed: %zu, avail: %u)\n", + cbReqSgBuf, pReq->pVirtqBuf->cbPhysReturn), + VERR_BUFFER_OVERFLOW); + + virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, pReq->uVirtqNbr, &ReqSgBuf, pReq->pVirtqBuf, true /* fFence TBD */); + virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, pReq->uVirtqNbr); + + Log2(("-----------------------------------------------------------------------------------------\n")); + } + + virtioScsiR3FreeReq(pTarget, pReq); + virtioScsiR3Release(pDevIns, pThis, pThisCC); + return rc; +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf} + + * Copy virtual memory from VSCSI layer to guest physical memory + */ +static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf, size_t cbCopy) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort); + PPDMDEVINS pDevIns = pTarget->pDevIns; + PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc; + RT_NOREF(hIoReq, cbCopy); + + if (!pReq->cbDataIn) + return VINF_SUCCESS; + + AssertReturn(pReq->pVirtqBuf, VERR_INVALID_PARAMETER); + + PVIRTIOSGBUF pSgPhysReturn = pReq->pVirtqBuf->pSgPhysReturn; + virtioCoreGCPhysChainAdvance(pSgPhysReturn, offDst); + + size_t cbCopied = 0; + size_t cbRemain = pReq->cbDataIn; + + /* Skip past the REQ_RESP_HDR_T and sense code if we're at the start of the buffer. */ + if (!pSgPhysReturn->idxSeg && pSgPhysReturn->cbSegLeft == pSgPhysReturn->paSegs[0].cbSeg) + virtioCoreGCPhysChainAdvance(pSgPhysReturn, pReq->uDataInOff); + + while (cbRemain) + { + cbCopied = RT_MIN(pSgBuf->cbSegLeft, pSgPhysReturn->cbSegLeft); + Assert(cbCopied > 0); + PDMDevHlpPCIPhysWriteUser(pDevIns, pSgPhysReturn->GCPhysCur, pSgBuf->pvSegCur, cbCopied); + RTSgBufAdvance(pSgBuf, cbCopied); + virtioCoreGCPhysChainAdvance(pSgPhysReturn, cbCopied); + cbRemain -= cbCopied; + } + RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); /* needed? */ + + Log3Func((".... Copied %lu bytes from %lu byte guest buffer, residual=%lu\n", + cbCopy, pReq->pVirtqBuf->cbPhysReturn, pReq->pVirtqBuf->cbPhysReturn - cbCopy)); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf} + * + * Copy guest physical memory to VSCSI layer virtual memory + */ +static DECLCALLBACK(int) virtioScsiR3IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf, size_t cbCopy) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort); + PPDMDEVINS pDevIns = pTarget->pDevIns; + PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc; + RT_NOREF(hIoReq, cbCopy); + + if (!pReq->cbDataOut) + return VINF_SUCCESS; + + PVIRTIOSGBUF pSgPhysSend = pReq->pVirtqBuf->pSgPhysSend; + virtioCoreGCPhysChainAdvance(pSgPhysSend, offSrc); + + size_t cbCopied = 0; + size_t cbRemain = pReq->cbDataOut; + while (cbRemain) + { + cbCopied = RT_MIN(pSgBuf->cbSegLeft, pSgPhysSend->cbSegLeft); + Assert(cbCopied > 0); + PDMDevHlpPCIPhysReadUser(pDevIns, pSgPhysSend->GCPhysCur, pSgBuf->pvSegCur, cbCopied); + RTSgBufAdvance(pSgBuf, cbCopied); + virtioCoreGCPhysChainAdvance(pSgPhysSend, cbCopied); + cbRemain -= cbCopied; + } + + Log2Func((".... Copied %lu bytes to %lu byte guest buffer, residual=%lu\n", + cbCopy, pReq->pVirtqBuf->cbPhysReturn, pReq->pVirtqBuf->cbPhysReturn - cbCopy)); + + return VINF_SUCCESS; +} + +/** + * Handles request queues for/on a worker thread. + * + * @returns VBox status code (logged by caller). + */ +static int virtioScsiR3ReqSubmit(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC, + uint16_t uVirtqNbr, PVIRTQBUF pVirtqBuf) +{ + /* + * Validate configuration values we use here before we start. + */ + uint32_t const cbCdb = pThis->virtioScsiConfig.uCdbSize; + uint32_t const cbSenseCfg = pThis->virtioScsiConfig.uSenseSize; + /** @todo Report these as errors to the guest or does the caller do that? */ + ASSERT_GUEST_LOGREL_MSG_RETURN(cbCdb <= VIRTIOSCSI_CDB_SIZE_MAX, ("cbCdb=%#x\n", cbCdb), VERR_OUT_OF_RANGE); + ASSERT_GUEST_LOGREL_MSG_RETURN(cbSenseCfg <= VIRTIOSCSI_SENSE_SIZE_MAX, ("cbSenseCfg=%#x\n", cbSenseCfg), VERR_OUT_OF_RANGE); + + /* + * Extract command header and CDB from guest physical memory + * The max size is rather small here (19 + 255 = 274), so put + * it on the stack. + */ + size_t const cbReqHdr = sizeof(REQ_CMD_HDR_T) + cbCdb; + AssertReturn(pVirtqBuf && pVirtqBuf->cbPhysSend >= cbReqHdr, VERR_INVALID_PARAMETER); + + AssertCompile(VIRTIOSCSI_CDB_SIZE_MAX < 4096); + union + { + RT_GCC_EXTENSION struct + { + REQ_CMD_HDR_T ReqHdr; + uint8_t abCdb[VIRTIOSCSI_CDB_SIZE_MAX]; + } ; + uint8_t ab[sizeof(REQ_CMD_HDR_T) + VIRTIOSCSI_CDB_SIZE_MAX]; + uint64_t au64Align[(sizeof(REQ_CMD_HDR_T) + VIRTIOSCSI_CDB_SIZE_MAX) / sizeof(uint64_t)]; + } VirtqReq; + RT_ZERO(VirtqReq); + + for (size_t offReq = 0; offReq < cbReqHdr; ) + { + size_t cbSeg = cbReqHdr - offReq; + RTGCPHYS GCPhys = virtioCoreGCPhysChainGetNextSeg(pVirtqBuf->pSgPhysSend, &cbSeg); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhys, &VirtqReq.ab[offReq], cbSeg); + offReq += cbSeg; + } + + uint8_t const uType = VirtqReq.ReqHdr.abVirtioLun[0]; + uint8_t const uTarget = VirtqReq.ReqHdr.abVirtioLun[1]; + uint32_t uScsiLun = RT_MAKE_U16(VirtqReq.ReqHdr.abVirtioLun[3], VirtqReq.ReqHdr.abVirtioLun[2]) & 0x3fff; + + bool fBadLUNFormat = false; + if (uType == 0xc1 && uTarget == 0x01) + { + LogRel(("* * * WARNING: REPORT LUNS LU ACCESSED. FEATURE NOT IMPLEMENTED SEE DevVirtioScsi.cpp * * * ")); + /* Force rejection. */ /** @todo figure out right way to handle. Note this is a very + * vague and confusing part of the VirtIO spec (which deviates from the SCSI standard). + * I have not been able to determine how to implement this properly. I've checked the + * source code of Guest drivers, and so far none seem to use it. If this message is logged, + * meaning a guest expects this feature, implementing it can be re-visited */ + uScsiLun = 0xff; + } + else + if (uType != 1) + fBadLUNFormat = true; + + LogFunc(("[%s] (Target: %d LUN: %d) CDB: %.*Rhxs\n", + SCSICmdText(VirtqReq.abCdb[0]), uTarget, uScsiLun, + virtioScsiEstimateCdbLen(VirtqReq.abCdb[0], cbCdb), &VirtqReq.abCdb[0])); + + Log3Func(("cmd id: %RX64, attr: %x, prio: %d, crn: %x\n", + VirtqReq.ReqHdr.uId, VirtqReq.ReqHdr.uTaskAttr, VirtqReq.ReqHdr.uPrio, VirtqReq.ReqHdr.uCrn)); + + /* + * Calculate request offsets and data sizes. + */ + uint32_t const offDataOut = sizeof(REQ_CMD_HDR_T) + cbCdb; + uint32_t const offDataIn = sizeof(REQ_RESP_HDR_T) + cbSenseCfg; + size_t const cbDataOut = pVirtqBuf->cbPhysSend - offDataOut; + /** @todo r=bird: Validate cbPhysReturn properly? I've just RT_MAX'ed it for now. */ + size_t const cbDataIn = RT_MAX(pVirtqBuf->cbPhysReturn, offDataIn) - offDataIn; + Assert(offDataOut <= UINT16_MAX); + Assert(offDataIn <= UINT16_MAX); + + /* + * Handle submission errors + */ + if (RT_LIKELY(!fBadLUNFormat)) + { /* likely */ } + else + { + Log2Func(("Error submitting request, bad LUN format\n")); + return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, 0 /*bStatus*/, + VIRTIOSCSI_S_FAILURE, NULL /*pbSense*/, 0 /*cbSense*/, cbSenseCfg); + } + + PVIRTIOSCSITARGET const pTarget = &pThisCC->paTargetInstances[uTarget]; + if (RT_LIKELY( uTarget < pThis->cTargets + && pTarget->fPresent + && pTarget->pDrvMediaEx)) + { /* likely */ } + else + { + Log2Func(("Error submitting request to bad target (%d) or bad LUN (%d)\n", uTarget, uScsiLun)); + uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED, + 0, SCSI_SENSE_ILLEGAL_REQUEST, + 0, 0, 0, 0, 10, SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED, 0, 0 }; + return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_CHECK_CONDITION, + VIRTIOSCSI_S_BAD_TARGET, abSense, sizeof(abSense), cbSenseCfg); + } + if (RT_LIKELY(uScsiLun == 0)) + { /* likely */ } + else + { + Log2Func(("Error submitting request to bad target (%d) or bad LUN (%d)\n", uTarget, uScsiLun)); + uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED, + 0, SCSI_SENSE_ILLEGAL_REQUEST, + 0, 0, 0, 0, 10, SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED, 0, 0 }; + return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_CHECK_CONDITION, + VIRTIOSCSI_S_OK, abSense, sizeof(abSense), cbSenseCfg); + } + if (RT_LIKELY(!pThis->fResetting)) + { /* likely */ } + else + { + Log2Func(("Aborting req submission because reset is in progress\n")); + return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_OK, + VIRTIOSCSI_S_RESET, NULL /*pbSense*/, 0 /*cbSense*/, cbSenseCfg); + } + +#if 0 + if (RT_LIKELY(!cbDataIn || !cbDataOut || pThis->fHasInOutBufs)) /* VirtIO 1.0, 5.6.6.1.1 */ + { /* likely */ } + else + { + Log2Func(("Error submitting request, got datain & dataout bufs w/o INOUT feature negotated\n")); + uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED, + 0, SCSI_SENSE_ILLEGAL_REQUEST, 0, 0, 0, 0, 10, 0, 0, 0 }; + return virtioScsiR3ReqErr4(pDevIns, pThis, uVirtqNbr, pVirtqBuf, cbDataIn + cbDataOut, SCSI_STATUS_CHECK_CONDITION, + VIRTIOSCSI_S_FAILURE, abSense, sizeof(abSense), cbSenseCfg); + } +#endif + /* + * Have underlying driver allocate a req of size set during initialization of this device. + */ + virtioScsiR3Retain(pThis); + + PDMMEDIAEXIOREQ hIoReq = NULL; + PVIRTIOSCSIREQ pReq = NULL; + PPDMIMEDIAEX pIMediaEx = pTarget->pDrvMediaEx; + + int rc = pIMediaEx->pfnIoReqAlloc(pIMediaEx, &hIoReq, (void **)&pReq, 0 /* uIoReqId */, + PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR); + + if (RT_FAILURE(rc)) + { + virtioScsiR3Release(pDevIns, pThis, pThisCC); + return rc; + } + + pReq->hIoReq = hIoReq; + pReq->pTarget = pTarget; + pReq->uVirtqNbr = uVirtqNbr; + pReq->cbDataIn = cbDataIn; + pReq->cbDataOut = cbDataOut; + pReq->pVirtqBuf = pVirtqBuf; + virtioCoreR3VirtqBufRetain(pVirtqBuf); /* (For pReq->pVirtqBuf. Released by virtioScsiR3FreeReq.) */ + pReq->uDataInOff = offDataIn; + pReq->uDataOutOff = offDataOut; + + pReq->cbSenseAlloc = cbSenseCfg; + pReq->pbSense = (uint8_t *)RTMemAllocZ(pReq->cbSenseAlloc); + AssertMsgReturnStmt(pReq->pbSense, ("Out of memory allocating sense buffer"), + virtioScsiR3FreeReq(pTarget, pReq);, VERR_NO_MEMORY); + + /* Note: DrvSCSI allocates one virtual memory buffer for input and output phases of the request */ + rc = pIMediaEx->pfnIoReqSendScsiCmd(pIMediaEx, pReq->hIoReq, uScsiLun, + &VirtqReq.abCdb[0], cbCdb, + PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN, &pReq->enmTxDir, + RT_MAX(cbDataIn, cbDataOut), + pReq->pbSense, pReq->cbSenseAlloc, &pReq->cbSenseLen, + &pReq->uStatus, RT_MS_30SEC); + + if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS) + { + /* + * Getting here means the request failed in early in the submission to the lower level driver, + * and there will be no callback to the finished/completion function for this request + */ + Assert(RT_FAILURE_NP(rc)); + Log2Func(("Request-submission error from lower-level driver\n")); + uint8_t uASC, uASCQ = 0; + switch (rc) + { + case VERR_NO_MEMORY: + uASC = SCSI_ASC_SYSTEM_RESOURCE_FAILURE; + break; + default: + uASC = SCSI_ASC_INTERNAL_TARGET_FAILURE; + break; + } + uint8_t abSense[] = { RT_BIT(7) | SCSI_SENSE_RESPONSE_CODE_CURR_FIXED, + 0, SCSI_SENSE_VENDOR_SPECIFIC, + 0, 0, 0, 0, 10, uASC, uASCQ, 0 }; + REQ_RESP_HDR_T respHdr = { 0 }; + respHdr.cbSenseLen = sizeof(abSense); + respHdr.uStatus = SCSI_STATUS_CHECK_CONDITION; + respHdr.uResponse = VIRTIOSCSI_S_FAILURE; + respHdr.uResidual = (cbDataIn + cbDataOut) & UINT32_MAX; + virtioScsiR3ReqErr(pDevIns, pThis, uVirtqNbr, pVirtqBuf, &respHdr, abSense, cbSenseCfg); + virtioScsiR3FreeReq(pTarget, pReq); + virtioScsiR3Release(pDevIns, pThis, pThisCC); + } + return VINF_SUCCESS; +} + +/** + * Handles control transfers for/on a worker thread. + * + * @returns VBox status code (ignored by the caller). + * @param pDevIns The device instance. + * @param pThis VirtIO SCSI shared instance data. + * @param pThisCC VirtIO SCSI ring-3 instance data. + * @param uVirtqNbr CONTROLQ_IDX + * @param pVirtqBuf Descriptor chain to process. + */ +static int virtioScsiR3Ctrl(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC, + uint16_t uVirtqNbr, PVIRTQBUF pVirtqBuf) +{ + AssertReturn(pVirtqBuf->cbPhysSend >= RT_MIN(sizeof(VIRTIOSCSI_CTRL_AN_T), + sizeof(VIRTIOSCSI_CTRL_TMF_T)), 0); + + /* + * Allocate buffer and read in the control command + */ + VIRTIO_SCSI_CTRL_UNION_T ScsiCtrlUnion; + RT_ZERO(ScsiCtrlUnion); + + size_t const cb = RT_MIN(pVirtqBuf->cbPhysSend, sizeof(VIRTIO_SCSI_CTRL_UNION_T)); + for (size_t uOffset = 0; uOffset < cb; ) + { + size_t cbSeg = cb - uOffset; + RTGCPHYS GCPhys = virtioCoreGCPhysChainGetNextSeg(pVirtqBuf->pSgPhysSend, &cbSeg); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhys, &ScsiCtrlUnion.ab[uOffset], cbSeg); + uOffset += cbSeg; + } + + AssertReturn( (ScsiCtrlUnion.Type.uType == VIRTIOSCSI_T_TMF + && pVirtqBuf->cbPhysSend >= sizeof(VIRTIOSCSI_CTRL_TMF_T)) + || ( ( ScsiCtrlUnion.Type.uType == VIRTIOSCSI_T_AN_QUERY + || ScsiCtrlUnion.Type.uType == VIRTIOSCSI_T_AN_SUBSCRIBE) + && pVirtqBuf->cbPhysSend >= sizeof(VIRTIOSCSI_CTRL_AN_T)), + 0 /** @todo r=bird: what kind of status is '0' here? */); + + union + { + uint32_t fSupportedEvents; + } uData; + uint8_t bResponse = VIRTIOSCSI_S_OK; + uint8_t cSegs; + RTSGSEG aReqSegs[2]; + switch (ScsiCtrlUnion.Type.uType) + { + case VIRTIOSCSI_T_TMF: /* Task Management Functions */ + { + uint8_t uTarget = ScsiCtrlUnion.Tmf.abScsiLun[1]; + uint32_t uScsiLun = RT_MAKE_U16(ScsiCtrlUnion.Tmf.abScsiLun[3], ScsiCtrlUnion.Tmf.abScsiLun[2]) & 0x3fff; + Log2Func(("[%s] (Target: %d LUN: %d) Task Mgt Function: %s\n", + VIRTQNAME(uVirtqNbr), uTarget, uScsiLun, virtioGetTMFTypeText(ScsiCtrlUnion.Tmf.uSubtype))); + + if (uTarget >= pThis->cTargets || !pThisCC->paTargetInstances[uTarget].fPresent) + bResponse = VIRTIOSCSI_S_BAD_TARGET; + else + if (uScsiLun != 0) + bResponse = VIRTIOSCSI_S_INCORRECT_LUN; + else + switch (ScsiCtrlUnion.Tmf.uSubtype) + { + case VIRTIOSCSI_T_TMF_ABORT_TASK: + bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; + break; + case VIRTIOSCSI_T_TMF_ABORT_TASK_SET: + bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; + break; + case VIRTIOSCSI_T_TMF_CLEAR_ACA: + bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; + break; + case VIRTIOSCSI_T_TMF_CLEAR_TASK_SET: + bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; + break; + case VIRTIOSCSI_T_TMF_I_T_NEXUS_RESET: + bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; + break; + case VIRTIOSCSI_T_TMF_LOGICAL_UNIT_RESET: + bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; + break; + case VIRTIOSCSI_T_TMF_QUERY_TASK: + bResponse = VIRTIOSCSI_S_FUNCTION_REJECTED; + break; + case VIRTIOSCSI_T_TMF_QUERY_TASK_SET: + bResponse = VIRTIOSCSI_S_FUNCTION_REJECTED; + break; + default: + LogFunc(("Unknown TMF type\n")); + bResponse = VIRTIOSCSI_S_FAILURE; + } + cSegs = 0; /* only bResponse */ + break; + } + case VIRTIOSCSI_T_AN_QUERY: /* Guest SCSI driver is querying supported async event notifications */ + { + uint8_t uTarget = ScsiCtrlUnion.AsyncNotify.abScsiLun[1]; + uint32_t uScsiLun = RT_MAKE_U16(ScsiCtrlUnion.AsyncNotify.abScsiLun[3], + ScsiCtrlUnion.AsyncNotify.abScsiLun[2]) & 0x3fff; + + if (uTarget >= pThis->cTargets || !pThisCC->paTargetInstances[uTarget].fPresent) + bResponse = VIRTIOSCSI_S_BAD_TARGET; + else + if (uScsiLun != 0) + bResponse = VIRTIOSCSI_S_INCORRECT_LUN; + else + bResponse = VIRTIOSCSI_S_FUNCTION_COMPLETE; + +#ifdef LOG_ENABLED + if (LogIs2Enabled()) + { + char szTypeText[128]; + virtioGetControlAsyncMaskText(szTypeText, sizeof(szTypeText), ScsiCtrlUnion.AsyncNotify.fEventsRequested); + Log2Func(("[%s] (Target: %d LUN: %d) Async. Notification Query: %s\n", + VIRTQNAME(uVirtqNbr), uTarget, uScsiLun, szTypeText)); + } +#endif + uData.fSupportedEvents = SUPPORTED_EVENTS; + aReqSegs[0].pvSeg = &uData.fSupportedEvents; + aReqSegs[0].cbSeg = sizeof(uData.fSupportedEvents); + cSegs = 1; + break; + } + case VIRTIOSCSI_T_AN_SUBSCRIBE: /* Guest SCSI driver is subscribing to async event notification(s) */ + { + if (ScsiCtrlUnion.AsyncNotify.fEventsRequested & ~SUBSCRIBABLE_EVENTS) + LogFunc(("Unsupported bits in event subscription event mask: %#x\n", + ScsiCtrlUnion.AsyncNotify.fEventsRequested)); + + uint8_t uTarget = ScsiCtrlUnion.AsyncNotify.abScsiLun[1]; + uint32_t uScsiLun = RT_MAKE_U16(ScsiCtrlUnion.AsyncNotify.abScsiLun[3], + ScsiCtrlUnion.AsyncNotify.abScsiLun[2]) & 0x3fff; + +#ifdef LOG_ENABLED + if (LogIs2Enabled()) + { + char szTypeText[128]; + virtioGetControlAsyncMaskText(szTypeText, sizeof(szTypeText), ScsiCtrlUnion.AsyncNotify.fEventsRequested); + Log2Func(("[%s] (Target: %d LUN: %d) Async. Notification Subscribe: %s\n", + VIRTQNAME(uVirtqNbr), uTarget, uScsiLun, szTypeText)); + } +#endif + if (uTarget >= pThis->cTargets || !pThisCC->paTargetInstances[uTarget].fPresent) + bResponse = VIRTIOSCSI_S_BAD_TARGET; + else + if (uScsiLun != 0) + bResponse = VIRTIOSCSI_S_INCORRECT_LUN; + else + { + bResponse = VIRTIOSCSI_S_FUNCTION_SUCCEEDED; /* or VIRTIOSCSI_S_FUNCTION_COMPLETE? */ + pThis->fAsyncEvtsEnabled = SUPPORTED_EVENTS & ScsiCtrlUnion.AsyncNotify.fEventsRequested; + } + + aReqSegs[0].pvSeg = &pThis->fAsyncEvtsEnabled; + aReqSegs[0].cbSeg = sizeof(pThis->fAsyncEvtsEnabled); + cSegs = 1; + break; + } + default: + { + LogFunc(("Unknown control type extracted from %s: %u\n", VIRTQNAME(uVirtqNbr), ScsiCtrlUnion.Type.uType)); + + bResponse = VIRTIOSCSI_S_FAILURE; + cSegs = 0; /* only bResponse */ + break; + } + } + + /* Add the response code: */ + aReqSegs[cSegs].pvSeg = &bResponse; + aReqSegs[cSegs].cbSeg = sizeof(bResponse); + cSegs++; + Assert(cSegs <= RT_ELEMENTS(aReqSegs)); + + LogFunc(("Response code: %s\n", virtioGetReqRespText(bResponse))); + + RTSGBUF ReqSgBuf; + RTSgBufInit(&ReqSgBuf, aReqSegs, cSegs); + + virtioCoreR3VirtqUsedBufPut(pDevIns, &pThis->Virtio, uVirtqNbr, &ReqSgBuf, pVirtqBuf, true /*fFence*/); + virtioCoreVirtqUsedRingSync(pDevIns, &pThis->Virtio, uVirtqNbr); + + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNPDMTHREADWAKEUPDEV} + */ +static DECLCALLBACK(int) virtioScsiR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[(uintptr_t)pThread->pvUser].hEvtProcess); +} + +/** + * @callback_method_impl{FNPDMTHREADDEV} + */ +static DECLCALLBACK(int) virtioScsiR3WorkerThread(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + uint16_t const uVirtqNbr = (uint16_t)(uintptr_t)pThread->pvUser; + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[uVirtqNbr]; + PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr]; + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + Log6Func(("[Re]starting %s worker\n", VIRTQNAME(uVirtqNbr))); + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + if ( !pWorkerR3->cRedoDescs + && IS_VIRTQ_EMPTY(pDevIns, &pThis->Virtio, uVirtqNbr)) + { + /* Atomic interlocks avoid missing alarm while going to sleep & notifier waking the awoken */ + ASMAtomicWriteBool(&pWorker->fSleeping, true); + bool fNotificationSent = ASMAtomicXchgBool(&pWorker->fNotified, false); + if (!fNotificationSent) + { + Log6Func(("%s worker sleeping...\n", VIRTQNAME(uVirtqNbr))); + Assert(ASMAtomicReadBool(&pWorker->fSleeping)); + int rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pWorker->hEvtProcess, RT_INDEFINITE_WAIT); + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + { + Log6Func(("%s worker thread not running, exiting\n", VIRTQNAME(uVirtqNbr))); + return VINF_SUCCESS; + } + if (rc == VERR_INTERRUPTED) + { + Log6Func(("%s worker interrupted ... continuing\n", VIRTQNAME(uVirtqNbr))); + continue; + } + Log6Func(("%s worker woken\n", VIRTQNAME(uVirtqNbr))); + ASMAtomicWriteBool(&pWorker->fNotified, false); + } + ASMAtomicWriteBool(&pWorker->fSleeping, false); + } + if (!virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr)) + { + LogFunc(("%s queue not enabled, worker aborting...\n", VIRTQNAME(uVirtqNbr))); + break; + } + + if (!pThis->afVirtqAttached[uVirtqNbr]) + { + LogFunc(("%s queue not attached, worker aborting...\n", VIRTQNAME(uVirtqNbr))); + break; + } + if (!pThisCC->fQuiescing) + { + /* Process any reqs that were suspended saved to the redo queue in save exec. */ + for (int i = 0; i < pWorkerR3->cRedoDescs; i++) + { +#ifdef VIRTIO_VBUF_ON_STACK + PVIRTQBUF pVirtqBuf = virtioCoreR3VirtqBufAlloc(); + if (!pVirtqBuf) + { + LogRel(("Failed to allocate memory for VIRTQBUF\n")); + break; /* No point in trying to allocate memory for other descriptor chains */ + } + int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr, + pWorkerR3->auRedoDescs[i], pVirtqBuf); +#else /* !VIRTIO_VBUF_ON_STACK */ + PVIRTQBUF pVirtqBuf; + int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr, + pWorkerR3->auRedoDescs[i], &pVirtqBuf); +#endif /* !VIRTIO_VBUF_ON_STACK */ + if (RT_FAILURE(rc)) + LogRel(("Error fetching desc chain to redo, %Rrc", rc)); + + rc = virtioScsiR3ReqSubmit(pDevIns, pThis, pThisCC, uVirtqNbr, pVirtqBuf); + if (RT_FAILURE(rc)) + LogRel(("Error submitting req packet, resetting %Rrc", rc)); + + virtioCoreR3VirtqBufRelease(&pThis->Virtio, pVirtqBuf); + } + pWorkerR3->cRedoDescs = 0; + + Log6Func(("fetching next descriptor chain from %s\n", VIRTQNAME(uVirtqNbr))); +#ifdef VIRTIO_VBUF_ON_STACK + PVIRTQBUF pVirtqBuf = virtioCoreR3VirtqBufAlloc(); + if (!pVirtqBuf) + LogRel(("Failed to allocate memory for VIRTQBUF\n")); + else + { + int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr, pVirtqBuf, true); +#else /* !VIRTIO_VBUF_ON_STACK */ + PVIRTQBUF pVirtqBuf = NULL; + int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, &pThis->Virtio, uVirtqNbr, &pVirtqBuf, true); +#endif /* !VIRTIO_VBUF_ON_STACK */ + if (rc == VERR_NOT_AVAILABLE) + { + Log6Func(("Nothing found in %s\n", VIRTQNAME(uVirtqNbr))); + continue; + } + + AssertRC(rc); + if (uVirtqNbr == CONTROLQ_IDX) + virtioScsiR3Ctrl(pDevIns, pThis, pThisCC, uVirtqNbr, pVirtqBuf); + else /* request queue index */ + { + rc = virtioScsiR3ReqSubmit(pDevIns, pThis, pThisCC, uVirtqNbr, pVirtqBuf); + if (RT_FAILURE(rc)) + LogRel(("Error submitting req packet, resetting %Rrc", rc)); + } + + virtioCoreR3VirtqBufRelease(&pThis->Virtio, pVirtqBuf); +#ifdef VIRTIO_VBUF_ON_STACK + } +#endif /* VIRTIO_VBUF_ON_STACK */ + } + } + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Sending evnets +*********************************************************************************************************************************/ + +/* + * @todo Figure out how to implement this with R0 changes. Not used by current linux driver + */ + +#if 0 +DECLINLINE(void) virtioScsiR3ReportEventsMissed(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget) +{ + virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_NO_EVENT | VIRTIOSCSI_T_EVENTS_MISSED, 0); +} +#endif + +#if 0 +/* SUBSCRIBABLE EVENT - not sure when to call this or how to detect when media is added or removed + * via the VBox GUI */ +DECLINLINE(void) virtioScsiR3ReportMediaChange(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget) +{ + if (pThis->fAsyncEvtsEnabled & VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE) + virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_ASYNC_NOTIFY, VIRTIOSCSI_EVT_ASYNC_MEDIA_CHANGE); +} + +/* ESSENTIAL (NON-SUBSCRIBABLE) EVENT TYPES (most guest virtio-scsi drivers ignore?) */ + +DECLINLINE(void) virtioScsiR3ReportTransportReset(PDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget) +{ + virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_TRANSPORT_RESET, VIRTIOSCSI_EVT_RESET_HARD); +} + +DECLINLINE(void) virtioScsiR3ReportParamChange(PDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget, + uint32_t uSenseCode, uint32_t uSenseQualifier) +{ + uint32_t uReason = uSenseQualifier << 8 | uSenseCode; + virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_PARAM_CHANGE, uReason); + +} + +DECLINLINE(void) virtioScsiR3ReportTargetRemoved(PDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget) +{ + if (pThis->fHasHotplug) + virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_TRANSPORT_RESET, VIRTIOSCSI_EVT_RESET_REMOVED); +} + +DECLINLINE(void) virtioScsiR3ReportTargetAdded(PDMDEVINS pDevInsPVIRTIOSCSI pThis, uint16_t uTarget) +{ + if (pThis->fHasHotplug) + virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_TRANSPORT_RESET, VIRTIOSCSI_EVT_RESET_RESCAN); +} + +#endif + +/** + * @callback_method_impl{VIRTIOCORER3,pfnStatusChanged} + */ +static DECLCALLBACK(void) virtioScsiR3StatusChanged(PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC, uint32_t fVirtioReady) +{ + PVIRTIOSCSI pThis = RT_FROM_MEMBER(pVirtio, VIRTIOSCSI, Virtio); + PVIRTIOSCSICC pThisCC = RT_FROM_MEMBER(pVirtioCC, VIRTIOSCSICC, Virtio); + + pThis->fVirtioReady = fVirtioReady; + + if (fVirtioReady) + { + LogFunc(("VirtIO ready\n-----------------------------------------------------------------------------------------\n")); + uint64_t fFeatures = virtioCoreGetNegotiatedFeatures(&pThis->Virtio); + pThis->fHasT10pi = fFeatures & VIRTIO_SCSI_F_T10_PI; + pThis->fHasHotplug = fFeatures & VIRTIO_SCSI_F_HOTPLUG; + pThis->fHasInOutBufs = fFeatures & VIRTIO_SCSI_F_INOUT; + pThis->fHasLunChange = fFeatures & VIRTIO_SCSI_F_CHANGE; + pThis->fResetting = false; + pThisCC->fQuiescing = false; + + for (unsigned i = 0; i < VIRTIOSCSI_VIRTQ_CNT; i++) + pThis->afVirtqAttached[i] = true; + } + else + { + LogFunc(("VirtIO is resetting\n")); + for (unsigned i = 0; i < VIRTIOSCSI_VIRTQ_CNT; i++) + pThis->afVirtqAttached[i] = false; + + /* + * BIOS may change these values. When the OS comes up, and KVM driver accessed + * through Windows, it assumes they are the default size. So as per the VirtIO 1.0 spec, + * 5.6.4, these device configuration values must be set to default upon device reset. + */ + pThis->virtioScsiConfig.uSenseSize = VIRTIOSCSI_SENSE_SIZE_DEFAULT; + pThis->virtioScsiConfig.uCdbSize = VIRTIOSCSI_CDB_SIZE_DEFAULT; + } + + +} + + +/********************************************************************************************************************************* +* LEDs * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMILEDPORTS,pfnQueryStatusLed, Target level.} + */ +static DECLCALLBACK(int) virtioScsiR3TargetQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, ILed); + if (iLUN == 0) + { + *ppLed = &pTarget->led; + Assert((*ppLed)->u32Magic == PDMLED_MAGIC); + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} +/** + * @interface_method_impl{PDMILEDPORTS,pfnQueryStatusLed, Device level.} + */ +static DECLCALLBACK(int) virtioScsiR3DeviceQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + PVIRTIOSCSICC pThisCC = RT_FROM_MEMBER(pInterface, VIRTIOSCSICC, ILeds); + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PVIRTIOSCSI); + if (iLUN < pThis->cTargets) + { + *ppLed = &pThisCC->paTargetInstances[iLUN].led; + Assert((*ppLed)->u32Magic == PDMLED_MAGIC); + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/********************************************************************************************************************************* +* PDMIMEDIAPORT (target) * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation, Target level.} + */ +static DECLCALLBACK(int) virtioScsiR3QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController, + uint32_t *piInstance, uint32_t *piLUN) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaPort); + PPDMDEVINS pDevIns = pTarget->pDevIns; + + AssertPtrReturn(ppcszController, VERR_INVALID_POINTER); + AssertPtrReturn(piInstance, VERR_INVALID_POINTER); + AssertPtrReturn(piLUN, VERR_INVALID_POINTER); + + *ppcszController = pDevIns->pReg->szName; + *piInstance = pDevIns->iInstance; + *piLUN = pTarget->uTarget; + + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Virtio config. * +*********************************************************************************************************************************/ + +/** + * Worker for virtioScsiR3DevCapWrite and virtioScsiR3DevCapRead. + */ +static int virtioScsiR3CfgAccessed(PVIRTIOSCSI pThis, uint32_t uOffsetOfAccess, void *pv, uint32_t cb, bool fWrite) +{ + AssertReturn(pv && cb <= sizeof(uint32_t), fWrite ? VINF_SUCCESS : VINF_IOM_MMIO_UNUSED_00); + + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uNumVirtqs, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uNumVirtqs, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uSegMax, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uSegMax, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxSectors, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxSectors, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uCmdPerLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uCmdPerLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uEventInfoSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uEventInfoSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uSenseSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS( uSenseSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uCdbSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS( uCdbSize, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxChannel, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxChannel, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxTarget, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxTarget, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + if (VIRTIO_DEV_CONFIG_MATCH_MEMBER( uMaxLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess)) + VIRTIO_DEV_CONFIG_ACCESS_READONLY( uMaxLun, VIRTIOSCSI_CONFIG_T, uOffsetOfAccess, &pThis->virtioScsiConfig); + else + { + LogFunc(("Bad access by guest to virtio_scsi_config: off=%u (%#x), cb=%u\n", uOffsetOfAccess, uOffsetOfAccess, cb)); + return fWrite ? VINF_SUCCESS : VINF_IOM_MMIO_UNUSED_00; + } + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{VIRTIOCORER3,pfnDevCapRead} + */ +static DECLCALLBACK(int) virtioScsiR3DevCapRead(PPDMDEVINS pDevIns, uint32_t uOffset, void *pv, uint32_t cb) +{ + return virtioScsiR3CfgAccessed(PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI), uOffset, pv, cb, false /*fRead*/); +} + +/** + * @callback_method_impl{VIRTIOCORER3,pfnDevCapWrite} + */ +static DECLCALLBACK(int) virtioScsiR3DevCapWrite(PPDMDEVINS pDevIns, uint32_t uOffset, const void *pv, uint32_t cb) +{ + return virtioScsiR3CfgAccessed(PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI), uOffset, (void *)pv, cb, true /*fWrite*/); +} + + +/********************************************************************************************************************************* +* IBase for device and targets * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface, Target level.} + */ +static DECLCALLBACK(void *) virtioScsiR3TargetQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pTarget->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pTarget->IMediaPort); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pTarget->IMediaExPort); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pTarget->ILed); + return NULL; +} + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface, Device level.} + */ +static DECLCALLBACK(void *) virtioScsiR3DeviceQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PVIRTIOSCSICC pThisCC = RT_FROM_MEMBER(pInterface, VIRTIOSCSICC, IBase); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds); + + return NULL; +} + + +/********************************************************************************************************************************* +* Misc * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, virtio-scsi debugger info callback.} + */ +static DECLCALLBACK(void) virtioScsiR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + + /* Parse arguments. */ + RT_NOREF(pszArgs); //bool fVerbose = pszArgs && strstr(pszArgs, "verbose") != NULL; + + /* Show basic information. */ + pHlp->pfnPrintf(pHlp, "%s#%d: virtio-scsci ", + pDevIns->pReg->szName, + pDevIns->iInstance); + pHlp->pfnPrintf(pHlp, "numTargets=%lu", pThis->cTargets); +} + + +/********************************************************************************************************************************* +* Saved state * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) virtioScsiR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + LogFunc(("LOAD EXEC!!\n")); + + AssertReturn(uPass == SSM_PASS_FINAL, VERR_SSM_UNEXPECTED_PASS); + AssertLogRelMsgReturn(uVersion == VIRTIOSCSI_SAVED_STATE_VERSION, + ("uVersion=%u\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + + virtioScsiSetVirtqNames(pThis); + for (int uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) + pHlp->pfnSSMGetBool(pSSM, &pThis->afVirtqAttached[uVirtqNbr]); + + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uNumVirtqs); + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uSegMax); + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uMaxSectors); + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uCmdPerLun); + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uEventInfoSize); + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uSenseSize); + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uCdbSize); + pHlp->pfnSSMGetU16(pSSM, &pThis->virtioScsiConfig.uMaxChannel); + pHlp->pfnSSMGetU16(pSSM, &pThis->virtioScsiConfig.uMaxTarget); + pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uMaxLun); + pHlp->pfnSSMGetU32(pSSM, &pThis->fAsyncEvtsEnabled); + pHlp->pfnSSMGetBool(pSSM, &pThis->fEventsMissed); + pHlp->pfnSSMGetU32(pSSM, &pThis->fVirtioReady); + pHlp->pfnSSMGetU32(pSSM, &pThis->fHasT10pi); + pHlp->pfnSSMGetU32(pSSM, &pThis->fHasHotplug); + pHlp->pfnSSMGetU32(pSSM, &pThis->fHasInOutBufs); + pHlp->pfnSSMGetU32(pSSM, &pThis->fHasLunChange); + pHlp->pfnSSMGetU32(pSSM, &pThis->fResetting); + + uint32_t cTargets; + int rc = pHlp->pfnSSMGetU32(pSSM, &cTargets); + AssertRCReturn(rc, rc); + AssertReturn(cTargets == pThis->cTargets, + pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_LOAD_CONFIG_MISMATCH, RT_SRC_POS, + N_("target count has changed: %u saved, %u configured now"), + cTargets, pThis->cTargets)); + + for (uint16_t uTarget = 0; uTarget < pThis->cTargets; uTarget++) + { + uint16_t cReqsRedo; + rc = pHlp->pfnSSMGetU16(pSSM, &cReqsRedo); + AssertRCReturn(rc, rc); + AssertReturn(cReqsRedo < VIRTQ_SIZE, + pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + N_("Bad count of I/O transactions to re-do in saved state (%#x, max %#x - 1)"), + cReqsRedo, VIRTQ_SIZE)); + + for (uint16_t uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) + { + PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr]; + pWorkerR3->cRedoDescs = 0; + } + + for (int i = 0; i < cReqsRedo; i++) + { + uint16_t uVirtqNbr; + rc = pHlp->pfnSSMGetU16(pSSM, &uVirtqNbr); + AssertRCReturn(rc, rc); + AssertReturn(uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT, + pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + N_("Bad queue index for re-do in saved state (%#x, max %#x)"), + uVirtqNbr, VIRTIOSCSI_VIRTQ_CNT - 1)); + + uint16_t idxHead; + rc = pHlp->pfnSSMGetU16(pSSM, &idxHead); + AssertRCReturn(rc, rc); + AssertReturn(idxHead < VIRTQ_SIZE, + pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS, + N_("Bad queue element index for re-do in saved state (%#x, max %#x)"), + idxHead, VIRTQ_SIZE - 1)); + + PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[uVirtqNbr]; + pWorkerR3->auRedoDescs[pWorkerR3->cRedoDescs++] = idxHead; + pWorkerR3->cRedoDescs %= VIRTQ_SIZE; + } + } + + /* + * Call the virtio core to let it load its state. + */ + rc = virtioCoreR3ModernDeviceLoadExec(&pThis->Virtio, pDevIns->pHlpR3, pSSM, + uVersion, VIRTIOSCSI_SAVED_STATE_VERSION, pThis->virtioScsiConfig.uNumVirtqs); + + /* + * Nudge request queue workers + */ + for (int uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) + { + if (pThis->afVirtqAttached[uVirtqNbr]) + { + LogFunc(("Waking %s worker.\n", VIRTQNAME(uVirtqNbr))); + int rc2 = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[uVirtqNbr].hEvtProcess); + AssertRCReturn(rc, rc2); + } + } + + return rc; +} + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) virtioScsiR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + LogFunc(("SAVE EXEC!!\n")); + + for (int uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) + pHlp->pfnSSMPutBool(pSSM, pThis->afVirtqAttached[uVirtqNbr]); + + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uNumVirtqs); + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uSegMax); + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uMaxSectors); + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uCmdPerLun); + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uEventInfoSize); + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uSenseSize); + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uCdbSize); + pHlp->pfnSSMPutU16(pSSM, pThis->virtioScsiConfig.uMaxChannel); + pHlp->pfnSSMPutU16(pSSM, pThis->virtioScsiConfig.uMaxTarget); + pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uMaxLun); + pHlp->pfnSSMPutU32(pSSM, pThis->fAsyncEvtsEnabled); + pHlp->pfnSSMPutBool(pSSM, pThis->fEventsMissed); + pHlp->pfnSSMPutU32(pSSM, pThis->fVirtioReady); + pHlp->pfnSSMPutU32(pSSM, pThis->fHasT10pi); + pHlp->pfnSSMPutU32(pSSM, pThis->fHasHotplug); + pHlp->pfnSSMPutU32(pSSM, pThis->fHasInOutBufs); + pHlp->pfnSSMPutU32(pSSM, pThis->fHasLunChange); + pHlp->pfnSSMPutU32(pSSM, pThis->fResetting); + + AssertMsg(!pThis->cActiveReqs, ("There are still outstanding requests on this device\n")); + + pHlp->pfnSSMPutU32(pSSM, pThis->cTargets); + + for (uint16_t uTarget = 0; uTarget < pThis->cTargets; uTarget++) + { + PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget]; + + /* Query all suspended requests and store them in the request queue. */ + if (pTarget->pDrvMediaEx) + { + uint32_t cReqsRedo = pTarget->pDrvMediaEx->pfnIoReqGetSuspendedCount(pTarget->pDrvMediaEx); + + pHlp->pfnSSMPutU16(pSSM, cReqsRedo); + + if (cReqsRedo) + { + PDMMEDIAEXIOREQ hIoReq; + PVIRTIOSCSIREQ pReq; + + int rc = pTarget->pDrvMediaEx->pfnIoReqQuerySuspendedStart(pTarget->pDrvMediaEx, &hIoReq, + (void **)&pReq); + AssertRCBreak(rc); + + while(--cReqsRedo) + { + pHlp->pfnSSMPutU16(pSSM, pReq->uVirtqNbr); + pHlp->pfnSSMPutU16(pSSM, pReq->pVirtqBuf->uHeadIdx); + + rc = pTarget->pDrvMediaEx->pfnIoReqQuerySuspendedNext(pTarget->pDrvMediaEx, hIoReq, + &hIoReq, (void **)&pReq); + AssertRCBreak(rc); + } + } + } + } + + /* + * Call the virtio core to let it save its state. + */ + return virtioCoreR3SaveExec(&pThis->Virtio, pDevIns->pHlpR3, pSSM, VIRTIOSCSI_SAVED_STATE_VERSION, VIRTIOSCSI_VIRTQ_CNT); +} + + +/********************************************************************************************************************************* +* Device interface. * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMDEVREGR3,pfnDetach} + * + * One harddisk at one port has been unplugged. + * The VM is suspended at this point. + */ +static DECLCALLBACK(void) virtioScsiR3Detach(PPDMDEVINS pDevIns, unsigned uTarget, uint32_t fFlags) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + AssertReturnVoid(uTarget < pThis->cTargets); + PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget]; + + LogFunc(("")); + + AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG, + ("virtio-scsi: Device does not support hotplugging\n")); + RT_NOREF(fFlags); + + /* + * Zero all important members. + */ + pTarget->fPresent = false; + pTarget->pDrvBase = NULL; + pTarget->pDrvMedia = NULL; + pTarget->pDrvMediaEx = NULL; +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnAttach} + * + * This is called when we change block driver. + */ +static DECLCALLBACK(int) virtioScsiR3Attach(PPDMDEVINS pDevIns, unsigned uTarget, uint32_t fFlags) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + AssertReturn(uTarget < pThis->cTargets, VERR_PDM_LUN_NOT_FOUND); + PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget]; + + Assert(pTarget->pDevIns == pDevIns); + AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG, + ("virtio-scsi: Device does not support hotplugging\n"), + VERR_INVALID_PARAMETER); + + AssertRelease(!pTarget->pDrvBase); + Assert(pTarget->uTarget == uTarget); + + /* + * Try attach the SCSI driver and get the interfaces, required as well as optional. + */ + int rc = PDMDevHlpDriverAttach(pDevIns, pTarget->uTarget, &pDevIns->IBase, &pTarget->pDrvBase, pTarget->pszTargetName); + if (RT_SUCCESS(rc)) + { + pTarget->fPresent = true; + pTarget->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIA); + AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMedia), + ("virtio-scsi configuration error: LUN#%d missing basic media interface!\n", uTarget), + VERR_PDM_MISSING_INTERFACE); + + /* Get the extended media interface. */ + pTarget->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIAEX); + AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx), + ("virtio-scsi configuration error: LUN#%d missing extended media interface!\n", uTarget), + VERR_PDM_MISSING_INTERFACE); + + rc = pTarget->pDrvMediaEx->pfnIoReqAllocSizeSet(pTarget->pDrvMediaEx, sizeof(VIRTIOSCSIREQ)); + AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx), + ("virtio-scsi configuration error: LUN#%u: Failed to set I/O request size!\n", uTarget), + rc); + } + else + AssertMsgFailed(("Failed to attach %s. rc=%Rrc\n", pTarget->pszTargetName, rc)); + + if (RT_FAILURE(rc)) + { + pTarget->fPresent = false; + pTarget->pDrvBase = NULL; + pTarget->pDrvMedia = NULL; + pTarget->pDrvMediaEx = NULL; + pThisCC->pMediaNotify = NULL; + } + return rc; +} + +/** + * @callback_method_impl{FNPDMDEVASYNCNOTIFY} + */ +static DECLCALLBACK(bool) virtioScsiR3DeviceQuiesced(PPDMDEVINS pDevIns) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + + if (ASMAtomicReadU32(&pThis->cActiveReqs)) + return false; + + LogFunc(("Device I/O activity quiesced: %s\n", + virtioCoreGetStateChangeText(pThisCC->enmQuiescingFor))); + + virtioCoreR3VmStateChanged(&pThis->Virtio, pThisCC->enmQuiescingFor); + + pThis->fResetting = false; + pThisCC->fQuiescing = false; + + return true; +} + +/** + * Worker for virtioScsiR3Reset() and virtioScsiR3SuspendOrPowerOff(). + */ +static void virtioScsiR3QuiesceDevice(PPDMDEVINS pDevIns, VIRTIOVMSTATECHANGED enmQuiscingFor) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + + /* Prevent worker threads from removing/processing elements from virtq's */ + pThisCC->fQuiescing = true; + pThisCC->enmQuiescingFor = enmQuiscingFor; + + PDMDevHlpSetAsyncNotification(pDevIns, virtioScsiR3DeviceQuiesced); + + /* If already quiesced invoke async callback. */ + if (!ASMAtomicReadU32(&pThis->cActiveReqs)) + PDMDevHlpAsyncNotificationCompleted(pDevIns); +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnReset} + */ +static DECLCALLBACK(void) virtioScsiR3Reset(PPDMDEVINS pDevIns) +{ + LogFunc(("\n")); + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + pThis->fResetting = true; + virtioScsiR3QuiesceDevice(pDevIns, kvirtIoVmStateChangedReset); +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnPowerOff} + */ +static DECLCALLBACK(void) virtioScsiR3SuspendOrPowerOff(PPDMDEVINS pDevIns, VIRTIOVMSTATECHANGED enmType) +{ + LogFunc(("\n")); + + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + + /* VM is halted, thus no new I/O being dumped into queues by the guest. + * Workers have been flagged to stop pulling stuff already queued-up by the guest. + * Now tell lower-level to to suspend reqs (for example, DrvVD suspends all reqs + * on its wait queue, and we will get a callback as the state changes to + * suspended (and later, resumed) for each). + */ + for (uint32_t i = 0; i < pThis->cTargets; i++) + { + PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[i]; + if (pTarget->pDrvMediaEx) + pTarget->pDrvMediaEx->pfnNotifySuspend(pTarget->pDrvMediaEx); + } + + virtioScsiR3QuiesceDevice(pDevIns, enmType); +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnSuspend} + */ +static DECLCALLBACK(void) virtioScsiR3PowerOff(PPDMDEVINS pDevIns) +{ + LogFunc(("\n")); + virtioScsiR3SuspendOrPowerOff(pDevIns, kvirtIoVmStateChangedPowerOff); +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnSuspend} + */ +static DECLCALLBACK(void) virtioScsiR3Suspend(PPDMDEVINS pDevIns) +{ + LogFunc(("\n")); + virtioScsiR3SuspendOrPowerOff(pDevIns, kvirtIoVmStateChangedSuspend); +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnResume} + */ +static DECLCALLBACK(void) virtioScsiR3Resume(PPDMDEVINS pDevIns) +{ + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + LogFunc(("\n")); + + pThisCC->fQuiescing = false; + + /* Wake worker threads flagged to skip pulling queue entries during quiesce + * to ensure they re-check their queues. Active request queues may already + * be awake due to new reqs coming in. + */ + for (uint16_t uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_REQ_VIRTQ_CNT; uVirtqNbr++) + { + if ( virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr) + && ASMAtomicReadBool(&pThis->aWorkers[uVirtqNbr].fSleeping)) + { + Log6Func(("waking %s worker.\n", VIRTQNAME(uVirtqNbr))); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[uVirtqNbr].hEvtProcess); + AssertRC(rc); + } + } + /* Ensure guest is working the queues too. */ + virtioCoreR3VmStateChanged(&pThis->Virtio, kvirtIoVmStateChangedResume); +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected} + */ +static DECLCALLBACK(void) virtioScsiR3MediumEjected(PPDMIMEDIAEXPORT pInterface) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort); + PPDMDEVINS pDevIns = pTarget->pDevIns; + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + +#if 0 /* need more info about how to use this event. The VirtIO 1.0 specification + * lists several SCSI related event types but presumes the reader knows + * how to use them without providing references. */ + virtioScsiR3ReportMediaChange(pDevIns, pThis, pTarget->uTarget); +#endif + + if (pThisCC->pMediaNotify) + { + int rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY, + (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2, + pThisCC->pMediaNotify, pTarget->uTarget); + AssertRC(rc); + } +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged} + */ +static DECLCALLBACK(void) virtioScsiR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState) +{ + PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort); + PPDMDEVINS pDevIns = pTarget->pDevIns; + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + RT_NOREF(hIoReq, pvIoReqAlloc); + + switch (enmState) + { + case PDMMEDIAEXIOREQSTATE_SUSPENDED: + { + /* Stop considering this request active */ + virtioScsiR3Release(pDevIns, pThis, pThisCC); + break; + } + case PDMMEDIAEXIOREQSTATE_ACTIVE: + virtioScsiR3Retain(pThis); + break; + default: + AssertMsgFailed(("Invalid request state given %u\n", enmState)); + } +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnDestruct} + */ +static DECLCALLBACK(int) virtioScsiR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + + RTMemFree(pThisCC->paTargetInstances); + pThisCC->paTargetInstances = NULL; + pThisCC->pMediaNotify = NULL; + + for (unsigned uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) + { + PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[uVirtqNbr]; + if (pWorker->hEvtProcess != NIL_SUPSEMEVENT) + { + PDMDevHlpSUPSemEventClose(pDevIns, pWorker->hEvtProcess); + pWorker->hEvtProcess = NIL_SUPSEMEVENT; + } + + if (pThisCC->aWorkers[uVirtqNbr].pThread) + { + /* Destroy the thread. */ + int rcThread; + int rc = PDMDevHlpThreadDestroy(pDevIns, pThisCC->aWorkers[uVirtqNbr].pThread, &rcThread); + if (RT_FAILURE(rc) || RT_FAILURE(rcThread)) + AssertMsgFailed(("%s Failed to destroythread rc=%Rrc rcThread=%Rrc\n", + __FUNCTION__, rc, rcThread)); + pThisCC->aWorkers[uVirtqNbr].pThread = NULL; + } + } + + virtioCoreR3Term(pDevIns, &pThis->Virtio, &pThisCC->Virtio); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMDEVREGR3,pfnConstruct} + */ +static DECLCALLBACK(int) virtioScsiR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + /* + * Quick initialization of the state data, making sure that the destructor always works. + */ + pThisCC->pDevIns = pDevIns; + + LogFunc(("PDM device instance: %d\n", iInstance)); + RTStrPrintf(pThis->szInstance, sizeof(pThis->szInstance), "VIRTIOSCSI%d", iInstance); + + pThisCC->IBase.pfnQueryInterface = virtioScsiR3DeviceQueryInterface; + pThisCC->ILeds.pfnQueryStatusLed = virtioScsiR3DeviceQueryStatusLed; + + /* + * Validate and read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "NumTargets|Bootable", ""); + + int rc = pHlp->pfnCFGMQueryU32Def(pCfg, "NumTargets", &pThis->cTargets, 1); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("virtio-scsi configuration error: failed to read NumTargets as integer")); + if (pThis->cTargets < 1 || pThis->cTargets > VIRTIOSCSI_MAX_TARGETS) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("virtio-scsi configuration error: NumTargets=%u is out of range (1..%u)"), + pThis->cTargets, VIRTIOSCSI_MAX_TARGETS); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "Bootable", &pThis->fBootable, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("virtio-scsi configuration error: failed to read Bootable as boolean")); + + LogRel(("%s: Targets=%u Bootable=%RTbool (unimplemented) R0Enabled=%RTbool RCEnabled=%RTbool\n", + pThis->szInstance, pThis->cTargets, pThis->fBootable, pDevIns->fR0Enabled, pDevIns->fRCEnabled)); + + + /* + * Do core virtio initialization. + */ + + /* Configure virtio_scsi_config that transacts via VirtIO implementation's Dev. Specific Cap callbacks */ + pThis->virtioScsiConfig.uNumVirtqs = VIRTIOSCSI_REQ_VIRTQ_CNT; + pThis->virtioScsiConfig.uSegMax = VIRTIOSCSI_MAX_SEG_COUNT; + pThis->virtioScsiConfig.uMaxSectors = VIRTIOSCSI_MAX_SECTORS_HINT; + pThis->virtioScsiConfig.uCmdPerLun = VIRTIOSCSI_MAX_COMMANDS_PER_LUN; + pThis->virtioScsiConfig.uEventInfoSize = sizeof(VIRTIOSCSI_EVENT_T); /*VirtIO 1.0 Spec says at least this size! */ + pThis->virtioScsiConfig.uSenseSize = VIRTIOSCSI_SENSE_SIZE_DEFAULT; + pThis->virtioScsiConfig.uCdbSize = VIRTIOSCSI_CDB_SIZE_DEFAULT; + pThis->virtioScsiConfig.uMaxChannel = VIRTIOSCSI_MAX_CHANNEL_HINT; + pThis->virtioScsiConfig.uMaxTarget = pThis->cTargets; + pThis->virtioScsiConfig.uMaxLun = VIRTIOSCSI_MAX_LUN; + + /* Initialize the generic Virtio core: */ + pThisCC->Virtio.pfnVirtqNotified = virtioScsiNotified; + pThisCC->Virtio.pfnStatusChanged = virtioScsiR3StatusChanged; + pThisCC->Virtio.pfnDevCapRead = virtioScsiR3DevCapRead; + pThisCC->Virtio.pfnDevCapWrite = virtioScsiR3DevCapWrite; + + VIRTIOPCIPARAMS VirtioPciParams; + VirtioPciParams.uDeviceId = PCI_DEVICE_ID_VIRTIOSCSI_HOST; + VirtioPciParams.uClassBase = PCI_CLASS_BASE_MASS_STORAGE; + VirtioPciParams.uClassSub = PCI_CLASS_SUB_SCSI_STORAGE_CONTROLLER; + VirtioPciParams.uClassProg = PCI_CLASS_PROG_UNSPECIFIED; + VirtioPciParams.uSubsystemId = PCI_DEVICE_ID_VIRTIOSCSI_HOST; /* VirtIO 1.0 spec allows PCI Device ID here */ + VirtioPciParams.uInterruptLine = 0x00; + VirtioPciParams.uInterruptPin = 0x01; + + rc = virtioCoreR3Init(pDevIns, &pThis->Virtio, &pThisCC->Virtio, &VirtioPciParams, pThis->szInstance, + VIRTIOSCSI_HOST_SCSI_FEATURES_OFFERED, 0 /*fOfferLegacy*/, + &pThis->virtioScsiConfig /*pvDevSpecificCap*/, sizeof(pThis->virtioScsiConfig)); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("virtio-scsi: failed to initialize VirtIO")); + + /* + * Initialize queues. + */ + + virtioScsiSetVirtqNames(pThis); + + /* Attach the queues and create worker threads for them: */ + for (uint16_t uVirtqNbr = 0; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) + { + rc = virtioCoreR3VirtqAttach(&pThis->Virtio, uVirtqNbr, VIRTQNAME(uVirtqNbr)); + if (RT_FAILURE(rc)) + continue; + if (uVirtqNbr == CONTROLQ_IDX || IS_REQ_VIRTQ(uVirtqNbr)) + { + rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->aWorkers[uVirtqNbr].pThread, + (void *)(uintptr_t)uVirtqNbr, virtioScsiR3WorkerThread, + virtioScsiR3WorkerWakeUp, 0, RTTHREADTYPE_IO, VIRTQNAME(uVirtqNbr)); + if (rc != VINF_SUCCESS) + { + LogRel(("Error creating thread for Virtual Virtq %s: %Rrc\n", VIRTQNAME(uVirtqNbr), rc)); + return rc; + } + + rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->aWorkers[uVirtqNbr].hEvtProcess); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("DevVirtioSCSI: Failed to create SUP event semaphore")); + } + pThis->afVirtqAttached[uVirtqNbr] = true; + } + + /* + * Initialize per device instances (targets). + */ + Log2Func(("Probing %d targets ...\n", pThis->cTargets)); + + pThisCC->paTargetInstances = (PVIRTIOSCSITARGET)RTMemAllocZ(sizeof(VIRTIOSCSITARGET) * pThis->cTargets); + if (!pThisCC->paTargetInstances) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to allocate memory for target states")); + + for (uint32_t uTarget = 0; uTarget < pThis->cTargets; uTarget++) + { + PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget]; + + if (RTStrAPrintf(&pTarget->pszTargetName, "VSCSI%u", uTarget) < 0) + AssertLogRelFailedReturn(VERR_NO_MEMORY); + + /* Initialize static parts of the device. */ + pTarget->pDevIns = pDevIns; + pTarget->uTarget = uTarget; + + pTarget->IBase.pfnQueryInterface = virtioScsiR3TargetQueryInterface; + + /* IMediaPort and IMediaExPort interfaces provide callbacks for VD media and downstream driver access */ + pTarget->IMediaPort.pfnQueryDeviceLocation = virtioScsiR3QueryDeviceLocation; + pTarget->IMediaPort.pfnQueryScsiInqStrings = NULL; + pTarget->IMediaExPort.pfnIoReqCompleteNotify = virtioScsiR3IoReqFinish; + pTarget->IMediaExPort.pfnIoReqCopyFromBuf = virtioScsiR3IoReqCopyFromBuf; + pTarget->IMediaExPort.pfnIoReqCopyToBuf = virtioScsiR3IoReqCopyToBuf; + pTarget->IMediaExPort.pfnIoReqStateChanged = virtioScsiR3IoReqStateChanged; + pTarget->IMediaExPort.pfnMediumEjected = virtioScsiR3MediumEjected; + pTarget->IMediaExPort.pfnIoReqQueryBuf = NULL; /* When used avoids copyFromBuf CopyToBuf*/ + pTarget->IMediaExPort.pfnIoReqQueryDiscardRanges = NULL; + + pTarget->IBase.pfnQueryInterface = virtioScsiR3TargetQueryInterface; + pTarget->ILed.pfnQueryStatusLed = virtioScsiR3TargetQueryStatusLed; + pTarget->led.u32Magic = PDMLED_MAGIC; + + LogFunc(("Attaching LUN: %s\n", pTarget->pszTargetName)); + + AssertReturn(uTarget < pThis->cTargets, VERR_PDM_NO_SUCH_LUN); + rc = PDMDevHlpDriverAttach(pDevIns, uTarget, &pTarget->IBase, &pTarget->pDrvBase, pTarget->pszTargetName); + if (RT_SUCCESS(rc)) + { + pTarget->fPresent = true; + + pTarget->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIA); + AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMedia), + ("virtio-scsi configuration error: LUN#%d missing basic media interface!\n", uTarget), + VERR_PDM_MISSING_INTERFACE); + /* Get the extended media interface. */ + pTarget->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pTarget->pDrvBase, PDMIMEDIAEX); + AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx), + ("virtio-scsi configuration error: LUN#%d missing extended media interface!\n", uTarget), + VERR_PDM_MISSING_INTERFACE); + + rc = pTarget->pDrvMediaEx->pfnIoReqAllocSizeSet(pTarget->pDrvMediaEx, sizeof(VIRTIOSCSIREQ)); + AssertMsgReturn(RT_VALID_PTR(pTarget->pDrvMediaEx), + ("virtio-scsi configuration error: LUN#%u: Failed to set I/O request size!\n", uTarget), + rc); + } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + { + pTarget->fPresent = false; + pTarget->pDrvBase = NULL; + Log(("virtio-scsi: no driver attached to device %s\n", pTarget->pszTargetName)); + rc = VINF_SUCCESS; + } + else + { + AssertLogRelMsgFailed(("virtio-scsi: Failed to attach %s: %Rrc\n", pTarget->pszTargetName, rc)); + return rc; + } + } + + /* + * Status driver (optional). + */ + PPDMIBASE pUpBase = NULL; + AssertCompile(PDM_STATUS_LUN >= VIRTIOSCSI_MAX_TARGETS); + rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pUpBase, "Status Port"); + if (RT_FAILURE(rc) && rc != VERR_PDM_NO_ATTACHED_DRIVER) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to attach the status LUN")); + if (RT_SUCCESS(rc) && pUpBase) + pThisCC->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pUpBase, PDMIMEDIANOTIFY); + + + /* + * Register saved state. + */ + rc = PDMDevHlpSSMRegister(pDevIns, VIRTIOSCSI_SAVED_STATE_VERSION, sizeof(*pThis), + virtioScsiR3SaveExec, virtioScsiR3LoadExec); + AssertRCReturn(rc, rc); + + /* + * Register the debugger info callback (ignore errors). + */ + char szTmp[128]; + RTStrPrintf(szTmp, sizeof(szTmp), "%s%u", pDevIns->pReg->szName, pDevIns->iInstance); + PDMDevHlpDBGFInfoRegister(pDevIns, szTmp, "virtio-scsi info", virtioScsiR3Info); + + return rc; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) virtioScsiRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + + PVIRTIOSCSI pThis = PDMDEVINS_2_DATA(pDevIns, PVIRTIOSCSI); + PVIRTIOSCSICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PVIRTIOSCSICC); + + + pThisCC->Virtio.pfnVirtqNotified = virtioScsiNotified; + return virtioCoreRZInit(pDevIns, &pThis->Virtio); +} + +#endif /* !IN_RING3 */ + + +/** + * The device registration structure. + */ +const PDMDEVREG g_DeviceVirtioSCSI = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "virtio-scsi", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE + | PDM_DEVREG_FLAGS_FIRST_SUSPEND_NOTIFICATION + | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION, + /* .fClass = */ PDM_DEVREG_CLASS_STORAGE, + /* .cMaxInstances = */ ~0U, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(VIRTIOSCSI), + /* .cbInstanceCC = */ sizeof(VIRTIOSCSICC), + /* .cbInstanceRC = */ sizeof(VIRTIOSCSIRC), + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ VBOX_MSIX_MAX_ENTRIES, + /* .pszDescription = */ "Virtio Host SCSI.\n", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ virtioScsiR3Construct, + /* .pfnDestruct = */ virtioScsiR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ virtioScsiR3Reset, + /* .pfnSuspend = */ virtioScsiR3Suspend, + /* .pfnResume = */ virtioScsiR3Resume, + /* .pfnAttach = */ virtioScsiR3Attach, + /* .pfnDetach = */ virtioScsiR3Detach, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ virtioScsiR3PowerOff, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ virtioScsiRZConstruct, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RC) + /* .pfnConstruct = */ virtioScsiRZConstruct, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +#endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + |