home *** CD-ROM | disk | FTP | other *** search
- /* $Id: wd7000.c,v 1.2 1994/01/15 06:02:32 drew Exp $
- * 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 <linux/ioport.h>
-
- #include "../block/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,(char *)0xd8000};
-
- 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;
-
- if(check_region(IO_BASE, 4)) return 0; /* IO ports in use */
- 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;
-
- snarf_region(IO_BASE, 4); /* Register our ports */
- /* 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;
- }
-
-
- /* We do not implement a reset function here, but the upper level code assumes
- that it will get some kind of response for the command in SCpnt. We must
- oblige, or the command will hang the scsi system */
-
- int wd7000_reset(Scsi_Cmnd * SCpnt)
- {
- #ifdef DEBUG
- printk("wd7000_reset\n");
- #endif
- if (SCpnt) SCpnt->flags |= NEEDS_JUMPSTART;
- 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 >> 11;
- /* if (ip[2] >= 1024) ip[2] = 1024; */
- return 0;
- }
-
-