You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3084 lines
94 KiB

/* vxbAhciStorage.c - AHCI SATA and ATAPI vxBus storage device driver */
/*
* Copyright (c) 2011-2016, 2018-2019 Wind River Systems, Inc.
*
* The right to copy, distribute, modify or otherwise make use
* of this software may be licensed only pursuant to the terms
* of an applicable Wind River license agreement.
*/
/*
modification history
--------------------
02a,09sep19,mw1 add sataReqMutex semaphore to protect freeReqPrivList. (VXW6-87076)
01z,26aug19,mw1 Use vxAtomic32Cas to protect access to queuedMode (VXW6-87119)
01y,19sep18,syt Enable Task File error interrupt enable (VXW6-86615)
01x,21dec16,yjl Fix VXW6-85923, AHCI storage slowing bootup when no disk is
physically connected
01w,17jun16,hma fix the sata test error (VXW6-83903)
01v,18jan16,hma add rename sata disk or patition function(VXW6-10898)
01u,21dec15,hma fix sata test error sometimes (VXW6-83231)
01t,21dec15,hma fix AHCI does not work for some board (VXW6-85077)
01s,25jun15,zly clear busy and data request flag via set AHCI_PCMD_CLO.
(VXW6-84548)
01r,05mar15,txu fix the error condition for ATA_SMART_EXCEEDED (VXW6-84062)
01q,27feb15,m_y export the sem and watch dog timeout value (VXW6-84034)
01p,19nov14,m_y modify watch dog time (VXW6-83672)
01o,17sep14,txu add S.M.A.R.T support (VXW6-82629)
01n,22feb14,m_y modify prdcount and wait time (VXW6-80627)
01m,22oct13,m_y remove unused function
01l,18jun13,m_y add code to support XBD sched policy and NCQ
01k,27mar13,clx added vxbXbdDevCreate method. (WIND00399793)
01j,05nov12,sye removed handle swap for PCI bus. (WIND00380133)
01i,30oct12,sye set detach state before remove logical device. (WIND00383457)
01h,19oct12,mpc fixed address translation for 32 bit GOS working on
64 bit Hypervisor. (WIND00383488)
01g,01aug12,sye spin up port only when HBA supports. (WIND00367545)
01f,02jul12,sye fixed static analyze issue. (WIND00354953)
01e,29may12,e_d check attach status before remove logical device. (WIND00351659)
01d,15mar12,e_d check port number to malloc sata device struct to
fix page fault exception on discontiguous sata
ports. (WIND00335995)
01c,08mar12,e_d moved ATA and ATAPI device initialization
to vxbSataLib.c. (WIND00333858)
01b,19feb12,syt added register swap operation to fit big endian type AHCI
register bank.
01a,22oct11,e_d adapted from vxbIntelAhciStorage.c version 01w. (WIND00307297)
*/
/*
DESCRIPTION:
This is a low level device driver for ATA/ATAPI devices on AHCI host controller.
It also provides necessary functions to the user for device and its control
features which are not utilized by file system.
As each controller is initialized, the sata controller is examined. A port
monitor task is spawned for each port. Initialization of the driver is handled
by the vxBus interface. The driver is fully initialized in vxBus "connection"
phase.
This monitor task is blocked on the message receiving. When it is
recognized that a device has been inserted (via an ISR) the "AHCI_ATTACH_MSG"
is given to the port monitor task.
If the device change indicates that a device is present, the XBD interface
to the device is created. 2 semaphores are used in this interface. A mutex
to force mutual exclusion between the driver and the other users of the xbd
interface, and a bio ready semaphore. Both of these semaphores are used by
the "bio task" which is created at this time for each device (port) as the
device becomes active. The bio ready indicates that there is work for the
driver to do. Part of the creation of the xbd interface is to send an
insertion event to the event reporting framework. This allows the filesystem
to use the newly created xbd.
If the device change indicates that a device has gone away, the XBD interface
is deleted, the bio service task is deleted and other resources used by the
bio service task are recovered. The device change indication allow for hot
removal/insertion of sata devices.
The interrupt handler has the controller specific control block passed as an
argument by the pci interrupt routine. The isr scans each port on that
controller for status bits. If the device change has occurred, the
"device change" is given to the waiting port monitor task. If the command has
completed, the command complete semaphore is given. This semaphore serializes
access to the driver. The bio task will call the function to read/write to
the device, and uses this semaphore for that purpose.
References:
1) ATAPI-5 specification "T13-1321D Revision 1b, 7 July 1999"
2) ATAPI for CD-ROMs "SFF-8020i Revision 2.6, Jan 22,1996"
3) Intel 82801BA (ICH2), 82801AA (ICH), and 82801AB (ICH0) IDE Controller
Programmer's Reference Manual, Revision 1.0 July 2000
4) Serial ATA advanced Host Controller Interface
SEE ALSO:
\tb VxWorks Programmer's Guide: I/O System
*/
/* includes */
#include <vxWorks.h>
#include <taskLib.h>
#include <ioLib.h>
#include <memLib.h>
#include <stdlib.h>
#include <errnoLib.h>
#include <stdio.h>
#include <string.h>
#include <private/semLibP.h>
#include <intLib.h>
#include <iv.h>
#include "cacheLib.h"
#include <wdLib.h>
#include <sysLib.h>
#include <sys/fcntlcom.h>
#include <logLib.h>
#include <drv/xbd/xbd.h>
#include <drv/erf/erfLib.h>
#include <drv/pcmcia/pccardLib.h>
#include <vxBusLib.h>
#include <hwif/vxbus/vxBus.h>
#include <hwif/vxbus/vxbPciLib.h>
#include <hwif/vxbus/hwConf.h>
#include <drv/pci/pciConfigLib.h>
#include <drv/pci/pciIntLib.h>
#include <fsMonitor.h>
#include <../h/vxbus/vxbAccess.h>
#include <../src/hwif/h/storage/vxbAhciStorage.h>
#include <../h/vmLib.h>
#include <../../../h/taskLib.h>
#include <spinLockLib.h>
/* extern */
IMPORT int usrAhciWatchdogValGet();
IMPORT int usrAhciSemTimoutValGet();
#if defined(INCLUDE_DRV_STORAGE_AHCI)
/* defines */
#ifdef AHCI_DEBUG
# ifdef LOCAL
# undef LOCAL
# define LOCAL
# endif /* LOCAL */
# define DEBUG_INIT 0x00000001
# define DEBUG_CMD 0x00000002
# define DEBUG_MON 0x00000004
# define DEBUG_INT 0x00000008
# define DEBUG_REG 0x00000010
# define DEBUG_BIST 0x00000020
# define DEBUG_ALWAYS 0xffffffff
# define DEBUG_NONE 0x00000000
UINT32 ahciSataDebug = DEBUG_NONE;
IMPORT FUNCPTR _func_logMsg;
# define AHCISATA_DBG_LOG(mask, string, a, b, c, d, e, f) \
if ((ahciSataDebug & mask) || (mask == DEBUG_ALWAYS)) \
{ \
if (_func_logMsg != NULL) \
_func_logMsg(string, (_Vx_usr_arg_t)a, (_Vx_usr_arg_t)b, \
(_Vx_usr_arg_t)c, (_Vx_usr_arg_t)d, \
(_Vx_usr_arg_t)e, (_Vx_usr_arg_t)f); \
}
#else
# define AHCISATA_DBG_LOG(mask, string, a, b, c, d, e, f)
#endif /* AHCI_DEBUG */
#define AHCI_QUEUE_SIZE (AHCI_MAX_DRIVES * 2)
#define AHCI_MON_PRIORITY 49
LOCAL BOOL ahciMonitorStarted = FALSE;
LOCAL MSG_Q_ID ahciMonQueue;
LOCAL UINT32 ahciBitMask[AHCI_MAX_DRIVES] =
{
0x00000001, 0x00000002, 0x00000004, 0x00000008,
0x00000010, 0x00000020, 0x00000040, 0x00000080,
0x00000100, 0x00000200, 0x00000400, 0x00000800,
0x00001000, 0x00002000, 0x00004000, 0x00008000,
0x00010000, 0x00020000, 0x00040000, 0x00080000,
0x00100000, 0x00200000, 0x00400000, 0x00800000,
0x01000000, 0x02000000, 0x04000000, 0x08000000,
0x10000000, 0x20000000, 0x40000000, 0x80000000
};
LOCAL int ahciTypes[AHCI_MAX_DRIVES] =
{
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL,
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL,
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL,
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL,
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL,
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL,
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL,
AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL, AHCI_MODE_ALL
};
/* list of supported device IDs */
LOCAL PCI_DEVVEND vxbIntelAhciIdList[] =
{
{0xFFFF, 0xFFFF}
};
LOCAL int ahciRetry = 3;
LOCAL void ahciWdog(AHCI_DRIVE *);
LOCAL STATUS ahciDrv(SATA_HOST *);
LOCAL void ahciCmdWaitForResource(AHCI_DRIVE *, BOOL);
LOCAL void ahciCmdReleaseResource(AHCI_DRIVE *, BOOL);
LOCAL void ahciMon(void);
LOCAL size_t ahciPrdSetup(UINT8 *, UINT32, AHCI_PRD *);
LOCAL void vxbAhciInstInit(VXB_DEVICE_ID);
LOCAL void vxbAhciInstInit2(VXB_DEVICE_ID);
LOCAL void vxbAhciInstConnect(VXB_DEVICE_ID);
LOCAL BOOL vxbAhciDevProbe (VXB_DEVICE_ID);
LOCAL STATUS ahciDriveInit (SATA_HOST *, int);
LOCAL STATUS ahciIntr(SATA_HOST *);
LOCAL STATUS ahciInit (SATA_HOST *, int);
LOCAL void * ahciVirtToPciAddr (void *);
LOCAL STATUS ahciSataCmdIssue(SATA_DEVICE *, FIS_ATA_REG *, SATA_DATA *);
LOCAL void ahciXferReq
(
SATA_DEVICE * pCtrl,
XBD_REQUEST * pXbdReq
);
LOCAL STATUS ahciReqResourceGet
(
AHCI_DRIVE * pDrive,
XBD_REQUEST * pXbdReq
);
LOCAL VOID ahciReqResourceRelease
(
AHCI_DRIVE * pDrive,
XBD_REQUEST * pXbdReq
);
LOCAL DRIVER_INITIALIZATION vxbAhciFuncs =
{
vxbAhciInstInit,
vxbAhciInstInit2,
vxbAhciInstConnect
};
/*
* With AHCI 1.0 standard, the PCI class code should be set
* 0x010601. Now we will probe class code to check this device
* and not add more PCI device ID and vendor ID in this file.
*/
LOCAL PCI_DRIVER_REGISTRATION vxbAhciStoragePciRegistration =
{
{
NULL, /* pNext == NULL */
VXB_DEVID_DEVICE, /* This is a device driver */
VXB_BUSID_PCI, /* hardware resides on a PCI bus */
VXB_VER_5_0_0, /* targeting vxbus version 2 api */
AHCI_NAME, /* driver named */
&vxbAhciFuncs, /* set of 3 pass functions */
NULL, /* no methods */
vxbAhciDevProbe, /* probe function */
},
NELEMENTS(vxbIntelAhciIdList),
&vxbIntelAhciIdList[0],
};
LOCAL struct vxbPlbRegister vxbAhciStoragePlbRegistration =
{
{
(struct vxbDevRegInfo *)&vxbAhciStoragePciRegistration,
VXB_DEVID_DEVICE,
VXB_BUSID_PLB,
VXB_VER_5_0_0,
AHCI_NAME,
&vxbAhciFuncs,
NULL,
NULL,
},
};
/*******************************************************************************
*
* vxbIntelAhciStorageRegister - register driver with vxbus
*
* RETURNS: N/A
*
* ERRNO
*/
void vxbAhciStorageRegister (void)
{
(void) vxbDevRegister ((struct vxbDevRegInfo *)&vxbAhciStoragePlbRegistration);
}
/*******************************************************************************
*
* vxbIntelAhciInstInit - First pass initialization
*
* RETURNS: N/A
*
* ERRNO
*/
LOCAL void vxbAhciInstInit
(
VXB_DEVICE_ID pDev
)
{
/* get the next available unit number */
if (pDev->busID == VXB_BUSID_PCI)
(void) vxbNextUnitGet (pDev);
}
/*******************************************************************************
*
* vxbIntelAhciInstInit2 - Second pass initialization
*
* RETURNS: N/A
*
* ERRNO
*/
LOCAL void vxbAhciInstInit2
(
VXB_DEVICE_ID pDev
)
{
int i;
SATA_HOST * pAhciDrvCtrl;
UINT32 ulPhyAddrLow = 0;
pAhciDrvCtrl = (SATA_HOST *)malloc (sizeof (SATA_HOST));
if (pAhciDrvCtrl == NULL)
return;
bzero ( (char *)pAhciDrvCtrl, sizeof (SATA_HOST) );
pAhciDrvCtrl->pDev = pDev;
pDev->pDrvCtrl = pAhciDrvCtrl;
if (pDev->busID == VXB_BUSID_PCI)
{
/* As defined in AHCI spec, BAR[5] is for AHCI base address,
* read the AHCI BASE register, and find the virture address.
*/
(void) VXB_PCI_BUS_CFG_READ (pDev, PCI_CFG_BASE_ADDRESS_5, 4, ulPhyAddrLow);
for (i = 0; i < VXB_MAXBARS; i++)
{
if ((pDev->regBaseFlags[i] == VXB_REG_MEM) \
&& (pDev->pRegBase[i] == (void *)(long)ulPhyAddrLow))
break;
}
}
else
{
for (i = 0; i < VXB_MAXBARS; i++)
{
if (pDev->regBaseFlags[i] == VXB_REG_MEM)
break;
}
}
if (i == VXB_MAXBARS)
{
free (pAhciDrvCtrl);
return;
}
(void) vxbRegMap (pDev, i, &pAhciDrvCtrl->regHandle);
pAhciDrvCtrl->regBase[0] = pDev->pRegBase[i];
/* PCI BUS already swapped handle */
if (pDev->busID != VXB_BUSID_PCI)
pAhciDrvCtrl->regHandle = (void *)AHCI_REG_HANDLE_SWAP((ULONG)pAhciDrvCtrl->regHandle);
/* reset AHCI controller. Here we must reset AHCI as soon as early when using PCI legacy interrupt!
* Because both Net Card and SATA Card use int-pin 1, with the same int line 83 for FT1500 board.
* When Net Card init, there are too many SATA interrupts to boot board if the AHCI not reset here.
*/
(void) CTRL_REG_READ(pAhciDrvCtrl, AHCI_GHC);
CTRL_REG_WRITE(pAhciDrvCtrl, AHCI_GHC, AHCI_GHC_HR);
}
/*******************************************************************************
*
* vxbIntelAhciInstConnect - Final initialization
*
* RETURNS: N/A
*
* ERRNO
*/
VXB_DEVICE_ID ahciDev = NULL;
LOCAL void vxbAhciInstConnect
(
VXB_DEVICE_ID pDev
)
{
SATA_HOST * pAhciDrvCtrl;
STATUS rc = OK;
VXB_ASSERT_NONNULL_V(pDev)
pAhciDrvCtrl = pDev->pDrvCtrl;
if (pAhciDrvCtrl == NULL)
return;
ahciDev = pDev;
pAhciDrvCtrl->ops.cmdIssue = (FUNCPTR)ahciSataCmdIssue;
pAhciDrvCtrl->ops.xferReq = (FUNCPTR)ahciXferReq;
/*
* If the monitor task is not started yet, create the message queue
* for it, and start it
*/
if (!ahciMonitorStarted)
{
if ((ahciMonQueue = msgQCreate(AHCI_QUEUE_SIZE, VXB_AHCI_MSG_SIZE,
MSG_Q_FIFO)) == NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to create SATA message queue\n",
0,0,0,0,0,0);
return;
}
if (taskSpawn("tAhciMon", AHCI_MON_PRIORITY, 0, 4096,
(FUNCPTR)ahciMon,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == TASK_ID_ERROR)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to create AHCI monitor task\n",
0,0,0,0,0,0);
(void) msgQDelete (ahciMonQueue);
return;
}
ahciMonitorStarted = TRUE;
}
rc = ahciDrv(pAhciDrvCtrl);
if(rc != OK)
AHCISATA_DBG_LOG(DEBUG_INIT,
"vxbIntelAhciInstConnect() ahciDrv init error\n",
0,0,0,0,0,0);
}
int usrAtaRename(char * oldName, char *newName)
{
DOS_VOLUME_DESC_ID pVolDesc;
SATA_HOST * pAhciDrvCtrl;
char *copiedName;
DEV_HDR *pDevHdr;
if (ahciDev ==NULL)
return ERROR;
pAhciDrvCtrl = ahciDev->pDrvCtrl;
fsmNameInstall (oldName, newName);
pVolDesc = dosFsVolDescGet (oldName, NULL);
if (pVolDesc == NULL)
{
printf("--can't find %s--\n",oldName);
return (ERROR);
}
pDevHdr = (DEV_HDR *)pVolDesc;
copiedName = (char *) malloc ((unsigned) (strlen (newName) + 1));
if (copiedName == NULL)
return (ERROR);
strcpy (copiedName, newName);
pDevHdr->name = copiedName;
return (OK);
}
/*******************************************************************************
*
* vxbAhciDevProbe - vxbus probe function
*
* This function is called by vxBus to probe device.
*
* RETURNS: TRUE if probe passes and assumed a valid ahci SATA
* (or compatible) device. FALSE otherwise.
*
* ERRNO: N/A
*/
LOCAL BOOL vxbAhciDevProbe
(
VXB_DEVICE_ID pDev
)
{
UINT16 devId = 0;
UINT16 vendorId = 0;
UINT32 classValue = 0;
VXB_PCI_BUS_CFG_READ (pDev, PCI_CFG_VENDOR_ID, 2, vendorId);
VXB_PCI_BUS_CFG_READ (pDev, PCI_CFG_DEVICE_ID, 2, devId);
if (vendorId == INTEL_VENDOR_ID)
{
switch (devId)
{
case ICH6R_DEVICE_ID:
case ICH6M_DEVICE_ID:
case ESB2_DEVICE_ID:
case ICH7M_DEVICE_ID:
case ICH8M_DEVICE_ID:
case ICH9R_DEVICE_ID:
case ICH9M_DEVICE_ID:
case ICH10R_DEVICE_ID:
case ICH10_DEVICE_ID:
case PCH_6PORT_DEVICE_ID_0:
case PCH_6PORT_DEVICE_ID_1:
case PCH_PATSBURG_DEVICE_ID:
case PCH_COUGAR_POINT_DEVICE_ID:
case IOH_TOPCILFF_DEVICE_ID:
return (TRUE);
default:
break;
}
}
VXB_PCI_BUS_CFG_READ (pDev, PCI_CFG_REVISION, 4, classValue);
if ((classValue & 0xFFFFFF00) == AHCI_CLASS_ID)
return (TRUE);
else
return (FALSE);
}
/*******************************************************************************
*
* ahciVirtToPciAddr - Translate a virtual CPU address to a physical PCI address
*
* Perform the necessary translations to change an address from virtual CPU to
* physical CPU, then to physical PCI.
*
* RETURNS: physical address
*/
LOCAL void * ahciVirtToPciAddr
(
void * addr
)
{
return (CACHE_DMA_VIRT_TO_PHYS(addr));
}
/*******************************************************************************
*
* ahciIntr - AHCI controller interrupt handler.
*
* This function is the AHCI controller interrupt handler.
*
* RETURNS: N/A
*/
LOCAL STATUS ahciIntr
(
SATA_HOST * pCtrl
)
{
int i, j;
UINT32 ctrlIntr, sataIntr, portIntr, taskStatus, sataStatus;
UINT32 cmdActive, sataActive, active;
AHCI_DRIVE *pDrive;
VXB_AHCI_MSG ctrlMsg;
VXB_ASSERT_NONNULL(pCtrl,ERROR)
ctrlMsg.pCtrl = pCtrl;
while (CTRL_REG_READ(pCtrl, AHCI_IS))
{
for (i = 0; i < pCtrl->numImpPorts; i++)
{
pDrive = pCtrl->sataDev[i];
ctrlIntr = CTRL_REG_READ(pCtrl, AHCI_IS) &
(ahciBitMask[pDrive->portPhyNum]);
AHCISATA_DBG_LOG(DEBUG_INT,
"ahciIntr() ctrlIntr = 0x%x\n",
ctrlIntr,0,0,0,0,0);
if (ctrlIntr & (ahciBitMask[pDrive->portPhyNum]))
{
pDrive->intCount++;
/* read all of the status registers */
sataIntr = PORT_REG_READ(pDrive, AHCI_PxSERR);
portIntr = PORT_REG_READ(pDrive, AHCI_PxIS);
AHCISATA_DBG_LOG(DEBUG_INT,
"ahciIntr() sataIntr = 0x%x portIntr = 0x%x\n",
sataIntr,portIntr,0,0,0,0);
PORT_REG_WRITE(pDrive, AHCI_PxSERR, sataIntr);
PORT_REG_WRITE(pDrive, AHCI_PxIS, portIntr);
CTRL_REG_WRITE(pCtrl, AHCI_IS, ctrlIntr);
taskStatus = PORT_REG_READ(pDrive, AHCI_PxTFD);
sataStatus = PORT_REG_READ(pDrive, AHCI_PxSSTS);
AHCISATA_DBG_LOG(DEBUG_INT,
"ahciIntr() taskStatus = 0x%x sataStatus = 0x%x\n",
taskStatus,sataStatus,0,0,0,0);
pDrive->intStatus = taskStatus & 0xFF;
pDrive->intError = (taskStatus & 0xFF00) >> 8;
cmdActive = PORT_REG_READ(pDrive,AHCI_PxCI);
sataActive = PORT_REG_READ(pDrive, AHCI_PxSACT);
AHCISATA_DBG_LOG(DEBUG_INT,
"ahciIntr() cmdActive = 0x%x sataActive = 0x%x\n",
cmdActive,sataActive,0,0,0,0);
/*
* If it was a drive attached interrupt, signal
* the monitor task.
*/
if ((portIntr & (AHCI_PIS_PRCS | AHCI_PIS_PCS)) &&
((sataStatus & AHCI_PSSTS_IPM_MSK) == AHCI_PSSTS_IPM_ACTIVE))
{
/*
* Check to make sure we didn't cause the
* drive attached by issuing a COMRESET
*/
AHCISATA_DBG_LOG(DEBUG_INT,
"ahciIntr() sata device attached\n",
0,0,0,0,0,0);
if (!pDrive->initActive)
{
AHCISATA_DBG_LOG(DEBUG_INT,
"SATA Drive inserted on ctrl %d,"
"logic port number %d\n",
pCtrl->pDev->unitNumber,
pDrive->sataPortDev.num,
0,0,0,0);
ctrlMsg.msgId = AHCI_ATTACH_MSG;
ctrlMsg.drive = (char)i;
/* return from msgQSend is not needed */
(void)msgQSend(ahciMonQueue, (char *)&ctrlMsg,
VXB_AHCI_MSG_SIZE,
NO_WAIT,MSG_PRI_NORMAL);
continue;
}
}
/*
* If it was a drive removed interrupt, mark it
* as removed, and signal any active transfers
* that they are now done....whether they like
* it or not...
*/
if (portIntr & AHCI_PIS_PRCS &&
((sataStatus & AHCI_PSSTS_IPM_MSK) == AHCI_PSSTS_IPM_NO_DEVICE))
{
/*
* Check to make sure we didn't cause the
* drive remove by issuing a COMRESET
*/
AHCISATA_DBG_LOG(DEBUG_INT,
"ahciIntr() sata device removed\n",
0,0,0,0,0,0);
if (!pDrive->initActive)
{
AHCISATA_DBG_LOG(DEBUG_INT,
"SATA Drive inserted on ctrl %d,"
"logic port number %d\n",
pCtrl->pDev->unitNumber,
pDrive->sataPortDev.num,
0,0,0,0);
pDrive->state = AHCI_DEV_NONE;
for (j = 0; j < pDrive->queueDepth; j++)
{
if (pDrive->cmdStarted & ahciBitMask[j])
{
/* return from semGive is not needed */
(void)semGive(pDrive->syncSem[j]);
}
}
ctrlMsg.msgId = AHCI_REMOVE_MSG;
ctrlMsg.drive = (char)i;
/* return from msgQSend is not needed */
(void)msgQSend(ahciMonQueue, (char *)&ctrlMsg,
VXB_AHCI_MSG_SIZE,
NO_WAIT,MSG_PRI_NORMAL);
}
continue;
}
/*
* If it was a task file error reported by the
* drive, signal the monitor task
*/
if (portIntr & AHCI_PIS_TFES)
{
AHCISATA_DBG_LOG(DEBUG_INT,
"ahciIntr() task file error reported\n",
0,0,0,0,0,0);
pDrive->portError = TRUE;
pDrive->taskFileErrorCount++;
for (j=0; j < pDrive->queueDepth; j++)
{
if (pDrive->cmdStarted & ahciBitMask[j])
{
/* return from semGive is not needed */
(void)semGive(pDrive->syncSem[j]);
}
}
pDrive->cmdStarted = 0;
ctrlMsg.msgId = AHCI_PORT_ERROR_MSG;
ctrlMsg.drive = (char)i;
/* return from msgQSend is not needed */
(void)msgQSend(ahciMonQueue, (char *)&ctrlMsg,
VXB_AHCI_MSG_SIZE,
NO_WAIT,MSG_PRI_NORMAL);
continue;
}
/*
* Check for completed commands, and signal the
* tasks that issued them
*/
if (pDrive->queuedMode)
active = sataActive;
else
active = cmdActive;
for (j = 0; j < pDrive->queueDepth; j++)
{
UINT32 slotBit;
slotBit = ahciBitMask[j];
if (pDrive->cmdStarted & slotBit)
{
if (!(active & slotBit))
{
/* return from semGive is not needed */
(void)semGive(pDrive->syncSem[j]);
pDrive->cmdStarted &= ~slotBit;
}
}
}
}
}
return OK;
}
return ERROR;
}
/*******************************************************************************
*
* ahciInit - Initialize a SATA disk controller
*
* This routine resets an SATA disk controller.
*
* RETURNS: OK, ERROR if the command didn't succeed.
*
*/
LOCAL STATUS ahciInit
(
SATA_HOST * pCtrl,
int drive
)
{
AHCI_DRIVE * pDrive;
UINT32 reg;
int i;
STATUS rc;
VXB_ASSERT_NONNULL(pCtrl,ERROR)
pDrive = (AHCI_DRIVE *)(pCtrl->sataDev[drive]);
if (pDrive == NULL)
return ERROR;
ahciCmdWaitForResource(pDrive,FALSE);
pCtrl->numCtrl = pCtrl->pDev->unitNumber;
/*
* Make sure we don't notify the monitor task for any insert/remove
* events that we cause by doing a COMRESET on the device
*/
pDrive->initActive = TRUE;
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciInit> Stopping Port \n",
0,0,0,0,0,0);
/* Stop the port in the controller */
reg = PORT_REG_READ(pDrive, AHCI_PxCMD);
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciInit> AHCI_PxCMD reg = 0x%x \n",
reg,0,0,0,0,0);
PORT_REG_WRITE(pDrive, AHCI_PxCMD,
reg & ~(AHCI_PCMD_FRE | AHCI_PCMD_POD | AHCI_PCMD_ST));
/* Wait for the port to stop */
pDrive->wdgOkay = TRUE;
rc = wdStart (pDrive->wdgId, (pDrive->wdgTimeout),
(WD_RTN) ahciWdog, (_Vx_usr_arg_t) pDrive);
if (rc != OK)
{
pDrive->wdgOkay = FALSE;
return (rc);
}
while ((PORT_REG_READ(pDrive, AHCI_PxCMD) & AHCI_PCMD_CR) &&
(pDrive->wdgOkay))
(void) taskDelay(3);
rc = wdCancel(pDrive->wdgId);
if (rc != OK)
return (rc);
/* Issue a COMRESET over Serial ATA */
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciInit> Issue a COMRESET \n",
0,0,0,0,0,0);
PORT_REG_WRITE(pDrive, AHCI_PxSCTL, AHCI_PSCTL_DET_RESET |
AHCI_PSCTL_IPM_NO_PARSLUM);
/* return from wdCancel is not needed */
(void)taskDelay(10);
PORT_REG_WRITE(pDrive, AHCI_PxSCTL, 0);
/* Wait for the Link to reactivate */
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciInit> Waiting for Link \n",
0,0,0,0,0,0);
pDrive->wdgOkay = TRUE;
rc = wdStart (pDrive->wdgId, (pDrive->wdgTimeout),
(WD_RTN) ahciWdog, (_Vx_usr_arg_t) pDrive);
if (rc != OK)
{
pDrive->wdgOkay = FALSE;
return (rc);
}
while (((PORT_REG_READ(pDrive, AHCI_PxSSTS) & AHCI_PSSTS_DET_MSK) !=
AHCI_PSSTS_DET_PHY) &&
(pDrive->wdgOkay))
(void) taskDelay(1);
rc = wdCancel(pDrive->wdgId);
if (rc != OK)
return (rc);
/*
* According to AHCI 1.1, the port should be in idle state and AHCI_PCMD_FRE
* should be set before start the port DMA engine
*/
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciInit> Restarting Port \n",
0,0,0,0,0,0);
reg = PORT_REG_READ(pDrive, AHCI_PxCMD);
PORT_REG_WRITE(pDrive, AHCI_PxCMD,
(reg | AHCI_PCMD_ICC_A | AHCI_PCMD_FRE | AHCI_PCMD_POD));
pDrive->wdgOkay = TRUE;
rc = wdStart (pDrive->wdgId, (pDrive->wdgTimeout),
(WD_RTN) ahciWdog, (_Vx_usr_arg_t) pDrive);
if (rc != OK)
{
pDrive->wdgOkay = FALSE;
return (rc);
}
while ((pDrive->wdgOkay) &&
((PORT_REG_READ(pDrive, AHCI_PxCMD) & AHCI_PCMD_CR) ||
(PORT_REG_READ(pDrive, AHCI_PxTFD) & AHCI_STAT_ACCESS)))
(void) taskDelay(3);
rc = wdCancel (pDrive->wdgId);
if (rc != OK)
return (rc);
/* Start the port DMA engine */
PORT_REG_WRITE(pDrive, AHCI_PxCMD, (PORT_REG_READ(pDrive, AHCI_PxCMD) |
AHCI_PCMD_ST));
/* The port should be in idle state before issue a soft reset command */
pDrive->wdgOkay = TRUE;
rc = wdStart (pDrive->wdgId, (pDrive->wdgTimeout),
(WD_RTN) ahciWdog, (_Vx_usr_arg_t) pDrive);
if (rc != OK)
{
pDrive->wdgOkay = FALSE;
return (rc);
}
while ((pDrive->wdgOkay) &&
(PORT_REG_READ(pDrive, AHCI_PxTFD) & AHCI_STAT_ACCESS))
taskDelay(3);
rc = wdCancel (pDrive->wdgId);
if (rc != OK)
return (rc);
/* Wait for the command to be issued */
pDrive->wdgOkay = TRUE;
rc = wdStart (pDrive->wdgId, (pDrive->wdgTimeout),
(WD_RTN) ahciWdog, (_Vx_usr_arg_t) pDrive);
if (rc != OK)
{
pDrive->wdgOkay = FALSE;
return (rc);
}
while ((PORT_REG_READ(pDrive, AHCI_PxCI) & 1) && (pDrive->wdgOkay))
(void)taskDelay(1); /* return from taskDelay is not needed */
rc = wdCancel(pDrive->wdgId);
if (rc != OK)
return (rc);
/* Wait for the command to be issued */
pDrive->wdgOkay = TRUE;
rc = wdStart (pDrive->wdgId, (pDrive->wdgTimeout),
(WD_RTN) ahciWdog, (_Vx_usr_arg_t) pDrive);
if (rc != OK)
{
pDrive->wdgOkay = FALSE;
return (rc);
}
while ((PORT_REG_READ(pDrive, AHCI_PxCI) & 1) && (pDrive->wdgOkay))
(void)taskDelay(1); /* return from taskDelay is not needed */
rc = wdCancel(pDrive->wdgId);
if (rc != OK)
return (rc);
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciInit> Port Stat %08x\n",
PORT_REG_READ(pDrive, AHCI_PxTFD),
0,0,0,0,0);
/* Reset the command slot semaphores */
for (i = 0; i < pDrive->queueDepth; i++)
{
rc = semBInit(pDrive->syncSem[i], SEM_Q_FIFO, SEM_EMPTY);
if (rc != OK)
return (rc);
}
pDrive->cmdStarted = 0;
pDrive->queuedMode = FALSE;
pDrive->initActive = FALSE;
ahciCmdReleaseResource(pDrive,FALSE);
if (pDrive->wdgOkay)
return (OK);
else
return (ERROR);
}
/*******************************************************************************
*
* ahciDriveInit - initialize SATA drive
*
* This routine checks the drive presence, identifies its type, initializes
* the drive and driver control structures with the parameters of the drive.
*
* RETURNS: OK if drive was initialized successful, or ERROR.
*/
LOCAL STATUS ahciDriveInit
(
SATA_HOST * pCtrl,
int drive
)
{
AHCI_DRIVE * pDrive;
int configType = pCtrl->configType[drive];
UINT32 reg;
STATUS rc = OK;
device_t xbd;
VXB_ASSERT_NONNULL(pCtrl,ERROR)
pDrive = (AHCI_DRIVE *)(pCtrl->sataDev[drive]);
if (pDrive == NULL)
return (ERROR);
rc = semTake (pDrive->muteSem, WAIT_FOREVER);
if (rc != OK)
return (rc);
/* Check to see if this is an ATAPI device */
reg = PORT_REG_READ(pDrive, AHCI_PxSIG);
if ((((reg & 0x00FF0000) >> 16) == 0x14) &&
(((reg & 0xFF000000) >> 24) == 0xEB))
pDrive->sataPortDev.type = ATA_TYPE_ATAPI;
else
pDrive->sataPortDev.type = ATA_TYPE_ATA;
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDriveInit> device type = %d\n",
pDrive->sataPortDev.type,
0,0,0,0,0);
if (pDrive->sataPortDev.type == ATA_TYPE_ATA)
{
rc = sataAtaNormalInit((SATA_DEVICE *)&pDrive->sataPortDev);
}
else if (pDrive->sataPortDev.type == ATA_TYPE_ATAPI)
{
rc = sataAtapiNormalInit((SATA_DEVICE *)&pDrive->sataPortDev);
}
if (rc != OK)
{
pDrive->sataPortDev.attached = FALSE;
pDrive->state = AHCI_DEV_PREAD_F;
(void)semGive (pDrive->muteSem);
goto driveInitExit;
}
pDrive->sataPortDev.attached = TRUE;
/*
* Native Command Queuing is OK if both controller and drive support it,
* and it is enabled in the Config word for the drive.
*/
if (pCtrl->okNcq && (configType & AHCI_NCQ_MODE))
pDrive->sataPortDev.okNcq = (short)
((pDrive->sataPortDev.info.sataCapabilities
& 0x0100) ? TRUE : FALSE);
else
pDrive->sataPortDev.okNcq = FALSE;
if (pDrive->sataPortDev.okNcq)
pDrive->queueDepth = min((pDrive->sataPortDev.info.queueDepth + 1),
pCtrl->numCommandSlots);
else
pDrive->queueDepth = 1;
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDriveInit> Setting transfer mode to 0x%x\n",
pDrive->sataPortDev.rwMode,
0,0,0,0,0);
pDrive->sataPortDev.prdMaxSize = MAX_BYTES_PER_PRD;
pDrive->sataPortDev.maxActiveReqs = pDrive->queueDepth;
pDrive->sataPortDev.maxBiosPerReq = AHCI_MAX_PRD_ENTRIES;
if(pDrive->sataPortDev.okLba48)
pDrive->sataPortDev.fisMaxSector = MAX_SECTORS_FISLBA48;
else
pDrive->sataPortDev.fisMaxSector = MAX_SECTORS_FISNOMAL;
pDrive->sataPortDev.maxXferBlks = pDrive->sataPortDev.fisMaxSector;
pDrive->sataPortDev.xbdDev.pPort = (void *)(&pDrive->sataPortDev);
/* Set multiple mode (multisector read/write) */
if ((pDrive->sataPortDev.rwMode == AHCI_PIO_MULTI) &&
(pDrive->sataPortDev.type == ATA_TYPE_ATA))
{
if (pDrive->sataPortDev.okMulti)
sataSetMulSector(&pDrive->sataPortDev);
else
pDrive->sataPortDev.rwMode = AHCI_PIO_SINGLE;
}
/* return from semGive is not needed */
(void)semGive (pDrive->muteSem);
xbd = sataXbdDevCreate(&pDrive->sataPortDev, (char *)NULL);
if (xbd != NULLDEV)
{
pDrive->state = AHCI_DEV_OK;
}
driveInitExit:
if (pDrive->state != AHCI_DEV_OK)
{
return (ERROR);
}
return (OK);
}
/*******************************************************************************
*
* ahciDrv - initialize the AHCI driver
*
* This routine initializes the AHCI driver, sets up interrupt
* vectors, and performs hardware initialization of the AHCI chip.
*
* RETURNS: OK, or ERROR if initialization fails.
*
*/
LOCAL STATUS ahciDrv
(
SATA_HOST * pCtrl
)
{
AHCI_DRIVE * pDrive;
int drive;
int i,j;
int currPort;
int count;
/*size_t memRequired;*/
UINT8 * cmdListBlock;
/*UINT8 * cmdListBlockBak;*/
UINT8 * otherMemBlock;
UINT32 reg, reg2;
void * physAddr;
STATUS rc;
UINT32 cmdListSize;
UINT32 maxCmdListSize;
UINT32 recvFisSize;
UINT32 cmdTableSize;
UINT32 eachCmdAreaSize;
UINT32 totalCmdAreaSize;
VXB_ASSERT_NONNULL(pCtrl,ERROR)
pCtrl->svcTaskCount = AHCI_SVC_TASK_COUNT_DEF;
pCtrl->configType = &ahciTypes[0];
/* reset AHCI controller */
(void) CTRL_REG_READ(pCtrl, AHCI_GHC);
CTRL_REG_WRITE(pCtrl, AHCI_GHC, AHCI_GHC_HR);
/* HBA reset should be done within 1 second */
count = sysClkRateGet();
for (i = 0; i < count; i++)
{
/* return from taskDelay is not needed */
(void)taskDelay(1);
if ((CTRL_REG_READ(pCtrl, AHCI_GHC) & AHCI_GHC_HR) == 0)
break;
}
if (i == count)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Reset AHCI Controller Failed\n",
0,0,0,0,0,0);
return(ERROR);
}
/* install the interrupt and enable it */
rc = vxbIntConnect(pCtrl->pDev, 0, (VOIDFUNCPTR)ahciIntr,
(void *)pCtrl);
if (rc != OK)
return (rc);
rc = vxbIntEnable(pCtrl->pDev,0,(VOIDFUNCPTR)ahciIntr, (void *)pCtrl);
if (rc != OK)
return (rc);
/* enable AHCI mode */
CTRL_REG_WRITE(pCtrl, AHCI_GHC, AHCI_GHC_AE);
/* get the number ports in the controller */
pCtrl->numPorts =
(int)((CTRL_REG_READ(pCtrl, AHCI_CAP) & AHCI_CAP_NP) + 1);
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> AHCI controller reports %d ports\n",
pCtrl->numPorts,0,0,0,0,0);
/* get the number of command slots on each port */
pCtrl->numCommandSlots = (int)(((CTRL_REG_READ(pCtrl, AHCI_CAP)
& AHCI_CAP_NCS) >> AHCI_CAP_NCS_SHFT) + 1);
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> AHCI controller reports %d command slots\n",
pCtrl->numCommandSlots,0,0,0,0,0);
/* Find out if the controller supports NCQ */
if (CTRL_REG_READ(pCtrl, AHCI_CAP) & AHCI_CAP_SNCQ)
pCtrl->okNcq = TRUE;
else
pCtrl->okNcq = FALSE;
/* Calculate the number of implemented ports */
pCtrl->numImpPorts = 0;
reg = CTRL_REG_READ(pCtrl, AHCI_PI);
for (i = 0; i < AHCI_PI_WIDTH; i++)
{
if (reg & 1)
pCtrl->numImpPorts++;
reg >>= 1;
}
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> AHCI controller reports %d implemented ports\n",
pCtrl->numImpPorts,0,0,0,0,0);
/* Update the sataHostDmaParentTag */
pCtrl->sataHostDmaParentTag = vxbDmaBufTagParentGet (pCtrl->pDev, 0);
lstInit(&pCtrl->freeReqPrivList);
/* Create user buf tag ID */
pCtrl->userBufTagId = vxbDmaBufTagCreate(
pCtrl->pDev, /* pInst */
pCtrl->sataHostDmaParentTag, /* parent */
1, /* alignment */
0, /* boundary */
VXB_SPACE_MAXADDR_32BIT, /* lowaddr */
VXB_SPACE_MAXADDR, /* highaddr */
NULL, /* filter */
NULL, /* filterarg */
ATA_MAX_SG_CNT * AHCI_PRD_MAX_BYTES, /* max size */
ATA_MAX_SG_CNT, /* nSegments */
AHCI_PRD_MAX_BYTES, /* max seg size */
0, /* flags */
NULL, /* lockfunc */
NULL, /* lockarg */
NULL); /* ppDmaTag */
if (pCtrl->userBufTagId == NULL)
{
return ERROR;
}
cmdListSize = sizeof(AHCI_CMD_LIST);
recvFisSize = sizeof(AHCI_RECV_FIS);
cmdTableSize = (sizeof(AHCI_CMD_TABLE) * (size_t)pCtrl->numCommandSlots);
maxCmdListSize = cmdListSize * AHCI_MAX_CMD_SLOTS;
/* CMD_LIST + RECV_FIS + CMD_TABLE */
eachCmdAreaSize = (maxCmdListSize +
recvFisSize +
cmdTableSize);
totalCmdAreaSize = (size_t)pCtrl->numImpPorts * eachCmdAreaSize;
/* Create cmdListTagId for command list area */
pCtrl->sataCmdListTag = vxbDmaBufTagCreate (
pCtrl->pDev,
pCtrl->sataHostDmaParentTag, /* parent */
1024, /* alignment */
0, /* boundary */
VXB_SPACE_MAXADDR_32BIT, /* lowaddr */
VXB_SPACE_MAXADDR, /* highaddr */
NULL, /* filter */
NULL, /* filterarg */
totalCmdAreaSize, /* max size */
1, /* nSegments */
totalCmdAreaSize, /* max seg size */
VXB_DMABUF_NOCACHE, /* flags */
NULL, /* lockfunc */
NULL, /* lockarg */
NULL); /* ppDmaTag */
if (pCtrl->sataCmdListTag == NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> sataCmdListTag create fail\n",
0, 0, 0, 0, 0, 0);
goto errorOut;
}
/* Allocate memory for command list area */
pCtrl->pCmdList = vxbDmaBufMemAlloc (
pCtrl->pDev,
pCtrl->sataCmdListTag,
NULL,
VXB_DMABUF_MEMALLOC_CLEAR_BUF,
&pCtrl->sataCmdListMap);
if (pCtrl->pCmdList == NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> pCmdList create fail\n",
0, 0, 0, 0, 0, 0);
goto errorOut;
}
/*
* Load the DMA MAP so that we get the correct BUS address
* to program the hardware
*/
if(OK != vxbDmaBufMapLoad (pCtrl->pDev,
pCtrl->sataCmdListTag,
pCtrl->sataCmdListMap,
pCtrl->pCmdList,
totalCmdAreaSize,
0))
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> vxbDmaBufMapLoad pCmdList create fail\n",
0, 0, 0, 0, 0, 0);
goto errorOut;
}
cmdListBlock = (UINT8 *) (pCtrl->pCmdList);
otherMemBlock = cmdListBlock + pCtrl->numImpPorts * maxCmdListSize;
/* Now setup the implemented port structures */
currPort = 0;
reg = CTRL_REG_READ(pCtrl, AHCI_PI);
for (i = 0; i < AHCI_PI_WIDTH; i++)
{
if (reg & 1)
{
pCtrl->sataDev[currPort] = (void *)malloc(sizeof(AHCI_DRIVE));
if (pCtrl->sataDev[currPort] == NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to allocate memory for"
"controller structures\n",
0,0,0,0,0,0);
goto errorOut;
}
bzero((char *)pCtrl->sataDev[currPort], sizeof(AHCI_DRIVE));
pDrive = (AHCI_DRIVE *)(pCtrl->sataDev[currPort]);
/*
* Store logic port number to fix discontiguous
* port and port number not begin from 0 issue.
* Now sataPortDev.num is set logic port num,
* portPhyNum is set physical port number.
*/
pDrive->sataPortDev.host = pCtrl;
pDrive->sataPortDev.num = (UINT32)currPort;
pDrive->sataPortDev.okSpin = FALSE;
pDrive->regsAddr = (void *)((ULONG)pCtrl->regBase[0] +
(ULONG)i * 0x80 + 0x100);
pDrive->intCount = 0;
pDrive->timeoutErrorCount = 0;
pDrive->taskFileErrorCount = 0;
pDrive->nextTag = 0;
pDrive->portPhyNum = (UINT8)i;
currPort++;
/* Allocate and set the pointers to the command list */
pDrive->commandList = (AHCI_CMD_LIST *)cmdListBlock;
physAddr = ahciVirtToPciAddr (cmdListBlock);
PORT_REG_WRITE(pDrive, AHCI_PxCLB, VXB_ADDR_LOW32(physAddr));
PORT_REG_WRITE(pDrive, AHCI_PxCLBU, VXB_ADDR_HIGH32(physAddr));
cmdListBlock += maxCmdListSize;
/* Allocate and set the pointers to the received first buffer */
pDrive->recvFis = (AHCI_RECV_FIS *)otherMemBlock;
physAddr = ahciVirtToPciAddr (otherMemBlock);
PORT_REG_WRITE(pDrive, AHCI_PxFB, VXB_ADDR_LOW32(physAddr));
PORT_REG_WRITE(pDrive, AHCI_PxFBU, VXB_ADDR_HIGH32(physAddr));
otherMemBlock += recvFisSize;
/*
*Allocate the command tables, and link them
*to the command list
*/
pDrive->commandTable = (AHCI_CMD_TABLE *)otherMemBlock;
for (j = 0; j < pCtrl->numCommandSlots; j++)
{
physAddr = ahciVirtToPciAddr (&pDrive->commandTable[j]);
pDrive->commandList[j].cmdTableAddressLow =
AHCI_SWAP(VXB_ADDR_LOW32(physAddr));
pDrive->commandList[j].cmdTableAddressHigh =
AHCI_SWAP(VXB_ADDR_HIGH32(physAddr));
}
otherMemBlock += cmdTableSize;
/* Flush the cache */
vxbDmaBufMapSync(pCtrl->pDev,
pCtrl->sataCmdListTag,
pCtrl->sataCmdListMap,
((off_t)pDrive->commandList - (off_t)pCtrl->pCmdList),
(size_t)pCtrl->numCommandSlots * cmdListSize,
_VXB_DMABUFSYNC_DMA_PREWRITE);
/* Flush the cache */
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Setup Logic Port Number %d: %p %p %p\n",
pDrive->sataPortDev.num,pDrive->commandList,
pDrive->recvFis,pDrive->commandTable,0,0);
/* Create the sync and mutex semaphores for the port */
for (j = 0; j < pCtrl->numCommandSlots; j++)
{
pDrive->syncSem[j] = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
if (pDrive->syncSem[j] == SEM_ID_NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to create syncSem for"
"%d slot\n",
j,0,0,0,0,0);
goto errorOut;
}
}
pDrive->cmdStarted = 0;
/*
* Set the queue depth to the number of command slots, until
* we can read it from the drive.
*/
pDrive->queueDepth = pCtrl->numCommandSlots;
pDrive->queuedMode = FALSE;
pDrive->muteSem = semMCreate(SEM_Q_PRIORITY |
SEM_DELETE_SAFE |
SEM_INVERSION_SAFE);
if (pDrive->muteSem == SEM_ID_NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to create muteSem \n",
0,0,0,0,0,0);
goto errorOut;
}
pDrive->tagMuteSem = semMCreate(SEM_Q_FIFO | SEM_DELETE_SAFE);
if (pDrive->tagMuteSem == SEM_ID_NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to create tagMuteSem \n",
0,0,0,0,0,0);
goto errorOut;
}
pDrive->queueSlotSem = semCCreate(SEM_Q_PRIORITY, 1);
if (pDrive->queueSlotSem == SEM_ID_NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to create queueSlotSem \n",
0,0,0,0,0,0);
goto errorOut;
}
pDrive->monSyncSem = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);
if (pDrive->monSyncSem == SEM_ID_NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> Unable to create monSyncSem \n",
0,0,0,0,0,0);
goto errorOut;
}
pDrive->wdgId = wdCreate ();
if (pDrive->wdgId == NULL)
{
AHCISATA_DBG_LOG(DEBUG_INIT,
"<ahciDrv> wdCreate() fail",
0,0,0,0,0,0);
goto errorOut;
}
pDrive->wdgTimeout =
( (usrAhciWatchdogValGet() > 0) ? usrAhciWatchdogValGet(): AHCI_WDG_TIMEOUT_DEF);
pDrive->semTimeout =
((usrAhciSemTimoutValGet() > 0) ? usrAhciSemTimoutValGet(): AHCI_SEM_TIMEOUT_DEF);
pDrive->slotBit = 0xFFFFFFFF;
/* Clear any interrupts that have occurred on the port */
reg2 = PORT_REG_READ(pDrive, AHCI_PxSERR);
PORT_REG_WRITE(pDrive, AHCI_PxSERR, reg2);
reg2 = PORT_REG_READ(pDrive, AHCI_PxIS);
PORT_REG_WRITE(pDrive, AHCI_PxIS, reg2);
/* Enable desired interrupts on the port */
PORT_REG_WRITE (pDrive, AHCI_PxIE,
(AHCI_PIE_PRCE | AHCI_PIE_PCE |
AHCI_PIE_PSE | AHCI_PIE_DSE |
AHCI_PIE_DPE | AHCI_PIE_DHRE |
AHCI_PIS_SDBS));
/* activate the port */
reg2 = AHCI_PCMD_ICC_A | AHCI_PCMD_FRE | AHCI_PCMD_POD;
PORT_REG_WRITE (pDrive, AHCI_PxCMD, reg2);
/* spin up the port */
if (CTRL_REG_READ(pCtrl, AHCI_CAP) & AHCI_CAP_SSS)
{
reg2 |= AHCI_PCMD_SUD;
PORT_REG_WRITE (pDrive, AHCI_PxCMD, reg2);
}
/* Clear BUSY and DATA REQUEST flag via setting AHCI_PCMD_CLO*/
if (CTRL_REG_READ(pCtrl, AHCI_CAP) & AHCI_CAP_SCLO)
{
reg2 = PORT_REG_READ(pDrive, AHCI_PxCMD);
reg2 |= AHCI_PCMD_CLO;
PORT_REG_WRITE(pDrive, AHCI_PxCMD, reg2);
}
/* Wait for setting complete */
pDrive->wdgOkay = TRUE;
rc = wdStart (pDrive->wdgId, (pDrive->wdgTimeout),
(WD_RTN) ahciWdog, (_Vx_usr_arg_t) pDrive);
if (rc != OK)
{
pDrive->wdgOkay = FALSE;
goto errorOut;
}
while ((PORT_REG_READ(pDrive, AHCI_PxTFD) & AHCI_STAT_BUSY /* AHCI_STAT_ACCESS */) &&
(pDrive->wdgOkay))
taskDelay(3);
rc = wdCancel (pDrive->wdgId);
if (rc != OK)
{
goto errorOut;
}
}
reg >>= 1;
}
/* enable AHCI interrupts */
CTRL_REG_WRITE(pCtrl, AHCI_GHC,
CTRL_REG_READ(pCtrl, AHCI_GHC) | AHCI_GHC_IE);
for (drive = 0; drive < pCtrl->numImpPorts ; drive++)
{
pDrive = (AHCI_DRIVE *)(pCtrl->sataDev[drive]);
pDrive->state = AHCI_DEV_INIT;
pDrive->sataPortDev.type = AHCI_TYPE_NONE;
pDrive->sataPortDev.okNcq = FALSE;
pDrive->portError = FALSE;
pDrive->initActive = FALSE;
if ((PORT_REG_READ(pDrive, AHCI_PxSSTS) & AHCI_PSSTS_DET_MSK) ==
AHCI_PSSTS_DET_PHY)
{
/* check return during initialization is not needed */
(void)ahciInit(pCtrl, drive);
(void)ahciDriveInit (pCtrl, drive);
}
else
{
pDrive->sataPortDev.type = AHCI_TYPE_NONE;
pDrive->state = AHCI_DEV_NONE;
}
}
return (OK);
errorOut:
for (i = 0; i < pCtrl->numImpPorts; i++)
{
pDrive = (AHCI_DRIVE *)pCtrl->sataDev[i];
if (pDrive != NULL)
{
if (pDrive->wdgId != NULL)
(void) wdDelete (pDrive->wdgId);
if (pDrive->monSyncSem != SEM_ID_NULL)
(void) semDelete (pDrive->monSyncSem);
if (pDrive->queueSlotSem != SEM_ID_NULL)
(void) semDelete (pDrive->queueSlotSem);
if (pDrive->tagMuteSem != SEM_ID_NULL)
(void) semDelete (pDrive->tagMuteSem);
if (pDrive->muteSem != SEM_ID_NULL)
(void) semDelete (pDrive->muteSem);
for (j = 0; j < pCtrl->numCommandSlots; j++)
{
if (pDrive->syncSem[j] != SEM_ID_NULL)
(void) semDelete (pDrive->syncSem[j]);
}
free (pDrive);
}
}
if (pCtrl->sataCmdListTag != NULL)
{
if (pCtrl->pCmdList != NULL)
vxbDmaBufMemFree(pCtrl->sataCmdListTag,
pCtrl->pCmdList,
pCtrl->sataCmdListMap);
(void) vxbDmaBufTagDestroy(pCtrl->sataCmdListTag);
}
if (pCtrl->userBufTagId != NULL)
{
(void) vxbDmaBufTagDestroy(pCtrl->sataCmdListTag);
}
return ERROR;
}
/*******************************************************************************
*
* ahciCommand - Send a command to a AHCI Drive
*
* This is the common routine used to send all FIS commands to a SATA AHCI drive.
*
* RETURNS: OK, ERROR if the command didn't succeed.
*/
LOCAL STATUS ahciSataCmdIssue
(
SATA_DEVICE * pCtrl,
FIS_ATA_REG * pFisAta,
SATA_DATA * pSataData
)
{
AHCI_DRIVE * pDrive;
AHCI_PRD *prd;
UINT8 *commandFis;
UINT8 *pktCmd;
AHCI_CMD_LIST *commandList;
UINT32 flagsPrdLength, reg;
size_t prdCount = 0;
int key;
int tag;
int wait;
VXB_AHCI_MSG ctrlMsg;
STATUS rc;
UINT32 tagBit;
BOOL queued = (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_NCQ);
SATA_HOST * pSataHost;
off_t fisOffset;
off_t prdOffset;
off_t listOffset;
VXB_ASSERT_NONNULL(pCtrl,ERROR)
VXB_ASSERT_NONNULL(pFisAta,ERROR)
pDrive = (AHCI_DRIVE *)(pCtrl->host->sataDev[pCtrl->num]);
if (pDrive == NULL)
return ERROR;
pSataHost = pCtrl->host;
if ((pFisAta->fisCmd.fisCmdFlag &
(ATA_FLAG_SRST_ON | ATA_FLAG_SRST_OFF)) == 0)
ahciCmdWaitForResource(pDrive,queued);
if (queued)
{
rc = semTake(pDrive->tagMuteSem, WAIT_FOREVER);
if (rc != OK)
{
ahciCmdReleaseResource(pDrive,queued);
return (rc);
}
/* Loop while incrementing the tag until we find a free one */
do
{
tag = pDrive->nextTag;
pDrive->nextTag = (pDrive->nextTag + 1) % pDrive->queueDepth;
} while (pDrive->cmdStarted & (ahciBitMask[tag]));
tagBit = ahciBitMask[tag];
/* return from semGive is not needed */
(void)semGive(pDrive->tagMuteSem);
}
else
{
/* If we aren't queueing, we always use slot zero for the command */
tag = 0;
tagBit = 0x00000001;
}
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciDrv> ahciCommand: pCtrl=%p drive=%d tag=%d"
"flags=%x qm=%d\n",
pCtrl, pDrive, tag,pFisAta->fisCmd.fisCmdFlag,
pDrive->queuedMode,0);
prd = &pDrive->commandTable[tag].prd[0];
commandFis = pDrive->commandTable[tag].commandFis;
pktCmd = pDrive->commandTable[tag].atapiCommand;
commandList = &pDrive->commandList[tag];
prdOffset = ((off_t)prd - (off_t)pSataHost->pCmdList);
fisOffset = ((off_t)commandFis - (off_t)pSataHost->pCmdList);
listOffset = ((off_t)commandList - (off_t)pSataHost->pCmdList);
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_SRST_ON)
{
/*
* Send a soft reset on FIS to the device. We don't wait for an
* interrupt in this case, as there are none in response to
* this FIS
*/
memcpy(commandFis, &(pFisAta->fisCmd.fisAtaCmd),
(pFisAta->fisCmd.fisCmdLen * sizeof(UINT32)));
commandList->flagsPrdLength = AHCI_SWAP(AHCI_CMD_LIST_C |
AHCI_CMD_LIST_R |
pFisAta->fisCmd.fisCmdLen);
/* Flush Command fis */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
fisOffset,
pFisAta->fisCmd.fisCmdLen * sizeof(UINT32),
_VXB_DMABUFSYNC_DMA_PREWRITE);
PORT_REG_WRITE(pDrive, AHCI_PxCI, tagBit);
return(OK);
}
else if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_SRST_OFF)
{
/*
* Send a soft reset off FIS to the device. We don't wait for an
* interrupt in this case, as there are none in response to
* this FIS
*/
memcpy(commandFis, &(pFisAta->fisCmd.fisAtaCmd),
(pFisAta->fisCmd.fisCmdLen * sizeof(UINT32)));
commandList->flagsPrdLength = AHCI_SWAP(pFisAta->fisCmd.fisCmdLen);
/* Flush Command fis */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
fisOffset,
pFisAta->fisCmd.fisCmdLen * sizeof(UINT32),
_VXB_DMABUFSYNC_DMA_PREWRITE);
PORT_REG_WRITE(pDrive, AHCI_PxCI, tagBit);
return(OK);
}
else
{
/* Build a FIS with an ATAPI command packet in it */
memcpy(commandFis, &(pFisAta->fisCmd.fisAtaCmd),
(pFisAta->fisCmd.fisCmdLen * sizeof(UINT32)));
if(pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_ATAPI)
memcpy(pktCmd, &(pFisAta->fisCmd.fisApatiCmd),
AHCI_ATAPI_MAX_CMD_LENGTH);
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSataCmdIssue> fis: %02x %02x %02x %02x %02x %02x\n",
commandFis[0],commandFis[1],commandFis[2],
commandFis[3],commandFis[4],commandFis[5]);
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSataCmdIssue> fis: %02x %02x %02x %02x %02x %02x\n",
commandFis[6],commandFis[7],commandFis[8],
commandFis[9],commandFis[10],commandFis[11]);
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSataCmdIssue> fis: %02x %02x %02x %02x %02x %02x\n",
commandFis[12],commandFis[13],commandFis[14],
commandFis[15],commandFis[16],commandFis[17]);
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSataCmdIssue> fis: %02x %02x\n",
commandFis[18],commandFis[19],0,0,0,0);
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_NON_DATA)
{
/* All of our command FIS's are 5 32 bit words long */
flagsPrdLength = pFisAta->fisCmd.fisCmdLen;
}
else
{
prdCount = ahciPrdSetup((UINT8 *)pSataData->buffer,
(UINT32)(pSataData->blkSize * pSataData->blkNum),
prd);
if (prdCount == (size_t)ERROR)
{
ahciCmdReleaseResource(pDrive,queued);
return(ERROR);
}
/*
* Store the length of the table, along with any flags that
* are needed
*/
flagsPrdLength = (UINT32)(prdCount << AHCI_CMD_LIST_PRDTL_SHFT) | 5;
if (pFisAta->fisCmd.fisCmdFlag == ATA_FLAG_OUT_DATA)
flagsPrdLength |= AHCI_CMD_LIST_W;
if ((pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_NCQ) == 0)
flagsPrdLength |= AHCI_CMD_LIST_P;
}
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_ATAPI)
flagsPrdLength |= (AHCI_CMD_LIST_A | AHCI_CMD_LIST_P);
commandList->flagsPrdLength = AHCI_SWAP(flagsPrdLength);
commandList->recvByteCount = 0;
/* Flush the memory areas we updated for the controller */
/* Flush the prd table */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
prdOffset,
prdCount * sizeof(AHCI_PRD),
_VXB_DMABUFSYNC_DMA_PREWRITE);
/* Flush Command list */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
listOffset,
sizeof(AHCI_CMD_LIST),
_VXB_DMABUFSYNC_DMA_PREWRITE);
/* Flush Command fis */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
fisOffset,
pFisAta->fisCmd.fisCmdLen * sizeof(UINT32),
_VXB_DMABUFSYNC_DMA_PREWRITE);
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_ATAPI)
{
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
((off_t)pktCmd - (off_t)pSataHost->pCmdList),
AHCI_ATAPI_MAX_CMD_LENGTH,
_VXB_DMABUFSYNC_DMA_PREWRITE);
}
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSataCmdIssue> flagsPrdLength: %08x\n",
commandList->flagsPrdLength,0,0,0,0,0);
/* Flush the data buffer for writes */
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_OUT_DATA)
CACHE_USER_FLUSH(pSataData->buffer,
pSataData->blkSize * pSataData->blkNum);
/* Start the command */
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_NCQ)
{
key = intCpuLock();
/*
* For queued commands we must write both the SATA active
* and command init registers
*/
PORT_REG_WRITE(pDrive, AHCI_PxSACT, tagBit);
reg = PORT_REG_READ(pDrive, AHCI_PxSACT);
PORT_REG_WRITE(pDrive, AHCI_PxCI, tagBit);
reg = PORT_REG_READ(pDrive, AHCI_PxCI);
pDrive->cmdStarted |= tagBit;
intCpuUnlock(key);
}
else
{
key = intCpuLock();
PORT_REG_WRITE(pDrive, AHCI_PxCI, tagBit);
reg = PORT_REG_READ(pDrive, AHCI_PxCI);
pDrive->cmdStarted |= tagBit;
intCpuUnlock(key);
}
/*
* Wait for completion interrupt
* If it was a command that needs to wait for spinup, then
* wait 20 seconds to complete, otherwise, just wait
* the set delay.
*/
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_WAIT_SPINUP)
wait = sysClkRateGet() * 20;
else
wait = pDrive->semTimeout;
if (semTake (pDrive->syncSem[tag], wait) == ERROR)
{
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSataCmdIssue> semTake syncSem timeout:"
"tag = %d pCtrl=%p\n",
pCtrl,tag,0,0,0,0);
pDrive->timeoutErrorCount++;
ctrlMsg.msgId = AHCI_PORT_ERROR_MSG;
ctrlMsg.drive = (char)pDrive->sataPortDev.num;
ctrlMsg.pCtrl = pCtrl->host;
/* return from msgQSend is not needed */
(void)msgQSend(ahciMonQueue, (char *)&ctrlMsg,
VXB_AHCI_MSG_SIZE, NO_WAIT, MSG_PRI_NORMAL);
ahciCmdReleaseResource(pDrive,queued);
return(ERROR);
}
/*
* If drive isn't there, or an error occurred and the transfer
* did not complete, return error
*/
if ((pDrive->state != AHCI_DEV_OK && pDrive->state != AHCI_DEV_INIT) ||
pDrive->portError)
{
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSataCmdIssue> port error: pDriver=%02x,"
" pCtrl=%p\n",
pCtrl,pDrive,0,0,0,0);
ahciCmdReleaseResource(pDrive,queued);
if (pDrive->state == AHCI_DEV_NONE)
(void) errnoSet (S_ioLib_DISK_NOT_PRESENT);
return(ERROR);
}
/* invalidate the read buffer */
if (pFisAta->fisCmd.fisCmdFlag & ATA_FLAG_IN_DATA)
CACHE_USER_INVALIDATE(pSataData->buffer,
pSataData->blkSize * pSataData->blkNum);
}
ahciCmdReleaseResource(pDrive,queued);
if ((commandFis[3] == ATA_SMART_CMD_RETURN_STATUS) &&
(commandFis[2] == ATA_CMD_SMART))
{
UINT8 cylLo, cylHi;
int * ptr;
int lrec = ERROR;
ptr = (int *)pSataData->buffer;
cylLo = pDrive->recvFis->d2hRegisterFis[5];
cylHi = pDrive->recvFis->d2hRegisterFis[6];
if (cylLo == 0x4F && cylHi == 0xC2 )
{
lrec = ATA_SMART_OK;
}
else if (cylLo == 0xF4 && cylHi == 0x2C )
{
lrec = ATA_SMART_EXCEEDED;
}
else
{
lrec = ERROR;
}
if (NULL != ptr)
{
*ptr = lrec;
}
}
return(OK);
}
/*******************************************************************************
*
* ahciCmdWaitForResource - Wait for access to the drive port.
*
* This routine waits for the drive (if in non-queued mode), or a available
* command slot/tag in the drive (if in queued mode).
*
* RETURNS: N/A
*/
LOCAL void ahciCmdWaitForResource
(
AHCI_DRIVE * pDrive,
BOOL queued
)
{
int i;
STATUS rc;
VXB_ASSERT_NONNULL_V(pDrive)
/*
* If the drive doesn't support native command queueing, just take the
* drive mutex to hold of other tasks and do one command at a time
*/
if (!pDrive->sataPortDev.okNcq)
{
rc = semTake(pDrive->muteSem, WAIT_FOREVER);
if (rc != OK)
return;
}
else
{
/*
* If this is a queued mode command, then we need to take one of the
* slot semaphores to guarantee we do not try to queue more commands
* than we have slots
*/
if (queued)
{
rc = semTake(pDrive->queueSlotSem, WAIT_FOREVER);
if (rc != OK)
return;
/*
* If we are not in queued mode, then set queued mode to true,
* and give all but one of the slot semaphores (the one that we
* hold), so that other tasks may queue commands. We lock
* tasks while we are doing this, so that the queued mode flag
* is not read, then changed before we get a chance to change
* it.
*/
(void) taskCpuLock();
if (!pDrive->queuedMode)
{
pDrive->queuedMode = TRUE;
(void) taskCpuUnlock();
for (i = 0; i < pDrive->queueDepth-1; i++)
{
/* return from taskDelay is not needed */
(void)semGive(pDrive->queueSlotSem);
}
}
else
{
(void) taskCpuUnlock();
}
}
else
{
/*
* For an unqueued command, we take the drive mutex, to make sure
* we only do one unqueued command at a time
*/
rc = semTake(pDrive->muteSem, WAIT_FOREVER);
if (rc != OK)
return;
/*
* If we were in queued mode, then we need to take all of the slot
* semaphores, which will mean all of the queued commands have
* completed.
*/
if (pDrive->queuedMode)
{
for (i=0;i<pDrive->queueDepth;i++)
{
rc = semTake(pDrive->queueSlotSem, WAIT_FOREVER);
if (rc != OK)
return;
}
pDrive->queuedMode = FALSE;
}
/*
* Otherwise, just take the single queued slot sem which is
* available in unqueued mode, to prevent a queued command from
* being executed until we are done
*/
else
{
rc = semTake(pDrive->queueSlotSem, WAIT_FOREVER);
if (rc != OK)
return;
}
}
}
}
/*******************************************************************************
*
* ahciCmdReleaseResource - Release a drive port.
*
* This routine releases a drive (if in non-queued mode), or an available
* command slot/tag in the drive (if in queued mode).
*
* RETURNS: N/A
*/
LOCAL void ahciCmdReleaseResource
(
AHCI_DRIVE * pDrive,
BOOL queued
)
{
VXB_ASSERT_NONNULL_V(pDrive)
/*
* If the drive doesn't support native command queueing, just give the
* drive mutex to allow other tasks to run
*/
if (!pDrive->sataPortDev.okNcq)
{
/* return from semGive is not needed */
(void)semGive(pDrive->muteSem);
}
/*
* Otherwise, give the queue slot semaphore. If it wasn't a queued
* command, then we need to also give the mutex to release our
* exclusive access to the device
*/
else
{
/* return from semGive is not needed */
(void)semGive(pDrive->queueSlotSem);
if (!queued)
{
/* return from semGive is not needed */
(void)semGive(pDrive->muteSem);
}
}
}
/*******************************************************************************
*
* ahciMon - Main routine of SATA monitor task
*
* This routine runs in an infinite loop, waiting for a message that a new drive
* has been inserted on a SATA controller, a port error has occurred, or a halt,
* stop, or start has been requested on a port.
*
* RETURNS: N/A
*/
LOCAL void ahciMon (void)
{
VXB_AHCI_MSG msg;
char msgId;
int drive;
AHCI_DRIVE * pDrive;
int retry;
SATA_HOST * pCtrl;
BOOL attached;
FOREVER
{
/* return from msgQReceive is not needed */
(void) msgQReceive(ahciMonQueue, (char *)&msg, VXB_AHCI_MSG_SIZE, WAIT_FOREVER);
msgId = msg.msgId;
drive = msg.drive;
pCtrl = msg.pCtrl;
if (pCtrl == NULL)
continue;
pDrive = pCtrl->sataDev[drive];
if (pDrive == NULL)
continue;
switch(msgId)
{
case AHCI_ATTACH_MSG:
AHCISATA_DBG_LOG(DEBUG_MON,
"<ahciMon> Attach msg received\n"
" for logic port number %d\n",
pDrive->sataPortDev.num,0,0,0,0,0);
if (pDrive->state != AHCI_DEV_NONE)
continue;
AHCISATA_DBG_LOG(DEBUG_MON,
"<ahciMon> starting Init for logic"
" port number %d\n",
pDrive->sataPortDev.num,0,0,0,0,0);
retry = 0;
pDrive->state = AHCI_DEV_INIT;
while (ahciInit(pCtrl,drive) == ERROR)
{
AHCISATA_DBG_LOG(DEBUG_MON,
"<ahciMon> ataInit failed"
" on logic port number %d, retry %d\n",
pDrive->sataPortDev.num,retry,0,0,0,0);
retry++;
if (retry >= ahciRetry)
break;
}
retry = 0;
while (ahciDriveInit(pCtrl,drive) == ERROR)
{
AHCISATA_DBG_LOG(DEBUG_MON,
"<ahciMon> ataDrivInit"
" failed on logic port number %d,"
" retry %d\n",
pDrive->sataPortDev.num,retry,0,0,0,0);
retry++;
if (retry >= ahciRetry)
break;
}
break;
case AHCI_REMOVE_MSG:
AHCISATA_DBG_LOG(DEBUG_MON,
"<ahciMon> Remove msg received"
" for logic port number %d\n",
pDrive->sataPortDev.num,0,0,0,0,0);
/* physical device was removed, remove logical device */
attached = pDrive->sataPortDev.attached;
pDrive->sataPortDev.attached = FALSE;
/* return from sataXbdDevDelete is not needed */
if (attached)
(void)sataXbdDevDelete(&pDrive->sataPortDev);
pDrive->sataPortDev.type = AHCI_TYPE_NONE;
break;
case AHCI_PORT_ERROR_MSG:
AHCISATA_DBG_LOG(DEBUG_MON,
"<ahciMon> Port Error msg received"
" for logic port number %d\n",
pDrive->sataPortDev.num,0,0,0,0,0);
(void)ahciInit(pCtrl,drive);
pDrive->portError = FALSE;
break;
default:
break;
}
}
}
/*******************************************************************************
*
* ahciPrdSetup - Setup the DMA PRD table in memory
*
* RETURNS: OK, ERROR if the setup didn't succeed.
*/
LOCAL size_t ahciPrdSetup
(
UINT8 * data,
UINT32 nBytes,
AHCI_PRD * prd
)
{
size_t prdCount = 0;
UINT32 byteCount;
UINT32 size;
void * addr;
AHCI_PRD *currPrd = prd;
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdSetup> data=0x%x nBytes=%d\n",
data,nBytes,0,0,0,0);
/* Translate the address to PCI memory space */
addr = ahciVirtToPciAddr (data);
size = nBytes;
if ((ULONG)addr & 1)
{
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdSetup> misaligned DMA buffer\n",
0,0,0,0,0,0);
return ((size_t)ERROR);
}
while (size)
{
if (++prdCount > AHCI_MAX_PRD_ENTRIES)
{
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdSetup> DMA table too small\n",
0,0,0,0,0,0);
return ((size_t) ERROR);
}
else
{
if (size > AHCI_PRD_MAX_BYTES)
byteCount = AHCI_PRD_MAX_BYTES;
else
byteCount = size;
currPrd->dataBaseAddressLow = AHCI_SWAP(VXB_ADDR_LOW32(addr));
currPrd->dataBaseAddressHigh = AHCI_SWAP(VXB_ADDR_HIGH32(addr));
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdSetup> Table addr %p\n",
addr,0,0,0,0,0);
addr = (void *)((ULONG)addr + byteCount);
size -= byteCount;
byteCount--;
if (size == 0)
byteCount |= AHCI_PRD_I;
currPrd->dataByteCountInterrupt = AHCI_SWAP(byteCount);
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdSetup> Table count 0x%x\n",
byteCount,0,0,0,0,0);
currPrd++;
}
}
return (prdCount);
}
/*******************************************************************************
*
* ahciWdog - SATA controller watchdog handler
*
* RETURNS: N/A
*/
LOCAL void ahciWdog
(
AHCI_DRIVE *pDrive
)
{
if (pDrive == NULL)
return;
pDrive->wdgOkay = FALSE;
}
/*******************************************************************************
*
* ahciCmdSlotGet - get a free command slot
*
* This routine gets a free command slot.
*
* RETURNS: slot number or AHCI_MAX_CMD_SLOTS indicate no slot avaliable
*
* ERRNO: N/A
*
* \NOMANUAL
*/
LOCAL UINT32 ahciCmdSlotGet
(
AHCI_DRIVE * pDrive,
BOOL queued
)
{
STATUS rc;
UINT32 slot = AHCI_MAX_CMD_SLOTS;
/*
* If we can find a free slot return the slot bit
* or else return AHCI_MAX_CMD_SLOTS(32)
* bit "1" menas the slot is free
*/
rc = semTake(pDrive->tagMuteSem, WAIT_FOREVER);
if (rc != OK)
{
return slot;
}
if (queued)
{
/* Loop while incrementing the tag until we find a free one */
do
{
slot = pDrive->nextTag;
pDrive->nextTag = (pDrive->nextTag + 1) % pDrive->queueDepth;
} while (!(pDrive->slotBit & (1 << slot)));
}
else
{
if (pDrive->slotBit & 0x01)
slot = 0;
}
/* occupy the slot */
if (slot < AHCI_MAX_CMD_SLOTS)
pDrive->slotBit &= ~(1 << slot);
(void)semGive(pDrive->tagMuteSem);
return slot;
}
/*******************************************************************************
*
* ahciCmdSlotRelease - release the command slot
*
* This routine release the command slot.
*
* RETURNS: N/A
*
* ERRNO: N/A
*
* \NOMANUAL
*/
LOCAL VOID ahciCmdSlotRelease
(
AHCI_DRIVE * pDrive,
UINT32 slot
)
{
if (slot >= AHCI_MAX_CMD_SLOTS)
return;
/* Release the slot */
if (semTake(pDrive->tagMuteSem, WAIT_FOREVER) == ERROR)
return;
pDrive->slotBit |= (1 << slot);
(void)semGive(pDrive->tagMuteSem);
return;
}
/*******************************************************************************
*
* ahciReqResourceRelease - release the request resource
*
* This routine releases the request resource.
*
* RETURNS: N/A
*
* ERRNO: N/A
*
* \NOMANUAL
*/
LOCAL VOID ahciReqResourceRelease
(
AHCI_DRIVE * pDrive,
XBD_REQUEST * pXbdReq
)
{
pXBD_REQ_PRIV pReqPriv;
pSATA_HOST pSataHost;
pSataHost = pDrive->sataPortDev.host;
/* Break the connection */
pReqPriv = (pXBD_REQ_PRIV)pXbdReq->drvSpecific;
if (pReqPriv != NULL)
{
/* Map Unload */
ataMapUnLoad(pSataHost, pXbdReq);
/* Release the command slot */
ahciCmdSlotRelease(pDrive, pReqPriv->cmdTag);
pXbdReq->drvSpecific = NULL;
ataReqPrivRelease(pSataHost, pReqPriv);
}
return;
}
/*******************************************************************************
*
* ahciReqResourceGet - get resource to handle the request
*
* This routine gets resource to handle the request
*
* RETURNS: OK, or ERROR if fail
*
* ERRNO: N/A
*
* \NOMANUAL
*/
LOCAL STATUS ahciReqResourceGet
(
AHCI_DRIVE * pDrive,
XBD_REQUEST * pXbdReq
)
{
int slot;
pXBD_REQ_PRIV pReqPriv;
pSATA_HOST pSataHost;
pSataHost = pDrive->sataPortDev.host;
pReqPriv = ataReqPrivGet(pSataHost, VXB_DMABUF_MAPCREATE_NOBOUNCE_BUF);
if (pReqPriv == NULL)
return ERROR;
/*
* The request only handle read/write command, so if the device support
* NCQ, the command must be queued command
*/
slot = ahciCmdSlotGet(pDrive, pDrive->sataPortDev.okNcq);
if (slot == AHCI_MAX_CMD_SLOTS)
{
AHCISATA_DBG_LOG(DEBUG_CMD,"ERR: no free slot\n",
0, 0, 0, 0, 0, 0);
ataReqPrivRelease(pSataHost, pReqPriv);
return ERROR;
}
pXbdReq->drvSpecific = pReqPriv;
pReqPriv->pXbdReq = pXbdReq;
pReqPriv->cmdTag = slot;
/* Map request */
if (OK != ataMapLoad(pSataHost, pXbdReq))
{
AHCISATA_DBG_LOG(DEBUG_CMD,"ERR: can not map the request\n",
0, 0, 0, 0, 0, 0);
ahciCmdSlotRelease(pDrive, slot);
ataReqPrivRelease(pSataHost, pReqPriv);
pXbdReq->drvSpecific = NULL;
return ERROR;
}
return OK;
}
/*******************************************************************************
*
* ahciPrdBuild - build prd table
*
* This routine builds prd table.
*
* RETURNS: prd count
*
* ERRNO: N/A
*
* \NOMANUAL
*/
LOCAL size_t ahciPrdBuild
(
pSG_LIST pSgList,
AHCI_PRD * pPrd
)
{
int i;
UINT32 byteCount;
bus_addr_t busAddr;
UINT32 size;
size_t prdCount = 0;
/* Build prd table */
for (i = 0; i < pSgList->bufCnt; i++)
{
size = pSgList->bufVec[i].iov_len;
busAddr = (bus_addr_t)pSgList->bufVec[i].iov_base;
while (size)
{
if (++prdCount > AHCI_MAX_PRD_ENTRIES)
{
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdBuild> DMA table too small\n",
0,0,0,0,0,0);
return ((size_t) ERROR);
}
else
{
if (size > AHCI_PRD_MAX_BYTES)
byteCount = AHCI_PRD_MAX_BYTES;
else
byteCount = size;
pPrd->dataBaseAddressLow = AHCI_SWAP(VXB_ADDR_LOW32(busAddr));
pPrd->dataBaseAddressHigh = AHCI_SWAP(VXB_ADDR_HIGH32(busAddr));
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdBuild> Table addr %p\n",
busAddr,0,0,0,0,0);
busAddr = (busAddr + byteCount);
size -= byteCount;
byteCount--;
if ((size == 0) && (i == (pSgList->bufCnt - 1)))
byteCount |= AHCI_PRD_I;
pPrd->dataByteCountInterrupt = AHCI_SWAP(byteCount);
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciPrdBuild> Table count 0x%x\n",
byteCount,0,0,0,0,0);
pPrd++;
}
}
}
return prdCount;
}
/*******************************************************************************
*
* ahciSgTrans - transfer the scatter-gather list
*
* This routine transfer the scatter-gather list
*
* RETURNS: OK, or ERROR if fail
*
* ERRNO: N/A
*
* \NOMANUAL
*/
LOCAL STATUS ahciSgTrans
(
SATA_DEVICE * pSataDev,
pATA_CMD_DESC pAtaCmdDesc,
pSG_LIST pSgList
)
{
int key;
int wait;
BOOL queued;
UINT32 tag;
UINT32 factor;
UINT8 * commandFis;
UINT32 reg;
UINT32 tagBit;
UINT32 flagsPrdLength;
AHCI_PRD * pPrd ;
AHCI_CMD_LIST * commandList;
VXB_AHCI_MSG ctrlMsg;
AHCI_DRIVE * pDrive;
SATA_HOST * pSataHost;
off_t fisOffset;
off_t prdOffset;
off_t listOffset;
size_t prdCount = 0;
pSataHost = pSataDev->host;
pDrive = (AHCI_DRIVE *)(pSataHost->sataDev[pSataDev->num]);
queued = (pAtaCmdDesc->flags & ATA_FLAG_NCQ);
tag = pAtaCmdDesc->tag;
ahciCmdWaitForResource(pDrive, queued);
tagBit = (1 << tag);
pPrd = &pDrive->commandTable[tag].prd[0];
commandFis = pDrive->commandTable[tag].commandFis;
commandList = &pDrive->commandList[tag];
prdOffset = ((off_t)pPrd - (off_t)pSataHost->pCmdList);
fisOffset = ((off_t)commandFis - (off_t)pSataHost->pCmdList);
listOffset = ((off_t)commandList - (off_t)pSataHost->pCmdList);
/* Build Fis */
sataCmdDescToFis(pAtaCmdDesc, commandFis);
/* Build prd table */
prdCount = ahciPrdBuild(pSgList, pPrd);
if (prdCount == (size_t)ERROR)
{
ahciCmdReleaseResource(pDrive,queued);
return(ERROR);
}
/* Store the length of the table, along with any flags that are needed */
flagsPrdLength = (UINT32)(prdCount << AHCI_CMD_LIST_PRDTL_SHFT) | 5;
if (pAtaCmdDesc->flags & ATA_FLAG_OUT_DATA)
flagsPrdLength |= AHCI_CMD_LIST_W;
if (!queued)
flagsPrdLength |= AHCI_CMD_LIST_P;
AHCISATA_DBG_LOG(DEBUG_CMD,"<ahciSgTrans> flagsPrdLength: %08x prdCount %d\n",
commandList->flagsPrdLength, prdCount,0,0,0,0);
commandList->flagsPrdLength = AHCI_SWAP(flagsPrdLength);
commandList->recvByteCount = 0;
/* Flush the prd table */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
prdOffset,
prdCount * sizeof(AHCI_PRD),
_VXB_DMABUFSYNC_DMA_PREWRITE);
/* Flush Command list */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
listOffset,
sizeof(AHCI_CMD_LIST),
_VXB_DMABUFSYNC_DMA_PREWRITE);
/* Flush Command fis */
(void) vxbDmaBufMapSync(pSataHost->pDev,
pSataHost->sataCmdListTag,
pSataHost->sataCmdListMap,
fisOffset,
20,
_VXB_DMABUFSYNC_DMA_PREWRITE);
/* Start the command */
if (queued)
{
key = intCpuLock();
/*
* For queued commands we must write both the SATA active
* and command init registers
*/
PORT_REG_WRITE(pDrive, AHCI_PxSACT, tagBit);
reg = PORT_REG_READ(pDrive, AHCI_PxSACT);
PORT_REG_WRITE(pDrive, AHCI_PxCI, tagBit);
reg = PORT_REG_READ(pDrive, AHCI_PxCI);
pDrive->cmdStarted |= tagBit;
intCpuUnlock(key);
}
else
{
key = intCpuLock();
PORT_REG_WRITE(pDrive, AHCI_PxCI, tagBit);
reg = PORT_REG_READ(pDrive, AHCI_PxCI);
pDrive->cmdStarted |= tagBit;
intCpuUnlock(key);
}
/* Cacluate wait factor */
if (pSgList->bufCnt > 32)
factor = pSgList->bufCnt >> 3;
else
factor = 1;
wait = 10 * pDrive->semTimeout * factor;
if (semTake (pDrive->syncSem[tag], wait) == ERROR)
{
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSgTrans> semTake syncSem timeout:"
"tag = %d \n",
tag, 0, 0, 0, 0, 0);
pDrive->timeoutErrorCount++;
ctrlMsg.msgId = AHCI_PORT_ERROR_MSG;
ctrlMsg.drive = (char)pDrive->sataPortDev.num;
ctrlMsg.pCtrl = pSataDev->host;
/* return from msgQSend is not needed */
(void)msgQSend(ahciMonQueue, (char *)&ctrlMsg,
VXB_AHCI_MSG_SIZE, NO_WAIT, MSG_PRI_NORMAL);
ahciCmdReleaseResource(pDrive,queued);
return(ERROR);
}
/*
* If drive isn't there, or an error occurred and the transfer
* did not complete, return error
*/
if ((pDrive->state != AHCI_DEV_OK && pDrive->state != AHCI_DEV_INIT) ||
pDrive->portError)
{
AHCISATA_DBG_LOG(DEBUG_CMD,
"<ahciSgTrans> port error: pDriver=%02x,"
" pSataDev=%p\n",
pSataDev, pDrive,0,0,0,0);
ahciCmdReleaseResource(pDrive,queued);
if (pDrive->state == AHCI_DEV_NONE)
(void) errnoSet (S_ioLib_DISK_NOT_PRESENT);
return(ERROR);
}
ahciCmdReleaseResource(pDrive,queued);
return OK;
}
/*******************************************************************************
*
* ahciXferReq - request transfer routine
*
* This routine used to transfer the request.
*
* RETURNS: N/A
*
* ERRNO: N/A
*
* \NOMANUAL
*/
LOCAL void ahciXferReq
(
SATA_DEVICE * pSataDev,
XBD_REQUEST * pXbdReq
)
{
int i;
int errCode;
UINT8 rwCmd;
UINT8 index;
UINT32 tag;
sector_t curLen;
sector_t transLen;
AHCI_DRIVE * pDrive;
SATA_DEVICE * pSataDevice;
pXBD_REQ_PRIV pReqPriv;
pATA_CMD_DESC pAtaCmdDesc;
pSG_LIST pSgList;
bus_addr_t busAddr;
UINT32 maxXferSize;
struct bio * pBio;
pDrive = (AHCI_DRIVE *)(pSataDev->host->sataDev[pSataDev->num]);
if (pDrive == NULL)
{
AHCISATA_DBG_LOG(DEBUG_CMD,"ERR: pDrive is NULL\n",
0, 0, 0, 0, 0,0);
sataXbdReqDone(pXbdReq, EIO);
return;
}
if (pXbdReq->numBios > ATA_MAX_SG_CNT)
{
AHCISATA_DBG_LOG(DEBUG_CMD,"ERR: numBios %d > %d\n",
pXbdReq->numBios, ATA_MAX_SG_CNT, 0, 0, 0,0);
errCode = EIO;
goto Out;
}
if (OK != ahciReqResourceGet(pDrive, pXbdReq))
{
AHCISATA_DBG_LOG(DEBUG_CMD,"ERR: ahciReqResourceGet fail\n",
0, 0, 0, 0, 0,0);
errCode = EIO;
goto Out;
}
pSataDevice = &pDrive->sataPortDev;
pReqPriv = (pXBD_REQ_PRIV)pXbdReq->drvSpecific;
pAtaCmdDesc = &(pReqPriv->ataCmdDesc);
pSgList = &(pReqPriv->sgList);
tag = pReqPriv->cmdTag;
/* Get command code */
if (pXbdReq->reqflags & BIO_WRITE)
{
rwCmd = sataRwCmdGet(pSataDevice, TRUE);
pAtaCmdDesc->flags |= ATA_FLAG_OUT_DATA;
}
else
{
rwCmd = sataRwCmdGet(pSataDevice, FALSE);
pAtaCmdDesc->flags |= ATA_FLAG_IN_DATA;
}
if (pSataDevice->okNcq)
{
pAtaCmdDesc->flags |= ATA_FLAG_NCQ;
}
/* The driver work in dma mode */
if (pSataDevice->okDma)
{
sataRwCmdDescBuild(pSataDevice,
pAtaCmdDesc,
pXbdReq->reqFirstBlk,
pXbdReq->reqNumBlks,
0,
rwCmd,
tag);
/* Init buffer vec */
pSgList->bufCnt = pXbdReq->numBios;
for (i = 0; i < pSgList->bufCnt; i++)
{
pSgList->bufVec[i].iov_base = ataBusAddrGet(pXbdReq, i);
pSgList->bufVec[i].iov_len = ataBufLenGet(pXbdReq, i);
}
/* Transfer the request */
if (OK != ahciSgTrans(pSataDevice, pAtaCmdDesc, pSgList))
{
AHCISATA_DBG_LOG(DEBUG_CMD,"ERR: Transfer request fail \n",
0, 0, 0, 0, 0,0);
errCode = EIO;
goto Out;
}
}
else
{
index = 0;
maxXferSize = (pSataDevice->fisMaxSector * pSataDevice->bytes);
for(pBio = pXbdReq->pBioHead; pBio != NULL; pBio = pBio->bio_chain, index ++)
{
transLen = 0;
/* Store the bus address */
busAddr = ataBusAddrGet(pXbdReq, index);
while (transLen < pBio->bio_bcount)
{
curLen = min(maxXferSize, (pBio->bio_bcount - transLen));
/* StartBlk = cur bio blkno + transfer sector */
sataRwCmdDescBuild(pSataDevice,
pAtaCmdDesc,
(pBio->bio_blkno + transLen / pSataDevice->bytes),
(UINT16)(curLen / pSataDevice->bytes),
0,
rwCmd,
tag);
/* Init buffer vec */
pSgList->bufCnt = 1;
pSgList->bufVec[0].iov_base = busAddr + transLen;
pSgList->bufVec[0].iov_len = curLen - 1;
/* Transfer the request */
if (OK != ahciSgTrans(pSataDevice, pAtaCmdDesc, pSgList))
{
AHCISATA_DBG_LOG(DEBUG_CMD,"ERR: Transfer request fail \n",
0, 0, 0, 0, 0,0);
errCode = EIO;
goto Out;
}
transLen += curLen;
}
}
}
if (pAtaCmdDesc->flags & ATA_FLAG_IN_DATA)
{
vxbDmaBufSync(pSataDevice->host->pDev,
pSataDevice->host->userBufTagId,
pReqPriv->usrBufMapId,
_VXB_DMABUFSYNC_DMA_POSTREAD);
}
/* Release the resource */
ahciReqResourceRelease(pDrive, pXbdReq);
/* Indicate all the bio complete */
sataXbdReqDone(pXbdReq, 0);
return;
Out:
AHCISATA_DBG_LOG(DEBUG_CMD, "drive %d req %p handle fail %d,release slot %d\n",
pDrive->portPhyNum, pXbdReq, errCode, pXbdReq, 0, 0);
ahciReqResourceRelease(pDrive, pXbdReq);
sataXbdReqDone(pXbdReq, errCode);
return;
}
#endif