summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Storage/DevATA.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Storage/DevATA.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Storage/DevATA.cpp')
-rw-r--r--src/VBox/Devices/Storage/DevATA.cpp8484
1 files changed, 8484 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/DevATA.cpp b/src/VBox/Devices/Storage/DevATA.cpp
new file mode 100644
index 00000000..bef5a2b0
--- /dev/null
+++ b/src/VBox/Devices/Storage/DevATA.cpp
@@ -0,0 +1,8484 @@
+/* $Id: DevATA.cpp $ */
+/** @file
+ * VBox storage devices: ATA/ATAPI controller device (disk and cdrom).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_IDE
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmstorageifs.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#ifdef IN_RING3
+# include <iprt/mem.h>
+# include <iprt/mp.h>
+# include <iprt/semaphore.h>
+# include <iprt/thread.h>
+# include <iprt/time.h>
+# include <iprt/uuid.h>
+#endif /* IN_RING3 */
+#include <iprt/critsect.h>
+#include <iprt/asm.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/pgm.h>
+
+#include <VBox/sup.h>
+#include <VBox/AssertGuest.h>
+#include <VBox/scsi.h>
+#include <VBox/scsiinline.h>
+#include <VBox/ata.h>
+
+#include "ATAPIPassthrough.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Temporary instrumentation for tracking down potential virtual disk
+ * write performance issues. */
+#undef VBOX_INSTRUMENT_DMA_WRITES
+
+/** @name The SSM saved state versions.
+ * @{
+ */
+/** The current saved state version. */
+#define ATA_SAVED_STATE_VERSION 21
+/** Saved state version without iCurLBA for ATA commands. */
+#define ATA_SAVED_STATE_VERSION_WITHOUT_ATA_ILBA 20
+/** The saved state version used by VirtualBox 3.0.
+ * This lacks the config part and has the type at the and. */
+#define ATA_SAVED_STATE_VERSION_VBOX_30 19
+#define ATA_SAVED_STATE_VERSION_WITH_BOOL_TYPE 18
+#define ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE 16
+#define ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS 17
+/** @} */
+
+/** Values read from an empty (with no devices attached) ATA bus. */
+#define ATA_EMPTY_BUS_DATA 0x7F
+#define ATA_EMPTY_BUS_DATA_32 0x7F7F7F7F
+
+/**
+ * Maximum number of sectors to transfer in a READ/WRITE MULTIPLE request.
+ * Set to 1 to disable multi-sector read support. According to the ATA
+ * specification this must be a power of 2 and it must fit in an 8 bit
+ * value. Thus the only valid values are 1, 2, 4, 8, 16, 32, 64 and 128.
+ */
+#define ATA_MAX_MULT_SECTORS 128
+
+/** The maxium I/O buffer size (for sanity). */
+#define ATA_MAX_SECTOR_SIZE _4K
+/** The maxium I/O buffer size (for sanity). */
+#define ATA_MAX_IO_BUFFER_SIZE (ATA_MAX_MULT_SECTORS * ATA_MAX_SECTOR_SIZE)
+
+/** Mask to be applied to all indexing into ATACONTROLLER::aIfs. */
+#define ATA_SELECTED_IF_MASK 1
+
+/**
+ * Fastest PIO mode supported by the drive.
+ */
+#define ATA_PIO_MODE_MAX 4
+/**
+ * Fastest MDMA mode supported by the drive.
+ */
+#define ATA_MDMA_MODE_MAX 2
+/**
+ * Fastest UDMA mode supported by the drive.
+ */
+#define ATA_UDMA_MODE_MAX 6
+
+/** ATAPI sense info size. */
+#define ATAPI_SENSE_SIZE 64
+
+/** The maximum number of release log entries per device. */
+#define MAX_LOG_REL_ERRORS 1024
+
+/* MediaEventStatus */
+#define ATA_EVENT_STATUS_UNCHANGED 0 /**< medium event status not changed */
+#define ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED 1 /**< medium eject requested (eject button pressed) */
+#define ATA_EVENT_STATUS_MEDIA_NEW 2 /**< new medium inserted */
+#define ATA_EVENT_STATUS_MEDIA_REMOVED 3 /**< medium removed */
+#define ATA_EVENT_STATUS_MEDIA_CHANGED 4 /**< medium was removed + new medium was inserted */
+
+/* Media track type */
+#define ATA_MEDIA_TYPE_UNKNOWN 0 /**< unknown CD type */
+#define ATA_MEDIA_NO_DISC 0x70 /**< Door closed, no medium */
+
+/** @defgroup grp_piix3atabmdma PIIX3 ATA Bus Master DMA
+ * @{
+ */
+
+/** @name BM_STATUS
+ * @{
+ */
+/** Currently performing a DMA operation. */
+#define BM_STATUS_DMAING 0x01
+/** An error occurred during the DMA operation. */
+#define BM_STATUS_ERROR 0x02
+/** The DMA unit has raised the IDE interrupt line. */
+#define BM_STATUS_INT 0x04
+/** User-defined bit 0, commonly used to signal that drive 0 supports DMA. */
+#define BM_STATUS_D0DMA 0x20
+/** User-defined bit 1, commonly used to signal that drive 1 supports DMA. */
+#define BM_STATUS_D1DMA 0x40
+/** @} */
+
+/** @name BM_CMD
+ * @{
+ */
+/** Start the DMA operation. */
+#define BM_CMD_START 0x01
+/** Data transfer direction: from device to memory if set. */
+#define BM_CMD_WRITE 0x08
+/** @} */
+
+/** Number of I/O ports per bus-master DMA controller. */
+#define BM_DMA_CTL_IOPORTS 8
+/** Mask corresponding to BM_DMA_CTL_IOPORTS. */
+#define BM_DMA_CTL_IOPORTS_MASK 7
+/** Shift count corresponding to BM_DMA_CTL_IOPORTS. */
+#define BM_DMA_CTL_IOPORTS_SHIFT 3
+
+/** @} */
+
+#define ATADEVSTATE_2_DEVINS(pIf) ( (pIf)->CTX_SUFF(pDevIns) )
+#define CONTROLLER_2_DEVINS(pController) ( (pController)->CTX_SUFF(pDevIns) )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** @defgroup grp_piix3atabmdma PIIX3 ATA Bus Master DMA
+ * @{
+ */
+/** PIIX3 Bus Master DMA unit state. */
+typedef struct BMDMAState
+{
+ /** Command register. */
+ uint8_t u8Cmd;
+ /** Status register. */
+ uint8_t u8Status;
+ /** Explicit alignment padding. */
+ uint8_t abAlignment[2];
+ /** Address of the MMIO region in the guest's memory space. */
+ RTGCPHYS32 GCPhysAddr;
+} BMDMAState;
+
+/** PIIX3 Bus Master DMA descriptor entry. */
+typedef struct BMDMADesc
+{
+ /** Address of the DMA source/target buffer. */
+ RTGCPHYS32 GCPhysBuffer;
+ /** Size of the DMA source/target buffer. */
+ uint32_t cbBuffer;
+} BMDMADesc;
+/** @} */
+
+
+/**
+ * The shared state of an ATA device.
+ */
+typedef struct ATADEVSTATE
+{
+ /** The I/O buffer.
+ * @note Page aligned in case it helps. */
+ uint8_t abIOBuffer[ATA_MAX_IO_BUFFER_SIZE];
+
+ /** Flag indicating whether the current command uses LBA48 mode. */
+ bool fLBA48;
+ /** Flag indicating whether this drive implements the ATAPI command set. */
+ bool fATAPI;
+ /** Set if this interface has asserted the IRQ. */
+ bool fIrqPending;
+ /** Currently configured number of sectors in a multi-sector transfer. */
+ uint8_t cMultSectors;
+ /** Physical CHS disk geometry (static). */
+ PDMMEDIAGEOMETRY PCHSGeometry;
+ /** Translated CHS disk geometry (variable). */
+ PDMMEDIAGEOMETRY XCHSGeometry;
+ /** Total number of sectors on this disk. */
+ uint64_t cTotalSectors;
+ /** Sector size of the medium. */
+ uint32_t cbSector;
+ /** Number of sectors to transfer per IRQ. */
+ uint32_t cSectorsPerIRQ;
+
+ /** ATA/ATAPI register 1: feature (write-only). */
+ uint8_t uATARegFeature;
+ /** ATA/ATAPI register 1: feature, high order byte. */
+ uint8_t uATARegFeatureHOB;
+ /** ATA/ATAPI register 1: error (read-only). */
+ uint8_t uATARegError;
+ /** ATA/ATAPI register 2: sector count (read/write). */
+ uint8_t uATARegNSector;
+ /** ATA/ATAPI register 2: sector count, high order byte. */
+ uint8_t uATARegNSectorHOB;
+ /** ATA/ATAPI register 3: sector (read/write). */
+ uint8_t uATARegSector;
+ /** ATA/ATAPI register 3: sector, high order byte. */
+ uint8_t uATARegSectorHOB;
+ /** ATA/ATAPI register 4: cylinder low (read/write). */
+ uint8_t uATARegLCyl;
+ /** ATA/ATAPI register 4: cylinder low, high order byte. */
+ uint8_t uATARegLCylHOB;
+ /** ATA/ATAPI register 5: cylinder high (read/write). */
+ uint8_t uATARegHCyl;
+ /** ATA/ATAPI register 5: cylinder high, high order byte. */
+ uint8_t uATARegHCylHOB;
+ /** ATA/ATAPI register 6: select drive/head (read/write). */
+ uint8_t uATARegSelect;
+ /** ATA/ATAPI register 7: status (read-only). */
+ uint8_t uATARegStatus;
+ /** ATA/ATAPI register 7: command (write-only). */
+ uint8_t uATARegCommand;
+ /** ATA/ATAPI drive control register (write-only). */
+ uint8_t uATARegDevCtl;
+
+ /** Currently active transfer mode (MDMA/UDMA) and speed. */
+ uint8_t uATATransferMode;
+ /** Current transfer direction. */
+ uint8_t uTxDir;
+ /** Index of callback for begin transfer. */
+ uint8_t iBeginTransfer;
+ /** Index of callback for source/sink of data. */
+ uint8_t iSourceSink;
+ /** Flag indicating whether the current command transfers data in DMA mode. */
+ bool fDMA;
+ /** Set to indicate that ATAPI transfer semantics must be used. */
+ bool fATAPITransfer;
+
+ /** Total ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbTotalTransfer;
+ /** Elementary ATA/ATAPI transfer size, shared PIO/DMA. */
+ uint32_t cbElementaryTransfer;
+ /** Maximum ATAPI elementary transfer size, PIO only. */
+ uint32_t cbPIOTransferLimit;
+ /** ATAPI passthrough transfer size, shared PIO/DMA */
+ uint32_t cbAtapiPassthroughTransfer;
+ /** Current read/write buffer position, shared PIO/DMA. */
+ uint32_t iIOBufferCur;
+ /** First element beyond end of valid buffer content, shared PIO/DMA. */
+ uint32_t iIOBufferEnd;
+ /** Align the following fields correctly. */
+ uint32_t Alignment0;
+
+ /** ATA/ATAPI current PIO read/write transfer position. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataStart;
+ /** ATA/ATAPI current PIO read/write transfer end. Not shared with DMA for safety reasons. */
+ uint32_t iIOBufferPIODataEnd;
+
+ /** Current LBA position (both ATA/ATAPI). */
+ uint32_t iCurLBA;
+ /** ATAPI current sector size. */
+ uint32_t cbATAPISector;
+ /** ATAPI current command. */
+ uint8_t abATAPICmd[ATAPI_PACKET_SIZE];
+ /** ATAPI sense data. */
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ /** HACK: Countdown till we report a newly unmounted drive as mounted. */
+ uint8_t cNotifiedMediaChange;
+ /** The same for GET_EVENT_STATUS for mechanism */
+ volatile uint32_t MediaEventStatus;
+
+ /** Media type if known. */
+ volatile uint32_t MediaTrackType;
+
+ /** The status LED state for this drive. */
+ PDMLED Led;
+
+ /** Size of I/O buffer. */
+ uint32_t cbIOBuffer;
+
+ /*
+ * No data that is part of the saved state after this point!!!!!
+ */
+
+ /** Counter for number of busy status seen in R3 in a row. */
+ uint8_t cBusyStatusHackR3;
+ /** Counter for number of busy status seen in GC/R0 in a row. */
+ uint8_t cBusyStatusHackRZ;
+ /** Defines the R3 yield rate by a mask (power of 2 minus one).
+ * Lower is more agressive. */
+ uint8_t cBusyStatusHackR3Rate;
+ /** Defines the R0/RC yield rate by a mask (power of 2 minus one).
+ * Lower is more agressive. */
+ uint8_t cBusyStatusHackRZRate;
+
+ /** Release statistics: number of ATA DMA commands. */
+ STAMCOUNTER StatATADMA;
+ /** Release statistics: number of ATA PIO commands. */
+ STAMCOUNTER StatATAPIO;
+ /** Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIDMA;
+ /** Release statistics: number of ATAPI PIO commands. */
+ STAMCOUNTER StatATAPIPIO;
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ /** Release statistics: number of DMA sector writes and the time spent. */
+ STAMPROFILEADV StatInstrVDWrites;
+#endif
+ /** Release statistics: Profiling RTThreadYield calls during status polling. */
+ STAMPROFILEADV StatStatusYields;
+
+ /** Statistics: number of read operations and the time spent reading. */
+ STAMPROFILEADV StatReads;
+ /** Statistics: number of bytes read. */
+ STAMCOUNTER StatBytesRead;
+ /** Statistics: number of write operations and the time spent writing. */
+ STAMPROFILEADV StatWrites;
+ /** Statistics: number of bytes written. */
+ STAMCOUNTER StatBytesWritten;
+ /** Statistics: number of flush operations and the time spend flushing. */
+ STAMPROFILE StatFlushes;
+
+ /** Enable passing through commands directly to the ATAPI drive. */
+ bool fATAPIPassthrough;
+ /** Flag whether to overwrite inquiry data in passthrough mode. */
+ bool fOverwriteInquiry;
+ /** Number of errors we've reported to the release log.
+ * This is to prevent flooding caused by something going horribly wrong.
+ * this value against MAX_LOG_REL_ERRORS in places likely to cause floods
+ * like the ones we currently seeing on the linux smoke tests (2006-11-10). */
+ uint32_t cErrors;
+ /** Timestamp of last started command. 0 if no command pending. */
+ uint64_t u64CmdTS;
+
+ /** The LUN number. */
+ uint32_t iLUN;
+ /** The controller number. */
+ uint8_t iCtl;
+ /** The device number. */
+ uint8_t iDev;
+ /** Set if the device is present. */
+ bool fPresent;
+ /** Explicit alignment. */
+ uint8_t bAlignment2;
+
+ /** The serial number to use for IDENTIFY DEVICE commands. */
+ char szSerialNumber[ATA_SERIAL_NUMBER_LENGTH+1];
+ /** The firmware revision to use for IDENTIFY DEVICE commands. */
+ char szFirmwareRevision[ATA_FIRMWARE_REVISION_LENGTH+1];
+ /** The model number to use for IDENTIFY DEVICE commands. */
+ char szModelNumber[ATA_MODEL_NUMBER_LENGTH+1];
+ /** The vendor identification string for SCSI INQUIRY commands. */
+ char szInquiryVendorId[SCSI_INQUIRY_VENDOR_ID_LENGTH+1];
+ /** The product identification string for SCSI INQUIRY commands. */
+ char szInquiryProductId[SCSI_INQUIRY_PRODUCT_ID_LENGTH+1];
+ /** The revision string for SCSI INQUIRY commands. */
+ char szInquiryRevision[SCSI_INQUIRY_REVISION_LENGTH+1];
+
+ /** Padding the structure to a multiple of 4096 for better I/O buffer alignment. */
+ uint8_t abAlignment4[7 + 3528];
+} ATADEVSTATE;
+AssertCompileMemberAlignment(ATADEVSTATE, cTotalSectors, 8);
+AssertCompileMemberAlignment(ATADEVSTATE, StatATADMA, 8);
+AssertCompileMemberAlignment(ATADEVSTATE, u64CmdTS, 8);
+AssertCompileMemberAlignment(ATADEVSTATE, szSerialNumber, 8);
+AssertCompileSizeAlignment(ATADEVSTATE, 4096); /* To align the buffer on a page boundrary. */
+/** Pointer to the shared state of an ATA device. */
+typedef ATADEVSTATE *PATADEVSTATE;
+
+
+/**
+ * The ring-3 state of an ATA device.
+ *
+ * @implements PDMIBASE
+ * @implements PDMIBLOCKPORT
+ * @implements PDMIMOUNTNOTIFY
+ */
+typedef struct ATADEVSTATER3
+{
+ /** Pointer to the attached driver's base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached driver's block interface. */
+ R3PTRTYPE(PPDMIMEDIA) pDrvMedia;
+ /** Pointer to the attached driver's mount interface.
+ * This is NULL if the driver isn't a removable unit. */
+ R3PTRTYPE(PPDMIMOUNT) pDrvMount;
+ /** The base interface. */
+ PDMIBASE IBase;
+ /** The block port interface. */
+ PDMIMEDIAPORT IPort;
+ /** The mount notify interface. */
+ PDMIMOUNTNOTIFY IMountNotify;
+
+ /** The LUN number. */
+ uint32_t iLUN;
+ /** The controller number. */
+ uint8_t iCtl;
+ /** The device number. */
+ uint8_t iDev;
+ /** Explicit alignment. */
+ uint8_t abAlignment2[2];
+ /** The device instance so we can get our bearings from an interface method. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** The current tracklist of the loaded medium if passthrough is used. */
+ R3PTRTYPE(PTRACKLIST) pTrackList;
+} ATADEVSTATER3;
+/** Pointer to the ring-3 state of an ATA device. */
+typedef ATADEVSTATER3 *PATADEVSTATER3;
+
+
+/**
+ * Transfer request forwarded to the async I/O thread.
+ */
+typedef struct ATATransferRequest
+{
+ /** The interface index the request is for. */
+ uint8_t iIf;
+ /** The index of the begin transfer callback to call. */
+ uint8_t iBeginTransfer;
+ /** The index of the source sink callback to call for doing the transfer. */
+ uint8_t iSourceSink;
+ /** Transfer direction. */
+ uint8_t uTxDir;
+ /** How many bytes to transfer. */
+ uint32_t cbTotalTransfer;
+} ATATransferRequest;
+
+
+/**
+ * Abort request forwarded to the async I/O thread.
+ */
+typedef struct ATAAbortRequest
+{
+ /** The interface index the request is for. */
+ uint8_t iIf;
+ /** Flag whether to reset the drive. */
+ bool fResetDrive;
+} ATAAbortRequest;
+
+
+/**
+ * Request type indicator.
+ */
+typedef enum
+{
+ /** Begin a new transfer. */
+ ATA_AIO_NEW = 0,
+ /** Continue a DMA transfer. */
+ ATA_AIO_DMA,
+ /** Continue a PIO transfer. */
+ ATA_AIO_PIO,
+ /** Reset the drives on current controller, stop all transfer activity. */
+ ATA_AIO_RESET_ASSERTED,
+ /** Reset the drives on current controller, resume operation. */
+ ATA_AIO_RESET_CLEARED,
+ /** Abort the current transfer of a particular drive. */
+ ATA_AIO_ABORT
+} ATAAIO;
+
+
+/**
+ * Combining structure for an ATA request to the async I/O thread
+ * started with the request type insicator.
+ */
+typedef struct ATARequest
+{
+ /** Request type. */
+ ATAAIO ReqType;
+ /** Request type dependent data. */
+ union
+ {
+ /** Transfer request specific data. */
+ ATATransferRequest t;
+ /** Abort request specific data. */
+ ATAAbortRequest a;
+ } u;
+} ATARequest;
+
+
+/**
+ * The shared state of an ATA controller.
+ *
+ * Has two devices, the master (0) and the slave (1).
+ */
+typedef struct ATACONTROLLER
+{
+ /** The ATA/ATAPI interfaces of this controller. */
+ ATADEVSTATE aIfs[2];
+
+ /** The base of the first I/O Port range. */
+ RTIOPORT IOPortBase1;
+ /** The base of the second I/O Port range. (0 if none) */
+ RTIOPORT IOPortBase2;
+ /** The assigned IRQ. */
+ uint32_t irq;
+ /** Access critical section */
+ PDMCRITSECT lock;
+
+ /** Selected drive. */
+ uint8_t iSelectedIf;
+ /** The interface on which to handle async I/O. */
+ uint8_t iAIOIf;
+ /** The state of the async I/O thread. */
+ uint8_t uAsyncIOState;
+ /** Flag indicating whether the next transfer is part of the current command. */
+ bool fChainedTransfer;
+ /** Set when the reset processing is currently active on this controller. */
+ bool fReset;
+ /** Flag whether the current transfer needs to be redone. */
+ bool fRedo;
+ /** Flag whether the redo suspend has been finished. */
+ bool fRedoIdle;
+ /** Flag whether the DMA operation to be redone is the final transfer. */
+ bool fRedoDMALastDesc;
+ /** The BusMaster DMA state. */
+ BMDMAState BmDma;
+ /** Pointer to first DMA descriptor. */
+ RTGCPHYS32 GCPhysFirstDMADesc;
+ /** Pointer to last DMA descriptor. */
+ RTGCPHYS32 GCPhysLastDMADesc;
+ /** Pointer to current DMA buffer (for redo operations). */
+ RTGCPHYS32 GCPhysRedoDMABuffer;
+ /** Size of current DMA buffer (for redo operations). */
+ uint32_t cbRedoDMABuffer;
+
+ /** The event semaphore the thread is waiting on for requests. */
+ SUPSEMEVENT hAsyncIOSem;
+ /** The request queue for the AIO thread. One element is always unused. */
+ ATARequest aAsyncIORequests[4];
+ /** The position at which to insert a new request for the AIO thread. */
+ volatile uint8_t AsyncIOReqHead;
+ /** The position at which to get a new request for the AIO thread. */
+ volatile uint8_t AsyncIOReqTail;
+ /** The controller number. */
+ uint8_t iCtl;
+ /** Magic delay before triggering interrupts in DMA mode. */
+ uint32_t msDelayIRQ;
+ /** The lock protecting the request queue. */
+ PDMCRITSECT AsyncIORequestLock;
+
+ /** Timestamp we started the reset. */
+ uint64_t u64ResetTime;
+
+ /** The first port in the first I/O port range, regular operation. */
+ IOMIOPORTHANDLE hIoPorts1First;
+ /** The other ports in the first I/O port range, regular operation. */
+ IOMIOPORTHANDLE hIoPorts1Other;
+ /** The second I/O port range, regular operation. */
+ IOMIOPORTHANDLE hIoPorts2;
+ /** The first I/O port range, empty controller operation. */
+ IOMIOPORTHANDLE hIoPortsEmpty1;
+ /** The second I/O port range, empty controller operation. */
+ IOMIOPORTHANDLE hIoPortsEmpty2;
+
+ /* Statistics */
+ STAMCOUNTER StatAsyncOps;
+ uint64_t StatAsyncMinWait;
+ uint64_t StatAsyncMaxWait;
+ STAMCOUNTER StatAsyncTimeUS;
+ STAMPROFILEADV StatAsyncTime;
+ STAMPROFILE StatLockWait;
+ uint8_t abAlignment4[3328];
+} ATACONTROLLER;
+AssertCompileMemberAlignment(ATACONTROLLER, lock, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, aIfs, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, u64ResetTime, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, StatAsyncOps, 8);
+AssertCompileMemberAlignment(ATACONTROLLER, AsyncIORequestLock, 8);
+AssertCompileSizeAlignment(ATACONTROLLER, 4096); /* To align the controllers, devices and I/O buffers on page boundaries. */
+/** Pointer to the shared state of an ATA controller. */
+typedef ATACONTROLLER *PATACONTROLLER;
+
+
+/**
+ * The ring-3 state of an ATA controller.
+ */
+typedef struct ATACONTROLLERR3
+{
+ /** The ATA/ATAPI interfaces of this controller. */
+ ATADEVSTATER3 aIfs[2];
+
+ /** Pointer to device instance. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** The async I/O thread handle. NIL_RTTHREAD if no thread. */
+ RTTHREAD hAsyncIOThread;
+ /** The event semaphore the thread is waiting on during suspended I/O. */
+ RTSEMEVENT hSuspendIOSem;
+ /** Set when the destroying the device instance and the thread must exit. */
+ uint32_t volatile fShutdown;
+ /** Whether to call PDMDevHlpAsyncNotificationCompleted when idle. */
+ bool volatile fSignalIdle;
+
+ /** The controller number. */
+ uint8_t iCtl;
+
+ uint8_t abAlignment[3];
+} ATACONTROLLERR3;
+/** Pointer to the ring-3 state of an ATA controller. */
+typedef ATACONTROLLERR3 *PATACONTROLLERR3;
+
+
+/** ATA chipset type. */
+typedef enum CHIPSET
+{
+ /** PIIX3 chipset, must be 0 for saved state compatibility */
+ CHIPSET_PIIX3 = 0,
+ /** PIIX4 chipset, must be 1 for saved state compatibility */
+ CHIPSET_PIIX4,
+ /** ICH6 chipset */
+ CHIPSET_ICH6,
+ CHIPSET_32BIT_HACK=0x7fffffff
+} CHIPSET;
+AssertCompileSize(CHIPSET, 4);
+
+/**
+ * The shared state of a ATA PCI device.
+ */
+typedef struct ATASTATE
+{
+ /** The controllers. */
+ ATACONTROLLER aCts[2];
+ /** Flag indicating chipset being emulated. */
+ CHIPSET enmChipset;
+ /** Explicit alignment padding. */
+ uint8_t abAlignment1[7];
+ /** PCI region \#4: Bus-master DMA I/O ports. */
+ IOMIOPORTHANDLE hIoPortsBmDma;
+} ATASTATE;
+/** Pointer to the shared state of an ATA PCI device. */
+typedef ATASTATE *PATASTATE;
+
+
+/**
+ * The ring-3 state of a ATA PCI device.
+ *
+ * @implements PDMILEDPORTS
+ */
+typedef struct ATASTATER3
+{
+ /** The controllers. */
+ ATACONTROLLERR3 aCts[2];
+ /** Status LUN: Base interface. */
+ PDMIBASE IBase;
+ /** Status LUN: Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Status LUN: Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+ /** Status LUN: Media Notify. */
+ R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify;
+ /** Pointer to device instance (for getting our bearings in interface methods). */
+ PPDMDEVINSR3 pDevIns;
+} ATASTATER3;
+/** Pointer to the ring-3 state of an ATA PCI device. */
+typedef ATASTATER3 *PATASTATER3;
+
+
+/**
+ * The ring-0 state of the ATA PCI device.
+ */
+typedef struct ATASTATER0
+{
+ uint64_t uUnused;
+} ATASTATER0;
+/** Pointer to the ring-0 state of an ATA PCI device. */
+typedef ATASTATER0 *PATASTATER0;
+
+
+/**
+ * The raw-mode state of the ATA PCI device.
+ */
+typedef struct ATASTATERC
+{
+ uint64_t uUnused;
+} ATASTATERC;
+/** Pointer to the raw-mode state of an ATA PCI device. */
+typedef ATASTATERC *PATASTATERC;
+
+
+/** The current context state of an ATA PCI device. */
+typedef CTX_SUFF(ATASTATE) ATASTATECC;
+/** Pointer to the current context state of an ATA PCI device. */
+typedef CTX_SUFF(PATASTATE) PATASTATECC;
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+#ifdef IN_RING3
+DECLINLINE(void) ataSetStatusValue(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t stat)
+{
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus = stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+#endif /* IN_RING3 */
+
+
+DECLINLINE(void) ataSetStatus(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t stat)
+{
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus |= stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+
+
+DECLINLINE(void) ataUnsetStatus(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t stat)
+{
+ /* Freeze status register contents while processing RESET. */
+ if (!pCtl->fReset)
+ {
+ s->uATARegStatus &= ~stat;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
+ }
+}
+
+#if defined(IN_RING3) || defined(IN_RING0)
+
+# ifdef IN_RING3
+typedef void FNBEGINTRANSFER(PATACONTROLLER pCtl, PATADEVSTATE s);
+typedef FNBEGINTRANSFER *PFNBEGINTRANSFER;
+typedef bool FNSOURCESINK(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3);
+typedef FNSOURCESINK *PFNSOURCESINK;
+
+static FNBEGINTRANSFER ataR3ReadWriteSectorsBT;
+static FNBEGINTRANSFER ataR3PacketBT;
+static FNBEGINTRANSFER atapiR3CmdBT;
+static FNBEGINTRANSFER atapiR3PassthroughCmdBT;
+
+static FNSOURCESINK ataR3IdentifySS;
+static FNSOURCESINK ataR3FlushSS;
+static FNSOURCESINK ataR3ReadSectorsSS;
+static FNSOURCESINK ataR3WriteSectorsSS;
+static FNSOURCESINK ataR3ExecuteDeviceDiagnosticSS;
+static FNSOURCESINK ataR3TrimSS;
+static FNSOURCESINK ataR3PacketSS;
+static FNSOURCESINK ataR3InitDevParmSS;
+static FNSOURCESINK ataR3RecalibrateSS;
+static FNSOURCESINK atapiR3GetConfigurationSS;
+static FNSOURCESINK atapiR3GetEventStatusNotificationSS;
+static FNSOURCESINK atapiR3IdentifySS;
+static FNSOURCESINK atapiR3InquirySS;
+static FNSOURCESINK atapiR3MechanismStatusSS;
+static FNSOURCESINK atapiR3ModeSenseErrorRecoverySS;
+static FNSOURCESINK atapiR3ModeSenseCDStatusSS;
+static FNSOURCESINK atapiR3ReadSS;
+static FNSOURCESINK atapiR3ReadCapacitySS;
+static FNSOURCESINK atapiR3ReadDiscInformationSS;
+static FNSOURCESINK atapiR3ReadTOCNormalSS;
+static FNSOURCESINK atapiR3ReadTOCMultiSS;
+static FNSOURCESINK atapiR3ReadTOCRawSS;
+static FNSOURCESINK atapiR3ReadTrackInformationSS;
+static FNSOURCESINK atapiR3RequestSenseSS;
+static FNSOURCESINK atapiR3PassthroughSS;
+static FNSOURCESINK atapiR3ReadDVDStructureSS;
+# endif /* IN_RING3 */
+
+/**
+ * Begin of transfer function indexes for g_apfnBeginTransFuncs.
+ */
+typedef enum ATAFNBT
+{
+ ATAFN_BT_NULL = 0,
+ ATAFN_BT_READ_WRITE_SECTORS,
+ ATAFN_BT_PACKET,
+ ATAFN_BT_ATAPI_CMD,
+ ATAFN_BT_ATAPI_PASSTHROUGH_CMD,
+ ATAFN_BT_MAX
+} ATAFNBT;
+
+# ifdef IN_RING3
+/**
+ * Array of end transfer functions, the index is ATAFNET.
+ * Make sure ATAFNET and this array match!
+ */
+static const PFNBEGINTRANSFER g_apfnBeginTransFuncs[ATAFN_BT_MAX] =
+{
+ NULL,
+ ataR3ReadWriteSectorsBT,
+ ataR3PacketBT,
+ atapiR3CmdBT,
+ atapiR3PassthroughCmdBT,
+};
+# endif /* IN_RING3 */
+
+/**
+ * Source/sink function indexes for g_apfnSourceSinkFuncs.
+ */
+typedef enum ATAFNSS
+{
+ ATAFN_SS_NULL = 0,
+ ATAFN_SS_IDENTIFY,
+ ATAFN_SS_FLUSH,
+ ATAFN_SS_READ_SECTORS,
+ ATAFN_SS_WRITE_SECTORS,
+ ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC,
+ ATAFN_SS_TRIM,
+ ATAFN_SS_PACKET,
+ ATAFN_SS_INITIALIZE_DEVICE_PARAMETERS,
+ ATAFN_SS_RECALIBRATE,
+ ATAFN_SS_ATAPI_GET_CONFIGURATION,
+ ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION,
+ ATAFN_SS_ATAPI_IDENTIFY,
+ ATAFN_SS_ATAPI_INQUIRY,
+ ATAFN_SS_ATAPI_MECHANISM_STATUS,
+ ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY,
+ ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS,
+ ATAFN_SS_ATAPI_READ,
+ ATAFN_SS_ATAPI_READ_CAPACITY,
+ ATAFN_SS_ATAPI_READ_DISC_INFORMATION,
+ ATAFN_SS_ATAPI_READ_TOC_NORMAL,
+ ATAFN_SS_ATAPI_READ_TOC_MULTI,
+ ATAFN_SS_ATAPI_READ_TOC_RAW,
+ ATAFN_SS_ATAPI_READ_TRACK_INFORMATION,
+ ATAFN_SS_ATAPI_REQUEST_SENSE,
+ ATAFN_SS_ATAPI_PASSTHROUGH,
+ ATAFN_SS_ATAPI_READ_DVD_STRUCTURE,
+ ATAFN_SS_MAX
+} ATAFNSS;
+
+# ifdef IN_RING3
+/**
+ * Array of source/sink functions, the index is ATAFNSS.
+ * Make sure ATAFNSS and this array match!
+ */
+static const PFNSOURCESINK g_apfnSourceSinkFuncs[ATAFN_SS_MAX] =
+{
+ NULL,
+ ataR3IdentifySS,
+ ataR3FlushSS,
+ ataR3ReadSectorsSS,
+ ataR3WriteSectorsSS,
+ ataR3ExecuteDeviceDiagnosticSS,
+ ataR3TrimSS,
+ ataR3PacketSS,
+ ataR3InitDevParmSS,
+ ataR3RecalibrateSS,
+ atapiR3GetConfigurationSS,
+ atapiR3GetEventStatusNotificationSS,
+ atapiR3IdentifySS,
+ atapiR3InquirySS,
+ atapiR3MechanismStatusSS,
+ atapiR3ModeSenseErrorRecoverySS,
+ atapiR3ModeSenseCDStatusSS,
+ atapiR3ReadSS,
+ atapiR3ReadCapacitySS,
+ atapiR3ReadDiscInformationSS,
+ atapiR3ReadTOCNormalSS,
+ atapiR3ReadTOCMultiSS,
+ atapiR3ReadTOCRawSS,
+ atapiR3ReadTrackInformationSS,
+ atapiR3RequestSenseSS,
+ atapiR3PassthroughSS,
+ atapiR3ReadDVDStructureSS
+};
+# endif /* IN_RING3 */
+
+
+static const ATARequest g_ataDMARequest = { ATA_AIO_DMA, { { 0, 0, 0, 0, 0 } } };
+static const ATARequest g_ataPIORequest = { ATA_AIO_PIO, { { 0, 0, 0, 0, 0 } } };
+# ifdef IN_RING3
+static const ATARequest g_ataResetARequest = { ATA_AIO_RESET_ASSERTED, { { 0, 0, 0, 0, 0 } } };
+static const ATARequest g_ataResetCRequest = { ATA_AIO_RESET_CLEARED, { { 0, 0, 0, 0, 0 } } };
+# endif
+
+# ifdef IN_RING3
+static void ataR3AsyncIOClearRequests(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ pCtl->AsyncIOReqHead = 0;
+ pCtl->AsyncIOReqTail = 0;
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+# endif /* IN_RING3 */
+
+static void ataHCAsyncIOPutRequest(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, const ATARequest *pReq)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ uint8_t const iAsyncIORequest = pCtl->AsyncIOReqHead % RT_ELEMENTS(pCtl->aAsyncIORequests);
+ Assert((iAsyncIORequest + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests) != pCtl->AsyncIOReqTail);
+ memcpy(&pCtl->aAsyncIORequests[iAsyncIORequest], pReq, sizeof(*pReq));
+ pCtl->AsyncIOReqHead = (iAsyncIORequest + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests);
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+
+ rc = PDMDevHlpCritSectScheduleExitEvent(pDevIns, &pCtl->lock, pCtl->hAsyncIOSem);
+ if (RT_FAILURE(rc))
+ {
+ rc = PDMDevHlpSUPSemEventSignal(pDevIns, pCtl->hAsyncIOSem);
+ AssertRC(rc);
+ }
+}
+
+# ifdef IN_RING3
+
+static const ATARequest *ataR3AsyncIOGetCurrentRequest(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ const ATARequest *pReq;
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail)
+ pReq = &pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail];
+ else
+ pReq = NULL;
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+ return pReq;
+}
+
+
+/**
+ * Remove the request with the given type, as it's finished. The request
+ * is not removed blindly, as this could mean a RESET request that is not
+ * yet processed (but has cleared the request queue) is lost.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl Controller for which to remove the request.
+ * @param ReqType Type of the request to remove.
+ */
+static void ataR3AsyncIORemoveCurrentRequest(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, ATAAIO ReqType)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail && pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail].ReqType == ReqType)
+ {
+ pCtl->AsyncIOReqTail++;
+ pCtl->AsyncIOReqTail %= RT_ELEMENTS(pCtl->aAsyncIORequests);
+ }
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+
+
+/**
+ * Dump the request queue for a particular controller. First dump the queue
+ * contents, then the already processed entries, as long as they haven't been
+ * overwritten.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl Controller for which to dump the queue.
+ */
+static void ataR3AsyncIODumpRequests(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ LogRel(("PIIX3 ATA: Ctl#%d: request queue dump (topmost is current):\n", pCtl->iCtl));
+ uint8_t curr = pCtl->AsyncIOReqTail;
+ do
+ {
+ if (curr == pCtl->AsyncIOReqHead)
+ LogRel(("PIIX3 ATA: Ctl#%d: processed requests (topmost is oldest):\n", pCtl->iCtl));
+ switch (pCtl->aAsyncIORequests[curr].ReqType)
+ {
+ case ATA_AIO_NEW:
+ LogRel(("new transfer request, iIf=%d iBeginTransfer=%d iSourceSink=%d cbTotalTransfer=%d uTxDir=%d\n",
+ pCtl->aAsyncIORequests[curr].u.t.iIf, pCtl->aAsyncIORequests[curr].u.t.iBeginTransfer,
+ pCtl->aAsyncIORequests[curr].u.t.iSourceSink, pCtl->aAsyncIORequests[curr].u.t.cbTotalTransfer,
+ pCtl->aAsyncIORequests[curr].u.t.uTxDir));
+ break;
+ case ATA_AIO_DMA:
+ LogRel(("dma transfer continuation\n"));
+ break;
+ case ATA_AIO_PIO:
+ LogRel(("pio transfer continuation\n"));
+ break;
+ case ATA_AIO_RESET_ASSERTED:
+ LogRel(("reset asserted request\n"));
+ break;
+ case ATA_AIO_RESET_CLEARED:
+ LogRel(("reset cleared request\n"));
+ break;
+ case ATA_AIO_ABORT:
+ LogRel(("abort request, iIf=%d fResetDrive=%d\n", pCtl->aAsyncIORequests[curr].u.a.iIf,
+ pCtl->aAsyncIORequests[curr].u.a.fResetDrive));
+ break;
+ default:
+ LogRel(("unknown request %d\n", pCtl->aAsyncIORequests[curr].ReqType));
+ }
+ curr = (curr + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests);
+ } while (curr != pCtl->AsyncIOReqTail);
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+
+
+/**
+ * Checks whether the request queue for a particular controller is empty
+ * or whether a particular controller is idle.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl Controller for which to check the queue.
+ * @param fStrict If set then the controller is checked to be idle.
+ */
+static bool ataR3AsyncIOIsIdle(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, bool fStrict)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ bool fIdle = pCtl->fRedoIdle;
+ if (!fIdle)
+ fIdle = (pCtl->AsyncIOReqHead == pCtl->AsyncIOReqTail);
+ if (fStrict)
+ fIdle &= (pCtl->uAsyncIOState == ATA_AIO_NEW);
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+ return fIdle;
+}
+
+
+/**
+ * Send a transfer request to the async I/O thread.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @param s Pointer to the ATA device state data.
+ * @param cbTotalTransfer Data transfer size.
+ * @param uTxDir Data transfer direction.
+ * @param iBeginTransfer Index of BeginTransfer callback.
+ * @param iSourceSink Index of SourceSink callback.
+ * @param fChainedTransfer Whether this is a transfer that is part of the previous command/transfer.
+ */
+static void ataR3StartTransfer(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s,
+ uint32_t cbTotalTransfer, uint8_t uTxDir, ATAFNBT iBeginTransfer,
+ ATAFNSS iSourceSink, bool fChainedTransfer)
+{
+ ATARequest Req;
+
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pCtl->lock));
+
+ /* Do not issue new requests while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed new request as RESET is active\n", __FUNCTION__, pCtl->iCtl));
+ return;
+ }
+
+ /* If the controller is already doing something else right now, ignore
+ * the command that is being submitted. Some broken guests issue commands
+ * twice (e.g. the Linux kernel that comes with Acronis True Image 8). */
+ if (!fChainedTransfer && !ataR3AsyncIOIsIdle(pDevIns, pCtl, true /*fStrict*/))
+ {
+ Log(("%s: Ctl#%d: ignored command %#04x, controller state %d\n", __FUNCTION__, pCtl->iCtl, s->uATARegCommand, pCtl->uAsyncIOState));
+ LogRel(("PIIX3 IDE: guest issued command %#04x while controller busy\n", s->uATARegCommand));
+ return;
+ }
+
+ Req.ReqType = ATA_AIO_NEW;
+ if (fChainedTransfer)
+ Req.u.t.iIf = pCtl->iAIOIf;
+ else
+ Req.u.t.iIf = pCtl->iSelectedIf;
+ Req.u.t.cbTotalTransfer = cbTotalTransfer;
+ Req.u.t.uTxDir = uTxDir;
+ Req.u.t.iBeginTransfer = iBeginTransfer;
+ Req.u.t.iSourceSink = iSourceSink;
+ ataSetStatusValue(pCtl, s, ATA_STAT_BUSY);
+ pCtl->fChainedTransfer = fChainedTransfer;
+
+ /*
+ * Kick the worker thread into action.
+ */
+ Log2(("%s: Ctl#%d: message to async I/O thread, new request\n", __FUNCTION__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &Req);
+}
+
+
+/**
+ * Send an abort command request to the async I/O thread.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @param s Pointer to the ATA device state data.
+ * @param fResetDrive Whether to reset the drive or just abort a command.
+ */
+static void ataR3AbortCurrentCommand(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, bool fResetDrive)
+{
+ ATARequest Req;
+
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pCtl->lock));
+
+ /* Do not issue new requests while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed aborting command as RESET is active\n", __FUNCTION__, pCtl->iCtl));
+ return;
+ }
+
+ Req.ReqType = ATA_AIO_ABORT;
+ Req.u.a.iIf = pCtl->iSelectedIf;
+ Req.u.a.fResetDrive = fResetDrive;
+ ataSetStatus(pCtl, s, ATA_STAT_BUSY);
+ Log2(("%s: Ctl#%d: message to async I/O thread, abort command on LUN#%d\n", __FUNCTION__, pCtl->iCtl, s->iLUN));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &Req);
+}
+
+# endif /* IN_RING3 */
+
+/**
+ * Set the internal interrupt pending status, update INTREQ as appropriate.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @param s Pointer to the ATA device state data.
+ */
+static void ataHCSetIRQ(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ if (!s->fIrqPending)
+ {
+ if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d asserting IRQ\n", __FUNCTION__, s->iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if the interrupt
+ * line is asserted. It monitors the line for a rising edge. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ /* Only actually set the IRQ line if updating the currently selected drive. */
+ if (s == &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK])
+ {
+ /** @todo experiment with adaptive IRQ delivery: for reads it is
+ * better to wait for IRQ delivery, as it reduces latency. */
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 1);
+ }
+ }
+ s->fIrqPending = true;
+ }
+}
+
+#endif /* IN_RING0 || IN_RING3 */
+
+/**
+ * Clear the internal interrupt pending status, update INTREQ as appropriate.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The ATA controller.
+ * @param s Pointer to the ATA device state data.
+ */
+static void ataUnsetIRQ(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ if (s->fIrqPending)
+ {
+ if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d deasserting IRQ\n", __FUNCTION__, s->iLUN));
+ /* Only actually unset the IRQ line if updating the currently selected drive. */
+ if (s == &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK])
+ {
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 0);
+ }
+ }
+ s->fIrqPending = false;
+ }
+}
+
+#if defined(IN_RING0) || defined(IN_RING3)
+
+static void ataHCPIOTransferStart(PATACONTROLLER pCtl, PATADEVSTATE s, uint32_t start, uint32_t size)
+{
+ Log2(("%s: LUN#%d start %d size %d\n", __FUNCTION__, s->iLUN, start, size));
+ s->iIOBufferPIODataStart = start;
+ s->iIOBufferPIODataEnd = start + size;
+ ataSetStatus(pCtl, s, ATA_STAT_DRQ | ATA_STAT_SEEK);
+ ataUnsetStatus(pCtl, s, ATA_STAT_BUSY);
+}
+
+
+static void ataHCPIOTransferStop(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ Log2(("%s: LUN#%d\n", __FUNCTION__, s->iLUN));
+ if (s->fATAPITransfer)
+ {
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ s->fATAPITransfer = false;
+ }
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferPIODataStart = 0;
+ s->iIOBufferPIODataEnd = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+static void ataHCPIOTransferLimitATAPI(PATADEVSTATE s)
+{
+ uint32_t cbLimit, cbTransfer;
+
+ cbLimit = s->cbPIOTransferLimit;
+ /* Use maximum transfer size if the guest requested 0. Avoids a hang. */
+ if (cbLimit == 0)
+ cbLimit = 0xfffe;
+ Log2(("%s: byte count limit=%d\n", __FUNCTION__, cbLimit));
+ if (cbLimit == 0xffff)
+ cbLimit--;
+ cbTransfer = RT_MIN(s->cbTotalTransfer, s->iIOBufferEnd - s->iIOBufferCur);
+ if (cbTransfer > cbLimit)
+ {
+ /* Byte count limit for clipping must be even in this case */
+ if (cbLimit & 1)
+ cbLimit--;
+ cbTransfer = cbLimit;
+ }
+ s->uATARegLCyl = cbTransfer;
+ s->uATARegHCyl = cbTransfer >> 8;
+ s->cbElementaryTransfer = cbTransfer;
+}
+
+# ifdef IN_RING3
+
+/**
+ * Enters the lock protecting the controller data against concurrent access.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The controller to lock.
+ */
+DECLINLINE(void) ataR3LockEnter(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ STAM_PROFILE_START(&pCtl->StatLockWait, a);
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->lock, rcLock);
+ STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
+}
+
+/**
+ * Leaves the lock protecting the controller against concurrent data access.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The controller to unlock.
+ */
+DECLINLINE(void) ataR3LockLeave(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+}
+
+static uint32_t ataR3GetNSectors(PATADEVSTATE s)
+{
+ /* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
+ if (s->fLBA48)
+ {
+ if (!s->uATARegNSector && !s->uATARegNSectorHOB)
+ return 65536;
+ else
+ return s->uATARegNSectorHOB << 8 | s->uATARegNSector;
+ }
+ else
+ {
+ if (!s->uATARegNSector)
+ return 256;
+ else
+ return s->uATARegNSector;
+ }
+}
+
+
+static void ataR3PadString(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
+{
+ for (uint32_t i = 0; i < cbSize; i++)
+ {
+ if (*pbSrc)
+ pbDst[i ^ 1] = *pbSrc++;
+ else
+ pbDst[i ^ 1] = ' ';
+ }
+}
+
+
+#if 0 /* unused */
+/**
+ * Compares two MSF values.
+ *
+ * @returns 1 if the first value is greater than the second value.
+ * 0 if both are equal
+ * -1 if the first value is smaller than the second value.
+ */
+DECLINLINE(int) atapiCmpMSF(const uint8_t *pbMSF1, const uint8_t *pbMSF2)
+{
+ int iRes = 0;
+
+ for (unsigned i = 0; i < 3; i++)
+ {
+ if (pbMSF1[i] < pbMSF2[i])
+ {
+ iRes = -1;
+ break;
+ }
+ else if (pbMSF1[i] > pbMSF2[i])
+ {
+ iRes = 1;
+ break;
+ }
+ }
+
+ return iRes;
+}
+#endif /* unused */
+
+static void ataR3CmdOK(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t status)
+{
+ s->uATARegError = 0; /* Not needed by ATA spec, but cannot hurt. */
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | status);
+}
+
+
+static void ataR3CmdError(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t uErrorCode)
+{
+ Log(("%s: code=%#x\n", __FUNCTION__, uErrorCode));
+ Assert(uErrorCode);
+ s->uATARegError = uErrorCode;
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_SEEK | ATA_STAT_ERR);
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+static uint32_t ataR3Checksum(void* ptr, size_t count)
+{
+ uint8_t u8Sum = 0xa5, *p = (uint8_t*)ptr;
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ {
+ u8Sum += *p++;
+ }
+
+ return (uint8_t)-(int32_t)u8Sum;
+}
+
+/**
+ * Sink/Source: IDENTIFY
+ */
+static bool ataR3IdentifySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint16_t *p;
+ RT_NOREF(pDevIns);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)&s->abIOBuffer[0];
+ memset(p, 0, 512);
+ p[0] = RT_H2LE_U16(0x0040);
+ p[1] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
+ p[3] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
+ /* Block size; obsolete, but required for the BIOS. */
+ p[5] = RT_H2LE_U16(s->cbSector);
+ p[6] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
+ ataR3PadString((uint8_t *)(p + 10), s->szSerialNumber, ATA_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ p[22] = RT_H2LE_U16(0); /* ECC bytes per sector */
+ ataR3PadString((uint8_t *)(p + 23), s->szFirmwareRevision, ATA_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataR3PadString((uint8_t *)(p + 27), s->szModelNumber, ATA_MODEL_NUMBER_LENGTH); /* model */
+# if ATA_MAX_MULT_SECTORS > 1
+ p[47] = RT_H2LE_U16(0x8000 | ATA_MAX_MULT_SECTORS);
+# endif
+ p[48] = RT_H2LE_U16(1); /* dword I/O, used by the BIOS */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 | 1 << 1 | 1 << 2); /* words 54-58,64-70,88 valid */
+ p[54] = RT_H2LE_U16(RT_MIN(s->XCHSGeometry.cCylinders, 16383));
+ p[55] = RT_H2LE_U16(s->XCHSGeometry.cHeads);
+ p[56] = RT_H2LE_U16(s->XCHSGeometry.cSectors);
+ p[57] = RT_H2LE_U16( RT_MIN(s->XCHSGeometry.cCylinders, 16383)
+ * s->XCHSGeometry.cHeads
+ * s->XCHSGeometry.cSectors);
+ p[58] = RT_H2LE_U16( RT_MIN(s->XCHSGeometry.cCylinders, 16383)
+ * s->XCHSGeometry.cHeads
+ * s->XCHSGeometry.cSectors >> 16);
+ if (s->cMultSectors)
+ p[59] = RT_H2LE_U16(0x100 | s->cMultSectors);
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ {
+ p[60] = RT_H2LE_U16(s->cTotalSectors);
+ p[61] = RT_H2LE_U16(s->cTotalSectors >> 16);
+ }
+ else
+ {
+ /* Report maximum number of sectors possible with LBA28 */
+ p[60] = RT_H2LE_U16(((1 << 28) - 1) & 0xffff);
+ p[61] = RT_H2LE_U16(((1 << 28) - 1) >> 16);
+ }
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ if ( pDevR3->pDrvMedia->pfnDiscard
+ || s->cbSector != 512
+ || pDevR3->pDrvMedia->pfnIsNonRotational(pDevR3->pDrvMedia))
+ {
+ p[80] = RT_H2LE_U16(0x1f0); /* support everything up to ATA/ATAPI-8 ACS */
+ p[81] = RT_H2LE_U16(0x28); /* conforms to ATA/ATAPI-8 ACS */
+ }
+ else
+ {
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ }
+ p[82] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* supports power management, write cache and look-ahead */
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ p[83] = RT_H2LE_U16(1 << 14 | 1 << 12); /* supports FLUSH CACHE */
+ else
+ p[83] = RT_H2LE_U16(1 << 14 | 1 << 10 | 1 << 12 | 1 << 13); /* supports LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* enabled power management, write cache and look-ahead */
+ if (s->cTotalSectors <= (1 << 28) - 1)
+ p[86] = RT_H2LE_U16(1 << 12); /* enabled FLUSH CACHE */
+ else
+ p[86] = RT_H2LE_U16(1 << 10 | 1 << 12 | 1 << 13); /* enabled LBA48, FLUSH CACHE and FLUSH CACHE EXT */
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+ if (s->cTotalSectors > (1 << 28) - 1)
+ {
+ p[100] = RT_H2LE_U16(s->cTotalSectors);
+ p[101] = RT_H2LE_U16(s->cTotalSectors >> 16);
+ p[102] = RT_H2LE_U16(s->cTotalSectors >> 32);
+ p[103] = RT_H2LE_U16(s->cTotalSectors >> 48);
+ }
+
+ if (s->cbSector != 512)
+ {
+ uint32_t cSectorSizeInWords = s->cbSector / sizeof(uint16_t);
+ /* Enable reporting of logical sector size. */
+ p[106] |= RT_H2LE_U16(RT_BIT(12) | RT_BIT(14));
+ p[117] = RT_H2LE_U16(cSectorSizeInWords);
+ p[118] = RT_H2LE_U16(cSectorSizeInWords >> 16);
+ }
+
+ if (pDevR3->pDrvMedia->pfnDiscard) /** @todo Set bit 14 in word 69 too? (Deterministic read after TRIM). */
+ p[169] = RT_H2LE_U16(1); /* DATA SET MANAGEMENT command supported. */
+ if (pDevR3->pDrvMedia->pfnIsNonRotational(pDevR3->pDrvMedia))
+ p[217] = RT_H2LE_U16(1); /* Non-rotational medium */
+ uint32_t uCsum = ataR3Checksum(p, 510);
+ p[255] = RT_H2LE_U16(0xa5 | (uCsum << 8)); /* Integrity word */
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+/**
+ * Sink/Source: FLUSH
+ */
+static bool ataR3FlushSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_NONE);
+ Assert(!s->cbElementaryTransfer);
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ STAM_PROFILE_START(&s->StatFlushes, f);
+ rc = pDevR3->pDrvMedia->pfnFlush(pDevR3->pDrvMedia);
+ AssertRC(rc);
+ STAM_PROFILE_STOP(&s->StatFlushes, f);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdOK(pCtl, s, 0);
+ return false;
+}
+
+/**
+ * Sink/Source: ATAPI IDENTIFY
+ */
+static bool atapiR3IdentifySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint16_t *p;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer == 512);
+
+ p = (uint16_t *)&s->abIOBuffer[0];
+ memset(p, 0, 512);
+ /* Removable CDROM, 3ms response, 12 byte packets */
+ p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 0 << 5 | 0 << 0);
+ ataR3PadString((uint8_t *)(p + 10), s->szSerialNumber, ATA_SERIAL_NUMBER_LENGTH); /* serial number */
+ p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
+ p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
+ ataR3PadString((uint8_t *)(p + 23), s->szFirmwareRevision, ATA_FIRMWARE_REVISION_LENGTH); /* firmware version */
+ ataR3PadString((uint8_t *)(p + 27), s->szModelNumber, ATA_MODEL_NUMBER_LENGTH); /* model */
+ p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
+ p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
+ p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
+ p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
+ p[53] = RT_H2LE_U16(1 << 1 | 1 << 2); /* words 64-70,88 are valid */
+ p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
+ p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
+ p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
+ p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
+ p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
+ p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
+ p[73] = RT_H2LE_U16(0x003e); /* ATAPI CDROM major */
+ p[74] = RT_H2LE_U16(9); /* ATAPI CDROM minor */
+ p[75] = RT_H2LE_U16(1); /* queue depth 1 */
+ p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
+ p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
+ p[82] = RT_H2LE_U16(1 << 4 | 1 << 9); /* supports packet command set and DEVICE RESET */
+ p[83] = RT_H2LE_U16(1 << 14);
+ p[84] = RT_H2LE_U16(1 << 14);
+ p[85] = RT_H2LE_U16(1 << 4 | 1 << 9); /* enabled packet command set and DEVICE RESET */
+ p[86] = RT_H2LE_U16(0);
+ p[87] = RT_H2LE_U16(1 << 14);
+ p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
+ p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
+ /* According to ATAPI-5 spec:
+ *
+ * The use of this word is optional.
+ * If bits 7:0 of this word contain the signature A5h, bits 15:8
+ * contain the data
+ * structure checksum.
+ * The data structure checksum is the twos complement of the sum of
+ * all bytes in words 0 through 254 and the byte consisting of
+ * bits 7:0 in word 255.
+ * Each byte shall be added with unsigned arithmetic,
+ * and overflow shall be ignored.
+ * The sum of all 512 bytes is zero when the checksum is correct.
+ */
+ uint32_t uCsum = ataR3Checksum(p, 510);
+ p[255] = RT_H2LE_U16(0xa5 | (uCsum << 8)); /* Integrity word */
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static void ataR3SetSignature(PATADEVSTATE s)
+{
+ s->uATARegSelect &= 0xf0; /* clear head */
+ /* put signature */
+ s->uATARegNSector = 1;
+ s->uATARegSector = 1;
+ if (s->fATAPI)
+ {
+ s->uATARegLCyl = 0x14;
+ s->uATARegHCyl = 0xeb;
+ }
+ else
+ {
+ s->uATARegLCyl = 0;
+ s->uATARegHCyl = 0;
+ }
+}
+
+
+static uint64_t ataR3GetSector(PATADEVSTATE s)
+{
+ uint64_t iLBA;
+ if (s->uATARegSelect & 0x40)
+ {
+ /* any LBA variant */
+ if (s->fLBA48)
+ {
+ /* LBA48 */
+ iLBA = ((uint64_t)s->uATARegHCylHOB << 40)
+ | ((uint64_t)s->uATARegLCylHOB << 32)
+ | ((uint64_t)s->uATARegSectorHOB << 24)
+ | ((uint64_t)s->uATARegHCyl << 16)
+ | ((uint64_t)s->uATARegLCyl << 8)
+ | s->uATARegSector;
+ }
+ else
+ {
+ /* LBA */
+ iLBA = ((uint32_t)(s->uATARegSelect & 0x0f) << 24)
+ | ((uint32_t)s->uATARegHCyl << 16)
+ | ((uint32_t)s->uATARegLCyl << 8)
+ | s->uATARegSector;
+ }
+ }
+ else
+ {
+ /* CHS */
+ iLBA = (((uint32_t)s->uATARegHCyl << 8) | s->uATARegLCyl) * s->XCHSGeometry.cHeads * s->XCHSGeometry.cSectors
+ + (s->uATARegSelect & 0x0f) * s->XCHSGeometry.cSectors
+ + (s->uATARegSector - 1);
+ LogFlowFunc(("CHS %u/%u/%u -> LBA %llu\n", ((uint32_t)s->uATARegHCyl << 8) | s->uATARegLCyl, s->uATARegSelect & 0x0f, s->uATARegSector, iLBA));
+ }
+ return iLBA;
+}
+
+static void ataR3SetSector(PATADEVSTATE s, uint64_t iLBA)
+{
+ uint32_t cyl, r;
+ if (s->uATARegSelect & 0x40)
+ {
+ /* any LBA variant */
+ if (s->fLBA48)
+ {
+ /* LBA48 */
+ s->uATARegHCylHOB = iLBA >> 40;
+ s->uATARegLCylHOB = iLBA >> 32;
+ s->uATARegSectorHOB = iLBA >> 24;
+ s->uATARegHCyl = iLBA >> 16;
+ s->uATARegLCyl = iLBA >> 8;
+ s->uATARegSector = iLBA;
+ }
+ else
+ {
+ /* LBA */
+ s->uATARegSelect = (s->uATARegSelect & 0xf0) | (iLBA >> 24);
+ s->uATARegHCyl = (iLBA >> 16);
+ s->uATARegLCyl = (iLBA >> 8);
+ s->uATARegSector = (iLBA);
+ }
+ }
+ else
+ {
+ /* CHS */
+ AssertMsgReturnVoid(s->XCHSGeometry.cHeads && s->XCHSGeometry.cSectors, ("Device geometry not set!\n"));
+ cyl = iLBA / (s->XCHSGeometry.cHeads * s->XCHSGeometry.cSectors);
+ r = iLBA % (s->XCHSGeometry.cHeads * s->XCHSGeometry.cSectors);
+ s->uATARegHCyl = cyl >> 8;
+ s->uATARegLCyl = cyl;
+ s->uATARegSelect = (s->uATARegSelect & 0xf0) | ((r / s->XCHSGeometry.cSectors) & 0x0f);
+ s->uATARegSector = (r % s->XCHSGeometry.cSectors) + 1;
+ LogFlowFunc(("LBA %llu -> CHS %u/%u/%u\n", iLBA, cyl, s->uATARegSelect & 0x0f, s->uATARegSector));
+ }
+}
+
+
+static void ataR3WarningDiskFull(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: Host disk full\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_DISKFULL",
+ N_("Host system reported disk full. VM execution is suspended. You can resume after freeing some space"));
+ AssertRC(rc);
+}
+
+static void ataR3WarningFileTooBig(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: File too big\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_FILETOOBIG",
+ N_("Host system reported that the file size limit of the host file system has been exceeded. VM execution is suspended. You need to move your virtual hard disk to a filesystem which allows bigger files"));
+ AssertRC(rc);
+}
+
+static void ataR3WarningISCSI(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: iSCSI target unavailable\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_ISCSIDOWN",
+ N_("The iSCSI target has stopped responding. VM execution is suspended. You can resume when it is available again"));
+ AssertRC(rc);
+}
+
+static void ataR3WarningFileStale(PPDMDEVINS pDevIns)
+{
+ int rc;
+ LogRel(("PIIX3 ATA: File handle became stale\n"));
+ rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevATA_FILESTALE",
+ N_("The file became stale (often due to a restarted NFS server). VM execution is suspended. You can resume when it is available again"));
+ AssertRC(rc);
+}
+
+
+static bool ataR3IsRedoSetWarning(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, int rc)
+{
+ Assert(!PDMDevHlpCritSectIsOwner(pDevIns, &pCtl->lock));
+ if (rc == VERR_DISK_FULL)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningDiskFull(pDevIns);
+ return true;
+ }
+ if (rc == VERR_FILE_TOO_BIG)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningFileTooBig(pDevIns);
+ return true;
+ }
+ if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
+ {
+ pCtl->fRedoIdle = true;
+ /* iSCSI connection abort (first error) or failure to reestablish
+ * connection (second error). Pause VM. On resume we'll retry. */
+ ataR3WarningISCSI(pDevIns);
+ return true;
+ }
+ if (rc == VERR_STALE_FILE_HANDLE)
+ {
+ pCtl->fRedoIdle = true;
+ ataR3WarningFileStale(pDevIns);
+ return true;
+ }
+ if (rc == VERR_VD_DEK_MISSING)
+ {
+ /* Error message already set. */
+ pCtl->fRedoIdle = true;
+ return true;
+ }
+
+ return false;
+}
+
+
+static int ataR3ReadSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3,
+ uint64_t u64Sector, void *pvBuf, uint32_t cSectors, bool *pfRedo)
+{
+ int rc;
+ uint32_t const cbSector = s->cbSector;
+ uint32_t cbToRead = cSectors * cbSector;
+ Assert(pvBuf == &s->abIOBuffer[0]);
+ AssertReturnStmt(cbToRead <= sizeof(s->abIOBuffer), *pfRedo = false, VERR_BUFFER_OVERFLOW);
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ STAM_PROFILE_ADV_START(&s->StatReads, r);
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ rc = pDevR3->pDrvMedia->pfnRead(pDevR3->pDrvMedia, u64Sector * cbSector, pvBuf, cbToRead);
+ s->Led.Actual.s.fReading = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+ Log4(("ataR3ReadSectors: rc=%Rrc cSectors=%#x u64Sector=%llu\n%.*Rhxd\n",
+ rc, cSectors, u64Sector, cbToRead, pvBuf));
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cbToRead);
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(pDevIns, pCtl, rc);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ return rc;
+}
+
+
+static int ataR3WriteSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3,
+ uint64_t u64Sector, const void *pvBuf, uint32_t cSectors, bool *pfRedo)
+{
+ int rc;
+ uint32_t const cbSector = s->cbSector;
+ uint32_t cbToWrite = cSectors * cbSector;
+ Assert(pvBuf == &s->abIOBuffer[0]);
+ AssertReturnStmt(cbToWrite <= sizeof(s->abIOBuffer), *pfRedo = false, VERR_BUFFER_OVERFLOW);
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ STAM_PROFILE_ADV_START(&s->StatWrites, w);
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+# ifdef VBOX_INSTRUMENT_DMA_WRITES
+ if (s->fDMA)
+ STAM_PROFILE_ADV_START(&s->StatInstrVDWrites, vw);
+# endif
+ rc = pDevR3->pDrvMedia->pfnWrite(pDevR3->pDrvMedia, u64Sector * cbSector, pvBuf, cbToWrite);
+# ifdef VBOX_INSTRUMENT_DMA_WRITES
+ if (s->fDMA)
+ STAM_PROFILE_ADV_STOP(&s->StatInstrVDWrites, vw);
+# endif
+ s->Led.Actual.s.fWriting = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatWrites, w);
+ Log4(("ataR3WriteSectors: rc=%Rrc cSectors=%#x u64Sector=%llu\n%.*Rhxd\n",
+ rc, cSectors, u64Sector, cbToWrite, pvBuf));
+
+ STAM_REL_COUNTER_ADD(&s->StatBytesWritten, cbToWrite);
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(pDevIns, pCtl, rc);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ return rc;
+}
+
+
+/**
+ * Begin Transfer: READ/WRITE SECTORS
+ */
+static void ataR3ReadWriteSectorsBT(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ uint32_t const cbSector = RT_MAX(s->cbSector, 1);
+ uint32_t cSectors;
+
+ cSectors = s->cbTotalTransfer / cbSector;
+ if (cSectors > s->cSectorsPerIRQ)
+ s->cbElementaryTransfer = s->cSectorsPerIRQ * cbSector;
+ else
+ s->cbElementaryTransfer = cSectors * cbSector;
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ ataR3CmdOK(pCtl, s, 0);
+}
+
+
+/**
+ * Sink/Source: READ SECTORS
+ */
+static bool ataR3ReadSectorsSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint32_t const cbSector = RT_MAX(s->cbSector, 1);
+ uint32_t cSectors;
+ uint64_t iLBA;
+ bool fRedo;
+ int rc;
+
+ cSectors = s->cbElementaryTransfer / cbSector;
+ Assert(cSectors);
+ iLBA = s->iCurLBA;
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataR3ReadSectors(pDevIns, pCtl, s, pDevR3, iLBA, s->abIOBuffer, cSectors, &fRedo);
+ if (RT_SUCCESS(rc))
+ {
+ /* When READ SECTORS etc. finishes, the address in the task
+ * file register points at the last sector read, not at the next
+ * sector that would be read. This ensures the registers always
+ * contain a valid sector address.
+ */
+ if (s->cbElementaryTransfer == s->cbTotalTransfer)
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3SetSector(s, iLBA + cSectors - 1);
+ }
+ else
+ ataR3SetSector(s, iLBA + cSectors);
+ s->uATARegNSector -= cSectors;
+ s->iCurLBA += cSectors;
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (fRedo)
+ return fRedo;
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk read error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, iLBA, cSectors));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataR3CmdError(pCtl, s, ID_ERR);
+ }
+ return false;
+}
+
+
+/**
+ * Sink/Source: WRITE SECTOR
+ */
+static bool ataR3WriteSectorsSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint32_t const cbSector = RT_MAX(s->cbSector, 1);
+ uint64_t iLBA;
+ uint32_t cSectors;
+ bool fRedo;
+ int rc;
+
+ cSectors = s->cbElementaryTransfer / cbSector;
+ Assert(cSectors);
+ iLBA = s->iCurLBA;
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
+ rc = ataR3WriteSectors(pDevIns, pCtl, s, pDevR3, iLBA, s->abIOBuffer, cSectors, &fRedo);
+ if (RT_SUCCESS(rc))
+ {
+ ataR3SetSector(s, iLBA + cSectors);
+ s->iCurLBA = iLBA + cSectors;
+ if (!s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (fRedo)
+ return fRedo;
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk write error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, iLBA, cSectors));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataR3CmdError(pCtl, s, ID_ERR);
+ }
+ return false;
+}
+
+
+static void atapiR3CmdOK(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ s->uATARegError = 0;
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY);
+ s->uATARegNSector = (s->uATARegNSector & ~7)
+ | ((s->uTxDir != PDMMEDIATXDIR_TO_DEVICE) ? ATAPI_INT_REASON_IO : 0)
+ | (!s->cbTotalTransfer ? ATAPI_INT_REASON_CD : 0);
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+
+ memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+ s->abATAPISense[0] = 0x70 | (1 << 7);
+ s->abATAPISense[7] = 10;
+}
+
+
+static void atapiR3CmdError(PATACONTROLLER pCtl, PATADEVSTATE s, const uint8_t *pabATAPISense, size_t cbATAPISense)
+{
+ Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
+ pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
+ s->uATARegError = pabATAPISense[2] << 4;
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_ERR);
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
+ memcpy(s->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(s->abATAPISense)));
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/** @todo deprecated function - doesn't provide enough info. Replace by direct
+ * calls to atapiR3CmdError() with full data. */
+static void atapiR3CmdErrorSimple(PATACONTROLLER pCtl, PATADEVSTATE s, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
+{
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ memset(abATAPISense, '\0', sizeof(abATAPISense));
+ abATAPISense[0] = 0x70 | (1 << 7);
+ abATAPISense[2] = uATAPISenseKey & 0x0f;
+ abATAPISense[7] = 10;
+ abATAPISense[12] = uATAPIASC;
+ atapiR3CmdError(pCtl, s, abATAPISense, sizeof(abATAPISense));
+}
+
+
+/**
+ * Begin Transfer: ATAPI command
+ */
+static void atapiR3CmdBT(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ s->fATAPITransfer = true;
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->cbAtapiPassthroughTransfer = s->cbTotalTransfer;
+ s->cbPIOTransferLimit = s->uATARegLCyl | (s->uATARegHCyl << 8);
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ atapiR3CmdOK(pCtl, s);
+}
+
+
+/**
+ * Begin Transfer: ATAPI Passthrough command
+ */
+static void atapiR3PassthroughCmdBT(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ atapiR3CmdBT(pCtl, s);
+}
+
+
+/**
+ * Sink/Source: READ
+ */
+static bool atapiR3ReadSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc;
+ uint64_t cbBlockRegion = 0;
+ VDREGIONDATAFORM enmDataForm;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ uint32_t const iATAPILBA = s->iCurLBA;
+ uint32_t const cbTransfer = RT_MIN(s->cbTotalTransfer, RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE));
+ uint32_t const cbATAPISector = s->cbATAPISector;
+ uint32_t const cSectors = cbTransfer / cbATAPISector;
+ Assert(cSectors * cbATAPISector <= cbTransfer);
+ Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iATAPILBA));
+ AssertLogRelReturn(cSectors * cbATAPISector <= sizeof(s->abIOBuffer), false);
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ rc = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->pDrvMedia, iATAPILBA, NULL, NULL,
+ &cbBlockRegion, &enmDataForm);
+ if (RT_SUCCESS(rc))
+ {
+ STAM_PROFILE_ADV_START(&s->StatReads, r);
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+
+ /* If the region block size and requested sector matches we can just pass the request through. */
+ if (cbBlockRegion == cbATAPISector)
+ rc = pDevR3->pDrvMedia->pfnRead(pDevR3->pDrvMedia, (uint64_t)iATAPILBA * cbATAPISector,
+ s->abIOBuffer, cbATAPISector * cSectors);
+ else
+ {
+ uint32_t const iEndSector = iATAPILBA + cSectors;
+ ASSERT_GUEST(iEndSector >= iATAPILBA);
+ if (cbBlockRegion == 2048 && cbATAPISector == 2352)
+ {
+ /* Generate the sync bytes. */
+ uint8_t *pbBuf = s->abIOBuffer;
+
+ for (uint32_t i = iATAPILBA; i < iEndSector; i++)
+ {
+ /* Sync bytes, see 4.2.3.8 CD Main Channel Block Formats */
+ *pbBuf++ = 0x00;
+ memset(pbBuf, 0xff, 10);
+ pbBuf += 10;
+ *pbBuf++ = 0x00;
+ /* MSF */
+ scsiLBA2MSF(pbBuf, i);
+ pbBuf += 3;
+ *pbBuf++ = 0x01; /* mode 1 data */
+ /* data */
+ rc = pDevR3->pDrvMedia->pfnRead(pDevR3->pDrvMedia, (uint64_t)i * 2048, pbBuf, 2048);
+ if (RT_FAILURE(rc))
+ break;
+ pbBuf += 2048;
+ /**
+ * @todo maybe compute ECC and parity, layout is:
+ * 2072 4 EDC
+ * 2076 172 P parity symbols
+ * 2248 104 Q parity symbols
+ */
+ memset(pbBuf, 0, 280);
+ pbBuf += 280;
+ }
+ }
+ else if (cbBlockRegion == 2352 && cbATAPISector == 2048)
+ {
+ /* Read only the user data portion. */
+ uint8_t *pbBuf = s->abIOBuffer;
+
+ for (uint32_t i = iATAPILBA; i < iEndSector; i++)
+ {
+ uint8_t abTmp[2352];
+ uint8_t cbSkip;
+
+ rc = pDevR3->pDrvMedia->pfnRead(pDevR3->pDrvMedia, (uint64_t)i * 2352, &abTmp[0], 2352);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Mode 2 has an additional subheader before user data; we need to
+ * skip 16 bytes for Mode 1 (sync + header) and 20 bytes for Mode 2 +
+ * (sync + header + subheader).
+ */
+ switch (enmDataForm) {
+ case VDREGIONDATAFORM_MODE2_2352:
+ case VDREGIONDATAFORM_XA_2352:
+ cbSkip = 24;
+ break;
+ case VDREGIONDATAFORM_MODE1_2352:
+ cbSkip = 16;
+ break;
+ default:
+ AssertMsgFailed(("Unexpected region form (%#u), using default skip value\n", enmDataForm));
+ cbSkip = 16;
+ }
+ memcpy(pbBuf, &abTmp[cbSkip], 2048);
+ pbBuf += 2048;
+ }
+ }
+ else
+ ASSERT_GUEST_MSG_FAILED(("Unsupported: cbBlockRegion=%u cbATAPISector=%u\n", cbBlockRegion, cbATAPISector));
+ }
+ s->Led.Actual.s.fReading = 0;
+ STAM_PROFILE_ADV_STOP(&s->StatReads, r);
+ }
+
+ ataR3LockEnter(pDevIns, pCtl);
+
+ if (RT_SUCCESS(rc))
+ {
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cbATAPISector * cSectors);
+
+ /* The initial buffer end value has been set up based on the total
+ * transfer size. But the I/O buffer size limits what can actually be
+ * done in one transfer, so set the actual value of the buffer end. */
+ s->cbElementaryTransfer = cbTransfer;
+ if (cbTransfer >= s->cbTotalTransfer)
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ s->iCurLBA = iATAPILBA + cSectors;
+ }
+ else
+ {
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM read error, %d sectors at LBA %d\n", s->iLUN, cSectors, iATAPILBA));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_MEDIUM_ERROR, SCSI_ASC_READ_ERROR);
+ }
+ return false;
+}
+
+/**
+ * Sets the given media track type.
+ */
+static uint32_t ataR3MediumTypeSet(PATADEVSTATE s, uint32_t MediaTrackType)
+{
+ return ASMAtomicXchgU32(&s->MediaTrackType, MediaTrackType);
+}
+
+
+/**
+ * Sink/Source: Passthrough
+ */
+static bool atapiR3PassthroughSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ uint32_t cbTransfer;
+ PSTAMPROFILEADV pProf = NULL;
+
+ cbTransfer = RT_MIN(s->cbAtapiPassthroughTransfer, RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE));
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE)
+ Log3(("ATAPI PT data write (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->abIOBuffer));
+
+ /* Simple heuristics: if there is at least one sector of data
+ * to transfer, it's worth updating the LEDs. */
+ if (cbTransfer >= 2048)
+ {
+ if (s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ {
+ s->Led.Asserted.s.fReading = s->Led.Actual.s.fReading = 1;
+ pProf = &s->StatReads;
+ }
+ else
+ {
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+ pProf = &s->StatWrites;
+ }
+ }
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+# if defined(LOG_ENABLED)
+ char szBuf[1024];
+
+ memset(szBuf, 0, sizeof(szBuf));
+
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_MODE_SELECT_10:
+ {
+ size_t cbBlkDescLength = scsiBE2H_U16(&s->abIOBuffer[6]);
+
+ SCSILogModePage(szBuf, sizeof(szBuf) - 1,
+ s->abIOBuffer + 8 + cbBlkDescLength,
+ cbTransfer - 8 - cbBlkDescLength);
+ break;
+ }
+ case SCSI_SEND_CUE_SHEET:
+ {
+ SCSILogCueSheet(szBuf, sizeof(szBuf) - 1,
+ s->abIOBuffer, cbTransfer);
+ break;
+ }
+ default:
+ break;
+ }
+
+ Log2(("%s\n", szBuf));
+# endif
+
+ if (pProf) { STAM_PROFILE_ADV_START(pProf, b); }
+
+ Assert(s->cbATAPISector);
+ const uint32_t cbATAPISector = RT_MAX(s->cbATAPISector, 1); /* paranoia */
+ const uint32_t cbIOBuffer = RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE); /* ditto */
+
+ if ( cbTransfer > SCSI_MAX_BUFFER_SIZE
+ || s->cbElementaryTransfer > cbIOBuffer)
+ {
+ /* Linux accepts commands with up to 100KB of data, but expects
+ * us to handle commands with up to 128KB of data. The usual
+ * imbalance of powers. */
+ uint8_t abATAPICmd[ATAPI_PACKET_SIZE];
+ uint32_t iATAPILBA, cSectors, cReqSectors, cbCurrTX;
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint32_t cSectorsMax; /**< Maximum amount of sectors to read without exceeding the I/O buffer. */
+
+ cSectorsMax = cbTransfer / cbATAPISector;
+ AssertStmt(cSectorsMax * s->cbATAPISector <= cbIOBuffer, cSectorsMax = cbIOBuffer / cbATAPISector);
+
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ iATAPILBA = scsiBE2H_U32(s->abATAPICmd + 2);
+ cSectors = scsiBE2H_U16(s->abATAPICmd + 7);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ iATAPILBA = scsiBE2H_U32(s->abATAPICmd + 2);
+ cSectors = scsiBE2H_U32(s->abATAPICmd + 6);
+ break;
+ case SCSI_READ_CD:
+ iATAPILBA = scsiBE2H_U32(s->abATAPICmd + 2);
+ cSectors = scsiBE2H_U24(s->abATAPICmd + 6);
+ break;
+ case SCSI_READ_CD_MSF:
+ iATAPILBA = scsiMSF2LBA(s->abATAPICmd + 3);
+ cSectors = scsiMSF2LBA(s->abATAPICmd + 6) - iATAPILBA;
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", s->abATAPICmd[0]));
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ ataR3LockEnter(pDevIns, pCtl);
+ return false;
+ }
+ cSectorsMax = RT_MIN(cSectorsMax, cSectors);
+ memcpy(abATAPICmd, s->abATAPICmd, ATAPI_PACKET_SIZE);
+ cReqSectors = 0;
+ for (uint32_t i = cSectorsMax; i > 0; i -= cReqSectors)
+ {
+ if (i * cbATAPISector > SCSI_MAX_BUFFER_SIZE)
+ cReqSectors = SCSI_MAX_BUFFER_SIZE / cbATAPISector;
+ else
+ cReqSectors = i;
+ cbCurrTX = cbATAPISector * cReqSectors;
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ scsiH2BE_U32(abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U16(abATAPICmd + 7, cReqSectors);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ scsiH2BE_U32(abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U32(abATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD:
+ scsiH2BE_U32(abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U24(abATAPICmd + 6, cReqSectors);
+ break;
+ case SCSI_READ_CD_MSF:
+ scsiLBA2MSF(abATAPICmd + 3, iATAPILBA);
+ scsiLBA2MSF(abATAPICmd + 6, iATAPILBA + cReqSectors);
+ break;
+ }
+ AssertLogRelReturn((uintptr_t)(pbBuf - &s->abIOBuffer[0]) + cbCurrTX <= sizeof(s->abIOBuffer), false);
+ rc = pDevR3->pDrvMedia->pfnSendCmd(pDevR3->pDrvMedia, abATAPICmd, ATAPI_PACKET_SIZE, (PDMMEDIATXDIR)s->uTxDir,
+ pbBuf, &cbCurrTX, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ if (rc != VINF_SUCCESS)
+ break;
+ iATAPILBA += cReqSectors;
+ pbBuf += cbATAPISector * cReqSectors;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Adjust ATAPI command for the next call. */
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_READ_10:
+ case SCSI_WRITE_10:
+ case SCSI_WRITE_AND_VERIFY_10:
+ scsiH2BE_U32(s->abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U16(s->abATAPICmd + 7, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_12:
+ case SCSI_WRITE_12:
+ scsiH2BE_U32(s->abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U32(s->abATAPICmd + 6, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_CD:
+ scsiH2BE_U32(s->abATAPICmd + 2, iATAPILBA);
+ scsiH2BE_U24(s->abATAPICmd + 6, cSectors - cSectorsMax);
+ break;
+ case SCSI_READ_CD_MSF:
+ scsiLBA2MSF(s->abATAPICmd + 3, iATAPILBA);
+ scsiLBA2MSF(s->abATAPICmd + 6, iATAPILBA + cSectors - cSectorsMax);
+ break;
+ default:
+ AssertMsgFailed(("Don't know how to split command %#04x\n", s->abATAPICmd[0]));
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ return false;
+ }
+ }
+ }
+ else
+ {
+ AssertLogRelReturn(cbTransfer <= sizeof(s->abIOBuffer), false);
+ rc = pDevR3->pDrvMedia->pfnSendCmd(pDevR3->pDrvMedia, s->abATAPICmd, ATAPI_PACKET_SIZE, (PDMMEDIATXDIR)s->uTxDir,
+ s->abIOBuffer, &cbTransfer, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
+ }
+ if (pProf) { STAM_PROFILE_ADV_STOP(pProf, b); }
+
+ ataR3LockEnter(pDevIns, pCtl);
+
+ /* Update the LEDs and the read/write statistics. */
+ if (cbTransfer >= 2048)
+ {
+ if (s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ {
+ s->Led.Actual.s.fReading = 0;
+ STAM_REL_COUNTER_ADD(&s->StatBytesRead, cbTransfer);
+ }
+ else
+ {
+ s->Led.Actual.s.fWriting = 0;
+ STAM_REL_COUNTER_ADD(&s->StatBytesWritten, cbTransfer);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Do post processing for certain commands. */
+ switch (s->abATAPICmd[0])
+ {
+ case SCSI_SEND_CUE_SHEET:
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ if (!pDevR3->pTrackList)
+ rc = ATAPIPassthroughTrackListCreateEmpty(&pDevR3->pTrackList);
+
+ if (RT_SUCCESS(rc))
+ rc = ATAPIPassthroughTrackListUpdate(pDevR3->pTrackList, s->abATAPICmd, s->abIOBuffer, sizeof(s->abIOBuffer));
+
+ if ( RT_FAILURE(rc)
+ && s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("ATA: Error (%Rrc) while updating the tracklist during %s, burning the disc might fail\n",
+ rc, s->abATAPICmd[0] == SCSI_SEND_CUE_SHEET ? "SEND CUE SHEET" : "READ TOC/PMA/ATIP"));
+ break;
+ }
+ case SCSI_SYNCHRONIZE_CACHE:
+ {
+ if (pDevR3->pTrackList)
+ ATAPIPassthroughTrackListClear(pDevR3->pTrackList);
+ break;
+ }
+ }
+
+ if (s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ {
+ /*
+ * Reply with the same amount of data as the real drive
+ * but only if the command wasn't split.
+ */
+ if (s->cbAtapiPassthroughTransfer < cbIOBuffer)
+ s->cbTotalTransfer = cbTransfer;
+
+ if ( s->abATAPICmd[0] == SCSI_INQUIRY
+ && s->fOverwriteInquiry)
+ {
+ /* Make sure that the real drive cannot be identified.
+ * Motivation: changing the VM configuration should be as
+ * invisible as possible to the guest. */
+ Log3(("ATAPI PT inquiry data before (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->abIOBuffer));
+ scsiPadStr(&s->abIOBuffer[8], "VBOX", 8);
+ scsiPadStr(&s->abIOBuffer[16], "CD-ROM", 16);
+ scsiPadStr(&s->abIOBuffer[32], "1.0", 4);
+ }
+
+ if (cbTransfer)
+ Log3(("ATAPI PT data read (%d):\n%.*Rhxd\n", cbTransfer, cbTransfer, s->abIOBuffer));
+ }
+
+ /* The initial buffer end value has been set up based on the total
+ * transfer size. But the I/O buffer size limits what can actually be
+ * done in one transfer, so set the actual value of the buffer end. */
+ Assert(cbTransfer <= s->cbAtapiPassthroughTransfer);
+ s->cbElementaryTransfer = cbTransfer;
+ s->cbAtapiPassthroughTransfer -= cbTransfer;
+ if (!s->cbAtapiPassthroughTransfer)
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ }
+ }
+ else
+ {
+ if (s->cErrors < MAX_LOG_REL_ERRORS)
+ {
+ uint8_t u8Cmd = s->abATAPICmd[0];
+ do
+ {
+ /* don't log superfluous errors */
+ if ( rc == VERR_DEV_IO_ERROR
+ && ( u8Cmd == SCSI_TEST_UNIT_READY
+ || u8Cmd == SCSI_READ_CAPACITY
+ || u8Cmd == SCSI_READ_DVD_STRUCTURE
+ || u8Cmd == SCSI_READ_TOC_PMA_ATIP))
+ break;
+ s->cErrors++;
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough cmd=%#04x sense=%d ASC=%#02x ASCQ=%#02x %Rrc\n",
+ s->iLUN, u8Cmd, abATAPISense[2] & 0x0f, abATAPISense[12], abATAPISense[13], rc));
+ } while (0);
+ }
+ atapiR3CmdError(pCtl, s, abATAPISense, sizeof(abATAPISense));
+ }
+ return false;
+}
+
+
+/**
+ * Begin Transfer: Read DVD structures
+ */
+static bool atapiR3ReadDVDStructureSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *buf = s->abIOBuffer;
+ int media = s->abATAPICmd[1];
+ int format = s->abATAPICmd[7];
+ RT_NOREF(pDevIns, pDevR3);
+
+ AssertCompile(sizeof(s->abIOBuffer) > UINT16_MAX /* want a RT_MIN() below, but clang takes offence at always false stuff */);
+ uint16_t max_len = scsiBE2H_U16(&s->abATAPICmd[8]);
+ memset(buf, 0, max_len);
+
+ switch (format) {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x0e:
+ case 0x0f:
+ case 0x10:
+ case 0x11:
+ case 0x30:
+ case 0x31:
+ case 0xff:
+ if (media == 0)
+ {
+ int uASC = SCSI_ASC_NONE;
+
+ switch (format)
+ {
+ case 0x0: /* Physical format information */
+ {
+ int layer = s->abATAPICmd[6];
+ uint64_t total_sectors;
+
+ if (layer != 0)
+ {
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ break;
+ }
+
+ total_sectors = s->cTotalSectors;
+ total_sectors >>= 2;
+ if (total_sectors == 0)
+ {
+ uASC = -SCSI_ASC_MEDIUM_NOT_PRESENT;
+ break;
+ }
+
+ buf[4] = 1; /* DVD-ROM, part version 1 */
+ buf[5] = 0xf; /* 120mm disc, minimum rate unspecified */
+ buf[6] = 1; /* one layer, read-only (per MMC-2 spec) */
+ buf[7] = 0; /* default densities */
+
+ /* FIXME: 0x30000 per spec? */
+ scsiH2BE_U32(buf + 8, 0); /* start sector */
+ scsiH2BE_U32(buf + 12, total_sectors - 1); /* end sector */
+ scsiH2BE_U32(buf + 16, total_sectors - 1); /* l0 end sector */
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U32(&buf[0], 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ uASC = (2048 + 4);
+ break;
+ }
+ case 0x01: /* DVD copyright information */
+ buf[4] = 0; /* no copyright data */
+ buf[5] = 0; /* no region restrictions */
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(buf, 4 + 2);
+
+ /* 4 byte header + 4 byte data */
+ uASC = (4 + 4);
+ break;
+
+ case 0x03: /* BCA information - invalid field for no BCA info */
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ break;
+
+ case 0x04: /* DVD disc manufacturing information */
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(buf, 2048 + 2);
+
+ /* 2k data + 4 byte header */
+ uASC = (2048 + 4);
+ break;
+ case 0xff:
+ /*
+ * This lists all the command capabilities above. Add new ones
+ * in order and update the length and buffer return values.
+ */
+
+ buf[4] = 0x00; /* Physical format */
+ buf[5] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 6), 2048 + 4);
+
+ buf[8] = 0x01; /* Copyright info */
+ buf[9] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 10), 4 + 4);
+
+ buf[12] = 0x03; /* BCA info */
+ buf[13] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 14), 188 + 4);
+
+ buf[16] = 0x04; /* Manufacturing info */
+ buf[17] = 0x40; /* Not writable, is readable */
+ scsiH2BE_U16((buf + 18), 2048 + 4);
+
+ /* Size of buffer, not including 2 byte size field */
+ scsiH2BE_U16(buf, 16 + 2);
+
+ /* data written + 4 byte header */
+ uASC = (16 + 4);
+ break;
+ default: /** @todo formats beyond DVD-ROM requires */
+ uASC = -SCSI_ASC_INV_FIELD_IN_CMD_PACKET;
+ }
+
+ if (uASC < 0)
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, -uASC);
+ return false;
+ }
+ break;
+ }
+ /** @todo BD support, fall through for now */
+ RT_FALL_THRU();
+
+ /* Generic disk structures */
+ case 0x80: /** @todo AACS volume identifier */
+ case 0x81: /** @todo AACS media serial number */
+ case 0x82: /** @todo AACS media identifier */
+ case 0x83: /** @todo AACS media key block */
+ case 0x90: /** @todo List of recognized format layers */
+ case 0xc0: /** @todo Write protection status */
+ default:
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+static bool atapiR3ReadSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s,
+ uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
+{
+ Assert(cSectors > 0);
+ s->iCurLBA = iATAPILBA;
+ s->cbATAPISector = cbSector;
+ ataR3StartTransfer(pDevIns, pCtl, s, cSectors * cbSector,
+ PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ, true);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ CAPACITY
+ */
+static bool atapiR3ReadCapacitySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+ scsiH2BE_U32(pbBuf, s->cTotalSectors - 1);
+ scsiH2BE_U32(pbBuf + 4, 2048);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ DISCK INFORMATION
+ */
+static bool atapiR3ReadDiscInformationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 34);
+ memset(pbBuf, '\0', 34);
+ scsiH2BE_U16(pbBuf, 32);
+ pbBuf[2] = (0 << 4) | (3 << 2) | (2 << 0); /* not erasable, complete session, complete disc */
+ pbBuf[3] = 1; /* number of first track */
+ pbBuf[4] = 1; /* number of sessions (LSB) */
+ pbBuf[5] = 1; /* first track number in last session (LSB) */
+ pbBuf[6] = (uint8_t)pDevR3->pDrvMedia->pfnGetRegionCount(pDevR3->pDrvMedia); /* last track number in last session (LSB) */
+ pbBuf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
+ pbBuf[8] = 0; /* disc type = CD-ROM */
+ pbBuf[9] = 0; /* number of sessions (MSB) */
+ pbBuf[10] = 0; /* number of sessions (MSB) */
+ pbBuf[11] = 0; /* number of sessions (MSB) */
+ scsiH2BE_U32(pbBuf + 16, 0xffffffff); /* last session lead-in start time is not available */
+ scsiH2BE_U32(pbBuf + 20, 0xffffffff); /* last possible start time for lead-out is not available */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TRACK INFORMATION
+ */
+static bool atapiR3ReadTrackInformationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint32_t u32LogAddr = scsiBE2H_U32(&s->abATAPICmd[2]);
+ uint8_t u8LogAddrType = s->abATAPICmd[1] & 0x03;
+ RT_NOREF(pDevIns);
+
+ int rc;
+ uint64_t u64LbaStart = 0;
+ uint32_t uRegion = 0;
+ uint64_t cBlocks = 0;
+ uint64_t cbBlock = 0;
+ uint8_t u8DataMode = 0xf; /* Unknown data mode. */
+ uint8_t u8TrackMode = 0;
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_INVALID;
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 36);
+
+ switch (u8LogAddrType)
+ {
+ case 0x00:
+ rc = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->pDrvMedia, u32LogAddr, &uRegion,
+ NULL, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ rc = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->pDrvMedia, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ break;
+ case 0x01:
+ {
+ if (u32LogAddr >= 1)
+ {
+ uRegion = u32LogAddr - 1;
+ rc = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->pDrvMedia, uRegion, &u64LbaStart,
+ &cBlocks, &cbBlock, &enmDataForm);
+ }
+ else
+ rc = VERR_NOT_FOUND; /** @todo Return lead-in information. */
+ break;
+ }
+ case 0x02:
+ default:
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ switch (enmDataForm)
+ {
+ case VDREGIONDATAFORM_MODE1_2048:
+ case VDREGIONDATAFORM_MODE1_2352:
+ case VDREGIONDATAFORM_MODE1_0:
+ u8DataMode = 1;
+ break;
+ case VDREGIONDATAFORM_XA_2336:
+ case VDREGIONDATAFORM_XA_2352:
+ case VDREGIONDATAFORM_XA_0:
+ case VDREGIONDATAFORM_MODE2_2336:
+ case VDREGIONDATAFORM_MODE2_2352:
+ case VDREGIONDATAFORM_MODE2_0:
+ u8DataMode = 2;
+ break;
+ default:
+ u8DataMode = 0xf;
+ }
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ u8TrackMode = 0x0;
+ else
+ u8TrackMode = 0x4;
+
+ memset(pbBuf, '\0', 36);
+ scsiH2BE_U16(pbBuf, 34);
+ pbBuf[2] = uRegion + 1; /* track number (LSB) */
+ pbBuf[3] = 1; /* session number (LSB) */
+ pbBuf[5] = (0 << 5) | (0 << 4) | u8TrackMode; /* not damaged, primary copy, data track */
+ pbBuf[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | u8DataMode; /* not reserved track, not blank, not packet writing, not fixed packet */
+ pbBuf[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
+ scsiH2BE_U32(pbBuf + 8, (uint32_t)u64LbaStart); /* track start address is 0 */
+ scsiH2BE_U32(pbBuf + 24, (uint32_t)cBlocks); /* track size */
+ pbBuf[32] = 0; /* track number (MSB) */
+ pbBuf[33] = 0; /* session number (MSB) */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureListProfiles(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 3*4)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x0); /* feature 0: list of profiles supported */
+ pbBuf[2] = (0 << 2) | (1 << 1) | (1 << 0); /* version 0, persistent, current */
+ pbBuf[3] = 8; /* additional bytes for profiles */
+ /* The MMC-3 spec says that DVD-ROM read capability should be reported
+ * before CD-ROM read capability. */
+ scsiH2BE_U16(pbBuf + 4, 0x10); /* profile: read-only DVD */
+ pbBuf[6] = (0 << 0); /* NOT current profile */
+ scsiH2BE_U16(pbBuf + 8, 0x08); /* profile: read only CD */
+ pbBuf[10] = (1 << 0); /* current profile */
+
+ return 3*4; /* Header + 2 profiles entries */
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureCore(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 12)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x1); /* feature 0001h: Core Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 8; /* Additional length */
+ scsiH2BE_U16(pbBuf + 4, 0x00000002); /* Physical interface ATAPI. */
+ pbBuf[8] = RT_BIT(0); /* DBE */
+ /* Rest is reserved. */
+
+ return 12;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureMorphing(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x2); /* feature 0002h: Morphing Feature */
+ pbBuf[2] = (0x1 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ pbBuf[4] = RT_BIT(1) | 0x0; /* OCEvent | !ASYNC */
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureRemovableMedium(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x3); /* feature 0003h: Removable Medium Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ /* Tray type loading | Load | Eject | !Pvnt Jmpr | !DBML | Lock */
+ pbBuf[4] = (0x2 << 5) | RT_BIT(4) | RT_BIT(3) | (0x0 << 2) | (0x0 << 1) | RT_BIT(0);
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureRandomReadable (PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 12)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x10); /* feature 0010h: Random Readable Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 8; /* Additional length */
+ scsiH2BE_U32(pbBuf + 4, 2048); /* Logical block size. */
+ scsiH2BE_U16(pbBuf + 8, 0x10); /* Blocking (0x10 for DVD, CD is not defined). */
+ pbBuf[10] = 0; /* PP not present */
+ /* Rest is reserved. */
+
+ return 12;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureCDRead(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x1e); /* feature 001Eh: CD Read Feature */
+ pbBuf[2] = (0x2 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 0; /* Additional length */
+ pbBuf[4] = (0x0 << 7) | (0x0 << 1) | 0x0; /* !DAP | !C2-Flags | !CD-Text. */
+ /* Rest is reserved. */
+
+ return 8;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeaturePowerManagement(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 4)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x100); /* feature 0100h: Power Management Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 0; /* Additional length */
+
+ return 4;
+}
+
+static DECLCALLBACK(uint32_t) atapiR3GetConfigurationFillFeatureTimeout(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(s);
+ if (cbBuf < 8)
+ return 0;
+
+ scsiH2BE_U16(pbBuf, 0x105); /* feature 0105h: Timeout Feature */
+ pbBuf[2] = (0x0 << 2) | RT_BIT(1) | RT_BIT(0); /* Version | Persistent | Current */
+ pbBuf[3] = 4; /* Additional length */
+ pbBuf[4] = 0x0; /* !Group3 */
+
+ return 8;
+}
+
+/**
+ * Callback to fill in the correct data for a feature.
+ *
+ * @returns Number of bytes written into the buffer.
+ * @param s The ATA device state.
+ * @param pbBuf The buffer to fill the data with.
+ * @param cbBuf Size of the buffer.
+ */
+typedef DECLCALLBACKTYPE(uint32_t, FNATAPIR3FEATUREFILL,(PATADEVSTATE s, uint8_t *pbBuf, size_t cbBuf));
+/** Pointer to a feature fill callback. */
+typedef FNATAPIR3FEATUREFILL *PFNATAPIR3FEATUREFILL;
+
+/**
+ * ATAPI feature descriptor.
+ */
+typedef struct ATAPIR3FEATDESC
+{
+ /** The feature number. */
+ uint16_t u16Feat;
+ /** The callback to fill in the correct data. */
+ PFNATAPIR3FEATUREFILL pfnFeatureFill;
+} ATAPIR3FEATDESC;
+
+/**
+ * Array of known ATAPI feature descriptors.
+ */
+static const ATAPIR3FEATDESC s_aAtapiR3Features[] =
+{
+ { 0x0000, atapiR3GetConfigurationFillFeatureListProfiles},
+ { 0x0001, atapiR3GetConfigurationFillFeatureCore},
+ { 0x0002, atapiR3GetConfigurationFillFeatureMorphing},
+ { 0x0003, atapiR3GetConfigurationFillFeatureRemovableMedium},
+ { 0x0010, atapiR3GetConfigurationFillFeatureRandomReadable},
+ { 0x001e, atapiR3GetConfigurationFillFeatureCDRead},
+ { 0x0100, atapiR3GetConfigurationFillFeaturePowerManagement},
+ { 0x0105, atapiR3GetConfigurationFillFeatureTimeout}
+};
+
+/**
+ * Sink/Source: ATAPI GET CONFIGURATION
+ */
+static bool atapiR3GetConfigurationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint32_t const cbIOBuffer = RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE);
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint32_t cbBuf = cbIOBuffer;
+ uint32_t cbCopied = 0;
+ uint16_t u16Sfn = scsiBE2H_U16(&s->abATAPICmd[2]);
+ uint8_t u8Rt = s->abATAPICmd[1] & 0x03;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 80);
+ /* Accept valid request types only. */
+ if (u8Rt == 3)
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+ memset(pbBuf, '\0', cbBuf);
+ /** @todo implement switching between CD-ROM and DVD-ROM profile (the only
+ * way to differentiate them right now is based on the image size). */
+ if (s->cTotalSectors)
+ scsiH2BE_U16(pbBuf + 6, 0x08); /* current profile: read-only CD */
+ else
+ scsiH2BE_U16(pbBuf + 6, 0x00); /* current profile: none -> no media */
+ cbBuf -= 8;
+ pbBuf += 8;
+
+ if (u8Rt == 0x2)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aAtapiR3Features); i++)
+ {
+ if (s_aAtapiR3Features[i].u16Feat == u16Sfn)
+ {
+ cbCopied = s_aAtapiR3Features[i].pfnFeatureFill(s, pbBuf, cbBuf);
+ cbBuf -= cbCopied;
+ pbBuf += cbCopied;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aAtapiR3Features); i++)
+ {
+ if (s_aAtapiR3Features[i].u16Feat > u16Sfn)
+ {
+ cbCopied = s_aAtapiR3Features[i].pfnFeatureFill(s, pbBuf, cbBuf);
+ cbBuf -= cbCopied;
+ pbBuf += cbCopied;
+ }
+ }
+ }
+
+ /* Set data length now - the field is not included in the final length. */
+ scsiH2BE_U32(s->abIOBuffer, cbIOBuffer - cbBuf - 4);
+
+ /* Other profiles we might want to add in the future: 0x40 (BD-ROM) and 0x50 (HDDVD-ROM) */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI GET EVENT STATUS NOTIFICATION
+ */
+static bool atapiR3GetEventStatusNotificationSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+
+ if (!(s->abATAPICmd[1] & 1))
+ {
+ /* no asynchronous operation supported */
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+ NewStatus = ATA_EVENT_STATUS_UNCHANGED;
+ switch (OldStatus)
+ {
+ case ATA_EVENT_STATUS_MEDIA_NEW:
+ /* mount */
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04; /* media */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = 0x02; /* new medium */
+ pbBuf[5] = 0x02; /* medium present / door closed */
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+
+ case ATA_EVENT_STATUS_MEDIA_CHANGED:
+ case ATA_EVENT_STATUS_MEDIA_REMOVED:
+ /* umount */
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04; /* media */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED ? 0x04 /* media changed */ : 0x03; /* media removed */
+ pbBuf[5] = 0x00; /* medium absent / door closed */
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ if (OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED)
+ NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+ break;
+
+ case ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED: /* currently unused */
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x04; /* media */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = 0x01; /* eject requested (eject button pressed) */
+ pbBuf[5] = 0x02; /* medium present / door closed */
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+
+ case ATA_EVENT_STATUS_UNCHANGED:
+ default:
+ scsiH2BE_U16(pbBuf + 0, 6);
+ pbBuf[2] = 0x01; /* operational change request / notification */
+ pbBuf[3] = 0x5e; /* supported = busy|media|external|power|operational */
+ pbBuf[4] = 0x00;
+ pbBuf[5] = 0x00;
+ pbBuf[6] = 0x00;
+ pbBuf[7] = 0x00;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI INQUIRY
+ */
+static bool atapiR3InquirySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 36);
+ pbBuf[0] = 0x05; /* CD-ROM */
+ pbBuf[1] = 0x80; /* removable */
+# if 1/*ndef VBOX*/ /** @todo implement MESN + AENC. (async notification on removal and stuff.) */
+ pbBuf[2] = 0x00; /* ISO */
+ pbBuf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
+# else
+ pbBuf[2] = 0x00; /* ISO */
+ pbBuf[3] = 0x91; /* format 1, MESN=1, AENC=9 ??? */
+# endif
+ pbBuf[4] = 31; /* additional length */
+ pbBuf[5] = 0; /* reserved */
+ pbBuf[6] = 0; /* reserved */
+ pbBuf[7] = 0; /* reserved */
+ scsiPadStr(pbBuf + 8, s->szInquiryVendorId, 8);
+ scsiPadStr(pbBuf + 16, s->szInquiryProductId, 16);
+ scsiPadStr(pbBuf + 32, s->szInquiryRevision, 4);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI MODE SENSE ERROR RECOVERY
+ */
+static bool atapiR3ModeSenseErrorRecoverySS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 16);
+ scsiH2BE_U16(&pbBuf[0], 16 + 6);
+ pbBuf[2] = (uint8_t)s->MediaTrackType;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 0;
+ pbBuf[6] = 0;
+ pbBuf[7] = 0;
+
+ pbBuf[8] = 0x01;
+ pbBuf[9] = 0x06;
+ pbBuf[10] = 0x00; /* Maximum error recovery */
+ pbBuf[11] = 0x05; /* 5 retries */
+ pbBuf[12] = 0x00;
+ pbBuf[13] = 0x00;
+ pbBuf[14] = 0x00;
+ pbBuf[15] = 0x00;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI MODE SENSE CD STATUS
+ */
+static bool atapiR3ModeSenseCDStatusSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns);
+
+ /* 28 bytes of total returned data corresponds to ATAPI 2.6. Note that at least some versions
+ * of NEC_IDE.SYS DOS driver (possibly other Oak Technology OTI-011 drivers) do not correctly
+ * handle cases where more than 28 bytes are returned due to bugs. See @bugref{5869}.
+ */
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 28);
+ scsiH2BE_U16(&pbBuf[0], 26);
+ pbBuf[2] = (uint8_t)s->MediaTrackType;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 0;
+ pbBuf[6] = 0;
+ pbBuf[7] = 0;
+
+ pbBuf[8] = 0x2a;
+ pbBuf[9] = 18; /* page length */
+ pbBuf[10] = 0x08; /* DVD-ROM read support */
+ pbBuf[11] = 0x00; /* no write support */
+ /* The following claims we support audio play. This is obviously false,
+ * but the Linux generic CDROM support makes many features depend on this
+ * capability. If it's not set, this causes many things to be disabled. */
+ pbBuf[12] = 0x71; /* multisession support, mode 2 form 1/2 support, audio play */
+ pbBuf[13] = 0x00; /* no subchannel reads supported */
+ pbBuf[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
+ if (pDevR3->pDrvMount && pDevR3->pDrvMount->pfnIsLocked(pDevR3->pDrvMount))
+ pbBuf[14] |= 1 << 1; /* report lock state */
+ pbBuf[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
+ scsiH2BE_U16(&pbBuf[16], 5632); /* (obsolete) claim 32x speed support */
+ scsiH2BE_U16(&pbBuf[18], 2); /* number of audio volume levels */
+ scsiH2BE_U16(&pbBuf[20], RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE) / _1K); /* buffer size supported in Kbyte */
+ scsiH2BE_U16(&pbBuf[22], 5632); /* (obsolete) current read speed 32x */
+ pbBuf[24] = 0; /* reserved */
+ pbBuf[25] = 0; /* reserved for digital audio (see idx 15) */
+ pbBuf[26] = 0; /* reserved */
+ pbBuf[27] = 0; /* reserved */
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI REQUEST SENSE
+ */
+static bool atapiR3RequestSenseSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ memset(pbBuf, '\0', RT_MIN(s->cbElementaryTransfer, sizeof(s->abIOBuffer)));
+ AssertCompile(sizeof(s->abIOBuffer) >= sizeof(s->abATAPISense));
+ memcpy(pbBuf, s->abATAPISense, RT_MIN(s->cbElementaryTransfer, sizeof(s->abATAPISense)));
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI MECHANISM STATUS
+ */
+static bool atapiR3MechanismStatusSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 8);
+ scsiH2BE_U16(pbBuf, 0);
+ /* no current LBA */
+ pbBuf[2] = 0;
+ pbBuf[3] = 0;
+ pbBuf[4] = 0;
+ pbBuf[5] = 1;
+ scsiH2BE_U16(pbBuf + 6, 0);
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TOC NORMAL
+ */
+static bool atapiR3ReadTOCNormalSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint8_t *q;
+ uint8_t iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+ RT_NOREF(pDevIns);
+
+ /* Track fields are 8-bit and 1-based, so cut the track count at 255,
+ avoiding any potential buffer overflow issues below. */
+ uint32_t cTracks = pDevR3->pDrvMedia->pfnGetRegionCount(pDevR3->pDrvMedia);
+ AssertStmt(cTracks <= UINT8_MAX, cTracks = UINT8_MAX);
+ AssertCompile(sizeof(s->abIOBuffer) >= 2 + 256 + 8);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ fMSF = (s->abATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->abATAPICmd[6];
+ if (iStartTrack == 0)
+ iStartTrack = 1;
+
+ if (iStartTrack > cTracks && iStartTrack != 0xaa)
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ return false;
+ }
+ q = pbBuf + 2;
+ *q++ = iStartTrack; /* first track number */
+ *q++ = cTracks; /* last track number */
+ for (uint32_t iTrack = iStartTrack; iTrack <= cTracks; iTrack++)
+ {
+ uint64_t uLbaStart = 0;
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_MODE1_2048;
+
+ int rc = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->pDrvMedia, iTrack - 1, &uLbaStart,
+ NULL, NULL, &enmDataForm);
+ AssertRC(rc);
+
+ *q++ = 0; /* reserved */
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ *q++ = 0x10; /* ADR, control */
+ else
+ *q++ = 0x14; /* ADR, control */
+
+ *q++ = (uint8_t)iTrack; /* track number */
+ *q++ = 0; /* reserved */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, (uint32_t)uLbaStart);
+ q += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(q, (uint32_t)uLbaStart);
+ q += 4;
+ }
+ }
+ /* lead out track */
+ *q++ = 0; /* reserved */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0xaa; /* track number */
+ *q++ = 0; /* reserved */
+
+ /* Query start and length of last track to get the start of the lead out track. */
+ uint64_t uLbaStart = 0;
+ uint64_t cBlocks = 0;
+
+ int rc = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->pDrvMedia, cTracks - 1, &uLbaStart,
+ &cBlocks, NULL, NULL);
+ AssertRC(rc);
+
+ uLbaStart += cBlocks;
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, (uint32_t)uLbaStart);
+ q += 3;
+ }
+ else
+ {
+ scsiH2BE_U32(q, (uint32_t)uLbaStart);
+ q += 4;
+ }
+ cbSize = q - pbBuf;
+ scsiH2BE_U16(pbBuf, cbSize - 2);
+ if (cbSize < s->cbTotalTransfer)
+ s->cbTotalTransfer = cbSize;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TOC MULTI
+ */
+static bool atapiR3ReadTOCMultiSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ bool fMSF;
+ RT_NOREF(pDevIns);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ Assert(s->cbElementaryTransfer <= 12);
+ fMSF = (s->abATAPICmd[1] >> 1) & 1;
+ /* multi session: only a single session defined */
+ /** @todo double-check this stuff against what a real drive says for a CD-ROM (not a CD-R)
+ * with only a single data session. Maybe solve the problem with "cdrdao read-toc" not being
+ * able to figure out whether numbers are in BCD or hex. */
+ memset(pbBuf, 0, 12);
+ pbBuf[1] = 0x0a;
+ pbBuf[2] = 0x01;
+ pbBuf[3] = 0x01;
+
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_MODE1_2048;
+ int rc = pDevR3->pDrvMedia->pfnQueryRegionProperties(pDevR3->pDrvMedia, 0, NULL, NULL, NULL, &enmDataForm);
+ AssertRC(rc);
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ pbBuf[5] = 0x10; /* ADR, control */
+ else
+ pbBuf[5] = 0x14; /* ADR, control */
+
+ pbBuf[6] = 1; /* first track in last complete session */
+ if (fMSF)
+ {
+ pbBuf[8] = 0; /* reserved */
+ scsiLBA2MSF(&pbBuf[9], 0);
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(pbBuf + 8, 0);
+ }
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+/**
+ * Sink/Source: ATAPI READ TOC RAW
+ */
+static bool atapiR3ReadTOCRawSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ uint8_t *pbBuf = s->abIOBuffer;
+ uint8_t *q;
+ uint8_t iStartTrack;
+ bool fMSF;
+ uint32_t cbSize;
+ RT_NOREF(pDevIns, pDevR3);
+
+ Assert(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE);
+ fMSF = (s->abATAPICmd[1] >> 1) & 1;
+ iStartTrack = s->abATAPICmd[6];
+
+ q = pbBuf + 2;
+ *q++ = 1; /* first session */
+ *q++ = 1; /* last session */
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa0; /* first track in program area */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* first track */
+ *q++ = 0x00; /* disk type CD-DA or CD data */
+ *q++ = 0;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa1; /* last track in program area */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ *q++ = 0;
+ *q++ = 1; /* last track */
+ *q++ = 0;
+ *q++ = 0;
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* data track */
+ *q++ = 0; /* track number */
+ *q++ = 0xa2; /* lead-out */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, s->cTotalSectors);
+ q += 3;
+ }
+ else
+ {
+ scsiH2BE_U32(q, s->cTotalSectors);
+ q += 4;
+ }
+
+ *q++ = 1; /* session number */
+ *q++ = 0x14; /* ADR, control */
+ *q++ = 0; /* track number */
+ *q++ = 1; /* point */
+ *q++ = 0; /* min */
+ *q++ = 0; /* sec */
+ *q++ = 0; /* frame */
+ if (fMSF)
+ {
+ *q++ = 0; /* reserved */
+ scsiLBA2MSF(q, 0);
+ q += 3;
+ }
+ else
+ {
+ /* sector 0 */
+ scsiH2BE_U32(q, 0);
+ q += 4;
+ }
+
+ cbSize = q - pbBuf;
+ scsiH2BE_U16(pbBuf, cbSize - 2);
+ if (cbSize < s->cbTotalTransfer)
+ s->cbTotalTransfer = cbSize;
+ s->iSourceSink = ATAFN_SS_NULL;
+ atapiR3CmdOK(pCtl, s);
+ return false;
+}
+
+
+static void atapiR3ParseCmdVirtualATAPI(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ const uint8_t *pbPacket = s->abATAPICmd;
+ uint32_t cbMax;
+ uint32_t cSectors, iATAPILBA;
+
+ switch (pbPacket[0])
+ {
+ case SCSI_TEST_UNIT_READY:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ if (s->cNotifiedMediaChange-- > 2)
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ }
+ else
+ {
+ PPDMIMOUNT const pDrvMount = pDevR3->pDrvMount;
+ if (pDrvMount && pDrvMount->pfnIsMounted(pDrvMount))
+ atapiR3CmdOK(pCtl, s);
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ }
+ break;
+ case SCSI_GET_EVENT_STATUS_NOTIFICATION:
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 8), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+ break;
+ case SCSI_MODE_SENSE_10:
+ {
+ uint8_t uPageControl, uPageCode;
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ uPageControl = pbPacket[2] >> 6;
+ uPageCode = pbPacket[2] & 0x3f;
+ switch (uPageControl)
+ {
+ case SCSI_PAGECONTROL_CURRENT:
+ switch (uPageCode)
+ {
+ case SCSI_MODEPAGE_ERROR_RECOVERY:
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 16), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY, true);
+ break;
+ case SCSI_MODEPAGE_CD_STATUS:
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 28), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS, true);
+ break;
+ default:
+ goto error_cmd;
+ }
+ break;
+ case SCSI_PAGECONTROL_CHANGEABLE:
+ goto error_cmd;
+ case SCSI_PAGECONTROL_DEFAULT:
+ goto error_cmd;
+ default:
+ case SCSI_PAGECONTROL_SAVED:
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
+ break;
+ }
+ break;
+ }
+ case SCSI_REQUEST_SENSE:
+ cbMax = pbPacket[4];
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 18), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+ break;
+ case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ {
+ PPDMIMOUNT const pDrvMount = pDevR3->pDrvMount;
+ if (pDrvMount && pDrvMount->pfnIsMounted(pDrvMount))
+ {
+ if (pbPacket[4] & 1)
+ pDrvMount->pfnLock(pDrvMount);
+ else
+ pDrvMount->pfnUnlock(pDrvMount);
+ atapiR3CmdOK(pCtl, s);
+ }
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ case SCSI_READ_10:
+ case SCSI_READ_12:
+ {
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ if (pbPacket[0] == SCSI_READ_10)
+ cSectors = scsiBE2H_U16(pbPacket + 7);
+ else
+ cSectors = scsiBE2H_U32(pbPacket + 6);
+ iATAPILBA = scsiBE2H_U32(pbPacket + 2);
+
+ if (cSectors == 0)
+ {
+ atapiR3CmdOK(pCtl, s);
+ break;
+ }
+
+ /* Check that the sector size is valid. */
+ VDREGIONDATAFORM enmDataForm = VDREGIONDATAFORM_INVALID;
+ int rc = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->pDrvMedia, iATAPILBA,
+ NULL, NULL, NULL, &enmDataForm);
+ if (RT_UNLIKELY( rc == VERR_NOT_FOUND
+ || ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)))
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on reading from places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (READ)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ else if ( enmDataForm != VDREGIONDATAFORM_MODE1_2048
+ && enmDataForm != VDREGIONDATAFORM_MODE1_2352
+ && enmDataForm != VDREGIONDATAFORM_MODE2_2336
+ && enmDataForm != VDREGIONDATAFORM_MODE2_2352
+ && enmDataForm != VDREGIONDATAFORM_RAW)
+ {
+ uint8_t abATAPISense[ATAPI_SENSE_SIZE];
+ RT_ZERO(abATAPISense);
+
+ abATAPISense[0] = 0x70 | (1 << 7);
+ abATAPISense[2] = (SCSI_SENSE_ILLEGAL_REQUEST & 0x0f) | SCSI_SENSE_FLAG_ILI;
+ scsiH2BE_U32(&abATAPISense[3], iATAPILBA);
+ abATAPISense[7] = 10;
+ abATAPISense[12] = SCSI_ASC_ILLEGAL_MODE_FOR_THIS_TRACK;
+ atapiR3CmdError(pCtl, s, &abATAPISense[0], sizeof(abATAPISense));
+ break;
+ }
+ atapiR3ReadSectors(pDevIns, pCtl, s, iATAPILBA, cSectors, 2048);
+ break;
+ }
+ case SCSI_READ_CD_MSF:
+ case SCSI_READ_CD:
+ {
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ if ((pbPacket[10] & 0x7) != 0)
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ if (pbPacket[0] == SCSI_READ_CD)
+ {
+ cSectors = (pbPacket[6] << 16) | (pbPacket[7] << 8) | pbPacket[8];
+ iATAPILBA = scsiBE2H_U32(pbPacket + 2);
+ }
+ else /* READ CD MSF */
+ {
+ iATAPILBA = scsiMSF2LBA(pbPacket + 3);
+ if (iATAPILBA > scsiMSF2LBA(pbPacket + 6))
+ {
+ Log2(("Start MSF %02u:%02u:%02u > end MSF %02u:%02u:%02u!\n", *(pbPacket + 3), *(pbPacket + 4), *(pbPacket + 5),
+ *(pbPacket + 6), *(pbPacket + 7), *(pbPacket + 8)));
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ cSectors = scsiMSF2LBA(pbPacket + 6) - iATAPILBA;
+ Log2(("Start MSF %02u:%02u:%02u -> LBA %u\n", *(pbPacket + 3), *(pbPacket + 4), *(pbPacket + 5), iATAPILBA));
+ Log2(("End MSF %02u:%02u:%02u -> %u sectors\n", *(pbPacket + 6), *(pbPacket + 7), *(pbPacket + 8), cSectors));
+ }
+ if (cSectors == 0)
+ {
+ atapiR3CmdOK(pCtl, s);
+ break;
+ }
+ if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on reading from places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (READ CD)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ /*
+ * If the LBA is in an audio track we are required to ignore pretty much all
+ * of the channel selection values (except 0x00) and map everything to 0x10
+ * which means read user data with a sector size of 2352 bytes.
+ *
+ * (MMC-6 chapter 6.19.2.6)
+ */
+ uint8_t uChnSel = pbPacket[9] & 0xf8;
+ VDREGIONDATAFORM enmDataForm;
+ int rc = pDevR3->pDrvMedia->pfnQueryRegionPropertiesForLba(pDevR3->pDrvMedia, iATAPILBA,
+ NULL, NULL, NULL, &enmDataForm);
+ AssertRC(rc);
+
+ if (enmDataForm == VDREGIONDATAFORM_CDDA)
+ {
+ if (uChnSel == 0)
+ {
+ /* nothing */
+ atapiR3CmdOK(pCtl, s);
+ }
+ else
+ atapiR3ReadSectors(pDevIns, pCtl, s, iATAPILBA, cSectors, 2352);
+ }
+ else
+ {
+ switch (uChnSel)
+ {
+ case 0x00:
+ /* nothing */
+ atapiR3CmdOK(pCtl, s);
+ break;
+ case 0x10:
+ /* normal read */
+ atapiR3ReadSectors(pDevIns, pCtl, s, iATAPILBA, cSectors, 2048);
+ break;
+ case 0xf8:
+ /* read all data */
+ atapiR3ReadSectors(pDevIns, pCtl, s, iATAPILBA, cSectors, 2352);
+ break;
+ default:
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM sector format not supported (%#x)\n", s->iLUN, pbPacket[9] & 0xf8));
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ }
+ break;
+ }
+ case SCSI_SEEK_10:
+ {
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ iATAPILBA = scsiBE2H_U32(pbPacket + 2);
+ if (iATAPILBA > s->cTotalSectors)
+ {
+ /* Rate limited logging, one log line per second. For
+ * guests that insist on seeking to places outside the
+ * valid area this often generates too many release log
+ * entries otherwise. */
+ static uint64_t uLastLogTS = 0;
+ if (RTTimeMilliTS() >= uLastLogTS + 1000)
+ {
+ LogRel(("PIIX3 ATA: LUN#%d: CD-ROM block number %Ld invalid (SEEK)\n", s->iLUN, (uint64_t)iATAPILBA));
+ uLastLogTS = RTTimeMilliTS();
+ }
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
+ break;
+ }
+ atapiR3CmdOK(pCtl, s);
+ ataSetStatus(pCtl, s, ATA_STAT_SEEK); /* Linux expects this. Required by ATAPI 2.x when seek completes. */
+ break;
+ }
+ case SCSI_START_STOP_UNIT:
+ {
+ int rc = VINF_SUCCESS;
+ switch (pbPacket[4] & 3)
+ {
+ case 0: /* 00 - Stop motor */
+ case 1: /* 01 - Start motor */
+ break;
+ case 2: /* 10 - Eject media */
+ {
+ /* This must be done from EMT. */
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PPDMIMOUNT pDrvMount = pDevR3->pDrvMount;
+ if (pDrvMount)
+ {
+ ataR3LockLeave(pDevIns, pCtl);
+
+ rc = PDMDevHlpVMReqPriorityCallWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pDrvMount->pfnUnmount, 3,
+ pDrvMount, false /*=fForce*/, true /*=fEject*/);
+ Assert(RT_SUCCESS(rc) || rc == VERR_PDM_MEDIA_LOCKED || rc == VERR_PDM_MEDIA_NOT_MOUNTED);
+ if (RT_SUCCESS(rc) && pThisCC->pMediaNotify)
+ {
+ rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY,
+ (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2,
+ pThisCC->pMediaNotify, s->iLUN);
+ AssertRC(rc);
+ }
+
+ ataR3LockEnter(pDevIns, pCtl);
+ }
+ else
+ rc = VINF_SUCCESS;
+ break;
+ }
+ case 3: /* 11 - Load media */
+ /** @todo rc = pDevR3->pDrvMount->pfnLoadMedia(pDevR3->pDrvMount) */
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ atapiR3CmdOK(pCtl, s);
+ ataSetStatus(pCtl, s, ATA_STAT_SEEK); /* Needed by NT 3.51/4.0, see @bugref{5869}. */
+ }
+ else
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
+ break;
+ }
+ case SCSI_MECHANISM_STATUS:
+ {
+ cbMax = scsiBE2H_U16(pbPacket + 8);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 8), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MECHANISM_STATUS, true);
+ break;
+ }
+ case SCSI_READ_TOC_PMA_ATIP:
+ {
+ uint8_t format;
+
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ /* SCSI MMC-3 spec says format is at offset 2 (lower 4 bits),
+ * but Linux kernel uses offset 9 (topmost 2 bits). Hope that
+ * the other field is clear... */
+ format = (pbPacket[2] & 0xf) | (pbPacket[9] >> 6);
+ switch (format)
+ {
+ case 0:
+ ataR3StartTransfer(pDevIns, pCtl, s, cbMax, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_NORMAL, true);
+ break;
+ case 1:
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 12), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_MULTI, true);
+ break;
+ case 2:
+ ataR3StartTransfer(pDevIns, pCtl, s, cbMax, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_RAW, true);
+ break;
+ default:
+ error_cmd:
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
+ break;
+ }
+ break;
+ }
+ case SCSI_READ_CAPACITY:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ ataR3StartTransfer(pDevIns, pCtl, s, 8, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_CAPACITY, true);
+ break;
+ case SCSI_READ_DISC_INFORMATION:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 34), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DISC_INFORMATION, true);
+ break;
+ case SCSI_READ_TRACK_INFORMATION:
+ if (s->cNotifiedMediaChange > 0)
+ {
+ s->cNotifiedMediaChange-- ;
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
+ break;
+ }
+ if (!pDevR3->pDrvMount || !pDevR3->pDrvMount->pfnIsMounted(pDevR3->pDrvMount))
+ {
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
+ break;
+ }
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 36), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TRACK_INFORMATION, true);
+ break;
+ case SCSI_GET_CONFIGURATION:
+ /* No media change stuff here, it can confuse Linux guests. */
+ cbMax = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 80), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_CONFIGURATION, true);
+ break;
+ case SCSI_INQUIRY:
+ cbMax = scsiBE2H_U16(pbPacket + 3);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 36), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_INQUIRY, true);
+ break;
+ case SCSI_READ_DVD_STRUCTURE:
+ cbMax = scsiBE2H_U16(pbPacket + 8);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbMax, 4), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DVD_STRUCTURE, true);
+ break;
+ default:
+ atapiR3CmdErrorSimple(pCtl, s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
+ break;
+ }
+}
+
+
+/*
+ * Parse ATAPI commands, passing them directly to the CD/DVD drive.
+ */
+static void atapiR3ParseCmdPassthrough(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ const uint8_t *pbPacket = &s->abATAPICmd[0];
+
+ /* Some cases we have to handle here. */
+ if ( pbPacket[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION
+ && ASMAtomicReadU32(&s->MediaEventStatus) != ATA_EVENT_STATUS_UNCHANGED)
+ {
+ uint32_t cbTransfer = scsiBE2H_U16(pbPacket + 7);
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(cbTransfer, 8), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
+ }
+ else if ( pbPacket[0] == SCSI_REQUEST_SENSE
+ && (s->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
+ ataR3StartTransfer(pDevIns, pCtl, s, RT_MIN(pbPacket[4], 18), PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
+ else
+ {
+ size_t cbBuf = 0;
+ size_t cbATAPISector = 0;
+ size_t cbTransfer = 0;
+ PDMMEDIATXDIR uTxDir = PDMMEDIATXDIR_NONE;
+ uint8_t u8ScsiSts = SCSI_STATUS_OK;
+
+ if (pbPacket[0] == SCSI_FORMAT_UNIT || pbPacket[0] == SCSI_GET_PERFORMANCE)
+ cbBuf = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
+
+ bool fPassthrough = ATAPIPassthroughParseCdb(pbPacket, sizeof(s->abATAPICmd), cbBuf, pDevR3->pTrackList,
+ &s->abATAPISense[0], sizeof(s->abATAPISense), &uTxDir, &cbTransfer,
+ &cbATAPISector, &u8ScsiSts);
+ if (fPassthrough)
+ {
+ s->cbATAPISector = (uint32_t)cbATAPISector;
+ Assert(s->cbATAPISector == (uint32_t)cbATAPISector);
+ Assert(cbTransfer == (uint32_t)cbTransfer);
+
+ /*
+ * Send a command to the drive, passing data in/out as required.
+ * Commands which exceed the I/O buffer size are split below
+ * or aborted if splitting is not implemented.
+ */
+ Log2(("ATAPI PT: max size %d\n", cbTransfer));
+ if (cbTransfer == 0)
+ uTxDir = PDMMEDIATXDIR_NONE;
+ ataR3StartTransfer(pDevIns, pCtl, s, (uint32_t)cbTransfer, uTxDir, ATAFN_BT_ATAPI_PASSTHROUGH_CMD, ATAFN_SS_ATAPI_PASSTHROUGH, true);
+ }
+ else if (u8ScsiSts == SCSI_STATUS_CHECK_CONDITION)
+ {
+ /* Sense data is already set, end the request and notify the guest. */
+ Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, s->abATAPISense[2] & 0x0f, SCSISenseText(s->abATAPISense[2] & 0x0f),
+ s->abATAPISense[12], s->abATAPISense[13], SCSISenseExtText(s->abATAPISense[12], s->abATAPISense[13])));
+ s->uATARegError = s->abATAPISense[2] << 4;
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_ERR);
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = 0;
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+ }
+ else if (u8ScsiSts == SCSI_STATUS_OK)
+ atapiR3CmdOK(pCtl, s);
+ }
+}
+
+
+static void atapiR3ParseCmd(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ const uint8_t *pbPacket;
+
+ pbPacket = s->abATAPICmd;
+# ifdef DEBUG
+ Log(("%s: LUN#%d DMA=%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0], SCSICmdText(pbPacket[0])));
+# else /* !DEBUG */
+ Log(("%s: LUN#%d DMA=%d CMD=%#04x\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0]));
+# endif /* !DEBUG */
+ Log2(("%s: limit=%#x packet: %.*Rhxs\n", __FUNCTION__, s->uATARegLCyl | (s->uATARegHCyl << 8), ATAPI_PACKET_SIZE, pbPacket));
+
+ if (s->fATAPIPassthrough)
+ atapiR3ParseCmdPassthrough(pDevIns, pCtl, s, pDevR3);
+ else
+ atapiR3ParseCmdVirtualATAPI(pDevIns, pCtl, s, pDevR3);
+}
+
+
+/**
+ * Sink/Source: PACKET
+ */
+static bool ataR3PacketSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ s->fDMA = !!(s->uATARegFeature & 1);
+ memcpy(s->abATAPICmd, s->abIOBuffer, ATAPI_PACKET_SIZE);
+ s->uTxDir = PDMMEDIATXDIR_NONE;
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ atapiR3ParseCmd(pDevIns, pCtl, s, pDevR3);
+ return false;
+}
+
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium removed" event
+ * from now on, regardless if there was a medium inserted or not.
+ */
+static void ataR3MediumRemoved(PATADEVSTATE s)
+{
+ ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_MEDIA_REMOVED);
+}
+
+
+/**
+ * SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium inserted". If
+ * there was already a medium inserted, don't forget to send the "medium
+ * removed" event first.
+ */
+static void ataR3MediumInserted(PATADEVSTATE s)
+{
+ uint32_t OldStatus, NewStatus;
+ do
+ {
+ OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
+ switch (OldStatus)
+ {
+ case ATA_EVENT_STATUS_MEDIA_CHANGED:
+ case ATA_EVENT_STATUS_MEDIA_REMOVED:
+ /* no change, we will send "medium removed" + "medium inserted" */
+ NewStatus = ATA_EVENT_STATUS_MEDIA_CHANGED;
+ break;
+ default:
+ NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
+ break;
+ }
+ } while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
+}
+
+
+/**
+ * @interface_method_impl{PDMIMOUNTNOTIFY,pfnMountNotify}
+ */
+static DECLCALLBACK(void) ataR3MountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IMountNotify);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pIfR3->pDevIns, PATASTATE);
+ PATADEVSTATE pIf = &RT_SAFE_SUBSCRIPT(RT_SAFE_SUBSCRIPT(pThis->aCts, pIfR3->iCtl).aIfs, pIfR3->iDev);
+ Log(("%s: changing LUN#%d\n", __FUNCTION__, pIfR3->iLUN));
+
+ /* Ignore the call if we're called while being attached. */
+ if (!pIfR3->pDrvMedia)
+ return;
+
+ uint32_t cRegions = pIfR3->pDrvMedia->pfnGetRegionCount(pIfR3->pDrvMedia);
+ for (uint32_t i = 0; i < cRegions; i++)
+ {
+ uint64_t cBlocks = 0;
+ int rc = pIfR3->pDrvMedia->pfnQueryRegionProperties(pIfR3->pDrvMedia, i, NULL, &cBlocks, NULL, NULL);
+ AssertRC(rc);
+ pIf->cTotalSectors += cBlocks;
+ }
+
+ LogRel(("PIIX3 ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough unchanged\n", pIf->iLUN, pIf->cTotalSectors));
+
+ /* Report media changed in TEST UNIT and other (probably incorrect) places. */
+ if (pIf->cNotifiedMediaChange < 2)
+ pIf->cNotifiedMediaChange = 1;
+ ataR3MediumInserted(pIf);
+ ataR3MediumTypeSet(pIf, ATA_MEDIA_TYPE_UNKNOWN);
+}
+
+/**
+ * @interface_method_impl{PDMIMOUNTNOTIFY,pfnUnmountNotify}
+ */
+static DECLCALLBACK(void) ataR3UnmountNotify(PPDMIMOUNTNOTIFY pInterface)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IMountNotify);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pIfR3->pDevIns, PATASTATE);
+ PATADEVSTATE pIf = &RT_SAFE_SUBSCRIPT(RT_SAFE_SUBSCRIPT(pThis->aCts, pIfR3->iCtl).aIfs, pIfR3->iDev);
+ Log(("%s:\n", __FUNCTION__));
+ pIf->cTotalSectors = 0;
+
+ /*
+ * Whatever I do, XP will not use the GET MEDIA STATUS nor the EVENT stuff.
+ * However, it will respond to TEST UNIT with a 0x6 0x28 (media changed) sense code.
+ * So, we'll give it 4 TEST UNIT command to catch up, two which the media is not
+ * present and 2 in which it is changed.
+ */
+ pIf->cNotifiedMediaChange = 1;
+ ataR3MediumRemoved(pIf);
+ ataR3MediumTypeSet(pIf, ATA_MEDIA_NO_DISC);
+}
+
+/**
+ * Begin Transfer: PACKET
+ */
+static void ataR3PacketBT(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->cbAtapiPassthroughTransfer = s->cbTotalTransfer;
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_CD;
+ Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY);
+}
+
+
+static void ataR3ResetDevice(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ LogFlowFunc(("\n"));
+ s->cMultSectors = ATA_MAX_MULT_SECTORS;
+ s->cNotifiedMediaChange = 0;
+ ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_UNCHANGED);
+ ASMAtomicWriteU32(&s->MediaTrackType, ATA_MEDIA_TYPE_UNKNOWN);
+ ataUnsetIRQ(pDevIns, pCtl, s);
+
+ s->uATARegSelect = 0x20;
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_SEEK);
+ ataR3SetSignature(s);
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->cbAtapiPassthroughTransfer = 0;
+ s->iIOBufferPIODataStart = 0;
+ s->iIOBufferPIODataEnd = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+ s->fDMA = false;
+ s->fATAPITransfer = false;
+ s->uATATransferMode = ATA_MODE_UDMA | 2; /* PIIX3 supports only up to UDMA2 */
+
+ s->XCHSGeometry = s->PCHSGeometry; /* Restore default CHS translation. */
+
+ s->uATARegFeature = 0;
+}
+
+
+static void ataR3DeviceDiag(PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ ataR3SetSignature(s);
+ if (s->fATAPI)
+ ataSetStatusValue(pCtl, s, 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(pCtl, s, ATA_STAT_READY | ATA_STAT_SEEK);
+ s->uATARegError = 0x01;
+}
+
+
+/**
+ * Sink/Source: EXECUTE DEVICE DIAGNOTIC
+ */
+static bool ataR3ExecuteDeviceDiagnosticSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ RT_NOREF(pDevIns, s, pDevR3);
+
+ /* EXECUTE DEVICE DIAGNOSTIC is a very special command which always
+ * gets executed, regardless of which device is selected. As a side
+ * effect, it always completes with device 0 selected.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ ataR3DeviceDiag(pCtl, &pCtl->aIfs[i]);
+
+ LogRel(("ATA: LUN#%d: EXECUTE DEVICE DIAGNOSTIC, status %02X\n", s->iLUN, s->uATARegStatus));
+ pCtl->iSelectedIf = 0;
+
+ return false;
+}
+
+
+/**
+ * Sink/Source: INITIALIZE DEVICE PARAMETERS
+ */
+static bool ataR3InitDevParmSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ RT_NOREF(pDevR3);
+ LogFlowFunc(("\n"));
+
+ /* Technical Note:
+ * On ST506 type drives with a separate controller, the INITIALIZE DRIVE PARAMETERS command was
+ * required to inform the controller of drive geometry. The controller needed to know the
+ * number of heads and sectors per track so that it could correctly advance to the next track
+ * or cylinder when executing multi-sector commands. Setting a geometry that didn't match the
+ * drive made very little sense because sectors had fixed CHS addresses. It was at best
+ * possible to reduce the drive's capacity by limiting the number of heads and/or sectors
+ * per track.
+ *
+ * IDE drives inherently have to know their true geometry, but most of them also support
+ * programmable translation that can be set through the INITIALIZE DEVICE PARAMETERS command.
+ * In fact most older IDE drives typically weren't operated using their default (native) geometry,
+ * and with newer IDE drives that's not even an option.
+ *
+ * Up to and including ATA-5, the standard defined a CHS to LBA translation (since ATA-6, CHS
+ * support is optional):
+ *
+ * LBA = (((cyl_num * heads_per_cyl) + head_num) * sectors_per_track) + sector_num - 1
+ *
+ * The INITIALIZE DEVICE PARAMETERS command sets the heads_per_cyl and sectors_per_track
+ * values used in the above formula.
+ *
+ * Drives must obviously support an INITIALIZE DRIVE PARAMETERS command matching the drive's
+ * default CHS translation. Everything else is optional.
+ *
+ * We support any geometry with non-zero sectors per track because there's no reason not to;
+ * this behavior is common in many if not most IDE drives.
+ */
+
+ PDMMEDIAGEOMETRY Geom = { 0 };
+
+ Geom.cHeads = (s->uATARegSelect & 0x0f) + 1; /* Effective range 1-16. */
+ Geom.cSectors = s->uATARegNSector; /* Range 0-255, zero is not valid. */
+
+ if (Geom.cSectors)
+ {
+ uint64_t cCylinders = s->cTotalSectors / (Geom.cHeads * Geom.cSectors);
+ Geom.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+
+ s->XCHSGeometry = Geom;
+
+ ataR3LockLeave(pDevIns, pCtl);
+ LogRel(("ATA: LUN#%d: INITIALIZE DEVICE PARAMETERS: %u sectors per track, %u heads\n",
+ s->iLUN, s->uATARegNSector, (s->uATARegSelect & 0x0f) + 1));
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ ataR3LockLeave(pDevIns, pCtl);
+ LogRel(("ATA: LUN#%d: INITIALIZE DEVICE PARAMETERS error (zero sectors per track)!\n", s->iLUN));
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ }
+ return false;
+}
+
+
+/**
+ * Sink/Source: RECALIBRATE
+ */
+static bool ataR3RecalibrateSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ RT_NOREF(pDevR3);
+ LogFlowFunc(("\n"));
+ ataR3LockLeave(pDevIns, pCtl);
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ return false;
+}
+
+
+static int ataR3TrimSectors(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3,
+ uint64_t u64Sector, uint32_t cSectors, bool *pfRedo)
+{
+ RTRANGE TrimRange;
+ int rc;
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ TrimRange.offStart = u64Sector * s->cbSector;
+ TrimRange.cbRange = cSectors * s->cbSector;
+
+ s->Led.Asserted.s.fWriting = s->Led.Actual.s.fWriting = 1;
+ rc = pDevR3->pDrvMedia->pfnDiscard(pDevR3->pDrvMedia, &TrimRange, 1);
+ s->Led.Actual.s.fWriting = 0;
+
+ if (RT_SUCCESS(rc))
+ *pfRedo = false;
+ else
+ *pfRedo = ataR3IsRedoSetWarning(pDevIns, pCtl, rc);
+
+ ataR3LockEnter(pDevIns, pCtl);
+ return rc;
+}
+
+
+/**
+ * Sink/Source: TRIM
+ */
+static bool ataR3TrimSS(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3)
+{
+ int rc = VERR_GENERAL_FAILURE;
+ uint32_t cRangesMax;
+ uint64_t *pu64Range = (uint64_t *)&s->abIOBuffer[0];
+ bool fRedo = false;
+
+ cRangesMax = RT_MIN(s->cbElementaryTransfer, sizeof(s->abIOBuffer)) / sizeof(uint64_t);
+ Assert(cRangesMax);
+
+ while (cRangesMax-- > 0)
+ {
+ if (ATA_RANGE_LENGTH_GET(*pu64Range) == 0)
+ break;
+
+ rc = ataR3TrimSectors(pDevIns, pCtl, s, pDevR3, *pu64Range & ATA_RANGE_LBA_MASK,
+ ATA_RANGE_LENGTH_GET(*pu64Range), &fRedo);
+ if (RT_FAILURE(rc))
+ break;
+
+ pu64Range++;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ s->iSourceSink = ATAFN_SS_NULL;
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ else
+ {
+ if (fRedo)
+ return fRedo;
+ if (s->cErrors++ < MAX_LOG_REL_ERRORS)
+ LogRel(("PIIX3 ATA: LUN#%d: disk trim error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
+ s->iLUN, rc, *pu64Range & ATA_RANGE_LBA_MASK, ATA_RANGE_LENGTH_GET(*pu64Range)));
+
+ /*
+ * Check if we got interrupted. We don't need to set status variables
+ * because the request was aborted.
+ */
+ if (rc != VERR_INTERRUPTED)
+ ataR3CmdError(pCtl, s, ID_ERR);
+ }
+
+ return false;
+}
+
+
+static void ataR3ParseCmd(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s, PATADEVSTATER3 pDevR3, uint8_t cmd)
+{
+# ifdef DEBUG
+ Log(("%s: LUN#%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, cmd, ATACmdText(cmd)));
+# else /* !DEBUG */
+ Log(("%s: LUN#%d CMD=%#04x\n", __FUNCTION__, s->iLUN, cmd));
+# endif /* !DEBUG */
+ s->fLBA48 = false;
+ s->fDMA = false;
+ if (cmd == ATA_IDLE_IMMEDIATE)
+ {
+ /* Detect Linux timeout recovery, first tries IDLE IMMEDIATE (which
+ * would overwrite the failing command unfortunately), then RESET. */
+ int32_t uCmdWait = -1;
+ uint64_t uNow = RTTimeNanoTS();
+ if (s->u64CmdTS)
+ uCmdWait = (uNow - s->u64CmdTS) / 1000;
+ LogRel(("PIIX3 ATA: LUN#%d: IDLE IMMEDIATE, CmdIf=%#04x (%d usec ago)\n",
+ s->iLUN, s->uATARegCommand, uCmdWait));
+ }
+ s->uATARegCommand = cmd;
+ switch (cmd)
+ {
+ case ATA_IDENTIFY_DEVICE:
+ if (pDevR3->pDrvMedia && !s->fATAPI)
+ ataR3StartTransfer(pDevIns, pCtl, s, 512, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_IDENTIFY, false);
+ else
+ {
+ if (s->fATAPI)
+ ataR3SetSignature(s);
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ ataUnsetStatus(pCtl, s, ATA_STAT_READY);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_RECALIBRATE:
+ if (s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_RECALIBRATE, false);
+ break;
+ case ATA_INITIALIZE_DEVICE_PARAMETERS:
+ if (s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_INITIALIZE_DEVICE_PARAMETERS, false);
+ break;
+ case ATA_SET_MULTIPLE_MODE:
+ if ( s->uATARegNSector != 0
+ && ( s->uATARegNSector > ATA_MAX_MULT_SECTORS
+ || (s->uATARegNSector & (s->uATARegNSector - 1)) != 0))
+ {
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ }
+ else
+ {
+ Log2(("%s: set multi sector count to %d\n", __FUNCTION__, s->uATARegNSector));
+ s->cMultSectors = s->uATARegNSector;
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_VERIFY_SECTORS_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_VERIFY_SECTORS:
+ case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES:
+ /* do sector number check ? */
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_SECTORS_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_SECTORS:
+ case ATA_READ_SECTORS_WITHOUT_RETRIES:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = 1;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_SECTORS_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_WRITE_SECTORS:
+ case ATA_WRITE_SECTORS_WITHOUT_RETRIES:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = 1;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_MULTIPLE_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_MULTIPLE:
+ if (!pDevR3->pDrvMedia || !s->cMultSectors || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_MULTIPLE_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_WRITE_MULTIPLE:
+ if (!pDevR3->pDrvMedia || !s->cMultSectors || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = s->cMultSectors;
+ s->iCurLBA = ataR3GetSector(s);
+ ataR3StartTransfer(pDevIns, pCtl, s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_DMA_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_READ_DMA:
+ case ATA_READ_DMA_WITHOUT_RETRIES:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->iCurLBA = ataR3GetSector(s);
+ s->fDMA = true;
+ ataR3StartTransfer(pDevIns, pCtl, s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
+ break;
+ case ATA_WRITE_DMA_EXT:
+ s->fLBA48 = true;
+ RT_FALL_THRU();
+ case ATA_WRITE_DMA:
+ case ATA_WRITE_DMA_WITHOUT_RETRIES:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
+ s->iCurLBA = ataR3GetSector(s);
+ s->fDMA = true;
+ ataR3StartTransfer(pDevIns, pCtl, s, ataR3GetNSectors(s) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
+ break;
+ case ATA_READ_NATIVE_MAX_ADDRESS_EXT:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ s->fLBA48 = true;
+ ataR3SetSector(s, s->cTotalSectors - 1);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SEEK: /* Used by the SCO OpenServer. Command is marked as obsolete */
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_READ_NATIVE_MAX_ADDRESS:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ ataR3SetSector(s, RT_MIN(s->cTotalSectors, 1 << 28) - 1);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_CHECK_POWER_MODE:
+ s->uATARegNSector = 0xff; /* drive active or idle */
+ ataR3CmdOK(pCtl, s, 0);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_SET_FEATURES:
+ Log2(("%s: feature=%#x\n", __FUNCTION__, s->uATARegFeature));
+ if (!pDevR3->pDrvMedia)
+ goto abort_cmd;
+ switch (s->uATARegFeature)
+ {
+ case 0x02: /* write cache enable */
+ Log2(("%s: write cache enable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0xaa: /* read look-ahead enable */
+ Log2(("%s: read look-ahead enable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x55: /* read look-ahead disable */
+ Log2(("%s: read look-ahead disable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0xcc: /* reverting to power-on defaults enable */
+ Log2(("%s: revert to power-on defaults enable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x66: /* reverting to power-on defaults disable */
+ Log2(("%s: revert to power-on defaults disable\n", __FUNCTION__));
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case 0x82: /* write cache disable */
+ Log2(("%s: write cache disable\n", __FUNCTION__));
+ /* As per the ATA/ATAPI-6 specs, a write cache disable
+ * command MUST flush the write buffers to disc. */
+ ataR3StartTransfer(pDevIns, pCtl, s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+ break;
+ case 0x03: { /* set transfer mode */
+ Log2(("%s: transfer mode %#04x\n", __FUNCTION__, s->uATARegNSector));
+ switch (s->uATARegNSector & 0xf8)
+ {
+ case 0x00: /* PIO default */
+ case 0x08: /* PIO mode */
+ break;
+ case ATA_MODE_MDMA: /* MDMA mode */
+ s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_MDMA_MODE_MAX);
+ break;
+ case ATA_MODE_UDMA: /* UDMA mode */
+ s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_UDMA_MODE_MAX);
+ break;
+ default:
+ goto abort_cmd;
+ }
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ }
+ default:
+ goto abort_cmd;
+ }
+ /*
+ * OS/2 workarond:
+ * The OS/2 IDE driver from MCP2 appears to rely on the feature register being
+ * reset here. According to the specification, this is a driver bug as the register
+ * contents are undefined after the call. This means we can just as well reset it.
+ */
+ s->uATARegFeature = 0;
+ break;
+ case ATA_FLUSH_CACHE_EXT:
+ case ATA_FLUSH_CACHE:
+ if (!pDevR3->pDrvMedia || s->fATAPI)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
+ break;
+ case ATA_STANDBY_IMMEDIATE:
+ ataR3CmdOK(pCtl, s, 0);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ case ATA_IDLE_IMMEDIATE:
+ LogRel(("PIIX3 ATA: LUN#%d: aborting current command\n", s->iLUN));
+ ataR3AbortCurrentCommand(pDevIns, pCtl, s, false);
+ break;
+ case ATA_SLEEP:
+ ataR3CmdOK(pCtl, s, 0);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ break;
+ /* ATAPI commands */
+ case ATA_IDENTIFY_PACKET_DEVICE:
+ if (s->fATAPI)
+ ataR3StartTransfer(pDevIns, pCtl, s, 512, PDMMEDIATXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_ATAPI_IDENTIFY, false);
+ else
+ {
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ }
+ break;
+ case ATA_EXECUTE_DEVICE_DIAGNOSTIC:
+ ataR3StartTransfer(pDevIns, pCtl, s, 0, PDMMEDIATXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC, false);
+ break;
+ case ATA_DEVICE_RESET:
+ if (!s->fATAPI)
+ goto abort_cmd;
+ LogRel(("PIIX3 ATA: LUN#%d: performing device RESET\n", s->iLUN));
+ ataR3AbortCurrentCommand(pDevIns, pCtl, s, true);
+ break;
+ case ATA_PACKET:
+ if (!s->fATAPI)
+ goto abort_cmd;
+ /* overlapping commands not supported */
+ if (s->uATARegFeature & 0x02)
+ goto abort_cmd;
+ ataR3StartTransfer(pDevIns, pCtl, s, ATAPI_PACKET_SIZE, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_PACKET, ATAFN_SS_PACKET, false);
+ break;
+ case ATA_DATA_SET_MANAGEMENT:
+ if (!pDevR3->pDrvMedia || !pDevR3->pDrvMedia->pfnDiscard)
+ goto abort_cmd;
+ if ( !(s->uATARegFeature & UINT8_C(0x01))
+ || (s->uATARegFeature & ~UINT8_C(0x01)))
+ goto abort_cmd;
+ s->fDMA = true;
+ ataR3StartTransfer(pDevIns, pCtl, s, (s->uATARegNSectorHOB << 8 | s->uATARegNSector) * s->cbSector, PDMMEDIATXDIR_TO_DEVICE, ATAFN_BT_NULL, ATAFN_SS_TRIM, false);
+ break;
+ default:
+ abort_cmd:
+ ataR3CmdError(pCtl, s, ABRT_ERR);
+ if (s->fATAPI)
+ ataUnsetStatus(pCtl, s, ATA_STAT_READY);
+ ataHCSetIRQ(pDevIns, pCtl, s); /* Shortcut, do not use AIO thread. */
+ break;
+ }
+}
+
+# endif /* IN_RING3 */
+#endif /* IN_RING0 || IN_RING3 */
+
+/*
+ * Note: There are four distinct cases of port I/O handling depending on
+ * which devices (if any) are attached to an IDE channel:
+ *
+ * 1) No device attached. No response to writes or reads (i.e. reads return
+ * all bits set).
+ *
+ * 2) Both devices attached. Reads and writes are processed normally.
+ *
+ * 3) Device 0 only. If device 0 is selected, normal behavior applies. But
+ * if Device 1 is selected, writes are still directed to Device 0 (except
+ * commands are not executed), reads from control/command registers are
+ * directed to Device 0, but status/alt status reads return 0. If Device 1
+ * is a PACKET device, all reads return 0. See ATAPI-6 clause 9.16.1 and
+ * Table 18 in clause 7.1.
+ *
+ * 4) Device 1 only - non-standard(!). Device 1 can't tell if Device 0 is
+ * present or not and behaves the same. That means if Device 0 is selected,
+ * Device 1 responds to writes (except commands are not executed) but does
+ * not respond to reads. If Device 1 selected, normal behavior applies.
+ * See ATAPI-6 clause 9.16.2 and Table 15 in clause 7.1.
+ */
+
+static VBOXSTRICTRC ataIOPortWriteU8(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t addr, uint32_t val, uintptr_t iCtl)
+{
+ RT_NOREF(iCtl);
+ Log2(("%s: LUN#%d write addr=%#x val=%#04x\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN, addr, val));
+ addr &= 7;
+ switch (addr)
+ {
+ case 0:
+ break;
+ case 1: /* feature register */
+ /* NOTE: data is written to the two drives */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegFeatureHOB = pCtl->aIfs[0].uATARegFeature;
+ pCtl->aIfs[1].uATARegFeatureHOB = pCtl->aIfs[1].uATARegFeature;
+ pCtl->aIfs[0].uATARegFeature = val;
+ pCtl->aIfs[1].uATARegFeature = val;
+ break;
+ case 2: /* sector count */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegNSectorHOB = pCtl->aIfs[0].uATARegNSector;
+ pCtl->aIfs[1].uATARegNSectorHOB = pCtl->aIfs[1].uATARegNSector;
+ pCtl->aIfs[0].uATARegNSector = val;
+ pCtl->aIfs[1].uATARegNSector = val;
+ break;
+ case 3: /* sector number */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegSectorHOB = pCtl->aIfs[0].uATARegSector;
+ pCtl->aIfs[1].uATARegSectorHOB = pCtl->aIfs[1].uATARegSector;
+ pCtl->aIfs[0].uATARegSector = val;
+ pCtl->aIfs[1].uATARegSector = val;
+ break;
+ case 4: /* cylinder low */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegLCylHOB = pCtl->aIfs[0].uATARegLCyl;
+ pCtl->aIfs[1].uATARegLCylHOB = pCtl->aIfs[1].uATARegLCyl;
+ pCtl->aIfs[0].uATARegLCyl = val;
+ pCtl->aIfs[1].uATARegLCyl = val;
+ break;
+ case 5: /* cylinder high */
+ pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
+ pCtl->aIfs[0].uATARegHCylHOB = pCtl->aIfs[0].uATARegHCyl;
+ pCtl->aIfs[1].uATARegHCylHOB = pCtl->aIfs[1].uATARegHCyl;
+ pCtl->aIfs[0].uATARegHCyl = val;
+ pCtl->aIfs[1].uATARegHCyl = val;
+ break;
+ case 6: /* drive/head */
+ pCtl->aIfs[0].uATARegSelect = (val & ~0x10) | 0xa0;
+ pCtl->aIfs[1].uATARegSelect = (val | 0x10) | 0xa0;
+ if (((val >> 4) & ATA_SELECTED_IF_MASK) != pCtl->iSelectedIf)
+ {
+ /* select another drive */
+ uintptr_t const iSelectedIf = (val >> 4) & ATA_SELECTED_IF_MASK;
+ pCtl->iSelectedIf = (uint8_t)iSelectedIf;
+ /* The IRQ line is multiplexed between the two drives, so
+ * update the state when switching to another drive. Only need
+ * to update interrupt line if it is enabled and there is a
+ * state change. */
+ if ( !(pCtl->aIfs[iSelectedIf].uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ)
+ && pCtl->aIfs[iSelectedIf].fIrqPending != pCtl->aIfs[iSelectedIf ^ 1].fIrqPending)
+ {
+ if (pCtl->aIfs[iSelectedIf].fIrqPending)
+ {
+ Log2(("%s: LUN#%d asserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[iSelectedIf].iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if
+ * the interrupt line is asserted. It monitors the line
+ * for a rising edge. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 1);
+ }
+ else
+ {
+ Log2(("%s: LUN#%d deasserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[iSelectedIf].iLUN));
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 0);
+ }
+ }
+ }
+ break;
+ default:
+ case 7: /* command */
+ {
+ /* ignore commands to non-existent device */
+ uintptr_t iSelectedIf = pCtl->iSelectedIf & ATA_SELECTED_IF_MASK;
+ PATADEVSTATE pDev = &pCtl->aIfs[iSelectedIf];
+ if (iSelectedIf && !pDev->fPresent) /** @todo r=bird the iSelectedIf test here looks bogus... explain. */
+ break;
+#ifndef IN_RING3
+ /* Don't do anything complicated in GC */
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else /* IN_RING3 */
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ ataUnsetIRQ(pDevIns, pCtl, &pCtl->aIfs[iSelectedIf]);
+ ataR3ParseCmd(pDevIns, pCtl, &pCtl->aIfs[iSelectedIf], &pThisCC->aCts[iCtl].aIfs[iSelectedIf], val);
+ break;
+#endif /* !IN_RING3 */
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+static VBOXSTRICTRC ataIOPortReadU8(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t addr, uint32_t *pu32)
+{
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+ uint32_t val;
+ bool fHOB;
+
+ /* Check if the guest is reading from a non-existent device. */
+ if (RT_LIKELY(s->fPresent))
+ { /* likely */ }
+ else
+ {
+ if (pCtl->iSelectedIf) /* Device 1 selected, Device 0 responding for it. */
+ {
+ Assert(pCtl->aIfs[0].fPresent);
+
+ /* When an ATAPI device 0 responds for non-present device 1, it generally
+ * returns zeros on reads. The Error register is an exception. See clause 7.1,
+ * table 16 in ATA-6 specification.
+ */
+ if (((addr & 7) != 1) && pCtl->aIfs[0].fATAPI)
+ {
+ Log2(("%s: addr=%#x, val=0: LUN#%d not attached/LUN#%d ATAPI\n", __FUNCTION__, addr, s->iLUN, pCtl->aIfs[0].iLUN));
+ *pu32 = 0;
+ return VINF_SUCCESS;
+ }
+ /* Else handle normally. */
+ }
+ else /* Device 0 selected (but not present). */
+ {
+ /* Because device 1 has no way to tell if there is device 0, the behavior is the same
+ * as for an empty bus; see comments in ataIOPortReadEmptyBus(). Note that EFI (TianoCore)
+ * relies on this behavior when detecting devices.
+ */
+ *pu32 = ATA_EMPTY_BUS_DATA;
+ Log2(("%s: addr=%#x: LUN#%d not attached, val=%#02x\n", __FUNCTION__, addr, s->iLUN, *pu32));
+ return VINF_SUCCESS;
+ }
+ }
+
+ fHOB = !!(s->uATARegDevCtl & (1 << 7));
+ switch (addr & 7)
+ {
+ case 0: /* data register */
+ val = 0xff;
+ break;
+ case 1: /* error register */
+ /* The ATA specification is very terse when it comes to specifying
+ * the precise effects of reading back the error/feature register.
+ * The error register (read-only) shares the register number with
+ * the feature register (write-only), so it seems that it's not
+ * necessary to support the usual HOB readback here. */
+ if (!s->fPresent)
+ val = 0;
+ else
+ val = s->uATARegError;
+ break;
+ case 2: /* sector count */
+ if (fHOB)
+ val = s->uATARegNSectorHOB;
+ else
+ val = s->uATARegNSector;
+ break;
+ case 3: /* sector number */
+ if (fHOB)
+ val = s->uATARegSectorHOB;
+ else
+ val = s->uATARegSector;
+ break;
+ case 4: /* cylinder low */
+ if (fHOB)
+ val = s->uATARegLCylHOB;
+ else
+ val = s->uATARegLCyl;
+ break;
+ case 5: /* cylinder high */
+ if (fHOB)
+ val = s->uATARegHCylHOB;
+ else
+ val = s->uATARegHCyl;
+ break;
+ case 6: /* drive/head */
+ /* This register must always work as long as there is at least
+ * one drive attached to the controller. It is common between
+ * both drives anyway (completely identical content). */
+ if (!pCtl->aIfs[0].fPresent && !pCtl->aIfs[1].fPresent)
+ val = 0;
+ else
+ val = s->uATARegSelect;
+ break;
+ default:
+ case 7: /* primary status */
+ {
+ if (!s->fPresent)
+ val = 0;
+ else
+ val = s->uATARegStatus;
+
+ /* Give the async I/O thread an opportunity to make progress,
+ * don't let it starve by guests polling frequently. EMT has a
+ * lower priority than the async I/O thread, but sometimes the
+ * host OS doesn't care. With some guests we are only allowed to
+ * be busy for about 5 milliseconds in some situations. Note that
+ * this is no guarantee for any other VBox thread getting
+ * scheduled, so this just lowers the CPU load a bit when drives
+ * are busy. It cannot help with timing problems. */
+ if (val & ATA_STAT_BUSY)
+ {
+#ifdef IN_RING3
+ /* @bugref{1960}: Don't yield all the time, unless it's a reset (can be tricky). */
+ bool fYield = (s->cBusyStatusHackR3++ & s->cBusyStatusHackR3Rate) == 0
+ || pCtl->fReset;
+
+ ataR3LockLeave(pDevIns, pCtl);
+
+ /*
+ * The thread might be stuck in an I/O operation due to a high I/O
+ * load on the host (see @bugref{3301}). To perform the reset
+ * successfully we interrupt the operation by sending a signal to
+ * the thread if the thread didn't responded in 10ms.
+ *
+ * This works only on POSIX hosts (Windows has a CancelSynchronousIo
+ * function which does the same but it was introduced with Vista) but
+ * so far this hang was only observed on Linux and Mac OS X.
+ *
+ * This is a workaround and needs to be solved properly.
+ */
+ if (pCtl->fReset)
+ {
+ uint64_t u64ResetTimeStop = RTTimeMilliTS();
+ if (u64ResetTimeStop - pCtl->u64ResetTime >= 10)
+ {
+ LogRel(("PIIX3 ATA LUN#%d: Async I/O thread probably stuck in operation, interrupting\n", s->iLUN));
+ pCtl->u64ResetTime = u64ResetTimeStop;
+# ifndef RT_OS_WINDOWS /* We've got this API on windows, but it doesn't necessarily interrupt I/O. */
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PATACONTROLLERR3 pCtlR3 = &RT_SAFE_SUBSCRIPT(pThisCC->aCts, pCtl->iCtl);
+ RTThreadPoke(pCtlR3->hAsyncIOThread);
+# endif
+ Assert(fYield);
+ }
+ }
+
+ if (fYield)
+ {
+ STAM_REL_PROFILE_ADV_START(&s->StatStatusYields, a);
+ RTThreadYield();
+ STAM_REL_PROFILE_ADV_STOP(&s->StatStatusYields, a);
+ }
+ ASMNopPause();
+
+ ataR3LockEnter(pDevIns, pCtl);
+
+ val = s->uATARegStatus;
+#else /* !IN_RING3 */
+ /* Cannot yield CPU in raw-mode and ring-0 context. And switching
+ * to host context for each and every busy status is too costly,
+ * especially on SMP systems where we don't gain much by
+ * yielding the CPU to someone else. */
+ if ((s->cBusyStatusHackRZ++ & s->cBusyStatusHackRZRate) == 1)
+ {
+ s->cBusyStatusHackR3 = 0; /* Forces a yield. */
+ return VINF_IOM_R3_IOPORT_READ;
+ }
+#endif /* !IN_RING3 */
+ }
+ else
+ {
+ s->cBusyStatusHackRZ = 0;
+ s->cBusyStatusHackR3 = 0;
+ }
+ ataUnsetIRQ(pDevIns, pCtl, s);
+ break;
+ }
+ }
+ Log2(("%s: LUN#%d addr=%#x val=%#04x\n", __FUNCTION__, s->iLUN, addr, val));
+ *pu32 = val;
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Read the Alternate status register. Does not affect interrupts.
+ */
+static uint32_t ataStatusRead(PATACONTROLLER pCtl, uint32_t uIoPortForLog)
+{
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+ uint32_t val;
+ RT_NOREF(uIoPortForLog);
+
+ Assert(pCtl->aIfs[0].fPresent || pCtl->aIfs[1].fPresent); /* Channel must not be empty. */
+ if (pCtl->iSelectedIf == 1 && !s->fPresent)
+ val = 0; /* Device 1 selected, Device 0 responding for it. */
+ else
+ val = s->uATARegStatus;
+ Log2(("%s: LUN#%d read addr=%#x val=%#04x\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN, uIoPortForLog, val));
+ return val;
+}
+
+static int ataControlWrite(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t val, uint32_t uIoPortForLog)
+{
+ RT_NOREF(uIoPortForLog);
+#ifndef IN_RING3
+ if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_RESET)
+ return VINF_IOM_R3_IOPORT_WRITE; /* The RESET stuff is too complicated for RC+R0. */
+#endif /* !IN_RING3 */
+
+ Log2(("%s: LUN#%d write addr=%#x val=%#04x\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN, uIoPortForLog, val));
+ /* RESET is common for both drives attached to a controller. */
+ if ( !(pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET)
+ && (val & ATA_DEVCTL_RESET))
+ {
+#ifdef IN_RING3
+ /* Software RESET low to high */
+ int32_t uCmdWait0 = -1;
+ int32_t uCmdWait1 = -1;
+ uint64_t uNow = RTTimeNanoTS();
+ if (pCtl->aIfs[0].u64CmdTS)
+ uCmdWait0 = (uNow - pCtl->aIfs[0].u64CmdTS) / 1000;
+ if (pCtl->aIfs[1].u64CmdTS)
+ uCmdWait1 = (uNow - pCtl->aIfs[1].u64CmdTS) / 1000;
+ LogRel(("PIIX3 ATA: Ctl#%d: RESET, DevSel=%d AIOIf=%d CmdIf0=%#04x (%d usec ago) CmdIf1=%#04x (%d usec ago)\n",
+ pCtl->iCtl, pCtl->iSelectedIf, pCtl->iAIOIf,
+ pCtl->aIfs[0].uATARegCommand, uCmdWait0,
+ pCtl->aIfs[1].uATARegCommand, uCmdWait1));
+ pCtl->fReset = true;
+ /* Everything must be done after the reset flag is set, otherwise
+ * there are unavoidable races with the currently executing request
+ * (which might just finish in the mean time). */
+ pCtl->fChainedTransfer = false;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ {
+ ataR3ResetDevice(pDevIns, pCtl, &pCtl->aIfs[i]);
+ /* The following cannot be done using ataSetStatusValue() since the
+ * reset flag is already set, which suppresses all status changes. */
+ pCtl->aIfs[i].uATARegStatus = ATA_STAT_BUSY | ATA_STAT_SEEK;
+ Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, pCtl->aIfs[i].iLUN, pCtl->aIfs[i].uATARegStatus));
+ pCtl->aIfs[i].uATARegError = 0x01;
+ }
+ pCtl->iSelectedIf = 0;
+ ataR3AsyncIOClearRequests(pDevIns, pCtl);
+ Log2(("%s: Ctl#%d: message to async I/O thread, resetA\n", __FUNCTION__, pCtl->iCtl));
+ if (val & ATA_DEVCTL_HOB)
+ {
+ val &= ~ATA_DEVCTL_HOB;
+ Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+ }
+
+ /* Save the timestamp we started the reset. */
+ pCtl->u64ResetTime = RTTimeMilliTS();
+
+ /* Issue the reset request now. */
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataResetARequest);
+#else /* !IN_RING3 */
+ AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+ }
+ else if ( (pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET)
+ && !(val & ATA_DEVCTL_RESET))
+ {
+#ifdef IN_RING3
+ /* Software RESET high to low */
+ Log(("%s: deasserting RESET\n", __FUNCTION__));
+ Log2(("%s: Ctl#%d: message to async I/O thread, resetC\n", __FUNCTION__, pCtl->iCtl));
+ if (val & ATA_DEVCTL_HOB)
+ {
+ val &= ~ATA_DEVCTL_HOB;
+ Log2(("%s: ignored setting HOB\n", __FUNCTION__));
+ }
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataResetCRequest);
+#else /* !IN_RING3 */
+ AssertMsgFailed(("RESET handling is too complicated for GC\n"));
+#endif /* IN_RING3 */
+ }
+
+ /* Change of interrupt disable flag. Update interrupt line if interrupt
+ * is pending on the current interface. */
+ if ( ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_DISABLE_IRQ)
+ && pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].fIrqPending)
+ {
+ if (!(val & ATA_DEVCTL_DISABLE_IRQ))
+ {
+ Log2(("%s: LUN#%d asserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN));
+ /* The BMDMA unit unconditionally sets BM_STATUS_INT if the
+ * interrupt line is asserted. It monitors the line for a rising
+ * edge. */
+ pCtl->BmDma.u8Status |= BM_STATUS_INT;
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 1);
+ }
+ else
+ {
+ Log2(("%s: LUN#%d deasserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].iLUN));
+ if (pCtl->irq == 16)
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+ else
+ PDMDevHlpISASetIrq(pDevIns, pCtl->irq, 0);
+ }
+ }
+
+ if (val & ATA_DEVCTL_HOB)
+ Log2(("%s: set HOB\n", __FUNCTION__));
+
+ pCtl->aIfs[0].uATARegDevCtl = val;
+ pCtl->aIfs[1].uATARegDevCtl = val;
+
+ return VINF_SUCCESS;
+}
+
+#if defined(IN_RING0) || defined(IN_RING3)
+
+static void ataHCPIOTransfer(PPDMDEVINS pDevIns, PATACONTROLLER pCtl)
+{
+ PATADEVSTATE s;
+
+ s = &pCtl->aIfs[pCtl->iAIOIf & ATA_SELECTED_IF_MASK];
+ Log3(("%s: if=%p\n", __FUNCTION__, s));
+
+ if (s->cbTotalTransfer && s->iIOBufferCur > s->iIOBufferEnd)
+ {
+# ifdef IN_RING3
+ LogRel(("PIIX3 ATA: LUN#%d: %s data in the middle of a PIO transfer - VERY SLOW\n",
+ s->iLUN, s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE ? "loading" : "storing"));
+ /* Any guest OS that triggers this case has a pathetic ATA driver.
+ * In a real system it would block the CPU via IORDY, here we do it
+ * very similarly by not continuing with the current instruction
+ * until the transfer to/from the storage medium is completed. */
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ bool fRedo;
+ uint8_t status = s->uATARegStatus;
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PATADEVSTATER3 pDevR3 = &RT_SAFE_SUBSCRIPT(RT_SAFE_SUBSCRIPT(pThisCC->aCts, pCtl->iCtl).aIfs, s->iDev);
+
+ ataSetStatusValue(pCtl, s, ATA_STAT_BUSY);
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo))
+ return;
+ ataSetStatusValue(pCtl, s, status);
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ Assert(iSourceSink == ATAFN_SS_NULL);
+# else
+ AssertReleaseFailed();
+# endif
+ }
+ if (s->cbTotalTransfer)
+ {
+ if (s->fATAPITransfer)
+ ataHCPIOTransferLimitATAPI(s);
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+
+ Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+ __FUNCTION__, s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE ? "T2I" : "I2T",
+ s->cbTotalTransfer, s->cbElementaryTransfer,
+ s->iIOBufferCur, s->iIOBufferEnd));
+ ataHCPIOTransferStart(pCtl, s, s->iIOBufferCur, s->cbElementaryTransfer);
+ s->cbTotalTransfer -= s->cbElementaryTransfer;
+ s->iIOBufferCur += s->cbElementaryTransfer;
+
+ if (s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ }
+ else
+ ataHCPIOTransferStop(pDevIns, pCtl, s);
+}
+
+
+DECLINLINE(void) ataHCPIOTransferFinish(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATADEVSTATE s)
+{
+ /* Do not interfere with RESET processing if the PIO transfer finishes
+ * while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed continuing PIO transfer as RESET is active\n", __FUNCTION__, pCtl->iCtl));
+ return;
+ }
+
+ if ( s->uTxDir == PDMMEDIATXDIR_TO_DEVICE
+ || ( s->iSourceSink != ATAFN_SS_NULL
+ && s->iIOBufferCur >= s->iIOBufferEnd))
+ {
+ /* Need to continue the transfer in the async I/O thread. This is
+ * the case for write operations or generally for not yet finished
+ * transfers (some data might need to be read). */
+ ataSetStatus(pCtl, s, ATA_STAT_BUSY);
+ ataUnsetStatus(pCtl, s, ATA_STAT_READY | ATA_STAT_DRQ);
+
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing PIO transfer\n", __FUNCTION__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataPIORequest);
+ }
+ else
+ {
+ /* Either everything finished (though some data might still be pending)
+ * or some data is pending before the next read is due. */
+
+ /* Continue a previously started transfer. */
+ ataUnsetStatus(pCtl, s, ATA_STAT_DRQ);
+ ataSetStatus(pCtl, s, ATA_STAT_READY);
+
+ if (s->cbTotalTransfer)
+ {
+ /* There is more to transfer, happens usually for large ATAPI
+ * reads - the protocol limits the chunk size to 65534 bytes. */
+ ataHCPIOTransfer(pDevIns, pCtl);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ }
+ else
+ {
+ Log2(("%s: Ctl#%d: skipping message to async I/O thread, ending PIO transfer\n", __FUNCTION__, pCtl->iCtl));
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pDevIns, pCtl);
+ Assert(!pCtl->fRedo);
+ }
+ }
+}
+
+#endif /* IN_RING0 || IN_RING3 */
+
+/**
+ * Fallback for ataCopyPioData124 that handles unaligned and out of bounds cases.
+ *
+ * @param pIf The device interface to work with.
+ * @param pbDst The destination buffer.
+ * @param pbSrc The source buffer.
+ * @param offStart The start offset (iIOBufferPIODataStart).
+ * @param cbCopy The number of bytes to copy, either 1, 2 or 4 bytes.
+ */
+DECL_NO_INLINE(static, void) ataCopyPioData124Slow(PATADEVSTATE pIf, uint8_t *pbDst, const uint8_t *pbSrc,
+ uint32_t offStart, uint32_t cbCopy)
+{
+ uint32_t const offNext = offStart + cbCopy;
+ uint32_t const cbIOBuffer = RT_MIN(pIf->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE);
+
+ if (offStart + cbCopy > cbIOBuffer)
+ {
+ Log(("%s: cbCopy=%#x offStart=%#x cbIOBuffer=%#x offNext=%#x (iIOBufferPIODataEnd=%#x)\n",
+ __FUNCTION__, cbCopy, offStart, cbIOBuffer, offNext, pIf->iIOBufferPIODataEnd));
+ if (offStart < cbIOBuffer)
+ cbCopy = cbIOBuffer - offStart;
+ else
+ cbCopy = 0;
+ }
+
+ switch (cbCopy)
+ {
+ case 4: pbDst[3] = pbSrc[3]; RT_FALL_THRU();
+ case 3: pbDst[2] = pbSrc[2]; RT_FALL_THRU();
+ case 2: pbDst[1] = pbSrc[1]; RT_FALL_THRU();
+ case 1: pbDst[0] = pbSrc[0]; RT_FALL_THRU();
+ case 0: break;
+ default: AssertFailed(); /* impossible */
+ }
+
+ pIf->iIOBufferPIODataStart = offNext;
+
+}
+
+
+/**
+ * Work for ataDataWrite & ataDataRead that copies data without using memcpy.
+ *
+ * This also updates pIf->iIOBufferPIODataStart.
+ *
+ * The two buffers are either stack (32-bit aligned) or somewhere within
+ * pIf->abIOBuffer.
+ *
+ * @param pIf The device interface to work with.
+ * @param pbDst The destination buffer.
+ * @param pbSrc The source buffer.
+ * @param offStart The start offset (iIOBufferPIODataStart).
+ * @param cbCopy The number of bytes to copy, either 1, 2 or 4 bytes.
+ */
+DECLINLINE(void) ataCopyPioData124(PATADEVSTATE pIf, uint8_t *pbDst, const uint8_t *pbSrc, uint32_t offStart, uint32_t cbCopy)
+{
+ /*
+ * Quick bounds checking can be done by checking that the abIOBuffer offset
+ * (iIOBufferPIODataStart) is aligned at the transfer size (which is ASSUMED
+ * to be 1, 2 or 4). However, since we're paranoid and don't currently
+ * trust iIOBufferPIODataEnd to be within bounds, we current check against the
+ * IO buffer size too.
+ */
+ Assert(cbCopy == 1 || cbCopy == 2 || cbCopy == 4);
+ if (RT_LIKELY( !(offStart & (cbCopy - 1))
+ && offStart + cbCopy <= RT_MIN(pIf->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE)))
+ {
+ switch (cbCopy)
+ {
+ case 4: *(uint32_t *)pbDst = *(uint32_t const *)pbSrc; break;
+ case 2: *(uint16_t *)pbDst = *(uint16_t const *)pbSrc; break;
+ case 1: *pbDst = *pbSrc; break;
+ }
+ pIf->iIOBufferPIODataStart = offStart + cbCopy;
+ }
+ else
+ ataCopyPioData124Slow(pIf, pbDst, pbSrc, offStart, cbCopy);
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for primary port range OUT operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWrite1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == pCtl->IOPortBase1);
+ Assert(cb == 2 || cb == 4); /* Writes to the data port may be 16-bit or 32-bit. */
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+ uint32_t const iIOBufferPIODataStart = RT_MIN(s->iIOBufferPIODataStart, sizeof(s->abIOBuffer));
+ uint32_t const iIOBufferPIODataEnd = RT_MIN(s->iIOBufferPIODataEnd, sizeof(s->abIOBuffer));
+
+ if (iIOBufferPIODataStart < iIOBufferPIODataEnd)
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_TO_DEVICE);
+ uint8_t *pbDst = &s->abIOBuffer[iIOBufferPIODataStart];
+ uint8_t const *pbSrc = (uint8_t const *)&u32;
+
+#ifdef IN_RC
+ /* Raw-mode: The ataHCPIOTransfer following the last transfer unit
+ requires I/O thread signalling, we must go to ring-3 for that. */
+ if (iIOBufferPIODataStart + cb < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cb);
+ else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+
+#elif defined(IN_RING0)
+ /* Ring-0: We can do I/O thread signalling here, however for paranoid reasons
+ triggered by a special case in ataHCPIOTransferFinish, we take extra care here. */
+ if (iIOBufferPIODataStart + cb < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cb);
+ else if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE) /* paranoia */
+ {
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cb);
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+ }
+ else
+ {
+ Log(("%s: Unexpected\n", __FUNCTION__));
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ }
+
+#else /* IN_RING 3*/
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cb);
+ if (s->iIOBufferPIODataStart >= iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+#endif /* IN_RING 3*/
+ }
+ else
+ Log2(("%s: DUMMY data\n", __FUNCTION__));
+
+ Log3(("%s: addr=%#x val=%.*Rhxs rc=%d\n", __FUNCTION__, offPort, cb, &u32, VBOXSTRICTRC_VAL(rc)));
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ else
+ Log3(("%s: addr=%#x -> %d\n", __FUNCTION__, offPort, VBOXSTRICTRC_VAL(rc)));
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for primary port range IN operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortRead1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == pCtl->IOPortBase1);
+
+ /* Reads from the data register may be 16-bit or 32-bit. Byte accesses are
+ upgraded to word. */
+ Assert(cb == 1 || cb == 2 || cb == 4);
+ uint32_t cbActual = cb != 1 ? cb : 2;
+ *pu32 = 0;
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+
+ if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
+ {
+ AssertMsg(s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE, ("%#x\n", s->uTxDir));
+ uint32_t const iIOBufferPIODataStart = RT_MIN(s->iIOBufferPIODataStart, sizeof(s->abIOBuffer));
+ uint32_t const iIOBufferPIODataEnd = RT_MIN(s->iIOBufferPIODataEnd, sizeof(s->abIOBuffer));
+ uint8_t const *pbSrc = &s->abIOBuffer[iIOBufferPIODataStart];
+ uint8_t *pbDst = (uint8_t *)pu32;
+
+#ifdef IN_RC
+ /* All but the last transfer unit is simple enough for RC, but
+ * sending a request to the async IO thread is too complicated. */
+ if (iIOBufferPIODataStart + cbActual < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cbActual);
+ else
+ rc = VINF_IOM_R3_IOPORT_READ;
+
+#elif defined(IN_RING0)
+ /* Ring-0: We can do I/O thread signalling here. However there is one
+ case in ataHCPIOTransfer that does a LogRel and would (but not from
+ here) call directly into the driver code. We detect that odd case
+ here cand return to ring-3 to handle it. */
+ if (iIOBufferPIODataStart + cbActual < iIOBufferPIODataEnd)
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cbActual);
+ else if ( s->cbTotalTransfer == 0
+ || s->iSourceSink != ATAFN_SS_NULL
+ || s->iIOBufferCur <= s->iIOBufferEnd)
+ {
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cbActual);
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+ }
+ else
+ {
+ Log(("%s: Unexpected\n",__FUNCTION__));
+ rc = VINF_IOM_R3_IOPORT_READ;
+ }
+
+#else /* IN_RING3 */
+ ataCopyPioData124(s, pbDst, pbSrc, iIOBufferPIODataStart, cbActual);
+ if (s->iIOBufferPIODataStart >= iIOBufferPIODataEnd)
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+#endif /* IN_RING3 */
+
+ /* Just to be on the safe side (caller takes care of this, really). */
+ if (cb == 1)
+ *pu32 &= 0xff;
+ }
+ else
+ {
+ Log2(("%s: DUMMY data\n", __FUNCTION__));
+ memset(pu32, 0xff, cb);
+ }
+ Log3(("%s: addr=%#x val=%.*Rhxs rc=%d\n", __FUNCTION__, offPort, cb, pu32, VBOXSTRICTRC_VAL(rc)));
+
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ else
+ Log3(("%s: addr=%#x -> %d\n", __FUNCTION__, offPort, VBOXSTRICTRC_VAL(rc)));
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWINSTRING,
+ * Port I/O Handler for primary port range IN string operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortReadStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == pCtl->IOPortBase1);
+ Assert(*pcTransfers > 0);
+
+ VBOXSTRICTRC rc;
+ if (cb == 2 || cb == 4)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+
+ uint32_t const offStart = s->iIOBufferPIODataStart;
+ uint32_t const offEnd = s->iIOBufferPIODataEnd;
+ if (offStart < offEnd)
+ {
+ /*
+ * Figure how much we can copy. Usually it's the same as the request.
+ * The last transfer unit cannot be handled in RC, as it involves
+ * thread communication. In R0 we let the non-string callback handle it,
+ * and ditto for overflows/dummy data.
+ */
+ uint32_t cAvailable = (offEnd - offStart) / cb;
+#ifndef IN_RING3
+ if (cAvailable > 0)
+ cAvailable--;
+#endif
+ uint32_t const cRequested = *pcTransfers;
+ if (cAvailable > cRequested)
+ cAvailable = cRequested;
+ uint32_t const cbTransfer = cAvailable * cb;
+ uint32_t const offEndThisXfer = offStart + cbTransfer;
+ if ( offEndThisXfer <= RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE)
+ && offStart < RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE) /* paranoia */
+ && cbTransfer > 0)
+ {
+ /*
+ * Do the transfer.
+ */
+ uint8_t const *pbSrc = &s->abIOBuffer[offStart];
+ memcpy(pbDst, pbSrc, cbTransfer);
+ Log3(("%s: addr=%#x cb=%#x cbTransfer=%#x val=%.*Rhxd\n", __FUNCTION__, offPort, cb, cbTransfer, cbTransfer, pbSrc));
+ s->iIOBufferPIODataStart = offEndThisXfer;
+#ifdef IN_RING3
+ if (offEndThisXfer >= offEnd)
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+#endif
+ *pcTransfers = cRequested - cAvailable;
+ }
+ else
+ Log2(("ataIOPortReadStr1Data: DUMMY/Overflow!\n"));
+ }
+ else
+ {
+ /*
+ * Dummy read (shouldn't happen) return 0xff like the non-string handler.
+ */
+ Log2(("ataIOPortReadStr1Data: DUMMY data (%#x bytes)\n", *pcTransfers * cb));
+ memset(pbDst, 0xff, *pcTransfers * cb);
+ *pcTransfers = 0;
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ }
+ /*
+ * Let the non-string I/O callback handle 1 byte reads.
+ */
+ else
+ {
+ Log2(("ataIOPortReadStr1Data: 1 byte read (%#x transfers)\n", *pcTransfers));
+ AssertFailed();
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUTSTRING,
+ * Port I/O Handler for primary port range OUT string operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWriteStr1Data(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ RT_NOREF(offPort);
+
+ Assert((uintptr_t)pvUser < 2);
+ Assert(offPort == pCtl->IOPortBase1);
+ Assert(*pcTransfers > 0);
+
+ VBOXSTRICTRC rc;
+ if (cb == 2 || cb == 4)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK];
+
+ uint32_t const offStart = s->iIOBufferPIODataStart;
+ uint32_t const offEnd = s->iIOBufferPIODataEnd;
+ Log3Func(("offStart=%#x offEnd=%#x *pcTransfers=%d cb=%d\n", offStart, offEnd, *pcTransfers, cb));
+ if (offStart < offEnd)
+ {
+ /*
+ * Figure how much we can copy. Usually it's the same as the request.
+ * The last transfer unit cannot be handled in RC, as it involves
+ * thread communication. In R0 we let the non-string callback handle it,
+ * and ditto for overflows/dummy data.
+ */
+ uint32_t cAvailable = (offEnd - offStart) / cb;
+#ifndef IN_RING3
+ if (cAvailable)
+ cAvailable--;
+#endif
+ uint32_t const cRequested = *pcTransfers;
+ if (cAvailable > cRequested)
+ cAvailable = cRequested;
+ uint32_t const cbTransfer = cAvailable * cb;
+ uint32_t const offEndThisXfer = offStart + cbTransfer;
+ if ( offEndThisXfer <= RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE)
+ && offStart < RT_MIN(s->cbIOBuffer, ATA_MAX_IO_BUFFER_SIZE) /* paranoia */
+ && cbTransfer > 0)
+ {
+ /*
+ * Do the transfer.
+ */
+ void *pvDst = &s->abIOBuffer[offStart];
+ memcpy(pvDst, pbSrc, cbTransfer);
+ Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, offPort, cbTransfer, pvDst));
+ s->iIOBufferPIODataStart = offEndThisXfer;
+#ifdef IN_RING3
+ if (offEndThisXfer >= offEnd)
+ ataHCPIOTransferFinish(pDevIns, pCtl, s);
+#endif
+ *pcTransfers = cRequested - cAvailable;
+ }
+ else
+ Log2(("ataIOPortWriteStr1Data: DUMMY/Overflow!\n"));
+ }
+ else
+ {
+ Log2(("ataIOPortWriteStr1Data: DUMMY data (%#x bytes)\n", *pcTransfers * cb));
+ *pcTransfers = 0;
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ }
+ /*
+ * Let the non-string I/O callback handle 1 byte reads.
+ */
+ else
+ {
+ Log2(("ataIOPortWriteStr1Data: 1 byte write (%#x transfers)\n", *pcTransfers));
+ AssertFailed();
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+#ifdef IN_RING3
+
+static void ataR3DMATransferStop(PATADEVSTATE s)
+{
+ s->cbTotalTransfer = 0;
+ s->cbElementaryTransfer = 0;
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ s->iSourceSink = ATAFN_SS_NULL;
+}
+
+
+/**
+ * Perform the entire DMA transfer in one go (unless a source/sink operation
+ * has to be redone or a RESET comes in between). Unlike the PIO counterpart
+ * this function cannot handle empty transfers.
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl Controller for which to perform the transfer, shared bits.
+ * @param pCtlR3 The ring-3 controller state.
+ */
+static void ataR3DMATransfer(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATACONTROLLERR3 pCtlR3)
+{
+ uint8_t const iAIOIf = pCtl->iAIOIf & ATA_SELECTED_IF_MASK;
+ PATADEVSTATE s = &pCtl->aIfs[iAIOIf];
+ PATADEVSTATER3 pDevR3 = &pCtlR3->aIfs[iAIOIf];
+ bool fRedo;
+ RTGCPHYS32 GCPhysDesc;
+ uint32_t cbTotalTransfer, cbElementaryTransfer;
+ uint32_t iIOBufferCur, iIOBufferEnd;
+ PDMMEDIATXDIR uTxDir;
+ bool fLastDesc = false;
+
+ Assert(sizeof(BMDMADesc) == 8);
+
+ fRedo = pCtl->fRedo;
+ if (RT_LIKELY(!fRedo))
+ Assert(s->cbTotalTransfer);
+ uTxDir = (PDMMEDIATXDIR)s->uTxDir;
+ cbTotalTransfer = s->cbTotalTransfer;
+ cbElementaryTransfer = RT_MIN(s->cbElementaryTransfer, sizeof(s->abIOBuffer));
+ iIOBufferEnd = RT_MIN(s->iIOBufferEnd, sizeof(s->abIOBuffer));
+ iIOBufferCur = RT_MIN(RT_MIN(s->iIOBufferCur, sizeof(s->abIOBuffer)), iIOBufferEnd);
+
+ /* The DMA loop is designed to hold the lock only when absolutely
+ * necessary. This avoids long freezes should the guest access the
+ * ATA registers etc. for some reason. */
+ ataR3LockLeave(pDevIns, pCtl);
+
+ Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
+ __FUNCTION__, uTxDir == PDMMEDIATXDIR_FROM_DEVICE ? "T2I" : "I2T",
+ cbTotalTransfer, cbElementaryTransfer,
+ iIOBufferCur, iIOBufferEnd));
+ for (GCPhysDesc = pCtl->GCPhysFirstDMADesc;
+ GCPhysDesc <= pCtl->GCPhysLastDMADesc;
+ GCPhysDesc += sizeof(BMDMADesc))
+ {
+ BMDMADesc DMADesc;
+ RTGCPHYS32 GCPhysBuffer;
+ uint32_t cbBuffer;
+
+ if (RT_UNLIKELY(fRedo))
+ {
+ GCPhysBuffer = pCtl->GCPhysRedoDMABuffer;
+ cbBuffer = pCtl->cbRedoDMABuffer;
+ fLastDesc = pCtl->fRedoDMALastDesc;
+ DMADesc.GCPhysBuffer = DMADesc.cbBuffer = 0; /* Shut up MSC. */
+ }
+ else
+ {
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysDesc, &DMADesc, sizeof(BMDMADesc));
+ GCPhysBuffer = RT_LE2H_U32(DMADesc.GCPhysBuffer);
+ cbBuffer = RT_LE2H_U32(DMADesc.cbBuffer);
+ fLastDesc = RT_BOOL(cbBuffer & UINT32_C(0x80000000));
+ cbBuffer &= 0xfffe;
+ if (cbBuffer == 0)
+ cbBuffer = 0x10000;
+ if (cbBuffer > cbTotalTransfer)
+ cbBuffer = cbTotalTransfer;
+ }
+
+ while (RT_UNLIKELY(fRedo) || (cbBuffer && cbTotalTransfer))
+ {
+ if (RT_LIKELY(!fRedo))
+ {
+ uint32_t cbXfer = RT_MIN(RT_MIN(cbBuffer, iIOBufferEnd - iIOBufferCur),
+ sizeof(s->abIOBuffer) - RT_MIN(iIOBufferCur, sizeof(s->abIOBuffer)));
+ Log2(("%s: DMA desc %#010x: addr=%#010x size=%#010x orig_size=%#010x\n", __FUNCTION__,
+ (int)GCPhysDesc, GCPhysBuffer, cbBuffer, RT_LE2H_U32(DMADesc.cbBuffer) & 0xfffe));
+
+ if (uTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ PDMDevHlpPCIPhysWriteUser(pDevIns, GCPhysBuffer, &s->abIOBuffer[iIOBufferCur], cbXfer);
+ else
+ PDMDevHlpPCIPhysReadUser(pDevIns, GCPhysBuffer, &s->abIOBuffer[iIOBufferCur], cbXfer);
+
+ iIOBufferCur += cbXfer;
+ cbTotalTransfer -= cbXfer;
+ cbBuffer -= cbXfer;
+ GCPhysBuffer += cbXfer;
+ }
+ if ( iIOBufferCur == iIOBufferEnd
+ && (uTxDir == PDMMEDIATXDIR_TO_DEVICE || cbTotalTransfer))
+ {
+ if (uTxDir == PDMMEDIATXDIR_FROM_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+ cbElementaryTransfer = cbTotalTransfer;
+
+ ataR3LockEnter(pDevIns, pCtl);
+
+ /* The RESET handler could have cleared the DMA transfer
+ * state (since we didn't hold the lock until just now
+ * the guest can continue in parallel). If so, the state
+ * is already set up so the loop is exited immediately. */
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ s->iIOBufferCur = iIOBufferCur;
+ s->iIOBufferEnd = iIOBufferEnd;
+ s->cbElementaryTransfer = cbElementaryTransfer;
+ s->cbTotalTransfer = cbTotalTransfer;
+ Log2(("%s: calling source/sink function\n", __FUNCTION__));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ if (RT_UNLIKELY(fRedo))
+ {
+ pCtl->GCPhysFirstDMADesc = GCPhysDesc;
+ pCtl->GCPhysRedoDMABuffer = GCPhysBuffer;
+ pCtl->cbRedoDMABuffer = cbBuffer;
+ pCtl->fRedoDMALastDesc = fLastDesc;
+ }
+ else
+ {
+ cbTotalTransfer = s->cbTotalTransfer;
+ cbElementaryTransfer = s->cbElementaryTransfer;
+
+ if (uTxDir == PDMMEDIATXDIR_TO_DEVICE && cbElementaryTransfer > cbTotalTransfer)
+ cbElementaryTransfer = cbTotalTransfer;
+ iIOBufferCur = 0;
+ iIOBufferEnd = RT_MIN(cbElementaryTransfer, sizeof(s->abIOBuffer));
+ }
+ pCtl->fRedo = fRedo;
+ }
+ else
+ {
+ /* This forces the loop to exit immediately. */
+ Assert(iSourceSink == ATAFN_SS_NULL);
+ GCPhysDesc = pCtl->GCPhysLastDMADesc + 1;
+ }
+
+ ataR3LockLeave(pDevIns, pCtl);
+ if (RT_UNLIKELY(fRedo))
+ break;
+ }
+ }
+
+ if (RT_UNLIKELY(fRedo))
+ break;
+
+ /* end of transfer */
+ if (!cbTotalTransfer || fLastDesc)
+ break;
+
+ ataR3LockEnter(pDevIns, pCtl);
+
+ if (!(pCtl->BmDma.u8Cmd & BM_CMD_START) || pCtl->fReset)
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: ABORT DMA%s\n", pCtl->iCtl, pCtl->fReset ? " due to RESET" : ""));
+ if (!pCtl->fReset)
+ ataR3DMATransferStop(s);
+ /* This forces the loop to exit immediately. */
+ GCPhysDesc = pCtl->GCPhysLastDMADesc + 1;
+ }
+
+ ataR3LockLeave(pDevIns, pCtl);
+ }
+
+ ataR3LockEnter(pDevIns, pCtl);
+ if (RT_UNLIKELY(fRedo))
+ return;
+
+ if (fLastDesc)
+ pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+ s->cbTotalTransfer = cbTotalTransfer;
+ s->cbElementaryTransfer = cbElementaryTransfer;
+ s->iIOBufferCur = iIOBufferCur;
+ s->iIOBufferEnd = iIOBufferEnd;
+}
+
+/**
+ * Signal PDM that we're idle (if we actually are).
+ *
+ * @param pDevIns The device instance.
+ * @param pCtl The shared controller state.
+ * @param pCtlR3 The ring-3 controller state.
+ */
+static void ataR3AsyncSignalIdle(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, PATACONTROLLERR3 pCtlR3)
+{
+ /*
+ * Take the lock here and recheck the idle indicator to avoid
+ * unnecessary work and racing ataR3WaitForAsyncIOIsIdle.
+ */
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->AsyncIORequestLock, VINF_SUCCESS);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pCtl->AsyncIORequestLock, rc);
+
+ if ( pCtlR3->fSignalIdle
+ && ataR3AsyncIOIsIdle(pDevIns, pCtl, false /*fStrict*/))
+ {
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+ RTThreadUserSignal(pCtlR3->hAsyncIOThread); /* for ataR3Construct/ataR3ResetCommon. */
+ }
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pCtl->AsyncIORequestLock);
+ AssertRC(rc);
+}
+
+/**
+ * Async I/O thread for an interface.
+ *
+ * Once upon a time this was readable code with several loops and a different
+ * semaphore for each purpose. But then came the "how can one save the state in
+ * the middle of a PIO transfer" question. The solution was to use an ASM,
+ * which is what's there now.
+ */
+static DECLCALLBACK(int) ataR3AsyncIOThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PATACONTROLLERR3 const pCtlR3 = (PATACONTROLLERR3)pvUser;
+ PPDMDEVINSR3 const pDevIns = pCtlR3->pDevIns;
+ PATASTATE const pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ uintptr_t const iCtl = pCtlR3 - &pThisCC->aCts[0];
+ PATACONTROLLER const pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, iCtl);
+ int rc = VINF_SUCCESS;
+ uint64_t u64TS = 0; /* shut up gcc */
+ uint64_t uWait;
+ const ATARequest *pReq;
+ RT_NOREF(hThreadSelf);
+ Assert(pCtl->iCtl == pCtlR3->iCtl);
+
+ pReq = NULL;
+ pCtl->fChainedTransfer = false;
+ while (!pCtlR3->fShutdown)
+ {
+ /* Keep this thread from doing anything as long as EMT is suspended. */
+ while (pCtl->fRedoIdle)
+ {
+ if (pCtlR3->fSignalIdle)
+ ataR3AsyncSignalIdle(pDevIns, pCtl, pCtlR3);
+ rc = RTSemEventWait(pCtlR3->hSuspendIOSem, RT_INDEFINITE_WAIT);
+ /* Continue if we got a signal by RTThreadPoke().
+ * We will get notified if there is a request to process.
+ */
+ if (RT_UNLIKELY(rc == VERR_INTERRUPTED))
+ continue;
+ if (RT_FAILURE(rc) || pCtlR3->fShutdown)
+ break;
+
+ pCtl->fRedoIdle = false;
+ }
+
+ /* Wait for work. */
+ while (pReq == NULL)
+ {
+ if (pCtlR3->fSignalIdle)
+ ataR3AsyncSignalIdle(pDevIns, pCtl, pCtlR3);
+ rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pCtl->hAsyncIOSem, RT_INDEFINITE_WAIT);
+ /* Continue if we got a signal by RTThreadPoke().
+ * We will get notified if there is a request to process.
+ */
+ if (RT_UNLIKELY(rc == VERR_INTERRUPTED))
+ continue;
+ if (RT_FAILURE(rc) || RT_UNLIKELY(pCtlR3->fShutdown))
+ break;
+
+ pReq = ataR3AsyncIOGetCurrentRequest(pDevIns, pCtl);
+ }
+
+ if (RT_FAILURE(rc) || pCtlR3->fShutdown)
+ break;
+
+ if (pReq == NULL)
+ continue;
+
+ ATAAIO ReqType = pReq->ReqType;
+
+ Log2(("%s: Ctl#%d: state=%d, req=%d\n", __FUNCTION__, pCtl->iCtl, pCtl->uAsyncIOState, ReqType));
+ if (pCtl->uAsyncIOState != ReqType)
+ {
+ /* The new state is not the state that was expected by the normal
+ * state changes. This is either a RESET/ABORT or there's something
+ * really strange going on. */
+ if ( (pCtl->uAsyncIOState == ATA_AIO_PIO || pCtl->uAsyncIOState == ATA_AIO_DMA)
+ && (ReqType == ATA_AIO_PIO || ReqType == ATA_AIO_DMA))
+ {
+ /* Incorrect sequence of PIO/DMA states. Dump request queue. */
+ ataR3AsyncIODumpRequests(pDevIns, pCtl);
+ }
+ AssertReleaseMsg( ReqType == ATA_AIO_RESET_ASSERTED
+ || ReqType == ATA_AIO_RESET_CLEARED
+ || ReqType == ATA_AIO_ABORT
+ || pCtl->uAsyncIOState == ReqType,
+ ("I/O state inconsistent: state=%d request=%d\n", pCtl->uAsyncIOState, ReqType));
+ }
+
+ /* Do our work. */
+ ataR3LockEnter(pDevIns, pCtl);
+
+ if (pCtl->uAsyncIOState == ATA_AIO_NEW && !pCtl->fChainedTransfer)
+ {
+ u64TS = RTTimeNanoTS();
+#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ STAM_PROFILE_ADV_START(&pCtl->StatAsyncTime, a);
+#endif
+ }
+
+ switch (ReqType)
+ {
+ case ATA_AIO_NEW:
+ {
+ uint8_t const iIf = pReq->u.t.iIf & ATA_SELECTED_IF_MASK;
+ pCtl->iAIOIf = iIf;
+ PATADEVSTATE s = &pCtl->aIfs[iIf];
+ PATADEVSTATER3 pDevR3 = &pCtlR3->aIfs[iIf];
+
+ s->cbTotalTransfer = pReq->u.t.cbTotalTransfer;
+ s->uTxDir = pReq->u.t.uTxDir;
+ s->iBeginTransfer = pReq->u.t.iBeginTransfer;
+ s->iSourceSink = pReq->u.t.iSourceSink;
+ s->iIOBufferEnd = 0;
+ s->u64CmdTS = u64TS;
+
+ if (s->fATAPI)
+ {
+ if (pCtl->fChainedTransfer)
+ {
+ /* Only count the actual transfers, not the PIO
+ * transfer of the ATAPI command bytes. */
+ if (s->fDMA)
+ STAM_REL_COUNTER_INC(&s->StatATAPIDMA);
+ else
+ STAM_REL_COUNTER_INC(&s->StatATAPIPIO);
+ }
+ }
+ else
+ {
+ if (s->fDMA)
+ STAM_REL_COUNTER_INC(&s->StatATADMA);
+ else
+ STAM_REL_COUNTER_INC(&s->StatATAPIO);
+ }
+
+ pCtl->fChainedTransfer = false;
+
+ uint8_t const iBeginTransfer = s->iBeginTransfer;
+ if ( iBeginTransfer != ATAFN_BT_NULL
+ && iBeginTransfer < RT_ELEMENTS(g_apfnBeginTransFuncs))
+ {
+ Log2(("%s: Ctl#%d: calling begin transfer function\n", __FUNCTION__, pCtl->iCtl));
+ g_apfnBeginTransFuncs[iBeginTransfer](pCtl, s);
+ s->iBeginTransfer = ATAFN_BT_NULL;
+ if (s->uTxDir != PDMMEDIATXDIR_FROM_DEVICE)
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ Assert(iBeginTransfer == ATAFN_BT_NULL);
+ s->cbElementaryTransfer = s->cbTotalTransfer;
+ s->iIOBufferEnd = s->cbTotalTransfer;
+ }
+ s->iIOBufferCur = 0;
+
+ if (s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ {
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, pCtl->iCtl));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo && !pCtl->fReset))
+ {
+ /* Operation failed at the initial transfer, restart
+ * everything from scratch by resending the current
+ * request. Occurs very rarely, not worth optimizing. */
+ LogRel(("%s: Ctl#%d: redo entire operation\n", __FUNCTION__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, pReq);
+ break;
+ }
+ }
+ else
+ {
+ Assert(iSourceSink == ATAFN_SS_NULL);
+ ataR3CmdOK(pCtl, s, ATA_STAT_SEEK);
+ }
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+
+ }
+
+ /* Do not go into the transfer phase if RESET is asserted.
+ * The CritSect is released while waiting for the host OS
+ * to finish the I/O, thus RESET is possible here. Most
+ * important: do not change uAsyncIOState. */
+ if (pCtl->fReset)
+ break;
+
+ if (s->fDMA)
+ {
+ if (s->cbTotalTransfer)
+ {
+ ataSetStatus(pCtl, s, ATA_STAT_DRQ);
+
+ pCtl->uAsyncIOState = ATA_AIO_DMA;
+ /* If BMDMA is already started, do the transfer now. */
+ if (pCtl->BmDma.u8Cmd & BM_CMD_START)
+ {
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing DMA transfer immediately\n", __FUNCTION__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataDMARequest);
+ }
+ }
+ else
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+ /* Finish DMA transfer. */
+ ataR3DMATransferStop(s);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ if (s->cbTotalTransfer)
+ {
+ ataHCPIOTransfer(pDevIns, pCtl);
+ Assert(!pCtl->fRedo);
+ if (s->fATAPITransfer || s->uTxDir != PDMMEDIATXDIR_TO_DEVICE)
+ ataHCSetIRQ(pDevIns, pCtl, s);
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+ {
+ /* Write operations and not yet finished transfers
+ * must be completed in the async I/O thread. */
+ pCtl->uAsyncIOState = ATA_AIO_PIO;
+ }
+ else
+ {
+ /* Finished read operation can be handled inline
+ * in the end of PIO transfer handling code. Linux
+ * depends on this, as it waits only briefly for
+ * devices to become ready after incoming data
+ * transfer. Cannot find anything in the ATA spec
+ * that backs this assumption, but as all kernels
+ * are affected (though most of the time it does
+ * not cause any harm) this must work. */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ Assert(s->uTxDir == PDMMEDIATXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pDevIns, pCtl);
+ Assert(!pCtl->fRedo);
+ if (!s->fATAPITransfer)
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ break;
+ }
+
+ case ATA_AIO_DMA:
+ {
+ BMDMAState *bm = &pCtl->BmDma;
+ PATADEVSTATE s = &pCtl->aIfs[pCtl->iAIOIf & ATA_SELECTED_IF_MASK];
+ ATAFNSS iOriginalSourceSink = (ATAFNSS)s->iSourceSink; /* Used by the hack below, but gets reset by then. */
+
+ if (s->uTxDir == PDMMEDIATXDIR_FROM_DEVICE)
+ AssertRelease(bm->u8Cmd & BM_CMD_WRITE);
+ else
+ AssertRelease(!(bm->u8Cmd & BM_CMD_WRITE));
+
+ if (RT_LIKELY(!pCtl->fRedo))
+ {
+ /* The specs say that the descriptor table must not cross a
+ * 4K boundary. */
+ pCtl->GCPhysFirstDMADesc = bm->GCPhysAddr;
+ pCtl->GCPhysLastDMADesc = RT_ALIGN_32(bm->GCPhysAddr + 1, _4K) - sizeof(BMDMADesc);
+ }
+ ataR3DMATransfer(pDevIns, pCtl, pCtlR3);
+
+ if (RT_UNLIKELY(pCtl->fRedo && !pCtl->fReset))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo DMA operation\n", pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataDMARequest);
+ break;
+ }
+
+ /* The infamous delay IRQ hack. */
+ if ( iOriginalSourceSink == ATAFN_SS_WRITE_SECTORS
+ && s->cbTotalTransfer == 0
+ && pCtl->msDelayIRQ)
+ {
+ /* Delay IRQ for writing. Required to get the Win2K
+ * installation work reliably (otherwise it crashes,
+ * usually during component install). So far no better
+ * solution has been found. */
+ Log(("%s: delay IRQ hack\n", __FUNCTION__));
+ ataR3LockLeave(pDevIns, pCtl);
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ }
+
+ ataUnsetStatus(pCtl, s, ATA_STAT_DRQ);
+ Assert(!pCtl->fChainedTransfer);
+ Assert(s->iSourceSink == ATAFN_SS_NULL);
+ if (s->fATAPITransfer)
+ {
+ s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
+ Log2(("%s: Ctl#%d: interrupt reason %#04x\n", __FUNCTION__, pCtl->iCtl, s->uATARegNSector));
+ s->fATAPITransfer = false;
+ }
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ break;
+ }
+
+ case ATA_AIO_PIO:
+ {
+ uint8_t const iIf = pCtl->iAIOIf & ATA_SELECTED_IF_MASK;
+ pCtl->iAIOIf = iIf;
+ PATADEVSTATE s = &pCtl->aIfs[iIf];
+ PATADEVSTATER3 pDevR3 = &pCtlR3->aIfs[iIf];
+
+ uint8_t const iSourceSink = s->iSourceSink;
+ if ( iSourceSink != ATAFN_SS_NULL
+ && iSourceSink < RT_ELEMENTS(g_apfnSourceSinkFuncs))
+ {
+ bool fRedo;
+ Log2(("%s: Ctl#%d: calling source/sink function\n", __FUNCTION__, pCtl->iCtl));
+ fRedo = g_apfnSourceSinkFuncs[iSourceSink](pDevIns, pCtl, s, pDevR3);
+ pCtl->fRedo = fRedo;
+ if (RT_UNLIKELY(fRedo && !pCtl->fReset))
+ {
+ LogRel(("PIIX3 ATA: Ctl#%d: redo PIO operation\n", pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataPIORequest);
+ break;
+ }
+ s->iIOBufferCur = 0;
+ s->iIOBufferEnd = s->cbElementaryTransfer;
+ }
+ else
+ {
+ /* Continue a previously started transfer. */
+ Assert(iSourceSink == ATAFN_SS_NULL);
+ ataUnsetStatus(pCtl, s, ATA_STAT_BUSY);
+ ataSetStatus(pCtl, s, ATA_STAT_READY);
+ }
+
+ /* It is possible that the drives on this controller get RESET
+ * during the above call to the source/sink function. If that's
+ * the case, don't restart the transfer and don't finish it the
+ * usual way. RESET handling took care of all that already.
+ * Most important: do not change uAsyncIOState. */
+ if (pCtl->fReset)
+ break;
+
+ if (s->cbTotalTransfer)
+ {
+ ataHCPIOTransfer(pDevIns, pCtl);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+
+ if (s->uTxDir == PDMMEDIATXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
+ {
+ /* Write operations and not yet finished transfers
+ * must be completed in the async I/O thread. */
+ pCtl->uAsyncIOState = ATA_AIO_PIO;
+ }
+ else
+ {
+ /* Finished read operation can be handled inline
+ * in the end of PIO transfer handling code. Linux
+ * depends on this, as it waits only briefly for
+ * devices to become ready after incoming data
+ * transfer. Cannot find anything in the ATA spec
+ * that backs this assumption, but as all kernels
+ * are affected (though most of the time it does
+ * not cause any harm) this must work. */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ }
+ else
+ {
+ /* The infamous delay IRQ hack. */
+ if (RT_UNLIKELY(pCtl->msDelayIRQ))
+ {
+ /* Various antique guests have buggy disk drivers silently
+ * assuming that disk operations take a relatively long time.
+ * Work around such bugs by holding off interrupts a bit.
+ */
+ Log(("%s: delay IRQ hack (PIO)\n", __FUNCTION__));
+ ataR3LockLeave(pDevIns, pCtl);
+ RTThreadSleep(pCtl->msDelayIRQ);
+ ataR3LockEnter(pDevIns, pCtl);
+ }
+
+ /* Finish PIO transfer. */
+ ataHCPIOTransfer(pDevIns, pCtl);
+ if ( !pCtl->fChainedTransfer
+ && !s->fATAPITransfer
+ && s->uTxDir != PDMMEDIATXDIR_FROM_DEVICE)
+ {
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ }
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ }
+ break;
+ }
+
+ case ATA_AIO_RESET_ASSERTED:
+ pCtl->uAsyncIOState = ATA_AIO_RESET_CLEARED;
+ ataHCPIOTransferStop(pDevIns, pCtl, &pCtl->aIfs[0]);
+ ataHCPIOTransferStop(pDevIns, pCtl, &pCtl->aIfs[1]);
+ /* Do not change the DMA registers, they are not affected by the
+ * ATA controller reset logic. It should be sufficient to issue a
+ * new command, which is now possible as the state is cleared. */
+ break;
+
+ case ATA_AIO_RESET_CLEARED:
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ pCtl->fReset = false;
+ /* Ensure that half-completed transfers are not redone. A reset
+ * cancels the entire transfer, so continuing is wrong. */
+ pCtl->fRedo = false;
+ pCtl->fRedoDMALastDesc = false;
+ LogRel(("PIIX3 ATA: Ctl#%d: finished processing RESET\n", pCtl->iCtl));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
+ {
+ ataR3SetSignature(&pCtl->aIfs[i]);
+ if (pCtl->aIfs[i].fATAPI)
+ ataSetStatusValue(pCtl, &pCtl->aIfs[i], 0); /* NOTE: READY is _not_ set */
+ else
+ ataSetStatusValue(pCtl, &pCtl->aIfs[i], ATA_STAT_READY | ATA_STAT_SEEK);
+ }
+ break;
+
+ case ATA_AIO_ABORT:
+ {
+ /* Abort the current command no matter what. There cannot be
+ * any command activity on the other drive otherwise using
+ * one thread per controller wouldn't work at all. */
+ PATADEVSTATE s = &pCtl->aIfs[pReq->u.a.iIf & ATA_SELECTED_IF_MASK];
+
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ /* Do not change the DMA registers, they are not affected by the
+ * ATA controller reset logic. It should be sufficient to issue a
+ * new command, which is now possible as the state is cleared. */
+ if (pReq->u.a.fResetDrive)
+ {
+ ataR3ResetDevice(pDevIns, pCtl, s);
+ ataR3DeviceDiag(pCtl, s);
+ }
+ else
+ {
+ /* Stop any pending DMA transfer. */
+ s->fDMA = false;
+ ataHCPIOTransferStop(pDevIns, pCtl, s);
+ ataUnsetStatus(pCtl, s, ATA_STAT_BUSY | ATA_STAT_DRQ | ATA_STAT_SEEK | ATA_STAT_ERR);
+ ataSetStatus(pCtl, s, ATA_STAT_READY);
+ ataHCSetIRQ(pDevIns, pCtl, s);
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Undefined async I/O state %d\n", pCtl->uAsyncIOState));
+ }
+
+ ataR3AsyncIORemoveCurrentRequest(pDevIns, pCtl, ReqType);
+ pReq = ataR3AsyncIOGetCurrentRequest(pDevIns, pCtl);
+
+ if (pCtl->uAsyncIOState == ATA_AIO_NEW && !pCtl->fChainedTransfer)
+ {
+# if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ STAM_PROFILE_ADV_STOP(&pCtl->StatAsyncTime, a);
+# endif
+
+ u64TS = RTTimeNanoTS() - u64TS;
+ uWait = u64TS / 1000;
+ uintptr_t const iAIOIf = pCtl->iAIOIf & ATA_SELECTED_IF_MASK;
+ Log(("%s: Ctl#%d: LUN#%d finished I/O transaction in %d microseconds\n",
+ __FUNCTION__, pCtl->iCtl, pCtl->aIfs[iAIOIf].iLUN, (uint32_t)(uWait)));
+ /* Mark command as finished. */
+ pCtl->aIfs[iAIOIf].u64CmdTS = 0;
+
+ /*
+ * Release logging of command execution times depends on the
+ * command type. ATAPI commands often take longer (due to CD/DVD
+ * spin up time etc.) so the threshold is different.
+ */
+ if (pCtl->aIfs[iAIOIf].uATARegCommand != ATA_PACKET)
+ {
+ if (uWait > 8 * 1000 * 1000)
+ {
+ /*
+ * Command took longer than 8 seconds. This is close
+ * enough or over the guest's command timeout, so place
+ * an entry in the release log to allow tracking such
+ * timing errors (which are often caused by the host).
+ */
+ LogRel(("PIIX3 ATA: execution time for ATA command %#04x was %d seconds\n",
+ pCtl->aIfs[iAIOIf].uATARegCommand, uWait / (1000 * 1000)));
+ }
+ }
+ else
+ {
+ if (uWait > 20 * 1000 * 1000)
+ {
+ /*
+ * Command took longer than 20 seconds. This is close
+ * enough or over the guest's command timeout, so place
+ * an entry in the release log to allow tracking such
+ * timing errors (which are often caused by the host).
+ */
+ LogRel(("PIIX3 ATA: execution time for ATAPI command %#04x was %d seconds\n",
+ pCtl->aIfs[iAIOIf].abATAPICmd[0], uWait / (1000 * 1000)));
+ }
+ }
+
+# if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
+ if (uWait < pCtl->StatAsyncMinWait || !pCtl->StatAsyncMinWait)
+ pCtl->StatAsyncMinWait = uWait;
+ if (uWait > pCtl->StatAsyncMaxWait)
+ pCtl->StatAsyncMaxWait = uWait;
+
+ STAM_COUNTER_ADD(&pCtl->StatAsyncTimeUS, uWait);
+ STAM_COUNTER_INC(&pCtl->StatAsyncOps);
+# endif /* DEBUG || VBOX_WITH_STATISTICS */
+ }
+
+ ataR3LockLeave(pDevIns, pCtl);
+ }
+
+ /* Signal the ultimate idleness. */
+ RTThreadUserSignal(pCtlR3->hAsyncIOThread);
+ if (pCtlR3->fSignalIdle)
+ PDMDevHlpAsyncNotificationCompleted(pDevIns);
+
+ /* Cleanup the state. */
+ /* Do not destroy request lock yet, still needed for proper shutdown. */
+ pCtlR3->fShutdown = false;
+
+ Log2(("%s: Ctl#%d: return %Rrc\n", __FUNCTION__, pCtl->iCtl, rc));
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+static uint32_t ataBMDMACmdReadB(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = pCtl->BmDma.u8Cmd;
+ RT_NOREF(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+
+static void ataBMDMACmdWriteB(PPDMDEVINS pDevIns, PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF(pDevIns, addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ if (!(val & BM_CMD_START))
+ {
+ pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
+ pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+ }
+ else
+ {
+#ifndef IN_RC
+ /* Check whether the guest OS wants to change DMA direction in
+ * mid-flight. Not allowed, according to the PIIX3 specs. */
+ Assert(!(pCtl->BmDma.u8Status & BM_STATUS_DMAING) || !((val ^ pCtl->BmDma.u8Cmd) & 0x04));
+ uint8_t uOldBmDmaStatus = pCtl->BmDma.u8Status;
+ pCtl->BmDma.u8Status |= BM_STATUS_DMAING;
+ pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
+
+ /* Do not continue DMA transfers while the RESET line is asserted. */
+ if (pCtl->fReset)
+ {
+ Log2(("%s: Ctl#%d: suppressed continuing DMA transfer as RESET is active\n", __FUNCTION__, pCtl->iCtl));
+ return;
+ }
+
+ /* Do not start DMA transfers if there's a PIO transfer going on,
+ * or if there is already a transfer started on this controller. */
+ if ( !pCtl->aIfs[pCtl->iSelectedIf & ATA_SELECTED_IF_MASK].fDMA
+ || (uOldBmDmaStatus & BM_STATUS_DMAING))
+ return;
+
+ if (pCtl->aIfs[pCtl->iAIOIf & ATA_SELECTED_IF_MASK].uATARegStatus & ATA_STAT_DRQ)
+ {
+ Log2(("%s: Ctl#%d: message to async I/O thread, continuing DMA transfer\n", __FUNCTION__, pCtl->iCtl));
+ ataHCAsyncIOPutRequest(pDevIns, pCtl, &g_ataDMARequest);
+ }
+#else /* !IN_RING3 */
+ AssertMsgFailed(("DMA START handling is too complicated for RC\n"));
+#endif /* IN_RING3 */
+ }
+}
+
+static uint32_t ataBMDMAStatusReadB(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = pCtl->BmDma.u8Status;
+ RT_NOREF(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static void ataBMDMAStatusWriteB(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF(addr);
+ Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.u8Status = (val & (BM_STATUS_D0DMA | BM_STATUS_D1DMA))
+ | (pCtl->BmDma.u8Status & BM_STATUS_DMAING)
+ | (pCtl->BmDma.u8Status & ~val & (BM_STATUS_ERROR | BM_STATUS_INT));
+}
+
+static uint32_t ataBMDMAAddrReadL(PATACONTROLLER pCtl, uint32_t addr)
+{
+ uint32_t val = (uint32_t)pCtl->BmDma.GCPhysAddr;
+ RT_NOREF(addr);
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ return val;
+}
+
+static void ataBMDMAAddrWriteL(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF(addr);
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.GCPhysAddr = val & ~3;
+}
+
+static void ataBMDMAAddrWriteLowWord(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ RT_NOREF(addr);
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ pCtl->BmDma.GCPhysAddr = (pCtl->BmDma.GCPhysAddr & 0xFFFF0000) | RT_LOWORD(val & ~3);
+
+}
+
+static void ataBMDMAAddrWriteHighWord(PATACONTROLLER pCtl, uint32_t addr, uint32_t val)
+{
+ Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
+ RT_NOREF(addr);
+ pCtl->BmDma.GCPhysAddr = (RT_LOWORD(val) << 16) | RT_LOWORD(pCtl->BmDma.GCPhysAddr);
+}
+
+/** Helper for ataBMDMAIOPortRead and ataBMDMAIOPortWrite. */
+#define VAL(port, size) ( ((port) & BM_DMA_CTL_IOPORTS_MASK) | ((size) << BM_DMA_CTL_IOPORTS_SHIFT) )
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for bus-master DMA IN operations - both controllers.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataBMDMAIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (offPort >> BM_DMA_CTL_IOPORTS_SHIFT));
+ RT_NOREF(pvUser);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ switch (VAL(offPort, cb))
+ {
+ case VAL(0, 1): *pu32 = ataBMDMACmdReadB(pCtl, offPort); break;
+ case VAL(0, 2): *pu32 = ataBMDMACmdReadB(pCtl, offPort); break;
+ case VAL(2, 1): *pu32 = ataBMDMAStatusReadB(pCtl, offPort); break;
+ case VAL(2, 2): *pu32 = ataBMDMAStatusReadB(pCtl, offPort); break;
+ case VAL(4, 4): *pu32 = ataBMDMAAddrReadL(pCtl, offPort); break;
+ case VAL(0, 4):
+ /* The SCO OpenServer tries to read 4 bytes starting from offset 0. */
+ *pu32 = ataBMDMACmdReadB(pCtl, offPort) | (ataBMDMAStatusReadB(pCtl, offPort) << 16);
+ break;
+ default:
+ ASSERT_GUEST_MSG_FAILED(("Unsupported read from port %x size=%d\n", offPort, cb));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ break;
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for bus-master DMA OUT operations - both controllers.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataBMDMAIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (offPort >> BM_DMA_CTL_IOPORTS_SHIFT));
+ RT_NOREF(pvUser);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ switch (VAL(offPort, cb))
+ {
+ case VAL(0, 1):
+#ifdef IN_RC
+ if (u32 & BM_CMD_START)
+ {
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ break;
+ }
+#endif
+ ataBMDMACmdWriteB(pDevIns, pCtl, offPort, u32);
+ break;
+ case VAL(2, 1): ataBMDMAStatusWriteB(pCtl, offPort, u32); break;
+ case VAL(4, 4): ataBMDMAAddrWriteL(pCtl, offPort, u32); break;
+ case VAL(4, 2): ataBMDMAAddrWriteLowWord(pCtl, offPort, u32); break;
+ case VAL(6, 2): ataBMDMAAddrWriteHighWord(pCtl, offPort, u32); break;
+ default:
+ ASSERT_GUEST_MSG_FAILED(("Unsupported write to port %x size=%d val=%x\n", offPort, cb, u32));
+ break;
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+#undef VAL
+
+#ifdef IN_RING3
+
+/* -=-=-=-=-=- ATASTATE::IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ataR3Status_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PATASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, ATASTATER3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- ATASTATE::ILeds -=-=-=-=-=- */
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ataR3Status_QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ if (iLUN < 4)
+ {
+ PATASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, ATASTATER3, ILeds);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PATASTATE);
+ switch (iLUN)
+ {
+ case 0: *ppLed = &pThis->aCts[0].aIfs[0].Led; break;
+ case 1: *ppLed = &pThis->aCts[0].aIfs[1].Led; break;
+ case 2: *ppLed = &pThis->aCts[1].aIfs[0].Led; break;
+ case 3: *ppLed = &pThis->aCts[1].aIfs[1].Led; break;
+ }
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/* -=-=-=-=-=- ATADEVSTATE::IBase -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ataR3QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pIfR3->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pIfR3->IPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNTNOTIFY, &pIfR3->IMountNotify);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- ATADEVSTATE::IPort -=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
+ */
+static DECLCALLBACK(int) ataR3QueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
+ uint32_t *piInstance, uint32_t *piLUN)
+{
+ PATADEVSTATER3 pIfR3 = RT_FROM_MEMBER(pInterface, ATADEVSTATER3, IPort);
+ PPDMDEVINS pDevIns = pIfR3->pDevIns;
+
+ AssertPtrReturn(ppcszController, VERR_INVALID_POINTER);
+ AssertPtrReturn(piInstance, VERR_INVALID_POINTER);
+ AssertPtrReturn(piLUN, VERR_INVALID_POINTER);
+
+ *ppcszController = pDevIns->pReg->szName;
+ *piInstance = pDevIns->iInstance;
+ *piLUN = pIfR3->iLUN;
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/* -=-=-=-=-=- Wrappers -=-=-=-=-=- */
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for OUT operations on unpopulated IDE channels.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWriteEmptyBus(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ RT_NOREF(pDevIns, pvUser, offPort, u32, cb);
+
+#ifdef VBOX_STRICT
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ Assert((uintptr_t)pvUser < 2);
+ Assert(!pCtl->aIfs[0].fPresent && !pCtl->aIfs[1].fPresent);
+#endif
+
+ /* This is simply a black hole, writes on unpopulated IDE channels elicit no response. */
+ LogFunc(("Empty bus: Ignoring write to port %x val=%x size=%d\n", offPort, u32, cb));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for IN operations on unpopulated IDE channels.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortReadEmptyBus(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pDevIns, offPort, pvUser);
+
+#ifdef VBOX_STRICT
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ Assert((uintptr_t)pvUser < 2);
+ Assert(cb <= 4);
+ Assert(!pCtl->aIfs[0].fPresent && !pCtl->aIfs[1].fPresent);
+#endif
+
+ /*
+ * Reads on unpopulated IDE channels behave in a unique way. Newer ATA specifications
+ * mandate that the host must have a pull-down resistor on signal DD7. As a consequence,
+ * bit 7 is always read as zero. This greatly aids in ATA device detection because
+ * the empty bus does not look to the host like a permanently busy drive, and no long
+ * timeouts (on the order of 30 seconds) are needed.
+ *
+ * The response is entirely static and does not require any locking or other fancy
+ * stuff. Breaking it out simplifies the I/O handling for non-empty IDE channels which
+ * is quite complicated enough already.
+ */
+ *pu32 = ATA_EMPTY_BUS_DATA_32 >> ((4 - cb) * 8);
+ LogFunc(("Empty bus: port %x val=%x size=%d\n", offPort, *pu32, cb));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for primary port range OUT operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWrite1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ uintptr_t iCtl = (uintptr_t)pvUser % RT_ELEMENTS(pThis->aCts);
+ PATACONTROLLER pCtl = &pThis->aCts[iCtl];
+
+ Assert((uintptr_t)pvUser < 2);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ /* Writes to the other command block ports should be 8-bit only. If they
+ * are not, the high bits are simply discarded. Undocumented, but observed
+ * on a real PIIX4 system.
+ */
+ if (cb > 1)
+ Log(("ataIOPortWrite1: suspect write to port %x val=%x size=%d\n", offPort, u32, cb));
+
+ rc = ataIOPortWriteU8(pDevIns, pCtl, offPort, u32, iCtl);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for primary port range IN operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortRead1Other(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+
+ Assert((uintptr_t)pvUser < 2);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ /* Reads from the other command block registers should be 8-bit only.
+ * If they are not, the low byte is propagated to the high bits.
+ * Undocumented, but observed on a real PIIX4 system.
+ */
+ rc = ataIOPortReadU8(pDevIns, pCtl, offPort, pu32);
+ if (cb > 1)
+ {
+ uint32_t pad;
+
+ /* Replicate the 8-bit result into the upper three bytes. */
+ pad = *pu32 & 0xff;
+ pad = pad | (pad << 8);
+ pad = pad | (pad << 16);
+ *pu32 = pad;
+ Log(("ataIOPortRead1: suspect read from port %x size=%d\n", offPort, cb));
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT,
+ * Port I/O Handler for secondary port range OUT operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ int rc;
+
+ Assert((uintptr_t)pvUser < 2);
+
+ if (cb == 1)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ rc = ataControlWrite(pDevIns, pCtl, u32, offPort);
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ }
+ else
+ {
+ Log(("ataIOPortWrite2: ignoring write to port %x+%x size=%d!\n", offPort, pCtl->IOPortBase2, cb));
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN,
+ * Port I/O Handler for secondary port range IN operations.}
+ * @note offPort is an absolute port number!
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ataIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATACONTROLLER pCtl = &RT_SAFE_SUBSCRIPT(pThis->aCts, (uintptr_t)pvUser);
+ int rc;
+
+ Assert((uintptr_t)pvUser < 2);
+
+ if (cb == 1)
+ {
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pCtl->lock, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ *pu32 = ataStatusRead(pCtl, offPort);
+ PDMDevHlpCritSectLeave(pDevIns, &pCtl->lock);
+ }
+ }
+ else
+ {
+ Log(("ataIOPortRead2: ignoring read from port %x+%x size=%d!\n", offPort, pCtl->IOPortBase2, cb));
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Detach notification.
+ *
+ * The DVD drive has been unplugged.
+ *
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(void) ataR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ AssertMsg(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PIIX3IDE: Device does not support hotplugging\n")); RT_NOREF(fFlags);
+
+ /*
+ * Locate the controller and stuff.
+ */
+ unsigned iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+ AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+ PATACONTROLLER pCtl = &pThis->aCts[iController];
+ PATACONTROLLERR3 pCtlR3 = &pThisCC->aCts[iController];
+
+ unsigned iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ PATADEVSTATE pIf = &pCtl->aIfs[iInterface];
+ PATADEVSTATER3 pIfR3 = &pCtlR3->aIfs[iInterface];
+
+ /*
+ * Zero some important members.
+ */
+ pIfR3->pDrvBase = NULL;
+ pIfR3->pDrvMedia = NULL;
+ pIfR3->pDrvMount = NULL;
+ pIf->fPresent = false;
+
+ /*
+ * In case there was a medium inserted.
+ */
+ ataR3MediumRemoved(pIf);
+}
+
+
+/**
+ * Configure a LUN.
+ *
+ * @returns VBox status code.
+ * @param pIf The ATA unit state, shared bits.
+ * @param pIfR3 The ATA unit state, ring-3 bits.
+ */
+static int ataR3ConfigLun(PATADEVSTATE pIf, PATADEVSTATER3 pIfR3)
+{
+ /*
+ * Query Block, Bios and Mount interfaces.
+ */
+ pIfR3->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pIfR3->pDrvBase, PDMIMEDIA);
+ if (!pIfR3->pDrvMedia)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d hasn't a block interface!\n", pIf->iLUN));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+
+ pIfR3->pDrvMount = PDMIBASE_QUERY_INTERFACE(pIfR3->pDrvBase, PDMIMOUNT);
+ pIf->fPresent = true;
+
+ /*
+ * Validate type.
+ */
+ PDMMEDIATYPE enmType = pIfR3->pDrvMedia->pfnGetType(pIfR3->pDrvMedia);
+ if ( enmType != PDMMEDIATYPE_CDROM
+ && enmType != PDMMEDIATYPE_DVD
+ && enmType != PDMMEDIATYPE_HARD_DISK)
+ {
+ AssertMsgFailed(("Configuration error: LUN#%d isn't a disk or cd/dvd-rom. enmType=%d\n", pIf->iLUN, enmType));
+ return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
+ }
+ if ( ( enmType == PDMMEDIATYPE_DVD
+ || enmType == PDMMEDIATYPE_CDROM)
+ && !pIfR3->pDrvMount)
+ {
+ AssertMsgFailed(("Internal error: cdrom without a mountable interface, WTF???!\n"));
+ return VERR_INTERNAL_ERROR;
+ }
+ pIf->fATAPI = enmType == PDMMEDIATYPE_DVD || enmType == PDMMEDIATYPE_CDROM;
+ pIf->fATAPIPassthrough = pIf->fATAPI && pIfR3->pDrvMedia->pfnSendCmd != NULL;
+
+ /*
+ * Allocate I/O buffer.
+ */
+ if (pIf->fATAPI)
+ pIf->cbSector = 2048; /* Not required for ATAPI, one medium can have multiple sector sizes. */
+ else
+ {
+ pIf->cbSector = pIfR3->pDrvMedia->pfnGetSectorSize(pIfR3->pDrvMedia);
+ AssertLogRelMsgReturn(pIf->cbSector > 0 && pIf->cbSector <= ATA_MAX_SECTOR_SIZE,
+ ("Unsupported sector size on LUN#%u: %#x (%d)\n", pIf->iLUN, pIf->cbSector, pIf->cbSector),
+ VERR_OUT_OF_RANGE);
+ }
+
+ if (pIf->cbIOBuffer)
+ {
+ /* Buffer is (probably) already allocated. Validate the fields,
+ * because memory corruption can also overwrite pIf->cbIOBuffer. */
+ if (pIf->fATAPI)
+ AssertLogRelReturn(pIf->cbIOBuffer == _128K, VERR_BUFFER_OVERFLOW);
+ else
+ AssertLogRelReturn(pIf->cbIOBuffer == ATA_MAX_MULT_SECTORS * pIf->cbSector, VERR_BUFFER_OVERFLOW);
+ }
+ else
+ {
+ if (pIf->fATAPI)
+ pIf->cbIOBuffer = _128K;
+ else
+ pIf->cbIOBuffer = ATA_MAX_MULT_SECTORS * pIf->cbSector;
+ }
+ AssertCompile(_128K <= ATA_MAX_IO_BUFFER_SIZE);
+ AssertCompileSize(pIf->abIOBuffer, ATA_MAX_IO_BUFFER_SIZE);
+ AssertLogRelMsgReturn(pIf->cbIOBuffer <= ATA_MAX_IO_BUFFER_SIZE,
+ ("LUN#%u: cbIOBuffer=%#x (%u)\n", pIf->iLUN, pIf->cbIOBuffer, pIf->cbIOBuffer),
+ VERR_BUFFER_OVERFLOW);
+
+ /*
+ * Init geometry (only for non-CD/DVD media).
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cRegions = pIfR3->pDrvMedia->pfnGetRegionCount(pIfR3->pDrvMedia);
+ pIf->cTotalSectors = 0;
+ for (uint32_t i = 0; i < cRegions; i++)
+ {
+ uint64_t cBlocks = 0;
+ rc = pIfR3->pDrvMedia->pfnQueryRegionProperties(pIfR3->pDrvMedia, i, NULL, &cBlocks, NULL, NULL);
+ AssertRC(rc);
+ pIf->cTotalSectors += cBlocks;
+ }
+
+ if (pIf->fATAPI)
+ {
+ pIf->PCHSGeometry.cCylinders = 0; /* dummy */
+ pIf->PCHSGeometry.cHeads = 0; /* dummy */
+ pIf->PCHSGeometry.cSectors = 0; /* dummy */
+ LogRel(("PIIX3 ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough %s\n",
+ pIf->iLUN, pIf->cTotalSectors, (pIf->fATAPIPassthrough ? "enabled" : "disabled")));
+ }
+ else
+ {
+ rc = pIfR3->pDrvMedia->pfnBiosGetPCHSGeometry(pIfR3->pDrvMedia, &pIf->PCHSGeometry);
+ if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
+ {
+ pIf->PCHSGeometry.cCylinders = 0;
+ pIf->PCHSGeometry.cHeads = 16; /*??*/
+ pIf->PCHSGeometry.cSectors = 63; /*??*/
+ }
+ else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
+ {
+ pIf->PCHSGeometry.cCylinders = 0; /* autodetect marker */
+ rc = VINF_SUCCESS;
+ }
+ AssertRC(rc);
+
+ if ( pIf->PCHSGeometry.cCylinders == 0
+ || pIf->PCHSGeometry.cHeads == 0
+ || pIf->PCHSGeometry.cSectors == 0
+ )
+ {
+ uint64_t cCylinders = pIf->cTotalSectors / (16 * 63);
+ pIf->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
+ pIf->PCHSGeometry.cHeads = 16;
+ pIf->PCHSGeometry.cSectors = 63;
+ /* Set the disk geometry information. Ignore errors. */
+ pIfR3->pDrvMedia->pfnBiosSetPCHSGeometry(pIfR3->pDrvMedia, &pIf->PCHSGeometry);
+ rc = VINF_SUCCESS;
+ }
+ LogRel(("PIIX3 ATA: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n",
+ pIf->iLUN, pIf->PCHSGeometry.cCylinders, pIf->PCHSGeometry.cHeads, pIf->PCHSGeometry.cSectors,
+ pIf->cTotalSectors));
+
+ if (pIfR3->pDrvMedia->pfnDiscard)
+ LogRel(("PIIX3 ATA: LUN#%d: TRIM enabled\n", pIf->iLUN));
+ }
+ /* Initialize the translated geometry. */
+ pIf->XCHSGeometry = pIf->PCHSGeometry;
+
+ /*
+ * Check if SMP system to adjust the agressiveness of the busy yield hack (@bugref{1960}).
+ *
+ * The hack is an ancient (2006?) one for dealing with UNI CPU systems where EMT
+ * would potentially monopolise the CPU and starve I/O threads. It causes the EMT to
+ * yield it's timeslice if the guest polls the status register during I/O. On modern
+ * multicore and multithreaded systems, yielding EMT too often may have adverse
+ * effects (slow grub) so we aim at avoiding repeating the yield there too often.
+ */
+ RTCPUID cCpus = RTMpGetOnlineCount();
+ if (cCpus <= 1)
+ {
+ pIf->cBusyStatusHackR3Rate = 1;
+ pIf->cBusyStatusHackRZRate = 7;
+ }
+ else if (cCpus <= 2)
+ {
+ pIf->cBusyStatusHackR3Rate = 3;
+ pIf->cBusyStatusHackRZRate = 15;
+ }
+ else if (cCpus <= 4)
+ {
+ pIf->cBusyStatusHackR3Rate = 15;
+ pIf->cBusyStatusHackRZRate = 31;
+ }
+ else
+ {
+ pIf->cBusyStatusHackR3Rate = 127;
+ pIf->cBusyStatusHackRZRate = 127;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Attach command.
+ *
+ * This is called when we change block driver for the DVD drive.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param iLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static DECLCALLBACK(int) ataR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ AssertMsgReturn(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG,
+ ("PIIX3IDE: Device does not support hotplugging\n"),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Locate the controller and stuff.
+ */
+ unsigned const iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
+ AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
+ PATACONTROLLER pCtl = &pThis->aCts[iController];
+ PATACONTROLLERR3 pCtlR3 = &pThisCC->aCts[iController];
+
+ unsigned const iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
+ PATADEVSTATE pIf = &pCtl->aIfs[iInterface];
+ PATADEVSTATER3 pIfR3 = &pCtlR3->aIfs[iInterface];
+
+ /* the usual paranoia */
+ AssertRelease(!pIfR3->pDrvBase);
+ AssertRelease(!pIfR3->pDrvMedia);
+ Assert(pIf->iLUN == iLUN);
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ int rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIfR3->IBase, &pIfR3->pDrvBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataR3ConfigLun(pIf, pIfR3);
+ /*
+ * In case there is a medium inserted.
+ */
+ ataR3MediumInserted(pIf);
+ ataR3MediumTypeSet(pIf, ATA_MEDIA_TYPE_UNKNOWN);
+ }
+ else
+ AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pIf->iLUN, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ pIfR3->pDrvBase = NULL;
+ pIfR3->pDrvMedia = NULL;
+ pIfR3->pDrvMount = NULL;
+ pIf->fPresent = false;
+ }
+ return rc;
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3Resume(PPDMDEVINS pDevIns)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ Log(("%s:\n", __FUNCTION__));
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].fRedo && pThis->aCts[i].fRedoIdle)
+ {
+ int rc = RTSemEventSignal(pThisCC->aCts[i].hSuspendIOSem);
+ AssertRC(rc);
+ }
+ }
+ return;
+}
+
+
+/**
+ * Checks if all (both) the async I/O threads have quiesced.
+ *
+ * @returns true on success.
+ * @returns false when one or more threads is still processing.
+ * @param pDevIns Pointer to the PDM device instance.
+ */
+static bool ataR3AllAsyncIOIsIdle(PPDMDEVINS pDevIns)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ bool fRc = ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false /*fStrict*/);
+ if (!fRc)
+ {
+ /* Make it signal PDM & itself when its done */
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].AsyncIORequestLock, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].AsyncIORequestLock, rcLock);
+
+ ASMAtomicWriteBool(&pThisCC->aCts[i].fSignalIdle, true);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->aCts[i].AsyncIORequestLock);
+
+ fRc = ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false /*fStrict*/);
+ if (!fRc)
+ {
+#if 0 /** @todo Need to do some time tracking here... */
+ LogRel(("PIIX3 ATA: Ctl#%u is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x\n",
+ i, pThis->aCts[i].iSelectedIf, pThis->aCts[i].iAIOIf,
+ pThis->aCts[i].aIfs[0].uATARegCommand, pThis->aCts[i].aIfs[1].uATARegCommand));
+#endif
+ return false;
+ }
+ }
+ ASMAtomicWriteBool(&pThisCC->aCts[i].fSignalIdle, false);
+ }
+ return true;
+}
+
+/**
+ * Prepare state save and load operation.
+ *
+ * @returns VBox status code.
+ * @param pDevIns Device instance of the device which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+static DECLCALLBACK(int) ataR3SaveLoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ RT_NOREF(pSSM);
+
+ /* sanity - the suspend notification will wait on the async stuff. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ AssertLogRelMsgReturn(ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false /*fStrict*/),
+ ("i=%u\n", i),
+ VERR_SSM_IDE_ASYNC_TIMEOUT);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @copydoc FNSSMDEVLIVEEXEC
+ */
+static DECLCALLBACK(int) ataR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ RT_NOREF(uPass);
+
+ pHlp->pfnSSMPutU8(pSSM, (uint8_t)pThis->enmChipset);
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pHlp->pfnSSMPutBool(pSSM, true); /* For controller enabled / disabled. */
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ pHlp->pfnSSMPutBool(pSSM, pThisCC->aCts[i].aIfs[j].pDrvBase != NULL);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aCts[i].aIfs[j].szSerialNumber);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aCts[i].aIfs[j].szFirmwareRevision);
+ pHlp->pfnSSMPutStrZ(pSSM, pThis->aCts[i].aIfs[j].szModelNumber);
+ }
+ }
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * @copydoc FNSSMDEVSAVEEXEC
+ */
+static DECLCALLBACK(int) ataR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ ataR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].iSelectedIf);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].iAIOIf);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].uAsyncIOState);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fChainedTransfer);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fReset);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fRedo);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fRedoIdle);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].fRedoDMALastDesc);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ pHlp->pfnSSMPutGCPhys32(pSSM, pThis->aCts[i].GCPhysFirstDMADesc);
+ pHlp->pfnSSMPutGCPhys32(pSSM, pThis->aCts[i].GCPhysLastDMADesc);
+ pHlp->pfnSSMPutGCPhys32(pSSM, pThis->aCts[i].GCPhysRedoDMABuffer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fLBA48);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fATAPI);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fIrqPending);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].cMultSectors);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].XCHSGeometry.cCylinders);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].XCHSGeometry.cHeads);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].XCHSGeometry.cSectors);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ pHlp->pfnSSMPutU64(pSSM, pThis->aCts[i].aIfs[j].cTotalSectors);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegFeature);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegError);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegNSector);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSector);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegLCyl);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegHCyl);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegSelect);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegStatus);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegCommand);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uATATransferMode);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].uTxDir);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].iBeginTransfer);
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].iSourceSink);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fDMA);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aCts[i].aIfs[j].fATAPITransfer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbElementaryTransfer);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferCur);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].iCurLBA);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbATAPISector);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].aIfs[j].abATAPICmd, sizeof(pThis->aCts[i].aIfs[j].abATAPICmd));
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].aIfs[j].abATAPISense, sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ pHlp->pfnSSMPutU8(pSSM, pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].MediaEventStatus);
+ pHlp->pfnSSMPutMem(pSSM, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+ pHlp->pfnSSMPutU32(pSSM, pThis->aCts[i].aIfs[j].cbIOBuffer);
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer)
+ pHlp->pfnSSMPutMem(pSSM, pThis->aCts[i].aIfs[j].abIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer);
+ }
+ }
+
+ return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+/**
+ * Converts the LUN number into a message string.
+ */
+static const char *ataR3StringifyLun(unsigned iLun)
+{
+ switch (iLun)
+ {
+ case 0: return "primary master";
+ case 1: return "primary slave";
+ case 2: return "secondary master";
+ case 3: return "secondary slave";
+ default: AssertFailedReturn("unknown lun");
+ }
+}
+
+/**
+ * FNSSMDEVLOADEXEC
+ */
+static DECLCALLBACK(int) ataR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+ uint32_t u32;
+
+ if ( uVersion != ATA_SAVED_STATE_VERSION
+ && uVersion != ATA_SAVED_STATE_VERSION_WITHOUT_ATA_ILBA
+ && uVersion != ATA_SAVED_STATE_VERSION_VBOX_30
+ && uVersion != ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE
+ && uVersion != ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS
+ && uVersion != ATA_SAVED_STATE_VERSION_WITH_BOOL_TYPE)
+ {
+ AssertMsgFailed(("uVersion=%d\n", uVersion));
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+
+ /*
+ * Verify the configuration.
+ */
+ if (uVersion > ATA_SAVED_STATE_VERSION_VBOX_30)
+ {
+ uint8_t u8Type;
+ rc = pHlp->pfnSSMGetU8(pSSM, &u8Type);
+ AssertRCReturn(rc, rc);
+ if ((CHIPSET)u8Type != pThis->enmChipset)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: enmChipset - saved=%u config=%u"), u8Type, pThis->enmChipset);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ bool fEnabled;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fEnabled);
+ AssertRCReturn(rc, rc);
+ if (!fEnabled)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Ctr#%u onfig mismatch: fEnabled != true"), i);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ ATADEVSTATE const *pIf = &pThis->aCts[i].aIfs[j];
+ ATADEVSTATER3 const *pIfR3 = &pThisCC->aCts[i].aIfs[j];
+
+ bool fInUse;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fInUse);
+ AssertRCReturn(rc, rc);
+ if (fInUse != (pIfR3->pDrvBase != NULL))
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
+ N_("The %s VM is missing a %s device. Please make sure the source and target VMs have compatible storage configurations"),
+ fInUse ? "target" : "source", ataR3StringifyLun(pIf->iLUN) );
+
+ char szSerialNumber[ATA_SERIAL_NUMBER_LENGTH+1];
+ rc = pHlp->pfnSSMGetStrZ(pSSM, szSerialNumber, sizeof(szSerialNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szSerialNumber, pIf->szSerialNumber))
+ LogRel(("PIIX3 ATA: LUN#%u config mismatch: Serial number - saved='%s' config='%s'\n",
+ pIf->iLUN, szSerialNumber, pIf->szSerialNumber));
+
+ char szFirmwareRevision[ATA_FIRMWARE_REVISION_LENGTH+1];
+ rc = pHlp->pfnSSMGetStrZ(pSSM, szFirmwareRevision, sizeof(szFirmwareRevision));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szFirmwareRevision, pIf->szFirmwareRevision))
+ LogRel(("PIIX3 ATA: LUN#%u config mismatch: Firmware revision - saved='%s' config='%s'\n",
+ pIf->iLUN, szFirmwareRevision, pIf->szFirmwareRevision));
+
+ char szModelNumber[ATA_MODEL_NUMBER_LENGTH+1];
+ rc = pHlp->pfnSSMGetStrZ(pSSM, szModelNumber, sizeof(szModelNumber));
+ AssertRCReturn(rc, rc);
+ if (strcmp(szModelNumber, pIf->szModelNumber))
+ LogRel(("PIIX3 ATA: LUN#%u config mismatch: Model number - saved='%s' config='%s'\n",
+ pIf->iLUN, szModelNumber, pIf->szModelNumber));
+ }
+ }
+ }
+ if (uPass != SSM_PASS_FINAL)
+ return VINF_SUCCESS;
+
+ /*
+ * Restore valid parts of the ATASTATE structure
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ /* integrity check */
+ if (!ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false))
+ {
+ AssertMsgFailed(("Async I/O for controller %d is active\n", i));
+ return VERR_INTERNAL_ERROR_4;
+ }
+
+ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].iSelectedIf);
+ AssertRCReturn(rc, rc);
+ AssertLogRelMsgStmt(pThis->aCts[i].iSelectedIf == (pThis->aCts[i].iSelectedIf & ATA_SELECTED_IF_MASK),
+ ("iSelectedIf = %d\n", pThis->aCts[i].iSelectedIf),
+ pThis->aCts[i].iSelectedIf &= ATA_SELECTED_IF_MASK);
+ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].iAIOIf);
+ AssertRCReturn(rc, rc);
+ AssertLogRelMsgStmt(pThis->aCts[i].iAIOIf == (pThis->aCts[i].iAIOIf & ATA_SELECTED_IF_MASK),
+ ("iAIOIf = %d\n", pThis->aCts[i].iAIOIf),
+ pThis->aCts[i].iAIOIf &= ATA_SELECTED_IF_MASK);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].uAsyncIOState);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fChainedTransfer);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fReset);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fRedo);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fRedoIdle);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].fRedoDMALastDesc);
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCts[i].BmDma, sizeof(pThis->aCts[i].BmDma));
+ pHlp->pfnSSMGetGCPhys32(pSSM, &pThis->aCts[i].GCPhysFirstDMADesc);
+ pHlp->pfnSSMGetGCPhys32(pSSM, &pThis->aCts[i].GCPhysLastDMADesc);
+ pHlp->pfnSSMGetGCPhys32(pSSM, &pThis->aCts[i].GCPhysRedoDMABuffer);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].cbRedoDMABuffer);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fLBA48);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fATAPI);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fIrqPending);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].cMultSectors);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].XCHSGeometry.cCylinders);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].XCHSGeometry.cHeads);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].XCHSGeometry.cSectors);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].cSectorsPerIRQ);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->aCts[i].aIfs[j].cTotalSectors);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegFeature);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegFeatureHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegError);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegNSector);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegNSectorHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSector);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSectorHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegLCyl);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegLCylHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegHCyl);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegHCylHOB);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegSelect);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegStatus);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegCommand);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATARegDevCtl);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uATATransferMode);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].uTxDir);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].iBeginTransfer);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].iSourceSink);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fDMA);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aCts[i].aIfs[j].fATAPITransfer);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].cbTotalTransfer);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].cbElementaryTransfer);
+ /* NB: cbPIOTransferLimit could be saved/restored but it's sufficient
+ * to re-calculate it here, with a tiny risk that it could be
+ * unnecessarily low for the current transfer only. Could be changed
+ * when changing the saved state in the future.
+ */
+ pThis->aCts[i].aIfs[j].cbPIOTransferLimit = (pThis->aCts[i].aIfs[j].uATARegHCyl << 8) | pThis->aCts[i].aIfs[j].uATARegLCyl;
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferCur);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferEnd);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferPIODataStart);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iIOBufferPIODataEnd);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].iCurLBA);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aCts[i].aIfs[j].cbATAPISector);
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCts[i].aIfs[j].abATAPICmd, sizeof(pThis->aCts[i].aIfs[j].abATAPICmd));
+ if (uVersion > ATA_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
+ pHlp->pfnSSMGetMem(pSSM, pThis->aCts[i].aIfs[j].abATAPISense, sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ else
+ {
+ uint8_t uATAPISenseKey, uATAPIASC;
+ memset(pThis->aCts[i].aIfs[j].abATAPISense, '\0', sizeof(pThis->aCts[i].aIfs[j].abATAPISense));
+ pThis->aCts[i].aIfs[j].abATAPISense[0] = 0x70 | (1 << 7);
+ pThis->aCts[i].aIfs[j].abATAPISense[7] = 10;
+ pHlp->pfnSSMGetU8(pSSM, &uATAPISenseKey);
+ pHlp->pfnSSMGetU8(pSSM, &uATAPIASC);
+ pThis->aCts[i].aIfs[j].abATAPISense[2] = uATAPISenseKey & 0x0f;
+ pThis->aCts[i].aIfs[j].abATAPISense[12] = uATAPIASC;
+ }
+ /** @todo triple-check this hack after passthrough is working */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->aCts[i].aIfs[j].cNotifiedMediaChange);
+ if (uVersion > ATA_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
+ pHlp->pfnSSMGetU32V(pSSM, &pThis->aCts[i].aIfs[j].MediaEventStatus);
+ else
+ pThis->aCts[i].aIfs[j].MediaEventStatus = ATA_EVENT_STATUS_UNCHANGED;
+ pHlp->pfnSSMGetMem(pSSM, &pThis->aCts[i].aIfs[j].Led, sizeof(pThis->aCts[i].aIfs[j].Led));
+
+ uint32_t cbIOBuffer = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cbIOBuffer);
+ AssertRCReturn(rc, rc);
+
+ if ( (uVersion <= ATA_SAVED_STATE_VERSION_WITHOUT_ATA_ILBA)
+ && !pThis->aCts[i].aIfs[j].fATAPI)
+ {
+ pThis->aCts[i].aIfs[j].iCurLBA = ataR3GetSector(&pThis->aCts[i].aIfs[j]);
+ }
+
+ if (cbIOBuffer)
+ {
+ if (cbIOBuffer <= sizeof(pThis->aCts[i].aIfs[j].abIOBuffer))
+ {
+ if (pThis->aCts[i].aIfs[j].cbIOBuffer != cbIOBuffer)
+ LogRel(("ATA: %u/%u: Restoring cbIOBuffer=%u; constructor set up %u!\n", i, j, cbIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer));
+ pThis->aCts[i].aIfs[j].cbIOBuffer = cbIOBuffer;
+ pHlp->pfnSSMGetMem(pSSM, pThis->aCts[i].aIfs[j].abIOBuffer, cbIOBuffer);
+ }
+ else
+ {
+ LogRel(("ATA: %u/%u: Restoring cbIOBuffer=%u, only prepared %u!\n", i, j, cbIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer));
+ if (pHlp->pfnSSMHandleGetAfter(pSSM) != SSMAFTER_DEBUG_IT)
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
+ N_("ATA: %u/%u: Restoring cbIOBuffer=%u, only prepared %u"),
+ i, j, cbIOBuffer, pThis->aCts[i].aIfs[j].cbIOBuffer);
+
+ /* skip the buffer if we're loading for the debugger / animator. */
+ pHlp->pfnSSMSkip(pSSM, cbIOBuffer);
+ }
+ }
+ else
+ AssertLogRelMsgStmt(pThis->aCts[i].aIfs[j].cbIOBuffer == 0,
+ ("ATA: %u/%u: cbIOBuffer=%u restoring zero!\n", i, j, pThis->aCts[i].aIfs[j].cbIOBuffer),
+ pThis->aCts[i].aIfs[j].cbIOBuffer = 0);
+ }
+ }
+ if (uVersion <= ATA_SAVED_STATE_VERSION_VBOX_30)
+ PDMDEVHLP_SSM_GET_ENUM8_RET(pHlp, pSSM, pThis->enmChipset, CHIPSET);
+
+ rc = pHlp->pfnSSMGetU32(pSSM, &u32);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (u32 != ~0U)
+ {
+ AssertMsgFailed(("u32=%#x expected ~0\n", u32));
+ rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Callback employed by ataSuspend and ataR3PowerOff.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) ataR3IsAsyncSuspendOrPowerOffDone(PPDMDEVINS pDevIns)
+{
+ return ataR3AllAsyncIOIsIdle(pDevIns);
+}
+
+
+/**
+ * Common worker for ataSuspend and ataR3PowerOff.
+ */
+static void ataR3SuspendOrPowerOff(PPDMDEVINS pDevIns)
+{
+ if (!ataR3AllAsyncIOIsIdle(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ataR3IsAsyncSuspendOrPowerOffDone);
+}
+
+
+/**
+ * Power Off notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3PowerOff(PPDMDEVINS pDevIns)
+{
+ Log(("%s:\n", __FUNCTION__));
+ ataR3SuspendOrPowerOff(pDevIns);
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3Suspend(PPDMDEVINS pDevIns)
+{
+ Log(("%s:\n", __FUNCTION__));
+ ataR3SuspendOrPowerOff(pDevIns);
+}
+
+
+/**
+ * Callback employed by ataR3Reset.
+ *
+ * @returns true if we've quiesced, false if we're still working.
+ * @param pDevIns The device instance.
+ */
+static DECLCALLBACK(bool) ataR3IsAsyncResetDone(PPDMDEVINS pDevIns)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+
+ if (!ataR3AllAsyncIOIsIdle(pDevIns))
+ return false;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].lock, VERR_INTERNAL_ERROR);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].lock, rcLock);
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ ataR3ResetDevice(pDevIns, &pThis->aCts[i], &pThis->aCts[i].aIfs[j]);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->aCts[i].lock);
+ }
+ return true;
+}
+
+
+/**
+ * Common reset worker for ataR3Reset and ataR3Construct.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ * @param fConstruct Indicates who is calling.
+ */
+static int ataR3ResetCommon(PPDMDEVINS pDevIns, bool fConstruct)
+{
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].lock, VERR_INTERNAL_ERROR);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].lock, rcLock);
+
+ pThis->aCts[i].iSelectedIf = 0;
+ pThis->aCts[i].iAIOIf = 0;
+ pThis->aCts[i].BmDma.u8Cmd = 0;
+ /* Report that both drives present on the bus are in DMA mode. This
+ * pretends that there is a BIOS that has set it up. Normal reset
+ * default is 0x00. */
+ pThis->aCts[i].BmDma.u8Status = (pThisCC->aCts[i].aIfs[0].pDrvBase != NULL ? BM_STATUS_D0DMA : 0)
+ | (pThisCC->aCts[i].aIfs[1].pDrvBase != NULL ? BM_STATUS_D1DMA : 0);
+ pThis->aCts[i].BmDma.GCPhysAddr = 0;
+
+ pThis->aCts[i].fReset = true;
+ pThis->aCts[i].fRedo = false;
+ pThis->aCts[i].fRedoIdle = false;
+ ataR3AsyncIOClearRequests(pDevIns, &pThis->aCts[i]);
+ Log2(("%s: Ctl#%d: message to async I/O thread, reset controller\n", __FUNCTION__, i));
+ ataHCAsyncIOPutRequest(pDevIns, &pThis->aCts[i], &g_ataResetARequest);
+ ataHCAsyncIOPutRequest(pDevIns, &pThis->aCts[i], &g_ataResetCRequest);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->aCts[i].lock);
+ }
+
+ int rcRet = VINF_SUCCESS;
+ if (!fConstruct)
+ {
+ /*
+ * Setup asynchronous notification completion if the requests haven't
+ * completed yet.
+ */
+ if (!ataR3IsAsyncResetDone(pDevIns))
+ PDMDevHlpSetAsyncNotification(pDevIns, ataR3IsAsyncResetDone);
+ }
+ else
+ {
+ /*
+ * Wait for the requests for complete.
+ *
+ * Would be real nice if we could do it all from EMT(0) and not
+ * involve the worker threads, then we could dispense with all the
+ * waiting and semaphore ping-pong here...
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->aCts[i].AsyncIORequestLock, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->aCts[i].AsyncIORequestLock, rc);
+
+ ASMAtomicWriteBool(&pThisCC->aCts[i].fSignalIdle, true);
+ rc = RTThreadUserReset(pThisCC->aCts[i].hAsyncIOThread);
+ AssertRC(rc);
+
+ rc = PDMDevHlpCritSectLeave(pDevIns, &pThis->aCts[i].AsyncIORequestLock);
+ AssertRC(rc);
+
+ if (!ataR3AsyncIOIsIdle(pDevIns, &pThis->aCts[i], false /*fStrict*/))
+ {
+ rc = RTThreadUserWait(pThisCC->aCts[i].hAsyncIOThread, 30*1000 /*ms*/);
+ if (RT_FAILURE(rc))
+ rc = RTThreadUserWait(pThisCC->aCts[i].hAsyncIOThread, 1000 /*ms*/);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ rcRet = rc;
+ }
+ }
+ }
+ ASMAtomicWriteBool(&pThisCC->aCts[i].fSignalIdle, false);
+ }
+ if (RT_SUCCESS(rcRet))
+ {
+ rcRet = ataR3IsAsyncResetDone(pDevIns) ? VINF_SUCCESS : VERR_INTERNAL_ERROR;
+ AssertRC(rcRet);
+ }
+ }
+ return rcRet;
+}
+
+/**
+ * Reset notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ataR3Reset(PPDMDEVINS pDevIns)
+{
+ ataR3ResetCommon(pDevIns, false /*fConstruct*/);
+}
+
+/**
+ * Destroy a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(int) ataR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATECC);
+ int rc;
+
+ Log(("ataR3Destruct\n"));
+
+ /*
+ * Tell the async I/O threads to terminate.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ ASMAtomicWriteU32(&pThisCC->aCts[i].fShutdown, true);
+ rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aCts[i].hAsyncIOSem);
+ AssertRC(rc);
+ rc = RTSemEventSignal(pThisCC->aCts[i].hSuspendIOSem);
+ AssertRC(rc);
+ }
+ }
+
+ /*
+ * Wait for the threads to terminate before destroying their resources.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ rc = RTThreadWait(pThisCC->aCts[i].hAsyncIOThread, 30000 /* 30 s*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThisCC->aCts[i].hAsyncIOThread = NIL_RTTHREAD;
+ else
+ LogRel(("PIIX3 ATA Dtor: Ctl#%u is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x rc=%Rrc\n",
+ i, pThis->aCts[i].iSelectedIf, pThis->aCts[i].iAIOIf,
+ pThis->aCts[i].aIfs[0].uATARegCommand, pThis->aCts[i].aIfs[1].uATARegCommand, rc));
+ }
+ }
+
+ /*
+ * Free resources.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->aCts[i].AsyncIORequestLock))
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->aCts[i].AsyncIORequestLock);
+ if (pThis->aCts[i].hAsyncIOSem != NIL_SUPSEMEVENT)
+ {
+ PDMDevHlpSUPSemEventClose(pDevIns, pThis->aCts[i].hAsyncIOSem);
+ pThis->aCts[i].hAsyncIOSem = NIL_SUPSEMEVENT;
+ }
+ if (pThisCC->aCts[i].hSuspendIOSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThisCC->aCts[i].hSuspendIOSem);
+ pThisCC->aCts[i].hSuspendIOSem = NIL_RTSEMEVENT;
+ }
+
+ /* try one final time */
+ if (pThisCC->aCts[i].hAsyncIOThread != NIL_RTTHREAD)
+ {
+ rc = RTThreadWait(pThisCC->aCts[i].hAsyncIOThread, 1 /*ms*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->aCts[i].hAsyncIOThread = NIL_RTTHREAD;
+ LogRel(("PIIX3 ATA Dtor: Ctl#%u actually completed.\n", i));
+ }
+ }
+
+ for (uint32_t iIf = 0; iIf < RT_ELEMENTS(pThis->aCts[i].aIfs); iIf++)
+ {
+ if (pThisCC->aCts[i].aIfs[iIf].pTrackList)
+ {
+ ATAPIPassthroughTrackListDestroy(pThisCC->aCts[i].aIfs[iIf].pTrackList);
+ pThisCC->aCts[i].aIfs[iIf].pTrackList = NULL;
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Convert config value to DEVPCBIOSBOOT.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ * @param pCfg Configuration handle.
+ * @param penmChipset Where to store the chipset type.
+ */
+static int ataR3ControllerFromCfg(PPDMDEVINS pDevIns, PCFGMNODE pCfg, CHIPSET *penmChipset)
+{
+ char szType[20];
+
+ int rc = pDevIns->pHlpR3->pfnCFGMQueryStringDef(pCfg, "Type", &szType[0], sizeof(szType), "PIIX4");
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("Configuration error: Querying \"Type\" as a string failed"));
+ if (!strcmp(szType, "PIIX3"))
+ *penmChipset = CHIPSET_PIIX3;
+ else if (!strcmp(szType, "PIIX4"))
+ *penmChipset = CHIPSET_PIIX4;
+ else if (!strcmp(szType, "ICH6"))
+ *penmChipset = CHIPSET_ICH6;
+ else
+ {
+ PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("Configuration error: The \"Type\" value \"%s\" is unknown"),
+ szType);
+ rc = VERR_INTERNAL_ERROR;
+ }
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) ataR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+ PATASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PATASTATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ PPDMIBASE pBase;
+ int rc;
+ uint32_t msDelayIRQ;
+
+ Assert(iInstance == 0);
+
+ /*
+ * Initialize NIL handle values (for the destructor).
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThis->aCts[i].iCtl = i;
+ pThis->aCts[i].hAsyncIOSem = NIL_SUPSEMEVENT;
+ pThis->aCts[i].hIoPorts1First = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPorts1Other = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPorts2 = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPortsEmpty1 = NIL_IOMIOPORTHANDLE;
+ pThis->aCts[i].hIoPortsEmpty2 = NIL_IOMIOPORTHANDLE;
+
+ pThisCC->aCts[i].iCtl = i;
+ pThisCC->aCts[i].hSuspendIOSem = NIL_RTSEMEVENT;
+ pThisCC->aCts[i].hAsyncIOThread = NIL_RTTHREAD;
+ }
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQDelay|Type", "PrimaryMaster|PrimarySlave|SecondaryMaster|SecondarySlave");
+
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "IRQDelay", &msDelayIRQ, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 configuration error: failed to read IRQDelay as integer"));
+ Log(("%s: msDelayIRQ=%d\n", __FUNCTION__, msDelayIRQ));
+ Assert(msDelayIRQ < 50);
+
+ CHIPSET enmChipset = CHIPSET_PIIX3;
+ rc = ataR3ControllerFromCfg(pDevIns, pCfg, &enmChipset);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->enmChipset = enmChipset;
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ /* Status LUN. */
+ pThisCC->IBase.pfnQueryInterface = ataR3Status_QueryInterface;
+ pThisCC->ILeds.pfnQueryStatusLed = ataR3Status_QueryStatusLed;
+
+ /* PCI configuration space. */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+ PDMPciDevSetVendorId(pPciDev, 0x8086); /* Intel */
+
+ /*
+ * When adding more IDE chipsets, don't forget to update pci_bios_init_device()
+ * as it explicitly checks for PCI id for IDE controllers.
+ */
+ switch (enmChipset)
+ {
+ case CHIPSET_ICH6:
+ PDMPciDevSetDeviceId(pPciDev, 0x269e); /* ICH6 IDE */
+ /** @todo do we need it? Do we need anything else? */
+ PDMPciDevSetByte(pPciDev, 0x48, 0x00); /* UDMACTL */
+ PDMPciDevSetByte(pPciDev, 0x4A, 0x00); /* UDMATIM */
+ PDMPciDevSetByte(pPciDev, 0x4B, 0x00);
+ {
+ /*
+ * See www.intel.com/Assets/PDF/manual/298600.pdf p. 30
+ * Report
+ * WR_Ping-Pong_EN: must be set
+ * PCR0, PCR1: 80-pin primary cable reporting for both disks
+ * SCR0, SCR1: 80-pin secondary cable reporting for both disks
+ */
+ uint16_t u16Config = (1<<10) | (1<<7) | (1<<6) | (1<<5) | (1<<4);
+ PDMPciDevSetByte(pPciDev, 0x54, u16Config & 0xff);
+ PDMPciDevSetByte(pPciDev, 0x55, u16Config >> 8);
+ }
+ break;
+ case CHIPSET_PIIX4:
+ PDMPciDevSetDeviceId(pPciDev, 0x7111); /* PIIX4 IDE */
+ PDMPciDevSetRevisionId(pPciDev, 0x01); /* PIIX4E */
+ PDMPciDevSetByte(pPciDev, 0x48, 0x00); /* UDMACTL */
+ PDMPciDevSetByte(pPciDev, 0x4A, 0x00); /* UDMATIM */
+ PDMPciDevSetByte(pPciDev, 0x4B, 0x00);
+ break;
+ case CHIPSET_PIIX3:
+ PDMPciDevSetDeviceId(pPciDev, 0x7010); /* PIIX3 IDE */
+ break;
+ default:
+ AssertMsgFailed(("Unsupported IDE chipset type: %d\n", enmChipset));
+ }
+
+ /** @todo
+ * This is the job of the BIOS / EFI!
+ *
+ * The same is done in DevPCI.cpp / pci_bios_init_device() but there is no
+ * corresponding function in DevPciIch9.cpp. The EFI has corresponding code
+ * in OvmfPkg/Library/PlatformBdsLib/BdsPlatform.c: NotifyDev() but this
+ * function assumes that the IDE controller is located at PCI 00:01.1 which
+ * is not true if the ICH9 chipset is used.
+ */
+ PDMPciDevSetWord(pPciDev, 0x40, 0x8000); /* enable IDE0 */
+ PDMPciDevSetWord(pPciDev, 0x42, 0x8000); /* enable IDE1 */
+
+ PDMPciDevSetCommand( pPciDev, PCI_COMMAND_IOACCESS | PCI_COMMAND_MEMACCESS | PCI_COMMAND_BUSMASTER);
+ PDMPciDevSetClassProg( pPciDev, 0x8a); /* programming interface = PCI_IDE bus-master is supported */
+ PDMPciDevSetClassSub( pPciDev, 0x01); /* class_sub = PCI_IDE */
+ PDMPciDevSetClassBase( pPciDev, 0x01); /* class_base = PCI_mass_storage */
+ PDMPciDevSetHeaderType(pPciDev, 0x00);
+
+ pThisCC->pDevIns = pDevIns;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ pThisCC->aCts[i].pDevIns = pDevIns;
+ pThisCC->aCts[i].iCtl = i;
+ pThis->aCts[i].iCtl = i;
+ pThis->aCts[i].msDelayIRQ = msDelayIRQ;
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ PATADEVSTATE pIf = &pThis->aCts[i].aIfs[j];
+ PATADEVSTATER3 pIfR3 = &pThisCC->aCts[i].aIfs[j];
+
+ pIfR3->iLUN = pIf->iLUN = i * RT_ELEMENTS(pThis->aCts) + j;
+ pIfR3->iCtl = pIf->iCtl = i;
+ pIfR3->iDev = pIf->iDev = j;
+ pIfR3->pDevIns = pDevIns;
+ pIfR3->IBase.pfnQueryInterface = ataR3QueryInterface;
+ pIfR3->IMountNotify.pfnMountNotify = ataR3MountNotify;
+ pIfR3->IMountNotify.pfnUnmountNotify = ataR3UnmountNotify;
+ pIfR3->IPort.pfnQueryDeviceLocation = ataR3QueryDeviceLocation;
+ pIf->Led.u32Magic = PDMLED_MAGIC;
+ }
+ }
+
+ Assert(RT_ELEMENTS(pThis->aCts) == 2);
+ pThis->aCts[0].irq = 14;
+ pThis->aCts[0].IOPortBase1 = 0x1f0;
+ pThis->aCts[0].IOPortBase2 = 0x3f6;
+ pThis->aCts[1].irq = 15;
+ pThis->aCts[1].IOPortBase1 = 0x170;
+ pThis->aCts[1].IOPortBase2 = 0x376;
+
+ /*
+ * Set the default critical section to NOP as we lock on controller level.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register the PCI device.
+ */
+ rc = PDMDevHlpPCIRegisterEx(pDevIns, pPciDev, PDMPCIDEVREG_F_NOT_MANDATORY_NO, 1 /*uPciDevNo*/, 1 /*uPciDevFn*/, "piix3ide");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register PCI device"));
+
+ /* Region #4: I/O ports for the two bus-master DMA controllers. */
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 4 /*iPciRegion*/, 0x10 /*cPorts*/,
+ ataBMDMAIOPortWrite, ataBMDMAIOPortRead, NULL /*pvUser*/, "ATA Bus Master DMA",
+ NULL /*paExtDescs*/, &pThis->hIoPortsBmDma);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register stats, create critical sections.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ for (uint32_t j = 0; j < RT_ELEMENTS(pThis->aCts[i].aIfs); j++)
+ {
+ PATADEVSTATE pIf = &pThis->aCts[i].aIfs[j];
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATADMA, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATA DMA transfers.", "/Devices/IDE%d/ATA%d/Unit%d/DMA", iInstance, i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIO, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATA PIO transfers.", "/Devices/IDE%d/ATA%d/Unit%d/PIO", iInstance, i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIDMA, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATAPI DMA transfers.", "/Devices/IDE%d/ATA%d/Unit%d/AtapiDMA", iInstance, i, j);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatATAPIPIO, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "Number of ATAPI PIO transfers.", "/Devices/IDE%d/ATA%d/Unit%d/AtapiPIO", iInstance, i, j);
+#ifdef VBOX_WITH_STATISTICS /** @todo release too. */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatReads, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the read operations.", "/Devices/IDE%d/ATA%d/Unit%d/Reads", iInstance, i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+ "Amount of data read.", "/Devices/IDE%d/ATA%d/Unit%d/ReadBytes", iInstance, i, j);
+#ifdef VBOX_INSTRUMENT_DMA_WRITES
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatInstrVDWrites,STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the VD DMA write operations.", "/Devices/IDE%d/ATA%d/Unit%d/InstrVDWrites", iInstance, i, j);
+#endif
+#ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatWrites, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the write operations.", "/Devices/IDE%d/ATA%d/Unit%d/Writes", iInstance, i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
+ "Amount of data written.", "/Devices/IDE%d/ATA%d/Unit%d/WrittenBytes", iInstance, i, j);
+#ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatFlushes, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of the flush operations.", "/Devices/IDE%d/ATA%d/Unit%d/Flushes", iInstance, i, j);
+#endif
+ PDMDevHlpSTAMRegisterF(pDevIns, &pIf->StatStatusYields, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of status polling yields.", "/Devices/IDE%d/ATA%d/Unit%d/StatusYields", iInstance, i, j);
+ }
+#ifdef VBOX_WITH_STATISTICS /** @todo release too. */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncOps, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ "The number of async operations.", "/Devices/IDE%d/ATA%d/Async/Operations", iInstance, i);
+ /** @todo STAMUNIT_MICROSECS */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncMinWait, STAMTYPE_U64_RESET, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Minimum wait in microseconds.", "/Devices/IDE%d/ATA%d/Async/MinWait", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncMaxWait, STAMTYPE_U64_RESET, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Maximum wait in microseconds.", "/Devices/IDE%d/ATA%d/Async/MaxWait", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncTimeUS, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Total time spent in microseconds.", "/Devices/IDE%d/ATA%d/Async/TotalTimeUS", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatAsyncTime, STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of async operations.", "/Devices/IDE%d/ATA%d/Async/Time", iInstance, i);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aCts[i].StatLockWait, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL,
+ "Profiling of locks.", "/Devices/IDE%d/ATA%d/Async/LockWait", iInstance, i);
+#endif /* VBOX_WITH_STATISTICS */
+
+ /* Initialize per-controller critical section. */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aCts[i].lock, RT_SRC_POS, "ATA#%u-Ctl", i);
+ AssertLogRelRCReturn(rc, rc);
+
+ /* Initialize per-controller async I/O request critical section. */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aCts[i].AsyncIORequestLock, RT_SRC_POS, "ATA#%u-Req", i);
+ AssertLogRelRCReturn(rc, rc);
+ }
+
+ /*
+ * Attach status driver (optional).
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ pThisCC->pMediaNotify = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIANOTIFY);
+ }
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot attach to status driver"));
+ }
+
+ /*
+ * Attach the units.
+ */
+ uint32_t cbTotalBuffer = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ PATACONTROLLER pCtl = &pThis->aCts[i];
+ PATACONTROLLERR3 pCtlR3 = &pThisCC->aCts[i];
+
+ /*
+ * Start the worker thread.
+ */
+ pCtl->uAsyncIOState = ATA_AIO_NEW;
+ rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pCtl->hAsyncIOSem);
+ AssertLogRelRCReturn(rc, rc);
+ rc = RTSemEventCreate(&pCtlR3->hSuspendIOSem);
+ AssertLogRelRCReturn(rc, rc);
+
+ ataR3AsyncIOClearRequests(pDevIns, pCtl);
+ rc = RTThreadCreateF(&pCtlR3->hAsyncIOThread, ataR3AsyncIOThread, pCtlR3, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ATA-%u", i);
+ AssertLogRelRCReturn(rc, rc);
+ Assert( pCtlR3->hAsyncIOThread != NIL_RTTHREAD && pCtl->hAsyncIOSem != NIL_SUPSEMEVENT
+ && pCtlR3->hSuspendIOSem != NIL_RTSEMEVENT && PDMDevHlpCritSectIsInitialized(pDevIns, &pCtl->AsyncIORequestLock));
+ Log(("%s: controller %d AIO thread id %#x; sem %p susp_sem %p\n", __FUNCTION__, i, pCtlR3->hAsyncIOThread, pCtl->hAsyncIOSem, pCtlR3->hSuspendIOSem));
+
+ for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
+ {
+ static const char *s_apszDescs[RT_ELEMENTS(pThis->aCts)][RT_ELEMENTS(pCtl->aIfs)] =
+ {
+ { "Primary Master", "Primary Slave" },
+ { "Secondary Master", "Secondary Slave" }
+ };
+
+ /*
+ * Try attach the block device and get the interfaces,
+ * required as well as optional.
+ */
+ PATADEVSTATE pIf = &pCtl->aIfs[j];
+ PATADEVSTATER3 pIfR3 = &pCtlR3->aIfs[j];
+
+ rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIfR3->IBase, &pIfR3->pDrvBase, s_apszDescs[i][j]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ataR3ConfigLun(pIf, pIfR3);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Init vendor product data.
+ */
+ static const char *s_apszCFGMKeys[RT_ELEMENTS(pThis->aCts)][RT_ELEMENTS(pCtl->aIfs)] =
+ {
+ { "PrimaryMaster", "PrimarySlave" },
+ { "SecondaryMaster", "SecondarySlave" }
+ };
+
+ /* Generate a default serial number. */
+ char szSerial[ATA_SERIAL_NUMBER_LENGTH+1];
+ RTUUID Uuid;
+ if (pIfR3->pDrvMedia)
+ rc = pIfR3->pDrvMedia->pfnGetUuid(pIfR3->pDrvMedia, &Uuid);
+ else
+ RTUuidClear(&Uuid);
+
+ if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
+ {
+ /* Generate a predictable serial for drives which don't have a UUID. */
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%x-%04x%04x",
+ pIf->iLUN + pDevIns->iInstance * 32,
+ pThis->aCts[i].IOPortBase1, pThis->aCts[i].IOPortBase2);
+ }
+ else
+ RTStrPrintf(szSerial, sizeof(szSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
+
+ /* Get user config if present using defaults otherwise. */
+ PCFGMNODE pCfgNode = pHlp->pfnCFGMGetChild(pCfg, s_apszCFGMKeys[i][j]);
+ rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "SerialNumber", pIf->szSerialNumber, sizeof(pIf->szSerialNumber),
+ szSerial);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"SerialNumber\" is longer than 20 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"SerialNumber\" as string"));
+ }
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "FirmwareRevision", pIf->szFirmwareRevision,
+ sizeof(pIf->szFirmwareRevision), "1.0");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"FirmwareRevision\" is longer than 8 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"FirmwareRevision\" as string"));
+ }
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ModelNumber", pIf->szModelNumber, sizeof(pIf->szModelNumber),
+ pIf->fATAPI ? "VBOX CD-ROM" : "VBOX HARDDISK");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ModelNumber\" is longer than 40 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ModelNumber\" as string"));
+ }
+
+ /* There are three other identification strings for CD drives used for INQUIRY */
+ if (pIf->fATAPI)
+ {
+ rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ATAPIVendorId", pIf->szInquiryVendorId,
+ sizeof(pIf->szInquiryVendorId), "VBOX");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ATAPIVendorId\" is longer than 16 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ATAPIVendorId\" as string"));
+ }
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ATAPIProductId", pIf->szInquiryProductId,
+ sizeof(pIf->szInquiryProductId), "CD-ROM");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ATAPIProductId\" is longer than 16 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ATAPIProductId\" as string"));
+ }
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ATAPIRevision", pIf->szInquiryRevision,
+ sizeof(pIf->szInquiryRevision), "1.0");
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("PIIX3 configuration error: \"ATAPIRevision\" is longer than 4 bytes"));
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"ATAPIRevision\" as string"));
+ }
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfgNode, "OverwriteInquiry", &pIf->fOverwriteInquiry, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("PIIX3 configuration error: failed to read \"OverwriteInquiry\" as boolean"));
+ }
+ }
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pIfR3->pDrvBase = NULL;
+ pIfR3->pDrvMedia = NULL;
+ pIf->cbIOBuffer = 0;
+ pIf->fPresent = false;
+ LogRel(("PIIX3 ATA: LUN#%d: no unit\n", pIf->iLUN));
+ }
+ else
+ {
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ /* Error already cached by DrvHostBase */
+ return rc;
+ default:
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("PIIX3 cannot attach drive to the %s"),
+ s_apszDescs[i][j]);
+ }
+ }
+ cbTotalBuffer += pIf->cbIOBuffer;
+ }
+ }
+
+ /*
+ * Register the I/O ports.
+ * The ports are all hardcoded and enforced by the PIIX3 host bridge controller.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ Assert(pThis->aCts[i].aIfs[0].fPresent == (pThisCC->aCts[i].aIfs[0].pDrvMedia != NULL));
+ Assert(pThis->aCts[i].aIfs[1].fPresent == (pThisCC->aCts[i].aIfs[1].pDrvMedia != NULL));
+
+ if (!pThisCC->aCts[i].aIfs[0].pDrvMedia && !pThisCC->aCts[i].aIfs[1].pDrvMedia)
+ {
+ /* No device present on this ATA bus; requires special handling. */
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase1, 8 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 1 - Empty Bus", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPortsEmpty1);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase2, 1 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 2 - Empty Bus", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPortsEmpty2);
+ AssertLogRelRCReturn(rc, rc);
+ }
+ else
+ {
+ /* At least one device present, register regular handlers. */
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase1, 1 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWrite1Data, ataIOPortRead1Data,
+ ataIOPortWriteStr1Data, ataIOPortReadStr1Data, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 1 - Data", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPorts1First);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase1 + 1, 7 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWrite1Other, ataIOPortRead1Other, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 1 - Other", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPorts1Other);
+ AssertLogRelRCReturn(rc, rc);
+
+
+ rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, pThis->aCts[i].IOPortBase2, 1 /*cPorts*/, IOM_IOPORT_F_ABS,
+ ataIOPortWrite2, ataIOPortRead2, NULL, NULL, (RTHCPTR)(uintptr_t)i,
+ "ATA I/O Base 2", NULL /*paExtDescs*/, &pThis->aCts[i].hIoPorts2);
+ AssertLogRelRCReturn(rc, rc);
+ }
+ }
+
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, ATA_SAVED_STATE_VERSION, sizeof(*pThis) + cbTotalBuffer, NULL,
+ NULL, ataR3LiveExec, NULL,
+ ataR3SaveLoadPrep, ataR3SaveExec, NULL,
+ ataR3SaveLoadPrep, ataR3LoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("PIIX3 cannot register save state handlers"));
+
+ /*
+ * Initialize the device state.
+ */
+ return ataR3ResetCommon(pDevIns, true /*fConstruct*/);
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ataRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PATASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PATASTATE);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsBmDma, ataBMDMAIOPortWrite, ataBMDMAIOPortRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
+ {
+ if (pThis->aCts[i].hIoPorts1First != NIL_IOMIOPORTHANDLE)
+ {
+ rc = PDMDevHlpIoPortSetUpContextEx(pDevIns, pThis->aCts[i].hIoPorts1First,
+ ataIOPortWrite1Data, ataIOPortRead1Data,
+ ataIOPortWriteStr1Data, ataIOPortReadStr1Data, (RTHCPTR)(uintptr_t)i);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPorts1Other,
+ ataIOPortWrite1Other, ataIOPortRead1Other, (RTHCPTR)(uintptr_t)i);
+ AssertLogRelRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPorts2,
+ ataIOPortWrite2, ataIOPortRead2, (RTHCPTR)(uintptr_t)i);
+ AssertLogRelRCReturn(rc, rc);
+ }
+ else
+ {
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPortsEmpty1,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, (void *)(uintptr_t)i /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->aCts[i].hIoPortsEmpty2,
+ ataIOPortWriteEmptyBus, ataIOPortReadEmptyBus, (void *)(uintptr_t)i /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DevicePIIX3IDE =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "piix3ide",
+ /* .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
+ | PDM_DEVREG_FLAGS_FIRST_RESET_NOTIFICATION,
+ /* .fClass = */ PDM_DEVREG_CLASS_STORAGE,
+ /* .cMaxInstances = */ 1,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(ATASTATE),
+ /* .cbInstanceCC = */ sizeof(ATASTATECC),
+ /* .cbInstanceRC = */ sizeof(ATASTATERC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "Intel PIIX3 ATA controller.\n"
+ " LUN #0 is primary master.\n"
+ " LUN #1 is primary slave.\n"
+ " LUN #2 is secondary master.\n"
+ " LUN #3 is secondary slave.\n"
+ " LUN #999 is the LED/Status connector.",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ ataR3Construct,
+ /* .pfnDestruct = */ ataR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ataR3Reset,
+ /* .pfnSuspend = */ ataR3Suspend,
+ /* .pfnResume = */ ataR3Resume,
+ /* .pfnAttach = */ ataR3Attach,
+ /* .pfnDetach = */ ataR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ ataR3PowerOff,
+ /* .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 = */ ataRZConstruct,
+ /* .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 = */ ataRZConstruct,
+ /* .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
+};
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */