/* $Id: DevVirtioSCSI.cpp 82968 2020-02-04 10:35:17Z vboxsync $ $Revision: 82968 $ $Date: 2020-02-04 10:35:17 +0000 (Tue, 04 Feb 2020) $ $Author: vboxsync $ */ /** @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-2020 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ //#define LOG_GROUP LOG_GROUP_DRV_SCSI #define LOG_GROUP LOG_GROUP_DEV_VIRTIO #include #include #include #include #include #include #include #include #include #include #include "../build/VBoxDD.h" #include #ifdef IN_RING3 # include # include # include # include # include # include #endif #include "../VirtIO/Virtio_1_0.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_QUEUE_CNT 4 /**< T.B.D. Consider increasing */ #define VIRTIOSCSI_QUEUE_CNT (VIRTIOSCSI_REQ_QUEUE_CNT + 2) #define VIRTIOSCSI_MAX_TARGETS 256 /**< T.B.D. Figure out a a good value for this. */ #define VIRTIOSCSI_MAX_LUN 256 /**< 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_CDB_SIZE_DEFAULT 32 /**< VirtIO 1.0: 32 on reset, guest can change */ #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 /**< Spec-defined Index of control queue */ #define EVENTQ_IDX 1 /**< Spec-defined Index of event queue */ #define VIRTQ_REQ_BASE 2 /**< Spec-defined base index of request queues */ #define VIRTQNAME(qIdx) (pThis->aszVirtqNames[qIdx]) /**< Macro to get queue name from its index */ #define CBVIRTQNAME(qIdx) RTStrNLen(VIRTQNAME(qIdx), sizeof(VIRTQNAME(qIdx))) #define IS_REQ_QUEUE(qIdx) (qIdx >= VIRTQ_REQ_BASE && qIdx < VIRTIOSCSI_QUEUE_CNT) #define VIRTIO_IS_IN_DIRECTION(pMediaExTxDirEnumValue) \ ((pMediaExTxDirEnumValue) == PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE) #define VIRTIO_IS_OUT_DIRECTION(pMediaExTxDirEnumValue) \ ((pMediaExTxDirEnumValue) == PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE) /********************************************************************************************************************************* * 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 uNumQueues; /**< 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 scsiCtrl; VIRTIOSCSI_CTRL_TMF_T scsiCtrlTmf; VIRTIOSCSI_CTRL_AN_T scsiCtrlAsyncNotify; 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 */ } 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 */ bool volatile fSleeping; /**< Flags whether worker thread is sleeping or not */ bool volatile fNotified; /**< Flags whether worker thread notified */ uint16_t auRedoDescs[VIRTQ_MAX_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; PPDMDEVINSRC pDevInsRC; PPDMDEVINSR0 pDevInsR0; /** 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_QUEUE_CNT]; /** Instance name */ char szInstance[16]; /** Device-specific spec-based VirtIO VIRTQNAMEs */ char aszVirtqNames[VIRTIOSCSI_QUEUE_CNT][VIRTIO_MAX_QUEUE_NAME_SIZE]; /** Track which VirtIO queues we've attached to */ bool afQueueAttached[VIRTIOSCSI_QUEUE_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_T10_PI was negotiated */ uint32_t fHasHotplug; /** True if VIRTIO_SCSI_F_T10_PI was negotiated */ uint32_t fHasInOutBufs; /** True if VIRTIO_SCSI_F_T10_PI 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_QUEUE_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; /** Queue to send tasks to R3. - HC ptr */ R3PTRTYPE(PPDMQUEUE) pNotifierQueueR3; /** 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 qIdx; /**< Index of queue this request arrived on */ PVIRTIO_DESC_CHAIN_T pDescChain; /**< Prepared desc chain pulled from virtq avail ring */ uint32_t cbDataIn; /**< size of dataout buffer */ uint32_t cbDataOut; /**< size of dataout buffer */ uint16_t uDataInOff; /**< Fixed size of respHdr + sense (precede datain) */ uint16_t uDataOutOff; /**< Fixed size of respHdr + sense (precede datain) */ 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; #ifdef IN_RING3 /* spans most of the file, at the moment. */ DECLINLINE(void) virtioScsiSetVirtqNames(PVIRTIOSCSI pThis) { RTStrCopy(pThis->aszVirtqNames[CONTROLQ_IDX], VIRTIO_MAX_QUEUE_NAME_SIZE, "controlq"); RTStrCopy(pThis->aszVirtqNames[EVENTQ_IDX], VIRTIO_MAX_QUEUE_NAME_SIZE, "eventq"); for (uint16_t qIdx = VIRTQ_REQ_BASE; qIdx < VIRTQ_REQ_BASE + VIRTIOSCSI_REQ_QUEUE_CNT; qIdx++) RTStrPrintf(pThis->aszVirtqNames[qIdx], VIRTIO_MAX_QUEUE_NAME_SIZE, "requestq<%d>", qIdx - VIRTQ_REQ_BASE); } #ifdef LOG_ENABLED DECLINLINE(const char *) virtioGetTxDirText(uint32_t enmTxDir) { switch (enmTxDir) { case PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN: return ""; case PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE: return ""; case PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE: return ""; case PDMMEDIAEXIOREQSCSITXDIR_NONE: return ""; default: return ""; } } 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 ""; } } 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 ""; } } 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 6; if (uCmd >= 0x20 && uCmd < 0x60) return 10; if (uCmd >= 0x60 && uCmd < 0x80) return cbMax; if (uCmd >= 0x80 && uCmd < 0xa0) return 16; if (uCmd >= 0xa0 && uCmd < 0xc0) return 12; return cbMax; } #endif /* LOG_ENABLED */ static int virtioScsiR3SendEvent(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget, uint32_t uEventType, uint32_t uReason) { 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] = event.abVirtioLun[5] = event.abVirtioLun[6] = event.abVirtioLun[7] = 0; 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; } if (virtioCoreQueueIsEmpty(pDevIns, &pThis->Virtio, EVENTQ_IDX)) { LogFunc(("eventq is empty, events missed (driver didn't preload queue)!\n")); ASMAtomicWriteBool(&pThis->fEventsMissed, true); return VINF_SUCCESS; } PVIRTIO_DESC_CHAIN_T pDescChain; virtioCoreR3QueueGet(pDevIns, &pThis->Virtio, EVENTQ_IDX, &pDescChain, true); PRTSGBUF pReqSegBuf = (PRTSGBUF)RTMemAllocZ(sizeof(RTSGBUF)); AssertReturn(pReqSegBuf, VERR_NO_MEMORY); PRTSGSEG paReqSegs = (PRTSGSEG)RTMemAllocZ(sizeof(RTSGSEG) * 2); AssertReturn(paReqSegs, VERR_NO_MEMORY); /* Copy segment data to malloc'd memory to avoid stack out-of-scope errors sanitizer doesn't detect */ paReqSegs[0].pvSeg = RTMemAlloc(sizeof(event)); AssertReturn(paReqSegs[0].pvSeg, VERR_NO_MEMORY); memcpy(paReqSegs[0].pvSeg, &event, sizeof(event)); RTSgBufInit(pReqSegBuf, paReqSegs, 1); virtioCoreR3QueuePut(pDevIns, &pThis->Virtio, EVENTQ_IDX, pReqSegBuf, pDescChain, true); virtioCoreQueueSync(pDevIns, &pThis->Virtio, EVENTQ_IDX); RTMemFree(paReqSegs[0].pvSeg); RTMemFree(paReqSegs); RTMemFree(pReqSegBuf); return VINF_SUCCESS; } /** Internal worker. */ static void virtioScsiR3FreeReq(PVIRTIOSCSITARGET pTarget, PVIRTIOSCSIREQ pReq) { RTMemFree(pReq->pbSense); pReq->pbSense = 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 pThisCC VirtIO SCSI ring-3 instance data. * @param qIdx Queue index * @param pDescChain Pointer to pre-processed descriptor chain pulled from virtq * @param pRespHdr Response header * @param pbSense Pointer to sense buffer or NULL if none. * * @returns VBox status code. */ static int virtioScsiR3ReqErr(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC, uint16_t qIdx, PVIRTIO_DESC_CHAIN_T pDescChain, REQ_RESP_HDR_T *pRespHdr, uint8_t *pbSense) { uint8_t *pabSenseBuf = (uint8_t *)RTMemAllocZ(pThis->virtioScsiConfig.uSenseSize); AssertReturn(pabSenseBuf, VERR_NO_MEMORY); Log2Func((" status: %s response: %s\n", SCSIStatusText(pRespHdr->uStatus), virtioGetReqRespText(pRespHdr->uResponse))); PRTSGBUF pReqSegBuf = (PRTSGBUF)RTMemAllocZ(sizeof(RTSGBUF)); AssertReturn(pReqSegBuf, VERR_NO_MEMORY); PRTSGSEG paReqSegs = (PRTSGSEG)RTMemAllocZ(sizeof(RTSGSEG) * 2); AssertReturn(paReqSegs, VERR_NO_MEMORY); paReqSegs[0].cbSeg = sizeof(*pRespHdr); paReqSegs[0].pvSeg = pRespHdr; paReqSegs[1].cbSeg = pThis->virtioScsiConfig.uSenseSize; paReqSegs[1].pvSeg = pabSenseBuf; if (pbSense && pRespHdr->cbSenseLen) memcpy(pabSenseBuf, pbSense, pRespHdr->cbSenseLen); else pRespHdr->cbSenseLen = 0; /* Copy segment data to malloc'd memory to avoid stack out-of-scope errors sanitizer doesn't detect */ for (int i = 0; i < 2; i++) { void *pv = paReqSegs[i].pvSeg; paReqSegs[i].pvSeg = RTMemAlloc(paReqSegs[i].cbSeg); AssertReturn(paReqSegs[i].pvSeg, VERR_NO_MEMORY); memcpy(paReqSegs[i].pvSeg, pv, paReqSegs[i].cbSeg); } RTSgBufInit(pReqSegBuf, paReqSegs, 2); if (pThis->fResetting) pRespHdr->uResponse = VIRTIOSCSI_S_RESET; virtioCoreR3QueuePut(pDevIns, &pThis->Virtio, qIdx, pReqSegBuf, pDescChain, true /* fFence */); virtioCoreQueueSync(pDevIns, &pThis->Virtio, qIdx); for (int i = 0; i < 2; i++) RTMemFree(paReqSegs[i].pvSeg); RTMemFree(paReqSegs); RTMemFree(pReqSegBuf); RTMemFree(pabSenseBuf); if (!ASMAtomicDecU32(&pThis->cActiveReqs) && pThisCC->fQuiescing) PDMDevHlpAsyncNotificationCompleted(pDevIns); Log2(("---------------------------------------------------------------------------------\n")); return VINF_SUCCESS; } 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: %s\n", RTErrGetDefine(rcReq))); if (LogIs3Enabled()) { LogFunc(("cbDataIn = %u, cbDataOut = %u (cbIn = %u, cbOut = %u)\n", pReq->cbDataIn, pReq->cbDataOut, pReq->pDescChain->cbPhysReturn, pReq->pDescChain->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]))); } int cSegs = 0; 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; virtioScsiR3ReqErr(pDevIns, pThis, pThisCC, pReq->qIdx, pReq->pDescChain, &respHdr, abSense); } else { Assert(pReq->pbSense != NULL); /* req datain bytes already in guest phys mem. via virtioScsiIoReqCopyFromBuf() */ PRTSGBUF pReqSegBuf = (PRTSGBUF)RTMemAllocZ(sizeof(RTSGBUF)); AssertReturn(pReqSegBuf, VERR_NO_MEMORY); PRTSGSEG paReqSegs = (PRTSGSEG)RTMemAllocZ(sizeof(RTSGSEG) * 2); AssertReturn(paReqSegs, VERR_NO_MEMORY); paReqSegs[cSegs].pvSeg = &respHdr; paReqSegs[cSegs++].cbSeg = sizeof(respHdr); paReqSegs[cSegs].pvSeg = pReq->pbSense; paReqSegs[cSegs++].cbSeg = pReq->cbSenseAlloc; /* VirtIO 1.0 spec 5.6.4/5.6.6.1 */ /* Copy segment data to malloc'd memory to avoid stack out-of-scope errors sanitizer doesn't detect */ for (int i = 0; i < cSegs; i++) { void *pv = paReqSegs[i].pvSeg; paReqSegs[i].pvSeg = RTMemAlloc(paReqSegs[i].cbSeg); AssertReturn(paReqSegs[i].pvSeg, VERR_NO_MEMORY); memcpy(paReqSegs[i].pvSeg, pv, paReqSegs[i].cbSeg); } RTSgBufInit(pReqSegBuf, paReqSegs, cSegs); size_t cbReqSgBuf = RTSgBufCalcTotalLength(pReqSegBuf); AssertMsgReturn(cbReqSgBuf <= pReq->pDescChain->cbPhysReturn, ("Guest expected less req data (space needed: %d, avail: %d)\n", cbReqSgBuf, pReq->pDescChain->cbPhysReturn), VERR_BUFFER_OVERFLOW); virtioCoreR3QueuePut(pDevIns, &pThis->Virtio, pReq->qIdx, pReqSegBuf, pReq->pDescChain, true /* fFence TBD */); virtioCoreQueueSync(pDevIns, &pThis->Virtio, pReq->qIdx); for (int i = 0; i < cSegs; i++) RTMemFree(paReqSegs[i].pvSeg); RTMemFree(paReqSegs); RTMemFree(pReqSegBuf); Log2(("-----------------------------------------------------------------------------------------\n")); } virtioScsiR3FreeReq(pTarget, pReq); if (!ASMAtomicDecU32(&pThis->cActiveReqs) && pThisCC->fQuiescing) PDMDevHlpAsyncNotificationCompleted(pDevIns); return VINF_SUCCESS; } /** * @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->pDescChain, VERR_INVALID_PARAMETER); PVIRTIOSGBUF pSgPhysReturn = pReq->pDescChain->pSgPhysReturn; virtioCoreSgBufAdvance(pSgPhysReturn, offDst); size_t cbCopied = 0; size_t cbRemain = pReq->cbDataIn; if (!pSgPhysReturn->idxSeg && pSgPhysReturn->cbSegLeft == pSgPhysReturn->paSegs[0].cbSeg) virtioCoreSgBufAdvance(pSgPhysReturn, pReq->uDataInOff); while (cbRemain) { PVIRTIOSGSEG paSeg = &pSgPhysReturn->paSegs[pSgPhysReturn->idxSeg]; uint64_t dstSgStart = (uint64_t)paSeg->gcPhys; uint64_t dstSgLen = (uint64_t)paSeg->cbSeg; uint64_t dstSgCur = (uint64_t)pSgPhysReturn->gcPhysCur; cbCopied = RT_MIN((uint64_t)pSgBuf->cbSegLeft, dstSgLen - (dstSgCur - dstSgStart)); PDMDevHlpPCIPhysWrite(pDevIns, (RTGCPHYS)pSgPhysReturn->gcPhysCur, pSgBuf->pvSegCur, cbCopied); RTSgBufAdvance(pSgBuf, cbCopied); virtioCoreSgBufAdvance(pSgPhysReturn, cbCopied); cbRemain -= cbCopied; } RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); /* needed? */ Log3Func((".... Copied %lu bytes from %lu byte guest buffer, residual=%lu\n", cbCopy, pReq->pDescChain->cbPhysReturn, pReq->pDescChain->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->pDescChain->pSgPhysSend; virtioCoreSgBufAdvance(pSgPhysSend, offSrc); size_t cbCopied = 0; size_t cbRemain = pReq->cbDataOut; while (cbRemain) { PVIRTIOSGSEG paSeg = &pSgPhysSend->paSegs[pSgPhysSend->idxSeg]; uint64_t srcSgStart = (uint64_t)paSeg->gcPhys; uint64_t srcSgLen = (uint64_t)paSeg->cbSeg; uint64_t srcSgCur = (uint64_t)pSgPhysSend->gcPhysCur; cbCopied = RT_MIN((uint64_t)pSgBuf->cbSegLeft, srcSgLen - (srcSgCur - srcSgStart)); PDMDevHlpPCIPhysRead(pDevIns, (RTGCPHYS)pSgPhysSend->gcPhysCur, pSgBuf->pvSegCur, cbCopied); RTSgBufAdvance(pSgBuf, cbCopied); virtioCoreSgBufAdvance(pSgPhysSend, cbCopied); cbRemain -= cbCopied; } Log2Func((".... Copied %lu bytes to %lu byte guest buffer, residual=%lu\n", cbCopy, pReq->pDescChain->cbPhysReturn, pReq->pDescChain->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 qIdx, PVIRTIO_DESC_CHAIN_T pDescChain) { ASMAtomicIncU32(&pThis->cActiveReqs); /* * Extract command header and CDB from guest physical memory */ size_t cbReqHdr = sizeof(REQ_CMD_HDR_T) + pThis->virtioScsiConfig.uCdbSize; AssertReturn(pDescChain->cbPhysSend >= cbReqHdr, VERR_INVALID_PARAMETER); PVIRTIOSCSI_REQ_CMD_T pVirtqReq = (PVIRTIOSCSI_REQ_CMD_T)RTMemAllocZ(cbReqHdr); AssertReturn(pVirtqReq, VERR_NO_MEMORY); uint8_t *pb = (uint8_t *)pVirtqReq; for (size_t cb = RT_MIN(pDescChain->cbPhysSend, cbReqHdr); cb; ) { size_t cbSeg = cb; RTGCPHYS GCPhys = virtioCoreSgBufGetNextSegment(pDescChain->pSgPhysSend, &cbSeg); PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pb, cbSeg); pb += cbSeg; cb -= cbSeg; } uint8_t uType = pVirtqReq->ReqHdr.abVirtioLun[0]; uint8_t uTarget = pVirtqReq->ReqHdr.abVirtioLun[1]; uint32_t uScsiLun = (pVirtqReq->ReqHdr.abVirtioLun[2] << 8 | pVirtqReq->ReqHdr.abVirtioLun[3]) & 0x3fff; bool fBadLUNFormat = false; if (uType == 0xc1 && uTarget == 0x01) { LogRel(("* * * REPORT LUNS LU ACCESSED * * * ")); /* 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. Guest drivers * whose source code has been checked, so far, don't seem to use it. If it starts * showing up in the logs can try to work */ uScsiLun = 0xff; } else if (uType != 1) fBadLUNFormat = true; LogFunc(("[%s] (Target: %d LUN: %d) CDB: %.*Rhxs\n", SCSICmdText(pVirtqReq->uCdb[0]), uTarget, uScsiLun, virtioScsiEstimateCdbLen(pVirtqReq->uCdb[0], pThis->virtioScsiConfig.uCdbSize), pVirtqReq->uCdb)); Log3Func(("cmd id: %RX64, attr: %x, prio: %d, crn: %x\n", pVirtqReq->ReqHdr.uId, pVirtqReq->ReqHdr.uTaskAttr, pVirtqReq->ReqHdr.uPrio, pVirtqReq->ReqHdr.uCrn)); /* * Calculate request offsets */ off_t uDataOutOff = sizeof(REQ_CMD_HDR_T) + pThis->virtioScsiConfig.uCdbSize; off_t uDataInOff = sizeof(REQ_RESP_HDR_T) + pThis->virtioScsiConfig.uSenseSize; uint32_t cbDataOut = pDescChain->cbPhysSend - uDataOutOff; uint32_t cbDataIn = pDescChain->cbPhysReturn - uDataInOff; /* * Handle submission errors */ if (RT_LIKELY(!fBadLUNFormat)) { /* likely */ } else { Log2Func(("Error submitting request, bad LUN format\n")); REQ_RESP_HDR_T respHdr = { 0 }; respHdr.cbSenseLen = 0; respHdr.uStatus = 0; respHdr.uResponse = VIRTIOSCSI_S_FAILURE; respHdr.uResidual = cbDataIn + cbDataOut; virtioScsiR3ReqErr(pDevIns, pThis, pThisCC, qIdx, pDescChain, &respHdr , NULL); RTMemFree(pVirtqReq); return VINF_SUCCESS; } if (RT_LIKELY( uTarget < pThis->cTargets && pThisCC->paTargetInstances[uTarget].fPresent && pThisCC->paTargetInstances[uTarget].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 }; REQ_RESP_HDR_T respHdr = { 0 }; respHdr.cbSenseLen = sizeof(abSense); respHdr.uStatus = SCSI_STATUS_CHECK_CONDITION; respHdr.uResponse = VIRTIOSCSI_S_BAD_TARGET; respHdr.uResidual = cbDataOut + cbDataIn; virtioScsiR3ReqErr(pDevIns, pThis, pThisCC, qIdx, pDescChain, &respHdr, abSense); RTMemFree(pVirtqReq); return VINF_SUCCESS; } 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 }; REQ_RESP_HDR_T respHdr = { 0 }; respHdr.cbSenseLen = sizeof(abSense); respHdr.uStatus = SCSI_STATUS_CHECK_CONDITION; respHdr.uResponse = VIRTIOSCSI_S_OK; respHdr.uResidual = cbDataOut + cbDataIn; virtioScsiR3ReqErr(pDevIns, pThis, pThisCC, qIdx, pDescChain, &respHdr, abSense); RTMemFree(pVirtqReq); return VINF_SUCCESS; } if (RT_LIKELY(!pThis->fResetting)) { /* likely */ } else { Log2Func(("Aborting req submission because reset is in progress\n")); REQ_RESP_HDR_T respHdr = { 0 }; respHdr.cbSenseLen = 0; respHdr.uStatus = SCSI_STATUS_OK; respHdr.uResponse = VIRTIOSCSI_S_RESET; respHdr.uResidual = cbDataIn + cbDataOut; virtioScsiR3ReqErr(pDevIns, pThis, pThisCC, qIdx, pDescChain, &respHdr, NULL); RTMemFree(pVirtqReq); return VINF_SUCCESS; } PVIRTIOSCSITARGET pTarget = &pThisCC->paTargetInstances[uTarget]; 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 }; 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; virtioScsiR3ReqErr(pDevIns, pThis, pThisCC, qIdx, pDescChain, &respHdr , abSense); RTMemFree(pVirtqReq); return VINF_SUCCESS; } /* * Have underlying driver allocate a req of size set during initialization of this device. */ PDMMEDIAEXIOREQ hIoReq = NULL; PVIRTIOSCSIREQ pReq; PPDMIMEDIAEX pIMediaEx = pTarget->pDrvMediaEx; int rc = pIMediaEx->pfnIoReqAlloc(pIMediaEx, &hIoReq, (void **)&pReq, 0 /* uIoReqId */, PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR); if (RT_FAILURE(rc)) { RTMemFree(pVirtqReq); AssertMsgRCReturn(rc, ("Failed to allocate I/O request, rc=%Rrc\n", rc), rc); } pReq->hIoReq = hIoReq; pReq->pTarget = pTarget; pReq->qIdx = qIdx; pReq->cbDataIn = cbDataIn; pReq->cbDataOut = cbDataOut; pReq->pDescChain = pDescChain; pReq->uDataInOff = uDataInOff; pReq->uDataOutOff = uDataOutOff; pReq->cbSenseAlloc = pThis->virtioScsiConfig.uSenseSize; pReq->pbSense = (uint8_t *)RTMemAllocZ(pReq->cbSenseAlloc); AssertMsgReturnStmt(pReq->pbSense, ("Out of memory allocating sense buffer"), virtioScsiR3FreeReq(pTarget, pReq); RTMemFree(pVirtqReq), 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, pVirtqReq->uCdb, (size_t)pThis->virtioScsiConfig.uCdbSize, 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; virtioScsiR3ReqErr(pDevIns, pThis, pThisCC, qIdx, pDescChain, &respHdr, abSense); virtioScsiR3FreeReq(pTarget, pReq); } RTMemFree(pVirtqReq); 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 qIdx CONTROLQ_IDX * @param pDescChain Descriptor chain to process. */ static int virtioScsiR3Ctrl(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, PVIRTIOSCSICC pThisCC, uint16_t qIdx, PVIRTIO_DESC_CHAIN_T pDescChain) { uint8_t bResponse = VIRTIOSCSI_S_OK; uint8_t cSegs = 0; AssertReturn(pDescChain->cbPhysSend >= RT_MIN(sizeof(VIRTIOSCSI_CTRL_AN_T), sizeof(VIRTIOSCSI_CTRL_TMF_T)), 0); /* * Allocate buffer and read in the control command */ PVIRTIO_SCSI_CTRL_UNION_T pScsiCtrlUnion = (PVIRTIO_SCSI_CTRL_UNION_T)RTMemAllocZ(sizeof(VIRTIO_SCSI_CTRL_UNION_T)); AssertPtrReturn(pScsiCtrlUnion, VERR_NO_MEMORY /*ignored*/); uint8_t *pb = pScsiCtrlUnion->ab; for (size_t cb = RT_MIN(pDescChain->cbPhysSend, sizeof(VIRTIO_SCSI_CTRL_UNION_T)); cb; ) { size_t cbSeg = cb; RTGCPHYS GCPhys = virtioCoreSgBufGetNextSegment(pDescChain->pSgPhysSend, &cbSeg); PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pb, cbSeg); pb += cbSeg; cb -= cbSeg; } AssertReturn( (pScsiCtrlUnion->scsiCtrl.uType == VIRTIOSCSI_T_TMF && pDescChain->cbPhysSend >= sizeof(VIRTIOSCSI_CTRL_TMF_T)) || ( ( pScsiCtrlUnion->scsiCtrl.uType == VIRTIOSCSI_T_AN_QUERY || pScsiCtrlUnion->scsiCtrl.uType == VIRTIOSCSI_T_AN_SUBSCRIBE) && pDescChain->cbPhysSend >= sizeof(VIRTIOSCSI_CTRL_AN_T)), 0); PRTSGSEG paReqSegs = (PRTSGSEG)RTMemAllocZ(sizeof(RTSGSEG) * 2); AssertReturn(paReqSegs, VERR_NO_MEMORY); switch (pScsiCtrlUnion->scsiCtrl.uType) { case VIRTIOSCSI_T_TMF: /* Task Management Functions */ { uint8_t uTarget = pScsiCtrlUnion->scsiCtrlTmf.abScsiLun[1]; uint32_t uScsiLun = (pScsiCtrlUnion->scsiCtrlTmf.abScsiLun[2] << 8 | pScsiCtrlUnion->scsiCtrlTmf.abScsiLun[3]) & 0x3fff; Log2Func(("[%s] (Target: %d LUN: %d) Task Mgt Function: %s\n", VIRTQNAME(qIdx), uTarget, uScsiLun, virtioGetTMFTypeText(pScsiCtrlUnion->scsiCtrlTmf.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 (pScsiCtrlUnion->scsiCtrlTmf.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; } RTSGSEG aSegs[] = { { &bResponse, sizeof(bResponse) } }; memcpy(paReqSegs, aSegs, sizeof(aSegs)); cSegs = RT_ELEMENTS(aSegs); break; } case VIRTIOSCSI_T_AN_QUERY: /* Guest SCSI driver is querying supported async event notifications */ { PVIRTIOSCSI_CTRL_AN_T pScsiCtrlAnQuery = &pScsiCtrlUnion->scsiCtrlAsyncNotify; uint8_t uTarget = pScsiCtrlAnQuery->abScsiLun[1]; uint32_t uScsiLun = (pScsiCtrlAnQuery->abScsiLun[2] << 8 | pScsiCtrlAnQuery->abScsiLun[3]) & 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), pScsiCtrlAnQuery->fEventsRequested); Log2Func(("[%s] (Target: %d LUN: %d) Async. Notification Query: %s\n", VIRTQNAME(qIdx), uTarget, uScsiLun, szTypeText)); } #endif uint32_t fSupportedEvents = SUPPORTED_EVENTS; RTSGSEG aSegs[] = { { &fSupportedEvents, sizeof(fSupportedEvents) }, { &bResponse, sizeof(bResponse) } }; memcpy(paReqSegs, aSegs, sizeof(aSegs)); cSegs = RT_ELEMENTS(aSegs); break; } case VIRTIOSCSI_T_AN_SUBSCRIBE: /* Guest SCSI driver is subscribing to async event notification(s) */ { PVIRTIOSCSI_CTRL_AN_T pScsiCtrlAnSubscribe = &pScsiCtrlUnion->scsiCtrlAsyncNotify; if (pScsiCtrlAnSubscribe->fEventsRequested & ~SUBSCRIBABLE_EVENTS) LogFunc(("Unsupported bits in event subscription event mask: %#x\n", pScsiCtrlAnSubscribe->fEventsRequested)); uint8_t uTarget = pScsiCtrlAnSubscribe->abScsiLun[1]; uint32_t uScsiLun = (pScsiCtrlAnSubscribe->abScsiLun[2] << 8 | pScsiCtrlAnSubscribe->abScsiLun[3]) & 0x3fff; #ifdef LOG_ENABLED if (LogIs2Enabled()) { char szTypeText[128]; virtioGetControlAsyncMaskText(szTypeText, sizeof(szTypeText), pScsiCtrlAnSubscribe->fEventsRequested); Log2Func(("[%s] (Target: %d LUN: %d) Async. Notification Subscribe: %s\n", VIRTQNAME(qIdx), 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 & pScsiCtrlAnSubscribe->fEventsRequested; } RTSGSEG aSegs[] = { { &pThis->fAsyncEvtsEnabled, sizeof(pThis->fAsyncEvtsEnabled) }, { &bResponse, sizeof(bResponse) } }; memcpy(paReqSegs, aSegs, sizeof(aSegs)); cSegs = RT_ELEMENTS(aSegs); break; } default: { LogFunc(("Unknown control type extracted from %s: %u\n", VIRTQNAME(qIdx), pScsiCtrlUnion->scsiCtrl.uType)); bResponse = VIRTIOSCSI_S_FAILURE; RTSGSEG aSegs[] = { { &bResponse, sizeof(bResponse) } }; memcpy(paReqSegs, aSegs, sizeof(aSegs)); cSegs = RT_ELEMENTS(aSegs); } } LogFunc(("Response code: %s\n", virtioGetReqRespText(bResponse))); PRTSGBUF pReqSegBuf = (PRTSGBUF)RTMemAllocZ(sizeof(RTSGBUF)); AssertReturn(pReqSegBuf, VERR_NO_MEMORY); /* Copy segment data to malloc'd memory to avoid stack out-of-scope errors sanitizer doesn't detect */ for (int i = 0; i < cSegs; i++) { void *pv = paReqSegs[i].pvSeg; paReqSegs[i].pvSeg = RTMemAlloc(paReqSegs[i].cbSeg); AssertReturn(paReqSegs[i].pvSeg, VERR_NO_MEMORY); memcpy(paReqSegs[i].pvSeg, pv, paReqSegs[i].cbSeg); } RTSgBufInit(pReqSegBuf, paReqSegs, cSegs); virtioCoreR3QueuePut(pDevIns, &pThis->Virtio, qIdx, pReqSegBuf, pDescChain, true); virtioCoreQueueSync(pDevIns, &pThis->Virtio, qIdx); for (int i = 0; i < cSegs; i++) RTMemFree(paReqSegs[i].pvSeg); RTMemFree(paReqSegs); RTMemFree(pReqSegBuf); 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 qIdx = (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[qIdx]; PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[qIdx]; if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) return VINF_SUCCESS; while (pThread->enmState == PDMTHREADSTATE_RUNNING) { if (!pWorkerR3->cRedoDescs && virtioCoreQueueIsEmpty(pDevIns, &pThis->Virtio, qIdx)) { /* Atomic interlocks avoid missing alarm while going to sleep & notifier waking the awoken */ ASMAtomicWriteBool(&pWorkerR3->fSleeping, true); bool fNotificationSent = ASMAtomicXchgBool(&pWorkerR3->fNotified, false); if (!fNotificationSent) { Log6Func(("%s worker sleeping...\n", VIRTQNAME(qIdx))); Assert(ASMAtomicReadBool(&pWorkerR3->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)) return VINF_SUCCESS; if (rc == VERR_INTERRUPTED) continue; Log6Func(("%s worker woken\n", VIRTQNAME(qIdx))); ASMAtomicWriteBool(&pWorkerR3->fNotified, false); } ASMAtomicWriteBool(&pWorkerR3->fSleeping, false); } if (!pThis->afQueueAttached[qIdx]) { LogFunc(("%s queue not attached, worker aborting...\n", VIRTQNAME(qIdx))); 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++) { PVIRTIO_DESC_CHAIN_T pDescChain; int rc = virtioCoreR3DescChainGet(pDevIns, &pThis->Virtio, qIdx, pWorkerR3->auRedoDescs[i], &pDescChain); if (RT_FAILURE(rc)) LogRel(("Error fetching desc chain to redo, %Rrc", rc)); rc = virtioScsiR3ReqSubmit(pDevIns, pThis, pThisCC, qIdx, pDescChain); if (RT_FAILURE(rc)) LogRel(("Error submitting req packet, resetting %Rrc", rc)); } pWorkerR3->cRedoDescs = 0; Log6Func(("fetching next descriptor chain from %s\n", VIRTQNAME(qIdx))); PVIRTIO_DESC_CHAIN_T pDescChain; int rc = virtioCoreR3QueueGet(pDevIns, &pThis->Virtio, qIdx, &pDescChain, true); if (rc == VERR_NOT_AVAILABLE) { Log6Func(("Nothing found in %s\n", VIRTQNAME(qIdx))); continue; } AssertRC(rc); if (qIdx == CONTROLQ_IDX) virtioScsiR3Ctrl(pDevIns, pThis, pThisCC, qIdx, pDescChain); else /* request queue index */ { rc = virtioScsiR3ReqSubmit(pDevIns, pThis, pThisCC, qIdx, pDescChain); if (RT_FAILURE(rc)) LogRel(("Error submitting req packet, resetting %Rrc", rc)); } } } return VINF_SUCCESS; } /********************************************************************************************************************************* * Sending evnets *********************************************************************************************************************************/ DECLINLINE(void) virtioScsiR3ReportEventsMissed(PPDMDEVINS pDevIns, PVIRTIOSCSI pThis, uint16_t uTarget) { virtioScsiR3SendEvent(pDevIns, pThis, uTarget, VIRTIOSCSI_T_NO_EVENT | VIRTIOSCSI_T_EVENTS_MISSED, 0); } #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,pfnQueueNotified} */ static DECLCALLBACK(void) virtioScsiR3Notified(PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC, uint16_t qIdx) { PVIRTIOSCSI pThis = RT_FROM_MEMBER(pVirtio, VIRTIOSCSI, Virtio); PVIRTIOSCSICC pThisCC = RT_FROM_MEMBER(pVirtioCC, VIRTIOSCSICC, Virtio); PPDMDEVINS pDevIns = pThisCC->pDevIns; AssertReturnVoid(qIdx < VIRTIOSCSI_QUEUE_CNT); PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[qIdx]; PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[qIdx]; #ifdef LOG_ENABLED RTLogFlush(NULL); #endif if (qIdx == CONTROLQ_IDX || IS_REQ_QUEUE(qIdx)) { Log6Func(("%s has available data\n", VIRTQNAME(qIdx))); /* Wake queue's worker thread up if sleeping */ if (!ASMAtomicXchgBool(&pWorkerR3->fNotified, true)) { if (ASMAtomicReadBool(&pWorkerR3->fSleeping)) { Log6Func(("waking %s worker.\n", VIRTQNAME(qIdx))); int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pWorker->hEvtProcess); AssertRC(rc); } } } else if (qIdx == EVENTQ_IDX) { Log3Func(("Driver queued buffer(s) to %s\n", VIRTQNAME(qIdx))); if (ASMAtomicXchgBool(&pThis->fEventsMissed, false)) virtioScsiR3ReportEventsMissed(pDevIns, pThis, 0); } else LogFunc(("Unexpected queue idx (ignoring): %d\n", qIdx)); } /** * @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_QUEUE_CNT; i++) pThis->afQueueAttached[i] = true; } else { LogFunc(("VirtIO is resetting\n")); for (unsigned i = 0; i < VIRTIOSCSI_QUEUE_CNT; i++) pThis->afQueueAttached[i] = false; } } /********************************************************************************************************************************* * 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. * *********************************************************************************************************************************/ /** * Resolves to boolean true if uOffset matches a field offset and size exactly, * (or if 64-bit field, if it accesses either 32-bit part as a 32-bit access) * Assumption is this critereon is mandated by VirtIO 1.0, Section 4.1.3.1) * (Easily re-written to allow unaligned bounded access to a field). * * @param member - Member of VIRTIO_PCI_COMMON_CFG_T * @result - true or false */ #define MATCH_SCSI_CONFIG(member) \ ( ( RT_SIZEOFMEMB(VIRTIOSCSI_CONFIG_T, member) == 8 \ && ( offConfig == RT_UOFFSETOF(VIRTIOSCSI_CONFIG_T, member) \ || offConfig == RT_UOFFSETOF(VIRTIOSCSI_CONFIG_T, member) + sizeof(uint32_t)) \ && cb == sizeof(uint32_t)) \ || ( offConfig == RT_UOFFSETOF(VIRTIOSCSI_CONFIG_T, member) \ && cb == RT_SIZEOFMEMB(VIRTIOSCSI_CONFIG_T, member)) ) #ifdef LOG_ENABLED # define LOG_SCSI_CONFIG_ACCESSOR(member) \ virtioCoreLogMappedIoValue(__FUNCTION__, #member, RT_SIZEOFMEMB(VIRTIOSCSI_CONFIG_T, member), \ pv, cb, offIntra, fWrite, false, 0); #else # define LOG_SCSI_CONFIG_ACCESSOR(member) do { } while (0) #endif #define SCSI_CONFIG_ACCESSOR(member) \ do \ { \ uint32_t offIntra = offConfig - RT_UOFFSETOF(VIRTIOSCSI_CONFIG_T, member); \ if (fWrite) \ memcpy((char *)&pThis->virtioScsiConfig.member + offIntra, pv, cb); \ else \ memcpy(pv, (const char *)&pThis->virtioScsiConfig.member + offIntra, cb); \ LOG_SCSI_CONFIG_ACCESSOR(member); \ } while(0) #define SCSI_CONFIG_ACCESSOR_READONLY(member) \ do \ { \ uint32_t offIntra = offConfig - RT_UOFFSETOF(VIRTIOSCSI_CONFIG_T, member); \ if (fWrite) \ LogFunc(("Guest attempted to write readonly virtio_pci_common_cfg.%s\n", #member)); \ else \ { \ memcpy(pv, (const char *)&pThis->virtioScsiConfig.member + offIntra, cb); \ LOG_SCSI_CONFIG_ACCESSOR(member); \ } \ } while(0) /** * Worker for virtioScsiR3DevCapWrite and virtioScsiR3DevCapRead. */ static int virtioScsiR3CfgAccessed(PVIRTIOSCSI pThis, uint32_t offConfig, void *pv, uint32_t cb, bool fWrite) { AssertReturn(pv && cb <= sizeof(uint32_t), fWrite ? VINF_SUCCESS : VINF_IOM_MMIO_UNUSED_00); if (MATCH_SCSI_CONFIG(uNumQueues)) SCSI_CONFIG_ACCESSOR_READONLY(uNumQueues); else if (MATCH_SCSI_CONFIG(uSegMax)) SCSI_CONFIG_ACCESSOR_READONLY(uSegMax); else if (MATCH_SCSI_CONFIG(uMaxSectors)) SCSI_CONFIG_ACCESSOR_READONLY(uMaxSectors); else if (MATCH_SCSI_CONFIG(uCmdPerLun)) SCSI_CONFIG_ACCESSOR_READONLY(uCmdPerLun); else if (MATCH_SCSI_CONFIG(uEventInfoSize)) SCSI_CONFIG_ACCESSOR_READONLY(uEventInfoSize); else if (MATCH_SCSI_CONFIG(uSenseSize)) SCSI_CONFIG_ACCESSOR(uSenseSize); else if (MATCH_SCSI_CONFIG(uCdbSize)) SCSI_CONFIG_ACCESSOR(uCdbSize); else if (MATCH_SCSI_CONFIG(uMaxChannel)) SCSI_CONFIG_ACCESSOR_READONLY(uMaxChannel); else if (MATCH_SCSI_CONFIG(uMaxTarget)) SCSI_CONFIG_ACCESSOR_READONLY(uMaxTarget); else if (MATCH_SCSI_CONFIG(uMaxLun)) SCSI_CONFIG_ACCESSOR_READONLY(uMaxLun); else { LogFunc(("Bad access by guest to virtio_scsi_config: off=%u (%#x), cb=%u\n", offConfig, offConfig, cb)); return fWrite ? VINF_SUCCESS : VINF_IOM_MMIO_UNUSED_00; } return VINF_SUCCESS; } #undef SCSI_CONFIG_ACCESSOR_READONLY #undef SCSI_CONFIG_ACCESSOR #undef LOG_ACCESSOR #undef MATCH_SCSI_CONFIG /** * @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 qIdx = 0; qIdx < VIRTIOSCSI_QUEUE_CNT; qIdx++) pHlp->pfnSSMGetBool(pSSM, &pThis->afQueueAttached[qIdx]); pHlp->pfnSSMGetU32(pSSM, &pThis->virtioScsiConfig.uNumQueues); 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); pHlp->pfnSSMGetU32(pSSM, &pThis->cTargets); for (uint16_t uTarget = 0; uTarget < pThis->cTargets; uTarget++) { uint16_t cReqsRedo; pHlp->pfnSSMGetU16(pSSM, &cReqsRedo); for (uint16_t qIdx = VIRTQ_REQ_BASE; qIdx < VIRTIOSCSI_QUEUE_CNT; qIdx++) { PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[qIdx]; pWorkerR3->cRedoDescs = 0; } for (int i = 0; i < cReqsRedo; i++) { uint16_t qIdx, uHeadIdx; pHlp->pfnSSMGetU16(pSSM, &qIdx); pHlp->pfnSSMGetU16(pSSM, &uHeadIdx); PVIRTIOSCSIWORKERR3 pWorkerR3 = &pThisCC->aWorkers[qIdx]; pWorkerR3->auRedoDescs[pWorkerR3->cRedoDescs++] = uHeadIdx; pWorkerR3->cRedoDescs %= VIRTQ_MAX_SIZE; } } /* * Call the virtio core to let it load its state. */ int rc = virtioCoreR3LoadExec(&pThis->Virtio, pDevIns->pHlpR3, pSSM); /* * Nudge request queue workers */ for (int qIdx = VIRTQ_REQ_BASE; qIdx < VIRTIOSCSI_QUEUE_CNT; qIdx++) { if (pThis->afQueueAttached[qIdx]) { LogFunc(("Waking %s worker.\n", VIRTQNAME(qIdx))); rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[qIdx].hEvtProcess); AssertRCReturn(rc, rc); } } 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 qIdx = 0; qIdx < VIRTIOSCSI_QUEUE_CNT; qIdx++) pHlp->pfnSSMPutBool(pSSM, pThis->afQueueAttached[qIdx]); pHlp->pfnSSMPutU32(pSSM, pThis->virtioScsiConfig.uNumQueues); 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->qIdx); pHlp->pfnSSMPutU16(pSSM, pReq->pDescChain->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); } /********************************************************************************************************************************* * 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(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(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(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 qIdx = 0; qIdx < VIRTIOSCSI_REQ_QUEUE_CNT; qIdx++) { if (ASMAtomicReadBool(&pThisCC->aWorkers[qIdx].fSleeping)) { Log6Func(("waking %s worker.\n", VIRTQNAME(qIdx))); int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[qIdx].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 = VMR3ReqCallNoWait(PDMDevHlpGetVM(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 */ if (!ASMAtomicDecU32(&pThis->cActiveReqs) && pThisCC->fQuiescing) PDMDevHlpAsyncNotificationCompleted(pDevIns); break; } case PDMMEDIAEXIOREQSTATE_ACTIVE: ASMAtomicIncU32(&pThis->cActiveReqs); 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 qIdx = 0; qIdx < VIRTIOSCSI_QUEUE_CNT; qIdx++) { PVIRTIOSCSIWORKER pWorker = &pThis->aWorkers[qIdx]; if (pWorker->hEvtProcess != NIL_SUPSEMEVENT) { PDMDevHlpSUPSemEventClose(pDevIns, pWorker->hEvtProcess); pWorker->hEvtProcess = NIL_SUPSEMEVENT; } if (pThisCC->aWorkers[qIdx].pThread) { /* Destroy the thread. */ int rcThread; int rc = PDMDevHlpThreadDestroy(pDevIns, pThisCC->aWorkers[qIdx].pThread, &rcThread); if (RT_FAILURE(rc) || RT_FAILURE(rcThread)) AssertMsgFailed(("%s Failed to destroythread rc=%Rrc rcThread=%Rrc\n", __FUNCTION__, rc, rcThread)); pThisCC->aWorkers[qIdx].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.uNumQueues = VIRTIOSCSI_REQ_QUEUE_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.pfnStatusChanged = virtioScsiR3StatusChanged; pThisCC->Virtio.pfnQueueNotified = virtioScsiR3Notified; 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, &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 qIdx = 0; qIdx < VIRTIOSCSI_QUEUE_CNT; qIdx++) { rc = virtioCoreR3QueueAttach(&pThis->Virtio, qIdx, VIRTQNAME(qIdx)); if (RT_FAILURE(rc)) continue; if (qIdx == CONTROLQ_IDX || IS_REQ_QUEUE(qIdx)) { rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->aWorkers[qIdx].pThread, (void *)(uintptr_t)qIdx, virtioScsiR3WorkerThread, virtioScsiR3WorkerWakeUp, 0, RTTHREADTYPE_IO, VIRTQNAME(qIdx)); if (rc != VINF_SUCCESS) { LogRel(("Error creating thread for Virtual Queue %s: %Rrc\n", VIRTQNAME(qIdx), rc)); return rc; } rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->aWorkers[qIdx].hEvtProcess); if (RT_FAILURE(rc)) return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("DevVirtioSCSI: Failed to create SUP event semaphore")); } pThis->afQueueAttached[qIdx] = 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(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(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(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; 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")); 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); return virtioCoreRZInit(pDevIns, &pThis->Virtio, &pThisCC->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 };