diff options
Diffstat (limited to 'src/VBox/Devices/Storage/DevAHCI.cpp')
-rw-r--r-- | src/VBox/Devices/Storage/DevAHCI.cpp | 6172 |
1 files changed, 6172 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/DevAHCI.cpp b/src/VBox/Devices/Storage/DevAHCI.cpp new file mode 100644 index 00000000..199dc04f --- /dev/null +++ b/src/VBox/Devices/Storage/DevAHCI.cpp @@ -0,0 +1,6172 @@ +/* $Id: DevAHCI.cpp $ */ +/** @file + * DevAHCI - AHCI controller device (disk and cdrom). + * + * Implements the AHCI standard 1.1 + */ + +/* + * 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 + */ + +/** @page pg_dev_ahci AHCI - Advanced Host Controller Interface Emulation. + * + * This component implements an AHCI serial ATA controller. The device is split + * into two parts. The first part implements the register interface for the + * guest and the second one does the data transfer. + * + * The guest can access the controller in two ways. The first one is the native + * way implementing the registers described in the AHCI specification and is + * the preferred one. The second implements the I/O ports used for booting from + * the hard disk and for guests which don't have an AHCI SATA driver. + * + * The data is transfered using the extended media interface, asynchronously if + * it is supported by the driver below otherwise it weill be done synchronous. + * Either way a thread is used to process new requests from the guest. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_AHCI +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmstorageifs.h> +#include <VBox/vmm/pdmqueue.h> +#include <VBox/vmm/pdmthread.h> +#include <VBox/vmm/pdmcritsect.h> +#include <VBox/sup.h> +#include <VBox/scsi.h> +#include <VBox/ata.h> +#include <VBox/AssertGuest.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/list.h> +#ifdef IN_RING3 +# include <iprt/param.h> +# include <iprt/thread.h> +# include <iprt/semaphore.h> +# include <iprt/alloc.h> +# include <iprt/uuid.h> +# include <iprt/time.h> +#endif +#include "VBoxDD.h" + +#if defined(VBOX_WITH_DTRACE) \ + && defined(IN_RING3) \ + && !defined(VBOX_DEVICE_STRUCT_TESTCASE) +# include "dtrace/VBoxDD.h" +#else +# define VBOXDD_AHCI_REQ_SUBMIT(a,b,c,d) do { } while (0) +# define VBOXDD_AHCI_REQ_COMPLETED(a,b,c,d) do { } while (0) +#endif + +/** Maximum number of ports available. + * Spec defines 32 but we have one allocated for command completion coalescing + * and another for a reserved future feature. + */ +#define AHCI_MAX_NR_PORTS_IMPL 30 +/** Maximum number of command slots available. */ +#define AHCI_NR_COMMAND_SLOTS 32 + +/** The current saved state version. */ +#define AHCI_SAVED_STATE_VERSION 9 +/** The saved state version before the ATAPI emulation was removed and the generic SCSI driver was used. */ +#define AHCI_SAVED_STATE_VERSION_PRE_ATAPI_REMOVE 8 +/** The saved state version before changing the port reset logic in an incompatible way. */ +#define AHCI_SAVED_STATE_VERSION_PRE_PORT_RESET_CHANGES 7 +/** Saved state version before the per port hotplug port was added. */ +#define AHCI_SAVED_STATE_VERSION_PRE_HOTPLUG_FLAG 6 +/** Saved state version before legacy ATA emulation was dropped. */ +#define AHCI_SAVED_STATE_VERSION_IDE_EMULATION 5 +/** Saved state version before ATAPI support was added. */ +#define AHCI_SAVED_STATE_VERSION_PRE_ATAPI 3 +/** The saved state version use in VirtualBox 3.0 and earlier. + * This was before the config was added and ahciIOTasks was dropped. */ +#define AHCI_SAVED_STATE_VERSION_VBOX_30 2 +/* for Older ATA state Read handling */ +#define ATA_CTL_SAVED_STATE_VERSION 3 +#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE 1 +#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS 2 + +/** The maximum number of release log entries per device. */ +#define MAX_LOG_REL_ERRORS 1024 + +/** + * 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 + +/** + * 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 + +/** + * Length of the configurable VPD data (without termination) + */ +#define AHCI_SERIAL_NUMBER_LENGTH 20 +#define AHCI_FIRMWARE_REVISION_LENGTH 8 +#define AHCI_MODEL_NUMBER_LENGTH 40 +#define AHCI_ATAPI_INQUIRY_VENDOR_ID_LENGTH 8 +#define AHCI_ATAPI_INQUIRY_PRODUCT_ID_LENGTH 16 +#define AHCI_ATAPI_INQUIRY_REVISION_LENGTH 4 + +/** ATAPI sense info size. */ +#define ATAPI_SENSE_SIZE 64 + +/** + * Command Header. + */ +typedef struct +{ + /** Description Information. */ + uint32_t u32DescInf; + /** Command status. */ + uint32_t u32PRDBC; + /** Command Table Base Address. */ + uint32_t u32CmdTblAddr; + /** Command Table Base Address - upper 32-bits. */ + uint32_t u32CmdTblAddrUp; + /** Reserved */ + uint32_t u32Reserved[4]; +} CmdHdr; +AssertCompileSize(CmdHdr, 32); + +/* Defines for the command header. */ +#define AHCI_CMDHDR_PRDTL_MASK 0xffff0000 +#define AHCI_CMDHDR_PRDTL_ENTRIES(x) ((x & AHCI_CMDHDR_PRDTL_MASK) >> 16) +#define AHCI_CMDHDR_C RT_BIT(10) +#define AHCI_CMDHDR_B RT_BIT(9) +#define AHCI_CMDHDR_R RT_BIT(8) +#define AHCI_CMDHDR_P RT_BIT(7) +#define AHCI_CMDHDR_W RT_BIT(6) +#define AHCI_CMDHDR_A RT_BIT(5) +#define AHCI_CMDHDR_CFL_MASK 0x1f + +#define AHCI_CMDHDR_PRDT_OFFSET 0x80 +#define AHCI_CMDHDR_ACMD_OFFSET 0x40 + +/* Defines for the command FIS. */ +/* Defines that are used in the first double word. */ +#define AHCI_CMDFIS_TYPE 0 /* The first byte. */ +# define AHCI_CMDFIS_TYPE_H2D 0x27 /* Register - Host to Device FIS. */ +# define AHCI_CMDFIS_TYPE_H2D_SIZE 20 /* Five double words. */ +# define AHCI_CMDFIS_TYPE_D2H 0x34 /* Register - Device to Host FIS. */ +# define AHCI_CMDFIS_TYPE_D2H_SIZE 20 /* Five double words. */ +# define AHCI_CMDFIS_TYPE_SETDEVBITS 0xa1 /* Set Device Bits - Device to Host FIS. */ +# define AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE 8 /* Two double words. */ +# define AHCI_CMDFIS_TYPE_DMAACTD2H 0x39 /* DMA Activate - Device to Host FIS. */ +# define AHCI_CMDFIS_TYPE_DMAACTD2H_SIZE 4 /* One double word. */ +# define AHCI_CMDFIS_TYPE_DMASETUP 0x41 /* DMA Setup - Bidirectional FIS. */ +# define AHCI_CMDFIS_TYPE_DMASETUP_SIZE 28 /* Seven double words. */ +# define AHCI_CMDFIS_TYPE_PIOSETUP 0x5f /* PIO Setup - Device to Host FIS. */ +# define AHCI_CMDFIS_TYPE_PIOSETUP_SIZE 20 /* Five double words. */ +# define AHCI_CMDFIS_TYPE_DATA 0x46 /* Data - Bidirectional FIS. */ + +#define AHCI_CMDFIS_BITS 1 /* Interrupt and Update bit. */ +#define AHCI_CMDFIS_C RT_BIT(7) /* Host to device. */ +#define AHCI_CMDFIS_I RT_BIT(6) /* Device to Host. */ +#define AHCI_CMDFIS_D RT_BIT(5) + +#define AHCI_CMDFIS_CMD 2 +#define AHCI_CMDFIS_FET 3 + +#define AHCI_CMDFIS_SECTN 4 +#define AHCI_CMDFIS_CYLL 5 +#define AHCI_CMDFIS_CYLH 6 +#define AHCI_CMDFIS_HEAD 7 + +#define AHCI_CMDFIS_SECTNEXP 8 +#define AHCI_CMDFIS_CYLLEXP 9 +#define AHCI_CMDFIS_CYLHEXP 10 +#define AHCI_CMDFIS_FETEXP 11 + +#define AHCI_CMDFIS_SECTC 12 +#define AHCI_CMDFIS_SECTCEXP 13 +#define AHCI_CMDFIS_CTL 15 +# define AHCI_CMDFIS_CTL_SRST RT_BIT(2) /* Reset device. */ +# define AHCI_CMDFIS_CTL_NIEN RT_BIT(1) /* Assert or clear interrupt. */ + +/* For D2H FIS */ +#define AHCI_CMDFIS_STS 2 +#define AHCI_CMDFIS_ERR 3 + +/** Pointer to a task state. */ +typedef struct AHCIREQ *PAHCIREQ; + +/** Task encountered a buffer overflow. */ +#define AHCI_REQ_OVERFLOW RT_BIT_32(0) +/** Request is a PIO data command, if this flag is not set it either is + * a command which does not transfer data or a DMA command based on the transfer size. */ +#define AHCI_REQ_PIO_DATA RT_BIT_32(1) +/** The request has the SACT register set. */ +#define AHCI_REQ_CLEAR_SACT RT_BIT_32(2) +/** Flag whether the request is queued. */ +#define AHCI_REQ_IS_QUEUED RT_BIT_32(3) +/** Flag whether the request is stored on the stack. */ +#define AHCI_REQ_IS_ON_STACK RT_BIT_32(4) +/** Flag whether this request transfers data from the device to the HBA or + * the other way around .*/ +#define AHCI_REQ_XFER_2_HOST RT_BIT_32(5) + +/** + * A task state. + */ +typedef struct AHCIREQ +{ + /** The I/O request handle from the driver below associated with this request. */ + PDMMEDIAEXIOREQ hIoReq; + /** Tag of the task. */ + uint32_t uTag; + /** The command Fis for this task. */ + uint8_t cmdFis[AHCI_CMDFIS_TYPE_H2D_SIZE]; + /** The ATAPI command data. */ + uint8_t aATAPICmd[ATAPI_PACKET_SIZE]; + /** Physical address of the command header. - GC */ + RTGCPHYS GCPhysCmdHdrAddr; + /** Physical address of the PRDT */ + RTGCPHYS GCPhysPrdtl; + /** Number of entries in the PRDTL. */ + unsigned cPrdtlEntries; + /** Data direction. */ + PDMMEDIAEXIOREQTYPE enmType; + /** Start offset. */ + uint64_t uOffset; + /** Number of bytes to transfer. */ + size_t cbTransfer; + /** Flags for this task. */ + uint32_t fFlags; + /** SCSI status code. */ + uint8_t u8ScsiSts; + /** Flag when the buffer is mapped. */ + bool fMapped; + /** Page lock when the buffer is mapped. */ + PGMPAGEMAPLOCK PgLck; +} AHCIREQ; + +/** + * Notifier queue item. + */ +typedef struct DEVPORTNOTIFIERQUEUEITEM +{ + /** The core part owned by the queue manager. */ + PDMQUEUEITEMCORE Core; + /** The port to process. */ + uint8_t iPort; +} DEVPORTNOTIFIERQUEUEITEM, *PDEVPORTNOTIFIERQUEUEITEM; + + +/** + * The shared state of an AHCI port. + */ +typedef struct AHCIPORT +{ + /** Command List Base Address. */ + uint32_t regCLB; + /** Command List Base Address upper bits. */ + uint32_t regCLBU; + /** FIS Base Address. */ + uint32_t regFB; + /** FIS Base Address upper bits. */ + uint32_t regFBU; + /** Interrupt Status. */ + volatile uint32_t regIS; + /** Interrupt Enable. */ + uint32_t regIE; + /** Command. */ + uint32_t regCMD; + /** Task File Data. */ + uint32_t regTFD; + /** Signature */ + uint32_t regSIG; + /** Serial ATA Status. */ + uint32_t regSSTS; + /** Serial ATA Control. */ + uint32_t regSCTL; + /** Serial ATA Error. */ + uint32_t regSERR; + /** Serial ATA Active. */ + volatile uint32_t regSACT; + /** Command Issue. */ + uint32_t regCI; + + /** Current number of active tasks. */ + volatile uint32_t cTasksActive; + uint32_t u32Alignment1; + /** Command List Base Address */ + volatile RTGCPHYS GCPhysAddrClb; + /** FIS Base Address */ + volatile RTGCPHYS GCPhysAddrFb; + + /** Device is powered on. */ + bool fPoweredOn; + /** Device has spun up. */ + bool fSpunUp; + /** First D2H FIS was sent. */ + bool fFirstD2HFisSent; + /** Attached device is a CD/DVD drive. */ + bool fATAPI; + /** Flag whether this port is in a reset state. */ + volatile bool fPortReset; + /** Flag whether TRIM is supported. */ + bool fTrimEnabled; + /** Flag if we are in a device reset. */ + bool fResetDevice; + /** Flag whether this port is hot plug capable. */ + bool fHotpluggable; + /** Flag whether the port is in redo task mode. */ + volatile bool fRedo; + /** Flag whether the worker thread is sleeping. */ + volatile bool fWrkThreadSleeping; + + bool afAlignment1[2]; + + /** Number of total sectors. */ + uint64_t cTotalSectors; + /** Size of one sector. */ + uint32_t cbSector; + /** Currently configured number of sectors in a multi-sector transfer. */ + uint32_t cMultSectors; + /** The LUN (same as port number). */ + uint32_t iLUN; + /** Set if there is a device present at the port. */ + bool fPresent; + /** Currently active transfer mode (MDMA/UDMA) and speed. */ + uint8_t uATATransferMode; + /** Exponent of logical sectors in a physical sector, number of logical sectors is 2^exp. */ + uint8_t cLogSectorsPerPhysicalExp; + uint8_t bAlignment2; + /** ATAPI sense data. */ + uint8_t abATAPISense[ATAPI_SENSE_SIZE]; + + /** Bitmap for finished tasks (R3 -> Guest). */ + volatile uint32_t u32TasksFinished; + /** Bitmap for finished queued tasks (R3 -> Guest). */ + volatile uint32_t u32QueuedTasksFinished; + /** Bitmap for new queued tasks (Guest -> R3). */ + volatile uint32_t u32TasksNew; + /** Bitmap of tasks which must be redone because of a non fatal error. */ + volatile uint32_t u32TasksRedo; + + /** Current command slot processed. + * Accessed by the guest by reading the CMD register. + * Holds the command slot of the command processed at the moment. */ + volatile uint32_t u32CurrentCommandSlot; + + /** Physical geometry of this image. */ + PDMMEDIAGEOMETRY PCHSGeometry; + + /** The status LED state for this drive. */ + PDMLED Led; + + /** The event semaphore the processing thread waits on. */ + SUPSEMEVENT hEvtProcess; + + /** The serial numnber to use for IDENTIFY DEVICE commands. */ + char szSerialNumber[AHCI_SERIAL_NUMBER_LENGTH+1]; /** < one extra byte for termination */ + /** The firmware revision to use for IDENTIFY DEVICE commands. */ + char szFirmwareRevision[AHCI_FIRMWARE_REVISION_LENGTH+1]; /** < one extra byte for termination */ + /** The model number to use for IDENTIFY DEVICE commands. */ + char szModelNumber[AHCI_MODEL_NUMBER_LENGTH+1]; /** < one extra byte for termination */ + /** The vendor identification string for SCSI INQUIRY commands. */ + char szInquiryVendorId[AHCI_ATAPI_INQUIRY_VENDOR_ID_LENGTH+1]; + /** The product identification string for SCSI INQUIRY commands. */ + char szInquiryProductId[AHCI_ATAPI_INQUIRY_PRODUCT_ID_LENGTH+1]; + /** The revision string for SCSI INQUIRY commands. */ + char szInquiryRevision[AHCI_ATAPI_INQUIRY_REVISION_LENGTH+1]; + /** Error counter */ + uint32_t cErrors; + + uint32_t u32Alignment5; +} AHCIPORT; +AssertCompileSizeAlignment(AHCIPORT, 8); +/** Pointer to the shared state of an AHCI port. */ +typedef AHCIPORT *PAHCIPORT; + + +/** + * The ring-3 state of an AHCI port. + * + * @implements PDMIBASE + * @implements PDMIMEDIAPORT + * @implements PDMIMEDIAEXPORT + */ +typedef struct AHCIPORTR3 +{ + /** Pointer to the device instance - only to get our bearings in an interface + * method, nothing else. */ + PPDMDEVINSR3 pDevIns; + + /** The LUN (same as port number). */ + uint32_t iLUN; + + /** Device specific settings (R3 only stuff). */ + /** 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 extended interface. */ + R3PTRTYPE(PPDMIMEDIAEX) pDrvMediaEx; + /** Port description. */ + char szDesc[8]; + /** The base interface. */ + PDMIBASE IBase; + /** The block port interface. */ + PDMIMEDIAPORT IPort; + /** The extended media port interface. */ + PDMIMEDIAEXPORT IMediaExPort; + + /** Async IO Thread. */ + R3PTRTYPE(PPDMTHREAD) pAsyncIOThread; + /** First task throwing an error. */ + R3PTRTYPE(volatile PAHCIREQ) pTaskErr; + +} AHCIPORTR3; +AssertCompileSizeAlignment(AHCIPORTR3, 8); +/** Pointer to the ring-3 state of an AHCI port. */ +typedef AHCIPORTR3 *PAHCIPORTR3; + + +/** + * Main AHCI device state. + * + * @implements PDMILEDPORTS + */ +typedef struct AHCI +{ + /** Global Host Control register of the HBA + * @todo r=bird: Make this a 'name' doxygen comment with { and add a + * corrsponding at-} where appropriate. I cannot tell where to put the + * latter. */ + + /** HBA Capabilities - Readonly */ + uint32_t regHbaCap; + /** HBA Control */ + uint32_t regHbaCtrl; + /** Interrupt Status */ + uint32_t regHbaIs; + /** Ports Implemented - Readonly */ + uint32_t regHbaPi; + /** AHCI Version - Readonly */ + uint32_t regHbaVs; + /** Command completion coalescing control */ + uint32_t regHbaCccCtl; + /** Command completion coalescing ports */ + uint32_t regHbaCccPorts; + + /** Index register for BIOS access. */ + uint32_t regIdx; + + /** Countdown timer for command completion coalescing. */ + TMTIMERHANDLE hHbaCccTimer; + + /** Which port number is used to mark an CCC interrupt */ + uint8_t uCccPortNr; + uint8_t abAlignment1[7]; + + /** Timeout value */ + uint64_t uCccTimeout; + /** Number of completions used to assert an interrupt */ + uint32_t uCccNr; + /** Current number of completed commands */ + uint32_t uCccCurrentNr; + + /** Register structure per port */ + AHCIPORT aPorts[AHCI_MAX_NR_PORTS_IMPL]; + + /** The critical section. */ + PDMCRITSECT lock; + + /** Bitmask of ports which asserted an interrupt. */ + volatile uint32_t u32PortsInterrupted; + /** Number of I/O threads currently active - used for async controller reset handling. */ + volatile uint32_t cThreadsActive; + + /** Flag whether the legacy port reset method should be used to make it work with saved states. */ + bool fLegacyPortResetMethod; + /** Enable tiger (10.4.x) SSTS hack or not. */ + bool fTigerHack; + /** Flag whether we have written the first 4bytes in an 8byte MMIO write successfully. */ + volatile bool f8ByteMMIO4BytesWrittenSuccessfully; + + /** Device is in a reset state. + * @todo r=bird: This isn't actually being modified by anyone... */ + bool fReset; + /** Supports 64bit addressing + * @todo r=bird: This isn't really being modified by anyone (always false). */ + bool f64BitAddr; + /** Flag whether the controller has BIOS access enabled. + * @todo r=bird: Not used, just queried from CFGM. */ + bool fBootable; + + bool afAlignment2[2]; + + /** Number of usable ports on this controller. */ + uint32_t cPortsImpl; + /** Number of usable command slots for each port. */ + uint32_t cCmdSlotsAvail; + + /** PCI region \#0: Legacy IDE fake, 8 ports. */ + IOMIOPORTHANDLE hIoPortsLegacyFake0; + /** PCI region \#1: Legacy IDE fake, 1 port. */ + IOMIOPORTHANDLE hIoPortsLegacyFake1; + /** PCI region \#2: Legacy IDE fake, 8 ports. */ + IOMIOPORTHANDLE hIoPortsLegacyFake2; + /** PCI region \#3: Legacy IDE fake, 1 port. */ + IOMIOPORTHANDLE hIoPortsLegacyFake3; + /** PCI region \#4: BMDMA I/O port range, 16 ports, used for the Index/Data + * pair register access. */ + IOMIOPORTHANDLE hIoPortIdxData; + /** PCI region \#5: MMIO registers. */ + IOMMMIOHANDLE hMmio; +} AHCI; +AssertCompileMemberAlignment(AHCI, aPorts, 8); +/** Pointer to the state of an AHCI device. */ +typedef AHCI *PAHCI; + + +/** + * Main AHCI device ring-3 state. + * + * @implements PDMILEDPORTS + */ +typedef struct AHCIR3 +{ + /** Pointer to the device instance - only for getting our bearings in + * interface methods. */ + PPDMDEVINSR3 pDevIns; + + /** Status LUN: The base interface. */ + PDMIBASE IBase; + /** Status LUN: Leds interface. */ + PDMILEDPORTS ILeds; + /** Status LUN: Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; + /** Status LUN: Media Notifys. */ + R3PTRTYPE(PPDMIMEDIANOTIFY) pMediaNotify; + + /** Register structure per port */ + AHCIPORTR3 aPorts[AHCI_MAX_NR_PORTS_IMPL]; + + /** Indicates that PDMDevHlpAsyncNotificationCompleted should be called when + * a port is entering the idle state. */ + bool volatile fSignalIdle; + bool afAlignment7[2+4]; +} AHCIR3; +/** Pointer to the ring-3 state of an AHCI device. */ +typedef AHCIR3 *PAHCIR3; + + +/** + * Main AHCI device ring-0 state. + */ +typedef struct AHCIR0 +{ + uint64_t uUnused; +} AHCIR0; +/** Pointer to the ring-0 state of an AHCI device. */ +typedef AHCIR0 *PAHCIR0; + + +/** + * Main AHCI device raw-mode state. + */ +typedef struct AHCIRC +{ + uint64_t uUnused; +} AHCIRC; +/** Pointer to the raw-mode state of an AHCI device. */ +typedef AHCIRC *PAHCIRC; + + +/** Main AHCI device current context state. */ +typedef CTX_SUFF(AHCI) AHCICC; +/** Pointer to the current context state of an AHCI device. */ +typedef CTX_SUFF(PAHCI) PAHCICC; + + +/** + * Scatter gather list entry. + */ +typedef struct +{ + /** Data Base Address. */ + uint32_t u32DBA; + /** Data Base Address - Upper 32-bits. */ + uint32_t u32DBAUp; + /** Reserved */ + uint32_t u32Reserved; + /** Description information. */ + uint32_t u32DescInf; +} SGLEntry; +AssertCompileSize(SGLEntry, 16); + +#ifdef IN_RING3 +/** + * Memory buffer callback. + * + * @param pDevIns The device instance. + * @param GCPhys The guest physical address of the memory buffer. + * @param pSgBuf The pointer to the host R3 S/G buffer. + * @param cbCopy How many bytes to copy between the two buffers. + * @param pcbSkip Initially contains the amount of bytes to skip + * starting from the guest physical address before + * accessing the S/G buffer and start copying data. + * On return this contains the remaining amount if + * cbCopy < *pcbSkip or 0 otherwise. + */ +typedef DECLCALLBACKTYPE(void, FNAHCIR3MEMCOPYCALLBACK,(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, + PRTSGBUF pSgBuf, size_t cbCopy, size_t *pcbSkip)); +/** Pointer to a memory copy buffer callback. */ +typedef FNAHCIR3MEMCOPYCALLBACK *PFNAHCIR3MEMCOPYCALLBACK; +#endif + +/** Defines for a scatter gather list entry. */ +#define SGLENTRY_DBA_READONLY ~(RT_BIT(0)) +#define SGLENTRY_DESCINF_I RT_BIT(31) +#define SGLENTRY_DESCINF_DBC 0x3fffff +#define SGLENTRY_DESCINF_READONLY 0x803fffff + +/* Defines for the global host control registers for the HBA. */ + +#define AHCI_HBA_GLOBAL_SIZE 0x100 + +/* Defines for the HBA Capabilities - Readonly */ +#define AHCI_HBA_CAP_S64A RT_BIT(31) +#define AHCI_HBA_CAP_SNCQ RT_BIT(30) +#define AHCI_HBA_CAP_SIS RT_BIT(28) +#define AHCI_HBA_CAP_SSS RT_BIT(27) +#define AHCI_HBA_CAP_SALP RT_BIT(26) +#define AHCI_HBA_CAP_SAL RT_BIT(25) +#define AHCI_HBA_CAP_SCLO RT_BIT(24) +#define AHCI_HBA_CAP_ISS (RT_BIT(23) | RT_BIT(22) | RT_BIT(21) | RT_BIT(20)) +# define AHCI_HBA_CAP_ISS_SHIFT(x) (((x) << 20) & AHCI_HBA_CAP_ISS) +# define AHCI_HBA_CAP_ISS_GEN1 RT_BIT(0) +# define AHCI_HBA_CAP_ISS_GEN2 RT_BIT(1) +#define AHCI_HBA_CAP_SNZO RT_BIT(19) +#define AHCI_HBA_CAP_SAM RT_BIT(18) +#define AHCI_HBA_CAP_SPM RT_BIT(17) +#define AHCI_HBA_CAP_PMD RT_BIT(15) +#define AHCI_HBA_CAP_SSC RT_BIT(14) +#define AHCI_HBA_CAP_PSC RT_BIT(13) +#define AHCI_HBA_CAP_NCS (RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8)) +#define AHCI_HBA_CAP_NCS_SET(x) (((x-1) << 8) & AHCI_HBA_CAP_NCS) /* 0's based */ +#define AHCI_HBA_CAP_CCCS RT_BIT(7) +#define AHCI_HBA_CAP_NP (RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0)) +#define AHCI_HBA_CAP_NP_SET(x) ((x-1) & AHCI_HBA_CAP_NP) /* 0's based */ + +/* Defines for the HBA Control register - Read/Write */ +#define AHCI_HBA_CTRL_AE RT_BIT(31) +#define AHCI_HBA_CTRL_IE RT_BIT(1) +#define AHCI_HBA_CTRL_HR RT_BIT(0) +#define AHCI_HBA_CTRL_RW_MASK (RT_BIT(0) | RT_BIT(1)) /* Mask for the used bits */ + +/* Defines for the HBA Version register - Readonly (We support AHCI 1.0) */ +#define AHCI_HBA_VS_MJR (1 << 16) +#define AHCI_HBA_VS_MNR 0x100 + +/* Defines for the command completion coalescing control register */ +#define AHCI_HBA_CCC_CTL_TV 0xffff0000 +#define AHCI_HBA_CCC_CTL_TV_SET(x) (x << 16) +#define AHCI_HBA_CCC_CTL_TV_GET(x) ((x & AHCI_HBA_CCC_CTL_TV) >> 16) + +#define AHCI_HBA_CCC_CTL_CC 0xff00 +#define AHCI_HBA_CCC_CTL_CC_SET(x) (x << 8) +#define AHCI_HBA_CCC_CTL_CC_GET(x) ((x & AHCI_HBA_CCC_CTL_CC) >> 8) + +#define AHCI_HBA_CCC_CTL_INT 0xf8 +#define AHCI_HBA_CCC_CTL_INT_SET(x) (x << 3) +#define AHCI_HBA_CCC_CTL_INT_GET(x) ((x & AHCI_HBA_CCC_CTL_INT) >> 3) + +#define AHCI_HBA_CCC_CTL_EN RT_BIT(0) + +/* Defines for the port registers. */ + +#define AHCI_PORT_REGISTER_SIZE 0x80 + +#define AHCI_PORT_CLB_RESERVED 0xfffffc00 /* For masking out the reserved bits. */ + +#define AHCI_PORT_FB_RESERVED 0xffffff00 /* For masking out the reserved bits. */ + +#define AHCI_PORT_IS_CPDS RT_BIT(31) +#define AHCI_PORT_IS_TFES RT_BIT(30) +#define AHCI_PORT_IS_HBFS RT_BIT(29) +#define AHCI_PORT_IS_HBDS RT_BIT(28) +#define AHCI_PORT_IS_IFS RT_BIT(27) +#define AHCI_PORT_IS_INFS RT_BIT(26) +#define AHCI_PORT_IS_OFS RT_BIT(24) +#define AHCI_PORT_IS_IPMS RT_BIT(23) +#define AHCI_PORT_IS_PRCS RT_BIT(22) +#define AHCI_PORT_IS_DIS RT_BIT(7) +#define AHCI_PORT_IS_PCS RT_BIT(6) +#define AHCI_PORT_IS_DPS RT_BIT(5) +#define AHCI_PORT_IS_UFS RT_BIT(4) +#define AHCI_PORT_IS_SDBS RT_BIT(3) +#define AHCI_PORT_IS_DSS RT_BIT(2) +#define AHCI_PORT_IS_PSS RT_BIT(1) +#define AHCI_PORT_IS_DHRS RT_BIT(0) +#define AHCI_PORT_IS_READONLY 0xfd8000af /* Readonly mask including reserved bits. */ + +#define AHCI_PORT_IE_CPDE RT_BIT(31) +#define AHCI_PORT_IE_TFEE RT_BIT(30) +#define AHCI_PORT_IE_HBFE RT_BIT(29) +#define AHCI_PORT_IE_HBDE RT_BIT(28) +#define AHCI_PORT_IE_IFE RT_BIT(27) +#define AHCI_PORT_IE_INFE RT_BIT(26) +#define AHCI_PORT_IE_OFE RT_BIT(24) +#define AHCI_PORT_IE_IPME RT_BIT(23) +#define AHCI_PORT_IE_PRCE RT_BIT(22) +#define AHCI_PORT_IE_DIE RT_BIT(7) /* Not supported for now, readonly. */ +#define AHCI_PORT_IE_PCE RT_BIT(6) +#define AHCI_PORT_IE_DPE RT_BIT(5) +#define AHCI_PORT_IE_UFE RT_BIT(4) +#define AHCI_PORT_IE_SDBE RT_BIT(3) +#define AHCI_PORT_IE_DSE RT_BIT(2) +#define AHCI_PORT_IE_PSE RT_BIT(1) +#define AHCI_PORT_IE_DHRE RT_BIT(0) +#define AHCI_PORT_IE_READONLY (0xfdc000ff) /* Readonly mask including reserved bits. */ + +#define AHCI_PORT_CMD_ICC (RT_BIT(28) | RT_BIT(29) | RT_BIT(30) | RT_BIT(31)) +#define AHCI_PORT_CMD_ICC_SHIFT(x) ((x) << 28) +# define AHCI_PORT_CMD_ICC_IDLE 0x0 +# define AHCI_PORT_CMD_ICC_ACTIVE 0x1 +# define AHCI_PORT_CMD_ICC_PARTIAL 0x2 +# define AHCI_PORT_CMD_ICC_SLUMBER 0x6 +#define AHCI_PORT_CMD_ASP RT_BIT(27) /* Not supported - Readonly */ +#define AHCI_PORT_CMD_ALPE RT_BIT(26) /* Not supported - Readonly */ +#define AHCI_PORT_CMD_DLAE RT_BIT(25) +#define AHCI_PORT_CMD_ATAPI RT_BIT(24) +#define AHCI_PORT_CMD_CPD RT_BIT(20) +#define AHCI_PORT_CMD_ISP RT_BIT(19) /* Readonly */ +#define AHCI_PORT_CMD_HPCP RT_BIT(18) +#define AHCI_PORT_CMD_PMA RT_BIT(17) /* Not supported - Readonly */ +#define AHCI_PORT_CMD_CPS RT_BIT(16) +#define AHCI_PORT_CMD_CR RT_BIT(15) /* Readonly */ +#define AHCI_PORT_CMD_FR RT_BIT(14) /* Readonly */ +#define AHCI_PORT_CMD_ISS RT_BIT(13) /* Readonly */ +#define AHCI_PORT_CMD_CCS (RT_BIT(8) | RT_BIT(9) | RT_BIT(10) | RT_BIT(11) | RT_BIT(12)) +#define AHCI_PORT_CMD_CCS_SHIFT(x) (x << 8) /* Readonly */ +#define AHCI_PORT_CMD_FRE RT_BIT(4) +#define AHCI_PORT_CMD_CLO RT_BIT(3) +#define AHCI_PORT_CMD_POD RT_BIT(2) +#define AHCI_PORT_CMD_SUD RT_BIT(1) +#define AHCI_PORT_CMD_ST RT_BIT(0) +#define AHCI_PORT_CMD_READONLY (0xff02001f & ~(AHCI_PORT_CMD_ASP | AHCI_PORT_CMD_ALPE | AHCI_PORT_CMD_PMA)) + +#define AHCI_PORT_SCTL_IPM (RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8)) +#define AHCI_PORT_SCTL_IPM_GET(x) ((x & AHCI_PORT_SCTL_IPM) >> 8) +#define AHCI_PORT_SCTL_SPD (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4)) +#define AHCI_PORT_SCTL_SPD_GET(x) ((x & AHCI_PORT_SCTL_SPD) >> 4) +#define AHCI_PORT_SCTL_DET (RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0)) +#define AHCI_PORT_SCTL_DET_GET(x) (x & AHCI_PORT_SCTL_DET) +#define AHCI_PORT_SCTL_DET_NINIT 0 +#define AHCI_PORT_SCTL_DET_INIT 1 +#define AHCI_PORT_SCTL_DET_OFFLINE 4 +#define AHCI_PORT_SCTL_READONLY 0xfff + +#define AHCI_PORT_SSTS_IPM (RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8)) +#define AHCI_PORT_SSTS_IPM_GET(x) ((x & AHCI_PORT_SCTL_IPM) >> 8) +#define AHCI_PORT_SSTS_SPD (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4)) +#define AHCI_PORT_SSTS_SPD_GET(x) ((x & AHCI_PORT_SCTL_SPD) >> 4) +#define AHCI_PORT_SSTS_DET (RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0)) +#define AHCI_PORT_SSTS_DET_GET(x) (x & AHCI_PORT_SCTL_DET) + +#define AHCI_PORT_TFD_BSY RT_BIT(7) +#define AHCI_PORT_TFD_DRQ RT_BIT(3) +#define AHCI_PORT_TFD_ERR RT_BIT(0) + +#define AHCI_PORT_SERR_X RT_BIT(26) +#define AHCI_PORT_SERR_W RT_BIT(18) +#define AHCI_PORT_SERR_N RT_BIT(16) + +/* Signatures for attached storage devices. */ +#define AHCI_PORT_SIG_DISK 0x00000101 +#define AHCI_PORT_SIG_ATAPI 0xeb140101 + +/* + * The AHCI spec defines an area of memory where the HBA posts received FIS's from the device. + * regFB points to the base of this area. + * Every FIS type has an offset where it is posted in this area. + */ +#define AHCI_RECFIS_DSFIS_OFFSET 0x00 /* DMA Setup FIS */ +#define AHCI_RECFIS_PSFIS_OFFSET 0x20 /* PIO Setup FIS */ +#define AHCI_RECFIS_RFIS_OFFSET 0x40 /* D2H Register FIS */ +#define AHCI_RECFIS_SDBFIS_OFFSET 0x58 /* Set Device Bits FIS */ +#define AHCI_RECFIS_UFIS_OFFSET 0x60 /* Unknown FIS type */ + +/** Mask to get the LBA value from a LBA range. */ +#define AHCI_RANGE_LBA_MASK UINT64_C(0xffffffffffff) +/** Mas to get the length value from a LBA range. */ +#define AHCI_RANGE_LENGTH_MASK UINT64_C(0xffff000000000000) +/** Returns the length of the range in sectors. */ +#define AHCI_RANGE_LENGTH_GET(val) (((val) & AHCI_RANGE_LENGTH_MASK) >> 48) + +/** + * AHCI register operator. + */ +typedef struct ahci_opreg +{ + const char *pszName; + VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value); +} AHCIOPREG; + +/** + * AHCI port register operator. + */ +typedef struct pAhciPort_opreg +{ + const char *pszName; + VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value); +} AHCIPORTOPREG; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +RT_C_DECLS_BEGIN +#ifdef IN_RING3 +static void ahciR3HBAReset(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIR3 pThisCC); +static int ahciPostFisIntoMemory(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, unsigned uFisType, uint8_t *pCmdFis); +static void ahciPostFirstD2HFisIntoMemory(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort); +static size_t ahciR3CopyBufferToPrdtl(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, const void *pvSrc, size_t cbSrc, size_t cbSkip); +static bool ahciR3CancelActiveTasks(PAHCIPORTR3 pAhciPortR3); +#endif +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define AHCI_RTGCPHYS_FROM_U32(Hi, Lo) ( (RTGCPHYS)RT_MAKE_U64(Lo, Hi) ) + +#ifdef IN_RING3 + +# ifdef LOG_USE_C99 +# define ahciLog(a) \ + Log(("R3 P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a)) +# else +# define ahciLog(a) \ + do { Log(("R3 P%u: ", pAhciPort->iLUN)); Log(a); } while(0) +# endif + +#elif defined(IN_RING0) + +# ifdef LOG_USE_C99 +# define ahciLog(a) \ + Log(("R0 P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a)) +# else +# define ahciLog(a) \ + do { Log(("R0 P%u: ", pAhciPort->iLUN)); Log(a); } while(0) +# endif + +#elif defined(IN_RC) + +# ifdef LOG_USE_C99 +# define ahciLog(a) \ + Log(("GC P%u: %M", pAhciPort->iLUN, _LogRelRemoveParentheseis a)) +# else +# define ahciLog(a) \ + do { Log(("GC P%u: ", pAhciPort->iLUN)); Log(a); } while(0) +# endif + +#endif + + + +/** + * Update PCI IRQ levels + */ +static void ahciHbaClearInterrupt(PPDMDEVINS pDevIns) +{ + Log(("%s: Clearing interrupt\n", __FUNCTION__)); + PDMDevHlpPCISetIrq(pDevIns, 0, 0); +} + +/** + * Updates the IRQ level and sets port bit in the global interrupt status register of the HBA. + */ +static int ahciHbaSetInterrupt(PPDMDEVINS pDevIns, PAHCI pThis, uint8_t iPort, int rcBusy) +{ + Log(("P%u: %s: Setting interrupt\n", iPort, __FUNCTION__)); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->lock, rcBusy); + if (rc != VINF_SUCCESS) + return rc; + + if (pThis->regHbaCtrl & AHCI_HBA_CTRL_IE) + { + if ((pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN) && (pThis->regHbaCccPorts & (1 << iPort))) + { + pThis->uCccCurrentNr++; + if (pThis->uCccCurrentNr >= pThis->uCccNr) + { + /* Reset command completion coalescing state. */ + PDMDevHlpTimerSetMillies(pDevIns, pThis->hHbaCccTimer, pThis->uCccTimeout); + pThis->uCccCurrentNr = 0; + + pThis->u32PortsInterrupted |= (1 << pThis->uCccPortNr); + if (!(pThis->u32PortsInterrupted & ~(1 << pThis->uCccPortNr))) + { + Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__)); + PDMDevHlpPCISetIrq(pDevIns, 0, 1); + } + } + } + else + { + /* If only the bit of the actual port is set assert an interrupt + * because the interrupt status register was already read by the guest + * and we need to send a new notification. + * Otherwise an interrupt is still pending. + */ + ASMAtomicOrU32((volatile uint32_t *)&pThis->u32PortsInterrupted, (1 << iPort)); + if (!(pThis->u32PortsInterrupted & ~(1 << iPort))) + { + Log(("P%u: %s: Fire interrupt\n", iPort, __FUNCTION__)); + PDMDevHlpPCISetIrq(pDevIns, 0, 1); + } + } + } + + PDMDevHlpCritSectLeave(pDevIns, &pThis->lock); + return VINF_SUCCESS; +} + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNTMTIMERDEV, Assert irq when an CCC timeout occurs.} + */ +static DECLCALLBACK(void) ahciCccTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + RT_NOREF(pDevIns, hTimer); + PAHCI pThis = (PAHCI)pvUser; + + int rc = ahciHbaSetInterrupt(pDevIns, pThis, pThis->uCccPortNr, VERR_IGNORED); + AssertRC(rc); +} + +/** + * Finishes the port reset of the given port. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The port to finish the reset on, shared bits. + * @param pAhciPortR3 The port to finish the reset on, ring-3 bits. + */ +static void ahciPortResetFinish(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3) +{ + ahciLog(("%s: Initiated.\n", __FUNCTION__)); + + /* Cancel all tasks first. */ + bool fAllTasksCanceled = ahciR3CancelActiveTasks(pAhciPortR3); + Assert(fAllTasksCanceled); NOREF(fAllTasksCanceled); + + /* Signature for SATA device. */ + if (pAhciPort->fATAPI) + pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI; + else + pAhciPort->regSIG = AHCI_PORT_SIG_DISK; + + /* We received a COMINIT from the device. Tell the guest. */ + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_PCS); + pAhciPort->regSERR |= AHCI_PORT_SERR_X; + pAhciPort->regTFD |= ATA_STAT_BUSY; + + if ((pAhciPort->regCMD & AHCI_PORT_CMD_FRE) && (!pAhciPort->fFirstD2HFisSent)) + { + ahciPostFirstD2HFisIntoMemory(pDevIns, pAhciPort); + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS); + + if (pAhciPort->regIE & AHCI_PORT_IE_DHRE) + { + int rc = ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + AssertRC(rc); + } + } + + pAhciPort->regSSTS = (0x01 << 8) /* Interface is active. */ + | (0x03 << 0); /* Device detected and communication established. */ + + /* + * Use the maximum allowed speed. + * (Not that it changes anything really) + */ + switch (AHCI_PORT_SCTL_SPD_GET(pAhciPort->regSCTL)) + { + case 0x01: + pAhciPort->regSSTS |= (0x01 << 4); /* Generation 1 (1.5GBps) speed. */ + break; + case 0x02: + case 0x00: + default: + pAhciPort->regSSTS |= (0x02 << 4); /* Generation 2 (3.0GBps) speed. */ + break; + } + + ASMAtomicXchgBool(&pAhciPort->fPortReset, false); +} + +#endif /* IN_RING3 */ + +/** + * Kicks the I/O thread from RC or R0. + * + * @param pDevIns The device instance. + * @param pAhciPort The port to kick, shared bits. + */ +static void ahciIoThreadKick(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort) +{ + LogFlowFunc(("Signal event semaphore\n")); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pAhciPort->hEvtProcess); + AssertRC(rc); +} + +static VBOXSTRICTRC PortCmdIssue_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + RT_NOREF(pThis, iReg); + + /* Update the CI register first. */ + uint32_t uCIValue = ASMAtomicXchgU32(&pAhciPort->u32TasksFinished, 0); + pAhciPort->regCI &= ~uCIValue; + + if ( (pAhciPort->regCMD & AHCI_PORT_CMD_CR) + && u32Value > 0) + { + /* + * Clear all tasks which are already marked as busy. The guest + * shouldn't write already busy tasks actually. + */ + u32Value &= ~pAhciPort->regCI; + + ASMAtomicOrU32(&pAhciPort->u32TasksNew, u32Value); + + /* Send a notification to R3 if u32TasksNew was 0 before our write. */ + if (ASMAtomicReadBool(&pAhciPort->fWrkThreadSleeping)) + ahciIoThreadKick(pDevIns, pAhciPort); + else + ahciLog(("%s: Worker thread busy, no need to kick.\n", __FUNCTION__)); + } + else + ahciLog(("%s: Nothing to do (CMD=%08x).\n", __FUNCTION__, pAhciPort->regCMD)); + + pAhciPort->regCI |= u32Value; + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortCmdIssue_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + + uint32_t uCIValue = ASMAtomicXchgU32(&pAhciPort->u32TasksFinished, 0); + ahciLog(("%s: read regCI=%#010x uCIValue=%#010x\n", __FUNCTION__, pAhciPort->regCI, uCIValue)); + + pAhciPort->regCI &= ~uCIValue; + *pu32Value = pAhciPort->regCI; + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortSActive_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + RT_NOREF(pDevIns, pThis, iReg); + + pAhciPort->regSACT |= u32Value; + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortSActive_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + + uint32_t u32TasksFinished = ASMAtomicXchgU32(&pAhciPort->u32QueuedTasksFinished, 0); + pAhciPort->regSACT &= ~u32TasksFinished; + + ahciLog(("%s: read regSACT=%#010x regCI=%#010x u32TasksFinished=%#010x\n", + __FUNCTION__, pAhciPort->regSACT, pAhciPort->regCI, u32TasksFinished)); + + *pu32Value = pAhciPort->regSACT; + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortSError_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + + if ( (u32Value & AHCI_PORT_SERR_X) + && (pAhciPort->regSERR & AHCI_PORT_SERR_X)) + { + ASMAtomicAndU32(&pAhciPort->regIS, ~AHCI_PORT_IS_PCS); + pAhciPort->regTFD |= ATA_STAT_ERR; + pAhciPort->regTFD &= ~(ATA_STAT_DRQ | ATA_STAT_BUSY); + } + + if ( (u32Value & AHCI_PORT_SERR_N) + && (pAhciPort->regSERR & AHCI_PORT_SERR_N)) + ASMAtomicAndU32(&pAhciPort->regIS, ~AHCI_PORT_IS_PRCS); + + pAhciPort->regSERR &= ~u32Value; + + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortSError_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regSERR=%#010x\n", __FUNCTION__, pAhciPort->regSERR)); + *pu32Value = pAhciPort->regSERR; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortSControl_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__, + AHCI_PORT_SCTL_IPM_GET(u32Value), AHCI_PORT_SCTL_SPD_GET(u32Value), AHCI_PORT_SCTL_DET_GET(u32Value))); + +#ifndef IN_RING3 + RT_NOREF(pDevIns, pAhciPort, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#else + if ((u32Value & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_INIT) + { + if (!ASMAtomicXchgBool(&pAhciPort->fPortReset, true)) + LogRel(("AHCI#%u: Port %d reset\n", pDevIns->iInstance, + pAhciPort->iLUN)); + + pAhciPort->regSSTS = 0; + pAhciPort->regSIG = UINT32_MAX; + pAhciPort->regTFD = 0x7f; + pAhciPort->fFirstD2HFisSent = false; + pAhciPort->regSCTL = u32Value; + } + else if ( (u32Value & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_NINIT + && (pAhciPort->regSCTL & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_INIT + && pAhciPort->fPresent) + { + /* Do the port reset here, so the guest sees the new status immediately. */ + if (pThis->fLegacyPortResetMethod) + { + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + PAHCIPORTR3 pAhciPortR3 = &RT_SAFE_SUBSCRIPT(pThisCC->aPorts, pAhciPort->iLUN); + ahciPortResetFinish(pDevIns, pThis, pAhciPort, pAhciPortR3); + pAhciPort->regSCTL = u32Value; /* Update after finishing the reset, so the I/O thread doesn't get a chance to do the reset. */ + } + else + { + if (!pThis->fTigerHack) + pAhciPort->regSSTS = 0x1; /* Indicate device presence detected but communication not established. */ + else + pAhciPort->regSSTS = 0x0; /* Indicate no device detected after COMRESET. [tiger hack] */ + pAhciPort->regSCTL = u32Value; /* Update before kicking the I/O thread. */ + + /* Kick the thread to finish the reset. */ + ahciIoThreadKick(pDevIns, pAhciPort); + } + } + else /* Just update the value if there is no device attached. */ + pAhciPort->regSCTL = u32Value; + + return VINF_SUCCESS; +#endif +} + +static VBOXSTRICTRC PortSControl_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regSCTL=%#010x\n", __FUNCTION__, pAhciPort->regSCTL)); + ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__, + AHCI_PORT_SCTL_IPM_GET(pAhciPort->regSCTL), AHCI_PORT_SCTL_SPD_GET(pAhciPort->regSCTL), + AHCI_PORT_SCTL_DET_GET(pAhciPort->regSCTL))); + + *pu32Value = pAhciPort->regSCTL; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortSStatus_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regSSTS=%#010x\n", __FUNCTION__, pAhciPort->regSSTS)); + ahciLog(("%s: IPM=%d SPD=%d DET=%d\n", __FUNCTION__, + AHCI_PORT_SSTS_IPM_GET(pAhciPort->regSSTS), AHCI_PORT_SSTS_SPD_GET(pAhciPort->regSSTS), + AHCI_PORT_SSTS_DET_GET(pAhciPort->regSSTS))); + + *pu32Value = pAhciPort->regSSTS; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortSignature_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regSIG=%#010x\n", __FUNCTION__, pAhciPort->regSIG)); + *pu32Value = pAhciPort->regSIG; + return VINF_SUCCESS; +} + +static VBOXSTRICTRC PortTaskFileData_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regTFD=%#010x\n", __FUNCTION__, pAhciPort->regTFD)); + ahciLog(("%s: ERR=%x BSY=%d DRQ=%d ERR=%d\n", __FUNCTION__, + (pAhciPort->regTFD >> 8), (pAhciPort->regTFD & AHCI_PORT_TFD_BSY) >> 7, + (pAhciPort->regTFD & AHCI_PORT_TFD_DRQ) >> 3, (pAhciPort->regTFD & AHCI_PORT_TFD_ERR))); + *pu32Value = pAhciPort->regTFD; + return VINF_SUCCESS; +} + +/** + * Read from the port command register. + */ +static VBOXSTRICTRC PortCmd_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regCMD=%#010x\n", __FUNCTION__, pAhciPort->regCMD | AHCI_PORT_CMD_CCS_SHIFT(pAhciPort->u32CurrentCommandSlot))); + ahciLog(("%s: ICC=%d ASP=%d ALPE=%d DLAE=%d ATAPI=%d CPD=%d ISP=%d HPCP=%d PMA=%d CPS=%d CR=%d FR=%d ISS=%d CCS=%d FRE=%d CLO=%d POD=%d SUD=%d ST=%d\n", + __FUNCTION__, (pAhciPort->regCMD & AHCI_PORT_CMD_ICC) >> 28, (pAhciPort->regCMD & AHCI_PORT_CMD_ASP) >> 27, + (pAhciPort->regCMD & AHCI_PORT_CMD_ALPE) >> 26, (pAhciPort->regCMD & AHCI_PORT_CMD_DLAE) >> 25, + (pAhciPort->regCMD & AHCI_PORT_CMD_ATAPI) >> 24, (pAhciPort->regCMD & AHCI_PORT_CMD_CPD) >> 20, + (pAhciPort->regCMD & AHCI_PORT_CMD_ISP) >> 19, (pAhciPort->regCMD & AHCI_PORT_CMD_HPCP) >> 18, + (pAhciPort->regCMD & AHCI_PORT_CMD_PMA) >> 17, (pAhciPort->regCMD & AHCI_PORT_CMD_CPS) >> 16, + (pAhciPort->regCMD & AHCI_PORT_CMD_CR) >> 15, (pAhciPort->regCMD & AHCI_PORT_CMD_FR) >> 14, + (pAhciPort->regCMD & AHCI_PORT_CMD_ISS) >> 13, pAhciPort->u32CurrentCommandSlot, + (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) >> 4, (pAhciPort->regCMD & AHCI_PORT_CMD_CLO) >> 3, + (pAhciPort->regCMD & AHCI_PORT_CMD_POD) >> 2, (pAhciPort->regCMD & AHCI_PORT_CMD_SUD) >> 1, + (pAhciPort->regCMD & AHCI_PORT_CMD_ST))); + *pu32Value = pAhciPort->regCMD | AHCI_PORT_CMD_CCS_SHIFT(pAhciPort->u32CurrentCommandSlot); + return VINF_SUCCESS; +} + +/** + * Write to the port command register. + * This is the register where all the data transfer is started + */ +static VBOXSTRICTRC PortCmd_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + ahciLog(("%s: ICC=%d ASP=%d ALPE=%d DLAE=%d ATAPI=%d CPD=%d ISP=%d HPCP=%d PMA=%d CPS=%d CR=%d FR=%d ISS=%d CCS=%d FRE=%d CLO=%d POD=%d SUD=%d ST=%d\n", + __FUNCTION__, (u32Value & AHCI_PORT_CMD_ICC) >> 28, (u32Value & AHCI_PORT_CMD_ASP) >> 27, + (u32Value & AHCI_PORT_CMD_ALPE) >> 26, (u32Value & AHCI_PORT_CMD_DLAE) >> 25, + (u32Value & AHCI_PORT_CMD_ATAPI) >> 24, (u32Value & AHCI_PORT_CMD_CPD) >> 20, + (u32Value & AHCI_PORT_CMD_ISP) >> 19, (u32Value & AHCI_PORT_CMD_HPCP) >> 18, + (u32Value & AHCI_PORT_CMD_PMA) >> 17, (u32Value & AHCI_PORT_CMD_CPS) >> 16, + (u32Value & AHCI_PORT_CMD_CR) >> 15, (u32Value & AHCI_PORT_CMD_FR) >> 14, + (u32Value & AHCI_PORT_CMD_ISS) >> 13, (u32Value & AHCI_PORT_CMD_CCS) >> 8, + (u32Value & AHCI_PORT_CMD_FRE) >> 4, (u32Value & AHCI_PORT_CMD_CLO) >> 3, + (u32Value & AHCI_PORT_CMD_POD) >> 2, (u32Value & AHCI_PORT_CMD_SUD) >> 1, + (u32Value & AHCI_PORT_CMD_ST))); + + /* The PxCMD.CCS bits are R/O and maintained separately. */ + u32Value &= ~AHCI_PORT_CMD_CCS; + + if (pAhciPort->fPoweredOn && pAhciPort->fSpunUp) + { + if (u32Value & AHCI_PORT_CMD_CLO) + { + ahciLog(("%s: Command list override requested\n", __FUNCTION__)); + u32Value &= ~(AHCI_PORT_TFD_BSY | AHCI_PORT_TFD_DRQ); + /* Clear the CLO bit. */ + u32Value &= ~(AHCI_PORT_CMD_CLO); + } + + if (u32Value & AHCI_PORT_CMD_ST) + { + /* + * Set engine state to running if there is a device attached and + * IS.PCS is clear. + */ + if ( pAhciPort->fPresent + && !(pAhciPort->regIS & AHCI_PORT_IS_PCS)) + { + ahciLog(("%s: Engine starts\n", __FUNCTION__)); + u32Value |= AHCI_PORT_CMD_CR; + + /* If there is something in CI, kick the I/O thread. */ + if ( pAhciPort->regCI > 0 + && ASMAtomicReadBool(&pAhciPort->fWrkThreadSleeping)) + { + ASMAtomicOrU32(&pAhciPort->u32TasksNew, pAhciPort->regCI); + LogFlowFunc(("Signal event semaphore\n")); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pAhciPort->hEvtProcess); + AssertRC(rc); + } + } + else + { + if (!pAhciPort->fPresent) + ahciLog(("%s: No pDrvBase, clearing PxCMD.CR!\n", __FUNCTION__)); + else + ahciLog(("%s: PxIS.PCS set (PxIS=%#010x), clearing PxCMD.CR!\n", __FUNCTION__, pAhciPort->regIS)); + + u32Value &= ~AHCI_PORT_CMD_CR; + } + } + else + { + ahciLog(("%s: Engine stops\n", __FUNCTION__)); + /* Clear command issue register. */ + pAhciPort->regCI = 0; + pAhciPort->regSACT = 0; + /* Clear current command slot. */ + pAhciPort->u32CurrentCommandSlot = 0; + u32Value &= ~AHCI_PORT_CMD_CR; + } + } + else if (pAhciPort->fPresent) + { + if ((u32Value & AHCI_PORT_CMD_POD) && (pAhciPort->regCMD & AHCI_PORT_CMD_CPS) && !pAhciPort->fPoweredOn) + { + ahciLog(("%s: Power on the device\n", __FUNCTION__)); + pAhciPort->fPoweredOn = true; + + /* + * Set states in the Port Signature and SStatus registers. + */ + if (pAhciPort->fATAPI) + pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI; + else + pAhciPort->regSIG = AHCI_PORT_SIG_DISK; + pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */ + (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */ + (0x03 << 0); /* Device detected and communication established. */ + + if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) + { +#ifndef IN_RING3 + return VINF_IOM_R3_MMIO_WRITE; +#else + ahciPostFirstD2HFisIntoMemory(pDevIns, pAhciPort); + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS); + + if (pAhciPort->regIE & AHCI_PORT_IE_DHRE) + { + int rc = ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + AssertRC(rc); + } +#endif + } + } + + if ((u32Value & AHCI_PORT_CMD_SUD) && pAhciPort->fPoweredOn && !pAhciPort->fSpunUp) + { + ahciLog(("%s: Spin up the device\n", __FUNCTION__)); + pAhciPort->fSpunUp = true; + } + } + else + ahciLog(("%s: No pDrvBase, no fPoweredOn + fSpunUp, doing nothing!\n", __FUNCTION__)); + + if (u32Value & AHCI_PORT_CMD_FRE) + { + ahciLog(("%s: FIS receive enabled\n", __FUNCTION__)); + + u32Value |= AHCI_PORT_CMD_FR; + + /* Send the first D2H FIS only if it wasn't already sent. */ + if ( !pAhciPort->fFirstD2HFisSent + && pAhciPort->fPresent) + { +#ifndef IN_RING3 + return VINF_IOM_R3_MMIO_WRITE; +#else + ahciPostFirstD2HFisIntoMemory(pDevIns, pAhciPort); + pAhciPort->fFirstD2HFisSent = true; +#endif + } + } + else if (!(u32Value & AHCI_PORT_CMD_FRE)) + { + ahciLog(("%s: FIS receive disabled\n", __FUNCTION__)); + u32Value &= ~AHCI_PORT_CMD_FR; + } + + pAhciPort->regCMD = u32Value; + + return VINF_SUCCESS; +} + +/** + * Read from the port interrupt enable register. + */ +static VBOXSTRICTRC PortIntrEnable_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regIE=%#010x\n", __FUNCTION__, pAhciPort->regIE)); + ahciLog(("%s: CPDE=%d TFEE=%d HBFE=%d HBDE=%d IFE=%d INFE=%d OFE=%d IPME=%d PRCE=%d DIE=%d PCE=%d DPE=%d UFE=%d SDBE=%d DSE=%d PSE=%d DHRE=%d\n", + __FUNCTION__, (pAhciPort->regIE & AHCI_PORT_IE_CPDE) >> 31, (pAhciPort->regIE & AHCI_PORT_IE_TFEE) >> 30, + (pAhciPort->regIE & AHCI_PORT_IE_HBFE) >> 29, (pAhciPort->regIE & AHCI_PORT_IE_HBDE) >> 28, + (pAhciPort->regIE & AHCI_PORT_IE_IFE) >> 27, (pAhciPort->regIE & AHCI_PORT_IE_INFE) >> 26, + (pAhciPort->regIE & AHCI_PORT_IE_OFE) >> 24, (pAhciPort->regIE & AHCI_PORT_IE_IPME) >> 23, + (pAhciPort->regIE & AHCI_PORT_IE_PRCE) >> 22, (pAhciPort->regIE & AHCI_PORT_IE_DIE) >> 7, + (pAhciPort->regIE & AHCI_PORT_IE_PCE) >> 6, (pAhciPort->regIE & AHCI_PORT_IE_DPE) >> 5, + (pAhciPort->regIE & AHCI_PORT_IE_UFE) >> 4, (pAhciPort->regIE & AHCI_PORT_IE_SDBE) >> 3, + (pAhciPort->regIE & AHCI_PORT_IE_DSE) >> 2, (pAhciPort->regIE & AHCI_PORT_IE_PSE) >> 1, + (pAhciPort->regIE & AHCI_PORT_IE_DHRE))); + *pu32Value = pAhciPort->regIE; + return VINF_SUCCESS; +} + +/** + * Write to the port interrupt enable register. + */ +static VBOXSTRICTRC PortIntrEnable_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + ahciLog(("%s: CPDE=%d TFEE=%d HBFE=%d HBDE=%d IFE=%d INFE=%d OFE=%d IPME=%d PRCE=%d DIE=%d PCE=%d DPE=%d UFE=%d SDBE=%d DSE=%d PSE=%d DHRE=%d\n", + __FUNCTION__, (u32Value & AHCI_PORT_IE_CPDE) >> 31, (u32Value & AHCI_PORT_IE_TFEE) >> 30, + (u32Value & AHCI_PORT_IE_HBFE) >> 29, (u32Value & AHCI_PORT_IE_HBDE) >> 28, + (u32Value & AHCI_PORT_IE_IFE) >> 27, (u32Value & AHCI_PORT_IE_INFE) >> 26, + (u32Value & AHCI_PORT_IE_OFE) >> 24, (u32Value & AHCI_PORT_IE_IPME) >> 23, + (u32Value & AHCI_PORT_IE_PRCE) >> 22, (u32Value & AHCI_PORT_IE_DIE) >> 7, + (u32Value & AHCI_PORT_IE_PCE) >> 6, (u32Value & AHCI_PORT_IE_DPE) >> 5, + (u32Value & AHCI_PORT_IE_UFE) >> 4, (u32Value & AHCI_PORT_IE_SDBE) >> 3, + (u32Value & AHCI_PORT_IE_DSE) >> 2, (u32Value & AHCI_PORT_IE_PSE) >> 1, + (u32Value & AHCI_PORT_IE_DHRE))); + + u32Value &= AHCI_PORT_IE_READONLY; + + /* Check if some a interrupt status bit changed*/ + uint32_t u32IntrStatus = ASMAtomicReadU32(&pAhciPort->regIS); + + int rc = VINF_SUCCESS; + if (u32Value & u32IntrStatus) + rc = ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VINF_IOM_R3_MMIO_WRITE); + + if (rc == VINF_SUCCESS) + pAhciPort->regIE = u32Value; + + return rc; +} + +/** + * Read from the port interrupt status register. + */ +static VBOXSTRICTRC PortIntrSts_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regIS=%#010x\n", __FUNCTION__, pAhciPort->regIS)); + ahciLog(("%s: CPDS=%d TFES=%d HBFS=%d HBDS=%d IFS=%d INFS=%d OFS=%d IPMS=%d PRCS=%d DIS=%d PCS=%d DPS=%d UFS=%d SDBS=%d DSS=%d PSS=%d DHRS=%d\n", + __FUNCTION__, (pAhciPort->regIS & AHCI_PORT_IS_CPDS) >> 31, (pAhciPort->regIS & AHCI_PORT_IS_TFES) >> 30, + (pAhciPort->regIS & AHCI_PORT_IS_HBFS) >> 29, (pAhciPort->regIS & AHCI_PORT_IS_HBDS) >> 28, + (pAhciPort->regIS & AHCI_PORT_IS_IFS) >> 27, (pAhciPort->regIS & AHCI_PORT_IS_INFS) >> 26, + (pAhciPort->regIS & AHCI_PORT_IS_OFS) >> 24, (pAhciPort->regIS & AHCI_PORT_IS_IPMS) >> 23, + (pAhciPort->regIS & AHCI_PORT_IS_PRCS) >> 22, (pAhciPort->regIS & AHCI_PORT_IS_DIS) >> 7, + (pAhciPort->regIS & AHCI_PORT_IS_PCS) >> 6, (pAhciPort->regIS & AHCI_PORT_IS_DPS) >> 5, + (pAhciPort->regIS & AHCI_PORT_IS_UFS) >> 4, (pAhciPort->regIS & AHCI_PORT_IS_SDBS) >> 3, + (pAhciPort->regIS & AHCI_PORT_IS_DSS) >> 2, (pAhciPort->regIS & AHCI_PORT_IS_PSS) >> 1, + (pAhciPort->regIS & AHCI_PORT_IS_DHRS))); + *pu32Value = pAhciPort->regIS; + return VINF_SUCCESS; +} + +/** + * Write to the port interrupt status register. + */ +static VBOXSTRICTRC PortIntrSts_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + ASMAtomicAndU32(&pAhciPort->regIS, ~(u32Value & AHCI_PORT_IS_READONLY)); + + return VINF_SUCCESS; +} + +/** + * Read from the port FIS base address upper 32bit register. + */ +static VBOXSTRICTRC PortFisAddrUp_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regFBU=%#010x\n", __FUNCTION__, pAhciPort->regFBU)); + *pu32Value = pAhciPort->regFBU; + return VINF_SUCCESS; +} + +/** + * Write to the port FIS base address upper 32bit register. + */ +static VBOXSTRICTRC PortFisAddrUp_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + + pAhciPort->regFBU = u32Value; + pAhciPort->GCPhysAddrFb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regFBU, pAhciPort->regFB); + + return VINF_SUCCESS; +} + +/** + * Read from the port FIS base address register. + */ +static VBOXSTRICTRC PortFisAddr_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regFB=%#010x\n", __FUNCTION__, pAhciPort->regFB)); + *pu32Value = pAhciPort->regFB; + return VINF_SUCCESS; +} + +/** + * Write to the port FIS base address register. + */ +static VBOXSTRICTRC PortFisAddr_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + + Assert(!(u32Value & ~AHCI_PORT_FB_RESERVED)); + + pAhciPort->regFB = (u32Value & AHCI_PORT_FB_RESERVED); + pAhciPort->GCPhysAddrFb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regFBU, pAhciPort->regFB); + + return VINF_SUCCESS; +} + +/** + * Write to the port command list base address upper 32bit register. + */ +static VBOXSTRICTRC PortCmdLstAddrUp_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + + pAhciPort->regCLBU = u32Value; + pAhciPort->GCPhysAddrClb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regCLBU, pAhciPort->regCLB); + + return VINF_SUCCESS; +} + +/** + * Read from the port command list base address upper 32bit register. + */ +static VBOXSTRICTRC PortCmdLstAddrUp_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regCLBU=%#010x\n", __FUNCTION__, pAhciPort->regCLBU)); + *pu32Value = pAhciPort->regCLBU; + return VINF_SUCCESS; +} + +/** + * Read from the port command list base address register. + */ +static VBOXSTRICTRC PortCmdLstAddr_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: read regCLB=%#010x\n", __FUNCTION__, pAhciPort->regCLB)); + *pu32Value = pAhciPort->regCLB; + return VINF_SUCCESS; +} + +/** + * Write to the port command list base address register. + */ +static VBOXSTRICTRC PortCmdLstAddr_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + ahciLog(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + + Assert(!(u32Value & ~AHCI_PORT_CLB_RESERVED)); + + pAhciPort->regCLB = (u32Value & AHCI_PORT_CLB_RESERVED); + pAhciPort->GCPhysAddrClb = AHCI_RTGCPHYS_FROM_U32(pAhciPort->regCLBU, pAhciPort->regCLB); + + return VINF_SUCCESS; +} + +/** + * Read from the global Version register. + */ +static VBOXSTRICTRC HbaVersion_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log(("%s: read regHbaVs=%#010x\n", __FUNCTION__, pThis->regHbaVs)); + *pu32Value = pThis->regHbaVs; + return VINF_SUCCESS; +} + +/** + * Read from the global Ports implemented register. + */ +static VBOXSTRICTRC HbaPortsImplemented_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log(("%s: read regHbaPi=%#010x\n", __FUNCTION__, pThis->regHbaPi)); + *pu32Value = pThis->regHbaPi; + return VINF_SUCCESS; +} + +/** + * Write to the global interrupt status register. + */ +static VBOXSTRICTRC HbaInterruptStatus_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(iReg); + Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->lock, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + pThis->regHbaIs &= ~(u32Value); + + /* + * Update interrupt status register and check for ports who + * set the interrupt inbetween. + */ + bool fClear = true; + pThis->regHbaIs |= ASMAtomicXchgU32(&pThis->u32PortsInterrupted, 0); + if (!pThis->regHbaIs) + { + unsigned i = 0; + + /* Check if the cleared ports have a interrupt status bit set. */ + while ((u32Value > 0) && (i < AHCI_MAX_NR_PORTS_IMPL)) + { + if (u32Value & 0x01) + { + PAHCIPORT pAhciPort = &pThis->aPorts[i]; + + if (pAhciPort->regIE & pAhciPort->regIS) + { + Log(("%s: Interrupt status of port %u set -> Set interrupt again\n", __FUNCTION__, i)); + ASMAtomicOrU32(&pThis->u32PortsInterrupted, 1 << i); + fClear = false; + break; + } + } + u32Value >>= 1; + i++; + } + } + else + fClear = false; + + if (fClear) + ahciHbaClearInterrupt(pDevIns); + else + { + Log(("%s: Not clearing interrupt: u32PortsInterrupted=%#010x\n", __FUNCTION__, pThis->u32PortsInterrupted)); + /* + * We need to set the interrupt again because the I/O APIC does not set it again even if the + * line is still high. + * We need to clear it first because the PCI bus only calls the interrupt controller if the state changes. + */ + PDMDevHlpPCISetIrq(pDevIns, 0, 0); + PDMDevHlpPCISetIrq(pDevIns, 0, 1); + } + + PDMDevHlpCritSectLeave(pDevIns, &pThis->lock); + return VINF_SUCCESS; +} + +/** + * Read from the global interrupt status register. + */ +static VBOXSTRICTRC HbaInterruptStatus_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(iReg); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->lock, VINF_IOM_R3_MMIO_READ); + if (rc != VINF_SUCCESS) + return rc; + + uint32_t u32PortsInterrupted = ASMAtomicXchgU32(&pThis->u32PortsInterrupted, 0); + + PDMDevHlpCritSectLeave(pDevIns, &pThis->lock); + Log(("%s: read regHbaIs=%#010x u32PortsInterrupted=%#010x\n", __FUNCTION__, pThis->regHbaIs, u32PortsInterrupted)); + + pThis->regHbaIs |= u32PortsInterrupted; + +#ifdef LOG_ENABLED + Log(("%s:", __FUNCTION__)); + uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts)); + for (unsigned i = 0; i < cPortsImpl; i++) + { + if ((pThis->regHbaIs >> i) & 0x01) + Log((" P%d", i)); + } + Log(("\n")); +#endif + + *pu32Value = pThis->regHbaIs; + + return VINF_SUCCESS; +} + +/** + * Write to the global control register. + */ +static VBOXSTRICTRC HbaControl_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + Log(("%s: write u32Value=%#010x\n" + "%s: AE=%d IE=%d HR=%d\n", + __FUNCTION__, u32Value, + __FUNCTION__, (u32Value & AHCI_HBA_CTRL_AE) >> 31, (u32Value & AHCI_HBA_CTRL_IE) >> 1, + (u32Value & AHCI_HBA_CTRL_HR))); + RT_NOREF(iReg); + +#ifndef IN_RING3 + RT_NOREF(pDevIns, pThis, u32Value); + return VINF_IOM_R3_MMIO_WRITE; +#else + /* + * Increase the active thread counter because we might set the host controller + * reset bit. + */ + ASMAtomicIncU32(&pThis->cThreadsActive); + ASMAtomicWriteU32(&pThis->regHbaCtrl, (u32Value & AHCI_HBA_CTRL_RW_MASK) | AHCI_HBA_CTRL_AE); + + /* + * Do the HBA reset if requested and there is no other active thread at the moment, + * the work is deferred to the last active thread otherwise. + */ + uint32_t cThreadsActive = ASMAtomicDecU32(&pThis->cThreadsActive); + if ( (u32Value & AHCI_HBA_CTRL_HR) + && !cThreadsActive) + ahciR3HBAReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC)); + + return VINF_SUCCESS; +#endif +} + +/** + * Read the global control register. + */ +static VBOXSTRICTRC HbaControl_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log(("%s: read regHbaCtrl=%#010x\n" + "%s: AE=%d IE=%d HR=%d\n", + __FUNCTION__, pThis->regHbaCtrl, + __FUNCTION__, (pThis->regHbaCtrl & AHCI_HBA_CTRL_AE) >> 31, (pThis->regHbaCtrl & AHCI_HBA_CTRL_IE) >> 1, + (pThis->regHbaCtrl & AHCI_HBA_CTRL_HR))); + *pu32Value = pThis->regHbaCtrl; + return VINF_SUCCESS; +} + +/** + * Read the global capabilities register. + */ +static VBOXSTRICTRC HbaCapabilities_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log(("%s: read regHbaCap=%#010x\n" + "%s: S64A=%d SNCQ=%d SIS=%d SSS=%d SALP=%d SAL=%d SCLO=%d ISS=%d SNZO=%d SAM=%d SPM=%d PMD=%d SSC=%d PSC=%d NCS=%d NP=%d\n", + __FUNCTION__, pThis->regHbaCap, + __FUNCTION__, (pThis->regHbaCap & AHCI_HBA_CAP_S64A) >> 31, (pThis->regHbaCap & AHCI_HBA_CAP_SNCQ) >> 30, + (pThis->regHbaCap & AHCI_HBA_CAP_SIS) >> 28, (pThis->regHbaCap & AHCI_HBA_CAP_SSS) >> 27, + (pThis->regHbaCap & AHCI_HBA_CAP_SALP) >> 26, (pThis->regHbaCap & AHCI_HBA_CAP_SAL) >> 25, + (pThis->regHbaCap & AHCI_HBA_CAP_SCLO) >> 24, (pThis->regHbaCap & AHCI_HBA_CAP_ISS) >> 20, + (pThis->regHbaCap & AHCI_HBA_CAP_SNZO) >> 19, (pThis->regHbaCap & AHCI_HBA_CAP_SAM) >> 18, + (pThis->regHbaCap & AHCI_HBA_CAP_SPM) >> 17, (pThis->regHbaCap & AHCI_HBA_CAP_PMD) >> 15, + (pThis->regHbaCap & AHCI_HBA_CAP_SSC) >> 14, (pThis->regHbaCap & AHCI_HBA_CAP_PSC) >> 13, + (pThis->regHbaCap & AHCI_HBA_CAP_NCS) >> 8, (pThis->regHbaCap & AHCI_HBA_CAP_NP))); + *pu32Value = pThis->regHbaCap; + return VINF_SUCCESS; +} + +/** + * Write to the global command completion coalescing control register. + */ +static VBOXSTRICTRC HbaCccCtl_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(iReg); + Log(("%s: write u32Value=%#010x\n" + "%s: TV=%d CC=%d INT=%d EN=%d\n", + __FUNCTION__, u32Value, + __FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(u32Value), AHCI_HBA_CCC_CTL_CC_GET(u32Value), + AHCI_HBA_CCC_CTL_INT_GET(u32Value), (u32Value & AHCI_HBA_CCC_CTL_EN))); + + pThis->regHbaCccCtl = u32Value; + pThis->uCccTimeout = AHCI_HBA_CCC_CTL_TV_GET(u32Value); + pThis->uCccPortNr = AHCI_HBA_CCC_CTL_INT_GET(u32Value); + pThis->uCccNr = AHCI_HBA_CCC_CTL_CC_GET(u32Value); + + if (u32Value & AHCI_HBA_CCC_CTL_EN) + PDMDevHlpTimerSetMillies(pDevIns, pThis->hHbaCccTimer, pThis->uCccTimeout); /* Arm the timer */ + else + PDMDevHlpTimerStop(pDevIns, pThis->hHbaCccTimer); + + return VINF_SUCCESS; +} + +/** + * Read the global command completion coalescing control register. + */ +static VBOXSTRICTRC HbaCccCtl_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log(("%s: read regHbaCccCtl=%#010x\n" + "%s: TV=%d CC=%d INT=%d EN=%d\n", + __FUNCTION__, pThis->regHbaCccCtl, + __FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(pThis->regHbaCccCtl), AHCI_HBA_CCC_CTL_CC_GET(pThis->regHbaCccCtl), + AHCI_HBA_CCC_CTL_INT_GET(pThis->regHbaCccCtl), (pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN))); + *pu32Value = pThis->regHbaCccCtl; + return VINF_SUCCESS; +} + +/** + * Write to the global command completion coalescing ports register. + */ +static VBOXSTRICTRC HbaCccPorts_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, iReg); + Log(("%s: write u32Value=%#010x\n", __FUNCTION__, u32Value)); + + pThis->regHbaCccPorts = u32Value; + + return VINF_SUCCESS; +} + +/** + * Read the global command completion coalescing ports register. + */ +static VBOXSTRICTRC HbaCccPorts_r(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log(("%s: read regHbaCccPorts=%#010x\n", __FUNCTION__, pThis->regHbaCccPorts)); + +#ifdef LOG_ENABLED + Log(("%s:", __FUNCTION__)); + uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts)); + for (unsigned i = 0; i < cPortsImpl; i++) + { + if ((pThis->regHbaCccPorts >> i) & 0x01) + Log((" P%d", i)); + } + Log(("\n")); +#endif + + *pu32Value = pThis->regHbaCccPorts; + return VINF_SUCCESS; +} + +/** + * Invalid write to global register + */ +static VBOXSTRICTRC HbaInvalid_w(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg, u32Value); + Log(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value)); + return VINF_SUCCESS; +} + +/** + * Invalid Port write. + */ +static VBOXSTRICTRC PortInvalid_w(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, pAhciPort, iReg, u32Value); + ahciLog(("%s: Write denied!!! iReg=%u u32Value=%#010x\n", __FUNCTION__, iReg, u32Value)); + return VINF_SUCCESS; +} + +/** + * Invalid Port read. + */ +static VBOXSTRICTRC PortInvalid_r(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, pAhciPort, iReg, pu32Value); + ahciLog(("%s: Read denied!!! iReg=%u\n", __FUNCTION__, iReg)); + return VINF_SUCCESS; +} + +/** + * Register descriptor table for global HBA registers + */ +static const AHCIOPREG g_aOpRegs[] = +{ + {"HbaCapabilites", HbaCapabilities_r, HbaInvalid_w}, /* Readonly */ + {"HbaControl" , HbaControl_r, HbaControl_w}, + {"HbaInterruptStatus", HbaInterruptStatus_r, HbaInterruptStatus_w}, + {"HbaPortsImplemented", HbaPortsImplemented_r, HbaInvalid_w}, /* Readonly */ + {"HbaVersion", HbaVersion_r, HbaInvalid_w}, /* ReadOnly */ + {"HbaCccCtl", HbaCccCtl_r, HbaCccCtl_w}, + {"HbaCccPorts", HbaCccPorts_r, HbaCccPorts_w}, +}; + +/** + * Register descriptor table for port registers + */ +static const AHCIPORTOPREG g_aPortOpRegs[] = +{ + {"PortCmdLstAddr", PortCmdLstAddr_r, PortCmdLstAddr_w}, + {"PortCmdLstAddrUp", PortCmdLstAddrUp_r, PortCmdLstAddrUp_w}, + {"PortFisAddr", PortFisAddr_r, PortFisAddr_w}, + {"PortFisAddrUp", PortFisAddrUp_r, PortFisAddrUp_w}, + {"PortIntrSts", PortIntrSts_r, PortIntrSts_w}, + {"PortIntrEnable", PortIntrEnable_r, PortIntrEnable_w}, + {"PortCmd", PortCmd_r, PortCmd_w}, + {"PortReserved1", PortInvalid_r, PortInvalid_w}, /* Not used. */ + {"PortTaskFileData", PortTaskFileData_r, PortInvalid_w}, /* Readonly */ + {"PortSignature", PortSignature_r, PortInvalid_w}, /* Readonly */ + {"PortSStatus", PortSStatus_r, PortInvalid_w}, /* Readonly */ + {"PortSControl", PortSControl_r, PortSControl_w}, + {"PortSError", PortSError_r, PortSError_w}, + {"PortSActive", PortSActive_r, PortSActive_w}, + {"PortCmdIssue", PortCmdIssue_r, PortCmdIssue_w}, + {"PortReserved2", PortInvalid_r, PortInvalid_w}, /* Not used. */ +}; + +#ifdef IN_RING3 + +/** + * Reset initiated by system software for one port. + * + * @param pAhciPort The port to reset, shared bits. + * @param pAhciPortR3 The port to reset, ring-3 bits. + */ +static void ahciR3PortSwReset(PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3) +{ + bool fAllTasksCanceled; + + /* Cancel all tasks first. */ + fAllTasksCanceled = ahciR3CancelActiveTasks(pAhciPortR3); + Assert(fAllTasksCanceled); + + Assert(pAhciPort->cTasksActive == 0); + + pAhciPort->regIS = 0; + pAhciPort->regIE = 0; + pAhciPort->regCMD = AHCI_PORT_CMD_CPD | /* Cold presence detection */ + AHCI_PORT_CMD_SUD | /* Device has spun up. */ + AHCI_PORT_CMD_POD; /* Port is powered on. */ + + /* Hotplugging supported?. */ + if (pAhciPort->fHotpluggable) + pAhciPort->regCMD |= AHCI_PORT_CMD_HPCP; + + pAhciPort->regTFD = (1 << 8) | ATA_STAT_SEEK | ATA_STAT_WRERR; + pAhciPort->regSIG = UINT32_MAX; + pAhciPort->regSSTS = 0; + pAhciPort->regSCTL = 0; + pAhciPort->regSERR = 0; + pAhciPort->regSACT = 0; + pAhciPort->regCI = 0; + + pAhciPort->fResetDevice = false; + pAhciPort->fPoweredOn = true; + pAhciPort->fSpunUp = true; + pAhciPort->cMultSectors = ATA_MAX_MULT_SECTORS; + pAhciPort->uATATransferMode = ATA_MODE_UDMA | 6; + + pAhciPort->u32TasksNew = 0; + pAhciPort->u32TasksRedo = 0; + pAhciPort->u32TasksFinished = 0; + pAhciPort->u32QueuedTasksFinished = 0; + pAhciPort->u32CurrentCommandSlot = 0; + + if (pAhciPort->fPresent) + { + pAhciPort->regCMD |= AHCI_PORT_CMD_CPS; /* Indicate that there is a device on that port */ + + if (pAhciPort->fPoweredOn) + { + /* + * Set states in the Port Signature and SStatus registers. + */ + if (pAhciPort->fATAPI) + pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI; + else + pAhciPort->regSIG = AHCI_PORT_SIG_DISK; + pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */ + (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */ + (0x03 << 0); /* Device detected and communication established. */ + } + } +} + +/** + * Hardware reset used for machine power on and reset. + * + * @param pAhciPort The port to reset, shared bits. + */ +static void ahciPortHwReset(PAHCIPORT pAhciPort) +{ + /* Reset the address registers. */ + pAhciPort->regCLB = 0; + pAhciPort->regCLBU = 0; + pAhciPort->regFB = 0; + pAhciPort->regFBU = 0; + + /* Reset calculated addresses. */ + pAhciPort->GCPhysAddrClb = 0; + pAhciPort->GCPhysAddrFb = 0; +} + +/** + * Create implemented ports bitmap. + * + * @returns 32bit bitmask with a bit set for every implemented port. + * @param cPorts Number of ports. + */ +static uint32_t ahciGetPortsImplemented(unsigned cPorts) +{ + uint32_t uPortsImplemented = 0; + + for (unsigned i = 0; i < cPorts; i++) + uPortsImplemented |= (1 << i); + + return uPortsImplemented; +} + +/** + * Reset the entire HBA. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pThisCC The ring-3 AHCI state. + */ +static void ahciR3HBAReset(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIR3 pThisCC) +{ + unsigned i; + int rc = VINF_SUCCESS; + + LogRel(("AHCI#%u: Reset the HBA\n", pDevIns->iInstance)); + + /* Stop the CCC timer. */ + if (pThis->regHbaCccCtl & AHCI_HBA_CCC_CTL_EN) + { + rc = PDMDevHlpTimerStop(pDevIns, pThis->hHbaCccTimer); + if (RT_FAILURE(rc)) + AssertMsgFailed(("%s: Failed to stop timer!\n", __FUNCTION__)); + } + + /* Reset every port */ + uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThisCC->aPorts)); + for (i = 0; i < cPortsImpl; i++) + { + PAHCIPORT pAhciPort = &pThis->aPorts[i]; + PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[i]; + + pAhciPort->iLUN = i; + pAhciPortR3->iLUN = i; + ahciR3PortSwReset(pAhciPort, pAhciPortR3); + } + + /* Init Global registers */ + pThis->regHbaCap = AHCI_HBA_CAP_ISS_SHIFT(AHCI_HBA_CAP_ISS_GEN2) + | AHCI_HBA_CAP_S64A /* 64bit addressing supported */ + | AHCI_HBA_CAP_SAM /* AHCI mode only */ + | AHCI_HBA_CAP_SNCQ /* Support native command queuing */ + | AHCI_HBA_CAP_SSS /* Staggered spin up */ + | AHCI_HBA_CAP_CCCS /* Support command completion coalescing */ + | AHCI_HBA_CAP_NCS_SET(pThis->cCmdSlotsAvail) /* Number of command slots we support */ + | AHCI_HBA_CAP_NP_SET(pThis->cPortsImpl); /* Number of supported ports */ + pThis->regHbaCtrl = AHCI_HBA_CTRL_AE; + pThis->regHbaPi = ahciGetPortsImplemented(pThis->cPortsImpl); + pThis->regHbaVs = AHCI_HBA_VS_MJR | AHCI_HBA_VS_MNR; + pThis->regHbaCccCtl = 0; + pThis->regHbaCccPorts = 0; + pThis->uCccTimeout = 0; + pThis->uCccPortNr = 0; + pThis->uCccNr = 0; + + /* Clear pending interrupts. */ + pThis->regHbaIs = 0; + pThis->u32PortsInterrupted = 0; + ahciHbaClearInterrupt(pDevIns); + + pThis->f64BitAddr = false; + pThis->u32PortsInterrupted = 0; + pThis->f8ByteMMIO4BytesWrittenSuccessfully = false; + /* Clear the HBA Reset bit */ + pThis->regHbaCtrl &= ~AHCI_HBA_CTRL_HR; +} + +#endif /* IN_RING3 */ + +/** + * Reads from a AHCI controller register. + * + * @returns Strict VBox status code. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param uReg The register to write. + * @param pv Where to store the result. + * @param cb Number of bytes read. + */ +static VBOXSTRICTRC ahciRegisterRead(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t uReg, void *pv, unsigned cb) +{ + VBOXSTRICTRC rc; + uint32_t iReg; + + /* + * If the access offset is smaller than AHCI_HBA_GLOBAL_SIZE the guest accesses the global registers. + * Otherwise it accesses the registers of a port. + */ + if (uReg < AHCI_HBA_GLOBAL_SIZE) + { + iReg = uReg >> 2; + Log3(("%s: Trying to read from global register %u\n", __FUNCTION__, iReg)); + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const AHCIOPREG *pReg = &g_aOpRegs[iReg]; + rc = pReg->pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv); + } + else + { + Log3(("%s: Trying to read global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs))); + *(uint32_t *)pv = 0; + rc = VINF_SUCCESS; + } + } + else + { + uint32_t iRegOffset; + uint32_t iPort; + + /* Calculate accessed port. */ + uReg -= AHCI_HBA_GLOBAL_SIZE; + iPort = uReg / AHCI_PORT_REGISTER_SIZE; + iRegOffset = (uReg % AHCI_PORT_REGISTER_SIZE); + iReg = iRegOffset >> 2; + + Log3(("%s: Trying to read from port %u and register %u\n", __FUNCTION__, iPort, iReg)); + + if (RT_LIKELY( iPort < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts)) + && iReg < RT_ELEMENTS(g_aPortOpRegs))) + { + const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg]; + rc = pPortReg->pfnRead(pDevIns, pThis, &pThis->aPorts[iPort], iReg, (uint32_t *)pv); + } + else + { + Log3(("%s: Trying to read port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs))); + rc = VINF_IOM_MMIO_UNUSED_00; + } + + /* + * Windows Vista tries to read one byte from some registers instead of four. + * Correct the value according to the read size. + */ + if (RT_SUCCESS(rc) && cb != sizeof(uint32_t)) + { + switch (cb) + { + case 1: + { + uint8_t uNewValue; + uint8_t *p = (uint8_t *)pv; + + iRegOffset &= 3; + Log3(("%s: iRegOffset=%u\n", __FUNCTION__, iRegOffset)); + uNewValue = p[iRegOffset]; + /* Clear old value */ + *(uint32_t *)pv = 0; + *(uint8_t *)pv = uNewValue; + break; + } + default: + ASSERT_GUEST_MSG_FAILED(("%s: unsupported access width cb=%d iPort=%x iRegOffset=%x iReg=%x!!!\n", + __FUNCTION__, cb, iPort, iRegOffset, iReg)); + } + } + } + + return rc; +} + +/** + * Writes a value to one of the AHCI controller registers. + * + * @returns Strict VBox status code. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param offReg The offset of the register to write to. + * @param u32Value The value to write. + */ +static VBOXSTRICTRC ahciRegisterWrite(PPDMDEVINS pDevIns, PAHCI pThis, uint32_t offReg, uint32_t u32Value) +{ + VBOXSTRICTRC rc; + uint32_t iReg; + + /* + * If the access offset is smaller than 100h the guest accesses the global registers. + * Otherwise it accesses the registers of a port. + */ + if (offReg < AHCI_HBA_GLOBAL_SIZE) + { + Log3(("Write global HBA register\n")); + iReg = offReg >> 2; + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const AHCIOPREG *pReg = &g_aOpRegs[iReg]; + rc = pReg->pfnWrite(pDevIns, pThis, iReg, u32Value); + } + else + { + Log3(("%s: Trying to write global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs))); + rc = VINF_SUCCESS; + } + } + else + { + uint32_t iPort; + Log3(("Write Port register\n")); + /* Calculate accessed port. */ + offReg -= AHCI_HBA_GLOBAL_SIZE; + iPort = offReg / AHCI_PORT_REGISTER_SIZE; + iReg = (offReg % AHCI_PORT_REGISTER_SIZE) >> 2; + Log3(("%s: Trying to write to port %u and register %u\n", __FUNCTION__, iPort, iReg)); + if (RT_LIKELY( iPort < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts)) + && iReg < RT_ELEMENTS(g_aPortOpRegs))) + { + const AHCIPORTOPREG *pPortReg = &g_aPortOpRegs[iReg]; + rc = pPortReg->pfnWrite(pDevIns, pThis, &pThis->aPorts[iPort], iReg, u32Value); + } + else + { + Log3(("%s: Trying to write port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs))); + rc = VINF_SUCCESS; + } + } + + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE} + */ +static DECLCALLBACK(VBOXSTRICTRC) ahciMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + Log2(("#%d ahciMMIORead: pvUser=%p:{%.*Rhxs} cb=%d off=%RGp\n", pDevIns->iInstance, pv, cb, pv, cb, off)); + RT_NOREF(pvUser); + + VBOXSTRICTRC rc = ahciRegisterRead(pDevIns, pThis, off, pv, cb); + + Log2(("#%d ahciMMIORead: return pvUser=%p:{%.*Rhxs} cb=%d off=%RGp rc=%Rrc\n", + pDevIns->iInstance, pv, cb, pv, cb, off, VBOXSTRICTRC_VAL(rc))); + return rc; +} + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE} + */ +static DECLCALLBACK(VBOXSTRICTRC) ahciMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + + Assert(cb == 4 || cb == 8); /* Assert IOM flags & sanity */ + Assert(!(off & (cb - 1))); /* Ditto. */ + + /* Break up 64 bits writes into two dword writes. */ + /** @todo Eliminate this code once the IOM/EM starts taking care of these + * situations. */ + if (cb == 8) + { + /* + * Only write the first 4 bytes if they weren't already. + * It is possible that the last write to the register caused a world + * switch and we entered this function again. + * Writing the first 4 bytes again could cause indeterminate behavior + * which can cause errors in the guest. + */ + VBOXSTRICTRC rc = VINF_SUCCESS; + if (!pThis->f8ByteMMIO4BytesWrittenSuccessfully) + { + rc = ahciMMIOWrite(pDevIns, pvUser, off, pv, 4); + if (rc != VINF_SUCCESS) + return rc; + + pThis->f8ByteMMIO4BytesWrittenSuccessfully = true; + } + + rc = ahciMMIOWrite(pDevIns, pvUser, off + 4, (uint8_t *)pv + 4, 4); + /* + * Reset flag again so that the first 4 bytes are written again on the next + * 8byte MMIO access. + */ + if (rc == VINF_SUCCESS) + pThis->f8ByteMMIO4BytesWrittenSuccessfully = false; + + return rc; + } + + /* Do the access. */ + Log2(("#%d ahciMMIOWrite: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp\n", pDevIns->iInstance, pv, cb, pv, cb, off)); + return ahciRegisterWrite(pDevIns, pThis, off, *(uint32_t const *)pv); +} + + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT, + * Fake IDE port handler provided to make solaris happy.} + */ +static DECLCALLBACK(VBOXSTRICTRC) +ahciLegacyFakeWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +{ + RT_NOREF(pDevIns, pvUser, offPort, u32, cb); + ASSERT_GUEST_MSG_FAILED(("Should not happen\n")); + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWIN, + * Fake IDE port handler provided to make solaris happy.} + */ +static DECLCALLBACK(VBOXSTRICTRC) +ahciLegacyFakeRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +{ + /** @todo we should set *pu32 to something. */ + RT_NOREF(pDevIns, pvUser, offPort, pu32, cb); + ASSERT_GUEST_MSG_FAILED(("Should not happen\n")); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT, + * I/O port handler for writes to the index/data register pair.} + */ +static DECLCALLBACK(VBOXSTRICTRC) ahciIdxDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + VBOXSTRICTRC rc = VINF_SUCCESS; + RT_NOREF(pvUser, cb); + + if (offPort >= 8) + { + ASSERT_GUEST(cb == 4); + + uint32_t const iReg = (offPort - 8) / 4; + if (iReg == 0) + { + /* Write the index register. */ + pThis->regIdx = u32; + } + else + { + /** @todo range check? */ + ASSERT_GUEST(iReg == 1); + rc = ahciRegisterWrite(pDevIns, pThis, pThis->regIdx, u32); + if (rc == VINF_IOM_R3_MMIO_WRITE) + rc = VINF_IOM_R3_IOPORT_WRITE; + } + } + /* else: ignore */ + + Log2(("#%d ahciIdxDataWrite: pu32=%p:{%.*Rhxs} cb=%d offPort=%#x rc=%Rrc\n", + pDevIns->iInstance, &u32, cb, &u32, cb, offPort, VBOXSTRICTRC_VAL(rc))); + return rc; +} + +/** + * @callback_method_impl{FNIOMIOPORTNEWOUT, + * I/O port handler for reads from the index/data register pair.} + */ +static DECLCALLBACK(VBOXSTRICTRC) ahciIdxDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + VBOXSTRICTRC rc = VINF_SUCCESS; + RT_NOREF(pvUser); + + if (offPort >= 8) + { + ASSERT_GUEST(cb == 4); + + uint32_t const iReg = (offPort - 8) / 4; + if (iReg == 0) + { + /* Read the index register. */ + *pu32 = pThis->regIdx; + } + else + { + /** @todo range check? */ + ASSERT_GUEST(iReg == 1); + rc = ahciRegisterRead(pDevIns, pThis, pThis->regIdx, pu32, cb); + if (rc == VINF_IOM_R3_MMIO_READ) + rc = VINF_IOM_R3_IOPORT_READ; + else if (rc == VINF_IOM_MMIO_UNUSED_00) + rc = VERR_IOM_IOPORT_UNUSED; + } + } + else + *pu32 = UINT32_MAX; + + Log2(("#%d ahciIdxDataRead: pu32=%p:{%.*Rhxs} cb=%d offPort=%#x rc=%Rrc\n", + pDevIns->iInstance, pu32, cb, pu32, cb, offPort, VBOXSTRICTRC_VAL(rc))); + return rc; +} + +#ifdef IN_RING3 + +/* -=-=-=-=-=- PAHCI::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) ahciR3Status_QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + PAHCICC pThisCC = RT_FROM_MEMBER(pInterface, AHCICC, ILeds); + if (iLUN < AHCI_MAX_NR_PORTS_IMPL) + { + PAHCI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PAHCI); + *ppLed = &pThis->aPorts[iLUN].Led; + Assert((*ppLed)->u32Magic == PDMLED_MAGIC); + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) ahciR3Status_QueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PAHCICC pThisCC = RT_FROM_MEMBER(pInterface, AHCICC, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds); + return NULL; +} + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) ahciR3PortQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pAhciPortR3->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pAhciPortR3->IPort); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAEXPORT, &pAhciPortR3->IMediaExPort); + return NULL; +} + +/** + * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation} + */ +static DECLCALLBACK(int) ahciR3PortQueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController, + uint32_t *piInstance, uint32_t *piLUN) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IPort); + PPDMDEVINS pDevIns = pAhciPortR3->pDevIns; + + AssertPtrReturn(ppcszController, VERR_INVALID_POINTER); + AssertPtrReturn(piInstance, VERR_INVALID_POINTER); + AssertPtrReturn(piLUN, VERR_INVALID_POINTER); + + *ppcszController = pDevIns->pReg->szName; + *piInstance = pDevIns->iInstance; + *piLUN = pAhciPortR3->iLUN; + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIMEDIAPORT,pfnQueryScsiInqStrings} + */ +static DECLCALLBACK(int) ahciR3PortQueryScsiInqStrings(PPDMIMEDIAPORT pInterface, const char **ppszVendorId, + const char **ppszProductId, const char **ppszRevision) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IPort); + PAHCI pThis = PDMDEVINS_2_DATA(pAhciPortR3->pDevIns, PAHCI); + PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN); + + if (ppszVendorId) + *ppszVendorId = &pAhciPort->szInquiryVendorId[0]; + if (ppszProductId) + *ppszProductId = &pAhciPort->szInquiryProductId[0]; + if (ppszRevision) + *ppszRevision = &pAhciPort->szInquiryRevision[0]; + return VINF_SUCCESS; +} + +#ifdef LOG_ENABLED + +/** + * Dump info about the FIS + * + * @param pAhciPort The port the command FIS was read from (shared bits). + * @param cmdFis The FIS to print info from. + */ +static void ahciDumpFisInfo(PAHCIPORT pAhciPort, uint8_t *cmdFis) +{ + ahciLog(("%s: *** Begin FIS info dump. ***\n", __FUNCTION__)); + /* Print FIS type. */ + switch (cmdFis[AHCI_CMDFIS_TYPE]) + { + case AHCI_CMDFIS_TYPE_H2D: + { + ahciLog(("%s: Command Fis type: H2D\n", __FUNCTION__)); + ahciLog(("%s: Command Fis size: %d bytes\n", __FUNCTION__, AHCI_CMDFIS_TYPE_H2D_SIZE)); + if (cmdFis[AHCI_CMDFIS_BITS] & AHCI_CMDFIS_C) + ahciLog(("%s: Command register update\n", __FUNCTION__)); + else + ahciLog(("%s: Control register update\n", __FUNCTION__)); + ahciLog(("%s: CMD=%#04x \"%s\"\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CMD], ATACmdText(cmdFis[AHCI_CMDFIS_CMD]))); + ahciLog(("%s: FEAT=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_FET])); + ahciLog(("%s: SECTN=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTN])); + ahciLog(("%s: CYLL=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLL])); + ahciLog(("%s: CYLH=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLH])); + ahciLog(("%s: HEAD=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_HEAD])); + + ahciLog(("%s: SECTNEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTNEXP])); + ahciLog(("%s: CYLLEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLLEXP])); + ahciLog(("%s: CYLHEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CYLHEXP])); + ahciLog(("%s: FETEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_FETEXP])); + + ahciLog(("%s: SECTC=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTC])); + ahciLog(("%s: SECTCEXP=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_SECTCEXP])); + ahciLog(("%s: CTL=%#04x\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CTL])); + if (cmdFis[AHCI_CMDFIS_CTL] & AHCI_CMDFIS_CTL_SRST) + ahciLog(("%s: Reset bit is set\n", __FUNCTION__)); + break; + } + case AHCI_CMDFIS_TYPE_D2H: + { + ahciLog(("%s: Command Fis type D2H\n", __FUNCTION__)); + ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_D2H_SIZE)); + break; + } + case AHCI_CMDFIS_TYPE_SETDEVBITS: + { + ahciLog(("%s: Command Fis type Set Device Bits\n", __FUNCTION__)); + ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE)); + break; + } + case AHCI_CMDFIS_TYPE_DMAACTD2H: + { + ahciLog(("%s: Command Fis type DMA Activate H2D\n", __FUNCTION__)); + ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_DMAACTD2H_SIZE)); + break; + } + case AHCI_CMDFIS_TYPE_DMASETUP: + { + ahciLog(("%s: Command Fis type DMA Setup\n", __FUNCTION__)); + ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_DMASETUP_SIZE)); + break; + } + case AHCI_CMDFIS_TYPE_PIOSETUP: + { + ahciLog(("%s: Command Fis type PIO Setup\n", __FUNCTION__)); + ahciLog(("%s: Command Fis size: %d\n", __FUNCTION__, AHCI_CMDFIS_TYPE_PIOSETUP_SIZE)); + break; + } + case AHCI_CMDFIS_TYPE_DATA: + { + ahciLog(("%s: Command Fis type Data\n", __FUNCTION__)); + break; + } + default: + ahciLog(("%s: ERROR Unknown command FIS type\n", __FUNCTION__)); + break; + } + ahciLog(("%s: *** End FIS info dump. ***\n", __FUNCTION__)); +} + +/** + * Dump info about the command header + * + * @param pAhciPort Pointer to the port the command header was read from + * (shared bits). + * @param pCmdHdr The command header to print info from. + */ +static void ahciDumpCmdHdrInfo(PAHCIPORT pAhciPort, CmdHdr *pCmdHdr) +{ + ahciLog(("%s: *** Begin command header info dump. ***\n", __FUNCTION__)); + ahciLog(("%s: Number of Scatter/Gatther List entries: %u\n", __FUNCTION__, AHCI_CMDHDR_PRDTL_ENTRIES(pCmdHdr->u32DescInf))); + if (pCmdHdr->u32DescInf & AHCI_CMDHDR_C) + ahciLog(("%s: Clear busy upon R_OK\n", __FUNCTION__)); + if (pCmdHdr->u32DescInf & AHCI_CMDHDR_B) + ahciLog(("%s: BIST Fis\n", __FUNCTION__)); + if (pCmdHdr->u32DescInf & AHCI_CMDHDR_R) + ahciLog(("%s: Device Reset Fis\n", __FUNCTION__)); + if (pCmdHdr->u32DescInf & AHCI_CMDHDR_P) + ahciLog(("%s: Command prefetchable\n", __FUNCTION__)); + if (pCmdHdr->u32DescInf & AHCI_CMDHDR_W) + ahciLog(("%s: Device write\n", __FUNCTION__)); + else + ahciLog(("%s: Device read\n", __FUNCTION__)); + if (pCmdHdr->u32DescInf & AHCI_CMDHDR_A) + ahciLog(("%s: ATAPI command\n", __FUNCTION__)); + else + ahciLog(("%s: ATA command\n", __FUNCTION__)); + + ahciLog(("%s: Command FIS length %u DW\n", __FUNCTION__, (pCmdHdr->u32DescInf & AHCI_CMDHDR_CFL_MASK))); + ahciLog(("%s: *** End command header info dump. ***\n", __FUNCTION__)); +} + +#endif /* LOG_ENABLED */ + +/** + * Post the first D2H FIS from the device into guest memory. + * + * @param pDevIns The device instance. + * @param pAhciPort Pointer to the port which "receives" the FIS (shared bits). + */ +static void ahciPostFirstD2HFisIntoMemory(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort) +{ + uint8_t d2hFis[AHCI_CMDFIS_TYPE_D2H_SIZE]; + + pAhciPort->fFirstD2HFisSent = true; + + ahciLog(("%s: Sending First D2H FIS from FIFO\n", __FUNCTION__)); + memset(&d2hFis[0], 0, sizeof(d2hFis)); + d2hFis[AHCI_CMDFIS_TYPE] = AHCI_CMDFIS_TYPE_D2H; + d2hFis[AHCI_CMDFIS_ERR] = 0x01; + + d2hFis[AHCI_CMDFIS_STS] = 0x00; + + /* Set the signature based on the device type. */ + if (pAhciPort->fATAPI) + { + d2hFis[AHCI_CMDFIS_CYLL] = 0x14; + d2hFis[AHCI_CMDFIS_CYLH] = 0xeb; + } + else + { + d2hFis[AHCI_CMDFIS_CYLL] = 0x00; + d2hFis[AHCI_CMDFIS_CYLH] = 0x00; + } + + d2hFis[AHCI_CMDFIS_HEAD] = 0x00; + d2hFis[AHCI_CMDFIS_SECTN] = 0x01; + d2hFis[AHCI_CMDFIS_SECTC] = 0x01; + + pAhciPort->regTFD = (1 << 8) | ATA_STAT_SEEK | ATA_STAT_WRERR; + if (!pAhciPort->fATAPI) + pAhciPort->regTFD |= ATA_STAT_READY; + + ahciPostFisIntoMemory(pDevIns, pAhciPort, AHCI_CMDFIS_TYPE_D2H, d2hFis); +} + +/** + * Post the FIS in the memory area allocated by the guest and set interrupt if necessary. + * + * @returns VBox status code + * @param pDevIns The device instance. + * @param pAhciPort The port which "receives" the FIS(shared bits). + * @param uFisType The type of the FIS. + * @param pCmdFis Pointer to the FIS which is to be posted into memory. + */ +static int ahciPostFisIntoMemory(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, unsigned uFisType, uint8_t *pCmdFis) +{ + int rc = VINF_SUCCESS; + RTGCPHYS GCPhysAddrRecFis = pAhciPort->GCPhysAddrFb; + unsigned cbFis = 0; + + ahciLog(("%s: pAhciPort=%p uFisType=%u pCmdFis=%p\n", __FUNCTION__, pAhciPort, uFisType, pCmdFis)); + + if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) + { + AssertMsg(GCPhysAddrRecFis, ("%s: GCPhysAddrRecFis is 0\n", __FUNCTION__)); + + /* Determine the offset and size of the FIS based on uFisType. */ + switch (uFisType) + { + case AHCI_CMDFIS_TYPE_D2H: + { + GCPhysAddrRecFis += AHCI_RECFIS_RFIS_OFFSET; + cbFis = AHCI_CMDFIS_TYPE_D2H_SIZE; + break; + } + case AHCI_CMDFIS_TYPE_SETDEVBITS: + { + GCPhysAddrRecFis += AHCI_RECFIS_SDBFIS_OFFSET; + cbFis = AHCI_CMDFIS_TYPE_SETDEVBITS_SIZE; + break; + } + case AHCI_CMDFIS_TYPE_DMASETUP: + { + GCPhysAddrRecFis += AHCI_RECFIS_DSFIS_OFFSET; + cbFis = AHCI_CMDFIS_TYPE_DMASETUP_SIZE; + break; + } + case AHCI_CMDFIS_TYPE_PIOSETUP: + { + GCPhysAddrRecFis += AHCI_RECFIS_PSFIS_OFFSET; + cbFis = AHCI_CMDFIS_TYPE_PIOSETUP_SIZE; + break; + } + default: + /* + * We should post the unknown FIS into memory too but this never happens because + * we know which FIS types we generate. ;) + */ + AssertMsgFailed(("%s: Unknown FIS type!\n", __FUNCTION__)); + } + + /* Post the FIS into memory. */ + ahciLog(("%s: PDMDevHlpPCIPhysWrite GCPhysAddrRecFis=%RGp cbFis=%u\n", __FUNCTION__, GCPhysAddrRecFis, cbFis)); + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysAddrRecFis, pCmdFis, cbFis); + } + + return rc; +} + +DECLINLINE(void) ahciReqSetStatus(PAHCIREQ pAhciReq, uint8_t u8Error, uint8_t u8Status) +{ + pAhciReq->cmdFis[AHCI_CMDFIS_ERR] = u8Error; + pAhciReq->cmdFis[AHCI_CMDFIS_STS] = u8Status; +} + +static void ataPadString(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] = ' '; + } +} + +static uint32_t ataChecksum(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; +} + +static int ahciIdentifySS(PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, void *pvBuf) +{ + uint16_t *p = (uint16_t *)pvBuf; + memset(p, 0, 512); + p[0] = RT_H2LE_U16(0x0040); + p[1] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383)); + p[3] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cHeads); + /* Block size; obsolete, but required for the BIOS. */ + p[5] = RT_H2LE_U16(512); + p[6] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cSectors); + ataPadString((uint8_t *)(p + 10), pAhciPort->szSerialNumber, AHCI_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 */ + ataPadString((uint8_t *)(p + 23), pAhciPort->szFirmwareRevision, AHCI_FIRMWARE_REVISION_LENGTH); /* firmware version */ + ataPadString((uint8_t *)(p + 27), pAhciPort->szModelNumber, AHCI_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(pAhciPort->PCHSGeometry.cCylinders, 16383)); + p[55] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cHeads); + p[56] = RT_H2LE_U16(pAhciPort->PCHSGeometry.cSectors); + p[57] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors); + p[58] = RT_H2LE_U16(RT_MIN(pAhciPort->PCHSGeometry.cCylinders, 16383) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors >> 16); + if (pAhciPort->cMultSectors) + p[59] = RT_H2LE_U16(0x100 | pAhciPort->cMultSectors); + if (pAhciPort->cTotalSectors <= (1 << 28) - 1) + { + p[60] = RT_H2LE_U16(pAhciPort->cTotalSectors); + p[61] = RT_H2LE_U16(pAhciPort->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, pAhciPort->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 ( pAhciPort->fTrimEnabled + || pAhciPort->cbSector != 512 + || pAhciPortR3->pDrvMedia->pfnIsNonRotational(pAhciPortR3->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 */ + 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 */ + 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, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */ + p[93] = RT_H2LE_U16(0x00); + p[100] = RT_H2LE_U16(pAhciPort->cTotalSectors); + p[101] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 16); + p[102] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 32); + p[103] = RT_H2LE_U16(pAhciPort->cTotalSectors >> 48); + + /* valid information, more than one logical sector per physical sector, 2^cLogSectorsPerPhysicalExp logical sectors per physical sector */ + if (pAhciPort->cLogSectorsPerPhysicalExp) + p[106] = RT_H2LE_U16(RT_BIT(14) | RT_BIT(13) | pAhciPort->cLogSectorsPerPhysicalExp); + + if (pAhciPort->cbSector != 512) + { + uint32_t cSectorSizeInWords = pAhciPort->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 (pAhciPortR3->pDrvMedia->pfnIsNonRotational(pAhciPortR3->pDrvMedia)) + p[217] = RT_H2LE_U16(1); /* Non-rotational medium */ + + if (pAhciPort->fTrimEnabled) /** @todo Set bit 14 in word 69 too? (Deterministic read after TRIM). */ + p[169] = RT_H2LE_U16(1); /* DATA SET MANAGEMENT command supported. */ + + /* The following are SATA specific */ + p[75] = RT_H2LE_U16(pThis->cCmdSlotsAvail - 1); /* Number of commands we support, 0's based */ + p[76] = RT_H2LE_U16((1 << 8) | (1 << 2)); /* Native command queuing and Serial ATA Gen2 (3.0 Gbps) speed supported */ + + uint32_t uCsum = ataChecksum(p, 510); + p[255] = RT_H2LE_U16(0xa5 | (uCsum << 8)); /* Integrity word */ + + return VINF_SUCCESS; +} + +static int ahciR3AtapiIdentify(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, PAHCIPORT pAhciPort, size_t cbData, size_t *pcbData) +{ + uint16_t p[256]; + + memset(p, 0, 512); + /* Removable CDROM, 50us response, 12 byte packets */ + p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 2 << 5 | 0 << 0); + ataPadString((uint8_t *)(p + 10), pAhciPort->szSerialNumber, AHCI_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 */ + ataPadString((uint8_t *)(p + 23), pAhciPort->szFirmwareRevision, AHCI_FIRMWARE_REVISION_LENGTH); /* firmware version */ + ataPadString((uint8_t *)(p + 27), pAhciPort->szModelNumber, AHCI_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, pAhciPort->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[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, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */ + p[93] = RT_H2LE_U16((1 | 1 << 1) << ((pAhciPort->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14); + + /* The following are SATA specific */ + p[75] = RT_H2LE_U16(31); /* We support 32 commands */ + p[76] = RT_H2LE_U16((1 << 8) | (1 << 2)); /* Native command queuing and Serial ATA Gen2 (3.0 Gbps) speed supported */ + + /* Copy the buffer in to the scatter gather list. */ + *pcbData = ahciR3CopyBufferToPrdtl(pDevIns, pAhciReq, (void *)&p[0], RT_MIN(cbData, sizeof(p)), 0 /* cbSkip */); + return VINF_SUCCESS; +} + +/** + * Reset all values after a reset of the attached storage device. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The port the device is attached to, shared bits(shared + * bits). + * @param pAhciReq The state to get the tag number from. + */ +static void ahciFinishStorageDeviceReset(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIREQ pAhciReq) +{ + int rc; + + /* Send a status good D2H FIS. */ + pAhciPort->fResetDevice = false; + if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) + ahciPostFirstD2HFisIntoMemory(pDevIns, pAhciPort); + + /* As this is the first D2H FIS after the reset update the signature in the SIG register of the port. */ + if (pAhciPort->fATAPI) + pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI; + else + pAhciPort->regSIG = AHCI_PORT_SIG_DISK; + ASMAtomicOrU32(&pAhciPort->u32TasksFinished, (1 << pAhciReq->uTag)); + + rc = ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + AssertRC(rc); +} + +/** + * Initiates a device reset caused by ATA_DEVICE_RESET (ATAPI only). + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The device to reset(shared bits). + * @param pAhciReq The task state. + */ +static void ahciDeviceReset(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIREQ pAhciReq) +{ + ASMAtomicWriteBool(&pAhciPort->fResetDevice, true); + + /* + * Because this ATAPI only and ATAPI can't have + * more than one command active at a time the task counter should be 0 + * and it is possible to finish the reset now. + */ + Assert(ASMAtomicReadU32(&pAhciPort->cTasksActive) == 0); + ahciFinishStorageDeviceReset(pDevIns, pThis, pAhciPort, pAhciReq); +} + +/** + * Create a PIO setup FIS and post it into the memory area of the guest. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The port of the SATA controller (shared bits). + * @param cbTransfer Transfer size of the request. + * @param pCmdFis Pointer to the command FIS from the guest. + * @param fRead Flag whether this is a read request. + * @param fInterrupt If an interrupt should be send to the guest. + */ +static void ahciSendPioSetupFis(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, + size_t cbTransfer, uint8_t *pCmdFis, bool fRead, bool fInterrupt) +{ + uint8_t abPioSetupFis[20]; + bool fAssertIntr = false; + + ahciLog(("%s: building PIO setup Fis\n", __FUNCTION__)); + + AssertMsg( cbTransfer > 0 + && cbTransfer <= 65534, + ("Can't send PIO setup FIS for requests with 0 bytes to transfer or greater than 65534\n")); + + if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) + { + memset(&abPioSetupFis[0], 0, sizeof(abPioSetupFis)); + abPioSetupFis[AHCI_CMDFIS_TYPE] = AHCI_CMDFIS_TYPE_PIOSETUP; + abPioSetupFis[AHCI_CMDFIS_BITS] = (fInterrupt ? AHCI_CMDFIS_I : 0); + if (fRead) + abPioSetupFis[AHCI_CMDFIS_BITS] |= AHCI_CMDFIS_D; + abPioSetupFis[AHCI_CMDFIS_STS] = pCmdFis[AHCI_CMDFIS_STS]; + abPioSetupFis[AHCI_CMDFIS_ERR] = pCmdFis[AHCI_CMDFIS_ERR]; + abPioSetupFis[AHCI_CMDFIS_SECTN] = pCmdFis[AHCI_CMDFIS_SECTN]; + abPioSetupFis[AHCI_CMDFIS_CYLL] = pCmdFis[AHCI_CMDFIS_CYLL]; + abPioSetupFis[AHCI_CMDFIS_CYLH] = pCmdFis[AHCI_CMDFIS_CYLH]; + abPioSetupFis[AHCI_CMDFIS_HEAD] = pCmdFis[AHCI_CMDFIS_HEAD]; + abPioSetupFis[AHCI_CMDFIS_SECTNEXP] = pCmdFis[AHCI_CMDFIS_SECTNEXP]; + abPioSetupFis[AHCI_CMDFIS_CYLLEXP] = pCmdFis[AHCI_CMDFIS_CYLLEXP]; + abPioSetupFis[AHCI_CMDFIS_CYLHEXP] = pCmdFis[AHCI_CMDFIS_CYLHEXP]; + abPioSetupFis[AHCI_CMDFIS_SECTC] = pCmdFis[AHCI_CMDFIS_SECTC]; + abPioSetupFis[AHCI_CMDFIS_SECTCEXP] = pCmdFis[AHCI_CMDFIS_SECTCEXP]; + + /* Set transfer count. */ + abPioSetupFis[16] = (cbTransfer >> 8) & 0xff; + abPioSetupFis[17] = cbTransfer & 0xff; + + /* Update registers. */ + pAhciPort->regTFD = (pCmdFis[AHCI_CMDFIS_ERR] << 8) | pCmdFis[AHCI_CMDFIS_STS]; + + ahciPostFisIntoMemory(pDevIns, pAhciPort, AHCI_CMDFIS_TYPE_PIOSETUP, abPioSetupFis); + + if (fInterrupt) + { + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_PSS); + /* Check if we should assert an interrupt */ + if (pAhciPort->regIE & AHCI_PORT_IE_PSE) + fAssertIntr = true; + } + + if (fAssertIntr) + { + int rc = ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + AssertRC(rc); + } + } +} + +/** + * Build a D2H FIS and post into the memory area of the guest. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The port of the SATA controller (shared bits). + * @param uTag The tag of the request. + * @param pCmdFis Pointer to the command FIS from the guest. + * @param fInterrupt If an interrupt should be send to the guest. + */ +static void ahciSendD2HFis(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, uint32_t uTag, uint8_t *pCmdFis, bool fInterrupt) +{ + uint8_t d2hFis[20]; + bool fAssertIntr = false; + + ahciLog(("%s: building D2H Fis\n", __FUNCTION__)); + + if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) + { + memset(&d2hFis[0], 0, sizeof(d2hFis)); + d2hFis[AHCI_CMDFIS_TYPE] = AHCI_CMDFIS_TYPE_D2H; + d2hFis[AHCI_CMDFIS_BITS] = (fInterrupt ? AHCI_CMDFIS_I : 0); + d2hFis[AHCI_CMDFIS_STS] = pCmdFis[AHCI_CMDFIS_STS]; + d2hFis[AHCI_CMDFIS_ERR] = pCmdFis[AHCI_CMDFIS_ERR]; + d2hFis[AHCI_CMDFIS_SECTN] = pCmdFis[AHCI_CMDFIS_SECTN]; + d2hFis[AHCI_CMDFIS_CYLL] = pCmdFis[AHCI_CMDFIS_CYLL]; + d2hFis[AHCI_CMDFIS_CYLH] = pCmdFis[AHCI_CMDFIS_CYLH]; + d2hFis[AHCI_CMDFIS_HEAD] = pCmdFis[AHCI_CMDFIS_HEAD]; + d2hFis[AHCI_CMDFIS_SECTNEXP] = pCmdFis[AHCI_CMDFIS_SECTNEXP]; + d2hFis[AHCI_CMDFIS_CYLLEXP] = pCmdFis[AHCI_CMDFIS_CYLLEXP]; + d2hFis[AHCI_CMDFIS_CYLHEXP] = pCmdFis[AHCI_CMDFIS_CYLHEXP]; + d2hFis[AHCI_CMDFIS_SECTC] = pCmdFis[AHCI_CMDFIS_SECTC]; + d2hFis[AHCI_CMDFIS_SECTCEXP] = pCmdFis[AHCI_CMDFIS_SECTCEXP]; + + /* Update registers. */ + pAhciPort->regTFD = (pCmdFis[AHCI_CMDFIS_ERR] << 8) | pCmdFis[AHCI_CMDFIS_STS]; + + ahciPostFisIntoMemory(pDevIns, pAhciPort, AHCI_CMDFIS_TYPE_D2H, d2hFis); + + if (pCmdFis[AHCI_CMDFIS_STS] & ATA_STAT_ERR) + { + /* Error bit is set. */ + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_TFES); + if (pAhciPort->regIE & AHCI_PORT_IE_TFEE) + fAssertIntr = true; + /* + * Don't mark the command slot as completed because the guest + * needs it to identify the failed command. + */ + } + else if (fInterrupt) + { + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_DHRS); + /* Check if we should assert an interrupt */ + if (pAhciPort->regIE & AHCI_PORT_IE_DHRE) + fAssertIntr = true; + + /* Mark command as completed. */ + ASMAtomicOrU32(&pAhciPort->u32TasksFinished, RT_BIT_32(uTag)); + } + + if (fAssertIntr) + { + int rc = ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + AssertRC(rc); + } + } +} + +/** + * Build a SDB Fis and post it into the memory area of the guest. + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The port for which the SDB Fis is send, shared bits. + * @param pAhciPortR3 The port for which the SDB Fis is send, ring-3 bits. + * @param uFinishedTasks Bitmask of finished tasks. + * @param fInterrupt If an interrupt should be asserted. + */ +static void ahciSendSDBFis(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, + uint32_t uFinishedTasks, bool fInterrupt) +{ + uint32_t sdbFis[2]; + bool fAssertIntr = false; + PAHCIREQ pTaskErr = ASMAtomicReadPtrT(&pAhciPortR3->pTaskErr, PAHCIREQ); + + ahciLog(("%s: Building SDB FIS\n", __FUNCTION__)); + + if (pAhciPort->regCMD & AHCI_PORT_CMD_FRE) + { + memset(&sdbFis[0], 0, sizeof(sdbFis)); + sdbFis[0] = AHCI_CMDFIS_TYPE_SETDEVBITS; + sdbFis[0] |= (fInterrupt ? (1 << 14) : 0); + if (RT_UNLIKELY(pTaskErr)) + { + sdbFis[0] = pTaskErr->cmdFis[AHCI_CMDFIS_ERR]; + sdbFis[0] |= (pTaskErr->cmdFis[AHCI_CMDFIS_STS] & 0x77) << 16; /* Some bits are marked as reserved and thus are masked out. */ + + /* Update registers. */ + pAhciPort->regTFD = (pTaskErr->cmdFis[AHCI_CMDFIS_ERR] << 8) | pTaskErr->cmdFis[AHCI_CMDFIS_STS]; + } + else + { + sdbFis[0] = 0; + sdbFis[0] |= (ATA_STAT_READY | ATA_STAT_SEEK) << 16; + pAhciPort->regTFD = ATA_STAT_READY | ATA_STAT_SEEK; + } + + sdbFis[1] = pAhciPort->u32QueuedTasksFinished | uFinishedTasks; + + ahciPostFisIntoMemory(pDevIns, pAhciPort, AHCI_CMDFIS_TYPE_SETDEVBITS, (uint8_t *)sdbFis); + + if (RT_UNLIKELY(pTaskErr)) + { + /* Error bit is set. */ + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_TFES); + if (pAhciPort->regIE & AHCI_PORT_IE_TFEE) + fAssertIntr = true; + } + + if (fInterrupt) + { + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_SDBS); + /* Check if we should assert an interrupt */ + if (pAhciPort->regIE & AHCI_PORT_IE_SDBE) + fAssertIntr = true; + } + + ASMAtomicOrU32(&pAhciPort->u32QueuedTasksFinished, uFinishedTasks); + + if (fAssertIntr) + { + int rc = ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + AssertRC(rc); + } + } +} + +static uint32_t ahciGetNSectors(uint8_t *pCmdFis, bool fLBA48) +{ + /* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */ + if (fLBA48) + { + if (!pCmdFis[AHCI_CMDFIS_SECTC] && !pCmdFis[AHCI_CMDFIS_SECTCEXP]) + return 65536; + else + return pCmdFis[AHCI_CMDFIS_SECTCEXP] << 8 | pCmdFis[AHCI_CMDFIS_SECTC]; + } + else + { + if (!pCmdFis[AHCI_CMDFIS_SECTC]) + return 256; + else + return pCmdFis[AHCI_CMDFIS_SECTC]; + } +} + +static uint64_t ahciGetSector(PAHCIPORT pAhciPort, uint8_t *pCmdFis, bool fLBA48) +{ + uint64_t iLBA; + if (pCmdFis[AHCI_CMDFIS_HEAD] & 0x40) + { + /* any LBA variant */ + if (fLBA48) + { + /* LBA48 */ + iLBA = ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLHEXP] << 40) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLLEXP] << 32) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_SECTNEXP] << 24) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLH] << 16) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLL] << 8) | + pCmdFis[AHCI_CMDFIS_SECTN]; + } + else + { + /* LBA */ + iLBA = ((pCmdFis[AHCI_CMDFIS_HEAD] & 0x0f) << 24) | (pCmdFis[AHCI_CMDFIS_CYLH] << 16) | + (pCmdFis[AHCI_CMDFIS_CYLL] << 8) | pCmdFis[AHCI_CMDFIS_SECTN]; + } + } + else + { + /* CHS */ + iLBA = ((pCmdFis[AHCI_CMDFIS_CYLH] << 8) | pCmdFis[AHCI_CMDFIS_CYLL]) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors + + (pCmdFis[AHCI_CMDFIS_HEAD] & 0x0f) * pAhciPort->PCHSGeometry.cSectors + + (pCmdFis[AHCI_CMDFIS_SECTN] - 1); + } + return iLBA; +} + +static uint64_t ahciGetSectorQueued(uint8_t *pCmdFis) +{ + uint64_t uLBA; + + uLBA = ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLHEXP] << 40) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLLEXP] << 32) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_SECTNEXP] << 24) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLH] << 16) | + ((uint64_t)pCmdFis[AHCI_CMDFIS_CYLL] << 8) | + pCmdFis[AHCI_CMDFIS_SECTN]; + + return uLBA; +} + +DECLINLINE(uint32_t) ahciGetNSectorsQueued(uint8_t *pCmdFis) +{ + if (!pCmdFis[AHCI_CMDFIS_FETEXP] && !pCmdFis[AHCI_CMDFIS_FET]) + return 65536; + else + return pCmdFis[AHCI_CMDFIS_FETEXP] << 8 | pCmdFis[AHCI_CMDFIS_FET]; +} + +/** + * Copy from guest to host memory worker. + * + * @copydoc FNAHCIR3MEMCOPYCALLBACK + */ +static DECLCALLBACK(void) ahciR3CopyBufferFromGuestWorker(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PRTSGBUF pSgBuf, + size_t cbCopy, size_t *pcbSkip) +{ + size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip); + cbCopy -= cbSkipped; + GCPhys += cbSkipped; + *pcbSkip -= cbSkipped; + + while (cbCopy) + { + size_t cbSeg = cbCopy; + void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg); + + AssertPtr(pvSeg); + Log5Func(("%RGp LB %#zx\n", GCPhys, cbSeg)); + PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pvSeg, cbSeg); + Log7Func(("%.*Rhxd\n", cbSeg, pvSeg)); + GCPhys += cbSeg; + cbCopy -= cbSeg; + } +} + +/** + * Copy from host to guest memory worker. + * + * @copydoc FNAHCIR3MEMCOPYCALLBACK + */ +static DECLCALLBACK(void) ahciR3CopyBufferToGuestWorker(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PRTSGBUF pSgBuf, + size_t cbCopy, size_t *pcbSkip) +{ + size_t cbSkipped = RT_MIN(cbCopy, *pcbSkip); + cbCopy -= cbSkipped; + GCPhys += cbSkipped; + *pcbSkip -= cbSkipped; + + while (cbCopy) + { + size_t cbSeg = cbCopy; + void *pvSeg = RTSgBufGetNextSegment(pSgBuf, &cbSeg); + + AssertPtr(pvSeg); + Log5Func(("%RGp LB %#zx\n", GCPhys, cbSeg)); + Log6Func(("%.*Rhxd\n", cbSeg, pvSeg)); + PDMDevHlpPCIPhysWriteUser(pDevIns, GCPhys, pvSeg, cbSeg); + GCPhys += cbSeg; + cbCopy -= cbSeg; + } +} + +/** + * Walks the PRDTL list copying data between the guest and host memory buffers. + * + * @returns Amount of bytes copied. + * @param pDevIns The device instance. + * @param pAhciReq AHCI request structure. + * @param pfnCopyWorker The copy method to apply for each guest buffer. + * @param pSgBuf The host S/G buffer. + * @param cbSkip How many bytes to skip in advance before starting to copy. + * @param cbCopy How many bytes to copy. + */ +static size_t ahciR3PrdtlWalk(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, + PFNAHCIR3MEMCOPYCALLBACK pfnCopyWorker, + PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy) +{ + RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl; + unsigned cPrdtlEntries = pAhciReq->cPrdtlEntries; + size_t cbCopied = 0; + + /* + * Add the amount to skip to the host buffer size to avoid a + * few conditionals later on. + */ + cbCopy += cbSkip; + + AssertMsgReturn(cPrdtlEntries > 0, ("Copying 0 bytes is not possible\n"), 0); + + do + { + SGLEntry aPrdtlEntries[32]; + uint32_t cPrdtlEntriesRead = cPrdtlEntries < RT_ELEMENTS(aPrdtlEntries) + ? cPrdtlEntries + : RT_ELEMENTS(aPrdtlEntries); + + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysPrdtl, &aPrdtlEntries[0], + cPrdtlEntriesRead * sizeof(SGLEntry)); + + for (uint32_t i = 0; (i < cPrdtlEntriesRead) && cbCopy; i++) + { + RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aPrdtlEntries[i].u32DBAUp, aPrdtlEntries[i].u32DBA); + uint32_t cbThisCopy = (aPrdtlEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1; + + cbThisCopy = (uint32_t)RT_MIN(cbThisCopy, cbCopy); + + /* Copy into SG entry. */ + pfnCopyWorker(pDevIns, GCPhysAddrDataBase, pSgBuf, cbThisCopy, &cbSkip); + + cbCopy -= cbThisCopy; + cbCopied += cbThisCopy; + } + + GCPhysPrdtl += cPrdtlEntriesRead * sizeof(SGLEntry); + cPrdtlEntries -= cPrdtlEntriesRead; + } while (cPrdtlEntries && cbCopy); + + if (cbCopied < cbCopy) + pAhciReq->fFlags |= AHCI_REQ_OVERFLOW; + + return cbCopied; +} + +/** + * Copies a data buffer into the S/G buffer set up by the guest. + * + * @returns Amount of bytes copied to the PRDTL. + * @param pDevIns The device instance. + * @param pAhciReq AHCI request structure. + * @param pSgBuf The S/G buffer to copy from. + * @param cbSkip How many bytes to skip in advance before starting to copy. + * @param cbCopy How many bytes to copy. + */ +static size_t ahciR3CopySgBufToPrdtl(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy) +{ + return ahciR3PrdtlWalk(pDevIns, pAhciReq, ahciR3CopyBufferToGuestWorker, pSgBuf, cbSkip, cbCopy); +} + +/** + * Copies the S/G buffer into a data buffer. + * + * @returns Amount of bytes copied from the PRDTL. + * @param pDevIns The device instance. + * @param pAhciReq AHCI request structure. + * @param pSgBuf The S/G buffer to copy into. + * @param cbSkip How many bytes to skip in advance before starting to copy. + * @param cbCopy How many bytes to copy. + */ +static size_t ahciR3CopySgBufFromPrdtl(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy) +{ + return ahciR3PrdtlWalk(pDevIns, pAhciReq, ahciR3CopyBufferFromGuestWorker, pSgBuf, cbSkip, cbCopy); +} + +/** + * Copy a simple memory buffer to the guest memory buffer. + * + * @returns Amount of bytes copied from the PRDTL. + * @param pDevIns The device instance. + * @param pAhciReq AHCI request structure. + * @param pvSrc The buffer to copy from. + * @param cbSrc How many bytes to copy. + * @param cbSkip How many bytes to skip initially. + */ +static size_t ahciR3CopyBufferToPrdtl(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, const void *pvSrc, size_t cbSrc, size_t cbSkip) +{ + RTSGSEG Seg; + RTSGBUF SgBuf; + Seg.pvSeg = (void *)pvSrc; + Seg.cbSeg = cbSrc; + RTSgBufInit(&SgBuf, &Seg, 1); + return ahciR3CopySgBufToPrdtl(pDevIns, pAhciReq, &SgBuf, cbSkip, cbSrc); +} + +/** + * Calculates the size of the guest buffer described by the PRDT. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pAhciReq AHCI request structure. + * @param pcbPrdt Where to store the size of the guest buffer. + */ +static int ahciR3PrdtQuerySize(PPDMDEVINS pDevIns, PAHCIREQ pAhciReq, size_t *pcbPrdt) +{ + RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl; + unsigned cPrdtlEntries = pAhciReq->cPrdtlEntries; + size_t cbPrdt = 0; + + do + { + SGLEntry aPrdtlEntries[32]; + uint32_t const cPrdtlEntriesRead = RT_MIN(cPrdtlEntries, RT_ELEMENTS(aPrdtlEntries)); + + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysPrdtl, &aPrdtlEntries[0], cPrdtlEntriesRead * sizeof(SGLEntry)); + + for (uint32_t i = 0; i < cPrdtlEntriesRead; i++) + cbPrdt += (aPrdtlEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1; + + GCPhysPrdtl += cPrdtlEntriesRead * sizeof(SGLEntry); + cPrdtlEntries -= cPrdtlEntriesRead; + } while (cPrdtlEntries); + + *pcbPrdt = cbPrdt; + return VINF_SUCCESS; +} + +/** + * Cancels all active tasks on the port. + * + * @returns Whether all active tasks were canceled. + * @param pAhciPortR3 The AHCI port, ring-3 bits. + */ +static bool ahciR3CancelActiveTasks(PAHCIPORTR3 pAhciPortR3) +{ + if (pAhciPortR3->pDrvMediaEx) + { + int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqCancelAll(pAhciPortR3->pDrvMediaEx); + AssertRC(rc); + } + return true; /* always true for now because tasks don't use guest memory as the buffer which makes canceling a task impossible. */ +} + +/** + * Creates the array of ranges to trim. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pAhciPort AHCI port state, shared bits. + * @param pAhciReq The request handling the TRIM request. + * @param idxRangeStart Index of the first range to start copying. + * @param paRanges Where to store the ranges. + * @param cRanges Number of ranges fitting into the array. + * @param pcRanges Where to store the amount of ranges actually copied on success. + */ +static int ahciTrimRangesCreate(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, PAHCIREQ pAhciReq, uint32_t idxRangeStart, + PRTRANGE paRanges, uint32_t cRanges, uint32_t *pcRanges) +{ + SGLEntry aPrdtlEntries[32]; + uint64_t aRanges[64]; + uint32_t cPrdtlEntries = pAhciReq->cPrdtlEntries; + RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl; + int rc = VERR_PDM_MEDIAEX_IOBUF_OVERFLOW; + uint32_t idxRange = 0; + + LogFlowFunc(("pAhciPort=%#p pAhciReq=%#p\n", pAhciPort, pAhciReq)); + + AssertMsgReturn(pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD, ("This is not a trim request\n"), VERR_INVALID_PARAMETER); + + if (!cPrdtlEntries) + pAhciReq->fFlags |= AHCI_REQ_OVERFLOW; + + /* Convert the ranges from ATA to our format. */ + while ( cPrdtlEntries + && idxRange < cRanges) + { + uint32_t cPrdtlEntriesRead = RT_MIN(cPrdtlEntries, RT_ELEMENTS(aPrdtlEntries)); + + rc = VINF_SUCCESS; + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysPrdtl, &aPrdtlEntries[0], cPrdtlEntriesRead * sizeof(SGLEntry)); + + for (uint32_t i = 0; i < cPrdtlEntriesRead && idxRange < cRanges; i++) + { + RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aPrdtlEntries[i].u32DBAUp, aPrdtlEntries[i].u32DBA); + uint32_t cbThisCopy = (aPrdtlEntries[i].u32DescInf & SGLENTRY_DESCINF_DBC) + 1; + + cbThisCopy = RT_MIN(cbThisCopy, sizeof(aRanges)); + + /* Copy into buffer. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysAddrDataBase, aRanges, cbThisCopy); + + for (unsigned idxRangeSrc = 0; idxRangeSrc < RT_ELEMENTS(aRanges) && idxRange < cRanges; idxRangeSrc++) + { + /* Skip range if told to do so. */ + if (!idxRangeStart) + { + aRanges[idxRangeSrc] = RT_H2LE_U64(aRanges[idxRangeSrc]); + if (AHCI_RANGE_LENGTH_GET(aRanges[idxRangeSrc]) != 0) + { + paRanges[idxRange].offStart = (aRanges[idxRangeSrc] & AHCI_RANGE_LBA_MASK) * pAhciPort->cbSector; + paRanges[idxRange].cbRange = AHCI_RANGE_LENGTH_GET(aRanges[idxRangeSrc]) * pAhciPort->cbSector; + idxRange++; + } + else + break; + } + else + idxRangeStart--; + } + } + + GCPhysPrdtl += cPrdtlEntriesRead * sizeof(SGLEntry); + cPrdtlEntries -= cPrdtlEntriesRead; + } + + *pcRanges = idxRange; + + LogFlowFunc(("returns rc=%Rrc\n", rc)); + return rc; +} + +/** + * Allocates a new AHCI request. + * + * @returns A new AHCI request structure or NULL if out of memory. + * @param pAhciPortR3 The AHCI port, ring-3 bits. + * @param uTag The tag to assign. + */ +static PAHCIREQ ahciR3ReqAlloc(PAHCIPORTR3 pAhciPortR3, uint32_t uTag) +{ + PAHCIREQ pAhciReq = NULL; + PDMMEDIAEXIOREQ hIoReq = NULL; + + int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqAlloc(pAhciPortR3->pDrvMediaEx, &hIoReq, (void **)&pAhciReq, + uTag, PDMIMEDIAEX_F_SUSPEND_ON_RECOVERABLE_ERR); + if (RT_SUCCESS(rc)) + { + pAhciReq->hIoReq = hIoReq; + pAhciReq->fMapped = false; + } + else + pAhciReq = NULL; + return pAhciReq; +} + +/** + * Frees a given AHCI request structure. + * + * @param pAhciPortR3 The AHCI port, ring-3 bits. + * @param pAhciReq The request to free. + */ +static void ahciR3ReqFree(PAHCIPORTR3 pAhciPortR3, PAHCIREQ pAhciReq) +{ + if ( pAhciReq + && !(pAhciReq->fFlags & AHCI_REQ_IS_ON_STACK)) + { + int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqFree(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq); + AssertRC(rc); + } +} + +/** + * Complete a data transfer task by freeing all occupied resources + * and notifying the guest. + * + * @returns Flag whether the given request was canceled inbetween; + * + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pThisCC The ring-3 AHCI state. + * @param pAhciPort Pointer to the port where to request completed, shared bits. + * @param pAhciPortR3 Pointer to the port where to request completed, ring-3 bits. + * @param pAhciReq Pointer to the task which finished. + * @param rcReq IPRT status code of the completed request. + */ +static bool ahciR3TransferComplete(PPDMDEVINS pDevIns, PAHCI pThis, PAHCICC pThisCC, + PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, PAHCIREQ pAhciReq, int rcReq) +{ + bool fCanceled = false; + + LogFlowFunc(("pAhciPort=%p pAhciReq=%p rcReq=%d\n", + pAhciPort, pAhciReq, rcReq)); + + VBOXDD_AHCI_REQ_COMPLETED(pAhciReq, rcReq, pAhciReq->uOffset, pAhciReq->cbTransfer); + + if (pAhciReq->fMapped) + PDMDevHlpPhysReleasePageMappingLock(pDevIns, &pAhciReq->PgLck); + + if (rcReq != VERR_PDM_MEDIAEX_IOREQ_CANCELED) + { + if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ) + pAhciPort->Led.Actual.s.fReading = 0; + else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_WRITE) + pAhciPort->Led.Actual.s.fWriting = 0; + else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD) + pAhciPort->Led.Actual.s.fWriting = 0; + else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_SCSI) + { + pAhciPort->Led.Actual.s.fWriting = 0; + pAhciPort->Led.Actual.s.fReading = 0; + } + + if (RT_FAILURE(rcReq)) + { + /* Log the error. */ + if (pAhciPort->cErrors++ < MAX_LOG_REL_ERRORS) + { + if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH) + LogRel(("AHCI#%uP%u: Flush returned rc=%Rrc\n", + pDevIns->iInstance, pAhciPort->iLUN, rcReq)); + else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD) + LogRel(("AHCI#%uP%u: Trim returned rc=%Rrc\n", + pDevIns->iInstance, pAhciPort->iLUN, rcReq)); + else + LogRel(("AHCI#%uP%u: %s at offset %llu (%zu bytes left) returned rc=%Rrc\n", + pDevIns->iInstance, pAhciPort->iLUN, + pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ + ? "Read" + : "Write", + pAhciReq->uOffset, + pAhciReq->cbTransfer, rcReq)); + } + + ahciReqSetStatus(pAhciReq, ID_ERR, ATA_STAT_READY | ATA_STAT_ERR); + /* + * We have to duplicate the request here as the underlying I/O + * request will be freed later. + */ + PAHCIREQ pReqDup = (PAHCIREQ)RTMemDup(pAhciReq, sizeof(AHCIREQ)); + if ( pReqDup + && !ASMAtomicCmpXchgPtr(&pAhciPortR3->pTaskErr, pReqDup, NULL)) + RTMemFree(pReqDup); + } + else + { + /* Status will be set already for non I/O requests. */ + if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_SCSI) + { + if (pAhciReq->u8ScsiSts == SCSI_STATUS_OK) + { + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] & ~7) + | ((pAhciReq->fFlags & AHCI_REQ_XFER_2_HOST) ? ATAPI_INT_REASON_IO : 0) + | (!pAhciReq->cbTransfer ? ATAPI_INT_REASON_CD : 0); + } + else + { + ahciReqSetStatus(pAhciReq, pAhciPort->abATAPISense[2] << 4, ATA_STAT_READY | ATA_STAT_ERR); + pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] & ~7) | + ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD; + pAhciReq->cbTransfer = 0; + LogFlowFunc(("SCSI request completed with %u status\n", pAhciReq->u8ScsiSts)); + } + } + else if (pAhciReq->enmType != PDMMEDIAEXIOREQTYPE_INVALID) + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + + /* Write updated command header into memory of the guest. */ + uint32_t u32PRDBC = 0; + if (pAhciReq->enmType != PDMMEDIAEXIOREQTYPE_INVALID) + { + size_t cbXfer = 0; + int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqQueryXferSize(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq, &cbXfer); + AssertRC(rc); + u32PRDBC = (uint32_t)RT_MIN(cbXfer, pAhciReq->cbTransfer); + } + else + u32PRDBC = (uint32_t)pAhciReq->cbTransfer; + + PDMDevHlpPCIPhysWriteMeta(pDevIns, pAhciReq->GCPhysCmdHdrAddr + RT_UOFFSETOF(CmdHdr, u32PRDBC), + &u32PRDBC, sizeof(u32PRDBC)); + + if (pAhciReq->fFlags & AHCI_REQ_OVERFLOW) + { + /* + * The guest tried to transfer more data than there is space in the buffer. + * Terminate task and set the overflow bit. + */ + /* Notify the guest. */ + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_OFS); + if (pAhciPort->regIE & AHCI_PORT_IE_OFE) + ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + } + } + + /* + * Make a copy of the required data now and free the request. Otherwise the guest + * might issue a new request with the same tag and we run into a conflict when allocating + * a new request with the same tag later on. + */ + uint32_t fFlags = pAhciReq->fFlags; + uint32_t uTag = pAhciReq->uTag; + size_t cbTransfer = pAhciReq->cbTransfer; + bool fRead = pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ; + uint8_t cmdFis[AHCI_CMDFIS_TYPE_H2D_SIZE]; + memcpy(&cmdFis[0], &pAhciReq->cmdFis[0], sizeof(cmdFis)); + + ahciR3ReqFree(pAhciPortR3, pAhciReq); + + /* Post a PIO setup FIS first if this is a PIO command which transfers data. */ + if (fFlags & AHCI_REQ_PIO_DATA) + ahciSendPioSetupFis(pDevIns, pThis, pAhciPort, cbTransfer, &cmdFis[0], fRead, false /* fInterrupt */); + + if (fFlags & AHCI_REQ_CLEAR_SACT) + { + if (RT_SUCCESS(rcReq) && !ASMAtomicReadPtrT(&pAhciPortR3->pTaskErr, PAHCIREQ)) + ASMAtomicOrU32(&pAhciPort->u32QueuedTasksFinished, RT_BIT_32(uTag)); + } + + if (fFlags & AHCI_REQ_IS_QUEUED) + { + /* + * Always raise an interrupt after task completion; delaying + * this (interrupt coalescing) increases latency and has a significant + * impact on performance (see @bugref{5071}) + */ + ahciSendSDBFis(pDevIns, pThis, pAhciPort, pAhciPortR3, 0, true); + } + else + ahciSendD2HFis(pDevIns, pThis, pAhciPort, uTag, &cmdFis[0], true); + } + else + { + /* + * Task was canceled, do the cleanup but DO NOT access the guest memory! + * The guest might use it for other things now because it doesn't know about that task anymore. + */ + fCanceled = true; + + /* Leave a log message about the canceled request. */ + if (pAhciPort->cErrors++ < MAX_LOG_REL_ERRORS) + { + if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_FLUSH) + LogRel(("AHCI#%uP%u: Canceled flush returned rc=%Rrc\n", + pDevIns->iInstance, pAhciPort->iLUN, rcReq)); + else if (pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_DISCARD) + LogRel(("AHCI#%uP%u: Canceled trim returned rc=%Rrc\n", + pDevIns->iInstance,pAhciPort->iLUN, rcReq)); + else + LogRel(("AHCI#%uP%u: Canceled %s at offset %llu (%zu bytes left) returned rc=%Rrc\n", + pDevIns->iInstance, pAhciPort->iLUN, + pAhciReq->enmType == PDMMEDIAEXIOREQTYPE_READ + ? "read" + : "write", + pAhciReq->uOffset, + pAhciReq->cbTransfer, rcReq)); + } + + ahciR3ReqFree(pAhciPortR3, pAhciReq); + } + + /* + * Decrement the active task counter as the last step or we might run into a + * hang during power off otherwise (see @bugref{7859}). + * Before it could happen that we signal PDM that we are done while we still have to + * copy the data to the guest but EMT might be busy destroying the driver chains + * below us while we have to delegate copying data to EMT instead of doing it + * on this thread. + */ + ASMAtomicDecU32(&pAhciPort->cTasksActive); + + if (pAhciPort->cTasksActive == 0 && pThisCC->fSignalIdle) + PDMDevHlpAsyncNotificationCompleted(pDevIns); + + return fCanceled; +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyFromBuf} + */ +static DECLCALLBACK(int) ahciR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, uint32_t offDst, PRTSGBUF pSgBuf, + size_t cbCopy) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort); + int rc = VINF_SUCCESS; + PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc; + RT_NOREF(hIoReq); + + ahciR3CopySgBufToPrdtl(pAhciPortR3->pDevIns, pIoReq, pSgBuf, offDst, cbCopy); + + if (pIoReq->fFlags & AHCI_REQ_OVERFLOW) + rc = VERR_PDM_MEDIAEX_IOBUF_OVERFLOW; + + return rc; +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCopyToBuf} + */ +static DECLCALLBACK(int) ahciR3IoReqCopyToBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, uint32_t offSrc, PRTSGBUF pSgBuf, + size_t cbCopy) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort); + int rc = VINF_SUCCESS; + PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc; + RT_NOREF(hIoReq); + + ahciR3CopySgBufFromPrdtl(pAhciPortR3->pDevIns, pIoReq, pSgBuf, offSrc, cbCopy); + if (pIoReq->fFlags & AHCI_REQ_OVERFLOW) + rc = VERR_PDM_MEDIAEX_IOBUF_UNDERRUN; + + return rc; +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqQueryBuf} + */ +static DECLCALLBACK(int) ahciR3IoReqQueryBuf(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, void **ppvBuf, size_t *pcbBuf) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort); + PPDMDEVINS pDevIns = pAhciPortR3->pDevIns; + PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc; + int rc = VERR_NOT_SUPPORTED; + RT_NOREF(hIoReq); + + /* Only allow single 4KB page aligned buffers at the moment. */ + if ( pIoReq->cPrdtlEntries == 1 + && pIoReq->cbTransfer == _4K) + { + RTGCPHYS GCPhysPrdt = pIoReq->GCPhysPrdtl; + SGLEntry PrdtEntry; + + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysPrdt, &PrdtEntry, sizeof(SGLEntry)); + + RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(PrdtEntry.u32DBAUp, PrdtEntry.u32DBA); + uint32_t cbData = (PrdtEntry.u32DescInf & SGLENTRY_DESCINF_DBC) + 1; + + if ( cbData >= _4K + && !(GCPhysAddrDataBase & (_4K - 1))) + { + rc = PDMDevHlpPCIPhysGCPhys2CCPtr(pDevIns, NULL /* pPciDev */, GCPhysAddrDataBase, 0, ppvBuf, &pIoReq->PgLck); + if (RT_SUCCESS(rc)) + { + pIoReq->fMapped = true; + *pcbBuf = cbData; + } + else + rc = VERR_NOT_SUPPORTED; + } + } + + return rc; +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqQueryDiscardRanges} + */ +static DECLCALLBACK(int) ahciR3IoReqQueryDiscardRanges(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, uint32_t idxRangeStart, + uint32_t cRanges, PRTRANGE paRanges, + uint32_t *pcRanges) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort); + PPDMDEVINS pDevIns = pAhciPortR3->pDevIns; + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN); + PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc; + RT_NOREF(hIoReq); + + return ahciTrimRangesCreate(pDevIns, pAhciPort, pIoReq, idxRangeStart, paRanges, cRanges, pcRanges); +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqCompleteNotify} + */ +static DECLCALLBACK(int) ahciR3IoReqCompleteNotify(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, int rcReq) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort); + PPDMDEVINS pDevIns = pAhciPortR3->pDevIns; + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN); + PAHCIREQ pIoReq = (PAHCIREQ)pvIoReqAlloc; + RT_NOREF(hIoReq); + + ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, pIoReq, rcReq); + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnIoReqStateChanged} + */ +static DECLCALLBACK(void) ahciR3IoReqStateChanged(PPDMIMEDIAEXPORT pInterface, PDMMEDIAEXIOREQ hIoReq, + void *pvIoReqAlloc, PDMMEDIAEXIOREQSTATE enmState) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort); + PPDMDEVINS pDevIns = pAhciPortR3->pDevIns; + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN); + RT_NOREF(hIoReq, pvIoReqAlloc); + + switch (enmState) + { + case PDMMEDIAEXIOREQSTATE_SUSPENDED: + { + /* Make sure the request is not accounted for so the VM can suspend successfully. */ + uint32_t cTasksActive = ASMAtomicDecU32(&pAhciPort->cTasksActive); + if (!cTasksActive && pThisCC->fSignalIdle) + PDMDevHlpAsyncNotificationCompleted(pDevIns); + break; + } + case PDMMEDIAEXIOREQSTATE_ACTIVE: + /* Make sure the request is accounted for so the VM suspends only when the request is complete. */ + ASMAtomicIncU32(&pAhciPort->cTasksActive); + break; + default: + AssertMsgFailed(("Invalid request state given %u\n", enmState)); + } +} + +/** + * @interface_method_impl{PDMIMEDIAEXPORT,pfnMediumEjected} + */ +static DECLCALLBACK(void) ahciR3MediumEjected(PPDMIMEDIAEXPORT pInterface) +{ + PAHCIPORTR3 pAhciPortR3 = RT_FROM_MEMBER(pInterface, AHCIPORTR3, IMediaExPort); + PPDMDEVINS pDevIns = pAhciPortR3->pDevIns; + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN); + + if (pThisCC->pMediaNotify) + { + int rc = PDMDevHlpVMReqCallNoWait(pDevIns, VMCPUID_ANY, + (PFNRT)pThisCC->pMediaNotify->pfnEjected, 2, + pThisCC->pMediaNotify, pAhciPort->iLUN); + AssertRC(rc); + } +} + +/** + * Process an non read/write ATA command. + * + * @returns The direction of the data transfer + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The AHCI port of the request, shared bits. + * @param pAhciPortR3 The AHCI port of the request, ring-3 bits. + * @param pAhciReq The AHCI request state. + * @param pCmdFis Pointer to the command FIS. + */ +static PDMMEDIAEXIOREQTYPE ahciProcessCmd(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, + PAHCIREQ pAhciReq, uint8_t *pCmdFis) +{ + PDMMEDIAEXIOREQTYPE enmType = PDMMEDIAEXIOREQTYPE_INVALID; + bool fLBA48 = false; + + AssertMsg(pCmdFis[AHCI_CMDFIS_TYPE] == AHCI_CMDFIS_TYPE_H2D, ("FIS is not a host to device Fis!!\n")); + + pAhciReq->cbTransfer = 0; + + switch (pCmdFis[AHCI_CMDFIS_CMD]) + { + case ATA_IDENTIFY_DEVICE: + { + if (pAhciPortR3->pDrvMedia && !pAhciPort->fATAPI) + { + uint16_t u16Temp[256]; + + /* Fill the buffer. */ + ahciIdentifySS(pThis, pAhciPort, pAhciPortR3, u16Temp); + + /* Copy the buffer. */ + size_t cbCopied = ahciR3CopyBufferToPrdtl(pDevIns, pAhciReq, &u16Temp[0], sizeof(u16Temp), 0 /* cbSkip */); + + pAhciReq->fFlags |= AHCI_REQ_PIO_DATA; + pAhciReq->cbTransfer = cbCopied; + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + } + else + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_SEEK | ATA_STAT_ERR); + break; + } + case ATA_READ_NATIVE_MAX_ADDRESS_EXT: + case ATA_READ_NATIVE_MAX_ADDRESS: + break; + case ATA_SET_FEATURES: + { + switch (pCmdFis[AHCI_CMDFIS_FET]) + { + case 0x02: /* write cache enable */ + case 0xaa: /* read look-ahead enable */ + case 0x55: /* read look-ahead disable */ + case 0xcc: /* reverting to power-on defaults enable */ + case 0x66: /* reverting to power-on defaults disable */ + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + break; + case 0x82: /* write cache disable */ + enmType = PDMMEDIAEXIOREQTYPE_FLUSH; + break; + case 0x03: + { + /* set transfer mode */ + Log2(("%s: transfer mode %#04x\n", __FUNCTION__, pCmdFis[AHCI_CMDFIS_SECTC])); + switch (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8) + { + case 0x00: /* PIO default */ + case 0x08: /* PIO mode */ + break; + case ATA_MODE_MDMA: /* MDMA mode */ + pAhciPort->uATATransferMode = (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8) | RT_MIN(pCmdFis[AHCI_CMDFIS_SECTC] & 0x07, ATA_MDMA_MODE_MAX); + break; + case ATA_MODE_UDMA: /* UDMA mode */ + pAhciPort->uATATransferMode = (pCmdFis[AHCI_CMDFIS_SECTC] & 0xf8) | RT_MIN(pCmdFis[AHCI_CMDFIS_SECTC] & 0x07, ATA_UDMA_MODE_MAX); + break; + } + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + break; + } + default: + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + } + break; + } + case ATA_DEVICE_RESET: + { + if (!pAhciPort->fATAPI) + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + else + { + /* Reset the device. */ + ahciDeviceReset(pDevIns, pThis, pAhciPort, pAhciReq); + } + break; + } + case ATA_FLUSH_CACHE_EXT: + case ATA_FLUSH_CACHE: + enmType = PDMMEDIAEXIOREQTYPE_FLUSH; + break; + case ATA_PACKET: + if (!pAhciPort->fATAPI) + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + else + enmType = PDMMEDIAEXIOREQTYPE_SCSI; + break; + case ATA_IDENTIFY_PACKET_DEVICE: + if (!pAhciPort->fATAPI) + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + else + { + size_t cbData; + ahciR3AtapiIdentify(pDevIns, pAhciReq, pAhciPort, 512, &cbData); + + pAhciReq->fFlags |= AHCI_REQ_PIO_DATA; + pAhciReq->cbTransfer = cbData; + pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciReq->cmdFis[AHCI_CMDFIS_SECTN] & ~7) + | ((pAhciReq->fFlags & AHCI_REQ_XFER_2_HOST) ? ATAPI_INT_REASON_IO : 0) + | (!pAhciReq->cbTransfer ? ATAPI_INT_REASON_CD : 0); + + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + } + break; + case ATA_SET_MULTIPLE_MODE: + if ( pCmdFis[AHCI_CMDFIS_SECTC] != 0 + && ( pCmdFis[AHCI_CMDFIS_SECTC] > ATA_MAX_MULT_SECTORS + || (pCmdFis[AHCI_CMDFIS_SECTC] & (pCmdFis[AHCI_CMDFIS_SECTC] - 1)) != 0)) + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + else + { + Log2(("%s: set multi sector count to %d\n", __FUNCTION__, pCmdFis[AHCI_CMDFIS_SECTC])); + pAhciPort->cMultSectors = pCmdFis[AHCI_CMDFIS_SECTC]; + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + } + break; + case ATA_STANDBY_IMMEDIATE: + break; /* Do nothing. */ + case ATA_CHECK_POWER_MODE: + pAhciReq->cmdFis[AHCI_CMDFIS_SECTC] = 0xff; /* drive active or idle */ + RT_FALL_THRU(); + case ATA_INITIALIZE_DEVICE_PARAMETERS: + case ATA_IDLE_IMMEDIATE: + case ATA_RECALIBRATE: + case ATA_NOP: + case ATA_READ_VERIFY_SECTORS_EXT: + case ATA_READ_VERIFY_SECTORS: + case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES: + case ATA_SLEEP: + ahciReqSetStatus(pAhciReq, 0, ATA_STAT_READY | ATA_STAT_SEEK); + break; + case ATA_READ_DMA_EXT: + fLBA48 = true; + RT_FALL_THRU(); + case ATA_READ_DMA: + { + pAhciReq->cbTransfer = ahciGetNSectors(pCmdFis, fLBA48) * pAhciPort->cbSector; + pAhciReq->uOffset = ahciGetSector(pAhciPort, pCmdFis, fLBA48) * pAhciPort->cbSector; + enmType = PDMMEDIAEXIOREQTYPE_READ; + break; + } + case ATA_WRITE_DMA_EXT: + fLBA48 = true; + RT_FALL_THRU(); + case ATA_WRITE_DMA: + { + pAhciReq->cbTransfer = ahciGetNSectors(pCmdFis, fLBA48) * pAhciPort->cbSector; + pAhciReq->uOffset = ahciGetSector(pAhciPort, pCmdFis, fLBA48) * pAhciPort->cbSector; + enmType = PDMMEDIAEXIOREQTYPE_WRITE; + break; + } + case ATA_READ_FPDMA_QUEUED: + { + pAhciReq->cbTransfer = ahciGetNSectorsQueued(pCmdFis) * pAhciPort->cbSector; + pAhciReq->uOffset = ahciGetSectorQueued(pCmdFis) * pAhciPort->cbSector; + pAhciReq->fFlags |= AHCI_REQ_IS_QUEUED; + enmType = PDMMEDIAEXIOREQTYPE_READ; + break; + } + case ATA_WRITE_FPDMA_QUEUED: + { + pAhciReq->cbTransfer = ahciGetNSectorsQueued(pCmdFis) * pAhciPort->cbSector; + pAhciReq->uOffset = ahciGetSectorQueued(pCmdFis) * pAhciPort->cbSector; + pAhciReq->fFlags |= AHCI_REQ_IS_QUEUED; + enmType = PDMMEDIAEXIOREQTYPE_WRITE; + break; + } + case ATA_READ_LOG_EXT: + { + size_t cbLogRead = ((pCmdFis[AHCI_CMDFIS_SECTCEXP] << 8) | pCmdFis[AHCI_CMDFIS_SECTC]) * 512; + unsigned offLogRead = ((pCmdFis[AHCI_CMDFIS_CYLLEXP] << 8) | pCmdFis[AHCI_CMDFIS_CYLL]) * 512; + unsigned iPage = pCmdFis[AHCI_CMDFIS_SECTN]; + + LogFlow(("Trying to read %zu bytes starting at offset %u from page %u\n", cbLogRead, offLogRead, iPage)); + + uint8_t aBuf[512]; + + memset(aBuf, 0, sizeof(aBuf)); + + if (offLogRead + cbLogRead <= sizeof(aBuf)) + { + switch (iPage) + { + case 0x10: + { + LogFlow(("Reading error page\n")); + PAHCIREQ pTaskErr = ASMAtomicXchgPtrT(&pAhciPortR3->pTaskErr, NULL, PAHCIREQ); + if (pTaskErr) + { + aBuf[0] = (pTaskErr->fFlags & AHCI_REQ_IS_QUEUED) ? pTaskErr->uTag : (1 << 7); + aBuf[2] = pTaskErr->cmdFis[AHCI_CMDFIS_STS]; + aBuf[3] = pTaskErr->cmdFis[AHCI_CMDFIS_ERR]; + aBuf[4] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTN]; + aBuf[5] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLL]; + aBuf[6] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLH]; + aBuf[7] = pTaskErr->cmdFis[AHCI_CMDFIS_HEAD]; + aBuf[8] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTNEXP]; + aBuf[9] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLLEXP]; + aBuf[10] = pTaskErr->cmdFis[AHCI_CMDFIS_CYLHEXP]; + aBuf[12] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTC]; + aBuf[13] = pTaskErr->cmdFis[AHCI_CMDFIS_SECTCEXP]; + + /* Calculate checksum */ + uint8_t uChkSum = 0; + for (unsigned i = 0; i < RT_ELEMENTS(aBuf)-1; i++) + uChkSum += aBuf[i]; + + aBuf[511] = (uint8_t)-(int8_t)uChkSum; + + /* Finally free the error task state structure because it is completely unused now. */ + RTMemFree(pTaskErr); + } + + /* + * Reading this log page results in an abort of all outstanding commands + * and clearing the SActive register and TaskFile register. + * + * See SATA2 1.2 spec chapter 4.2.3.4 + */ + bool fAbortedAll = ahciR3CancelActiveTasks(pAhciPortR3); + Assert(fAbortedAll); NOREF(fAbortedAll); + ahciSendSDBFis(pDevIns, pThis, pAhciPort, pAhciPortR3, UINT32_C(0xffffffff), true); + + break; + } + } + + /* Copy the buffer. */ + size_t cbCopied = ahciR3CopyBufferToPrdtl(pDevIns, pAhciReq, &aBuf[offLogRead], cbLogRead, 0 /* cbSkip */); + + pAhciReq->fFlags |= AHCI_REQ_PIO_DATA; + pAhciReq->cbTransfer = cbCopied; + } + + break; + } + case ATA_DATA_SET_MANAGEMENT: + { + if (pAhciPort->fTrimEnabled) + { + /* Check that the trim bit is set and all other bits are 0. */ + if ( !(pAhciReq->cmdFis[AHCI_CMDFIS_FET] & UINT16_C(0x01)) + || (pAhciReq->cmdFis[AHCI_CMDFIS_FET] & ~UINT16_C(0x1))) + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + else + enmType = PDMMEDIAEXIOREQTYPE_DISCARD; + break; + } + /* else: fall through and report error to the guest. */ + } + RT_FALL_THRU(); + /* All not implemented commands go below. */ + case ATA_SECURITY_FREEZE_LOCK: + case ATA_SMART: + case ATA_NV_CACHE: + case ATA_IDLE: + case ATA_TRUSTED_RECEIVE_DMA: /* Windows 8+ */ + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + break; + default: /* For debugging purposes. */ + AssertMsgFailed(("Unknown command issued (%#x)\n", pCmdFis[AHCI_CMDFIS_CMD])); + ahciReqSetStatus(pAhciReq, ABRT_ERR, ATA_STAT_READY | ATA_STAT_ERR); + } + + return enmType; +} + +/** + * Retrieve a command FIS from guest memory. + * + * @returns whether the H2D FIS was successfully read from the guest memory. + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The AHCI port of the request, shared bits. + * @param pAhciReq The state of the actual task. + */ +static bool ahciPortTaskGetCommandFis(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIREQ pAhciReq) +{ + AssertMsgReturn(pAhciPort->GCPhysAddrClb && pAhciPort->GCPhysAddrFb, + ("%s: GCPhysAddrClb and/or GCPhysAddrFb are 0\n", __FUNCTION__), + false); + + /* + * First we are reading the command header pointed to by regCLB. + * From this we get the address of the command table which we are reading too. + * We can process the Command FIS afterwards. + */ + CmdHdr cmdHdr; + pAhciReq->GCPhysCmdHdrAddr = pAhciPort->GCPhysAddrClb + pAhciReq->uTag * sizeof(CmdHdr); + LogFlow(("%s: PDMDevHlpPCIPhysReadMeta GCPhysAddrCmdLst=%RGp cbCmdHdr=%u\n", __FUNCTION__, + pAhciReq->GCPhysCmdHdrAddr, sizeof(CmdHdr))); + PDMDevHlpPCIPhysReadMeta(pDevIns, pAhciReq->GCPhysCmdHdrAddr, &cmdHdr, sizeof(CmdHdr)); + +#ifdef LOG_ENABLED + /* Print some infos about the command header. */ + ahciDumpCmdHdrInfo(pAhciPort, &cmdHdr); +#endif + + RTGCPHYS GCPhysAddrCmdTbl = AHCI_RTGCPHYS_FROM_U32(cmdHdr.u32CmdTblAddrUp, cmdHdr.u32CmdTblAddr); + + AssertMsgReturn((cmdHdr.u32DescInf & AHCI_CMDHDR_CFL_MASK) * sizeof(uint32_t) == AHCI_CMDFIS_TYPE_H2D_SIZE, + ("This is not a command FIS!!\n"), + false); + + /* Read the command Fis. */ + LogFlow(("%s: PDMDevHlpPCIPhysReadMeta GCPhysAddrCmdTbl=%RGp cbCmdFis=%u\n", __FUNCTION__, GCPhysAddrCmdTbl, AHCI_CMDFIS_TYPE_H2D_SIZE)); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysAddrCmdTbl, &pAhciReq->cmdFis[0], AHCI_CMDFIS_TYPE_H2D_SIZE); + + AssertMsgReturn(pAhciReq->cmdFis[AHCI_CMDFIS_TYPE] == AHCI_CMDFIS_TYPE_H2D, + ("This is not a command FIS\n"), + false); + + /* Set transfer direction. */ + pAhciReq->fFlags |= (cmdHdr.u32DescInf & AHCI_CMDHDR_W) ? 0 : AHCI_REQ_XFER_2_HOST; + + /* If this is an ATAPI command read the atapi command. */ + if (cmdHdr.u32DescInf & AHCI_CMDHDR_A) + { + GCPhysAddrCmdTbl += AHCI_CMDHDR_ACMD_OFFSET; + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysAddrCmdTbl, &pAhciReq->aATAPICmd[0], ATAPI_PACKET_SIZE); + } + + /* We "received" the FIS. Clear the BSY bit in regTFD. */ + if ((cmdHdr.u32DescInf & AHCI_CMDHDR_C) && (pAhciReq->fFlags & AHCI_REQ_CLEAR_SACT)) + { + /* + * We need to send a FIS which clears the busy bit if this is a queued command so that the guest can queue other commands. + * but this FIS does not assert an interrupt + */ + ahciSendD2HFis(pDevIns, pThis, pAhciPort, pAhciReq->uTag, pAhciReq->cmdFis, false); + pAhciPort->regTFD &= ~AHCI_PORT_TFD_BSY; + } + + pAhciReq->GCPhysPrdtl = AHCI_RTGCPHYS_FROM_U32(cmdHdr.u32CmdTblAddrUp, cmdHdr.u32CmdTblAddr) + AHCI_CMDHDR_PRDT_OFFSET; + pAhciReq->cPrdtlEntries = AHCI_CMDHDR_PRDTL_ENTRIES(cmdHdr.u32DescInf); + +#ifdef LOG_ENABLED + /* Print some infos about the FIS. */ + ahciDumpFisInfo(pAhciPort, &pAhciReq->cmdFis[0]); + + /* Print the PRDT */ + ahciLog(("PRDT address %RGp number of entries %u\n", pAhciReq->GCPhysPrdtl, pAhciReq->cPrdtlEntries)); + RTGCPHYS GCPhysPrdtl = pAhciReq->GCPhysPrdtl; + + for (unsigned i = 0; i < pAhciReq->cPrdtlEntries; i++) + { + SGLEntry SGEntry; + + ahciLog(("Entry %u at address %RGp\n", i, GCPhysPrdtl)); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysPrdtl, &SGEntry, sizeof(SGLEntry)); + + RTGCPHYS GCPhysDataAddr = AHCI_RTGCPHYS_FROM_U32(SGEntry.u32DBAUp, SGEntry.u32DBA); + ahciLog(("GCPhysAddr=%RGp Size=%u\n", GCPhysDataAddr, SGEntry.u32DescInf & SGLENTRY_DESCINF_DBC)); + + GCPhysPrdtl += sizeof(SGLEntry); + } +#endif + + return true; +} + +/** + * Submits a given request for execution. + * + * @returns Flag whether the request was canceled inbetween. + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pThisCC The ring-3 AHCI state. + * @param pAhciPort The port the request is for, shared bits. + * @param pAhciPortR3 The port the request is for, ring-3 bits. + * @param pAhciReq The request to submit. + * @param enmType The request type. + */ +static bool ahciR3ReqSubmit(PPDMDEVINS pDevIns, PAHCI pThis, PAHCICC pThisCC, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, + PAHCIREQ pAhciReq, PDMMEDIAEXIOREQTYPE enmType) +{ + int rc = VINF_SUCCESS; + bool fReqCanceled = false; + + VBOXDD_AHCI_REQ_SUBMIT(pAhciReq, pAhciReq->enmType, pAhciReq->uOffset, pAhciReq->cbTransfer); + + if (enmType == PDMMEDIAEXIOREQTYPE_FLUSH) + rc = pAhciPortR3->pDrvMediaEx->pfnIoReqFlush(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq); + else if (enmType == PDMMEDIAEXIOREQTYPE_DISCARD) + { + uint32_t cRangesMax; + + /* The data buffer contains LBA range entries. Each range is 8 bytes big. */ + if (!pAhciReq->cmdFis[AHCI_CMDFIS_SECTC] && !pAhciReq->cmdFis[AHCI_CMDFIS_SECTCEXP]) + cRangesMax = 65536 * 512 / 8; + else + cRangesMax = pAhciReq->cmdFis[AHCI_CMDFIS_SECTC] * 512 / 8; + + pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1; + rc = pAhciPortR3->pDrvMediaEx->pfnIoReqDiscard(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq, + cRangesMax); + } + else if (enmType == PDMMEDIAEXIOREQTYPE_READ) + { + pAhciPort->Led.Asserted.s.fReading = pAhciPort->Led.Actual.s.fReading = 1; + rc = pAhciPortR3->pDrvMediaEx->pfnIoReqRead(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq, + pAhciReq->uOffset, pAhciReq->cbTransfer); + } + else if (enmType == PDMMEDIAEXIOREQTYPE_WRITE) + { + pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1; + rc = pAhciPortR3->pDrvMediaEx->pfnIoReqWrite(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq, + pAhciReq->uOffset, pAhciReq->cbTransfer); + } + else if (enmType == PDMMEDIAEXIOREQTYPE_SCSI) + { + size_t cbBuf = 0; + + if (pAhciReq->cPrdtlEntries) + rc = ahciR3PrdtQuerySize(pDevIns, pAhciReq, &cbBuf); + pAhciReq->cbTransfer = cbBuf; + if (RT_SUCCESS(rc)) + { + if (cbBuf && (pAhciReq->fFlags & AHCI_REQ_XFER_2_HOST)) + pAhciPort->Led.Asserted.s.fReading = pAhciPort->Led.Actual.s.fReading = 1; + else if (cbBuf) + pAhciPort->Led.Asserted.s.fWriting = pAhciPort->Led.Actual.s.fWriting = 1; + rc = pAhciPortR3->pDrvMediaEx->pfnIoReqSendScsiCmd(pAhciPortR3->pDrvMediaEx, pAhciReq->hIoReq, + 0, &pAhciReq->aATAPICmd[0], ATAPI_PACKET_SIZE, + PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN, NULL, cbBuf, + &pAhciPort->abATAPISense[0], sizeof(pAhciPort->abATAPISense), NULL, + &pAhciReq->u8ScsiSts, 30 * RT_MS_1SEC); + } + } + + if (rc == VINF_SUCCESS) + fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, pAhciReq, VINF_SUCCESS); + else if (rc != VINF_PDM_MEDIAEX_IOREQ_IN_PROGRESS) + fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, pAhciReq, rc); + + return fReqCanceled; +} + +/** + * Prepares the command for execution coping it from guest memory and doing a few + * validation checks on it. + * + * @returns Whether the command was successfully fetched from guest memory and + * can be continued. + * @param pDevIns The device instance. + * @param pThis The shared AHCI state. + * @param pAhciPort The AHCI port the request is for, shared bits. + * @param pAhciReq Request structure to copy the command to. + */ +static bool ahciR3CmdPrepare(PPDMDEVINS pDevIns, PAHCI pThis, PAHCIPORT pAhciPort, PAHCIREQ pAhciReq) +{ + /* Set current command slot */ + ASMAtomicWriteU32(&pAhciPort->u32CurrentCommandSlot, pAhciReq->uTag); + + bool fContinue = ahciPortTaskGetCommandFis(pDevIns, pThis, pAhciPort, pAhciReq); + if (fContinue) + { + /* Mark the task as processed by the HBA if this is a queued task so that it doesn't occur in the CI register anymore. */ + if (pAhciPort->regSACT & RT_BIT_32(pAhciReq->uTag)) + { + pAhciReq->fFlags |= AHCI_REQ_CLEAR_SACT; + ASMAtomicOrU32(&pAhciPort->u32TasksFinished, RT_BIT_32(pAhciReq->uTag)); + } + + if (pAhciReq->cmdFis[AHCI_CMDFIS_BITS] & AHCI_CMDFIS_C) + { + /* + * It is possible that the request counter can get one higher than the maximum because + * the request counter is decremented after the guest was notified about the completed + * request (see @bugref{7859}). If the completing thread is preempted in between the + * guest might already issue another request before the request counter is decremented + * which would trigger the following assertion incorrectly in the past. + */ + AssertLogRelMsg(ASMAtomicReadU32(&pAhciPort->cTasksActive) <= AHCI_NR_COMMAND_SLOTS, + ("AHCI#%uP%u: There are more than %u (+1) requests active", + pDevIns->iInstance, pAhciPort->iLUN, + AHCI_NR_COMMAND_SLOTS)); + ASMAtomicIncU32(&pAhciPort->cTasksActive); + } + else + { + /* If the reset bit is set put the device into reset state. */ + if (pAhciReq->cmdFis[AHCI_CMDFIS_CTL] & AHCI_CMDFIS_CTL_SRST) + { + ahciLog(("%s: Setting device into reset state\n", __FUNCTION__)); + pAhciPort->fResetDevice = true; + ahciSendD2HFis(pDevIns, pThis, pAhciPort, pAhciReq->uTag, pAhciReq->cmdFis, true); + } + else if (pAhciPort->fResetDevice) /* The bit is not set and we are in a reset state. */ + ahciFinishStorageDeviceReset(pDevIns, pThis, pAhciPort, pAhciReq); + else /* We are not in a reset state update the control registers. */ + AssertMsgFailed(("%s: Update the control register\n", __FUNCTION__)); + + fContinue = false; + } + } + else + { + /* + * Couldn't find anything in either the AHCI or SATA spec which + * indicates what should be done if the FIS is not read successfully. + * The closest thing is in the state machine, stating that the device + * should go into idle state again (SATA spec 1.0 chapter 8.7.1). + * Do the same here and ignore any corrupt FIS types, after all + * the guest messed up everything and this behavior is undefined. + */ + fContinue = false; + } + + return fContinue; +} + +/** + * @callback_method_impl{FNPDMTHREADDEV, The async IO thread for one port.} + */ +static DECLCALLBACK(int) ahciAsyncIOLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PAHCIPORTR3 pAhciPortR3 = (PAHCIPORTR3)pThread->pvUser; + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN); + int rc = VINF_SUCCESS; + + ahciLog(("%s: Port %d entering async IO loop.\n", __FUNCTION__, pAhciPort->iLUN)); + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + unsigned idx = 0; + uint32_t u32Tasks = 0; + uint32_t u32RegHbaCtrl = 0; + + ASMAtomicWriteBool(&pAhciPort->fWrkThreadSleeping, true); + u32Tasks = ASMAtomicXchgU32(&pAhciPort->u32TasksNew, 0); + if (!u32Tasks) + { + Assert(ASMAtomicReadBool(&pAhciPort->fWrkThreadSleeping)); + rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pAhciPort->hEvtProcess, RT_INDEFINITE_WAIT); + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + break; + LogFlowFunc(("Woken up with rc=%Rrc\n", rc)); + u32Tasks = ASMAtomicXchgU32(&pAhciPort->u32TasksNew, 0); + } + + ASMAtomicWriteBool(&pAhciPort->fWrkThreadSleeping, false); + ASMAtomicIncU32(&pThis->cThreadsActive); + + /* Check whether the thread should be suspended. */ + if (pThisCC->fSignalIdle) + { + if (!ASMAtomicDecU32(&pThis->cThreadsActive)) + PDMDevHlpAsyncNotificationCompleted(pDevIns); + continue; + } + + /* + * Check whether the global host controller bit is set and go to sleep immediately again + * if it is set. + */ + u32RegHbaCtrl = ASMAtomicReadU32(&pThis->regHbaCtrl); + if ( u32RegHbaCtrl & AHCI_HBA_CTRL_HR + && !ASMAtomicDecU32(&pThis->cThreadsActive)) + { + ahciR3HBAReset(pDevIns, pThis, pThisCC); + if (pThisCC->fSignalIdle) + PDMDevHlpAsyncNotificationCompleted(pDevIns); + continue; + } + + idx = ASMBitFirstSetU32(u32Tasks); + while ( idx + && !pAhciPort->fPortReset) + { + bool fReqCanceled = false; + + /* Decrement to get the slot number. */ + idx--; + ahciLog(("%s: Processing command at slot %d\n", __FUNCTION__, idx)); + + PAHCIREQ pAhciReq = ahciR3ReqAlloc(pAhciPortR3, idx); + if (RT_LIKELY(pAhciReq)) + { + pAhciReq->uTag = idx; + pAhciReq->fFlags = 0; + + bool fContinue = ahciR3CmdPrepare(pDevIns, pThis, pAhciPort, pAhciReq); + if (fContinue) + { + PDMMEDIAEXIOREQTYPE enmType = ahciProcessCmd(pDevIns, pThis, pAhciPort, pAhciPortR3, + pAhciReq, pAhciReq->cmdFis); + pAhciReq->enmType = enmType; + + if (enmType != PDMMEDIAEXIOREQTYPE_INVALID) + fReqCanceled = ahciR3ReqSubmit(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, pAhciReq, enmType); + else + fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, + pAhciReq, VINF_SUCCESS); + } /* Command */ + else + ahciR3ReqFree(pAhciPortR3, pAhciReq); + } + else /* !Request allocated, use on stack variant to signal the error. */ + { + AHCIREQ Req; + Req.uTag = idx; + Req.fFlags = AHCI_REQ_IS_ON_STACK; + Req.fMapped = false; + Req.cbTransfer = 0; + Req.uOffset = 0; + Req.enmType = PDMMEDIAEXIOREQTYPE_INVALID; + + bool fContinue = ahciR3CmdPrepare(pDevIns, pThis, pAhciPort, &Req); + if (fContinue) + fReqCanceled = ahciR3TransferComplete(pDevIns, pThis, pThisCC, pAhciPort, pAhciPortR3, &Req, VERR_NO_MEMORY); + } + + /* + * Don't process other requests if the last one was canceled, + * the others are not valid anymore. + */ + if (fReqCanceled) + break; + + u32Tasks &= ~RT_BIT_32(idx); /* Clear task bit. */ + idx = ASMBitFirstSetU32(u32Tasks); + } /* while tasks available */ + + /* Check whether a port reset was active. */ + if ( ASMAtomicReadBool(&pAhciPort->fPortReset) + && (pAhciPort->regSCTL & AHCI_PORT_SCTL_DET) == AHCI_PORT_SCTL_DET_NINIT) + ahciPortResetFinish(pDevIns, pThis, pAhciPort, pAhciPortR3); + + /* + * Check whether a host controller reset is pending and execute the reset + * if this is the last active thread. + */ + u32RegHbaCtrl = ASMAtomicReadU32(&pThis->regHbaCtrl); + uint32_t cThreadsActive = ASMAtomicDecU32(&pThis->cThreadsActive); + if ( (u32RegHbaCtrl & AHCI_HBA_CTRL_HR) + && !cThreadsActive) + ahciR3HBAReset(pDevIns, pThis, pThisCC); + + if (!cThreadsActive && pThisCC->fSignalIdle) + PDMDevHlpAsyncNotificationCompleted(pDevIns); + } /* While running */ + + ahciLog(("%s: Port %d async IO thread exiting\n", __FUNCTION__, pAhciPort->iLUN)); + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNPDMTHREADWAKEUPDEV} + */ +static DECLCALLBACK(int) ahciAsyncIOLoopWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PAHCIPORTR3 pAhciPortR3 = (PAHCIPORTR3)pThread->pvUser; + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIPORT pAhciPort = &RT_SAFE_SUBSCRIPT(pThis->aPorts, pAhciPortR3->iLUN); + return PDMDevHlpSUPSemEventSignal(pDevIns, pAhciPort->hEvtProcess); +} + +/* -=-=-=-=- DBGF -=-=-=-=- */ + +/** + * AHCI status info callback. + * + * @param pDevIns The device instance. + * @param pHlp The output helpers. + * @param pszArgs The arguments. + */ +static DECLCALLBACK(void) ahciR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + + /* + * Show info. + */ + pHlp->pfnPrintf(pHlp, + "%s#%d: mmio=%RGp ports=%u GC=%RTbool R0=%RTbool\n", + pDevIns->pReg->szName, + pDevIns->iInstance, + PDMDevHlpMmioGetMappingAddress(pDevIns, pThis->hMmio), + pThis->cPortsImpl, + pDevIns->fRCEnabled, + pDevIns->fR0Enabled); + + /* + * Show global registers. + */ + pHlp->pfnPrintf(pHlp, "HbaCap=%#x\n", pThis->regHbaCap); + pHlp->pfnPrintf(pHlp, "HbaCtrl=%#x\n", pThis->regHbaCtrl); + pHlp->pfnPrintf(pHlp, "HbaIs=%#x\n", pThis->regHbaIs); + pHlp->pfnPrintf(pHlp, "HbaPi=%#x\n", pThis->regHbaPi); + pHlp->pfnPrintf(pHlp, "HbaVs=%#x\n", pThis->regHbaVs); + pHlp->pfnPrintf(pHlp, "HbaCccCtl=%#x\n", pThis->regHbaCccCtl); + pHlp->pfnPrintf(pHlp, "HbaCccPorts=%#x\n", pThis->regHbaCccPorts); + pHlp->pfnPrintf(pHlp, "PortsInterrupted=%#x\n", pThis->u32PortsInterrupted); + + /* + * Per port data. + */ + uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts)); + for (unsigned i = 0; i < cPortsImpl; i++) + { + PAHCIPORT pThisPort = &pThis->aPorts[i]; + + pHlp->pfnPrintf(pHlp, "Port %d: device-attached=%RTbool\n", pThisPort->iLUN, pThisPort->fPresent); + pHlp->pfnPrintf(pHlp, "PortClb=%#x\n", pThisPort->regCLB); + pHlp->pfnPrintf(pHlp, "PortClbU=%#x\n", pThisPort->regCLBU); + pHlp->pfnPrintf(pHlp, "PortFb=%#x\n", pThisPort->regFB); + pHlp->pfnPrintf(pHlp, "PortFbU=%#x\n", pThisPort->regFBU); + pHlp->pfnPrintf(pHlp, "PortIs=%#x\n", pThisPort->regIS); + pHlp->pfnPrintf(pHlp, "PortIe=%#x\n", pThisPort->regIE); + pHlp->pfnPrintf(pHlp, "PortCmd=%#x\n", pThisPort->regCMD); + pHlp->pfnPrintf(pHlp, "PortTfd=%#x\n", pThisPort->regTFD); + pHlp->pfnPrintf(pHlp, "PortSig=%#x\n", pThisPort->regSIG); + pHlp->pfnPrintf(pHlp, "PortSSts=%#x\n", pThisPort->regSSTS); + pHlp->pfnPrintf(pHlp, "PortSCtl=%#x\n", pThisPort->regSCTL); + pHlp->pfnPrintf(pHlp, "PortSErr=%#x\n", pThisPort->regSERR); + pHlp->pfnPrintf(pHlp, "PortSAct=%#x\n", pThisPort->regSACT); + pHlp->pfnPrintf(pHlp, "PortCi=%#x\n", pThisPort->regCI); + pHlp->pfnPrintf(pHlp, "PortPhysClb=%RGp\n", pThisPort->GCPhysAddrClb); + pHlp->pfnPrintf(pHlp, "PortPhysFb=%RGp\n", pThisPort->GCPhysAddrFb); + pHlp->pfnPrintf(pHlp, "PortActTasksActive=%u\n", pThisPort->cTasksActive); + pHlp->pfnPrintf(pHlp, "PortPoweredOn=%RTbool\n", pThisPort->fPoweredOn); + pHlp->pfnPrintf(pHlp, "PortSpunUp=%RTbool\n", pThisPort->fSpunUp); + pHlp->pfnPrintf(pHlp, "PortFirstD2HFisSent=%RTbool\n", pThisPort->fFirstD2HFisSent); + pHlp->pfnPrintf(pHlp, "PortATAPI=%RTbool\n", pThisPort->fATAPI); + pHlp->pfnPrintf(pHlp, "PortTasksFinished=%#x\n", pThisPort->u32TasksFinished); + pHlp->pfnPrintf(pHlp, "PortQueuedTasksFinished=%#x\n", pThisPort->u32QueuedTasksFinished); + pHlp->pfnPrintf(pHlp, "PortTasksNew=%#x\n", pThisPort->u32TasksNew); + pHlp->pfnPrintf(pHlp, "\n"); + } +} + +/* -=-=-=-=- Helper -=-=-=-=- */ + +/** + * Checks if all asynchronous I/O is finished, both AHCI and IDE. + * + * Used by ahciR3Reset, ahciR3Suspend and ahciR3PowerOff. ahciR3SavePrep makes + * use of it in strict builds (which is why it's up here). + * + * @returns true if quiesced, false if busy. + * @param pDevIns The device instance. + */ +static bool ahciR3AllAsyncIOIsFinished(PPDMDEVINS pDevIns) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + + if (pThis->cThreadsActive) + return false; + + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aPorts); i++) + { + PAHCIPORT pThisPort = &pThis->aPorts[i]; + if (pThisPort->fPresent) + { + if ( (pThisPort->cTasksActive != 0) + || (pThisPort->u32TasksNew != 0)) + return false; + } + } + return true; +} + +/* -=-=-=-=- Saved State -=-=-=-=- */ + +/** + * @callback_method_impl{FNSSMDEVSAVEPREP} + */ +static DECLCALLBACK(int) ahciR3SavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pDevIns, pSSM); + Assert(ahciR3AllAsyncIOIsFinished(pDevIns)); + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNSSMDEVLOADPREP} + */ +static DECLCALLBACK(int) ahciR3LoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pDevIns, pSSM); + Assert(ahciR3AllAsyncIOIsFinished(pDevIns)); + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNSSMDEVLIVEEXEC} + */ +static DECLCALLBACK(int) ahciR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + RT_NOREF(uPass); + + /* config. */ + pHlp->pfnSSMPutU32(pSSM, pThis->cPortsImpl); + for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++) + { + pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fPresent); + pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fHotpluggable); + pHlp->pfnSSMPutStrZ(pSSM, pThis->aPorts[i].szSerialNumber); + pHlp->pfnSSMPutStrZ(pSSM, pThis->aPorts[i].szFirmwareRevision); + pHlp->pfnSSMPutStrZ(pSSM, pThis->aPorts[i].szModelNumber); + } + + static const char *s_apszIdeEmuPortNames[4] = { "PrimaryMaster", "PrimarySlave", "SecondaryMaster", "SecondarySlave" }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apszIdeEmuPortNames); i++) + { + uint32_t iPort; + int rc = pHlp->pfnCFGMQueryU32Def(pDevIns->pCfg, s_apszIdeEmuPortNames[i], &iPort, i); + AssertRCReturn(rc, rc); + pHlp->pfnSSMPutU32(pSSM, iPort); + } + + return VINF_SSM_DONT_CALL_AGAIN; +} + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) ahciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + uint32_t i; + int rc; + + Assert(!pThis->f8ByteMMIO4BytesWrittenSuccessfully); + + /* The config */ + rc = ahciR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL); + AssertRCReturn(rc, rc); + + /* The main device structure. */ + pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCap); + pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCtrl); + pHlp->pfnSSMPutU32(pSSM, pThis->regHbaIs); + pHlp->pfnSSMPutU32(pSSM, pThis->regHbaPi); + pHlp->pfnSSMPutU32(pSSM, pThis->regHbaVs); + pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCccCtl); + pHlp->pfnSSMPutU32(pSSM, pThis->regHbaCccPorts); + pHlp->pfnSSMPutU8(pSSM, pThis->uCccPortNr); + pHlp->pfnSSMPutU64(pSSM, pThis->uCccTimeout); + pHlp->pfnSSMPutU32(pSSM, pThis->uCccNr); + pHlp->pfnSSMPutU32(pSSM, pThis->uCccCurrentNr); + pHlp->pfnSSMPutU32(pSSM, pThis->u32PortsInterrupted); + pHlp->pfnSSMPutBool(pSSM, pThis->fReset); + pHlp->pfnSSMPutBool(pSSM, pThis->f64BitAddr); + pHlp->pfnSSMPutBool(pSSM, pDevIns->fR0Enabled); + pHlp->pfnSSMPutBool(pSSM, pDevIns->fRCEnabled); + pHlp->pfnSSMPutBool(pSSM, pThis->fLegacyPortResetMethod); + + /* Now every port. */ + for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++) + { + Assert(pThis->aPorts[i].cTasksActive == 0); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCLB); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCLBU); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regFB); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regFBU); + pHlp->pfnSSMPutGCPhys(pSSM, pThis->aPorts[i].GCPhysAddrClb); + pHlp->pfnSSMPutGCPhys(pSSM, pThis->aPorts[i].GCPhysAddrFb); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regIS); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regIE); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCMD); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regTFD); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSIG); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSSTS); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSCTL); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSERR); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regSACT); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].regCI); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].PCHSGeometry.cCylinders); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].PCHSGeometry.cHeads); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].PCHSGeometry.cSectors); + pHlp->pfnSSMPutU64(pSSM, pThis->aPorts[i].cTotalSectors); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].cMultSectors); + pHlp->pfnSSMPutU8(pSSM, pThis->aPorts[i].uATATransferMode); + pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fResetDevice); + pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fPoweredOn); + pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fSpunUp); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].u32TasksFinished); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].u32QueuedTasksFinished); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[i].u32CurrentCommandSlot); + + /* ATAPI saved state. */ + pHlp->pfnSSMPutBool(pSSM, pThis->aPorts[i].fATAPI); + pHlp->pfnSSMPutMem(pSSM, &pThis->aPorts[i].abATAPISense[0], sizeof(pThis->aPorts[i].abATAPISense)); + } + + return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */ +} + +/** + * Loads a saved legacy ATA emulated device state. + * + * @returns VBox status code. + * @param pHlp The device helper call table. + * @param pSSM The handle to the saved state. + */ +static int ahciR3LoadLegacyEmulationState(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM) +{ + int rc; + uint32_t u32Version; + uint32_t u32; + uint32_t u32IOBuffer; + + /* Test for correct version. */ + rc = pHlp->pfnSSMGetU32(pSSM, &u32Version); + AssertRCReturn(rc, rc); + LogFlow(("LoadOldSavedStates u32Version = %d\n", u32Version)); + + if ( u32Version != ATA_CTL_SAVED_STATE_VERSION + && u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE + && u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS) + { + AssertMsgFailed(("u32Version=%d\n", u32Version)); + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + } + + pHlp->pfnSSMSkip(pSSM, 19 + 5 * sizeof(bool) + 8 /* sizeof(BMDMAState) */); + + for (uint32_t j = 0; j < 2; j++) + { + pHlp->pfnSSMSkip(pSSM, 88 + 5 * sizeof(bool) ); + + if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE) + pHlp->pfnSSMSkip(pSSM, 64); + else + pHlp->pfnSSMSkip(pSSM, 2); + /** @todo triple-check this hack after passthrough is working */ + pHlp->pfnSSMSkip(pSSM, 1); + + if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS) + pHlp->pfnSSMSkip(pSSM, 4); + + pHlp->pfnSSMSkip(pSSM, sizeof(PDMLED)); + pHlp->pfnSSMGetU32(pSSM, &u32IOBuffer); + if (u32IOBuffer) + pHlp->pfnSSMSkip(pSSM, u32IOBuffer); + } + + 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_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) ahciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + uint32_t u32; + int rc; + + if ( uVersion > AHCI_SAVED_STATE_VERSION + || uVersion < AHCI_SAVED_STATE_VERSION_VBOX_30) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + /* Deal with the priod after removing the saved IDE bits where the saved + state version remained unchanged. */ + if ( uVersion == AHCI_SAVED_STATE_VERSION_IDE_EMULATION + && pHlp->pfnSSMHandleRevision(pSSM) >= 79045 + && pHlp->pfnSSMHandleRevision(pSSM) < 79201) + uVersion++; + + /* + * Check whether we have to resort to the legacy port reset method to + * prevent older BIOS versions from failing after a reset. + */ + if (uVersion <= AHCI_SAVED_STATE_VERSION_PRE_PORT_RESET_CHANGES) + pThis->fLegacyPortResetMethod = true; + + /* Verify config. */ + if (uVersion > AHCI_SAVED_STATE_VERSION_VBOX_30) + { + rc = pHlp->pfnSSMGetU32(pSSM, &u32); + AssertRCReturn(rc, rc); + if (u32 != pThis->cPortsImpl) + { + LogRel(("AHCI: Config mismatch: cPortsImpl - saved=%u config=%u\n", u32, pThis->cPortsImpl)); + if ( u32 < pThis->cPortsImpl + || u32 > AHCI_MAX_NR_PORTS_IMPL) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: cPortsImpl - saved=%u config=%u"), + u32, pThis->cPortsImpl); + } + + for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++) + { + bool fInUse; + rc = pHlp->pfnSSMGetBool(pSSM, &fInUse); + AssertRCReturn(rc, rc); + if (fInUse != pThis->aPorts[i].fPresent) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("The %s VM is missing a device on port %u. Please make sure the source and target VMs have compatible storage configurations"), + fInUse ? "target" : "source", i); + + if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_HOTPLUG_FLAG) + { + bool fHotpluggable; + rc = pHlp->pfnSSMGetBool(pSSM, &fHotpluggable); + AssertRCReturn(rc, rc); + if (fHotpluggable != pThis->aPorts[i].fHotpluggable) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("AHCI: Port %u config mismatch: Hotplug flag - saved=%RTbool config=%RTbool\n"), + i, fHotpluggable, pThis->aPorts[i].fHotpluggable); + } + else + Assert(pThis->aPorts[i].fHotpluggable); + + char szSerialNumber[AHCI_SERIAL_NUMBER_LENGTH+1]; + rc = pHlp->pfnSSMGetStrZ(pSSM, szSerialNumber, sizeof(szSerialNumber)); + AssertRCReturn(rc, rc); + if (strcmp(szSerialNumber, pThis->aPorts[i].szSerialNumber)) + LogRel(("AHCI: Port %u config mismatch: Serial number - saved='%s' config='%s'\n", + i, szSerialNumber, pThis->aPorts[i].szSerialNumber)); + + char szFirmwareRevision[AHCI_FIRMWARE_REVISION_LENGTH+1]; + rc = pHlp->pfnSSMGetStrZ(pSSM, szFirmwareRevision, sizeof(szFirmwareRevision)); + AssertRCReturn(rc, rc); + if (strcmp(szFirmwareRevision, pThis->aPorts[i].szFirmwareRevision)) + LogRel(("AHCI: Port %u config mismatch: Firmware revision - saved='%s' config='%s'\n", + i, szFirmwareRevision, pThis->aPorts[i].szFirmwareRevision)); + + char szModelNumber[AHCI_MODEL_NUMBER_LENGTH+1]; + rc = pHlp->pfnSSMGetStrZ(pSSM, szModelNumber, sizeof(szModelNumber)); + AssertRCReturn(rc, rc); + if (strcmp(szModelNumber, pThis->aPorts[i].szModelNumber)) + LogRel(("AHCI: Port %u config mismatch: Model number - saved='%s' config='%s'\n", + i, szModelNumber, pThis->aPorts[i].szModelNumber)); + } + + static const char *s_apszIdeEmuPortNames[4] = { "PrimaryMaster", "PrimarySlave", "SecondaryMaster", "SecondarySlave" }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apszIdeEmuPortNames); i++) + { + uint32_t iPort; + rc = pHlp->pfnCFGMQueryU32Def(pDevIns->pCfg, s_apszIdeEmuPortNames[i], &iPort, i); + AssertRCReturn(rc, rc); + + uint32_t iPortSaved; + rc = pHlp->pfnSSMGetU32(pSSM, &iPortSaved); + AssertRCReturn(rc, rc); + + if (iPortSaved != iPort) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("IDE %s config mismatch: saved=%u config=%u"), + s_apszIdeEmuPortNames[i], iPortSaved, iPort); + } + } + + if (uPass == SSM_PASS_FINAL) + { + /* Restore data. */ + + /* The main device structure. */ + pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCap); + pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCtrl); + pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaIs); + pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaPi); + pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaVs); + pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCccCtl); + pHlp->pfnSSMGetU32(pSSM, &pThis->regHbaCccPorts); + pHlp->pfnSSMGetU8(pSSM, &pThis->uCccPortNr); + pHlp->pfnSSMGetU64(pSSM, &pThis->uCccTimeout); + pHlp->pfnSSMGetU32(pSSM, &pThis->uCccNr); + pHlp->pfnSSMGetU32(pSSM, &pThis->uCccCurrentNr); + + pHlp->pfnSSMGetU32V(pSSM, &pThis->u32PortsInterrupted); + pHlp->pfnSSMGetBool(pSSM, &pThis->fReset); + pHlp->pfnSSMGetBool(pSSM, &pThis->f64BitAddr); + bool fIgn; + pHlp->pfnSSMGetBool(pSSM, &fIgn); /* Was fR0Enabled, which should never have been saved! */ + pHlp->pfnSSMGetBool(pSSM, &fIgn); /* Was fGCEnabled, which should never have been saved! */ + if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_PORT_RESET_CHANGES) + pHlp->pfnSSMGetBool(pSSM, &pThis->fLegacyPortResetMethod); + + /* Now every port. */ + for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++) + { + PAHCIPORT pAhciPort = &pThis->aPorts[i]; + + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regCLB); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regCLBU); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regFB); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regFBU); + pHlp->pfnSSMGetGCPhysV(pSSM, &pThis->aPorts[i].GCPhysAddrClb); + pHlp->pfnSSMGetGCPhysV(pSSM, &pThis->aPorts[i].GCPhysAddrFb); + pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].regIS); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regIE); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regCMD); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regTFD); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSIG); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSSTS); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSCTL); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].regSERR); + pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].regSACT); + pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].regCI); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].PCHSGeometry.cCylinders); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].PCHSGeometry.cHeads); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].PCHSGeometry.cSectors); + pHlp->pfnSSMGetU64(pSSM, &pThis->aPorts[i].cTotalSectors); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[i].cMultSectors); + pHlp->pfnSSMGetU8(pSSM, &pThis->aPorts[i].uATATransferMode); + pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fResetDevice); + + if (uVersion <= AHCI_SAVED_STATE_VERSION_VBOX_30) + pHlp->pfnSSMSkip(pSSM, AHCI_NR_COMMAND_SLOTS * sizeof(uint8_t)); /* no active data here */ + + if (uVersion < AHCI_SAVED_STATE_VERSION_IDE_EMULATION) + { + /* The old positions in the FIFO, not required. */ + pHlp->pfnSSMSkip(pSSM, 2*sizeof(uint8_t)); + } + pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fPoweredOn); + pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fSpunUp); + pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].u32TasksFinished); + pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].u32QueuedTasksFinished); + + if (uVersion >= AHCI_SAVED_STATE_VERSION_IDE_EMULATION) + pHlp->pfnSSMGetU32V(pSSM, &pThis->aPorts[i].u32CurrentCommandSlot); + + if (uVersion > AHCI_SAVED_STATE_VERSION_PRE_ATAPI) + { + pHlp->pfnSSMGetBool(pSSM, &pThis->aPorts[i].fATAPI); + pHlp->pfnSSMGetMem(pSSM, pThis->aPorts[i].abATAPISense, sizeof(pThis->aPorts[i].abATAPISense)); + if (uVersion <= AHCI_SAVED_STATE_VERSION_PRE_ATAPI_REMOVE) + { + pHlp->pfnSSMSkip(pSSM, 1); /* cNotifiedMediaChange. */ + pHlp->pfnSSMSkip(pSSM, 4); /* MediaEventStatus */ + } + } + else if (pThis->aPorts[i].fATAPI) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch: atapi - saved=false config=true")); + + /* Check if we have tasks pending. */ + uint32_t fTasksOutstanding = pAhciPort->regCI & ~pAhciPort->u32TasksFinished; + uint32_t fQueuedTasksOutstanding = pAhciPort->regSACT & ~pAhciPort->u32QueuedTasksFinished; + + pAhciPort->u32TasksNew = fTasksOutstanding | fQueuedTasksOutstanding; + + if (pAhciPort->u32TasksNew) + { + /* + * There are tasks pending. The VM was saved after a task failed + * because of non-fatal error. Set the redo flag. + */ + pAhciPort->fRedo = true; + } + } + + if (uVersion <= AHCI_SAVED_STATE_VERSION_IDE_EMULATION) + { + for (uint32_t i = 0; i < 2; i++) + { + rc = ahciR3LoadLegacyEmulationState(pHlp, pSSM); + if(RT_FAILURE(rc)) + return rc; + } + } + + rc = pHlp->pfnSSMGetU32(pSSM, &u32); + if (RT_FAILURE(rc)) + return rc; + AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + } + + return VINF_SUCCESS; +} + +/* -=-=-=-=- device PDM interface -=-=-=-=- */ + +/** + * Configure the attached device for a port. + * + * Used by ahciR3Construct and ahciR3Attach. + * + * @returns VBox status code + * @param pDevIns The device instance data. + * @param pAhciPort The port for which the device is to be configured, shared bits. + * @param pAhciPortR3 The port for which the device is to be configured, ring-3 bits. + */ +static int ahciR3ConfigureLUN(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3) +{ + /* Query the media interface. */ + pAhciPortR3->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pAhciPortR3->pDrvBase, PDMIMEDIA); + AssertMsgReturn(RT_VALID_PTR(pAhciPortR3->pDrvMedia), + ("AHCI configuration error: LUN#%d misses the basic media interface!\n", pAhciPort->iLUN), + VERR_PDM_MISSING_INTERFACE); + + /* Get the extended media interface. */ + pAhciPortR3->pDrvMediaEx = PDMIBASE_QUERY_INTERFACE(pAhciPortR3->pDrvBase, PDMIMEDIAEX); + AssertMsgReturn(RT_VALID_PTR(pAhciPortR3->pDrvMediaEx), + ("AHCI configuration error: LUN#%d misses the extended media interface!\n", pAhciPort->iLUN), + VERR_PDM_MISSING_INTERFACE); + + /* + * Validate type. + */ + PDMMEDIATYPE enmType = pAhciPortR3->pDrvMedia->pfnGetType(pAhciPortR3->pDrvMedia); + AssertMsgReturn(enmType == PDMMEDIATYPE_HARD_DISK || enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD, + ("AHCI configuration error: LUN#%d isn't a disk or cd/dvd. enmType=%u\n", pAhciPort->iLUN, enmType), + VERR_PDM_UNSUPPORTED_BLOCK_TYPE); + + int rc = pAhciPortR3->pDrvMediaEx->pfnIoReqAllocSizeSet(pAhciPortR3->pDrvMediaEx, sizeof(AHCIREQ)); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("AHCI configuration error: LUN#%u: Failed to set I/O request size!"), + pAhciPort->iLUN); + + uint32_t fFeatures = 0; + rc = pAhciPortR3->pDrvMediaEx->pfnQueryFeatures(pAhciPortR3->pDrvMediaEx, &fFeatures); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("AHCI configuration error: LUN#%u: Failed to query features of device"), + pAhciPort->iLUN); + + if (fFeatures & PDMIMEDIAEX_FEATURE_F_DISCARD) + pAhciPort->fTrimEnabled = true; + + pAhciPort->fPresent = true; + + pAhciPort->fATAPI = (enmType == PDMMEDIATYPE_CDROM || enmType == PDMMEDIATYPE_DVD) + && RT_BOOL(fFeatures & PDMIMEDIAEX_FEATURE_F_RAWSCSICMD); + if (pAhciPort->fATAPI) + { + pAhciPort->PCHSGeometry.cCylinders = 0; + pAhciPort->PCHSGeometry.cHeads = 0; + pAhciPort->PCHSGeometry.cSectors = 0; + LogRel(("AHCI: LUN#%d: CD/DVD\n", pAhciPort->iLUN)); + } + else + { + pAhciPort->cbSector = pAhciPortR3->pDrvMedia->pfnGetSectorSize(pAhciPortR3->pDrvMedia); + pAhciPort->cTotalSectors = pAhciPortR3->pDrvMedia->pfnGetSize(pAhciPortR3->pDrvMedia) / pAhciPort->cbSector; + rc = pAhciPortR3->pDrvMedia->pfnBiosGetPCHSGeometry(pAhciPortR3->pDrvMedia, &pAhciPort->PCHSGeometry); + if (rc == VERR_PDM_MEDIA_NOT_MOUNTED) + { + pAhciPort->PCHSGeometry.cCylinders = 0; + pAhciPort->PCHSGeometry.cHeads = 16; /*??*/ + pAhciPort->PCHSGeometry.cSectors = 63; /*??*/ + } + else if (rc == VERR_PDM_GEOMETRY_NOT_SET) + { + pAhciPort->PCHSGeometry.cCylinders = 0; /* autodetect marker */ + rc = VINF_SUCCESS; + } + AssertRC(rc); + + if ( pAhciPort->PCHSGeometry.cCylinders == 0 + || pAhciPort->PCHSGeometry.cHeads == 0 + || pAhciPort->PCHSGeometry.cSectors == 0) + { + uint64_t cCylinders = pAhciPort->cTotalSectors / (16 * 63); + pAhciPort->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1); + pAhciPort->PCHSGeometry.cHeads = 16; + pAhciPort->PCHSGeometry.cSectors = 63; + /* Set the disk geometry information. Ignore errors. */ + pAhciPortR3->pDrvMedia->pfnBiosSetPCHSGeometry(pAhciPortR3->pDrvMedia, &pAhciPort->PCHSGeometry); + rc = VINF_SUCCESS; + } + LogRel(("AHCI: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n", + pAhciPort->iLUN, pAhciPort->PCHSGeometry.cCylinders, + pAhciPort->PCHSGeometry.cHeads, pAhciPort->PCHSGeometry.cSectors, + pAhciPort->cTotalSectors)); + if (pAhciPort->fTrimEnabled) + LogRel(("AHCI: LUN#%d: Enabled TRIM support\n", pAhciPort->iLUN)); + } + return rc; +} + +/** + * Callback employed by ahciR3Suspend and ahciR3PowerOff. + * + * @returns true if we've quiesced, false if we're still working. + * @param pDevIns The device instance. + */ +static DECLCALLBACK(bool) ahciR3IsAsyncSuspendOrPowerOffDone(PPDMDEVINS pDevIns) +{ + if (!ahciR3AllAsyncIOIsFinished(pDevIns)) + return false; + + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + ASMAtomicWriteBool(&pThisCC->fSignalIdle, false); + return true; +} + +/** + * Common worker for ahciR3Suspend and ahciR3PowerOff. + */ +static void ahciR3SuspendOrPowerOff(PPDMDEVINS pDevIns) +{ + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + + ASMAtomicWriteBool(&pThisCC->fSignalIdle, true); + if (!ahciR3AllAsyncIOIsFinished(pDevIns)) + PDMDevHlpSetAsyncNotification(pDevIns, ahciR3IsAsyncSuspendOrPowerOffDone); + else + ASMAtomicWriteBool(&pThisCC->fSignalIdle, false); + + for (uint32_t i = 0; i < RT_ELEMENTS(pThisCC->aPorts); i++) + { + PAHCIPORTR3 pThisPort = &pThisCC->aPorts[i]; + if (pThisPort->pDrvMediaEx) + pThisPort->pDrvMediaEx->pfnNotifySuspend(pThisPort->pDrvMediaEx); + } +} + +/** + * Suspend notification. + * + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ahciR3Suspend(PPDMDEVINS pDevIns) +{ + Log(("ahciR3Suspend\n")); + ahciR3SuspendOrPowerOff(pDevIns); +} + +/** + * Resume notification. + * + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ahciR3Resume(PPDMDEVINS pDevIns) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + + /* + * Check if one of the ports has pending tasks. + * Queue a notification item again in this case. + */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aPorts); i++) + { + PAHCIPORT pAhciPort = &pThis->aPorts[i]; + + if (pAhciPort->u32TasksRedo) + { + pAhciPort->u32TasksNew |= pAhciPort->u32TasksRedo; + pAhciPort->u32TasksRedo = 0; + + Assert(pAhciPort->fRedo); + pAhciPort->fRedo = false; + + /* Notify the async IO thread. */ + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pAhciPort->hEvtProcess); + AssertRC(rc); + } + } + + Log(("%s:\n", __FUNCTION__)); +} + +/** + * Initializes the VPD data of a attached device. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pAhciPort The attached device, shared bits. + * @param pAhciPortR3 The attached device, ring-3 bits. + * @param pszName Name of the port to get the CFGM node. + */ +static int ahciR3VpdInit(PPDMDEVINS pDevIns, PAHCIPORT pAhciPort, PAHCIPORTR3 pAhciPortR3, const char *pszName) +{ + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + /* Generate a default serial number. */ + char szSerial[AHCI_SERIAL_NUMBER_LENGTH+1]; + RTUUID Uuid; + + int rc = VINF_SUCCESS; + if (pAhciPortR3->pDrvMedia) + rc = pAhciPortR3->pDrvMedia->pfnGetUuid(pAhciPortR3->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-1a2b3c4d", pAhciPort->iLUN); + } + 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(pDevIns->pCfg, pszName); + rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "SerialNumber", pAhciPort->szSerialNumber, + sizeof(pAhciPort->szSerialNumber), szSerial); + if (RT_FAILURE(rc)) + { + if (rc == VERR_CFGM_NOT_ENOUGH_SPACE) + return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER, + N_("AHCI configuration error: \"SerialNumber\" is longer than 20 bytes")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read \"SerialNumber\" as string")); + } + + rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "FirmwareRevision", pAhciPort->szFirmwareRevision, + sizeof(pAhciPort->szFirmwareRevision), "1.0"); + if (RT_FAILURE(rc)) + { + if (rc == VERR_CFGM_NOT_ENOUGH_SPACE) + return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER, + N_("AHCI configuration error: \"FirmwareRevision\" is longer than 8 bytes")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read \"FirmwareRevision\" as string")); + } + + rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ModelNumber", pAhciPort->szModelNumber, sizeof(pAhciPort->szModelNumber), + pAhciPort->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_("AHCI configuration error: \"ModelNumber\" is longer than 40 bytes")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read \"ModelNumber\" as string")); + } + + rc = pHlp->pfnCFGMQueryU8Def(pCfgNode, "LogicalSectorsPerPhysical", &pAhciPort->cLogSectorsPerPhysicalExp, 0); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AHCI configuration error: failed to read \"LogicalSectorsPerPhysical\" as integer")); + if (pAhciPort->cLogSectorsPerPhysicalExp >= 16) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AHCI configuration error: \"LogicalSectorsPerPhysical\" must be between 0 and 15")); + + /* There are three other identification strings for CD drives used for INQUIRY */ + if (pAhciPort->fATAPI) + { + rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ATAPIVendorId", pAhciPort->szInquiryVendorId, + sizeof(pAhciPort->szInquiryVendorId), "VBOX"); + if (RT_FAILURE(rc)) + { + if (rc == VERR_CFGM_NOT_ENOUGH_SPACE) + return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER, + N_("AHCI configuration error: \"ATAPIVendorId\" is longer than 16 bytes")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read \"ATAPIVendorId\" as string")); + } + + rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ATAPIProductId", pAhciPort->szInquiryProductId, + sizeof(pAhciPort->szInquiryProductId), "CD-ROM"); + if (RT_FAILURE(rc)) + { + if (rc == VERR_CFGM_NOT_ENOUGH_SPACE) + return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER, + N_("AHCI configuration error: \"ATAPIProductId\" is longer than 16 bytes")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read \"ATAPIProductId\" as string")); + } + + rc = pHlp->pfnCFGMQueryStringDef(pCfgNode, "ATAPIRevision", pAhciPort->szInquiryRevision, + sizeof(pAhciPort->szInquiryRevision), "1.0"); + if (RT_FAILURE(rc)) + { + if (rc == VERR_CFGM_NOT_ENOUGH_SPACE) + return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER, + N_("AHCI configuration error: \"ATAPIRevision\" is longer than 4 bytes")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read \"ATAPIRevision\" as string")); + } + } + + return rc; +} + + +/** + * Detach notification. + * + * One harddisk at one port has been unplugged. + * The VM is suspended at this point. + * + * @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) ahciR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + int rc = VINF_SUCCESS; + + Log(("%s:\n", __FUNCTION__)); + + AssertMsgReturnVoid(iLUN < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThisCC->aPorts)), ("iLUN=%u", iLUN)); + PAHCIPORT pAhciPort = &pThis->aPorts[iLUN]; + PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[iLUN]; + AssertMsgReturnVoid( pAhciPort->fHotpluggable + || (fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG), + ("AHCI: Port %d is not marked hotpluggable\n", pAhciPort->iLUN)); + + + if (pAhciPortR3->pAsyncIOThread) + { + int rcThread; + /* Destroy the thread. */ + rc = PDMDevHlpThreadDestroy(pDevIns, pAhciPortR3->pAsyncIOThread, &rcThread); + if (RT_FAILURE(rc) || RT_FAILURE(rcThread)) + AssertMsgFailed(("%s Failed to destroy async IO thread rc=%Rrc rcThread=%Rrc\n", __FUNCTION__, rc, rcThread)); + + pAhciPortR3->pAsyncIOThread = NULL; + pAhciPort->fWrkThreadSleeping = true; + } + + if (!(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG)) + { + /* + * Inform the guest about the removed device. + */ + pAhciPort->regSSTS = 0; + pAhciPort->regSIG = 0; + /* + * Clear CR bit too to prevent submission of new commands when CI is written + * (AHCI Spec 1.2: 7.4 Interaction of the Command List and Port Change Status). + */ + ASMAtomicAndU32(&pAhciPort->regCMD, ~(AHCI_PORT_CMD_CPS | AHCI_PORT_CMD_CR)); + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_CPDS | AHCI_PORT_IS_PRCS | AHCI_PORT_IS_PCS); + ASMAtomicOrU32(&pAhciPort->regSERR, AHCI_PORT_SERR_X | AHCI_PORT_SERR_N); + if (pAhciPort->regIE & (AHCI_PORT_IE_CPDE | AHCI_PORT_IE_PCE | AHCI_PORT_IE_PRCE)) + ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + } + + /* + * Zero some important members. + */ + pAhciPortR3->pDrvBase = NULL; + pAhciPortR3->pDrvMedia = NULL; + pAhciPortR3->pDrvMediaEx = NULL; + pAhciPort->fPresent = false; +} + +/** + * Attach command. + * + * This is called when we change block driver for one port. + * The VM is suspended at this point. + * + * @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) ahciR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + int rc; + + Log(("%s:\n", __FUNCTION__)); + + /* the usual paranoia */ + AssertMsgReturn(iLUN < RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThisCC->aPorts)), ("iLUN=%u", iLUN), VERR_PDM_LUN_NOT_FOUND); + PAHCIPORT pAhciPort = &pThis->aPorts[iLUN]; + PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[iLUN]; + AssertRelease(!pAhciPortR3->pDrvBase); + AssertRelease(!pAhciPortR3->pDrvMedia); + AssertRelease(!pAhciPortR3->pDrvMediaEx); + Assert(pAhciPort->iLUN == iLUN); + Assert(pAhciPortR3->iLUN == iLUN); + + AssertMsgReturn( pAhciPort->fHotpluggable + || (fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG), + ("AHCI: Port %d is not marked hotpluggable\n", pAhciPort->iLUN), + VERR_INVALID_PARAMETER); + + /* + * Try attach the block device and get the interfaces, + * required as well as optional. + */ + rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPortR3->IBase, &pAhciPortR3->pDrvBase, pAhciPortR3->szDesc); + if (RT_SUCCESS(rc)) + rc = ahciR3ConfigureLUN(pDevIns, pAhciPort, pAhciPortR3); + else + AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pAhciPort->iLUN, rc)); + + if (RT_FAILURE(rc)) + { + pAhciPortR3->pDrvBase = NULL; + pAhciPortR3->pDrvMedia = NULL; + pAhciPortR3->pDrvMediaEx = NULL; + pAhciPort->fPresent = false; + } + else + { + rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pAhciPort->hEvtProcess); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("AHCI: Failed to create SUP event semaphore")); + + /* Create the async IO thread. */ + rc = PDMDevHlpThreadCreate(pDevIns, &pAhciPortR3->pAsyncIOThread, pAhciPortR3, ahciAsyncIOLoop, + ahciAsyncIOLoopWakeUp, 0, RTTHREADTYPE_IO, pAhciPortR3->szDesc); + if (RT_FAILURE(rc)) + return rc; + + /* + * Init vendor product data. + */ + if (RT_SUCCESS(rc)) + rc = ahciR3VpdInit(pDevIns, pAhciPort, pAhciPortR3, pAhciPortR3->szDesc); + + /* Inform the guest about the added device in case of hotplugging. */ + if ( RT_SUCCESS(rc) + && !(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG)) + { + AssertMsgReturn(pAhciPort->fHotpluggable, + ("AHCI: Port %d is not marked hotpluggable\n", pAhciPort->iLUN), + VERR_NOT_SUPPORTED); + + /* + * Initialize registers + */ + ASMAtomicOrU32(&pAhciPort->regCMD, AHCI_PORT_CMD_CPS); + ASMAtomicOrU32(&pAhciPort->regIS, AHCI_PORT_IS_CPDS | AHCI_PORT_IS_PRCS | AHCI_PORT_IS_PCS); + ASMAtomicOrU32(&pAhciPort->regSERR, AHCI_PORT_SERR_X | AHCI_PORT_SERR_N); + + if (pAhciPort->fATAPI) + pAhciPort->regSIG = AHCI_PORT_SIG_ATAPI; + else + pAhciPort->regSIG = AHCI_PORT_SIG_DISK; + pAhciPort->regSSTS = (0x01 << 8) | /* Interface is active. */ + (0x02 << 4) | /* Generation 2 (3.0GBps) speed. */ + (0x03 << 0); /* Device detected and communication established. */ + + if ( (pAhciPort->regIE & AHCI_PORT_IE_CPDE) + || (pAhciPort->regIE & AHCI_PORT_IE_PCE) + || (pAhciPort->regIE & AHCI_PORT_IE_PRCE)) + ahciHbaSetInterrupt(pDevIns, pThis, pAhciPort->iLUN, VERR_IGNORED); + } + + } + + return rc; +} + +/** + * Common reset worker. + * + * @param pDevIns The device instance data. + */ +static int ahciR3ResetCommon(PPDMDEVINS pDevIns) +{ + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + ahciR3HBAReset(pDevIns, pThis, pThisCC); + + /* Hardware reset for the ports. */ + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aPorts); i++) + ahciPortHwReset(&pThis->aPorts[i]); + return VINF_SUCCESS; +} + +/** + * Callback employed by ahciR3Reset. + * + * @returns true if we've quiesced, false if we're still working. + * @param pDevIns The device instance. + */ +static DECLCALLBACK(bool) ahciR3IsAsyncResetDone(PPDMDEVINS pDevIns) +{ + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + + if (!ahciR3AllAsyncIOIsFinished(pDevIns)) + return false; + ASMAtomicWriteBool(&pThisCC->fSignalIdle, false); + + ahciR3ResetCommon(pDevIns); + return true; +} + +/** + * Reset notification. + * + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ahciR3Reset(PPDMDEVINS pDevIns) +{ + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + + ASMAtomicWriteBool(&pThisCC->fSignalIdle, true); + if (!ahciR3AllAsyncIOIsFinished(pDevIns)) + PDMDevHlpSetAsyncNotification(pDevIns, ahciR3IsAsyncResetDone); + else + { + ASMAtomicWriteBool(&pThisCC->fSignalIdle, false); + ahciR3ResetCommon(pDevIns); + } +} + +/** + * Poweroff notification. + * + * @param pDevIns Pointer to the device instance + */ +static DECLCALLBACK(void) ahciR3PowerOff(PPDMDEVINS pDevIns) +{ + Log(("achiR3PowerOff\n")); + ahciR3SuspendOrPowerOff(pDevIns); +} + +/** + * 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) ahciR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + int rc = VINF_SUCCESS; + + /* + * At this point the async I/O thread is suspended and will not enter + * this module again. So, no coordination is needed here and PDM + * will take care of terminating and cleaning up the thread. + */ + if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->lock)) + { + PDMDevHlpTimerDestroy(pDevIns, pThis->hHbaCccTimer); + pThis->hHbaCccTimer = NIL_TMTIMERHANDLE; + + Log(("%s: Destruct every port\n", __FUNCTION__)); + uint32_t const cPortsImpl = RT_MIN(pThis->cPortsImpl, RT_ELEMENTS(pThis->aPorts)); + for (unsigned iActPort = 0; iActPort < cPortsImpl; iActPort++) + { + PAHCIPORT pAhciPort = &pThis->aPorts[iActPort]; + + if (pAhciPort->hEvtProcess != NIL_SUPSEMEVENT) + { + PDMDevHlpSUPSemEventClose(pDevIns, pAhciPort->hEvtProcess); + pAhciPort->hEvtProcess = NIL_SUPSEMEVENT; + } + } + + PDMDevHlpCritSectDelete(pDevIns, &pThis->lock); + } + + return rc; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) ahciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + PAHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAHCICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + PPDMIBASE pBase; + int rc; + unsigned i; + uint32_t cbTotalBufferSize = 0; /** @todo r=bird: cbTotalBufferSize isn't ever set. */ + + LogFlowFunc(("pThis=%#p\n", pThis)); + /* + * Initialize the instance data (everything touched by the destructor need + * to be initialized here!). + */ + PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; + PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev); + + PDMPciDevSetVendorId(pPciDev, 0x8086); /* Intel */ + PDMPciDevSetDeviceId(pPciDev, 0x2829); /* ICH-8M */ + PDMPciDevSetCommand(pPciDev, 0x0000); +#ifdef VBOX_WITH_MSI_DEVICES + PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST); + PDMPciDevSetCapabilityList(pPciDev, 0x80); +#else + PDMPciDevSetCapabilityList(pPciDev, 0x70); +#endif + PDMPciDevSetRevisionId(pPciDev, 0x02); + PDMPciDevSetClassProg(pPciDev, 0x01); + PDMPciDevSetClassSub(pPciDev, 0x06); + PDMPciDevSetClassBase(pPciDev, 0x01); + PDMPciDevSetBaseAddress(pPciDev, 5, false, false, false, 0x00000000); + + PDMPciDevSetInterruptLine(pPciDev, 0x00); + PDMPciDevSetInterruptPin(pPciDev, 0x01); + + PDMPciDevSetByte(pPciDev, 0x70, VBOX_PCI_CAP_ID_PM); /* Capability ID: PCI Power Management Interface */ + PDMPciDevSetByte(pPciDev, 0x71, 0xa8); /* next */ + PDMPciDevSetByte(pPciDev, 0x72, 0x03); /* version ? */ + + PDMPciDevSetByte(pPciDev, 0x90, 0x40); /* AHCI mode. */ + PDMPciDevSetByte(pPciDev, 0x92, 0x3f); + PDMPciDevSetByte(pPciDev, 0x94, 0x80); + PDMPciDevSetByte(pPciDev, 0x95, 0x01); + PDMPciDevSetByte(pPciDev, 0x97, 0x78); + + PDMPciDevSetByte(pPciDev, 0xa8, 0x12); /* SATACR capability */ + PDMPciDevSetByte(pPciDev, 0xa9, 0x00); /* next */ + PDMPciDevSetWord(pPciDev, 0xaa, 0x0010); /* Revision */ + PDMPciDevSetDWord(pPciDev, 0xac, 0x00000028); /* SATA Capability Register 1 */ + + pThis->cThreadsActive = 0; + + pThisCC->pDevIns = pDevIns; + pThisCC->IBase.pfnQueryInterface = ahciR3Status_QueryInterface; + pThisCC->ILeds.pfnQueryStatusLed = ahciR3Status_QueryStatusLed; + + /* Initialize port members. */ + for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++) + { + PAHCIPORT pAhciPort = &pThis->aPorts[i]; + PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[i]; + pAhciPortR3->pDevIns = pDevIns; + pAhciPort->iLUN = i; + pAhciPortR3->iLUN = i; + pAhciPort->Led.u32Magic = PDMLED_MAGIC; + pAhciPortR3->pDrvBase = NULL; + pAhciPortR3->pAsyncIOThread = NULL; + pAhciPort->hEvtProcess = NIL_SUPSEMEVENT; + pAhciPort->fHotpluggable = true; + } + + /* + * Init locks, using explicit locking where necessary. + */ + rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->lock, RT_SRC_POS, "AHCI#%u", iInstance); + if (RT_FAILURE(rc)) + { + Log(("%s: Failed to create critical section.\n", __FUNCTION__)); + return rc; + } + + /* + * Validate and read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, + "PrimaryMaster|PrimarySlave|SecondaryMaster" + "|SecondarySlave|PortCount|Bootable|CmdSlotsAvail|TigerHack", + "Port*"); + + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "PortCount", &pThis->cPortsImpl, AHCI_MAX_NR_PORTS_IMPL); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read PortCount as integer")); + Log(("%s: cPortsImpl=%u\n", __FUNCTION__, pThis->cPortsImpl)); + if (pThis->cPortsImpl > AHCI_MAX_NR_PORTS_IMPL) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("AHCI configuration error: PortCount=%u should not exceed %u"), + pThis->cPortsImpl, AHCI_MAX_NR_PORTS_IMPL); + if (pThis->cPortsImpl < 1) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("AHCI configuration error: PortCount=%u should be at least 1"), + pThis->cPortsImpl); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "Bootable", &pThis->fBootable, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("AHCI configuration error: failed to read Bootable as boolean")); + + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "CmdSlotsAvail", &pThis->cCmdSlotsAvail, AHCI_NR_COMMAND_SLOTS); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read CmdSlotsAvail as integer")); + Log(("%s: cCmdSlotsAvail=%u\n", __FUNCTION__, pThis->cCmdSlotsAvail)); + if (pThis->cCmdSlotsAvail > AHCI_NR_COMMAND_SLOTS) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("AHCI configuration error: CmdSlotsAvail=%u should not exceed %u"), + pThis->cPortsImpl, AHCI_NR_COMMAND_SLOTS); + if (pThis->cCmdSlotsAvail < 1) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("AHCI configuration error: CmdSlotsAvail=%u should be at least 1"), + pThis->cCmdSlotsAvail); + bool fTigerHack; + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "TigerHack", &fTigerHack, false); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read TigerHack as boolean")); + + + /* + * Register the PCI device, it's I/O regions. + */ + rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = 0x80; + MsiReg.iMsiNextOffset = 0x70; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE(rc)) + { + PCIDevSetCapabilityList(pPciDev, 0x70); + /* That's OK, we can work without MSI */ + } +#endif + + /* + * Solaris 10 U5 fails to map the AHCI register space when the sets (0..3) + * for the legacy IDE registers are not available. We set up "fake" entries + * in the PCI configuration register. That means they are available but + * read and writes from/to them have no effect. No guest should access them + * anyway because the controller is marked as AHCI in the Programming + * interface and we don't have an option to change to IDE emulation (real + * hardware provides an option in the BIOS to switch to it which also changes + * device Id and other things in the PCI configuration space). + */ + rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 0 /*iPciRegion*/, 8 /*cPorts*/, + ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/, + "AHCI Fake #0", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake0); + AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region"))); + + rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 1 /*iPciRegion*/, 1 /*cPorts*/, + ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/, + "AHCI Fake #1", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake1); + AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region"))); + + rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 2 /*iPciRegion*/, 8 /*cPorts*/, + ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/, + "AHCI Fake #2", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake2); + AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region"))); + + rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 3 /*iPciRegion*/, 1 /*cPorts*/, + ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/, + "AHCI Fake #3", NULL /*paExtDescs*/, &pThis->hIoPortsLegacyFake3); + AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region"))); + + /* + * The non-fake PCI I/O regions: + * Note! The 4352 byte MMIO region will be rounded up to GUEST_PAGE_SIZE. + */ + rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 4 /*iPciRegion*/, 0x10 /*cPorts*/, + ahciIdxDataWrite, ahciIdxDataRead, NULL /*pvUser*/, + "AHCI IDX/DATA", NULL /*paExtDescs*/, &pThis->hIoPortIdxData); + AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI I/O region for BMDMA"))); + + + /** @todo change this to IOMMMIO_FLAGS_WRITE_ONLY_DWORD once EM/IOM starts + * handling 2nd DWORD failures on split accesses correctly. */ + rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 5 /*iPciRegion*/, 4352 /*cbRegion*/, PCI_ADDRESS_SPACE_MEM, + ahciMMIOWrite, ahciMMIORead, NULL /*pvUser*/, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_QWORD_READ_MISSING, + "AHCI", &pThis->hMmio); + AssertRCReturn(rc, PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot register PCI memory region for registers"))); + + /* + * Create the timer for command completion coalescing feature. + */ + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ahciCccTimer, pThis, + TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, "AHCI CCC", &pThis->hHbaCccTimer); + AssertRCReturn(rc, rc); + + /* + * Initialize ports. + */ + + /* Initialize static members on every port. */ + for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++) + ahciPortHwReset(&pThis->aPorts[i]); + + /* Attach drivers to every available port. */ + for (i = 0; i < pThis->cPortsImpl; i++) + { + PAHCIPORT pAhciPort = &pThis->aPorts[i]; + PAHCIPORTR3 pAhciPortR3 = &pThisCC->aPorts[i]; + + RTStrPrintf(pAhciPortR3->szDesc, sizeof(pAhciPortR3->szDesc), "Port%u", i); + + /* + * Init interfaces. + */ + pAhciPortR3->IBase.pfnQueryInterface = ahciR3PortQueryInterface; + pAhciPortR3->IMediaExPort.pfnIoReqCompleteNotify = ahciR3IoReqCompleteNotify; + pAhciPortR3->IMediaExPort.pfnIoReqCopyFromBuf = ahciR3IoReqCopyFromBuf; + pAhciPortR3->IMediaExPort.pfnIoReqCopyToBuf = ahciR3IoReqCopyToBuf; + pAhciPortR3->IMediaExPort.pfnIoReqQueryBuf = ahciR3IoReqQueryBuf; + pAhciPortR3->IMediaExPort.pfnIoReqQueryDiscardRanges = ahciR3IoReqQueryDiscardRanges; + pAhciPortR3->IMediaExPort.pfnIoReqStateChanged = ahciR3IoReqStateChanged; + pAhciPortR3->IMediaExPort.pfnMediumEjected = ahciR3MediumEjected; + pAhciPortR3->IPort.pfnQueryDeviceLocation = ahciR3PortQueryDeviceLocation; + pAhciPortR3->IPort.pfnQueryScsiInqStrings = ahciR3PortQueryScsiInqStrings; + pAhciPort->fWrkThreadSleeping = true; + + /* Query per port configuration options if available. */ + PCFGMNODE pCfgPort = pHlp->pfnCFGMGetChild(pDevIns->pCfg, pAhciPortR3->szDesc); + if (pCfgPort) + { + rc = pHlp->pfnCFGMQueryBoolDef(pCfgPort, "Hotpluggable", &pAhciPort->fHotpluggable, true); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI configuration error: failed to read Hotpluggable as boolean")); + } + + /* + * Attach the block driver + */ + rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPortR3->IBase, &pAhciPortR3->pDrvBase, pAhciPortR3->szDesc); + if (RT_SUCCESS(rc)) + { + rc = ahciR3ConfigureLUN(pDevIns, pAhciPort, pAhciPortR3); + if (RT_FAILURE(rc)) + { + Log(("%s: Failed to configure the %s.\n", __FUNCTION__, pAhciPortR3->szDesc)); + return rc; + } + + /* Mark that a device is present on that port */ + if (i < 6) + pPciDev->abConfig[0x93] |= (1 << i); + + /* + * Init vendor product data. + */ + rc = ahciR3VpdInit(pDevIns, pAhciPort, pAhciPortR3, pAhciPortR3->szDesc); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pAhciPort->hEvtProcess); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("AHCI: Failed to create SUP event semaphore")); + + rc = PDMDevHlpThreadCreate(pDevIns, &pAhciPortR3->pAsyncIOThread, pAhciPortR3, ahciAsyncIOLoop, + ahciAsyncIOLoopWakeUp, 0, RTTHREADTYPE_IO, pAhciPortR3->szDesc); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("AHCI: Failed to create worker thread %s"), pAhciPortR3->szDesc); + } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + { + pAhciPortR3->pDrvBase = NULL; + pAhciPort->fPresent = false; + rc = VINF_SUCCESS; + LogRel(("AHCI: %s: No driver attached\n", pAhciPortR3->szDesc)); + } + else + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("AHCI: Failed to attach drive to %s"), pAhciPortR3->szDesc); + } + + /* + * 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 + AssertMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER, ("Failed to attach to status driver. rc=%Rrc\n", rc), + PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI cannot attach to status driver"))); + + /* + * Saved state. + */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, AHCI_SAVED_STATE_VERSION, sizeof(*pThis) + cbTotalBufferSize, NULL, + NULL, ahciR3LiveExec, NULL, + ahciR3SavePrep, ahciR3SaveExec, NULL, + ahciR3LoadPrep, ahciR3LoadExec, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Register the info item. + */ + char szTmp[128]; + RTStrPrintf(szTmp, sizeof(szTmp), "%s%d", pDevIns->pReg->szName, pDevIns->iInstance); + PDMDevHlpDBGFInfoRegister(pDevIns, szTmp, "AHCI info", ahciR3Info); + + return ahciR3ResetCommon(pDevIns); +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) ahciRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PAHCI pThis = PDMDEVINS_2_DATA(pDevIns, PAHCI); + + int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns)); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake0, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake1, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake2, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsLegacyFake3, ahciLegacyFakeWrite, ahciLegacyFakeRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortIdxData, ahciIdxDataWrite, ahciIdxDataRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ahciMMIOWrite, ahciMMIORead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +/** + * The device registration structure. + */ +const PDMDEVREG g_DeviceAHCI = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "ahci", + /* .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 = */ ~0U, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(AHCI), + /* .cbInstanceCC = */ sizeof(AHCICC), + /* .cbInstanceRC = */ sizeof(AHCIRC), + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "Intel AHCI controller.\n", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ ahciR3Construct, + /* .pfnDestruct = */ ahciR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ ahciR3Reset, + /* .pfnSuspend = */ ahciR3Suspend, + /* .pfnResume = */ ahciR3Resume, + /* .pfnAttach = */ ahciR3Attach, + /* .pfnDetach = */ ahciR3Detach, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ ahciR3PowerOff, + /* .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 = */ ahciRZConstruct, + /* .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 = */ ahciRZConstruct, + /* .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 */ |