DevAHCI.cpp revision e2ebb3d022edb845fd1158d6338e3def1d0e2159
/* $Id$ */
/** @file
* VBox storage devices: AHCI controller device (disk and cdrom).
* Implements the AHCI standard 1.1
*/
/*
* Copyright (C) 2006-2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/** @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 transferred in an asynchronous way using one thread per implemented
* port or using the new async completion interface which is still under
* development. [not quite up to date]
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
//#define DEBUG
#define LOG_GROUP LOG_GROUP_DEV_AHCI
#ifdef IN_RING3
# include <iprt/semaphore.h>
#endif
#include "ide.h"
#include "ATAController.h"
#include "VBoxDD.h"
#define AHCI_MAX_NR_PORTS_IMPL 30
#define AHCI_NR_COMMAND_SLOTS 32
#define AHCI_NR_OF_ALLOWED_BIGGER_LISTS 100
/** The current saved state version. */
#define AHCI_SAVED_STATE_VERSION 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
/**
* 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
/* MediaEventStatus */
#define ATA_EVENT_STATUS_UNCHANGED 0 /**< medium event status not changed */
#define ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED 4 /**< medium eject requested (eject button pressed) */
/* Media track type */
#define ATA_MEDIA_TYPE_UNKNOWN 0 /**< unknown CD type */
/** ATAPI sense info size. */
#define ATAPI_SENSE_SIZE 64
/* Command Header. */
typedef struct
{
/** Description Information. */
/** Command status. */
/** Command Table Base Address. */
/** Command Table Base Address - upper 32-bits. */
/** Reserved */
} CmdHdr;
/* Defines for the command header. */
#define AHCI_CMDHDR_PRDTL_MASK 0xffff0000
#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_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
/* For D2H FIS */
#define AHCI_CMDFIS_STS 2
#define AHCI_CMDFIS_ERR 3
/**
* Scatter gather list entry data.
*/
typedef struct AHCIPORTTASKSTATESGENTRY
{
/** Flag whether the buffer in the list is from the guest or an
* allocated temporary buffer because the segments in the guest
* are not sector aligned.
*/
bool fGuestMemory;
/** Flag dependent data. */
union
{
/** Data to handle direct mappings of guest buffers. */
struct
{
/** The page lock. */
} direct;
/** Data to handle temporary buffers. */
struct
{
/** The first segment in the guest which is not sector aligned. */
/** Number of unaligned buffers in the guest. */
/** Pointer to the start of the buffer. */
void *pvBuf;
} temp;
} u;
/** Pointer to a pointer of a scatter gather list entry. */
/** Pointer to a task state. */
typedef struct AHCIPORTTASKSTATE *PAHCIPORTTASKSTATE;
/**
* Data processing callback
*
* @returns VBox status.
* @param pAhciPortTaskState The task state.
*/
/** Pointer to a FNAHCIPOSTPROCESS() function. */
typedef FNAHCIPOSTPROCESS *PFNAHCIPOSTPROCESS;
/**
* Transfer type.
*/
typedef enum AHCITXDIR
{
/** Invalid */
AHCITXDIR_INVALID = 0,
/** None */
/** Read */
/** Write */
/** Flush */
/** Trim */
} AHCITXDIR;
/**
* Task state.
*/
typedef enum AHCITXSTATE
{
/** Invalid. */
AHCITXSTATE_INVALID = 0,
/** Task is not active. */
/** Task is active */
/** Task was canceled but the request didn't completed yet. */
/** 32bit hack. */
AHCITXSTATE_32BIT_HACK = 0x7fffffff
} AHCITXSTATE, *PAHCITXSTATE;
/**
* A task state.
*/
typedef struct AHCIPORTTASKSTATE
{
/** Task state. */
volatile AHCITXSTATE enmTxState;
/** Tag of the task. */
/** Command is queued. */
bool fQueued;
/** The command header for this task. */
/** The command Fis for this task. */
/** The ATAPI command data. */
/** Size of one sector for the ATAPI transfer. */
/** Physical address of the command header. - GC */
/** Data direction. */
/** Start offset. */
/** Number of bytes to transfer. */
/** ATA error register */
/** ATA status register */
/** How many entries would fit into the sg list. */
/** Number of used SG list entries. */
/** Pointer to the first entry of the scatter gather list. */
/** Number of scatter gather list entries. */
/** Total number of bytes the guest reserved for this request.
* Sum of all SG entries. */
/** Pointer to the first mapping information entry. */
/** Size of the temporary buffer for unaligned guest segments. */
/** Pointer to the temporary buffer. */
void *pvBufferUnaligned;
/** Number of times in a row the scatter gather list was too big. */
/** Post processing callback.
* If this is set we will use a buffer for the data
* and the callback copies the data to the destination. */
/** Pointer to the array of PDM ranges. */
/** Number of entries in the array. */
unsigned cRanges;
/**
* Notifier queue item.
*/
typedef struct DEVPORTNOTIFIERQUEUEITEM
{
/** The core part owned by the queue manager. */
/** The port to process. */
/**
* @implements PDMIBASE
* @implements PDMIBLOCKPORT
* @implements PDMIBLOCKASYNCPORT
* @implements PDMIMOUNTNOTIFY
*/
typedef struct AHCIPort
{
/** Pointer to the device instance - HC ptr */
/** Pointer to the device instance - R0 ptr */
/** Pointer to the device instance - RC ptr. */
#if HC_ARCH_BITS == 64
#endif
/** Pointer to the parent AHCI structure - R3 ptr. */
/** Pointer to the parent AHCI structure - R0 ptr. */
/** Pointer to the parent AHCI structure - RC ptr. */
/** Command List Base Address. */
/** Command List Base Address upper bits. */
/** FIS Base Address. */
/** FIS Base Address upper bits. */
/** Interrupt Status. */
/** Interrupt Enable. */
/** Command. */
/** Task File Data. */
/** Signature */
/** Serial ATA Status. */
/** Serial ATA Control. */
/** Serial ATA Error. */
/** Serial ATA Active. */
/** Command Issue. */
#if HC_ARCH_BITS == 64
#endif
/** Command List Base Address */
volatile RTGCPHYS GCPhysAddrClb;
/** FIS Base Address */
volatile RTGCPHYS GCPhysAddrFb;
/** Current number of active tasks. */
volatile uint32_t cTasksActive;
/** Device is powered on. */
bool fPoweredOn;
/** Device has spun up. */
bool fSpunUp;
/** First D2H FIS was send. */
bool fFirstD2HFisSend;
/** Mark the drive as having a non-rotational medium (i.e. as a SSD). */
bool fNonRotational;
bool fATAPI;
/** Passthrough SCSI commands. */
bool fATAPIPassthrough;
/** Flag whether this port is in a reset state. */
volatile bool fPortReset;
/** If we use the new async interface. */
bool fAsyncInterface;
/** Flag if we are in a device reset. */
bool fResetDevice;
/** Flag whether the I/O thread idles. */
volatile bool fAsyncIOThreadIdle;
/** Flag whether the port is in redo task mode. */
volatile bool fRedo;
#if HC_ARCH_BITS == 64
bool fAlignment2;
#endif
/** Number of total sectors. */
/** Currently configured number of sectors in a multi-sector transfer. */
/** ATAPI sense data. */
/** HACK: Countdown till we report a newly unmounted drive as mounted. */
/** The same for GET_EVENT_STATUS for mechanism */
volatile uint32_t MediaEventStatus;
/** Media type if known. */
volatile uint32_t MediaTrackType;
/** The LUN. */
/** 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;
/** 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;
#if HC_ARCH_BITS == 64
#endif
/** Device specific settings (R3 only stuff). */
/** Pointer to the attached driver's base interface. */
/** Pointer to the attached driver's block interface. */
/** Pointer to the attached driver's async block interface. */
/** Pointer to the attached driver's block bios interface. */
/** Pointer to the attached driver's mount interface. */
/** The base interface. */
/** The block port interface. */
/** The optional block async port interface. */
/** The mount notify interface. */
/** Physical geometry of this image. */
/** The status LED state for this drive. */
#if HC_ARCH_BITS == 64
#endif
/** Async IO Thread. */
/** Request semaphore. */
/**
* Array of cached tasks. The tag number is the index value.
* Only used with the async interface.
*/
/** First task throwing an error. */
#if HC_ARCH_BITS == 32
#endif
/** Release statistics: number of DMA commands. */
/** Release statistics: number of bytes written. */
/** Release statistics: number of bytes read. */
/** Release statistics: Number of I/O requests processed per second. */
#ifdef VBOX_WITH_STATISTICS
/** Statistics: Time to complete one request. */
/** Statistics: Time to map requests into R3. */
/** Statistics: Amount of time to destroy a list. */
#endif /* VBOX_WITH_STATISTICS */
/** The serial numnber to use for IDENTIFY DEVICE commands. */
/** The firmware revision to use for IDENTIFY DEVICE commands. */
/** The model number to use for IDENTIFY DEVICE commands. */
/** The vendor identification string for SCSI INQUIRY commands. */
/** The product identification string for SCSI INQUIRY commands. */
/** The revision string for SCSI INQUIRY commands. */
/** Error counter */
} AHCIPort;
/** Pointer to the state of an AHCI port. */
/**
* Main AHCI device state.
*
* @implements PDMILEDPORTS
*/
typedef struct AHCI
{
/** The PCI device structure. */
/** Pointer to the device instance - R3 ptr */
/** Pointer to the device instance - R0 ptr */
/** Pointer to the device instance - RC ptr. */
#if HC_ARCH_BITS == 64
#endif
/** Status LUN: The base interface. */
/** Status LUN: Leds interface. */
/** Status LUN: Partner of ILeds. */
/** Status LUN: Media Notifys. */
#if HC_ARCH_BITS == 32
#endif
/** Base address of the MMIO region. */
/** Global Host Control register of the HBA */
/** HBA Capabilities - Readonly */
/** HBA Control */
/** Interrupt Status */
/** Ports Implemented - Readonly */
/** AHCI Version - Readonly */
/** Command completion coalescing control */
/** Command completion coalescing ports */
/** Index register for BIOS access. */
#if HC_ARCH_BITS == 64
#endif
/** Countdown timer for command completion coalescing - R3 ptr */
/** Countdown timer for command completion coalescing - R0 ptr */
/** Countdown timer for command completion coalescing - RC ptr */
#if HC_ARCH_BITS == 64
#endif
/** Queue to send tasks to R3. - HC ptr */
/** Queue to send tasks to R3. - HC ptr */
/** Queue to send tasks to R3. - RC ptr */
#if HC_ARCH_BITS == 64
#endif
/** Which port number is used to mark an CCC interrupt */
#if HC_ARCH_BITS == 64
#endif
/** Timeout value */
/** Number of completions used to assert an interrupt */
/** Current number of completed commands */
/** Register structure per port */
/** Needed values for the emulated ide channels. */
/** The critical section. */
/** Bitmask of ports which asserted an interrupt. */
volatile uint32_t u32PortsInterrupted;
/** Device is in a reset state. */
bool fReset;
/** Supports 64bit addressing */
bool f64BitAddr;
/** GC enabled. */
bool fGCEnabled;
/** R0 enabled. */
bool fR0Enabled;
/** If the new async interface is used if available. */
/** Indicates that PDMDevHlpAsyncNotificationCompleted should be called when
* a port is entering the idle state. */
bool volatile fSignalIdle;
/** Flag whether the controller has BIOS access enabled. */
bool fBootable;
/** Number of usable ports on this controller. */
/** Number of usable command slots for each port. */
/** Flag whether we have written the first 4bytes in an 8byte MMIO write successfully. */
volatile bool f8ByteMMIO4BytesWrittenSuccessfully;
} AHCI;
/** Pointer to the state of an AHCI device. */
/**
* Scatter gather list entry.
*/
typedef struct
{
/** Data Base Address. */
/** Data Base Address - Upper 32-bits. */
/** Reserved */
/** Description information. */
} SGLEntry;
/** Defines for a scatter gather list entry. */
#define SGLENTRY_DBA_READONLY ~(RT_BIT(0))
#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_ISS_GEN1 RT_BIT(0)
#define AHCI_HBA_CTRL_HR RT_BIT(0)
/* Defines for the HBA Version register - Readonly (We support AHCI 1.0) */
#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_CC 0xff00
#define AHCI_HBA_CCC_CTL_CC_SET(x) (x << 8)
#define AHCI_HBA_CCC_CTL_INT 0xf8
#define AHCI_HBA_CCC_CTL_INT_SET(x) (x << 3)
#define AHCI_HBA_CCC_CTL_EN RT_BIT(0)
/* Defines for the port registers. */
#define AHCI_PORT_REGISTER_SIZE 0x80
#define AHCI_PORT_IS_DHRS RT_BIT(0)
#define AHCI_PORT_IE_DHRE RT_BIT(0)
#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_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_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_DET_GET(x) (x & AHCI_PORT_SCTL_DET)
#define AHCI_PORT_TFD_ERR RT_BIT(0)
/* 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.
*/
/** Mask to get the LBA value from a LBA range. */
/** Mas to get the length value from a LBA range. */
/** Returns the length of the range in sectors. */
/**
* AHCI register operator.
*/
typedef struct ahci_opreg
{
const char *pszName;
} AHCIOPREG;
/**
* AHCI port register operator.
*/
typedef struct pAhciPort_opreg
{
const char *pszName;
#ifndef VBOX_DEVICE_STRUCT_TESTCASE
#ifdef IN_RING3
static int ahciScatterGatherListCopyFromBuffer(PAHCIPORTTASKSTATE pAhciPortTaskState, void *pvBuf, size_t cbBuf);
static int ahciScatterGatherListCreate(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, bool fReadonly);
static int ahciScatterGatherListDestroy(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState);
static void ahciScatterGatherListGetTotalBufferSize(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState);
static int ahciScatterGatherListCreateSafe(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState,
bool fReadonly, unsigned cSGEntriesProcessed);
#endif
#define PDMIMOUNT_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIPort, IMount)) )
#define PDMIMOUNTNOTIFY_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIPort, IMountNotify)) )
#define PDMIBASE_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIPort, IBase)) )
#define PDMIBLOCKPORT_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCIPort, IPort)) )
#define PDMIBASE_2_PAHCI(pInterface) ( (PAHCI)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCI, IBase)) )
#define PDMILEDPORTS_2_PAHCI(pInterface) ( (PAHCI)((uintptr_t)(pInterface) - RT_OFFSETOF(AHCI, ILeds)) )
#if 1
#else
#endif
#ifdef IN_RING3
# ifdef LOG_USE_C99
# define ahciLog(a) \
# else
# define ahciLog(a) \
# endif
# ifdef LOG_USE_C99
# define ahciLog(a) \
# else
# define ahciLog(a) \
# endif
# ifdef LOG_USE_C99
# define ahciLog(a) \
# else
# define ahciLog(a) \
# endif
#endif
/**
* Update PCI IRQ levels
*/
{
}
/**
* Updates the IRQ level and sets port bit in the global interrupt status register of the HBA.
*/
{
if (rc != VINF_SUCCESS)
return rc;
{
{
pAhci->uCccCurrentNr++;
{
/* Reset command completion coalescing state. */
pAhci->uCccCurrentNr = 0;
{
}
}
}
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.
*/
{
}
}
}
return VINF_SUCCESS;
}
#ifdef IN_RING3
/*
* Assert irq when an CCC timeout occurs
*/
{
}
#endif
{
/* Update the CI register first. */
&& u32Value > 0)
{
/*
* Clear all tasks which are already marked as busy. The guest
* shouldn't write already busy tasks actually.
*/
/* Send a notification to R3 if u32TasksNew was before our write. */
{
PDEVPORTNOTIFIERQUEUEITEM pItem = (PDEVPORTNOTIFIERQUEUEITEM)PDMQueueAlloc(ahci->CTX_SUFF(pNotifierQueue));
}
}
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
{
ahciLog(("%s: read regSACT=%#010x regCI=%#010x u32TasksFinished=%#010x\n",
return VINF_SUCCESS;
}
{
if ( (u32Value & AHCI_PORT_SERR_X)
{
}
if ( (u32Value & AHCI_PORT_SERR_N)
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
{
AHCI_PORT_SCTL_IPM_GET(u32Value), AHCI_PORT_SCTL_SPD_GET(u32Value), AHCI_PORT_SCTL_DET_GET(u32Value)));
#ifndef IN_RING3
return VINF_IOM_HC_MMIO_WRITE;
#else
{
bool fAllTasksCanceled;
/* Cancel all tasks first. */
pAhciPort->fFirstD2HFisSend = false;
}
{
{
/* Signature for SATA device. */
else
(0x03 << 0); /* Device detected and communication established. */
/*
* Use the maximum allowed speed.
* (Not that it changes anything really)
*/
{
case 0x01:
break;
case 0x02:
case 0x00:
default:
break;
}
/* We received a COMINIT from the device. Tell the guest. */
{
{
}
}
}
}
return VINF_SUCCESS;
#endif
}
{
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
/**
* Read from the port command register.
*/
{
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,
return VINF_SUCCESS;
}
/**
* Write to the port command register.
* This is the register where all the data transfer is started
*/
{
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",
(u32Value & AHCI_PORT_CMD_ST)));
{
if (u32Value & AHCI_PORT_CMD_CLO)
{
/* 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. */
}
else
{
/* Clear command issue register. */
/* Clear current command slot. */
u32Value &= ~AHCI_PORT_CMD_CR;
}
}
{
if ((u32Value & AHCI_PORT_CMD_POD) && (pAhciPort->regCMD & AHCI_PORT_CMD_CPS) && !pAhciPort->fPoweredOn)
{
pAhciPort->fPoweredOn = true;
/*
* Set states in the Port Signature and SStatus registers.
*/
else
(0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
(0x03 << 0); /* Device detected and communication established. */
{
#ifndef IN_RING3
return VINF_IOM_HC_MMIO_WRITE;
#else
{
}
#endif
}
}
{
}
}
if (u32Value & AHCI_PORT_CMD_FRE)
{
/* Send the first D2H FIS only if it wasn't already send. */
if ( !pAhciPort->fFirstD2HFisSend
{
#ifndef IN_RING3
return VINF_IOM_HC_MMIO_WRITE;
#else
pAhciPort->fFirstD2HFisSend = true;
#endif
}
}
else if (!(u32Value & AHCI_PORT_CMD_FRE))
{
u32Value &= ~AHCI_PORT_CMD_FR;
}
return VINF_SUCCESS;
}
/**
* Read from the port interrupt enable register.
*/
{
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,
return VINF_SUCCESS;
}
/**
* Write to the port interrupt enable register.
*/
{
int rc = VINF_SUCCESS;
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",
(u32Value & AHCI_PORT_IE_DHRE)));
/* Check if some a interrupt status bit changed*/
if (u32Value & u32IntrStatus)
if (rc == VINF_SUCCESS)
return rc;
}
/**
* Read from the port interrupt status register.
*/
{
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,
return VINF_SUCCESS;
}
/**
* Write to the port interrupt status register.
*/
{
return VINF_SUCCESS;
}
/**
* Read from the port FIS base address upper 32bit register.
*/
{
return VINF_SUCCESS;
}
/**
* Write to the port FIS base address upper 32bit register.
*/
{
return VINF_SUCCESS;
}
/**
* Read from the port FIS base address register.
*/
{
return VINF_SUCCESS;
}
/**
* Write to the port FIS base address register.
*/
{
return VINF_SUCCESS;
}
/**
* Write to the port command list base address upper 32bit register.
*/
{
return VINF_SUCCESS;
}
/**
* Read from the port command list base address upper 32bit register.
*/
{
return VINF_SUCCESS;
}
/**
* Read from the port command list base address register.
*/
{
return VINF_SUCCESS;
}
/**
* Write to the port command list base address register.
*/
{
return VINF_SUCCESS;
}
/**
* Read from the global Version register.
*/
{
return VINF_SUCCESS;
}
/**
* Read from the global Ports implemented register.
*/
{
return VINF_SUCCESS;
}
/**
* Write to the global interrupt status register.
*/
{
int rc;
if (rc != VINF_SUCCESS)
return rc;
if (u32Value > 0)
{
/*
* Clear the interrupt only if no port has signalled
* an interrupt and the guest has cleared all set interrupt
* notification bits.
*/
bool fClear = true;
if (fClear)
{
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)
{
{
fClear = false;
break;
}
}
i++;
}
}
if (fClear)
else
{
Log(("%s: Not clearing interrupt: u32PortsInterrupted=%#010x\n", __FUNCTION__, ahci->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.
*/
}
}
return VINF_SUCCESS;
}
/**
* Read from the global interrupt status register.
*/
{
int rc;
if (rc != VINF_SUCCESS)
return rc;
Log(("%s: read regHbaIs=%#010x u32PortsInterrupted=%#010x\n", __FUNCTION__, ahci->regHbaIs, u32PortsInterrupted));
#ifdef LOG_ENABLED
unsigned i;
for (i = 0; i < ahci->cPortsImpl; i++)
{
Log((" P%d", i));
}
Log(("\n"));
#endif
return VINF_SUCCESS;
}
/**
* Write to the global control register.
*/
{
Log(("%s: write u32Value=%#010x\n"
"%s: AE=%d IE=%d HR=%d\n",
(u32Value & AHCI_HBA_CTRL_HR)));
#ifndef IN_RING3
return VINF_IOM_HC_MMIO_WRITE;
#else
return VINF_SUCCESS;
#endif
}
/**
* Read the global control register.
*/
{
Log(("%s: read regHbaCtrl=%#010x\n"
"%s: AE=%d IE=%d HR=%d\n",
__FUNCTION__, (ahci->regHbaCtrl & AHCI_HBA_CTRL_AE) >> 31, (ahci->regHbaCtrl & AHCI_HBA_CTRL_IE) >> 1,
return VINF_SUCCESS;
}
/**
* Read the global capabilities register.
*/
{
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__, (ahci->regHbaCap & AHCI_HBA_CAP_S64A) >> 31, (ahci->regHbaCap & AHCI_HBA_CAP_SNCQ) >> 30,
return VINF_SUCCESS;
}
/**
* Write to the global command completion coalescing control register.
*/
{
Log(("%s: write u32Value=%#010x\n"
"%s: TV=%d CC=%d INT=%d EN=%d\n",
if (u32Value & AHCI_HBA_CCC_CTL_EN)
{
/* Arm the timer */
}
else
{
}
return VINF_SUCCESS;
}
/**
* Read the global command completion coalescing control register.
*/
{
Log(("%s: read regHbaCccCtl=%#010x\n"
"%s: TV=%d CC=%d INT=%d EN=%d\n",
__FUNCTION__, AHCI_HBA_CCC_CTL_TV_GET(ahci->regHbaCccCtl), AHCI_HBA_CCC_CTL_CC_GET(ahci->regHbaCccCtl),
return VINF_SUCCESS;
}
/**
* Write to the global command completion coalescing ports register.
*/
{
return VINF_SUCCESS;
}
/**
* Read the global command completion coalescing ports register.
*/
{
#ifdef LOG_ENABLED
unsigned i;
for (i = 0; i < ahci->cPortsImpl; i++)
{
Log((" P%d", i));
}
Log(("\n"));
#endif
return VINF_SUCCESS;
}
/**
* Invalid write to global register
*/
{
return VINF_SUCCESS;
}
/**
* Invalid Port write.
*/
{
return VINF_SUCCESS;
}
/**
* Invalid Port read.
*/
{
return VINF_SUCCESS;
}
/**
* Register descriptor table for global HBA registers
*/
{
};
/**
* Register descriptor table for port registers
*/
static const AHCIPORTOPREG g_aPortOpRegs[] =
{
};
#ifdef IN_RING3
/**
* Reset initiated by system software for one port.
*
* @param pAhciPort The port to reset.
*/
{
bool fAllTasksCanceled;
/* Cancel all tasks first. */
AHCI_PORT_CMD_HPCP | /* Hotplugging supported. */
AHCI_PORT_CMD_SUD | /* Device has spun up. */
AHCI_PORT_CMD_POD; /* Port is powered on. */
pAhciPort->fResetDevice = false;
pAhciPort->fPoweredOn = true;
pAhciPort->u32TasksNew = 0;
pAhciPort->u32TasksFinished = 0;
pAhciPort->cTasksActive = 0;
{
if (pAhciPort->fPoweredOn)
{
/*
* Set states in the Port Signature and SStatus registers.
*/
else
(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.
*/
{
/* Reset the address registers. */
/* 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.
*/
{
for (unsigned i = 0; i < cPorts; i++)
uPortsImplemented |= (1 << i);
return uPortsImplemented;
}
/**
* Reset the entire HBA.
*
* @param pThis The HBA state.
*/
{
unsigned i;
int rc = VINF_SUCCESS;
LogFlow(("Reset the HBA controller\n"));
/* Stop the CCC timer. */
{
if (RT_FAILURE(rc))
}
/* Reset every port */
for (i = 0; i < pThis->cPortsImpl; i++)
{
}
/* Init Global registers */
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 */
pThis->regHbaCccCtl = 0;
pThis->regHbaCccPorts = 0;
pThis->uCccTimeout = 0;
pThis->uCccPortNr = 0;
pThis->f64BitAddr = false;
pThis->u32PortsInterrupted = 0;
pThis->f8ByteMMIO4BytesWrittenSuccessfully = false;
/* Clear the HBA Reset bit */
}
#endif
/**
* Reads from a AHCI controller register.
*
* @returns VBox status code.
*
* @param pAhci The AHCI instance.
* @param uReg The register to write.
* @param pv Where to store the result.
* @param cb Number of bytes read.
*/
{
int rc = VINF_SUCCESS;
/*
* 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)
{
{
}
else
{
Log3(("%s: Trying to read global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs)));
}
}
else
{
/* Calculate accessed port. */
{
}
else
{
Log3(("%s: Trying to read port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs)));
}
/*
* Windows Vista tries to read one byte from some registers instead of four.
* Correct the value according to the read size.
*/
{
switch (cb)
{
case 1:
{
iRegOffset &= 3;
uNewValue = p[iRegOffset];
/* Clear old value */
break;
}
default:
AssertMsgFailed(("%s: unsupported access width cb=%d iPort=%x iRegOffset=%x iReg=%x!!!\n",
}
}
}
return rc;
}
/**
* Writes a value to one of the AHCI controller registers.
*
* @returns VBox status code.
*
* @param pAhci The AHCI instance.
* @param uReg The register to write.
* @param pv Where to fetch the result.
* @param cb Number of bytes to write.
*/
{
int rc = VINF_SUCCESS;
if (uReg < AHCI_HBA_GLOBAL_SIZE)
{
Log3(("Write global HBA register\n"));
{
}
else
{
Log3(("%s: Trying to write global register %u/%u!!!\n", __FUNCTION__, iReg, RT_ELEMENTS(g_aOpRegs)));
rc = VINF_SUCCESS;
}
}
else
{
Log3(("Write Port register\n"));
/* Calculate accessed port. */
{
}
else
{
Log3(("%s: Trying to write port %u register %u/%u!!!\n", __FUNCTION__, iPort, iReg, RT_ELEMENTS(g_aPortOpRegs)));
rc = VINF_SUCCESS;
}
}
return rc;
}
/**
* Memory mapped I/O Handler for read operations.
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument.
* @param GCPhysAddr Physical address (in GC) where the read starts.
* @param pv Where to store the result.
* @param cb Number of bytes read.
*/
PDMBOTHCBDECL(int) ahciMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
{
int rc = VINF_SUCCESS;
/* Break up 64 bits reads into two dword reads. */
if (cb == 8)
{
if (RT_FAILURE(rc))
return rc;
}
Log2(("#%d ahciMMIORead: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp rc=%Rrc\n",
Log2(("#%d ahciMMIORead: return pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp rc=%Rrc\n",
return rc;
}
/**
* Memory mapped I/O Handler for write operations.
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument.
* @param GCPhysAddr Physical address (in GC) where the read starts.
* @param pv Where to fetch the result.
* @param cb Number of bytes to write.
*/
PDMBOTHCBDECL(int) ahciMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
{
int rc = VINF_SUCCESS;
/* Break up 64 bits writes into two dword writes. */
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.
*/
{
if (rc != VINF_SUCCESS)
return rc;
pAhci->f8ByteMMIO4BytesWrittenSuccessfully = true;
}
/*
* Reset flag again so that the first 4 bytes are written again on the next
* 8byte MMIO access.
*/
if (rc == VINF_SUCCESS)
pAhci->f8ByteMMIO4BytesWrittenSuccessfully = false;
return rc;
}
Log2(("#%d ahciMMIOWrite: pvUser=%p:{%.*Rhxs} cb=%d GCPhysAddr=%RGp\n",
/* Validate access. */
{
return VINF_SUCCESS;
}
if (GCPhysAddr & 0x3)
{
return VINF_SUCCESS;
}
/*
* If the access offset is smaller than 100h the guest accesses the global registers.
* Otherwise it accesses the registers of a port.
*/
return rc;
}
PDMBOTHCBDECL(int) ahciIOPortWrite1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
}
PDMBOTHCBDECL(int) ahciIOPortRead1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
}
PDMBOTHCBDECL(int) ahciIOPortWrite2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
}
PDMBOTHCBDECL(int) ahciIOPortRead2(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
}
PDMBOTHCBDECL(int) ahciLegacyFakeWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
AssertMsgFailed(("Should not happen\n"));
return VINF_SUCCESS;
}
PDMBOTHCBDECL(int) ahciLegacyFakeRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
AssertMsgFailed(("Should not happen\n"));
return VINF_SUCCESS;
}
/**
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument.
* @param Port Port address where the write starts.
* @param pv Where to fetch the result.
* @param cb Number of bytes to write.
*/
PDMBOTHCBDECL(int) ahciIdxDataWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
int rc = VINF_SUCCESS;
{
if (iReg == 0)
{
/* Write the index register. */
}
else
{
if (rc == VINF_IOM_HC_MMIO_WRITE)
}
}
/* else: ignore */
Log2(("#%d ahciIdxDataWrite: pu32=%p:{%.*Rhxs} cb=%d Port=%#x rc=%Rrc\n",
return rc;
}
/**
*
* @returns VBox status code.
*
* @param pDevIns The device instance.
* @param pvUser User argument.
* @param Port Port address where the read starts.
* @param pv Where to fetch the result.
* @param cb Number of bytes to write.
*/
PDMBOTHCBDECL(int) ahciIdxDataRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
int rc = VINF_SUCCESS;
{
if (iReg == 0)
{
/* Read the index register. */
}
else
{
if (rc == VINF_IOM_HC_MMIO_READ)
}
}
else
Log2(("#%d ahciIdxDataRead: pu32=%p:{%.*Rhxs} cb=%d Port=%#x rc=%Rrc\n",
return rc;
}
#ifndef IN_RING0
/**
* Port I/O Handler for primary port range IN string operations.
* @see FNIOMIOPORTINSTRING for details.
*/
PDMBOTHCBDECL(int) ahciIOPortReadStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
{
}
/**
* Port I/O Handler for primary port range OUT string operations.
* @see FNIOMIOPORTOUTSTRING for details.
*/
PDMBOTHCBDECL(int) ahciIOPortWriteStr1(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
{
}
#endif /* !IN_RING0 */
#ifdef IN_RING3
static DECLCALLBACK(int) ahciR3MMIOMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
{
int rc = VINF_SUCCESS;
/* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
if (RT_FAILURE(rc))
return rc;
if (pThis->fR0Enabled)
{
if (RT_FAILURE(rc))
return rc;
}
if (pThis->fGCEnabled)
{
if (RT_FAILURE(rc))
return rc;
}
return rc;
}
/**
* Map the legacy I/O port ranges to make Solaris work with the controller.
*/
static DECLCALLBACK(int) ahciR3LegacyFakeIORangeMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
{
int rc = VINF_SUCCESS;
Log2(("%s: registering fake I/O area at GCPhysAddr=%RGp cb=%u\n", __FUNCTION__, GCPhysAddress, cb));
/* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
if (RT_FAILURE(rc))
return rc;
if (pThis->fR0Enabled)
{
if (RT_FAILURE(rc))
return rc;
}
if (pThis->fGCEnabled)
{
if (RT_FAILURE(rc))
return rc;
}
return rc;
}
/**
*/
static DECLCALLBACK(int) ahciR3IdxDataIORangeMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
{
int rc = VINF_SUCCESS;
Log2(("%s: registering fake I/O area at GCPhysAddr=%RGp cb=%u\n", __FUNCTION__, GCPhysAddress, cb));
/* We use the assigned size here, because we currently only support page aligned MMIO ranges. */
if (RT_FAILURE(rc))
return rc;
if (pThis->fR0Enabled)
{
if (RT_FAILURE(rc))
return rc;
}
if (pThis->fGCEnabled)
{
if (RT_FAILURE(rc))
return rc;
}
return rc;
}
/* -=-=-=-=-=- 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)
{
if (iLUN < AHCI_MAX_NR_PORTS_IMPL)
{
return VINF_SUCCESS;
}
return VERR_PDM_LUN_NOT_FOUND;
}
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
{
return NULL;
}
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
{
return NULL;
}
static DECLCALLBACK(int) ahciR3PortQueryDeviceLocation(PPDMIBLOCKPORT pInterface, const char **ppcszController,
{
return VINF_SUCCESS;
}
#ifdef DEBUG
/**
* Dump info about the FIS
*
* @returns nothing
* @param pAhciPort The port the command FIS was read from.
* @param cmdFis The FIS to print info from.
*/
{
/* Print FIS type. */
switch (cmdFis[AHCI_CMDFIS_TYPE])
{
case AHCI_CMDFIS_TYPE_H2D:
{
{
}
else
{
}
ahciLog(("%s: CMD=%#04x \"%s\"\n", __FUNCTION__, cmdFis[AHCI_CMDFIS_CMD], ATACmdText(cmdFis[AHCI_CMDFIS_CMD])));
{
}
}
break;
case AHCI_CMDFIS_TYPE_D2H:
{
}
break;
{
}
break;
{
}
break;
{
}
break;
{
}
break;
case AHCI_CMDFIS_TYPE_DATA:
{
}
break;
default:
break;
}
}
/**
* Dump info about the command header
*
* @returns nothing
* @param pAhciPort Pointer to the port the command header was read from.
* @param pCmdHdr The command header to print info from.
*/
{
ahciLog(("%s: Number of Scatter/Gatther List entries: %u\n", __FUNCTION__, AHCI_CMDHDR_PRDTL_ENTRIES(pCmdHdr->u32DescInf)));
else
else
ahciLog(("%s: Command FIS length %u DW\n", __FUNCTION__, (pCmdHdr->u32DescInf & AHCI_CMDHDR_CFL_MASK)));
}
#endif /* DEBUG */
/**
* Post the first D2H FIS from the device into guest memory.
*
* @returns nothing
* @param pAhciPort Pointer to the port which "receives" the FIS.
*/
{
pAhciPort->fFirstD2HFisSend = true;
/* Set the signature based on the device type. */
{
}
else
{
}
}
/**
* Post the FIS in the memory area allocated by the guest and set interrupt if necessary.
*
* @returns VBox status code
* @param pAhciPort The port which "receives" the FIS.
* @param uFisType The type of the FIS.
* @param pCmdFis Pointer to the FIS which is to be posted into memory.
*/
{
int rc = VINF_SUCCESS;
unsigned cbFis = 0;
{
/* Determine the offset and size of the FIS based on uFisType. */
switch (uFisType)
{
case AHCI_CMDFIS_TYPE_D2H:
{
}
break;
{
}
break;
{
}
break;
{
}
break;
default:
/*
* We should post the unknown FIS into memory too but this never happens because
* we know which FIS types we generate. ;)
*/
}
/* Post the FIS into memory. */
ahciLog(("%s: PDMDevHlpPhysWrite GCPhysAddrRecFis=%RGp cbFis=%u\n", __FUNCTION__, GCPhysAddrRecFis, cbFis));
}
return rc;
}
{
}
{
}
{
}
{
}
{
}
{
}
{
iATAPILBA += 150;
}
{
}
{
pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] & ~7)
}
static void atapiCmdError(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, const uint8_t *pabATAPISense, size_t cbATAPISense)
{
Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] & ~7) |
memcpy(pAhciPort->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(pAhciPort->abATAPISense)));
}
/** @todo deprecated function - doesn't provide enough info. Replace by direct
* calls to atapiCmdError() with full data. */
static void atapiCmdErrorSimple(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
{
}
{
{
if (*pbSrc)
else
pbDst[i] = ' ';
}
}
{
{
if (*pbSrc)
else
}
}
{
size_t i;
for (i = 0; i < count; i++)
{
u8Sum += *p++;
}
}
{
uint16_t *p;
int rc = VINF_SUCCESS;
memset(p, 0, 512);
p[0] = RT_H2LE_U16(0x0040);
/* Block size; obsolete, but required for the BIOS. */
ataPadString((uint8_t *)(p + 10), pAhciPort->szSerialNumber, AHCI_SERIAL_NUMBER_LENGTH); /* serial number */
ataPadString((uint8_t *)(p + 23), pAhciPort->szFirmwareRevision, AHCI_FIRMWARE_REVISION_LENGTH); /* firmware version */
#if ATA_MAX_MULT_SECTORS > 1
#endif
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)
{
}
else
{
/* Report maximum number of sectors possible with LBA28 */
}
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 */
{
}
else
{
}
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[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[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */
if (pAhciPort->fNonRotational)
if (pAhciPort->pDrvBlock->pfnDiscard) /** @todo: Set bit 14 in word 69 too? (Deterministic read after TRIM). */
/* The following are SATA specific */
p[75] = RT_H2LE_U16(pAhciPort->CTX_SUFF(pAhci)->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 */
return VINF_SUCCESS;
}
/**
*/
typedef enum ATAPIFN
{
ATAFN_SS_NULL = 0,
} ATAPIFN;
/**
* Make sure ATAFNSS and this array match!
*/
{
NULL,
};
static int atapiIdentifySS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
uint16_t p[256];
memset(p, 0, 512);
/* Removable CDROM, 50us response, 12 byte packets */
ataPadString((uint8_t *)(p + 10), pAhciPort->szSerialNumber, AHCI_SERIAL_NUMBER_LENGTH); /* serial number */
ataPadString((uint8_t *)(p + 23), pAhciPort->szFirmwareRevision, AHCI_FIRMWARE_REVISION_LENGTH); /* firmware version */
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[86] = RT_H2LE_U16(0);
p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, pAhciPort->uATATransferMode)); /* UDMA modes supported / mode enabled */
/* The following are SATA specific */
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. */
return VINF_SUCCESS;
}
static int atapiReadCapacitySS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiReadDiscInformationSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
aBuf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiReadTrackInformationSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
if ((pAhciPortTaskState->aATAPICmd[1] & 0x03) != 1 || ataBE2H_U32(&pAhciPortTaskState->aATAPICmd[2]) != 1)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return VINF_SUCCESS;
}
aBuf[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | (1 << 0); /* not reserved track, not blank, not packet writing, not fixed packet, data mode 1 */
aBuf[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static size_t atapiGetConfigurationFillFeatureListProfiles(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
return 0;
/* The MMC-3 spec says that DVD-ROM read capability should be reported
* before CD-ROM read capability. */
return 3*4; /* Header + 2 profiles entries */
}
static size_t atapiGetConfigurationFillFeatureCore(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
if (cbBuf < 12)
return 0;
/* Rest is reserved. */
return 12;
}
static size_t atapiGetConfigurationFillFeatureMorphing(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
if (cbBuf < 8)
return 0;
/* Rest is reserved. */
return 8;
}
static size_t atapiGetConfigurationFillFeatureRemovableMedium(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
if (cbBuf < 8)
return 0;
/* Tray type loading | Load | Eject | !Pvnt Jmpr | !DBML | Lock */
/* Rest is reserved. */
return 8;
}
static size_t atapiGetConfigurationFillFeatureRandomReadable(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
if (cbBuf < 12)
return 0;
/* Rest is reserved. */
return 12;
}
static size_t atapiGetConfigurationFillFeatureCDRead(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
if (cbBuf < 8)
return 0;
/* Rest is reserved. */
return 8;
}
static size_t atapiGetConfigurationFillFeaturePowerManagement(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
if (cbBuf < 4)
return 0;
return 4;
}
static size_t atapiGetConfigurationFillFeatureTimeout(PAHCIPort pAhciPort, uint8_t *pbBuf, size_t cbBuf)
{
if (cbBuf < 8)
return 0;
return 8;
}
static int atapiGetConfigurationSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
/* Accept valid request types only, and only starting feature 0. */
if ((pAhciPortTaskState->aATAPICmd[1] & 0x03) == 3 || ataBE2H_U16(&pAhciPortTaskState->aATAPICmd[2]) != 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return VINF_SUCCESS;
}
/** @todo implement switching between CD-ROM and DVD-ROM profile (the only
* way to differentiate them right now is based on the image size). */
if (pAhciPort->cTotalSectors)
else
cbBuf -= 8;
pbBuf += 8;
/* Set data length now. */
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiGetEventStatusNotificationSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
{
/* no asynchronous operation supported */
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return VINF_SUCCESS;
}
do
{
switch (OldStatus)
{
/* mount */
break;
/* umount */
break;
case ATA_EVENT_STATUS_MEDIA_EJECT_REQUESTED: /* currently unused */
break;
default:
break;
}
*pcbData = ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&abBuf[0], sizeof(abBuf));
return VINF_SUCCESS;
}
{
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiModeSenseErrorRecoverySS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
aBuf[3] = 0;
aBuf[4] = 0;
aBuf[5] = 0;
aBuf[6] = 0;
aBuf[7] = 0;
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiModeSenseCDStatusSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
aBuf[3] = 0;
aBuf[4] = 0;
aBuf[5] = 0;
aBuf[6] = 0;
aBuf[7] = 0;
/* The following claims we support audio play. This is obviously false,
* but the Linux generic CDROM support makes many features depend on this
* capability. If it's not set, this causes many things to be disabled. */
aBuf[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
aBuf[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
ataH2BE_U16(&aBuf[20], 128); /* buffer size supported in Kbyte - We don't have a buffer because we write directly into guest memory.
Just write the value DevATA is using. */
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiRequestSenseSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
/* Copy the buffer in to the scatter gather list. */
*pcbData = ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, pAhciPort->abATAPISense, sizeof(pAhciPort->abATAPISense));
return VINF_SUCCESS;
}
static int atapiMechanismStatusSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
ataH2BE_U16(&aBuf[0], 0);
/* no current LBA */
aBuf[2] = 0;
aBuf[3] = 0;
aBuf[4] = 0;
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiReadTOCNormalSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
bool fMSF;
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return VINF_SUCCESS;
}
q = aBuf + 2;
*q++ = 1; /* first session */
*q++ = 1; /* last session */
if (iStartTrack <= 1)
{
*q++ = 0; /* reserved */
*q++ = 0x14; /* ADR, control */
*q++ = 1; /* track number */
*q++ = 0; /* reserved */
if (fMSF)
{
*q++ = 0; /* reserved */
ataLBA2MSF(q, 0);
q += 3;
}
else
{
/* sector 0 */
ataH2BE_U32(q, 0);
q += 4;
}
}
/* lead out track */
*q++ = 0; /* reserved */
*q++ = 0x14; /* ADR, control */
*q++ = 0xaa; /* track number */
*q++ = 0; /* reserved */
if (fMSF)
{
*q++ = 0; /* reserved */
q += 3;
}
else
{
q += 4;
}
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiReadTOCMultiSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
bool fMSF;
/* multi session: only a single session defined */
/** @todo double-check this stuff against what a real drive says for a CD-ROM (not a CD-R) with only a single data session. Maybe solve the problem with "cdrdao read-toc" not being able to figure out whether numbers are in BCD or hex. */
if (fMSF)
{
}
else
{
/* sector 0 */
}
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
static int atapiReadTOCRawSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
uint8_t *q, iStartTrack;
bool fMSF;
q = aBuf + 2;
*q++ = 1; /* first session */
*q++ = 1; /* last session */
*q++ = 1; /* session number */
*q++ = 0x14; /* data track */
*q++ = 0; /* track number */
*q++ = 0xa0; /* first track in program area */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
*q++ = 0;
*q++ = 1; /* first track */
*q++ = 0x00; /* disk type CD-DA or CD data */
*q++ = 0;
*q++ = 1; /* session number */
*q++ = 0x14; /* data track */
*q++ = 0; /* track number */
*q++ = 0xa1; /* last track in program area */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
*q++ = 0;
*q++ = 1; /* last track */
*q++ = 0;
*q++ = 0;
*q++ = 1; /* session number */
*q++ = 0x14; /* data track */
*q++ = 0; /* track number */
*q++ = 0xa2; /* lead-out */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
if (fMSF)
{
*q++ = 0; /* reserved */
q += 3;
}
else
{
q += 4;
}
*q++ = 1; /* session number */
*q++ = 0x14; /* ADR, control */
*q++ = 0; /* track number */
*q++ = 1; /* point */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
if (fMSF)
{
*q++ = 0; /* reserved */
ataLBA2MSF(q, 0);
q += 3;
}
else
{
/* sector 0 */
ataH2BE_U32(q, 0);
q += 4;
}
/* Copy the buffer in to the scatter gather list. */
return VINF_SUCCESS;
}
/**
* Sets the given media track type.
*/
{
}
static int atapiPassthroughSS(PAHCIPORTTASKSTATE pAhciPortTaskState, PAHCIPort pAhciPort, int *pcbData)
{
int rc = VINF_SUCCESS;
/* Simple heuristics: if there is at least one sector of data
* to transfer, it's worth updating the LEDs. */
if (cbTransfer >= 2048)
{
else
}
if (cbTransfer > SCSI_MAX_BUFFER_SIZE)
{
/* Linux accepts commands with up to 100KB of data, but expects
* us to handle commands with up to 128KB of data. The usual
* imbalance of powers. */
switch (pAhciPortTaskState->aATAPICmd[0])
{
case SCSI_READ_10:
case SCSI_WRITE_10:
case SCSI_WRITE_AND_VERIFY_10:
break;
case SCSI_READ_12:
case SCSI_WRITE_12:
break;
case SCSI_READ_CD:
break;
case SCSI_READ_CD_MSF:
break;
default:
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
return false;
}
cReqSectors = 0;
{
else
cReqSectors = i;
switch (pAhciPortTaskState->aATAPICmd[0])
{
case SCSI_READ_10:
case SCSI_WRITE_10:
case SCSI_WRITE_AND_VERIFY_10:
break;
case SCSI_READ_12:
case SCSI_WRITE_12:
break;
case SCSI_READ_CD:
break;
case SCSI_READ_CD_MSF:
break;
}
&cbCurrTX,
sizeof(abATAPISense),
30000 /**< @todo timeout */);
if (rc != VINF_SUCCESS)
break;
iATAPILBA += cReqSectors;
}
}
else
{
else
sizeof(abATAPISense),
30000 /**< @todo timeout */);
}
if (cbTransfer >= 2048)
{
{
}
else
{
}
}
if (RT_SUCCESS(rc))
{
/* Reply with the same amount of data as the real drive. */
*pcbData = cbTransfer;
{
{
/* Make sure that the real drive cannot be identified.
* Motivation: changing the VM configuration should be as
* invisible as possible to the guest. */
}
{
/* Set the media type if we can detect it. */
/** @todo: Implemented only for formatted TOC now. */
&& cbTransfer >= 6)
{
else
if (OldMediaType != NewMediaType)
LogRel(("AHCI: LUN#%d: CD-ROM passthrough, detected %s CD\n",
? "data"
: "audio"));
}
else /* Play safe and set to unknown. */
}
if (cbTransfer)
Log3(("ATAPI PT data read (%d): %.*Rhxs\n", cbTransfer, cbTransfer, (uint8_t *)pAhciPortTaskState->pSGListHead[0].pvSeg));
}
}
else
{
{
do
{
/* don't log superfluous errors */
if ( rc == VERR_DEV_IO_ERROR
&& ( u8Cmd == SCSI_TEST_UNIT_READY
|| u8Cmd == SCSI_READ_CAPACITY
|| u8Cmd == SCSI_READ_DVD_STRUCTURE
|| u8Cmd == SCSI_READ_TOC_PMA_ATIP))
break;
LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough cmd=%#04x sense=%d ASC=%#02x ASCQ=%#02x %Rrc\n",
} while (0);
}
}
return false;
}
static int atapiDoTransfer(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, ATAPIFN iSourceSink)
{
int cbTransfered = 0;
int rc, rcSourceSink;
/*
* Create scatter gather list. We use a safe mapping here because it is
* possible that the buffer is not a multiple of 512. The normal
* creator would assert later here.
*/
{
}
{
}
/* Write updated command header into memory of the guest. */
PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, &pAhciPortTaskState->cmdHdr, sizeof(CmdHdr));
return rcSourceSink;
}
{
{
/* sync bytes */
*pbBufDst++ = 0x00;
pbBufDst += 11;
/* MSF */
ataLBA2MSF(pbBufDst, i);
pbBufDst += 3;
/* data */
pbBufDst += 2048;
pbBufSrc += 2048;
/* ECC */
pbBufDst += 288;
}
return VINF_SUCCESS;
}
static int atapiReadSectors(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
{
switch (cbSector)
{
case 2048:
break;
case 2352:
{
break;
}
default:
AssertMsgFailed(("Unsupported sectors size\n"));
break;
}
return VINF_SUCCESS;
}
static AHCITXDIR atapiParseCmdVirtualATAPI(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
{
switch (pbPacket[0])
{
case SCSI_TEST_UNIT_READY:
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
else
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
}
else
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
break;
case SCSI_MODE_SENSE_10:
{
switch (uPageControl)
{
case SCSI_PAGECONTROL_CURRENT:
switch (uPageCode)
{
break;
case SCSI_MODEPAGE_CD_STATUS:
break;
default:
goto error_cmd;
}
break;
goto error_cmd;
case SCSI_PAGECONTROL_DEFAULT:
goto error_cmd;
default:
case SCSI_PAGECONTROL_SAVED:
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
break;
}
}
break;
case SCSI_REQUEST_SENSE:
break;
{
else
}
else
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
case SCSI_READ_10:
case SCSI_READ_12:
{
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
if (pbPacket[0] == SCSI_READ_10)
else
if (cSectors == 0)
{
break;
}
{
/* Rate limited logging, one log line per second. For
* guests that insist on reading from places outside the
* valid area this often generates too many release log
* entries otherwise. */
static uint64_t uLastLogTS = 0;
{
LogRel(("AHCI ATAPI: LUN#%d: CD-ROM block number %Ld invalid (READ)\n", pAhciPort->iLUN, (uint64_t)iATAPILBA + cSectors));
uLastLogTS = RTTimeMilliTS();
}
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
rc = AHCITXDIR_READ;
}
break;
case SCSI_READ_CD:
{
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
if (cSectors == 0)
{
break;
}
{
/* Rate limited logging, one log line per second. For
* guests that insist on reading from places outside the
* valid area this often generates too many release log
* entries otherwise. */
static uint64_t uLastLogTS = 0;
{
LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (READ CD)\n", pAhciPort->iLUN, (uint64_t)iATAPILBA + cSectors));
uLastLogTS = RTTimeMilliTS();
}
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
{
case 0x00:
/* nothing */
break;
case 0x10:
/* normal read */
rc = AHCITXDIR_READ;
break;
case 0xf8:
/* read all data */
rc = AHCITXDIR_READ;
break;
default:
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
}
}
break;
case SCSI_SEEK_10:
{
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
{
/* Rate limited logging, one log line per second. For
* guests that insist on seeking to places outside the
* valid area this often generates too many release log
* entries otherwise. */
static uint64_t uLastLogTS = 0;
{
LogRel(("AHCI ATAPI: LUN#%d: CD-ROM block number %Ld invalid (SEEK)\n", pAhciPort->iLUN, (uint64_t)iATAPILBA));
uLastLogTS = RTTimeMilliTS();
}
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
}
break;
case SCSI_START_STOP_UNIT:
{
int rc2 = VINF_SUCCESS;
{
case 0: /* 00 - Stop motor */
case 1: /* 01 - Start motor */
break;
case 2: /* 10 - Eject media */
{
/* This must be done from EMT. */
{
}
break;
}
case 3: /* 11 - Load media */
/** @todo rc = s->pDrvMount->pfnLoadMedia(s->pDrvMount) */
break;
}
if (RT_SUCCESS(rc2))
else
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
}
break;
case SCSI_MECHANISM_STATUS:
{
}
break;
case SCSI_READ_TOC_PMA_ATIP:
{
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
/* SCSI MMC-3 spec says format is at offset 2 (lower 4 bits),
* but Linux kernel uses offset 9 (topmost 2 bits). Hope that
* the other field is clear... */
switch (format)
{
case 0:
break;
case 1:
break;
case 2:
break;
default:
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
}
}
break;
case SCSI_READ_CAPACITY:
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
break;
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
break;
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
break;
case SCSI_GET_CONFIGURATION:
/* No media change stuff here, it can confuse Linux guests. */
break;
case SCSI_INQUIRY:
break;
default:
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
break;
}
return rc;
}
/*
*/
static AHCITXDIR atapiParseCmdPassthrough(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
{
uint32_t cbTransfer = 0;
switch (pbPacket[0])
{
case SCSI_BLANK:
goto sendcmd;
case SCSI_CLOSE_TRACK_SESSION:
goto sendcmd;
case SCSI_ERASE_10:
goto sendcmd;
case SCSI_FORMAT_UNIT:
cbTransfer = pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CYLL] | (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CYLH] << 8); /* use ATAPI transfer length */
goto sendcmd;
case SCSI_GET_CONFIGURATION:
goto sendcmd;
{
break;
}
goto sendcmd;
case SCSI_GET_PERFORMANCE:
cbTransfer = pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CYLL] | (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CYLH] << 8); /* use ATAPI transfer length */
goto sendcmd;
case SCSI_INQUIRY:
goto sendcmd;
case SCSI_LOAD_UNLOAD_MEDIUM:
goto sendcmd;
case SCSI_MECHANISM_STATUS:
goto sendcmd;
case SCSI_MODE_SELECT_10:
goto sendcmd;
case SCSI_MODE_SENSE_10:
goto sendcmd;
case SCSI_PAUSE_RESUME:
goto sendcmd;
case SCSI_PLAY_AUDIO_10:
goto sendcmd;
case SCSI_PLAY_AUDIO_12:
goto sendcmd;
case SCSI_PLAY_AUDIO_MSF:
goto sendcmd;
/** @todo do not forget to unlock when a VM is shut down */
goto sendcmd;
case SCSI_READ_10:
goto sendcmd;
case SCSI_READ_12:
goto sendcmd;
case SCSI_READ_BUFFER:
goto sendcmd;
goto sendcmd;
case SCSI_READ_CAPACITY:
cbTransfer = 8;
goto sendcmd;
case SCSI_READ_CD:
{
/* Get sector size based on the expected sector type field. */
{
case 0x0: /* All types. */
else
pAhciPortTaskState->cbATAPISector = 2048; /* Might be incorrect if we couldn't determine the type. */
break;
case 0x1: /* CD-DA */
break;
case 0x2: /* Mode 1 */
break;
case 0x3: /* Mode 2 formless */
break;
case 0x4: /* Mode 2 form 1 */
break;
case 0x5: /* Mode 2 form 2 */
break;
default: /* Reserved */
AssertMsgFailed(("Unknown sector type\n"));
pAhciPortTaskState->cbATAPISector = 0; /** @todo we should probably fail the command here already. */
}
goto sendcmd;
}
case SCSI_READ_CD_MSF:
if (cSectors > 32)
cSectors = 32; /* Limit transfer size to 64~74K. Safety first. In any case this can only harm software doing CDDA extraction. */
goto sendcmd;
goto sendcmd;
case SCSI_READ_DVD_STRUCTURE:
goto sendcmd;
goto sendcmd;
case SCSI_READ_SUBCHANNEL:
goto sendcmd;
case SCSI_READ_TOC_PMA_ATIP:
goto sendcmd;
goto sendcmd;
case SCSI_REPAIR_TRACK:
goto sendcmd;
case SCSI_REPORT_KEY:
goto sendcmd;
case SCSI_REQUEST_SENSE:
{
break;
}
goto sendcmd;
case SCSI_RESERVE_TRACK:
goto sendcmd;
case SCSI_SCAN:
goto sendcmd;
case SCSI_SEEK_10:
goto sendcmd;
case SCSI_SEND_CUE_SHEET:
goto sendcmd;
case SCSI_SEND_DVD_STRUCTURE:
goto sendcmd;
case SCSI_SEND_EVENT:
goto sendcmd;
case SCSI_SEND_KEY:
goto sendcmd;
goto sendcmd;
case SCSI_SET_CD_SPEED:
goto sendcmd;
case SCSI_SET_READ_AHEAD:
goto sendcmd;
case SCSI_SET_STREAMING:
goto sendcmd;
case SCSI_START_STOP_UNIT:
goto sendcmd;
case SCSI_STOP_PLAY_SCAN:
goto sendcmd;
case SCSI_SYNCHRONIZE_CACHE:
goto sendcmd;
case SCSI_TEST_UNIT_READY:
goto sendcmd;
case SCSI_VERIFY_10:
goto sendcmd;
case SCSI_WRITE_10:
goto sendcmd;
case SCSI_WRITE_12:
goto sendcmd;
case SCSI_WRITE_AND_VERIFY_10:
/* The sector size is determined by the async I/O thread. */
/* Preliminary, will be corrected once the sector size is known. */
goto sendcmd;
case SCSI_WRITE_BUFFER:
{
case 0x04: /* download microcode */
case 0x05: /* download microcode and save */
case 0x06: /* download microcode with offsets */
case 0x07: /* download microcode with offsets and save */
case 0x0e: /* download microcode with offsets and defer activation */
case 0x0f: /* activate deferred microcode */
LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough command attempted to update firmware, blocked\n", pAhciPort->iLUN));
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
default:
goto sendcmd;
}
break;
case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
goto sendcmd;
case SCSI_REZERO_UNIT:
/* Obsolete command used by cdrecord. What else would one expect?
* This command is not sent to the drive, it is handled internally,
* as the Linux kernel doesn't like it (message "scsi: unknown
* opcode 0x01" in syslog) and replies with a sense code of 0,
* which sends cdrecord to an endless loop. */
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
break;
default:
LogRel(("AHCI: LUN#%d: passthrough unimplemented for command %#x\n", pAhciPort->iLUN, pbPacket[0]));
atapiCmdErrorSimple(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
break;
if (cbTransfer == 0)
}
return AHCITXDIR_NONE;
}
{
#ifdef DEBUG
Log(("%s: LUN#%d CMD=%#04x \"%s\"\n", __FUNCTION__, pAhciPort->iLUN, pbPacket[0], SCSICmdText(pbPacket[0])));
#else /* !DEBUG */
#endif /* !DEBUG */
Log2(("%s: limit=%#x packet: %.*Rhxs\n", __FUNCTION__, pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CYLL] | (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_CYLH] << 8), ATAPI_PACKET_SIZE, pbPacket));
if (pAhciPort->fATAPIPassthrough)
else
return enmTxDir;
}
/**
* Reset all values after a reset of the attached storage device.
*
* @returns nothing
* @param pAhciPort The port the device is attached to.
* @param pAhciPortTaskState The state to get the tag number from.
*/
static void ahciFinishStorageDeviceReset(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
{
int rc;
/* Send a status good D2H FIS. */
pAhciPort->fResetDevice = false;
/* As this is the first D2H FIS after the reset update the signature in the SIG register of the port. */
else
}
/**
* Initiates a device reset caused by ATA_DEVICE_RESET (ATAPI only).
*
* @returns nothing.
* @param pAhciPort The device to reset.
* @param pAhciPortTaskState The task state.
*/
{
/*
* 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.
*/
}
/**
* Build a D2H FIS and post into the memory area of the guest.
*
* @returns Nothing
* @param pAhciPort The port of the SATA controller.
* @param pAhciPortTaskState The state of the task.
* @param pCmdFis Pointer to the command FIS from the guest.
* @param fInterrupt If an interrupt should be send to the guest.
*/
static void ahciSendD2HFis(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint8_t *pCmdFis, bool fInterrupt)
{
bool fAssertIntr = false;
{
/* Update registers. */
{
/* Error bit is set. */
fAssertIntr = true;
/*
* Don't mark the command slot as completed because the guest
* needs it to identify the failed command.
*/
}
else if (fInterrupt)
{
/* Check if we should assert an interrupt */
fAssertIntr = true;
/* Mark command as completed. */
}
if (fAssertIntr)
{
}
}
}
/**
* Build a SDB Fis and post it into the memory area of the guest.
*
* @returns Nothing
* @param pAhciPort The port for which the SDB Fis is send.
* @param uFinishedTasks Bitmask of finished tasks.
* @param fInterrupt If an interrupt should be asserted.
*/
{
bool fAssertIntr = false;
{
if (RT_UNLIKELY(pTaskErr))
{
sdbFis[0] |= (pTaskErr->uATARegStatus & 0x77) << 16; /* Some bits are marked as reserved and thus are masked out. */
/* Update registers. */
}
else
{
sdbFis[0] = 0;
}
if (RT_UNLIKELY(pTaskErr))
{
/* Error bit is set. */
fAssertIntr = true;
}
if (fInterrupt)
{
/* Check if we should assert an interrupt */
fAssertIntr = true;
}
if (fAssertIntr)
{
}
}
}
{
/* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
if (fLBA48)
{
return 65536;
else
}
else
{
if (!pCmdFis[AHCI_CMDFIS_SECTC])
return 256;
else
return pCmdFis[AHCI_CMDFIS_SECTC];
}
}
{
{
/* any LBA variant */
if (fLBA48)
{
/* LBA48 */
}
else
{
/* LBA */
}
}
else
{
/* CHS */
iLBA = ((pCmdFis[AHCI_CMDFIS_CYLH] << 8) | pCmdFis[AHCI_CMDFIS_CYLL]) * pAhciPort->PCHSGeometry.cHeads * pAhciPort->PCHSGeometry.cSectors +
}
return iLBA;
}
{
return uLBA;
}
{
return 65536;
else
}
{
}
static void ahciScatterGatherListGetTotalBufferSize(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState)
{
unsigned cActualSGEntry;
SGLEntry aSGLEntry[32]; /* Holds read sg entries from guest. Biggest seen number of entries a guest set up. */
unsigned cSGLEntriesGCRead;
unsigned cSGLEntriesGCLeft; /* Available scatter gather list entries in GC */
/* Retrieve the total number of bytes reserved for this request. */
if (cSGLEntriesGCLeft)
{
/* Set start address of the entries. */
GCPhysAddrPRDTLEntryStart = AHCI_RTGCPHYS_FROM_U32(pCmdHdr->u32CmdTblAddrUp, pCmdHdr->u32CmdTblAddr) + AHCI_CMDHDR_PRDT_OFFSET;
do
{
cSGLEntriesGCRead = (cSGLEntriesGCLeft < RT_ELEMENTS(aSGLEntry)) ? cSGLEntriesGCLeft : RT_ELEMENTS(aSGLEntry);
/* Read the SG entries. */
PDMDevHlpPhysRead(pDevIns, GCPhysAddrPRDTLEntryStart, &aSGLEntry[0], cSGLEntriesGCRead * sizeof(SGLEntry));
/* Set address to the next entries to read. */
} while (cSGLEntriesGCLeft);
}
}
static int ahciScatterGatherListAllocate(PAHCIPORTTASKSTATE pAhciPortTaskState, uint32_t cSGList, uint32_t cbUnaligned)
{
{
/* The entries are not allocated yet or the number is too small. */
{
}
/* Allocate R3 scatter gather list. */
if (!pAhciPortTaskState->pSGListHead)
return VERR_NO_MEMORY;
pAhciPortTaskState->paSGEntries = (PAHCIPORTTASKSTATESGENTRY)RTMemAllocZ(cSGList * sizeof(AHCIPORTTASKSTATESGENTRY));
if (!pAhciPortTaskState->paSGEntries)
return VERR_NO_MEMORY;
/* Reset usage statistics. */
}
{
/*
* The list is too big. Increment counter.
* So that the destroying function can free
* the list if it is too big too many times
* in a row.
*/
}
else
{
/*
* Needed entries matches current size.
* Reset counter.
*/
}
{
return VERR_NO_MEMORY;
}
/* Make debugging easier. */
#ifdef DEBUG
memset(pAhciPortTaskState->paSGEntries, 0, pAhciPortTaskState->cSGListSize * sizeof(AHCIPORTTASKSTATESGENTRY));
#endif
return VINF_SUCCESS;
}
/**
* Fallback scatter gather list creator.
* Used if the normal one fails in PDMDevHlpPhysGCPhys2CCPtr() or
* PDMDevHlpPhysGCPhys2CCPtrReadonly() or post processing
* is used.
*
* returns VBox status code.
* @param pAhciPort The ahci port.
* @param pAhciPortTaskState The task state which contains the S/G list entries.
* @param fReadonly If the mappings should be readonly.
* @param cSGEntriesProcessed Number of entries the normal creator processed
* before an error occurred. Used to free
* any resources allocated before.
* @thread EMT
*/
static int ahciScatterGatherListCreateSafe(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState,
bool fReadonly, unsigned cSGEntriesProcessed)
{
{
if (pSGInfoCurr->fGuestMemory)
{
/* Release the lock. */
}
/* Go to the next entry. */
pSGInfoCurr++;
}
{
}
{
}
{
}
/* Allocate new buffers and SG lists. */
return VERR_NO_MEMORY;
if (!pAhciPortTaskState->pSGListHead)
{
return VERR_NO_MEMORY;
}
pAhciPortTaskState->paSGEntries = (PAHCIPORTTASKSTATESGENTRY)RTMemAllocZ(1 * sizeof(AHCIPORTTASKSTATESGENTRY));
if (!pAhciPortTaskState->paSGEntries)
{
return VERR_NO_MEMORY;
}
/* Set pointers. */
if (pAhciPortTaskState->cbTransfer)
{
/* Allocate a separate buffer if we have to do post processing . */
{
{
return VERR_NO_MEMORY;
}
}
else
}
else
{
}
pAhciPortTaskState->paSGEntries[0].u.temp.cUnaligned = AHCI_CMDHDR_PRDTL_ENTRIES(pCmdHdr->u32DescInf);
pAhciPortTaskState->paSGEntries[0].u.temp.GCPhysAddrBaseFirstUnaligned = AHCI_RTGCPHYS_FROM_U32(pCmdHdr->u32CmdTblAddrUp, pCmdHdr->u32CmdTblAddr) + AHCI_CMDHDR_PRDT_OFFSET;
return VINF_SUCCESS;
}
/**
* Create scatter gather list descriptors.
*
* @returns VBox status code.
* @param pAhciPort The ahci port.
* @param pAhciPortTaskState The task state which contains the S/G list entries.
* @param fReadonly If the mappings should be readonly.
* @thread EMT
*/
static int ahciScatterGatherListCreate(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, bool fReadonly)
{
int rc = VINF_SUCCESS;
unsigned cActualSGEntry;
unsigned cSGEntriesR3 = 0; /* Needed scatter gather list entries in R3. */
unsigned cSGEntriesProcessed = 0; /* Number of SG entries processed. */
SGLEntry aSGLEntry[32]; /* Holds read sg entries from guest. Biggest seen number of entries a guest set up. */
unsigned cSGLEntriesGCRead;
unsigned cSGLEntriesGCLeft; /* Available scatter gather list entries in GC */
bool fUnaligned; /* Flag whether the current buffer is unaligned. */
bool fDoMapping = false;
/*
* Create a safe mapping when doing post processing because the size of the
* data to transfer and the amount of guest memory reserved can differ.
*
* @fixme: Read performance is really bad on OS X hosts because there is no
* S/G support and the I/O manager has to create a newrequest
* for every segment. The default limit of active requests is 16 on OS X
* which causes a the bad read performance (writes are not affected
* because of the writeback cache).
* For now we will always use an intermediate buffer until
* there is support for host S/G operations.
*/
if (pAhciPortTaskState->pfnPostProcess || true)
{
}
/*
* We need to calculate the number of SG list entries in R3 first because the data buffers in the guest don't need to be
* page aligned. Hence the number of SG list entries in the guest can differ from the ones we need
* because PDMDevHlpPhysGCPhys2CCPtr works only on a page base.
* In the first pass we calculate the number of segments in R3 and in the second pass we map the guest segments into R3.
*/
for (int i = 0; i < 2; i++)
{
/* Set start address of the entries. */
GCPhysAddrPRDTLEntryStart = AHCI_RTGCPHYS_FROM_U32(pCmdHdr->u32CmdTblAddrUp, pCmdHdr->u32CmdTblAddr) + AHCI_CMDHDR_PRDT_OFFSET;
fUnaligned = false;
cbUnaligned = 0;
cUnaligned = 0;
if (fDoMapping)
{
/* The number of needed SG entries in R3 is known. Allocate needed memory. */
/* We are now able to map the pages into R3. */
/* Initialize first segment to remove the need for additional if checks later in the code. */
pSGEntryCurr->cbSeg = 0;
pSGInfoCurr->fGuestMemory= false;
}
do
{
cSGLEntriesGCRead = (cSGLEntriesGCLeft < RT_ELEMENTS(aSGLEntry)) ? cSGLEntriesGCLeft : RT_ELEMENTS(aSGLEntry);
/* Read the SG entries. */
PDMDevHlpPhysRead(pDevIns, GCPhysAddrPRDTLEntryStart, &aSGLEntry[0], cSGLEntriesGCRead * sizeof(SGLEntry));
{
/* Check if the buffer is sector aligned. */
if (cbDataToTransfer % 512 != 0)
{
if (!fUnaligned)
{
/* We are not in an unaligned buffer but this is the first unaligned one. */
fUnaligned = true;
cSGEntriesR3++;
cUnaligned = 1;
}
else
{
/* We are already in an unaligned buffer and this one is unaligned too. */
cUnaligned++;
}
}
else /* Guest segment size is sector aligned. */
{
if (fUnaligned)
{
if (cbUnaligned % 512 == 0)
{
/*
* The last buffer started at an offset
* not aligned to a sector boundary but this buffer
* is sector aligned. Check if the current size of all
* unaligned segments is a multiple of a sector.
* If that's the case we can now map the segments again into R3.
*/
fUnaligned = false;
if (fDoMapping)
{
/* Set up the entry. */
pSGInfoCurr->fGuestMemory = false;
/*
* If the transfer is to the device we need to copy the content of the not mapped guest
* segments into the temporary buffer.
*/
/* Advance to next entry saving the pointers to the current ones. */
pSGInfoCurr++;
pSGEntryCurr++;
}
}
else
{
cUnaligned++;
}
}
else
{
/*
* The size of the guest segment is sector aligned but it is possible that the segment crosses
* a page boundary in a way splitting the segment into parts which are not sector aligned.
* We have to treat them like unaligned guest segments then.
*/
GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aSGLEntry[cActualSGEntry].u32DBAUp, aSGLEntry[cActualSGEntry].u32DBA);
/*
* Check if the physical address is page aligned.
*/
{
/* Difference from the buffer start to the next page boundary. */
if (u32GCPhysAddrDiff % 512 != 0)
{
if (!fUnaligned)
{
/* We are not in an unaligned buffer but this is the first unaligned one. */
fUnaligned = true;
cSGEntriesR3++;
cUnaligned = 1;
ahciLog(("%s: Guest segment is sector aligned but crosses a page boundary cb=%d\n", __FUNCTION__, cbDataToTransfer));
}
else
{
/* We are already in an unaligned buffer and this one is unaligned too. */
cUnaligned++;
}
}
else
{
ahciLog(("%s: Align page: GCPhysAddrDataBase=%RGp GCPhysAddrDataNextPage=%RGp\n",
/* Check if the mapping ends at the page boundary and set segment size accordingly. */
/* Subtract size of the buffer in the actual page. */
{
/* We don't need to map the buffer if it is in the same page as the previous one. */
if (fDoMapping)
{
pSGInfoCurr->fGuestMemory = true;
/* Create the mapping. */
if (fReadonly)
0, (const void **)&pbMapping,
else
0, (void **)&pbMapping,
if (RT_FAILURE(rc))
{
/* Mapping failed. Fall back to a bounce buffer. */
ahciLog(("%s: Mapping guest physical address %RGp failed with rc=%Rrc\n",
}
if ((pbMapping + (GCPhysAddrDataBase - GCPhysBufferPageAligned) == ((uint8_t *)pSGEntryPrev->pvSeg + pSGEntryCurr->cbSeg)))
{
ahciLog(("%s: Merged mapping pbMapping=%#p into current segment pvSeg=%#p. New size is cbSeg=%d\n",
}
else
{
/* Let pvBuf point to the start of the buffer in the page. */
pSGEntryCurr++;
}
pSGInfoCurr++;
}
else
cSGEntriesR3++;
}
else if (fDoMapping)
{
ahciLog(("%s: Buffer is already in previous mapping pvSeg=%#p. New size is cbSeg=%d\n",
}
/* Let physical address point to the next page in the buffer. */
}
}
if (!fUnaligned)
{
/* The address is now page aligned. */
while (cbDataToTransfer)
{
ahciLog(("%s: GCPhysAddrDataBase=%RGp cbDataToTransfer=%u cSGEntriesR3=%u\n",
/* Check if this is the last page the buffer is in. */
if (fDoMapping)
{
void *pvMapping;
pSGInfoCurr->fGuestMemory = true;
/* Create the mapping. */
if (fReadonly)
rc = PDMDevHlpPhysGCPhys2CCPtrReadOnly(pDevIns, GCPhysAddrDataBase, 0, (const void **)&pvMapping, &pSGInfoCurr->u.direct.PageLock);
else
rc = PDMDevHlpPhysGCPhys2CCPtr(pDevIns, GCPhysAddrDataBase, 0, &pvMapping, &pSGInfoCurr->u.direct.PageLock);
if (RT_FAILURE(rc))
{
/* Mapping failed. Fall back to a bounce buffer. */
ahciLog(("%s: Mapping guest physical address %RGp failed with rc=%Rrc\n",
}
/* Check for adjacent mappings. */
&& (pSGInfoPrev->fGuestMemory == true))
{
/* Yes they are adjacent. Just add the size of this mapping to the previous segment. */
ahciLog(("%s: Merged mapping pvMapping=%#p into current segment pvSeg=%#p. New size is cbSeg=%d\n",
}
else
{
/* No they are not. Use a new sg entry. */
pSGEntryCurr++;
}
pSGInfoCurr++;
}
else
cSGEntriesR3++;
/* Go to the next page. */
}
} /* if (!fUnaligned) */
} /* if !fUnaligned */
} /* if guest segment is sector aligned. */
} /* for SGEntries read */
/* Set address to the next entries to read. */
} while (cSGLEntriesGCLeft);
fDoMapping = true;
} /* for passes */
/* Check if the last processed segment was unaligned. We need to add it now. */
if (fUnaligned)
{
/* Set up the entry. */
pSGInfoCurr->fGuestMemory = false;
/*
* If the transfer is to the device we need to copy the content of the not mapped guest
* segments into the temporary buffer.
*/
}
return rc;
}
/**
* Free all memory of the scatter gather list.
*
* @returns nothing.
* @param pAhciPortTaskState Task state.
*/
{
/* Safety. */
}
/**
* Destroy a scatter gather list and free all occupied resources (mappings, etc.)
*
* @returns VBox status code.
* @param pAhciPort The ahci port.
* @param pAhciPortTaskState The task state which contains the S/G list entries.
*/
{
{
int rc;
/* Free the buffer holding the unprocessed data. They are not needed anymore. */
}
for (unsigned cActualSGEntry = 0; cActualSGEntry < pAhciPortTaskState->cSGEntries; cActualSGEntry++)
{
if (pSGInfoCurr->fGuestMemory)
{
/* Release the lock. */
}
{
/* Copy the data into the guest segments now. */
}
/* Go to the next entry. */
pSGInfoCurr++;
}
/* Free allocated memory if the list was too big too many times. */
return VINF_SUCCESS;
}
/**
* Copy a temporary buffer into a part of the guest scatter gather list
* described by the given descriptor entry.
*
* @returns nothing.
* @param pDevIns Pointer to the device instance data.
* @param pSGInfo Pointer to the segment info structure which describes the guest segments
* to write to which are unaligned.
*/
{
do
{
for (uint32_t i = 0; i < cSGEntriesRead; i++)
{
RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aSGLEntries[i].u32DBAUp, aSGLEntries[i].u32DBA);
/* Copy into SG entry. */
}
} while (cSGEntriesLeft);
}
/**
* Copy a part of the guest scatter gather list into a temporary buffer.
*
* @returns nothing.
* @param pDevIns Pointer to the device instance data.
* @param pSGInfo Pointer to the segment info structure which describes the guest segments
* to read from which are unaligned.
*/
{
do
{
for (uint32_t i = 0; i < cSGEntriesRead; i++)
{
RTGCPHYS GCPhysAddrDataBase = AHCI_RTGCPHYS_FROM_U32(aSGLEntries[i].u32DBAUp, aSGLEntries[i].u32DBA);
/* Copy into buffer. */
}
} while (cSGEntriesLeft);
}
/**
* Copy the content of a buffer to a scatter gather list.
*
* @returns Number of bytes transferred.
* @param pAhciPortTaskState The task state which contains the S/G list entries.
* @param pvBuf Pointer to the buffer which should be copied.
* @param cbBuf Size of the buffer.
*/
static int ahciScatterGatherListCopyFromBuffer(PAHCIPORTTASKSTATE pAhciPortTaskState, void *pvBuf, size_t cbBuf)
{
unsigned cSGEntry = 0;
{
/* We finished. */
if (!cbBuf)
break;
/* Advance the buffer. */
/* Go to the next entry in the list. */
pSGEntry++;
cSGEntry++;
}
return cbCopied;
}
/**
* Cancels all active tasks on the port.
*
* @returns Whether all active tasks were canceled.
* @param pAhciPort The ahci port.
*/
{
{
if (VALID_PTR(pAhciPortTaskState))
{
bool fXchg = false;
ASMAtomicCmpXchgSize(&pAhciPortTaskState->enmTxState, AHCITXSTATE_CANCELED, AHCITXSTATE_ACTIVE, fXchg);
if (fXchg)
{
/* Task is active and was canceled. */
/*
* Clear the pointer in the cached array. The controller will allocate a
* a new task structure for this tag.
*/
}
else
("Invalid task state, must be free!\n"));
}
}
return true; /* always true for now because tasks don't use guest memory as the buffer which makes canceling a task impossible. */
}
/* -=-=-=-=- IBlockAsyncPort -=-=-=-=- */
/** Makes a PAHCIPort out of a PPDMIBLOCKASYNCPORT. */
#define PDMIBLOCKASYNCPORT_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)pInterface - RT_OFFSETOF(AHCIPort, IPortAsync)) )
{
int rc;
LogRel(("AHCI: Host disk full\n"));
rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevAHCI_DISKFULL",
N_("Host system reported disk full. VM execution is suspended. You can resume after freeing some space"));
}
{
int rc;
LogRel(("AHCI: File too big\n"));
rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevAHCI_FILETOOBIG",
N_("Host system reported that the file size limit of the host file system has been exceeded. VM execution is suspended. You need to move your virtual hard disk to a filesystem which allows bigger files"));
}
{
int rc;
LogRel(("AHCI: iSCSI target unavailable\n"));
rc = PDMDevHlpVMSetRuntimeError(pDevIns, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "DevAHCI_ISCSIDOWN",
N_("The iSCSI target has stopped responding. VM execution is suspended. You can resume when it is available again"));
}
{
if (rc == VERR_DISK_FULL)
{
return true;
}
if (rc == VERR_FILE_TOO_BIG)
{
return true;
}
{
/* iSCSI connection abort (first error) or failure to reestablish
* connection (second error). Pause VM. On resume we'll retry. */
return true;
}
return false;
}
/**
* Complete a data transfer task by freeing all occupied resources
* and notifying the guest.
*
* @returns VBox status code
*
* @param pAhciPort Pointer to the port where to request completed.
* @param pAhciPortTaskState Pointer to the task which finished.
* @param rcReq IPRT status code of the completed request.
*/
static int ahciTransferComplete(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, int rcReq)
{
bool fXchg = false;
bool fRedo = false;
if (fXchg)
{
/* Free system resources occupied by the scatter gather list. */
{
}
{
}
if (RT_FAILURE(rcReq))
{
/* Log the error. */
{
LogRel(("AHCI#%u: Flush returned rc=%Rrc\n",
else
LogRel(("AHCI#%u: %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
? "Read"
: "Write",
}
if (!fRedo)
{
}
else
}
else
{
/* Write updated command header into memory of the guest. */
}
/* Add the task to the cache. */
if (!fRedo)
{
if (pAhciPortTaskState->fQueued)
{
/*
* Always raise an interrupt after task completion; delaying
* this (interrupt coalescing) increases latency and has a significant
* impact on performance (see #5071)
*/
ahciSendSDBFis(pAhciPort, 0, true);
}
else
}
}
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.
*/
("Task is not active but wasn't canceled!\n"));
/* Leave a log message about the canceled request. */
{
LogRel(("AHCI#%u: Canceled flush returned rc=%Rrc\n",
else
LogRel(("AHCI#%u: Canceled %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
? "read"
: "write",
}
/* Finally free the task state structure because it is completely unused now. */
}
return VINF_SUCCESS;
}
/**
* Notification callback for a completed transfer.
*
* @returns VBox status code.
* @param pInterface Pointer to the interface.
* @param pvUser User data.
* @param rcReq IPRT Status code of the completed request.
*/
static DECLCALLBACK(int) ahciTransferCompleteNotify(PPDMIBLOCKASYNCPORT pInterface, void *pvUser, int rcReq)
{
ahciLog(("%s: pInterface=%p pvUser=%p uTag=%u\n",
return rc;
}
/**
* Creates the array of ranges to trim.
*
* @returns VBox status code.
* @param pAhciPort AHCI port state.
* @param pAhciportTaskState The task state handling the TRIM request.
*/
{
unsigned cRangesMax;
unsigned cRanges = 0;
int rc = VINF_SUCCESS;
/* First check that the trim bit is set and all other bits are 0. */
return VERR_INVALID_PARAMETER;
/* The data buffer contains LBA range entries. Each range is 8 bytes big. */
do
{
/*
* Count the number of valid ranges in the buffer.
* A length of 0 is invalid and is only used for padding
*/
for (unsigned i = 0; i < RT_ELEMENTS(aRanges); i++)
{
if (AHCI_RANGE_LENGTH_GET(aRanges[i]) != 0)
cRanges++;
else
break;
}
cRangesMax -= 64;
} while (cRangesMax);
if (pAhciPortTaskState->paRanges)
{
/* Convert the ranges from the guest to our format. */
do
{
for (unsigned i = 0; i < RT_ELEMENTS(aRanges); i++)
{
if (AHCI_RANGE_LENGTH_GET(aRanges[i]) != 0)
{
idxRange++;
}
else
break;
}
}
return rc;
}
{
}
/**
*
* @returns The direction of the data transfer
* @param pCmdHdr Pointer to the command header.
*/
static AHCITXDIR ahciProcessCmd(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint8_t *pCmdFis)
{
bool fLBA48 = false;
AssertMsg(pCmdFis[AHCI_CMDFIS_TYPE] == AHCI_CMDFIS_TYPE_H2D, ("FIS is not a host to device Fis!!\n"));
pAhciPortTaskState->cbTransfer = 0;
switch (pCmdFis[AHCI_CMDFIS_CMD])
{
case ATA_IDENTIFY_DEVICE:
{
{
int rc2;
/* Fill the buffer. */
/* Create scatter gather list. */
if (RT_FAILURE(rc2))
/* Copy the buffer. */
pCmdHdr->u32PRDBC = ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, &u16Temp[0], sizeof(u16Temp));
/* Destroy list. */
if (RT_FAILURE(rc2))
/* Write updated command header into memory of the guest. */
PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, pCmdHdr, sizeof(CmdHdr));
}
else
{
}
break;
}
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 */
break;
case 0x82: /* write cache disable */
break;
case 0x03:
{ /* set transfer mode */
{
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;
}
break;
}
default:
}
break;
}
case ATA_DEVICE_RESET:
{
{
}
else
{
/* Reset the device. */
}
break;
}
case ATA_FLUSH_CACHE_EXT:
case ATA_FLUSH_CACHE:
break;
case ATA_PACKET:
{
}
else
break;
{
}
else
{
}
break;
case ATA_SET_MULTIPLE_MODE:
if ( pCmdFis[AHCI_CMDFIS_SECTC] != 0
{
}
else
{
}
break;
case ATA_STANDBY_IMMEDIATE:
break; /* Do nothing. */
case ATA_CHECK_POWER_MODE:
/* fall through */
case ATA_IDLE_IMMEDIATE:
case ATA_RECALIBRATE:
case ATA_NOP:
case ATA_READ_VERIFY_SECTORS:
case ATA_SLEEP:
break;
case ATA_READ_DMA_EXT:
fLBA48 = true;
case ATA_READ_DMA:
{
rc = AHCITXDIR_READ;
break;
}
case ATA_WRITE_DMA_EXT:
fLBA48 = true;
case ATA_WRITE_DMA:
{
break;
}
case ATA_READ_FPDMA_QUEUED:
{
rc = AHCITXDIR_READ;
break;
}
case ATA_WRITE_FPDMA_QUEUED:
{
break;
}
case ATA_READ_LOG_EXT:
{
LogFlow(("Trying to read %zu bytes starting at offset %u from page %u\n", cbLogRead, offLogRead, iPage));
{
switch (iPage)
{
case 0x10:
{
LogFlow(("Reading error page\n"));
if (pTaskErr)
{
/* Calculate checksum */
/*
* Reading this log page results in an abort of all outstanding commands
* and clearing the SActive register and TaskFile register.
*/
}
break;
}
}
/* Create scatter gather list. */
if (RT_FAILURE(rc2))
/* Copy the buffer. */
pCmdHdr->u32PRDBC = ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, &aBuf[offLogRead], cbLogRead);
/* Destroy list. */
if (RT_FAILURE(rc2))
/* Write updated command header into memory of the guest. */
PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, pCmdHdr, sizeof(CmdHdr));
}
break;
}
case ATA_DATA_SET_MANAGEMENT:
{
{
rc = AHCITXDIR_TRIM;
break;
}
/* else: fall through and report error to the guest. */
}
/* All not implemented commands go below. */
case ATA_SECURITY_FREEZE_LOCK:
case ATA_SMART:
case ATA_NV_CACHE:
case ATA_IDLE:
break;
default: /* For debugging purposes. */
AssertMsgFailed(("Unknown command issued\n"));
}
return rc;
}
/**
* Retrieve a command FIS from guest memory.
*
* @returns nothing
* @param pAhciPortTaskState The state of the actual task.
*/
{
AssertMsg(pAhciPort->GCPhysAddrClb && pAhciPort->GCPhysAddrFb, ("%s: GCPhysAddrClb and/or GCPhysAddrFb are 0\n", __FUNCTION__));
/*
* 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.
*/
pAhciPortTaskState->GCPhysCmdHdrAddr = pAhciPort->GCPhysAddrClb + pAhciPortTaskState->uTag * sizeof(CmdHdr);
PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, &pAhciPortTaskState->cmdHdr, sizeof(CmdHdr));
#ifdef DEBUG
/* Print some infos about the command header. */
#endif
GCPhysAddrCmdTbl = AHCI_RTGCPHYS_FROM_U32(pAhciPortTaskState->cmdHdr.u32CmdTblAddrUp, pAhciPortTaskState->cmdHdr.u32CmdTblAddr);
AssertMsg((pAhciPortTaskState->cmdHdr.u32DescInf & AHCI_CMDHDR_CFL_MASK) * sizeof(uint32_t) == AHCI_CMDFIS_TYPE_H2D_SIZE,
("This is not a command FIS!!\n"));
/* Read the command Fis. */
LogFlow(("%s: PDMDevHlpPhysRead GCPhysAddrCmdTbl=%RGp cbCmdFis=%u\n", __FUNCTION__, GCPhysAddrCmdTbl, AHCI_CMDFIS_TYPE_H2D_SIZE));
PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrCmdTbl, &pAhciPortTaskState->cmdFis[0], AHCI_CMDFIS_TYPE_H2D_SIZE);
/* Set transfer direction. */
pAhciPortTaskState->enmTxDir = (pAhciPortTaskState->cmdHdr.u32DescInf & AHCI_CMDHDR_W) ? AHCITXDIR_WRITE : AHCITXDIR_READ;
/* If this is an ATAPI command read the atapi command. */
{
PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrCmdTbl, &pAhciPortTaskState->aATAPICmd[0], ATAPI_PACKET_SIZE);
}
/* We "received" the FIS. Clear the BSY bit in regTFD. */
{
/*
* 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
*/
}
#ifdef DEBUG
/* Print some infos about the FIS. */
/* Print the PRDT */
RTGCPHYS GCPhysAddrPRDTLEntryStart = AHCI_RTGCPHYS_FROM_U32(pAhciPortTaskState->cmdHdr.u32CmdTblAddrUp, pAhciPortTaskState->cmdHdr.u32CmdTblAddr) + AHCI_CMDHDR_PRDT_OFFSET;
ahciLog(("PRDT address %RGp number of entries %u\n", GCPhysAddrPRDTLEntryStart, AHCI_CMDHDR_PRDTL_ENTRIES(pAhciPortTaskState->cmdHdr.u32DescInf)));
{
PDMDevHlpPhysRead(pAhciPort->CTX_SUFF(pDevIns), GCPhysAddrPRDTLEntryStart, &SGEntry, sizeof(SGLEntry));
GCPhysAddrPRDTLEntryStart += sizeof(SGLEntry);
}
#endif
}
/**
* Transmit queue consumer
* Queue a new async task.
*
* @returns Success indicator.
* If false the item will not be removed and the flushing will stop.
* @param pDevIns The device instance.
* @param pItem The item to consume. Upon return this item will be freed.
*/
{
int rc = VINF_SUCCESS;
if (!pAhciPort->fAsyncInterface)
{
/* Notify the async IO thread. */
}
else
{
unsigned idx = 0;
while (idx)
{
/* Decrement to get the slot number. */
idx--;
/*
* Check if there is already an allocated task struct in the cache.
* Allocate a new task otherwise.
*/
{
}
else
bool fXchg;
/* Set current command slot */
/* 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. */
{
pAhciPortTaskState->fQueued = true;
}
else
pAhciPortTaskState->fQueued = false;
{
/* If the reset bit is set put the device into reset state. */
{
pAhciPort->fResetDevice = true;
return true;
}
{
return true;
}
else /* We are not in a reset state update the control registers. */
}
else
{
if (enmTxDir != AHCITXDIR_NONE)
{
if (enmTxDir != AHCITXDIR_FLUSH)
{
rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, (enmTxDir == AHCITXDIR_READ) ? false : true);
if (RT_FAILURE(rc))
}
if (enmTxDir == AHCITXDIR_FLUSH)
{
}
else if (enmTxDir == AHCITXDIR_READ)
{
rc = pAhciPort->pDrvBlockAsync->pfnStartRead(pAhciPort->pDrvBlockAsync, pAhciPortTaskState->uOffset,
}
else
{
rc = pAhciPort->pDrvBlockAsync->pfnStartWrite(pAhciPort->pDrvBlockAsync, pAhciPortTaskState->uOffset,
}
if (rc == VINF_VD_ASYNC_IO_FINISHED)
}
else
{
/* There is nothing left to do. Notify the guest. */
/* Add the task to the cache. */
}
} /* Command */
} /* while tasks available */
} /* fUseAsyncInterface */
return true;
}
/* The async IO thread for one port. */
{
int rc = VINF_SUCCESS;
uint64_t u64StartTime = 0;
uint64_t u64StopTime = 0;
uint32_t uIOsPerSec = 0;
uint32_t fTasksToProcess = 0;
unsigned idx = 0;
return VINF_SUCCESS;
/* We use only one task structure. */
if (!pAhciPortTaskState)
{
AssertMsgFailed(("Failed to allocate task state memory\n"));
return VERR_NO_MEMORY;
}
{
/* New run to get number of I/O requests per second?. */
if (!u64StartTime)
if (pAhci->fSignalIdle)
if (rc == VERR_TIMEOUT)
{
/* No I/O requests in-between. Reset statistics and wait again. */
pAhciPort->StatIORequestsPerSecond.c = 0;
}
break;
/* Go to sleep again if we are in redo mode. */
continue;
/* Process commands. */
while ( idx
{
idx--;
AssertMsg(pAhciPortTaskState->uTag < AHCI_NR_COMMAND_SLOTS, ("%s: Invalid Tag number %u!!\n", __FUNCTION__, pAhciPortTaskState->uTag));
/* Set current command slot */
/* 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. */
{
pAhciPortTaskState->fQueued = true;
}
else
pAhciPortTaskState->fQueued = false;
{
/* If the reset bit is set put the device into reset state. */
{
pAhciPort->fResetDevice = true;
}
{
}
/* TODO: We are not in a reset state update the control registers. */
}
else
{
if (enmTxDir == AHCITXDIR_FLUSH)
{
/* Log the error. */
if ( RT_FAILURE(rc)
{
LogRel(("AHCI#%u: Flush returned rc=%Rrc\n",
}
if (RT_FAILURE(rc))
{
{
}
else
{
/* Add the task to the mask again. */
}
}
else
{
}
{
if (pAhciPortTaskState->fQueued)
else
{
/* Task is not queued send D2H FIS */
}
}
}
else if (enmTxDir == AHCITXDIR_TRIM)
{
rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, (enmTxDir == AHCITXDIR_READ) ? false : true);
if (RT_FAILURE(rc))
if (RT_SUCCESS(rc))
{
rc = pAhciPort->pDrvBlock->pfnDiscard(pAhciPort->pDrvBlock, pAhciPortTaskState->paRanges, pAhciPortTaskState->cRanges);
}
/* Cleanup. */
if (RT_FAILURE(rc2))
/* Log the error. */
if ( RT_FAILURE(rc)
{
LogRel(("AHCI#%u: Trim returned rc=%Rrc\n",
}
if (RT_FAILURE(rc))
{
{
}
else
{
/* Add the task to the mask again. */
}
}
else
{
}
{
if (pAhciPortTaskState->fQueued)
else
{
/* Task is not queued send D2H FIS */
}
}
}
else if (enmTxDir != AHCITXDIR_NONE)
{
rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, (enmTxDir == AHCITXDIR_READ) ? false : true);
if (RT_FAILURE(rc))
/* Initialize all values. */
while (cbTransfer)
{
AssertMsg(!(cbProcess % 512), ("Number of bytes to process is not sector aligned %lu\n", cbProcess));
if (enmTxDir == AHCITXDIR_READ)
{
if (RT_FAILURE(rc))
break;
}
else
{
if (RT_FAILURE(rc))
break;
}
/* Go to the next entry. */
cbTransfer -= cbProcess;
pSegCurr++;
pSGInfoCurr++;
}
/* Log the error. */
if ( RT_FAILURE(rc)
{
LogRel(("AHCI#%u: %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
? "Read"
: "Write",
}
/* Cleanup. */
if (RT_FAILURE(rc2))
{
if (RT_FAILURE(rc))
{
{
}
else
{
/* Add the task to the mask again. */
}
}
else
{
}
{
/* Write updated command header into memory of the guest. */
if (pAhciPortTaskState->fQueued)
else
{
/* Task is not queued send D2H FIS */
}
}
}
}
else
{
/* Nothing left to do. Notify the guest. */
}
}
{
#ifdef DEBUG
/* Be paranoid. */
pAhciPortTaskState->uOffset = 0;
pAhciPortTaskState->cbTransfer = 0;
#endif
/* If we encountered an error notify the guest and continue with the next task. */
if (RT_FAILURE(rc))
{
ahciSendSDBFis(pAhciPort, 0, true);
}
}
} /* while tasks to process */
ahciSendSDBFis(pAhciPort, 0, true);
u64StopTime = RTTimeMilliTS();
/* Check if one second has passed. */
{
/* Calculate number of I/O requests per second. */
ahciLog(("%s: Processed %u requests in %llu ms -> %u requests/s\n", __FUNCTION__, uIORequestsProcessed, u64StopTime - u64StartTime, uIOsPerSec));
u64StartTime = 0;
uIORequestsProcessed = 0;
/* For the release statistics. There is no macro to set the counter to a specific value. */
}
} /* While running */
if (pAhci->fSignalIdle)
/* Free task state memory */
return VINF_SUCCESS;
}
/**
* Unblock the async I/O thread so it can respond to a state change.
*
* @returns VBox status code.
* @param pDevIns The pcnet device instance.
* @param pThread The send thread.
*/
{
}
/* -=-=-=-=- DBGF -=-=-=-=- */
/**
* AHCI status info callback.
*
* @param pDevIns The device instance.
* @param pHlp The output helpers.
* @param pszArgs The arguments.
*/
{
/*
* Show info.
*/
"%s#%d: mmio=%RGp ports=%u GC=%RTbool R0=%RTbool\n",
pThis->fGCEnabled ? true : false,
pThis->fR0Enabled ? true : false);
/*
* Show global registers.
*/
/*
* Per port data.
*/
for (unsigned i = 0; i < pThis->cPortsImpl; i++)
{
}
}
/* -=-=-=-=- 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.
*/
{
{
{
bool fFinished;
if (pThisPort->fAsyncInterface)
else
if (!fFinished)
return false;
}
}
return false;
return true;
}
/* -=-=-=-=- Saved State -=-=-=-=- */
/**
* @copydoc FNDEVSSMSAVEPREP
*/
{
return VINF_SUCCESS;
}
/**
* @copydoc FNDEVSSMLOADPREP
*/
{
return VINF_SUCCESS;
}
/**
* @copydoc FNDEVSSMLIVEEXEC
*/
{
/* config. */
for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
{
}
static const char *s_apszIdeEmuPortNames[4] = { "PrimaryMaster", "PrimarySlave", "SecondaryMaster", "SecondarySlave" };
{
}
return VINF_SSM_DONT_CALL_AGAIN;
}
/**
* @copydoc FNDEVSSMSAVEEXEC
*/
{
uint32_t i;
int rc;
/* The config */
/* The main device structure. */
/* Now every port. */
for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
{
/* ATAPI saved state. */
}
/* Now the emulated ata controllers. */
{
if (RT_FAILURE(rc))
return rc;
}
}
/**
* Loads a saved AHCI device state.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pSSM The handle to the saved state.
* @param uVersion The data unit version number.
* @param uPass The data pass.
*/
static DECLCALLBACK(int) ahciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
{
int rc;
if ( uVersion > AHCI_SAVED_STATE_VERSION
/* Verify config. */
{
{
|| u32 > AHCI_MAX_NR_PORTS_IMPL)
}
for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
{
bool fInUse;
N_("The %s VM is missing a device on port %u. Please make sure the source and target VMs have compatible storage configurations"),
LogRel(("AHCI: Port %u config mismatch: Serial number - saved='%s' config='%s'\n",
LogRel(("AHCI: Port %u config mismatch: Firmware revision - saved='%s' config='%s'\n",
LogRel(("AHCI: Port %u config mismatch: Model number - saved='%s' config='%s'\n",
}
static const char *s_apszIdeEmuPortNames[4] = { "PrimaryMaster", "PrimarySlave", "SecondaryMaster", "SecondarySlave" };
{
if (iPortSaved != iPort)
}
}
if (uPass == SSM_PASS_FINAL)
{
/* Restore data. */
/* The main device structure. */
/* Now every port. */
for (uint32_t i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
{
if (uVersion < AHCI_SAVED_STATE_VERSION)
{
/* The old positions in the FIFO, not required. */
}
if (uVersion >= AHCI_SAVED_STATE_VERSION)
{
}
/* Check if we have tasks pending. */
if (pAhciPort->u32TasksNew)
{
/*
* There are tasks pending. The VM was saved after a task failed
* because of non-fatal error. Set the redo flag.
*/
}
}
/* Now the emulated ata controllers. */
{
if (RT_FAILURE(rc))
return rc;
}
if (RT_FAILURE(rc))
return rc;
}
return VINF_SUCCESS;
}
/* -=-=-=-=- device PDM interface -=-=-=-=- */
{
uint32_t i;
/* Relocate every port. */
{
}
/* Relocate emulated ATA controllers. */
}
/**
* 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.
*/
{
int rc = VINF_SUCCESS;
unsigned iActPort = 0;
/*
* 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 (pAhciPort->pAsyncIOThread)
{
/* Destroy the event semaphore. */
if (RT_FAILURE(rc))
{
}
}
/* Free all cached tasks. */
for (uint32_t i = 0; i < AHCI_NR_COMMAND_SLOTS; i++)
{
if (pAhciPort->aCachedTasks[i])
{
}
}
}
/* Destroy emulated ATA controllers. */
}
return rc;
}
/**
* SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium removed" event
* from now on, regardless if there was a medium inserted or not.
*/
{
}
/**
* SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium inserted". If
* there was already a medium inserted, don't forget to send the "medium
* removed" event first.
*/
{
do
{
switch (OldStatus)
{
/* no change, we will send "medium removed" + "medium inserted" */
break;
default:
break;
}
}
/**
* Called when a media is mounted.
*
* @param pInterface Pointer to the interface structure containing the called function pointer.
*/
{
/* Ignore the call if we're called while being attached. */
return;
{
LogRel(("AHCI: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough unchanged\n", pAhciPort->iLUN, pAhciPort->cTotalSectors));
/* Report media changed in TEST UNIT and other (probably incorrect) places. */
}
else
AssertMsgFailed(("Hard disks don't have a mount interface!\n"));
}
/**
* Called when a media is unmounted
* @param pInterface Pointer to the interface structure containing the called function pointer.
*/
{
pAhciPort->cTotalSectors = 0;
{
/*
* Whatever I do, XP will not use the GET MEDIA STATUS nor the EVENT stuff.
* However, it will respond to TEST UNIT with a 0x6 0x28 (media changed) sense code.
* So, we'll give it 4 TEST UNIT command to catch up, two which the media is not
* present and 2 in which it is changed.
*/
}
else
AssertMsgFailed(("Hard disks don't have a mount interface!\n"));
}
/**
* 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.
*/
{
int rc = VINF_SUCCESS;
/*
* Query the block and blockbios interfaces.
*/
{
return VERR_PDM_MISSING_INTERFACE;
}
if (!pAhciPort->pDrvBlockBios)
{
return VERR_PDM_MISSING_INTERFACE;
}
/* Try to get the optional async block interface. */
/*
* Validate type.
*/
if ( enmType != PDMBLOCKTYPE_HARD_DISK
&& enmType != PDMBLOCKTYPE_CDROM
&& enmType != PDMBLOCKTYPE_DVD)
{
AssertMsgFailed(("Configuration error: LUN#%d isn't a disk or cd/dvd. enmType=%d\n", pAhciPort->iLUN, enmType));
return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
}
{
AssertMsgFailed(("Internal error: CD/DVD-ROM without a mountable interface\n"));
return VERR_INTERNAL_ERROR;
}
pAhciPort->fATAPIPassthrough = pAhciPort->fATAPI ? (pAhciPort->pDrvBlock->pfnSendCmd != NULL) : false;
{
LogRel(("AHCI LUN#%d: CD/DVD, total number of sectors %Ld, passthrough %s\n", pAhciPort->iLUN, pAhciPort->cTotalSectors, (pAhciPort->fATAPIPassthrough ? "enabled" : "disabled")));
}
else
{
if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
{
}
else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
{
rc = VINF_SUCCESS;
}
{
/* Set the disk geometry information. Ignore errors. */
rc = VINF_SUCCESS;
}
LogRel(("AHCI: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n",
}
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.
*/
{
return false;
return true;
}
/**
* Common worker for ahciR3Suspend and ahciR3PowerOff.
*/
{
else
}
/**
* Suspend notification.
*
* @param pDevIns The device instance data.
*/
{
Log(("ahciR3Suspend\n"));
}
/**
* Resume notification.
*
* @param pDevIns The device instance data.
*/
{
/*
* Check if one of the ports has pending tasks.
* Queue a notification item again in this case.
*/
{
if (pAhciPort->u32TasksNew)
{
PDEVPORTNOTIFIERQUEUEITEM pItem = (PDEVPORTNOTIFIERQUEUEITEM)PDMQueueAlloc(pAhci->CTX_SUFF(pNotifierQueue));
}
}
}
/**
* Initializes the VPD data of a attached device.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pAhciPort The attached device.
* @param szName Name of the port to get the CFGM node.
*/
{
int rc = VINF_SUCCESS;
/* Generate a default serial number. */
else
RTUuidClear(&Uuid);
{
/* Generate a predictable serial for drives which don't have a UUID. */
}
else
/* Get user config if present using defaults otherwise. */
rc = CFGMR3QueryStringDef(pCfgNode, "SerialNumber", pAhciPort->szSerialNumber, sizeof(pAhciPort->szSerialNumber),
szSerial);
if (RT_FAILURE(rc))
{
if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
N_("AHCI configuration error: \"SerialNumber\" is longer than 20 bytes"));
N_("AHCI configuration error: failed to read \"SerialNumber\" as string"));
}
rc = CFGMR3QueryStringDef(pCfgNode, "FirmwareRevision", pAhciPort->szFirmwareRevision, sizeof(pAhciPort->szFirmwareRevision),
"1.0");
if (RT_FAILURE(rc))
{
if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
N_("AHCI configuration error: \"FirmwareRevision\" is longer than 8 bytes"));
N_("AHCI configuration error: failed to read \"FirmwareRevision\" as string"));
}
rc = CFGMR3QueryStringDef(pCfgNode, "ModelNumber", pAhciPort->szModelNumber, sizeof(pAhciPort->szModelNumber),
if (RT_FAILURE(rc))
{
if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
N_("AHCI configuration error: \"ModelNumber\" is longer than 40 bytes"));
N_("AHCI configuration error: failed to read \"ModelNumber\" as string"));
}
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read \"NonRotationalMedium\" as boolean"));
/* There are three other identification strings for CD drives used for INQUIRY */
{
rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIVendorId", pAhciPort->szInquiryVendorId, sizeof(pAhciPort->szInquiryVendorId),
"VBOX");
if (RT_FAILURE(rc))
{
if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
N_("AHCI configuration error: \"ATAPIVendorId\" is longer than 16 bytes"));
N_("AHCI configuration error: failed to read \"ATAPIVendorId\" as string"));
}
rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIProductId", pAhciPort->szInquiryProductId, sizeof(pAhciPort->szInquiryProductId),
"CD-ROM");
if (RT_FAILURE(rc))
{
if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
N_("AHCI configuration error: \"ATAPIProductId\" is longer than 16 bytes"));
N_("AHCI configuration error: failed to read \"ATAPIProductId\" as string"));
}
rc = CFGMR3QueryStringDef(pCfgNode, "ATAPIRevision", pAhciPort->szInquiryRevision, sizeof(pAhciPort->szInquiryRevision),
"1.0");
if (RT_FAILURE(rc))
{
if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
N_("AHCI configuration error: \"ATAPIRevision\" is longer than 4 bytes"));
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.
*/
{
int rc = VINF_SUCCESS;
if (!pAhciPort->fAsyncInterface)
{
int rcThread;
/* Destroy the thread. */
AssertMsgFailed(("%s Failed to destroy async IO thread rc=%Rrc rcThread=%Rrc\n", __FUNCTION__, rc, rcThread));
if (RT_FAILURE(rc))
}
/* Check if the changed port uses IDE emulation. */
bool fMaster = false;
{
{
fMaster = j == 0 ? true : false;
}
}
if (pCtl)
if (!(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG))
{
/*
* Inform the guest about the removed device.
*/
/*
* 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).
*/
}
/*
* Zero some important members.
*/
}
/**
* 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.
*/
{
int rc;
/* the usual paranoia */
/*
* Try attach the block device and get the interfaces,
* required as well as optional.
*/
rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPort->IBase, &pAhciPort->pDrvBase, NULL);
if (RT_SUCCESS(rc))
else
if (RT_FAILURE(rc))
{
}
else
{
/* Check if the changed port uses IDE emulation. */
bool fMaster = false;
{
{
fMaster = j == 0 ? true : false;
}
}
/* Attach to the controller if available */
if (pCtl)
if (RT_SUCCESS(rc))
{
char szName[24];
if ( pAhciPort->pDrvBlockAsync
{
pAhciPort->fAsyncInterface = true;
}
else
{
pAhciPort->fAsyncInterface = false;
/* Create event semaphore. */
if (RT_FAILURE(rc))
{
return rc;
}
/* Create the async IO thread. */
rc = PDMDevHlpThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop, ahciAsyncIOLoopWakeUp, 0,
if (RT_FAILURE(rc))
{
return rc;
}
}
/*
* Init vendor product data.
*/
if (RT_SUCCESS(rc))
/* Inform the guest about the added device in case of hotplugging. */
if ( RT_SUCCESS(rc)
&& !(fFlags & PDM_TACH_FLAGS_NOT_HOT_PLUG))
{
/*
* Initialize registers
*/
else
(0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
(0x03 << 0); /* Device detected and communication established. */
}
}
}
return rc;
}
/**
* Common reset worker.
*
* @param pDevIns The device instance data.
*/
{
/* Hardware reset for the ports. */
return VINF_SUCCESS;
}
/**
* Callback employed by ahciR3Reset.
*
* @returns true if we've quiesced, false if we're still working.
* @param pDevIns The device instance.
*/
{
return false;
return true;
}
/**
* Reset notification.
*
* @param pDevIns The device instance data.
*/
{
else
{
}
}
/**
* Poweroff notification.
*
* @param pDevIns Pointer to the device instance
*/
{
Log(("achiR3PowerOff\n"));
}
/**
* @interface_method_impl{PDMDEVREG,pfnConstruct}
*/
{
int rc = VINF_SUCCESS;
unsigned i = 0;
bool fGCEnabled = false;
bool fR0Enabled = false;
/*
* Validate and read configuration.
*/
"R0Enabled\0"
"PrimaryMaster\0"
"PrimarySlave\0"
"SecondaryMaster\0"
"SecondarySlave\0"
"PortCount\0"
"UseAsyncInterfaceIfAvailable\0"
"Bootable\0"
"CmdSlotsAvail\0"))
N_("AHCI configuration error: unknown option specified"));
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read GCEnabled as boolean"));
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read R0Enabled as boolean"));
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read PortCount as integer"));
N_("AHCI configuration error: PortCount=%u should not exceed %u"),
N_("AHCI configuration error: PortCount=%u should be at least 1"),
pThis->cPortsImpl);
rc = CFGMR3QueryBoolDef(pCfg, "UseAsyncInterfaceIfAvailable", &pThis->fUseAsyncInterfaceIfAvailable, true);
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read UseAsyncInterfaceIfAvailable as boolean"));
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read Bootable as boolean"));
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read CmdSlotsAvail as integer"));
N_("AHCI configuration error: CmdSlotsAvail=%u should not exceed %u"),
N_("AHCI configuration error: CmdSlotsAvail=%u should be at least 1"),
#ifdef VBOX_WITH_MSI_DEVICES
#else
#endif
/*
* Register the PCI device, it's I/O regions.
*/
if (RT_FAILURE(rc))
return rc;
#ifdef VBOX_WITH_MSI_DEVICES
if (RT_FAILURE (rc))
{
/* That's OK, we can work without MSI */
}
#endif
/*
* Solaris 10 U5 fails to map the AHCI register space when the sets (0..5) for the legacy
* IDE registers are not available.
* We set up "fake" entries in the PCI configuration register.
* 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).
*/
if (RT_FAILURE(rc))
N_("AHCI cannot register PCI I/O region"));
if (RT_FAILURE(rc))
N_("AHCI cannot register PCI I/O region"));
if (RT_FAILURE(rc))
N_("AHCI cannot register PCI I/O region"));
if (RT_FAILURE(rc))
N_("AHCI cannot register PCI I/O region"));
if (RT_FAILURE(rc))
N_("AHCI cannot register PCI I/O region for BMDMA"));
if (RT_FAILURE(rc))
N_("AHCI cannot register PCI memory region for registers"));
if (RT_FAILURE(rc))
{
return rc;
}
/* Create the timer for command completion coalescing feature. */
if (RT_FAILURE(rc))
{
return rc;
}
/* Status LUN. */
/*
* Create the notification queue.
*
* We need 2 items for every port because of SMP races.
*/
if (RT_FAILURE(rc))
return rc;
/* Initialize static members on every port. */
for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
{
/*
* Init members of the port.
*/
/* Register statistics counter. */
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatDMA, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatIORequestsPerSecond, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
"Number of processed I/O requests per second.", "/Devices/SATA%d/Port%d/IORequestsPerSecond", iInstance, i);
#ifdef VBOX_WITH_STATISTICS
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileProcessTime, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
"Amount of time to process one request.", "/Devices/SATA%d/Port%d/ProfileProcessTime", iInstance, i);
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileMapIntoR3, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
"Amount of time to map the guest buffers into R3.", "/Devices/SATA%d/Port%d/ProfileMapIntoR3", iInstance, i);
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileReadWrite, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
"Amount of time for the read/write operation to complete.", "/Devices/SATA%d/Port%d/ProfileReadWrite", iInstance, i);
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatProfileDestroyScatterGatherList, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
"Amount of time to destroy the scatter gather list and free associated resources.", "/Devices/SATA%d/Port%d/ProfileDestroyScatterGatherList", iInstance, i);
#endif
}
/* Attach drivers to every available port. */
for (i = 0; i < pThis->cPortsImpl; i++)
{
char szName[24];
/*
* Init interfaces.
*/
pAhciPort->fAsyncIOThreadIdle = true;
/*
* Attach the block driver
*/
rc = PDMDevHlpDriverAttach(pDevIns, pAhciPort->iLUN, &pAhciPort->IBase, &pAhciPort->pDrvBase, szName);
if (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
{
return rc;
}
/* Mark that a device is present on that port */
if (i < 6)
/*
* Init vendor product data.
*/
if (RT_FAILURE(rc))
return rc;
/*
* If the new async interface is available we use a PDMQueue to transmit
* the requests into R3.
* Otherwise we use a event semaphore and a async I/O thread which processes them.
*/
{
pAhciPort->fAsyncInterface = true;
}
else
{
pAhciPort->fAsyncInterface = false;
rc = PDMDevHlpThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop, ahciAsyncIOLoopWakeUp, 0,
}
}
else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
{
rc = VINF_SUCCESS;
}
else
}
/*
* Attach status driver (optional).
*/
if (RT_SUCCESS(rc))
{
}
else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
{
}
{
/*
* Setup IDE emulation.
* We only emulate the I/O ports but not bus master DMA.
* If the configuration values are not found the setup of the ports is as follows:
* Primary Master: Port 0
* Primary Slave: Port 1
* Secondary Master: Port 2
* Secondary Slave: Port 3
*/
/*
* Setup I/O ports for the PCI device.
*/
{
uint32_t cbSSMState = 0;
{
{ "PrimaryMaster", "PrimarySlave" },
{ "SecondaryMaster", "SecondarySlave" }
};
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
char szName[24];
&cbSSMState, szName);
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
return rc;
if (pThis->fR0Enabled)
{
if (RT_FAILURE(rc))
return rc;
}
if (pThis->fGCEnabled)
{
if (RT_FAILURE(rc))
return rc;
}
if (RT_FAILURE(rc))
return rc;
if (pThis->fR0Enabled)
{
if (RT_FAILURE(rc))
return rc;
}
if (pThis->fGCEnabled)
{
if (RT_FAILURE(rc))
return rc;
}
}
}
rc = PDMDevHlpSSMRegisterEx(pDevIns, AHCI_SAVED_STATE_VERSION, sizeof(*pThis)+cbTotalBufferSize, NULL,
if (RT_FAILURE(rc))
return rc;
/*
* Register the info item.
*/
char szTmp[128];
}
/**
* The device registration structure.
*/
const PDMDEVREG g_DeviceAHCI =
{
/* u32Version */
/* szName */
"ahci",
/* szRCMod */
"VBoxDDGC.gc",
/* szR0Mod */
"VBoxDDR0.r0",
/* pszDescription */
"Intel AHCI controller.\n",
/* fFlags */
/* fClass */
/* cMaxInstances */
~0,
/* cbInstance */
sizeof(AHCI),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
/* pfnSuspend */
/* pfnResume */
/* pfnAttach */
/* pfnDetach */
/* pfnQueryInterface. */
NULL,
/* pfnInitComplete */
NULL,
/* pfnPowerOff */
/* pfnSoftReset */
NULL,
/* u32VersionEnd */
};
#endif /* IN_RING3 */
#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */