/* 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 #include #include #include #include #include /* shorthand byteswapping macros */ #define LE(n) B_HOST_TO_LENDIAN_INT32(n) #define HE(n) B_LENDIAN_TO_HOST_INT32(n) #include #include #include #include #include #include "53c8xx.h" #include "symbios.h" #if USE_STATFS #include 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;isyncsize;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;idatain_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;isram_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;isyncsize;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 };