blob: 88edabb351e0756746ea70f24e40460cd40ea41b [file] [log] [blame]
/* $Id: $
* linux/kernel/wd7000.c
*
* Copyright (C) 1992 Thomas Wuensche
* closely related to the aha1542 driver from Tommy Thorn
* ( as close as different hardware allows on a lowlevel-driver :-) )
*
* Revised (and renamed) by John Boyd <boyd@cis.ohio-state.edu> to
* accomodate Eric Youngdale's modifications to scsi.c. Nov 1992.
*
* Additional changes to support scatter/gather. Dec. 1992. tw/jb
*/
#include <stdarg.h>
#include <linux/kernel.h>
#include <linux/head.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <asm/system.h>
#include <asm/dma.h>
#include <asm/io.h>
#include "../blk.h"
#include "scsi.h"
#include "hosts.h"
/* #define DEBUG */
#include "wd7000.h"
#ifdef DEBUG
#define DEB(x) x
#else
#define DEB(x)
#endif
/*
Driver data structures:
- mb and scbs are required for interfacing with the host adapter.
An SCB has extra fields not visible to the adapter; mb's
_cannot_ do this, since the adapter assumes they are contiguous in
memory, 4 bytes each, with ICMBs following OGMBs, and uses this fact
to access them.
- An icb is for host-only (non-SCSI) commands. ICBs are 16 bytes each;
the additional bytes are used only by the driver.
- For now, a pool of SCBs are kept in global storage by this driver,
and are allocated and freed as needed.
The 7000-FASST2 marks OGMBs empty as soon as it has _started_ a command,
not when it has finished. Since the SCB must be around for completion,
problems arise when SCBs correspond to OGMBs, which may be reallocated
earlier (or delayed unnecessarily until a command completes).
Mailboxes are used as transient data structures, simply for
carrying SCB addresses to/from the 7000-FASST2.
Note also since SCBs are not "permanently" associated with mailboxes,
there is no need to keep a global list of Scsi_Cmnd pointers indexed
by OGMB. Again, SCBs reference their Scsi_Cmnds directly, so mailbox
indices need not be involved.
*/
static struct {
struct wd_mailbox ogmb[OGMB_CNT];
struct wd_mailbox icmb[ICMB_CNT];
} mb;
static int next_ogmb = 0; /* to reduce contention at mailboxes */
static Scb scbs[MAX_SCBS];
static Scb *scbfree = NULL;
static int wd7000_host = 0;
static unchar controlstat = 0;
static unchar rev_1 = 0, rev_2 = 0; /* filled in by wd7000_revision */
#define wd7000_intr_ack() outb(0,INTR_ACK)
#define WAITnexttimeout 3000000
static inline void wd7000_enable_intr(void)
{
controlstat |= INT_EN;
outb(controlstat,CONTROL);
}
static inline void wd7000_enable_dma(void)
{
controlstat |= DMA_EN;
outb(controlstat,CONTROL);
set_dma_mode(DMA_CH, DMA_MODE_CASCADE);
enable_dma(DMA_CH);
}
#define WAIT(port, mask, allof, noneof) \
{ register WAITbits; \
register WAITtimeout = WAITnexttimeout; \
while (1) { \
WAITbits = inb(port) & (mask); \
if ((WAITbits & (allof)) == (allof) && ((WAITbits & (noneof)) == 0)) \
break; \
if (--WAITtimeout == 0) goto fail; \
} \
}
static inline void delay( unsigned how_long )
{
unsigned long time = jiffies + how_long;
while (jiffies < time);
}
static inline int command_out(unchar *cmdp, int len)
{
while (len--) {
WAIT(ASC_STAT, STATMASK, CMD_RDY, 0);
outb(*cmdp++, COMMAND);
}
return 1;
fail:
printk("wd7000_out WAIT failed(%d): ", len+1);
return 0;
}
static inline Scb *alloc_scb(void)
{
Scb *scb;
unsigned long flags;
save_flags(flags);
cli();
if (scbfree == NULL) {
panic("wd7000: can't allocate free SCB.\n");
restore_flags(flags);
return NULL;
}
scb = scbfree; scbfree = scb->next;
memset(scb, 0, sizeof(Scb)); scb->next = NULL;
restore_flags(flags);
return scb;
}
static inline void free_scb( Scb *scb )
{
unsigned long flags;
save_flags(flags);
cli();
memset(scb, 0, sizeof(Scb));
scb->next = scbfree; scbfree = scb;
restore_flags(flags);
}
static inline void init_scbs(void)
{
int i;
unsigned long flags;
save_flags(flags);
cli();
scbfree = &(scbs[0]);
for (i = 0; i < MAX_SCBS-1; i++) scbs[i].next = &(scbs[i+1]);
scbs[MAX_SCBS-1].next = NULL;
restore_flags(flags);
}
static int mail_out( Scb *scbptr )
/*
* Note: this can also be used for ICBs; just cast to the parm type.
*/
{
int i, ogmb;
unsigned long flags;
DEB(printk("wd7000_scb_out: %06x");)
/* We first look for a free outgoing mailbox */
save_flags(flags);
cli();
ogmb = next_ogmb;
for (i = 0; i < OGMB_CNT; i++) {
if (mb.ogmb[ogmb].status == 0) {
DEB(printk(" using OGMB %x",ogmb));
mb.ogmb[ogmb].status = 1;
any2scsi(mb.ogmb[ogmb].scbptr, scbptr);
next_ogmb = (ogmb+1) % OGMB_CNT;
break;
} else
ogmb = (++ogmb) % OGMB_CNT;
}
restore_flags(flags);
DEB(printk(", scb is %x",scbptr);)
if (i >= OGMB_CNT) {
DEB(printk(", no free OGMBs.\n");)
/* Alternatively, issue "interrupt on free OGMB", and sleep... */
return 0;
}
wd7000_enable_intr();
do {
WAIT(ASC_STAT,STATMASK,CMD_RDY,0);
outb(START_OGMB|ogmb, COMMAND);
WAIT(ASC_STAT,STATMASK,CMD_RDY,0);
} while (inb(ASC_STAT) & CMD_REJ);
DEB(printk(", awaiting interrupt.\n");)
return 1;
fail:
DEB(printk(", WAIT timed out.\n");)
return 0;
}
int make_code(unsigned hosterr, unsigned scsierr)
{
#ifdef DEBUG
int in_error = hosterr;
#endif
switch ((hosterr>>8)&0xff){
case 0: /* Reserved */
hosterr = DID_ERROR;
break;
case 1: /* Command Complete, no errors */
hosterr = DID_OK;
break;
case 2: /* Command complete, error logged in scb status (scsierr) */
hosterr = DID_OK;
break;
case 4: /* Command failed to complete - timeout */
hosterr = DID_TIME_OUT;
break;
case 5: /* Command terminated; Bus reset by external device */
hosterr = DID_RESET;
break;
case 6: /* Unexpected Command Received w/ host as target */
hosterr = DID_BAD_TARGET;
break;
case 80: /* Unexpected Reselection */
case 81: /* Unexpected Selection */
hosterr = DID_BAD_INTR;
break;
case 82: /* Abort Command Message */
hosterr = DID_ABORT;
break;
case 83: /* SCSI Bus Software Reset */
case 84: /* SCSI Bus Hardware Reset */
hosterr = DID_RESET;
break;
default: /* Reserved */
hosterr = DID_ERROR;
break;
}
#ifdef DEBUG
if (scsierr||hosterr)
printk("\nSCSI command error: SCSI %02x host %04x return %d",
scsierr,in_error,hosterr);
#endif
return scsierr | (hosterr << 16);
}
static void wd7000_scsi_done(Scsi_Cmnd * SCpnt)
{
DEB(printk("wd7000_scsi_done: %06x\n",SCpnt);)
SCpnt->SCp.phase = 0;
}
void wd7000_intr_handle(int irq)
{
int flag, icmb, errstatus, icmb_status;
int host_error, scsi_error;
Scb *scb; /* for SCSI commands */
unchar *icb; /* for host commands */
Scsi_Cmnd *SCpnt;
flag = inb(INTR_STAT);
DEB(printk("wd7000_intr_handle: intr stat = %02x",flag);)
if (!(inb(ASC_STAT)&0x80)){
DEB(printk("\nwd7000_intr_handle: phantom interrupt...\n");)
wd7000_intr_ack();
return;
}
/* check for an incoming mailbox */
if ((flag & 0x40) == 0) {
/* for a free OGMB - need code for this case... */
DEB(printk("wd7000_intr_handle: free outgoing mailbox\n");)
wd7000_intr_ack();
return;
}
/* The interrupt is for an incoming mailbox */
icmb = flag & 0x3f;
scb = (struct scb *) scsi2int(mb.icmb[icmb].scbptr);
icmb_status = mb.icmb[icmb].status;
mb.icmb[icmb].status = 0;
#ifdef DEBUG
printk(" ICMB %d posted for SCB/ICB %06x, status %02x, vue %02x",
icmb, scb, icmb_status, scb->vue );
#endif
if (!(scb->op & 0x80)) { /* an SCB is done */
SCpnt = scb->SCpnt;
if (--(SCpnt->SCp.phase) <= 0) { /* all scbs for SCpnt are done */
host_error = scb->vue | (icmb_status << 8);
scsi_error = scb->status;
errstatus = make_code(host_error,scsi_error);
SCpnt->result = errstatus;
if (SCpnt->host_scribble != NULL)
scsi_free(SCpnt->host_scribble,WD7000_SCRIBBLE);
free_scb(scb);
SCpnt->scsi_done(SCpnt);
}
} else { /* an ICB is done */
icb = (unchar *) scb;
icb[ICB_STATUS] = icmb_status;
icb[ICB_PHASE] = 0;
}
wd7000_intr_ack();
DEB(printk(".\n");)
return;
}
int wd7000_queuecommand(Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *))
{
Scb *scb;
Sgb *sgb;
unchar *cdb;
unchar idlun;
short cdblen;
cdb = (unchar *) SCpnt->cmnd;
cdblen = COMMAND_SIZE(*cdb);
idlun = ((SCpnt->target << 5) & 0xe0) | (SCpnt->lun & 7);
SCpnt->scsi_done = done;
SCpnt->SCp.phase = 1;
scb = alloc_scb();
scb->idlun = idlun;
memcpy(scb->cdb, cdb, cdblen);
scb->direc = 0x40; /* Disable direction check */
scb->SCpnt = SCpnt; /* so we can find stuff later */
SCpnt->host_scribble = NULL;
DEB(printk("request_bufflen is %x, bufflen is %x\n",\
SCpnt->request_bufflen, SCpnt->bufflen);)
if (SCpnt->use_sg) {
struct scatterlist *sg = (struct scatterlist *) SCpnt->request_buffer;
unsigned i;
if (scsi_hosts[wd7000_host].sg_tablesize <= 0) {
panic("wd7000_queuecommand: scatter/gather not supported.\n");
}
#ifdef DEBUG
printk("Using scatter/gather with %d elements.\n",SCpnt->use_sg);
#endif
/*
Allocate memory for a scatter/gather-list in wd7000 format.
Save the pointer at host_scribble.
*/
#ifdef DEBUG
if (SCpnt->use_sg > WD7000_SG)
panic("WD7000: requesting too many scatterblocks\n");
#endif
SCpnt->host_scribble = (unsigned char *) scsi_malloc(WD7000_SCRIBBLE);
sgb = (Sgb *) SCpnt->host_scribble;
if (sgb == NULL)
panic("wd7000_queuecommand: scsi_malloc() failed.\n");
scb->op = 1;
any2scsi(scb->dataptr, sgb);
any2scsi(scb->maxlen, SCpnt->use_sg * sizeof (Sgb) );
for (i = 0; i < SCpnt->use_sg; i++) {
any2scsi(sgb->ptr, sg[i].address);
any2scsi(sgb->len, sg[i].length);
sgb++;
}
DEB(printk("Using %d bytes for %d scatter/gather blocks\n",\
scsi2int(scb->maxlen), SCpnt->use_sg);)
} else {
scb->op = 0;
any2scsi(scb->dataptr, SCpnt->request_buffer);
any2scsi(scb->maxlen, SCpnt->request_bufflen);
}
return mail_out(scb);
}
int wd7000_command(Scsi_Cmnd *SCpnt)
{
wd7000_queuecommand(SCpnt, wd7000_scsi_done);
while (SCpnt->SCp.phase > 0); /* phase counts scbs down to 0 */
return SCpnt->result;
}
int wd7000_init(void)
{ int i;
unchar init_block[] = {
INITIALIZATION, 7, BUS_ON, BUS_OFF, 0, 0, 0, 0, OGMB_CNT, ICMB_CNT
};
/* Reset the adapter. */
outb(SCSI_RES|ASC_RES, CONTROL);
delay(1); /* reset pulse: this is 10ms, only need 25us */
outb(0,CONTROL); controlstat = 0;
/*
Wait 2 seconds, then expect Command Port Ready.
I suspect something else needs to be done here, but I don't know
what. The OEM doc says power-up diagnostics take 2 seconds, and
indeed, SCSI commands submitted before then will time out, but
none of what follows seems deterred by _not_ waiting 2 secs.
*/
delay(200);
WAIT(ASC_STAT, STATMASK, CMD_RDY, 0);
DEB(printk("wd7000_init: Power-on Diagnostics finished\n");)
if (((i=inb(INTR_STAT)) != 1) && (i != 7)) {
panic("wd7000_init: Power-on Diagnostics error\n");
return 0;
}
/* Clear mailboxes */
memset(&mb,0,sizeof (mb));
/* Set up SCB free list */
init_scbs();
/* Set up init block */
any2scsi(init_block+5,&mb);
/* Execute init command */
if (!command_out(init_block,sizeof(init_block))) {
panic("WD-7000 Initialization failed.\n");
return 0;
}
/* Wait until init finished */
WAIT(ASC_STAT, STATMASK, CMD_RDY | ASC_INI, 0);
outb(DISABLE_UNS_INTR, COMMAND);
WAIT(ASC_STAT, STATMASK, CMD_RDY | ASC_INI, 0);
/* Enable Interrupt and DMA */
if (request_irq(IRQ_LVL, wd7000_intr_handle)) {
panic("Unable to allocate IRQ for WD-7000.\n");
return 0;
};
if(request_dma(DMA_CH)) {
panic("Unable to allocate DMA channel for WD-7000.\n");
free_irq(IRQ_LVL);
return 0;
};
wd7000_enable_dma();
wd7000_enable_intr();
printk("WD-7000 initialized.\n");
return 1;
fail:
return 0; /* 0 = not ok */
}
void wd7000_revision(void)
{
volatile unchar icb[ICB_LEN] = {0x8c}; /* read firmware revision level */
icb[ICB_PHASE] = 1;
mail_out( (struct scb *) icb );
while (icb[ICB_PHASE]) /* wait for completion */;
rev_1 = icb[1];
rev_2 = icb[2];
/*
For boards at rev 7.0 or later, enable scatter/gather.
*/
if (rev_1 >= 7) scsi_hosts[wd7000_host].sg_tablesize = WD7000_SG;
}
static const char *wd_bases[] = {(char *)0xce000};
typedef struct {
char * signature;
unsigned offset;
unsigned length;
} Signature;
static const Signature signatures[] = {{"SSTBIOS",0xd,0x7}};
#define NUM_SIGNATURES (sizeof(signatures)/sizeof(Signature))
int wd7000_detect(int hostnum)
/*
* return non-zero on detection
*/
{
int i,j;
char const *base_address = NULL;
for(i=0;i<(sizeof(wd_bases)/sizeof(char *));i++){
for(j=0;j<NUM_SIGNATURES;j++){
if(!memcmp((void *)(wd_bases[i] + signatures[j].offset),
(void *) signatures[j].signature,signatures[j].length)){
base_address=wd_bases[i];
printk("WD-7000 detected.\n");
}
}
}
if (base_address == NULL) return 0;
/* Store our host number */
wd7000_host = hostnum;
wd7000_init();
wd7000_revision(); /* will set scatter/gather by rev level */
return 1;
}
static void wd7000_append_info( char *info, const char *fmt, ... )
/*
* This is just so I can use vsprintf...
*/
{
va_list args;
extern int vsprintf(char *buf, const char *fmt, va_list args);
va_start(args, fmt);
vsprintf(info, fmt, args);
va_end(args);
return;
}
const char *wd7000_info(void)
{
static char info[80] = "Western Digital WD-7000, Firmware Revision ";
wd7000_revision();
wd7000_append_info( info+strlen(info), "%d.%d.\n", rev_1, rev_2 );
return info;
}
int wd7000_abort(Scsi_Cmnd * SCpnt, int i)
{
#ifdef DEBUG
printk("wd7000_abort: Scsi_Cmnd = 0x%08x, code = %d ", SCpnt, i);
printk("id %d lun %d cdb", SCpnt->target, SCpnt->lun);
{ int j; unchar *cdbj = (unchar *) SCpnt->cmnd;
for (j=0; j < COMMAND_SIZE(*cdbj); j++) printk(" %02x", *(cdbj++));
printk(" result %08x\n", SCpnt->result);
}
#endif
return 0;
}
int wd7000_reset(void)
{
#ifdef DEBUG
printk("wd7000_reset\n");
#endif
return 0;
}
int wd7000_biosparam(int size, int dev, int* ip)
/*
* This is borrowed directly from aha1542.c, but my disks are organized
* this way, so I think it will work OK.
*/
{
ip[0] = 64;
ip[1] = 32;
ip[2] = (size + 2047) >> 11;
/* if (ip[2] >= 1024) ip[2] = 1024; */
return 0;
}