DevAHCI.cpp revision d3faf04f5ef353bbc31bb75a17444d7902726d2e
/* $Id$ */
/** @file
*
* VBox storage devices:
* AHCI controller device (disk).
* Implements the AHCI standard 1.1
*/
/*
* Copyright (C) 2006-2009 Sun Microsystems, Inc.
*
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/** @page pg_dev_ahci AHCI - Advanced Host Controller Interface Emulation.
*
* This component implements an AHCI SATA 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.
*
*/
/*******************************************************************************
* 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_SAVED_STATE_VERSION 2
#define AHCI_NR_OF_ALLOWED_BIGGER_LISTS 100
/**
* 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
/* 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. */
/**
* 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 */
/** Number of scatter gather list entries. */
/** How many entries would fit into the sg list. */
/** Pointer to the first entry of the scatter gather list. */
/** 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. */
/**
* 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. */
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.
*/
/** 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. */
#if HC_ARCH_BITS == 64
#endif
/*
* Main AHCI device state.
*/
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
/** The base interface */
/** Status Port - Leds interface. */
/** 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. */
/** 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. */
/** The critical section. */
/** 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. */
/* 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);
#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)
{
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;
{
/* We received a COMINIT signal */
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) ahciMMIOMap(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) ahciLegacyFakeIORangeMap(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) ahciStatus_QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
{
if (iLUN < AHCI_MAX_NR_PORTS_IMPL)
{
return VINF_SUCCESS;
}
return VERR_PDM_LUN_NOT_FOUND;
}
/**
* Queries an interface to the driver.
*
* @returns Pointer to interface.
* @returns NULL if the interface was not supported by the device.
* @param pInterface Pointer to ATADevState::IBase.
* @param enmInterface The requested interface identification.
*/
static DECLCALLBACK(void *) ahciStatus_QueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
{
switch (enmInterface)
{
case PDMINTERFACE_BASE:
case PDMINTERFACE_LED_PORTS:
default:
return NULL;
}
}
/**
* Query interface method for the AHCI port.
*/
{
switch (enmInterface)
{
case PDMINTERFACE_BASE:
case PDMINTERFACE_BLOCK_PORT:
return &pAhciPort->IPortAsync;
return &pAhciPort->IMountNotify;
default:
return NULL;
}
}
{
uint32_t i;
/* Relocate every port. */
{
}
/* Relocate emulated ATA controllers. */
}
#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];
char aSerial[20];
int rc;
rc = pAhciPort->pDrvBlock ? pAhciPort->pDrvBlock->pfnGetUuid(pAhciPort->pDrvBlock, &Uuid) : RTUuidClear(&Uuid);
{
/* Generate a predictable serial for drives which don't have a UUID. */
}
else
memset(p, 0, 512);
/* Removable CDROM, 50us response, 12 byte packets */
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. */
ahciScatterGatherListCopyFromBuffer(pAhciPortTaskState, (void *)&p[0], sizeof(p));
*pcbData = sizeof(p);
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. */
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
/* Write updated command header into memory of the guest. */
PDMDevHlpPhysWrite(pAhciPort->CTX_SUFF(pDevIns), pAhciPortTaskState->GCPhysCmdHdrAddr, &pAhciPortTaskState->cmdHdr, sizeof(CmdHdr));
return rcSourceSink;
}
static int atapiReadSectors(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
{
switch (cbSector)
{
case 2048:
break;
case 2352:
{
AssertMsgFailed(("2352 read\n"));
/* @todo: This is quite difficult as the data transfer is not handled here
We need to add the sync bytes etc. here and modify the pointers
and size of the sg entries. */
#if 0
{
/* sync bytes */
*pbBuf++ = 0x00;
pbBuf += 11;
/* MSF */
ataLBA2MSF(pbBuf, i);
pbBuf += 3;
/* data */
if (RT_FAILURE(rc))
break;
pbBuf += 2048;
/* ECC */
pbBuf += 288;
}
#endif
}
break;
default:
AssertMsgFailed(("Unsupported sectors size\n"));
break;
}
return VINF_SUCCESS;
}
{
int rc = 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 rc;
}
/**
* 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 pAhciPortTaskState The state of the last task.
* @param fInterrupt If an interrupt should be asserted.
*/
static void ahciSendSDBFis(PAHCIPort pAhciPort, uint32_t uFinishedTasks, PAHCIPORTTASKSTATE pAhciPortTaskState, bool fInterrupt)
{
bool fAssertIntr = false;
{
sdbFis[0] |= (pAhciPortTaskState->uATARegStatus & 0x77) << 16; /* Some bits are marked as reserved and thus are masked out. */
{
/* Error bit is set. */
fAssertIntr = true;
}
if (fInterrupt)
{
/* Check if we should assert an interrupt */
fAssertIntr = true;
}
/* Update registers. */
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 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;
}
/**
* 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. */
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;
/*
* 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))
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))
/* 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.
*/
{
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 VBox status code.
* @param pAhciPortTaskState The task state which contains the S/G list entries.
* @param pvBuf Pointer to the buffer which should be copied.
* @param cbBuf Size of the buffer.
*/
static int ahciScatterGatherListCopyFromBuffer(PAHCIPORTTASKSTATE pAhciPortTaskState, void *pvBuf, size_t cbBuf)
{
unsigned cSGEntry = 0;
{
/* We finished. */
if (!cbBuf)
break;
/* Advance the buffer. */
/* Go to the next entry in the list. */
pSGEntry++;
cSGEntry++;
}
#if 0
#endif
return VINF_SUCCESS;
}
/* -=-=-=-=- 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.
*/
{
/* Free system resources occupied by the scatter gather list. */
/* Write updated command header into memory of the guest. */
{
}
else
{
}
if (pAhciPortTaskState->fQueued)
{
if (!cOutstandingTasks)
}
else
/* 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.
*/
{
ahciLog(("%s: pInterface=%p pvUser=%p uTag=%u\n",
}
/**
*
* @returns The direction of the data transfer
* @param pCmdHdr Pointer to the command header.
*/
static int ahciProcessCmd(PAHCIPort pAhciPort, PAHCIPORTTASKSTATE pAhciPortTaskState, uint8_t *pCmdFis)
{
int rc = PDMBLOCKTXDIR_NONE;
bool fLBA48 = false;
AssertMsg(pCmdFis[AHCI_CMDFIS_TYPE] == AHCI_CMDFIS_TYPE_H2D, ("FIS is not a host to device Fis!!\n"));
switch (pCmdFis[AHCI_CMDFIS_CMD])
{
case ATA_IDENTIFY_DEVICE:
{
{
/* Fill the buffer. */
/* Create scatter gather list. */
if (RT_FAILURE(rc))
/* Copy the buffer. */
if (RT_FAILURE(rc))
/* Destroy list. */
if (RT_FAILURE(rc))
/* 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:
break;
case ATA_READ_DMA_EXT:
fLBA48 = true;
case ATA_READ_DMA:
{
break;
}
case ATA_WRITE_DMA_EXT:
fLBA48 = true;
case ATA_WRITE_DMA:
{
break;
}
case ATA_READ_FPDMA_QUEUED:
{
break;
}
case ATA_WRITE_FPDMA_QUEUED:
{
break;
}
/* All not implemented commands go below. */
case ATA_SECURITY_FREEZE_LOCK:
case ATA_SMART:
case ATA_NV_CACHE:
case ATA_SLEEP: /* Powermanagement not supported. */
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->uTxDir = (pAhciPortTaskState->cmdHdr.u32DescInf & AHCI_CMDHDR_W) ? PDMBLOCKTXDIR_TO_DEVICE : PDMBLOCKTXDIR_FROM_DEVICE;
/* 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
{
int iTxDir;
/* Check if there is already an allocated task struct in the cache.
* Allocate a new task otherwise.
*/
{
}
else
{
}
/** 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 (iTxDir != PDMBLOCKTXDIR_NONE)
{
if (pAhciPortTaskState->fQueued)
{
}
rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE) ? false : true);
if (RT_FAILURE(rc))
if (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
{
rc = pAhciPort->pDrvBlockAsync->pfnStartRead(pAhciPort->pDrvBlockAsync, pAhciPortTaskState->uOffset,
}
else
{
rc = pAhciPort->pDrvBlockAsync->pfnStartWrite(pAhciPort->pDrvBlockAsync, pAhciPortTaskState->uOffset,
}
if (rc == VINF_VD_ASYNC_IO_FINISHED)
if (RT_FAILURE(rc))
}
else
{
/* 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 (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)
{
int iTxDir;
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 (iTxDir != PDMBLOCKTXDIR_NONE)
{
rc = ahciScatterGatherListCreate(pAhciPort, pAhciPortTaskState, (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE) ? 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 (iTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
{
if (RT_FAILURE(rc))
}
else
{
if (RT_FAILURE(rc))
}
/* Go to the next entry. */
cbTransfer -= cbProcess;
pSegCurr++;
pSGInfoCurr++;
}
/* Cleanup. */
if (RT_FAILURE(rc))
{
/* 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 (!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. */
}
}
/* 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.
*/
}
/**
* 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.
*
* @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.
*/
pAhciPort->pDrvBlock = (PDMIBLOCK *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_BLOCK);
{
return VERR_PDM_MISSING_INTERFACE;
}
pAhciPort->pDrvBlockBios = (PDMIBLOCKBIOS *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_BLOCK_BIOS);
if (!pAhciPort->pDrvBlockBios)
{
return VERR_PDM_MISSING_INTERFACE;
}
pAhciPort->pDrvMount = (PDMIMOUNT *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_MOUNT);
/* Try to get the optional async block interface. */
pAhciPort->pDrvBlockAsync = (PDMIBLOCKASYNC *)pAhciPort->pDrvBase->pfnQueryInterface(pAhciPort->pDrvBase, PDMINTERFACE_BLOCK_ASYNC);
/*
* 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;
}
{
bool fAllFinished;
u64Start = RTTimeMilliTS();
for (;;)
{
fAllFinished = true;
{
{
if (pAhciPort->fAsyncInterface)
else
if (!fAllFinished)
break;
}
}
if ( fAllFinished
break;
/* Sleep a bit. */
RTThreadSleep(100);
}
return fAllFinished;
}
{
AssertMsgFailed(("One port is still active\n"));
{
int rc;
if (RT_FAILURE(rc))
return rc;
}
return VINF_SUCCESS;
}
{
{
int rc;
if (RT_FAILURE(rc))
return rc;
}
return VINF_SUCCESS;
}
/**
* Suspend notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
{
AssertMsgFailed(("AHCI: One port is still active\n"));
{
}
return;
}
/**
* Resume notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
{
{
}
return;
}
/**
* Saves a state of the AHCI device.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pSSMHandle The handle to save the state to.
*/
{
uint32_t i;
/* First the main device structure. */
/* Now every port. */
for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
{
}
/* Now the emulated ata controllers. */
{
int rc;
if (RT_FAILURE(rc))
return rc;
}
}
/**
* Loads a saved AHCI device state.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pSSMHandle The handle to the saved state.
* @param u32Version The data unit version number.
*/
static DECLCALLBACK(int) ahciLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSMHandle, uint32_t u32Version)
{
uint32_t i;
int rc = VINF_SUCCESS;
if (u32Version != AHCI_SAVED_STATE_VERSION)
/* Restore data. */
/* First the main device structure. */
/* Now every port. */
for (i = 0; i < AHCI_MAX_NR_PORTS_IMPL; i++)
{
}
/* Now the emulated ata controllers. */
{
int rc;
if (RT_FAILURE(rc))
return rc;
}
if (RT_FAILURE(rc))
return rc;
if (u32 != ~0U)
{
return rc;
}
return VINF_SUCCESS;
}
/**
* 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 = PDMDevHlpPDMThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop, ahciAsyncIOLoopWakeUp, 0,
if (RT_FAILURE(rc))
{
return rc;
}
}
}
return rc;
}
/**
* Reset notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
{
AssertMsgFailed(("AHCI: One port is still active\n"));
/* Hardware reset for the ports. */
}
/**
* Poweroff notification.
*
* @returns nothing
* @param pDevIns Pointer to the device instance
*/
{
AssertMsgFailed(("AHCI: One port is still active\n"));
}
/**
* Construct a device instance for a VM.
*
* @returns VBox status.
* @param pDevIns The device instance data.
* If the registration structure is needed, pDevIns->pDevReg points to it.
* @param iInstance Instance number. Use this to figure out which registers and such to use.
* The device number is also found in pDevIns->iInstance, but since it's
* likely to be freqently used PDM passes it as parameter.
* @param pCfgHandle Configuration node handle for the device. Use this to obtain the configuration
* of the device instance. It's also found in pDevIns->pCfgHandle, but like
* iInstance it's expected to be used a bit in this function.
*/
{
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");
if (RT_FAILURE(rc))
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(pCfgHandle, "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, ahciLegacyFakeIORangeMap);
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 = PDMDevHlpPDMQueueCreate(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,
"Amount of data written.", "/Devices/SATA/Port%d/WrittenBytes", i);
PDMDevHlpSTAMRegisterF(pDevIns, &pAhciPort->StatIORequestsPerSecond, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
"Number of processed I/O requests per second.", "/Devices/SATA/Port%d/IORequestsPerSecond", 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/Port%d/ProfileProcessTime", 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/Port%d/ProfileMapIntoR3", 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/Port%d/ProfileReadWrite", 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/Port%d/ProfileDestroyScatterGatherList", 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),
"VBOX HARDDISK");
if (RT_FAILURE(rc))
{
if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
N_("AHCI configuration error: \"ModelNumber\" is longer than 40 bytes"));
N_("AHCI configuration error: failed to read \"ModelNumber\" as string"));
}
/*
* If 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 = PDMDevHlpPDMThreadCreate(pDevIns, &pAhciPort->pAsyncIOThread, pAhciPort, ahciAsyncIOLoop, ahciAsyncIOLoopWakeUp, 0,
}
}
else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
{
rc = VINF_SUCCESS;
}
else
#ifdef DEBUG
for (uint32_t i = 0; i < AHCI_NR_COMMAND_SLOTS; i++)
#endif
}
/*
* Attach status driver (optional).
*/
if (RT_SUCCESS(rc))
pThis->pLedsConnector = (PDMILEDCONNECTORS *)pBase->pfnQueryInterface(pBase, PDMINTERFACE_LED_CONNECTORS);
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;
}
}
if (RT_FAILURE(rc))
return rc;
return rc;
}
/**
* The device registration structure.
*/
const PDMDEVREG g_DeviceAHCI =
{
/* u32Version */
/* szDeviceName */
"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 */