Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Mylex MultiMaster (Buslogic BT-*) SCSI Host Adapter
 * in both 24-bit and 32-bit mode.
 * 24-bit mode works for Adaptec AHA-154xx series too.
 *
 * To do:
 *      allocate more Ccb's as needed, up to NMbox-1;
 *      add nmbox and nccb to Ctlr struct for the above;
 *      64-bit LUN/explicit wide support necessary?
 *
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "ureg.h"
#include "../port/error.h"

#include "../port/sd.h"

#define K2BPA(va, tbdf) PADDR(va)
#define BPA2K(pa, tbdf) KADDR(pa)

extern SDifc sdmylexifc;

enum {                                  /* registers */
        Rcontrol        = 0x00,         /* WO: control register */
        Rstatus         = 0x00,         /* RO: status register */
        Rcpr            = 0x01,         /* WO: command/parameter register */
        Rdatain         = 0x01,         /* RO: data-in register */
        Rinterrupt      = 0x02,         /* RO: interrupt register */
};

enum {                                  /* Rcontrol */
        Rsbus           = 0x10,         /* SCSI Bus Reset */
        Rint            = 0x20,         /* Interrupt Reset */
        Rsoft           = 0x40,         /* Soft Reset */
        Rhard           = 0x80,         /* Hard Reset */
};

enum {                                  /* Rstatus */
        Cmdinv          = 0x01,         /* Command Invalid */
        Dirrdy          = 0x04,         /* Data In Register Ready */
        Cprbsy          = 0x08,         /* Command/Parameter Register Busy */
        Hardy           = 0x10,         /* Host Adapter Ready */
        Inreq           = 0x20,         /* Initialisation Required */
        Dfail           = 0x40,         /* Diagnostic Failure */
        Dact            = 0x80,         /* Diagnostic Active */
};

enum {                                  /* Rcpr */
        Cinitialise     = 0x01,         /* Initialise Mailbox */
        Cstart          = 0x02,         /* Start Mailbox Command */
        Cinquiry        = 0x04,         /* Adapter Inquiry */
        Ceombri         = 0x05,         /* Enable OMBR Interrupt */
        Cinquire        = 0x0B,         /* Inquire Configuration */
        Cextbios        = 0x28,         /* AHA-1542: extended BIOS info. */
        Cmbienable      = 0x29,         /* AHA-1542: Mailbox interface enable */
        Ciem            = 0x81,         /* Initialise Extended Mailbox */
        Ciesi           = 0x8D,         /* Inquire Extended Setup Information */
        Cerrm           = 0x8F,         /* Enable strict round-robin mode */
        Cwide           = 0x96,         /* Wide CCB */
};

enum {                                  /* Rinterrupt */
        Imbl            = 0x01,         /* Incoming Mailbox Loaded */
        Mbor            = 0x02,         /* Mailbox Out Ready */
        Cmdc            = 0x04,         /* Command Complete */
        Rsts            = 0x08,         /* SCSI Reset State */
        Intv            = 0x80,         /* Interrupt Valid */
};

typedef struct Mbox24 Mbox24;
struct Mbox24 {
        uchar   code;                   /* action/completion code */
        uchar   ccb[3];                 /* CCB pointer (MSB, ..., LSB) */
};

typedef struct Mbox32 Mbox32;
struct Mbox32 {
        uchar   ccb[4];                 /* CCB pointer (LSB, ..., MSB) */
        uchar   btstat;                 /* BT-7[45]7[SD] status */
        uchar   sdstat;                 /* SCSI device status */
        uchar   pad;
        uchar   code;                   /* action/completion code */
};

enum {                                  /* mailbox commands */
        Mbfree          = 0x00,         /* Mailbox not in use */

        Mbostart        = 0x01,         /* Start a mailbox command */
        Mboabort        = 0x02,         /* Abort a mailbox command */

        Mbiok           = 0x01,         /* CCB completed without error */
        Mbiabort        = 0x02,         /* CCB aborted at request of host */
        Mbinx           = 0x03,         /* Aborted CCB not found */
        Mbierror        = 0x04,         /* CCB completed with error */
};

typedef struct Ccb24 Ccb24;
typedef struct Ccb32 Ccb32;
typedef union Ccb Ccb;

typedef struct Ccb24 {
        uchar   opcode;                 /* Operation code */
        uchar   datadir;                /* Data direction control */
        uchar   cdblen;                 /* Length of CDB */
        uchar   senselen;               /* Length of sense area */
        uchar   datalen[3];             /* Data length (MSB, ..., LSB) */
        uchar   dataptr[3];             /* Data pointer (MSB, ..., LSB) */
        uchar   linkptr[3];             /* Link pointer (MSB, ..., LSB) */
        uchar   linkid;                 /* command linking identifier */
        uchar   btstat;                 /* BT-* adapter status */
        uchar   sdstat;                 /* SCSI device status */
        uchar   reserved[2];            /* */
        uchar   cs[12+0xFF];            /* Command descriptor block + Sense */

        void*   data;                   /* buffer if address > 24-bits */

        Rendez;
        int     done;                   /* command completed */

        Ccb*    ccb;                    /* link on free list */
} Ccb24;


typedef struct Ccb32 {
        uchar   opcode;                 /* Operation code */
        uchar   datadir;                /* Data direction control */
        uchar   cdblen;                 /* Length of CDB */
        uchar   senselen;               /* Length of sense area */
        uchar   datalen[4];             /* Data length (LSB, ..., MSB) */
        uchar   dataptr[4];             /* Data pointer (LSB, ..., MSB) */
        uchar   reserved[2];
        uchar   btstat;                 /* BT-* adapter status */
        uchar   sdstat;                 /* SCSI device status */
        uchar   targetid;               /* Target ID */
        uchar   luntag;                 /* LUN & tag */
        uchar   cdb[12];                /* Command descriptor block */
        uchar   ccbctl;                 /* CCB control */
        uchar   linkid;                 /* command linking identifier */
        uchar   linkptr[4];             /* Link pointer (LSB, ..., MSB) */
        uchar   senseptr[4];            /* Sense pointer (LSB, ..., MSB) */
        uchar   sense[0xFF];            /* Sense bytes */

        Rendez;
        int     done;                   /* command completed */

        Ccb*    ccb;                    /* link on free list */
} Ccb32;

typedef union Ccb {
        Ccb24;
        Ccb32;
} Ccb;

enum {                                  /* opcode */
        OInitiator      = 0x00,         /* initiator CCB */
        Ordl            = 0x03,         /* initiator CCB with
                                         * residual data length returned
                                         */
};

enum {                                  /* datadir */
        CCBdatain       = 0x08,         /* inbound, length is checked */
        CCBdataout      = 0x10,         /* outbound, length is checked */
};

enum {                                  /* btstat */
        Eok             = 0x00,         /* normal completion with no errors */
};

enum {                                  /* luntag */
        TagEnable       = 0x20,         /* Tag enable */
        SQTag           = 0x00,         /* Simple Queue Tag */
        HQTag           = 0x40,         /* Head of Queue Tag */
        OQTag           = 0x80,         /* Ordered Queue Tag */
};

enum {                                  /* CCB control */
        NoDisc          = 0x08,         /* No disconnect */
        NoUnd           = 0x10,         /* No underrrun error report */
        NoData          = 0x20,         /* No data transfer */
        NoStat          = 0x40,         /* No CCB status if zero */
        NoIntr          = 0x80,         /* No Interrupts */
};

typedef struct Ctlr Ctlr;
struct Ctlr {
        int     port;                   /* I/O port */
        int     id;                     /* adapter SCSI id */
        int     bus;                    /* 24 or 32 -bit */
        int     irq;
        int     wide;
        Pcidev* pcidev;
        SDev*   sdev;
        int     spurious;

        Lock    issuelock;

        Lock    ccblock;
        QLock   ccbq;
        Rendez  ccbr;

        Lock    mboxlock;
        void*   mb;                     /* mailbox out + mailbox in */
        int     mbox;                   /* current mailbox out index into mb */
        int     mbix;                   /* current mailbox in index into mb */

        Lock    cachelock;
        Ccb*    ccb;                    /* list of free Ccb's */
        Ccb**   cache;                  /* last completed Ccb */
};

/*
 * The number of mailboxes should be a multiple of 8 (4 for Mbox32)
 * to ensure the boundary between the out and in mailboxes doesn't
 * straddle a cache-line boundary.
 * The number of Ccb's should be less than the number of mailboxes to
 * ensure no queueing is necessary on mailbox allocation.
 */
enum {
        NMbox           = 8*8,          /* number of Mbox's */
        NCcb            = NMbox-1,      /* number of Ccb's */
};

#define PADDR24(a, n)   ((PADDR(a)+(n)) <= (1<<24))

static void
ccbfree(Ctlr* ctlr, Ccb* ccb)
{
        lock(&ctlr->ccblock);
        if(ctlr->bus == 24)
                ((Ccb24*)ccb)->ccb = ctlr->ccb;
        else
                ((Ccb32*)ccb)->ccb = ctlr->ccb;
        if(ctlr->ccb == nil)
                wakeup(&ctlr->ccbr);
        ctlr->ccb = ccb;
        unlock(&ctlr->ccblock);
}

static int
ccbavailable(void* a)
{
        return ((Ctlr*)a)->ccb != nil;
}

static Ccb*
ccballoc(Ctlr* ctlr)
{
        Ccb *ccb;

        for(;;){
                lock(&ctlr->ccblock);
                if((ccb = ctlr->ccb) != nil){
                        if(ctlr->bus == 24)
                                 ctlr->ccb = ((Ccb24*)ccb)->ccb;
                        else
                                 ctlr->ccb = ((Ccb32*)ccb)->ccb;
                        unlock(&ctlr->ccblock);
                        break;
                }

                unlock(&ctlr->ccblock);
                qlock(&ctlr->ccbq);
                if(waserror()){
                        qunlock(&ctlr->ccbq);
                        continue;
                }
                sleep(&ctlr->ccbr, ccbavailable, ctlr);
                qunlock(&ctlr->ccbq);
                poperror();
        }

        return ccb;
}

static int
done24(void* arg)
{
        return ((Ccb24*)arg)->done;
}

static int
mylex24rio(SDreq* r)
{
        ulong p;
        Ctlr *ctlr;
        Ccb24 *ccb;
        Mbox24 *mb;
        uchar *data, lun, *sense;
        int d, n, btstat, sdstat, target;

        ctlr = r->unit->dev->ctlr;
        target = r->unit->subno;
        lun = (r->cmd[1]>>5) & 0x07;

        /*
         * Ctlr->cache holds the last completed Ccb for this target if it
         * returned 'check condition'.
         * If this command is a request-sense and there is valid sense data
         * from the last completed Ccb, return it immediately.
         */
        lock(&ctlr->cachelock);
        if((ccb = ctlr->cache[target]) != nil){
                ctlr->cache[target] = nil;
                if(r->cmd[0] == 0x03
                && ccb->sdstat == SDcheck && lun == ((ccb->cs[1]>>5) & 0x07)){
                        unlock(&ctlr->cachelock);
                        if(r->dlen){
                                sense = &ccb->cs[ccb->cdblen];
                                n = 8+sense[7];
                                if(n > r->dlen)
                                        n = r->dlen;
                                memmove(r->data, sense, n);
                                r->rlen = n;
                        }
                        ccbfree(ctlr, (Ccb*)ccb);
                        return SDok;
                }
        }
        unlock(&ctlr->cachelock);
        if(ccb == nil)
                ccb = ccballoc(ctlr);

        /*
         * Check if the transfer is to memory above the 24-bit limit the
         * controller can address. If it is, try to allocate a temporary
         * buffer as a staging area.
         */
        n = r->dlen;
        if(n && !PADDR24(r->data, n)){
                data = mallocz(n, 0);
                if(data == nil || !PADDR24(data, n)){
                        if(data != nil){
                                free(data);
                                ccb->data = nil;
                        }
                        ccbfree(ctlr, (Ccb*)ccb);
                        return SDmalloc;
                }
                if(r->write)
                        memmove(data, r->data, n);
                ccb->data = r->data;
        }
        else
                data = r->data;

        /*
         * Fill in the ccb.
         */
        ccb->opcode = Ordl;

        ccb->datadir = (target<<5)|lun;
        if(n == 0)
                ccb->datadir |= CCBdataout|CCBdatain;
        else if(!r->write)
                ccb->datadir |= CCBdatain;
        else
                ccb->datadir |= CCBdataout;

        ccb->cdblen = r->clen;
        ccb->senselen = 0xFF;

        ccb->datalen[0] = n>>16;
        ccb->datalen[1] = n>>8;
        ccb->datalen[2] = n;
        if(data == nil)
                p = 0;
        else
                p = PADDR(data);
        ccb->dataptr[0] = p>>16;
        ccb->dataptr[1] = p>>8;
        ccb->dataptr[2] = p;

        ccb->linkptr[0] = ccb->linkptr[1] = ccb->linkptr[2] = 0;
        ccb->linkid = 0;
        ccb->btstat = ccb->sdstat = 0;
        ccb->reserved[0] = ccb->reserved[1] = 0;

        memmove(ccb->cs, r->cmd, r->clen);

        /*
         * There's one more mbox than there there is
         * ccb so there is always one free.
         */
        lock(&ctlr->mboxlock);
        mb = ctlr->mb;
        mb += ctlr->mbox;
        p = PADDR(ccb);
        mb->ccb[0] = p>>16;
        mb->ccb[1] = p>>8;
        mb->ccb[2] = p;
        mb->code = Mbostart;
        ctlr->mbox++;
        if(ctlr->mbox >= NMbox)
                ctlr->mbox = 0;

        /*
         * This command does not require Hardy
         * and doesn't generate a Cmdc interrupt.
         */
        ccb->done = 0;
        outb(ctlr->port+Rcpr, Cstart);
        unlock(&ctlr->mboxlock);

        /*
         * Wait for the request to complete and return the status.
         * Since the buffer is not reference counted cannot return
         * until the DMA is done writing into the buffer so the caller
         * cannot free the buffer prematurely.
         */
        while(waserror())
                ;
        sleep(ccb, done24, ccb);
        poperror();

        /*
         * Save the status and patch up the number of
         * bytes actually transferred.
         * There's a firmware bug on some 956C controllers
         * which causes the return count from a successful
         * READ CAPACITY not be updated, so fix it here.
         */
        sdstat = ccb->sdstat;
        btstat = ccb->btstat;

        d = ccb->datalen[0]<<16;
        d |= ccb->datalen[1]<<8;
        d |= ccb->datalen[2];
        if(ccb->cs[0] == 0x25 && sdstat == SDok)
                d = 0;
        n -= d;
        r->rlen = n;

        /*
         * Tidy things up if a staging area was used for the data,
         */
        if(ccb->data != nil){
                if(sdstat == SDok && btstat == 0 && !r->write)
                        memmove(ccb->data, data, n);
                free(data);
                ccb->data = nil;
        }

        /*
         * If there was a check-condition, save the
         * ccb for a possible request-sense command.
         */
        if(sdstat == SDcheck){
                if(r->flags & SDnosense){
                        lock(&ctlr->cachelock);
                        if(ctlr->cache[target])
                                ccbfree(ctlr, ctlr->cache[target]);
                        ctlr->cache[target] = (Ccb*)ccb;
                        unlock(&ctlr->cachelock);
                        return SDcheck;
                }
                sense = &ccb->cs[ccb->cdblen];
                n = 8+sense[7];
                if(n > sizeof(r->sense)-1)
                        n = sizeof(r->sense)-1;
                memmove(r->sense, sense, n);
                r->flags |= SDvalidsense;
        }
        ccbfree(ctlr, (Ccb*)ccb);

        if(btstat){
                if(btstat == 0x11)
                        return SDtimeout;
                return SDeio;
        }
        return sdstat;
}

static void
mylex24interrupt(Ureg*, void* arg)
{
        ulong pa;
        Ctlr *ctlr;
        Ccb24 *ccb;
        Mbox24 *mb, *mbox;
        int port, rinterrupt, rstatus;

        ctlr = arg;
        port = ctlr->port;

        /*
         * Save and clear the interrupt(s). The only
         * interrupts expected are Cmdc, which is ignored,
         * and Imbl which means something completed.
         * There's one spurious interrupt left over from
         * initialisation, ignore it.
         */
        rinterrupt = inb(port+Rinterrupt);
        rstatus = inb(port+Rstatus);
        outb(port+Rcontrol, Rint);
        if((rinterrupt & ~(Cmdc|Imbl)) != Intv && ctlr->spurious++)
                print("%s: interrupt 0x%2.2ux\n",
                        ctlr->sdev->name, rinterrupt);
        if((rinterrupt & Cmdc) && (rstatus & Cmdinv))
                print("%s: command invalid\n", ctlr->sdev->name);

        /*
         * Look for something in the mail.
         * If there is, save the status, free the mailbox
         * and wakeup whoever.
         */
        mb = ctlr->mb;
        for(mbox = &mb[ctlr->mbix]; mbox->code; mbox = &mb[ctlr->mbix]){
                pa = (mbox->ccb[0]<<16)|(mbox->ccb[1]<<8)|mbox->ccb[2];
                ccb = BPA2K(pa, BUSUNKNOWN);
                mbox->code = 0;
                ccb->done = 1;
                wakeup(ccb);

                ctlr->mbix++;
                if(ctlr->mbix >= NMbox+NMbox)
                        ctlr->mbix = NMbox;
        }
}

static int
done32(void* arg)
{
        return ((Ccb32*)arg)->done;
}

static int
mylex32rio(SDreq* r)
{
        ulong p;
        uchar lun;
        Ctlr *ctlr;
        Ccb32 *ccb;
        Mbox32 *mb;
        int d, n, btstat, sdstat, target;

        ctlr = r->unit->dev->ctlr;
        target = r->unit->subno;
        lun = (r->cmd[1]>>5) & 0x07;

        /*
         * Ctlr->cache holds the last completed Ccb for this target if it
         * returned 'check condition'.
         * If this command is a request-sense and there is valid sense data
         * from the last completed Ccb, return it immediately.
         */
        lock(&ctlr->cachelock);
        if((ccb = ctlr->cache[target]) != nil){
                ctlr->cache[target] = nil;
                if(r->cmd[0] == 0x03
                && ccb->sdstat == SDcheck && lun == (ccb->luntag & 0x07)){
                        unlock(&ctlr->cachelock);
                        if(r->dlen){
                                n = 8+ccb->sense[7];
                                if(n > r->dlen)
                                        n = r->dlen;
                                memmove(r->data, ccb->sense, n);
                                r->rlen = n;
                        }
                        ccbfree(ctlr, (Ccb*)ccb);
                        return SDok;
                }
        }
        unlock(&ctlr->cachelock);
        if(ccb == nil)
                ccb = ccballoc(ctlr);

        /*
         * Fill in the ccb.
         */
        ccb->opcode = Ordl;

        n = r->dlen;
        if(n == 0)
                ccb->datadir = CCBdataout|CCBdatain;
        else if(!r->write)
                ccb->datadir = CCBdatain;
        else
                ccb->datadir = CCBdataout;

        ccb->cdblen = r->clen;

        ccb->datalen[0] = n;
        ccb->datalen[1] = n>>8;
        ccb->datalen[2] = n>>16;
        ccb->datalen[3] = n>>24;
        if(r->data == nil)
                p = 0;
        else
                p = PADDR(r->data);
        ccb->dataptr[0] = p;
        ccb->dataptr[1] = p>>8;
        ccb->dataptr[2] = p>>16;
        ccb->dataptr[3] = p>>24;

        ccb->targetid = target;
        ccb->luntag = lun;
        if(r->unit->inquiry[7] & 0x02)
                if(ctlr->wide)
                        ccb->datadir |= SQTag|TagEnable;
                else
                        ccb->luntag |= SQTag|TagEnable;
        memmove(ccb->cdb, r->cmd, r->clen);
        ccb->btstat = ccb->sdstat = 0;
        ccb->ccbctl = 0;

        /*
         * There's one more mbox than there there is
         * ccb so there is always one free.
         */
        lock(&ctlr->mboxlock);
        mb = ctlr->mb;
        mb += ctlr->mbox;
        p = PADDR(ccb);
        mb->ccb[0] = p;
        mb->ccb[1] = p>>8;
        mb->ccb[2] = p>>16;
        mb->ccb[3] = p>>24;
        mb->code = Mbostart;
        ctlr->mbox++;
        if(ctlr->mbox >= NMbox)
                ctlr->mbox = 0;

        /*
         * This command does not require Hardy
         * and doesn't generate a Cmdc interrupt.
         */
        ccb->done = 0;
        outb(ctlr->port+Rcpr, Cstart);
        unlock(&ctlr->mboxlock);

        /*
         * Wait for the request to complete and return the status.
         * Since the buffer is not reference counted cannot return
         * until the DMA is done writing into the buffer so the caller
         * cannot free the buffer prematurely.
         */
        while(waserror())
                ;
        sleep(ccb, done32, ccb);
        poperror();

        /*
         * Save the status and patch up the number of
         * bytes actually transferred.
         * There's a firmware bug on some 956C controllers
         * which causes the return count from a successful
         * READ CAPACITY not to be updated, so fix it here.
         */
        sdstat = ccb->sdstat;
        btstat = ccb->btstat;

        d = ccb->datalen[0];
        d |= (ccb->datalen[1]<<8);
        d |= (ccb->datalen[2]<<16);
        d |= (ccb->datalen[3]<<24);
        if(ccb->cdb[0] == 0x25 && sdstat == SDok)
                d = 0;
        n -= d;
        r->rlen = n;

        /*
         * If there was a check-condition, save the
         * ccb for a possible request-sense command.
         */
        if(sdstat == SDcheck){
                if(r->flags & SDnosense){
                        lock(&ctlr->cachelock);
                        if(ctlr->cache[target])
                                ccbfree(ctlr, ctlr->cache[target]);
                        ctlr->cache[target] = (Ccb*)ccb;
                        unlock(&ctlr->cachelock);
                        return SDcheck;
                }
                n = 8+ccb->sense[7];
                if(n > sizeof(r->sense)-1)
                        n = sizeof(r->sense)-1;
                memmove(r->sense, ccb->sense, n);
                r->flags |= SDvalidsense;
        }
        ccbfree(ctlr, (Ccb*)ccb);

        if(btstat){
                if(btstat == 0x11)
                        return SDtimeout;
                return SDeio;
        }
        return sdstat;
}

static void
mylex32interrupt(Ureg*, void* arg)
{
        ulong pa;
        Ctlr *ctlr;
        Ccb32 *ccb;
        Mbox32 *mb, *mbox;
        int port, rinterrupt, rstatus;

        ctlr = arg;
        port = ctlr->port;

        /*
         * Save and clear the interrupt(s). The only
         * interrupts expected are Cmdc, which is ignored,
         * and Imbl which means something completed.
         * There's one spurious interrupt left over from
         * initialisation, ignore it.
         * In order to share PCI IRQs, just ignore spurious interrupts.
         */
        rinterrupt = inb(port+Rinterrupt);
        rstatus = inb(port+Rstatus);
        outb(port+Rcontrol, Rint);
        if(0 && (rinterrupt & ~(Cmdc|Imbl)) != Intv && ctlr->spurious++)
                print("%s: interrupt 0x%2.2ux\n",
                        ctlr->sdev->name, rinterrupt);
        if((rinterrupt & Cmdc) && (rstatus & Cmdinv))
                print("%s: command invalid\n", ctlr->sdev->name);

        /*
         * Look for something in the mail.
         * If there is, free the mailbox and wakeup whoever.
         */
        mb = ctlr->mb;
        for(mbox = &mb[ctlr->mbix]; mbox->code; mbox = &mb[ctlr->mbix]){
                pa = (mbox->ccb[3]<<24)
                    |(mbox->ccb[2]<<16)
                    |(mbox->ccb[1]<<8)
                    |mbox->ccb[0];
                if(ctlr->pcidev)
                        ccb = BPA2K(pa, ctlr->pcidev->tbdf);
                else
                        ccb = BPA2K(pa, BUSUNKNOWN);
                mbox->code = 0;
                ccb->done = 1;
                wakeup(ccb);

                ctlr->mbix++;
                if(ctlr->mbix >= NMbox+NMbox)
                        ctlr->mbix = NMbox;
        }
}

static int
mylexrio(SDreq* r)
{
        int subno;
        Ctlr *ctlr;

        subno = r->unit->subno;
        ctlr = r->unit->dev->ctlr;
        if(subno == ctlr->id || (!ctlr->wide && subno >= 8))
                r->status = SDtimeout;
        else if(ctlr->bus == 24)
                r->status = mylex24rio(r);
        else
                r->status = mylex32rio(r);
        return r->status;
}

/*
 * Issue a command to a controller. The command and its length is
 * contained in cmd and cmdlen. If any data is to be
 * returned, datalen should be non-zero, and the returned data
 * will be placed in data.
 * If Cmdc is set, bail out, the invalid command will be handled
 * when the interrupt is processed.
 */
static void
issueio(int port, uchar* cmd, int cmdlen, uchar* data, int datalen)
{
        int len;

        if(cmd[0] != Cstart && cmd[0] != Ceombri){
                while(!(inb(port+Rstatus) & Hardy))
                        ;
        }
        outb(port+Rcpr, cmd[0]);

        len = 1;
        while(len < cmdlen){
                if(!(inb(port+Rstatus) & Cprbsy)){
                        outb(port+Rcpr, cmd[len]);
                        len++;
                }
                if(inb(port+Rinterrupt) & Cmdc)
                        return;
        }

        if(datalen){
                len = 0;
                while(len < datalen){
                        if(inb(port+Rstatus) & Dirrdy){
                                data[len] = inb(port+Rdatain);
                                len++;
                        }
                        if(inb(port+Rinterrupt) & Cmdc)
                                return;
                }
        }
}

/*
 * Issue a command to a controller, wait for it to complete then
 * try to reset the interrupt. Should only be called at initialisation.
 */
static int
issue(Ctlr* ctlr, uchar* cmd, int cmdlen, uchar* data, int datalen)
{
        int port;
        uchar rinterrupt, rstatus;
        static Lock mylexissuelock;

        port = ctlr->port;

        ilock(&ctlr->issuelock);
        issueio(port, cmd, cmdlen, data, datalen);

        while(!((rinterrupt = inb(port+Rinterrupt)) & Cmdc))
                ;

        rstatus = inb(port+Rstatus);
        outb(port+Rcontrol, Rint);
        iunlock(&ctlr->issuelock);

        if((rinterrupt & Cmdc) && (rstatus & Cmdinv))
                return 0;
        return 1;
}

static SDev*
mylexprobe(int port, int irq)
{
        SDev *sdev;
        Ctlr *ctlr;
        uchar cmd[6], data[256];
        int clen, dlen, timeo;
        static int count;

        if(ioalloc(port, 0x3, 0, "mylex") < 0)
                return nil;
        ctlr = nil;
        sdev = nil;
        /*
         * 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 a compatible board or it's broken.
         * If the controller has SCAM set this can take a while.
         */
        if(getconf("*noscsireset") != nil)
                outb(port+Rcontrol, Rhard);
        else
                outb(port+Rcontrol, Rhard|Rsbus);
        for(timeo = 0; timeo < 100; timeo++){
                if(inb(port+Rstatus) == (Inreq|Hardy))
                        break;
                delay(100);
        }
        if(inb(port+Rstatus) != (Inreq|Hardy)){
buggery:
                if(ctlr != nil)
                        free(ctlr);
                if (sdev != nil)
                        free(sdev);
                iofree(port);
                return nil;
        }

        if((ctlr = malloc(sizeof(Ctlr))) == nil)
                goto buggery;
        ctlr->port = port;
        ctlr->irq = irq;
        ctlr->bus = 24;
        ctlr->wide = 0;

        /*
         * Try to determine if this is a 32-bit MultiMaster controller
         * by attempting to obtain the extended inquiry information;
         * this command is not implemented on Adaptec 154xx
         * controllers. If successful, the first byte of the returned
         * data is the host adapter bus type, 'E' for 32-bit EISA,
         * PCI and VLB buses.
         */
        cmd[0] = Ciesi;
        cmd[1] = 14;
        clen = 2;
        dlen = 256;
        if(issue(ctlr, cmd, clen, data, dlen)){
                if(data[0] == 'E')
                        ctlr->bus = 32;
                ctlr->wide = data[0x0D] & 0x01;
                /*
                 * devsd doesn't pass us the `spec' argument, so
                 * we'll assume that sd0 goes to the first scsi host
                 * adapter found, etc.
                 */
                print("#S/sd%d: mylex SCSI: port 0x%ux: %d-bit, ",
                        count++, ctlr->port, ctlr->bus);
                if (ctlr->wide)
                        print("wide\n");
                else
                        print("narrow\n");
        }
        else{
                /*
                 * Inconceivable though it may seem, a hard controller reset
                 * is necessary here to clear out the command queue. Every
                 * board seems to lock-up in a different way if you give an
                 * invalid command and then try to clear out the
                 * command/parameter and/or data-in register.
                 * Soft reset doesn't do the job either. Fortunately no
                 * serious initialisation has been done yet so there's nothing
                 * to tidy up.
                 */
                outb(port+Rcontrol, Rhard);
                for(timeo = 0; timeo < 100; timeo++){
                        if(inb(port+Rstatus) == (Inreq|Hardy))
                                break;
                        delay(100);
                }
                if(inb(port+Rstatus) != (Inreq|Hardy))
                        goto buggery;
        }

        /*
         * If the BIOS is enabled on the AHA-1542C/CF and BIOS options for
         * support of drives > 1Gb, dynamic scanning of the SCSI bus or more
         * than 2 drives under DOS 5.0 are enabled, the BIOS disables
         * accepting Cmbinit to protect against running with drivers which
         * don't support those options. In order to unlock the interface it
         * is necessary to read a lock-code using Cextbios and write it back
         * using Cmbienable; the lock-code is non-zero.
         */
        cmd[0] = Cinquiry;
        clen = 1;
        dlen = 4;
        if(issue(ctlr, cmd, clen, data, dlen) == 0)
                goto buggery;
        if(data[0] >= 0x43){
                cmd[0] = Cextbios;
                clen = 1;
                dlen = 2;
                if(issue(ctlr, cmd, clen, data, dlen) == 0)
                        goto buggery;

                /*
                 * Lock-code returned in data[1]. If it's non-zero write
                 * it back along with bit 0 of byte 0 cleared to enable
                 * mailbox initialisation.
                 */
                if(data[1]){
                        cmd[0] = Cmbienable;
                        cmd[1] = 0;
                        cmd[2] = data[1];
                        clen = 3;
                        if(issue(ctlr, cmd, clen, 0, 0) == 0)
                                goto buggery;
                }
        }

        /*
         * Get the id, DMA and IRQ info from the board. This will
         * cause an interrupt which will hopefully not cause any
         * trouble because the interrupt number isn't known yet.
         * This is necessary as the DMA won't be set up if the
         * board has the BIOS disabled.
         *
         * If the IRQ is already known, this must be a 32-bit PCI
         * or EISA card, in which case the returned DMA and IRQ can
         * be ignored.
         */
        cmd[0] = Cinquire;
        clen = 1;
        dlen = 3;
        if(issue(ctlr, cmd, clen, data, dlen) == 0)
                goto buggery;

        ctlr->id = data[2] & 0x07;
        if(ctlr->irq < 0){
                switch(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:
                        if(ctlr->bus == 24)
                                goto buggery;
                        break;
                }
        
                switch(data[1]){                /* Interrupt Channel */
                case 0x40:
                        ctlr->irq = 15;
                        break;
                case 0x20:
                        ctlr->irq = 14;
                        break;
                case 0x08:
                        ctlr->irq = 12;
                        break;
                case 0x04:
                        ctlr->irq = 11;
                        break;
                case 0x02:
                        ctlr->irq = 10;
                        break;
                case 0x01:
                        ctlr->irq = 9;
                        break;
                default:
                        goto buggery;
                }
        }

        if((sdev = malloc(sizeof(SDev))) == nil)
                goto buggery;
        sdev->ifc = &sdmylexifc;
        sdev->ctlr = ctlr;
        sdev->idno = '0';
        ctlr->sdev = sdev;
        if(!ctlr->wide)
                sdev->nunit = 8;
        else
                sdev->nunit = 16;

        return sdev;
}

static int mylexport[8] = {
        0x330, 0x334, 0x230, 0x234, 0x130, 0x134, 0x000, 0x000,
};

static SDev*
mylexpnp(void)
{
        Pcidev *p;
        Ctlr *ctlr;
        ISAConf isa;
        int cfg, ctlrno, i, x;
        SDev *sdev, *head, *tail;

        p = nil;
        head = tail = nil;
        while(p = pcimatch(p, 0x104B, 0)){
                if((sdev = mylexprobe(p->mem[0].bar & ~0x01, p->intl)) == nil)
                        continue;

                ctlr = sdev->ctlr;
                ctlr->pcidev = p;

                if(head != nil)
                        tail->next = sdev;
                else
                        head = sdev;
                tail = sdev;
        }

        if(strncmp(KADDR(0xFFFD9), "EISA", 4) == 0){
                for(cfg = 0x1000; cfg < MaxEISA*0x1000; cfg += 0x1000){
                        x = 0;
                        for(i = 0; i < 4; i++)
                                x |= inb(cfg+CfgEISA+i)<<(i*8);
                        if(x != 0x0142B30A && x != 0x0242B30A)
                                continue;
        
                        x = inb(cfg+0xC8C);
                        if((sdev = mylexprobe(mylexport[x & 0x07], -1)) == nil)
                                continue;
        
                        if(head != nil)
                                tail->next = sdev;
                        else
                                head = sdev;
                        tail = sdev;
                }
        }

        for(ctlrno = 0; ctlrno < 4; ctlrno++){
                memset(&isa, 0, sizeof(isa));
                if(!isaconfig("scsi", ctlrno, &isa))
                        continue;
                if(strcmp(isa.type, "aha1542"))
                        continue;
                if((sdev = mylexprobe(isa.port, -1)) == nil)
                        continue;

                if(head != nil)
                        tail->next = sdev;
                else
                        head = sdev;
                tail = sdev;
        }

        return head;
}

static int
mylex24enable(Ctlr* ctlr)
{
        ulong p;
        Ccb24 *ccb, *ccbp;
        uchar cmd[6], *v;
        int len;

        len = (sizeof(Mbox24)*NMbox*2)+(sizeof(Ccb24)*NCcb);
        v = xspanalloc(len, 32, 0);

        if(!PADDR24(ctlr, sizeof(Ctlr)) || !PADDR24(v, len))
                return 0;

        ctlr->mb = v;
        v += sizeof(Mbox24)*NMbox*2;

        ccb = (Ccb24*)v;
        for(ccbp = ccb; ccbp < &ccb[NCcb]; ccbp++){
                ccbp->ccb = ctlr->ccb;
                ctlr->ccb = (Ccb*)ccbp;
        }

        /*
         * Initialise the software controller and
         * set the board scanning the mailboxes.
         */
        ctlr->mbix = NMbox;

        cmd[0] = Cinitialise;
        cmd[1] = NMbox;
        p = K2BPA(ctlr->mb, BUSUNKNOWN);
        cmd[2] = p>>16;
        cmd[3] = p>>8;
        cmd[4] = p;

        return issue(ctlr, cmd, 5, 0, 0);
}

static int
mylex32enable(Ctlr* ctlr)
{
        ulong p;
        Ccb32 *ccb, *ccbp;
        uchar cmd[6], *v;

        v = xspanalloc((sizeof(Mbox32)*NMbox*2)+(sizeof(Ccb32)*NCcb), 32, 0);

        ctlr->mb = v;
        v += sizeof(Mbox32)*NMbox*2;

        ccb = (Ccb32*)v;
        for(ccbp = ccb; ccbp < &ccb[NCcb]; ccbp++){
                /*
                 * Fill in some stuff that doesn't change.
                 */
                ccbp->senselen = sizeof(ccbp->sense);
                p = PADDR(ccbp->sense);
                ccbp->senseptr[0] = p;
                ccbp->senseptr[1] = p>>8;
                ccbp->senseptr[2] = p>>16;
                ccbp->senseptr[3] = p>>24;

                ccbp->ccb = ctlr->ccb;
                ctlr->ccb = (Ccb*)ccbp;
        }

        /*
         * Attempt wide mode setup.
         */
        if(ctlr->wide){
                cmd[0] = Cwide;
                cmd[1] = 1;
                if(!issue(ctlr, cmd, 2, 0, 0)) {
                        ctlr->wide = 0;
                        print("mylex32enable: port 0x%ux: scsi wide-mode setup "
                                "failed on wide host adapter", ctlr->port);
                }
        }

        /*
         * Initialise the software controller and
         * set the board scanning the mailboxes.
         */
        ctlr->mbix = NMbox;

        cmd[0] = Ciem;
        cmd[1] = NMbox;
        if(ctlr->pcidev)
                p = K2BPA(ctlr->mb, ctlr->tbdf);
        else
                p = K2BPA(ctlr->mb, BUSUNKNOWN);
        cmd[2] = p;
        cmd[3] = p>>8;
        cmd[4] = p>>16;
        cmd[5] = p>>24;

        return issue(ctlr, cmd, 6, 0, 0);
}

static int
mylexenable(SDev* sdev)
{
        int tbdf;
        Ctlr *ctlr;
        void (*interrupt)(Ureg*, void*);
        char name[32];

        ctlr = sdev->ctlr;
        if(ctlr->cache == nil){
                if((ctlr->cache = malloc(sdev->nunit*sizeof(Ccb*))) == nil)
                        return 0;
        }

        tbdf = BUSUNKNOWN;
        if(ctlr->bus == 32){
                if(ctlr->pcidev){
                        tbdf = ctlr->pcidev->tbdf;
                        pcisetbme(ctlr->pcidev);
                }
                if(!mylex32enable(ctlr))
                        return 0;
                interrupt = mylex32interrupt;
        }
        else if(mylex24enable(ctlr))
                interrupt = mylex24interrupt;
        else
                return 0;

        snprint(name, sizeof(name), "sd%c (%s)", sdev->idno, sdev->ifc->name);
        intrenable(ctlr->irq, interrupt, ctlr, tbdf, name);

        return 1;
}

SDifc sdmylexifc = {
        "mylex",                        /* name */

        mylexpnp,                       /* pnp */
        nil,                            /* legacy */
        mylexenable,                    /* enable */
        nil,                            /* disable */

        scsiverify,                     /* verify */
        scsionline,                     /* online */
        mylexrio,                       /* rio */
        nil,                            /* rctl */
        nil,                            /* wctl */

        scsibio,                        /* bio */
        nil,                            /* probe */
        nil,                            /* clear */
        nil,                            /* rtopctl */
        nil,                            /* wtopctl */
};