wd7000.c
来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,669 行 · 第 1/4 页
C
1,669 行
/* * 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. *//* * WD7000-specific scatter/gather element structure */typedef struct sgb { unchar len[3]; unchar ptr[3]; /* Also SCSI-style - MSB first */} Sgb;typedef struct scb { /* Command Control Block 5.4.1 */ unchar op; /* Command Control Block Operation Code */ unchar idlun; /* op=0,2:Target Id, op=1:Initiator Id */ /* Outbound data transfer, length is checked */ /* Inbound data transfer, length is checked */ /* Logical Unit Number */ unchar cdb[12]; /* SCSI Command Block */ volatile unchar status; /* SCSI Return Status */ volatile unchar vue; /* Vendor Unique Error Code */ unchar maxlen[3]; /* Maximum Data Transfer Length */ unchar dataptr[3]; /* SCSI Data Block Pointer */ unchar linkptr[3]; /* Next Command Link Pointer */ unchar direc; /* Transfer Direction */ unchar reserved2[6]; /* SCSI Command Descriptor Block */ /* end of hardware SCB */ struct scsi_cmnd *SCpnt;/* scsi_cmnd using this SCB */ Sgb sgb[WD7000_SG]; /* Scatter/gather list for this SCB */ Adapter *host; /* host adapter */ struct scb *next; /* for lists of scbs */} Scb;/* * This driver is written to allow host-only commands to be executed. * These use a 16-byte block called an ICB. The format is extended by the * driver to 18 bytes, to support the status returned in the ICMB and * an execution phase code. * * There are other formats besides these; these are the ones I've tried * to use. Formats for some of the defined ICB opcodes are not defined * (notably, get/set unsolicited interrupt status) in my copy of the OEM * manual, and others are ambiguous/hard to follow. */#define ICB_OP_MASK 0x80 /* distinguishes scbs from icbs */#define ICB_OP_OPEN_RBUF 0x80 /* open receive buffer */#define ICB_OP_RECV_CMD 0x81 /* receive command from initiator */#define ICB_OP_RECV_DATA 0x82 /* receive data from initiator */#define ICB_OP_RECV_SDATA 0x83 /* receive data with status from init. */#define ICB_OP_SEND_DATA 0x84 /* send data with status to initiator */#define ICB_OP_SEND_STAT 0x86 /* send command status to initiator */ /* 0x87 is reserved */#define ICB_OP_READ_INIT 0x88 /* read initialization bytes */#define ICB_OP_READ_ID 0x89 /* read adapter's SCSI ID */#define ICB_OP_SET_UMASK 0x8A /* set unsolicited interrupt mask */#define ICB_OP_GET_UMASK 0x8B /* read unsolicited interrupt mask */#define ICB_OP_GET_REVISION 0x8C /* read firmware revision level */#define ICB_OP_DIAGNOSTICS 0x8D /* execute diagnostics */#define ICB_OP_SET_EPARMS 0x8E /* set execution parameters */#define ICB_OP_GET_EPARMS 0x8F /* read execution parameters */typedef struct icbRecvCmd { unchar op; unchar IDlun; /* Initiator SCSI ID/lun */ unchar len[3]; /* command buffer length */ unchar ptr[3]; /* command buffer address */ unchar rsvd[7]; /* reserved */ volatile unchar vue; /* vendor-unique error code */ volatile unchar status; /* returned (icmb) status */ volatile unchar phase; /* used by interrupt handler */} IcbRecvCmd;typedef struct icbSendStat { unchar op; unchar IDlun; /* Target SCSI ID/lun */ unchar stat; /* (outgoing) completion status byte 1 */ unchar rsvd[12]; /* reserved */ volatile unchar vue; /* vendor-unique error code */ volatile unchar status; /* returned (icmb) status */ volatile unchar phase; /* used by interrupt handler */} IcbSendStat;typedef struct icbRevLvl { unchar op; volatile unchar primary; /* primary revision level (returned) */ volatile unchar secondary; /* secondary revision level (returned) */ unchar rsvd[12]; /* reserved */ volatile unchar vue; /* vendor-unique error code */ volatile unchar status; /* returned (icmb) status */ volatile unchar phase; /* used by interrupt handler */} IcbRevLvl;typedef struct icbUnsMask { /* I'm totally guessing here */ unchar op; volatile unchar mask[14]; /* mask bits */#if 0 unchar rsvd[12]; /* reserved */#endif volatile unchar vue; /* vendor-unique error code */ volatile unchar status; /* returned (icmb) status */ volatile unchar phase; /* used by interrupt handler */} IcbUnsMask;typedef struct icbDiag { unchar op; unchar type; /* diagnostics type code (0-3) */ unchar len[3]; /* buffer length */ unchar ptr[3]; /* buffer address */ unchar rsvd[7]; /* reserved */ volatile unchar vue; /* vendor-unique error code */ volatile unchar status; /* returned (icmb) status */ volatile unchar phase; /* used by interrupt handler */} IcbDiag;#define ICB_DIAG_POWERUP 0 /* Power-up diags only */#define ICB_DIAG_WALKING 1 /* walking 1's pattern */#define ICB_DIAG_DMA 2 /* DMA - system memory diags */#define ICB_DIAG_FULL 3 /* do both 1 & 2 */typedef struct icbParms { unchar op; unchar rsvd1; /* reserved */ unchar len[3]; /* parms buffer length */ unchar ptr[3]; /* parms buffer address */ unchar idx[2]; /* index (MSB-LSB) */ unchar rsvd2[5]; /* reserved */ volatile unchar vue; /* vendor-unique error code */ volatile unchar status; /* returned (icmb) status */ volatile unchar phase; /* used by interrupt handler */} IcbParms;typedef struct icbAny { unchar op; unchar data[14]; /* format-specific data */ volatile unchar vue; /* vendor-unique error code */ volatile unchar status; /* returned (icmb) status */ volatile unchar phase; /* used by interrupt handler */} IcbAny;typedef union icb { unchar op; /* ICB opcode */ IcbRecvCmd recv_cmd; /* format for receive command */ IcbSendStat send_stat; /* format for send status */ IcbRevLvl rev_lvl; /* format for get revision level */ IcbDiag diag; /* format for execute diagnostics */ IcbParms eparms; /* format for get/set exec parms */ IcbAny icb; /* generic format */ unchar data[18];} Icb;#ifdef MODULEstatic char *wd7000;MODULE_PARM(wd7000, "s");#endif/* * Driver SCB structure pool. * * The SCBs declared here are shared by all host adapters; hence, this * structure is not part of the Adapter structure. */static Scb scbs[MAX_SCBS];static Scb *scbfree; /* free list */static int freescbs = MAX_SCBS; /* free list counter */static spinlock_t scbpool_lock; /* guards the scb free list and count *//* * END of data/declarations - code follows. */static void __init setup_error(char *mesg, int *ints){ if (ints[0] == 3) printk(KERN_ERR "wd7000_setup: \"wd7000=%d,%d,0x%x\" -> %s\n", ints[1], ints[2], ints[3], mesg); else if (ints[0] == 4) printk(KERN_ERR "wd7000_setup: \"wd7000=%d,%d,0x%x,%d\" -> %s\n", ints[1], ints[2], ints[3], ints[4], mesg); else printk(KERN_ERR "wd7000_setup: \"wd7000=%d,%d,0x%x,%d,%d\" -> %s\n", ints[1], ints[2], ints[3], ints[4], ints[5], mesg);}/* * Note: You can now set these options from the kernel's "command line". * The syntax is: * * wd7000=<IRQ>,<DMA>,<IO>[,<BUS_ON>[,<BUS_OFF>]] * * , where BUS_ON and BUS_OFF are in nanoseconds. BIOS default values * are 8000ns for BUS_ON and 1875ns for BUS_OFF. * eg: * wd7000=7,6,0x350 * * will configure the driver for a WD-7000 controller * using IRQ 15 with a DMA channel 6, at IO base address 0x350. */static int __init wd7000_setup(char *str){ static short wd7000_card_num; /* .bss will zero this */ short i; int ints[6]; (void) get_options(str, ARRAY_SIZE(ints), ints); if (wd7000_card_num >= NUM_CONFIGS) { printk(KERN_ERR "%s: Too many \"wd7000=\" configurations in " "command line!\n", __FUNCTION__); return 0; } if ((ints[0] < 3) || (ints[0] > 5)) { printk(KERN_ERR "%s: Error in command line! " "Usage: wd7000=<IRQ>,<DMA>,IO>[,<BUS_ON>" "[,<BUS_OFF>]]\n", __FUNCTION__); } else { for (i = 0; i < NUM_IRQS; i++) if (ints[1] == wd7000_irq[i]) break; if (i == NUM_IRQS) { setup_error("invalid IRQ.", ints); return 0; } else configs[wd7000_card_num].irq = ints[1]; for (i = 0; i < NUM_DMAS; i++) if (ints[2] == wd7000_dma[i]) break; if (i == NUM_DMAS) { setup_error("invalid DMA channel.", ints); return 0; } else configs[wd7000_card_num].dma = ints[2]; for (i = 0; i < NUM_IOPORTS; i++) if (ints[3] == wd7000_iobase[i]) break; if (i == NUM_IOPORTS) { setup_error("invalid I/O base address.", ints); return 0; } else configs[wd7000_card_num].iobase = ints[3]; if (ints[0] > 3) { if ((ints[4] < 500) || (ints[4] > 31875)) { setup_error("BUS_ON value is out of range (500" " to 31875 nanoseconds)!", ints); configs[wd7000_card_num].bus_on = BUS_ON; } else configs[wd7000_card_num].bus_on = ints[4] / 125; } else configs[wd7000_card_num].bus_on = BUS_ON; if (ints[0] > 4) { if ((ints[5] < 500) || (ints[5] > 31875)) { setup_error("BUS_OFF value is out of range (500" " to 31875 nanoseconds)!", ints); configs[wd7000_card_num].bus_off = BUS_OFF; } else configs[wd7000_card_num].bus_off = ints[5] / 125; } else configs[wd7000_card_num].bus_off = BUS_OFF; if (wd7000_card_num) { for (i = 0; i < (wd7000_card_num - 1); i++) { int j = i + 1; for (; j < wd7000_card_num; j++) if (configs[i].irq == configs[j].irq) { setup_error("duplicated IRQ!", ints); return 0; } if (configs[i].dma == configs[j].dma) { setup_error("duplicated DMA " "channel!", ints); return 0; } if (configs[i].iobase == configs[j].iobase) { setup_error("duplicated I/O " "base address!", ints); return 0; } } } dprintk(KERN_DEBUG "wd7000_setup: IRQ=%d, DMA=%d, I/O=0x%x, " "BUS_ON=%dns, BUS_OFF=%dns\n", configs[wd7000_card_num].irq, configs[wd7000_card_num].dma, configs[wd7000_card_num].iobase, configs[wd7000_card_num].bus_on * 125, configs[wd7000_card_num].bus_off * 125); wd7000_card_num++; } return 1;}__setup("wd7000=", wd7000_setup);static inline void any2scsi(unchar * scsi, int any){ *scsi++ = (unsigned)any >> 16; *scsi++ = (unsigned)any >> 8; *scsi++ = any;}static inline int scsi2int(unchar * scsi){ return (scsi[0] << 16) | (scsi[1] << 8) | scsi[2];}static inline void wd7000_enable_intr(Adapter * host){ host->control |= INT_EN; outb(host->control, host->iobase + ASC_CONTROL);}static inline void wd7000_enable_dma(Adapter * host){ unsigned long flags; host->control |= DMA_EN; outb(host->control, host->iobase + ASC_CONTROL); flags = claim_dma_lock(); set_dma_mode(host->dma, DMA_MODE_CASCADE); enable_dma(host->dma); release_dma_lock(flags);}#define WAITnexttimeout 200 /* 2 seconds */static inline short WAIT(unsigned port, unsigned mask, unsigned allof, unsigned noneof){ unsigned WAITbits; unsigned long WAITtimeout = jiffies + WAITnexttimeout; while (time_before_eq(jiffies, WAITtimeout)) { WAITbits = inb(port) & mask; if (((WAITbits & allof) == allof) && ((WAITbits & noneof) == 0)) return (0); } return (1);}static inline int command_out(Adapter * host, unchar * cmd, int len){ if (!WAIT(host->iobase + ASC_STAT, ASC_STATMASK, CMD_RDY, 0)) { while (len--) { do { outb(*cmd, host->iobase + ASC_COMMAND); WAIT(host->iobase + ASC_STAT, ASC_STATMASK, CMD_RDY, 0); } while (inb(host->iobase + ASC_STAT) & CMD_REJ); cmd++; } return (1); } printk(KERN_WARNING "wd7000 command_out: WAIT failed(%d)\n", len + 1); return (0);}/* * This version of alloc_scbs is in preparation for supporting multiple * commands per lun and command chaining, by queueing pending commands. * We will need to allocate Scbs in blocks since they will wait to be * executed so there is the possibility of deadlock otherwise. * Also, to keep larger requests from being starved by smaller requests, * we limit access to this routine with an internal busy flag, so that * the satisfiability of a request is not dependent on the size of the * request. */static inline Scb *alloc_scbs(struct Scsi_Host *host, int needed){ Scb *scb, *p = NULL; unsigned long flags; unsigned long timeout = jiffies + WAITnexttimeout; unsigned long now; int i; if (needed <= 0) return (NULL); /* sanity check */ spin_unlock_irq(host->host_lock); retry: while (freescbs < needed) { timeout = jiffies + WAITnexttimeout; do { /* FIXME: can we actually just yield here ?? */ for (now = jiffies; now == jiffies;) cpu_relax(); /* wait a jiffy */ } while (freescbs < needed && time_before_eq(jiffies, timeout)); /* * If we get here with enough free Scbs, we can take them. * Otherwise, we timed out and didn't get enough. */ if (freescbs < needed) { printk(KERN_ERR "wd7000: can't get enough free SCBs.\n");
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?