/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* SATA midlayer interface for PMC drier.
*/
#include <sys/scsi/adapters/pmcs/pmcs.h>
static void
SATAcopy(pmcs_cmd_t *sp, void *kbuf, uint32_t amt)
{
struct buf *bp = scsi_pkt2bp(CMD2PKT(sp));
bp_mapin(scsi_pkt2bp(CMD2PKT(sp)));
/* There is only one direction currently */
(void) memcpy(bp->b_un.b_addr, kbuf, amt);
CMD2PKT(sp)->pkt_resid -= amt;
CMD2PKT(sp)->pkt_state |= STATE_XFERRED_DATA;
bp_mapout(scsi_pkt2bp(CMD2PKT(sp)));
}
/*
* Run a non block-io command. Some commands are interpreted
* out of extant data. Some imply actually running a SATA command.
*
* Returns zero if we were able to run.
*
* Returns -1 only if other commands are active, either another
* command here or regular I/O active.
*
* Called with PHY lock and xp statlock held.
*/
#define SRESPSZ 128
static int
pmcs_sata_special_work(pmcs_hw_t *pwp, pmcs_xscsi_t *xp)
{
int i;
int saq;
pmcs_cmd_t *sp;
struct scsi_pkt *pkt;
pmcs_phy_t *pptr;
uint8_t rp[SRESPSZ];
ata_identify_t *id;
uint32_t amt = 0;
uint8_t key = 0x05; /* illegal command */
uint8_t asc = 0;
uint8_t ascq = 0;
uint8_t status = STATUS_GOOD;
if (xp->actv_cnt) {
pmcs_prt(pwp, PMCS_PRT_DEBUG1, NULL, xp,
"%s: target %p actv count %u",
__func__, (void *)xp, xp->actv_cnt);
return (-1);
}
if (xp->special_running) {
pmcs_prt(pwp, PMCS_PRT_DEBUG, NULL, xp,
"%s: target %p special running already",
__func__, (void *)xp);
return (-1);
}
xp->special_needed = 0;
/*
* We're now running special.
*/
xp->special_running = 1;
pptr = xp->phy;
sp = STAILQ_FIRST(&xp->sq);
if (sp == NULL) {
xp->special_running = 0;
return (0);
}
pkt = CMD2PKT(sp);
pmcs_prt(pwp, PMCS_PRT_DEBUG2, pptr, xp,
"%s: target %p cmd %p cdb0 %x with actv_cnt %u",
__func__, (void *)xp, (void *)sp, pkt->pkt_cdbp[0], xp->actv_cnt);
if (pkt->pkt_cdbp[0] == SCMD_INQUIRY ||
pkt->pkt_cdbp[0] == SCMD_READ_CAPACITY) {
int retval;
if (pmcs_acquire_scratch(pwp, B_FALSE)) {
xp->special_running = 0;
return (-1);
}
saq = 1;
mutex_exit(&xp->statlock);
retval = pmcs_sata_identify(pwp, pptr);
mutex_enter(&xp->statlock);
if (retval) {
pmcs_release_scratch(pwp);
xp->special_running = 0;
pmcs_prt(pwp, PMCS_PRT_DEBUG2, pptr, xp,
"%s: target %p identify failed %x",
__func__, (void *)xp, retval);
/*
* If the failure is due to not being
* able to get resources, return such
* that we'll try later. Otherwise,
* fail current command.
*/
if (retval == ENOMEM) {
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
"%s: sata identify failed (ENOMEM) for "
"cmd %p", __func__, (void *)sp);
return (-1);
}
pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD;
if (retval == ETIMEDOUT) {
pkt->pkt_reason = CMD_TIMEOUT;
pkt->pkt_statistics |= STAT_TIMEOUT;
} else {
pkt->pkt_reason = CMD_TRAN_ERR;
}
goto out;
}
id = pwp->scratch;
/*
* Check to see if this device is an NCQ capable device.
* Yes, we'll end up doing this check for every INQUIRY
* if indeed we *are* only a pio device, but this is so
* infrequent that it's not really worth an extra bitfield.
*
* Note that PIO mode here means that the PMCS firmware
* performs PIO- not us.
*/
if (xp->ncq == 0) {
/*
* Reset existing stuff.
*/
xp->pio = 0;
xp->qdepth = 1;
xp->tagmap = 0;
if (id->word76 != 0 && id->word76 != 0xffff &&
(LE_16(id->word76) & (1 << 8))) {
xp->ncq = 1;
xp->qdepth = (LE_16(id->word75) & 0x1f) + 1;
pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, xp,
"%s: device %s supports NCQ %u deep",
__func__, xp->phy->path, xp->qdepth);
} else {
/*
* Default back to PIO.
*
* Note that non-FPDMA would still be possible,
* but for this specific configuration, if it's
* not NCQ it's safest to assume PIO.
*/
xp->pio = 1;
pmcs_prt(pwp, PMCS_PRT_DEBUG_CONFIG, pptr, xp,
"%s: device %s assumed PIO",
__func__, xp->phy->path);
}
}
} else {
saq = 0;
id = NULL;
}
bzero(rp, SRESPSZ);
switch (pkt->pkt_cdbp[0]) {
case SCMD_INQUIRY:
{
struct scsi_inquiry *inqp;
uint16_t *a, *b;
/* Check for illegal bits */
if ((pkt->pkt_cdbp[1] & 0xfc) || pkt->pkt_cdbp[5]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
if (pkt->pkt_cdbp[1] & 0x1) {
switch (pkt->pkt_cdbp[2]) {
case 0x0:
rp[3] = 3;
rp[5] = 0x80;
rp[6] = 0x83;
amt = 7;
break;
case 0x80:
rp[1] = 0x80;
rp[3] = 0x14;
a = (void *) &rp[4];
b = id->model_number;
for (i = 0; i < 5; i++) {
*a = ddi_swap16(*b);
a++;
b++;
}
amt = 24;
break;
case 0x83:
rp[1] = 0x83;
if ((LE_16(id->word87) & 0x100) &&
(LE_16(id->word108) >> 12) == 5) {
rp[3] = 12;
rp[4] = 1;
rp[5] = 3;
rp[7] = 8;
rp[8] = LE_16(id->word108) >> 8;
rp[9] = LE_16(id->word108);
rp[10] = LE_16(id->word109) >> 8;
rp[11] = LE_16(id->word109);
rp[12] = LE_16(id->word110) >> 8;
rp[13] = LE_16(id->word110);
rp[14] = LE_16(id->word111) >> 8;
rp[15] = LE_16(id->word111);
amt = 16;
} else {
rp[3] = 64;
rp[4] = 2;
rp[5] = 1;
rp[7] = 60;
rp[8] = 'A';
rp[9] = 'T';
rp[10] = 'A';
rp[11] = ' ';
rp[12] = ' ';
rp[13] = ' ';
rp[14] = ' ';
rp[15] = ' ';
a = (void *) &rp[16];
b = id->model_number;
for (i = 0; i < 20; i++) {
*a = ddi_swap16(*b);
a++;
b++;
}
a = (void *) &rp[40];
b = id->serial_number;
for (i = 0; i < 10; i++) {
*a = ddi_swap16(*b);
a++;
b++;
}
amt = 68;
}
break;
default:
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
} else {
inqp = (struct scsi_inquiry *)rp;
inqp->inq_qual = 0;
inqp->inq_ansi = 5; /* spc3 */
inqp->inq_rdf = 2; /* response format 2 */
inqp->inq_len = 32;
if (xp->ncq && (xp->qdepth > 1)) {
inqp->inq_cmdque = 1;
}
(void) memcpy(inqp->inq_vid, "ATA ", 8);
a = (void *)inqp->inq_pid;
b = id->model_number;
for (i = 0; i < 8; i++) {
*a = ddi_swap16(*b);
a++;
b++;
}
if (id->firmware_revision[2] == 0x2020 &&
id->firmware_revision[3] == 0x2020) {
inqp->inq_revision[0] =
ddi_swap16(id->firmware_revision[0]) >> 8;
inqp->inq_revision[1] =
ddi_swap16(id->firmware_revision[0]);
inqp->inq_revision[2] =
ddi_swap16(id->firmware_revision[1]) >> 8;
inqp->inq_revision[3] =
ddi_swap16(id->firmware_revision[1]);
} else {
inqp->inq_revision[0] =
ddi_swap16(id->firmware_revision[2]) >> 8;
inqp->inq_revision[1] =
ddi_swap16(id->firmware_revision[2]);
inqp->inq_revision[2] =
ddi_swap16(id->firmware_revision[3]) >> 8;
inqp->inq_revision[3] =
ddi_swap16(id->firmware_revision[3]);
}
amt = 36;
}
amt = pmcs_set_resid(pkt, amt, pkt->pkt_cdbp[4]);
if (amt) {
if (xp->actv_cnt) {
xp->special_needed = 1;
xp->special_running = 0;
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
"%s: @ line %d", __func__, __LINE__);
if (saq) {
pmcs_release_scratch(pwp);
}
return (-1);
}
SATAcopy(sp, rp, amt);
}
break;
}
case SCMD_READ_CAPACITY:
{
uint64_t last_block;
uint32_t block_size = 512; /* XXXX */
xp->capacity = LBA_CAPACITY(id);
last_block = xp->capacity - 1;
/* Check for illegal bits */
if ((pkt->pkt_cdbp[1] & 0xfe) || pkt->pkt_cdbp[6] ||
(pkt->pkt_cdbp[8] & 0xfe) || pkt->pkt_cdbp[7] ||
pkt->pkt_cdbp[9]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
for (i = 1; i < 10; i++) {
if (pkt->pkt_cdbp[i]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
}
if (status != STATUS_GOOD) {
break;
}
if (last_block > 0xffffffffULL) {
last_block = 0xffffffffULL;
}
rp[0] = (last_block >> 24) & 0xff;
rp[1] = (last_block >> 16) & 0xff;
rp[2] = (last_block >> 8) & 0xff;
rp[3] = (last_block) & 0xff;
rp[4] = (block_size >> 24) & 0xff;
rp[5] = (block_size >> 16) & 0xff;
rp[6] = (block_size >> 8) & 0xff;
rp[7] = (block_size) & 0xff;
amt = 8;
amt = pmcs_set_resid(pkt, amt, 8);
if (amt) {
if (xp->actv_cnt) {
xp->special_needed = 1;
xp->special_running = 0;
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
"%s: @ line %d", __func__, __LINE__);
if (saq) {
pmcs_release_scratch(pwp);
}
return (-1);
}
SATAcopy(sp, rp, amt);
}
break;
}
case SCMD_REPORT_LUNS: {
int rl_len;
/* Check for illegal bits */
if (pkt->pkt_cdbp[1] || pkt->pkt_cdbp[3] || pkt->pkt_cdbp[4] ||
pkt->pkt_cdbp[5] || pkt->pkt_cdbp[10] ||
pkt->pkt_cdbp[11]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
rp[3] = 8;
rl_len = 16; /* list length (4) + reserved (4) + 1 LUN (8) */
amt = rl_len;
amt = pmcs_set_resid(pkt, amt, rl_len);
if (amt) {
if (xp->actv_cnt) {
xp->special_needed = 1;
xp->special_running = 0;
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
"%s: @ line %d", __func__, __LINE__);
if (saq) {
pmcs_release_scratch(pwp);
}
return (-1);
}
SATAcopy(sp, rp, rl_len);
}
break;
}
case SCMD_REQUEST_SENSE:
/* Check for illegal bits */
if ((pkt->pkt_cdbp[1] & 0xfe) || pkt->pkt_cdbp[2] ||
pkt->pkt_cdbp[3] || pkt->pkt_cdbp[5]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
rp[0] = 0xf0;
amt = 18;
amt = pmcs_set_resid(pkt, amt, pkt->pkt_cdbp[4]);
if (amt) {
if (xp->actv_cnt) {
xp->special_needed = 1;
xp->special_running = 0;
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
"%s: @ line %d", __func__, __LINE__);
if (saq) {
pmcs_release_scratch(pwp);
}
return (-1);
}
SATAcopy(sp, rp, 18);
}
break;
case SCMD_START_STOP:
/* Check for illegal bits */
if ((pkt->pkt_cdbp[1] & 0xfe) || pkt->pkt_cdbp[2] ||
(pkt->pkt_cdbp[3] & 0xf0) || (pkt->pkt_cdbp[4] & 0x08) ||
pkt->pkt_cdbp[5]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
break;
case SCMD_SYNCHRONIZE_CACHE:
/* Check for illegal bits */
if ((pkt->pkt_cdbp[1] & 0xf8) || (pkt->pkt_cdbp[6] & 0xe0) ||
pkt->pkt_cdbp[9]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
break;
case SCMD_TEST_UNIT_READY:
/* Check for illegal bits */
if (pkt->pkt_cdbp[1] || pkt->pkt_cdbp[2] || pkt->pkt_cdbp[3] ||
pkt->pkt_cdbp[4] || pkt->pkt_cdbp[5]) {
status = STATUS_CHECK;
asc = 0x24; /* invalid field in cdb */
break;
}
if (xp->ca) {
status = STATUS_CHECK;
key = 0x6;
asc = 0x28;
xp->ca = 0;
}
break;
default:
asc = 0x20; /* invalid operation command code */
status = STATUS_CHECK;
break;
}
if (status != STATUS_GOOD) {
bzero(rp, 18);
rp[0] = 0xf0;
rp[2] = key;
rp[12] = asc;
rp[13] = ascq;
pmcs_latch_status(pwp, sp, status, rp, 18, pptr->path);
} else {
pmcs_latch_status(pwp, sp, status, NULL, 0, pptr->path);
}
out:
STAILQ_REMOVE_HEAD(&xp->sq, cmd_next);
pmcs_prt(pwp, PMCS_PRT_DEBUG2, pptr, xp,
"%s: pkt %p tgt %u done reason=%x state=%x resid=%ld status=%x",
__func__, (void *)pkt, xp->target_num, pkt->pkt_reason,
pkt->pkt_state, pkt->pkt_resid, status);
if (saq) {
pmcs_release_scratch(pwp);
}
if (xp->draining) {
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, xp,
"%s: waking up drain waiters", __func__);
cv_signal(&pwp->drain_cv);
}
mutex_exit(&xp->statlock);
mutex_enter(&pwp->cq_lock);
STAILQ_INSERT_TAIL(&pwp->cq, sp, cmd_next);
PMCS_CQ_RUN_LOCKED(pwp);
mutex_exit(&pwp->cq_lock);
mutex_enter(&xp->statlock);
xp->special_running = 0;
return (0);
}
/*
* Run all special commands queued up for a SATA device.
* We're only called if the caller knows we have work to do.
*
* We can't run them if things are still active for the device,
* return saying we didn't run anything.
*
* When we finish, wake up anyone waiting for active commands
* to go to zero.
*
* Called with PHY lock and xp statlock held.
*/
int
pmcs_run_sata_special(pmcs_hw_t *pwp, pmcs_xscsi_t *xp)
{
while (!STAILQ_EMPTY(&xp->sq)) {
if (pmcs_sata_special_work(pwp, xp)) {
return (-1);
}
}
return (0);
}
/*
* Search for SATA special commands to run and run them.
* If we succeed in running the special command(s), kick
* the normal commands into operation again. Call completion
* for any commands that were completed while we were here.
*
* Called unlocked.
*/
void
pmcs_sata_work(pmcs_hw_t *pwp)
{
pmcs_xscsi_t *xp;
int spinagain = 0;
uint16_t target;
for (target = 0; target < pwp->max_dev; target++) {
xp = pwp->targets[target];
if ((xp == NULL) || (xp->phy == NULL)) {
continue;
}
pmcs_lock_phy(xp->phy);
mutex_enter(&xp->statlock);
if (STAILQ_EMPTY(&xp->sq)) {
mutex_exit(&xp->statlock);
pmcs_unlock_phy(xp->phy);
continue;
}
if (xp->actv_cnt) {
xp->special_needed = 1;
pmcs_prt(pwp, PMCS_PRT_DEBUG1, NULL, xp,
"%s: deferring until drained", __func__);
spinagain++;
} else {
if (pmcs_run_sata_special(pwp, xp)) {
spinagain++;
}
}
mutex_exit(&xp->statlock);
pmcs_unlock_phy(xp->phy);
}
if (spinagain) {
SCHEDULE_WORK(pwp, PMCS_WORK_SATA_RUN);
} else {
SCHEDULE_WORK(pwp, PMCS_WORK_RUN_QUEUES);
}
/*
* Run completion on any commands ready for it.
*/
PMCS_CQ_RUN(pwp);
}
/*
* Called with PHY lock held and scratch acquired
*/
int
pmcs_sata_identify(pmcs_hw_t *pwp, pmcs_phy_t *pptr)
{
fis_t fis;
fis[0] = (IDENTIFY_DEVICE << 16) | (1 << 15) | FIS_REG_H2DEV;
fis[1] = 0;
fis[2] = 0;
fis[3] = 0;
fis[4] = 0;
return (pmcs_run_sata_cmd(pwp, pptr, fis, SATA_PROTOCOL_PIO,
PMCIN_DATADIR_2_INI, sizeof (ata_identify_t)));
}
/*
* Called with PHY lock held and scratch held
*/
int
pmcs_run_sata_cmd(pmcs_hw_t *pwp, pmcs_phy_t *pptr, fis_t fis, uint32_t mode,
uint32_t ddir, uint32_t dlen)
{
struct pmcwork *pwrk;
uint32_t *ptr, msg[PMCS_MSG_SIZE];
uint32_t iq, htag, status;
int i, result = 0;
pwrk = pmcs_gwork(pwp, PMCS_TAG_TYPE_WAIT, pptr);
if (pwrk == NULL) {
return (ENOMEM);
}
msg[0] = LE_32(PMCS_IOMB_IN_SAS(PMCS_OQ_IODONE,
PMCIN_SATA_HOST_IO_START));
htag = pwrk->htag;
pwrk->arg = msg;
pwrk->dtype = SATA;
msg[1] = LE_32(pwrk->htag);
msg[2] = LE_32(pptr->device_id);
msg[3] = LE_32(dlen);
msg[4] = LE_32(mode | ddir);
if (dlen) {
if (ddir == PMCIN_DATADIR_2_DEV) {
if (ddi_dma_sync(pwp->cip_handles, 0, 0,
DDI_DMA_SYNC_FORDEV) != DDI_SUCCESS) {
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
"Condition check failed at %s():%d",
__func__, __LINE__);
}
}
msg[12] = LE_32(DWORD0(pwp->scratch_dma));
msg[13] = LE_32(DWORD1(pwp->scratch_dma));
msg[14] = LE_32(dlen);
msg[15] = 0;
} else {
msg[12] = 0;
msg[13] = 0;
msg[14] = 0;
msg[15] = 0;
}
for (i = 0; i < 5; i++) {
msg[5+i] = LE_32(fis[i]);
}
msg[10] = 0;
msg[11] = 0;
GET_IO_IQ_ENTRY(pwp, ptr, pptr->device_id, iq);
if (ptr == NULL) {
pmcs_pwork(pwp, pwrk);
return (ENOMEM);
}
COPY_MESSAGE(ptr, msg, PMCS_MSG_SIZE);
pwrk->state = PMCS_WORK_STATE_ONCHIP;
INC_IQ_ENTRY(pwp, iq);
pmcs_unlock_phy(pptr);
WAIT_FOR(pwrk, 1000, result);
pmcs_pwork(pwp, pwrk);
pmcs_lock_phy(pptr);
if (result) {
pmcs_timed_out(pwp, htag, __func__);
if (pmcs_abort(pwp, pptr, htag, 0, 1)) {
pptr->abort_pending = 1;
SCHEDULE_WORK(pwp, PMCS_WORK_ABORT_HANDLE);
}
return (ETIMEDOUT);
}
status = LE_32(msg[2]);
if (status != PMCOUT_STATUS_OK) {
if (status == PMCOUT_STATUS_OPEN_CNX_ERROR_STP_RESOURCES_BUSY) {
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, pptr->target,
"%s: Potential affiliation active on 0x%" PRIx64,
__func__, pmcs_barray2wwn(pptr->sas_address));
} else {
pmcs_prt(pwp, PMCS_PRT_DEBUG2, pptr, pptr->target,
"%s: SATA I/O returned with IOMB status 0x%x",
__func__, status);
}
return (EIO);
}
if (LE_32(ptr[3]) != 0) {
size_t j, amt = LE_32(ptr[3]);
if (amt > sizeof (fis_t)) {
amt = sizeof (fis_t);
}
amt >>= 2;
for (j = 0; j < amt; j++) {
fis[j] = LE_32(msg[4 + j]);
}
}
if (dlen && ddir == PMCIN_DATADIR_2_INI) {
if (ddi_dma_sync(pwp->cip_handles, 0, 0,
DDI_DMA_SYNC_FORKERNEL) != DDI_SUCCESS) {
pmcs_prt(pwp, PMCS_PRT_DEBUG, pptr, NULL,
"Condition check failed at %s():%d",
__func__, __LINE__);
}
}
return (0);
}