|
|
Plan 9 NeXT
/*
* Adaptec AHA-154[02][BC] Intelligent Host Adapter.
*/
#include "u.h"
#include "lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
enum {
NCtlr = 1,
NTarget = 8, /* targets per controller */
Timeout = 10, /* seconds to wait for things to complete */
Port = 0x330, /* factory defaults: I/O port */
CtlrID = 7, /* adapter SCSI id */
Irq = 11, /* interrupt request level */
};
enum { /* registers */
Rc = 0, /* WO: control */
Rs = 0, /* RO: status */
Rcdo = 1, /* WO: command/data out */
Rdi = 1, /* RO: data in */
Rif = 2, /* RO: interrupt flags */
};
enum { /* Rc */
Scrst = 0x10, /* SCSI bus reset */
Irst = 0x20, /* interrupt reset */
Srst = 0x40, /* soft reset */
Hrst = 0x80, /* hard reset */
};
enum { /* Rs */
Invdcmd = 0x01, /* invalid host adapter command */
Df = 0x04, /* data in port full */
Cdf = 0x08, /* command/data port full */
Idle = 0x10, /* SCSI host adapter idle */
Init = 0x20, /* mailbox initialisation required */
Diagf = 0x40, /* internal diagnostic failure */
Stst = 0x80, /* self testing in progress */
};
enum { /* Rcdo */
Cnop = 0x00, /* no operation */
Cmbinit = 0x01, /* mailbox initialisation */
Cstart = 0x02, /* start SCSI command */
Cbios = 0x03, /* start PC/AT BIOS command */
Cinquiry = 0x04, /* adapter inquiry */
Cmboie = 0x05, /* enable mailbox out available interrupt */
Cselection = 0x06, /* set selection timeout */
Cbuson = 0x07, /* set bus-on time */
Cbusoff = 0x08, /* set bus-off time */
Ctransfer = 0x09, /* set transfer speed */
Cdevices = 0x0A, /* return installed devices */
Cconfiguration = 0x0B, /* return configuration data */
Ctenable = 0x0C, /* enable target mode */
Csetup = 0x0D, /* return setup data */
Cwbuff = 0x1A, /* write adapter channel 2 buffer */
Crbuff = 0x1B, /* read adapter channel 2 buffer */
Cwfifo = 0x1C, /* write adapter FIFO buffer */
Crfifo = 0x1D, /* read adapter FIFO buffer */
Cecho = 0x1F, /* ECHO command data */
Cdiag = 0x20, /* adapter diagnostic */
Coptions = 0x21, /* set host adapter options */
};
enum { /* Rif */
Mbif = 0x01, /* mailbox in full */
Mboa = 0x02, /* mailbox out available */
Hacc = 0x04, /* host adapter command complete */
Scrd = 0x08, /* SCSI reset detected */
Ai = 0x80, /* any interrupt */
};
typedef struct {
uchar cmd; /* command */
uchar msb; /* CCB pointer MSB */
uchar mid; /* CCB pointer MID */
uchar lsb; /* CCB pointer LSB */
} Mbox;
enum { /* mailbox commands */
Mbfree = 0x00, /* mailbox is free */
Mbostart = 0x01, /* start SCSI or adapter command */
Mboabort = 0x02, /* abort SCSI or adapter command */
Mbiok = 0x01, /* CCB completed without error */
Mbiabort = 0x02, /* CCB aborted by host */
Mbinx = 0x03, /* aborted CCB not found */
Mbierror = 0x04, /* CCB completed with error */
};
typedef struct {
uchar op; /* command control block operation code */
uchar ctl; /* address and direction control */
uchar cmdlen; /* SCSI command length */
uchar reqlen; /* request sense allocation length */
uchar datalen[3]; /* data length (MSB, MID, LSB) */
uchar dataptr[3]; /* data pointer (MSB, MID, LSB) */
uchar linkptr[3]; /* link pointer (MSB, MID, LSB) */
uchar linkid; /* command linking identifier */
uchar hastat; /* host adapter status */
uchar tarstat; /* target device status */
uchar reserved[2];
uchar cs[12+0xFF]; /* SCSI command and sense bytes */
} Ccb;
enum { /* op */
OInitiator = 0x00, /* initiator CCB */
OTarget = 0x01, /* target CCB */
Osg = 0x02, /* initiator CCB with scatter/gather */
Ordl = 0x03, /* initiator CCB, residual data length returned */
Osgrdl = 0x04, /* initiator CCB, both of the above */
Obdr = 0x81, /* bus device reset */
};
enum { /* ctl */
CCBdatain = 0x08, /* inbound data transfer, length is checked */
CCBdataout = 0x10, /* outbound data transfer, length is checked */
};
enum { /* hastat */
Eok = 0x00, /* no host adapter detected error */
Etimeout = 0x11, /* selection timeout */
Elength = 0x12, /* data over/under run */
Ebusfree = 0x13, /* unexpected bus free */
Ephase = 0x14, /* target bus phase sequence error */
Eopcode = 0x16, /* invalid CCB opcode */
Elink = 0x17, /* linked CCB does not have same LUN */
Edirection = 0x18, /* invalid target direction received from host */
Eduplicate = 0x19, /* duplicate CCB received in target mode */
Esegment = 0x1A, /* invalid CCB or segment list parameter */
};
enum { /* tarstat */
Sgood = 0x00, /* good status */
Scheck = 0x02, /* check status */
Sbusy = 0x08, /* LUN busy */
};
typedef struct Target Target;
typedef struct Ctlr Ctlr;
struct Target {
Ccb ccb;
ulong paddr; /* physical address of ccb */
uchar *sense; /* address of returned sense data */
int done;
Target *active; /* link on active list */
};
struct Ctlr {
ulong port; /* I/O port */
ulong id; /* adapter SCSI id */
uchar installed[NTarget]; /* installed devices data */
uchar cmd[5]; /* adapter command out */
uchar cmdlen; /* adapter command out length */
uchar data[256]; /* adapter command data in */
uchar datalen; /* adapter command data in length */
Mbox mb[NTarget+NTarget]; /* mailbox out + mailbox in */
Mbox *mbox; /* current mailbox out index into mb */
Mbox *mbix; /* current mailbox in index into mb */
Target target[NTarget];
Target *active; /* link on active list */
};
static Ctlr softctlr[NCtlr];
/*
* Issue a command to the controller. The command and its length is
* contained in ctlr->cmd and ctlr->cmdlen. If any data is to be
* returned, ctlr->datalen should be non-0, and the returned data will
* be placed in ctlr->data.
* If we see Hacc set, bail out, we'll process
* the invalid command at interrupt time.
*/
static void
issue(Ctlr *ctlr)
{
int len;
len = 0;
while(len < ctlr->cmdlen){
if((inb(ctlr->port+Rs) & Cdf) == 0){
outb(ctlr->port+Rcdo, ctlr->cmd[len]);
len++;
}
if(inb(ctlr->port+Rif) & Hacc)
return;
}
if(ctlr->datalen){
len = 0;
while(len < ctlr->datalen){
if(inb(ctlr->port+Rs) & Df){
ctlr->data[len] = inb(ctlr->port+Rdi);
len++;
}
if(inb(ctlr->port+Rif) & Hacc)
return;
}
}
}
static void
scsiwait(Ctlr *cp, Target *tp)
{
ulong start;
int x;
static void interrupt(Ureg*, void*);
x = spllo();
start = m->ticks;
while(TK2SEC(m->ticks - start) < Timeout && tp->done == 0)
;
if(TK2SEC(m->ticks - start) >= Timeout){
print("scsiwait timed out\n");
interrupt(0, cp);
}
splx(x);
}
static int
scsiio(int bus, Scsi *p, int rw)
{
Ctlr *ctlr;
Target *tp;
ushort status;
ulong len, s;
/*
* Wait for the target to become free,
* then set it up. The Adaptec will allow us to
* queue multiple transactions per target, but
* gives no guarantee about ordering, so we just
* allow one per target.
*/
ctlr = &softctlr[bus];
tp = &ctlr->target[p->target];
/*
* If this is a request-sense and we have valid sense data
* from the last command, return it immediately.
* A pox on these weird enum names and the WD33C93A status
* codes.
*/
if(p->cmd.base[0] == ScsiExtsens && tp->sense){
len = 8+tp->sense[7];
memmove(p->data.ptr, tp->sense, len);
p->data.ptr += len;
tp->sense = 0;
return 0x6000;
}
tp->sense = 0;
/*
* Fill in the ccb.
*/
tp->ccb.op = Ordl;
tp->ccb.ctl = (p->target<<5)|p->lun;
len = p->cmd.lim - p->cmd.base;
tp->ccb.cmdlen = len;
memmove(tp->ccb.cs, p->cmd.base, len);
tp->ccb.reqlen = 0xFF;
len = p->data.lim - p->data.base;
tp->ccb.datalen[0] = (len>>16) & 0xFF;
tp->ccb.datalen[1] = (len>>8) & 0xFF;
tp->ccb.datalen[2] = len;
if(len == 0)
tp->ccb.ctl |= CCBdataout|CCBdatain;
else if(rw == ScsiIn)
tp->ccb.ctl |= CCBdatain;
else
tp->ccb.ctl |= CCBdataout;
len = PADDR(p->data.base);
tp->ccb.dataptr[0] = (len>>16) & 0xFF;
tp->ccb.dataptr[1] = (len>>8) & 0xFF;
tp->ccb.dataptr[2] = len;
tp->ccb.linkptr[0] = tp->ccb.linkptr[1] = tp->ccb.linkptr[2] = 0;
tp->ccb.linkid = 0;
tp->ccb.hastat = tp->ccb.tarstat = 0;
tp->ccb.reserved[0] = tp->ccb.reserved[1] = 0;
/*
* Link the target onto the beginning of the
* ctlr active list and start the request.
* The interrupt routine has to be able to take
* requests off the queue in any order.
*/
s = splhi();
tp->done = 0;
tp->active = ctlr->active;
ctlr->active = tp;
ctlr->mbox->msb = (tp->paddr>>16) & 0xFF;
ctlr->mbox->mid = (tp->paddr>>8) & 0xFF;
ctlr->mbox->lsb = tp->paddr & 0xFF;
ctlr->mbox->cmd = Mbostart;
ctlr->cmd[0] = Cstart;
ctlr->cmdlen = 1;
ctlr->datalen = 0;
issue(ctlr);
ctlr->mbox++;
if(ctlr->mbox >= &ctlr->mb[NTarget])
ctlr->mbox = ctlr->mb;
splx(s);
/*
* Wait for the request to complete
* and return the status.
*/
scsiwait(ctlr, tp);
if((status = (tp->ccb.hastat<<8)) == 0)
status = 0x6000;
status |= tp->ccb.tarstat;
len = (tp->ccb.datalen[0]<<16)|(tp->ccb.datalen[1]<<8)|tp->ccb.datalen[2];
p->data.ptr = p->data.lim - len;
/*
* If the command returned sense data, keep a note
* of where it is for a subsequent request-sense command.
*/
if(tp->ccb.tarstat == Scheck && tp->ccb.hastat == Eok)
tp->sense = &tp->ccb.cs[tp->ccb.cmdlen];
/*
* Hack: if there was a non-zero host-status, the target
* status will be 0. Unfortunately, this causes trouble
* higher up as, unlike the 'real' driver, we can't pick
* it off with 'error()'.
* So we have to make sure both halves of the status are
* non-zero.
*/
if((status & 0xFF00) != 0x6000)
status |= 0x20;
return status;
}
static int
aha1542exec(Scsi *p, int rw)
{
Ctlr *ctlr = &softctlr[0];
if(ctlr->port == 0)
return 0x6001;
if(p->target == CtlrID)
return 0x6002;
return p->status = scsiio(0, p, rw);
}
static void
interrupt(Ureg*, void *arg)
{
Ctlr *ctlr;
uchar rif, rs;
Target *tp, **l;
ulong paddr;
ctlr = arg;
/*
* Save and clear the interrupt(s). The only
* interrupts expected are Hacc, which we ignore,
* and Mbif which means something completed.
*/
rif = inb(ctlr->port+Rif);
rs = inb(ctlr->port+Rs);
outb(ctlr->port+Rc, Irst);
if(rif & ~(Ai|Hacc|Mbif))
print("adaptec%d: interrupt #%2.2ux\n", 0, rif);
if((rif & Hacc) && (rs & Invdcmd))
print("adaptec%d: invdcmd #%2.2ux, len %d\n", 0, ctlr->cmd[0], ctlr->cmdlen);
/*
* Look for something in the mail.
* If there is, try to find the recipient from the
* ccb address, take it off the active list and
* wakeup whoever.
*/
while(ctlr->mbix->cmd){
paddr = (ctlr->mbix->msb<<16)|(ctlr->mbix->mid<<8)|ctlr->mbix->lsb;
l = &ctlr->active;
for(tp = *l; tp; tp = tp->active){
if(tp->paddr == paddr)
break;
l = &tp->active;
}
if(tp == 0)
panic("adaptec%d: no target for ccb #%lux\n", 0, paddr);
*l = tp->active;
ctlr->mbix->cmd = 0;
tp->done = 1;
ctlr->mbix++;
if(ctlr->mbix >= &ctlr->mb[NTarget+NTarget])
ctlr->mbix = &ctlr->mb[NTarget];
}
}
static void
issuepollcmd(Ctlr *ctlr)
{
ulong s;
uchar rif, rs;
s = splhi();
issue(ctlr);
while(((rif = inb(ctlr->port+Rif)) & Hacc) == 0)
;
rs = inb(ctlr->port+Rs);
outb(ctlr->port+Rc, Irst);
if((rif & Hacc) && (rs & Invdcmd))
print("adaptec%d: invdcmd #%2.2ux, len %d\n",
0, ctlr->cmd[0], ctlr->cmdlen);
splx(s);
}
static void
reset(Ctlr *ctlr)
{
ulong paddr;
int i;
/*
* Initialise the software controller and set the board
* scanning the mailboxes.
* Need code here to tidy things up if we're
* resetting after being active.
*/
for(i = 0; i < NTarget; i++){
ctlr->target[i].paddr = PADDR(&ctlr->target[i].ccb);
ctlr->mbox = ctlr->mb;
ctlr->mbix = &ctlr->mb[NTarget];
}
ctlr->cmd[0] = Cmbinit;
paddr = PADDR(ctlr->mb);
ctlr->cmd[1] = NTarget;
ctlr->cmd[2] = (paddr>>16) & 0xFF;
ctlr->cmd[3] = (paddr>>8) & 0xFF;
ctlr->cmd[4] = paddr & 0xFF;
ctlr->cmdlen = 5;
ctlr->datalen = 0;
issuepollcmd(ctlr);
}
int (*
aha1542reset(void))(Scsi*, int)
{
Ctlr *ctlr;
/*
* For the moment assume the factory default settings
* and one controller.
*/
ctlr = &softctlr[0];
memset(ctlr, 0, sizeof(Ctlr));
ctlr->port = Port;
/*
* Attempt to hard-reset the board and reset
* the SCSI bus. If the board state doesn't settle to
* idle with mailbox initialisation required, either
* it isn't an Adaptec or it's broken.
* The 154[02]C has an extra I/O port we could use for
* identification, but that wouldn't work on the B.
*/
outb(ctlr->port+Rc, Hrst|Scrst);
delay(500);
if(inb(ctlr->port+Rs) != (Init|Idle))
return 0;
/*
* Get the DMA and IRQ info from the board. This will
* cause an interrupt which we hope doesn't cause any
* trouble because we don't know which one it is yet.
* We have to do this to get the DMA info as it won't
* be set up if the board has the BIOS disabled.
*/
ctlr->cmd[0] = Cconfiguration;
ctlr->cmdlen = 1;
ctlr->datalen = 3;
issuepollcmd(ctlr);
switch(ctlr->data[0]){ /* DMA Arbitration Priority */
case 0x80: /* Channel 7 */
outb(0xD6, 0xC3);
outb(0xD4, 0x03);
break;
case 0x40: /* Channel 6 */
outb(0xD6, 0xC2);
outb(0xD4, 0x02);
break;
case 0x20: /* Channel 5 */
outb(0xD6, 0xC1);
outb(0xD4, 0x01);
break;
case 0x01: /* Channel 0 */
outb(0x0B, 0xC0);
outb(0x0A, 0x00);
break;
default:
/*
* This might be an EISA card (e.g. Buslogic 747)
* which doesn't have ISA DMA compatibility set
* so no DMA channel will show.
* Carry on regardless.
print("adaptec%d: invalid DMA priority #%2.2ux\n", 0, ctlr->data[0]);
return 0;
*/
break;
}
switch(ctlr->data[1]){ /* Interrupt Channel */
case 0x40:
ctlr->data[1] = 15;
break;
case 0x20:
ctlr->data[1] = 14;
break;
case 0x08:
ctlr->data[1] = 12;
break;
case 0x04:
ctlr->data[1] = 11;
break;
case 0x02:
ctlr->data[1] = 10;
break;
case 0x01:
ctlr->data[1] = 9;
break;
default:
print("adaptec%d: invalid irq #%2.2ux\n", 0, ctlr->data[1]);
return 0;
}
setvec(Int0vec+ctlr->data[1], interrupt, ctlr);
ctlr->id = ctlr->data[2] & 0x07;
reset(ctlr);
return aha1542exec;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.