#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)));
(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)));
}
#define SRESPSZ 132
CTASSERT(SRESPSZ == sizeof (struct scsi_inquiry));
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;
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;
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 (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;
if (xp->ncq == 0) {
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 {
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;
if ((pkt->pkt_cdbp[1] & 0xfc) || pkt->pkt_cdbp[5]) {
status = STATUS_CHECK;
asc = 0x24;
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;
break;
}
} else {
inqp = (struct scsi_inquiry *)rp;
inqp->inq_qual = 0;
inqp->inq_ansi = 5;
inqp->inq_rdf = 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;
xp->capacity = LBA_CAPACITY(id);
last_block = xp->capacity - 1;
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;
break;
}
for (i = 1; i < 10; i++) {
if (pkt->pkt_cdbp[i]) {
status = STATUS_CHECK;
asc = 0x24;
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;
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;
break;
}
rp[3] = 8;
rl_len = 16;
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:
if ((pkt->pkt_cdbp[1] & 0xfe) || pkt->pkt_cdbp[2] ||
pkt->pkt_cdbp[3] || pkt->pkt_cdbp[5]) {
status = STATUS_CHECK;
asc = 0x24;
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:
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;
break;
}
break;
case SCMD_SYNCHRONIZE_CACHE:
if ((pkt->pkt_cdbp[1] & 0xf8) || (pkt->pkt_cdbp[6] & 0xe0) ||
pkt->pkt_cdbp[9]) {
status = STATUS_CHECK;
asc = 0x24;
break;
}
break;
case SCMD_TEST_UNIT_READY:
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;
break;
}
if (xp->ca) {
status = STATUS_CHECK;
key = 0x6;
asc = 0x28;
xp->ca = 0;
}
break;
default:
asc = 0x20;
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);
}
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);
}
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);
}
PMCS_CQ_RUN(pwp);
}
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)));
}
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);
}