root/src/add-ons/kernel/busses/scsi/53c8xx/53c8xx.c
/*
        Copyright 1998-1999, Be Incorporated.   All Rights Reserved.
        This file may be used under the terms of the Be Sample Code License.
*/

/*
** 53c8xx.c - Symbios 53c8xx SIM
*/

#define DEBUG_SYMBIOS  1   /* Print Debugging Messages */
#define DEBUG_ISR      0   /* messages in ISR... leave off */
#define DEBUG_PM       0   /* messages when Phase Mismatch occurs... leave off */
#define DEBUG_SAFETY   0   /* don't load driver if serial debug off */

#define USE_STATFS     0

/*
** Debugging Macros
*/
#if DEBUG_SYMBIOS
#define d_printf dprintf
#else
#define d_printf(x...)
#endif

#include <module.h>
#include <OS.h>
#include <KernelExport.h>
#include <PCI.h>
#include <CAM.h>
#include <ByteOrder.h>

/* shorthand byteswapping macros */
#define LE(n) B_HOST_TO_LENDIAN_INT32(n)
#define HE(n) B_LENDIAN_TO_HOST_INT32(n)

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include <string.h>
#include <iovec.h>

#include "53c8xx.h"

#include "symbios.h"

#if USE_STATFS
#include <stat_module.h>

static int32
stat_controller(void *stats, char **buf)
{
        Symbios *s = (Symbios *) stats;

        if(*buf = (char *) malloc(256)){
                sprintf(*buf,
                                "Chipset:   %s\n"
                                "IO Base:   0x%08x\n"
                                "SRAM Base: 0x%08x\n"
                                "IRQ Line:  %d\n"
                                "SCSI Bus:  %s\n",
                                s->name, s->iobase, s->sram_phys, s->irq,
                                s->max_targ_id > 7 ? "Wide" : "Narrow");
                return strlen(*buf);
        } else {
                return 0;
        }
}

static int32
stat_target(void *stats, char **buf)
{
        SymTarg *st = (SymTarg *) stats;

        if(st->flags & tf_ignore){
                *buf = NULL;
                return 0;
        }

        if(*buf = (char *) malloc(256)){
                if(st->offset){
                        sprintf(*buf,
                                        "Width:     %s\n"
                                        "Transfer:  Sync\n"
                                        "Period:    %d ns\n"
                                        "Offset:    %d\n",
                                        st->wide ? "Wide" : "Narrow",
                                        st->period,
                                        st->offset
                                        );
                } else {
                        sprintf(*buf,
                                        "Width:     %s\n"
                                        "Transfer:  Async\n",
                                        st->wide ? "Wide" : "Narrow");
                }
                return strlen(*buf);
        } else {
                return 0;
        }
}


static void register_stats(Symbios *s)
{
        char buf[128];
        stat_module_info_t *stats;
        int i;

        if(s->registered) return;

        if(get_module("generic/stat_module", (module_info**) &stats) == B_OK){
                sprintf(buf,"scsi/53c8xx/%d/info",s->num);
                stats->register_statistics(buf, s, stat_controller);
                for(i=0;i<=s->max_targ_id;i++){
                        sprintf(buf,"scsi/53c8xx/%d/targ_%x",s->num,i);
                        stats->register_statistics(buf, &(s->targ[i]), stat_target);
                }
                s->registered = 1;
        } else {
                dprintf("symbios: cannot find stats module...\n");
        }
}

#else
#define register_stats(x)

#endif

/*
** Constants for the SIM
*/
#define SIM_VERSION 0x01
#define HBA_VERSION 0x01

static char sim_vendor_name[]   = "Be, Inc.";
static char hba_vendor_name[]   = "Symbios";

static pci_module_info          *pci;
static cam_for_sim_module_info  *cam;

static char     cam_name[] = B_CAM_FOR_SIM_MODULE_NAME;
static char     pci_name[] = B_PCI_MODULE_NAME;

/*
** Supported Device / Device Attributes table
*/
#define symf_sram       0x0001      /* on board SCRIPTS ram */
#define symf_doubler    0x0002      /* SCLK doubler available */
#define symf_quadrupler 0x0004      /* SCLK quadrupler available */
#define symf_untested   0x1000      /* never actually tested one of these */
#define symf_wide       0x0008      /* supports WIDE bus */
#define symf_short      0x0010      /* short max period (8) */

static struct {
        uint32 id;
        uint32 rev;
        char *name;
        int flags;
} devinfo[] = {
        { 0x0001, 0x10, "53c810a", symf_short },
        { 0x0001, 0x00, "53c810",  symf_short },
        { 0x0006, 0x00, "53c860",  symf_wide | symf_short | symf_untested },
        { 0x0004, 0x00, "53c815",  symf_short | symf_untested },
        { 0x0002, 0x00, "53c820",  symf_wide | symf_short | symf_untested },
        { 0x0003, 0x10, "53c825a", symf_wide | symf_short |symf_sram | symf_untested },
        { 0x0003, 0x00, "53c825",  symf_wide | symf_short | symf_untested },
        { 0x000f, 0x02, "53c875",  symf_wide | symf_sram | symf_doubler },
        { 0x000f, 0x00, "53c875",  symf_wide | symf_sram },
        { 0x008f, 0x00, "53c875j", symf_wide | symf_sram | symf_doubler },
        { 0x000d, 0x00, "53c885",  symf_wide | symf_sram | symf_untested },
        { 0x000c, 0x00, "53c895",  symf_wide | symf_sram | symf_quadrupler | symf_untested },
        { 0x000b, 0x00, "53c896",  symf_wide | symf_sram | symf_untested },
        { 0, 0, NULL, 0 }
};

#include "scripts.c"

static void
setparams(SymTarg *t, uint period, uint offset, uint wide)
{
        Symbios *s = t->adapter;

        if(wide){
                if(!t->wide) kprintf("symbios%ld: target %ld wide\n",s->num,t->id);
                t->wide = 1;
        } else {
                t->wide = 0;
        }

        if(period){
                int i;
                for(i=0;i<s->syncsize;i++){
                        if(period <= s->syncinfo[i].period){
                                t->period = s->syncinfo[i].period;
                                t->offset = offset;

                                t->device[3] = s->syncinfo[i].scntl3;
                                if(t->wide) t->device[3] |= 0x08;
                                t->device[2] = t->id;
                                t->device[1] = s->syncinfo[i].sxfer | (offset & 0x0f);
                                t->device[0] = 0;
                                kprintf("symbios%ld: target %ld sync period=%ld, offset=%d\n",
                                                s->num, t->id, t->period, offset);
                                return;
                        }
                }
        }

        t->period = 0;
        t->offset = 0;

        t->device[3] = s->scntl3; /* scntl3 - clock divisor */
        if(t->wide) t->device[3] |= 0x08;
        t->device[2] = t->id;     /* dest id */
        t->device[1] = 0;         /* sync xfer */
        t->device[0] = 0;         /* reserved */
}

static long init_symbios(Symbios *s, int restarting);

/*
** IO Macros
*/

/* XXX - fix me bjs */
#define inb(p)     (*pci->read_io_8)(s->iobase + p)
#define outb(p,v)  (*pci->write_io_8)(s->iobase + p,v)
#define inw(p)     (*pci->read_io_16)(s->iobase + p)
#define outw(p,v)  (*pci->write_io_16)(s->iobase + p,v)
#define in32(p)    (*pci->read_io_32)(s->iobase + p)
#define out32(p,v) (*pci->write_io_32)(s->iobase + p,v)


/* patch in an external symbol */
#define RESOLV(sname,value) \
{ int i; \
        d_printf("symbios%d: relocting %d instances of %s to 0x%08x\n", \
                         s->num,sizeof(E_##sname##_Used)/4,#sname,value); \
        for(i=0;i<(sizeof(E_##sname##_Used)/4);i++) { \
                scr[E_##sname##_Used[i]] = ((uint32) value); \
        } \
}

/* calc phys addr of a ptr inside the sram area */
#define PHADDR(lvar) ((s->sram_phys) + (((uint32) &(lvar)) - ((uint32) s->script)))

/* calc phys addr of a ptr inside the priv area */
/*#define PPHADDR(ptr) (st->priv_phys + (((uint32) ptr) - ((uint32) st->priv)))*/
#define PPHADDR(ptr) (phys + (((uint32) ptr) - ((uint32) sp)))

/* prepare a Targ's scripts indirect table for one or more exec_io's */
static void prep_io(SymPriv *sp, uint32 phys)
{
        sp->syncmsg.address = LE(PPHADDR(sp->_syncmsg));
        sp->syncmsg.count = LE(3);

        sp->widemsg.address = LE(PPHADDR(sp->_widemsg));
        sp->widemsg.count = LE(2);

        sp->sendmsg.address = LE(PPHADDR(sp->_sendmsg));

        sp->recvmsg.address = LE(PPHADDR(sp->_recvmsg));
        sp->recvmsg.count = LE(1);

        sp->status.address = LE(PPHADDR(sp->_status));
        sp->status.count = LE(1);

        sp->extdmsg.address = LE(PPHADDR(sp->_extdmsg));
        sp->extdmsg.count = LE(1);

        sp->command.address = LE(PPHADDR(sp->_command));
}

/*
 * actually execute an io transaction via SCRIPTS
 * you MUST hold st->sem_targ before calling this
 *
 */
static void exec_io(SymTarg *st, void *cmd, int cmdlen, void *msg, int msglen,
                                   void *data, int datalen, int sg)
{
        cpu_status former;
        Symbios *s = st->adapter;

        memcpy((void *) &(st->priv->device.count), st->device, 4);

        st->priv->sendmsg.count = LE(msglen);
        memcpy(st->priv->_sendmsg, msg, msglen);
        st->priv->command.count = LE(cmdlen);
        memcpy(st->priv->_command, cmd, cmdlen);

        st->table_phys = st->priv_phys + ADJUST_PRIV_TO_TABLE;

        if(datalen){
                int i,sgcount;
                uint32 opcode;
                SymInd *t = st->priv->table;
                physical_entry *pe = (physical_entry *) &(st->priv->table[1]);

                if(st->inbound){
                        opcode = s->op_in;
                        st->datain_phys = st->table_phys;
                        st->dataout_phys = s->sram_phys + Ent_phase_dataerr;
                } else {
                        opcode = s->op_out;
                        st->dataout_phys = st->table_phys;
                        st->datain_phys = s->sram_phys + Ent_phase_dataerr;
                }

                if(sg) {
                        iovec *vec = (iovec *) data;
                        for(sgcount=0,i=0;i<datalen;i++){
                                get_memory_map(vec[i].iov_base, vec[i].iov_len, &pe[sgcount], 130-sgcount);
                                while(pe[sgcount].size && (sgcount < 130)){
                                        t[sgcount].address = LE((uint32) pe[sgcount].address);
                                        t[sgcount].count = LE(opcode | pe[sgcount].size);
                                        sgcount++;
                                }
                                if((sgcount == 130) && pe[sgcount].size){
                                        panic("symbios: sg list overrun");
                                }
                        }
                } else {
                        get_memory_map(data, datalen, pe, 130);
                        for(i=0;pe[i].size;i++){
                                t[i].address = LE((uint32) pe[i].address);
                                t[i].count = LE(opcode | pe[i].size);
                        }
                        sgcount = i;
                }
                t[sgcount].count = LE(OP_END);
                t[sgcount].address = LE(ARG_END);

//              for(i=0;i<=sgcount;i++){
//                      dprintf("sym: %04d - %08x %08x\n",i,t[i].address,t[i].count);
//              }
        } else {
                st->datain_phys = s->sram_phys + Ent_phase_dataerr;
                st->dataout_phys = s->sram_phys + Ent_phase_dataerr;
        }

//      dprintf("sym: pp = %08x  di = %08x  do = %08x\n",st->priv_phys,st->datain_phys,st->dataout_phys);

        st->status = status_queued;

/*      dprintf("symbios: enqueueing %02x %02x %02x ... for %d (%d bytes %s)\n",
                        ((uchar *)cmd)[0],((uchar *)cmd)[1],((uchar *)cmd)[2],
                        st->device[2],datalen,st->inbound?"IN":"OUT");
*/
        former = disable_interrupts();
        acquire_spinlock(&(s->hwlock));

        /* enqueue the request */
        if(s->startqueuetail){
                s->startqueuetail->next = st;
        } else {
                s->startqueue = st;
        }
        st->next = NULL;
        s->startqueuetail = st;

        /* If the adapter is idle, signal it so that this request may be started */
        if(s->status == IDLE) outb(sym_istat, sym_istat_sigp);

        release_spinlock(&(s->hwlock));
        restore_interrupts(former);

        /* wait for completion */
        acquire_sem(st->sem_done);

#if 0
        if(acquire_sem_etc(st->sem_done, 1, B_TIMEOUT, 10*1000000) != B_OK){
                kprintf("sym: targ %d never finished,  argh...\n",st->device[2]);
                init_symbios(st->adapter,1);
                st->state = sTIMEOUT;
                return;
        }
#endif
}


#if DEBUG_ISR
#define kp kprintf
#else
#define kp(x...)
#endif

static int32
scsi_int_dispatch(void *data)
{
        Symbios *s = (Symbios *) data;
        int reselected = 0;
        uchar istat;

        if(s->reset) return B_UNHANDLED_INTERRUPT;

        acquire_spinlock(&(s->hwlock));
        istat = inb(sym_istat);

        if(istat & sym_istat_dip){
                uchar dstat = inb(sym_dstat);

                if(dstat & sym_dstat_sir){
                        /* Handle and interrupt from the SCRIPTS program */
                        uint32 status = HE(in32(sym_dsps));
        //              kprintf("<%02x>",status);

                        switch(status){
                        case status_ready:
                                kp("sym: ready\n");
                                break;

                        case status_iocomplete:
                                kp("sym: done %08x\n",s->active);
                                if(s->active){
                                        /* io is complete -- any more io is an error */
                                        s->active->datain_phys = s->sram_phys + Ent_phase_dataerr;
                                        s->active->dataout_phys = s->sram_phys + Ent_phase_dataerr;
                                }
                                break;

                        case status_reselected:{
                                uint32 id = inb(sym_ssid);
                                if(id & sym_ssid_val) {
                                        s->active = &(s->targ[id & s->idmask]);
                                        kp("sym: resel %08x\n",s->active);
                                        if(s->active->status != status_waiting){
                                                s->active = NULL;
                                                kprintf("symbios: bad reselect %ld\n",id & sym_ssid_encid);
                                        } else {
                                                reselected = 1;
                                        }
                                } else {
                                        kprintf("symbios: invalid reselection!?\n");
                                }
                                break;
                        }

                        case status_timeout:
                                /* inform the unlucky party and dequeue it */
                                kp("sym: timeout %08lx\n",s->startqueue);
                                if(s->startqueue){
                                        s->startqueue->status = status_timeout;
                                        release_sem_etc(s->startqueue->sem_done, 1, B_DO_NOT_RESCHEDULE);
                                        if(!(s->startqueue = s->startqueue->next)){
                                                s->startqueuetail = NULL;
                                        }
                                }
                                break;

                        case status_selected:
                                /* selection succeeded.  Remove from start queue and make active */
                                kp("sym: selected %08lx\n",s->startqueue);
                                if(s->startqueue){
                                        s->active = s->startqueue;
                                        s->active->status = status_active;
                                        if(!(s->startqueue = s->startqueue->next)){
                                                s->startqueuetail = NULL;
                                        }
                                }
                                break;

                        case status_syncin:
                                setparams(s->active,
                                                  s->active->priv->_syncmsg[0]*4,
                                                  s->active->priv->_syncmsg[1],
                                                  s->active->wide);
                                break;

                        case status_widein:
                                setparams(s->active, s->active->period, s->active->offset,
                                                  s->active->priv->_widemsg[1]);
                                break;

                        case status_ignore_residue:
                                kprintf("ignore residue 0x%02x\n",s->active->priv->_extdmsg[0]);
                                break;

                        case status_disconnect:
                                kp("sym: disc %08lx\n",s->active);
                                /* device disconnected. make inactive */
                                if(s->active){
                                        s->active->status = status_waiting;
                                        s->active = NULL;
                                }
                                break;

                        case status_badmsg:
                                kp("sym: badmsg %02x\n",s->active->priv->_recvmsg[0]);

                        case status_complete:
                        case status_badstatus:
                        case status_overrun:
                        case status_underrun:
                        case status_badphase:
                        case status_badextmsg:
                                kp("sym: error %08lx / %02x\n",s->active,status);
                                /* transaction completed successfully or in error. report our status. */
                                if(s->active){
                                        s->active->status = status;
                                        release_sem_etc(s->active->sem_done, 1, B_DO_NOT_RESCHEDULE);
                                        s->active = NULL;
                                }
                                break;

                        case status_selftest:
                                /* signal a response to the selftest ... don't actually start up the
                                   SCRIPTS like we normally do */
                                s->status = OFFLINE;
                                goto done;
                                break;

                        default:
                                kp("sym: int 0x%08lx ...\n",status);
                        }
                        goto reschedule;
                } else {
                        kprintf("symbios: weird error, dstat = %02x\n",dstat);
                }
        }

        if(istat & sym_istat_sip){
                uchar sist0;
                sist0 = inb(sym_sist1);

                if(sist0 & sym_sist1_sbmc){
                        kprintf("sym: SBMC %02x!\n",inb(sym_stest4) & 0xc0);
                }

                if(sist0 & sym_sist1_sto){
                        /* select timeout */
                        kp("sym: Timeout %08lx\n",s->startqueue);
                        /* inform the unlucky party and dequeue it */
                        if(s->startqueue){
                                s->startqueue->status = status_timeout;
                                release_sem_etc(s->startqueue->sem_done, 1, B_DO_NOT_RESCHEDULE);
                                if(!(s->startqueue = s->startqueue->next)){
                                        s->startqueuetail = NULL;
                                }
                        } else {
                                kprintf("symbios: ghost target timed out\n");
                        }
                        inb(sym_sist0); //   apparently we MUST read sist0 as well
                        goto reschedule;
                }

                sist0 = inb(sym_sist0) & 0x8f;

                if(sist0 && s->active){
                        if(sist0 & sym_sist0_ma){
                                /* phase mismatch -- we experienced a disconnect while in
                                   a DataIn or DataOut... gotta figure out how much we
                                   transferred, update the sgtable, take the FIFOs into
                                   account, etc (see 9-9 in Symbios PCI-SCSI Programming Guide) */
                                SymInd *t;
                                uint32 dfifo_val, bytesleft, dbc;
                                uint32 dsp = HE(in32(sym_dsp));
                                uint32 n = (dsp - s->active->table_phys) / 8 - 1;

                                if((dsp < s->active->priv_phys) || (n > 129)) {
                                        /* we mismatched during some other phase ?! */
                                        kprintf("Phase Mismatch (dsp = 0x%08lx)\n",dsp);
                                        goto reschedule;
                                }

                                t = &(s->active->priv->table[n]);

#if 0
                                t->count = HE(t->count);
                                t->address = HE(t->count);
#endif
                                /* dbc initially = table[n].count, counts down */
                                dbc = (uint32) HE(in32(sym_dbc)) & 0x00ffffffL;

                                t->count &= 0xffffff;
#if DEBUG_PM
                                kprintf("PM(%s) dbc=0x%08x, n=%02d, a=0x%08x, l=0x%08x\n",
                                                s->active->inbound ? " in" : "out", dbc, n, t->address, t->count);
#endif
                                if(s->active->inbound){
                                        /* data in is easy... flush happens automatically */
                                        t->address += t->count - dbc;
                                        t->count = dbc;
#if DEBUG_PM
                                        kprintf("                              a=0x%08x, l=0x%08x\n",
                                        t->address, t->count);
#endif
                                        s->active->datain_phys = s->active->table_phys + 8*(t->count ? n : n+1);
                                        t->count |= s->op_in;
#if 0
                                        t->count = LE(t->count);
                                        t->address = LE(t->address);
#endif
                                        goto reschedule;
                                } else {
                                        if(inb(sym_ctest5) & 0x20){
                                                /* wide FIFO */
                                                dfifo_val = ((inb(sym_ctest5) & 0x03) << 8) | inb(sym_dfifo);
                                                bytesleft = (dfifo_val - (dbc & 0x3ff)) & 0x3ff;
                                        } else {
                                                dfifo_val = (inb(sym_dfifo) & 0x7f);
                                                bytesleft = (dfifo_val - (dbc & 0x7f)) & 0x7f;
                                        }
                                        if(inb(sym_sstat0) & 0x20) bytesleft++;
                                        if(inb(sym_sstat2) & 0x20) bytesleft++;
                                        if(inb(sym_sstat0) & 0x40) bytesleft++;
                                        if(inb(sym_sstat2) & 0x40) bytesleft++;

                                        /* clear fifo */
                                        outb(sym_ctest3, 0x04);

                                        t->address += t->count - dbc;
                                        t->count = dbc;

                                        /* adjust for data that didn't make it to the target */
                                        t->address -= bytesleft;
                                        t->count += bytesleft;
#if DEBUG_PM
                                        kprintf("                              a=0x%08x, l=0x%08x\n",
                                        t->address, t->count);
#endif
                                        s->active->dataout_phys = s->active->table_phys + 8*(t->count ? n : n+1);
                                        t->count |= s->op_out;
#if 0
                                        t->count = LE(t->count);
                                        t->address = LE(t->address);
#endif
                                        spin(10);
                                        goto reschedule;
                                }
                        }

                        if(sist0 & sym_sist0_udc){
                                kprintf("symbios: Unexpected Disconnect (dsp = 0x%08lx)\n", in32(sym_dsp));
                        }

                        if(sist0 & sym_sist0_sge){
                                kprintf("symbios: SCSI Gross Error\n");
                        }

                        if(sist0 & sym_sist0_rst){
                                kprintf("symbios: SCSI Reset\n");
                        }

                        if(sist0 & sym_sist0_par){
                                kprintf("symbios: Parity Error\n");
                        }

                        s->active->status = status_badphase;
                        release_sem_etc(s->active->sem_done, 1, B_DO_NOT_RESCHEDULE);
                        s->active = NULL;

                        goto reschedule;
                }
        } else {
                /* nothing happened... must be somebody else's problem */
                release_spinlock(&(s->hwlock));
                 return B_UNHANDLED_INTERRUPT;
        }

reschedule:
        /* start the SCRIPTS processor at one of three places, depending on state
        **
        ** 1. If there is an active transaction, insure that the script is patched
        **    correctly, the DSA is loaded, and start up at "switch".
        **
        ** 2. If there is a transaction at the head of the startqueue, set the DSA
        **    and start up at "start" to try to select the target and start the
        **    transaction
        **
        ** 3. If there is nothing else to do, go to "idle" and wait for signal or
        **    reselection
        */

        if(s->active){
                out32(sym_dsa, s->active->priv_phys + ADJUST_PRIV_TO_DSA);
                s->script[PATCH_DATAIN] = LE(s->active->datain_phys);
                s->script[PATCH_DATAOUT] = LE(s->active->dataout_phys);

                s->active->status = status_active;
                s->status = ACTIVE;
                if(reselected){
                        out32(sym_dsp, LE(s->sram_phys + Ent_switch_resel));
                        //kp("sym: restart @ %08x / reselected\n",Ent_switch_resel);
                } else {
                        out32(sym_dsp, LE(s->sram_phys + Ent_switch));
                        //kp("sym: restart @ %08x / selected\n", Ent_switch);
                }
        } else {
                if(s->startqueue){
                        out32(sym_dsa, LE(s->startqueue->priv_phys + ADJUST_PRIV_TO_DSA));

                        s->startqueue->status = status_selecting;
                        s->status = START;
                        out32(sym_dsp, LE(s->sram_phys + Ent_start));
                        //kp("sym: restart @ %08x / started\n", Ent_start);
                } else {
                        s->status = IDLE;
                        out32(sym_dsp, LE(s->sram_phys + Ent_idle));
                        //kp("sym: restart @ %08x / idle\n", Ent_idle);
                }
        }

done:
        release_spinlock(&(s->hwlock));
    return B_HANDLED_INTERRUPT;
}


/* init the adapter... if restarting, no bus reset or whathaveyou */
static long init_symbios(Symbios *s, int restarting)
{
    d_printf("symbios%ld: init_symbios()\n",s->num);

        if(restarting){
                s->reset = 1;
        } else {
                s->reset = 0;
        }

        if(!restarting){
                /* reset the SCSI bus */
                dprintf("symbios%ld: scsi bus reset\n",s->num);
                outb(sym_scntl1, sym_scntl1_rst);
                spin(25);
                outb(sym_scntl1, 0);
                spin(250000);

                /* clear ints */
                inb(sym_istat);
                inb(sym_sist0);
                inb(sym_sist1);
                inb(sym_dstat);

                install_io_interrupt_handler(s->irq, scsi_int_dispatch, s, 0);
                d_printf("symbios%ld: registered interrupt handler for irq %ld\n",s->num,s->irq);
        }

        /* enable irqs, prefetch, no 53c700 compat */
        outb(sym_dcntl, sym_dcntl_com);

        /* enable all DMA ints */
        outb(sym_dien, sym_dien_sir | sym_dien_mdpe | sym_dien_bf | sym_dien_abrt
                 | sym_dien_iid);
        /* enable all fatal SCSI ints */
        outb(sym_sien0, sym_sien0_ma | sym_sien0_sge | sym_sien0_udc | sym_sien0_rst |
                 sym_sien0_par);
        outb(sym_sien1, sym_sien1_sto | sym_sien1_sbmc); // XXX

        /* sel / hth timeouts */
        outb(sym_stime0, 0xbb);

        /* clear ints */
        inb(sym_istat);
        inb(sym_sist0);
        inb(sym_sist1);
        inb(sym_dstat);

        /* clear ints */
        inb(sym_sist0);
        inb(sym_sist1);
        inb(sym_dstat);

        if(restarting){
                s->reset = 0;
        } else {
                int i;
                s->status = TEST;

                dprintf("symbios%ld: selftest ",s->num);
                out32(sym_dsp, LE(s->sram_phys + Ent_test));
                for(i=0;(s->status == TEST) && i<10;i++) {
                        dprintf(".");
                        spin(10000);
                }
                if(s->status == TEST){
                        dprintf("FAIL\n");
                        return B_ERROR; //XXX teardown
                } else {
                        dprintf("PASS\n");
                }
        }

        s->status = IDLE;
        out32(sym_dsp, LE(s->sram_phys + Ent_idle));
    d_printf("symbios%ld: started script\n",s->num);

    return B_NO_ERROR;
}

/* When an inquiry succeeds the negotiator gets the option to attempt to
** request a better transfer agreement with the target.
*/
static void negotiator(Symbios *s, SymTarg *targ, uchar *ident, uchar *msg)
{
        if(ident[7] & 0x20){ /* wide supported */
                if(targ->flags & tf_ask_wide){
                        uchar cmd[6] = { 0, 0, 0, 0, 0, 0 };
                        targ->flags &= (~tf_ask_wide); /* only ask once */

                        dprintf("symbios%ld: negotiating wide xfer with target %ld\n",
                        s->num,targ->id);

                        msg[1] = 0x01; /* extended message */
                        msg[2] = 0x02; /* length           */
                        msg[3] = 0x03; /* sync negotiate   */
                        msg[4] = 0x01; /* 16 bit wide      */

                        exec_io(targ, cmd, 6, msg, 5, NULL, 0, 0);
                }
        }

        if(ident[7] & 0x10){ /* sync supported */
                if(targ->flags & tf_ask_sync) {
                        uchar cmd[6] = { 0, 0, 0, 0, 0, 0 };

                        targ->flags &= (~tf_ask_sync); /* only ask once */

                        dprintf("symbios%ld: negotiating sync xfer with target %ld\n",
                        s->num,targ->id);

                        msg[1] = 0x01; /* extended message */
                        msg[2] = 0x03; /* length           */
                        msg[3] = 0x01; /* sync negotiate   */
                        msg[4] = s->syncinfo[0].period / 4; /* sync period / 4  */
                        msg[5] = s->maxoffset;

                        exec_io(targ, cmd, 6, msg, 6, NULL, 0, 0);
                }
        }
}

/* Convert a CCB_SCSIIO into a BL_CCB32 and (possibly SG array).
**
*/
static long sim_execute_scsi_io(Symbios *s, CCB_HEADER *ccbh)
{
        CCB_SCSIIO *ccb = (CCB_SCSIIO *) ccbh;
        uchar *cdb;
        physical_entry pe[2];
        SymTarg *targ;
        uchar msg[8];

        targ = s->targ + ccb->cam_ch.cam_target_id;

        if(targ->flags & tf_ignore){
                ccbh->cam_status = CAM_SEL_TIMEOUT;
                return B_OK;
        }

        if(ccb->cam_ch.cam_flags & CAM_CDB_POINTER) {
                cdb = ccb->cam_cdb_io.cam_cdb_ptr;
        } else {
                cdb = ccb->cam_cdb_io.cam_cdb_bytes;
        }

        get_memory_map((void*) (ccb->cam_sim_priv), 1536, pe, 2);

        /* identify message */
        msg[0] = 0xC0 | (ccb->cam_ch.cam_target_lun & 0x07);

        /* fill out table */
        prep_io((SymPriv *) ccb->cam_sim_priv, (uint32) pe[0].address);

        /* insure only one transaction at a time for any given target */
        acquire_sem(targ->sem_targ);

        targ->priv = (SymPriv *) ccb->cam_sim_priv;;
        targ->priv_phys = (uint32 ) pe[0].address;

        targ->inbound = (ccb->cam_ch.cam_flags & CAM_DIR_IN) ? 1 : 0;

        if(ccb->cam_ch.cam_flags & CAM_SCATTER_VALID){
                exec_io(targ, cdb, ccb->cam_cdb_len, msg, 1,
                                ccb->cam_data_ptr, ccb->cam_sglist_cnt, 1);
        } else {
                exec_io(targ, cdb, ccb->cam_cdb_len, msg, 1,
                                ccb->cam_data_ptr, ccb->cam_dxfer_len, 0);
        }

/*      dprintf("symbios%d: state = 0x%02x, status = 0x%02x\n",
                        s->num,targ->state,targ->priv->status[0]);*/

        /* decode status */
        switch(targ->status){
        case status_complete:
                if((ccb->cam_scsi_status=targ->priv->_status[0]) != 0) {
                        ccbh->cam_status = CAM_REQ_CMP_ERR;

                        /* nonzero status is an error ... 0x02 = check condition */
                        if((ccb->cam_scsi_status == 0x02) &&
                           !(ccb->cam_ch.cam_flags & CAM_DIS_AUTOSENSE) &&
                           ccb->cam_sense_ptr && ccb->cam_sense_len){
                                   uchar command[6];

                                   command[0] = 0x03;           /* request_sense */
                                   command[1] = ccb->cam_ch.cam_target_lun << 5;
                                   command[2] = 0;
                                   command[3] = 0;
                                   command[4] = ccb->cam_sense_len;
                                   command[5] = 0;

                                   targ->inbound = 1;
                                   exec_io(targ, command, 6, msg, 1,
                                                   ccb->cam_sense_ptr, ccb->cam_sense_len, 0);

                                   if(targ->priv->_status[0]){
                                           ccb->cam_ch.cam_status |= CAM_AUTOSENSE_FAIL;
                                   } else {
                                           ccb->cam_ch.cam_status |= CAM_AUTOSNS_VALID;
                                   }
                        }
                } else {
                        ccbh->cam_status = CAM_REQ_CMP;

                        if(cdb[0] == 0x12) {
                                /* inquiry just succeeded ... is it non SG and with enough data to
                                   snoop the support bits? */
                                if(!(ccb->cam_ch.cam_flags & CAM_SCATTER_VALID) && (ccb->cam_dxfer_len>7)){
                                        negotiator(s, targ, ccb->cam_data_ptr, msg);
                                }
                        }
                }
                break;

        case status_timeout:
                ccbh->cam_status = CAM_SEL_TIMEOUT;
                break;

        default: // XXX
                ccbh->cam_status = CAM_SEL_TIMEOUT;
        }

        targ->status = status_inactive;
//      dprintf("symbios%d: releasing targ @ 0x%08x\n",s->num,targ);
        release_sem(targ->sem_targ);
        return B_OK;
}

/*
** sim_path_inquiry returns info on the target/lun.
*/
static long sim_path_inquiry(Symbios *s, CCB_HEADER *ccbh)
{
    CCB_PATHINQ *ccb;
    ccb = (CCB_PATHINQ *) ccbh;
    ccb->cam_version_num = SIM_VERSION;
    ccb->cam_target_sprt = 0;
    ccb->cam_hba_eng_cnt = 0;
    memset (ccb->cam_vuhba_flags, 0, VUHBA);
    ccb->cam_sim_priv = SIM_PRIV;
    ccb->cam_async_flags = 0;
    ccb->cam_initiator_id = s->host_targ_id;
        ccb->cam_hba_inquiry = s->max_targ_id > 7 ? PI_WIDE_16 : 0 ;
    strncpy (ccb->cam_sim_vid, sim_vendor_name, SIM_ID);
    strncpy (ccb->cam_hba_vid, hba_vendor_name, HBA_ID);
    ccb->cam_osd_usage = 0;
    ccbh->cam_status = CAM_REQ_CMP;
        register_stats(s);
    return 0;
}


/*
** sim_extended_path_inquiry returns info on the target/lun.
*/
static long sim_extended_path_inquiry(Symbios *s, CCB_HEADER *ccbh)
{
    CCB_EXTENDED_PATHINQ *ccb;

    sim_path_inquiry(s, ccbh);
    ccb = (CCB_EXTENDED_PATHINQ *) ccbh;
    sprintf(ccb->cam_sim_version, "%d.0", SIM_VERSION);
    sprintf(ccb->cam_hba_version, "%d.0", HBA_VERSION);
    strncpy(ccb->cam_controller_family, "Symbios", FAM_ID);
    strncpy(ccb->cam_controller_type, s->name, TYPE_ID);
    return 0;
}

/*
** scsi_sim_action performes the scsi i/o command embedded in the
** passed ccb.
**
** The target/lun ids are assumed to be in range.
*/
static long sim_action(Symbios *s, CCB_HEADER *ccbh)
{
        ccbh->cam_status = CAM_REQ_INPROG;
        switch(ccbh->cam_func_code){
        case XPT_SCSI_IO:
                return sim_execute_scsi_io(s,ccbh);
        case XPT_PATH_INQ:
                return sim_path_inquiry(s,ccbh);
        case XPT_EXTENDED_PATH_INQ:
                return sim_extended_path_inquiry(s, ccbh);
        default:
                ccbh->cam_status = CAM_REQ_INVALID;
                return -1;
        }
}

static void reloc_script(Symbios *s)
{
        int i;
        ulong *scr = s->script;

        memcpy(scr, SCRIPT, sizeof(SCRIPT));
        for(i=0;i<PATCHES;i++){
                scr[LABELPATCHES[i]] += s->sram_phys;
        }
        d_printf("symbios%ld: loaded %ld byte SCRIPT, relocated %ld labels\n",
                         s->num, sizeof(SCRIPT), PATCHES);

                /* disable scsi ints */
        outb(sym_scratcha, 0x42);
        outb(sym_scratcha+1, 0x00);
        outb(sym_scratchb, 0x04);
        outb(sym_sien0, 0);
        outb(sym_sien1, 0);
        outb(sym_dien, sym_dien_sir);

        /* clear ints */
        inb(sym_sist0);
        inb(sym_sist1);
        inb(sym_dstat);

        outb(sym_dmode, /*( sym_dmode_diom | sym_dmode_siom, 0 )*/ 0);  /* FIXME: ??? */
}

static uint32 sym_readclock(Symbios *s)
{
        uint32 ms,a,i;
        bigtime_t t0,t1;

        outw(sym_sien0 , 0);    /* mask all scsi interrupts        */
        outb(sym_dien , 0);     /* mask all dma interrupts         */
        inw(sym_sist0);         /* clear pending scsi interrupts   */
        outb(sym_scntl3, 4);    /* set pre-scaler to divide by 3   */

        for(a=0,i=0;i<5;i++){
                ms = 0;
                outb(sym_stime1, 0);    /* disable general purpose timer   */
                spin(10000);            /* let it all settle for 10ms      */
                inw(sym_sist0);         /* another one, just to be sure :) */

                t0 = system_time();
                outb(sym_stime1, 11);   /* delay of 128ms */
                while (!(inb(sym_sist1) & sym_sist1_gen)) snooze(250);
                t1 = system_time();
                ms = (t1-t0)/1000 + 10; /* we seem to be off by 10ms typically */

                a += ((1 << 11) * 4400) / ms;
        }

        outb(sym_stime1, 0);    /* disable general purpose timer   */
        return a / 5;
}

static uchar id_bits[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };

/*
** Allocate the actual memory for the cardinfo object
*/
static Symbios *create_cardinfo(int num, pci_info *pi, int flags)
{
        char name[32];
        Symbios *s;
        int i,scf;
        area_id aid;
        uint32 stest2,stest4;

        if((pi->u.h0.interrupt_line == 0) || (pi->u.h0.interrupt_line > 128)) {
                return NULL; /* invalid IRQ */
        }

        if(!(s = (Symbios *) malloc(sizeof(Symbios)))) return NULL;

        s->num = num;
        s->iobase = pi->u.h0.base_registers[0];
        s->irq = pi->u.h0.interrupt_line;
        B_INITIALIZE_SPINLOCK(&s->hwlock);
        s->startqueue = NULL;
        s->startqueuetail = NULL;
        s->active = NULL;

        sprintf(name,"sym%d:sram",num);
        if(flags & symf_sram){
                unsigned char *c;
                s->sram_phys = pi->u.h0.base_registers[2];
                if((aid=map_physical_memory(name, s->sram_phys, 4096,
                                                                         B_ANY_KERNEL_ADDRESS, B_READ_AREA + B_WRITE_AREA,
                                                                         (void **) &(s->script))) < 0){
                        free(s);
                        return NULL;
                }
                /* memory io test */
                c = (unsigned char *) s->script;
                for(i=0;i<4096;i++) c[i] = (255 - (i & 0xff));
                for(i=0;i<4096;i++) {
                        if(c[i] != (255 - (i & 0xff))) {
                                d_printf("symbios%d: scripts ram io error @ %d\n",num,i);
                                goto err;
                        }
                }
        } else {
                uchar *a;
                physical_entry entries[2];
                aid = create_area(name, (void **)&a, B_ANY_KERNEL_ADDRESS, 4096*5,
                        B_32_BIT_CONTIGUOUS, B_READ_AREA | B_WRITE_AREA);
                if(aid == B_ERROR || aid == B_BAD_VALUE || aid == B_NO_MEMORY){
                        free(s);
                    return NULL;
                }
                get_memory_map(a, 4096, entries, 2);
                s->sram_phys = (uint32) entries[0].address;
                s->script = (uint32 *) a;
        }

        d_printf("symbios%d: scripts ram @ 0x%08lx, mapped to 0x%08lx (%s)\n",
                         num, s->sram_phys, (uint32) s->script,
                         flags & symf_sram ? "onboard" : "offboard" );

        /* what are we set at now? */
        s->host_targ_id = inb(sym_scid) & 0x07;
        dprintf("symbios%ld: host id %ld\n",s->num,s->host_targ_id);

        s->host_targ_id = 7;  /* XXX figure this out somehow... */
        s->max_targ_id = (flags & symf_wide) ? 15 : 7;

        stest2 = inb(sym_stest2);
        stest4 = inb(sym_stest4);

        /* software reset */
        outb(sym_istat, sym_istat_srst);
        spin(10000);
        outb(sym_istat, 0);
        spin(10000);

        /* initiator mode, full arbitration */
        outb(sym_scntl0, sym_scntl0_arb0 | sym_scntl0_arb1);

        outb(sym_scntl1, 0);
        outb(sym_scntl2, 0);

        /* initiator id=7, respond to reselection */
        /* respond to reselect of id 7 */
        outb(sym_respid, id_bits[s->host_targ_id]);
        outb(sym_scid, sym_scid_rre | s->host_targ_id);

        outb(sym_dmode, 0);

        dprintf("symbios%ld: stest2 = 0x%02lx, stest4 = 0x%02lx\n",s->num,stest2,stest4);

        /* no differential, no loopback, no hiz, no always-wide, no filter, no lowlevel */
        outb(sym_stest2, 0); // save diff bit
        outb(sym_stest3, 0);

//      if(flags & symf_quadrupler){
//              outb(sym_stest4, sym_stest4_lvd);
//      }

        outb(sym_stest1, 0);    /* make sure clock doubler is OFF  */

        s->sclk = sym_readclock(s);
        dprintf("symbios%ld: clock is %ldKHz\n",s->num,s->sclk);

        if(flags & symf_doubler){
                /* if we have a doubler and we don't already have an 80MHz clock */
                if((s->sclk > 35000) && (s->sclk < 45000)){
                        dprintf("symbios%ld: enabling clock doubler...\n",s->num);
                        outb(sym_stest1, 0x08);  /* enable doubler */
                        spin(200);                /* wait 20us      */
                        outb(sym_stest3, 0xa0);  /* halt sclk, enable TolerANT*/
                        outb(sym_scntl3, 0x05);  /* SCLK/4         */
                        outb(sym_stest1, 0x0c);  /* engage doubler */
                        outb(sym_stest3, 0x80);  /* reenable sclk, leave TolerANT on  */

                        spin(3000);

                        s->sclk = sym_readclock(s);
                        dprintf("symbios%ld: clock is %ldKHz\n",s->num,s->sclk);
                }
        }
        if(flags & symf_quadrupler){
                if((s->sclk > 35000) && (s->sclk < 45000)){
                        dprintf("symbios%ld: enabling clock quadrupler...\n",s->num);
                        outb(sym_stest1, 0x08);  /* enable doubler */
                        spin(200);                /* wait 20us      */
                        outb(sym_stest3, 0xa0);  /* halt sclk, enable TolerANT*/
                        outb(sym_scntl3, 0x05);  /* SCLK/4         */
                        outb(sym_stest1, 0x0c);  /* engage doubler */
                        outb(sym_stest3, 0x80);  /* reenable sclk, leave TolerANT on  */

                        spin(3000);

                        s->sclk = sym_readclock(s);
                        dprintf("symbios%ld: clock is %ldKHz\n",s->num,s->sclk);
                        s->sclk = 160000;
                }
        }
        outb(sym_stest3, 0x80);  /* leave TolerANT on  */

        scf = 0;
        /* set CCF / SCF according to specs */
        if(s->sclk < 25010) {
                dprintf("symbios%ld: unsupported clock frequency\n",s->num);
                goto err;  //           s->scntl3 = 0x01;
        } else if(s->sclk < 37510){
                dprintf("symbios%ld: unsupported clock frequency\n",s->num);
                goto err;  //           s->scntl3 = 0x02;
        } else if(s->sclk < 50010){
                /* 40MHz - divide by 1, 2 */
                scf = 0x10;
                s->scntl3 = 0x03;
        } else if(s->sclk < 75010){
                dprintf("symbios%ld: unsupported clock frequency\n",s->num);
                goto err; //            s->scntl3 = 0x04;
        } else if(s->sclk < 85000){
                /* 80 MHz - divide by 2, 4*/
                scf = 0x30;
                s->scntl3 = 0x05;
        } else {
                /* 160 MHz - divide by 4, 8 */
                scf = 0x50;
                s->scntl3 = 0x07;
        }


        s->maxoffset = (flags & symf_short) ? 8 : 15 ;
        s->syncsize = 0;

        if(scf == 0x50){
                /* calculate values for 160MHz clock */
                for(i=0;i<4;i++){
                        s->syncinfo[s->syncsize].sxfer = i << 5;
                        s->syncinfo[s->syncsize].scntl3 = s->scntl3 | 0x90; /* /2, Ultra2 */
                        s->syncinfo[s->syncsize].period_ns = (625 * (i+4)) / 100;
                        s->syncinfo[s->syncsize].period = 4 * (s->syncinfo[s->syncsize].period_ns / 4);
                        s->syncsize++;
                }
        }

        if(scf >= 0x30){
                /* calculate values for 80MHz clock */
                for(i=0;i<4;i++){
                        s->syncinfo[s->syncsize].sxfer = i << 5;
                        if(scf == 0x30){
                                s->syncinfo[s->syncsize].scntl3 = s->scntl3 | 0x90; /* /2, Ultra */
                        } else {
                                s->syncinfo[s->syncsize].scntl3 = s->scntl3 | 0xb0; /* /4, Ultra2 */
                        }

                        s->syncinfo[s->syncsize].period_ns = (125 * (i+4)) / 10;
                        s->syncinfo[s->syncsize].period = 4 * (s->syncinfo[s->syncsize].period_ns / 4);
                        s->syncsize++;
                }
        }

        /* calculate values for 40MHz clock */
        for(i=0;i<8;i++){
                s->syncinfo[s->syncsize].sxfer = i << 5;
                s->syncinfo[s->syncsize].scntl3 = s->scntl3 | scf;
                s->syncinfo[s->syncsize].period_ns = 25 * (i+4);
                s->syncinfo[s->syncsize].period = 4 * (s->syncinfo[s->syncsize].period_ns / 4);
                s->syncsize++;
        }

        for(i=0;i<s->syncsize;i++){
                dprintf("symbios%ld: syncinfo[%d] = { %02x, %02x, %d ns, %d ns }\n",
                                s->num, i,
                                s->syncinfo[i].sxfer, s->syncinfo[i].scntl3,
                                s->syncinfo[i].period_ns, s->syncinfo[i].period);
        }

        for(i=0;i<16;i++){
                s->targ[i].id = i;
                s->targ[i].adapter = s;
                s->targ[i].wide = 0;
                s->targ[i].offset = 0;
                s->targ[i].status = status_inactive;

                if((i == s->host_targ_id) || (i > s->max_targ_id)){
                        s->targ[i].flags = tf_ignore;
                } else {
                        s->targ[i].flags = tf_ask_sync;
                        if(flags & symf_wide) s->targ[i].flags |= tf_ask_wide;
//                      s->targ[i].flags = 0;

                        setparams(s->targ + i, 0, 0, 0);

                        sprintf(name,"sym%ld:%02d:lock",s->num,i);
                        s->targ[i].sem_targ = create_sem(1,name);

                        sprintf(name,"sym%ld:%02d:done",s->num,i);
                        s->targ[i].sem_done = create_sem(0,name);
                }
        }

        if(flags & symf_wide){
                s->idmask = 15;
                s->op_in = OP_WDATA_IN;
                s->op_out = OP_WDATA_OUT;
        } else {
                s->idmask = 7;
                s->op_in = OP_NDATA_IN;
                s->op_out = OP_NDATA_OUT;
        }

        reloc_script(s);
    return s;

err:
        free(s);
        delete_area(aid);
        return NULL;
}


/*
** Multiple Card Cruft
*/
#define MAXCARDS 4

static Symbios *cardinfo[MAXCARDS] = { NULL, NULL, NULL, NULL };

static long sim_init0(void)                { return init_symbios(cardinfo[0],0); }
static long sim_init1(void)                { return init_symbios(cardinfo[1],0); }
static long sim_init2(void)                { return init_symbios(cardinfo[2],0); }
static long sim_init3(void)                { return init_symbios(cardinfo[3],0); }
static long sim_action0(CCB_HEADER *ccbh)  { return sim_action(cardinfo[0],ccbh); }
static long sim_action1(CCB_HEADER *ccbh)  { return sim_action(cardinfo[1],ccbh); }
static long sim_action2(CCB_HEADER *ccbh)  { return sim_action(cardinfo[2],ccbh); }
static long sim_action3(CCB_HEADER *ccbh)  { return sim_action(cardinfo[3],ccbh); }

static long (*sim_init_funcs[MAXCARDS])(void) = {
    sim_init0, sim_init1, sim_init2, sim_init3
};

static long (*sim_action_funcs[MAXCARDS])(CCB_HEADER *) = {
    sim_action0, sim_action1, sim_action2, sim_action3
};


/*
** Detect the controller and register the SIM with the CAM layer.
** returns the number of controllers installed...
*/
static int
sim_install_symbios(void)
{
    int i, j, iobase, irq;
    int cardcount = 0;
    pci_info h;
    CAM_SIM_ENTRY entry;

    dprintf("symbios: sim_install()\n");

    for (i = 0; ; i++) {
                if ((*pci->get_nth_pci_info) (i, &h) != B_NO_ERROR) {
                        /*if(!cardcount) d_printf("symbios: no controller found\n");*/
                        break;
                }

//              d_printf("scan: %04x %04x %02x\n", h.device_id, h.vendor_id, h.revision);

#define PCI_VENDOR_SYMBIOS 0x1000

                if(h.vendor_id == PCI_VENDOR_SYMBIOS) {
                        for(j=0;devinfo[j].id;j++){
                                if((devinfo[j].id == h.device_id) && (h.revision >= devinfo[j].rev)){
                                        iobase = h.u.h0.base_registers[0];
                                        irq = h.u.h0.interrupt_line;
                                        d_printf("symbios%d: %s controller @ 0x%08x, irq %d\n",
                                                         cardcount, devinfo[j].name, iobase, irq);

                                        if(cardcount == MAXCARDS){
                                                d_printf("symbios: too many controllers!\n");
                                                return cardcount;
                                        }

                                        if((cardinfo[cardcount]=create_cardinfo(cardcount,&h,devinfo[j].flags)) != NULL){
                                                cardinfo[cardcount]->name = devinfo[j].name;
                                                entry.sim_init = sim_init_funcs[cardcount];
                                                entry.sim_action = sim_action_funcs[cardcount];
                                                cardinfo[cardcount]->registered = 0;
                                                register_stats(cardinfo[cardcount]);

                                                (*cam->xpt_bus_register)(&entry);
                                                cardcount++;
                                        } else {
                                                d_printf("symbios%d: cannot allocate cardinfo\n",cardcount);
                                        }
                                        break;
                                }
                        }
                }
    }

    return cardcount;
}

static status_t std_ops(int32 op, ...)
{
        switch(op) {
        case B_MODULE_INIT:
#if DEBUG_SAFETY
                set_dprintf_enabled(true);
#endif
                if (get_module(pci_name, (module_info **) &pci) != B_OK)
                        return B_ERROR;

                if (get_module(cam_name, (module_info **) &cam) != B_OK) {
                        put_module(pci_name);
                        return B_ERROR;
                }

                if(sim_install_symbios()){
                        return B_OK;
                }

                put_module(pci_name);
                put_module(cam_name);
                return B_ERROR;

        case B_MODULE_UNINIT:
                put_module(pci_name);
                put_module(cam_name);
                return B_OK;

        default:
                return B_ERROR;
        }
}

static
sim_module_info sim_symbios_module = {
        { "busses/scsi/53c8xx/v1", 0, &std_ops }
};

_EXPORT
module_info  *modules[] =
{
        (module_info *) &sim_symbios_module,
        NULL
};