DevAHCI.cpp revision 7a6ba152515c963d275e7c1371ba39155ec6cf58
/* $Id$ */
/** @file
* VBox storage devices: AHCI controller device (disk and cdrom).
* Implements the AHCI standard 1.1
*/
/*
* Copyright (C) 2006-2009 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 transfered in an asychronous 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
#include <VBox/pdmqueue.h>
#include <VBox/pdmthread.h>
#include <VBox/pdmcritsect.h>
#ifdef IN_RING3
# include <iprt/semaphore.h>
#endif
#include "ide.h"
#include "ATAController.h"
#include "../Builtins.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 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
/* 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 */
} AHCITXDIR;
/**
* A task state.
*/
typedef struct AHCIPORTTASKSTATE
{
/** Tag of the task. */
/** Command is queued. */
bool fQueued;
/** The command header for this task. */
/** The command Fis for this task. */
/** The ATAPI comnmand data. */
/** 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. */
#ifdef RT_STRICT
/** Flag whether the task state is currently active - used for debugging */
volatile bool fActive;
#endif
/**
* Notifier queue item.
*/
typedef struct DEVPORTNOTIFIERQUEUEITEM
{
/** The core part owned by the queue manager. */
/** On which port the async io thread should be put into action. */
/** Which task to process. */
/** Flag whether the task is queued. */
/**
* @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;
/** If we use the new async interface. */
bool fAsyncInterface;
#if HC_ARCH_BITS == 64
#endif
/** Async IO Thread. */
/** Request semaphore. */
/** Task queue. */
/** Actual write position. */
/** Actual read position. */
/** Actual number of active tasks. */
volatile uint32_t uActTasksActive;
/** Device is powered on. */
bool fPoweredOn;
/** Device has spun up. */
bool fSpunUp;
/** First D2H FIS was send. */
bool fFirstD2HFisSend;
bool fATAPI;
#if HC_ARCH_BITS == 64
#endif
/** Device specific settings. */
/** 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
/** Number of total sectors. */
/** Currently configured number of sectors in a multi-sector transfer. */
/** ATAPI sense key. */
/** ATAPI additional sens code. */
/** HACK: Countdown till we report a newly unmounted drive as mounted. */
/** The LUN. */
/** Flag if we are in a device reset. */
bool fResetDevice;
/** Bitmask for finished tasks. */
volatile uint32_t u32TasksFinished;
/** Bitmask for finished queued tasks. */
volatile uint32_t u32QueuedTasksFinished;
/**
* Array of cached tasks. The tag number is the index value.
* Only used with the async interface.
*/
/** First task throwing an error. */
#if 0 /*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 */
/** Flag whether a notification was already send to R3. */
volatile bool fNotificationSend;
/** Flag whether this port is in a reset state. */
volatile bool fPortReset;
/** Flag whether the I/O thread idles. */
volatile bool fAsyncIOThreadIdle;
/** 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. */
#if HC_ARCH_BITS == 64
#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 */
#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. */
/** 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;
bool afAlignment8[1];
/** Number of usable ports on this controller. */
#if HC_ARCH_BITS == 64
#endif
/** Flag whether we have written the first 4bytes in an 8byte MMIO write successfully. */
volatile bool f8ByteMMIO4BytesWrittenSuccessfully;
/** At which number of I/O requests per second we consider having high I/O load. */
/** How many milliseconds to sleep. */
} 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.
*/
#define AHCI_TASK_IS_QUEUED(x) ((x) & 0x1)
#define AHCI_TASK_GET_TAG(x) ((x) >> 1)
/**
* 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
PDMBOTHCBDECL(int) ahciMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
PDMBOTHCBDECL(int) ahciMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
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);
PDMBOTHCBDECL(int) ahciLegacyFakeRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb);
#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 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.
*/
{
{
{
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.
*/
{
}
}
}
}
#ifdef IN_RING3
/*
* Assert irq when an CCC timeout occurs
*/
{
}
#endif
{
/* Update the CI register first. */
{
/* Mark the tasks set in the value as used. */
for (uint8_t i = 0; i < AHCI_NR_COMMAND_SLOTS; i++)
{
/* Queue task if bit is set in written value and not already in progress. */
{
if (!pAhciPort->fAsyncInterface)
{
/* Put the tag number of the task into the FIFO. */
if (!fNotificationSend)
{
/* Send new notification. */
}
}
else
{
}
}
}
}
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)
{
}
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
{
AHCI_PORT_SCTL_IPM_GET(u32Value), AHCI_PORT_SCTL_SPD_GET(u32Value), AHCI_PORT_SCTL_DET_GET(u32Value)));
{
pAhciPort->fFirstD2HFisSend = false;
}
{
#ifndef IN_RING3
return VINF_IOM_HC_MMIO_WRITE;
#else
{
/* Reset queue. */
pAhciPort->uActWritePos = 0;
pAhciPort->uActReadPos = 0;
/* 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. */
{
}
}
#endif
}
return VINF_SUCCESS;
}
{
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
*/
{
int rc = VINF_SUCCESS;
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. */
}
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.
*/
(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 rc;
}
/**
* 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.
*/
{
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*/
return VINF_SUCCESS;
}
/**
* 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)));
return VINF_SUCCESS;
}
/**
* 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[] =
{
};
/**
* Reset initiated by system software for one port.
*
* @param pAhciPort The port to reset.
*/
{
AHCI_PORT_CMD_SUD | /* Device has spun up. */
AHCI_PORT_CMD_POD; /* Port is powered on. */
pAhciPort->fResetDevice = false;
pAhciPort->fPoweredOn = true;
pAhciPort->fNotificationSend = false;
pAhciPort->u32TasksFinished = 0;
pAhciPort->uActWritePos = 0;
pAhciPort->uActReadPos = 0;
pAhciPort->uActTasksActive = 0;
{
if (pAhciPort->fPoweredOn)
{
/*
* Set states in the Port Signature and SStatus registers.
*/
(0x02 << 4) | /* Generation 2 (3.0GBps) speed. */
(0x03 << 0); /* Device detected and communication established. */
}
}
}
#ifdef IN_RING3
/**
* 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;
}
#endif
/**
* 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 */
}
/**
* 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",
/*
* 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 (uOffset < 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 uOffset=%x iPort=%x iRegOffset=%x iReg=%x!!!\n", __FUNCTION__, cb, uOffset, iPort, iRegOffset, iReg));
}
}
}
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 *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.
*/
if (uOffset < 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;
}
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;
}
#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;
}
/* -=-=-=-=-=- 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;
}
#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 Poitner 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 neccessary.
*
* @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, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
{
pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] = (pAhciPortTaskState->cmdFis[AHCI_CMDFIS_SECTN] & ~7) |
}
{
{
if (*pbSrc)
else
pbDst[i] = ' ';
}
}
{
{
if (*pbSrc)
else
}
}
{
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 */
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 */
/* 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 */
return VINF_SUCCESS;
}
//static int atapiPassthroughSS(PAHCIPORTTASKSTATE, PAHCIPort, int *);
/**
*/
typedef enum ATAPIFN
{
ATAFN_SS_NULL = 0,
//ATAFN_SS_ATAPI_PASSTHROUGH,
} ATAPIFN;
/**
* Make sure ATAFNSS and this array match!
*/
{
NULL,
//atapiPassthroughSS
};
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)
{
atapiCmdError(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 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)
{
atapiCmdError(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). Also
* implement signalling "no current profile" if no medium is loaded. */
/* The MMC-3 spec says that DVD-ROM read capability should be reported
* before CD-ROM read capability. */
/* Copy the buffer in to the scatter gather list. */
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. */
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;
{
atapiCmdError(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;
}
static int atapiDoTransfer(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, ATAPIFN iSourceSink)
{
int cbTransfered;
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;
}
{
int iTxDir = PDMBLOCKTXDIR_NONE;
switch (pbPacket[0])
{
case SCSI_TEST_UNIT_READY:
if (pAhciPort->cNotifiedMediaChange > 0)
{
else
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
}
else
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:
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
break;
}
}
break;
case SCSI_REQUEST_SENSE:
break;
{
else
}
else
break;
case SCSI_READ_10:
case SCSI_READ_12:
{
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
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();
}
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
}
break;
case SCSI_READ_CD:
{
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
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();
}
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
{
case 0x00:
/* nothing */
break;
case 0x10:
/* normal read */
break;
case 0xf8:
/* read all data */
break;
default:
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
}
}
break;
case SCSI_SEEK_10:
{
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
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();
}
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
}
break;
case SCSI_START_STOP_UNIT:
{
int rc = 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(rc))
else
atapiCmdError(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)
{
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
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:
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
}
}
break;
case SCSI_READ_CAPACITY:
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
break;
}
break;
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
break;
}
break;
if (pAhciPort->cNotifiedMediaChange > 0)
{
atapiCmdError(pAhciPort, pAhciPortTaskState, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
{
break;
}
break;
case SCSI_GET_CONFIGURATION:
/* No media change stuff here, it can confuse Linux guests. */
break;
case SCSI_INQUIRY:
break;
default:
break;
}
return iTxDir;
}
/**
* 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)
{
/* 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. */
}
/**
* 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;
}
if (fInterrupt)
{
/* Check if we should assert an interrupt */
fAssertIntr = true;
}
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;
PAHCIPORTTASKSTATE pTaskErr = (PAHCIPORTTASKSTATE)ASMAtomicReadPtr((void * volatile *)&pAhciPort->pTaskErr);
{
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. */
/* 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 procecssed
* before an error occurred. Used to free
* any ressources 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 procesed. */
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
*/
{
}
/*
* 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;
}
/**
* 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 transfered.
* @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;
int cbCopied = 0;
{
/* We finished. */
if (!cbBuf)
break;
/* Advance the buffer. */
/* Go to the next entry in the list. */
pSGEntry++;
cSGEntry++;
}
return cbCopied;
}
/* -=-=-=-=- IBlockAsyncPort -=-=-=-=- */
/** Makes a PAHCIPort out of a PPDMIBLOCKASYNCPORT. */
#define PDMIBLOCKASYNCPORT_2_PAHCIPORT(pInterface) ( (PAHCIPort)((uintptr_t)pInterface - RT_OFFSETOF(AHCIPort, IPortAsync)) )
/**
* Complete a data transfer task by freeing all occupied ressources
* 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)
{
/* 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",
}
}
else
{
/* Write updated command header into memory of the guest. */
}
{
}
{
}
if (pAhciPortTaskState->fQueued)
{
#ifdef RT_STRICT
#endif
if (!cOutstandingTasks)
ahciSendSDBFis(pAhciPort, 0, true);
}
else
{
#ifdef RT_STRICT
#endif
}
/* Add the task to the cache. */
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;
}
/**
*
* @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_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"));
PAHCIPORTTASKSTATE pTaskErr = (PAHCIPORTTASKSTATE)ASMAtomicXchgPtr((void * volatile *)&pAhciPort->pTaskErr, NULL);
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;
}
/* All not implemented commands go below. */
case ATA_SECURITY_FREEZE_LOCK:
case ATA_SMART:
case ATA_NV_CACHE:
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
{
/*
* Check if there is already an allocated task struct in the cache.
* Allocate a new task otherwise.
*/
{
}
else
{
}
#ifdef RT_STRICT
#endif
/** 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. */
if (pNotifierItem->fQueued)
{
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. */
{
}
}
if (enmTxDir != AHCITXDIR_NONE)
{
if (pAhciPortTaskState->fQueued)
{
}
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
{
#ifdef RT_STRICT
#endif
/* There is nothing left to do. Notify the guest. */
/* Add the task to the cache. */
}
}
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;
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 inbetween. Reset statistics and wait again. */
pAhciPort->StatIORequestsPerSecond.c = 0;
}
break;
/*
* To maximize the throughput of the controller we try to minimize the
* number of world switches during interrupts by grouping as many
* I/O requests together as possible.
* On the other side we want to get minimal latency if the I/O load is low.
* Thatswhy the number of I/O requests per second is measured and if it is over
* a threshold the thread waits for other requests from the guest.
*/
{
do
{
/* Sleep some time. */
/* Check if we got some new requests inbetween. */
{
/*
* Check if the queue is full. If that is the case
* there is no point waiting another round.
*/
&& (RT_ELEMENTS(pAhciPort->ahciIOTasks) - pAhciPort->uActReadPos + uActWritePosPrev) == AHCI_NR_COMMAND_SLOTS) )
{
break;
}
}
else /* No change break out of the loop. */
{
#ifdef DEBUG
else
#endif
break;
}
} while (true);
}
/* Process commands. */
while ( (cTasksToProcess > 0)
{
AssertMsg(pAhciPortTaskState->uTag < AHCI_NR_COMMAND_SLOTS, ("%s: Invalid Tag number!!\n", __FUNCTION__));
/** 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. */
if (AHCI_TASK_IS_QUEUED(uActTag))
{
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
{
}
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
{
}
/* 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;
/* Make the port number invalid making it easier to track down bugs. */
#endif
pAhciPort->uActReadPos++;
/* If we encountered an error notify the guest and continue with the next task. */
if (RT_FAILURE(rc))
{
uQueuedTasksFinished = 0;
}
else if (!cTasksToProcess)
}
uQueuedTasksFinished = 0;
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 rc;
}
/**
* 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.
*/
{
}
/**
* 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;
/*
* Initialize registers
*/
}
/**
* Called when a media is unmounted
* @param pInterface Pointer to the interface structure containing the called function pointer.
*/
{
pAhciPort->cTotalSectors = 0;
/*
* Inform the guest about the removed device.
*/
}
/* -=-=-=-=- 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++)
{
/* No need to save */
}
/* 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++)
{
}
/* 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;
}
/**
* 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;
}
{
LogRel(("AHCI LUN#%d: CD/DVD, total number of sectors %Ld\n", pAhciPort->iLUN, pAhciPort->cTotalSectors));
}
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.
*/
{
return;
}
/**
* 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))
}
/*
* 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
{
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;
}
}
}
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"
"HighIOThreshold\0"
"MillisToSleep\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 HighIOThreshold as integer"));
if (RT_FAILURE(rc))
N_("AHCI configuration error: failed to read MillisToSleep as integer"));
/*
* Register the PCI device, it's I/O regions.
*/
if (RT_FAILURE(rc))
return rc;
/*
* 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"));
rc = PDMDevHlpPCIIORegionRegister(pDevIns, 4, 0x10, PCI_ADDRESS_SPACE_IO, ahciR3LegacyFakeIORangeMap);
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 transmit queue.
*/
rc = PDMDevHlpQueueCreate(pDevIns, sizeof(DEVPORTNOTIFIERQUEUEITEM), 30*32 /*Maximum of 30 ports multiplied with 32 tasks each port*/, 0,
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_ALWAYS, STAMUNIT_OCCURENCES,
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatIORequestsPerSecond, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, 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_ALWAYS, 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_ALWAYS, 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_ALWAYS, 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_ALWAYS, STAMUNIT_NS_PER_CALL,
"Amount of time to destroy the scatter gather list and free associated ressources.", "/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.
*/
/* 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"));
}
/* 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_("PIIX3 configuration error: \"ATAPIVendorId\" is longer than 16 bytes"));
N_("PIIX3 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_("PIIX3 configuration error: \"ATAPIProductId\" is longer than 16 bytes"));
N_("PIIX3 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_("PIIX3 configuration error: \"ATAPIRevision\" is longer than 4 bytes"));
N_("PIIX3 configuration error: failed to read \"ATAPIRevision\" as string"));
}
}
/*
* 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
#ifdef DEBUG
for (uint32_t j = 0; j < AHCI_NR_COMMAND_SLOTS; j++)
#endif
}
/*
* 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];
rc = ataControllerInit(pDevIns, pCtl, pThis->ahciPort[iPortMaster].pDrvBase, pThis->ahciPort[iPortSlave].pDrvBase,
&cbSSMState, szName, &pThis->ahciPort[iPortMaster].Led, &pThis->ahciPort[iPortMaster].StatBytesRead,
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;
}
/**
* 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 */