| /* $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; |
| } |
| |