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>
#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;
if(t->wide) t->device[3] |= 0x08;
t->device[2] = t->id;
t->device[1] = 0;
t->device[0] = 0;
}
static long init_symbios(Symbios *s, int restarting);
** IO Macros
*/
#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)
#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); \
} \
}
#define PHADDR(lvar) ((s->sram_phys) + (((uint32) &(lvar)) - ((uint32) s->script)))
#define PPHADDR(ptr) (phys + (((uint32) ptr) - ((uint32) sp)))
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);
} else {
st->datain_phys = s->sram_phys + Ent_phase_dataerr;
st->dataout_phys = s->sram_phys + Ent_phase_dataerr;
}
st->status = status_queued;
((uchar *)cmd)[0],((uchar *)cmd)[1],((uchar *)cmd)[2],
st->device[2],datalen,st->inbound?"IN":"OUT");
*/
former = disable_interrupts();
acquire_spinlock(&(s->hwlock));
if(s->startqueuetail){
s->startqueuetail->next = st;
} else {
s->startqueue = st;
}
st->next = NULL;
s->startqueuetail = st;
if(s->status == IDLE) outb(sym_istat, sym_istat_sigp);
release_spinlock(&(s->hwlock));
restore_interrupts(former);
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){
uint32 status = HE(in32(sym_dsps));
switch(status){
case status_ready:
kp("sym: ready\n");
break;
case status_iocomplete:
kp("sym: done %08x\n",s->active);
if(s->active){
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:
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:
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);
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);
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:
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){
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;
}
} else {
kprintf("symbios: ghost target timed out\n");
}
inb(sym_sist0);
goto reschedule;
}
sist0 = inb(sym_sist0) & 0x8f;
if(sist0 && s->active){
if(sist0 & sym_sist0_ma){
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)) {
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 = (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){
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){
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++;
outb(sym_ctest3, 0x04);
t->address += t->count - dbc;
t->count = dbc;
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 {
release_spinlock(&(s->hwlock));
return B_UNHANDLED_INTERRUPT;
}
reschedule:
**
** 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));
} else {
out32(sym_dsp, LE(s->sram_phys + 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));
} else {
s->status = IDLE;
out32(sym_dsp, LE(s->sram_phys + Ent_idle));
}
}
done:
release_spinlock(&(s->hwlock));
return B_HANDLED_INTERRUPT;
}
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){
dprintf("symbios%ld: scsi bus reset\n",s->num);
outb(sym_scntl1, sym_scntl1_rst);
spin(25);
outb(sym_scntl1, 0);
spin(250000);
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);
}
outb(sym_dcntl, sym_dcntl_com);
outb(sym_dien, sym_dien_sir | sym_dien_mdpe | sym_dien_bf | sym_dien_abrt
| sym_dien_iid);
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);
outb(sym_stime0, 0xbb);
inb(sym_istat);
inb(sym_sist0);
inb(sym_sist1);
inb(sym_dstat);
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;
} 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;
}
** request a better transfer agreement with the target.
*/
static void negotiator(Symbios *s, SymTarg *targ, uchar *ident, uchar *msg)
{
if(ident[7] & 0x20){
if(targ->flags & tf_ask_wide){
uchar cmd[6] = { 0, 0, 0, 0, 0, 0 };
targ->flags &= (~tf_ask_wide);
dprintf("symbios%ld: negotiating wide xfer with target %ld\n",
s->num,targ->id);
msg[1] = 0x01;
msg[2] = 0x02;
msg[3] = 0x03;
msg[4] = 0x01;
exec_io(targ, cmd, 6, msg, 5, NULL, 0, 0);
}
}
if(ident[7] & 0x10){
if(targ->flags & tf_ask_sync) {
uchar cmd[6] = { 0, 0, 0, 0, 0, 0 };
targ->flags &= (~tf_ask_sync);
dprintf("symbios%ld: negotiating sync xfer with target %ld\n",
s->num,targ->id);
msg[1] = 0x01;
msg[2] = 0x03;
msg[3] = 0x01;
msg[4] = s->syncinfo[0].period / 4;
msg[5] = s->maxoffset;
exec_io(targ, cmd, 6, msg, 6, NULL, 0, 0);
}
}
}
**
*/
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);
msg[0] = 0xC0 | (ccb->cam_ch.cam_target_lun & 0x07);
prep_io((SymPriv *) ccb->cam_sim_priv, (uint32) pe[0].address);
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);
}
s->num,targ->state,targ->priv->status[0]);*/
switch(targ->status){
case status_complete:
if((ccb->cam_scsi_status=targ->priv->_status[0]) != 0) {
ccbh->cam_status = CAM_REQ_CMP_ERR;
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;
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) {
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:
ccbh->cam_status = CAM_SEL_TIMEOUT;
}
targ->status = status_inactive;
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);
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);
inb(sym_sist0);
inb(sym_sist1);
inb(sym_dstat);
outb(sym_dmode, 0);
}
static uint32 sym_readclock(Symbios *s)
{
uint32 ms,a,i;
bigtime_t t0,t1;
outw(sym_sien0 , 0);
outb(sym_dien , 0);
inw(sym_sist0);
outb(sym_scntl3, 4);
for(a=0,i=0;i<5;i++){
ms = 0;
outb(sym_stime1, 0);
spin(10000);
inw(sym_sist0);
t0 = system_time();
outb(sym_stime1, 11);
while (!(inb(sym_sist1) & sym_sist1_gen)) snooze(250);
t1 = system_time();
ms = (t1-t0)/1000 + 10;
a += ((1 << 11) * 4400) / ms;
}
outb(sym_stime1, 0);
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;
}
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;
}
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" );
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;
s->max_targ_id = (flags & symf_wide) ? 15 : 7;
stest2 = inb(sym_stest2);
stest4 = inb(sym_stest4);
outb(sym_istat, sym_istat_srst);
spin(10000);
outb(sym_istat, 0);
spin(10000);
outb(sym_scntl0, sym_scntl0_arb0 | sym_scntl0_arb1);
outb(sym_scntl1, 0);
outb(sym_scntl2, 0);
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);
outb(sym_stest2, 0);
outb(sym_stest3, 0);
outb(sym_stest1, 0);
s->sclk = sym_readclock(s);
dprintf("symbios%ld: clock is %ldKHz\n",s->num,s->sclk);
if(flags & symf_doubler){
if((s->sclk > 35000) && (s->sclk < 45000)){
dprintf("symbios%ld: enabling clock doubler...\n",s->num);
outb(sym_stest1, 0x08);
spin(200);
outb(sym_stest3, 0xa0);
outb(sym_scntl3, 0x05);
outb(sym_stest1, 0x0c);
outb(sym_stest3, 0x80);
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);
spin(200);
outb(sym_stest3, 0xa0);
outb(sym_scntl3, 0x05);
outb(sym_stest1, 0x0c);
outb(sym_stest3, 0x80);
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);
scf = 0;
if(s->sclk < 25010) {
dprintf("symbios%ld: unsupported clock frequency\n",s->num);
goto err;
} else if(s->sclk < 37510){
dprintf("symbios%ld: unsupported clock frequency\n",s->num);
goto err;
} else if(s->sclk < 50010){
scf = 0x10;
s->scntl3 = 0x03;
} else if(s->sclk < 75010){
dprintf("symbios%ld: unsupported clock frequency\n",s->num);
goto err;
} else if(s->sclk < 85000){
scf = 0x30;
s->scntl3 = 0x05;
} else {
scf = 0x50;
s->scntl3 = 0x07;
}
s->maxoffset = (flags & symf_short) ? 8 : 15 ;
s->syncsize = 0;
if(scf == 0x50){
for(i=0;i<4;i++){
s->syncinfo[s->syncsize].sxfer = i << 5;
s->syncinfo[s->syncsize].scntl3 = s->scntl3 | 0x90;
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){
for(i=0;i<4;i++){
s->syncinfo[s->syncsize].sxfer = i << 5;
if(scf == 0x30){
s->syncinfo[s->syncsize].scntl3 = s->scntl3 | 0x90;
} else {
s->syncinfo[s->syncsize].scntl3 = s->scntl3 | 0xb0;
}
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++;
}
}
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;
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) {
break;
}
#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
};