/* $NetBSD: siop.c,v 1.3 2016/04/30 15:09:25 skrll Exp $ */
/*
* Copyright (c) 2010 KIYOHARA Takashi
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>
#include <dev/microcode/siop/siop.out>
#include "boot.h"
#include "sdvar.h"
#define SIOP_DEFAULT_TARGET 7
#define ALLOC(T, A) \
(T *)(((uint32_t)alloc(sizeof(T) + (A)) + (A)) & ~((A) - 1))
#define VTOPHYS(va) (uint32_t)(va)
#define DEVTOV(pa) (uint32_t)(pa)
#define wbinv(adr, siz) _wbinv(VTOPHYS(adr), (uint32_t)(siz))
#define inv(adr, siz) _inv(VTOPHYS(adr), (uint32_t)(siz))
/* 53c810 supports little endian */
#define htoc32(x) htole32(x)
#define ctoh32(x) le32toh(x)
static void siop_pci_reset(int);
static void siop_setuptables(struct siop_adapter *, struct siop_xfer *,
struct scsi_xfer *);
static void siop_ma(struct siop_adapter *, struct scsi_xfer *);
static void siop_sdp(struct siop_adapter *, struct siop_xfer *,
struct scsi_xfer *, int);
static void siop_update_resid(struct siop_adapter *, struct siop_xfer *,
struct scsi_xfer *, int);
static int siop_intr(struct siop_adapter *);
static void siop_scsicmd_end(struct siop_adapter *, struct scsi_xfer *);
static int siop_scsi_request(struct siop_adapter *, struct scsi_xfer *);
static void siop_start(struct siop_adapter *, struct scsi_xfer *);
static void siop_xfer_setup(struct siop_xfer *, void *);
static int siop_add_reselsw(struct siop_adapter *, int, int);
static void siop_update_scntl3(struct siop_adapter *, int, int);
static int _scsi_inquire(struct siop_adapter *, int, int, int, char *);
static void scsi_request_sense(struct siop_adapter *, struct scsi_xfer *);
static int scsi_interpret_sense(struct siop_adapter *, struct scsi_xfer *);
static int scsi_probe(struct siop_adapter *);
static struct siop_adapter adapt;
static void
siop_pci_reset(int addr)
{
int dmode, ctest5;
const int maxburst = 4; /* 53c810 */
dmode = readb(addr + SIOP_DMODE);
ctest5 = readb(addr + SIOP_CTEST5);
writeb(addr + SIOP_CTEST4, readb(addr + SIOP_CTEST4) & ~CTEST4_BDIS);
ctest5 &= ~CTEST5_BBCK;
ctest5 |= (maxburst - 1) & CTEST5_BBCK;
writeb(addr + SIOP_CTEST5, ctest5);
dmode |= DMODE_ERL;
dmode &= ~DMODE_BL_MASK;
dmode |= ((maxburst - 1) << DMODE_BL_SHIFT) & DMODE_BL_MASK;
writeb(addr + SIOP_DMODE, dmode);
}
static void
siop_setuptables(struct siop_adapter *adp, struct siop_xfer *xfer,
struct scsi_xfer *xs)
{
int msgoffset = 1;
xfer->siop_tables.id =
htoc32((adp->clock_div << 24) | (xs->target << 16));
memset(xfer->siop_tables.msg_out, 0, sizeof(xfer->siop_tables.msg_out));
/* request sense doesn't disconnect */
if (xs->cmd->opcode == SCSI_REQUEST_SENSE)
xfer->siop_tables.msg_out[0] = MSG_IDENTIFY(xs->lun, 0);
else
xfer->siop_tables.msg_out[0] = MSG_IDENTIFY(xs->lun, 1);
xfer->siop_tables.t_msgout.count = htoc32(msgoffset);
xfer->siop_tables.status =
htoc32(SCSI_SIOP_NOSTATUS); /* set invalid status */
xfer->siop_tables.cmd.count = htoc32(xs->cmdlen);
xfer->siop_tables.cmd.addr = htoc32(local_to_PCI((u_long)xs->cmd));
if (xs->datalen != 0) {
xfer->siop_tables.data[0].count = htoc32(xs->datalen);
xfer->siop_tables.data[0].addr =
htoc32(local_to_PCI((u_long)xs->data));
}
}
static void
siop_ma(struct siop_adapter *adp, struct scsi_xfer *xs)
{
int offset, dbc;
/*
* compute how much of the current table didn't get handled when
* a phase mismatch occurs
*/
if (xs->datalen == 0)
return; /* no valid data transfer */
offset = readb(adp->addr + SIOP_SCRATCHA + 1);
if (offset >= SIOP_NSG) {
printf("bad offset in siop_sdp (%d)\n", offset);
return;
}
dbc = readl(adp->addr + SIOP_DBC) & 0x00ffffff;
xs->resid = dbc;
}
static void
siop_sdp(struct siop_adapter *adp, struct siop_xfer *xfer, struct scsi_xfer *xs,
int offset)
{
if (xs->datalen == 0)
return; /* no data pointers to save */
/*
* offset == SIOP_NSG may be a valid condition if we get a Save data
* pointer when the xfer is done. Just ignore the Save data pointer
* in this case
*/
if (offset == SIOP_NSG)
return;
/*
* Save data pointer. We do this by adjusting the tables to point
* at the begginning of the data not yet transfered.
* offset points to the first table with untransfered data.
*/
/*
* before doing that we decrease resid from the ammount of data which
* has been transfered.
*/
siop_update_resid(adp, xfer, xs, offset);
#if 0
/*
* First let see if we have a resid from a phase mismatch. If so,
* we have to adjst the table at offset to remove transfered data.
*/
if (siop_cmd->flags & CMDFL_RESID) {
scr_table_t *table;
siop_cmd->flags &= ~CMDFL_RESID;
table = &xfer->siop_tables.data[offset];
/* "cut" already transfered data from this table */
table->addr =
htoc32(ctoh32(table->addr) + ctoh32(table->count) -
siop_cmd->resid);
table->count = htoc32(siop_cmd->resid);
}
#endif
/*
* now we can remove entries which have been transfered.
* We just move the entries with data left at the beggining of the
* tables
*/
memmove(xfer->siop_tables.data, &xfer->siop_tables.data[offset],
(SIOP_NSG - offset) * sizeof(scr_table_t));
}
static void
siop_update_resid(struct siop_adapter *adp, struct siop_xfer *xfer,
struct scsi_xfer *xs, int offset)
{
int i;
if (xs->datalen == 0)
return; /* no data to transfer */
/*
* update resid. First account for the table entries which have
* been fully completed.
*/
for (i = 0; i < offset; i++)
xs->resid -= ctoh32(xfer->siop_tables.data[i].count);
#if 0
/*
* if CMDFL_RESID is set, the last table (pointed by offset) is a
* partial transfers. If not, offset points to the entry folloing
* the last full transfer.
*/
if (siop_cmd->flags & CMDFL_RESID) {
scr_table_t *table = &xfer->siop_tables.data[offset];
xs->resid -= ctoh32(table->count) - xs->resid;
}
#endif
}
#define CALL_SCRIPT(ent) writel(adp->addr + SIOP_DSP, scriptaddr + ent);
static int
siop_intr(struct siop_adapter *adp)
{
struct siop_xfer *siop_xfer = NULL;
struct scsi_xfer *xs = NULL;
u_long scriptaddr = local_to_PCI((u_long)adp->script);
int offset, target, lun, tag, restart = 0, need_reset = 0;
uint32_t dsa, irqcode;
uint16_t sist;
uint8_t dstat = 0, sstat1, istat;
istat = readb(adp->addr + SIOP_ISTAT);
if ((istat & (ISTAT_INTF | ISTAT_DIP | ISTAT_SIP)) == 0)
return 0;
if (istat & ISTAT_INTF) {
printf("INTRF\n");
writeb(adp->addr + SIOP_ISTAT, ISTAT_INTF);
}
if ((istat & (ISTAT_DIP | ISTAT_SIP | ISTAT_ABRT)) ==
(ISTAT_DIP | ISTAT_ABRT))
/* clear abort */
writeb(adp->addr + SIOP_ISTAT, 0);
/* use DSA to find the current siop_cmd */
dsa = readl(adp->addr + SIOP_DSA);
if (dsa >= local_to_PCI((u_long)adp->xfer) &&
dsa < local_to_PCI((u_long)adp->xfer) + SIOP_TABLE_SIZE) {
dsa -= local_to_PCI((u_long)adp->xfer);
siop_xfer = adp->xfer;
_inv((u_long)siop_xfer, sizeof(*siop_xfer));
xs = adp->xs;
}
if (istat & ISTAT_DIP)
dstat = readb(adp->addr + SIOP_DSTAT);
if (istat & ISTAT_SIP) {
if (istat & ISTAT_DIP)
delay(10);
/*
* Can't read sist0 & sist1 independently, or we have to
* insert delay
*/
sist = readw(adp->addr + SIOP_SIST0);
sstat1 = readb(adp->addr + SIOP_SSTAT1);
if ((sist & SIST0_MA) && need_reset == 0) {
if (siop_xfer) {
int scratcha0;
dstat = readb(adp->addr + SIOP_DSTAT);
/*
* first restore DSA, in case we were in a S/G
* operation.
*/
writel(adp->addr + SIOP_DSA,
local_to_PCI((u_long)siop_xfer));
scratcha0 = readb(adp->addr + SIOP_SCRATCHA);
switch (sstat1 & SSTAT1_PHASE_MASK) {
case SSTAT1_PHASE_STATUS:
/*
* previous phase may be aborted for any reason
* ( for example, the target has less data to
* transfer than requested). Compute resid and
* just go to status, the command should
* terminate.
*/
if (scratcha0 & A_flag_data)
siop_ma(adp, xs);
else if ((dstat & DSTAT_DFE) == 0)
printf("PHASE STATUS: siop_clearfifo...\n");
// siop_clearfifo(adp);
CALL_SCRIPT(Ent_status);
return 1;
case SSTAT1_PHASE_MSGIN:
/*
* target may be ready to disconnect
* Compute resid which would be used later
* if a save data pointer is needed.
*/
if (scratcha0 & A_flag_data)
siop_ma(adp, xs);
else if ((dstat & DSTAT_DFE) == 0)
printf("PHASE MSGIN: siop_clearfifo...\n");
// siop_clearfifo(adp);
writeb(adp->addr + SIOP_SCRATCHA,
scratcha0 & ~A_flag_data);
CALL_SCRIPT(Ent_msgin);
return 1;
}
printf("unexpected phase mismatch %d\n",
sstat1 & SSTAT1_PHASE_MASK);
} else
printf("phase mismatch without command\n");
need_reset = 1;
}
if (sist & (SIST1_STO << 8)) {
/* selection time out, assume there's no device here */
if (siop_xfer) {
xs->error = XS_SELTIMEOUT;
goto end;
} else
printf("selection timeout without command\n");
}
/* Else it's an unhandled exception (for now). */
printf("unhandled scsi interrupt,"
" sist=0x%x sstat1=0x%x DSA=0x%x DSP=0x%lx\n",
sist, sstat1, dsa,
readl(adp->addr + SIOP_DSP) - scriptaddr);
if (siop_xfer) {
xs->error = XS_SELTIMEOUT;
goto end;
}
need_reset = 1;
}
if (need_reset) {
reset:
printf("XXXXX: fatal error, need reset the bus...\n");
return 1;
}
//scintr:
if ((istat & ISTAT_DIP) && (dstat & DSTAT_SIR)) { /* script interrupt */
irqcode = readl(adp->addr + SIOP_DSPS);
/*
* no command, or an inactive command is only valid for a
* reselect interrupt
*/
if ((irqcode & 0x80) == 0) {
if (siop_xfer == NULL) {
printf(
"script interrupt 0x%x with invalid DSA\n",
irqcode);
goto reset;
}
}
switch(irqcode) {
case A_int_err:
printf("error, DSP=0x%lx\n",
readl(adp->addr + SIOP_DSP) - scriptaddr);
if (xs) {
xs->error = XS_SELTIMEOUT;
goto end;
} else {
goto reset;
}
case A_int_reseltarg:
printf("reselect with invalid target\n");
goto reset;
case A_int_resellun:
target = readb(adp->addr + SIOP_SCRATCHA) & 0xf;
lun = readb(adp->addr + SIOP_SCRATCHA + 1);
tag = readb(adp->addr + SIOP_SCRATCHA + 2);
if (target != adp->xs->target ||
lun != adp->xs->lun ||
tag != 0) {
printf("unknwon resellun:"
" target %d lun %d tag %d\n",
target, lun, tag);
goto reset;
}
siop_xfer = adp->xfer;
dsa = local_to_PCI((u_long)siop_xfer);
writel(adp->addr + SIOP_DSP,
dsa + sizeof(struct siop_common_xfer) +
Ent_ldsa_reload_dsa);
_wbinv((u_long)siop_xfer, sizeof(*siop_xfer));
return 1;
case A_int_reseltag:
printf("reselect with invalid tag\n");
goto reset;
case A_int_disc:
offset = readb(adp->addr + SIOP_SCRATCHA + 1);
siop_sdp(adp, siop_xfer, xs, offset);
#if 0
/* we start again with no offset */
siop_cmd->saved_offset = SIOP_NOOFFSET;
#endif
_wbinv((u_long)siop_xfer, sizeof(*siop_xfer));
CALL_SCRIPT(Ent_script_sched);
return 1;
case A_int_resfail:
printf("reselect failed\n");
return 1;
case A_int_done:
if (xs == NULL) {
printf("done without command, DSA=0x%lx\n",
local_to_PCI((u_long)adp->xfer));
return 1;
}
/* update resid. */
offset = readb(adp->addr + SIOP_SCRATCHA + 1);
#if 0
/*
* if we got a disconnect between the last data phase
* and the status phase, offset will be 0. In this
* case, siop_cmd->saved_offset will have the proper
* value if it got updated by the controller
*/
if (offset == 0 &&
siop_cmd->saved_offset != SIOP_NOOFFSET)
offset = siop_cmd->saved_offset;
#endif
siop_update_resid(adp, siop_xfer, xs, offset);
goto end;
default:
printf("unknown irqcode %x\n", irqcode);
if (xs) {
xs->error = XS_SELTIMEOUT;
goto end;
}
goto reset;
}
return 1;
}
/* We just should't get there */
panic("siop_intr: I shouldn't be there !");
return 1;
end:
/*
* restart the script now if command completed properly
* Otherwise wait for siop_scsicmd_end(), we may need to cleanup the
* queue
*/
xs->status = ctoh32(siop_xfer->siop_tables.status);
if (xs->status == SCSI_OK)
writel(adp->addr + SIOP_DSP, scriptaddr + Ent_script_sched);
else
restart = 1;
siop_scsicmd_end(adp, xs);
if (restart)
writel(adp->addr + SIOP_DSP, scriptaddr + Ent_script_sched);
return 1;
}
static void
siop_scsicmd_end(struct siop_adapter *adp, struct scsi_xfer *xs)
{
switch(xs->status) {
case SCSI_OK:
xs->error = XS_NOERROR;
break;
case SCSI_BUSY:
case SCSI_CHECK:
case SCSI_QUEUE_FULL:
xs->error = XS_BUSY;
break;
case SCSI_SIOP_NOCHECK:
/*
* don't check status, xs->error is already valid
*/
break;
case SCSI_SIOP_NOSTATUS:
/*
* the status byte was not updated, cmd was
* aborted
*/
xs->error = XS_SELTIMEOUT;
break;
default:
printf("invalid status code %d\n", xs->status);
xs->error = XS_DRIVER_STUFFUP;
}
_inv((u_long)xs->cmd, xs->cmdlen);
if (xs->datalen != 0)
_inv((u_long)xs->data, xs->datalen);
xs->xs_status = XS_STS_DONE;
}
static int
siop_scsi_request(struct siop_adapter *adp, struct scsi_xfer *xs)
{
void *xfer = adp->xfer;
int timo, error;
if (adp->sel_t != xs->target) {
const int free_lo = __arraycount(siop_script);
int i;
void *scriptaddr = (void *)local_to_PCI((u_long)adp->script);
if (adp->sel_t != -1)
adp->script[Ent_resel_targ0 / 4 + adp->sel_t * 2] =
htoc32(0x800c00ff);
for (i = 0; i < __arraycount(lun_switch); i++)
adp->script[free_lo + i] = htoc32(lun_switch[i]);
adp->script[free_lo + E_abs_lunsw_return_Used[0]] =
htoc32(scriptaddr + Ent_lunsw_return);
siop_add_reselsw(adp, xs->target, free_lo);
adp->sel_t = xs->target;
}
restart:
siop_setuptables(adp, xfer, xs);
/* load the DMA maps */
if (xs->datalen != 0)
_inv((u_long)xs->data, xs->datalen);
_wbinv((u_long)xs->cmd, xs->cmdlen);
_wbinv((u_long)xfer, sizeof(struct siop_xfer));
siop_start(adp, xs);
adp->xs = xs;
timo = 0;
while (!(xs->xs_status & XS_STS_DONE)) {
delay(1000);
siop_intr(adp);
if (timo++ > 3000) { /* XXXX: 3sec */
printf("%s: timeout\n", __func__);
return ETIMEDOUT;
}
}
if (xs->error != XS_NOERROR) {
if (xs->error == XS_BUSY || xs->status == SCSI_CHECK)
scsi_request_sense(adp, xs);
switch (xs->error) {
case XS_SENSE:
case XS_SHORTSENSE:
error = scsi_interpret_sense(adp, xs);
break;
case XS_RESOURCE_SHORTAGE:
printf("adapter resource shortage\n");
/* FALLTHROUGH */
case XS_BUSY:
error = EBUSY;
break;
case XS_REQUEUE:
printf("XXXX: requeue...\n");
error = ERESTART;
break;
case XS_SELTIMEOUT:
case XS_TIMEOUT:
error = EIO;
break;
case XS_RESET:
error = EIO;
break;
case XS_DRIVER_STUFFUP:
printf("generic HBA error\n");
error = EIO;
break;
default:
printf("invalid return code from adapter: %d\n",
xs->error);
error = EIO;
break;
}
if (error == ERESTART) {
xs->error = XS_NOERROR;
xs->status = SCSI_OK;
xs->xs_status &= ~XS_STS_DONE;
goto restart;
}
return error;
}
return 0;
}
static void
siop_start(struct siop_adapter *adp, struct scsi_xfer *xs)
{
struct siop_xfer *siop_xfer = adp->xfer;
uint32_t dsa, *script = adp->script;
int slot;
void *scriptaddr = (void *)local_to_PCI((u_long)script);
const int siop_common_xfer_size = sizeof(struct siop_common_xfer);
/*
* The queue management here is a bit tricky: the script always looks
* at the slot from first to last, so if we always use the first
* free slot commands can stay at the tail of the queue ~forever.
* The algorithm used here is to restart from the head when we know
* that the queue is empty, and only add commands after the last one.
* When we're at the end of the queue wait for the script to clear it.
* The best thing to do here would be to implement a circular queue,
* but using only 53c720 features this can be "interesting".
* A mid-way solution could be to implement 2 queues and swap orders.
*/
slot = adp->currschedslot;
/*
* If the instruction is 0x80000000 (JUMP foo, IF FALSE) the slot is
* free. As this is the last used slot, all previous slots are free,
* we can restart from 0.
*/
if (ctoh32(script[(Ent_script_sched_slot0 / 4) + slot * 2]) ==
0x80000000) {
slot = adp->currschedslot = 0;
} else {
slot++;
}
/*
* find a free scheduler slot and load it.
*/
#define SIOP_NSLOTS 0x40
for (; slot < SIOP_NSLOTS; slot++) {
/*
* If cmd if 0x80000000 the slot is free
*/
if (ctoh32(script[(Ent_script_sched_slot0 / 4) + slot * 2]) ==
0x80000000)
break;
}
if (slot == SIOP_NSLOTS) {
/*
* no more free slot, no need to continue. freeze the queue
* and requeue this command.
*/
printf("no mode free slot\n");
return;
}
/* patch scripts with DSA addr */
dsa = local_to_PCI((u_long)siop_xfer);
/* CMD script: MOVE MEMORY addr */
siop_xfer->resel[E_ldsa_abs_slot_Used[0]] =
htoc32(scriptaddr + Ent_script_sched_slot0 + slot * 8);
_wbinv((u_long)siop_xfer, sizeof(*siop_xfer));
/* scheduler slot: JUMP ldsa_select */
script[(Ent_script_sched_slot0 / 4) + slot * 2 + 1] =
htoc32(dsa + siop_common_xfer_size + Ent_ldsa_select);
/*
* Change JUMP cmd so that this slot will be handled
*/
script[(Ent_script_sched_slot0 / 4) + slot * 2] = htoc32(0x80080000);
adp->currschedslot = slot;
/* make sure SCRIPT processor will read valid data */
_wbinv((u_long)script, SIOP_SCRIPT_SIZE);
/* Signal script it has some work to do */
writeb(adp->addr + SIOP_ISTAT, ISTAT_SIGP);
/* and wait for IRQ */
}
static void
siop_xfer_setup(struct siop_xfer *xfer, void *scriptaddr)
{
const int off_msg_in = offsetof(struct siop_common_xfer, msg_in);
const int off_status = offsetof(struct siop_common_xfer, status);
uint32_t dsa, *scr;
int i;
memset(xfer, 0, sizeof(*xfer));
dsa = local_to_PCI((u_long)xfer);
xfer->siop_tables.t_msgout.count = htoc32(1);
xfer->siop_tables.t_msgout.addr = htoc32(dsa);
xfer->siop_tables.t_msgin.count = htoc32(1);
xfer->siop_tables.t_msgin.addr = htoc32(dsa + off_msg_in);
xfer->siop_tables.t_extmsgin.count = htoc32(2);
xfer->siop_tables.t_extmsgin.addr = htoc32(dsa + off_msg_in + 1);
xfer->siop_tables.t_extmsgdata.addr = htoc32(dsa + off_msg_in + 3);
xfer->siop_tables.t_status.count = htoc32(1);
xfer->siop_tables.t_status.addr = htoc32(dsa + off_status);
/* The select/reselect script */
scr = xfer->resel;
for (i = 0; i < __arraycount(load_dsa); i++)
scr[i] = htoc32(load_dsa[i]);
/*
* 0x78000000 is a 'move data8 to reg'. data8 is the second
* octet, reg offset is the third.
*/
scr[Ent_rdsa0 / 4] = htoc32(0x78100000 | ((dsa & 0x000000ff) << 8));
scr[Ent_rdsa1 / 4] = htoc32(0x78110000 | ( dsa & 0x0000ff00 ));
scr[Ent_rdsa2 / 4] = htoc32(0x78120000 | ((dsa & 0x00ff0000) >> 8));
scr[Ent_rdsa3 / 4] = htoc32(0x78130000 | ((dsa & 0xff000000) >> 16));
scr[E_ldsa_abs_reselected_Used[0]] =
htoc32(scriptaddr + Ent_reselected);
scr[E_ldsa_abs_reselect_Used[0]] = htoc32(scriptaddr + Ent_reselect);
scr[E_ldsa_abs_selected_Used[0]] = htoc32(scriptaddr + Ent_selected);
scr[E_ldsa_abs_data_Used[0]] =
htoc32(dsa + sizeof(struct siop_common_xfer) + Ent_ldsa_data);
/* JUMP foo, IF FALSE - used by MOVE MEMORY to clear the slot */
scr[Ent_ldsa_data / 4] = htoc32(0x80000000);
}
static int
siop_add_reselsw(struct siop_adapter *adp, int target, int lunsw_off)
{
uint32_t *script = adp->script;
int reseloff;
void *scriptaddr = (void *)local_to_PCI((u_long)adp->script);
/*
* add an entry to resel switch
*/
reseloff = Ent_resel_targ0 / 4 + target * 2;
if ((ctoh32(script[reseloff]) & 0xff) != 0xff) {
/* it's not free */
printf("siop: resel switch full\n");
return EBUSY;
}
/* JUMP abs_foo, IF target | 0x80; */
script[reseloff + 0] = htoc32(0x800c0080 | target);
script[reseloff + 1] =
htoc32(scriptaddr + lunsw_off * 4 + Ent_lun_switch_entry);
siop_update_scntl3(adp, target, lunsw_off);
return 0;
}
static void
siop_update_scntl3(struct siop_adapter *adp, int target, int lunsw_off)
{
uint32_t *script = adp->script;
/* MOVE target->id >> 24 TO SCNTL3 */
script[lunsw_off + (Ent_restore_scntl3 / 4)] =
htoc32(0x78030000 | ((adp->clock_div >> 16) & 0x0000ff00));
/* MOVE target->id >> 8 TO SXFER */
script[lunsw_off + (Ent_restore_scntl3 / 4) + 2] =
htoc32(0x78050000 | (0x000000000 & 0x0000ff00));
_wbinv((u_long)script, SIOP_SCRIPT_SIZE);
}
/*
* SCSI functions
*/
static int
_scsi_inquire(struct siop_adapter *adp, int t, int l, int buflen, char *buf)
{
struct scsipi_inquiry *cmd = (struct scsipi_inquiry *)adp->cmd;
struct scsipi_inquiry_data *inqbuf =
(struct scsipi_inquiry_data *)adp->data;
struct scsi_xfer xs;
int error;
memset(cmd, 0, sizeof(*cmd));
cmd->opcode = INQUIRY;
cmd->length = SCSIPI_INQUIRY_LENGTH_SCSI2;
memset(inqbuf, 0, sizeof(*inqbuf));
memset(&xs, 0, sizeof(xs));
xs.target = t;
xs.lun = l;
xs.cmdlen = sizeof(*cmd);
xs.cmd = (void *)cmd;
xs.datalen = SCSIPI_INQUIRY_LENGTH_SCSI2;
xs.data = (void *)inqbuf;
xs.error = XS_NOERROR;
xs.resid = xs.datalen;
xs.status = SCSI_OK;
error = siop_scsi_request(adp, &xs);
if (error != 0)
return error;
memcpy(buf, inqbuf, buflen);
return 0;
}
static void
scsi_request_sense(struct siop_adapter *adp, struct scsi_xfer *xs)
{
struct scsi_request_sense *cmd = adp->sense;
struct scsi_sense_data *data = (struct scsi_sense_data *)adp->data;
struct scsi_xfer sense;
int error;
memset(cmd, 0, sizeof(struct scsi_request_sense));
cmd->opcode = SCSI_REQUEST_SENSE;
cmd->length = sizeof(struct scsi_sense_data);
memset(data, 0, sizeof(struct scsi_sense_data));
memset(&sense, 0, sizeof(sense));
sense.target = xs->target;
sense.lun = xs->lun;
sense.cmdlen = sizeof(struct scsi_request_sense);
sense.cmd = (void *)cmd;
sense.datalen = sizeof(struct scsi_sense_data);
sense.data = (void *)data;
sense.error = XS_NOERROR;
sense.resid = sense.datalen;
sense.status = SCSI_OK;
error = siop_scsi_request(adp, &sense);
switch (error) {
case 0:
/* we have a valid sense */
xs->error = XS_SENSE;
return;
case EINTR:
/* REQUEST_SENSE interrupted by bus reset. */
xs->error = XS_RESET;
return;
case EIO:
/* request sense coudn't be performed */
/*
* XXX this isn't quite right but we don't have anything
* better for now
*/
xs->error = XS_DRIVER_STUFFUP;
return;
default:
/* Notify that request sense failed. */
xs->error = XS_DRIVER_STUFFUP;
printf("request sense failed with error %d\n", error);
return;
}
}
/*
* scsi_interpret_sense:
*
* Look at the returned sense and act on the error, determining
* the unix error number to pass back. (0 = report no error)
*
* NOTE: If we return ERESTART, we are expected to haved
* thawed the device!
*
* THIS IS THE DEFAULT ERROR HANDLER FOR SCSI DEVICES.
*/
static int
scsi_interpret_sense(struct siop_adapter *adp, struct scsi_xfer *xs)
{
struct scsi_sense_data *sense;
u_int8_t key;
int error = 0;
uint32_t info;
static const char *error_mes[] = {
"soft error (corrected)",
"not ready", "medium error",
"non-media hardware failure", "illegal request",
"unit attention", "readonly device",
"no data found", "vendor unique",
"copy aborted", "command aborted",
"search returned equal", "volume overflow",
"verify miscompare", "unknown error key"
};
sense = (struct scsi_sense_data *)xs->data;
/* otherwise use the default */
switch (SSD_RCODE(sense->response_code)) {
/*
* Old SCSI-1 and SASI devices respond with
* codes other than 70.
*/
case 0x00: /* no error (command completed OK) */
return 0;
case 0x04: /* drive not ready after it was selected */
if (adp->sd->sc_flags & FLAGS_REMOVABLE)
adp->sd->sc_flags &= ~FLAGS_MEDIA_LOADED;
/* XXX - display some sort of error here? */
return EIO;
case 0x20: /* invalid command */
return EINVAL;
case 0x25: /* invalid LUN (Adaptec ACB-4000) */
return EACCES;
/*
* If it's code 70, use the extended stuff and
* interpret the key
*/
case 0x71: /* delayed error */
key = SSD_SENSE_KEY(sense->flags);
printf(" DEFERRED ERROR, key = 0x%x\n", key);
/* FALLTHROUGH */
case 0x70:
if ((sense->response_code & SSD_RCODE_VALID) != 0)
info = _4btol(sense->info);
else
info = 0;
key = SSD_SENSE_KEY(sense->flags);
switch (key) {
case SKEY_NO_SENSE:
case SKEY_RECOVERED_ERROR:
if (xs->resid == xs->datalen && xs->datalen) {
/*
* Why is this here?
*/
xs->resid = 0; /* not short read */
}
case SKEY_EQUAL:
break;
case SKEY_NOT_READY:
if (adp->sd->sc_flags & FLAGS_REMOVABLE)
adp->sd->sc_flags &= ~FLAGS_MEDIA_LOADED;
if (sense->asc == 0x3A) {
error = ENODEV; /* Medium not present */
} else
error = EIO;
break;
case SKEY_ILLEGAL_REQUEST:
error = EINVAL;
break;
case SKEY_UNIT_ATTENTION:
if (sense->asc == 0x29 &&
sense->ascq == 0x00) {
/* device or bus reset */
return ERESTART;
}
if (adp->sd->sc_flags & FLAGS_REMOVABLE)
adp->sd->sc_flags &= ~FLAGS_MEDIA_LOADED;
if (!(adp->sd->sc_flags & FLAGS_REMOVABLE))
return ERESTART;
error = EIO;
break;
case SKEY_DATA_PROTECT:
error = EROFS;
break;
case SKEY_BLANK_CHECK:
break;
case SKEY_ABORTED_COMMAND:
break;
case SKEY_VOLUME_OVERFLOW:
error = ENOSPC;
break;
default:
error = EIO;
break;
}
/* Print brief(er) sense information */
printf("%s", error_mes[key - 1]);
if ((sense->response_code & SSD_RCODE_VALID) != 0) {
switch (key) {
case SKEY_NOT_READY:
case SKEY_ILLEGAL_REQUEST:
case SKEY_UNIT_ATTENTION:
case SKEY_DATA_PROTECT:
break;
case SKEY_BLANK_CHECK:
printf(", requested size: %d (decimal)",
info);
break;
case SKEY_ABORTED_COMMAND:
printf(", cmd 0x%x, info 0x%x",
xs->cmd->opcode, info);
break;
default:
printf(", info = %d (decimal)", info);
}
}
if (sense->extra_len != 0) {
int n;
printf(", data =");
for (n = 0; n < sense->extra_len; n++)
printf(" %x", sense->csi[n]);
}
printf("\n");
return error;
/*
* Some other code, just report it
*/
default:
printf("Sense Error Code 0x%x",
SSD_RCODE(sense->response_code));
if ((sense->response_code & SSD_RCODE_VALID) != 0) {
struct scsi_sense_data_unextended *usense =
(struct scsi_sense_data_unextended *)sense;
printf(" at block no. %d (decimal)",
_3btol(usense->block));
}
printf("\n");
return EIO;
}
}
static int
scsi_probe(struct siop_adapter *adp)
{
struct scsipi_inquiry_data *inqbuf;
int found, t, l;
uint8_t device;
char buf[SCSIPI_INQUIRY_LENGTH_SCSI2],
product[sizeof(inqbuf->product) + 1];
found = 0;
for (t = 0; t < 8; t++) {
if (t == adp->id)
continue;
for (l = 0; l < 8; l++) {
if (_scsi_inquire(adp, t, l, sizeof(buf), buf) != 0)
continue;
inqbuf = (struct scsipi_inquiry_data *)buf;
device = inqbuf->device & SID_TYPE;
if (device == T_NODEVICE)
continue;
if (device != T_DIRECT &&
device != T_OPTICAL &&
device != T_SIMPLE_DIRECT)
continue;
memset(product, 0, sizeof(product));
strncpy(product, inqbuf->product, sizeof(product) - 1);
printf("sd(%d,%d,[0-7]): <%s>\n", t, l, product);
found++;
}
}
return found;
}
int
scsi_inquire(struct sd_softc *sd, int buflen, void *buf)
{
struct siop_adapter *adp;
int error;
if (sd->sc_bus != 0)
return ENOTSUP;
if (adapt.addr == 0xffffffff)
return ENOENT;
adp = &adapt;
adp->sd = sd;
error = _scsi_inquire(adp, sd->sc_target, sd->sc_lun, buflen, buf);
adp->sd = NULL;
return error;
}
/*
* scsi_mode_sense
* get a sense page from a device
*/
int
scsi_mode_sense(struct sd_softc *sd, int byte2, int page,
struct scsi_mode_parameter_header_6 *data, int len)
{
struct scsi_mode_sense_6 cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.opcode = SCSI_MODE_SENSE_6;
cmd.byte2 = byte2;
cmd.page = page;
cmd.length = len & 0xff;
return scsi_command(sd, (void *)&cmd, sizeof(cmd), (void *)data, len);
}
int
scsi_command(struct sd_softc *sd, void *cmd, int cmdlen, void *data,
int datalen)
{
struct siop_adapter *adp;
struct scsi_xfer xs;
int error;
if (sd->sc_bus != 0)
return ENOTSUP;
if (adapt.addr == 0xffffffff)
return ENOENT;
adp = &adapt;
memcpy(adp->cmd, cmd, cmdlen);
adp->sd = sd;
memset(&xs, 0, sizeof(xs));
xs.target = sd->sc_target;
xs.lun = sd->sc_lun;
xs.cmdlen = cmdlen;
xs.cmd = adp->cmd;
xs.datalen = datalen;
xs.data = adp->data;
xs.error = XS_NOERROR;
xs.resid = datalen;
xs.status = SCSI_OK;
error = siop_scsi_request(adp, &xs);
adp->sd = NULL;
if (error != 0)
return error;
if (datalen > 0)
memcpy(data, adp->data, datalen);
return 0;
}
/*
* Initialize the device.
*/
int
siop_init(int bus, int dev, int func)
{
struct siop_adapter tmp;
struct siop_xfer *xfer;
struct scsipi_generic *cmd;
struct scsi_request_sense *sense;
uint32_t reg;
u_long addr;
uint32_t *script;
int slot, id, i;
void *scriptaddr;
u_char *data;
const int clock_div = 3; /* 53c810 */
slot = PCISlotnum(bus, dev, func);
if (slot == -1)
return ENOENT;
addr = PCIAddress(slot, 1, PCI_MAPREG_TYPE_MEM);
if (addr == 0xffffffff)
return EINVAL;
enablePCI(slot, 0, 1, 1);
script = ALLOC(uint32_t, SIOP_SCRIPT_SIZE);
if (script == NULL)
return ENOMEM;
scriptaddr = (void *)local_to_PCI((u_long)script);
cmd = ALLOC(struct scsipi_generic, SIOP_SCSI_COMMAND_SIZE);
if (cmd == NULL)
return ENOMEM;
sense = ALLOC(struct scsi_request_sense, SIOP_SCSI_COMMAND_SIZE);
if (sense == NULL)
return ENOMEM;
data = ALLOC(u_char, SIOP_SCSI_DATA_SIZE);
if (data == NULL)
return ENOMEM;
xfer = ALLOC(struct siop_xfer, sizeof(struct siop_xfer));
if (xfer == NULL)
return ENOMEM;
siop_xfer_setup(xfer, scriptaddr);
id = readb(addr + SIOP_SCID) & SCID_ENCID_MASK;
/* reset bus */
reg = readb(addr + SIOP_SCNTL1);
writeb(addr + SIOP_SCNTL1, reg | SCNTL1_RST);
delay(100);
writeb(addr + SIOP_SCNTL1, reg);
/* reset the chip */
writeb(addr + SIOP_ISTAT, ISTAT_SRST);
delay(1000);
writeb(addr + SIOP_ISTAT, 0);
/* init registers */
writeb(addr + SIOP_SCNTL0, SCNTL0_ARB_MASK | SCNTL0_EPC | SCNTL0_AAP);
writeb(addr + SIOP_SCNTL1, 0);
writeb(addr + SIOP_SCNTL3, clock_div);
writeb(addr + SIOP_SXFER, 0);
writeb(addr + SIOP_DIEN, 0xff);
writeb(addr + SIOP_SIEN0, 0xff & ~(SIEN0_CMP | SIEN0_SEL | SIEN0_RSL));
writeb(addr + SIOP_SIEN1, 0xff & ~(SIEN1_HTH | SIEN1_GEN));
writeb(addr + SIOP_STEST2, 0);
writeb(addr + SIOP_STEST3, STEST3_TE);
writeb(addr + SIOP_STIME0, (0xb << STIME0_SEL_SHIFT));
writeb(addr + SIOP_SCID, id | SCID_RRE);
writeb(addr + SIOP_RESPID0, 1 << id);
writeb(addr + SIOP_DCNTL, DCNTL_COM);
siop_pci_reset(addr);
/* copy and patch the script */
for (i = 0; i < __arraycount(siop_script); i++)
script[i] = htoc32(siop_script[i]);
for (i = 0; i < __arraycount(E_abs_msgin_Used); i++)
script[E_abs_msgin_Used[i]] =
htoc32(scriptaddr + Ent_msgin_space);
/* start script */
_wbinv((u_long)script, SIOP_SCRIPT_SIZE);
writel(addr + SIOP_DSP, (int)scriptaddr + Ent_reselect);
memset(&tmp, 0, sizeof(tmp));
tmp.id = id;
tmp.clock_div = clock_div;
tmp.addr = addr;
tmp.script = script;
tmp.xfer = xfer;
tmp.cmd = cmd;
tmp.sense = sense;
tmp.data = data;
tmp.currschedslot = 0;
tmp.sel_t = -1;
if (scsi_probe(&tmp) == 0) {
adapt.addr = 0xffffffff;
return ENXIO;
}
adapt = tmp;
return 0;
}