diff options
Diffstat (limited to 'linux/src/drivers/scsi')
96 files changed, 137575 insertions, 0 deletions
diff --git a/linux/src/drivers/scsi/53c7,8xx.h b/linux/src/drivers/scsi/53c7,8xx.h new file mode 100644 index 00000000..80fbad3b --- /dev/null +++ b/linux/src/drivers/scsi/53c7,8xx.h @@ -0,0 +1,1584 @@ +/* + * NCR 53c{7,8}0x0 driver, header file + * + * Sponsored by + * iX Multiuser Multitasking Magazine + * Hannover, Germany + * hm@ix.de + * + * Copyright 1993, 1994, 1995 Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@PoohSticks.ORG + * +1 (303) 786-7975 + * + * TolerANT and SCSI SCRIPTS are registered trademarks of NCR Corporation. + * + * PRE-ALPHA + * + * For more information, please consult + * + * NCR 53C700/53C700-66 + * SCSI I/O Processor + * Data Manual + * + * NCR 53C810 + * PCI-SCSI I/O Processor + * Data Manual + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * +1 (719) 578-3400 + * + * Toll free literature number + * +1 (800) 334-5454 + * + */ + +#ifndef NCR53c7x0_H +#define NCR53c7x0_H +#if !defined(LINUX_1_2) && !defined(LINUX_1_3) +#include <linux/version.h> +#if LINUX_VERSION_CODE > 65536 + 3 * 256 +#define LINUX_1_3 +#else +#define LINUX_1_2 +#endif +#endif + +/* + * Prevent name space pollution in hosts.c, and only provide the + * define we need to get the NCR53c7x0 driver into the host template + * array. + */ + +#if defined(HOSTS_C) || defined(MODULE) +#include <scsi/scsicam.h> + +extern int NCR53c7xx_abort(Scsi_Cmnd *); +extern int NCR53c7xx_detect(Scsi_Host_Template *tpnt); +extern int NCR53c7xx_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +extern int NCR53c7xx_reset(Scsi_Cmnd *, unsigned int); +#ifdef MODULE +extern int NCR53c7xx_release(struct Scsi_Host *); +#else +#define NCR53c7xx_release NULL +#endif + +#ifdef LINUX_1_2 +#define NCR53c7xx {NULL, NULL, "NCR53c{7,8}xx (rel 17)", NCR53c7xx_detect,\ + NULL, /* info */ NULL, /* command, deprecated */ NULL, \ + NCR53c7xx_queue_command, NCR53c7xx_abort, NCR53c7xx_reset, \ + NULL /* slave attach */, scsicam_bios_param, /* can queue */ 24, \ + /* id */ 7, 127 /* old SG_ALL */, /* cmd per lun */ 3, \ + /* present */ 0, /* unchecked isa dma */ 0, DISABLE_CLUSTERING} +#else +#define NCR53c7xx {NULL, NULL, NULL, NULL, \ + "NCR53c{7,8}xx (rel 17)", NCR53c7xx_detect,\ + NULL, /* info */ NULL, /* command, deprecated */ NULL, \ + NCR53c7xx_queue_command, NCR53c7xx_abort, NCR53c7xx_reset, \ + NULL /* slave attach */, scsicam_bios_param, /* can queue */ 24, \ + /* id */ 7, 127 /* old SG_ALL */, /* cmd per lun */ 3, \ + /* present */ 0, /* unchecked isa dma */ 0, DISABLE_CLUSTERING} +#endif + +#endif /* defined(HOSTS_C) || defined(MODULE) */ + +#ifndef HOSTS_C +#ifdef LINUX_1_2 +/* + * Change virtual addresses to physical addresses and vv. + * These are trivial on the 1:1 Linux/i386 mapping (but if we ever + * make the kernel segment mapped at 0, we need to do translation + * on the i386 as well) + */ +extern inline unsigned long virt_to_phys(volatile void * address) +{ + return (unsigned long) address; +} + +extern inline void * phys_to_virt(unsigned long address) +{ + return (void *) address; +} + +/* + * IO bus memory addresses are also 1:1 with the physical address + */ +#define virt_to_bus virt_to_phys +#define bus_to_virt phys_to_virt + +/* + * readX/writeX() are used to access memory mapped devices. On some + * architectures the memory mapped IO stuff needs to be accessed + * differently. On the x86 architecture, we just read/write the + * memory location directly. + */ +#define readb(addr) (*(volatile unsigned char *) (addr)) +#define readw(addr) (*(volatile unsigned short *) (addr)) +#define readl(addr) (*(volatile unsigned int *) (addr)) + +#define writeb(b,addr) ((*(volatile unsigned char *) (addr)) = (b)) +#define writew(b,addr) ((*(volatile unsigned short *) (addr)) = (b)) +#define writel(b,addr) ((*(volatile unsigned int *) (addr)) = (b)) + +#define mb() + +#endif /* def LINUX_1_2 */ + +/* Register addresses, ordered numerically */ + +/* SCSI control 0 rw, default = 0xc0 */ +#define SCNTL0_REG 0x00 +#define SCNTL0_ARB1 0x80 /* 0 0 = simple arbitration */ +#define SCNTL0_ARB2 0x40 /* 1 1 = full arbitration */ +#define SCNTL0_STRT 0x20 /* Start Sequence */ +#define SCNTL0_WATN 0x10 /* Select with ATN */ +#define SCNTL0_EPC 0x08 /* Enable parity checking */ +/* Bit 2 is reserved on 800 series chips */ +#define SCNTL0_EPG_700 0x04 /* Enable parity generation */ +#define SCNTL0_AAP 0x02 /* ATN/ on parity error */ +#define SCNTL0_TRG 0x01 /* Target mode */ + +/* SCSI control 1 rw, default = 0x00 */ + +#define SCNTL1_REG 0x01 +#define SCNTL1_EXC 0x80 /* Extra Clock Cycle of Data setup */ +#define SCNTL1_ADB 0x40 /* contents of SODL on bus */ +#define SCNTL1_ESR_700 0x20 /* Enable SIOP response to selection + and reselection */ +#define SCNTL1_DHP_800 0x20 /* Disable halt on parity error or ATN + target mode only */ +#define SCNTL1_CON 0x10 /* Connected */ +#define SCNTL1_RST 0x08 /* SCSI RST/ */ +#define SCNTL1_AESP 0x04 /* Force bad parity */ +#define SCNTL1_SND_700 0x02 /* Start SCSI send */ +#define SCNTL1_IARB_800 0x02 /* Immediate Arbitration, start + arbitration immediately after + busfree is detected */ +#define SCNTL1_RCV_700 0x01 /* Start SCSI receive */ +#define SCNTL1_SST_800 0x01 /* Start SCSI transfer */ + +/* SCSI control 2 rw, */ + +#define SCNTL2_REG_800 0x02 +#define SCNTL2_800_SDU 0x80 /* SCSI disconnect unexpected */ + +/* SCSI control 3 rw */ + +#define SCNTL3_REG_800 0x03 +#define SCNTL3_800_SCF_SHIFT 4 +#define SCNTL3_800_SCF_MASK 0x70 +#define SCNTL3_800_SCF2 0x40 /* Synchronous divisor */ +#define SCNTL3_800_SCF1 0x20 /* 0x00 = SCLK/3 */ +#define SCNTL3_800_SCF0 0x10 /* 0x10 = SCLK/1 */ + /* 0x20 = SCLK/1.5 + 0x30 = SCLK/2 + 0x40 = SCLK/3 */ + +#define SCNTL3_800_CCF_SHIFT 0 +#define SCNTL3_800_CCF_MASK 0x07 +#define SCNTL3_800_CCF2 0x04 /* 0x00 50.01 to 66 */ +#define SCNTL3_800_CCF1 0x02 /* 0x01 16.67 to 25 */ +#define SCNTL3_800_CCF0 0x01 /* 0x02 25.01 - 37.5 + 0x03 37.51 - 50 + 0x04 50.01 - 66 */ + +/* + * SCSI destination ID rw - the appropriate bit is set for the selected + * target ID. This is written by the SCSI SCRIPTS processor. + * default = 0x00 + */ +#define SDID_REG_700 0x02 +#define SDID_REG_800 0x06 + +#define GP_REG_800 0x07 /* General purpose IO */ +#define GP_800_IO1 0x02 +#define GP_800_IO2 0x01 + + +/* SCSI interrupt enable rw, default = 0x00 */ +#define SIEN_REG_700 0x03 +#define SIEN0_REG_800 0x40 +#define SIEN_MA 0x80 /* Phase mismatch (ini) or ATN (tgt) */ +#define SIEN_FC 0x40 /* Function complete */ +#define SIEN_700_STO 0x20 /* Selection or reselection timeout */ +#define SIEN_800_SEL 0x20 /* Selected */ +#define SIEN_700_SEL 0x10 /* Selected or reselected */ +#define SIEN_800_RESEL 0x10 /* Reselected */ +#define SIEN_SGE 0x08 /* SCSI gross error */ +#define SIEN_UDC 0x04 /* Unexpected disconnect */ +#define SIEN_RST 0x02 /* SCSI RST/ received */ +#define SIEN_PAR 0x01 /* Parity error */ + +/* + * SCSI chip ID rw + * NCR53c700 : + * When arbitrating, the highest bit is used, when reselection or selection + * occurs, the chip responds to all IDs for which a bit is set. + * default = 0x00 + * NCR53c810 : + * Uses bit mapping + */ +#define SCID_REG 0x04 +/* Bit 7 is reserved on 800 series chips */ +#define SCID_800_RRE 0x40 /* Enable response to reselection */ +#define SCID_800_SRE 0x20 /* Enable response to selection */ +/* Bits four and three are reserved on 800 series chips */ +#define SCID_800_ENC_MASK 0x07 /* Encoded SCSI ID */ + +/* SCSI transfer rw, default = 0x00 */ +#define SXFER_REG 0x05 +#define SXFER_DHP 0x80 /* Disable halt on parity */ + +#define SXFER_TP2 0x40 /* Transfer period msb */ +#define SXFER_TP1 0x20 +#define SXFER_TP0 0x10 /* lsb */ +#define SXFER_TP_MASK 0x70 +/* FIXME : SXFER_TP_SHIFT == 5 is right for '8xx chips */ +#define SXFER_TP_SHIFT 5 +#define SXFER_TP_4 0x00 /* Divisors */ +#define SXFER_TP_5 0x10<<1 +#define SXFER_TP_6 0x20<<1 +#define SXFER_TP_7 0x30<<1 +#define SXFER_TP_8 0x40<<1 +#define SXFER_TP_9 0x50<<1 +#define SXFER_TP_10 0x60<<1 +#define SXFER_TP_11 0x70<<1 + +#define SXFER_MO3 0x08 /* Max offset msb */ +#define SXFER_MO2 0x04 +#define SXFER_MO1 0x02 +#define SXFER_MO0 0x01 /* lsb */ +#define SXFER_MO_MASK 0x0f +#define SXFER_MO_SHIFT 0 + +/* + * SCSI output data latch rw + * The contents of this register are driven onto the SCSI bus when + * the Assert Data Bus bit of the SCNTL1 register is set and + * the CD, IO, and MSG bits of the SOCL register match the SCSI phase + */ +#define SODL_REG_700 0x06 +#define SODL_REG_800 0x54 + + +/* + * SCSI output control latch rw, default = 0 + * Note that when the chip is being manually programmed as an initiator, + * the MSG, CD, and IO bits must be set correctly for the phase the target + * is driving the bus in. Otherwise no data transfer will occur due to + * phase mismatch. + */ + +#define SBCL_REG 0x0b +#define SBCL_REQ 0x80 /* REQ */ +#define SBCL_ACK 0x40 /* ACK */ +#define SBCL_BSY 0x20 /* BSY */ +#define SBCL_SEL 0x10 /* SEL */ +#define SBCL_ATN 0x08 /* ATN */ +#define SBCL_MSG 0x04 /* MSG */ +#define SBCL_CD 0x02 /* C/D */ +#define SBCL_IO 0x01 /* I/O */ +#define SBCL_PHASE_CMDOUT SBCL_CD +#define SBCL_PHASE_DATAIN SBCL_IO +#define SBCL_PHASE_DATAOUT 0 +#define SBCL_PHASE_MSGIN (SBCL_CD|SBCL_IO|SBCL_MSG) +#define SBCL_PHASE_MSGOUT (SBCL_CD|SBCL_MSG) +#define SBCL_PHASE_STATIN (SBCL_CD|SBCL_IO) +#define SBCL_PHASE_MASK (SBCL_CD|SBCL_IO|SBCL_MSG) + +/* + * SCSI first byte received latch ro + * This register contains the first byte received during a block MOVE + * SCSI SCRIPTS instruction, including + * + * Initiator mode Target mode + * Message in Command + * Status Message out + * Data in Data out + * + * It also contains the selecting or reselecting device's ID and our + * ID. + * + * Note that this is the register the various IF conditionals can + * operate on. + */ +#define SFBR_REG 0x08 + +/* + * SCSI input data latch ro + * In initiator mode, data is latched into this register on the rising + * edge of REQ/. In target mode, data is latched on the rising edge of + * ACK/ + */ +#define SIDL_REG_700 0x09 +#define SIDL_REG_800 0x50 + +/* + * SCSI bus data lines ro + * This register reflects the instantaneous status of the SCSI data + * lines. Note that SCNTL0 must be set to disable parity checking, + * otherwise reading this register will latch new parity. + */ +#define SBDL_REG_700 0x0a +#define SBDL_REG_800 0x58 + +#define SSID_REG_800 0x0a +#define SSID_800_VAL 0x80 /* Exactly two bits asserted at sel */ +#define SSID_800_ENCID_MASK 0x07 /* Device which performed operation */ + + +/* + * SCSI bus control lines rw, + * instantaneous readout of control lines + */ +#define SOCL_REG 0x0b +#define SOCL_REQ 0x80 /* REQ ro */ +#define SOCL_ACK 0x40 /* ACK ro */ +#define SOCL_BSY 0x20 /* BSY ro */ +#define SOCL_SEL 0x10 /* SEL ro */ +#define SOCL_ATN 0x08 /* ATN ro */ +#define SOCL_MSG 0x04 /* MSG ro */ +#define SOCL_CD 0x02 /* C/D ro */ +#define SOCL_IO 0x01 /* I/O ro */ +/* + * Synchronous SCSI Clock Control bits + * 0 - set by DCNTL + * 1 - SCLK / 1.0 + * 2 - SCLK / 1.5 + * 3 - SCLK / 2.0 + */ +#define SBCL_SSCF1 0x02 /* wo, -66 only */ +#define SBCL_SSCF0 0x01 /* wo, -66 only */ +#define SBCL_SSCF_MASK 0x03 + +/* + * XXX note : when reading the DSTAT and STAT registers to clear interrupts, + * insure that 10 clocks elapse between the two + */ +/* DMA status ro */ +#define DSTAT_REG 0x0c +#define DSTAT_DFE 0x80 /* DMA FIFO empty */ +#define DSTAT_800_MDPE 0x40 /* Master Data Parity Error */ +#define DSTAT_800_BF 0x20 /* Bus Fault */ +#define DSTAT_ABRT 0x10 /* Aborted - set on error */ +#define DSTAT_SSI 0x08 /* SCRIPTS single step interrupt */ +#define DSTAT_SIR 0x04 /* SCRIPTS interrupt received - + set when INT instruction is + executed */ +#define DSTAT_WTD 0x02 /* Watchdog timeout detected */ +#define DSTAT_OPC 0x01 /* Illegal instruction */ +#define DSTAT_800_IID 0x01 /* Same thing, different name */ + + +/* NCR53c800 moves this stuff into SIST0 */ +#define SSTAT0_REG 0x0d /* SCSI status 0 ro */ +#define SIST0_REG_800 0x42 +#define SSTAT0_MA 0x80 /* ini : phase mismatch, + * tgt : ATN/ asserted + */ +#define SSTAT0_CMP 0x40 /* function complete */ +#define SSTAT0_700_STO 0x20 /* Selection or reselection timeout */ +#define SIST0_800_SEL 0x20 /* Selected */ +#define SSTAT0_700_SEL 0x10 /* Selected or reselected */ +#define SIST0_800_RSL 0x10 /* Reselected */ +#define SSTAT0_SGE 0x08 /* SCSI gross error */ +#define SSTAT0_UDC 0x04 /* Unexpected disconnect */ +#define SSTAT0_RST 0x02 /* SCSI RST/ received */ +#define SSTAT0_PAR 0x01 /* Parity error */ + +/* And uses SSTAT0 for what was SSTAT1 */ + +#define SSTAT1_REG 0x0e /* SCSI status 1 ro */ +#define SSTAT1_ILF 0x80 /* SIDL full */ +#define SSTAT1_ORF 0x40 /* SODR full */ +#define SSTAT1_OLF 0x20 /* SODL full */ +#define SSTAT1_AIP 0x10 /* Arbitration in progress */ +#define SSTAT1_LOA 0x08 /* Lost arbitration */ +#define SSTAT1_WOA 0x04 /* Won arbitration */ +#define SSTAT1_RST 0x02 /* Instant readout of RST/ */ +#define SSTAT1_SDP 0x01 /* Instant readout of SDP/ */ + +#define SSTAT2_REG 0x0f /* SCSI status 2 ro */ +#define SSTAT2_FF3 0x80 /* number of bytes in synchronous */ +#define SSTAT2_FF2 0x40 /* data FIFO */ +#define SSTAT2_FF1 0x20 +#define SSTAT2_FF0 0x10 +#define SSTAT2_FF_MASK 0xf0 +#define SSTAT2_FF_SHIFT 4 + +/* + * Latched signals, latched on the leading edge of REQ/ for initiators, + * ACK/ for targets. + */ +#define SSTAT2_SDP 0x08 /* SDP */ +#define SSTAT2_MSG 0x04 /* MSG */ +#define SSTAT2_CD 0x02 /* C/D */ +#define SSTAT2_IO 0x01 /* I/O */ +#define SSTAT2_PHASE_CMDOUT SSTAT2_CD +#define SSTAT2_PHASE_DATAIN SSTAT2_IO +#define SSTAT2_PHASE_DATAOUT 0 +#define SSTAT2_PHASE_MSGIN (SSTAT2_CD|SSTAT2_IO|SSTAT2_MSG) +#define SSTAT2_PHASE_MSGOUT (SSTAT2_CD|SSTAT2_MSG) +#define SSTAT2_PHASE_STATIN (SSTAT2_CD|SSTAT2_IO) +#define SSTAT2_PHASE_MASK (SSTAT2_CD|SSTAT2_IO|SSTAT2_MSG) + + +/* NCR53c700-66 only */ +#define SCRATCHA_REG_00 0x10 /* through 0x13 Scratch A rw */ +/* NCR53c710 and higher */ +#define DSA_REG 0x10 /* DATA structure address */ + +#define CTEST0_REG_700 0x14 /* Chip test 0 ro */ +#define CTEST0_REG_800 0x18 /* Chip test 0 rw, general purpose */ +/* 0x80 - 0x04 are reserved */ +#define CTEST0_700_RTRG 0x02 /* Real target mode */ +#define CTEST0_700_DDIR 0x01 /* Data direction, 1 = + * SCSI bus to host, 0 = + * host to SCSI. + */ + +#define CTEST1_REG_700 0x15 /* Chip test 1 ro */ +#define CTEST1_REG_800 0x19 /* Chip test 1 ro */ +#define CTEST1_FMT3 0x80 /* Identify which byte lanes are empty */ +#define CTEST1_FMT2 0x40 /* in the DMA FIFO */ +#define CTEST1_FMT1 0x20 +#define CTEST1_FMT0 0x10 + +#define CTEST1_FFL3 0x08 /* Identify which bytes lanes are full */ +#define CTEST1_FFL2 0x04 /* in the DMA FIFO */ +#define CTEST1_FFL1 0x02 +#define CTEST1_FFL0 0x01 + +#define CTEST2_REG_700 0x16 /* Chip test 2 ro */ +#define CTEST2_REG_800 0x1a /* Chip test 2 ro */ + +#define CTEST2_800_DDIR 0x80 /* 1 = SCSI->host */ +#define CTEST2_800_SIGP 0x40 /* A copy of SIGP in ISTAT. + Reading this register clears */ +#define CTEST2_800_CIO 0x20 /* Configured as IO */. +#define CTEST2_800_CM 0x10 /* Configured as memory */ + +/* 0x80 - 0x40 are reserved on 700 series chips */ +#define CTEST2_700_SOFF 0x20 /* SCSI Offset Compare, + * As an initiator, this bit is + * one when the synchronous offset + * is zero, as a target this bit + * is one when the synchronous + * offset is at the maximum + * defined in SXFER + */ +#define CTEST2_700_SFP 0x10 /* SCSI FIFO parity bit, + * reading CTEST3 unloads a byte + * from the FIFO and sets this + */ +#define CTEST2_700_DFP 0x08 /* DMA FIFO parity bit, + * reading CTEST6 unloads a byte + * from the FIFO and sets this + */ +#define CTEST2_TEOP 0x04 /* SCSI true end of process, + * indicates a totally finished + * transfer + */ +#define CTEST2_DREQ 0x02 /* Data request signal */ +/* 0x01 is reserved on 700 series chips */ +#define CTEST2_800_DACK 0x01 + +/* + * Chip test 3 ro + * Unloads the bottom byte of the eight deep SCSI synchronous FIFO, + * check SSTAT2 FIFO full bits to determine size. Note that a GROSS + * error results if a read is attempted on this register. Also note + * that 16 and 32 bit reads of this register will cause corruption. + */ +#define CTEST3_REG_700 0x17 +/* Chip test 3 rw */ +#define CTEST3_REG_800 0x1b +#define CTEST3_800_V3 0x80 /* Chip revision */ +#define CTEST3_800_V2 0x40 +#define CTEST3_800_V1 0x20 +#define CTEST3_800_V0 0x10 +#define CTEST3_800_FLF 0x08 /* Flush DMA FIFO */ +#define CTEST3_800_CLF 0x04 /* Clear DMA FIFO */ +#define CTEST3_800_FM 0x02 /* Fetch mode pin */ +/* bit 0 is reserved on 800 series chips */ + +#define CTEST4_REG_700 0x18 /* Chip test 4 rw */ +#define CTEST4_REG_800 0x21 /* Chip test 4 rw */ +/* 0x80 is reserved on 700 series chips */ +#define CTEST4_800_BDIS 0x80 /* Burst mode disable */ +#define CTEST4_ZMOD 0x40 /* High impedance mode */ +#define CTEST4_SZM 0x20 /* SCSI bus high impedance */ +#define CTEST4_700_SLBE 0x10 /* SCSI loopback enabled */ +#define CTEST4_800_SRTM 0x10 /* Shadow Register Test Mode */ +#define CTEST4_700_SFWR 0x08 /* SCSI FIFO write enable, + * redirects writes from SODL + * to the SCSI FIFO. + */ +#define CTEST4_800_MPEE 0x08 /* Enable parity checking + during master cycles on PCI + bus */ + +/* + * These bits send the contents of the CTEST6 register to the appropriate + * byte lane of the 32 bit DMA FIFO. Normal operation is zero, otherwise + * the high bit means the low two bits select the byte lane. + */ +#define CTEST4_FBL2 0x04 +#define CTEST4_FBL1 0x02 +#define CTEST4_FBL0 0x01 +#define CTEST4_FBL_MASK 0x07 +#define CTEST4_FBL_0 0x04 /* Select DMA FIFO byte lane 0 */ +#define CTEST4_FBL_1 0x05 /* Select DMA FIFO byte lane 1 */ +#define CTEST4_FBL_2 0x06 /* Select DMA FIFO byte lane 2 */ +#define CTEST4_FBL_3 0x07 /* Select DMA FIFO byte lane 3 */ +#define CTEST4_800_SAVE (CTEST4_800_BDIS) + + +#define CTEST5_REG_700 0x19 /* Chip test 5 rw */ +#define CTEST5_REG_800 0x22 /* Chip test 5 rw */ +/* + * Clock Address Incrementor. When set, it increments the + * DNAD register to the next bus size boundary. It automatically + * resets itself when the operation is complete. + */ +#define CTEST5_ADCK 0x80 +/* + * Clock Byte Counter. When set, it decrements the DBC register to + * the next bus size boundary. + */ +#define CTEST5_BBCK 0x40 +/* + * Reset SCSI Offset. Setting this bit to 1 clears the current offset + * pointer in the SCSI synchronous offset counter (SSTAT). This bit + * is set to 1 if a SCSI Gross Error Condition occurs. The offset should + * be cleared when a synchronous transfer fails. When written, it is + * automatically cleared after the SCSI synchronous offset counter is + * reset. + */ +/* Bit 5 is reserved on 800 series chips */ +#define CTEST5_700_ROFF 0x20 +/* + * Master Control for Set or Reset pulses. When 1, causes the low + * four bits of register to set when set, 0 causes the low bits to + * clear when set. + */ +#define CTEST5_MASR 0x10 +#define CTEST5_DDIR 0x08 /* DMA direction */ +/* + * Bits 2-0 are reserved on 800 series chips + */ +#define CTEST5_700_EOP 0x04 /* End of process */ +#define CTEST5_700_DREQ 0x02 /* Data request */ +#define CTEST5_700_DACK 0x01 /* Data acknowledge */ + +/* + * Chip test 6 rw - writing to this register writes to the byte + * lane in the DMA FIFO as determined by the FBL bits in the CTEST4 + * register. + */ +#define CTEST6_REG_700 0x1a +#define CTEST6_REG_800 0x23 + +#define CTEST7_REG 0x1b /* Chip test 7 rw */ +/* 0x80 - 0x40 are reserved on NCR53c700 and NCR53c700-66 chips */ +#define CTEST7_10_CDIS 0x80 /* Cache burst disable */ +#define CTEST7_10_SC1 0x40 /* Snoop control bits */ +#define CTEST7_10_SC0 0x20 +#define CTEST7_10_SC_MASK 0x60 +/* 0x20 is reserved on the NCR53c700 */ +#define CTEST7_0060_FM 0x20 /* Fetch mode */ +#define CTEST7_STD 0x10 /* Selection timeout disable */ +#define CTEST7_DFP 0x08 /* DMA FIFO parity bit for CTEST6 */ +#define CTEST7_EVP 0x04 /* 1 = host bus even parity, 0 = odd */ +#define CTEST7_10_TT1 0x02 /* Transfer type */ +#define CTEST7_00_DC 0x02 /* Set to drive DC low during instruction + fetch */ +#define CTEST7_DIFF 0x01 /* Differential mode */ + +#define CTEST7_SAVE ( CTEST7_EVP | CTEST7_DIFF ) + + +#define TEMP_REG 0x1c /* through 0x1f Temporary stack rw */ + +#define DFIFO_REG 0x20 /* DMA FIFO rw */ +/* + * 0x80 is reserved on the NCR53c710, the CLF and FLF bits have been + * moved into the CTEST8 register. + */ +#define DFIFO_00_FLF 0x80 /* Flush DMA FIFO to memory */ +#define DFIFO_00_CLF 0x40 /* Clear DMA and SCSI FIFOs */ +#define DFIFO_BO6 0x40 +#define DFIFO_BO5 0x20 +#define DFIFO_BO4 0x10 +#define DFIFO_BO3 0x08 +#define DFIFO_BO2 0x04 +#define DFIFO_BO1 0x02 +#define DFIFO_BO0 0x01 +#define DFIFO_10_BO_MASK 0x7f /* 7 bit counter */ +#define DFIFO_00_BO_MASK 0x3f /* 6 bit counter */ + +/* + * Interrupt status rw + * Note that this is the only register which can be read while SCSI + * SCRIPTS are being executed. + */ +#define ISTAT_REG_700 0x21 +#define ISTAT_REG_800 0x14 +#define ISTAT_ABRT 0x80 /* Software abort, write + *1 to abort, wait for interrupt. */ +/* 0x40 and 0x20 are reserved on NCR53c700 and NCR53c700-66 chips */ +#define ISTAT_10_SRST 0x40 /* software reset */ +#define ISTAT_10_SIGP 0x20 /* signal script */ +/* 0x10 is reserved on NCR53c700 series chips */ +#define ISTAT_800_SEM 0x10 /* semaphore */ +#define ISTAT_CON 0x08 /* 1 when connected */ +#define ISTAT_800_INTF 0x04 /* Interrupt on the fly */ +#define ISTAT_700_PRE 0x04 /* Pointer register empty. + * Set to 1 when DSPS and DSP + * registers are empty in pipeline + * mode, always set otherwise. + */ +#define ISTAT_SIP 0x02 /* SCSI interrupt pending from + * SCSI portion of SIOP see + * SSTAT0 + */ +#define ISTAT_DIP 0x01 /* DMA interrupt pending + * see DSTAT + */ + +/* NCR53c700-66 and NCR53c710 only */ +#define CTEST8_REG 0x22 /* Chip test 8 rw */ +#define CTEST8_0066_EAS 0x80 /* Enable alternate SCSI clock, + * ie read from SCLK/ rather than CLK/ + */ +#define CTEST8_0066_EFM 0x40 /* Enable fetch and master outputs */ +#define CTEST8_0066_GRP 0x20 /* Generate Receive Parity for + * pass through. This insures that + * bad parity won't reach the host + * bus. + */ +#define CTEST8_0066_TE 0x10 /* TolerANT enable. Enable + * active negation, should only + * be used for slow SCSI + * non-differential. + */ +#define CTEST8_0066_HSC 0x08 /* Halt SCSI clock */ +#define CTEST8_0066_SRA 0x04 /* Shorten REQ/ACK filtering, + * must be set for fast SCSI-II + * speeds. + */ +#define CTEST8_0066_DAS 0x02 /* Disable automatic target/initiator + * switching. + */ +#define CTEST8_0066_LDE 0x01 /* Last disconnect enable. + * The status of pending + * disconnect is maintained by + * the core, eliminating + * the possibility of missing a + * selection or reselection + * while waiting to fetch a + * WAIT DISCONNECT opcode. + */ + +#define CTEST8_10_V3 0x80 /* Chip revision */ +#define CTEST8_10_V2 0x40 +#define CTEST8_10_V1 0x20 +#define CTEST8_10_V0 0x10 +#define CTEST8_10_V_MASK 0xf0 +#define CTEST8_10_FLF 0x08 /* Flush FIFOs */ +#define CTEST8_10_CLF 0x04 /* Clear FIFOs */ +#define CTEST8_10_FM 0x02 /* Fetch pin mode */ +#define CTEST8_10_SM 0x01 /* Snoop pin mode */ + + +/* + * The CTEST9 register may be used to differentiate between a + * NCR53c700 and a NCR53c710. + * + * Write 0xff to this register. + * Read it. + * If the contents are 0xff, it is a NCR53c700 + * If the contents are 0x00, it is a NCR53c700-66 first revision + * If the contents are some other value, it is some other NCR53c700-66 + */ +#define CTEST9_REG_00 0x23 /* Chip test 9 ro */ +#define LCRC_REG_10 0x23 + +/* + * 0x24 through 0x27 are the DMA byte counter register. Instructions + * write their high 8 bits into the DCMD register, the low 24 bits into + * the DBC register. + * + * Function is dependent on the command type being executed. + */ + + +#define DBC_REG 0x24 +/* + * For Block Move Instructions, DBC is a 24 bit quantity representing + * the number of bytes to transfer. + * For Transfer Control Instructions, DBC is bit fielded as follows : + */ +/* Bits 20 - 23 should be clear */ +#define DBC_TCI_TRUE (1 << 19) /* Jump when true */ +#define DBC_TCI_COMPARE_DATA (1 << 18) /* Compare data */ +#define DBC_TCI_COMPARE_PHASE (1 << 17) /* Compare phase with DCMD field */ +#define DBC_TCI_WAIT_FOR_VALID (1 << 16) /* Wait for REQ */ +/* Bits 8 - 15 are reserved on some implementations ? */ +#define DBC_TCI_MASK_MASK 0xff00 /* Mask for data compare */ +#define DBC_TCI_MASK_SHIFT 8 +#define DBC_TCI_DATA_MASK 0xff /* Data to be compared */ +#define DBC_TCI_DATA_SHIFT 0 + +#define DBC_RWRI_IMMEDIATE_MASK 0xff00 /* Immediate data */ +#define DBC_RWRI_IMMEDIATE_SHIFT 8 /* Amount to shift */ +#define DBC_RWRI_ADDRESS_MASK 0x3f0000 /* Register address */ +#define DBC_RWRI_ADDRESS_SHIFT 16 + + +/* + * DMA command r/w + */ +#define DCMD_REG 0x27 +#define DCMD_TYPE_MASK 0xc0 /* Masks off type */ +#define DCMD_TYPE_BMI 0x00 /* Indicates a Block Move instruction */ +#define DCMD_BMI_IO 0x01 /* I/O, CD, and MSG bits selecting */ +#define DCMD_BMI_CD 0x02 /* the phase for the block MOVE */ +#define DCMD_BMI_MSG 0x04 /* instruction */ + +#define DCMD_BMI_OP_MASK 0x18 /* mask for opcode */ +#define DCMD_BMI_OP_MOVE_T 0x00 /* MOVE */ +#define DCMD_BMI_OP_MOVE_I 0x08 /* MOVE Initiator */ + +#define DCMD_BMI_INDIRECT 0x20 /* Indirect addressing */ + +#define DCMD_TYPE_TCI 0x80 /* Indicates a Transfer Control + instruction */ +#define DCMD_TCI_IO 0x01 /* I/O, CD, and MSG bits selecting */ +#define DCMD_TCI_CD 0x02 /* the phase for the block MOVE */ +#define DCMD_TCI_MSG 0x04 /* instruction */ +#define DCMD_TCI_OP_MASK 0x38 /* mask for opcode */ +#define DCMD_TCI_OP_JUMP 0x00 /* JUMP */ +#define DCMD_TCI_OP_CALL 0x08 /* CALL */ +#define DCMD_TCI_OP_RETURN 0x10 /* RETURN */ +#define DCMD_TCI_OP_INT 0x18 /* INT */ + +#define DCMD_TYPE_RWRI 0x40 /* Indicates I/O or register Read/Write + instruction */ +#define DCMD_RWRI_OPC_MASK 0x38 /* Opcode mask */ +#define DCMD_RWRI_OPC_WRITE 0x28 /* Write SFBR to register */ +#define DCMD_RWRI_OPC_READ 0x30 /* Read register to SFBR */ +#define DCMD_RWRI_OPC_MODIFY 0x38 /* Modify in place */ + +#define DCMD_RWRI_OP_MASK 0x07 +#define DCMD_RWRI_OP_MOVE 0x00 +#define DCMD_RWRI_OP_SHL 0x01 +#define DCMD_RWRI_OP_OR 0x02 +#define DCMD_RWRI_OP_XOR 0x03 +#define DCMD_RWRI_OP_AND 0x04 +#define DCMD_RWRI_OP_SHR 0x05 +#define DCMD_RWRI_OP_ADD 0x06 +#define DCMD_RWRI_OP_ADDC 0x07 + +#define DCMD_TYPE_MMI 0xc0 /* Indicates a Memory Move instruction + (three words) */ + + +#define DNAD_REG 0x28 /* through 0x2b DMA next address for + data */ +#define DSP_REG 0x2c /* through 0x2f DMA SCRIPTS pointer rw */ +#define DSPS_REG 0x30 /* through 0x33 DMA SCRIPTS pointer + save rw */ +#define DMODE_REG_00 0x34 /* DMA mode rw */ +#define DMODE_00_BL1 0x80 /* Burst length bits */ +#define DMODE_00_BL0 0x40 +#define DMODE_BL_MASK 0xc0 +/* Burst lengths (800) */ +#define DMODE_BL_2 0x00 /* 2 transfer */ +#define DMODE_BL_4 0x40 /* 4 transfers */ +#define DMODE_BL_8 0x80 /* 8 transfers */ +#define DMODE_BL_16 0xc0 /* 16 transfers */ + +#define DMODE_700_BW16 0x20 /* Host buswidth = 16 */ +#define DMODE_700_286 0x10 /* 286 mode */ +#define DMODE_700_IOM 0x08 /* Transfer to IO port */ +#define DMODE_700_FAM 0x04 /* Fixed address mode */ +#define DMODE_700_PIPE 0x02 /* Pipeline mode disables + * automatic fetch / exec + */ +#define DMODE_MAN 0x01 /* Manual start mode, + * requires a 1 to be written + * to the start DMA bit in the DCNTL + * register to run scripts + */ + +#define DMODE_700_SAVE ( DMODE_00_BL_MASK | DMODE_00_BW16 | DMODE_00_286 ) + +/* NCR53c800 series only */ +#define SCRATCHA_REG_800 0x34 /* through 0x37 Scratch A rw */ +/* NCR53c710 only */ +#define SCRATCB_REG_10 0x34 /* through 0x37 scratch B rw */ + +#define DMODE_REG_10 0x38 /* DMA mode rw, NCR53c710 and newer */ +#define DMODE_800_SIOM 0x20 /* Source IO = 1 */ +#define DMODE_800_DIOM 0x10 /* Destination IO = 1 */ +#define DMODE_800_ERL 0x08 /* Enable Read Line */ + +/* 35-38 are reserved on 700 and 700-66 series chips */ +#define DIEN_REG 0x39 /* DMA interrupt enable rw */ +/* 0x80, 0x40, and 0x20 are reserved on 700-series chips */ +#define DIEN_800_MDPE 0x40 /* Master data parity error */ +#define DIEN_800_BF 0x20 /* BUS fault */ +#define DIEN_ABRT 0x10 /* Enable aborted interrupt */ +#define DIEN_SSI 0x08 /* Enable single step interrupt */ +#define DIEN_SIR 0x04 /* Enable SCRIPTS INT command + * interrupt + */ +/* 0x02 is reserved on 800 series chips */ +#define DIEN_700_WTD 0x02 /* Enable watchdog timeout interrupt */ +#define DIEN_700_OPC 0x01 /* Enable illegal instruction + * interrupt + */ +#define DIEN_800_IID 0x01 /* Same meaning, different name */ + +/* + * DMA watchdog timer rw + * set in 16 CLK input periods. + */ +#define DWT_REG 0x3a + +/* DMA control rw */ +#define DCNTL_REG 0x3b +#define DCNTL_700_CF1 0x80 /* Clock divisor bits */ +#define DCNTL_700_CF0 0x40 +#define DCNTL_700_CF_MASK 0xc0 +/* Clock divisors Divisor SCLK range (MHZ) */ +#define DCNTL_700_CF_2 0x00 /* 2.0 37.51-50.00 */ +#define DCNTL_700_CF_1_5 0x40 /* 1.5 25.01-37.50 */ +#define DCNTL_700_CF_1 0x80 /* 1.0 16.67-25.00 */ +#define DCNTL_700_CF_3 0xc0 /* 3.0 50.01-66.67 (53c700-66) */ + +#define DCNTL_700_S16 0x20 /* Load scripts 16 bits at a time */ +#define DCNTL_SSM 0x10 /* Single step mode */ +#define DCNTL_700_LLM 0x08 /* Low level mode, can only be set + * after selection */ +#define DCNTL_800_IRQM 0x08 /* Totem pole IRQ pin */ +#define DCNTL_STD 0x04 /* Start DMA / SCRIPTS */ +/* 0x02 is reserved */ +#define DCNTL_00_RST 0x01 /* Software reset, resets everything + * but 286 mode bit in DMODE. On the + * NCR53c710, this bit moved to CTEST8 + */ +#define DCNTL_10_COM 0x01 /* 700 software compatibility mode */ + +#define DCNTL_700_SAVE ( DCNTL_CF_MASK | DCNTL_S16) + + +/* NCR53c700-66 only */ +#define SCRATCHB_REG_00 0x3c /* through 0x3f scratch b rw */ +#define SCRATCHB_REG_800 0x5c /* through 0x5f scratch b rw */ +/* NCR53c710 only */ +#define ADDER_REG_10 0x3c /* Adder, NCR53c710 only */ + +#define SIEN1_REG_800 0x41 +#define SIEN1_800_STO 0x04 /* selection/reselection timeout */ +#define SIEN1_800_GEN 0x02 /* general purpose timer */ +#define SIEN1_800_HTH 0x01 /* handshake to handshake */ + +#define SIST1_REG_800 0x43 +#define SIST1_800_STO 0x04 /* selection/reselection timeout */ +#define SIST1_800_GEN 0x02 /* general purpose timer */ +#define SIST1_800_HTH 0x01 /* handshake to handshake */ + +#define SLPAR_REG_800 0x44 /* Parity */ + +#define MACNTL_REG_800 0x46 /* Memory access control */ +#define MACNTL_800_TYP3 0x80 +#define MACNTL_800_TYP2 0x40 +#define MACNTL_800_TYP1 0x20 +#define MACNTL_800_TYP0 0x10 +#define MACNTL_800_DWR 0x08 +#define MACNTL_800_DRD 0x04 +#define MACNTL_800_PSCPT 0x02 +#define MACNTL_800_SCPTS 0x01 + +#define GPCNTL_REG_800 0x47 /* General Purpose Pin Control */ + +/* Timeouts are expressed such that 0=off, 1=100us, doubling after that */ +#define STIME0_REG_800 0x48 /* SCSI Timer Register 0 */ +#define STIME0_800_HTH_MASK 0xf0 /* Handshake to Handshake timeout */ +#define STIME0_800_HTH_SHIFT 4 +#define STIME0_800_SEL_MASK 0x0f /* Selection timeout */ +#define STIME0_800_SEL_SHIFT 0 + +#define STIME1_REG_800 0x49 +#define STIME1_800_GEN_MASK 0x0f /* General purpose timer */ + +#define RESPID_REG_800 0x4a /* Response ID, bit fielded. 8 + bits on narrow chips, 16 on WIDE */ + +#define STEST0_REG_800 0x4c +#define STEST0_800_SLT 0x08 /* Selection response logic test */ +#define STEST0_800_ART 0x04 /* Arbitration priority encoder test */ +#define STEST0_800_SOZ 0x02 /* Synchronous offset zero */ +#define STEST0_800_SOM 0x01 /* Synchronous offset maximum */ + +#define STEST1_REG_800 0x4d +#define STEST1_800_SCLK 0x80 /* Disable SCSI clock */ + +#define STEST2_REG_800 0x4e +#define STEST2_800_SCE 0x80 /* Enable SOCL/SODL */ +#define STEST2_800_ROF 0x40 /* Reset SCSI sync offset */ +#define STEST2_800_SLB 0x10 /* Enable SCSI loopback mode */ +#define STEST2_800_SZM 0x08 /* SCSI high impedance mode */ +#define STEST2_800_EXT 0x02 /* Extend REQ/ACK filter 30 to 60ns */ +#define STEST2_800_LOW 0x01 /* SCSI low level mode */ + +#define STEST3_REG_800 0x4f +#define STEST3_800_TE 0x80 /* Enable active negation */ +#define STEST3_800_STR 0x40 /* SCSI FIFO test read */ +#define STEST3_800_HSC 0x20 /* Halt SCSI clock */ +#define STEST3_800_DSI 0x10 /* Disable single initiator response */ +#define STEST3_800_TTM 0x04 /* Time test mode */ +#define STEST3_800_CSF 0x02 /* Clear SCSI FIFO */ +#define STEST3_800_STW 0x01 /* SCSI FIFO test write */ + +#define OPTION_PARITY 0x1 /* Enable parity checking */ +#define OPTION_TAGGED_QUEUE 0x2 /* Enable SCSI-II tagged queuing */ +#define OPTION_700 0x8 /* Always run NCR53c700 scripts */ +#define OPTION_INTFLY 0x10 /* Use INTFLY interrupts */ +#define OPTION_DEBUG_INTR 0x20 /* Debug interrupts */ +#define OPTION_DEBUG_INIT_ONLY 0x40 /* Run initialization code and + simple test code, return + DID_NO_CONNECT if any SCSI + commands are attempted. */ +#define OPTION_DEBUG_READ_ONLY 0x80 /* Return DID_ERROR if any + SCSI write is attempted */ +#define OPTION_DEBUG_TRACE 0x100 /* Animated trace mode, print + each address and instruction + executed to debug buffer. */ +#define OPTION_DEBUG_SINGLE 0x200 /* stop after executing one + instruction */ +#define OPTION_SYNCHRONOUS 0x400 /* Enable sync SCSI. */ +#define OPTION_MEMORY_MAPPED 0x800 /* NCR registers have valid + memory mapping */ +#define OPTION_IO_MAPPED 0x1000 /* NCR registers have valid + I/O mapping */ +#define OPTION_DEBUG_PROBE_ONLY 0x2000 /* Probe only, don't even init */ +#define OPTION_DEBUG_TESTS_ONLY 0x4000 /* Probe, init, run selected tests */ +#define OPTION_DEBUG_TEST0 0x08000 /* Run test 0 */ +#define OPTION_DEBUG_TEST1 0x10000 /* Run test 1 */ +#define OPTION_DEBUG_TEST2 0x20000 /* Run test 2 */ +#define OPTION_DEBUG_DUMP 0x40000 /* Dump commands */ +#define OPTION_DEBUG_TARGET_LIMIT 0x80000 /* Only talk to target+luns specified */ +#define OPTION_DEBUG_NCOMMANDS_LIMIT 0x100000 /* Limit the number of commands */ +#define OPTION_DEBUG_SCRIPT 0x200000 /* Print when checkpoints are passed */ +#define OPTION_DEBUG_FIXUP 0x400000 /* print fixup values */ +#define OPTION_DEBUG_DSA 0x800000 +#define OPTION_DEBUG_CORRUPTION 0x1000000 /* Detect script corruption */ +#define OPTION_DEBUG_SDTR 0x2000000 /* Debug SDTR problem */ +#define OPTION_DEBUG_MISMATCH 0x4000000 /* Debug phase mismatches */ +#define OPTION_DISCONNECT 0x8000000 /* Allow disconnect */ +#define OPTION_DEBUG_DISCONNECT 0x10000000 +#define OPTION_ALWAYS_SYNCHRONOUS 0x20000000 /* Negotiate sync. transfers + on power up */ +#define OPTION_DEBUG_QUEUES 0x80000000 +#define OPTION_DEBUG_ALLOCATION 0x100000000LL +#define OPTION_DEBUG_SYNCHRONOUS 0x200000000LL /* Sanity check SXFER and + SCNTL3 registers */ +#define OPTION_NO_ASYNC 0x400000000LL /* Don't automagically send + SDTR for async transfers when + we haven't been told to do + a synchronous transfer. */ +#define OPTION_NO_PRINT_RACE 0x800000000LL /* Don't print message when + the reselect/WAIT DISCONNECT + race condition hits */ +#if !defined(PERM_OPTIONS) +#define PERM_OPTIONS 0 +#endif + +struct NCR53c7x0_synchronous { + u32 select_indirect; /* Value used for indirect selection */ + u32 script[8]; /* Size ?? Script used when target is + reselected */ + unsigned char synchronous_want[5]; /* Per target desired SDTR */ +/* + * Set_synchronous programs these, select_indirect and current settings after + * int_debug_should show a match. + */ + unsigned char sxfer_sanity, scntl3_sanity; +}; + +#define CMD_FLAG_SDTR 1 /* Initiating synchronous + transfer negotiation */ +#define CMD_FLAG_WDTR 2 /* Initiating wide transfer + negotiation */ +#define CMD_FLAG_DID_SDTR 4 /* did SDTR */ +#define CMD_FLAG_DID_WDTR 8 /* did WDTR */ + +struct NCR53c7x0_table_indirect { + u32 count; + void *address; +}; + +enum ncr_event { + EVENT_NONE = 0, +/* + * Order is IMPORTANT, since these must correspond to the event interrupts + * in 53c7,8xx.scr + */ + + EVENT_ISSUE_QUEUE = 0x5000000, /* Command was added to issue queue */ + EVENT_START_QUEUE, /* Command moved to start queue */ + EVENT_SELECT, /* Command completed selection */ + EVENT_DISCONNECT, /* Command disconnected */ + EVENT_RESELECT, /* Command reselected */ + EVENT_COMPLETE, /* Command completed */ + EVENT_IDLE, + EVENT_SELECT_FAILED, + EVENT_BEFORE_SELECT, + EVENT_RESELECT_FAILED +}; + +struct NCR53c7x0_event { + enum ncr_event event; /* What type of event */ + unsigned char target; + unsigned char lun; + struct timeval time; + u32 *dsa; /* What's in the DSA register now (virt) */ +/* + * A few things from that SCSI pid so we know what happened after + * the Scsi_Cmnd structure in question may have disappeared. + */ + unsigned long pid; /* The SCSI PID which caused this + event */ + unsigned char cmnd[12]; +}; + +/* + * Things in the NCR53c7x0_cmd structure are split into two parts : + * + * 1. A fixed portion, for things which are not accessed directly by static NCR + * code (ie, are referenced only by the Linux side of the driver, + * or only by dynamically generated code). + * + * 2. The DSA portion, for things which are accessed directly by static NCR + * code. + * + * This is a little ugly, but it + * 1. Avoids conflicts between the NCR code's picture of the structure, and + * Linux code's idea of what it looks like. + * + * 2. Minimizes the pain in the Linux side of the code needed + * to calculate real dsa locations for things, etc. + * + */ + +struct NCR53c7x0_cmd { + void *real; /* Real, unaligned address for + free function */ + void (* free)(void *, int); /* Command to deallocate; NULL + for structures allocated with + scsi_register, etc. */ + Scsi_Cmnd *cmd; /* Associated Scsi_Cmnd + structure, Scsi_Cmnd points + at NCR53c7x0_cmd using + host_scribble structure */ + + int size; /* scsi_malloc'd size of this + structure */ + + int flags; /* CMD_* flags */ + +/* + * SDTR and WIDE messages are an either/or affair + * in this message, since we will go into message out and send + * _the whole mess_ without dropping out of message out to + * let the target go into message in after sending the first + * message. + */ + + unsigned char select[11]; /* Select message, includes + IDENTIFY + (optional) QUEUE TAG + (optional) SDTR or WDTR + */ + + + volatile struct NCR53c7x0_cmd *next; /* Linux maintained lists (free, + running, eventually finished */ + + + u32 *data_transfer_start; /* Start of data transfer routines */ + u32 *data_transfer_end; /* Address after end of data transfer o + routines */ +/* + * The following three fields were moved from the DSA proper to here + * since only dynamically generated NCR code refers to them, meaning + * we don't need dsa_* absolutes, and it is simpler to let the + * host code refer to them directly. + */ + +/* + * HARD CODED : residual and saved_residual need to agree with the sizes + * used in NCR53c7,8xx.scr. + * + * FIXME: we want to consider the case where we have odd-length + * scatter/gather buffers and a WIDE transfer, in which case + * we'll need to use the CHAIN MOVE instruction. Ick. + */ + u32 residual[6]; /* Residual data transfer which + allows pointer code to work + right. + + [0-1] : Conditional call to + appropriate other transfer + routine. + [2-3] : Residual block transfer + instruction. + [4-5] : Jump to instruction + after splice. + */ + u32 saved_residual[6]; /* Copy of old residual, so we + can get another partial + transfer and still recover + */ + + u32 saved_data_pointer; /* Saved data pointer */ + + u32 dsa_next_addr; /* _Address_ of dsa_next field + in this dsa for RISCy + style constant. */ + + u32 dsa_addr; /* Address of dsa; RISCy style + constant */ + + u32 dsa[0]; /* Variable length (depending + on host type, number of scatter / + gather buffers, etc). */ +}; + +struct NCR53c7x0_break { + u32 *address, old_instruction[2]; + struct NCR53c7x0_break *next; + unsigned char old_size; /* Size of old instruction */ +}; + +/* Indicates that the NCR is not executing code */ +#define STATE_HALTED 0 +/* + * Indicates that the NCR is executing the wait for select / reselect + * script. Only used when running NCR53c700 compatible scripts, only + * state during which an ABORT is _not_ considered an error condition. + */ +#define STATE_WAITING 1 +/* Indicates that the NCR is executing other code. */ +#define STATE_RUNNING 2 +/* + * Indicates that the NCR was being aborted. + */ +#define STATE_ABORTING 3 +/* Indicates that the NCR was successfully aborted. */ +#define STATE_ABORTED 4 +/* Indicates that the NCR has been disabled due to a fatal error */ +#define STATE_DISABLED 5 + +/* + * Where knowledge of SCSI SCRIPT(tm) specified values are needed + * in an interrupt handler, an interrupt handler exists for each + * different SCSI script so we don't have name space problems. + * + * Return values of these handlers are as follows : + */ +#define SPECIFIC_INT_NOTHING 0 /* don't even restart */ +#define SPECIFIC_INT_RESTART 1 /* restart at the next instruction */ +#define SPECIFIC_INT_ABORT 2 /* recoverable error, abort cmd */ +#define SPECIFIC_INT_PANIC 3 /* unrecoverable error, panic */ +#define SPECIFIC_INT_DONE 4 /* normal command completion */ +#define SPECIFIC_INT_BREAK 5 /* break point encountered */ + +struct NCR53c7x0_hostdata { + int size; /* Size of entire Scsi_Host + structure */ + int board; /* set to board type, useful if + we have host specific things, + ie, a general purpose I/O + bit is being used to enable + termination, etc. */ + + int chip; /* set to chip type; 700-66 is + 700-66, rest are last three + digits of part number */ + /* + * PCI bus, device, function, only for NCR53c8x0 chips. + * pci_valid indicates that the PCI configuration information + * is valid, and we can twiddle MAX_LAT, etc. as recommended + * for maximum performance in the NCR documentation. + */ + unsigned char pci_bus, pci_device_fn; + unsigned pci_valid:1; + + u32 *dsp; /* dsp to restart with after + all stacked interrupts are + handled. */ + + unsigned dsp_changed:1; /* Has dsp changed within this + set of stacked interrupts ? */ + + unsigned char dstat; /* Most recent value of dstat */ + unsigned dstat_valid:1; + + unsigned expecting_iid:1; /* Expect IID interrupt */ + unsigned expecting_sto:1; /* Expect STO interrupt */ + + /* + * The code stays cleaner if we use variables with function + * pointers and offsets that are unique for the different + * scripts rather than having a slew of switch(hostdata->chip) + * statements. + * + * It also means that the #defines from the SCSI SCRIPTS(tm) + * don't have to be visible outside of the script-specific + * instructions, preventing name space pollution. + */ + + void (* init_fixup)(struct Scsi_Host *host); + void (* init_save_regs)(struct Scsi_Host *host); + void (* dsa_fixup)(struct NCR53c7x0_cmd *cmd); + void (* soft_reset)(struct Scsi_Host *host); + int (* run_tests)(struct Scsi_Host *host); + + /* + * Called when DSTAT_SIR is set, indicating an interrupt generated + * by the INT instruction, where values are unique for each SCSI + * script. Should return one of the SPEC_* values. + */ + + int (* dstat_sir_intr)(struct Scsi_Host *host, struct NCR53c7x0_cmd *cmd); + + int dsa_len; /* Size of DSA structure */ + + /* + * Location of DSA fields for the SCSI SCRIPT corresponding to this + * chip. + */ + + s32 dsa_start; + s32 dsa_end; + s32 dsa_next; + s32 dsa_prev; + s32 dsa_cmnd; + s32 dsa_select; + s32 dsa_msgout; + s32 dsa_cmdout; + s32 dsa_dataout; + s32 dsa_datain; + s32 dsa_msgin; + s32 dsa_msgout_other; + s32 dsa_write_sync; + s32 dsa_write_resume; + s32 dsa_check_reselect; + s32 dsa_status; + s32 dsa_saved_pointer; + s32 dsa_jump_dest; + + /* + * Important entry points that generic fixup code needs + * to know about, fixed up. + */ + + s32 E_accept_message; + s32 E_command_complete; + s32 E_data_transfer; + s32 E_dsa_code_template; + s32 E_dsa_code_template_end; + s32 E_end_data_transfer; + s32 E_msg_in; + s32 E_initiator_abort; + s32 E_other_transfer; + s32 E_other_in; + s32 E_other_out; + s32 E_target_abort; + s32 E_debug_break; + s32 E_reject_message; + s32 E_respond_message; + s32 E_select; + s32 E_select_msgout; + s32 E_test_0; + s32 E_test_1; + s32 E_test_2; + s32 E_test_3; + s32 E_dsa_zero; + s32 E_cmdout_cmdout; + s32 E_wait_reselect; + s32 E_dsa_code_begin; + + long long options; /* Bitfielded set of options enabled */ + volatile u32 test_completed; /* Test completed */ + int test_running; /* Test currently running */ + s32 test_source; + volatile s32 test_dest; + + volatile int state; /* state of driver, only used for + OPTION_700 */ + + unsigned char dmode; /* + * set to the address of the DMODE + * register for this chip. + */ + unsigned char istat; /* + * set to the address of the ISTAT + * register for this chip. + */ + + int scsi_clock; /* + * SCSI clock in HZ. 0 may be used + * for unknown, although this will + * disable synchronous negotiation. + */ + + volatile int intrs; /* Number of interrupts */ + volatile int resets; /* Number of SCSI resets */ + unsigned char saved_dmode; + unsigned char saved_ctest4; + unsigned char saved_ctest7; + unsigned char saved_dcntl; + unsigned char saved_scntl3; + + unsigned char this_id_mask; + + /* Debugger information */ + struct NCR53c7x0_break *breakpoints, /* Linked list of all break points */ + *breakpoint_current; /* Current breakpoint being stepped + through, NULL if we are running + normally. */ +#ifdef NCR_DEBUG + int debug_size; /* Size of debug buffer */ + volatile int debug_count; /* Current data count */ + volatile char *debug_buf; /* Output ring buffer */ + volatile char *debug_write; /* Current write pointer */ + volatile char *debug_read; /* Current read pointer */ +#endif /* def NCR_DEBUG */ + + /* XXX - primitive debugging junk, remove when working ? */ + int debug_print_limit; /* Number of commands to print + out exhaustive debugging + information for if + OPTION_DEBUG_DUMP is set */ + + unsigned char debug_lun_limit[16]; /* If OPTION_DEBUG_TARGET_LIMIT + set, puke if commands are sent + to other target/lun combinations */ + + int debug_count_limit; /* Number of commands to execute + before puking to limit debugging + output */ + + + volatile unsigned idle:1; /* set to 1 if idle */ + + /* + * Table of synchronous+wide transfer parameters set on a per-target + * basis. + */ + + volatile struct NCR53c7x0_synchronous sync[16]; + + volatile Scsi_Cmnd *issue_queue; + /* waiting to be issued by + Linux driver */ + volatile struct NCR53c7x0_cmd *running_list; + /* commands running, maintained + by Linux driver */ + + volatile struct NCR53c7x0_cmd *current; /* currently connected + nexus, ONLY valid for + NCR53c700/NCR53c700-66 + */ + + volatile struct NCR53c7x0_cmd *spare; /* pointer to spare, + allocated at probe time, + which we can use for + initialization */ + volatile struct NCR53c7x0_cmd *free; + int max_cmd_size; /* Maximum size of NCR53c7x0_cmd + based on number of + scatter/gather segments, etc. + */ + volatile int num_cmds; /* Number of commands + allocated */ + volatile int extra_allocate; + volatile unsigned char cmd_allocated[16]; /* Have we allocated commands + for this target yet? If not, + do so ASAP */ + volatile unsigned char busy[16][8]; /* number of commands + executing on each target + */ + /* + * Eventually, I'll switch to a coroutine for calling + * cmd->done(cmd), etc. so that we can overlap interrupt + * processing with this code for maximum performance. + */ + + volatile struct NCR53c7x0_cmd *finished_queue; + + + /* Shared variables between SCRIPT and host driver */ + volatile u32 *schedule; /* Array of JUMPs to dsa_begin + routines of various DSAs. + When not in use, replace + with jump to next slot */ + + + volatile unsigned char msg_buf[16]; /* buffer for messages + other than the command + complete message */ + + /* Per-target default synchronous and WIDE messages */ + volatile unsigned char synchronous_want[16][5]; + volatile unsigned char wide_want[16][4]; + + /* Bit fielded set of targets we want to speak synchronously with */ + volatile u16 initiate_sdtr; + /* Bit fielded set of targets we want to speak wide with */ + volatile u16 initiate_wdtr; + /* Bit fielded list of targets we've talked to. */ + volatile u16 talked_to; + + /* Array of bit-fielded lun lists that we need to request_sense */ + volatile unsigned char request_sense[16]; + + u32 addr_reconnect_dsa_head; /* RISCy style constant, + address of following */ + volatile u32 reconnect_dsa_head; + /* Data identifying nexus we are trying to match during reselection */ + volatile unsigned char reselected_identify; /* IDENTIFY message */ + volatile unsigned char reselected_tag; /* second byte of queue tag + message or 0 */ + /* These were static variables before we moved them */ + + s32 NCR53c7xx_zero; + s32 NCR53c7xx_sink; + u32 NOP_insn; + char NCR53c7xx_msg_reject; + char NCR53c7xx_msg_abort; + char NCR53c7xx_msg_nop; + + volatile int event_size, event_index; + volatile struct NCR53c7x0_event *events; + + /* If we need to generate code to kill off the currently connected + command, this is where we do it. Should have a BMI instruction + to source or sink the current data, followed by a JUMP + to abort_connected */ + + u32 *abort_script; + + int script_count; /* Size of script in words */ + u32 script[0]; /* Relocated SCSI script */ + +}; + +#define IRQ_NONE 255 +#define DMA_NONE 255 +#define IRQ_AUTO 254 +#define DMA_AUTO 254 + +#define BOARD_GENERIC 0 + +#define NCR53c7x0_insn_size(insn) \ + (((insn) & DCMD_TYPE_MASK) == DCMD_TYPE_MMI ? 3 : 2) + + +#define NCR53c7x0_local_declare() \ + volatile unsigned char *NCR53c7x0_address_memory; \ + unsigned int NCR53c7x0_address_io; \ + int NCR53c7x0_memory_mapped + +#define NCR53c7x0_local_setup(host) \ + NCR53c7x0_address_memory = (void *) (host)->base; \ + NCR53c7x0_address_io = (unsigned int) (host)->io_port; \ + NCR53c7x0_memory_mapped = ((struct NCR53c7x0_hostdata *) \ + host->hostdata)-> options & OPTION_MEMORY_MAPPED + +#define NCR53c7x0_read8(address) \ + (NCR53c7x0_memory_mapped ? \ + (unsigned int)readb(NCR53c7x0_address_memory + (address)) : \ + inb(NCR53c7x0_address_io + (address))) + +#define NCR53c7x0_read16(address) \ + (NCR53c7x0_memory_mapped ? \ + (unsigned int)readw(NCR53c7x0_address_memory + (address)) : \ + inw(NCR53c7x0_address_io + (address))) + +#define NCR53c7x0_read32(address) \ + (NCR53c7x0_memory_mapped ? \ + (unsigned int) readl(NCR53c7x0_address_memory + (address)) : \ + inl(NCR53c7x0_address_io + (address))) + +#define NCR53c7x0_write8(address,value) \ + (NCR53c7x0_memory_mapped ? \ + ({writeb((value), NCR53c7x0_address_memory + (address)); mb();}) : \ + outb((value), NCR53c7x0_address_io + (address))) + +#define NCR53c7x0_write16(address,value) \ + (NCR53c7x0_memory_mapped ? \ + ({writew((value), NCR53c7x0_address_memory + (address)); mb();}) : \ + outw((value), NCR53c7x0_address_io + (address))) + +#define NCR53c7x0_write32(address,value) \ + (NCR53c7x0_memory_mapped ? \ + ({writel((value), NCR53c7x0_address_memory + (address)); mb();}) : \ + outl((value), NCR53c7x0_address_io + (address))) + +/* Patch arbitrary 32 bit words in the script */ +#define patch_abs_32(script, offset, symbol, value) \ + for (i = 0; i < (sizeof (A_##symbol##_used) / sizeof \ + (u32)); ++i) { \ + (script)[A_##symbol##_used[i] - (offset)] += (value); \ + if (hostdata->options & OPTION_DEBUG_FIXUP) \ + printk("scsi%d : %s reference %d at 0x%x in %s is now 0x%x\n",\ + host->host_no, #symbol, i, A_##symbol##_used[i] - \ + (int)(offset), #script, (script)[A_##symbol##_used[i] - \ + (offset)]); \ + } + +/* Patch read/write instruction immediate field */ +#define patch_abs_rwri_data(script, offset, symbol, value) \ + for (i = 0; i < (sizeof (A_##symbol##_used) / sizeof \ + (u32)); ++i) \ + (script)[A_##symbol##_used[i] - (offset)] = \ + ((script)[A_##symbol##_used[i] - (offset)] & \ + ~DBC_RWRI_IMMEDIATE_MASK) | \ + (((value) << DBC_RWRI_IMMEDIATE_SHIFT) & \ + DBC_RWRI_IMMEDIATE_MASK) + +/* Patch transfer control instruction data field */ +#define patch_abs_tci_data(script, offset, symbol, value) \ + for (i = 0; i < (sizeof (A_##symbol##_used) / sizeof \ + (u32)); ++i) \ + (script)[A_##symbol##_used[i] - (offset)] = \ + ((script)[A_##symbol##_used[i] - (offset)] & \ + ~DBC_TCI_DATA_MASK) | \ + (((value) << DBC_TCI_DATA_SHIFT) & \ + DBC_TCI_DATA_MASK) + +/* Patch field in dsa structure (assignment should be +=?) */ +#define patch_dsa_32(dsa, symbol, word, value) \ + { \ + (dsa)[(hostdata->##symbol - hostdata->dsa_start) / sizeof(u32) \ + + (word)] = (value); \ + if (hostdata->options & OPTION_DEBUG_DSA) \ + printk("scsi : dsa %s symbol %s(%d) word %d now 0x%x\n", \ + #dsa, #symbol, hostdata->##symbol, \ + (word), (u32) (value)); \ + } + +/* Paranoid people could use panic() here. */ +#define FATAL(host) shutdown((host)); + +#endif /* NCR53c7x0_C */ +#endif /* NCR53c7x0_H */ diff --git a/linux/src/drivers/scsi/53c78xx.c b/linux/src/drivers/scsi/53c78xx.c new file mode 100644 index 00000000..8925ee52 --- /dev/null +++ b/linux/src/drivers/scsi/53c78xx.c @@ -0,0 +1,6401 @@ +/* + * PERM_OPTIONS are driver options which will be enabled for all NCR boards + * in the system at driver initialization time. + * + * Don't THINK about touching these in PERM_OPTIONS : + * OPTION_IO_MAPPED + * Memory mapped IO does not work under i86 Linux. + * + * OPTION_DEBUG_TEST1 + * Test 1 does bus mastering and interrupt tests, which will help weed + * out brain damaged main boards. + * + * These are development kernel changes. Code for them included in this + * driver release may or may not work. If you turn them on, you should be + * running the latest copy of the development sources from + * + * ftp://tsx-11.mit.edu/pub/linux/ALPHA/scsi/53c7,8xx + * + * and be subscribed to the ncr53c810@colorado.edu mailing list. To + * subscribe, send mail to majordomo@colorado.edu with + * + * subscribe ncr53c810 + * + * in the text. + * + * + * OPTION_NO_ASYNC + * Don't negotiate for asynchronous transfers on the first command + * when OPTION_ALWAYS_SYNCHRONOUS is set. Useful for dain bramaged + * devices which do something bad rather than sending a MESSAGE + * REJECT back to us like they should if they can't cope. + * + * OPTION_SYNCHRONOUS + * Enable support for synchronous transfers. Target negotiated + * synchronous transfers will be responded to. To initiate + * a synchronous transfer request, call + * + * request_synchronous (hostno, target) + * + * from within KGDB. + * + * OPTION_ALWAYS_SYNCHRONOUS + * Negotiate for synchronous transfers with every target after + * driver initialization or a SCSI bus reset. This is a bit dangerous, + * since there are some dain bramaged SCSI devices which will accept + * SDTR messages but keep talking asynchronously. + * + * OPTION_DISCONNECT + * Enable support for disconnect/reconnect. To change the + * default setting on a given host adapter, call + * + * request_disconnect (hostno, allow) + * + * where allow is non-zero to allow, 0 to disallow. + * + * If you really want to run 10MHz FAST SCSI-II transfers, you should + * know that the NCR driver currently ignores parity information. Most + * systems do 5MHz SCSI fine. I've seen a lot that have problems faster + * than 8MHz. To play it safe, we only request 5MHz transfers. + * + * If you'd rather get 10MHz transfers, edit sdtr_message and change + * the fourth byte from 50 to 25. + */ + +#include <linux/config.h> + +#ifdef CONFIG_SCSI_NCR53C7xx_sync +#ifdef CONFIG_SCSI_NCR53C7xx_DISCONNECT +#define PERM_OPTIONS (OPTION_IO_MAPPED|OPTION_DEBUG_TEST1|OPTION_DISCONNECT|\ + OPTION_SYNCHRONOUS|OPTION_ALWAYS_SYNCHRONOUS) +#else +#define PERM_OPTIONS (OPTION_IO_MAPPED|OPTION_DEBUG_TEST1|\ + OPTION_SYNCHRONOUS|OPTION_ALWAYS_SYNCHRONOUS) +#endif +#else +#ifdef CONFIG_SCSI_NCR53C7xx_DISCONNECT +#define PERM_OPTIONS (OPTION_IO_MAPPED|OPTION_DEBUG_TEST1|OPTION_DISCONNECT|\ + OPTION_SYNCHRONOUS) +#else +#define PERM_OPTIONS (OPTION_IO_MAPPED|OPTION_DEBUG_TEST1|OPTION_SYNCHRONOUS) +#endif +#endif + +/* + * Sponsored by + * iX Multiuser Multitasking Magazine + * Hannover, Germany + * hm@ix.de + * + * Copyright 1993, 1994, 1995 Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@PoohSticks.ORG + * +1 (303) 786-7975 + * + * TolerANT and SCSI SCRIPTS are registered trademarks of NCR Corporation. + * + * For more information, please consult + * + * NCR53C810 + * SCSI I/O Processor + * Programmer's Guide + * + * NCR 53C810 + * PCI-SCSI I/O Processor + * Data Manual + * + * NCR 53C810/53C820 + * PCI-SCSI I/O Processor Design In Guide + * + * For literature on Symbios Logic Inc. formerly NCR, SCSI, + * and Communication products please call (800) 334-5454 or + * (719) 536-3300. + * + * PCI BIOS Specification Revision + * PCI Local Bus Specification + * PCI System Design Guide + * + * PCI Special Interest Group + * M/S HF3-15A + * 5200 N.E. Elam Young Parkway + * Hillsboro, Oregon 97124-6497 + * +1 (503) 696-2000 + * +1 (800) 433-5177 + */ + +/* + * Design issues : + * The cumulative latency needed to propagate a read/write request + * through the file system, buffer cache, driver stacks, SCSI host, and + * SCSI device is ultimately the limiting factor in throughput once we + * have a sufficiently fast host adapter. + * + * So, to maximize performance we want to keep the ratio of latency to data + * transfer time to a minimum by + * 1. Minimizing the total number of commands sent (typical command latency + * including drive and bus mastering host overhead is as high as 4.5ms) + * to transfer a given amount of data. + * + * This is accomplished by placing no arbitrary limit on the number + * of scatter/gather buffers supported, since we can transfer 1K + * per scatter/gather buffer without Eric's cluster patches, + * 4K with. + * + * 2. Minimizing the number of fatal interrupts serviced, since + * fatal interrupts halt the SCSI I/O processor. Basically, + * this means offloading the practical maximum amount of processing + * to the SCSI chip. + * + * On the NCR53c810/820/720, this is accomplished by using + * interrupt-on-the-fly signals when commands complete, + * and only handling fatal errors and SDTR / WDTR messages + * in the host code. + * + * On the NCR53c710, interrupts are generated as on the NCR53c8x0, + * only the lack of a interrupt-on-the-fly facility complicates + * things. Also, SCSI ID registers and commands are + * bit fielded rather than binary encoded. + * + * On the NCR53c700 and NCR53c700-66, operations that are done via + * indirect, table mode on the more advanced chips must be + * replaced by calls through a jump table which + * acts as a surrogate for the DSA. Unfortunately, this + * will mean that we must service an interrupt for each + * disconnect/reconnect. + * + * 3. Eliminating latency by pipelining operations at the different levels. + * + * This driver allows a configurable number of commands to be enqueued + * for each target/lun combination (experimentally, I have discovered + * that two seems to work best) and will ultimately allow for + * SCSI-II tagged queuing. + * + * + * Architecture : + * This driver is built around a Linux queue of commands waiting to + * be executed, and a shared Linux/NCR array of commands to start. Commands + * are transfered to the array by the run_process_issue_queue() function + * which is called whenever a command completes. + * + * As commands are completed, the interrupt routine is triggered, + * looks for commands in the linked list of completed commands with + * valid status, removes these commands from a list of running commands, + * calls the done routine, and flags their target/luns as not busy. + * + * Due to limitations in the intelligence of the NCR chips, certain + * concessions are made. In many cases, it is easier to dynamically + * generate/fix-up code rather than calculate on the NCR at run time. + * So, code is generated or fixed up for + * + * - Handling data transfers, using a variable number of MOVE instructions + * interspersed with CALL MSG_IN, WHEN MSGIN instructions. + * + * The DATAIN and DATAOUT routines are separate, so that an incorrect + * direction can be trapped, and space isn't wasted. + * + * It may turn out that we're better off using some sort + * of table indirect instruction in a loop with a variable + * sized table on the NCR53c710 and newer chips. + * + * - Checking for reselection (NCR53c710 and better) + * + * - Handling the details of SCSI context switches (NCR53c710 and better), + * such as reprogramming appropriate synchronous parameters, + * removing the dsa structure from the NCR's queue of outstanding + * commands, etc. + * + */ + +/* + * Accommodate differences between stock 1.2.x and 1.3.x asm-i386/types.h + * so lusers can drop in 53c7,8xx.* and get something which compiles + * without warnings. + */ + +#if !defined(LINUX_1_2) && !defined(LINUX_1_3) +#include <linux/version.h> +#if LINUX_VERSION_CODE > 65536 + 3 * 256 +#define LINUX_1_3 +#else +#define LINUX_1_2 +#endif +#endif + +#ifdef LINUX_1_2 +#define u32 bogus_u32 +#define s32 bogus_s32 +#include <asm/types.h> +#undef u32 +#undef s32 +typedef __signed__ int s32; +typedef unsigned int u32; +#endif /* def LINUX_1_2 */ + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/system.h> +#include <linux/delay.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/string.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/time.h> +#ifdef LINUX_1_2 +#include "../block/blk.h" +#else +#include <linux/blk.h> +#endif +#undef current + +#include "scsi.h" +#include "hosts.h" +#include "53c7,8xx.h" +#include "constants.h" +#include "sd.h" +#include <linux/stat.h> +#include <linux/stddef.h> + +#ifndef LINUX_1_2 +struct proc_dir_entry proc_scsi_ncr53c7xx = { + PROC_SCSI_NCR53C7xx, 9, "ncr53c7xx", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; +#endif + +static int check_address (unsigned long addr, int size); +static void dump_events (struct Scsi_Host *host, int count); +static Scsi_Cmnd * return_outstanding_commands (struct Scsi_Host *host, + int free, int issue); +static void hard_reset (struct Scsi_Host *host); +static void ncr_scsi_reset (struct Scsi_Host *host); +static void print_lots (struct Scsi_Host *host); +static void set_synchronous (struct Scsi_Host *host, int target, int sxfer, + int scntl3, int now_connected); +static int datapath_residual (struct Scsi_Host *host); +static const char * sbcl_to_phase (int sbcl); +static void print_progress (Scsi_Cmnd *cmd); +static void print_queues (struct Scsi_Host *host); +static void process_issue_queue (unsigned long flags); +static int shutdown (struct Scsi_Host *host); +static void abnormal_finished (struct NCR53c7x0_cmd *cmd, int result); +static int disable (struct Scsi_Host *host); +static int NCR53c8xx_run_tests (struct Scsi_Host *host); +static int NCR53c8xx_script_len; +static int NCR53c8xx_dsa_len; +static void NCR53c7x0_intr(int irq, void *dev_id, struct pt_regs * regs); +static int ncr_halt (struct Scsi_Host *host); +static void intr_phase_mismatch (struct Scsi_Host *host, struct NCR53c7x0_cmd + *cmd); +static void intr_dma (struct Scsi_Host *host, struct NCR53c7x0_cmd *cmd); +static void print_dsa (struct Scsi_Host *host, u32 *dsa, + const char *prefix); +static int print_insn (struct Scsi_Host *host, const u32 *insn, + const char *prefix, int kernel); + +static void NCR53c8xx_dsa_fixup (struct NCR53c7x0_cmd *cmd); +static void NCR53c8x0_init_fixup (struct Scsi_Host *host); +static int NCR53c8x0_dstat_sir_intr (struct Scsi_Host *host, struct + NCR53c7x0_cmd *cmd); +static void NCR53c8x0_soft_reset (struct Scsi_Host *host); + +/* INSMOD variables */ +static long long perm_options = PERM_OPTIONS; +/* 14 = .5s; 15 is max; decreasing divides by two. */ +static int selection_timeout = 14; +/* Size of event list (per host adapter) */ +static int track_events = 0; + +static struct Scsi_Host *first_host = NULL; /* Head of list of NCR boards */ +static Scsi_Host_Template *the_template = NULL; + +/* + * KNOWN BUGS : + * - There is some sort of conflict when the PPP driver is compiled with + * support for 16 channels? + * + * - On systems which predate the 1.3.x initialization order change, + * the NCR driver will cause Cannot get free page messages to appear. + * These are harmless, but I don't know of an easy way to avoid them. + * + * - With OPTION_DISCONNECT, on two systems under unknown circumstances, + * we get a PHASE MISMATCH with DSA set to zero (suggests that we + * are occurring somewhere in the reselection code) where + * DSP=some value DCMD|DBC=same value. + * + * Closer inspection suggests that we may be trying to execute + * some portion of the DSA? + * scsi0 : handling residual transfer (+ 0 bytes from DMA FIFO) + * scsi0 : handling residual transfer (+ 0 bytes from DMA FIFO) + * scsi0 : no current command : unexpected phase MSGIN. + * DSP=0x1c46cc, DCMD|DBC=0x1c46ac, DSA=0x0 + * DSPS=0x0, TEMP=0x1c3e70, DMODE=0x80 + * scsi0 : DSP-> + * 001c46cc : 0x001c46cc 0x00000000 + * 001c46d4 : 0x001c5ea0 0x000011f8 + * + * Changed the print code in the phase_mismatch handler so + * that we call print_lots to try to diagnose this. + * + */ + +/* + * Possible future direction of architecture for max performance : + * + * We're using a single start array for the NCR chip. This is + * sub-optimal, because we cannot add a command which would conflict with + * an executing command to this start queue, and therefore must insert the + * next command for a given I/T/L combination after the first has completed; + * incurring our interrupt latency between SCSI commands. + * + * To allow further pipelining of the NCR and host CPU operation, we want + * to set things up so that immediately on termination of a command destined + * for a given LUN, we get that LUN busy again. + * + * To do this, we need to add a 32 bit pointer to which is jumped to + * on completion of a command. If no new command is available, this + * would point to the usual DSA issue queue select routine. + * + * If one were, it would point to a per-NCR53c7x0_cmd select routine + * which starts execution immediately, inserting the command at the head + * of the start queue if the NCR chip is selected or reselected. + * + * We would change so that we keep a list of outstanding commands + * for each unit, rather than a single running_list. We'd insert + * a new command into the right running list; if the NCR didn't + * have something running for that yet, we'd put it in the + * start queue as well. Some magic needs to happen to handle the + * race condition between the first command terminating before the + * new one is written. + * + * Potential for profiling : + * Call do_gettimeofday(struct timeval *tv) to get 800ns resolution. + */ + + +/* + * TODO : + * 1. To support WIDE transfers, not much needs to happen. We + * should do CHMOVE instructions instead of MOVEs when + * we have scatter/gather segments of uneven length. When + * we do this, we need to handle the case where we disconnect + * between segments. + * + * 2. Currently, when Icky things happen we do a FATAL(). Instead, + * we want to do an integrity check on the parts of the NCR hostdata + * structure which were initialized at boot time; FATAL() if that + * fails, and otherwise try to recover. Keep track of how many + * times this has happened within a single SCSI command; if it + * gets excessive, then FATAL(). + * + * 3. Parity checking is currently disabled, and a few things should + * happen here now that we support synchronous SCSI transfers : + * 1. On soft-reset, we should set the EPC (Enable Parity Checking) + * and AAP (Assert SATN/ on parity error) bits in SCNTL0. + * + * 2. We should enable the parity interrupt in the SIEN0 register. + * + * 3. intr_phase_mismatch() needs to believe that message out is + * always an "acceptable" phase to have a mismatch in. If + * the old phase was MSG_IN, we should send a MESSAGE PARITY + * error. If the old phase was something else, we should send + * a INITIATOR_DETECTED_ERROR message. Note that this could + * cause a RESTORE POINTERS message; so we should handle that + * correctly first. Instead, we should probably do an + * initiator_abort. + * + * 4. MPEE bit of CTEST4 should be set so we get interrupted if + * we detect an error. + * + * + * 5. The initial code has been tested on the NCR53c810. I don't + * have access to NCR53c700, 700-66 (Forex boards), NCR53c710 + * (NCR Pentium systems), NCR53c720, NCR53c820, or NCR53c825 boards to + * finish development on those platforms. + * + * NCR53c820/825/720 - need to add wide transfer support, including WDTR + * negotiation, programming of wide transfer capabilities + * on reselection and table indirect selection. + * + * NCR53c710 - need to add fatal interrupt or GEN code for + * command completion signaling. Need to modify all + * SDID, SCID, etc. registers, and table indirect select code + * since these use bit fielded (ie 1<<target) instead of + * binary encoded target ids. Need to accommodate + * different register mappings, probably scan through + * the SCRIPT code and change the non SFBR register operand + * of all MOVE instructions. + * + * NCR53c700/700-66 - need to add code to refix addresses on + * every nexus change, eliminate all table indirect code, + * very messy. + * + * 6. The NCR53c7x0 series is very popular on other platforms that + * could be running Linux - ie, some high performance AMIGA SCSI + * boards use it. + * + * So, I should include #ifdef'd code so that it is + * compatible with these systems. + * + * Specifically, the little Endian assumptions I made in my + * bit fields need to change, and if the NCR doesn't see memory + * the right way, we need to provide options to reverse words + * when the scripts are relocated. + * + * 7. Use vremap() to access memory mapped boards. + */ + +/* + * Allow for simultaneous existence of multiple SCSI scripts so we + * can have a single driver binary for all of the family. + * + * - one for NCR53c700 and NCR53c700-66 chips (not yet supported) + * - one for rest (only the NCR53c810, 815, 820, and 825 are currently + * supported) + * + * So that we only need two SCSI scripts, we need to modify things so + * that we fixup register accesses in READ/WRITE instructions, and + * we'll also have to accommodate the bit vs. binary encoding of IDs + * with the 7xx chips. + */ + +/* + * Use pci_chips_ids to translate in both directions between PCI device ID + * and chip numbers. + */ + +static struct { + unsigned short pci_device_id; + int chip; +/* + * The revision field of the PCI_CLASS_REVISION register is compared + * against each of these fields if the field is not -1. If it + * is less than min_revision or larger than max_revision, a warning + * message is printed. + */ + int max_revision; + int min_revision; +} pci_chip_ids[] = { + {PCI_DEVICE_ID_NCR_53C810, 810, 2, 1}, + {PCI_DEVICE_ID_NCR_53C815, 815, 3, 2}, + {PCI_DEVICE_ID_NCR_53C820, 820, -1, -1}, + {PCI_DEVICE_ID_NCR_53C825, 825, -1, -1} +}; + +#define NPCI_CHIP_IDS (sizeof (pci_chip_ids) / sizeof(pci_chip_ids[0])) + +#define ROUNDUP(adr,type) \ + ((void *) (((long) (adr) + sizeof(type) - 1) & ~(sizeof(type) - 1))) + +/* + * Forced detection and autoprobe code for various hardware. Currently, + * entry points for these are not included in init/main.c because if the + * PCI BIOS code isn't working right, you're not going to be able to use + * the hardware anyways; this way we force users to solve their + * problems rather than forcing detection and blaming us when it + * does not work. + */ + +static struct override { + int chip; /* 700, 70066, 710, 720, 810, 820 */ + int board; /* Any special board level gunk */ + unsigned pci:1; + union { + struct { + int base; /* Memory address - indicates memory mapped regs */ + int io_port;/* I/O port address - indicates I/O mapped regs */ + int irq; /* IRQ line */ + int dma; /* DMA channel - often none */ + } normal; + struct { + int bus; + int device; + int function; + } pci; + } data; + long long options; +} overrides [4] = {{0,},}; +static int commandline_current = 0; +static int no_overrides = 0; + +#if 0 +#define OVERRIDE_LIMIT (sizeof(overrides) / sizeof(struct override)) +#else +#define OVERRIDE_LIMIT commandline_current +#endif + +/* + * Function: issue_to_cmd + * + * Purpose: convert jump instruction in issue array to NCR53c7x0_cmd + * structure pointer. + * + * Inputs; issue - pointer to start of NOP or JUMP instruction + * in issue array. + * + * Returns: pointer to command on success; 0 if opcode is NOP. + */ + +static inline struct NCR53c7x0_cmd * +issue_to_cmd (struct Scsi_Host *host, struct NCR53c7x0_hostdata *hostdata, + u32 *issue) +{ + return (issue[0] != hostdata->NOP_insn) ? + /* + * If the IF TRUE bit is set, it's a JUMP instruction. The + * operand is a bus pointer to the dsa_begin routine for this DSA. The + * dsa field of the NCR53c7x0_cmd structure starts with the + * DSA code template. By converting to a virtual address, + * subtracting the code template size, and offset of the + * dsa field, we end up with a pointer to the start of the + * structure (alternatively, we could use the + * dsa_cmnd field, an anachronism from when we weren't + * sure what the relationship between the NCR structures + * and host structures were going to be. + */ + (struct NCR53c7x0_cmd *) ((char *) bus_to_virt (issue[1]) - + (hostdata->E_dsa_code_begin - hostdata->E_dsa_code_template) - + offsetof(struct NCR53c7x0_cmd, dsa)) + /* If the IF TRUE bit is not set, it's a NOP */ + : NULL; +} + + +/* + * Function : static internal_setup(int board, int chip, char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : board - currently, unsupported. chip - 700, 70066, 710, 720 + * 810, 815, 820, 825, although currently only the NCR53c810 is + * supported. + * + */ + +static void +internal_setup(int board, int chip, char *str, int *ints) { + unsigned char pci; /* Specifies a PCI override, with bus, device, + function */ + + pci = (str && !strcmp (str, "pci")) ? 1 : 0; + +/* + * Override syntaxes are as follows : + * ncr53c700,ncr53c700-66,ncr53c710,ncr53c720=mem,io,irq,dma + * ncr53c810,ncr53c820,ncr53c825=mem,io,irq or pci,bus,device,function + */ + + if (commandline_current < OVERRIDE_LIMIT) { + overrides[commandline_current].pci = pci ? 1 : 0; + if (!pci) { + overrides[commandline_current].data.normal.base = ints[1]; + overrides[commandline_current].data.normal.io_port = ints[2]; + overrides[commandline_current].data.normal.irq = ints[3]; + overrides[commandline_current].data.normal.dma = (ints[0] >= 4) ? + ints[4] : DMA_NONE; + /* FIXME: options is now a long long */ + overrides[commandline_current].options = (ints[0] >= 5) ? + ints[5] : 0; + } else { + overrides[commandline_current].data.pci.bus = ints[1]; + overrides[commandline_current].data.pci.device = ints[2]; + overrides[commandline_current].data.pci.function = ints[3]; + /* FIXME: options is now a long long */ + overrides[commandline_current].options = (ints[0] >= 4) ? + ints[4] : 0; + } + overrides[commandline_current].board = board; + overrides[commandline_current].chip = chip; + ++commandline_current; + ++no_overrides; + } else { + printk ("53c7,7x0.c:internal_setup() : too many overrides\n"); + } +} + +/* + * XXX - we might want to implement a single override function + * with a chip type field, revamp the command line configuration, + * etc. + */ + +#define setup_wrapper(x) \ +void ncr53c##x##_setup (char *str, int *ints) { \ + internal_setup (BOARD_GENERIC, x, str, ints); \ +} + +setup_wrapper(700) +setup_wrapper(70066) +setup_wrapper(710) +setup_wrapper(720) +setup_wrapper(810) +setup_wrapper(815) +setup_wrapper(820) +setup_wrapper(825) + +/* + * FIXME: we should junk these, in favor of synchronous_want and + * wide_want in the NCR53c7x0_hostdata structure. + */ + +/* Template for "preferred" synchronous transfer parameters. */ + +static const unsigned char sdtr_message[] = { +#ifdef CONFIG_SCSI_NCR53C7xx_FAST + EXTENDED_MESSAGE, 3 /* length */, EXTENDED_SDTR, 25 /* *4ns */, 8 /* off */ +#else + EXTENDED_MESSAGE, 3 /* length */, EXTENDED_SDTR, 50 /* *4ns */, 8 /* off */ +#endif +}; + +/* Template to request asynchronous transfers */ + +static const unsigned char async_message[] = { + EXTENDED_MESSAGE, 3 /* length */, EXTENDED_SDTR, 0, 0 /* asynchronous */ +}; + +/* Template for "preferred" WIDE transfer parameters */ + +static const unsigned char wdtr_message[] = { + EXTENDED_MESSAGE, 2 /* length */, EXTENDED_WDTR, 1 /* 2^1 bytes */ +}; + +/* + * Function : struct Scsi_Host *find_host (int host) + * + * Purpose : KGDB support function which translates a host number + * to a host structure. + * + * Inputs : host - number of SCSI host + * + * Returns : NULL on failure, pointer to host structure on success. + */ + +static struct Scsi_Host * +find_host (int host) { + struct Scsi_Host *h; + for (h = first_host; h && h->host_no != host; h = h->next); + if (!h) { + printk (KERN_ALERT "scsi%d not found\n", host); + return NULL; + } else if (h->hostt != the_template) { + printk (KERN_ALERT "scsi%d is not a NCR board\n", host); + return NULL; + } + return h; +} + +/* + * Function : request_synchronous (int host, int target) + * + * Purpose : KGDB interface which will allow us to negotiate for + * synchronous transfers. This ill be replaced with a more + * integrated function; perhaps a new entry in the scsi_host + * structure, accessible via an ioctl() or perhaps /proc/scsi. + * + * Inputs : host - number of SCSI host; target - number of target. + * + * Returns : 0 when negotiation has been setup for next SCSI command, + * -1 on failure. + */ + +static int +request_synchronous (int host, int target) { + struct Scsi_Host *h; + struct NCR53c7x0_hostdata *hostdata; + unsigned long flags; + if (target < 0) { + printk (KERN_ALERT "target %d is bogus\n", target); + return -1; + } + if (!(h = find_host (host))) + return -1; + else if (h->this_id == target) { + printk (KERN_ALERT "target %d is host ID\n", target); + return -1; + } +#ifndef LINUX_1_2 + else if (target > h->max_id) { + printk (KERN_ALERT "target %d exceeds maximum of %d\n", target, + h->max_id); + return -1; + } +#endif + hostdata = (struct NCR53c7x0_hostdata *)h->hostdata; + + save_flags(flags); + cli(); + if (hostdata->initiate_sdtr & (1 << target)) { + restore_flags(flags); + printk (KERN_ALERT "target %d already doing SDTR\n", target); + return -1; + } + hostdata->initiate_sdtr |= (1 << target); + restore_flags(flags); + return 0; +} + +/* + * Function : request_disconnect (int host, int on_or_off) + * + * Purpose : KGDB support function, tells us to allow or disallow + * disconnections. + * + * Inputs : host - number of SCSI host; on_or_off - non-zero to allow, + * zero to disallow. + * + * Returns : 0 on success, * -1 on failure. + */ + +static int +request_disconnect (int host, int on_or_off) { + struct Scsi_Host *h; + struct NCR53c7x0_hostdata *hostdata; + if (!(h = find_host (host))) + return -1; + hostdata = (struct NCR53c7x0_hostdata *) h->hostdata; + if (on_or_off) + hostdata->options |= OPTION_DISCONNECT; + else + hostdata->options &= ~OPTION_DISCONNECT; + return 0; +} + +/* + * Function : static void NCR53c7x0_driver_init (struct Scsi_Host *host) + * + * Purpose : Initialize internal structures, as required on startup, or + * after a SCSI bus reset. + * + * Inputs : host - pointer to this host adapter's structure + */ + +static void +NCR53c7x0_driver_init (struct Scsi_Host *host) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int i, j; + u32 *current; + for (i = 0; i < 16; ++i) { + hostdata->request_sense[i] = 0; + for (j = 0; j < 8; ++j) + hostdata->busy[i][j] = 0; + set_synchronous (host, i, /* sxfer */ 0, hostdata->saved_scntl3, 0); + } + hostdata->issue_queue = NULL; + hostdata->running_list = hostdata->finished_queue = + hostdata->current = NULL; + for (i = 0, current = (u32 *) hostdata->schedule; + i < host->can_queue; ++i, current += 2) { + current[0] = hostdata->NOP_insn; + current[1] = 0xdeadbeef; + } + current[0] = ((DCMD_TYPE_TCI|DCMD_TCI_OP_JUMP) << 24) | DBC_TCI_TRUE; + current[1] = (u32) virt_to_bus (hostdata->script) + + hostdata->E_wait_reselect; + hostdata->reconnect_dsa_head = 0; + hostdata->addr_reconnect_dsa_head = (u32) + virt_to_bus((void *) &(hostdata->reconnect_dsa_head)); + hostdata->expecting_iid = 0; + hostdata->expecting_sto = 0; + if (hostdata->options & OPTION_ALWAYS_SYNCHRONOUS) + hostdata->initiate_sdtr = 0xffff; + else + hostdata->initiate_sdtr = 0; + hostdata->talked_to = 0; + hostdata->idle = 1; +} + +/* + * Function : static int ccf_to_clock (int ccf) + * + * Purpose : Return the largest SCSI clock allowable for a given + * clock conversion factor, allowing us to do synchronous periods + * when we don't know what the SCSI clock is by taking at least + * as long as the device says we can. + * + * Inputs : ccf + * + * Returns : clock on success, -1 on failure. + */ + +static int +ccf_to_clock (int ccf) { + switch (ccf) { + case 1: return 25000000; /* Divide by 1.0 */ + case 2: return 37500000; /* Divide by 1.5 */ + case 3: return 50000000; /* Divide by 2.0 */ + case 0: /* Divide by 3.0 */ + case 4: return 66000000; + default: return -1; + } +} + +/* + * Function : static int clock_to_ccf (int clock) + * + * Purpose : Return the clock conversion factor for a given SCSI clock. + * + * Inputs : clock - SCSI clock expressed in Hz. + * + * Returns : ccf on success, -1 on failure. + */ + +static int +clock_to_ccf (int clock) { + if (clock < 16666666) + return -1; + if (clock < 25000000) + return 1; /* Divide by 1.0 */ + else if (clock < 37500000) + return 2; /* Divide by 1.5 */ + else if (clock < 50000000) + return 3; /* Divide by 2.0 */ + else if (clock < 66000000) + return 4; /* Divide by 3.0 */ + else + return -1; +} + +/* + * Function : static int NCR53c7x0_init (struct Scsi_Host *host) + * + * Purpose : initialize the internal structures for a given SCSI host + * + * Inputs : host - pointer to this host adapter's structure + * + * Preconditions : when this function is called, the chip_type + * field of the hostdata structure MUST have been set. + * + * Returns : 0 on success, -1 on failure. + */ + +static int +NCR53c7x0_init (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + int i, ccf, expected_ccf; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + struct Scsi_Host *search; + /* + * There are some things which we need to know about in order to provide + * a semblance of support. Print 'em if they aren't what we expect, + * otherwise don't add to the noise. + * + * -1 means we don't know what to expect. + */ + int expected_id = -1; + int expected_clock = -1; + int uninitialized = 0; + /* + * FIXME : this is only on Intel boxes. On other platforms, this + * will differ. + */ + int expected_mapping = OPTION_IO_MAPPED; + NCR53c7x0_local_setup(host); + + switch (hostdata->chip) { + case 820: + case 825: +#ifdef notyet + host->max_id = 15; +#endif + /* Fall through */ + case 810: + case 815: + hostdata->dstat_sir_intr = NCR53c8x0_dstat_sir_intr; + hostdata->init_save_regs = NULL; + hostdata->dsa_fixup = NCR53c8xx_dsa_fixup; + hostdata->init_fixup = NCR53c8x0_init_fixup; + hostdata->soft_reset = NCR53c8x0_soft_reset; + hostdata->run_tests = NCR53c8xx_run_tests; +/* Is the SCSI clock ever anything else on these chips? */ + expected_clock = hostdata->scsi_clock = 40000000; + expected_id = 7; + break; + default: + printk ("scsi%d : chip type of %d is not supported yet, detaching.\n", + host->host_no, hostdata->chip); + scsi_unregister (host); + return -1; + } + + /* Assign constants accessed by NCR */ + hostdata->NCR53c7xx_zero = 0; + hostdata->NCR53c7xx_msg_reject = MESSAGE_REJECT; + hostdata->NCR53c7xx_msg_abort = ABORT; + hostdata->NCR53c7xx_msg_nop = NOP; + hostdata->NOP_insn = (DCMD_TYPE_TCI|DCMD_TCI_OP_JUMP) << 24; + + if (expected_mapping == -1 || + (hostdata->options & (OPTION_MEMORY_MAPPED)) != + (expected_mapping & OPTION_MEMORY_MAPPED)) + printk ("scsi%d : using %s mapped access\n", host->host_no, + (hostdata->options & OPTION_MEMORY_MAPPED) ? "memory" : + "io"); + + hostdata->dmode = (hostdata->chip == 700 || hostdata->chip == 70066) ? + DMODE_REG_00 : DMODE_REG_10; + hostdata->istat = ((hostdata->chip / 100) == 8) ? + ISTAT_REG_800 : ISTAT_REG_700; + +/* Only the ISTAT register is readable when the NCR is running, so make + sure it's halted. */ + ncr_halt(host); + +/* + * XXX - the NCR53c700 uses bitfielded registers for SCID, SDID, etc, + * as does the 710 with one bit per SCSI ID. Conversely, the NCR + * uses a normal, 3 bit binary representation of these values. + * + * Get the rest of the NCR documentation, and FIND OUT where the change + * was. + */ +#if 0 + tmp = hostdata->this_id_mask = NCR53c7x0_read8(SCID_REG); + for (host->this_id = 0; tmp != 1; tmp >>=1, ++host->this_id); +#else + host->this_id = NCR53c7x0_read8(SCID_REG) & 15; + if (host->this_id == 0) + host->this_id = 7; /* sanitize hostid---0 doesn't make sense */ + hostdata->this_id_mask = 1 << host->this_id; +#endif + +/* + * Note : we should never encounter a board setup for ID0. So, + * if we see ID0, assume that it was uninitialized and set it + * to the industry standard 7. + */ + if (!host->this_id) { + printk("scsi%d : initiator ID was %d, changing to 7\n", + host->host_no, host->this_id); + host->this_id = 7; + hostdata->this_id_mask = 1 << 7; + uninitialized = 1; + }; + + if (expected_id == -1 || host->this_id != expected_id) + printk("scsi%d : using initiator ID %d\n", host->host_no, + host->this_id); + + /* + * Save important registers to allow a soft reset. + */ + + if ((hostdata->chip / 100) == 8) { + /* + * CTEST4 controls burst mode disable. + */ + hostdata->saved_ctest4 = NCR53c7x0_read8(CTEST4_REG_800) & + CTEST4_800_SAVE; + } else { + /* + * CTEST7 controls cache snooping, burst mode, and support for + * external differential drivers. + */ + hostdata->saved_ctest7 = NCR53c7x0_read8(CTEST7_REG) & CTEST7_SAVE; + } + + /* + * On NCR53c700 series chips, DCNTL controls the SCSI clock divisor, + * on 800 series chips, it allows for a totem-pole IRQ driver. + */ + + hostdata->saved_dcntl = NCR53c7x0_read8(DCNTL_REG); + + /* + * DCNTL_800_IRQM controls weather we are using an open drain + * driver (reset) or totem pole driver (set). In all cases, + * it's level active. I suppose this is an issue when we're trying to + * wire-or the same PCI INTx line? + */ + if ((hostdata->chip / 100) == 8) + hostdata->saved_dcntl &= ~DCNTL_800_IRQM; + + /* + * DMODE controls DMA burst length, and on 700 series chips, + * 286 mode and bus width + */ + hostdata->saved_dmode = NCR53c7x0_read8(hostdata->dmode); + + /* + * Now that burst length and enabled/disabled status is known, + * clue the user in on it. + */ + + if ((hostdata->chip / 100) == 8) { + if (hostdata->saved_ctest4 & CTEST4_800_BDIS) { + printk ("scsi%d : burst mode disabled\n", host->host_no); + } else { + switch (hostdata->saved_dmode & DMODE_BL_MASK) { + case DMODE_BL_2: i = 2; break; + case DMODE_BL_4: i = 4; break; + case DMODE_BL_8: i = 8; break; + case DMODE_BL_16: i = 16; break; + default: i = 0; + } + printk ("scsi%d : burst length %d\n", host->host_no, i); + } + } + + /* + * On NCR53c810 and NCR53c820 chips, SCNTL3 contails the synchronous + * and normal clock conversion factors. + */ + if (hostdata->chip / 100 == 8) { + expected_ccf = clock_to_ccf (expected_clock); + hostdata->saved_scntl3 = NCR53c7x0_read8(SCNTL3_REG_800); + ccf = hostdata->saved_scntl3 & SCNTL3_800_CCF_MASK; + if (expected_ccf != -1 && ccf != expected_ccf && !ccf) { + hostdata->saved_scntl3 = (hostdata->saved_scntl3 & + ~SCNTL3_800_CCF_MASK) | expected_ccf; + if (!uninitialized) { + printk ("scsi%d : reset ccf to %d from %d\n", + host->host_no, expected_ccf, ccf); + uninitialized = 1; + } + } + } else + ccf = 0; + + /* + * If we don't have a SCSI clock programmed, pick one on the upper + * bound of that allowed by NCR so that our transfers err on the + * slow side, since transfer period must be >= the agreed + * upon period. + */ + + if ((!hostdata->scsi_clock) && (hostdata->scsi_clock = ccf_to_clock (ccf)) + == -1) { + printk ("scsi%d : clock conversion factor %d unknown.\n" + " synchronous transfers disabled\n", + host->host_no, ccf); + hostdata->options &= ~OPTION_SYNCHRONOUS; + hostdata->scsi_clock = 0; + } + + if (expected_clock == -1 || hostdata->scsi_clock != expected_clock) + printk ("scsi%d : using %dMHz SCSI clock\n", host->host_no, + hostdata->scsi_clock / 1000000); + + for (i = 0; i < 16; ++i) + hostdata->cmd_allocated[i] = 0; + + if (hostdata->init_save_regs) + hostdata->init_save_regs (host); + if (hostdata->init_fixup) + hostdata->init_fixup (host); + + if (!the_template) { + the_template = host->hostt; + first_host = host; + } + + /* + * Linux SCSI drivers have always been plagued with initialization + * problems - some didn't work with the BIOS disabled since they expected + * initialization from it, some didn't work when the networking code + * was enabled and registers got scrambled, etc. + * + * To avoid problems like this, in the future, we will do a soft + * reset on the SCSI chip, taking it back to a sane state. + */ + + hostdata->soft_reset (host); + +#if 1 + hostdata->debug_count_limit = -1; +#else + hostdata->debug_count_limit = 1; +#endif + hostdata->intrs = -1; + hostdata->resets = -1; + memcpy ((void *) hostdata->synchronous_want, (void *) sdtr_message, + sizeof (hostdata->synchronous_want)); + + NCR53c7x0_driver_init (host); + + /* + * Set up an interrupt handler if we aren't already sharing an IRQ + * with another board. + */ + + for (search = first_host; search && !(search->hostt == the_template && + search->irq == host->irq && search != host); search=search->next); + + if (!search) { + if (request_irq(host->irq, NCR53c7x0_intr, SA_INTERRUPT, "53c7,8xx", NULL)) { + printk("scsi%d : IRQ%d not free, detaching\n" + " You have either a configuration problem, or a\n" + " broken BIOS. You may wish to manually assign\n" + " an interrupt to the NCR board rather than using\n" + " an automatic setting.\n", + host->host_no, host->irq); + scsi_unregister (host); + return -1; + } + } else { + printk("scsi%d : using interrupt handler previously installed for scsi%d\n", + host->host_no, search->host_no); + } + + + if ((hostdata->run_tests && hostdata->run_tests(host) == -1) || + (hostdata->options & OPTION_DEBUG_TESTS_ONLY)) { + /* XXX Should disable interrupts, etc. here */ + scsi_unregister (host); + return -1; + } else { + if (host->io_port) { + host->n_io_port = 128; + request_region (host->io_port, host->n_io_port, "ncr53c7,8xx"); + } + } + + if (NCR53c7x0_read8 (SBCL_REG) & SBCL_BSY) { + printk ("scsi%d : bus wedge, doing SCSI reset\n", host->host_no); + hard_reset (host); + } + return 0; +} + +/* + * Function : static int normal_init(Scsi_Host_Template *tpnt, int board, + * int chip, u32 base, int io_port, int irq, int dma, int pcivalid, + * unsigned char pci_bus, unsigned char pci_device_fn, + * long long options); + * + * Purpose : initializes a NCR53c7,8x0 based on base addresses, + * IRQ, and DMA channel. + * + * Useful where a new NCR chip is backwards compatible with + * a supported chip, but the DEVICE ID has changed so it + * doesn't show up when the autoprobe does a pcibios_find_device. + * + * Inputs : tpnt - Template for this SCSI adapter, board - board level + * product, chip - 810, 820, or 825, bus - PCI bus, device_fn - + * device and function encoding as used by PCI BIOS calls. + * + * Returns : 0 on success, -1 on failure. + * + */ + +static int +normal_init (Scsi_Host_Template *tpnt, int board, int chip, + u32 base, int io_port, int irq, int dma, int pci_valid, + unsigned char pci_bus, unsigned char pci_device_fn, long long options) { + struct Scsi_Host *instance; + struct NCR53c7x0_hostdata *hostdata; + char chip_str[80]; + int script_len = 0, dsa_len = 0, size = 0, max_cmd_size = 0, + schedule_size = 0, ok = 0; + void *tmp; + + options |= perm_options; + + switch (chip) { + case 825: + case 820: + case 815: + case 810: + schedule_size = (tpnt->can_queue + 1) * 8 /* JUMP instruction size */; + script_len = NCR53c8xx_script_len; + dsa_len = NCR53c8xx_dsa_len; + options |= OPTION_INTFLY; + sprintf (chip_str, "NCR53c%d", chip); + break; + default: + printk("scsi-ncr53c7,8xx : unsupported SCSI chip %d\n", chip); + return -1; + } + + printk("scsi-ncr53c7,8xx : %s at memory 0x%x, io 0x%x, irq %d", + chip_str, (unsigned) base, io_port, irq); + if (dma == DMA_NONE) + printk("\n"); + else + printk(", dma %d\n", dma); + + if ((chip / 100 == 8) && !pci_valid) + printk ("scsi-ncr53c7,8xx : for better reliability and performance, please use the\n" + " PCI override instead.\n" + " Syntax : ncr53c8{10,15,20,25}=pci,<bus>,<device>,<function>\n" + " <bus> and <device> are usually 0.\n"); + + if (options & OPTION_DEBUG_PROBE_ONLY) { + printk ("scsi-ncr53c7,8xx : probe only enabled, aborting initialization\n"); + return -1; + } + + max_cmd_size = sizeof(struct NCR53c7x0_cmd) + dsa_len + + /* Size of dynamic part of command structure : */ + 2 * /* Worst case : we don't know if we need DATA IN or DATA out */ + ( 2 * /* Current instructions per scatter/gather segment */ + tpnt->sg_tablesize + + 3 /* Current startup / termination required per phase */ + ) * + 8 /* Each instruction is eight bytes */; + + /* Allocate fixed part of hostdata, dynamic part to hold appropriate + SCSI SCRIPT(tm) plus a single, maximum-sized NCR53c7x0_cmd structure. + + We need a NCR53c7x0_cmd structure for scan_scsis() when we are + not loaded as a module, and when we're loaded as a module, we + can't use a non-dynamically allocated structure because modules + are vmalloc()'d, which can allow structures to cross page + boundaries and breaks our physical/virtual address assumptions + for DMA. + + So, we stick it past the end of our hostdata structure. + + ASSUMPTION : + Regardless of how many simultaneous SCSI commands we allow, + the probe code only executes a _single_ instruction at a time, + so we only need one here, and don't need to allocate NCR53c7x0_cmd + structures for each target until we are no longer in scan_scsis + and kmalloc() has become functional (memory_init() happens + after all device driver initialization). + */ + + size = sizeof(struct NCR53c7x0_hostdata) + script_len + + /* Note that alignment will be guaranteed, since we put the command + allocated at probe time after the fixed-up SCSI script, which + consists of 32 bit words, aligned on a 32 bit boundary. But + on a 64bit machine we need 8 byte alignment for hostdata->free, so + we add in another 4 bytes to take care of potential misalignment + */ + (sizeof(void *) - sizeof(u32)) + max_cmd_size + schedule_size; + + instance = scsi_register (tpnt, size); + if (!instance) + return -1; + + /* FIXME : if we ever support an ISA NCR53c7xx based board, we + need to check if the chip is running in a 16 bit mode, and if so + unregister it if it is past the 16M (0x1000000) mark */ + + hostdata = (struct NCR53c7x0_hostdata *) + instance->hostdata; + hostdata->size = size; + hostdata->script_count = script_len / sizeof(u32); + hostdata = (struct NCR53c7x0_hostdata *) instance->hostdata; + hostdata->board = board; + hostdata->chip = chip; + if ((hostdata->pci_valid = pci_valid)) { + hostdata->pci_bus = pci_bus; + hostdata->pci_device_fn = pci_device_fn; + } + + /* + * Being memory mapped is more desirable, since + * + * - Memory accesses may be faster. + * + * - The destination and source address spaces are the same for + * all instructions, meaning we don't have to twiddle dmode or + * any other registers. + * + * So, we try for memory mapped, and if we don't get it, + * we go for port mapped, and that failing we tell the user + * it can't work. + */ + + if (base) { + instance->base = (unsigned char *) (unsigned long) base; + /* Check for forced I/O mapping */ + if (!(options & OPTION_IO_MAPPED)) { + options |= OPTION_MEMORY_MAPPED; + ok = 1; + } + } else { + options &= ~OPTION_MEMORY_MAPPED; + } + + if (io_port) { + instance->io_port = io_port; + options |= OPTION_IO_MAPPED; + ok = 1; + } else { + options &= ~OPTION_IO_MAPPED; + } + + if (!ok) { + printk ("scsi%d : not initializing, no I/O or memory mapping known \n", + instance->host_no); + scsi_unregister (instance); + return -1; + } + instance->irq = irq; + instance->dma_channel = dma; + + hostdata->options = options; + hostdata->dsa_len = dsa_len; + hostdata->max_cmd_size = max_cmd_size; + hostdata->num_cmds = 1; + /* Initialize single command */ + tmp = (hostdata->script + hostdata->script_count); + hostdata->free = ROUNDUP(tmp, void *); + hostdata->free->real = tmp; + hostdata->free->size = max_cmd_size; + hostdata->free->free = NULL; + hostdata->free->next = NULL; + hostdata->extra_allocate = 0; + + /* Allocate command start code space */ + hostdata->schedule = (chip == 700 || chip == 70066) ? + NULL : (u32 *) ((char *)hostdata->free + max_cmd_size); + +/* + * For diagnostic purposes, we don't really care how fast things blaze. + * For profiling, we want to access the 800ns resolution system clock, + * using a 'C' call on the host processor. + * + * Therefore, there's no need for the NCR chip to directly manipulate + * this data, and we should put it wherever is most convenient for + * Linux. + */ + if (track_events) + hostdata->events = (struct NCR53c7x0_event *) (track_events ? + vmalloc (sizeof (struct NCR53c7x0_event) * track_events) : NULL); + else + hostdata->events = NULL; + + if (hostdata->events) { + memset ((void *) hostdata->events, 0, sizeof(struct NCR53c7x0_event) * + track_events); + hostdata->event_size = track_events; + hostdata->event_index = 0; + } else + hostdata->event_size = 0; + + return NCR53c7x0_init(instance); +} + + +/* + * Function : static int ncr_pci_init(Scsi_Host_Template *tpnt, int board, + * int chip, int bus, int device_fn, long long options) + * + * Purpose : initializes a NCR53c800 family based on the PCI + * bus, device, and function location of it. Allows + * reprogramming of latency timer and determining addresses + * and whether bus mastering, etc. are OK. + * + * Useful where a new NCR chip is backwards compatible with + * a supported chip, but the DEVICE ID has changed so it + * doesn't show up when the autoprobe does a pcibios_find_device. + * + * Inputs : tpnt - Template for this SCSI adapter, board - board level + * product, chip - 810, 820, or 825, bus - PCI bus, device_fn - + * device and function encoding as used by PCI BIOS calls. + * + * Returns : 0 on success, -1 on failure. + * + */ + +static int +ncr_pci_init (Scsi_Host_Template *tpnt, int board, int chip, + unsigned char bus, unsigned char device_fn, long long options) { + unsigned short vendor_id, device_id, command; +#ifdef LINUX_1_2 + unsigned long +#else + unsigned int +#endif + base, io_port; + unsigned char irq, revision; + int error, expected_chip; + int expected_id = -1, max_revision = -1, min_revision = -1; + int i; + + printk("scsi-ncr53c7,8xx : at PCI bus %d, device %d, function %d\n", + bus, (int) (device_fn & 0xf8) >> 3, + (int) device_fn & 7); + + if (!pcibios_present()) { + printk("scsi-ncr53c7,8xx : not initializing due to lack of PCI BIOS,\n" + " try using memory, port, irq override instead.\n"); + return -1; + } + + if ((error = pcibios_read_config_word (bus, device_fn, PCI_VENDOR_ID, + &vendor_id)) || + (error = pcibios_read_config_word (bus, device_fn, PCI_DEVICE_ID, + &device_id)) || + (error = pcibios_read_config_word (bus, device_fn, PCI_COMMAND, + &command)) || + (error = pcibios_read_config_dword (bus, device_fn, + PCI_BASE_ADDRESS_0, &io_port)) || + (error = pcibios_read_config_dword (bus, device_fn, + PCI_BASE_ADDRESS_1, &base)) || + (error = pcibios_read_config_byte (bus, device_fn, PCI_CLASS_REVISION, + &revision)) || + (error = pcibios_read_config_byte (bus, device_fn, PCI_INTERRUPT_LINE, + &irq))) { + printk ("scsi-ncr53c7,8xx : error %s not initializing due to error reading configuration space\n" + " perhaps you specified an incorrect PCI bus, device, or function.\n" + , pcibios_strerror(error)); + return -1; + } + + /* If any one ever clones the NCR chips, this will have to change */ + + if (vendor_id != PCI_VENDOR_ID_NCR) { + printk ("scsi-ncr53c7,8xx : not initializing, 0x%04x is not NCR vendor ID\n", + (int) vendor_id); + return -1; + } + + + /* + * Bit 0 is the address space indicator and must be one for I/O + * space mappings, bit 1 is reserved, discard them after checking + * that they have the correct value of 1. + */ + + if (command & PCI_COMMAND_IO) { + if ((io_port & 3) != 1) { + printk ("scsi-ncr53c7,8xx : disabling I/O mapping since base address 0 (0x%x)\n" + " bits 0..1 indicate a non-IO mapping\n", + (unsigned) io_port); + io_port = 0; + } else + io_port &= PCI_BASE_ADDRESS_IO_MASK; + } else { + io_port = 0; + } + + if (command & PCI_COMMAND_MEMORY) { + if ((base & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) { + printk("scsi-ncr53c7,8xx : disabling memory mapping since base address 1\n" + " contains a non-memory mapping\n"); + base = 0; + } else + base &= PCI_BASE_ADDRESS_MEM_MASK; + } else { + base = 0; + } + + if (!io_port && !base) { + printk ("scsi-ncr53c7,8xx : not initializing, both I/O and memory mappings disabled\n"); + return -1; + } + + if (!(command & PCI_COMMAND_MASTER)) { + printk ("scsi-ncr53c7,8xx : not initializing, BUS MASTERING was disabled\n"); + return -1; + } + + for (i = 0; i < NPCI_CHIP_IDS; ++i) { + if (device_id == pci_chip_ids[i].pci_device_id) { + max_revision = pci_chip_ids[i].max_revision; + min_revision = pci_chip_ids[i].min_revision; + expected_chip = pci_chip_ids[i].chip; + } + if (chip == pci_chip_ids[i].chip) + expected_id = pci_chip_ids[i].pci_device_id; + } + + if (chip && device_id != expected_id) + printk ("scsi-ncr53c7,8xx : warning : device id of 0x%04x doesn't\n" + " match expected 0x%04x\n", + (unsigned int) device_id, (unsigned int) expected_id ); + + if (max_revision != -1 && revision > max_revision) + printk ("scsi-ncr53c7,8xx : warning : revision of %d is greater than %d.\n", + (int) revision, max_revision); + else if (min_revision != -1 && revision < min_revision) + printk ("scsi-ncr53c7,8xx : warning : revision of %d is less than %d.\n", + (int) revision, min_revision); + + if (io_port && check_region (io_port, 128)) { + printk ("scsi-ncr53c7,8xx : IO region 0x%x to 0x%x is in use\n", + (unsigned) io_port, (unsigned) io_port + 127); + return -1; + } + + return normal_init (tpnt, board, chip, (int) base, io_port, + (int) irq, DMA_NONE, 1, bus, device_fn, options); +} + + +/* + * Function : int NCR53c7xx_detect(Scsi_Host_Template *tpnt) + * + * Purpose : detects and initializes NCR53c7,8x0 SCSI chips + * that were autoprobed, overridden on the LILO command line, + * or specified at compile time. + * + * Inputs : tpnt - template for this SCSI adapter + * + * Returns : number of host adapters detected + * + */ + +int +NCR53c7xx_detect(Scsi_Host_Template *tpnt) { + int i; + int current_override; + int count; /* Number of boards detected */ + unsigned char pci_bus, pci_device_fn; + static short pci_index=0; /* Device index to PCI BIOS calls */ + +#ifndef LINUX_1_2 + tpnt->proc_dir = &proc_scsi_ncr53c7xx; +#endif + + for (current_override = count = 0; current_override < OVERRIDE_LIMIT; + ++current_override) { + if (overrides[current_override].pci ? + !ncr_pci_init (tpnt, overrides[current_override].board, + overrides[current_override].chip, + (unsigned char) overrides[current_override].data.pci.bus, + (((overrides[current_override].data.pci.device + << 3) & 0xf8)|(overrides[current_override].data.pci.function & + 7)), overrides[current_override].options): + !normal_init (tpnt, overrides[current_override].board, + overrides[current_override].chip, + overrides[current_override].data.normal.base, + overrides[current_override].data.normal.io_port, + overrides[current_override].data.normal.irq, + overrides[current_override].data.normal.dma, + 0 /* PCI data invalid */, 0 /* PCI bus place holder */, + 0 /* PCI device_function place holder */, + overrides[current_override].options)) { + ++count; + } + } + + if (pcibios_present()) { + for (i = 0; i < NPCI_CHIP_IDS; ++i) + for (pci_index = 0; + !pcibios_find_device (PCI_VENDOR_ID_NCR, + pci_chip_ids[i].pci_device_id, pci_index, &pci_bus, + &pci_device_fn); + ++pci_index) + if (!ncr_pci_init (tpnt, BOARD_GENERIC, pci_chip_ids[i].chip, + pci_bus, pci_device_fn, /* no options */ 0)) + ++count; + } + return count; +} + +/* NCR53c810 and NCR53c820 script handling code */ + +#include "53c8xx_d.h" +#ifdef A_int_debug_sync +#define DEBUG_SYNC_INTR A_int_debug_sync +#endif +static int NCR53c8xx_script_len = sizeof (SCRIPT); +static int NCR53c8xx_dsa_len = A_dsa_end + Ent_dsa_zero - Ent_dsa_code_template; + +/* + * Function : static void NCR53c8x0_init_fixup (struct Scsi_Host *host) + * + * Purpose : copy and fixup the SCSI SCRIPTS(tm) code for this device. + * + * Inputs : host - pointer to this host adapter's structure + * + */ + +static void +NCR53c8x0_init_fixup (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned char tmp; + int i, ncr_to_memory, memory_to_ncr; + u32 base; + NCR53c7x0_local_setup(host); + + + /* XXX - NOTE : this code MUST be made endian aware */ + /* Copy code into buffer that was allocated at detection time. */ + memcpy ((void *) hostdata->script, (void *) SCRIPT, + sizeof(SCRIPT)); + /* Fixup labels */ + for (i = 0; i < PATCHES; ++i) + hostdata->script[LABELPATCHES[i]] += + virt_to_bus(hostdata->script); + /* Fixup addresses of constants that used to be EXTERNAL */ + + patch_abs_32 (hostdata->script, 0, NCR53c7xx_msg_abort, + virt_to_bus(&(hostdata->NCR53c7xx_msg_abort))); + patch_abs_32 (hostdata->script, 0, NCR53c7xx_msg_reject, + virt_to_bus(&(hostdata->NCR53c7xx_msg_reject))); + patch_abs_32 (hostdata->script, 0, NCR53c7xx_zero, + virt_to_bus(&(hostdata->NCR53c7xx_zero))); + patch_abs_32 (hostdata->script, 0, NCR53c7xx_sink, + virt_to_bus(&(hostdata->NCR53c7xx_sink))); + patch_abs_32 (hostdata->script, 0, NOP_insn, + virt_to_bus(&(hostdata->NOP_insn))); + patch_abs_32 (hostdata->script, 0, schedule, + virt_to_bus((void *) hostdata->schedule)); + + /* Fixup references to external variables: */ + for (i = 0; i < EXTERNAL_PATCHES_LEN; ++i) + hostdata->script[EXTERNAL_PATCHES[i].offset] += + virt_to_bus(EXTERNAL_PATCHES[i].address); + + /* + * Fixup absolutes set at boot-time. + * + * All non-code absolute variables suffixed with "dsa_" and "int_" + * are constants, and need no fixup provided the assembler has done + * it for us (I don't know what the "real" NCR assembler does in + * this case, my assembler does the right magic). + */ + + patch_abs_rwri_data (hostdata->script, 0, dsa_save_data_pointer, + Ent_dsa_code_save_data_pointer - Ent_dsa_zero); + patch_abs_rwri_data (hostdata->script, 0, dsa_restore_pointers, + Ent_dsa_code_restore_pointers - Ent_dsa_zero); + patch_abs_rwri_data (hostdata->script, 0, dsa_check_reselect, + Ent_dsa_code_check_reselect - Ent_dsa_zero); + + /* + * Just for the hell of it, preserve the settings of + * Burst Length and Enable Read Line bits from the DMODE + * register. Make sure SCRIPTS start automagically. + */ + + tmp = NCR53c7x0_read8(DMODE_REG_10); + tmp &= (DMODE_800_ERL | DMODE_BL_MASK); + + if (!(hostdata->options & OPTION_MEMORY_MAPPED)) { + base = (u32) host->io_port; + memory_to_ncr = tmp|DMODE_800_DIOM; + ncr_to_memory = tmp|DMODE_800_SIOM; + } else { + base = virt_to_bus(host->base); + memory_to_ncr = ncr_to_memory = tmp; + } + + patch_abs_32 (hostdata->script, 0, addr_scratch, base + SCRATCHA_REG_800); + patch_abs_32 (hostdata->script, 0, addr_temp, base + TEMP_REG); + + /* + * I needed some variables in the script to be accessible to + * both the NCR chip and the host processor. For these variables, + * I made the arbitrary decision to store them directly in the + * hostdata structure rather than in the RELATIVE area of the + * SCRIPTS. + */ + + + patch_abs_rwri_data (hostdata->script, 0, dmode_memory_to_memory, tmp); + patch_abs_rwri_data (hostdata->script, 0, dmode_memory_to_ncr, memory_to_ncr); + patch_abs_rwri_data (hostdata->script, 0, dmode_ncr_to_memory, ncr_to_memory); + + patch_abs_32 (hostdata->script, 0, msg_buf, + virt_to_bus((void *)&(hostdata->msg_buf))); + patch_abs_32 (hostdata->script, 0, reconnect_dsa_head, + virt_to_bus((void *)&(hostdata->reconnect_dsa_head))); + patch_abs_32 (hostdata->script, 0, addr_reconnect_dsa_head, + virt_to_bus((void *)&(hostdata->addr_reconnect_dsa_head))); + patch_abs_32 (hostdata->script, 0, reselected_identify, + virt_to_bus((void *)&(hostdata->reselected_identify))); +/* reselected_tag is currently unused */ +#if 0 + patch_abs_32 (hostdata->script, 0, reselected_tag, + virt_to_bus((void *)&(hostdata->reselected_tag))); +#endif + + patch_abs_32 (hostdata->script, 0, test_dest, + virt_to_bus((void*)&hostdata->test_dest)); + patch_abs_32 (hostdata->script, 0, test_src, + virt_to_bus(&hostdata->test_source)); + + patch_abs_rwri_data (hostdata->script, 0, dsa_check_reselect, + (unsigned char)(Ent_dsa_code_check_reselect - Ent_dsa_zero)); + +/* These are for event logging; the ncr_event enum contains the + actual interrupt numbers. */ +#ifdef A_int_EVENT_SELECT + patch_abs_32 (hostdata->script, 0, int_EVENT_SELECT, (u32) EVENT_SELECT); +#endif +#ifdef A_int_EVENT_DISCONNECT + patch_abs_32 (hostdata->script, 0, int_EVENT_DISCONNECT, (u32) EVENT_DISCONNECT); +#endif +#ifdef A_int_EVENT_RESELECT + patch_abs_32 (hostdata->script, 0, int_EVENT_RESELECT, (u32) EVENT_RESELECT); +#endif +#ifdef A_int_EVENT_COMPLETE + patch_abs_32 (hostdata->script, 0, int_EVENT_COMPLETE, (u32) EVENT_COMPLETE); +#endif +#ifdef A_int_EVENT_IDLE + patch_abs_32 (hostdata->script, 0, int_EVENT_IDLE, (u32) EVENT_IDLE); +#endif +#ifdef A_int_EVENT_SELECT_FAILED + patch_abs_32 (hostdata->script, 0, int_EVENT_SELECT_FAILED, + (u32) EVENT_SELECT_FAILED); +#endif +#ifdef A_int_EVENT_BEFORE_SELECT + patch_abs_32 (hostdata->script, 0, int_EVENT_BEFORE_SELECT, + (u32) EVENT_BEFORE_SELECT); +#endif +#ifdef A_int_EVENT_RESELECT_FAILED + patch_abs_32 (hostdata->script, 0, int_EVENT_RESELECT_FAILED, + (u32) EVENT_RESELECT_FAILED); +#endif + + /* + * Make sure the NCR and Linux code agree on the location of + * certain fields. + */ + + hostdata->E_accept_message = Ent_accept_message; + hostdata->E_command_complete = Ent_command_complete; + hostdata->E_cmdout_cmdout = Ent_cmdout_cmdout; + hostdata->E_data_transfer = Ent_data_transfer; + hostdata->E_debug_break = Ent_debug_break; + hostdata->E_dsa_code_template = Ent_dsa_code_template; + hostdata->E_dsa_code_template_end = Ent_dsa_code_template_end; + hostdata->E_end_data_transfer = Ent_end_data_transfer; + hostdata->E_initiator_abort = Ent_initiator_abort; + hostdata->E_msg_in = Ent_msg_in; + hostdata->E_other_transfer = Ent_other_transfer; + hostdata->E_other_in = Ent_other_in; + hostdata->E_other_out = Ent_other_out; + hostdata->E_reject_message = Ent_reject_message; + hostdata->E_respond_message = Ent_respond_message; + hostdata->E_select = Ent_select; + hostdata->E_select_msgout = Ent_select_msgout; + hostdata->E_target_abort = Ent_target_abort; +#ifdef Ent_test_0 + hostdata->E_test_0 = Ent_test_0; +#endif + hostdata->E_test_1 = Ent_test_1; + hostdata->E_test_2 = Ent_test_2; +#ifdef Ent_test_3 + hostdata->E_test_3 = Ent_test_3; +#endif + hostdata->E_wait_reselect = Ent_wait_reselect; + hostdata->E_dsa_code_begin = Ent_dsa_code_begin; + + hostdata->dsa_cmdout = A_dsa_cmdout; + hostdata->dsa_cmnd = A_dsa_cmnd; + hostdata->dsa_datain = A_dsa_datain; + hostdata->dsa_dataout = A_dsa_dataout; + hostdata->dsa_end = A_dsa_end; + hostdata->dsa_msgin = A_dsa_msgin; + hostdata->dsa_msgout = A_dsa_msgout; + hostdata->dsa_msgout_other = A_dsa_msgout_other; + hostdata->dsa_next = A_dsa_next; + hostdata->dsa_select = A_dsa_select; + hostdata->dsa_start = Ent_dsa_code_template - Ent_dsa_zero; + hostdata->dsa_status = A_dsa_status; + hostdata->dsa_jump_dest = Ent_dsa_code_fix_jump - Ent_dsa_zero + + 8 /* destination operand */; + + /* sanity check */ + if (A_dsa_fields_start != Ent_dsa_code_template_end - + Ent_dsa_zero) + printk("scsi%d : NCR dsa_fields start is %d not %d\n", + host->host_no, A_dsa_fields_start, Ent_dsa_code_template_end - + Ent_dsa_zero); + + printk("scsi%d : NCR code relocated to 0x%lx (virt 0x%p)\n", host->host_no, + virt_to_bus(hostdata->script), hostdata->script); +} + +/* + * Function : static int NCR53c8xx_run_tests (struct Scsi_Host *host) + * + * Purpose : run various verification tests on the NCR chip, + * including interrupt generation, and proper bus mastering + * operation. + * + * Inputs : host - a properly initialized Scsi_Host structure + * + * Preconditions : the NCR chip must be in a halted state. + * + * Returns : 0 if all tests were successful, -1 on error. + * + */ + +static int +NCR53c8xx_run_tests (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned long timeout; + u32 start; + int failed, i; + unsigned long flags; + NCR53c7x0_local_setup(host); + + /* The NCR chip _must_ be idle to run the test scripts */ + + save_flags(flags); + cli(); + if (!hostdata->idle) { + printk ("scsi%d : chip not idle, aborting tests\n", host->host_no); + restore_flags(flags); + return -1; + } + + /* + * Check for functional interrupts, this could work as an + * autoprobe routine. + */ + + if ((hostdata->options & OPTION_DEBUG_TEST1) && + hostdata->state != STATE_DISABLED) { + hostdata->idle = 0; + hostdata->test_running = 1; + hostdata->test_completed = -1; + hostdata->test_dest = 0; + hostdata->test_source = 0xdeadbeef; + start = virt_to_bus (hostdata->script) + hostdata->E_test_1; + hostdata->state = STATE_RUNNING; + printk ("scsi%d : test 1", host->host_no); + NCR53c7x0_write32 (DSP_REG, start); + printk (" started\n"); + sti(); + + /* + * This is currently a .5 second timeout, since (in theory) no slow + * board will take that long. In practice, we've seen one + * pentium which occasionally fails with this, but works with + * 10 times as much? + */ + + timeout = jiffies + 5 * HZ / 10; + while ((hostdata->test_completed == -1) && jiffies < timeout) + barrier(); + + failed = 1; + if (hostdata->test_completed == -1) + printk ("scsi%d : driver test 1 timed out%s\n",host->host_no , + (hostdata->test_dest == 0xdeadbeef) ? + " due to lost interrupt.\n" + " Please verify that the correct IRQ is being used for your board,\n" + " and that the motherboard IRQ jumpering matches the PCI setup on\n" + " PCI systems.\n" + " If you are using a NCR53c810 board in a PCI system, you should\n" + " also verify that the board is jumpered to use PCI INTA, since\n" + " most PCI motherboards lack support for INTB, INTC, and INTD.\n" + : ""); + else if (hostdata->test_completed != 1) + printk ("scsi%d : test 1 bad interrupt value (%d)\n", + host->host_no, hostdata->test_completed); + else + failed = (hostdata->test_dest != 0xdeadbeef); + + if (hostdata->test_dest != 0xdeadbeef) { + printk ("scsi%d : driver test 1 read 0x%x instead of 0xdeadbeef indicating a\n" + " probable cache invalidation problem. Please configure caching\n" + " as write-through or disabled\n", + host->host_no, hostdata->test_dest); + } + + if (failed) { + printk ("scsi%d : DSP = 0x%p (script at 0x%p, start at 0x%x)\n", + host->host_no, bus_to_virt(NCR53c7x0_read32(DSP_REG)), + hostdata->script, start); + printk ("scsi%d : DSPS = 0x%x\n", host->host_no, + NCR53c7x0_read32(DSPS_REG)); + restore_flags(flags); + return -1; + } + hostdata->test_running = 0; + } + + if ((hostdata->options & OPTION_DEBUG_TEST2) && + hostdata->state != STATE_DISABLED) { + u32 dsa[48]; + unsigned char identify = IDENTIFY(0, 0); + unsigned char cmd[6]; + unsigned char data[36]; + unsigned char status = 0xff; + unsigned char msg = 0xff; + + cmd[0] = INQUIRY; + cmd[1] = cmd[2] = cmd[3] = cmd[5] = 0; + cmd[4] = sizeof(data); + + dsa[2] = 1; + dsa[3] = virt_to_bus(&identify); + dsa[4] = 6; + dsa[5] = virt_to_bus(&cmd); + dsa[6] = sizeof(data); + dsa[7] = virt_to_bus(&data); + dsa[8] = 1; + dsa[9] = virt_to_bus(&status); + dsa[10] = 1; + dsa[11] = virt_to_bus(&msg); + + for (i = 0; i < 3; ++i) { + cli(); + if (!hostdata->idle) { + printk ("scsi%d : chip not idle, aborting tests\n", host->host_no); + restore_flags(flags); + return -1; + } + + /* SCNTL3 SDID */ + dsa[0] = (0x33 << 24) | (i << 16) ; + hostdata->idle = 0; + hostdata->test_running = 2; + hostdata->test_completed = -1; + start = virt_to_bus(hostdata->script) + hostdata->E_test_2; + hostdata->state = STATE_RUNNING; + NCR53c7x0_write32 (DSA_REG, virt_to_bus(dsa)); + NCR53c7x0_write32 (DSP_REG, start); + sti(); + + timeout = jiffies + 5 * HZ; /* arbitrary */ + while ((hostdata->test_completed == -1) && jiffies < timeout) + barrier(); + NCR53c7x0_write32 (DSA_REG, 0); + + if (hostdata->test_completed == 2) { + data[35] = 0; + printk ("scsi%d : test 2 INQUIRY to target %d, lun 0 : %s\n", + host->host_no, i, data + 8); + printk ("scsi%d : status ", host->host_no); + print_status (status); + printk ("\nscsi%d : message ", host->host_no); + print_msg (&msg); + printk ("\n"); + } else if (hostdata->test_completed == 3) { + printk("scsi%d : test 2 no connection with target %d\n", + host->host_no, i); + if (!hostdata->idle) { + printk("scsi%d : not idle\n", host->host_no); + restore_flags(flags); + return -1; + } + } else if (hostdata->test_completed == -1) { + printk ("scsi%d : test 2 timed out\n", host->host_no); + restore_flags(flags); + return -1; + } + hostdata->test_running = 0; + } + } + + restore_flags(flags); + return 0; +} + +/* + * Function : static void NCR53c8xx_dsa_fixup (struct NCR53c7x0_cmd *cmd) + * + * Purpose : copy the NCR53c8xx dsa structure into cmd's dsa buffer, + * performing all necessary relocation. + * + * Inputs : cmd, a NCR53c7x0_cmd structure with a dsa area large + * enough to hold the NCR53c8xx dsa. + */ + +static void +NCR53c8xx_dsa_fixup (struct NCR53c7x0_cmd *cmd) { + Scsi_Cmnd *c = cmd->cmd; + struct Scsi_Host *host = c->host; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int i; + + memcpy (cmd->dsa, hostdata->script + (hostdata->E_dsa_code_template / 4), + hostdata->E_dsa_code_template_end - hostdata->E_dsa_code_template); + + /* + * Note : within the NCR 'C' code, dsa points to the _start_ + * of the DSA structure, and _not_ the offset of dsa_zero within + * that structure used to facilitate shorter signed offsets + * for the 8 bit ALU. + * + * The implications of this are that + * + * - 32 bit A_dsa_* absolute values require an additional + * dsa_zero added to their value to be correct, since they are + * relative to dsa_zero which is in essentially a separate + * space from the code symbols. + * + * - All other symbols require no special treatment. + */ + + patch_abs_tci_data (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_lun, c->lun); + patch_abs_32 (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_addr_next, virt_to_bus(&cmd->dsa_next_addr)); + patch_abs_32 (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_next, virt_to_bus(cmd->dsa) + Ent_dsa_zero - + Ent_dsa_code_template + A_dsa_next); + patch_abs_32 (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_sync, virt_to_bus((void *)hostdata->sync[c->target].script)); + patch_abs_tci_data (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_target, c->target); + /* XXX - new pointer stuff */ + patch_abs_32 (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_addr_saved_pointer, virt_to_bus(&cmd->saved_data_pointer)); + patch_abs_32 (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_addr_saved_residual, virt_to_bus(&cmd->saved_residual)); + patch_abs_32 (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_addr_residual, virt_to_bus(&cmd->residual)); + + /* XXX - new start stuff */ + patch_abs_32 (cmd->dsa, Ent_dsa_code_template / sizeof(u32), + dsa_temp_addr_dsa_value, virt_to_bus(&cmd->dsa_addr)); + +} + +/* + * Function : run_process_issue_queue (void) + * + * Purpose : insure that the coroutine is running and will process our + * request. process_issue_queue_running is checked/set here (in an + * inline function) rather than in process_issue_queue itself to reduce + * the chances of stack overflow. + * + */ + +static volatile int process_issue_queue_running = 0; + +static __inline__ void +run_process_issue_queue(void) { + unsigned long flags; + save_flags (flags); + cli(); + if (!process_issue_queue_running) { + process_issue_queue_running = 1; + process_issue_queue(flags); + /* + * process_issue_queue_running is cleared in process_issue_queue + * once it can't do more work, and process_issue_queue exits with + * interrupts disabled. + */ + } + restore_flags (flags); +} + +/* + * Function : static void abnormal_finished (struct NCR53c7x0_cmd *cmd, int + * result) + * + * Purpose : mark SCSI command as finished, OR'ing the host portion + * of the result word into the result field of the corresponding + * Scsi_Cmnd structure, and removing it from the internal queues. + * + * Inputs : cmd - command, result - entire result field + * + * Preconditions : the NCR chip should be in a halted state when + * abnormal_finished is run, since it modifies structures which + * the NCR expects to have exclusive access to. + */ + +static void +abnormal_finished (struct NCR53c7x0_cmd *cmd, int result) { + Scsi_Cmnd *c = cmd->cmd; + struct Scsi_Host *host = c->host; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned long flags; + int left, found; + volatile struct NCR53c7x0_cmd * linux_search; + volatile struct NCR53c7x0_cmd * volatile *linux_prev; + volatile u32 *ncr_prev, *current, ncr_search; + +#if 0 + printk ("scsi%d: abnormal finished\n", host->host_no); +#endif + + save_flags(flags); + cli(); + found = 0; + /* + * Traverse the NCR issue array until we find a match or run out + * of instructions. Instructions in the NCR issue array are + * either JUMP or NOP instructions, which are 2 words in length. + */ + + + for (found = 0, left = host->can_queue, current = hostdata->schedule; + left > 0; --left, current += 2) + { + if (issue_to_cmd (host, hostdata, (u32 *) current) == cmd) + { + current[0] = hostdata->NOP_insn; + current[1] = 0xdeadbeef; + ++found; + break; + } + } + + /* + * Traverse the NCR reconnect list of DSA structures until we find + * a pointer to this dsa or have found too many command structures. + * We let prev point at the next field of the previous element or + * head of the list, so we don't do anything different for removing + * the head element. + */ + + for (left = host->can_queue, + ncr_search = hostdata->reconnect_dsa_head, + ncr_prev = &hostdata->reconnect_dsa_head; + left >= 0 && ncr_search && + ((char*)bus_to_virt(ncr_search) + hostdata->dsa_start) + != (char *) cmd->dsa; + ncr_prev = (u32*) ((char*)bus_to_virt(ncr_search) + + hostdata->dsa_next), ncr_search = *ncr_prev, --left); + + if (left < 0) + printk("scsi%d: loop detected in ncr reconnect list\n", + host->host_no); + else if (ncr_search) + if (found) + printk("scsi%d: scsi %ld in ncr issue array and reconnect lists\n", + host->host_no, c->pid); + else { + volatile u32 * next = (u32 *) + ((char *)bus_to_virt(ncr_search) + hostdata->dsa_next); + *ncr_prev = *next; +/* If we're at the tail end of the issue queue, update that pointer too. */ + found = 1; + } + + /* + * Traverse the host running list until we find this command or discover + * we have too many elements, pointing linux_prev at the next field of the + * linux_previous element or head of the list, search at this element. + */ + + for (left = host->can_queue, linux_search = hostdata->running_list, + linux_prev = &hostdata->running_list; + left >= 0 && linux_search && linux_search != cmd; + linux_prev = &(linux_search->next), + linux_search = linux_search->next, --left); + + if (left < 0) + printk ("scsi%d: loop detected in host running list for scsi pid %ld\n", + host->host_no, c->pid); + else if (linux_search) { + *linux_prev = linux_search->next; + --hostdata->busy[c->target][c->lun]; + } + + /* Return the NCR command structure to the free list */ + cmd->next = hostdata->free; + hostdata->free = cmd; + c->host_scribble = NULL; + + /* And return */ + c->result = result; + c->scsi_done(c); + + restore_flags(flags); + run_process_issue_queue(); +} + +/* + * Function : static void intr_break (struct Scsi_Host *host, + * struct NCR53c7x0_cmd *cmd) + * + * Purpose : Handler for breakpoint interrupts from a SCSI script + * + * Inputs : host - pointer to this host adapter's structure, + * cmd - pointer to the command (if any) dsa was pointing + * to. + * + */ + +static void +intr_break (struct Scsi_Host *host, struct + NCR53c7x0_cmd *cmd) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_break *bp; +#if 0 + Scsi_Cmnd *c = cmd ? cmd->cmd : NULL; +#endif + u32 *dsp; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned long flags; + NCR53c7x0_local_setup(host); + + /* + * Find the break point corresponding to this address, and + * dump the appropriate debugging information to standard + * output. + */ + save_flags(flags); + cli(); + dsp = (u32 *) bus_to_virt(NCR53c7x0_read32(DSP_REG)); + for (bp = hostdata->breakpoints; bp && bp->address != dsp; + bp = bp->next); + if (!bp) + panic("scsi%d : break point interrupt from %p with no breakpoint!", + host->host_no, dsp); + + /* + * Configure the NCR chip for manual start mode, so that we can + * point the DSP register at the instruction that follows the + * INT int_debug_break instruction. + */ + + NCR53c7x0_write8 (hostdata->dmode, + NCR53c7x0_read8(hostdata->dmode)|DMODE_MAN); + + /* + * And update the DSP register, using the size of the old + * instruction in bytes. + */ + + restore_flags(flags); +} +/* + * Function : static void print_synchronous (const char *prefix, + * const unsigned char *msg) + * + * Purpose : print a pretty, user and machine parsable representation + * of a SDTR message, including the "real" parameters, data + * clock so we can tell transfer rate at a glance. + * + * Inputs ; prefix - text to prepend, msg - SDTR message (5 bytes) + */ + +static void +print_synchronous (const char *prefix, const unsigned char *msg) { + if (msg[4]) { + int Hz = 1000000000 / (msg[3] * 4); + int integer = Hz / 1000000; + int fraction = (Hz - (integer * 1000000)) / 10000; + printk ("%speriod %dns offset %d %d.%02dMHz %s SCSI%s\n", + prefix, (int) msg[3] * 4, (int) msg[4], integer, fraction, + (((msg[3] * 4) < 200) ? "FAST" : "synchronous"), + (((msg[3] * 4) < 200) ? "-II" : "")); + } else + printk ("%sasynchronous SCSI\n", prefix); +} + +/* + * Function : static void set_synchronous (struct Scsi_Host *host, + * int target, int sxfer, int scntl3, int now_connected) + * + * Purpose : reprogram transfers between the selected SCSI initiator and + * target with the given register values; in the indirect + * select operand, reselection script, and chip registers. + * + * Inputs : host - NCR53c7,8xx SCSI host, target - number SCSI target id, + * sxfer and scntl3 - NCR registers. now_connected - if non-zero, + * we should reprogram the registers now too. + */ + +static void +set_synchronous (struct Scsi_Host *host, int target, int sxfer, int scntl3, + int now_connected) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + u32 *script; + NCR53c7x0_local_setup(host); + + /* These are eight bit registers */ + sxfer &= 0xff; + scntl3 &= 0xff; + + hostdata->sync[target].sxfer_sanity = sxfer; + hostdata->sync[target].scntl3_sanity = scntl3; + +/* + * HARD CODED : synchronous script is EIGHT words long. This + * must agree with 53c7.8xx.h + */ + + if ((hostdata->chip != 700) && (hostdata->chip != 70066)) { + hostdata->sync[target].select_indirect = (scntl3 << 24) | + (target << 16) | (sxfer << 8); + + script = (u32 *) hostdata->sync[target].script; + + /* XXX - add NCR53c7x0 code to reprogram SCF bits if we want to */ + if ((hostdata->chip / 100) == 8) { + script[0] = ((DCMD_TYPE_RWRI | DCMD_RWRI_OPC_MODIFY | + DCMD_RWRI_OP_MOVE) << 24) | + (SCNTL3_REG_800 << 16) | (scntl3 << 8); + script[1] = 0; + script += 2; + } + + script[0] = ((DCMD_TYPE_RWRI | DCMD_RWRI_OPC_MODIFY | + DCMD_RWRI_OP_MOVE) << 24) | + (SXFER_REG << 16) | (sxfer << 8); + script[1] = 0; + script += 2; + +#ifdef DEBUG_SYNC_INTR + if (hostdata->options & OPTION_DEBUG_DISCONNECT) { + script[0] = ((DCMD_TYPE_TCI|DCMD_TCI_OP_INT) << 24) | DBC_TCI_TRUE; + script[1] = DEBUG_SYNC_INTR; + script += 2; + } +#endif + + script[0] = ((DCMD_TYPE_TCI|DCMD_TCI_OP_RETURN) << 24) | DBC_TCI_TRUE; + script[1] = 0; + script += 2; + } + + if (hostdata->options & OPTION_DEBUG_SYNCHRONOUS) + printk ("scsi%d : target %d sync parameters are sxfer=0x%x, scntl3=0x%x\n", + host->host_no, target, sxfer, scntl3); + + if (now_connected) { + if ((hostdata->chip / 100) == 8) + NCR53c7x0_write8(SCNTL3_REG_800, scntl3); + NCR53c7x0_write8(SXFER_REG, sxfer); + } +} + + +/* + * Function : static int asynchronous (struct Scsi_Host *host, int target) + * + * Purpose : reprogram between the selected SCSI Host adapter and target + * (assumed to be currently connected) for asynchronous transfers. + * + * Inputs : host - SCSI host structure, target - numeric target ID. + * + * Preconditions : the NCR chip should be in one of the halted states + */ + +static void +asynchronous (struct Scsi_Host *host, int target) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + NCR53c7x0_local_setup(host); + set_synchronous (host, target, /* no offset */ 0, hostdata->saved_scntl3, + 1); + printk ("scsi%d : setting target %d to asynchronous SCSI\n", + host->host_no, target); +} + +/* + * XXX - do we want to go out of our way (ie, add extra code to selection + * in the NCR53c710/NCR53c720 script) to reprogram the synchronous + * conversion bits, or can we be content in just setting the + * sxfer bits? + */ + +/* Table for NCR53c8xx synchronous values */ +static const struct { + int div; /* Total clock divisor * 10 */ + unsigned char scf; /* */ + unsigned char tp; /* 4 + tp = xferp divisor */ +} syncs[] = { +/* div scf tp div scf tp div scf tp */ + { 40, 1, 0}, { 50, 1, 1}, { 60, 1, 2}, + { 70, 1, 3}, { 75, 2, 1}, { 80, 1, 4}, + { 90, 1, 5}, { 100, 1, 6}, { 105, 2, 3}, + { 110, 1, 7}, { 120, 2, 4}, { 135, 2, 5}, + { 140, 3, 3}, { 150, 2, 6}, { 160, 3, 4}, + { 165, 2, 7}, { 180, 3, 5}, { 200, 3, 6}, + { 210, 4, 3}, { 220, 3, 7}, { 240, 4, 4}, + { 270, 4, 5}, { 300, 4, 6}, { 330, 4, 7} +}; + +/* + * Function : static void synchronous (struct Scsi_Host *host, int target, + * char *msg) + * + * Purpose : reprogram transfers between the selected SCSI initiator and + * target for synchronous SCSI transfers such that the synchronous + * offset is less than that requested and period at least as long + * as that requested. Also modify *msg such that it contains + * an appropriate response. + * + * Inputs : host - NCR53c7,8xx SCSI host, target - number SCSI target id, + * msg - synchronous transfer request. + */ + + +static void +synchronous (struct Scsi_Host *host, int target, char *msg) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int desire, divisor, i, limit; + unsigned char scntl3, sxfer; +/* The diagnostic message fits on one line, even with max. width integers */ + char buf[80]; + +/* Desired transfer clock in Hz */ + desire = 1000000000L / (msg[3] * 4); +/* Scale the available SCSI clock by 10 so we get tenths */ + divisor = (hostdata->scsi_clock * 10) / desire; + +/* NCR chips can handle at most an offset of 8 */ + if (msg[4] > 8) + msg[4] = 8; + + if (hostdata->options & OPTION_DEBUG_SDTR) + printk("scsi%d : optimal synchronous divisor of %d.%01d\n", + host->host_no, divisor / 10, divisor % 10); + + limit = (sizeof(syncs) / sizeof(syncs[0]) -1); + for (i = 0; (i < limit) && (divisor > syncs[i].div); ++i); + + if (hostdata->options & OPTION_DEBUG_SDTR) + printk("scsi%d : selected synchronous divisor of %d.%01d\n", + host->host_no, syncs[i].div / 10, syncs[i].div % 10); + + msg[3] = ((1000000000L / hostdata->scsi_clock) * syncs[i].div / 10 / 4); + + if (hostdata->options & OPTION_DEBUG_SDTR) + printk("scsi%d : selected synchronous period of %dns\n", host->host_no, + msg[3] * 4); + + scntl3 = (hostdata->chip / 100 == 8) ? ((hostdata->saved_scntl3 & + ~SCNTL3_800_SCF_MASK) | (syncs[i].scf << SCNTL3_800_SCF_SHIFT)) : 0; + sxfer = (msg[4] << SXFER_MO_SHIFT) | ((syncs[i].tp) << SXFER_TP_SHIFT); + if (hostdata->options & OPTION_DEBUG_SDTR) + printk ("scsi%d : sxfer=0x%x scntl3=0x%x\n", + host->host_no, (int) sxfer, (int) scntl3); + set_synchronous (host, target, sxfer, scntl3, 1); + sprintf (buf, "scsi%d : setting target %d to ", host->host_no, target); + print_synchronous (buf, msg); +} + +/* + * Function : static int NCR53c8x0_dstat_sir_intr (struct Scsi_Host *host, + * struct NCR53c7x0_cmd *cmd) + * + * Purpose : Handler for INT generated instructions for the + * NCR53c810/820 SCSI SCRIPT + * + * Inputs : host - pointer to this host adapter's structure, + * cmd - pointer to the command (if any) dsa was pointing + * to. + * + */ + +static int +NCR53c8x0_dstat_sir_intr (struct Scsi_Host *host, struct + NCR53c7x0_cmd *cmd) { + NCR53c7x0_local_declare(); + int print; + Scsi_Cmnd *c = cmd ? cmd->cmd : NULL; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + u32 dsps,*dsp; /* Argument of the INT instruction */ + NCR53c7x0_local_setup(host); + dsps = NCR53c7x0_read32(DSPS_REG); + dsp = (u32 *) bus_to_virt(NCR53c7x0_read32(DSP_REG)); + + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : DSPS = 0x%x\n", host->host_no, dsps); + + switch (dsps) { + case A_int_msg_1: + print = 1; + switch (hostdata->msg_buf[0]) { + /* + * Unless we've initiated synchronous negotiation, I don't + * think that this should happen. + */ + case MESSAGE_REJECT: + hostdata->dsp = hostdata->script + hostdata->E_accept_message / + sizeof(u32); + hostdata->dsp_changed = 1; + if (cmd && (cmd->flags & CMD_FLAG_SDTR)) { + printk ("scsi%d : target %d rejected SDTR\n", host->host_no, + c->target); + cmd->flags &= ~CMD_FLAG_SDTR; + asynchronous (host, c->target); + print = 0; + } + break; + case INITIATE_RECOVERY: + printk ("scsi%d : extended contingent allegiance not supported yet, rejecting\n", + host->host_no); + /* Fall through to default */ + hostdata->dsp = hostdata->script + hostdata->E_reject_message / + sizeof(u32); + hostdata->dsp_changed = 1; + break; + default: + printk ("scsi%d : unsupported message, rejecting\n", + host->host_no); + hostdata->dsp = hostdata->script + hostdata->E_reject_message / + sizeof(u32); + hostdata->dsp_changed = 1; + } + if (print) { + printk ("scsi%d : received message", host->host_no); + if (c) + printk (" from target %d lun %d ", c->target, c->lun); + print_msg ((unsigned char *) hostdata->msg_buf); + printk("\n"); + } + + return SPECIFIC_INT_NOTHING; + + + case A_int_msg_sdtr: +/* + * At this point, hostdata->msg_buf contains + * 0 EXTENDED MESSAGE + * 1 length + * 2 SDTR + * 3 period * 4ns + * 4 offset + */ + + if (cmd) { + char buf[80]; + sprintf (buf, "scsi%d : target %d %s ", host->host_no, c->target, + (cmd->flags & CMD_FLAG_SDTR) ? "accepting" : "requesting"); + print_synchronous (buf, (unsigned char *) hostdata->msg_buf); + + /* + * Initiator initiated, won't happen unless synchronous + * transfers are enabled. If we get a SDTR message in + * response to our SDTR, we should program our parameters + * such that + * offset <= requested offset + * period >= requested period + */ + if (cmd->flags & CMD_FLAG_SDTR) { + cmd->flags &= ~CMD_FLAG_SDTR; + if (hostdata->msg_buf[4]) + synchronous (host, c->target, (unsigned char *) + hostdata->msg_buf); + else + asynchronous (host, c->target); + hostdata->dsp = hostdata->script + hostdata->E_accept_message / + sizeof(u32); + hostdata->dsp_changed = 1; + return SPECIFIC_INT_NOTHING; + } else { + if (hostdata->options & OPTION_SYNCHRONOUS) { + cmd->flags |= CMD_FLAG_DID_SDTR; + synchronous (host, c->target, (unsigned char *) + hostdata->msg_buf); + } else { + hostdata->msg_buf[4] = 0; /* 0 offset = async */ + asynchronous (host, c->target); + } + patch_dsa_32 (cmd->dsa, dsa_msgout_other, 0, 5); + patch_dsa_32 (cmd->dsa, dsa_msgout_other, 1, (u32) + virt_to_bus ((void *)&hostdata->msg_buf)); + hostdata->dsp = hostdata->script + + hostdata->E_respond_message / sizeof(u32); + hostdata->dsp_changed = 1; + } + return SPECIFIC_INT_NOTHING; + } + /* Fall through to abort if we couldn't find a cmd, and + therefore a dsa structure to twiddle */ + case A_int_msg_wdtr: + hostdata->dsp = hostdata->script + hostdata->E_reject_message / + sizeof(u32); + hostdata->dsp_changed = 1; + return SPECIFIC_INT_NOTHING; + case A_int_err_unexpected_phase: + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : unexpected phase\n", host->host_no); + return SPECIFIC_INT_ABORT; + case A_int_err_selected: + printk ("scsi%d : selected by target %d\n", host->host_no, + (int) NCR53c7x0_read8(SDID_REG_800) &7); + hostdata->dsp = hostdata->script + hostdata->E_target_abort / + sizeof(u32); + hostdata->dsp_changed = 1; + return SPECIFIC_INT_NOTHING; + case A_int_err_unexpected_reselect: + printk ("scsi%d : unexpected reselect by target %d lun %d\n", + host->host_no, (int) NCR53c7x0_read8(SDID_REG_800) & 7, + hostdata->reselected_identify & 7); + hostdata->dsp = hostdata->script + hostdata->E_initiator_abort / + sizeof(u32); + hostdata->dsp_changed = 1; + return SPECIFIC_INT_NOTHING; +/* + * Since contingent allegiance conditions are cleared by the next + * command issued to a target, we must issue a REQUEST SENSE + * command after receiving a CHECK CONDITION status, before + * another command is issued. + * + * Since this NCR53c7x0_cmd will be freed after use, we don't + * care if we step on the various fields, so modify a few things. + */ + case A_int_err_check_condition: +#if 0 + if (hostdata->options & OPTION_DEBUG_INTR) +#endif + printk ("scsi%d : CHECK CONDITION\n", host->host_no); + if (!c) { + printk("scsi%d : CHECK CONDITION with no SCSI command\n", + host->host_no); + return SPECIFIC_INT_PANIC; + } + + /* + * FIXME : this uses the normal one-byte selection message. + * We may want to renegotiate for synchronous & WIDE transfers + * since these could be the crux of our problem. + * + hostdata->NOP_insn* FIXME : once SCSI-II tagged queuing is implemented, we'll + * have to set this up so that the rest of the DSA + * agrees with this being an untagged queue'd command. + */ + + patch_dsa_32 (cmd->dsa, dsa_msgout, 0, 1); + + /* + * Modify the table indirect for COMMAND OUT phase, since + * Request Sense is a six byte command. + */ + + patch_dsa_32 (cmd->dsa, dsa_cmdout, 0, 6); + + c->cmnd[0] = REQUEST_SENSE; + c->cmnd[1] &= 0xe0; /* Zero all but LUN */ + c->cmnd[2] = 0; + c->cmnd[3] = 0; + c->cmnd[4] = sizeof(c->sense_buffer); + c->cmnd[5] = 0; + + /* + * Disable dataout phase, and program datain to transfer to the + * sense buffer, and add a jump to other_transfer after the + * command so overflow/underrun conditions are detected. + */ + + patch_dsa_32 (cmd->dsa, dsa_dataout, 0, + virt_to_bus(hostdata->script) + hostdata->E_other_transfer); + patch_dsa_32 (cmd->dsa, dsa_datain, 0, + virt_to_bus(cmd->data_transfer_start)); + cmd->data_transfer_start[0] = (((DCMD_TYPE_BMI | DCMD_BMI_OP_MOVE_I | + DCMD_BMI_IO)) << 24) | sizeof(c->sense_buffer); + cmd->data_transfer_start[1] = (u32) virt_to_bus(c->sense_buffer); + + cmd->data_transfer_start[2] = ((DCMD_TYPE_TCI | DCMD_TCI_OP_JUMP) + << 24) | DBC_TCI_TRUE; + cmd->data_transfer_start[3] = (u32) virt_to_bus(hostdata->script) + + hostdata->E_other_transfer; + + /* + * Currently, this command is flagged as completed, ie + * it has valid status and message data. Reflag it as + * incomplete. Q - need to do something so that original + * status, etc are used. + */ + + cmd->cmd->result = 0xffff; + + /* + * Restart command as a REQUEST SENSE. + */ + hostdata->dsp = (u32 *) hostdata->script + hostdata->E_select / + sizeof(u32); + hostdata->dsp_changed = 1; + return SPECIFIC_INT_NOTHING; + case A_int_debug_break: + return SPECIFIC_INT_BREAK; + case A_int_norm_aborted: + hostdata->dsp = (u32 *) hostdata->schedule; + hostdata->dsp_changed = 1; + if (cmd) + abnormal_finished (cmd, DID_ERROR << 16); + return SPECIFIC_INT_NOTHING; + case A_int_test_1: + case A_int_test_2: + hostdata->idle = 1; + hostdata->test_completed = (dsps - A_int_test_1) / 0x00010000 + 1; + if (hostdata->options & OPTION_DEBUG_INTR) + printk("scsi%d : test%d complete\n", host->host_no, + hostdata->test_completed); + return SPECIFIC_INT_NOTHING; +#ifdef A_int_debug_reselected_ok + case A_int_debug_reselected_ok: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR| + OPTION_DEBUG_DISCONNECT)) { + /* + * Note - this dsa is not based on location relative to + * the command structure, but to location relative to the + * DSA register + */ + u32 *dsa; + dsa = (u32 *) bus_to_virt (NCR53c7x0_read32(DSA_REG)); + + printk("scsi%d : reselected_ok (DSA = 0x%x (virt 0x%p)\n", + host->host_no, NCR53c7x0_read32(DSA_REG), dsa); + printk("scsi%d : resume address is 0x%x (virt 0x%p)\n", + host->host_no, cmd->saved_data_pointer, + bus_to_virt(cmd->saved_data_pointer)); + print_insn (host, hostdata->script + Ent_reselected_ok / + sizeof(u32), "", 1); + printk ("scsi%d : sxfer=0x%x, scntl3=0x%x\n", + host->host_no, NCR53c7x0_read8(SXFER_REG), + NCR53c7x0_read8(SCNTL3_REG_800)); + if (c) { + print_insn (host, (u32 *) + hostdata->sync[c->target].script, "", 1); + print_insn (host, (u32 *) + hostdata->sync[c->target].script + 2, "", 1); + } + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_reselect_check + case A_int_debug_reselect_check: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR)) { + u32 *dsa; +#if 0 + u32 *code; +#endif + /* + * Note - this dsa is not based on location relative to + * the command structure, but to location relative to the + * DSA register + */ + dsa = bus_to_virt (NCR53c7x0_read32(DSA_REG)); + printk("scsi%d : reselected_check_next (DSA = 0x%lx (virt 0x%p))\n", + host->host_no, virt_to_bus(dsa), dsa); + if (dsa) { + printk("scsi%d : resume address is 0x%x (virt 0x%p)\n", + host->host_no, cmd->saved_data_pointer, + bus_to_virt (cmd->saved_data_pointer)); +#if 0 + printk("scsi%d : template code :\n", host->host_no); + for (code = dsa + (Ent_dsa_code_check_reselect - Ent_dsa_zero) + / sizeof(u32); code < (dsa + Ent_dsa_zero / sizeof(u32)); + code += print_insn (host, code, "", 1)); +#endif + } + print_insn (host, hostdata->script + Ent_reselected_ok / + sizeof(u32), "", 1); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_dsa_schedule + case A_int_debug_dsa_schedule: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR)) { + u32 *dsa; + /* + * Note - this dsa is not based on location relative to + * the command structure, but to location relative to the + * DSA register + */ + dsa = (u32 *) bus_to_virt (NCR53c7x0_read32(DSA_REG)); + printk("scsi%d : dsa_schedule (old DSA = 0x%lx (virt 0x%p))\n", + host->host_no, virt_to_bus(dsa), dsa); + if (dsa) + printk("scsi%d : resume address is 0x%x (virt 0x%p)\n" + " (temp was 0x%x (virt 0x%p))\n", + host->host_no, cmd->saved_data_pointer, + bus_to_virt (cmd->saved_data_pointer), + NCR53c7x0_read32 (TEMP_REG), + bus_to_virt (NCR53c7x0_read32(TEMP_REG))); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_scheduled + case A_int_debug_scheduled: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR)) { + printk("scsi%d : new I/O 0x%x (virt 0x%p) scheduled\n", + host->host_no, NCR53c7x0_read32(DSA_REG), + bus_to_virt(NCR53c7x0_read32(DSA_REG))); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_idle + case A_int_debug_idle: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR)) { + printk("scsi%d : idle\n", host->host_no); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_cmd + case A_int_debug_cmd: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR)) { + printk("scsi%d : command sent\n"); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_dsa_loaded + case A_int_debug_dsa_loaded: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR)) { + printk("scsi%d : DSA loaded with 0x%x (virt 0x%p)\n", host->host_no, + NCR53c7x0_read32(DSA_REG), + bus_to_virt(NCR53c7x0_read32(DSA_REG))); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_reselected + case A_int_debug_reselected: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR| + OPTION_DEBUG_DISCONNECT)) { + printk("scsi%d : reselected by target %d lun %d\n", + host->host_no, (int) NCR53c7x0_read8(SDID_REG_800) & ~0x80, + (int) hostdata->reselected_identify & 7); + print_queues(host); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_disconnect_msg + case A_int_debug_disconnect_msg: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR)) { + if (c) + printk("scsi%d : target %d lun %d disconnecting\n", + host->host_no, c->target, c->lun); + else + printk("scsi%d : unknown target disconnecting\n", + host->host_no); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_disconnected + case A_int_debug_disconnected: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR| + OPTION_DEBUG_DISCONNECT)) { + printk ("scsi%d : disconnected, new queues are\n", + host->host_no); + print_queues(host); +#if 0 + printk ("scsi%d : sxfer=0x%x, scntl3=0x%x\n", + host->host_no, NCR53c7x0_read8(SXFER_REG), + NCR53c7x0_read8(SCNTL3_REG_800)); +#endif + if (c) { + print_insn (host, (u32 *) + hostdata->sync[c->target].script, "", 1); + print_insn (host, (u32 *) + hostdata->sync[c->target].script + 2, "", 1); + } + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_panic + case A_int_debug_panic: + printk("scsi%d : int_debug_panic received\n", host->host_no); + print_lots (host); + return SPECIFIC_INT_PANIC; +#endif +#ifdef A_int_debug_saved + case A_int_debug_saved: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR| + OPTION_DEBUG_DISCONNECT)) { + printk ("scsi%d : saved data pointer 0x%x (virt 0x%p)\n", + host->host_no, cmd->saved_data_pointer, + bus_to_virt (cmd->saved_data_pointer)); + print_progress (c); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_restored + case A_int_debug_restored: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR| + OPTION_DEBUG_DISCONNECT)) { + if (cmd) { + int size; + printk ("scsi%d : restored data pointer 0x%x (virt 0x%p)\n", + host->host_no, cmd->saved_data_pointer, bus_to_virt ( + cmd->saved_data_pointer)); + size = print_insn (host, (u32 *) + bus_to_virt(cmd->saved_data_pointer), "", 1); + size = print_insn (host, (u32 *) + bus_to_virt(cmd->saved_data_pointer) + size, "", 1); + print_progress (c); + } +#if 0 + printk ("scsi%d : datapath residual %d\n", + host->host_no, datapath_residual (host)) ; +#endif + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_sync + case A_int_debug_sync: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR| + OPTION_DEBUG_DISCONNECT|OPTION_DEBUG_SDTR)) { + unsigned char sxfer = NCR53c7x0_read8 (SXFER_REG), + scntl3 = NCR53c7x0_read8 (SCNTL3_REG_800); + if (c) { + if (sxfer != hostdata->sync[c->target].sxfer_sanity || + scntl3 != hostdata->sync[c->target].scntl3_sanity) { + printk ("scsi%d : sync sanity check failed sxfer=0x%x, scntl3=0x%x", + host->host_no, sxfer, scntl3); + NCR53c7x0_write8 (SXFER_REG, sxfer); + NCR53c7x0_write8 (SCNTL3_REG_800, scntl3); + } + } else + printk ("scsi%d : unknown command sxfer=0x%x, scntl3=0x%x\n", + host->host_no, (int) sxfer, (int) scntl3); + } + return SPECIFIC_INT_RESTART; +#endif +#ifdef A_int_debug_datain + case A_int_debug_datain: + if (hostdata->options & (OPTION_DEBUG_SCRIPT|OPTION_DEBUG_INTR| + OPTION_DEBUG_DISCONNECT|OPTION_DEBUG_SDTR)) { + int size; + printk ("scsi%d : In do_datain (%s) sxfer=0x%x, scntl3=0x%x\n" + " datapath residual=%d\n", + host->host_no, sbcl_to_phase (NCR53c7x0_read8 (SBCL_REG)), + (int) NCR53c7x0_read8(SXFER_REG), + (int) NCR53c7x0_read8(SCNTL3_REG_800), + datapath_residual (host)) ; + print_insn (host, dsp, "", 1); + size = print_insn (host, (u32 *) bus_to_virt(dsp[1]), "", 1); + print_insn (host, (u32 *) bus_to_virt(dsp[1]) + size, "", 1); + } + return SPECIFIC_INT_RESTART; +#endif +/* + * FIXME : for 7xx support, we need to read SDID_REG_700 and handle + * the comparison as bitfielded, not binary. + */ +#ifdef A_int_debug_check_dsa + case A_int_debug_check_dsa: + if (NCR53c7x0_read8 (SCNTL1_REG) & SCNTL1_CON) { + int sdid = NCR53c7x0_read8 (SDID_REG_800) & 15; + char *where = dsp - NCR53c7x0_insn_size(NCR53c7x0_read8 + (DCMD_REG)) == hostdata->script + + Ent_select_check_dsa / sizeof(u32) ? + "selection" : "reselection"; + if (c && sdid != c->target) { + printk ("scsi%d : SDID target %d != DSA target %d at %s\n", + host->host_no, sdid, c->target, where); + print_lots(host); + dump_events (host, 20); + return SPECIFIC_INT_PANIC; + } + } + return SPECIFIC_INT_RESTART; +#endif + default: + if ((dsps & 0xff000000) == 0x03000000) { + printk ("scsi%d : misc debug interrupt 0x%x\n", + host->host_no, dsps); + return SPECIFIC_INT_RESTART; + } else if ((dsps & 0xff000000) == 0x05000000) { + if (hostdata->events) { + struct NCR53c7x0_event *event; + ++hostdata->event_index; + if (hostdata->event_index >= hostdata->event_size) + hostdata->event_index = 0; + event = (struct NCR53c7x0_event *) hostdata->events + + hostdata->event_index; + event->event = (enum ncr_event) dsps; + event->dsa = bus_to_virt(NCR53c7x0_read32(DSA_REG)); + /* FIXME : this needs to change for the '7xx family */ + if (NCR53c7x0_read8 (SCNTL1_REG) & SCNTL1_CON) + event->target = NCR53c7x0_read8(SSID_REG_800); + else + event->target = 255; + + if (event->event == EVENT_RESELECT) + event->lun = hostdata->reselected_identify & 0xf; + else if (c) + event->lun = c->lun; + else + event->lun = 255; + do_gettimeofday(&(event->time)); + if (c) { + event->pid = c->pid; + memcpy ((void *) event->cmnd, (void *) c->cmnd, + sizeof (event->cmnd)); + } else { + event->pid = -1; + } + } + return SPECIFIC_INT_RESTART; + } + + printk ("scsi%d : unknown user interrupt 0x%x\n", + host->host_no, (unsigned) dsps); + return SPECIFIC_INT_PANIC; + } +} + +/* + * XXX - the stock NCR assembler won't output the scriptu.h file, + * which undefine's all #define'd CPP symbols from the script.h + * file, which will create problems if you use multiple scripts + * with the same symbol names. + * + * If you insist on using NCR's assembler, you could generate + * scriptu.h from script.h using something like + * + * grep #define script.h | \ + * sed 's/#define[ ][ ]*\([_a-zA-Z][_a-zA-Z0-9]*\).*$/#undefine \1/' \ + * > scriptu.h + */ + +#include "53c8xx_u.h" + +/* XXX - add alternate script handling code here */ + + +#ifdef NCR_DEBUG +/* + * Debugging without a debugger is no fun. So, I've provided + * a debugging interface in the NCR53c7x0 driver. To avoid + * kernel cruft, there's just enough here to act as an interface + * to a user level debugger (aka, GDB). + * + * + * The following restrictions apply to debugger commands : + * 1. The command must be terminated by a newline. + * 2. Command length must be less than 80 bytes including the + * newline. + * 3. The entire command must be written with one system call. + */ + +static const char debugger_help = +"bc <addr> - clear breakpoint\n" +"bl - list breakpoints\n" +"bs <addr> - set breakpoint\n" +"g - start\n" +"h - halt\n" +"? - this message\n" +"i - info\n" +"mp <addr> <size> - print memory\n" +"ms <addr> <size> <value> - store memory\n" +"rp <num> <size> - print register\n" +"rs <num> <size> <value> - store register\n" +"s - single step\n" +"tb - begin trace \n" +"te - end trace\n"; + +/* + * Whenever we change a break point, we should probably + * set the NCR up so that it is in a single step mode. + */ + +static int debugger_fn_bc (struct Scsi_Host *host, struct debugger_token *token, + u32 args[]) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + instance->hostdata; + struct NCR53c7x0_break *bp, **prev; + unsigned long flags; + save_flags(flags); + cli(); + for (bp = (struct NCR53c7x0_break *) instance->breakpoints, + prev = (struct NCR53c7x0_break **) &instance->breakpoints; + bp; prev = (struct NCR53c7x0_break **) &(bp->next), + bp = (struct NCR53c7x0_break *) bp->next); + + if (!bp) { + restore_flags(flags); + return -EIO; + } + + /* + * XXX - we need to insure that the processor is halted + * here in order to prevent a race condition. + */ + + memcpy ((void *) bp->addr, (void *) bp->old, sizeof(bp->old)); + if (prev) + *prev = bp->next; + + restore_flags(flags); + return 0; +} + + +static int +debugger_fn_bl (struct Scsi_Host *host, struct debugger_token *token, + u32 args[]) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + struct NCR53c7x0_break *bp; + char buf[80]; + size_t len; + unsigned long flags; + /* + * XXX - we need to insure that the processor is halted + * here in order to prevent a race condition. So, if the + * processor isn't halted, print an error message and continue. + */ + + sprintf (buf, "scsi%d : bp : warning : processor not halted\b", + host->host_no); + debugger_kernel_write (host, buf, strlen(buf)); + + save_flags(flags); + cli(); + for (bp = (struct NCR53c7x0_break *) host->breakpoints; + bp; bp = (struct NCR53c7x0_break *) bp->next); { + sprintf (buf, "scsi%d : bp : success : at %08x, replaces %08x %08x", + bp->addr, bp->old[0], bp->old[1]); + len = strlen(buf); + if ((bp->old[0] & (DCMD_TYPE_MASK << 24)) == + (DCMD_TYPE_MMI << 24)) { + sprintf(buf + len, "%08x\n", * (u32 *) bp->addr); + } else { + sprintf(buf + len, "\n"); + } + len = strlen(buf); + debugger_kernel_write (host, buf, len); + } + restore_flags(flags); + return 0; +} + +static int +debugger_fn_bs (struct Scsi_Host *host, struct debugger_token *token, + u32 args[]) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + struct NCR53c7x0_break *bp; + char buf[80]; + size_t len; + unsigned long flags; + save_flags(flags); + cli(); + + if (hostdata->state != STATE_HALTED) { + sprintf (buf, "scsi%d : bs : failure : NCR not halted\n", host->host_no); + debugger_kernel_write (host, buf, strlen(buf)); + restore_flags(flags); + return -1; + } + + if (!(bp = kmalloc (sizeof (struct NCR53c7x0_break)))) { + printk ("scsi%d : kmalloc(%d) of breakpoint structure failed, try again\n", + host->host_no, sizeof(struct NCR53c7x0_break)); + restore_flags(flags); + return -1; + } + + bp->address = (u32 *) args[0]; + memcpy ((void *) bp->old_instruction, (void *) bp->address, 8); + bp->old_size = (((bp->old_instruction[0] >> 24) & DCMD_TYPE_MASK) == + DCMD_TYPE_MMI ? 3 : 2; + bp->next = hostdata->breakpoints; + hostdata->breakpoints = bp->next; + memcpy ((void *) bp->address, (void *) hostdata->E_debug_break, 8); + + restore_flags(flags); + return 0; +} + +#define TOKEN(name,nargs) {#name, nargs, debugger_fn_##name} +static const struct debugger_token { + char *name; + int numargs; + int (*fn)(struct debugger_token *token, u32 args[]); +} debugger_tokens[] = { + TOKEN(bc,1), TOKEN(bl,0), TOKEN(bs,1), TOKEN(g,0), TOKEN(halt,0), + {DT_help, "?", 0} , TOKEN(h,0), TOKEN(i,0), TOKEN(mp,2), + TOKEN(ms,3), TOKEN(rp,2), TOKEN(rs,2), TOKEN(s,0), TOKEN(tb,0), TOKEN(te,0) +}; + +#define NDT sizeof(debugger_tokens / sizeof(struct debugger_token)) + +static struct Scsi_Host * inode_to_host (struct inode *inode) { + int dev; + struct Scsi_Host *tmp; + for (dev = MINOR(inode->rdev), host = first_host; + (host->hostt == the_template); --dev, host = host->next) + if (!dev) return host; + return NULL; +} + + +static int +debugger_user_write (struct inode *inode,struct file *filp, + char *buf,int count) { + struct Scsi_Host *host; /* This SCSI host */ + struct NCR53c7x0_hostadata *hostdata; + char input_buf[80], /* Kernel space copy of buf */ + *ptr; /* Pointer to argument list */ + u32 args[3]; /* Arguments */ + int i, j, error, len; + + if (!(host = inode_to_host(inode))) + return -ENXIO; + + hostdata = (struct NCR53c7x0_hostdata *) host->hostdata; + + if (error = verify_area(VERIFY_READ,buf,count)) + return error; + + if (count > 80) + return -EIO; + + memcpy_from_fs(input_buf, buf, count); + + if (input_buf[count - 1] != '\n') + return -EIO; + + input_buf[count - 1]=0; + + for (i = 0; i < NDT; ++i) { + len = strlen (debugger_tokens[i].name); + if (!strncmp(input_buf, debugger_tokens[i].name, len)) + break; + }; + + if (i == NDT) + return -EIO; + + for (ptr = input_buf + len, j = 0; j < debugger_tokens[i].nargs && *ptr;) { + if (*ptr == ' ' || *ptr == '\t') { + ++ptr; + } else if (isdigit(*ptr)) { + args[j++] = simple_strtoul (ptr, &ptr, 0); + } else { + return -EIO; + } + } + + if (j != debugger_tokens[i].nargs) + return -EIO; + + return count; +} + +static int +debugger_user_read (struct inode *inode,struct file *filp, + char *buf,int count) { + struct Scsi_Host *instance; + +} + +static int +debugger_kernel_write (struct Scsi_Host *host, char *buf, size_t + buflen) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int copy, left; + unsigned long flags; + save_flags(flags); + cli(); + while (buflen) { + left = (hostdata->debug_buf + hostdata->debug_size - 1) - + hostdata->debug_write; + copy = (buflen <= left) ? buflen : left; + memcpy (hostdata->debug_write, buf, copy); + buf += copy; + buflen -= copy; + hostdata->debug_count += copy; + if ((hostdata->debug_write += copy) == + (hostdata->debug_buf + hostdata->debug_size)) + hosdata->debug_write = hostdata->debug_buf; + } + restore_flags(flags); +} + +#endif /* def NCRDEBUG */ + +/* + * Function : static void NCR538xx_soft_reset (struct Scsi_Host *host) + * + * Purpose : perform a soft reset of the NCR53c8xx chip + * + * Inputs : host - pointer to this host adapter's structure + * + * Preconditions : NCR53c7x0_init must have been called for this + * host. + * + */ + +static void +NCR53c8x0_soft_reset (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + NCR53c7x0_local_setup(host); + + + /* + * Do a soft reset of the chip so that everything is + * reinitialized to the power-on state. + * + * Basically follow the procedure outlined in the NCR53c700 + * data manual under Chapter Six, How to Use, Steps Necessary to + * Start SCRIPTS, with the exception of actually starting the + * script and setting up the synchronous transfer gunk. + */ + + NCR53c7x0_write8(ISTAT_REG_800, ISTAT_10_SRST); + NCR53c7x0_write8(ISTAT_REG_800, 0); + NCR53c7x0_write8(hostdata->dmode, hostdata->saved_dmode & ~DMODE_MAN); + + + /* + * Respond to reselection by targets and use our _initiator_ SCSI ID + * for arbitration. If notyet, also respond to SCSI selection. + * + * XXX - Note : we must reprogram this when reselecting as + * a target. + */ + +#ifdef notyet + NCR53c7x0_write8(SCID_REG, (host->this_id & 7)|SCID_800_RRE|SCID_800_SRE); +#else + NCR53c7x0_write8(SCID_REG, (host->this_id & 7)|SCID_800_RRE); +#endif + NCR53c7x0_write8(RESPID_REG_800, hostdata->this_id_mask); + + /* + * Use a maximum (1.6) second handshake to handshake timeout, + * and SCSI recommended .5s selection timeout. + */ + + /* + * The new gcc won't recognize preprocessing directives + * within macro args. + */ +#if 0 + NCR53c7x0_write8(STIME0_REG_800, + ((selection_timeout << STIME0_800_SEL_SHIFT) & STIME0_800_SEL_MASK) + | ((15 << STIME0_800_HTH_SHIFT) & STIME0_800_HTH_MASK)); +#else +/* Disable HTH interrupt */ + NCR53c7x0_write8(STIME0_REG_800, + ((selection_timeout << STIME0_800_SEL_SHIFT) & STIME0_800_SEL_MASK)); +#endif + + + /* + * Enable active negation for happy synchronous transfers. + */ + + NCR53c7x0_write8(STEST3_REG_800, STEST3_800_TE); + + /* + * Enable all interrupts, except parity which we only want when + * the user requests it. + */ + + NCR53c7x0_write8(DIEN_REG, DIEN_800_MDPE | DIEN_800_BF | + DIEN_ABRT | DIEN_SSI | DIEN_SIR | DIEN_800_IID); + + + NCR53c7x0_write8(SIEN0_REG_800, ((hostdata->options & OPTION_PARITY) ? + SIEN_PAR : 0) | SIEN_RST | SIEN_UDC | SIEN_SGE | SIEN_MA); + NCR53c7x0_write8(SIEN1_REG_800, SIEN1_800_STO | SIEN1_800_HTH); + + /* + * Use saved clock frequency divisor and scripts loaded in 16 bit + * mode flags from the saved dcntl. + */ + + NCR53c7x0_write8(DCNTL_REG, hostdata->saved_dcntl); + NCR53c7x0_write8(CTEST4_REG_800, hostdata->saved_ctest4); + + /* Enable active negation */ + NCR53c7x0_write8(STEST3_REG_800, STEST3_800_TE); +} + +/* + * Function static struct NCR53c7x0_cmd *allocate_cmd (Scsi_Cmnd *cmd) + * + * Purpose : Return the first free NCR53c7x0_cmd structure (which are + * reused in a LIFO manner to minimize cache thrashing). + * + * Side effects : If we haven't yet scheduled allocation of NCR53c7x0_cmd + * structures for this device, do so. Attempt to complete all scheduled + * allocations using kmalloc(), putting NCR53c7x0_cmd structures on + * the free list. Teach programmers not to drink and hack. + * + * Inputs : cmd - SCSI command + * + * Returns : NCR53c7x0_cmd structure allocated on behalf of cmd; + * NULL on failure. + */ + +static struct NCR53c7x0_cmd * +allocate_cmd (Scsi_Cmnd *cmd) { + struct Scsi_Host *host = cmd->host; + struct NCR53c7x0_hostdata *hostdata = + (struct NCR53c7x0_hostdata *) host->hostdata; + void *real; /* Real address */ + int size; /* Size of *tmp */ + struct NCR53c7x0_cmd *tmp; + unsigned long flags; + + if (hostdata->options & OPTION_DEBUG_ALLOCATION) + printk ("scsi%d : num_cmds = %d, can_queue = %d\n" + " target = %d, lun = %d, %s\n", + host->host_no, hostdata->num_cmds, host->can_queue, + cmd->target, cmd->lun, (hostdata->cmd_allocated[cmd->target] & + (1 << cmd->lun)) ? "already allocated" : "not allocated"); + +/* + * If we have not yet reserved commands for this I_T_L nexus, and + * the device exists (as indicated by permanent Scsi_Cmnd structures + * being allocated under 1.3.x, or being outside of scan_scsis in + * 1.2.x), do so now. + */ + if (!(hostdata->cmd_allocated[cmd->target] & (1 << cmd->lun)) && +#ifdef LINUX_1_2 + !in_scan_scsis +#else + cmd->device && cmd->device->has_cmdblocks +#endif + ) { + if ((hostdata->extra_allocate + hostdata->num_cmds) < host->can_queue) + hostdata->extra_allocate += host->cmd_per_lun; + hostdata->cmd_allocated[cmd->target] |= (1 << cmd->lun); + } + + for (; hostdata->extra_allocate > 0 ; --hostdata->extra_allocate, + ++hostdata->num_cmds) { + /* historically, kmalloc has returned unaligned addresses; pad so we + have enough room to ROUNDUP */ + size = hostdata->max_cmd_size + sizeof (void *); +/* FIXME: for ISA bus '7xx chips, we need to or GFP_DMA in here */ + real = kmalloc (size, GFP_ATOMIC); + if (!real) { + if (hostdata->options & OPTION_DEBUG_ALLOCATION) + printk ("scsi%d : kmalloc(%d) failed\n", + host->host_no, size); + break; + } + tmp = ROUNDUP(real, void *); + tmp->real = real; + tmp->size = size; +#ifdef LINUX_1_2 + tmp->free = ((void (*)(void *, int)) kfree_s); +#else + tmp->free = ((void (*)(void *, int)) kfree); +#endif + save_flags (flags); + cli(); + tmp->next = hostdata->free; + hostdata->free = tmp; + restore_flags (flags); + } + save_flags(flags); + cli(); + tmp = (struct NCR53c7x0_cmd *) hostdata->free; + if (tmp) { + hostdata->free = tmp->next; + } + restore_flags(flags); + if (!tmp) + printk ("scsi%d : can't allocate command for target %d lun %d\n", + host->host_no, cmd->target, cmd->lun); + return tmp; +} + +/* + * Function static struct NCR53c7x0_cmd *create_cmd (Scsi_Cmnd *cmd) + * + * + * Purpose : allocate a NCR53c7x0_cmd structure, initialize it based on the + * Scsi_Cmnd structure passed in cmd, including dsa and Linux field + * initialization, and dsa code relocation. + * + * Inputs : cmd - SCSI command + * + * Returns : NCR53c7x0_cmd structure corresponding to cmd, + * NULL on failure. + */ + +static struct NCR53c7x0_cmd * +create_cmd (Scsi_Cmnd *cmd) { + NCR53c7x0_local_declare(); + struct Scsi_Host *host = cmd->host; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + struct NCR53c7x0_cmd *tmp; /* NCR53c7x0_cmd structure for this command */ + int datain, /* Number of instructions per phase */ + dataout; + int data_transfer_instructions, /* Count of dynamic instructions */ + i; /* Counter */ + u32 *cmd_datain, /* Address of datain/dataout code */ + *cmd_dataout; /* Incremented as we assemble */ +#ifdef notyet + unsigned char *msgptr; /* Current byte in select message */ + int msglen; /* Length of whole select message */ +#endif + unsigned long flags; + NCR53c7x0_local_setup(cmd->host); + + if (!(tmp = allocate_cmd (cmd))) + return NULL; + + + /* + * Decide whether we need to generate commands for DATA IN, + * DATA OUT, neither, or both based on the SCSI command + */ + + switch (cmd->cmnd[0]) { + /* These commands do DATA IN */ + case INQUIRY: + case MODE_SENSE: + case READ_6: + case READ_10: + case READ_CAPACITY: + case REQUEST_SENSE: + datain = 2 * (cmd->use_sg ? cmd->use_sg : 1) + 3; + dataout = 0; + break; + /* These commands do DATA OUT */ + case MODE_SELECT: + case WRITE_6: + case WRITE_10: + case START_STOP: /* also SCAN, which may do DATA OUT */ +#if 0 + printk("scsi%d : command is ", host->host_no); + print_command(cmd->cmnd); +#endif +#if 0 + printk ("scsi%d : %d scatter/gather segments\n", host->host_no, + cmd->use_sg); +#endif + datain = 0; + dataout = 2 * (cmd->use_sg ? cmd->use_sg : 1) + 3; +#if 0 + hostdata->options |= OPTION_DEBUG_INTR; +#endif + break; + /* + * These commands do no data transfer, we should force an + * interrupt if a data phase is attempted on them. + */ + case TEST_UNIT_READY: + datain = dataout = 0; + break; + /* + * We don't know about these commands, so generate code to handle + * both DATA IN and DATA OUT phases. + */ + default: + datain = dataout = 2 * (cmd->use_sg ? cmd->use_sg : 1) + 3; + } + + /* + * New code : so that active pointers work correctly regardless + * of where the saved data pointer is at, we want to immediately + * enter the dynamic code after selection, and on a non-data + * phase perform a CALL to the non-data phase handler, with + * returns back to this address. + * + * If a phase mismatch is encountered in the middle of a + * Block MOVE instruction, we want to _leave_ that instruction + * unchanged as the current case is, modify a temporary buffer, + * and point the active pointer (TEMP) at that. + * + * Furthermore, we want to implement a saved data pointer, + * set by the SAVE_DATA_POINTERs message. + * + * So, the data transfer segments will change to + * CALL data_transfer, WHEN NOT data phase + * MOVE x, x, WHEN data phase + * ( repeat ) + * JUMP other_transfer + */ + + data_transfer_instructions = datain + dataout; + + /* + * When we perform a request sense, we overwrite various things, + * including the data transfer code. Make sure we have enough + * space to do that. + */ + + if (data_transfer_instructions < 2) + data_transfer_instructions = 2; + + + /* + * The saved data pointer is set up so that a RESTORE POINTERS message + * will start the data transfer over at the beginning. + */ + + tmp->saved_data_pointer = virt_to_bus (hostdata->script) + + hostdata->E_data_transfer; + + /* + * Initialize Linux specific fields. + */ + + tmp->cmd = cmd; + tmp->next = NULL; + tmp->flags = 0; + tmp->dsa_next_addr = virt_to_bus(tmp->dsa) + hostdata->dsa_next - + hostdata->dsa_start; + tmp->dsa_addr = virt_to_bus(tmp->dsa) - hostdata->dsa_start; + + /* + * Calculate addresses of dynamic code to fill in DSA + */ + + tmp->data_transfer_start = tmp->dsa + (hostdata->dsa_end - + hostdata->dsa_start) / sizeof(u32); + tmp->data_transfer_end = tmp->data_transfer_start + + 2 * data_transfer_instructions; + + cmd_datain = datain ? tmp->data_transfer_start : NULL; + cmd_dataout = dataout ? (datain ? cmd_datain + 2 * datain : tmp-> + data_transfer_start) : NULL; + + /* + * Fill in the NCR53c7x0_cmd structure as follows + * dsa, with fixed up DSA code + * datain code + * dataout code + */ + + /* Copy template code into dsa and perform all necessary fixups */ + if (hostdata->dsa_fixup) + hostdata->dsa_fixup(tmp); + + patch_dsa_32(tmp->dsa, dsa_next, 0, 0); + patch_dsa_32(tmp->dsa, dsa_cmnd, 0, virt_to_bus(cmd)); + + if (hostdata->options & OPTION_DEBUG_SYNCHRONOUS) + if (hostdata->sync[cmd->target].select_indirect != + ((hostdata->sync[cmd->target].scntl3_sanity << 24) | + (cmd->target << 16) | + (hostdata->sync[cmd->target].sxfer_sanity << 8))) { + printk ("scsi%d : sanity check failed select_indirect=0x%x\n", + host->host_no, hostdata->sync[cmd->target].select_indirect); + FATAL(host); + + } + + patch_dsa_32(tmp->dsa, dsa_select, 0, hostdata->sync[cmd->target]. + select_indirect); + /* + * Right now, we'll do the WIDE and SYNCHRONOUS negotiations on + * different commands; although it should be trivial to do them + * both at the same time. + */ + if (hostdata->initiate_wdtr & (1 << cmd->target)) { + memcpy ((void *) (tmp->select + 1), (void *) wdtr_message, + sizeof(wdtr_message)); + patch_dsa_32(tmp->dsa, dsa_msgout, 0, 1 + sizeof(wdtr_message)); + save_flags(flags); + cli(); + hostdata->initiate_wdtr &= ~(1 << cmd->target); + restore_flags(flags); + } else if (hostdata->initiate_sdtr & (1 << cmd->target)) { + memcpy ((void *) (tmp->select + 1), (void *) sdtr_message, + sizeof(sdtr_message)); + patch_dsa_32(tmp->dsa, dsa_msgout, 0, 1 + sizeof(sdtr_message)); + tmp->flags |= CMD_FLAG_SDTR; + save_flags(flags); + cli(); + hostdata->initiate_sdtr &= ~(1 << cmd->target); + restore_flags(flags); + + } +#if 1 + else if (!(hostdata->talked_to & (1 << cmd->target)) && + !(hostdata->options & OPTION_NO_ASYNC)) { + memcpy ((void *) (tmp->select + 1), (void *) async_message, + sizeof(async_message)); + patch_dsa_32(tmp->dsa, dsa_msgout, 0, 1 + sizeof(async_message)); + tmp->flags |= CMD_FLAG_SDTR; + } +#endif + else + patch_dsa_32(tmp->dsa, dsa_msgout, 0, 1); + hostdata->talked_to |= (1 << cmd->target); + tmp->select[0] = (hostdata->options & OPTION_DISCONNECT) ? + IDENTIFY (1, cmd->lun) : IDENTIFY (0, cmd->lun); + patch_dsa_32(tmp->dsa, dsa_msgout, 1, virt_to_bus(tmp->select)); + patch_dsa_32(tmp->dsa, dsa_cmdout, 0, cmd->cmd_len); + patch_dsa_32(tmp->dsa, dsa_cmdout, 1, virt_to_bus(cmd->cmnd)); + patch_dsa_32(tmp->dsa, dsa_dataout, 0, cmd_dataout ? + virt_to_bus (cmd_dataout) + : virt_to_bus (hostdata->script) + hostdata->E_other_transfer); + patch_dsa_32(tmp->dsa, dsa_datain, 0, cmd_datain ? + virt_to_bus (cmd_datain) + : virt_to_bus (hostdata->script) + hostdata->E_other_transfer); + /* + * XXX - need to make endian aware, should use separate variables + * for both status and message bytes. + */ + patch_dsa_32(tmp->dsa, dsa_msgin, 0, 1); +/* + * FIXME : these only works for little endian. We probably want to + * provide message and status fields in the NCR53c7x0_cmd + * structure, and assign them to cmd->result when we're done. + */ + patch_dsa_32(tmp->dsa, dsa_msgin, 1, virt_to_bus(&cmd->result) + 1); + patch_dsa_32(tmp->dsa, dsa_status, 0, 1); + patch_dsa_32(tmp->dsa, dsa_status, 1, virt_to_bus(&cmd->result)); + patch_dsa_32(tmp->dsa, dsa_msgout_other, 0, 1); + patch_dsa_32(tmp->dsa, dsa_msgout_other, 1, + virt_to_bus(&(hostdata->NCR53c7xx_msg_nop))); + + /* + * Generate code for zero or more of the DATA IN, DATA OUT phases + * in the format + * + * CALL data_transfer, WHEN NOT phase + * MOVE first buffer length, first buffer address, WHEN phase + * ... + * MOVE last buffer length, last buffer address, WHEN phase + * JUMP other_transfer + */ + +/* + * See if we're getting to data transfer by generating an unconditional + * interrupt. + */ +#if 0 + if (datain) { + cmd_datain[0] = 0x98080000; + cmd_datain[1] = 0x03ffd00d; + cmd_datain += 2; + } +#endif + +/* + * XXX - I'm undecided whether all of this nonsense is faster + * in the long run, or whether I should just go and implement a loop + * on the NCR chip using table indirect mode? + * + * In any case, this is how it _must_ be done for 53c700/700-66 chips, + * so this stays even when we come up with something better. + * + * When we're limited to 1 simultaneous command, no overlapping processing, + * we're seeing 630K/sec, with 7% CPU usage on a slow Syquest 45M + * drive. + * + * Not bad, not good. We'll see. + */ + + for (i = 0; cmd->use_sg ? (i < cmd->use_sg) : !i; cmd_datain += 4, + cmd_dataout += 4, ++i) { + u32 buf = cmd->use_sg ? + virt_to_bus(((struct scatterlist *)cmd->buffer)[i].address) : + virt_to_bus(cmd->request_buffer); + u32 count = cmd->use_sg ? + ((struct scatterlist *)cmd->buffer)[i].length : + cmd->request_bufflen; + + if (datain) { + /* CALL other_in, WHEN NOT DATA_IN */ + cmd_datain[0] = ((DCMD_TYPE_TCI | DCMD_TCI_OP_CALL | + DCMD_TCI_IO) << 24) | + DBC_TCI_WAIT_FOR_VALID | DBC_TCI_COMPARE_PHASE; + cmd_datain[1] = virt_to_bus (hostdata->script) + + hostdata->E_other_in; + /* MOVE count, buf, WHEN DATA_IN */ + cmd_datain[2] = ((DCMD_TYPE_BMI | DCMD_BMI_OP_MOVE_I | DCMD_BMI_IO) + << 24) | count; + cmd_datain[3] = buf; +#if 0 + print_insn (host, cmd_datain, "dynamic ", 1); + print_insn (host, cmd_datain + 2, "dynamic ", 1); +#endif + } + if (dataout) { + /* CALL other_out, WHEN NOT DATA_OUT */ + cmd_dataout[0] = ((DCMD_TYPE_TCI | DCMD_TCI_OP_CALL) << 24) | + DBC_TCI_WAIT_FOR_VALID | DBC_TCI_COMPARE_PHASE; + cmd_dataout[1] = virt_to_bus(hostdata->script) + + hostdata->E_other_out; + /* MOVE count, buf, WHEN DATA+OUT */ + cmd_dataout[2] = ((DCMD_TYPE_BMI | DCMD_BMI_OP_MOVE_I) << 24) + | count; + cmd_dataout[3] = buf; +#if 0 + print_insn (host, cmd_dataout, "dynamic ", 1); + print_insn (host, cmd_dataout + 2, "dynamic ", 1); +#endif + } + } + + /* + * Install JUMP instructions after the data transfer routines to return + * control to the do_other_transfer routines. + */ + + + if (datain) { + cmd_datain[0] = ((DCMD_TYPE_TCI | DCMD_TCI_OP_JUMP) << 24) | + DBC_TCI_TRUE; + cmd_datain[1] = virt_to_bus(hostdata->script) + + hostdata->E_other_transfer; +#if 0 + print_insn (host, cmd_datain, "dynamic jump ", 1); +#endif + cmd_datain += 2; + } +#if 0 + if (datain) { + cmd_datain[0] = 0x98080000; + cmd_datain[1] = 0x03ffdeed; + cmd_datain += 2; + } +#endif + if (dataout) { + cmd_dataout[0] = ((DCMD_TYPE_TCI | DCMD_TCI_OP_JUMP) << 24) | + DBC_TCI_TRUE; + cmd_dataout[1] = virt_to_bus(hostdata->script) + + hostdata->E_other_transfer; +#if 0 + print_insn (host, cmd_dataout, "dynamic jump ", 1); +#endif + cmd_dataout += 2; + } + return tmp; +} + +/* + * Function : int NCR53c7xx_queue_command (Scsi_Cmnd *cmd, + * void (*done)(Scsi_Cmnd *)) + * + * Purpose : enqueues a SCSI command + * + * Inputs : cmd - SCSI command, done - function called on completion, with + * a pointer to the command descriptor. + * + * Returns : 0 + * + * Side effects : + * cmd is added to the per instance driver issue_queue, with major + * twiddling done to the host specific fields of cmd. If the + * process_issue_queue coroutine isn't running, it is restarted. + * + * NOTE : we use the host_scribble field of the Scsi_Cmnd structure to + * hold our own data, and pervert the ptr field of the SCp field + * to create a linked list. + */ + +int +NCR53c7xx_queue_command (Scsi_Cmnd *cmd, void (* done)(Scsi_Cmnd *)) { + struct Scsi_Host *host = cmd->host; + struct NCR53c7x0_hostdata *hostdata = + (struct NCR53c7x0_hostdata *) host->hostdata; + unsigned long flags; + Scsi_Cmnd *tmp; + + cmd->scsi_done = done; + cmd->host_scribble = NULL; + cmd->SCp.ptr = NULL; + cmd->SCp.buffer = NULL; + + save_flags(flags); + cli(); + if ((hostdata->options & (OPTION_DEBUG_INIT_ONLY|OPTION_DEBUG_PROBE_ONLY)) + || ((hostdata->options & OPTION_DEBUG_TARGET_LIMIT) && + !(hostdata->debug_lun_limit[cmd->target] & (1 << cmd->lun))) +#ifdef LINUX_1_2 + || cmd->target > 7 +#else + || cmd->target > host->max_id +#endif + || cmd->target == host->this_id + || hostdata->state == STATE_DISABLED) { + printk("scsi%d : disabled or bad target %d lun %d\n", host->host_no, + cmd->target, cmd->lun); + cmd->result = (DID_BAD_TARGET << 16); + } else if ((hostdata->options & OPTION_DEBUG_NCOMMANDS_LIMIT) && + (hostdata->debug_count_limit == 0)) { + printk("scsi%d : maximum commands exceeded\n", host->host_no); + cmd->result = (DID_BAD_TARGET << 16); + cmd->result = (DID_BAD_TARGET << 16); + } else if (hostdata->options & OPTION_DEBUG_READ_ONLY) { + switch (cmd->cmnd[0]) { + case WRITE_6: + case WRITE_10: + printk("scsi%d : WRITE attempted with NO_WRITE debugging flag set\n", + host->host_no); + cmd->result = (DID_BAD_TARGET << 16); + } + } else { + if ((hostdata->options & OPTION_DEBUG_TARGET_LIMIT) && + hostdata->debug_count_limit != -1) + --hostdata->debug_count_limit; + restore_flags (flags); + cmd->result = 0xffff; /* The NCR will overwrite message + and status with valid data */ + cmd->host_scribble = (unsigned char *) tmp = create_cmd (cmd); + } + cli(); + /* + * REQUEST SENSE commands are inserted at the head of the queue + * so that we do not clear the contingent allegiance condition + * they may be looking at. + */ + + if (!(hostdata->issue_queue) || (cmd->cmnd[0] == REQUEST_SENSE)) { + cmd->SCp.ptr = (unsigned char *) hostdata->issue_queue; + hostdata->issue_queue = cmd; + } else { + for (tmp = (Scsi_Cmnd *) hostdata->issue_queue; tmp->SCp.ptr; + tmp = (Scsi_Cmnd *) tmp->SCp.ptr); + tmp->SCp.ptr = (unsigned char *) cmd; + } + restore_flags (flags); + run_process_issue_queue(); + return 0; +} + +/* + * Function : void to_schedule_list (struct Scsi_Host *host, + * struct NCR53c7x0_hostdata * hostdata, Scsi_Cmnd *cmd) + * + * Purpose : takes a SCSI command which was just removed from the + * issue queue, and deals with it by inserting it in the first + * free slot in the schedule list or by terminating it immediately. + * + * Inputs : + * host - SCSI host adapter; hostdata - hostdata structure for + * this adapter; cmd - a pointer to the command; should have + * the host_scribble field initialized to point to a valid + * + * Side effects : + * cmd is added to the per instance schedule list, with minor + * twiddling done to the host specific fields of cmd. + * + */ + +static __inline__ void +to_schedule_list (struct Scsi_Host *host, struct NCR53c7x0_hostdata *hostdata, + struct NCR53c7x0_cmd *cmd) { + NCR53c7x0_local_declare(); + Scsi_Cmnd *tmp = cmd->cmd; + unsigned long flags; + /* dsa start is negative, so subtraction is used */ + volatile u32 *current; + + int i; + NCR53c7x0_local_setup(host); +#if 0 + printk("scsi%d : new dsa is 0x%lx (virt 0x%p)\n", host->host_no, + virt_to_bus(dsa), dsa); +#endif + + save_flags(flags); + cli(); + + /* + * Work around race condition : if an interrupt fired and we + * got disabled forget about this command. + */ + + if (hostdata->state == STATE_DISABLED) { + printk("scsi%d : driver disabled\n", host->host_no); + tmp->result = (DID_BAD_TARGET << 16); + cmd->next = (struct NCR53c7x0_cmd *) hostdata->free; + hostdata->free = cmd; + tmp->scsi_done(tmp); + restore_flags (flags); + return; + } + + for (i = host->can_queue, current = hostdata->schedule; + i > 0 && current[0] != hostdata->NOP_insn; + --i, current += 2 /* JUMP instructions are two words */); + + if (i > 0) { + ++hostdata->busy[tmp->target][tmp->lun]; + cmd->next = hostdata->running_list; + hostdata->running_list = cmd; + + /* Restore this instruction to a NOP once the command starts */ + cmd->dsa [(hostdata->dsa_jump_dest - hostdata->dsa_start) / + sizeof(u32)] = (u32) virt_to_bus ((void *)current); + /* Replace the current jump operand. */ + current[1] = + virt_to_bus ((void *) cmd->dsa) + hostdata->E_dsa_code_begin - + hostdata->E_dsa_code_template; + /* Replace the NOP instruction with a JUMP */ + current[0] = ((DCMD_TYPE_TCI|DCMD_TCI_OP_JUMP) << 24) | + DBC_TCI_TRUE; + } else { + printk ("scsi%d: no free slot\n", host->host_no); + disable(host); + tmp->result = (DID_ERROR << 16); + cmd->next = (struct NCR53c7x0_cmd *) hostdata->free; + hostdata->free = cmd; + tmp->scsi_done(tmp); + restore_flags (flags); + return; + } + + /* + * If the NCR chip is in an idle state, start it running the scheduler + * immediately. Otherwise, signal the chip to jump to schedule as + * soon as it is idle. + */ + if (hostdata->idle) { + hostdata->idle = 0; + hostdata->state = STATE_RUNNING; + NCR53c7x0_write32 (DSP_REG, virt_to_bus ((void *)hostdata->schedule)); + } else { + NCR53c7x0_write8(hostdata->istat, ISTAT_10_SIGP); + } + + restore_flags(flags); +} + +/* + * Function : busyp (struct Scsi_Host *host, struct NCR53c7x0_hostdata + * *hostdata, Scsi_Cmnd *cmd) + * + * Purpose : decide if we can pass the given SCSI command on to the + * device in question or not. + * + * Returns : non-zero when we're busy, 0 when we aren't. + */ + +static __inline__ int +busyp (struct Scsi_Host *host, struct NCR53c7x0_hostdata *hostdata, + Scsi_Cmnd *cmd) { + /* FIXME : in the future, this needs to accommodate SCSI-II tagged + queuing, and we may be able to play with fairness here a bit. + */ + return hostdata->busy[cmd->target][cmd->lun]; +} + +/* + * Function : process_issue_queue (void) + * + * Purpose : transfer commands from the issue queue to NCR start queue + * of each NCR53c7/8xx in the system, avoiding kernel stack + * overflows when the scsi_done() function is invoked recursively. + * + * NOTE : process_issue_queue exits with interrupts *disabled*, so the + * caller must reenable them if it desires. + * + * NOTE : process_issue_queue should be called from both + * NCR53c7x0_queue_command() and from the interrupt handler + * after command completion in case NCR53c7x0_queue_command() + * isn't invoked again but we've freed up resources that are + * needed. + */ + +static void +process_issue_queue (unsigned long flags) { + Scsi_Cmnd *tmp, *prev; + struct Scsi_Host *host; + struct NCR53c7x0_hostdata *hostdata; + int done; + + /* + * We run (with interrupts disabled) until we're sure that none of + * the host adapters have anything that can be done, at which point + * we set process_issue_queue_running to 0 and exit. + * + * Interrupts are enabled before doing various other internal + * instructions, after we've decided that we need to run through + * the loop again. + * + */ + + do { + cli(); /* Freeze request queues */ + done = 1; + for (host = first_host; host && host->hostt == the_template; + host = host->next) { + hostdata = (struct NCR53c7x0_hostdata *) host->hostdata; + cli(); + if (hostdata->issue_queue) { + if (hostdata->state == STATE_DISABLED) { + tmp = (Scsi_Cmnd *) hostdata->issue_queue; + hostdata->issue_queue = (Scsi_Cmnd *) tmp->SCp.ptr; + tmp->result = (DID_BAD_TARGET << 16); + if (tmp->host_scribble) { + ((struct NCR53c7x0_cmd *)tmp->host_scribble)->next = + hostdata->free; + hostdata->free = + (struct NCR53c7x0_cmd *)tmp->host_scribble; + tmp->host_scribble = NULL; + } + tmp->scsi_done (tmp); + done = 0; + } else + for (tmp = (Scsi_Cmnd *) hostdata->issue_queue, + prev = NULL; tmp; prev = tmp, tmp = (Scsi_Cmnd *) + tmp->SCp.ptr) + if (!tmp->host_scribble || + !busyp (host, hostdata, tmp)) { + if (prev) + prev->SCp.ptr = tmp->SCp.ptr; + else + hostdata->issue_queue = (Scsi_Cmnd *) + tmp->SCp.ptr; + tmp->SCp.ptr = NULL; + if (tmp->host_scribble) { + if (hostdata->options & OPTION_DEBUG_QUEUES) + printk ("scsi%d : moving command for target %d lun %d to start list\n", + host->host_no, tmp->target, tmp->lun); + + + to_schedule_list (host, hostdata, + (struct NCR53c7x0_cmd *) + tmp->host_scribble); + } else { + if (((tmp->result & 0xff) == 0xff) || + ((tmp->result & 0xff00) == 0xff00)) { + printk ("scsi%d : danger Will Robinson!\n", + host->host_no); + tmp->result = DID_ERROR << 16; + disable (host); + } + tmp->scsi_done(tmp); + } + done = 0; + } /* if target/lun is not busy */ + } /* if hostdata->issue_queue */ + if (!done) + restore_flags (flags); + } /* for host */ + } while (!done); + process_issue_queue_running = 0; +} + +/* + * Function : static void intr_scsi (struct Scsi_Host *host, + * struct NCR53c7x0_cmd *cmd) + * + * Purpose : handle all SCSI interrupts, indicated by the setting + * of the SIP bit in the ISTAT register. + * + * Inputs : host, cmd - host and NCR command causing the interrupt, cmd + * may be NULL. + */ + +static void +intr_scsi (struct Scsi_Host *host, struct NCR53c7x0_cmd *cmd) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = + (struct NCR53c7x0_hostdata *) host->hostdata; + unsigned char sstat0_sist0, sist1, /* Registers */ + fatal; /* Did a fatal interrupt + occur ? */ + + int is_8xx_chip; + NCR53c7x0_local_setup(host); + + fatal = 0; + + is_8xx_chip = ((unsigned) (hostdata->chip - 800)) < 100; + if (is_8xx_chip) { + sstat0_sist0 = NCR53c7x0_read8(SIST0_REG_800); + udelay(1); + sist1 = NCR53c7x0_read8(SIST1_REG_800); + } else { + sstat0_sist0 = NCR53c7x0_read8(SSTAT0_REG); + sist1 = 0; + } + + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : SIST0 0x%0x, SIST1 0x%0x\n", host->host_no, + sstat0_sist0, sist1); + + /* 250ms selection timeout */ + if ((is_8xx_chip && (sist1 & SIST1_800_STO)) || + (!is_8xx_chip && (sstat0_sist0 & SSTAT0_700_STO))) { + fatal = 1; + if (hostdata->options & OPTION_DEBUG_INTR) { + printk ("scsi%d : Selection Timeout\n", host->host_no); + if (cmd) { + printk("scsi%d : target %d, lun %d, command ", + host->host_no, cmd->cmd->target, cmd->cmd->lun); + print_command (cmd->cmd->cmnd); + printk("scsi%d : dsp = 0x%x (virt 0x%p)\n", host->host_no, + NCR53c7x0_read32(DSP_REG), + bus_to_virt(NCR53c7x0_read32(DSP_REG))); + } else { + printk("scsi%d : no command\n", host->host_no); + } + } +/* + * XXX - question : how do we want to handle the Illegal Instruction + * interrupt, which may occur before or after the Selection Timeout + * interrupt? + */ + + if (1) { + hostdata->idle = 1; + hostdata->expecting_sto = 0; + + if (hostdata->test_running) { + hostdata->test_running = 0; + hostdata->test_completed = 3; + } else if (cmd) { + abnormal_finished(cmd, DID_BAD_TARGET << 16); + } +#if 0 + hostdata->intrs = 0; +#endif + } + } + +/* + * FIXME : in theory, we can also get a UDC when a STO occurs. + */ + if (sstat0_sist0 & SSTAT0_UDC) { + fatal = 1; + if (cmd) { + printk("scsi%d : target %d lun %d unexpected disconnect\n", + host->host_no, cmd->cmd->target, cmd->cmd->lun); + print_lots (host); + abnormal_finished(cmd, DID_ERROR << 16); + } else + printk("scsi%d : unexpected disconnect (no command)\n", + host->host_no); + + hostdata->dsp = (u32 *) hostdata->schedule; + hostdata->dsp_changed = 1; + } + + /* SCSI PARITY error */ + if (sstat0_sist0 & SSTAT0_PAR) { + fatal = 1; + if (cmd && cmd->cmd) { + printk("scsi%d : target %d lun %d parity error.\n", + host->host_no, cmd->cmd->target, cmd->cmd->lun); + abnormal_finished (cmd, DID_PARITY << 16); + } else + printk("scsi%d : parity error\n", host->host_no); + /* Should send message out, parity error */ + + /* XXX - Reduce synchronous transfer rate! */ + hostdata->dsp = hostdata->script + hostdata->E_initiator_abort / + sizeof(u32); + hostdata->dsp_changed = 1; + /* SCSI GROSS error */ + } + + if (sstat0_sist0 & SSTAT0_SGE) { + fatal = 1; + printk("scsi%d : gross error\n", host->host_no); + /* Reset SCSI offset */ + if ((hostdata->chip / 100) == 8) { + NCR53c7x0_write8 (STEST2_REG_800, STEST2_800_ROF); + } + + /* + * A SCSI gross error may occur when we have + * + * - A synchronous offset which causes the SCSI FIFO to be overwritten. + * + * - A REQ which causes the maximum synchronous offset programmed in + * the SXFER register to be exceeded. + * + * - A phase change with an outstanding synchronous offset. + * + * - Residual data in the synchronous data FIFO, with a transfer + * other than a synchronous receive is started.$# + */ + + + /* XXX Should deduce synchronous transfer rate! */ + hostdata->dsp = hostdata->script + hostdata->E_initiator_abort / + sizeof(u32); + hostdata->dsp_changed = 1; + /* Phase mismatch */ + } + + if (sstat0_sist0 & SSTAT0_MA) { + fatal = 1; + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : SSTAT0_MA\n", host->host_no); + intr_phase_mismatch (host, cmd); + } + +#if 0 + if (sstat0_sist0 & SIST0_800_RSL) + printk ("scsi%d : Oh no Mr. Bill!\n", host->host_no); +#endif + +/* + * If a fatal SCSI interrupt occurs, we must insure that the DMA and + * SCSI FIFOs were flushed. + */ + + if (fatal) { + if (!hostdata->dstat_valid) { + hostdata->dstat = NCR53c7x0_read8(DSTAT_REG); + hostdata->dstat_valid = 1; + } + +/* XXX - code check for 700/800 chips */ + if (!(hostdata->dstat & DSTAT_DFE)) { + printk ("scsi%d : DMA FIFO not empty\n", host->host_no); + if (NCR53c7x0_read8 (CTEST2_REG_800) & CTEST2_800_DDIR) { + printk ("scsi%d: Flushing DMA FIFO\n", + host->host_no); + NCR53c7x0_write8 (CTEST3_REG_800, CTEST3_800_FLF); + while (!((hostdata->dstat = NCR53c7x0_read8(DSTAT_REG)) & + DSTAT_DFE)); + } else { + NCR53c7x0_write8 (CTEST3_REG_800, CTEST3_800_CLF); + while (NCR53c7x0_read8 (CTEST3_REG_800) & CTEST3_800_CLF); + } + hostdata->dstat |= DSTAT_DFE; + } + } +} + +/* + * Function : static void NCR53c7x0_intr (int irq, void *dev_id, struct pt_regs * regs) + * + * Purpose : handle NCR53c7x0 interrupts for all NCR devices sharing + * the same IRQ line. + * + * Inputs : Since we're using the SA_INTERRUPT interrupt handler + * semantics, irq indicates the interrupt which invoked + * this handler. + */ + +static void +NCR53c7x0_intr (int irq, void *dev_id, struct pt_regs * regs) { + NCR53c7x0_local_declare(); + struct Scsi_Host *host; /* Host we are looking at */ + unsigned char istat; /* Values of interrupt regs */ + struct NCR53c7x0_hostdata *hostdata; /* host->hostdata */ + struct NCR53c7x0_cmd *cmd, /* command which halted */ + **cmd_prev_ptr; + u32 *dsa; /* DSA */ + int done = 1; /* Indicates when handler + should terminate */ + int interrupted = 0; /* This HA generated + an interrupt */ + int have_intfly; /* Don't print warning + messages when we stack + INTFLYs */ + unsigned long flags; + +#ifdef NCR_DEBUG + char buf[80]; /* Debugging sprintf buffer */ + size_t buflen; /* Length of same */ +#endif + + do { + done = 1; + for (host = first_host; host; host = host->next) + if (host->hostt == the_template && host->irq == irq) { + NCR53c7x0_local_setup(host); + + hostdata = (struct NCR53c7x0_hostdata *) host->hostdata; + hostdata->dsp_changed = 0; + interrupted = 0; + have_intfly = 0; + + do { + int is_8xx_chip; + + hostdata->dstat_valid = 0; + interrupted = 0; + /* + * Only read istat once, since reading it again will unstack + * interrupts? + */ + istat = NCR53c7x0_read8(hostdata->istat); + + /* + * INTFLY interrupts are used by the NCR53c720, NCR53c810, + * and NCR53c820 to signify completion of a command. Since + * the SCSI processor continues running, we can't just look + * at the contents of the DSA register and continue running. + */ +/* XXX - this is too big, offends my sense of aesthetics, and should + move to intr_intfly() */ + is_8xx_chip = ((unsigned) (hostdata->chip - 800)) < 100; + if ((hostdata->options & OPTION_INTFLY) && + (is_8xx_chip && (istat & ISTAT_800_INTF))) { + char search_found = 0; /* Got at least one ? */ + done = 0; + interrupted = 1; + + /* + * Clear the INTF bit by writing a one. + * This reset operation is self-clearing. + */ + NCR53c7x0_write8(hostdata->istat, istat|ISTAT_800_INTF); + + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : INTFLY\n", host->host_no); + + /* + * Traverse our list of running commands, and look + * for those with valid (non-0xff ff) status and message + * bytes encoded in the result which signify command + * completion. + */ + + + save_flags(flags); + cli(); +restart: + for (cmd_prev_ptr = (struct NCR53c7x0_cmd **) + &(hostdata->running_list), cmd = + (struct NCR53c7x0_cmd *) hostdata->running_list; cmd ; + cmd_prev_ptr = (struct NCR53c7x0_cmd **) &(cmd->next), + cmd = (struct NCR53c7x0_cmd *) cmd->next) { + Scsi_Cmnd *tmp; + + if (!cmd) { + printk("scsi%d : very weird.\n", host->host_no); + break; + } + + if (!(tmp = cmd->cmd)) { + printk("scsi%d : weird. NCR53c7x0_cmd has no Scsi_Cmnd\n", + host->host_no); + continue; + } +#if 0 + printk ("scsi%d : looking at result of 0x%x\n", + host->host_no, cmd->cmd->result); +#endif + + if (((tmp->result & 0xff) == 0xff) || + ((tmp->result & 0xff00) == 0xff00)) + continue; + + search_found = 1; + + /* Important - remove from list _before_ done is called */ + if (cmd_prev_ptr) + *cmd_prev_ptr = (struct NCR53c7x0_cmd *) cmd->next; + + --hostdata->busy[tmp->target][tmp->lun]; + cmd->next = hostdata->free; + hostdata->free = cmd; + + tmp->host_scribble = NULL; + + if (hostdata->options & OPTION_DEBUG_INTR) { + printk ("scsi%d : command complete : pid %lu, id %d,lun %d result 0x%x ", + host->host_no, tmp->pid, tmp->target, tmp->lun, tmp->result); + print_command (tmp->cmnd); + } + +#if 0 + hostdata->options &= ~OPTION_DEBUG_INTR; +#endif + tmp->scsi_done(tmp); + goto restart; + + } + restore_flags(flags); + + /* + * I think that we're stacking INTFLY interrupts; taking care of + * all the finished commands on the first one, and then getting + * worried when we see the next one. The magic with have_intfly + * should tell if this is the case.. + */ + + if (!search_found && !have_intfly) { + printk ("scsi%d : WARNING : INTFLY with no completed commands.\n", + host->host_no); + } else if (!have_intfly) { + have_intfly = 1; + run_process_issue_queue(); + } + } + + if (istat & (ISTAT_SIP|ISTAT_DIP)) { + done = 0; + interrupted = 1; + hostdata->state = STATE_HALTED; + + if (NCR53c7x0_read8 ((hostdata->chip / 100) == 8 ? + SSTAT1_REG : SSTAT2_REG) & SSTAT2_FF_MASK) + printk ("scsi%d : SCSI FIFO not empty\n", + host->host_no); + + /* + * NCR53c700 and NCR53c700-66 change the current SCSI + * process, hostdata->current, in the Linux driver so + * cmd = hostdata->current. + * + * With other chips, we must look through the commands + * executing and find the command structure which + * corresponds to the DSA register. + */ + + if (hostdata->options & OPTION_700) { + cmd = (struct NCR53c7x0_cmd *) hostdata->current; + } else { + dsa = bus_to_virt(NCR53c7x0_read32(DSA_REG)); + for (cmd = (struct NCR53c7x0_cmd *) + hostdata->running_list; cmd && + (dsa + (hostdata->dsa_start / sizeof(u32))) != + cmd->dsa; + cmd = (struct NCR53c7x0_cmd *)(cmd->next)); + } + if (hostdata->options & OPTION_DEBUG_INTR) { + if (cmd) { + printk("scsi%d : interrupt for pid %lu, id %d, lun %d ", + host->host_no, cmd->cmd->pid, (int) cmd->cmd->target, + (int) cmd->cmd->lun); + print_command (cmd->cmd->cmnd); + } else { + printk("scsi%d : no active command\n", host->host_no); + } + } + + if (istat & ISTAT_SIP) { + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : ISTAT_SIP\n", host->host_no); + intr_scsi (host, cmd); + } + + if (istat & ISTAT_DIP) { + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : ISTAT_DIP\n", host->host_no); + intr_dma (host, cmd); + } + + if (!hostdata->dstat_valid) { + hostdata->dstat = NCR53c7x0_read8(DSTAT_REG); + hostdata->dstat_valid = 1; + } + + /* XXX - code check for 700/800 chips */ + if (!(hostdata->dstat & DSTAT_DFE)) { + printk ("scsi%d : DMA FIFO not empty\n", host->host_no); + if (NCR53c7x0_read8 (CTEST2_REG_800) & CTEST2_800_DDIR) { + printk ("scsi%d: Flushing DMA FIFO\n", + host->host_no); + NCR53c7x0_write8 (CTEST3_REG_800, CTEST3_800_FLF); + while (!((hostdata->dstat = NCR53c7x0_read8(DSTAT_REG)) & + DSTAT_DFE)); + } else + { + NCR53c7x0_write8 (CTEST3_REG_800, CTEST3_800_CLF); + while (NCR53c7x0_read8 (CTEST3_REG_800) & CTEST3_800_CLF); + } + hostdata->dstat |= DSTAT_DFE; + } + } + } while (interrupted); + + + + if (hostdata->intrs != -1) + hostdata->intrs++; +#if 0 + if (hostdata->intrs > 40) { + printk("scsi%d : too many interrupts, halting", host->host_no); + disable(host); + } +#endif + + if (!hostdata->idle && hostdata->state == STATE_HALTED) { + if (!hostdata->dsp_changed) { + hostdata->dsp = (u32 *) + bus_to_virt(NCR53c7x0_read32(DSP_REG)); + } + +#if 0 + printk("scsi%d : new dsp is 0x%lx (virt 0x%p)\n", + host->host_no, virt_to_bus(hostdata->dsp), hostdata->dsp); +#endif + + hostdata->state = STATE_RUNNING; + NCR53c7x0_write32 (DSP_REG, virt_to_bus(hostdata->dsp)); + } + } + } while (!done); +} + + +/* + * Function : static int abort_connected (struct Scsi_Host *host) + * + * Purpose : Assuming that the NCR SCSI processor is currently + * halted, break the currently established nexus. Clean + * up of the NCR53c7x0_cmd and Scsi_Cmnd structures should + * be done on receipt of the abort interrupt. + * + * Inputs : host - SCSI host + * + */ + +static int +abort_connected (struct Scsi_Host *host) { +#ifdef NEW_ABORT + NCR53c7x0_local_declare(); +#endif + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; +/* FIXME : this probably should change for production kernels; at the + least, counter should move to a per-host structure. */ + static int counter = 5; +#ifdef NEW_ABORT + int sstat, phase, offset; + u32 *script; + NCR53c7x0_local_setup(host); +#endif + + if (--counter <= 0) { + disable(host); + return 0; + } + + printk ("scsi%d : DANGER : abort_connected() called \n", + host->host_no); + +#ifdef NEW_ABORT + +/* + * New strategy : Rather than using a generic abort routine, + * we'll specifically try to source or sink the appropriate + * amount of data for the phase we're currently in (taking into + * account the current synchronous offset) + */ + + sstat = (NCR53c8x0_read8 ((chip / 100) == 8 ? SSTAT1_REG : SSTAT2_REG); + offset = OFFSET (sstat & SSTAT2_FF_MASK) >> SSTAT2_FF_SHIFT; + phase = sstat & SSTAT2_PHASE_MASK; + +/* + * SET ATN + * MOVE source_or_sink, WHEN CURRENT PHASE + * < repeat for each outstanding byte > + * JUMP send_abort_message + */ + + script = hostdata->abort_script = kmalloc ( + 8 /* instruction size */ * ( + 1 /* set ATN */ + + (!offset ? 1 : offset) /* One transfer per outstanding byte */ + + 1 /* send abort message */), + GFP_ATOMIC); + + +#else /* def NEW_ABORT */ + hostdata->dsp = hostdata->script + hostdata->E_initiator_abort / + sizeof(u32); +#endif /* def NEW_ABORT */ + hostdata->dsp_changed = 1; + +/* XXX - need to flag the command as aborted after the abort_connected + code runs + */ + return 0; +} + +/* + * Function : static int datapath_residual (Scsi_Host *host) + * + * Purpose : return residual data count of what's in the chip. + * + * Inputs : host - SCSI host + */ + +static int +datapath_residual (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int count, synchronous, sstat; + NCR53c7x0_local_setup(host); + /* COMPAT : the 700 and 700-66 need to use DFIFO_00_BO_MASK */ + count = ((NCR53c7x0_read8 (DFIFO_REG) & DFIFO_10_BO_MASK) - + (NCR53c7x0_read32 (DBC_REG) & DFIFO_10_BO_MASK)) & DFIFO_10_BO_MASK; + synchronous = NCR53c7x0_read8 (SXFER_REG) & SXFER_MO_MASK; + /* COMPAT : DDIR is elsewhere on non-'8xx chips. */ + if (NCR53c7x0_read8 (CTEST2_REG_800) & CTEST2_800_DDIR) { + /* Receive */ + if (synchronous) + count += (NCR53c7x0_read8 ((hostdata->chip / 100) == 8 ? + SSTAT1_REG : SSTAT2_REG) & SSTAT2_FF_MASK) >> SSTAT2_FF_SHIFT; + else + if (NCR53c7x0_read8 ((hostdata->chip / 100) == 8 ? + SSTAT0_REG : SSTAT1_REG) & SSTAT1_ILF) + ++count; + } else { + /* Send */ + sstat = ((hostdata->chip / 100) == 8) ? NCR53c7x0_read8 (SSTAT0_REG) : + NCR53c7x0_read8 (SSTAT1_REG); + if (sstat & SSTAT1_OLF) + ++count; + if (synchronous && (sstat & SSTAT1_ORF)) + ++count; + } + return count; +} + +/* + * Function : static const char * sbcl_to_phase (int sbcl)_ + * + * Purpose : Convert SBCL register to user-parsable phase representation + * + * Inputs : sbcl - value of sbcl register + */ + + +static const char * +sbcl_to_phase (int sbcl) { + switch (sbcl & SBCL_PHASE_MASK) { + case SBCL_PHASE_DATAIN: + return "DATAIN"; + case SBCL_PHASE_DATAOUT: + return "DATAOUT"; + case SBCL_PHASE_MSGIN: + return "MSGIN"; + case SBCL_PHASE_MSGOUT: + return "MSGOUT"; + case SBCL_PHASE_CMDOUT: + return "CMDOUT"; + case SBCL_PHASE_STATIN: + return "STATUSIN"; + default: + return "unknown"; + } +} + +/* + * Function : static const char * sstat2_to_phase (int sstat)_ + * + * Purpose : Convert SSTAT2 register to user-parsable phase representation + * + * Inputs : sstat - value of sstat register + */ + + +static const char * +sstat2_to_phase (int sstat) { + switch (sstat & SSTAT2_PHASE_MASK) { + case SSTAT2_PHASE_DATAIN: + return "DATAIN"; + case SSTAT2_PHASE_DATAOUT: + return "DATAOUT"; + case SSTAT2_PHASE_MSGIN: + return "MSGIN"; + case SSTAT2_PHASE_MSGOUT: + return "MSGOUT"; + case SSTAT2_PHASE_CMDOUT: + return "CMDOUT"; + case SSTAT2_PHASE_STATIN: + return "STATUSIN"; + default: + return "unknown"; + } +} + +/* + * Function : static void intr_phase_mismatch (struct Scsi_Host *host, + * struct NCR53c7x0_cmd *cmd) + * + * Purpose : Handle phase mismatch interrupts + * + * Inputs : host, cmd - host and NCR command causing the interrupt, cmd + * may be NULL. + * + * Side effects : The abort_connected() routine is called or the NCR chip + * is restarted, jumping to the command_complete entry point, or + * patching the address and transfer count of the current instruction + * and calling the msg_in entry point as appropriate. + */ + +static void +intr_phase_mismatch (struct Scsi_Host *host, struct NCR53c7x0_cmd *cmd) { + NCR53c7x0_local_declare(); + u32 dbc_dcmd, *dsp, *dsp_next; + unsigned char dcmd, sbcl; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int residual; + enum {ACTION_ABORT, ACTION_ABORT_PRINT, ACTION_CONTINUE} action = + ACTION_ABORT_PRINT; + const char *where = NULL; + NCR53c7x0_local_setup(host); + + /* + * Corrective action is based on where in the SCSI SCRIPT(tm) the error + * occurred, as well as which SCSI phase we are currently in. + */ + dsp_next = bus_to_virt(NCR53c7x0_read32(DSP_REG)); + + /* + * Fetch the current instruction, and remove the operands for easier + * interpretation. + */ + dbc_dcmd = NCR53c7x0_read32(DBC_REG); + dcmd = (dbc_dcmd & 0xff000000) >> 24; + /* + * Like other processors, the NCR adjusts the instruction pointer before + * instruction decode. Set the DSP address back to what it should + * be for this instruction based on its size (2 or 3 32 bit words). + */ + dsp = dsp_next - NCR53c7x0_insn_size(dcmd); + + + /* + * Read new SCSI phase from the SBCL lines. Since all of our code uses + * a WHEN conditional instead of an IF conditional, we don't need to + * wait for a new REQ. + */ + sbcl = NCR53c7x0_read8(SBCL_REG) & SBCL_PHASE_MASK; + + if (!cmd) { + action = ACTION_ABORT_PRINT; + where = "no current command"; + /* + * The way my SCSI SCRIPTS(tm) are architected, recoverable phase + * mismatches should only occur where we're doing a multi-byte + * BMI instruction. Specifically, this means + * + * - select messages (a SCSI-I target may ignore additional messages + * after the IDENTIFY; any target may reject a SDTR or WDTR) + * + * - command out (targets may send a message to signal an error + * condition, or go into STATUSIN after they've decided + * they don't like the command. + * + * - reply_message (targets may reject a multi-byte message in the + * middle) + * + * - data transfer routines (command completion with buffer space + * left, disconnect message, or error message) + */ + } else if (((dsp >= cmd->data_transfer_start && + dsp < cmd->data_transfer_end)) || dsp == (cmd->residual + 2)) { + if ((dcmd & (DCMD_TYPE_MASK|DCMD_BMI_OP_MASK|DCMD_BMI_INDIRECT| + DCMD_BMI_MSG|DCMD_BMI_CD)) == (DCMD_TYPE_BMI| + DCMD_BMI_OP_MOVE_I)) { + residual = datapath_residual (host); + if (hostdata->options & OPTION_DEBUG_DISCONNECT) + printk ("scsi%d : handling residual transfer (+ %d bytes from DMA FIFO)\n", + host->host_no, residual); + + /* + * The first instruction is a CALL to the alternate handler for + * this data transfer phase, so we can do calls to + * munge_msg_restart as we would if control were passed + * from normal dynamic code. + */ + if (dsp != cmd->residual + 2) { + cmd->residual[0] = ((DCMD_TYPE_TCI | DCMD_TCI_OP_CALL | + ((dcmd & DCMD_BMI_IO) ? DCMD_TCI_IO : 0)) << 24) | + DBC_TCI_WAIT_FOR_VALID | DBC_TCI_COMPARE_PHASE; + cmd->residual[1] = virt_to_bus(hostdata->script) + + ((dcmd & DCMD_BMI_IO) + ? hostdata->E_other_in : hostdata->E_other_out); + } + + /* + * The second instruction is the a data transfer block + * move instruction, reflecting the pointer and count at the + * time of the phase mismatch. + */ + cmd->residual[2] = dbc_dcmd + residual; + cmd->residual[3] = NCR53c7x0_read32(DNAD_REG) - residual; + + /* + * The third and final instruction is a jump to the instruction + * which follows the instruction which had to be 'split' + */ + if (dsp != cmd->residual + 2) { + cmd->residual[4] = ((DCMD_TYPE_TCI|DCMD_TCI_OP_JUMP) + << 24) | DBC_TCI_TRUE; + cmd->residual[5] = virt_to_bus(dsp_next); + } + + /* + * For the sake of simplicity, transfer control to the + * conditional CALL at the start of the residual buffer. + */ + hostdata->dsp = cmd->residual; + hostdata->dsp_changed = 1; + action = ACTION_CONTINUE; + } else { + where = "non-BMI dynamic DSA code"; + action = ACTION_ABORT_PRINT; + } + } else if (dsp == (hostdata->script + hostdata->E_select_msgout / 4)) { + /* Release ATN */ + NCR53c7x0_write8 (SOCL_REG, 0); + switch (sbcl) { + /* + * Some devices (SQ555 come to mind) grab the IDENTIFY message + * sent on selection, and decide to go into COMMAND OUT phase + * rather than accepting the rest of the messages or rejecting + * them. Handle these devices gracefully. + */ + case SBCL_PHASE_CMDOUT: + hostdata->dsp = dsp + 2 /* two _words_ */; + hostdata->dsp_changed = 1; + printk ("scsi%d : target %d ignored SDTR and went into COMMAND OUT\n", + host->host_no, cmd->cmd->target); + cmd->flags &= ~CMD_FLAG_SDTR; + action = ACTION_CONTINUE; + break; + case SBCL_PHASE_MSGIN: + hostdata->dsp = hostdata->script + hostdata->E_msg_in / + sizeof(u32); + hostdata->dsp_changed = 1; + action = ACTION_CONTINUE; + break; + default: + where="select message out"; + action = ACTION_ABORT_PRINT; + } + /* + * Some SCSI devices will interpret a command as they read the bytes + * off the SCSI bus, and may decide that the command is Bogus before + * they've read the entire command off the bus. + */ + } else if (dsp == hostdata->script + hostdata->E_cmdout_cmdout / sizeof + (u32)) { + hostdata->dsp = hostdata->script + hostdata->E_data_transfer / + sizeof (u32); + hostdata->dsp_changed = 1; + action = ACTION_CONTINUE; + /* FIXME : we need to handle message reject, etc. within msg_respond. */ +#ifdef notyet + } else if (dsp == hostdata->script + hostdata->E_reply_message) { + switch (sbcl) { + /* Any other phase mismatches abort the currently executing command. */ +#endif + } else { + where = "unknown location"; + action = ACTION_ABORT_PRINT; + } + + /* Flush DMA FIFO */ + if (!hostdata->dstat_valid) { + hostdata->dstat = NCR53c7x0_read8(DSTAT_REG); + hostdata->dstat_valid = 1; + } + if (!(hostdata->dstat & DSTAT_DFE)) { + if (NCR53c7x0_read8 (CTEST2_REG_800) & CTEST2_800_DDIR) { + printk ("scsi%d: Flushing DMA FIFO\n", + host->host_no); + NCR53c7x0_write8 (CTEST3_REG_800, CTEST3_800_FLF); + /* FIXME : what about stacked DMA interrupts? */ + while (!((hostdata->dstat = NCR53c7x0_read8(DSTAT_REG)) & + DSTAT_DFE)); + } else { + NCR53c7x0_write8 (CTEST3_REG_800, CTEST3_800_CLF); + while (NCR53c7x0_read8 (CTEST3_REG_800) & CTEST3_800_CLF); + } + hostdata->dstat |= DSTAT_DFE; + } + + switch (action) { + case ACTION_ABORT_PRINT: + printk("scsi%d : %s : unexpected phase %s.\n", + host->host_no, where ? where : "unknown location", + sbcl_to_phase(sbcl)); + print_lots (host); + /* Fall through to ACTION_ABORT */ + case ACTION_ABORT: + abort_connected (host); + break; + case ACTION_CONTINUE: + break; + } + +#if 0 + if (hostdata->dsp_changed) { + printk("scsi%d: new dsp 0x%p\n", host->host_no, hostdata->dsp); + print_insn (host, hostdata->dsp, "", 1); + } +#endif + +} + +/* + * Function : static void intr_bf (struct Scsi_Host *host, + * struct NCR53c7x0_cmd *cmd) + * + * Purpose : handle BUS FAULT interrupts + * + * Inputs : host, cmd - host and NCR command causing the interrupt, cmd + * may be NULL. + */ + +static void +intr_bf (struct Scsi_Host *host, struct NCR53c7x0_cmd *cmd) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + u32 *dsp, + *next_dsp, /* Current dsp */ + *dsa, + dbc_dcmd; /* DCMD (high eight bits) + DBC */ + unsigned short pci_status; + int tmp; + unsigned long flags; + char *reason = NULL; + /* Default behavior is for a silent error, with a retry until we've + exhausted retries. */ + enum {MAYBE, ALWAYS, NEVER} retry = MAYBE; + int report = 0; + NCR53c7x0_local_setup(host); + + dbc_dcmd = NCR53c7x0_read32 (DBC_REG); + next_dsp = bus_to_virt (NCR53c7x0_read32(DSP_REG)); + dsp = next_dsp - NCR53c7x0_insn_size ((dbc_dcmd >> 24) & 0xff); +/* FIXME - check chip type */ + dsa = bus_to_virt (NCR53c7x0_read32(DSA_REG)); + + /* + * Bus faults can be caused by either a Bad Address or + * Target Abort. We should check the Received Target Abort + * bit of the PCI status register and Master Abort Bit. + * + * - Master Abort bit indicates that no device claimed + * the address with DEVSEL within five clocks + * + * - Target Abort bit indicates that a target claimed it, + * but changed its mind once it saw the byte enables. + * + */ + + if ((hostdata->chip / 100) == 8) { + save_flags (flags); + cli(); + tmp = pcibios_read_config_word (hostdata->pci_bus, + hostdata->pci_device_fn, PCI_STATUS, &pci_status); + restore_flags (flags); + if (tmp == PCIBIOS_SUCCESSFUL) { + if (pci_status & PCI_STATUS_REC_TARGET_ABORT) { + reason = "PCI target abort"; + pci_status &= ~PCI_STATUS_REC_TARGET_ABORT; + } else if (pci_status & PCI_STATUS_REC_MASTER_ABORT) { + reason = "No device asserted PCI DEVSEL within five bus clocks"; + pci_status &= ~PCI_STATUS_REC_MASTER_ABORT; + } else if (pci_status & PCI_STATUS_PARITY) { + report = 1; + pci_status &= ~PCI_STATUS_PARITY; + } + } else { + printk ("scsi%d : couldn't read status register : %s\n", + host->host_no, pcibios_strerror (tmp)); + retry = NEVER; + } + } + +#ifndef notyet + report = 1; +#endif + if (report && reason) { + printk(KERN_ALERT "scsi%d : BUS FAULT reason = %s\n", + host->host_no, reason ? reason : "unknown"); + print_lots (host); + } + +#ifndef notyet + retry = NEVER; +#endif + + /* + * TODO : we should attempt to recover from any spurious bus + * faults. After X retries, we should figure that things are + * sufficiently wedged, and call NCR53c7xx_reset. + * + * This code should only get executed once we've decided that we + * cannot retry. + */ + + if (retry == NEVER) { + printk(KERN_ALERT " mail drew@PoohSticks.ORG\n"); + FATAL (host); + } +} + +/* + * Function : static void intr_dma (struct Scsi_Host *host, + * struct NCR53c7x0_cmd *cmd) + * + * Purpose : handle all DMA interrupts, indicated by the setting + * of the DIP bit in the ISTAT register. + * + * Inputs : host, cmd - host and NCR command causing the interrupt, cmd + * may be NULL. + */ + +static void +intr_dma (struct Scsi_Host *host, struct NCR53c7x0_cmd *cmd) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned char dstat; /* DSTAT */ + u32 *dsp, + *next_dsp, /* Current dsp */ + *dsa, + dbc_dcmd; /* DCMD (high eight bits) + DBC */ + int tmp; + unsigned long flags; + NCR53c7x0_local_setup(host); + + if (!hostdata->dstat_valid) { + hostdata->dstat = NCR53c7x0_read8(DSTAT_REG); + hostdata->dstat_valid = 1; + } + + dstat = hostdata->dstat; + + if (hostdata->options & OPTION_DEBUG_INTR) + printk("scsi%d : DSTAT=0x%x\n", host->host_no, (int) dstat); + + dbc_dcmd = NCR53c7x0_read32 (DBC_REG); + next_dsp = bus_to_virt(NCR53c7x0_read32(DSP_REG)); + dsp = next_dsp - NCR53c7x0_insn_size ((dbc_dcmd >> 24) & 0xff); +/* XXX - check chip type */ + dsa = bus_to_virt(NCR53c7x0_read32(DSA_REG)); + + /* + * DSTAT_ABRT is the aborted interrupt. This is set whenever the + * SCSI chip is aborted. + * + * With NCR53c700 and NCR53c700-66 style chips, we should only + * get this when the chip is currently running the accept + * reselect/select code and we have set the abort bit in the + * ISTAT register. + * + */ + + if (dstat & DSTAT_ABRT) { +#if 0 + /* XXX - add code here to deal with normal abort */ + if ((hostdata->options & OPTION_700) && (hostdata->state == + STATE_ABORTING)) { + } else +#endif + { + printk(KERN_ALERT "scsi%d : unexpected abort interrupt at\n" + " ", host->host_no); + print_insn (host, dsp, KERN_ALERT "s ", 1); + FATAL (host); + } + } + + /* + * DSTAT_SSI is the single step interrupt. Should be generated + * whenever we have single stepped or are tracing. + */ + + if (dstat & DSTAT_SSI) { + if (hostdata->options & OPTION_DEBUG_TRACE) { + } else if (hostdata->options & OPTION_DEBUG_SINGLE) { + print_insn (host, dsp, "s ", 0); + save_flags(flags); + cli(); +/* XXX - should we do this, or can we get away with writing dsp? */ + + NCR53c7x0_write8 (DCNTL_REG, (NCR53c7x0_read8(DCNTL_REG) & + ~DCNTL_SSM) | DCNTL_STD); + restore_flags(flags); + } else { + printk(KERN_ALERT "scsi%d : unexpected single step interrupt at\n" + " ", host->host_no); + print_insn (host, dsp, KERN_ALERT "", 1); + printk(KERN_ALERT " mail drew@PoohSticks.ORG\n"); + FATAL (host); + } + } + + /* + * DSTAT_IID / DSTAT_OPC (same bit, same meaning, only the name + * is different) is generated whenever an illegal instruction is + * encountered. + * + * XXX - we may want to emulate INTFLY here, so we can use + * the same SCSI SCRIPT (tm) for NCR53c710 through NCR53c810 + * chips. + */ + + if (dstat & DSTAT_OPC) { + /* + * Ascertain if this IID interrupts occurred before or after a STO + * interrupt. Since the interrupt handling code now leaves + * DSP unmodified until _after_ all stacked interrupts have been + * processed, reading the DSP returns the original DSP register. + * This means that if dsp lies between the select code, and + * message out following the selection code (where the IID interrupt + * would have to have occurred by due to the implicit wait for REQ), + * we have an IID interrupt resulting from a STO condition and + * can ignore it. + */ + + if (((dsp >= (hostdata->script + hostdata->E_select / sizeof(u32))) && + (dsp <= (hostdata->script + hostdata->E_select_msgout / + sizeof(u32) + 8))) || (hostdata->test_running == 2)) { + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : ignoring DSTAT_IID for SSTAT_STO\n", + host->host_no); + if (hostdata->expecting_iid) { + hostdata->expecting_iid = 0; + hostdata->idle = 1; + if (hostdata->test_running == 2) { + hostdata->test_running = 0; + hostdata->test_completed = 3; + } else if (cmd) + abnormal_finished (cmd, DID_BAD_TARGET << 16); + } else { + hostdata->expecting_sto = 1; + } + /* + * We can't guarantee we'll be able to execute the WAIT DISCONNECT + * instruction within the 3.4us of bus free and arbitration delay + * that a target can RESELECT in and assert REQ after we've dropped + * ACK. If this happens, we'll get an illegal instruction interrupt. + * Doing away with the WAIT DISCONNECT instructions broke everything, + * so instead I'll settle for moving one WAIT DISCONNECT a few + * instructions closer to the CLEAR ACK before it to minimize the + * chances of this happening, and handle it if it occurs anyway. + * + * Simply continue with what we were doing, and control should + * be transfered to the schedule routine which will ultimately + * pass control onto the reselection or selection (not yet) + * code. + */ + } else if (dbc_dcmd == 0x48000000 && (NCR53c7x0_read8 (SBCL_REG) & + SBCL_REQ)) { + if (!(hostdata->options & OPTION_NO_PRINT_RACE)) + { + printk("scsi%d: REQ before WAIT DISCONNECT IID\n", + host->host_no); + hostdata->options |= OPTION_NO_PRINT_RACE; + } + } else { + printk(KERN_ALERT "scsi%d : illegal instruction\n", host->host_no); + print_lots (host); + printk(KERN_ALERT " mail drew@PoohSticks.ORG with ALL\n" + " boot messages and diagnostic output\n"); + FATAL (host); + } + } + + /* + * DSTAT_BF are bus fault errors + */ + + if (dstat & DSTAT_800_BF) { + intr_bf (host, cmd); + } + + + /* + * DSTAT_SIR interrupts are generated by the execution of + * the INT instruction. Since the exact values available + * are determined entirely by the SCSI script running, + * and are local to a particular script, a unique handler + * is called for each script. + */ + + if (dstat & DSTAT_SIR) { + if (hostdata->options & OPTION_DEBUG_INTR) + printk ("scsi%d : DSTAT_SIR\n", host->host_no); + switch ((tmp = hostdata->dstat_sir_intr (host, cmd))) { + case SPECIFIC_INT_NOTHING: + case SPECIFIC_INT_RESTART: + break; + case SPECIFIC_INT_ABORT: + abort_connected(host); + break; + case SPECIFIC_INT_PANIC: + printk(KERN_ALERT "scsi%d : failure at ", host->host_no); + print_insn (host, dsp, KERN_ALERT "", 1); + printk(KERN_ALERT " dstat_sir_intr() returned SPECIFIC_INT_PANIC\n"); + FATAL (host); + break; + case SPECIFIC_INT_BREAK: + intr_break (host, cmd); + break; + default: + printk(KERN_ALERT "scsi%d : failure at ", host->host_no); + print_insn (host, dsp, KERN_ALERT "", 1); + printk(KERN_ALERT" dstat_sir_intr() returned unknown value %d\n", + tmp); + FATAL (host); + } + } + + if ((hostdata->chip / 100) == 8 && (dstat & DSTAT_800_MDPE)) { + printk(KERN_ALERT "scsi%d : Master Data Parity Error\n", + host->host_no); + FATAL (host); + } +} + +/* + * Function : static int print_insn (struct Scsi_Host *host, + * u32 *insn, int kernel) + * + * Purpose : print numeric representation of the instruction pointed + * to by insn to the debugging or kernel message buffer + * as appropriate. + * + * If desired, a user level program can interpret this + * information. + * + * Inputs : host, insn - host, pointer to instruction, prefix - + * string to prepend, kernel - use printk instead of debugging buffer. + * + * Returns : size, in u32s, of instruction printed. + */ + +/* + * FIXME: should change kernel parameter so that it takes an ENUM + * specifying severity - either KERN_ALERT or KERN_PANIC so + * all panic messages are output with the same severity. + */ + +static int +print_insn (struct Scsi_Host *host, const u32 *insn, + const char *prefix, int kernel) { + char buf[160], /* Temporary buffer and pointer. ICKY + arbitrary length. */ + + + *tmp; + unsigned char dcmd; /* dcmd register for *insn */ + int size; + + /* + * Check to see if the instruction pointer is not bogus before + * indirecting through it; avoiding red-zone at start of + * memory. + * + * FIXME: icky magic needs to happen here on non-intel boxes which + * don't have kernel memory mapped in like this. Might be reasonable + * to use vverify()? + */ + + if (MAP_NR(insn) < 1 || MAP_NR(insn + 8) > MAP_NR(high_memory) || + ((((dcmd = (insn[0] >> 24) & 0xff) & DCMD_TYPE_MMI) == DCMD_TYPE_MMI) && + MAP_NR(insn + 12) > MAP_NR(high_memory))) { + size = 0; + sprintf (buf, "%s%p: address out of range\n", + prefix, insn); + } else { +/* + * FIXME : (void *) cast in virt_to_bus should be unnecessary, because + * it should take const void * as argument. + */ + sprintf(buf, "%s0x%lx (virt 0x%p) : 0x%08x 0x%08x (virt 0x%p)", + (prefix ? prefix : ""), virt_to_bus((void *) insn), insn, + insn[0], insn[1], bus_to_virt (insn[1])); + tmp = buf + strlen(buf); + if ((dcmd & DCMD_TYPE_MASK) == DCMD_TYPE_MMI) { + sprintf (tmp, " 0x%08x (virt 0x%p)\n", insn[2], + bus_to_virt(insn[2])); + size = 3; + } else { + sprintf (tmp, "\n"); + size = 2; + } + } + + if (kernel) + printk ("%s", buf); +#ifdef NCR_DEBUG + else { + size_t len = strlen(buf); + debugger_kernel_write(host, buf, len); + } +#endif + return size; +} + +/* + * Function : static const char *ncr_state (int state) + * + * Purpose : convert state (probably from hostdata->state) to a string + * + * Inputs : state + * + * Returns : char * representation of state, "unknown" on error. + */ + +static const char * +ncr_state (int state) { + switch (state) { + case STATE_HALTED: return "halted"; + case STATE_WAITING: return "waiting"; + case STATE_RUNNING: return "running"; + case STATE_ABORTING: return "aborting"; + case STATE_DISABLED: return "disabled"; + default: return "unknown"; + } +} + +/* + * Function : int NCR53c7xx_abort (Scsi_Cmnd *cmd) + * + * Purpose : Abort an errant SCSI command, doing all necessary + * cleanup of the issue_queue, running_list, shared Linux/NCR + * dsa issue and reconnect queues. + * + * Inputs : cmd - command to abort, code - entire result field + * + * Returns : 0 on success, -1 on failure. + */ + +int +NCR53c7xx_abort (Scsi_Cmnd *cmd) { + NCR53c7x0_local_declare(); + struct Scsi_Host *host = cmd->host; + struct NCR53c7x0_hostdata *hostdata = host ? (struct NCR53c7x0_hostdata *) + host->hostdata : NULL; + unsigned long flags; + struct NCR53c7x0_cmd *curr, **prev; + Scsi_Cmnd *me, **last; +#if 0 + static long cache_pid = -1; +#endif + + + if (!host) { + printk ("Bogus SCSI command pid %ld; no host structure\n", + cmd->pid); + return SCSI_ABORT_ERROR; + } else if (!hostdata) { + printk ("Bogus SCSI host %d; no hostdata\n", host->host_no); + return SCSI_ABORT_ERROR; + } + NCR53c7x0_local_setup(host); + +/* + * CHECK : I don't think that reading ISTAT will unstack any interrupts, + * since we need to write the INTF bit to clear it, and SCSI/DMA + * interrupts don't clear until we read SSTAT/SIST and DSTAT registers. + * + * See that this is the case. + * + * I suspect that several of our failures may be coming from a new fatal + * interrupt (possibly due to a phase mismatch) happening after we've left + * the interrupt handler, but before the PIC has had the interrupt condition + * cleared. + */ + + if (NCR53c7x0_read8(hostdata->istat) & + (ISTAT_DIP|ISTAT_SIP| + (hostdata->chip / 100 == 8 ? ISTAT_800_INTF : 0))) { + printk ("scsi%d : dropped interrupt for command %ld\n", host->host_no, + cmd->pid); + NCR53c7x0_intr (host->irq, NULL, NULL); + return SCSI_ABORT_BUSY; + } + + save_flags(flags); + cli(); +#if 0 + if (cache_pid == cmd->pid) + panic ("scsi%d : bloody fetus %d\n", host->host_no, cmd->pid); + else + cache_pid = cmd->pid; +#endif + + +/* + * The command could be hiding in the issue_queue. This would be very + * nice, as commands can't be moved from the high level driver's issue queue + * into the shared queue until an interrupt routine is serviced, and this + * moving is atomic. + * + * If this is the case, we don't have to worry about anything - we simply + * pull the command out of the old queue, and call it aborted. + */ + + for (me = (Scsi_Cmnd *) hostdata->issue_queue, + last = (Scsi_Cmnd **) &(hostdata->issue_queue); + me && me != cmd; last = (Scsi_Cmnd **)&(me->SCp.ptr), + me = (Scsi_Cmnd *)me->SCp.ptr); + + if (me) { + *last = (Scsi_Cmnd *) me->SCp.ptr; + if (me->host_scribble) { + ((struct NCR53c7x0_cmd *)me->host_scribble)->next = hostdata->free; + hostdata->free = (struct NCR53c7x0_cmd *) me->host_scribble; + me->host_scribble = NULL; + } + cmd->result = DID_ABORT << 16; + cmd->scsi_done(cmd); + printk ("scsi%d : found command %ld in Linux issue queue\n", + host->host_no, me->pid); + restore_flags(flags); + run_process_issue_queue(); + return SCSI_ABORT_SUCCESS; + } + +/* + * That failing, the command could be in our list of already executing + * commands. If this is the case, drastic measures are called for. + */ + + for (curr = (struct NCR53c7x0_cmd *) hostdata->running_list, + prev = (struct NCR53c7x0_cmd **) &(hostdata->running_list); + curr && curr->cmd != cmd; prev = (struct NCR53c7x0_cmd **) + &(curr->next), curr = (struct NCR53c7x0_cmd *) curr->next); + + if (curr) { + if ((cmd->result & 0xff) != 0xff && (cmd->result & 0xff00) != 0xff00) { + if (prev) + *prev = (struct NCR53c7x0_cmd *) curr->next; + curr->next = (struct NCR53c7x0_cmd *) hostdata->free; + cmd->host_scribble = NULL; + hostdata->free = curr; + cmd->scsi_done(cmd); + printk ("scsi%d : found finished command %ld in running list\n", + host->host_no, cmd->pid); + restore_flags(flags); + return SCSI_ABORT_NOT_RUNNING; + } else { + printk ("scsi%d : DANGER : command running, can not abort.\n", + cmd->host->host_no); + restore_flags(flags); + return SCSI_ABORT_BUSY; + } + } + +/* + * And if we couldn't find it in any of our queues, it must have been + * a dropped interrupt. + */ + + curr = (struct NCR53c7x0_cmd *) cmd->host_scribble; + if (curr) { + curr->next = hostdata->free; + hostdata->free = curr; + cmd->host_scribble = NULL; + } + + if (((cmd->result & 0xff00) == 0xff00) || + ((cmd->result & 0xff) == 0xff)) { + printk ("scsi%d : did this command ever run?\n", host->host_no); + cmd->result = DID_ABORT << 16; + } else { + printk ("scsi%d : probably lost INTFLY, normal completion\n", + host->host_no); +/* + * FIXME : We need to add an additional flag which indicates if a + * command was ever counted as BUSY, so if we end up here we can + * decrement the busy count if and only if it is necessary. + */ + --hostdata->busy[cmd->target][cmd->lun]; + } + restore_flags(flags); + cmd->scsi_done(cmd); + +/* + * We need to run process_issue_queue since termination of this command + * may allow another queued command to execute first? + */ + return SCSI_ABORT_NOT_RUNNING; +} + +/* + * Function : int NCR53c7xx_reset (Scsi_Cmnd *cmd) + * + * Purpose : perform a hard reset of the SCSI bus and NCR + * chip. + * + * Inputs : cmd - command which caused the SCSI RESET + * + * Returns : 0 on success. + */ + +int +NCR53c7xx_reset (Scsi_Cmnd *cmd, unsigned int reset_flags) { + NCR53c7x0_local_declare(); + unsigned long flags; + int found = 0; + struct NCR53c7x0_cmd * c; + Scsi_Cmnd *tmp; + /* + * When we call scsi_done(), it's going to wake up anything sleeping on the + * resources which were in use by the aborted commands, and we'll start to + * get new commands. + * + * We can't let this happen until after we've re-initialized the driver + * structures, and can't reinitialize those structures until after we've + * dealt with their contents. + * + * So, we need to find all of the commands which were running, stick + * them on a linked list of completed commands (we'll use the host_scribble + * pointer), do our reinitialization, and then call the done function for + * each command. + */ + Scsi_Cmnd *nuke_list = NULL; + struct Scsi_Host *host = cmd->host; + struct NCR53c7x0_hostdata *hostdata = + (struct NCR53c7x0_hostdata *) host->hostdata; + + NCR53c7x0_local_setup(host); + save_flags(flags); + cli(); + ncr_halt (host); + print_lots (host); + dump_events (host, 30); + ncr_scsi_reset (host); + for (tmp = nuke_list = return_outstanding_commands (host, 1 /* free */, + 0 /* issue */ ); tmp; tmp = (Scsi_Cmnd *) tmp->SCp.buffer) + if (tmp == cmd) { + found = 1; + break; + } + + /* + * If we didn't find the command which caused this reset in our running + * list, then we've lost it. See that it terminates normally anyway. + */ + if (!found) { + c = (struct NCR53c7x0_cmd *) cmd->host_scribble; + if (c) { + cmd->host_scribble = NULL; + c->next = hostdata->free; + hostdata->free = c; + } else + printk ("scsi%d: lost command %ld\n", host->host_no, cmd->pid); + cmd->SCp.buffer = (struct scatterlist *) nuke_list; + nuke_list = cmd; + } + + NCR53c7x0_driver_init (host); + hostdata->soft_reset (host); + if (hostdata->resets == 0) + disable(host); + else if (hostdata->resets != -1) + --hostdata->resets; + sti(); + for (; nuke_list; nuke_list = tmp) { + tmp = (Scsi_Cmnd *) nuke_list->SCp.buffer; + nuke_list->result = DID_RESET << 16; + nuke_list->scsi_done (nuke_list); + } + restore_flags(flags); + return SCSI_RESET_SUCCESS; +} + +/* + * The NCR SDMS bios follows Annex A of the SCSI-CAM draft, and + * therefore shares the scsicam_bios_param function. + */ + +/* + * Function : int insn_to_offset (Scsi_Cmnd *cmd, u32 *insn) + * + * Purpose : convert instructions stored at NCR pointer into data + * pointer offset. + * + * Inputs : cmd - SCSI command; insn - pointer to instruction. Either current + * DSP, or saved data pointer. + * + * Returns : offset on success, -1 on failure. + */ + + +static int +insn_to_offset (Scsi_Cmnd *cmd, u32 *insn) { + struct NCR53c7x0_hostdata *hostdata = + (struct NCR53c7x0_hostdata *) cmd->host->hostdata; + struct NCR53c7x0_cmd *ncmd = + (struct NCR53c7x0_cmd *) cmd->host_scribble; + int offset = 0, buffers; + struct scatterlist *segment; + char *ptr; + int found = 0; + +/* + * With the current code implementation, if the insn is inside dynamically + * generated code, the data pointer will be the instruction preceding + * the next transfer segment. + */ + + if (!check_address ((unsigned long) ncmd, sizeof (struct NCR53c7x0_cmd)) && + ((insn >= ncmd->data_transfer_start && + insn < ncmd->data_transfer_end) || + (insn >= ncmd->residual && + insn < (ncmd->residual + + sizeof(ncmd->residual))))) { + ptr = bus_to_virt(insn[3]); + + if ((buffers = cmd->use_sg)) { + for (offset = 0, + segment = (struct scatterlist *) cmd->buffer; + buffers && !((found = ((ptr >= segment->address) && + (ptr < (segment->address + segment->length))))); + --buffers, offset += segment->length, ++segment) +#if 0 + printk("scsi%d: comparing 0x%p to 0x%p\n", + cmd->host->host_no, saved, segment->address); +#else + ; +#endif + offset += ptr - segment->address; + } else { + found = 1; + offset = ptr - (char *) (cmd->request_buffer); + } + } else if ((insn >= hostdata->script + + hostdata->E_data_transfer / sizeof(u32)) && + (insn <= hostdata->script + + hostdata->E_end_data_transfer / sizeof(u32))) { + found = 1; + offset = 0; + } + return found ? offset : -1; +} + + + +/* + * Function : void print_progress (Scsi_Cmnd *cmd) + * + * Purpose : print the current location of the saved data pointer + * + * Inputs : cmd - command we are interested in + * + */ + +static void +print_progress (Scsi_Cmnd *cmd) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_cmd *ncmd = + (struct NCR53c7x0_cmd *) cmd->host_scribble; + int offset, i; + char *where; + u32 *ptr; + NCR53c7x0_local_setup (cmd->host); + for (i = 0; i < 2; ++i) { + if (check_address ((unsigned long) ncmd, + sizeof (struct NCR53c7x0_cmd)) == -1) + continue; + if (!i) { + where = "saved"; + ptr = bus_to_virt(ncmd->saved_data_pointer); + } else { + where = "active"; + ptr = bus_to_virt (NCR53c7x0_read32 (DSP_REG) - + NCR53c7x0_insn_size (NCR53c7x0_read8 (DCMD_REG)) * + sizeof(u32)); + } + offset = insn_to_offset (cmd, ptr); + + if (offset != -1) + printk ("scsi%d : %s data pointer at offset %d\n", + cmd->host->host_no, where, offset); + else { + int size; + printk ("scsi%d : can't determine %s data pointer offset\n", + cmd->host->host_no, where); + if (ncmd) { + size = print_insn (cmd->host, + bus_to_virt(ncmd->saved_data_pointer), "", 1); + print_insn (cmd->host, + bus_to_virt(ncmd->saved_data_pointer) + size * sizeof(u32), + "", 1); + } + } + } +} + + +static void +print_dsa (struct Scsi_Host *host, u32 *dsa, const char *prefix) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int i, len; + char *ptr; + Scsi_Cmnd *cmd; + + if (check_address ((unsigned long) dsa, hostdata->dsa_end - + hostdata->dsa_start) == -1) { + printk("scsi%d : bad dsa virt 0x%p\n", host->host_no, dsa); + return; + } + printk("%sscsi%d : dsa at phys 0x%lx (virt 0x%p)\n" + " + %d : dsa_msgout length = %u, data = 0x%x (virt 0x%p)\n" , + prefix ? prefix : "", + host->host_no, virt_to_bus (dsa), dsa, hostdata->dsa_msgout, + dsa[hostdata->dsa_msgout / sizeof(u32)], + dsa[hostdata->dsa_msgout / sizeof(u32) + 1], + bus_to_virt (dsa[hostdata->dsa_msgout / sizeof(u32) + 1])); + + /* + * Only print messages if they're sane in length so we don't + * blow the kernel printk buffer on something which won't buy us + * anything. + */ + + if (dsa[hostdata->dsa_msgout / sizeof(u32)] < + sizeof (hostdata->free->select)) + for (i = dsa[hostdata->dsa_msgout / sizeof(u32)], + ptr = bus_to_virt (dsa[hostdata->dsa_msgout / sizeof(u32) + 1]); + i > 0 && !check_address ((unsigned long) ptr, 1); + ptr += len, i -= len) { + printk(" "); + len = print_msg (ptr); + printk("\n"); + if (!len) + break; + } + + printk(" + %d : select_indirect = 0x%x\n", + hostdata->dsa_select, dsa[hostdata->dsa_select / sizeof(u32)]); + cmd = (Scsi_Cmnd *) bus_to_virt(dsa[hostdata->dsa_cmnd / sizeof(u32)]); + printk(" + %d : dsa_cmnd = 0x%x ", hostdata->dsa_cmnd, + (u32) virt_to_bus(cmd)); + if (cmd) { + printk(" result = 0x%x, target = %d, lun = %d, cmd = ", + cmd->result, cmd->target, cmd->lun); + print_command(cmd->cmnd); + } else + printk("\n"); + printk(" + %d : dsa_next = 0x%x\n", hostdata->dsa_next, + dsa[hostdata->dsa_next / sizeof(u32)]); + if (cmd) { + printk("scsi%d target %d : sxfer_sanity = 0x%x, scntl3_sanity = 0x%x\n" + " script : ", + host->host_no, cmd->target, + hostdata->sync[cmd->target].sxfer_sanity, + hostdata->sync[cmd->target].scntl3_sanity); + for (i = 0; i < (sizeof(hostdata->sync[cmd->target].script) / 4); ++i) + printk ("0x%x ", hostdata->sync[cmd->target].script[i]); + printk ("\n"); + print_progress (cmd); + } +} +/* + * Function : void print_queues (Scsi_Host *host) + * + * Purpose : print the contents of the NCR issue and reconnect queues + * + * Inputs : host - SCSI host we are interested in + * + */ + +static void +print_queues (struct Scsi_Host *host) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + u32 *dsa, *next_dsa; + volatile u32 *current; + int left; + Scsi_Cmnd *cmd, *next_cmd; + unsigned long flags; + + printk ("scsi%d : issue queue\n", host->host_no); + + for (left = host->can_queue, cmd = (Scsi_Cmnd *) hostdata->issue_queue; + left >= 0 && cmd; + cmd = next_cmd) { + next_cmd = (Scsi_Cmnd *) cmd->SCp.ptr; + save_flags(flags); + cli(); + if (cmd->host_scribble) { + if (check_address ((unsigned long) (cmd->host_scribble), + sizeof (cmd->host_scribble)) == -1) + printk ("scsi%d: scsi pid %ld bad pointer to NCR53c7x0_cmd\n", + host->host_no, cmd->pid); + /* print_dsa does sanity check on address, no need to check */ + else + print_dsa (host, ((struct NCR53c7x0_cmd *) cmd->host_scribble) + -> dsa, ""); + } else + printk ("scsi%d : scsi pid %ld for target %d lun %d has no NCR53c7x0_cmd\n", + host->host_no, cmd->pid, cmd->target, cmd->lun); + restore_flags(flags); + } + + if (left <= 0) { + printk ("scsi%d : loop detected in issue queue\n", + host->host_no); + } + + /* + * Traverse the NCR reconnect and start DSA structures, printing out + * each element until we hit the end or detect a loop. Currently, + * the reconnect structure is a linked list; and the start structure + * is an array. Eventually, the reconnect structure will become a + * list as well, since this simplifies the code. + */ + + printk ("scsi%d : schedule dsa array :\n", host->host_no); + for (left = host->can_queue, current = hostdata->schedule; + left > 0; current += 2, --left) + if (current[0] != hostdata->NOP_insn) +/* FIXME : convert pointer to dsa_begin to pointer to dsa. */ + print_dsa (host, bus_to_virt (current[1] - + (hostdata->E_dsa_code_begin - + hostdata->E_dsa_code_template)), ""); + printk ("scsi%d : end schedule dsa array\n", host->host_no); + + printk ("scsi%d : reconnect_dsa_head :\n", host->host_no); + + for (left = host->can_queue, + dsa = bus_to_virt (hostdata->reconnect_dsa_head); + left >= 0 && dsa; + dsa = next_dsa) { + save_flags (flags); + cli(); + if (check_address ((unsigned long) dsa, sizeof(dsa)) == -1) { + printk ("scsi%d: bad DSA pointer 0x%p", host->host_no, + dsa); + next_dsa = NULL; + } + else + { + next_dsa = bus_to_virt(dsa[hostdata->dsa_next / sizeof(u32)]); + print_dsa (host, dsa, ""); + } + restore_flags(flags); + } + printk ("scsi%d : end reconnect_dsa_head\n", host->host_no); + if (left < 0) + printk("scsi%d: possible loop in ncr reconnect list\n", + host->host_no); +} + +static void +print_lots (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = + (struct NCR53c7x0_hostdata *) host->hostdata; + u32 *dsp_next, *dsp, *dsa, dbc_dcmd; + unsigned char dcmd, sbcl; + int i, size; + NCR53c7x0_local_setup(host); + + if ((dsp_next = bus_to_virt(NCR53c7x0_read32 (DSP_REG)))) { + dbc_dcmd = NCR53c7x0_read32(DBC_REG); + dcmd = (dbc_dcmd & 0xff000000) >> 24; + dsp = dsp_next - NCR53c7x0_insn_size(dcmd); + dsa = bus_to_virt(NCR53c7x0_read32(DSA_REG)); + sbcl = NCR53c7x0_read8 (SBCL_REG); + + + printk ("scsi%d : DCMD|DBC=0x%x, DNAD=0x%x (virt 0x%p)\n" + " DSA=0x%lx (virt 0x%p)\n" + " DSPS=0x%x, TEMP=0x%x (virt 0x%p), DMODE=0x%x\n" + " SXFER=0x%x, SCNTL3=0x%x\n" + " %s%s%sphase=%s, %d bytes in SCSI FIFO\n" + " STEST0=0x%x\n", + host->host_no, dbc_dcmd, NCR53c7x0_read32(DNAD_REG), + bus_to_virt(NCR53c7x0_read32(DNAD_REG)), + virt_to_bus(dsa), dsa, + NCR53c7x0_read32(DSPS_REG), NCR53c7x0_read32(TEMP_REG), + bus_to_virt (NCR53c7x0_read32(TEMP_REG)), + (int) NCR53c7x0_read8(hostdata->dmode), + (int) NCR53c7x0_read8(SXFER_REG), + (int) NCR53c7x0_read8(SCNTL3_REG_800), + (sbcl & SBCL_BSY) ? "BSY " : "", + (sbcl & SBCL_SEL) ? "SEL " : "", + (sbcl & SBCL_REQ) ? "REQ " : "", + sstat2_to_phase(NCR53c7x0_read8 (((hostdata->chip / 100) == 8) ? + SSTAT1_REG : SSTAT2_REG)), + (NCR53c7x0_read8 ((hostdata->chip / 100) == 8 ? + SSTAT1_REG : SSTAT2_REG) & SSTAT2_FF_MASK) >> SSTAT2_FF_SHIFT, + NCR53c7x0_read8 (STEST0_REG_800)); + printk ("scsi%d : DSP 0x%lx (virt 0x%p) ->\n", host->host_no, + virt_to_bus(dsp), dsp); + for (i = 6; i > 0; --i, dsp += size) + size = print_insn (host, dsp, "", 1); + if (NCR53c7x0_read8 (SCNTL1_REG) & SCNTL1_CON) { + printk ("scsi%d : connected (SDID=0x%x, SSID=0x%x)\n", + host->host_no, NCR53c7x0_read8 (SDID_REG_800), + NCR53c7x0_read8 (SSID_REG_800)); + print_dsa (host, dsa, ""); + } + +#if 1 + print_queues (host); +#endif + } +} + +/* + * Function : static int shutdown (struct Scsi_Host *host) + * + * Purpose : does a clean (we hope) shutdown of the NCR SCSI + * chip. Use prior to dumping core, unloading the NCR driver, + * + * Returns : 0 on success + */ +static int +shutdown (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + unsigned long flags; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + NCR53c7x0_local_setup(host); + save_flags (flags); + cli(); +/* Get in a state where we can reset the SCSI bus */ + ncr_halt (host); + ncr_scsi_reset (host); + hostdata->soft_reset(host); + + disable (host); + restore_flags (flags); + return 0; +} + +/* + * Function : void ncr_scsi_reset (struct Scsi_Host *host) + * + * Purpose : reset the SCSI bus. + */ + +static void +ncr_scsi_reset (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned long flags; + int sien = 0; + NCR53c7x0_local_setup(host); + save_flags (flags); + cli(); + if ((hostdata->chip / 100) == 8) { + sien = NCR53c7x0_read8(SIEN0_REG_800); + NCR53c7x0_write8(SIEN0_REG_800, sien & ~SIEN_RST); + } + NCR53c7x0_write8(SCNTL1_REG, SCNTL1_RST); + udelay(25); /* Minimum amount of time to assert RST */ + NCR53c7x0_write8(SCNTL1_REG, 0); + if ((hostdata->chip / 100) == 8) { + NCR53c7x0_write8(SIEN0_REG_800, sien); + } + restore_flags (flags); +} + +/* + * Function : void hard_reset (struct Scsi_Host *host) + * + */ + +static void +hard_reset (struct Scsi_Host *host) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned long flags; + save_flags (flags); + cli(); + ncr_scsi_reset(host); + NCR53c7x0_driver_init (host); + if (hostdata->soft_reset) + hostdata->soft_reset (host); + restore_flags(flags); +} + + +/* + * Function : Scsi_Cmnd *return_outstanding_commands (struct Scsi_Host *host, + * int free, int issue) + * + * Purpose : return a linked list (using the SCp.buffer field as next, + * so we don't perturb hostdata. We don't use a field of the + * NCR53c7x0_cmd structure since we may not have allocated one + * for the command causing the reset.) of Scsi_Cmnd structures that + * had propagated below the Linux issue queue level. If free is set, + * free the NCR53c7x0_cmd structures which are associated with + * the Scsi_Cmnd structures, and clean up any internal + * NCR lists that the commands were on. If issue is set, + * also return commands in the issue queue. + * + * Returns : linked list of commands + * + * NOTE : the caller should insure that the NCR chip is halted + * if the free flag is set. + */ + +static Scsi_Cmnd * +return_outstanding_commands (struct Scsi_Host *host, int free, int issue) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + struct NCR53c7x0_cmd *c; + int i; + u32 *current; + Scsi_Cmnd *list = NULL, *tmp; + for (c = (struct NCR53c7x0_cmd *) hostdata->running_list; c; + c = (struct NCR53c7x0_cmd *) c->next) { + if (c->cmd->SCp.buffer) { + printk ("scsi%d : loop detected in running list!\n", host->host_no); + break; + } else { + printk ("The sti() implicit in a printk() prevents hangs\n"); + break; + } + + c->cmd->SCp.buffer = (struct scatterlist *) list; + list = c->cmd; + if (free) { + c->next = hostdata->free; + hostdata->free = c; + } + } + + if (free) { + for (i = 0, current = (u32 *) hostdata->schedule; + i < host->can_queue; ++i, current += 2) { + current[0] = hostdata->NOP_insn; + current[1] = 0xdeadbeef; + } + hostdata->current = NULL; + } + + if (issue) { + for (tmp = (Scsi_Cmnd *) hostdata->issue_queue; tmp; tmp = tmp->next) { + if (tmp->SCp.buffer) { + printk ("scsi%d : loop detected in issue queue!\n", + host->host_no); + break; + } + tmp->SCp.buffer = (struct scatterlist *) list; + list = tmp; + } + if (free) + hostdata->issue_queue = NULL; + + } + return list; +} + +/* + * Function : static int disable (struct Scsi_Host *host) + * + * Purpose : disables the given NCR host, causing all commands + * to return a driver error. Call this so we can unload the + * module during development and try again. Eventually, + * we should be able to find clean workarounds for these + * problems. + * + * Inputs : host - hostadapter to twiddle + * + * Returns : 0 on success. + */ + +static int +disable (struct Scsi_Host *host) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + unsigned long flags; + Scsi_Cmnd *nuke_list, *tmp; + save_flags(flags); + cli(); + if (hostdata->state != STATE_HALTED) + ncr_halt (host); + nuke_list = return_outstanding_commands (host, 1 /* free */, 1 /* issue */); + hard_reset (host); + hostdata->state = STATE_DISABLED; + restore_flags(flags); + printk ("scsi%d : nuking commands\n", host->host_no); + for (; nuke_list; nuke_list = tmp) { + tmp = (Scsi_Cmnd *) nuke_list->SCp.buffer; + nuke_list->result = DID_ERROR << 16; + nuke_list->scsi_done(nuke_list); + } + printk ("scsi%d : done. \n", host->host_no); + printk (KERN_ALERT "scsi%d : disabled. Unload and reload\n", + host->host_no); + return 0; +} + +/* + * Function : static int ncr_halt (struct Scsi_Host *host) + * + * Purpose : halts the SCSI SCRIPTS(tm) processor on the NCR chip + * + * Inputs : host - SCSI chip to halt + * + * Returns : 0 on success + */ + +static int +ncr_halt (struct Scsi_Host *host) { + NCR53c7x0_local_declare(); + unsigned long flags; + unsigned char istat, tmp; + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + int stage; + NCR53c7x0_local_setup(host); + + save_flags(flags); + cli(); + /* Stage 0 : eat all interrupts + Stage 1 : set ABORT + Stage 2 : eat all but abort interrupts + Stage 3 : eat all interrupts + */ + for (stage = 0;;) { + if (stage == 1) { + NCR53c7x0_write8(hostdata->istat, ISTAT_ABRT); + ++stage; + } + istat = NCR53c7x0_read8 (hostdata->istat); + if (istat & ISTAT_SIP) { + if ((hostdata->chip / 100) == 8) { + tmp = NCR53c7x0_read8(SIST0_REG_800); + udelay(1); + tmp = NCR53c7x0_read8(SIST1_REG_800); + } else { + tmp = NCR53c7x0_read8(SSTAT0_REG); + } + } else if (istat & ISTAT_DIP) { + tmp = NCR53c7x0_read8(DSTAT_REG); + if (stage == 2) { + if (tmp & DSTAT_ABRT) { + NCR53c7x0_write8(hostdata->istat, 0); + ++stage; + } else { + printk(KERN_ALERT "scsi%d : could not halt NCR chip\n", + host->host_no); + disable (host); + } + } + } + if (!(istat & (ISTAT_SIP|ISTAT_DIP))) + if (stage == 0) + ++stage; + else if (stage == 3) + break; + } + hostdata->state = STATE_HALTED; + restore_flags(flags); +#if 0 + print_lots (host); +#endif + return 0; +} + +/* + * Function: event_name (int event) + * + * Purpose: map event enum into user-readable strings. + */ + +static const char * +event_name (int event) { + switch (event) { + case EVENT_NONE: return "none"; + case EVENT_ISSUE_QUEUE: return "to issue queue"; + case EVENT_START_QUEUE: return "to start queue"; + case EVENT_SELECT: return "selected"; + case EVENT_DISCONNECT: return "disconnected"; + case EVENT_RESELECT: return "reselected"; + case EVENT_COMPLETE: return "completed"; + case EVENT_IDLE: return "idle"; + case EVENT_SELECT_FAILED: return "select failed"; + case EVENT_BEFORE_SELECT: return "before select"; + case EVENT_RESELECT_FAILED: return "reselect failed"; + default: return "unknown"; + } +} + +/* + * Function : void dump_events (struct Scsi_Host *host, count) + * + * Purpose : print last count events which have occurred. + */ +static void +dump_events (struct Scsi_Host *host, int count) { + struct NCR53c7x0_hostdata *hostdata = (struct NCR53c7x0_hostdata *) + host->hostdata; + struct NCR53c7x0_event event; + int i; + unsigned long flags; + if (hostdata->events) { + if (count > hostdata->event_size) + count = hostdata->event_size; + for (i = hostdata->event_index; count > 0; + i = (i ? i - 1 : hostdata->event_size -1), --count) { + save_flags(flags); +/* + * By copying the event we're currently examining with interrupts + * disabled, we can do multiple printk(), etc. operations and + * still be guaranteed that they're happening on the same + * event structure. + */ + cli(); +#if 0 + event = hostdata->events[i]; +#else + memcpy ((void *) &event, (void *) &(hostdata->events[i]), + sizeof(event)); +#endif + + restore_flags(flags); + printk ("scsi%d : %s event %d at %ld secs %ld usecs target %d lun %d\n", + host->host_no, event_name (event.event), count, + (long) event.time.tv_sec, (long) event.time.tv_usec, + event.target, event.lun); + if (event.dsa) + printk (" event for dsa 0x%lx (virt 0x%p)\n", + virt_to_bus(event.dsa), event.dsa); + if (event.pid != -1) { + printk (" event for pid %ld ", event.pid); + print_command (event.cmnd); + } + } + } +} + +/* + * Function: check_address + * + * Purpose: Check to see if a possibly corrupt pointer will fault the + * kernel. + * + * Inputs: addr - address; size - size of area + * + * Returns: 0 if area is OK, -1 on error. + * + * NOTES: should be implemented in terms of vverify on kernels + * that have it. + */ + +static int +check_address (unsigned long addr, int size) { + return (MAP_NR(addr) < 1 || MAP_NR(addr + size) > MAP_NR(high_memory) ? + -1 : 0); +} + +#ifdef MODULE +int +NCR53c7x0_release(struct Scsi_Host *host) { + struct NCR53c7x0_hostdata *hostdata = + (struct NCR53c7x0_hostdata *) host->hostdata; + struct NCR53c7x0_cmd *cmd, *tmp; + shutdown (host); + if (host->irq != IRQ_NONE) + { + int irq_count; + struct Scsi_Host *tmp; + for (irq_count = 0, tmp = first_host; tmp; tmp = tmp->next) + if (tmp->hostt == the_template && tmp->irq == host->irq) + ++irq_count; + if (irq_count == 1) + free_irq(host->irq, NULL); + } + if (host->dma_channel != DMA_NONE) + free_dma(host->dma_channel); + if (host->io_port) + release_region(host->io_port, host->n_io_port); + + for (cmd = (struct NCR53c7x0_cmd *) hostdata->free; cmd; cmd = tmp, + --hostdata->num_cmds) { + tmp = (struct NCR53c7x0_cmd *) cmd->next; + /* + * If we're going to loop, try to stop it to get a more accurate + * count of the leaked commands. + */ + cmd->next = NULL; + if (cmd->free) + cmd->free ((void *) cmd->real, cmd->size); + } + if (hostdata->num_cmds) + printk ("scsi%d : leaked %d NCR53c7x0_cmd structures\n", + host->host_no, hostdata->num_cmds); + if (hostdata->events) + vfree ((void *)hostdata->events); + return 1; +} +Scsi_Host_Template driver_template = NCR53c7xx; +#include "scsi_module.c" +#endif /* def MODULE */ diff --git a/linux/src/drivers/scsi/53c8xx_d.h b/linux/src/drivers/scsi/53c8xx_d.h new file mode 100644 index 00000000..b5863406 --- /dev/null +++ b/linux/src/drivers/scsi/53c8xx_d.h @@ -0,0 +1,2677 @@ +u32 SCRIPT[] = { +/* + + +; NCR 53c810 driver, main script +; Sponsored by +; iX Multiuser Multitasking Magazine +; hm@ix.de +; +; Copyright 1993, 1994, 1995 Drew Eckhardt +; Visionary Computing +; (Unix and Linux consulting and custom programming) +; drew@PoohSticks.ORG +; +1 (303) 786-7975 +; +; TolerANT and SCSI SCRIPTS are registered trademarks of NCR Corporation. +; +; PRE-ALPHA +; +; For more information, please consult +; +; NCR 53C810 +; PCI-SCSI I/O Processor +; Data Manual +; +; NCR 53C710 +; SCSI I/O Processor +; Programmers Guide +; +; NCR Microelectronics +; 1635 Aeroplaza Drive +; Colorado Springs, CO 80916 +; 1+ (719) 578-3400 +; +; Toll free literature number +; +1 (800) 334-5454 +; +; IMPORTANT : This code is self modifying due to the limitations of +; the NCR53c7,8xx series chips. Persons debugging this code with +; the remote debugger should take this into account, and NOT set +; breakpoints in modified instructions. +; +; Design: +; The NCR53c7,8xx family of SCSI chips are busmasters with an onboard +; microcontroller using a simple instruction set. +; +; So, to minimize the effects of interrupt latency, and to maximize +; throughput, this driver offloads the practical maximum amount +; of processing to the SCSI chip while still maintaining a common +; structure. +; +; Where tradeoffs were needed between efficiency on the older +; chips and the newer NCR53c800 series, the NCR53c800 series +; was chosen. +; +; While the NCR53c700 and NCR53c700-66 lacked the facilities to fully +; automate SCSI transfers without host processor intervention, this +; isn't the case with the NCR53c710 and newer chips which allow +; +; - reads and writes to the internal registers from within the SCSI +; scripts, allowing the SCSI SCRIPTS(tm) code to save processor +; state so that multiple threads of execution are possible, and also +; provide an ALU for loop control, etc. +; +; - table indirect addressing for some instructions. This allows +; pointers to be located relative to the DSA ((Data Structure +; Address) register. +; +; These features make it possible to implement a mailbox style interface, +; where the same piece of code is run to handle I/O for multiple threads +; at once minimizing our need to relocate code. Since the NCR53c700/ +; NCR53c800 series have a unique combination of features, making a +; a standard ingoing/outgoing mailbox system, costly, I've modified it. +; +; - Mailboxes are a mixture of code and data. This lets us greatly +; simplify the NCR53c810 code and do things that would otherwise +; not be possible. +; +; The saved data pointer is now implemented as follows : +; +; Control flow has been architected such that if control reaches +; munge_save_data_pointer, on a restore pointers message or +; reconnection, a jump to the address formerly in the TEMP register +; will allow the SCSI command to resume execution. +; + +; +; Note : the DSA structures must be aligned on 32 bit boundaries, +; since the source and destination of MOVE MEMORY instructions +; must share the same alignment and this is the alignment of the +; NCR registers. +; + +ABSOLUTE dsa_temp_lun = 0 ; Patch to lun for current dsa +ABSOLUTE dsa_temp_next = 0 ; Patch to dsa next for current dsa +ABSOLUTE dsa_temp_addr_next = 0 ; Patch to address of dsa next address + ; for current dsa +ABSOLUTE dsa_temp_sync = 0 ; Patch to address of per-target + ; sync routine +ABSOLUTE dsa_temp_target = 0 ; Patch to id for current dsa +ABSOLUTE dsa_temp_addr_saved_pointer = 0; Patch to address of per-command + ; saved data pointer +ABSOLUTE dsa_temp_addr_residual = 0 ; Patch to address of per-command + ; current residual code +ABSOLUTE dsa_temp_addr_saved_residual = 0; Patch to address of per-command + ; saved residual code +ABSOLUTE dsa_temp_addr_new_value = 0 ; Address of value for JUMP operand +ABSOLUTE dsa_temp_addr_array_value = 0 ; Address to copy to +ABSOLUTE dsa_temp_addr_dsa_value = 0 ; Address of this DSA value + +; +; Once a device has initiated reselection, we need to compare it +; against the singly linked list of commands which have disconnected +; and are pending reselection. These commands are maintained in +; an unordered singly linked list of DSA structures, through the +; DSA pointers at their 'centers' headed by the reconnect_dsa_head +; pointer. +; +; To avoid complications in removing commands from the list, +; I minimize the amount of expensive (at eight operations per +; addition @ 500-600ns each) pointer operations which must +; be done in the NCR driver by precomputing them on the +; host processor during dsa structure generation. +; +; The fixed-up per DSA code knows how to recognize the nexus +; associated with the corresponding SCSI command, and modifies +; the source and destination pointers for the MOVE MEMORY +; instruction which is executed when reselected_ok is called +; to remove the command from the list. Similarly, DSA is +; loaded with the address of the next DSA structure and +; reselected_check_next is called if a failure occurs. +; +; Perhaps more concisely, the net effect of the mess is +; +; for (dsa = reconnect_dsa_head, dest = &reconnect_dsa_head, +; src = NULL; dsa; dest = &dsa->next, dsa = dsa->next) { +; src = &dsa->next; +; if (target_id == dsa->id && target_lun == dsa->lun) { +; *dest = *src; +; break; +; } +; } +; +; if (!dsa) +; error (int_err_unexpected_reselect); +; else +; longjmp (dsa->jump_resume, 0); +; +; + + +; Define DSA structure used for mailboxes +ENTRY dsa_code_template +dsa_code_template: +ENTRY dsa_code_begin +dsa_code_begin: + MOVE dmode_memory_to_ncr TO DMODE + +at 0x00000000 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, dsa_temp_addr_dsa_value, addr_scratch + +at 0x00000002 : */ 0xc0000004,0x00000000,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000005 : */ 0x78380000,0x00000000, +/* + CALL scratch_to_dsa + +at 0x00000007 : */ 0x88080000,0x00000980, +/* + CALL select + +at 0x00000009 : */ 0x88080000,0x000001fc, +/* +; Handle the phase mismatch which may have resulted from the +; MOVE FROM dsa_msgout if we returned here. The CLEAR ATN +; may or may not be necessary, and we should update script_asm.pl +; to handle multiple pieces. + CLEAR ATN + +at 0x0000000b : */ 0x60000008,0x00000000, +/* + CLEAR ACK + +at 0x0000000d : */ 0x60000040,0x00000000, +/* + +; Replace second operand with address of JUMP instruction dest operand +; in schedule table for this DSA. Becomes dsa_jump_dest in 53c7,8xx.c. +ENTRY dsa_code_fix_jump +dsa_code_fix_jump: + MOVE MEMORY 4, NOP_insn, 0 + +at 0x0000000f : */ 0xc0000004,0x00000000,0x00000000, +/* + JUMP select_done + +at 0x00000012 : */ 0x80080000,0x00000224, +/* + +; wrong_dsa loads the DSA register with the value of the dsa_next +; field. +; +wrong_dsa: +; Patch the MOVE MEMORY INSTRUCTION such that +; the destination address is the address of the OLD +; next pointer. +; + MOVE MEMORY 4, dsa_temp_addr_next, reselected_ok + 8 + +at 0x00000014 : */ 0xc0000004,0x00000000,0x00000758, +/* + MOVE dmode_memory_to_ncr TO DMODE + +at 0x00000017 : */ 0x78380000,0x00000000, +/* +; +; Move the _contents_ of the next pointer into the DSA register as +; the next I_T_L or I_T_L_Q tupple to check against the established +; nexus. +; + MOVE MEMORY 4, dsa_temp_next, addr_scratch + +at 0x00000019 : */ 0xc0000004,0x00000000,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x0000001c : */ 0x78380000,0x00000000, +/* + CALL scratch_to_dsa + +at 0x0000001e : */ 0x88080000,0x00000980, +/* + JUMP reselected_check_next + +at 0x00000020 : */ 0x80080000,0x000006a4, +/* + +ABSOLUTE dsa_save_data_pointer = 0 +ENTRY dsa_code_save_data_pointer +dsa_code_save_data_pointer: + MOVE dmode_ncr_to_memory TO DMODE + +at 0x00000022 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_temp, dsa_temp_addr_saved_pointer + +at 0x00000024 : */ 0xc0000004,0x00000000,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000027 : */ 0x78380000,0x00000000, +/* +; HARD CODED : 24 bytes needs to agree with 53c7,8xx.h + MOVE MEMORY 24, dsa_temp_addr_residual, dsa_temp_addr_saved_residual + +at 0x00000029 : */ 0xc0000018,0x00000000,0x00000000, +/* + CLEAR ACK + +at 0x0000002c : */ 0x60000040,0x00000000, +/* + + + + RETURN + +at 0x0000002e : */ 0x90080000,0x00000000, +/* +ABSOLUTE dsa_restore_pointers = 0 +ENTRY dsa_code_restore_pointers +dsa_code_restore_pointers: + MOVE dmode_memory_to_ncr TO DMODE + +at 0x00000030 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, dsa_temp_addr_saved_pointer, addr_temp + +at 0x00000032 : */ 0xc0000004,0x00000000,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000035 : */ 0x78380000,0x00000000, +/* +; HARD CODED : 24 bytes needs to agree with 53c7,8xx.h + MOVE MEMORY 24, dsa_temp_addr_saved_residual, dsa_temp_addr_residual + +at 0x00000037 : */ 0xc0000018,0x00000000,0x00000000, +/* + CLEAR ACK + +at 0x0000003a : */ 0x60000040,0x00000000, +/* + + + + RETURN + +at 0x0000003c : */ 0x90080000,0x00000000, +/* + +ABSOLUTE dsa_check_reselect = 0 +; dsa_check_reselect determines whether or not the current target and +; lun match the current DSA +ENTRY dsa_code_check_reselect +dsa_code_check_reselect: + MOVE SSID TO SFBR ; SSID contains 3 bit target ID + +at 0x0000003e : */ 0x720a0000,0x00000000, +/* +; FIXME : we need to accommodate bit fielded and binary here for '7xx/'8xx chips + JUMP REL (wrong_dsa), IF NOT dsa_temp_target, AND MASK 0xf8 + +at 0x00000040 : */ 0x8084f800,0x00ffff48, +/* +; +; Hack - move to scratch first, since SFBR is not writeable +; via the CPU and hence a MOVE MEMORY instruction. +; + MOVE dmode_memory_to_ncr TO DMODE + +at 0x00000042 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 1, reselected_identify, addr_scratch + +at 0x00000044 : */ 0xc0000001,0x00000000,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000047 : */ 0x78380000,0x00000000, +/* + MOVE SCRATCH0 TO SFBR + +at 0x00000049 : */ 0x72340000,0x00000000, +/* +; FIXME : we need to accommodate bit fielded and binary here for '7xx/'8xx chips + JUMP REL (wrong_dsa), IF NOT dsa_temp_lun, AND MASK 0xf8 + +at 0x0000004b : */ 0x8084f800,0x00ffff1c, +/* +; Patch the MOVE MEMORY INSTRUCTION such that +; the source address is the address of this dsa's +; next pointer. + MOVE MEMORY 4, dsa_temp_addr_next, reselected_ok + 4 + +at 0x0000004d : */ 0xc0000004,0x00000000,0x00000754, +/* + CALL reselected_ok + +at 0x00000050 : */ 0x88080000,0x00000750, +/* + CALL dsa_temp_sync + +at 0x00000052 : */ 0x88080000,0x00000000, +/* +; Release ACK on the IDENTIFY message _after_ we've set the synchronous +; transfer parameters! + CLEAR ACK + +at 0x00000054 : */ 0x60000040,0x00000000, +/* +; Implicitly restore pointers on reselection, so a RETURN +; will transfer control back to the right spot. + CALL REL (dsa_code_restore_pointers) + +at 0x00000056 : */ 0x88880000,0x00ffff60, +/* + RETURN + +at 0x00000058 : */ 0x90080000,0x00000000, +/* +ENTRY dsa_zero +dsa_zero: +ENTRY dsa_code_template_end +dsa_code_template_end: + +; Perform sanity check for dsa_fields_start == dsa_code_template_end - +; dsa_zero, puke. + +ABSOLUTE dsa_fields_start = 0 ; Sanity marker + ; pad 48 bytes (fix this RSN) +ABSOLUTE dsa_next = 48 ; len 4 Next DSA + ; del 4 Previous DSA address +ABSOLUTE dsa_cmnd = 56 ; len 4 Scsi_Cmnd * for this thread. +ABSOLUTE dsa_select = 60 ; len 4 Device ID, Period, Offset for + ; table indirect select +ABSOLUTE dsa_msgout = 64 ; len 8 table indirect move parameter for + ; select message +ABSOLUTE dsa_cmdout = 72 ; len 8 table indirect move parameter for + ; command +ABSOLUTE dsa_dataout = 80 ; len 4 code pointer for dataout +ABSOLUTE dsa_datain = 84 ; len 4 code pointer for datain +ABSOLUTE dsa_msgin = 88 ; len 8 table indirect move for msgin +ABSOLUTE dsa_status = 96 ; len 8 table indirect move for status byte +ABSOLUTE dsa_msgout_other = 104 ; len 8 table indirect for normal message out + ; (Synchronous transfer negotiation, etc). +ABSOLUTE dsa_end = 112 + +ABSOLUTE schedule = 0 ; Array of JUMP dsa_begin or JUMP (next), + ; terminated by a call to JUMP wait_reselect + +; Linked lists of DSA structures +ABSOLUTE reconnect_dsa_head = 0 ; Link list of DSAs which can reconnect +ABSOLUTE addr_reconnect_dsa_head = 0 ; Address of variable containing + ; address of reconnect_dsa_head + +; These select the source and destination of a MOVE MEMORY instruction +ABSOLUTE dmode_memory_to_memory = 0x0 +ABSOLUTE dmode_memory_to_ncr = 0x0 +ABSOLUTE dmode_ncr_to_memory = 0x0 + +ABSOLUTE addr_scratch = 0x0 +ABSOLUTE addr_temp = 0x0 + + +; Interrupts - +; MSB indicates type +; 0 handle error condition +; 1 handle message +; 2 handle normal condition +; 3 debugging interrupt +; 4 testing interrupt +; Next byte indicates specific error + +; XXX not yet implemented, I'm not sure if I want to - +; Next byte indicates the routine the error occurred in +; The LSB indicates the specific place the error occurred + +ABSOLUTE int_err_unexpected_phase = 0x00000000 ; Unexpected phase encountered +ABSOLUTE int_err_selected = 0x00010000 ; SELECTED (nee RESELECTED) +ABSOLUTE int_err_unexpected_reselect = 0x00020000 +ABSOLUTE int_err_check_condition = 0x00030000 +ABSOLUTE int_err_no_phase = 0x00040000 +ABSOLUTE int_msg_wdtr = 0x01000000 ; WDTR message received +ABSOLUTE int_msg_sdtr = 0x01010000 ; SDTR received +ABSOLUTE int_msg_1 = 0x01020000 ; single byte special message + ; received + +ABSOLUTE int_norm_select_complete = 0x02000000 ; Select complete, reprogram + ; registers. +ABSOLUTE int_norm_reselect_complete = 0x02010000 ; Nexus established +ABSOLUTE int_norm_command_complete = 0x02020000 ; Command complete +ABSOLUTE int_norm_disconnected = 0x02030000 ; Disconnected +ABSOLUTE int_norm_aborted =0x02040000 ; Aborted *dsa +ABSOLUTE int_norm_reset = 0x02050000 ; Generated BUS reset. +ABSOLUTE int_debug_break = 0x03000000 ; Break point + +ABSOLUTE int_debug_panic = 0x030b0000 ; Panic driver + + +ABSOLUTE int_test_1 = 0x04000000 ; Test 1 complete +ABSOLUTE int_test_2 = 0x04010000 ; Test 2 complete +ABSOLUTE int_test_3 = 0x04020000 ; Test 3 complete + + +; These should start with 0x05000000, with low bits incrementing for +; each one. + + + +ABSOLUTE NCR53c7xx_msg_abort = 0 ; Pointer to abort message +ABSOLUTE NCR53c7xx_msg_reject = 0 ; Pointer to reject message +ABSOLUTE NCR53c7xx_zero = 0 ; long with zero in it, use for source +ABSOLUTE NCR53c7xx_sink = 0 ; long to dump worthless data in +ABSOLUTE NOP_insn = 0 ; NOP instruction + +; Pointer to message, potentially multi-byte +ABSOLUTE msg_buf = 0 + +; Pointer to holding area for reselection information +ABSOLUTE reselected_identify = 0 +ABSOLUTE reselected_tag = 0 + +; Request sense command pointer, it's a 6 byte command, should +; be constant for all commands since we always want 16 bytes of +; sense and we don't need to change any fields as we did under +; SCSI-I when we actually cared about the LUN field. +;EXTERNAL NCR53c7xx_sense ; Request sense command + + +; dsa_schedule +; PURPOSE : after a DISCONNECT message has been received, and pointers +; saved, insert the current DSA structure at the head of the +; disconnected queue and fall through to the scheduler. +; +; CALLS : OK +; +; INPUTS : dsa - current DSA structure, reconnect_dsa_head - list +; of disconnected commands +; +; MODIFIES : SCRATCH, reconnect_dsa_head +; +; EXITS : always passes control to schedule + +ENTRY dsa_schedule +dsa_schedule: + + + + +; +; Calculate the address of the next pointer within the DSA +; structure of the command that is currently disconnecting +; + CALL dsa_to_scratch + +at 0x0000005a : */ 0x88080000,0x00000938, +/* + MOVE SCRATCH0 + dsa_next TO SCRATCH0 + +at 0x0000005c : */ 0x7e343000,0x00000000, +/* + MOVE SCRATCH1 + 0 TO SCRATCH1 WITH CARRY + +at 0x0000005e : */ 0x7f350000,0x00000000, +/* + MOVE SCRATCH2 + 0 TO SCRATCH2 WITH CARRY + +at 0x00000060 : */ 0x7f360000,0x00000000, +/* + MOVE SCRATCH3 + 0 TO SCRATCH3 WITH CARRY + +at 0x00000062 : */ 0x7f370000,0x00000000, +/* + +; Point the next field of this DSA structure at the current disconnected +; list + MOVE dmode_ncr_to_memory TO DMODE + +at 0x00000064 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_scratch, dsa_schedule_insert + 8 + +at 0x00000066 : */ 0xc0000004,0x00000000,0x000001b4, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000069 : */ 0x78380000,0x00000000, +/* +dsa_schedule_insert: + MOVE MEMORY 4, reconnect_dsa_head, 0 + +at 0x0000006b : */ 0xc0000004,0x00000000,0x00000000, +/* + +; And update the head pointer. + CALL dsa_to_scratch + +at 0x0000006e : */ 0x88080000,0x00000938, +/* + MOVE dmode_ncr_to_memory TO DMODE + +at 0x00000070 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_scratch, reconnect_dsa_head + +at 0x00000072 : */ 0xc0000004,0x00000000,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000075 : */ 0x78380000,0x00000000, +/* + + + MOVE SCNTL2 & 0x7f TO SCNTL2 + +at 0x00000077 : */ 0x7c027f00,0x00000000, +/* + CLEAR ACK + +at 0x00000079 : */ 0x60000040,0x00000000, +/* + + WAIT DISCONNECT + +at 0x0000007b : */ 0x48000000,0x00000000, +/* + + + + + + + JUMP schedule + +at 0x0000007d : */ 0x80080000,0x00000000, +/* + + +; +; select +; +; PURPOSE : establish a nexus for the SCSI command referenced by DSA. +; On success, the current DSA structure is removed from the issue +; queue. Usually, this is entered as a fall-through from schedule, +; although the contingent allegiance handling code will write +; the select entry address to the DSP to restart a command as a +; REQUEST SENSE. A message is sent (usually IDENTIFY, although +; additional SDTR or WDTR messages may be sent). COMMAND OUT +; is handled. +; +; INPUTS : DSA - SCSI command, issue_dsa_head +; +; CALLS : NOT OK +; +; MODIFIES : SCRATCH, issue_dsa_head +; +; EXITS : on reselection or selection, go to select_failed +; otherwise, RETURN so control is passed back to +; dsa_begin. +; + +ENTRY select +select: + + + + + + + + + + + + + CLEAR TARGET + +at 0x0000007f : */ 0x60000200,0x00000000, +/* + +; XXX +; +; In effect, SELECTION operations are backgrounded, with execution +; continuing until code which waits for REQ or a fatal interrupt is +; encountered. +; +; So, for more performance, we could overlap the code which removes +; the command from the NCRs issue queue with the selection, but +; at this point I don't want to deal with the error recovery. +; + + + SELECT ATN FROM dsa_select, select_failed + +at 0x00000081 : */ 0x4300003c,0x000007a4, +/* + JUMP select_msgout, WHEN MSG_OUT + +at 0x00000083 : */ 0x860b0000,0x00000214, +/* +ENTRY select_msgout +select_msgout: + MOVE FROM dsa_msgout, WHEN MSG_OUT + +at 0x00000085 : */ 0x1e000000,0x00000040, +/* + + + + + + + + + + + RETURN + +at 0x00000087 : */ 0x90080000,0x00000000, +/* + +; +; select_done +; +; PURPOSE: continue on to normal data transfer; called as the exit +; point from dsa_begin. +; +; INPUTS: dsa +; +; CALLS: OK +; +; + +select_done: + + + + + + + +; After a successful selection, we should get either a CMD phase or +; some transfer request negotiation message. + + JUMP cmdout, WHEN CMD + +at 0x00000089 : */ 0x820b0000,0x00000244, +/* + INT int_err_unexpected_phase, WHEN NOT MSG_IN + +at 0x0000008b : */ 0x9f030000,0x00000000, +/* + +select_msg_in: + CALL msg_in, WHEN MSG_IN + +at 0x0000008d : */ 0x8f0b0000,0x00000404, +/* + JUMP select_msg_in, WHEN MSG_IN + +at 0x0000008f : */ 0x870b0000,0x00000234, +/* + +cmdout: + INT int_err_unexpected_phase, WHEN NOT CMD + +at 0x00000091 : */ 0x9a030000,0x00000000, +/* + + + +ENTRY cmdout_cmdout +cmdout_cmdout: + + MOVE FROM dsa_cmdout, WHEN CMD + +at 0x00000093 : */ 0x1a000000,0x00000048, +/* + + + + +; +; data_transfer +; other_out +; other_in +; other_transfer +; +; PURPOSE : handle the main data transfer for a SCSI command in +; several parts. In the first part, data_transfer, DATA_IN +; and DATA_OUT phases are allowed, with the user provided +; code (usually dynamically generated based on the scatter/gather +; list associated with a SCSI command) called to handle these +; phases. +; +; After control has passed to one of the user provided +; DATA_IN or DATA_OUT routines, back calls are made to +; other_transfer_in or other_transfer_out to handle non-DATA IN +; and DATA OUT phases respectively, with the state of the active +; data pointer being preserved in TEMP. +; +; On completion, the user code passes control to other_transfer +; which causes DATA_IN and DATA_OUT to result in unexpected_phase +; interrupts so that data overruns may be trapped. +; +; INPUTS : DSA - SCSI command +; +; CALLS : OK in data_transfer_start, not ok in other_out and other_in, ok in +; other_transfer +; +; MODIFIES : SCRATCH +; +; EXITS : if STATUS IN is detected, signifying command completion, +; the NCR jumps to command_complete. If MSG IN occurs, a +; CALL is made to msg_in. Otherwise, other_transfer runs in +; an infinite loop. +; + +ENTRY data_transfer +data_transfer: + JUMP cmdout_cmdout, WHEN CMD + +at 0x00000095 : */ 0x820b0000,0x0000024c, +/* + CALL msg_in, WHEN MSG_IN + +at 0x00000097 : */ 0x8f0b0000,0x00000404, +/* + INT int_err_unexpected_phase, WHEN MSG_OUT + +at 0x00000099 : */ 0x9e0b0000,0x00000000, +/* + JUMP do_dataout, WHEN DATA_OUT + +at 0x0000009b : */ 0x800b0000,0x0000028c, +/* + JUMP do_datain, WHEN DATA_IN + +at 0x0000009d : */ 0x810b0000,0x000002e4, +/* + JUMP command_complete, WHEN STATUS + +at 0x0000009f : */ 0x830b0000,0x0000060c, +/* + JUMP data_transfer + +at 0x000000a1 : */ 0x80080000,0x00000254, +/* +ENTRY end_data_transfer +end_data_transfer: + +; +; FIXME: On NCR53c700 and NCR53c700-66 chips, do_dataout/do_datain +; should be fixed up whenever the nexus changes so it can point to the +; correct routine for that command. +; + + +; Nasty jump to dsa->dataout +do_dataout: + CALL dsa_to_scratch + +at 0x000000a3 : */ 0x88080000,0x00000938, +/* + MOVE SCRATCH0 + dsa_dataout TO SCRATCH0 + +at 0x000000a5 : */ 0x7e345000,0x00000000, +/* + MOVE SCRATCH1 + 0 TO SCRATCH1 WITH CARRY + +at 0x000000a7 : */ 0x7f350000,0x00000000, +/* + MOVE SCRATCH2 + 0 TO SCRATCH2 WITH CARRY + +at 0x000000a9 : */ 0x7f360000,0x00000000, +/* + MOVE SCRATCH3 + 0 TO SCRATCH3 WITH CARRY + +at 0x000000ab : */ 0x7f370000,0x00000000, +/* + MOVE dmode_ncr_to_memory TO DMODE + +at 0x000000ad : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_scratch, dataout_to_jump + 4 + +at 0x000000af : */ 0xc0000004,0x00000000,0x000002d4, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x000000b2 : */ 0x78380000,0x00000000, +/* +dataout_to_jump: + MOVE MEMORY 4, 0, dataout_jump + 4 + +at 0x000000b4 : */ 0xc0000004,0x00000000,0x000002e0, +/* +dataout_jump: + JUMP 0 + +at 0x000000b7 : */ 0x80080000,0x00000000, +/* + +; Nasty jump to dsa->dsain +do_datain: + CALL dsa_to_scratch + +at 0x000000b9 : */ 0x88080000,0x00000938, +/* + MOVE SCRATCH0 + dsa_datain TO SCRATCH0 + +at 0x000000bb : */ 0x7e345400,0x00000000, +/* + MOVE SCRATCH1 + 0 TO SCRATCH1 WITH CARRY + +at 0x000000bd : */ 0x7f350000,0x00000000, +/* + MOVE SCRATCH2 + 0 TO SCRATCH2 WITH CARRY + +at 0x000000bf : */ 0x7f360000,0x00000000, +/* + MOVE SCRATCH3 + 0 TO SCRATCH3 WITH CARRY + +at 0x000000c1 : */ 0x7f370000,0x00000000, +/* + MOVE dmode_ncr_to_memory TO DMODE + +at 0x000000c3 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_scratch, datain_to_jump + 4 + +at 0x000000c5 : */ 0xc0000004,0x00000000,0x0000032c, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x000000c8 : */ 0x78380000,0x00000000, +/* +ENTRY datain_to_jump +datain_to_jump: + MOVE MEMORY 4, 0, datain_jump + 4 + +at 0x000000ca : */ 0xc0000004,0x00000000,0x00000338, +/* + + + +datain_jump: + JUMP 0 + +at 0x000000cd : */ 0x80080000,0x00000000, +/* + + + +; Note that other_out and other_in loop until a non-data phase +; is discovered, so we only execute return statements when we +; can go on to the next data phase block move statement. + +ENTRY other_out +other_out: + + + + INT int_err_unexpected_phase, WHEN CMD + +at 0x000000cf : */ 0x9a0b0000,0x00000000, +/* + JUMP msg_in_restart, WHEN MSG_IN + +at 0x000000d1 : */ 0x870b0000,0x000003e4, +/* + INT int_err_unexpected_phase, WHEN MSG_OUT + +at 0x000000d3 : */ 0x9e0b0000,0x00000000, +/* + INT int_err_unexpected_phase, WHEN DATA_IN + +at 0x000000d5 : */ 0x990b0000,0x00000000, +/* + JUMP command_complete, WHEN STATUS + +at 0x000000d7 : */ 0x830b0000,0x0000060c, +/* + JUMP other_out, WHEN NOT DATA_OUT + +at 0x000000d9 : */ 0x80030000,0x0000033c, +/* + RETURN + +at 0x000000db : */ 0x90080000,0x00000000, +/* + +ENTRY other_in +other_in: + + + + INT int_err_unexpected_phase, WHEN CMD + +at 0x000000dd : */ 0x9a0b0000,0x00000000, +/* + JUMP msg_in_restart, WHEN MSG_IN + +at 0x000000df : */ 0x870b0000,0x000003e4, +/* + INT int_err_unexpected_phase, WHEN MSG_OUT + +at 0x000000e1 : */ 0x9e0b0000,0x00000000, +/* + INT int_err_unexpected_phase, WHEN DATA_OUT + +at 0x000000e3 : */ 0x980b0000,0x00000000, +/* + JUMP command_complete, WHEN STATUS + +at 0x000000e5 : */ 0x830b0000,0x0000060c, +/* + JUMP other_in, WHEN NOT DATA_IN + +at 0x000000e7 : */ 0x81030000,0x00000374, +/* + RETURN + +at 0x000000e9 : */ 0x90080000,0x00000000, +/* + + +ENTRY other_transfer +other_transfer: + INT int_err_unexpected_phase, WHEN CMD + +at 0x000000eb : */ 0x9a0b0000,0x00000000, +/* + CALL msg_in, WHEN MSG_IN + +at 0x000000ed : */ 0x8f0b0000,0x00000404, +/* + INT int_err_unexpected_phase, WHEN MSG_OUT + +at 0x000000ef : */ 0x9e0b0000,0x00000000, +/* + INT int_err_unexpected_phase, WHEN DATA_OUT + +at 0x000000f1 : */ 0x980b0000,0x00000000, +/* + INT int_err_unexpected_phase, WHEN DATA_IN + +at 0x000000f3 : */ 0x990b0000,0x00000000, +/* + JUMP command_complete, WHEN STATUS + +at 0x000000f5 : */ 0x830b0000,0x0000060c, +/* + JUMP other_transfer + +at 0x000000f7 : */ 0x80080000,0x000003ac, +/* + +; +; msg_in_restart +; msg_in +; munge_msg +; +; PURPOSE : process messages from a target. msg_in is called when the +; caller hasn't read the first byte of the message. munge_message +; is called when the caller has read the first byte of the message, +; and left it in SFBR. msg_in_restart is called when the caller +; hasn't read the first byte of the message, and wishes RETURN +; to transfer control back to the address of the conditional +; CALL instruction rather than to the instruction after it. +; +; Various int_* interrupts are generated when the host system +; needs to intervene, as is the case with SDTR, WDTR, and +; INITIATE RECOVERY messages. +; +; When the host system handles one of these interrupts, +; it can respond by reentering at reject_message, +; which rejects the message and returns control to +; the caller of msg_in or munge_msg, accept_message +; which clears ACK and returns control, or reply_message +; which sends the message pointed to by the DSA +; msgout_other table indirect field. +; +; DISCONNECT messages are handled by moving the command +; to the reconnect_dsa_queue. +; +; INPUTS : DSA - SCSI COMMAND, SFBR - first byte of message (munge_msg +; only) +; +; CALLS : NO. The TEMP register isn't backed up to allow nested calls. +; +; MODIFIES : SCRATCH, DSA on DISCONNECT +; +; EXITS : On receipt of SAVE DATA POINTER, RESTORE POINTERS, +; and normal return from message handlers running under +; Linux, control is returned to the caller. Receipt +; of DISCONNECT messages pass control to dsa_schedule. +; +ENTRY msg_in_restart +msg_in_restart: +; XXX - hackish +; +; Since it's easier to debug changes to the statically +; compiled code, rather than the dynamically generated +; stuff, such as +; +; MOVE x, y, WHEN data_phase +; CALL other_z, WHEN NOT data_phase +; MOVE x, y, WHEN data_phase +; +; I'd like to have certain routines (notably the message handler) +; restart on the conditional call rather than the next instruction. +; +; So, subtract 8 from the return address + + MOVE TEMP0 + 0xf8 TO TEMP0 + +at 0x000000f9 : */ 0x7e1cf800,0x00000000, +/* + MOVE TEMP1 + 0xff TO TEMP1 WITH CARRY + +at 0x000000fb : */ 0x7f1dff00,0x00000000, +/* + MOVE TEMP2 + 0xff TO TEMP2 WITH CARRY + +at 0x000000fd : */ 0x7f1eff00,0x00000000, +/* + MOVE TEMP3 + 0xff TO TEMP3 WITH CARRY + +at 0x000000ff : */ 0x7f1fff00,0x00000000, +/* + +ENTRY msg_in +msg_in: + MOVE 1, msg_buf, WHEN MSG_IN + +at 0x00000101 : */ 0x0f000001,0x00000000, +/* + +munge_msg: + JUMP munge_extended, IF 0x01 ; EXTENDED MESSAGE + +at 0x00000103 : */ 0x800c0001,0x00000524, +/* + JUMP munge_2, IF 0x20, AND MASK 0xdf ; two byte message + +at 0x00000105 : */ 0x800cdf20,0x0000044c, +/* +; +; XXX - I've seen a handful of broken SCSI devices which fail to issue +; a SAVE POINTERS message before disconnecting in the middle of +; a transfer, assuming that the DATA POINTER will be implicitly +; restored. +; +; Historically, I've often done an implicit save when the DISCONNECT +; message is processed. We may want to consider having the option of +; doing that here. +; + JUMP munge_save_data_pointer, IF 0x02 ; SAVE DATA POINTER + +at 0x00000107 : */ 0x800c0002,0x00000454, +/* + JUMP munge_restore_pointers, IF 0x03 ; RESTORE POINTERS + +at 0x00000109 : */ 0x800c0003,0x000004b8, +/* + JUMP munge_disconnect, IF 0x04 ; DISCONNECT + +at 0x0000010b : */ 0x800c0004,0x0000051c, +/* + INT int_msg_1, IF 0x07 ; MESSAGE REJECT + +at 0x0000010d : */ 0x980c0007,0x01020000, +/* + INT int_msg_1, IF 0x0f ; INITIATE RECOVERY + +at 0x0000010f : */ 0x980c000f,0x01020000, +/* + + + + JUMP reject_message + +at 0x00000111 : */ 0x80080000,0x000005b4, +/* + +munge_2: + JUMP reject_message + +at 0x00000113 : */ 0x80080000,0x000005b4, +/* +; +; The SCSI standard allows targets to recover from transient +; error conditions by backing up the data pointer with a +; RESTORE POINTERS message. +; +; So, we must save and restore the _residual_ code as well as +; the current instruction pointer. Because of this messiness, +; it is simpler to put dynamic code in the dsa for this and to +; just do a simple jump down there. +; + +munge_save_data_pointer: + MOVE DSA0 + dsa_save_data_pointer TO SFBR + +at 0x00000115 : */ 0x76100000,0x00000000, +/* + MOVE SFBR TO SCRATCH0 + +at 0x00000117 : */ 0x6a340000,0x00000000, +/* + MOVE DSA1 + 0xff TO SFBR WITH CARRY + +at 0x00000119 : */ 0x7711ff00,0x00000000, +/* + MOVE SFBR TO SCRATCH1 + +at 0x0000011b : */ 0x6a350000,0x00000000, +/* + MOVE DSA2 + 0xff TO SFBR WITH CARRY + +at 0x0000011d : */ 0x7712ff00,0x00000000, +/* + MOVE SFBR TO SCRATCH2 + +at 0x0000011f : */ 0x6a360000,0x00000000, +/* + MOVE DSA3 + 0xff TO SFBR WITH CARRY + +at 0x00000121 : */ 0x7713ff00,0x00000000, +/* + MOVE SFBR TO SCRATCH3 + +at 0x00000123 : */ 0x6a370000,0x00000000, +/* + + MOVE dmode_ncr_to_memory TO DMODE + +at 0x00000125 : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_scratch, jump_dsa_save + 4 + +at 0x00000127 : */ 0xc0000004,0x00000000,0x000004b4, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x0000012a : */ 0x78380000,0x00000000, +/* +jump_dsa_save: + JUMP 0 + +at 0x0000012c : */ 0x80080000,0x00000000, +/* + +munge_restore_pointers: + MOVE DSA0 + dsa_restore_pointers TO SFBR + +at 0x0000012e : */ 0x76100000,0x00000000, +/* + MOVE SFBR TO SCRATCH0 + +at 0x00000130 : */ 0x6a340000,0x00000000, +/* + MOVE DSA1 + 0xff TO SFBR WITH CARRY + +at 0x00000132 : */ 0x7711ff00,0x00000000, +/* + MOVE SFBR TO SCRATCH1 + +at 0x00000134 : */ 0x6a350000,0x00000000, +/* + MOVE DSA2 + 0xff TO SFBR WITH CARRY + +at 0x00000136 : */ 0x7712ff00,0x00000000, +/* + MOVE SFBR TO SCRATCH2 + +at 0x00000138 : */ 0x6a360000,0x00000000, +/* + MOVE DSA3 + 0xff TO SFBR WITH CARRY + +at 0x0000013a : */ 0x7713ff00,0x00000000, +/* + MOVE SFBR TO SCRATCH3 + +at 0x0000013c : */ 0x6a370000,0x00000000, +/* + + MOVE dmode_ncr_to_memory TO DMODE + +at 0x0000013e : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_scratch, jump_dsa_restore + 4 + +at 0x00000140 : */ 0xc0000004,0x00000000,0x00000518, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000143 : */ 0x78380000,0x00000000, +/* +jump_dsa_restore: + JUMP 0 + +at 0x00000145 : */ 0x80080000,0x00000000, +/* + + +munge_disconnect: + + + + + + + + + + + + + + + + + JUMP dsa_schedule + +at 0x00000147 : */ 0x80080000,0x00000168, +/* + + + + + +munge_extended: + CLEAR ACK + +at 0x00000149 : */ 0x60000040,0x00000000, +/* + INT int_err_unexpected_phase, WHEN NOT MSG_IN + +at 0x0000014b : */ 0x9f030000,0x00000000, +/* + MOVE 1, msg_buf + 1, WHEN MSG_IN + +at 0x0000014d : */ 0x0f000001,0x00000001, +/* + JUMP munge_extended_2, IF 0x02 + +at 0x0000014f : */ 0x800c0002,0x00000554, +/* + JUMP munge_extended_3, IF 0x03 + +at 0x00000151 : */ 0x800c0003,0x00000584, +/* + JUMP reject_message + +at 0x00000153 : */ 0x80080000,0x000005b4, +/* + +munge_extended_2: + CLEAR ACK + +at 0x00000155 : */ 0x60000040,0x00000000, +/* + MOVE 1, msg_buf + 2, WHEN MSG_IN + +at 0x00000157 : */ 0x0f000001,0x00000002, +/* + JUMP reject_message, IF NOT 0x02 ; Must be WDTR + +at 0x00000159 : */ 0x80040002,0x000005b4, +/* + CLEAR ACK + +at 0x0000015b : */ 0x60000040,0x00000000, +/* + MOVE 1, msg_buf + 3, WHEN MSG_IN + +at 0x0000015d : */ 0x0f000001,0x00000003, +/* + INT int_msg_wdtr + +at 0x0000015f : */ 0x98080000,0x01000000, +/* + +munge_extended_3: + CLEAR ACK + +at 0x00000161 : */ 0x60000040,0x00000000, +/* + MOVE 1, msg_buf + 2, WHEN MSG_IN + +at 0x00000163 : */ 0x0f000001,0x00000002, +/* + JUMP reject_message, IF NOT 0x01 ; Must be SDTR + +at 0x00000165 : */ 0x80040001,0x000005b4, +/* + CLEAR ACK + +at 0x00000167 : */ 0x60000040,0x00000000, +/* + MOVE 2, msg_buf + 3, WHEN MSG_IN + +at 0x00000169 : */ 0x0f000002,0x00000003, +/* + INT int_msg_sdtr + +at 0x0000016b : */ 0x98080000,0x01010000, +/* + +ENTRY reject_message +reject_message: + SET ATN + +at 0x0000016d : */ 0x58000008,0x00000000, +/* + CLEAR ACK + +at 0x0000016f : */ 0x60000040,0x00000000, +/* + MOVE 1, NCR53c7xx_msg_reject, WHEN MSG_OUT + +at 0x00000171 : */ 0x0e000001,0x00000000, +/* + RETURN + +at 0x00000173 : */ 0x90080000,0x00000000, +/* + +ENTRY accept_message +accept_message: + CLEAR ATN + +at 0x00000175 : */ 0x60000008,0x00000000, +/* + CLEAR ACK + +at 0x00000177 : */ 0x60000040,0x00000000, +/* + RETURN + +at 0x00000179 : */ 0x90080000,0x00000000, +/* + +ENTRY respond_message +respond_message: + SET ATN + +at 0x0000017b : */ 0x58000008,0x00000000, +/* + CLEAR ACK + +at 0x0000017d : */ 0x60000040,0x00000000, +/* + MOVE FROM dsa_msgout_other, WHEN MSG_OUT + +at 0x0000017f : */ 0x1e000000,0x00000068, +/* + RETURN + +at 0x00000181 : */ 0x90080000,0x00000000, +/* + +; +; command_complete +; +; PURPOSE : handle command termination when STATUS IN is detected by reading +; a status byte followed by a command termination message. +; +; Normal termination results in an INTFLY instruction, and +; the host system can pick out which command terminated by +; examining the MESSAGE and STATUS buffers of all currently +; executing commands; +; +; Abnormal (CHECK_CONDITION) termination results in an +; int_err_check_condition interrupt so that a REQUEST SENSE +; command can be issued out-of-order so that no other command +; clears the contingent allegiance condition. +; +; +; INPUTS : DSA - command +; +; CALLS : OK +; +; EXITS : On successful termination, control is passed to schedule. +; On abnormal termination, the user will usually modify the +; DSA fields and corresponding buffers and return control +; to select. +; + +ENTRY command_complete +command_complete: + MOVE FROM dsa_status, WHEN STATUS + +at 0x00000183 : */ 0x1b000000,0x00000060, +/* + + MOVE SFBR TO SCRATCH0 ; Save status + +at 0x00000185 : */ 0x6a340000,0x00000000, +/* + +ENTRY command_complete_msgin +command_complete_msgin: + MOVE FROM dsa_msgin, WHEN MSG_IN + +at 0x00000187 : */ 0x1f000000,0x00000058, +/* +; Indicate that we should be expecting a disconnect + MOVE SCNTL2 & 0x7f TO SCNTL2 + +at 0x00000189 : */ 0x7c027f00,0x00000000, +/* + CLEAR ACK + +at 0x0000018b : */ 0x60000040,0x00000000, +/* + + WAIT DISCONNECT + +at 0x0000018d : */ 0x48000000,0x00000000, +/* + +; +; The SCSI specification states that when a UNIT ATTENTION condition +; is pending, as indicated by a CHECK CONDITION status message, +; the target shall revert to asynchronous transfers. Since +; synchronous transfers parameters are maintained on a per INITIATOR/TARGET +; basis, and returning control to our scheduler could work on a command +; running on another lun on that target using the old parameters, we must +; interrupt the host processor to get them changed, or change them ourselves. +; +; Once SCSI-II tagged queueing is implemented, things will be even more +; hairy, since contingent allegiance conditions exist on a per-target/lun +; basis, and issuing a new command with a different tag would clear it. +; In these cases, we must interrupt the host processor to get a request +; added to the HEAD of the queue with the request sense command, or we +; must automatically issue the request sense command. + + + + + + INTFLY + +at 0x0000018f : */ 0x98180000,0x00000000, +/* + + + + + + JUMP schedule + +at 0x00000191 : */ 0x80080000,0x00000000, +/* +command_failed: + INT int_err_check_condition + +at 0x00000193 : */ 0x98080000,0x00030000, +/* + + + + +; +; wait_reselect +; +; PURPOSE : This is essentially the idle routine, where control lands +; when there are no new processes to schedule. wait_reselect +; waits for reselection, selection, and new commands. +; +; When a successful reselection occurs, with the aid +; of fixed up code in each DSA, wait_reselect walks the +; reconnect_dsa_queue, asking each dsa if the target ID +; and LUN match its. +; +; If a match is found, a call is made back to reselected_ok, +; which through the miracles of self modifying code, extracts +; the found DSA from the reconnect_dsa_queue and then +; returns control to the DSAs thread of execution. +; +; INPUTS : NONE +; +; CALLS : OK +; +; MODIFIES : DSA, +; +; EXITS : On successful reselection, control is returned to the +; DSA which called reselected_ok. If the WAIT RESELECT +; was interrupted by a new commands arrival signaled by +; SIG_P, control is passed to schedule. If the NCR is +; selected, the host system is interrupted with an +; int_err_selected which is usually responded to by +; setting DSP to the target_abort address. + +ENTRY wait_reselect +wait_reselect: + + + + + + + WAIT RESELECT wait_reselect_failed + +at 0x00000195 : */ 0x50000000,0x0000076c, +/* + +reselected: + + + + CLEAR TARGET + +at 0x00000197 : */ 0x60000200,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x00000199 : */ 0x78380000,0x00000000, +/* + ; Read all data needed to reestablish the nexus - + MOVE 1, reselected_identify, WHEN MSG_IN + +at 0x0000019b : */ 0x0f000001,0x00000000, +/* + ; We used to CLEAR ACK here. + + + + + + ; Point DSA at the current head of the disconnected queue. + MOVE dmode_memory_to_ncr TO DMODE + +at 0x0000019d : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, reconnect_dsa_head, addr_scratch + +at 0x0000019f : */ 0xc0000004,0x00000000,0x00000000, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x000001a2 : */ 0x78380000,0x00000000, +/* + CALL scratch_to_dsa + +at 0x000001a4 : */ 0x88080000,0x00000980, +/* + + ; Fix the update-next pointer so that the reconnect_dsa_head + ; pointer is the one that will be updated if this DSA is a hit + ; and we remove it from the queue. + + MOVE MEMORY 4, addr_reconnect_dsa_head, reselected_ok + 8 + +at 0x000001a6 : */ 0xc0000004,0x00000000,0x00000758, +/* + +ENTRY reselected_check_next +reselected_check_next: + + + + ; Check for a NULL pointer. + MOVE DSA0 TO SFBR + +at 0x000001a9 : */ 0x72100000,0x00000000, +/* + JUMP reselected_not_end, IF NOT 0 + +at 0x000001ab : */ 0x80040000,0x000006ec, +/* + MOVE DSA1 TO SFBR + +at 0x000001ad : */ 0x72110000,0x00000000, +/* + JUMP reselected_not_end, IF NOT 0 + +at 0x000001af : */ 0x80040000,0x000006ec, +/* + MOVE DSA2 TO SFBR + +at 0x000001b1 : */ 0x72120000,0x00000000, +/* + JUMP reselected_not_end, IF NOT 0 + +at 0x000001b3 : */ 0x80040000,0x000006ec, +/* + MOVE DSA3 TO SFBR + +at 0x000001b5 : */ 0x72130000,0x00000000, +/* + JUMP reselected_not_end, IF NOT 0 + +at 0x000001b7 : */ 0x80040000,0x000006ec, +/* + INT int_err_unexpected_reselect + +at 0x000001b9 : */ 0x98080000,0x00020000, +/* + +reselected_not_end: + ; + ; XXX the ALU is only eight bits wide, and the assembler + ; wont do the dirt work for us. As long as dsa_check_reselect + ; is negative, we need to sign extend with 1 bits to the full + ; 32 bit width of the address. + ; + ; A potential work around would be to have a known alignment + ; of the DSA structure such that the base address plus + ; dsa_check_reselect doesn't require carrying from bytes + ; higher than the LSB. + ; + + MOVE DSA0 TO SFBR + +at 0x000001bb : */ 0x72100000,0x00000000, +/* + MOVE SFBR + dsa_check_reselect TO SCRATCH0 + +at 0x000001bd : */ 0x6e340000,0x00000000, +/* + MOVE DSA1 TO SFBR + +at 0x000001bf : */ 0x72110000,0x00000000, +/* + MOVE SFBR + 0xff TO SCRATCH1 WITH CARRY + +at 0x000001c1 : */ 0x6f35ff00,0x00000000, +/* + MOVE DSA2 TO SFBR + +at 0x000001c3 : */ 0x72120000,0x00000000, +/* + MOVE SFBR + 0xff TO SCRATCH2 WITH CARRY + +at 0x000001c5 : */ 0x6f36ff00,0x00000000, +/* + MOVE DSA3 TO SFBR + +at 0x000001c7 : */ 0x72130000,0x00000000, +/* + MOVE SFBR + 0xff TO SCRATCH3 WITH CARRY + +at 0x000001c9 : */ 0x6f37ff00,0x00000000, +/* + + MOVE dmode_ncr_to_memory TO DMODE + +at 0x000001cb : */ 0x78380000,0x00000000, +/* + MOVE MEMORY 4, addr_scratch, reselected_check + 4 + +at 0x000001cd : */ 0xc0000004,0x00000000,0x0000074c, +/* + MOVE dmode_memory_to_memory TO DMODE + +at 0x000001d0 : */ 0x78380000,0x00000000, +/* +reselected_check: + JUMP 0 + +at 0x000001d2 : */ 0x80080000,0x00000000, +/* + + +; +; +ENTRY reselected_ok +reselected_ok: + MOVE MEMORY 4, 0, 0 ; Patched : first word + +at 0x000001d4 : */ 0xc0000004,0x00000000,0x00000000, +/* + ; is address of + ; successful dsa_next + ; Second word is last + ; unsuccessful dsa_next, + ; starting with + ; dsa_reconnect_head + ; We used to CLEAR ACK here. + + + + + + + RETURN ; Return control to where + +at 0x000001d7 : */ 0x90080000,0x00000000, +/* + + + + +selected: + INT int_err_selected; + +at 0x000001d9 : */ 0x98080000,0x00010000, +/* + +; +; A select or reselect failure can be caused by one of two conditions : +; 1. SIG_P was set. This will be the case if the user has written +; a new value to a previously NULL head of the issue queue. +; +; 2. The NCR53c810 was selected or reselected by another device. +; +; 3. The bus was already busy since we were selected or reselected +; before starting the command. + +wait_reselect_failed: + + + +; Check selected bit. + MOVE SIST0 & 0x20 TO SFBR + +at 0x000001db : */ 0x74422000,0x00000000, +/* + JUMP selected, IF 0x20 + +at 0x000001dd : */ 0x800c0020,0x00000764, +/* +; Reading CTEST2 clears the SIG_P bit in the ISTAT register. + MOVE CTEST2 & 0x40 TO SFBR + +at 0x000001df : */ 0x741a4000,0x00000000, +/* + JUMP schedule, IF 0x40 + +at 0x000001e1 : */ 0x800c0040,0x00000000, +/* +; Check connected bit. +; FIXME: this needs to change if we support target mode + MOVE ISTAT & 0x08 TO SFBR + +at 0x000001e3 : */ 0x74140800,0x00000000, +/* + JUMP reselected, IF 0x08 + +at 0x000001e5 : */ 0x800c0008,0x0000065c, +/* +; FIXME : Something bogus happened, and we shouldn't fail silently. + + + + INT int_debug_panic + +at 0x000001e7 : */ 0x98080000,0x030b0000, +/* + + + +select_failed: + + + +; Otherwise, mask the selected and reselected bits off SIST0 + MOVE SIST0 & 0x30 TO SFBR + +at 0x000001e9 : */ 0x74423000,0x00000000, +/* + JUMP selected, IF 0x20 + +at 0x000001eb : */ 0x800c0020,0x00000764, +/* + JUMP reselected, IF 0x10 + +at 0x000001ed : */ 0x800c0010,0x0000065c, +/* +; If SIGP is set, the user just gave us another command, and +; we should restart or return to the scheduler. +; Reading CTEST2 clears the SIG_P bit in the ISTAT register. + MOVE CTEST2 & 0x40 TO SFBR + +at 0x000001ef : */ 0x741a4000,0x00000000, +/* + JUMP select, IF 0x40 + +at 0x000001f1 : */ 0x800c0040,0x000001fc, +/* +; Check connected bit. +; FIXME: this needs to change if we support target mode +; FIXME: is this really necessary? + MOVE ISTAT & 0x08 TO SFBR + +at 0x000001f3 : */ 0x74140800,0x00000000, +/* + JUMP reselected, IF 0x08 + +at 0x000001f5 : */ 0x800c0008,0x0000065c, +/* +; FIXME : Something bogus happened, and we shouldn't fail silently. + + + + INT int_debug_panic + +at 0x000001f7 : */ 0x98080000,0x030b0000, +/* + + +; +; test_1 +; test_2 +; +; PURPOSE : run some verification tests on the NCR. test_1 +; copies test_src to test_dest and interrupts the host +; processor, testing for cache coherency and interrupt +; problems in the processes. +; +; test_2 runs a command with offsets relative to the +; DSA on entry, and is useful for miscellaneous experimentation. +; + +; Verify that interrupts are working correctly and that we don't +; have a cache invalidation problem. + +ABSOLUTE test_src = 0, test_dest = 0 +ENTRY test_1 +test_1: + MOVE MEMORY 4, test_src, test_dest + +at 0x000001f9 : */ 0xc0000004,0x00000000,0x00000000, +/* + INT int_test_1 + +at 0x000001fc : */ 0x98080000,0x04000000, +/* + +; +; Run arbitrary commands, with test code establishing a DSA +; + +ENTRY test_2 +test_2: + CLEAR TARGET + +at 0x000001fe : */ 0x60000200,0x00000000, +/* + SELECT ATN FROM 0, test_2_fail + +at 0x00000200 : */ 0x43000000,0x00000850, +/* + JUMP test_2_msgout, WHEN MSG_OUT + +at 0x00000202 : */ 0x860b0000,0x00000810, +/* +ENTRY test_2_msgout +test_2_msgout: + MOVE FROM 8, WHEN MSG_OUT + +at 0x00000204 : */ 0x1e000000,0x00000008, +/* + MOVE FROM 16, WHEN CMD + +at 0x00000206 : */ 0x1a000000,0x00000010, +/* + MOVE FROM 24, WHEN DATA_IN + +at 0x00000208 : */ 0x19000000,0x00000018, +/* + MOVE FROM 32, WHEN STATUS + +at 0x0000020a : */ 0x1b000000,0x00000020, +/* + MOVE FROM 40, WHEN MSG_IN + +at 0x0000020c : */ 0x1f000000,0x00000028, +/* + MOVE SCNTL2 & 0x7f TO SCNTL2 + +at 0x0000020e : */ 0x7c027f00,0x00000000, +/* + CLEAR ACK + +at 0x00000210 : */ 0x60000040,0x00000000, +/* + WAIT DISCONNECT + +at 0x00000212 : */ 0x48000000,0x00000000, +/* +test_2_fail: + INT int_test_2 + +at 0x00000214 : */ 0x98080000,0x04010000, +/* + +ENTRY debug_break +debug_break: + INT int_debug_break + +at 0x00000216 : */ 0x98080000,0x03000000, +/* + +; +; initiator_abort +; target_abort +; +; PURPOSE : Abort the currently established nexus from with initiator +; or target mode. +; +; + +ENTRY target_abort +target_abort: + SET TARGET + +at 0x00000218 : */ 0x58000200,0x00000000, +/* + DISCONNECT + +at 0x0000021a : */ 0x48000000,0x00000000, +/* + CLEAR TARGET + +at 0x0000021c : */ 0x60000200,0x00000000, +/* + JUMP schedule + +at 0x0000021e : */ 0x80080000,0x00000000, +/* + +ENTRY initiator_abort +initiator_abort: + SET ATN + +at 0x00000220 : */ 0x58000008,0x00000000, +/* +; +; The SCSI-I specification says that targets may go into MSG out at +; their leisure upon receipt of the ATN single. On all versions of the +; specification, we can't change phases until REQ transitions true->false, +; so we need to sink/source one byte of data to allow the transition. +; +; For the sake of safety, we'll only source one byte of data in all +; cases, but to accommodate the SCSI-I dain bramage, we'll sink an +; arbitrary number of bytes. + JUMP spew_cmd, WHEN CMD + +at 0x00000222 : */ 0x820b0000,0x000008b8, +/* + JUMP eat_msgin, WHEN MSG_IN + +at 0x00000224 : */ 0x870b0000,0x000008c8, +/* + JUMP eat_datain, WHEN DATA_IN + +at 0x00000226 : */ 0x810b0000,0x000008f8, +/* + JUMP eat_status, WHEN STATUS + +at 0x00000228 : */ 0x830b0000,0x000008e0, +/* + JUMP spew_dataout, WHEN DATA_OUT + +at 0x0000022a : */ 0x800b0000,0x00000910, +/* + JUMP sated + +at 0x0000022c : */ 0x80080000,0x00000918, +/* +spew_cmd: + MOVE 1, NCR53c7xx_zero, WHEN CMD + +at 0x0000022e : */ 0x0a000001,0x00000000, +/* + JUMP sated + +at 0x00000230 : */ 0x80080000,0x00000918, +/* +eat_msgin: + MOVE 1, NCR53c7xx_sink, WHEN MSG_IN + +at 0x00000232 : */ 0x0f000001,0x00000000, +/* + JUMP eat_msgin, WHEN MSG_IN + +at 0x00000234 : */ 0x870b0000,0x000008c8, +/* + JUMP sated + +at 0x00000236 : */ 0x80080000,0x00000918, +/* +eat_status: + MOVE 1, NCR53c7xx_sink, WHEN STATUS + +at 0x00000238 : */ 0x0b000001,0x00000000, +/* + JUMP eat_status, WHEN STATUS + +at 0x0000023a : */ 0x830b0000,0x000008e0, +/* + JUMP sated + +at 0x0000023c : */ 0x80080000,0x00000918, +/* +eat_datain: + MOVE 1, NCR53c7xx_sink, WHEN DATA_IN + +at 0x0000023e : */ 0x09000001,0x00000000, +/* + JUMP eat_datain, WHEN DATA_IN + +at 0x00000240 : */ 0x810b0000,0x000008f8, +/* + JUMP sated + +at 0x00000242 : */ 0x80080000,0x00000918, +/* +spew_dataout: + MOVE 1, NCR53c7xx_zero, WHEN DATA_OUT + +at 0x00000244 : */ 0x08000001,0x00000000, +/* +sated: + MOVE SCNTL2 & 0x7f TO SCNTL2 + +at 0x00000246 : */ 0x7c027f00,0x00000000, +/* + MOVE 1, NCR53c7xx_msg_abort, WHEN MSG_OUT + +at 0x00000248 : */ 0x0e000001,0x00000000, +/* + WAIT DISCONNECT + +at 0x0000024a : */ 0x48000000,0x00000000, +/* + INT int_norm_aborted + +at 0x0000024c : */ 0x98080000,0x02040000, +/* + +; +; dsa_to_scratch +; scratch_to_dsa +; +; PURPOSE : +; The NCR chips cannot do a move memory instruction with the DSA register +; as the source or destination. So, we provide a couple of subroutines +; that let us switch between the DSA register and scratch register. +; +; Memory moves to/from the DSPS register also don't work, but we +; don't use them. +; +; + + +dsa_to_scratch: + MOVE DSA0 TO SFBR + +at 0x0000024e : */ 0x72100000,0x00000000, +/* + MOVE SFBR TO SCRATCH0 + +at 0x00000250 : */ 0x6a340000,0x00000000, +/* + MOVE DSA1 TO SFBR + +at 0x00000252 : */ 0x72110000,0x00000000, +/* + MOVE SFBR TO SCRATCH1 + +at 0x00000254 : */ 0x6a350000,0x00000000, +/* + MOVE DSA2 TO SFBR + +at 0x00000256 : */ 0x72120000,0x00000000, +/* + MOVE SFBR TO SCRATCH2 + +at 0x00000258 : */ 0x6a360000,0x00000000, +/* + MOVE DSA3 TO SFBR + +at 0x0000025a : */ 0x72130000,0x00000000, +/* + MOVE SFBR TO SCRATCH3 + +at 0x0000025c : */ 0x6a370000,0x00000000, +/* + RETURN + +at 0x0000025e : */ 0x90080000,0x00000000, +/* + +scratch_to_dsa: + MOVE SCRATCH0 TO SFBR + +at 0x00000260 : */ 0x72340000,0x00000000, +/* + MOVE SFBR TO DSA0 + +at 0x00000262 : */ 0x6a100000,0x00000000, +/* + MOVE SCRATCH1 TO SFBR + +at 0x00000264 : */ 0x72350000,0x00000000, +/* + MOVE SFBR TO DSA1 + +at 0x00000266 : */ 0x6a110000,0x00000000, +/* + MOVE SCRATCH2 TO SFBR + +at 0x00000268 : */ 0x72360000,0x00000000, +/* + MOVE SFBR TO DSA2 + +at 0x0000026a : */ 0x6a120000,0x00000000, +/* + MOVE SCRATCH3 TO SFBR + +at 0x0000026c : */ 0x72370000,0x00000000, +/* + MOVE SFBR TO DSA3 + +at 0x0000026e : */ 0x6a130000,0x00000000, +/* + RETURN + +at 0x00000270 : */ 0x90080000,0x00000000, +}; + +#define A_NCR53c7xx_msg_abort 0x00000000 +u32 A_NCR53c7xx_msg_abort_used[] = { + 0x00000249, +}; + +#define A_NCR53c7xx_msg_reject 0x00000000 +u32 A_NCR53c7xx_msg_reject_used[] = { + 0x00000172, +}; + +#define A_NCR53c7xx_sink 0x00000000 +u32 A_NCR53c7xx_sink_used[] = { + 0x00000233, + 0x00000239, + 0x0000023f, +}; + +#define A_NCR53c7xx_zero 0x00000000 +u32 A_NCR53c7xx_zero_used[] = { + 0x0000022f, + 0x00000245, +}; + +#define A_NOP_insn 0x00000000 +u32 A_NOP_insn_used[] = { + 0x00000010, +}; + +#define A_addr_reconnect_dsa_head 0x00000000 +u32 A_addr_reconnect_dsa_head_used[] = { + 0x000001a7, +}; + +#define A_addr_scratch 0x00000000 +u32 A_addr_scratch_used[] = { + 0x00000004, + 0x0000001b, + 0x00000046, + 0x00000067, + 0x00000073, + 0x000000b0, + 0x000000c6, + 0x00000128, + 0x00000141, + 0x000001a1, + 0x000001ce, +}; + +#define A_addr_temp 0x00000000 +u32 A_addr_temp_used[] = { + 0x00000025, + 0x00000034, +}; + +#define A_dmode_memory_to_memory 0x00000000 +u32 A_dmode_memory_to_memory_used[] = { + 0x00000005, + 0x0000001c, + 0x00000027, + 0x00000035, + 0x00000047, + 0x00000069, + 0x00000075, + 0x000000b2, + 0x000000c8, + 0x0000012a, + 0x00000143, + 0x00000199, + 0x000001a2, + 0x000001d0, +}; + +#define A_dmode_memory_to_ncr 0x00000000 +u32 A_dmode_memory_to_ncr_used[] = { + 0x00000000, + 0x00000017, + 0x00000030, + 0x00000042, + 0x0000019d, +}; + +#define A_dmode_ncr_to_memory 0x00000000 +u32 A_dmode_ncr_to_memory_used[] = { + 0x00000022, + 0x00000064, + 0x00000070, + 0x000000ad, + 0x000000c3, + 0x00000125, + 0x0000013e, + 0x000001cb, +}; + +#define A_dsa_check_reselect 0x00000000 +u32 A_dsa_check_reselect_used[] = { + 0x000001bd, +}; + +#define A_dsa_cmdout 0x00000048 +u32 A_dsa_cmdout_used[] = { + 0x00000094, +}; + +#define A_dsa_cmnd 0x00000038 +u32 A_dsa_cmnd_used[] = { +}; + +#define A_dsa_datain 0x00000054 +u32 A_dsa_datain_used[] = { + 0x000000bb, +}; + +#define A_dsa_dataout 0x00000050 +u32 A_dsa_dataout_used[] = { + 0x000000a5, +}; + +#define A_dsa_end 0x00000070 +u32 A_dsa_end_used[] = { +}; + +#define A_dsa_fields_start 0x00000000 +u32 A_dsa_fields_start_used[] = { +}; + +#define A_dsa_msgin 0x00000058 +u32 A_dsa_msgin_used[] = { + 0x00000188, +}; + +#define A_dsa_msgout 0x00000040 +u32 A_dsa_msgout_used[] = { + 0x00000086, +}; + +#define A_dsa_msgout_other 0x00000068 +u32 A_dsa_msgout_other_used[] = { + 0x00000180, +}; + +#define A_dsa_next 0x00000030 +u32 A_dsa_next_used[] = { + 0x0000005c, +}; + +#define A_dsa_restore_pointers 0x00000000 +u32 A_dsa_restore_pointers_used[] = { + 0x0000012e, +}; + +#define A_dsa_save_data_pointer 0x00000000 +u32 A_dsa_save_data_pointer_used[] = { + 0x00000115, +}; + +#define A_dsa_select 0x0000003c +u32 A_dsa_select_used[] = { + 0x00000081, +}; + +#define A_dsa_status 0x00000060 +u32 A_dsa_status_used[] = { + 0x00000184, +}; + +#define A_dsa_temp_addr_array_value 0x00000000 +u32 A_dsa_temp_addr_array_value_used[] = { +}; + +#define A_dsa_temp_addr_dsa_value 0x00000000 +u32 A_dsa_temp_addr_dsa_value_used[] = { + 0x00000003, +}; + +#define A_dsa_temp_addr_new_value 0x00000000 +u32 A_dsa_temp_addr_new_value_used[] = { +}; + +#define A_dsa_temp_addr_next 0x00000000 +u32 A_dsa_temp_addr_next_used[] = { + 0x00000015, + 0x0000004e, +}; + +#define A_dsa_temp_addr_residual 0x00000000 +u32 A_dsa_temp_addr_residual_used[] = { + 0x0000002a, + 0x00000039, +}; + +#define A_dsa_temp_addr_saved_pointer 0x00000000 +u32 A_dsa_temp_addr_saved_pointer_used[] = { + 0x00000026, + 0x00000033, +}; + +#define A_dsa_temp_addr_saved_residual 0x00000000 +u32 A_dsa_temp_addr_saved_residual_used[] = { + 0x0000002b, + 0x00000038, +}; + +#define A_dsa_temp_lun 0x00000000 +u32 A_dsa_temp_lun_used[] = { + 0x0000004b, +}; + +#define A_dsa_temp_next 0x00000000 +u32 A_dsa_temp_next_used[] = { + 0x0000001a, +}; + +#define A_dsa_temp_sync 0x00000000 +u32 A_dsa_temp_sync_used[] = { + 0x00000053, +}; + +#define A_dsa_temp_target 0x00000000 +u32 A_dsa_temp_target_used[] = { + 0x00000040, +}; + +#define A_int_debug_break 0x03000000 +u32 A_int_debug_break_used[] = { + 0x00000217, +}; + +#define A_int_debug_panic 0x030b0000 +u32 A_int_debug_panic_used[] = { + 0x000001e8, + 0x000001f8, +}; + +#define A_int_err_check_condition 0x00030000 +u32 A_int_err_check_condition_used[] = { + 0x00000194, +}; + +#define A_int_err_no_phase 0x00040000 +u32 A_int_err_no_phase_used[] = { +}; + +#define A_int_err_selected 0x00010000 +u32 A_int_err_selected_used[] = { + 0x000001da, +}; + +#define A_int_err_unexpected_phase 0x00000000 +u32 A_int_err_unexpected_phase_used[] = { + 0x0000008c, + 0x00000092, + 0x0000009a, + 0x000000d0, + 0x000000d4, + 0x000000d6, + 0x000000de, + 0x000000e2, + 0x000000e4, + 0x000000ec, + 0x000000f0, + 0x000000f2, + 0x000000f4, + 0x0000014c, +}; + +#define A_int_err_unexpected_reselect 0x00020000 +u32 A_int_err_unexpected_reselect_used[] = { + 0x000001ba, +}; + +#define A_int_msg_1 0x01020000 +u32 A_int_msg_1_used[] = { + 0x0000010e, + 0x00000110, +}; + +#define A_int_msg_sdtr 0x01010000 +u32 A_int_msg_sdtr_used[] = { + 0x0000016c, +}; + +#define A_int_msg_wdtr 0x01000000 +u32 A_int_msg_wdtr_used[] = { + 0x00000160, +}; + +#define A_int_norm_aborted 0x02040000 +u32 A_int_norm_aborted_used[] = { + 0x0000024d, +}; + +#define A_int_norm_command_complete 0x02020000 +u32 A_int_norm_command_complete_used[] = { +}; + +#define A_int_norm_disconnected 0x02030000 +u32 A_int_norm_disconnected_used[] = { +}; + +#define A_int_norm_reselect_complete 0x02010000 +u32 A_int_norm_reselect_complete_used[] = { +}; + +#define A_int_norm_reset 0x02050000 +u32 A_int_norm_reset_used[] = { +}; + +#define A_int_norm_select_complete 0x02000000 +u32 A_int_norm_select_complete_used[] = { +}; + +#define A_int_test_1 0x04000000 +u32 A_int_test_1_used[] = { + 0x000001fd, +}; + +#define A_int_test_2 0x04010000 +u32 A_int_test_2_used[] = { + 0x00000215, +}; + +#define A_int_test_3 0x04020000 +u32 A_int_test_3_used[] = { +}; + +#define A_msg_buf 0x00000000 +u32 A_msg_buf_used[] = { + 0x00000102, + 0x0000014e, + 0x00000158, + 0x0000015e, + 0x00000164, + 0x0000016a, +}; + +#define A_reconnect_dsa_head 0x00000000 +u32 A_reconnect_dsa_head_used[] = { + 0x0000006c, + 0x00000074, + 0x000001a0, +}; + +#define A_reselected_identify 0x00000000 +u32 A_reselected_identify_used[] = { + 0x00000045, + 0x0000019c, +}; + +#define A_reselected_tag 0x00000000 +u32 A_reselected_tag_used[] = { +}; + +#define A_schedule 0x00000000 +u32 A_schedule_used[] = { + 0x0000007e, + 0x00000192, + 0x000001e2, + 0x0000021f, +}; + +#define A_test_dest 0x00000000 +u32 A_test_dest_used[] = { + 0x000001fb, +}; + +#define A_test_src 0x00000000 +u32 A_test_src_used[] = { + 0x000001fa, +}; + +#define Ent_accept_message 0x000005d4 +#define Ent_cmdout_cmdout 0x0000024c +#define Ent_command_complete 0x0000060c +#define Ent_command_complete_msgin 0x0000061c +#define Ent_data_transfer 0x00000254 +#define Ent_datain_to_jump 0x00000328 +#define Ent_debug_break 0x00000858 +#define Ent_dsa_code_begin 0x00000000 +#define Ent_dsa_code_check_reselect 0x000000f8 +#define Ent_dsa_code_fix_jump 0x0000003c +#define Ent_dsa_code_restore_pointers 0x000000c0 +#define Ent_dsa_code_save_data_pointer 0x00000088 +#define Ent_dsa_code_template 0x00000000 +#define Ent_dsa_code_template_end 0x00000168 +#define Ent_dsa_schedule 0x00000168 +#define Ent_dsa_zero 0x00000168 +#define Ent_end_data_transfer 0x0000028c +#define Ent_initiator_abort 0x00000880 +#define Ent_msg_in 0x00000404 +#define Ent_msg_in_restart 0x000003e4 +#define Ent_other_in 0x00000374 +#define Ent_other_out 0x0000033c +#define Ent_other_transfer 0x000003ac +#define Ent_reject_message 0x000005b4 +#define Ent_reselected_check_next 0x000006a4 +#define Ent_reselected_ok 0x00000750 +#define Ent_respond_message 0x000005ec +#define Ent_select 0x000001fc +#define Ent_select_msgout 0x00000214 +#define Ent_target_abort 0x00000860 +#define Ent_test_1 0x000007e4 +#define Ent_test_2 0x000007f8 +#define Ent_test_2_msgout 0x00000810 +#define Ent_wait_reselect 0x00000654 +u32 LABELPATCHES[] = { + 0x00000008, + 0x0000000a, + 0x00000013, + 0x00000016, + 0x0000001f, + 0x00000021, + 0x0000004f, + 0x00000051, + 0x0000005b, + 0x00000068, + 0x0000006f, + 0x00000082, + 0x00000084, + 0x0000008a, + 0x0000008e, + 0x00000090, + 0x00000096, + 0x00000098, + 0x0000009c, + 0x0000009e, + 0x000000a0, + 0x000000a2, + 0x000000a4, + 0x000000b1, + 0x000000b6, + 0x000000ba, + 0x000000c7, + 0x000000cc, + 0x000000d2, + 0x000000d8, + 0x000000da, + 0x000000e0, + 0x000000e6, + 0x000000e8, + 0x000000ee, + 0x000000f6, + 0x000000f8, + 0x00000104, + 0x00000106, + 0x00000108, + 0x0000010a, + 0x0000010c, + 0x00000112, + 0x00000114, + 0x00000129, + 0x00000142, + 0x00000148, + 0x00000150, + 0x00000152, + 0x00000154, + 0x0000015a, + 0x00000166, + 0x00000196, + 0x000001a5, + 0x000001a8, + 0x000001ac, + 0x000001b0, + 0x000001b4, + 0x000001b8, + 0x000001cf, + 0x000001de, + 0x000001e6, + 0x000001ec, + 0x000001ee, + 0x000001f2, + 0x000001f6, + 0x00000201, + 0x00000203, + 0x00000223, + 0x00000225, + 0x00000227, + 0x00000229, + 0x0000022b, + 0x0000022d, + 0x00000231, + 0x00000235, + 0x00000237, + 0x0000023b, + 0x0000023d, + 0x00000241, + 0x00000243, +}; + +struct { + u32 offset; + void *address; +} EXTERNAL_PATCHES[] = { +}; + +u32 INSTRUCTIONS = 301; +u32 PATCHES = 81; +u32 EXTERNAL_PATCHES_LEN = 0; diff --git a/linux/src/drivers/scsi/53c8xx_u.h b/linux/src/drivers/scsi/53c8xx_u.h new file mode 100644 index 00000000..c3d486fe --- /dev/null +++ b/linux/src/drivers/scsi/53c8xx_u.h @@ -0,0 +1,97 @@ +#undef A_NCR53c7xx_msg_abort +#undef A_NCR53c7xx_msg_reject +#undef A_NCR53c7xx_sink +#undef A_NCR53c7xx_zero +#undef A_NOP_insn +#undef A_addr_reconnect_dsa_head +#undef A_addr_scratch +#undef A_addr_temp +#undef A_dmode_memory_to_memory +#undef A_dmode_memory_to_ncr +#undef A_dmode_ncr_to_memory +#undef A_dsa_check_reselect +#undef A_dsa_cmdout +#undef A_dsa_cmnd +#undef A_dsa_datain +#undef A_dsa_dataout +#undef A_dsa_end +#undef A_dsa_fields_start +#undef A_dsa_msgin +#undef A_dsa_msgout +#undef A_dsa_msgout_other +#undef A_dsa_next +#undef A_dsa_restore_pointers +#undef A_dsa_save_data_pointer +#undef A_dsa_select +#undef A_dsa_status +#undef A_dsa_temp_addr_array_value +#undef A_dsa_temp_addr_dsa_value +#undef A_dsa_temp_addr_new_value +#undef A_dsa_temp_addr_next +#undef A_dsa_temp_addr_residual +#undef A_dsa_temp_addr_saved_pointer +#undef A_dsa_temp_addr_saved_residual +#undef A_dsa_temp_lun +#undef A_dsa_temp_next +#undef A_dsa_temp_sync +#undef A_dsa_temp_target +#undef A_int_debug_break +#undef A_int_debug_panic +#undef A_int_err_check_condition +#undef A_int_err_no_phase +#undef A_int_err_selected +#undef A_int_err_unexpected_phase +#undef A_int_err_unexpected_reselect +#undef A_int_msg_1 +#undef A_int_msg_sdtr +#undef A_int_msg_wdtr +#undef A_int_norm_aborted +#undef A_int_norm_command_complete +#undef A_int_norm_disconnected +#undef A_int_norm_reselect_complete +#undef A_int_norm_reset +#undef A_int_norm_select_complete +#undef A_int_test_1 +#undef A_int_test_2 +#undef A_int_test_3 +#undef A_msg_buf +#undef A_reconnect_dsa_head +#undef A_reselected_identify +#undef A_reselected_tag +#undef A_schedule +#undef A_test_dest +#undef A_test_src +#undef Ent_accept_message +#undef Ent_cmdout_cmdout +#undef Ent_command_complete +#undef Ent_command_complete_msgin +#undef Ent_data_transfer +#undef Ent_datain_to_jump +#undef Ent_debug_break +#undef Ent_dsa_code_begin +#undef Ent_dsa_code_check_reselect +#undef Ent_dsa_code_fix_jump +#undef Ent_dsa_code_restore_pointers +#undef Ent_dsa_code_save_data_pointer +#undef Ent_dsa_code_template +#undef Ent_dsa_code_template_end +#undef Ent_dsa_schedule +#undef Ent_dsa_zero +#undef Ent_end_data_transfer +#undef Ent_initiator_abort +#undef Ent_msg_in +#undef Ent_msg_in_restart +#undef Ent_other_in +#undef Ent_other_out +#undef Ent_other_transfer +#undef Ent_reject_message +#undef Ent_reselected_check_next +#undef Ent_reselected_ok +#undef Ent_respond_message +#undef Ent_select +#undef Ent_select_msgout +#undef Ent_target_abort +#undef Ent_test_1 +#undef Ent_test_2 +#undef Ent_test_2_msgout +#undef Ent_wait_reselect diff --git a/linux/src/drivers/scsi/AM53C974.c b/linux/src/drivers/scsi/AM53C974.c new file mode 100644 index 00000000..9ff5a2e4 --- /dev/null +++ b/linux/src/drivers/scsi/AM53C974.c @@ -0,0 +1,2274 @@ +#include <linux/module.h> + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/string.h> +#include <linux/blk.h> + +#include <asm/io.h> +#include <asm/system.h> + +#include "scsi.h" +#include "hosts.h" +#include "AM53C974.h" +#include "constants.h" +#include "sd.h" + +/* AM53/79C974 (PCscsi) driver release 0.5 + * + * The architecture and much of the code of this device + * driver was originally developed by Drew Eckhardt for + * the NCR5380. The following copyrights apply: + * For the architecture and all pieces of code which can also be found + * in the NCR5380 device driver: + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 666-5836 + * + * The AM53C974_nobios_detect code was originally developed by + * Robin Cutshaw (robin@xfree86.org) and is used here in a + * slightly modified form. + * + * For the remaining code: + * Copyright 1994, D. Frieauff + * EMail: fri@rsx42sun0.dofn.de + * Phone: x49-7545-8-2256 , x49-7541-42305 + */ + +/* + * $Log: AM53C974.c,v $ + */ + +#ifdef AM53C974_DEBUG + #define DEB(x) x + #ifdef AM53C974_DEBUG_KEYWAIT + #define KEYWAIT() AM53C974_keywait() + #else + #define KEYWAIT() + #endif + #ifdef AM53C974_DEBUG_INIT + #define DEB_INIT(x) x + #else + #define DEB_INIT(x) + #endif + #ifdef AM53C974_DEBUG_MSG + #define DEB_MSG(x) x + #else + #define DEB_MSG(x) + #endif + #ifdef AM53C974_DEB_RESEL + #define DEB_RESEL(x) x + #else + #define DEB_RESEL(x) + #endif + #ifdef AM53C974_DEBUG_QUEUE + #define DEB_QUEUE(x) x + #define LIST(x,y) {printk("LINE:%d Adding %p to %p\n", __LINE__, (void*)(x), (void*)(y)); if ((x)==(y)) udelay(5); } + #define REMOVE(w,x,y,z) {printk("LINE:%d Removing: %p->%p %p->%p \n", __LINE__, (void*)(w), (void*)(x), (void*)(y), (void*)(z)); if ((x)==(y)) udelay(5); } + #else + #define DEB_QUEUE(x) + #define LIST(x,y) + #define REMOVE(w,x,y,z) + #endif + #ifdef AM53C974_DEBUG_INFO + #define DEB_INFO(x) x + #else + #define DEB_INFO(x) + #endif + #ifdef AM53C974_DEBUG_LINKED + #define DEB_LINKED(x) x + #else + #define DEB_LINKED(x) + #endif + #ifdef AM53C974_DEBUG_INTR + #define DEB_INTR(x) x + #else + #define DEB_INTR(x) + #endif +#else + #define DEB_INIT(x) + #define DEB(x) + #define DEB_QUEUE(x) + #define LIST(x,y) + #define REMOVE(w,x,y,z) + #define DEB_INFO(x) + #define DEB_LINKED(x) + #define DEB_INTR(x) + #define DEB_MSG(x) + #define DEB_RESEL(x) + #define KEYWAIT() +#endif + #ifdef AM53C974_DEBUG_ABORT + #define DEB_ABORT(x) x + #else + #define DEB_ABORT(x) + #endif + +#ifdef VERBOSE_AM53C974_DEBUG +#define VDEB(x) x +#else +#define VDEB(x) +#endif + +#define INSIDE(x,l,h) ( ((x) >= (l)) && ((x) <= (h)) ) + +#ifdef AM53C974_DEBUG +static void AM53C974_print_pci(struct Scsi_Host *instance); +static void AM53C974_print_phase(struct Scsi_Host *instance); +static void AM53C974_print_queues(struct Scsi_Host *instance); +#endif /* AM53C974_DEBUG */ +static void AM53C974_print(struct Scsi_Host *instance); +static void AM53C974_keywait(void); +static int AM53C974_bios_detect(Scsi_Host_Template *tpnt); +static int AM53C974_nobios_detect(Scsi_Host_Template *tpnt); +static int AM53C974_init(Scsi_Host_Template *tpnt, pci_config_t pci_config); +static void AM53C974_config_after_reset(struct Scsi_Host *instance); +static __inline__ void initialize_SCp(Scsi_Cmnd *cmd); +static __inline__ void run_main(void); +static void AM53C974_main (void); +static void AM53C974_intr(int irq, void *dev_id, struct pt_regs *regs); +static void AM53C974_intr_disconnect(struct Scsi_Host *instance); +static int AM53C974_sync_neg(struct Scsi_Host *instance, int target, unsigned char *msg); +static __inline__ void AM53C974_set_async(struct Scsi_Host *instance, int target); +static __inline__ void AM53C974_set_sync(struct Scsi_Host *instance, int target); +static void AM53C974_information_transfer(struct Scsi_Host *instance, + unsigned char statreg, unsigned char isreg, + unsigned char instreg, unsigned char cfifo, + unsigned char dmastatus); +static int AM53C974_message(struct Scsi_Host *instance, Scsi_Cmnd *cmd, unsigned char msg); +static void AM53C974_select(struct Scsi_Host *instance, Scsi_Cmnd *cmd, int tag); +static void AM53C974_intr_reselect(struct Scsi_Host *instance, unsigned char statreg); +static __inline__ void AM53C974_transfer_dma(struct Scsi_Host *instance, short dir, + unsigned long length, char *data); +static void AM53C974_dma_blast(struct Scsi_Host *instance, unsigned char dmastatus, + unsigned char statreg); +static void AM53C974_intr_bus_reset(struct Scsi_Host *instance); + +static struct Scsi_Host *first_instance = NULL; +static Scsi_Host_Template *the_template = NULL; +static struct Scsi_Host *first_host = NULL; /* Head of list of AMD boards */ +static volatile int main_running = 0; +static int commandline_current = 0; +override_t overrides[7] = { {-1, 0, 0, 0}, }; /* LILO overrides */ + +struct proc_dir_entry proc_scsi_am53c974 = { + PROC_SCSI_AM53C974, 8, "am53c974", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#ifdef AM53C974_DEBUG +static int deb_stop = 1; + +/************************************************************************** + * Function : void AM53C974_print_pci(struct Scsi_Host *instance) + * + * Purpose : dump the PCI registers for debugging purposes + * + * Input : instance - which AM53C974 + **************************************************************************/ +static void AM53C974_print_pci(struct Scsi_Host *instance) +{ +int i; +unsigned short vendor_id, device_id, command, status, scratch[8]; +unsigned long class_revision, base; +unsigned char irq, cache_line_size, latency_timer, header_type; + +AM53C974_PCIREG_OPEN(); + +for (i = 0; i < 8; i++) *(scratch + i) = AM53C974_PCIREG_READ_WORD(instance, PCI_SCRATCH_REG_0 + 2*i); +vendor_id = AM53C974_PCIREG_READ_WORD(instance, PCI_VENDOR_ID); +device_id = AM53C974_PCIREG_READ_WORD(instance, PCI_DEVICE_ID); +command = AM53C974_PCIREG_READ_WORD(instance, PCI_COMMAND); +status = AM53C974_PCIREG_READ_WORD(instance, PCI_STATUS); +class_revision = AM53C974_PCIREG_READ_DWORD(instance, PCI_CLASS_REVISION); +cache_line_size = AM53C974_PCIREG_READ_BYTE(instance, PCI_CACHE_LINE_SIZE); +latency_timer = AM53C974_PCIREG_READ_BYTE(instance, PCI_LATENCY_TIMER); +header_type = AM53C974_PCIREG_READ_BYTE(instance, PCI_HEADER_TYPE); +base = AM53C974_PCIREG_READ_DWORD(instance, PCI_BASE_ADDRESS_0); +irq = AM53C974_PCIREG_READ_BYTE(instance, PCI_INTERRUPT_LINE); + +AM53C974_PCIREG_CLOSE(); + + +printk("------------- start of PCI register dump -------------\n"); +printk("PCI_VENDOR_ID: 0x%x\n", vendor_id); +printk("PCI_DEVICE_ID: 0x%x\n", device_id); +printk("PCI_COMMAND: 0x%x\n", command); +printk("PCI_STATUS: 0x%x\n", status); +printk("PCI_CLASS_REVISION: 0x%lx\n", class_revision); +printk("PCI_CACHE_LINE_SIZE: 0x%x\n", cache_line_size); +printk("PCI_LATENCY_TIMER: 0x%x\n", latency_timer); +printk("PCI_HEADER_TYPE: 0x%x\n", header_type); +printk("PCI_BASE_ADDRESS_0: 0x%lx\n", base); +printk("PCI_INTERRUPT_LINE: %d\n", irq); +for (i = 0; i < 8; i++) printk("PCI_SCRATCH_%d: 0x%x\n", i, scratch[i]); +printk("------------- end of PCI register dump -------------\n\n"); +} + +static struct { + unsigned char value; + char *name; +} phases[] = { +{PHASE_DATAOUT, "DATAOUT"}, {PHASE_DATAIN, "DATAIN"}, {PHASE_CMDOUT, "CMDOUT"}, +{PHASE_STATIN, "STATIN"}, {PHASE_MSGOUT, "MSGOUT"}, {PHASE_MSGIN, "MSGIN"}, +{PHASE_RES_0, "RESERVED 0"}, {PHASE_RES_1, "RESERVED 1"}}; + +/************************************************************************** + * Function : void AM53C974_print_phase(struct Scsi_Host *instance) + * + * Purpose : print the current SCSI phase for debugging purposes + * + * Input : instance - which AM53C974 + **************************************************************************/ +static void AM53C974_print_phase(struct Scsi_Host *instance) +{ +AM53C974_local_declare(); +unsigned char statreg, latched; +int i; +AM53C974_setio(instance); + +latched = (AM53C974_read_8(CNTLREG2)) & CNTLREG2_ENF; +statreg = AM53C974_read_8(STATREG); +for (i = 0; (phases[i].value != PHASE_RES_1) && + (phases[i].value != (statreg & STATREG_PHASE)); ++i); +if (latched) + printk("scsi%d : phase %s, latched at end of last command\n", instance->host_no, phases[i].name); + else + printk("scsi%d : phase %s, real time\n", instance->host_no, phases[i].name); +} + +/************************************************************************** + * Function : void AM53C974_print_queues(struct Scsi_Host *instance) + * + * Purpose : print commands in the various queues + * + * Inputs : instance - which AM53C974 + **************************************************************************/ +static void AM53C974_print_queues(struct Scsi_Host *instance) +{ +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +Scsi_Cmnd *ptr; + +printk("AM53C974: coroutine is%s running.\n", main_running ? "" : "n't"); + +cli(); + +if (!hostdata->connected) { + printk ("scsi%d: no currently connected command\n", instance->host_no); } + else { + print_Scsi_Cmnd ((Scsi_Cmnd *)hostdata->connected); } +if (!hostdata->sel_cmd) { + printk ("scsi%d: no currently arbitrating command\n", instance->host_no); } + else { + print_Scsi_Cmnd ((Scsi_Cmnd *)hostdata->sel_cmd); } + +printk ("scsi%d: issue_queue ", instance->host_no); +if (!hostdata->issue_queue) + printk("empty\n"); + else { + printk(":\n"); + for (ptr = (Scsi_Cmnd *)hostdata->issue_queue; ptr; ptr = (Scsi_Cmnd *)ptr->host_scribble) + print_Scsi_Cmnd (ptr); } + +printk ("scsi%d: disconnected_queue ", instance->host_no); +if (!hostdata->disconnected_queue) + printk("empty\n"); + else { + printk(":\n"); + for (ptr = (Scsi_Cmnd *) hostdata->disconnected_queue; ptr; ptr = (Scsi_Cmnd *)ptr->host_scribble) + print_Scsi_Cmnd (ptr); } + +sti(); +} + +#endif /* AM53C974_DEBUG */ + +/************************************************************************** + * Function : void AM53C974_print(struct Scsi_Host *instance) + * + * Purpose : dump the chip registers for debugging purposes + * + * Input : instance - which AM53C974 + **************************************************************************/ +static void AM53C974_print(struct Scsi_Host *instance) +{ +AM53C974_local_declare(); +unsigned long ctcreg, dmastc, dmaspa, dmawbc, dmawac; +unsigned char cmdreg, statreg, isreg, cfireg, cntlreg[4], dmacmd, dmastatus; +AM53C974_setio(instance); + +cli(); +ctcreg = AM53C974_read_8(CTCHREG) << 16; +ctcreg |= AM53C974_read_8(CTCMREG) << 8; +ctcreg |= AM53C974_read_8(CTCLREG); +cmdreg = AM53C974_read_8(CMDREG); +statreg = AM53C974_read_8(STATREG); +isreg = AM53C974_read_8(ISREG); +cfireg = AM53C974_read_8(CFIREG); +cntlreg[0] = AM53C974_read_8(CNTLREG1); +cntlreg[1] = AM53C974_read_8(CNTLREG2); +cntlreg[2] = AM53C974_read_8(CNTLREG3); +cntlreg[3] = AM53C974_read_8(CNTLREG4); +dmacmd = AM53C974_read_8(DMACMD); +dmastc = AM53C974_read_32(DMASTC); +dmaspa = AM53C974_read_32(DMASPA); +dmawbc = AM53C974_read_32(DMAWBC); +dmawac = AM53C974_read_32(DMAWAC); +dmastatus = AM53C974_read_8(DMASTATUS); +sti(); + +printk("AM53C974 register dump:\n"); +printk("IO base: 0x%04lx; CTCREG: 0x%04lx; CMDREG: 0x%02x; STATREG: 0x%02x; ISREG: 0x%02x\n", + io_port, ctcreg, cmdreg, statreg, isreg); +printk("CFIREG: 0x%02x; CNTLREG1-4: 0x%02x; 0x%02x; 0x%02x; 0x%02x\n", + cfireg, cntlreg[0], cntlreg[1], cntlreg[2], cntlreg[3]); +printk("DMACMD: 0x%02x; DMASTC: 0x%04lx; DMASPA: 0x%04lx\n", dmacmd, dmastc, dmaspa); +printk("DMAWBC: 0x%04lx; DMAWAC: 0x%04lx; DMASTATUS: 0x%02x\n", dmawbc, dmawac, dmastatus); +printk("---------------------------------------------------------\n"); +} + +/************************************************************************** +* Function : void AM53C974_keywait(void) +* +* Purpose : wait until a key is pressed, if it was the 'r' key leave singlestep mode; +* this function is used for debugging only +* +* Input : none +**************************************************************************/ +static void AM53C974_keywait(void) +{ +#ifdef AM53C974_DEBUG +int key; + +if (!deb_stop) return; +#endif + +cli(); +while ((inb_p(0x64) & 0x01) != 0x01) ; +#ifdef AM53C974_DEBUG +key = inb(0x60); +if (key == 0x93) deb_stop = 0; /* don't stop if 'r' was pressed */ +#endif +sti(); +} + +/************************************************************************** +* Function : AM53C974_setup(char *str, int *ints) +* +* Purpose : LILO command line initialization of the overrides array, +* +* Inputs : str - unused, ints - array of integer parameters with ints[0] +* equal to the number of ints. +* +* NOTE : this function needs to be declared as an external function +* in init/main.c and included there in the bootsetups list +***************************************************************************/ +void AM53C974_setup(char *str, int *ints) +{ +if (ints[0] < 4) + printk("AM53C974_setup: wrong number of parameters;\n correct syntax is: AM53C974=host-scsi-id, target-scsi-id, max-rate, max-offset\n"); + else { + if (commandline_current < (sizeof(overrides) / sizeof(override_t))) { + if ((ints[1] < 0) || (ints[1] > 7) || + (ints[2] < 0) || (ints[2] > 7) || + (ints[1] == ints[2]) || + (ints[3] < (DEF_CLK / MAX_PERIOD)) || (ints[3] > (DEF_CLK / MIN_PERIOD)) || + (ints[4] < 0) || (ints[4] > MAX_OFFSET)) + printk("AM53C974_setup: illegal parameter\n"); + else { + overrides[commandline_current].host_scsi_id = ints[1]; + overrides[commandline_current].target_scsi_id = ints[2]; + overrides[commandline_current].max_rate = ints[3]; + overrides[commandline_current].max_offset = ints[4]; + commandline_current++; } + } + else + printk("AM53C974_setup: too many overrides\n"); + } +} + +#if defined (CONFIG_PCI) +/************************************************************************** +* Function : int AM53C974_bios_detect(Scsi_Host_Template *tpnt) +* +* Purpose : detects and initializes AM53C974 SCSI chips with PCI Bios +* +* Inputs : tpnt - host template +* +* Returns : number of host adapters detected +**************************************************************************/ +int AM53C974_bios_detect(Scsi_Host_Template *tpnt) +{ +int count = 0; /* number of boards detected */ +int pci_index; +pci_config_t pci_config; + +for (pci_index = 0; pci_index <= 16; ++pci_index) { + unsigned char pci_bus, pci_device_fn; + if (pcibios_find_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_SCSI, pci_index, &pci_bus, &pci_device_fn) != 0) + break; + + pcibios_read_config_word(pci_bus, pci_device_fn, PCI_VENDOR_ID, &pci_config._vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, PCI_DEVICE_ID, &pci_config._device); + pcibios_read_config_word(pci_bus, pci_device_fn, PCI_COMMAND, &pci_config._command); + pcibios_read_config_word(pci_bus, pci_device_fn, PCI_STATUS, &pci_config._status); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_CLASS_REVISION, &pci_config._class_revision); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_CACHE_LINE_SIZE, &pci_config._cache_line_size); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_LATENCY_TIMER, &pci_config._latency_timer); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_HEADER_TYPE, &pci_config._header_type); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_BIST, &pci_config._bist); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_0, &pci_config._base0); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_1, &pci_config._base1); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_2, &pci_config._base2); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_3, &pci_config._base3); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_4, &pci_config._base4); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_5, &pci_config._base5); + pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_ROM_ADDRESS, &pci_config._baserom); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_INTERRUPT_LINE, &pci_config._int_line); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_INTERRUPT_PIN, &pci_config._int_pin); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_MIN_GNT, &pci_config._min_gnt); + pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_MAX_LAT, &pci_config._max_lat); + pci_config._pcibus = 0xFFFFFFFF; + pci_config._cardnum = 0xFFFFFFFF; + + /* check whether device is I/O mapped -- should be */ + if (!(pci_config._command & PCI_COMMAND_IO)) continue; + + /* PCI Spec 2.1 states that it is either the driver's or the PCI card's responsibility + to set the PCI Master Enable Bit if needed. + (from Mark Stockton <marks@schooner.sys.hou.compaq.com>) */ + if (!(pci_config._command & PCI_COMMAND_MASTER)) { + pci_config._command |= PCI_COMMAND_MASTER; + printk("PCI Master Bit has not been set. Setting...\n"); + pcibios_write_config_word(pci_bus, pci_device_fn, PCI_COMMAND, pci_config._command); } + + /* everything seems OK now, so initialize */ + if (AM53C974_init(tpnt, pci_config)) count++ ; + } +return (count); +} +#endif + +/************************************************************************** +* Function : int AM53C974_nobios_detect(Scsi_Host_Template *tpnt) +* +* Purpose : detects and initializes AM53C974 SCSI chips using PCI config 2 +* +* Inputs : tpnt - host template +* +* Returns : number of host adapters detected +* +* NOTE : This code assumes the controller on PCI bus 0. +* +* Origin: Robin Cutshaw (robin@xfree86.org) +**************************************************************************/ +int AM53C974_nobios_detect(Scsi_Host_Template *tpnt) +{ +int count = 0; /* number of boards detected */ +pci_config_t pci_config; + +/* first try PCI config method 1 */ +for (pci_config._pcibus = 0; pci_config._pcibus < 0x10; pci_config._pcibus++) { + for (pci_config._cardnum = 0; pci_config._cardnum < 0x20; pci_config._cardnum++) { + unsigned long config_cmd; + config_cmd = 0x80000000 | (pci_config._pcibus<<16) | (pci_config._cardnum<<11); + + outl(config_cmd, 0xCF8); /* ioreg 0 */ + pci_config._device_vendor = inl(0xCFC); + + if ((pci_config._vendor == PCI_VENDOR_ID_AMD) && (pci_config._device == PCI_DEVICE_ID_AMD_SCSI)) { + outl(config_cmd | PCI_COMMAND, 0xCF8); pci_config._status_command = inl(0xCFC); + outl(config_cmd | PCI_CLASS_REVISION, 0xCF8); pci_config._class_revision = inl(0xCFC); + outl(config_cmd | PCI_CACHE_LINE_SIZE, 0xCF8); pci_config._bist_header_latency_cache = inl(0xCFC); + outl(config_cmd | PCI_BASE_ADDRESS_0, 0xCF8); pci_config._base0 = inl(0xCFC); + outl(config_cmd | PCI_BASE_ADDRESS_1, 0xCF8); pci_config._base1 = inl(0xCFC); + outl(config_cmd | PCI_BASE_ADDRESS_2, 0xCF8); pci_config._base2 = inl(0xCFC); + outl(config_cmd | PCI_BASE_ADDRESS_3, 0xCF8); pci_config._base3 = inl(0xCFC); + outl(config_cmd | PCI_BASE_ADDRESS_4, 0xCF8); pci_config._base4 = inl(0xCFC); + outl(config_cmd | PCI_BASE_ADDRESS_5, 0xCF8); pci_config._base5 = inl(0xCFC); + outl(config_cmd | PCI_ROM_ADDRESS, 0xCF8); pci_config._baserom = inl(0xCFC); + outl(config_cmd | PCI_INTERRUPT_LINE, 0xCF8); pci_config._max_min_ipin_iline = inl(0xCFC); + + /* check whether device is I/O mapped -- should be */ + if (!(pci_config._command & PCI_COMMAND_IO)) continue; + + /* PCI Spec 2.1 states that it is either the driver's or the PCI card's responsibility + to set the PCI Master Enable Bit if needed. + From Mark Stockton <marks@schooner.sys.hou.compaq.com> */ + if (!(pci_config._command & PCI_COMMAND_MASTER)) { + pci_config._command |= PCI_COMMAND_MASTER; + printk("Config 1; PCI Master Bit has not been set. Setting...\n"); + outl(config_cmd | PCI_COMMAND, 0xCF8); outw(pci_config._command, 0xCFC); } + + /* everything seems OK now, so initialize */ + if (AM53C974_init(tpnt, pci_config)) count++ ; + } + } + } +outb(0, 0xCF8); /* is this really necessary? */ + +/* try PCI config method 2, if no device was detected by method 1 */ +if (!count) { + AM53C974_PCIREG_OPEN(); + + pci_config._pcibus = 0xFFFFFFFF; + pci_config._cardnum = 0xFFFFFFFF; + + for (pci_config._ioaddr = 0xC000; pci_config._ioaddr < 0xD000; pci_config._ioaddr += 0x0100) { + pci_config._device_vendor = inl(pci_config._ioaddr); + + if ((pci_config._vendor == PCI_VENDOR_ID_AMD) && (pci_config._device == PCI_DEVICE_ID_AMD_SCSI)) { + pci_config._status_command = inl(pci_config._ioaddr + PCI_COMMAND); + pci_config._class_revision = inl(pci_config._ioaddr + PCI_CLASS_REVISION); + pci_config._bist_header_latency_cache = inl(pci_config._ioaddr + PCI_CACHE_LINE_SIZE); + pci_config._base0 = inl(pci_config._ioaddr + PCI_BASE_ADDRESS_0); + pci_config._base1 = inl(pci_config._ioaddr + PCI_BASE_ADDRESS_1); + pci_config._base2 = inl(pci_config._ioaddr + PCI_BASE_ADDRESS_2); + pci_config._base3 = inl(pci_config._ioaddr + PCI_BASE_ADDRESS_3); + pci_config._base4 = inl(pci_config._ioaddr + PCI_BASE_ADDRESS_4); + pci_config._base5 = inl(pci_config._ioaddr + PCI_BASE_ADDRESS_5); + pci_config._baserom = inl(pci_config._ioaddr + PCI_ROM_ADDRESS); + pci_config._max_min_ipin_iline = inl(pci_config._ioaddr + PCI_INTERRUPT_LINE); + + /* check whether device is I/O mapped -- should be */ + if (!(pci_config._command & PCI_COMMAND_IO)) continue; + + /* PCI Spec 2.1 states that it is either the driver's or the PCI card's responsibility + to set the PCI Master Enable Bit if needed. + From Mark Stockton <marks@schooner.sys.hou.compaq.com> */ + if (!(pci_config._command & PCI_COMMAND_MASTER)) { + pci_config._command |= PCI_COMMAND_MASTER; + printk("Config 2; PCI Master Bit has not been set. Setting...\n"); + outw(pci_config._command, pci_config._ioaddr + PCI_COMMAND); } + + /* everything seems OK now, so initialize */ + if (AM53C974_init(tpnt, pci_config)) count++ ; + } + } + AM53C974_PCIREG_CLOSE(); + } + +return(count); +} + +/************************************************************************** +* Function : int AM53C974_detect(Scsi_Host_Template *tpnt) +* +* Purpose : detects and initializes AM53C974 SCSI chips +* +* Inputs : tpnt - host template +* +* Returns : number of host adapters detected +**************************************************************************/ +int AM53C974_detect(Scsi_Host_Template *tpnt) +{ +int count; /* number of boards detected */ + +tpnt->proc_dir = &proc_scsi_am53c974; + +#if defined (CONFIG_PCI) +if (pcibios_present()) + count = AM53C974_bios_detect(tpnt); + else +#endif +count = AM53C974_nobios_detect(tpnt); +return (count); +} + +/************************************************************************** +* Function : int AM53C974_init(Scsi_Host_Template *tpnt, pci_config_t pci_config) +* +* Purpose : initializes instance and corresponding AM53/79C974 chip, +* +* Inputs : tpnt - template, pci_config - PCI configuration, +* +* Returns : 1 on success, 0 on failure. +* +* NOTE: If no override for the controller's SCSI id is given and AM53C974_SCSI_ID +* is not defined we assume that the SCSI address of this controller is correctly +* set up by the BIOS (as reflected by contents of register CNTLREG1). +* This is the only BIOS assistance we need. +**************************************************************************/ +static int AM53C974_init(Scsi_Host_Template *tpnt, pci_config_t pci_config) +{ +AM53C974_local_declare(); +int i, j; +struct Scsi_Host *instance, *search; +struct AM53C974_hostdata *hostdata; + +#ifdef AM53C974_OPTION_DEBUG_PROBE_ONLY + printk ("AM53C974: probe only enabled, aborting initialization\n"); + return 0; +#endif + +instance = scsi_register(tpnt, sizeof(struct AM53C974_hostdata)); +hostdata = (struct AM53C974_hostdata *)instance->hostdata; +instance->base = NULL; +instance->io_port = pci_config._base0 & (pci_config._base0 & 0x1 ? + 0xFFFFFFFC : 0xFFFFFFF0); +instance->irq = pci_config._int_line; +instance->dma_channel = -1; +AM53C974_setio(instance); + +#ifdef AM53C974_SCSI_ID +instance->this_id = AM53C974_SCSI_ID; +AM53C974_write_8(CNTLREG1, instance->this_id & CNTLREG1_SID); +#else +instance->this_id = AM53C974_read_8(CNTLREG1) & CNTLREG1_SID; +if (instance->this_id != 7) + printk("scsi%d: WARNING: unusual hostadapter SCSI id %d; please verify!\n", + instance->host_no, instance->this_id); +#endif + +for (i = 0; i < sizeof(hostdata->msgout); i++) { + hostdata->msgout[i] = NOP; + hostdata->last_message[i] = NOP; } +for (i = 0; i < 8; i++) { + hostdata->busy[i] = 0; + hostdata->sync_per[i] = DEF_STP; + hostdata->sync_off[i] = 0; + hostdata->sync_neg[i] = 0; + hostdata->sync_en[i] = DEFAULT_SYNC_NEGOTIATION_ENABLED; + hostdata->max_rate[i] = DEFAULT_RATE; + hostdata->max_offset[i] = DEFAULT_SYNC_OFFSET; } + +/* overwrite defaults by LILO overrides */ +for (i = 0; i < commandline_current; i++) { + if (overrides[i].host_scsi_id == instance->this_id) { + j = overrides[i].target_scsi_id; + hostdata->sync_en[j] = 1; + hostdata->max_rate[j] = overrides[i].max_rate; + hostdata->max_offset[j] = overrides[i].max_offset; + } + } + +hostdata->sel_cmd = NULL; +hostdata->connected = NULL; +hostdata->issue_queue = NULL; +hostdata->disconnected_queue = NULL; +hostdata->in_reset = 0; +hostdata->aborted = 0; +hostdata->selecting = 0; +hostdata->disconnecting = 0; +hostdata->dma_busy = 0; + +/* Set up an interrupt handler if we aren't already sharing an IRQ with another board */ +for (search = first_host; + search && ( ((the_template != NULL) && (search->hostt != the_template)) || + (search->irq != instance->irq) || (search == instance) ); + search = search->next); +if (!search) { + if (request_irq(instance->irq, AM53C974_intr, SA_INTERRUPT, "AM53C974", NULL)) { + printk("scsi%d: IRQ%d not free, detaching\n", instance->host_no, instance->irq); + scsi_unregister(instance); + return 0; } + } + else { + printk("scsi%d: using interrupt handler previously installed for scsi%d\n", + instance->host_no, search->host_no); } + +if (!the_template) { + the_template = instance->hostt; + first_instance = instance; } + +/* do hard reset */ +AM53C974_write_8(CMDREG, CMDREG_RDEV); /* reset device */ +udelay(5); +AM53C974_write_8(CMDREG, CMDREG_NOP); +AM53C974_write_8(CNTLREG1, CNTLREG1_DISR | instance->this_id); +AM53C974_write_8(CMDREG, CMDREG_RBUS); /* reset SCSI bus */ +udelay(10); +AM53C974_config_after_reset(instance); +udelay(500000); +return(1); +} + +/********************************************************************* +* Function : AM53C974_config_after_reset(struct Scsi_Host *instance) * +* * +* Purpose : initializes chip registers after reset * +* * +* Inputs : instance - which AM53C974 * +* * +* Returns : nothing * +**********************************************************************/ +static void AM53C974_config_after_reset(struct Scsi_Host *instance) +{ +AM53C974_local_declare(); +AM53C974_setio(instance); + +/* clear SCSI FIFO */ +AM53C974_write_8(CMDREG, CMDREG_CFIFO); + +/* configure device */ +AM53C974_write_8(STIMREG, DEF_SCSI_TIMEOUT); +AM53C974_write_8(STPREG, DEF_STP & STPREG_STP); +AM53C974_write_8(SOFREG, (DEF_SOF_RAD<<6) | (DEF_SOF_RAA<<4)); +AM53C974_write_8(CLKFREG, DEF_CLKF & CLKFREG_MASK); +AM53C974_write_8(CNTLREG1, (DEF_ETM<<7) | CNTLREG1_DISR | (DEF_PERE<<4) | instance->this_id); +AM53C974_write_8(CNTLREG2, (DEF_ENF<<6)); +AM53C974_write_8(CNTLREG3, (DEF_ADIDCHK<<7) | (DEF_FASTSCSI<<4) | (DEF_FASTCLK<<3)); +AM53C974_write_8(CNTLREG4, (DEF_GLITCH<<6) | (DEF_PWD<<5) | (DEF_RAE<<3) | (DEF_RADE<<2) | CNTLREG4_RES); +} + +/*********************************************************************** +* Function : const char *AM53C974_info(struct Scsi_Host *instance) * +* * +* Purpose : return device driver information * +* * +* Inputs : instance - which AM53C974 * +* * +* Returns : info string * +************************************************************************/ +const char *AM53C974_info(struct Scsi_Host *instance) +{ +static char info[100]; + +sprintf(info, "AM53/79C974 PCscsi driver rev. %d.%d; host I/O address: 0x%x; irq: %d\n", + AM53C974_DRIVER_REVISION_MAJOR, AM53C974_DRIVER_REVISION_MINOR, + instance->io_port, instance->irq); +return (info); +} + +/************************************************************************** +* Function : int AM53C974_command (Scsi_Cmnd *SCpnt) * +* * +* Purpose : the unqueued SCSI command function, replaced by the * +* AM53C974_queue_command function * +* * +* Inputs : SCpnt - pointer to command structure * +* * +* Returns :status, see hosts.h for details * +***************************************************************************/ +int AM53C974_command(Scsi_Cmnd *SCpnt) +{ +DEB(printk("AM53C974_command called\n")); +return 0; +} + +/************************************************************************** +* Function : void initialize_SCp(Scsi_Cmnd *cmd) * +* * +* Purpose : initialize the saved data pointers for cmd to point to the * +* start of the buffer. * +* * +* Inputs : cmd - Scsi_Cmnd structure to have pointers reset. * +* * +* Returns : nothing * +**************************************************************************/ +static __inline__ void initialize_SCp(Scsi_Cmnd *cmd) +{ +if (cmd->use_sg) { + cmd->SCp.buffer = (struct scatterlist *)cmd->buffer; + cmd->SCp.buffers_residual = cmd->use_sg - 1; + cmd->SCp.ptr = (char *)cmd->SCp.buffer->address; + cmd->SCp.this_residual = cmd->SCp.buffer->length; } + else { + cmd->SCp.buffer = NULL; + cmd->SCp.buffers_residual = 0; + cmd->SCp.ptr = (char *)cmd->request_buffer; + cmd->SCp.this_residual = cmd->request_bufflen; } +} + +/************************************************************************** +* Function : run_main(void) * +* * +* Purpose : insure that the coroutine is running and will process our * +* request. main_running is checked/set here (in an inline * +* function rather than in AM53C974_main itself to reduce the * +* chances of stack overflow. * +* * +* * +* Inputs : none * +* * +* Returns : nothing * +**************************************************************************/ +static __inline__ void run_main(void) +{ +cli(); +if (!main_running) { + /* main_running is cleared in AM53C974_main once it can't do + more work, and AM53C974_main exits with interrupts disabled. */ + main_running = 1; + AM53C974_main(); + sti(); } + else + sti(); +} + +/************************************************************************** +* Function : int AM53C974_queue_command(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) +* +* Purpose : writes SCSI command into AM53C974 FIFO +* +* Inputs : cmd - SCSI command, done - function called on completion, with +* a pointer to the command descriptor. +* +* Returns : status, see hosts.h for details +* +* Side effects : +* cmd is added to the per instance issue_queue, with minor +* twiddling done to the host specific fields of cmd. If the +* main coroutine is not running, it is restarted. +**************************************************************************/ +int AM53C974_queue_command(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) +{ +struct Scsi_Host *instance = cmd->host; +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +Scsi_Cmnd *tmp; + +cli(); +DEB_QUEUE(printk(SEPARATOR_LINE)); +DEB_QUEUE(printk("scsi%d: AM53C974_queue_command called\n", instance->host_no)); +DEB_QUEUE(printk("cmd=%02x target=%02x lun=%02x bufflen=%d use_sg = %02x\n", + cmd->cmnd[0], cmd->target, cmd->lun, cmd->request_bufflen, cmd->use_sg)); + +/* We use the host_scribble field as a pointer to the next command in a queue */ +cmd->host_scribble = NULL; +cmd->scsi_done = done; +cmd->result = 0; +cmd->device->disconnect = 0; + +/* Insert the cmd into the issue queue. Note that REQUEST SENSE + * commands are added to the head of the queue since any command will + * clear the contingent allegiance condition that exists and the + * sense data is only guaranteed to be valid while the condition exists. */ +if (!(hostdata->issue_queue) || (cmd->cmnd[0] == REQUEST_SENSE)) { + LIST(cmd, hostdata->issue_queue); + cmd->host_scribble = (unsigned char *)hostdata->issue_queue; + hostdata->issue_queue = cmd; } + else { + for (tmp = (Scsi_Cmnd *)hostdata->issue_queue; tmp->host_scribble; + tmp = (Scsi_Cmnd *)tmp->host_scribble); + LIST(cmd, tmp); + tmp->host_scribble = (unsigned char *)cmd; } + +DEB_QUEUE(printk("scsi%d : command added to %s of queue\n", instance->host_no, + (cmd->cmnd[0] == REQUEST_SENSE) ? "head" : "tail")); + +/* Run the coroutine if it isn't already running. */ +run_main(); +return 0; +} + +/************************************************************************** + * Function : AM53C974_main (void) + * + * Purpose : AM53C974_main is a coroutine that runs as long as more work can + * be done on the AM53C974 host adapters in a system. Both + * AM53C974_queue_command() and AM53C974_intr() will try to start it + * in case it is not running. + * + * NOTE : AM53C974_main exits with interrupts *disabled*, the caller should + * reenable them. This prevents reentrancy and kernel stack overflow. + **************************************************************************/ +static void AM53C974_main(void) +{ +AM53C974_local_declare(); +Scsi_Cmnd *tmp, *prev; +struct Scsi_Host *instance; +struct AM53C974_hostdata *hostdata; +int done; + +/* We run (with interrupts disabled) until we're sure that none of + * the host adapters have anything that can be done, at which point + * we set main_running to 0 and exit. */ + +do { + cli(); /* Freeze request queues */ + done = 1; + for (instance = first_instance; instance && instance->hostt == the_template; + instance = instance->next) { + hostdata = (struct AM53C974_hostdata *)instance->hostdata; + AM53C974_setio(instance); + /* start to select target if we are not connected and not in the + selection process */ + if (!hostdata->connected && !hostdata->sel_cmd) { + /* Search through the issue_queue for a command destined for a target + that is not busy. */ + for (tmp = (Scsi_Cmnd *)hostdata->issue_queue, prev = NULL; tmp; + prev = tmp, tmp = (Scsi_Cmnd *)tmp->host_scribble) { + /* When we find one, remove it from the issue queue. */ + if (!(hostdata->busy[tmp->target] & (1 << tmp->lun))) { + if (prev) { + REMOVE(prev, (Scsi_Cmnd *)(prev->host_scribble), tmp, + (Scsi_Cmnd *)(tmp->host_scribble)); + prev->host_scribble = tmp->host_scribble; } + else { + REMOVE(-1, hostdata->issue_queue, tmp, tmp->host_scribble); + hostdata->issue_queue = (Scsi_Cmnd *)tmp->host_scribble; } + tmp->host_scribble = NULL; + + /* go into selection mode, disable reselection and wait for + SO interrupt which will continue with the selection process */ + hostdata->selecting = 1; + hostdata->sel_cmd = tmp; + AM53C974_write_8(CMDREG, CMDREG_DSR); + break; + } /* if target/lun is not busy */ + + } /* for */ + } /* if (!hostdata->connected) */ + else { + DEB(printk("main: connected; cmd = 0x%lx, sel_cmd = 0x%lx\n", + (long)hostdata->connected, (long)hostdata->sel_cmd)); + } + } /* for instance */ + } while (!done); +main_running = 0; +} + +/************************************************************************ +* Function : AM53C974_intr(int irq, void *dev_id, struct pt_regs *regs) * +* * +* Purpose : interrupt handler * +* * +* Inputs : irq - interrupt line, regs - ? * +* * +* Returns : nothing * +************************************************************************/ +static void AM53C974_intr(int irq, void *dev_id, struct pt_regs *regs) +{ +AM53C974_local_declare(); +struct Scsi_Host *instance; +struct AM53C974_hostdata *hostdata; +unsigned char cmdreg, dmastatus, statreg, isreg, instreg, cfifo; + +/* find AM53C974 hostadapter responsible for this interrupt */ +for (instance = first_instance; instance; instance = instance->next) + if ((instance->irq == irq) && (instance->hostt == the_template)) goto FOUND; +sti(); +return; + +/* found; now decode and process */ +FOUND: +hostdata = (struct AM53C974_hostdata *)instance->hostdata; +AM53C974_setio(instance); +dmastatus = AM53C974_read_8(DMASTATUS); + +DEB_INTR(printk(SEPARATOR_LINE)); +DEB_INTR(printk("AM53C974 interrupt; dmastatus=0x%02x\n", dmastatus)); +KEYWAIT(); + +/*** DMA related interrupts ***/ +if (hostdata->connected && (dmastatus & (DMASTATUS_ERROR | DMASTATUS_PWDN | + DMASTATUS_ABORT))) { + /* DMA error or POWERDOWN */ + printk("scsi%d: DMA error or powerdown; dmastatus: 0x%02x\n", + instance->host_no, dmastatus); +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + panic("scsi%d: cannot recover\n", instance->host_no); } + +if (hostdata->connected && (dmastatus & DMASTATUS_DONE)) { + /* DMA transfer done */ + unsigned long residual; + cli(); + if (!(AM53C974_read_8(DMACMD) & DMACMD_DIR)) { + do { + dmastatus = AM53C974_read_8(DMASTATUS); + residual = AM53C974_read_8(CTCLREG) | (AM53C974_read_8(CTCMREG) << 8) | + (AM53C974_read_8(CTCHREG) << 16); + residual += AM53C974_read_8(CFIREG) & CFIREG_CF; + } while (!(dmastatus & DMASTATUS_SCSIINT) && residual); + residual = AM53C974_read_8(CTCLREG) | (AM53C974_read_8(CTCMREG) << 8) | + (AM53C974_read_8(CTCHREG) << 16); + residual += AM53C974_read_8(CFIREG) & CFIREG_CF; + } + else + residual = 0; + hostdata->connected->SCp.ptr += hostdata->connected->SCp.this_residual - residual; + hostdata->connected->SCp.this_residual = residual; + + AM53C974_write_8(DMACMD, DMACMD_IDLE); + + /* if service request missed before, process it now (ugly) */ + if (hostdata->dma_busy) { + hostdata->dma_busy = 0; + cmdreg = AM53C974_read_8(CMDREG); + statreg = AM53C974_read_8(STATREG); + isreg = AM53C974_read_8(ISREG); + instreg = AM53C974_read_8(INSTREG); + cfifo = AM53C974_cfifo(); + AM53C974_information_transfer(instance, statreg, isreg, instreg, cfifo, + dmastatus); } + sti(); + } + +if (!(dmastatus & DMASTATUS_SCSIINT)) { + sti(); + return; } + +/*** SCSI related interrupts ***/ +cmdreg = AM53C974_read_8(CMDREG); +statreg = AM53C974_read_8(STATREG); +isreg = AM53C974_read_8(ISREG); +instreg = AM53C974_read_8(INSTREG); +cfifo = AM53C974_cfifo(); + +DEB_INTR(printk("scsi%d: statreg: 0x%02x; isreg: 0x%02x; instreg: 0x%02x; cfifo: 0x%02x\n", + instance->host_no, statreg, isreg, instreg, cfifo)); + +if (statreg & STATREG_PE) { + /* parity error */ +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + printk("scsi%d : PARITY error\n", instance->host_no); + if (hostdata->connected) hostdata->sync_off[hostdata->connected->target] = 0; /* setup asynchronous transfer */ + hostdata->aborted = 1; } + +if (statreg & STATREG_IOE) { + /* illegal operation error */ +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + printk("scsi%d : ILLEGAL OPERATION error\n", instance->host_no); + printk("cmdreg: 0x%02x; dmacmd: 0x%02x; statreg: 0x%02x; \n" + "isreg: 0x%02x; instreg: 0x%02x; cfifo: 0x%02x\n", + cmdreg, AM53C974_read_8(DMACMD), statreg, isreg, instreg, cfifo); } +if (hostdata->in_reset && (instreg & INSTREG_SRST)) { + /* RESET INTERRUPT */ +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + DEB(printk("Bus reset interrupt received\n")); + AM53C974_intr_bus_reset(instance); + cli(); + if (hostdata->connected) { + hostdata->connected->result = DID_RESET << 16; + hostdata->connected->scsi_done((Scsi_Cmnd *)hostdata->connected); + hostdata->connected = NULL; } + else { + if (hostdata->sel_cmd) { + hostdata->sel_cmd->result = DID_RESET << 16; + hostdata->sel_cmd->scsi_done((Scsi_Cmnd *)hostdata->sel_cmd); + hostdata->sel_cmd = NULL; } + } + sti(); + if (hostdata->in_reset == 1) goto EXIT; + else return; + } + +if (instreg & INSTREG_ICMD) { + /* INVALID COMMAND INTERRUPT */ +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + printk("scsi%d: Invalid command interrupt\n", instance->host_no); + printk("cmdreg: 0x%02x; dmacmd: 0x%02x; statreg: 0x%02x; dmastatus: 0x%02x; \n" + "isreg: 0x%02x; instreg: 0x%02x; cfifo: 0x%02x\n", + cmdreg, AM53C974_read_8(DMACMD), statreg, dmastatus, isreg, instreg, cfifo); + panic("scsi%d: cannot recover\n", instance->host_no); } + +if (instreg & INSTREG_DIS) { + /* DISCONNECT INTERRUPT */ + DEB_INTR(printk("Disconnect interrupt received; ")); + cli(); + AM53C974_intr_disconnect(instance); + sti(); + goto EXIT; } + +if (instreg & INSTREG_RESEL) { + /* RESELECTION INTERRUPT */ + DEB_INTR(printk("Reselection interrupt received\n")); + cli(); + AM53C974_intr_reselect(instance, statreg); + sti(); + goto EXIT; } + +if (instreg & INSTREG_SO) { + DEB_INTR(printk("Successful operation interrupt received\n")); + if (hostdata->selecting) { + DEB_INTR(printk("DSR completed, starting select\n")); + cli(); + AM53C974_select(instance, (Scsi_Cmnd *)hostdata->sel_cmd, + (hostdata->sel_cmd->cmnd[0] == REQUEST_SENSE) ? + TAG_NONE : TAG_NEXT); + hostdata->selecting = 0; + AM53C974_set_sync(instance, hostdata->sel_cmd->target); + sti(); + return; } + + if (hostdata->sel_cmd != NULL) { + if ( ((isreg & ISREG_IS) != ISREG_OK_NO_STOP) && + ((isreg & ISREG_IS) != ISREG_OK_STOP) ) { + /* UNSUCCESSFUL SELECTION */ + DEB_INTR(printk("unsuccessful selection\n")); + cli(); + hostdata->dma_busy = 0; + LIST(hostdata->sel_cmd, hostdata->issue_queue); + hostdata->sel_cmd->host_scribble = (unsigned char *)hostdata->issue_queue; + hostdata->issue_queue = hostdata->sel_cmd; + hostdata->sel_cmd = NULL; + hostdata->selecting = 0; + sti(); + goto EXIT; } + else { + /* SUCCESSFUL SELECTION */ + DEB(printk("successful selection; cmd=0x%02lx\n", (long)hostdata->sel_cmd)); + cli(); + hostdata->dma_busy = 0; + hostdata->disconnecting = 0; + hostdata->connected = hostdata->sel_cmd; + hostdata->sel_cmd = NULL; + hostdata->selecting = 0; +#ifdef SCSI2 + if (!hostdata->connected->device->tagged_queue) +#endif + hostdata->busy[hostdata->connected->target] |= (1 << hostdata->connected->lun); + /* very strange -- use_sg is sometimes nonzero for request sense commands !! */ + if ((hostdata->connected->cmnd[0] == REQUEST_SENSE) && hostdata->connected->use_sg) { + DEB(printk("scsi%d: REQUEST_SENSE command with nonzero use_sg\n", instance->host_no)); + KEYWAIT(); + hostdata->connected->use_sg = 0; } + initialize_SCp((Scsi_Cmnd *)hostdata->connected); + hostdata->connected->SCp.phase = PHASE_CMDOUT; + AM53C974_information_transfer(instance, statreg, isreg, instreg, cfifo, dmastatus); + sti(); + return; } + } + else { + cli(); + AM53C974_information_transfer(instance, statreg, isreg, instreg, cfifo, dmastatus); + sti(); + return; } + } + +if (instreg & INSTREG_SR) { + DEB_INTR(printk("Service request interrupt received, ")); + if (hostdata->connected) { + DEB_INTR(printk("calling information_transfer\n")); + cli(); + AM53C974_information_transfer(instance, statreg, isreg, instreg, cfifo, dmastatus); + sti(); } + else { + printk("scsi%d: weird: service request when no command connected\n", instance->host_no); + AM53C974_write_8(CMDREG, CMDREG_CFIFO); } /* clear FIFO */ + return; + } + +EXIT: + DEB_INTR(printk("intr: starting main\n")); + run_main(); + DEB_INTR(printk("end of intr\n")); +} + +/************************************************************************** +* Function : AM53C974_intr_disconnect(struct Scsi_Host *instance) +* +* Purpose : manage target disconnection +* +* Inputs : instance -- which AM53C974 +* +* Returns : nothing +**************************************************************************/ +static void AM53C974_intr_disconnect(struct Scsi_Host *instance) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +Scsi_Cmnd *cmd; +AM53C974_setio(instance); + +if (hostdata->sel_cmd != NULL) { + /* normal selection timeout, typical for nonexisting targets */ + cmd = (Scsi_Cmnd *)hostdata->sel_cmd; + DEB_INTR(printk("bad target\n")); + cmd->result = DID_BAD_TARGET << 16; + goto EXIT_FINISHED; } + +if (!hostdata->connected) { + /* can happen if controller was reset, a device tried to reconnect, + failed and disconnects now */ + AM53C974_write_8(CMDREG, CMDREG_CFIFO); + return; } + +if (hostdata->disconnecting) { + /* target sent disconnect message, so we are prepared */ + cmd = (Scsi_Cmnd *)hostdata->connected; + AM53C974_set_async(instance, cmd->target); + DEB_INTR(printk("scsi%d : disc. from cmnd %d for ta %d, lun %d\n", + instance->host_no, cmd->cmnd[0], cmd->target, cmd->lun)); + if (cmd->device->disconnect) { + /* target wants to reselect later */ + DEB_INTR(printk("ok, re-enabling selection\n")); + LIST(cmd,hostdata->disconnected_queue); + cmd->host_scribble = (unsigned char *)hostdata->disconnected_queue; + hostdata->disconnected_queue = cmd; + DEB_QUEUE(printk("scsi%d : command for target %d lun %d this %d was moved from connected to" + " the disconnected_queue\n", instance->host_no, cmd->target, + cmd->lun, hostdata->disconnected_queue->SCp.this_residual)); + DEB_QUEUE(AM53C974_print_queues(instance)); + goto EXIT_UNFINISHED; } + else { + /* target does not want to reselect later, we are really finished */ +#ifdef AM53C974_DEBUG + if (cmd->cmnd[0] == REQUEST_SENSE) { + int i; + printk("Request sense data dump:\n"); + for (i = 0; i < cmd->request_bufflen; i++) { + printk("%02x ", *((char *)(cmd->request_buffer) + i)); + if (i && !(i % 16)) printk("\n"); } + printk("\n"); } +#endif + goto EXIT_FINISHED; } /* !cmd->device->disconnect */ + } /* if (hostdata->disconnecting) */ + +/* no disconnect message received; unexpected disconnection */ +cmd = (Scsi_Cmnd *)hostdata->connected; +if (cmd) { +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + AM53C974_set_async(instance, cmd->target); + printk("scsi%d: Unexpected disconnect; phase: %d; target: %d; this_residual: %d; buffers_residual: %d; message: %d\n", + instance->host_no, cmd->SCp.phase, cmd->target, cmd->SCp.this_residual, cmd->SCp.buffers_residual, + cmd->SCp.Message); + printk("cmdreg: 0x%02x; statreg: 0x%02x; isreg: 0x%02x; cfifo: 0x%02x\n", + AM53C974_read_8(CMDREG), AM53C974_read_8(STATREG), AM53C974_read_8(ISREG), + AM53C974_read_8(CFIREG) & CFIREG_CF); + + if ((hostdata->last_message[0] == EXTENDED_MESSAGE) && + (hostdata->last_message[2] == EXTENDED_SDTR)) { + /* sync. negotiation was aborted, setup asynchronous transfer with target */ + hostdata->sync_off[cmd->target] = 0; } + if (hostdata->aborted || hostdata->msgout[0] == ABORT) + cmd->result = DID_ABORT << 16; + else + cmd->result = DID_ERROR << 16; + goto EXIT_FINISHED; } + +EXIT_FINISHED: +hostdata->aborted = 0; +hostdata->msgout[0] = NOP; +hostdata->sel_cmd = NULL; +hostdata->connected = NULL; +hostdata->selecting = 0; +hostdata->disconnecting = 0; +hostdata->dma_busy = 0; +hostdata->busy[cmd->target] &= ~(1 << cmd->lun); +AM53C974_write_8(CMDREG, CMDREG_CFIFO); +DEB(printk("disconnect; issue_queue: 0x%lx, disconnected_queue: 0x%lx\n", + (long)hostdata->issue_queue, (long)hostdata->disconnected_queue)); +cmd->scsi_done(cmd); + +if (!hostdata->selecting) { + AM53C974_set_async(instance, cmd->target); + AM53C974_write_8(CMDREG, CMDREG_ESR); } /* allow reselect */ +return; + +EXIT_UNFINISHED: +hostdata->msgout[0] = NOP; +hostdata->sel_cmd = NULL; +hostdata->connected = NULL; +hostdata->aborted = 0; +hostdata->selecting = 0; +hostdata->disconnecting = 0; +hostdata->dma_busy = 0; +DEB(printk("disconnect; issue_queue: 0x%lx, disconnected_queue: 0x%lx\n", + (long)hostdata->issue_queue, (long)hostdata->disconnected_queue)); +if (!hostdata->selecting) { + AM53C974_set_async(instance, cmd->target); + AM53C974_write_8(CMDREG, CMDREG_ESR); } /* allow reselect */ +return; +} + +/************************************************************************** +* Function : int AM53C974_sync_neg(struct Scsi_Host *instance, int target, unsigned char *msg) +* +* Purpose : setup message string for sync. negotiation +* +* Inputs : instance -- which AM53C974 +* target -- which SCSI target to deal with +* msg -- input message string +* +* Returns : 0 if parameters accepted or 1 if not accepted +* +* Side effects: hostdata is changed +* +* Note: we assume here that fastclk is enabled +**************************************************************************/ +static int AM53C974_sync_neg(struct Scsi_Host *instance, int target, unsigned char *msg) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +int period, offset, i, rate, rate_rem; +AM53C974_setio(instance); + +period = (DEF_CLK * msg[3] * 8 + 1000) / 2000; +if (period < MIN_PERIOD) { + period = MIN_PERIOD; + hostdata->msgout[3] = period / 4; } + else + if (period > MAX_PERIOD) { + period = MAX_PERIOD; + hostdata->msgout[3] = period / 4; } + else + hostdata->msgout[3] = msg[3]; +offset = msg[4]; +if (offset > MAX_OFFSET) offset = MAX_OFFSET; +hostdata->msgout[4] = offset; +hostdata->sync_per[target] = period; +hostdata->sync_off[target] = offset; +for (i = 0; i < 3; i++) hostdata->msgout[i] = msg[i]; +if ((hostdata->msgout[3] != msg[3]) || (msg[4] != offset)) return(1); + +rate = DEF_CLK / period; +rate_rem = 10 * (DEF_CLK - period * rate) / period; + +if (offset) + printk("\ntarget %d: rate=%d.%d Mhz, synchronous, sync offset=%d bytes\n", + target, rate, rate_rem, offset); + else + printk("\ntarget %d: rate=%d.%d Mhz, asynchronous\n", target, rate, rate_rem); + +return(0); +} + +/************************************************************************** +* Function : AM53C974_set_async(struct Scsi_Host *instance, int target) +* +* Purpose : put controller into async. mode +* +* Inputs : instance -- which AM53C974 +* target -- which SCSI target to deal with +* +* Returns : nothing +**************************************************************************/ +static __inline__ void AM53C974_set_async(struct Scsi_Host *instance, int target) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +AM53C974_setio(instance); + +AM53C974_write_8(STPREG, hostdata->sync_per[target]); +AM53C974_write_8(SOFREG, (DEF_SOF_RAD<<6) | (DEF_SOF_RAA<<4)); +} + +/************************************************************************** +* Function : AM53C974_set_sync(struct Scsi_Host *instance, int target) +* +* Purpose : put controller into sync. mode +* +* Inputs : instance -- which AM53C974 +* target -- which SCSI target to deal with +* +* Returns : nothing +**************************************************************************/ +static __inline__ void AM53C974_set_sync(struct Scsi_Host *instance, int target) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +AM53C974_setio(instance); + +AM53C974_write_8(STPREG, hostdata->sync_per[target]); +AM53C974_write_8(SOFREG, (SOFREG_SO & hostdata->sync_off[target]) | + (DEF_SOF_RAD<<6) | (DEF_SOF_RAA<<4)); +} + +/*********************************************************************** +* Function : AM53C974_information_transfer(struct Scsi_Host *instance, * +* unsigned char statreg, unsigned char isreg, * +* unsigned char instreg, unsigned char cfifo, * +* unsigned char dmastatus) * +* * +* Purpose : handle phase changes * +* * +* Inputs : instance - which AM53C974 * +* statreg - status register * +* isreg - internal state register * +* instreg - interrupt status register * +* cfifo - number of bytes in FIFO * +* dmastatus - dma status register * +* * +* Returns : nothing * +************************************************************************/ +static void AM53C974_information_transfer(struct Scsi_Host *instance, + unsigned char statreg, unsigned char isreg, + unsigned char instreg, unsigned char cfifo, + unsigned char dmastatus) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +Scsi_Cmnd *cmd = (Scsi_Cmnd *)hostdata->connected; +int ret, i, len, residual=-1; +AM53C974_setio(instance); + +DEB_INFO(printk(SEPARATOR_LINE)); +switch (statreg & STATREG_PHASE) { /* scsi phase */ + case PHASE_DATAOUT: + DEB_INFO(printk("Dataout phase; cmd=0x%lx, sel_cmd=0x%lx, this_residual=%d, buffers_residual=%d\n", + (long)hostdata->connected, (long)hostdata->sel_cmd, cmd->SCp.this_residual, cmd->SCp.buffers_residual)); + cmd->SCp.phase = PHASE_DATAOUT; + goto PHASE_DATA_IO; + + case PHASE_DATAIN: + DEB_INFO(printk("Datain phase; cmd=0x%lx, sel_cmd=0x%lx, this_residual=%d, buffers_residual=%d\n", + (long)hostdata->connected, (long)hostdata->sel_cmd, cmd->SCp.this_residual, cmd->SCp.buffers_residual)); + cmd->SCp.phase = PHASE_DATAIN; + PHASE_DATA_IO: + if (hostdata->aborted) { + AM53C974_write_8(DMACMD, DMACMD_IDLE); + AM53C974_write_8(CMDREG, CMDREG_CFIFO); + AM53C974_write_8(CMDREG, CMDREG_SATN); + return; } + if ((!cmd->SCp.this_residual) && cmd->SCp.buffers_residual) { + cmd->SCp.buffer++; + cmd->SCp.buffers_residual--; + cmd->SCp.ptr = (unsigned char *)cmd->SCp.buffer->address; + cmd->SCp.this_residual = cmd->SCp.buffer->length; } + if (cmd->SCp.this_residual) { + if (!(AM53C974_read_8(DMACMD) & DMACMD_START)) { + hostdata->dma_busy = 0; + AM53C974_transfer_dma(instance, statreg & STATREG_IO, + (unsigned long)cmd->SCp.this_residual, + cmd->SCp.ptr); } + else + hostdata->dma_busy = 1; + } + return; + + case PHASE_MSGIN: + DEB_INFO(printk("Message-In phase; cmd=0x%lx, sel_cmd=0x%lx\n", + (long)hostdata->connected, (long)hostdata->sel_cmd)); + AM53C974_set_async(instance, cmd->target); + if (cmd->SCp.phase == PHASE_DATAIN) + AM53C974_dma_blast(instance, dmastatus, statreg); + if ((cmd->SCp.phase == PHASE_DATAOUT) && (AM53C974_read_8(DMACMD) & DMACMD_START)) { + AM53C974_write_8(DMACMD, DMACMD_IDLE); + residual = cfifo + (AM53C974_read_8(CTCLREG) | (AM53C974_read_8(CTCMREG) << 8) | + (AM53C974_read_8(CTCHREG) << 16)); + cmd->SCp.ptr += cmd->SCp.this_residual - residual; + cmd->SCp.this_residual = residual; + if (cfifo) { AM53C974_write_8(CMDREG, CMDREG_CFIFO); cfifo = 0; } + } + if (cmd->SCp.phase == PHASE_STATIN) { + while ((AM53C974_read_8(CFIREG) & CFIREG_CF) < 2) ; + cmd->SCp.Status = AM53C974_read_8(FFREG); + cmd->SCp.Message = AM53C974_read_8(FFREG); + DEB_INFO(printk("Message-In phase; status=0x%02x, message=0x%02x\n", + cmd->SCp.Status, cmd->SCp.Message)); + ret = AM53C974_message(instance, cmd, cmd->SCp.Message); } + else { + if (!cfifo) { + AM53C974_write_8(CMDREG, CMDREG_IT); + AM53C974_poll_int(); + cmd->SCp.Message = AM53C974_read_8(FFREG); + } + ret = AM53C974_message(instance, cmd, cmd->SCp.Message); + } + cmd->SCp.phase = PHASE_MSGIN; + AM53C974_set_sync(instance, cmd->target); + break; + case PHASE_MSGOUT: + DEB_INFO(printk("Message-Out phase; cfifo=%d; msgout[0]=0x%02x\n", + AM53C974_read_8(CFIREG) & CFIREG_CF, hostdata->msgout[0])); + AM53C974_write_8(DMACMD, DMACMD_IDLE); + AM53C974_set_async(instance, cmd->target); + for (i = 0; i < sizeof(hostdata->last_message); i++) + hostdata->last_message[i] = hostdata->msgout[i]; + if ((hostdata->msgout[0] == 0) || INSIDE(hostdata->msgout[0], 0x02, 0x1F) || + INSIDE(hostdata->msgout[0], 0x80, 0xFF)) + len = 1; + else { + if (hostdata->msgout[0] == EXTENDED_MESSAGE) { +#ifdef AM53C974_DEBUG_INFO + printk("Extended message dump:\n"); + for (i = 0; i < hostdata->msgout[1] + 2; i++) { + printk("%02x ", hostdata->msgout[i]); + if (i && !(i % 16)) printk("\n"); } + printk("\n"); +#endif + len = hostdata->msgout[1] + 2; } + else + len = 2; + } + for (i = 0; i < len; i++) AM53C974_write_8(FFREG, hostdata->msgout[i]); + AM53C974_write_8(CMDREG, CMDREG_IT); + cmd->SCp.phase = PHASE_MSGOUT; + hostdata->msgout[0] = NOP; + AM53C974_set_sync(instance, cmd->target); + break; + + case PHASE_CMDOUT: + DEB_INFO(printk("Command-Out phase\n")); + AM53C974_set_async(instance, cmd->target); + for (i = 0; i < cmd->cmd_len; i++) AM53C974_write_8(FFREG, cmd->cmnd[i]); + AM53C974_write_8(CMDREG, CMDREG_IT); + cmd->SCp.phase = PHASE_CMDOUT; + AM53C974_set_sync(instance, cmd->target); + break; + + case PHASE_STATIN: + DEB_INFO(printk("Status phase\n")); + if (cmd->SCp.phase == PHASE_DATAIN) + AM53C974_dma_blast(instance, dmastatus, statreg); + AM53C974_set_async(instance, cmd->target); + if (cmd->SCp.phase == PHASE_DATAOUT) { + unsigned long residual; + + if (AM53C974_read_8(DMACMD) & DMACMD_START) { + AM53C974_write_8(DMACMD, DMACMD_IDLE); + residual = cfifo + (AM53C974_read_8(CTCLREG) | (AM53C974_read_8(CTCMREG) << 8) | + (AM53C974_read_8(CTCHREG) << 16)); + cmd->SCp.ptr += cmd->SCp.this_residual - residual; + cmd->SCp.this_residual = residual; } + if (cfifo) { AM53C974_write_8(CMDREG, CMDREG_CFIFO); cfifo = 0; } + } + cmd->SCp.phase = PHASE_STATIN; + AM53C974_write_8(CMDREG, CMDREG_ICCS); /* command complete */ + break; + + case PHASE_RES_0: + case PHASE_RES_1: +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + DEB_INFO(printk("Reserved phase\n")); + break; + } +KEYWAIT(); +} + +/****************************************************************************** +* Function : int AM53C974_message(struct Scsi_Host *instance, Scsi_Cmnd *cmd, +* unsigned char msg) +* +* Purpose : handle SCSI messages +* +* Inputs : instance -- which AM53C974 +* cmd -- SCSI command the message belongs to +* msg -- message id byte +* +* Returns : 1 on success, 0 on failure. +**************************************************************************/ +static int AM53C974_message(struct Scsi_Host *instance, Scsi_Cmnd *cmd, + unsigned char msg) +{ +AM53C974_local_declare(); +static unsigned char extended_msg[10]; +unsigned char statreg; +int len, ret = 0; +unsigned char *p; +#ifdef AM53C974_DEBUG_MSG +int j; +#endif +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +AM53C974_setio(instance); + +DEB_MSG(printk(SEPARATOR_LINE)); + +/* Linking lets us reduce the time required to get the + * next command out to the device, hopefully this will + * mean we don't waste another revolution due to the delays + * required by ARBITRATION and another SELECTION. + * In the current implementation proposal, low level drivers + * merely have to start the next command, pointed to by + * next_link, done() is called as with unlinked commands. */ +switch (msg) { +#ifdef LINKED + case LINKED_CMD_COMPLETE: + case LINKED_FLG_CMD_COMPLETE: + /* Accept message by releasing ACK */ + DEB_LINKED(printk("scsi%d : target %d lun %d linked command complete.\n", + instance->host_no, cmd->target, cmd->lun)); + /* Sanity check : A linked command should only terminate with + * one of these messages if there are more linked commands available. */ + if (!cmd->next_link) { + printk("scsi%d : target %d lun %d linked command complete, no next_link\n" + instance->host_no, cmd->target, cmd->lun); + hostdata->aborted = 1; + AM53C974_write_8(CMDREG, CMDREG_SATN); + AM53C974_write_8(CMDREG, CMDREG_MA); + break; } + if (hostdata->aborted) { + DEB_ABORT(printk("ATN set for cmnd %d upon reception of LINKED_CMD_COMPLETE or" + "LINKED_FLG_CMD_COMPLETE message\n", cmd->cmnd[0])); + AM53C974_write_8(CMDREG, CMDREG_SATN); } + AM53C974_write_8(CMDREG, CMDREG_MA); + + initialize_SCp(cmd->next_link); + /* The next command is still part of this process */ + cmd->next_link->tag = cmd->tag; + cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8); + DEB_LINKED(printk("scsi%d : target %d lun %d linked request done, calling scsi_done().\n", + instance->host_no, cmd->target, cmd->lun)); + cmd->scsi_done(cmd); + cmd = hostdata->connected; + break; + +#endif /* def LINKED */ + + case ABORT: + case COMMAND_COMPLETE: + DEB_MSG(printk("scsi%d: command complete message received; cmd %d for target %d, lun %d\n", + instance->host_no, cmd->cmnd[0], cmd->target, cmd->lun)); + hostdata->disconnecting = 1; + cmd->device->disconnect = 0; + + /* I'm not sure what the correct thing to do here is : + * + * If the command that just executed is NOT a request + * sense, the obvious thing to do is to set the result + * code to the values of the stored parameters. + * If it was a REQUEST SENSE command, we need some way + * to differentiate between the failure code of the original + * and the failure code of the REQUEST sense - the obvious + * case is success, where we fall through and leave the result + * code unchanged. + * + * The non-obvious place is where the REQUEST SENSE failed */ + if (cmd->cmnd[0] != REQUEST_SENSE) + cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8); + else if (cmd->SCp.Status != GOOD) + cmd->result = (cmd->result & 0x00ffff) | (DID_ERROR << 16); + if (hostdata->aborted) { + AM53C974_write_8(CMDREG, CMDREG_SATN); + AM53C974_write_8(CMDREG, CMDREG_MA); + DEB_ABORT(printk("ATN set for cmnd %d upon reception of ABORT or" + "COMMAND_COMPLETE message\n", cmd->cmnd[0])); + break; } + if ((cmd->cmnd[0] != REQUEST_SENSE) && (cmd->SCp.Status == CHECK_CONDITION)) { + DEB_MSG(printk("scsi%d : performing request sense\n", instance->host_no)); + cmd->cmnd[0] = REQUEST_SENSE; + cmd->cmnd[1] &= 0xe0; + cmd->cmnd[2] = 0; + cmd->cmnd[3] = 0; + cmd->cmnd[4] = sizeof(cmd->sense_buffer); + cmd->cmnd[5] = 0; + cmd->SCp.buffer = NULL; + cmd->SCp.buffers_residual = 0; + cmd->SCp.ptr = (char *)cmd->sense_buffer; + cmd->SCp.this_residual = sizeof(cmd->sense_buffer); + LIST(cmd,hostdata->issue_queue); + cmd->host_scribble = (unsigned char *)hostdata->issue_queue; + hostdata->issue_queue = (Scsi_Cmnd *)cmd; + DEB_MSG(printk("scsi%d : REQUEST SENSE added to head of issue queue\n",instance->host_no)); + } + + /* Accept message by clearing ACK */ + AM53C974_write_8(CMDREG, CMDREG_MA); + break; + + case MESSAGE_REJECT: + DEB_MSG(printk("scsi%d: reject message received; cmd %d for target %d, lun %d\n", + instance->host_no, cmd->cmnd[0], cmd->target, cmd->lun)); + switch (hostdata->last_message[0]) { + case EXTENDED_MESSAGE: + if (hostdata->last_message[2] == EXTENDED_SDTR) { + /* sync. negotiation was rejected, setup asynchronous transfer with target */ + printk("\ntarget %d: rate=%d Mhz, asynchronous (sync. negotiation rejected)\n", + cmd->target, DEF_CLK / DEF_STP); + hostdata->sync_off[cmd->target] = 0; + hostdata->sync_per[cmd->target] = DEF_STP; } + break; + case HEAD_OF_QUEUE_TAG: + case ORDERED_QUEUE_TAG: + case SIMPLE_QUEUE_TAG: + cmd->device->tagged_queue = 0; + hostdata->busy[cmd->target] |= (1 << cmd->lun); + break; + default: + break; + } + if (hostdata->aborted) AM53C974_write_8(CMDREG, CMDREG_SATN); + AM53C974_write_8(CMDREG, CMDREG_MA); + break; + + case DISCONNECT: + DEB_MSG(printk("scsi%d: disconnect message received; cmd %d for target %d, lun %d\n", + instance->host_no, cmd->cmnd[0], cmd->target, cmd->lun)); + cmd->device->disconnect = 1; + hostdata->disconnecting = 1; + AM53C974_write_8(CMDREG, CMDREG_MA); /* Accept message by clearing ACK */ + break; + + case SAVE_POINTERS: + case RESTORE_POINTERS: + DEB_MSG(printk("scsi%d: save/restore pointers message received; cmd %d for target %d, lun %d\n", + instance->host_no, cmd->cmnd[0], cmd->target, cmd->lun)); + /* The SCSI data pointer is *IMPLICITLY* saved on a disconnect + * operation, in violation of the SCSI spec so we can safely + * ignore SAVE/RESTORE pointers calls. + * + * Unfortunately, some disks violate the SCSI spec and + * don't issue the required SAVE_POINTERS message before + * disconnecting, and we have to break spec to remain + * compatible. */ + if (hostdata->aborted) { + DEB_ABORT(printk("ATN set for cmnd %d upon reception of SAVE/REST. POINTERS message\n", + cmd->cmnd[0])); + AM53C974_write_8(CMDREG, CMDREG_SATN); } + AM53C974_write_8(CMDREG, CMDREG_MA); + break; + + case EXTENDED_MESSAGE: + DEB_MSG(printk("scsi%d: extended message received; cmd %d for target %d, lun %d\n", + instance->host_no, cmd->cmnd[0], cmd->target, cmd->lun)); + /* Extended messages are sent in the following format : + * Byte + * 0 EXTENDED_MESSAGE == 1 + * 1 length (includes one byte for code, doesn't include first two bytes) + * 2 code + * 3..length+1 arguments + */ + /* BEWARE!! THIS CODE IS EXTREMELY UGLY */ + extended_msg[0] = EXTENDED_MESSAGE; + AM53C974_read_8(INSTREG) ; /* clear int */ + AM53C974_write_8(CMDREG, CMDREG_MA); /* ack. msg byte, then wait for SO */ + AM53C974_poll_int(); + /* get length */ + AM53C974_write_8(CMDREG, CMDREG_IT); + AM53C974_poll_int(); + AM53C974_write_8(CMDREG, CMDREG_MA); /* ack. msg byte, then wait for SO */ + AM53C974_poll_int(); + extended_msg[1] = len = AM53C974_read_8(FFREG); /* get length */ + p = extended_msg+2; + /* read the remaining (len) bytes */ + while (len) { + AM53C974_write_8(CMDREG, CMDREG_IT); + AM53C974_poll_int(); + if (len > 1) { + AM53C974_write_8(CMDREG, CMDREG_MA); /* ack. msg byte, then wait for SO */ + AM53C974_poll_int(); } + *p = AM53C974_read_8(FFREG); + p++; len--; } + +#ifdef AM53C974_DEBUG_MSG + printk("scsi%d: received extended message: ", instance->host_no); + for (j = 0; j < extended_msg[1] + 2; j++) { + printk("0x%02x ", extended_msg[j]); + if (j && !(j % 16)) printk("\n"); } + printk("\n"); +#endif + + /* check message */ + if (extended_msg[2] == EXTENDED_SDTR) + ret = AM53C974_sync_neg(instance, cmd->target, extended_msg); + if (ret || hostdata->aborted) AM53C974_write_8(CMDREG, CMDREG_SATN); + + AM53C974_write_8(CMDREG, CMDREG_MA); + break; + + default: + printk("scsi%d: unknown message 0x%02x received\n",instance->host_no, msg); +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif + /* reject message */ + hostdata->msgout[0] = MESSAGE_REJECT; + AM53C974_write_8(CMDREG, CMDREG_SATN); + AM53C974_write_8(CMDREG, CMDREG_MA); + return(0); + break; + + } /* switch (msg) */ +KEYWAIT(); +return(1); +} + +/************************************************************************** +* Function : AM53C974_select(struct Scsi_Host *instance, Scsi_Cmnd *cmd, int tag) +* +* Purpose : try to establish nexus for the command; +* start sync negotiation via start stop and transfer the command in +* cmdout phase in case of an inquiry or req. sense command with no +* sync. neg. performed yet +* +* Inputs : instance -- which AM53C974 +* cmd -- command which requires the selection +* tag -- tagged queueing +* +* Returns : nothing +* +* Note: this function initializes the selection process, which is continued +* in the interrupt handler +**************************************************************************/ +static void AM53C974_select(struct Scsi_Host *instance, Scsi_Cmnd *cmd, int tag) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +unsigned char cfifo, tmp[3]; +unsigned int i, len, cmd_size = COMMAND_SIZE(cmd->cmnd[0]); +AM53C974_setio(instance); + +cfifo = AM53C974_cfifo(); +if (cfifo) { + printk("scsi%d: select error; %d residual bytes in FIFO\n", instance->host_no, cfifo); + AM53C974_write_8(CMDREG, CMDREG_CFIFO); /* clear FIFO */ + } + +tmp[0] = IDENTIFY(1, cmd->lun); + +#ifdef SCSI2 +if (cmd->device->tagged_queue && (tag != TAG_NONE)) { + tmp[1] = SIMPLE_QUEUE_TAG; + if (tag == TAG_NEXT) { + /* 0 is TAG_NONE, used to imply no tag for this command */ + if (cmd->device->current_tag == 0) cmd->device->current_tag = 1; + cmd->tag = cmd->device->current_tag; + cmd->device->current_tag++; } + else + cmd->tag = (unsigned char)tag; + tmp[2] = cmd->tag; + hostdata->last_message[0] = SIMPLE_QUEUE_TAG; + len = 3; + AM53C974_write_8(FFREG, tmp[0]); + AM53C974_write_8(FFREG, tmp[1]); + AM53C974_write_8(FFREG, tmp[2]); + } + else +#endif /* def SCSI2 */ + { + len = 1; + AM53C974_write_8(FFREG, tmp[0]); + cmd->tag = 0; } + +/* in case of an inquiry or req. sense command with no sync. neg performed yet, we start + sync negotiation via start stops and transfer the command in cmdout phase */ +if (((cmd->cmnd[0] == INQUIRY) || (cmd->cmnd[0] == REQUEST_SENSE)) && + !(hostdata->sync_neg[cmd->target]) && hostdata->sync_en[cmd->target]) { + hostdata->sync_neg[cmd->target] = 1; + hostdata->msgout[0] = EXTENDED_MESSAGE; + hostdata->msgout[1] = 3; + hostdata->msgout[2] = EXTENDED_SDTR; + hostdata->msgout[3] = 250 / (int)hostdata->max_rate[cmd->target]; + hostdata->msgout[4] = hostdata->max_offset[cmd->target]; + len += 5; } + +AM53C974_write_8(SDIDREG, SDIREG_MASK & cmd->target); /* setup dest. id */ +AM53C974_write_8(STIMREG, DEF_SCSI_TIMEOUT); /* setup timeout reg */ +switch (len) { + case 1: + for (i = 0; i < cmd_size; i++) AM53C974_write_8(FFREG, cmd->cmnd[i]); + AM53C974_write_8(CMDREG, CMDREG_SAS); /* select with ATN, 1 msg byte */ + hostdata->msgout[0] = NOP; + break; + case 3: + for (i = 0; i < cmd_size; i++) AM53C974_write_8(FFREG, cmd->cmnd[i]); + AM53C974_write_8(CMDREG, CMDREG_SA3S); /* select with ATN, 3 msg bytes */ + hostdata->msgout[0] = NOP; + break; + default: + AM53C974_write_8(CMDREG, CMDREG_SASS); /* select with ATN, stop steps; continue in message out phase */ + break; + } +} + +/************************************************************************** +* Function : AM53C974_intr_select(struct Scsi_Host *instance, unsigned char statreg) +* +* Purpose : handle reselection +* +* Inputs : instance -- which AM53C974 +* statreg -- status register +* +* Returns : nothing +* +* side effects: manipulates hostdata +**************************************************************************/ +static void AM53C974_intr_reselect(struct Scsi_Host *instance, unsigned char statreg) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +unsigned char cfifo, msg[3], lun, t, target = 0; +#ifdef SCSI2 + unsigned char tag; +#endif +Scsi_Cmnd *tmp = NULL, *prev; +AM53C974_setio(instance); + +cfifo = AM53C974_cfifo(); + +if (hostdata->selecting) { + /* caught reselect interrupt in selection process; + put selecting command back into the issue queue and continue with the + reselecting command */ + DEB_RESEL(printk("AM53C974_intr_reselect: in selection process\n")); + LIST(hostdata->sel_cmd, hostdata->issue_queue); + hostdata->sel_cmd->host_scribble = (unsigned char *)hostdata->issue_queue; + hostdata->issue_queue = hostdata->sel_cmd; + hostdata->sel_cmd = NULL; + hostdata->selecting = 0; } + +/* 2 bytes must be in the FIFO now */ +if (cfifo != 2) { + printk("scsi %d: error: %d bytes in fifo, 2 expected\n", instance->host_no, cfifo); + hostdata->aborted = 1; + goto EXIT_ABORT; } + +/* determine target which reselected */ +t = AM53C974_read_8(FFREG); +if (!(t & (1 << instance->this_id))) { + printk("scsi %d: error: invalid host id\n", instance->host_no); + hostdata->aborted = 1; + goto EXIT_ABORT; } +t ^= (1 << instance->this_id); +target = 0; while (t != 1) { t >>= 1; target++; } +DEB_RESEL(printk("scsi %d: reselect; target: %d\n", instance->host_no, target)); + +if (hostdata->aborted) goto EXIT_ABORT; + +if ((statreg & STATREG_PHASE) != PHASE_MSGIN) { + printk("scsi %d: error: upon reselection interrupt not in MSGIN\n", instance->host_no); + hostdata->aborted = 1; + goto EXIT_ABORT; } + +msg[0] = AM53C974_read_8(FFREG); +if (!msg[0] & 0x80) { + printk("scsi%d: error: expecting IDENTIFY message, got ", instance->host_no); + print_msg(msg); + hostdata->aborted = 1; + goto EXIT_ABORT; } + +lun = (msg[0] & 0x07); + +/* We need to add code for SCSI-II to track which devices have + * I_T_L_Q nexuses established, and which have simple I_T_L + * nexuses so we can chose to do additional data transfer. */ +#ifdef SCSI2 +#error "SCSI-II tagged queueing is not supported yet" +#endif + +/* Find the command corresponding to the I_T_L or I_T_L_Q nexus we + * just reestablished, and remove it from the disconnected queue. */ +for (tmp = (Scsi_Cmnd *)hostdata->disconnected_queue, prev = NULL; + tmp; prev = tmp, tmp = (Scsi_Cmnd *)tmp->host_scribble) + if ((target == tmp->target) && (lun == tmp->lun) +#ifdef SCSI2 + && (tag == tmp->tag) +#endif + ) { + if (prev) { + REMOVE(prev, (Scsi_Cmnd *)(prev->host_scribble), tmp, + (Scsi_Cmnd *)(tmp->host_scribble)); + prev->host_scribble = tmp->host_scribble; } + else { + REMOVE(-1, hostdata->disconnected_queue, tmp, tmp->host_scribble); + hostdata->disconnected_queue = (Scsi_Cmnd *)tmp->host_scribble; } + tmp->host_scribble = NULL; + hostdata->connected = tmp; + break; } + +if (!tmp) { +#ifdef SCSI2 + printk("scsi%d: warning : target %d lun %d tag %d not in disconnect_queue.\n", + instance->host_no, target, lun, tag); +#else + printk("scsi%d: warning : target %d lun %d not in disconnect_queue.\n", + instance->host_no, target, lun); +#endif + /* Since we have an established nexus that we can't do anything with, we must abort it. */ + hostdata->aborted = 1; + DEB(AM53C974_keywait()); + goto EXIT_ABORT; } + else + goto EXIT_OK; + +EXIT_ABORT: +AM53C974_write_8(CMDREG, CMDREG_SATN); +AM53C974_write_8(CMDREG, CMDREG_MA); +return; + +EXIT_OK: +DEB_RESEL(printk("scsi%d: nexus established, target = %d, lun = %d, tag = %d\n", + instance->host_no, target, tmp->lun, tmp->tag)); +AM53C974_set_sync(instance, target); +AM53C974_write_8(SDIDREG, SDIREG_MASK & target); /* setup dest. id */ +AM53C974_write_8(CMDREG, CMDREG_MA); +hostdata->dma_busy = 0; +hostdata->connected->SCp.phase = PHASE_CMDOUT; +} + +/************************************************************************** +* Function : AM53C974_transfer_dma(struct Scsi_Host *instance, short dir, +* unsigned long length, char *data) +* +* Purpose : setup DMA transfer +* +* Inputs : instance -- which AM53C974 +* dir -- direction flag, 0: write to device, read from memory; +* 1: read from device, write to memory +* length -- number of bytes to transfer to from buffer +* data -- pointer to data buffer +* +* Returns : nothing +**************************************************************************/ +static __inline__ void AM53C974_transfer_dma(struct Scsi_Host *instance, short dir, + unsigned long length, char *data) +{ +AM53C974_local_declare(); +AM53C974_setio(instance); + +AM53C974_write_8(CMDREG, CMDREG_NOP); +AM53C974_write_8(DMACMD, (dir << 7) | DMACMD_INTE_D); /* idle command */ +AM53C974_write_8(STCLREG, (unsigned char)(length & 0xff)); +AM53C974_write_8(STCMREG, (unsigned char)((length & 0xff00) >> 8)); +AM53C974_write_8(STCHREG, (unsigned char)((length & 0xff0000) >> 16)); +AM53C974_write_32(DMASTC, length & 0xffffff); +AM53C974_write_32(DMASPA, virt_to_bus(data)); +AM53C974_write_8(CMDREG, CMDREG_IT | CMDREG_DMA); +AM53C974_write_8(DMACMD, (dir << 7) | DMACMD_INTE_D | DMACMD_START); +} + +/************************************************************************** +* Function : AM53C974_dma_blast(struct Scsi_Host *instance, unsigned char dmastatus, +* unsigned char statreg) +* +* Purpose : cleanup DMA transfer +* +* Inputs : instance -- which AM53C974 +* dmastatus -- dma status register +* statreg -- status register +* +* Returns : nothing +**************************************************************************/ +static void AM53C974_dma_blast(struct Scsi_Host *instance, unsigned char dmastatus, + unsigned char statreg) +{ +AM53C974_local_declare(); +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +unsigned long ctcreg; +int dir = statreg & STATREG_IO; +int cfifo, pio, i = 0; +AM53C974_setio(instance); + +do { + cfifo = AM53C974_cfifo(); + i++; + } while (cfifo && (i < 50000)); +pio = (i == 50000) ? 1: 0; + +if (statreg & STATREG_CTZ) { AM53C974_write_8(DMACMD, DMACMD_IDLE); return; } + +if (dmastatus & DMASTATUS_DONE) { AM53C974_write_8(DMACMD, DMACMD_IDLE); return; } + +AM53C974_write_8(DMACMD, ((dir << 7) & DMACMD_DIR) | DMACMD_BLAST); +while(!(AM53C974_read_8(DMASTATUS) & DMASTATUS_BCMPLT)) ; +AM53C974_write_8(DMACMD, DMACMD_IDLE); + +if (pio) { + /* transfer residual bytes via PIO */ + unsigned char *wac = (unsigned char *)AM53C974_read_32(DMAWAC); + printk("pio mode, residual=%d\n", AM53C974_read_8(CFIREG) & CFIREG_CF); + while (AM53C974_read_8(CFIREG) & CFIREG_CF) *(wac++) = AM53C974_read_8(FFREG); + } + +ctcreg = AM53C974_read_8(CTCLREG) | (AM53C974_read_8(CTCMREG) << 8) | + (AM53C974_read_8(CTCHREG) << 16); + +hostdata->connected->SCp.ptr += hostdata->connected->SCp.this_residual - ctcreg; +hostdata->connected->SCp.this_residual = ctcreg; +} + +/************************************************************************** +* Function : AM53C974_intr_bus_reset(struct Scsi_Host *instance) +* +* Purpose : handle bus reset interrupt +* +* Inputs : instance -- which AM53C974 +* +* Returns : nothing +**************************************************************************/ +static void AM53C974_intr_bus_reset(struct Scsi_Host *instance) +{ +AM53C974_local_declare(); +unsigned char cntlreg1; +AM53C974_setio(instance); + +AM53C974_write_8(CMDREG, CMDREG_CFIFO); +AM53C974_write_8(CMDREG, CMDREG_NOP); + +cntlreg1 = AM53C974_read_8(CNTLREG1); +AM53C974_write_8(CNTLREG1, cntlreg1 | CNTLREG1_DISR); +} + +/************************************************************************** +* Function : int AM53C974_abort(Scsi_Cmnd *cmd) +* +* Purpose : abort a command +* +* Inputs : cmd - the Scsi_Cmnd to abort, code - code to set the +* host byte of the result field to, if zero DID_ABORTED is +* used. +* +* Returns : 0 - success, -1 on failure. + **************************************************************************/ +int AM53C974_abort(Scsi_Cmnd *cmd) +{ +AM53C974_local_declare(); +struct Scsi_Host *instance = cmd->host; +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +Scsi_Cmnd *tmp, **prev; + +#ifdef AM53C974_DEBUG + deb_stop = 1; +#endif +cli(); +AM53C974_setio(instance); + +DEB_ABORT(printk(SEPARATOR_LINE)); +DEB_ABORT(printk("scsi%d : AM53C974_abort called -- trouble starts!!\n", instance->host_no)); +DEB_ABORT(AM53C974_print(instance)); +DEB_ABORT(AM53C974_keywait()); + +/* Case 1 : If the command is the currently executing command, + we'll set the aborted flag and return control so that the + information transfer routine can exit cleanly. */ +if ((hostdata->connected == cmd) || (hostdata->sel_cmd == cmd)) { + DEB_ABORT(printk("scsi%d: aborting connected command\n", instance->host_no)); + hostdata->aborted = 1; + hostdata->msgout[0] = ABORT; + sti(); + return(SCSI_ABORT_PENDING); } + +/* Case 2 : If the command hasn't been issued yet, + we simply remove it from the issue queue. */ +for (prev = (Scsi_Cmnd **)&(hostdata->issue_queue), + tmp = (Scsi_Cmnd *)hostdata->issue_queue; tmp; + prev = (Scsi_Cmnd **)&(tmp->host_scribble), + tmp = (Scsi_Cmnd *)tmp->host_scribble) { + if (cmd == tmp) { + DEB_ABORT(printk("scsi%d : abort removed command from issue queue.\n", instance->host_no)); + REMOVE(5, *prev, tmp, tmp->host_scribble); + (*prev) = (Scsi_Cmnd *)tmp->host_scribble; + tmp->host_scribble = NULL; + tmp->result = DID_ABORT << 16; + sti(); + tmp->done(tmp); + return(SCSI_ABORT_SUCCESS); } +#ifdef AM53C974_DEBUG_ABORT + else { + if (prev == (Scsi_Cmnd **)tmp) + printk("scsi%d : LOOP\n", instance->host_no); + } +#endif + } + +/* Case 3 : If any commands are connected, we're going to fail the abort + * and let the high level SCSI driver retry at a later time or + * issue a reset. + * + * Timeouts, and therefore aborted commands, will be highly unlikely + * and handling them cleanly in this situation would make the common + * case of noresets less efficient, and would pollute our code. So, + * we fail. */ +if (hostdata->connected || hostdata->sel_cmd) { + DEB_ABORT(printk("scsi%d : abort failed, other command connected.\n", instance->host_no)); + sti(); + return(SCSI_ABORT_NOT_RUNNING); } + +/* Case 4: If the command is currently disconnected from the bus, and + * there are no connected commands, we reconnect the I_T_L or + * I_T_L_Q nexus associated with it, go into message out, and send + * an abort message. */ +for (tmp = (Scsi_Cmnd *)hostdata->disconnected_queue; tmp; + tmp = (Scsi_Cmnd *)tmp->host_scribble) { + if (cmd == tmp) { + DEB_ABORT(printk("scsi%d: aborting disconnected command\n", instance->host_no)); + hostdata->aborted = 1; + hostdata->msgout[0] = ABORT; + hostdata->selecting = 1; + hostdata->sel_cmd = tmp; + AM53C974_write_8(CMDREG, CMDREG_DSR); + sti(); + return(SCSI_ABORT_PENDING); } + } + +/* Case 5 : If we reached this point, the command was not found in any of + * the queues. + * + * We probably reached this point because of an unlikely race condition + * between the command completing successfully and the abortion code, + * so we won't panic, but we will notify the user in case something really + * broke. */ +DEB_ABORT(printk("scsi%d : abort failed, command not found.\n", instance->host_no)); +sti(); +return(SCSI_ABORT_NOT_RUNNING); +} + +/************************************************************************** +* Function : int AM53C974_reset(Scsi_Cmnd *cmd) +* +* Purpose : reset the SCSI controller and bus +* +* Inputs : cmd -- which command within the command block was responsible for the reset +* +* Returns : status (SCSI_ABORT_SUCCESS) +**************************************************************************/ +int AM53C974_reset(Scsi_Cmnd *cmd, unsigned int flags) +{ +AM53C974_local_declare(); +int i; +struct Scsi_Host *instance = cmd->host; +struct AM53C974_hostdata *hostdata = (struct AM53C974_hostdata *)instance->hostdata; +AM53C974_setio(instance); + +cli(); +DEB(printk("AM53C974_reset called; ")); + +printk("AM53C974_reset called\n"); +AM53C974_print(instance); +AM53C974_keywait(); + +/* do hard reset */ +AM53C974_write_8(CMDREG, CMDREG_RDEV); +AM53C974_write_8(CMDREG, CMDREG_NOP); +hostdata->msgout[0] = NOP; +for (i = 0; i < 8; i++) { + hostdata->busy[i] = 0; + hostdata->sync_per[i] = DEF_STP; + hostdata->sync_off[i] = 0; + hostdata->sync_neg[i] = 0; } +hostdata->last_message[0] = NOP; +hostdata->sel_cmd = NULL; +hostdata->connected = NULL; +hostdata->issue_queue = NULL; +hostdata->disconnected_queue = NULL; +hostdata->in_reset = 0; +hostdata->aborted = 0; +hostdata->selecting = 0; +hostdata->disconnecting = 0; +hostdata->dma_busy = 0; + +/* reset bus */ +AM53C974_write_8(CNTLREG1, CNTLREG1_DISR | instance->this_id); /* disable interrupt upon SCSI RESET */ +AM53C974_write_8(CMDREG, CMDREG_RBUS); /* reset SCSI bus */ +udelay(40); +AM53C974_config_after_reset(instance); + +sti(); +cmd->result = DID_RESET << 16; +cmd->scsi_done(cmd); +return SCSI_ABORT_SUCCESS; +} + + +/* + * AM53C974_release() + * + * Release resources allocated for a single AM53C974 adapter. + */ +int +AM53C974_release(struct Scsi_Host *shp) +{ + free_irq(shp->irq, NULL); + scsi_unregister(shp); + return 0; +} + + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = AM53C974; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/AM53C974.h b/linux/src/drivers/scsi/AM53C974.h new file mode 100644 index 00000000..ab51d294 --- /dev/null +++ b/linux/src/drivers/scsi/AM53C974.h @@ -0,0 +1,413 @@ +/* AM53/79C974 (PCscsi) driver release 0.5 + * + * The architecture and much of the code of this device + * driver was originally developed by Drew Eckhardt for + * the NCR5380. The following copyrights apply: + * For the architecture and all parts similar to the NCR5380: + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 666-5836 + * + * The AM53C974_nobios_detect code was originally developed by + * Robin Cutshaw (robin@xfree86.org) and is used here in a + * modified form. + * + * For the other parts: + * Copyright 1994, D. Frieauff + * EMail: fri@rsx42sun0.dofn.de + * Phone: x49-7545-8-2256 , x49-7541-42305 + */ + +/* + * $Log: AM53C974.h,v $ + */ + +#ifndef AM53C974_H +#define AM53C974_H + +#include <scsi/scsicam.h> + +/*************************************************************************************** +* Default setting of the controller's SCSI id. Edit and uncomment this only if your * +* BIOS does not correctly initialize the controller's SCSI id. * +* If you don't get a warning during boot, it is correctly initialized. * +****************************************************************************************/ +/* #define AM53C974_SCSI_ID 7 */ + +/*************************************************************************************** +* Default settings for sync. negotiation enable, transfer rate and sync. offset. * +* These settings can be replaced by LILO overrides (append) with the following syntax: * +* AM53C974=host-scsi-id, target-scsi-id, max-rate, max-offset * +* Sync. negotiation is disabled by default and will be enabled for those targets which * +* are specified in the LILO override * +****************************************************************************************/ +#define DEFAULT_SYNC_NEGOTIATION_ENABLED 0 /* 0 or 1 */ +#define DEFAULT_RATE 5 /* MHz, min: 3; max: 10 */ +#define DEFAULT_SYNC_OFFSET 0 /* bytes, min: 0; max: 15; use 0 for async. mode */ + + +/* --------------------- don't edit below here --------------------- */ + +#define AM53C974_DRIVER_REVISION_MAJOR 0 +#define AM53C974_DRIVER_REVISION_MINOR 5 +#define SEPARATOR_LINE \ +"--------------------------------------------------------------------------\n" + +/* debug control */ +/* #define AM53C974_DEBUG */ +/* #define AM53C974_DEBUG_MSG */ +/* #define AM53C974_DEBUG_KEYWAIT */ +/* #define AM53C974_DEBUG_INIT */ +/* #define AM53C974_DEBUG_QUEUE */ +/* #define AM53C974_DEBUG_INFO */ +/* #define AM53C974_DEBUG_LINKED */ +/* #define VERBOSE_AM53C974_DEBUG */ +/* #define AM53C974_DEBUG_INTR */ +/* #define AM53C974_DEB_RESEL */ +#define AM53C974_DEBUG_ABORT +/* #define AM53C974_OPTION_DEBUG_PROBE_ONLY */ + +/* special options/constants */ +#define DEF_CLK 40 /* chip clock freq. in MHz */ +#define MIN_PERIOD 4 /* for negotiation: min. number of clocks per cycle */ +#define MAX_PERIOD 13 /* for negotiation: max. number of clocks per cycle */ +#define MAX_OFFSET 15 /* for negotiation: max. offset (0=async) */ + +#define DEF_SCSI_TIMEOUT 245 /* STIMREG value, 40 Mhz */ +#define DEF_STP 8 /* STPREG value assuming 5.0 MB/sec, FASTCLK, FASTSCSI */ +#define DEF_SOF_RAD 0 /* REQ/ACK deassertion delay */ +#define DEF_SOF_RAA 0 /* REQ/ACK assertion delay */ +#define DEF_ETM 0 /* CNTLREG1, ext. timing mode */ +#define DEF_PERE 1 /* CNTLREG1, parity error reporting */ +#define DEF_CLKF 0 /* CLKFREG, 0=40 Mhz */ +#define DEF_ENF 1 /* CNTLREG2, enable features */ +#define DEF_ADIDCHK 0 /* CNTLREG3, additional ID check */ +#define DEF_FASTSCSI 1 /* CNTLREG3, fast SCSI */ +#define DEF_FASTCLK 1 /* CNTLREG3, fast clocking, 5 MB/sec at 40MHz chip clk */ +#define DEF_GLITCH 1 /* CNTLREG4, glitch eater, 0=12ns, 1=35ns, 2=25ns, 3=off */ +#define DEF_PWD 0 /* CNTLREG4, reduced power feature */ +#define DEF_RAE 0 /* CNTLREG4, RAE active negation on REQ, ACK only */ +#define DEF_RADE 1 /* 1CNTLREG4, active negation on REQ, ACK and data */ + +/*** PCI block ***/ +/* standard registers are defined in <linux/pci.h> */ +#ifndef PCI_VENDOR_ID_AMD +#define PCI_VENDOR_ID_AMD 0x1022 +#define PCI_DEVICE_ID_AMD_SCSI 0x2020 +#endif +#define PCI_BASE_MASK 0xFFFFFFE0 +#define PCI_COMMAND_PERREN 0x40 +#define PCI_SCRATCH_REG_0 0x40 /* 16 bits */ +#define PCI_SCRATCH_REG_1 0x42 /* 16 bits */ +#define PCI_SCRATCH_REG_2 0x44 /* 16 bits */ +#define PCI_SCRATCH_REG_3 0x46 /* 16 bits */ +#define PCI_SCRATCH_REG_4 0x48 /* 16 bits */ +#define PCI_SCRATCH_REG_5 0x4A /* 16 bits */ +#define PCI_SCRATCH_REG_6 0x4C /* 16 bits */ +#define PCI_SCRATCH_REG_7 0x4E /* 16 bits */ + +/*** SCSI block ***/ +#define CTCLREG 0x00 /* r current transf. count, low byte */ +#define CTCMREG 0x04 /* r current transf. count, middle byte */ +#define CTCHREG 0x38 /* r current transf. count, high byte */ +#define STCLREG 0x00 /* w start transf. count, low byte */ +#define STCMREG 0x04 /* w start transf. count, middle byte */ +#define STCHREG 0x38 /* w start transf. count, high byte */ +#define FFREG 0x08 /* rw SCSI FIFO reg. */ +#define STIMREG 0x14 /* w SCSI timeout reg. */ + +#define SDIDREG 0x10 /* w SCSI destination ID reg. */ +#define SDIREG_MASK 0x07 /* mask */ + +#define STPREG 0x18 /* w synchronous transf. period reg. */ +#define STPREG_STP 0x1F /* synchr. transfer period */ + +#define CLKFREG 0x24 /* w clock factor reg. */ +#define CLKFREG_MASK 0x07 /* mask */ + +#define CMDREG 0x0C /* rw SCSI command reg. */ +#define CMDREG_DMA 0x80 /* set DMA mode (set together with opcodes below) */ +#define CMDREG_IT 0x10 /* information transfer */ +#define CMDREG_ICCS 0x11 /* initiator command complete steps */ +#define CMDREG_MA 0x12 /* message accepted */ +#define CMDREG_TPB 0x98 /* transfer pad bytes, DMA mode only */ +#define CMDREG_SATN 0x1A /* set ATN */ +#define CMDREG_RATN 0x1B /* reset ATN */ +#define CMDREG_SOAS 0x41 /* select without ATN steps */ +#define CMDREG_SAS 0x42 /* select with ATN steps (1 msg byte) */ +#define CMDREG_SASS 0x43 /* select with ATN and stop steps */ +#define CMDREG_ESR 0x44 /* enable selection/reselection */ +#define CMDREG_DSR 0x45 /* disable selection/reselection */ +#define CMDREG_SA3S 0x46 /* select with ATN 3 steps (3 msg bytes) */ +#define CMDREG_NOP 0x00 /* no operation */ +#define CMDREG_CFIFO 0x01 /* clear FIFO */ +#define CMDREG_RDEV 0x02 /* reset device */ +#define CMDREG_RBUS 0x03 /* reset SCSI bus */ + +#define STATREG 0x10 /* r SCSI status reg. */ +#define STATREG_INT 0x80 /* SCSI interrupt condition detected */ +#define STATREG_IOE 0x40 /* SCSI illegal operation error detected */ +#define STATREG_PE 0x20 /* SCSI parity error detected */ +#define STATREG_CTZ 0x10 /* CTC reg decremented to zero */ +#define STATREG_MSG 0x04 /* SCSI MSG phase (latched?) */ +#define STATREG_CD 0x02 /* SCSI C/D phase (latched?) */ +#define STATREG_IO 0x01 /* SCSI I/O phase (latched?) */ +#define STATREG_PHASE 0x07 /* SCSI phase mask */ + +#define INSTREG 0x14 /* r interrupt status reg. */ +#define INSTREG_SRST 0x80 /* SCSI reset detected */ +#define INSTREG_ICMD 0x40 /* SCSI invalid command detected */ +#define INSTREG_DIS 0x20 /* target disconnected or sel/resel timeout*/ +#define INSTREG_SR 0x10 /* device on bus has service request */ +#define INSTREG_SO 0x08 /* successful operation */ +#define INSTREG_RESEL 0x04 /* device reselected as initiator */ + +#define ISREG 0x18 /* r internal state reg. */ +#define ISREG_SOF 0x08 /* synchronous offset flag (act. low) */ +#define ISREG_IS 0x07 /* status of intermediate op. */ +#define ISREG_OK_NO_STOP 0x04 /* selection successful */ +#define ISREG_OK_STOP 0x01 /* selection successful */ + +#define CFIREG 0x1C /* r current FIFO/internal state reg. */ +#define CFIREG_IS 0xE0 /* status of intermediate op. */ +#define CFIREG_CF 0x1F /* number of bytes in SCSI FIFO */ + +#define SOFREG 0x1C /* w synchr. offset reg. */ +#define SOFREG_RAD 0xC0 /* REQ/ACK deassertion delay (sync.) */ +#define SOFREG_RAA 0x30 /* REQ/ACK assertion delay (sync.) */ +#define SOFREG_SO 0x0F /* synch. offset (sync.) */ + +#define CNTLREG1 0x20 /* rw control register one */ +#define CNTLREG1_ETM 0x80 /* set extended timing mode */ +#define CNTLREG1_DISR 0x40 /* disable interrupt on SCSI reset */ +#define CNTLREG1_PERE 0x10 /* enable parity error reporting */ +#define CNTLREG1_SID 0x07 /* host adapter SCSI ID */ + +#define CNTLREG2 0x2C /* rw control register two */ +#define CNTLREG2_ENF 0x40 /* enable features */ + +#define CNTLREG3 0x30 /* rw control register three */ +#define CNTLREG3_ADIDCHK 0x80 /* additional ID check */ +#define CNTLREG3_FASTSCSI 0x10 /* fast SCSI */ +#define CNTLREG3_FASTCLK 0x08 /* fast SCSI clocking */ + +#define CNTLREG4 0x34 /* rw control register four */ +#define CNTLREG4_GLITCH 0xC0 /* glitch eater */ +#define CNTLREG4_PWD 0x20 /* reduced power feature */ +#define CNTLREG4_RAE 0x08 /* write only, active negot. ctrl. */ +#define CNTLREG4_RADE 0x04 /* active negot. ctrl. */ +#define CNTLREG4_RES 0x10 /* reserved bit, must be 1 */ + +/*** DMA block ***/ +#define DMACMD 0x40 /* rw command */ +#define DMACMD_DIR 0x80 /* transfer direction (1=read from device) */ +#define DMACMD_INTE_D 0x40 /* DMA transfer interrupt enable */ +#define DMACMD_INTE_P 0x20 /* page transfer interrupt enable */ +#define DMACMD_MDL 0x10 /* map to memory descriptor list */ +#define DMACMD_DIAG 0x04 /* diagnostics, set to 0 */ +#define DMACMD_IDLE 0x00 /* idle cmd */ +#define DMACMD_BLAST 0x01 /* flush FIFO to memory */ +#define DMACMD_ABORT 0x02 /* terminate DMA */ +#define DMACMD_START 0x03 /* start DMA */ + +#define DMASTATUS 0x54 /* r status register */ +#define DMASTATUS_BCMPLT 0x20 /* BLAST complete */ +#define DMASTATUS_SCSIINT 0x10 /* SCSI interrupt pending */ +#define DMASTATUS_DONE 0x08 /* DMA transfer terminated */ +#define DMASTATUS_ABORT 0x04 /* DMA transfer aborted */ +#define DMASTATUS_ERROR 0x02 /* DMA transfer error */ +#define DMASTATUS_PWDN 0x02 /* power down indicator */ + +#define DMASTC 0x44 /* rw starting transfer count */ +#define DMASPA 0x48 /* rw starting physical address */ +#define DMAWBC 0x4C /* r working byte counter */ +#define DMAWAC 0x50 /* r working address counter */ +#define DMASMDLA 0x58 /* rw starting MDL address */ +#define DMAWMAC 0x5C /* r working MDL counter */ + +/*** SCSI phases ***/ +#define PHASE_MSGIN 0x07 +#define PHASE_MSGOUT 0x06 +#define PHASE_RES_1 0x05 +#define PHASE_RES_0 0x04 +#define PHASE_STATIN 0x03 +#define PHASE_CMDOUT 0x02 +#define PHASE_DATAIN 0x01 +#define PHASE_DATAOUT 0x00 + +struct AM53C974_hostdata { + volatile unsigned in_reset:1; /* flag, says bus reset pending */ + volatile unsigned aborted:1; /* flag, says aborted */ + volatile unsigned selecting:1; /* selection started, but not yet finished */ + volatile unsigned disconnecting: 1; /* disconnection started, but not yet finished */ + volatile unsigned dma_busy:1; /* dma busy when service request for info transfer received */ + volatile unsigned char msgout[10]; /* message to output in MSGOUT_PHASE */ + volatile unsigned char last_message[10]; /* last message OUT */ + volatile Scsi_Cmnd *issue_queue; /* waiting to be issued */ + volatile Scsi_Cmnd *disconnected_queue; /* waiting for reconnect */ + volatile Scsi_Cmnd *sel_cmd; /* command for selection */ + volatile Scsi_Cmnd *connected; /* currently connected command */ + volatile unsigned char busy[8]; /* index = target, bit = lun */ + unsigned char sync_per[8]; /* synchronous transfer period (in effect) */ + unsigned char sync_off[8]; /* synchronous offset (in effect) */ + unsigned char sync_neg[8]; /* sync. negotiation performed (in effect) */ + unsigned char sync_en[8]; /* sync. negotiation performed (in effect) */ + unsigned char max_rate[8]; /* max. transfer rate (setup) */ + unsigned char max_offset[8]; /* max. sync. offset (setup), only valid if corresponding sync_en is nonzero */ + }; + +#define AM53C974 { \ + NULL, /* pointer to next in list */ \ + NULL, /* long * usage_count */ \ + NULL, /* struct proc_dir_entry *proc_dir */ \ + NULL, /* int (*proc_info)(char *, char **, off_t, int, int, int); */ \ + "AM53C974", /* name */ \ + AM53C974_detect, /* int (* detect)(struct SHT *) */ \ + NULL, /* int (*release)(struct Scsi_Host *) */ \ + AM53C974_info, /* const char *(* info)(struct Scsi_Host *) */ \ + AM53C974_command, /* int (* command)(Scsi_Cmnd *) */ \ + AM53C974_queue_command, /* int (* queuecommand)(Scsi_Cmnd *, \ + void (*done)(Scsi_Cmnd *)) */ \ + AM53C974_abort, /* int (* abort)(Scsi_Cmnd *) */ \ + AM53C974_reset, /* int (* reset)(Scsi_Cmnd *) */ \ + NULL, /* int (* slave_attach)(int, int) */ \ + scsicam_bios_param, /* int (* bios_param)(Disk *, int, int[]) */ \ + 12, /* can_queue */ \ + -1, /* this_id */ \ + SG_ALL, /* sg_tablesize */ \ + 1, /* cmd_per_lun */ \ + 0, /* present, i.e. how many adapters of this kind */ \ + 0, /* unchecked_isa_dma */ \ + DISABLE_CLUSTERING /* use_clustering */ \ + } + +void AM53C974_setup(char *str, int *ints); +int AM53C974_detect(Scsi_Host_Template *tpnt); +int AM53C974_biosparm(Disk *disk, int dev, int *info_array); +const char *AM53C974_info(struct Scsi_Host *); +int AM53C974_command(Scsi_Cmnd *SCpnt); +int AM53C974_queue_command(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)); +int AM53C974_abort(Scsi_Cmnd *cmd); +int AM53C974_reset (Scsi_Cmnd *cmd, unsigned int flags); + +#define AM53C974_local_declare() unsigned long io_port +#define AM53C974_setio(instance) io_port = instance->io_port +#define AM53C974_read_8(addr) inb(io_port + (addr)) +#define AM53C974_write_8(addr,x) outb((x), io_port + (addr)) +#define AM53C974_read_16(addr) inw(io_port + (addr)) +#define AM53C974_write_16(addr,x) outw((x), io_port + (addr)) +#define AM53C974_read_32(addr) inl(io_port + (addr)) +#define AM53C974_write_32(addr,x) outl((x), io_port + (addr)) + +#define AM53C974_poll_int() { do { statreg = AM53C974_read_8(STATREG); } \ + while (!(statreg & STATREG_INT)) ; \ + AM53C974_read_8(INSTREG) ; } /* clear int */ +#define AM53C974_cfifo() (AM53C974_read_8(CFIREG) & CFIREG_CF) + +/* These are "special" values for the tag parameter passed to AM53C974_select. */ +#define TAG_NEXT -1 /* Use next free tag */ +#define TAG_NONE -2 /* Establish I_T_L nexus instead of I_T_L_Q + * even on SCSI-II devices */ + +/************ LILO overrides *************/ +typedef struct _override_t { + int host_scsi_id; /* SCSI id of the bus controller */ + int target_scsi_id; /* SCSI id of target */ + int max_rate; /* max. transfer rate */ + int max_offset; /* max. sync. offset, 0 = asynchronous */ + } override_t; + +/************ PCI stuff *************/ +#define AM53C974_PCIREG_OPEN() outb(0xF1, 0xCF8); outb(0, 0xCFA) +#define AM53C974_PCIREG_CLOSE() outb(0, 0xCF8) +#define AM53C974_PCIREG_READ_BYTE(instance,a) ( inb((a) + (instance)->io_port) ) +#define AM53C974_PCIREG_READ_WORD(instance,a) ( inw((a) + (instance)->io_port) ) +#define AM53C974_PCIREG_READ_DWORD(instance,a) ( inl((a) + (instance)->io_port) ) +#define AM53C974_PCIREG_WRITE_BYTE(instance,x,a) ( outb((x), (a) + (instance)->io_port) ) +#define AM53C974_PCIREG_WRITE_WORD(instance,x,a) ( outw((x), (a) + (instance)->io_port) ) +#define AM53C974_PCIREG_WRITE_DWORD(instance,x,a) ( outl((x), (a) + (instance)->io_port) ) + +typedef struct _pci_config_t { + /* start of official PCI config space header */ + union { + unsigned int device_vendor; + struct { + unsigned short vendor; + unsigned short device; + } dv; + } dv_id; +#define _device_vendor dv_id.device_vendor +#define _vendor dv_id.dv.vendor +#define _device dv_id.dv.device + union { + unsigned int status_command; + struct { + unsigned short command; + unsigned short status; + } sc; + } stat_cmd; +#define _status_command stat_cmd.status_command +#define _command stat_cmd.sc.command +#define _status stat_cmd.sc.status + union { + unsigned int class_revision; + struct { + unsigned char rev_id; + unsigned char prog_if; + unsigned char sub_class; + unsigned char base_class; + } cr; + } class_rev; +#define _class_revision class_rev.class_revision +#define _rev_id class_rev.cr.rev_id +#define _prog_if class_rev.cr.prog_if +#define _sub_class class_rev.cr.sub_class +#define _base_class class_rev.cr.base_class + union { + unsigned int bist_header_latency_cache; + struct { + unsigned char cache_line_size; + unsigned char latency_timer; + unsigned char header_type; + unsigned char bist; + } bhlc; + } bhlc; +#define _bist_header_latency_cache bhlc.bist_header_latency_cache +#define _cache_line_size bhlc.bhlc.cache_line_size +#define _latency_timer bhlc.bhlc.latency_timer +#define _header_type bhlc.bhlc.header_type +#define _bist bhlc.bhlc.bist + unsigned int _base0; + unsigned int _base1; + unsigned int _base2; + unsigned int _base3; + unsigned int _base4; + unsigned int _base5; + unsigned int rsvd1; + unsigned int rsvd2; + unsigned int _baserom; + unsigned int rsvd3; + unsigned int rsvd4; + union { + unsigned int max_min_ipin_iline; + struct { + unsigned char int_line; + unsigned char int_pin; + unsigned char min_gnt; + unsigned char max_lat; + } mmii; + } mmii; +#define _max_min_ipin_iline mmii.max_min_ipin_iline +#define _int_line mmii.mmii.int_line +#define _int_pin mmii.mmii.int_pin +#define _min_gnt mmii.mmii.min_gnt +#define _max_lat mmii.mmii.max_lat + /* end of official PCI config space header */ + unsigned short _ioaddr; /* config type 1 - private I/O addr */ + unsigned int _pcibus; /* config type 2 - private bus id */ + unsigned int _cardnum; /* config type 2 - private card number */ +} pci_config_t; + +#endif /* AM53C974_H */ diff --git a/linux/src/drivers/scsi/BusLogic.c b/linux/src/drivers/scsi/BusLogic.c new file mode 100644 index 00000000..3c52e154 --- /dev/null +++ b/linux/src/drivers/scsi/BusLogic.c @@ -0,0 +1,5003 @@ +/* + + Linux Driver for BusLogic MultiMaster and FlashPoint SCSI Host Adapters + + Copyright 1995-1998 by Leonard N. Zubkoff <lnz@dandelion.com> + + This program is free software; you may redistribute and/or modify it under + the terms of the GNU General Public License Version 2 as published by the + Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for complete details. + + The author respectfully requests that any modifications to this software be + sent directly to him for evaluation and testing. + + Special thanks to Wayne Yen, Jin-Lon Hon, and Alex Win of BusLogic, whose + advice has been invaluable, to David Gentzel, for writing the original Linux + BusLogic driver, and to Paul Gortmaker, for being such a dedicated test site. + + Finally, special thanks to Mylex/BusLogic for making the FlashPoint SCCB + Manager available as freely redistributable source code. + +*/ + + +#define BusLogic_DriverVersion "2.0.15" +#define BusLogic_DriverDate "17 August 1998" + + +#include <linux/version.h> +#include <linux/module.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/blkdev.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" +#include "BusLogic.h" +#include "FlashPoint.c" + + +/* + BusLogic_DriverOptionsCount is a count of the number of BusLogic Driver + Options specifications provided via the Linux Kernel Command Line or via + the Loadable Kernel Module Installation Facility. +*/ + +static int + BusLogic_DriverOptionsCount = 0; + + +/* + BusLogic_DriverOptions is an array of Driver Options structures representing + BusLogic Driver Options specifications provided via the Linux Kernel Command + Line or via the Loadable Kernel Module Installation Facility. +*/ + +static BusLogic_DriverOptions_T + BusLogic_DriverOptions[BusLogic_MaxHostAdapters]; + + +/* + BusLogic_Options can be assigned a string by the Loadable Kernel Module + Installation Facility to be parsed for BusLogic Driver Options + specifications. +*/ + +static char + *BusLogic_Options = NULL; + + +/* + BusLogic_ProbeOptions is a set of Probe Options to be applied across + all BusLogic Host Adapters. +*/ + +static BusLogic_ProbeOptions_T + BusLogic_ProbeOptions = { 0 }; + + +/* + BusLogic_GlobalOptions is a set of Global Options to be applied across + all BusLogic Host Adapters. +*/ + +static BusLogic_GlobalOptions_T + BusLogic_GlobalOptions = { 0 }; + + +/* + BusLogic_FirstRegisteredHostAdapter and BusLogic_LastRegisteredHostAdapter + are pointers to the first and last registered BusLogic Host Adapters. +*/ + +static BusLogic_HostAdapter_T + *BusLogic_FirstRegisteredHostAdapter = NULL, + *BusLogic_LastRegisteredHostAdapter = NULL; + + +/* + BusLogic_ProbeInfoCount is the number of entries in BusLogic_ProbeInfoList. +*/ + +static int + BusLogic_ProbeInfoCount = 0; + + +/* + BusLogic_ProbeInfoList is the list of I/O Addresses and Bus Probe Information + to be checked for potential BusLogic Host Adapters. It is initialized by + interrogating the PCI Configuration Space on PCI machines as well as from the + list of standard BusLogic I/O Addresses. +*/ + +static BusLogic_ProbeInfo_T + *BusLogic_ProbeInfoList = NULL; + + +/* + BusLogic_CommandFailureReason holds a string identifying the reason why a + call to BusLogic_Command failed. It is only non-NULL when BusLogic_Command + returns a failure code. +*/ + +static char + *BusLogic_CommandFailureReason; + + +/* + BusLogic_ProcDirectoryEntry is the BusLogic /proc/scsi directory entry. +*/ + +PROC_DirectoryEntry_T + BusLogic_ProcDirectoryEntry = + { PROC_SCSI_BUSLOGIC, 8, "BusLogic", S_IFDIR | S_IRUGO | S_IXUGO, 2 }; + + +/* + BusLogic_AnnounceDriver announces the Driver Version and Date, Author's + Name, Copyright Notice, and Electronic Mail Address. +*/ + +static void BusLogic_AnnounceDriver(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_Announce("***** BusLogic SCSI Driver Version " + BusLogic_DriverVersion " of " + BusLogic_DriverDate " *****\n", HostAdapter); + BusLogic_Announce("Copyright 1995-1998 by Leonard N. Zubkoff " + "<lnz@dandelion.com>\n", HostAdapter); +} + + +/* + BusLogic_DriverInfo returns the Host Adapter Name to identify this SCSI + Driver and Host Adapter. +*/ + +const char *BusLogic_DriverInfo(SCSI_Host_T *Host) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) Host->hostdata; + return HostAdapter->FullModelName; +} + + +/* + BusLogic_RegisterHostAdapter adds Host Adapter to the list of registered + BusLogic Host Adapters. +*/ + +static void BusLogic_RegisterHostAdapter(BusLogic_HostAdapter_T *HostAdapter) +{ + HostAdapter->Next = NULL; + if (BusLogic_FirstRegisteredHostAdapter == NULL) + { + BusLogic_FirstRegisteredHostAdapter = HostAdapter; + BusLogic_LastRegisteredHostAdapter = HostAdapter; + } + else + { + BusLogic_LastRegisteredHostAdapter->Next = HostAdapter; + BusLogic_LastRegisteredHostAdapter = HostAdapter; + } +} + + +/* + BusLogic_UnregisterHostAdapter removes Host Adapter from the list of + registered BusLogic Host Adapters. +*/ + +static void BusLogic_UnregisterHostAdapter(BusLogic_HostAdapter_T *HostAdapter) +{ + if (HostAdapter == BusLogic_FirstRegisteredHostAdapter) + { + BusLogic_FirstRegisteredHostAdapter = + BusLogic_FirstRegisteredHostAdapter->Next; + if (HostAdapter == BusLogic_LastRegisteredHostAdapter) + BusLogic_LastRegisteredHostAdapter = NULL; + } + else + { + BusLogic_HostAdapter_T *PreviousHostAdapter = + BusLogic_FirstRegisteredHostAdapter; + while (PreviousHostAdapter != NULL && + PreviousHostAdapter->Next != HostAdapter) + PreviousHostAdapter = PreviousHostAdapter->Next; + if (PreviousHostAdapter != NULL) + PreviousHostAdapter->Next = HostAdapter->Next; + } + HostAdapter->Next = NULL; +} + + +/* + BusLogic_InitializeCCBs initializes a group of Command Control Blocks (CCBs) + for Host Adapter from the BlockSize bytes located at BlockPointer. The newly + created CCBs are added to Host Adapter's free list. +*/ + +static void BusLogic_InitializeCCBs(BusLogic_HostAdapter_T *HostAdapter, + void *BlockPointer, int BlockSize) +{ + BusLogic_CCB_T *CCB = (BusLogic_CCB_T *) BlockPointer; + memset(BlockPointer, 0, BlockSize); + CCB->AllocationGroupHead = true; + while ((BlockSize -= sizeof(BusLogic_CCB_T)) >= 0) + { + CCB->Status = BusLogic_CCB_Free; + CCB->HostAdapter = HostAdapter; + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) + { + CCB->CallbackFunction = BusLogic_QueueCompletedCCB; + CCB->BaseAddress = HostAdapter->FlashPointInfo.BaseAddress; + } + CCB->Next = HostAdapter->Free_CCBs; + CCB->NextAll = HostAdapter->All_CCBs; + HostAdapter->Free_CCBs = CCB; + HostAdapter->All_CCBs = CCB; + HostAdapter->AllocatedCCBs++; + CCB++; + } +} + + +/* + BusLogic_CreateInitialCCBs allocates the initial CCBs for Host Adapter. +*/ + +static boolean BusLogic_CreateInitialCCBs(BusLogic_HostAdapter_T *HostAdapter) +{ + int BlockSize = BusLogic_CCB_AllocationGroupSize * sizeof(BusLogic_CCB_T); + while (HostAdapter->AllocatedCCBs < HostAdapter->InitialCCBs) + { + void *BlockPointer = kmalloc(BlockSize, + (HostAdapter->BounceBuffersRequired + ? GFP_ATOMIC | GFP_DMA + : GFP_ATOMIC)); + if (BlockPointer == NULL) + { + BusLogic_Error("UNABLE TO ALLOCATE CCB GROUP - DETACHING\n", + HostAdapter); + return false; + } + BusLogic_InitializeCCBs(HostAdapter, BlockPointer, BlockSize); + } + return true; +} + + +/* + BusLogic_DestroyCCBs deallocates the CCBs for Host Adapter. +*/ + +static void BusLogic_DestroyCCBs(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_CCB_T *NextCCB = HostAdapter->All_CCBs, *CCB; + HostAdapter->All_CCBs = NULL; + HostAdapter->Free_CCBs = NULL; + while ((CCB = NextCCB) != NULL) + { + NextCCB = CCB->NextAll; + if (CCB->AllocationGroupHead) + kfree(CCB); + } +} + + +/* + BusLogic_CreateAdditionalCCBs allocates Additional CCBs for Host Adapter. If + allocation fails and there are no remaining CCBs available, the Driver Queue + Depth is decreased to a known safe value to avoid potential deadlocks when + multiple host adapters share the same IRQ Channel. +*/ + +static void BusLogic_CreateAdditionalCCBs(BusLogic_HostAdapter_T *HostAdapter, + int AdditionalCCBs, + boolean SuccessMessageP) +{ + int BlockSize = BusLogic_CCB_AllocationGroupSize * sizeof(BusLogic_CCB_T); + int PreviouslyAllocated = HostAdapter->AllocatedCCBs; + if (AdditionalCCBs <= 0) return; + while (HostAdapter->AllocatedCCBs - PreviouslyAllocated < AdditionalCCBs) + { + void *BlockPointer = kmalloc(BlockSize, + (HostAdapter->BounceBuffersRequired + ? GFP_ATOMIC | GFP_DMA + : GFP_ATOMIC)); + if (BlockPointer == NULL) break; + BusLogic_InitializeCCBs(HostAdapter, BlockPointer, BlockSize); + } + if (HostAdapter->AllocatedCCBs > PreviouslyAllocated) + { + if (SuccessMessageP) + BusLogic_Notice("Allocated %d additional CCBs (total now %d)\n", + HostAdapter, + HostAdapter->AllocatedCCBs - PreviouslyAllocated, + HostAdapter->AllocatedCCBs); + return; + } + BusLogic_Notice("Failed to allocate additional CCBs\n", HostAdapter); + if (HostAdapter->DriverQueueDepth > + HostAdapter->AllocatedCCBs - HostAdapter->TargetDeviceCount) + { + HostAdapter->DriverQueueDepth = + HostAdapter->AllocatedCCBs - HostAdapter->TargetDeviceCount; + HostAdapter->SCSI_Host->can_queue = HostAdapter->DriverQueueDepth; + } +} + + +/* + BusLogic_AllocateCCB allocates a CCB from Host Adapter's free list, + allocating more memory from the Kernel if necessary. The Host Adapter's + Lock should already have been acquired by the caller. +*/ + +static BusLogic_CCB_T *BusLogic_AllocateCCB(BusLogic_HostAdapter_T + *HostAdapter) +{ + static unsigned long SerialNumber = 0; + BusLogic_CCB_T *CCB; + CCB = HostAdapter->Free_CCBs; + if (CCB != NULL) + { + CCB->SerialNumber = ++SerialNumber; + HostAdapter->Free_CCBs = CCB->Next; + CCB->Next = NULL; + if (HostAdapter->Free_CCBs == NULL) + BusLogic_CreateAdditionalCCBs(HostAdapter, + HostAdapter->IncrementalCCBs, + true); + return CCB; + } + BusLogic_CreateAdditionalCCBs(HostAdapter, + HostAdapter->IncrementalCCBs, + true); + CCB = HostAdapter->Free_CCBs; + if (CCB == NULL) return NULL; + CCB->SerialNumber = ++SerialNumber; + HostAdapter->Free_CCBs = CCB->Next; + CCB->Next = NULL; + return CCB; +} + + +/* + BusLogic_DeallocateCCB deallocates a CCB, returning it to the Host Adapter's + free list. The Host Adapter's Lock should already have been acquired by the + caller. +*/ + +static void BusLogic_DeallocateCCB(BusLogic_CCB_T *CCB) +{ + BusLogic_HostAdapter_T *HostAdapter = CCB->HostAdapter; + CCB->Command = NULL; + CCB->Status = BusLogic_CCB_Free; + CCB->Next = HostAdapter->Free_CCBs; + HostAdapter->Free_CCBs = CCB; +} + + +/* + BusLogic_Command sends the command OperationCode to HostAdapter, optionally + providing ParameterLength bytes of ParameterData and receiving at most + ReplyLength bytes of ReplyData; any excess reply data is received but + discarded. + + On success, this function returns the number of reply bytes read from + the Host Adapter (including any discarded data); on failure, it returns + -1 if the command was invalid, or -2 if a timeout occurred. + + BusLogic_Command is called exclusively during host adapter detection and + initialization, so performance and latency are not critical, and exclusive + access to the Host Adapter hardware is assumed. Once the host adapter and + driver are initialized, the only Host Adapter command that is issued is the + single byte Execute Mailbox Command operation code, which does not require + waiting for the Host Adapter Ready bit to be set in the Status Register. +*/ + +static int BusLogic_Command(BusLogic_HostAdapter_T *HostAdapter, + BusLogic_OperationCode_T OperationCode, + void *ParameterData, + int ParameterLength, + void *ReplyData, + int ReplyLength) +{ + unsigned char *ParameterPointer = (unsigned char *) ParameterData; + unsigned char *ReplyPointer = (unsigned char *) ReplyData; + BusLogic_StatusRegister_T StatusRegister; + BusLogic_InterruptRegister_T InterruptRegister; + ProcessorFlags_T ProcessorFlags = 0; + int ReplyBytes = 0, Result; + long TimeoutCounter; + /* + Clear out the Reply Data if provided. + */ + if (ReplyLength > 0) + memset(ReplyData, 0, ReplyLength); + /* + If the IRQ Channel has not yet been acquired, then interrupts must be + disabled while issuing host adapter commands since a Command Complete + interrupt could occur if the IRQ Channel was previously enabled by another + BusLogic Host Adapter or another driver sharing the same IRQ Channel. + */ + if (!HostAdapter->IRQ_ChannelAcquired) + { + save_flags(ProcessorFlags); + cli(); + } + /* + Wait for the Host Adapter Ready bit to be set and the Command/Parameter + Register Busy bit to be reset in the Status Register. + */ + TimeoutCounter = 10000; + while (--TimeoutCounter >= 0) + { + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (StatusRegister.Bits.HostAdapterReady && + !StatusRegister.Bits.CommandParameterRegisterBusy) + break; + udelay(100); + } + if (TimeoutCounter < 0) + { + BusLogic_CommandFailureReason = "Timeout waiting for Host Adapter Ready"; + Result = -2; + goto Done; + } + /* + Write the OperationCode to the Command/Parameter Register. + */ + HostAdapter->HostAdapterCommandCompleted = false; + BusLogic_WriteCommandParameterRegister(HostAdapter, OperationCode); + /* + Write any additional Parameter Bytes. + */ + TimeoutCounter = 10000; + while (ParameterLength > 0 && --TimeoutCounter >= 0) + { + /* + Wait 100 microseconds to give the Host Adapter enough time to determine + whether the last value written to the Command/Parameter Register was + valid or not. If the Command Complete bit is set in the Interrupt + Register, then the Command Invalid bit in the Status Register will be + reset if the Operation Code or Parameter was valid and the command + has completed, or set if the Operation Code or Parameter was invalid. + If the Data In Register Ready bit is set in the Status Register, then + the Operation Code was valid, and data is waiting to be read back + from the Host Adapter. Otherwise, wait for the Command/Parameter + Register Busy bit in the Status Register to be reset. + */ + udelay(100); + InterruptRegister.All = BusLogic_ReadInterruptRegister(HostAdapter); + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (InterruptRegister.Bits.CommandComplete) break; + if (HostAdapter->HostAdapterCommandCompleted) break; + if (StatusRegister.Bits.DataInRegisterReady) break; + if (StatusRegister.Bits.CommandParameterRegisterBusy) continue; + BusLogic_WriteCommandParameterRegister(HostAdapter, *ParameterPointer++); + ParameterLength--; + } + if (TimeoutCounter < 0) + { + BusLogic_CommandFailureReason = + "Timeout waiting for Parameter Acceptance"; + Result = -2; + goto Done; + } + /* + The Modify I/O Address command does not cause a Command Complete Interrupt. + */ + if (OperationCode == BusLogic_ModifyIOAddress) + { + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (StatusRegister.Bits.CommandInvalid) + { + BusLogic_CommandFailureReason = "Modify I/O Address Invalid"; + Result = -1; + goto Done; + } + if (BusLogic_GlobalOptions.TraceConfiguration) + BusLogic_Notice("BusLogic_Command(%02X) Status = %02X: " + "(Modify I/O Address)\n", HostAdapter, + OperationCode, StatusRegister.All); + Result = 0; + goto Done; + } + /* + Select an appropriate timeout value for awaiting command completion. + */ + switch (OperationCode) + { + case BusLogic_InquireInstalledDevicesID0to7: + case BusLogic_InquireInstalledDevicesID8to15: + case BusLogic_InquireTargetDevices: + /* Approximately 60 seconds. */ + TimeoutCounter = 60*10000; + break; + default: + /* Approximately 1 second. */ + TimeoutCounter = 10000; + break; + } + /* + Receive any Reply Bytes, waiting for either the Command Complete bit to + be set in the Interrupt Register, or for the Interrupt Handler to set the + Host Adapter Command Completed bit in the Host Adapter structure. + */ + while (--TimeoutCounter >= 0) + { + InterruptRegister.All = BusLogic_ReadInterruptRegister(HostAdapter); + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (InterruptRegister.Bits.CommandComplete) break; + if (HostAdapter->HostAdapterCommandCompleted) break; + if (StatusRegister.Bits.DataInRegisterReady) + { + if (++ReplyBytes <= ReplyLength) + *ReplyPointer++ = BusLogic_ReadDataInRegister(HostAdapter); + else BusLogic_ReadDataInRegister(HostAdapter); + } + if (OperationCode == BusLogic_FetchHostAdapterLocalRAM && + StatusRegister.Bits.HostAdapterReady) break; + udelay(100); + } + if (TimeoutCounter < 0) + { + BusLogic_CommandFailureReason = "Timeout waiting for Command Complete"; + Result = -2; + goto Done; + } + /* + Clear any pending Command Complete Interrupt. + */ + BusLogic_InterruptReset(HostAdapter); + /* + Provide tracing information if requested. + */ + if (BusLogic_GlobalOptions.TraceConfiguration) + { + int i; + BusLogic_Notice("BusLogic_Command(%02X) Status = %02X: %2d ==> %2d:", + HostAdapter, OperationCode, + StatusRegister.All, ReplyLength, ReplyBytes); + if (ReplyLength > ReplyBytes) ReplyLength = ReplyBytes; + for (i = 0; i < ReplyLength; i++) + BusLogic_Notice(" %02X", HostAdapter, + ((unsigned char *) ReplyData)[i]); + BusLogic_Notice("\n", HostAdapter); + } + /* + Process Command Invalid conditions. + */ + if (StatusRegister.Bits.CommandInvalid) + { + /* + Some early BusLogic Host Adapters may not recover properly from + a Command Invalid condition, so if this appears to be the case, + a Soft Reset is issued to the Host Adapter. Potentially invalid + commands are never attempted after Mailbox Initialization is + performed, so there should be no Host Adapter state lost by a + Soft Reset in response to a Command Invalid condition. + */ + udelay(1000); + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (StatusRegister.Bits.CommandInvalid || + StatusRegister.Bits.Reserved || + StatusRegister.Bits.DataInRegisterReady || + StatusRegister.Bits.CommandParameterRegisterBusy || + !StatusRegister.Bits.HostAdapterReady || + !StatusRegister.Bits.InitializationRequired || + StatusRegister.Bits.DiagnosticActive || + StatusRegister.Bits.DiagnosticFailure) + { + BusLogic_SoftReset(HostAdapter); + udelay(1000); + } + BusLogic_CommandFailureReason = "Command Invalid"; + Result = -1; + goto Done; + } + /* + Handle Excess Parameters Supplied conditions. + */ + if (ParameterLength > 0) + { + BusLogic_CommandFailureReason = "Excess Parameters Supplied"; + Result = -1; + goto Done; + } + /* + Indicate the command completed successfully. + */ + BusLogic_CommandFailureReason = NULL; + Result = ReplyBytes; + /* + Restore the interrupt status if necessary and return. + */ +Done: + if (!HostAdapter->IRQ_ChannelAcquired) + restore_flags(ProcessorFlags); + return Result; +} + + +/* + BusLogic_AppendProbeAddressISA appends a single ISA I/O Address to the list + of I/O Address and Bus Probe Information to be checked for potential BusLogic + Host Adapters. +*/ + +static void BusLogic_AppendProbeAddressISA(BusLogic_IO_Address_T IO_Address) +{ + BusLogic_ProbeInfo_T *ProbeInfo; + if (BusLogic_ProbeInfoCount >= BusLogic_MaxHostAdapters) return; + ProbeInfo = &BusLogic_ProbeInfoList[BusLogic_ProbeInfoCount++]; + ProbeInfo->HostAdapterType = BusLogic_MultiMaster; + ProbeInfo->HostAdapterBusType = BusLogic_ISA_Bus; + ProbeInfo->IO_Address = IO_Address; +} + + +/* + BusLogic_InitializeProbeInfoListISA initializes the list of I/O Address and + Bus Probe Information to be checked for potential BusLogic SCSI Host Adapters + only from the list of standard BusLogic MultiMaster ISA I/O Addresses. +*/ + +static void BusLogic_InitializeProbeInfoListISA(BusLogic_HostAdapter_T + *PrototypeHostAdapter) +{ + /* + If BusLogic Driver Options specifications requested that ISA Bus Probes + be inhibited, do not proceed further. + */ + if (BusLogic_ProbeOptions.NoProbeISA) return; + /* + Append the list of standard BusLogic MultiMaster ISA I/O Addresses. + */ + if (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe330 + : check_region(0x330, BusLogic_MultiMasterAddressCount) == 0) + BusLogic_AppendProbeAddressISA(0x330); + if (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe334 + : check_region(0x334, BusLogic_MultiMasterAddressCount) == 0) + BusLogic_AppendProbeAddressISA(0x334); + if (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe230 + : check_region(0x230, BusLogic_MultiMasterAddressCount) == 0) + BusLogic_AppendProbeAddressISA(0x230); + if (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe234 + : check_region(0x234, BusLogic_MultiMasterAddressCount) == 0) + BusLogic_AppendProbeAddressISA(0x234); + if (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe130 + : check_region(0x130, BusLogic_MultiMasterAddressCount) == 0) + BusLogic_AppendProbeAddressISA(0x130); + if (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe134 + : check_region(0x134, BusLogic_MultiMasterAddressCount) == 0) + BusLogic_AppendProbeAddressISA(0x134); +} + + +#ifdef CONFIG_PCI + + +/* + BusLogic_SortProbeInfo sorts a section of BusLogic_ProbeInfoList in order + of increasing PCI Bus and Device Number. +*/ + +static void BusLogic_SortProbeInfo(BusLogic_ProbeInfo_T *ProbeInfoList, + int ProbeInfoCount) +{ + int LastInterchange = ProbeInfoCount-1, Bound, j; + while (LastInterchange > 0) + { + Bound = LastInterchange; + LastInterchange = 0; + for (j = 0; j < Bound; j++) + { + BusLogic_ProbeInfo_T *ProbeInfo1 = &ProbeInfoList[j]; + BusLogic_ProbeInfo_T *ProbeInfo2 = &ProbeInfoList[j+1]; + if (ProbeInfo1->Bus > ProbeInfo2->Bus || + (ProbeInfo1->Bus == ProbeInfo2->Bus && + (ProbeInfo1->Device > ProbeInfo2->Device))) + { + BusLogic_ProbeInfo_T TempProbeInfo; + memcpy(&TempProbeInfo, ProbeInfo1, sizeof(BusLogic_ProbeInfo_T)); + memcpy(ProbeInfo1, ProbeInfo2, sizeof(BusLogic_ProbeInfo_T)); + memcpy(ProbeInfo2, &TempProbeInfo, sizeof(BusLogic_ProbeInfo_T)); + LastInterchange = j; + } + } + } +} + + +/* + BusLogic_InitializeMultiMasterProbeInfo initializes the list of I/O Address + and Bus Probe Information to be checked for potential BusLogic MultiMaster + SCSI Host Adapters by interrogating the PCI Configuration Space on PCI + machines as well as from the list of standard BusLogic MultiMaster ISA + I/O Addresses. It returns the number of PCI MultiMaster Host Adapters found. +*/ + +static int BusLogic_InitializeMultiMasterProbeInfo(BusLogic_HostAdapter_T + *PrototypeHostAdapter) +{ + BusLogic_ProbeInfo_T *PrimaryProbeInfo = + &BusLogic_ProbeInfoList[BusLogic_ProbeInfoCount]; + int NonPrimaryPCIMultiMasterIndex = BusLogic_ProbeInfoCount + 1; + int NonPrimaryPCIMultiMasterCount = 0, PCIMultiMasterCount = 0; + boolean ForceBusDeviceScanningOrder = false; + boolean ForceBusDeviceScanningOrderChecked = false; + boolean StandardAddressSeen[6]; + unsigned char Bus, DeviceFunction; + unsigned int BaseAddress0, BaseAddress1; + unsigned char IRQ_Channel; + BusLogic_IO_Address_T IO_Address; + BusLogic_PCI_Address_T PCI_Address; + unsigned short Index = 0; + int i; + if (BusLogic_ProbeInfoCount >= BusLogic_MaxHostAdapters) return 0; + BusLogic_ProbeInfoCount++; + for (i = 0; i < 6; i++) + StandardAddressSeen[i] = false; + /* + Iterate over the MultiMaster PCI Host Adapters. For each enumerated host + adapter, determine whether its ISA Compatible I/O Port is enabled and if + so, whether it is assigned the Primary I/O Address. A host adapter that is + assigned the Primary I/O Address will always be the preferred boot device. + The MultiMaster BIOS will first recognize a host adapter at the Primary I/O + Address, then any other PCI host adapters, and finally any host adapters + located at the remaining standard ISA I/O Addresses. When a PCI host + adapter is found with its ISA Compatible I/O Port enabled, a command is + issued to disable the ISA Compatible I/O Port, and it is noted that the + particular standard ISA I/O Address need not be probed. + */ + PrimaryProbeInfo->IO_Address = 0; + while (pcibios_find_device(PCI_VENDOR_ID_BUSLOGIC, + PCI_DEVICE_ID_BUSLOGIC_MULTIMASTER, + Index++, &Bus, &DeviceFunction) == 0) + if (pcibios_read_config_dword(Bus, DeviceFunction, + PCI_BASE_ADDRESS_0, &BaseAddress0) == 0 && + pcibios_read_config_dword(Bus, DeviceFunction, + PCI_BASE_ADDRESS_1, &BaseAddress1) == 0 && + pcibios_read_config_byte(Bus, DeviceFunction, + PCI_INTERRUPT_LINE, &IRQ_Channel) == 0) + { + BusLogic_HostAdapter_T *HostAdapter = PrototypeHostAdapter; + BusLogic_PCIHostAdapterInformation_T PCIHostAdapterInformation; + BusLogic_ModifyIOAddressRequest_T ModifyIOAddressRequest; + unsigned char Device = DeviceFunction >> 3; + IO_Address = BaseAddress0 & PCI_BASE_ADDRESS_IO_MASK; + PCI_Address = BaseAddress1 & PCI_BASE_ADDRESS_MEM_MASK; + if ((BaseAddress0 & PCI_BASE_ADDRESS_SPACE) + != PCI_BASE_ADDRESS_SPACE_IO) + { + BusLogic_Error("BusLogic: Base Address0 0x%X not I/O for " + "MultiMaster Host Adapter\n", NULL, BaseAddress0); + BusLogic_Error("at PCI Bus %d Device %d I/O Address 0x%X\n", + NULL, Bus, Device, IO_Address); + continue; + } + if ((BaseAddress1 & PCI_BASE_ADDRESS_SPACE) + != PCI_BASE_ADDRESS_SPACE_MEMORY) + { + BusLogic_Error("BusLogic: Base Address1 0x%X not Memory for " + "MultiMaster Host Adapter\n", NULL, BaseAddress1); + BusLogic_Error("at PCI Bus %d Device %d PCI Address 0x%X\n", + NULL, Bus, Device, PCI_Address); + continue; + } + if (IRQ_Channel == 0 || IRQ_Channel >= NR_IRQS) + { + BusLogic_Error("BusLogic: IRQ Channel %d illegal for " + "MultiMaster Host Adapter\n", NULL, IRQ_Channel); + BusLogic_Error("at PCI Bus %d Device %d I/O Address 0x%X\n", + NULL, Bus, Device, IO_Address); + continue; + } + if (BusLogic_GlobalOptions.TraceProbe) + { + BusLogic_Notice("BusLogic: PCI MultiMaster Host Adapter " + "detected at\n", NULL); + BusLogic_Notice("BusLogic: PCI Bus %d Device %d I/O Address " + "0x%X PCI Address 0x%X\n", NULL, + Bus, Device, IO_Address, PCI_Address); + } + /* + Issue the Inquire PCI Host Adapter Information command to determine + the ISA Compatible I/O Port. If the ISA Compatible I/O Port is + known and enabled, note that the particular Standard ISA I/O + Address should not be probed. + */ + HostAdapter->IO_Address = IO_Address; + BusLogic_InterruptReset(HostAdapter); + if (BusLogic_Command(HostAdapter, + BusLogic_InquirePCIHostAdapterInformation, + NULL, 0, &PCIHostAdapterInformation, + sizeof(PCIHostAdapterInformation)) + == sizeof(PCIHostAdapterInformation)) + { + if (PCIHostAdapterInformation.ISACompatibleIOPort < 6) + StandardAddressSeen[PCIHostAdapterInformation + .ISACompatibleIOPort] = true; + } + else PCIHostAdapterInformation.ISACompatibleIOPort = + BusLogic_IO_Disable; + /* + Issue the Modify I/O Address command to disable the ISA Compatible + I/O Port. + */ + ModifyIOAddressRequest = BusLogic_IO_Disable; + BusLogic_Command(HostAdapter, BusLogic_ModifyIOAddress, + &ModifyIOAddressRequest, + sizeof(ModifyIOAddressRequest), NULL, 0); + /* + For the first MultiMaster Host Adapter enumerated, issue the Fetch + Host Adapter Local RAM command to read byte 45 of the AutoSCSI area, + for the setting of the "Use Bus And Device # For PCI Scanning Seq." + option. Issue the Inquire Board ID command since this option is + only valid for the BT-948/958/958D. + */ + if (!ForceBusDeviceScanningOrderChecked) + { + BusLogic_FetchHostAdapterLocalRAMRequest_T + FetchHostAdapterLocalRAMRequest; + BusLogic_AutoSCSIByte45_T AutoSCSIByte45; + BusLogic_BoardID_T BoardID; + FetchHostAdapterLocalRAMRequest.ByteOffset = + BusLogic_AutoSCSI_BaseOffset + 45; + FetchHostAdapterLocalRAMRequest.ByteCount = + sizeof(AutoSCSIByte45); + BusLogic_Command(HostAdapter, + BusLogic_FetchHostAdapterLocalRAM, + &FetchHostAdapterLocalRAMRequest, + sizeof(FetchHostAdapterLocalRAMRequest), + &AutoSCSIByte45, sizeof(AutoSCSIByte45)); + BusLogic_Command(HostAdapter, BusLogic_InquireBoardID, + NULL, 0, &BoardID, sizeof(BoardID)); + if (BoardID.FirmwareVersion1stDigit == '5') + ForceBusDeviceScanningOrder = + AutoSCSIByte45.ForceBusDeviceScanningOrder; + ForceBusDeviceScanningOrderChecked = true; + } + /* + Determine whether this MultiMaster Host Adapter has its ISA + Compatible I/O Port enabled and is assigned the Primary I/O Address. + If it does, then it is the Primary MultiMaster Host Adapter and must + be recognized first. If it does not, then it is added to the list + for probing after any Primary MultiMaster Host Adapter is probed. + */ + if (PCIHostAdapterInformation.ISACompatibleIOPort == BusLogic_IO_330) + { + PrimaryProbeInfo->HostAdapterType = BusLogic_MultiMaster; + PrimaryProbeInfo->HostAdapterBusType = BusLogic_PCI_Bus; + PrimaryProbeInfo->IO_Address = IO_Address; + PrimaryProbeInfo->PCI_Address = PCI_Address; + PrimaryProbeInfo->Bus = Bus; + PrimaryProbeInfo->Device = Device; + PrimaryProbeInfo->IRQ_Channel = IRQ_Channel; + PCIMultiMasterCount++; + } + else if (BusLogic_ProbeInfoCount < BusLogic_MaxHostAdapters) + { + BusLogic_ProbeInfo_T *ProbeInfo = + &BusLogic_ProbeInfoList[BusLogic_ProbeInfoCount++]; + ProbeInfo->HostAdapterType = BusLogic_MultiMaster; + ProbeInfo->HostAdapterBusType = BusLogic_PCI_Bus; + ProbeInfo->IO_Address = IO_Address; + ProbeInfo->PCI_Address = PCI_Address; + ProbeInfo->Bus = Bus; + ProbeInfo->Device = Device; + ProbeInfo->IRQ_Channel = IRQ_Channel; + NonPrimaryPCIMultiMasterCount++; + PCIMultiMasterCount++; + } + else BusLogic_Warning("BusLogic: Too many Host Adapters " + "detected\n", NULL); + } + /* + If the AutoSCSI "Use Bus And Device # For PCI Scanning Seq." option is ON + for the first enumerated MultiMaster Host Adapter, and if that host adapter + is a BT-948/958/958D, then the MultiMaster BIOS will recognize MultiMaster + Host Adapters in the order of increasing PCI Bus and Device Number. In + that case, sort the probe information into the same order the BIOS uses. + If this option is OFF, then the MultiMaster BIOS will recognize MultiMaster + Host Adapters in the order they are enumerated by the PCI BIOS, and hence + no sorting is necessary. + */ + if (ForceBusDeviceScanningOrder) + BusLogic_SortProbeInfo(&BusLogic_ProbeInfoList[ + NonPrimaryPCIMultiMasterIndex], + NonPrimaryPCIMultiMasterCount); + /* + If no PCI MultiMaster Host Adapter is assigned the Primary I/O Address, + then the Primary I/O Address must be probed explicitly before any PCI + host adapters are probed. + */ + if (!BusLogic_ProbeOptions.NoProbeISA) + if (PrimaryProbeInfo->IO_Address == 0 && + (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe330 + : check_region(0x330, BusLogic_MultiMasterAddressCount) == 0)) + { + PrimaryProbeInfo->HostAdapterType = BusLogic_MultiMaster; + PrimaryProbeInfo->HostAdapterBusType = BusLogic_ISA_Bus; + PrimaryProbeInfo->IO_Address = 0x330; + } + /* + Append the list of standard BusLogic MultiMaster ISA I/O Addresses, + omitting the Primary I/O Address which has already been handled. + */ + if (!BusLogic_ProbeOptions.NoProbeISA) + { + if (!StandardAddressSeen[1] && + (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe334 + : check_region(0x334, BusLogic_MultiMasterAddressCount) == 0)) + BusLogic_AppendProbeAddressISA(0x334); + if (!StandardAddressSeen[2] && + (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe230 + : check_region(0x230, BusLogic_MultiMasterAddressCount) == 0)) + BusLogic_AppendProbeAddressISA(0x230); + if (!StandardAddressSeen[3] && + (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe234 + : check_region(0x234, BusLogic_MultiMasterAddressCount) == 0)) + BusLogic_AppendProbeAddressISA(0x234); + if (!StandardAddressSeen[4] && + (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe130 + : check_region(0x130, BusLogic_MultiMasterAddressCount) == 0)) + BusLogic_AppendProbeAddressISA(0x130); + if (!StandardAddressSeen[5] && + (BusLogic_ProbeOptions.LimitedProbeISA + ? BusLogic_ProbeOptions.Probe134 + : check_region(0x134, BusLogic_MultiMasterAddressCount) == 0)) + BusLogic_AppendProbeAddressISA(0x134); + } + /* + Iterate over the older non-compliant MultiMaster PCI Host Adapters, + noting the PCI bus location and assigned IRQ Channel. + */ + Index = 0; + while (pcibios_find_device(PCI_VENDOR_ID_BUSLOGIC, + PCI_DEVICE_ID_BUSLOGIC_MULTIMASTER_NC, + Index++, &Bus, &DeviceFunction) == 0) + if (pcibios_read_config_dword(Bus, DeviceFunction, + PCI_BASE_ADDRESS_0, &BaseAddress0) == 0 && + pcibios_read_config_byte(Bus, DeviceFunction, + PCI_INTERRUPT_LINE, &IRQ_Channel) == 0) + { + unsigned char Device = DeviceFunction >> 3; + IO_Address = BaseAddress0 & PCI_BASE_ADDRESS_IO_MASK; + if (IO_Address == 0 || IRQ_Channel == 0 || IRQ_Channel >= NR_IRQS) + continue; + for (i = 0; i < BusLogic_ProbeInfoCount; i++) + { + BusLogic_ProbeInfo_T *ProbeInfo = &BusLogic_ProbeInfoList[i]; + if (ProbeInfo->IO_Address == IO_Address && + ProbeInfo->HostAdapterType == BusLogic_MultiMaster) + { + ProbeInfo->HostAdapterBusType = BusLogic_PCI_Bus; + ProbeInfo->PCI_Address = 0; + ProbeInfo->Bus = Bus; + ProbeInfo->Device = Device; + ProbeInfo->IRQ_Channel = IRQ_Channel; + break; + } + } + } + return PCIMultiMasterCount; +} + + +/* + BusLogic_InitializeFlashPointProbeInfo initializes the list of I/O Address + and Bus Probe Information to be checked for potential BusLogic FlashPoint + Host Adapters by interrogating the PCI Configuration Space. It returns the + number of FlashPoint Host Adapters found. +*/ + +static int BusLogic_InitializeFlashPointProbeInfo(BusLogic_HostAdapter_T + *PrototypeHostAdapter) +{ + int FlashPointIndex = BusLogic_ProbeInfoCount, FlashPointCount = 0; + unsigned char Bus, DeviceFunction; + unsigned int BaseAddress0, BaseAddress1; + unsigned char IRQ_Channel; + BusLogic_IO_Address_T IO_Address; + BusLogic_PCI_Address_T PCI_Address; + unsigned short Index = 0; + /* + Interrogate PCI Configuration Space for any FlashPoint Host Adapters. + */ + while (pcibios_find_device(PCI_VENDOR_ID_BUSLOGIC, + PCI_DEVICE_ID_BUSLOGIC_FLASHPOINT, + Index++, &Bus, &DeviceFunction) == 0) + if (pcibios_read_config_dword(Bus, DeviceFunction, + PCI_BASE_ADDRESS_0, &BaseAddress0) == 0 && + pcibios_read_config_dword(Bus, DeviceFunction, + PCI_BASE_ADDRESS_1, &BaseAddress1) == 0 && + pcibios_read_config_byte(Bus, DeviceFunction, + PCI_INTERRUPT_LINE, &IRQ_Channel) == 0) + { + unsigned char Device = DeviceFunction >> 3; + IO_Address = BaseAddress0 & PCI_BASE_ADDRESS_IO_MASK; + PCI_Address = BaseAddress1 & PCI_BASE_ADDRESS_MEM_MASK; +#ifndef CONFIG_SCSI_OMIT_FLASHPOINT + if ((BaseAddress0 & PCI_BASE_ADDRESS_SPACE) + != PCI_BASE_ADDRESS_SPACE_IO) + { + BusLogic_Error("BusLogic: Base Address0 0x%X not I/O for " + "FlashPoint Host Adapter\n", NULL, BaseAddress0); + BusLogic_Error("at PCI Bus %d Device %d I/O Address 0x%X\n", + NULL, Bus, Device, IO_Address); + continue; + } + if ((BaseAddress1 & PCI_BASE_ADDRESS_SPACE) + != PCI_BASE_ADDRESS_SPACE_MEMORY) + { + BusLogic_Error("BusLogic: Base Address1 0x%X not Memory for " + "FlashPoint Host Adapter\n", NULL, BaseAddress1); + BusLogic_Error("at PCI Bus %d Device %d PCI Address 0x%X\n", + NULL, Bus, Device, PCI_Address); + continue; + } + if (IRQ_Channel == 0 || IRQ_Channel >= NR_IRQS) + { + BusLogic_Error("BusLogic: IRQ Channel %d illegal for " + "FlashPoint Host Adapter\n", NULL, IRQ_Channel); + BusLogic_Error("at PCI Bus %d Device %d I/O Address 0x%X\n", + NULL, Bus, Device, IO_Address); + continue; + } + if (BusLogic_GlobalOptions.TraceProbe) + { + BusLogic_Notice("BusLogic: FlashPoint Host Adapter " + "detected at\n", NULL); + BusLogic_Notice("BusLogic: PCI Bus %d Device %d I/O Address " + "0x%X PCI Address 0x%X\n", NULL, + Bus, Device, IO_Address, PCI_Address); + } + if (BusLogic_ProbeInfoCount < BusLogic_MaxHostAdapters) + { + BusLogic_ProbeInfo_T *ProbeInfo = + &BusLogic_ProbeInfoList[BusLogic_ProbeInfoCount++]; + ProbeInfo->HostAdapterType = BusLogic_FlashPoint; + ProbeInfo->HostAdapterBusType = BusLogic_PCI_Bus; + ProbeInfo->IO_Address = IO_Address; + ProbeInfo->PCI_Address = PCI_Address; + ProbeInfo->Bus = Bus; + ProbeInfo->Device = Device; + ProbeInfo->IRQ_Channel = IRQ_Channel; + FlashPointCount++; + } + else BusLogic_Warning("BusLogic: Too many Host Adapters " + "detected\n", NULL); +#else + BusLogic_Error("BusLogic: FlashPoint Host Adapter detected at " + "PCI Bus %d Device %d\n", NULL, Bus, Device); + BusLogic_Error("BusLogic: I/O Address 0x%X PCI Address 0x%X, " + "but FlashPoint\n", NULL, IO_Address, PCI_Address); + BusLogic_Error("BusLogic: support was omitted in this kernel " + "configuration.\n", NULL); +#endif + } + /* + The FlashPoint BIOS will scan for FlashPoint Host Adapters in the order of + increasing PCI Bus and Device Number, so sort the probe information into + the same order the BIOS uses. + */ + BusLogic_SortProbeInfo(&BusLogic_ProbeInfoList[FlashPointIndex], + FlashPointCount); + return FlashPointCount; +} + + +/* + BusLogic_InitializeProbeInfoList initializes the list of I/O Address and Bus + Probe Information to be checked for potential BusLogic SCSI Host Adapters by + interrogating the PCI Configuration Space on PCI machines as well as from the + list of standard BusLogic MultiMaster ISA I/O Addresses. By default, if both + FlashPoint and PCI MultiMaster Host Adapters are present, this driver will + probe for FlashPoint Host Adapters first unless the BIOS primary disk is + controlled by the first PCI MultiMaster Host Adapter, in which case + MultiMaster Host Adapters will be probed first. The BusLogic Driver Options + specifications "MultiMasterFirst" and "FlashPointFirst" can be used to force + a particular probe order. +*/ + +static void BusLogic_InitializeProbeInfoList(BusLogic_HostAdapter_T + *PrototypeHostAdapter) +{ + /* + If a PCI BIOS is present, interrogate it for MultiMaster and FlashPoint + Host Adapters; otherwise, default to the standard ISA MultiMaster probe. + */ + if (!BusLogic_ProbeOptions.NoProbePCI && pcibios_present()) + { + if (BusLogic_ProbeOptions.MultiMasterFirst) + { + BusLogic_InitializeMultiMasterProbeInfo(PrototypeHostAdapter); + BusLogic_InitializeFlashPointProbeInfo(PrototypeHostAdapter); + } + else if (BusLogic_ProbeOptions.FlashPointFirst) + { + BusLogic_InitializeFlashPointProbeInfo(PrototypeHostAdapter); + BusLogic_InitializeMultiMasterProbeInfo(PrototypeHostAdapter); + } + else + { + int FlashPointCount = + BusLogic_InitializeFlashPointProbeInfo(PrototypeHostAdapter); + int PCIMultiMasterCount = + BusLogic_InitializeMultiMasterProbeInfo(PrototypeHostAdapter); + if (FlashPointCount > 0 && PCIMultiMasterCount > 0) + { + BusLogic_ProbeInfo_T *ProbeInfo = + &BusLogic_ProbeInfoList[FlashPointCount]; + BusLogic_HostAdapter_T *HostAdapter = PrototypeHostAdapter; + BusLogic_FetchHostAdapterLocalRAMRequest_T + FetchHostAdapterLocalRAMRequest; + BusLogic_BIOSDriveMapByte_T Drive0MapByte; + while (ProbeInfo->HostAdapterBusType != BusLogic_PCI_Bus) + ProbeInfo++; + HostAdapter->IO_Address = ProbeInfo->IO_Address; + FetchHostAdapterLocalRAMRequest.ByteOffset = + BusLogic_BIOS_BaseOffset + BusLogic_BIOS_DriveMapOffset + 0; + FetchHostAdapterLocalRAMRequest.ByteCount = + sizeof(Drive0MapByte); + BusLogic_Command(HostAdapter, + BusLogic_FetchHostAdapterLocalRAM, + &FetchHostAdapterLocalRAMRequest, + sizeof(FetchHostAdapterLocalRAMRequest), + &Drive0MapByte, sizeof(Drive0MapByte)); + /* + If the Map Byte for BIOS Drive 0 indicates that BIOS Drive 0 + is controlled by this PCI MultiMaster Host Adapter, then + reverse the probe order so that MultiMaster Host Adapters are + probed before FlashPoint Host Adapters. + */ + if (Drive0MapByte.DiskGeometry != + BusLogic_BIOS_Disk_Not_Installed) + { + BusLogic_ProbeInfo_T + SavedProbeInfo[BusLogic_MaxHostAdapters]; + int MultiMasterCount = + BusLogic_ProbeInfoCount - FlashPointCount; + memcpy(SavedProbeInfo, + BusLogic_ProbeInfoList, + BusLogic_ProbeInfoCount + * sizeof(BusLogic_ProbeInfo_T)); + memcpy(&BusLogic_ProbeInfoList[0], + &SavedProbeInfo[FlashPointCount], + MultiMasterCount * sizeof(BusLogic_ProbeInfo_T)); + memcpy(&BusLogic_ProbeInfoList[MultiMasterCount], + &SavedProbeInfo[0], + FlashPointCount * sizeof(BusLogic_ProbeInfo_T)); + } + } + } + } + else BusLogic_InitializeProbeInfoListISA(PrototypeHostAdapter); +} + + +#endif /* CONFIG_PCI */ + + +/* + BusLogic_Failure prints a standardized error message, and then returns false. +*/ + +static boolean BusLogic_Failure(BusLogic_HostAdapter_T *HostAdapter, + char *ErrorMessage) +{ + BusLogic_AnnounceDriver(HostAdapter); + if (HostAdapter->HostAdapterBusType == BusLogic_PCI_Bus) + { + BusLogic_Error("While configuring BusLogic PCI Host Adapter at\n", + HostAdapter); + BusLogic_Error("Bus %d Device %d I/O Address 0x%X PCI Address 0x%X:\n", + HostAdapter, HostAdapter->Bus, HostAdapter->Device, + HostAdapter->IO_Address, HostAdapter->PCI_Address); + } + else BusLogic_Error("While configuring BusLogic Host Adapter at " + "I/O Address 0x%X:\n", HostAdapter, + HostAdapter->IO_Address); + BusLogic_Error("%s FAILED - DETACHING\n", HostAdapter, ErrorMessage); + if (BusLogic_CommandFailureReason != NULL) + BusLogic_Error("ADDITIONAL FAILURE INFO - %s\n", HostAdapter, + BusLogic_CommandFailureReason); + return false; +} + + +/* + BusLogic_ProbeHostAdapter probes for a BusLogic Host Adapter. +*/ + +static boolean BusLogic_ProbeHostAdapter(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_StatusRegister_T StatusRegister; + BusLogic_InterruptRegister_T InterruptRegister; + BusLogic_GeometryRegister_T GeometryRegister; + /* + FlashPoint Host Adapters are Probed by the FlashPoint SCCB Manager. + */ + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) + { + FlashPoint_Info_T *FlashPointInfo = &HostAdapter->FlashPointInfo; + FlashPointInfo->BaseAddress = + (BusLogic_Base_Address_T) HostAdapter->IO_Address; + FlashPointInfo->IRQ_Channel = HostAdapter->IRQ_Channel; + FlashPointInfo->Present = false; + if (!(FlashPoint_ProbeHostAdapter(FlashPointInfo) == 0 && + FlashPointInfo->Present)) + { + BusLogic_Error("BusLogic: FlashPoint Host Adapter detected at " + "PCI Bus %d Device %d\n", HostAdapter, + HostAdapter->Bus, HostAdapter->Device); + BusLogic_Error("BusLogic: I/O Address 0x%X PCI Address 0x%X, " + "but FlashPoint\n", HostAdapter, + HostAdapter->IO_Address, HostAdapter->PCI_Address); + BusLogic_Error("BusLogic: Probe Function failed to validate it.\n", + HostAdapter); + return false; + } + if (BusLogic_GlobalOptions.TraceProbe) + BusLogic_Notice("BusLogic_Probe(0x%X): FlashPoint Found\n", + HostAdapter, HostAdapter->IO_Address); + /* + Indicate the Host Adapter Probe completed successfully. + */ + return true; + } + /* + Read the Status, Interrupt, and Geometry Registers to test if there are I/O + ports that respond, and to check the values to determine if they are from a + BusLogic Host Adapter. A nonexistent I/O port will return 0xFF, in which + case there is definitely no BusLogic Host Adapter at this base I/O Address. + The test here is a subset of that used by the BusLogic Host Adapter BIOS. + */ + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + InterruptRegister.All = BusLogic_ReadInterruptRegister(HostAdapter); + GeometryRegister.All = BusLogic_ReadGeometryRegister(HostAdapter); + if (BusLogic_GlobalOptions.TraceProbe) + BusLogic_Notice("BusLogic_Probe(0x%X): Status 0x%02X, Interrupt 0x%02X, " + "Geometry 0x%02X\n", HostAdapter, + HostAdapter->IO_Address, StatusRegister.All, + InterruptRegister.All, GeometryRegister.All); + if (StatusRegister.All == 0 || + StatusRegister.Bits.DiagnosticActive || + StatusRegister.Bits.CommandParameterRegisterBusy || + StatusRegister.Bits.Reserved || + StatusRegister.Bits.CommandInvalid || + InterruptRegister.Bits.Reserved != 0) + return false; + /* + Check the undocumented Geometry Register to test if there is an I/O port + that responded. Adaptec Host Adapters do not implement the Geometry + Register, so this test helps serve to avoid incorrectly recognizing an + Adaptec 1542A or 1542B as a BusLogic. Unfortunately, the Adaptec 1542C + series does respond to the Geometry Register I/O port, but it will be + rejected later when the Inquire Extended Setup Information command is + issued in BusLogic_CheckHostAdapter. The AMI FastDisk Host Adapter is a + BusLogic clone that implements the same interface as earlier BusLogic + Host Adapters, including the undocumented commands, and is therefore + supported by this driver. However, the AMI FastDisk always returns 0x00 + upon reading the Geometry Register, so the extended translation option + should always be left disabled on the AMI FastDisk. + */ + if (GeometryRegister.All == 0xFF) return false; + /* + Indicate the Host Adapter Probe completed successfully. + */ + return true; +} + + +/* + BusLogic_HardwareResetHostAdapter issues a Hardware Reset to the Host Adapter + and waits for Host Adapter Diagnostics to complete. If HardReset is true, a + Hard Reset is performed which also initiates a SCSI Bus Reset. Otherwise, a + Soft Reset is performed which only resets the Host Adapter without forcing a + SCSI Bus Reset. +*/ + +static boolean BusLogic_HardwareResetHostAdapter(BusLogic_HostAdapter_T + *HostAdapter, + boolean HardReset) +{ + BusLogic_StatusRegister_T StatusRegister; + int TimeoutCounter; + /* + FlashPoint Host Adapters are Hard Reset by the FlashPoint SCCB Manager. + */ + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) + { + FlashPoint_Info_T *FlashPointInfo = &HostAdapter->FlashPointInfo; + FlashPointInfo->HostSoftReset = !HardReset; + FlashPointInfo->ReportDataUnderrun = true; + HostAdapter->CardHandle = + FlashPoint_HardwareResetHostAdapter(FlashPointInfo); + if (HostAdapter->CardHandle == FlashPoint_BadCardHandle) return false; + /* + Indicate the Host Adapter Hard Reset completed successfully. + */ + return true; + } + /* + Issue a Hard Reset or Soft Reset Command to the Host Adapter. The Host + Adapter should respond by setting Diagnostic Active in the Status Register. + */ + if (HardReset) + BusLogic_HardReset(HostAdapter); + else BusLogic_SoftReset(HostAdapter); + /* + Wait until Diagnostic Active is set in the Status Register. + */ + TimeoutCounter = 5*10000; + while (--TimeoutCounter >= 0) + { + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (StatusRegister.Bits.DiagnosticActive) break; + udelay(100); + } + if (BusLogic_GlobalOptions.TraceHardwareReset) + BusLogic_Notice("BusLogic_HardwareReset(0x%X): Diagnostic Active, " + "Status 0x%02X\n", HostAdapter, + HostAdapter->IO_Address, StatusRegister.All); + if (TimeoutCounter < 0) return false; + /* + Wait 100 microseconds to allow completion of any initial diagnostic + activity which might leave the contents of the Status Register + unpredictable. + */ + udelay(100); + /* + Wait until Diagnostic Active is reset in the Status Register. + */ + TimeoutCounter = 10*10000; + while (--TimeoutCounter >= 0) + { + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (!StatusRegister.Bits.DiagnosticActive) break; + udelay(100); + } + if (BusLogic_GlobalOptions.TraceHardwareReset) + BusLogic_Notice("BusLogic_HardwareReset(0x%X): Diagnostic Completed, " + "Status 0x%02X\n", HostAdapter, + HostAdapter->IO_Address, StatusRegister.All); + if (TimeoutCounter < 0) return false; + /* + Wait until at least one of the Diagnostic Failure, Host Adapter Ready, + or Data In Register Ready bits is set in the Status Register. + */ + TimeoutCounter = 10000; + while (--TimeoutCounter >= 0) + { + StatusRegister.All = BusLogic_ReadStatusRegister(HostAdapter); + if (StatusRegister.Bits.DiagnosticFailure || + StatusRegister.Bits.HostAdapterReady || + StatusRegister.Bits.DataInRegisterReady) + break; + udelay(100); + } + if (BusLogic_GlobalOptions.TraceHardwareReset) + BusLogic_Notice("BusLogic_HardwareReset(0x%X): Host Adapter Ready, " + "Status 0x%02X\n", HostAdapter, + HostAdapter->IO_Address, StatusRegister.All); + if (TimeoutCounter < 0) return false; + /* + If Diagnostic Failure is set or Host Adapter Ready is reset, then an + error occurred during the Host Adapter diagnostics. If Data In Register + Ready is set, then there is an Error Code available. + */ + if (StatusRegister.Bits.DiagnosticFailure || + !StatusRegister.Bits.HostAdapterReady) + { + BusLogic_CommandFailureReason = NULL; + BusLogic_Failure(HostAdapter, "HARD RESET DIAGNOSTICS"); + BusLogic_Error("HOST ADAPTER STATUS REGISTER = %02X\n", + HostAdapter, StatusRegister.All); + if (StatusRegister.Bits.DataInRegisterReady) + { + unsigned char ErrorCode = BusLogic_ReadDataInRegister(HostAdapter); + BusLogic_Error("HOST ADAPTER ERROR CODE = %d\n", + HostAdapter, ErrorCode); + } + return false; + } + /* + Indicate the Host Adapter Hard Reset completed successfully. + */ + return true; +} + + +/* + BusLogic_CheckHostAdapter checks to be sure this really is a BusLogic + Host Adapter. +*/ + +static boolean BusLogic_CheckHostAdapter(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_ExtendedSetupInformation_T ExtendedSetupInformation; + BusLogic_RequestedReplyLength_T RequestedReplyLength; + boolean Result = true; + /* + FlashPoint Host Adapters do not require this protection. + */ + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) return true; + /* + Issue the Inquire Extended Setup Information command. Only genuine + BusLogic Host Adapters and true clones support this command. Adaptec 1542C + series Host Adapters that respond to the Geometry Register I/O port will + fail this command. + */ + RequestedReplyLength = sizeof(ExtendedSetupInformation); + if (BusLogic_Command(HostAdapter, + BusLogic_InquireExtendedSetupInformation, + &RequestedReplyLength, + sizeof(RequestedReplyLength), + &ExtendedSetupInformation, + sizeof(ExtendedSetupInformation)) + != sizeof(ExtendedSetupInformation)) + Result = false; + /* + Provide tracing information if requested and return. + */ + if (BusLogic_GlobalOptions.TraceProbe) + BusLogic_Notice("BusLogic_Check(0x%X): MultiMaster %s\n", HostAdapter, + HostAdapter->IO_Address, (Result ? "Found" : "Not Found")); + return Result; +} + + +/* + BusLogic_ReadHostAdapterConfiguration reads the Configuration Information + from Host Adapter and initializes the Host Adapter structure. +*/ + +static boolean BusLogic_ReadHostAdapterConfiguration(BusLogic_HostAdapter_T + *HostAdapter) +{ + BusLogic_BoardID_T BoardID; + BusLogic_Configuration_T Configuration; + BusLogic_SetupInformation_T SetupInformation; + BusLogic_ExtendedSetupInformation_T ExtendedSetupInformation; + BusLogic_HostAdapterModelNumber_T HostAdapterModelNumber; + BusLogic_FirmwareVersion3rdDigit_T FirmwareVersion3rdDigit; + BusLogic_FirmwareVersionLetter_T FirmwareVersionLetter; + BusLogic_PCIHostAdapterInformation_T PCIHostAdapterInformation; + BusLogic_FetchHostAdapterLocalRAMRequest_T FetchHostAdapterLocalRAMRequest; + BusLogic_AutoSCSIData_T AutoSCSIData; + BusLogic_GeometryRegister_T GeometryRegister; + BusLogic_RequestedReplyLength_T RequestedReplyLength; + unsigned char *TargetPointer, Character; + int TargetID, i; + /* + Configuration Information for FlashPoint Host Adapters is provided in the + FlashPoint_Info structure by the FlashPoint SCCB Manager's Probe Function. + Initialize fields in the Host Adapter structure from the FlashPoint_Info + structure. + */ + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) + { + FlashPoint_Info_T *FlashPointInfo = &HostAdapter->FlashPointInfo; + TargetPointer = HostAdapter->ModelName; + *TargetPointer++ = 'B'; + *TargetPointer++ = 'T'; + *TargetPointer++ = '-'; + for (i = 0; i < sizeof(FlashPointInfo->ModelNumber); i++) + *TargetPointer++ = FlashPointInfo->ModelNumber[i]; + *TargetPointer++ = '\0'; + strcpy(HostAdapter->FirmwareVersion, FlashPoint_FirmwareVersion); + HostAdapter->SCSI_ID = FlashPointInfo->SCSI_ID; + HostAdapter->ExtendedTranslationEnabled = + FlashPointInfo->ExtendedTranslationEnabled; + HostAdapter->ParityCheckingEnabled = + FlashPointInfo->ParityCheckingEnabled; + HostAdapter->BusResetEnabled = !FlashPointInfo->HostSoftReset; + HostAdapter->LevelSensitiveInterrupt = true; + HostAdapter->HostWideSCSI = FlashPointInfo->HostWideSCSI; + HostAdapter->HostDifferentialSCSI = false; + HostAdapter->HostSupportsSCAM = true; + HostAdapter->HostUltraSCSI = true; + HostAdapter->ExtendedLUNSupport = true; + HostAdapter->TerminationInfoValid = true; + HostAdapter->LowByteTerminated = FlashPointInfo->LowByteTerminated; + HostAdapter->HighByteTerminated = FlashPointInfo->HighByteTerminated; + HostAdapter->SCAM_Enabled = FlashPointInfo->SCAM_Enabled; + HostAdapter->SCAM_Level2 = FlashPointInfo->SCAM_Level2; + HostAdapter->DriverScatterGatherLimit = BusLogic_ScatterGatherLimit; + HostAdapter->MaxTargetDevices = (HostAdapter->HostWideSCSI ? 16 : 8); + HostAdapter->MaxLogicalUnits = 32; + HostAdapter->InitialCCBs = 4 * BusLogic_CCB_AllocationGroupSize; + HostAdapter->IncrementalCCBs = BusLogic_CCB_AllocationGroupSize; + HostAdapter->DriverQueueDepth = 255; + HostAdapter->HostAdapterQueueDepth = HostAdapter->DriverQueueDepth; + HostAdapter->SynchronousPermitted = FlashPointInfo->SynchronousPermitted; + HostAdapter->FastPermitted = FlashPointInfo->FastPermitted; + HostAdapter->UltraPermitted = FlashPointInfo->UltraPermitted; + HostAdapter->WidePermitted = FlashPointInfo->WidePermitted; + HostAdapter->DisconnectPermitted = FlashPointInfo->DisconnectPermitted; + HostAdapter->TaggedQueuingPermitted = 0xFFFF; + goto Common; + } + /* + Issue the Inquire Board ID command. + */ + if (BusLogic_Command(HostAdapter, BusLogic_InquireBoardID, NULL, 0, + &BoardID, sizeof(BoardID)) != sizeof(BoardID)) + return BusLogic_Failure(HostAdapter, "INQUIRE BOARD ID"); + /* + Issue the Inquire Configuration command. + */ + if (BusLogic_Command(HostAdapter, BusLogic_InquireConfiguration, NULL, 0, + &Configuration, sizeof(Configuration)) + != sizeof(Configuration)) + return BusLogic_Failure(HostAdapter, "INQUIRE CONFIGURATION"); + /* + Issue the Inquire Setup Information command. + */ + RequestedReplyLength = sizeof(SetupInformation); + if (BusLogic_Command(HostAdapter, BusLogic_InquireSetupInformation, + &RequestedReplyLength, sizeof(RequestedReplyLength), + &SetupInformation, sizeof(SetupInformation)) + != sizeof(SetupInformation)) + return BusLogic_Failure(HostAdapter, "INQUIRE SETUP INFORMATION"); + /* + Issue the Inquire Extended Setup Information command. + */ + RequestedReplyLength = sizeof(ExtendedSetupInformation); + if (BusLogic_Command(HostAdapter, BusLogic_InquireExtendedSetupInformation, + &RequestedReplyLength, sizeof(RequestedReplyLength), + &ExtendedSetupInformation, + sizeof(ExtendedSetupInformation)) + != sizeof(ExtendedSetupInformation)) + return BusLogic_Failure(HostAdapter, "INQUIRE EXTENDED SETUP INFORMATION"); + /* + Issue the Inquire Firmware Version 3rd Digit command. + */ + FirmwareVersion3rdDigit = '\0'; + if (BoardID.FirmwareVersion1stDigit > '0') + if (BusLogic_Command(HostAdapter, BusLogic_InquireFirmwareVersion3rdDigit, + NULL, 0, &FirmwareVersion3rdDigit, + sizeof(FirmwareVersion3rdDigit)) + != sizeof(FirmwareVersion3rdDigit)) + return BusLogic_Failure(HostAdapter, "INQUIRE FIRMWARE 3RD DIGIT"); + /* + Issue the Inquire Host Adapter Model Number command. + */ + if (ExtendedSetupInformation.BusType == 'A' && + BoardID.FirmwareVersion1stDigit == '2') + /* BusLogic BT-542B ISA 2.xx */ + strcpy(HostAdapterModelNumber, "542B"); + else if (ExtendedSetupInformation.BusType == 'E' && + BoardID.FirmwareVersion1stDigit == '2' && + (BoardID.FirmwareVersion2ndDigit <= '1' || + (BoardID.FirmwareVersion2ndDigit == '2' && + FirmwareVersion3rdDigit == '0'))) + /* BusLogic BT-742A EISA 2.1x or 2.20 */ + strcpy(HostAdapterModelNumber, "742A"); + else if (ExtendedSetupInformation.BusType == 'E' && + BoardID.FirmwareVersion1stDigit == '0') + /* AMI FastDisk EISA Series 441 0.x */ + strcpy(HostAdapterModelNumber, "747A"); + else + { + RequestedReplyLength = sizeof(HostAdapterModelNumber); + if (BusLogic_Command(HostAdapter, BusLogic_InquireHostAdapterModelNumber, + &RequestedReplyLength, sizeof(RequestedReplyLength), + &HostAdapterModelNumber, + sizeof(HostAdapterModelNumber)) + != sizeof(HostAdapterModelNumber)) + return BusLogic_Failure(HostAdapter, + "INQUIRE HOST ADAPTER MODEL NUMBER"); + } + /* + BusLogic MultiMaster Host Adapters can be identified by their model number + and the major version number of their firmware as follows: + + 5.xx BusLogic "W" Series Host Adapters: + BT-948/958/958D + 4.xx BusLogic "C" Series Host Adapters: + BT-946C/956C/956CD/747C/757C/757CD/445C/545C/540CF + 3.xx BusLogic "S" Series Host Adapters: + BT-747S/747D/757S/757D/445S/545S/542D + BT-542B/742A (revision H) + 2.xx BusLogic "A" Series Host Adapters: + BT-542B/742A (revision G and below) + 0.xx AMI FastDisk VLB/EISA BusLogic Clone Host Adapter + */ + /* + Save the Model Name and Host Adapter Name in the Host Adapter structure. + */ + TargetPointer = HostAdapter->ModelName; + *TargetPointer++ = 'B'; + *TargetPointer++ = 'T'; + *TargetPointer++ = '-'; + for (i = 0; i < sizeof(HostAdapterModelNumber); i++) + { + Character = HostAdapterModelNumber[i]; + if (Character == ' ' || Character == '\0') break; + *TargetPointer++ = Character; + } + *TargetPointer++ = '\0'; + /* + Save the Firmware Version in the Host Adapter structure. + */ + TargetPointer = HostAdapter->FirmwareVersion; + *TargetPointer++ = BoardID.FirmwareVersion1stDigit; + *TargetPointer++ = '.'; + *TargetPointer++ = BoardID.FirmwareVersion2ndDigit; + if (FirmwareVersion3rdDigit != ' ' && FirmwareVersion3rdDigit != '\0') + *TargetPointer++ = FirmwareVersion3rdDigit; + *TargetPointer = '\0'; + /* + Issue the Inquire Firmware Version Letter command. + */ + if (strcmp(HostAdapter->FirmwareVersion, "3.3") >= 0) + { + if (BusLogic_Command(HostAdapter, BusLogic_InquireFirmwareVersionLetter, + NULL, 0, &FirmwareVersionLetter, + sizeof(FirmwareVersionLetter)) + != sizeof(FirmwareVersionLetter)) + return BusLogic_Failure(HostAdapter, + "INQUIRE FIRMWARE VERSION LETTER"); + if (FirmwareVersionLetter != ' ' && FirmwareVersionLetter != '\0') + *TargetPointer++ = FirmwareVersionLetter; + *TargetPointer = '\0'; + } + /* + Save the Host Adapter SCSI ID in the Host Adapter structure. + */ + HostAdapter->SCSI_ID = Configuration.HostAdapterID; + /* + Determine the Bus Type and save it in the Host Adapter structure, determine + and save the IRQ Channel if necessary, and determine and save the DMA + Channel for ISA Host Adapters. + */ + HostAdapter->HostAdapterBusType = + BusLogic_HostAdapterBusTypes[HostAdapter->ModelName[3] - '4']; + if (HostAdapter->IRQ_Channel == 0) + { + if (Configuration.IRQ_Channel9) + HostAdapter->IRQ_Channel = 9; + else if (Configuration.IRQ_Channel10) + HostAdapter->IRQ_Channel = 10; + else if (Configuration.IRQ_Channel11) + HostAdapter->IRQ_Channel = 11; + else if (Configuration.IRQ_Channel12) + HostAdapter->IRQ_Channel = 12; + else if (Configuration.IRQ_Channel14) + HostAdapter->IRQ_Channel = 14; + else if (Configuration.IRQ_Channel15) + HostAdapter->IRQ_Channel = 15; + } + if (HostAdapter->HostAdapterBusType == BusLogic_ISA_Bus) + { + if (Configuration.DMA_Channel5) + HostAdapter->DMA_Channel = 5; + else if (Configuration.DMA_Channel6) + HostAdapter->DMA_Channel = 6; + else if (Configuration.DMA_Channel7) + HostAdapter->DMA_Channel = 7; + } + /* + Determine whether Extended Translation is enabled and save it in + the Host Adapter structure. + */ + GeometryRegister.All = BusLogic_ReadGeometryRegister(HostAdapter); + HostAdapter->ExtendedTranslationEnabled = + GeometryRegister.Bits.ExtendedTranslationEnabled; + /* + Save the Scatter Gather Limits, Level Sensitive Interrupt flag, Wide + SCSI flag, Differential SCSI flag, SCAM Supported flag, and + Ultra SCSI flag in the Host Adapter structure. + */ + HostAdapter->HostAdapterScatterGatherLimit = + ExtendedSetupInformation.ScatterGatherLimit; + HostAdapter->DriverScatterGatherLimit = + HostAdapter->HostAdapterScatterGatherLimit; + if (HostAdapter->HostAdapterScatterGatherLimit > BusLogic_ScatterGatherLimit) + HostAdapter->DriverScatterGatherLimit = BusLogic_ScatterGatherLimit; + if (ExtendedSetupInformation.Misc.LevelSensitiveInterrupt) + HostAdapter->LevelSensitiveInterrupt = true; + HostAdapter->HostWideSCSI = ExtendedSetupInformation.HostWideSCSI; + HostAdapter->HostDifferentialSCSI = + ExtendedSetupInformation.HostDifferentialSCSI; + HostAdapter->HostSupportsSCAM = ExtendedSetupInformation.HostSupportsSCAM; + HostAdapter->HostUltraSCSI = ExtendedSetupInformation.HostUltraSCSI; + /* + Determine whether Extended LUN Format CCBs are supported and save the + information in the Host Adapter structure. + */ + if (HostAdapter->FirmwareVersion[0] == '5' || + (HostAdapter->FirmwareVersion[0] == '4' && HostAdapter->HostWideSCSI)) + HostAdapter->ExtendedLUNSupport = true; + /* + Issue the Inquire PCI Host Adapter Information command to read the + Termination Information from "W" series MultiMaster Host Adapters. + */ + if (HostAdapter->FirmwareVersion[0] == '5') + { + if (BusLogic_Command(HostAdapter, + BusLogic_InquirePCIHostAdapterInformation, + NULL, 0, &PCIHostAdapterInformation, + sizeof(PCIHostAdapterInformation)) + != sizeof(PCIHostAdapterInformation)) + return BusLogic_Failure(HostAdapter, + "INQUIRE PCI HOST ADAPTER INFORMATION"); + /* + Save the Termination Information in the Host Adapter structure. + */ + if (PCIHostAdapterInformation.GenericInfoValid) + { + HostAdapter->TerminationInfoValid = true; + HostAdapter->LowByteTerminated = + PCIHostAdapterInformation.LowByteTerminated; + HostAdapter->HighByteTerminated = + PCIHostAdapterInformation.HighByteTerminated; + } + } + /* + Issue the Fetch Host Adapter Local RAM command to read the AutoSCSI data + from "W" and "C" series MultiMaster Host Adapters. + */ + if (HostAdapter->FirmwareVersion[0] >= '4') + { + FetchHostAdapterLocalRAMRequest.ByteOffset = + BusLogic_AutoSCSI_BaseOffset; + FetchHostAdapterLocalRAMRequest.ByteCount = sizeof(AutoSCSIData); + if (BusLogic_Command(HostAdapter, + BusLogic_FetchHostAdapterLocalRAM, + &FetchHostAdapterLocalRAMRequest, + sizeof(FetchHostAdapterLocalRAMRequest), + &AutoSCSIData, sizeof(AutoSCSIData)) + != sizeof(AutoSCSIData)) + return BusLogic_Failure(HostAdapter, "FETCH HOST ADAPTER LOCAL RAM"); + /* + Save the Parity Checking Enabled, Bus Reset Enabled, and Termination + Information in the Host Adapter structure. + */ + HostAdapter->ParityCheckingEnabled = AutoSCSIData.ParityCheckingEnabled; + HostAdapter->BusResetEnabled = AutoSCSIData.BusResetEnabled; + if (HostAdapter->FirmwareVersion[0] == '4') + { + HostAdapter->TerminationInfoValid = true; + HostAdapter->LowByteTerminated = AutoSCSIData.LowByteTerminated; + HostAdapter->HighByteTerminated = AutoSCSIData.HighByteTerminated; + } + /* + Save the Wide Permitted, Fast Permitted, Synchronous Permitted, + Disconnect Permitted, Ultra Permitted, and SCAM Information in the + Host Adapter structure. + */ + HostAdapter->WidePermitted = AutoSCSIData.WidePermitted; + HostAdapter->FastPermitted = AutoSCSIData.FastPermitted; + HostAdapter->SynchronousPermitted = + AutoSCSIData.SynchronousPermitted; + HostAdapter->DisconnectPermitted = + AutoSCSIData.DisconnectPermitted; + if (HostAdapter->HostUltraSCSI) + HostAdapter->UltraPermitted = AutoSCSIData.UltraPermitted; + if (HostAdapter->HostSupportsSCAM) + { + HostAdapter->SCAM_Enabled = AutoSCSIData.SCAM_Enabled; + HostAdapter->SCAM_Level2 = AutoSCSIData.SCAM_Level2; + } + } + /* + Initialize fields in the Host Adapter structure for "S" and "A" series + MultiMaster Host Adapters. + */ + if (HostAdapter->FirmwareVersion[0] < '4') + { + if (SetupInformation.SynchronousInitiationEnabled) + { + HostAdapter->SynchronousPermitted = 0xFF; + if (HostAdapter->HostAdapterBusType == BusLogic_EISA_Bus) + { + if (ExtendedSetupInformation.Misc.FastOnEISA) + HostAdapter->FastPermitted = 0xFF; + if (strcmp(HostAdapter->ModelName, "BT-757") == 0) + HostAdapter->WidePermitted = 0xFF; + } + } + HostAdapter->DisconnectPermitted = 0xFF; + HostAdapter->ParityCheckingEnabled = + SetupInformation.ParityCheckingEnabled; + HostAdapter->BusResetEnabled = true; + } + /* + Determine the maximum number of Target IDs and Logical Units supported by + this driver for Wide and Narrow Host Adapters. + */ + HostAdapter->MaxTargetDevices = (HostAdapter->HostWideSCSI ? 16 : 8); + HostAdapter->MaxLogicalUnits = (HostAdapter->ExtendedLUNSupport ? 32 : 8); + /* + Select appropriate values for the Mailbox Count, Driver Queue Depth, + Initial CCBs, and Incremental CCBs variables based on whether or not Strict + Round Robin Mode is supported. If Strict Round Robin Mode is supported, + then there is no performance degradation in using the maximum possible + number of Outgoing and Incoming Mailboxes and allowing the Tagged and + Untagged Queue Depths to determine the actual utilization. If Strict Round + Robin Mode is not supported, then the Host Adapter must scan all the + Outgoing Mailboxes whenever an Outgoing Mailbox entry is made, which can + cause a substantial performance penalty. The host adapters actually have + room to store the following number of CCBs internally; that is, they can + internally queue and manage this many active commands on the SCSI bus + simultaneously. Performance measurements demonstrate that the Driver Queue + Depth should be set to the Mailbox Count, rather than the Host Adapter + Queue Depth (internal CCB capacity), as it is more efficient to have the + queued commands waiting in Outgoing Mailboxes if necessary than to block + the process in the higher levels of the SCSI Subsystem. + + 192 BT-948/958/958D + 100 BT-946C/956C/956CD/747C/757C/757CD/445C + 50 BT-545C/540CF + 30 BT-747S/747D/757S/757D/445S/545S/542D/542B/742A + */ + if (HostAdapter->FirmwareVersion[0] == '5') + HostAdapter->HostAdapterQueueDepth = 192; + else if (HostAdapter->FirmwareVersion[0] == '4') + HostAdapter->HostAdapterQueueDepth = + (HostAdapter->HostAdapterBusType != BusLogic_ISA_Bus ? 100 : 50); + else HostAdapter->HostAdapterQueueDepth = 30; + if (strcmp(HostAdapter->FirmwareVersion, "3.31") >= 0) + { + HostAdapter->StrictRoundRobinModeSupport = true; + HostAdapter->MailboxCount = BusLogic_MaxMailboxes; + } + else + { + HostAdapter->StrictRoundRobinModeSupport = false; + HostAdapter->MailboxCount = 32; + } + HostAdapter->DriverQueueDepth = HostAdapter->MailboxCount; + HostAdapter->InitialCCBs = 4 * BusLogic_CCB_AllocationGroupSize; + HostAdapter->IncrementalCCBs = BusLogic_CCB_AllocationGroupSize; + /* + Tagged Queuing support is available and operates properly on all "W" series + MultiMaster Host Adapters, on "C" series MultiMaster Host Adapters with + firmware version 4.22 and above, and on "S" series MultiMaster Host + Adapters with firmware version 3.35 and above. + */ + HostAdapter->TaggedQueuingPermitted = 0; + switch (HostAdapter->FirmwareVersion[0]) + { + case '5': + HostAdapter->TaggedQueuingPermitted = 0xFFFF; + break; + case '4': + if (strcmp(HostAdapter->FirmwareVersion, "4.22") >= 0) + HostAdapter->TaggedQueuingPermitted = 0xFFFF; + break; + case '3': + if (strcmp(HostAdapter->FirmwareVersion, "3.35") >= 0) + HostAdapter->TaggedQueuingPermitted = 0xFFFF; + break; + } + /* + Determine the Host Adapter BIOS Address if the BIOS is enabled and + save it in the Host Adapter structure. The BIOS is disabled if the + BIOS_Address is 0. + */ + HostAdapter->BIOS_Address = ExtendedSetupInformation.BIOS_Address << 12; + /* + ISA Host Adapters require Bounce Buffers if there is more than 16MB memory. + */ + if (HostAdapter->HostAdapterBusType == BusLogic_ISA_Bus && + (void *) high_memory > (void *) MAX_DMA_ADDRESS) + HostAdapter->BounceBuffersRequired = true; + /* + BusLogic BT-445S Host Adapters prior to board revision E have a hardware + bug whereby when the BIOS is enabled, transfers to/from the same address + range the BIOS occupies modulo 16MB are handled incorrectly. Only properly + functioning BT-445S Host Adapters have firmware version 3.37, so require + that ISA Bounce Buffers be used for the buggy BT-445S models if there is + more than 16MB memory. + */ + if (HostAdapter->BIOS_Address > 0 && + strcmp(HostAdapter->ModelName, "BT-445S") == 0 && + strcmp(HostAdapter->FirmwareVersion, "3.37") < 0 && + (void *) high_memory > (void *) MAX_DMA_ADDRESS) + HostAdapter->BounceBuffersRequired = true; + /* + Initialize parameters common to MultiMaster and FlashPoint Host Adapters. + */ +Common: + /* + Initialize the Host Adapter Full Model Name from the Model Name. + */ + strcpy(HostAdapter->FullModelName, "BusLogic "); + strcat(HostAdapter->FullModelName, HostAdapter->ModelName); + /* + Select an appropriate value for the Tagged Queue Depth either from a + BusLogic Driver Options specification, or based on whether this Host + Adapter requires that ISA Bounce Buffers be used. The Tagged Queue Depth + is left at 0 for automatic determination in BusLogic_SelectQueueDepths. + Initialize the Untagged Queue Depth. + */ + for (TargetID = 0; TargetID < BusLogic_MaxTargetDevices; TargetID++) + { + unsigned char QueueDepth = 0; + if (HostAdapter->DriverOptions != NULL && + HostAdapter->DriverOptions->QueueDepth[TargetID] > 0) + QueueDepth = HostAdapter->DriverOptions->QueueDepth[TargetID]; + else if (HostAdapter->BounceBuffersRequired) + QueueDepth = BusLogic_TaggedQueueDepthBB; + HostAdapter->QueueDepth[TargetID] = QueueDepth; + } + if (HostAdapter->BounceBuffersRequired) + HostAdapter->UntaggedQueueDepth = BusLogic_UntaggedQueueDepthBB; + else HostAdapter->UntaggedQueueDepth = BusLogic_UntaggedQueueDepth; + if (HostAdapter->DriverOptions != NULL) + HostAdapter->CommonQueueDepth = + HostAdapter->DriverOptions->CommonQueueDepth; + if (HostAdapter->CommonQueueDepth > 0 && + HostAdapter->CommonQueueDepth < HostAdapter->UntaggedQueueDepth) + HostAdapter->UntaggedQueueDepth = HostAdapter->CommonQueueDepth; + /* + Tagged Queuing is only allowed if Disconnect/Reconnect is permitted. + Therefore, mask the Tagged Queuing Permitted Default bits with the + Disconnect/Reconnect Permitted bits. + */ + HostAdapter->TaggedQueuingPermitted &= HostAdapter->DisconnectPermitted; + /* + Combine the default Tagged Queuing Permitted bits with any BusLogic Driver + Options Tagged Queuing specification. + */ + if (HostAdapter->DriverOptions != NULL) + HostAdapter->TaggedQueuingPermitted = + (HostAdapter->DriverOptions->TaggedQueuingPermitted & + HostAdapter->DriverOptions->TaggedQueuingPermittedMask) | + (HostAdapter->TaggedQueuingPermitted & + ~HostAdapter->DriverOptions->TaggedQueuingPermittedMask); + /* + Select appropriate values for the Error Recovery Strategy array + either from a BusLogic Driver Options specification, or using + BusLogic_ErrorRecovery_Default. + */ + for (TargetID = 0; TargetID < BusLogic_MaxTargetDevices; TargetID++) + if (HostAdapter->DriverOptions != NULL) + HostAdapter->ErrorRecoveryStrategy[TargetID] = + HostAdapter->DriverOptions->ErrorRecoveryStrategy[TargetID]; + else HostAdapter->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_Default; + /* + Select an appropriate value for Bus Settle Time either from a BusLogic + Driver Options specification, or from BusLogic_DefaultBusSettleTime. + */ + if (HostAdapter->DriverOptions != NULL && + HostAdapter->DriverOptions->BusSettleTime > 0) + HostAdapter->BusSettleTime = HostAdapter->DriverOptions->BusSettleTime; + else HostAdapter->BusSettleTime = BusLogic_DefaultBusSettleTime; + /* + Indicate reading the Host Adapter Configuration completed successfully. + */ + return true; +} + + +/* + BusLogic_ReportHostAdapterConfiguration reports the configuration of + Host Adapter. +*/ + +static boolean BusLogic_ReportHostAdapterConfiguration(BusLogic_HostAdapter_T + *HostAdapter) +{ + unsigned short AllTargetsMask = (1 << HostAdapter->MaxTargetDevices) - 1; + unsigned short SynchronousPermitted, FastPermitted; + unsigned short UltraPermitted, WidePermitted; + unsigned short DisconnectPermitted, TaggedQueuingPermitted; + boolean CommonSynchronousNegotiation, CommonTaggedQueueDepth; + boolean CommonErrorRecovery; + char SynchronousString[BusLogic_MaxTargetDevices+1]; + char WideString[BusLogic_MaxTargetDevices+1]; + char DisconnectString[BusLogic_MaxTargetDevices+1]; + char TaggedQueuingString[BusLogic_MaxTargetDevices+1]; + char ErrorRecoveryString[BusLogic_MaxTargetDevices+1]; + char *SynchronousMessage = SynchronousString; + char *WideMessage = WideString; + char *DisconnectMessage = DisconnectString; + char *TaggedQueuingMessage = TaggedQueuingString; + char *ErrorRecoveryMessage = ErrorRecoveryString; + int TargetID; + BusLogic_Info("Configuring BusLogic Model %s %s%s%s%s SCSI Host Adapter\n", + HostAdapter, HostAdapter->ModelName, + BusLogic_HostAdapterBusNames[HostAdapter->HostAdapterBusType], + (HostAdapter->HostWideSCSI ? " Wide" : ""), + (HostAdapter->HostDifferentialSCSI ? " Differential" : ""), + (HostAdapter->HostUltraSCSI ? " Ultra" : "")); + BusLogic_Info(" Firmware Version: %s, I/O Address: 0x%X, " + "IRQ Channel: %d/%s\n", HostAdapter, + HostAdapter->FirmwareVersion, + HostAdapter->IO_Address, HostAdapter->IRQ_Channel, + (HostAdapter->LevelSensitiveInterrupt ? "Level" : "Edge")); + if (HostAdapter->HostAdapterBusType != BusLogic_PCI_Bus) + { + BusLogic_Info(" DMA Channel: ", HostAdapter); + if (HostAdapter->DMA_Channel > 0) + BusLogic_Info("%d, ", HostAdapter, HostAdapter->DMA_Channel); + else BusLogic_Info("None, ", HostAdapter); + if (HostAdapter->BIOS_Address > 0) + BusLogic_Info("BIOS Address: 0x%X, ", HostAdapter, + HostAdapter->BIOS_Address); + else BusLogic_Info("BIOS Address: None, ", HostAdapter); + } + else + { + BusLogic_Info(" PCI Bus: %d, Device: %d, Address: ", + HostAdapter, HostAdapter->Bus, HostAdapter->Device); + if (HostAdapter->PCI_Address > 0) + BusLogic_Info("0x%X, ", HostAdapter, HostAdapter->PCI_Address); + else BusLogic_Info("Unassigned, ", HostAdapter); + } + BusLogic_Info("Host Adapter SCSI ID: %d\n", HostAdapter, + HostAdapter->SCSI_ID); + BusLogic_Info(" Parity Checking: %s, Extended Translation: %s\n", + HostAdapter, + (HostAdapter->ParityCheckingEnabled + ? "Enabled" : "Disabled"), + (HostAdapter->ExtendedTranslationEnabled + ? "Enabled" : "Disabled")); + AllTargetsMask &= ~(1 << HostAdapter->SCSI_ID); + SynchronousPermitted = HostAdapter->SynchronousPermitted & AllTargetsMask; + FastPermitted = HostAdapter->FastPermitted & AllTargetsMask; + UltraPermitted = HostAdapter->UltraPermitted & AllTargetsMask; + if ((BusLogic_MultiMasterHostAdapterP(HostAdapter) && + (HostAdapter->FirmwareVersion[0] >= '4' || + HostAdapter->HostAdapterBusType == BusLogic_EISA_Bus)) || + BusLogic_FlashPointHostAdapterP(HostAdapter)) + { + CommonSynchronousNegotiation = false; + if (SynchronousPermitted == 0) + { + SynchronousMessage = "Disabled"; + CommonSynchronousNegotiation = true; + } + else if (SynchronousPermitted == AllTargetsMask) + { + if (FastPermitted == 0) + { + SynchronousMessage = "Slow"; + CommonSynchronousNegotiation = true; + } + else if (FastPermitted == AllTargetsMask) + { + if (UltraPermitted == 0) + { + SynchronousMessage = "Fast"; + CommonSynchronousNegotiation = true; + } + else if (UltraPermitted == AllTargetsMask) + { + SynchronousMessage = "Ultra"; + CommonSynchronousNegotiation = true; + } + } + } + if (!CommonSynchronousNegotiation) + { + for (TargetID = 0; + TargetID < HostAdapter->MaxTargetDevices; + TargetID++) + SynchronousString[TargetID] = + ((!(SynchronousPermitted & (1 << TargetID))) ? 'N' : + (!(FastPermitted & (1 << TargetID)) ? 'S' : + (!(UltraPermitted & (1 << TargetID)) ? 'F' : 'U'))); + SynchronousString[HostAdapter->SCSI_ID] = '#'; + SynchronousString[HostAdapter->MaxTargetDevices] = '\0'; + } + } + else SynchronousMessage = + (SynchronousPermitted == 0 ? "Disabled" : "Enabled"); + WidePermitted = HostAdapter->WidePermitted & AllTargetsMask; + if (WidePermitted == 0) + WideMessage = "Disabled"; + else if (WidePermitted == AllTargetsMask) + WideMessage = "Enabled"; + else + { + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + WideString[TargetID] = + ((WidePermitted & (1 << TargetID)) ? 'Y' : 'N'); + WideString[HostAdapter->SCSI_ID] = '#'; + WideString[HostAdapter->MaxTargetDevices] = '\0'; + } + DisconnectPermitted = HostAdapter->DisconnectPermitted & AllTargetsMask; + if (DisconnectPermitted == 0) + DisconnectMessage = "Disabled"; + else if (DisconnectPermitted == AllTargetsMask) + DisconnectMessage = "Enabled"; + else + { + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + DisconnectString[TargetID] = + ((DisconnectPermitted & (1 << TargetID)) ? 'Y' : 'N'); + DisconnectString[HostAdapter->SCSI_ID] = '#'; + DisconnectString[HostAdapter->MaxTargetDevices] = '\0'; + } + TaggedQueuingPermitted = + HostAdapter->TaggedQueuingPermitted & AllTargetsMask; + if (TaggedQueuingPermitted == 0) + TaggedQueuingMessage = "Disabled"; + else if (TaggedQueuingPermitted == AllTargetsMask) + TaggedQueuingMessage = "Enabled"; + else + { + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + TaggedQueuingString[TargetID] = + ((TaggedQueuingPermitted & (1 << TargetID)) ? 'Y' : 'N'); + TaggedQueuingString[HostAdapter->SCSI_ID] = '#'; + TaggedQueuingString[HostAdapter->MaxTargetDevices] = '\0'; + } + BusLogic_Info(" Synchronous Negotiation: %s, Wide Negotiation: %s\n", + HostAdapter, SynchronousMessage, WideMessage); + BusLogic_Info(" Disconnect/Reconnect: %s, Tagged Queuing: %s\n", + HostAdapter, DisconnectMessage, TaggedQueuingMessage); + if (BusLogic_MultiMasterHostAdapterP(HostAdapter)) + { + BusLogic_Info(" Scatter/Gather Limit: %d of %d segments, " + "Mailboxes: %d\n", HostAdapter, + HostAdapter->DriverScatterGatherLimit, + HostAdapter->HostAdapterScatterGatherLimit, + HostAdapter->MailboxCount); + BusLogic_Info(" Driver Queue Depth: %d, " + "Host Adapter Queue Depth: %d\n", + HostAdapter, HostAdapter->DriverQueueDepth, + HostAdapter->HostAdapterQueueDepth); + } + else BusLogic_Info(" Driver Queue Depth: %d, " + "Scatter/Gather Limit: %d segments\n", + HostAdapter, HostAdapter->DriverQueueDepth, + HostAdapter->DriverScatterGatherLimit); + BusLogic_Info(" Tagged Queue Depth: ", HostAdapter); + CommonTaggedQueueDepth = true; + for (TargetID = 1; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + if (HostAdapter->QueueDepth[TargetID] != HostAdapter->QueueDepth[0]) + { + CommonTaggedQueueDepth = false; + break; + } + if (CommonTaggedQueueDepth) + { + if (HostAdapter->QueueDepth[0] > 0) + BusLogic_Info("%d", HostAdapter, HostAdapter->QueueDepth[0]); + else BusLogic_Info("Automatic", HostAdapter); + } + else BusLogic_Info("Individual", HostAdapter); + BusLogic_Info(", Untagged Queue Depth: %d\n", HostAdapter, + HostAdapter->UntaggedQueueDepth); + CommonErrorRecovery = true; + for (TargetID = 1; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + if (HostAdapter->ErrorRecoveryStrategy[TargetID] != + HostAdapter->ErrorRecoveryStrategy[0]) + { + CommonErrorRecovery = false; + break; + } + if (CommonErrorRecovery) + ErrorRecoveryMessage = + BusLogic_ErrorRecoveryStrategyNames[ + HostAdapter->ErrorRecoveryStrategy[0]]; + else + { + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + ErrorRecoveryString[TargetID] = + BusLogic_ErrorRecoveryStrategyLetters[ + HostAdapter->ErrorRecoveryStrategy[TargetID]]; + ErrorRecoveryString[HostAdapter->SCSI_ID] = '#'; + ErrorRecoveryString[HostAdapter->MaxTargetDevices] = '\0'; + } + BusLogic_Info(" Error Recovery Strategy: %s, SCSI Bus Reset: %s\n", + HostAdapter, ErrorRecoveryMessage, + (HostAdapter->BusResetEnabled ? "Enabled" : "Disabled")); + if (HostAdapter->TerminationInfoValid) + { + if (HostAdapter->HostWideSCSI) + BusLogic_Info(" SCSI Bus Termination: %s", HostAdapter, + (HostAdapter->LowByteTerminated + ? (HostAdapter->HighByteTerminated + ? "Both Enabled" : "Low Enabled") + : (HostAdapter->HighByteTerminated + ? "High Enabled" : "Both Disabled"))); + else BusLogic_Info(" SCSI Bus Termination: %s", HostAdapter, + (HostAdapter->LowByteTerminated ? + "Enabled" : "Disabled")); + if (HostAdapter->HostSupportsSCAM) + BusLogic_Info(", SCAM: %s", HostAdapter, + (HostAdapter->SCAM_Enabled + ? (HostAdapter->SCAM_Level2 + ? "Enabled, Level 2" : "Enabled, Level 1") + : "Disabled")); + BusLogic_Info("\n", HostAdapter); + } + /* + Indicate reporting the Host Adapter configuration completed successfully. + */ + return true; +} + + +/* + BusLogic_AcquireResources acquires the system resources necessary to use + Host Adapter. +*/ + +static boolean BusLogic_AcquireResources(BusLogic_HostAdapter_T *HostAdapter) +{ + if (HostAdapter->IRQ_Channel == 0) + { + BusLogic_Error("NO LEGAL INTERRUPT CHANNEL ASSIGNED - DETACHING\n", + HostAdapter); + return false; + } + /* + Acquire shared access to the IRQ Channel. + */ + if (request_irq(HostAdapter->IRQ_Channel, BusLogic_InterruptHandler, + SA_INTERRUPT | SA_SHIRQ, + HostAdapter->FullModelName, HostAdapter) < 0) + { + BusLogic_Error("UNABLE TO ACQUIRE IRQ CHANNEL %d - DETACHING\n", + HostAdapter, HostAdapter->IRQ_Channel); + return false; + } + HostAdapter->IRQ_ChannelAcquired = true; + /* + Acquire exclusive access to the DMA Channel. + */ + if (HostAdapter->DMA_Channel > 0) + { + if (request_dma(HostAdapter->DMA_Channel, + HostAdapter->FullModelName) < 0) + { + BusLogic_Error("UNABLE TO ACQUIRE DMA CHANNEL %d - DETACHING\n", + HostAdapter, HostAdapter->DMA_Channel); + return false; + } + set_dma_mode(HostAdapter->DMA_Channel, DMA_MODE_CASCADE); + enable_dma(HostAdapter->DMA_Channel); + HostAdapter->DMA_ChannelAcquired = true; + } + /* + Indicate the System Resource Acquisition completed successfully, + */ + return true; +} + + +/* + BusLogic_ReleaseResources releases any system resources previously acquired + by BusLogic_AcquireResources. +*/ + +static void BusLogic_ReleaseResources(BusLogic_HostAdapter_T *HostAdapter) +{ + /* + Release shared access to the IRQ Channel. + */ + if (HostAdapter->IRQ_ChannelAcquired) + free_irq(HostAdapter->IRQ_Channel, HostAdapter); + /* + Release exclusive access to the DMA Channel. + */ + if (HostAdapter->DMA_ChannelAcquired) + free_dma(HostAdapter->DMA_Channel); +} + + +/* + BusLogic_InitializeHostAdapter initializes Host Adapter. This is the only + function called during SCSI Host Adapter detection which modifies the state + of the Host Adapter from its initial power on or hard reset state. +*/ + +static boolean BusLogic_InitializeHostAdapter(BusLogic_HostAdapter_T + *HostAdapter) +{ + BusLogic_ExtendedMailboxRequest_T ExtendedMailboxRequest; + BusLogic_RoundRobinModeRequest_T RoundRobinModeRequest; + BusLogic_SetCCBFormatRequest_T SetCCBFormatRequest; + int TargetID; + /* + Initialize the pointers to the first and last CCBs that are queued for + completion processing. + */ + HostAdapter->FirstCompletedCCB = NULL; + HostAdapter->LastCompletedCCB = NULL; + /* + Initialize the Bus Device Reset Pending CCB, Tagged Queuing Active, + Command Successful Flag, Active Commands, and Commands Since Reset + for each Target Device. + */ + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + HostAdapter->BusDeviceResetPendingCCB[TargetID] = NULL; + HostAdapter->TargetFlags[TargetID].TaggedQueuingActive = false; + HostAdapter->TargetFlags[TargetID].CommandSuccessfulFlag = false; + HostAdapter->ActiveCommands[TargetID] = 0; + HostAdapter->CommandsSinceReset[TargetID] = 0; + } + /* + FlashPoint Host Adapters do not use Outgoing and Incoming Mailboxes. + */ + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) goto Done; + /* + Initialize the Outgoing and Incoming Mailbox pointers. + */ + HostAdapter->FirstOutgoingMailbox = + (BusLogic_OutgoingMailbox_T *) HostAdapter->MailboxSpace; + HostAdapter->LastOutgoingMailbox = + HostAdapter->FirstOutgoingMailbox + HostAdapter->MailboxCount - 1; + HostAdapter->NextOutgoingMailbox = HostAdapter->FirstOutgoingMailbox; + HostAdapter->FirstIncomingMailbox = + (BusLogic_IncomingMailbox_T *) (HostAdapter->LastOutgoingMailbox + 1); + HostAdapter->LastIncomingMailbox = + HostAdapter->FirstIncomingMailbox + HostAdapter->MailboxCount - 1; + HostAdapter->NextIncomingMailbox = HostAdapter->FirstIncomingMailbox; + /* + Initialize the Outgoing and Incoming Mailbox structures. + */ + memset(HostAdapter->FirstOutgoingMailbox, 0, + HostAdapter->MailboxCount * sizeof(BusLogic_OutgoingMailbox_T)); + memset(HostAdapter->FirstIncomingMailbox, 0, + HostAdapter->MailboxCount * sizeof(BusLogic_IncomingMailbox_T)); + /* + Initialize the Host Adapter's Pointer to the Outgoing/Incoming Mailboxes. + */ + ExtendedMailboxRequest.MailboxCount = HostAdapter->MailboxCount; + ExtendedMailboxRequest.BaseMailboxAddress = + Virtual_to_Bus(HostAdapter->FirstOutgoingMailbox); + if (BusLogic_Command(HostAdapter, BusLogic_InitializeExtendedMailbox, + &ExtendedMailboxRequest, + sizeof(ExtendedMailboxRequest), NULL, 0) < 0) + return BusLogic_Failure(HostAdapter, "MAILBOX INITIALIZATION"); + /* + Enable Strict Round Robin Mode if supported by the Host Adapter. In + Strict Round Robin Mode, the Host Adapter only looks at the next Outgoing + Mailbox for each new command, rather than scanning through all the + Outgoing Mailboxes to find any that have new commands in them. Strict + Round Robin Mode is significantly more efficient. + */ + if (HostAdapter->StrictRoundRobinModeSupport) + { + RoundRobinModeRequest = BusLogic_StrictRoundRobinMode; + if (BusLogic_Command(HostAdapter, BusLogic_EnableStrictRoundRobinMode, + &RoundRobinModeRequest, + sizeof(RoundRobinModeRequest), NULL, 0) < 0) + return BusLogic_Failure(HostAdapter, "ENABLE STRICT ROUND ROBIN MODE"); + } + /* + For Host Adapters that support Extended LUN Format CCBs, issue the Set CCB + Format command to allow 32 Logical Units per Target Device. + */ + if (HostAdapter->ExtendedLUNSupport) + { + SetCCBFormatRequest = BusLogic_ExtendedLUNFormatCCB; + if (BusLogic_Command(HostAdapter, BusLogic_SetCCBFormat, + &SetCCBFormatRequest, sizeof(SetCCBFormatRequest), + NULL, 0) < 0) + return BusLogic_Failure(HostAdapter, "SET CCB FORMAT"); + } + /* + Announce Successful Initialization. + */ +Done: + if (!HostAdapter->HostAdapterInitialized) + { + BusLogic_Info("*** %s Initialized Successfully ***\n", + HostAdapter, HostAdapter->FullModelName); + BusLogic_Info("\n", HostAdapter); + } + else BusLogic_Warning("*** %s Initialized Successfully ***\n", + HostAdapter, HostAdapter->FullModelName); + HostAdapter->HostAdapterInitialized = true; + /* + Indicate the Host Adapter Initialization completed successfully. + */ + return true; +} + + +/* + BusLogic_TargetDeviceInquiry inquires about the Target Devices accessible + through Host Adapter. +*/ + +static boolean BusLogic_TargetDeviceInquiry(BusLogic_HostAdapter_T + *HostAdapter) +{ + BusLogic_InstalledDevices_T InstalledDevices; + BusLogic_InstalledDevices8_T InstalledDevicesID0to7; + BusLogic_SetupInformation_T SetupInformation; + BusLogic_SynchronousPeriod_T SynchronousPeriod; + BusLogic_RequestedReplyLength_T RequestedReplyLength; + int TargetID; + /* + Wait a few seconds between the Host Adapter Hard Reset which initiates + a SCSI Bus Reset and issuing any SCSI Commands. Some SCSI devices get + confused if they receive SCSI Commands too soon after a SCSI Bus Reset. + */ + BusLogic_Delay(HostAdapter->BusSettleTime); + /* + FlashPoint Host Adapters do not provide for Target Device Inquiry. + */ + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) return true; + /* + Inhibit the Target Device Inquiry if requested. + */ + if (HostAdapter->DriverOptions != NULL && + HostAdapter->DriverOptions->LocalOptions.InhibitTargetInquiry) + return true; + /* + Issue the Inquire Target Devices command for host adapters with firmware + version 4.25 or later, or the Inquire Installed Devices ID 0 to 7 command + for older host adapters. This is necessary to force Synchronous Transfer + Negotiation so that the Inquire Setup Information and Inquire Synchronous + Period commands will return valid data. The Inquire Target Devices command + is preferable to Inquire Installed Devices ID 0 to 7 since it only probes + Logical Unit 0 of each Target Device. + */ + if (strcmp(HostAdapter->FirmwareVersion, "4.25") >= 0) + { + if (BusLogic_Command(HostAdapter, BusLogic_InquireTargetDevices, NULL, 0, + &InstalledDevices, sizeof(InstalledDevices)) + != sizeof(InstalledDevices)) + return BusLogic_Failure(HostAdapter, "INQUIRE TARGET DEVICES"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + HostAdapter->TargetFlags[TargetID].TargetExists = + (InstalledDevices & (1 << TargetID) ? true : false); + } + else + { + if (BusLogic_Command(HostAdapter, BusLogic_InquireInstalledDevicesID0to7, + NULL, 0, &InstalledDevicesID0to7, + sizeof(InstalledDevicesID0to7)) + != sizeof(InstalledDevicesID0to7)) + return BusLogic_Failure(HostAdapter, + "INQUIRE INSTALLED DEVICES ID 0 TO 7"); + for (TargetID = 0; TargetID < 8; TargetID++) + HostAdapter->TargetFlags[TargetID].TargetExists = + (InstalledDevicesID0to7[TargetID] != 0 ? true : false); + } + /* + Issue the Inquire Setup Information command. + */ + RequestedReplyLength = sizeof(SetupInformation); + if (BusLogic_Command(HostAdapter, BusLogic_InquireSetupInformation, + &RequestedReplyLength, sizeof(RequestedReplyLength), + &SetupInformation, sizeof(SetupInformation)) + != sizeof(SetupInformation)) + return BusLogic_Failure(HostAdapter, "INQUIRE SETUP INFORMATION"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + HostAdapter->SynchronousOffset[TargetID] = + (TargetID < 8 + ? SetupInformation.SynchronousValuesID0to7[TargetID].Offset + : SetupInformation.SynchronousValuesID8to15[TargetID-8].Offset); + if (strcmp(HostAdapter->FirmwareVersion, "5.06L") >= 0) + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + HostAdapter->TargetFlags[TargetID].WideTransfersActive = + (TargetID < 8 + ? (SetupInformation.WideTransfersActiveID0to7 & (1 << TargetID) + ? true : false) + : (SetupInformation.WideTransfersActiveID8to15 & (1 << (TargetID-8)) + ? true : false)); + /* + Issue the Inquire Synchronous Period command. + */ + if (HostAdapter->FirmwareVersion[0] >= '3') + { + RequestedReplyLength = sizeof(SynchronousPeriod); + if (BusLogic_Command(HostAdapter, BusLogic_InquireSynchronousPeriod, + &RequestedReplyLength, sizeof(RequestedReplyLength), + &SynchronousPeriod, sizeof(SynchronousPeriod)) + != sizeof(SynchronousPeriod)) + return BusLogic_Failure(HostAdapter, "INQUIRE SYNCHRONOUS PERIOD"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + HostAdapter->SynchronousPeriod[TargetID] = SynchronousPeriod[TargetID]; + } + else + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + if (SetupInformation.SynchronousValuesID0to7[TargetID].Offset > 0) + HostAdapter->SynchronousPeriod[TargetID] = + 20 + 5 * SetupInformation.SynchronousValuesID0to7[TargetID] + .TransferPeriod; + /* + Indicate the Target Device Inquiry completed successfully. + */ + return true; +} + + +/* + BusLogic_ReportTargetDeviceInfo reports about the Target Devices accessible + through Host Adapter. +*/ + +static void BusLogic_ReportTargetDeviceInfo(BusLogic_HostAdapter_T + *HostAdapter) +{ + int TargetID; + /* + Inhibit the Target Device Inquiry and Reporting if requested. + */ + if (BusLogic_MultiMasterHostAdapterP(HostAdapter) && + HostAdapter->DriverOptions != NULL && + HostAdapter->DriverOptions->LocalOptions.InhibitTargetInquiry) + return; + /* + Report on the Target Devices found. + */ + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + BusLogic_TargetFlags_T *TargetFlags = &HostAdapter->TargetFlags[TargetID]; + if (TargetFlags->TargetExists && !TargetFlags->TargetInfoReported) + { + int SynchronousTransferRate = 0; + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) + { + boolean WideTransfersActive; + FlashPoint_InquireTargetInfo( + HostAdapter->CardHandle, TargetID, + &HostAdapter->SynchronousPeriod[TargetID], + &HostAdapter->SynchronousOffset[TargetID], + &WideTransfersActive); + TargetFlags->WideTransfersActive = WideTransfersActive; + } + else if (TargetFlags->WideTransfersSupported && + (HostAdapter->WidePermitted & (1 << TargetID)) && + strcmp(HostAdapter->FirmwareVersion, "5.06L") < 0) + TargetFlags->WideTransfersActive = true; + if (HostAdapter->SynchronousPeriod[TargetID] > 0) + SynchronousTransferRate = + 100000 / HostAdapter->SynchronousPeriod[TargetID]; + if (TargetFlags->WideTransfersActive) + SynchronousTransferRate <<= 1; + if (SynchronousTransferRate >= 9950) + { + SynchronousTransferRate = (SynchronousTransferRate + 50) / 100; + BusLogic_Info("Target %d: Queue Depth %d, %sSynchronous at " + "%d.%01d MB/sec, offset %d\n", + HostAdapter, TargetID, + HostAdapter->QueueDepth[TargetID], + (TargetFlags->WideTransfersActive ? "Wide " : ""), + SynchronousTransferRate / 10, + SynchronousTransferRate % 10, + HostAdapter->SynchronousOffset[TargetID]); + } + else if (SynchronousTransferRate > 0) + { + SynchronousTransferRate = (SynchronousTransferRate + 5) / 10; + BusLogic_Info("Target %d: Queue Depth %d, %sSynchronous at " + "%d.%02d MB/sec, offset %d\n", + HostAdapter, TargetID, + HostAdapter->QueueDepth[TargetID], + (TargetFlags->WideTransfersActive ? "Wide " : ""), + SynchronousTransferRate / 100, + SynchronousTransferRate % 100, + HostAdapter->SynchronousOffset[TargetID]); + } + else BusLogic_Info("Target %d: Queue Depth %d, Asynchronous\n", + HostAdapter, TargetID, + HostAdapter->QueueDepth[TargetID]); + TargetFlags->TargetInfoReported = true; + } + } +} + + +/* + BusLogic_InitializeHostStructure initializes the fields in the SCSI Host + structure. The base, io_port, n_io_ports, irq, and dma_channel fields in the + SCSI Host structure are intentionally left uninitialized, as this driver + handles acquisition and release of these resources explicitly, as well as + ensuring exclusive access to the Host Adapter hardware and data structures + through explicit acquisition and release of the Host Adapter's Lock. +*/ + +static void BusLogic_InitializeHostStructure(BusLogic_HostAdapter_T + *HostAdapter, + SCSI_Host_T *Host) +{ + Host->max_id = HostAdapter->MaxTargetDevices; + Host->max_lun = HostAdapter->MaxLogicalUnits; + Host->max_channel = 0; + Host->unique_id = HostAdapter->IO_Address; + Host->this_id = HostAdapter->SCSI_ID; + Host->can_queue = HostAdapter->DriverQueueDepth; + Host->sg_tablesize = HostAdapter->DriverScatterGatherLimit; + Host->unchecked_isa_dma = HostAdapter->BounceBuffersRequired; + Host->cmd_per_lun = HostAdapter->UntaggedQueueDepth; +} + + +/* + BusLogic_SelectQueueDepths selects Queue Depths for each Target Device based + on the Host Adapter's Total Queue Depth and the number, type, speed, and + capabilities of the Target Devices. When called for the last Host Adapter, + it reports on the Target Device Information for all BusLogic Host Adapters + since all the Target Devices have now been probed. +*/ + +static void BusLogic_SelectQueueDepths(SCSI_Host_T *Host, + SCSI_Device_T *DeviceList) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) Host->hostdata; + int TaggedDeviceCount = 0, AutomaticTaggedDeviceCount = 0; + int UntaggedDeviceCount = 0, AutomaticTaggedQueueDepth = 0; + int AllocatedQueueDepth = 0; + SCSI_Device_T *Device; + int TargetID; + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + if (HostAdapter->TargetFlags[TargetID].TargetExists) + { + int QueueDepth = HostAdapter->QueueDepth[TargetID]; + if (HostAdapter->TargetFlags[TargetID].TaggedQueuingSupported && + (HostAdapter->TaggedQueuingPermitted & (1 << TargetID))) + { + TaggedDeviceCount++; + if (QueueDepth == 0) AutomaticTaggedDeviceCount++; + } + else + { + UntaggedDeviceCount++; + if (QueueDepth == 0 || + QueueDepth > HostAdapter->UntaggedQueueDepth) + { + QueueDepth = HostAdapter->UntaggedQueueDepth; + HostAdapter->QueueDepth[TargetID] = QueueDepth; + } + } + AllocatedQueueDepth += QueueDepth; + if (QueueDepth == 1) + HostAdapter->TaggedQueuingPermitted &= ~(1 << TargetID); + } + HostAdapter->TargetDeviceCount = TaggedDeviceCount + UntaggedDeviceCount; + if (AutomaticTaggedDeviceCount > 0) + { + AutomaticTaggedQueueDepth = + (HostAdapter->HostAdapterQueueDepth - AllocatedQueueDepth) + / AutomaticTaggedDeviceCount; + if (AutomaticTaggedQueueDepth > BusLogic_MaxAutomaticTaggedQueueDepth) + AutomaticTaggedQueueDepth = BusLogic_MaxAutomaticTaggedQueueDepth; + if (AutomaticTaggedQueueDepth < BusLogic_MinAutomaticTaggedQueueDepth) + AutomaticTaggedQueueDepth = BusLogic_MinAutomaticTaggedQueueDepth; + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + if (HostAdapter->TargetFlags[TargetID].TargetExists && + HostAdapter->QueueDepth[TargetID] == 0) + { + AllocatedQueueDepth += AutomaticTaggedQueueDepth; + HostAdapter->QueueDepth[TargetID] = AutomaticTaggedQueueDepth; + } + } + for (Device = DeviceList; Device != NULL; Device = Device->next) + if (Device->host == Host) + Device->queue_depth = HostAdapter->QueueDepth[Device->id]; + /* Allocate an extra CCB for each Target Device for a Bus Device Reset. */ + AllocatedQueueDepth += HostAdapter->TargetDeviceCount; + if (AllocatedQueueDepth > HostAdapter->DriverQueueDepth) + AllocatedQueueDepth = HostAdapter->DriverQueueDepth; + BusLogic_CreateAdditionalCCBs(HostAdapter, + AllocatedQueueDepth + - HostAdapter->AllocatedCCBs, + false); + if (HostAdapter == BusLogic_LastRegisteredHostAdapter) + for (HostAdapter = BusLogic_FirstRegisteredHostAdapter; + HostAdapter != NULL; + HostAdapter = HostAdapter->Next) + BusLogic_ReportTargetDeviceInfo(HostAdapter); +} + + +/* + BusLogic_DetectHostAdapter probes for BusLogic Host Adapters at the standard + I/O Addresses where they may be located, initializing, registering, and + reporting the configuration of each BusLogic Host Adapter it finds. It + returns the number of BusLogic Host Adapters successfully initialized and + registered. +*/ + +int BusLogic_DetectHostAdapter(SCSI_Host_Template_T *HostTemplate) +{ + int BusLogicHostAdapterCount = 0, DriverOptionsIndex = 0, ProbeIndex; + BusLogic_HostAdapter_T *PrototypeHostAdapter; + if (BusLogic_ProbeOptions.NoProbe) return 0; + BusLogic_ProbeInfoList = (BusLogic_ProbeInfo_T *) + kmalloc(BusLogic_MaxHostAdapters * sizeof(BusLogic_ProbeInfo_T), + GFP_ATOMIC); + if (BusLogic_ProbeInfoList == NULL) + { + BusLogic_Error("BusLogic: Unable to allocate Probe Info List\n", NULL); + return 0; + } + memset(BusLogic_ProbeInfoList, 0, + BusLogic_MaxHostAdapters * sizeof(BusLogic_ProbeInfo_T)); + PrototypeHostAdapter = (BusLogic_HostAdapter_T *) + kmalloc(sizeof(BusLogic_HostAdapter_T), GFP_ATOMIC); + if (PrototypeHostAdapter == NULL) + { + kfree(BusLogic_ProbeInfoList); + BusLogic_Error("BusLogic: Unable to allocate Prototype " + "Host Adapter\n", NULL); + return 0; + } + memset(PrototypeHostAdapter, 0, sizeof(BusLogic_HostAdapter_T)); + if (BusLogic_Options != NULL) + BusLogic_ParseDriverOptions(BusLogic_Options); + BusLogic_InitializeProbeInfoList(PrototypeHostAdapter); + for (ProbeIndex = 0; ProbeIndex < BusLogic_ProbeInfoCount; ProbeIndex++) + { + BusLogic_ProbeInfo_T *ProbeInfo = &BusLogic_ProbeInfoList[ProbeIndex]; + BusLogic_HostAdapter_T *HostAdapter = PrototypeHostAdapter; + SCSI_Host_T *Host; + if (ProbeInfo->IO_Address == 0) continue; + memset(HostAdapter, 0, sizeof(BusLogic_HostAdapter_T)); + HostAdapter->HostAdapterType = ProbeInfo->HostAdapterType; + HostAdapter->HostAdapterBusType = ProbeInfo->HostAdapterBusType; + HostAdapter->IO_Address = ProbeInfo->IO_Address; + HostAdapter->PCI_Address = ProbeInfo->PCI_Address; + HostAdapter->Bus = ProbeInfo->Bus; + HostAdapter->Device = ProbeInfo->Device; + HostAdapter->IRQ_Channel = ProbeInfo->IRQ_Channel; + HostAdapter->AddressCount = + BusLogic_HostAdapterAddressCount[HostAdapter->HostAdapterType]; + /* + Probe the Host Adapter. If unsuccessful, abort further initialization. + */ + if (!BusLogic_ProbeHostAdapter(HostAdapter)) continue; + /* + Hard Reset the Host Adapter. If unsuccessful, abort further + initialization. + */ + if (!BusLogic_HardwareResetHostAdapter(HostAdapter, true)) continue; + /* + Check the Host Adapter. If unsuccessful, abort further initialization. + */ + if (!BusLogic_CheckHostAdapter(HostAdapter)) continue; + /* + Initialize the Driver Options field if provided. + */ + if (DriverOptionsIndex < BusLogic_DriverOptionsCount) + HostAdapter->DriverOptions = + &BusLogic_DriverOptions[DriverOptionsIndex++]; + /* + Announce the Driver Version and Date, Author's Name, Copyright Notice, + and Electronic Mail Address. + */ + BusLogic_AnnounceDriver(HostAdapter); + /* + Register usage of the I/O Address range. From this point onward, any + failure will be assumed to be due to a problem with the Host Adapter, + rather than due to having mistakenly identified this port as belonging + to a BusLogic Host Adapter. The I/O Address range will not be + released, thereby preventing it from being incorrectly identified as + any other type of Host Adapter. + */ + request_region(HostAdapter->IO_Address, HostAdapter->AddressCount, + "BusLogic"); + /* + Register the SCSI Host structure. + */ + Host = scsi_register(HostTemplate, sizeof(BusLogic_HostAdapter_T)); + HostAdapter = (BusLogic_HostAdapter_T *) Host->hostdata; + memcpy(HostAdapter, PrototypeHostAdapter, sizeof(BusLogic_HostAdapter_T)); + HostAdapter->SCSI_Host = Host; + HostAdapter->HostNumber = Host->host_no; + Host->select_queue_depths = BusLogic_SelectQueueDepths; + /* + Add Host Adapter to the end of the list of registered BusLogic + Host Adapters. + */ + BusLogic_RegisterHostAdapter(HostAdapter); + /* + Read the Host Adapter Configuration, Configure the Host Adapter, + Acquire the System Resources necessary to use the Host Adapter, then + Create the Initial CCBs, Initialize the Host Adapter, and finally + perform Target Device Inquiry. + */ + if (BusLogic_ReadHostAdapterConfiguration(HostAdapter) && + BusLogic_ReportHostAdapterConfiguration(HostAdapter) && + BusLogic_AcquireResources(HostAdapter) && + BusLogic_CreateInitialCCBs(HostAdapter) && + BusLogic_InitializeHostAdapter(HostAdapter) && + BusLogic_TargetDeviceInquiry(HostAdapter)) + { + /* + Initialization has been completed successfully. Release and + re-register usage of the I/O Address range so that the Model + Name of the Host Adapter will appear, and initialize the SCSI + Host structure. + */ + release_region(HostAdapter->IO_Address, + HostAdapter->AddressCount); + request_region(HostAdapter->IO_Address, + HostAdapter->AddressCount, + HostAdapter->FullModelName); + BusLogic_InitializeHostStructure(HostAdapter, Host); + BusLogicHostAdapterCount++; + } + else + { + /* + An error occurred during Host Adapter Configuration Querying, Host + Adapter Configuration, Resource Acquisition, CCB Creation, Host + Adapter Initialization, or Target Device Inquiry, so remove Host + Adapter from the list of registered BusLogic Host Adapters, destroy + the CCBs, Release the System Resources, and Unregister the SCSI + Host. + */ + BusLogic_DestroyCCBs(HostAdapter); + BusLogic_ReleaseResources(HostAdapter); + BusLogic_UnregisterHostAdapter(HostAdapter); + scsi_unregister(Host); + } + } + kfree(PrototypeHostAdapter); + kfree(BusLogic_ProbeInfoList); + BusLogic_ProbeInfoList = NULL; + return BusLogicHostAdapterCount; +} + + +/* + BusLogic_ReleaseHostAdapter releases all resources previously acquired to + support a specific Host Adapter, including the I/O Address range, and + unregisters the BusLogic Host Adapter. +*/ + +int BusLogic_ReleaseHostAdapter(SCSI_Host_T *Host) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) Host->hostdata; + /* + FlashPoint Host Adapters must first be released by the FlashPoint + SCCB Manager. + */ + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) + FlashPoint_ReleaseHostAdapter(HostAdapter->CardHandle); + /* + Destroy the CCBs and release any system resources acquired to + support Host Adapter. + */ + BusLogic_DestroyCCBs(HostAdapter); + BusLogic_ReleaseResources(HostAdapter); + /* + Release usage of the I/O Address range. + */ + release_region(HostAdapter->IO_Address, HostAdapter->AddressCount); + /* + Remove Host Adapter from the list of registered BusLogic Host Adapters. + */ + BusLogic_UnregisterHostAdapter(HostAdapter); + return 0; +} + + +/* + BusLogic_QueueCompletedCCB queues CCB for completion processing. +*/ + +static void BusLogic_QueueCompletedCCB(BusLogic_CCB_T *CCB) +{ + BusLogic_HostAdapter_T *HostAdapter = CCB->HostAdapter; + CCB->Status = BusLogic_CCB_Completed; + CCB->Next = NULL; + if (HostAdapter->FirstCompletedCCB == NULL) + { + HostAdapter->FirstCompletedCCB = CCB; + HostAdapter->LastCompletedCCB = CCB; + } + else + { + HostAdapter->LastCompletedCCB->Next = CCB; + HostAdapter->LastCompletedCCB = CCB; + } + HostAdapter->ActiveCommands[CCB->TargetID]--; +} + + +/* + BusLogic_ComputeResultCode computes a SCSI Subsystem Result Code from + the Host Adapter Status and Target Device Status. +*/ + +static int BusLogic_ComputeResultCode(BusLogic_HostAdapter_T *HostAdapter, + BusLogic_HostAdapterStatus_T + HostAdapterStatus, + BusLogic_TargetDeviceStatus_T + TargetDeviceStatus) +{ + int HostStatus; + switch (HostAdapterStatus) + { + case BusLogic_CommandCompletedNormally: + case BusLogic_LinkedCommandCompleted: + case BusLogic_LinkedCommandCompletedWithFlag: + HostStatus = DID_OK; + break; + case BusLogic_SCSISelectionTimeout: + HostStatus = DID_TIME_OUT; + break; + case BusLogic_InvalidOutgoingMailboxActionCode: + case BusLogic_InvalidCommandOperationCode: + case BusLogic_InvalidCommandParameter: + BusLogic_Warning("BusLogic Driver Protocol Error 0x%02X\n", + HostAdapter, HostAdapterStatus); + case BusLogic_DataUnderRun: + case BusLogic_DataOverRun: + case BusLogic_UnexpectedBusFree: + case BusLogic_LinkedCCBhasInvalidLUN: + case BusLogic_AutoRequestSenseFailed: + case BusLogic_TaggedQueuingMessageRejected: + case BusLogic_UnsupportedMessageReceived: + case BusLogic_HostAdapterHardwareFailed: + case BusLogic_TargetDeviceReconnectedImproperly: + case BusLogic_AbortQueueGenerated: + case BusLogic_HostAdapterSoftwareError: + case BusLogic_HostAdapterHardwareTimeoutError: + case BusLogic_SCSIParityErrorDetected: + HostStatus = DID_ERROR; + break; + case BusLogic_InvalidBusPhaseRequested: + case BusLogic_TargetFailedResponseToATN: + case BusLogic_HostAdapterAssertedRST: + case BusLogic_OtherDeviceAssertedRST: + case BusLogic_HostAdapterAssertedBusDeviceReset: + HostStatus = DID_RESET; + break; + default: + BusLogic_Warning("Unknown Host Adapter Status 0x%02X\n", + HostAdapter, HostAdapterStatus); + HostStatus = DID_ERROR; + break; + } + return (HostStatus << 16) | TargetDeviceStatus; +} + + +/* + BusLogic_ScanIncomingMailboxes scans the Incoming Mailboxes saving any + Incoming Mailbox entries for completion processing. +*/ + +static void BusLogic_ScanIncomingMailboxes(BusLogic_HostAdapter_T *HostAdapter) +{ + /* + Scan through the Incoming Mailboxes in Strict Round Robin fashion, saving + any completed CCBs for further processing. It is essential that for each + CCB and SCSI Command issued, command completion processing is performed + exactly once. Therefore, only Incoming Mailboxes with completion code + Command Completed Without Error, Command Completed With Error, or Command + Aborted At Host Request are saved for completion processing. When an + Incoming Mailbox has a completion code of Aborted Command Not Found, the + CCB had already completed or been aborted before the current Abort request + was processed, and so completion processing has already occurred and no + further action should be taken. + */ + BusLogic_IncomingMailbox_T *NextIncomingMailbox = + HostAdapter->NextIncomingMailbox; + BusLogic_CompletionCode_T CompletionCode; + while ((CompletionCode = NextIncomingMailbox->CompletionCode) != + BusLogic_IncomingMailboxFree) + { + BusLogic_CCB_T *CCB = (BusLogic_CCB_T *) + Bus_to_Virtual(NextIncomingMailbox->CCB); + if (CompletionCode != BusLogic_AbortedCommandNotFound) + { + if (CCB->Status == BusLogic_CCB_Active || + CCB->Status == BusLogic_CCB_Reset) + { + /* + Save the Completion Code for this CCB and queue the CCB + for completion processing. + */ + CCB->CompletionCode = CompletionCode; + BusLogic_QueueCompletedCCB(CCB); + } + else + { + /* + If a CCB ever appears in an Incoming Mailbox and is not marked + as status Active or Reset, then there is most likely a bug in + the Host Adapter firmware. + */ + BusLogic_Warning("Illegal CCB #%ld status %d in " + "Incoming Mailbox\n", HostAdapter, + CCB->SerialNumber, CCB->Status); + } + } + NextIncomingMailbox->CompletionCode = BusLogic_IncomingMailboxFree; + if (++NextIncomingMailbox > HostAdapter->LastIncomingMailbox) + NextIncomingMailbox = HostAdapter->FirstIncomingMailbox; + } + HostAdapter->NextIncomingMailbox = NextIncomingMailbox; +} + + +/* + BusLogic_ProcessCompletedCCBs iterates over the completed CCBs for Host + Adapter setting the SCSI Command Result Codes, deallocating the CCBs, and + calling the SCSI Subsystem Completion Routines. The Host Adapter's Lock + should already have been acquired by the caller. +*/ + +static void BusLogic_ProcessCompletedCCBs(BusLogic_HostAdapter_T *HostAdapter) +{ + if (HostAdapter->ProcessCompletedCCBsActive) return; + HostAdapter->ProcessCompletedCCBsActive = true; + while (HostAdapter->FirstCompletedCCB != NULL) + { + BusLogic_CCB_T *CCB = HostAdapter->FirstCompletedCCB; + SCSI_Command_T *Command = CCB->Command; + HostAdapter->FirstCompletedCCB = CCB->Next; + if (HostAdapter->FirstCompletedCCB == NULL) + HostAdapter->LastCompletedCCB = NULL; + /* + Process the Completed CCB. + */ + if (CCB->Opcode == BusLogic_BusDeviceReset) + { + int TargetID = CCB->TargetID; + BusLogic_Warning("Bus Device Reset CCB #%ld to Target " + "%d Completed\n", HostAdapter, + CCB->SerialNumber, TargetID); + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[TargetID].BusDeviceResetsCompleted); + HostAdapter->TargetFlags[TargetID].TaggedQueuingActive = false; + HostAdapter->CommandsSinceReset[TargetID] = 0; + HostAdapter->LastResetCompleted[TargetID] = jiffies; + /* + Place CCB back on the Host Adapter's free list. + */ + BusLogic_DeallocateCCB(CCB); + /* + Bus Device Reset CCBs have the Command field non-NULL only when a + Bus Device Reset was requested for a Command that did not have a + currently active CCB in the Host Adapter (i.e., a Synchronous + Bus Device Reset), and hence would not have its Completion Routine + called otherwise. + */ + while (Command != NULL) + { + SCSI_Command_T *NextCommand = Command->reset_chain; + Command->reset_chain = NULL; + Command->result = DID_RESET << 16; + Command->scsi_done(Command); + Command = NextCommand; + } + /* + Iterate over the CCBs for this Host Adapter performing completion + processing for any CCBs marked as Reset for this Target. + */ + for (CCB = HostAdapter->All_CCBs; CCB != NULL; CCB = CCB->NextAll) + if (CCB->Status == BusLogic_CCB_Reset && CCB->TargetID == TargetID) + { + Command = CCB->Command; + BusLogic_DeallocateCCB(CCB); + HostAdapter->ActiveCommands[TargetID]--; + Command->result = DID_RESET << 16; + Command->scsi_done(Command); + } + HostAdapter->BusDeviceResetPendingCCB[TargetID] = NULL; + } + else + { + /* + Translate the Completion Code, Host Adapter Status, and Target + Device Status into a SCSI Subsystem Result Code. + */ + switch (CCB->CompletionCode) + { + case BusLogic_IncomingMailboxFree: + case BusLogic_AbortedCommandNotFound: + case BusLogic_InvalidCCB: + BusLogic_Warning("CCB #%ld to Target %d Impossible State\n", + HostAdapter, CCB->SerialNumber, CCB->TargetID); + break; + case BusLogic_CommandCompletedWithoutError: + HostAdapter->TargetStatistics[CCB->TargetID] + .CommandsCompleted++; + HostAdapter->TargetFlags[CCB->TargetID] + .CommandSuccessfulFlag = true; + Command->result = DID_OK << 16; + break; + case BusLogic_CommandAbortedAtHostRequest: + BusLogic_Warning("CCB #%ld to Target %d Aborted\n", + HostAdapter, CCB->SerialNumber, CCB->TargetID); + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[CCB->TargetID] + .CommandAbortsCompleted); + Command->result = DID_ABORT << 16; + break; + case BusLogic_CommandCompletedWithError: + Command->result = + BusLogic_ComputeResultCode(HostAdapter, + CCB->HostAdapterStatus, + CCB->TargetDeviceStatus); + if (CCB->HostAdapterStatus != BusLogic_SCSISelectionTimeout) + { + HostAdapter->TargetStatistics[CCB->TargetID] + .CommandsCompleted++; + if (BusLogic_GlobalOptions.TraceErrors) + { + int i; + BusLogic_Notice("CCB #%ld Target %d: Result %X Host " + "Adapter Status %02X " + "Target Status %02X\n", + HostAdapter, CCB->SerialNumber, + CCB->TargetID, Command->result, + CCB->HostAdapterStatus, + CCB->TargetDeviceStatus); + BusLogic_Notice("CDB ", HostAdapter); + for (i = 0; i < CCB->CDB_Length; i++) + BusLogic_Notice(" %02X", HostAdapter, CCB->CDB[i]); + BusLogic_Notice("\n", HostAdapter); + BusLogic_Notice("Sense ", HostAdapter); + for (i = 0; i < CCB->SenseDataLength; i++) + BusLogic_Notice(" %02X", HostAdapter, + Command->sense_buffer[i]); + BusLogic_Notice("\n", HostAdapter); + } + } + break; + } + /* + When an INQUIRY command completes normally, save the + CmdQue (Tagged Queuing Supported) and WBus16 (16 Bit + Wide Data Transfers Supported) bits. + */ + if (CCB->CDB[0] == INQUIRY && CCB->CDB[1] == 0 && + CCB->HostAdapterStatus == BusLogic_CommandCompletedNormally) + { + BusLogic_TargetFlags_T *TargetFlags = + &HostAdapter->TargetFlags[CCB->TargetID]; + SCSI_Inquiry_T *InquiryResult = + (SCSI_Inquiry_T *) Command->request_buffer; + TargetFlags->TargetExists = true; + TargetFlags->TaggedQueuingSupported = InquiryResult->CmdQue; + TargetFlags->WideTransfersSupported = InquiryResult->WBus16; + } + /* + Place CCB back on the Host Adapter's free list. + */ + BusLogic_DeallocateCCB(CCB); + /* + Call the SCSI Command Completion Routine. + */ + Command->scsi_done(Command); + } + } + HostAdapter->ProcessCompletedCCBsActive = false; +} + + +/* + BusLogic_InterruptHandler handles hardware interrupts from BusLogic Host + Adapters. +*/ + +static void BusLogic_InterruptHandler(int IRQ_Channel, + void *DeviceIdentifier, + Registers_T *InterruptRegisters) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) DeviceIdentifier; + ProcessorFlags_T ProcessorFlags; + /* + Acquire exclusive access to Host Adapter. + */ + BusLogic_AcquireHostAdapterLockIH(HostAdapter, &ProcessorFlags); + /* + Handle Interrupts appropriately for each Host Adapter type. + */ + if (BusLogic_MultiMasterHostAdapterP(HostAdapter)) + { + BusLogic_InterruptRegister_T InterruptRegister; + /* + Read the Host Adapter Interrupt Register. + */ + InterruptRegister.All = BusLogic_ReadInterruptRegister(HostAdapter); + if (InterruptRegister.Bits.InterruptValid) + { + /* + Acknowledge the interrupt and reset the Host Adapter + Interrupt Register. + */ + BusLogic_InterruptReset(HostAdapter); + /* + Process valid External SCSI Bus Reset and Incoming Mailbox + Loaded Interrupts. Command Complete Interrupts are noted, + and Outgoing Mailbox Available Interrupts are ignored, as + they are never enabled. + */ + if (InterruptRegister.Bits.ExternalBusReset) + HostAdapter->HostAdapterExternalReset = true; + else if (InterruptRegister.Bits.IncomingMailboxLoaded) + BusLogic_ScanIncomingMailboxes(HostAdapter); + else if (InterruptRegister.Bits.CommandComplete) + HostAdapter->HostAdapterCommandCompleted = true; + } + } + else + { + /* + Check if there is a pending interrupt for this Host Adapter. + */ + if (FlashPoint_InterruptPending(HostAdapter->CardHandle)) + switch (FlashPoint_HandleInterrupt(HostAdapter->CardHandle)) + { + case FlashPoint_NormalInterrupt: + break; + case FlashPoint_ExternalBusReset: + HostAdapter->HostAdapterExternalReset = true; + break; + case FlashPoint_InternalError: + BusLogic_Warning("Internal FlashPoint Error detected" + " - Resetting Host Adapter\n", HostAdapter); + HostAdapter->HostAdapterInternalError = true; + break; + } + } + /* + Process any completed CCBs. + */ + if (HostAdapter->FirstCompletedCCB != NULL) + BusLogic_ProcessCompletedCCBs(HostAdapter); + /* + Reset the Host Adapter if requested. + */ + if (HostAdapter->HostAdapterExternalReset || + HostAdapter->HostAdapterInternalError) + { + BusLogic_ResetHostAdapter(HostAdapter, NULL, 0); + HostAdapter->HostAdapterExternalReset = false; + HostAdapter->HostAdapterInternalError = false; + scsi_mark_host_reset(HostAdapter->SCSI_Host); + } + /* + Release exclusive access to Host Adapter. + */ + BusLogic_ReleaseHostAdapterLockIH(HostAdapter, &ProcessorFlags); +} + + +/* + BusLogic_WriteOutgoingMailbox places CCB and Action Code into an Outgoing + Mailbox for execution by Host Adapter. The Host Adapter's Lock should + already have been acquired by the caller. +*/ + +static boolean BusLogic_WriteOutgoingMailbox(BusLogic_HostAdapter_T + *HostAdapter, + BusLogic_ActionCode_T ActionCode, + BusLogic_CCB_T *CCB) +{ + BusLogic_OutgoingMailbox_T *NextOutgoingMailbox; + NextOutgoingMailbox = HostAdapter->NextOutgoingMailbox; + if (NextOutgoingMailbox->ActionCode == BusLogic_OutgoingMailboxFree) + { + CCB->Status = BusLogic_CCB_Active; + /* + The CCB field must be written before the Action Code field since + the Host Adapter is operating asynchronously and the locking code + does not protect against simultaneous access by the Host Adapter. + */ + NextOutgoingMailbox->CCB = Virtual_to_Bus(CCB); + NextOutgoingMailbox->ActionCode = ActionCode; + BusLogic_StartMailboxCommand(HostAdapter); + if (++NextOutgoingMailbox > HostAdapter->LastOutgoingMailbox) + NextOutgoingMailbox = HostAdapter->FirstOutgoingMailbox; + HostAdapter->NextOutgoingMailbox = NextOutgoingMailbox; + if (ActionCode == BusLogic_MailboxStartCommand) + { + HostAdapter->ActiveCommands[CCB->TargetID]++; + if (CCB->Opcode != BusLogic_BusDeviceReset) + HostAdapter->TargetStatistics[CCB->TargetID].CommandsAttempted++; + } + return true; + } + return false; +} + + +/* + BusLogic_QueueCommand creates a CCB for Command and places it into an + Outgoing Mailbox for execution by the associated Host Adapter. +*/ + +int BusLogic_QueueCommand(SCSI_Command_T *Command, + void (*CompletionRoutine)(SCSI_Command_T *)) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) Command->host->hostdata; + BusLogic_TargetFlags_T *TargetFlags = + &HostAdapter->TargetFlags[Command->target]; + BusLogic_TargetStatistics_T *TargetStatistics = + HostAdapter->TargetStatistics; + unsigned char *CDB = Command->cmnd; + int CDB_Length = Command->cmd_len; + int TargetID = Command->target; + int LogicalUnit = Command->lun; + void *BufferPointer = Command->request_buffer; + int BufferLength = Command->request_bufflen; + int SegmentCount = Command->use_sg; + ProcessorFlags_T ProcessorFlags; + BusLogic_CCB_T *CCB; + /* + SCSI REQUEST_SENSE commands will be executed automatically by the Host + Adapter for any errors, so they should not be executed explicitly unless + the Sense Data is zero indicating that no error occurred. + */ + if (CDB[0] == REQUEST_SENSE && Command->sense_buffer[0] != 0) + { + Command->result = DID_OK << 16; + CompletionRoutine(Command); + return 0; + } + /* + Acquire exclusive access to Host Adapter. + */ + BusLogic_AcquireHostAdapterLock(HostAdapter, &ProcessorFlags); + /* + Allocate a CCB from the Host Adapter's free list. In the unlikely event + that there are none available and memory allocation fails, wait 1 second + and try again. If that fails, the Host Adapter is probably hung so signal + an error as a Host Adapter Hard Reset should be initiated soon. + */ + CCB = BusLogic_AllocateCCB(HostAdapter); + if (CCB == NULL) + { + BusLogic_Delay(1); + CCB = BusLogic_AllocateCCB(HostAdapter); + if (CCB == NULL) + { + Command->result = DID_ERROR << 16; + CompletionRoutine(Command); + goto Done; + } + } + /* + Initialize the fields in the BusLogic Command Control Block (CCB). + */ + if (SegmentCount == 0) + { + CCB->Opcode = BusLogic_InitiatorCCB; + CCB->DataLength = BufferLength; + CCB->DataPointer = Virtual_to_Bus(BufferPointer); + } + else + { + SCSI_ScatterList_T *ScatterList = (SCSI_ScatterList_T *) BufferPointer; + int Segment; + CCB->Opcode = BusLogic_InitiatorCCB_ScatterGather; + CCB->DataLength = SegmentCount * sizeof(BusLogic_ScatterGatherSegment_T); + if (BusLogic_MultiMasterHostAdapterP(HostAdapter)) + CCB->DataPointer = Virtual_to_Bus(CCB->ScatterGatherList); + else CCB->DataPointer = Virtual_to_32Bit_Virtual(CCB->ScatterGatherList); + for (Segment = 0; Segment < SegmentCount; Segment++) + { + CCB->ScatterGatherList[Segment].SegmentByteCount = + ScatterList[Segment].length; + CCB->ScatterGatherList[Segment].SegmentDataPointer = + Virtual_to_Bus(ScatterList[Segment].address); + } + } + switch (CDB[0]) + { + case READ_6: + case READ_10: + CCB->DataDirection = BusLogic_DataInLengthChecked; + TargetStatistics[TargetID].ReadCommands++; + BusLogic_IncrementByteCounter( + &TargetStatistics[TargetID].TotalBytesRead, BufferLength); + BusLogic_IncrementSizeBucket( + TargetStatistics[TargetID].ReadCommandSizeBuckets, BufferLength); + break; + case WRITE_6: + case WRITE_10: + CCB->DataDirection = BusLogic_DataOutLengthChecked; + TargetStatistics[TargetID].WriteCommands++; + BusLogic_IncrementByteCounter( + &TargetStatistics[TargetID].TotalBytesWritten, BufferLength); + BusLogic_IncrementSizeBucket( + TargetStatistics[TargetID].WriteCommandSizeBuckets, BufferLength); + break; + default: + CCB->DataDirection = BusLogic_UncheckedDataTransfer; + break; + } + CCB->CDB_Length = CDB_Length; + CCB->SenseDataLength = sizeof(Command->sense_buffer); + CCB->HostAdapterStatus = 0; + CCB->TargetDeviceStatus = 0; + CCB->TargetID = TargetID; + CCB->LogicalUnit = LogicalUnit; + CCB->TagEnable = false; + CCB->LegacyTagEnable = false; + /* + BusLogic recommends that after a Reset the first couple of commands that + are sent to a Target Device be sent in a non Tagged Queue fashion so that + the Host Adapter and Target Device can establish Synchronous and Wide + Transfer before Queue Tag messages can interfere with the Synchronous and + Wide Negotiation messages. By waiting to enable Tagged Queuing until after + the first BusLogic_MaxTaggedQueueDepth commands have been queued, it is + assured that after a Reset any pending commands are requeued before Tagged + Queuing is enabled and that the Tagged Queuing message will not occur while + the partition table is being printed. In addition, some devices do not + properly handle the transition from non-tagged to tagged commands, so it is + necessary to wait until there are no pending commands for a target device + before queuing tagged commands. + */ + if (HostAdapter->CommandsSinceReset[TargetID]++ >= + BusLogic_MaxTaggedQueueDepth && + !TargetFlags->TaggedQueuingActive && + HostAdapter->ActiveCommands[TargetID] == 0 && + TargetFlags->TaggedQueuingSupported && + (HostAdapter->TaggedQueuingPermitted & (1 << TargetID))) + { + TargetFlags->TaggedQueuingActive = true; + BusLogic_Notice("Tagged Queuing now active for Target %d\n", + HostAdapter, TargetID); + } + if (TargetFlags->TaggedQueuingActive) + { + BusLogic_QueueTag_T QueueTag = BusLogic_SimpleQueueTag; + /* + When using Tagged Queuing with Simple Queue Tags, it appears that disk + drive controllers do not guarantee that a queued command will not + remain in a disconnected state indefinitely if commands that read or + write nearer the head position continue to arrive without interruption. + Therefore, for each Target Device this driver keeps track of the last + time either the queue was empty or an Ordered Queue Tag was issued. If + more than 4 seconds (one fifth of the 20 second disk timeout) have + elapsed since this last sequence point, this command will be issued + with an Ordered Queue Tag rather than a Simple Queue Tag, which forces + the Target Device to complete all previously queued commands before + this command may be executed. + */ + if (HostAdapter->ActiveCommands[TargetID] == 0) + HostAdapter->LastSequencePoint[TargetID] = jiffies; + else if (jiffies - HostAdapter->LastSequencePoint[TargetID] > 4*HZ) + { + HostAdapter->LastSequencePoint[TargetID] = jiffies; + QueueTag = BusLogic_OrderedQueueTag; + } + if (HostAdapter->ExtendedLUNSupport) + { + CCB->TagEnable = true; + CCB->QueueTag = QueueTag; + } + else + { + CCB->LegacyTagEnable = true; + CCB->LegacyQueueTag = QueueTag; + } + } + memcpy(CCB->CDB, CDB, CDB_Length); + CCB->SenseDataPointer = Virtual_to_Bus(&Command->sense_buffer); + CCB->Command = Command; + Command->scsi_done = CompletionRoutine; + if (BusLogic_MultiMasterHostAdapterP(HostAdapter)) + { + /* + Place the CCB in an Outgoing Mailbox. The higher levels of the SCSI + Subsystem should not attempt to queue more commands than can be placed + in Outgoing Mailboxes, so there should always be one free. In the + unlikely event that there are none available, wait 1 second and try + again. If that fails, the Host Adapter is probably hung so signal an + error as a Host Adapter Hard Reset should be initiated soon. + */ + if (!BusLogic_WriteOutgoingMailbox( + HostAdapter, BusLogic_MailboxStartCommand, CCB)) + { + BusLogic_Warning("Unable to write Outgoing Mailbox - " + "Pausing for 1 second\n", HostAdapter); + BusLogic_Delay(1); + if (!BusLogic_WriteOutgoingMailbox( + HostAdapter, BusLogic_MailboxStartCommand, CCB)) + { + BusLogic_Warning("Still unable to write Outgoing Mailbox - " + "Host Adapter Dead?\n", HostAdapter); + BusLogic_DeallocateCCB(CCB); + Command->result = DID_ERROR << 16; + Command->scsi_done(Command); + } + } + } + else + { + /* + Call the FlashPoint SCCB Manager to start execution of the CCB. + */ + CCB->Status = BusLogic_CCB_Active; + HostAdapter->ActiveCommands[TargetID]++; + TargetStatistics[TargetID].CommandsAttempted++; + FlashPoint_StartCCB(HostAdapter->CardHandle, CCB); + /* + The Command may have already completed and BusLogic_QueueCompletedCCB + been called, or it may still be pending. + */ + if (CCB->Status == BusLogic_CCB_Completed) + BusLogic_ProcessCompletedCCBs(HostAdapter); + } + /* + Release exclusive access to Host Adapter. + */ +Done: + BusLogic_ReleaseHostAdapterLock(HostAdapter, &ProcessorFlags); + return 0; +} + + +/* + BusLogic_AbortCommand aborts Command if possible. +*/ + +int BusLogic_AbortCommand(SCSI_Command_T *Command) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) Command->host->hostdata; + int TargetID = Command->target; + ProcessorFlags_T ProcessorFlags; + BusLogic_CCB_T *CCB; + int Result; + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[TargetID].CommandAbortsRequested); + /* + Acquire exclusive access to Host Adapter. + */ + BusLogic_AcquireHostAdapterLock(HostAdapter, &ProcessorFlags); + /* + If this Command has already completed, then no Abort is necessary. + */ + if (Command->serial_number != Command->serial_number_at_timeout) + { + BusLogic_Warning("Unable to Abort Command to Target %d - " + "Already Completed\n", HostAdapter, TargetID); + Result = SCSI_ABORT_NOT_RUNNING; + goto Done; + } + /* + Attempt to find an Active CCB for this Command. If no Active CCB for this + Command is found, then no Abort is necessary. + */ + for (CCB = HostAdapter->All_CCBs; CCB != NULL; CCB = CCB->NextAll) + if (CCB->Command == Command) break; + if (CCB == NULL) + { + BusLogic_Warning("Unable to Abort Command to Target %d - " + "No CCB Found\n", HostAdapter, TargetID); + Result = SCSI_ABORT_NOT_RUNNING; + goto Done; + } + else if (CCB->Status == BusLogic_CCB_Completed) + { + BusLogic_Warning("Unable to Abort Command to Target %d - " + "CCB Completed\n", HostAdapter, TargetID); + Result = SCSI_ABORT_NOT_RUNNING; + goto Done; + } + else if (CCB->Status == BusLogic_CCB_Reset) + { + BusLogic_Warning("Unable to Abort Command to Target %d - " + "CCB Reset\n", HostAdapter, TargetID); + Result = SCSI_ABORT_PENDING; + goto Done; + } + if (BusLogic_MultiMasterHostAdapterP(HostAdapter)) + { + /* + Attempt to Abort this CCB. MultiMaster Firmware versions prior to 5.xx + do not generate Abort Tag messages, but only generate the non-tagged + Abort message. Since non-tagged commands are not sent by the Host + Adapter until the queue of outstanding tagged commands has completed, + and the Abort message is treated as a non-tagged command, it is + effectively impossible to abort commands when Tagged Queuing is active. + Firmware version 5.xx does generate Abort Tag messages, so it is + possible to abort commands when Tagged Queuing is active. + */ + if (HostAdapter->TargetFlags[TargetID].TaggedQueuingActive && + HostAdapter->FirmwareVersion[0] < '5') + { + BusLogic_Warning("Unable to Abort CCB #%ld to Target %d - " + "Abort Tag Not Supported\n", + HostAdapter, CCB->SerialNumber, TargetID); + Result = SCSI_ABORT_SNOOZE; + } + else if (BusLogic_WriteOutgoingMailbox( + HostAdapter, BusLogic_MailboxAbortCommand, CCB)) + { + BusLogic_Warning("Aborting CCB #%ld to Target %d\n", + HostAdapter, CCB->SerialNumber, TargetID); + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[TargetID].CommandAbortsAttempted); + Result = SCSI_ABORT_PENDING; + } + else + { + BusLogic_Warning("Unable to Abort CCB #%ld to Target %d - " + "No Outgoing Mailboxes\n", + HostAdapter, CCB->SerialNumber, TargetID); + Result = SCSI_ABORT_BUSY; + } + } + else + { + /* + Call the FlashPoint SCCB Manager to abort execution of the CCB. + */ + BusLogic_Warning("Aborting CCB #%ld to Target %d\n", + HostAdapter, CCB->SerialNumber, TargetID); + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[TargetID].CommandAbortsAttempted); + FlashPoint_AbortCCB(HostAdapter->CardHandle, CCB); + /* + The Abort may have already been completed and + BusLogic_QueueCompletedCCB been called, or it + may still be pending. + */ + Result = SCSI_ABORT_PENDING; + if (CCB->Status == BusLogic_CCB_Completed) + { + BusLogic_ProcessCompletedCCBs(HostAdapter); + Result = SCSI_ABORT_SUCCESS; + } + } + /* + Release exclusive access to Host Adapter. + */ +Done: + BusLogic_ReleaseHostAdapterLock(HostAdapter, &ProcessorFlags); + return Result; +} + + +/* + BusLogic_ResetHostAdapter resets Host Adapter if possible, marking all + currently executing SCSI Commands as having been Reset. +*/ + +static int BusLogic_ResetHostAdapter(BusLogic_HostAdapter_T *HostAdapter, + SCSI_Command_T *Command, + unsigned int ResetFlags) +{ + ProcessorFlags_T ProcessorFlags; + BusLogic_CCB_T *CCB; + int TargetID, Result; + boolean HardReset; + if (HostAdapter->HostAdapterExternalReset) + { + BusLogic_IncrementErrorCounter(&HostAdapter->ExternalHostAdapterResets); + HardReset = false; + } + else if (HostAdapter->HostAdapterInternalError) + { + BusLogic_IncrementErrorCounter(&HostAdapter->HostAdapterInternalErrors); + HardReset = true; + } + else + { + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[Command->target] + .HostAdapterResetsRequested); + HardReset = true; + } + /* + Acquire exclusive access to Host Adapter. + */ + BusLogic_AcquireHostAdapterLock(HostAdapter, &ProcessorFlags); + /* + If this is an Asynchronous Reset and this Command has already completed, + then no Reset is necessary. + */ + if (ResetFlags & SCSI_RESET_ASYNCHRONOUS) + { + TargetID = Command->target; + if (Command->serial_number != Command->serial_number_at_timeout) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "Already Completed or Reset\n", + HostAdapter, TargetID); + Result = SCSI_RESET_NOT_RUNNING; + goto Done; + } + for (CCB = HostAdapter->All_CCBs; CCB != NULL; CCB = CCB->NextAll) + if (CCB->Command == Command) break; + if (CCB == NULL) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "No CCB Found\n", HostAdapter, TargetID); + Result = SCSI_RESET_NOT_RUNNING; + goto Done; + } + else if (CCB->Status == BusLogic_CCB_Completed) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "CCB Completed\n", HostAdapter, TargetID); + Result = SCSI_RESET_NOT_RUNNING; + goto Done; + } + else if (CCB->Status == BusLogic_CCB_Reset && + HostAdapter->BusDeviceResetPendingCCB[TargetID] == NULL) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "Reset Pending\n", HostAdapter, TargetID); + Result = SCSI_RESET_PENDING; + goto Done; + } + } + if (Command == NULL) + { + if (HostAdapter->HostAdapterInternalError) + BusLogic_Warning("Resetting %s due to Host Adapter Internal Error\n", + HostAdapter, HostAdapter->FullModelName); + else BusLogic_Warning("Resetting %s due to External SCSI Bus Reset\n", + HostAdapter, HostAdapter->FullModelName); + } + else + { + BusLogic_Warning("Resetting %s due to Target %d\n", HostAdapter, + HostAdapter->FullModelName, Command->target); + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[Command->target] + .HostAdapterResetsAttempted); + } + /* + Attempt to Reset and Reinitialize the Host Adapter. + */ + if (!(BusLogic_HardwareResetHostAdapter(HostAdapter, HardReset) && + BusLogic_InitializeHostAdapter(HostAdapter))) + { + BusLogic_Error("Resetting %s Failed\n", HostAdapter, + HostAdapter->FullModelName); + Result = SCSI_RESET_ERROR; + goto Done; + } + if (Command != NULL) + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[Command->target] + .HostAdapterResetsCompleted); + /* + Mark all currently executing CCBs as having been Reset. + */ + for (CCB = HostAdapter->All_CCBs; CCB != NULL; CCB = CCB->NextAll) + if (CCB->Status == BusLogic_CCB_Active) + CCB->Status = BusLogic_CCB_Reset; + /* + Wait a few seconds between the Host Adapter Hard Reset which initiates + a SCSI Bus Reset and issuing any SCSI Commands. Some SCSI devices get + confused if they receive SCSI Commands too soon after a SCSI Bus Reset. + Note that a timer interrupt may occur here, but all active CCBs have + already been marked Reset and so a reentrant call will return Pending. + */ + if (HardReset) + BusLogic_Delay(HostAdapter->BusSettleTime); + /* + If this is a Synchronous Reset, perform completion processing for + the Command being Reset. + */ + if (ResetFlags & SCSI_RESET_SYNCHRONOUS) + { + Command->result = DID_RESET << 16; + Command->scsi_done(Command); + } + /* + Perform completion processing for all CCBs marked as Reset. + */ + for (CCB = HostAdapter->All_CCBs; CCB != NULL; CCB = CCB->NextAll) + if (CCB->Status == BusLogic_CCB_Reset) + { + Command = CCB->Command; + BusLogic_DeallocateCCB(CCB); + while (Command != NULL) + { + SCSI_Command_T *NextCommand = Command->reset_chain; + Command->reset_chain = NULL; + Command->result = DID_RESET << 16; + Command->scsi_done(Command); + Command = NextCommand; + } + } + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + HostAdapter->LastResetAttempted[TargetID] = jiffies; + HostAdapter->LastResetCompleted[TargetID] = jiffies; + } + Result = SCSI_RESET_SUCCESS | SCSI_RESET_HOST_RESET; + /* + Release exclusive access to Host Adapter. + */ +Done: + BusLogic_ReleaseHostAdapterLock(HostAdapter, &ProcessorFlags); + return Result; +} + + +/* + BusLogic_SendBusDeviceReset sends a Bus Device Reset to the Target + Device associated with Command. +*/ + +static int BusLogic_SendBusDeviceReset(BusLogic_HostAdapter_T *HostAdapter, + SCSI_Command_T *Command, + unsigned int ResetFlags) +{ + int TargetID = Command->target; + BusLogic_CCB_T *CCB, *XCCB; + ProcessorFlags_T ProcessorFlags; + int Result = -1; + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[TargetID].BusDeviceResetsRequested); + /* + Acquire exclusive access to Host Adapter. + */ + BusLogic_AcquireHostAdapterLock(HostAdapter, &ProcessorFlags); + /* + If this is an Asynchronous Reset and this Command has already completed, + then no Reset is necessary. + */ + if (ResetFlags & SCSI_RESET_ASYNCHRONOUS) + { + if (Command->serial_number != Command->serial_number_at_timeout) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "Already Completed\n", HostAdapter, TargetID); + Result = SCSI_RESET_NOT_RUNNING; + goto Done; + } + for (CCB = HostAdapter->All_CCBs; CCB != NULL; CCB = CCB->NextAll) + if (CCB->Command == Command) break; + if (CCB == NULL) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "No CCB Found\n", HostAdapter, TargetID); + Result = SCSI_RESET_NOT_RUNNING; + goto Done; + } + else if (CCB->Status == BusLogic_CCB_Completed) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "CCB Completed\n", HostAdapter, TargetID); + Result = SCSI_RESET_NOT_RUNNING; + goto Done; + } + else if (CCB->Status == BusLogic_CCB_Reset) + { + BusLogic_Warning("Unable to Reset Command to Target %d - " + "Reset Pending\n", HostAdapter, TargetID); + Result = SCSI_RESET_PENDING; + goto Done; + } + else if (HostAdapter->BusDeviceResetPendingCCB[TargetID] != NULL) + { + BusLogic_Warning("Bus Device Reset already pending to Target %d\n", + HostAdapter, TargetID); + goto Done; + } + } + /* + If this is a Synchronous Reset and a Bus Device Reset is already pending + for this Target Device, do not send a second one. Add this Command to + the list of Commands for which completion processing must be performed + when the Bus Device Reset CCB completes. + */ + if (ResetFlags & SCSI_RESET_SYNCHRONOUS) + if ((CCB = HostAdapter->BusDeviceResetPendingCCB[TargetID]) != NULL) + { + Command->reset_chain = CCB->Command; + CCB->Command = Command; + BusLogic_Warning("Unable to Reset Command to Target %d - " + "Reset Pending\n", HostAdapter, TargetID); + Result = SCSI_RESET_PENDING; + goto Done; + } + if (BusLogic_MultiMasterHostAdapterP(HostAdapter)) + { + /* + MultiMaster Firmware versions prior to 5.xx treat a Bus Device Reset as + a non-tagged command. Since non-tagged commands are not sent by the + Host Adapter until the queue of outstanding tagged commands has + completed, it is effectively impossible to send a Bus Device Reset + while there are tagged commands outstanding. Therefore, in that case a + full Host Adapter Hard Reset and SCSI Bus Reset must be done. + */ + if (HostAdapter->TargetFlags[TargetID].TaggedQueuingActive && + HostAdapter->ActiveCommands[TargetID] > 0 && + HostAdapter->FirmwareVersion[0] < '5') + goto Done; + } + /* + Allocate a CCB from the Host Adapter's free list. In the unlikely event + that there are none available and memory allocation fails, attempt a full + Host Adapter Hard Reset and SCSI Bus Reset. + */ + CCB = BusLogic_AllocateCCB(HostAdapter); + if (CCB == NULL) goto Done; + BusLogic_Warning("Sending Bus Device Reset CCB #%ld to Target %d\n", + HostAdapter, CCB->SerialNumber, TargetID); + CCB->Opcode = BusLogic_BusDeviceReset; + CCB->TargetID = TargetID; + /* + For Synchronous Resets, arrange for the interrupt handler to perform + completion processing for the Command being Reset. + */ + if (ResetFlags & SCSI_RESET_SYNCHRONOUS) + { + Command->reset_chain = NULL; + CCB->Command = Command; + } + if (BusLogic_MultiMasterHostAdapterP(HostAdapter)) + { + /* + Attempt to write an Outgoing Mailbox with the Bus Device Reset CCB. + If sending a Bus Device Reset is impossible, attempt a full Host + Adapter Hard Reset and SCSI Bus Reset. + */ + if (!(BusLogic_WriteOutgoingMailbox( + HostAdapter, BusLogic_MailboxStartCommand, CCB))) + { + BusLogic_Warning("Unable to write Outgoing Mailbox for " + "Bus Device Reset\n", HostAdapter); + BusLogic_DeallocateCCB(CCB); + goto Done; + } + } + else + { + /* + Call the FlashPoint SCCB Manager to start execution of the CCB. + */ + CCB->Status = BusLogic_CCB_Active; + HostAdapter->ActiveCommands[TargetID]++; + FlashPoint_StartCCB(HostAdapter->CardHandle, CCB); + } + /* + If there is a currently executing CCB in the Host Adapter for this Command + (i.e. this is an Asynchronous Reset), then an Incoming Mailbox entry may be + made with a completion code of BusLogic_HostAdapterAssertedBusDeviceReset. + If there is no active CCB for this Command (i.e. this is a Synchronous + Reset), then the Bus Device Reset CCB's Command field will have been set + to the Command so that the interrupt for the completion of the Bus Device + Reset can call the Completion Routine for the Command. On successful + execution of a Bus Device Reset, older firmware versions did return the + pending CCBs with the appropriate completion code, but more recent firmware + versions only return the Bus Device Reset CCB itself. This driver handles + both cases by marking all the currently executing CCBs to this Target + Device as Reset. When the Bus Device Reset CCB is processed by the + interrupt handler, any remaining CCBs marked as Reset will have completion + processing performed. + */ + BusLogic_IncrementErrorCounter( + &HostAdapter->TargetStatistics[TargetID].BusDeviceResetsAttempted); + HostAdapter->BusDeviceResetPendingCCB[TargetID] = CCB; + HostAdapter->LastResetAttempted[TargetID] = jiffies; + for (XCCB = HostAdapter->All_CCBs; XCCB != NULL; XCCB = XCCB->NextAll) + if (XCCB->Status == BusLogic_CCB_Active && XCCB->TargetID == TargetID) + XCCB->Status = BusLogic_CCB_Reset; + /* + FlashPoint Host Adapters may have already completed the Bus Device + Reset and BusLogic_QueueCompletedCCB been called, or it may still be + pending. + */ + Result = SCSI_RESET_PENDING; + if (BusLogic_FlashPointHostAdapterP(HostAdapter)) + if (CCB->Status == BusLogic_CCB_Completed) + { + BusLogic_ProcessCompletedCCBs(HostAdapter); + Result = SCSI_RESET_SUCCESS; + } + /* + If a Bus Device Reset was not possible for some reason, force a full + Host Adapter Hard Reset and SCSI Bus Reset. + */ +Done: + if (Result < 0) + Result = BusLogic_ResetHostAdapter(HostAdapter, Command, ResetFlags); + /* + Release exclusive access to Host Adapter. + */ + BusLogic_ReleaseHostAdapterLock(HostAdapter, &ProcessorFlags); + return Result; +} + + +/* + BusLogic_ResetCommand takes appropriate action to reset Command. +*/ + +int BusLogic_ResetCommand(SCSI_Command_T *Command, unsigned int ResetFlags) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) Command->host->hostdata; + int TargetID = Command->target; + BusLogic_ErrorRecoveryStrategy_T + ErrorRecoveryStrategy = HostAdapter->ErrorRecoveryStrategy[TargetID]; + /* + Disable Tagged Queuing if it is active for this Target Device and if + it has been less than 10 minutes since the last reset occurred, or since + the system was initialized if no prior resets have occurred. + */ + if (HostAdapter->TargetFlags[TargetID].TaggedQueuingActive && + jiffies - HostAdapter->LastResetCompleted[TargetID] < 10*60*HZ) + { + HostAdapter->TaggedQueuingPermitted &= ~(1 << TargetID); + HostAdapter->TargetFlags[TargetID].TaggedQueuingActive = false; + BusLogic_Warning("Tagged Queuing now disabled for Target %d\n", + HostAdapter, TargetID); + } + switch (ErrorRecoveryStrategy) + { + case BusLogic_ErrorRecovery_Default: + if (ResetFlags & SCSI_RESET_SUGGEST_HOST_RESET) + return BusLogic_ResetHostAdapter(HostAdapter, Command, ResetFlags); + else if (ResetFlags & SCSI_RESET_SUGGEST_BUS_RESET) + return BusLogic_ResetHostAdapter(HostAdapter, Command, ResetFlags); + /* Fall through to Bus Device Reset case. */ + case BusLogic_ErrorRecovery_BusDeviceReset: + /* + The Bus Device Reset Error Recovery Strategy only graduates to a Hard + Reset when no commands have completed successfully since the last Bus + Device Reset and it has been at least 100 milliseconds. This prevents + a sequence of commands that all timeout together from immediately + forcing a Hard Reset before the Bus Device Reset has had a chance to + clear the error condition. + */ + if (HostAdapter->TargetFlags[TargetID].CommandSuccessfulFlag || + jiffies - HostAdapter->LastResetAttempted[TargetID] < HZ/10) + { + HostAdapter->TargetFlags[TargetID].CommandSuccessfulFlag = false; + return BusLogic_SendBusDeviceReset(HostAdapter, Command, ResetFlags); + } + /* Fall through to Hard Reset case. */ + case BusLogic_ErrorRecovery_HardReset: + return BusLogic_ResetHostAdapter(HostAdapter, Command, ResetFlags); + case BusLogic_ErrorRecovery_None: + BusLogic_Warning("Error Recovery for Target %d Suppressed\n", + HostAdapter, TargetID); + break; + } + return SCSI_RESET_PUNT; +} + + +/* + BusLogic_BIOSDiskParameters returns the Heads/Sectors/Cylinders BIOS Disk + Parameters for Disk. The default disk geometry is 64 heads, 32 sectors, and + the appropriate number of cylinders so as not to exceed drive capacity. In + order for disks equal to or larger than 1 GB to be addressable by the BIOS + without exceeding the BIOS limitation of 1024 cylinders, Extended Translation + may be enabled in AutoSCSI on FlashPoint Host Adapters and on "W" and "C" + series MultiMaster Host Adapters, or by a dip switch setting on "S" and "A" + series MultiMaster Host Adapters. With Extended Translation enabled, drives + between 1 GB inclusive and 2 GB exclusive are given a disk geometry of 128 + heads and 32 sectors, and drives above 2 GB inclusive are given a disk + geometry of 255 heads and 63 sectors. However, if the BIOS detects that the + Extended Translation setting does not match the geometry in the partition + table, then the translation inferred from the partition table will be used by + the BIOS, and a warning may be displayed. +*/ + +int BusLogic_BIOSDiskParameters(SCSI_Disk_T *Disk, KernelDevice_T Device, + int *Parameters) +{ + BusLogic_HostAdapter_T *HostAdapter = + (BusLogic_HostAdapter_T *) Disk->device->host->hostdata; + BIOS_DiskParameters_T *DiskParameters = (BIOS_DiskParameters_T *) Parameters; + struct buffer_head *BufferHead; + if (HostAdapter->ExtendedTranslationEnabled && + Disk->capacity >= 2*1024*1024 /* 1 GB in 512 byte sectors */) + { + if (Disk->capacity >= 4*1024*1024 /* 2 GB in 512 byte sectors */) + { + DiskParameters->Heads = 255; + DiskParameters->Sectors = 63; + } + else + { + DiskParameters->Heads = 128; + DiskParameters->Sectors = 32; + } + } + else + { + DiskParameters->Heads = 64; + DiskParameters->Sectors = 32; + } + DiskParameters->Cylinders = + Disk->capacity / (DiskParameters->Heads * DiskParameters->Sectors); + /* + Attempt to read the first 1024 bytes from the disk device. + */ + BufferHead = bread(MKDEV(MAJOR(Device), MINOR(Device) & ~0x0F), 0, 1024); + if (BufferHead == NULL) return 0; + /* + If the boot sector partition table flag is valid, search for a partition + table entry whose end_head matches one of the standard BusLogic geometry + translations (64/32, 128/32, or 255/63). + */ + if (*(unsigned short *) (BufferHead->b_data + 0x1FE) == 0xAA55) + { + PartitionTable_T *FirstPartitionEntry = + (PartitionTable_T *) (BufferHead->b_data + 0x1BE); + PartitionTable_T *PartitionEntry = FirstPartitionEntry; + int SavedCylinders = DiskParameters->Cylinders, PartitionNumber; + unsigned char PartitionEntryEndHead, PartitionEntryEndSector; + for (PartitionNumber = 0; PartitionNumber < 4; PartitionNumber++) + { + PartitionEntryEndHead = PartitionEntry->end_head; + PartitionEntryEndSector = PartitionEntry->end_sector & 0x3F; + if (PartitionEntryEndHead == 64-1) + { + DiskParameters->Heads = 64; + DiskParameters->Sectors = 32; + break; + } + else if (PartitionEntryEndHead == 128-1) + { + DiskParameters->Heads = 128; + DiskParameters->Sectors = 32; + break; + } + else if (PartitionEntryEndHead == 255-1) + { + DiskParameters->Heads = 255; + DiskParameters->Sectors = 63; + break; + } + PartitionEntry++; + } + if (PartitionNumber == 4) + { + PartitionEntryEndHead = FirstPartitionEntry->end_head; + PartitionEntryEndSector = FirstPartitionEntry->end_sector & 0x3F; + } + DiskParameters->Cylinders = + Disk->capacity / (DiskParameters->Heads * DiskParameters->Sectors); + if (PartitionNumber < 4 && + PartitionEntryEndSector == DiskParameters->Sectors) + { + if (DiskParameters->Cylinders != SavedCylinders) + BusLogic_Warning("Adopting Geometry %d/%d from Partition Table\n", + HostAdapter, + DiskParameters->Heads, DiskParameters->Sectors); + } + else if (PartitionEntryEndHead > 0 || PartitionEntryEndSector > 0) + { + BusLogic_Warning("Warning: Partition Table appears to " + "have Geometry %d/%d which is\n", HostAdapter, + PartitionEntryEndHead + 1, + PartitionEntryEndSector); + BusLogic_Warning("not compatible with current BusLogic " + "Host Adapter Geometry %d/%d\n", HostAdapter, + DiskParameters->Heads, DiskParameters->Sectors); + } + } + brelse(BufferHead); + return 0; +} + + +/* + BugLogic_ProcDirectoryInfo implements /proc/scsi/BusLogic/<N>. +*/ + +int BusLogic_ProcDirectoryInfo(char *ProcBuffer, char **StartPointer, + off_t Offset, int BytesAvailable, + int HostNumber, int WriteFlag) +{ + BusLogic_HostAdapter_T *HostAdapter; + BusLogic_TargetStatistics_T *TargetStatistics; + int TargetID, Length; + char *Buffer; + for (HostAdapter = BusLogic_FirstRegisteredHostAdapter; + HostAdapter != NULL; + HostAdapter = HostAdapter->Next) + if (HostAdapter->HostNumber == HostNumber) break; + if (HostAdapter == NULL) + { + BusLogic_Error("Cannot find Host Adapter for SCSI Host %d\n", + NULL, HostNumber); + return 0; + } + TargetStatistics = HostAdapter->TargetStatistics; + if (WriteFlag) + { + HostAdapter->ExternalHostAdapterResets = 0; + HostAdapter->HostAdapterInternalErrors = 0; + memset(TargetStatistics, 0, + BusLogic_MaxTargetDevices * sizeof(BusLogic_TargetStatistics_T)); + return 0; + } + Buffer = HostAdapter->MessageBuffer; + Length = HostAdapter->MessageBufferLength; + Length += sprintf(&Buffer[Length], "\n\ +Current Driver Queue Depth: %d\n\ +Currently Allocated CCBs: %d\n", + HostAdapter->DriverQueueDepth, + HostAdapter->AllocatedCCBs); + Length += sprintf(&Buffer[Length], "\n\n\ + DATA TRANSFER STATISTICS\n\ +\n\ +Target Tagged Queuing Queue Depth Active Attempted Completed\n\ +====== ============== =========== ====== ========= =========\n"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + BusLogic_TargetFlags_T *TargetFlags = &HostAdapter->TargetFlags[TargetID]; + if (!TargetFlags->TargetExists) continue; + Length += + sprintf(&Buffer[Length], " %2d %s", TargetID, + (TargetFlags->TaggedQueuingSupported + ? (TargetFlags->TaggedQueuingActive + ? " Active" + : (HostAdapter->TaggedQueuingPermitted & (1 << TargetID) + ? " Permitted" : " Disabled")) + : "Not Supported")); + Length += sprintf(&Buffer[Length], + " %3d %3u %9u %9u\n", + HostAdapter->QueueDepth[TargetID], + HostAdapter->ActiveCommands[TargetID], + TargetStatistics[TargetID].CommandsAttempted, + TargetStatistics[TargetID].CommandsCompleted); + } + Length += sprintf(&Buffer[Length], "\n\ +Target Read Commands Write Commands Total Bytes Read Total Bytes Written\n\ +====== ============= ============== =================== ===================\n"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + BusLogic_TargetFlags_T *TargetFlags = &HostAdapter->TargetFlags[TargetID]; + if (!TargetFlags->TargetExists) continue; + Length += + sprintf(&Buffer[Length], " %2d %9u %9u", TargetID, + TargetStatistics[TargetID].ReadCommands, + TargetStatistics[TargetID].WriteCommands); + if (TargetStatistics[TargetID].TotalBytesRead.Billions > 0) + Length += + sprintf(&Buffer[Length], " %9u%09u", + TargetStatistics[TargetID].TotalBytesRead.Billions, + TargetStatistics[TargetID].TotalBytesRead.Units); + else + Length += + sprintf(&Buffer[Length], " %9u", + TargetStatistics[TargetID].TotalBytesRead.Units); + if (TargetStatistics[TargetID].TotalBytesWritten.Billions > 0) + Length += + sprintf(&Buffer[Length], " %9u%09u\n", + TargetStatistics[TargetID].TotalBytesWritten.Billions, + TargetStatistics[TargetID].TotalBytesWritten.Units); + else + Length += + sprintf(&Buffer[Length], " %9u\n", + TargetStatistics[TargetID].TotalBytesWritten.Units); + } + Length += sprintf(&Buffer[Length], "\n\ +Target Command 0-1KB 1-2KB 2-4KB 4-8KB 8-16KB\n\ +====== ======= ========= ========= ========= ========= =========\n"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + BusLogic_TargetFlags_T *TargetFlags = &HostAdapter->TargetFlags[TargetID]; + if (!TargetFlags->TargetExists) continue; + Length += + sprintf(&Buffer[Length], + " %2d Read %9u %9u %9u %9u %9u\n", TargetID, + TargetStatistics[TargetID].ReadCommandSizeBuckets[0], + TargetStatistics[TargetID].ReadCommandSizeBuckets[1], + TargetStatistics[TargetID].ReadCommandSizeBuckets[2], + TargetStatistics[TargetID].ReadCommandSizeBuckets[3], + TargetStatistics[TargetID].ReadCommandSizeBuckets[4]); + Length += + sprintf(&Buffer[Length], + " %2d Write %9u %9u %9u %9u %9u\n", TargetID, + TargetStatistics[TargetID].WriteCommandSizeBuckets[0], + TargetStatistics[TargetID].WriteCommandSizeBuckets[1], + TargetStatistics[TargetID].WriteCommandSizeBuckets[2], + TargetStatistics[TargetID].WriteCommandSizeBuckets[3], + TargetStatistics[TargetID].WriteCommandSizeBuckets[4]); + } + Length += sprintf(&Buffer[Length], "\n\ +Target Command 16-32KB 32-64KB 64-128KB 128-256KB 256KB+\n\ +====== ======= ========= ========= ========= ========= =========\n"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + BusLogic_TargetFlags_T *TargetFlags = &HostAdapter->TargetFlags[TargetID]; + if (!TargetFlags->TargetExists) continue; + Length += + sprintf(&Buffer[Length], + " %2d Read %9u %9u %9u %9u %9u\n", TargetID, + TargetStatistics[TargetID].ReadCommandSizeBuckets[5], + TargetStatistics[TargetID].ReadCommandSizeBuckets[6], + TargetStatistics[TargetID].ReadCommandSizeBuckets[7], + TargetStatistics[TargetID].ReadCommandSizeBuckets[8], + TargetStatistics[TargetID].ReadCommandSizeBuckets[9]); + Length += + sprintf(&Buffer[Length], + " %2d Write %9u %9u %9u %9u %9u\n", TargetID, + TargetStatistics[TargetID].WriteCommandSizeBuckets[5], + TargetStatistics[TargetID].WriteCommandSizeBuckets[6], + TargetStatistics[TargetID].WriteCommandSizeBuckets[7], + TargetStatistics[TargetID].WriteCommandSizeBuckets[8], + TargetStatistics[TargetID].WriteCommandSizeBuckets[9]); + } + Length += sprintf(&Buffer[Length], "\n\n\ + ERROR RECOVERY STATISTICS\n\ +\n\ + Command Aborts Bus Device Resets Host Adapter Resets\n\ +Target Requested Completed Requested Completed Requested Completed\n\ + ID \\\\\\\\ Attempted //// \\\\\\\\ Attempted //// \\\\\\\\ Attempted ////\n\ +====== ===== ===== ===== ===== ===== ===== ===== ===== =====\n"); + for (TargetID = 0; TargetID < HostAdapter->MaxTargetDevices; TargetID++) + { + BusLogic_TargetFlags_T *TargetFlags = &HostAdapter->TargetFlags[TargetID]; + if (!TargetFlags->TargetExists) continue; + Length += + sprintf(&Buffer[Length], "\ + %2d %5d %5d %5d %5d %5d %5d %5d %5d %5d\n", TargetID, + TargetStatistics[TargetID].CommandAbortsRequested, + TargetStatistics[TargetID].CommandAbortsAttempted, + TargetStatistics[TargetID].CommandAbortsCompleted, + TargetStatistics[TargetID].BusDeviceResetsRequested, + TargetStatistics[TargetID].BusDeviceResetsAttempted, + TargetStatistics[TargetID].BusDeviceResetsCompleted, + TargetStatistics[TargetID].HostAdapterResetsRequested, + TargetStatistics[TargetID].HostAdapterResetsAttempted, + TargetStatistics[TargetID].HostAdapterResetsCompleted); + } + Length += sprintf(&Buffer[Length], "\nExternal Host Adapter Resets: %d\n", + HostAdapter->ExternalHostAdapterResets); + Length += sprintf(&Buffer[Length], "Host Adapter Internal Errors: %d\n", + HostAdapter->HostAdapterInternalErrors); + if (Length >= BusLogic_MessageBufferSize) + BusLogic_Error("Message Buffer length %d exceeds size %d\n", + HostAdapter, Length, BusLogic_MessageBufferSize); + if ((Length -= Offset) <= 0) return 0; + if (Length >= BytesAvailable) Length = BytesAvailable; + *StartPointer = &HostAdapter->MessageBuffer[Offset]; + return Length; +} + + +/* + BusLogic_Message prints Driver Messages. +*/ + +static void BusLogic_Message(BusLogic_MessageLevel_T MessageLevel, + char *Format, + BusLogic_HostAdapter_T *HostAdapter, + ...) +{ + static char Buffer[BusLogic_LineBufferSize]; + static boolean BeginningOfLine = true; + va_list Arguments; + int Length = 0; + va_start(Arguments, HostAdapter); + Length = vsprintf(Buffer, Format, Arguments); + va_end(Arguments); + if (MessageLevel == BusLogic_AnnounceLevel) + { + static int AnnouncementLines = 0; + strcpy(&HostAdapter->MessageBuffer[HostAdapter->MessageBufferLength], + Buffer); + HostAdapter->MessageBufferLength += Length; + if (++AnnouncementLines <= 2) + printk("%sscsi: %s", BusLogic_MessageLevelMap[MessageLevel], Buffer); + } + else if (MessageLevel == BusLogic_InfoLevel) + { + strcpy(&HostAdapter->MessageBuffer[HostAdapter->MessageBufferLength], + Buffer); + HostAdapter->MessageBufferLength += Length; + if (BeginningOfLine) + { + if (Buffer[0] != '\n' || Length > 1) + printk("%sscsi%d: %s", BusLogic_MessageLevelMap[MessageLevel], + HostAdapter->HostNumber, Buffer); + } + else printk("%s", Buffer); + } + else + { + if (BeginningOfLine) + { + if (HostAdapter != NULL && HostAdapter->HostAdapterInitialized) + printk("%sscsi%d: %s", BusLogic_MessageLevelMap[MessageLevel], + HostAdapter->HostNumber, Buffer); + else printk("%s%s", BusLogic_MessageLevelMap[MessageLevel], Buffer); + } + else printk("%s", Buffer); + } + BeginningOfLine = (Buffer[Length-1] == '\n'); +} + + +/* + BusLogic_ParseKeyword parses an individual option keyword. It returns true + and updates the pointer if the keyword is recognized and false otherwise. +*/ + +static boolean BusLogic_ParseKeyword(char **StringPointer, char *Keyword) +{ + char *Pointer = *StringPointer; + while (*Keyword != '\0') + { + char StringChar = *Pointer++; + char KeywordChar = *Keyword++; + if (StringChar >= 'A' && StringChar <= 'Z') + StringChar += 'a' - 'Z'; + if (KeywordChar >= 'A' && KeywordChar <= 'Z') + KeywordChar += 'a' - 'Z'; + if (StringChar != KeywordChar) return false; + } + *StringPointer = Pointer; + return true; +} + + +/* + BusLogic_ParseDriverOptions handles processing of BusLogic Driver Options + specifications. + + BusLogic Driver Options may be specified either via the Linux Kernel Command + Line or via the Loadable Kernel Module Installation Facility. Driver Options + for multiple host adapters may be specified either by separating the option + strings by a semicolon, or by specifying multiple "BusLogic=" strings on the + command line. Individual option specifications for a single host adapter are + separated by commas. The Probing and Debugging Options apply to all host + adapters whereas the remaining options apply individually only to the + selected host adapter. + + The BusLogic Driver Probing Options comprise the following: + + IO:<integer> + + The "IO:" option specifies an ISA I/O Address to be probed for a non-PCI + MultiMaster Host Adapter. If neither "IO:" nor "NoProbeISA" options are + specified, then the standard list of BusLogic MultiMaster ISA I/O Addresses + will be probed (0x330, 0x334, 0x230, 0x234, 0x130, and 0x134). Multiple + "IO:" options may be specified to precisely determine the I/O Addresses to + be probed, but the probe order will always follow the standard list. + + NoProbe + + The "NoProbe" option disables all probing and therefore no BusLogic Host + Adapters will be detected. + + NoProbeISA + + The "NoProbeISA" option disables probing of the standard BusLogic ISA I/O + Addresses and therefore only PCI MultiMaster and FlashPoint Host Adapters + will be detected. + + NoProbePCI + + The "NoProbePCI" options disables the interrogation of PCI Configuration + Space and therefore only ISA Multimaster Host Adapters will be detected, as + well as PCI Multimaster Host Adapters that have their ISA Compatible I/O + Port set to "Primary" or "Alternate". + + NoSortPCI + + The "NoSortPCI" option forces PCI MultiMaster Host Adapters to be + enumerated in the order provided by the PCI BIOS, ignoring any setting of + the AutoSCSI "Use Bus And Device # For PCI Scanning Seq." option. + + MultiMasterFirst + + The "MultiMasterFirst" option forces MultiMaster Host Adapters to be probed + before FlashPoint Host Adapters. By default, if both FlashPoint and PCI + MultiMaster Host Adapters are present, this driver will probe for + FlashPoint Host Adapters first unless the BIOS primary disk is controlled + by the first PCI MultiMaster Host Adapter, in which case MultiMaster Host + Adapters will be probed first. + + FlashPointFirst + + The "FlashPointFirst" option forces FlashPoint Host Adapters to be probed + before MultiMaster Host Adapters. + + The BusLogic Driver Tagged Queuing Options allow for explicitly specifying + the Queue Depth and whether Tagged Queuing is permitted for each Target + Device (assuming that the Target Device supports Tagged Queuing). The Queue + Depth is the number of SCSI Commands that are allowed to be concurrently + presented for execution (either to the Host Adapter or Target Device). Note + that explicitly enabling Tagged Queuing may lead to problems; the option to + enable or disable Tagged Queuing is provided primarily to allow disabling + Tagged Queuing on Target Devices that do not implement it correctly. The + following options are available: + + QueueDepth:<integer> + + The "QueueDepth:" or QD:" option specifies the Queue Depth to use for all + Target Devices that support Tagged Queuing, as well as the maximum Queue + Depth for devices that do not support Tagged Queuing. If no Queue Depth + option is provided, the Queue Depth will be determined automatically based + on the Host Adapter's Total Queue Depth and the number, type, speed, and + capabilities of the detected Target Devices. For Host Adapters that + require ISA Bounce Buffers, the Queue Depth is automatically set by default + to BusLogic_TaggedQueueDepthBB or BusLogic_UntaggedQueueDepthBB to avoid + excessive preallocation of DMA Bounce Buffer memory. Target Devices that + do not support Tagged Queuing always have their Queue Depth set to + BusLogic_UntaggedQueueDepth or BusLogic_UntaggedQueueDepthBB, unless a + lower Queue Depth option is provided. A Queue Depth of 1 automatically + disables Tagged Queuing. + + QueueDepth:[<integer>,<integer>...] + + The "QueueDepth:[...]" or "QD:[...]" option specifies the Queue Depth + individually for each Target Device. If an <integer> is omitted, the + associated Target Device will have its Queue Depth selected automatically. + + TaggedQueuing:Default + + The "TaggedQueuing:Default" or "TQ:Default" option permits Tagged Queuing + based on the firmware version of the BusLogic Host Adapter and based on + whether the Queue Depth allows queuing multiple commands. + + TaggedQueuing:Enable + + The "TaggedQueuing:Enable" or "TQ:Enable" option enables Tagged Queuing for + all Target Devices on this Host Adapter, overriding any limitation that + would otherwise be imposed based on the Host Adapter firmware version. + + TaggedQueuing:Disable + + The "TaggedQueuing:Disable" or "TQ:Disable" option disables Tagged Queuing + for all Target Devices on this Host Adapter. + + TaggedQueuing:<Target-Spec> + + The "TaggedQueuing:<Target-Spec>" or "TQ:<Target-Spec>" option controls + Tagged Queuing individually for each Target Device. <Target-Spec> is a + sequence of "Y", "N", and "X" characters. "Y" enables Tagged Queuing, "N" + disables Tagged Queuing, and "X" accepts the default based on the firmware + version. The first character refers to Target Device 0, the second to + Target Device 1, and so on; if the sequence of "Y", "N", and "X" characters + does not cover all the Target Devices, unspecified characters are assumed + to be "X". + + The BusLogic Driver Error Recovery Option allows for explicitly specifying + the Error Recovery action to be performed when BusLogic_ResetCommand is + called due to a SCSI Command failing to complete successfully. The following + options are available: + + ErrorRecovery:Default + + The "ErrorRecovery:Default" or "ER:Default" option selects between the Hard + Reset and Bus Device Reset options based on the recommendation of the SCSI + Subsystem. + + ErrorRecovery:HardReset + + The "ErrorRecovery:HardReset" or "ER:HardReset" option will initiate a Host + Adapter Hard Reset which also causes a SCSI Bus Reset. + + ErrorRecovery:BusDeviceReset + + The "ErrorRecovery:BusDeviceReset" or "ER:BusDeviceReset" option will send + a Bus Device Reset message to the individual Target Device causing the + error. If Error Recovery is again initiated for this Target Device and no + SCSI Command to this Target Device has completed successfully since the Bus + Device Reset message was sent, then a Hard Reset will be attempted. + + ErrorRecovery:None + + The "ErrorRecovery:None" or "ER:None" option suppresses Error Recovery. + This option should only be selected if a SCSI Bus Reset or Bus Device Reset + will cause the Target Device or a critical operation to suffer a complete + and unrecoverable failure. + + ErrorRecovery:<Target-Spec> + + The "ErrorRecovery:<Target-Spec>" or "ER:<Target-Spec>" option controls + Error Recovery individually for each Target Device. <Target-Spec> is a + sequence of "D", "H", "B", and "N" characters. "D" selects Default, "H" + selects Hard Reset, "B" selects Bus Device Reset, and "N" selects None. + The first character refers to Target Device 0, the second to Target Device + 1, and so on; if the sequence of "D", "H", "B", and "N" characters does not + cover all the possible Target Devices, unspecified characters are assumed + to be "D". + + The BusLogic Driver Miscellaneous Options comprise the following: + + BusSettleTime:<seconds> + + The "BusSettleTime:" or "BST:" option specifies the Bus Settle Time in + seconds. The Bus Settle Time is the amount of time to wait between a Host + Adapter Hard Reset which initiates a SCSI Bus Reset and issuing any SCSI + Commands. If unspecified, it defaults to BusLogic_DefaultBusSettleTime. + + InhibitTargetInquiry + + The "InhibitTargetInquiry" option inhibits the execution of an Inquire + Target Devices or Inquire Installed Devices command on MultiMaster Host + Adapters. This may be necessary with some older Target Devices that do not + respond correctly when Logical Units above 0 are addressed. + + The BusLogic Driver Debugging Options comprise the following: + + TraceProbe + + The "TraceProbe" option enables tracing of Host Adapter Probing. + + TraceHardwareReset + + The "TraceHardwareReset" option enables tracing of Host Adapter Hardware + Reset. + + TraceConfiguration + + The "TraceConfiguration" option enables tracing of Host Adapter + Configuration. + + TraceErrors + + The "TraceErrors" option enables tracing of SCSI Commands that return an + error from the Target Device. The CDB and Sense Data will be printed for + each SCSI Command that fails. + + Debug + + The "Debug" option enables all debugging options. + + The following examples demonstrate setting the Queue Depth for Target Devices + 1 and 2 on the first host adapter to 7 and 15, the Queue Depth for all Target + Devices on the second host adapter to 31, and the Bus Settle Time on the + second host adapter to 30 seconds. + + Linux Kernel Command Line: + + linux BusLogic=QueueDepth:[,7,15];QueueDepth:31,BusSettleTime:30 + + LILO Linux Boot Loader (in /etc/lilo.conf): + + append = "BusLogic=QueueDepth:[,7,15];QueueDepth:31,BusSettleTime:30" + + INSMOD Loadable Kernel Module Installation Facility: + + insmod BusLogic.o \ + 'BusLogic_Options="QueueDepth:[,7,15];QueueDepth:31,BusSettleTime:30"' + + NOTE: Module Utilities 2.1.71 or later is required for correct parsing + of driver options containing commas. + +*/ + +static void BusLogic_ParseDriverOptions(char *OptionsString) +{ + while (true) + { + BusLogic_DriverOptions_T *DriverOptions = + &BusLogic_DriverOptions[BusLogic_DriverOptionsCount++]; + int TargetID; + memset(DriverOptions, 0, sizeof(BusLogic_DriverOptions_T)); + for (TargetID = 0; TargetID < BusLogic_MaxTargetDevices; TargetID++) + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_Default; + while (*OptionsString != '\0' && *OptionsString != ';') + { + /* Probing Options. */ + if (BusLogic_ParseKeyword(&OptionsString, "IO:")) + { + BusLogic_IO_Address_T IO_Address = + simple_strtoul(OptionsString, &OptionsString, 0); + BusLogic_ProbeOptions.LimitedProbeISA = true; + switch (IO_Address) + { + case 0x330: + BusLogic_ProbeOptions.Probe330 = true; + break; + case 0x334: + BusLogic_ProbeOptions.Probe334 = true; + break; + case 0x230: + BusLogic_ProbeOptions.Probe230 = true; + break; + case 0x234: + BusLogic_ProbeOptions.Probe234 = true; + break; + case 0x130: + BusLogic_ProbeOptions.Probe130 = true; + break; + case 0x134: + BusLogic_ProbeOptions.Probe134 = true; + break; + default: + BusLogic_Error("BusLogic: Invalid Driver Options " + "(illegal I/O Address 0x%X)\n", + NULL, IO_Address); + return; + } + } + else if (BusLogic_ParseKeyword(&OptionsString, "NoProbeISA")) + BusLogic_ProbeOptions.NoProbeISA = true; + else if (BusLogic_ParseKeyword(&OptionsString, "NoProbePCI")) + BusLogic_ProbeOptions.NoProbePCI = true; + else if (BusLogic_ParseKeyword(&OptionsString, "NoProbe")) + BusLogic_ProbeOptions.NoProbe = true; + else if (BusLogic_ParseKeyword(&OptionsString, "NoSortPCI")) + BusLogic_ProbeOptions.NoSortPCI = true; + else if (BusLogic_ParseKeyword(&OptionsString, "MultiMasterFirst")) + BusLogic_ProbeOptions.MultiMasterFirst = true; + else if (BusLogic_ParseKeyword(&OptionsString, "FlashPointFirst")) + BusLogic_ProbeOptions.FlashPointFirst = true; + /* Tagged Queuing Options. */ + else if (BusLogic_ParseKeyword(&OptionsString, "QueueDepth:[") || + BusLogic_ParseKeyword(&OptionsString, "QD:[")) + { + for (TargetID = 0; + TargetID < BusLogic_MaxTargetDevices; + TargetID++) + { + unsigned short QueueDepth = + simple_strtoul(OptionsString, &OptionsString, 0); + if (QueueDepth > BusLogic_MaxTaggedQueueDepth) + { + BusLogic_Error("BusLogic: Invalid Driver Options " + "(illegal Queue Depth %d)\n", + NULL, QueueDepth); + return; + } + DriverOptions->QueueDepth[TargetID] = QueueDepth; + if (*OptionsString == ',') + OptionsString++; + else if (*OptionsString == ']') + break; + else + { + BusLogic_Error("BusLogic: Invalid Driver Options " + "(',' or ']' expected at '%s')\n", + NULL, OptionsString); + return; + } + } + if (*OptionsString != ']') + { + BusLogic_Error("BusLogic: Invalid Driver Options " + "(']' expected at '%s')\n", + NULL, OptionsString); + return; + } + else OptionsString++; + } + else if (BusLogic_ParseKeyword(&OptionsString, "QueueDepth:") || + BusLogic_ParseKeyword(&OptionsString, "QD:")) + { + unsigned short QueueDepth = + simple_strtoul(OptionsString, &OptionsString, 0); + if (QueueDepth == 0 || QueueDepth > BusLogic_MaxTaggedQueueDepth) + { + BusLogic_Error("BusLogic: Invalid Driver Options " + "(illegal Queue Depth %d)\n", + NULL, QueueDepth); + return; + } + DriverOptions->CommonQueueDepth = QueueDepth; + for (TargetID = 0; + TargetID < BusLogic_MaxTargetDevices; + TargetID++) + DriverOptions->QueueDepth[TargetID] = QueueDepth; + } + else if (BusLogic_ParseKeyword(&OptionsString, "TaggedQueuing:") || + BusLogic_ParseKeyword(&OptionsString, "TQ:")) + { + if (BusLogic_ParseKeyword(&OptionsString, "Default")) + { + DriverOptions->TaggedQueuingPermitted = 0x0000; + DriverOptions->TaggedQueuingPermittedMask = 0x0000; + } + else if (BusLogic_ParseKeyword(&OptionsString, "Enable")) + { + DriverOptions->TaggedQueuingPermitted = 0xFFFF; + DriverOptions->TaggedQueuingPermittedMask = 0xFFFF; + } + else if (BusLogic_ParseKeyword(&OptionsString, "Disable")) + { + DriverOptions->TaggedQueuingPermitted = 0x0000; + DriverOptions->TaggedQueuingPermittedMask = 0xFFFF; + } + else + { + unsigned short TargetBit; + for (TargetID = 0, TargetBit = 1; + TargetID < BusLogic_MaxTargetDevices; + TargetID++, TargetBit <<= 1) + switch (*OptionsString++) + { + case 'Y': + DriverOptions->TaggedQueuingPermitted |= TargetBit; + DriverOptions->TaggedQueuingPermittedMask |= TargetBit; + break; + case 'N': + DriverOptions->TaggedQueuingPermitted &= ~TargetBit; + DriverOptions->TaggedQueuingPermittedMask |= TargetBit; + break; + case 'X': + break; + default: + OptionsString--; + TargetID = BusLogic_MaxTargetDevices; + break; + } + } + } + /* Error Recovery Option. */ + else if (BusLogic_ParseKeyword(&OptionsString, "ErrorRecovery:") || + BusLogic_ParseKeyword(&OptionsString, "ER:")) + { + if (BusLogic_ParseKeyword(&OptionsString, "Default")) + for (TargetID = 0; + TargetID < BusLogic_MaxTargetDevices; + TargetID++) + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_Default; + else if (BusLogic_ParseKeyword(&OptionsString, "HardReset")) + for (TargetID = 0; + TargetID < BusLogic_MaxTargetDevices; + TargetID++) + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_HardReset; + else if (BusLogic_ParseKeyword(&OptionsString, "BusDeviceReset")) + for (TargetID = 0; + TargetID < BusLogic_MaxTargetDevices; + TargetID++) + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_BusDeviceReset; + else if (BusLogic_ParseKeyword(&OptionsString, "None")) + for (TargetID = 0; + TargetID < BusLogic_MaxTargetDevices; + TargetID++) + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_None; + else + for (TargetID = 0; + TargetID < BusLogic_MaxTargetDevices; + TargetID++) + switch (*OptionsString++) + { + case 'D': + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_Default; + break; + case 'H': + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_HardReset; + break; + case 'B': + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_BusDeviceReset; + break; + case 'N': + DriverOptions->ErrorRecoveryStrategy[TargetID] = + BusLogic_ErrorRecovery_None; + break; + default: + OptionsString--; + TargetID = BusLogic_MaxTargetDevices; + break; + } + } + /* Miscellaneous Options. */ + else if (BusLogic_ParseKeyword(&OptionsString, "BusSettleTime:") || + BusLogic_ParseKeyword(&OptionsString, "BST:")) + { + unsigned short BusSettleTime = + simple_strtoul(OptionsString, &OptionsString, 0); + if (BusSettleTime > 5 * 60) + { + BusLogic_Error("BusLogic: Invalid Driver Options " + "(illegal Bus Settle Time %d)\n", + NULL, BusSettleTime); + return; + } + DriverOptions->BusSettleTime = BusSettleTime; + } + else if (BusLogic_ParseKeyword(&OptionsString, + "InhibitTargetInquiry")) + DriverOptions->LocalOptions.InhibitTargetInquiry = true; + /* Debugging Options. */ + else if (BusLogic_ParseKeyword(&OptionsString, "TraceProbe")) + BusLogic_GlobalOptions.TraceProbe = true; + else if (BusLogic_ParseKeyword(&OptionsString, "TraceHardwareReset")) + BusLogic_GlobalOptions.TraceHardwareReset = true; + else if (BusLogic_ParseKeyword(&OptionsString, "TraceConfiguration")) + BusLogic_GlobalOptions.TraceConfiguration = true; + else if (BusLogic_ParseKeyword(&OptionsString, "TraceErrors")) + BusLogic_GlobalOptions.TraceErrors = true; + else if (BusLogic_ParseKeyword(&OptionsString, "Debug")) + { + BusLogic_GlobalOptions.TraceProbe = true; + BusLogic_GlobalOptions.TraceHardwareReset = true; + BusLogic_GlobalOptions.TraceConfiguration = true; + BusLogic_GlobalOptions.TraceErrors = true; + } + if (*OptionsString == ',') + OptionsString++; + else if (*OptionsString != ';' && *OptionsString != '\0') + { + BusLogic_Error("BusLogic: Unexpected Driver Option '%s' " + "ignored\n", NULL, OptionsString); + *OptionsString = '\0'; + } + } + if (!(BusLogic_DriverOptionsCount == 0 || + BusLogic_ProbeInfoCount == 0 || + BusLogic_DriverOptionsCount == BusLogic_ProbeInfoCount)) + { + BusLogic_Error("BusLogic: Invalid Driver Options " + "(all or no I/O Addresses must be specified)\n", NULL); + return; + } + /* + Tagged Queuing is disabled when the Queue Depth is 1 since queuing + multiple commands is not possible. + */ + for (TargetID = 0; TargetID < BusLogic_MaxTargetDevices; TargetID++) + if (DriverOptions->QueueDepth[TargetID] == 1) + { + unsigned short TargetBit = 1 << TargetID; + DriverOptions->TaggedQueuingPermitted &= ~TargetBit; + DriverOptions->TaggedQueuingPermittedMask |= TargetBit; + } + if (*OptionsString == ';') OptionsString++; + if (*OptionsString == '\0') return; + } +} + + +/* + BusLogic_Setup handles processing of Kernel Command Line Arguments. +*/ + +void BusLogic_Setup(char *CommandLineString, int *CommandLineIntegers) +{ + if (CommandLineIntegers[0] != 0) + { + BusLogic_Error("BusLogic: Obsolete Command Line Entry " + "Format Ignored\n", NULL); + return; + } + if (CommandLineString == NULL || *CommandLineString == '\0') return; + BusLogic_ParseDriverOptions(CommandLineString); +} + + +/* + Include Module support if requested. +*/ + +#ifdef MODULE + +SCSI_Host_Template_T driver_template = BUSLOGIC; + +#include "scsi_module.c" + +#endif diff --git a/linux/src/drivers/scsi/BusLogic.h b/linux/src/drivers/scsi/BusLogic.h new file mode 100644 index 00000000..f60ee071 --- /dev/null +++ b/linux/src/drivers/scsi/BusLogic.h @@ -0,0 +1,1775 @@ +/* + + Linux Driver for BusLogic MultiMaster and FlashPoint SCSI Host Adapters + + Copyright 1995-1998 by Leonard N. Zubkoff <lnz@dandelion.com> + + This program is free software; you may redistribute and/or modify it under + the terms of the GNU General Public License Version 2 as published by the + Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for complete details. + + The author respectfully requests that any modifications to this software be + sent directly to him for evaluation and testing. + + Special thanks to Wayne Yen, Jin-Lon Hon, and Alex Win of BusLogic, whose + advice has been invaluable, to David Gentzel, for writing the original Linux + BusLogic driver, and to Paul Gortmaker, for being such a dedicated test site. + + Finally, special thanks to Mylex/BusLogic for making the FlashPoint SCCB + Manager available as freely redistributable source code. + +*/ + + +#include <linux/config.h> + + +/* + Define types for some of the structures that interface with the rest + of the Linux Kernel and SCSI Subsystem. +*/ + +typedef kdev_t KernelDevice_T; +typedef struct proc_dir_entry PROC_DirectoryEntry_T; +typedef unsigned long ProcessorFlags_T; +typedef struct pt_regs Registers_T; +typedef struct partition PartitionTable_T; +typedef Scsi_Host_Template SCSI_Host_Template_T; +typedef struct Scsi_Host SCSI_Host_T; +typedef struct scsi_device SCSI_Device_T; +typedef struct scsi_disk SCSI_Disk_T; +typedef struct scsi_cmnd SCSI_Command_T; +typedef struct scatterlist SCSI_ScatterList_T; + + +/* + Define prototypes for the BusLogic Driver Interface Functions. +*/ + +extern PROC_DirectoryEntry_T BusLogic_ProcDirectoryEntry; +extern const char *BusLogic_DriverInfo(SCSI_Host_T *); +extern int BusLogic_DetectHostAdapter(SCSI_Host_Template_T *); +extern int BusLogic_ReleaseHostAdapter(SCSI_Host_T *); +extern int BusLogic_QueueCommand(SCSI_Command_T *, + void (*CompletionRoutine)(SCSI_Command_T *)); +extern int BusLogic_AbortCommand(SCSI_Command_T *); +extern int BusLogic_ResetCommand(SCSI_Command_T *, unsigned int); +extern int BusLogic_BIOSDiskParameters(SCSI_Disk_T *, KernelDevice_T, int *); +extern int BusLogic_ProcDirectoryInfo(char *, char **, off_t, int, int, int); + + +/* + Define the BusLogic SCSI Host Template structure. +*/ + +#define BUSLOGIC \ + { proc_dir: &BusLogic_ProcDirectoryEntry, /* ProcFS Directory Entry */ \ + proc_info: BusLogic_ProcDirectoryInfo, /* ProcFS Info Function */ \ + name: "BusLogic", /* Driver Name */ \ + detect: BusLogic_DetectHostAdapter, /* Detect Host Adapter */ \ + release: BusLogic_ReleaseHostAdapter, /* Release Host Adapter */ \ + info: BusLogic_DriverInfo, /* Driver Info Function */ \ + queuecommand: BusLogic_QueueCommand, /* Queue Command Function */ \ + abort: BusLogic_AbortCommand, /* Abort Command Function */ \ + reset: BusLogic_ResetCommand, /* Reset Command Function */ \ + bios_param: BusLogic_BIOSDiskParameters, /* BIOS Disk Parameters */ \ + unchecked_isa_dma: 1, /* Default Initial Value */ \ + use_clustering: ENABLE_CLUSTERING } /* Enable Clustering */ + + +/* + BusLogic_DriverVersion protects the private portion of this file. +*/ + +#ifdef BusLogic_DriverVersion + + +/* + FlashPoint support is only available for the Intel x86 Architecture with + CONFIG_PCI set. +*/ + +#ifndef __i386__ +#undef CONFIG_SCSI_OMIT_FLASHPOINT +#define CONFIG_SCSI_OMIT_FLASHPOINT +#endif + +#ifndef CONFIG_PCI +#undef CONFIG_SCSI_OMIT_FLASHPOINT +#define CONFIG_SCSI_OMIT_FLASHPOINT +#define BusLogic_InitializeProbeInfoListISA \ + BusLogic_InitializeProbeInfoList +#endif + + +/* + Define the maximum number of BusLogic Host Adapters supported by this driver. +*/ + +#define BusLogic_MaxHostAdapters 16 + + +/* + Define the maximum number of Target Devices supported by this driver. +*/ + +#define BusLogic_MaxTargetDevices 16 + + +/* + Define the maximum number of Scatter/Gather Segments used by this driver. + For optimal performance, it is important that this limit be at least as + large as the largest single request generated by the I/O Subsystem. +*/ + +#define BusLogic_ScatterGatherLimit 128 + + +/* + Define the maximum, maximum automatic, minimum automatic, and default Queue + Depth to allow for Target Devices depending on whether or not they support + Tagged Queuing and whether or not ISA Bounce Buffers are required. +*/ + +#define BusLogic_MaxTaggedQueueDepth 64 +#define BusLogic_MaxAutomaticTaggedQueueDepth 28 +#define BusLogic_MinAutomaticTaggedQueueDepth 7 +#define BusLogic_TaggedQueueDepthBB 3 +#define BusLogic_UntaggedQueueDepth 3 +#define BusLogic_UntaggedQueueDepthBB 2 + + +/* + Define the default amount of time in seconds to wait between a Host Adapter + Hard Reset which initiates a SCSI Bus Reset and issuing any SCSI commands. + Some SCSI devices get confused if they receive SCSI commands too soon after + a SCSI Bus Reset. +*/ + +#define BusLogic_DefaultBusSettleTime 2 + + +/* + Define the maximum number of Mailboxes that should be used for MultiMaster + Host Adapters. This number is chosen to be larger than the maximum Host + Adapter Queue Depth and small enough so that the Host Adapter structure + does not cross an allocation block size boundary. +*/ + +#define BusLogic_MaxMailboxes 211 + + +/* + Define the number of CCBs that should be allocated as a group to optimize + Kernel memory allocation. +*/ + +#define BusLogic_CCB_AllocationGroupSize 7 + + +/* + Define the Host Adapter Line and Message Buffer Sizes. +*/ + +#define BusLogic_LineBufferSize 100 +#define BusLogic_MessageBufferSize 9700 + + +/* + Define the Driver Message Levels. +*/ + +typedef enum BusLogic_MessageLevel +{ + BusLogic_AnnounceLevel = 0, + BusLogic_InfoLevel = 1, + BusLogic_NoticeLevel = 2, + BusLogic_WarningLevel = 3, + BusLogic_ErrorLevel = 4 +} +BusLogic_MessageLevel_T; + +static char + *BusLogic_MessageLevelMap[] = + { KERN_NOTICE, KERN_NOTICE, KERN_NOTICE, KERN_WARNING, KERN_ERR }; + + +/* + Define Driver Message macros. +*/ + +#define BusLogic_Announce(Format, Arguments...) \ + BusLogic_Message(BusLogic_AnnounceLevel, Format, ##Arguments) + +#define BusLogic_Info(Format, Arguments...) \ + BusLogic_Message(BusLogic_InfoLevel, Format, ##Arguments) + +#define BusLogic_Notice(Format, Arguments...) \ + BusLogic_Message(BusLogic_NoticeLevel, Format, ##Arguments) + +#define BusLogic_Warning(Format, Arguments...) \ + BusLogic_Message(BusLogic_WarningLevel, Format, ##Arguments) + +#define BusLogic_Error(Format, Arguments...) \ + BusLogic_Message(BusLogic_ErrorLevel, Format, ##Arguments) + + +/* + Define the types of BusLogic Host Adapters that are supported and the number + of I/O Addresses required by each type. +*/ + +typedef enum +{ + BusLogic_MultiMaster = 1, + BusLogic_FlashPoint = 2 +} +__attribute__ ((packed)) +BusLogic_HostAdapterType_T; + +#define BusLogic_MultiMasterAddressCount 4 +#define BusLogic_FlashPointAddressCount 256 + +static int + BusLogic_HostAdapterAddressCount[3] = + { 0, BusLogic_MultiMasterAddressCount, BusLogic_FlashPointAddressCount }; + + +/* + Define macros for testing the Host Adapter Type. +*/ + +#ifndef CONFIG_SCSI_OMIT_FLASHPOINT + +#define BusLogic_MultiMasterHostAdapterP(HostAdapter) \ + (HostAdapter->HostAdapterType == BusLogic_MultiMaster) + +#define BusLogic_FlashPointHostAdapterP(HostAdapter) \ + (HostAdapter->HostAdapterType == BusLogic_FlashPoint) + +#else + +#define BusLogic_MultiMasterHostAdapterP(HostAdapter) \ + (true) + +#define BusLogic_FlashPointHostAdapterP(HostAdapter) \ + (false) + +#endif + + +/* + Define the possible Host Adapter Bus Types. +*/ + +typedef enum +{ + BusLogic_Unknown_Bus = 0, + BusLogic_ISA_Bus = 1, + BusLogic_EISA_Bus = 2, + BusLogic_PCI_Bus = 3, + BusLogic_VESA_Bus = 4, + BusLogic_MCA_Bus = 5 +} +__attribute__ ((packed)) +BusLogic_HostAdapterBusType_T; + +static char + *BusLogic_HostAdapterBusNames[] = + { "Unknown", "ISA", "EISA", "PCI", "VESA", "MCA" }; + +static BusLogic_HostAdapterBusType_T + BusLogic_HostAdapterBusTypes[] = + { BusLogic_VESA_Bus, /* BT-4xx */ + BusLogic_ISA_Bus, /* BT-5xx */ + BusLogic_MCA_Bus, /* BT-6xx */ + BusLogic_EISA_Bus, /* BT-7xx */ + BusLogic_Unknown_Bus, /* BT-8xx */ + BusLogic_PCI_Bus }; /* BT-9xx */ + + +/* + Define the possible Host Adapter BIOS Disk Geometry Translations. +*/ + +typedef enum BusLogic_BIOS_DiskGeometryTranslation +{ + BusLogic_BIOS_Disk_Not_Installed = 0, + BusLogic_BIOS_Disk_Installed_64x32 = 1, + BusLogic_BIOS_Disk_Installed_128x32 = 2, + BusLogic_BIOS_Disk_Installed_255x63 = 3 +} +__attribute__ ((packed)) +BusLogic_BIOS_DiskGeometryTranslation_T; + + +/* + Define a Boolean data type. +*/ + +typedef enum { false, true } __attribute__ ((packed)) boolean; + + +/* + Define a 32 bit I/O Address data type. +*/ + +typedef unsigned int BusLogic_IO_Address_T; + + +/* + Define a 32 bit PCI Bus Address data type. +*/ + +typedef unsigned int BusLogic_PCI_Address_T; + + +/* + Define a 32 bit Base Address data type. +*/ + +typedef unsigned int BusLogic_Base_Address_T; + + +/* + Define a 32 bit Bus Address data type. +*/ + +typedef unsigned int BusLogic_BusAddress_T; + + +/* + Define a 32 bit Byte Count data type. +*/ + +typedef unsigned int BusLogic_ByteCount_T; + + +/* + Define a 10^18 Statistics Byte Counter data type. +*/ + +typedef struct BusLogic_ByteCounter +{ + unsigned int Units; + unsigned int Billions; +} +BusLogic_ByteCounter_T; + + +/* + Define the structure for I/O Address and Bus Probing Information. +*/ + +typedef struct BusLogic_ProbeInfo +{ + BusLogic_HostAdapterType_T HostAdapterType; + BusLogic_HostAdapterBusType_T HostAdapterBusType; + BusLogic_IO_Address_T IO_Address; + BusLogic_PCI_Address_T PCI_Address; + unsigned char Bus; + unsigned char Device; + unsigned char IRQ_Channel; +} +BusLogic_ProbeInfo_T; + + +/* + Define the Probe Options. +*/ + +typedef struct BusLogic_ProbeOptions +{ + boolean NoProbe:1; /* Bit 0 */ + boolean NoProbeISA:1; /* Bit 1 */ + boolean NoProbePCI:1; /* Bit 2 */ + boolean NoSortPCI:1; /* Bit 3 */ + boolean MultiMasterFirst:1; /* Bit 4 */ + boolean FlashPointFirst:1; /* Bit 5 */ + boolean LimitedProbeISA:1; /* Bit 6 */ + boolean Probe330:1; /* Bit 7 */ + boolean Probe334:1; /* Bit 8 */ + boolean Probe230:1; /* Bit 9 */ + boolean Probe234:1; /* Bit 10 */ + boolean Probe130:1; /* Bit 11 */ + boolean Probe134:1; /* Bit 12 */ +} +BusLogic_ProbeOptions_T; + + +/* + Define the Global Options. +*/ + +typedef struct BusLogic_GlobalOptions +{ + boolean TraceProbe:1; /* Bit 0 */ + boolean TraceHardwareReset:1; /* Bit 1 */ + boolean TraceConfiguration:1; /* Bit 2 */ + boolean TraceErrors:1; /* Bit 3 */ +} +BusLogic_GlobalOptions_T; + + +/* + Define the Local Options. +*/ + +typedef struct BusLogic_LocalOptions +{ + boolean InhibitTargetInquiry:1; /* Bit 0 */ +} +BusLogic_LocalOptions_T; + + +/* + Define the Error Recovery Strategy Options. +*/ + +typedef enum +{ + BusLogic_ErrorRecovery_Default = 0, + BusLogic_ErrorRecovery_BusDeviceReset = 1, + BusLogic_ErrorRecovery_HardReset = 2, + BusLogic_ErrorRecovery_None = 3 +} +__attribute__ ((packed)) +BusLogic_ErrorRecoveryStrategy_T; + +static char + *BusLogic_ErrorRecoveryStrategyNames[] = + { "Default", "Bus Device Reset", "Hard Reset", "None" }, + BusLogic_ErrorRecoveryStrategyLetters[] = + { 'D', 'B', 'H', 'N' }; + + +/* + Define the BusLogic SCSI Host Adapter I/O Register Offsets. +*/ + +#define BusLogic_ControlRegisterOffset 0 /* WO register */ +#define BusLogic_StatusRegisterOffset 0 /* RO register */ +#define BusLogic_CommandParameterRegisterOffset 1 /* WO register */ +#define BusLogic_DataInRegisterOffset 1 /* RO register */ +#define BusLogic_InterruptRegisterOffset 2 /* RO register */ +#define BusLogic_GeometryRegisterOffset 3 /* RO register */ + + +/* + Define the structure of the write-only Control Register. +*/ + +typedef union BusLogic_ControlRegister +{ + unsigned char All; + struct { + unsigned char :4; /* Bits 0-3 */ + boolean SCSIBusReset:1; /* Bit 4 */ + boolean InterruptReset:1; /* Bit 5 */ + boolean SoftReset:1; /* Bit 6 */ + boolean HardReset:1; /* Bit 7 */ + } Bits; +} +BusLogic_ControlRegister_T; + + +/* + Define the structure of the read-only Status Register. +*/ + +typedef union BusLogic_StatusRegister +{ + unsigned char All; + struct { + boolean CommandInvalid:1; /* Bit 0 */ + boolean Reserved:1; /* Bit 1 */ + boolean DataInRegisterReady:1; /* Bit 2 */ + boolean CommandParameterRegisterBusy:1; /* Bit 3 */ + boolean HostAdapterReady:1; /* Bit 4 */ + boolean InitializationRequired:1; /* Bit 5 */ + boolean DiagnosticFailure:1; /* Bit 6 */ + boolean DiagnosticActive:1; /* Bit 7 */ + } Bits; +} +BusLogic_StatusRegister_T; + + +/* + Define the structure of the read-only Interrupt Register. +*/ + +typedef union BusLogic_InterruptRegister +{ + unsigned char All; + struct { + boolean IncomingMailboxLoaded:1; /* Bit 0 */ + boolean OutgoingMailboxAvailable:1; /* Bit 1 */ + boolean CommandComplete:1; /* Bit 2 */ + boolean ExternalBusReset:1; /* Bit 3 */ + unsigned char Reserved:3; /* Bits 4-6 */ + boolean InterruptValid:1; /* Bit 7 */ + } Bits; +} +BusLogic_InterruptRegister_T; + + +/* + Define the structure of the read-only Geometry Register. +*/ + +typedef union BusLogic_GeometryRegister +{ + unsigned char All; + struct { + BusLogic_BIOS_DiskGeometryTranslation_T Drive0Geometry:2; /* Bits 0-1 */ + BusLogic_BIOS_DiskGeometryTranslation_T Drive1Geometry:2; /* Bits 2-3 */ + unsigned char :3; /* Bits 4-6 */ + boolean ExtendedTranslationEnabled:1; /* Bit 7 */ + } Bits; +} +BusLogic_GeometryRegister_T; + + +/* + Define the BusLogic SCSI Host Adapter Command Register Operation Codes. +*/ + +typedef enum +{ + BusLogic_TestCommandCompleteInterrupt = 0x00, + BusLogic_InitializeMailbox = 0x01, + BusLogic_ExecuteMailboxCommand = 0x02, + BusLogic_ExecuteBIOSCommand = 0x03, + BusLogic_InquireBoardID = 0x04, + BusLogic_EnableOutgoingMailboxAvailableInt = 0x05, + BusLogic_SetSCSISelectionTimeout = 0x06, + BusLogic_SetPreemptTimeOnBus = 0x07, + BusLogic_SetTimeOffBus = 0x08, + BusLogic_SetBusTransferRate = 0x09, + BusLogic_InquireInstalledDevicesID0to7 = 0x0A, + BusLogic_InquireConfiguration = 0x0B, + BusLogic_EnableTargetMode = 0x0C, + BusLogic_InquireSetupInformation = 0x0D, + BusLogic_WriteAdapterLocalRAM = 0x1A, + BusLogic_ReadAdapterLocalRAM = 0x1B, + BusLogic_WriteBusMasterChipFIFO = 0x1C, + BusLogic_ReadBusMasterChipFIFO = 0x1D, + BusLogic_EchoCommandData = 0x1F, + BusLogic_HostAdapterDiagnostic = 0x20, + BusLogic_SetAdapterOptions = 0x21, + BusLogic_InquireInstalledDevicesID8to15 = 0x23, + BusLogic_InquireTargetDevices = 0x24, + BusLogic_DisableHostAdapterInterrupt = 0x25, + BusLogic_InitializeExtendedMailbox = 0x81, + BusLogic_ExecuteSCSICommand = 0x83, + BusLogic_InquireFirmwareVersion3rdDigit = 0x84, + BusLogic_InquireFirmwareVersionLetter = 0x85, + BusLogic_InquirePCIHostAdapterInformation = 0x86, + BusLogic_InquireHostAdapterModelNumber = 0x8B, + BusLogic_InquireSynchronousPeriod = 0x8C, + BusLogic_InquireExtendedSetupInformation = 0x8D, + BusLogic_EnableStrictRoundRobinMode = 0x8F, + BusLogic_StoreHostAdapterLocalRAM = 0x90, + BusLogic_FetchHostAdapterLocalRAM = 0x91, + BusLogic_StoreLocalDataInEEPROM = 0x92, + BusLogic_UploadAutoSCSICode = 0x94, + BusLogic_ModifyIOAddress = 0x95, + BusLogic_SetCCBFormat = 0x96, + BusLogic_WriteInquiryBuffer = 0x9A, + BusLogic_ReadInquiryBuffer = 0x9B, + BusLogic_FlashROMUploadDownload = 0xA7, + BusLogic_ReadSCAMData = 0xA8, + BusLogic_WriteSCAMData = 0xA9 +} +BusLogic_OperationCode_T; + + +/* + Define the Inquire Board ID reply structure. +*/ + +typedef struct BusLogic_BoardID +{ + unsigned char BoardType; /* Byte 0 */ + unsigned char CustomFeatures; /* Byte 1 */ + unsigned char FirmwareVersion1stDigit; /* Byte 2 */ + unsigned char FirmwareVersion2ndDigit; /* Byte 3 */ +} +BusLogic_BoardID_T; + + +/* + Define the Inquire Installed Devices ID 0 to 7 and Inquire Installed + Devices ID 8 to 15 reply type. For each Target Device, a byte is returned + where bit 0 set indicates that Logical Unit 0 exists, bit 1 set indicates + that Logical Unit 1 exists, and so on. +*/ + +typedef unsigned char BusLogic_InstalledDevices8_T[8]; + + +/* + Define the Inquire Target Devices reply type. Inquire Target Devices only + tests Logical Unit 0 of each Target Device unlike the Inquire Installed + Devices commands which test Logical Units 0 - 7. Two bytes are returned, + where byte 0 bit 0 set indicates that Target Device 0 exists, and so on. +*/ + +typedef unsigned short BusLogic_InstalledDevices_T; + + +/* + Define the Inquire Configuration reply structure. +*/ + +typedef struct BusLogic_Configuration +{ + unsigned char :5; /* Byte 0 Bits 0-4 */ + boolean DMA_Channel5:1; /* Byte 0 Bit 5 */ + boolean DMA_Channel6:1; /* Byte 0 Bit 6 */ + boolean DMA_Channel7:1; /* Byte 0 Bit 7 */ + boolean IRQ_Channel9:1; /* Byte 1 Bit 0 */ + boolean IRQ_Channel10:1; /* Byte 1 Bit 1 */ + boolean IRQ_Channel11:1; /* Byte 1 Bit 2 */ + boolean IRQ_Channel12:1; /* Byte 1 Bit 3 */ + unsigned char :1; /* Byte 1 Bit 4 */ + boolean IRQ_Channel14:1; /* Byte 1 Bit 5 */ + boolean IRQ_Channel15:1; /* Byte 1 Bit 6 */ + unsigned char :1; /* Byte 1 Bit 7 */ + unsigned char HostAdapterID:4; /* Byte 2 Bits 0-3 */ + unsigned char :4; /* Byte 2 Bits 4-7 */ +} +BusLogic_Configuration_T; + + +/* + Define the Inquire Setup Information reply structure. +*/ + +typedef struct BusLogic_SynchronousValue +{ + unsigned char Offset:4; /* Bits 0-3 */ + unsigned char TransferPeriod:3; /* Bits 4-6 */ + boolean Synchronous:1; /* Bit 7 */ +} +BusLogic_SynchronousValue_T; + +typedef BusLogic_SynchronousValue_T + BusLogic_SynchronousValues8_T[8]; + +typedef BusLogic_SynchronousValue_T + BusLogic_SynchronousValues_T[BusLogic_MaxTargetDevices]; + +typedef struct BusLogic_SetupInformation +{ + boolean SynchronousInitiationEnabled:1; /* Byte 0 Bit 0 */ + boolean ParityCheckingEnabled:1; /* Byte 0 Bit 1 */ + unsigned char :6; /* Byte 0 Bits 2-7 */ + unsigned char BusTransferRate; /* Byte 1 */ + unsigned char PreemptTimeOnBus; /* Byte 2 */ + unsigned char TimeOffBus; /* Byte 3 */ + unsigned char MailboxCount; /* Byte 4 */ + unsigned char MailboxAddress[3]; /* Bytes 5-7 */ + BusLogic_SynchronousValues8_T SynchronousValuesID0to7; /* Bytes 8-15 */ + unsigned char DisconnectPermittedID0to7; /* Byte 16 */ + unsigned char Signature; /* Byte 17 */ + unsigned char CharacterD; /* Byte 18 */ + unsigned char HostBusType; /* Byte 19 */ + unsigned char WideTransfersPermittedID0to7; /* Byte 20 */ + unsigned char WideTransfersActiveID0to7; /* Byte 21 */ + BusLogic_SynchronousValues8_T SynchronousValuesID8to15; /* Bytes 22-29 */ + unsigned char DisconnectPermittedID8to15; /* Byte 30 */ + unsigned char :8; /* Byte 31 */ + unsigned char WideTransfersPermittedID8to15; /* Byte 32 */ + unsigned char WideTransfersActiveID8to15; /* Byte 33 */ +} +BusLogic_SetupInformation_T; + + +/* + Define the Initialize Extended Mailbox request structure. +*/ + +typedef struct BusLogic_ExtendedMailboxRequest +{ + unsigned char MailboxCount; /* Byte 0 */ + BusLogic_BusAddress_T BaseMailboxAddress; /* Bytes 1-4 */ +} +__attribute__ ((packed)) +BusLogic_ExtendedMailboxRequest_T; + + +/* + Define the Inquire Firmware Version 3rd Digit reply type. +*/ + +typedef unsigned char BusLogic_FirmwareVersion3rdDigit_T; + + +/* + Define the Inquire Firmware Version Letter reply type. +*/ + +typedef unsigned char BusLogic_FirmwareVersionLetter_T; + + +/* + Define the Inquire PCI Host Adapter Information reply type. The ISA + Compatible I/O Port values are defined here and are also used with + the Modify I/O Address command. +*/ + +typedef enum BusLogic_ISACompatibleIOPort +{ + BusLogic_IO_330 = 0, + BusLogic_IO_334 = 1, + BusLogic_IO_230 = 2, + BusLogic_IO_234 = 3, + BusLogic_IO_130 = 4, + BusLogic_IO_134 = 5, + BusLogic_IO_Disable = 6, + BusLogic_IO_Disable2 = 7 +} +__attribute__ ((packed)) +BusLogic_ISACompatibleIOPort_T; + +typedef struct BusLogic_PCIHostAdapterInformation +{ + BusLogic_ISACompatibleIOPort_T ISACompatibleIOPort; /* Byte 0 */ + unsigned char PCIAssignedIRQChannel; /* Byte 1 */ + boolean LowByteTerminated:1; /* Byte 2 Bit 0 */ + boolean HighByteTerminated:1; /* Byte 2 Bit 1 */ + unsigned char :2; /* Byte 2 Bits 2-3 */ + boolean JP1:1; /* Byte 2 Bit 4 */ + boolean JP2:1; /* Byte 2 Bit 5 */ + boolean JP3:1; /* Byte 2 Bit 6 */ + boolean GenericInfoValid:1; /* Byte 2 Bit 7 */ + unsigned char :8; /* Byte 3 */ +} +BusLogic_PCIHostAdapterInformation_T; + + +/* + Define the Inquire Host Adapter Model Number reply type. +*/ + +typedef unsigned char BusLogic_HostAdapterModelNumber_T[5]; + + +/* + Define the Inquire Synchronous Period reply type. For each Target Device, + a byte is returned which represents the Synchronous Transfer Period in units + of 10 nanoseconds. +*/ + +typedef unsigned char BusLogic_SynchronousPeriod_T[BusLogic_MaxTargetDevices]; + + +/* + Define the Inquire Extended Setup Information reply structure. +*/ + +typedef struct BusLogic_ExtendedSetupInformation +{ + unsigned char BusType; /* Byte 0 */ + unsigned char BIOS_Address; /* Byte 1 */ + unsigned short ScatterGatherLimit; /* Bytes 2-3 */ + unsigned char MailboxCount; /* Byte 4 */ + BusLogic_BusAddress_T BaseMailboxAddress; /* Bytes 5-8 */ + struct { unsigned char :2; /* Byte 9 Bits 0-1 */ + boolean FastOnEISA:1; /* Byte 9 Bit 2 */ + unsigned char :3; /* Byte 9 Bits 3-5 */ + boolean LevelSensitiveInterrupt:1; /* Byte 9 Bit 6 */ + unsigned char :1; } Misc; /* Byte 9 Bit 7 */ + unsigned char FirmwareRevision[3]; /* Bytes 10-12 */ + boolean HostWideSCSI:1; /* Byte 13 Bit 0 */ + boolean HostDifferentialSCSI:1; /* Byte 13 Bit 1 */ + boolean HostSupportsSCAM:1; /* Byte 13 Bit 2 */ + boolean HostUltraSCSI:1; /* Byte 13 Bit 3 */ + boolean HostSmartTermination:1; /* Byte 13 Bit 4 */ + unsigned char :3; /* Byte 13 Bits 5-7 */ +} +__attribute__ ((packed)) +BusLogic_ExtendedSetupInformation_T; + + +/* + Define the Enable Strict Round Robin Mode request type. +*/ + +typedef enum BusLogic_RoundRobinModeRequest +{ + BusLogic_AggressiveRoundRobinMode = 0, + BusLogic_StrictRoundRobinMode = 1 +} +__attribute__ ((packed)) +BusLogic_RoundRobinModeRequest_T; + + +/* + Define the Fetch Host Adapter Local RAM request type. +*/ + +#define BusLogic_BIOS_BaseOffset 0 +#define BusLogic_AutoSCSI_BaseOffset 64 + +typedef struct BusLogic_FetchHostAdapterLocalRAMRequest +{ + unsigned char ByteOffset; /* Byte 0 */ + unsigned char ByteCount; /* Byte 1 */ +} +BusLogic_FetchHostAdapterLocalRAMRequest_T; + + +/* + Define the Host Adapter Local RAM AutoSCSI structure. +*/ + +typedef struct BusLogic_AutoSCSIData +{ + unsigned char InternalFactorySignature[2]; /* Bytes 0-1 */ + unsigned char InformationByteCount; /* Byte 2 */ + unsigned char HostAdapterType[6]; /* Bytes 3-8 */ + unsigned char :8; /* Byte 9 */ + boolean FloppyEnabled:1; /* Byte 10 Bit 0 */ + boolean FloppySecondary:1; /* Byte 10 Bit 1 */ + boolean LevelSensitiveInterrupt:1; /* Byte 10 Bit 2 */ + unsigned char :2; /* Byte 10 Bits 3-4 */ + unsigned char SystemRAMAreaForBIOS:3; /* Byte 10 Bits 5-7 */ + unsigned char DMA_Channel:7; /* Byte 11 Bits 0-6 */ + boolean DMA_AutoConfiguration:1; /* Byte 11 Bit 7 */ + unsigned char IRQ_Channel:7; /* Byte 12 Bits 0-6 */ + boolean IRQ_AutoConfiguration:1; /* Byte 12 Bit 7 */ + unsigned char DMA_TransferRate; /* Byte 13 */ + unsigned char SCSI_ID; /* Byte 14 */ + boolean LowByteTerminated:1; /* Byte 15 Bit 0 */ + boolean ParityCheckingEnabled:1; /* Byte 15 Bit 1 */ + boolean HighByteTerminated:1; /* Byte 15 Bit 2 */ + boolean NoisyCablingEnvironment:1; /* Byte 15 Bit 3 */ + boolean FastSynchronousNegotiation:1; /* Byte 15 Bit 4 */ + boolean BusResetEnabled:1; /* Byte 15 Bit 5 */ + boolean :1; /* Byte 15 Bit 6 */ + boolean ActiveNegationEnabled:1; /* Byte 15 Bit 7 */ + unsigned char BusOnDelay; /* Byte 16 */ + unsigned char BusOffDelay; /* Byte 17 */ + boolean HostAdapterBIOSEnabled:1; /* Byte 18 Bit 0 */ + boolean BIOSRedirectionOfINT19Enabled:1; /* Byte 18 Bit 1 */ + boolean ExtendedTranslationEnabled:1; /* Byte 18 Bit 2 */ + boolean MapRemovableAsFixedEnabled:1; /* Byte 18 Bit 3 */ + boolean :1; /* Byte 18 Bit 4 */ + boolean BIOSSupportsMoreThan2DrivesEnabled:1; /* Byte 18 Bit 5 */ + boolean BIOSInterruptModeEnabled:1; /* Byte 18 Bit 6 */ + boolean FlopticalSupportEnabled:1; /* Byte 19 Bit 7 */ + unsigned short DeviceEnabled; /* Bytes 19-20 */ + unsigned short WidePermitted; /* Bytes 21-22 */ + unsigned short FastPermitted; /* Bytes 23-24 */ + unsigned short SynchronousPermitted; /* Bytes 25-26 */ + unsigned short DisconnectPermitted; /* Bytes 27-28 */ + unsigned short SendStartUnitCommand; /* Bytes 29-30 */ + unsigned short IgnoreInBIOSScan; /* Bytes 31-32 */ + unsigned char PCIInterruptPin:2; /* Byte 33 Bits 0-1 */ + unsigned char HostAdapterIOPortAddress:2; /* Byte 33 Bits 2-3 */ + boolean StrictRoundRobinModeEnabled:1; /* Byte 33 Bit 4 */ + boolean VESABusSpeedGreaterThan33MHz:1; /* Byte 33 Bit 5 */ + boolean VESABurstWriteEnabled:1; /* Byte 33 Bit 6 */ + boolean VESABurstReadEnabled:1; /* Byte 33 Bit 7 */ + unsigned short UltraPermitted; /* Bytes 34-35 */ + unsigned int :32; /* Bytes 36-39 */ + unsigned char :8; /* Byte 40 */ + unsigned char AutoSCSIMaximumLUN; /* Byte 41 */ + boolean :1; /* Byte 42 Bit 0 */ + boolean SCAM_Dominant:1; /* Byte 42 Bit 1 */ + boolean SCAM_Enabled:1; /* Byte 42 Bit 2 */ + boolean SCAM_Level2:1; /* Byte 42 Bit 3 */ + unsigned char :4; /* Byte 42 Bits 4-7 */ + boolean INT13ExtensionEnabled:1; /* Byte 43 Bit 0 */ + boolean :1; /* Byte 43 Bit 1 */ + boolean CDROMBootEnabled:1; /* Byte 43 Bit 2 */ + unsigned char :5; /* Byte 43 Bits 3-7 */ + unsigned char BootTargetID:4; /* Byte 44 Bits 0-3 */ + unsigned char BootChannel:4; /* Byte 44 Bits 4-7 */ + unsigned char ForceBusDeviceScanningOrder:1; /* Byte 45 Bit 0 */ + unsigned char :7; /* Byte 45 Bits 1-7 */ + unsigned short NonTaggedToAlternateLUNPermitted; /* Bytes 46-47 */ + unsigned short RenegotiateSyncAfterCheckCondition; /* Bytes 48-49 */ + unsigned char Reserved[10]; /* Bytes 50-59 */ + unsigned char ManufacturingDiagnostic[2]; /* Bytes 60-61 */ + unsigned short Checksum; /* Bytes 62-63 */ +} +__attribute__ ((packed)) +BusLogic_AutoSCSIData_T; + + +/* + Define the Host Adapter Local RAM Auto SCSI Byte 45 structure. +*/ + +typedef struct BusLogic_AutoSCSIByte45 +{ + unsigned char ForceBusDeviceScanningOrder:1; /* Bit 0 */ + unsigned char :7; /* Bits 1-7 */ +} +BusLogic_AutoSCSIByte45_T; + + +/* + Define the Host Adapter Local RAM BIOS Drive Map Byte structure. +*/ + +#define BusLogic_BIOS_DriveMapOffset 17 + +typedef struct BusLogic_BIOSDriveMapByte +{ + unsigned char TargetIDBit3:1; /* Bit 0 */ + unsigned char :2; /* Bits 1-2 */ + BusLogic_BIOS_DiskGeometryTranslation_T DiskGeometry:2; /* Bits 3-4 */ + unsigned char TargetID:3; /* Bits 5-7 */ +} +BusLogic_BIOSDriveMapByte_T; + + +/* + Define the Modify I/O Address request type. On PCI Host Adapters, the + Modify I/O Address command allows modification of the ISA compatible I/O + Address that the Host Adapter responds to; it does not affect the PCI + compliant I/O Address assigned at system initialization. +*/ + +typedef BusLogic_ISACompatibleIOPort_T BusLogic_ModifyIOAddressRequest_T; + + +/* + Define the Set CCB Format request type. Extended LUN Format CCBs are + necessary to support more than 8 Logical Units per Target Device. +*/ + +typedef enum BusLogic_SetCCBFormatRequest +{ + BusLogic_LegacyLUNFormatCCB = 0, + BusLogic_ExtendedLUNFormatCCB = 1 +} +__attribute__ ((packed)) +BusLogic_SetCCBFormatRequest_T; + + +/* + Define the Requested Reply Length type used by the Inquire Setup Information, + Inquire Host Adapter Model Number, Inquire Synchronous Period, and Inquire + Extended Setup Information commands. +*/ + +typedef unsigned char BusLogic_RequestedReplyLength_T; + + +/* + Define the Outgoing Mailbox Action Codes. +*/ + +typedef enum +{ + BusLogic_OutgoingMailboxFree = 0x00, + BusLogic_MailboxStartCommand = 0x01, + BusLogic_MailboxAbortCommand = 0x02 +} +__attribute__ ((packed)) +BusLogic_ActionCode_T; + + +/* + Define the Incoming Mailbox Completion Codes. The MultiMaster Firmware + only uses codes 0 - 4. The FlashPoint SCCB Manager has no mailboxes, so + completion codes are stored in the CCB; it only uses codes 1, 2, 4, and 5. +*/ + +typedef enum +{ + BusLogic_IncomingMailboxFree = 0x00, + BusLogic_CommandCompletedWithoutError = 0x01, + BusLogic_CommandAbortedAtHostRequest = 0x02, + BusLogic_AbortedCommandNotFound = 0x03, + BusLogic_CommandCompletedWithError = 0x04, + BusLogic_InvalidCCB = 0x05 +} +__attribute__ ((packed)) +BusLogic_CompletionCode_T; + + +/* + Define the Command Control Block (CCB) Opcodes. +*/ + +typedef enum +{ + BusLogic_InitiatorCCB = 0x00, + BusLogic_TargetCCB = 0x01, + BusLogic_InitiatorCCB_ScatterGather = 0x02, + BusLogic_InitiatorCCB_ResidualDataLength = 0x03, + BusLogic_InitiatorCCB_ScatterGatherResidual = 0x04, + BusLogic_BusDeviceReset = 0x81 +} +__attribute__ ((packed)) +BusLogic_CCB_Opcode_T; + + +/* + Define the CCB Data Direction Codes. +*/ + +typedef enum +{ + BusLogic_UncheckedDataTransfer = 0, + BusLogic_DataInLengthChecked = 1, + BusLogic_DataOutLengthChecked = 2, + BusLogic_NoDataTransfer = 3 +} +BusLogic_DataDirection_T; + + +/* + Define the Host Adapter Status Codes. The MultiMaster Firmware does not + return status code 0x0C; it uses 0x12 for both overruns and underruns. +*/ + +typedef enum +{ + BusLogic_CommandCompletedNormally = 0x00, + BusLogic_LinkedCommandCompleted = 0x0A, + BusLogic_LinkedCommandCompletedWithFlag = 0x0B, + BusLogic_DataUnderRun = 0x0C, + BusLogic_SCSISelectionTimeout = 0x11, + BusLogic_DataOverRun = 0x12, + BusLogic_UnexpectedBusFree = 0x13, + BusLogic_InvalidBusPhaseRequested = 0x14, + BusLogic_InvalidOutgoingMailboxActionCode = 0x15, + BusLogic_InvalidCommandOperationCode = 0x16, + BusLogic_LinkedCCBhasInvalidLUN = 0x17, + BusLogic_InvalidCommandParameter = 0x1A, + BusLogic_AutoRequestSenseFailed = 0x1B, + BusLogic_TaggedQueuingMessageRejected = 0x1C, + BusLogic_UnsupportedMessageReceived = 0x1D, + BusLogic_HostAdapterHardwareFailed = 0x20, + BusLogic_TargetFailedResponseToATN = 0x21, + BusLogic_HostAdapterAssertedRST = 0x22, + BusLogic_OtherDeviceAssertedRST = 0x23, + BusLogic_TargetDeviceReconnectedImproperly = 0x24, + BusLogic_HostAdapterAssertedBusDeviceReset = 0x25, + BusLogic_AbortQueueGenerated = 0x26, + BusLogic_HostAdapterSoftwareError = 0x27, + BusLogic_HostAdapterHardwareTimeoutError = 0x30, + BusLogic_SCSIParityErrorDetected = 0x34 +} +__attribute__ ((packed)) +BusLogic_HostAdapterStatus_T; + + +/* + Define the SCSI Target Device Status Codes. +*/ + +typedef enum +{ + BusLogic_OperationGood = 0x00, + BusLogic_CheckCondition = 0x02, + BusLogic_DeviceBusy = 0x08 +} +__attribute__ ((packed)) +BusLogic_TargetDeviceStatus_T; + + +/* + Define the Queue Tag Codes. +*/ + +typedef enum +{ + BusLogic_SimpleQueueTag = 0, + BusLogic_HeadOfQueueTag = 1, + BusLogic_OrderedQueueTag = 2, + BusLogic_ReservedQT = 3 +} +BusLogic_QueueTag_T; + + +/* + Define the SCSI Command Descriptor Block (CDB). +*/ + +#define BusLogic_CDB_MaxLength 12 + +typedef unsigned char SCSI_CDB_T[BusLogic_CDB_MaxLength]; + + +/* + Define the Scatter/Gather Segment structure required by the MultiMaster + Firmware Interface and the FlashPoint SCCB Manager. +*/ + +typedef struct BusLogic_ScatterGatherSegment +{ + BusLogic_ByteCount_T SegmentByteCount; /* Bytes 0-3 */ + BusLogic_BusAddress_T SegmentDataPointer; /* Bytes 4-7 */ +} +BusLogic_ScatterGatherSegment_T; + + +/* + Define the Driver CCB Status Codes. +*/ + +typedef enum +{ + BusLogic_CCB_Free = 0, + BusLogic_CCB_Active = 1, + BusLogic_CCB_Completed = 2, + BusLogic_CCB_Reset = 3 +} +__attribute__ ((packed)) +BusLogic_CCB_Status_T; + + +/* + Define the 32 Bit Mode Command Control Block (CCB) structure. The first 40 + bytes are defined by and common to both the MultiMaster Firmware and the + FlashPoint SCCB Manager. The next 60 bytes are defined by the FlashPoint + SCCB Manager. The remaining components are defined by the Linux BusLogic + Driver. Extended LUN Format CCBs differ from Legacy LUN Format 32 Bit Mode + CCBs only in having the TagEnable and QueueTag fields moved from byte 17 to + byte 1, and the Logical Unit field in byte 17 expanded to 6 bits. In theory, + Extended LUN Format CCBs can support up to 64 Logical Units, but in practice + many devices will respond improperly to Logical Units between 32 and 63, and + the SCSI-2 specification defines Bit 5 as LUNTAR. Extended LUN Format CCBs + are used by recent versions of the MultiMaster Firmware, as well as by the + FlashPoint SCCB Manager; the FlashPoint SCCB Manager only supports 32 Logical + Units. Since 64 Logical Units are unlikely to be needed in practice, and + since they are problematic for the above reasons, and since limiting them to + 5 bits simplifies the CCB structure definition, this driver only supports + 32 Logical Units per Target Device. +*/ + +typedef struct BusLogic_CCB +{ + /* + MultiMaster Firmware and FlashPoint SCCB Manager Common Portion. + */ + BusLogic_CCB_Opcode_T Opcode; /* Byte 0 */ + unsigned char :3; /* Byte 1 Bits 0-2 */ + BusLogic_DataDirection_T DataDirection:2; /* Byte 1 Bits 3-4 */ + boolean TagEnable:1; /* Byte 1 Bit 5 */ + BusLogic_QueueTag_T QueueTag:2; /* Byte 1 Bits 6-7 */ + unsigned char CDB_Length; /* Byte 2 */ + unsigned char SenseDataLength; /* Byte 3 */ + BusLogic_ByteCount_T DataLength; /* Bytes 4-7 */ + BusLogic_BusAddress_T DataPointer; /* Bytes 8-11 */ + unsigned char :8; /* Byte 12 */ + unsigned char :8; /* Byte 13 */ + BusLogic_HostAdapterStatus_T HostAdapterStatus; /* Byte 14 */ + BusLogic_TargetDeviceStatus_T TargetDeviceStatus; /* Byte 15 */ + unsigned char TargetID; /* Byte 16 */ + unsigned char LogicalUnit:5; /* Byte 17 Bits 0-4 */ + boolean LegacyTagEnable:1; /* Byte 17 Bit 5 */ + BusLogic_QueueTag_T LegacyQueueTag:2; /* Byte 17 Bits 6-7 */ + SCSI_CDB_T CDB; /* Bytes 18-29 */ + unsigned char :8; /* Byte 30 */ + unsigned char :8; /* Byte 31 */ + unsigned int :32; /* Bytes 32-35 */ + BusLogic_BusAddress_T SenseDataPointer; /* Bytes 36-39 */ + /* + FlashPoint SCCB Manager Defined Portion. + */ + void (*CallbackFunction)(struct BusLogic_CCB *); /* Bytes 40-43 */ + BusLogic_Base_Address_T BaseAddress; /* Bytes 44-47 */ + BusLogic_CompletionCode_T CompletionCode; /* Byte 48 */ +#ifndef CONFIG_SCSI_OMIT_FLASHPOINT + unsigned char :8; /* Byte 49 */ + unsigned short OS_Flags; /* Bytes 50-51 */ + unsigned char Private[48]; /* Bytes 52-99 */ +#endif + /* + BusLogic Linux Driver Defined Portion. + */ + boolean AllocationGroupHead; + BusLogic_CCB_Status_T Status; + unsigned long SerialNumber; + SCSI_Command_T *Command; + struct BusLogic_HostAdapter *HostAdapter; + struct BusLogic_CCB *Next; + struct BusLogic_CCB *NextAll; + BusLogic_ScatterGatherSegment_T + ScatterGatherList[BusLogic_ScatterGatherLimit]; +} +BusLogic_CCB_T; + + +/* + Define the 32 Bit Mode Outgoing Mailbox structure. +*/ + +typedef struct BusLogic_OutgoingMailbox +{ + BusLogic_BusAddress_T CCB; /* Bytes 0-3 */ + unsigned int :24; /* Bytes 4-6 */ + BusLogic_ActionCode_T ActionCode; /* Byte 7 */ +} +BusLogic_OutgoingMailbox_T; + + +/* + Define the 32 Bit Mode Incoming Mailbox structure. +*/ + +typedef struct BusLogic_IncomingMailbox +{ + BusLogic_BusAddress_T CCB; /* Bytes 0-3 */ + BusLogic_HostAdapterStatus_T HostAdapterStatus; /* Byte 4 */ + BusLogic_TargetDeviceStatus_T TargetDeviceStatus; /* Byte 5 */ + unsigned char :8; /* Byte 6 */ + BusLogic_CompletionCode_T CompletionCode; /* Byte 7 */ +} +BusLogic_IncomingMailbox_T; + + +/* + Define the BusLogic Driver Options structure. +*/ + +typedef struct BusLogic_DriverOptions +{ + unsigned short TaggedQueuingPermitted; + unsigned short TaggedQueuingPermittedMask; + unsigned short BusSettleTime; + BusLogic_LocalOptions_T LocalOptions; + unsigned char CommonQueueDepth; + unsigned char QueueDepth[BusLogic_MaxTargetDevices]; + BusLogic_ErrorRecoveryStrategy_T + ErrorRecoveryStrategy[BusLogic_MaxTargetDevices]; +} +BusLogic_DriverOptions_T; + + +/* + Define the Host Adapter Target Flags structure. +*/ + +typedef struct BusLogic_TargetFlags +{ + boolean TargetExists:1; + boolean TaggedQueuingSupported:1; + boolean WideTransfersSupported:1; + boolean TaggedQueuingActive:1; + boolean WideTransfersActive:1; + boolean CommandSuccessfulFlag:1; + boolean TargetInfoReported:1; +} +BusLogic_TargetFlags_T; + + +/* + Define the Host Adapter Target Statistics structure. +*/ + +#define BusLogic_SizeBuckets 10 + +typedef unsigned int BusLogic_CommandSizeBuckets_T[BusLogic_SizeBuckets]; + +typedef struct BusLogic_TargetStatistics +{ + unsigned int CommandsAttempted; + unsigned int CommandsCompleted; + unsigned int ReadCommands; + unsigned int WriteCommands; + BusLogic_ByteCounter_T TotalBytesRead; + BusLogic_ByteCounter_T TotalBytesWritten; + BusLogic_CommandSizeBuckets_T ReadCommandSizeBuckets; + BusLogic_CommandSizeBuckets_T WriteCommandSizeBuckets; + unsigned short CommandAbortsRequested; + unsigned short CommandAbortsAttempted; + unsigned short CommandAbortsCompleted; + unsigned short BusDeviceResetsRequested; + unsigned short BusDeviceResetsAttempted; + unsigned short BusDeviceResetsCompleted; + unsigned short HostAdapterResetsRequested; + unsigned short HostAdapterResetsAttempted; + unsigned short HostAdapterResetsCompleted; +} +BusLogic_TargetStatistics_T; + + +/* + Define the FlashPoint Card Handle data type. +*/ + +#define FlashPoint_BadCardHandle 0xFFFFFFFF + +typedef unsigned int FlashPoint_CardHandle_T; + + +/* + Define the FlashPoint Information structure. This structure is defined + by the FlashPoint SCCB Manager. +*/ + +typedef struct FlashPoint_Info +{ + BusLogic_Base_Address_T BaseAddress; /* Bytes 0-3 */ + boolean Present; /* Byte 4 */ + unsigned char IRQ_Channel; /* Byte 5 */ + unsigned char SCSI_ID; /* Byte 6 */ + unsigned char SCSI_LUN; /* Byte 7 */ + unsigned short FirmwareRevision; /* Bytes 8-9 */ + unsigned short SynchronousPermitted; /* Bytes 10-11 */ + unsigned short FastPermitted; /* Bytes 12-13 */ + unsigned short UltraPermitted; /* Bytes 14-15 */ + unsigned short DisconnectPermitted; /* Bytes 16-17 */ + unsigned short WidePermitted; /* Bytes 18-19 */ + boolean ParityCheckingEnabled:1; /* Byte 20 Bit 0 */ + boolean HostWideSCSI:1; /* Byte 20 Bit 1 */ + boolean HostSoftReset:1; /* Byte 20 Bit 2 */ + boolean ExtendedTranslationEnabled:1; /* Byte 20 Bit 3 */ + boolean LowByteTerminated:1; /* Byte 20 Bit 4 */ + boolean HighByteTerminated:1; /* Byte 20 Bit 5 */ + boolean ReportDataUnderrun:1; /* Byte 20 Bit 6 */ + boolean SCAM_Enabled:1; /* Byte 20 Bit 7 */ + boolean SCAM_Level2:1; /* Byte 21 Bit 0 */ + unsigned char :7; /* Byte 21 Bits 1-7 */ + unsigned char Family; /* Byte 22 */ + unsigned char BusType; /* Byte 23 */ + unsigned char ModelNumber[3]; /* Bytes 24-26 */ + unsigned char RelativeCardNumber; /* Byte 27 */ + unsigned char Reserved[4]; /* Bytes 28-31 */ + unsigned int OS_Reserved; /* Bytes 32-35 */ + unsigned char TranslationInfo[4]; /* Bytes 36-39 */ + unsigned int Reserved2[5]; /* Bytes 40-59 */ + unsigned int SecondaryRange; /* Bytes 60-63 */ +} +FlashPoint_Info_T; + + +/* + Define the BusLogic Driver Host Adapter structure. +*/ + +typedef struct BusLogic_HostAdapter +{ + SCSI_Host_T *SCSI_Host; + BusLogic_HostAdapterType_T HostAdapterType; + BusLogic_HostAdapterBusType_T HostAdapterBusType; + BusLogic_IO_Address_T IO_Address; + BusLogic_PCI_Address_T PCI_Address; + unsigned short AddressCount; + unsigned char HostNumber; + unsigned char ModelName[9]; + unsigned char FirmwareVersion[6]; + unsigned char FullModelName[18]; + unsigned char Bus; + unsigned char Device; + unsigned char IRQ_Channel; + unsigned char DMA_Channel; + unsigned char SCSI_ID; + boolean IRQ_ChannelAcquired:1; + boolean DMA_ChannelAcquired:1; + boolean ExtendedTranslationEnabled:1; + boolean ParityCheckingEnabled:1; + boolean BusResetEnabled:1; + boolean LevelSensitiveInterrupt:1; + boolean HostWideSCSI:1; + boolean HostDifferentialSCSI:1; + boolean HostSupportsSCAM:1; + boolean HostUltraSCSI:1; + boolean ExtendedLUNSupport:1; + boolean TerminationInfoValid:1; + boolean LowByteTerminated:1; + boolean HighByteTerminated:1; + boolean BounceBuffersRequired:1; + boolean StrictRoundRobinModeSupport:1; + boolean SCAM_Enabled:1; + boolean SCAM_Level2:1; + boolean HostAdapterInitialized:1; + boolean HostAdapterExternalReset:1; + boolean HostAdapterInternalError:1; + boolean ProcessCompletedCCBsActive; + volatile boolean HostAdapterCommandCompleted; + unsigned short HostAdapterScatterGatherLimit; + unsigned short DriverScatterGatherLimit; + unsigned short MaxTargetDevices; + unsigned short MaxLogicalUnits; + unsigned short MailboxCount; + unsigned short InitialCCBs; + unsigned short IncrementalCCBs; + unsigned short AllocatedCCBs; + unsigned short DriverQueueDepth; + unsigned short HostAdapterQueueDepth; + unsigned short UntaggedQueueDepth; + unsigned short CommonQueueDepth; + unsigned short BusSettleTime; + unsigned short SynchronousPermitted; + unsigned short FastPermitted; + unsigned short UltraPermitted; + unsigned short WidePermitted; + unsigned short DisconnectPermitted; + unsigned short TaggedQueuingPermitted; + unsigned short ExternalHostAdapterResets; + unsigned short HostAdapterInternalErrors; + unsigned short TargetDeviceCount; + unsigned short MessageBufferLength; + BusLogic_BusAddress_T BIOS_Address; + BusLogic_DriverOptions_T *DriverOptions; + FlashPoint_Info_T FlashPointInfo; + FlashPoint_CardHandle_T CardHandle; + struct BusLogic_HostAdapter *Next; + BusLogic_CCB_T *All_CCBs; + BusLogic_CCB_T *Free_CCBs; + BusLogic_CCB_T *FirstCompletedCCB; + BusLogic_CCB_T *LastCompletedCCB; + BusLogic_CCB_T *BusDeviceResetPendingCCB[BusLogic_MaxTargetDevices]; + BusLogic_ErrorRecoveryStrategy_T + ErrorRecoveryStrategy[BusLogic_MaxTargetDevices]; + BusLogic_TargetFlags_T TargetFlags[BusLogic_MaxTargetDevices]; + unsigned char QueueDepth[BusLogic_MaxTargetDevices]; + unsigned char SynchronousPeriod[BusLogic_MaxTargetDevices]; + unsigned char SynchronousOffset[BusLogic_MaxTargetDevices]; + unsigned char ActiveCommands[BusLogic_MaxTargetDevices]; + unsigned int CommandsSinceReset[BusLogic_MaxTargetDevices]; + unsigned long LastSequencePoint[BusLogic_MaxTargetDevices]; + unsigned long LastResetAttempted[BusLogic_MaxTargetDevices]; + unsigned long LastResetCompleted[BusLogic_MaxTargetDevices]; + BusLogic_OutgoingMailbox_T *FirstOutgoingMailbox; + BusLogic_OutgoingMailbox_T *LastOutgoingMailbox; + BusLogic_OutgoingMailbox_T *NextOutgoingMailbox; + BusLogic_IncomingMailbox_T *FirstIncomingMailbox; + BusLogic_IncomingMailbox_T *LastIncomingMailbox; + BusLogic_IncomingMailbox_T *NextIncomingMailbox; + BusLogic_TargetStatistics_T TargetStatistics[BusLogic_MaxTargetDevices]; + unsigned char MailboxSpace[BusLogic_MaxMailboxes + * (sizeof(BusLogic_OutgoingMailbox_T) + + sizeof(BusLogic_IncomingMailbox_T))]; + char MessageBuffer[BusLogic_MessageBufferSize]; +} +BusLogic_HostAdapter_T; + + +/* + Define a structure for the BIOS Disk Parameters. +*/ + +typedef struct BIOS_DiskParameters +{ + int Heads; + int Sectors; + int Cylinders; +} +BIOS_DiskParameters_T; + + +/* + Define a structure for the SCSI Inquiry command results. +*/ + +typedef struct SCSI_Inquiry +{ + unsigned char PeripheralDeviceType:5; /* Byte 0 Bits 0-4 */ + unsigned char PeripheralQualifier:3; /* Byte 0 Bits 5-7 */ + unsigned char DeviceTypeModifier:7; /* Byte 1 Bits 0-6 */ + boolean RMB:1; /* Byte 1 Bit 7 */ + unsigned char ANSI_ApprovedVersion:3; /* Byte 2 Bits 0-2 */ + unsigned char ECMA_Version:3; /* Byte 2 Bits 3-5 */ + unsigned char ISO_Version:2; /* Byte 2 Bits 6-7 */ + unsigned char ResponseDataFormat:4; /* Byte 3 Bits 0-3 */ + unsigned char :2; /* Byte 3 Bits 4-5 */ + boolean TrmIOP:1; /* Byte 3 Bit 6 */ + boolean AENC:1; /* Byte 3 Bit 7 */ + unsigned char AdditionalLength; /* Byte 4 */ + unsigned char :8; /* Byte 5 */ + unsigned char :8; /* Byte 6 */ + boolean SftRe:1; /* Byte 7 Bit 0 */ + boolean CmdQue:1; /* Byte 7 Bit 1 */ + boolean :1; /* Byte 7 Bit 2 */ + boolean Linked:1; /* Byte 7 Bit 3 */ + boolean Sync:1; /* Byte 7 Bit 4 */ + boolean WBus16:1; /* Byte 7 Bit 5 */ + boolean WBus32:1; /* Byte 7 Bit 6 */ + boolean RelAdr:1; /* Byte 7 Bit 7 */ + unsigned char VendorIdentification[8]; /* Bytes 8-15 */ + unsigned char ProductIdentification[16]; /* Bytes 16-31 */ + unsigned char ProductRevisionLevel[4]; /* Bytes 32-35 */ +} +SCSI_Inquiry_T; + + +/* + BusLogic_AcquireHostAdapterLock acquires exclusive access to Host Adapter. +*/ + +static inline +void BusLogic_AcquireHostAdapterLock(BusLogic_HostAdapter_T *HostAdapter, + ProcessorFlags_T *ProcessorFlags) +{ + save_flags(*ProcessorFlags); + cli(); +} + + +/* + BusLogic_ReleaseHostAdapterLock releases exclusive access to Host Adapter. +*/ + +static inline +void BusLogic_ReleaseHostAdapterLock(BusLogic_HostAdapter_T *HostAdapter, + ProcessorFlags_T *ProcessorFlags) +{ + restore_flags(*ProcessorFlags); +} + + +/* + BusLogic_AcquireHostAdapterLockIH acquires exclusive access to Host Adapter, + but is only called from the interrupt handler when interrupts are disabled. +*/ + +static inline +void BusLogic_AcquireHostAdapterLockIH(BusLogic_HostAdapter_T *HostAdapter, + ProcessorFlags_T *ProcessorFlags) +{ +} + + +/* + BusLogic_ReleaseHostAdapterLockIH releases exclusive access to Host Adapter, + but is only called from the interrupt handler when interrupts are disabled. +*/ + +static inline +void BusLogic_ReleaseHostAdapterLockIH(BusLogic_HostAdapter_T *HostAdapter, + ProcessorFlags_T *ProcessorFlags) +{ +} + + +/* + Define functions to provide an abstraction for reading and writing the + Host Adapter I/O Registers. +*/ + +static inline +void BusLogic_SCSIBusReset(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_ControlRegister_T ControlRegister; + ControlRegister.All = 0; + ControlRegister.Bits.SCSIBusReset = true; + outb(ControlRegister.All, + HostAdapter->IO_Address + BusLogic_ControlRegisterOffset); +} + +static inline +void BusLogic_InterruptReset(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_ControlRegister_T ControlRegister; + ControlRegister.All = 0; + ControlRegister.Bits.InterruptReset = true; + outb(ControlRegister.All, + HostAdapter->IO_Address + BusLogic_ControlRegisterOffset); +} + +static inline +void BusLogic_SoftReset(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_ControlRegister_T ControlRegister; + ControlRegister.All = 0; + ControlRegister.Bits.SoftReset = true; + outb(ControlRegister.All, + HostAdapter->IO_Address + BusLogic_ControlRegisterOffset); +} + +static inline +void BusLogic_HardReset(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_ControlRegister_T ControlRegister; + ControlRegister.All = 0; + ControlRegister.Bits.HardReset = true; + outb(ControlRegister.All, + HostAdapter->IO_Address + BusLogic_ControlRegisterOffset); +} + +static inline +unsigned char BusLogic_ReadStatusRegister(BusLogic_HostAdapter_T *HostAdapter) +{ + return inb(HostAdapter->IO_Address + BusLogic_StatusRegisterOffset); +} + +static inline +void BusLogic_WriteCommandParameterRegister(BusLogic_HostAdapter_T + *HostAdapter, + unsigned char Value) +{ + outb(Value, + HostAdapter->IO_Address + BusLogic_CommandParameterRegisterOffset); +} + +static inline +unsigned char BusLogic_ReadDataInRegister(BusLogic_HostAdapter_T *HostAdapter) +{ + return inb(HostAdapter->IO_Address + BusLogic_DataInRegisterOffset); +} + +static inline +unsigned char BusLogic_ReadInterruptRegister(BusLogic_HostAdapter_T + *HostAdapter) +{ + return inb(HostAdapter->IO_Address + BusLogic_InterruptRegisterOffset); +} + +static inline +unsigned char BusLogic_ReadGeometryRegister(BusLogic_HostAdapter_T + *HostAdapter) +{ + return inb(HostAdapter->IO_Address + BusLogic_GeometryRegisterOffset); +} + + +/* + BusLogic_StartMailboxCommand issues an Execute Mailbox Command, which + notifies the Host Adapter that an entry has been made in an Outgoing + Mailbox. +*/ + +static inline +void BusLogic_StartMailboxCommand(BusLogic_HostAdapter_T *HostAdapter) +{ + BusLogic_WriteCommandParameterRegister(HostAdapter, + BusLogic_ExecuteMailboxCommand); +} + + +/* + BusLogic_Delay waits for Seconds to elapse. +*/ + +static inline void BusLogic_Delay(int Seconds) +{ + int Milliseconds = 1000 * Seconds; + unsigned long ProcessorFlags; + save_flags(ProcessorFlags); + sti(); + while (--Milliseconds >= 0) udelay(1000); + restore_flags(ProcessorFlags); +} + + +/* + Virtual_to_Bus and Bus_to_Virtual map between Kernel Virtual Addresses + and PCI/VLB/EISA/ISA Bus Addresses. +*/ + +static inline BusLogic_BusAddress_T Virtual_to_Bus(void *VirtualAddress) +{ + return (BusLogic_BusAddress_T) virt_to_bus(VirtualAddress); +} + +static inline void *Bus_to_Virtual(BusLogic_BusAddress_T BusAddress) +{ + return (void *) bus_to_virt(BusAddress); +} + + +/* + Virtual_to_32Bit_Virtual maps between Kernel Virtual Addresses and + 32 bit Kernel Virtual Addresses. This avoids compilation warnings + on 64 bit architectures. +*/ + +static inline +BusLogic_BusAddress_T Virtual_to_32Bit_Virtual(void *VirtualAddress) +{ + return (BusLogic_BusAddress_T) (unsigned long) VirtualAddress; +} + + +/* + BusLogic_IncrementErrorCounter increments Error Counter by 1, stopping at + 65535 rather than wrapping around to 0. +*/ + +static inline void BusLogic_IncrementErrorCounter(unsigned short *ErrorCounter) +{ + if (*ErrorCounter < 65535) (*ErrorCounter)++; +} + + +/* + BusLogic_IncrementByteCounter increments Byte Counter by Amount. +*/ + +static inline void BusLogic_IncrementByteCounter(BusLogic_ByteCounter_T + *ByteCounter, + unsigned int Amount) +{ + ByteCounter->Units += Amount; + if (ByteCounter->Units > 999999999) + { + ByteCounter->Units -= 1000000000; + ByteCounter->Billions++; + } +} + + +/* + BusLogic_IncrementSizeBucket increments the Bucket for Amount. +*/ + +static inline void BusLogic_IncrementSizeBucket(BusLogic_CommandSizeBuckets_T + CommandSizeBuckets, + unsigned int Amount) +{ + int Index = 0; + if (Amount < 8*1024) + { + if (Amount < 2*1024) + Index = (Amount < 1*1024 ? 0 : 1); + else Index = (Amount < 4*1024 ? 2 : 3); + } + else if (Amount < 128*1024) + { + if (Amount < 32*1024) + Index = (Amount < 16*1024 ? 4 : 5); + else Index = (Amount < 64*1024 ? 6 : 7); + } + else Index = (Amount < 256*1024 ? 8 : 9); + CommandSizeBuckets[Index]++; +} + + +/* + Define the version number of the FlashPoint Firmware (SCCB Manager). +*/ + +#define FlashPoint_FirmwareVersion "5.02" + + +/* + Define the possible return values from FlashPoint_HandleInterrupt. +*/ + +#define FlashPoint_NormalInterrupt 0x00 +#define FlashPoint_InternalError 0xFE +#define FlashPoint_ExternalBusReset 0xFF + + +/* + Define prototypes for the forward referenced BusLogic Driver + Internal Functions. +*/ + +static void BusLogic_QueueCompletedCCB(BusLogic_CCB_T *); +static void BusLogic_InterruptHandler(int, void *, Registers_T *); +static int BusLogic_ResetHostAdapter(BusLogic_HostAdapter_T *, + SCSI_Command_T *, unsigned int); +static void BusLogic_Message(BusLogic_MessageLevel_T, char *, + BusLogic_HostAdapter_T *, ...); +static void BusLogic_ParseDriverOptions(char *); + + +#endif /* BusLogic_DriverVersion */ diff --git a/linux/src/drivers/scsi/FlashPoint.c b/linux/src/drivers/scsi/FlashPoint.c new file mode 100644 index 00000000..a74c3c5f --- /dev/null +++ b/linux/src/drivers/scsi/FlashPoint.c @@ -0,0 +1,12159 @@ +/* + + FlashPoint.c -- FlashPoint SCCB Manager for Linux + + This file contains the FlashPoint SCCB Manager from BusLogic's FlashPoint + Driver Developer's Kit, with minor modifications by Leonard N. Zubkoff for + Linux compatibility. It was provided by BusLogic in the form of 16 separate + source files, which would have unnecessarily cluttered the scsi directory, so + the individual files have been combined into this single file. + + Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + + This file is available under both the GNU General Public License + and a BSD-style copyright; see LICENSE.FlashPoint for details. + +*/ + + +#include <linux/config.h> + + +#ifndef CONFIG_SCSI_OMIT_FLASHPOINT + + +#define UNIX +#define FW_TYPE _SCCB_MGR_ +#define MAX_CARDS 8 +#undef BUSTYPE_PCI + + +#define OS_InPortByte(port) inb(port) +#define OS_InPortWord(port) inw(port) +#define OS_InPortLong(port) inl(port) +#define OS_OutPortByte(port, value) outb(value, port) +#define OS_OutPortWord(port, value) outw(value, port) +#define OS_OutPortLong(port, value) outl(value, port) +#define OS_Lock(x) +#define OS_UnLock(x) + + +/* + Define name replacements for compatibility with the Linux BusLogic Driver. +*/ + +#define SccbMgr_sense_adapter FlashPoint_ProbeHostAdapter +#define SccbMgr_config_adapter FlashPoint_HardwareResetHostAdapter +#define SccbMgr_unload_card FlashPoint_ReleaseHostAdapter +#define SccbMgr_start_sccb FlashPoint_StartCCB +#define SccbMgr_abort_sccb FlashPoint_AbortCCB +#define SccbMgr_my_int FlashPoint_InterruptPending +#define SccbMgr_isr FlashPoint_HandleInterrupt + + +/* + Define name replacements to avoid kernel namespace pollution. +*/ + +#define BL_Card FPT_BL_Card +#define BusMasterInit FPT_BusMasterInit +#define CalcCrc16 FPT_CalcCrc16 +#define CalcLrc FPT_CalcLrc +#define ChkIfChipInitialized FPT_ChkIfChipInitialized +#define DiagBusMaster FPT_DiagBusMaster +#define DiagEEPROM FPT_DiagEEPROM +#define DiagXbow FPT_DiagXbow +#define GetTarLun FPT_GetTarLun +#define RNVRamData FPT_RNVRamData +#define RdStack FPT_RdStack +#define SccbMgrTableInitAll FPT_SccbMgrTableInitAll +#define SccbMgrTableInitCard FPT_SccbMgrTableInitCard +#define SccbMgrTableInitTarget FPT_SccbMgrTableInitTarget +#define SccbMgr_bad_isr FPT_SccbMgr_bad_isr +#define SccbMgr_scsi_reset FPT_SccbMgr_scsi_reset +#define SccbMgr_timer_expired FPT_SccbMgr_timer_expired +#define SendMsg FPT_SendMsg +#define Wait FPT_Wait +#define Wait1Second FPT_Wait1Second +#define WrStack FPT_WrStack +#define XbowInit FPT_XbowInit +#define autoCmdCmplt FPT_autoCmdCmplt +#define autoLoadDefaultMap FPT_autoLoadDefaultMap +#define busMstrDataXferStart FPT_busMstrDataXferStart +#define busMstrSGDataXferStart FPT_busMstrSGDataXferStart +#define busMstrTimeOut FPT_busMstrTimeOut +#define dataXferProcessor FPT_dataXferProcessor +#define default_intena FPT_default_intena +#define hostDataXferAbort FPT_hostDataXferAbort +#define hostDataXferRestart FPT_hostDataXferRestart +#define inisci FPT_inisci +#define mbCards FPT_mbCards +#define nvRamInfo FPT_nvRamInfo +#define phaseBusFree FPT_phaseBusFree +#define phaseChkFifo FPT_phaseChkFifo +#define phaseCommand FPT_phaseCommand +#define phaseDataIn FPT_phaseDataIn +#define phaseDataOut FPT_phaseDataOut +#define phaseDecode FPT_phaseDecode +#define phaseIllegal FPT_phaseIllegal +#define phaseMsgIn FPT_phaseMsgIn +#define phaseMsgOut FPT_phaseMsgOut +#define phaseStatus FPT_phaseStatus +#define queueAddSccb FPT_queueAddSccb +#define queueCmdComplete FPT_queueCmdComplete +#define queueDisconnect FPT_queueDisconnect +#define queueFindSccb FPT_queueFindSccb +#define queueFlushSccb FPT_queueFlushSccb +#define queueFlushTargSccb FPT_queueFlushTargSccb +#define queueSearchSelect FPT_queueSearchSelect +#define queueSelectFail FPT_queueSelectFail +#define s_PhaseTbl FPT_s_PhaseTbl +#define scamHAString FPT_scamHAString +#define scamInfo FPT_scamInfo +#define scarb FPT_scarb +#define scasid FPT_scasid +#define scbusf FPT_scbusf +#define sccbMgrTbl FPT_sccbMgrTbl +#define schkdd FPT_schkdd +#define scini FPT_scini +#define sciso FPT_sciso +#define scmachid FPT_scmachid +#define scsavdi FPT_scsavdi +#define scsel FPT_scsel +#define scsell FPT_scsell +#define scsendi FPT_scsendi +#define scvalq FPT_scvalq +#define scwirod FPT_scwirod +#define scwiros FPT_scwiros +#define scwtsel FPT_scwtsel +#define scxferc FPT_scxferc +#define sdecm FPT_sdecm +#define sfm FPT_sfm +#define shandem FPT_shandem +#define sinits FPT_sinits +#define sisyncn FPT_sisyncn +#define sisyncr FPT_sisyncr +#define siwidn FPT_siwidn +#define siwidr FPT_siwidr +#define sres FPT_sres +#define sresb FPT_sresb +#define ssel FPT_ssel +#define ssenss FPT_ssenss +#define sssyncv FPT_sssyncv +#define stsyncn FPT_stsyncn +#define stwidn FPT_stwidn +#define sxfrp FPT_sxfrp +#define utilEERead FPT_utilEERead +#define utilEEReadOrg FPT_utilEEReadOrg +#define utilEESendCmdAddr FPT_utilEESendCmdAddr +#define utilEEWrite FPT_utilEEWrite +#define utilEEWriteOnOff FPT_utilEEWriteOnOff +#define utilUpdateResidual FPT_utilUpdateResidual + + +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: globals.h $ + * + * Description: Common shared global defines. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ +#ifndef __GLOBALS_H__ +#define __GLOBALS_H__ + +#define _UCB_MGR_ 1 +#define _SCCB_MGR_ 2 + +/*#include <osflags.h>*/ + +#define MAX_CDBLEN 12 + +#define SCAM_LEV_2 1 + +#define CRCMASK 0xA001 + +/* In your osflags.h file, please ENSURE that only ONE OS FLAG + is on at a time !!! Also, please make sure you turn set the + variable FW_TYPE to either _UCB_MGR_ or _SCCB_MGR_ !!! */ + +#if defined(DOS) || defined(WIN95_16) || defined(OS2) || defined(OTHER_16) + #define COMPILER_16_BIT 1 +#elif defined(NETWARE) || defined(NT) || defined(WIN95_32) || defined(UNIX) || defined(OTHER_32) || defined(SOLARIS_REAL_MODE) + #define COMPILER_32_BIT 1 +#endif + + +#define BL_VENDOR_ID 0x104B +#define FP_DEVICE_ID 0x8130 +#define MM_DEVICE_ID 0x1040 + + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE (!(FALSE)) +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#define FAILURE 0xFFFFFFFFL + + +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef unsigned int UINT; +typedef unsigned long ULONG; +typedef unsigned char * PUCHAR; +typedef unsigned short* PUSHORT; +typedef unsigned long * PULONG; +typedef void * PVOID; + + +#if defined(COMPILER_16_BIT) +typedef unsigned char far * uchar_ptr; +typedef unsigned short far * ushort_ptr; +typedef unsigned long far * ulong_ptr; +#endif /* 16_BIT_COMPILER */ + +#if defined(COMPILER_32_BIT) +typedef unsigned char * uchar_ptr; +typedef unsigned short * ushort_ptr; +typedef unsigned long * ulong_ptr; +#endif /* 32_BIT_COMPILER */ + + +/* NEW TYPE DEFINITIONS (shared with Mylex North) + +** Use following type defines to avoid confusion in 16 and 32-bit +** environments. Avoid using 'int' as it denotes 16 bits in 16-bit +** environment and 32 in 32-bit environments. + +*/ + +#define s08bits char +#define s16bits short +#define s32bits long + +#define u08bits unsigned s08bits +#define u16bits unsigned s16bits +#define u32bits unsigned s32bits + +#if defined(COMPILER_16_BIT) + +typedef u08bits far * pu08bits; +typedef u16bits far * pu16bits; +typedef u32bits far * pu32bits; + +#endif /* COMPILER_16_BIT */ + +#if defined(COMPILER_32_BIT) + +typedef u08bits * pu08bits; +typedef u16bits * pu16bits; +typedef u32bits * pu32bits; + +#endif /* COMPILER_32_BIT */ + + +#define BIT(x) ((UCHAR)(1<<(x))) /* single-bit mask in bit position x */ +#define BITW(x) ((USHORT)(1<<(x))) /* single-bit mask in bit position x */ + + + +#if defined(DOS) +/*#include <dos.h>*/ + #undef inportb /* undefine for Borland Lib */ + #undef inport /* they may have define I/O function in LIB */ + #undef outportb + #undef outport + + #define OS_InPortByte(ioport) inportb(ioport) + #define OS_InPortWord(ioport) inport(ioport) + #define OS_InPortLong(ioport) inportq(ioport, val) + #define OS_OutPortByte(ioport, val) outportb(ioport, val) + #define OS_OutPortWord(ioport, val) outport(ioport, val) + #define OS_OutPortLong(ioport) outportq(ioport, val) +#endif /* DOS */ + +#if defined(NETWARE) || defined(OTHER_32) || defined(OTHER_16) + extern u08bits OS_InPortByte(u32bits ioport); + extern u16bits OS_InPortWord(u32bits ioport); + extern u32bits OS_InPortLong(u32bits ioport); + + extern OS_InPortByteBuffer(u32bits ioport, pu08bits buffer, u32bits count); + extern OS_InPortWordBuffer(u32bits ioport, pu16bits buffer, u32bits count); + extern OS_OutPortByte(u32bits ioport, u08bits val); + extern OS_OutPortWord(u32bits ioport, u16bits val); + extern OS_OutPortLong(u32bits ioport, u32bits val); + extern OS_OutPortByteBuffer(u32bits ioport, pu08bits buffer, u32bits count); + extern OS_OutPortWordBuffer(u32bits ioport, pu16bits buffer, u32bits count); +#endif /* NETWARE || OTHER_32 || OTHER_16 */ + +#if defined (NT) || defined(WIN95_32) || defined(WIN95_16) + #if defined(NT) + + extern __declspec(dllimport) u08bits ScsiPortReadPortUchar(pu08bits ioport); + extern __declspec(dllimport) u16bits ScsiPortReadPortUshort(pu16bits ioport); + extern __declspec(dllimport) u32bits ScsiPortReadPortUlong(pu32bits ioport); + extern __declspec(dllimport) void ScsiPortWritePortUchar(pu08bits ioport, u08bits val); + extern __declspec(dllimport) void ScsiPortWritePortUshort(pu16bits port, u16bits val); + extern __declspec(dllimport) void ScsiPortWritePortUlong(pu32bits port, u32bits val); + + #else + + extern u08bits ScsiPortReadPortUchar(pu08bits ioport); + extern u16bits ScsiPortReadPortUshort(pu16bits ioport); + extern u32bits ScsiPortReadPortUlong(pu32bits ioport); + extern void ScsiPortWritePortUchar(pu08bits ioport, u08bits val); + extern void ScsiPortWritePortUshort(pu16bits port, u16bits val); + extern void ScsiPortWritePortUlong(pu32bits port, u32bits val); + #endif + + + #define OS_InPortByte(ioport) ScsiPortReadPortUchar((pu08bits) ioport) + #define OS_InPortWord(ioport) ScsiPortReadPortUshort((pu16bits) ioport) + #define OS_InPortLong(ioport) ScsiPortReadPortUlong((pu32bits) ioport) + + #define OS_OutPortByte(ioport, val) ScsiPortWritePortUchar((pu08bits) ioport, (u08bits) val) + #define OS_OutPortWord(ioport, val) ScsiPortWritePortUshort((pu16bits) ioport, (u16bits) val) + #define OS_OutPortLong(ioport, val) ScsiPortWritePortUlong((pu32bits) ioport, (u32bits) val) + #define OS_OutPortByteBuffer(ioport, buffer, count) \ + ScsiPortWritePortBufferUchar((pu08bits)&port, (pu08bits) buffer, (u32bits) count) + #define OS_OutPortWordBuffer(ioport, buffer, count) \ + ScsiPortWritePortBufferUshort((pu16bits)&port, (pu16bits) buffer, (u32bits) count) + + #define OS_Lock(x) + #define OS_UnLock(x) +#endif /* NT || WIN95_32 || WIN95_16 */ + +#if defined (UNIX) && !defined(OS_InPortByte) + #define OS_InPortByte(ioport) inb((u16bits)ioport) + #define OS_InPortWord(ioport) inw((u16bits)ioport) + #define OS_InPortLong(ioport) inl((u16bits)ioport) + #define OS_OutPortByte(ioport,val) outb((u16bits)ioport, (u08bits)val) + #define OS_OutPortWord(ioport,val) outw((u16bits)ioport, (u16bits)val) + #define OS_OutPortLong(ioport,val) outl((u16bits)ioport, (u32bits)val) + + #define OS_Lock(x) + #define OS_UnLock(x) +#endif /* UNIX */ + + +#if defined(OS2) + extern u08bits inb(u32bits ioport); + extern u16bits inw(u32bits ioport); + extern void outb(u32bits ioport, u08bits val); + extern void outw(u32bits ioport, u16bits val); + + #define OS_InPortByte(ioport) inb(ioport) + #define OS_InPortWord(ioport) inw(ioport) + #define OS_OutPortByte(ioport, val) outb(ioport, val) + #define OS_OutPortWord(ioport, val) outw(ioport, val) + extern u32bits OS_InPortLong(u32bits ioport); + extern void OS_OutPortLong(u32bits ioport, u32bits val); + + #define OS_Lock(x) + #define OS_UnLock(x) +#endif /* OS2 */ + +#if defined(SOLARIS_REAL_MODE) + +extern unsigned char inb(unsigned long ioport); +extern unsigned short inw(unsigned long ioport); + +#define OS_InPortByte(ioport) inb(ioport) +#define OS_InPortWord(ioport) inw(ioport) + +extern void OS_OutPortByte(unsigned long ioport, unsigned char val); +extern void OS_OutPortWord(unsigned long ioport, unsigned short val); +extern unsigned long OS_InPortLong(unsigned long ioport); +extern void OS_OutPortLong(unsigned long ioport, unsigned long val); + +#define OS_Lock(x) +#define OS_UnLock(x) + +#endif /* SOLARIS_REAL_MODE */ + +#endif /* __GLOBALS_H__ */ + +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: sccbmgr.h $ + * + * Description: Common shared SCCB Interface defines and SCCB + * Manager specifics defines. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +#ifndef __SCCB_H__ +#define __SCCB_H__ + +/*#include <osflags.h>*/ +/*#include <globals.h>*/ + +#if defined(BUGBUG) +#define debug_size 32 +#endif + +#if defined(DOS) + + typedef struct _SCCB near *PSCCB; + #if (FW_TYPE == _SCCB_MGR_) + typedef void (*CALL_BK_FN)(PSCCB); + #endif + +#elif defined(OS2) + + typedef struct _SCCB far *PSCCB; + #if (FW_TYPE == _SCCB_MGR_) + typedef void (far *CALL_BK_FN)(PSCCB); + #endif + +#else + + typedef struct _SCCB *PSCCB; + #if (FW_TYPE == _SCCB_MGR_) + typedef void (*CALL_BK_FN)(PSCCB); + #endif + +#endif + + +typedef struct SCCBMgr_info { + ULONG si_baseaddr; + UCHAR si_present; + UCHAR si_intvect; + UCHAR si_id; + UCHAR si_lun; + USHORT si_fw_revision; + USHORT si_per_targ_init_sync; + USHORT si_per_targ_fast_nego; + USHORT si_per_targ_ultra_nego; + USHORT si_per_targ_no_disc; + USHORT si_per_targ_wide_nego; + USHORT si_flags; + UCHAR si_card_family; + UCHAR si_bustype; + UCHAR si_card_model[3]; + UCHAR si_relative_cardnum; + UCHAR si_reserved[4]; + ULONG si_OS_reserved; + UCHAR si_XlatInfo[4]; + ULONG si_reserved2[5]; + ULONG si_secondary_range; +} SCCBMGR_INFO; + +#if defined(DOS) + typedef SCCBMGR_INFO * PSCCBMGR_INFO; +#else + #if defined (COMPILER_16_BIT) + typedef SCCBMGR_INFO far * PSCCBMGR_INFO; + #else + typedef SCCBMGR_INFO * PSCCBMGR_INFO; + #endif +#endif // defined(DOS) + + + + +#if (FW_TYPE==_SCCB_MGR_) + #define SCSI_PARITY_ENA 0x0001 + #define LOW_BYTE_TERM 0x0010 + #define HIGH_BYTE_TERM 0x0020 + #define BUSTYPE_PCI 0x3 +#endif + +#define SUPPORT_16TAR_32LUN 0x0002 +#define SOFT_RESET 0x0004 +#define EXTENDED_TRANSLATION 0x0008 +#define POST_ALL_UNDERRRUNS 0x0040 +#define FLAG_SCAM_ENABLED 0x0080 +#define FLAG_SCAM_LEVEL2 0x0100 + + + + +#define HARPOON_FAMILY 0x02 + + +#define ISA_BUS_CARD 0x01 +#define EISA_BUS_CARD 0x02 +#define PCI_BUS_CARD 0x03 +#define VESA_BUS_CARD 0x04 + +/* SCCB struc used for both SCCB and UCB manager compiles! + * The UCB Manager treats the SCCB as it's 'native hardware structure' + */ + + +#pragma pack(1) +typedef struct _SCCB { + UCHAR OperationCode; + UCHAR ControlByte; + UCHAR CdbLength; + UCHAR RequestSenseLength; + ULONG DataLength; + ULONG DataPointer; + UCHAR CcbRes[2]; + UCHAR HostStatus; + UCHAR TargetStatus; + UCHAR TargID; + UCHAR Lun; + UCHAR Cdb[12]; + UCHAR CcbRes1; + UCHAR Reserved1; + ULONG Reserved2; + ULONG SensePointer; + + + CALL_BK_FN SccbCallback; /* VOID (*SccbCallback)(); */ + ULONG SccbIOPort; /* Identifies board base port */ + UCHAR SccbStatus; + UCHAR SCCBRes2; + USHORT SccbOSFlags; + + + ULONG Sccb_XferCnt; /* actual transfer count */ + ULONG Sccb_ATC; + ULONG SccbVirtDataPtr; /* virtual addr for OS/2 */ + ULONG Sccb_res1; + USHORT Sccb_MGRFlags; + USHORT Sccb_sgseg; + UCHAR Sccb_scsimsg; /* identify msg for selection */ + UCHAR Sccb_tag; + UCHAR Sccb_scsistat; + UCHAR Sccb_idmsg; /* image of last msg in */ + PSCCB Sccb_forwardlink; + PSCCB Sccb_backlink; + ULONG Sccb_savedATC; + UCHAR Save_Cdb[6]; + UCHAR Save_CdbLen; + UCHAR Sccb_XferState; + ULONG Sccb_SGoffset; +#if (FW_TYPE == _UCB_MGR_) + PUCB Sccb_ucb_ptr; +#endif + } SCCB; + +#define SCCB_SIZE sizeof(SCCB) + +#pragma pack() + + + +#define SCSI_INITIATOR_COMMAND 0x00 +#define TARGET_MODE_COMMAND 0x01 +#define SCATTER_GATHER_COMMAND 0x02 +#define RESIDUAL_COMMAND 0x03 +#define RESIDUAL_SG_COMMAND 0x04 +#define RESET_COMMAND 0x81 + + +#define F_USE_CMD_Q 0x20 /*Inidcates TAGGED command. */ +#define TAG_TYPE_MASK 0xC0 /*Type of tag msg to send. */ +#define TAG_Q_MASK 0xE0 +#define SCCB_DATA_XFER_OUT 0x10 /* Write */ +#define SCCB_DATA_XFER_IN 0x08 /* Read */ + + +#define FOURTEEN_BYTES 0x00 /* Request Sense Buffer size */ +#define NO_AUTO_REQUEST_SENSE 0x01 /* No Request Sense Buffer */ + + +#define BUS_FREE_ST 0 +#define SELECT_ST 1 +#define SELECT_BDR_ST 2 /* Select w\ Bus Device Reset */ +#define SELECT_SN_ST 3 /* Select w\ Sync Nego */ +#define SELECT_WN_ST 4 /* Select w\ Wide Data Nego */ +#define SELECT_Q_ST 5 /* Select w\ Tagged Q'ing */ +#define COMMAND_ST 6 +#define DATA_OUT_ST 7 +#define DATA_IN_ST 8 +#define DISCONNECT_ST 9 +#define STATUS_ST 10 +#define ABORT_ST 11 +#define MESSAGE_ST 12 + + +#define F_HOST_XFER_DIR 0x01 +#define F_ALL_XFERRED 0x02 +#define F_SG_XFER 0x04 +#define F_AUTO_SENSE 0x08 +#define F_ODD_BALL_CNT 0x10 +#define F_NO_DATA_YET 0x80 + + +#define F_STATUSLOADED 0x01 +#define F_MSGLOADED 0x02 +#define F_DEV_SELECTED 0x04 + + +#define SCCB_COMPLETE 0x00 /* SCCB completed without error */ +#define SCCB_DATA_UNDER_RUN 0x0C +#define SCCB_SELECTION_TIMEOUT 0x11 /* Set SCSI selection timed out */ +#define SCCB_DATA_OVER_RUN 0x12 +#define SCCB_UNEXPECTED_BUS_FREE 0x13 /* Target dropped SCSI BSY */ +#define SCCB_PHASE_SEQUENCE_FAIL 0x14 /* Target bus phase sequence failure */ + +#define SCCB_INVALID_OP_CODE 0x16 /* SCCB invalid operation code */ +#define SCCB_INVALID_SCCB 0x1A /* Invalid SCCB - bad parameter */ +#define SCCB_GROSS_FW_ERR 0x27 /* Major problem! */ +#define SCCB_BM_ERR 0x30 /* BusMaster error. */ +#define SCCB_PARITY_ERR 0x34 /* SCSI parity error */ + + + +#if (FW_TYPE==_UCB_MGR_) + #define HBA_AUTO_SENSE_FAIL 0x1B + #define HBA_TQ_REJECTED 0x1C + #define HBA_UNSUPORTED_MSG 0x1D + #define HBA_HW_ERROR 0x20 + #define HBA_ATN_NOT_RESPONDED 0x21 + #define HBA_SCSI_RESET_BY_ADAPTER 0x22 + #define HBA_SCSI_RESET_BY_TARGET 0x23 + #define HBA_WRONG_CONNECTION 0x24 + #define HBA_BUS_DEVICE_RESET 0x25 + #define HBA_ABORT_QUEUE 0x26 + +#else // these are not defined in BUDI/UCB + + #define SCCB_INVALID_DIRECTION 0x18 /* Invalid target direction */ + #define SCCB_DUPLICATE_SCCB 0x19 /* Duplicate SCCB */ + #define SCCB_SCSI_RST 0x35 /* SCSI RESET detected. */ + +#endif // (FW_TYPE==_UCB_MGR_) + + +#define SCCB_IN_PROCESS 0x00 +#define SCCB_SUCCESS 0x01 +#define SCCB_ABORT 0x02 +#define SCCB_NOT_FOUND 0x03 +#define SCCB_ERROR 0x04 +#define SCCB_INVALID 0x05 + +#define SCCB_SIZE sizeof(SCCB) + + + + +#if (FW_TYPE == _UCB_MGR_) + void SccbMgr_start_sccb(CARD_HANDLE pCurrCard, PUCB p_ucb); + s32bits SccbMgr_abort_sccb(CARD_HANDLE pCurrCard, PUCB p_ucb); + u08bits SccbMgr_my_int(CARD_HANDLE pCurrCard); + s32bits SccbMgr_isr(CARD_HANDLE pCurrCard); + void SccbMgr_scsi_reset(CARD_HANDLE pCurrCard); + void SccbMgr_timer_expired(CARD_HANDLE pCurrCard); + void SccbMgr_unload_card(CARD_HANDLE pCurrCard); + void SccbMgr_restore_foreign_state(CARD_HANDLE pCurrCard); + void SccbMgr_restore_native_state(CARD_HANDLE pCurrCard); + void SccbMgr_save_foreign_state(PADAPTER_INFO pAdapterInfo); + +#endif + + +#if (FW_TYPE == _SCCB_MGR_) + + #if defined (DOS) + int SccbMgr_sense_adapter(PSCCBMGR_INFO pCardInfo); + USHORT SccbMgr_config_adapter(PSCCBMGR_INFO pCardInfo); + void SccbMgr_start_sccb(USHORT pCurrCard, PSCCB p_SCCB); + int SccbMgr_abort_sccb(USHORT pCurrCard, PSCCB p_SCCB); + UCHAR SccbMgr_my_int(USHORT pCurrCard); + int SccbMgr_isr(USHORT pCurrCard); + void SccbMgr_scsi_reset(USHORT pCurrCard); + void SccbMgr_timer_expired(USHORT pCurrCard); + USHORT SccbMgr_status(USHORT pCurrCard); + void SccbMgr_unload_card(USHORT pCurrCard); + + #else //non-DOS + + int SccbMgr_sense_adapter(PSCCBMGR_INFO pCardInfo); + ULONG SccbMgr_config_adapter(PSCCBMGR_INFO pCardInfo); + void SccbMgr_start_sccb(ULONG pCurrCard, PSCCB p_SCCB); + int SccbMgr_abort_sccb(ULONG pCurrCard, PSCCB p_SCCB); + UCHAR SccbMgr_my_int(ULONG pCurrCard); + int SccbMgr_isr(ULONG pCurrCard); + void SccbMgr_scsi_reset(ULONG pCurrCard); + void SccbMgr_enable_int(ULONG pCurrCard); + void SccbMgr_disable_int(ULONG pCurrCard); + void SccbMgr_timer_expired(ULONG pCurrCard); + void SccbMgr_unload_card(ULONG pCurrCard); + + #endif +#endif // (FW_TYPE == _SCCB_MGR_) + +#endif /* __SCCB_H__ */ + +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: blx30.h $ + * + * Description: This module contains SCCB/UCB Manager implementation + * specific stuff. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + + +#ifndef __blx30_H__ +#define __blx30_H__ + +/*#include <globals.h>*/ + +#define ORION_FW_REV 3110 + + + + +#define HARP_REVD 1 + + +#if defined(DOS) +#define QUEUE_DEPTH 8+1 /*1 for Normal disconnect 0 for Q'ing. */ +#else +#define QUEUE_DEPTH 254+1 /*1 for Normal disconnect 32 for Q'ing. */ +#endif // defined(DOS) + +#define MAX_MB_CARDS 4 /* Max. no of cards suppoerted on Mother Board */ + +#define WIDE_SCSI 1 + +#if defined(WIDE_SCSI) + #if defined(DOS) + #define MAX_SCSI_TAR 16 + #define MAX_LUN 8 + #define LUN_MASK 0x07 + #else + #define MAX_SCSI_TAR 16 + #define MAX_LUN 32 + #define LUN_MASK 0x1f + + #endif +#else + #define MAX_SCSI_TAR 8 + #define MAX_LUN 8 + #define LUN_MASK 0x07 +#endif + +#if defined(HARP_REVA) +#define SG_BUF_CNT 15 /*Number of prefetched elements. */ +#else +#define SG_BUF_CNT 16 /*Number of prefetched elements. */ +#endif + +#define SG_ELEMENT_SIZE 8 /*Eight byte per element. */ +#define SG_LOCAL_MASK 0x00000000L +#define SG_ELEMENT_MASK 0xFFFFFFFFL + + +#if (FW_TYPE == _UCB_MGR_) + #define OPC_DECODE_NORMAL 0x0f7f +#endif // _UCB_MGR_ + + + +#if defined(DOS) + +/*#include <dos.h>*/ + #define RD_HARPOON(ioport) (OS_InPortByte(ioport)) + #define RDW_HARPOON(ioport) (OS_InPortWord(ioport)) + #define WR_HARPOON(ioport,val) (OS_OutPortByte(ioport,val)) + #define WRW_HARPOON(ioport,val) (OS_OutPortWord(ioport,val)) + + #define RD_HARP32(port,offset,data) asm{db 66h; \ + push ax; \ + mov dx,port; \ + add dx, offset; \ + db 66h; \ + in ax,dx; \ + db 66h; \ + mov word ptr data,ax;\ + db 66h; \ + pop ax} + + #define WR_HARP32(port,offset,data) asm{db 66h; \ + push ax; \ + mov dx,port; \ + add dx, offset; \ + db 66h; \ + mov ax,word ptr data;\ + db 66h; \ + out dx,ax; \ + db 66h; \ + pop ax} +#endif /* DOS */ + +#if defined(NETWARE) || defined(OTHER_32) || defined(OTHER_16) + #define RD_HARPOON(ioport) OS_InPortByte((unsigned long)ioport) + #define RDW_HARPOON(ioport) OS_InPortWord((unsigned long)ioport) + #define RD_HARP32(ioport,offset,data) (data = OS_InPortLong(ioport + offset)) + #define WR_HARPOON(ioport,val) OS_OutPortByte((ULONG)ioport,(UCHAR) val) + #define WRW_HARPOON(ioport,val) OS_OutPortWord((ULONG)ioport,(USHORT)val) + #define WR_HARP32(ioport,offset,data) OS_OutPortLong((ioport + offset), data) +#endif /* NETWARE || OTHER_32 || OTHER_16 */ + +#if defined(NT) || defined(WIN95_32) || defined(WIN95_16) + #define RD_HARPOON(ioport) OS_InPortByte((ULONG)ioport) + #define RDW_HARPOON(ioport) OS_InPortWord((ULONG)ioport) + #define RD_HARP32(ioport,offset,data) (data = OS_InPortLong((ULONG)(ioport + offset))) + #define WR_HARPOON(ioport,val) OS_OutPortByte((ULONG)ioport,(UCHAR) val) + #define WRW_HARPOON(ioport,val) OS_OutPortWord((ULONG)ioport,(USHORT)val) + #define WR_HARP32(ioport,offset,data) OS_OutPortLong((ULONG)(ioport + offset), data) +#endif /* NT || WIN95_32 || WIN95_16 */ + +#if defined (UNIX) + #define RD_HARPOON(ioport) OS_InPortByte((u32bits)ioport) + #define RDW_HARPOON(ioport) OS_InPortWord((u32bits)ioport) + #define RD_HARP32(ioport,offset,data) (data = OS_InPortLong((u32bits)(ioport + offset))) + #define WR_HARPOON(ioport,val) OS_OutPortByte((u32bits)ioport,(u08bits) val) + #define WRW_HARPOON(ioport,val) OS_OutPortWord((u32bits)ioport,(u16bits)val) + #define WR_HARP32(ioport,offset,data) OS_OutPortLong((u32bits)(ioport + offset), data) +#endif /* UNIX */ + +#if defined(OS2) + #define RD_HARPOON(ioport) OS_InPortByte((unsigned long)ioport) + #define RDW_HARPOON(ioport) OS_InPortWord((unsigned long)ioport) + #define RD_HARP32(ioport,offset,data) (data = OS_InPortLong((ULONG)(ioport + offset))) + #define WR_HARPOON(ioport,val) OS_OutPortByte((ULONG)ioport,(UCHAR) val) + #define WRW_HARPOON(ioport,val) OS_OutPortWord((ULONG)ioport,(USHORT)val) + #define WR_HARP32(ioport,offset,data) OS_OutPortLong(((ULONG)(ioport + offset)), data) +#endif /* OS2 */ + +#if defined(SOLARIS_REAL_MODE) + + #define RD_HARPOON(ioport) OS_InPortByte((unsigned long)ioport) + #define RDW_HARPOON(ioport) OS_InPortWord((unsigned long)ioport) + #define RD_HARP32(ioport,offset,data) (data = OS_InPortLong((ULONG)(ioport + offset))) + #define WR_HARPOON(ioport,val) OS_OutPortByte((ULONG)ioport,(UCHAR) val) + #define WRW_HARPOON(ioport,val) OS_OutPortWord((ULONG)ioport,(USHORT)val) + #define WR_HARP32(ioport,offset,data) OS_OutPortLong((ULONG)(ioport + offset), (ULONG)data) + +#endif /* SOLARIS_REAL_MODE */ + +#endif /* __BLX30_H__ */ + + +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: target.h $ + * + * Description: Definitions for Target related structures + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +#ifndef __TARGET__ +#define __TARGET__ + +/*#include <globals.h>*/ +/*#include <blx30.h>*/ + + +#define TAR_SYNC_MASK (BIT(7)+BIT(6)) +#define SYNC_UNKNOWN 0x00 +#define SYNC_TRYING BIT(6) +#define SYNC_SUPPORTED (BIT(7)+BIT(6)) + +#define TAR_WIDE_MASK (BIT(5)+BIT(4)) +#define WIDE_DISABLED 0x00 +#define WIDE_ENABLED BIT(4) +#define WIDE_NEGOCIATED BIT(5) + +#define TAR_TAG_Q_MASK (BIT(3)+BIT(2)) +#define TAG_Q_UNKNOWN 0x00 +#define TAG_Q_TRYING BIT(2) +#define TAG_Q_REJECT BIT(3) +#define TAG_Q_SUPPORTED (BIT(3)+BIT(2)) + +#define TAR_ALLOW_DISC BIT(0) + + +#define EE_SYNC_MASK (BIT(0)+BIT(1)) +#define EE_SYNC_ASYNC 0x00 +#define EE_SYNC_5MB BIT(0) +#define EE_SYNC_10MB BIT(1) +#define EE_SYNC_20MB (BIT(0)+BIT(1)) + +#define EE_ALLOW_DISC BIT(6) +#define EE_WIDE_SCSI BIT(7) + + +#if defined(DOS) + typedef struct SCCBMgr_tar_info near *PSCCBMgr_tar_info; + +#elif defined(OS2) + typedef struct SCCBMgr_tar_info far *PSCCBMgr_tar_info; + +#else + typedef struct SCCBMgr_tar_info *PSCCBMgr_tar_info; + +#endif + + +typedef struct SCCBMgr_tar_info { + + PSCCB TarSelQ_Head; + PSCCB TarSelQ_Tail; + UCHAR TarLUN_CA; /*Contingent Allgiance */ + UCHAR TarTagQ_Cnt; + UCHAR TarSelQ_Cnt; + UCHAR TarStatus; + UCHAR TarEEValue; + UCHAR TarSyncCtrl; + UCHAR TarReserved[2]; /* for alignment */ + UCHAR LunDiscQ_Idx[MAX_LUN]; + UCHAR TarLUNBusy[MAX_LUN]; +} SCCBMGR_TAR_INFO; + +typedef struct NVRAMInfo { + UCHAR niModel; /* Model No. of card */ + UCHAR niCardNo; /* Card no. */ +#if defined(DOS) + USHORT niBaseAddr; /* Port Address of card */ +#else + ULONG niBaseAddr; /* Port Address of card */ +#endif + UCHAR niSysConf; /* Adapter Configuration byte - Byte 16 of eeprom map */ + UCHAR niScsiConf; /* SCSI Configuration byte - Byte 17 of eeprom map */ + UCHAR niScamConf; /* SCAM Configuration byte - Byte 20 of eeprom map */ + UCHAR niAdapId; /* Host Adapter ID - Byte 24 of eerpom map */ + UCHAR niSyncTbl[MAX_SCSI_TAR / 2]; /* Sync/Wide byte of targets */ + UCHAR niScamTbl[MAX_SCSI_TAR][4]; /* Compressed Scam name string of Targets */ +}NVRAMINFO; + +#if defined(DOS) +typedef NVRAMINFO near *PNVRamInfo; +#elif defined (OS2) +typedef NVRAMINFO far *PNVRamInfo; +#else +typedef NVRAMINFO *PNVRamInfo; +#endif + +#define MODEL_LT 1 +#define MODEL_DL 2 +#define MODEL_LW 3 +#define MODEL_DW 4 + + +typedef struct SCCBcard { + PSCCB currentSCCB; +#if (FW_TYPE==_SCCB_MGR_) + PSCCBMGR_INFO cardInfo; +#else + PADAPTER_INFO cardInfo; +#endif + +#if defined(DOS) + USHORT ioPort; +#else + ULONG ioPort; +#endif + + USHORT cmdCounter; + UCHAR discQCount; + UCHAR tagQ_Lst; + UCHAR cardIndex; + UCHAR scanIndex; + UCHAR globalFlags; + UCHAR ourId; + PNVRamInfo pNvRamInfo; + PSCCB discQ_Tbl[QUEUE_DEPTH]; + +}SCCBCARD; + +#if defined(DOS) +typedef struct SCCBcard near *PSCCBcard; +#elif defined (OS2) +typedef struct SCCBcard far *PSCCBcard; +#else +typedef struct SCCBcard *PSCCBcard; +#endif + + +#define F_TAG_STARTED 0x01 +#define F_CONLUN_IO 0x02 +#define F_DO_RENEGO 0x04 +#define F_NO_FILTER 0x08 +#define F_GREEN_PC 0x10 +#define F_HOST_XFER_ACT 0x20 +#define F_NEW_SCCB_CMD 0x40 +#define F_UPDATE_EEPROM 0x80 + + +#define ID_STRING_LENGTH 32 +#define TYPE_CODE0 0x63 /*Level2 Mstr (bits 7-6), */ + +#define TYPE_CODE1 00 /*No ID yet */ + +#define SLV_TYPE_CODE0 0xA3 /*Priority Bit set (bits 7-6), */ + +#define ASSIGN_ID 0x00 +#define SET_P_FLAG 0x01 +#define CFG_CMPLT 0x03 +#define DOM_MSTR 0x0F +#define SYNC_PTRN 0x1F + +#define ID_0_7 0x18 +#define ID_8_F 0x11 +#define ID_10_17 0x12 +#define ID_18_1F 0x0B +#define MISC_CODE 0x14 +#define CLR_P_FLAG 0x18 +#define LOCATE_ON 0x12 +#define LOCATE_OFF 0x0B + +#define LVL_1_MST 0x00 +#define LVL_2_MST 0x40 +#define DOM_LVL_2 0xC0 + + +#define INIT_SELTD 0x01 +#define LEVEL2_TAR 0x02 + + +enum scam_id_st { ID0,ID1,ID2,ID3,ID4,ID5,ID6,ID7,ID8,ID9,ID10,ID11,ID12, + ID13,ID14,ID15,ID_UNUSED,ID_UNASSIGNED,ID_ASSIGNED,LEGACY, + CLR_PRIORITY,NO_ID_AVAIL }; + +typedef struct SCCBscam_info { + + UCHAR id_string[ID_STRING_LENGTH]; + enum scam_id_st state; + +} SCCBSCAM_INFO, *PSCCBSCAM_INFO; + +#endif +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: scsi2.h $ + * + * Description: Register definitions for HARPOON ASIC. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +#ifndef __SCSI_H__ +#define __SCSI_H__ + + + +#define SCSI_TEST_UNIT_READY 0x00 +#define SCSI_REZERO_UNIT 0x01 +#define SCSI_REQUEST_SENSE 0x03 +#define SCSI_FORMAT_UNIT 0x04 +#define SCSI_REASSIGN 0x07 +#define SCSI_READ 0x08 +#define SCSI_WRITE 0x0A +#define SCSI_SEEK 0x0B +#define SCSI_INQUIRY 0x12 +#define SCSI_MODE_SELECT 0x15 +#define SCSI_RESERVE_UNIT 0x16 +#define SCSI_RELEASE_UNIT 0x17 +#define SCSI_MODE_SENSE 0x1A +#define SCSI_START_STOP_UNIT 0x1B +#define SCSI_SEND_DIAGNOSTIC 0x1D +#define SCSI_READ_CAPACITY 0x25 +#define SCSI_READ_EXTENDED 0x28 +#define SCSI_WRITE_EXTENDED 0x2A +#define SCSI_SEEK_EXTENDED 0x2B +#define SCSI_WRITE_AND_VERIFY 0x2E +#define SCSI_VERIFY 0x2F +#define SCSI_READ_DEFECT_DATA 0x37 +#define SCSI_WRITE_BUFFER 0x3B +#define SCSI_READ_BUFFER 0x3C +#define SCSI_RECV_DIAGNOSTIC 0x1C +#define SCSI_READ_LONG 0x3E +#define SCSI_WRITE_LONG 0x3F +#define SCSI_LAST_SCSI_CMND SCSI_WRITE_LONG +#define SCSI_INVALID_CMND 0xFF + + + +#define SSGOOD 0x00 +#define SSCHECK 0x02 +#define SSCOND_MET 0x04 +#define SSBUSY 0x08 +#define SSRESERVATION_CONFLICT 0x18 +#define SSCMD_TERM 0x22 +#define SSQ_FULL 0x28 + + +#define SKNO_SEN 0x00 +#define SKRECOV_ERR 0x01 +#define SKNOT_RDY 0x02 +#define SKMED_ERR 0x03 +#define SKHW_ERR 0x04 +#define SKILL_REQ 0x05 +#define SKUNIT_ATTN 0x06 +#define SKDATA_PROTECT 0x07 +#define SKBLNK_CHK 0x08 +#define SKCPY_ABORT 0x0A +#define SKABORT_CMD 0x0B +#define SKEQUAL 0x0C +#define SKVOL_OVF 0x0D +#define SKMIS_CMP 0x0E + + +#define SMCMD_COMP 0x00 +#define SMEXT 0x01 +#define SMSAVE_DATA_PTR 0x02 +#define SMREST_DATA_PTR 0x03 +#define SMDISC 0x04 +#define SMINIT_DETEC_ERR 0x05 +#define SMABORT 0x06 +#define SMREJECT 0x07 +#define SMNO_OP 0x08 +#define SMPARITY 0x09 +#define SMDEV_RESET 0x0C +#define SMABORT_TAG 0x0D +#define SMINIT_RECOVERY 0x0F +#define SMREL_RECOVERY 0x10 + +#define SMIDENT 0x80 +#define DISC_PRIV 0x40 + + +#define SMSYNC 0x01 +#define SM10MBS 0x19 /* 100ns */ +#define SM5MBS 0x32 /* 200ns */ +#define SMOFFSET 0x0F /* Maxoffset value */ +#define SMWDTR 0x03 +#define SM8BIT 0x00 +#define SM16BIT 0x01 +#define SM32BIT 0x02 +#define SMIGNORWR 0x23 /* Ignore Wide Residue */ + + +#define ARBITRATION_DELAY 0x01 /* 2.4us using a 40Mhz clock */ +#define BUS_SETTLE_DELAY 0x01 /* 400ns */ +#define BUS_CLEAR_DELAY 0x01 /* 800ns */ + + + +#define SPHASE_TO 0x0A /* 10 second timeout waiting for */ +#define SCMD_TO 0x0F /* Overall command timeout */ + + + +#define SIX_BYTE_CMD 0x06 +#define TEN_BYTE_CMD 0x0A +#define TWELVE_BYTE_CMD 0x0C + +#define ASYNC 0x00 +#define PERI25NS 0x06 /* 25/4ns to next clock for xbow. */ +#define SYNC10MBS 0x19 +#define SYNC5MBS 0x32 +#define MAX_OFFSET 0x0F /* Maxbyteoffset for Sync Xfers */ + +#endif +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: eeprom.h $ + * + * Description: Definitions for EEPROM related structures + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +#ifndef __EEPROM__ +#define __EEPROM__ + +/*#include <globals.h>*/ + +#define EEPROM_WD_CNT 256 + +#define EEPROM_CHECK_SUM 0 +#define FW_SIGNATURE 2 +#define MODEL_NUMB_0 4 +#define MODEL_NUMB_1 5 +#define MODEL_NUMB_2 6 +#define MODEL_NUMB_3 7 +#define MODEL_NUMB_4 8 +#define MODEL_NUMB_5 9 +#define IO_BASE_ADDR 10 +#define IRQ_NUMBER 12 +#define PCI_INT_PIN 13 +#define BUS_DELAY 14 /*On time in byte 14 off delay in 15 */ +#define SYSTEM_CONFIG 16 +#define SCSI_CONFIG 17 +#define BIOS_CONFIG 18 +#define SPIN_UP_DELAY 19 +#define SCAM_CONFIG 20 +#define ADAPTER_SCSI_ID 24 + + +#define IGNORE_B_SCAN 32 +#define SEND_START_ENA 34 +#define DEVICE_ENABLE 36 + +#define SYNC_RATE_TBL 38 +#define SYNC_RATE_TBL01 38 +#define SYNC_RATE_TBL23 40 +#define SYNC_RATE_TBL45 42 +#define SYNC_RATE_TBL67 44 +#define SYNC_RATE_TBL89 46 +#define SYNC_RATE_TBLab 48 +#define SYNC_RATE_TBLcd 50 +#define SYNC_RATE_TBLef 52 + + + +#define EE_SCAMBASE 256 + + + + #define DOM_MASTER (BIT(0) + BIT(1)) + #define SCAM_ENABLED BIT(2) + #define SCAM_LEVEL2 BIT(3) + + + #define RENEGO_ENA BITW(10) + #define CONNIO_ENA BITW(11) + #define GREEN_PC_ENA BITW(12) + + + #define AUTO_RATE_00 00 + #define AUTO_RATE_05 01 + #define AUTO_RATE_10 02 + #define AUTO_RATE_20 03 + + #define WIDE_NEGO_BIT BIT(7) + #define DISC_ENABLE_BIT BIT(6) + + +#endif +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: harpoon.h $ + * + * Description: Register definitions for HARPOON ASIC. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + + +/*#include <globals.h>*/ + +#ifndef __HARPOON__ +#define __HARPOON__ + + + #define hp_vendor_id_0 0x00 /* LSB */ + #define ORION_VEND_0 0x4B + + #define hp_vendor_id_1 0x01 /* MSB */ + #define ORION_VEND_1 0x10 + + #define hp_device_id_0 0x02 /* LSB */ + #define ORION_DEV_0 0x30 + + #define hp_device_id_1 0x03 /* MSB */ + #define ORION_DEV_1 0x81 + + /* Sub Vendor ID and Sub Device ID only available in + Harpoon Version 2 and higher */ + + #define hp_sub_vendor_id_0 0x04 /* LSB */ + #define hp_sub_vendor_id_1 0x05 /* MSB */ + #define hp_sub_device_id_0 0x06 /* LSB */ + #define hp_sub_device_id_1 0x07 /* MSB */ + + + #define hp_dual_addr_lo 0x08 + #define hp_dual_addr_lmi 0x09 + #define hp_dual_addr_hmi 0x0A + #define hp_dual_addr_hi 0x0B + + #define hp_semaphore 0x0C + #define SCCB_MGR_ACTIVE BIT(0) + #define TICKLE_ME BIT(1) + #define SCCB_MGR_PRESENT BIT(3) + #define BIOS_IN_USE BIT(4) + + #define hp_user_defined_D 0x0D + + #define hp_reserved_E 0x0E + + #define hp_sys_ctrl 0x0F + + #define STOP_CLK BIT(0) /*Turn off BusMaster Clock */ + #define DRVR_RST BIT(1) /*Firmware Reset to 80C15 chip */ + #define HALT_MACH BIT(3) /*Halt State Machine */ + #define HARD_ABORT BIT(4) /*Hard Abort */ + #define DIAG_MODE BIT(5) /*Diagnostic Mode */ + + #define BM_ABORT_TMOUT 0x50 /*Halt State machine time out */ + + #define hp_sys_cfg 0x10 + + #define DONT_RST_FIFO BIT(7) /*Don't reset FIFO */ + + + #define hp_host_ctrl0 0x11 + + #define DUAL_ADDR_MODE BIT(0) /*Enable 64-bit addresses */ + #define IO_MEM_SPACE BIT(1) /*I/O Memory Space */ + #define RESOURCE_LOCK BIT(2) /*Enable Resource Lock */ + #define IGNOR_ACCESS_ERR BIT(3) /*Ignore Access Error */ + #define HOST_INT_EDGE BIT(4) /*Host interrupt level/edge mode sel */ + #define SIX_CLOCKS BIT(5) /*6 Clocks between Strobe */ + #define DMA_EVEN_PARITY BIT(6) /*Enable DMA Enen Parity */ + +/* + #define BURST_MODE BIT(0) +*/ + + #define hp_reserved_12 0x12 + + #define hp_host_blk_cnt 0x13 + + #define XFER_BLK1 0x00 /* 0 0 0 1 byte per block*/ + #define XFER_BLK2 0x01 /* 0 0 1 2 byte per block*/ + #define XFER_BLK4 0x02 /* 0 1 0 4 byte per block*/ + #define XFER_BLK8 0x03 /* 0 1 1 8 byte per block*/ + #define XFER_BLK16 0x04 /* 1 0 0 16 byte per block*/ + #define XFER_BLK32 0x05 /* 1 0 1 32 byte per block*/ + #define XFER_BLK64 0x06 /* 1 1 0 64 byte per block*/ + + #define BM_THRESHOLD 0x40 /* PCI mode can only xfer 16 bytes*/ + + + #define hp_reserved_14 0x14 + #define hp_reserved_15 0x15 + #define hp_reserved_16 0x16 + + #define hp_int_mask 0x17 + + #define INT_CMD_COMPL BIT(0) /* DMA command complete */ + #define INT_EXT_STATUS BIT(1) /* Extended Status Set */ + #define INT_SCSI BIT(2) /* Scsi block interrupt */ + #define INT_FIFO_RDY BIT(4) /* FIFO data ready */ + + + #define hp_xfer_cnt_lo 0x18 + #define hp_xfer_cnt_mi 0x19 + #define hp_xfer_cnt_hi 0x1A + #define hp_xfer_cmd 0x1B + + #define XFER_HOST_DMA 0x00 /* 0 0 0 Transfer Host -> DMA */ + #define XFER_DMA_HOST 0x01 /* 0 0 1 Transfer DMA -> Host */ + #define XFER_HOST_MPU 0x02 /* 0 1 0 Transfer Host -> MPU */ + #define XFER_MPU_HOST 0x03 /* 0 1 1 Transfer MPU -> Host */ + #define XFER_DMA_MPU 0x04 /* 1 0 0 Transfer DMA -> MPU */ + #define XFER_MPU_DMA 0x05 /* 1 0 1 Transfer MPU -> DMA */ + #define SET_SEMAPHORE 0x06 /* 1 1 0 Set Semaphore */ + #define XFER_NOP 0x07 /* 1 1 1 Transfer NOP */ + #define XFER_MB_MPU 0x06 /* 1 1 0 Transfer MB -> MPU */ + #define XFER_MB_DMA 0x07 /* 1 1 1 Transfer MB -> DMA */ + + + #define XFER_HOST_AUTO 0x00 /* 0 0 Auto Transfer Size */ + #define XFER_HOST_8BIT 0x08 /* 0 1 8 BIT Transfer Size */ + #define XFER_HOST_16BIT 0x10 /* 1 0 16 BIT Transfer Size */ + #define XFER_HOST_32BIT 0x18 /* 1 1 32 BIT Transfer Size */ + + #define XFER_DMA_8BIT 0x20 /* 0 1 8 BIT Transfer Size */ + #define XFER_DMA_16BIT 0x40 /* 1 0 16 BIT Transfer Size */ + + #define DISABLE_INT BIT(7) /*Do not interrupt at end of cmd. */ + + #define HOST_WRT_CMD ((DISABLE_INT + XFER_HOST_DMA + XFER_HOST_AUTO + XFER_DMA_8BIT)) + #define HOST_RD_CMD ((DISABLE_INT + XFER_DMA_HOST + XFER_HOST_AUTO + XFER_DMA_8BIT)) + #define WIDE_HOST_WRT_CMD ((DISABLE_INT + XFER_HOST_DMA + XFER_HOST_AUTO + XFER_DMA_16BIT)) + #define WIDE_HOST_RD_CMD ((DISABLE_INT + XFER_DMA_HOST + XFER_HOST_AUTO + XFER_DMA_16BIT)) + + #define hp_host_addr_lo 0x1C + #define hp_host_addr_lmi 0x1D + #define hp_host_addr_hmi 0x1E + #define hp_host_addr_hi 0x1F + + #define hp_pio_data 0x20 + #define hp_reserved_21 0x21 + #define hp_ee_ctrl 0x22 + + #define EXT_ARB_ACK BIT(7) + #define SCSI_TERM_ENA_H BIT(6) /* SCSI high byte terminator */ + #define SEE_MS BIT(5) + #define SEE_CS BIT(3) + #define SEE_CLK BIT(2) + #define SEE_DO BIT(1) + #define SEE_DI BIT(0) + + #define EE_READ 0x06 + #define EE_WRITE 0x05 + #define EWEN 0x04 + #define EWEN_ADDR 0x03C0 + #define EWDS 0x04 + #define EWDS_ADDR 0x0000 + + #define hp_brdctl 0x23 + + #define DAT_7 BIT(7) + #define DAT_6 BIT(6) + #define DAT_5 BIT(5) + #define BRD_STB BIT(4) + #define BRD_CS BIT(3) + #define BRD_WR BIT(2) + + #define hp_reserved_24 0x24 + #define hp_reserved_25 0x25 + + + + + #define hp_bm_ctrl 0x26 + + #define SCSI_TERM_ENA_L BIT(0) /*Enable/Disable external terminators */ + #define FLUSH_XFER_CNTR BIT(1) /*Flush transfer counter */ + #define BM_XFER_MIN_8 BIT(2) /*Enable bus master transfer of 9 */ + #define BIOS_ENA BIT(3) /*Enable BIOS/FLASH Enable */ + #define FORCE1_XFER BIT(5) /*Always xfer one byte in byte mode */ + #define FAST_SINGLE BIT(6) /*?? */ + + #define BMCTRL_DEFAULT (FORCE1_XFER|FAST_SINGLE|SCSI_TERM_ENA_L) + + #define hp_reserved_27 0x27 + + #define hp_sg_addr 0x28 + #define hp_page_ctrl 0x29 + + #define SCATTER_EN BIT(0) + #define SGRAM_ARAM BIT(1) + #define BIOS_SHADOW BIT(2) + #define G_INT_DISABLE BIT(3) /* Enable/Disable all Interrupts */ + #define NARROW_SCSI_CARD BIT(4) /* NARROW/WIDE SCSI config pin */ + + #define hp_reserved_2A 0x2A + #define hp_pci_cmd_cfg 0x2B + + #define IO_SPACE_ENA BIT(0) /*enable I/O space */ + #define MEM_SPACE_ENA BIT(1) /*enable memory space */ + #define BUS_MSTR_ENA BIT(2) /*enable bus master operation */ + #define MEM_WI_ENA BIT(4) /*enable Write and Invalidate */ + #define PAR_ERR_RESP BIT(6) /*enable parity error responce. */ + + #define hp_reserved_2C 0x2C + + #define hp_pci_stat_cfg 0x2D + + #define DATA_PARITY_ERR BIT(0) + #define REC_TARGET_ABORT BIT(4) /*received Target abort */ + #define REC_MASTER_ABORT BIT(5) /*received Master abort */ + #define SIG_SYSTEM_ERR BIT(6) + #define DETECTED_PAR_ERR BIT(7) + + #define hp_reserved_2E 0x2E + + #define hp_sys_status 0x2F + + #define SLV_DATA_RDY BIT(0) /*Slave data ready */ + #define XFER_CNT_ZERO BIT(1) /*Transfer counter = 0 */ + #define BM_FIFO_EMPTY BIT(2) /*FIFO empty */ + #define BM_FIFO_FULL BIT(3) /*FIFO full */ + #define HOST_OP_DONE BIT(4) /*host operation done */ + #define DMA_OP_DONE BIT(5) /*DMA operation done */ + #define SLV_OP_DONE BIT(6) /*Slave operation done */ + #define PWR_ON_FLAG BIT(7) /*Power on flag */ + + #define hp_reserved_30 0x30 + + #define hp_host_status0 0x31 + + #define HOST_TERM BIT(5) /*Host Terminal Count */ + #define HOST_TRSHLD BIT(6) /*Host Threshold */ + #define CONNECTED_2_HOST BIT(7) /*Connected to Host */ + + #define hp_reserved_32 0x32 + + #define hp_rev_num 0x33 + + #define REV_A_CONST 0x0E + #define REV_B_CONST 0x0E + + #define hp_stack_data 0x34 + #define hp_stack_addr 0x35 + + #define hp_ext_status 0x36 + + #define BM_FORCE_OFF BIT(0) /*Bus Master is forced to get off */ + #define PCI_TGT_ABORT BIT(0) /*PCI bus master transaction aborted */ + #define PCI_DEV_TMOUT BIT(1) /*PCI Device Time out */ + #define FIFO_TC_NOT_ZERO BIT(2) /*FIFO or transfer counter not zero */ + #define CHIP_RST_OCCUR BIT(3) /*Chip reset occurs */ + #define CMD_ABORTED BIT(4) /*Command aborted */ + #define BM_PARITY_ERR BIT(5) /*parity error on data received */ + #define PIO_OVERRUN BIT(6) /*Slave data overrun */ + #define BM_CMD_BUSY BIT(7) /*Bus master transfer command busy */ + #define BAD_EXT_STATUS (BM_FORCE_OFF | PCI_DEV_TMOUT | CMD_ABORTED | \ + BM_PARITY_ERR | PIO_OVERRUN) + + #define hp_int_status 0x37 + + #define BM_CMD_CMPL BIT(0) /*Bus Master command complete */ + #define EXT_STATUS_ON BIT(1) /*Extended status is valid */ + #define SCSI_INTERRUPT BIT(2) /*Global indication of a SCSI int. */ + #define BM_FIFO_RDY BIT(4) + #define INT_ASSERTED BIT(5) /* */ + #define SRAM_BUSY BIT(6) /*Scatter/Gather RAM busy */ + #define CMD_REG_BUSY BIT(7) + + + #define hp_fifo_cnt 0x38 + #define hp_curr_host_cnt 0x39 + #define hp_reserved_3A 0x3A + #define hp_fifo_in_addr 0x3B + + #define hp_fifo_out_addr 0x3C + #define hp_reserved_3D 0x3D + #define hp_reserved_3E 0x3E + #define hp_reserved_3F 0x3F + + + + extern USHORT default_intena; + + #define hp_intena 0x40 + + #define RESET BITW(7) + #define PROG_HLT BITW(6) + #define PARITY BITW(5) + #define FIFO BITW(4) + #define SEL BITW(3) + #define SCAM_SEL BITW(2) + #define RSEL BITW(1) + #define TIMEOUT BITW(0) + #define BUS_FREE BITW(15) + #define XFER_CNT_0 BITW(14) + #define PHASE BITW(13) + #define IUNKWN BITW(12) + #define ICMD_COMP BITW(11) + #define ITICKLE BITW(10) + #define IDO_STRT BITW(9) + #define ITAR_DISC BITW(8) + #define AUTO_INT (BITW(12)+BITW(11)+BITW(10)+BITW(9)+BITW(8)) + #define CLR_ALL_INT 0xFFFF + #define CLR_ALL_INT_1 0xFF00 + + #define hp_intstat 0x42 + + #define hp_scsisig 0x44 + + #define SCSI_SEL BIT(7) + #define SCSI_BSY BIT(6) + #define SCSI_REQ BIT(5) + #define SCSI_ACK BIT(4) + #define SCSI_ATN BIT(3) + #define SCSI_CD BIT(2) + #define SCSI_MSG BIT(1) + #define SCSI_IOBIT BIT(0) + + #define S_SCSI_PHZ (BIT(2)+BIT(1)+BIT(0)) + #define S_CMD_PH (BIT(2) ) + #define S_MSGO_PH (BIT(2)+BIT(1) ) + #define S_STAT_PH (BIT(2) +BIT(0)) + #define S_MSGI_PH (BIT(2)+BIT(1)+BIT(0)) + #define S_DATAI_PH ( BIT(0)) + #define S_DATAO_PH 0x00 + #define S_ILL_PH ( BIT(1) ) + + #define hp_scsictrl_0 0x45 + + #define NO_ARB BIT(7) + #define SEL_TAR BIT(6) + #define ENA_ATN BIT(4) + #define ENA_RESEL BIT(2) + #define SCSI_RST BIT(1) + #define ENA_SCAM_SEL BIT(0) + + + + #define hp_portctrl_0 0x46 + + #define SCSI_PORT BIT(7) + #define SCSI_INBIT BIT(6) + #define DMA_PORT BIT(5) + #define DMA_RD BIT(4) + #define HOST_PORT BIT(3) + #define HOST_WRT BIT(2) + #define SCSI_BUS_EN BIT(1) + #define START_TO BIT(0) + + #define hp_scsireset 0x47 + + #define SCSI_TAR BIT(7) + #define SCSI_INI BIT(6) + #define SCAM_EN BIT(5) + #define ACK_HOLD BIT(4) + #define DMA_RESET BIT(3) + #define HPSCSI_RESET BIT(2) + #define PROG_RESET BIT(1) + #define FIFO_CLR BIT(0) + + #define hp_xfercnt_0 0x48 + #define hp_xfercnt_1 0x49 + #define hp_xfercnt_2 0x4A + #define hp_xfercnt_3 0x4B + + #define hp_fifodata_0 0x4C + #define hp_fifodata_1 0x4D + #define hp_addstat 0x4E + + #define SCAM_TIMER BIT(7) + #define AUTO_RUNNING BIT(6) + #define FAST_SYNC BIT(5) + #define SCSI_MODE8 BIT(3) + #define SCSI_PAR_ERR BIT(0) + + #define hp_prgmcnt_0 0x4F + + #define AUTO_PC_MASK 0x3F + + #define hp_selfid_0 0x50 + #define hp_selfid_1 0x51 + #define hp_arb_id 0x52 + + #define ARB_ID (BIT(3) + BIT(2) + BIT(1) + BIT(0)) + + #define hp_select_id 0x53 + + #define RESEL_ID (BIT(7) + BIT(6) + BIT(5) + BIT(4)) + #define SELECT_ID (BIT(3) + BIT(2) + BIT(1) + BIT(0)) + + #define hp_synctarg_base 0x54 + #define hp_synctarg_12 0x54 + #define hp_synctarg_13 0x55 + #define hp_synctarg_14 0x56 + #define hp_synctarg_15 0x57 + + #define hp_synctarg_8 0x58 + #define hp_synctarg_9 0x59 + #define hp_synctarg_10 0x5A + #define hp_synctarg_11 0x5B + + #define hp_synctarg_4 0x5C + #define hp_synctarg_5 0x5D + #define hp_synctarg_6 0x5E + #define hp_synctarg_7 0x5F + + #define hp_synctarg_0 0x60 + #define hp_synctarg_1 0x61 + #define hp_synctarg_2 0x62 + #define hp_synctarg_3 0x63 + + #define RATE_20MB 0x00 + #define RATE_10MB ( BIT(5)) + #define RATE_6_6MB ( BIT(6) ) + #define RATE_5MB ( BIT(6)+BIT(5)) + #define RATE_4MB (BIT(7) ) + #define RATE_3_33MB (BIT(7) +BIT(5)) + #define RATE_2_85MB (BIT(7)+BIT(6) ) + #define RATE_2_5MB (BIT(7)+BIT(5)+BIT(6)) + #define NEXT_CLK BIT(5) + #define SLOWEST_SYNC (BIT(7)+BIT(6)+BIT(5)) + #define NARROW_SCSI BIT(4) + #define SYNC_OFFSET (BIT(3) + BIT(2) + BIT(1) + BIT(0)) + #define DEFAULT_ASYNC 0x00 + #define DEFAULT_OFFSET 0x0F + + #define hp_autostart_0 0x64 + #define hp_autostart_1 0x65 + #define hp_autostart_2 0x66 + #define hp_autostart_3 0x67 + + + + #define DISABLE 0x00 + #define AUTO_IMMED BIT(5) + #define SELECT BIT(6) + #define RESELECT (BIT(6)+BIT(5)) + #define BUSFREE BIT(7) + #define XFER_0 (BIT(7)+BIT(5)) + #define END_DATA (BIT(7)+BIT(6)) + #define MSG_PHZ (BIT(7)+BIT(6)+BIT(5)) + + #define hp_gp_reg_0 0x68 + #define hp_gp_reg_1 0x69 + #define hp_gp_reg_2 0x6A + #define hp_gp_reg_3 0x6B + + #define hp_seltimeout 0x6C + + + #define TO_2ms 0x54 /* 2.0503ms */ + #define TO_4ms 0x67 /* 3.9959ms */ + + #define TO_5ms 0x03 /* 4.9152ms */ + #define TO_10ms 0x07 /* 11.xxxms */ + #define TO_250ms 0x99 /* 250.68ms */ + #define TO_290ms 0xB1 /* 289.99ms */ + #define TO_350ms 0xD6 /* 350.62ms */ + #define TO_417ms 0xFF /* 417.79ms */ + + #define hp_clkctrl_0 0x6D + + #define PWR_DWN BIT(6) + #define ACTdeassert BIT(4) + #define ATNonErr BIT(3) + #define CLK_30MHZ BIT(1) + #define CLK_40MHZ (BIT(1) + BIT(0)) + #define CLK_50MHZ BIT(2) + + #define CLKCTRL_DEFAULT (ACTdeassert | CLK_40MHZ) + + #define hp_fiforead 0x6E + #define hp_fifowrite 0x6F + + #define hp_offsetctr 0x70 + #define hp_xferstat 0x71 + + #define FIFO_FULL BIT(7) + #define FIFO_EMPTY BIT(6) + #define FIFO_MASK 0x3F /* Mask for the FIFO count value. */ + #define FIFO_LEN 0x20 + + #define hp_portctrl_1 0x72 + + #define EVEN_HOST_P BIT(5) + #define INVT_SCSI BIT(4) + #define CHK_SCSI_P BIT(3) + #define HOST_MODE8 BIT(0) + #define HOST_MODE16 0x00 + + #define hp_xfer_pad 0x73 + + #define ID_UNLOCK BIT(3) + #define XFER_PAD BIT(2) + + #define hp_scsidata_0 0x74 + #define hp_scsidata_1 0x75 + #define hp_timer_0 0x76 + #define hp_timer_1 0x77 + + #define hp_reserved_78 0x78 + #define hp_reserved_79 0x79 + #define hp_reserved_7A 0x7A + #define hp_reserved_7B 0x7B + + #define hp_reserved_7C 0x7C + #define hp_reserved_7D 0x7D + #define hp_reserved_7E 0x7E + #define hp_reserved_7F 0x7F + + #define hp_aramBase 0x80 + #define BIOS_DATA_OFFSET 0x60 + #define BIOS_RELATIVE_CARD 0x64 + + + + + #define AUTO_LEN 0x80 + #define AR0 0x00 + #define AR1 BITW(8) + #define AR2 BITW(9) + #define AR3 (BITW(9) + BITW(8)) + #define SDATA BITW(10) + + #define NOP_OP 0x00 /* Nop command */ + + #define CRD_OP BITW(11) /* Cmp Reg. w/ Data */ + + #define CRR_OP BITW(12) /* Cmp Reg. w. Reg. */ + + #define CBE_OP (BITW(14)+BITW(12)+BITW(11)) /* Cmp SCSI cmd class & Branch EQ */ + + #define CBN_OP (BITW(14)+BITW(13)) /* Cmp SCSI cmd class & Branch NOT EQ */ + + #define CPE_OP (BITW(14)+BITW(11)) /* Cmp SCSI phs & Branch EQ */ + + #define CPN_OP (BITW(14)+BITW(12)) /* Cmp SCSI phs & Branch NOT EQ */ + + + #define ADATA_OUT 0x00 + #define ADATA_IN BITW(8) + #define ACOMMAND BITW(10) + #define ASTATUS (BITW(10)+BITW(8)) + #define AMSG_OUT (BITW(10)+BITW(9)) + #define AMSG_IN (BITW(10)+BITW(9)+BITW(8)) + #define AILLEGAL (BITW(9)+BITW(8)) + + + #define BRH_OP BITW(13) /* Branch */ + + + #define ALWAYS 0x00 + #define EQUAL BITW(8) + #define NOT_EQ BITW(9) + + #define TCB_OP (BITW(13)+BITW(11)) /* Test condition & branch */ + + + #define ATN_SET BITW(8) + #define ATN_RESET BITW(9) + #define XFER_CNT (BITW(9)+BITW(8)) + #define FIFO_0 BITW(10) + #define FIFO_NOT0 (BITW(10)+BITW(8)) + #define T_USE_SYNC0 (BITW(10)+BITW(9)) + + + #define MPM_OP BITW(15) /* Match phase and move data */ + + #define MDR_OP (BITW(12)+BITW(11)) /* Move data to Reg. */ + + #define MRR_OP BITW(14) /* Move DReg. to Reg. */ + + + #define S_IDREG (BIT(2)+BIT(1)+BIT(0)) + + + #define D_AR0 0x00 + #define D_AR1 BIT(0) + #define D_AR2 BIT(1) + #define D_AR3 (BIT(1) + BIT(0)) + #define D_SDATA BIT(2) + #define D_BUCKET (BIT(2) + BIT(1) + BIT(0)) + + + #define ADR_OP (BITW(13)+BITW(12)) /* Logical AND Reg. w. Data */ + + #define ADS_OP (BITW(14)+BITW(13)+BITW(12)) + + #define ODR_OP (BITW(13)+BITW(12)+BITW(11)) + + #define ODS_OP (BITW(14)+BITW(13)+BITW(12)+BITW(11)) + + #define STR_OP (BITW(15)+BITW(14)) /* Store to A_Reg. */ + + #define AINT_ENA1 0x00 + #define AINT_STAT1 BITW(8) + #define ASCSI_SIG BITW(9) + #define ASCSI_CNTL (BITW(9)+BITW(8)) + #define APORT_CNTL BITW(10) + #define ARST_CNTL (BITW(10)+BITW(8)) + #define AXFERCNT0 (BITW(10)+BITW(9)) + #define AXFERCNT1 (BITW(10)+BITW(9)+BITW(8)) + #define AXFERCNT2 BITW(11) + #define AFIFO_DATA (BITW(11)+BITW(8)) + #define ASCSISELID (BITW(11)+BITW(9)) + #define ASCSISYNC0 (BITW(11)+BITW(9)+BITW(8)) + + + #define RAT_OP (BITW(14)+BITW(13)+BITW(11)) + + #define SSI_OP (BITW(15)+BITW(11)) + + + #define SSI_ITAR_DISC (ITAR_DISC >> 8) + #define SSI_IDO_STRT (IDO_STRT >> 8) + #define SSI_IDI_STRT (IDO_STRT >> 8) + + #define SSI_ICMD_COMP (ICMD_COMP >> 8) + #define SSI_ITICKLE (ITICKLE >> 8) + + #define SSI_IUNKWN (IUNKWN >> 8) + #define SSI_INO_CC (IUNKWN >> 8) + #define SSI_IRFAIL (IUNKWN >> 8) + + + #define NP 0x10 /*Next Phase */ + #define NTCMD 0x02 /*Non- Tagged Command start */ + #define CMDPZ 0x04 /*Command phase */ + #define DINT 0x12 /*Data Out/In interrupt */ + #define DI 0x13 /*Data Out */ + #define MI 0x14 /*Message In */ + #define DC 0x19 /*Disconnect Message */ + #define ST 0x1D /*Status Phase */ + #define UNKNWN 0x24 /*Unknown bus action */ + #define CC 0x25 /*Command Completion failure */ + #define TICK 0x26 /*New target reselected us. */ + #define RFAIL 0x27 /*Reselection failed */ + #define SELCHK 0x28 /*Select & Check SCSI ID latch reg */ + + + #define ID_MSG_STRT hp_aramBase + 0x00 + #define NON_TAG_ID_MSG hp_aramBase + 0x06 + #define CMD_STRT hp_aramBase + 0x08 + #define SYNC_MSGS hp_aramBase + 0x08 + + + + + + #define TAG_STRT 0x00 + #define SELECTION_START 0x00 + #define DISCONNECT_START 0x10/2 + #define END_DATA_START 0x14/2 + #define NONTAG_STRT 0x02/2 + #define CMD_ONLY_STRT CMDPZ/2 + #define TICKLE_STRT TICK/2 + #define SELCHK_STRT SELCHK/2 + + + + +#define mEEPROM_CLK_DELAY(port) (RD_HARPOON(port+hp_intstat_1)) + +#define mWAIT_10MS(port) (RD_HARPOON(port+hp_intstat_1)) + + +#define CLR_XFER_CNT(port) (WR_HARPOON(port+hp_xfercnt_0, 0x00)) + +#define SET_XFER_CNT(port, data) (WR_HARP32(port,hp_xfercnt_0,data)) + +#define GET_XFER_CNT(port, xfercnt) {RD_HARP32(port,hp_xfercnt_0,xfercnt); xfercnt &= 0xFFFFFF;} +/* #define GET_XFER_CNT(port, xfercnt) (xfercnt = RD_HARPOON(port+hp_xfercnt_2), \ + xfercnt <<= 16,\ + xfercnt |= RDW_HARPOON((USHORT)(port+hp_xfercnt_0))) + */ +#if defined(DOS) +#define HP_SETUP_ADDR_CNT(port,addr,count) (WRW_HARPOON((USHORT)(port+hp_host_addr_lo), (USHORT)(addr & 0x0000FFFFL)),\ + addr >>= 16,\ + WRW_HARPOON((USHORT)(port+hp_host_addr_hmi), (USHORT)(addr & 0x0000FFFFL)),\ + WR_HARP32(port,hp_xfercnt_0,count),\ + WRW_HARPOON((USHORT)(port+hp_xfer_cnt_lo), (USHORT)(count & 0x0000FFFFL)),\ + count >>= 16,\ + WR_HARPOON(port+hp_xfer_cnt_hi, (count & 0xFF))) +#else +#define HP_SETUP_ADDR_CNT(port,addr,count) (WRW_HARPOON((port+hp_host_addr_lo), (USHORT)(addr & 0x0000FFFFL)),\ + addr >>= 16,\ + WRW_HARPOON((port+hp_host_addr_hmi), (USHORT)(addr & 0x0000FFFFL)),\ + WR_HARP32(port,hp_xfercnt_0,count),\ + WRW_HARPOON((port+hp_xfer_cnt_lo), (USHORT)(count & 0x0000FFFFL)),\ + count >>= 16,\ + WR_HARPOON(port+hp_xfer_cnt_hi, (count & 0xFF))) +#endif + +#define ACCEPT_MSG(port) {while(RD_HARPOON(port+hp_scsisig) & SCSI_REQ){}\ + WR_HARPOON(port+hp_scsisig, S_ILL_PH);} + + +#define ACCEPT_MSG_ATN(port) {while(RD_HARPOON(port+hp_scsisig) & SCSI_REQ){}\ + WR_HARPOON(port+hp_scsisig, (S_ILL_PH|SCSI_ATN));} + +#define ACCEPT_STAT(port) {while(RD_HARPOON(port+hp_scsisig) & SCSI_REQ){}\ + WR_HARPOON(port+hp_scsisig, S_ILL_PH);} + +#define ACCEPT_STAT_ATN(port) {while(RD_HARPOON(port+hp_scsisig) & SCSI_REQ){}\ + WR_HARPOON(port+hp_scsisig, (S_ILL_PH|SCSI_ATN));} + +#define DISABLE_AUTO(port) (WR_HARPOON(port+hp_scsireset, PROG_RESET),\ + WR_HARPOON(port+hp_scsireset, 0x00)) + +#define ARAM_ACCESS(p_port) (WR_HARPOON(p_port+hp_page_ctrl, \ + (RD_HARPOON(p_port+hp_page_ctrl) | SGRAM_ARAM))) + +#define SGRAM_ACCESS(p_port) (WR_HARPOON(p_port+hp_page_ctrl, \ + (RD_HARPOON(p_port+hp_page_ctrl) & ~SGRAM_ARAM))) + +#define MDISABLE_INT(p_port) (WR_HARPOON(p_port+hp_page_ctrl, \ + (RD_HARPOON(p_port+hp_page_ctrl) | G_INT_DISABLE))) + +#define MENABLE_INT(p_port) (WR_HARPOON(p_port+hp_page_ctrl, \ + (RD_HARPOON(p_port+hp_page_ctrl) & ~G_INT_DISABLE))) + + + +#endif + + +#if (FW_TYPE==_UCB_MGR_) +void ReadNVRam(PSCCBcard pCurrCard,PUCB p_ucb); +void WriteNVRam(PSCCBcard pCurrCard,PUCB p_ucb); +void UpdateCheckSum(u32bits baseport); +#endif // (FW_TYPE==_UCB_MGR_) + +#if defined(DOS) +UCHAR sfm(USHORT port, PSCCB pcurrSCCB); +void scsiStartAuto(USHORT port); +UCHAR sisyncn(USHORT port, UCHAR p_card, UCHAR syncFlag); +void ssel(USHORT port, UCHAR p_card); +void sres(USHORT port, UCHAR p_card, PSCCBcard pCurrCard); +void sdecm(UCHAR message, USHORT port, UCHAR p_card); +void shandem(USHORT port, UCHAR p_card,PSCCB pCurrSCCB); +void stsyncn(USHORT port, UCHAR p_card); +void sisyncr(USHORT port,UCHAR sync_pulse, UCHAR offset); +void sssyncv(USHORT p_port, UCHAR p_id, UCHAR p_sync_value, PSCCBMgr_tar_info currTar_Info); +void sresb(USHORT port, UCHAR p_card); +void sxfrp(USHORT p_port, UCHAR p_card); +void schkdd(USHORT port, UCHAR p_card); +UCHAR RdStack(USHORT port, UCHAR index); +void WrStack(USHORT portBase, UCHAR index, UCHAR data); +UCHAR ChkIfChipInitialized(USHORT ioPort); + +#if defined(V302) +UCHAR GetTarLun(USHORT port, UCHAR p_card, UCHAR our_target, PSCCBcard pCurrCard, PUCHAR tag, PUCHAR lun); +#endif + +void SendMsg(USHORT port, UCHAR message); +void queueFlushTargSccb(UCHAR p_card, UCHAR thisTarg, UCHAR error_code); +UCHAR scsellDOS(USHORT p_port, UCHAR targ_id); +#else +UCHAR sfm(ULONG port, PSCCB pcurrSCCB); +void scsiStartAuto(ULONG port); +UCHAR sisyncn(ULONG port, UCHAR p_card, UCHAR syncFlag); +void ssel(ULONG port, UCHAR p_card); +void sres(ULONG port, UCHAR p_card, PSCCBcard pCurrCard); +void sdecm(UCHAR message, ULONG port, UCHAR p_card); +void shandem(ULONG port, UCHAR p_card,PSCCB pCurrSCCB); +void stsyncn(ULONG port, UCHAR p_card); +void sisyncr(ULONG port,UCHAR sync_pulse, UCHAR offset); +void sssyncv(ULONG p_port, UCHAR p_id, UCHAR p_sync_value, PSCCBMgr_tar_info currTar_Info); +void sresb(ULONG port, UCHAR p_card); +void sxfrp(ULONG p_port, UCHAR p_card); +void schkdd(ULONG port, UCHAR p_card); +UCHAR RdStack(ULONG port, UCHAR index); +void WrStack(ULONG portBase, UCHAR index, UCHAR data); +UCHAR ChkIfChipInitialized(ULONG ioPort); + +#if defined(V302) +UCHAR GetTarLun(ULONG port, UCHAR p_card, UCHAR our_target, PSCCBcard pCurrCard, PUCHAR tar, PUCHAR lun); +#endif + +void SendMsg(ULONG port, UCHAR message); +void queueFlushTargSccb(UCHAR p_card, UCHAR thisTarg, UCHAR error_code); +#endif + +void ssenss(PSCCBcard pCurrCard); +void sinits(PSCCB p_sccb, UCHAR p_card); +void RNVRamData(PNVRamInfo pNvRamInfo); + +#if defined(WIDE_SCSI) + #if defined(DOS) + UCHAR siwidn(USHORT port, UCHAR p_card); + void stwidn(USHORT port, UCHAR p_card); + void siwidr(USHORT port, UCHAR width); + #else + UCHAR siwidn(ULONG port, UCHAR p_card); + void stwidn(ULONG port, UCHAR p_card); + void siwidr(ULONG port, UCHAR width); + #endif +#endif + + +void queueSelectFail(PSCCBcard pCurrCard, UCHAR p_card); +void queueDisconnect(PSCCB p_SCCB, UCHAR p_card); +void queueCmdComplete(PSCCBcard pCurrCard, PSCCB p_SCCB, UCHAR p_card); +void queueSearchSelect(PSCCBcard pCurrCard, UCHAR p_card); +void queueFlushSccb(UCHAR p_card, UCHAR error_code); +void queueAddSccb(PSCCB p_SCCB, UCHAR card); +UCHAR queueFindSccb(PSCCB p_SCCB, UCHAR p_card); +void utilUpdateResidual(PSCCB p_SCCB); +USHORT CalcCrc16(UCHAR buffer[]); +UCHAR CalcLrc(UCHAR buffer[]); + + +#if defined(DOS) +void Wait1Second(USHORT p_port); +void Wait(USHORT p_port, UCHAR p_delay); +void utilEEWriteOnOff(USHORT p_port,UCHAR p_mode); +void utilEEWrite(USHORT p_port, USHORT ee_data, USHORT ee_addr); +USHORT utilEERead(USHORT p_port, USHORT ee_addr); +USHORT utilEEReadOrg(USHORT p_port, USHORT ee_addr); +void utilEESendCmdAddr(USHORT p_port, UCHAR ee_cmd, USHORT ee_addr); +#else +void Wait1Second(ULONG p_port); +void Wait(ULONG p_port, UCHAR p_delay); +void utilEEWriteOnOff(ULONG p_port,UCHAR p_mode); +void utilEEWrite(ULONG p_port, USHORT ee_data, USHORT ee_addr); +USHORT utilEERead(ULONG p_port, USHORT ee_addr); +USHORT utilEEReadOrg(ULONG p_port, USHORT ee_addr); +void utilEESendCmdAddr(ULONG p_port, UCHAR ee_cmd, USHORT ee_addr); +#endif + + + +#if defined(OS2) + void far phaseDataOut(ULONG port, UCHAR p_card); + void far phaseDataIn(ULONG port, UCHAR p_card); + void far phaseCommand(ULONG port, UCHAR p_card); + void far phaseStatus(ULONG port, UCHAR p_card); + void far phaseMsgOut(ULONG port, UCHAR p_card); + void far phaseMsgIn(ULONG port, UCHAR p_card); + void far phaseIllegal(ULONG port, UCHAR p_card); +#else + #if defined(DOS) + void phaseDataOut(USHORT port, UCHAR p_card); + void phaseDataIn(USHORT port, UCHAR p_card); + void phaseCommand(USHORT port, UCHAR p_card); + void phaseStatus(USHORT port, UCHAR p_card); + void phaseMsgOut(USHORT port, UCHAR p_card); + void phaseMsgIn(USHORT port, UCHAR p_card); + void phaseIllegal(USHORT port, UCHAR p_card); + #else + void phaseDataOut(ULONG port, UCHAR p_card); + void phaseDataIn(ULONG port, UCHAR p_card); + void phaseCommand(ULONG port, UCHAR p_card); + void phaseStatus(ULONG port, UCHAR p_card); + void phaseMsgOut(ULONG port, UCHAR p_card); + void phaseMsgIn(ULONG port, UCHAR p_card); + void phaseIllegal(ULONG port, UCHAR p_card); + #endif +#endif + +#if defined(DOS) +void phaseDecode(USHORT port, UCHAR p_card); +void phaseChkFifo(USHORT port, UCHAR p_card); +void phaseBusFree(USHORT p_port, UCHAR p_card); +#else +void phaseDecode(ULONG port, UCHAR p_card); +void phaseChkFifo(ULONG port, UCHAR p_card); +void phaseBusFree(ULONG p_port, UCHAR p_card); +#endif + + + + +#if defined(DOS) +void XbowInit(USHORT port, UCHAR scamFlg); +void BusMasterInit(USHORT p_port); +int DiagXbow(USHORT port); +int DiagBusMaster(USHORT port); +void DiagEEPROM(USHORT p_port); +#else +void XbowInit(ULONG port, UCHAR scamFlg); +void BusMasterInit(ULONG p_port); +int DiagXbow(ULONG port); +int DiagBusMaster(ULONG port); +void DiagEEPROM(ULONG p_port); +#endif + + + + +#if defined(DOS) +void busMstrAbort(USHORT port); +UCHAR busMstrTimeOut(USHORT port); +void dataXferProcessor(USHORT port, PSCCBcard pCurrCard); +void busMstrSGDataXferStart(USHORT port, PSCCB pCurrSCCB); +void busMstrDataXferStart(USHORT port, PSCCB pCurrSCCB); +void hostDataXferAbort(USHORT port, UCHAR p_card, PSCCB pCurrSCCB); +#else +void busMstrAbort(ULONG port); +UCHAR busMstrTimeOut(ULONG port); +void dataXferProcessor(ULONG port, PSCCBcard pCurrCard); +void busMstrSGDataXferStart(ULONG port, PSCCB pCurrSCCB); +void busMstrDataXferStart(ULONG port, PSCCB pCurrSCCB); +void hostDataXferAbort(ULONG port, UCHAR p_card, PSCCB pCurrSCCB); +#endif +void hostDataXferRestart(PSCCB currSCCB); + + +#if defined (DOS) +UCHAR SccbMgr_bad_isr(USHORT p_port, UCHAR p_card, PSCCBcard pCurrCard, USHORT p_int); +#else +UCHAR SccbMgr_bad_isr(ULONG p_port, UCHAR p_card, PSCCBcard pCurrCard, USHORT p_int); + +#endif + +void SccbMgrTableInitAll(void); +void SccbMgrTableInitCard(PSCCBcard pCurrCard, UCHAR p_card); +void SccbMgrTableInitTarget(UCHAR p_card, UCHAR target); + + + +void scini(UCHAR p_card, UCHAR p_our_id, UCHAR p_power_up); + +#if defined(DOS) +int scarb(USHORT p_port, UCHAR p_sel_type); +void scbusf(USHORT p_port); +void scsel(USHORT p_port); +void scasid(UCHAR p_card, USHORT p_port); +UCHAR scxferc(USHORT p_port, UCHAR p_data); +UCHAR scsendi(USHORT p_port, UCHAR p_id_string[]); +UCHAR sciso(USHORT p_port, UCHAR p_id_string[]); +void scwirod(USHORT p_port, UCHAR p_data_bit); +void scwiros(USHORT p_port, UCHAR p_data_bit); +UCHAR scvalq(UCHAR p_quintet); +UCHAR scsell(USHORT p_port, UCHAR targ_id); +void scwtsel(USHORT p_port); +void inisci(UCHAR p_card, USHORT p_port, UCHAR p_our_id); +void scsavdi(UCHAR p_card, USHORT p_port); +#else +int scarb(ULONG p_port, UCHAR p_sel_type); +void scbusf(ULONG p_port); +void scsel(ULONG p_port); +void scasid(UCHAR p_card, ULONG p_port); +UCHAR scxferc(ULONG p_port, UCHAR p_data); +UCHAR scsendi(ULONG p_port, UCHAR p_id_string[]); +UCHAR sciso(ULONG p_port, UCHAR p_id_string[]); +void scwirod(ULONG p_port, UCHAR p_data_bit); +void scwiros(ULONG p_port, UCHAR p_data_bit); +UCHAR scvalq(UCHAR p_quintet); +UCHAR scsell(ULONG p_port, UCHAR targ_id); +void scwtsel(ULONG p_port); +void inisci(UCHAR p_card, ULONG p_port, UCHAR p_our_id); +void scsavdi(UCHAR p_card, ULONG p_port); +#endif +UCHAR scmachid(UCHAR p_card, UCHAR p_id_string[]); + + +#if defined(DOS) +void autoCmdCmplt(USHORT p_port, UCHAR p_card); +void autoLoadDefaultMap(USHORT p_port); +#else +void autoCmdCmplt(ULONG p_port, UCHAR p_card); +void autoLoadDefaultMap(ULONG p_port); +#endif + + + +#if (FW_TYPE==_SCCB_MGR_) + void OS_start_timer(unsigned long ioport, unsigned long timeout); + void OS_stop_timer(unsigned long ioport, unsigned long timeout); + void OS_disable_int(unsigned char intvec); + void OS_enable_int(unsigned char intvec); + void OS_delay(unsigned long count); + int OS_VirtToPhys(u32bits CardHandle, u32bits *physaddr, u32bits *virtaddr); + #if !(defined(UNIX) || defined(OS2) || defined(SOLARIS_REAL_MODE)) + void OS_Lock(PSCCBMGR_INFO pCardInfo); + void OS_UnLock(PSCCBMGR_INFO pCardInfo); +#endif // if FW_TYPE == ... + +#endif + +extern SCCBCARD BL_Card[MAX_CARDS]; +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; + + +#if defined(OS2) + extern void (far *s_PhaseTbl[8]) (ULONG, UCHAR); +#else + #if defined(DOS) + extern void (*s_PhaseTbl[8]) (USHORT, UCHAR); + #else + extern void (*s_PhaseTbl[8]) (ULONG, UCHAR); + #endif +#endif + +extern SCCBSCAM_INFO scamInfo[MAX_SCSI_TAR]; +extern NVRAMINFO nvRamInfo[MAX_MB_CARDS]; +#if defined(DOS) || defined(OS2) +extern UCHAR temp_id_string[ID_STRING_LENGTH]; +#endif +extern UCHAR scamHAString[]; + + +extern UCHAR mbCards; +#if defined(BUGBUG) +extern UCHAR debug_int[MAX_CARDS][debug_size]; +extern UCHAR debug_index[MAX_CARDS]; +void Debug_Load(UCHAR p_card, UCHAR p_bug_data); +#endif + +#if (FW_TYPE==_SCCB_MGR_) +#if defined(DOS) + extern UCHAR first_time; +#endif +#endif /* (FW_TYPE==_SCCB_MGR_) */ + +#if (FW_TYPE==_UCB_MGR_) +#if defined(DOS) + extern u08bits first_time; +#endif +#endif /* (FW_TYPE==_UCB_MGR_) */ + +#if defined(BUGBUG) +void Debug_Load(UCHAR p_card, UCHAR p_bug_data); +#endif + +extern unsigned int SccbGlobalFlags; + + +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: sccb.c $ + * + * Description: Functions relating to handling of the SCCB interface + * between the device driver and the HARPOON. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ + /*#include <budioctl.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <eeprom.h>*/ +/*#include <scsi2.h>*/ +/*#include <harpoon.h>*/ + + + +#if (FW_TYPE==_SCCB_MGR_) +#define mOS_Lock(card) OS_Lock((PSCCBMGR_INFO)(((PSCCBcard)card)->cardInfo)) +#define mOS_UnLock(card) OS_UnLock((PSCCBMGR_INFO)(((PSCCBcard)card)->cardInfo)) +#else /* FW_TYPE==_UCB_MGR_ */ +#define mOS_Lock(card) OS_Lock((u32bits)(((PSCCBcard)card)->ioPort)) +#define mOS_UnLock(card) OS_UnLock((u32bits)(((PSCCBcard)card)->ioPort)) +#endif + + +/* +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; +extern SCCBCARD BL_Card[MAX_CARDS]; + +extern NVRAMINFO nvRamInfo[MAX_MB_CARDS]; +extern UCHAR mbCards; + +#if defined (OS2) + extern void (far *s_PhaseTbl[8]) (ULONG, UCHAR); +#else + #if defined(DOS) + extern void (*s_PhaseTbl[8]) (USHORT, UCHAR); + #else + extern void (*s_PhaseTbl[8]) (ULONG, UCHAR); + #endif +#endif + + +#if defined(BUGBUG) +extern UCHAR debug_int[MAX_CARDS][debug_size]; +extern UCHAR debug_index[MAX_CARDS]; +void Debug_Load(UCHAR p_card, UCHAR p_bug_data); +#endif +*/ + +#if (FW_TYPE==_SCCB_MGR_) + +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_sense_adapter + * + * Description: Setup and/or Search for cards and return info to caller. + * + *---------------------------------------------------------------------*/ + +int SccbMgr_sense_adapter(PSCCBMGR_INFO pCardInfo) +{ +#if defined(DOS) +#else + static UCHAR first_time = 1; +#endif + + UCHAR i,j,id,ScamFlg; + USHORT temp,temp2,temp3,temp4,temp5,temp6; +#if defined(DOS) + USHORT ioport; +#else + ULONG ioport; +#endif + PNVRamInfo pCurrNvRam; + +#if defined(DOS) + ioport = (USHORT)pCardInfo->si_baseaddr; +#else + ioport = pCardInfo->si_baseaddr; +#endif + + + if (RD_HARPOON(ioport+hp_vendor_id_0) != ORION_VEND_0) + return((int)FAILURE); + + if ((RD_HARPOON(ioport+hp_vendor_id_1) != ORION_VEND_1)) + return((int)FAILURE); + + if ((RD_HARPOON(ioport+hp_device_id_0) != ORION_DEV_0)) + return((int)FAILURE); + + if ((RD_HARPOON(ioport+hp_device_id_1) != ORION_DEV_1)) + return((int)FAILURE); + + + if (RD_HARPOON(ioport+hp_rev_num) != 0x0f){ + +/* For new Harpoon then check for sub_device ID LSB + the bits(0-3) must be all ZERO for compatible with + current version of SCCBMgr, else skip this Harpoon + device. */ + + if (RD_HARPOON(ioport+hp_sub_device_id_0) & 0x0f) + return((int)FAILURE); + } + + if (first_time) + { + SccbMgrTableInitAll(); + first_time = 0; + mbCards = 0; + } + + if(RdStack(ioport, 0) != 0x00) { + if(ChkIfChipInitialized(ioport) == FALSE) + { + pCurrNvRam = NULL; + WR_HARPOON(ioport+hp_semaphore, 0x00); + XbowInit(ioport, 0); /*Must Init the SCSI before attempting */ + DiagEEPROM(ioport); + } + else + { + if(mbCards < MAX_MB_CARDS) { + pCurrNvRam = &nvRamInfo[mbCards]; + mbCards++; + pCurrNvRam->niBaseAddr = ioport; + RNVRamData(pCurrNvRam); + }else + return((int) FAILURE); + } + }else + pCurrNvRam = NULL; +#if defined (NO_BIOS_OPTION) + pCurrNvRam = NULL; + XbowInit(ioport, 0); /*Must Init the SCSI before attempting */ + DiagEEPROM(ioport); +#endif /* No BIOS Option */ + + WR_HARPOON(ioport+hp_clkctrl_0, CLKCTRL_DEFAULT); + WR_HARPOON(ioport+hp_sys_ctrl, 0x00); + + if(pCurrNvRam) + pCardInfo->si_id = pCurrNvRam->niAdapId; + else + pCardInfo->si_id = (UCHAR)(utilEERead(ioport, (ADAPTER_SCSI_ID/2)) & + (UCHAR)0x0FF); + + pCardInfo->si_lun = 0x00; + pCardInfo->si_fw_revision = ORION_FW_REV; + temp2 = 0x0000; + temp3 = 0x0000; + temp4 = 0x0000; + temp5 = 0x0000; + temp6 = 0x0000; + + for (id = 0; id < (16/2); id++) { + + if(pCurrNvRam){ + temp = (USHORT) pCurrNvRam->niSyncTbl[id]; + temp = ((temp & 0x03) + ((temp << 4) & 0xc0)) + + (((temp << 4) & 0x0300) + ((temp << 8) & 0xc000)); + }else + temp = utilEERead(ioport, (USHORT)((SYNC_RATE_TBL/2)+id)); + + for (i = 0; i < 2; temp >>=8,i++) { + + temp2 >>= 1; + temp3 >>= 1; + temp4 >>= 1; + temp5 >>= 1; + temp6 >>= 1; + switch (temp & 0x3) + { + case AUTO_RATE_20: /* Synchronous, 20 mega-transfers/second */ + temp6 |= 0x8000; /* Fall through */ + case AUTO_RATE_10: /* Synchronous, 10 mega-transfers/second */ + temp5 |= 0x8000; /* Fall through */ + case AUTO_RATE_05: /* Synchronous, 5 mega-transfers/second */ + temp2 |= 0x8000; /* Fall through */ + case AUTO_RATE_00: /* Asynchronous */ + break; + } + + if (temp & DISC_ENABLE_BIT) + temp3 |= 0x8000; + + if (temp & WIDE_NEGO_BIT) + temp4 |= 0x8000; + + } + } + + pCardInfo->si_per_targ_init_sync = temp2; + pCardInfo->si_per_targ_no_disc = temp3; + pCardInfo->si_per_targ_wide_nego = temp4; + pCardInfo->si_per_targ_fast_nego = temp5; + pCardInfo->si_per_targ_ultra_nego = temp6; + + if(pCurrNvRam) + i = pCurrNvRam->niSysConf; + else + i = (UCHAR)(utilEERead(ioport, (SYSTEM_CONFIG/2))); + + if(pCurrNvRam) + ScamFlg = pCurrNvRam->niScamConf; + else + ScamFlg = (UCHAR) utilEERead(ioport, SCAM_CONFIG/2); + + pCardInfo->si_flags = 0x0000; + + if (i & 0x01) + pCardInfo->si_flags |= SCSI_PARITY_ENA; + + if (!(i & 0x02)) + pCardInfo->si_flags |= SOFT_RESET; + + if (i & 0x10) + pCardInfo->si_flags |= EXTENDED_TRANSLATION; + + if (ScamFlg & SCAM_ENABLED) + pCardInfo->si_flags |= FLAG_SCAM_ENABLED; + + if (ScamFlg & SCAM_LEVEL2) + pCardInfo->si_flags |= FLAG_SCAM_LEVEL2; + + j = (RD_HARPOON(ioport+hp_bm_ctrl) & ~SCSI_TERM_ENA_L); + if (i & 0x04) { + j |= SCSI_TERM_ENA_L; + } + WR_HARPOON(ioport+hp_bm_ctrl, j ); + + j = (RD_HARPOON(ioport+hp_ee_ctrl) & ~SCSI_TERM_ENA_H); + if (i & 0x08) { + j |= SCSI_TERM_ENA_H; + } + WR_HARPOON(ioport+hp_ee_ctrl, j ); + + if (!(RD_HARPOON(ioport+hp_page_ctrl) & NARROW_SCSI_CARD)) + + pCardInfo->si_flags |= SUPPORT_16TAR_32LUN; + + pCardInfo->si_card_family = HARPOON_FAMILY; + pCardInfo->si_bustype = BUSTYPE_PCI; + + if(pCurrNvRam){ + pCardInfo->si_card_model[0] = '9'; + switch(pCurrNvRam->niModel & 0x0f){ + case MODEL_LT: + pCardInfo->si_card_model[1] = '3'; + pCardInfo->si_card_model[2] = '0'; + break; + case MODEL_LW: + pCardInfo->si_card_model[1] = '5'; + pCardInfo->si_card_model[2] = '0'; + break; + case MODEL_DL: + pCardInfo->si_card_model[1] = '3'; + pCardInfo->si_card_model[2] = '2'; + break; + case MODEL_DW: + pCardInfo->si_card_model[1] = '5'; + pCardInfo->si_card_model[2] = '2'; + break; + } + }else{ + temp = utilEERead(ioport, (MODEL_NUMB_0/2)); + pCardInfo->si_card_model[0] = (UCHAR)(temp >> 8); + temp = utilEERead(ioport, (MODEL_NUMB_2/2)); + + pCardInfo->si_card_model[1] = (UCHAR)(temp & 0x00FF); + pCardInfo->si_card_model[2] = (UCHAR)(temp >> 8); + } + + if (pCardInfo->si_card_model[1] == '3') + { + if (RD_HARPOON(ioport+hp_ee_ctrl) & BIT(7)) + pCardInfo->si_flags |= LOW_BYTE_TERM; + } + else if (pCardInfo->si_card_model[2] == '0') + { + temp = RD_HARPOON(ioport+hp_xfer_pad); + WR_HARPOON(ioport+hp_xfer_pad, (temp & ~BIT(4))); + if (RD_HARPOON(ioport+hp_ee_ctrl) & BIT(7)) + pCardInfo->si_flags |= LOW_BYTE_TERM; + WR_HARPOON(ioport+hp_xfer_pad, (temp | BIT(4))); + if (RD_HARPOON(ioport+hp_ee_ctrl) & BIT(7)) + pCardInfo->si_flags |= HIGH_BYTE_TERM; + WR_HARPOON(ioport+hp_xfer_pad, temp); + } + else + { + temp = RD_HARPOON(ioport+hp_ee_ctrl); + temp2 = RD_HARPOON(ioport+hp_xfer_pad); + WR_HARPOON(ioport+hp_ee_ctrl, (temp | SEE_CS)); + WR_HARPOON(ioport+hp_xfer_pad, (temp2 | BIT(4))); + temp3 = 0; + for (i = 0; i < 8; i++) + { + temp3 <<= 1; + if (!(RD_HARPOON(ioport+hp_ee_ctrl) & BIT(7))) + temp3 |= 1; + WR_HARPOON(ioport+hp_xfer_pad, (temp2 & ~BIT(4))); + WR_HARPOON(ioport+hp_xfer_pad, (temp2 | BIT(4))); + } + WR_HARPOON(ioport+hp_ee_ctrl, temp); + WR_HARPOON(ioport+hp_xfer_pad, temp2); + if (!(temp3 & BIT(7))) + pCardInfo->si_flags |= LOW_BYTE_TERM; + if (!(temp3 & BIT(6))) + pCardInfo->si_flags |= HIGH_BYTE_TERM; + } + + + ARAM_ACCESS(ioport); + + for ( i = 0; i < 4; i++ ) { + + pCardInfo->si_XlatInfo[i] = + RD_HARPOON(ioport+hp_aramBase+BIOS_DATA_OFFSET+i); + } + + /* return with -1 if no sort, else return with + logical card number sorted by BIOS (zero-based) */ + + pCardInfo->si_relative_cardnum = + (UCHAR)(RD_HARPOON(ioport+hp_aramBase+BIOS_RELATIVE_CARD)-1); + + SGRAM_ACCESS(ioport); + + s_PhaseTbl[0] = phaseDataOut; + s_PhaseTbl[1] = phaseDataIn; + s_PhaseTbl[2] = phaseIllegal; + s_PhaseTbl[3] = phaseIllegal; + s_PhaseTbl[4] = phaseCommand; + s_PhaseTbl[5] = phaseStatus; + s_PhaseTbl[6] = phaseMsgOut; + s_PhaseTbl[7] = phaseMsgIn; + + pCardInfo->si_present = 0x01; + +#if defined(BUGBUG) + + + for (i = 0; i < MAX_CARDS; i++) { + + for (id=0; id<debug_size; id++) + debug_int[i][id] = (UCHAR)0x00; + debug_index[i] = 0; + } + +#endif + + return(0); +} + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_config_adapter + * + * Description: Setup adapter for normal operation (hard reset). + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +USHORT SccbMgr_config_adapter(PSCCBMGR_INFO pCardInfo) +#else +ULONG SccbMgr_config_adapter(PSCCBMGR_INFO pCardInfo) +#endif +{ + PSCCBcard CurrCard = NULL; + PNVRamInfo pCurrNvRam; + UCHAR i,j,thisCard, ScamFlg; + USHORT temp,sync_bit_map,id; +#if defined(DOS) + USHORT ioport; +#else + ULONG ioport; +#endif + +#if defined(DOS) + ioport = (USHORT)pCardInfo->si_baseaddr; +#else + ioport = pCardInfo->si_baseaddr; +#endif + + for(thisCard =0; thisCard <= MAX_CARDS; thisCard++) { + + if (thisCard == MAX_CARDS) { + + return(FAILURE); + } + + if (BL_Card[thisCard].ioPort == ioport) { + + CurrCard = &BL_Card[thisCard]; + SccbMgrTableInitCard(CurrCard,thisCard); + break; + } + + else if (BL_Card[thisCard].ioPort == 0x00) { + + BL_Card[thisCard].ioPort = ioport; + CurrCard = &BL_Card[thisCard]; + + if(mbCards) + for(i = 0; i < mbCards; i++){ + if(CurrCard->ioPort == nvRamInfo[i].niBaseAddr) + CurrCard->pNvRamInfo = &nvRamInfo[i]; + } + SccbMgrTableInitCard(CurrCard,thisCard); + CurrCard->cardIndex = thisCard; + CurrCard->cardInfo = pCardInfo; + + break; + } + } + + pCurrNvRam = CurrCard->pNvRamInfo; + + if(pCurrNvRam){ + ScamFlg = pCurrNvRam->niScamConf; + } + else{ + ScamFlg = (UCHAR) utilEERead(ioport, SCAM_CONFIG/2); + } + + + BusMasterInit(ioport); + XbowInit(ioport, ScamFlg); + +#if defined (NO_BIOS_OPTION) + + + if (DiagXbow(ioport)) return(FAILURE); + if (DiagBusMaster(ioport)) return(FAILURE); + +#endif /* No BIOS Option */ + + autoLoadDefaultMap(ioport); + + + for (i = 0,id = 0x01; i != pCardInfo->si_id; i++,id <<= 1){} + + WR_HARPOON(ioport+hp_selfid_0, id); + WR_HARPOON(ioport+hp_selfid_1, 0x00); + WR_HARPOON(ioport+hp_arb_id, pCardInfo->si_id); + CurrCard->ourId = pCardInfo->si_id; + + i = (UCHAR) pCardInfo->si_flags; + if (i & SCSI_PARITY_ENA) + WR_HARPOON(ioport+hp_portctrl_1,(HOST_MODE8 | CHK_SCSI_P)); + + j = (RD_HARPOON(ioport+hp_bm_ctrl) & ~SCSI_TERM_ENA_L); + if (i & LOW_BYTE_TERM) + j |= SCSI_TERM_ENA_L; + WR_HARPOON(ioport+hp_bm_ctrl, j); + + j = (RD_HARPOON(ioport+hp_ee_ctrl) & ~SCSI_TERM_ENA_H); + if (i & HIGH_BYTE_TERM) + j |= SCSI_TERM_ENA_H; + WR_HARPOON(ioport+hp_ee_ctrl, j ); + + + if (!(pCardInfo->si_flags & SOFT_RESET)) { + + sresb(ioport,thisCard); + + scini(thisCard, pCardInfo->si_id, 0); + } + + + + if (pCardInfo->si_flags & POST_ALL_UNDERRRUNS) + CurrCard->globalFlags |= F_NO_FILTER; + + if(pCurrNvRam){ + if(pCurrNvRam->niSysConf & 0x10) + CurrCard->globalFlags |= F_GREEN_PC; + } + else{ + if (utilEERead(ioport, (SYSTEM_CONFIG/2)) & GREEN_PC_ENA) + CurrCard->globalFlags |= F_GREEN_PC; + } + + /* Set global flag to indicate Re-Negotiation to be done on all + ckeck condition */ + if(pCurrNvRam){ + if(pCurrNvRam->niScsiConf & 0x04) + CurrCard->globalFlags |= F_DO_RENEGO; + } + else{ + if (utilEERead(ioport, (SCSI_CONFIG/2)) & RENEGO_ENA) + CurrCard->globalFlags |= F_DO_RENEGO; + } + + if(pCurrNvRam){ + if(pCurrNvRam->niScsiConf & 0x08) + CurrCard->globalFlags |= F_CONLUN_IO; + } + else{ + if (utilEERead(ioport, (SCSI_CONFIG/2)) & CONNIO_ENA) + CurrCard->globalFlags |= F_CONLUN_IO; + } + + + temp = pCardInfo->si_per_targ_no_disc; + + for (i = 0,id = 1; i < MAX_SCSI_TAR; i++, id <<= 1) { + + if (temp & id) + sccbMgrTbl[thisCard][i].TarStatus |= TAR_ALLOW_DISC; + } + + sync_bit_map = 0x0001; + + for (id = 0; id < (MAX_SCSI_TAR/2); id++) { + + if(pCurrNvRam){ + temp = (USHORT) pCurrNvRam->niSyncTbl[id]; + temp = ((temp & 0x03) + ((temp << 4) & 0xc0)) + + (((temp << 4) & 0x0300) + ((temp << 8) & 0xc000)); + }else + temp = utilEERead(ioport, (USHORT)((SYNC_RATE_TBL/2)+id)); + + for (i = 0; i < 2; temp >>=8,i++) { + + if (pCardInfo->si_per_targ_init_sync & sync_bit_map) { + + sccbMgrTbl[thisCard][id*2+i].TarEEValue = (UCHAR)temp; + } + + else { + sccbMgrTbl[thisCard][id*2+i].TarStatus |= SYNC_SUPPORTED; + sccbMgrTbl[thisCard][id*2+i].TarEEValue = + (UCHAR)(temp & ~EE_SYNC_MASK); + } + +#if defined(WIDE_SCSI) +/* if ((pCardInfo->si_per_targ_wide_nego & sync_bit_map) || + (id*2+i >= 8)){ +*/ + if (pCardInfo->si_per_targ_wide_nego & sync_bit_map){ + + sccbMgrTbl[thisCard][id*2+i].TarEEValue |= EE_WIDE_SCSI; + + } + + else { /* NARROW SCSI */ + sccbMgrTbl[thisCard][id*2+i].TarStatus |= WIDE_NEGOCIATED; + } + +#else + sccbMgrTbl[thisCard][id*2+i].TarStatus |= WIDE_NEGOCIATED; +#endif + + + sync_bit_map <<= 1; + + + + } + } + + WR_HARPOON((ioport+hp_semaphore), + (UCHAR)(RD_HARPOON((ioport+hp_semaphore)) | SCCB_MGR_PRESENT)); + +#if defined(DOS) + return((USHORT)CurrCard); +#else + return((ULONG)CurrCard); +#endif +} + +#else /* end (FW_TYPE==_SCCB_MGR_) */ + + + +STATIC s16bits FP_PresenceCheck(PMGR_INFO pMgrInfo) +{ + PMGR_ENTRYPNTS pMgr_EntryPnts = &pMgrInfo->mi_Functions; + + pMgr_EntryPnts->UCBMgr_probe_adapter = probe_adapter; + pMgr_EntryPnts->UCBMgr_init_adapter = init_adapter; + pMgr_EntryPnts->UCBMgr_start_UCB = SccbMgr_start_sccb; + pMgr_EntryPnts->UCBMgr_build_UCB = build_UCB; + pMgr_EntryPnts->UCBMgr_abort_UCB = SccbMgr_abort_sccb; + pMgr_EntryPnts->UCBMgr_my_int = SccbMgr_my_int; + pMgr_EntryPnts->UCBMgr_isr = SccbMgr_isr; + pMgr_EntryPnts->UCBMgr_scsi_reset = SccbMgr_scsi_reset; + pMgr_EntryPnts->UCBMgr_timer_expired = SccbMgr_timer_expired; +#ifndef NO_IOCTLS + pMgr_EntryPnts->UCBMgr_unload_card = SccbMgr_unload_card; + pMgr_EntryPnts->UCBMgr_save_foreign_state = + SccbMgr_save_foreign_state; + pMgr_EntryPnts->UCBMgr_restore_foreign_state = + SccbMgr_restore_foreign_state; + pMgr_EntryPnts->UCBMgr_restore_native_state = + SccbMgr_restore_native_state; +#endif /*NO_IOCTLS*/ + + pMgrInfo->mi_SGListFormat=0x01; + pMgrInfo->mi_DataPtrFormat=0x01; + pMgrInfo->mi_MaxSGElements= (u16bits) 0xffffffff; + pMgrInfo->mi_MgrPrivateLen=sizeof(SCCB); + pMgrInfo->mi_PCIVendorID=BL_VENDOR_ID; + pMgrInfo->mi_PCIDeviceID=FP_DEVICE_ID; + pMgrInfo->mi_MgrAttributes= ATTR_IO_MAPPED + + ATTR_PHYSICAL_ADDRESS + + ATTR_VIRTUAL_ADDRESS + + ATTR_OVERLAPPED_IO_IOCTLS_OK; + pMgrInfo->mi_IoRangeLen = 256; + return(0); +} + + + +/*--------------------------------------------------------------------- + * + * Function: probe_adapter + * + * Description: Setup and/or Search for cards and return info to caller. + * + *---------------------------------------------------------------------*/ +STATIC s32bits probe_adapter(PADAPTER_INFO pAdapterInfo) +{ + u16bits temp,temp2,temp3,temp4; + u08bits i,j,id; + +#if defined(DOS) +#else + static u08bits first_time = 1; +#endif + BASE_PORT ioport; + PNVRamInfo pCurrNvRam; + + ioport = (BASE_PORT)pAdapterInfo->ai_baseaddr; + + + + if (RD_HARPOON(ioport+hp_vendor_id_0) != ORION_VEND_0) + return(1); + + if ((RD_HARPOON(ioport+hp_vendor_id_1) != ORION_VEND_1)) + return(2); + + if ((RD_HARPOON(ioport+hp_device_id_0) != ORION_DEV_0)) + return(3); + + if ((RD_HARPOON(ioport+hp_device_id_1) != ORION_DEV_1)) + return(4); + + + if (RD_HARPOON(ioport+hp_rev_num) != 0x0f){ + + +/* For new Harpoon then check for sub_device ID LSB + the bits(0-3) must be all ZERO for compatible with + current version of SCCBMgr, else skip this Harpoon + device. */ + + if (RD_HARPOON(ioport+hp_sub_device_id_0) & 0x0f) + return(5); + } + + if (first_time) { + + SccbMgrTableInitAll(); + first_time = 0; + mbCards = 0; + } + + if(RdStack(ioport, 0) != 0x00) { + if(ChkIfChipInitialized(ioport) == FALSE) + { + pCurrNvRam = NULL; + WR_HARPOON(ioport+hp_semaphore, 0x00); + XbowInit(ioport, 0); /*Must Init the SCSI before attempting */ + DiagEEPROM(ioport); + } + else + { + if(mbCards < MAX_MB_CARDS) { + pCurrNvRam = &nvRamInfo[mbCards]; + mbCards++; + pCurrNvRam->niBaseAddr = ioport; + RNVRamData(pCurrNvRam); + }else + return((int) FAILURE); + } + }else + pCurrNvRam = NULL; + +#if defined (NO_BIOS_OPTION) + pCurrNvRam = NULL; + XbowInit(ioport, 0); /*Must Init the SCSI before attempting */ + DiagEEPROM(ioport); +#endif /* No BIOS Option */ + + WR_HARPOON(ioport+hp_clkctrl_0, CLKCTRL_DEFAULT); + WR_HARPOON(ioport+hp_sys_ctrl, 0x00); + + if(pCurrNvRam) + pAdapterInfo->ai_id = pCurrNvRam->niAdapId; + else + pAdapterInfo->ai_id = (u08bits)(utilEERead(ioport, (ADAPTER_SCSI_ID/2)) & + (u08bits)0x0FF); + + pAdapterInfo->ai_lun = 0x00; + pAdapterInfo->ai_fw_revision[0] = '3'; + pAdapterInfo->ai_fw_revision[1] = '1'; + pAdapterInfo->ai_fw_revision[2] = '1'; + pAdapterInfo->ai_fw_revision[3] = ' '; + pAdapterInfo->ai_NumChannels = 1; + + temp2 = 0x0000; + temp3 = 0x0000; + temp4 = 0x0000; + + for (id = 0; id < (16/2); id++) { + + if(pCurrNvRam){ + temp = (USHORT) pCurrNvRam->niSyncTbl[id]; + temp = ((temp & 0x03) + ((temp << 4) & 0xc0)) + + (((temp << 4) & 0x0300) + ((temp << 8) & 0xc000)); + }else + temp = utilEERead(ioport, (u16bits)((SYNC_RATE_TBL/2)+id)); + + for (i = 0; i < 2; temp >>=8,i++) { + + if ((temp & 0x03) != AUTO_RATE_00) { + + temp2 >>= 0x01; + temp2 |= 0x8000; + } + + else { + temp2 >>= 0x01; + } + + if (temp & DISC_ENABLE_BIT) { + + temp3 >>= 0x01; + temp3 |= 0x8000; + } + + else { + temp3 >>= 0x01; + } + + if (temp & WIDE_NEGO_BIT) { + + temp4 >>= 0x01; + temp4 |= 0x8000; + } + + else { + temp4 >>= 0x01; + } + + } + } + + pAdapterInfo->ai_per_targ_init_sync = temp2; + pAdapterInfo->ai_per_targ_no_disc = temp3; + pAdapterInfo->ai_per_targ_wide_nego = temp4; + if(pCurrNvRam) + i = pCurrNvRam->niSysConf; + else + i = (u08bits)(utilEERead(ioport, (SYSTEM_CONFIG/2))); + + /* + ** interrupts always level-triggered for FlashPoint + */ + pAdapterInfo->ai_stateinfo |= LEVEL_TRIG; + + if (i & 0x01) + pAdapterInfo->ai_stateinfo |= SCSI_PARITY_ENA; + + if (i & 0x02) /* SCSI Bus reset in AutoSCSI Set ? */ + { + if(pCurrNvRam) + { + j = pCurrNvRam->niScamConf; + } + else + { + j = (u08bits) utilEERead(ioport, SCAM_CONFIG/2); + } + if(j & SCAM_ENABLED) + { + if(j & SCAM_LEVEL2) + { + pAdapterInfo->ai_stateinfo |= SCAM2_ENA; + } + else + { + pAdapterInfo->ai_stateinfo |= SCAM1_ENA; + } + } + } + j = (RD_HARPOON(ioport+hp_bm_ctrl) & ~SCSI_TERM_ENA_L); + if (i & 0x04) { + j |= SCSI_TERM_ENA_L; + pAdapterInfo->ai_stateinfo |= LOW_BYTE_TERM_ENA; + } + WR_HARPOON(ioport+hp_bm_ctrl, j ); + + j = (RD_HARPOON(ioport+hp_ee_ctrl) & ~SCSI_TERM_ENA_H); + if (i & 0x08) { + j |= SCSI_TERM_ENA_H; + pAdapterInfo->ai_stateinfo |= HIGH_BYTE_TERM_ENA; + } + WR_HARPOON(ioport+hp_ee_ctrl, j ); + + if(RD_HARPOON(ioport + hp_page_ctrl) & BIOS_SHADOW) + { + pAdapterInfo->ai_FlashRomSize = 64 * 1024; /* 64k ROM */ + } + else + { + pAdapterInfo->ai_FlashRomSize = 32 * 1024; /* 32k ROM */ + } + + pAdapterInfo->ai_stateinfo |= (FAST20_ENA | TAG_QUEUE_ENA); + if (!(RD_HARPOON(ioport+hp_page_ctrl) & NARROW_SCSI_CARD)) + { + pAdapterInfo->ai_attributes |= (WIDE_CAPABLE | FAST20_CAPABLE + | SCAM2_CAPABLE + | TAG_QUEUE_CAPABLE + | SUPRESS_UNDERRRUNS_CAPABLE + | SCSI_PARITY_CAPABLE); + pAdapterInfo->ai_MaxTarg = 16; + pAdapterInfo->ai_MaxLun = 32; + } + else + { + pAdapterInfo->ai_attributes |= (FAST20_CAPABLE | SCAM2_CAPABLE + | TAG_QUEUE_CAPABLE + | SUPRESS_UNDERRRUNS_CAPABLE + | SCSI_PARITY_CAPABLE); + pAdapterInfo->ai_MaxTarg = 8; + pAdapterInfo->ai_MaxLun = 8; + } + + pAdapterInfo->ai_product_family = HARPOON_FAMILY; + pAdapterInfo->ai_HBAbustype = BUSTYPE_PCI; + + for (i=0;i<CARD_MODEL_NAMELEN;i++) + { + pAdapterInfo->ai_card_model[i]=' '; /* initialize the ai_card_model */ + } + + if(pCurrNvRam){ + pAdapterInfo->ai_card_model[0] = '9'; + switch(pCurrNvRam->niModel & 0x0f){ + case MODEL_LT: + pAdapterInfo->ai_card_model[1] = '3'; + pAdapterInfo->ai_card_model[2] = '0'; + break; + case MODEL_LW: + pAdapterInfo->ai_card_model[1] = '5'; + pAdapterInfo->ai_card_model[2] = '0'; + break; + case MODEL_DL: + pAdapterInfo->ai_card_model[1] = '3'; + pAdapterInfo->ai_card_model[2] = '2'; + break; + case MODEL_DW: + pAdapterInfo->ai_card_model[1] = '5'; + pAdapterInfo->ai_card_model[2] = '2'; + break; + } + }else{ + temp = utilEERead(ioport, (MODEL_NUMB_0/2)); + pAdapterInfo->ai_card_model[0] = (u08bits)(temp >> 8); + temp = utilEERead(ioport, (MODEL_NUMB_2/2)); + + pAdapterInfo->ai_card_model[1] = (u08bits)(temp & 0x00FF); + pAdapterInfo->ai_card_model[2] = (u08bits)(temp >> 8); + } + + + + pAdapterInfo->ai_FiberProductType = 0; + + pAdapterInfo->ai_secondary_range = 0; + + for (i=0;i<WORLD_WIDE_NAMELEN;i++) + { + pAdapterInfo->ai_worldwidename[i]='\0'; + } + + for (i=0;i<VENDOR_NAMELEN;i++) + { + pAdapterInfo->ai_vendorstring[i]='\0'; + } + pAdapterInfo->ai_vendorstring[0]='B'; + pAdapterInfo->ai_vendorstring[1]='U'; + pAdapterInfo->ai_vendorstring[2]='S'; + pAdapterInfo->ai_vendorstring[3]='L'; + pAdapterInfo->ai_vendorstring[4]='O'; + pAdapterInfo->ai_vendorstring[5]='G'; + pAdapterInfo->ai_vendorstring[6]='I'; + pAdapterInfo->ai_vendorstring[7]='C'; + + for (i=0;i<FAMILY_NAMELEN;i++) + { + pAdapterInfo->ai_AdapterFamilyString[i]='\0'; + } + pAdapterInfo->ai_AdapterFamilyString[0]='F'; + pAdapterInfo->ai_AdapterFamilyString[1]='L'; + pAdapterInfo->ai_AdapterFamilyString[2]='A'; + pAdapterInfo->ai_AdapterFamilyString[3]='S'; + pAdapterInfo->ai_AdapterFamilyString[4]='H'; + pAdapterInfo->ai_AdapterFamilyString[5]='P'; + pAdapterInfo->ai_AdapterFamilyString[6]='O'; + pAdapterInfo->ai_AdapterFamilyString[7]='I'; + pAdapterInfo->ai_AdapterFamilyString[8]='N'; + pAdapterInfo->ai_AdapterFamilyString[9]='T'; + + ARAM_ACCESS(ioport); + + for ( i = 0; i < 4; i++ ) { + + pAdapterInfo->ai_XlatInfo[i] = + RD_HARPOON(ioport+hp_aramBase+BIOS_DATA_OFFSET+i); + } + + /* return with -1 if no sort, else return with + logical card number sorted by BIOS (zero-based) */ + + + pAdapterInfo->ai_relative_cardnum = + (u08bits)(RD_HARPOON(ioport+hp_aramBase+BIOS_RELATIVE_CARD)-1); + + SGRAM_ACCESS(ioport); + + s_PhaseTbl[0] = phaseDataOut; + s_PhaseTbl[1] = phaseDataIn; + s_PhaseTbl[2] = phaseIllegal; + s_PhaseTbl[3] = phaseIllegal; + s_PhaseTbl[4] = phaseCommand; + s_PhaseTbl[5] = phaseStatus; + s_PhaseTbl[6] = phaseMsgOut; + s_PhaseTbl[7] = phaseMsgIn; + + pAdapterInfo->ai_present = 0x01; + +#if defined(BUGBUG) + + + for (i = 0; i < MAX_CARDS; i++) { + + for (id=0; id<debug_size; id++) + debug_int[i][id] = (u08bits)0x00; + debug_index[i] = 0; + } + +#endif + + return(0); +} + + + + + +/*--------------------------------------------------------------------- + * + * Function: init_adapter, exported to BUDI via UCBMgr_init_adapter entry + * + * + * Description: Setup adapter for normal operation (hard reset). + * + *---------------------------------------------------------------------*/ +STATIC CARD_HANDLE init_adapter(PADAPTER_INFO pCardInfo) +{ + PSCCBcard CurrCard; + PNVRamInfo pCurrNvRam; + u08bits i,j,thisCard, ScamFlg; + u16bits temp,sync_bit_map,id; + BASE_PORT ioport; + + ioport = (BASE_PORT)pCardInfo->ai_baseaddr; + + for(thisCard =0; thisCard <= MAX_CARDS; thisCard++) { + + if (thisCard == MAX_CARDS) { + + return(FAILURE); + } + + if (BL_Card[thisCard].ioPort == ioport) { + + CurrCard = &BL_Card[thisCard]; + SccbMgrTableInitCard(CurrCard,thisCard); + break; + } + + else if (BL_Card[thisCard].ioPort == 0x00) { + + BL_Card[thisCard].ioPort = ioport; + CurrCard = &BL_Card[thisCard]; + + if(mbCards) + for(i = 0; i < mbCards; i++){ + if(CurrCard->ioPort == nvRamInfo[i].niBaseAddr) + CurrCard->pNvRamInfo = &nvRamInfo[i]; + } + SccbMgrTableInitCard(CurrCard,thisCard); + CurrCard->cardIndex = thisCard; + CurrCard->cardInfo = pCardInfo; + + break; + } + } + + pCurrNvRam = CurrCard->pNvRamInfo; + + + if(pCurrNvRam){ + ScamFlg = pCurrNvRam->niScamConf; + } + else{ + ScamFlg = (UCHAR) utilEERead(ioport, SCAM_CONFIG/2); + } + + + BusMasterInit(ioport); + XbowInit(ioport, ScamFlg); + +#if defined (NO_BIOS_OPTION) + + + if (DiagXbow(ioport)) return(FAILURE); + if (DiagBusMaster(ioport)) return(FAILURE); + +#endif /* No BIOS Option */ + + autoLoadDefaultMap(ioport); + + + for (i = 0,id = 0x01; i != pCardInfo->ai_id; i++,id <<= 1){} + + WR_HARPOON(ioport+hp_selfid_0, id); + WR_HARPOON(ioport+hp_selfid_1, 0x00); + WR_HARPOON(ioport+hp_arb_id, pCardInfo->ai_id); + CurrCard->ourId = (unsigned char) pCardInfo->ai_id; + + i = (u08bits) pCardInfo->ai_stateinfo; + if (i & SCSI_PARITY_ENA) + WR_HARPOON(ioport+hp_portctrl_1,(HOST_MODE8 | CHK_SCSI_P)); + + j = (RD_HARPOON(ioport+hp_bm_ctrl) & ~SCSI_TERM_ENA_L); + if (i & LOW_BYTE_TERM_ENA) + j |= SCSI_TERM_ENA_L; + WR_HARPOON(ioport+hp_bm_ctrl, j); + + j = (RD_HARPOON(ioport+hp_ee_ctrl) & ~SCSI_TERM_ENA_H); + if (i & HIGH_BYTE_TERM_ENA) + j |= SCSI_TERM_ENA_H; + WR_HARPOON(ioport+hp_ee_ctrl, j ); + + + if (!(pCardInfo->ai_stateinfo & NO_RESET_IN_INIT)) { + + sresb(ioport,thisCard); + + scini(thisCard, (u08bits) pCardInfo->ai_id, 0); + } + + + + if (pCardInfo->ai_stateinfo & SUPRESS_UNDERRRUNS_ENA) + CurrCard->globalFlags |= F_NO_FILTER; + + if(pCurrNvRam){ + if(pCurrNvRam->niSysConf & 0x10) + CurrCard->globalFlags |= F_GREEN_PC; + } + else{ + if (utilEERead(ioport, (SYSTEM_CONFIG/2)) & GREEN_PC_ENA) + CurrCard->globalFlags |= F_GREEN_PC; + } + + /* Set global flag to indicate Re-Negotiation to be done on all + ckeck condition */ + if(pCurrNvRam){ + if(pCurrNvRam->niScsiConf & 0x04) + CurrCard->globalFlags |= F_DO_RENEGO; + } + else{ + if (utilEERead(ioport, (SCSI_CONFIG/2)) & RENEGO_ENA) + CurrCard->globalFlags |= F_DO_RENEGO; + } + + if(pCurrNvRam){ + if(pCurrNvRam->niScsiConf & 0x08) + CurrCard->globalFlags |= F_CONLUN_IO; + } + else{ + if (utilEERead(ioport, (SCSI_CONFIG/2)) & CONNIO_ENA) + CurrCard->globalFlags |= F_CONLUN_IO; + } + + temp = pCardInfo->ai_per_targ_no_disc; + + for (i = 0,id = 1; i < MAX_SCSI_TAR; i++, id <<= 1) { + + if (temp & id) + sccbMgrTbl[thisCard][i].TarStatus |= TAR_ALLOW_DISC; + } + + sync_bit_map = 0x0001; + + for (id = 0; id < (MAX_SCSI_TAR/2); id++){ + + if(pCurrNvRam){ + temp = (USHORT) pCurrNvRam->niSyncTbl[id]; + temp = ((temp & 0x03) + ((temp << 4) & 0xc0)) + + (((temp << 4) & 0x0300) + ((temp << 8) & 0xc000)); + }else + temp = utilEERead(ioport, (u16bits)((SYNC_RATE_TBL/2)+id)); + + for (i = 0; i < 2; temp >>=8,i++){ + + if (pCardInfo->ai_per_targ_init_sync & sync_bit_map){ + + sccbMgrTbl[thisCard][id*2+i].TarEEValue = (u08bits)temp; + } + + else { + sccbMgrTbl[thisCard][id*2+i].TarStatus |= SYNC_SUPPORTED; + sccbMgrTbl[thisCard][id*2+i].TarEEValue = + (u08bits)(temp & ~EE_SYNC_MASK); + } + +#if defined(WIDE_SCSI) +/* if ((pCardInfo->ai_per_targ_wide_nego & sync_bit_map) || + (id*2+i >= 8)){ +*/ + if (pCardInfo->ai_per_targ_wide_nego & sync_bit_map){ + + sccbMgrTbl[thisCard][id*2+i].TarEEValue |= EE_WIDE_SCSI; + + } + + else { /* NARROW SCSI */ + sccbMgrTbl[thisCard][id*2+i].TarStatus |= WIDE_NEGOCIATED; + } + +#else + sccbMgrTbl[thisCard][id*2+i].TarStatus |= WIDE_NEGOCIATED; +#endif + + + sync_bit_map <<= 1; + } + } + + + pCardInfo->ai_SGListFormat=0x01; + pCardInfo->ai_DataPtrFormat=0x01; + pCardInfo->ai_AEN_mask &= SCSI_RESET_COMPLETE; + + WR_HARPOON((ioport+hp_semaphore), + (u08bits)(RD_HARPOON((ioport+hp_semaphore)) | SCCB_MGR_PRESENT)); + + return((u32bits)CurrCard); + +} + + +/*--------------------------------------------------------------------- + * + * Function: build_ucb, exported to BUDI via UCBMgr_build_ucb entry + * + * Description: prepare fw portion of ucb. do not start, resource not guaranteed + * so don't manipulate anything that's derived from states which + * may change + * + *---------------------------------------------------------------------*/ +void build_UCB(CARD_HANDLE pCurrCard, PUCB p_ucb) +{ + + u08bits thisCard; + u08bits i,j; + + PSCCB p_sccb; + + + thisCard = ((PSCCBcard) pCurrCard)->cardIndex; + + + p_sccb=(PSCCB)p_ucb->UCB_MgrPrivatePtr; + + + p_sccb->Sccb_ucb_ptr=p_ucb; + + switch (p_ucb->UCB_opcode & (OPC_DEVICE_RESET+OPC_XFER_SG+OPC_CHK_RESIDUAL)) + { + case OPC_DEVICE_RESET: + p_sccb->OperationCode=RESET_COMMAND; + break; + case OPC_XFER_SG: + p_sccb->OperationCode=SCATTER_GATHER_COMMAND; + break; + case OPC_XFER_SG+OPC_CHK_RESIDUAL: + p_sccb->OperationCode=RESIDUAL_SG_COMMAND; + break; + case OPC_CHK_RESIDUAL: + + p_sccb->OperationCode=RESIDUAL_COMMAND; + break; + default: + p_sccb->OperationCode=SCSI_INITIATOR_COMMAND; + break; + } + + if (p_ucb->UCB_opcode & OPC_TQ_ENABLE) + { + p_sccb->ControlByte = (u08bits)((p_ucb->UCB_opcode & OPC_TQ_MASK)>>2) | F_USE_CMD_Q; + } + else + { + p_sccb->ControlByte = 0; + } + + + p_sccb->CdbLength = (u08bits)p_ucb->UCB_cdblen; + + if (p_ucb->UCB_opcode & OPC_NO_AUTO_SENSE) + { + p_sccb->RequestSenseLength = 0; + } + else + { + p_sccb->RequestSenseLength = (unsigned char) p_ucb->UCB_senselen; + } + + + if (p_ucb->UCB_opcode & OPC_XFER_SG) + { + p_sccb->DataPointer=p_ucb->UCB_virt_dataptr; + p_sccb->DataLength = (((u32bits)p_ucb->UCB_NumSgElements)<<3); + } + else + { + p_sccb->DataPointer=p_ucb->UCB_phys_dataptr; + p_sccb->DataLength=p_ucb->UCB_datalen; + }; + + p_sccb->HostStatus=0; + p_sccb->TargetStatus=0; + p_sccb->TargID=(unsigned char)p_ucb->UCB_targid; + p_sccb->Lun=(unsigned char) p_ucb->UCB_lun; + p_sccb->SccbIOPort=((PSCCBcard)pCurrCard)->ioPort; + + j=p_ucb->UCB_cdblen; + for (i=0;i<j;i++) + { + p_sccb->Cdb[i] = p_ucb->UCB_cdb[i]; + } + + p_sccb->SensePointer=p_ucb->UCB_phys_senseptr; + + sinits(p_sccb,thisCard); + +} +#ifndef NO_IOCTLS + +/*--------------------------------------------------------------------- + * + * Function: GetDevSyncRate + * + *---------------------------------------------------------------------*/ +STATIC int GetDevSyncRate(PSCCBcard pCurrCard,PUCB p_ucb) +{ + struct _SYNC_RATE_INFO * pSyncStr; + PSCCBMgr_tar_info currTar_Info; + BASE_PORT ioport; + u08bits scsiID, j; + +#if (FW_TYPE != _SCCB_MGR_) + if( p_ucb->UCB_targid >= pCurrCard->cardInfo->ai_MaxTarg ) + { + return(1); + } +#endif + + ioport = pCurrCard->ioPort; + pSyncStr = (struct _SYNC_RATE_INFO *) p_ucb->UCB_virt_dataptr; + scsiID = (u08bits) p_ucb->UCB_targid; + currTar_Info = &sccbMgrTbl[pCurrCard->cardIndex][scsiID]; + j = currTar_Info->TarSyncCtrl; + + switch (currTar_Info->TarEEValue & EE_SYNC_MASK) + { + case EE_SYNC_ASYNC: + pSyncStr->RequestMegaXferRate = 0x00; + break; + case EE_SYNC_5MB: + pSyncStr->RequestMegaXferRate = (j & NARROW_SCSI) ? 50 : 100; + break; + case EE_SYNC_10MB: + pSyncStr->RequestMegaXferRate = (j & NARROW_SCSI) ? 100 : 200; + break; + case EE_SYNC_20MB: + pSyncStr->RequestMegaXferRate = (j & NARROW_SCSI) ? 200 : 400; + break; + } + + switch ((j >> 5) & 0x07) + { + case 0x00: + if((j & 0x07) == 0x00) + { + pSyncStr->ActualMegaXferRate = 0x00; /* Async Mode */ + } + else + { + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 200 : 400; + } + break; + case 0x01: + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 100 : 200; + break; + case 0x02: + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 66 : 122; + break; + case 0x03: + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 50 : 100; + break; + case 0x04: + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 40 : 80; + break; + case 0x05: + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 33 : 66; + break; + case 0x06: + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 28 : 56; + break; + case 0x07: + pSyncStr->ActualMegaXferRate = (j & NARROW_SCSI) ? 25 : 50; + break; + } + pSyncStr->NegotiatedOffset = j & 0x0f; + + return(0); +} + +/*--------------------------------------------------------------------- + * + * Function: SetDevSyncRate + * + *---------------------------------------------------------------------*/ +STATIC int SetDevSyncRate(PSCCBcard pCurrCard, PUCB p_ucb) +{ + struct _SYNC_RATE_INFO * pSyncStr; + PSCCBMgr_tar_info currTar_Info; + BASE_PORT ioPort; + u08bits scsiID, i, j, syncVal; + u16bits syncOffset, actualXferRate; + union { + u08bits tempb[2]; + u16bits tempw; + }temp2; + +#if (FW_TYPE != _SCCB_MGR_) + if( p_ucb->UCB_targid >= pCurrCard->cardInfo->ai_MaxTarg ) + { + return(1); + } +#endif + + ioPort = pCurrCard->ioPort; + pSyncStr = (struct _SYNC_RATE_INFO *) p_ucb->UCB_virt_dataptr; + scsiID = (u08bits) p_ucb->UCB_targid; + currTar_Info = &sccbMgrTbl[pCurrCard->cardIndex][scsiID]; + i = RD_HARPOON(ioPort+hp_xfer_pad); /* Save current value */ + WR_HARPOON(ioPort+hp_xfer_pad, (i | ID_UNLOCK)); + WR_HARPOON(ioPort+hp_select_id, ((scsiID << 4) | scsiID)); + j = RD_HARPOON(ioPort+hp_synctarg_0); + WR_HARPOON(ioPort+hp_xfer_pad, i); /* restore value */ + + actualXferRate = pSyncStr->ActualMegaXferRate; + if(!(j & NARROW_SCSI)) + { + actualXferRate <<= 1; + } + if(actualXferRate == 0x00) + { + syncVal = EE_SYNC_ASYNC; /* Async Mode */ + } + if(actualXferRate == 0x0200) + { + syncVal = EE_SYNC_20MB; /* 20/40 MB Mode */ + } + if(actualXferRate > 0x0050 && actualXferRate < 0x0200 ) + { + syncVal = EE_SYNC_10MB; /* 10/20 MB Mode */ + } + else + { + syncVal = EE_SYNC_5MB; /* 5/10 MB Mode */ + } + if(currTar_Info->TarEEValue && EE_SYNC_MASK == syncVal) + return(0); + currTar_Info->TarEEValue = (currTar_Info->TarEEValue & !EE_SYNC_MASK) + | syncVal; + syncOffset = (SYNC_RATE_TBL + scsiID) / 2; + temp2.tempw = utilEERead(ioPort, syncOffset); + if(scsiID & 0x01) + { + temp2.tempb[0] = (temp2.tempb[0] & !EE_SYNC_MASK) | syncVal; + } + else + { + temp2.tempb[1] = (temp2.tempb[1] & !EE_SYNC_MASK) | syncVal; + } + utilEEWriteOnOff(ioPort, 1); + utilEEWrite(ioPort, temp2.tempw, syncOffset); + utilEEWriteOnOff(ioPort, 0); + UpdateCheckSum(ioPort); + + return(0); +} +/*--------------------------------------------------------------------- + * + * Function: GetDevWideMode + * + *---------------------------------------------------------------------*/ +int GetDevWideMode(PSCCBcard pCurrCard,PUCB p_ucb) +{ + u08bits *pData; + + pData = (u08bits *)p_ucb->UCB_virt_dataptr; + if(sccbMgrTbl[pCurrCard->cardIndex][p_ucb->UCB_targid].TarEEValue + & EE_WIDE_SCSI) + { + *pData = 1; + } + else + { + *pData = 0; + } + + return(0); +} + +/*--------------------------------------------------------------------- + * + * Function: SetDevWideMode + * + *---------------------------------------------------------------------*/ +int SetDevWideMode(PSCCBcard pCurrCard,PUCB p_ucb) +{ + u08bits *pData; + PSCCBMgr_tar_info currTar_Info; + BASE_PORT ioPort; + u08bits scsiID, scsiWideMode; + u16bits syncOffset; + union { + u08bits tempb[2]; + u16bits tempw; + }temp2; + +#if (FW_TYPE != _SCCB_MGR_) + if( !(pCurrCard->cardInfo->ai_attributes & WIDE_CAPABLE) ) + { + return(1); + } + + if( p_ucb->UCB_targid >= pCurrCard->cardInfo->ai_MaxTarg ) + { + return(1); + } +#endif + + ioPort = pCurrCard->ioPort; + pData = (u08bits *)p_ucb->UCB_virt_dataptr; + scsiID = (u08bits) p_ucb->UCB_targid; + currTar_Info = &sccbMgrTbl[pCurrCard->cardIndex][scsiID]; + + if(*pData) + { + if(currTar_Info->TarEEValue & EE_WIDE_SCSI) + { + return(0); + } + else + { + scsiWideMode = EE_WIDE_SCSI; + } + } + else + { + if(!currTar_Info->TarEEValue & EE_WIDE_SCSI) + { + return(0); + } + else + { + scsiWideMode = 0; + } + } + currTar_Info->TarEEValue = (currTar_Info->TarEEValue & !EE_WIDE_SCSI) + | scsiWideMode; + + syncOffset = (SYNC_RATE_TBL + scsiID) / 2; + temp2.tempw = utilEERead(ioPort, syncOffset); + if(scsiID & 0x01) + { + temp2.tempb[0] = (temp2.tempb[0] & !EE_WIDE_SCSI) | scsiWideMode; + } + else + { + temp2.tempb[1] = (temp2.tempb[1] & !EE_WIDE_SCSI) | scsiWideMode; + } + utilEEWriteOnOff(ioPort, 1); + utilEEWrite(ioPort, temp2.tempw, syncOffset); + utilEEWriteOnOff(ioPort, 0); + UpdateCheckSum(ioPort); + + return(0); +} + +/*--------------------------------------------------------------------- + * + * Function: ReadNVRam + * + *---------------------------------------------------------------------*/ +void ReadNVRam(PSCCBcard pCurrCard,PUCB p_ucb) +{ + u08bits *pdata; + u16bits i,numwrds,numbytes,offset,temp; + u08bits OneMore = FALSE; +#if defined(DOS) + u16bits ioport; +#else + u32bits ioport; +#endif + + numbytes = (u16bits) p_ucb->UCB_datalen; + ioport = pCurrCard->ioPort; + pdata = (u08bits *) p_ucb->UCB_virt_dataptr; + offset = (u16bits) (p_ucb->UCB_IOCTLParams[0]); + + + + if (offset & 0x1) + { + *((u16bits*) pdata) = utilEERead(ioport,(u16bits)((offset - 1) / 2)); /* 16 bit read */ + *pdata = *(pdata + 1); + ++offset; + ++pdata; + --numbytes; + } + + numwrds = numbytes / 2; + if (numbytes & 1) + OneMore = TRUE; + + for (i = 0; i < numwrds; i++) + { + *((u16bits*) pdata) = utilEERead(ioport,(u16bits)(offset / 2)); + pdata += 2; + offset += 2; + } + if (OneMore) + { + --pdata; + -- offset; + temp = utilEERead(ioport,(u16bits)(offset / 2)); + *pdata = (u08bits) (temp); + } + +} /* end proc ReadNVRam */ + + +/*--------------------------------------------------------------------- + * + * Function: WriteNVRam + * + *---------------------------------------------------------------------*/ +void WriteNVRam(PSCCBcard pCurrCard,PUCB p_ucb) +{ + u08bits *pdata; + u16bits i,numwrds,numbytes,offset, eeprom_end; + u08bits OneMore = FALSE; + union { + u08bits tempb[2]; + u16bits tempw; + } temp2; + +#if defined(DOS) + u16bits ioport; +#else + u32bits ioport; +#endif + + numbytes = (u16bits) p_ucb->UCB_datalen; + ioport = pCurrCard->ioPort; + pdata = (u08bits *) p_ucb->UCB_virt_dataptr; + offset = (u16bits) (p_ucb->UCB_IOCTLParams[0]); + + if (RD_HARPOON(ioport+hp_page_ctrl) & NARROW_SCSI_CARD) + eeprom_end = 512; + else + eeprom_end = 768; + + if(offset > eeprom_end) + return; + + if((offset + numbytes) > eeprom_end) + numbytes = eeprom_end - offset; + + utilEEWriteOnOff(ioport,1); /* Enable write access to the EEPROM */ + + + + if (offset & 0x1) + { + temp2.tempw = utilEERead(ioport,(u16bits)((offset - 1) / 2)); /* 16 bit read */ + temp2.tempb[1] = *pdata; + utilEEWrite(ioport, temp2.tempw, (u16bits)((offset -1) / 2)); + *pdata = *(pdata + 1); + ++offset; + ++pdata; + --numbytes; + } + + numwrds = numbytes / 2; + if (numbytes & 1) + OneMore = TRUE; + + for (i = 0; i < numwrds; i++) + { + utilEEWrite(ioport, *((pu16bits)pdata),(u16bits)(offset / 2)); + pdata += 2; + offset += 2; + } + if (OneMore) + { + + temp2.tempw = utilEERead(ioport,(u16bits)(offset / 2)); + temp2.tempb[0] = *pdata; + utilEEWrite(ioport, temp2.tempw, (u16bits)(offset / 2)); + } + utilEEWriteOnOff(ioport,0); /* Turn off write access */ + UpdateCheckSum((u32bits)ioport); + +} /* end proc WriteNVRam */ + + + +/*--------------------------------------------------------------------- + * + * Function: UpdateCheckSum + * + * Description: Update Check Sum in EEPROM + * + *---------------------------------------------------------------------*/ + + +void UpdateCheckSum(u32bits baseport) +{ + USHORT i,sum_data, eeprom_end; + + sum_data = 0x0000; + + + if (RD_HARPOON(baseport+hp_page_ctrl) & NARROW_SCSI_CARD) + eeprom_end = 512; + else + eeprom_end = 768; + + for (i = 1; i < eeprom_end/2; i++) + { + sum_data += utilEERead(baseport, i); + } + + utilEEWriteOnOff(baseport,1); /* Enable write access to the EEPROM */ + + utilEEWrite(baseport, sum_data, EEPROM_CHECK_SUM/2); + utilEEWriteOnOff(baseport,0); /* Turn off write access */ +} + +void SccbMgr_save_foreign_state(PADAPTER_INFO pAdapterInfo) +{ +} + + +void SccbMgr_restore_foreign_state(CARD_HANDLE pCurrCard) +{ +} + +void SccbMgr_restore_native_state(CARD_HANDLE pCurrCard) +{ +} + +#endif /* NO_IOCTLS */ + +#endif /* (FW_TYPE==_UCB_MGR_) */ + +#ifndef NO_IOCTLS +#if (FW_TYPE==_UCB_MGR_) +void SccbMgr_unload_card(CARD_HANDLE pCurrCard) +#else +#if defined(DOS) +void SccbMgr_unload_card(USHORT pCurrCard) +#else +void SccbMgr_unload_card(ULONG pCurrCard) +#endif +#endif +{ + UCHAR i; +#if defined(DOS) + USHORT portBase; + USHORT regOffset; +#else + ULONG portBase; + ULONG regOffset; +#endif + ULONG scamData; +#if defined(OS2) + ULONG far *pScamTbl; +#else + ULONG *pScamTbl; +#endif + PNVRamInfo pCurrNvRam; + + pCurrNvRam = ((PSCCBcard)pCurrCard)->pNvRamInfo; + + if(pCurrNvRam){ + WrStack(pCurrNvRam->niBaseAddr, 0, pCurrNvRam->niModel); + WrStack(pCurrNvRam->niBaseAddr, 1, pCurrNvRam->niSysConf); + WrStack(pCurrNvRam->niBaseAddr, 2, pCurrNvRam->niScsiConf); + WrStack(pCurrNvRam->niBaseAddr, 3, pCurrNvRam->niScamConf); + WrStack(pCurrNvRam->niBaseAddr, 4, pCurrNvRam->niAdapId); + + for(i = 0; i < MAX_SCSI_TAR / 2; i++) + WrStack(pCurrNvRam->niBaseAddr, (UCHAR)(i+5), pCurrNvRam->niSyncTbl[i]); + + portBase = pCurrNvRam->niBaseAddr; + + for(i = 0; i < MAX_SCSI_TAR; i++){ + regOffset = hp_aramBase + 64 + i*4; +#if defined(OS2) + pScamTbl = (ULONG far *) &pCurrNvRam->niScamTbl[i]; +#else + pScamTbl = (ULONG *) &pCurrNvRam->niScamTbl[i]; +#endif + scamData = *pScamTbl; + WR_HARP32(portBase, regOffset, scamData); + } + + }else{ + WrStack(((PSCCBcard)pCurrCard)->ioPort, 0, 0); + } +} +#endif /* NO_IOCTLS */ + + +void RNVRamData(PNVRamInfo pNvRamInfo) +{ + UCHAR i; +#if defined(DOS) + USHORT portBase; + USHORT regOffset; +#else + ULONG portBase; + ULONG regOffset; +#endif + ULONG scamData; +#if defined (OS2) + ULONG far *pScamTbl; +#else + ULONG *pScamTbl; +#endif + + pNvRamInfo->niModel = RdStack(pNvRamInfo->niBaseAddr, 0); + pNvRamInfo->niSysConf = RdStack(pNvRamInfo->niBaseAddr, 1); + pNvRamInfo->niScsiConf = RdStack(pNvRamInfo->niBaseAddr, 2); + pNvRamInfo->niScamConf = RdStack(pNvRamInfo->niBaseAddr, 3); + pNvRamInfo->niAdapId = RdStack(pNvRamInfo->niBaseAddr, 4); + + for(i = 0; i < MAX_SCSI_TAR / 2; i++) + pNvRamInfo->niSyncTbl[i] = RdStack(pNvRamInfo->niBaseAddr, (UCHAR)(i+5)); + + portBase = pNvRamInfo->niBaseAddr; + + for(i = 0; i < MAX_SCSI_TAR; i++){ + regOffset = hp_aramBase + 64 + i*4; + RD_HARP32(portBase, regOffset, scamData); +#if defined(OS2) + pScamTbl = (ULONG far *) &pNvRamInfo->niScamTbl[i]; +#else + pScamTbl = (ULONG *) &pNvRamInfo->niScamTbl[i]; +#endif + *pScamTbl = scamData; + } + +} + +#if defined(DOS) +UCHAR RdStack(USHORT portBase, UCHAR index) +#else +UCHAR RdStack(ULONG portBase, UCHAR index) +#endif +{ + WR_HARPOON(portBase + hp_stack_addr, index); + return(RD_HARPOON(portBase + hp_stack_data)); +} + +#if defined(DOS) +void WrStack(USHORT portBase, UCHAR index, UCHAR data) +#else +void WrStack(ULONG portBase, UCHAR index, UCHAR data) +#endif +{ + WR_HARPOON(portBase + hp_stack_addr, index); + WR_HARPOON(portBase + hp_stack_data, data); +} + + +#if (FW_TYPE==_UCB_MGR_) +u08bits ChkIfChipInitialized(BASE_PORT ioPort) +#else +#if defined(DOS) +UCHAR ChkIfChipInitialized(USHORT ioPort) +#else +UCHAR ChkIfChipInitialized(ULONG ioPort) +#endif +#endif +{ + if((RD_HARPOON(ioPort + hp_arb_id) & 0x0f) != RdStack(ioPort, 4)) + return(FALSE); + if((RD_HARPOON(ioPort + hp_clkctrl_0) & CLKCTRL_DEFAULT) + != CLKCTRL_DEFAULT) + return(FALSE); + if((RD_HARPOON(ioPort + hp_seltimeout) == TO_250ms) || + (RD_HARPOON(ioPort + hp_seltimeout) == TO_290ms)) + return(TRUE); + return(FALSE); + +} +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_start_sccb + * + * Description: Start a command pointed to by p_Sccb. When the + * command is completed it will be returned via the + * callback function. + * + *---------------------------------------------------------------------*/ +#if (FW_TYPE==_UCB_MGR_) +void SccbMgr_start_sccb(CARD_HANDLE pCurrCard, PUCB p_ucb) +#else +#if defined(DOS) +void SccbMgr_start_sccb(USHORT pCurrCard, PSCCB p_Sccb) +#else +void SccbMgr_start_sccb(ULONG pCurrCard, PSCCB p_Sccb) +#endif +#endif +{ +#if defined(DOS) + USHORT ioport; +#else + ULONG ioport; +#endif + UCHAR thisCard, lun; + PSCCB pSaveSccb; + CALL_BK_FN callback; + +#if (FW_TYPE==_UCB_MGR_) + PSCCB p_Sccb; +#endif + + mOS_Lock((PSCCBcard)pCurrCard); + thisCard = ((PSCCBcard) pCurrCard)->cardIndex; + ioport = ((PSCCBcard) pCurrCard)->ioPort; + +#if (FW_TYPE==_UCB_MGR_) + p_Sccb = (PSCCB)p_ucb->UCB_MgrPrivatePtr; +#endif + + if((p_Sccb->TargID > MAX_SCSI_TAR) || (p_Sccb->Lun > MAX_LUN)) + { + +#if (FW_TYPE==_UCB_MGR_) + p_ucb->UCB_hbastat = SCCB_COMPLETE; + p_ucb->UCB_status=SCCB_ERROR; + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); +#endif + +#if (FW_TYPE==_SCCB_MGR_) + p_Sccb->HostStatus = SCCB_COMPLETE; + p_Sccb->SccbStatus = SCCB_ERROR; + callback = (CALL_BK_FN)p_Sccb->SccbCallback; + if (callback) + callback(p_Sccb); +#endif + + mOS_UnLock((PSCCBcard)pCurrCard); + return; + } + +#if (FW_TYPE==_SCCB_MGR_) + sinits(p_Sccb,thisCard); +#endif + + +#if (FW_TYPE==_UCB_MGR_) +#ifndef NO_IOCTLS + + if (p_ucb->UCB_opcode & OPC_IOCTL) + { + + switch (p_ucb->UCB_IOCTLCommand) + { + case READ_NVRAM: + ReadNVRam((PSCCBcard)pCurrCard,p_ucb); + p_ucb->UCB_status=UCB_SUCCESS; + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + + case WRITE_NVRAM: + WriteNVRam((PSCCBcard)pCurrCard,p_ucb); + p_ucb->UCB_status=UCB_SUCCESS; + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + + case SEND_SCSI_PASSTHRU: +#if (FW_TYPE != _SCCB_MGR_) + if( p_ucb->UCB_targid >= + ((PSCCBcard)pCurrCard)->cardInfo->ai_MaxTarg ) + { + p_ucb->UCB_status = UCB_ERROR; + p_ucb->UCB_hbastat = HASTAT_HW_ERROR; + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + } +#endif + break; + + case HARD_RESET: + p_ucb->UCB_status = UCB_INVALID; + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + case GET_DEVICE_SYNCRATE: + if( !GetDevSyncRate((PSCCBcard)pCurrCard,p_ucb) ) + { + p_ucb->UCB_status = UCB_SUCCESS; + } + else + { + p_ucb->UCB_status = UCB_ERROR; + p_ucb->UCB_hbastat = HASTAT_HW_ERROR; + } + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + case SET_DEVICE_SYNCRATE: + if( !SetDevSyncRate((PSCCBcard)pCurrCard,p_ucb) ) + { + p_ucb->UCB_status = UCB_SUCCESS; + } + else + { + p_ucb->UCB_status = UCB_ERROR; + p_ucb->UCB_hbastat = HASTAT_HW_ERROR; + } + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + case GET_WIDE_MODE: + if( !GetDevWideMode((PSCCBcard)pCurrCard,p_ucb) ) + { + p_ucb->UCB_status = UCB_SUCCESS; + } + else + { + p_ucb->UCB_status = UCB_ERROR; + p_ucb->UCB_hbastat = HASTAT_HW_ERROR; + } + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + case SET_WIDE_MODE: + if( !SetDevWideMode((PSCCBcard)pCurrCard,p_ucb) ) + { + p_ucb->UCB_status = UCB_SUCCESS; + } + else + { + p_ucb->UCB_status = UCB_ERROR; + p_ucb->UCB_hbastat = HASTAT_HW_ERROR; + } + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + default: + p_ucb->UCB_status=UCB_INVALID; + callback = (CALL_BK_FN)p_ucb->UCB_callback; + if (callback) + callback(p_ucb); + mOS_UnLock((PSCCBcard)pCurrCard); + return; + } + } +#endif /* NO_IOCTLS */ +#endif /* (FW_TYPE==_UCB_MGR_) */ + + + if (!((PSCCBcard) pCurrCard)->cmdCounter) + { + WR_HARPOON(ioport+hp_semaphore, (RD_HARPOON(ioport+hp_semaphore) + | SCCB_MGR_ACTIVE)); + + if (((PSCCBcard) pCurrCard)->globalFlags & F_GREEN_PC) + { + WR_HARPOON(ioport+hp_clkctrl_0, CLKCTRL_DEFAULT); + WR_HARPOON(ioport+hp_sys_ctrl, 0x00); + } + } + + ((PSCCBcard)pCurrCard)->cmdCounter++; + + if (RD_HARPOON(ioport+hp_semaphore) & BIOS_IN_USE) { + + WR_HARPOON(ioport+hp_semaphore, (RD_HARPOON(ioport+hp_semaphore) + | TICKLE_ME)); + if(p_Sccb->OperationCode == RESET_COMMAND) + { + pSaveSccb = ((PSCCBcard) pCurrCard)->currentSCCB; + ((PSCCBcard) pCurrCard)->currentSCCB = p_Sccb; + queueSelectFail(&BL_Card[thisCard], thisCard); + ((PSCCBcard) pCurrCard)->currentSCCB = pSaveSccb; + } + else + { + queueAddSccb(p_Sccb,thisCard); + } + } + + else if ((RD_HARPOON(ioport+hp_page_ctrl) & G_INT_DISABLE)) { + + if(p_Sccb->OperationCode == RESET_COMMAND) + { + pSaveSccb = ((PSCCBcard) pCurrCard)->currentSCCB; + ((PSCCBcard) pCurrCard)->currentSCCB = p_Sccb; + queueSelectFail(&BL_Card[thisCard], thisCard); + ((PSCCBcard) pCurrCard)->currentSCCB = pSaveSccb; + } + else + { + queueAddSccb(p_Sccb,thisCard); + } + } + + else { + + MDISABLE_INT(ioport); + + if((((PSCCBcard) pCurrCard)->globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[thisCard][p_Sccb->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + lun = p_Sccb->Lun; + else + lun = 0; + if ((((PSCCBcard) pCurrCard)->currentSCCB == NULL) && + (sccbMgrTbl[thisCard][p_Sccb->TargID].TarSelQ_Cnt == 0) && + (sccbMgrTbl[thisCard][p_Sccb->TargID].TarLUNBusy[lun] + == FALSE)) { + + ((PSCCBcard) pCurrCard)->currentSCCB = p_Sccb; + mOS_UnLock((PSCCBcard)pCurrCard); +#if defined(DOS) + ssel((USHORT)p_Sccb->SccbIOPort,thisCard); +#else + ssel(p_Sccb->SccbIOPort,thisCard); +#endif + mOS_Lock((PSCCBcard)pCurrCard); + } + + else { + + if(p_Sccb->OperationCode == RESET_COMMAND) + { + pSaveSccb = ((PSCCBcard) pCurrCard)->currentSCCB; + ((PSCCBcard) pCurrCard)->currentSCCB = p_Sccb; + queueSelectFail(&BL_Card[thisCard], thisCard); + ((PSCCBcard) pCurrCard)->currentSCCB = pSaveSccb; + } + else + { + queueAddSccb(p_Sccb,thisCard); + } + } + + + MENABLE_INT(ioport); + } + + mOS_UnLock((PSCCBcard)pCurrCard); +} + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_abort_sccb + * + * Description: Abort the command pointed to by p_Sccb. When the + * command is completed it will be returned via the + * callback function. + * + *---------------------------------------------------------------------*/ +#if (FW_TYPE==_UCB_MGR_) +s32bits SccbMgr_abort_sccb(CARD_HANDLE pCurrCard, PUCB p_ucb) +#else +#if defined(DOS) +int SccbMgr_abort_sccb(USHORT pCurrCard, PSCCB p_Sccb) +#else +int SccbMgr_abort_sccb(ULONG pCurrCard, PSCCB p_Sccb) +#endif +#endif + +{ +#if defined(DOS) + USHORT ioport; +#else + ULONG ioport; +#endif + + UCHAR thisCard; + CALL_BK_FN callback; + UCHAR TID; + PSCCB pSaveSCCB; + PSCCBMgr_tar_info currTar_Info; + + +#if (FW_TYPE==_UCB_MGR_) + PSCCB p_Sccb; + p_Sccb=(PSCCB)p_ucb->UCB_MgrPrivatePtr; +#endif + + ioport = ((PSCCBcard) pCurrCard)->ioPort; + + thisCard = ((PSCCBcard)pCurrCard)->cardIndex; + + mOS_Lock((PSCCBcard)pCurrCard); + + if (RD_HARPOON(ioport+hp_page_ctrl) & G_INT_DISABLE) + { + mOS_UnLock((PSCCBcard)pCurrCard); + } + + else + { + + if (queueFindSccb(p_Sccb,thisCard)) + { + + mOS_UnLock((PSCCBcard)pCurrCard); + + ((PSCCBcard)pCurrCard)->cmdCounter--; + + if (!((PSCCBcard)pCurrCard)->cmdCounter) + WR_HARPOON(ioport+hp_semaphore,(RD_HARPOON(ioport+hp_semaphore) + & (UCHAR)(~(SCCB_MGR_ACTIVE | TICKLE_ME)) )); + +#if (FW_TYPE==_SCCB_MGR_) + p_Sccb->SccbStatus = SCCB_ABORT; + callback = p_Sccb->SccbCallback; + callback(p_Sccb); +#else + p_ucb->UCB_status=SCCB_ABORT; + callback = (CALL_BK_FN)p_ucb->UCB_callback; + callback(p_ucb); +#endif + + return(0); + } + + else + { + mOS_UnLock((PSCCBcard)pCurrCard); + + if (((PSCCBcard)pCurrCard)->currentSCCB == p_Sccb) + { + p_Sccb->SccbStatus = SCCB_ABORT; + return(0); + + } + + else + { + + TID = p_Sccb->TargID; + + + if(p_Sccb->Sccb_tag) + { + MDISABLE_INT(ioport); + if (((PSCCBcard) pCurrCard)->discQ_Tbl[p_Sccb->Sccb_tag]==p_Sccb) + { + p_Sccb->SccbStatus = SCCB_ABORT; + p_Sccb->Sccb_scsistat = ABORT_ST; +#if (FW_TYPE==_UCB_MGR_) + p_ucb->UCB_status=SCCB_ABORT; +#endif + p_Sccb->Sccb_scsimsg = SMABORT_TAG; + + if(((PSCCBcard) pCurrCard)->currentSCCB == NULL) + { + ((PSCCBcard) pCurrCard)->currentSCCB = p_Sccb; + ssel(ioport, thisCard); + } + else + { + pSaveSCCB = ((PSCCBcard) pCurrCard)->currentSCCB; + ((PSCCBcard) pCurrCard)->currentSCCB = p_Sccb; + queueSelectFail((PSCCBcard) pCurrCard, thisCard); + ((PSCCBcard) pCurrCard)->currentSCCB = pSaveSCCB; + } + } + MENABLE_INT(ioport); + return(0); + } + else + { + currTar_Info = &sccbMgrTbl[thisCard][p_Sccb->TargID]; + + if(BL_Card[thisCard].discQ_Tbl[currTar_Info->LunDiscQ_Idx[p_Sccb->Lun]] + == p_Sccb) + { + p_Sccb->SccbStatus = SCCB_ABORT; + return(0); + } + } + } + } + } + return(-1); +} + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_my_int + * + * Description: Do a quick check to determine if there is a pending + * interrupt for this card and disable the IRQ Pin if so. + * + *---------------------------------------------------------------------*/ +#if (FW_TYPE==_UCB_MGR_) +u08bits SccbMgr_my_int(CARD_HANDLE pCurrCard) +#else +#if defined(DOS) +UCHAR SccbMgr_my_int(USHORT pCurrCard) +#else +UCHAR SccbMgr_my_int(ULONG pCurrCard) +#endif +#endif +{ +#if defined(DOS) + USHORT ioport; +#else + ULONG ioport; +#endif + + ioport = ((PSCCBcard)pCurrCard)->ioPort; + + if (RD_HARPOON(ioport+hp_int_status) & INT_ASSERTED) + { + +#if defined(DOS) + MDISABLE_INT(ioport); +#endif + + return(TRUE); + } + + else + + return(FALSE); +} + + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_isr + * + * Description: This is our entry point when an interrupt is generated + * by the card and the upper level driver passes it on to + * us. + * + *---------------------------------------------------------------------*/ +#if (FW_TYPE==_UCB_MGR_) +s32bits SccbMgr_isr(CARD_HANDLE pCurrCard) +#else +#if defined(DOS) +int SccbMgr_isr(USHORT pCurrCard) +#else +int SccbMgr_isr(ULONG pCurrCard) +#endif +#endif +{ + PSCCB currSCCB; + UCHAR thisCard,result,bm_status, bm_int_st; + USHORT hp_int; + UCHAR i, target; +#if defined(DOS) + USHORT ioport; +#else + ULONG ioport; +#endif + + mOS_Lock((PSCCBcard)pCurrCard); + + thisCard = ((PSCCBcard)pCurrCard)->cardIndex; + ioport = ((PSCCBcard)pCurrCard)->ioPort; + + MDISABLE_INT(ioport); + +#if defined(BUGBUG) + WR_HARPOON(ioport+hp_user_defined_D, RD_HARPOON(ioport+hp_int_status)); +#endif + + if ((bm_int_st=RD_HARPOON(ioport+hp_int_status)) & EXT_STATUS_ON) + bm_status = RD_HARPOON(ioport+hp_ext_status) & (UCHAR)BAD_EXT_STATUS; + else + bm_status = 0; + + WR_HARPOON(ioport+hp_int_mask, (INT_CMD_COMPL | SCSI_INTERRUPT)); + + mOS_UnLock((PSCCBcard)pCurrCard); + + while ((hp_int = RDW_HARPOON((ioport+hp_intstat)) & default_intena) | + bm_status) + { + + currSCCB = ((PSCCBcard)pCurrCard)->currentSCCB; + +#if defined(BUGBUG) + Debug_Load(thisCard,(UCHAR) 0XFF); + Debug_Load(thisCard,bm_int_st); + + Debug_Load(thisCard,hp_int_0); + Debug_Load(thisCard,hp_int_1); +#endif + + + if (hp_int & (FIFO | TIMEOUT | RESET | SCAM_SEL) || bm_status) { + result = SccbMgr_bad_isr(ioport,thisCard,((PSCCBcard)pCurrCard),hp_int); + WRW_HARPOON((ioport+hp_intstat), (FIFO | TIMEOUT | RESET | SCAM_SEL)); + bm_status = 0; + + if (result) { + + mOS_Lock((PSCCBcard)pCurrCard); + MENABLE_INT(ioport); + mOS_UnLock((PSCCBcard)pCurrCard); + return(result); + } + } + + + else if (hp_int & ICMD_COMP) { + + if ( !(hp_int & BUS_FREE) ) { + /* Wait for the BusFree before starting a new command. We + must also check for being reselected since the BusFree + may not show up if another device reselects us in 1.5us or + less. SRR Wednesday, 3/8/1995. + */ + while (!(RDW_HARPOON((ioport+hp_intstat)) & (BUS_FREE | RSEL))) ; + } + + if (((PSCCBcard)pCurrCard)->globalFlags & F_HOST_XFER_ACT) + + phaseChkFifo(ioport, thisCard); + +/* WRW_HARPOON((ioport+hp_intstat), + (BUS_FREE | ICMD_COMP | ITAR_DISC | XFER_CNT_0)); + */ + + WRW_HARPOON((ioport+hp_intstat), CLR_ALL_INT_1); + + autoCmdCmplt(ioport,thisCard); + + } + + + else if (hp_int & ITAR_DISC) + { + + if (((PSCCBcard)pCurrCard)->globalFlags & F_HOST_XFER_ACT) { + + phaseChkFifo(ioport, thisCard); + + } + + if (RD_HARPOON(ioport+hp_gp_reg_1) == SMSAVE_DATA_PTR) { + + WR_HARPOON(ioport+hp_gp_reg_1, 0x00); + currSCCB->Sccb_XferState |= F_NO_DATA_YET; + + currSCCB->Sccb_savedATC = currSCCB->Sccb_ATC; + } + + currSCCB->Sccb_scsistat = DISCONNECT_ST; + queueDisconnect(currSCCB,thisCard); + + /* Wait for the BusFree before starting a new command. We + must also check for being reselected since the BusFree + may not show up if another device reselects us in 1.5us or + less. SRR Wednesday, 3/8/1995. + */ + while (!(RDW_HARPOON((ioport+hp_intstat)) & (BUS_FREE | RSEL)) && + !((RDW_HARPOON((ioport+hp_intstat)) & PHASE) && + RD_HARPOON((ioport+hp_scsisig)) == + (SCSI_BSY | SCSI_REQ | SCSI_CD | SCSI_MSG | SCSI_IOBIT))) ; + + /* + The additional loop exit condition above detects a timing problem + with the revision D/E harpoon chips. The caller should reset the + host adapter to recover when 0xFE is returned. + */ + if (!(RDW_HARPOON((ioport+hp_intstat)) & (BUS_FREE | RSEL))) + { + mOS_Lock((PSCCBcard)pCurrCard); + MENABLE_INT(ioport); + mOS_UnLock((PSCCBcard)pCurrCard); + return 0xFE; + } + + WRW_HARPOON((ioport+hp_intstat), (BUS_FREE | ITAR_DISC)); + + + ((PSCCBcard)pCurrCard)->globalFlags |= F_NEW_SCCB_CMD; + + } + + + else if (hp_int & RSEL) { + + WRW_HARPOON((ioport+hp_intstat), (PROG_HLT | RSEL | PHASE | BUS_FREE)); + + if (RDW_HARPOON((ioport+hp_intstat)) & ITAR_DISC) + { + if (((PSCCBcard)pCurrCard)->globalFlags & F_HOST_XFER_ACT) + { + phaseChkFifo(ioport, thisCard); + } + + if (RD_HARPOON(ioport+hp_gp_reg_1) == SMSAVE_DATA_PTR) + { + WR_HARPOON(ioport+hp_gp_reg_1, 0x00); + currSCCB->Sccb_XferState |= F_NO_DATA_YET; + currSCCB->Sccb_savedATC = currSCCB->Sccb_ATC; + } + + WRW_HARPOON((ioport+hp_intstat), (BUS_FREE | ITAR_DISC)); + currSCCB->Sccb_scsistat = DISCONNECT_ST; + queueDisconnect(currSCCB,thisCard); + } + + sres(ioport,thisCard,((PSCCBcard)pCurrCard)); + phaseDecode(ioport,thisCard); + + } + + + else if ((hp_int & IDO_STRT) && (!(hp_int & BUS_FREE))) + { + + WRW_HARPOON((ioport+hp_intstat), (IDO_STRT | XFER_CNT_0)); + phaseDecode(ioport,thisCard); + + } + + + else if ( (hp_int & IUNKWN) || (hp_int & PROG_HLT) ) + { + WRW_HARPOON((ioport+hp_intstat), (PHASE | IUNKWN | PROG_HLT)); + if ((RD_HARPOON(ioport+hp_prgmcnt_0) & (UCHAR)0x3f)< (UCHAR)SELCHK) + { + phaseDecode(ioport,thisCard); + } + else + { + /* Harpoon problem some SCSI target device respond to selection + with short BUSY pulse (<400ns) this will make the Harpoon is not able + to latch the correct Target ID into reg. x53. + The work around require to correct this reg. But when write to this + reg. (0x53) also increment the FIFO write addr reg (0x6f), thus we + need to read this reg first then restore it later. After update to 0x53 */ + + i = (UCHAR)(RD_HARPOON(ioport+hp_fifowrite)); + target = (UCHAR)(RD_HARPOON(ioport+hp_gp_reg_3)); + WR_HARPOON(ioport+hp_xfer_pad, (UCHAR) ID_UNLOCK); + WR_HARPOON(ioport+hp_select_id, (UCHAR)(target | target<<4)); + WR_HARPOON(ioport+hp_xfer_pad, (UCHAR) 0x00); + WR_HARPOON(ioport+hp_fifowrite, i); + WR_HARPOON(ioport+hp_autostart_3, (AUTO_IMMED+TAG_STRT)); + } + } + + else if (hp_int & XFER_CNT_0) { + + WRW_HARPOON((ioport+hp_intstat), XFER_CNT_0); + + schkdd(ioport,thisCard); + + } + + + else if (hp_int & BUS_FREE) { + + WRW_HARPOON((ioport+hp_intstat), BUS_FREE); + + if (((PSCCBcard)pCurrCard)->globalFlags & F_HOST_XFER_ACT) { + + hostDataXferAbort(ioport,thisCard,currSCCB); + } + + phaseBusFree(ioport,thisCard); + } + + + else if (hp_int & ITICKLE) { + + WRW_HARPOON((ioport+hp_intstat), ITICKLE); + ((PSCCBcard)pCurrCard)->globalFlags |= F_NEW_SCCB_CMD; + } + + + + if (((PSCCBcard)pCurrCard)->globalFlags & F_NEW_SCCB_CMD) { + + + ((PSCCBcard)pCurrCard)->globalFlags &= ~F_NEW_SCCB_CMD; + + + if (((PSCCBcard)pCurrCard)->currentSCCB == NULL) { + + queueSearchSelect(((PSCCBcard)pCurrCard),thisCard); + } + + if (((PSCCBcard)pCurrCard)->currentSCCB != NULL) { + ((PSCCBcard)pCurrCard)->globalFlags &= ~F_NEW_SCCB_CMD; + ssel(ioport,thisCard); + } + + break; + + } + + } /*end while */ + + mOS_Lock((PSCCBcard)pCurrCard); + MENABLE_INT(ioport); + mOS_UnLock((PSCCBcard)pCurrCard); + + return(0); +} + +/*--------------------------------------------------------------------- + * + * Function: Sccb_bad_isr + * + * Description: Some type of interrupt has occurred which is slightly + * out of the ordinary. We will now decode it fully, in + * this routine. This is broken up in an attempt to save + * processing time. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +UCHAR SccbMgr_bad_isr(USHORT p_port, UCHAR p_card, PSCCBcard pCurrCard, USHORT p_int) +#else +UCHAR SccbMgr_bad_isr(ULONG p_port, UCHAR p_card, PSCCBcard pCurrCard, USHORT p_int) +#endif +{ +#if defined(HARP_REVX) + ULONG timer; +#endif +UCHAR temp, ScamFlg; +PSCCBMgr_tar_info currTar_Info; +PNVRamInfo pCurrNvRam; + + + if (RD_HARPOON(p_port+hp_ext_status) & + (BM_FORCE_OFF | PCI_DEV_TMOUT | BM_PARITY_ERR | PIO_OVERRUN) ) + { + + if (pCurrCard->globalFlags & F_HOST_XFER_ACT) + { + + hostDataXferAbort(p_port,p_card, pCurrCard->currentSCCB); + } + + if (RD_HARPOON(p_port+hp_pci_stat_cfg) & REC_MASTER_ABORT) + + { + WR_HARPOON(p_port+hp_pci_stat_cfg, + (RD_HARPOON(p_port+hp_pci_stat_cfg) & ~REC_MASTER_ABORT)); + + WR_HARPOON(p_port+hp_host_blk_cnt, 0x00); + + } + + if (pCurrCard->currentSCCB != NULL) + { + + if (!pCurrCard->currentSCCB->HostStatus) + pCurrCard->currentSCCB->HostStatus = SCCB_BM_ERR; + + sxfrp(p_port,p_card); + + temp = (UCHAR)(RD_HARPOON(p_port+hp_ee_ctrl) & + (EXT_ARB_ACK | SCSI_TERM_ENA_H)); + WR_HARPOON(p_port+hp_ee_ctrl, ((UCHAR)temp | SEE_MS | SEE_CS)); + WR_HARPOON(p_port+hp_ee_ctrl, temp); + + if (!(RDW_HARPOON((p_port+hp_intstat)) & (BUS_FREE | RESET))) + { + phaseDecode(p_port,p_card); + } + } + } + + + else if (p_int & RESET) + { + + WR_HARPOON(p_port+hp_clkctrl_0, CLKCTRL_DEFAULT); + WR_HARPOON(p_port+hp_sys_ctrl, 0x00); + if (pCurrCard->currentSCCB != NULL) { + + if (pCurrCard->globalFlags & F_HOST_XFER_ACT) + + hostDataXferAbort(p_port,p_card, pCurrCard->currentSCCB); + } + + + DISABLE_AUTO(p_port); + + sresb(p_port,p_card); + + while(RD_HARPOON(p_port+hp_scsictrl_0) & SCSI_RST) {} + + pCurrNvRam = pCurrCard->pNvRamInfo; + if(pCurrNvRam){ + ScamFlg = pCurrNvRam->niScamConf; + } + else{ + ScamFlg = (UCHAR) utilEERead(p_port, SCAM_CONFIG/2); + } + + XbowInit(p_port, ScamFlg); + + scini(p_card, pCurrCard->ourId, 0); + + return(0xFF); + } + + + else if (p_int & FIFO) { + + WRW_HARPOON((p_port+hp_intstat), FIFO); + +#if defined(HARP_REVX) + + for (timer=0x00FFFFFFL; timer != 0x00000000L; timer--) { + + if (RD_HARPOON(p_port+hp_xferstat) & FIFO_EMPTY) + break; + + if (RDW_HARPOON((p_port+hp_intstat)) & BUS_FREE) + break; + } + + + if ( (RD_HARPOON(p_port+hp_xferstat) & FIFO_EMPTY) && + (RD_HARPOON(p_port+hp_fiforead) != + RD_HARPOON(p_port+hp_fifowrite)) && + (RD_HARPOON(p_port+hp_xfercnt_0)) + ) + + WR_HARPOON((p_port+hp_xferstat), 0x01); + +/* else + */ +/* sxfrp(p_port,p_card); + */ +#else + if (pCurrCard->currentSCCB != NULL) + sxfrp(p_port,p_card); +#endif + } + + else if (p_int & TIMEOUT) + { + + DISABLE_AUTO(p_port); + + WRW_HARPOON((p_port+hp_intstat), + (PROG_HLT | TIMEOUT | SEL |BUS_FREE | PHASE | IUNKWN)); + + pCurrCard->currentSCCB->HostStatus = SCCB_SELECTION_TIMEOUT; + + + currTar_Info = &sccbMgrTbl[p_card][pCurrCard->currentSCCB->TargID]; + if((pCurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + currTar_Info->TarLUNBusy[pCurrCard->currentSCCB->Lun] = FALSE; + else + currTar_Info->TarLUNBusy[0] = FALSE; + + + if (currTar_Info->TarEEValue & EE_SYNC_MASK) + { + currTar_Info->TarSyncCtrl = 0; + currTar_Info->TarStatus &= ~TAR_SYNC_MASK; + } + + if (currTar_Info->TarEEValue & EE_WIDE_SCSI) + { + currTar_Info->TarStatus &= ~TAR_WIDE_MASK; + } + + sssyncv(p_port, pCurrCard->currentSCCB->TargID, NARROW_SCSI,currTar_Info); + + queueCmdComplete(pCurrCard, pCurrCard->currentSCCB, p_card); + + } + +#if defined(SCAM_LEV_2) + + else if (p_int & SCAM_SEL) + { + + scarb(p_port,LEVEL2_TAR); + scsel(p_port); + scasid(p_card, p_port); + + scbusf(p_port); + + WRW_HARPOON((p_port+hp_intstat), SCAM_SEL); + } +#endif + + return(0x00); +} + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_scsi_reset + * + * Description: A SCSI bus reset will be generated and all outstanding + * Sccbs will be returned via the callback. + * + *---------------------------------------------------------------------*/ +#if (FW_TYPE==_UCB_MGR_) +void SccbMgr_scsi_reset(CARD_HANDLE pCurrCard) +#else +#if defined(DOS) +void SccbMgr_scsi_reset(USHORT pCurrCard) +#else +void SccbMgr_scsi_reset(ULONG pCurrCard) +#endif +#endif +{ + UCHAR thisCard; + + thisCard = ((PSCCBcard)pCurrCard)->cardIndex; + + mOS_Lock((PSCCBcard)pCurrCard); + + if (((PSCCBcard) pCurrCard)->globalFlags & F_GREEN_PC) + { + WR_HARPOON(((PSCCBcard) pCurrCard)->ioPort+hp_clkctrl_0, CLKCTRL_DEFAULT); + WR_HARPOON(((PSCCBcard) pCurrCard)->ioPort+hp_sys_ctrl, 0x00); + } + + sresb(((PSCCBcard)pCurrCard)->ioPort,thisCard); + + if (RD_HARPOON(((PSCCBcard)pCurrCard)->ioPort+hp_ext_status) & BM_CMD_BUSY) + { + WR_HARPOON(((PSCCBcard) pCurrCard)->ioPort+hp_page_ctrl, + (RD_HARPOON(((PSCCBcard) pCurrCard)->ioPort+hp_page_ctrl) + & ~SCATTER_EN)); + + WR_HARPOON(((PSCCBcard) pCurrCard)->ioPort+hp_sg_addr,0x00); + + ((PSCCBcard) pCurrCard)->globalFlags &= ~F_HOST_XFER_ACT; + busMstrTimeOut(((PSCCBcard) pCurrCard)->ioPort); + + WR_HARPOON(((PSCCBcard) pCurrCard)->ioPort+hp_int_mask, + (INT_CMD_COMPL | SCSI_INTERRUPT)); + } + +/* + if (utilEERead(((PSCCBcard)pCurrCard)->ioPort, (SCAM_CONFIG/2)) + & SCAM_ENABLED) +*/ + scini(thisCard, ((PSCCBcard)pCurrCard)->ourId, 0); + +#if (FW_TYPE==_UCB_MGR_) + ((PSCCBcard)pCurrCard)->cardInfo->ai_AEN_routine(0x01,pCurrCard,0,0,0,0); +#endif + + mOS_UnLock((PSCCBcard)pCurrCard); +} + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_timer_expired + * + * Description: This function allow me to kill my own job that has not + * yet completed, and has cause a timeout to occur. This + * timeout has caused the upper level driver to call this + * function. + * + *---------------------------------------------------------------------*/ + +#if (FW_TYPE==_UCB_MGR_) +void SccbMgr_timer_expired(CARD_HANDLE pCurrCard) +#else +#if defined(DOS) +void SccbMgr_timer_expired(USHORT pCurrCard) +#else +void SccbMgr_timer_expired(ULONG pCurrCard) +#endif +#endif +{ +} + +#if defined(DOS) +/*--------------------------------------------------------------------- + * + * Function: SccbMgr_status + * + * Description: This function returns the number of outstanding SCCB's. + * This is specific to the DOS enviroment, which needs this + * to help them keep protected and real mode commands staight. + * + *---------------------------------------------------------------------*/ + +USHORT SccbMgr_status(USHORT pCurrCard) +{ + return(BL_Card[pCurrCard].cmdCounter); +} +#endif + +/*--------------------------------------------------------------------- + * + * Function: SccbMgrTableInit + * + * Description: Initialize all Sccb manager data structures. + * + *---------------------------------------------------------------------*/ + +void SccbMgrTableInitAll() +{ + UCHAR thisCard; + + for (thisCard = 0; thisCard < MAX_CARDS; thisCard++) + { + SccbMgrTableInitCard(&BL_Card[thisCard],thisCard); + + BL_Card[thisCard].ioPort = 0x00; + BL_Card[thisCard].cardInfo = NULL; + BL_Card[thisCard].cardIndex = 0xFF; + BL_Card[thisCard].ourId = 0x00; + BL_Card[thisCard].pNvRamInfo = NULL; + } +} + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgrTableInit + * + * Description: Initialize all Sccb manager data structures. + * + *---------------------------------------------------------------------*/ + +void SccbMgrTableInitCard(PSCCBcard pCurrCard, UCHAR p_card) +{ + UCHAR scsiID, qtag; + + for (qtag = 0; qtag < QUEUE_DEPTH; qtag++) + { + BL_Card[p_card].discQ_Tbl[qtag] = NULL; + } + + for (scsiID = 0; scsiID < MAX_SCSI_TAR; scsiID++) + { + sccbMgrTbl[p_card][scsiID].TarStatus = 0; + sccbMgrTbl[p_card][scsiID].TarEEValue = 0; + SccbMgrTableInitTarget(p_card, scsiID); + } + + pCurrCard->scanIndex = 0x00; + pCurrCard->currentSCCB = NULL; + pCurrCard->globalFlags = 0x00; + pCurrCard->cmdCounter = 0x00; + pCurrCard->tagQ_Lst = 0x01; + pCurrCard->discQCount = 0; + + +} + + +/*--------------------------------------------------------------------- + * + * Function: SccbMgrTableInit + * + * Description: Initialize all Sccb manager data structures. + * + *---------------------------------------------------------------------*/ + +void SccbMgrTableInitTarget(UCHAR p_card, UCHAR target) +{ + + UCHAR lun, qtag; + PSCCBMgr_tar_info currTar_Info; + + currTar_Info = &sccbMgrTbl[p_card][target]; + + currTar_Info->TarSelQ_Cnt = 0; + currTar_Info->TarSyncCtrl = 0; + + currTar_Info->TarSelQ_Head = NULL; + currTar_Info->TarSelQ_Tail = NULL; + currTar_Info->TarTagQ_Cnt = 0; + currTar_Info->TarLUN_CA = FALSE; + + + for (lun = 0; lun < MAX_LUN; lun++) + { + currTar_Info->TarLUNBusy[lun] = FALSE; + currTar_Info->LunDiscQ_Idx[lun] = 0; + } + + for (qtag = 0; qtag < QUEUE_DEPTH; qtag++) + { + if(BL_Card[p_card].discQ_Tbl[qtag] != NULL) + { + if(BL_Card[p_card].discQ_Tbl[qtag]->TargID == target) + { + BL_Card[p_card].discQ_Tbl[qtag] = NULL; + BL_Card[p_card].discQCount--; + } + } + } +} + +#if defined(BUGBUG) + +/***************************************************************** + * Save the current byte in the debug array + *****************************************************************/ + + +void Debug_Load(UCHAR p_card, UCHAR p_bug_data) +{ + debug_int[p_card][debug_index[p_card]] = p_bug_data; + debug_index[p_card]++; + + if (debug_index[p_card] == debug_size) + + debug_index[p_card] = 0; +} + +#endif +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: sccb_dat.c $ + * + * Description: Functions relating to handling of the SCCB interface + * between the device driver and the HARPOON. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <harpoon.h>*/ + +/* +** IMPORTANT NOTE!!! +** +** You MUST preassign all data to a valid value or zero. This is +** required due to the MS compiler bug under OS/2 and Solaris Real-Mode +** driver environment. +*/ + + +SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR] = { { { 0 } } }; +SCCBCARD BL_Card[MAX_CARDS] = { { 0 } }; +SCCBSCAM_INFO scamInfo[MAX_SCSI_TAR] = { { { 0 } } }; +NVRAMINFO nvRamInfo[MAX_MB_CARDS] = { { 0 } }; + + +#if defined(OS2) +void (far *s_PhaseTbl[8]) (ULONG, UCHAR) = { 0 }; +UCHAR temp_id_string[ID_STRING_LENGTH] = { 0 }; +#elif defined(SOLARIS_REAL_MODE) || defined(__STDC__) +void (*s_PhaseTbl[8]) (ULONG, UCHAR) = { 0 }; +#else +void (*s_PhaseTbl[8]) (); +#endif + +#if defined(DOS) +UCHAR first_time = 0; +#endif + +UCHAR mbCards = 0; +UCHAR scamHAString[] = {0x63, 0x07, 'B', 'U', 'S', 'L', 'O', 'G', 'I', 'C', \ + ' ', 'B', 'T', '-', '9', '3', '0', \ + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, \ + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20}; + +USHORT default_intena = 0; + +#if defined(BUGBUG) +UCHAR debug_int[MAX_CARDS][debug_size] = { 0 }; +UCHAR debug_index[MAX_CARDS] = { 0 }; +UCHAR reserved_1[3] = { 0 }; +#endif +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: scsi.c $ + * + * Description: Functions for handling SCSI bus functions such as + * selection/reselection, sync negotiation, message-in + * decoding. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <scsi2.h>*/ +/*#include <eeprom.h>*/ +/*#include <harpoon.h>*/ + + +/* +extern SCCBCARD BL_Card[MAX_CARDS]; +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; +#if defined(BUGBUG) +void Debug_Load(UCHAR p_card, UCHAR p_bug_data); +#endif +*/ + +/*--------------------------------------------------------------------- + * + * Function: sfetm + * + * Description: Read in a message byte from the SCSI bus, and check + * for a parity error. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +UCHAR sfm(USHORT port, PSCCB pCurrSCCB) +#else +UCHAR sfm(ULONG port, PSCCB pCurrSCCB) +#endif +{ + UCHAR message; + USHORT TimeOutLoop; + + TimeOutLoop = 0; + while( (!(RD_HARPOON(port+hp_scsisig) & SCSI_REQ)) && + (TimeOutLoop++ < 20000) ){} + + + WR_HARPOON(port+hp_portctrl_0, SCSI_PORT); + + message = RD_HARPOON(port+hp_scsidata_0); + + WR_HARPOON(port+hp_scsisig, SCSI_ACK + S_MSGI_PH); + + + if (TimeOutLoop > 20000) + message = 0x00; /* force message byte = 0 if Time Out on Req */ + + if ((RDW_HARPOON((port+hp_intstat)) & PARITY) && + (RD_HARPOON(port+hp_addstat) & SCSI_PAR_ERR)) + { + WR_HARPOON(port+hp_scsisig, (SCSI_ACK + S_ILL_PH)); + WR_HARPOON(port+hp_xferstat, 0); + WR_HARPOON(port+hp_fiforead, 0); + WR_HARPOON(port+hp_fifowrite, 0); + if (pCurrSCCB != NULL) + { + pCurrSCCB->Sccb_scsimsg = SMPARITY; + } + message = 0x00; + do + { + ACCEPT_MSG_ATN(port); + TimeOutLoop = 0; + while( (!(RD_HARPOON(port+hp_scsisig) & SCSI_REQ)) && + (TimeOutLoop++ < 20000) ){} + if (TimeOutLoop > 20000) + { + WRW_HARPOON((port+hp_intstat), PARITY); + return(message); + } + if ((RD_HARPOON(port+hp_scsisig) & S_SCSI_PHZ) != S_MSGI_PH) + { + WRW_HARPOON((port+hp_intstat), PARITY); + return(message); + } + WR_HARPOON(port+hp_portctrl_0, SCSI_PORT); + + RD_HARPOON(port+hp_scsidata_0); + + WR_HARPOON(port+hp_scsisig, (SCSI_ACK + S_ILL_PH)); + + }while(1); + + } + WR_HARPOON(port+hp_scsisig, (SCSI_ACK + S_ILL_PH)); + WR_HARPOON(port+hp_xferstat, 0); + WR_HARPOON(port+hp_fiforead, 0); + WR_HARPOON(port+hp_fifowrite, 0); + return(message); +} + + +/*--------------------------------------------------------------------- + * + * Function: ssel + * + * Description: Load up automation and select target device. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void ssel(USHORT port, UCHAR p_card) +#else +void ssel(ULONG port, UCHAR p_card) +#endif +{ + +#if defined(DOS) + UCHAR auto_loaded, i, target, *theCCB; +#elif defined(OS2) + UCHAR auto_loaded, i, target; + UCHAR far *theCCB; +#else + UCHAR auto_loaded, i, target, *theCCB; +#endif + +#if defined(DOS) + USHORT cdb_reg; +#else + ULONG cdb_reg; +#endif + PSCCBcard CurrCard; + PSCCB currSCCB; + PSCCBMgr_tar_info currTar_Info; + UCHAR lastTag, lun; + + CurrCard = &BL_Card[p_card]; + currSCCB = CurrCard->currentSCCB; + target = currSCCB->TargID; + currTar_Info = &sccbMgrTbl[p_card][target]; + lastTag = CurrCard->tagQ_Lst; + + ARAM_ACCESS(port); + + + if ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) == TAG_Q_REJECT) + currSCCB->ControlByte &= ~F_USE_CMD_Q; + + if(((CurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + + lun = currSCCB->Lun; + else + lun = 0; + + +#if defined(DOS) + currTar_Info->TarLUNBusy[lun] = TRUE; + +#else + + if (CurrCard->globalFlags & F_TAG_STARTED) + { + if (!(currSCCB->ControlByte & F_USE_CMD_Q)) + { + if ((currTar_Info->TarLUN_CA == FALSE) + && ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) + == TAG_Q_TRYING)) + { + + if (currTar_Info->TarTagQ_Cnt !=0) + { + currTar_Info->TarLUNBusy[lun] = TRUE; + queueSelectFail(CurrCard,p_card); + SGRAM_ACCESS(port); + return; + } + + else { + currTar_Info->TarLUNBusy[lun] = TRUE; + } + + } /*End non-tagged */ + + else { + currTar_Info->TarLUNBusy[lun] = TRUE; + } + + } /*!Use cmd Q Tagged */ + + else { + if (currTar_Info->TarLUN_CA == TRUE) + { + queueSelectFail(CurrCard,p_card); + SGRAM_ACCESS(port); + return; + } + + currTar_Info->TarLUNBusy[lun] = TRUE; + + } /*else use cmd Q tagged */ + + } /*if glob tagged started */ + + else { + currTar_Info->TarLUNBusy[lun] = TRUE; + } + +#endif /* DOS */ + + + + if((((CurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + || (!(currSCCB->ControlByte & F_USE_CMD_Q)))) + { + if(CurrCard->discQCount >= QUEUE_DEPTH) + { + currTar_Info->TarLUNBusy[lun] = TRUE; + queueSelectFail(CurrCard,p_card); + SGRAM_ACCESS(port); + return; + } + for (i = 1; i < QUEUE_DEPTH; i++) + { + if (++lastTag >= QUEUE_DEPTH) lastTag = 1; + if (CurrCard->discQ_Tbl[lastTag] == NULL) + { + CurrCard->tagQ_Lst = lastTag; + currTar_Info->LunDiscQ_Idx[lun] = lastTag; + CurrCard->discQ_Tbl[lastTag] = currSCCB; + CurrCard->discQCount++; + break; + } + } + if(i == QUEUE_DEPTH) + { + currTar_Info->TarLUNBusy[lun] = TRUE; + queueSelectFail(CurrCard,p_card); + SGRAM_ACCESS(port); + return; + } + } + + + + auto_loaded = FALSE; + + WR_HARPOON(port+hp_select_id, target); + WR_HARPOON(port+hp_gp_reg_3, target); /* Use by new automation logic */ + + if (currSCCB->OperationCode == RESET_COMMAND) { + WRW_HARPOON((port+ID_MSG_STRT), (MPM_OP+AMSG_OUT+ + (currSCCB->Sccb_idmsg & ~DISC_PRIV))); + + WRW_HARPOON((port+ID_MSG_STRT+2),BRH_OP+ALWAYS+NP); + + currSCCB->Sccb_scsimsg = SMDEV_RESET; + + WR_HARPOON(port+hp_autostart_3, (SELECT+SELCHK_STRT)); + auto_loaded = TRUE; + currSCCB->Sccb_scsistat = SELECT_BDR_ST; + + if (currTar_Info->TarEEValue & EE_SYNC_MASK) + { + currTar_Info->TarSyncCtrl = 0; + currTar_Info->TarStatus &= ~TAR_SYNC_MASK; + } + +#if defined(WIDE_SCSI) + + if (currTar_Info->TarEEValue & EE_WIDE_SCSI) + { + currTar_Info->TarStatus &= ~TAR_WIDE_MASK; + } +#endif + + sssyncv(port, target, NARROW_SCSI,currTar_Info); + SccbMgrTableInitTarget(p_card, target); + + } + + else if(currSCCB->Sccb_scsistat == ABORT_ST) + { + WRW_HARPOON((port+ID_MSG_STRT), (MPM_OP+AMSG_OUT+ + (currSCCB->Sccb_idmsg & ~DISC_PRIV))); + + WRW_HARPOON((port+ID_MSG_STRT+2),BRH_OP+ALWAYS+CMDPZ); + + WRW_HARPOON((port+SYNC_MSGS+0), (MPM_OP+AMSG_OUT+ + (((UCHAR)(currSCCB->ControlByte & TAG_TYPE_MASK) + >> 6) | (UCHAR)0x20))); + WRW_HARPOON((port+SYNC_MSGS+2), + (MPM_OP+AMSG_OUT+currSCCB->Sccb_tag)); + WRW_HARPOON((port+SYNC_MSGS+4), (BRH_OP+ALWAYS+NP )); + + WR_HARPOON(port+hp_autostart_3, (SELECT+SELCHK_STRT)); + auto_loaded = TRUE; + + } + +#if defined(WIDE_SCSI) + + + else if (!(currTar_Info->TarStatus & WIDE_NEGOCIATED)) { + auto_loaded = siwidn(port,p_card); + currSCCB->Sccb_scsistat = SELECT_WN_ST; + } + +#endif + + + else if (!((currTar_Info->TarStatus & TAR_SYNC_MASK) + == SYNC_SUPPORTED)) { + auto_loaded = sisyncn(port,p_card, FALSE); + currSCCB->Sccb_scsistat = SELECT_SN_ST; + } + + + if (!auto_loaded) + { + +#if !defined(DOS) + if (currSCCB->ControlByte & F_USE_CMD_Q) + { + + CurrCard->globalFlags |= F_TAG_STARTED; + + if ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) + == TAG_Q_REJECT) + { + currSCCB->ControlByte &= ~F_USE_CMD_Q; + + /* Fix up the start instruction with a jump to + Non-Tag-CMD handling */ + WRW_HARPOON((port+ID_MSG_STRT),BRH_OP+ALWAYS+NTCMD); + + WRW_HARPOON((port+NON_TAG_ID_MSG), + (MPM_OP+AMSG_OUT+currSCCB->Sccb_idmsg)); + + WR_HARPOON(port+hp_autostart_3, (SELECT+SELCHK_STRT)); + + /* Setup our STATE so we know what happend when + the wheels fall off. */ + currSCCB->Sccb_scsistat = SELECT_ST; + + currTar_Info->TarLUNBusy[lun] = TRUE; + } + + else + { + WRW_HARPOON((port+ID_MSG_STRT), (MPM_OP+AMSG_OUT+currSCCB->Sccb_idmsg)); + + WRW_HARPOON((port+ID_MSG_STRT+2), (MPM_OP+AMSG_OUT+ + (((UCHAR)(currSCCB->ControlByte & TAG_TYPE_MASK) + >> 6) | (UCHAR)0x20))); + + for (i = 1; i < QUEUE_DEPTH; i++) + { + if (++lastTag >= QUEUE_DEPTH) lastTag = 1; + if (CurrCard->discQ_Tbl[lastTag] == NULL) + { + WRW_HARPOON((port+ID_MSG_STRT+6), + (MPM_OP+AMSG_OUT+lastTag)); + CurrCard->tagQ_Lst = lastTag; + currSCCB->Sccb_tag = lastTag; + CurrCard->discQ_Tbl[lastTag] = currSCCB; + CurrCard->discQCount++; + break; + } + } + + + if ( i == QUEUE_DEPTH ) + { + currTar_Info->TarLUNBusy[lun] = TRUE; + queueSelectFail(CurrCard,p_card); + SGRAM_ACCESS(port); + return; + } + + currSCCB->Sccb_scsistat = SELECT_Q_ST; + + WR_HARPOON(port+hp_autostart_3, (SELECT+SELCHK_STRT)); + } + } + + else + { +#endif /* !DOS */ + + WRW_HARPOON((port+ID_MSG_STRT),BRH_OP+ALWAYS+NTCMD); + + WRW_HARPOON((port+NON_TAG_ID_MSG), + (MPM_OP+AMSG_OUT+currSCCB->Sccb_idmsg)); + + currSCCB->Sccb_scsistat = SELECT_ST; + + WR_HARPOON(port+hp_autostart_3, (SELECT+SELCHK_STRT)); +#if !defined(DOS) + } +#endif + + +#if defined(OS2) + theCCB = (UCHAR far *)&currSCCB->Cdb[0]; +#else + theCCB = (UCHAR *)&currSCCB->Cdb[0]; +#endif + + cdb_reg = port + CMD_STRT; + + for (i=0; i < currSCCB->CdbLength; i++) + { + WRW_HARPOON(cdb_reg, (MPM_OP + ACOMMAND + *theCCB)); + cdb_reg +=2; + theCCB++; + } + + if (currSCCB->CdbLength != TWELVE_BYTE_CMD) + WRW_HARPOON(cdb_reg, (BRH_OP+ALWAYS+ NP)); + + } /* auto_loaded */ + +#if defined(WIDE_SCSI) + WRW_HARPOON((port+hp_fiforead), (USHORT) 0x00); + WR_HARPOON(port+hp_xferstat, 0x00); +#endif + + WRW_HARPOON((port+hp_intstat), (PROG_HLT | TIMEOUT | SEL | BUS_FREE)); + + WR_HARPOON(port+hp_portctrl_0,(SCSI_PORT)); + + + if (!(currSCCB->Sccb_MGRFlags & F_DEV_SELECTED)) + { + WR_HARPOON(port+hp_scsictrl_0, (SEL_TAR | ENA_ATN | ENA_RESEL | ENA_SCAM_SEL)); + } + else + { + +/* auto_loaded = (RD_HARPOON(port+hp_autostart_3) & (UCHAR)0x1F); + auto_loaded |= AUTO_IMMED; */ + auto_loaded = AUTO_IMMED; + + DISABLE_AUTO(port); + + WR_HARPOON(port+hp_autostart_3, auto_loaded); + } + + SGRAM_ACCESS(port); +} + + +/*--------------------------------------------------------------------- + * + * Function: sres + * + * Description: Hookup the correct CCB and handle the incoming messages. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void sres(USHORT port, UCHAR p_card, PSCCBcard pCurrCard) +#else +void sres(ULONG port, UCHAR p_card, PSCCBcard pCurrCard) +#endif +{ + +#if defined(V302) +#ifdef DOS + UCHAR our_target,message, msgRetryCount; + extern UCHAR lun, tag; +#else + UCHAR our_target,message,lun,tag, msgRetryCount; +#endif + +#else /* V302 */ + UCHAR our_target, message, lun = 0, tag, msgRetryCount; +#endif /* V302 */ + + + PSCCBMgr_tar_info currTar_Info; + PSCCB currSCCB; + + + + + if(pCurrCard->currentSCCB != NULL) + { + currTar_Info = &sccbMgrTbl[p_card][pCurrCard->currentSCCB->TargID]; + DISABLE_AUTO(port); + + + WR_HARPOON((port+hp_scsictrl_0),(ENA_RESEL | ENA_SCAM_SEL)); + + + currSCCB = pCurrCard->currentSCCB; + if(currSCCB->Sccb_scsistat == SELECT_WN_ST) + { + currTar_Info->TarStatus &= ~TAR_WIDE_MASK; + currSCCB->Sccb_scsistat = BUS_FREE_ST; + } + if(currSCCB->Sccb_scsistat == SELECT_SN_ST) + { + currTar_Info->TarStatus &= ~TAR_SYNC_MASK; + currSCCB->Sccb_scsistat = BUS_FREE_ST; + } + if(((pCurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + currTar_Info->TarLUNBusy[currSCCB->Lun] = FALSE; + if(currSCCB->Sccb_scsistat != ABORT_ST) + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[currSCCB->Lun]] + = NULL; + } + } + else + { + currTar_Info->TarLUNBusy[0] = FALSE; + if(currSCCB->Sccb_tag) + { + if(currSCCB->Sccb_scsistat != ABORT_ST) + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[currSCCB->Sccb_tag] = NULL; + } + }else + { + if(currSCCB->Sccb_scsistat != ABORT_ST) + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[0]] = NULL; + } + } + } + + queueSelectFail(&BL_Card[p_card],p_card); + } + +#if defined(WIDE_SCSI) + WRW_HARPOON((port+hp_fiforead), (USHORT) 0x00); +#endif + + + our_target = (UCHAR)(RD_HARPOON(port+hp_select_id) >> 4); + currTar_Info = &sccbMgrTbl[p_card][our_target]; + + + msgRetryCount = 0; + do + { + +#if defined(V302) + + message = GetTarLun(port, p_card, our_target, pCurrCard, &tag, &lun); + +#else /* V302 */ + + currTar_Info = &sccbMgrTbl[p_card][our_target]; + tag = 0; + + + while(!(RD_HARPOON(port+hp_scsisig) & SCSI_REQ)) + { + if (! (RD_HARPOON(port+hp_scsisig) & SCSI_BSY)) + { + + WRW_HARPOON((port+hp_intstat), PHASE); + return; + } + } + + WRW_HARPOON((port+hp_intstat), PHASE); + if ((RD_HARPOON(port+hp_scsisig) & S_SCSI_PHZ) == S_MSGI_PH) + { + + message = sfm(port,pCurrCard->currentSCCB); + if (message) + { + + if (message <= (0x80 | LUN_MASK)) + { + lun = message & (UCHAR)LUN_MASK; + +#if !defined(DOS) + if ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) == TAG_Q_TRYING) + { + if (currTar_Info->TarTagQ_Cnt != 0) + { + + if (!(currTar_Info->TarLUN_CA)) + { + ACCEPT_MSG(port); /*Release the ACK for ID msg. */ + + + message = sfm(port,pCurrCard->currentSCCB); + if (message) + { + ACCEPT_MSG(port); + } + + else + message = FALSE; + + if(message != FALSE) + { + tag = sfm(port,pCurrCard->currentSCCB); + + if (!(tag)) + message = FALSE; + } + + } /*C.A. exists! */ + + } /*End Q cnt != 0 */ + + } /*End Tag cmds supported! */ +#endif /* !DOS */ + + } /*End valid ID message. */ + + else + { + + ACCEPT_MSG_ATN(port); + } + + } /* End good id message. */ + + else + { + + message = FALSE; + } + } + else + { + ACCEPT_MSG_ATN(port); + + while (!(RDW_HARPOON((port+hp_intstat)) & (PHASE | RESET)) && + !(RD_HARPOON(port+hp_scsisig) & SCSI_REQ) && + (RD_HARPOON(port+hp_scsisig) & SCSI_BSY)) ; + + return; + } + +#endif /* V302 */ + + if(message == FALSE) + { + msgRetryCount++; + if(msgRetryCount == 1) + { + SendMsg(port, SMPARITY); + } + else + { + SendMsg(port, SMDEV_RESET); + + sssyncv(port, our_target, NARROW_SCSI,currTar_Info); + + if (sccbMgrTbl[p_card][our_target].TarEEValue & EE_SYNC_MASK) + { + + sccbMgrTbl[p_card][our_target].TarStatus &= ~TAR_SYNC_MASK; + + } + + if (sccbMgrTbl[p_card][our_target].TarEEValue & EE_WIDE_SCSI) + { + + sccbMgrTbl[p_card][our_target].TarStatus &= ~TAR_WIDE_MASK; + } + + + queueFlushTargSccb(p_card, our_target, SCCB_COMPLETE); + SccbMgrTableInitTarget(p_card,our_target); + return; + } + } + }while(message == FALSE); + + + + if(((pCurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + currTar_Info->TarLUNBusy[lun] = TRUE; + pCurrCard->currentSCCB = pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[lun]]; + if(pCurrCard->currentSCCB != NULL) + { + ACCEPT_MSG(port); + } + else + { + ACCEPT_MSG_ATN(port); + } + } + else + { + currTar_Info->TarLUNBusy[0] = TRUE; + + + if (tag) + { + if (pCurrCard->discQ_Tbl[tag] != NULL) + { + pCurrCard->currentSCCB = pCurrCard->discQ_Tbl[tag]; + currTar_Info->TarTagQ_Cnt--; + ACCEPT_MSG(port); + } + else + { + ACCEPT_MSG_ATN(port); + } + }else + { + pCurrCard->currentSCCB = pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[0]]; + if(pCurrCard->currentSCCB != NULL) + { + ACCEPT_MSG(port); + } + else + { + ACCEPT_MSG_ATN(port); + } + } + } + + if(pCurrCard->currentSCCB != NULL) + { + if(pCurrCard->currentSCCB->Sccb_scsistat == ABORT_ST) + { + /* During Abort Tag command, the target could have got re-selected + and completed the command. Check the select Q and remove the CCB + if it is in the Select Q */ + queueFindSccb(pCurrCard->currentSCCB, p_card); + } + } + + + while (!(RDW_HARPOON((port+hp_intstat)) & (PHASE | RESET)) && + !(RD_HARPOON(port+hp_scsisig) & SCSI_REQ) && + (RD_HARPOON(port+hp_scsisig) & SCSI_BSY)) ; +} + +#if defined(V302) + +#if defined(DOS) +UCHAR GetTarLun(USHORT port, UCHAR p_card, UCHAR our_target, PSCCBcard pCurrCard, PUCHAR tag, PUCHAR lun) +#else +UCHAR GetTarLun(ULONG port, UCHAR p_card, UCHAR our_target, PSCCBcard pCurrCard, PUCHAR tag, PUCHAR lun) +#endif +{ + UCHAR message; + PSCCBMgr_tar_info currTar_Info; + + + currTar_Info = &sccbMgrTbl[p_card][our_target]; + *tag = 0; + + + while(!(RD_HARPOON(port+hp_scsisig) & SCSI_REQ)) + { + if (! (RD_HARPOON(port+hp_scsisig) & SCSI_BSY)) + { + + WRW_HARPOON((port+hp_intstat), PHASE); + return(TRUE); + } + } + + WRW_HARPOON((port+hp_intstat), PHASE); + if ((RD_HARPOON(port+hp_scsisig) & S_SCSI_PHZ) == S_MSGI_PH) + { + + message = sfm(port,pCurrCard->currentSCCB); + if (message) + { + + if (message <= (0x80 | LUN_MASK)) + { + *lun = message & (UCHAR)LUN_MASK; + +#if !defined(DOS) + if ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) == TAG_Q_TRYING) + { + if (currTar_Info->TarTagQ_Cnt != 0) + { + + if (!(currTar_Info->TarLUN_CA)) + { + ACCEPT_MSG(port); /*Release the ACK for ID msg. */ + + + message = sfm(port,pCurrCard->currentSCCB); + if (message) + { + ACCEPT_MSG(port); + } + + else + return(FALSE); + + *tag = sfm(port,pCurrCard->currentSCCB); + + if (!(*tag)) return(FALSE); + + } /*C.A. exists! */ + + } /*End Q cnt != 0 */ + + } /*End Tag cmds supported! */ +#endif /* !DOS */ + + } /*End valid ID message. */ + + else + { + + ACCEPT_MSG_ATN(port); + } + + } /* End good id message. */ + + else + { + + return(FALSE); + } + } + else + { + ACCEPT_MSG_ATN(port); + return(TRUE); + } + return(TRUE); +} + +#endif /* V302 */ + +#if defined(DOS) +void SendMsg(USHORT port, UCHAR message) +#else +void SendMsg(ULONG port, UCHAR message) +#endif +{ + while(!(RD_HARPOON(port+hp_scsisig) & SCSI_REQ)) + { + if (! (RD_HARPOON(port+hp_scsisig) & SCSI_BSY)) + { + + WRW_HARPOON((port+hp_intstat), PHASE); + return; + } + } + + WRW_HARPOON((port+hp_intstat), PHASE); + if ((RD_HARPOON(port+hp_scsisig) & S_SCSI_PHZ) == S_MSGO_PH) + { + WRW_HARPOON((port+hp_intstat), (BUS_FREE | PHASE | XFER_CNT_0)); + + + WR_HARPOON(port+hp_portctrl_0, SCSI_BUS_EN); + + WR_HARPOON(port+hp_scsidata_0,message); + + WR_HARPOON(port+hp_scsisig, (SCSI_ACK + S_ILL_PH)); + + ACCEPT_MSG(port); + + WR_HARPOON(port+hp_portctrl_0, 0x00); + + if ((message == SMABORT) || (message == SMDEV_RESET) || + (message == SMABORT_TAG) ) + { + while(!(RDW_HARPOON((port+hp_intstat)) & (BUS_FREE | PHASE))) {} + + if (RDW_HARPOON((port+hp_intstat)) & BUS_FREE) + { + WRW_HARPOON((port+hp_intstat), BUS_FREE); + } + } + } +} + +/*--------------------------------------------------------------------- + * + * Function: sdecm + * + * Description: Determine the proper responce to the message from the + * target device. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void sdecm(UCHAR message, USHORT port, UCHAR p_card) +#else +void sdecm(UCHAR message, ULONG port, UCHAR p_card) +#endif +{ + PSCCB currSCCB; + PSCCBcard CurrCard; + PSCCBMgr_tar_info currTar_Info; + + CurrCard = &BL_Card[p_card]; + currSCCB = CurrCard->currentSCCB; + + currTar_Info = &sccbMgrTbl[p_card][currSCCB->TargID]; + + if (message == SMREST_DATA_PTR) + { + if (!(currSCCB->Sccb_XferState & F_NO_DATA_YET)) + { + currSCCB->Sccb_ATC = currSCCB->Sccb_savedATC; + + hostDataXferRestart(currSCCB); + } + + ACCEPT_MSG(port); + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + + else if (message == SMCMD_COMP) + { + + + if (currSCCB->Sccb_scsistat == SELECT_Q_ST) + { + currTar_Info->TarStatus &= ~(UCHAR)TAR_TAG_Q_MASK; + currTar_Info->TarStatus |= (UCHAR)TAG_Q_REJECT; + } + + ACCEPT_MSG(port); + + } + + else if ((message == SMNO_OP) || (message >= SMIDENT) + || (message == SMINIT_RECOVERY) || (message == SMREL_RECOVERY)) + { + + ACCEPT_MSG(port); + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + + else if (message == SMREJECT) + { + + if ((currSCCB->Sccb_scsistat == SELECT_SN_ST) || + (currSCCB->Sccb_scsistat == SELECT_WN_ST) || + ((currTar_Info->TarStatus & TAR_SYNC_MASK) == SYNC_TRYING ) || + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) == TAG_Q_TRYING ) ) + + { + WRW_HARPOON((port+hp_intstat), BUS_FREE); + + ACCEPT_MSG(port); + + + while ((!(RD_HARPOON(port+hp_scsisig) & SCSI_REQ)) && + (!(RDW_HARPOON((port+hp_intstat)) & BUS_FREE))) {} + + if(currSCCB->Lun == 0x00) + { + if ((currSCCB->Sccb_scsistat == SELECT_SN_ST)) + { + + currTar_Info->TarStatus |= (UCHAR)SYNC_SUPPORTED; + + currTar_Info->TarEEValue &= ~EE_SYNC_MASK; + } + +#if defined(WIDE_SCSI) + else if ((currSCCB->Sccb_scsistat == SELECT_WN_ST)) + { + + + currTar_Info->TarStatus = (currTar_Info->TarStatus & + ~WIDE_ENABLED) | WIDE_NEGOCIATED; + + currTar_Info->TarEEValue &= ~EE_WIDE_SCSI; + + } +#endif + + else if ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) == TAG_Q_TRYING ) + { + currTar_Info->TarStatus = (currTar_Info->TarStatus & + ~(UCHAR)TAR_TAG_Q_MASK) | TAG_Q_REJECT; + + + currSCCB->ControlByte &= ~F_USE_CMD_Q; + CurrCard->discQCount--; + CurrCard->discQ_Tbl[currSCCB->Sccb_tag] = NULL; + currSCCB->Sccb_tag = 0x00; + + } + } + + if (RDW_HARPOON((port+hp_intstat)) & BUS_FREE) + { + + + if(currSCCB->Lun == 0x00) + { + WRW_HARPOON((port+hp_intstat), BUS_FREE); + CurrCard->globalFlags |= F_NEW_SCCB_CMD; + } + } + + else + { + + if((CurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + currTar_Info->TarLUNBusy[currSCCB->Lun] = TRUE; + else + currTar_Info->TarLUNBusy[0] = TRUE; + + + currSCCB->ControlByte &= ~(UCHAR)F_USE_CMD_Q; + + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + + } + } + + else + { + ACCEPT_MSG(port); + + while ((!(RD_HARPOON(port+hp_scsisig) & SCSI_REQ)) && + (!(RDW_HARPOON((port+hp_intstat)) & BUS_FREE))) {} + + if (!(RDW_HARPOON((port+hp_intstat)) & BUS_FREE)) + { + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + } + } + + else if (message == SMEXT) + { + + ACCEPT_MSG(port); + shandem(port,p_card,currSCCB); + } + + else if (message == SMIGNORWR) + { + + ACCEPT_MSG(port); /* ACK the RESIDUE MSG */ + + message = sfm(port,currSCCB); + + if(currSCCB->Sccb_scsimsg != SMPARITY) + ACCEPT_MSG(port); + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + + + else + { + + currSCCB->HostStatus = SCCB_PHASE_SEQUENCE_FAIL; + currSCCB->Sccb_scsimsg = SMREJECT; + + ACCEPT_MSG_ATN(port); + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } +} + + +/*--------------------------------------------------------------------- + * + * Function: shandem + * + * Description: Decide what to do with the extended message. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void shandem(USHORT port, UCHAR p_card, PSCCB pCurrSCCB) +#else +void shandem(ULONG port, UCHAR p_card, PSCCB pCurrSCCB) +#endif +{ + UCHAR length,message; + + length = sfm(port,pCurrSCCB); + if (length) + { + + ACCEPT_MSG(port); + message = sfm(port,pCurrSCCB); + if (message) + { + + if (message == SMSYNC) + { + + if (length == 0x03) + { + + ACCEPT_MSG(port); + stsyncn(port,p_card); + } + else + { + + pCurrSCCB->Sccb_scsimsg = SMREJECT; + ACCEPT_MSG_ATN(port); + } + } +#if defined(WIDE_SCSI) + else if (message == SMWDTR) + { + + if (length == 0x02) + { + + ACCEPT_MSG(port); + stwidn(port,p_card); + } + else + { + + pCurrSCCB->Sccb_scsimsg = SMREJECT; + ACCEPT_MSG_ATN(port); + + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + } +#endif + else + { + + pCurrSCCB->Sccb_scsimsg = SMREJECT; + ACCEPT_MSG_ATN(port); + + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + } + else + { + if(pCurrSCCB->Sccb_scsimsg != SMPARITY) + ACCEPT_MSG(port); + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + }else + { + if(pCurrSCCB->Sccb_scsimsg == SMPARITY) + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } +} + + +/*--------------------------------------------------------------------- + * + * Function: sisyncn + * + * Description: Read in a message byte from the SCSI bus, and check + * for a parity error. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +UCHAR sisyncn(USHORT port, UCHAR p_card, UCHAR syncFlag) +#else +UCHAR sisyncn(ULONG port, UCHAR p_card, UCHAR syncFlag) +#endif +{ + PSCCB currSCCB; + PSCCBMgr_tar_info currTar_Info; + + currSCCB = BL_Card[p_card].currentSCCB; + currTar_Info = &sccbMgrTbl[p_card][currSCCB->TargID]; + + if (!((currTar_Info->TarStatus & TAR_SYNC_MASK) == SYNC_TRYING)) { + + + WRW_HARPOON((port+ID_MSG_STRT), + (MPM_OP+AMSG_OUT+(currSCCB->Sccb_idmsg & ~(UCHAR)DISC_PRIV))); + + WRW_HARPOON((port+ID_MSG_STRT+2),BRH_OP+ALWAYS+CMDPZ); + + WRW_HARPOON((port+SYNC_MSGS+0), (MPM_OP+AMSG_OUT+SMEXT )); + WRW_HARPOON((port+SYNC_MSGS+2), (MPM_OP+AMSG_OUT+0x03 )); + WRW_HARPOON((port+SYNC_MSGS+4), (MPM_OP+AMSG_OUT+SMSYNC)); + + + if ((currTar_Info->TarEEValue & EE_SYNC_MASK) == EE_SYNC_20MB) + + WRW_HARPOON((port+SYNC_MSGS+6), (MPM_OP+AMSG_OUT+ 12)); + + else if ((currTar_Info->TarEEValue & EE_SYNC_MASK) == EE_SYNC_10MB) + + WRW_HARPOON((port+SYNC_MSGS+6), (MPM_OP+AMSG_OUT+ 25)); + + else if ((currTar_Info->TarEEValue & EE_SYNC_MASK) == EE_SYNC_5MB) + + WRW_HARPOON((port+SYNC_MSGS+6), (MPM_OP+AMSG_OUT+ 50)); + + else + WRW_HARPOON((port+SYNC_MSGS+6), (MPM_OP+AMSG_OUT+ 00)); + + + WRW_HARPOON((port+SYNC_MSGS+8), (RAT_OP )); + WRW_HARPOON((port+SYNC_MSGS+10),(MPM_OP+AMSG_OUT+DEFAULT_OFFSET)); + WRW_HARPOON((port+SYNC_MSGS+12),(BRH_OP+ALWAYS+NP )); + + + if(syncFlag == FALSE) + { + WR_HARPOON(port+hp_autostart_3, (SELECT+SELCHK_STRT)); + currTar_Info->TarStatus = ((currTar_Info->TarStatus & + ~(UCHAR)TAR_SYNC_MASK) | (UCHAR)SYNC_TRYING); + } + else + { + WR_HARPOON(port+hp_autostart_3, (AUTO_IMMED + CMD_ONLY_STRT)); + } + + + return(TRUE); + } + + else { + + currTar_Info->TarStatus |= (UCHAR)SYNC_SUPPORTED; + currTar_Info->TarEEValue &= ~EE_SYNC_MASK; + return(FALSE); + } +} + + + +/*--------------------------------------------------------------------- + * + * Function: stsyncn + * + * Description: The has sent us a Sync Nego message so handle it as + * necessary. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void stsyncn(USHORT port, UCHAR p_card) +#else +void stsyncn(ULONG port, UCHAR p_card) +#endif +{ + UCHAR sync_msg,offset,sync_reg,our_sync_msg; + PSCCB currSCCB; + PSCCBMgr_tar_info currTar_Info; + + currSCCB = BL_Card[p_card].currentSCCB; + currTar_Info = &sccbMgrTbl[p_card][currSCCB->TargID]; + + sync_msg = sfm(port,currSCCB); + + if((sync_msg == 0x00) && (currSCCB->Sccb_scsimsg == SMPARITY)) + { + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + return; + } + + ACCEPT_MSG(port); + + + offset = sfm(port,currSCCB); + + if((offset == 0x00) && (currSCCB->Sccb_scsimsg == SMPARITY)) + { + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + return; + } + + if ((currTar_Info->TarEEValue & EE_SYNC_MASK) == EE_SYNC_20MB) + + our_sync_msg = 12; /* Setup our Message to 20mb/s */ + + else if ((currTar_Info->TarEEValue & EE_SYNC_MASK) == EE_SYNC_10MB) + + our_sync_msg = 25; /* Setup our Message to 10mb/s */ + + else if ((currTar_Info->TarEEValue & EE_SYNC_MASK) == EE_SYNC_5MB) + + our_sync_msg = 50; /* Setup our Message to 5mb/s */ + else + + our_sync_msg = 0; /* Message = Async */ + + if (sync_msg < our_sync_msg) { + sync_msg = our_sync_msg; /*if faster, then set to max. */ + } + + if (offset == ASYNC) + sync_msg = ASYNC; + + if (offset > MAX_OFFSET) + offset = MAX_OFFSET; + + sync_reg = 0x00; + + if (sync_msg > 12) + + sync_reg = 0x20; /* Use 10MB/s */ + + if (sync_msg > 25) + + sync_reg = 0x40; /* Use 6.6MB/s */ + + if (sync_msg > 38) + + sync_reg = 0x60; /* Use 5MB/s */ + + if (sync_msg > 50) + + sync_reg = 0x80; /* Use 4MB/s */ + + if (sync_msg > 62) + + sync_reg = 0xA0; /* Use 3.33MB/s */ + + if (sync_msg > 75) + + sync_reg = 0xC0; /* Use 2.85MB/s */ + + if (sync_msg > 87) + + sync_reg = 0xE0; /* Use 2.5MB/s */ + + if (sync_msg > 100) { + + sync_reg = 0x00; /* Use ASYNC */ + offset = 0x00; + } + + +#if defined(WIDE_SCSI) + if (currTar_Info->TarStatus & WIDE_ENABLED) + + sync_reg |= offset; + + else + + sync_reg |= (offset | NARROW_SCSI); + +#else + sync_reg |= (offset | NARROW_SCSI); +#endif + + sssyncv(port,currSCCB->TargID,sync_reg,currTar_Info); + + + if (currSCCB->Sccb_scsistat == SELECT_SN_ST) { + + + ACCEPT_MSG(port); + + currTar_Info->TarStatus = ((currTar_Info->TarStatus & + ~(UCHAR)TAR_SYNC_MASK) | (UCHAR)SYNC_SUPPORTED); + + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + + else { + + + ACCEPT_MSG_ATN(port); + + sisyncr(port,sync_msg,offset); + + currTar_Info->TarStatus = ((currTar_Info->TarStatus & + ~(UCHAR)TAR_SYNC_MASK) | (UCHAR)SYNC_SUPPORTED); + } +} + + +/*--------------------------------------------------------------------- + * + * Function: sisyncr + * + * Description: Answer the targets sync message. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void sisyncr(USHORT port,UCHAR sync_pulse, UCHAR offset) +#else +void sisyncr(ULONG port,UCHAR sync_pulse, UCHAR offset) +#endif +{ + ARAM_ACCESS(port); + WRW_HARPOON((port+SYNC_MSGS+0), (MPM_OP+AMSG_OUT+SMEXT )); + WRW_HARPOON((port+SYNC_MSGS+2), (MPM_OP+AMSG_OUT+0x03 )); + WRW_HARPOON((port+SYNC_MSGS+4), (MPM_OP+AMSG_OUT+SMSYNC)); + WRW_HARPOON((port+SYNC_MSGS+6), (MPM_OP+AMSG_OUT+sync_pulse)); + WRW_HARPOON((port+SYNC_MSGS+8), (RAT_OP )); + WRW_HARPOON((port+SYNC_MSGS+10),(MPM_OP+AMSG_OUT+offset)); + WRW_HARPOON((port+SYNC_MSGS+12),(BRH_OP+ALWAYS+NP )); + SGRAM_ACCESS(port); + + WR_HARPOON(port+hp_portctrl_0, SCSI_PORT); + WRW_HARPOON((port+hp_intstat), CLR_ALL_INT_1); + + WR_HARPOON(port+hp_autostart_3, (AUTO_IMMED+CMD_ONLY_STRT)); + + while (!(RDW_HARPOON((port+hp_intstat)) & (BUS_FREE | AUTO_INT))) {} +} + + + +#if defined(WIDE_SCSI) + +/*--------------------------------------------------------------------- + * + * Function: siwidn + * + * Description: Read in a message byte from the SCSI bus, and check + * for a parity error. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +UCHAR siwidn(USHORT port, UCHAR p_card) +#else +UCHAR siwidn(ULONG port, UCHAR p_card) +#endif +{ + PSCCB currSCCB; + PSCCBMgr_tar_info currTar_Info; + + currSCCB = BL_Card[p_card].currentSCCB; + currTar_Info = &sccbMgrTbl[p_card][currSCCB->TargID]; + + if (!((currTar_Info->TarStatus & TAR_WIDE_MASK) == WIDE_NEGOCIATED)) { + + + WRW_HARPOON((port+ID_MSG_STRT), + (MPM_OP+AMSG_OUT+(currSCCB->Sccb_idmsg & ~(UCHAR)DISC_PRIV))); + + WRW_HARPOON((port+ID_MSG_STRT+2),BRH_OP+ALWAYS+CMDPZ); + + WRW_HARPOON((port+SYNC_MSGS+0), (MPM_OP+AMSG_OUT+SMEXT )); + WRW_HARPOON((port+SYNC_MSGS+2), (MPM_OP+AMSG_OUT+0x02 )); + WRW_HARPOON((port+SYNC_MSGS+4), (MPM_OP+AMSG_OUT+SMWDTR)); + WRW_HARPOON((port+SYNC_MSGS+6), (RAT_OP )); + WRW_HARPOON((port+SYNC_MSGS+8), (MPM_OP+AMSG_OUT+ SM16BIT)); + WRW_HARPOON((port+SYNC_MSGS+10),(BRH_OP+ALWAYS+NP )); + + WR_HARPOON(port+hp_autostart_3, (SELECT+SELCHK_STRT)); + + + currTar_Info->TarStatus = ((currTar_Info->TarStatus & + ~(UCHAR)TAR_WIDE_MASK) | (UCHAR)WIDE_ENABLED); + + return(TRUE); + } + + else { + + currTar_Info->TarStatus = ((currTar_Info->TarStatus & + ~(UCHAR)TAR_WIDE_MASK) | WIDE_NEGOCIATED); + + currTar_Info->TarEEValue &= ~EE_WIDE_SCSI; + return(FALSE); + } +} + + + +/*--------------------------------------------------------------------- + * + * Function: stwidn + * + * Description: The has sent us a Wide Nego message so handle it as + * necessary. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void stwidn(USHORT port, UCHAR p_card) +#else +void stwidn(ULONG port, UCHAR p_card) +#endif +{ + UCHAR width; + PSCCB currSCCB; + PSCCBMgr_tar_info currTar_Info; + + currSCCB = BL_Card[p_card].currentSCCB; + currTar_Info = &sccbMgrTbl[p_card][currSCCB->TargID]; + + width = sfm(port,currSCCB); + + if((width == 0x00) && (currSCCB->Sccb_scsimsg == SMPARITY)) + { + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + return; + } + + + if (!(currTar_Info->TarEEValue & EE_WIDE_SCSI)) + width = 0; + + if (width) { + currTar_Info->TarStatus |= WIDE_ENABLED; + width = 0; + } + else { + width = NARROW_SCSI; + currTar_Info->TarStatus &= ~WIDE_ENABLED; + } + + + sssyncv(port,currSCCB->TargID,width,currTar_Info); + + + if (currSCCB->Sccb_scsistat == SELECT_WN_ST) + { + + + + currTar_Info->TarStatus |= WIDE_NEGOCIATED; + + if (!((currTar_Info->TarStatus & TAR_SYNC_MASK) == SYNC_SUPPORTED)) + { + ACCEPT_MSG_ATN(port); + ARAM_ACCESS(port); + sisyncn(port,p_card, TRUE); + currSCCB->Sccb_scsistat = SELECT_SN_ST; + SGRAM_ACCESS(port); + } + else + { + ACCEPT_MSG(port); + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + } + + else { + + + ACCEPT_MSG_ATN(port); + + if (currTar_Info->TarEEValue & EE_WIDE_SCSI) + width = SM16BIT; + else + width = SM8BIT; + + siwidr(port,width); + + currTar_Info->TarStatus |= (WIDE_NEGOCIATED | WIDE_ENABLED); + } +} + + +/*--------------------------------------------------------------------- + * + * Function: siwidr + * + * Description: Answer the targets Wide nego message. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void siwidr(USHORT port, UCHAR width) +#else +void siwidr(ULONG port, UCHAR width) +#endif +{ + ARAM_ACCESS(port); + WRW_HARPOON((port+SYNC_MSGS+0), (MPM_OP+AMSG_OUT+SMEXT )); + WRW_HARPOON((port+SYNC_MSGS+2), (MPM_OP+AMSG_OUT+0x02 )); + WRW_HARPOON((port+SYNC_MSGS+4), (MPM_OP+AMSG_OUT+SMWDTR)); + WRW_HARPOON((port+SYNC_MSGS+6), (RAT_OP )); + WRW_HARPOON((port+SYNC_MSGS+8),(MPM_OP+AMSG_OUT+width)); + WRW_HARPOON((port+SYNC_MSGS+10),(BRH_OP+ALWAYS+NP )); + SGRAM_ACCESS(port); + + WR_HARPOON(port+hp_portctrl_0, SCSI_PORT); + WRW_HARPOON((port+hp_intstat), CLR_ALL_INT_1); + + WR_HARPOON(port+hp_autostart_3, (AUTO_IMMED+CMD_ONLY_STRT)); + + while (!(RDW_HARPOON((port+hp_intstat)) & (BUS_FREE | AUTO_INT))) {} +} + +#endif + + + +/*--------------------------------------------------------------------- + * + * Function: sssyncv + * + * Description: Write the desired value to the Sync Register for the + * ID specified. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void sssyncv(USHORT p_port, UCHAR p_id, UCHAR p_sync_value,PSCCBMgr_tar_info currTar_Info) +#else +void sssyncv(ULONG p_port, UCHAR p_id, UCHAR p_sync_value,PSCCBMgr_tar_info currTar_Info) +#endif +{ + UCHAR index; + + index = p_id; + + switch (index) { + + case 0: + index = 12; /* hp_synctarg_0 */ + break; + case 1: + index = 13; /* hp_synctarg_1 */ + break; + case 2: + index = 14; /* hp_synctarg_2 */ + break; + case 3: + index = 15; /* hp_synctarg_3 */ + break; + case 4: + index = 8; /* hp_synctarg_4 */ + break; + case 5: + index = 9; /* hp_synctarg_5 */ + break; + case 6: + index = 10; /* hp_synctarg_6 */ + break; + case 7: + index = 11; /* hp_synctarg_7 */ + break; + case 8: + index = 4; /* hp_synctarg_8 */ + break; + case 9: + index = 5; /* hp_synctarg_9 */ + break; + case 10: + index = 6; /* hp_synctarg_10 */ + break; + case 11: + index = 7; /* hp_synctarg_11 */ + break; + case 12: + index = 0; /* hp_synctarg_12 */ + break; + case 13: + index = 1; /* hp_synctarg_13 */ + break; + case 14: + index = 2; /* hp_synctarg_14 */ + break; + case 15: + index = 3; /* hp_synctarg_15 */ + + } + + WR_HARPOON(p_port+hp_synctarg_base+index, p_sync_value); + + currTar_Info->TarSyncCtrl = p_sync_value; +} + + +/*--------------------------------------------------------------------- + * + * Function: sresb + * + * Description: Reset the desired card's SCSI bus. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void sresb(USHORT port, UCHAR p_card) +#else +void sresb(ULONG port, UCHAR p_card) +#endif +{ + UCHAR scsiID, i; + + PSCCBMgr_tar_info currTar_Info; + + WR_HARPOON(port+hp_page_ctrl, + (RD_HARPOON(port+hp_page_ctrl) | G_INT_DISABLE)); + WRW_HARPOON((port+hp_intstat), CLR_ALL_INT); + + WR_HARPOON(port+hp_scsictrl_0, SCSI_RST); + + scsiID = RD_HARPOON(port+hp_seltimeout); + WR_HARPOON(port+hp_seltimeout,TO_5ms); + WRW_HARPOON((port+hp_intstat), TIMEOUT); + + WR_HARPOON(port+hp_portctrl_0,(SCSI_PORT | START_TO)); + + while (!(RDW_HARPOON((port+hp_intstat)) & TIMEOUT)) {} + + WR_HARPOON(port+hp_seltimeout,scsiID); + + WR_HARPOON(port+hp_scsictrl_0, ENA_SCAM_SEL); + + Wait(port, TO_5ms); + + WRW_HARPOON((port+hp_intstat), CLR_ALL_INT); + + WR_HARPOON(port+hp_int_mask, (RD_HARPOON(port+hp_int_mask) | 0x00)); + + for (scsiID = 0; scsiID < MAX_SCSI_TAR; scsiID++) + { + currTar_Info = &sccbMgrTbl[p_card][scsiID]; + + if (currTar_Info->TarEEValue & EE_SYNC_MASK) + { + currTar_Info->TarSyncCtrl = 0; + currTar_Info->TarStatus &= ~TAR_SYNC_MASK; + } + + if (currTar_Info->TarEEValue & EE_WIDE_SCSI) + { + currTar_Info->TarStatus &= ~TAR_WIDE_MASK; + } + + sssyncv(port, scsiID, NARROW_SCSI,currTar_Info); + + SccbMgrTableInitTarget(p_card, scsiID); + } + + BL_Card[p_card].scanIndex = 0x00; + BL_Card[p_card].currentSCCB = NULL; + BL_Card[p_card].globalFlags &= ~(F_TAG_STARTED | F_HOST_XFER_ACT + | F_NEW_SCCB_CMD); + BL_Card[p_card].cmdCounter = 0x00; + BL_Card[p_card].discQCount = 0x00; + BL_Card[p_card].tagQ_Lst = 0x01; + + for(i = 0; i < QUEUE_DEPTH; i++) + BL_Card[p_card].discQ_Tbl[i] = NULL; + + WR_HARPOON(port+hp_page_ctrl, + (RD_HARPOON(port+hp_page_ctrl) & ~G_INT_DISABLE)); + +} + +/*--------------------------------------------------------------------- + * + * Function: ssenss + * + * Description: Setup for the Auto Sense command. + * + *---------------------------------------------------------------------*/ +void ssenss(PSCCBcard pCurrCard) +{ + UCHAR i; + PSCCB currSCCB; + + currSCCB = pCurrCard->currentSCCB; + + + currSCCB->Save_CdbLen = currSCCB->CdbLength; + + for (i = 0; i < 6; i++) { + + currSCCB->Save_Cdb[i] = currSCCB->Cdb[i]; + } + + currSCCB->CdbLength = SIX_BYTE_CMD; + currSCCB->Cdb[0] = SCSI_REQUEST_SENSE; + currSCCB->Cdb[1] = currSCCB->Cdb[1] & (UCHAR)0xE0; /*Keep LUN. */ + currSCCB->Cdb[2] = 0x00; + currSCCB->Cdb[3] = 0x00; + currSCCB->Cdb[4] = currSCCB->RequestSenseLength; + currSCCB->Cdb[5] = 0x00; + + currSCCB->Sccb_XferCnt = (unsigned long)currSCCB->RequestSenseLength; + + currSCCB->Sccb_ATC = 0x00; + + currSCCB->Sccb_XferState |= F_AUTO_SENSE; + + currSCCB->Sccb_XferState &= ~F_SG_XFER; + + currSCCB->Sccb_idmsg = currSCCB->Sccb_idmsg & ~(UCHAR)DISC_PRIV; + + currSCCB->ControlByte = 0x00; + + currSCCB->Sccb_MGRFlags &= F_STATUSLOADED; +} + + + +/*--------------------------------------------------------------------- + * + * Function: sxfrp + * + * Description: Transfer data into the bit bucket until the device + * decides to switch phase. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void sxfrp(USHORT p_port, UCHAR p_card) +#else +void sxfrp(ULONG p_port, UCHAR p_card) +#endif +{ + UCHAR curr_phz; + + + DISABLE_AUTO(p_port); + + if (BL_Card[p_card].globalFlags & F_HOST_XFER_ACT) { + + hostDataXferAbort(p_port,p_card,BL_Card[p_card].currentSCCB); + + } + + /* If the Automation handled the end of the transfer then do not + match the phase or we will get out of sync with the ISR. */ + + if (RDW_HARPOON((p_port+hp_intstat)) & (BUS_FREE | XFER_CNT_0 | AUTO_INT)) + return; + + WR_HARPOON(p_port+hp_xfercnt_0, 0x00); + + curr_phz = RD_HARPOON(p_port+hp_scsisig) & (UCHAR)S_SCSI_PHZ; + + WRW_HARPOON((p_port+hp_intstat), XFER_CNT_0); + + + WR_HARPOON(p_port+hp_scsisig, curr_phz); + + while ( !(RDW_HARPOON((p_port+hp_intstat)) & (BUS_FREE | RESET)) && + (curr_phz == (RD_HARPOON(p_port+hp_scsisig) & (UCHAR)S_SCSI_PHZ)) ) + { + if (curr_phz & (UCHAR)SCSI_IOBIT) + { + WR_HARPOON(p_port+hp_portctrl_0, (SCSI_PORT | HOST_PORT | SCSI_INBIT)); + + if (!(RD_HARPOON(p_port+hp_xferstat) & FIFO_EMPTY)) + { + RD_HARPOON(p_port+hp_fifodata_0); + } + } + else + { + WR_HARPOON(p_port+hp_portctrl_0, (SCSI_PORT | HOST_PORT | HOST_WRT)); + if (RD_HARPOON(p_port+hp_xferstat) & FIFO_EMPTY) + { + WR_HARPOON(p_port+hp_fifodata_0,0xFA); + } + } + } /* End of While loop for padding data I/O phase */ + + while ( !(RDW_HARPOON((p_port+hp_intstat)) & (BUS_FREE | RESET))) + { + if (RD_HARPOON(p_port+hp_scsisig) & SCSI_REQ) + break; + } + + WR_HARPOON(p_port+hp_portctrl_0, (SCSI_PORT | HOST_PORT | SCSI_INBIT)); + while (!(RD_HARPOON(p_port+hp_xferstat) & FIFO_EMPTY)) + { + RD_HARPOON(p_port+hp_fifodata_0); + } + + if ( !(RDW_HARPOON((p_port+hp_intstat)) & (BUS_FREE | RESET))) + { + WR_HARPOON(p_port+hp_autostart_0, (AUTO_IMMED+DISCONNECT_START)); + while (!(RDW_HARPOON((p_port+hp_intstat)) & AUTO_INT)) {} + + if (RDW_HARPOON((p_port+hp_intstat)) & (ICMD_COMP | ITAR_DISC)) + while (!(RDW_HARPOON((p_port+hp_intstat)) & (BUS_FREE | RSEL))) ; + } +} + + +/*--------------------------------------------------------------------- + * + * Function: schkdd + * + * Description: Make sure data has been flushed from both FIFOs and abort + * the operations if necessary. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void schkdd(USHORT port, UCHAR p_card) +#else +void schkdd(ULONG port, UCHAR p_card) +#endif +{ + USHORT TimeOutLoop; + UCHAR sPhase; + + PSCCB currSCCB; + + currSCCB = BL_Card[p_card].currentSCCB; + + + if ((currSCCB->Sccb_scsistat != DATA_OUT_ST) && + (currSCCB->Sccb_scsistat != DATA_IN_ST)) { + return; + } + + + + if (currSCCB->Sccb_XferState & F_ODD_BALL_CNT) + { + + currSCCB->Sccb_ATC += (currSCCB->Sccb_XferCnt-1); + + currSCCB->Sccb_XferCnt = 1; + + currSCCB->Sccb_XferState &= ~F_ODD_BALL_CNT; + WRW_HARPOON((port+hp_fiforead), (USHORT) 0x00); + WR_HARPOON(port+hp_xferstat, 0x00); + } + + else + { + + currSCCB->Sccb_ATC += currSCCB->Sccb_XferCnt; + + currSCCB->Sccb_XferCnt = 0; + } + + if ((RDW_HARPOON((port+hp_intstat)) & PARITY) && + (currSCCB->HostStatus == SCCB_COMPLETE)) { + + currSCCB->HostStatus = SCCB_PARITY_ERR; + WRW_HARPOON((port+hp_intstat), PARITY); + } + + + hostDataXferAbort(port,p_card,currSCCB); + + + while (RD_HARPOON(port+hp_scsisig) & SCSI_ACK) {} + + TimeOutLoop = 0; + + while(RD_HARPOON(port+hp_xferstat) & FIFO_EMPTY) + { + if (RDW_HARPOON((port+hp_intstat)) & BUS_FREE) { + return; + } + if (RD_HARPOON(port+hp_offsetctr) & (UCHAR)0x1F) { + break; + } + if (RDW_HARPOON((port+hp_intstat)) & RESET) { + return; + } + if ((RD_HARPOON(port+hp_scsisig) & SCSI_REQ) || (TimeOutLoop++>0x3000) ) + break; + } + + sPhase = RD_HARPOON(port+hp_scsisig) & (SCSI_BSY | S_SCSI_PHZ); + if ((!(RD_HARPOON(port+hp_xferstat) & FIFO_EMPTY)) || + (RD_HARPOON(port+hp_offsetctr) & (UCHAR)0x1F) || + (sPhase == (SCSI_BSY | S_DATAO_PH)) || + (sPhase == (SCSI_BSY | S_DATAI_PH))) + { + + WR_HARPOON(port+hp_portctrl_0, SCSI_PORT); + + if (!(currSCCB->Sccb_XferState & F_ALL_XFERRED)) + { + if (currSCCB->Sccb_XferState & F_HOST_XFER_DIR) { + phaseDataIn(port,p_card); + } + + else { + phaseDataOut(port,p_card); + } + } + else + { + sxfrp(port,p_card); + if (!(RDW_HARPOON((port+hp_intstat)) & + (BUS_FREE | ICMD_COMP | ITAR_DISC | RESET))) + { + WRW_HARPOON((port+hp_intstat), AUTO_INT); + phaseDecode(port,p_card); + } + } + + } + + else { + WR_HARPOON(port+hp_portctrl_0, 0x00); + } +} + + +/*--------------------------------------------------------------------- + * + * Function: sinits + * + * Description: Setup SCCB manager fields in this SCCB. + * + *---------------------------------------------------------------------*/ + +void sinits(PSCCB p_sccb, UCHAR p_card) +{ + PSCCBMgr_tar_info currTar_Info; + + if((p_sccb->TargID > MAX_SCSI_TAR) || (p_sccb->Lun > MAX_LUN)) + { + return; + } + currTar_Info = &sccbMgrTbl[p_card][p_sccb->TargID]; + + p_sccb->Sccb_XferState = 0x00; + p_sccb->Sccb_XferCnt = p_sccb->DataLength; + + if ((p_sccb->OperationCode == SCATTER_GATHER_COMMAND) || + (p_sccb->OperationCode == RESIDUAL_SG_COMMAND)) { + + p_sccb->Sccb_SGoffset = 0; + p_sccb->Sccb_XferState = F_SG_XFER; + p_sccb->Sccb_XferCnt = 0x00; + } + + if (p_sccb->DataLength == 0x00) + + p_sccb->Sccb_XferState |= F_ALL_XFERRED; + + if (p_sccb->ControlByte & F_USE_CMD_Q) + { + if ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) == TAG_Q_REJECT) + p_sccb->ControlByte &= ~F_USE_CMD_Q; + + else + currTar_Info->TarStatus |= TAG_Q_TRYING; + } + +/* For !single SCSI device in system & device allow Disconnect + or command is tag_q type then send Cmd with Disconnect Enable + else send Cmd with Disconnect Disable */ + +/* + if (((!(BL_Card[p_card].globalFlags & F_SINGLE_DEVICE)) && + (currTar_Info->TarStatus & TAR_ALLOW_DISC)) || + (currTar_Info->TarStatus & TAG_Q_TRYING)) { +*/ + if ((currTar_Info->TarStatus & TAR_ALLOW_DISC) || + (currTar_Info->TarStatus & TAG_Q_TRYING)) { + p_sccb->Sccb_idmsg = (UCHAR)(SMIDENT | DISC_PRIV) | p_sccb->Lun; + } + + else { + + p_sccb->Sccb_idmsg = (UCHAR)SMIDENT | p_sccb->Lun; + } + + p_sccb->HostStatus = 0x00; + p_sccb->TargetStatus = 0x00; + p_sccb->Sccb_tag = 0x00; + p_sccb->Sccb_MGRFlags = 0x00; + p_sccb->Sccb_sgseg = 0x00; + p_sccb->Sccb_ATC = 0x00; + p_sccb->Sccb_savedATC = 0x00; +/* + p_sccb->SccbVirtDataPtr = 0x00; + p_sccb->Sccb_forwardlink = NULL; + p_sccb->Sccb_backlink = NULL; + */ + p_sccb->Sccb_scsistat = BUS_FREE_ST; + p_sccb->SccbStatus = SCCB_IN_PROCESS; + p_sccb->Sccb_scsimsg = SMNO_OP; + +} + + +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: phase.c $ + * + * Description: Functions to intially handle the SCSI bus phase when + * the target asserts request (and the automation is not + * enabled to handle the situation). + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <scsi2.h>*/ +/*#include <harpoon.h>*/ + + +/* +extern SCCBCARD BL_Card[MAX_CARDS]; +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; + +#if defined(OS2) + extern void (far *s_PhaseTbl[8]) (ULONG, UCHAR); +#else + #if defined(DOS) + extern void (*s_PhaseTbl[8]) (USHORT, UCHAR); + #else + extern void (*s_PhaseTbl[8]) (ULONG, UCHAR); + #endif +#endif +*/ + +/*--------------------------------------------------------------------- + * + * Function: Phase Decode + * + * Description: Determine the phase and call the appropriate function. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void phaseDecode(USHORT p_port, UCHAR p_card) +#else +void phaseDecode(ULONG p_port, UCHAR p_card) +#endif +{ + unsigned char phase_ref; +#if defined(OS2) + void (far *phase) (ULONG, UCHAR); +#else + #if defined(DOS) + void (*phase) (USHORT, UCHAR); + #else + void (*phase) (ULONG, UCHAR); + #endif +#endif + + + DISABLE_AUTO(p_port); + + phase_ref = (UCHAR) (RD_HARPOON(p_port+hp_scsisig) & S_SCSI_PHZ); + + phase = s_PhaseTbl[phase_ref]; + + (*phase)(p_port, p_card); /* Call the correct phase func */ +} + + + +/*--------------------------------------------------------------------- + * + * Function: Data Out Phase + * + * Description: Start up both the BusMaster and Xbow. + * + *---------------------------------------------------------------------*/ + +#if defined(OS2) +void far phaseDataOut(ULONG port, UCHAR p_card) +#else +#if defined(DOS) +void phaseDataOut(USHORT port, UCHAR p_card) +#else +void phaseDataOut(ULONG port, UCHAR p_card) +#endif +#endif +{ + + PSCCB currSCCB; + + currSCCB = BL_Card[p_card].currentSCCB; + if (currSCCB == NULL) + { + return; /* Exit if No SCCB record */ + } + + currSCCB->Sccb_scsistat = DATA_OUT_ST; + currSCCB->Sccb_XferState &= ~(F_HOST_XFER_DIR | F_NO_DATA_YET); + + WR_HARPOON(port+hp_portctrl_0, SCSI_PORT); + + WRW_HARPOON((port+hp_intstat), XFER_CNT_0); + + WR_HARPOON(port+hp_autostart_0, (END_DATA+END_DATA_START)); + + dataXferProcessor(port, &BL_Card[p_card]); + +#if defined(NOBUGBUG) + if (RDW_HARPOON((port+hp_intstat)) & XFER_CNT_0) + WRW_HARPOON((port+hp_intstat), XFER_CNT_0); + +#endif + + + if (currSCCB->Sccb_XferCnt == 0) { + + + if ((currSCCB->ControlByte & SCCB_DATA_XFER_OUT) && + (currSCCB->HostStatus == SCCB_COMPLETE)) + currSCCB->HostStatus = SCCB_DATA_OVER_RUN; + + sxfrp(port,p_card); + if (!(RDW_HARPOON((port+hp_intstat)) & (BUS_FREE | RESET))) + phaseDecode(port,p_card); + } +} + + +/*--------------------------------------------------------------------- + * + * Function: Data In Phase + * + * Description: Startup the BusMaster and the XBOW. + * + *---------------------------------------------------------------------*/ + +#if defined(OS2) +void far phaseDataIn(ULONG port, UCHAR p_card) +#else +#if defined(DOS) +void phaseDataIn(USHORT port, UCHAR p_card) +#else +void phaseDataIn(ULONG port, UCHAR p_card) +#endif +#endif +{ + + PSCCB currSCCB; + + currSCCB = BL_Card[p_card].currentSCCB; + + if (currSCCB == NULL) + { + return; /* Exit if No SCCB record */ + } + + + currSCCB->Sccb_scsistat = DATA_IN_ST; + currSCCB->Sccb_XferState |= F_HOST_XFER_DIR; + currSCCB->Sccb_XferState &= ~F_NO_DATA_YET; + + WR_HARPOON(port+hp_portctrl_0, SCSI_PORT); + + WRW_HARPOON((port+hp_intstat), XFER_CNT_0); + + WR_HARPOON(port+hp_autostart_0, (END_DATA+END_DATA_START)); + + dataXferProcessor(port, &BL_Card[p_card]); + + if (currSCCB->Sccb_XferCnt == 0) { + + + if ((currSCCB->ControlByte & SCCB_DATA_XFER_IN) && + (currSCCB->HostStatus == SCCB_COMPLETE)) + currSCCB->HostStatus = SCCB_DATA_OVER_RUN; + + sxfrp(port,p_card); + if (!(RDW_HARPOON((port+hp_intstat)) & (BUS_FREE | RESET))) + phaseDecode(port,p_card); + + } +} + +/*--------------------------------------------------------------------- + * + * Function: Command Phase + * + * Description: Load the CDB into the automation and start it up. + * + *---------------------------------------------------------------------*/ + +#if defined(OS2) +void far phaseCommand(ULONG p_port, UCHAR p_card) +#else +#if defined(DOS) +void phaseCommand(USHORT p_port, UCHAR p_card) +#else +void phaseCommand(ULONG p_port, UCHAR p_card) +#endif +#endif +{ + PSCCB currSCCB; +#if defined(DOS) + USHORT cdb_reg; +#else + ULONG cdb_reg; +#endif + UCHAR i; + + currSCCB = BL_Card[p_card].currentSCCB; + + if (currSCCB->OperationCode == RESET_COMMAND) { + + currSCCB->HostStatus = SCCB_PHASE_SEQUENCE_FAIL; + currSCCB->CdbLength = SIX_BYTE_CMD; + } + + WR_HARPOON(p_port+hp_scsisig, 0x00); + + ARAM_ACCESS(p_port); + + + cdb_reg = p_port + CMD_STRT; + + for (i=0; i < currSCCB->CdbLength; i++) { + + if (currSCCB->OperationCode == RESET_COMMAND) + + WRW_HARPOON(cdb_reg, (MPM_OP + ACOMMAND + 0x00)); + + else + WRW_HARPOON(cdb_reg, (MPM_OP + ACOMMAND + currSCCB->Cdb[i])); + cdb_reg +=2; + } + + if (currSCCB->CdbLength != TWELVE_BYTE_CMD) + WRW_HARPOON(cdb_reg, (BRH_OP+ALWAYS+ NP)); + + WR_HARPOON(p_port+hp_portctrl_0,(SCSI_PORT)); + + currSCCB->Sccb_scsistat = COMMAND_ST; + + WR_HARPOON(p_port+hp_autostart_3, (AUTO_IMMED | CMD_ONLY_STRT)); + SGRAM_ACCESS(p_port); +} + + +/*--------------------------------------------------------------------- + * + * Function: Status phase + * + * Description: Bring in the status and command complete message bytes + * + *---------------------------------------------------------------------*/ + +#if defined(OS2) +void far phaseStatus(ULONG port, UCHAR p_card) +#else +#if defined(DOS) +void phaseStatus(USHORT port, UCHAR p_card) +#else +void phaseStatus(ULONG port, UCHAR p_card) +#endif +#endif +{ + /* Start-up the automation to finish off this command and let the + isr handle the interrupt for command complete when it comes in. + We could wait here for the interrupt to be generated? + */ + + WR_HARPOON(port+hp_scsisig, 0x00); + + WR_HARPOON(port+hp_autostart_0, (AUTO_IMMED+END_DATA_START)); +} + + +/*--------------------------------------------------------------------- + * + * Function: Phase Message Out + * + * Description: Send out our message (if we have one) and handle whatever + * else is involed. + * + *---------------------------------------------------------------------*/ + +#if defined(OS2) +void far phaseMsgOut(ULONG port, UCHAR p_card) +#else +#if defined(DOS) +void phaseMsgOut(USHORT port, UCHAR p_card) +#else +void phaseMsgOut(ULONG port, UCHAR p_card) +#endif +#endif +{ + UCHAR message,scsiID; + PSCCB currSCCB; + PSCCBMgr_tar_info currTar_Info; + + currSCCB = BL_Card[p_card].currentSCCB; + + if (currSCCB != NULL) { + + message = currSCCB->Sccb_scsimsg; + scsiID = currSCCB->TargID; + + if (message == SMDEV_RESET) + { + + + currTar_Info = &sccbMgrTbl[p_card][scsiID]; + currTar_Info->TarSyncCtrl = 0; + sssyncv(port, scsiID, NARROW_SCSI,currTar_Info); + + if (sccbMgrTbl[p_card][scsiID].TarEEValue & EE_SYNC_MASK) + { + + sccbMgrTbl[p_card][scsiID].TarStatus &= ~TAR_SYNC_MASK; + + } + + if (sccbMgrTbl[p_card][scsiID].TarEEValue & EE_WIDE_SCSI) + { + + sccbMgrTbl[p_card][scsiID].TarStatus &= ~TAR_WIDE_MASK; + } + + + queueFlushSccb(p_card,SCCB_COMPLETE); + SccbMgrTableInitTarget(p_card,scsiID); + } + else if (currSCCB->Sccb_scsistat == ABORT_ST) + { + currSCCB->HostStatus = SCCB_COMPLETE; + if(BL_Card[p_card].discQ_Tbl[currSCCB->Sccb_tag] != NULL) + { + BL_Card[p_card].discQ_Tbl[currSCCB->Sccb_tag] = NULL; + sccbMgrTbl[p_card][scsiID].TarTagQ_Cnt--; + } + + } + + else if (currSCCB->Sccb_scsistat < COMMAND_ST) + { + + + if(message == SMNO_OP) + { + currSCCB->Sccb_MGRFlags |= F_DEV_SELECTED; + + ssel(port,p_card); + return; + } + } + else + { + + + if (message == SMABORT) + + queueFlushSccb(p_card,SCCB_COMPLETE); + } + + } + else + { + message = SMABORT; + } + + WRW_HARPOON((port+hp_intstat), (BUS_FREE | PHASE | XFER_CNT_0)); + + + WR_HARPOON(port+hp_portctrl_0, SCSI_BUS_EN); + + WR_HARPOON(port+hp_scsidata_0,message); + + WR_HARPOON(port+hp_scsisig, (SCSI_ACK + S_ILL_PH)); + + ACCEPT_MSG(port); + + WR_HARPOON(port+hp_portctrl_0, 0x00); + + if ((message == SMABORT) || (message == SMDEV_RESET) || + (message == SMABORT_TAG) ) + { + + while(!(RDW_HARPOON((port+hp_intstat)) & (BUS_FREE | PHASE))) {} + + if (RDW_HARPOON((port+hp_intstat)) & BUS_FREE) + { + WRW_HARPOON((port+hp_intstat), BUS_FREE); + + if (currSCCB != NULL) + { + + if((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = FALSE; + else + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = FALSE; + + queueCmdComplete(&BL_Card[p_card],currSCCB, p_card); + } + + else + { + BL_Card[p_card].globalFlags |= F_NEW_SCCB_CMD; + } + } + + else + { + + sxfrp(port,p_card); + } + } + + else + { + + if(message == SMPARITY) + { + currSCCB->Sccb_scsimsg = SMNO_OP; + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + else + { + sxfrp(port,p_card); + } + } +} + + +/*--------------------------------------------------------------------- + * + * Function: Message In phase + * + * Description: Bring in the message and determine what to do with it. + * + *---------------------------------------------------------------------*/ + +#if defined(OS2) +void far phaseMsgIn(ULONG port, UCHAR p_card) +#else +#if defined(DOS) +void phaseMsgIn(USHORT port, UCHAR p_card) +#else +void phaseMsgIn(ULONG port, UCHAR p_card) +#endif +#endif +{ + UCHAR message; + PSCCB currSCCB; + + currSCCB = BL_Card[p_card].currentSCCB; + + if (BL_Card[p_card].globalFlags & F_HOST_XFER_ACT) + { + + phaseChkFifo(port, p_card); + } + + message = RD_HARPOON(port+hp_scsidata_0); + if ((message == SMDISC) || (message == SMSAVE_DATA_PTR)) + { + + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+END_DATA_START)); + + } + + else + { + + message = sfm(port,currSCCB); + if (message) + { + + + sdecm(message,port,p_card); + + } + else + { + if(currSCCB->Sccb_scsimsg != SMPARITY) + ACCEPT_MSG(port); + WR_HARPOON(port+hp_autostart_1, (AUTO_IMMED+DISCONNECT_START)); + } + } + +} + + +/*--------------------------------------------------------------------- + * + * Function: Illegal phase + * + * Description: Target switched to some illegal phase, so all we can do + * is report an error back to the host (if that is possible) + * and send an ABORT message to the misbehaving target. + * + *---------------------------------------------------------------------*/ + +#if defined(OS2) +void far phaseIllegal(ULONG port, UCHAR p_card) +#else +#if defined(DOS) +void phaseIllegal(USHORT port, UCHAR p_card) +#else +void phaseIllegal(ULONG port, UCHAR p_card) +#endif +#endif +{ + PSCCB currSCCB; + + currSCCB = BL_Card[p_card].currentSCCB; + + WR_HARPOON(port+hp_scsisig, RD_HARPOON(port+hp_scsisig)); + if (currSCCB != NULL) { + + currSCCB->HostStatus = SCCB_PHASE_SEQUENCE_FAIL; + currSCCB->Sccb_scsistat = ABORT_ST; + currSCCB->Sccb_scsimsg = SMABORT; + } + + ACCEPT_MSG_ATN(port); +} + + + +/*--------------------------------------------------------------------- + * + * Function: Phase Check FIFO + * + * Description: Make sure data has been flushed from both FIFOs and abort + * the operations if necessary. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void phaseChkFifo(USHORT port, UCHAR p_card) +#else +void phaseChkFifo(ULONG port, UCHAR p_card) +#endif +{ + ULONG xfercnt; + PSCCB currSCCB; + + currSCCB = BL_Card[p_card].currentSCCB; + + if (currSCCB->Sccb_scsistat == DATA_IN_ST) + { + + while((!(RD_HARPOON(port+hp_xferstat) & FIFO_EMPTY)) && + (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY)) {} + + + if (!(RD_HARPOON(port+hp_xferstat) & FIFO_EMPTY)) + { + currSCCB->Sccb_ATC += currSCCB->Sccb_XferCnt; + + currSCCB->Sccb_XferCnt = 0; + + if ((RDW_HARPOON((port+hp_intstat)) & PARITY) && + (currSCCB->HostStatus == SCCB_COMPLETE)) + { + currSCCB->HostStatus = SCCB_PARITY_ERR; + WRW_HARPOON((port+hp_intstat), PARITY); + } + + hostDataXferAbort(port,p_card,currSCCB); + + dataXferProcessor(port, &BL_Card[p_card]); + + while((!(RD_HARPOON(port+hp_xferstat) & FIFO_EMPTY)) && + (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY)) {} + + } + } /*End Data In specific code. */ + + + +#if defined(DOS) + asm { mov dx,port; + add dx,hp_xfercnt_2; + in al,dx; + dec dx; + xor ah,ah; + mov word ptr xfercnt+2,ax; + in al,dx; + dec dx; + mov ah,al; + in al,dx; + mov word ptr xfercnt,ax; + } +#else + GET_XFER_CNT(port,xfercnt); +#endif + + + WR_HARPOON(port+hp_xfercnt_0, 0x00); + + + WR_HARPOON(port+hp_portctrl_0, 0x00); + + currSCCB->Sccb_ATC += (currSCCB->Sccb_XferCnt - xfercnt); + + currSCCB->Sccb_XferCnt = xfercnt; + + if ((RDW_HARPOON((port+hp_intstat)) & PARITY) && + (currSCCB->HostStatus == SCCB_COMPLETE)) { + + currSCCB->HostStatus = SCCB_PARITY_ERR; + WRW_HARPOON((port+hp_intstat), PARITY); + } + + + hostDataXferAbort(port,p_card,currSCCB); + + + WR_HARPOON(port+hp_fifowrite, 0x00); + WR_HARPOON(port+hp_fiforead, 0x00); + WR_HARPOON(port+hp_xferstat, 0x00); + + WRW_HARPOON((port+hp_intstat), XFER_CNT_0); +} + + +/*--------------------------------------------------------------------- + * + * Function: Phase Bus Free + * + * Description: We just went bus free so figure out if it was + * because of command complete or from a disconnect. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void phaseBusFree(USHORT port, UCHAR p_card) +#else +void phaseBusFree(ULONG port, UCHAR p_card) +#endif +{ + PSCCB currSCCB; + + currSCCB = BL_Card[p_card].currentSCCB; + + if (currSCCB != NULL) + { + + DISABLE_AUTO(port); + + + if (currSCCB->OperationCode == RESET_COMMAND) + { + + if((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = FALSE; + else + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = FALSE; + + queueCmdComplete(&BL_Card[p_card], currSCCB, p_card); + + queueSearchSelect(&BL_Card[p_card],p_card); + + } + + else if(currSCCB->Sccb_scsistat == SELECT_SN_ST) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus |= + (UCHAR)SYNC_SUPPORTED; + sccbMgrTbl[p_card][currSCCB->TargID].TarEEValue &= ~EE_SYNC_MASK; + } + + else if(currSCCB->Sccb_scsistat == SELECT_WN_ST) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus = + (sccbMgrTbl[p_card][currSCCB->TargID]. + TarStatus & ~WIDE_ENABLED) | WIDE_NEGOCIATED; + + sccbMgrTbl[p_card][currSCCB->TargID].TarEEValue &= ~EE_WIDE_SCSI; + } + +#if !defined(DOS) + else if(currSCCB->Sccb_scsistat == SELECT_Q_ST) + { + /* Make sure this is not a phony BUS_FREE. If we were + reselected or if BUSY is NOT on then this is a + valid BUS FREE. SRR Wednesday, 5/10/1995. */ + + if ((!(RD_HARPOON(port+hp_scsisig) & SCSI_BSY)) || + (RDW_HARPOON((port+hp_intstat)) & RSEL)) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus &= ~TAR_TAG_Q_MASK; + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus |= TAG_Q_REJECT; + } + + else + { + return; + } + } +#endif + + else + { + + currSCCB->Sccb_scsistat = BUS_FREE_ST; + + if (!currSCCB->HostStatus) + { + currSCCB->HostStatus = SCCB_PHASE_SEQUENCE_FAIL; + } + + if((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = FALSE; + else + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = FALSE; + + queueCmdComplete(&BL_Card[p_card], currSCCB, p_card); + return; + } + + + BL_Card[p_card].globalFlags |= F_NEW_SCCB_CMD; + + } /*end if !=null */ +} + + + + +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: automate.c $ + * + * Description: Functions relating to programming the automation of + * the HARPOON. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <scsi2.h>*/ +/*#include <harpoon.h>*/ + +/* +extern SCCBCARD BL_Card[MAX_CARDS]; +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; +extern SCCBCARD BL_Card[MAX_CARDS]; +*/ + +/*--------------------------------------------------------------------- + * + * Function: Auto Load Default Map + * + * Description: Load the Automation RAM with the defualt map values. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void autoLoadDefaultMap(USHORT p_port) +#else +void autoLoadDefaultMap(ULONG p_port) +#endif +{ +#if defined(DOS) + USHORT map_addr; +#else + ULONG map_addr; +#endif + + ARAM_ACCESS(p_port); + map_addr = p_port + hp_aramBase; + + WRW_HARPOON(map_addr, (MPM_OP+AMSG_OUT+ 0xC0)); /*ID MESSAGE */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+AMSG_OUT+ 0x20)); /*SIMPLE TAG QUEUEING MSG */ + map_addr +=2; + WRW_HARPOON(map_addr, RAT_OP); /*RESET ATTENTION */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+AMSG_OUT+ 0x00)); /*TAG ID MSG */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 0 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 1 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 2 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 3 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 4 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 5 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 6 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 7 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 8 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 9 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 10 */ + map_addr +=2; + WRW_HARPOON(map_addr, (MPM_OP+ACOMMAND+ 0x00)); /*CDB BYTE 11 */ + map_addr +=2; + WRW_HARPOON(map_addr, (CPE_OP+ADATA_OUT+ DINT)); /*JUMP IF DATA OUT */ + map_addr +=2; + WRW_HARPOON(map_addr, (TCB_OP+FIFO_0+ DI)); /*JUMP IF NO DATA IN FIFO */ + map_addr +=2; /*This means AYNC DATA IN */ + WRW_HARPOON(map_addr, (SSI_OP+ SSI_IDO_STRT)); /*STOP AND INTERRUPT */ + map_addr +=2; + WRW_HARPOON(map_addr, (CPE_OP+ADATA_IN+DINT)); /*JUMP IF NOT DATA IN PHZ */ + map_addr +=2; + WRW_HARPOON(map_addr, (CPN_OP+AMSG_IN+ ST)); /*IF NOT MSG IN CHECK 4 DATA IN */ + map_addr +=2; + WRW_HARPOON(map_addr, (CRD_OP+SDATA+ 0x02)); /*SAVE DATA PTR MSG? */ + map_addr +=2; + WRW_HARPOON(map_addr, (BRH_OP+NOT_EQ+ DC)); /*GO CHECK FOR DISCONNECT MSG */ + map_addr +=2; + WRW_HARPOON(map_addr, (MRR_OP+SDATA+ D_AR1)); /*SAVE DATA PTRS MSG */ + map_addr +=2; + WRW_HARPOON(map_addr, (CPN_OP+AMSG_IN+ ST)); /*IF NOT MSG IN CHECK DATA IN */ + map_addr +=2; + WRW_HARPOON(map_addr, (CRD_OP+SDATA+ 0x04)); /*DISCONNECT MSG? */ + map_addr +=2; + WRW_HARPOON(map_addr, (BRH_OP+NOT_EQ+ UNKNWN));/*UKNKNOWN MSG */ + map_addr +=2; + WRW_HARPOON(map_addr, (MRR_OP+SDATA+ D_BUCKET));/*XFER DISCONNECT MSG */ + map_addr +=2; + WRW_HARPOON(map_addr, (SSI_OP+ SSI_ITAR_DISC));/*STOP AND INTERRUPT */ + map_addr +=2; + WRW_HARPOON(map_addr, (CPN_OP+ASTATUS+ UNKNWN));/*JUMP IF NOT STATUS PHZ. */ + map_addr +=2; + WRW_HARPOON(map_addr, (MRR_OP+SDATA+ D_AR0)); /*GET STATUS BYTE */ + map_addr +=2; + WRW_HARPOON(map_addr, (CPN_OP+AMSG_IN+ CC)); /*ERROR IF NOT MSG IN PHZ */ + map_addr +=2; + WRW_HARPOON(map_addr, (CRD_OP+SDATA+ 0x00)); /*CHECK FOR CMD COMPLETE MSG. */ + map_addr +=2; + WRW_HARPOON(map_addr, (BRH_OP+NOT_EQ+ CC)); /*ERROR IF NOT CMD COMPLETE MSG. */ + map_addr +=2; + WRW_HARPOON(map_addr, (MRR_OP+SDATA+ D_BUCKET));/*GET CMD COMPLETE MSG */ + map_addr +=2; + WRW_HARPOON(map_addr, (SSI_OP+ SSI_ICMD_COMP));/*END OF COMMAND */ + map_addr +=2; + + WRW_HARPOON(map_addr, (SSI_OP+ SSI_IUNKWN)); /*RECEIVED UNKNOWN MSG BYTE */ + map_addr +=2; + WRW_HARPOON(map_addr, (SSI_OP+ SSI_INO_CC)); /*NO COMMAND COMPLETE AFTER STATUS */ + map_addr +=2; + WRW_HARPOON(map_addr, (SSI_OP+ SSI_ITICKLE)); /*BIOS Tickled the Mgr */ + map_addr +=2; + WRW_HARPOON(map_addr, (SSI_OP+ SSI_IRFAIL)); /*EXPECTED ID/TAG MESSAGES AND */ + map_addr +=2; /* DIDN'T GET ONE */ + WRW_HARPOON(map_addr, (CRR_OP+AR3+ S_IDREG)); /* comp SCSI SEL ID & AR3*/ + map_addr +=2; + WRW_HARPOON(map_addr, (BRH_OP+EQUAL+ 0x00)); /*SEL ID OK then Conti. */ + map_addr +=2; + WRW_HARPOON(map_addr, (SSI_OP+ SSI_INO_CC)); /*NO COMMAND COMPLETE AFTER STATUS */ + + + + SGRAM_ACCESS(p_port); +} + +/*--------------------------------------------------------------------- + * + * Function: Auto Command Complete + * + * Description: Post command back to host and find another command + * to execute. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void autoCmdCmplt(USHORT p_port, UCHAR p_card) +#else +void autoCmdCmplt(ULONG p_port, UCHAR p_card) +#endif +{ + PSCCB currSCCB; + UCHAR status_byte; + + currSCCB = BL_Card[p_card].currentSCCB; + + status_byte = RD_HARPOON(p_port+hp_gp_reg_0); + + sccbMgrTbl[p_card][currSCCB->TargID].TarLUN_CA = FALSE; + + if (status_byte != SSGOOD) { + + if (status_byte == SSQ_FULL) { + + + if(((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = TRUE; + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[currSCCB->Lun]] = NULL; + } + else + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = TRUE; + if(currSCCB->Sccb_tag) + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[currSCCB->Sccb_tag] = NULL; + }else + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[0]] = NULL; + } + } + + currSCCB->Sccb_MGRFlags |= F_STATUSLOADED; + + queueSelectFail(&BL_Card[p_card],p_card); + + return; + } + + if(currSCCB->Sccb_scsistat == SELECT_SN_ST) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus |= + (UCHAR)SYNC_SUPPORTED; + + sccbMgrTbl[p_card][currSCCB->TargID].TarEEValue &= ~EE_SYNC_MASK; + BL_Card[p_card].globalFlags |= F_NEW_SCCB_CMD; + + if(((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = TRUE; + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[currSCCB->Lun]] = NULL; + } + else + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = TRUE; + if(currSCCB->Sccb_tag) + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[currSCCB->Sccb_tag] = NULL; + }else + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[0]] = NULL; + } + } + return; + + } + + if(currSCCB->Sccb_scsistat == SELECT_WN_ST) + { + + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus = + (sccbMgrTbl[p_card][currSCCB->TargID]. + TarStatus & ~WIDE_ENABLED) | WIDE_NEGOCIATED; + + sccbMgrTbl[p_card][currSCCB->TargID].TarEEValue &= ~EE_WIDE_SCSI; + BL_Card[p_card].globalFlags |= F_NEW_SCCB_CMD; + + if(((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = TRUE; + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[currSCCB->Lun]] = NULL; + } + else + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = TRUE; + if(currSCCB->Sccb_tag) + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[currSCCB->Sccb_tag] = NULL; + }else + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[0]] = NULL; + } + } + return; + + } + + if (status_byte == SSCHECK) + { + if(BL_Card[p_card].globalFlags & F_DO_RENEGO) + { + if (sccbMgrTbl[p_card][currSCCB->TargID].TarEEValue & EE_SYNC_MASK) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus &= ~TAR_SYNC_MASK; + } + if (sccbMgrTbl[p_card][currSCCB->TargID].TarEEValue & EE_WIDE_SCSI) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarStatus &= ~TAR_WIDE_MASK; + } + } + } + + if (!(currSCCB->Sccb_XferState & F_AUTO_SENSE)) { + + currSCCB->SccbStatus = SCCB_ERROR; + currSCCB->TargetStatus = status_byte; + + if (status_byte == SSCHECK) { + + sccbMgrTbl[p_card][currSCCB->TargID].TarLUN_CA + = TRUE; + + +#if (FW_TYPE==_SCCB_MGR_) + if (currSCCB->RequestSenseLength != NO_AUTO_REQUEST_SENSE) { + + if (currSCCB->RequestSenseLength == 0) + currSCCB->RequestSenseLength = 14; + + ssenss(&BL_Card[p_card]); + BL_Card[p_card].globalFlags |= F_NEW_SCCB_CMD; + + if(((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = TRUE; + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[currSCCB->Lun]] = NULL; + } + else + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = TRUE; + if(currSCCB->Sccb_tag) + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[currSCCB->Sccb_tag] = NULL; + }else + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[0]] = NULL; + } + } + return; + } +#else + if ((!(currSCCB->Sccb_ucb_ptr->UCB_opcode & OPC_NO_AUTO_SENSE)) && + (currSCCB->RequestSenseLength)) + { + ssenss(&BL_Card[p_card]); + BL_Card[p_card].globalFlags |= F_NEW_SCCB_CMD; + + if(((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = TRUE; + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[currSCCB->Lun]] = NULL; + } + else + { + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = TRUE; + if(currSCCB->Sccb_tag) + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[currSCCB->Sccb_tag] = NULL; + }else + { + if(BL_Card[p_card].discQCount != 0) + BL_Card[p_card].discQCount--; + BL_Card[p_card].discQ_Tbl[sccbMgrTbl[p_card][currSCCB->TargID].LunDiscQ_Idx[0]] = NULL; + } + } + return; + } + +#endif + } + } + } + + + if((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((sccbMgrTbl[p_card][currSCCB->TargID].TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[currSCCB->Lun] = FALSE; + else + sccbMgrTbl[p_card][currSCCB->TargID].TarLUNBusy[0] = FALSE; + + + queueCmdComplete(&BL_Card[p_card], currSCCB, p_card); +} +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: busmstr.c $ + * + * Description: Functions to start, stop, and abort BusMaster operations. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <scsi2.h>*/ +/*#include <harpoon.h>*/ + + +/* +extern SCCBCARD BL_Card[MAX_CARDS]; +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; +*/ + +#define SHORT_WAIT 0x0000000F +#define LONG_WAIT 0x0000FFFFL + +#if defined(BUGBUG) +void Debug_Load(UCHAR p_card, UCHAR p_bug_data); +#endif + +/*--------------------------------------------------------------------- + * + * Function: Data Transfer Processor + * + * Description: This routine performs two tasks. + * (1) Start data transfer by calling HOST_DATA_XFER_START + * function. Once data transfer is started, (2) Depends + * on the type of data transfer mode Scatter/Gather mode + * or NON Scatter/Gather mode. In NON Scatter/Gather mode, + * this routine checks Sccb_MGRFlag (F_HOST_XFER_ACT bit) for + * data transfer done. In Scatter/Gather mode, this routine + * checks bus master command complete and dual rank busy + * bit to keep chaining SC transfer command. Similarly, + * in Scatter/Gather mode, it checks Sccb_MGRFlag + * (F_HOST_XFER_ACT bit) for data transfer done. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void dataXferProcessor(USHORT port, PSCCBcard pCurrCard) +#else +void dataXferProcessor(ULONG port, PSCCBcard pCurrCard) +#endif +{ + PSCCB currSCCB; + + currSCCB = pCurrCard->currentSCCB; + + if (currSCCB->Sccb_XferState & F_SG_XFER) + { + if (pCurrCard->globalFlags & F_HOST_XFER_ACT) + + { + currSCCB->Sccb_sgseg += (UCHAR)SG_BUF_CNT; + currSCCB->Sccb_SGoffset = 0x00; + } + pCurrCard->globalFlags |= F_HOST_XFER_ACT; + + busMstrSGDataXferStart(port, currSCCB); + } + + else + { + if (!(pCurrCard->globalFlags & F_HOST_XFER_ACT)) + { + pCurrCard->globalFlags |= F_HOST_XFER_ACT; + + busMstrDataXferStart(port, currSCCB); + } + } +} + + +/*--------------------------------------------------------------------- + * + * Function: BusMaster Scatter Gather Data Transfer Start + * + * Description: + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void busMstrSGDataXferStart(USHORT p_port, PSCCB pcurrSCCB) +#else +void busMstrSGDataXferStart(ULONG p_port, PSCCB pcurrSCCB) +#endif +{ + ULONG count,addr,tmpSGCnt; + UINT sg_index; + UCHAR sg_count, i; +#if defined(DOS) + USHORT reg_offset; +#else + ULONG reg_offset; +#endif + + + if (pcurrSCCB->Sccb_XferState & F_HOST_XFER_DIR) { + + count = ((ULONG) HOST_RD_CMD)<<24; + } + + else { + count = ((ULONG) HOST_WRT_CMD)<<24; + } + + sg_count = 0; + tmpSGCnt = 0; + sg_index = pcurrSCCB->Sccb_sgseg; + reg_offset = hp_aramBase; + + + i = (UCHAR) (RD_HARPOON(p_port+hp_page_ctrl) & ~(SGRAM_ARAM|SCATTER_EN)); + + + WR_HARPOON(p_port+hp_page_ctrl, i); + + while ((sg_count < (UCHAR)SG_BUF_CNT) && + ((ULONG)(sg_index * (UINT)SG_ELEMENT_SIZE) < pcurrSCCB->DataLength) ) { + +#if defined(COMPILER_16_BIT) && !defined(DOS) + tmpSGCnt += *(((ULONG far *)pcurrSCCB->DataPointer)+ + (sg_index * 2)); + + count |= *(((ULONG far *)pcurrSCCB->DataPointer)+ + (sg_index * 2)); + + addr = *(((ULONG far *)pcurrSCCB->DataPointer)+ + ((sg_index * 2) + 1)); + +#else + tmpSGCnt += *(((ULONG *)pcurrSCCB->DataPointer)+ + (sg_index * 2)); + + count |= *(((ULONG *)pcurrSCCB->DataPointer)+ + (sg_index * 2)); + + addr = *(((ULONG *)pcurrSCCB->DataPointer)+ + ((sg_index * 2) + 1)); +#endif + + + if ((!sg_count) && (pcurrSCCB->Sccb_SGoffset)) { + + addr += ((count & 0x00FFFFFFL) - pcurrSCCB->Sccb_SGoffset); + count = (count & 0xFF000000L) | pcurrSCCB->Sccb_SGoffset; + + tmpSGCnt = count & 0x00FFFFFFL; + } + + WR_HARP32(p_port,reg_offset,addr); + reg_offset +=4; + + WR_HARP32(p_port,reg_offset,count); + reg_offset +=4; + + count &= 0xFF000000L; + sg_index++; + sg_count++; + + } /*End While */ + + pcurrSCCB->Sccb_XferCnt = tmpSGCnt; + + WR_HARPOON(p_port+hp_sg_addr,(sg_count<<4)); + + if (pcurrSCCB->Sccb_XferState & F_HOST_XFER_DIR) { + + WR_HARP32(p_port,hp_xfercnt_0,tmpSGCnt); + + + WR_HARPOON(p_port+hp_portctrl_0,(DMA_PORT | SCSI_PORT | SCSI_INBIT)); + WR_HARPOON(p_port+hp_scsisig, S_DATAI_PH); + } + + else { + + + if ((!(RD_HARPOON(p_port+hp_synctarg_0) & NARROW_SCSI)) && + (tmpSGCnt & 0x000000001)) + { + + pcurrSCCB->Sccb_XferState |= F_ODD_BALL_CNT; + tmpSGCnt--; + } + + + WR_HARP32(p_port,hp_xfercnt_0,tmpSGCnt); + + WR_HARPOON(p_port+hp_portctrl_0,(SCSI_PORT | DMA_PORT | DMA_RD)); + WR_HARPOON(p_port+hp_scsisig, S_DATAO_PH); + } + + + WR_HARPOON(p_port+hp_page_ctrl, (UCHAR) (i | SCATTER_EN)); + +} + + +/*--------------------------------------------------------------------- + * + * Function: BusMaster Data Transfer Start + * + * Description: + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void busMstrDataXferStart(USHORT p_port, PSCCB pcurrSCCB) +#else +void busMstrDataXferStart(ULONG p_port, PSCCB pcurrSCCB) +#endif +{ + ULONG addr,count; + + if (!(pcurrSCCB->Sccb_XferState & F_AUTO_SENSE)) { + + count = pcurrSCCB->Sccb_XferCnt; + + addr = (ULONG) pcurrSCCB->DataPointer + pcurrSCCB->Sccb_ATC; + } + + else { + addr = pcurrSCCB->SensePointer; + count = pcurrSCCB->RequestSenseLength; + + } + +#if defined(DOS) + asm { mov dx,p_port; + mov ax,word ptr count; + add dx,hp_xfer_cnt_lo; + out dx,al; + inc dx; + xchg ah,al + out dx,al; + inc dx; + mov ax,word ptr count+2; + out dx,al; + inc dx; + inc dx; + mov ax,word ptr addr; + out dx,al; + inc dx; + xchg ah,al + out dx,al; + inc dx; + mov ax,word ptr addr+2; + out dx,al; + inc dx; + xchg ah,al + out dx,al; + } + + WR_HARP32(p_port,hp_xfercnt_0,count); + +#else + HP_SETUP_ADDR_CNT(p_port,addr,count); +#endif + + + if (pcurrSCCB->Sccb_XferState & F_HOST_XFER_DIR) { + + WR_HARPOON(p_port+hp_portctrl_0,(DMA_PORT | SCSI_PORT | SCSI_INBIT)); + WR_HARPOON(p_port+hp_scsisig, S_DATAI_PH); + + WR_HARPOON(p_port+hp_xfer_cmd, + (XFER_DMA_HOST | XFER_HOST_AUTO | XFER_DMA_8BIT)); + } + + else { + + WR_HARPOON(p_port+hp_portctrl_0,(SCSI_PORT | DMA_PORT | DMA_RD)); + WR_HARPOON(p_port+hp_scsisig, S_DATAO_PH); + + WR_HARPOON(p_port+hp_xfer_cmd, + (XFER_HOST_DMA | XFER_HOST_AUTO | XFER_DMA_8BIT)); + + } +} + + +/*--------------------------------------------------------------------- + * + * Function: BusMaster Timeout Handler + * + * Description: This function is called after a bus master command busy time + * out is detected. This routines issue halt state machine + * with a software time out for command busy. If command busy + * is still asserted at the end of the time out, it issues + * hard abort with another software time out. It hard abort + * command busy is also time out, it'll just give up. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +UCHAR busMstrTimeOut(USHORT p_port) +#else +UCHAR busMstrTimeOut(ULONG p_port) +#endif +{ + ULONG timeout; + + timeout = LONG_WAIT; + + WR_HARPOON(p_port+hp_sys_ctrl, HALT_MACH); + + while ((!(RD_HARPOON(p_port+hp_ext_status) & CMD_ABORTED)) && timeout--) {} + + + + if (RD_HARPOON(p_port+hp_ext_status) & BM_CMD_BUSY) { + WR_HARPOON(p_port+hp_sys_ctrl, HARD_ABORT); + + timeout = LONG_WAIT; + while ((RD_HARPOON(p_port+hp_ext_status) & BM_CMD_BUSY) && timeout--) {} + } + + RD_HARPOON(p_port+hp_int_status); /*Clear command complete */ + + if (RD_HARPOON(p_port+hp_ext_status) & BM_CMD_BUSY) { + return(TRUE); + } + + else { + return(FALSE); + } +} + + +/*--------------------------------------------------------------------- + * + * Function: Host Data Transfer Abort + * + * Description: Abort any in progress transfer. + * + *---------------------------------------------------------------------*/ +#if defined(DOS) +void hostDataXferAbort(USHORT port, UCHAR p_card, PSCCB pCurrSCCB) +#else +void hostDataXferAbort(ULONG port, UCHAR p_card, PSCCB pCurrSCCB) +#endif +{ + + ULONG timeout; + ULONG remain_cnt; + UINT sg_ptr; + + BL_Card[p_card].globalFlags &= ~F_HOST_XFER_ACT; + + if (pCurrSCCB->Sccb_XferState & F_AUTO_SENSE) { + + + if (!(RD_HARPOON(port+hp_int_status) & INT_CMD_COMPL)) { + + WR_HARPOON(port+hp_bm_ctrl, (RD_HARPOON(port+hp_bm_ctrl) | FLUSH_XFER_CNTR)); + timeout = LONG_WAIT; + + while ((RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) && timeout--) {} + + WR_HARPOON(port+hp_bm_ctrl, (RD_HARPOON(port+hp_bm_ctrl) & ~FLUSH_XFER_CNTR)); + + if (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) { + + if (busMstrTimeOut(port)) { + + if (pCurrSCCB->HostStatus == 0x00) + + pCurrSCCB->HostStatus = SCCB_BM_ERR; + + } + + if (RD_HARPOON(port+hp_int_status) & INT_EXT_STATUS) + + if (RD_HARPOON(port+hp_ext_status) & BAD_EXT_STATUS) + + if (pCurrSCCB->HostStatus == 0x00) + + { + pCurrSCCB->HostStatus = SCCB_BM_ERR; +#if defined(BUGBUG) + WR_HARPOON(port+hp_dual_addr_lo, + RD_HARPOON(port+hp_ext_status)); +#endif + } + } + } + } + + else if (pCurrSCCB->Sccb_XferCnt) { + + if (pCurrSCCB->Sccb_XferState & F_SG_XFER) { + + + WR_HARPOON(port+hp_page_ctrl, (RD_HARPOON(port+hp_page_ctrl) & + ~SCATTER_EN)); + + WR_HARPOON(port+hp_sg_addr,0x00); + + sg_ptr = pCurrSCCB->Sccb_sgseg + SG_BUF_CNT; + + if (sg_ptr > (UINT)(pCurrSCCB->DataLength / SG_ELEMENT_SIZE)) { + + sg_ptr = (UINT)(pCurrSCCB->DataLength / SG_ELEMENT_SIZE); + } + + remain_cnt = pCurrSCCB->Sccb_XferCnt; + + while (remain_cnt < 0x01000000L) { + + sg_ptr--; + +#if defined(COMPILER_16_BIT) && !defined(DOS) + if (remain_cnt > (ULONG)(*(((ULONG far *)pCurrSCCB-> + DataPointer) + (sg_ptr * 2)))) { + + remain_cnt -= (ULONG)(*(((ULONG far *)pCurrSCCB-> + DataPointer) + (sg_ptr * 2))); + } + +#else + if (remain_cnt > (ULONG)(*(((ULONG *)pCurrSCCB-> + DataPointer) + (sg_ptr * 2)))) { + + remain_cnt -= (ULONG)(*(((ULONG *)pCurrSCCB-> + DataPointer) + (sg_ptr * 2))); + } +#endif + + else { + + break; + } + } + + + + if (remain_cnt < 0x01000000L) { + + + pCurrSCCB->Sccb_SGoffset = remain_cnt; + + pCurrSCCB->Sccb_sgseg = (USHORT)sg_ptr; + + + if ((ULONG)(sg_ptr * SG_ELEMENT_SIZE) == pCurrSCCB->DataLength + && (remain_cnt == 0)) + + pCurrSCCB->Sccb_XferState |= F_ALL_XFERRED; + } + + else { + + + if (pCurrSCCB->HostStatus == 0x00) { + + pCurrSCCB->HostStatus = SCCB_GROSS_FW_ERR; + } + } + } + + + if (!(pCurrSCCB->Sccb_XferState & F_HOST_XFER_DIR)) { + + + if (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) { + + busMstrTimeOut(port); + } + + else { + + if (RD_HARPOON(port+hp_int_status) & INT_EXT_STATUS) { + + if (RD_HARPOON(port+hp_ext_status) & BAD_EXT_STATUS) { + + if (pCurrSCCB->HostStatus == 0x00) { + + pCurrSCCB->HostStatus = SCCB_BM_ERR; +#if defined(BUGBUG) + WR_HARPOON(port+hp_dual_addr_lo, + RD_HARPOON(port+hp_ext_status)); +#endif + } + } + } + + } + } + + else { + + + if ((RD_HARPOON(port+hp_fifo_cnt)) >= BM_THRESHOLD) { + + timeout = SHORT_WAIT; + + while ((RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) && + ((RD_HARPOON(port+hp_fifo_cnt)) >= BM_THRESHOLD) && + timeout--) {} + } + + if (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) { + + WR_HARPOON(port+hp_bm_ctrl, (RD_HARPOON(port+hp_bm_ctrl) | + FLUSH_XFER_CNTR)); + + timeout = LONG_WAIT; + + while ((RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) && + timeout--) {} + + WR_HARPOON(port+hp_bm_ctrl, (RD_HARPOON(port+hp_bm_ctrl) & + ~FLUSH_XFER_CNTR)); + + + if (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) { + + if (pCurrSCCB->HostStatus == 0x00) { + + pCurrSCCB->HostStatus = SCCB_BM_ERR; + } + + busMstrTimeOut(port); + } + } + + if (RD_HARPOON(port+hp_int_status) & INT_EXT_STATUS) { + + if (RD_HARPOON(port+hp_ext_status) & BAD_EXT_STATUS) { + + if (pCurrSCCB->HostStatus == 0x00) { + + pCurrSCCB->HostStatus = SCCB_BM_ERR; +#if defined(BUGBUG) + WR_HARPOON(port+hp_dual_addr_lo, + RD_HARPOON(port+hp_ext_status)); +#endif + } + } + } + } + + } + + else { + + + if (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) { + + timeout = LONG_WAIT; + + while ((RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) && timeout--) {} + + if (RD_HARPOON(port+hp_ext_status) & BM_CMD_BUSY) { + + if (pCurrSCCB->HostStatus == 0x00) { + + pCurrSCCB->HostStatus = SCCB_BM_ERR; + } + + busMstrTimeOut(port); + } + } + + + if (RD_HARPOON(port+hp_int_status) & INT_EXT_STATUS) { + + if (RD_HARPOON(port+hp_ext_status) & BAD_EXT_STATUS) { + + if (pCurrSCCB->HostStatus == 0x00) { + + pCurrSCCB->HostStatus = SCCB_BM_ERR; +#if defined(BUGBUG) + WR_HARPOON(port+hp_dual_addr_lo, + RD_HARPOON(port+hp_ext_status)); +#endif + } + } + + } + + if (pCurrSCCB->Sccb_XferState & F_SG_XFER) { + + WR_HARPOON(port+hp_page_ctrl, (RD_HARPOON(port+hp_page_ctrl) & + ~SCATTER_EN)); + + WR_HARPOON(port+hp_sg_addr,0x00); + + pCurrSCCB->Sccb_sgseg += SG_BUF_CNT; + + pCurrSCCB->Sccb_SGoffset = 0x00; + + + if ((ULONG)(pCurrSCCB->Sccb_sgseg * SG_ELEMENT_SIZE) >= + pCurrSCCB->DataLength) { + + pCurrSCCB->Sccb_XferState |= F_ALL_XFERRED; + + pCurrSCCB->Sccb_sgseg = (USHORT)(pCurrSCCB->DataLength / SG_ELEMENT_SIZE); + + } + } + + else { + + if (!(pCurrSCCB->Sccb_XferState & F_AUTO_SENSE)) + + pCurrSCCB->Sccb_XferState |= F_ALL_XFERRED; + } + } + + WR_HARPOON(port+hp_int_mask,(INT_CMD_COMPL | SCSI_INTERRUPT)); +} + + + +/*--------------------------------------------------------------------- + * + * Function: Host Data Transfer Restart + * + * Description: Reset the available count due to a restore data + * pointers message. + * + *---------------------------------------------------------------------*/ +void hostDataXferRestart(PSCCB currSCCB) +{ + ULONG data_count; + UINT sg_index; +#if defined(COMPILER_16_BIT) && !defined(DOS) + ULONG far *sg_ptr; +#else + ULONG *sg_ptr; +#endif + + if (currSCCB->Sccb_XferState & F_SG_XFER) { + + currSCCB->Sccb_XferCnt = 0; + + sg_index = 0xffff; /*Index by long words into sg list. */ + data_count = 0; /*Running count of SG xfer counts. */ + +#if defined(COMPILER_16_BIT) && !defined(DOS) + sg_ptr = (ULONG far *)currSCCB->DataPointer; +#else + sg_ptr = (ULONG *)currSCCB->DataPointer; +#endif + + while (data_count < currSCCB->Sccb_ATC) { + + sg_index++; + data_count += *(sg_ptr+(sg_index * 2)); + } + + if (data_count == currSCCB->Sccb_ATC) { + + currSCCB->Sccb_SGoffset = 0; + sg_index++; + } + + else { + currSCCB->Sccb_SGoffset = data_count - currSCCB->Sccb_ATC; + } + + currSCCB->Sccb_sgseg = (USHORT)sg_index; + } + + else { + currSCCB->Sccb_XferCnt = currSCCB->DataLength - currSCCB->Sccb_ATC; + } +} +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: scam.c $ + * + * Description: Functions relating to handling of the SCAM selection + * and the determination of the SCSI IDs to be assigned + * to all perspective SCSI targets. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <scsi2.h>*/ +/*#include <eeprom.h>*/ +/*#include <harpoon.h>*/ + + + +/* +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; +extern SCCBCARD BL_Card[MAX_CARDS]; +extern SCCBSCAM_INFO scamInfo[MAX_SCSI_TAR]; +extern NVRAMINFO nvRamInfo[MAX_MB_CARDS]; +#if defined(DOS) || defined(OS2) +extern UCHAR temp_id_string[ID_STRING_LENGTH]; +#endif +extern UCHAR scamHAString[]; +*/ +/*--------------------------------------------------------------------- + * + * Function: scini + * + * Description: Setup all data structures necessary for SCAM selection. + * + *---------------------------------------------------------------------*/ + +void scini(UCHAR p_card, UCHAR p_our_id, UCHAR p_power_up) +{ + +#if defined(SCAM_LEV_2) + UCHAR loser,assigned_id; +#endif +#if defined(DOS) + + USHORT p_port; +#else + ULONG p_port; +#endif + + UCHAR i,k,ScamFlg ; + PSCCBcard currCard; + PNVRamInfo pCurrNvRam; + + currCard = &BL_Card[p_card]; + p_port = currCard->ioPort; + pCurrNvRam = currCard->pNvRamInfo; + + + if(pCurrNvRam){ + ScamFlg = pCurrNvRam->niScamConf; + i = pCurrNvRam->niSysConf; + } + else{ + ScamFlg = (UCHAR) utilEERead(p_port, SCAM_CONFIG/2); + i = (UCHAR)(utilEERead(p_port, (SYSTEM_CONFIG/2))); + } + if(!(i & 0x02)) /* check if reset bus in AutoSCSI parameter set */ + return; + + inisci(p_card,p_port, p_our_id); + + /* Force to wait 1 sec after SCSI bus reset. Some SCAM device FW + too slow to return to SCAM selection */ + + /* if (p_power_up) + Wait1Second(p_port); + else + Wait(p_port, TO_250ms); */ + + Wait1Second(p_port); + +#if defined(SCAM_LEV_2) + + if ((ScamFlg & SCAM_ENABLED) && (ScamFlg & SCAM_LEVEL2)) + { + while (!(scarb(p_port,INIT_SELTD))) {} + + scsel(p_port); + + do { + scxferc(p_port,SYNC_PTRN); + scxferc(p_port,DOM_MSTR); + loser = scsendi(p_port,&scamInfo[p_our_id].id_string[0]); + } while ( loser == 0xFF ); + + scbusf(p_port); + + if ((p_power_up) && (!loser)) + { + sresb(p_port,p_card); + Wait(p_port, TO_250ms); + + while (!(scarb(p_port,INIT_SELTD))) {} + + scsel(p_port); + + do { + scxferc(p_port, SYNC_PTRN); + scxferc(p_port, DOM_MSTR); + loser = scsendi(p_port,&scamInfo[p_our_id]. + id_string[0]); + } while ( loser == 0xFF ); + + scbusf(p_port); + } + } + + else + { + loser = FALSE; + } + + + if (!loser) + { + +#endif /* SCAM_LEV_2 */ + + scamInfo[p_our_id].state = ID_ASSIGNED; + + + if (ScamFlg & SCAM_ENABLED) + { + + for (i=0; i < MAX_SCSI_TAR; i++) + { + if ((scamInfo[i].state == ID_UNASSIGNED) || + (scamInfo[i].state == ID_UNUSED)) + { + if (scsell(p_port,i)) + { + scamInfo[i].state = LEGACY; + if ((scamInfo[i].id_string[0] != 0xFF) || + (scamInfo[i].id_string[1] != 0xFA)) + { + + scamInfo[i].id_string[0] = 0xFF; + scamInfo[i].id_string[1] = 0xFA; + if(pCurrNvRam == NULL) + currCard->globalFlags |= F_UPDATE_EEPROM; + } + } + } + } + + sresb(p_port,p_card); + Wait1Second(p_port); + while (!(scarb(p_port,INIT_SELTD))) {} + scsel(p_port); + scasid(p_card, p_port); + } + +#if defined(SCAM_LEV_2) + + } + + else if ((loser) && (ScamFlg & SCAM_ENABLED)) + { + scamInfo[p_our_id].id_string[0] = SLV_TYPE_CODE0; + assigned_id = FALSE; + scwtsel(p_port); + + do { + while (scxferc(p_port,0x00) != SYNC_PTRN) {} + + i = scxferc(p_port,0x00); + if (i == ASSIGN_ID) + { + if (!(scsendi(p_port,&scamInfo[p_our_id].id_string[0]))) + { + i = scxferc(p_port,0x00); + if (scvalq(i)) + { + k = scxferc(p_port,0x00); + + if (scvalq(k)) + { + currCard->ourId = + ((UCHAR)(i<<3)+(k & (UCHAR)7)) & (UCHAR) 0x3F; + inisci(p_card, p_port, p_our_id); + scamInfo[currCard->ourId].state = ID_ASSIGNED; + scamInfo[currCard->ourId].id_string[0] + = SLV_TYPE_CODE0; + assigned_id = TRUE; + } + } + } + } + + else if (i == SET_P_FLAG) + { + if (!(scsendi(p_port, + &scamInfo[p_our_id].id_string[0]))) + scamInfo[p_our_id].id_string[0] |= 0x80; + } + }while (!assigned_id); + + while (scxferc(p_port,0x00) != CFG_CMPLT) {} + } + +#endif /* SCAM_LEV_2 */ + if (ScamFlg & SCAM_ENABLED) + { + scbusf(p_port); + if (currCard->globalFlags & F_UPDATE_EEPROM) + { + scsavdi(p_card, p_port); + currCard->globalFlags &= ~F_UPDATE_EEPROM; + } + } + + +#if defined(DOS) + for (i=0; i < MAX_SCSI_TAR; i++) + { + if (((ScamFlg & SCAM_ENABLED) && (scamInfo[i].state == LEGACY)) + || (i != p_our_id)) + { + scsellDOS(p_port,i); + } + } +#endif + +/* + for (i=0,k=0; i < MAX_SCSI_TAR; i++) + { + if ((scamInfo[i].state == ID_ASSIGNED) || + (scamInfo[i].state == LEGACY)) + k++; + } + + if (k==2) + currCard->globalFlags |= F_SINGLE_DEVICE; + else + currCard->globalFlags &= ~F_SINGLE_DEVICE; +*/ +} + + +/*--------------------------------------------------------------------- + * + * Function: scarb + * + * Description: Gain control of the bus and wait SCAM select time (250ms) + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +int scarb(USHORT p_port, UCHAR p_sel_type) +#else +int scarb(ULONG p_port, UCHAR p_sel_type) +#endif +{ + if (p_sel_type == INIT_SELTD) + { + + while (RD_HARPOON(p_port+hp_scsisig) & (SCSI_SEL | SCSI_BSY)) {} + + + if (RD_HARPOON(p_port+hp_scsisig) & SCSI_SEL) + return(FALSE); + + if (RD_HARPOON(p_port+hp_scsidata_0) != 00) + return(FALSE); + + WR_HARPOON(p_port+hp_scsisig, (RD_HARPOON(p_port+hp_scsisig) | SCSI_BSY)); + + if (RD_HARPOON(p_port+hp_scsisig) & SCSI_SEL) { + + WR_HARPOON(p_port+hp_scsisig, (RD_HARPOON(p_port+hp_scsisig) & + ~SCSI_BSY)); + return(FALSE); + } + + + WR_HARPOON(p_port+hp_scsisig, (RD_HARPOON(p_port+hp_scsisig) | SCSI_SEL)); + + if (RD_HARPOON(p_port+hp_scsidata_0) != 00) { + + WR_HARPOON(p_port+hp_scsisig, (RD_HARPOON(p_port+hp_scsisig) & + ~(SCSI_BSY | SCSI_SEL))); + return(FALSE); + } + } + + + WR_HARPOON(p_port+hp_clkctrl_0, (RD_HARPOON(p_port+hp_clkctrl_0) + & ~ACTdeassert)); + WR_HARPOON(p_port+hp_scsireset, SCAM_EN); + WR_HARPOON(p_port+hp_scsidata_0, 0x00); +#if defined(WIDE_SCSI) + WR_HARPOON(p_port+hp_scsidata_1, 0x00); +#endif + WR_HARPOON(p_port+hp_portctrl_0, SCSI_BUS_EN); + + WR_HARPOON(p_port+hp_scsisig, (RD_HARPOON(p_port+hp_scsisig) | SCSI_MSG)); + + WR_HARPOON(p_port+hp_scsisig, (RD_HARPOON(p_port+hp_scsisig) + & ~SCSI_BSY)); + + Wait(p_port,TO_250ms); + + return(TRUE); +} + + +/*--------------------------------------------------------------------- + * + * Function: scbusf + * + * Description: Release the SCSI bus and disable SCAM selection. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void scbusf(USHORT p_port) +#else +void scbusf(ULONG p_port) +#endif +{ + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) | G_INT_DISABLE)); + + + WR_HARPOON(p_port+hp_scsidata_0, 0x00); + + WR_HARPOON(p_port+hp_portctrl_0, (RD_HARPOON(p_port+hp_portctrl_0) + & ~SCSI_BUS_EN)); + + WR_HARPOON(p_port+hp_scsisig, 0x00); + + + WR_HARPOON(p_port+hp_scsireset, (RD_HARPOON(p_port+hp_scsireset) + & ~SCAM_EN)); + + WR_HARPOON(p_port+hp_clkctrl_0, (RD_HARPOON(p_port+hp_clkctrl_0) + | ACTdeassert)); + +#if defined(SCAM_LEV_2) + WRW_HARPOON((p_port+hp_intstat), (BUS_FREE | AUTO_INT | SCAM_SEL)); +#else + WRW_HARPOON((p_port+hp_intstat), (BUS_FREE | AUTO_INT)); +#endif + + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) & ~G_INT_DISABLE)); +} + + + +/*--------------------------------------------------------------------- + * + * Function: scasid + * + * Description: Assign an ID to all the SCAM devices. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void scasid(UCHAR p_card, USHORT p_port) +#else +void scasid(UCHAR p_card, ULONG p_port) +#endif +{ +#if defined(DOS) || defined(OS2) + /* Use external defined in global space area, instead of Stack + space. WIN/95 DOS doesnot work TINY mode. The OS doesnot intialize + SS equal to DS. Thus the array allocated on stack doesnot get + access correctly */ +#else + UCHAR temp_id_string[ID_STRING_LENGTH]; +#endif + + UCHAR i,k,scam_id; + UCHAR crcBytes[3]; + PNVRamInfo pCurrNvRam; + ushort_ptr pCrcBytes; + + pCurrNvRam = BL_Card[p_card].pNvRamInfo; + + i=FALSE; + + while (!i) + { + + for (k=0; k < ID_STRING_LENGTH; k++) + { + temp_id_string[k] = (UCHAR) 0x00; + } + + scxferc(p_port,SYNC_PTRN); + scxferc(p_port,ASSIGN_ID); + + if (!(sciso(p_port,&temp_id_string[0]))) + { + if(pCurrNvRam){ + pCrcBytes = (ushort_ptr)&crcBytes[0]; + *pCrcBytes = CalcCrc16(&temp_id_string[0]); + crcBytes[2] = CalcLrc(&temp_id_string[0]); + temp_id_string[1] = crcBytes[2]; + temp_id_string[2] = crcBytes[0]; + temp_id_string[3] = crcBytes[1]; + for(k = 4; k < ID_STRING_LENGTH; k++) + temp_id_string[k] = (UCHAR) 0x00; + } + i = scmachid(p_card,temp_id_string); + + if (i == CLR_PRIORITY) + { + scxferc(p_port,MISC_CODE); + scxferc(p_port,CLR_P_FLAG); + i = FALSE; /*Not the last ID yet. */ + } + + else if (i != NO_ID_AVAIL) + { + if (i < 8 ) + scxferc(p_port,ID_0_7); + else + scxferc(p_port,ID_8_F); + + scam_id = (i & (UCHAR) 0x07); + + + for (k=1; k < 0x08; k <<= 1) + if (!( k & i )) + scam_id += 0x08; /*Count number of zeros in DB0-3. */ + + scxferc(p_port,scam_id); + + i = FALSE; /*Not the last ID yet. */ + } + } + + else + { + i = TRUE; + } + + } /*End while */ + + scxferc(p_port,SYNC_PTRN); + scxferc(p_port,CFG_CMPLT); +} + + + + + +/*--------------------------------------------------------------------- + * + * Function: scsel + * + * Description: Select all the SCAM devices. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void scsel(USHORT p_port) +#else +void scsel(ULONG p_port) +#endif +{ + + WR_HARPOON(p_port+hp_scsisig, SCSI_SEL); + scwiros(p_port, SCSI_MSG); + + WR_HARPOON(p_port+hp_scsisig, (SCSI_SEL | SCSI_BSY)); + + + WR_HARPOON(p_port+hp_scsisig, (SCSI_SEL | SCSI_BSY | SCSI_IOBIT | SCSI_CD)); + WR_HARPOON(p_port+hp_scsidata_0, (UCHAR)(RD_HARPOON(p_port+hp_scsidata_0) | + (UCHAR)(BIT(7)+BIT(6)))); + + + WR_HARPOON(p_port+hp_scsisig, (SCSI_BSY | SCSI_IOBIT | SCSI_CD)); + scwiros(p_port, SCSI_SEL); + + WR_HARPOON(p_port+hp_scsidata_0, (UCHAR)(RD_HARPOON(p_port+hp_scsidata_0) & + ~(UCHAR)BIT(6))); + scwirod(p_port, BIT(6)); + + WR_HARPOON(p_port+hp_scsisig, (SCSI_SEL | SCSI_BSY | SCSI_IOBIT | SCSI_CD)); +} + + + +/*--------------------------------------------------------------------- + * + * Function: scxferc + * + * Description: Handshake the p_data (DB4-0) across the bus. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +UCHAR scxferc(USHORT p_port, UCHAR p_data) +#else +UCHAR scxferc(ULONG p_port, UCHAR p_data) +#endif +{ + UCHAR curr_data, ret_data; + + curr_data = p_data | BIT(7) | BIT(5); /*Start with DB7 & DB5 asserted. */ + + WR_HARPOON(p_port+hp_scsidata_0, curr_data); + + curr_data &= ~BIT(7); + + WR_HARPOON(p_port+hp_scsidata_0, curr_data); + + scwirod(p_port,BIT(7)); /*Wait for DB7 to be released. */ + while (!(RD_HARPOON(p_port+hp_scsidata_0) & BIT(5))); + + ret_data = (RD_HARPOON(p_port+hp_scsidata_0) & (UCHAR) 0x1F); + + curr_data |= BIT(6); + + WR_HARPOON(p_port+hp_scsidata_0, curr_data); + + curr_data &= ~BIT(5); + + WR_HARPOON(p_port+hp_scsidata_0, curr_data); + + scwirod(p_port,BIT(5)); /*Wait for DB5 to be released. */ + + curr_data &= ~(BIT(4)|BIT(3)|BIT(2)|BIT(1)|BIT(0)); /*Release data bits */ + curr_data |= BIT(7); + + WR_HARPOON(p_port+hp_scsidata_0, curr_data); + + curr_data &= ~BIT(6); + + WR_HARPOON(p_port+hp_scsidata_0, curr_data); + + scwirod(p_port,BIT(6)); /*Wait for DB6 to be released. */ + + return(ret_data); +} + + +/*--------------------------------------------------------------------- + * + * Function: scsendi + * + * Description: Transfer our Identification string to determine if we + * will be the dominant master. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +UCHAR scsendi(USHORT p_port, UCHAR p_id_string[]) +#else +UCHAR scsendi(ULONG p_port, UCHAR p_id_string[]) +#endif +{ + UCHAR ret_data,byte_cnt,bit_cnt,defer; + + defer = FALSE; + + for (byte_cnt = 0; byte_cnt < ID_STRING_LENGTH; byte_cnt++) { + + for (bit_cnt = 0x80; bit_cnt != 0 ; bit_cnt >>= 1) { + + if (defer) + ret_data = scxferc(p_port,00); + + else if (p_id_string[byte_cnt] & bit_cnt) + + ret_data = scxferc(p_port,02); + + else { + + ret_data = scxferc(p_port,01); + if (ret_data & 02) + defer = TRUE; + } + + if ((ret_data & 0x1C) == 0x10) + return(0x00); /*End of isolation stage, we won! */ + + if (ret_data & 0x1C) + return(0xFF); + + if ((defer) && (!(ret_data & 0x1F))) + return(0x01); /*End of isolation stage, we lost. */ + + } /*bit loop */ + + } /*byte loop */ + + if (defer) + return(0x01); /*We lost */ + else + return(0); /*We WON! Yeeessss! */ +} + + + +/*--------------------------------------------------------------------- + * + * Function: sciso + * + * Description: Transfer the Identification string. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +UCHAR sciso(USHORT p_port, UCHAR p_id_string[]) +#else +UCHAR sciso(ULONG p_port, UCHAR p_id_string[]) +#endif +{ + UCHAR ret_data,the_data,byte_cnt,bit_cnt; + + the_data = 0; + + for (byte_cnt = 0; byte_cnt < ID_STRING_LENGTH; byte_cnt++) { + + for (bit_cnt = 0; bit_cnt < 8; bit_cnt++) { + + ret_data = scxferc(p_port,0); + + if (ret_data & 0xFC) + return(0xFF); + + else { + + the_data <<= 1; + if (ret_data & BIT(1)) { + the_data |= 1; + } + } + + if ((ret_data & 0x1F) == 0) + { +/* + if(bit_cnt != 0 || bit_cnt != 8) + { + byte_cnt = 0; + bit_cnt = 0; + scxferc(p_port, SYNC_PTRN); + scxferc(p_port, ASSIGN_ID); + continue; + } +*/ + if (byte_cnt) + return(0x00); + else + return(0xFF); + } + + } /*bit loop */ + + p_id_string[byte_cnt] = the_data; + + } /*byte loop */ + + return(0); +} + + + +/*--------------------------------------------------------------------- + * + * Function: scwirod + * + * Description: Sample the SCSI data bus making sure the signal has been + * deasserted for the correct number of consecutive samples. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void scwirod(USHORT p_port, UCHAR p_data_bit) +#else +void scwirod(ULONG p_port, UCHAR p_data_bit) +#endif +{ + UCHAR i; + + i = 0; + while ( i < MAX_SCSI_TAR ) { + + if (RD_HARPOON(p_port+hp_scsidata_0) & p_data_bit) + + i = 0; + + else + + i++; + + } +} + + + +/*--------------------------------------------------------------------- + * + * Function: scwiros + * + * Description: Sample the SCSI Signal lines making sure the signal has been + * deasserted for the correct number of consecutive samples. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void scwiros(USHORT p_port, UCHAR p_data_bit) +#else +void scwiros(ULONG p_port, UCHAR p_data_bit) +#endif +{ + UCHAR i; + + i = 0; + while ( i < MAX_SCSI_TAR ) { + + if (RD_HARPOON(p_port+hp_scsisig) & p_data_bit) + + i = 0; + + else + + i++; + + } +} + + +/*--------------------------------------------------------------------- + * + * Function: scvalq + * + * Description: Make sure we received a valid data byte. + * + *---------------------------------------------------------------------*/ + +UCHAR scvalq(UCHAR p_quintet) +{ + UCHAR count; + + for (count=1; count < 0x08; count<<=1) { + if (!(p_quintet & count)) + p_quintet -= 0x80; + } + + if (p_quintet & 0x18) + return(FALSE); + + else + return(TRUE); +} + + +/*--------------------------------------------------------------------- + * + * Function: scsell + * + * Description: Select the specified device ID using a selection timeout + * less than 4ms. If somebody responds then it is a legacy + * drive and this ID must be marked as such. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +UCHAR scsell(USHORT p_port, UCHAR targ_id) +#else +UCHAR scsell(ULONG p_port, UCHAR targ_id) +#endif +{ +#if defined(DOS) + USHORT i; +#else + ULONG i; +#endif + + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) | G_INT_DISABLE)); + + ARAM_ACCESS(p_port); + + WR_HARPOON(p_port+hp_addstat,(RD_HARPOON(p_port+hp_addstat) | SCAM_TIMER)); + WR_HARPOON(p_port+hp_seltimeout,TO_4ms); + + + for (i = p_port+CMD_STRT; i < p_port+CMD_STRT+12; i+=2) { + WRW_HARPOON(i, (MPM_OP+ACOMMAND)); + } + WRW_HARPOON(i, (BRH_OP+ALWAYS+ NP)); + + WRW_HARPOON((p_port+hp_intstat), + (RESET | TIMEOUT | SEL | BUS_FREE | AUTO_INT)); + + WR_HARPOON(p_port+hp_select_id, targ_id); + + WR_HARPOON(p_port+hp_portctrl_0, SCSI_PORT); + WR_HARPOON(p_port+hp_autostart_3, (SELECT | CMD_ONLY_STRT)); + WR_HARPOON(p_port+hp_scsictrl_0, (SEL_TAR | ENA_RESEL)); + + + while (!(RDW_HARPOON((p_port+hp_intstat)) & + (RESET | PROG_HLT | TIMEOUT | AUTO_INT))) {} + + if (RDW_HARPOON((p_port+hp_intstat)) & RESET) + Wait(p_port, TO_250ms); + + DISABLE_AUTO(p_port); + + WR_HARPOON(p_port+hp_addstat,(RD_HARPOON(p_port+hp_addstat) & ~SCAM_TIMER)); + WR_HARPOON(p_port+hp_seltimeout,TO_290ms); + + SGRAM_ACCESS(p_port); + + if (RDW_HARPOON((p_port+hp_intstat)) & (RESET | TIMEOUT) ) { + + WRW_HARPOON((p_port+hp_intstat), + (RESET | TIMEOUT | SEL | BUS_FREE | PHASE)); + + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) & ~G_INT_DISABLE)); + + return(FALSE); /*No legacy device */ + } + + else { + + while(!(RDW_HARPOON((p_port+hp_intstat)) & BUS_FREE)) { + if (RD_HARPOON(p_port+hp_scsisig) & SCSI_REQ) + { + WR_HARPOON(p_port+hp_scsisig, (SCSI_ACK + S_ILL_PH)); + ACCEPT_MSG(p_port); + } + } + + WRW_HARPOON((p_port+hp_intstat), CLR_ALL_INT_1); + + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) & ~G_INT_DISABLE)); + + return(TRUE); /*Found one of them oldies! */ + } +} + +#if defined(DOS) +/*--------------------------------------------------------------------- + * + * Function: scsell for DOS + * + * Description: Select the specified device ID using a selection timeout + * less than 2ms. This was specially required to solve + * the problem with Plextor 12X CD-ROM drive. This drive + * was responding the Selection at the end of 4ms and + * hanging the system. + * + *---------------------------------------------------------------------*/ + +UCHAR scsellDOS(USHORT p_port, UCHAR targ_id) +{ + USHORT i; + + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) | G_INT_DISABLE)); + + ARAM_ACCESS(p_port); + + WR_HARPOON(p_port+hp_addstat,(RD_HARPOON(p_port+hp_addstat) | SCAM_TIMER)); + WR_HARPOON(p_port+hp_seltimeout,TO_2ms); + + + for (i = p_port+CMD_STRT; i < p_port+CMD_STRT+12; i+=2) { + WRW_HARPOON(i, (MPM_OP+ACOMMAND)); + } + WRW_HARPOON(i, (BRH_OP+ALWAYS+ NP)); + + WRW_HARPOON((p_port+hp_intstat), + (RESET | TIMEOUT | SEL | BUS_FREE | AUTO_INT)); + + WR_HARPOON(p_port+hp_select_id, targ_id); + + WR_HARPOON(p_port+hp_portctrl_0, SCSI_PORT); + WR_HARPOON(p_port+hp_autostart_3, (SELECT | CMD_ONLY_STRT)); + WR_HARPOON(p_port+hp_scsictrl_0, (SEL_TAR | ENA_RESEL)); + + + while (!(RDW_HARPOON((p_port+hp_intstat)) & + (RESET | PROG_HLT | TIMEOUT | AUTO_INT))) {} + + if (RDW_HARPOON((p_port+hp_intstat)) & RESET) + Wait(p_port, TO_250ms); + + DISABLE_AUTO(p_port); + + WR_HARPOON(p_port+hp_addstat,(RD_HARPOON(p_port+hp_addstat) & ~SCAM_TIMER)); + WR_HARPOON(p_port+hp_seltimeout,TO_290ms); + + SGRAM_ACCESS(p_port); + + if (RDW_HARPOON((p_port+hp_intstat)) & (RESET | TIMEOUT) ) { + + WRW_HARPOON((p_port+hp_intstat), + (RESET | TIMEOUT | SEL | BUS_FREE | PHASE)); + + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) & ~G_INT_DISABLE)); + + return(FALSE); /*No legacy device */ + } + + else { + + while(!(RDW_HARPOON((p_port+hp_intstat)) & BUS_FREE)) { + if (RD_HARPOON(p_port+hp_scsisig) & SCSI_REQ) + { + WR_HARPOON(p_port+hp_scsisig, (SCSI_ACK + S_ILL_PH)); + ACCEPT_MSG(p_port); + } + } + + WRW_HARPOON((p_port+hp_intstat), CLR_ALL_INT_1); + + WR_HARPOON(p_port+hp_page_ctrl, + (RD_HARPOON(p_port+hp_page_ctrl) & ~G_INT_DISABLE)); + + return(TRUE); /*Found one of them oldies! */ + } +} +#endif /* DOS */ + +/*--------------------------------------------------------------------- + * + * Function: scwtsel + * + * Description: Wait to be selected by another SCAM initiator. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void scwtsel(USHORT p_port) +#else +void scwtsel(ULONG p_port) +#endif +{ + while(!(RDW_HARPOON((p_port+hp_intstat)) & SCAM_SEL)) {} +} + + +/*--------------------------------------------------------------------- + * + * Function: inisci + * + * Description: Setup the data Structure with the info from the EEPROM. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void inisci(UCHAR p_card, USHORT p_port, UCHAR p_our_id) +#else +void inisci(UCHAR p_card, ULONG p_port, UCHAR p_our_id) +#endif +{ + UCHAR i,k,max_id; + USHORT ee_data; + PNVRamInfo pCurrNvRam; + + pCurrNvRam = BL_Card[p_card].pNvRamInfo; + + if (RD_HARPOON(p_port+hp_page_ctrl) & NARROW_SCSI_CARD) + max_id = 0x08; + + else + max_id = 0x10; + + if(pCurrNvRam){ + for(i = 0; i < max_id; i++){ + + for(k = 0; k < 4; k++) + scamInfo[i].id_string[k] = pCurrNvRam->niScamTbl[i][k]; + for(k = 4; k < ID_STRING_LENGTH; k++) + scamInfo[i].id_string[k] = (UCHAR) 0x00; + + if(scamInfo[i].id_string[0] == 0x00) + scamInfo[i].state = ID_UNUSED; /*Default to unused ID. */ + else + scamInfo[i].state = ID_UNASSIGNED; /*Default to unassigned ID. */ + + } + }else { + for (i=0; i < max_id; i++) + { + for (k=0; k < ID_STRING_LENGTH; k+=2) + { + ee_data = utilEERead(p_port, (USHORT)((EE_SCAMBASE/2) + + (USHORT) (i*((USHORT)ID_STRING_LENGTH/2)) + (USHORT)(k/2))); + scamInfo[i].id_string[k] = (UCHAR) ee_data; + ee_data >>= 8; + scamInfo[i].id_string[k+1] = (UCHAR) ee_data; + } + + if ((scamInfo[i].id_string[0] == 0x00) || + (scamInfo[i].id_string[0] == 0xFF)) + + scamInfo[i].state = ID_UNUSED; /*Default to unused ID. */ + + else + scamInfo[i].state = ID_UNASSIGNED; /*Default to unassigned ID. */ + + } + } + for(k = 0; k < ID_STRING_LENGTH; k++) + scamInfo[p_our_id].id_string[k] = scamHAString[k]; + +} + +/*--------------------------------------------------------------------- + * + * Function: scmachid + * + * Description: Match the Device ID string with our values stored in + * the EEPROM. + * + *---------------------------------------------------------------------*/ + +UCHAR scmachid(UCHAR p_card, UCHAR p_id_string[]) +{ + + UCHAR i,k,match; + + + for (i=0; i < MAX_SCSI_TAR; i++) { + +#if !defined(SCAM_LEV_2) + if (scamInfo[i].state == ID_UNASSIGNED) + { +#endif + match = TRUE; + + for (k=0; k < ID_STRING_LENGTH; k++) + { + if (p_id_string[k] != scamInfo[i].id_string[k]) + match = FALSE; + } + + if (match) + { + scamInfo[i].state = ID_ASSIGNED; + return(i); + } + +#if !defined(SCAM_LEV_2) + } +#endif + + } + + + + if (p_id_string[0] & BIT(5)) + i = 8; + else + i = MAX_SCSI_TAR; + + if (((p_id_string[0] & 0x06) == 0x02) || ((p_id_string[0] & 0x06) == 0x04)) + match = p_id_string[1] & (UCHAR) 0x1F; + else + match = 7; + + while (i > 0) + { + i--; + + if (scamInfo[match].state == ID_UNUSED) + { + for (k=0; k < ID_STRING_LENGTH; k++) + { + scamInfo[match].id_string[k] = p_id_string[k]; + } + + scamInfo[match].state = ID_ASSIGNED; + + if(BL_Card[p_card].pNvRamInfo == NULL) + BL_Card[p_card].globalFlags |= F_UPDATE_EEPROM; + return(match); + + } + + + match--; + + if (match == 0xFF) + { + if (p_id_string[0] & BIT(5)) + match = 7; + else + match = MAX_SCSI_TAR-1; + } + } + + + + if (p_id_string[0] & BIT(7)) + { + return(CLR_PRIORITY); + } + + + if (p_id_string[0] & BIT(5)) + i = 8; + else + i = MAX_SCSI_TAR; + + if (((p_id_string[0] & 0x06) == 0x02) || ((p_id_string[0] & 0x06) == 0x04)) + match = p_id_string[1] & (UCHAR) 0x1F; + else + match = 7; + + while (i > 0) + { + + i--; + + if (scamInfo[match].state == ID_UNASSIGNED) + { + for (k=0; k < ID_STRING_LENGTH; k++) + { + scamInfo[match].id_string[k] = p_id_string[k]; + } + + scamInfo[match].id_string[0] |= BIT(7); + scamInfo[match].state = ID_ASSIGNED; + if(BL_Card[p_card].pNvRamInfo == NULL) + BL_Card[p_card].globalFlags |= F_UPDATE_EEPROM; + return(match); + + } + + + match--; + + if (match == 0xFF) + { + if (p_id_string[0] & BIT(5)) + match = 7; + else + match = MAX_SCSI_TAR-1; + } + } + + return(NO_ID_AVAIL); +} + + +/*--------------------------------------------------------------------- + * + * Function: scsavdi + * + * Description: Save off the device SCAM ID strings. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void scsavdi(UCHAR p_card, USHORT p_port) +#else +void scsavdi(UCHAR p_card, ULONG p_port) +#endif +{ + UCHAR i,k,max_id; + USHORT ee_data,sum_data; + + + sum_data = 0x0000; + + for (i = 1; i < EE_SCAMBASE/2; i++) + { + sum_data += utilEERead(p_port, i); + } + + + utilEEWriteOnOff(p_port,1); /* Enable write access to the EEPROM */ + + if (RD_HARPOON(p_port+hp_page_ctrl) & NARROW_SCSI_CARD) + max_id = 0x08; + + else + max_id = 0x10; + + for (i=0; i < max_id; i++) + { + + for (k=0; k < ID_STRING_LENGTH; k+=2) + { + ee_data = scamInfo[i].id_string[k+1]; + ee_data <<= 8; + ee_data |= scamInfo[i].id_string[k]; + sum_data += ee_data; + utilEEWrite(p_port, ee_data, (USHORT)((EE_SCAMBASE/2) + + (USHORT)(i*((USHORT)ID_STRING_LENGTH/2)) + (USHORT)(k/2))); + } + } + + + utilEEWrite(p_port, sum_data, EEPROM_CHECK_SUM/2); + utilEEWriteOnOff(p_port,0); /* Turn off write access */ +} +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: diagnose.c $ + * + * Description: Diagnostic funtions for testing the integrity of + * the HARPOON. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ + +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <eeprom.h>*/ +/*#include <harpoon.h>*/ + +/*--------------------------------------------------------------------- + * + * Function: XbowInit + * + * Description: Setup the Xbow for normal operation. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void XbowInit(USHORT port, UCHAR ScamFlg) +#else +void XbowInit(ULONG port, UCHAR ScamFlg) +#endif +{ +UCHAR i; + + i = RD_HARPOON(port+hp_page_ctrl); + WR_HARPOON(port+hp_page_ctrl, (UCHAR) (i | G_INT_DISABLE)); + + WR_HARPOON(port+hp_scsireset,0x00); + WR_HARPOON(port+hp_portctrl_1,HOST_MODE8); + + WR_HARPOON(port+hp_scsireset,(DMA_RESET | HPSCSI_RESET | PROG_RESET | \ + FIFO_CLR)); + + WR_HARPOON(port+hp_scsireset,SCSI_INI); + + WR_HARPOON(port+hp_clkctrl_0,CLKCTRL_DEFAULT); + + WR_HARPOON(port+hp_scsisig,0x00); /* Clear any signals we might */ + WR_HARPOON(port+hp_scsictrl_0,ENA_SCAM_SEL); + + WRW_HARPOON((port+hp_intstat), CLR_ALL_INT); + +#if defined(SCAM_LEV_2) + default_intena = RESET | RSEL | PROG_HLT | TIMEOUT | + BUS_FREE | XFER_CNT_0 | AUTO_INT; + + if ((ScamFlg & SCAM_ENABLED) && (ScamFlg & SCAM_LEVEL2)) + default_intena |= SCAM_SEL; + +#else + default_intena = RESET | RSEL | PROG_HLT | TIMEOUT | + BUS_FREE | XFER_CNT_0 | AUTO_INT; +#endif + WRW_HARPOON((port+hp_intena), default_intena); + + WR_HARPOON(port+hp_seltimeout,TO_290ms); + + /* Turn on SCSI_MODE8 for narrow cards to fix the + strapping issue with the DUAL CHANNEL card */ + if (RD_HARPOON(port+hp_page_ctrl) & NARROW_SCSI_CARD) + WR_HARPOON(port+hp_addstat,SCSI_MODE8); + +#if defined(NO_BIOS_OPTION) + + WR_HARPOON(port+hp_synctarg_0,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_1,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_2,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_3,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_4,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_5,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_6,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_7,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_8,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_9,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_10,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_11,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_12,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_13,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_14,NARROW_SCSI); + WR_HARPOON(port+hp_synctarg_15,NARROW_SCSI); + +#endif + WR_HARPOON(port+hp_page_ctrl, i); + +} + + +/*--------------------------------------------------------------------- + * + * Function: BusMasterInit + * + * Description: Initialize the BusMaster for normal operations. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void BusMasterInit(USHORT p_port) +#else +void BusMasterInit(ULONG p_port) +#endif +{ + + + WR_HARPOON(p_port+hp_sys_ctrl, DRVR_RST); + WR_HARPOON(p_port+hp_sys_ctrl, 0x00); + + WR_HARPOON(p_port+hp_host_blk_cnt, XFER_BLK64); + + + WR_HARPOON(p_port+hp_bm_ctrl, (BMCTRL_DEFAULT)); + + WR_HARPOON(p_port+hp_ee_ctrl, (SCSI_TERM_ENA_H)); + + +#if defined(NT) + + WR_HARPOON(p_port+hp_pci_cmd_cfg, (RD_HARPOON(p_port+hp_pci_cmd_cfg) + & ~MEM_SPACE_ENA)); + +#endif + + RD_HARPOON(p_port+hp_int_status); /*Clear interrupts. */ + WR_HARPOON(p_port+hp_int_mask, (INT_CMD_COMPL | SCSI_INTERRUPT)); + WR_HARPOON(p_port+hp_page_ctrl, (RD_HARPOON(p_port+hp_page_ctrl) & + ~SCATTER_EN)); +} + + +/*--------------------------------------------------------------------- + * + * Function: DiagXbow + * + * Description: Test Xbow integrity. Non-zero return indicates an error. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +int DiagXbow(USHORT port) +#else +int DiagXbow(ULONG port) +#endif +{ + unsigned char fifo_cnt,loop_cnt; + + unsigned char fifodata[5]; + fifodata[0] = 0x00; + fifodata[1] = 0xFF; + fifodata[2] = 0x55; + fifodata[3] = 0xAA; + fifodata[4] = 0x00; + + + WRW_HARPOON((port+hp_intstat), CLR_ALL_INT); + WRW_HARPOON((port+hp_intena), 0x0000); + + WR_HARPOON(port+hp_seltimeout,TO_5ms); + + WR_HARPOON(port+hp_portctrl_0,START_TO); + + + for(fifodata[4] = 0x01; fifodata[4] != (UCHAR) 0; fifodata[4] = fifodata[4] << 1) { + + WR_HARPOON(port+hp_selfid_0,fifodata[4]); + WR_HARPOON(port+hp_selfid_1,fifodata[4]); + + if ((RD_HARPOON(port+hp_selfid_0) != fifodata[4]) || + (RD_HARPOON(port+hp_selfid_1) != fifodata[4])) + return(1); + } + + + for(loop_cnt = 0; loop_cnt < 4; loop_cnt++) { + + WR_HARPOON(port+hp_portctrl_0,(HOST_PORT | HOST_WRT | START_TO)); + + + for (fifo_cnt = 0; fifo_cnt < FIFO_LEN; fifo_cnt++) { + + WR_HARPOON(port+hp_fifodata_0, fifodata[loop_cnt]); + } + + + if (!(RD_HARPOON(port+hp_xferstat) & FIFO_FULL)) + return(1); + + + WR_HARPOON(port+hp_portctrl_0,(HOST_PORT | START_TO)); + + for (fifo_cnt = 0; fifo_cnt < FIFO_LEN; fifo_cnt++) { + + if (RD_HARPOON(port+hp_fifodata_0) != fifodata[loop_cnt]) + return(1); + } + + + if (!(RD_HARPOON(port+hp_xferstat) & FIFO_EMPTY)) + return(1); + } + + + while(!(RDW_HARPOON((port+hp_intstat)) & TIMEOUT)) {} + + + WR_HARPOON(port+hp_seltimeout,TO_290ms); + + WRW_HARPOON((port+hp_intstat), CLR_ALL_INT); + + WRW_HARPOON((port+hp_intena), default_intena); + + return(0); +} + + +/*--------------------------------------------------------------------- + * + * Function: DiagBusMaster + * + * Description: Test BusMaster integrity. Non-zero return indicates an + * error. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +int DiagBusMaster(USHORT port) +#else +int DiagBusMaster(ULONG port) +#endif +{ + UCHAR testdata; + + for(testdata = (UCHAR) 1; testdata != (UCHAR)0; testdata = testdata << 1) { + + WR_HARPOON(port+hp_xfer_cnt_lo,testdata); + WR_HARPOON(port+hp_xfer_cnt_mi,testdata); + WR_HARPOON(port+hp_xfer_cnt_hi,testdata); + WR_HARPOON(port+hp_host_addr_lo,testdata); + WR_HARPOON(port+hp_host_addr_lmi,testdata); + WR_HARPOON(port+hp_host_addr_hmi,testdata); + WR_HARPOON(port+hp_host_addr_hi,testdata); + + if ((RD_HARPOON(port+hp_xfer_cnt_lo) != testdata) || + (RD_HARPOON(port+hp_xfer_cnt_mi) != testdata) || + (RD_HARPOON(port+hp_xfer_cnt_hi) != testdata) || + (RD_HARPOON(port+hp_host_addr_lo) != testdata) || + (RD_HARPOON(port+hp_host_addr_lmi) != testdata) || + (RD_HARPOON(port+hp_host_addr_hmi) != testdata) || + (RD_HARPOON(port+hp_host_addr_hi) != testdata)) + + return(1); + } + RD_HARPOON(port+hp_int_status); /*Clear interrupts. */ + return(0); +} + + + +/*--------------------------------------------------------------------- + * + * Function: DiagEEPROM + * + * Description: Verfiy checksum and 'Key' and initialize the EEPROM if + * neccessary. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void DiagEEPROM(USHORT p_port) +#else +void DiagEEPROM(ULONG p_port) +#endif + +{ + USHORT index,temp,max_wd_cnt; + + if (RD_HARPOON(p_port+hp_page_ctrl) & NARROW_SCSI_CARD) + max_wd_cnt = EEPROM_WD_CNT; + else + max_wd_cnt = EEPROM_WD_CNT * 2; + + temp = utilEERead(p_port, FW_SIGNATURE/2); + + if (temp == 0x4641) { + + for (index = 2; index < max_wd_cnt; index++) { + + temp += utilEERead(p_port, index); + + } + + if (temp == utilEERead(p_port, EEPROM_CHECK_SUM/2)) { + + return; /*EEPROM is Okay so return now! */ + } + } + + + utilEEWriteOnOff(p_port,(UCHAR)1); + + for (index = 0; index < max_wd_cnt; index++) { + + utilEEWrite(p_port, 0x0000, index); + } + + temp = 0; + + utilEEWrite(p_port, 0x4641, FW_SIGNATURE/2); + temp += 0x4641; + utilEEWrite(p_port, 0x3920, MODEL_NUMB_0/2); + temp += 0x3920; + utilEEWrite(p_port, 0x3033, MODEL_NUMB_2/2); + temp += 0x3033; + utilEEWrite(p_port, 0x2020, MODEL_NUMB_4/2); + temp += 0x2020; + utilEEWrite(p_port, 0x70D3, SYSTEM_CONFIG/2); + temp += 0x70D3; + utilEEWrite(p_port, 0x0010, BIOS_CONFIG/2); + temp += 0x0010; + utilEEWrite(p_port, 0x0003, SCAM_CONFIG/2); + temp += 0x0003; + utilEEWrite(p_port, 0x0007, ADAPTER_SCSI_ID/2); + temp += 0x0007; + + utilEEWrite(p_port, 0x0000, IGNORE_B_SCAN/2); + temp += 0x0000; + utilEEWrite(p_port, 0x0000, SEND_START_ENA/2); + temp += 0x0000; + utilEEWrite(p_port, 0x0000, DEVICE_ENABLE/2); + temp += 0x0000; + + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBL01/2); + temp += 0x4242; + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBL23/2); + temp += 0x4242; + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBL45/2); + temp += 0x4242; + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBL67/2); + temp += 0x4242; + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBL89/2); + temp += 0x4242; + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBLab/2); + temp += 0x4242; + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBLcd/2); + temp += 0x4242; + utilEEWrite(p_port, 0x4242, SYNC_RATE_TBLef/2); + temp += 0x4242; + + + utilEEWrite(p_port, 0x6C46, 64/2); /*PRODUCT ID */ + temp += 0x6C46; + utilEEWrite(p_port, 0x7361, 66/2); /* FlashPoint LT */ + temp += 0x7361; + utilEEWrite(p_port, 0x5068, 68/2); + temp += 0x5068; + utilEEWrite(p_port, 0x696F, 70/2); + temp += 0x696F; + utilEEWrite(p_port, 0x746E, 72/2); + temp += 0x746E; + utilEEWrite(p_port, 0x4C20, 74/2); + temp += 0x4C20; + utilEEWrite(p_port, 0x2054, 76/2); + temp += 0x2054; + utilEEWrite(p_port, 0x2020, 78/2); + temp += 0x2020; + + index = ((EE_SCAMBASE/2)+(7*16)); + utilEEWrite(p_port, (0x0700+TYPE_CODE0), index); + temp += (0x0700+TYPE_CODE0); + index++; + utilEEWrite(p_port, 0x5542, index); /*Vendor ID code */ + temp += 0x5542; /* BUSLOGIC */ + index++; + utilEEWrite(p_port, 0x4C53, index); + temp += 0x4C53; + index++; + utilEEWrite(p_port, 0x474F, index); + temp += 0x474F; + index++; + utilEEWrite(p_port, 0x4349, index); + temp += 0x4349; + index++; + utilEEWrite(p_port, 0x5442, index); /*Vendor unique code */ + temp += 0x5442; /* BT- 930 */ + index++; + utilEEWrite(p_port, 0x202D, index); + temp += 0x202D; + index++; + utilEEWrite(p_port, 0x3339, index); + temp += 0x3339; + index++; /*Serial # */ + utilEEWrite(p_port, 0x2030, index); /* 01234567 */ + temp += 0x2030; + index++; + utilEEWrite(p_port, 0x5453, index); + temp += 0x5453; + index++; + utilEEWrite(p_port, 0x5645, index); + temp += 0x5645; + index++; + utilEEWrite(p_port, 0x2045, index); + temp += 0x2045; + index++; + utilEEWrite(p_port, 0x202F, index); + temp += 0x202F; + index++; + utilEEWrite(p_port, 0x4F4A, index); + temp += 0x4F4A; + index++; + utilEEWrite(p_port, 0x204E, index); + temp += 0x204E; + index++; + utilEEWrite(p_port, 0x3539, index); + temp += 0x3539; + + + + utilEEWrite(p_port, temp, EEPROM_CHECK_SUM/2); + + utilEEWriteOnOff(p_port,(UCHAR)0); + +} + +#ident "$Id: FlashPoint.c,v 1.1 1999/04/26 05:53:56 tb Exp $" +/*---------------------------------------------------------------------- + * + * + * Copyright 1995-1996 by Mylex Corporation. All Rights Reserved + * + * This file is available under both the GNU General Public License + * and a BSD-style copyright; see LICENSE.FlashPoint for details. + * + * $Workfile: utility.c $ + * + * Description: Utility functions relating to queueing and EEPROM + * manipulation and any other garbage functions. + * + * $Date: 1999/04/26 05:53:56 $ + * + * $Revision: 1.1 $ + * + *----------------------------------------------------------------------*/ +/*#include <globals.h>*/ + +#if (FW_TYPE==_UCB_MGR_) + /*#include <budi.h>*/ +#endif + +/*#include <sccbmgr.h>*/ +/*#include <blx30.h>*/ +/*#include <target.h>*/ +/*#include <scsi2.h>*/ +/*#include <harpoon.h>*/ + + +/* +extern SCCBCARD BL_Card[MAX_CARDS]; +extern SCCBMGR_TAR_INFO sccbMgrTbl[MAX_CARDS][MAX_SCSI_TAR]; +extern unsigned int SccbGlobalFlags; +*/ + +/*--------------------------------------------------------------------- + * + * Function: Queue Search Select + * + * Description: Try to find a new command to execute. + * + *---------------------------------------------------------------------*/ + +void queueSearchSelect(PSCCBcard pCurrCard, UCHAR p_card) +{ + UCHAR scan_ptr, lun; + PSCCBMgr_tar_info currTar_Info; + PSCCB pOldSccb; + + scan_ptr = pCurrCard->scanIndex; + do + { + currTar_Info = &sccbMgrTbl[p_card][scan_ptr]; + if((pCurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING)) + { + if (currTar_Info->TarSelQ_Cnt != 0) + { + + scan_ptr++; + if (scan_ptr == MAX_SCSI_TAR) + scan_ptr = 0; + + for(lun=0; lun < MAX_LUN; lun++) + { + if(currTar_Info->TarLUNBusy[lun] == FALSE) + { + + pCurrCard->currentSCCB = currTar_Info->TarSelQ_Head; + pOldSccb = NULL; + + while((pCurrCard->currentSCCB != NULL) && + (lun != pCurrCard->currentSCCB->Lun)) + { + pOldSccb = pCurrCard->currentSCCB; + pCurrCard->currentSCCB = (PSCCB)(pCurrCard->currentSCCB)-> + Sccb_forwardlink; + } + if(pCurrCard->currentSCCB == NULL) + continue; + if(pOldSccb != NULL) + { + pOldSccb->Sccb_forwardlink = (PSCCB)(pCurrCard->currentSCCB)-> + Sccb_forwardlink; + pOldSccb->Sccb_backlink = (PSCCB)(pCurrCard->currentSCCB)-> + Sccb_backlink; + currTar_Info->TarSelQ_Cnt--; + } + else + { + currTar_Info->TarSelQ_Head = (PSCCB)(pCurrCard->currentSCCB)->Sccb_forwardlink; + + if (currTar_Info->TarSelQ_Head == NULL) + { + currTar_Info->TarSelQ_Tail = NULL; + currTar_Info->TarSelQ_Cnt = 0; + } + else + { + currTar_Info->TarSelQ_Cnt--; + currTar_Info->TarSelQ_Head->Sccb_backlink = (PSCCB)NULL; + } + } + pCurrCard->scanIndex = scan_ptr; + + pCurrCard->globalFlags |= F_NEW_SCCB_CMD; + + break; + } + } + } + + else + { + scan_ptr++; + if (scan_ptr == MAX_SCSI_TAR) { + scan_ptr = 0; + } + } + + } + else + { + if ((currTar_Info->TarSelQ_Cnt != 0) && + (currTar_Info->TarLUNBusy[0] == FALSE)) + { + + pCurrCard->currentSCCB = currTar_Info->TarSelQ_Head; + + currTar_Info->TarSelQ_Head = (PSCCB)(pCurrCard->currentSCCB)->Sccb_forwardlink; + + if (currTar_Info->TarSelQ_Head == NULL) + { + currTar_Info->TarSelQ_Tail = NULL; + currTar_Info->TarSelQ_Cnt = 0; + } + else + { + currTar_Info->TarSelQ_Cnt--; + currTar_Info->TarSelQ_Head->Sccb_backlink = (PSCCB)NULL; + } + + scan_ptr++; + if (scan_ptr == MAX_SCSI_TAR) + scan_ptr = 0; + + pCurrCard->scanIndex = scan_ptr; + + pCurrCard->globalFlags |= F_NEW_SCCB_CMD; + + break; + } + + else + { + scan_ptr++; + if (scan_ptr == MAX_SCSI_TAR) + { + scan_ptr = 0; + } + } + } + } while (scan_ptr != pCurrCard->scanIndex); +} + + +/*--------------------------------------------------------------------- + * + * Function: Queue Select Fail + * + * Description: Add the current SCCB to the head of the Queue. + * + *---------------------------------------------------------------------*/ + +void queueSelectFail(PSCCBcard pCurrCard, UCHAR p_card) +{ + UCHAR thisTarg; + PSCCBMgr_tar_info currTar_Info; + + if (pCurrCard->currentSCCB != NULL) + { + thisTarg = (UCHAR)(((PSCCB)(pCurrCard->currentSCCB))->TargID); + currTar_Info = &sccbMgrTbl[p_card][thisTarg]; + + pCurrCard->currentSCCB->Sccb_backlink = (PSCCB)NULL; + + pCurrCard->currentSCCB->Sccb_forwardlink = currTar_Info->TarSelQ_Head; + + if (currTar_Info->TarSelQ_Cnt == 0) + { + currTar_Info->TarSelQ_Tail = pCurrCard->currentSCCB; + } + + else + { + currTar_Info->TarSelQ_Head->Sccb_backlink = pCurrCard->currentSCCB; + } + + + currTar_Info->TarSelQ_Head = pCurrCard->currentSCCB; + + pCurrCard->currentSCCB = NULL; + currTar_Info->TarSelQ_Cnt++; + } +} +/*--------------------------------------------------------------------- + * + * Function: Queue Command Complete + * + * Description: Call the callback function with the current SCCB. + * + *---------------------------------------------------------------------*/ + +void queueCmdComplete(PSCCBcard pCurrCard, PSCCB p_sccb, UCHAR p_card) +{ + +#if (FW_TYPE==_UCB_MGR_) + + u08bits SCSIcmd; + CALL_BK_FN callback; + PSCCBMgr_tar_info currTar_Info; + + PUCB p_ucb; + p_ucb=p_sccb->Sccb_ucb_ptr; + + SCSIcmd = p_sccb->Cdb[0]; + + + if (!(p_sccb->Sccb_XferState & F_ALL_XFERRED)) + { + + if ((p_ucb->UCB_opcode & OPC_CHK_UNDER_OVER_RUN) && + (p_sccb->HostStatus == SCCB_COMPLETE) && + (p_sccb->TargetStatus != SSCHECK)) + + if ((SCSIcmd == SCSI_READ) || + (SCSIcmd == SCSI_WRITE) || + (SCSIcmd == SCSI_READ_EXTENDED) || + (SCSIcmd == SCSI_WRITE_EXTENDED) || + (SCSIcmd == SCSI_WRITE_AND_VERIFY) || + (SCSIcmd == SCSI_START_STOP_UNIT) || + (pCurrCard->globalFlags & F_NO_FILTER) + ) + p_sccb->HostStatus = SCCB_DATA_UNDER_RUN; + } + + p_ucb->UCB_status=SCCB_SUCCESS; + + if ((p_ucb->UCB_hbastat=p_sccb->HostStatus) || (p_ucb->UCB_scsistat=p_sccb->TargetStatus)) + { + p_ucb->UCB_status=SCCB_ERROR; + } + + if ((p_sccb->OperationCode == RESIDUAL_SG_COMMAND) || + (p_sccb->OperationCode == RESIDUAL_COMMAND)) + { + + utilUpdateResidual(p_sccb); + + p_ucb->UCB_datalen=p_sccb->DataLength; + } + + pCurrCard->cmdCounter--; + if (!pCurrCard->cmdCounter) + { + + if (pCurrCard->globalFlags & F_GREEN_PC) + { + WR_HARPOON(pCurrCard->ioPort+hp_clkctrl_0,(PWR_DWN | CLKCTRL_DEFAULT)); + WR_HARPOON(pCurrCard->ioPort+hp_sys_ctrl, STOP_CLK); + } + + WR_HARPOON(pCurrCard->ioPort+hp_semaphore, + (RD_HARPOON(pCurrCard->ioPort+hp_semaphore) & ~SCCB_MGR_ACTIVE)); + } + + if(pCurrCard->discQCount != 0) + { + currTar_Info = &sccbMgrTbl[p_card][p_sccb->TargID]; + if(((pCurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[p_sccb->Lun]] = NULL; + } + else + { + if(p_sccb->Sccb_tag) + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[p_sccb->Sccb_tag] = NULL; + }else + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[0]] = NULL; + } + } + + } + callback = (CALL_BK_FN)p_ucb->UCB_callback; + callback(p_ucb); + pCurrCard->globalFlags |= F_NEW_SCCB_CMD; + pCurrCard->currentSCCB = NULL; +} + + + + +#else + + UCHAR i, SCSIcmd; + CALL_BK_FN callback; + PSCCBMgr_tar_info currTar_Info; + + SCSIcmd = p_sccb->Cdb[0]; + + + if (!(p_sccb->Sccb_XferState & F_ALL_XFERRED)) { + + if ((p_sccb->ControlByte & (SCCB_DATA_XFER_OUT | SCCB_DATA_XFER_IN)) && + (p_sccb->HostStatus == SCCB_COMPLETE) && + (p_sccb->TargetStatus != SSCHECK)) + + if ((SCSIcmd == SCSI_READ) || + (SCSIcmd == SCSI_WRITE) || + (SCSIcmd == SCSI_READ_EXTENDED) || + (SCSIcmd == SCSI_WRITE_EXTENDED) || + (SCSIcmd == SCSI_WRITE_AND_VERIFY) || + (SCSIcmd == SCSI_START_STOP_UNIT) || + (pCurrCard->globalFlags & F_NO_FILTER) + ) + p_sccb->HostStatus = SCCB_DATA_UNDER_RUN; + } + + + if(p_sccb->SccbStatus == SCCB_IN_PROCESS) + { + if (p_sccb->HostStatus || p_sccb->TargetStatus) + p_sccb->SccbStatus = SCCB_ERROR; + else + p_sccb->SccbStatus = SCCB_SUCCESS; + } + + if (p_sccb->Sccb_XferState & F_AUTO_SENSE) { + + p_sccb->CdbLength = p_sccb->Save_CdbLen; + for (i=0; i < 6; i++) { + p_sccb->Cdb[i] = p_sccb->Save_Cdb[i]; + } + } + + if ((p_sccb->OperationCode == RESIDUAL_SG_COMMAND) || + (p_sccb->OperationCode == RESIDUAL_COMMAND)) { + + utilUpdateResidual(p_sccb); + } + + pCurrCard->cmdCounter--; + if (!pCurrCard->cmdCounter) { + + if (pCurrCard->globalFlags & F_GREEN_PC) { + WR_HARPOON(pCurrCard->ioPort+hp_clkctrl_0,(PWR_DWN | CLKCTRL_DEFAULT)); + WR_HARPOON(pCurrCard->ioPort+hp_sys_ctrl, STOP_CLK); + } + + WR_HARPOON(pCurrCard->ioPort+hp_semaphore, + (RD_HARPOON(pCurrCard->ioPort+hp_semaphore) & ~SCCB_MGR_ACTIVE)); + + } + + if(pCurrCard->discQCount != 0) + { + currTar_Info = &sccbMgrTbl[p_card][p_sccb->TargID]; + if(((pCurrCard->globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[p_sccb->Lun]] = NULL; + } + else + { + if(p_sccb->Sccb_tag) + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[p_sccb->Sccb_tag] = NULL; + }else + { + pCurrCard->discQCount--; + pCurrCard->discQ_Tbl[currTar_Info->LunDiscQ_Idx[0]] = NULL; + } + } + + } + + callback = (CALL_BK_FN)p_sccb->SccbCallback; + callback(p_sccb); + pCurrCard->globalFlags |= F_NEW_SCCB_CMD; + pCurrCard->currentSCCB = NULL; +} +#endif /* ( if FW_TYPE==...) */ + + +/*--------------------------------------------------------------------- + * + * Function: Queue Disconnect + * + * Description: Add SCCB to our disconnect array. + * + *---------------------------------------------------------------------*/ +void queueDisconnect(PSCCB p_sccb, UCHAR p_card) +{ + PSCCBMgr_tar_info currTar_Info; + + currTar_Info = &sccbMgrTbl[p_card][p_sccb->TargID]; + + if(((BL_Card[p_card].globalFlags & F_CONLUN_IO) && + ((currTar_Info->TarStatus & TAR_TAG_Q_MASK) != TAG_Q_TRYING))) + { + BL_Card[p_card].discQ_Tbl[currTar_Info->LunDiscQ_Idx[p_sccb->Lun]] = p_sccb; + } + else + { + if (p_sccb->Sccb_tag) + { + BL_Card[p_card].discQ_Tbl[p_sccb->Sccb_tag] = p_sccb; + sccbMgrTbl[p_card][p_sccb->TargID].TarLUNBusy[0] = FALSE; + sccbMgrTbl[p_card][p_sccb->TargID].TarTagQ_Cnt++; + }else + { + BL_Card[p_card].discQ_Tbl[currTar_Info->LunDiscQ_Idx[0]] = p_sccb; + } + } + BL_Card[p_card].currentSCCB = NULL; +} + + +/*--------------------------------------------------------------------- + * + * Function: Queue Flush SCCB + * + * Description: Flush all SCCB's back to the host driver for this target. + * + *---------------------------------------------------------------------*/ + +void queueFlushSccb(UCHAR p_card, UCHAR error_code) +{ + UCHAR qtag,thisTarg; + PSCCB currSCCB; + PSCCBMgr_tar_info currTar_Info; + + currSCCB = BL_Card[p_card].currentSCCB; + if(currSCCB != NULL) + { + thisTarg = (UCHAR)currSCCB->TargID; + currTar_Info = &sccbMgrTbl[p_card][thisTarg]; + + for (qtag=0; qtag<QUEUE_DEPTH; qtag++) { + + if (BL_Card[p_card].discQ_Tbl[qtag] && + (BL_Card[p_card].discQ_Tbl[qtag]->TargID == thisTarg)) + { + + BL_Card[p_card].discQ_Tbl[qtag]->HostStatus = (UCHAR)error_code; + + queueCmdComplete(&BL_Card[p_card],BL_Card[p_card].discQ_Tbl[qtag], p_card); + + BL_Card[p_card].discQ_Tbl[qtag] = NULL; + currTar_Info->TarTagQ_Cnt--; + + } + } + } + +} + +/*--------------------------------------------------------------------- + * + * Function: Queue Flush Target SCCB + * + * Description: Flush all SCCB's back to the host driver for this target. + * + *---------------------------------------------------------------------*/ + +void queueFlushTargSccb(UCHAR p_card, UCHAR thisTarg, UCHAR error_code) +{ + UCHAR qtag; + PSCCBMgr_tar_info currTar_Info; + + currTar_Info = &sccbMgrTbl[p_card][thisTarg]; + + for (qtag=0; qtag<QUEUE_DEPTH; qtag++) { + + if (BL_Card[p_card].discQ_Tbl[qtag] && + (BL_Card[p_card].discQ_Tbl[qtag]->TargID == thisTarg)) + { + + BL_Card[p_card].discQ_Tbl[qtag]->HostStatus = (UCHAR)error_code; + + queueCmdComplete(&BL_Card[p_card],BL_Card[p_card].discQ_Tbl[qtag], p_card); + + BL_Card[p_card].discQ_Tbl[qtag] = NULL; + currTar_Info->TarTagQ_Cnt--; + + } + } + +} + + + + + +void queueAddSccb(PSCCB p_SCCB, UCHAR p_card) +{ + PSCCBMgr_tar_info currTar_Info; + currTar_Info = &sccbMgrTbl[p_card][p_SCCB->TargID]; + + p_SCCB->Sccb_forwardlink = NULL; + + p_SCCB->Sccb_backlink = currTar_Info->TarSelQ_Tail; + + if (currTar_Info->TarSelQ_Cnt == 0) { + + currTar_Info->TarSelQ_Head = p_SCCB; + } + + else { + + currTar_Info->TarSelQ_Tail->Sccb_forwardlink = p_SCCB; + } + + + currTar_Info->TarSelQ_Tail = p_SCCB; + currTar_Info->TarSelQ_Cnt++; +} + + +/*--------------------------------------------------------------------- + * + * Function: Queue Find SCCB + * + * Description: Search the target select Queue for this SCCB, and + * remove it if found. + * + *---------------------------------------------------------------------*/ + +UCHAR queueFindSccb(PSCCB p_SCCB, UCHAR p_card) +{ + PSCCB q_ptr; + PSCCBMgr_tar_info currTar_Info; + + currTar_Info = &sccbMgrTbl[p_card][p_SCCB->TargID]; + + q_ptr = currTar_Info->TarSelQ_Head; + + while(q_ptr != NULL) { + + if (q_ptr == p_SCCB) { + + + if (currTar_Info->TarSelQ_Head == q_ptr) { + + currTar_Info->TarSelQ_Head = q_ptr->Sccb_forwardlink; + } + + if (currTar_Info->TarSelQ_Tail == q_ptr) { + + currTar_Info->TarSelQ_Tail = q_ptr->Sccb_backlink; + } + + if (q_ptr->Sccb_forwardlink != NULL) { + q_ptr->Sccb_forwardlink->Sccb_backlink = q_ptr->Sccb_backlink; + } + + if (q_ptr->Sccb_backlink != NULL) { + q_ptr->Sccb_backlink->Sccb_forwardlink = q_ptr->Sccb_forwardlink; + } + + currTar_Info->TarSelQ_Cnt--; + + return(TRUE); + } + + else { + q_ptr = q_ptr->Sccb_forwardlink; + } + } + + + return(FALSE); + +} + + +/*--------------------------------------------------------------------- + * + * Function: Utility Update Residual Count + * + * Description: Update the XferCnt to the remaining byte count. + * If we transferred all the data then just write zero. + * If Non-SG transfer then report Total Cnt - Actual Transfer + * Cnt. For SG transfers add the count fields of all + * remaining SG elements, as well as any partial remaining + * element. + * + *---------------------------------------------------------------------*/ + +void utilUpdateResidual(PSCCB p_SCCB) +{ + ULONG partial_cnt; + UINT sg_index; +#if defined(COMPILER_16_BIT) && !defined(DOS) + ULONG far *sg_ptr; +#else + ULONG *sg_ptr; +#endif + + if (p_SCCB->Sccb_XferState & F_ALL_XFERRED) { + + p_SCCB->DataLength = 0x0000; + } + + else if (p_SCCB->Sccb_XferState & F_SG_XFER) { + + partial_cnt = 0x0000; + + sg_index = p_SCCB->Sccb_sgseg; + +#if defined(COMPILER_16_BIT) && !defined(DOS) + sg_ptr = (ULONG far *)p_SCCB->DataPointer; +#else + sg_ptr = (ULONG *)p_SCCB->DataPointer; +#endif + + if (p_SCCB->Sccb_SGoffset) { + + partial_cnt = p_SCCB->Sccb_SGoffset; + sg_index++; + } + + while ( ((ULONG)sg_index * (ULONG)SG_ELEMENT_SIZE) < + p_SCCB->DataLength ) { + + partial_cnt += *(sg_ptr+(sg_index * 2)); + sg_index++; + } + + p_SCCB->DataLength = partial_cnt; + } + + else { + + p_SCCB->DataLength -= p_SCCB->Sccb_ATC; + } +} + + +/*--------------------------------------------------------------------- + * + * Function: Wait 1 Second + * + * Description: Wait for 1 second. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void Wait1Second(USHORT p_port) +#else +void Wait1Second(ULONG p_port) +#endif +{ + UCHAR i; + + for(i=0; i < 4; i++) { + + Wait(p_port, TO_250ms); + + if ((RD_HARPOON(p_port+hp_scsictrl_0) & SCSI_RST)) + break; + + if((RDW_HARPOON((p_port+hp_intstat)) & SCAM_SEL)) + break; + } +} + + +/*--------------------------------------------------------------------- + * + * Function: Wait + * + * Description: Wait the desired delay. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void Wait(USHORT p_port, UCHAR p_delay) +#else +void Wait(ULONG p_port, UCHAR p_delay) +#endif +{ + UCHAR old_timer; + UCHAR green_flag; + + old_timer = RD_HARPOON(p_port+hp_seltimeout); + + green_flag=RD_HARPOON(p_port+hp_clkctrl_0); + WR_HARPOON(p_port+hp_clkctrl_0, CLKCTRL_DEFAULT); + + WR_HARPOON(p_port+hp_seltimeout,p_delay); + WRW_HARPOON((p_port+hp_intstat), TIMEOUT); + WRW_HARPOON((p_port+hp_intena), (default_intena & ~TIMEOUT)); + + + WR_HARPOON(p_port+hp_portctrl_0, + (RD_HARPOON(p_port+hp_portctrl_0) | START_TO)); + + while (!(RDW_HARPOON((p_port+hp_intstat)) & TIMEOUT)) { + + if ((RD_HARPOON(p_port+hp_scsictrl_0) & SCSI_RST)) + break; + + if ((RDW_HARPOON((p_port+hp_intstat)) & SCAM_SEL)) + break; + } + + WR_HARPOON(p_port+hp_portctrl_0, + (RD_HARPOON(p_port+hp_portctrl_0) & ~START_TO)); + + WRW_HARPOON((p_port+hp_intstat), TIMEOUT); + WRW_HARPOON((p_port+hp_intena), default_intena); + + WR_HARPOON(p_port+hp_clkctrl_0,green_flag); + + WR_HARPOON(p_port+hp_seltimeout,old_timer); +} + + +/*--------------------------------------------------------------------- + * + * Function: Enable/Disable Write to EEPROM + * + * Description: The EEPROM must first be enabled for writes + * A total of 9 clocks are needed. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void utilEEWriteOnOff(USHORT p_port,UCHAR p_mode) +#else +void utilEEWriteOnOff(ULONG p_port,UCHAR p_mode) +#endif +{ + UCHAR ee_value; + + ee_value = (UCHAR)(RD_HARPOON(p_port+hp_ee_ctrl) & (EXT_ARB_ACK | SCSI_TERM_ENA_H)); + + if (p_mode) + + utilEESendCmdAddr(p_port, EWEN, EWEN_ADDR); + + else + + + utilEESendCmdAddr(p_port, EWDS, EWDS_ADDR); + + WR_HARPOON(p_port+hp_ee_ctrl, (ee_value | SEE_MS)); /*Turn off CS */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); /*Turn off Master Select */ +} + + +/*--------------------------------------------------------------------- + * + * Function: Write EEPROM + * + * Description: Write a word to the EEPROM at the specified + * address. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void utilEEWrite(USHORT p_port, USHORT ee_data, USHORT ee_addr) +#else +void utilEEWrite(ULONG p_port, USHORT ee_data, USHORT ee_addr) +#endif +{ + + UCHAR ee_value; + USHORT i; + + ee_value = (UCHAR)((RD_HARPOON(p_port+hp_ee_ctrl) & (EXT_ARB_ACK | SCSI_TERM_ENA_H))| + (SEE_MS | SEE_CS)); + + + + utilEESendCmdAddr(p_port, EE_WRITE, ee_addr); + + + ee_value |= (SEE_MS + SEE_CS); + + for(i = 0x8000; i != 0; i>>=1) { + + if (i & ee_data) + ee_value |= SEE_DO; + else + ee_value &= ~SEE_DO; + + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + ee_value |= SEE_CLK; /* Clock data! */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + ee_value &= ~SEE_CLK; + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + } + ee_value &= (EXT_ARB_ACK | SCSI_TERM_ENA_H); + WR_HARPOON(p_port+hp_ee_ctrl, (ee_value | SEE_MS)); + + Wait(p_port, TO_10ms); + + WR_HARPOON(p_port+hp_ee_ctrl, (ee_value | SEE_MS | SEE_CS)); /* Set CS to EEPROM */ + WR_HARPOON(p_port+hp_ee_ctrl, (ee_value | SEE_MS)); /* Turn off CS */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); /* Turn off Master Select */ +} + +/*--------------------------------------------------------------------- + * + * Function: Read EEPROM + * + * Description: Read a word from the EEPROM at the desired + * address. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +USHORT utilEERead(USHORT p_port, USHORT ee_addr) +#else +USHORT utilEERead(ULONG p_port, USHORT ee_addr) +#endif +{ + USHORT i, ee_data1, ee_data2; + + i = 0; + ee_data1 = utilEEReadOrg(p_port, ee_addr); + do + { + ee_data2 = utilEEReadOrg(p_port, ee_addr); + + if(ee_data1 == ee_data2) + return(ee_data1); + + ee_data1 = ee_data2; + i++; + + }while(i < 4); + + return(ee_data1); +} + +/*--------------------------------------------------------------------- + * + * Function: Read EEPROM Original + * + * Description: Read a word from the EEPROM at the desired + * address. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +USHORT utilEEReadOrg(USHORT p_port, USHORT ee_addr) +#else +USHORT utilEEReadOrg(ULONG p_port, USHORT ee_addr) +#endif +{ + + UCHAR ee_value; + USHORT i, ee_data; + + ee_value = (UCHAR)((RD_HARPOON(p_port+hp_ee_ctrl) & (EXT_ARB_ACK | SCSI_TERM_ENA_H))| + (SEE_MS | SEE_CS)); + + + utilEESendCmdAddr(p_port, EE_READ, ee_addr); + + + ee_value |= (SEE_MS + SEE_CS); + ee_data = 0; + + for(i = 1; i <= 16; i++) { + + ee_value |= SEE_CLK; /* Clock data! */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + ee_value &= ~SEE_CLK; + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + + ee_data <<= 1; + + if (RD_HARPOON(p_port+hp_ee_ctrl) & SEE_DI) + ee_data |= 1; + } + + ee_value &= ~(SEE_MS + SEE_CS); + WR_HARPOON(p_port+hp_ee_ctrl, (ee_value | SEE_MS)); /*Turn off CS */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); /*Turn off Master Select */ + + return(ee_data); +} + + +/*--------------------------------------------------------------------- + * + * Function: Send EE command and Address to the EEPROM + * + * Description: Transfers the correct command and sends the address + * to the eeprom. + * + *---------------------------------------------------------------------*/ + +#if defined(DOS) +void utilEESendCmdAddr(USHORT p_port, UCHAR ee_cmd, USHORT ee_addr) +#else +void utilEESendCmdAddr(ULONG p_port, UCHAR ee_cmd, USHORT ee_addr) +#endif +{ + UCHAR ee_value; + UCHAR narrow_flg; + + USHORT i; + + + narrow_flg= (UCHAR)(RD_HARPOON(p_port+hp_page_ctrl) & NARROW_SCSI_CARD); + + + ee_value = SEE_MS; + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + + ee_value |= SEE_CS; /* Set CS to EEPROM */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + + + for(i = 0x04; i != 0; i>>=1) { + + if (i & ee_cmd) + ee_value |= SEE_DO; + else + ee_value &= ~SEE_DO; + + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + ee_value |= SEE_CLK; /* Clock data! */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + ee_value &= ~SEE_CLK; + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + } + + + if (narrow_flg) + i = 0x0080; + + else + i = 0x0200; + + + while (i != 0) { + + if (i & ee_addr) + ee_value |= SEE_DO; + else + ee_value &= ~SEE_DO; + + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + ee_value |= SEE_CLK; /* Clock data! */ + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + ee_value &= ~SEE_CLK; + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + WR_HARPOON(p_port+hp_ee_ctrl, ee_value); + + i >>= 1; + } +} + +USHORT CalcCrc16(UCHAR buffer[]) +{ + USHORT crc=0; + int i,j; + USHORT ch; + for (i=0; i < ID_STRING_LENGTH; i++) + { + ch = (USHORT) buffer[i]; + for(j=0; j < 8; j++) + { + if ((crc ^ ch) & 1) + crc = (crc >> 1) ^ CRCMASK; + else + crc >>= 1; + ch >>= 1; + } + } + return(crc); +} + +UCHAR CalcLrc(UCHAR buffer[]) +{ + int i; + UCHAR lrc; + lrc = 0; + for(i = 0; i < ID_STRING_LENGTH; i++) + lrc ^= buffer[i]; + return(lrc); +} + + + +/* + The following inline definitions avoid type conflicts. +*/ + +static inline unsigned char +FlashPoint__ProbeHostAdapter(FlashPoint_Info_T *FlashPointInfo) +{ + return FlashPoint_ProbeHostAdapter((PSCCBMGR_INFO) FlashPointInfo); +} + + +static inline FlashPoint_CardHandle_T +FlashPoint__HardwareResetHostAdapter(FlashPoint_Info_T *FlashPointInfo) +{ + return FlashPoint_HardwareResetHostAdapter((PSCCBMGR_INFO) FlashPointInfo); +} + +static inline void +FlashPoint__ReleaseHostAdapter(FlashPoint_CardHandle_T CardHandle) +{ + FlashPoint_ReleaseHostAdapter(CardHandle); +} + + +static inline void +FlashPoint__StartCCB(FlashPoint_CardHandle_T CardHandle, BusLogic_CCB_T *CCB) +{ + FlashPoint_StartCCB(CardHandle, (PSCCB) CCB); +} + + +static inline void +FlashPoint__AbortCCB(FlashPoint_CardHandle_T CardHandle, BusLogic_CCB_T *CCB) +{ + FlashPoint_AbortCCB(CardHandle, (PSCCB) CCB); +} + + +static inline boolean +FlashPoint__InterruptPending(FlashPoint_CardHandle_T CardHandle) +{ + return FlashPoint_InterruptPending(CardHandle); +} + + +static inline int +FlashPoint__HandleInterrupt(FlashPoint_CardHandle_T CardHandle) +{ + return FlashPoint_HandleInterrupt(CardHandle); +} + + +#define FlashPoint_ProbeHostAdapter FlashPoint__ProbeHostAdapter +#define FlashPoint_HardwareResetHostAdapter FlashPoint__HardwareResetHostAdapter +#define FlashPoint_ReleaseHostAdapter FlashPoint__ReleaseHostAdapter +#define FlashPoint_StartCCB FlashPoint__StartCCB +#define FlashPoint_AbortCCB FlashPoint__AbortCCB +#define FlashPoint_InterruptPending FlashPoint__InterruptPending +#define FlashPoint_HandleInterrupt FlashPoint__HandleInterrupt + + +/* + FlashPoint_InquireTargetInfo returns the Synchronous Period, Synchronous + Offset, and Wide Transfers Active information for TargetID on CardHandle. +*/ + +void FlashPoint_InquireTargetInfo(FlashPoint_CardHandle_T CardHandle, + int TargetID, + unsigned char *SynchronousPeriod, + unsigned char *SynchronousOffset, + unsigned char *WideTransfersActive) +{ + SCCBMGR_TAR_INFO *TargetInfo = + &sccbMgrTbl[((SCCBCARD *)CardHandle)->cardIndex][TargetID]; + if ((TargetInfo->TarSyncCtrl & SYNC_OFFSET) > 0) + { + *SynchronousPeriod = 5 * ((TargetInfo->TarSyncCtrl >> 5) + 1); + *SynchronousOffset = TargetInfo->TarSyncCtrl & SYNC_OFFSET; + } + else + { + *SynchronousPeriod = 0; + *SynchronousOffset = 0; + } + *WideTransfersActive = (TargetInfo->TarSyncCtrl & NARROW_SCSI ? 0 : 1); +} + + +#else /* CONFIG_SCSI_OMIT_FLASHPOINT */ + + +/* + Define prototypes for the FlashPoint SCCB Manager Functions. +*/ + +extern unsigned char FlashPoint_ProbeHostAdapter(FlashPoint_Info_T *); +extern FlashPoint_CardHandle_T + FlashPoint_HardwareResetHostAdapter(FlashPoint_Info_T *); +extern void FlashPoint_StartCCB(FlashPoint_CardHandle_T, BusLogic_CCB_T *); +extern int FlashPoint_AbortCCB(FlashPoint_CardHandle_T, BusLogic_CCB_T *); +extern boolean FlashPoint_InterruptPending(FlashPoint_CardHandle_T); +extern int FlashPoint_HandleInterrupt(FlashPoint_CardHandle_T); +extern void FlashPoint_ReleaseHostAdapter(FlashPoint_CardHandle_T); +extern void FlashPoint_InquireTargetInfo(FlashPoint_CardHandle_T, + int, unsigned char *, + unsigned char *, unsigned char *); + + +#endif /* CONFIG_SCSI_OMIT_FLASHPOINT */ diff --git a/linux/src/drivers/scsi/NCR5380.c b/linux/src/drivers/scsi/NCR5380.c new file mode 100644 index 00000000..e6fc75c9 --- /dev/null +++ b/linux/src/drivers/scsi/NCR5380.c @@ -0,0 +1,3247 @@ +#ifndef NDEBUG +#define NDEBUG (NDEBUG_RESTART_SELECT | NDEBUG_ABORT) +#endif +/* + * NCR 5380 generic driver routines. These should make it *trivial* + * to implement 5380 SCSI drivers under Linux with a non-trantor + * architecture. + * + * Note that these routines also work with NR53c400 family chips. + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 666-5836 + * + * DISTRIBUTION RELEASE 6. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * $Log: NCR5380.c,v $ + * Revision 1.7 1996/3/2 Ray Van Tassle (rayvt@comm.mot.com) + * added proc_info + * added support needed for DTC 3180/3280 + * fixed a couple of bugs + * + + * Revision 1.5 1994/01/19 09:14:57 drew + * Fixed udelay() hack that was being used on DATAOUT phases + * instead of a proper wait for the final handshake. + * + * Revision 1.4 1994/01/19 06:44:25 drew + * *** empty log message *** + * + * Revision 1.3 1994/01/19 05:24:40 drew + * Added support for TCR LAST_BYTE_SENT bit. + * + * Revision 1.2 1994/01/15 06:14:11 drew + * REAL DMA support, bug fixes. + * + * Revision 1.1 1994/01/15 06:00:54 drew + * Initial revision + * + */ + +/* + * Further development / testing that should be done : + * 1. Cleanup the NCR5380_transfer_dma function and DMA operation complete + * code so that everything does the same thing that's done at the + * end of a pseudo-DMA read operation. + * + * 2. Fix REAL_DMA (interrupt driven, polled works fine) - + * basically, transfer size needs to be reduced by one + * and the last byte read as is done with PSEUDO_DMA. + * + * 3. Test USLEEP code + * + * 4. Test SCSI-II tagged queueing (I have no devices which support + * tagged queueing) + * + * 5. Test linked command handling code after Eric is ready with + * the high level code. + */ + +#if (NDEBUG & NDEBUG_LISTS) +#define LIST(x,y) {printk("LINE:%d Adding %p to %p\n", __LINE__, (void*)(x), (void*)(y)); if ((x)==(y)) udelay(5); } +#define REMOVE(w,x,y,z) {printk("LINE:%d Removing: %p->%p %p->%p \n", __LINE__, (void*)(w), (void*)(x), (void*)(y), (void*)(z)); if ((x)==(y)) udelay(5); } +#else +#define LIST(x,y) +#define REMOVE(w,x,y,z) +#endif + +#ifndef notyet +#undef LINKED +#undef USLEEP +#undef REAL_DMA +#endif + +#ifdef REAL_DMA_POLL +#undef READ_OVERRUNS +#define READ_OVERRUNS +#endif + +/* + * Design + * Issues : + * + * The other Linux SCSI drivers were written when Linux was Intel PC-only, + * and specifically for each board rather than each chip. This makes their + * adaptation to platforms like the Mac (Some of which use NCR5380's) + * more difficult than it has to be. + * + * Also, many of the SCSI drivers were written before the command queuing + * routines were implemented, meaning their implementations of queued + * commands were hacked on rather than designed in from the start. + * + * When I designed the Linux SCSI drivers I figured that + * while having two different SCSI boards in a system might be useful + * for debugging things, two of the same type wouldn't be used. + * Well, I was wrong and a number of users have mailed me about running + * multiple high-performance SCSI boards in a server. + * + * Finally, when I get questions from users, I have no idea what + * revision of my driver they are running. + * + * This driver attempts to address these problems : + * This is a generic 5380 driver. To use it on a different platform, + * one simply writes appropriate system specific macros (ie, data + * transfer - some PC's will use the I/O bus, 68K's must use + * memory mapped) and drops this file in their 'C' wrapper. + * + * As far as command queueing, two queues are maintained for + * each 5380 in the system - commands that haven't been issued yet, + * and commands that are currently executing. This means that an + * unlimited number of commands may be queued, letting + * more commands propagate from the higher driver levels giving higher + * throughput. Note that both I_T_L and I_T_L_Q nexuses are supported, + * allowing multiple commands to propagate all the way to a SCSI-II device + * while a command is already executing. + * + * To solve the multiple-boards-in-the-same-system problem, + * there is a separate instance structure for each instance + * of a 5380 in the system. So, multiple NCR5380 drivers will + * be able to coexist with appropriate changes to the high level + * SCSI code. + * + * A NCR5380_PUBLIC_REVISION macro is provided, with the release + * number (updated for each public release) printed by the + * NCR5380_print_options command, which should be called from the + * wrapper detect function, so that I know what release of the driver + * users are using. + * + * Issues specific to the NCR5380 : + * + * When used in a PIO or pseudo-dma mode, the NCR5380 is a braindead + * piece of hardware that requires you to sit in a loop polling for + * the REQ signal as long as you are connected. Some devices are + * brain dead (ie, many TEXEL CD ROM drives) and won't disconnect + * while doing long seek operations. + * + * The workaround for this is to keep track of devices that have + * disconnected. If the device hasn't disconnected, for commands that + * should disconnect, we do something like + * + * while (!REQ is asserted) { sleep for N usecs; poll for M usecs } + * + * Some tweaking of N and M needs to be done. An algorithm based + * on "time to data" would give the best results as long as short time + * to datas (ie, on the same track) were considered, however these + * broken devices are the exception rather than the rule and I'd rather + * spend my time optimizing for the normal case. + * + * Architecture : + * + * At the heart of the design is a coroutine, NCR5380_main, + * which is started when not running by the interrupt handler, + * timer, and queue command function. It attempts to establish + * I_T_L or I_T_L_Q nexuses by removing the commands from the + * issue queue and calling NCR5380_select() if a nexus + * is not established. + * + * Once a nexus is established, the NCR5380_information_transfer() + * phase goes through the various phases as instructed by the target. + * if the target goes into MSG IN and sends a DISCONNECT message, + * the command structure is placed into the per instance disconnected + * queue, and NCR5380_main tries to find more work. If USLEEP + * was defined, and the target is idle for too long, the system + * will try to sleep. + * + * If a command has disconnected, eventually an interrupt will trigger, + * calling NCR5380_intr() which will in turn call NCR5380_reselect + * to reestablish a nexus. This will run main if necessary. + * + * On command termination, the done function will be called as + * appropriate. + * + * SCSI pointers are maintained in the SCp field of SCSI command + * structures, being initialized after the command is connected + * in NCR5380_select, and set as appropriate in NCR5380_information_transfer. + * Note that in violation of the standard, an implicit SAVE POINTERS operation + * is done, since some BROKEN disks fail to issue an explicit SAVE POINTERS. + */ + +/* + * Using this file : + * This file a skeleton Linux SCSI driver for the NCR 5380 series + * of chips. To use it, you write a architecture specific functions + * and macros and include this file in your driver. + * + * These macros control options : + * AUTOPROBE_IRQ - if defined, the NCR5380_probe_irq() function will be + * defined. + * + * AUTOSENSE - if defined, REQUEST SENSE will be performed automatically + * for commands that return with a CHECK CONDITION status. + * + * DIFFERENTIAL - if defined, NCR53c81 chips will use external differential + * transceivers. + * + * DONT_USE_INTR - if defined, never use interrupts, even if we probe or + * override-configure an IRQ. + * + * LIMIT_TRANSFERSIZE - if defined, limit the pseudo-dma transfers to 512 + * bytes at a time. Since interrupts are disabled by default during + * these transfers, we might need this to give reasonable interrupt + * service time if the transfer size gets too large. + * + * LINKED - if defined, linked commands are supported. + * + * PSEUDO_DMA - if defined, PSEUDO DMA is used during the data transfer phases. + * + * REAL_DMA - if defined, REAL DMA is used during the data transfer phases. + * + * REAL_DMA_POLL - if defined, REAL DMA is used but the driver doesn't + * rely on phase mismatch and EOP interrupts to determine end + * of phase. + * + * SCSI2 - if defined, SCSI-2 tagged queuing is used where possible + * + * UNSAFE - leave interrupts enabled during pseudo-DMA transfers. You + * only really want to use this if you're having a problem with + * dropped characters during high speed communications, and even + * then, you're going to be better off twiddling with transfersize + * in the high level code. + * + * USLEEP - if defined, on devices that aren't disconnecting from the + * bus, we will go to sleep so that the CPU can get real work done + * when we run a command that won't complete immediately. + * + * Note that if USLEEP is defined, NCR5380_TIMER *must* also be + * defined. + * + * Defaults for these will be provided if USLEEP is defined, although + * the user may want to adjust these to allocate CPU resources to + * the SCSI driver or "real" code. + * + * USLEEP_SLEEP - amount of time, in jiffies, to sleep + * + * USLEEP_POLL - amount of time, in jiffies, to poll + * + * These macros MUST be defined : + * NCR5380_local_declare() - declare any local variables needed for your + * transfer routines. + * + * NCR5380_setup(instance) - initialize any local variables needed from a given + * instance of the host adapter for NCR5380_{read,write,pread,pwrite} + * + * NCR5380_read(register) - read from the specified register + * + * NCR5380_write(register, value) - write to the specific register + * + * NCR5380_implementation_fields - additional fields needed for this + * specific implementation of the NCR5380 + * + * Either real DMA *or* pseudo DMA may be implemented + * REAL functions : + * NCR5380_REAL_DMA should be defined if real DMA is to be used. + * Note that the DMA setup functions should return the number of bytes + * that they were able to program the controller for. + * + * Also note that generic i386/PC versions of these macros are + * available as NCR5380_i386_dma_write_setup, + * NCR5380_i386_dma_read_setup, and NCR5380_i386_dma_residual. + * + * NCR5380_dma_write_setup(instance, src, count) - initialize + * NCR5380_dma_read_setup(instance, dst, count) - initialize + * NCR5380_dma_residual(instance); - residual count + * + * PSEUDO functions : + * NCR5380_pwrite(instance, src, count) + * NCR5380_pread(instance, dst, count); + * + * If nothing specific to this implementation needs doing (ie, with external + * hardware), you must also define + * + * NCR5380_queue_command + * NCR5380_reset + * NCR5380_abort + * NCR5380_proc_info + * + * to be the global entry points into the specific driver, ie + * #define NCR5380_queue_command t128_queue_command. + * + * If this is not done, the routines will be defined as static functions + * with the NCR5380* names and the user must provide a globally + * accessible wrapper function. + * + * The generic driver is initialized by calling NCR5380_init(instance), + * after setting the appropriate host specific fields and ID. If the + * driver wishes to autoprobe for an IRQ line, the NCR5380_probe_irq(instance, + * possible) function may be used. Before the specific driver initialization + * code finishes, NCR5380_print_options should be called. + */ + +static int do_abort (struct Scsi_Host *host); +static void do_reset (struct Scsi_Host *host); +static struct Scsi_Host *first_instance = NULL; +static Scsi_Host_Template *the_template = NULL; + +/* + * Function : void initialize_SCp(Scsi_Cmnd *cmd) + * + * Purpose : initialize the saved data pointers for cmd to point to the + * start of the buffer. + * + * Inputs : cmd - Scsi_Cmnd structure to have pointers reset. + */ + +static __inline__ void initialize_SCp(Scsi_Cmnd *cmd) { + /* + * Initialize the Scsi Pointer field so that all of the commands in the + * various queues are valid. + */ + + if (cmd->use_sg) { + cmd->SCp.buffer = (struct scatterlist *) cmd->buffer; + cmd->SCp.buffers_residual = cmd->use_sg - 1; + cmd->SCp.ptr = (char *) cmd->SCp.buffer->address; + cmd->SCp.this_residual = cmd->SCp.buffer->length; + } else { + cmd->SCp.buffer = NULL; + cmd->SCp.buffers_residual = 0; + cmd->SCp.ptr = (char *) cmd->request_buffer; + cmd->SCp.this_residual = cmd->request_bufflen; + } +} + +#include <linux/delay.h> + +#ifdef NDEBUG +static struct { + unsigned char mask; + const char * name;} +signals[] = {{ SR_DBP, "PARITY"}, { SR_RST, "RST" }, { SR_BSY, "BSY" }, + { SR_REQ, "REQ" }, { SR_MSG, "MSG" }, { SR_CD, "CD" }, { SR_IO, "IO" }, + { SR_SEL, "SEL" }, {0, NULL}}, +basrs[] = {{BASR_ATN, "ATN"}, {BASR_ACK, "ACK"}, {0, NULL}}, +icrs[] = {{ICR_ASSERT_RST, "ASSERT RST"},{ICR_ASSERT_ACK, "ASSERT ACK"}, + {ICR_ASSERT_BSY, "ASSERT BSY"}, {ICR_ASSERT_SEL, "ASSERT SEL"}, + {ICR_ASSERT_ATN, "ASSERT ATN"}, {ICR_ASSERT_DATA, "ASSERT DATA"}, + {0, NULL}}, +mrs[] = {{MR_BLOCK_DMA_MODE, "MODE BLOCK DMA"}, {MR_TARGET, "MODE TARGET"}, + {MR_ENABLE_PAR_CHECK, "MODE PARITY CHECK"}, {MR_ENABLE_PAR_INTR, + "MODE PARITY INTR"}, {MR_MONITOR_BSY, "MODE MONITOR BSY"}, + {MR_DMA_MODE, "MODE DMA"}, {MR_ARBITRATE, "MODE ARBITRATION"}, + {0, NULL}}; + +/* + * Function : void NCR5380_print(struct Scsi_Host *instance) + * + * Purpose : print the SCSI bus signals for debugging purposes + * + * Input : instance - which NCR5380 + */ + +static void NCR5380_print(struct Scsi_Host *instance) { + NCR5380_local_declare(); + unsigned char status, data, basr, mr, icr, i; + NCR5380_setup(instance); + cli(); + data = NCR5380_read(CURRENT_SCSI_DATA_REG); + status = NCR5380_read(STATUS_REG); + mr = NCR5380_read(MODE_REG); + icr = NCR5380_read(INITIATOR_COMMAND_REG); + basr = NCR5380_read(BUS_AND_STATUS_REG); + sti(); + printk("STATUS_REG: %02x ", status); + for (i = 0; signals[i].mask ; ++i) + if (status & signals[i].mask) + printk(",%s", signals[i].name); + printk("\nBASR: %02x ", basr); + for (i = 0; basrs[i].mask ; ++i) + if (basr & basrs[i].mask) + printk(",%s", basrs[i].name); + printk("\nICR: %02x ", icr); + for (i = 0; icrs[i].mask; ++i) + if (icr & icrs[i].mask) + printk(",%s", icrs[i].name); + printk("\nMODE: %02x ", mr); + for (i = 0; mrs[i].mask; ++i) + if (mr & mrs[i].mask) + printk(",%s", mrs[i].name); + printk("\n"); +} + +static struct { + unsigned char value; + const char *name; +} phases[] = { +{PHASE_DATAOUT, "DATAOUT"}, {PHASE_DATAIN, "DATAIN"}, {PHASE_CMDOUT, "CMDOUT"}, +{PHASE_STATIN, "STATIN"}, {PHASE_MSGOUT, "MSGOUT"}, {PHASE_MSGIN, "MSGIN"}, +{PHASE_UNKNOWN, "UNKNOWN"}}; + +/* + * Function : void NCR5380_print_phase(struct Scsi_Host *instance) + * + * Purpose : print the current SCSI phase for debugging purposes + * + * Input : instance - which NCR5380 + */ + +static void NCR5380_print_phase(struct Scsi_Host *instance) { + NCR5380_local_declare(); + unsigned char status; + int i; + NCR5380_setup(instance); + + status = NCR5380_read(STATUS_REG); + if (!(status & SR_REQ)) + printk("scsi%d : REQ not asserted, phase unknown.\n", + instance->host_no); + else { + for (i = 0; (phases[i].value != PHASE_UNKNOWN) && + (phases[i].value != (status & PHASE_MASK)); ++i); + printk("scsi%d : phase %s\n", instance->host_no, phases[i].name); + } +} +#endif + +/* + * We need to have our coroutine active given these constraints : + * 1. The mutex flag, main_running, can only be set when the main + * routine can actually process data, otherwise SCSI commands + * will never get issued. + * + * 2. NCR5380_main() shouldn't be called before it has exited, because + * other drivers have had kernel stack overflows in similar + * situations. + * + * 3. We don't want to inline NCR5380_main() because of space concerns, + * even though it is only called in two places. + * + * So, the solution is to set the mutex in an inline wrapper for the + * main coroutine, and have the main coroutine exit with interrupts + * disabled after the final search through the queues so that no race + * conditions are possible. + */ + +static volatile int main_running = 0; + +/* + * Function : run_main(void) + * + * Purpose : insure that the coroutine is running and will process our + * request. main_running is checked/set here (in an inline function) + * rather than in NCR5380_main itself to reduce the chances of stack + * overflow. + * + */ + +static __inline__ void run_main(void) { + cli(); + if (!main_running) { + main_running = 1; + NCR5380_main(); + /* + * main_running is cleared in NCR5380_main once it can't do + * more work, and NCR5380_main exits with interrupts disabled. + */ + sti(); + } else + sti(); +} + +#ifdef USLEEP +#ifndef NCR5380_TIMER +#error "NCR5380_TIMER must be defined so that this type of NCR5380 driver gets a unique timer." +#endif + +/* + * These need tweaking, and would probably work best as per-device + * flags initialized differently for disk, tape, cd, etc devices. + * People with broken devices are free to experiment as to what gives + * the best results for them. + * + * USLEEP_SLEEP should be a minimum seek time. + * + * USLEEP_POLL should be a maximum rotational latency. + */ +#ifndef USLEEP_SLEEP +/* 20 ms (reasonable hard disk speed) */ +#define USLEEP_SLEEP (20*HZ/1000) +#endif +/* 300 RPM (floppy speed) */ +#ifndef USLEEP_POLL +#define USLEEP_POLL (200*HZ/1000) +#endif + +static struct Scsi_Host * expires_first = NULL; + +/* + * Function : int should_disconnect (unsigned char cmd) + * + * Purpose : decide weather a command would normally disconnect or + * not, since if it won't disconnect we should go to sleep. + * + * Input : cmd - opcode of SCSI command + * + * Returns : DISCONNECT_LONG if we should disconnect for a really long + * time (ie always, sleep, look for REQ active, sleep), + * DISCONNECT_TIME_TO_DATA if we would only disconnect for a normal + * time-to-data delay, DISCONNECT_NONE if this command would return + * immediately. + * + * Future sleep algorithms based on time to data can exploit + * something like this so they can differentiate between "normal" + * (ie, read, write, seek) and unusual commands (ie, * format). + * + * Note : We don't deal with commands that handle an immediate disconnect, + * + */ + +static int should_disconnect (unsigned char cmd) { + switch (cmd) { + case READ_6: + case WRITE_6: + case SEEK_6: + case READ_10: + case WRITE_10: + case SEEK_10: + return DISCONNECT_TIME_TO_DATA; + case FORMAT_UNIT: + case SEARCH_HIGH: + case SEARCH_LOW: + case SEARCH_EQUAL: + return DISCONNECT_LONG; + default: + return DISCONNECT_NONE; + } +} + +/* + * Assumes instance->time_expires has been set in higher level code. + */ + +static int NCR5380_set_timer (struct Scsi_Host *instance) { + struct Scsi_Host *tmp, **prev; + + cli(); + if (((struct NCR5380_hostdata *) (instance->host_data))->next_timer) { + sti(); + return -1; + } + + for (prev = &expires_first, tmp = expires_first; tmp; + prev = &(((struct NCR5380_hostdata *) tmp->host_data)->next_timer), + tmp = ((struct NCR5380_hostdata *) tmp->host_data)->next_timer) + if (instance->time_expires < tmp->time_expires) + break; + + instance->next_timer = tmp; + *prev = instance; + timer_table[NCR5380_TIMER].expires = expires_first->time_expires; + timer_active |= 1 << NCR5380_TIMER; + sti(); + return 0; +} + +/* Doing something about unwanted reentrancy here might be useful */ +void NCR5380_timer_fn(void) { + struct Scsi_Host *instance; + cli(); + for (; expires_first && expires_first->time_expires >= jiffies; ) { + instance = ((NCR5380_hostdata *) expires_first->host_data)-> + expires_next; + ((NCR5380_hostdata *) expires_first->host_data)->expires_next = + NULL; + ((NCR5380_hostdata *) expires_first->host_data)->time_expires = + 0; + expires_first = instance; + } + + if (expires_first) { + timer_table[NCR5380_TIMER].expires = ((NCR5380_hostdata *) + expires_first->host_data)->time_expires; + timer_active |= (1 << NCR5380_TIMER); + } else { + timer_table[NCR5380_TIMER].expires = 0; + timer_active &= ~(1 << MCR5380_TIMER); + } + sti(); + + run_main(); +} +#endif /* def USLEEP */ + +static void NCR5380_all_init (void) { + static int done = 0; + if (!done) { +#if (NDEBUG & NDEBUG_INIT) + printk("scsi : NCR5380_all_init()\n"); +#endif + done = 1; +#ifdef USLEEP + timer_table[NCR5380_TIMER].expires = 0; + timer_table[NCR5380_TIMER].fn = NCR5380_timer_fn; +#endif + } +} + +#ifdef AUTOPROBE_IRQ +/* + * Function : int NCR5380_probe_irq (struct Scsi_Host *instance, int possible) + * + * Purpose : autoprobe for the IRQ line used by the NCR5380. + * + * Inputs : instance - pointer to this instance of the NCR5380 driver, + * possible - bitmask of permissible interrupts. + * + * Returns : number of the IRQ selected, IRQ_NONE if no interrupt fired. + * + * XXX no effort is made to deal with spurious interrupts. + */ + + +static int probe_irq; +static void probe_intr (int irq, void *dev_id, struct pt_regs * regs) { + probe_irq = irq; +}; + +static int NCR5380_probe_irq (struct Scsi_Host *instance, int possible) { + NCR5380_local_declare(); + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) + instance->hostdata; + unsigned long timeout; + int trying_irqs, i, mask; + NCR5380_setup(instance); + + for (trying_irqs = i = 0, mask = 1; i < 16; ++i, mask <<= 1) + if ((mask & possible) && (request_irq(i, &probe_intr, SA_INTERRUPT, "NCR-probe", NULL) + == 0)) + trying_irqs |= mask; + + timeout = jiffies + 250*HZ/1000; + probe_irq = IRQ_NONE; + +/* + * A interrupt is triggered whenever BSY = false, SEL = true + * and a bit set in the SELECT_ENABLE_REG is asserted on the + * SCSI bus. + * + * Note that the bus is only driven when the phase control signals + * (I/O, C/D, and MSG) match those in the TCR, so we must reset that + * to zero. + */ + + NCR5380_write(TARGET_COMMAND_REG, 0); + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + NCR5380_write(OUTPUT_DATA_REG, hostdata->id_mask); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_DATA | + ICR_ASSERT_SEL); + + while (probe_irq == IRQ_NONE && jiffies < timeout) + barrier(); + + NCR5380_write(SELECT_ENABLE_REG, 0); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + + for (i = 0, mask = 1; i < 16; ++i, mask <<= 1) + if (trying_irqs & mask) + free_irq(i, NULL); + + return probe_irq; +} +#endif /* AUTOPROBE_IRQ */ + +/* + * Function : void NCR58380_print_options (struct Scsi_Host *instance) + * + * Purpose : called by probe code indicating the NCR5380 driver + * options that were selected. + * + * Inputs : instance, pointer to this instance. Unused. + */ + +static void NCR5380_print_options (struct Scsi_Host *instance) { + printk(" generic options" +#ifdef AUTOPROBE_IRQ + " AUTOPROBE_IRQ" +#endif +#ifdef AUTOSENSE + " AUTOSENSE" +#endif +#ifdef DIFFERENTIAL + " DIFFERENTIAL" +#endif +#ifdef REAL_DMA + " REAL DMA" +#endif +#ifdef REAL_DMA_POLL + " REAL DMA POLL" +#endif +#ifdef PARITY + " PARITY" +#endif +#ifdef PSEUDO_DMA + " PSEUDO DMA" +#endif +#ifdef SCSI2 + " SCSI-2" +#endif +#ifdef UNSAFE + " UNSAFE " +#endif + ); +#ifdef USLEEP + printk(" USLEEP, USLEEP_POLL=%d USLEEP_SLEEP=%d", USLEEP_POLL, USLEEP_SLEEP); +#endif + printk(" generic release=%d", NCR5380_PUBLIC_RELEASE); + if (((struct NCR5380_hostdata *)instance->hostdata)->flags & FLAG_NCR53C400) { + printk(" ncr53c400 release=%d", NCR53C400_PUBLIC_RELEASE); + } +} + +/* + * Function : void NCR5380_print_status (struct Scsi_Host *instance) + * + * Purpose : print commands in the various queues, called from + * NCR5380_abort and NCR5380_debug to aid debugging. + * + * Inputs : instance, pointer to this instance. + */ + +static void NCR5380_print_status (struct Scsi_Host *instance) { + static char pr_bfr[512]; + char *start; + int len; + + printk("NCR5380 : coroutine is%s running.\n", + main_running ? "" : "n't"); + +#ifdef NDEBUG + NCR5380_print (instance); + NCR5380_print_phase (instance); +#endif + + len = NCR5380_proc_info(pr_bfr, &start, 0, sizeof(pr_bfr), + instance->host_no, 0); + pr_bfr[len] = 0; + printk("\n%s\n", pr_bfr); + } + +/******************************************/ +/* + * /proc/scsi/[dtc pas16 t128 generic]/[0-ASC_NUM_BOARD_SUPPORTED] + * + * *buffer: I/O buffer + * **start: if inout == FALSE pointer into buffer where user read should start + * offset: current offset + * length: length of buffer + * hostno: Scsi_Host host_no + * inout: TRUE - user is writing; FALSE - user is reading + * + * Return the number of bytes read from or written +*/ + +#undef SPRINTF +#define SPRINTF(args...) do { if(pos < buffer + length-80) pos += sprintf(pos, ## args); } while(0) +static +char *lprint_Scsi_Cmnd (Scsi_Cmnd *cmd, char *pos, char *buffer, int length); +static +char *lprint_command (unsigned char *cmd, char *pos, char *buffer, int len); +static +char *lprint_opcode(int opcode, char *pos, char *buffer, int length); + +#ifndef NCR5380_proc_info +static +#endif +int NCR5380_proc_info ( + char *buffer, char **start,off_t offset, + int length,int hostno,int inout) +{ + char *pos = buffer; + struct Scsi_Host *instance; + struct NCR5380_hostdata *hostdata; + Scsi_Cmnd *ptr; + + for (instance = first_instance; instance && + instance->host_no != hostno; instance=instance->next) + ; + if (!instance) + return(-ESRCH); + hostdata = (struct NCR5380_hostdata *)instance->hostdata; + + if (inout) { /* Has data been written to the file ? */ +#ifdef DTC_PUBLIC_RELEASE + dtc_wmaxi = dtc_maxi = 0; +#endif +#ifdef PAS16_PUBLIC_RELEASE + pas_wmaxi = pas_maxi = 0; +#endif + return(-ENOSYS); /* Currently this is a no-op */ + } + SPRINTF("NCR5380 core release=%d. ", NCR5380_PUBLIC_RELEASE); + if (((struct NCR5380_hostdata *)instance->hostdata)->flags & FLAG_NCR53C400) + SPRINTF("ncr53c400 release=%d. ", NCR53C400_PUBLIC_RELEASE); +#ifdef DTC_PUBLIC_RELEASE + SPRINTF("DTC 3180/3280 release %d", DTC_PUBLIC_RELEASE); +#endif +#ifdef T128_PUBLIC_RELEASE + SPRINTF("T128 release %d", T128_PUBLIC_RELEASE); +#endif +#ifdef GENERIC_NCR5380_PUBLIC_RELEASE + SPRINTF("Generic5380 release %d", GENERIC_NCR5380_PUBLIC_RELEASE); +#endif +#ifdef PAS16_PUBLIC_RELEASE +SPRINTF("PAS16 release=%d", PAS16_PUBLIC_RELEASE); +#endif + + SPRINTF("\nBase Addr: 0x%05lX ", (long)instance->base); + SPRINTF("io_port: %04x ", (int)instance->io_port); + if (instance->irq == IRQ_NONE) + SPRINTF("IRQ: None.\n"); + else + SPRINTF("IRQ: %d.\n", instance->irq); + +#ifdef DTC_PUBLIC_RELEASE + SPRINTF("Highwater I/O busy_spin_counts -- write: %d read: %d\n", + dtc_wmaxi, dtc_maxi); +#endif +#ifdef PAS16_PUBLIC_RELEASE + SPRINTF("Highwater I/O busy_spin_counts -- write: %d read: %d\n", + pas_wmaxi, pas_maxi); +#endif + cli(); + SPRINTF("NCR5380 : coroutine is%s running.\n", main_running ? "" : "n't"); + if (!hostdata->connected) + SPRINTF("scsi%d: no currently connected command\n", instance->host_no); + else + pos = lprint_Scsi_Cmnd ((Scsi_Cmnd *) hostdata->connected, + pos, buffer, length); + SPRINTF("scsi%d: issue_queue\n", instance->host_no); + for (ptr = (Scsi_Cmnd *) hostdata->issue_queue; ptr; + ptr = (Scsi_Cmnd *) ptr->host_scribble) + pos = lprint_Scsi_Cmnd (ptr, pos, buffer, length); + + SPRINTF("scsi%d: disconnected_queue\n", instance->host_no); + for (ptr = (Scsi_Cmnd *) hostdata->disconnected_queue; ptr; + ptr = (Scsi_Cmnd *) ptr->host_scribble) + pos = lprint_Scsi_Cmnd (ptr, pos, buffer, length); + + sti(); + *start=buffer; + if (pos - buffer < offset) + return 0; + else if (pos - buffer - offset < length) + return pos - buffer - offset; + return length; +} + +static +char *lprint_Scsi_Cmnd (Scsi_Cmnd *cmd, char *pos, char *buffer, int length) { + SPRINTF("scsi%d : destination target %d, lun %d\n", + cmd->host->host_no, cmd->target, cmd->lun); + SPRINTF(" command = "); + pos = lprint_command (cmd->cmnd, pos, buffer, length); + return (pos); +} + +static +char *lprint_command (unsigned char *command, + char *pos, char *buffer, int length) { + int i, s; + pos = lprint_opcode(command[0], pos, buffer, length); + for ( i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i) + SPRINTF("%02x ", command[i]); + SPRINTF("\n"); + return(pos); +} + +static +char *lprint_opcode(int opcode, char *pos, char *buffer, int length) { + SPRINTF("%2d (0x%02x)", opcode, opcode); + return(pos); +} + + +/* + * Function : void NCR5380_init (struct Scsi_Host *instance, flags) + * + * Purpose : initializes *instance and corresponding 5380 chip, + * with flags OR'd into the initial flags value. + * + * Inputs : instance - instantiation of the 5380 driver. + * + * Notes : I assume that the host, hostno, and id bits have been + * set correctly. I don't care about the irq and other fields. + * + */ + +static void NCR5380_init (struct Scsi_Host *instance, int flags) { + NCR5380_local_declare(); + int i, pass; + unsigned long timeout; + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) + instance->hostdata; + + /* + * On NCR53C400 boards, NCR5380 registers are mapped 8 past + * the base address. + */ + +#ifdef NCR53C400 + if (flags & FLAG_NCR53C400) + instance->NCR5380_instance_name += NCR53C400_address_adjust; +#endif + + NCR5380_setup(instance); + + NCR5380_all_init(); + + hostdata->aborted = 0; + hostdata->id_mask = 1 << instance->this_id; + for (i = hostdata->id_mask; i <= 0x80; i <<= 1) + if (i > hostdata->id_mask) + hostdata->id_higher_mask |= i; + for (i = 0; i < 8; ++i) + hostdata->busy[i] = 0; +#ifdef REAL_DMA + hostdata->dmalen = 0; +#endif + hostdata->targets_present = 0; + hostdata->connected = NULL; + hostdata->issue_queue = NULL; + hostdata->disconnected_queue = NULL; +#ifdef NCR5380_STATS + for (i = 0; i < 8; ++i) { + hostdata->time_read[i] = 0; + hostdata->time_write[i] = 0; + hostdata->bytes_read[i] = 0; + hostdata->bytes_write[i] = 0; + } + hostdata->timebase = 0; + hostdata->pendingw = 0; + hostdata->pendingr = 0; +#endif + + /* The CHECK code seems to break the 53C400. Will check it later maybe */ + if (flags & FLAG_NCR53C400) + hostdata->flags = FLAG_HAS_LAST_BYTE_SENT | flags; + else + hostdata->flags = FLAG_CHECK_LAST_BYTE_SENT | flags; + + if (!the_template) { + the_template = instance->hostt; + first_instance = instance; + } + + +#ifdef USLEEP + hostdata->time_expires = 0; + hostdata->next_timer = NULL; +#endif + +#ifndef AUTOSENSE + if ((instance->cmd_per_lun > 1) || instance->can_queue > 1)) + printk("scsi%d : WARNING : support for multiple outstanding commands enabled\n" + " without AUTOSENSE option, contingent allegiance conditions may\n" + " be incorrectly cleared.\n", instance->host_no); +#endif /* def AUTOSENSE */ + + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_write(TARGET_COMMAND_REG, 0); + NCR5380_write(SELECT_ENABLE_REG, 0); + +#ifdef NCR53C400 + if (hostdata->flags & FLAG_NCR53C400) { + NCR5380_write(C400_CONTROL_STATUS_REG, CSR_BASE); + } +#endif + + /* + * Detect and correct bus wedge problems. + * + * If the system crashed, it may have crashed in a state + * where a SCSI command was still executing, and the + * SCSI bus is not in a BUS FREE STATE. + * + * If this is the case, we'll try to abort the currently + * established nexus which we know nothing about, and that + * failing, do a hard reset of the SCSI bus + */ + + for (pass = 1; (NCR5380_read(STATUS_REG) & SR_BSY) && + pass <= 6 ; ++pass) { + switch (pass) { + case 1: + case 3: + case 5: + printk("scsi%d: SCSI bus busy, waiting up to five seconds\n", + instance->host_no); + timeout = jiffies + 5*HZ; + while (jiffies < timeout && (NCR5380_read(STATUS_REG) & SR_BSY)); + break; + case 2: + printk("scsi%d: bus busy, attempting abort\n", + instance->host_no); + do_abort (instance); + break; + case 4: + printk("scsi%d: bus busy, attempting reset\n", + instance->host_no); + do_reset (instance); + break; + case 6: + printk("scsi%d: bus locked solid or invalid override\n", + instance->host_no); + } + } +} + +/* + * Function : int NCR5380_queue_command (Scsi_Cmnd *cmd, + * void (*done)(Scsi_Cmnd *)) + * + * Purpose : enqueues a SCSI command + * + * Inputs : cmd - SCSI command, done - function called on completion, with + * a pointer to the command descriptor. + * + * Returns : 0 + * + * Side effects : + * cmd is added to the per instance issue_queue, with minor + * twiddling done to the host specific fields of cmd. If the + * main coroutine is not running, it is restarted. + * + */ + +/* Only make static if a wrapper function is used */ +#ifndef NCR5380_queue_command +static +#endif +int NCR5380_queue_command (Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) { + struct Scsi_Host *instance = cmd->host; + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) + instance->hostdata; + Scsi_Cmnd *tmp; + +#if (NDEBUG & NDEBUG_NO_WRITE) + switch (cmd->cmnd[0]) { + case WRITE_6: + case WRITE_10: + printk("scsi%d : WRITE attempted with NO_WRITE debugging flag set\n", + instance->host_no); + cmd->result = (DID_ERROR << 16); + done(cmd); + return 0; + } +#endif /* (NDEBUG & NDEBUG_NO_WRITE) */ + +#ifdef NCR5380_STATS +# if 0 + if (!hostdata->connected && !hostdata->issue_queue && + !hostdata->disconnected_queue) { + hostdata->timebase = jiffies; + } +# endif +# ifdef NCR5380_STAT_LIMIT + if (cmd->request_bufflen > NCR5380_STAT_LIMIT) +# endif + switch (cmd->cmnd[0]) + { + case WRITE: + case WRITE_6: + case WRITE_10: + hostdata->time_write[cmd->target] -= (jiffies - hostdata->timebase); + hostdata->bytes_write[cmd->target] += cmd->request_bufflen; + hostdata->pendingw++; + break; + case READ: + case READ_6: + case READ_10: + hostdata->time_read[cmd->target] -= (jiffies - hostdata->timebase); + hostdata->bytes_read[cmd->target] += cmd->request_bufflen; + hostdata->pendingr++; + break; + } +#endif + + /* + * We use the host_scribble field as a pointer to the next command + * in a queue + */ + + cmd->host_scribble = NULL; + cmd->scsi_done = done; + + cmd->result = 0; + + + /* + * Insert the cmd into the issue queue. Note that REQUEST SENSE + * commands are added to the head of the queue since any command will + * clear the contingent allegiance condition that exists and the + * sense data is only guaranteed to be valid while the condition exists. + */ + + cli(); + if (!(hostdata->issue_queue) || (cmd->cmnd[0] == REQUEST_SENSE)) { + LIST(cmd, hostdata->issue_queue); + cmd->host_scribble = (unsigned char *) hostdata->issue_queue; + hostdata->issue_queue = cmd; + } else { + for (tmp = (Scsi_Cmnd *) hostdata->issue_queue; tmp->host_scribble; + tmp = (Scsi_Cmnd *) tmp->host_scribble); + LIST(cmd, tmp); + tmp->host_scribble = (unsigned char *) cmd; + } +#if (NDEBUG & NDEBUG_QUEUES) + printk("scsi%d : command added to %s of queue\n", instance->host_no, + (cmd->cmnd[0] == REQUEST_SENSE) ? "head" : "tail"); +#endif + +/* Run the coroutine if it isn't already running. */ + run_main(); + return 0; +} + +/* + * Function : NCR5380_main (void) + * + * Purpose : NCR5380_main is a coroutine that runs as long as more work can + * be done on the NCR5380 host adapters in a system. Both + * NCR5380_queue_command() and NCR5380_intr() will try to start it + * in case it is not running. + * + * NOTE : NCR5380_main exits with interrupts *disabled*, the caller should + * reenable them. This prevents reentrancy and kernel stack overflow. + */ + +static void NCR5380_main (void) { + Scsi_Cmnd *tmp, *prev; + struct Scsi_Host *instance; + struct NCR5380_hostdata *hostdata; + int done; + + /* + * We run (with interrupts disabled) until we're sure that none of + * the host adapters have anything that can be done, at which point + * we set main_running to 0 and exit. + * + * Interrupts are enabled before doing various other internal + * instructions, after we've decided that we need to run through + * the loop again. + * + * this should prevent any race conditions. + */ + + do { + cli(); /* Freeze request queues */ + done = 1; + for (instance = first_instance; instance && + instance->hostt == the_template; instance=instance->next) { + hostdata = (struct NCR5380_hostdata *) instance->hostdata; + cli(); + if (!hostdata->connected) { +#if (NDEBUG & NDEBUG_MAIN) + printk("scsi%d : not connected\n", instance->host_no); +#endif + /* + * Search through the issue_queue for a command destined + * for a target that's not busy. + */ +#if (NDEBUG & NDEBUG_LISTS) + for (tmp= (Scsi_Cmnd *) hostdata->issue_queue, prev=NULL; tmp && (tmp != prev); prev=tmp, tmp=(Scsi_Cmnd*)tmp->host_scribble) + ; + /*printk("%p ", tmp);*/ + if ((tmp == prev) && tmp) printk(" LOOP\n");/* else printk("\n");*/ +#endif + for (tmp = (Scsi_Cmnd *) hostdata->issue_queue, + prev = NULL; tmp; prev = tmp, tmp = (Scsi_Cmnd *) + tmp->host_scribble) { + +#if (NDEBUG & NDEBUG_LISTS) + if (prev != tmp) + printk("MAIN tmp=%p target=%d busy=%d lun=%d\n", tmp, tmp->target, hostdata->busy[tmp->target], tmp->lun); +#endif + /* When we find one, remove it from the issue queue. */ + if (!(hostdata->busy[tmp->target] & (1 << tmp->lun))) { + if (prev) { + REMOVE(prev,prev->host_scribble,tmp,tmp->host_scribble); + prev->host_scribble = tmp->host_scribble; + } else { + REMOVE(-1,hostdata->issue_queue,tmp,tmp->host_scribble); + hostdata->issue_queue = (Scsi_Cmnd *) tmp->host_scribble; + } + tmp->host_scribble = NULL; + + /* reenable interrupts after finding one */ + sti(); + + /* + * Attempt to establish an I_T_L nexus here. + * On success, instance->hostdata->connected is set. + * On failure, we must add the command back to the + * issue queue so we can keep trying. + */ +#if (NDEBUG & (NDEBUG_MAIN | NDEBUG_QUEUES)) + printk("scsi%d : main() : command for target %d lun %d removed from issue_queue\n", + instance->host_no, tmp->target, tmp->lun); +#endif + + /* + * A successful selection is defined as one that + * leaves us with the command connected and + * in hostdata->connected, OR has terminated the + * command. + * + * With successful commands, we fall through + * and see if we can do an information transfer, + * with failures we will restart. + */ + + if (!NCR5380_select(instance, tmp, + /* + * REQUEST SENSE commands are issued without tagged + * queueing, even on SCSI-II devices because the + * contingent allegiance condition exists for the + * entire unit. + */ + (tmp->cmnd[0] == REQUEST_SENSE) ? TAG_NONE : + TAG_NEXT)) { + break; + } else { + cli(); + LIST(tmp, hostdata->issue_queue); + tmp->host_scribble = (unsigned char *) + hostdata->issue_queue; + hostdata->issue_queue = tmp; + done = 0; + sti(); +#if (NDEBUG & (NDEBUG_MAIN | NDEBUG_QUEUES)) + printk("scsi%d : main(): select() failed, returned to issue_queue\n", + instance->host_no); +#endif + } + } /* if target/lun is not busy */ + } /* for */ + } /* if (!hostdata->connected) */ + + if (hostdata->connected +#ifdef REAL_DMA + && !hostdata->dmalen +#endif +#ifdef USLEEP + && (!hostdata->time_expires || hostdata->time_expires >= jiffies) +#endif + ) { + sti(); +#if (NDEBUG & NDEBUG_MAIN) + printk("scsi%d : main() : performing information transfer\n", + instance->host_no); +#endif + NCR5380_information_transfer(instance); +#if (NDEBUG & NDEBUG_MAIN) + printk("scsi%d : main() : done set false\n", instance->host_no); +#endif + done = 0; + } else + break; + } /* for instance */ + } while (!done); + main_running = 0; +} + +#ifndef DONT_USE_INTR +/* + * Function : void NCR5380_intr (int irq) + * + * Purpose : handle interrupts, reestablishing I_T_L or I_T_L_Q nexuses + * from the disconnected queue, and restarting NCR5380_main() + * as required. + * + * Inputs : int irq, irq that caused this interrupt. + * + */ + +static void NCR5380_intr (int irq, void *dev_id, struct pt_regs * regs) { + NCR5380_local_declare(); + struct Scsi_Host *instance; + int done; + unsigned char basr; +#if (NDEBUG & NDEBUG_INTR) + printk("scsi : NCR5380 irq %d triggered\n", irq); +#endif + do { + done = 1; + for (instance = first_instance; instance && (instance->hostt == + the_template); instance = instance->next) + if (instance->irq == irq) { + + /* Look for pending interrupts */ + NCR5380_setup(instance); + basr = NCR5380_read(BUS_AND_STATUS_REG); + /* XXX dispatch to appropriate routine if found and done=0 */ + if (basr & BASR_IRQ) { +#if (NDEBUG & NDEBUG_INTR) + NCR5380_print(instance); +#endif + if ((NCR5380_read(STATUS_REG) & (SR_SEL | SR_IO)) == + (SR_SEL | SR_IO)) { + done = 0; + sti(); +#if (NDEBUG & NDEBUG_INTR) + printk("scsi%d : SEL interrupt\n", instance->host_no); +#endif + NCR5380_reselect(instance); + (void) NCR5380_read(RESET_PARITY_INTERRUPT_REG); + } else if (basr & BASR_PARITY_ERROR) { +#if (NDEBUG & NDEBUG_INTR) + printk("scsi%d : PARITY interrupt\n", instance->host_no); +#endif + (void) NCR5380_read(RESET_PARITY_INTERRUPT_REG); + } else if ((NCR5380_read(STATUS_REG) & SR_RST) == SR_RST) { +#if (NDEBUG & NDEBUG_INTR) + printk("scsi%d : RESET interrupt\n", instance->host_no); +#endif + (void)NCR5380_read(RESET_PARITY_INTERRUPT_REG); + } else { +/* + * XXX the rest of the interrupt conditions should *only* occur during a + * DMA transfer, which I haven't gotten around to fixing yet. + */ + +#if defined(REAL_DMA) + /* + * We should only get PHASE MISMATCH and EOP interrupts + * if we have DMA enabled, so do a sanity check based on + * the current setting of the MODE register. + */ + + if ((NCR5380_read(MODE_REG) & MR_DMA) && ((basr & + BASR_END_DMA_TRANSFER) || + !(basr & BASR_PHASE_MATCH))) { + int transfered; + + if (!hostdata->connected) + panic("scsi%d : received end of DMA interrupt with no connected cmd\n", + instance->hostno); + + transfered = (hostdata->dmalen - NCR5380_dma_residual(instance)); + hostdata->connected->SCp.this_residual -= transferred; + hostdata->connected->SCp.ptr += transferred; + hostdata->dmalen = 0; + + (void) NCR5380_read(RESET_PARITY_INTERRUPT_REG); +#if NCR_TIMEOUT + { + unsigned long timeout = jiffies + NCR_TIMEOUT; + + while (NCR5380_read(BUS_AND_STATUS_REG) & BASR_ACK + && jiffies < timeout) + ; + if (jiffies >= timeout) + printk("scsi%d: timeout at NCR5380.c:%d\n", + host->host_no, __LINE__); + } +#else /* NCR_TIMEOUT */ + while (NCR5380_read(BUS_AND_STATUS_REG) & BASR_ACK); +#endif + + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + } +#else +#if (NDEBUG & NDEBUG_INTR) + printk("scsi : unknown interrupt, BASR 0x%X, MR 0x%X, SR 0x%x\n", basr, NCR5380_read(MODE_REG), NCR5380_read(STATUS_REG)); +#endif + (void) NCR5380_read(RESET_PARITY_INTERRUPT_REG); +#endif + } + } /* if BASR_IRQ */ + if (!done) + run_main(); + } /* if (instance->irq == irq) */ + } while (!done); +} +#endif + +#ifdef NCR5380_STATS +static void collect_stats(struct NCR5380_hostdata* hostdata, Scsi_Cmnd* cmd) +{ +# ifdef NCR5380_STAT_LIMIT + if (cmd->request_bufflen > NCR5380_STAT_LIMIT) +# endif + switch (cmd->cmnd[0]) + { + case WRITE: + case WRITE_6: + case WRITE_10: + hostdata->time_write[cmd->target] += (jiffies - hostdata->timebase); + /*hostdata->bytes_write[cmd->target] += cmd->request_bufflen;*/ + hostdata->pendingw--; + break; + case READ: + case READ_6: + case READ_10: + hostdata->time_read[cmd->target] += (jiffies - hostdata->timebase); + /*hostdata->bytes_read[cmd->target] += cmd->request_bufflen;*/ + hostdata->pendingr--; + break; + } +} +#endif + +/* + * Function : int NCR5380_select (struct Scsi_Host *instance, Scsi_Cmnd *cmd, + * int tag); + * + * Purpose : establishes I_T_L or I_T_L_Q nexus for new or existing command, + * including ARBITRATION, SELECTION, and initial message out for + * IDENTIFY and queue messages. + * + * Inputs : instance - instantiation of the 5380 driver on which this + * target lives, cmd - SCSI command to execute, tag - set to TAG_NEXT for + * new tag, TAG_NONE for untagged queueing, otherwise set to the tag for + * the command that is presently connected. + * + * Returns : -1 if selection could not execute for some reason, + * 0 if selection succeeded or failed because the target + * did not respond. + * + * Side effects : + * If bus busy, arbitration failed, etc, NCR5380_select() will exit + * with registers as they should have been on entry - ie + * SELECT_ENABLE will be set appropriately, the NCR5380 + * will cease to drive any SCSI bus signals. + * + * If successful : I_T_L or I_T_L_Q nexus will be established, + * instance->connected will be set to cmd. + * SELECT interrupt will be disabled. + * + * If failed (no target) : cmd->scsi_done() will be called, and the + * cmd->result host byte set to DID_BAD_TARGET. + */ + +static int NCR5380_select (struct Scsi_Host *instance, Scsi_Cmnd *cmd, + int tag) { + NCR5380_local_declare(); + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata*) + instance->hostdata; + unsigned char tmp[3], phase; + unsigned char *data; + int len; + unsigned long timeout; + NCR5380_setup(instance); + + hostdata->restart_select = 0; +#if defined (NDEBUG) && (NDEBUG & NDEBUG_ARBITRATION) + NCR5380_print(instance); + printk("scsi%d : starting arbitration, id = %d\n", instance->host_no, + instance->this_id); +#endif + cli(); + + /* + * Set the phase bits to 0, otherwise the NCR5380 won't drive the + * data bus during SELECTION. + */ + + NCR5380_write(TARGET_COMMAND_REG, 0); + + + /* + * Start arbitration. + */ + + NCR5380_write(OUTPUT_DATA_REG, hostdata->id_mask); + NCR5380_write(MODE_REG, MR_ARBITRATE); + + sti(); + + /* Wait for arbitration logic to complete */ +#if NCR_TIMEOUT + { + unsigned long timeout = jiffies + 2*NCR_TIMEOUT; + + while (!(NCR5380_read(INITIATOR_COMMAND_REG) & ICR_ARBITRATION_PROGRESS) + && jiffies < timeout) + ; + if (jiffies >= timeout) + { + printk("scsi: arbitration timeout at %d\n", __LINE__); + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + return -1; + } + } +#else /* NCR_TIMEOUT */ + while (!(NCR5380_read(INITIATOR_COMMAND_REG) & ICR_ARBITRATION_PROGRESS)); +#endif + +#if (NDEBUG & NDEBUG_ARBITRATION) + printk("scsi%d : arbitration complete\n", instance->host_no); +/* Avoid GCC 2.4.5 asm needs to many reloads error */ + __asm__("nop"); +#endif + + /* + * The arbitration delay is 2.2us, but this is a minimum and there is + * no maximum so we can safely sleep for ceil(2.2) usecs to accommodate + * the integral nature of udelay(). + * + */ + + udelay(3); + + /* Check for lost arbitration */ + if ((NCR5380_read(INITIATOR_COMMAND_REG) & ICR_ARBITRATION_LOST) || + (NCR5380_read(CURRENT_SCSI_DATA_REG) & hostdata->id_higher_mask) || + (NCR5380_read(INITIATOR_COMMAND_REG) & ICR_ARBITRATION_LOST)) { + NCR5380_write(MODE_REG, MR_BASE); +#if (NDEBUG & NDEBUG_ARBITRATION) + printk("scsi%d : lost arbitration, deasserting MR_ARBITRATE\n", + instance->host_no); +#endif + return -1; + } + + + + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_SEL); + + if (NCR5380_read(INITIATOR_COMMAND_REG) & ICR_ARBITRATION_LOST) { + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); +#if (NDEBUG & NDEBUG_ARBITRATION) + printk("scsi%d : lost arbitration, deasserting ICR_ASSERT_SEL\n", + instance->host_no); +#endif + return -1; + } + + /* + * Again, bus clear + bus settle time is 1.2us, however, this is + * a minimum so we'll udelay ceil(1.2) + */ + + udelay(2); + +#if (NDEBUG & NDEBUG_ARBITRATION) + printk("scsi%d : won arbitration\n", instance->host_no); +#endif + + + /* + * Now that we have won arbitration, start Selection process, asserting + * the host and target ID's on the SCSI bus. + */ + + NCR5380_write(OUTPUT_DATA_REG, (hostdata->id_mask | (1 << cmd->target))); + + /* + * Raise ATN while SEL is true before BSY goes false from arbitration, + * since this is the only way to guarantee that we'll get a MESSAGE OUT + * phase immediately after selection. + */ + + NCR5380_write(INITIATOR_COMMAND_REG, (ICR_BASE | ICR_ASSERT_BSY | + ICR_ASSERT_DATA | ICR_ASSERT_ATN | ICR_ASSERT_SEL )); + NCR5380_write(MODE_REG, MR_BASE); + + /* + * Reselect interrupts must be turned off prior to the dropping of BSY, + * otherwise we will trigger an interrupt. + */ + NCR5380_write(SELECT_ENABLE_REG, 0); + + /* + * The initiator shall then wait at least two deskew delays and release + * the BSY signal. + */ + udelay(1); /* wingel -- wait two bus deskew delay >2*45ns */ + + /* Reset BSY */ + NCR5380_write(INITIATOR_COMMAND_REG, (ICR_BASE | ICR_ASSERT_DATA | + ICR_ASSERT_ATN | ICR_ASSERT_SEL)); + + /* + * Something weird happens when we cease to drive BSY - looks + * like the board/chip is letting us do another read before the + * appropriate propagation delay has expired, and we're confusing + * a BSY signal from ourselves as the target's response to SELECTION. + * + * A small delay (the 'C++' frontend breaks the pipeline with an + * unnecessary jump, making it work on my 386-33/Trantor T128, the + * tighter 'C' code breaks and requires this) solves the problem - + * the 1 us delay is arbitrary, and only used because this delay will + * be the same on other platforms and since it works here, it should + * work there. + * + * wingel suggests that this could be due to failing to wait + * one deskew delay. + */ + + udelay(1); + +#if (NDEBUG & NDEBUG_SELECTION) + printk("scsi%d : selecting target %d\n", instance->host_no, cmd->target); +#endif + + /* + * The SCSI specification calls for a 250 ms timeout for the actual + * selection. + */ + + timeout = jiffies + 250*HZ/1000; + + /* + * XXX very interesting - we're seeing a bounce where the BSY we + * asserted is being reflected / still asserted (propagation delay?) + * and it's detecting as true. Sigh. + */ + + while ((jiffies < timeout) && !(NCR5380_read(STATUS_REG) & + (SR_BSY | SR_IO))); + + if ((NCR5380_read(STATUS_REG) & (SR_SEL | SR_IO)) == + (SR_SEL | SR_IO)) { + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + NCR5380_reselect(instance); + printk ("scsi%d : reselection after won arbitration?\n", + instance->host_no); + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + return -1; + } + + /* + * No less than two deskew delays after the initiator detects the + * BSY signal is true, it shall release the SEL signal and may + * change the DATA BUS. -wingel + */ + + udelay(1); + + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN); + + if (!(NCR5380_read(STATUS_REG) & SR_BSY)) { + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + if (hostdata->targets_present & (1 << cmd->target)) { + printk("scsi%d : weirdness\n", instance->host_no); + if (hostdata->restart_select) + printk("\trestart select\n"); +#ifdef NDEBUG + NCR5380_print (instance); +#endif + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + return -1; + } + cmd->result = DID_BAD_TARGET << 16; +#ifdef NCR5380_STATS + collect_stats(hostdata, cmd); +#endif + cmd->scsi_done(cmd); + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); +#if (NDEBUG & NDEBUG_SELECTION) + printk("scsi%d : target did not respond within 250ms\n", + instance->host_no); +#endif + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + return 0; + } + + hostdata->targets_present |= (1 << cmd->target); + + /* + * Since we followed the SCSI spec, and raised ATN while SEL + * was true but before BSY was false during selection, the information + * transfer phase should be a MESSAGE OUT phase so that we can send the + * IDENTIFY message. + * + * If SCSI-II tagged queuing is enabled, we also send a SIMPLE_QUEUE_TAG + * message (2 bytes) with a tag ID that we increment with every command + * until it wraps back to 0. + * + * XXX - it turns out that there are some broken SCSI-II devices, + * which claim to support tagged queuing but fail when more than + * some number of commands are issued at once. + */ + + /* Wait for start of REQ/ACK handshake */ +#ifdef NCR_TIMEOUT + { + unsigned long timeout = jiffies + NCR_TIMEOUT; + + while (!(NCR5380_read(STATUS_REG) & SR_REQ) && jiffies < timeout); + + if (jiffies >= timeout) { + printk("scsi%d: timeout at NCR5380.c:%d\n", __LINE__); + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + return -1; + } + } +#else /* NCR_TIMEOUT */ + while (!(NCR5380_read(STATUS_REG) & SR_REQ)); +#endif /* def NCR_TIMEOUT */ + +#if (NDEBUG & NDEBUG_SELECTION) + printk("scsi%d : target %d selected, going into MESSAGE OUT phase.\n", + instance->host_no, cmd->target); +#endif + tmp[0] = IDENTIFY(((instance->irq == IRQ_NONE) ? 0 : 1), cmd->lun); +#ifdef SCSI2 + if (cmd->device->tagged_queue && (tag != TAG_NONE)) { + tmp[1] = SIMPLE_QUEUE_TAG; + if (tag == TAG_NEXT) { + /* 0 is TAG_NONE, used to imply no tag for this command */ + if (cmd->device->current_tag == 0) + cmd->device->current_tag = 1; + + cmd->tag = cmd->device->current_tag; + cmd->device->current_tag++; + } else + cmd->tag = (unsigned char) tag; + + tmp[2] = cmd->tag; + hostdata->last_message = SIMPLE_QUEUE_TAG; + len = 3; + } else +#endif /* def SCSI2 */ + { + len = 1; + cmd->tag=0; + } + + /* Send message(s) */ + data = tmp; + phase = PHASE_MSGOUT; + NCR5380_transfer_pio(instance, &phase, &len, &data); +#if (NDEBUG & NDEBUG_SELECTION) + printk("scsi%d : nexus established.\n", instance->host_no); +#endif + /* XXX need to handle errors here */ + hostdata->connected = cmd; +#ifdef SCSI2 + if (!cmd->device->tagged_queue) +#endif + hostdata->busy[cmd->target] |= (1 << cmd->lun); + + initialize_SCp(cmd); + + + return 0; +} + +/* + * Function : int NCR5380_transfer_pio (struct Scsi_Host *instance, + * unsigned char *phase, int *count, unsigned char **data) + * + * Purpose : transfers data in given phase using polled I/O + * + * Inputs : instance - instance of driver, *phase - pointer to + * what phase is expected, *count - pointer to number of + * bytes to transfer, **data - pointer to data pointer. + * + * Returns : -1 when different phase is entered without transferring + * maximum number of bytes, 0 if all bytes or transfered or exit + * is in same phase. + * + * Also, *phase, *count, *data are modified in place. + * + * XXX Note : handling for bus free may be useful. + */ + +/* + * Note : this code is not as quick as it could be, however it + * IS 100% reliable, and for the actual data transfer where speed + * counts, we will always do a pseudo DMA or DMA transfer. + */ + +static int NCR5380_transfer_pio (struct Scsi_Host *instance, + unsigned char *phase, int *count, unsigned char **data) { + NCR5380_local_declare(); + register unsigned char p = *phase, tmp; + register int c = *count; + register unsigned char *d = *data; + NCR5380_setup(instance); + +#if (NDEBUG & NDEBUG_PIO) + if (!(p & SR_IO)) + printk("scsi%d : pio write %d bytes\n", instance->host_no, c); + else + printk("scsi%d : pio read %d bytes\n", instance->host_no, c); +#endif + + /* + * The NCR5380 chip will only drive the SCSI bus when the + * phase specified in the appropriate bits of the TARGET COMMAND + * REGISTER match the STATUS REGISTER + */ + + NCR5380_write(TARGET_COMMAND_REG, PHASE_SR_TO_TCR(p)); + + do { + /* + * Wait for assertion of REQ, after which the phase bits will be + * valid + */ + while (!((tmp = NCR5380_read(STATUS_REG)) & SR_REQ)); + +#if (NDEBUG & NDEBUG_HANDSHAKE) + printk("scsi%d : REQ detected\n", instance->host_no); +#endif + + /* Check for phase mismatch */ + if ((tmp & PHASE_MASK) != p) { +#if (NDEBUG & NDEBUG_PIO) + printk("scsi%d : phase mismatch\n", instance->host_no); + NCR5380_print_phase(instance); +#endif + break; + } + + /* Do actual transfer from SCSI bus to / from memory */ + if (!(p & SR_IO)) + NCR5380_write(OUTPUT_DATA_REG, *d); + else + *d = NCR5380_read(CURRENT_SCSI_DATA_REG); + + ++d; + + /* + * The SCSI standard suggests that in MSGOUT phase, the initiator + * should drop ATN on the last byte of the message phase + * after REQ has been asserted for the handshake but before + * the initiator raises ACK. + */ + + if (!(p & SR_IO)) { + if (!((p & SR_MSG) && c > 1)) { + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | + ICR_ASSERT_DATA); +#if (NDEBUG & NDEBUG_PIO) + NCR5380_print(instance); +#endif + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | + ICR_ASSERT_DATA | ICR_ASSERT_ACK); + } else { + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | + ICR_ASSERT_DATA | ICR_ASSERT_ATN); +#if (NDEBUG & NDEBUG_PIO) + NCR5380_print(instance); +#endif + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | + ICR_ASSERT_DATA | ICR_ASSERT_ATN | ICR_ASSERT_ACK); + } + } else { +#if (NDEBUG & NDEBUG_PIO) + NCR5380_print(instance); +#endif + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ACK); + } + + while (NCR5380_read(STATUS_REG) & SR_REQ); + +#if (NDEBUG & NDEBUG_HANDSHAKE) + printk("scsi%d : req false, handshake complete\n", instance->host_no); +#endif + +/* + * We have several special cases to consider during REQ/ACK handshaking : + * 1. We were in MSGOUT phase, and we are on the last byte of the + * message. ATN must be dropped as ACK is dropped. + * + * 2. We are in a MSGIN phase, and we are on the last byte of the + * message. We must exit with ACK asserted, so that the calling + * code may raise ATN before dropping ACK to reject the message. + * + * 3. ACK and ATN are clear and the target may proceed as normal. + */ + if (!(p == PHASE_MSGIN && c == 1)) { + if (p == PHASE_MSGOUT && c > 1) + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN); + else + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + } + } while (--c); + +#if (NDEBUG & NDEBUG_PIO) + printk("scsi%d : residual %d\n", instance->host_no, c); +#endif + + *count = c; + *data = d; + tmp = NCR5380_read(STATUS_REG); + if (tmp & SR_REQ) + *phase = tmp & PHASE_MASK; + else + *phase = PHASE_UNKNOWN; + + if (!c || (*phase == p)) + return 0; + else + return -1; +} + +static void do_reset (struct Scsi_Host *host) { + NCR5380_local_declare(); + NCR5380_setup(host); + + cli(); + NCR5380_write(TARGET_COMMAND_REG, + PHASE_SR_TO_TCR(NCR5380_read(STATUS_REG) & PHASE_MASK)); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_RST); + udelay(25); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + sti(); +} + +/* + * Function : do_abort (Scsi_Host *host) + * + * Purpose : abort the currently established nexus. Should only be + * called from a routine which can drop into a + * + * Returns : 0 on success, -1 on failure. + */ + +static int do_abort (struct Scsi_Host *host) { + NCR5380_local_declare(); + unsigned char tmp, *msgptr, phase; + int len; + NCR5380_setup(host); + + + /* Request message out phase */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN); + + /* + * Wait for the target to indicate a valid phase by asserting + * REQ. Once this happens, we'll have either a MSGOUT phase + * and can immediately send the ABORT message, or we'll have some + * other phase and will have to source/sink data. + * + * We really don't care what value was on the bus or what value + * the target sees, so we just handshake. + */ + + while (!(tmp = NCR5380_read(STATUS_REG)) & SR_REQ); + + NCR5380_write(TARGET_COMMAND_REG, PHASE_SR_TO_TCR(tmp)); + + if ((tmp & PHASE_MASK) != PHASE_MSGOUT) { + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN | + ICR_ASSERT_ACK); + while (NCR5380_read(STATUS_REG) & SR_REQ); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN); + } + + tmp = ABORT; + msgptr = &tmp; + len = 1; + phase = PHASE_MSGOUT; + NCR5380_transfer_pio (host, &phase, &len, &msgptr); + + /* + * If we got here, and the command completed successfully, + * we're about to go into bus free state. + */ + + return len ? -1 : 0; +} + +#if defined(REAL_DMA) || defined(PSEUDO_DMA) || defined (REAL_DMA_POLL) +/* + * Function : int NCR5380_transfer_dma (struct Scsi_Host *instance, + * unsigned char *phase, int *count, unsigned char **data) + * + * Purpose : transfers data in given phase using either real + * or pseudo DMA. + * + * Inputs : instance - instance of driver, *phase - pointer to + * what phase is expected, *count - pointer to number of + * bytes to transfer, **data - pointer to data pointer. + * + * Returns : -1 when different phase is entered without transferring + * maximum number of bytes, 0 if all bytes or transfered or exit + * is in same phase. + * + * Also, *phase, *count, *data are modified in place. + * + */ + + +static int NCR5380_transfer_dma (struct Scsi_Host *instance, + unsigned char *phase, int *count, unsigned char **data) { + NCR5380_local_declare(); + register int c = *count; + register unsigned char p = *phase; + register unsigned char *d = *data; + unsigned char tmp; + int foo; +#if defined(REAL_DMA_POLL) + int cnt, toPIO; + unsigned char saved_data = 0, overrun = 0, residue; +#endif + + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) + instance->hostdata; + + NCR5380_setup(instance); + + if ((tmp = (NCR5380_read(STATUS_REG) & PHASE_MASK)) != p) { + *phase = tmp; + return -1; + } +#if defined(REAL_DMA) || defined(REAL_DMA_POLL) +#ifdef READ_OVERRUNS + if (p & SR_IO) { + c -= 2; + } +#endif +#if (NDEBUG & NDEBUG_DMA) + printk("scsi%d : initializing DMA channel %d for %s, %d bytes %s %0x\n", + instance->host_no, instance->dma_channel, (p & SR_IO) ? "reading" : + "writing", c, (p & SR_IO) ? "to" : "from", (unsigned) d); +#endif + hostdata->dma_len = (p & SR_IO) ? + NCR5380_dma_read_setup(instance, d, c) : + NCR5380_dma_write_setup(instance, d, c); +#endif + + NCR5380_write(TARGET_COMMAND_REG, PHASE_SR_TO_TCR(p)); + +#ifdef REAL_DMA + NCR5380_write(MODE_REG, MR_BASE | MR_DMA_MODE | MR_ENABLE_EOP_INTR | MR_MONITOR_BSY); +#elif defined(REAL_DMA_POLL) + NCR5380_write(MODE_REG, MR_BASE | MR_DMA_MODE); +#else + /* + * Note : on my sample board, watch-dog timeouts occurred when interrupts + * were not disabled for the duration of a single DMA transfer, from + * before the setting of DMA mode to after transfer of the last byte. + */ + +#if defined(PSEUDO_DMA) && !defined(UNSAFE) + cli(); +#endif + /* KLL May need eop and parity in 53c400 */ + if (hostdata->flags & FLAG_NCR53C400) + NCR5380_write(MODE_REG, MR_BASE | MR_DMA_MODE | MR_ENABLE_PAR_CHECK + | MR_ENABLE_PAR_INTR | MR_ENABLE_EOP_INTR | MR_DMA_MODE + | MR_MONITOR_BSY); + else + NCR5380_write(MODE_REG, MR_BASE | MR_DMA_MODE); +#endif /* def REAL_DMA */ + +#if (NDEBUG & NDEBUG_DMA) & 0 + printk("scsi%d : mode reg = 0x%X\n", instance->host_no, NCR5380_read(MODE_REG)); +#endif + +/* + * FOO stuff. For some UNAPPARENT reason, I'm getting + * watchdog timers fired on bootup for NO APPARENT REASON, meaning it's + * probably a timing problem. + * + * Since this is the only place I have back-to-back writes, perhaps this + * is the problem? + */ + + if (p & SR_IO) { +#ifndef FOO + udelay(1); +#endif + NCR5380_write(START_DMA_INITIATOR_RECEIVE_REG, 0); + } else { +#ifndef FOO + udelay(1); +#endif + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_DATA); +#ifndef FOO + udelay(1); +#endif + NCR5380_write(START_DMA_SEND_REG, 0); +#ifndef FOO + udelay(1); +#endif + } + +#if defined(REAL_DMA_POLL) + do { + tmp = NCR5380_read(BUS_AND_STATUS_REG); + } while ((tmp & BASR_PHASE_MATCH) && !(tmp & (BASR_BUSY_ERROR | + BASR_END_DMA_TRANSFER))); + +/* + At this point, either we've completed DMA, or we have a phase mismatch, + or we've unexpectedly lost BUSY (which is a real error). + + For write DMAs, we want to wait until the last byte has been + transferred out over the bus before we turn off DMA mode. Alas, there + seems to be no terribly good way of doing this on a 5380 under all + conditions. For non-scatter-gather operations, we can wait until REQ + and ACK both go false, or until a phase mismatch occurs. Gather-writes + are nastier, since the device will be expecting more data than we + are prepared to send it, and REQ will remain asserted. On a 53C8[01] we + could test LAST BIT SENT to assure transfer (I imagine this is precisely + why this signal was added to the newer chips) but on the older 538[01] + this signal does not exist. The workaround for this lack is a watchdog; + we bail out of the wait-loop after a modest amount of wait-time if + the usual exit conditions are not met. Not a terribly clean or + correct solution :-% + + Reads are equally tricky due to a nasty characteristic of the NCR5380. + If the chip is in DMA mode for an READ, it will respond to a target's + REQ by latching the SCSI data into the INPUT DATA register and asserting + ACK, even if it has _already_ been notified by the DMA controller that + the current DMA transfer has completed! If the NCR5380 is then taken + out of DMA mode, this already-acknowledged byte is lost. + + This is not a problem for "one DMA transfer per command" reads, because + the situation will never arise... either all of the data is DMA'ed + properly, or the target switches to MESSAGE IN phase to signal a + disconnection (either operation bringing the DMA to a clean halt). + However, in order to handle scatter-reads, we must work around the + problem. The chosen fix is to DMA N-2 bytes, then check for the + condition before taking the NCR5380 out of DMA mode. One or two extra + bytes are transferred via PIO as necessary to fill out the original + request. +*/ + + if (p & SR_IO) { +#ifdef READ_OVERRUNS + udelay(10); + if (((NCR5380_read(BUS_AND_STATUS_REG) & (BASR_PHASE_MATCH|BASR_ACK)) == + (BASR_PHASE_MATCH | BASR_ACK))) { + saved_data = NCR5380_read(INPUT_DATA_REGISTER); + overrun = 1; + } +#endif + } else { + int limit = 100; + while (((tmp = NCR5380_read(BUS_AND_STATUS_REG)) & BASR_ACK) || + (NCR5380_read(STATUS_REG) & SR_REQ)) { + if (!(tmp & BASR_PHASE_MATCH)) break; + if (--limit < 0) break; + } + } + + +#if (NDEBUG & NDEBUG_DMA) + printk("scsi%d : polled DMA transfer complete, basr 0x%X, sr 0x%X\n", + instance->host_no, tmp, NCR5380_read(STATUS_REG)); +#endif + + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + + residue = NCR5380_dma_residual(instance); + c -= residue; + *count -= c; + *data += c; + *phase = NCR5380_read(STATUS_REG) & PHASE_MASK; + +#ifdef READ_OVERRUNS + if (*phase == p && (p & SR_IO) && residue == 0) { + if (overrun) { +#if (NDEBUG & NDEBUG_DMA) + printk("Got an input overrun, using saved byte\n"); +#endif + **data = saved_data; + *data += 1; + *count -= 1; + cnt = toPIO = 1; + } else { + printk("No overrun??\n"); + cnt = toPIO = 2; + } +#if (NDEBUG & NDEBUG_DMA) + printk("Doing %d-byte PIO to 0x%X\n", cnt, *data); +#endif + NCR5380_transfer_pio(instance, phase, &cnt, data); + *count -= toPIO - cnt; + } +#endif + +#if (NDEBUG & NDEBUG_DMA) + printk("Return with data ptr = 0x%X, count %d, last 0x%X, next 0x%X\n", + *data, *count, *(*data+*count-1), *(*data+*count)); +#endif + return 0; + +#elif defined(REAL_DMA) + return 0; +#else /* defined(REAL_DMA_POLL) */ + if (p & SR_IO) { +#ifdef DMA_WORKS_RIGHT + foo = NCR5380_pread(instance, d, c); +#else + int diff = 1; + if (hostdata->flags & FLAG_NCR53C400) { + diff=0; + } + + if (!(foo = NCR5380_pread(instance, d, c - diff))) { + /* + * We can't disable DMA mode after successfully transferring + * what we plan to be the last byte, since that would open up + * a race condition where if the target asserted REQ before + * we got the DMA mode reset, the NCR5380 would have latched + * an additional byte into the INPUT DATA register and we'd + * have dropped it. + * + * The workaround was to transfer one fewer bytes than we + * intended to with the pseudo-DMA read function, wait for + * the chip to latch the last byte, read it, and then disable + * pseudo-DMA mode. + * + * After REQ is asserted, the NCR5380 asserts DRQ and ACK. + * REQ is deasserted when ACK is asserted, and not reasserted + * until ACK goes false. Since the NCR5380 won't lower ACK + * until DACK is asserted, which won't happen unless we twiddle + * the DMA port or we take the NCR5380 out of DMA mode, we + * can guarantee that we won't handshake another extra + * byte. + */ + + if (!(hostdata->flags & FLAG_NCR53C400)) { + while (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_DRQ)); + /* Wait for clean handshake */ + while (NCR5380_read(STATUS_REG) & SR_REQ); + d[c - 1] = NCR5380_read(INPUT_DATA_REG); + } + } +#endif + } else { +#ifdef DMA_WORKS_RIGHT + foo = NCR5380_pwrite(instance, d, c); +#else + int timeout; +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("About to pwrite %d bytes\n", c); +#endif + if (!(foo = NCR5380_pwrite(instance, d, c))) { + /* + * Wait for the last byte to be sent. If REQ is being asserted for + * the byte we're interested, we'll ACK it and it will go false. + */ + if (!(hostdata->flags & FLAG_HAS_LAST_BYTE_SENT)) { + timeout = 20000; +#if 1 +#if 1 + while (!(NCR5380_read(BUS_AND_STATUS_REG) & + BASR_DRQ) && (NCR5380_read(BUS_AND_STATUS_REG) & + BASR_PHASE_MATCH)); +#else + if (NCR5380_read(STATUS_REG) & SR_REQ) { + for (; timeout && + !(NCR5380_read(BUS_AND_STATUS_REG) & BASR_ACK); + --timeout); + for (; timeout && (NCR5380_read(STATUS_REG) & SR_REQ); + --timeout); + } +#endif + + +#if (NDEBUG & NDEBUG_LAST_BYTE_SENT) + if (!timeout) + printk("scsi%d : timed out on last byte\n", + instance->host_no); +#endif + + + if (hostdata->flags & FLAG_CHECK_LAST_BYTE_SENT) { + hostdata->flags &= ~FLAG_CHECK_LAST_BYTE_SENT; + if (NCR5380_read(TARGET_COMMAND_REG) & TCR_LAST_BYTE_SENT) { + hostdata->flags |= FLAG_HAS_LAST_BYTE_SENT; +#if (NDEBUG & NDEBUG_LAST_BYTE_SENT) + printk("scsi%d : last bit sent works\n", + instance->host_no); +#endif + } + } + } else { +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("Waiting for LASTBYTE\n"); +#endif + while (!(NCR5380_read(TARGET_COMMAND_REG) & TCR_LAST_BYTE_SENT)); +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("Got LASTBYTE\n"); +#endif + } +#else + udelay (5); +#endif + } +#endif + } + + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + + if ((!(p & SR_IO)) && (hostdata->flags & FLAG_NCR53C400)) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: Checking for IRQ\n"); +#endif + if (NCR5380_read(BUS_AND_STATUS_REG) & BASR_IRQ) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: got it, reading reset interrupt reg\n"); +#endif + NCR5380_read(RESET_PARITY_INTERRUPT_REG); + } else { + printk("53C400w: IRQ NOT THERE!\n"); + } + } + + *data = d + c; + *count = 0; + *phase = NCR5380_read(STATUS_REG) & PHASE_MASK; +#if 0 + NCR5380_print_phase(instance); +#endif +#if defined(PSEUDO_DMA) && !defined(UNSAFE) + sti(); +#endif /* defined(REAL_DMA_POLL) */ + return foo; +#endif /* def REAL_DMA */ +} +#endif /* defined(REAL_DMA) | defined(PSEUDO_DMA) */ + +/* + * Function : NCR5380_information_transfer (struct Scsi_Host *instance) + * + * Purpose : run through the various SCSI phases and do as the target + * directs us to. Operates on the currently connected command, + * instance->connected. + * + * Inputs : instance, instance for which we are doing commands + * + * Side effects : SCSI things happen, the disconnected queue will be + * modified if a command disconnects, *instance->connected will + * change. + * + * XXX Note : we need to watch for bus free or a reset condition here + * to recover from an unexpected bus free condition. + */ + +static void NCR5380_information_transfer (struct Scsi_Host *instance) { + NCR5380_local_declare(); + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) + instance->hostdata; + unsigned char msgout = NOP; + int sink = 0; + int len; +#if defined(PSEUDO_DMA) || defined(REAL_DMA_POLL) + int transfersize; +#endif + unsigned char *data; + unsigned char phase, tmp, extended_msg[10], old_phase=0xff; + Scsi_Cmnd *cmd = (Scsi_Cmnd *) hostdata->connected; + NCR5380_setup(instance); + + while (1) { + tmp = NCR5380_read(STATUS_REG); + /* We only have a valid SCSI phase when REQ is asserted */ + if (tmp & SR_REQ) { + phase = (tmp & PHASE_MASK); + if (phase != old_phase) { + old_phase = phase; +#if (NDEBUG & NDEBUG_INFORMATION) + NCR5380_print_phase(instance); +#endif + } + + if (sink && (phase != PHASE_MSGOUT)) { + NCR5380_write(TARGET_COMMAND_REG, PHASE_SR_TO_TCR(tmp)); + + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_ATN | + ICR_ASSERT_ACK); + while (NCR5380_read(STATUS_REG) & SR_REQ); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | + ICR_ASSERT_ATN); + sink = 0; + continue; + } + + switch (phase) { + case PHASE_DATAIN: + case PHASE_DATAOUT: +#if (NDEBUG & NDEBUG_NO_DATAOUT) + printk("scsi%d : NDEBUG_NO_DATAOUT set, attempted DATAOUT aborted\n", + instance->host_no); + sink = 1; + do_abort(instance); + cmd->result = DID_ERROR << 16; + cmd->done(cmd); + return; +#endif + /* + * If there is no room left in the current buffer in the + * scatter-gather list, move onto the next one. + */ + + if (!cmd->SCp.this_residual && cmd->SCp.buffers_residual) { + ++cmd->SCp.buffer; + --cmd->SCp.buffers_residual; + cmd->SCp.this_residual = cmd->SCp.buffer->length; + cmd->SCp.ptr = cmd->SCp.buffer->address; +#if (NDEBUG & NDEBUG_INFORMATION) + printk("scsi%d : %d bytes and %d buffers left\n", + instance->host_no, cmd->SCp.this_residual, + cmd->SCp.buffers_residual); +#endif + } + + /* + * The preferred transfer method is going to be + * PSEUDO-DMA for systems that are strictly PIO, + * since we can let the hardware do the handshaking. + * + * For this to work, we need to know the transfersize + * ahead of time, since the pseudo-DMA code will sit + * in an unconditional loop. + */ + +#if defined(PSEUDO_DMA) || defined(REAL_DMA_POLL) + /* KLL + * PSEUDO_DMA is defined here. If this is the g_NCR5380 + * driver then it will always be defined, so the + * FLAG_NO_PSEUDO_DMA is used to inhibit PDMA in the base + * NCR5380 case. I think this is a fairly clean solution. + * We supplement these 2 if's with the flag. + */ +#ifdef NCR5380_dma_xfer_len + if (!cmd->device->borken && + !(hostdata->flags & FLAG_NO_PSEUDO_DMA) && + (transfersize = NCR5380_dma_xfer_len(instance, cmd)) != 0) { +#else + transfersize = cmd->transfersize; + +#ifdef LIMIT_TRANSFERSIZE /* If we have problems with interrupt service */ + if( transfersize > 512 ) + transfersize = 512; +#endif /* LIMIT_TRANSFERSIZE */ + + if (!cmd->device->borken && transfersize && + !(hostdata->flags & FLAG_NO_PSEUDO_DMA) && + cmd->SCp.this_residual && !(cmd->SCp.this_residual % + transfersize)) { + /* Limit transfers to 32K, for xx400 & xx406 + * pseudoDMA that transfers in 128 bytes blocks. */ + if (transfersize > 32*1024) + transfersize = 32*1024; +#endif + len = transfersize; + if (NCR5380_transfer_dma(instance, &phase, + &len, (unsigned char **) &cmd->SCp.ptr)) { + /* + * If the watchdog timer fires, all future accesses to this + * device will use the polled-IO. + */ + printk("scsi%d : switching target %d lun %d to slow handshake\n", + instance->host_no, cmd->target, cmd->lun); + cmd->device->borken = 1; + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | + ICR_ASSERT_ATN); + sink = 1; + do_abort(instance); + cmd->result = DID_ERROR << 16; + cmd->done(cmd); + /* XXX - need to source or sink data here, as appropriate */ + } else + cmd->SCp.this_residual -= transfersize - len; + } else +#endif /* defined(PSEUDO_DMA) || defined(REAL_DMA_POLL) */ + NCR5380_transfer_pio(instance, &phase, + (int *) &cmd->SCp.this_residual, (unsigned char **) + &cmd->SCp.ptr); + break; + case PHASE_MSGIN: + len = 1; + data = &tmp; + NCR5380_transfer_pio(instance, &phase, &len, &data); + cmd->SCp.Message = tmp; + + switch (tmp) { + /* + * Linking lets us reduce the time required to get the + * next command out to the device, hopefully this will + * mean we don't waste another revolution due to the delays + * required by ARBITRATION and another SELECTION. + * + * In the current implementation proposal, low level drivers + * merely have to start the next command, pointed to by + * next_link, done() is called as with unlinked commands. + */ +#ifdef LINKED + case LINKED_CMD_COMPLETE: + case LINKED_FLG_CMD_COMPLETE: + /* Accept message by clearing ACK */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + +#if (NDEBUG & NDEBUG_LINKED) + printk("scsi%d : target %d lun %d linked command complete.\n", + instance->host_no, cmd->target, cmd->lun); +#endif + /* + * Sanity check : A linked command should only terminate with + * one of these messages if there are more linked commands + * available. + */ + + if (!cmd->next_link) { + printk("scsi%d : target %d lun %d linked command complete, no next_link\n" + instance->host_no, cmd->target, cmd->lun); + sink = 1; + do_abort (instance); + return; + } + + initialize_SCp(cmd->next_link); + /* The next command is still part of this process */ + cmd->next_link->tag = cmd->tag; + cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8); +#if (NDEBUG & NDEBUG_LINKED) + printk("scsi%d : target %d lun %d linked request done, calling scsi_done().\n", + instance->host_no, cmd->target, cmd->lun); +#endif +#ifdef NCR5380_STATS + collect_stats(hostdata, cmd); +#endif + cmd->scsi_done(cmd); + cmd = hostdata->connected; + break; +#endif /* def LINKED */ + case ABORT: + case COMMAND_COMPLETE: + /* Accept message by clearing ACK */ + sink = 1; + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + hostdata->connected = NULL; +#if (NDEBUG & NDEBUG_QUEUES) + printk("scsi%d : command for target %d, lun %d completed\n", + instance->host_no, cmd->target, cmd->lun); +#endif + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + + /* + * I'm not sure what the correct thing to do here is : + * + * If the command that just executed is NOT a request + * sense, the obvious thing to do is to set the result + * code to the values of the stored parameters. + * + * If it was a REQUEST SENSE command, we need some way + * to differentiate between the failure code of the original + * and the failure code of the REQUEST sense - the obvious + * case is success, where we fall through and leave the result + * code unchanged. + * + * The non-obvious place is where the REQUEST SENSE failed + */ + + if (cmd->cmnd[0] != REQUEST_SENSE) + cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8); + else if (cmd->SCp.Status != GOOD) + cmd->result = (cmd->result & 0x00ffff) | (DID_ERROR << 16); + +#ifdef AUTOSENSE + if ((cmd->cmnd[0] != REQUEST_SENSE) && + (cmd->SCp.Status == CHECK_CONDITION)) { +#if (NDEBUG & NDEBUG_AUTOSENSE) + printk("scsi%d : performing request sense\n", + instance->host_no); +#endif + cmd->cmnd[0] = REQUEST_SENSE; + cmd->cmnd[1] &= 0xe0; + cmd->cmnd[2] = 0; + cmd->cmnd[3] = 0; + cmd->cmnd[4] = sizeof(cmd->sense_buffer); + cmd->cmnd[5] = 0; + + cmd->SCp.buffer = NULL; + cmd->SCp.buffers_residual = 0; + cmd->SCp.ptr = (char *) cmd->sense_buffer; + cmd->SCp.this_residual = sizeof(cmd->sense_buffer); + + cli(); + LIST(cmd,hostdata->issue_queue); + cmd->host_scribble = (unsigned char *) + hostdata->issue_queue; + hostdata->issue_queue = (Scsi_Cmnd *) cmd; + sti(); +#if (NDEBUG & NDEBUG_QUEUES) + printk("scsi%d : REQUEST SENSE added to head of issue queue\n",instance->host_no); +#endif + } else { +#endif /* def AUTOSENSE */ +#ifdef NCR5380_STATS + collect_stats(hostdata, cmd); +#endif + cmd->scsi_done(cmd); + } + + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + /* + * Restore phase bits to 0 so an interrupted selection, + * arbitration can resume. + */ + NCR5380_write(TARGET_COMMAND_REG, 0); + + while ((NCR5380_read(STATUS_REG) & SR_BSY) && !hostdata->connected) + barrier(); + return; + case MESSAGE_REJECT: + /* Accept message by clearing ACK */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + switch (hostdata->last_message) { + case HEAD_OF_QUEUE_TAG: + case ORDERED_QUEUE_TAG: + case SIMPLE_QUEUE_TAG: + cmd->device->tagged_queue = 0; + hostdata->busy[cmd->target] |= (1 << cmd->lun); + break; + default: + break; + } + case DISCONNECT: + /* Accept message by clearing ACK */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + cmd->device->disconnect = 1; + cli(); + LIST(cmd,hostdata->disconnected_queue); + cmd->host_scribble = (unsigned char *) + hostdata->disconnected_queue; + hostdata->connected = NULL; + hostdata->disconnected_queue = cmd; + sti(); +#if (NDEBUG & NDEBUG_QUEUES) + printk("scsi%d : command for target %d lun %d was moved from connected to" + " the disconnected_queue\n", instance->host_no, + cmd->target, cmd->lun); +#endif + /* + * Restore phase bits to 0 so an interrupted selection, + * arbitration can resume. + */ + NCR5380_write(TARGET_COMMAND_REG, 0); + + /* Enable reselect interrupts */ + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + /* Wait for bus free to avoid nasty timeouts */ + while ((NCR5380_read(STATUS_REG) & SR_BSY) && !hostdata->connected) + barrier(); +#if 0 + NCR5380_print_status(instance); +#endif + return; + /* + * The SCSI data pointer is *IMPLICITLY* saved on a disconnect + * operation, in violation of the SCSI spec so we can safely + * ignore SAVE/RESTORE pointers calls. + * + * Unfortunately, some disks violate the SCSI spec and + * don't issue the required SAVE_POINTERS message before + * disconnecting, and we have to break spec to remain + * compatible. + */ + case SAVE_POINTERS: + case RESTORE_POINTERS: + /* Accept message by clearing ACK */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + break; + case EXTENDED_MESSAGE: +/* + * Extended messages are sent in the following format : + * Byte + * 0 EXTENDED_MESSAGE == 1 + * 1 length (includes one byte for code, doesn't + * include first two bytes) + * 2 code + * 3..length+1 arguments + * + * Start the extended message buffer with the EXTENDED_MESSAGE + * byte, since print_msg() wants the whole thing. + */ + extended_msg[0] = EXTENDED_MESSAGE; + /* Accept first byte by clearing ACK */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + +#if (NDEBUG & NDEBUG_EXTENDED) + printk("scsi%d : receiving extended message\n", + instance->host_no); +#endif + + len = 2; + data = extended_msg + 1; + phase = PHASE_MSGIN; + NCR5380_transfer_pio(instance, &phase, &len, &data); + +#if (NDEBUG & NDEBUG_EXTENDED) + printk("scsi%d : length=%d, code=0x%02x\n", + instance->host_no, (int) extended_msg[1], + (int) extended_msg[2]); +#endif + + if (!len && extended_msg[1] <= + (sizeof (extended_msg) - 1)) { + /* Accept third byte by clearing ACK */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + len = extended_msg[1] - 1; + data = extended_msg + 3; + phase = PHASE_MSGIN; + + NCR5380_transfer_pio(instance, &phase, &len, &data); + +#if (NDEBUG & NDEBUG_EXTENDED) + printk("scsi%d : message received, residual %d\n", + instance->host_no, len); +#endif + + switch (extended_msg[2]) { + case EXTENDED_SDTR: + case EXTENDED_WDTR: + case EXTENDED_MODIFY_DATA_POINTER: + case EXTENDED_EXTENDED_IDENTIFY: + tmp = 0; + } + } else if (len) { + printk("scsi%d: error receiving extended message\n", + instance->host_no); + tmp = 0; + } else { + printk("scsi%d: extended message code %02x length %d is too long\n", + instance->host_no, extended_msg[2], extended_msg[1]); + tmp = 0; + } + /* Fall through to reject message */ + + /* + * If we get something weird that we aren't expecting, + * reject it. + */ + default: + if (!tmp) { + printk("scsi%d: rejecting message ", instance->host_no); + print_msg (extended_msg); + printk("\n"); + } else if (tmp != EXTENDED_MESSAGE) + printk("scsi%d: rejecting unknown message %02x from target %d, lun %d\n", + instance->host_no, tmp, cmd->target, cmd->lun); + else + printk("scsi%d: rejecting unknown extended message code %02x, length %d from target %d, lun %d\n", + instance->host_no, extended_msg[1], extended_msg[0], cmd->target, cmd->lun); + + msgout = MESSAGE_REJECT; + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | + ICR_ASSERT_ATN); + break; + } /* switch (tmp) */ + break; + case PHASE_MSGOUT: + len = 1; + data = &msgout; + hostdata->last_message = msgout; + NCR5380_transfer_pio(instance, &phase, &len, &data); + if (msgout == ABORT) { + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + hostdata->connected = NULL; + cmd->result = DID_ERROR << 16; +#ifdef NCR5380_STATS + collect_stats(hostdata, cmd); +#endif + cmd->scsi_done(cmd); + NCR5380_write(SELECT_ENABLE_REG, hostdata->id_mask); + return; + } + msgout = NOP; + break; + case PHASE_CMDOUT: + len = cmd->cmd_len; + data = cmd->cmnd; + /* + * XXX for performance reasons, on machines with a + * PSEUDO-DMA architecture we should probably + * use the dma transfer function. + */ + NCR5380_transfer_pio(instance, &phase, &len, + &data); +#ifdef USLEEP + if (!disconnect && should_disconnect(cmd->cmnd[0])) { + hostdata->time_expires = jiffies + USLEEP_SLEEP; +#if (NDEBUG & NDEBUG_USLEEP) + printk("scsi%d : issued command, sleeping until %ul\n", instance->host_no, + hostdata->time_expires); +#endif + NCR5380_set_timer (instance); + return; + } +#endif /* def USLEEP */ + break; + case PHASE_STATIN: + len = 1; + data = &tmp; + NCR5380_transfer_pio(instance, &phase, &len, &data); + cmd->SCp.Status = tmp; + break; + default: + printk("scsi%d : unknown phase\n", instance->host_no); +#ifdef NDEBUG + NCR5380_print(instance); +#endif + } /* switch(phase) */ + } /* if (tmp * SR_REQ) */ +#ifdef USLEEP + else { + if (!disconnect && hostdata->time_expires && jiffies > + hostdata->time_expires) { + hostdata->time_expires = jiffies + USLEEP_SLEEP; +#if (NDEBUG & NDEBUG_USLEEP) + printk("scsi%d : poll timed out, sleeping until %ul\n", instance->host_no, + hostdata->time_expires); +#endif + NCR5380_set_timer (instance); + return; + } + } +#endif + } /* while (1) */ +} + +/* + * Function : void NCR5380_reselect (struct Scsi_Host *instance) + * + * Purpose : does reselection, initializing the instance->connected + * field to point to the Scsi_Cmnd for which the I_T_L or I_T_L_Q + * nexus has been reestablished, + * + * Inputs : instance - this instance of the NCR5380. + * + */ + + +static void NCR5380_reselect (struct Scsi_Host *instance) { + NCR5380_local_declare(); + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) + instance->hostdata; + unsigned char target_mask; + unsigned char lun, phase; + int len; +#ifdef SCSI2 + unsigned char tag; +#endif + unsigned char msg[3]; + unsigned char *data; + Scsi_Cmnd *tmp = NULL, *prev; + int abort = 0; + NCR5380_setup(instance); + + /* + * Disable arbitration, etc. since the host adapter obviously + * lost, and tell an interrupted NCR5380_select() to restart. + */ + + NCR5380_write(MODE_REG, MR_BASE); + hostdata->restart_select = 1; + + target_mask = NCR5380_read(CURRENT_SCSI_DATA_REG) & ~(hostdata->id_mask); + +#if (NDEBUG & NDEBUG_RESELECTION) + printk("scsi%d : reselect\n", instance->host_no); +#endif + + /* + * At this point, we have detected that our SCSI ID is on the bus, + * SEL is true and BSY was false for at least one bus settle delay + * (400 ns). + * + * We must assert BSY ourselves, until the target drops the SEL + * signal. + */ + + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE | ICR_ASSERT_BSY); + + while (NCR5380_read(STATUS_REG) & SR_SEL); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + + /* + * Wait for target to go into MSGIN. + */ + + while (!(NCR5380_read(STATUS_REG) & SR_REQ)); + + len = 1; + data = msg; + phase = PHASE_MSGIN; + NCR5380_transfer_pio(instance, &phase, &len, &data); + + + if (!msg[0] & 0x80) { + printk("scsi%d : expecting IDENTIFY message, got ", + instance->host_no); + print_msg(msg); + abort = 1; + } else { + /* Accept message by clearing ACK */ + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + lun = (msg[0] & 0x07); + + /* + * We need to add code for SCSI-II to track which devices have + * I_T_L_Q nexuses established, and which have simple I_T_L + * nexuses so we can chose to do additional data transfer. + */ + +#ifdef SCSI2 +#error "SCSI-II tagged queueing is not supported yet" +#endif + + /* + * Find the command corresponding to the I_T_L or I_T_L_Q nexus we + * just reestablished, and remove it from the disconnected queue. + */ + + + for (tmp = (Scsi_Cmnd *) hostdata->disconnected_queue, prev = NULL; + tmp; prev = tmp, tmp = (Scsi_Cmnd *) tmp->host_scribble) + if ((target_mask == (1 << tmp->target)) && (lun == tmp->lun) +#ifdef SCSI2 + && (tag == tmp->tag) +#endif +) { + if (prev) { + REMOVE(prev,prev->host_scribble,tmp,tmp->host_scribble); + prev->host_scribble = tmp->host_scribble; + } else { + REMOVE(-1,hostdata->disconnected_queue,tmp,tmp->host_scribble); + hostdata->disconnected_queue = (Scsi_Cmnd *) tmp->host_scribble; + } + tmp->host_scribble = NULL; + break; + } + + if (!tmp) { +#ifdef SCSI2 + printk("scsi%d : warning : target bitmask %02x lun %d tag %d not in disconnect_queue.\n", + instance->host_no, target_mask, lun, tag); +#else + printk("scsi%d : warning : target bitmask %02x lun %d not in disconnect_queue.\n", + instance->host_no, target_mask, lun); +#endif + /* + * Since we have an established nexus that we can't do anything with, + * we must abort it. + */ + abort = 1; + } + } + + if (abort) { + do_abort (instance); + } else { + hostdata->connected = tmp; +#if (NDEBUG & NDEBUG_RESELECTION) + printk("scsi%d : nexus established, target = %d, lun = %d, tag = %d\n", + instance->host_no, tmp->target, tmp->lun, tmp->tag); +#endif + } +} + +/* + * Function : void NCR5380_dma_complete (struct Scsi_Host *instance) + * + * Purpose : called by interrupt handler when DMA finishes or a phase + * mismatch occurs (which would finish the DMA transfer). + * + * Inputs : instance - this instance of the NCR5380. + * + * Returns : pointer to the Scsi_Cmnd structure for which the I_T_L + * nexus has been reestablished, on failure NULL is returned. + */ + +#ifdef REAL_DMA +static void NCR5380_dma_complete (NCR5380_instance *instance) { + NCR5380_local_declare(); + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata * + instance->hostdata); + int transferred; + NCR5380_setup(instance); + + /* + * XXX this might not be right. + * + * Wait for final byte to transfer, ie wait for ACK to go false. + * + * We should use the Last Byte Sent bit, unfortunately this is + * not available on the 5380/5381 (only the various CMOS chips) + */ + + while (NCR5380_read(BUS_AND_STATUS_REG) & BASR_ACK); + + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_write(INITIATOR_COMMAND_REG, ICR_BASE); + + /* + * The only places we should see a phase mismatch and have to send + * data from the same set of pointers will be the data transfer + * phases. So, residual, requested length are only important here. + */ + + if (!(hostdata->connected->SCp.phase & SR_CD)) { + transferred = instance->dmalen - NCR5380_dma_residual(); + hostdata->connected->SCp.this_residual -= transferred; + hostdata->connected->SCp.ptr += transferred; + } +} +#endif /* def REAL_DMA */ + +/* + * Function : int NCR5380_abort (Scsi_Cmnd *cmd) + * + * Purpose : abort a command + * + * Inputs : cmd - the Scsi_Cmnd to abort, code - code to set the + * host byte of the result field to, if zero DID_ABORTED is + * used. + * + * Returns : 0 - success, -1 on failure. + * + * XXX - there is no way to abort the command that is currently + * connected, you have to wait for it to complete. If this is + * a problem, we could implement longjmp() / setjmp(), setjmp() + * called where the loop started in NCR5380_main(). + */ + +#ifndef NCR5380_abort +static +#endif +int NCR5380_abort (Scsi_Cmnd *cmd) { + NCR5380_local_declare(); + struct Scsi_Host *instance = cmd->host; + struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) + instance->hostdata; + Scsi_Cmnd *tmp, **prev; + + printk("scsi%d : aborting command\n", instance->host_no); + print_Scsi_Cmnd (cmd); + + NCR5380_print_status (instance); + + printk("scsi%d : aborting command\n", instance->host_no); + print_Scsi_Cmnd (cmd); + + NCR5380_print_status (instance); + + cli(); + NCR5380_setup(instance); + +#if (NDEBUG & NDEBUG_ABORT) + printk("scsi%d : abort called\n", instance->host_no); + printk(" basr 0x%X, sr 0x%X\n", + NCR5380_read(BUS_AND_STATUS_REG), NCR5380_read(STATUS_REG)); +#endif + +#if 0 +/* + * Case 1 : If the command is the currently executing command, + * we'll set the aborted flag and return control so that + * information transfer routine can exit cleanly. + */ + + if (hostdata->connected == cmd) { +#if (NDEBUG & NDEBUG_ABORT) + printk("scsi%d : aborting connected command\n", instance->host_no); +#endif + hostdata->aborted = 1; +/* + * We should perform BSY checking, and make sure we haven't slipped + * into BUS FREE. + */ + + NCR5380_write(INITIATOR_COMMAND_REG, ICR_ASSERT_ATN); +/* + * Since we can't change phases until we've completed the current + * handshake, we have to source or sink a byte of data if the current + * phase is not MSGOUT. + */ + +/* + * Return control to the executing NCR drive so we can clear the + * aborted flag and get back into our main loop. + */ + + return 0; + } +#endif + +/* + * Case 2 : If the command hasn't been issued yet, we simply remove it + * from the issue queue. + */ +#if (NDEBUG & NDEBUG_ABORT) + /* KLL */ + printk("scsi%d : abort going into loop.\n", instance->host_no); +#endif + for (prev = (Scsi_Cmnd **) &(hostdata->issue_queue), + tmp = (Scsi_Cmnd *) hostdata->issue_queue; + tmp; prev = (Scsi_Cmnd **) &(tmp->host_scribble), tmp = + (Scsi_Cmnd *) tmp->host_scribble) + if (cmd == tmp) { + REMOVE(5,*prev,tmp,tmp->host_scribble); + (*prev) = (Scsi_Cmnd *) tmp->host_scribble; + tmp->host_scribble = NULL; + tmp->result = DID_ABORT << 16; + sti(); +#if (NDEBUG & NDEBUG_ABORT) + printk("scsi%d : abort removed command from issue queue.\n", + instance->host_no); +#endif + tmp->done(tmp); + return SCSI_ABORT_SUCCESS; + } +#if (NDEBUG & NDEBUG_ABORT) + /* KLL */ + else if (prev == tmp) printk("scsi%d : LOOP\n", instance->host_no); +#endif + +/* + * Case 3 : If any commands are connected, we're going to fail the abort + * and let the high level SCSI driver retry at a later time or + * issue a reset. + * + * Timeouts, and therefore aborted commands, will be highly unlikely + * and handling them cleanly in this situation would make the common + * case of noresets less efficient, and would pollute our code. So, + * we fail. + */ + + if (hostdata->connected) { + sti(); +#if (NDEBUG & NDEBUG_ABORT) + printk("scsi%d : abort failed, command connected.\n", instance->host_no); +#endif + return SCSI_ABORT_NOT_RUNNING; + } + +/* + * Case 4: If the command is currently disconnected from the bus, and + * there are no connected commands, we reconnect the I_T_L or + * I_T_L_Q nexus associated with it, go into message out, and send + * an abort message. + * + * This case is especially ugly. In order to reestablish the nexus, we + * need to call NCR5380_select(). The easiest way to implement this + * function was to abort if the bus was busy, and let the interrupt + * handler triggered on the SEL for reselect take care of lost arbitrations + * where necessary, meaning interrupts need to be enabled. + * + * When interrupts are enabled, the queues may change - so we + * can't remove it from the disconnected queue before selecting it + * because that could cause a failure in hashing the nexus if that + * device reselected. + * + * Since the queues may change, we can't use the pointers from when we + * first locate it. + * + * So, we must first locate the command, and if NCR5380_select() + * succeeds, then issue the abort, relocate the command and remove + * it from the disconnected queue. + */ + + for (tmp = (Scsi_Cmnd *) hostdata->disconnected_queue; tmp; + tmp = (Scsi_Cmnd *) tmp->host_scribble) + if (cmd == tmp) { + sti(); +#if (NDEBUG & NDEBUG_ABORT) + printk("scsi%d : aborting disconnected command.\n", instance->host_no); +#endif + + if (NCR5380_select (instance, cmd, (int) cmd->tag)) + return SCSI_ABORT_BUSY; + +#if (NDEBUG & NDEBUG_ABORT) + printk("scsi%d : nexus reestablished.\n", instance->host_no); +#endif + + do_abort (instance); + + cli(); + for (prev = (Scsi_Cmnd **) &(hostdata->disconnected_queue), + tmp = (Scsi_Cmnd *) hostdata->disconnected_queue; + tmp; prev = (Scsi_Cmnd **) &(tmp->host_scribble), tmp = + (Scsi_Cmnd *) tmp->host_scribble) + if (cmd == tmp) { + REMOVE(5,*prev,tmp,tmp->host_scribble); + *prev = (Scsi_Cmnd *) tmp->host_scribble; + tmp->host_scribble = NULL; + tmp->result = DID_ABORT << 16; + sti(); + tmp->done(tmp); + return SCSI_ABORT_SUCCESS; + } + } + +/* + * Case 5 : If we reached this point, the command was not found in any of + * the queues. + * + * We probably reached this point because of an unlikely race condition + * between the command completing successfully and the abortion code, + * so we won't panic, but we will notify the user in case something really + * broke. + */ + + sti(); + printk("scsi%d : warning : SCSI command probably completed successfully\n" + " before abortion\n", instance->host_no); + return SCSI_ABORT_NOT_RUNNING; +} + + +/* + * Function : int NCR5380_reset (Scsi_Cmnd *cmd, unsigned int reset_flags) + * + * Purpose : reset the SCSI bus. + * + * Returns : SCSI_RESET_WAKEUP + * + */ + +#ifndef NCR5380_reset +static +#endif +int NCR5380_reset (Scsi_Cmnd *cmd, unsigned int dummy) { + NCR5380_local_declare(); + NCR5380_setup(cmd->host); + + NCR5380_print_status (cmd->host); + do_reset (cmd->host); + + return SCSI_RESET_WAKEUP; +} + diff --git a/linux/src/drivers/scsi/NCR5380.h b/linux/src/drivers/scsi/NCR5380.h new file mode 100644 index 00000000..08e2897a --- /dev/null +++ b/linux/src/drivers/scsi/NCR5380.h @@ -0,0 +1,373 @@ +/* + * NCR 5380 defines + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix consulting and custom programming) + * drew@colorado.edu + * +1 (303) 666-5836 + * + * DISTRIBUTION RELEASE 7 + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * $Log: NCR5380.h,v $ + */ + +#ifndef NCR5380_H +#define NCR5380_H + +#define NCR5380_PUBLIC_RELEASE 7 +#define NCR53C400_PUBLIC_RELEASE 2 + +#define NDEBUG_ARBITRATION 0x1 +#define NDEBUG_AUTOSENSE 0x2 +#define NDEBUG_DMA 0x4 +#define NDEBUG_HANDSHAKE 0x8 +#define NDEBUG_INFORMATION 0x10 +#define NDEBUG_INIT 0x20 +#define NDEBUG_INTR 0x40 +#define NDEBUG_LINKED 0x80 +#define NDEBUG_MAIN 0x100 +#define NDEBUG_NO_DATAOUT 0x200 +#define NDEBUG_NO_WRITE 0x400 +#define NDEBUG_PIO 0x800 +#define NDEBUG_PSEUDO_DMA 0x1000 +#define NDEBUG_QUEUES 0x2000 +#define NDEBUG_RESELECTION 0x4000 +#define NDEBUG_SELECTION 0x8000 +#define NDEBUG_USLEEP 0x10000 +#define NDEBUG_LAST_BYTE_SENT 0x20000 +#define NDEBUG_RESTART_SELECT 0x40000 +#define NDEBUG_EXTENDED 0x80000 +#define NDEBUG_C400_PREAD 0x100000 +#define NDEBUG_C400_PWRITE 0x200000 +#define NDEBUG_LISTS 0x400000 + +/* + * The contents of the OUTPUT DATA register are asserted on the bus when + * either arbitration is occurring or the phase-indicating signals ( + * IO, CD, MSG) in the TARGET COMMAND register and the ASSERT DATA + * bit in the INITIATOR COMMAND register is set. + */ + +#define OUTPUT_DATA_REG 0 /* wo DATA lines on SCSI bus */ +#define CURRENT_SCSI_DATA_REG 0 /* ro same */ + +#define INITIATOR_COMMAND_REG 1 /* rw */ +#define ICR_ASSERT_RST 0x80 /* rw Set to assert RST */ +#define ICR_ARBITRATION_PROGRESS 0x40 /* ro Indicates arbitration complete */ +#define ICR_TRI_STATE 0x40 /* wo Set to tri-state drivers */ +#define ICR_ARBITRATION_LOST 0x20 /* ro Indicates arbitration lost */ +#define ICR_DIFF_ENABLE 0x20 /* wo Set to enable diff. drivers */ +#define ICR_ASSERT_ACK 0x10 /* rw ini Set to assert ACK */ +#define ICR_ASSERT_BSY 0x08 /* rw Set to assert BSY */ +#define ICR_ASSERT_SEL 0x04 /* rw Set to assert SEL */ +#define ICR_ASSERT_ATN 0x02 /* rw Set to assert ATN */ +#define ICR_ASSERT_DATA 0x01 /* rw SCSI_DATA_REG is asserted */ + +#ifdef DIFFERENTIAL +#define ICR_BASE ICR_DIFF_ENABLE +#else +#define ICR_BASE 0 +#endif + +#define MODE_REG 2 +/* + * Note : BLOCK_DMA code will keep DRQ asserted for the duration of the + * transfer, causing the chip to hog the bus. You probably don't want + * this. + */ +#define MR_BLOCK_DMA_MODE 0x80 /* rw block mode DMA */ +#define MR_TARGET 0x40 /* rw target mode */ +#define MR_ENABLE_PAR_CHECK 0x20 /* rw enable parity checking */ +#define MR_ENABLE_PAR_INTR 0x10 /* rw enable bad parity interrupt */ +#define MR_ENABLE_EOP_INTR 0x08 /* rw enable eop interrupt */ +#define MR_MONITOR_BSY 0x04 /* rw enable int on unexpected bsy fail */ +#define MR_DMA_MODE 0x02 /* rw DMA / pseudo DMA mode */ +#define MR_ARBITRATE 0x01 /* rw start arbitration */ + +#ifdef PARITY +#define MR_BASE MR_ENABLE_PAR_CHECK +#else +#define MR_BASE 0 +#endif + +#define TARGET_COMMAND_REG 3 +#define TCR_LAST_BYTE_SENT 0x80 /* ro DMA done */ +#define TCR_ASSERT_REQ 0x08 /* tgt rw assert REQ */ +#define TCR_ASSERT_MSG 0x04 /* tgt rw assert MSG */ +#define TCR_ASSERT_CD 0x02 /* tgt rw assert CD */ +#define TCR_ASSERT_IO 0x01 /* tgt rw assert IO */ + +#define STATUS_REG 4 /* ro */ +/* + * Note : a set bit indicates an active signal, driven by us or another + * device. + */ +#define SR_RST 0x80 +#define SR_BSY 0x40 +#define SR_REQ 0x20 +#define SR_MSG 0x10 +#define SR_CD 0x08 +#define SR_IO 0x04 +#define SR_SEL 0x02 +#define SR_DBP 0x01 + +/* + * Setting a bit in this register will cause an interrupt to be generated when + * BSY is false and SEL true and this bit is asserted on the bus. + */ +#define SELECT_ENABLE_REG 4 /* wo */ + +#define BUS_AND_STATUS_REG 5 /* ro */ +#define BASR_END_DMA_TRANSFER 0x80 /* ro set on end of transfer */ +#define BASR_DRQ 0x40 /* ro mirror of DRQ pin */ +#define BASR_PARITY_ERROR 0x20 /* ro parity error detected */ +#define BASR_IRQ 0x10 /* ro mirror of IRQ pin */ +#define BASR_PHASE_MATCH 0x08 /* ro Set when MSG CD IO match TCR */ +#define BASR_BUSY_ERROR 0x04 /* ro Unexpected change to inactive state */ +#define BASR_ATN 0x02 /* ro BUS status */ +#define BASR_ACK 0x01 /* ro BUS status */ + +/* Write any value to this register to start a DMA send */ +#define START_DMA_SEND_REG 5 /* wo */ + +/* + * Used in DMA transfer mode, data is latched from the SCSI bus on + * the falling edge of REQ (ini) or ACK (tgt) + */ +#define INPUT_DATA_REG 6 /* ro */ + +/* Write any value to this register to start a DMA receive */ +#define START_DMA_TARGET_RECEIVE_REG 6 /* wo */ + +/* Read this register to clear interrupt conditions */ +#define RESET_PARITY_INTERRUPT_REG 7 /* ro */ + +/* Write any value to this register to start an ini mode DMA receive */ +#define START_DMA_INITIATOR_RECEIVE_REG 7 /* wo */ + +#define C400_CONTROL_STATUS_REG NCR53C400_register_offset-8 /* rw */ + +#define CSR_RESET 0x80 /* wo Resets 53c400 */ +#define CSR_53C80_REG 0x80 /* ro 5380 registers busy */ +#define CSR_TRANS_DIR 0x40 /* rw Data transfer direction */ +#define CSR_SCSI_BUFF_INTR 0x20 /* rw Enable int on transfer ready */ +#define CSR_53C80_INTR 0x10 /* rw Enable 53c80 interrupts */ +#define CSR_SHARED_INTR 0x08 /* rw Interrupt sharing */ +#define CSR_HOST_BUF_NOT_RDY 0x04 /* ro Is Host buffer ready */ +#define CSR_SCSI_BUF_RDY 0x02 /* ro SCSI buffer read */ +#define CSR_GATED_53C80_IRQ 0x01 /* ro Last block xferred */ + +#if 0 +#define CSR_BASE CSR_SCSI_BUFF_INTR | CSR_53C80_INTR +#else +#define CSR_BASE CSR_53C80_INTR +#endif + +/* Number of 128-byte blocks to be transferred */ +#define C400_BLOCK_COUNTER_REG NCR53C400_register_offset-7 /* rw */ + +/* Resume transfer after disconnect */ +#define C400_RESUME_TRANSFER_REG NCR53C400_register_offset-6 /* wo */ + +/* Access to host buffer stack */ +#define C400_HOST_BUFFER NCR53C400_register_offset-4 /* rw */ + + +/* Note : PHASE_* macros are based on the values of the STATUS register */ +#define PHASE_MASK (SR_MSG | SR_CD | SR_IO) + +#define PHASE_DATAOUT 0 +#define PHASE_DATAIN SR_IO +#define PHASE_CMDOUT SR_CD +#define PHASE_STATIN (SR_CD | SR_IO) +#define PHASE_MSGOUT (SR_MSG | SR_CD) +#define PHASE_MSGIN (SR_MSG | SR_CD | SR_IO) +#define PHASE_UNKNOWN 0xff + +/* + * Convert status register phase to something we can use to set phase in + * the target register so we can get phase mismatch interrupts on DMA + * transfers. + */ + +#define PHASE_SR_TO_TCR(phase) ((phase) >> 2) + +/* + * The internal should_disconnect() function returns these based on the + * expected length of a disconnect if a device supports disconnect/ + * reconnect. + */ + +#define DISCONNECT_NONE 0 +#define DISCONNECT_TIME_TO_DATA 1 +#define DISCONNECT_LONG 2 + +/* + * These are "special" values for the tag parameter passed to NCR5380_select. + */ + +#define TAG_NEXT -1 /* Use next free tag */ +#define TAG_NONE -2 /* + * Establish I_T_L nexus instead of I_T_L_Q + * even on SCSI-II devices. + */ + +/* + * These are "special" values for the irq and dma_channel fields of the + * Scsi_Host structure + */ + +#define IRQ_NONE 255 +#define DMA_NONE 255 +#define IRQ_AUTO 254 +#define DMA_AUTO 254 + +#define FLAG_HAS_LAST_BYTE_SENT 1 /* NCR53c81 or better */ +#define FLAG_CHECK_LAST_BYTE_SENT 2 /* Only test once */ +#define FLAG_NCR53C400 4 /* NCR53c400 */ +#define FLAG_NO_PSEUDO_DMA 8 /* Inhibit DMA */ + +#ifndef ASM +struct NCR5380_hostdata { + NCR5380_implementation_fields; /* implementation specific */ + unsigned char id_mask, id_higher_mask; /* 1 << id, all bits greater */ + unsigned char targets_present; /* targets we have connected + to, so we can call a select + failure a retryable condition */ + volatile unsigned char busy[8]; /* index = target, bit = lun */ +#if defined(REAL_DMA) || defined(REAL_DMA_POLL) + volatile int dma_len; /* requested length of DMA */ +#endif + volatile unsigned char last_message; /* last message OUT */ + volatile Scsi_Cmnd *connected; /* currently connected command */ + volatile Scsi_Cmnd *issue_queue; /* waiting to be issued */ + volatile Scsi_Cmnd *disconnected_queue; /* waiting for reconnect */ + volatile int restart_select; /* we have disconnected, + used to restart + NCR5380_select() */ + volatile unsigned aborted:1; /* flag, says aborted */ + int flags; +#ifdef USLEEP + unsigned long time_expires; /* in jiffies, set prior to sleeping */ + struct Scsi_Host *next_timer; +#endif +#ifdef NCR5380_STATS + unsigned timebase; /* Base for time calcs */ + long time_read[8]; /* time to do reads */ + long time_write[8]; /* time to do writes */ + unsigned long bytes_read[8]; /* bytes read */ + unsigned long bytes_write[8]; /* bytes written */ + unsigned pendingr; + unsigned pendingw; +#endif +}; + +#ifdef __KERNEL__ +static struct Scsi_Host *first_instance; /* linked list of 5380's */ + +#if defined(AUTOPROBE_IRQ) +static int NCR5380_probe_irq (struct Scsi_Host *instance, int possible); +#endif +static void NCR5380_init (struct Scsi_Host *instance, int flags); +static void NCR5380_information_transfer (struct Scsi_Host *instance); +#ifndef DONT_USE_INTR +static void NCR5380_intr (int irq, void *dev_id, struct pt_regs * regs); +#endif +static void NCR5380_main (void); +static void NCR5380_print_options (struct Scsi_Host *instance); +static void NCR5380_print_phase (struct Scsi_Host *instance); +static void NCR5380_print (struct Scsi_Host *instance); +#ifndef NCR5380_abort +static +#endif +int NCR5380_abort (Scsi_Cmnd *cmd); +#ifndef NCR5380_reset +static +#endif +int NCR5380_reset (Scsi_Cmnd *cmd, unsigned int reset_flags); +#ifndef NCR5380_queue_command +static +#endif +int NCR5380_queue_command (Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)); + + +static void NCR5380_reselect (struct Scsi_Host *instance); +static int NCR5380_select (struct Scsi_Host *instance, Scsi_Cmnd *cmd, int tag); +#if defined(PSEUDO_DMA) || defined(REAL_DMA) || defined(REAL_DMA_POLL) +static int NCR5380_transfer_dma (struct Scsi_Host *instance, + unsigned char *phase, int *count, unsigned char **data); +#endif +static int NCR5380_transfer_pio (struct Scsi_Host *instance, + unsigned char *phase, int *count, unsigned char **data); + +#if (defined(REAL_DMA) || defined(REAL_DMA_POLL)) + +#if defined(i386) || defined(__alpha__) + +static __inline__ int NCR5380_pc_dma_setup (struct Scsi_Host *instance, + unsigned char *ptr, unsigned int count, unsigned char mode) { + unsigned limit; + unsigned long bus_addr = virt_to_bus(ptr); + + if (instance->dma_channel <=3) { + if (count > 65536) + count = 65536; + limit = 65536 - (bus_addr & 0xFFFF); + } else { + if (count > 65536 * 2) + count = 65536 * 2; + limit = 65536* 2 - (bus_addr & 0x1FFFF); + } + + if (count > limit) count = limit; + + if ((count & 1) || (bus_addr & 1)) + panic ("scsi%d : attempted unaligned DMA transfer\n", instance->host_no); + cli(); + disable_dma(instance->dma_channel); + clear_dma_ff(instance->dma_channel); + set_dma_addr(instance->dma_channel, bus_addr); + set_dma_count(instance->dma_channel, count); + set_dma_mode(instance->dma_channel, mode); + enable_dma(instance->dma_channel); + sti(); + return count; +} + +static __inline__ int NCR5380_pc_dma_write_setup (struct Scsi_Host *instance, + unsigned char *src, unsigned int count) { + return NCR5380_pc_dma_setup (instance, src, count, DMA_MODE_WRITE); +} + +static __inline__ int NCR5380_pc_dma_read_setup (struct Scsi_Host *instance, + unsigned char *src, unsigned int count) { + return NCR5380_pc_dma_setup (instance, src, count, DMA_MODE_READ); +} + +static __inline__ int NCR5380_pc_dma_residual (struct Scsi_Host *instance) { + register int tmp; + cli(); + clear_dma_ff(instance->dma_channel); + tmp = get_dma_residue(instance->dma_channel); + sti(); + return tmp; +} +#endif /* defined(i386) || defined(__alpha__) */ +#endif /* defined(REAL_DMA) */ +#endif __KERNEL_ +#endif /* ndef ASM */ +#endif /* NCR5380_H */ diff --git a/linux/src/drivers/scsi/NCR53c406a.c b/linux/src/drivers/scsi/NCR53c406a.c new file mode 100644 index 00000000..9f2de9a0 --- /dev/null +++ b/linux/src/drivers/scsi/NCR53c406a.c @@ -0,0 +1,1079 @@ +/* + * NCR53c406.c + * Low-level SCSI driver for NCR53c406a chip. + * Copyright (C) 1994, 1995, 1996 Normunds Saumanis (normunds@fi.ibm.com) + * + * LILO command line usage: ncr53c406a=<PORTBASE>[,<IRQ>[,<FASTPIO>]] + * Specify IRQ = 0 for non-interrupt driven mode. + * FASTPIO = 1 for fast pio mode, 0 for slow mode. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#define NCR53C406A_DEBUG 0 +#define VERBOSE_NCR53C406A_DEBUG 0 + +/* Set this to 1 for PIO mode (recommended) or to 0 for DMA mode */ +#define USE_PIO 1 + +#define USE_BIOS 0 +/* #define BIOS_ADDR 0xD8000 */ /* define this if autoprobe fails */ +/* #define PORT_BASE 0x330 */ /* define this if autoprobe fails */ +/* #define IRQ_LEV 0 */ /* define this if autoprobe fails */ +#define DMA_CHAN 5 /* this is ignored if DMA is disabled */ + +/* Set this to 0 if you encounter kernel lockups while transferring + * data in PIO mode */ +#define USE_FAST_PIO 1 + +/* ============= End of user configurable parameters ============= */ + +#include <linux/module.h> + +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/bitops.h> +#include <asm/irq.h> + +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" + +#include "NCR53c406a.h" + +/* ============================================================= */ + +#define WATCHDOG 5000000 + +#define SYNC_MODE 0 /* Synchronous transfer mode */ + +#if DEBUG +#undef NCR53C406A_DEBUG +#define NCR53C406A_DEBUG 1 +#endif + +#if USE_PIO +#define USE_DMA 0 +#else +#define USE_DMA 1 +#endif + +/* Default configuration */ +#define C1_IMG 0x07 /* ID=7 */ +#define C2_IMG 0x48 /* FE SCSI2 */ +#if USE_DMA +#define C3_IMG 0x21 /* CDB TE */ +#else +#define C3_IMG 0x20 /* CDB */ +#endif +#define C4_IMG 0x04 /* ANE */ +#define C5_IMG 0xb6 /* AA PI SIE POL */ + +#define REG0 (outb(C4_IMG, CONFIG4)) +#define REG1 (outb(C5_IMG, CONFIG5)) + +#if NCR53C406A_DEBUG +#define DEB(x) x +#else +#define DEB(x) +#endif + +#if VERBOSE_NCR53C406A_DEBUG +#define VDEB(x) x +#else +#define VDEB(x) +#endif + +#define LOAD_DMA_COUNT(count) \ + outb(count & 0xff, TC_LSB); \ + outb((count >> 8) & 0xff, TC_MSB); \ + outb((count >> 16) & 0xff, TC_HIGH); + +/* Chip commands */ +#define DMA_OP 0x80 + +#define SCSI_NOP 0x00 +#define FLUSH_FIFO 0x01 +#define CHIP_RESET 0x02 +#define SCSI_RESET 0x03 +#define RESELECT 0x40 +#define SELECT_NO_ATN 0x41 +#define SELECT_ATN 0x42 +#define SELECT_ATN_STOP 0x43 +#define ENABLE_SEL 0x44 +#define DISABLE_SEL 0x45 +#define SELECT_ATN3 0x46 +#define RESELECT3 0x47 +#define TRANSFER_INFO 0x10 +#define INIT_CMD_COMPLETE 0x11 +#define MSG_ACCEPT 0x12 +#define TRANSFER_PAD 0x18 +#define SET_ATN 0x1a +#define RESET_ATN 0x1b +#define SEND_MSG 0x20 +#define SEND_STATUS 0x21 +#define SEND_DATA 0x22 +#define DISCONN_SEQ 0x23 +#define TERMINATE_SEQ 0x24 +#define TARG_CMD_COMPLETE 0x25 +#define DISCONN 0x27 +#define RECV_MSG 0x28 +#define RECV_CMD 0x29 +#define RECV_DATA 0x2a +#define RECV_CMD_SEQ 0x2b +#define TARGET_ABORT_DMA 0x04 + +/*----------------------------------------------------------------*/ +/* the following will set the monitor border color (useful to find + where something crashed or gets stuck at */ +/* 1 = blue + 2 = green + 3 = cyan + 4 = red + 5 = magenta + 6 = yellow + 7 = white +*/ + +#if NCR53C406A_DEBUG +#define rtrc(i) {inb(0x3da);outb(0x31,0x3c0);outb((i),0x3c0);} +#else +#define rtrc(i) {} +#endif +/*----------------------------------------------------------------*/ + +enum Phase { + idle, + data_out, + data_in, + command_ph, + status_ph, + message_out, + message_in +}; + +/* Static function prototypes */ +static void NCR53c406a_intr(int, void *, struct pt_regs *); +static void internal_done(Scsi_Cmnd *); +static void wait_intr(void); +static void chip_init(void); +static void calc_port_addr(void); +#ifndef IRQ_LEV +static int irq_probe(void); +#endif + +/* ================================================================= */ + +#if USE_BIOS +static void *bios_base = (void *)0; +#endif + +#if PORT_BASE +static int port_base = PORT_BASE; +#else +static int port_base = 0; +#endif + +#if IRQ_LEV +static int irq_level = IRQ_LEV; +#else +static int irq_level = -1; /* 0 is 'no irq', so use -1 for 'uninitialized'*/ +#endif + +#if USE_DMA +static int dma_chan = 0; +#endif + +#if USE_PIO +static int fast_pio = USE_FAST_PIO; +#endif + +static Scsi_Cmnd *current_SC = NULL; +static volatile int internal_done_flag = 0; +static volatile int internal_done_errcode = 0; +static char info_msg[256]; + +struct proc_dir_entry proc_scsi_NCR53c406a = { + PROC_SCSI_NCR53C406A, 7, "NCR53c406a", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; +/* ================================================================= */ + +/* possible BIOS locations */ +#if USE_BIOS +static void *addresses[] = { + (void *)0xd8000, + (void *)0xc8000 +}; +#define ADDRESS_COUNT (sizeof( addresses ) / sizeof( unsigned )) +#endif USE_BIOS + +/* possible i/o port addresses */ +static unsigned short ports[] = { 0x230, 0x330 }; +#define PORT_COUNT (sizeof( ports ) / sizeof( unsigned short )) + +/* possible interrupt channels */ +static unsigned short intrs[] = { 10, 11, 12, 15 }; +#define INTR_COUNT (sizeof( intrs ) / sizeof( unsigned short )) + +/* signatures for NCR 53c406a based controllers */ +#if USE_BIOS +struct signature { + char *signature; + int sig_offset; + int sig_length; +} signatures[] = { + /* 1 2 3 4 5 6 */ + /* 123456789012345678901234567890123456789012345678901234567890 */ + { "Copyright (C) Acculogic, Inc.\r\n2.8M Diskette Extension Bios ver 4.04.03 03/01/1993", 61, 82 }, +}; +#define SIGNATURE_COUNT (sizeof( signatures ) / sizeof( struct signature )) +#endif USE_BIOS + +/* ============================================================ */ + +/* Control Register Set 0 */ +static int TC_LSB; /* transfer counter lsb */ +static int TC_MSB; /* transfer counter msb */ +static int SCSI_FIFO; /* scsi fifo register */ +static int CMD_REG; /* command register */ +static int STAT_REG; /* status register */ +static int DEST_ID; /* selection/reselection bus id */ +static int INT_REG; /* interrupt status register */ +static int SRTIMOUT; /* select/reselect timeout reg */ +static int SEQ_REG; /* sequence step register */ +static int SYNCPRD; /* synchronous transfer period */ +static int FIFO_FLAGS; /* indicates # of bytes in fifo */ +static int SYNCOFF; /* synchronous offset register */ +static int CONFIG1; /* configuration register */ +static int CLKCONV; /* clock conversion reg */ +/*static int TESTREG;*/ /* test mode register */ +static int CONFIG2; /* Configuration 2 Register */ +static int CONFIG3; /* Configuration 3 Register */ +static int CONFIG4; /* Configuration 4 Register */ +static int TC_HIGH; /* Transfer Counter High */ +/*static int FIFO_BOTTOM;*/ /* Reserve FIFO byte register */ + +/* Control Register Set 1 */ +/*static int JUMPER_SENSE;*/ /* Jumper sense port reg (r/w) */ +/*static int SRAM_PTR;*/ /* SRAM address pointer reg (r/w) */ +/*static int SRAM_DATA;*/ /* SRAM data register (r/w) */ +static int PIO_FIFO; /* PIO FIFO registers (r/w) */ +/*static int PIO_FIFO1;*/ /* */ +/*static int PIO_FIFO2;*/ /* */ +/*static int PIO_FIFO3;*/ /* */ +static int PIO_STATUS; /* PIO status (r/w) */ +/*static int ATA_CMD;*/ /* ATA command/status reg (r/w) */ +/*static int ATA_ERR;*/ /* ATA features/error register (r/w)*/ +static int PIO_FLAG; /* PIO flag interrupt enable (r/w) */ +static int CONFIG5; /* Configuration 5 register (r/w) */ +/*static int SIGNATURE;*/ /* Signature Register (r) */ +/*static int CONFIG6;*/ /* Configuration 6 register (r) */ + +/* ============================================================== */ + +#if USE_DMA +static __inline__ int +NCR53c406a_dma_setup (unsigned char *ptr, + unsigned int count, + unsigned char mode) { + unsigned limit; + unsigned long flags = 0; + + VDEB(printk("dma: before count=%d ", count)); + if (dma_chan <=3) { + if (count > 65536) + count = 65536; + limit = 65536 - (((unsigned) ptr) & 0xFFFF); + } else { + if (count > (65536<<1)) + count = (65536<<1); + limit = (65536<<1) - (((unsigned) ptr) & 0x1FFFF); + } + + if (count > limit) count = limit; + + VDEB(printk("after count=%d\n", count)); + if ((count & 1) || (((unsigned) ptr) & 1)) + panic ("NCR53c406a: attempted unaligned DMA transfer\n"); + + save_flags(flags); + cli(); + disable_dma(dma_chan); + clear_dma_ff(dma_chan); + set_dma_addr(dma_chan, (long) ptr); + set_dma_count(dma_chan, count); + set_dma_mode(dma_chan, mode); + enable_dma(dma_chan); + restore_flags(flags); + + return count; +} + +static __inline__ int +NCR53c406a_dma_write(unsigned char *src, unsigned int count) { + return NCR53c406a_dma_setup (src, count, DMA_MODE_WRITE); +} + +static __inline__ int +NCR53c406a_dma_read(unsigned char *src, unsigned int count) { + return NCR53c406a_dma_setup (src, count, DMA_MODE_READ); +} + +static __inline__ int +NCR53c406a_dma_residual (void) { + register int tmp; + unsigned long flags = 0; + save_flags(flags); + cli(); + clear_dma_ff(dma_chan); + tmp = get_dma_residue(dma_chan); + restore_flags(flags); + + return tmp; +} +#endif USE_DMA + +#if USE_PIO +static __inline__ int NCR53c406a_pio_read(unsigned char *request, + unsigned int reqlen) +{ + int i; + int len; /* current scsi fifo size */ + unsigned long flags = 0; + + REG1; + while (reqlen) { + i = inb(PIO_STATUS); + /* VDEB(printk("pio_status=%x\n", i)); */ + if (i & 0x80) + return 0; + + switch( i & 0x1e ) { + default: + case 0x10: + len=0; break; + case 0x0: + len=1; break; + case 0x8: + len=42; break; + case 0xc: + len=84; break; + case 0xe: + len=128; break; + } + + if ((i & 0x40) && len == 0) { /* fifo empty and interrupt occurred */ + return 0; + } + + if (len) { + if( len > reqlen ) + len = reqlen; + + save_flags(flags); + cli(); + if( fast_pio && len > 3 ) { + insl(PIO_FIFO,request,len>>2); + request += len & 0xfc; + reqlen -= len & 0xfc; + } + else { + while(len--) { + *request++ = inb(PIO_FIFO); + reqlen--; + } + } + restore_flags(flags); + } + } + return 0; +} + +static __inline__ int NCR53c406a_pio_write(unsigned char *request, + unsigned int reqlen) +{ + int i = 0; + int len; /* current scsi fifo size */ + unsigned long flags = 0; + + REG1; + while (reqlen && !(i&0x40)) { + i = inb(PIO_STATUS); + /* VDEB(printk("pio_status=%x\n", i)); */ + if (i & 0x80) /* error */ + return 0; + + switch( i & 0x1e ) { + case 0x10: + len=128; break; + case 0x0: + len=84; break; + case 0x8: + len=42; break; + case 0xc: + len=1; break; + default: + case 0xe: + len=0; break; + } + + if (len) { + if( len > reqlen ) + len = reqlen; + + save_flags(flags); + cli(); + if( fast_pio && len > 3 ) { + outsl(PIO_FIFO,request,len>>2); + request += len & 0xfc; + reqlen -= len & 0xfc; + } + else { + while(len--) { + outb(*request++, PIO_FIFO); + reqlen--; + } + } + restore_flags(flags); + } + } + return 0; +} +#endif USE_PIO + +int +NCR53c406a_detect(Scsi_Host_Template * tpnt){ + struct Scsi_Host *shpnt; +#ifndef PORT_BASE + int i; +#endif + +#if USE_BIOS + int ii, jj; + bios_base = 0; + /* look for a valid signature */ + for( ii=0; ii < ADDRESS_COUNT && !bios_base; ii++) + for( jj=0; (jj < SIGNATURE_COUNT) && !bios_base; jj++) + if(!memcmp((void *) addresses[ii]+signatures[jj].sig_offset, + (void *) signatures[jj].signature, + (int) signatures[jj].sig_length)) + bios_base=addresses[ii]; + + if(!bios_base){ + printk("NCR53c406a: BIOS signature not found\n"); + return 0; + } + + DEB(printk("NCR53c406a BIOS found at %X\n", (unsigned int) bios_base);); +#endif USE_BIOS + +#ifdef PORT_BASE + if (check_region(port_base, 0x10)) /* ports already snatched */ + port_base = 0; + +#else /* autodetect */ + if (port_base) { /* LILO override */ + if (check_region(port_base, 0x10)) + port_base = 0; + } + else { + for(i=0; i<PORT_COUNT && !port_base; i++){ + if(check_region(ports[i], 0x10)){ + DEB(printk("NCR53c406a: port %x in use\n", ports[i])); + } + else { + VDEB(printk("NCR53c406a: port %x available\n", ports[i])); + outb(C5_IMG, ports[i] + 0x0d); /* reg set 1 */ + if( (inb(ports[i] + 0x0e) ^ inb(ports[i] + 0x0e)) == 7 + && (inb(ports[i] + 0x0e) ^ inb(ports[i] + 0x0e)) == 7 + && (inb(ports[i] + 0x0e) & 0xf8) == 0x58 ) { + VDEB(printk("NCR53c406a: Sig register valid\n")); + VDEB(printk("port_base=%x\n", port_base)); + port_base = ports[i]; + } + } + } + } +#endif PORT_BASE + + if(!port_base){ /* no ports found */ + printk("NCR53c406a: no available ports found\n"); + return 0; + } + + DEB(printk("NCR53c406a detected\n")); + + calc_port_addr(); + chip_init(); + +#ifndef IRQ_LEV + if (irq_level < 0) { /* LILO override if >= 0*/ + irq_level=irq_probe(); + if (irq_level < 0) { /* Trouble */ + printk("NCR53c406a: IRQ problem, irq_level=%d, giving up\n", irq_level); + return 0; + } + } +#endif + + DEB(printk("NCR53c406a: using port_base %x\n", port_base)); + request_region(port_base, 0x10, "NCR53c406a"); + + if(irq_level > 0) { + if(request_irq(irq_level, NCR53c406a_intr, 0, "NCR53c406a", NULL)){ + printk("NCR53c406a: unable to allocate IRQ %d\n", irq_level); + return 0; + } + tpnt->can_queue = 1; + DEB(printk("NCR53c406a: allocated IRQ %d\n", irq_level)); + } + else if (irq_level == 0) { + tpnt->can_queue = 0; + DEB(printk("NCR53c406a: No interrupts detected\n")); +#if USE_DMA + printk("NCR53c406a: No interrupts found and DMA mode defined. Giving up.\n"); + return 0; +#endif USE_DMA + } + else { + DEB(printk("NCR53c406a: Shouldn't get here!\n")); + return 0; + } + +#if USE_DMA + dma_chan = DMA_CHAN; + if(request_dma(dma_chan, "NCR53c406a") != 0){ + printk("NCR53c406a: unable to allocate DMA channel %d\n", dma_chan); + return 0; + } + + DEB(printk("Allocated DMA channel %d\n", dma_chan)); +#endif USE_DMA + + tpnt->present = 1; + tpnt->proc_dir = &proc_scsi_NCR53c406a; + + shpnt = scsi_register(tpnt, 0); + shpnt->irq = irq_level; + shpnt->io_port = port_base; + shpnt->n_io_port = 0x10; +#if USE_DMA + shpnt->dma = dma_chan; +#endif + +#if USE_DMA + sprintf(info_msg, "NCR53c406a at 0x%x, IRQ %d, DMA channel %d.", + port_base, irq_level, dma_chan); +#else + sprintf(info_msg, "NCR53c406a at 0x%x, IRQ %d, %s PIO mode.", + port_base, irq_level, fast_pio ? "fast" : "slow"); +#endif + + return (tpnt->present); +} + +/* called from init/main.c */ +void NCR53c406a_setup(char *str, int *ints) +{ + static size_t setup_idx = 0; + size_t i; + + DEB(printk("NCR53c406a: Setup called\n");); + + if (setup_idx >= PORT_COUNT - 1) { + printk("NCR53c406a: Setup called too many times. Bad LILO params?\n"); + return; + } + if (ints[0] < 1 || ints[0] > 3) { + printk("NCR53c406a: Malformed command line\n"); + printk("NCR53c406a: Usage: ncr53c406a=<PORTBASE>[,<IRQ>[,<FASTPIO>]]\n"); + return; + } + for (i = 0; i < PORT_COUNT && !port_base; i++) + if (ports[i] == ints[1]) { + port_base = ints[1]; + DEB(printk("NCR53c406a: Specified port_base 0x%X\n", port_base);) + } + if (!port_base) { + printk("NCR53c406a: Invalid PORTBASE 0x%X specified\n", ints[1]); + return; + } + + if (ints[0] > 1) { + if (ints[2] == 0) { + irq_level = 0; + DEB(printk("NCR53c406a: Specified irq %d\n", irq_level);) + } + else + for (i = 0; i < INTR_COUNT && irq_level < 0; i++) + if (intrs[i] == ints[2]) { + irq_level = ints[2]; + DEB(printk("NCR53c406a: Specified irq %d\n", port_base);) + } + if (irq_level < 0) + printk("NCR53c406a: Invalid IRQ %d specified\n", ints[2]); + } + + if (ints[0] > 2) + fast_pio = ints[3]; + + DEB(printk("NCR53c406a: port_base=0x%X, irq=%d, fast_pio=%d\n", + port_base, irq_level, fast_pio);) +} + +const char* +NCR53c406a_info(struct Scsi_Host *SChost){ + DEB(printk("NCR53c406a_info called\n")); + return (info_msg); +} + +static void internal_done(Scsi_Cmnd *SCpnt) { + internal_done_errcode = SCpnt->result; + ++internal_done_flag; +} + + +static void wait_intr() { + int i = jiffies + WATCHDOG; + + while(i>jiffies && !(inb(STAT_REG)&0xe0)) /* wait for a pseudo-interrupt */ + barrier(); + + if (i <= jiffies) { /* Timed out */ + rtrc(0); + current_SC->result = DID_TIME_OUT << 16; + current_SC->SCp.phase = idle; + current_SC->scsi_done(current_SC); + return; + } + + NCR53c406a_intr(0, NULL, NULL); +} + +int NCR53c406a_command(Scsi_Cmnd *SCpnt){ + DEB(printk("NCR53c406a_command called\n")); + NCR53c406a_queue(SCpnt, internal_done); + if(irq_level) + while (!internal_done_flag); + else /* interrupts not supported */ + while (!internal_done_flag) + wait_intr(); + + internal_done_flag = 0; + return internal_done_errcode; +} + + +int +NCR53c406a_queue(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)){ + int i; + unsigned long flags = 0; + + VDEB(printk("NCR53c406a_queue called\n")); + DEB(printk("cmd=%02x, cmd_len=%02x, target=%02x, lun=%02x, bufflen=%d\n", + SCpnt->cmnd[0], + SCpnt->cmd_len, + SCpnt->target, + SCpnt->lun, + SCpnt->request_bufflen)); + +#if 0 + VDEB(for(i=0; i<SCpnt->cmd_len; i++) + printk("cmd[%d]=%02x ", i, SCpnt->cmnd[i])); + VDEB(printk("\n")); +#endif + + current_SC = SCpnt; + current_SC->scsi_done = done; + current_SC->SCp.phase = command_ph; + current_SC->SCp.Status = 0; + current_SC->SCp.Message = 0; + + save_flags(flags); + cli(); + REG0; + outb(SCpnt->target, DEST_ID); /* set destination */ + outb(FLUSH_FIFO, CMD_REG); /* reset the fifos */ + + for(i=0; i<SCpnt->cmd_len; i++){ + outb(SCpnt->cmnd[i], SCSI_FIFO); + } + outb(SELECT_NO_ATN, CMD_REG); + restore_flags(flags); + + rtrc(1); + return 0; +} + +int +NCR53c406a_abort(Scsi_Cmnd *SCpnt){ + DEB(printk("NCR53c406a_abort called\n")); + return SCSI_ABORT_SNOOZE; /* Don't know how to abort */ +} + +int +NCR53c406a_reset(Scsi_Cmnd *SCpnt, unsigned int flags){ + DEB(printk("NCR53c406a_reset called\n")); + outb(C4_IMG, CONFIG4); /* Select reg set 0 */ + outb(CHIP_RESET, CMD_REG); + outb(SCSI_NOP, CMD_REG); /* required after reset */ + outb(SCSI_RESET, CMD_REG); + chip_init(); + + rtrc(2); + if (irq_level) + return SCSI_RESET_PENDING; /* should get an interrupt */ + else + return SCSI_RESET_WAKEUP; /* won't get any interrupts */ +} + +int +NCR53c406a_biosparm(Scsi_Disk *disk, kdev_t dev, int* info_array){ + int size; + + DEB(printk("NCR53c406a_biosparm called\n")); + + size = disk->capacity; + info_array[0] = 64; /* heads */ + info_array[1] = 32; /* sectors */ + info_array[2] = size>>11; /* cylinders */ + if (info_array[2] > 1024) { /* big disk */ + info_array[0] = 255; + info_array[1] = 63; + info_array[2] = size / (255*63); + } + return 0; + } + + static void +NCR53c406a_intr(int unused, void *dev_id, struct pt_regs *regs){ + DEB(unsigned char fifo_size;) + DEB(unsigned char seq_reg;) + unsigned char status, int_reg; + unsigned long flags = 0; +#if USE_PIO + unsigned char pio_status; + struct scatterlist *sglist; + unsigned int sgcount; +#endif + + VDEB(printk("NCR53c406a_intr called\n")); + + save_flags(flags); + cli(); +#if USE_PIO + REG1; + pio_status = inb(PIO_STATUS); +#endif + REG0; + status = inb(STAT_REG); + DEB(seq_reg = inb(SEQ_REG)); + int_reg = inb(INT_REG); + DEB(fifo_size = inb(FIFO_FLAGS) & 0x1f); + restore_flags(flags); + +#if NCR53C406A_DEBUG + printk("status=%02x, seq_reg=%02x, int_reg=%02x, fifo_size=%02x", + status, seq_reg, int_reg, fifo_size); +#if (USE_DMA) + printk("\n"); +#else + printk(", pio=%02x\n", pio_status); +#endif USE_DMA +#endif NCR53C406A_DEBUG + + if(int_reg & 0x80){ /* SCSI reset intr */ + rtrc(3); + DEB(printk("NCR53c406a: reset intr received\n")); + current_SC->SCp.phase = idle; + current_SC->result = DID_RESET << 16; + current_SC->scsi_done(current_SC); + return; + } + +#if USE_PIO + if(pio_status & 0x80) { + printk("NCR53C406A: Warning: PIO error!\n"); + current_SC->SCp.phase = idle; + current_SC->result = DID_ERROR << 16; + current_SC->scsi_done(current_SC); + return; + } +#endif USE_PIO + + if(status & 0x20) { /* Parity error */ + printk("NCR53c406a: Warning: parity error!\n"); + current_SC->SCp.phase = idle; + current_SC->result = DID_PARITY << 16; + current_SC->scsi_done(current_SC); + return; + } + + if(status & 0x40) { /* Gross error */ + printk("NCR53c406a: Warning: gross error!\n"); + current_SC->SCp.phase = idle; + current_SC->result = DID_ERROR << 16; + current_SC->scsi_done(current_SC); + return; + } + + if(int_reg & 0x20){ /* Disconnect */ + DEB(printk("NCR53c406a: disconnect intr received\n")); + if(current_SC->SCp.phase != message_in){ /* Unexpected disconnect */ + current_SC->result = DID_NO_CONNECT << 16; + } + else{ /* Command complete, return status and message */ + current_SC->result = (current_SC->SCp.Status & 0xff) + | ((current_SC->SCp.Message & 0xff) << 8) | (DID_OK << 16); + } + + rtrc(0); + current_SC->SCp.phase = idle; + current_SC->scsi_done( current_SC ); + return; + } + + switch(status & 0x07){ /* scsi phase */ + case 0x00: /* DATA-OUT */ + if(int_reg & 0x10){ /* Target requesting info transfer */ + rtrc(5); + current_SC->SCp.phase = data_out; + VDEB(printk("NCR53c406a: Data-Out phase\n")); + outb(FLUSH_FIFO, CMD_REG); + LOAD_DMA_COUNT(current_SC->request_bufflen); /* Max transfer size */ +#if USE_DMA /* No s/g support for DMA */ + NCR53c406a_dma_write(current_SC->request_buffer, + current_SC->request_bufflen); +#endif USE_DMA + outb(TRANSFER_INFO | DMA_OP, CMD_REG); +#if USE_PIO + if (!current_SC->use_sg) /* Don't use scatter-gather */ + NCR53c406a_pio_write(current_SC->request_buffer, + current_SC->request_bufflen); + else { /* use scatter-gather */ + sgcount = current_SC->use_sg; + sglist = current_SC->request_buffer; + while( sgcount-- ) { + NCR53c406a_pio_write(sglist->address, sglist->length); + sglist++; + } + } + REG0; +#endif USE_PIO + } + break; + + case 0x01: /* DATA-IN */ + if(int_reg & 0x10){ /* Target requesting info transfer */ + rtrc(6); + current_SC->SCp.phase = data_in; + VDEB(printk("NCR53c406a: Data-In phase\n")); + outb(FLUSH_FIFO, CMD_REG); + LOAD_DMA_COUNT(current_SC->request_bufflen); /* Max transfer size */ +#if USE_DMA /* No s/g support for DMA */ + NCR53c406a_dma_read(current_SC->request_buffer, + current_SC->request_bufflen); +#endif USE_DMA + outb(TRANSFER_INFO | DMA_OP, CMD_REG); +#if USE_PIO + if (!current_SC->use_sg) /* Don't use scatter-gather */ + NCR53c406a_pio_read(current_SC->request_buffer, + current_SC->request_bufflen); + else { /* Use scatter-gather */ + sgcount = current_SC->use_sg; + sglist = current_SC->request_buffer; + while( sgcount-- ) { + NCR53c406a_pio_read(sglist->address, sglist->length); + sglist++; + } + } + REG0; +#endif USE_PIO + } + break; + + case 0x02: /* COMMAND */ + current_SC->SCp.phase = command_ph; + printk("NCR53c406a: Warning: Unknown interrupt occurred in command phase!\n"); + break; + + case 0x03: /* STATUS */ + rtrc(7); + current_SC->SCp.phase = status_ph; + VDEB(printk("NCR53c406a: Status phase\n")); + outb(FLUSH_FIFO, CMD_REG); + outb(INIT_CMD_COMPLETE, CMD_REG); + break; + + case 0x04: /* Reserved */ + case 0x05: /* Reserved */ + printk("NCR53c406a: WARNING: Reserved phase!!!\n"); + break; + + case 0x06: /* MESSAGE-OUT */ + DEB(printk("NCR53c406a: Message-Out phase\n")); + current_SC->SCp.phase = message_out; + outb(SET_ATN, CMD_REG); /* Reject the message */ + outb(MSG_ACCEPT, CMD_REG); + break; + + case 0x07: /* MESSAGE-IN */ + rtrc(4); + VDEB(printk("NCR53c406a: Message-In phase\n")); + current_SC->SCp.phase = message_in; + + current_SC->SCp.Status = inb(SCSI_FIFO); + current_SC->SCp.Message = inb(SCSI_FIFO); + + VDEB(printk("SCSI FIFO size=%d\n", inb(FIFO_FLAGS) & 0x1f)); + DEB(printk("Status = %02x Message = %02x\n", + current_SC->SCp.Status, current_SC->SCp.Message)); + + if(current_SC->SCp.Message == SAVE_POINTERS || + current_SC->SCp.Message == DISCONNECT) { + outb(SET_ATN, CMD_REG); /* Reject message */ + DEB(printk("Discarding SAVE_POINTERS message\n")); + } + outb(MSG_ACCEPT, CMD_REG); + break; + } +} + +#ifndef IRQ_LEV +static int irq_probe() +{ + int irqs, irq; + int i; + + inb(INT_REG); /* clear the interrupt register */ + sti(); + irqs = probe_irq_on(); + + /* Invalid command will cause an interrupt */ + REG0; + outb(0xff, CMD_REG); + + /* Wait for the interrupt to occur */ + i = jiffies + WATCHDOG; + while(i > jiffies && !(inb(STAT_REG) & 0x80)) + barrier(); + if (i <= jiffies) { /* Timed out, must be hardware trouble */ + probe_irq_off(irqs); + return -1; + } + + irq = probe_irq_off(irqs); + + /* Kick the chip */ + outb(CHIP_RESET, CMD_REG); + outb(SCSI_NOP, CMD_REG); + chip_init(); + + return irq; +} +#endif IRQ_LEV + +static void chip_init() +{ + REG1; +#if USE_DMA + outb(0x00, PIO_STATUS); +#else /* USE_PIO */ + outb(0x01, PIO_STATUS); +#endif + outb(0x00, PIO_FLAG); + + outb(C4_IMG, CONFIG4); /* REG0; */ + outb(C3_IMG, CONFIG3); + outb(C2_IMG, CONFIG2); + outb(C1_IMG, CONFIG1); + + outb(0x05, CLKCONV); /* clock conversion factor */ + outb(0x9C, SRTIMOUT); /* Selection timeout */ + outb(0x05, SYNCPRD); /* Synchronous transfer period */ + outb(SYNC_MODE, SYNCOFF); /* synchronous mode */ +} + +void calc_port_addr() +{ + /* Control Register Set 0 */ + TC_LSB = (port_base+0x00); + TC_MSB = (port_base+0x01); + SCSI_FIFO = (port_base+0x02); + CMD_REG = (port_base+0x03); + STAT_REG = (port_base+0x04); + DEST_ID = (port_base+0x04); + INT_REG = (port_base+0x05); + SRTIMOUT = (port_base+0x05); + SEQ_REG = (port_base+0x06); + SYNCPRD = (port_base+0x06); + FIFO_FLAGS = (port_base+0x07); + SYNCOFF = (port_base+0x07); + CONFIG1 = (port_base+0x08); + CLKCONV = (port_base+0x09); + /* TESTREG = (port_base+0x0A); */ + CONFIG2 = (port_base+0x0B); + CONFIG3 = (port_base+0x0C); + CONFIG4 = (port_base+0x0D); + TC_HIGH = (port_base+0x0E); + /* FIFO_BOTTOM = (port_base+0x0F); */ + + /* Control Register Set 1 */ + /* JUMPER_SENSE = (port_base+0x00);*/ + /* SRAM_PTR = (port_base+0x01);*/ + /* SRAM_DATA = (port_base+0x02);*/ + PIO_FIFO = (port_base+0x04); + /* PIO_FIFO1 = (port_base+0x05);*/ + /* PIO_FIFO2 = (port_base+0x06);*/ + /* PIO_FIFO3 = (port_base+0x07);*/ + PIO_STATUS = (port_base+0x08); + /* ATA_CMD = (port_base+0x09);*/ + /* ATA_ERR = (port_base+0x0A);*/ + PIO_FLAG = (port_base+0x0B); + CONFIG5 = (port_base+0x0D); + /* SIGNATURE = (port_base+0x0E);*/ + /* CONFIG6 = (port_base+0x0F);*/ +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = NCR53c406a; + +#include "scsi_module.c" +#endif + +/* + * Overrides for Emacs so that we get a uniform tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/NCR53c406a.h b/linux/src/drivers/scsi/NCR53c406a.h new file mode 100644 index 00000000..88e45e5e --- /dev/null +++ b/linux/src/drivers/scsi/NCR53c406a.h @@ -0,0 +1,83 @@ +#ifndef _NCR53C406A_H +#define _NCR53C406A_H + +/* + * NCR53c406a.h + * + * Copyright (C) 1994 Normunds Saumanis (normunds@rx.tech.swh.lv) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + */ + +#ifndef NULL +#define NULL 0 +#endif + +/* NOTE: scatter-gather support only works in PIO mode. + * Use SG_NONE if DMA mode is enabled! + */ +#define NCR53c406a { \ + NULL /* next */, \ + NULL /* usage count */, \ + &proc_scsi_NCR53c406a /* proc_dir */, \ + NULL /* proc_info */, \ + "NCR53c406a" /* name */, \ + NCR53c406a_detect /* detect */, \ + NULL /* release */, \ + NCR53c406a_info /* info */, \ + NCR53c406a_command /* command */, \ + NCR53c406a_queue /* queuecommand */, \ + NCR53c406a_abort /* abort */, \ + NCR53c406a_reset /* reset */, \ + NULL /* slave_attach */, \ + NCR53c406a_biosparm /* biosparm */, \ + 1 /* can_queue */, \ + 7 /* SCSI ID of the chip */, \ + 32 /*SG_ALL*/ /*SG_NONE*/, \ + 1 /* commands per lun */, \ + 0 /* number of boards in system */, \ + 1 /* unchecked_isa_dma */, \ + ENABLE_CLUSTERING \ +} + +extern struct proc_dir_entry proc_scsi_NCR53c406a; + +int NCR53c406a_detect(Scsi_Host_Template *); +const char* NCR53c406a_info(struct Scsi_Host *); + +int NCR53c406a_command(Scsi_Cmnd *); +int NCR53c406a_queue(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int NCR53c406a_abort(Scsi_Cmnd *); +int NCR53c406a_reset(Scsi_Cmnd *, unsigned int); +int NCR53c406a_biosparm(Disk *, kdev_t, int []); + +#endif /* _NCR53C406A_H */ + +/* + * Overrides for Emacs so that we get a uniform tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ + diff --git a/linux/src/drivers/scsi/advansys.c b/linux/src/drivers/scsi/advansys.c new file mode 100644 index 00000000..5b7b4e07 --- /dev/null +++ b/linux/src/drivers/scsi/advansys.c @@ -0,0 +1,15547 @@ +/* $Id: advansys.c,v 1.1 1999/04/26 05:54:04 tb Exp $ */ +#define ASC_VERSION "3.1E" /* AdvanSys Driver Version */ + +/* + * advansys.c - Linux Host Driver for AdvanSys SCSI Adapters + * + * Copyright (c) 1995-1998 Advanced System Products, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that redistributions of source + * code retain the above copyright notice and this comment without + * modification. + * + * There is an AdvanSys Linux WWW page at: + * http://www.advansys.com/linux.html + * + * The latest version of the AdvanSys driver is available at: + * ftp://ftp.advansys.com/pub/linux/linux.tgz + * + * Please send questions, comments, bug reports to: + * bobf@advansys.com (Bob Frey) + */ + +/* + + Documentation for the AdvanSys Driver + + A. Linux Kernel Testing + B. Adapters Supported by this Driver + C. Linux v1.2.X - Directions for Adding the AdvanSys Driver + D. Linux v1.3.1 - v1.3.57 - Directions for Adding the AdvanSys Driver + E. Linux v1.3.58 and Newer - Upgrading the AdvanSys Driver + F. Source Comments + G. Driver Compile Time Options and Debugging + H. Driver LILO Option + I. Release History + J. Known Problems or Issues + K. Credits + L. AdvanSys Contact Information + + A. Linux Kernel Testing + + This driver has been tested in the following Linux kernels: v1.2.13, + v1.3.57, v2.0.33, v2.1.77. These kernel versions are major releases + of Linux or the latest Linux kernel versions available when this version + of the driver was released. The driver should also work in earlier + versions of the Linux kernel. Beginning with v1.3.58 the AdvanSys driver + is included with all Linux kernels. Please refer to sections C, D, and + E for instructions on adding or upgrading the AdvanSys driver. + + B. Adapters Supported by this Driver + + AdvanSys (Advanced System Products, Inc.) manufactures the following + RISC-based, Bus-Mastering, Fast (10 Mhz) and Ultra (20 Mhz) Narrow + (8-bit transfer) SCSI Host Adapters for the ISA, EISA, VL, and PCI + buses and RISC-based, Bus-Mastering, Ultra (20 Mhz) Wide (16-bit + transfer) SCSI Host Adapters for the PCI bus. + + The CDB counts below indicate the number of SCSI CDB (Command + Descriptor Block) requests that can be stored in the RISC chip + cache and board LRAM. A CDB is a single SCSI command. The driver + detect routine will display the number of CDBs available for each + adapter detected. The number of CDBs used by the driver can be + lowered in the BIOS by changing the 'Host Queue Size' adapter setting. + + Connectivity Products: + ABP510/5150 - Bus-Master ISA (240 CDB) (Footnote 1) + ABP5140 - Bus-Master ISA PnP (16 CDB) (Footnote 1, 3) + ABP5142 - Bus-Master ISA PnP with floppy (16 CDB) (Footnote 4) + ABP920 - Bus-Master PCI (16 CDB) + ABP930 - Bus-Master PCI (16 CDB) (Footnote 5) + ABP930U - Bus-Master PCI Ultra (16 CDB) + ABP930UA - Bus-Master PCI Ultra (16 CDB) + ABP960 - Bus-Master PCI MAC/PC (16 CDB) (Footnote 2) + ABP960U - Bus-Master PCI MAC/PC Ultra (16 CDB) (Footnote 2) + + Single Channel Products: + ABP542 - Bus-Master ISA with floppy (240 CDB) + ABP742 - Bus-Master EISA (240 CDB) + ABP842 - Bus-Master VL (240 CDB) + ABP940 - Bus-Master PCI (240 CDB) + ABP940U - Bus-Master PCI Ultra (240 CDB) + ABP970 - Bus-Master PCI MAC/PC (240 CDB) + ABP970U - Bus-Master PCI MAC/PC Ultra (240 CDB) + ABP940UW - Bus-Master PCI Ultra-Wide (240 CDB) + + Multi Channel Products: + ABP752 - Dual Channel Bus-Master EISA (240 CDB Per Channel) + ABP852 - Dual Channel Bus-Master VL (240 CDB Per Channel) + ABP950 - Dual Channel Bus-Master PCI (240 CDB Per Channel) + ABP980 - Four Channel Bus-Master PCI (240 CDB Per Channel) + ABP980U - Four Channel Bus-Master PCI Ultra (240 CDB Per Channel) + + Footnotes: + 1. This board has been shipped by HP with the 4020i CD-R drive. + The board has no BIOS so it cannot control a boot device, but + it can control any secondary SCSI device. + 2. This board has been sold by Iomega as a Jaz Jet PCI adapter. + 3. This board has been sold by SIIG as the i540 SpeedMaster. + 4. This board has been sold by SIIG as the i542 SpeedMaster. + 5. This board has been sold by SIIG as the Fast SCSI Pro PCI. + + C. Linux v1.2.X - Directions for Adding the AdvanSys Driver + + These directions apply to v1.2.13. For versions that follow v1.2.13. + but precede v1.3.57 some of the changes for Linux v1.3.X listed + below may need to be modified or included. A patch is available + for v1.2.13 from the AdvanSys WWW and FTP sites. + + There are two source files: advansys.h and advansys.c. Copy + both of these files to the directory /usr/src/linux/drivers/scsi. + + 1. Add the following line to /usr/src/linux/arch/i386/config.in + after "comment 'SCSI low-level drivers'": + + bool 'AdvanSys SCSI support' CONFIG_SCSI_ADVANSYS y + + 2. Add the following lines to /usr/src/linux/drivers/scsi/hosts.c + after "#include "hosts.h"": + + #ifdef CONFIG_SCSI_ADVANSYS + #include "advansys.h" + #endif + + and after "static Scsi_Host_Template builtin_scsi_hosts[] =": + + #ifdef CONFIG_SCSI_ADVANSYS + ADVANSYS, + #endif + + 3. Add the following lines to /usr/src/linux/drivers/scsi/Makefile: + + ifdef CONFIG_SCSI_ADVANSYS + SCSI_SRCS := $(SCSI_SRCS) advansys.c + SCSI_OBJS := $(SCSI_OBJS) advansys.o + else + SCSI_MODULE_OBJS := $(SCSI_MODULE_OBJS) advansys.o + endif + + 4. (Optional) If you would like to enable the LILO command line + and /etc/lilo.conf 'advansys' option, make the following changes. + This option can be used to disable I/O port scanning or to limit + I/O port scanning to specific addresses. Refer to the 'Driver + LILO Option' section below. Add the following lines to + /usr/src/linux/init/main.c in the prototype section: + + extern void advansys_setup(char *str, int *ints); + + and add the following lines to the bootsetups[] array. + + #ifdef CONFIG_SCSI_ADVANSYS + { "advansys=", advansys_setup }, + #endif + + 5. If you have the HP 4020i CD-R driver and Linux v1.2.X you should + add a fix to the CD-ROM target driver. This fix will allow + you to mount CDs with the iso9660 file system. Linux v1.3.X + already has this fix. In the file /usr/src/linux/drivers/scsi/sr.c + and function get_sectorsize() after the line: + + if(scsi_CDs[i].sector_size == 0) scsi_CDs[i].sector_size = 2048; + + add the following line: + + if(scsi_CDs[i].sector_size == 2340) scsi_CDs[i].sector_size = 2048; + + 6. In the directory /usr/src/linux run 'make config' to configure + the AdvanSys driver, then run 'make vmlinux' or 'make zlilo' to + make the kernel. If the AdvanSys driver is not configured, then + a loadable module can be built by running 'make modules' and + 'make modules_install'. Use 'insmod' and 'rmmod' to install + and remove advansys.o. + + D. Linux v1.3.1 - v1.3.57 - Directions for Adding the AdvanSys Driver + + These directions apply to v1.3.57. For versions that precede v1.3.57 + some of these changes may need to be modified or eliminated. A patch + is available for v1.3.57 from the AdvanSys WWW and FTP sites. + Beginning with v1.3.58 this driver is included with the Linux + distribution eliminating the need for making any changes. + + There are two source files: advansys.h and advansys.c. Copy + both of these files to the directory /usr/src/linux/drivers/scsi. + + 1. Add the following line to /usr/src/linux/drivers/scsi/Config.in + after "comment 'SCSI low-level drivers'": + + dep_tristate 'AdvanSys SCSI support' CONFIG_SCSI_ADVANSYS $CONFIG_SCSI + + 2. Add the following lines to /usr/src/linux/drivers/scsi/hosts.c + after "#include "hosts.h"": + + #ifdef CONFIG_SCSI_ADVANSYS + #include "advansys.h" + #endif + + and after "static Scsi_Host_Template builtin_scsi_hosts[] =": + + #ifdef CONFIG_SCSI_ADVANSYS + ADVANSYS, + #endif + + 3. Add the following lines to /usr/src/linux/drivers/scsi/Makefile: + + ifeq ($(CONFIG_SCSI_ADVANSYS),y) + L_OBJS += advansys.o + else + ifeq ($(CONFIG_SCSI_ADVANSYS),m) + M_OBJS += advansys.o + endif + endif + + 4. Add the following line to /usr/src/linux/include/linux/proc_fs.h + in the enum scsi_directory_inos array: + + PROC_SCSI_ADVANSYS, + + 5. (Optional) If you would like to enable the LILO command line + and /etc/lilo.conf 'advansys' option, make the following changes. + This option can be used to disable I/O port scanning or to limit + I/O port scanning to specific addresses. Refer to the 'Driver + LILO Option' section below. Add the following lines to + /usr/src/linux/init/main.c in the prototype section: + + extern void advansys_setup(char *str, int *ints); + + and add the following lines to the bootsetups[] array. + + #ifdef CONFIG_SCSI_ADVANSYS + { "advansys=", advansys_setup }, + #endif + + 6. In the directory /usr/src/linux run 'make config' to configure + the AdvanSys driver, then run 'make vmlinux' or 'make zlilo' to + make the kernel. If the AdvanSys driver is not configured, then + a loadable module can be built by running 'make modules' and + 'make modules_install'. Use 'insmod' and 'rmmod' to install + and remove advansys.o. + + E. Linux v1.3.58 and Newer - Upgrading the AdvanSys Driver + + To upgrade the AdvanSys driver in a Linux v1.3.58 and newer + kernel, first check the version of the current driver. The + version is defined by the manifest constant ASC_VERSION at + the beginning of advansys.c. The new driver should have a + ASC_VERSION value greater than the current version. To install + the new driver rename advansys.c and advansys.h in the Linux + kernel source tree drivers/scsi directory to different names + or save them to a different directory in case you want to revert + to the old version of the driver. After the old driver is saved + copy the new advansys.c and advansys.h to drivers/scsi, rebuild + the kernel, and install the new kernel. No other changes are needed. + + F. Source Comments + + 1. Use tab stops set to 4 for the source files. For vi use 'se tabstops=4'. + + 2. This driver should be maintained in multiple files. But to make + it easier to include with Linux and to follow Linux conventions, + the whole driver is maintained in the source files advansys.h and + advansys.c. In this file logical sections of the driver begin with + a comment that contains '---'. The following are the logical sections + of the driver below. + + --- Linux Version + --- Linux Include Files + --- Driver Options + --- Debugging Header + --- Asc Library Constants and Macros + --- Adv Library Constants and Macros + --- Driver Constants and Macros + --- Driver Structures + --- Driver Data + --- Driver Function Prototypes + --- Linux 'Scsi_Host_Template' and advansys_setup() Functions + --- Loadable Driver Support + --- Miscellaneous Driver Functions + --- Functions Required by the Asc Library + --- Functions Required by the Adv Library + --- Tracing and Debugging Functions + --- Asc Library Functions + --- Adv Library Functions + + 3. The string 'XXX' is used to flag code that needs to be re-written + or that contains a problem that needs to be addressed. + + 4. I have stripped comments from and reformatted the source for the + Asc Library and Adv Library to reduce the size of this file. This + source can be found under the following headings. The Asc Library + is used to support Narrow Boards. The Adv Library is used to + support Wide Boards. + + --- Asc Library Constants and Macros + --- Adv Library Constants and Macros + --- Asc Library Functions + --- Adv Library Functions + + G. Driver Compile Time Options and Debugging + + In this source file the following constants can be defined. They are + defined in the source below. Both of these options are enabled by + default. + + 1. ADVANSYS_ASSERT - Enable driver assertions (Def: Enabled) + + Enabling this option adds assertion logic statements to the + driver. If an assertion fails a message will be displayed to + the console, but the system will continue to operate. Any + assertions encountered should be reported to the person + responsible for the driver. Assertion statements may proactively + detect problems with the driver and facilitate fixing these + problems. Enabling assertions will add a small overhead to the + execution of the driver. + + 2. ADVANSYS_DEBUG - Enable driver debugging (Def: Disabled) + + Enabling this option adds tracing functions to the driver and + the ability to set a driver tracing level at boot time. This + option will also export symbols not required outside the driver to + the kernel name space. This option is very useful for debugging + the driver, but it will add to the size of the driver execution + image and add overhead to the execution of the driver. + + The amount of debugging output can be controlled with the global + variable 'asc_dbglvl'. The higher the number the more output. By + default the debug level is 0. + + If the driver is loaded at boot time and the LILO Driver Option + is included in the system, the debug level can be changed by + specifying a 5th (ASC_NUM_IOPORT_PROBE + 1) I/O Port. The + first three hex digits of the pseudo I/O Port must be set to + 'deb' and the fourth hex digit specifies the debug level: 0 - F. + The following command line will look for an adapter at 0x330 + and set the debug level to 2. + + linux advansys=0x330,0,0,0,0xdeb2 + + If the driver is built as a loadable module this variable can be + defined when the driver is loaded. The following insmod command + will set the debug level to one. + + insmod advansys.o asc_dbglvl=1 + + Debugging Message Levels: + 0: Errors Only + 1: High-Level Tracing + 2-N: Verbose Tracing + + I don't know the approved way for turning on printk()s to the + console. Here's a program I use to do this. Debug output is + logged in /var/adm/messages. + + main() + { + syscall(103, 7, 0, 0); + } + + I found that increasing LOG_BUF_LEN to 40960 in kernel/printk.c + prevents most level 1 debug messages from being lost. + + 3. ADVANSYS_STATS - Enable statistics (Def: Enabled >= v1.3.0) + + Enabling this option adds statistics collection and display + through /proc to the driver. The information is useful for + monitoring driver and device performance. It will add to the + size of the driver execution image and add minor overhead to + the execution of the driver. + + Statistics are maintained on a per adapter basis. Driver entry + point call counts and transfer size counts are maintained. + Statistics are only available for kernels greater than or equal + to v1.3.0 with the CONFIG_PROC_FS (/proc) file system configured. + + AdvanSys SCSI adapter files have the following path name format: + + /proc/scsi/advansys/[0-(ASC_NUM_BOARD_SUPPORTED-1)] + + This information can be displayed with cat. For example: + + cat /proc/scsi/advansys/0 + + When ADVANSYS_STATS is not defined the AdvanSys /proc files only + contain adapter and device configuration information. + + H. Driver LILO Option + + If init/main.c is modified as described in the 'Directions for Adding + the AdvanSys Driver to Linux' section (B.4.) above, the driver will + recognize the 'advansys' LILO command line and /etc/lilo.conf option. + This option can be used to either disable I/O port scanning or to limit + scanning to 1 - 4 I/O ports. Regardless of the option setting EISA and + PCI boards will still be searched for and detected. This option only + affects searching for ISA and VL boards. + + Examples: + 1. Eliminate I/O port scanning: + boot: linux advansys= + or + boot: linux advansys=0x0 + 2. Limit I/O port scanning to one I/O port: + boot: linux advansys=0x110 + 3. Limit I/O port scanning to four I/O ports: + boot: linux advansys=0x110,0x210,0x230,0x330 + + For a loadable module the same effect can be achieved by setting + the 'asc_iopflag' variable and 'asc_ioport' array when loading + the driver, e.g. + + insmod advansys.o asc_iopflag=1 asc_ioport=0x110,0x330 + + If ADVANSYS_DEBUG is defined a 5th (ASC_NUM_IOPORT_PROBE + 1) + I/O Port may be added to specify the driver debug level. Refer to + the 'Driver Compile Time Options and Debugging' section above for + more information. + + I. Release History + + BETA-1.0 (12/23/95): + First Release + + BETA-1.1 (12/28/95): + 1. Prevent advansys_detect() from being called twice. + 2. Add LILO 0xdeb[0-f] option to set 'asc_dbglvl'. + + 1.2 (1/12/96): + 1. Prevent re-entrancy in the interrupt handler which + resulted in the driver hanging Linux. + 2. Fix problem that prevented ABP-940 cards from being + recognized on some PCI motherboards. + 3. Add support for the ABP-5140 PnP ISA card. + 4. Fix check condition return status. + 5. Add conditionally compiled code for Linux v1.3.X. + + 1.3 (2/23/96): + 1. Fix problem in advansys_biosparam() that resulted in the + wrong drive geometry being returned for drives > 1GB with + extended translation enabled. + 2. Add additional tracing during device initialization. + 3. Change code that only applies to ISA PnP adapter. + 4. Eliminate 'make dep' warning. + 5. Try to fix problem with handling resets by increasing their + timeout value. + + 1.4 (5/8/96): + 1. Change definitions to eliminate conflicts with other subsystems. + 2. Add versioning code for the shared interrupt changes. + 3. Eliminate problem in asc_rmqueue() with iterating after removing + a request. + 4. Remove reset request loop problem from the "Known Problems or + Issues" section. This problem was isolated and fixed in the + mid-level SCSI driver. + + 1.5 (8/8/96): + 1. Add support for ABP-940U (PCI Ultra) adapter. + 2. Add support for IRQ sharing by setting the SA_SHIRQ flag for + request_irq and supplying a dev_id pointer to both request_irq() + and free_irq(). + 3. In AscSearchIOPortAddr11() restore a call to check_region() which + should be used before I/O port probing. + 4. Fix bug in asc_prt_hex() which resulted in the displaying + the wrong data. + 5. Incorporate miscellaneous Asc Library bug fixes and new microcode. + 6. Change driver versioning to be specific to each Linux sub-level. + 7. Change statistics gathering to be per adapter instead of global + to the driver. + 8. Add more information and statistics to the adapter /proc file: + /proc/scsi/advansys[0...]. + 9. Remove 'cmd_per_lun' from the "Known Problems or Issues" list. + This problem has been addressed with the SCSI mid-level changes + made in v1.3.89. The advansys_select_queue_depths() function + was added for the v1.3.89 changes. + + 1.6 (9/10/96): + 1. Incorporate miscellaneous Asc Library bug fixes and new microcode. + + 1.7 (9/25/96): + 1. Enable clustering and optimize the setting of the maximum number + of scatter gather elements for any particular board. Clustering + increases CPU utilization, but results in a relatively larger + increase in I/O throughput. + 2. Improve the performance of the request queuing functions by + adding a last pointer to the queue structure. + 3. Correct problems with reset and abort request handling that + could have hung or crashed Linux. + 4. Add more information to the adapter /proc file: + /proc/scsi/advansys[0...]. + 5. Remove the request timeout issue form the driver issues list. + 6. Miscellaneous documentation additions and changes. + + 1.8 (10/4/96): + 1. Make changes to handle the new v2.1.0 kernel memory mapping + in which a kernel virtual address may not be equivalent to its + bus or DMA memory address. + 2. Change abort and reset request handling to make it yet even + more robust. + 3. Try to mitigate request starvation by sending ordered requests + to heavily loaded, tag queuing enabled devices. + 4. Maintain statistics on request response time. + 5. Add request response time statistics and other information to + the adapter /proc file: /proc/scsi/advansys[0...]. + + 1.9 (10/21/96): + 1. Add conditionally compiled code (ASC_QUEUE_FLOW_CONTROL) to + make use of mid-level SCSI driver device queue depth flow + control mechanism. This will eliminate aborts caused by a + device being unable to keep up with requests and eliminate + repeat busy or QUEUE FULL status returned by a device. + 2. Incorporate miscellaneous Asc Library bug fixes. + 3. To allow the driver to work in kernels with broken module + support set 'cmd_per_lun' if the driver is compiled as a + module. This change affects kernels v1.3.89 to present. + 4. Remove PCI BIOS address from the driver banner. The PCI BIOS + is relocated by the motherboard BIOS and its new address can + not be determined by the driver. + 5. Add mid-level SCSI queue depth information to the adapter + /proc file: /proc/scsi/advansys[0...]. + + 2.0 (11/14/96): + 1. Change allocation of global structures used for device + initialization to guarantee they are in DMA-able memory. + Previously when the driver was loaded as a module these + structures might not have been in DMA-able memory, causing + device initialization to fail. + + 2.1 (12/30/96): + 1. In advansys_reset(), if the request is a synchronous reset + request, even if the request serial number has changed, then + complete the request. + 2. Add Asc Library bug fixes including new microcode. + 3. Clear inquiry buffer before using it. + 4. Correct ifdef typo. + + 2.2 (1/15/97): + 1. Add Asc Library bug fixes including new microcode. + 2. Add synchronous data transfer rate information to the + adapter /proc file: /proc/scsi/advansys[0...]. + 3. Change ADVANSYS_DEBUG to be disabled by default. This + will reduce the size of the driver image, eliminate execution + overhead, and remove unneeded symbols from the kernel symbol + space that were previously added by the driver. + 4. Add new compile-time option ADVANSYS_ASSERT for assertion + code that used to be defined within ADVANSYS_DEBUG. This + option is enabled by default. + + 2.8 (5/26/97): + 1. Change version number to 2.8 to synchronize the Linux driver + version numbering with other AdvanSys drivers. + 2. Reformat source files without tabs to present the same view + of the file to everyone regardless of the editor tab setting + being used. + 3. Add Asc Library bug fixes. + + 3.1A (1/8/98): + 1. Change version number to 3.1 to indicate that support for + Ultra-Wide adapters (ABP-940UW) is included in this release. + 2. Add Asc Library (Narrow Board) bug fixes. + 3. Report an underrun condition with the host status byte set + to DID_UNDERRUN. Currently DID_UNDERRUN is defined to 0 which + causes the underrun condition to be ignored. When Linux defines + its own DID_UNDERRUN the constant defined in this file can be + removed. + 4. Add patch to AscWaitTixISRDone(). + 5. Add support for up to 16 different AdvanSys host adapter SCSI + channels in one system. This allows four cards with four channels + to be used in one system. + + 3.1B (1/9/98): + 1. Handle that PCI register base addresses are not always page + aligned even though ioremap() requires that the address argument + be page aligned. + + 3.1C (1/10/98): + 1. Update latest BIOS version checked for from the /proc file. + 2. Don't set microcode SDTR variable at initialization. Instead + wait until device capabilities have been detected from an Inquiry + command. + + 3.1D (1/21/98): + 1. Improve performance when the driver is compiled as module by + allowing up to 64 scatter-gather elements instead of 8. + + 3.1E (5/1/98): + 1. Set time delay in AscWaitTixISRDone() to 1000 ms. + 2. Include SMP locking changes. + 3. For v2.1.93 and newer kernels use CONFIG_PCI and new PCI BIOS + access functions. + 4. Update board serial number printing. + 5. Try allocating an IRQ both with and without the SA_INTERRUPT + flag set to allow IRQ sharing with drivers that do not set + the SA_INTERRUPT flag. Also display a more descriptive error + message if request_irq() fails. + 5. Update to latest Asc and Adv Libraries. + + J. Known Problems or Issues + + 1. Remove conditional constants (ASC_QUEUE_FLOW_CONTROL) around + the queue depth flow control code when mid-level SCSI changes + are included in Linux. + + K. Credits + + Nathan Hartwell <mage@cdc3.cdc.net> provided the directions and + basis for the Linux v1.3.X changes which were included in the + 1.2 release. + + Thomas E Zerucha <zerucha@shell.portal.com> pointed out a bug + in advansys_biosparam() which was fixed in the 1.3 release. + + Erik Ratcliffe <erik@caldera.com> has done testing of the + AdvanSys driver in the Caldera releases. + + Rik van Riel <H.H.vanRiel@fys.ruu.nl> provided a patch to + AscWaitTixISRDone() which he found necessary to make the + driver work with a SCSI-1 disk. + + Mark Moran <mmoran@mmoran.com> has helped test Ultra-Wide + support in the 3.1A driver. + + L. AdvanSys Contact Information + + Mail: Advanced System Products, Inc. + 1150 Ringwood Court + San Jose, CA 95131 + Operator: 1-408-383-9400 + FAX: 1-408-383-9612 + Tech Support: 1-800-525-7440/1-408-467-2930 + BBS: 1-408-383-9540 (14400,N,8,1) + Interactive FAX: 1-408-383-9753 + Customer Direct Sales: 1-800-525-7443/1-408-383-5777 + Tech Support E-Mail: support@advansys.com + FTP Site: ftp.advansys.com (login: anonymous) + Web Site: http://www.advansys.com + +*/ + + +/* + * --- Linux Version + */ + +/* Convert Linux Version, Patch-level, Sub-level to LINUX_VERSION_CODE. */ +#define ASC_LINUX_VERSION(V, P, S) (((V) * 65536) + ((P) * 256) + (S)) + +#ifndef LINUX_VERSION_CODE +#include <linux/version.h> +#endif /* LINUX_VERSION_CODE */ + + +/* + * --- Linux Include Files + */ + +#include <linux/config.h> +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) +#ifdef MODULE +#include <linux/module.h> +#endif /* MODULE */ +#endif /* version >= v1.3.0 */ +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/head.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) +#include <linux/proc_fs.h> +#endif /* version >= v1.3.0 */ +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(2,1,23) +#include <linux/init.h> +#endif /* version >= v2.1.23 */ +#include <asm/io.h> +#include <asm/system.h> +#include <asm/dma.h> +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) +#include "../block/blk.h" +#else /* version >= v1.3.0 */ +#include <linux/blk.h> +#include <linux/stat.h> +#endif /* version >= v1.3.0 */ +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(2,1,95) +#include <asm/spinlock.h> +#endif /* version >= 2.1.95 */ +#include "scsi.h" +#include "hosts.h" +#include "sd.h" +#include "advansys.h" +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(2,1,93) +#ifdef CONFIG_PCI +#include <linux/pci.h> +#endif /* CONFIG_PCI */ +#else /* version < v2.1.93 */ +/* + * For earlier than v2.1.93 the driver has its own PCI configuration. + * If PCI is not needed in a kernel before v2.1.93 this define can be + * turned-off to make the driver object smaller. + */ +#define ASC_CONFIG_PCI +#endif /* version < v2.1.93 */ + +/* + * If Linux eventually defines a DID_UNDERRUN, the constant here can be + * removed. The current value of zero for DID_UNDERRUN results in underrun + * conditions being ignored. + */ +#define DID_UNDERRUN 0 + + +/* + * --- Driver Options + */ + +/* Enable driver assertions. */ +#define ADVANSYS_ASSERT + +/* Enable driver tracing. */ +/* #define ADVANSYS_DEBUG */ + +/* + * Because of no /proc to display them, statistics are disabled + * for versions prior to v1.3.0. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) +#undef ADVANSYS_STATS /* Disable statistics */ +#else /* version >= v1.3.0 */ +#define ADVANSYS_STATS /* Enable statistics. */ +#endif /* version >= v1.3.0 */ + + +/* + * --- Debugging Header + */ + +#ifdef ADVANSYS_DEBUG +#define STATIC +#else /* ADVANSYS_DEBUG */ +#define STATIC static +#endif /* ADVANSYS_DEBUG */ + + +/* + * --- Asc Library Constants and Macros + */ + +#define ASC_LIB_VERSION_MAJOR 1 +#define ASC_LIB_VERSION_MINOR 22 +#define ASC_LIB_SERIAL_NUMBER 113 + +typedef unsigned char uchar; + +#ifndef NULL +#define NULL (0) +#endif +#ifndef TRUE +#define TRUE (1) +#endif +#ifndef FALSE +#define FALSE (0) +#endif +#define REG register +#define rchar REG __s8 +#define rshort REG __s16 +#define rint REG __s32 +#define rlong REG __s32 +#define ruchar REG __u8 +#define rushort REG __u16 +#define ruint REG __u32 +#define rulong REG __u32 +#define NULLPTR (void *)0 +#define FNULLPTR (void *)0UL +#define EOF (-1) +#define EOS '\0' +#define ERR (-1) +#define UB_ERR (uchar)(0xFF) +#define UW_ERR (uint)(0xFFFF) +#define UL_ERR (ulong)(0xFFFFFFFFUL) +#define iseven_word(val) ((((uint)val) & (uint)0x0001) == 0) +#define isodd_word(val) ((((uint)val) & (uint)0x0001) != 0) +#define toeven_word(val) (((uint)val) & (uint)0xFFFE) +#define biton(val, bits) (((uint)(val >> bits) & (uint)0x0001) != 0) +#define bitoff(val, bits) (((uint)(val >> bits) & (uint)0x0001) == 0) +#define lbiton(val, bits) (((ulong)(val >> bits) & (ulong)0x00000001UL) != 0) +#define lbitoff(val, bits) (((ulong)(val >> bits) & (ulong)0x00000001UL) == 0) +#define absh(val) ((val) < 0 ? -(val) : (val)) +#define swapbyte(ch) ((((ch) << 4) | ((ch) >> 4))) +#ifndef GBYTE +#define GBYTE (0x40000000UL) +#endif +#ifndef MBYTE +#define MBYTE (0x100000UL) +#endif +#ifndef KBYTE +#define KBYTE (0x400) +#endif +#define HI_BYTE(x) (*((__u8 *)(&x)+1)) +#define LO_BYTE(x) (*((__u8 *)&x)) +#define HI_WORD(x) (*((__u16 *)(&x)+1)) +#define LO_WORD(x) (*((__u16 *)&x)) +#ifndef MAKEWORD +#define MAKEWORD(lo, hi) ((__u16) (((__u16) lo) | ((__u16) hi << 8))) +#endif +#ifndef MAKELONG +#define MAKELONG(lo, hi) ((__u32) (((__u32) lo) | ((__u32) hi << 16))) +#endif +#define SwapWords(dWord) ((__u32) ((dWord >> 16) | (dWord << 16))) +#define SwapBytes(word) ((__u16) ((word >> 8) | (word << 8))) +#define BigToLittle(dWord) ((__u32) (SwapWords(MAKELONG(SwapBytes(LO_WORD(dWord)), SwapBytes(HI_WORD(dWord)))))) +#define LittleToBig(dWord) BigToLittle(dWord) +#define AscPCIConfigVendorIDRegister 0x0000 +#define AscPCIConfigDeviceIDRegister 0x0002 +#define AscPCIConfigCommandRegister 0x0004 +#define AscPCIConfigStatusRegister 0x0006 +#define AscPCIConfigRevisionIDRegister 0x0008 +#define AscPCIConfigCacheSize 0x000C +#define AscPCIConfigLatencyTimer 0x000D +#define AscPCIIOBaseRegister 0x0010 +#define AscPCICmdRegBits_IOMemBusMaster 0x0007 +#define ASC_PCI_ID2BUS(id) ((id) & 0xFF) +#define ASC_PCI_ID2DEV(id) (((id) >> 11) & 0x1F) +#define ASC_PCI_ID2FUNC(id) (((id) >> 8) & 0x7) +#define ASC_PCI_MKID(bus, dev, func) ((((dev) & 0x1F) << 11) | (((func) & 0x7) << 8) | ((bus) & 0xFF)) +#define ASC_PCI_VENDORID 0x10CD +#define ASC_PCI_DEVICEID_1200A 0x1100 +#define ASC_PCI_DEVICEID_1200B 0x1200 +#define ASC_PCI_DEVICEID_ULTRA 0x1300 +#define ASC_PCI_REVISION_3150 0x02 +#define ASC_PCI_REVISION_3050 0x03 + +#define ASC_DVCLIB_CALL_DONE (1) +#define ASC_DVCLIB_CALL_FAILED (0) +#define ASC_DVCLIB_CALL_ERROR (-1) + +#define PortAddr unsigned short /* port address size */ +#define Ptr2Func ulong +#define inp(port) inb(port) +#define inpw(port) inw(port) +#define inpl(port) inl(port) +#define outp(port, byte) outb((byte), (port)) +#define outpw(port, word) outw((word), (port)) +#define outpl(port, long) outl((long), (port)) +#define ASC_MAX_SG_QUEUE 7 +#define ASC_MAX_SG_LIST SG_ALL + +#define ASC_CS_TYPE unsigned short +#ifndef asc_ptr_type +#define asc_ptr_type +#endif + +#ifndef ASC_GET_PTR2FUNC +#define ASC_GET_PTR2FUNC(fun) (Ptr2Func)(fun) +#endif +#define FLIP_BYTE_NIBBLE(x) (((x<<4)& 0xFF) | (x>>4)) +#define ASC_IS_ISA (0x0001) +#define ASC_IS_ISAPNP (0x0081) +#define ASC_IS_EISA (0x0002) +#define ASC_IS_PCI (0x0004) +#define ASC_IS_PCI_ULTRA (0x0104) +#define ASC_IS_PCMCIA (0x0008) +#define ASC_IS_MCA (0x0020) +#define ASC_IS_VL (0x0040) +#define ASC_ISA_PNP_PORT_ADDR (0x279) +#define ASC_ISA_PNP_PORT_WRITE (ASC_ISA_PNP_PORT_ADDR+0x800) +#define ASC_IS_WIDESCSI_16 (0x0100) +#define ASC_IS_WIDESCSI_32 (0x0200) +#define ASC_IS_BIG_ENDIAN (0x8000) +#define ASC_CHIP_MIN_VER_VL (0x01) +#define ASC_CHIP_MAX_VER_VL (0x07) +#define ASC_CHIP_MIN_VER_PCI (0x09) +#define ASC_CHIP_MAX_VER_PCI (0x0F) +#define ASC_CHIP_VER_PCI_BIT (0x08) +#define ASC_CHIP_MIN_VER_ISA (0x11) +#define ASC_CHIP_MIN_VER_ISA_PNP (0x21) +#define ASC_CHIP_MAX_VER_ISA (0x27) +#define ASC_CHIP_VER_ISA_BIT (0x30) +#define ASC_CHIP_VER_ISAPNP_BIT (0x20) +#define ASC_CHIP_VER_ASYN_BUG (0x21) +#define ASC_CHIP_VER_PCI 0x08 +#define ASC_CHIP_VER_PCI_ULTRA_3150 (ASC_CHIP_VER_PCI | 0x02) +#define ASC_CHIP_VER_PCI_ULTRA_3050 (ASC_CHIP_VER_PCI | 0x03) +#define ASC_CHIP_MIN_VER_EISA (0x41) +#define ASC_CHIP_MAX_VER_EISA (0x47) +#define ASC_CHIP_VER_EISA_BIT (0x40) +#define ASC_CHIP_LATEST_VER_EISA ((ASC_CHIP_MIN_VER_EISA - 1) + 3) +#define ASC_MAX_LIB_SUPPORTED_ISA_CHIP_VER 0x21 +#define ASC_MAX_LIB_SUPPORTED_PCI_CHIP_VER 0x0A +#define ASC_MAX_VL_DMA_ADDR (0x07FFFFFFL) +#define ASC_MAX_VL_DMA_COUNT (0x07FFFFFFL) +#define ASC_MAX_PCI_DMA_ADDR (0xFFFFFFFFL) +#define ASC_MAX_PCI_DMA_COUNT (0xFFFFFFFFL) +#define ASC_MAX_ISA_DMA_ADDR (0x00FFFFFFL) +#define ASC_MAX_ISA_DMA_COUNT (0x00FFFFFFL) +#define ASC_MAX_EISA_DMA_ADDR (0x07FFFFFFL) +#define ASC_MAX_EISA_DMA_COUNT (0x07FFFFFFL) +#ifndef inpw_noswap +#define inpw_noswap(port) inpw(port) +#endif +#ifndef outpw_noswap +#define outpw_noswap(port, data) outpw(port, data) +#endif +#define ASC_SCSI_ID_BITS 3 +#define ASC_SCSI_TIX_TYPE uchar +#define ASC_ALL_DEVICE_BIT_SET 0xFF +#ifdef ASC_WIDESCSI_16 +#undef ASC_SCSI_ID_BITS +#define ASC_SCSI_ID_BITS 4 +#define ASC_ALL_DEVICE_BIT_SET 0xFFFF +#endif +#ifdef ASC_WIDESCSI_32 +#undef ASC_SCSI_ID_BITS +#define ASC_SCSI_ID_BITS 5 +#define ASC_ALL_DEVICE_BIT_SET 0xFFFFFFFFL +#endif +#if ASC_SCSI_ID_BITS == 3 +#define ASC_SCSI_BIT_ID_TYPE uchar +#define ASC_MAX_TID 7 +#define ASC_MAX_LUN 7 +#define ASC_SCSI_WIDTH_BIT_SET 0xFF +#elif ASC_SCSI_ID_BITS == 4 +#define ASC_SCSI_BIT_ID_TYPE ushort +#define ASC_MAX_TID 15 +#define ASC_MAX_LUN 7 +#define ASC_SCSI_WIDTH_BIT_SET 0xFFFF +#elif ASC_SCSI_ID_BITS == 5 +#define ASC_SCSI_BIT_ID_TYPE ulong +#define ASC_MAX_TID 31 +#define ASC_MAX_LUN 7 +#define ASC_SCSI_WIDTH_BIT_SET 0xFFFFFFFF +#else +#error ASC_SCSI_ID_BITS definition is wrong +#endif +#define ASC_MAX_SENSE_LEN 32 +#define ASC_MIN_SENSE_LEN 14 +#define ASC_MAX_CDB_LEN 12 +#define ASC_SCSI_RESET_HOLD_TIME_US 60 +#define SCSICMD_TestUnitReady 0x00 +#define SCSICMD_Rewind 0x01 +#define SCSICMD_Rezero 0x01 +#define SCSICMD_RequestSense 0x03 +#define SCSICMD_Format 0x04 +#define SCSICMD_FormatUnit 0x04 +#define SCSICMD_Read6 0x08 +#define SCSICMD_Write6 0x0A +#define SCSICMD_Seek6 0x0B +#define SCSICMD_Inquiry 0x12 +#define SCSICMD_Verify6 0x13 +#define SCSICMD_ModeSelect6 0x15 +#define SCSICMD_ModeSense6 0x1A +#define SCSICMD_StartStopUnit 0x1B +#define SCSICMD_LoadUnloadTape 0x1B +#define SCSICMD_ReadCapacity 0x25 +#define SCSICMD_Read10 0x28 +#define SCSICMD_Write10 0x2A +#define SCSICMD_Seek10 0x2B +#define SCSICMD_Erase10 0x2C +#define SCSICMD_WriteAndVerify10 0x2E +#define SCSICMD_Verify10 0x2F +#define SCSICMD_WriteBuffer 0x3B +#define SCSICMD_ReadBuffer 0x3C +#define SCSICMD_ReadLong 0x3E +#define SCSICMD_WriteLong 0x3F +#define SCSICMD_ReadTOC 0x43 +#define SCSICMD_ReadHeader 0x44 +#define SCSICMD_ModeSelect10 0x55 +#define SCSICMD_ModeSense10 0x5A +#define SCSI_TYPE_DASD 0x00 +#define SCSI_TYPE_SASD 0x01 +#define SCSI_TYPE_PRN 0x02 +#define SCSI_TYPE_PROC 0x03 +#define SCSI_TYPE_WORM 0x04 +#define SCSI_TYPE_CDROM 0x05 +#define SCSI_TYPE_SCANNER 0x06 +#define SCSI_TYPE_OPTMEM 0x07 +#define SCSI_TYPE_MED_CHG 0x08 +#define SCSI_TYPE_COMM 0x09 +#define SCSI_TYPE_UNKNOWN 0x1F +#define SCSI_TYPE_NO_DVC 0xFF +#define ASC_SCSIDIR_NOCHK 0x00 +#define ASC_SCSIDIR_T2H 0x08 +#define ASC_SCSIDIR_H2T 0x10 +#define ASC_SCSIDIR_NODATA 0x18 +#define SCSI_SENKEY_NO_SENSE 0x00 +#define SCSI_SENKEY_UNDEFINED 0x01 +#define SCSI_SENKEY_NOT_READY 0x02 +#define SCSI_SENKEY_MEDIUM_ERR 0x03 +#define SCSI_SENKEY_HW_ERR 0x04 +#define SCSI_SENKEY_ILLEGAL 0x05 +#define SCSI_SENKEY_ATTENTION 0x06 +#define SCSI_SENKEY_PROTECTED 0x07 +#define SCSI_SENKEY_BLANK 0x08 +#define SCSI_SENKEY_V_UNIQUE 0x09 +#define SCSI_SENKEY_CPY_ABORT 0x0A +#define SCSI_SENKEY_ABORT 0x0B +#define SCSI_SENKEY_EQUAL 0x0C +#define SCSI_SENKEY_VOL_OVERFLOW 0x0D +#define SCSI_SENKEY_MISCOMP 0x0E +#define SCSI_SENKEY_RESERVED 0x0F +#define SCSI_ASC_NOMEDIA 0x3A +#define ASC_SRB_HOST(x) ((uchar)((uchar)(x) >> 4)) +#define ASC_SRB_TID(x) ((uchar)((uchar)(x) & (uchar)0x0F)) +#define ASC_SRB_LUN(x) ((uchar)((uint)(x) >> 13)) +#define PUT_CDB1(x) ((uchar)((uint)(x) >> 8)) +#define SS_GOOD 0x00 +#define SS_CHK_CONDITION 0x02 +#define SS_CONDITION_MET 0x04 +#define SS_TARGET_BUSY 0x08 +#define SS_INTERMID 0x10 +#define SS_INTERMID_COND_MET 0x14 +#define SS_RSERV_CONFLICT 0x18 +#define SS_CMD_TERMINATED 0x22 +#define SS_QUEUE_FULL 0x28 +#define MS_CMD_DONE 0x00 +#define MS_EXTEND 0x01 +#define MS_SDTR_LEN 0x03 +#define MS_SDTR_CODE 0x01 +#define MS_WDTR_LEN 0x02 +#define MS_WDTR_CODE 0x03 +#define MS_MDP_LEN 0x05 +#define MS_MDP_CODE 0x00 +#define M1_SAVE_DATA_PTR 0x02 +#define M1_RESTORE_PTRS 0x03 +#define M1_DISCONNECT 0x04 +#define M1_INIT_DETECTED_ERR 0x05 +#define M1_ABORT 0x06 +#define M1_MSG_REJECT 0x07 +#define M1_NO_OP 0x08 +#define M1_MSG_PARITY_ERR 0x09 +#define M1_LINK_CMD_DONE 0x0A +#define M1_LINK_CMD_DONE_WFLAG 0x0B +#define M1_BUS_DVC_RESET 0x0C +#define M1_ABORT_TAG 0x0D +#define M1_CLR_QUEUE 0x0E +#define M1_INIT_RECOVERY 0x0F +#define M1_RELEASE_RECOVERY 0x10 +#define M1_KILL_IO_PROC 0x11 +#define M2_QTAG_MSG_SIMPLE 0x20 +#define M2_QTAG_MSG_HEAD 0x21 +#define M2_QTAG_MSG_ORDERED 0x22 +#define M2_IGNORE_WIDE_RESIDUE 0x23 + +typedef struct { + uchar peri_dvc_type:5; + uchar peri_qualifier:3; +} ASC_SCSI_INQ0; + +typedef struct { + uchar dvc_type_modifier:7; + uchar rmb:1; +} ASC_SCSI_INQ1; + +typedef struct { + uchar ansi_apr_ver:3; + uchar ecma_ver:3; + uchar iso_ver:2; +} ASC_SCSI_INQ2; + +typedef struct { + uchar rsp_data_fmt:4; + uchar res:2; + uchar TemIOP:1; + uchar aenc:1; +} ASC_SCSI_INQ3; + +typedef struct { + uchar StfRe:1; + uchar CmdQue:1; + uchar Reserved:1; + uchar Linked:1; + uchar Sync:1; + uchar WBus16:1; + uchar WBus32:1; + uchar RelAdr:1; +} ASC_SCSI_INQ7; + +typedef struct { + ASC_SCSI_INQ0 byte0; + ASC_SCSI_INQ1 byte1; + ASC_SCSI_INQ2 byte2; + ASC_SCSI_INQ3 byte3; + uchar add_len; + uchar res1; + uchar res2; + ASC_SCSI_INQ7 byte7; + uchar vendor_id[8]; + uchar product_id[16]; + uchar product_rev_level[4]; +} ASC_SCSI_INQUIRY; + +typedef struct asc_req_sense { + uchar err_code:7; + uchar info_valid:1; + uchar segment_no; + uchar sense_key:4; + uchar reserved_bit:1; + uchar sense_ILI:1; + uchar sense_EOM:1; + uchar file_mark:1; + uchar info1[4]; + uchar add_sense_len; + uchar cmd_sp_info[4]; + uchar asc; + uchar ascq; + uchar fruc; + uchar sks_byte0:7; + uchar sks_valid:1; + uchar sks_bytes[2]; + uchar notused[2]; + uchar ex_sense_code; + uchar info2[4]; +} ASC_REQ_SENSE; + +#define ASC_SG_LIST_PER_Q 7 +#define QS_FREE 0x00 +#define QS_READY 0x01 +#define QS_DISC1 0x02 +#define QS_DISC2 0x04 +#define QS_BUSY 0x08 +#define QS_ABORTED 0x40 +#define QS_DONE 0x80 +#define QC_NO_CALLBACK 0x01 +#define QC_SG_SWAP_QUEUE 0x02 +#define QC_SG_HEAD 0x04 +#define QC_DATA_IN 0x08 +#define QC_DATA_OUT 0x10 +#define QC_URGENT 0x20 +#define QC_MSG_OUT 0x40 +#define QC_REQ_SENSE 0x80 +#define QCSG_SG_XFER_LIST 0x02 +#define QCSG_SG_XFER_MORE 0x04 +#define QCSG_SG_XFER_END 0x08 +#define QD_IN_PROGRESS 0x00 +#define QD_NO_ERROR 0x01 +#define QD_ABORTED_BY_HOST 0x02 +#define QD_WITH_ERROR 0x04 +#define QD_INVALID_REQUEST 0x80 +#define QD_INVALID_HOST_NUM 0x81 +#define QD_INVALID_DEVICE 0x82 +#define QD_ERR_INTERNAL 0xFF +#define QHSTA_NO_ERROR 0x00 +#define QHSTA_M_SEL_TIMEOUT 0x11 +#define QHSTA_M_DATA_OVER_RUN 0x12 +#define QHSTA_M_DATA_UNDER_RUN 0x12 +#define QHSTA_M_UNEXPECTED_BUS_FREE 0x13 +#define QHSTA_M_BAD_BUS_PHASE_SEQ 0x14 +#define QHSTA_D_QDONE_SG_LIST_CORRUPTED 0x21 +#define QHSTA_D_ASC_DVC_ERROR_CODE_SET 0x22 +#define QHSTA_D_HOST_ABORT_FAILED 0x23 +#define QHSTA_D_EXE_SCSI_Q_FAILED 0x24 +#define QHSTA_D_EXE_SCSI_Q_BUSY_TIMEOUT 0x25 +#define QHSTA_D_ASPI_NO_BUF_POOL 0x26 +#define QHSTA_M_WTM_TIMEOUT 0x41 +#define QHSTA_M_BAD_CMPL_STATUS_IN 0x42 +#define QHSTA_M_NO_AUTO_REQ_SENSE 0x43 +#define QHSTA_M_AUTO_REQ_SENSE_FAIL 0x44 +#define QHSTA_M_TARGET_STATUS_BUSY 0x45 +#define QHSTA_M_BAD_TAG_CODE 0x46 +#define QHSTA_M_BAD_QUEUE_FULL_OR_BUSY 0x47 +#define QHSTA_M_HUNG_REQ_SCSI_BUS_RESET 0x48 +#define QHSTA_D_LRAM_CMP_ERROR 0x81 +#define QHSTA_M_MICRO_CODE_ERROR_HALT 0xA1 +#define ASC_FLAG_SCSIQ_REQ 0x01 +#define ASC_FLAG_BIOS_SCSIQ_REQ 0x02 +#define ASC_FLAG_BIOS_ASYNC_IO 0x04 +#define ASC_FLAG_SRB_LINEAR_ADDR 0x08 +#define ASC_FLAG_WIN16 0x10 +#define ASC_FLAG_WIN32 0x20 +#define ASC_FLAG_ISA_OVER_16MB 0x40 +#define ASC_FLAG_DOS_VM_CALLBACK 0x80 +#define ASC_TAG_FLAG_EXTRA_BYTES 0x10 +#define ASC_TAG_FLAG_DISABLE_DISCONNECT 0x04 +#define ASC_TAG_FLAG_DISABLE_ASYN_USE_SYN_FIX 0x08 +#define ASC_TAG_FLAG_DISABLE_CHK_COND_INT_HOST 0x40 +#define ASC_SCSIQ_CPY_BEG 4 +#define ASC_SCSIQ_SGHD_CPY_BEG 2 +#define ASC_SCSIQ_B_FWD 0 +#define ASC_SCSIQ_B_BWD 1 +#define ASC_SCSIQ_B_STATUS 2 +#define ASC_SCSIQ_B_QNO 3 +#define ASC_SCSIQ_B_CNTL 4 +#define ASC_SCSIQ_B_SG_QUEUE_CNT 5 +#define ASC_SCSIQ_D_DATA_ADDR 8 +#define ASC_SCSIQ_D_DATA_CNT 12 +#define ASC_SCSIQ_B_SENSE_LEN 20 +#define ASC_SCSIQ_DONE_INFO_BEG 22 +#define ASC_SCSIQ_D_SRBPTR 22 +#define ASC_SCSIQ_B_TARGET_IX 26 +#define ASC_SCSIQ_B_CDB_LEN 28 +#define ASC_SCSIQ_B_TAG_CODE 29 +#define ASC_SCSIQ_W_VM_ID 30 +#define ASC_SCSIQ_DONE_STATUS 32 +#define ASC_SCSIQ_HOST_STATUS 33 +#define ASC_SCSIQ_SCSI_STATUS 34 +#define ASC_SCSIQ_CDB_BEG 36 +#define ASC_SCSIQ_DW_REMAIN_XFER_ADDR 56 +#define ASC_SCSIQ_DW_REMAIN_XFER_CNT 60 +#define ASC_SCSIQ_B_SG_WK_QP 49 +#define ASC_SCSIQ_B_SG_WK_IX 50 +#define ASC_SCSIQ_W_REQ_COUNT 52 +#define ASC_SCSIQ_B_LIST_CNT 6 +#define ASC_SCSIQ_B_CUR_LIST_CNT 7 +#define ASC_SGQ_B_SG_CNTL 4 +#define ASC_SGQ_B_SG_HEAD_QP 5 +#define ASC_SGQ_B_SG_LIST_CNT 6 +#define ASC_SGQ_B_SG_CUR_LIST_CNT 7 +#define ASC_SGQ_LIST_BEG 8 +#define ASC_DEF_SCSI1_QNG 4 +#define ASC_MAX_SCSI1_QNG 4 +#define ASC_DEF_SCSI2_QNG 16 +#define ASC_MAX_SCSI2_QNG 32 +#define ASC_TAG_CODE_MASK 0x23 +#define ASC_STOP_REQ_RISC_STOP 0x01 +#define ASC_STOP_ACK_RISC_STOP 0x03 +#define ASC_STOP_CLEAN_UP_BUSY_Q 0x10 +#define ASC_STOP_CLEAN_UP_DISC_Q 0x20 +#define ASC_STOP_HOST_REQ_RISC_HALT 0x40 +#define ASC_TIDLUN_TO_IX(tid, lun) (ASC_SCSI_TIX_TYPE)((tid) + ((lun)<<ASC_SCSI_ID_BITS)) +#define ASC_TID_TO_TARGET_ID(tid) (ASC_SCSI_BIT_ID_TYPE)(0x01 << (tid)) +#define ASC_TIX_TO_TARGET_ID(tix) (0x01 << ((tix) & ASC_MAX_TID)) +#define ASC_TIX_TO_TID(tix) ((tix) & ASC_MAX_TID) +#define ASC_TID_TO_TIX(tid) ((tid) & ASC_MAX_TID) +#define ASC_TIX_TO_LUN(tix) (((tix) >> ASC_SCSI_ID_BITS) & ASC_MAX_LUN) +#define ASC_QNO_TO_QADDR(q_no) ((ASC_QADR_BEG)+((int)(q_no) << 6)) + +typedef struct asc_scisq_1 { + uchar status; + uchar q_no; + uchar cntl; + uchar sg_queue_cnt; + uchar target_id; + uchar target_lun; + ulong data_addr; + ulong data_cnt; + ulong sense_addr; + uchar sense_len; + uchar extra_bytes; +} ASC_SCSIQ_1; + +typedef struct asc_scisq_2 { + ulong srb_ptr; + uchar target_ix; + uchar flag; + uchar cdb_len; + uchar tag_code; + ushort vm_id; +} ASC_SCSIQ_2; + +typedef struct asc_scsiq_3 { + uchar done_stat; + uchar host_stat; + uchar scsi_stat; + uchar scsi_msg; +} ASC_SCSIQ_3; + +typedef struct asc_scsiq_4 { + uchar cdb[ASC_MAX_CDB_LEN]; + uchar y_first_sg_list_qp; + uchar y_working_sg_qp; + uchar y_working_sg_ix; + uchar y_res; + ushort x_req_count; + ushort x_reconnect_rtn; + ulong x_saved_data_addr; + ulong x_saved_data_cnt; +} ASC_SCSIQ_4; + +typedef struct asc_q_done_info { + ASC_SCSIQ_2 d2; + ASC_SCSIQ_3 d3; + uchar q_status; + uchar q_no; + uchar cntl; + uchar sense_len; + uchar extra_bytes; + uchar res; + ulong remain_bytes; +} ASC_QDONE_INFO; + +typedef struct asc_sg_list { + ulong addr; + ulong bytes; +} ASC_SG_LIST; + +typedef struct asc_sg_head { + ushort entry_cnt; + ushort queue_cnt; + ushort entry_to_copy; + ushort res; + ASC_SG_LIST sg_list[ASC_MAX_SG_LIST]; +} ASC_SG_HEAD; + +#define ASC_MIN_SG_LIST 2 + +typedef struct asc_min_sg_head { + ushort entry_cnt; + ushort queue_cnt; + ushort entry_to_copy; + ushort res; + ASC_SG_LIST sg_list[ASC_MIN_SG_LIST]; +} ASC_MIN_SG_HEAD; + +#define QCX_SORT (0x0001) +#define QCX_COALEASE (0x0002) + +typedef struct asc_scsi_q { + ASC_SCSIQ_1 q1; + ASC_SCSIQ_2 q2; + uchar *cdbptr; + ASC_SG_HEAD *sg_head; +} ASC_SCSI_Q; + +typedef struct asc_scsi_req_q { + ASC_SCSIQ_1 r1; + ASC_SCSIQ_2 r2; + uchar *cdbptr; + ASC_SG_HEAD *sg_head; + uchar *sense_ptr; + ASC_SCSIQ_3 r3; + uchar cdb[ASC_MAX_CDB_LEN]; + uchar sense[ASC_MIN_SENSE_LEN]; +} ASC_SCSI_REQ_Q; + +typedef struct asc_scsi_bios_req_q { + ASC_SCSIQ_1 r1; + ASC_SCSIQ_2 r2; + uchar *cdbptr; + ASC_SG_HEAD *sg_head; + uchar *sense_ptr; + ASC_SCSIQ_3 r3; + uchar cdb[ASC_MAX_CDB_LEN]; + uchar sense[ASC_MIN_SENSE_LEN]; +} ASC_SCSI_BIOS_REQ_Q; + +typedef struct asc_risc_q { + uchar fwd; + uchar bwd; + ASC_SCSIQ_1 i1; + ASC_SCSIQ_2 i2; + ASC_SCSIQ_3 i3; + ASC_SCSIQ_4 i4; +} ASC_RISC_Q; + +typedef struct asc_sg_list_q { + uchar seq_no; + uchar q_no; + uchar cntl; + uchar sg_head_qp; + uchar sg_list_cnt; + uchar sg_cur_list_cnt; +} ASC_SG_LIST_Q; + +typedef struct asc_risc_sg_list_q { + uchar fwd; + uchar bwd; + ASC_SG_LIST_Q sg; + ASC_SG_LIST sg_list[7]; +} ASC_RISC_SG_LIST_Q; + +#define ASC_EXE_SCSI_IO_MAX_IDLE_LOOP 0x1000000UL +#define ASC_EXE_SCSI_IO_MAX_WAIT_LOOP 1024 +#define ASCQ_ERR_NO_ERROR 0 +#define ASCQ_ERR_IO_NOT_FOUND 1 +#define ASCQ_ERR_LOCAL_MEM 2 +#define ASCQ_ERR_CHKSUM 3 +#define ASCQ_ERR_START_CHIP 4 +#define ASCQ_ERR_INT_TARGET_ID 5 +#define ASCQ_ERR_INT_LOCAL_MEM 6 +#define ASCQ_ERR_HALT_RISC 7 +#define ASCQ_ERR_GET_ASPI_ENTRY 8 +#define ASCQ_ERR_CLOSE_ASPI 9 +#define ASCQ_ERR_HOST_INQUIRY 0x0A +#define ASCQ_ERR_SAVED_SRB_BAD 0x0B +#define ASCQ_ERR_QCNTL_SG_LIST 0x0C +#define ASCQ_ERR_Q_STATUS 0x0D +#define ASCQ_ERR_WR_SCSIQ 0x0E +#define ASCQ_ERR_PC_ADDR 0x0F +#define ASCQ_ERR_SYN_OFFSET 0x10 +#define ASCQ_ERR_SYN_XFER_TIME 0x11 +#define ASCQ_ERR_LOCK_DMA 0x12 +#define ASCQ_ERR_UNLOCK_DMA 0x13 +#define ASCQ_ERR_VDS_CHK_INSTALL 0x14 +#define ASCQ_ERR_MICRO_CODE_HALT 0x15 +#define ASCQ_ERR_SET_LRAM_ADDR 0x16 +#define ASCQ_ERR_CUR_QNG 0x17 +#define ASCQ_ERR_SG_Q_LINKS 0x18 +#define ASCQ_ERR_SCSIQ_PTR 0x19 +#define ASCQ_ERR_ISR_RE_ENTRY 0x1A +#define ASCQ_ERR_CRITICAL_RE_ENTRY 0x1B +#define ASCQ_ERR_ISR_ON_CRITICAL 0x1C +#define ASCQ_ERR_SG_LIST_ODD_ADDRESS 0x1D +#define ASCQ_ERR_XFER_ADDRESS_TOO_BIG 0x1E +#define ASCQ_ERR_SCSIQ_NULL_PTR 0x1F +#define ASCQ_ERR_SCSIQ_BAD_NEXT_PTR 0x20 +#define ASCQ_ERR_GET_NUM_OF_FREE_Q 0x21 +#define ASCQ_ERR_SEND_SCSI_Q 0x22 +#define ASCQ_ERR_HOST_REQ_RISC_HALT 0x23 +#define ASCQ_ERR_RESET_SDTR 0x24 +#define ASC_WARN_NO_ERROR 0x0000 +#define ASC_WARN_IO_PORT_ROTATE 0x0001 +#define ASC_WARN_EEPROM_CHKSUM 0x0002 +#define ASC_WARN_IRQ_MODIFIED 0x0004 +#define ASC_WARN_AUTO_CONFIG 0x0008 +#define ASC_WARN_CMD_QNG_CONFLICT 0x0010 +#define ASC_WARN_EEPROM_RECOVER 0x0020 +#define ASC_WARN_CFG_MSW_RECOVER 0x0040 +#define ASC_WARN_SET_PCI_CONFIG_SPACE 0x0080 +#define ASC_IERR_WRITE_EEPROM 0x0001 +#define ASC_IERR_MCODE_CHKSUM 0x0002 +#define ASC_IERR_SET_PC_ADDR 0x0004 +#define ASC_IERR_START_STOP_CHIP 0x0008 +#define ASC_IERR_IRQ_NO 0x0010 +#define ASC_IERR_SET_IRQ_NO 0x0020 +#define ASC_IERR_CHIP_VERSION 0x0040 +#define ASC_IERR_SET_SCSI_ID 0x0080 +#define ASC_IERR_GET_PHY_ADDR 0x0100 +#define ASC_IERR_BAD_SIGNATURE 0x0200 +#define ASC_IERR_NO_BUS_TYPE 0x0400 +#define ASC_IERR_SCAM 0x0800 +#define ASC_IERR_SET_SDTR 0x1000 +#define ASC_IERR_RW_LRAM 0x8000 +#define ASC_DEF_IRQ_NO 10 +#define ASC_MAX_IRQ_NO 15 +#define ASC_MIN_IRQ_NO 10 +#define ASC_MIN_REMAIN_Q (0x02) +#define ASC_DEF_MAX_TOTAL_QNG (0xF0) +#define ASC_MIN_TAG_Q_PER_DVC (0x04) +#define ASC_DEF_TAG_Q_PER_DVC (0x04) +#define ASC_MIN_FREE_Q ASC_MIN_REMAIN_Q +#define ASC_MIN_TOTAL_QNG ((ASC_MAX_SG_QUEUE)+(ASC_MIN_FREE_Q)) +#define ASC_MAX_TOTAL_QNG 240 +#define ASC_MAX_PCI_ULTRA_INRAM_TOTAL_QNG 16 +#define ASC_MAX_PCI_ULTRA_INRAM_TAG_QNG 8 +#define ASC_MAX_PCI_INRAM_TOTAL_QNG 20 +#define ASC_MAX_INRAM_TAG_QNG 16 +#define ASC_IOADR_TABLE_MAX_IX 11 +#define ASC_IOADR_GAP 0x10 +#define ASC_SEARCH_IOP_GAP 0x10 +#define ASC_MIN_IOP_ADDR (PortAddr)0x0100 +#define ASC_MAX_IOP_ADDR (PortAddr)0x3F0 +#define ASC_IOADR_1 (PortAddr)0x0110 +#define ASC_IOADR_2 (PortAddr)0x0130 +#define ASC_IOADR_3 (PortAddr)0x0150 +#define ASC_IOADR_4 (PortAddr)0x0190 +#define ASC_IOADR_5 (PortAddr)0x0210 +#define ASC_IOADR_6 (PortAddr)0x0230 +#define ASC_IOADR_7 (PortAddr)0x0250 +#define ASC_IOADR_8 (PortAddr)0x0330 +#define ASC_IOADR_DEF ASC_IOADR_8 +#define ASC_LIB_SCSIQ_WK_SP 256 +#define ASC_MAX_SYN_XFER_NO 16 +#define ASC_SYN_MAX_OFFSET 0x0F +#define ASC_DEF_SDTR_OFFSET 0x0F +#define ASC_DEF_SDTR_INDEX 0x00 +#define ASC_SDTR_ULTRA_PCI_10MB_INDEX 0x02 +#define SYN_XFER_NS_0 25 +#define SYN_XFER_NS_1 30 +#define SYN_XFER_NS_2 35 +#define SYN_XFER_NS_3 40 +#define SYN_XFER_NS_4 50 +#define SYN_XFER_NS_5 60 +#define SYN_XFER_NS_6 70 +#define SYN_XFER_NS_7 85 +#define SYN_ULTRA_XFER_NS_0 12 +#define SYN_ULTRA_XFER_NS_1 19 +#define SYN_ULTRA_XFER_NS_2 25 +#define SYN_ULTRA_XFER_NS_3 32 +#define SYN_ULTRA_XFER_NS_4 38 +#define SYN_ULTRA_XFER_NS_5 44 +#define SYN_ULTRA_XFER_NS_6 50 +#define SYN_ULTRA_XFER_NS_7 57 +#define SYN_ULTRA_XFER_NS_8 63 +#define SYN_ULTRA_XFER_NS_9 69 +#define SYN_ULTRA_XFER_NS_10 75 +#define SYN_ULTRA_XFER_NS_11 82 +#define SYN_ULTRA_XFER_NS_12 88 +#define SYN_ULTRA_XFER_NS_13 94 +#define SYN_ULTRA_XFER_NS_14 100 +#define SYN_ULTRA_XFER_NS_15 107 + +typedef struct ext_msg { + uchar msg_type; + uchar msg_len; + uchar msg_req; + union { + struct { + uchar sdtr_xfer_period; + uchar sdtr_req_ack_offset; + } sdtr; + struct { + uchar wdtr_width; + } wdtr; + struct { + uchar mdp_b3; + uchar mdp_b2; + uchar mdp_b1; + uchar mdp_b0; + } mdp; + } u_ext_msg; + uchar res; +} EXT_MSG; + +#define xfer_period u_ext_msg.sdtr.sdtr_xfer_period +#define req_ack_offset u_ext_msg.sdtr.sdtr_req_ack_offset +#define wdtr_width u_ext_msg.wdtr.wdtr_width +#define mdp_b3 u_ext_msg.mdp_b3 +#define mdp_b2 u_ext_msg.mdp_b2 +#define mdp_b1 u_ext_msg.mdp_b1 +#define mdp_b0 u_ext_msg.mdp_b0 + +typedef struct asc_dvc_cfg { + ASC_SCSI_BIT_ID_TYPE can_tagged_qng; + ASC_SCSI_BIT_ID_TYPE cmd_qng_enabled; + ASC_SCSI_BIT_ID_TYPE disc_enable; + ASC_SCSI_BIT_ID_TYPE sdtr_enable; + uchar chip_scsi_id:4; + uchar isa_dma_speed:4; + uchar isa_dma_channel; + uchar chip_version; + ushort pci_device_id; + ushort lib_serial_no; + ushort lib_version; + ushort mcode_date; + ushort mcode_version; + uchar max_tag_qng[ASC_MAX_TID + 1]; + uchar *overrun_buf; + uchar sdtr_period_offset[ASC_MAX_TID + 1]; + ushort pci_slot_info; + uchar adapter_info[6]; +} ASC_DVC_CFG; + +#define ASC_DEF_DVC_CNTL 0xFFFF +#define ASC_DEF_CHIP_SCSI_ID 7 +#define ASC_DEF_ISA_DMA_SPEED 4 +#define ASC_INIT_STATE_NULL 0x0000 +#define ASC_INIT_STATE_BEG_GET_CFG 0x0001 +#define ASC_INIT_STATE_END_GET_CFG 0x0002 +#define ASC_INIT_STATE_BEG_SET_CFG 0x0004 +#define ASC_INIT_STATE_END_SET_CFG 0x0008 +#define ASC_INIT_STATE_BEG_LOAD_MC 0x0010 +#define ASC_INIT_STATE_END_LOAD_MC 0x0020 +#define ASC_INIT_STATE_BEG_INQUIRY 0x0040 +#define ASC_INIT_STATE_END_INQUIRY 0x0080 +#define ASC_INIT_RESET_SCSI_DONE 0x0100 +#define ASC_INIT_STATE_WITHOUT_EEP 0x8000 +#define ASC_PCI_DEVICE_ID_REV_A 0x1100 +#define ASC_PCI_DEVICE_ID_REV_B 0x1200 +#define ASC_BUG_FIX_IF_NOT_DWB 0x0001 +#define ASC_BUG_FIX_ASYN_USE_SYN 0x0002 +#define ASYN_SDTR_DATA_FIX_PCI_REV_AB 0x41 +#define ASC_MIN_TAGGED_CMD 7 +#define ASC_MAX_SCSI_RESET_WAIT 30 + +typedef struct asc_dvc_var { + PortAddr iop_base; + ushort err_code; + ushort dvc_cntl; + ushort bug_fix_cntl; + ushort bus_type; + Ptr2Func isr_callback; + Ptr2Func exe_callback; + ASC_SCSI_BIT_ID_TYPE init_sdtr; + ASC_SCSI_BIT_ID_TYPE sdtr_done; + ASC_SCSI_BIT_ID_TYPE use_tagged_qng; + ASC_SCSI_BIT_ID_TYPE unit_not_ready; + ASC_SCSI_BIT_ID_TYPE queue_full_or_busy; + ASC_SCSI_BIT_ID_TYPE start_motor; + uchar scsi_reset_wait; + uchar chip_no; + char is_in_int; + uchar max_total_qng; + uchar cur_total_qng; + uchar in_critical_cnt; + uchar irq_no; + uchar last_q_shortage; + ushort init_state; + uchar cur_dvc_qng[ASC_MAX_TID + 1]; + uchar max_dvc_qng[ASC_MAX_TID + 1]; + ASC_SCSI_Q *scsiq_busy_head[ASC_MAX_TID + 1]; + ASC_SCSI_Q *scsiq_busy_tail[ASC_MAX_TID + 1]; + uchar sdtr_period_tbl[ASC_MAX_SYN_XFER_NO]; + ASC_DVC_CFG *cfg; + Ptr2Func saved_ptr2func; + ASC_SCSI_BIT_ID_TYPE pci_fix_asyn_xfer_always; + char redo_scam; + ushort res2; + uchar dos_int13_table[ASC_MAX_TID + 1]; + ulong max_dma_count; + ASC_SCSI_BIT_ID_TYPE no_scam; + ASC_SCSI_BIT_ID_TYPE pci_fix_asyn_xfer; + uchar max_sdtr_index; + uchar host_init_sdtr_index; + ulong drv_ptr; + ulong uc_break; + ulong res7; + ulong res8; +} ASC_DVC_VAR; + +typedef int (* ASC_ISR_CALLBACK) (ASC_DVC_VAR asc_ptr_type *, ASC_QDONE_INFO *); +typedef int (* ASC_EXE_CALLBACK) (ASC_DVC_VAR asc_ptr_type *, ASC_SCSI_Q *); + +typedef struct asc_dvc_inq_info { + uchar type[ASC_MAX_TID + 1][ASC_MAX_LUN + 1]; +} ASC_DVC_INQ_INFO; + +typedef struct asc_cap_info { + ulong lba; + ulong blk_size; +} ASC_CAP_INFO; + +typedef struct asc_cap_info_array { + ASC_CAP_INFO cap_info[ASC_MAX_TID + 1][ASC_MAX_LUN + 1]; +} ASC_CAP_INFO_ARRAY; + +#define ASC_MCNTL_NO_SEL_TIMEOUT (ushort)0x0001 +#define ASC_MCNTL_NULL_TARGET (ushort)0x0002 +#define ASC_CNTL_INITIATOR (ushort)0x0001 +#define ASC_CNTL_BIOS_GT_1GB (ushort)0x0002 +#define ASC_CNTL_BIOS_GT_2_DISK (ushort)0x0004 +#define ASC_CNTL_BIOS_REMOVABLE (ushort)0x0008 +#define ASC_CNTL_NO_SCAM (ushort)0x0010 +#define ASC_CNTL_INT_MULTI_Q (ushort)0x0080 +#define ASC_CNTL_NO_LUN_SUPPORT (ushort)0x0040 +#define ASC_CNTL_NO_VERIFY_COPY (ushort)0x0100 +#define ASC_CNTL_RESET_SCSI (ushort)0x0200 +#define ASC_CNTL_INIT_INQUIRY (ushort)0x0400 +#define ASC_CNTL_INIT_VERBOSE (ushort)0x0800 +#define ASC_CNTL_SCSI_PARITY (ushort)0x1000 +#define ASC_CNTL_BURST_MODE (ushort)0x2000 +#define ASC_CNTL_SDTR_ENABLE_ULTRA (ushort)0x4000 +#define ASC_EEP_DVC_CFG_BEG_VL 2 +#define ASC_EEP_MAX_DVC_ADDR_VL 15 +#define ASC_EEP_DVC_CFG_BEG 32 +#define ASC_EEP_MAX_DVC_ADDR 45 +#define ASC_EEP_DEFINED_WORDS 10 +#define ASC_EEP_MAX_ADDR 63 +#define ASC_EEP_RES_WORDS 0 +#define ASC_EEP_MAX_RETRY 20 +#define ASC_MAX_INIT_BUSY_RETRY 8 +#define ASC_EEP_ISA_PNP_WSIZE 16 + +typedef struct asceep_config { + ushort cfg_lsw; + ushort cfg_msw; + uchar init_sdtr; + uchar disc_enable; + uchar use_cmd_qng; + uchar start_motor; + uchar max_total_qng; + uchar max_tag_qng; + uchar bios_scan; + uchar power_up_wait; + uchar no_scam; + uchar chip_scsi_id:4; + uchar isa_dma_speed:4; + uchar dos_int13_table[ASC_MAX_TID + 1]; + uchar adapter_info[6]; + ushort cntl; + ushort chksum; +} ASCEEP_CONFIG; + +#define ASC_PCI_CFG_LSW_SCSI_PARITY 0x0800 +#define ASC_PCI_CFG_LSW_BURST_MODE 0x0080 +#define ASC_PCI_CFG_LSW_INTR_ABLE 0x0020 + +#define ASC_EEP_CMD_READ 0x80 +#define ASC_EEP_CMD_WRITE 0x40 +#define ASC_EEP_CMD_WRITE_ABLE 0x30 +#define ASC_EEP_CMD_WRITE_DISABLE 0x00 +#define ASC_OVERRUN_BSIZE 0x00000048UL +#define ASC_CTRL_BREAK_ONCE 0x0001 +#define ASC_CTRL_BREAK_STAY_IDLE 0x0002 +#define ASCV_MSGOUT_BEG 0x0000 +#define ASCV_MSGOUT_SDTR_PERIOD (ASCV_MSGOUT_BEG+3) +#define ASCV_MSGOUT_SDTR_OFFSET (ASCV_MSGOUT_BEG+4) +#define ASCV_BREAK_SAVED_CODE (ushort)0x0006 +#define ASCV_MSGIN_BEG (ASCV_MSGOUT_BEG+8) +#define ASCV_MSGIN_SDTR_PERIOD (ASCV_MSGIN_BEG+3) +#define ASCV_MSGIN_SDTR_OFFSET (ASCV_MSGIN_BEG+4) +#define ASCV_SDTR_DATA_BEG (ASCV_MSGIN_BEG+8) +#define ASCV_SDTR_DONE_BEG (ASCV_SDTR_DATA_BEG+8) +#define ASCV_MAX_DVC_QNG_BEG (ushort)0x0020 +#define ASCV_BREAK_ADDR (ushort)0x0028 +#define ASCV_BREAK_NOTIFY_COUNT (ushort)0x002A +#define ASCV_BREAK_CONTROL (ushort)0x002C +#define ASCV_BREAK_HIT_COUNT (ushort)0x002E + +#define ASCV_ASCDVC_ERR_CODE_W (ushort)0x0030 +#define ASCV_MCODE_CHKSUM_W (ushort)0x0032 +#define ASCV_MCODE_SIZE_W (ushort)0x0034 +#define ASCV_STOP_CODE_B (ushort)0x0036 +#define ASCV_DVC_ERR_CODE_B (ushort)0x0037 +#define ASCV_OVERRUN_PADDR_D (ushort)0x0038 +#define ASCV_OVERRUN_BSIZE_D (ushort)0x003C +#define ASCV_HALTCODE_W (ushort)0x0040 +#define ASCV_CHKSUM_W (ushort)0x0042 +#define ASCV_MC_DATE_W (ushort)0x0044 +#define ASCV_MC_VER_W (ushort)0x0046 +#define ASCV_NEXTRDY_B (ushort)0x0048 +#define ASCV_DONENEXT_B (ushort)0x0049 +#define ASCV_USE_TAGGED_QNG_B (ushort)0x004A +#define ASCV_SCSIBUSY_B (ushort)0x004B +#define ASCV_Q_DONE_IN_PROGRESS_B (ushort)0x004C +#define ASCV_CURCDB_B (ushort)0x004D +#define ASCV_RCLUN_B (ushort)0x004E +#define ASCV_BUSY_QHEAD_B (ushort)0x004F +#define ASCV_DISC1_QHEAD_B (ushort)0x0050 +#define ASCV_DISC_ENABLE_B (ushort)0x0052 +#define ASCV_CAN_TAGGED_QNG_B (ushort)0x0053 +#define ASCV_HOSTSCSI_ID_B (ushort)0x0055 +#define ASCV_MCODE_CNTL_B (ushort)0x0056 +#define ASCV_NULL_TARGET_B (ushort)0x0057 +#define ASCV_FREE_Q_HEAD_W (ushort)0x0058 +#define ASCV_DONE_Q_TAIL_W (ushort)0x005A +#define ASCV_FREE_Q_HEAD_B (ushort)(ASCV_FREE_Q_HEAD_W+1) +#define ASCV_DONE_Q_TAIL_B (ushort)(ASCV_DONE_Q_TAIL_W+1) +#define ASCV_HOST_FLAG_B (ushort)0x005D +#define ASCV_TOTAL_READY_Q_B (ushort)0x0064 +#define ASCV_VER_SERIAL_B (ushort)0x0065 +#define ASCV_HALTCODE_SAVED_W (ushort)0x0066 +#define ASCV_WTM_FLAG_B (ushort)0x0068 +#define ASCV_RISC_FLAG_B (ushort)0x006A +#define ASCV_REQ_SG_LIST_QP (ushort)0x006B +#define ASC_HOST_FLAG_IN_ISR 0x01 +#define ASC_HOST_FLAG_ACK_INT 0x02 +#define ASC_RISC_FLAG_GEN_INT 0x01 +#define ASC_RISC_FLAG_REQ_SG_LIST 0x02 +#define IOP_CTRL (0x0F) +#define IOP_STATUS (0x0E) +#define IOP_INT_ACK IOP_STATUS +#define IOP_REG_IFC (0x0D) +#define IOP_SYN_OFFSET (0x0B) +#define IOP_EXTRA_CONTROL (0x0D) +#define IOP_REG_PC (0x0C) +#define IOP_RAM_ADDR (0x0A) +#define IOP_RAM_DATA (0x08) +#define IOP_EEP_DATA (0x06) +#define IOP_EEP_CMD (0x07) +#define IOP_VERSION (0x03) +#define IOP_CONFIG_HIGH (0x04) +#define IOP_CONFIG_LOW (0x02) +#define IOP_SIG_BYTE (0x01) +#define IOP_SIG_WORD (0x00) +#define IOP_REG_DC1 (0x0E) +#define IOP_REG_DC0 (0x0C) +#define IOP_REG_SB (0x0B) +#define IOP_REG_DA1 (0x0A) +#define IOP_REG_DA0 (0x08) +#define IOP_REG_SC (0x09) +#define IOP_DMA_SPEED (0x07) +#define IOP_REG_FLAG (0x07) +#define IOP_FIFO_H (0x06) +#define IOP_FIFO_L (0x04) +#define IOP_REG_ID (0x05) +#define IOP_REG_QP (0x03) +#define IOP_REG_IH (0x02) +#define IOP_REG_IX (0x01) +#define IOP_REG_AX (0x00) +#define IFC_REG_LOCK (0x00) +#define IFC_REG_UNLOCK (0x09) +#define IFC_WR_EN_FILTER (0x10) +#define IFC_RD_NO_EEPROM (0x10) +#define IFC_SLEW_RATE (0x20) +#define IFC_ACT_NEG (0x40) +#define IFC_INP_FILTER (0x80) +#define IFC_INIT_DEFAULT (IFC_ACT_NEG | IFC_REG_UNLOCK) +#define SC_SEL (uchar)(0x80) +#define SC_BSY (uchar)(0x40) +#define SC_ACK (uchar)(0x20) +#define SC_REQ (uchar)(0x10) +#define SC_ATN (uchar)(0x08) +#define SC_IO (uchar)(0x04) +#define SC_CD (uchar)(0x02) +#define SC_MSG (uchar)(0x01) +#define SEC_SCSI_CTL (uchar)(0x80) +#define SEC_ACTIVE_NEGATE (uchar)(0x40) +#define SEC_SLEW_RATE (uchar)(0x20) +#define SEC_ENABLE_FILTER (uchar)(0x10) +#define ASC_HALT_EXTMSG_IN (ushort)0x8000 +#define ASC_HALT_CHK_CONDITION (ushort)0x8100 +#define ASC_HALT_SS_QUEUE_FULL (ushort)0x8200 +#define ASC_HALT_DISABLE_ASYN_USE_SYN_FIX (ushort)0x8300 +#define ASC_HALT_ENABLE_ASYN_USE_SYN_FIX (ushort)0x8400 +#define ASC_HALT_SDTR_REJECTED (ushort)0x4000 +#define ASC_MAX_QNO 0xF8 +#define ASC_DATA_SEC_BEG (ushort)0x0080 +#define ASC_DATA_SEC_END (ushort)0x0080 +#define ASC_CODE_SEC_BEG (ushort)0x0080 +#define ASC_CODE_SEC_END (ushort)0x0080 +#define ASC_QADR_BEG (0x4000) +#define ASC_QADR_USED (ushort)(ASC_MAX_QNO * 64) +#define ASC_QADR_END (ushort)0x7FFF +#define ASC_QLAST_ADR (ushort)0x7FC0 +#define ASC_QBLK_SIZE 0x40 +#define ASC_BIOS_DATA_QBEG 0xF8 +#define ASC_MIN_ACTIVE_QNO 0x01 +#define ASC_QLINK_END 0xFF +#define ASC_EEPROM_WORDS 0x10 +#define ASC_MAX_MGS_LEN 0x10 +#define ASC_BIOS_ADDR_DEF 0xDC00 +#define ASC_BIOS_SIZE 0x3800 +#define ASC_BIOS_RAM_OFF 0x3800 +#define ASC_BIOS_RAM_SIZE 0x800 +#define ASC_BIOS_MIN_ADDR 0xC000 +#define ASC_BIOS_MAX_ADDR 0xEC00 +#define ASC_BIOS_BANK_SIZE 0x0400 +#define ASC_MCODE_START_ADDR 0x0080 +#define ASC_CFG0_HOST_INT_ON 0x0020 +#define ASC_CFG0_BIOS_ON 0x0040 +#define ASC_CFG0_VERA_BURST_ON 0x0080 +#define ASC_CFG0_SCSI_PARITY_ON 0x0800 +#define ASC_CFG1_SCSI_TARGET_ON 0x0080 +#define ASC_CFG1_LRAM_8BITS_ON 0x0800 +#define ASC_CFG_MSW_CLR_MASK 0x3080 +#define CSW_TEST1 (ASC_CS_TYPE)0x8000 +#define CSW_AUTO_CONFIG (ASC_CS_TYPE)0x4000 +#define CSW_RESERVED1 (ASC_CS_TYPE)0x2000 +#define CSW_IRQ_WRITTEN (ASC_CS_TYPE)0x1000 +#define CSW_33MHZ_SELECTED (ASC_CS_TYPE)0x0800 +#define CSW_TEST2 (ASC_CS_TYPE)0x0400 +#define CSW_TEST3 (ASC_CS_TYPE)0x0200 +#define CSW_RESERVED2 (ASC_CS_TYPE)0x0100 +#define CSW_DMA_DONE (ASC_CS_TYPE)0x0080 +#define CSW_FIFO_RDY (ASC_CS_TYPE)0x0040 +#define CSW_EEP_READ_DONE (ASC_CS_TYPE)0x0020 +#define CSW_HALTED (ASC_CS_TYPE)0x0010 +#define CSW_SCSI_RESET_ACTIVE (ASC_CS_TYPE)0x0008 +#define CSW_PARITY_ERR (ASC_CS_TYPE)0x0004 +#define CSW_SCSI_RESET_LATCH (ASC_CS_TYPE)0x0002 +#define CSW_INT_PENDING (ASC_CS_TYPE)0x0001 +#define CIW_CLR_SCSI_RESET_INT (ASC_CS_TYPE)0x1000 +#define CIW_INT_ACK (ASC_CS_TYPE)0x0100 +#define CIW_TEST1 (ASC_CS_TYPE)0x0200 +#define CIW_TEST2 (ASC_CS_TYPE)0x0400 +#define CIW_SEL_33MHZ (ASC_CS_TYPE)0x0800 +#define CIW_IRQ_ACT (ASC_CS_TYPE)0x1000 +#define CC_CHIP_RESET (uchar)0x80 +#define CC_SCSI_RESET (uchar)0x40 +#define CC_HALT (uchar)0x20 +#define CC_SINGLE_STEP (uchar)0x10 +#define CC_DMA_ABLE (uchar)0x08 +#define CC_TEST (uchar)0x04 +#define CC_BANK_ONE (uchar)0x02 +#define CC_DIAG (uchar)0x01 +#define ASC_1000_ID0W 0x04C1 +#define ASC_1000_ID0W_FIX 0x00C1 +#define ASC_1000_ID1B 0x25 +#define ASC_EISA_BIG_IOP_GAP (0x1C30-0x0C50) +#define ASC_EISA_SMALL_IOP_GAP (0x0020) +#define ASC_EISA_MIN_IOP_ADDR (0x0C30) +#define ASC_EISA_MAX_IOP_ADDR (0xFC50) +#define ASC_EISA_REV_IOP_MASK (0x0C83) +#define ASC_EISA_PID_IOP_MASK (0x0C80) +#define ASC_EISA_CFG_IOP_MASK (0x0C86) +#define ASC_GET_EISA_SLOT(iop) (PortAddr)((iop) & 0xF000) +#define ASC_EISA_ID_740 0x01745004UL +#define ASC_EISA_ID_750 0x01755004UL +#define INS_HALTINT (ushort)0x6281 +#define INS_HALT (ushort)0x6280 +#define INS_SINT (ushort)0x6200 +#define INS_RFLAG_WTM (ushort)0x7380 +#define ASC_MC_SAVE_CODE_WSIZE 0x500 +#define ASC_MC_SAVE_DATA_WSIZE 0x40 + +typedef struct asc_mc_saved { + ushort data[ASC_MC_SAVE_DATA_WSIZE]; + ushort code[ASC_MC_SAVE_CODE_WSIZE]; +} ASC_MC_SAVED; + +#define AscGetQDoneInProgress(port) AscReadLramByte((port), ASCV_Q_DONE_IN_PROGRESS_B) +#define AscPutQDoneInProgress(port, val) AscWriteLramByte((port), ASCV_Q_DONE_IN_PROGRESS_B, val) +#define AscGetVarFreeQHead(port) AscReadLramWord((port), ASCV_FREE_Q_HEAD_W) +#define AscGetVarDoneQTail(port) AscReadLramWord((port), ASCV_DONE_Q_TAIL_W) +#define AscPutVarFreeQHead(port, val) AscWriteLramWord((port), ASCV_FREE_Q_HEAD_W, val) +#define AscPutVarDoneQTail(port, val) AscWriteLramWord((port), ASCV_DONE_Q_TAIL_W, val) +#define AscGetRiscVarFreeQHead(port) AscReadLramByte((port), ASCV_NEXTRDY_B) +#define AscGetRiscVarDoneQTail(port) AscReadLramByte((port), ASCV_DONENEXT_B) +#define AscPutRiscVarFreeQHead(port, val) AscWriteLramByte((port), ASCV_NEXTRDY_B, val) +#define AscPutRiscVarDoneQTail(port, val) AscWriteLramByte((port), ASCV_DONENEXT_B, val) +#define AscPutMCodeSDTRDoneAtID(port, id, data) AscWriteLramByte((port), (ushort)((ushort)ASCV_SDTR_DONE_BEG+(ushort)id), (data)) ; +#define AscGetMCodeSDTRDoneAtID(port, id) AscReadLramByte((port), (ushort)((ushort)ASCV_SDTR_DONE_BEG+(ushort)id)) ; +#define AscPutMCodeInitSDTRAtID(port, id, data) AscWriteLramByte((port), (ushort)((ushort)ASCV_SDTR_DATA_BEG+(ushort)id), data) ; +#define AscGetMCodeInitSDTRAtID(port, id) AscReadLramByte((port), (ushort)((ushort)ASCV_SDTR_DATA_BEG+(ushort)id)) ; +#define AscSynIndexToPeriod(index) (uchar)(asc_dvc->sdtr_period_tbl[ (index) ]) +#define AscGetChipSignatureByte(port) (uchar)inp((port)+IOP_SIG_BYTE) +#define AscGetChipSignatureWord(port) (ushort)inpw((port)+IOP_SIG_WORD) +#define AscGetChipVerNo(port) (uchar)inp((port)+IOP_VERSION) +#define AscGetChipCfgLsw(port) (ushort)inpw((port)+IOP_CONFIG_LOW) +#define AscGetChipCfgMsw(port) (ushort)inpw((port)+IOP_CONFIG_HIGH) +#define AscSetChipCfgLsw(port, data) outpw((port)+IOP_CONFIG_LOW, data) +#define AscSetChipCfgMsw(port, data) outpw((port)+IOP_CONFIG_HIGH, data) +#define AscGetChipEEPCmd(port) (uchar)inp((port)+IOP_EEP_CMD) +#define AscSetChipEEPCmd(port, data) outp((port)+IOP_EEP_CMD, data) +#define AscGetChipEEPData(port) (ushort)inpw((port)+IOP_EEP_DATA) +#define AscSetChipEEPData(port, data) outpw((port)+IOP_EEP_DATA, data) +#define AscGetChipLramAddr(port) (ushort)inpw((PortAddr)((port)+IOP_RAM_ADDR)) +#define AscSetChipLramAddr(port, addr) outpw((PortAddr)((port)+IOP_RAM_ADDR), addr) +#define AscGetChipLramData(port) (ushort)inpw((port)+IOP_RAM_DATA) +#define AscSetChipLramData(port, data) outpw((port)+IOP_RAM_DATA, data) +#define AscGetChipLramDataNoSwap(port) (ushort)inpw_noswap((port)+IOP_RAM_DATA) +#define AscSetChipLramDataNoSwap(port, data) outpw_noswap((port)+IOP_RAM_DATA, data) +#define AscGetChipIFC(port) (uchar)inp((port)+IOP_REG_IFC) +#define AscSetChipIFC(port, data) outp((port)+IOP_REG_IFC, data) +#define AscGetChipStatus(port) (ASC_CS_TYPE)inpw((port)+IOP_STATUS) +#define AscSetChipStatus(port, cs_val) outpw((port)+IOP_STATUS, cs_val) +#define AscGetChipControl(port) (uchar)inp((port)+IOP_CTRL) +#define AscSetChipControl(port, cc_val) outp((port)+IOP_CTRL, cc_val) +#define AscGetChipSyn(port) (uchar)inp((port)+IOP_SYN_OFFSET) +#define AscSetChipSyn(port, data) outp((port)+IOP_SYN_OFFSET, data) +#define AscSetPCAddr(port, data) outpw((port)+IOP_REG_PC, data) +#define AscGetPCAddr(port) (ushort)inpw((port)+IOP_REG_PC) +#define AscIsIntPending(port) (AscGetChipStatus(port) & (CSW_INT_PENDING | CSW_SCSI_RESET_LATCH)) +#define AscGetChipScsiID(port) ((AscGetChipCfgLsw(port) >> 8) & ASC_MAX_TID) +#define AscGetExtraControl(port) (uchar)inp((port)+IOP_EXTRA_CONTROL) +#define AscSetExtraControl(port, data) outp((port)+IOP_EXTRA_CONTROL, data) +#define AscReadChipAX(port) (ushort)inpw((port)+IOP_REG_AX) +#define AscWriteChipAX(port, data) outpw((port)+IOP_REG_AX, data) +#define AscReadChipIX(port) (uchar)inp((port)+IOP_REG_IX) +#define AscWriteChipIX(port, data) outp((port)+IOP_REG_IX, data) +#define AscReadChipIH(port) (ushort)inpw((port)+IOP_REG_IH) +#define AscWriteChipIH(port, data) outpw((port)+IOP_REG_IH, data) +#define AscReadChipQP(port) (uchar)inp((port)+IOP_REG_QP) +#define AscWriteChipQP(port, data) outp((port)+IOP_REG_QP, data) +#define AscReadChipFIFO_L(port) (ushort)inpw((port)+IOP_REG_FIFO_L) +#define AscWriteChipFIFO_L(port, data) outpw((port)+IOP_REG_FIFO_L, data) +#define AscReadChipFIFO_H(port) (ushort)inpw((port)+IOP_REG_FIFO_H) +#define AscWriteChipFIFO_H(port, data) outpw((port)+IOP_REG_FIFO_H, data) +#define AscReadChipDmaSpeed(port) (uchar)inp((port)+IOP_DMA_SPEED) +#define AscWriteChipDmaSpeed(port, data) outp((port)+IOP_DMA_SPEED, data) +#define AscReadChipDA0(port) (ushort)inpw((port)+IOP_REG_DA0) +#define AscWriteChipDA0(port) outpw((port)+IOP_REG_DA0, data) +#define AscReadChipDA1(port) (ushort)inpw((port)+IOP_REG_DA1) +#define AscWriteChipDA1(port) outpw((port)+IOP_REG_DA1, data) +#define AscReadChipDC0(port) (ushort)inpw((port)+IOP_REG_DC0) +#define AscWriteChipDC0(port) outpw((port)+IOP_REG_DC0, data) +#define AscReadChipDC1(port) (ushort)inpw((port)+IOP_REG_DC1) +#define AscWriteChipDC1(port) outpw((port)+IOP_REG_DC1, data) +#define AscReadChipDvcID(port) (uchar)inp((port)+IOP_REG_ID) +#define AscWriteChipDvcID(port, data) outp((port)+IOP_REG_ID, data) + +STATIC int AscWriteEEPCmdReg(PortAddr iop_base, uchar cmd_reg); +STATIC int AscWriteEEPDataReg(PortAddr iop_base, ushort data_reg); +STATIC void AscWaitEEPRead(void); +STATIC void AscWaitEEPWrite(void); +STATIC ushort AscReadEEPWord(PortAddr, uchar); +STATIC ushort AscWriteEEPWord(PortAddr, uchar, ushort); +STATIC ushort AscGetEEPConfig(PortAddr, ASCEEP_CONFIG *, ushort); +STATIC int AscSetEEPConfigOnce(PortAddr, ASCEEP_CONFIG *, ushort); +STATIC int AscSetEEPConfig(PortAddr, ASCEEP_CONFIG *, ushort); +STATIC int AscStartChip(PortAddr); +STATIC int AscStopChip(PortAddr); +STATIC void AscSetChipIH(PortAddr, ushort); +STATIC int AscIsChipHalted(PortAddr); +STATIC void AscAckInterrupt(PortAddr); +STATIC void AscDisableInterrupt(PortAddr); +STATIC void AscEnableInterrupt(PortAddr); +STATIC void AscSetBank(PortAddr, uchar); +STATIC int AscResetChipAndScsiBus(ASC_DVC_VAR *); +STATIC ushort AscGetIsaDmaChannel(PortAddr); +STATIC ushort AscSetIsaDmaChannel(PortAddr, ushort); +STATIC uchar AscSetIsaDmaSpeed(PortAddr, uchar); +STATIC uchar AscGetIsaDmaSpeed(PortAddr); +STATIC uchar AscReadLramByte(PortAddr, ushort); +STATIC ushort AscReadLramWord(PortAddr, ushort); +STATIC ulong AscReadLramDWord(PortAddr, ushort); +STATIC void AscWriteLramWord(PortAddr, ushort, ushort); +STATIC void AscWriteLramDWord(PortAddr, ushort, ulong); +STATIC void AscWriteLramByte(PortAddr, ushort, uchar); +STATIC ulong AscMemSumLramWord(PortAddr, ushort, rint); +STATIC void AscMemWordSetLram(PortAddr, ushort, ushort, rint); +STATIC void AscMemWordCopyToLram(PortAddr, ushort, ushort *, int); +STATIC void AscMemDWordCopyToLram(PortAddr, ushort, ulong *, int); +STATIC void AscMemWordCopyFromLram(PortAddr, ushort, ushort *, int); +STATIC ushort AscInitAscDvcVar(ASC_DVC_VAR asc_ptr_type *); +STATIC ushort AscInitFromEEP(ASC_DVC_VAR asc_ptr_type *); +STATIC ushort AscInitFromAscDvcVar(ASC_DVC_VAR asc_ptr_type *); +STATIC ushort AscInitMicroCodeVar(ASC_DVC_VAR asc_ptr_type * asc_dvc); +STATIC int AscTestExternalLram(ASC_DVC_VAR asc_ptr_type *); +STATIC uchar AscMsgOutSDTR(ASC_DVC_VAR asc_ptr_type *, uchar, uchar); +STATIC uchar AscCalSDTRData(ASC_DVC_VAR asc_ptr_type *, uchar, uchar); +STATIC void AscSetChipSDTR(PortAddr, uchar, uchar); +STATIC uchar AscGetSynPeriodIndex(ASC_DVC_VAR asc_ptr_type *, ruchar); +STATIC uchar AscAllocFreeQueue(PortAddr, uchar); +STATIC uchar AscAllocMultipleFreeQueue(PortAddr, uchar, uchar); +STATIC int AscRiscHaltedAbortSRB(ASC_DVC_VAR asc_ptr_type *, ulong); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int AscRiscHaltedAbortTIX(ASC_DVC_VAR asc_ptr_type *, uchar); +#endif /* version >= v1.3.89 */ +STATIC int AscHostReqRiscHalt(PortAddr); +STATIC int AscStopQueueExe(PortAddr); +STATIC int AscStartQueueExe(PortAddr); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int AscCleanUpDiscQueue(PortAddr); +#endif /* version >= v1.3.89 */ +STATIC int AscCleanUpBusyQueue(PortAddr); +STATIC int AscWaitTixISRDone(ASC_DVC_VAR asc_ptr_type *, uchar); +STATIC int AscWaitISRDone(ASC_DVC_VAR asc_ptr_type *); +STATIC ulong AscGetOnePhyAddr(ASC_DVC_VAR asc_ptr_type *, uchar *, + ulong); +STATIC int AscSendScsiQueue(ASC_DVC_VAR asc_ptr_type * asc_dvc, + ASC_SCSI_Q * scsiq, + uchar n_q_required); +STATIC int AscPutReadyQueue(ASC_DVC_VAR asc_ptr_type *, + ASC_SCSI_Q *, uchar); +STATIC int AscPutReadySgListQueue(ASC_DVC_VAR asc_ptr_type *, + ASC_SCSI_Q *, uchar); +STATIC int AscSetChipSynRegAtID(PortAddr, uchar, uchar); +STATIC int AscSetRunChipSynRegAtID(PortAddr, uchar, uchar); +STATIC ushort AscInitLram(ASC_DVC_VAR asc_ptr_type *); +STATIC int AscReInitLram(ASC_DVC_VAR asc_ptr_type *); +STATIC ushort AscInitQLinkVar(ASC_DVC_VAR asc_ptr_type *); +STATIC int AscSetLibErrorCode(ASC_DVC_VAR asc_ptr_type *, ushort); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int _AscWaitQDone(PortAddr, ASC_SCSI_Q *); +#endif /* version >= v1.3.89 */ +STATIC int AscIsrChipHalted(ASC_DVC_VAR asc_ptr_type *); +STATIC uchar _AscCopyLramScsiDoneQ(PortAddr, ushort, + ASC_QDONE_INFO *, ulong); +STATIC int AscIsrQDone(ASC_DVC_VAR asc_ptr_type *); +STATIC int AscCompareString(uchar *, uchar *, int); +STATIC ushort AscGetEisaChipCfg(PortAddr); +STATIC ulong AscGetEisaProductID(PortAddr); +STATIC PortAddr AscSearchIOPortAddrEISA(PortAddr); +STATIC uchar AscGetChipScsiCtrl(PortAddr); +STATIC uchar AscSetChipScsiID(PortAddr, uchar); +STATIC uchar AscGetChipVersion(PortAddr, ushort); +STATIC ushort AscGetChipBusType(PortAddr); +STATIC ulong AscLoadMicroCode(PortAddr, ushort, ushort *, ushort); +STATIC int AscFindSignature(PortAddr); +STATIC PortAddr AscSearchIOPortAddr11(PortAddr); +STATIC void AscToggleIRQAct(PortAddr); +STATIC void AscSetISAPNPWaitForKey(void); +STATIC uchar AscGetChipIRQ(PortAddr, ushort); +STATIC uchar AscSetChipIRQ(PortAddr, uchar, ushort); +STATIC ushort AscGetChipBiosAddress(PortAddr, ushort); +STATIC int DvcEnterCritical(void); +STATIC void DvcLeaveCritical(int); +STATIC void DvcInPortWords(PortAddr, ushort *, int); +STATIC void DvcOutPortWords(PortAddr, ushort *, int); +STATIC void DvcOutPortDWords(PortAddr, ulong *, int); +STATIC uchar DvcReadPCIConfigByte(ASC_DVC_VAR asc_ptr_type *, ushort); +STATIC void DvcWritePCIConfigByte(ASC_DVC_VAR asc_ptr_type *, + ushort, uchar); +STATIC ushort AscGetChipBiosAddress(PortAddr, ushort); +STATIC void DvcSleepMilliSecond(ulong); +STATIC void DvcDelayNanoSecond(ASC_DVC_VAR asc_ptr_type *, ulong); +STATIC ulong DvcGetSGList(ASC_DVC_VAR asc_ptr_type *, uchar *, + ulong, ASC_SG_HEAD *); +STATIC void DvcPutScsiQ(PortAddr, ushort, ushort *, int); +STATIC void DvcGetQinfo(PortAddr, ushort, ushort *, int); +STATIC PortAddr AscSearchIOPortAddr(PortAddr, ushort); +STATIC ushort AscInitGetConfig(ASC_DVC_VAR asc_ptr_type *); +STATIC ushort AscInitSetConfig(ASC_DVC_VAR asc_ptr_type *); +STATIC ushort AscInitAsc1000Driver(ASC_DVC_VAR asc_ptr_type *); +STATIC void AscAsyncFix(ASC_DVC_VAR asc_ptr_type *, uchar, + ASC_SCSI_INQUIRY *); +STATIC int AscTagQueuingSafe(ASC_SCSI_INQUIRY *); +STATIC void AscInquiryHandling(ASC_DVC_VAR asc_ptr_type *, + uchar, ASC_SCSI_INQUIRY *); +STATIC int AscExeScsiQueue(ASC_DVC_VAR asc_ptr_type *, ASC_SCSI_Q *); +STATIC int AscISR(ASC_DVC_VAR asc_ptr_type *); +STATIC uint AscGetNumOfFreeQueue(ASC_DVC_VAR asc_ptr_type *, uchar, + uchar); +STATIC int AscSgListToQueue(int); +STATIC int AscAbortSRB(ASC_DVC_VAR asc_ptr_type *, ulong); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int AscResetDevice(ASC_DVC_VAR asc_ptr_type *, uchar); +#endif /* version >= v1.3.89 */ +STATIC int AscResetSB(ASC_DVC_VAR asc_ptr_type *); +STATIC void AscEnableIsaDma(uchar); +STATIC ulong AscGetMaxDmaCount(ushort); + + +/* + * --- Adv Library Constants and Macros + */ + +#define ADV_LIB_VERSION_MAJOR 3 +#define ADV_LIB_VERSION_MINOR 45 + +/* d_os_dep.h */ +#define ADV_OS_LINUX + +/* + * Define Adv Library required special types. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) +#define AdvPortAddr unsigned short /* I/O Port address size */ +#else /* version >= v1,3,0 */ +#define AdvPortAddr unsigned long /* Virtual memory address size */ +#endif /* version >= v1,3,0 */ + +/* + * Define Adv Library required memory access macros. + */ +#define ADV_MEM_READB(addr) readb(addr) +#define ADV_MEM_READW(addr) readw(addr) +#define ADV_MEM_WRITEB(addr, byte) writeb(byte, addr) +#define ADV_MEM_WRITEW(addr, word) writew(word, addr) + +/* + * The I/O memory mapping function names changed in 2.1.X. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,0) +#define ioremap vremap +#define iounmap vfree +#endif /* version < v2.1.0 */ + +/* + * Define total number of simultaneous maximum element scatter-gather + * requests, i.e. ADV_TOT_SG_LIST * ADV_MAX_SG_LIST is the total number + * of simultaneous scatter-gather elements supported per wide adapter. + */ +#define ADV_TOT_SG_LIST 64 + +/* + * Define Adv Library required per request scatter-gather element limit. + */ +#define ADV_MAX_SG_LIST 64 + +/* + * Scatter-Gather Definitions per request. + * + * Because SG block memory is allocated in virtual memory but is + * referenced by the microcode as physical memory, we need to do + * calculations to insure there will be enough physically contiguous + * memory to support ADV_MAX_SG_LIST SG entries. + */ + +/* Number of SG blocks needed. */ +#define ADV_NUM_SG_BLOCK \ + ((ADV_MAX_SG_LIST + (NO_OF_SG_PER_BLOCK - 1))/NO_OF_SG_PER_BLOCK) + +/* Total contiguous memory needed for SG blocks. */ +#define ADV_SG_TOTAL_MEM_SIZE \ + (sizeof(ADV_SG_BLOCK) * ADV_NUM_SG_BLOCK) + +#define ASC_PAGE_SIZE PAGE_SIZE + +/* + * Number of page crossings possible for the total contiguous virtual memory + * needed for SG blocks. + * + * We need to allocate this many additional SG blocks in virtual memory to + * insure there will be space for ADV_NUM_SG_BLOCK physically contiguous + * scatter-gather blocks. + */ +#define ADV_NUM_PAGE_CROSSING \ + ((ADV_SG_TOTAL_MEM_SIZE + (ASC_PAGE_SIZE - 1))/ASC_PAGE_SIZE) + +/* + * Define Adv Library Assertion Macro. + */ + +#define ADV_ASSERT(a) ASC_ASSERT(a) + +/* a_condor.h */ +#define ADV_PCI_VENDOR_ID 0x10CD +#define ADV_PCI_DEVICE_ID_REV_A 0x2300 + +#define ASC_EEP_DVC_CFG_BEGIN (0x00) +#define ASC_EEP_DVC_CFG_END (0x15) +#define ASC_EEP_DVC_CTL_BEGIN (0x16) /* location of OEM name */ +#define ASC_EEP_MAX_WORD_ADDR (0x1E) + +#define ASC_EEP_DELAY_MS 100 + +/* + * EEPROM bits reference by the RISC after initialization. + */ +#define ADV_EEPROM_BIG_ENDIAN 0x8000 /* EEPROM Bit 15 */ +#define ADV_EEPROM_BIOS_ENABLE 0x4000 /* EEPROM Bit 14 */ +#define ADV_EEPROM_TERM_POL 0x2000 /* EEPROM Bit 13 */ + +/* + * EEPROM configuration format + * + * Field naming convention: + * + * *_enable indicates the field enables or disables the feature. The + * value is never reset. + * + * *_able indicates both whether a feature should be enabled or disabled + * and whether a device isi capable of the feature. At initialization + * this field may be set, but later if a device is found to be incapable + * of the feature, the field is cleared. + * + * Default values are maintained in a_init.c in the structure + * Default_EEPROM_Config. + */ +typedef struct adveep_config +{ + /* Word Offset, Description */ + + ushort cfg_lsw; /* 00 power up initialization */ + /* bit 13 set - Term Polarity Control */ + /* bit 14 set - BIOS Enable */ + /* bit 15 set - Big Endian Mode */ + ushort cfg_msw; /* 01 unused */ + ushort disc_enable; /* 02 disconnect enable */ + ushort wdtr_able; /* 03 Wide DTR able */ + ushort sdtr_able; /* 04 Synchronous DTR able */ + ushort start_motor; /* 05 send start up motor */ + ushort tagqng_able; /* 06 tag queuing able */ + ushort bios_scan; /* 07 BIOS device control */ + ushort scam_tolerant; /* 08 no scam */ + + uchar adapter_scsi_id; /* 09 Host Adapter ID */ + uchar bios_boot_delay; /* power up wait */ + + uchar scsi_reset_delay; /* 10 reset delay */ + uchar bios_id_lun; /* first boot device scsi id & lun */ + /* high nibble is lun */ + /* low nibble is scsi id */ + + uchar termination; /* 11 0 - automatic */ + /* 1 - low off / high off */ + /* 2 - low off / high on */ + /* 3 - low on / high on */ + /* There is no low on / high off */ + + uchar reserved1; /* reserved byte (not used) */ + + ushort bios_ctrl; /* 12 BIOS control bits */ + /* bit 0 set: BIOS don't act as initiator. */ + /* bit 1 set: BIOS > 1 GB support */ + /* bit 2 set: BIOS > 2 Disk Support */ + /* bit 3 set: BIOS don't support removables */ + /* bit 4 set: BIOS support bootable CD */ + /* bit 5 set: */ + /* bit 6 set: BIOS support multiple LUNs */ + /* bit 7 set: BIOS display of message */ + /* bit 8 set: */ + /* bit 9 set: Reset SCSI bus during init. */ + /* bit 10 set: */ + /* bit 11 set: No verbose initialization. */ + /* bit 12 set: SCSI parity enabled */ + /* bit 13 set: */ + /* bit 14 set: */ + /* bit 15 set: */ + ushort ultra_able; /* 13 ULTRA speed able */ + ushort reserved2; /* 14 reserved */ + uchar max_host_qng; /* 15 maximum host queuing */ + uchar max_dvc_qng; /* maximum per device queuing */ + ushort dvc_cntl; /* 16 control bit for driver */ + ushort bug_fix; /* 17 control bit for bug fix */ + ushort serial_number_word1; /* 18 Board serial number word 1 */ + ushort serial_number_word2; /* 19 Board serial number word 2 */ + ushort serial_number_word3; /* 20 Board serial number word 3 */ + ushort check_sum; /* 21 EEP check sum */ + uchar oem_name[16]; /* 22 OEM name */ + ushort dvc_err_code; /* 30 last device driver error code */ + ushort adv_err_code; /* 31 last uc and Adv Lib error code */ + ushort adv_err_addr; /* 32 last uc error address */ + ushort saved_dvc_err_code; /* 33 saved last dev. driver error code */ + ushort saved_adv_err_code; /* 34 saved last uc and Adv Lib error code */ + ushort saved_adv_err_addr; /* 35 saved last uc error address */ + ushort num_of_err; /* 36 number of error */ +} ADVEEP_CONFIG; + +/* + * EEPROM Commands + */ +#define ASC_EEP_CMD_DONE 0x0200 +#define ASC_EEP_CMD_DONE_ERR 0x0001 + +/* cfg_word */ +#define EEP_CFG_WORD_BIG_ENDIAN 0x8000 + +/* bios_ctrl */ +#define BIOS_CTRL_BIOS 0x0001 +#define BIOS_CTRL_EXTENDED_XLAT 0x0002 +#define BIOS_CTRL_GT_2_DISK 0x0004 +#define BIOS_CTRL_BIOS_REMOVABLE 0x0008 +#define BIOS_CTRL_BOOTABLE_CD 0x0010 +#define BIOS_CTRL_MULTIPLE_LUN 0x0040 +#define BIOS_CTRL_DISPLAY_MSG 0x0080 +#define BIOS_CTRL_NO_SCAM 0x0100 +#define BIOS_CTRL_RESET_SCSI_BUS 0x0200 +#define BIOS_CTRL_INIT_VERBOSE 0x0800 +#define BIOS_CTRL_SCSI_PARITY 0x1000 + +/* + * ASC 3550 Internal Memory Size - 8KB + */ +#define ADV_CONDOR_MEMSIZE 0x2000 /* 8 KB Internal Memory */ + +/* + * ASC 3550 I/O Length - 64 bytes + */ +#define ADV_CONDOR_IOLEN 0x40 /* I/O Port Range in bytes */ + +/* + * Byte I/O register address from base of 'iop_base'. + */ +#define IOPB_INTR_STATUS_REG 0x00 +#define IOPB_CHIP_ID_1 0x01 +#define IOPB_INTR_ENABLES 0x02 +#define IOPB_CHIP_TYPE_REV 0x03 +#define IOPB_RES_ADDR_4 0x04 +#define IOPB_RES_ADDR_5 0x05 +#define IOPB_RAM_DATA 0x06 +#define IOPB_RES_ADDR_7 0x07 +#define IOPB_FLAG_REG 0x08 +#define IOPB_RES_ADDR_9 0x09 +#define IOPB_RISC_CSR 0x0A +#define IOPB_RES_ADDR_B 0x0B +#define IOPB_RES_ADDR_C 0x0C +#define IOPB_RES_ADDR_D 0x0D +#define IOPB_RES_ADDR_E 0x0E +#define IOPB_RES_ADDR_F 0x0F +#define IOPB_MEM_CFG 0x10 +#define IOPB_RES_ADDR_11 0x11 +#define IOPB_RES_ADDR_12 0x12 +#define IOPB_RES_ADDR_13 0x13 +#define IOPB_FLASH_PAGE 0x14 +#define IOPB_RES_ADDR_15 0x15 +#define IOPB_RES_ADDR_16 0x16 +#define IOPB_RES_ADDR_17 0x17 +#define IOPB_FLASH_DATA 0x18 +#define IOPB_RES_ADDR_19 0x19 +#define IOPB_RES_ADDR_1A 0x1A +#define IOPB_RES_ADDR_1B 0x1B +#define IOPB_RES_ADDR_1C 0x1C +#define IOPB_RES_ADDR_1D 0x1D +#define IOPB_RES_ADDR_1E 0x1E +#define IOPB_RES_ADDR_1F 0x1F +#define IOPB_DMA_CFG0 0x20 +#define IOPB_DMA_CFG1 0x21 +#define IOPB_TICKLE 0x22 +#define IOPB_DMA_REG_WR 0x23 +#define IOPB_SDMA_STATUS 0x24 +#define IOPB_SCSI_BYTE_CNT 0x25 +#define IOPB_HOST_BYTE_CNT 0x26 +#define IOPB_BYTE_LEFT_TO_XFER 0x27 +#define IOPB_BYTE_TO_XFER_0 0x28 +#define IOPB_BYTE_TO_XFER_1 0x29 +#define IOPB_BYTE_TO_XFER_2 0x2A +#define IOPB_BYTE_TO_XFER_3 0x2B +#define IOPB_ACC_GRP 0x2C +#define IOPB_RES_ADDR_2D 0x2D +#define IOPB_DEV_ID 0x2E +#define IOPB_RES_ADDR_2F 0x2F +#define IOPB_SCSI_DATA 0x30 +#define IOPB_RES_ADDR_31 0x31 +#define IOPB_RES_ADDR_32 0x32 +#define IOPB_SCSI_DATA_HSHK 0x33 +#define IOPB_SCSI_CTRL 0x34 +#define IOPB_RES_ADDR_35 0x35 +#define IOPB_RES_ADDR_36 0x36 +#define IOPB_RES_ADDR_37 0x37 +#define IOPB_RES_ADDR_38 0x38 +#define IOPB_RES_ADDR_39 0x39 +#define IOPB_RES_ADDR_3A 0x3A +#define IOPB_RES_ADDR_3B 0x3B +#define IOPB_RFIFO_CNT 0x3C +#define IOPB_RES_ADDR_3D 0x3D +#define IOPB_RES_ADDR_3E 0x3E +#define IOPB_RES_ADDR_3F 0x3F + +/* + * Word I/O register address from base of 'iop_base'. + */ +#define IOPW_CHIP_ID_0 0x00 /* CID0 */ +#define IOPW_CTRL_REG 0x02 /* CC */ +#define IOPW_RAM_ADDR 0x04 /* LA */ +#define IOPW_RAM_DATA 0x06 /* LD */ +#define IOPW_RES_ADDR_08 0x08 +#define IOPW_RISC_CSR 0x0A /* CSR */ +#define IOPW_SCSI_CFG0 0x0C /* CFG0 */ +#define IOPW_SCSI_CFG1 0x0E /* CFG1 */ +#define IOPW_RES_ADDR_10 0x10 +#define IOPW_SEL_MASK 0x12 /* SM */ +#define IOPW_RES_ADDR_14 0x14 +#define IOPW_FLASH_ADDR 0x16 /* FA */ +#define IOPW_RES_ADDR_18 0x18 +#define IOPW_EE_CMD 0x1A /* EC */ +#define IOPW_EE_DATA 0x1C /* ED */ +#define IOPW_SFIFO_CNT 0x1E /* SFC */ +#define IOPW_RES_ADDR_20 0x20 +#define IOPW_Q_BASE 0x22 /* QB */ +#define IOPW_QP 0x24 /* QP */ +#define IOPW_IX 0x26 /* IX */ +#define IOPW_SP 0x28 /* SP */ +#define IOPW_PC 0x2A /* PC */ +#define IOPW_RES_ADDR_2C 0x2C +#define IOPW_RES_ADDR_2E 0x2E +#define IOPW_SCSI_DATA 0x30 /* SD */ +#define IOPW_SCSI_DATA_HSHK 0x32 /* SDH */ +#define IOPW_SCSI_CTRL 0x34 /* SC */ +#define IOPW_HSHK_CFG 0x36 /* HCFG */ +#define IOPW_SXFR_STATUS 0x36 /* SXS */ +#define IOPW_SXFR_CNTL 0x38 /* SXL */ +#define IOPW_SXFR_CNTH 0x3A /* SXH */ +#define IOPW_RES_ADDR_3C 0x3C +#define IOPW_RFIFO_DATA 0x3E /* RFD */ + +/* + * Doubleword I/O register address from base of 'iop_base'. + */ +#define IOPDW_RES_ADDR_0 0x00 +#define IOPDW_RAM_DATA 0x04 +#define IOPDW_RES_ADDR_8 0x08 +#define IOPDW_RES_ADDR_C 0x0C +#define IOPDW_RES_ADDR_10 0x10 +#define IOPDW_RES_ADDR_14 0x14 +#define IOPDW_RES_ADDR_18 0x18 +#define IOPDW_RES_ADDR_1C 0x1C +#define IOPDW_SDMA_ADDR0 0x20 +#define IOPDW_SDMA_ADDR1 0x24 +#define IOPDW_SDMA_COUNT 0x28 +#define IOPDW_SDMA_ERROR 0x2C +#define IOPDW_RDMA_ADDR0 0x30 +#define IOPDW_RDMA_ADDR1 0x34 +#define IOPDW_RDMA_COUNT 0x38 +#define IOPDW_RDMA_ERROR 0x3C + +#define ADV_CHIP_ID_BYTE 0x25 +#define ADV_CHIP_ID_WORD 0x04C1 + +#define ADV_SC_SCSI_BUS_RESET 0x2000 + +#define ADV_INTR_ENABLE_HOST_INTR 0x01 +#define ADV_INTR_ENABLE_SEL_INTR 0x02 +#define ADV_INTR_ENABLE_DPR_INTR 0x04 +#define ADV_INTR_ENABLE_RTA_INTR 0x08 +#define ADV_INTR_ENABLE_RMA_INTR 0x10 +#define ADV_INTR_ENABLE_RST_INTR 0x20 +#define ADV_INTR_ENABLE_DPE_INTR 0x40 +#define ADV_INTR_ENABLE_GLOBAL_INTR 0x80 + +#define ADV_INTR_STATUS_INTRA 0x01 +#define ADV_INTR_STATUS_INTRB 0x02 +#define ADV_INTR_STATUS_INTRC 0x04 + +#define ADV_RISC_CSR_STOP (0x0000) +#define ADV_RISC_TEST_COND (0x2000) +#define ADV_RISC_CSR_RUN (0x4000) +#define ADV_RISC_CSR_SINGLE_STEP (0x8000) + +#define ADV_CTRL_REG_HOST_INTR 0x0100 +#define ADV_CTRL_REG_SEL_INTR 0x0200 +#define ADV_CTRL_REG_DPR_INTR 0x0400 +#define ADV_CTRL_REG_RTA_INTR 0x0800 +#define ADV_CTRL_REG_RMA_INTR 0x1000 +#define ADV_CTRL_REG_RES_BIT14 0x2000 +#define ADV_CTRL_REG_DPE_INTR 0x4000 +#define ADV_CTRL_REG_POWER_DONE 0x8000 +#define ADV_CTRL_REG_ANY_INTR 0xFF00 + +#define ADV_CTRL_REG_CMD_RESET 0x00C6 +#define ADV_CTRL_REG_CMD_WR_IO_REG 0x00C5 +#define ADV_CTRL_REG_CMD_RD_IO_REG 0x00C4 +#define ADV_CTRL_REG_CMD_WR_PCI_CFG_SPACE 0x00C3 +#define ADV_CTRL_REG_CMD_RD_PCI_CFG_SPACE 0x00C2 + +#define ADV_SCSI_CTRL_RSTOUT 0x2000 + +#define AdvIsIntPending(port) \ + (AdvReadWordRegister(port, IOPW_CTRL_REG) & ADV_CTRL_REG_HOST_INTR) + +/* + * SCSI_CFG0 Register bit definitions + */ +#define TIMER_MODEAB 0xC000 /* Watchdog, Second, and Select. Timer Ctrl. */ +#define PARITY_EN 0x2000 /* Enable SCSI Parity Error detection */ +#define EVEN_PARITY 0x1000 /* Select Even Parity */ +#define WD_LONG 0x0800 /* Watchdog Interval, 1: 57 min, 0: 13 sec */ +#define QUEUE_128 0x0400 /* Queue Size, 1: 128 byte, 0: 64 byte */ +#define PRIM_MODE 0x0100 /* Primitive SCSI mode */ +#define SCAM_EN 0x0080 /* Enable SCAM selection */ +#define SEL_TMO_LONG 0x0040 /* Sel/Resel Timeout, 1: 400 ms, 0: 1.6 ms */ +#define CFRM_ID 0x0020 /* SCAM id sel. confirm., 1: fast, 0: 6.4 ms */ +#define OUR_ID_EN 0x0010 /* Enable OUR_ID bits */ +#define OUR_ID 0x000F /* SCSI ID */ + +/* + * SCSI_CFG1 Register bit definitions + */ +#define BIG_ENDIAN 0x8000 /* Enable Big Endian Mode MIO:15, EEP:15 */ +#define TERM_POL 0x2000 /* Terminator Polarity Ctrl. MIO:13, EEP:13 */ +#define SLEW_RATE 0x1000 /* SCSI output buffer slew rate */ +#define FILTER_SEL 0x0C00 /* Filter Period Selection */ +#define FLTR_DISABLE 0x0000 /* Input Filtering Disabled */ +#define FLTR_11_TO_20NS 0x0800 /* Input Filtering 11ns to 20ns */ +#define FLTR_21_TO_39NS 0x0C00 /* Input Filtering 21ns to 39ns */ +#define ACTIVE_DBL 0x0200 /* Disable Active Negation */ +#define DIFF_MODE 0x0100 /* SCSI differential Mode (Read-Only) */ +#define DIFF_SENSE 0x0080 /* 1: No SE cables, 0: SE cable (Read-Only) */ +#define TERM_CTL_SEL 0x0040 /* Enable TERM_CTL_H and TERM_CTL_L */ +#define TERM_CTL 0x0030 /* External SCSI Termination Bits */ +#define TERM_CTL_H 0x0020 /* Enable External SCSI Upper Termination */ +#define TERM_CTL_L 0x0010 /* Enable External SCSI Lower Termination */ +#define CABLE_DETECT 0x000F /* External SCSI Cable Connection Status */ + +#define CABLE_ILLEGAL_A 0x7 + /* x 0 0 0 | on on | Illegal (all 3 connectors are used) */ + +#define CABLE_ILLEGAL_B 0xB + /* 0 x 0 0 | on on | Illegal (all 3 connectors are used) */ + +/* + The following table details the SCSI_CFG1 Termination Polarity, + Termination Control and Cable Detect bits. + + Cable Detect | Termination + Bit 3 2 1 0 | 5 4 | Notes + _____________|________|____________________ + 1 1 1 0 | on on | Internal wide only + 1 1 0 1 | on on | Internal narrow only + 1 0 1 1 | on on | External narrow only + 0 x 1 1 | on on | External wide only + 1 1 0 0 | on off| Internal wide and internal narrow + 1 0 1 0 | on off| Internal wide and external narrow + 0 x 1 0 | off off| Internal wide and external wide + 1 0 0 1 | on off| Internal narrow and external narrow + 0 x 0 1 | on off| Internal narrow and external wide + 1 1 1 1 | on on | No devices are attached + x 0 0 0 | on on | Illegal (all 3 connectors are used) + 0 x 0 0 | on on | Illegal (all 3 connectors are used) + + x means don't-care (either '0' or '1') + + If term_pol (bit 13) is '0' (active-low terminator enable), then: + 'on' is '0' and 'off' is '1'. + + If term_pol bit is '1' (meaning active-hi terminator enable), then: + 'on' is '1' and 'off' is '0'. + */ + +/* + * MEM_CFG Register bit definitions + */ +#define BIOS_EN 0x40 /* BIOS Enable MIO:14,EEP:14 */ +#define FAST_EE_CLK 0x20 /* Diagnostic Bit */ +#define RAM_SZ 0x1C /* Specify size of RAM to RISC */ +#define RAM_SZ_2KB 0x00 /* 2 KB */ +#define RAM_SZ_4KB 0x04 /* 4 KB */ +#define RAM_SZ_8KB 0x08 /* 8 KB */ +#define RAM_SZ_16KB 0x0C /* 16 KB */ +#define RAM_SZ_32KB 0x10 /* 32 KB */ +#define RAM_SZ_64KB 0x14 /* 64 KB */ + +/* + * DMA_CFG0 Register bit definitions + * + * This register is only accessible to the host. + */ +#define BC_THRESH_ENB 0x80 /* PCI DMA Start Conditions */ +#define FIFO_THRESH 0x70 /* PCI DMA FIFO Threshold */ +#define FIFO_THRESH_16B 0x00 /* 16 bytes */ +#define FIFO_THRESH_32B 0x20 /* 32 bytes */ +#define FIFO_THRESH_48B 0x30 /* 48 bytes */ +#define FIFO_THRESH_64B 0x40 /* 64 bytes */ +#define FIFO_THRESH_80B 0x50 /* 80 bytes (default) */ +#define FIFO_THRESH_96B 0x60 /* 96 bytes */ +#define FIFO_THRESH_112B 0x70 /* 112 bytes */ +#define START_CTL 0x0C /* DMA start conditions */ +#define START_CTL_TH 0x00 /* Wait threshold level (default) */ +#define START_CTL_ID 0x04 /* Wait SDMA/SBUS idle */ +#define START_CTL_THID 0x08 /* Wait threshold and SDMA/SBUS idle */ +#define START_CTL_EMFU 0x0C /* Wait SDMA FIFO empty/full */ +#define READ_CMD 0x03 /* Memory Read Method */ +#define READ_CMD_MR 0x00 /* Memory Read */ +#define READ_CMD_MRL 0x02 /* Memory Read Long */ +#define READ_CMD_MRM 0x03 /* Memory Read Multiple (default) */ + +/* a_advlib.h */ + +/* + * Adv Library Status Definitions + */ +#define ADV_TRUE 1 +#define ADV_FALSE 0 +#define ADV_NOERROR 1 +#define ADV_SUCCESS 1 +#define ADV_BUSY 0 +#define ADV_ERROR (-1) + + +/* + * ASC_DVC_VAR 'warn_code' values + */ +#define ASC_WARN_EEPROM_CHKSUM 0x0002 /* EEP check sum error */ +#define ASC_WARN_EEPROM_TERMINATION 0x0004 /* EEP termination bad field */ +#define ASC_WARN_SET_PCI_CONFIG_SPACE 0x0080 /* PCI config space set error */ +#define ASC_WARN_ERROR 0xFFFF /* ADV_ERROR return */ + +#define ADV_MAX_TID 15 /* max. target identifier */ +#define ADV_MAX_LUN 7 /* max. logical unit number */ + + +/* + * AscInitGetConfig() and AscInitAsc1000Driver() Definitions + * + * Error code values are set in ASC_DVC_VAR 'err_code'. + */ +#define ASC_IERR_WRITE_EEPROM 0x0001 /* write EEPROM error */ +#define ASC_IERR_MCODE_CHKSUM 0x0002 /* micro code check sum error */ +#define ASC_IERR_START_STOP_CHIP 0x0008 /* start/stop chip failed */ +#define ASC_IERR_CHIP_VERSION 0x0040 /* wrong chip version */ +#define ASC_IERR_SET_SCSI_ID 0x0080 /* set SCSI ID failed */ +#define ASC_IERR_BAD_SIGNATURE 0x0200 /* signature not found */ +#define ASC_IERR_ILLEGAL_CONNECTION 0x0400 /* Illegal cable connection */ +#define ASC_IERR_SINGLE_END_DEVICE 0x0800 /* Single-end used w/differential */ +#define ASC_IERR_REVERSED_CABLE 0x1000 /* Narrow flat cable reversed */ +#define ASC_IERR_RW_LRAM 0x8000 /* read/write local RAM error */ + +/* + * Fixed locations of microcode operating variables. + */ +#define ASC_MC_CODE_BEGIN_ADDR 0x0028 /* microcode start address */ +#define ASC_MC_CODE_END_ADDR 0x002A /* microcode end address */ +#define ASC_MC_CODE_CHK_SUM 0x002C /* microcode code checksum */ +#define ASC_MC_STACK_BEGIN 0x002E /* microcode stack begin */ +#define ASC_MC_STACK_END 0x0030 /* microcode stack end */ +#define ASC_MC_VERSION_DATE 0x0038 /* microcode version */ +#define ASC_MC_VERSION_NUM 0x003A /* microcode number */ +#define ASCV_VER_SERIAL_W 0x003C /* used in dos_init */ +#define ASC_MC_BIOSMEM 0x0040 /* BIOS RISC Memory Start */ +#define ASC_MC_BIOSLEN 0x0050 /* BIOS RISC Memory Length */ +#define ASC_MC_HALTCODE 0x0094 /* microcode halt code */ +#define ASC_MC_CALLERPC 0x0096 /* microcode halt caller PC */ +#define ASC_MC_ADAPTER_SCSI_ID 0x0098 /* one ID byte + reserved */ +#define ASC_MC_ULTRA_ABLE 0x009C +#define ASC_MC_SDTR_ABLE 0x009E +#define ASC_MC_TAGQNG_ABLE 0x00A0 +#define ASC_MC_DISC_ENABLE 0x00A2 +#define ASC_MC_IDLE_CMD 0x00A6 +#define ASC_MC_IDLE_PARA_STAT 0x00A8 +#define ASC_MC_DEFAULT_SCSI_CFG0 0x00AC +#define ASC_MC_DEFAULT_SCSI_CFG1 0x00AE +#define ASC_MC_DEFAULT_MEM_CFG 0x00B0 +#define ASC_MC_DEFAULT_SEL_MASK 0x00B2 +#define ASC_MC_RISC_NEXT_READY 0x00B4 +#define ASC_MC_RISC_NEXT_DONE 0x00B5 +#define ASC_MC_SDTR_DONE 0x00B6 +#define ASC_MC_NUMBER_OF_QUEUED_CMD 0x00C0 +#define ASC_MC_NUMBER_OF_MAX_CMD 0x00D0 +#define ASC_MC_DEVICE_HSHK_CFG_TABLE 0x0100 +#define ASC_MC_WDTR_ABLE 0x0120 /* Wide Transfer TID bitmask. */ +#define ASC_MC_CONTROL_FLAG 0x0122 /* Microcode control flag. */ +#define ASC_MC_WDTR_DONE 0x0124 +#define ASC_MC_HOST_NEXT_READY 0x0128 /* Host Next Ready RQL Entry. */ +#define ASC_MC_HOST_NEXT_DONE 0x0129 /* Host Next Done RQL Entry. */ + +/* + * BIOS LRAM variable absolute offsets. + */ +#define BIOS_CODESEG 0x54 +#define BIOS_CODELEN 0x56 +#define BIOS_SIGNATURE 0x58 +#define BIOS_VERSION 0x5A +#define BIOS_SIGNATURE 0x58 + +/* + * Microcode Control Flags + * + * Flags set by the Adv Library in RISC variable 'control_flag' (0x122) + * and handled by the microcode. + */ +#define CONTROL_FLAG_IGNORE_PERR 0x0001 /* Ignore DMA Parity Errors */ + +/* + * ASC_MC_DEVICE_HSHK_CFG_TABLE microcode table or HSHK_CFG register format + */ +#define HSHK_CFG_WIDE_XFR 0x8000 +#define HSHK_CFG_RATE 0x0F00 +#define HSHK_CFG_OFFSET 0x001F + +/* + * LRAM RISC Queue Lists (LRAM addresses 0x1200 - 0x19FF) + * + * Each of the 255 Adv Library/Microcode RISC queue lists or mailboxes + * starting at LRAM address 0x1200 is 8 bytes and has the following + * structure. Only 253 of these are actually used for command queues. + */ + +#define ASC_MC_RISC_Q_LIST_BASE 0x1200 +#define ASC_MC_RISC_Q_LIST_SIZE 0x0008 +#define ASC_MC_RISC_Q_TOTAL_CNT 0x00FF /* Num. queue slots in LRAM. */ +#define ASC_MC_RISC_Q_FIRST 0x0001 +#define ASC_MC_RISC_Q_LAST 0x00FF + +#define ASC_DEF_MAX_HOST_QNG 0xFD /* Max. number of host commands (253) */ +#define ASC_DEF_MIN_HOST_QNG 0x10 /* Min. number of host commands (16) */ +#define ASC_DEF_MAX_DVC_QNG 0x3F /* Max. number commands per device (63) */ +#define ASC_DEF_MIN_DVC_QNG 0x04 /* Min. number commands per device (4) */ + +/* RISC Queue List structure - 8 bytes */ +#define RQL_FWD 0 /* forward pointer (1 byte) */ +#define RQL_BWD 1 /* backward pointer (1 byte) */ +#define RQL_STATE 2 /* state byte - free, ready, done, aborted (1 byte) */ +#define RQL_TID 3 /* request target id (1 byte) */ +#define RQL_PHYADDR 4 /* request physical pointer (4 bytes) */ + +/* RISC Queue List state values */ +#define ASC_MC_QS_FREE 0x00 +#define ASC_MC_QS_READY 0x01 +#define ASC_MC_QS_DONE 0x40 +#define ASC_MC_QS_ABORTED 0x80 + +/* RISC Queue List pointer values */ +#define ASC_MC_NULL_Q 0x00 /* NULL_Q == 0 */ +#define ASC_MC_BIOS_Q 0xFF /* BIOS_Q = 255 */ + +/* ASC_SCSI_REQ_Q 'cntl' field values */ +#define ASC_MC_QC_START_MOTOR 0x02 /* Issue start motor. */ +#define ASC_MC_QC_NO_OVERRUN 0x04 /* Don't report overrun. */ +#define ASC_MC_QC_FIRST_DMA 0x08 /* Internal microcode flag. */ +#define ASC_MC_QC_ABORTED 0x10 /* Request aborted by host. */ +#define ASC_MC_QC_REQ_SENSE 0x20 /* Auto-Request Sense. */ +#define ASC_MC_QC_DOS_REQ 0x80 /* Request issued by DOS. */ + + +/* + * ASC_SCSI_REQ_Q 'a_flag' definitions + * + * The Adv Library should limit use to the lower nibble (4 bits) of + * a_flag. Drivers are free to use the upper nibble (4 bits) of a_flag. + */ +#define ADV_POLL_REQUEST 0x01 /* poll for request completion */ +#define ADV_SCSIQ_DONE 0x02 /* request done */ + +/* + * Adapter temporary configuration structure + * + * This structure can be discarded after initialization. Don't add + * fields here needed after initialization. + * + * Field naming convention: + * + * *_enable indicates the field enables or disables a feature. The + * value of the field is never reset. + */ +typedef struct adv_dvc_cfg { + ushort disc_enable; /* enable disconnection */ + uchar chip_version; /* chip version */ + uchar termination; /* Term. Ctrl. bits 6-5 of SCSI_CFG1 register */ + ushort pci_device_id; /* PCI device code number */ + ushort lib_version; /* Adv Library version number */ + ushort control_flag; /* Microcode Control Flag */ + ushort mcode_date; /* Microcode date */ + ushort mcode_version; /* Microcode version */ + ushort pci_slot_info; /* high byte device/function number */ + /* bits 7-3 device num., bits 2-0 function num. */ + /* low byte bus num. */ + ushort bios_boot_wait; /* BIOS boot time delay */ + ushort serial1; /* EEPROM serial number word 1 */ + ushort serial2; /* EEPROM serial number word 2 */ + ushort serial3; /* EEPROM serial number word 3 */ +} ADV_DVC_CFG; + +/* + * Adapter operation variable structure. + * + * One structure is required per host adapter. + * + * Field naming convention: + * + * *_able indicates both whether a feature should be enabled or disabled + * and whether a device isi capable of the feature. At initialization + * this field may be set, but later if a device is found to be incapable + * of the feature, the field is cleared. + */ +typedef struct adv_dvc_var { + AdvPortAddr iop_base; /* I/O port address */ + ushort err_code; /* fatal error code */ + ushort bios_ctrl; /* BIOS control word, EEPROM word 12 */ + Ptr2Func isr_callback; /* pointer to function, called in AdvISR() */ + Ptr2Func sbreset_callback; /* pointer to function, called in AdvISR() */ + ushort wdtr_able; /* try WDTR for a device */ + ushort sdtr_able; /* try SDTR for a device */ + ushort ultra_able; /* try SDTR Ultra speed for a device */ + ushort tagqng_able; /* try tagged queuing with a device */ + uchar max_dvc_qng; /* maximum number of tagged commands per device */ + ushort start_motor; /* start motor command allowed */ + uchar scsi_reset_wait; /* delay in seconds after scsi bus reset */ + uchar chip_no; /* should be assigned by caller */ + uchar max_host_qng; /* maximum number of Q'ed command allowed */ + uchar cur_host_qng; /* total number of queue command */ + uchar irq_no; /* IRQ number */ + ushort no_scam; /* scam_tolerant of EEPROM */ + ushort idle_cmd_done; /* microcode idle command done set by AdvISR() */ + ulong drv_ptr; /* driver pointer to private structure */ + uchar chip_scsi_id; /* chip SCSI target ID */ + /* + * Note: The following fields will not be used after initialization. The + * driver may discard the buffer after initialization is done. + */ + ADV_DVC_CFG *cfg; /* temporary configuration structure */ +} ADV_DVC_VAR; + +#define NO_OF_SG_PER_BLOCK 15 + +typedef struct asc_sg_block { + uchar reserved1; + uchar reserved2; + uchar first_entry_no; /* starting entry number */ + uchar last_entry_no; /* last entry number */ + struct asc_sg_block *sg_ptr; /* links to the next sg block */ + struct { + ulong sg_addr; /* SG element address */ + ulong sg_count; /* SG element count */ + } sg_list[NO_OF_SG_PER_BLOCK]; +} ADV_SG_BLOCK; + +/* + * ASC_SCSI_REQ_Q - microcode request structure + * + * All fields in this structure up to byte 60 are used by the microcode. + * The microcode makes assumptions about the size and ordering of fields + * in this structure. Do not change the structure definition here without + * coordinating the change with the microcode. + */ +typedef struct adv_scsi_req_q { + uchar cntl; /* Ucode flags and state (ASC_MC_QC_*). */ + uchar sg_entry_cnt; /* SG element count. Zero for no SG. */ + uchar target_id; /* Device target identifier. */ + uchar target_lun; /* Device target logical unit number. */ + ulong data_addr; /* Data buffer physical address. */ + ulong data_cnt; /* Data count. Ucode sets to residual. */ + ulong sense_addr; /* Sense buffer physical address. */ + ulong srb_ptr; /* Driver request pointer. */ + uchar a_flag; /* Adv Library flag field. */ + uchar sense_len; /* Auto-sense length. Ucode sets to residual. */ + uchar cdb_len; /* SCSI CDB length. */ + uchar tag_code; /* SCSI-2 Tag Queue Code: 00, 20-22. */ + uchar done_status; /* Completion status. */ + uchar scsi_status; /* SCSI status byte. */ + uchar host_status; /* Ucode host status. */ + uchar ux_sg_ix; /* Ucode working SG variable. */ + uchar cdb[12]; /* SCSI command block. */ + ulong sg_real_addr; /* SG list physical address. */ + struct adv_scsi_req_q *free_scsiq_link; + ulong ux_wk_data_cnt; /* Saved data count at disconnection. */ + struct adv_scsi_req_q *scsiq_ptr; + ADV_SG_BLOCK *sg_list_ptr; /* SG list virtual address. */ + /* + * End of microcode structure - 60 bytes. The rest of the structure + * is used by the Adv Library and ignored by the microcode. + */ + ulong vsense_addr; /* Sense buffer virtual address. */ + ulong vdata_addr; /* Data buffer virtual address. */ + uchar orig_sense_len; /* Original length of sense buffer. */ +} ADV_SCSI_REQ_Q; /* BIOS - 70 bytes, DOS - 76 bytes, W95, WNT - 69 bytes */ + +/* + * Microcode idle loop commands + */ +#define IDLE_CMD_COMPLETED 0 +#define IDLE_CMD_STOP_CHIP 0x0001 +#define IDLE_CMD_STOP_CHIP_SEND_INT 0x0002 +#define IDLE_CMD_SEND_INT 0x0004 +#define IDLE_CMD_ABORT 0x0008 +#define IDLE_CMD_DEVICE_RESET 0x0010 +#define IDLE_CMD_SCSI_RESET 0x0020 + +/* + * AdvSendIdleCmd() flag definitions. + */ +#define ADV_NOWAIT 0x01 + +/* + * Wait loop time out values. + */ +#define SCSI_WAIT_10_SEC 10 /* 10 seconds */ +#define SCSI_MS_PER_SEC 1000 /* milliseconds per second */ + +/* + * Device drivers must define the following functions. + */ +STATIC int DvcEnterCritical(void); +STATIC void DvcLeaveCritical(int); +STATIC void DvcSleepMilliSecond(ulong); +STATIC uchar DvcAdvReadPCIConfigByte(ADV_DVC_VAR *, ushort); +STATIC void DvcAdvWritePCIConfigByte(ADV_DVC_VAR *, ushort, uchar); +STATIC ulong DvcGetPhyAddr(ADV_DVC_VAR *, ADV_SCSI_REQ_Q *, + uchar *, long *, int); +STATIC void DvcDelayMicroSecond(ADV_DVC_VAR *, ushort); + +/* + * Adv Library functions available to drivers. + */ +STATIC int AdvExeScsiQueue(ADV_DVC_VAR *, + ADV_SCSI_REQ_Q *); +STATIC int AdvISR(ADV_DVC_VAR *); +STATIC int AdvInitGetConfig(ADV_DVC_VAR *); +STATIC int AdvInitAsc3550Driver(ADV_DVC_VAR *); +STATIC int AdvResetSB(ADV_DVC_VAR *); + +/* + * Internal Adv Library functions. + */ +STATIC int AdvSendIdleCmd(ADV_DVC_VAR *, ushort, ulong, int); +STATIC void AdvResetChip(ADV_DVC_VAR *); +STATIC int AdvSendScsiCmd(ADV_DVC_VAR *, ADV_SCSI_REQ_Q *); +STATIC void AdvInquiryHandling(ADV_DVC_VAR *, ADV_SCSI_REQ_Q *); +STATIC int AdvInitFromEEP(ADV_DVC_VAR *); +STATIC ushort AdvGetEEPConfig(AdvPortAddr, ADVEEP_CONFIG *); +STATIC void AdvSetEEPConfig(AdvPortAddr, ADVEEP_CONFIG *); +STATIC void AdvWaitEEPCmd(AdvPortAddr); +STATIC ushort AdvReadEEPWord(AdvPortAddr, int); +STATIC void AdvResetSCSIBus(ADV_DVC_VAR *); + +/* + * PCI Bus Definitions + */ +#define AscPCICmdRegBits_BusMastering 0x0007 +#define AscPCICmdRegBits_ParErrRespCtrl 0x0040 + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) + +/* Read byte from a register. */ +#define AdvReadByteRegister(iop_base, reg_off) \ + (inp((iop_base) + (reg_off))) + +/* Write byte to a register. */ +#define AdvWriteByteRegister(iop_base, reg_off, byte) \ + (outp((iop_base) + (reg_off), (byte))) + +/* Read word (2 bytes) from a register. */ +#define AdvReadWordRegister(iop_base, reg_off) \ + (inpw((iop_base) + (reg_off))) + +/* Write word (2 bytes) to a register. */ +#define AdvWriteWordRegister(iop_base, reg_off, word) \ + (outpw((iop_base) + (reg_off), (word))) + +/* Read byte from LRAM. */ +#define AdvReadByteLram(iop_base, addr, byte) \ +do { \ + outpw((iop_base) + IOPW_RAM_ADDR, (addr)); \ + (byte) = inp((iop_base) + IOPB_RAM_DATA); \ +} while (0) + +/* Write byte to LRAM. */ +#define AdvWriteByteLram(iop_base, addr, byte) \ + (outpw((iop_base) + IOPW_RAM_ADDR, (addr)), \ + outp((iop_base) + IOPB_RAM_DATA, (byte))) + +/* Read word (2 bytes) from LRAM. */ +#define AdvReadWordLram(iop_base, addr, word) \ +do { \ + outpw((iop_base) + IOPW_RAM_ADDR, (addr)); \ + (word) = inpw((iop_base) + IOPW_RAM_DATA); \ +} while (0) + +/* Write word (2 bytes) to LRAM. */ +#define AdvWriteWordLram(iop_base, addr, word) \ + (outpw((iop_base) + IOPW_RAM_ADDR, (addr)), \ + outpw((iop_base) + IOPW_RAM_DATA, (word))) + +/* Write double word (4 bytes) to LRAM */ +/* Because of unspecified C language ordering don't use auto-increment. */ +#define AdvWriteDWordLram(iop_base, addr, dword) \ + ((outpw((iop_base) + IOPW_RAM_ADDR, (addr)), \ + outpw((iop_base) + IOPW_RAM_DATA, (ushort) ((dword) & 0xFFFF))), \ + (outpw((iop_base) + IOPW_RAM_ADDR, (addr) + 2), \ + outpw((iop_base) + IOPW_RAM_DATA, (ushort) ((dword >> 16) & 0xFFFF)))) + +/* Read word (2 bytes) from LRAM assuming that the address is already set. */ +#define AdvReadWordAutoIncLram(iop_base) \ + (inpw((iop_base) + IOPW_RAM_DATA)) + +/* Write word (2 bytes) to LRAM assuming that the address is already set. */ +#define AdvWriteWordAutoIncLram(iop_base, word) \ + (outpw((iop_base) + IOPW_RAM_DATA, (word))) + +#else /* version >= v1,3,0 */ + +/* Read byte from a register. */ +#define AdvReadByteRegister(iop_base, reg_off) \ + (ADV_MEM_READB((iop_base) + (reg_off))) + +/* Write byte to a register. */ +#define AdvWriteByteRegister(iop_base, reg_off, byte) \ + (ADV_MEM_WRITEB((iop_base) + (reg_off), (byte))) + +/* Read word (2 bytes) from a register. */ +#define AdvReadWordRegister(iop_base, reg_off) \ + (ADV_MEM_READW((iop_base) + (reg_off))) + +/* Write word (2 bytes) to a register. */ +#define AdvWriteWordRegister(iop_base, reg_off, word) \ + (ADV_MEM_WRITEW((iop_base) + (reg_off), (word))) + +/* Read byte from LRAM. */ +#define AdvReadByteLram(iop_base, addr, byte) \ +do { \ + ADV_MEM_WRITEW((iop_base) + IOPW_RAM_ADDR, (addr)); \ + (byte) = ADV_MEM_READB((iop_base) + IOPB_RAM_DATA); \ +} while (0) + +/* Write byte to LRAM. */ +#define AdvWriteByteLram(iop_base, addr, byte) \ + (ADV_MEM_WRITEW((iop_base) + IOPW_RAM_ADDR, (addr)), \ + ADV_MEM_WRITEB((iop_base) + IOPB_RAM_DATA, (byte))) + +/* Read word (2 bytes) from LRAM. */ +#define AdvReadWordLram(iop_base, addr, word) \ +do { \ + ADV_MEM_WRITEW((iop_base) + IOPW_RAM_ADDR, (addr)); \ + (word) = ADV_MEM_READW((iop_base) + IOPW_RAM_DATA); \ +} while (0) + +/* Write word (2 bytes) to LRAM. */ +#define AdvWriteWordLram(iop_base, addr, word) \ + (ADV_MEM_WRITEW((iop_base) + IOPW_RAM_ADDR, (addr)), \ + ADV_MEM_WRITEW((iop_base) + IOPW_RAM_DATA, (word))) + +/* Write double word (4 bytes) to LRAM */ +/* Because of unspecified C language ordering don't use auto-increment. */ +#define AdvWriteDWordLram(iop_base, addr, dword) \ + ((ADV_MEM_WRITEW((iop_base) + IOPW_RAM_ADDR, (addr)), \ + ADV_MEM_WRITEW((iop_base) + IOPW_RAM_DATA, \ + (ushort) ((dword) & 0xFFFF))), \ + (ADV_MEM_WRITEW((iop_base) + IOPW_RAM_ADDR, (addr) + 2), \ + ADV_MEM_WRITEW((iop_base) + IOPW_RAM_DATA, \ + (ushort) ((dword >> 16) & 0xFFFF)))) + +/* Read word (2 bytes) from LRAM assuming that the address is already set. */ +#define AdvReadWordAutoIncLram(iop_base) \ + (ADV_MEM_READW((iop_base) + IOPW_RAM_DATA)) + +/* Write word (2 bytes) to LRAM assuming that the address is already set. */ +#define AdvWriteWordAutoIncLram(iop_base, word) \ + (ADV_MEM_WRITEW((iop_base) + IOPW_RAM_DATA, (word))) + +#endif /* version >= v1,3,0 */ + +/* + * Define macro to check for Condor signature. + * + * Evaluate to ADV_TRUE if a Condor chip is found the specified port + * address 'iop_base'. Otherwise evalue to ADV_FALSE. + */ +#define AdvFindSignature(iop_base) \ + (((AdvReadByteRegister((iop_base), IOPB_CHIP_ID_1) == \ + ADV_CHIP_ID_BYTE) && \ + (AdvReadWordRegister((iop_base), IOPW_CHIP_ID_0) == \ + ADV_CHIP_ID_WORD)) ? ADV_TRUE : ADV_FALSE) + +/* + * Define macro to Return the version number of the chip at 'iop_base'. + * + * The second parameter 'bus_type' is currently unused. + */ +#define AdvGetChipVersion(iop_base, bus_type) \ + AdvReadByteRegister((iop_base), IOPB_CHIP_TYPE_REV) + +/* + * Abort an SRB in the chip's RISC Memory. The 'srb_ptr' argument must + * match the ASC_SCSI_REQ_Q 'srb_ptr' field. + * + * If the request has not yet been sent to the device it will simply be + * aborted from RISC memory. If the request is disconnected it will be + * aborted on reselection by sending an Abort Message to the target ID. + * + * Return value: + * ADV_TRUE(1) - Queue was successfully aborted. + * ADV_FALSE(0) - Queue was not found on the active queue list. + */ +#define AdvAbortSRB(asc_dvc, srb_ptr) \ + AdvSendIdleCmd((asc_dvc), (ushort) IDLE_CMD_ABORT, \ + (ulong) (srb_ptr), 0) + +/* + * Send a Bus Device Reset Message to the specified target ID. + * + * All outstanding commands will be purged if sending the + * Bus Device Reset Message is successful. + * + * Return Value: + * ADV_TRUE(1) - All requests on the target are purged. + * ADV_FALSE(0) - Couldn't issue Bus Device Reset Message; Requests + * are not purged. + */ +#define AdvResetDevice(asc_dvc, target_id) \ + AdvSendIdleCmd((asc_dvc), (ushort) IDLE_CMD_DEVICE_RESET, \ + (ulong) (target_id), 0) + +/* + * SCSI Wide Type definition. + */ +#define ADV_SCSI_BIT_ID_TYPE ushort + +/* + * AdvInitScsiTarget() 'cntl_flag' options. + */ +#define ADV_SCAN_LUN 0x01 +#define ADV_CAPINFO_NOLUN 0x02 + +/* + * Convert target id to target id bit mask. + */ +#define ADV_TID_TO_TIDMASK(tid) (0x01 << ((tid) & ADV_MAX_TID)) + +/* + * ASC_SCSI_REQ_Q 'done_status' and 'host_status' return values. + */ + +#define QD_NO_STATUS 0x00 /* Request not completed yet. */ +#define QD_NO_ERROR 0x01 +#define QD_ABORTED_BY_HOST 0x02 +#define QD_WITH_ERROR 0x04 + +#define QHSTA_NO_ERROR 0x00 +#define QHSTA_M_SEL_TIMEOUT 0x11 +#define QHSTA_M_DATA_OVER_RUN 0x12 +#define QHSTA_M_UNEXPECTED_BUS_FREE 0x13 +#define QHSTA_M_QUEUE_ABORTED 0x15 +#define QHSTA_M_SXFR_SDMA_ERR 0x16 /* SXFR_STATUS SCSI DMA Error */ +#define QHSTA_M_SXFR_SXFR_PERR 0x17 /* SXFR_STATUS SCSI Bus Parity Error */ +#define QHSTA_M_RDMA_PERR 0x18 /* RISC PCI DMA parity error */ +#define QHSTA_M_SXFR_OFF_UFLW 0x19 /* SXFR_STATUS Offset Underflow */ +#define QHSTA_M_SXFR_OFF_OFLW 0x20 /* SXFR_STATUS Offset Overflow */ +#define QHSTA_M_SXFR_WD_TMO 0x21 /* SXFR_STATUS Watchdog Timeout */ +#define QHSTA_M_SXFR_DESELECTED 0x22 /* SXFR_STATUS Deselected */ +/* Note: QHSTA_M_SXFR_XFR_OFLW is identical to QHSTA_M_DATA_OVER_RUN. */ +#define QHSTA_M_SXFR_XFR_OFLW 0x12 /* SXFR_STATUS Transfer Overflow */ +#define QHSTA_M_SXFR_XFR_PH_ERR 0x24 /* SXFR_STATUS Transfer Phase Error */ +#define QHSTA_M_SXFR_UNKNOWN_ERROR 0x25 /* SXFR_STATUS Unknown Error */ +#define QHSTA_M_WTM_TIMEOUT 0x41 +#define QHSTA_M_BAD_CMPL_STATUS_IN 0x42 +#define QHSTA_M_NO_AUTO_REQ_SENSE 0x43 +#define QHSTA_M_AUTO_REQ_SENSE_FAIL 0x44 +#define QHSTA_M_INVALID_DEVICE 0x45 /* Bad target ID */ + +typedef int (* ADV_ISR_CALLBACK) + (ADV_DVC_VAR *, ADV_SCSI_REQ_Q *); + +typedef int (* ADV_SBRESET_CALLBACK) + (ADV_DVC_VAR *); + +/* + * Default EEPROM Configuration structure defined in a_init.c. + */ +extern ADVEEP_CONFIG Default_EEPROM_Config; + +/* + * DvcGetPhyAddr() flag arguments + */ +#define ADV_IS_SCSIQ_FLAG 0x01 /* 'addr' is ASC_SCSI_REQ_Q pointer */ +#define ADV_ASCGETSGLIST_VADDR 0x02 /* 'addr' is AscGetSGList() virtual addr */ +#define ADV_IS_SENSE_FLAG 0x04 /* 'addr' is sense virtual pointer */ +#define ADV_IS_DATA_FLAG 0x08 /* 'addr' is data virtual pointer */ +#define ADV_IS_SGLIST_FLAG 0x10 /* 'addr' is sglist virtual pointer */ + +/* Return the address that is aligned at the next doubleword >= to 'addr'. */ +#define ADV_DWALIGN(addr) (((ulong) (addr) + 0x3) & ~0x3) + +/* + * Total contiguous memory needed for driver SG blocks. + * + * ADV_MAX_SG_LIST must be defined by a driver. It is the maximum + * number of scatter-gather elements the driver supports in a + * single request. + */ + +#ifndef ADV_MAX_SG_LIST +Forced Error: Driver must define ADV_MAX_SG_LIST. +#endif /* ADV_MAX_SG_LIST */ + +#define ADV_SG_LIST_MAX_BYTE_SIZE \ + (sizeof(ADV_SG_BLOCK) * \ + ((ADV_MAX_SG_LIST + (NO_OF_SG_PER_BLOCK - 1))/NO_OF_SG_PER_BLOCK)) + +/* + * A driver may optionally define the assertion macro ADV_ASSERT() in + * its d_os_dep.h file. If the macro has not already been defined, + * then define the macro to a no-op. + */ +#ifndef ADV_ASSERT +#define ADV_ASSERT(a) +#endif /* ADV_ASSERT */ + + +/* + * --- Driver Constants and Macros + */ + +#define ASC_NUM_BOARD_SUPPORTED 16 +#define ASC_NUM_IOPORT_PROBE 4 +#define ASC_NUM_BUS 4 + +/* Reference Scsi_Host hostdata */ +#define ASC_BOARDP(host) ((asc_board_t *) &((host)->hostdata)) + +/* asc_board_t flags */ +#define ASC_HOST_IN_RESET 0x01 +#define ASC_HOST_IN_ABORT 0x02 +#define ASC_IS_WIDE_BOARD 0x04 /* AdvanSys Wide Board */ +#define ASC_SELECT_QUEUE_DEPTHS 0x08 + +#define ASC_NARROW_BOARD(boardp) (((boardp)->flags & ASC_IS_WIDE_BOARD) == 0) +#define ASC_WIDE_BOARD(boardp) ((boardp)->flags & ASC_IS_WIDE_BOARD) + +#define NO_ISA_DMA 0xff /* No ISA DMA Channel Used */ + +/* + * If the Linux kernel version supports freeing initialization code + * and data after loading, define macros for this purpose. These macros + * are not used when the driver is built as a module, cf. linux/init.h. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,23) +#define ASC_INITFUNC(func) func +#define ASC_INITDATA +#define ASC_INIT +#else /* version >= v2.1.23 */ +#define ASC_INITFUNC(func) __initfunc(func) +#define ASC_INITDATA __initdata +#define ASC_INIT __init +#endif /* version >= v2.1.23 */ + +#define ASC_INFO_SIZE 128 /* advansys_info() line size */ + +/* /proc/scsi/advansys/[0...] related definitions */ +#define ASC_PRTBUF_SIZE 2048 +#define ASC_PRTLINE_SIZE 160 + +#define ASC_PRT_NEXT() \ + if (cp) { \ + totlen += len; \ + leftlen -= len; \ + if (leftlen == 0) { \ + return totlen; \ + } \ + cp += len; \ + } + +#define ASC_MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/* Asc Library return codes */ +#define ASC_TRUE 1 +#define ASC_FALSE 0 +#define ASC_NOERROR 1 +#define ASC_BUSY 0 +#define ASC_ERROR (-1) + +/* Scsi_Cmnd function return codes */ +#define STATUS_BYTE(byte) (byte) +#define MSG_BYTE(byte) ((byte) << 8) +#define HOST_BYTE(byte) ((byte) << 16) +#define DRIVER_BYTE(byte) ((byte) << 24) + +/* + * The following definitions and macros are OS independent interfaces to + * the queue functions: + * REQ - SCSI request structure + * REQP - pointer to SCSI request structure + * REQPTID(reqp) - reqp's target id + * REQPNEXT(reqp) - reqp's next pointer + * REQPNEXTP(reqp) - pointer to reqp's next pointer + * REQPTIME(reqp) - reqp's time stamp value + * REQTIMESTAMP() - system time stamp value + */ +typedef Scsi_Cmnd REQ, *REQP; +#define REQPNEXT(reqp) ((REQP) ((reqp)->host_scribble)) +#define REQPNEXTP(reqp) ((REQP *) &((reqp)->host_scribble)) +#define REQPTID(reqp) ((reqp)->target) +#define REQPTIME(reqp) ((reqp)->SCp.this_residual) +#define REQTIMESTAMP() (jiffies) + +#define REQTIMESTAT(function, ascq, reqp, tid) \ +{ \ + /* + * If the request time stamp is less than the system time stamp, then \ + * maybe the system time stamp wrapped. Set the request time to zero.\ + */ \ + if (REQPTIME(reqp) <= REQTIMESTAMP()) { \ + REQPTIME(reqp) = REQTIMESTAMP() - REQPTIME(reqp); \ + } else { \ + /* Indicate an error occurred with the assertion. */ \ + ASC_ASSERT(REQPTIME(reqp) <= REQTIMESTAMP()); \ + REQPTIME(reqp) = 0; \ + } \ + /* Handle first minimum time case without external initialization. */ \ + if (((ascq)->q_tot_cnt[tid] == 1) || \ + (REQPTIME(reqp) < (ascq)->q_min_tim[tid])) { \ + (ascq)->q_min_tim[tid] = REQPTIME(reqp); \ + ASC_DBG3(1, "%s: new q_min_tim[%d] %u\n", \ + (function), (tid), (ascq)->q_min_tim[tid]); \ + } \ + if (REQPTIME(reqp) > (ascq)->q_max_tim[tid]) { \ + (ascq)->q_max_tim[tid] = REQPTIME(reqp); \ + ASC_DBG3(1, "%s: new q_max_tim[%d] %u\n", \ + (function), tid, (ascq)->q_max_tim[tid]); \ + } \ + (ascq)->q_tot_tim[tid] += REQPTIME(reqp); \ + /* Reset the time stamp field. */ \ + REQPTIME(reqp) = 0; \ +} + +/* asc_enqueue() flags */ +#define ASC_FRONT 1 +#define ASC_BACK 2 + +/* asc_dequeue_list() argument */ +#define ASC_TID_ALL (-1) + +/* Return non-zero, if the queue is empty. */ +#define ASC_QUEUE_EMPTY(ascq) ((ascq)->q_tidmask == 0) + +/* PCI configuration declarations */ + +#define PCI_BASE_CLASS_PREDEFINED 0x00 +#define PCI_BASE_CLASS_MASS_STORAGE 0x01 +#define PCI_BASE_CLASS_NETWORK 0x02 +#define PCI_BASE_CLASS_DISPLAY 0x03 +#define PCI_BASE_CLASS_MULTIMEDIA 0x04 +#define PCI_BASE_CLASS_MEMORY_CONTROLLER 0x05 +#define PCI_BASE_CLASS_BRIDGE_DEVICE 0x06 + +/* MASS STORAGE */ +#define PCI_SUB_CLASS_SCSI_CONTROLLER 0x00 +#define PCI_SUB_CLASS_IDE_CONTROLLER 0x01 +#define PCI_SUB_CLASS_FLOPPY_DISK_CONTROLLER 0x02 +#define PCI_SUB_CLASS_IPI_BUS_CONTROLLER 0x03 +#define PCI_SUB_CLASS_OTHER_MASS_CONTROLLER 0x80 + +/* NETWORK CONTROLLER */ +#define PCI_SUB_CLASS_ETHERNET_CONTROLLER 0x00 +#define PCI_SUB_CLASS_TOKEN_RING_CONTROLLER 0x01 +#define PCI_SUB_CLASS_FDDI_CONTROLLER 0x02 +#define PCI_SUB_CLASS_OTHER_NETWORK_CONTROLLER 0x80 + +/* DISPLAY CONTROLLER */ +#define PCI_SUB_CLASS_VGA_CONTROLLER 0x00 +#define PCI_SUB_CLASS_XGA_CONTROLLER 0x01 +#define PCI_SUB_CLASS_OTHER_DISPLAY_CONTROLLER 0x80 + +/* MULTIMEDIA CONTROLLER */ +#define PCI_SUB_CLASS_VIDEO_DEVICE 0x00 +#define PCI_SUB_CLASS_AUDIO_DEVICE 0x01 +#define PCI_SUB_CLASS_OTHER_MULTIMEDIA_DEVICE 0x80 + +/* MEMORY CONTROLLER */ +#define PCI_SUB_CLASS_RAM_CONTROLLER 0x00 +#define PCI_SUB_CLASS_FLASH_CONTROLLER 0x01 +#define PCI_SUB_CLASS_OTHER_MEMORY_CONTROLLER 0x80 + +/* BRIDGE CONTROLLER */ +#define PCI_SUB_CLASS_HOST_BRIDGE_CONTROLLER 0x00 +#define PCI_SUB_CLASS_ISA_BRIDGE_CONTROLLER 0x01 +#define PCI_SUB_CLASS_EISA_BRIDGE_CONTROLLER 0x02 +#define PCI_SUB_CLASS_MC_BRIDGE_CONTROLLER 0x03 +#define PCI_SUB_CLASS_PCI_TO_PCI_BRIDGE_CONTROLLER 0x04 +#define PCI_SUB_CLASS_PCMCIA_BRIDGE_CONTROLLER 0x05 +#define PCI_SUB_CLASS_OTHER_BRIDGE_CONTROLLER 0x80 + +#define PCI_MAX_SLOT 0x1F +#define PCI_MAX_BUS 0xFF +#define PCI_IOADDRESS_MASK 0xFFFE +#define ASC_PCI_VENDORID 0x10CD +#define ASC_PCI_DEVICE_ID_CNT 4 /* PCI Device ID count. */ +#define ASC_PCI_DEVICE_ID_1100 0x1100 +#define ASC_PCI_DEVICE_ID_1200 0x1200 +#define ASC_PCI_DEVICE_ID_1300 0x1300 +#define ASC_PCI_DEVICE_ID_2300 0x2300 + +/* PCI IO Port Addresses to generate special cycle */ + +#define PCI_CONFIG_ADDRESS_MECH1 0x0CF8 +#define PCI_CONFIG_DATA_MECH1 0x0CFC + +#define PCI_CONFIG_FORWARD_REGISTER 0x0CFA /* 0=type 0; 1=type 1; */ + +#define PCI_CONFIG_BUS_NUMBER_MASK 0x00FF0000 +#define PCI_CONFIG_DEVICE_FUNCTION_MASK 0x0000FF00 +#define PCI_CONFIG_REGISTER_NUMBER_MASK 0x000000F8 + +#define PCI_DEVICE_FOUND 0x0000 +#define PCI_DEVICE_NOT_FOUND 0xffff + +#define SUBCLASS_OFFSET 0x0A +#define CLASSCODE_OFFSET 0x0B +#define VENDORID_OFFSET 0x00 +#define DEVICEID_OFFSET 0x02 + +#ifndef ADVANSYS_STATS +#define ASC_STATS(shp, counter) +#define ASC_STATS_ADD(shp, counter, count) +#else /* ADVANSYS_STATS */ +#define ASC_STATS(shp, counter) \ + (ASC_BOARDP(shp)->asc_stats.counter++) + +#define ASC_STATS_ADD(shp, counter, count) \ + (ASC_BOARDP(shp)->asc_stats.counter += (count)) +#endif /* ADVANSYS_STATS */ + +#define ASC_CEILING(val, unit) (((val) + ((unit) - 1))/(unit)) + +/* If the result wraps when calculating tenths, return 0. */ +#define ASC_TENTHS(num, den) \ + (((10 * ((num)/(den))) > (((num) * 10)/(den))) ? \ + 0 : ((((num) * 10)/(den)) - (10 * ((num)/(den))))) + +/* + * Display a message to the console. + */ +#define ASC_PRINT(s) \ + { \ + printk("advansys: "); \ + printk(s); \ + } + +#define ASC_PRINT1(s, a1) \ + { \ + printk("advansys: "); \ + printk((s), (a1)); \ + } + +#define ASC_PRINT2(s, a1, a2) \ + { \ + printk("advansys: "); \ + printk((s), (a1), (a2)); \ + } + +#define ASC_PRINT3(s, a1, a2, a3) \ + { \ + printk("advansys: "); \ + printk((s), (a1), (a2), (a3)); \ + } + +#define ASC_PRINT4(s, a1, a2, a3, a4) \ + { \ + printk("advansys: "); \ + printk((s), (a1), (a2), (a3), (a4)); \ + } + + +#ifndef ADVANSYS_DEBUG + +#define ASC_DBG(lvl, s) +#define ASC_DBG1(lvl, s, a1) +#define ASC_DBG2(lvl, s, a1, a2) +#define ASC_DBG3(lvl, s, a1, a2, a3) +#define ASC_DBG4(lvl, s, a1, a2, a3, a4) +#define ASC_DBG_PRT_SCSI_HOST(lvl, s) +#define ASC_DBG_PRT_SCSI_CMND(lvl, s) +#define ASC_DBG_PRT_ASC_SCSI_Q(lvl, scsiqp) +#define ASC_DBG_PRT_ADV_SCSI_REQ_Q(lvl, scsiqp) +#define ASC_DBG_PRT_ASC_QDONE_INFO(lvl, qdone) +#define ADV_DBG_PRT_ADV_SCSI_REQ_Q(lvl, scsiqp) +#define ASC_DBG_PRT_HEX(lvl, name, start, length) +#define ASC_DBG_PRT_CDB(lvl, cdb, len) +#define ASC_DBG_PRT_SENSE(lvl, sense, len) +#define ASC_DBG_PRT_INQUIRY(lvl, inq, len) + +#else /* ADVANSYS_DEBUG */ + +/* + * Debugging Message Levels: + * 0: Errors Only + * 1: High-Level Tracing + * 2-N: Verbose Tracing + */ + +#define ASC_DBG(lvl, s) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + printk(s); \ + } \ + } + +#define ASC_DBG1(lvl, s, a1) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + printk((s), (a1)); \ + } \ + } + +#define ASC_DBG2(lvl, s, a1, a2) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + printk((s), (a1), (a2)); \ + } \ + } + +#define ASC_DBG3(lvl, s, a1, a2, a3) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + printk((s), (a1), (a2), (a3)); \ + } \ + } + +#define ASC_DBG4(lvl, s, a1, a2, a3, a4) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + printk((s), (a1), (a2), (a3), (a4)); \ + } \ + } + +#define ASC_DBG_PRT_SCSI_HOST(lvl, s) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + asc_prt_scsi_host(s); \ + } \ + } + +#define ASC_DBG_PRT_SCSI_CMND(lvl, s) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + asc_prt_scsi_cmnd(s); \ + } \ + } + +#define ASC_DBG_PRT_ASC_SCSI_Q(lvl, scsiqp) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + asc_prt_asc_scsi_q(scsiqp); \ + } \ + } + +#define ASC_DBG_PRT_ASC_QDONE_INFO(lvl, qdone) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + asc_prt_asc_qdone_info(qdone); \ + } \ + } + +#define ASC_DBG_PRT_ADV_SCSI_REQ_Q(lvl, scsiqp) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + asc_prt_adv_scsi_req_q(scsiqp); \ + } \ + } + +#define ASC_DBG_PRT_HEX(lvl, name, start, length) \ + { \ + if (asc_dbglvl >= (lvl)) { \ + asc_prt_hex((name), (start), (length)); \ + } \ + } + +#define ASC_DBG_PRT_CDB(lvl, cdb, len) \ + ASC_DBG_PRT_HEX((lvl), "CDB", (uchar *) (cdb), (len)); + +#define ASC_DBG_PRT_SENSE(lvl, sense, len) \ + ASC_DBG_PRT_HEX((lvl), "SENSE", (uchar *) (sense), (len)); + +#define ASC_DBG_PRT_INQUIRY(lvl, inq, len) \ + ASC_DBG_PRT_HEX((lvl), "INQUIRY", (uchar *) (inq), (len)); +#endif /* ADVANSYS_DEBUG */ + +#ifndef ADVANSYS_ASSERT +#define ASC_ASSERT(a) +#else /* ADVANSYS_ASSERT */ + +#define ASC_ASSERT(a) \ + { \ + if (!(a)) { \ + printk("ASC_ASSERT() Failure: file %s, line %d\n", \ + __FILE__, __LINE__); \ + } \ + } + +#endif /* ADVANSYS_ASSERT */ + + +/* + * --- Driver Structures + */ + +#ifdef ADVANSYS_STATS + +/* Per board statistics structure */ +struct asc_stats { + /* Driver Entrypoint Statistics */ + ulong command; /* # calls to advansys_command() */ + ulong queuecommand; /* # calls to advansys_queuecommand() */ + ulong abort; /* # calls to advansys_abort() */ + ulong reset; /* # calls to advansys_reset() */ + ulong biosparam; /* # calls to advansys_biosparam() */ + ulong interrupt; /* # advansys_interrupt() calls */ + ulong callback; /* # calls to asc/adv_isr_callback() */ + ulong done; /* # calls to request's scsi_done function */ + ulong build_error; /* # asc/adv_build_req() ASC_ERROR returns. */ + ulong adv_build_noreq; /* # adv_build_req() adv_req_t alloc. fail. */ + ulong adv_build_nosg; /* # adv_build_req() adv_sgblk_t alloc. fail. */ + /* AscExeScsiQueue()/AdvExeScsiQueue() Statistics */ + ulong exe_noerror; /* # ASC_NOERROR returns. */ + ulong exe_busy; /* # ASC_BUSY returns. */ + ulong exe_error; /* # ASC_ERROR returns. */ + ulong exe_unknown; /* # unknown returns. */ + /* Data Transfer Statistics */ + ulong cont_cnt; /* # non-scatter-gather I/O requests received */ + ulong cont_xfer; /* # contiguous transfer 512-bytes */ + ulong sg_cnt; /* # scatter-gather I/O requests received */ + ulong sg_elem; /* # scatter-gather elements */ + ulong sg_xfer; /* # scatter-gather transfer 512-bytes */ +}; +#endif /* ADVANSYS_STATS */ + +/* + * Request queuing structure + */ +typedef struct asc_queue { + ADV_SCSI_BIT_ID_TYPE q_tidmask; /* queue mask */ + REQP q_first[ADV_MAX_TID+1]; /* first queued request */ + REQP q_last[ADV_MAX_TID+1]; /* last queued request */ +#ifdef ADVANSYS_STATS + short q_cur_cnt[ADV_MAX_TID+1]; /* current queue count */ + short q_max_cnt[ADV_MAX_TID+1]; /* maximum queue count */ + ulong q_tot_cnt[ADV_MAX_TID+1]; /* total enqueue count */ + ulong q_tot_tim[ADV_MAX_TID+1]; /* total time queued */ + ushort q_max_tim[ADV_MAX_TID+1]; /* maximum time queued */ + ushort q_min_tim[ADV_MAX_TID+1]; /* minimum time queued */ +#endif /* ADVANSYS_STATS */ +} asc_queue_t; + +/* + * Adv Library Request Structures + * + * The following two se structures are used to process Wide Board requests. + * One structure is needed for each command received from the Mid-Level SCSI + * driver. + * + * The ADV_SCSI_REQ_Q structure in adv_req_t is passed to the Adv Library + * and microcode with the ADV_SCSI_REQ_Q field 'srb_ptr' pointing to the + * adv_req_t. The adv_req_t structure 'cmndp' field in turn points to the + * Mid-Level SCSI request structure. + * + * The adv_sgblk_t structure is used to handle requests that include + * scatter-gather elements. + */ +typedef struct adv_sgblk { + ADV_SG_BLOCK sg_block[ADV_NUM_SG_BLOCK + ADV_NUM_PAGE_CROSSING]; + uchar align2[4]; /* Sgblock structure padding. */ + struct adv_sgblk *next_sgblkp; /* Next scatter-gather structure. */ +} adv_sgblk_t; + +typedef struct adv_req { + ADV_SCSI_REQ_Q scsi_req_q; /* Adv Library request structure. */ + uchar align1[4]; /* Request structure padding. */ + Scsi_Cmnd *cmndp; /* Mid-Level SCSI command pointer. */ + adv_sgblk_t *sgblkp; /* Adv Library scatter-gather pointer. */ + struct adv_req *next_reqp; /* Next Request Structure. */ +} adv_req_t; + +/* + * Structure allocated for each board. + * + * This structure is allocated by scsi_register() at the end + * of the 'Scsi_Host' structure starting at the 'hostdata' + * field. It is guaranteed to be allocated from DMA-able memory. + */ +typedef struct asc_board { + int id; /* Board Id */ + uint flags; /* Board flags */ + union { + ASC_DVC_VAR asc_dvc_var; /* Narrow board */ + ADV_DVC_VAR adv_dvc_var; /* Wide board */ + } dvc_var; + union { + ASC_DVC_CFG asc_dvc_cfg; /* Narrow board */ + ADV_DVC_CFG adv_dvc_cfg; /* Wide board */ + } dvc_cfg; + asc_queue_t active; /* Active command queue */ + asc_queue_t waiting; /* Waiting command queue */ + asc_queue_t done; /* Done command queue */ + ADV_SCSI_BIT_ID_TYPE init_tidmask; /* Target init./valid mask */ + Scsi_Device *device[ADV_MAX_TID+1]; /* Mid-Level Scsi Device */ + ushort reqcnt[ADV_MAX_TID+1]; /* Starvation request count */ +#if ASC_QUEUE_FLOW_CONTROL + ushort nerrcnt[ADV_MAX_TID+1]; /* No error request count */ +#endif /* ASC_QUEUE_FLOW_CONTROL */ + ADV_SCSI_BIT_ID_TYPE queue_full; /* Queue full mask */ + ushort queue_full_cnt[ADV_MAX_TID+1]; /* Queue full count */ + union { + ASCEEP_CONFIG asc_eep; /* Narrow EEPROM config. */ + ADVEEP_CONFIG adv_eep; /* Wide EEPROM config. */ + } eep_config; + ulong last_reset; /* Saved last reset time */ +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + /* /proc/scsi/advansys/[0...] */ + char *prtbuf; /* Statistics Print Buffer */ +#endif /* version >= v1.3.0 */ +#ifdef ADVANSYS_STATS + struct asc_stats asc_stats; /* Board statistics */ +#endif /* ADVANSYS_STATS */ + /* + * The following fields are used only for Narrow Boards. + */ + /* The following three structures must be in DMA-able memory. */ + ASC_SCSI_REQ_Q scsireqq; + ASC_CAP_INFO cap_info; + ASC_SCSI_INQUIRY inquiry; + uchar sdtr_data[ASC_MAX_TID+1]; /* SDTR information */ + /* + * The following fields are used only for Wide Boards. + */ + void *ioremap_addr; /* I/O Memory remap address. */ + ushort ioport; /* I/O Port address. */ + adv_req_t *orig_reqp; /* adv_req_t memory block. */ + adv_req_t *adv_reqp; /* Request structures. */ + adv_sgblk_t *orig_sgblkp; /* adv_sgblk_t memory block. */ + adv_sgblk_t *adv_sgblkp; /* Scatter-gather structures. */ + ushort bios_signature; /* BIOS Signature. */ + ushort bios_version; /* BIOS Version. */ + ushort bios_codeseg; /* BIOS Code Segment. */ + ushort bios_codelen; /* BIOS Code Segment Length. */ +} asc_board_t; + +/* + * PCI configuration structures + */ +typedef struct _PCI_DATA_ +{ + uchar type; + uchar bus; + uchar slot; + uchar func; + uchar offset; +} PCI_DATA; + +typedef struct _PCI_DEVICE_ +{ + ushort vendorID; + ushort deviceID; + ushort slotNumber; + ushort slotFound; + uchar busNumber; + uchar maxBusNumber; + uchar devFunc; + ushort startSlot; + ushort endSlot; + uchar bridge; + uchar type; +} PCI_DEVICE; + +typedef struct _PCI_CONFIG_SPACE_ +{ + ushort vendorID; + ushort deviceID; + ushort command; + ushort status; + uchar revision; + uchar classCode[3]; + uchar cacheSize; + uchar latencyTimer; + uchar headerType; + uchar bist; + ulong baseAddress[6]; + ushort reserved[4]; + ulong optionRomAddr; + ushort reserved2[4]; + uchar irqLine; + uchar irqPin; + uchar minGnt; + uchar maxLatency; +} PCI_CONFIG_SPACE; + + +/* + * --- Driver Data + */ + +/* Note: All driver global data should be initialized. */ + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) +struct proc_dir_entry proc_scsi_advansys = +{ + PROC_SCSI_ADVANSYS, /* unsigned short low_ino */ + 8, /* unsigned short namelen */ + "advansys", /* const char *name */ + S_IFDIR | S_IRUGO | S_IXUGO, /* mode_t mode */ + 2 /* nlink_t nlink */ +}; +#endif /* version >= v1.3.0 */ + +/* Number of boards detected in system. */ +STATIC int asc_board_count = 0; +STATIC struct Scsi_Host *asc_host[ASC_NUM_BOARD_SUPPORTED] = { 0 }; + +/* Overrun buffer shared between all boards. */ +STATIC uchar overrun_buf[ASC_OVERRUN_BSIZE] = { 0 }; + +/* + * Global structures required to issue a command. + */ +STATIC ASC_SCSI_Q asc_scsi_q = { { 0 } }; +STATIC ASC_SG_HEAD asc_sg_head = { 0 }; + +/* List of supported bus types. */ +STATIC ushort asc_bus[ASC_NUM_BUS] ASC_INITDATA = { + ASC_IS_ISA, + ASC_IS_VL, + ASC_IS_EISA, + ASC_IS_PCI, +}; + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI +STATIC int pci_scan_method ASC_INITDATA = -1; +#endif /* ASC_CONFIG_PCI */ +#endif /* version < v2.1.93 */ + +/* + * Used with the LILO 'advansys' option to eliminate or + * limit I/O port probing at boot time, cf. advansys_setup(). + */ +STATIC int asc_iopflag = ASC_FALSE; +STATIC int asc_ioport[ASC_NUM_IOPORT_PROBE] = { 0, 0, 0, 0 }; + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) +/* + * In kernels earlier than v1.3.0, kmalloc() does not work + * during driver initialization. Therefore statically declare + * 16 elements of each structure. v1.3.0 kernels will probably + * not need any more than this number. + */ +uchar adv_req_buf[16 * sizeof(adv_req_t)] = { 0 }; +uchar adv_sgblk_buf[16 * sizeof(adv_sgblk_t)] = { 0 }; +#endif /* version >= v1,3,0 */ + +#ifdef ADVANSYS_DEBUG +STATIC char * +asc_bus_name[ASC_NUM_BUS] = { + "ASC_IS_ISA", + "ASC_IS_VL", + "ASC_IS_EISA", + "ASC_IS_PCI", +}; + +STATIC int asc_dbglvl = 0; +#endif /* ADVANSYS_DEBUG */ + +/* Declaration for Asc Library internal data referenced by driver. */ +STATIC PortAddr _asc_def_iop_base[]; + + +/* + * --- Driver Function Prototypes + * + * advansys.h contains function prototypes for functions global to Linux. + */ + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) +STATIC int asc_proc_copy(off_t, off_t, char *, int , char *, int); +#endif /* version >= v1.3.0 */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,70) +STATIC void advansys_interrupt(int, struct pt_regs *); +#else /* version >= v1.3.70 */ +STATIC void advansys_interrupt(int, void *, struct pt_regs *); +#endif /* version >= v1.3.70 */ +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC void advansys_select_queue_depths(struct Scsi_Host *, + Scsi_Device *); +#endif /* version >= v1.3.89 */ +STATIC void advansys_command_done(Scsi_Cmnd *); +STATIC void asc_scsi_done_list(Scsi_Cmnd *); +STATIC int asc_execute_scsi_cmnd(Scsi_Cmnd *); +STATIC int asc_build_req(asc_board_t *, Scsi_Cmnd *); +STATIC int adv_build_req(asc_board_t *, Scsi_Cmnd *, ADV_SCSI_REQ_Q **); +STATIC int adv_get_sglist(ADV_DVC_VAR *, ADV_SCSI_REQ_Q *, Scsi_Cmnd *); +STATIC void asc_isr_callback(ASC_DVC_VAR *, ASC_QDONE_INFO *); +STATIC void adv_isr_callback(ADV_DVC_VAR *, ADV_SCSI_REQ_Q *); +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI +STATIC int asc_srch_pci_dev(PCI_DEVICE *); +STATIC uchar asc_scan_method(void); +STATIC int asc_pci_find_dev(PCI_DEVICE *); +STATIC void asc_get_pci_cfg(PCI_DEVICE *, PCI_CONFIG_SPACE *); +STATIC ushort asc_get_cfg_word(PCI_DATA *); +STATIC uchar asc_get_cfg_byte(PCI_DATA *); +STATIC void asc_put_cfg_byte(PCI_DATA *, uchar); +#endif /* ASC_CONFIG_PCI */ +#endif /* version < v2.1.93 */ +STATIC void asc_enqueue(asc_queue_t *, REQP, int); +STATIC REQP asc_dequeue(asc_queue_t *, int); +STATIC REQP asc_dequeue_list(asc_queue_t *, REQP *, int); +STATIC int asc_rmqueue(asc_queue_t *, REQP); +STATIC int asc_isqueued(asc_queue_t *, REQP); +STATIC void asc_execute_queue(asc_queue_t *); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) +STATIC int asc_prt_board_devices(struct Scsi_Host *, char *, int); +STATIC int asc_prt_adv_bios(struct Scsi_Host *, char *, int); +STATIC int asc_get_eeprom_string(ushort *serialnum, uchar *cp); +STATIC int asc_prt_asc_board_eeprom(struct Scsi_Host *, char *, int); +STATIC int asc_prt_adv_board_eeprom(struct Scsi_Host *, char *, int); +STATIC int asc_prt_driver_conf(struct Scsi_Host *, char *, int); +STATIC int asc_prt_asc_board_info(struct Scsi_Host *, char *, int); +STATIC int asc_prt_adv_board_info(struct Scsi_Host *, char *, int); +STATIC int asc_prt_line(char *, int, char *fmt, ...); +#endif /* version >= v1.3.0 */ + +/* Declaration for Asc Library internal functions reference by driver. */ +STATIC int AscFindSignature(PortAddr); +STATIC ushort AscGetEEPConfig(PortAddr, ASCEEP_CONFIG *, ushort); + +#ifdef ADVANSYS_STATS +STATIC int asc_prt_board_stats(struct Scsi_Host *, char *, int); +#endif /* ADVANSYS_STATS */ + +#ifdef ADVANSYS_DEBUG +STATIC void asc_prt_scsi_host(struct Scsi_Host *); +STATIC void asc_prt_scsi_cmnd(Scsi_Cmnd *); +STATIC void asc_prt_asc_dvc_cfg(ASC_DVC_CFG *); +STATIC void asc_prt_asc_dvc_var(ASC_DVC_VAR *); +STATIC void asc_prt_asc_scsi_q(ASC_SCSI_Q *); +STATIC void asc_prt_asc_qdone_info(ASC_QDONE_INFO *); +STATIC void asc_prt_adv_dvc_cfg(ADV_DVC_CFG *); +STATIC void asc_prt_adv_dvc_var(ADV_DVC_VAR *); +STATIC void asc_prt_adv_scsi_req_q(ADV_SCSI_REQ_Q *); +STATIC void asc_prt_adv_sgblock(int, ADV_SG_BLOCK *); +STATIC void asc_prt_hex(char *f, uchar *, int); +#endif /* ADVANSYS_DEBUG */ + +#ifdef ADVANSYS_ASSERT +STATIC int interrupts_enabled(void); +#endif /* ADVANSYS_ASSERT */ + + +/* + * --- Linux 'Scsi_Host_Template' and advansys_setup() Functions + */ + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) +/* + * advansys_proc_info() - /proc/scsi/advansys/[0-(ASC_NUM_BOARD_SUPPORTED-1)] + * + * *buffer: I/O buffer + * **start: if inout == FALSE pointer into buffer where user read should start + * offset: current offset into a /proc/scsi/advansys/[0...] file + * length: length of buffer + * hostno: Scsi_Host host_no + * inout: TRUE - user is writing; FALSE - user is reading + * + * Return the number of bytes read from or written to a + * /proc/scsi/advansys/[0...] file. + * + * Note: This function uses the per board buffer 'prtbuf' which is + * allocated when the board is initialized in advansys_detect(). The + * buffer is ASC_PRTBUF_SIZE bytes. The function asc_proc_copy() is + * used to write to the buffer. The way asc_proc_copy() is written + * if 'prtbuf' is too small it will not be overwritten. Instead the + * user just won't get all the available statistics. + */ +int +advansys_proc_info(char *buffer, char **start, off_t offset, int length, + int hostno, int inout) +{ + struct Scsi_Host *shp; + asc_board_t *boardp; + int i; + char *cp; + int cplen; + int cnt; + int totcnt; + int leftlen; + char *curbuf; + off_t advoffset; + Scsi_Device *scd; + + ASC_DBG(1, "advansys_proc_info: begin\n"); + + /* + * User write not supported. + */ + if (inout == TRUE) { + return(-ENOSYS); + } + + /* + * User read of /proc/scsi/advansys/[0...] file. + */ + + /* Find the specified board. */ + for (i = 0; i < asc_board_count; i++) { + if (asc_host[i]->host_no == hostno) { + break; + } + } + if (i == asc_board_count) { + return(-ENOENT); + } + + shp = asc_host[i]; + boardp = ASC_BOARDP(shp); + + /* Copy read data starting at the beginning of the buffer. */ + *start = buffer; + curbuf = buffer; + advoffset = 0; + totcnt = 0; + leftlen = length; + + /* + * Get board configuration information. + * + * advansys_info() returns the board string from its own static buffer. + */ + cp = (char *) advansys_info(shp); + strcat(cp, "\n"); + cplen = strlen(cp); + /* Copy board information. */ + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; + + /* + * Display Wide Board BIOS Information. + */ + if (ASC_WIDE_BOARD(boardp)) { + cp = boardp->prtbuf; + cplen = asc_prt_adv_bios(shp, cp, ASC_PRTBUF_SIZE); + ASC_ASSERT(cplen < ASC_PRTBUF_SIZE); + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; + } + + /* + * Display driver information for each device attached to the board. + */ + cp = boardp->prtbuf; + cplen = asc_prt_board_devices(shp, cp, ASC_PRTBUF_SIZE); + ASC_ASSERT(cplen < ASC_PRTBUF_SIZE); + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; + + /* + * Display target driver information for each device attached + * to the board. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,75) + for (scd = scsi_devices; scd; scd = scd->next) +#else /* version >= v2.1.75 */ + for (scd = shp->host_queue; scd; scd = scd->next) +#endif /* version >= v2.1.75 */ + { + if (scd->host == shp) { + cp = boardp->prtbuf; + /* + * Note: If proc_print_scsidevice() writes more than + * ASC_PRTBUF_SIZE bytes, it will overrun 'prtbuf'. + */ + proc_print_scsidevice(scd, cp, &cplen, 0); + ASC_ASSERT(cplen < ASC_PRTBUF_SIZE); + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; + } + } + + /* + * Display EEPROM configuration for the board. + */ + cp = boardp->prtbuf; + if (ASC_NARROW_BOARD(boardp)) { + cplen = asc_prt_asc_board_eeprom(shp, cp, ASC_PRTBUF_SIZE); + } else { + cplen = asc_prt_adv_board_eeprom(shp, cp, ASC_PRTBUF_SIZE); + } + ASC_ASSERT(cplen < ASC_PRTBUF_SIZE); + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; + + /* + * Display driver configuration and information for the board. + */ + cp = boardp->prtbuf; + cplen = asc_prt_driver_conf(shp, cp, ASC_PRTBUF_SIZE); + ASC_ASSERT(cplen < ASC_PRTBUF_SIZE); + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; + +#ifdef ADVANSYS_STATS + /* + * Display driver statistics for the board. + */ + cp = boardp->prtbuf; + cplen = asc_prt_board_stats(shp, cp, ASC_PRTBUF_SIZE); + ASC_ASSERT(cplen < ASC_PRTBUF_SIZE); + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; +#endif /* ADVANSYS_STATS */ + + /* + * Display Asc Library dynamic configuration information + * for the board. + */ + cp = boardp->prtbuf; + if (ASC_NARROW_BOARD(boardp)) { + cplen = asc_prt_asc_board_info(shp, cp, ASC_PRTBUF_SIZE); + } else { + cplen = asc_prt_adv_board_info(shp, cp, ASC_PRTBUF_SIZE); + } + ASC_ASSERT(cplen < ASC_PRTBUF_SIZE); + cnt = asc_proc_copy(advoffset, offset, curbuf, leftlen, cp, cplen); + totcnt += cnt; + leftlen -= cnt; + if (leftlen == 0) { + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + return totcnt; + } + advoffset += cplen; + curbuf += cnt; + + ASC_DBG1(1, "advansys_proc_info: totcnt %d\n", totcnt); + + return totcnt; +} +#endif /* version >= v1.3.0 */ + +/* + * advansys_detect() + * + * Detect function for AdvanSys adapters. + * + * Argument is a pointer to the host driver's scsi_hosts entry. + * + * Return number of adapters found. + * + * Note: Because this function is called during system initialization + * it must not call SCSI mid-level functions including scsi_malloc() + * and scsi_free(). + */ +ASC_INITFUNC( +int +advansys_detect(Scsi_Host_Template *tpnt) +) +{ + static int detect_called = ASC_FALSE; + int iop; + int bus; + struct Scsi_Host *shp; + asc_board_t *boardp; + ASC_DVC_VAR *asc_dvc_varp = NULL; + ADV_DVC_VAR *adv_dvc_varp = NULL; + int ioport = 0; + int share_irq = FALSE; +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + PCI_DEVICE pciDevice; + PCI_CONFIG_SPACE pciConfig; +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + unsigned long pci_memory_address; +#endif /* version >= v1,3,0 */ +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + struct pci_dev *pci_devp = NULL; + int pci_device_id_cnt = 0; + unsigned int pci_device_id[ASC_PCI_DEVICE_ID_CNT] = { + ASC_PCI_DEVICE_ID_1100, + ASC_PCI_DEVICE_ID_1200, + ASC_PCI_DEVICE_ID_1300, + ASC_PCI_DEVICE_ID_2300 + }; + unsigned long pci_memory_address; +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ + int warn_code, err_code; + int ret; + + if (detect_called == ASC_FALSE) { + detect_called = ASC_TRUE; + } else { + printk("AdvanSys SCSI: advansys_detect() multiple calls ignored\n"); + return 0; + } + + ASC_DBG(1, "advansys_detect: begin\n"); + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + tpnt->proc_dir = &proc_scsi_advansys; +#endif /* version >= v1.3.0 */ + + asc_board_count = 0; + + /* + * If I/O port probing has been modified, then verify and + * clean-up the 'asc_ioport' list. + */ + if (asc_iopflag == ASC_TRUE) { + for (ioport = 0; ioport < ASC_NUM_IOPORT_PROBE; ioport++) { + ASC_DBG2(1, "advansys_detect: asc_ioport[%d] %x\n", + ioport, asc_ioport[ioport]); + if (asc_ioport[ioport] != 0) { + for (iop = 0; iop < ASC_IOADR_TABLE_MAX_IX; iop++) { + if (_asc_def_iop_base[iop] == asc_ioport[ioport]) { + break; + } + } + if (iop == ASC_IOADR_TABLE_MAX_IX) { + printk( +"AdvanSys SCSI: specified I/O Port 0x%X is invalid\n", + asc_ioport[ioport]); + asc_ioport[ioport] = 0; + } + } + } + ioport = 0; + } + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + memset(&pciDevice, 0, sizeof(PCI_DEVICE)); + memset(&pciConfig, 0, sizeof(PCI_CONFIG_SPACE)); + pciDevice.maxBusNumber = PCI_MAX_BUS; + pciDevice.endSlot = PCI_MAX_SLOT; +#endif /* ASC_CONFIG_PCI */ +#endif /* version < v2.1.93 */ + + for (bus = 0; bus < ASC_NUM_BUS; bus++) { + + ASC_DBG2(1, "advansys_detect: bus search type %d (%s)\n", + bus, asc_bus_name[bus]); + iop = 0; + + while (asc_board_count < ASC_NUM_BOARD_SUPPORTED) { + + ASC_DBG1(2, "advansys_detect: asc_board_count %d\n", + asc_board_count); + + switch (asc_bus[bus]) { + case ASC_IS_ISA: + case ASC_IS_VL: + if (asc_iopflag == ASC_FALSE) { + iop = AscSearchIOPortAddr(iop, asc_bus[bus]); + } else { + /* + * ISA and VL I/O port scanning has either been + * eliminated or limited to selected ports on + * the LILO command line, /etc/lilo.conf, or + * by setting variables when the module was loaded. + */ + ASC_DBG(1, "advansys_detect: I/O port scanning modified\n"); + ioport_try_again: + iop = 0; + for (; ioport < ASC_NUM_IOPORT_PROBE; ioport++) { + if ((iop = asc_ioport[ioport]) != 0) { + break; + } + } + if (iop) { + ASC_DBG1(1, "advansys_detect: probing I/O port %x...\n", + iop); + if (check_region(iop, ASC_IOADR_GAP) != 0) { + printk( +"AdvanSys SCSI: specified I/O Port 0x%X is busy\n", iop); + /* Don't try this I/O port twice. */ + asc_ioport[ioport] = 0; + goto ioport_try_again; + } else if (AscFindSignature(iop) == ASC_FALSE) { + printk( +"AdvanSys SCSI: specified I/O Port 0x%X has no adapter\n", iop); + /* Don't try this I/O port twice. */ + asc_ioport[ioport] = 0; + goto ioport_try_again; + } else { + /* + * If this isn't an ISA board, then it must be + * a VL board. If currently looking an ISA + * board is being looked for then try for + * another ISA board in 'asc_ioport'. + */ + if (asc_bus[bus] == ASC_IS_ISA && + (AscGetChipVersion(iop, ASC_IS_ISA) & + ASC_CHIP_VER_ISA_BIT) == 0) { + /* + * Don't clear 'asc_ioport[ioport]'. Try + * this board again for VL. Increment + * 'ioport' past this board. + */ + ioport++; + goto ioport_try_again; + } + } + /* + * This board appears good, don't try the I/O port + * again by clearing its value. Increment 'ioport' + * for the next iteration. + */ + asc_ioport[ioport++] = 0; + } + } + break; + + case ASC_IS_EISA: + iop = AscSearchIOPortAddr(iop, asc_bus[bus]); + break; + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + case ASC_IS_PCI: + if (asc_srch_pci_dev(&pciDevice) != PCI_DEVICE_FOUND) { + iop = 0; + } else { + ASC_DBG2(2, + "advansys_detect: slotFound %d, busNumber %d\n", + pciDevice.slotFound, pciDevice.busNumber); + asc_get_pci_cfg(&pciDevice, &pciConfig); + iop = pciConfig.baseAddress[0] & PCI_IOADDRESS_MASK; + ASC_DBG2(1, + "advansys_detect: vendorID %X, deviceID %X\n", + pciConfig.vendorID, pciConfig.deviceID); + ASC_DBG2(2, "advansys_detect: iop %X, irqLine %d\n", + iop, pciConfig.irqLine); + } + break; +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + case ASC_IS_PCI: + while (pci_device_id_cnt < ASC_PCI_DEVICE_ID_CNT) { + if ((pci_devp = pci_find_device(ASC_PCI_VENDORID, + pci_device_id[pci_device_id_cnt], pci_devp)) == NULL) { + pci_device_id_cnt++; + } else { + break; + } + } + if (pci_devp == NULL) { + iop = 0; + } else { + ASC_DBG2(2, + "advansys_detect: devfn %d, bus number %d\n", + pci_devp->devfn, pci_devp->bus->number); + iop = pci_devp->base_address[0] & PCI_IOADDRESS_MASK; + ASC_DBG2(1, + "advansys_detect: vendorID %X, deviceID %X\n", + pci_devp->vendor, pci_devp->device); + ASC_DBG2(2, "advansys_detect: iop %X, irqLine %d\n", + iop, pci_devp->irq); + } + break; +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ + + default: + ASC_PRINT1("advansys_detect: unknown bus type: %d\n", + asc_bus[bus]); + break; + } + ASC_DBG1(1, "advansys_detect: iop %x\n", iop); + + /* + * Adapter not found, try next bus type. + */ + if (iop == 0) { + break; + } + + /* + * Adapter found. + * + * Register the adapter, get its configuration, and + * initialize it. + */ + ASC_DBG(2, "advansys_detect: scsi_register()\n"); + shp = scsi_register(tpnt, sizeof(asc_board_t)); + + /* Save a pointer to the Scsi_host of each board found. */ + asc_host[asc_board_count++] = shp; + + /* Initialize private per board data */ + boardp = ASC_BOARDP(shp); + memset(boardp, 0, sizeof(asc_board_t)); + boardp->id = asc_board_count - 1; + + /* + * Handle both narrow and wide boards. + * + * If a Wide board was detected, set the board structure + * wide board flag. Set-up the board structure based on + * the board type. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + if (asc_bus[bus] == ASC_IS_PCI && + pciConfig.deviceID == ASC_PCI_DEVICE_ID_2300) { + boardp->flags |= ASC_IS_WIDE_BOARD; + } +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + if (asc_bus[bus] == ASC_IS_PCI && + pci_devp->device == ASC_PCI_DEVICE_ID_2300) { + boardp->flags |= ASC_IS_WIDE_BOARD; + } +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ + + if (ASC_NARROW_BOARD(boardp)) { + ASC_DBG(1, "advansys_detect: narrow board\n"); + asc_dvc_varp = &boardp->dvc_var.asc_dvc_var; + asc_dvc_varp->bus_type = asc_bus[bus]; + asc_dvc_varp->drv_ptr = (ulong) boardp; + asc_dvc_varp->cfg = &boardp->dvc_cfg.asc_dvc_cfg; + asc_dvc_varp->cfg->overrun_buf = &overrun_buf[0]; + asc_dvc_varp->iop_base = iop; + asc_dvc_varp->isr_callback = (Ptr2Func) asc_isr_callback; + } else { + ASC_DBG(1, "advansys_detect: wide board\n"); + adv_dvc_varp = &boardp->dvc_var.adv_dvc_var; + adv_dvc_varp->drv_ptr = (ulong) boardp; + adv_dvc_varp->cfg = &boardp->dvc_cfg.adv_dvc_cfg; + adv_dvc_varp->isr_callback = (Ptr2Func) adv_isr_callback; + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) + adv_dvc_varp->iop_base = iop; +#else /* version >= v1,3,0 */ + /* + * Map the board's registers into virtual memory for + * PCI slave access. Only memory accesses are used to + * access the board's registers. + * + * Note: The PCI register base address is not always + * page aligned, but the address passed to ioremap() + * must be page aligned. It is guaranteed that the + * PCI register base address will not cross a page + * boundary. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + pci_memory_address = pciConfig.baseAddress[1]; + if ((boardp->ioremap_addr = + ioremap(pci_memory_address & PAGE_MASK, + PAGE_SIZE)) == 0) { + ASC_PRINT3( +"advansys_detect: board %d: ioremap(%lx, %d) returned NULL\n", + boardp->id, pci_memory_address, ADV_CONDOR_IOLEN); + scsi_unregister(shp); + asc_board_count--; + continue; + } + adv_dvc_varp->iop_base = (AdvPortAddr) + (boardp->ioremap_addr + + (pci_memory_address - (pci_memory_address & PAGE_MASK))); +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + pci_memory_address = pci_devp->base_address[1]; + if ((boardp->ioremap_addr = + ioremap(pci_memory_address & PAGE_MASK, + PAGE_SIZE)) == 0) { + ASC_PRINT3( +"advansys_detect: board %d: ioremap(%lx, %d) returned NULL\n", + boardp->id, pci_memory_address, ADV_CONDOR_IOLEN); + scsi_unregister(shp); + asc_board_count--; + continue; + } + adv_dvc_varp->iop_base = (AdvPortAddr) + (boardp->ioremap_addr + + (pci_memory_address - (pci_memory_address & PAGE_MASK))); +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ +#endif /* version >= v1,3,0 */ + + /* + * Even though it isn't used to access the board in + * kernels greater than or equal to v1.3.0, save + * the I/O Port address so that it can be reported and + * displayed. + */ + boardp->ioport = iop; + } + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + /* + * Allocate buffer for printing information from + * /proc/scsi/advansys/[0...]. + */ + if ((boardp->prtbuf = + kmalloc(ASC_PRTBUF_SIZE, GFP_ATOMIC)) == NULL) { + ASC_PRINT3( +"advansys_detect: board %d: kmalloc(%d, %d) returned NULL\n", + boardp->id, ASC_PRTBUF_SIZE, GFP_ATOMIC); + scsi_unregister(shp); + asc_board_count--; + continue; + } +#endif /* version >= v1.3.0 */ + + if (ASC_NARROW_BOARD(boardp)) { + /* + * Set the board bus type and PCI IRQ before + * calling AscInitGetConfig(). + */ + switch (asc_dvc_varp->bus_type) { + case ASC_IS_ISA: + shp->unchecked_isa_dma = TRUE; + share_irq = FALSE; + break; + case ASC_IS_VL: + shp->unchecked_isa_dma = FALSE; + share_irq = FALSE; + break; + case ASC_IS_EISA: + shp->unchecked_isa_dma = FALSE; + share_irq = TRUE; + break; +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + case ASC_IS_PCI: + shp->irq = asc_dvc_varp->irq_no = pciConfig.irqLine; + asc_dvc_varp->cfg->pci_device_id = pciConfig.deviceID; + asc_dvc_varp->cfg->pci_slot_info = + ASC_PCI_MKID(pciDevice.busNumber, + pciDevice.slotFound, + pciDevice.devFunc); + shp->unchecked_isa_dma = FALSE; + share_irq = TRUE; + break; +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + case ASC_IS_PCI: + shp->irq = asc_dvc_varp->irq_no = pci_devp->irq; + asc_dvc_varp->cfg->pci_device_id = pci_devp->device; + asc_dvc_varp->cfg->pci_slot_info = + ASC_PCI_MKID(pci_devp->bus->number, + PCI_SLOT(pci_devp->devfn), + PCI_FUNC(pci_devp->devfn)); + shp->unchecked_isa_dma = FALSE; + share_irq = TRUE; + break; +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ + default: + ASC_PRINT2( +"advansys_detect: board %d: unknown adapter type: %d\n", + boardp->id, asc_dvc_varp->bus_type); + shp->unchecked_isa_dma = TRUE; + share_irq = FALSE; + break; + } + } else { + /* + * For Wide boards set PCI information before calling + * AdvInitGetConfig(). + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + shp->irq = adv_dvc_varp->irq_no = pciConfig.irqLine; + adv_dvc_varp->cfg->pci_device_id = pciConfig.deviceID; + adv_dvc_varp->cfg->pci_slot_info = + ASC_PCI_MKID(pciDevice.busNumber, + pciDevice.slotFound, + pciDevice.devFunc); + shp->unchecked_isa_dma = FALSE; + share_irq = TRUE; +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + shp->irq = adv_dvc_varp->irq_no = pci_devp->irq; + adv_dvc_varp->cfg->pci_device_id = pci_devp->device; + adv_dvc_varp->cfg->pci_slot_info = + ASC_PCI_MKID(pci_devp->bus->number, + PCI_SLOT(pci_devp->devfn), + PCI_FUNC(pci_devp->devfn)); + shp->unchecked_isa_dma = FALSE; + share_irq = TRUE; +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ + } + + /* + * Read the board configuration. + */ + if (ASC_NARROW_BOARD(boardp)) { + /* + * NOTE: AscInitGetConfig() may change the board's + * bus_type value. The asc_bus[bus] value should no + * longer be used. If the bus_type field must be + * referenced only use the bit-wise AND operator "&". + */ + ASC_DBG(2, "advansys_detect: AscInitGetConfig()\n"); + switch(ret = AscInitGetConfig(asc_dvc_varp)) { + case 0: /* No error */ + break; + case ASC_WARN_IO_PORT_ROTATE: + ASC_PRINT1( +"AscInitGetConfig: board %d: I/O port address modified\n", + boardp->id); + break; + case ASC_WARN_AUTO_CONFIG: + ASC_PRINT1( +"AscInitGetConfig: board %d: I/O port increment switch enabled\n", + boardp->id); + break; + case ASC_WARN_EEPROM_CHKSUM: + ASC_PRINT1( +"AscInitGetConfig: board %d: EEPROM checksum error\n", + boardp->id); + break; + case ASC_WARN_IRQ_MODIFIED: + ASC_PRINT1( +"AscInitGetConfig: board %d: IRQ modified\n", + boardp->id); + break; + case ASC_WARN_CMD_QNG_CONFLICT: + ASC_PRINT1( +"AscInitGetConfig: board %d: tag queuing enabled w/o disconnects\n", + boardp->id); + break; + default: + ASC_PRINT2( +"AscInitGetConfig: board %d: unknown warning: %x\n", + boardp->id, ret); + break; + } + if ((err_code = asc_dvc_varp->err_code) != 0) { + ASC_PRINT3( +"AscInitGetConfig: board %d error: init_state %x, err_code %x\n", + boardp->id, asc_dvc_varp->init_state, + asc_dvc_varp->err_code); + } + } else { + ASC_DBG(2, "advansys_detect: AdvInitGetConfig()\n"); + if ((ret = AdvInitGetConfig(adv_dvc_varp)) != 0) { + ASC_PRINT2("AdvInitGetConfig: board %d: warning: %x\n", + boardp->id, ret); + } + if ((err_code = adv_dvc_varp->err_code) != 0) { + ASC_PRINT2( +"AdvInitGetConfig: board %d error: err_code %x\n", + boardp->id, adv_dvc_varp->err_code); + } + } + + if (err_code != 0) { +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + kfree(boardp->prtbuf); +#endif /* version >= v1.3.0 */ + scsi_unregister(shp); + asc_board_count--; + continue; + } + + /* + * Save the EEPROM configuration so that it can be displayed + * from /proc/scsi/advansys/[0...]. + */ + if (ASC_NARROW_BOARD(boardp)) { + + ASCEEP_CONFIG *ep; + + /* + * Set the adapter's target id bit in the 'init_tidmask' field. + */ + boardp->init_tidmask |= + ADV_TID_TO_TIDMASK(asc_dvc_varp->cfg->chip_scsi_id); + + /* + * Save EEPROM settings for the board. + */ + ep = &boardp->eep_config.asc_eep; + + ep->init_sdtr = asc_dvc_varp->cfg->sdtr_enable; + ep->disc_enable = asc_dvc_varp->cfg->disc_enable; + ep->use_cmd_qng = asc_dvc_varp->cfg->cmd_qng_enabled; + ep->isa_dma_speed = asc_dvc_varp->cfg->isa_dma_speed; + ep->start_motor = asc_dvc_varp->start_motor; + ep->cntl = asc_dvc_varp->dvc_cntl; + ep->no_scam = asc_dvc_varp->no_scam; + ep->max_total_qng = asc_dvc_varp->max_total_qng; + ep->chip_scsi_id = asc_dvc_varp->cfg->chip_scsi_id; + /* 'max_tag_qng' is set to the same value for every device. */ + ep->max_tag_qng = asc_dvc_varp->cfg->max_tag_qng[0]; + ep->adapter_info[0] = asc_dvc_varp->cfg->adapter_info[0]; + ep->adapter_info[1] = asc_dvc_varp->cfg->adapter_info[1]; + ep->adapter_info[2] = asc_dvc_varp->cfg->adapter_info[2]; + ep->adapter_info[3] = asc_dvc_varp->cfg->adapter_info[3]; + ep->adapter_info[4] = asc_dvc_varp->cfg->adapter_info[4]; + ep->adapter_info[5] = asc_dvc_varp->cfg->adapter_info[5]; + ep->adapter_info[6] = asc_dvc_varp->cfg->adapter_info[6]; + + /* + * Modify board configuration. + */ + ASC_DBG(2, "advansys_detect: AscInitSetConfig()\n"); + switch (ret = AscInitSetConfig(asc_dvc_varp)) { + case 0: /* No error. */ + break; + case ASC_WARN_IO_PORT_ROTATE: + ASC_PRINT1( +"AscInitSetConfig: board %d: I/O port address modified\n", + boardp->id); + break; + case ASC_WARN_AUTO_CONFIG: + ASC_PRINT1( +"AscInitSetConfig: board %d: I/O port increment switch enabled\n", + boardp->id); + break; + case ASC_WARN_EEPROM_CHKSUM: + ASC_PRINT1( +"AscInitSetConfig: board %d: EEPROM checksum error\n", + boardp->id); + break; + case ASC_WARN_IRQ_MODIFIED: + ASC_PRINT1( +"AscInitSetConfig: board %d: IRQ modified\n", + boardp->id); + break; + case ASC_WARN_CMD_QNG_CONFLICT: + ASC_PRINT1( +"AscInitSetConfig: board %d: tag queuing w/o disconnects\n", + boardp->id); + break; + default: + ASC_PRINT2( +"AscInitSetConfig: board %d: unknown warning: %x\n", + boardp->id, ret); + break; + } + if (asc_dvc_varp->err_code != 0) { + ASC_PRINT3( +"AscInitSetConfig: board %d error: init_state %x, err_code %x\n", + boardp->id, asc_dvc_varp->init_state, + asc_dvc_varp->err_code); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + kfree(boardp->prtbuf); +#endif /* version >= v1.3.0 */ + scsi_unregister(shp); + asc_board_count--; + continue; + } + + /* + * Finish initializing the 'Scsi_Host' structure. + */ + /* AscInitSetConfig() will set the IRQ for non-PCI boards. */ + if ((asc_dvc_varp->bus_type & ASC_IS_PCI) == 0) { + shp->irq = asc_dvc_varp->irq_no; + } + } else { + + ADVEEP_CONFIG *ep; + + /* + * Save Wide EEP Configuration Information. + */ + ep = &boardp->eep_config.adv_eep; + + ep->adapter_scsi_id = adv_dvc_varp->chip_scsi_id; + ep->max_host_qng = adv_dvc_varp->max_host_qng; + ep->max_dvc_qng = adv_dvc_varp->max_dvc_qng; + ep->termination = adv_dvc_varp->cfg->termination; + ep->disc_enable = adv_dvc_varp->cfg->disc_enable; + ep->bios_ctrl = adv_dvc_varp->bios_ctrl; + ep->wdtr_able = adv_dvc_varp->wdtr_able; + ep->sdtr_able = adv_dvc_varp->sdtr_able; + ep->ultra_able = adv_dvc_varp->ultra_able; + ep->tagqng_able = adv_dvc_varp->tagqng_able; + ep->start_motor = adv_dvc_varp->start_motor; + ep->scsi_reset_delay = adv_dvc_varp->scsi_reset_wait; + ep->bios_boot_delay = adv_dvc_varp->cfg->bios_boot_wait; + ep->serial_number_word1 = adv_dvc_varp->cfg->serial1; + ep->serial_number_word2 = adv_dvc_varp->cfg->serial2; + ep->serial_number_word3 = adv_dvc_varp->cfg->serial3; + + /* + * Set the adapter's target id bit in the 'init_tidmask' field. + */ + boardp->init_tidmask |= + ADV_TID_TO_TIDMASK(adv_dvc_varp->chip_scsi_id); + + /* + * Finish initializing the 'Scsi_Host' structure. + */ + shp->irq = adv_dvc_varp->irq_no; + } + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + /* + * Channels are numbered beginning with 0. For AdvanSys One host + * structure supports one channel. Multi-channel boards have a + * separate host structure for each channel. + */ + shp->max_channel = 0; +#endif /* version >= v1.3.89 */ + if (ASC_NARROW_BOARD(boardp)) { + shp->max_id = ASC_MAX_TID + 1; + shp->max_lun = ASC_MAX_LUN + 1; + + shp->io_port = asc_dvc_varp->iop_base; + shp->n_io_port = ASC_IOADR_GAP; + shp->this_id = asc_dvc_varp->cfg->chip_scsi_id; + + /* Set maximum number of queues the adapter can handle. */ + shp->can_queue = asc_dvc_varp->max_total_qng; + } else { + shp->max_id = ADV_MAX_TID + 1; + shp->max_lun = ADV_MAX_LUN + 1; + + /* + * Save the I/O Port address and length even though the + * in v1.3.0 and greater kernels the region is not used + * by a Wide board. Instead the board is accessed with + * Memory Mapped I/O. + */ + shp->io_port = iop; + shp->n_io_port = ADV_CONDOR_IOLEN; + + shp->this_id = adv_dvc_varp->chip_scsi_id; + + /* Set maximum number of queues the adapter can handle. */ + shp->can_queue = adv_dvc_varp->max_host_qng; + } + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,89) + /* + * In old kernels without tag queuing support and with memory + * allocation problems set a conservative 'cmd_per_lun' value. + */ +#ifdef MODULE + shp->cmd_per_lun = 1; +#else /* MODULE */ + shp->cmd_per_lun = 4; +#endif /* MODULE */ + ASC_DBG1(1, "advansys_detect: cmd_per_lun: %d\n", shp->cmd_per_lun); +#else /* version >= v1.3.89 */ + /* + * Following v1.3.89, 'cmd_per_lun' is no longer needed + * and should be set to zero. + * + * But because of a bug introduced in v1.3.89 if the driver is + * compiled as a module and 'cmd_per_lun' is zero, the Mid-Level + * SCSI function 'allocate_device' will panic. To allow the driver + * to work as a module in these kernels set 'cmd_per_lun' to 1. + */ +#ifdef MODULE + shp->cmd_per_lun = 1; +#else /* MODULE */ + shp->cmd_per_lun = 0; +#endif /* MODULE */ + /* + * Use the host 'select_queue_depths' function to determine + * the number of commands to queue per device. + */ + shp->select_queue_depths = advansys_select_queue_depths; +#endif /* version >= v1.3.89 */ + + /* + * Set the maximum number of scatter-gather elements the + * adapter can handle. + */ + if (ASC_NARROW_BOARD(boardp)) { + /* + * Allow two commands with 'sg_tablesize' scatter-gather + * elements to be executed simultaneously. This value is + * the theoretical hardware limit. It may be decreased + * below. + */ + shp->sg_tablesize = + (((asc_dvc_varp->max_total_qng - 2) / 2) * + ASC_SG_LIST_PER_Q) + 1; + } else { + shp->sg_tablesize = ADV_MAX_SG_LIST; + } + +#ifdef MODULE + /* + * If the driver is compiled as a module, set a limit on the + * 'sg_tablesize' value to prevent memory allocation failures. + * Memory allocation errors are more likely to occur at module + * load time, then at driver initialization time. + */ + if (shp->sg_tablesize > 64) { + shp->sg_tablesize = 64; + } +#endif /* MODULE */ + + /* + * The value of 'sg_tablesize' can not exceed the SCSI + * mid-level driver definition of SG_ALL. SG_ALL also + * must not be exceeded, because it is used to define the + * size of the scatter-gather table in 'struct asc_sg_head'. + */ + if (shp->sg_tablesize > SG_ALL) { + shp->sg_tablesize = SG_ALL; + } + + ASC_DBG1(1, "advansys_detect: sg_tablesize: %d\n", + shp->sg_tablesize); + + /* BIOS start address. */ + if (ASC_NARROW_BOARD(boardp)) { + shp->base = (char *) ((ulong) AscGetChipBiosAddress( + asc_dvc_varp->iop_base, + asc_dvc_varp->bus_type)); + } else { + /* + * Fill-in BIOS board variables. The Wide BIOS saves + * information in LRAM that is used by the driver. + */ + AdvReadWordLram(adv_dvc_varp->iop_base, BIOS_SIGNATURE, + boardp->bios_signature); + AdvReadWordLram(adv_dvc_varp->iop_base, BIOS_VERSION, + boardp->bios_version); + AdvReadWordLram(adv_dvc_varp->iop_base, BIOS_CODESEG, + boardp->bios_codeseg); + AdvReadWordLram(adv_dvc_varp->iop_base, BIOS_CODELEN, + boardp->bios_codelen); + + ASC_DBG2(1, + "advansys_detect: bios_signature %x, bios_version %x\n", + boardp->bios_signature, boardp->bios_version); + + ASC_DBG2(1, + "advansys_detect: bios_codeseg %x, bios_codelen %x\n", + boardp->bios_codeseg, boardp->bios_codelen); + + /* + * If the BIOS saved a valid signature, then fill in + * the BIOS code segment base address. + */ + if (boardp->bios_signature == 0x55AA) { + /* + * Convert x86 realmode code segment to a linear + * address by shifting left 4. + */ + shp->base = (uchar *) (boardp->bios_codeseg << 4); + } else { + shp->base = 0; + } + } + + /* + * Register Board Resources - I/O Port, DMA, IRQ + */ + + /* Register I/O port range. */ + ASC_DBG(2, "advansys_detect: request_region()\n"); + request_region(shp->io_port, shp->n_io_port, "advansys"); + + /* Register DMA Channel for Narrow boards. */ + shp->dma_channel = NO_ISA_DMA; /* Default to no ISA DMA. */ + if (ASC_NARROW_BOARD(boardp)) { + /* Register DMA channel for ISA bus. */ + if (asc_dvc_varp->bus_type & ASC_IS_ISA) { + shp->dma_channel = asc_dvc_varp->cfg->isa_dma_channel; + if ((ret = + request_dma(shp->dma_channel, "advansys")) != 0) { + ASC_PRINT3( +"advansys_detect: board %d: request_dma() %d failed %d\n", + boardp->id, shp->dma_channel, ret); + release_region(shp->io_port, shp->n_io_port); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + kfree(boardp->prtbuf); +#endif /* version >= v1.3.0 */ + scsi_unregister(shp); + asc_board_count--; + continue; + } + AscEnableIsaDma(shp->dma_channel); + } + } + + /* Register IRQ Number. */ + ASC_DBG1(2, "advansys_detect: request_irq() %d\n", shp->irq); +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,70) + if ((ret = request_irq(shp->irq, advansys_interrupt, + SA_INTERRUPT, "advansys")) != 0) +#else /* version >= v1.3.70 */ + /* + * If request_irq() fails with the SA_INTERRUPT flag set, + * then try again without the SA_INTERRUPT flag set. This + * allows IRQ sharing to work even with other drivers that + * do not set the SA_INTERRUPT flag. + * + * If SA_INTERRUPT is not set, then interrupts are enabled + * before the driver interrupt function is called. + */ + if (((ret = request_irq(shp->irq, advansys_interrupt, + SA_INTERRUPT | (share_irq == TRUE ? SA_SHIRQ : 0), + "advansys", boardp)) != 0) && + ((ret = request_irq(shp->irq, advansys_interrupt, + (share_irq == TRUE ? SA_SHIRQ : 0), + "advansys", boardp)) != 0)) +#endif /* version >= v1.3.70 */ + { + if (ret == -EBUSY) { + ASC_PRINT2( +"advansys_detect: board %d: request_irq(): IRQ %d already in use.\n", + boardp->id, shp->irq); + } else if (ret == -EINVAL) { + ASC_PRINT2( +"advansys_detect: board %d: request_irq(): IRQ %d not valid.\n", + boardp->id, shp->irq); + } else { + ASC_PRINT3( +"advansys_detect: board %d: request_irq(): IRQ %d failed with %d\n", + boardp->id, shp->irq, ret); + } + release_region(shp->io_port, shp->n_io_port); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + iounmap(boardp->ioremap_addr); +#endif /* version >= v1,3,0 */ + if (shp->dma_channel != NO_ISA_DMA) { + free_dma(shp->dma_channel); + } +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + kfree(boardp->prtbuf); +#endif /* version >= v1.3.0 */ + scsi_unregister(shp); + asc_board_count--; + continue; + } + + /* + * Initialize board RISC chip and enable interrupts. + */ + if (ASC_NARROW_BOARD(boardp)) { + ASC_DBG(2, "advansys_detect: AscInitAsc1000Driver()\n"); + warn_code = AscInitAsc1000Driver(asc_dvc_varp); + err_code = asc_dvc_varp->err_code; + + if (warn_code || err_code) { + ASC_PRINT4( +"AscInitAsc1000Driver: board %d: error: init_state %x, warn %x error %x\n", + boardp->id, asc_dvc_varp->init_state, + warn_code, err_code); + } + } else { + int req_cnt; + adv_req_t *reqp = NULL; + int sg_cnt; + adv_sgblk_t *sgp = NULL; + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) + req_cnt = sizeof(adv_req_buf)/sizeof(adv_req_t); + sg_cnt = sizeof(adv_sgblk_buf)/sizeof(adv_sgblk_t); + reqp = (adv_req_t *) &adv_req_buf[0]; + sgp = (adv_sgblk_t *) &adv_sgblk_buf[0]; +#else /* version >= v1.3.0 */ + /* + * Allocate up to 'max_host_qng' request structures for + * the Wide board. + */ + for (req_cnt = adv_dvc_varp->max_host_qng; + req_cnt > 0; req_cnt--) { + + reqp = (adv_req_t *) + kmalloc(sizeof(adv_req_t) * req_cnt, GFP_ATOMIC); + + ASC_DBG3(1, + "advansys_detect: reqp %x, req_cnt %d, bytes %d\n", + (unsigned) reqp, req_cnt, sizeof(adv_req_t) * req_cnt); + + if (reqp != NULL) { + break; + } + } + + /* + * Allocate up to ADV_TOT_SG_LIST request structures for + * the Wide board. + */ + for (sg_cnt = ADV_TOT_SG_LIST; sg_cnt > 0; sg_cnt--) { + + sgp = (adv_sgblk_t *) + kmalloc(sizeof(adv_sgblk_t) * sg_cnt, GFP_ATOMIC); + + ASC_DBG3(1, + "advansys_detect: sgp %x, sg_cnt %d, bytes %d\n", + (unsigned) sgp, sg_cnt, sizeof(adv_sgblk_t) * sg_cnt); + + if (sgp != NULL) { + break; + } + } +#endif /* version >= v1.3.0 */ + + /* + * If no request structures or scatter-gather structures could + * be allocated, then return an error. Otherwise continue with + * initialization. + */ + if (reqp == NULL) { + ASC_PRINT1( +"advansys_detect: board %d: error: failed to kmalloc() adv_req_t buffer.\n", + boardp->id); + err_code = ADV_ERROR; + } else if (sgp == NULL) { + kfree(reqp); + ASC_PRINT1( +"advansys_detect: board %d: error: failed to kmalloc() adv_sgblk_t buffer.\n", + boardp->id); + err_code = ADV_ERROR; + } else { + + /* + * Save original pointer for kfree() in case the + * driver is built as a module and can be unloaded. + */ + boardp->orig_reqp = reqp; + + /* + * Point 'adv_reqp' to the request structures and + * link them together. + */ + req_cnt--; + reqp[req_cnt].next_reqp = NULL; + for (; req_cnt > 0; req_cnt--) { + reqp[req_cnt - 1].next_reqp = &reqp[req_cnt]; + } + boardp->adv_reqp = &reqp[0]; + + /* + * Save original pointer for kfree() in case the + * driver is built as a module and can be unloaded. + */ + boardp->orig_sgblkp = sgp; + + /* + * Point 'adv_sgblkp' to the request structures and + * link them together. + */ + sg_cnt--; + sgp[sg_cnt].next_sgblkp = NULL; + for (; sg_cnt > 0; sg_cnt--) { + sgp[sg_cnt - 1].next_sgblkp = &sgp[sg_cnt]; + } + boardp->adv_sgblkp = &sgp[0]; + + ASC_DBG(2, "advansys_detect: AdvInitAsc3550Driver()\n"); + warn_code = AdvInitAsc3550Driver(adv_dvc_varp); + err_code = adv_dvc_varp->err_code; + + if (warn_code || err_code) { + ASC_PRINT3( +"AdvInitAsc3550Driver: board %d: error: warn %x, error %x\n", + boardp->id, warn_code, adv_dvc_varp->err_code); + } + } + } + + if (err_code != 0) { + release_region(shp->io_port, shp->n_io_port); + if (ASC_WIDE_BOARD(boardp)) { +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + iounmap(boardp->ioremap_addr); +#endif /* version >= v1,3,0 */ + if (boardp->orig_reqp) { + kfree(boardp->orig_reqp); + boardp->orig_reqp = boardp->adv_reqp = NULL; + } + if (boardp->orig_sgblkp) { + kfree(boardp->orig_sgblkp); + boardp->orig_sgblkp = boardp->adv_sgblkp = NULL; + } + } + if (shp->dma_channel != NO_ISA_DMA) { + free_dma(shp->dma_channel); + } +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + kfree(boardp->prtbuf); +#endif /* version >= v1.3.0 */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,70) + free_irq(shp->irq); +#else /* version >= v1.3.70 */ + free_irq(shp->irq, boardp); +#endif /* version >= v1.3.70 */ + scsi_unregister(shp); + asc_board_count--; + continue; + } + ASC_DBG_PRT_SCSI_HOST(2, shp); + } + } + ASC_DBG1(1, "advansys_detect: done: asc_board_count %d\n", asc_board_count); + return asc_board_count; +} + +/* + * advansys_release() + * + * Release resources allocated for a single AdvanSys adapter. + */ +int +advansys_release(struct Scsi_Host *shp) +{ + asc_board_t *boardp; + + ASC_DBG(1, "advansys_release: begin\n"); + boardp = ASC_BOARDP(shp); +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,70) + free_irq(shp->irq); +#else /* version >= v1.3.70 */ + free_irq(shp->irq, boardp); +#endif /* version >= v1.3.70 */ + if (shp->dma_channel != NO_ISA_DMA) { + ASC_DBG(1, "advansys_release: free_dma()\n"); + free_dma(shp->dma_channel); + } + release_region(shp->io_port, shp->n_io_port); + if (ASC_WIDE_BOARD(boardp)) { +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + iounmap(boardp->ioremap_addr); +#endif /* version >= v1,3,0 */ + if (boardp->orig_reqp) { + kfree(boardp->orig_reqp); + boardp->orig_reqp = boardp->adv_reqp = NULL; + } + if (boardp->orig_sgblkp) { + kfree(boardp->orig_sgblkp); + boardp->orig_sgblkp = boardp->adv_sgblkp = NULL; + } + } +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) + ASC_ASSERT(boardp->prtbuf != NULL); + kfree(boardp->prtbuf); +#endif /* version >= v1.3.0 */ + scsi_unregister(shp); + ASC_DBG(1, "advansys_release: end\n"); + return 0; +} + +/* + * advansys_info() + * + * Return suitable for printing on the console with the argument + * adapter's configuration information. + * + * Note: The information line should not exceed ASC_INFO_SIZE bytes, + * otherwise the static 'info' array will be overrun. + */ +const char * +advansys_info(struct Scsi_Host *shp) +{ + static char info[ASC_INFO_SIZE]; + asc_board_t *boardp; + ASC_DVC_VAR *asc_dvc_varp; + ADV_DVC_VAR *adv_dvc_varp; + char *busname; + + boardp = ASC_BOARDP(shp); + if (ASC_NARROW_BOARD(boardp)) { + asc_dvc_varp = &boardp->dvc_var.asc_dvc_var; + ASC_DBG(1, "advansys_info: begin\n"); + if (asc_dvc_varp->bus_type & ASC_IS_ISA) { + if ((asc_dvc_varp->bus_type & ASC_IS_ISAPNP) == ASC_IS_ISAPNP) { + busname = "ISA PnP"; + } else { + busname = "ISA"; + } + sprintf(info, +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,92) +"AdvanSys SCSI %s: %s %u CDB: BIOS %X, IO %X/%X, IRQ %u, DMA %u", +#else /* version >= v2.1.92 */ +"AdvanSys SCSI %s: %s %u CDB: BIOS %X, IO %lX/%X, IRQ %u, DMA %u", +#endif /* version >= v2.1.92 */ + ASC_VERSION, busname, asc_dvc_varp->max_total_qng, + (unsigned) shp->base, + shp->io_port, shp->n_io_port - 1, + shp->irq, shp->dma_channel); + } else if (asc_dvc_varp->bus_type & ASC_IS_PCI) { + if ((asc_dvc_varp->bus_type & ASC_IS_PCI_ULTRA) + == ASC_IS_PCI_ULTRA) { + busname = "PCI Ultra"; + } else { + busname = "PCI"; + } + sprintf(info, +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,92) + "AdvanSys SCSI %s: %s %u CDB: IO %X/%X, IRQ %u", +#else /* version >= v2.1.92 */ + "AdvanSys SCSI %s: %s %u CDB: IO %lX/%X, IRQ %u", +#endif /* version >= v2.1.92 */ + ASC_VERSION, busname, asc_dvc_varp->max_total_qng, + shp->io_port, shp->n_io_port - 1, shp->irq); + } else { + if (asc_dvc_varp->bus_type & ASC_IS_VL) { + busname = "VL"; + } else if (asc_dvc_varp->bus_type & ASC_IS_EISA) { + busname = "EISA"; + } else { + busname = "?"; + ASC_PRINT2( + "advansys_info: board %d: unknown bus type %d\n", + boardp->id, asc_dvc_varp->bus_type); + } + sprintf(info, +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,92) + "AdvanSys SCSI %s: %s %u CDB: BIOS %X, IO %X/%X, IRQ %u", +#else /* version >= v2.1.92 */ + "AdvanSys SCSI %s: %s %u CDB: BIOS %X, IO %lX/%X, IRQ %u", +#endif /* version >= v2.1.92 */ + ASC_VERSION, busname, asc_dvc_varp->max_total_qng, + (unsigned) shp->base, shp->io_port - 1, + shp->n_io_port, shp->irq); + } + } else { + /* + * Wide Adapter Information + * + * Memory-mapped I/O is used instead of I/O space to access + * the adapter, but display the I/O Port range. The Memory + * I/O address is displayed through the driver /proc file. + */ + adv_dvc_varp = &boardp->dvc_var.adv_dvc_var; + if (boardp->bios_signature == 0x55AA) { + sprintf(info, +"AdvanSys SCSI %s: PCI Ultra-Wide: BIOS %X/%X, IO %X/%X, IRQ %u", + ASC_VERSION, + boardp->bios_codeseg << 4, + boardp->bios_codelen > 0 ? + (boardp->bios_codelen << 9) - 1 : 0, + (unsigned) boardp->ioport, ADV_CONDOR_IOLEN - 1, + shp->irq); + } else { + sprintf(info, +"AdvanSys SCSI %s: PCI Ultra-Wide: IO %X/%X, IRQ %u", + ASC_VERSION, + (unsigned) boardp->ioport, + (ADV_CONDOR_IOLEN - 1), + shp->irq); + } + } + ASC_ASSERT(strlen(info) < ASC_INFO_SIZE); + ASC_DBG(1, "advansys_info: end\n"); + return info; +} + +/* + * advansys_command() - polled I/O entrypoint. + * + * Apparently host drivers shouldn't return until the command + * is finished. + * + * Note: This is an old interface that is no longer used by the SCSI + * mid-level driver. The new interface, advansys_queuecommand(), + * currently handles all requests. + */ +int +advansys_command(Scsi_Cmnd *scp) +{ + ASC_DBG1(1, "advansys_command: scp %x\n", (unsigned) scp); + ASC_STATS(scp->host, command); + scp->SCp.Status = 0; /* Set to a known state */ + advansys_queuecommand(scp, advansys_command_done); + while (scp->SCp.Status == 0) { + continue; + } + ASC_DBG1(1, "advansys_command: result %x\n", scp->result); + return scp->result; +} + +/* + * advansys_queuecommand() - interrupt-driven I/O entrypoint. + * + * This function always returns 0. Command return status is saved + * in the 'scp' result field. + */ +int +advansys_queuecommand(Scsi_Cmnd *scp, void (*done)(Scsi_Cmnd *)) +{ + struct Scsi_Host *shp; + asc_board_t *boardp; + int flags; + Scsi_Cmnd *done_scp; + + shp = scp->host; + boardp = ASC_BOARDP(shp); + ASC_STATS(shp, queuecommand); + + /* + * Disable interrupts to preserve request ordering and provide + * mutually exclusive access to global structures used to initiate + * a request. + */ + save_flags(flags); + cli(); + + /* + * Block new commands while handling a reset or abort request. + */ + if (boardp->flags & (ASC_HOST_IN_RESET | ASC_HOST_IN_ABORT)) { + if (boardp->flags & ASC_HOST_IN_RESET) { + ASC_DBG1(1, + "advansys_queuecommand: scp %x blocked for reset request\n", + (unsigned) scp); + scp->result = HOST_BYTE(DID_RESET); + } else { + ASC_DBG1(1, + "advansys_queuecommand: scp %x blocked for abort request\n", + (unsigned) scp); + scp->result = HOST_BYTE(DID_ABORT); + } + + /* + * Add blocked requests to the board's 'done' queue. The queued + * requests will be completed at the end of the abort or reset + * handling. + */ + asc_enqueue(&boardp->done, scp, ASC_BACK); + restore_flags(flags); + return 0; + } + + /* + * Attempt to execute any waiting commands for the board. + */ + if (!ASC_QUEUE_EMPTY(&boardp->waiting)) { + ASC_DBG(1, + "advansys_queuecommand: before asc_execute_queue() waiting\n"); + asc_execute_queue(&boardp->waiting); + } + + /* + * Save the function pointer to Linux mid-level 'done' function + * and attempt to execute the command. + * + * If ASC_ERROR is returned the request has been added to the + * board's 'active' queue and will be completed by the interrupt + * handler. + * + * If ASC_BUSY is returned add the request to the board's per + * target waiting list. + * + * If an error occurred, the request will have been placed on the + * board's 'done' queue and must be completed before returning. + */ + scp->scsi_done = done; + switch (asc_execute_scsi_cmnd(scp)) { + case ASC_NOERROR: + break; + case ASC_BUSY: + asc_enqueue(&boardp->waiting, scp, ASC_BACK); + break; + case ASC_ERROR: + default: + done_scp = asc_dequeue_list(&boardp->done, NULL, ASC_TID_ALL); + /* Interrupts could be enabled here. */ + asc_scsi_done_list(done_scp); + break; + } + + restore_flags(flags); + return 0; +} + +/* + * advansys_abort() + * + * Abort the command specified by 'scp'. + */ +int +advansys_abort(Scsi_Cmnd *scp) +{ + struct Scsi_Host *shp; + asc_board_t *boardp; + ASC_DVC_VAR *asc_dvc_varp; + ADV_DVC_VAR *adv_dvc_varp; + int flags; + int do_scsi_done; + int scp_found; + Scsi_Cmnd *done_scp = NULL; + int ret; + + /* Save current flags and disable interrupts. */ + save_flags(flags); + cli(); + + ASC_DBG1(1, "advansys_abort: scp %x\n", (unsigned) scp); + +#ifdef ADVANSYS_STATS + if (scp->host != NULL) { + ASC_STATS(scp->host, abort); + } +#endif /* ADVANSYS_STATS */ + +#ifdef ADVANSYS_ASSERT + do_scsi_done = ASC_ERROR; + scp_found = ASC_ERROR; + ret = ASC_ERROR; +#endif /* ADVANSYS_ASSERT */ + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + if (scp->serial_number != scp->serial_number_at_timeout) { + ASC_PRINT1( +"advansys_abort: timeout serial number changed for request %x\n", + (unsigned) scp); + do_scsi_done = ASC_FALSE; + scp_found = ASC_FALSE; + ret = SCSI_ABORT_NOT_RUNNING; + } else +#endif /* version >= v1.3.89 */ + if ((shp = scp->host) == NULL) { + scp->result = HOST_BYTE(DID_ERROR); + do_scsi_done = ASC_TRUE; + scp_found = ASC_FALSE; + ret = SCSI_ABORT_ERROR; + } else if ((boardp = ASC_BOARDP(shp))->flags & + (ASC_HOST_IN_RESET | ASC_HOST_IN_ABORT)) { + ASC_PRINT2( +"advansys_abort: board %d: Nested host reset or abort, flags 0x%x\n", + boardp->id, boardp->flags); + do_scsi_done = ASC_TRUE; + if ((asc_rmqueue(&boardp->active, scp) == ASC_TRUE) || + (asc_rmqueue(&boardp->waiting, scp) == ASC_TRUE)) { + scp_found = ASC_TRUE; + } else { + scp_found = ASC_FALSE; + } + scp->result = HOST_BYTE(DID_ERROR); + ret = SCSI_ABORT_ERROR; + } else { + /* Set abort flag to avoid nested reset or abort requests. */ + boardp->flags |= ASC_HOST_IN_ABORT; + + do_scsi_done = ASC_TRUE; + if (asc_rmqueue(&boardp->waiting, scp) == ASC_TRUE) { + /* + * If asc_rmqueue() found the command on the waiting + * queue, it had not been sent to the device. After + * the queue is removed, no other handling is required. + */ + ASC_DBG1(1, "advansys_abort: scp %x found on waiting queue\n", + (unsigned) scp); + scp_found = ASC_TRUE; + scp->result = HOST_BYTE(DID_ABORT); + ret = SCSI_ABORT_SUCCESS; + } else if (asc_isqueued(&boardp->active, scp) == ASC_TRUE) { + /* + * If asc_isqueued() found the command on the active + * queue, it has been sent to the device. The command + * will be returned through the interrupt handler after + * it has been aborted. + */ + + if (ASC_NARROW_BOARD(boardp)) { + /* + * Narrow Board + */ + asc_dvc_varp = &boardp->dvc_var.asc_dvc_var; + scp->result = HOST_BYTE(DID_ABORT); + + sti(); /* Enable interrupts for AscAbortSRB(). */ + ASC_DBG1(1, "advansys_abort: before AscAbortSRB(), scp %x\n", + (unsigned) scp); + switch (AscAbortSRB(asc_dvc_varp, (ulong) scp)) { + case ASC_TRUE: + /* asc_isr_callback() will be called */ + ASC_DBG(1, "advansys_abort: AscAbortSRB() TRUE\n"); + ret = SCSI_ABORT_PENDING; + break; + case ASC_FALSE: + /* Request has apparently already completed. */ + ASC_DBG(1, "advansys_abort: AscAbortSRB() FALSE\n"); + ret = SCSI_ABORT_NOT_RUNNING; + break; + case ASC_ERROR: + default: + ASC_DBG(1, "advansys_abort: AscAbortSRB() ERROR\n"); + ret = SCSI_ABORT_ERROR; + break; + } + cli(); + } else { + /* + * Wide Board + */ + adv_dvc_varp = &boardp->dvc_var.adv_dvc_var; + scp->result = HOST_BYTE(DID_ABORT); + + ASC_DBG1(1, "advansys_abort: before AdvAbortSRB(), scp %x\n", + (unsigned) scp); + switch (AdvAbortSRB(adv_dvc_varp, (ulong) scp)) { + case ASC_TRUE: + /* asc_isr_callback() will be called */ + ASC_DBG(1, "advansys_abort: AdvAbortSRB() TRUE\n"); + ret = SCSI_ABORT_PENDING; + break; + case ASC_FALSE: + /* Request has apparently already completed. */ + ASC_DBG(1, "advansys_abort: AdvAbortSRB() FALSE\n"); + ret = SCSI_ABORT_NOT_RUNNING; + break; + case ASC_ERROR: + default: + ASC_DBG(1, "advansys_abort: AdvAbortSRB() ERROR\n"); + ret = SCSI_ABORT_ERROR; + break; + } + /* + * Ensure all requests completed by the microcode have + * been processed by calling AdvISR(). + */ + (void) AdvISR(adv_dvc_varp); + } + + /* + * The request will either still be on the active queue + * or have been added to the board's done queue. + */ + if (asc_rmqueue(&boardp->active, scp) == ASC_TRUE) { + scp->result = HOST_BYTE(DID_ABORT); + scp_found = ASC_TRUE; + } else { + scp_found = asc_rmqueue(&boardp->done, scp); + ASC_ASSERT(scp_found == ASC_TRUE); + } + + } else { + /* + * The command was not found on the active or waiting queues. + */ + do_scsi_done = ASC_TRUE; + scp_found = ASC_FALSE; + ret = SCSI_ABORT_NOT_RUNNING; + } + + /* Clear abort flag. */ + boardp->flags &= ~ASC_HOST_IN_ABORT; + + /* + * Because the ASC_HOST_IN_ABORT flag causes both + * 'advansys_interrupt' and 'asc_isr_callback' to + * queue requests to the board's 'done' queue and + * prevents waiting commands from being executed, + * these queued requests must be handled here. + */ + done_scp = asc_dequeue_list(&boardp->done, NULL, ASC_TID_ALL); + + /* + * Start any waiting commands for the board. + */ + if (!ASC_QUEUE_EMPTY(&boardp->waiting)) { + ASC_DBG(1, "advansys_interrupt: before asc_execute_queue()\n"); + asc_execute_queue(&boardp->waiting); + } + } + + /* Interrupts could be enabled here. */ + + /* + * Complete the request to be aborted, unless it has been + * restarted as detected above, even if it was not found on + * the device active or waiting queues. + */ + ASC_ASSERT(do_scsi_done != ASC_ERROR); + ASC_ASSERT(scp_found != ASC_ERROR); + if (do_scsi_done == ASC_TRUE) { + if (scp->scsi_done == NULL) { + ASC_PRINT1( +"advansys_abort: aborted request scsi_done() is NULL, %x\n", + (unsigned) scp); + } else { + if (scp_found == ASC_FALSE) { + ASC_PRINT1( +"advansys_abort: abort request not active or waiting, completing anyway %x\n", + (unsigned) scp); + } + ASC_STATS(scp->host, done); + scp->scsi_done(scp); + } + } + + /* + * It is possible for the request done function to re-enable + * interrupts without confusing the driver. But here interrupts + * aren't enabled until all requests have been completed. + */ + if (done_scp != NULL) { + asc_scsi_done_list(done_scp); + } + + ASC_DBG1(1, "advansys_abort: ret %d\n", ret); + + /* Re-enable interrupts, if they were enabled on entry. */ + restore_flags(flags); + + ASC_ASSERT(ret != ASC_ERROR); + return ret; +} + +/* + * advansys_reset() + * + * Reset the device associated with the command 'scp'. + */ +int +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,89) +advansys_reset(Scsi_Cmnd *scp) +#else /* version >= v1.3.89 */ +advansys_reset(Scsi_Cmnd *scp, unsigned int reset_flags) +#endif /* version >= v1.3.89 */ +{ + struct Scsi_Host *shp; + asc_board_t *boardp; + ASC_DVC_VAR *asc_dvc_varp; + ADV_DVC_VAR *adv_dvc_varp; + int flags; + Scsi_Cmnd *done_scp = NULL, *last_scp = NULL; + Scsi_Cmnd *tscp, *new_last_scp; + int do_scsi_done; + int scp_found; + int status; + int target; + int ret; + int device_reset = ASC_FALSE; + + /* Save current flags and disable interrupts. */ + save_flags(flags); + cli(); + + ASC_DBG1(1, "advansys_reset: %x\n", (unsigned) scp); + +#ifdef ADVANSYS_STATS + if (scp->host != NULL) { + ASC_STATS(scp->host, reset); + } +#endif /* ADVANSYS_STATS */ + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + if ((reset_flags & SCSI_RESET_ASYNCHRONOUS) && + (scp->serial_number != scp->serial_number_at_timeout)) { + ASC_PRINT1( +"advansys_reset: timeout serial number changed for request %x\n", + (unsigned) scp); + do_scsi_done = ASC_FALSE; + scp_found = ASC_FALSE; + ret = SCSI_RESET_NOT_RUNNING; + } else +#endif /* version >= v1.3.89 */ + if ((shp = scp->host) == NULL) { + scp->result = HOST_BYTE(DID_ERROR); + do_scsi_done = ASC_TRUE; + scp_found = ASC_FALSE; + ret = SCSI_RESET_ERROR; + } else if ((boardp = ASC_BOARDP(shp))->flags & + (ASC_HOST_IN_RESET | ASC_HOST_IN_ABORT)) { + ASC_PRINT2( +"advansys_reset: board %d: Nested host reset or abort, flags 0x%x\n", + boardp->id, boardp->flags); + do_scsi_done = ASC_TRUE; + if ((asc_rmqueue(&boardp->active, scp) == ASC_TRUE) || + (asc_rmqueue(&boardp->waiting, scp) == ASC_TRUE)) { + scp_found = ASC_TRUE; + } else { + scp_found = ASC_FALSE; + } + scp->result = HOST_BYTE(DID_ERROR); + ret = SCSI_RESET_ERROR; + } else if (jiffies >= boardp->last_reset && + jiffies < (boardp->last_reset + (10 * HZ))) { + /* + * Don't allow a reset to be attempted within 10 seconds + * of the last reset. + * + * If 'jiffies' wrapping occurs, the reset request will go + * through, because a wrapped 'jiffies' would not pass the + * test above. + */ + ASC_DBG(1, + "advansys_reset: reset within 10 sec of last reset ignored\n"); + do_scsi_done = ASC_TRUE; + if ((asc_rmqueue(&boardp->active, scp) == ASC_TRUE) || + (asc_rmqueue(&boardp->waiting, scp) == ASC_TRUE)) { + scp_found = ASC_TRUE; + } else { + scp_found = ASC_FALSE; + } + scp->result = HOST_BYTE(DID_ERROR); + ret = SCSI_RESET_ERROR; + } else { + do_scsi_done = ASC_TRUE; + + /* Set reset flag to avoid nested reset or abort requests. */ + boardp->flags |= ASC_HOST_IN_RESET; + + /* + * If the request is on the target waiting or active queue + * or the board done queue, then remove it and note that it + * was found. + */ + if (asc_rmqueue(&boardp->active, scp) == ASC_TRUE) { + ASC_DBG(1, "advansys_reset: active scp_found = TRUE\n"); + scp_found = ASC_TRUE; + } else if (asc_rmqueue(&boardp->waiting, scp) == ASC_TRUE) { + ASC_DBG(1, "advansys_reset: waiting scp_found = TRUE\n"); + scp_found = ASC_TRUE; + } else if (asc_rmqueue(&boardp->done, scp) == ASC_TRUE) { + scp_found = ASC_TRUE; + } else { + scp_found = ASC_FALSE; + } + + + if (ASC_NARROW_BOARD(boardp)) { + /* + * Narrow Board + * + * If the suggest reset bus flags are set, then reset the bus. + * Otherwise only reset the device. + */ + asc_dvc_varp = &boardp->dvc_var.asc_dvc_var; +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + if (reset_flags & + (SCSI_RESET_SUGGEST_BUS_RESET | + SCSI_RESET_SUGGEST_HOST_RESET)) { +#endif /* version >= v1.3.89 */ + + /* + * Reset the target's SCSI bus. + */ + ASC_DBG(1, "advansys_reset: before AscResetSB()\n"); + sti(); /* Enable interrupts for AscResetSB(). */ + status = AscResetSB(asc_dvc_varp); + cli(); + switch (status) { + case ASC_TRUE: + ASC_DBG(1, "advansys_reset: AscResetSB() success\n"); + ret = SCSI_RESET_SUCCESS; + break; + case ASC_ERROR: + default: + ASC_DBG(1, "advansys_reset: AscResetSB() failed\n"); + ret = SCSI_RESET_ERROR; + break; + } + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + } else { + /* + * Reset the specified device. If the device reset fails, + * then reset the SCSI bus. + */ + + ASC_DBG1(1, + "advansys_reset: before AscResetDevice(), target %d\n", + scp->target); + sti(); /* Enable interrupts for AscResetDevice(). */ + status = AscResetDevice(asc_dvc_varp, scp->target); + cli(); + + switch (status) { + case ASC_TRUE: + ASC_DBG(1, "advansys_reset: AscResetDevice() success\n"); + device_reset = ASC_TRUE; + ret = SCSI_RESET_SUCCESS; + break; + case ASC_ERROR: + default: + ASC_DBG(1, +"advansys_reset: AscResetDevice() failed; Calling AscResetSB()\n"); + sti(); /* Enable interrupts for AscResetSB(). */ + status = AscResetSB(asc_dvc_varp); + cli(); + switch (status) { + case ASC_TRUE: + ASC_DBG(1, "advansys_reset: AscResetSB() TRUE\n"); + ret = SCSI_RESET_SUCCESS; + break; + case ASC_ERROR: + default: + ASC_DBG(1, "advansys_reset: AscResetSB() ERROR\n"); + ret = SCSI_RESET_ERROR; + break; + } + break; + } + } +#endif /* version >= v1.3.89 */ + } else { + /* + * Wide Board + * + * If the suggest reset bus flags are set, then reset the bus. + * Otherwise only reset the device. + */ + adv_dvc_varp = &boardp->dvc_var.adv_dvc_var; +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + if (reset_flags & + (SCSI_RESET_SUGGEST_BUS_RESET | + SCSI_RESET_SUGGEST_HOST_RESET)) { +#endif /* version >= v1.3.89 */ + + /* + * Reset the target's SCSI bus. + */ + ASC_DBG(1, "advansys_reset: before AdvResetSB()\n"); + switch (AdvResetSB(adv_dvc_varp)) { + case ASC_TRUE: + ASC_DBG(1, "advansys_reset: AdvResetSB() success\n"); + ret = SCSI_RESET_SUCCESS; + break; + case ASC_FALSE: + default: + ASC_DBG(1, "advansys_reset: AdvResetSB() failed\n"); + ret = SCSI_RESET_ERROR; + break; + } + /* + * Ensure all requests completed by the microcode have + * been processed by calling AdvISR(). + */ + (void) AdvISR(adv_dvc_varp); +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + } else { + /* + * Reset the specified device. If the device reset fails, + * then reset the SCSI bus. + */ + + ASC_DBG1(1, + "advansys_reset: before AdvResetDevice(), target %d\n", + scp->target); + + switch (AdvResetDevice(adv_dvc_varp, scp->target)) { + case ASC_TRUE: + ASC_DBG(1, "advansys_reset: AdvResetDevice() success\n"); + device_reset = ASC_TRUE; + ret = SCSI_RESET_SUCCESS; + break; + case ASC_FALSE: + default: + ASC_DBG(1, +"advansys_reset: AdvResetDevice() failed; Calling AdvResetSB()\n"); + + switch (AdvResetSB(adv_dvc_varp)) { + case ASC_TRUE: + ASC_DBG(1, "advansys_reset: AdvResetSB() TRUE\n"); + ret = SCSI_RESET_SUCCESS; + break; + case ASC_FALSE: + default: + ASC_DBG(1, "advansys_reset: AdvResetSB() ERROR\n"); + ret = SCSI_RESET_ERROR; + break; + } + break; + } + /* + * Ensure all requests completed by the microcode have + * been processed by calling AdvISR(). + */ + (void) AdvISR(adv_dvc_varp); + } +#endif /* version >= v1.3.89 */ + } + + /* + * Because the ASC_HOST_IN_RESET flag causes both + * 'advansys_interrupt' and 'asc_isr_callback' to + * queue requests to the board's 'done' queue and + * prevents waiting commands from being executed, + * these queued requests must be handled here. + */ + done_scp = asc_dequeue_list(&boardp->done, &last_scp, + ASC_TID_ALL); + + /* + * If a device reset was performed dequeue all waiting + * and active requests for the device and set the request + * status to DID_RESET. + * + * If a SCSI bus reset was performed dequeue all waiting + * and active requests for all devices and set the request + * status to DID_RESET. + */ + if (device_reset == ASC_TRUE) { + target = scp->target; + } else { + target = ASC_TID_ALL; + } + + /* + * Add active requests to 'done_scp' and set the request status + * to DID_RESET. + */ + if (done_scp == NULL) { + done_scp = asc_dequeue_list(&boardp->active, &last_scp, target); + for (tscp = done_scp; tscp; tscp = REQPNEXT(tscp)) { + tscp->result = HOST_BYTE(DID_RESET); + } + } else { + ASC_ASSERT(last_scp != NULL); + REQPNEXT(last_scp) = asc_dequeue_list(&boardp->active, + &new_last_scp, target); + if (new_last_scp != NULL) { + ASC_ASSERT(REQPNEXT(last_scp) != NULL); + for (tscp = REQPNEXT(last_scp); tscp; tscp = REQPNEXT(tscp)) { + tscp->result = HOST_BYTE(DID_RESET); + } + last_scp = new_last_scp; + } + } + + /* + * Add waiting requests to 'done_scp' and set the request status + * to DID_RESET. + */ + if (done_scp == NULL) { + done_scp = asc_dequeue_list(&boardp->waiting, &last_scp, target); + for (tscp = done_scp; tscp; tscp = REQPNEXT(tscp)) { + tscp->result = HOST_BYTE(DID_RESET); + } + } else { + ASC_ASSERT(last_scp != NULL); + REQPNEXT(last_scp) = asc_dequeue_list(&boardp->waiting, + &new_last_scp, target); + if (new_last_scp != NULL) { + ASC_ASSERT(REQPNEXT(last_scp) != NULL); + for (tscp = REQPNEXT(last_scp); tscp; tscp = REQPNEXT(tscp)) { + tscp->result = HOST_BYTE(DID_RESET); + } + last_scp = new_last_scp; + } + } + + /* Save the time of the most recently completed reset. */ + boardp->last_reset = jiffies; + + /* Clear reset flag. */ + boardp->flags &= ~ASC_HOST_IN_RESET; + + /* + * Start any waiting commands for the board. + */ + if (!ASC_QUEUE_EMPTY(&boardp->waiting)) { + ASC_DBG(1, "advansys_interrupt: before asc_execute_queue()\n"); + asc_execute_queue(&boardp->waiting); + } + ret = SCSI_RESET_SUCCESS; + } + + /* Interrupts could be enabled here. */ + + ASC_ASSERT(do_scsi_done != ASC_ERROR); + ASC_ASSERT(scp_found != ASC_ERROR); + if (do_scsi_done == ASC_TRUE) { + if (scp->scsi_done == NULL) { + ASC_PRINT1( +"advansys_reset: reset request scsi_done() is NULL, %x\n", + (unsigned) scp); + } else { + if (scp_found == ASC_FALSE) { + ASC_PRINT1( +"advansys_reset: reset request not active or waiting, completing anyway %x\n", + (unsigned) scp); + } + ASC_STATS(scp->host, done); + scp->scsi_done(scp); + } + } + + /* + * It is possible for the request done function to re-enable + * interrupts without confusing the driver. But here interrupts + * aren't enabled until requests have been completed. + */ + if (done_scp != NULL) { + asc_scsi_done_list(done_scp); + } + + ASC_DBG1(1, "advansys_reset: ret %d\n", ret); + + /* Re-enable interrupts, if they were enabled on entry. */ + restore_flags(flags); + + ASC_ASSERT(ret != ASC_ERROR); + return ret; +} + +/* + * advansys_biosparam() + * + * Translate disk drive geometry if the "BIOS greater than 1 GB" + * support is enabled for a drive. + * + * ip (information pointer) is an int array with the following definition: + * ip[0]: heads + * ip[1]: sectors + * ip[2]: cylinders + */ +int +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) +advansys_biosparam(Disk *dp, int dep, int ip[]) +#else /* version >= v1.3.0 */ +advansys_biosparam(Disk *dp, kdev_t dep, int ip[]) +#endif /* version >= v1.3.0 */ +{ + asc_board_t *boardp; + + ASC_DBG(1, "advansys_biosparam: begin\n"); + ASC_STATS(dp->device->host, biosparam); + boardp = ASC_BOARDP(dp->device->host); + if (ASC_NARROW_BOARD(boardp)) { + if ((boardp->dvc_var.asc_dvc_var.dvc_cntl & + ASC_CNTL_BIOS_GT_1GB) && dp->capacity > 0x200000) { + ip[0] = 255; + ip[1] = 63; + } else { + ip[0] = 64; + ip[1] = 32; + } + } else { + if ((boardp->dvc_var.adv_dvc_var.bios_ctrl & + BIOS_CTRL_EXTENDED_XLAT) && dp->capacity > 0x200000) { + ip[0] = 255; + ip[1] = 63; + } else { + ip[0] = 64; + ip[1] = 32; + } + } + ip[2] = dp->capacity / (ip[0] * ip[1]); + ASC_DBG(1, "advansys_biosparam: end\n"); + return 0; +} + +/* + * advansys_setup() + * + * This function is called from init/main.c at boot time. + * It it passed LILO parameters that can be set from the + * LILO command line or in /etc/lilo.conf. + * + * It is used by the AdvanSys driver to either disable I/O + * port scanning or to limit scanning to 1 - 4 I/O ports. + * Regardless of the option setting EISA and PCI boards + * will still be searched for and detected. This option + * only affects searching for ISA and VL boards. + * + * If ADVANSYS_DEBUG is defined the driver debug level may + * be set using the 5th (ASC_NUM_IOPORT_PROBE + 1) I/O Port. + * + * Examples: + * 1. Eliminate I/O port scanning: + * boot: linux advansys= + * or + * boot: linux advansys=0x0 + * 2. Limit I/O port scanning to one I/O port: + * boot: linux advansys=0x110 + * 3. Limit I/O port scanning to four I/O ports: + * boot: linux advansys=0x110,0x210,0x230,0x330 + * 4. If ADVANSYS_DEBUG, limit I/O port scanning to four I/O ports and + * set the driver debug level to 2. + * boot: linux advansys=0x110,0x210,0x230,0x330,0xdeb2 + * + * ints[0] - number of arguments + * ints[1] - first argument + * ints[2] - second argument + * ... + */ +ASC_INITFUNC( +void +advansys_setup(char *str, int *ints) +) +{ + int i; + + if (asc_iopflag == ASC_TRUE) { + printk("AdvanSys SCSI: 'advansys' LILO option may appear only once\n"); + return; + } + + asc_iopflag = ASC_TRUE; + + if (ints[0] > ASC_NUM_IOPORT_PROBE) { +#ifdef ADVANSYS_DEBUG + if ((ints[0] == ASC_NUM_IOPORT_PROBE + 1) && + (ints[ASC_NUM_IOPORT_PROBE + 1] >> 4 == 0xdeb)) { + asc_dbglvl = ints[ASC_NUM_IOPORT_PROBE + 1] & 0xf; + } else { +#endif /* ADVANSYS_DEBUG */ + printk("AdvanSys SCSI: only %d I/O ports accepted\n", + ASC_NUM_IOPORT_PROBE); +#ifdef ADVANSYS_DEBUG + } +#endif /* ADVANSYS_DEBUG */ + } + +#ifdef ADVANSYS_DEBUG + ASC_DBG1(1, "advansys_setup: ints[0] %d\n", ints[0]); + for (i = 1; i < ints[0]; i++) { + ASC_DBG2(1, " ints[%d] %x", i, ints[i]); + } + ASC_DBG(1, "\n"); +#endif /* ADVANSYS_DEBUG */ + + for (i = 1; i <= ints[0] && i <= ASC_NUM_IOPORT_PROBE; i++) { + asc_ioport[i-1] = ints[i]; + ASC_DBG2(1, "advansys_setup: asc_ioport[%d] %x\n", + i - 1, asc_ioport[i-1]); + } +} + + +/* + * --- Loadable Driver Support + */ + +#ifdef MODULE +Scsi_Host_Template driver_template = ADVANSYS; +# include "scsi_module.c" +#endif /* MODULE */ + + +/* + * --- Miscellaneous Driver Functions + */ + +/* + * First-level interrupt handler. + * + * For versions > v1.3.70, 'dev_id' is a pointer to the interrupting + * adapter's asc_board_t. Because all boards are currently checked + * for interrupts on each interrupt, 'dev_id' is not referenced. 'dev_id' + * could be used to identify an interrupt passed to the AdvanSys driver, + * which is for a device sharing an interrupt with an AdvanSys adapter. + */ +STATIC void +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,70) +advansys_interrupt(int irq, struct pt_regs *regs) +#else /* version >= v1.3.70 */ +advansys_interrupt(int irq, void *dev_id, struct pt_regs *regs) +#endif /* version >= v1.3.70 */ +{ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,95) + int flags; +#else /* version >= v2.1.95 */ + unsigned long flags; +#endif /* version >= v2.1.95 */ + int i; + asc_board_t *boardp; + Scsi_Cmnd *done_scp = NULL, *last_scp = NULL; + Scsi_Cmnd *new_last_scp; + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,95) + /* Disable interrupts, if they aren't already disabled. */ + save_flags(flags); + cli(); +#else /* version >= v2.1.95 */ + /* + * Disable interrupts, if they aren't already disabled and acquire + * the I/O spinlock. + */ + spin_lock_irqsave(&io_request_lock, flags); +#endif /* version >= v2.1.95 */ + + ASC_DBG(1, "advansys_interrupt: begin\n"); + + /* + * Check for interrupts on all boards. + * AscISR() will call asc_isr_callback(). + */ + for (i = 0; i < asc_board_count; i++) { + boardp = ASC_BOARDP(asc_host[i]); + ASC_DBG2(2, "advansys_interrupt: i %d, boardp %lx\n", + i, (ulong) boardp) + if (ASC_NARROW_BOARD(boardp)) { + /* + * Narrow Board + */ + if (AscIsIntPending(asc_host[i]->io_port)) { + ASC_STATS(asc_host[i], interrupt); + ASC_DBG(1, "advansys_interrupt: before AscISR()\n"); + AscISR(&boardp->dvc_var.asc_dvc_var); + } + } else { + /* + * Wide Board + */ + ASC_DBG(1, "advansys_interrupt: before AdvISR()\n"); + if (AdvISR(&boardp->dvc_var.adv_dvc_var)) { + ASC_STATS(asc_host[i], interrupt); + } + } + + /* + * Start waiting requests and create a list of completed requests. + * + * If a reset or abort request is being performed for the board, + * the reset or abort handler will complete pending requests after + * it has completed. + */ + if ((boardp->flags & (ASC_HOST_IN_RESET | ASC_HOST_IN_ABORT)) == 0) { + ASC_DBG2(1, "advansys_interrupt: done_scp %lx, last_scp %lx\n", + (ulong) done_scp, (ulong) last_scp); + + /* Start any waiting commands for the board. */ + if (!ASC_QUEUE_EMPTY(&boardp->waiting)) { + ASC_DBG(1, "advansys_interrupt: before asc_execute_queue()\n"); + asc_execute_queue(&boardp->waiting); + } + + /* + * Add to the list of requests that must be completed. + * + * 'done_scp' will always be NULL on the first iteration + * of this loop. 'last_scp' is set at the same time as + * 'done_scp'. + */ + if (done_scp == NULL) { + done_scp = asc_dequeue_list(&boardp->done, &last_scp, + ASC_TID_ALL); + } else { + ASC_ASSERT(last_scp != NULL); + REQPNEXT(last_scp) = asc_dequeue_list(&boardp->done, + &new_last_scp, ASC_TID_ALL); + if (new_last_scp != NULL) { + ASC_ASSERT(REQPNEXT(last_scp) != NULL); + last_scp = new_last_scp; + } + } + } + } + + /* Interrupts could be enabled here. */ + + /* + * It is possible for the request done function to re-enable + * interrupts without confusing the driver. But here the + * original flags aren't restored until all requests have been + * completed. + */ + asc_scsi_done_list(done_scp); + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,95) + /* + * Restore the original flags which will enable interrupts + * if and only if they were enabled on entry. + */ + restore_flags(flags); +#else /* version >= v2.1.95 */ + /* + * Release the I/O spinlock and restore the original flags + * which will enable interrupts if and only if they were + * enabled on entry. + */ + spin_unlock_irqrestore(&io_request_lock, flags); +#endif /* version >= v2.1.95 */ + + ASC_DBG(1, "advansys_interrupt: end\n"); + return; +} + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +/* + * Set the number of commands to queue per device for the + * specified host adapter. + */ +STATIC void +advansys_select_queue_depths(struct Scsi_Host *shp, Scsi_Device *devicelist) +{ + Scsi_Device *device; + asc_board_t *boardp; + + boardp = ASC_BOARDP(shp); + boardp->flags |= ASC_SELECT_QUEUE_DEPTHS; + for (device = devicelist; device != NULL; device = device->next) { + if (device->host != shp) { + continue; + } + /* + * Save a pointer to the device and set its initial/maximum + * queue depth. + */ + boardp->device[device->id] = device; + if (ASC_NARROW_BOARD(boardp)) { + device->queue_depth = + boardp->dvc_var.asc_dvc_var.max_dvc_qng[device->id]; + } else { + device->queue_depth = + boardp->dvc_var.adv_dvc_var.max_dvc_qng; + } + ASC_DBG3(1, "advansys_select_queue_depths: shp %x, id %d, depth %d\n", + (unsigned) shp, device->id, device->queue_depth); + } +} +#endif /* version >= v1.3.89 */ + +/* + * Function used only with polled I/O requests that are initiated by + * advansys_command(). + */ +STATIC void +advansys_command_done(Scsi_Cmnd *scp) +{ + ASC_DBG1(1, "advansys_command_done: scp %x\n", (unsigned) scp); + scp->SCp.Status = 1; +} + +/* + * Complete all requests on the singly linked list pointed + * to by 'scp'. + * + * Interrupts can be enabled on entry. + */ +STATIC void +asc_scsi_done_list(Scsi_Cmnd *scp) +{ + Scsi_Cmnd *tscp; + + ASC_DBG(2, "asc_scsi_done_list: begin\n"); + while (scp != NULL) { + ASC_DBG1(3, "asc_scsi_done_list: scp %x\n", (unsigned) scp); + tscp = REQPNEXT(scp); + REQPNEXT(scp) = NULL; + ASC_STATS(scp->host, done); + ASC_ASSERT(scp->scsi_done != NULL); + scp->scsi_done(scp); + scp = tscp; + } + ASC_DBG(2, "asc_scsi_done_list: done\n"); + return; +} + +/* + * Execute a single 'Scsi_Cmnd'. + * + * The function 'done' is called when the request has been completed. + * + * Scsi_Cmnd: + * + * host - board controlling device + * device - device to send command + * target - target of device + * lun - lun of device + * cmd_len - length of SCSI CDB + * cmnd - buffer for SCSI 8, 10, or 12 byte CDB + * use_sg - if non-zero indicates scatter-gather request with use_sg elements + * + * if (use_sg == 0) { + * request_buffer - buffer address for request + * request_bufflen - length of request buffer + * } else { + * request_buffer - pointer to scatterlist structure + * } + * + * sense_buffer - sense command buffer + * + * result (4 bytes of an int): + * Byte Meaning + * 0 SCSI Status Byte Code + * 1 SCSI One Byte Message Code + * 2 Host Error Code + * 3 Mid-Level Error Code + * + * host driver fields: + * SCp - Scsi_Pointer used for command processing status + * scsi_done - used to save caller's done function + * host_scribble - used for pointer to another Scsi_Cmnd + * + * If this function returns ASC_NOERROR or ASC_ERROR the request + * has been enqueued on the board's 'done' queue and must be + * completed by the caller. + * + * If ASC_BUSY is returned the request must be enqueued by the + * caller and re-tried later. + */ +STATIC int +asc_execute_scsi_cmnd(Scsi_Cmnd *scp) +{ + asc_board_t *boardp; + ASC_DVC_VAR *asc_dvc_varp; + ADV_DVC_VAR *adv_dvc_varp; + ADV_SCSI_REQ_Q *adv_scsiqp; + Scsi_Device *device; + int ret; + + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_DBG2(1, "asc_execute_scsi_cmnd: scp %x, done %x\n", + (unsigned) scp, (unsigned) scp->scsi_done); + + boardp = ASC_BOARDP(scp->host); + device = boardp->device[scp->target]; + + if (ASC_NARROW_BOARD(boardp)) { + /* + * Build and execute Narrow Board request. + */ + + asc_dvc_varp = &boardp->dvc_var.asc_dvc_var; + + /* + * Build Asc Library request structure using the + * global structures 'asc_scsi_req' and 'asc_sg_head'. + * + * asc_build_req() can not return ASC_BUSY. + */ + if (asc_build_req(boardp, scp) == ASC_ERROR) { + ASC_STATS(scp->host, build_error); + return ASC_ERROR; + } + + /* + * Execute the command. If there is no error, add the command + * to the active queue. + */ + switch (ret = AscExeScsiQueue(asc_dvc_varp, &asc_scsi_q)) { + case ASC_NOERROR: + ASC_STATS(scp->host, exe_noerror); + /* + * Increment monotonically increasing per device successful + * request counter. Wrapping doesn't matter. + */ + boardp->reqcnt[scp->target]++; + +#if ASC_QUEUE_FLOW_CONTROL + /* + * Conditionally increment the device queue depth. + * + * If no error occurred and there have been 100 consecutive + * successful requests and the current queue depth is less + * than the maximum queue depth, then increment the current + * queue depth. + */ + if (boardp->nerrcnt[scp->target]++ > 100) { + boardp->nerrcnt[scp->target] = 0; + if (device != NULL && + (device->queue_curr_depth < device->queue_depth) && + (!(boardp->queue_full & + ADV_TID_TO_TIDMASK(scp->target)) || + (boardp->queue_full_cnt[scp->target] > + device->queue_curr_depth))) { + device->queue_curr_depth++; + } + } +#endif /* ASC_QUEUE_FLOW_CONTROL */ + asc_enqueue(&boardp->active, scp, ASC_BACK); + ASC_DBG(1, + "asc_execute_scsi_cmnd: AscExeScsiQueue(), ASC_NOERROR\n"); + break; + case ASC_BUSY: + /* Caller must enqueue request and retry later. */ + ASC_STATS(scp->host, exe_busy); +#if ASC_QUEUE_FLOW_CONTROL + /* + * Clear consecutive no error counter and if possible decrement + * queue depth. + */ + boardp->nerrcnt[scp->target] = 0; + if (device != NULL && device->queue_curr_depth > 1) { + device->queue_curr_depth--; + } +#endif /* ASC_QUEUE_FLOW_CONTROL */ + break; + case ASC_ERROR: + ASC_PRINT2( +"asc_execute_scsi_cmnd: board %d: AscExeScsiQueue() ASC_ERROR, err_code %x\n", + boardp->id, asc_dvc_varp->err_code); + ASC_STATS(scp->host, exe_error); +#if ASC_QUEUE_FLOW_CONTROL + /* Clear consecutive no error counter. */ + boardp->nerrcnt[scp->target] = 0; +#endif /* ASC_QUEUE_FLOW_CONTROL */ + scp->result = HOST_BYTE(DID_ERROR); + asc_enqueue(&boardp->done, scp, ASC_BACK); + break; + default: + ASC_PRINT2( +"asc_execute_scsi_cmnd: board %d: AscExeScsiQueue() unknown, err_code %x\n", + boardp->id, asc_dvc_varp->err_code); + ASC_STATS(scp->host, exe_unknown); +#if ASC_QUEUE_FLOW_CONTROL + /* Clear consecutive no error counter. */ + boardp->nerrcnt[scp->target] = 0; +#endif /* ASC_QUEUE_FLOW_CONTROL */ + scp->result = HOST_BYTE(DID_ERROR); + asc_enqueue(&boardp->done, scp, ASC_BACK); + break; + } + } else { + /* + * Build and execute Wide Board request. + */ + adv_dvc_varp = &boardp->dvc_var.adv_dvc_var; + + /* + * Build and get a pointer to an Adv Library request structure. + * + * If the request is successfully built then send it below, + * otherwise return with an error. + */ + switch (adv_build_req(boardp, scp, &adv_scsiqp)) { + case ASC_NOERROR: + ASC_DBG(3, "asc_execute_scsi_cmnd: adv_build_req ASC_NOERROR\n"); + break; + case ASC_BUSY: + ASC_DBG(1, "asc_execute_scsi_cmnd: adv_build_req ASC_BUSY\n"); + return ASC_BUSY; + case ASC_ERROR: + default: + ASC_DBG(1, "asc_execute_scsi_cmnd: adv_build_req ASC_ERROR\n"); + ASC_STATS(scp->host, build_error); + return ASC_ERROR; + } + + /* + * Execute the command. If there is no error, add the command + * to the active queue. + */ + switch (ret = AdvExeScsiQueue(adv_dvc_varp, adv_scsiqp)) { + case ASC_NOERROR: + ASC_STATS(scp->host, exe_noerror); + /* + * Increment monotonically increasing per device successful + * request counter. Wrapping doesn't matter. + */ + boardp->reqcnt[scp->target]++; + asc_enqueue(&boardp->active, scp, ASC_BACK); + ASC_DBG(1, + "asc_execute_scsi_cmnd: AdvExeScsiQueue(), ASC_NOERROR\n"); + break; + case ASC_BUSY: + /* Caller must enqueue request and retry later. */ + ASC_STATS(scp->host, exe_busy); + break; + case ASC_ERROR: + ASC_PRINT2( +"asc_execute_scsi_cmnd: board %d: AdvExeScsiQueue() ASC_ERROR, err_code %x\n", + boardp->id, adv_dvc_varp->err_code); + ASC_STATS(scp->host, exe_error); + scp->result = HOST_BYTE(DID_ERROR); + asc_enqueue(&boardp->done, scp, ASC_BACK); + break; + default: + ASC_PRINT2( +"asc_execute_scsi_cmnd: board %d: AdvExeScsiQueue() unknown, err_code %x\n", + boardp->id, adv_dvc_varp->err_code); + ASC_STATS(scp->host, exe_unknown); + scp->result = HOST_BYTE(DID_ERROR); + asc_enqueue(&boardp->done, scp, ASC_BACK); + break; + } + } + + ASC_DBG(1, "asc_execute_scsi_cmnd: end\n"); + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + return ret; +} + +/* + * Build a request structure for the Asc Library (Narrow Board). + * + * The global structures 'asc_scsi_q' and 'asc_sg_head' are + * used to build the request. + * + * If an error occurs, then return ASC_ERROR. + */ +STATIC int +asc_build_req(asc_board_t *boardp, Scsi_Cmnd *scp) +{ + /* + * Mutually exclusive access is required to 'asc_scsi_q' and + * 'asc_sg_head' until after the request is started. + */ + memset(&asc_scsi_q, 0, sizeof(ASC_SCSI_Q)); + + /* + * Point the ASC_SCSI_Q to the 'Scsi_Cmnd'. + */ + asc_scsi_q.q2.srb_ptr = (ulong) scp; + + /* + * Build the ASC_SCSI_Q request. + */ + ASC_ASSERT(scp->cmd_len <= ASC_MAX_CDB_LEN); + if (scp->cmd_len > ASC_MAX_CDB_LEN) { + scp->cmd_len = ASC_MAX_CDB_LEN; + } + asc_scsi_q.cdbptr = &scp->cmnd[0]; + asc_scsi_q.q2.cdb_len = scp->cmd_len; + asc_scsi_q.q1.target_id = ASC_TID_TO_TARGET_ID(scp->target); + asc_scsi_q.q1.target_lun = scp->lun; + asc_scsi_q.q2.target_ix = ASC_TIDLUN_TO_IX(scp->target, scp->lun); +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + asc_scsi_q.q1.sense_addr = (ulong) &scp->sense_buffer[0]; +#else /* version >= v2.0.0 */ + asc_scsi_q.q1.sense_addr = virt_to_bus(&scp->sense_buffer[0]); +#endif /* version >= v2.0.0 */ + asc_scsi_q.q1.sense_len = sizeof(scp->sense_buffer); + + /* + * If there are any outstanding requests for the current target, + * then every 255th request send an ORDERED request. This heuristic + * tries to retain the benefit of request sorting while preventing + * request starvation. 255 is the max number of tags or pending commands + * a device may have outstanding. + * + * The request count is incremented below for every successfully + * started request. + * + */ + if ((boardp->dvc_var.asc_dvc_var.cur_dvc_qng[scp->target] > 0) && + (boardp->reqcnt[scp->target] % 255) == 0) { + asc_scsi_q.q2.tag_code = M2_QTAG_MSG_ORDERED; + } else { + asc_scsi_q.q2.tag_code = M2_QTAG_MSG_SIMPLE; + } + + /* + * Build ASC_SCSI_Q for a contiguous buffer or a scatter-gather + * buffer command. + */ + if (scp->use_sg == 0) { + /* + * CDB request of single contiguous buffer. + */ + ASC_STATS(scp->host, cont_cnt); +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + asc_scsi_q.q1.data_addr = (ulong) scp->request_buffer; +#else /* version >= v2.0.0 */ + asc_scsi_q.q1.data_addr = virt_to_bus(scp->request_buffer); +#endif /* version >= v2.0.0 */ + asc_scsi_q.q1.data_cnt = scp->request_bufflen; + ASC_STATS_ADD(scp->host, cont_xfer, + ASC_CEILING(scp->request_bufflen, 512)); + asc_scsi_q.q1.sg_queue_cnt = 0; + asc_scsi_q.sg_head = NULL; + } else { + /* + * CDB scatter-gather request list. + */ + int sgcnt; + struct scatterlist *slp; + + if (scp->use_sg > scp->host->sg_tablesize) { + ASC_PRINT3( +"asc_build_req: board %d: use_sg %d > sg_tablesize %d\n", + boardp->id, scp->use_sg, scp->host->sg_tablesize); + scp->result = HOST_BYTE(DID_ERROR); + asc_enqueue(&boardp->done, scp, ASC_BACK); + return ASC_ERROR; + } + + ASC_STATS(scp->host, sg_cnt); + + /* + * Use global ASC_SG_HEAD structure and set the ASC_SCSI_Q + * structure to point to it. + */ + memset(&asc_sg_head, 0, sizeof(ASC_SG_HEAD)); + + asc_scsi_q.q1.cntl |= QC_SG_HEAD; + asc_scsi_q.sg_head = &asc_sg_head; + asc_scsi_q.q1.data_cnt = 0; + asc_scsi_q.q1.data_addr = 0; + asc_sg_head.entry_cnt = asc_scsi_q.q1.sg_queue_cnt = scp->use_sg; + ASC_STATS_ADD(scp->host, sg_elem, asc_sg_head.entry_cnt); + + /* + * Convert scatter-gather list into ASC_SG_HEAD list. + */ + slp = (struct scatterlist *) scp->request_buffer; + for (sgcnt = 0; sgcnt < scp->use_sg; sgcnt++, slp++) { +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + asc_sg_head.sg_list[sgcnt].addr = (ulong) slp->address; +#else /* version >= v2.0.0 */ + asc_sg_head.sg_list[sgcnt].addr = virt_to_bus(slp->address); +#endif /* version >= v2.0.0 */ + asc_sg_head.sg_list[sgcnt].bytes = slp->length; + ASC_STATS_ADD(scp->host, sg_xfer, ASC_CEILING(slp->length, 512)); + } + } + + ASC_DBG_PRT_ASC_SCSI_Q(2, &asc_scsi_q); + ASC_DBG_PRT_CDB(1, scp->cmnd, scp->cmd_len); + + return ASC_NOERROR; +} + +/* + * Build a request structure for the Adv Library (Wide Board). + * + * If an adv_req_t can not be allocated to issue the request, + * then return ASC_BUSY. If an error occurs, then return ASC_ERROR. + */ +STATIC int +adv_build_req(asc_board_t *boardp, Scsi_Cmnd *scp, + ADV_SCSI_REQ_Q **adv_scsiqpp) +{ + adv_req_t *reqp; + ADV_SCSI_REQ_Q *scsiqp; + int i; + + /* + * Allocate an adv_req_t structure from the board to execute + * the command. + */ + if (boardp->adv_reqp == NULL) { + ASC_DBG(1, "adv_build_req: no free adv_req_t\n"); + ASC_STATS(scp->host, adv_build_noreq); + return ASC_BUSY; + } else { + reqp = boardp->adv_reqp; + boardp->adv_reqp = reqp->next_reqp; + reqp->next_reqp = NULL; + } + + /* + * Get 4-byte aligned ADV_SCSI_REQ_Q and ADV_SG_BLOCK pointers. + */ + scsiqp = (ADV_SCSI_REQ_Q *) ADV_DWALIGN(&reqp->scsi_req_q); + memset(scsiqp, 0, sizeof(ADV_SCSI_REQ_Q)); + + /* + * Set the ADV_SCSI_REQ_Q 'srb_ptr' to point to the adv_req_t structure. + */ + scsiqp->srb_ptr = (ulong) reqp; + + /* + * Set the adv_req_t 'cmndp' to point to the Scsi_Cmnd structure. + */ + reqp->cmndp = scp; + + /* + * Build the ADV_SCSI_REQ_Q request. + */ + + /* + * Set CDB length and copy it to the request structure. + */ + ASC_ASSERT(scp->cmd_len <= ASC_MAX_CDB_LEN); + if (scp->cmd_len > ASC_MAX_CDB_LEN) { + scp->cmd_len = ASC_MAX_CDB_LEN; + } + scsiqp->cdb_len = scp->cmd_len; + for (i = 0; i < scp->cmd_len; i++) { + scsiqp->cdb[i] = scp->cmnd[i]; + } + + scsiqp->target_id = scp->target; + scsiqp->target_lun = scp->lun; + + scsiqp->vsense_addr = (ulong) &scp->sense_buffer[0]; +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + scsiqp->sense_addr = (ulong) &scp->sense_buffer[0]; +#else /* version >= v2.0.0 */ + scsiqp->sense_addr = virt_to_bus(&scp->sense_buffer[0]); +#endif /* version >= v2.0.0 */ + scsiqp->sense_len = sizeof(scp->sense_buffer); + + /* + * Build ADV_SCSI_REQ_Q for a contiguous buffer or a scatter-gather + * buffer command. + */ + scsiqp->data_cnt = scp->request_bufflen; + scsiqp->vdata_addr = (ulong) scp->request_buffer; +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + scsiqp->data_addr = (ulong) scp->request_buffer; +#else /* version >= v2.0.0 */ + scsiqp->data_addr = virt_to_bus(scp->request_buffer); +#endif /* version >= v2.0.0 */ + + if (scp->use_sg == 0) { + /* + * CDB request of single contiguous buffer. + */ + reqp->sgblkp = NULL; + scsiqp->sg_list_ptr = NULL; + ASC_STATS(scp->host, cont_cnt); + ASC_STATS_ADD(scp->host, cont_xfer, + ASC_CEILING(scp->request_bufflen, 512)); + } else { + /* + * CDB scatter-gather request list. + */ + if (scp->use_sg > ADV_MAX_SG_LIST) { + ASC_PRINT3( +"adv_build_req: board %d: use_sg %d > ADV_MAX_SG_LIST %d\n", + boardp->id, scp->use_sg, scp->host->sg_tablesize); + scp->result = HOST_BYTE(DID_ERROR); + asc_enqueue(&boardp->done, scp, ASC_BACK); + + /* + * Free the 'adv_req_t' structure by adding it back to the + * board free list. + */ + reqp->next_reqp = boardp->adv_reqp; + boardp->adv_reqp = reqp; + + return ASC_ERROR; + } + + /* + * Allocate an 'adv_sgblk_t' structure from the board to + * execute the command. + */ + if (boardp->adv_sgblkp == NULL) { + ASC_DBG(1, "adv_build_req: no free adv_sgblk_t\n"); + ASC_STATS(scp->host, adv_build_nosg); + /* + * Free the 'adv_req_t' structure by adding it back to the + * board free list. + */ + reqp->next_reqp = boardp->adv_reqp; + boardp->adv_reqp = reqp; + return ASC_BUSY; + } else { + reqp->sgblkp = boardp->adv_sgblkp; + boardp->adv_sgblkp = reqp->sgblkp->next_sgblkp; + reqp->sgblkp->next_sgblkp = NULL; + } + + /* + * Build scatter-gather list. + */ + scsiqp->sg_list_ptr = (ADV_SG_BLOCK *) + ADV_DWALIGN(&reqp->sgblkp->sg_block[0]); + + memset(scsiqp->sg_list_ptr, 0, sizeof(ADV_SG_BLOCK) * + (ADV_NUM_SG_BLOCK + ADV_NUM_PAGE_CROSSING)); + + if (adv_get_sglist(&boardp->dvc_var.adv_dvc_var, scsiqp, scp) == + ADV_ERROR) { + + /* + * Free the adv_sgblk_t structure, if any, by adding it back + * to the board free list. + */ + ASC_ASSERT(reqp->sgblkp != NULL); + reqp->sgblkp->next_sgblkp = boardp->adv_sgblkp; + boardp->adv_sgblkp = reqp->sgblkp; + + /* + * Free the adv_req_t structure by adding it back to the + * board free list. + */ + reqp->next_reqp = boardp->adv_reqp; + boardp->adv_reqp = reqp; + + return ADV_ERROR; + } + + ASC_STATS(scp->host, sg_cnt); + ASC_STATS_ADD(scp->host, sg_elem, scp->use_sg); + } + + ASC_DBG_PRT_ADV_SCSI_REQ_Q(2, scsiqp); + ASC_DBG_PRT_CDB(1, scp->cmnd, scp->cmd_len); + + *adv_scsiqpp = scsiqp; + + return ASC_NOERROR; +} + +/* + * Build scatter-gather list for Adv Library (Wide Board). + * + * Return: + * ADV_SUCCESS(1) - SG List successfully created + * ADV_ERROR(-1) - SG List creation failed + */ +STATIC int +adv_get_sglist(ADV_DVC_VAR *adv_dvc_varp, ADV_SCSI_REQ_Q *scsiqp, + Scsi_Cmnd *scp) +{ + ADV_SG_BLOCK *sg_block; /* virtual address of a SG */ + ulong sg_block_next_addr; /* block and its next */ + ulong sg_block_physical_addr; + int sg_block_index, i; /* how many SG entries */ + struct scatterlist *slp; + int sg_elem_cnt; + + slp = (struct scatterlist *) scp->request_buffer; + sg_elem_cnt = scp->use_sg; + + sg_block = scsiqp->sg_list_ptr; + sg_block_next_addr = (ulong) sg_block; /* allow math operation */ + sg_block_physical_addr = +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + (ulong) scsiqp->sg_list_ptr; +#else /* version >= v2.0.0 */ + virt_to_bus(scsiqp->sg_list_ptr); +#endif /* version >= v2.0.0 */ + ADV_ASSERT(ADV_DWALIGN(sg_block_physical_addr) == + sg_block_physical_addr); + scsiqp->sg_real_addr = sg_block_physical_addr; + + sg_block_index = 0; + do + { + sg_block->first_entry_no = sg_block_index; + for (i = 0; i < NO_OF_SG_PER_BLOCK; i++) + { + sg_block->sg_list[i].sg_addr = +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + (ulong) slp->address; +#else /* version >= v2.0.0 */ + virt_to_bus(slp->address); +#endif /* version >= v2.0.0 */ + sg_block->sg_list[i].sg_count = slp->length; + ASC_STATS_ADD(scp->host, sg_xfer, ASC_CEILING(slp->length, 512)); + + if (--sg_elem_cnt == 0) + { /* last entry, get out */ + scsiqp->sg_entry_cnt = sg_block_index + i + 1; + sg_block->last_entry_no = sg_block_index + i; + sg_block->sg_ptr = 0L; /* next link = NULL */ + return ADV_SUCCESS; + } + slp++; + } + sg_block_next_addr += sizeof(ADV_SG_BLOCK); + sg_block_physical_addr += sizeof(ADV_SG_BLOCK); + ADV_ASSERT(ADV_DWALIGN(sg_block_physical_addr) == + sg_block_physical_addr); + + sg_block_index += NO_OF_SG_PER_BLOCK; + sg_block->sg_ptr = (ADV_SG_BLOCK *) sg_block_physical_addr; + sg_block->last_entry_no = sg_block_index - 1; + sg_block = (ADV_SG_BLOCK *) sg_block_next_addr; /* virtual addr */ + } + while (1); + /* NOTREACHED */ +} + +/* + * asc_isr_callback() - Second Level Interrupt Handler called by AscISR(). + * + * Interrupt callback function for the Narrow SCSI Asc Library. + */ +STATIC void +asc_isr_callback(ASC_DVC_VAR *asc_dvc_varp, ASC_QDONE_INFO *qdonep) +{ + asc_board_t *boardp; + Scsi_Cmnd *scp; + struct Scsi_Host *shp; + int underrun = ASC_FALSE; + int i; + + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_DBG2(1, "asc_isr_callback: asc_dvc_varp %x, qdonep %x\n", + (unsigned) asc_dvc_varp, (unsigned) qdonep); + ASC_DBG_PRT_ASC_QDONE_INFO(2, qdonep); + + /* + * Get the Scsi_Cmnd structure and Scsi_Host structure for the + * command that has been completed. + */ + scp = (Scsi_Cmnd *) qdonep->d2.srb_ptr; + ASC_DBG1(1, "asc_isr_callback: scp %x\n", (unsigned) scp); + + if (scp == NULL) { + ASC_PRINT("asc_isr_callback: scp is NULL\n"); + return; + } + ASC_DBG_PRT_CDB(2, scp->cmnd, scp->cmd_len); + + /* + * If the request's host pointer is not valid, display a + * message and return. + */ + shp = scp->host; + for (i = 0; i < asc_board_count; i++) { + if (asc_host[i] == shp) { + break; + } + } + if (i == asc_board_count) { + ASC_PRINT2("asc_isr_callback: scp %x has bad host pointer, host %x\n", + (unsigned) scp, (unsigned) shp); + return; + } + + ASC_STATS(shp, callback); + ASC_DBG1(1, "asc_isr_callback: shp %x\n", (unsigned) shp); + + /* + * If the request isn't found on the active queue, it may + * have been removed to handle a reset or abort request. + * Display a message and return. + */ + boardp = ASC_BOARDP(shp); + ASC_ASSERT(asc_dvc_varp == &boardp->dvc_var.asc_dvc_var); + if (asc_rmqueue(&boardp->active, scp) == ASC_FALSE) { + ASC_PRINT2("asc_isr_callback: board %d: scp %x not on active queue\n", + boardp->id, (unsigned) scp); + return; + } + + /* + * Check for an underrun condition. + */ + if (scp->request_bufflen != 0 && qdonep->remain_bytes != 0 && + qdonep->remain_bytes <= scp->request_bufflen != 0) { + ASC_DBG1(1, "asc_isr_callback: underrun condition %u bytes\n", + (unsigned) qdonep->remain_bytes); + underrun = ASC_TRUE; + } + + /* + * 'qdonep' contains the command's ending status. + */ + switch (qdonep->d3.done_stat) { + case QD_NO_ERROR: + ASC_DBG(2, "asc_isr_callback: QD_NO_ERROR\n"); + switch (qdonep->d3.host_stat) { + case QHSTA_NO_ERROR: + scp->result = 0; + break; + default: + /* QHSTA error occurred */ + scp->result = HOST_BYTE(DID_ERROR); + break; + } + + /* + * If an INQUIRY command completed successfully, then call + * the AscInquiryHandling() function to set-up the device. + */ + if (scp->cmnd[0] == SCSICMD_Inquiry && scp->lun == 0 && + (scp->request_bufflen - qdonep->remain_bytes) >= 8) + { + AscInquiryHandling(asc_dvc_varp, scp->target & 0x7, + (ASC_SCSI_INQUIRY *) scp->request_buffer); + } + + /* + * If there was an underrun without any other error, + * set DID_ERROR to indicate the underrun error. + * + * Note: There is no way yet to indicate the number + * of underrun bytes. + */ + if (scp->result == 0 && underrun == ASC_TRUE) { + scp->result = HOST_BYTE(DID_UNDERRUN); + } + break; + + case QD_WITH_ERROR: + ASC_DBG(2, "asc_isr_callback: QD_WITH_ERROR\n"); + switch (qdonep->d3.host_stat) { + case QHSTA_NO_ERROR: + if (qdonep->d3.scsi_stat == SS_CHK_CONDITION) { + ASC_DBG(2, "asc_isr_callback: SS_CHK_CONDITION\n"); + ASC_DBG_PRT_SENSE(2, scp->sense_buffer, + sizeof(scp->sense_buffer)); + /* + * Note: The 'status_byte()' macro used by target drivers + * defined in scsi.h shifts the status byte returned by + * host drivers right by 1 bit. This is why target drivers + * also use right shifted status byte definitions. For + * instance target drivers use CHECK_CONDITION, defined to + * 0x1, instead of the SCSI defined check condition value + * of 0x2. Host drivers are supposed to return the status + * byte as it is defined by SCSI. + */ + scp->result = DRIVER_BYTE(DRIVER_SENSE) | + STATUS_BYTE(qdonep->d3.scsi_stat); + } else { + scp->result = STATUS_BYTE(qdonep->d3.scsi_stat); + } + break; + + default: + /* QHSTA error occurred */ + ASC_DBG1(1, "asc_isr_callback: host_stat %x\n", + qdonep->d3.host_stat); + scp->result = HOST_BYTE(DID_BAD_TARGET); + break; + } + break; + + case QD_ABORTED_BY_HOST: + ASC_DBG(1, "asc_isr_callback: QD_ABORTED_BY_HOST\n"); + scp->result = HOST_BYTE(DID_ABORT) | MSG_BYTE(qdonep->d3.scsi_msg) | + STATUS_BYTE(qdonep->d3.scsi_stat); + break; + + default: + ASC_DBG1(1, "asc_isr_callback: done_stat %x\n", qdonep->d3.done_stat); + scp->result = HOST_BYTE(DID_ERROR) | MSG_BYTE(qdonep->d3.scsi_msg) | + STATUS_BYTE(qdonep->d3.scsi_stat); + break; + } + + /* + * If the 'init_tidmask' bit isn't already set for the target and the + * current request finished normally, then set the bit for the target + * to indicate that a device is present. + */ + if ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(scp->target)) == 0 && + qdonep->d3.done_stat == QD_NO_ERROR && + qdonep->d3.host_stat == QHSTA_NO_ERROR) { + boardp->init_tidmask |= ADV_TID_TO_TIDMASK(scp->target); + } + + /* + * Because interrupts may be enabled by the 'Scsi_Cmnd' done + * function, add the command to the end of the board's done queue. + * The done function for the command will be called from + * advansys_interrupt(). + */ + asc_enqueue(&boardp->done, scp, ASC_BACK); + + return; +} + +/* + * adv_isr_callback() - Second Level Interrupt Handler called by AdvISR(). + * + * Callback function for the Wide SCSI Adv Library. + */ +STATIC void +adv_isr_callback(ADV_DVC_VAR *adv_dvc_varp, ADV_SCSI_REQ_Q *scsiqp) +{ + asc_board_t *boardp; + adv_req_t *reqp; + Scsi_Cmnd *scp; + struct Scsi_Host *shp; + int underrun = ASC_FALSE; + int i; + + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_DBG2(1, "adv_isr_callback: adv_dvc_varp %x, scsiqp %x\n", + (unsigned) adv_dvc_varp, (unsigned) scsiqp); + ASC_DBG_PRT_ADV_SCSI_REQ_Q(2, scsiqp); + + /* + * Get the adv_req_t structure for the command that has been + * completed. The adv_req_t structure actually contains the + * completed ADV_SCSI_REQ_Q structure. + */ + reqp = (adv_req_t *) scsiqp->srb_ptr; + ASC_DBG1(1, "adv_isr_callback: reqp %x\n", (unsigned) reqp); + if (reqp == NULL) { + ASC_PRINT("adv_isr_callback: reqp is NULL\n"); + return; + } + + /* + * Get the Scsi_Cmnd structure and Scsi_Host structure for the + * command that has been completed. + * + * Note: The adv_req_t request structure and adv_sgblk_t structure, + * if any, * dropped, because a board structure pointer can not be + * determined. + */ + scp = reqp->cmndp; + ASC_DBG1(1, "adv_isr_callback: scp %x\n", (unsigned) scp); + if (scp == NULL) { + ASC_PRINT("adv_isr_callback: scp is NULL; adv_req_t dropped.\n"); + return; + } + ASC_DBG_PRT_CDB(2, scp->cmnd, scp->cmd_len); + + /* + * If the request's host pointer is not valid, display a message + * and return. + */ + shp = scp->host; + for (i = 0; i < asc_board_count; i++) { + if (asc_host[i] == shp) { + break; + } + } + /* + * Note: If the host structure is not found, the adv_req_t request + * structure and adv_sgblk_t structure, if any, is dropped. + */ + if (i == asc_board_count) { + ASC_PRINT2("adv_isr_callback: scp %x has bad host pointer, host %x\n", + (unsigned) scp, (unsigned) shp); + return; + } + + ASC_STATS(shp, callback); + ASC_DBG1(1, "adv_isr_callback: shp %x\n", (unsigned) shp); + + /* + * If the request isn't found on the active queue, it may have been + * removed to handle a reset or abort request. Display a message and + * return. + * + * Note: Because the structure may still be in use don't attempt + * to free the adv_req_t and adv_sgblk_t, if any, structures. + */ + boardp = ASC_BOARDP(shp); + ASC_ASSERT(adv_dvc_varp == &boardp->dvc_var.adv_dvc_var); + if (asc_rmqueue(&boardp->active, scp) == ASC_FALSE) { + ASC_PRINT2("adv_isr_callback: board %d: scp %x not on active queue\n", + boardp->id, (unsigned) scp); + return; + } + + /* + * Check for an underrun condition. + */ + if (scp->request_bufflen != 0 && scsiqp->data_cnt != 0) { + ASC_DBG1(1, "adv_isr_callback: underrun condition %lu bytes\n", + scsiqp->data_cnt); + underrun = ASC_TRUE; + } + + /* + * 'done_status' contains the command's ending status. + */ + switch (scsiqp->done_status) { + case QD_NO_ERROR: + ASC_DBG(2, "adv_isr_callback: QD_NO_ERROR\n"); + switch (scsiqp->host_status) { + case QHSTA_NO_ERROR: + scp->result = 0; + break; + default: + /* QHSTA error occurred. */ + ASC_DBG1(2, "adv_isr_callback: host_status %x\n", + scsiqp->host_status); + scp->result = HOST_BYTE(DID_ERROR); + break; + } + /* + * If there was an underrun without any other error, + * set DID_ERROR to indicate the underrun error. + * + * Note: There is no way yet to indicate the number + * of underrun bytes. + */ + if (scp->result == 0 && underrun == ASC_TRUE) { + scp->result = HOST_BYTE(DID_UNDERRUN); + } + break; + + case QD_WITH_ERROR: + ASC_DBG(2, "adv_isr_callback: QD_WITH_ERROR\n"); + switch (scsiqp->host_status) { + case QHSTA_NO_ERROR: + if (scsiqp->scsi_status == SS_CHK_CONDITION) { + ASC_DBG(2, "adv_isr_callback: SS_CHK_CONDITION\n"); + ASC_DBG_PRT_SENSE(2, scp->sense_buffer, + sizeof(scp->sense_buffer)); + /* + * Note: The 'status_byte()' macro used by target drivers + * defined in scsi.h shifts the status byte returned by + * host drivers right by 1 bit. This is why target drivers + * also use right shifted status byte definitions. For + * instance target drivers use CHECK_CONDITION, defined to + * 0x1, instead of the SCSI defined check condition value + * of 0x2. Host drivers are supposed to return the status + * byte as it is defined by SCSI. + */ + scp->result = DRIVER_BYTE(DRIVER_SENSE) | + STATUS_BYTE(scsiqp->scsi_status); + } else { + scp->result = STATUS_BYTE(scsiqp->scsi_status); + } + break; + + default: + /* Some other QHSTA error occurred. */ + ASC_DBG1(1, "adv_isr_callback: host_status %x\n", + scsiqp->host_status); + scp->result = HOST_BYTE(DID_BAD_TARGET); + break; + } + break; + + case QD_ABORTED_BY_HOST: + ASC_DBG(1, "adv_isr_callback: QD_ABORTED_BY_HOST\n"); + scp->result = HOST_BYTE(DID_ABORT) | STATUS_BYTE(scsiqp->scsi_status); + break; + + default: + ASC_DBG1(1, "adv_isr_callback: done_status %x\n", scsiqp->done_status); + scp->result = HOST_BYTE(DID_ERROR) | STATUS_BYTE(scsiqp->scsi_status); + break; + } + + /* + * If the 'init_tidmask' bit isn't already set for the target and the + * current request finished normally, then set the bit for the target + * to indicate that a device is present. + */ + if ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(scp->target)) == 0 && + scsiqp->done_status == QD_NO_ERROR && + scsiqp->host_status == QHSTA_NO_ERROR) { + boardp->init_tidmask |= ADV_TID_TO_TIDMASK(scp->target); + } + + /* + * Because interrupts may be enabled by the 'Scsi_Cmnd' done + * function, add the command to the end of the board's done queue. + * The done function for the command will be called from + * advansys_interrupt(). + */ + asc_enqueue(&boardp->done, scp, ASC_BACK); + + /* + * Free the adv_sgblk_t structure, if any, by adding it back + * to the board free list. + */ + if (reqp->sgblkp != NULL) { + reqp->sgblkp->next_sgblkp = boardp->adv_sgblkp; + boardp->adv_sgblkp = reqp->sgblkp; + } + + /* + * Free the adv_req_t structure used with the command by adding + * it back to the board free list. + */ + reqp->next_reqp = boardp->adv_reqp; + boardp->adv_reqp = reqp; + + ASC_DBG(1, "adv_isr_callback: done\n"); + + return; +} + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI +/* + * Search for an AdvanSys PCI device in the PCI configuration space. + */ +ASC_INITFUNC( +STATIC int +asc_srch_pci_dev(PCI_DEVICE *pciDevice) +) +{ + int ret = PCI_DEVICE_NOT_FOUND; + + ASC_DBG(2, "asc_srch_pci_dev: begin\n"); + + if (pci_scan_method == -1) { + pci_scan_method = asc_scan_method(); + } + pciDevice->type = pci_scan_method; + ASC_DBG1(2, "asc_srch_pci_dev: type %d\n", pciDevice->type); + + ret = asc_pci_find_dev(pciDevice); + ASC_DBG1(2, "asc_srch_pci_dev: asc_pci_find_dev() return %d\n", ret); + if (ret == PCI_DEVICE_FOUND) { + pciDevice->slotNumber = pciDevice->slotFound + 1; + pciDevice->startSlot = pciDevice->slotFound + 1; + } else { + if (pciDevice->bridge > pciDevice->busNumber) { + ASC_DBG2(2, "asc_srch_pci_dev: bridge %x, busNumber %x\n", + pciDevice->bridge, pciDevice->busNumber); + pciDevice->busNumber++; + pciDevice->slotNumber = 0; + pciDevice->startSlot = 0; + pciDevice->endSlot = 0x0f; + ret = asc_srch_pci_dev(pciDevice); + ASC_DBG1(2, "asc_srch_pci_dev: recursive call return %d\n", ret); + } + } + + ASC_DBG1(2, "asc_srch_pci_dev: return %d\n", ret); + return ret; +} + +/* + * Determine the access method to be used for 'pciDevice'. + */ +ASC_INITFUNC( +STATIC uchar +asc_scan_method(void) +) +{ + ushort data; + PCI_DATA pciData; + uchar type; + uchar slot; + + ASC_DBG(2, "asc_scan_method: begin\n"); + memset(&pciData, 0, sizeof(pciData)); + for (type = 1; type < 3; type++) { + pciData.type = type; + for (slot = 0; slot < PCI_MAX_SLOT; slot++) { + pciData.slot = slot; + data = asc_get_cfg_word(&pciData); + if ((data != 0xFFFF) && (data != 0x0000)) { + ASC_DBG2(4, "asc_scan_method: data %x, type %d\n", data, type); + return (type); + } + } + } + ASC_DBG1(4, "asc_scan_method: type %d\n", type); + return (type); +} + +/* + * Check for an AdvanSys PCI device in 'pciDevice'. + * + * Return PCI_DEVICE_FOUND if found, otherwise return PCI_DEVICE_NOT_FOUND. + */ +ASC_INITFUNC( +STATIC int +asc_pci_find_dev(PCI_DEVICE *pciDevice) +) +{ + PCI_DATA pciData; + ushort vendorid, deviceid; + uchar classcode, subclass; + uchar lslot; + + ASC_DBG(3, "asc_pci_find_dev: begin\n"); + pciData.type = pciDevice->type; + pciData.bus = pciDevice->busNumber; + pciData.func = pciDevice->devFunc; + lslot = pciDevice->startSlot; + for (; lslot < pciDevice->endSlot; lslot++) { + pciData.slot = lslot; + pciData.offset = VENDORID_OFFSET; + vendorid = asc_get_cfg_word(&pciData); + ASC_DBG1(3, "asc_pci_find_dev: vendorid %x\n", vendorid); + if (vendorid != 0xffff) { + pciData.offset = DEVICEID_OFFSET; + deviceid = asc_get_cfg_word(&pciData); + ASC_DBG1(3, "asc_pci_find_dev: deviceid %x\n", deviceid); + if ((vendorid == ASC_PCI_VENDORID) && + ((deviceid == ASC_PCI_DEVICE_ID_1100) || + (deviceid == ASC_PCI_DEVICE_ID_1200) || + (deviceid == ASC_PCI_DEVICE_ID_1300) || + (deviceid == ASC_PCI_DEVICE_ID_2300))) { + pciDevice->slotFound = lslot; + ASC_DBG(3, "asc_pci_find_dev: PCI_DEVICE_FOUND\n"); + return PCI_DEVICE_FOUND; + } else { + pciData.offset = SUBCLASS_OFFSET; + subclass = asc_get_cfg_byte(&pciData); + pciData.offset = CLASSCODE_OFFSET; + classcode = asc_get_cfg_byte(&pciData); + if ((classcode & PCI_BASE_CLASS_BRIDGE_DEVICE) && + (subclass & PCI_SUB_CLASS_PCI_TO_PCI_BRIDGE_CONTROLLER)) { + pciDevice->bridge++; + } + ASC_DBG2(3, "asc_pci_find_dev: subclass %x, classcode %x\n", + subclass, classcode); + } + } + } + return PCI_DEVICE_NOT_FOUND; +} + +/* + * Read PCI configuration data into 'pciConfig'. + */ +ASC_INITFUNC( +STATIC void +asc_get_pci_cfg(PCI_DEVICE *pciDevice, PCI_CONFIG_SPACE *pciConfig) +) +{ + PCI_DATA pciData; + uchar counter; + uchar *localConfig; + + ASC_DBG1(4, "asc_get_pci_cfg: slotFound %d\n ", + pciDevice->slotFound); + + pciData.type = pciDevice->type; + pciData.bus = pciDevice->busNumber; + pciData.slot = pciDevice->slotFound; + pciData.func = pciDevice->devFunc; + localConfig = (uchar *) pciConfig; + + for (counter = 0; counter < sizeof(PCI_CONFIG_SPACE); counter++) { + pciData.offset = counter; + *localConfig = asc_get_cfg_byte(&pciData); + ASC_DBG1(4, "asc_get_pci_cfg: byte %x\n", *localConfig); + localConfig++; + } + ASC_DBG1(4, "asc_get_pci_cfg: counter %d\n", counter); +} + +/* + * Read a word (16 bits) from the PCI configuration space. + * + * The configuration mechanism is checked for the correct access method. + */ +ASC_INITFUNC( +STATIC ushort +asc_get_cfg_word(PCI_DATA *pciData) +) +{ + ushort tmp; + ulong address; + ulong lbus = pciData->bus; + ulong lslot = pciData->slot; + ulong lfunc = pciData->func; + uchar t2CFA, t2CF8; + ulong t1CF8, t1CFC; + + ASC_DBG4(4, "asc_get_cfg_word: type %d, bus %lu, slot %lu, func %lu\n", + pciData->type, lbus, lslot, lfunc); + + /* + * Check type of configuration mechanism. + */ + if (pciData->type == 2) { + /* + * Save registers to be restored later. + */ + t2CFA = inp(0xCFA); /* save PCI bus register */ + t2CF8 = inp(0xCF8); /* save config space enable register */ + + /* + * Write the bus and enable registers. + */ + /* set for type 1 cycle, if needed */ + outp(0xCFA, pciData->bus); + /* set the function number */ + outp(0xCF8, 0x10 | (pciData->func << 1)) ; + + /* + * Read the configuration space type 2 locations. + */ + tmp = (ushort) inpw(0xC000 | ((pciData->slot << 8) + pciData->offset)); + + outp(0xCFA, t2CFA); /* save PCI bus register */ + outp(0xCF8, t2CF8); /* save config space enable register */ + } else { + /* + * Type 1 or 3 configuration mechanism. + * + * Save the CONFIG_ADDRESS and CONFIG_DATA register values. + */ + t1CF8 = inpl(0xCF8); + t1CFC = inpl(0xCFC); + + /* + * enable <31>, bus = <23:16>, slot = <15:11>, + * func = <10:8>, reg = <7:2> + */ + address = (ulong) ((lbus << 16) | (lslot << 11) | + (lfunc << 8) | (pciData->offset & 0xFC) | 0x80000000L); + + /* + * Write out the address to CONFIG_ADDRESS. + */ + outpl(0xCF8, address); + + /* + * Read in word from CONFIG_DATA. + */ + tmp = (ushort) ((inpl(0xCFC) >> + ((pciData->offset & 2) * 8)) & 0xFFFF); + + /* + * Restore registers. + */ + outpl(0xCF8, t1CF8); + outpl(0xCFC, t1CFC); + } + ASC_DBG1(4, "asc_get_cfg_word: config data: %x\n", tmp); + return tmp; +} + +/* + * Reads a byte from the PCI configuration space. + * + * The configuration mechanism is checked for the correct access method. + */ +ASC_INITFUNC( +STATIC uchar +asc_get_cfg_byte(PCI_DATA *pciData) +) +{ + uchar tmp; + ulong address; + ulong lbus = pciData->bus, lslot = pciData->slot, lfunc = pciData->func; + uchar t2CFA, t2CF8; + ulong t1CF8, t1CFC; + + ASC_DBG1(4, "asc_get_cfg_byte: type: %d\n", pciData->type); + + /* + * Check type of configuration mechanism. + */ + if (pciData->type == 2) { + /* + * Save registers to be restored later. + */ + t2CFA = inp(0xCFA); /* save PCI bus register */ + t2CF8 = inp(0xCF8); /* save config space enable register */ + + /* + * Write the bus and enable registers. + */ + /* set for type 1 cycle, if needed */ + outp(0xCFA, pciData->bus); + /* set the function number */ + outp(0xCF8, 0x10 | (pciData->func << 1)); + + /* + * Read configuration space type 2 locations. + */ + tmp = inp(0xC000 | ((pciData->slot << 8) + pciData->offset)); + + /* + * Restore registers. + */ + outp(0xCF8, t2CF8); /* restore the enable register */ + outp(0xCFA, t2CFA); /* restore PCI bus register */ + } else { + /* + * Type 1 or 3 configuration mechanism. + * + * Save CONFIG_ADDRESS and CONFIG_DATA register values. + */ + t1CF8 = inpl(0xCF8); + t1CFC = inpl(0xCFC); + + /* + * enable <31>, bus = <23:16>, slot = <15:11>, func = <10:8>, + * reg = <7:2> + */ + address = (ulong) ((lbus << 16) | (lslot << 11) | + (lfunc << 8) | (pciData->offset & 0xFC) | 0x80000000L); + + /* + * Write out address to CONFIG_ADDRESS. + */ + outpl(0xCF8, address); + + /* + * Read in word from CONFIG_DATA. + */ + tmp = (uchar) ((inpl(0xCFC) >> ((pciData->offset & 3) * 8)) & 0xFF); + + /* + * Restore registers. + */ + outpl(0xCF8, t1CF8); + outpl(0xCFC, t1CFC); + } + ASC_DBG1(4, "asc_get_cfg_byte: config data: %x\n", tmp); + return tmp; +} + +/* + * Write a byte to the PCI configuration space. + */ +ASC_INITFUNC( +STATIC void +asc_put_cfg_byte(PCI_DATA *pciData, uchar byte_data) +) +{ + ulong tmpl; + ulong address; + ulong lbus = pciData->bus, lslot = pciData->slot, lfunc = pciData->func; + uchar t2CFA, t2CF8; + ulong t1CF8, t1CFC; + + ASC_DBG2(4, "asc_put_cfg_byte: type: %d, byte_data %x\n", + pciData->type, byte_data); + + /* + * Check type of configuration mechanism. + */ + if (pciData->type == 2) { + + /* + * Save registers to be restored later. + */ + t2CFA = inp(0xCFA); /* save PCI bus register */ + t2CF8 = inp(0xCF8); /* save config space enable register */ + + /* + * Write bus and enable registers. + */ + outp(0xCFA, pciData->bus); + + /* + * Set the function number. + */ + outp(0xCF8, 0x10 | (pciData->func << 1)); + + /* + * Write the configuration space type 2 locations. + */ + outp(0xC000 | ((pciData->slot << 8) + pciData->offset), byte_data); + + /* + * Restore registers. + */ + outp(0xCF8, t2CF8); /* restore the enable register */ + outp(0xCFA, t2CFA); /* restore PCI bus register */ + } else { + + /* + * Type 1 or 3 configuration mechanism. + * + * Save the CONFIG_ADDRESS and CONFIG_DATA register values. + */ + t1CF8 = inpl(0xCF8); + t1CFC = inpl(0xCFC); + + /* + * enable <31>, bus = <23:16>, slot = <15:11>, func = <10:8>, + * reg = <7:2> + */ + address = (ulong) ((lbus << 16) | (lslot << 11) | (lfunc << 8) | + (pciData->offset & 0xFC) | 0x80000000L); + /* + * Write out address to CONFIG_ADDRESS. + */ + outpl(0xCF8, address); + + /* + * Write double word to CONFIG_DATA preserving the bytes + * in the double not written. + */ + tmpl = inpl(0xCFC) & ~(0xFF << ((pciData->offset & 3) * 8)); + outpl(0xCFC, tmpl | (byte_data << ((pciData->offset & 3) * 8))); + + /* + * Restore registers. + */ + outpl(0xCF8, t1CF8); + outpl(0xCFC, t1CFC); + } + ASC_DBG(4, "asc_put_cfg_byte: end\n"); +} +#endif /* ASC_CONFIG_PCI */ +#endif /* version < v2.1.93 */ + +/* + * Add a 'REQP' to the end of specified queue. Set 'tidmask' + * to indicate a command is queued for the device. + * + * 'flag' may be either ASC_FRONT or ASC_BACK. + * + * 'REQPNEXT(reqp)' returns reqp's next pointer. + */ +STATIC void +asc_enqueue(asc_queue_t *ascq, REQP reqp, int flag) +{ + int tid; + + ASC_DBG3(3, "asc_enqueue: ascq %x, reqp %x, flag %d\n", + (unsigned) ascq, (unsigned) reqp, flag); + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_ASSERT(reqp != NULL); + ASC_ASSERT(flag == ASC_FRONT || flag == ASC_BACK); + tid = REQPTID(reqp); + ASC_ASSERT(tid >= 0 && tid <= ADV_MAX_TID); + if (flag == ASC_FRONT) { + REQPNEXT(reqp) = ascq->q_first[tid]; + ascq->q_first[tid] = reqp; + /* If the queue was empty, set the last pointer. */ + if (ascq->q_last[tid] == NULL) { + ascq->q_last[tid] = reqp; + } + } else { /* ASC_BACK */ + if (ascq->q_last[tid] != NULL) { + REQPNEXT(ascq->q_last[tid]) = reqp; + } + ascq->q_last[tid] = reqp; + REQPNEXT(reqp) = NULL; + /* If the queue was empty, set the first pointer. */ + if (ascq->q_first[tid] == NULL) { + ascq->q_first[tid] = reqp; + } + } + /* The queue has at least one entry, set its bit. */ + ascq->q_tidmask |= ADV_TID_TO_TIDMASK(tid); +#ifdef ADVANSYS_STATS + /* Maintain request queue statistics. */ + ascq->q_tot_cnt[tid]++; + ascq->q_cur_cnt[tid]++; + if (ascq->q_cur_cnt[tid] > ascq->q_max_cnt[tid]) { + ascq->q_max_cnt[tid] = ascq->q_cur_cnt[tid]; + ASC_DBG2(2, "asc_enqueue: new q_max_cnt[%d] %d\n", + tid, ascq->q_max_cnt[tid]); + } + REQPTIME(reqp) = REQTIMESTAMP(); +#endif /* ADVANSYS_STATS */ + ASC_DBG1(3, "asc_enqueue: reqp %x\n", (unsigned) reqp); + return; +} + +/* + * Return first queued 'REQP' on the specified queue for + * the specified target device. Clear the 'tidmask' bit for + * the device if no more commands are left queued for it. + * + * 'REQPNEXT(reqp)' returns reqp's next pointer. + */ +STATIC REQP +asc_dequeue(asc_queue_t *ascq, int tid) +{ + REQP reqp; + + ASC_DBG2(3, "asc_dequeue: ascq %x, tid %d\n", (unsigned) ascq, tid); + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_ASSERT(tid >= 0 && tid <= ADV_MAX_TID); + if ((reqp = ascq->q_first[tid]) != NULL) { + ASC_ASSERT(ascq->q_tidmask & ADV_TID_TO_TIDMASK(tid)); + ascq->q_first[tid] = REQPNEXT(reqp); + /* If the queue is empty, clear its bit and the last pointer. */ + if (ascq->q_first[tid] == NULL) { + ascq->q_tidmask &= ~ADV_TID_TO_TIDMASK(tid); + ASC_ASSERT(ascq->q_last[tid] == reqp); + ascq->q_last[tid] = NULL; + } +#ifdef ADVANSYS_STATS + /* Maintain request queue statistics. */ + ascq->q_cur_cnt[tid]--; + ASC_ASSERT(ascq->q_cur_cnt[tid] >= 0); + REQTIMESTAT("asc_dequeue", ascq, reqp, tid); +#endif /* ADVANSYS_STATS */ + } + ASC_DBG1(3, "asc_dequeue: reqp %x\n", (unsigned) reqp); + return reqp; +} + +/* + * Return a pointer to a singly linked list of all the requests queued + * for 'tid' on the 'asc_queue_t' pointed to by 'ascq'. + * + * If 'lastpp' is not NULL, '*lastpp' will be set to point to the + * the last request returned in the singly linked list. + * + * 'tid' should either be a valid target id or if it is ASC_TID_ALL, + * then all queued requests are concatenated into one list and + * returned. + * + * Note: If 'lastpp' is used to append a new list to the end of + * an old list, only change the old list last pointer if '*lastpp' + * (or the function return value) is not NULL, i.e. use a temporary + * variable for 'lastpp' and check its value after the function return + * before assigning it to the list last pointer. + * + * Unfortunately collecting queuing time statistics adds overhead to + * the function that isn't inherent to the function's algorithm. + */ +STATIC REQP +asc_dequeue_list(asc_queue_t *ascq, REQP *lastpp, int tid) +{ + REQP firstp, lastp; + int i; + + ASC_DBG2(3, "asc_dequeue_list: ascq %x, tid %d\n", (unsigned) ascq, tid); + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_ASSERT((tid == ASC_TID_ALL) || (tid >= 0 && tid <= ADV_MAX_TID)); + + /* + * If 'tid' is not ASC_TID_ALL, return requests only for + * the specified 'tid'. If 'tid' is ASC_TID_ALL, return all + * requests for all tids. + */ + if (tid != ASC_TID_ALL) { + /* Return all requests for the specified 'tid'. */ + if ((ascq->q_tidmask & ADV_TID_TO_TIDMASK(tid)) == 0) { + /* List is empty; Set first and last return pointers to NULL. */ + firstp = lastp = NULL; + } else { + firstp = ascq->q_first[tid]; + lastp = ascq->q_last[tid]; + ascq->q_first[tid] = ascq->q_last[tid] = NULL; + ascq->q_tidmask &= ~ADV_TID_TO_TIDMASK(tid); +#ifdef ADVANSYS_STATS + { + REQP reqp; + ascq->q_cur_cnt[tid] = 0; + for (reqp = firstp; reqp; reqp = REQPNEXT(reqp)) { + REQTIMESTAT("asc_dequeue_list", ascq, reqp, tid); + } + } +#endif /* ADVANSYS_STATS */ + } + } else { + /* Return all requests for all tids. */ + firstp = lastp = NULL; + for (i = 0; i <= ADV_MAX_TID; i++) { + if (ascq->q_tidmask & ADV_TID_TO_TIDMASK(i)) { + if (firstp == NULL) { + firstp = ascq->q_first[i]; + lastp = ascq->q_last[i]; + } else { + ASC_ASSERT(lastp != NULL); + REQPNEXT(lastp) = ascq->q_first[i]; + lastp = ascq->q_last[i]; + } + ascq->q_first[i] = ascq->q_last[i] = NULL; + ascq->q_tidmask &= ~ADV_TID_TO_TIDMASK(i); +#ifdef ADVANSYS_STATS + ascq->q_cur_cnt[i] = 0; +#endif /* ADVANSYS_STATS */ + } + } +#ifdef ADVANSYS_STATS + { + REQP reqp; + for (reqp = firstp; reqp; reqp = REQPNEXT(reqp)) { + REQTIMESTAT("asc_dequeue_list", ascq, reqp, reqp->target); + } + } +#endif /* ADVANSYS_STATS */ + } + if (lastpp) { + *lastpp = lastp; + } + ASC_DBG1(3, "asc_dequeue_list: firstp %x\n", (unsigned) firstp); + return firstp; +} + +/* + * Remove the specified 'REQP' from the specified queue for + * the specified target device. Clear the 'tidmask' bit for the + * device if no more commands are left queued for it. + * + * 'REQPNEXT(reqp)' returns reqp's the next pointer. + * + * Return ASC_TRUE if the command was found and removed, + * otherwise return ASC_FALSE. + */ +STATIC int +asc_rmqueue(asc_queue_t *ascq, REQP reqp) +{ + REQP currp, prevp; + int tid; + int ret = ASC_FALSE; + + ASC_DBG2(3, "asc_rmqueue: ascq %x, reqp %x\n", + (unsigned) ascq, (unsigned) reqp); + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_ASSERT(reqp != NULL); + + tid = REQPTID(reqp); + ASC_ASSERT(tid >= 0 && tid <= ADV_MAX_TID); + + /* + * Handle the common case of 'reqp' being the first + * entry on the queue. + */ + if (reqp == ascq->q_first[tid]) { + ret = ASC_TRUE; + ascq->q_first[tid] = REQPNEXT(reqp); + /* If the queue is now empty, clear its bit and the last pointer. */ + if (ascq->q_first[tid] == NULL) { + ascq->q_tidmask &= ~ADV_TID_TO_TIDMASK(tid); + ASC_ASSERT(ascq->q_last[tid] == reqp); + ascq->q_last[tid] = NULL; + } + } else if (ascq->q_first[tid] != NULL) { + ASC_ASSERT(ascq->q_last[tid] != NULL); + /* + * Because the case of 'reqp' being the first entry has been + * handled above and it is known the queue is not empty, if + * 'reqp' is found on the queue it is guaranteed the queue will + * not become empty and that 'q_first[tid]' will not be changed. + * + * Set 'prevp' to the first entry, 'currp' to the second entry, + * and search for 'reqp'. + */ + for (prevp = ascq->q_first[tid], currp = REQPNEXT(prevp); + currp; prevp = currp, currp = REQPNEXT(currp)) { + if (currp == reqp) { + ret = ASC_TRUE; + REQPNEXT(prevp) = REQPNEXT(currp); + REQPNEXT(reqp) = NULL; + if (ascq->q_last[tid] == reqp) { + ascq->q_last[tid] = prevp; + } + break; + } + } + } +#ifdef ADVANSYS_STATS + /* Maintain request queue statistics. */ + if (ret == ASC_TRUE) { + ascq->q_cur_cnt[tid]--; + REQTIMESTAT("asc_rmqueue", ascq, reqp, tid); + } + ASC_ASSERT(ascq->q_cur_cnt[tid] >= 0); +#endif /* ADVANSYS_STATS */ + ASC_DBG2(3, "asc_rmqueue: reqp %x, ret %d\n", (unsigned) reqp, ret); + return ret; +} + +/* + * If the specified 'REQP' is queued on the specified queue for + * the specified target device, return ASC_TRUE. + */ +STATIC int +asc_isqueued(asc_queue_t *ascq, REQP reqp) +{ + REQP treqp; + int tid; + int ret = ASC_FALSE; + + ASC_DBG2(3, "asc_isqueued: ascq %x, reqp %x\n", + (unsigned) ascq, (unsigned) reqp); + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + ASC_ASSERT(reqp != NULL); + + tid = REQPTID(reqp); + ASC_ASSERT(tid >= 0 && tid <= ADV_MAX_TID); + + for (treqp = ascq->q_first[tid]; treqp; treqp = REQPNEXT(treqp)) { + ASC_ASSERT(ascq->q_tidmask & ADV_TID_TO_TIDMASK(tid)); + if (treqp == reqp) { + ret = ASC_TRUE; + break; + } + } + ASC_DBG1(3, "asc_isqueued: ret %x\n", ret); + return ret; +} + +/* + * Execute as many queued requests as possible for the specified queue. + * + * Calls asc_execute_scsi_cmnd() to execute a REQP/Scsi_Cmnd. + */ +STATIC void +asc_execute_queue(asc_queue_t *ascq) +{ + ADV_SCSI_BIT_ID_TYPE scan_tidmask; + REQP reqp; + int i; + + ASC_DBG1(1, "asc_execute_queue: ascq %x\n", (unsigned) ascq); + ASC_ASSERT(interrupts_enabled() == ASC_FALSE); + /* + * Execute queued commands for devices attached to + * the current board in round-robin fashion. + */ + scan_tidmask = ascq->q_tidmask; + do { + for (i = 0; i <= ADV_MAX_TID; i++) { + if (scan_tidmask & ADV_TID_TO_TIDMASK(i)) { + if ((reqp = asc_dequeue(ascq, i)) == NULL) { + scan_tidmask &= ~ADV_TID_TO_TIDMASK(i); + } else if (asc_execute_scsi_cmnd((Scsi_Cmnd *) reqp) + == ASC_BUSY) { + scan_tidmask &= ~ADV_TID_TO_TIDMASK(i); + /* Put the request back at front of the list. */ + asc_enqueue(ascq, reqp, ASC_FRONT); + } + } + } + } while (scan_tidmask); + return; +} + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,0) +/* + * asc_prt_board_devices() + * + * Print driver information for devices attached to the board. + * + * Note: no single line should be greater than ASC_PRTLINE_SIZE, + * cf. asc_prt_line(). + * + * Return the number of characters copied into 'cp'. No more than + * 'cplen' characters will be copied to 'cp'. + */ +STATIC int +asc_prt_board_devices(struct Scsi_Host *shp, char *cp, int cplen) +{ + asc_board_t *boardp; + int leftlen; + int totlen; + int len; + int chip_scsi_id; + int i; + + boardp = ASC_BOARDP(shp); + leftlen = cplen; + totlen = len = 0; + + len = asc_prt_line(cp, leftlen, +"\nDevice Information for AdvanSys SCSI Host %d:\n", shp->host_no); + ASC_PRT_NEXT(); + + if (ASC_NARROW_BOARD(boardp)) { + chip_scsi_id = boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id; + } else { + chip_scsi_id = boardp->dvc_var.adv_dvc_var.chip_scsi_id; + } + + len = asc_prt_line(cp, leftlen, "Target IDs Detected:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if (boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) { + len = asc_prt_line(cp, leftlen, " %X,", i); + ASC_PRT_NEXT(); + } + } + len = asc_prt_line(cp, leftlen, " (%X=Host Adapter)\n", chip_scsi_id); + ASC_PRT_NEXT(); + + return totlen; +} + +/* + * Display Wide Board BIOS Information. + */ +STATIC int +asc_prt_adv_bios(struct Scsi_Host *shp, char *cp, int cplen) +{ + asc_board_t *boardp; + int leftlen; + int totlen; + int len; + int upgrade = ASC_FALSE; + ushort major, minor, letter; + + boardp = ASC_BOARDP(shp); + leftlen = cplen; + totlen = len = 0; + + len = asc_prt_line(cp, leftlen, "\nROM BIOS Version: "); + ASC_PRT_NEXT(); + + /* + * If the BIOS saved a valid signature, then fill in + * the BIOS code segment base address. + */ + if (boardp->bios_signature != 0x55AA) { + len = asc_prt_line(cp, leftlen, "Pre-3.1\n"); + ASC_PRT_NEXT(); + upgrade = ASC_TRUE; + } else { + major = (boardp->bios_version >> 12) & 0xF; + minor = (boardp->bios_version >> 8) & 0xF; + letter = (boardp->bios_version & 0xFF); + + len = asc_prt_line(cp, leftlen, "%d.%d%c\n", + major, minor, letter >= 26 ? '?' : letter + 'A'); + ASC_PRT_NEXT(); + + /* Current available ROM BIOS release is 3.1C. */ + if (major < 3 || (major <= 3 && minor < 1) || + (major <= 3 && minor <= 1 && letter < ('C'- 'A'))) { + upgrade = ASC_TRUE; + } + } + if (upgrade == ASC_TRUE) { + len = asc_prt_line(cp, leftlen, +"Newer version of ROM BIOS available: ftp://ftp.advansys.com/pub\n"); + ASC_PRT_NEXT(); + } + + return totlen; +} + +/* + * Add serial number to information bar if signature AAh + * is found in at bit 15-9 (7 bits) of word 1. + * + * Serial Number consists fo 12 alpha-numeric digits. + * + * 1 - Product type (A,B,C,D..) Word0: 15-13 (3 bits) + * 2 - MFG Location (A,B,C,D..) Word0: 12-10 (3 bits) + * 3-4 - Product ID (0-99) Word0: 9-0 (10 bits) + * 5 - Product revision (A-J) Word0: " " + * + * Signature Word1: 15-9 (7 bits) + * 6 - Year (0-9) Word1: 8-6 (3 bits) & Word2: 15 (1 bit) + * 7-8 - Week of the year (1-52) Word1: 5-0 (6 bits) + * + * 9-12 - Serial Number (A001-Z999) Word2: 14-0 (15 bits) + * + * Note 1: Only production cards will have a serial number. + * + * Note 2: Signature is most significant 7 bits (0xFE). + * + * Returns ASC_TRUE if serial number found, otherwise returns ASC_FALSE. + */ +STATIC int +asc_get_eeprom_string(ushort *serialnum, uchar *cp) +{ + ushort w, num; + + if ((serialnum[1] & 0xFE00) != ((ushort) 0xAA << 8)) { + return ASC_FALSE; + } else { + /* + * First word - 6 digits. + */ + w = serialnum[0]; + + /* Product type - 1st digit. */ + if ((*cp = 'A' + ((w & 0xE000) >> 13)) == 'H') { + /* Product type is P=Prototype */ + *cp += 0x8; + } + cp++; + + /* Manufacturing location - 2nd digit. */ + *cp++ = 'A' + ((w & 0x1C00) >> 10); + + /* Product ID - 3rd, 4th digits. */ + num = w & 0x3FF; + *cp++ = '0' + (num / 100); + num %= 100; + *cp++ = '0' + (num / 10); + + /* Product revision - 5th digit. */ + *cp++ = 'A' + (num % 10); + + /* + * Second word + */ + w = serialnum[1]; + + /* + * Year - 6th digit. + * + * If bit 15 of third word is set, then the + * last digit of the year is greater than 7. + */ + if (serialnum[2] & 0x8000) { + *cp++ = '8' + ((w & 0x1C0) >> 6); + } else { + *cp++ = '0' + ((w & 0x1C0) >> 6); + } + + /* Week of year - 7th, 8th digits. */ + num = w & 0x003F; + *cp++ = '0' + num / 10; + num %= 10; + *cp++ = '0' + num; + + /* + * Third word + */ + w = serialnum[2] & 0x7FFF; + + /* Serial number - 9th digit. */ + *cp++ = 'A' + (w / 1000); + + /* 10th, 11th, 12th digits. */ + num = w % 1000; + *cp++ = '0' + num / 100; + num %= 100; + *cp++ = '0' + num / 10; + num %= 10; + *cp++ = '0' + num; + + *cp = '\0'; /* Null Terminate the string. */ + return ASC_TRUE; + } +} + +/* + * asc_prt_asc_board_eeprom() + * + * Print board EEPROM configuration. + * + * Note: no single line should be greater than ASC_PRTLINE_SIZE, + * cf. asc_prt_line(). + * + * Return the number of characters copied into 'cp'. No more than + * 'cplen' characters will be copied to 'cp'. + */ +STATIC int +asc_prt_asc_board_eeprom(struct Scsi_Host *shp, char *cp, int cplen) +{ + asc_board_t *boardp; + ASC_DVC_VAR *asc_dvc_varp; + int leftlen; + int totlen; + int len; + ASCEEP_CONFIG *ep; + int i; + int isa_dma_speed[] = { 10, 8, 7, 6, 5, 4, 3, 2 }; + uchar serialstr[13]; + + boardp = ASC_BOARDP(shp); + asc_dvc_varp = &boardp->dvc_var.asc_dvc_var; + ep = &boardp->eep_config.asc_eep; + + leftlen = cplen; + totlen = len = 0; + + len = asc_prt_line(cp, leftlen, +"\nEEPROM Settings for AdvanSys SCSI Host %d:\n", shp->host_no); + ASC_PRT_NEXT(); + + if (asc_get_eeprom_string((ushort *) &ep->adapter_info[0], serialstr) == + ASC_TRUE) { + len = asc_prt_line(cp, leftlen, " Serial Number: %s\n", serialstr); + ASC_PRT_NEXT(); + } else { + if (ep->adapter_info[5] == 0xBB) { + len = asc_prt_line(cp, leftlen, + " Default Settings Used for EEPROM-less Adapter.\n"); + ASC_PRT_NEXT(); + } else { + len = asc_prt_line(cp, leftlen, + " Serial Number Signature Not Present.\n"); + ASC_PRT_NEXT(); + } + } + + len = asc_prt_line(cp, leftlen, +" Host SCSI ID: %u, Host Queue Size: %u, Device Queue Size: %u\n", + ep->chip_scsi_id, ep->max_total_qng, ep->max_tag_qng); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" cntl %x, no_scam %x\n", + ep->cntl, ep->no_scam); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Target ID: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %d", i); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Disconnects: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->disc_enable & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Command Queuing: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->use_cmd_qng & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Start Motor: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->start_motor & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Synchronous Transfer:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->init_sdtr & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + if (asc_dvc_varp->bus_type & ASC_IS_ISA) { + len = asc_prt_line(cp, leftlen, +" Host ISA DMA speed: %d MB/S\n", + isa_dma_speed[ep->isa_dma_speed]); + ASC_PRT_NEXT(); + } + + return totlen; +} + +/* + * asc_prt_adv_board_eeprom() + * + * Print board EEPROM configuration. + * + * Note: no single line should be greater than ASC_PRTLINE_SIZE, + * cf. asc_prt_line(). + * + * Return the number of characters copied into 'cp'. No more than + * 'cplen' characters will be copied to 'cp'. + */ +STATIC int +asc_prt_adv_board_eeprom(struct Scsi_Host *shp, char *cp, int cplen) +{ + asc_board_t *boardp; + ADV_DVC_VAR *adv_dvc_varp; + int leftlen; + int totlen; + int len; + int i; + char *termstr; + uchar serialstr[13]; + ADVEEP_CONFIG *ep; + + boardp = ASC_BOARDP(shp); + adv_dvc_varp = &boardp->dvc_var.adv_dvc_var; + ep = &boardp->eep_config.adv_eep; + + leftlen = cplen; + totlen = len = 0; + + len = asc_prt_line(cp, leftlen, +"\nEEPROM Settings for AdvanSys SCSI Host %d:\n", shp->host_no); + ASC_PRT_NEXT(); + + if (asc_get_eeprom_string(&ep->serial_number_word1, serialstr) == + ASC_TRUE) { + len = asc_prt_line(cp, leftlen, " Serial Number: %s\n", serialstr); + ASC_PRT_NEXT(); + } else { + len = asc_prt_line(cp, leftlen, + " Serial Number Signature Not Present.\n"); + ASC_PRT_NEXT(); + } + + len = asc_prt_line(cp, leftlen, +" Host SCSI ID: %u, Host Queue Size: %u, Device Queue Size: %u\n", + ep->adapter_scsi_id, ep->max_host_qng, ep->max_dvc_qng); + ASC_PRT_NEXT(); + + switch (ep->termination) { + case 1: + termstr = "Low Off/High Off"; + break; + case 2: + termstr = "Low Off/High On"; + break; + case 3: + termstr = "Low On/High On"; + break; + default: + case 0: + termstr = "Automatic"; + break; + } + + len = asc_prt_line(cp, leftlen, +" termination: %u (%s), bios_ctrl: %x\n", + ep->termination, termstr, ep->bios_ctrl); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Target ID: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %X", i); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Disconnects: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->disc_enable & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Command Queuing: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->tagqng_able & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Start Motor: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->start_motor & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Synchronous Transfer:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->sdtr_able & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Ultra Transfer: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->ultra_able & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Wide Transfer: "); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + len = asc_prt_line(cp, leftlen, " %c", + (ep->wdtr_able & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + return totlen; +} + +/* + * asc_prt_driver_conf() + * + * Note: no single line should be greater than ASC_PRTLINE_SIZE, + * cf. asc_prt_line(). + * + * Return the number of characters copied into 'cp'. No more than + * 'cplen' characters will be copied to 'cp'. + */ +STATIC int +asc_prt_driver_conf(struct Scsi_Host *shp, char *cp, int cplen) +{ + asc_board_t *boardp; + int leftlen; + int totlen; + int len; + int chip_scsi_id; +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + int i; +#endif /* version >= v1.3.89 */ + + boardp = ASC_BOARDP(shp); + + leftlen = cplen; + totlen = len = 0; + + len = asc_prt_line(cp, leftlen, +"\nLinux Driver Configuration and Information for AdvanSys SCSI Host %d:\n", + shp->host_no); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,89) +" host_busy %u, last_reset %u, max_id %u, max_lun %u\n", + shp->host_busy, shp->last_reset, shp->max_id, shp->max_lun); +#else /* version >= v1.3.89 */ +" host_busy %u, last_reset %u, max_id %u, max_lun %u, max_channel %u\n", + shp->host_busy, shp->last_reset, shp->max_id, shp->max_lun, + shp->max_channel); +#endif /* version >= v1.3.89 */ + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,57) +" can_queue %d, this_id %d, sg_tablesize %u, cmd_per_lun %u\n", + shp->can_queue, shp->this_id, shp->sg_tablesize, shp->cmd_per_lun); +#else /* version >= v1.3.57 */ +" unique_id %d, can_queue %d, this_id %d, sg_tablesize %u, cmd_per_lun %u\n", + shp->unique_id, shp->can_queue, shp->this_id, shp->sg_tablesize, + shp->cmd_per_lun); +#endif /* version >= v1.3.57 */ + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,57) +" unchecked_isa_dma %d, loaded_as_module %d\n", + shp->unchecked_isa_dma, shp->loaded_as_module); +#else /* version >= v1.3.57 */ +" unchecked_isa_dma %d, use_clustering %d, loaded_as_module %d\n", + shp->unchecked_isa_dma, shp->use_clustering, shp->loaded_as_module); +#endif /* version >= v1.3.57 */ + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, " flags %x, last_reset %x, jiffies %x\n", + boardp->flags, boardp->last_reset, jiffies); + ASC_PRT_NEXT(); + + if (ASC_NARROW_BOARD(boardp)) { + chip_scsi_id = boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id; + } else { + chip_scsi_id = boardp->dvc_var.adv_dvc_var.chip_scsi_id; + } + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) + if (boardp->flags & ASC_SELECT_QUEUE_DEPTHS) { + len = asc_prt_line(cp, leftlen, " queue_depth:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + if (boardp->device[i] == NULL) { + continue; + } + len = asc_prt_line(cp, leftlen, " %X:%d", + i, boardp->device[i]->queue_depth); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + } +#endif /* version >= v1.3.89 */ + +#if ASC_QUEUE_FLOW_CONTROL + if (ASC_NARROW_BOARD(boardp)) { + len = asc_prt_line(cp, leftlen, " queue_curr_depth:"); + ASC_PRT_NEXT(); + /* Use ASC_MAX_TID for Narrow Board. */ + for (i = 0; i <= ASC_MAX_TID; i++) { + if ((boardp->asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + if (boardp->device[i] == NULL) { + continue; + } + len = asc_prt_line(cp, leftlen, " %d:%d", + i, boardp->device[i]->queue_curr_depth); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, " queue_count:"); + ASC_PRT_NEXT(); + /* Use ASC_MAX_TID for Narrow Board. */ + for (i = 0; i <= ASC_MAX_TID; i++) { + if ((boardp->asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + if (boardp->device[i] == NULL) { + continue; + } + len = asc_prt_line(cp, leftlen, " %d:%d", + i, boardp->device[i]->queue_count); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + } +#endif /* ASC_QUEUE_FLOW_CONTROL */ + + return totlen; +} + +/* + * asc_prt_asc_board_info() + * + * Print dynamic board configuration information. + * + * Note: no single line should be greater than ASC_PRTLINE_SIZE, + * cf. asc_prt_line(). + * + * Return the number of characters copied into 'cp'. No more than + * 'cplen' characters will be copied to 'cp'. + */ +STATIC int +asc_prt_asc_board_info(struct Scsi_Host *shp, char *cp, int cplen) +{ + asc_board_t *boardp; + int leftlen; + int totlen; + int len; + ASC_DVC_VAR *v; + ASC_DVC_CFG *c; + int i; + + boardp = ASC_BOARDP(shp); + v = &boardp->dvc_var.asc_dvc_var; + c = &boardp->dvc_cfg.asc_dvc_cfg; + + leftlen = cplen; + totlen = len = 0; + + len = asc_prt_line(cp, leftlen, +"\nAsc Library Configuration and Statistics for AdvanSys SCSI Host %d:\n", + shp->host_no); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" chip_version %u, lib_version %x, lib_serial_no %u, mcode_date %x\n", + c->chip_version, c->lib_version, c->lib_serial_no, c->mcode_date); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" mcode_version %x, err_code %u\n", + c->mcode_version, v->err_code); + ASC_PRT_NEXT(); + + /* Current number of commands waiting for the host. */ + len = asc_prt_line(cp, leftlen, +" Total Command Pending: %d\n", v->cur_total_qng); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Command Queuing:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + if ((boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + len = asc_prt_line(cp, leftlen, " %d:%c", + i, (v->use_tagged_qng & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + /* Current number of commands waiting for a device. */ + len = asc_prt_line(cp, leftlen, +" Command Queue Pending:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + if ((boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + len = asc_prt_line(cp, leftlen, " %d:%u", i, v->cur_dvc_qng[i]); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + /* Current limit on number of commands that can be sent to a device. */ + len = asc_prt_line(cp, leftlen, +" Command Queue Limit:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + if ((boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + len = asc_prt_line(cp, leftlen, " %d:%u", i, v->max_dvc_qng[i]); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + /* Indicate whether the device has returned queue full status. */ + len = asc_prt_line(cp, leftlen, +" Command Queue Full:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + if ((boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + if (boardp->queue_full & ADV_TID_TO_TIDMASK(i)) { + len = asc_prt_line(cp, leftlen, " %d:Y-%d", + i, boardp->queue_full_cnt[i]); + } else { + len = asc_prt_line(cp, leftlen, " %d:N", i); + } + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Synchronous Transfer:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ASC_MAX_TID; i++) { + if ((boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + len = asc_prt_line(cp, leftlen, " %d:%c", + i, (v->sdtr_done & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + for (i = 0; i <= ASC_MAX_TID; i++) { + uchar syn_period_ix; + + if ((boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + if ((v->sdtr_done & ADV_TID_TO_TIDMASK(i)) == 0) { + continue; + } + syn_period_ix = (boardp->sdtr_data[i] >> 4) & (v->max_sdtr_index - 1); + len = asc_prt_line(cp, leftlen, " %d:", i); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, + " Transfer Period Factor: %d (%d.%d Mhz),", + v->sdtr_period_tbl[syn_period_ix], + 250 / v->sdtr_period_tbl[syn_period_ix], + ASC_TENTHS(250, v->sdtr_period_tbl[syn_period_ix])); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, " REQ/ACK Offset: %d\n", + boardp->sdtr_data[i] & ASC_SYN_MAX_OFFSET); + ASC_PRT_NEXT(); + } + + return totlen; +} + +/* + * asc_prt_adv_board_info() + * + * Print dynamic board configuration information. + * + * Note: no single line should be greater than ASC_PRTLINE_SIZE, + * cf. asc_prt_line(). + * + * Return the number of characters copied into 'cp'. No more than + * 'cplen' characters will be copied to 'cp'. + */ +STATIC int +asc_prt_adv_board_info(struct Scsi_Host *shp, char *cp, int cplen) +{ + asc_board_t *boardp; + int leftlen; + int totlen; + int len; + int i; + ADV_DVC_VAR *v; + ADV_DVC_CFG *c; + AdvPortAddr iop_base; + ushort chip_scsi_id; + ushort lramword; + uchar lrambyte; + ushort sdtr_able; + ushort period; + + boardp = ASC_BOARDP(shp); + v = &boardp->dvc_var.adv_dvc_var; + c = &boardp->dvc_cfg.adv_dvc_cfg; + iop_base = v->iop_base; + chip_scsi_id = v->chip_scsi_id; + + leftlen = cplen; + totlen = len = 0; + + len = asc_prt_line(cp, leftlen, +"\nAdv Library Configuration and Statistics for AdvanSys SCSI Host %d:\n", + shp->host_no); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" iop_base %lx, cable_detect: %X, err_code %u, idle_cmd_done %u\n", + v->iop_base, + AdvReadWordRegister(iop_base, IOPW_SCSI_CFG1) & CABLE_DETECT, + v->err_code, v->idle_cmd_done); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" chip_version %u, lib_version %x, mcode_date %x, mcode_version %x\n", + c->chip_version, c->lib_version, c->mcode_date, c->mcode_version); + ASC_PRT_NEXT(); + + AdvReadWordLram(iop_base, ASC_MC_TAGQNG_ABLE, lramword); + len = asc_prt_line(cp, leftlen, +" Queuing Enabled:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + + len = asc_prt_line(cp, leftlen, " %X:%c", + i, (lramword & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Queue Limit:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + + AdvReadByteLram(iop_base, ASC_MC_NUMBER_OF_MAX_CMD + i, lrambyte); + + len = asc_prt_line(cp, leftlen, " %X:%d", i, lrambyte); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Command Pending:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + + AdvReadByteLram(iop_base, ASC_MC_NUMBER_OF_QUEUED_CMD + i, lrambyte); + + len = asc_prt_line(cp, leftlen, " %X:%d", i, lrambyte); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + AdvReadWordLram(iop_base, ASC_MC_WDTR_ABLE, lramword); + len = asc_prt_line(cp, leftlen, +" Wide Enabled:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + + len = asc_prt_line(cp, leftlen, " %X:%c", + i, (lramword & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" Transfer Bit Width:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + + AdvReadWordLram(iop_base, ASC_MC_DEVICE_HSHK_CFG_TABLE + (2 * i), + lramword); + len = asc_prt_line(cp, leftlen, " %X:%d", + i, (lramword & 0x8000) ? 16 : 8); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + AdvReadWordLram(iop_base, ASC_MC_SDTR_ABLE, sdtr_able); + len = asc_prt_line(cp, leftlen, +" Synchronous Enabled:"); + ASC_PRT_NEXT(); + for (i = 0; i <= ADV_MAX_TID; i++) { + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + + len = asc_prt_line(cp, leftlen, " %X:%c", + i, (sdtr_able & ADV_TID_TO_TIDMASK(i)) ? 'Y' : 'N'); + ASC_PRT_NEXT(); + } + len = asc_prt_line(cp, leftlen, "\n"); + ASC_PRT_NEXT(); + + for (i = 0; i <= ADV_MAX_TID; i++) { + + AdvReadWordLram(iop_base, ASC_MC_DEVICE_HSHK_CFG_TABLE + (2 * i), + lramword); + lramword &= ~0x8000; + + if ((chip_scsi_id == i) || + ((sdtr_able & ADV_TID_TO_TIDMASK(i)) == 0) || + (lramword == 0)) { + continue; + } + + len = asc_prt_line(cp, leftlen, " %X:", i); + ASC_PRT_NEXT(); + + period = (((lramword >> 8) * 25) + 50)/4; + + len = asc_prt_line(cp, leftlen, + " Transfer Period Factor: %d (%d.%d Mhz),", + period, 250/period, ASC_TENTHS(250, period)); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, " REQ/ACK Offset: %d\n", + lramword & 0x1F); + ASC_PRT_NEXT(); + } + + return totlen; +} + +/* + * asc_proc_copy() + * + * Copy proc information to a read buffer taking into account the current + * read offset in the file and the remaining space in the read buffer. + */ +STATIC int +asc_proc_copy(off_t advoffset, off_t offset, char *curbuf, int leftlen, + char *cp, int cplen) +{ + int cnt = 0; + + ASC_DBG3(2, "asc_proc_copy: offset %d, advoffset %d, cplen %d\n", + (unsigned) offset, (unsigned) advoffset, cplen); + if (offset <= advoffset) { + /* Read offset below current offset, copy everything. */ + cnt = ASC_MIN(cplen, leftlen); + ASC_DBG3(2, "asc_proc_copy: curbuf %x, cp %x, cnt %d\n", + (unsigned) curbuf, (unsigned) cp, cnt); + memcpy(curbuf, cp, cnt); + } else if (offset < advoffset + cplen) { + /* Read offset within current range, partial copy. */ + cnt = (advoffset + cplen) - offset; + cp = (cp + cplen) - cnt; + cnt = ASC_MIN(cnt, leftlen); + ASC_DBG3(2, "asc_proc_copy: curbuf %x, cp %x, cnt %d\n", + (unsigned) curbuf, (unsigned) cp, cnt); + memcpy(curbuf, cp, cnt); + } + return cnt; +} + +/* + * asc_prt_line() + * + * If 'cp' is NULL print to the console, otherwise print to a buffer. + * + * Return 0 if printing to the console, otherwise return the number of + * bytes written to the buffer. + * + * Note: If any single line is greater than ASC_PRTLINE_SIZE bytes the stack + * will be corrupted. 's[]' is defined to be ASC_PRTLINE_SIZE bytes. + */ +STATIC int +asc_prt_line(char *buf, int buflen, char *fmt, ...) +{ + va_list args; + int ret; + char s[ASC_PRTLINE_SIZE]; + + va_start(args, fmt); + ret = vsprintf(s, fmt, args); + ASC_ASSERT(ret < ASC_PRTLINE_SIZE); + if (buf == NULL) { + (void) printk(s); + ret = 0; + } else { + ret = ASC_MIN(buflen, ret); + memcpy(buf, s, ret); + } + va_end(args); + return ret; +} +#endif /* version >= v1.3.0 */ + + +/* + * --- Functions Required by the Asc Library + */ + +/* + * Delay for 'n' milliseconds. Don't use the 'jiffies' + * global variable which is incremented once every 5 ms + * from a timer interrupt, because this function may be + * called when interrupts are disabled. + */ +STATIC void +DvcSleepMilliSecond(ulong n) +{ + ulong i; + + ASC_DBG1(4, "DvcSleepMilliSecond: %lu\n", n); + for (i = 0; i < n; i++) { + udelay(1000); + } +} + +STATIC int +DvcEnterCritical(void) +{ + int flags; + + save_flags(flags); + cli(); + return flags; +} + +STATIC void +DvcLeaveCritical(int flags) +{ + restore_flags(flags); +} + +STATIC ulong +DvcGetSGList(ASC_DVC_VAR *asc_dvc_sg, uchar *buf_addr, ulong buf_len, + ASC_SG_HEAD *asc_sg_head_ptr) +{ + ulong buf_size; + + buf_size = buf_len; + asc_sg_head_ptr->entry_cnt = 1; +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + asc_sg_head_ptr->sg_list[0].addr = (ulong) buf_addr; +#else /* version >= v2.0.0 */ + asc_sg_head_ptr->sg_list[0].addr = virt_to_bus(buf_addr); +#endif /* version >= v2.0.0 */ + asc_sg_head_ptr->sg_list[0].bytes = buf_size; + return buf_size; +} + +/* + * void + * DvcPutScsiQ(PortAddr iop_base, ushort s_addr, ushort *outbuf, int words) + * + * Calling/Exit State: + * none + * + * Description: + * Output an ASC_SCSI_Q structure to the chip + */ +STATIC void +DvcPutScsiQ(PortAddr iop_base, ushort s_addr, ushort *outbuf, int words) +{ + int i; + + ASC_DBG_PRT_HEX(2, "DvcPutScsiQ", (uchar *) outbuf, 2 * words); + AscSetChipLramAddr(iop_base, s_addr); + for (i = 0; i < words; i++, outbuf++) { + if (i == 2 || i == 10) { + continue; + } + AscSetChipLramDataNoSwap(iop_base, *outbuf); + } +} + +/* + * void + * DvcGetQinfo(PortAddr iop_base, ushort s_addr, ushort *inbuf, int words) + * + * Calling/Exit State: + * none + * + * Description: + * Input an ASC_QDONE_INFO structure from the chip + */ +STATIC void +DvcGetQinfo(PortAddr iop_base, ushort s_addr, ushort *inbuf, int words) +{ + int i; + + AscSetChipLramAddr(iop_base, s_addr); + for (i = 0; i < words; i++, inbuf++) { + if (i == 5) { + continue; + } + *inbuf = AscGetChipLramDataNoSwap(iop_base); + } + ASC_DBG_PRT_HEX(2, "DvcGetQinfo", (uchar *) inbuf, 2 * words); +} + +/* + * void DvcOutPortWords(ushort iop_base, ushort &outbuf, int words) + * + * Calling/Exit State: + * none + * + * Description: + * output a buffer to an i/o port address + */ +STATIC void +DvcOutPortWords(ushort iop_base, ushort *outbuf, int words) +{ + int i; + + for (i = 0; i < words; i++, outbuf++) + outpw(iop_base, *outbuf); +} + +/* + * void DvcInPortWords(ushort iop_base, ushort &outbuf, int words) + * + * Calling/Exit State: + * none + * + * Description: + * input a buffer from an i/o port address + */ +STATIC void +DvcInPortWords(ushort iop_base, ushort *inbuf, int words) +{ + int i; + + for (i = 0; i < words; i++, inbuf++) + *inbuf = inpw(iop_base); +} + +/* + * void DvcOutPortDWords(PortAddr port, ulong *pdw, int dwords) + * + * Calling/Exit State: + * none + * + * Description: + * output a buffer of 32-bit integers to an i/o port address in + * 16 bit integer units + */ +STATIC void +DvcOutPortDWords(PortAddr port, ulong *pdw, int dwords) +{ + int i; + int words; + ushort *pw; + + pw = (ushort *) pdw; + words = dwords << 1; + for(i = 0; i < words; i++, pw++) { + outpw(port, *pw); + } + return; +} + +/* + * Read a PCI configuration byte. + */ +ASC_INITFUNC( +STATIC uchar +DvcReadPCIConfigByte( + ASC_DVC_VAR asc_ptr_type *asc_dvc, + ushort offset) +) +{ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + PCI_DATA pciData; + + pciData.bus = ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info); + pciData.slot = ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info); + pciData.func = ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info); + pciData.offset = offset; + pciData.type = pci_scan_method; + return asc_get_cfg_byte(&pciData); +#else /* ASC_CONFIG_PCI */ + return 0; +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + uchar byte_data; + pcibios_read_config_byte(ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info), + PCI_DEVFN(ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info), + ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info)), + offset, &byte_data); + return byte_data; +#else /* CONFIG_PCI */ + return 0; +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ +} + +/* + * Write a PCI configuration byte. + */ +ASC_INITFUNC( +STATIC void +DvcWritePCIConfigByte( + ASC_DVC_VAR asc_ptr_type *asc_dvc, + ushort offset, + uchar byte_data) +) +{ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + PCI_DATA pciData; + + pciData.bus = ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info); + pciData.slot = ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info); + pciData.func = ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info); + pciData.offset = offset; + pciData.type = pci_scan_method; + asc_put_cfg_byte(&pciData, byte_data); +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + pcibios_write_config_byte(ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info), + PCI_DEVFN(ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info), + ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info)), + offset, byte_data); +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ +} + +/* + * Return the BIOS address of the adapter at the specified + * I/O port and with the specified bus type. + */ +ASC_INITFUNC( +STATIC ushort +AscGetChipBiosAddress( + PortAddr iop_base, + ushort bus_type +) +) +{ + ushort cfg_lsw ; + ushort bios_addr ; + + /* + * The PCI BIOS is re-located by the motherboard BIOS. Because + * of this the driver can not determine where a PCI BIOS is + * loaded and executes. + */ + if (bus_type & ASC_IS_PCI) + { + return(0); + } + + if((bus_type & ASC_IS_EISA) != 0) + { + cfg_lsw = AscGetEisaChipCfg(iop_base) ; + cfg_lsw &= 0x000F ; + bios_addr = (ushort)(ASC_BIOS_MIN_ADDR + + (cfg_lsw * ASC_BIOS_BANK_SIZE)) ; + return(bios_addr) ; + }/* if */ + + cfg_lsw = AscGetChipCfgLsw(iop_base) ; + + /* + * ISA PnP uses the top bit as the 32K BIOS flag + */ + if (bus_type == ASC_IS_ISAPNP) + { + cfg_lsw &= 0x7FFF; + }/* if */ + + bios_addr = (ushort)(((cfg_lsw >> 12) * ASC_BIOS_BANK_SIZE) + + ASC_BIOS_MIN_ADDR) ; + return(bios_addr) ; +} + + +/* + * --- Functions Required by the Adv Library + */ + +/* + * DvcGetPhyAddr() + * + * Return the physical address of 'vaddr' and set '*lenp' to the + * number of physically contiguous bytes that follow 'vaddr'. + * 'flag' indicates the type of structure whose physical address + * is being translated. + * + * Note: Because Linux currently doesn't page the kernel and all + * kernel buffers are physically contiguous, leave '*lenp' unchanged. + */ +ulong +DvcGetPhyAddr(ADV_DVC_VAR *asc_dvc, ADV_SCSI_REQ_Q *scsiq, + uchar *vaddr, long *lenp, int flag) +{ + ulong paddr; + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,0,0) + paddr = (ulong) vaddr; +#else /* version >= v2.0.0 */ + paddr = virt_to_bus(vaddr); +#endif /* version >= v2.0.0 */ + + ASC_DBG4(4, + "DvcGetPhyAddr: vaddr 0x%lx, lenp 0x%lx *lenp %lu, paddr 0x%lx\n", + (ulong) vaddr, (ulong) lenp, (ulong) *((ulong *) lenp), paddr); + + return paddr; +} + +/* + * Read a PCI configuration byte. + */ +ASC_INITFUNC( +STATIC uchar +DvcAdvReadPCIConfigByte( + ADV_DVC_VAR *asc_dvc, + ushort offset) +) +{ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + PCI_DATA pciData; + + pciData.bus = ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info); + pciData.slot = ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info); + pciData.func = ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info); + pciData.offset = offset; + pciData.type = pci_scan_method; + return asc_get_cfg_byte(&pciData); +#else /* ASC_CONFIG_PCI */ + return 0; +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + uchar byte_data; + pcibios_read_config_byte(ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info), + PCI_DEVFN(ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info), + ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info)), + offset, &byte_data); + return byte_data; +#else /* CONFIG_PCI */ + return 0; +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ +} + +/* + * Write a PCI configuration byte. + */ +ASC_INITFUNC( +STATIC void +DvcAdvWritePCIConfigByte( + ADV_DVC_VAR *asc_dvc, + ushort offset, + uchar byte_data) +) +{ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,93) +#ifdef ASC_CONFIG_PCI + PCI_DATA pciData; + + pciData.bus = ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info); + pciData.slot = ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info); + pciData.func = ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info); + pciData.offset = offset; + pciData.type = pci_scan_method; + asc_put_cfg_byte(&pciData, byte_data); +#endif /* ASC_CONFIG_PCI */ +#else /* version >= v2.1.93 */ +#ifdef CONFIG_PCI + pcibios_write_config_byte(ASC_PCI_ID2BUS(asc_dvc->cfg->pci_slot_info), + PCI_DEVFN(ASC_PCI_ID2DEV(asc_dvc->cfg->pci_slot_info), + ASC_PCI_ID2FUNC(asc_dvc->cfg->pci_slot_info)), + offset, byte_data); +#endif /* CONFIG_PCI */ +#endif /* version >= v2.1.93 */ +} + +/* + * --- Tracing and Debugging Functions + */ + +#ifdef ADVANSYS_STATS +/* + * asc_prt_board_stats() + * + * Note: no single line should be greater than ASC_PRTLINE_SIZE, + * cf. asc_prt_line(). + * + * Return the number of characters copied into 'cp'. No more than + * 'cplen' characters will be copied to 'cp'. + */ +STATIC int +asc_prt_board_stats(struct Scsi_Host *shp, char *cp, int cplen) +{ + int leftlen; + int totlen; + int len; + struct asc_stats *s; + int i; + ushort chip_scsi_id; + asc_board_t *boardp; + asc_queue_t *active; + asc_queue_t *waiting; + + leftlen = cplen; + totlen = len = 0; + + boardp = ASC_BOARDP(shp); + s = &boardp->asc_stats; + + len = asc_prt_line(cp, leftlen, +"\nLinux Driver Statistics for AdvanSys SCSI Host %d:\n", shp->host_no); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" command %lu, queuecommand %lu, abort %lu, reset %lu, biosparam %lu\n", + s->command, s->queuecommand, s->abort, s->reset, s->biosparam); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" interrupt %lu, callback %lu, done %lu\n", + s->interrupt, s->callback, s->done); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" exe_noerror %lu, exe_busy %lu, exe_error %lu, exe_unknown %lu\n", + s->exe_noerror, s->exe_busy, s->exe_error, s->exe_unknown); + ASC_PRT_NEXT(); + + if (ASC_NARROW_BOARD(boardp)) { + len = asc_prt_line(cp, leftlen, +" build_error %lu\n", + s->build_error); + } else { + len = asc_prt_line(cp, leftlen, +" build_error %lu, build_noreq %lu, build_nosg %lu\n", + s->build_error, s->adv_build_noreq, s->adv_build_nosg); + } + ASC_PRT_NEXT(); + + /* + * Display data transfer statistics. + */ + if (s->cont_cnt > 0) { + len = asc_prt_line(cp, leftlen, " cont_cnt %lu, ", s->cont_cnt); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, "cont_xfer %lu.%01lu kb ", + s->cont_xfer/2, + ASC_TENTHS(s->cont_xfer, 2)); + ASC_PRT_NEXT(); + + /* Contiguous transfer average size */ + len = asc_prt_line(cp, leftlen, "avg_xfer %lu.%01lu kb\n", + (s->cont_xfer/2)/s->cont_cnt, + ASC_TENTHS((s->cont_xfer/2), s->cont_cnt)); + ASC_PRT_NEXT(); + } + + if (s->sg_cnt > 0) { + + len = asc_prt_line(cp, leftlen, " sg_cnt %lu, sg_elem %lu, ", + s->sg_cnt, s->sg_elem); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, "sg_xfer %lu.%01lu kb\n", + s->sg_xfer/2, + ASC_TENTHS(s->sg_xfer, 2)); + ASC_PRT_NEXT(); + + /* Scatter gather transfer statistics */ + len = asc_prt_line(cp, leftlen, " avg_num_elem %lu.%01lu, ", + s->sg_elem/s->sg_cnt, + ASC_TENTHS(s->sg_elem, s->sg_cnt)); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, "avg_elem_size %lu.%01lu kb, ", + (s->sg_xfer/2)/s->sg_elem, + ASC_TENTHS((s->sg_xfer/2), s->sg_elem)); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, "avg_xfer_size %lu.%01lu kb\n", + (s->sg_xfer/2)/s->sg_cnt, + ASC_TENTHS((s->sg_xfer/2), s->sg_cnt)); + ASC_PRT_NEXT(); + } + + /* + * Display request queuing statistics. + */ + len = asc_prt_line(cp, leftlen, +" Active and Waiting Request Queues (Time Unit: %d HZ):\n", HZ); + ASC_PRT_NEXT(); + + active = &ASC_BOARDP(shp)->active; + waiting = &ASC_BOARDP(shp)->waiting; + + if (ASC_NARROW_BOARD(boardp)) { + chip_scsi_id = boardp->dvc_cfg.asc_dvc_cfg.chip_scsi_id; + } else { + chip_scsi_id = boardp->dvc_var.adv_dvc_var.chip_scsi_id; + } + + for (i = 0; i <= ADV_MAX_TID; i++) { + + if ((chip_scsi_id == i) || + ((boardp->init_tidmask & ADV_TID_TO_TIDMASK(i)) == 0)) { + continue; + } + + if (active->q_tot_cnt[i] > 0 || waiting->q_tot_cnt[i] > 0) { + len = asc_prt_line(cp, leftlen, " target %d\n", i); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" active: cnt [cur %d, max %d, tot %u], time [min %d, max %d, avg %lu.%01lu]\n", + active->q_cur_cnt[i], active->q_max_cnt[i], + active->q_tot_cnt[i], + active->q_min_tim[i], active->q_max_tim[i], + (active->q_tot_cnt[i] == 0) ? 0 : + (active->q_tot_tim[i]/active->q_tot_cnt[i]), + (active->q_tot_cnt[i] == 0) ? 0 : + ASC_TENTHS(active->q_tot_tim[i], active->q_tot_cnt[i])); + ASC_PRT_NEXT(); + + len = asc_prt_line(cp, leftlen, +" waiting: cnt [cur %d, max %d, tot %u], time [min %u, max %u, avg %lu.%01lu]\n", + waiting->q_cur_cnt[i], waiting->q_max_cnt[i], + waiting->q_tot_cnt[i], + waiting->q_min_tim[i], waiting->q_max_tim[i], + (waiting->q_tot_cnt[i] == 0) ? 0 : + (waiting->q_tot_tim[i]/waiting->q_tot_cnt[i]), + (waiting->q_tot_cnt[i] == 0) ? 0 : + ASC_TENTHS(waiting->q_tot_tim[i], waiting->q_tot_cnt[i])); + ASC_PRT_NEXT(); + } + } + + return totlen; +} +#endif /* ADVANSYS_STATS */ + +#ifdef ADVANSYS_DEBUG +/* + * asc_prt_scsi_host() + */ +STATIC void +asc_prt_scsi_host(struct Scsi_Host *s) +{ + asc_board_t *boardp; + + boardp = ASC_BOARDP(s); + + printk("Scsi_Host at addr %x\n", (unsigned) s); + printk( +" next %x, extra_bytes %u, host_busy %u, host_no %d, last_reset %d,\n", + (unsigned) s->next, s->extra_bytes, s->host_busy, s->host_no, + (unsigned) s->last_reset); + + printk( +" host_wait %x, host_queue %x, hostt %x, block %x,\n", + (unsigned) s->host_wait, (unsigned) s->host_queue, + (unsigned) s->hostt, (unsigned) s->block); + + printk( +" wish_block %d, base %x, io_port %d, n_io_port %d, irq %d, dma_channel %d,\n", + s->wish_block, (unsigned) s->base, s->io_port, s->n_io_port, + s->irq, s->dma_channel); + + printk( +" this_id %d, can_queue %d,\n", s->this_id, s->can_queue); + + printk( +" cmd_per_lun %d, sg_tablesize %d, unchecked_isa_dma %d, loaded_as_module %d\n", + s->cmd_per_lun, s->sg_tablesize, s->unchecked_isa_dma, + s->loaded_as_module); + + if (ASC_NARROW_BOARD(boardp)) { + asc_prt_asc_dvc_var(&ASC_BOARDP(s)->dvc_var.asc_dvc_var); + asc_prt_asc_dvc_cfg(&ASC_BOARDP(s)->dvc_cfg.asc_dvc_cfg); + } else { + asc_prt_adv_dvc_var(&ASC_BOARDP(s)->dvc_var.adv_dvc_var); + asc_prt_adv_dvc_cfg(&ASC_BOARDP(s)->dvc_cfg.adv_dvc_cfg); + } +} + +/* + * asc_prt_scsi_cmnd() + */ +STATIC void +asc_prt_scsi_cmnd(Scsi_Cmnd *s) +{ + printk("Scsi_Cmnd at addr %x\n", (unsigned) s); + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) + printk( +" host %x, device %x, target %u, lun %u\n", + (unsigned) s->host, (unsigned) s->device, s->target, s->lun); +#else /* version >= v1.3.0 */ + printk( +" host %x, device %x, target %u, lun %u, channel %u,\n", + (unsigned) s->host, (unsigned) s->device, s->target, s->lun, + s->channel); +#endif /* version >= v1.3.0 */ + + asc_prt_hex(" CDB", s->cmnd, s->cmd_len); + + printk( +" use_sg %u, sglist_len %u, abort_reason %x\n", + s->use_sg, s->sglist_len, s->abort_reason); + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,89) + printk( +" retries %d, allowed %d\n", + s->retries, s->allowed); +#else /* version >= v1.3.89 */ + printk( +" serial_number %x, serial_number_at_timeout %x, retries %d, allowed %d\n", + (unsigned) s->serial_number, (unsigned) s->serial_number_at_timeout, + s->retries, s->allowed); +#endif /* version >= v1.3.89 */ + + printk( +" timeout_per_command %d, timeout_total %d, timeout %d\n", + s->timeout_per_command, s->timeout_total, s->timeout); + + printk( +" internal_timeout %u, flags %u, this_count %d\n", + s->internal_timeout, s->flags, s->this_count); + + printk( +" scsi_done %x, done %x, host_scribble %x, result %x\n", + (unsigned) s->scsi_done, (unsigned) s->done, + (unsigned) s->host_scribble, s->result); + + printk( +" tag %u, pid %u\n", + (unsigned) s->tag, (unsigned) s->pid); +} + +/* + * asc_prt_asc_dvc_var() + */ +STATIC void +asc_prt_asc_dvc_var(ASC_DVC_VAR *h) +{ + printk("ASC_DVC_VAR at addr %x\n", (unsigned) h); + + printk( +" iop_base %x, err_code %x, dvc_cntl %x, bug_fix_cntl %d,\n", + h->iop_base, h->err_code, h->dvc_cntl, h->bug_fix_cntl); + + printk( +" bus_type %d, isr_callback %x, exe_callback %x, init_sdtr %x,\n", + h->bus_type, (unsigned) h->isr_callback, (unsigned) h->exe_callback, + (unsigned) h->init_sdtr); + + printk( +" sdtr_done %x, use_tagged_qng %x, unit_not_ready %x, chip_no %x,\n", + (unsigned) h->sdtr_done, (unsigned) h->use_tagged_qng, + (unsigned) h->unit_not_ready, (unsigned) h->chip_no); + + printk( +" queue_full_or_busy %x, start_motor %x, scsi_reset_wait %x, irq_no %x,\n", + (unsigned) h->queue_full_or_busy, (unsigned) h->start_motor, + (unsigned) h->scsi_reset_wait, (unsigned) h->irq_no); + + printk( +" is_in_int %x, max_total_qng %x, cur_total_qng %x, in_critical_cnt %x,\n", + (unsigned) h->is_in_int, (unsigned) h->max_total_qng, + (unsigned) h->cur_total_qng, (unsigned) h->in_critical_cnt); + + printk( +" last_q_shortage %x, init_state %x, no_scam %x, pci_fix_asyn_xfer %x,\n", + (unsigned) h->last_q_shortage, (unsigned) h->init_state, + (unsigned) h->no_scam, (unsigned) h->pci_fix_asyn_xfer); + + printk( +" cfg %x, saved_ptr2func %x\n", + (unsigned) h->cfg, (unsigned) h->saved_ptr2func); +} + +/* + * asc_prt_asc_dvc_cfg() + */ +STATIC void +asc_prt_asc_dvc_cfg(ASC_DVC_CFG *h) +{ + printk("ASC_DVC_CFG at addr %x\n", (unsigned) h); + + printk( +" can_tagged_qng %x, cmd_qng_enabled %x, disc_enable %x, sdtr_enable %x,\n", + h->can_tagged_qng, h->cmd_qng_enabled, h->disc_enable, + h->sdtr_enable); + + printk( +" chip_scsi_id %d, isa_dma_speed %d, isa_dma_channel %d, chip_version %d,\n", + h->chip_scsi_id, h->isa_dma_speed, h->isa_dma_channel, + h->chip_version); + + printk( +" pci_device_id %d, lib_serial_no %x, lib_version %x, mcode_date %x,\n", + h->pci_device_id, h->lib_serial_no, h->lib_version, h->mcode_date); + + printk( +" mcode_version %d, overrun_buf %x\n", + h->mcode_version, (unsigned) h->overrun_buf); +} + +/* + * asc_prt_asc_scsi_q() + */ +STATIC void +asc_prt_asc_scsi_q(ASC_SCSI_Q *q) +{ + ASC_SG_HEAD *sgp; + int i; + + printk("ASC_SCSI_Q at addr %x\n", (unsigned) q); + + printk( +" target_ix %u, target_lun %u, srb_ptr %x, tag_code %u,\n", + q->q2.target_ix, q->q1.target_lun, + (unsigned) q->q2.srb_ptr, q->q2.tag_code); + + printk( +" data_addr %x, data_cnt %lu, sense_addr %x, sense_len %u,\n", + (unsigned) q->q1.data_addr, q->q1.data_cnt, + (unsigned) q->q1.sense_addr, q->q1.sense_len); + + printk( +" cdbptr %x, cdb_len %u, sg_head %x, sg_queue_cnt %u\n", + (unsigned) q->cdbptr, q->q2.cdb_len, + (unsigned) q->sg_head, q->q1.sg_queue_cnt); + + if (q->sg_head) { + sgp = q->sg_head; + printk("ASC_SG_HEAD at addr %x\n", (unsigned) sgp); + printk(" entry_cnt %u, queue_cnt %u\n", sgp->entry_cnt, sgp->queue_cnt); + for (i = 0; i < sgp->entry_cnt; i++) { + printk(" [%u]: addr %x, bytes %lu\n", + i, (unsigned) sgp->sg_list[i].addr, sgp->sg_list[i].bytes); + } + + } +} + +/* + * asc_prt_asc_qdone_info() + */ +STATIC void +asc_prt_asc_qdone_info(ASC_QDONE_INFO *q) +{ + printk("ASC_QDONE_INFO at addr %x\n", (unsigned) q); + printk( +" srb_ptr %x, target_ix %u, cdb_len %u, tag_code %u, done_stat %x\n", + (unsigned) q->d2.srb_ptr, q->d2.target_ix, q->d2.cdb_len, + q->d2.tag_code, q->d3.done_stat); + printk( +" host_stat %x, scsi_stat %x, scsi_msg %x\n", + q->d3.host_stat, q->d3.scsi_stat, q->d3.scsi_msg); +} + +/* + * asc_prt_adv_dvc_var() + * + * Display an ADV_DVC_VAR structure. + */ +STATIC void +asc_prt_adv_dvc_var(ADV_DVC_VAR *h) +{ + printk(" ADV_DVC_VAR at addr 0x%lx\n", (ulong) h); + + printk( +" iop_base 0x%lx, err_code 0x%x, ultra_able 0x%x\n", + (ulong) h->iop_base, h->err_code, (unsigned) h->ultra_able); + + printk( +" isr_callback 0x%x, sdtr_able 0x%x, wdtr_able 0x%x\n", + (unsigned) h->isr_callback, (unsigned) h->wdtr_able, + (unsigned) h->sdtr_able); + + printk( +" start_motor 0x%x, scsi_reset_wait 0x%x, irq_no 0x%x,\n", + (unsigned) h->start_motor, + (unsigned) h->scsi_reset_wait, (unsigned) h->irq_no); + + printk( +" max_host_qng 0x%x, cur_host_qng 0x%x, max_dvc_qng 0x%x\n", + (unsigned) h->max_host_qng, (unsigned) h->cur_host_qng, + (unsigned) h->max_dvc_qng); + + printk( +" no_scam 0x%x, tagqng_able 0x%x, chip_scsi_id 0x%x, cfg 0x%lx\n", + (unsigned) h->no_scam, (unsigned) h->tagqng_able, + (unsigned) h->chip_scsi_id, (ulong) h->cfg); + +} + +/* + * asc_prt_adv_dvc_cfg() + * + * Display an ADV_DVC_CFG structure. + */ +STATIC void +asc_prt_adv_dvc_cfg(ADV_DVC_CFG *h) +{ + printk(" ADV_DVC_CFG at addr 0x%lx\n", (ulong) h); + + printk( +" disc_enable 0x%x, termination 0x%x\n", + h->disc_enable, h->termination); + + printk( +" chip_version 0x%x, mcode_date 0x%x\n", + h->chip_version, h->mcode_date); + + printk( +" mcode_version 0x%x, pci_device_id 0x%x, lib_version 0x%x\n", + h->mcode_version, h->pci_device_id, h->lib_version); + + printk( +" control_flag 0x%x, pci_slot_info 0x%x\n", + h->control_flag, h->pci_slot_info); +} + +/* + * asc_prt_adv_scsi_req_q() + * + * Display an ADV_SCSI_REQ_Q structure. + */ +STATIC void +asc_prt_adv_scsi_req_q(ADV_SCSI_REQ_Q *q) +{ + int i; + struct asc_sg_block *sg_ptr; + + printk("ADV_SCSI_REQ_Q at addr %x\n", (unsigned) q); + + printk( +" target_id %u, target_lun %u, srb_ptr 0x%lx, a_flag 0x%x\n", + q->target_id, q->target_lun, q->srb_ptr, q->a_flag); + + printk(" cntl 0x%x, data_addr 0x%lx, vdata_addr 0x%lx\n", + q->cntl, q->data_addr, q->vdata_addr); + + printk( +" data_cnt %lu, sense_addr 0x%lx, sense_len %u,\n", + q->data_cnt, q->sense_addr, q->sense_len); + + printk( +" cdb_len %u, done_status 0x%x, host_status 0x%x, scsi_status 0x%x\n", + q->cdb_len, q->done_status, q->host_status, q->scsi_status); + + printk( +" vsense_addr 0x%lx, scsiq_ptr 0x%lx, ux_wk_data_cnt %lu\n", + (ulong) q->vsense_addr, (ulong) q->scsiq_ptr, + (ulong) q->ux_wk_data_cnt); + + printk( +" sg_list_ptr 0x%lx, sg_real_addr 0x%lx, sg_entry_cnt %u\n", + (ulong) q->sg_list_ptr, (ulong) q->sg_real_addr, q->sg_entry_cnt); + + printk( +" ux_sg_ix %u, orig_sense_len %u\n", + q->ux_sg_ix, q->orig_sense_len); + + /* Display the request's ADV_SG_BLOCK structures. */ + for (sg_ptr = q->sg_list_ptr, i = 0; sg_ptr != NULL; + sg_ptr = sg_ptr->sg_ptr, i++) { + /* + * 'sg_ptr' is a physical address. Convert it to a virtual + * address by indexing 'i' into the virtual address array + * 'sg_list_ptr'. + * + * At the end of the each iteration of the loop 'sg_ptr' is + * converted back into a physical address by setting 'sg_ptr' + * to the next pointer 'sg_ptr->sg_ptr'. + */ + sg_ptr = &(((ADV_SG_BLOCK *) (q->sg_list_ptr))[i]); + asc_prt_adv_sgblock(i, sg_ptr); + } +} + +/* + * asc_prt_adv_sgblock() + * + * Display an ADV_SG_BLOCK structure. + */ +STATIC void +asc_prt_adv_sgblock(int sgblockno, ADV_SG_BLOCK *b) +{ + int i, s; + + /* Calculate starting entry number for the current block. */ + s = sgblockno * NO_OF_SG_PER_BLOCK; + + printk(" ADV_SG_BLOCK at addr 0x%lx (sgblockno %lu)\n", + (ulong) b, (ulong) sgblockno); + printk( +" first_entry_no %lu, last_entry_no %lu, sg_ptr 0x%lx\n", + (ulong) b->first_entry_no, (ulong) b->last_entry_no, (ulong) b->sg_ptr); + ASC_ASSERT(b->first_entry_no - s >= 0); + ASC_ASSERT(b->last_entry_no - s >= 0); + ASC_ASSERT(b->last_entry_no - s <= NO_OF_SG_PER_BLOCK); + ASC_ASSERT(b->first_entry_no - s <= NO_OF_SG_PER_BLOCK); + ASC_ASSERT(b->first_entry_no - s <= NO_OF_SG_PER_BLOCK); + ASC_ASSERT(b->first_entry_no - s <= b->last_entry_no - s); + for (i = b->first_entry_no - s; i <= b->last_entry_no - s; i++) { + printk(" [%lu]: sg_addr 0x%lx, sg_count 0x%lx\n", + (ulong) i, (ulong) b->sg_list[i].sg_addr, + (ulong) b->sg_list[i].sg_count); + } +} + +/* + * asc_prt_hex() + * + * Print hexadecimal output in 4 byte groupings 32 bytes + * or 8 double-words per line. + */ +STATIC void +asc_prt_hex(char *f, uchar *s, int l) +{ + int i; + int j; + int k; + int m; + + printk("%s: (%d bytes)\n", f, l); + + for (i = 0; i < l; i += 32) { + + /* Display a maximum of 8 double-words per line. */ + if ((k = (l - i) / 4) >= 8) { + k = 8; + m = 0; + } else { + m = (l - i) % 4 ; + } + + for (j = 0; j < k; j++) { + printk(" %2.2X%2.2X%2.2X%2.2X", + (unsigned) s[i+(j*4)], (unsigned) s[i+(j*4)+1], + (unsigned) s[i+(j*4)+2], (unsigned) s[i+(j*4)+3]); + } + + switch (m) { + case 0: + default: + break; + case 1: + printk(" %2.2X", + (unsigned) s[i+(j*4)]); + break; + case 2: + printk(" %2.2X%2.2X", + (unsigned) s[i+(j*4)], + (unsigned) s[i+(j*4)+1]); + break; + case 3: + printk(" %2.2X%2.2X%2.2X", + (unsigned) s[i+(j*4)+1], + (unsigned) s[i+(j*4)+2], + (unsigned) s[i+(j*4)+3]); + break; + } + + printk("\n"); + } +} +#endif /* ADVANSYS_DEBUG */ + +#ifdef ADVANSYS_ASSERT +/* + * interrupts_enabled() + * + * Return 1 if interrupts are enabled, otherwise return 0. + */ +STATIC int +interrupts_enabled(void) +{ + int flags; + + save_flags(flags); + if (flags & 0x0200) { + return ASC_TRUE; + } else { + return ASC_FALSE; + } +} +#endif /* ADVANSYS_ASSERT */ + + +/* + * --- Asc Library Functions + */ + +ASC_INITFUNC( +STATIC ushort +AscGetEisaChipCfg( + PortAddr iop_base +) +) +{ + PortAddr eisa_cfg_iop; + + eisa_cfg_iop = (PortAddr) ASC_GET_EISA_SLOT(iop_base) | + (PortAddr) (ASC_EISA_CFG_IOP_MASK); + return (inpw(eisa_cfg_iop)); +} + +ASC_INITFUNC( +STATIC uchar +AscSetChipScsiID( + PortAddr iop_base, + uchar new_host_id +) +) +{ + ushort cfg_lsw; + + if (AscGetChipScsiID(iop_base) == new_host_id) { + return (new_host_id); + } + cfg_lsw = AscGetChipCfgLsw(iop_base); + cfg_lsw &= 0xF8FF; + cfg_lsw |= (ushort) ((new_host_id & ASC_MAX_TID) << 8); + AscSetChipCfgLsw(iop_base, cfg_lsw); + return (AscGetChipScsiID(iop_base)); +} + +ASC_INITFUNC( +STATIC uchar +AscGetChipScsiCtrl( + PortAddr iop_base +) +) +{ + uchar sc; + + AscSetBank(iop_base, 1); + sc = inp(iop_base + IOP_REG_SC); + AscSetBank(iop_base, 0); + return (sc); +} + +ASC_INITFUNC( +STATIC uchar +AscGetChipVersion( + PortAddr iop_base, + ushort bus_type +) +) +{ + if ((bus_type & ASC_IS_EISA) != 0) { + PortAddr eisa_iop; + uchar revision; + eisa_iop = (PortAddr) ASC_GET_EISA_SLOT(iop_base) | + (PortAddr) ASC_EISA_REV_IOP_MASK; + revision = inp(eisa_iop); + return ((uchar) ((ASC_CHIP_MIN_VER_EISA - 1) + revision)); + } + return (AscGetChipVerNo(iop_base)); +} + +ASC_INITFUNC( +STATIC ushort +AscGetChipBusType( + PortAddr iop_base +) +) +{ + ushort chip_ver; + + chip_ver = AscGetChipVerNo(iop_base); + if ( + (chip_ver >= ASC_CHIP_MIN_VER_VL) + && (chip_ver <= ASC_CHIP_MAX_VER_VL) +) { + if ( + ((iop_base & 0x0C30) == 0x0C30) + || ((iop_base & 0x0C50) == 0x0C50) +) { + return (ASC_IS_EISA); + } + return (ASC_IS_VL); + } + if ((chip_ver >= ASC_CHIP_MIN_VER_ISA) && + (chip_ver <= ASC_CHIP_MAX_VER_ISA)) { + if (chip_ver >= ASC_CHIP_MIN_VER_ISA_PNP) { + return (ASC_IS_ISAPNP); + } + return (ASC_IS_ISA); + } else if ((chip_ver >= ASC_CHIP_MIN_VER_PCI) && + (chip_ver <= ASC_CHIP_MAX_VER_PCI)) { + return (ASC_IS_PCI); + } + return (0); +} + +ASC_INITFUNC( +STATIC ulong +AscLoadMicroCode( + PortAddr iop_base, + ushort s_addr, + ushort *mcode_buf, + ushort mcode_size +) +) +{ + ulong chksum; + ushort mcode_word_size; + ushort mcode_chksum; + + mcode_word_size = (ushort) (mcode_size >> 1); + AscMemWordSetLram(iop_base, s_addr, 0, mcode_word_size); + AscMemWordCopyToLram(iop_base, s_addr, mcode_buf, mcode_word_size); + chksum = AscMemSumLramWord(iop_base, s_addr, mcode_word_size); + mcode_chksum = (ushort) AscMemSumLramWord(iop_base, + (ushort) ASC_CODE_SEC_BEG, + (ushort) ((mcode_size - s_addr - (ushort) ASC_CODE_SEC_BEG) / 2)); + AscWriteLramWord(iop_base, ASCV_MCODE_CHKSUM_W, mcode_chksum); + AscWriteLramWord(iop_base, ASCV_MCODE_SIZE_W, mcode_size); + return (chksum); +} + +ASC_INITFUNC( +STATIC int +AscFindSignature( + PortAddr iop_base +) +) +{ + ushort sig_word; + + if (AscGetChipSignatureByte(iop_base) == (uchar) ASC_1000_ID1B) { + sig_word = AscGetChipSignatureWord(iop_base); + if ((sig_word == (ushort) ASC_1000_ID0W) || + (sig_word == (ushort) ASC_1000_ID0W_FIX)) { + return (1); + } + } + return (0); +} + +STATIC uchar _isa_pnp_inited ASC_INITDATA = 0; +STATIC PortAddr _asc_def_iop_base[ASC_IOADR_TABLE_MAX_IX] ASC_INITDATA = +{ + 0x100, ASC_IOADR_1, 0x120, ASC_IOADR_2, 0x140, ASC_IOADR_3, ASC_IOADR_4, + ASC_IOADR_5, ASC_IOADR_6, ASC_IOADR_7, ASC_IOADR_8 +}; + +ASC_INITFUNC( +STATIC PortAddr +AscSearchIOPortAddr( + PortAddr iop_beg, + ushort bus_type +) +) +{ + if (bus_type & ASC_IS_VL) { + while ((iop_beg = AscSearchIOPortAddr11(iop_beg)) != 0) { + if (AscGetChipVersion(iop_beg, bus_type) <= ASC_CHIP_MAX_VER_VL) { + return (iop_beg); + } + } + return (0); + } + if (bus_type & ASC_IS_ISA) { + if (_isa_pnp_inited == 0) { + AscSetISAPNPWaitForKey(); + _isa_pnp_inited++; + } + while ((iop_beg = AscSearchIOPortAddr11(iop_beg)) != 0) { + if ((AscGetChipVersion(iop_beg, bus_type) & ASC_CHIP_VER_ISA_BIT) != 0) { + return (iop_beg); + } + } + return (0); + } + if (bus_type & ASC_IS_EISA) { + if ((iop_beg = AscSearchIOPortAddrEISA(iop_beg)) != 0) { + return (iop_beg); + } + return (0); + } + return (0); +} + +ASC_INITFUNC( +STATIC PortAddr +AscSearchIOPortAddr11( + PortAddr s_addr +) +) +{ + int i; + PortAddr iop_base; + + for (i = 0; i < ASC_IOADR_TABLE_MAX_IX; i++) { + if (_asc_def_iop_base[i] > s_addr) { + break; + } + } + for (; i < ASC_IOADR_TABLE_MAX_IX; i++) { + iop_base = _asc_def_iop_base[i]; + if (check_region(iop_base, ASC_IOADR_GAP) != 0) { + ASC_DBG1(1, + "AscSearchIOPortAddr11: check_region() failed I/O port %x\n", + iop_base); + continue; + } + ASC_DBG1(1, "AscSearchIOPortAddr11: probing I/O port %x\n", iop_base); + if (AscFindSignature(iop_base)) { + return (iop_base); + } + } + return (0); +} + +ASC_INITFUNC( +STATIC void +AscToggleIRQAct( + PortAddr iop_base +) +) +{ + AscSetChipStatus(iop_base, CIW_IRQ_ACT); + AscSetChipStatus(iop_base, 0); + return; +} + +ASC_INITFUNC( +STATIC void +AscSetISAPNPWaitForKey( + void) +) +{ + outp(ASC_ISA_PNP_PORT_ADDR, 0x02); + outp(ASC_ISA_PNP_PORT_WRITE, 0x02); + return; +} + +ASC_INITFUNC( +STATIC uchar +AscGetChipIRQ( + PortAddr iop_base, + ushort bus_type +) +) +{ + ushort cfg_lsw; + uchar chip_irq; + + if ((bus_type & ASC_IS_EISA) != 0) { + cfg_lsw = AscGetEisaChipCfg(iop_base); + chip_irq = (uchar) (((cfg_lsw >> 8) & 0x07) + 10); + if ((chip_irq == 13) || (chip_irq > 15)) { + return (0); + } + return (chip_irq); + } + if ((bus_type & ASC_IS_VL) != 0) { + cfg_lsw = AscGetChipCfgLsw(iop_base); + chip_irq = (uchar) (((cfg_lsw >> 2) & 0x07)); + if ((chip_irq == 0) || + (chip_irq == 4) || + (chip_irq == 7)) { + return (0); + } + return ((uchar) (chip_irq + (ASC_MIN_IRQ_NO - 1))); + } + cfg_lsw = AscGetChipCfgLsw(iop_base); + chip_irq = (uchar) (((cfg_lsw >> 2) & 0x03)); + if (chip_irq == 3) + chip_irq += (uchar) 2; + return ((uchar) (chip_irq + ASC_MIN_IRQ_NO)); +} + +ASC_INITFUNC( +STATIC uchar +AscSetChipIRQ( + PortAddr iop_base, + uchar irq_no, + ushort bus_type +) +) +{ + ushort cfg_lsw; + + if ((bus_type & ASC_IS_VL) != 0) { + if (irq_no != 0) { + if ((irq_no < ASC_MIN_IRQ_NO) || (irq_no > ASC_MAX_IRQ_NO)) { + irq_no = 0; + } else { + irq_no -= (uchar) ((ASC_MIN_IRQ_NO - 1)); + } + } + cfg_lsw = (ushort) (AscGetChipCfgLsw(iop_base) & 0xFFE3); + cfg_lsw |= (ushort) 0x0010; + AscSetChipCfgLsw(iop_base, cfg_lsw); + AscToggleIRQAct(iop_base); + cfg_lsw = (ushort) (AscGetChipCfgLsw(iop_base) & 0xFFE0); + cfg_lsw |= (ushort) ((irq_no & 0x07) << 2); + AscSetChipCfgLsw(iop_base, cfg_lsw); + AscToggleIRQAct(iop_base); + return (AscGetChipIRQ(iop_base, bus_type)); + } + if ((bus_type & (ASC_IS_ISA)) != 0) { + if (irq_no == 15) + irq_no -= (uchar) 2; + irq_no -= (uchar) ASC_MIN_IRQ_NO; + cfg_lsw = (ushort) (AscGetChipCfgLsw(iop_base) & 0xFFF3); + cfg_lsw |= (ushort) ((irq_no & 0x03) << 2); + AscSetChipCfgLsw(iop_base, cfg_lsw); + return (AscGetChipIRQ(iop_base, bus_type)); + } + return (0); +} + +ASC_INITFUNC( +STATIC void +AscEnableIsaDma( + uchar dma_channel +) +) +{ + if (dma_channel < 4) { + outp(0x000B, (ushort) (0xC0 | dma_channel)); + outp(0x000A, dma_channel); + } else if (dma_channel < 8) { + outp(0x00D6, (ushort) (0xC0 | (dma_channel - 4))); + outp(0x00D4, (ushort) (dma_channel - 4)); + } + return; +} + +STATIC int +AscIsrChipHalted( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + EXT_MSG ext_msg; + EXT_MSG out_msg; + ushort halt_q_addr; + int sdtr_accept; + ushort int_halt_code; + ASC_SCSI_BIT_ID_TYPE scsi_busy; + ASC_SCSI_BIT_ID_TYPE target_id; + PortAddr iop_base; + uchar tag_code; + uchar q_status; + uchar halt_qp; + uchar sdtr_data; + uchar target_ix; + uchar q_cntl, tid_no; + uchar cur_dvc_qng; + uchar asyn_sdtr; + uchar scsi_status; + asc_board_t *boardp; + + ASC_ASSERT(asc_dvc->drv_ptr != 0); + boardp = (asc_board_t *) asc_dvc->drv_ptr; + + iop_base = asc_dvc->iop_base; + int_halt_code = AscReadLramWord(iop_base, ASCV_HALTCODE_W); + + halt_qp = AscReadLramByte(iop_base, ASCV_CURCDB_B); + halt_q_addr = ASC_QNO_TO_QADDR(halt_qp); + target_ix = AscReadLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_TARGET_IX)); + q_cntl = AscReadLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_CNTL)); + tid_no = ASC_TIX_TO_TID(target_ix); + target_id = (uchar) ASC_TID_TO_TARGET_ID(tid_no); + if (asc_dvc->pci_fix_asyn_xfer & target_id) { + + asyn_sdtr = ASYN_SDTR_DATA_FIX_PCI_REV_AB; + } else { + asyn_sdtr = 0; + } + if (int_halt_code == ASC_HALT_DISABLE_ASYN_USE_SYN_FIX) { + if (asc_dvc->pci_fix_asyn_xfer & target_id) { + AscSetChipSDTR(iop_base, 0, tid_no); + boardp->sdtr_data[tid_no] = 0; + } + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } else if (int_halt_code == ASC_HALT_ENABLE_ASYN_USE_SYN_FIX) { + if (asc_dvc->pci_fix_asyn_xfer & target_id) { + AscSetChipSDTR(iop_base, asyn_sdtr, tid_no); + boardp->sdtr_data[tid_no] = asyn_sdtr; + } + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } else if (int_halt_code == ASC_HALT_EXTMSG_IN) { + + AscMemWordCopyFromLram(iop_base, + ASCV_MSGIN_BEG, + (ushort *) & ext_msg, + (ushort) (sizeof (EXT_MSG) >> 1)); + + if (ext_msg.msg_type == MS_EXTEND && + ext_msg.msg_req == MS_SDTR_CODE && + ext_msg.msg_len == MS_SDTR_LEN) { + sdtr_accept = TRUE; + if ((ext_msg.req_ack_offset > ASC_SYN_MAX_OFFSET)) { + + sdtr_accept = FALSE; + ext_msg.req_ack_offset = ASC_SYN_MAX_OFFSET; + } + if ((ext_msg.xfer_period < + asc_dvc->sdtr_period_tbl[asc_dvc->host_init_sdtr_index]) || + (ext_msg.xfer_period > + asc_dvc->sdtr_period_tbl[asc_dvc->max_sdtr_index])) { + sdtr_accept = FALSE; + ext_msg.xfer_period = + asc_dvc->sdtr_period_tbl[asc_dvc->host_init_sdtr_index]; + } + if (sdtr_accept) { + sdtr_data = AscCalSDTRData(asc_dvc, ext_msg.xfer_period, + ext_msg.req_ack_offset); + if ((sdtr_data == 0xFF)) { + + q_cntl |= QC_MSG_OUT; + asc_dvc->init_sdtr &= ~target_id; + asc_dvc->sdtr_done &= ~target_id; + AscSetChipSDTR(iop_base, asyn_sdtr, tid_no); + boardp->sdtr_data[tid_no] = asyn_sdtr; + } + } + if (ext_msg.req_ack_offset == 0) { + + q_cntl &= ~QC_MSG_OUT; + asc_dvc->init_sdtr &= ~target_id; + asc_dvc->sdtr_done &= ~target_id; + AscSetChipSDTR(iop_base, asyn_sdtr, tid_no); + } else { + if (sdtr_accept && (q_cntl & QC_MSG_OUT)) { + + q_cntl &= ~QC_MSG_OUT; + asc_dvc->sdtr_done |= target_id; + asc_dvc->init_sdtr |= target_id; + asc_dvc->pci_fix_asyn_xfer &= ~target_id; + sdtr_data = AscCalSDTRData(asc_dvc, ext_msg.xfer_period, + ext_msg.req_ack_offset); + AscSetChipSDTR(iop_base, sdtr_data, tid_no); + boardp->sdtr_data[tid_no] = sdtr_data; + } else { + + q_cntl |= QC_MSG_OUT; + AscMsgOutSDTR(asc_dvc, + ext_msg.xfer_period, + ext_msg.req_ack_offset); + asc_dvc->pci_fix_asyn_xfer &= ~target_id; + sdtr_data = AscCalSDTRData(asc_dvc, ext_msg.xfer_period, + ext_msg.req_ack_offset); + AscSetChipSDTR(iop_base, sdtr_data, tid_no); + boardp->sdtr_data[tid_no] = sdtr_data; + asc_dvc->sdtr_done |= target_id; + asc_dvc->init_sdtr |= target_id; + } + } + + AscWriteLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_CNTL), + q_cntl); + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } else if (ext_msg.msg_type == MS_EXTEND && + ext_msg.msg_req == MS_WDTR_CODE && + ext_msg.msg_len == MS_WDTR_LEN) { + + ext_msg.wdtr_width = 0; + AscMemWordCopyToLram(iop_base, + ASCV_MSGOUT_BEG, + (ushort *) & ext_msg, + (ushort) (sizeof (EXT_MSG) >> 1)); + q_cntl |= QC_MSG_OUT; + AscWriteLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_CNTL), + q_cntl); + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } else { + + ext_msg.msg_type = M1_MSG_REJECT; + AscMemWordCopyToLram(iop_base, + ASCV_MSGOUT_BEG, + (ushort *) & ext_msg, + (ushort) (sizeof (EXT_MSG) >> 1)); + q_cntl |= QC_MSG_OUT; + AscWriteLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_CNTL), + q_cntl); + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } + } else if (int_halt_code == ASC_HALT_CHK_CONDITION) { + + q_cntl |= QC_REQ_SENSE; + + if ((asc_dvc->init_sdtr & target_id) != 0) { + + asc_dvc->sdtr_done &= ~target_id; + + sdtr_data = AscGetMCodeInitSDTRAtID(iop_base, tid_no); + q_cntl |= QC_MSG_OUT; + AscMsgOutSDTR(asc_dvc, + asc_dvc->sdtr_period_tbl[(sdtr_data >> 4) & + (uchar) (asc_dvc->max_sdtr_index - 1)], + (uchar) (sdtr_data & (uchar) ASC_SYN_MAX_OFFSET)); + } + + AscWriteLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_CNTL), + q_cntl); + + tag_code = AscReadLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_TAG_CODE)); + tag_code &= 0xDC; + if ( + (asc_dvc->pci_fix_asyn_xfer & target_id) + && !(asc_dvc->pci_fix_asyn_xfer_always & target_id) +) { + + tag_code |= (ASC_TAG_FLAG_DISABLE_DISCONNECT + | ASC_TAG_FLAG_DISABLE_ASYN_USE_SYN_FIX); + + } + AscWriteLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_TAG_CODE), + tag_code); + + q_status = AscReadLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_STATUS)); + q_status |= (QS_READY | QS_BUSY); + AscWriteLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_STATUS), + q_status); + + scsi_busy = AscReadLramByte(iop_base, + (ushort) ASCV_SCSIBUSY_B); + scsi_busy &= ~target_id; + AscWriteLramByte(iop_base, (ushort) ASCV_SCSIBUSY_B, scsi_busy); + + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } else if (int_halt_code == ASC_HALT_SDTR_REJECTED) { + + AscMemWordCopyFromLram(iop_base, + ASCV_MSGOUT_BEG, + (ushort *) & out_msg, + (ushort) (sizeof (EXT_MSG) >> 1)); + + if ((out_msg.msg_type == MS_EXTEND) && + (out_msg.msg_len == MS_SDTR_LEN) && + (out_msg.msg_req == MS_SDTR_CODE)) { + + asc_dvc->init_sdtr &= ~target_id; + asc_dvc->sdtr_done &= ~target_id; + AscSetChipSDTR(iop_base, asyn_sdtr, tid_no); + boardp->sdtr_data[tid_no] = asyn_sdtr; + } + q_cntl &= ~QC_MSG_OUT; + AscWriteLramByte(iop_base, + (ushort) (halt_q_addr + (ushort) ASC_SCSIQ_B_CNTL), + q_cntl); + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } else if (int_halt_code == ASC_HALT_SS_QUEUE_FULL) { + + scsi_status = AscReadLramByte(iop_base, + (ushort) ((ushort) halt_q_addr + (ushort) ASC_SCSIQ_SCSI_STATUS)); + cur_dvc_qng = AscReadLramByte(iop_base, + (ushort) ((ushort) ASC_QADR_BEG + (ushort) target_ix)); + if ((cur_dvc_qng > 0) && + (asc_dvc->cur_dvc_qng[tid_no] > 0)) { + + scsi_busy = AscReadLramByte(iop_base, + (ushort) ASCV_SCSIBUSY_B); + scsi_busy |= target_id; + AscWriteLramByte(iop_base, + (ushort) ASCV_SCSIBUSY_B, scsi_busy); + asc_dvc->queue_full_or_busy |= target_id; + + if (scsi_status == SS_QUEUE_FULL) { + if (cur_dvc_qng > ASC_MIN_TAGGED_CMD) { + cur_dvc_qng -= 1; + asc_dvc->max_dvc_qng[tid_no] = cur_dvc_qng; + + AscWriteLramByte(iop_base, + (ushort) ((ushort) ASCV_MAX_DVC_QNG_BEG + + (ushort) tid_no), + cur_dvc_qng); + + /* + * Set the device queue depth to the number of + * active requests when the QUEUE FULL condition + * was encountered. + */ + boardp->queue_full |= target_id; + boardp->queue_full_cnt[tid_no] = cur_dvc_qng; +#if ASC_QUEUE_FLOW_CONTROL + if (boardp->device[tid_no] != NULL && + boardp->device[tid_no]->queue_curr_depth > + cur_dvc_qng) { + boardp->device[tid_no]->queue_curr_depth = + cur_dvc_qng; + } +#endif /* ASC_QUEUE_FLOW_CONTROL */ + } + } + } + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + return (0); + } + return (0); +} + +STATIC uchar +_AscCopyLramScsiDoneQ( + PortAddr iop_base, + ushort q_addr, + REG ASC_QDONE_INFO * scsiq, + ulong max_dma_count +) +{ + ushort _val; + uchar sg_queue_cnt; + + DvcGetQinfo(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_DONE_INFO_BEG), + (ushort *) scsiq, + (ushort) ((sizeof (ASC_SCSIQ_2) + sizeof (ASC_SCSIQ_3)) / 2)); + _val = AscReadLramWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_STATUS)); + scsiq->q_status = (uchar) _val; + scsiq->q_no = (uchar) (_val >> 8); + _val = AscReadLramWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_CNTL)); + scsiq->cntl = (uchar) _val; + sg_queue_cnt = (uchar) (_val >> 8); + _val = AscReadLramWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_SENSE_LEN)); + scsiq->sense_len = (uchar) _val; + scsiq->extra_bytes = (uchar) (_val >> 8); + scsiq->remain_bytes = AscReadLramWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_DW_REMAIN_XFER_CNT)); + scsiq->remain_bytes &= max_dma_count; + return (sg_queue_cnt); +} + +STATIC int +AscIsrQDone( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + uchar next_qp; + uchar n_q_used; + uchar sg_list_qp; + uchar sg_queue_cnt; + uchar q_cnt; + uchar done_q_tail; + uchar tid_no; + ASC_SCSI_BIT_ID_TYPE scsi_busy; + ASC_SCSI_BIT_ID_TYPE target_id; + PortAddr iop_base; + ushort q_addr; + ushort sg_q_addr; + uchar cur_target_qng; + ASC_QDONE_INFO scsiq_buf; + REG ASC_QDONE_INFO *scsiq; + int false_overrun; + ASC_ISR_CALLBACK asc_isr_callback; + + iop_base = asc_dvc->iop_base; + asc_isr_callback = (ASC_ISR_CALLBACK) asc_dvc->isr_callback; + n_q_used = 1; + scsiq = (ASC_QDONE_INFO *) & scsiq_buf; + done_q_tail = (uchar) AscGetVarDoneQTail(iop_base); + q_addr = ASC_QNO_TO_QADDR(done_q_tail); + next_qp = AscReadLramByte(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_FWD)); + if (next_qp != ASC_QLINK_END) { + AscPutVarDoneQTail(iop_base, next_qp); + q_addr = ASC_QNO_TO_QADDR(next_qp); + sg_queue_cnt = _AscCopyLramScsiDoneQ(iop_base, q_addr, scsiq, + asc_dvc->max_dma_count); + AscWriteLramByte(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_STATUS), + (uchar) (scsiq->q_status & (uchar) ~ (QS_READY | QS_ABORTED))); + tid_no = ASC_TIX_TO_TID(scsiq->d2.target_ix); + target_id = ASC_TIX_TO_TARGET_ID(scsiq->d2.target_ix); + if ((scsiq->cntl & QC_SG_HEAD) != 0) { + sg_q_addr = q_addr; + sg_list_qp = next_qp; + for (q_cnt = 0; q_cnt < sg_queue_cnt; q_cnt++) { + sg_list_qp = AscReadLramByte(iop_base, + (ushort) (sg_q_addr + (ushort) ASC_SCSIQ_B_FWD)); + sg_q_addr = ASC_QNO_TO_QADDR(sg_list_qp); + if (sg_list_qp == ASC_QLINK_END) { + AscSetLibErrorCode(asc_dvc, ASCQ_ERR_SG_Q_LINKS); + scsiq->d3.done_stat = QD_WITH_ERROR; + scsiq->d3.host_stat = QHSTA_D_QDONE_SG_LIST_CORRUPTED; + goto FATAL_ERR_QDONE; + } + AscWriteLramByte(iop_base, + (ushort) (sg_q_addr + (ushort) ASC_SCSIQ_B_STATUS), + QS_FREE); + } + n_q_used = sg_queue_cnt + 1; + AscPutVarDoneQTail(iop_base, sg_list_qp); + } + if (asc_dvc->queue_full_or_busy & target_id) { + cur_target_qng = AscReadLramByte(iop_base, + (ushort) ((ushort) ASC_QADR_BEG + (ushort) scsiq->d2.target_ix)); + if (cur_target_qng < asc_dvc->max_dvc_qng[tid_no]) { + scsi_busy = AscReadLramByte(iop_base, + (ushort) ASCV_SCSIBUSY_B); + scsi_busy &= ~target_id; + AscWriteLramByte(iop_base, + (ushort) ASCV_SCSIBUSY_B, scsi_busy); + asc_dvc->queue_full_or_busy &= ~target_id; + } + } + if (asc_dvc->cur_total_qng >= n_q_used) { + asc_dvc->cur_total_qng -= n_q_used; + if (asc_dvc->cur_dvc_qng[tid_no] != 0) { + asc_dvc->cur_dvc_qng[tid_no]--; + } + } else { + AscSetLibErrorCode(asc_dvc, ASCQ_ERR_CUR_QNG); + scsiq->d3.done_stat = QD_WITH_ERROR; + goto FATAL_ERR_QDONE; + } + if ((scsiq->d2.srb_ptr == 0UL) || + ((scsiq->q_status & QS_ABORTED) != 0)) { + return (0x11); + } else if (scsiq->q_status == QS_DONE) { + false_overrun = FALSE; + if (scsiq->extra_bytes != 0) { + scsiq->remain_bytes += (ulong) scsiq->extra_bytes; + } + if (scsiq->d3.done_stat == QD_WITH_ERROR) { + if (scsiq->d3.host_stat == QHSTA_M_DATA_OVER_RUN) { + if ((scsiq->cntl & (QC_DATA_IN | QC_DATA_OUT)) == 0) { + scsiq->d3.done_stat = QD_NO_ERROR; + scsiq->d3.host_stat = QHSTA_NO_ERROR; + } else if (false_overrun) { + scsiq->d3.done_stat = QD_NO_ERROR; + scsiq->d3.host_stat = QHSTA_NO_ERROR; + } + } else if (scsiq->d3.host_stat == + QHSTA_M_HUNG_REQ_SCSI_BUS_RESET) { + AscStopChip(iop_base); + AscSetChipControl(iop_base, + (uchar) (CC_SCSI_RESET | CC_HALT)); + DvcDelayNanoSecond(asc_dvc, 60000); + AscSetChipControl(iop_base, CC_HALT); + AscSetChipStatus(iop_base, CIW_CLR_SCSI_RESET_INT); + AscSetChipStatus(iop_base, 0); + AscSetChipControl(iop_base, 0); + } + } + if ((scsiq->cntl & QC_NO_CALLBACK) == 0) { + (*asc_isr_callback) (asc_dvc, scsiq); + } else { + if ((AscReadLramByte(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_CDB_BEG)) == + SCSICMD_StartStopUnit)) { + asc_dvc->unit_not_ready &= ~target_id; + if (scsiq->d3.done_stat != QD_NO_ERROR) { + asc_dvc->start_motor &= ~target_id; + } + } + } + return (1); + } else { + AscSetLibErrorCode(asc_dvc, ASCQ_ERR_Q_STATUS); + FATAL_ERR_QDONE: + if ((scsiq->cntl & QC_NO_CALLBACK) == 0) { + (*asc_isr_callback) (asc_dvc, scsiq); + } + return (0x80); + } + } + return (0); +} + +STATIC int +AscISR( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + ASC_CS_TYPE chipstat; + PortAddr iop_base; + ushort saved_ram_addr; + uchar ctrl_reg; + uchar saved_ctrl_reg; + int int_pending; + int status; + uchar host_flag; + + iop_base = asc_dvc->iop_base; + int_pending = FALSE; + if (((asc_dvc->init_state & ASC_INIT_STATE_END_LOAD_MC) == 0) + || (asc_dvc->isr_callback == 0) +) { + return (ERR); + } + if (asc_dvc->in_critical_cnt != 0) { + AscSetLibErrorCode(asc_dvc, ASCQ_ERR_ISR_ON_CRITICAL); + return (ERR); + } + if (asc_dvc->is_in_int) { + AscSetLibErrorCode(asc_dvc, ASCQ_ERR_ISR_RE_ENTRY); + return (ERR); + } + asc_dvc->is_in_int = TRUE; + ctrl_reg = AscGetChipControl(iop_base); + saved_ctrl_reg = ctrl_reg & (~(CC_SCSI_RESET | CC_CHIP_RESET | + CC_SINGLE_STEP | CC_DIAG | CC_TEST)); + chipstat = AscGetChipStatus(iop_base); + if (chipstat & CSW_SCSI_RESET_LATCH) { + if (!(asc_dvc->bus_type & (ASC_IS_VL | ASC_IS_EISA))) { + int_pending = TRUE; + asc_dvc->sdtr_done = 0; + saved_ctrl_reg &= (uchar) (~CC_HALT); + while (AscGetChipStatus(iop_base) & CSW_SCSI_RESET_ACTIVE) ; + AscSetChipControl(iop_base, (CC_CHIP_RESET | CC_HALT)); + AscSetChipControl(iop_base, CC_HALT); + AscSetChipStatus(iop_base, CIW_CLR_SCSI_RESET_INT); + AscSetChipStatus(iop_base, 0); + chipstat = AscGetChipStatus(iop_base); + } + } + saved_ram_addr = AscGetChipLramAddr(iop_base); + host_flag = AscReadLramByte(iop_base, + ASCV_HOST_FLAG_B) & (uchar) (~ASC_HOST_FLAG_IN_ISR); + AscWriteLramByte(iop_base, ASCV_HOST_FLAG_B, + (uchar) (host_flag | (uchar) ASC_HOST_FLAG_IN_ISR)); + if ((chipstat & CSW_INT_PENDING) + || (int_pending) +) { + AscAckInterrupt(iop_base); + int_pending = TRUE; + if ((chipstat & CSW_HALTED) && + (ctrl_reg & CC_SINGLE_STEP)) { + if (AscIsrChipHalted(asc_dvc) == ERR) { + goto ISR_REPORT_QDONE_FATAL_ERROR; + } else { + saved_ctrl_reg &= (uchar) (~CC_HALT); + } + } else { + ISR_REPORT_QDONE_FATAL_ERROR: + if ((asc_dvc->dvc_cntl & ASC_CNTL_INT_MULTI_Q) != 0) { + while (((status = AscIsrQDone(asc_dvc)) & 0x01) != 0) { + } + } else { + do { + if ((status = AscIsrQDone(asc_dvc)) == 1) { + break; + } + } while (status == 0x11); + } + if ((status & 0x80) != 0) + int_pending = ERR; + } + } + AscWriteLramByte(iop_base, ASCV_HOST_FLAG_B, host_flag); + AscSetChipLramAddr(iop_base, saved_ram_addr); + AscSetChipControl(iop_base, saved_ctrl_reg); + asc_dvc->is_in_int = FALSE; + return (int_pending); +} + +STATIC uchar _asc_mcode_buf[] ASC_INITDATA = +{ + 0x01, 0x03, 0x01, 0x19, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x91, 0x10, 0x0A, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0x80, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x23, 0x00, 0x24, 0x00, 0x00, 0x00, 0x07, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE2, 0x88, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x73, 0x48, 0x04, 0x36, 0x00, 0x00, 0xA2, 0xC2, 0x00, 0x80, 0x73, 0x03, 0x23, 0x36, 0x40, + 0xB6, 0x00, 0x36, 0x00, 0x05, 0xD6, 0x0C, 0xD2, 0x12, 0xDA, 0x00, 0xA2, 0xC2, 0x00, 0x92, 0x80, + 0x1E, 0x98, 0x50, 0x00, 0xF5, 0x00, 0x48, 0x98, 0xDF, 0x23, 0x36, 0x60, 0xB6, 0x00, 0x92, 0x80, + 0x4F, 0x00, 0xF5, 0x00, 0x48, 0x98, 0xEF, 0x23, 0x36, 0x60, 0xB6, 0x00, 0x92, 0x80, 0x80, 0x62, + 0x92, 0x80, 0x00, 0x46, 0x17, 0xEE, 0x13, 0xEA, 0x02, 0x01, 0x09, 0xD8, 0xCD, 0x04, 0x4D, 0x00, + 0x00, 0xA3, 0xD6, 0x00, 0xA6, 0x97, 0x7F, 0x23, 0x04, 0x61, 0x84, 0x01, 0xE6, 0x84, 0xD2, 0xC1, + 0x80, 0x73, 0xCD, 0x04, 0x4D, 0x00, 0x00, 0xA3, 0xE2, 0x01, 0xA6, 0x97, 0xCE, 0x81, 0x00, 0x33, + 0x02, 0x00, 0xC0, 0x88, 0x80, 0x73, 0x80, 0x77, 0x00, 0x01, 0x01, 0xA1, 0x02, 0x01, 0x4F, 0x00, + 0x84, 0x97, 0x07, 0xA6, 0x0C, 0x01, 0x00, 0x33, 0x03, 0x00, 0xC0, 0x88, 0x03, 0x03, 0x03, 0xDE, + 0x00, 0x33, 0x05, 0x00, 0xC0, 0x88, 0xCE, 0x00, 0x69, 0x60, 0xCE, 0x00, 0x02, 0x03, 0x4A, 0x60, + 0x00, 0xA2, 0x80, 0x01, 0x80, 0x63, 0x07, 0xA6, 0x2C, 0x01, 0x80, 0x81, 0x03, 0x03, 0x80, 0x63, + 0xE2, 0x00, 0x07, 0xA6, 0x3C, 0x01, 0x00, 0x33, 0x04, 0x00, 0xC0, 0x88, 0x03, 0x07, 0x02, 0x01, + 0x04, 0xCA, 0x0D, 0x23, 0x68, 0x98, 0x4D, 0x04, 0x04, 0x85, 0x05, 0xD8, 0x0D, 0x23, 0x68, 0x98, + 0xCD, 0x04, 0x15, 0x23, 0xF6, 0x88, 0xFB, 0x23, 0x02, 0x61, 0x82, 0x01, 0x80, 0x63, 0x02, 0x03, + 0x06, 0xA3, 0x6A, 0x01, 0x00, 0x33, 0x0A, 0x00, 0xC0, 0x88, 0x4E, 0x00, 0x07, 0xA3, 0x76, 0x01, + 0x00, 0x33, 0x0B, 0x00, 0xC0, 0x88, 0xCD, 0x04, 0x36, 0x2D, 0x00, 0x33, 0x1A, 0x00, 0xC0, 0x88, + 0x50, 0x04, 0x90, 0x81, 0x06, 0xAB, 0x8A, 0x01, 0x90, 0x81, 0x4E, 0x00, 0x07, 0xA3, 0x9A, 0x01, + 0x50, 0x00, 0x00, 0xA3, 0x44, 0x01, 0x00, 0x05, 0x84, 0x81, 0x46, 0x97, 0x02, 0x01, 0x05, 0xC6, + 0x04, 0x23, 0xA0, 0x01, 0x15, 0x23, 0xA1, 0x01, 0xC6, 0x81, 0xFD, 0x23, 0x02, 0x61, 0x82, 0x01, + 0x0A, 0xDA, 0x4A, 0x00, 0x06, 0x61, 0x00, 0xA0, 0xBC, 0x01, 0x80, 0x63, 0xCD, 0x04, 0x36, 0x2D, + 0x00, 0x33, 0x1B, 0x00, 0xC0, 0x88, 0x06, 0x23, 0x68, 0x98, 0xCD, 0x04, 0xE6, 0x84, 0x06, 0x01, + 0x00, 0xA2, 0xDC, 0x01, 0x57, 0x60, 0x00, 0xA0, 0xE2, 0x01, 0xE6, 0x84, 0x80, 0x23, 0xA0, 0x01, + 0xE6, 0x84, 0x80, 0x73, 0x4B, 0x00, 0x06, 0x61, 0x00, 0xA2, 0x08, 0x02, 0x04, 0x01, 0x0C, 0xDE, + 0x02, 0x01, 0x03, 0xCC, 0x4F, 0x00, 0x84, 0x97, 0x04, 0x82, 0x08, 0x23, 0x02, 0x41, 0x82, 0x01, + 0x4F, 0x00, 0x62, 0x97, 0x48, 0x04, 0x84, 0x80, 0xF0, 0x97, 0x00, 0x46, 0x56, 0x00, 0x03, 0xC0, + 0x01, 0x23, 0xE8, 0x00, 0x81, 0x73, 0x06, 0x29, 0x03, 0x42, 0x06, 0xE2, 0x03, 0xEE, 0x67, 0xEB, + 0x11, 0x23, 0xF6, 0x88, 0x04, 0x98, 0xF4, 0x80, 0x80, 0x73, 0x80, 0x77, 0x07, 0xA4, 0x32, 0x02, + 0x7C, 0x95, 0x06, 0xA6, 0x3C, 0x02, 0x03, 0xA6, 0x4C, 0x04, 0xC0, 0x88, 0x04, 0x01, 0x03, 0xD8, + 0xB2, 0x98, 0x6A, 0x96, 0x4E, 0x82, 0xFE, 0x95, 0x80, 0x67, 0x83, 0x03, 0x80, 0x63, 0xB6, 0x2D, + 0x02, 0xA6, 0x78, 0x02, 0x07, 0xA6, 0x66, 0x02, 0x06, 0xA6, 0x6A, 0x02, 0x03, 0xA6, 0x6E, 0x02, + 0x00, 0x33, 0x10, 0x00, 0xC0, 0x88, 0x7C, 0x95, 0x50, 0x82, 0x60, 0x96, 0x50, 0x82, 0x04, 0x23, + 0xA0, 0x01, 0x14, 0x23, 0xA1, 0x01, 0x3C, 0x84, 0x04, 0x01, 0x0C, 0xDC, 0xE0, 0x23, 0x25, 0x61, + 0xEF, 0x00, 0x14, 0x01, 0x4F, 0x04, 0xA8, 0x01, 0x6F, 0x00, 0xA5, 0x01, 0x03, 0x23, 0xA4, 0x01, + 0x06, 0x23, 0x9C, 0x01, 0x24, 0x2B, 0x1C, 0x01, 0x02, 0xA6, 0xB6, 0x02, 0x07, 0xA6, 0x66, 0x02, + 0x06, 0xA6, 0x6A, 0x02, 0x03, 0xA6, 0x20, 0x04, 0x01, 0xA6, 0xC0, 0x02, 0x00, 0xA6, 0xC0, 0x02, + 0x00, 0x33, 0x12, 0x00, 0xC0, 0x88, 0x00, 0x0E, 0x80, 0x63, 0x00, 0x43, 0x00, 0xA0, 0x98, 0x02, + 0x4D, 0x04, 0x04, 0x01, 0x0B, 0xDC, 0xE7, 0x23, 0x04, 0x61, 0x84, 0x01, 0x10, 0x31, 0x12, 0x35, + 0x14, 0x01, 0xEC, 0x00, 0x6C, 0x38, 0x00, 0x3F, 0x00, 0x00, 0xF6, 0x82, 0x18, 0x23, 0x04, 0x61, + 0x18, 0xA0, 0xEE, 0x02, 0x04, 0x01, 0x9C, 0xC8, 0x00, 0x33, 0x1F, 0x00, 0xC0, 0x88, 0x08, 0x31, + 0x0A, 0x35, 0x0C, 0x39, 0x0E, 0x3D, 0x7E, 0x98, 0xB6, 0x2D, 0x01, 0xA6, 0x20, 0x03, 0x00, 0xA6, + 0x20, 0x03, 0x07, 0xA6, 0x18, 0x03, 0x06, 0xA6, 0x1C, 0x03, 0x03, 0xA6, 0x20, 0x04, 0x02, 0xA6, + 0x78, 0x02, 0x00, 0x33, 0x33, 0x00, 0xC0, 0x88, 0x7C, 0x95, 0xFA, 0x82, 0x60, 0x96, 0xFA, 0x82, + 0x82, 0x98, 0x80, 0x42, 0x7E, 0x98, 0x60, 0xE4, 0x04, 0x01, 0x29, 0xC8, 0x31, 0x05, 0x07, 0x01, + 0x00, 0xA2, 0x60, 0x03, 0x00, 0x43, 0x87, 0x01, 0x05, 0x05, 0x86, 0x98, 0x7E, 0x98, 0x00, 0xA6, + 0x22, 0x03, 0x07, 0xA6, 0x58, 0x03, 0x03, 0xA6, 0x3C, 0x04, 0x06, 0xA6, 0x5C, 0x03, 0x01, 0xA6, + 0x22, 0x03, 0x00, 0x33, 0x25, 0x00, 0xC0, 0x88, 0x7C, 0x95, 0x3E, 0x83, 0x60, 0x96, 0x3E, 0x83, + 0x04, 0x01, 0x0C, 0xCE, 0x03, 0xC8, 0x00, 0x33, 0x42, 0x00, 0xC0, 0x88, 0x00, 0x01, 0x05, 0x05, + 0xFF, 0xA2, 0x7E, 0x03, 0xB1, 0x01, 0x08, 0x23, 0xB2, 0x01, 0x3A, 0x83, 0x05, 0x05, 0x15, 0x01, + 0x00, 0xA2, 0x9E, 0x03, 0xEC, 0x00, 0x6E, 0x00, 0x95, 0x01, 0x6C, 0x38, 0x00, 0x3F, 0x00, 0x00, + 0x01, 0xA6, 0x9A, 0x03, 0x00, 0xA6, 0x9A, 0x03, 0x12, 0x84, 0x80, 0x42, 0x7E, 0x98, 0x01, 0xA6, + 0xA8, 0x03, 0x00, 0xA6, 0xC0, 0x03, 0x12, 0x84, 0xA6, 0x98, 0x80, 0x42, 0x01, 0xA6, 0xA8, 0x03, + 0x07, 0xA6, 0xB6, 0x03, 0xD8, 0x83, 0x7C, 0x95, 0xAC, 0x83, 0x00, 0x33, 0x2F, 0x00, 0xC0, 0x88, + 0xA6, 0x98, 0x80, 0x42, 0x00, 0xA6, 0xC0, 0x03, 0x07, 0xA6, 0xCE, 0x03, 0xD8, 0x83, 0x7C, 0x95, + 0xC4, 0x83, 0x00, 0x33, 0x26, 0x00, 0xC0, 0x88, 0x38, 0x2B, 0x80, 0x32, 0x80, 0x36, 0x04, 0x23, + 0xA0, 0x01, 0x12, 0x23, 0xA1, 0x01, 0x12, 0x84, 0x06, 0xF0, 0x06, 0xA4, 0xF6, 0x03, 0x80, 0x6B, + 0x05, 0x23, 0x83, 0x03, 0x80, 0x63, 0x03, 0xA6, 0x10, 0x04, 0x07, 0xA6, 0x08, 0x04, 0x06, 0xA6, + 0x0C, 0x04, 0x00, 0x33, 0x17, 0x00, 0xC0, 0x88, 0x7C, 0x95, 0xF6, 0x83, 0x60, 0x96, 0xF6, 0x83, + 0x20, 0x84, 0x06, 0xF0, 0x06, 0xA4, 0x20, 0x04, 0x80, 0x6B, 0x05, 0x23, 0x83, 0x03, 0x80, 0x63, + 0xB6, 0x2D, 0x03, 0xA6, 0x3C, 0x04, 0x07, 0xA6, 0x34, 0x04, 0x06, 0xA6, 0x38, 0x04, 0x00, 0x33, + 0x30, 0x00, 0xC0, 0x88, 0x7C, 0x95, 0x20, 0x84, 0x60, 0x96, 0x20, 0x84, 0x1D, 0x01, 0x06, 0xCC, + 0x00, 0x33, 0x00, 0x84, 0xC0, 0x20, 0x00, 0x23, 0xEA, 0x00, 0x81, 0x62, 0xA2, 0x0D, 0x80, 0x63, + 0x07, 0xA6, 0x5A, 0x04, 0x00, 0x33, 0x18, 0x00, 0xC0, 0x88, 0x03, 0x03, 0x80, 0x63, 0xA3, 0x01, + 0x07, 0xA4, 0x64, 0x04, 0x23, 0x01, 0x00, 0xA2, 0x86, 0x04, 0x0A, 0xA0, 0x76, 0x04, 0xE0, 0x00, + 0x00, 0x33, 0x1D, 0x00, 0xC0, 0x88, 0x0B, 0xA0, 0x82, 0x04, 0xE0, 0x00, 0x00, 0x33, 0x1E, 0x00, + 0xC0, 0x88, 0x42, 0x23, 0xF6, 0x88, 0x00, 0x23, 0x22, 0xA3, 0xE6, 0x04, 0x08, 0x23, 0x22, 0xA3, + 0xA2, 0x04, 0x28, 0x23, 0x22, 0xA3, 0xAE, 0x04, 0x02, 0x23, 0x22, 0xA3, 0xC4, 0x04, 0x42, 0x23, + 0xF6, 0x88, 0x4A, 0x00, 0x06, 0x61, 0x00, 0xA0, 0xAE, 0x04, 0x45, 0x23, 0xF6, 0x88, 0x04, 0x98, + 0x00, 0xA2, 0xC0, 0x04, 0xB2, 0x98, 0x00, 0x33, 0x00, 0x82, 0xC0, 0x20, 0x81, 0x62, 0xF0, 0x81, + 0x47, 0x23, 0xF6, 0x88, 0x04, 0x01, 0x0B, 0xDE, 0x04, 0x98, 0xB2, 0x98, 0x00, 0x33, 0x00, 0x81, + 0xC0, 0x20, 0x81, 0x62, 0x14, 0x01, 0x00, 0xA0, 0x08, 0x02, 0x43, 0x23, 0xF6, 0x88, 0x04, 0x23, + 0xA0, 0x01, 0x44, 0x23, 0xA1, 0x01, 0x80, 0x73, 0x4D, 0x00, 0x03, 0xA3, 0xF4, 0x04, 0x00, 0x33, + 0x27, 0x00, 0xC0, 0x88, 0x04, 0x01, 0x04, 0xDC, 0x02, 0x23, 0xA2, 0x01, 0x04, 0x23, 0xA0, 0x01, + 0x04, 0x98, 0x26, 0x95, 0x4B, 0x00, 0xF6, 0x00, 0x4F, 0x04, 0x4F, 0x00, 0x00, 0xA3, 0x22, 0x05, + 0x00, 0x05, 0x76, 0x00, 0x06, 0x61, 0x00, 0xA2, 0x1C, 0x05, 0x0A, 0x85, 0x46, 0x97, 0xCD, 0x04, + 0x24, 0x85, 0x48, 0x04, 0x84, 0x80, 0x02, 0x01, 0x03, 0xDA, 0x80, 0x23, 0x82, 0x01, 0x34, 0x85, + 0x02, 0x23, 0xA0, 0x01, 0x4A, 0x00, 0x06, 0x61, 0x00, 0xA2, 0x40, 0x05, 0x1D, 0x01, 0x04, 0xD6, + 0xFF, 0x23, 0x86, 0x41, 0x4B, 0x60, 0xCB, 0x00, 0xFF, 0x23, 0x80, 0x01, 0x49, 0x00, 0x81, 0x01, + 0x04, 0x01, 0x02, 0xC8, 0x30, 0x01, 0x80, 0x01, 0xF7, 0x04, 0x03, 0x01, 0x49, 0x04, 0x80, 0x01, + 0xC9, 0x00, 0x00, 0x05, 0x00, 0x01, 0xFF, 0xA0, 0x60, 0x05, 0x77, 0x04, 0x01, 0x23, 0xEA, 0x00, + 0x5D, 0x00, 0xFE, 0xC7, 0x00, 0x62, 0x00, 0x23, 0xEA, 0x00, 0x00, 0x63, 0x07, 0xA4, 0xF8, 0x05, + 0x03, 0x03, 0x02, 0xA0, 0x8E, 0x05, 0xF4, 0x85, 0x00, 0x33, 0x2D, 0x00, 0xC0, 0x88, 0x04, 0xA0, + 0xB8, 0x05, 0x80, 0x63, 0x00, 0x23, 0xDF, 0x00, 0x4A, 0x00, 0x06, 0x61, 0x00, 0xA2, 0xA4, 0x05, + 0x1D, 0x01, 0x06, 0xD6, 0x02, 0x23, 0x02, 0x41, 0x82, 0x01, 0x50, 0x00, 0x62, 0x97, 0x04, 0x85, + 0x04, 0x23, 0x02, 0x41, 0x82, 0x01, 0x04, 0x85, 0x08, 0xA0, 0xBE, 0x05, 0xF4, 0x85, 0x03, 0xA0, + 0xC4, 0x05, 0xF4, 0x85, 0x01, 0xA0, 0xCE, 0x05, 0x88, 0x00, 0x80, 0x63, 0xCC, 0x86, 0x07, 0xA0, + 0xEE, 0x05, 0x5F, 0x00, 0x00, 0x2B, 0xDF, 0x08, 0x00, 0xA2, 0xE6, 0x05, 0x80, 0x67, 0x80, 0x63, + 0x01, 0xA2, 0x7A, 0x06, 0x7C, 0x85, 0x06, 0x23, 0x68, 0x98, 0x48, 0x23, 0xF6, 0x88, 0x07, 0x23, + 0x80, 0x00, 0x06, 0x87, 0x80, 0x63, 0x7C, 0x85, 0x00, 0x23, 0xDF, 0x00, 0x00, 0x63, 0x4A, 0x00, + 0x06, 0x61, 0x00, 0xA2, 0x36, 0x06, 0x1D, 0x01, 0x16, 0xD4, 0xC0, 0x23, 0x07, 0x41, 0x83, 0x03, + 0x80, 0x63, 0x06, 0xA6, 0x1C, 0x06, 0x00, 0x33, 0x37, 0x00, 0xC0, 0x88, 0x1D, 0x01, 0x01, 0xD6, + 0x20, 0x23, 0x63, 0x60, 0x83, 0x03, 0x80, 0x63, 0x02, 0x23, 0xDF, 0x00, 0x07, 0xA6, 0x7C, 0x05, + 0xEF, 0x04, 0x6F, 0x00, 0x00, 0x63, 0x4B, 0x00, 0x06, 0x41, 0xCB, 0x00, 0x52, 0x00, 0x06, 0x61, + 0x00, 0xA2, 0x4E, 0x06, 0x1D, 0x01, 0x03, 0xCA, 0xC0, 0x23, 0x07, 0x41, 0x00, 0x63, 0x1D, 0x01, + 0x04, 0xCC, 0x00, 0x33, 0x00, 0x83, 0xC0, 0x20, 0x81, 0x62, 0x80, 0x23, 0x07, 0x41, 0x00, 0x63, + 0x80, 0x67, 0x08, 0x23, 0x83, 0x03, 0x80, 0x63, 0x00, 0x63, 0x01, 0x23, 0xDF, 0x00, 0x06, 0xA6, + 0x84, 0x06, 0x07, 0xA6, 0x7C, 0x05, 0x80, 0x67, 0x80, 0x63, 0x00, 0x33, 0x00, 0x40, 0xC0, 0x20, + 0x81, 0x62, 0x00, 0x63, 0x00, 0x00, 0xFE, 0x95, 0x83, 0x03, 0x80, 0x63, 0x06, 0xA6, 0x94, 0x06, + 0x07, 0xA6, 0x7C, 0x05, 0x00, 0x00, 0x01, 0xA0, 0x14, 0x07, 0x00, 0x2B, 0x40, 0x0E, 0x80, 0x63, + 0x01, 0x00, 0x06, 0xA6, 0xAA, 0x06, 0x07, 0xA6, 0x7C, 0x05, 0x40, 0x0E, 0x80, 0x63, 0x00, 0x43, + 0x00, 0xA0, 0xA2, 0x06, 0x06, 0xA6, 0xBC, 0x06, 0x07, 0xA6, 0x7C, 0x05, 0x80, 0x67, 0x40, 0x0E, + 0x80, 0x63, 0x07, 0xA6, 0x7C, 0x05, 0x00, 0x23, 0xDF, 0x00, 0x00, 0x63, 0x07, 0xA6, 0xD6, 0x06, + 0x00, 0x33, 0x2A, 0x00, 0xC0, 0x88, 0x03, 0x03, 0x80, 0x63, 0x89, 0x00, 0x0A, 0x2B, 0x07, 0xA6, + 0xE8, 0x06, 0x00, 0x33, 0x29, 0x00, 0xC0, 0x88, 0x00, 0x43, 0x00, 0xA2, 0xF4, 0x06, 0xC0, 0x0E, + 0x80, 0x63, 0xDE, 0x86, 0xC0, 0x0E, 0x00, 0x33, 0x00, 0x80, 0xC0, 0x20, 0x81, 0x62, 0x04, 0x01, + 0x02, 0xDA, 0x80, 0x63, 0x7C, 0x85, 0x80, 0x7B, 0x80, 0x63, 0x06, 0xA6, 0x8C, 0x06, 0x00, 0x33, + 0x2C, 0x00, 0xC0, 0x88, 0x0C, 0xA2, 0x2E, 0x07, 0xFE, 0x95, 0x83, 0x03, 0x80, 0x63, 0x06, 0xA6, + 0x2C, 0x07, 0x07, 0xA6, 0x7C, 0x05, 0x00, 0x33, 0x3D, 0x00, 0xC0, 0x88, 0x00, 0x00, 0x80, 0x67, + 0x83, 0x03, 0x80, 0x63, 0x0C, 0xA0, 0x44, 0x07, 0x07, 0xA6, 0x7C, 0x05, 0xBF, 0x23, 0x04, 0x61, + 0x84, 0x01, 0xE6, 0x84, 0x00, 0x63, 0xF0, 0x04, 0x01, 0x01, 0xF1, 0x00, 0x00, 0x01, 0xF2, 0x00, + 0x01, 0x05, 0x80, 0x01, 0x72, 0x04, 0x71, 0x00, 0x81, 0x01, 0x70, 0x04, 0x80, 0x05, 0x81, 0x05, + 0x00, 0x63, 0xF0, 0x04, 0xF2, 0x00, 0x72, 0x04, 0x01, 0x01, 0xF1, 0x00, 0x70, 0x00, 0x81, 0x01, + 0x70, 0x04, 0x71, 0x00, 0x81, 0x01, 0x72, 0x00, 0x80, 0x01, 0x71, 0x04, 0x70, 0x00, 0x80, 0x01, + 0x70, 0x04, 0x00, 0x63, 0xF0, 0x04, 0xF2, 0x00, 0x72, 0x04, 0x00, 0x01, 0xF1, 0x00, 0x70, 0x00, + 0x80, 0x01, 0x70, 0x04, 0x71, 0x00, 0x80, 0x01, 0x72, 0x00, 0x81, 0x01, 0x71, 0x04, 0x70, 0x00, + 0x81, 0x01, 0x70, 0x04, 0x00, 0x63, 0x00, 0x23, 0xB3, 0x01, 0x83, 0x05, 0xA3, 0x01, 0xA2, 0x01, + 0xA1, 0x01, 0x01, 0x23, 0xA0, 0x01, 0x00, 0x01, 0xC8, 0x00, 0x03, 0xA1, 0xC4, 0x07, 0x00, 0x33, + 0x07, 0x00, 0xC0, 0x88, 0x80, 0x05, 0x81, 0x05, 0x04, 0x01, 0x11, 0xC8, 0x48, 0x00, 0xB0, 0x01, + 0xB1, 0x01, 0x08, 0x23, 0xB2, 0x01, 0x05, 0x01, 0x48, 0x04, 0x00, 0x43, 0x00, 0xA2, 0xE4, 0x07, + 0x00, 0x05, 0xDA, 0x87, 0x00, 0x01, 0xC8, 0x00, 0xFF, 0x23, 0x80, 0x01, 0x05, 0x05, 0x00, 0x63, + 0xF7, 0x04, 0x1A, 0x09, 0xF6, 0x08, 0x6E, 0x04, 0x00, 0x02, 0x80, 0x43, 0x76, 0x08, 0x80, 0x02, + 0x77, 0x04, 0x00, 0x63, 0xF7, 0x04, 0x1A, 0x09, 0xF6, 0x08, 0x6E, 0x04, 0x00, 0x02, 0x00, 0xA0, + 0x14, 0x08, 0x16, 0x88, 0x00, 0x43, 0x76, 0x08, 0x80, 0x02, 0x77, 0x04, 0x00, 0x63, 0xF3, 0x04, + 0x00, 0x23, 0xF4, 0x00, 0x74, 0x00, 0x80, 0x43, 0xF4, 0x00, 0xCF, 0x40, 0x00, 0xA2, 0x44, 0x08, + 0x74, 0x04, 0x02, 0x01, 0xF7, 0xC9, 0xF6, 0xD9, 0x00, 0x01, 0x01, 0xA1, 0x24, 0x08, 0x04, 0x98, + 0x26, 0x95, 0x24, 0x88, 0x73, 0x04, 0x00, 0x63, 0xF3, 0x04, 0x75, 0x04, 0x5A, 0x88, 0x02, 0x01, + 0x04, 0xD8, 0x46, 0x97, 0x04, 0x98, 0x26, 0x95, 0x4A, 0x88, 0x75, 0x00, 0x00, 0xA3, 0x64, 0x08, + 0x00, 0x05, 0x4E, 0x88, 0x73, 0x04, 0x00, 0x63, 0x80, 0x7B, 0x80, 0x63, 0x06, 0xA6, 0x76, 0x08, + 0x00, 0x33, 0x3E, 0x00, 0xC0, 0x88, 0x80, 0x67, 0x83, 0x03, 0x80, 0x63, 0x00, 0x63, 0x38, 0x2B, + 0x9C, 0x88, 0x38, 0x2B, 0x92, 0x88, 0x32, 0x09, 0x31, 0x05, 0x92, 0x98, 0x05, 0x05, 0xB2, 0x09, + 0x00, 0x63, 0x00, 0x32, 0x00, 0x36, 0x00, 0x3A, 0x00, 0x3E, 0x00, 0x63, 0x80, 0x32, 0x80, 0x36, + 0x80, 0x3A, 0x80, 0x3E, 0x00, 0x63, 0x38, 0x2B, 0x40, 0x32, 0x40, 0x36, 0x40, 0x3A, 0x40, 0x3E, + 0x00, 0x63, 0x5A, 0x20, 0xC9, 0x40, 0x00, 0xA0, 0xB2, 0x08, 0x5D, 0x00, 0xFE, 0xC3, 0x00, 0x63, + 0x80, 0x73, 0xE6, 0x20, 0x02, 0x23, 0xE8, 0x00, 0x82, 0x73, 0xFF, 0xFD, 0x80, 0x73, 0x13, 0x23, + 0xF6, 0x88, 0x66, 0x20, 0xC0, 0x20, 0x04, 0x23, 0xA0, 0x01, 0xA1, 0x23, 0xA1, 0x01, 0x81, 0x62, + 0xE0, 0x88, 0x80, 0x73, 0x80, 0x77, 0x68, 0x00, 0x00, 0xA2, 0x80, 0x00, 0x03, 0xC2, 0xF1, 0xC7, + 0x41, 0x23, 0xF6, 0x88, 0x11, 0x23, 0xA1, 0x01, 0x04, 0x23, 0xA0, 0x01, 0xE6, 0x84, +}; + +STATIC ushort _asc_mcode_size ASC_INITDATA = sizeof(_asc_mcode_buf); +STATIC ulong _asc_mcode_chksum ASC_INITDATA = 0x012B5442UL; + +#define ASC_SYN_OFFSET_ONE_DISABLE_LIST 16 +STATIC uchar _syn_offset_one_disable_cmd[ASC_SYN_OFFSET_ONE_DISABLE_LIST] = +{ + SCSICMD_Inquiry, + SCSICMD_RequestSense, + SCSICMD_ReadCapacity, + SCSICMD_ReadTOC, + SCSICMD_ModeSelect6, + SCSICMD_ModeSense6, + SCSICMD_ModeSelect10, + SCSICMD_ModeSense10, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF +}; + +STATIC int +AscExeScsiQueue( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + REG ASC_SCSI_Q * scsiq +) +{ + PortAddr iop_base; + int last_int_level; + int sta; + int n_q_required; + int disable_syn_offset_one_fix; + int i; + ulong addr; + ASC_EXE_CALLBACK asc_exe_callback; + ushort sg_entry_cnt = 0; + ushort sg_entry_cnt_minus_one = 0; + uchar target_ix; + uchar tid_no; + uchar sdtr_data; + uchar extra_bytes; + uchar scsi_cmd; + uchar disable_cmd; + ASC_SG_HEAD *sg_head; + ulong data_cnt; + + iop_base = asc_dvc->iop_base; + sg_head = scsiq->sg_head; + asc_exe_callback = (ASC_EXE_CALLBACK) asc_dvc->exe_callback; + if (asc_dvc->err_code != 0) + return (ERR); + if (scsiq == (ASC_SCSI_Q *) 0L) { + AscSetLibErrorCode(asc_dvc, ASCQ_ERR_SCSIQ_NULL_PTR); + return (ERR); + } + scsiq->q1.q_no = 0; + if ((scsiq->q2.tag_code & ASC_TAG_FLAG_EXTRA_BYTES) == 0) { + scsiq->q1.extra_bytes = 0; + } + sta = 0; + target_ix = scsiq->q2.target_ix; + tid_no = ASC_TIX_TO_TID(target_ix); + n_q_required = 1; + if (scsiq->cdbptr[0] == SCSICMD_RequestSense) { + if ((asc_dvc->init_sdtr & scsiq->q1.target_id) != 0) { + asc_dvc->sdtr_done &= ~scsiq->q1.target_id ; + sdtr_data = AscGetMCodeInitSDTRAtID(iop_base, tid_no); + AscMsgOutSDTR(asc_dvc, + asc_dvc->sdtr_period_tbl[(sdtr_data >> 4) & + (uchar) (asc_dvc->max_sdtr_index - 1)], + (uchar) (sdtr_data & (uchar) ASC_SYN_MAX_OFFSET)); + scsiq->q1.cntl |= (QC_MSG_OUT | QC_URGENT); + } + } + last_int_level = DvcEnterCritical(); + if (asc_dvc->in_critical_cnt != 0) { + DvcLeaveCritical(last_int_level); + AscSetLibErrorCode(asc_dvc, ASCQ_ERR_CRITICAL_RE_ENTRY); + return (ERR); + } + asc_dvc->in_critical_cnt++; + if ((scsiq->q1.cntl & QC_SG_HEAD) != 0) { + if ((sg_entry_cnt = sg_head->entry_cnt) == 0) { + asc_dvc->in_critical_cnt--; + DvcLeaveCritical(last_int_level); + return (ERR); + } + if (sg_entry_cnt > ASC_MAX_SG_LIST) { + return (ERR); + } + if (sg_entry_cnt == 1) { + scsiq->q1.data_addr = sg_head->sg_list[0].addr; + scsiq->q1.data_cnt = sg_head->sg_list[0].bytes; + scsiq->q1.cntl &= ~(QC_SG_HEAD | QC_SG_SWAP_QUEUE); + } + sg_entry_cnt_minus_one = sg_entry_cnt - 1; + } + scsi_cmd = scsiq->cdbptr[0]; + disable_syn_offset_one_fix = FALSE; + if ((asc_dvc->pci_fix_asyn_xfer & scsiq->q1.target_id) && + !(asc_dvc->pci_fix_asyn_xfer_always & scsiq->q1.target_id)) { + if (scsiq->q1.cntl & QC_SG_HEAD) { + data_cnt = 0; + for (i = 0; i < sg_entry_cnt; i++) { + data_cnt += sg_head->sg_list[i].bytes; + } + } else { + data_cnt = scsiq->q1.data_cnt; + } + if (data_cnt != 0UL) { + if (data_cnt < 512UL) { + disable_syn_offset_one_fix = TRUE; + } else { + for (i = 0; i < ASC_SYN_OFFSET_ONE_DISABLE_LIST; i++) { + disable_cmd = _syn_offset_one_disable_cmd[i]; + if (disable_cmd == 0xFF) { + break; + } + if (scsi_cmd == disable_cmd) { + disable_syn_offset_one_fix = TRUE; + break; + } + } + } + } + } + if (disable_syn_offset_one_fix) { + scsiq->q2.tag_code &= ~M2_QTAG_MSG_SIMPLE; + scsiq->q2.tag_code |= (ASC_TAG_FLAG_DISABLE_ASYN_USE_SYN_FIX | + ASC_TAG_FLAG_DISABLE_DISCONNECT); + } else { + scsiq->q2.tag_code &= 0x23; + } + if ((scsiq->q1.cntl & QC_SG_HEAD) != 0) { + if (asc_dvc->bug_fix_cntl) { + if (asc_dvc->bug_fix_cntl & ASC_BUG_FIX_IF_NOT_DWB) { + if ((scsi_cmd == SCSICMD_Read6) || + (scsi_cmd == SCSICMD_Read10)) { + addr = sg_head->sg_list[sg_entry_cnt_minus_one].addr + + sg_head->sg_list[sg_entry_cnt_minus_one].bytes; + extra_bytes = (uchar) ((ushort) addr & 0x0003); + if ((extra_bytes != 0) && + ((scsiq->q2.tag_code & ASC_TAG_FLAG_EXTRA_BYTES) + == 0)) { + scsiq->q2.tag_code |= ASC_TAG_FLAG_EXTRA_BYTES; + scsiq->q1.extra_bytes = extra_bytes; + sg_head->sg_list[sg_entry_cnt_minus_one].bytes -= + (ulong) extra_bytes; + } + } + } + } + sg_head->entry_to_copy = sg_head->entry_cnt; + n_q_required = AscSgListToQueue(sg_entry_cnt); + if ((AscGetNumOfFreeQueue(asc_dvc, target_ix, n_q_required) >= + (uint) n_q_required) || ((scsiq->q1.cntl & QC_URGENT) != 0)) { + if ((sta = AscSendScsiQueue(asc_dvc, scsiq, + n_q_required)) == 1) { + asc_dvc->in_critical_cnt--; + if (asc_exe_callback != 0) { + (*asc_exe_callback) (asc_dvc, scsiq); + } + DvcLeaveCritical(last_int_level); + return (sta); + } + } + } else { + if (asc_dvc->bug_fix_cntl) { + if (asc_dvc->bug_fix_cntl & ASC_BUG_FIX_IF_NOT_DWB) { + if ((scsi_cmd == SCSICMD_Read6) || + (scsi_cmd == SCSICMD_Read10)) { + addr = scsiq->q1.data_addr + scsiq->q1.data_cnt; + extra_bytes = (uchar) ((ushort) addr & 0x0003); + if ((extra_bytes != 0) && + ((scsiq->q2.tag_code & ASC_TAG_FLAG_EXTRA_BYTES) + == 0)) { + if (((ushort) scsiq->q1.data_cnt & 0x01FF) == 0) { + scsiq->q2.tag_code |= ASC_TAG_FLAG_EXTRA_BYTES; + scsiq->q1.data_cnt -= (ulong) extra_bytes; + scsiq->q1.extra_bytes = extra_bytes; + } + } + } + } + } + n_q_required = 1; + if ((AscGetNumOfFreeQueue(asc_dvc, target_ix, 1) >= 1) || + ((scsiq->q1.cntl & QC_URGENT) != 0)) { + if ((sta = AscSendScsiQueue(asc_dvc, scsiq, + n_q_required)) == 1) { + asc_dvc->in_critical_cnt--; + if (asc_exe_callback != 0) { + (*asc_exe_callback) (asc_dvc, scsiq); + } + DvcLeaveCritical(last_int_level); + return (sta); + } + } + } + asc_dvc->in_critical_cnt--; + DvcLeaveCritical(last_int_level); + return (sta); +} + +STATIC int +AscSendScsiQueue( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + REG ASC_SCSI_Q * scsiq, + uchar n_q_required +) +{ + PortAddr iop_base; + uchar free_q_head; + uchar next_qp; + uchar tid_no; + uchar target_ix; + int sta; + + iop_base = asc_dvc->iop_base; + target_ix = scsiq->q2.target_ix; + tid_no = ASC_TIX_TO_TID(target_ix); + sta = 0; + free_q_head = (uchar) AscGetVarFreeQHead(iop_base); + if (n_q_required > 1) { + if ((next_qp = AscAllocMultipleFreeQueue(iop_base, + free_q_head, (uchar) (n_q_required))) + != (uchar) ASC_QLINK_END) { + asc_dvc->last_q_shortage = 0; + scsiq->sg_head->queue_cnt = n_q_required - 1; + scsiq->q1.q_no = free_q_head; + if ((sta = AscPutReadySgListQueue(asc_dvc, scsiq, + free_q_head)) == 1) { + AscPutVarFreeQHead(iop_base, next_qp); + asc_dvc->cur_total_qng += (uchar) (n_q_required); + asc_dvc->cur_dvc_qng[tid_no]++; + } + return (sta); + } + } else if (n_q_required == 1) { + if ((next_qp = AscAllocFreeQueue(iop_base, + free_q_head)) != ASC_QLINK_END) { + scsiq->q1.q_no = free_q_head; + if ((sta = AscPutReadyQueue(asc_dvc, scsiq, + free_q_head)) == 1) { + AscPutVarFreeQHead(iop_base, next_qp); + asc_dvc->cur_total_qng++; + asc_dvc->cur_dvc_qng[tid_no]++; + } + return (sta); + } + } + return (sta); +} + +STATIC int +AscSgListToQueue( + int sg_list +) +{ + int n_sg_list_qs; + + n_sg_list_qs = ((sg_list - 1) / ASC_SG_LIST_PER_Q); + if (((sg_list - 1) % ASC_SG_LIST_PER_Q) != 0) + n_sg_list_qs++; + return (n_sg_list_qs + 1); +} + + +STATIC uint +AscGetNumOfFreeQueue( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + uchar target_ix, + uchar n_qs +) +{ + uint cur_used_qs; + uint cur_free_qs; + ASC_SCSI_BIT_ID_TYPE target_id; + uchar tid_no; + + target_id = ASC_TIX_TO_TARGET_ID(target_ix); + tid_no = ASC_TIX_TO_TID(target_ix); + if ((asc_dvc->unit_not_ready & target_id) || + (asc_dvc->queue_full_or_busy & target_id)) { + return (0); + } + if (n_qs == 1) { + cur_used_qs = (uint) asc_dvc->cur_total_qng + + (uint) asc_dvc->last_q_shortage + + (uint) ASC_MIN_FREE_Q; + } else { + cur_used_qs = (uint) asc_dvc->cur_total_qng + + (uint) ASC_MIN_FREE_Q; + } + if ((uint) (cur_used_qs + n_qs) <= (uint) asc_dvc->max_total_qng) { + cur_free_qs = (uint) asc_dvc->max_total_qng - cur_used_qs; + if (asc_dvc->cur_dvc_qng[tid_no] >= + asc_dvc->max_dvc_qng[tid_no]) { + return (0); + } + return (cur_free_qs); + } + if (n_qs > 1) { + if ((n_qs > asc_dvc->last_q_shortage) && (n_qs <= (asc_dvc->max_total_qng - ASC_MIN_FREE_Q))) { + asc_dvc->last_q_shortage = n_qs; + } + } + return (0); +} + +STATIC int +AscPutReadyQueue( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + REG ASC_SCSI_Q * scsiq, + uchar q_no +) +{ + ushort q_addr; + uchar tid_no; + uchar sdtr_data; + uchar syn_period_ix; + uchar syn_offset; + PortAddr iop_base; + + iop_base = asc_dvc->iop_base; + if (((asc_dvc->init_sdtr & scsiq->q1.target_id) != 0) && + ((asc_dvc->sdtr_done & scsiq->q1.target_id) == 0)) { + tid_no = ASC_TIX_TO_TID(scsiq->q2.target_ix); + sdtr_data = AscGetMCodeInitSDTRAtID(iop_base, tid_no); + syn_period_ix = (sdtr_data >> 4) & (asc_dvc->max_sdtr_index - 1); + syn_offset = sdtr_data & ASC_SYN_MAX_OFFSET; + AscMsgOutSDTR(asc_dvc, + asc_dvc->sdtr_period_tbl[syn_period_ix], + syn_offset); + scsiq->q1.cntl |= QC_MSG_OUT; + } + q_addr = ASC_QNO_TO_QADDR(q_no); + if ((scsiq->q1.target_id & asc_dvc->use_tagged_qng) == 0) { + scsiq->q2.tag_code &= ~M2_QTAG_MSG_SIMPLE; + } + scsiq->q1.status = QS_FREE; + AscMemWordCopyToLram(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_CDB_BEG), + (ushort *) scsiq->cdbptr, + (ushort) ((ushort) scsiq->q2.cdb_len >> 1)); + DvcPutScsiQ(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_CPY_BEG), + (ushort *) & scsiq->q1.cntl, + (ushort) ((((sizeof (ASC_SCSIQ_1) + sizeof (ASC_SCSIQ_2)) / 2) - 1))); + AscWriteLramWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_STATUS), + (ushort) (((ushort) scsiq->q1.q_no << 8) | (ushort) QS_READY)); + return (1); +} + +STATIC int +AscPutReadySgListQueue( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + REG ASC_SCSI_Q * scsiq, + uchar q_no +) +{ + int sta; + int i; + ASC_SG_HEAD *sg_head; + ASC_SG_LIST_Q scsi_sg_q; + ulong saved_data_addr; + ulong saved_data_cnt; + PortAddr iop_base; + ushort sg_list_dwords; + ushort sg_index; + ushort sg_entry_cnt; + ushort q_addr; + uchar next_qp; + + iop_base = asc_dvc->iop_base; + sg_head = scsiq->sg_head; + saved_data_addr = scsiq->q1.data_addr; + saved_data_cnt = scsiq->q1.data_cnt; + scsiq->q1.data_addr = sg_head->sg_list[0].addr; + scsiq->q1.data_cnt = sg_head->sg_list[0].bytes; + sg_entry_cnt = sg_head->entry_cnt - 1; + if (sg_entry_cnt != 0) { + scsiq->q1.cntl |= QC_SG_HEAD; + q_addr = ASC_QNO_TO_QADDR(q_no); + sg_index = 1; + scsiq->q1.sg_queue_cnt = sg_head->queue_cnt; + scsi_sg_q.sg_head_qp = q_no; + scsi_sg_q.cntl = QCSG_SG_XFER_LIST; + for (i = 0; i < sg_head->queue_cnt; i++) { + scsi_sg_q.seq_no = i + 1; + if (sg_entry_cnt > ASC_SG_LIST_PER_Q) { + sg_list_dwords = (uchar) (ASC_SG_LIST_PER_Q * 2); + sg_entry_cnt -= ASC_SG_LIST_PER_Q; + if (i == 0) { + scsi_sg_q.sg_list_cnt = ASC_SG_LIST_PER_Q; + scsi_sg_q.sg_cur_list_cnt = ASC_SG_LIST_PER_Q; + } else { + scsi_sg_q.sg_list_cnt = ASC_SG_LIST_PER_Q - 1; + scsi_sg_q.sg_cur_list_cnt = ASC_SG_LIST_PER_Q - 1; + } + } else { + scsi_sg_q.cntl |= QCSG_SG_XFER_END; + sg_list_dwords = sg_entry_cnt << 1; + if (i == 0) { + scsi_sg_q.sg_list_cnt = sg_entry_cnt; + scsi_sg_q.sg_cur_list_cnt = sg_entry_cnt; + } else { + scsi_sg_q.sg_list_cnt = sg_entry_cnt - 1; + scsi_sg_q.sg_cur_list_cnt = sg_entry_cnt - 1; + } + sg_entry_cnt = 0; + } + next_qp = AscReadLramByte(iop_base, + (ushort) (q_addr + ASC_SCSIQ_B_FWD)); + scsi_sg_q.q_no = next_qp; + q_addr = ASC_QNO_TO_QADDR(next_qp); + AscMemWordCopyToLram(iop_base, + (ushort) (q_addr + ASC_SCSIQ_SGHD_CPY_BEG), + (ushort *) & scsi_sg_q, + (ushort) (sizeof (ASC_SG_LIST_Q) >> 1)); + AscMemDWordCopyToLram(iop_base, + (ushort) (q_addr + ASC_SGQ_LIST_BEG), + (ulong *) & sg_head->sg_list[sg_index], + (ushort) sg_list_dwords); + sg_index += ASC_SG_LIST_PER_Q; + } + } else { + scsiq->q1.cntl &= ~QC_SG_HEAD; + } + sta = AscPutReadyQueue(asc_dvc, scsiq, q_no); + scsiq->q1.data_addr = saved_data_addr; + scsiq->q1.data_cnt = saved_data_cnt; + return (sta); +} + +STATIC int +AscAbortSRB( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + ulong srb_ptr +) +{ + int sta; + ASC_SCSI_BIT_ID_TYPE saved_unit_not_ready; + PortAddr iop_base; + + iop_base = asc_dvc->iop_base; + sta = ERR; + saved_unit_not_ready = asc_dvc->unit_not_ready; + asc_dvc->unit_not_ready = 0xFF; + AscWaitISRDone(asc_dvc); + if (AscStopQueueExe(iop_base) == 1) { + if (AscRiscHaltedAbortSRB(asc_dvc, srb_ptr) == 1) { + sta = 1; + AscCleanUpBusyQueue(iop_base); + AscStartQueueExe(iop_base); + } else { + sta = 0; + AscStartQueueExe(iop_base); + } + } + asc_dvc->unit_not_ready = saved_unit_not_ready; + return (sta); +} + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int +AscResetDevice( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + uchar target_ix +) +{ + PortAddr iop_base; + int sta; + uchar tid_no; + + ASC_SCSI_BIT_ID_TYPE target_id; + int i; + ASC_SCSI_REQ_Q scsiq_buf; + ASC_SCSI_REQ_Q *scsiq; + uchar *buf; + ASC_SCSI_BIT_ID_TYPE saved_unit_not_ready; + iop_base = asc_dvc->iop_base; + tid_no = ASC_TIX_TO_TID(target_ix); + target_id = ASC_TID_TO_TARGET_ID(tid_no); + saved_unit_not_ready = asc_dvc->unit_not_ready; + asc_dvc->unit_not_ready = target_id; + sta = ERR; + AscWaitTixISRDone(asc_dvc, target_ix); + if (AscStopQueueExe(iop_base) == 1) { + if (AscRiscHaltedAbortTIX(asc_dvc, target_ix) == 1) { + AscCleanUpBusyQueue(iop_base); + AscStartQueueExe(iop_base); + AscWaitTixISRDone(asc_dvc, target_ix); + sta = TRUE; + scsiq = (ASC_SCSI_REQ_Q *) & scsiq_buf; + buf = (uchar *) & scsiq_buf; + for (i = 0; i < sizeof (ASC_SCSI_REQ_Q); i++) { + *buf++ = 0x00; + } + scsiq->r1.status = (uchar) QS_READY; + scsiq->r2.cdb_len = 6; + scsiq->r2.tag_code = M2_QTAG_MSG_SIMPLE; + scsiq->r1.target_id = target_id; + scsiq->r2.target_ix = ASC_TIDLUN_TO_IX(tid_no, 0); + scsiq->cdbptr = (uchar *) scsiq->cdb; + scsiq->r1.cntl = QC_NO_CALLBACK | QC_MSG_OUT | QC_URGENT; + AscWriteLramByte(asc_dvc->iop_base, ASCV_MSGOUT_BEG, + M1_BUS_DVC_RESET); + asc_dvc->unit_not_ready &= ~target_id; + asc_dvc->sdtr_done |= target_id; + if (AscExeScsiQueue(asc_dvc, (ASC_SCSI_Q *) scsiq) + == 1) { + asc_dvc->unit_not_ready = target_id; + DvcSleepMilliSecond(1000); + _AscWaitQDone(iop_base, (ASC_SCSI_Q *) scsiq); + if (AscStopQueueExe(iop_base) == 1) { + AscCleanUpDiscQueue(iop_base); + AscStartQueueExe(iop_base); + if (asc_dvc->pci_fix_asyn_xfer & target_id) { + AscSetRunChipSynRegAtID(iop_base, tid_no, + ASYN_SDTR_DATA_FIX_PCI_REV_AB); + } + AscWaitTixISRDone(asc_dvc, target_ix); + } + } else { + sta = 0; + } + asc_dvc->sdtr_done &= ~target_id; + } else { + sta = ERR; + AscStartQueueExe(iop_base); + } + } + asc_dvc->unit_not_ready = saved_unit_not_ready; + return (sta); +} +#endif /* version >= v1.3.89 */ + +STATIC int +AscResetSB( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + int sta; + int i; + PortAddr iop_base; + + iop_base = asc_dvc->iop_base; + asc_dvc->unit_not_ready = 0xFF; + sta = TRUE; + AscWaitISRDone(asc_dvc); + AscStopQueueExe(iop_base); + asc_dvc->sdtr_done = 0; + AscResetChipAndScsiBus(asc_dvc); + DvcSleepMilliSecond((ulong) ((ushort) asc_dvc->scsi_reset_wait * 1000)); + AscReInitLram(asc_dvc); + for (i = 0; i <= ASC_MAX_TID; i++) { + asc_dvc->cur_dvc_qng[i] = 0; + if (asc_dvc->pci_fix_asyn_xfer & (ASC_SCSI_BIT_ID_TYPE) (0x01 << i)) { + AscSetChipSynRegAtID(iop_base, i, ASYN_SDTR_DATA_FIX_PCI_REV_AB); + } + } + asc_dvc->err_code = 0; + AscSetPCAddr(iop_base, ASC_MCODE_START_ADDR); + if (AscGetPCAddr(iop_base) != ASC_MCODE_START_ADDR) { + sta = ERR; + } + if (AscStartChip(iop_base) == 0) { + sta = ERR; + } + AscStartQueueExe(iop_base); + asc_dvc->unit_not_ready = 0; + asc_dvc->queue_full_or_busy = 0; + return (sta); +} + +STATIC int +AscSetRunChipSynRegAtID( + PortAddr iop_base, + uchar tid_no, + uchar sdtr_data +) +{ + int sta = FALSE; + + if (AscHostReqRiscHalt(iop_base)) { + sta = AscSetChipSynRegAtID(iop_base, tid_no, sdtr_data); + AscStartChip(iop_base); + return (sta); + } + return (sta); +} + +STATIC int +AscSetChipSynRegAtID( + PortAddr iop_base, + uchar id, + uchar sdtr_data +) +{ + ASC_SCSI_BIT_ID_TYPE org_id; + int i; + int sta = TRUE; + + AscSetBank(iop_base, 1); + org_id = AscReadChipDvcID(iop_base); + for (i = 0; i <= ASC_MAX_TID; i++) { + if (org_id == (0x01 << i)) + break; + } + org_id = i; + AscWriteChipDvcID(iop_base, id); + if (AscReadChipDvcID(iop_base) == (0x01 << id)) { + AscSetBank(iop_base, 0); + AscSetChipSyn(iop_base, sdtr_data); + if (AscGetChipSyn(iop_base) != sdtr_data) { + sta = FALSE; + } + } else { + sta = FALSE; + } + AscSetBank(iop_base, 1); + AscWriteChipDvcID(iop_base, org_id); + AscSetBank(iop_base, 0); + return (sta); +} + +STATIC int +AscReInitLram( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + AscInitLram(asc_dvc); + AscInitQLinkVar(asc_dvc); + return (0); +} + +STATIC ushort +AscInitLram( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + uchar i; + ushort s_addr; + PortAddr iop_base; + ushort warn_code; + + iop_base = asc_dvc->iop_base; + warn_code = 0; + AscMemWordSetLram(iop_base, ASC_QADR_BEG, 0, + (ushort) (((int) (asc_dvc->max_total_qng + 2 + 1) * 64) >> 1) +); + i = ASC_MIN_ACTIVE_QNO; + s_addr = ASC_QADR_BEG + ASC_QBLK_SIZE; + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_FWD), + (uchar) (i + 1)); + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_BWD), + (uchar) (asc_dvc->max_total_qng)); + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_QNO), + (uchar) i); + i++; + s_addr += ASC_QBLK_SIZE; + for (; i < asc_dvc->max_total_qng; i++, s_addr += ASC_QBLK_SIZE) { + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_FWD), + (uchar) (i + 1)); + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_BWD), + (uchar) (i - 1)); + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_QNO), + (uchar) i); + } + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_FWD), + (uchar) ASC_QLINK_END); + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_BWD), + (uchar) (asc_dvc->max_total_qng - 1)); + AscWriteLramByte(iop_base, (ushort) (s_addr + ASC_SCSIQ_B_QNO), + (uchar) asc_dvc->max_total_qng); + i++; + s_addr += ASC_QBLK_SIZE; + for (; i <= (uchar) (asc_dvc->max_total_qng + 3); + i++, s_addr += ASC_QBLK_SIZE) { + AscWriteLramByte(iop_base, + (ushort) (s_addr + (ushort) ASC_SCSIQ_B_FWD), i); + AscWriteLramByte(iop_base, + (ushort) (s_addr + (ushort) ASC_SCSIQ_B_BWD), i); + AscWriteLramByte(iop_base, + (ushort) (s_addr + (ushort) ASC_SCSIQ_B_QNO), i); + } + return (warn_code); +} + +STATIC ushort +AscInitQLinkVar( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + PortAddr iop_base; + int i; + ushort lram_addr; + + iop_base = asc_dvc->iop_base; + AscPutRiscVarFreeQHead(iop_base, 1); + AscPutRiscVarDoneQTail(iop_base, asc_dvc->max_total_qng); + AscPutVarFreeQHead(iop_base, 1); + AscPutVarDoneQTail(iop_base, asc_dvc->max_total_qng); + AscWriteLramByte(iop_base, ASCV_BUSY_QHEAD_B, + (uchar) ((int) asc_dvc->max_total_qng + 1)); + AscWriteLramByte(iop_base, ASCV_DISC1_QHEAD_B, + (uchar) ((int) asc_dvc->max_total_qng + 2)); + AscWriteLramByte(iop_base, (ushort) ASCV_TOTAL_READY_Q_B, + asc_dvc->max_total_qng); + AscWriteLramWord(iop_base, ASCV_ASCDVC_ERR_CODE_W, 0); + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0); + AscWriteLramByte(iop_base, ASCV_STOP_CODE_B, 0); + AscWriteLramByte(iop_base, ASCV_SCSIBUSY_B, 0); + AscWriteLramByte(iop_base, ASCV_WTM_FLAG_B, 0); + AscPutQDoneInProgress(iop_base, 0); + lram_addr = ASC_QADR_BEG; + for (i = 0; i < 32; i++, lram_addr += 2) { + AscWriteLramWord(iop_base, lram_addr, 0); + } + return (0); +} + +STATIC int +AscSetLibErrorCode( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + ushort err_code +) +{ + if (asc_dvc->err_code == 0) { + asc_dvc->err_code = err_code; + AscWriteLramWord(asc_dvc->iop_base, ASCV_ASCDVC_ERR_CODE_W, + err_code); + } + return (err_code); +} + + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int +_AscWaitQDone( + PortAddr iop_base, + REG ASC_SCSI_Q * scsiq +) +{ + ushort q_addr; + uchar q_status; + int count = 0; + + while (scsiq->q1.q_no == 0) ; + q_addr = ASC_QNO_TO_QADDR(scsiq->q1.q_no); + do { + q_status = AscReadLramByte(iop_base, q_addr + ASC_SCSIQ_B_STATUS); + DvcSleepMilliSecond(100L); + if (count++ > 30) { + return (0); + } + } while ((q_status & QS_READY) != 0); + return (1); +} +#endif /* version >= v1.3.89 */ + +STATIC uchar +AscMsgOutSDTR( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + uchar sdtr_period, + uchar sdtr_offset +) +{ + EXT_MSG sdtr_buf; + uchar sdtr_period_index; + PortAddr iop_base; + + iop_base = asc_dvc->iop_base; + sdtr_buf.msg_type = MS_EXTEND; + sdtr_buf.msg_len = MS_SDTR_LEN; + sdtr_buf.msg_req = MS_SDTR_CODE; + sdtr_buf.xfer_period = sdtr_period; + sdtr_offset &= ASC_SYN_MAX_OFFSET; + sdtr_buf.req_ack_offset = sdtr_offset; + if ((sdtr_period_index = + AscGetSynPeriodIndex(asc_dvc, sdtr_period)) <= + asc_dvc->max_sdtr_index) { + AscMemWordCopyToLram(iop_base, + ASCV_MSGOUT_BEG, + (ushort *) & sdtr_buf, + (ushort) (sizeof (EXT_MSG) >> 1)); + return ((sdtr_period_index << 4) | sdtr_offset); + } else { + + sdtr_buf.req_ack_offset = 0; + AscMemWordCopyToLram(iop_base, + ASCV_MSGOUT_BEG, + (ushort *) & sdtr_buf, + (ushort) (sizeof (EXT_MSG) >> 1)); + return (0); + } +} + +STATIC uchar +AscCalSDTRData( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + uchar sdtr_period, + uchar syn_offset +) +{ + uchar byte; + uchar sdtr_period_ix; + + sdtr_period_ix = AscGetSynPeriodIndex(asc_dvc, sdtr_period); + if ( + (sdtr_period_ix > asc_dvc->max_sdtr_index) +) { + return (0xFF); + } + byte = (sdtr_period_ix << 4) | (syn_offset & ASC_SYN_MAX_OFFSET); + return (byte); +} + +STATIC void +AscSetChipSDTR( + PortAddr iop_base, + uchar sdtr_data, + uchar tid_no +) +{ + AscSetChipSynRegAtID(iop_base, tid_no, sdtr_data); + AscPutMCodeSDTRDoneAtID(iop_base, tid_no, sdtr_data); + return; +} + +STATIC uchar +AscGetSynPeriodIndex( + ASC_DVC_VAR asc_ptr_type * asc_dvc, + ruchar syn_time +) +{ + ruchar *period_table; + int max_index; + int min_index; + int i; + + period_table = asc_dvc->sdtr_period_tbl; + max_index = (int) asc_dvc->max_sdtr_index; + min_index = (int)asc_dvc->host_init_sdtr_index ; + if ((syn_time <= period_table[max_index])) { + for (i = min_index; i < (max_index - 1); i++) { + if (syn_time <= period_table[i]) { + return ((uchar) i); + } + } + return ((uchar) max_index); + } else { + return ((uchar) (max_index + 1)); + } +} + +STATIC uchar +AscAllocFreeQueue( + PortAddr iop_base, + uchar free_q_head +) +{ + ushort q_addr; + uchar next_qp; + uchar q_status; + + q_addr = ASC_QNO_TO_QADDR(free_q_head); + q_status = (uchar) AscReadLramByte(iop_base, + (ushort) (q_addr + ASC_SCSIQ_B_STATUS)); + next_qp = AscReadLramByte(iop_base, + (ushort) (q_addr + ASC_SCSIQ_B_FWD)); + if (((q_status & QS_READY) == 0) && (next_qp != ASC_QLINK_END)) { + return (next_qp); + } + return (ASC_QLINK_END); +} + +STATIC uchar +AscAllocMultipleFreeQueue( + PortAddr iop_base, + uchar free_q_head, + uchar n_free_q +) +{ + uchar i; + + for (i = 0; i < n_free_q; i++) { + if ((free_q_head = AscAllocFreeQueue(iop_base, free_q_head)) + == ASC_QLINK_END) { + return (ASC_QLINK_END); + } + } + return (free_q_head); +} + +STATIC int +AscRiscHaltedAbortSRB( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + ulong srb_ptr +) +{ + PortAddr iop_base; + ushort q_addr; + uchar q_no; + ASC_QDONE_INFO scsiq_buf; + ASC_QDONE_INFO *scsiq; + ASC_ISR_CALLBACK asc_isr_callback; + int last_int_level; + + iop_base = asc_dvc->iop_base; + asc_isr_callback = (ASC_ISR_CALLBACK) asc_dvc->isr_callback; + last_int_level = DvcEnterCritical(); + scsiq = (ASC_QDONE_INFO *) & scsiq_buf; + for (q_no = ASC_MIN_ACTIVE_QNO; q_no <= asc_dvc->max_total_qng; + q_no++) { + q_addr = ASC_QNO_TO_QADDR(q_no); + scsiq->d2.srb_ptr = AscReadLramDWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_D_SRBPTR)); + if (scsiq->d2.srb_ptr == srb_ptr) { + _AscCopyLramScsiDoneQ(iop_base, q_addr, scsiq, asc_dvc->max_dma_count); + if (((scsiq->q_status & QS_READY) != 0) + && ((scsiq->q_status & QS_ABORTED) == 0) + && ((scsiq->cntl & QCSG_SG_XFER_LIST) == 0)) { + scsiq->q_status |= QS_ABORTED; + scsiq->d3.done_stat = QD_ABORTED_BY_HOST; + AscWriteLramDWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_D_SRBPTR), + 0L); + AscWriteLramByte(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_STATUS), + scsiq->q_status); + (*asc_isr_callback) (asc_dvc, scsiq); + return (1); + } + } + } + DvcLeaveCritical(last_int_level); + return (0); +} + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int +AscRiscHaltedAbortTIX( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + uchar target_ix +) +{ + PortAddr iop_base; + ushort q_addr; + uchar q_no; + ASC_QDONE_INFO scsiq_buf; + ASC_QDONE_INFO *scsiq; + ASC_ISR_CALLBACK asc_isr_callback; + int last_int_level; + + iop_base = asc_dvc->iop_base; + asc_isr_callback = (ASC_ISR_CALLBACK) asc_dvc->isr_callback; + last_int_level = DvcEnterCritical(); + scsiq = (ASC_QDONE_INFO *) & scsiq_buf; + for (q_no = ASC_MIN_ACTIVE_QNO; q_no <= asc_dvc->max_total_qng; + q_no++) { + q_addr = ASC_QNO_TO_QADDR(q_no); + _AscCopyLramScsiDoneQ(iop_base, q_addr, scsiq, asc_dvc->max_dma_count); + if (((scsiq->q_status & QS_READY) != 0) && + ((scsiq->q_status & QS_ABORTED) == 0) && + ((scsiq->cntl & QCSG_SG_XFER_LIST) == 0)) { + if (scsiq->d2.target_ix == target_ix) { + scsiq->q_status |= QS_ABORTED; + scsiq->d3.done_stat = QD_ABORTED_BY_HOST; + AscWriteLramDWord(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_D_SRBPTR), + 0L); + AscWriteLramByte(iop_base, + (ushort) (q_addr + (ushort) ASC_SCSIQ_B_STATUS), + scsiq->q_status); + (*asc_isr_callback) (asc_dvc, scsiq); + } + } + } + DvcLeaveCritical(last_int_level); + return (1); +} +#endif /* version >= v1.3.89 */ + +STATIC int +AscHostReqRiscHalt( + PortAddr iop_base +) +{ + int count = 0; + int sta = 0; + uchar saved_stop_code; + + if (AscIsChipHalted(iop_base)) + return (1); + saved_stop_code = AscReadLramByte(iop_base, ASCV_STOP_CODE_B); + AscWriteLramByte(iop_base, ASCV_STOP_CODE_B, + ASC_STOP_HOST_REQ_RISC_HALT | ASC_STOP_REQ_RISC_STOP +); + do { + if (AscIsChipHalted(iop_base)) { + sta = 1; + break; + } + DvcSleepMilliSecond(100); + } while (count++ < 20); + AscWriteLramByte(iop_base, ASCV_STOP_CODE_B, saved_stop_code); + return (sta); +} + +STATIC int +AscStopQueueExe( + PortAddr iop_base +) +{ + int count = 0; + + if (AscReadLramByte(iop_base, ASCV_STOP_CODE_B) == 0) { + AscWriteLramByte(iop_base, ASCV_STOP_CODE_B, + ASC_STOP_REQ_RISC_STOP); + do { + if ( + AscReadLramByte(iop_base, ASCV_STOP_CODE_B) & + ASC_STOP_ACK_RISC_STOP) { + return (1); + } + DvcSleepMilliSecond(100); + } while (count++ < 20); + } + return (0); +} + +STATIC int +AscStartQueueExe( + PortAddr iop_base +) +{ + if (AscReadLramByte(iop_base, ASCV_STOP_CODE_B) != 0) { + AscWriteLramByte(iop_base, ASCV_STOP_CODE_B, 0); + } + return (1); +} + +STATIC int +AscCleanUpBusyQueue( + PortAddr iop_base +) +{ + int count; + uchar stop_code; + + count = 0; + if (AscReadLramByte(iop_base, ASCV_STOP_CODE_B) != 0) { + AscWriteLramByte(iop_base, ASCV_STOP_CODE_B, + ASC_STOP_CLEAN_UP_BUSY_Q); + do { + stop_code = AscReadLramByte(iop_base, ASCV_STOP_CODE_B); + if ((stop_code & ASC_STOP_CLEAN_UP_BUSY_Q) == 0) + break; + DvcSleepMilliSecond(100); + } while (count++ < 20); + } + return (1); +} + +#if LINUX_VERSION_CODE >= ASC_LINUX_VERSION(1,3,89) +STATIC int +AscCleanUpDiscQueue( + PortAddr iop_base +) +{ + int count; + uchar stop_code; + + count = 0; + if (AscReadLramByte(iop_base, ASCV_STOP_CODE_B) != 0) { + AscWriteLramByte(iop_base, ASCV_STOP_CODE_B, + ASC_STOP_CLEAN_UP_DISC_Q); + do { + stop_code = AscReadLramByte(iop_base, ASCV_STOP_CODE_B); + if ((stop_code & ASC_STOP_CLEAN_UP_DISC_Q) == 0) + break; + DvcSleepMilliSecond(100); + } while (count++ < 20); + } + return (1); +} +#endif /* version >= v1.3.89 */ + +STATIC int +AscWaitTixISRDone( + ASC_DVC_VAR asc_ptr_type * asc_dvc, + uchar target_ix +) +{ + uchar cur_req; + uchar tid_no; + int i = 0; + + tid_no = ASC_TIX_TO_TID(target_ix); + while (i++ < 10) { + if ((cur_req = asc_dvc->cur_dvc_qng[tid_no]) == 0) { + break; + } + DvcSleepMilliSecond(1000L); + if (asc_dvc->cur_dvc_qng[tid_no] == cur_req) { + break; + } + } + return (1); +} + +STATIC int +AscWaitISRDone( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc +) +{ + int tid; + + for (tid = 0; tid <= ASC_MAX_TID; tid++) { + AscWaitTixISRDone(asc_dvc, ASC_TID_TO_TIX(tid)); + } + return (1); +} + +STATIC ulong +AscGetOnePhyAddr( + REG ASC_DVC_VAR asc_ptr_type * asc_dvc, + uchar * buf_addr, + ulong buf_size +) +{ + ASC_MIN_SG_HEAD sg_head; + + sg_head.entry_cnt = ASC_MIN_SG_LIST; + if (DvcGetSGList(asc_dvc, (uchar *) buf_addr, + buf_size, (ASC_SG_HEAD *) & sg_head) != buf_size) { + return (0L); + } + if (sg_head.entry_cnt > 1) { + return (0L); + } + return (sg_head.sg_list[0].addr); +} + +STATIC void +DvcDelayMicroSecond(ADV_DVC_VAR *asc_dvc, ushort micro_sec) +{ + udelay(micro_sec); +} + +STATIC void +DvcDelayNanoSecond(ASC_DVC_VAR asc_ptr_type * asc_dvc, ulong nano_sec) +{ + udelay((nano_sec + 999)/1000); +} + +ASC_INITFUNC( +STATIC ulong +AscGetEisaProductID( + PortAddr iop_base +) +) +{ + PortAddr eisa_iop; + ushort product_id_high, product_id_low; + ulong product_id; + + eisa_iop = ASC_GET_EISA_SLOT(iop_base) | ASC_EISA_PID_IOP_MASK; + product_id_low = inpw(eisa_iop); + product_id_high = inpw(eisa_iop + 2); + product_id = ((ulong) product_id_high << 16) | (ulong) product_id_low; + return (product_id); +} + +ASC_INITFUNC( +STATIC PortAddr +AscSearchIOPortAddrEISA( + PortAddr iop_base +) +) +{ + ulong eisa_product_id; + + if (iop_base == 0) { + iop_base = ASC_EISA_MIN_IOP_ADDR; + } else { + if (iop_base == ASC_EISA_MAX_IOP_ADDR) + return (0); + if ((iop_base & 0x0050) == 0x0050) { + iop_base += ASC_EISA_BIG_IOP_GAP; + } else { + iop_base += ASC_EISA_SMALL_IOP_GAP; + } + } + while (iop_base <= ASC_EISA_MAX_IOP_ADDR) { + eisa_product_id = AscGetEisaProductID(iop_base); + if ((eisa_product_id == ASC_EISA_ID_740) || + (eisa_product_id == ASC_EISA_ID_750)) { + if (AscFindSignature(iop_base)) { + inpw(iop_base + 4); + return (iop_base); + } + } + if (iop_base == ASC_EISA_MAX_IOP_ADDR) + return (0); + if ((iop_base & 0x0050) == 0x0050) { + iop_base += ASC_EISA_BIG_IOP_GAP; + } else { + iop_base += ASC_EISA_SMALL_IOP_GAP; + } + } + return (0); +} + +STATIC int +AscStartChip( + PortAddr iop_base +) +{ + AscSetChipControl(iop_base, 0); + if ((AscGetChipStatus(iop_base) & CSW_HALTED) != 0) { + return (0); + } + return (1); +} + +STATIC int +AscStopChip( + PortAddr iop_base +) +{ + uchar cc_val; + + cc_val = AscGetChipControl(iop_base) & (~(CC_SINGLE_STEP | CC_TEST | CC_DIAG)); + AscSetChipControl(iop_base, (uchar) (cc_val | CC_HALT)); + AscSetChipIH(iop_base, INS_HALT); + AscSetChipIH(iop_base, INS_RFLAG_WTM); + if ((AscGetChipStatus(iop_base) & CSW_HALTED) == 0) { + return (0); + } + return (1); +} + +STATIC int +AscIsChipHalted( + PortAddr iop_base +) +{ + if ((AscGetChipStatus(iop_base) & CSW_HALTED) != 0) { + if ((AscGetChipControl(iop_base) & CC_HALT) != 0) { + return (1); + } + } + return (0); +} + +STATIC void +AscSetChipIH( + PortAddr iop_base, + ushort ins_code +) +{ + AscSetBank(iop_base, 1); + AscWriteChipIH(iop_base, ins_code); + AscSetBank(iop_base, 0); + return; +} + +STATIC void +AscAckInterrupt( + PortAddr iop_base +) +{ + uchar host_flag; + uchar risc_flag; + ushort loop; + + loop = 0; + do { + risc_flag = AscReadLramByte(iop_base, ASCV_RISC_FLAG_B); + if (loop++ > 0x7FFF) { + break; + } + } while ((risc_flag & ASC_RISC_FLAG_GEN_INT) != 0); + host_flag = AscReadLramByte(iop_base, ASCV_HOST_FLAG_B) & (~ASC_HOST_FLAG_ACK_INT); + AscWriteLramByte(iop_base, ASCV_HOST_FLAG_B, + (uchar) (host_flag | ASC_HOST_FLAG_ACK_INT)); + AscSetChipStatus(iop_base, CIW_INT_ACK); + loop = 0; + while (AscGetChipStatus(iop_base) & CSW_INT_PENDING) { + AscSetChipStatus(iop_base, CIW_INT_ACK); + if (loop++ > 3) { + break; + } + } + AscWriteLramByte(iop_base, ASCV_HOST_FLAG_B, host_flag); + return; +} + +STATIC void +AscDisableInterrupt( + PortAddr iop_base +) +{ + ushort cfg; + + cfg = AscGetChipCfgLsw(iop_base); + AscSetChipCfgLsw(iop_base, cfg & (~ASC_CFG0_HOST_INT_ON)); + return; +} + +STATIC void +AscEnableInterrupt( + PortAddr iop_base +) +{ + ushort cfg; + + cfg = AscGetChipCfgLsw(iop_base); + AscSetChipCfgLsw(iop_base, cfg | ASC_CFG0_HOST_INT_ON); + return; +} + + + +STATIC void +AscSetBank( + PortAddr iop_base, + uchar bank +) +{ + uchar val; + + val = AscGetChipControl(iop_base) & + (~(CC_SINGLE_STEP | CC_TEST | CC_DIAG | CC_SCSI_RESET | CC_CHIP_RESET)); + if (bank == 1) { + val |= CC_BANK_ONE; + } else if (bank == 2) { + val |= CC_DIAG | CC_BANK_ONE; + } else { + val &= ~CC_BANK_ONE; + } + AscSetChipControl(iop_base, val); + return; +} + +STATIC int +AscResetChipAndScsiBus( + ASC_DVC_VAR *asc_dvc +) +{ + PortAddr iop_base; + + iop_base = asc_dvc->iop_base; + while (AscGetChipStatus(iop_base) & CSW_SCSI_RESET_ACTIVE) ; + AscStopChip(iop_base); + AscSetChipControl(iop_base, CC_CHIP_RESET | CC_SCSI_RESET | CC_HALT); + DvcDelayNanoSecond(asc_dvc, 60000); + AscSetChipIH(iop_base, INS_RFLAG_WTM); + AscSetChipIH(iop_base, INS_HALT); + AscSetChipControl(iop_base, CC_CHIP_RESET | CC_HALT); + AscSetChipControl(iop_base, CC_HALT); + DvcSleepMilliSecond(200); + AscSetChipStatus(iop_base, CIW_CLR_SCSI_RESET_INT); + AscSetChipStatus(iop_base, 0); + return (AscIsChipHalted(iop_base)); +} + +ASC_INITFUNC( +STATIC ulong +AscGetMaxDmaCount( + ushort bus_type +) +) +{ + if (bus_type & ASC_IS_ISA) + return (ASC_MAX_ISA_DMA_COUNT); + else if (bus_type & (ASC_IS_EISA | ASC_IS_VL)) + return (ASC_MAX_VL_DMA_COUNT); + return (ASC_MAX_PCI_DMA_COUNT); +} + +ASC_INITFUNC( +STATIC ushort +AscGetIsaDmaChannel( + PortAddr iop_base +) +) +{ + ushort channel; + + channel = AscGetChipCfgLsw(iop_base) & 0x0003; + if (channel == 0x03) + return (0); + else if (channel == 0x00) + return (7); + return (channel + 4); +} + +ASC_INITFUNC( +STATIC ushort +AscSetIsaDmaChannel( + PortAddr iop_base, + ushort dma_channel +) +) +{ + ushort cfg_lsw; + uchar value; + + if ((dma_channel >= 5) && (dma_channel <= 7)) { + if (dma_channel == 7) + value = 0x00; + else + value = dma_channel - 4; + cfg_lsw = AscGetChipCfgLsw(iop_base) & 0xFFFC; + cfg_lsw |= value; + AscSetChipCfgLsw(iop_base, cfg_lsw); + return (AscGetIsaDmaChannel(iop_base)); + } + return (0); +} + +ASC_INITFUNC( +STATIC uchar +AscSetIsaDmaSpeed( + PortAddr iop_base, + uchar speed_value +) +) +{ + speed_value &= 0x07; + AscSetBank(iop_base, 1); + AscWriteChipDmaSpeed(iop_base, speed_value); + AscSetBank(iop_base, 0); + return (AscGetIsaDmaSpeed(iop_base)); +} + +ASC_INITFUNC( +STATIC uchar +AscGetIsaDmaSpeed( + PortAddr iop_base +) +) +{ + uchar speed_value; + + AscSetBank(iop_base, 1); + speed_value = AscReadChipDmaSpeed(iop_base); + speed_value &= 0x07; + AscSetBank(iop_base, 0); + return (speed_value); +} + +ASC_INITFUNC( +STATIC ushort +AscReadPCIConfigWord( + ASC_DVC_VAR asc_ptr_type *asc_dvc, + ushort pci_config_offset) +) +{ + uchar lsb, msb; + + lsb = DvcReadPCIConfigByte(asc_dvc, pci_config_offset); + msb = DvcReadPCIConfigByte(asc_dvc, pci_config_offset + 1); + return ((ushort) ((msb << 8) | lsb)); +} + +ASC_INITFUNC( +STATIC ushort +AscInitGetConfig( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + ushort warn_code; + PortAddr iop_base; + ushort PCIDeviceID; + ushort PCIVendorID; + uchar PCIRevisionID; + uchar prevCmdRegBits; + + warn_code = 0; + iop_base = asc_dvc->iop_base; + asc_dvc->init_state = ASC_INIT_STATE_BEG_GET_CFG; + if (asc_dvc->err_code != 0) { + return (UW_ERR); + } + if (asc_dvc->bus_type == ASC_IS_PCI) { + PCIVendorID = AscReadPCIConfigWord(asc_dvc, + AscPCIConfigVendorIDRegister); + + PCIDeviceID = AscReadPCIConfigWord(asc_dvc, + AscPCIConfigDeviceIDRegister); + + PCIRevisionID = DvcReadPCIConfigByte(asc_dvc, + AscPCIConfigRevisionIDRegister); + + if (PCIVendorID != ASC_PCI_VENDORID) { + warn_code |= ASC_WARN_SET_PCI_CONFIG_SPACE; + } + prevCmdRegBits = DvcReadPCIConfigByte(asc_dvc, + AscPCIConfigCommandRegister); + + if ((prevCmdRegBits & AscPCICmdRegBits_IOMemBusMaster) != + AscPCICmdRegBits_IOMemBusMaster) { + DvcWritePCIConfigByte(asc_dvc, + AscPCIConfigCommandRegister, + (prevCmdRegBits | + AscPCICmdRegBits_IOMemBusMaster)); + + if ((DvcReadPCIConfigByte(asc_dvc, + AscPCIConfigCommandRegister) + & AscPCICmdRegBits_IOMemBusMaster) + != AscPCICmdRegBits_IOMemBusMaster) { + warn_code |= ASC_WARN_SET_PCI_CONFIG_SPACE; + } + } + if ((PCIDeviceID == ASC_PCI_DEVICEID_1200A) || + (PCIDeviceID == ASC_PCI_DEVICEID_1200B)) { + DvcWritePCIConfigByte(asc_dvc, + AscPCIConfigLatencyTimer, 0x00); + if (DvcReadPCIConfigByte(asc_dvc, AscPCIConfigLatencyTimer) + != 0x00) { + warn_code |= ASC_WARN_SET_PCI_CONFIG_SPACE; + } + } else if (PCIDeviceID == ASC_PCI_DEVICEID_ULTRA) { + if (DvcReadPCIConfigByte(asc_dvc, + AscPCIConfigLatencyTimer) < 0x20) { + DvcWritePCIConfigByte(asc_dvc, + AscPCIConfigLatencyTimer, 0x20); + + if (DvcReadPCIConfigByte(asc_dvc, + AscPCIConfigLatencyTimer) < 0x20) { + warn_code |= ASC_WARN_SET_PCI_CONFIG_SPACE; + } + } + } + } + + if (AscFindSignature(iop_base)) { + warn_code |= AscInitAscDvcVar(asc_dvc); + warn_code |= AscInitFromEEP(asc_dvc); + asc_dvc->init_state |= ASC_INIT_STATE_END_GET_CFG; + if (asc_dvc->scsi_reset_wait > ASC_MAX_SCSI_RESET_WAIT) { + asc_dvc->scsi_reset_wait = ASC_MAX_SCSI_RESET_WAIT; + } + } else { + asc_dvc->err_code = ASC_IERR_BAD_SIGNATURE; + } + return(warn_code); +} + +ASC_INITFUNC( +STATIC ushort +AscInitSetConfig( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + ushort warn_code = 0; + + asc_dvc->init_state |= ASC_INIT_STATE_BEG_SET_CFG; + if (asc_dvc->err_code != 0) + return (UW_ERR); + if (AscFindSignature(asc_dvc->iop_base)) { + warn_code |= AscInitFromAscDvcVar(asc_dvc); + asc_dvc->init_state |= ASC_INIT_STATE_END_SET_CFG; + } else { + asc_dvc->err_code = ASC_IERR_BAD_SIGNATURE; + } + return (warn_code); +} + +ASC_INITFUNC( +STATIC ushort +AscInitFromAscDvcVar( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + PortAddr iop_base; + ushort cfg_msw; + ushort warn_code; + ushort pci_device_id; + + iop_base = asc_dvc->iop_base; + pci_device_id = asc_dvc->cfg->pci_device_id; + warn_code = 0; + cfg_msw = AscGetChipCfgMsw(iop_base); + if ((cfg_msw & ASC_CFG_MSW_CLR_MASK) != 0) { + cfg_msw &= (~(ASC_CFG_MSW_CLR_MASK)); + warn_code |= ASC_WARN_CFG_MSW_RECOVER; + AscSetChipCfgMsw(iop_base, cfg_msw); + } + if ((asc_dvc->cfg->cmd_qng_enabled & asc_dvc->cfg->disc_enable) != + asc_dvc->cfg->cmd_qng_enabled) { + asc_dvc->cfg->disc_enable = asc_dvc->cfg->cmd_qng_enabled; + warn_code |= ASC_WARN_CMD_QNG_CONFLICT; + } + if (AscGetChipStatus(iop_base) & CSW_AUTO_CONFIG) { + warn_code |= ASC_WARN_AUTO_CONFIG; + } + if ((asc_dvc->bus_type & (ASC_IS_ISA | ASC_IS_VL)) != 0) { + if (AscSetChipIRQ(iop_base, asc_dvc->irq_no, asc_dvc->bus_type) + != asc_dvc->irq_no) { + asc_dvc->err_code |= ASC_IERR_SET_IRQ_NO; + } + } + if (asc_dvc->bus_type & ASC_IS_PCI) { + cfg_msw &= 0xFFC0; + AscSetChipCfgMsw(iop_base, cfg_msw); + if ((asc_dvc->bus_type & ASC_IS_PCI_ULTRA) == ASC_IS_PCI_ULTRA) { + } else { + if ((pci_device_id == ASC_PCI_DEVICE_ID_REV_A) || + (pci_device_id == ASC_PCI_DEVICE_ID_REV_B)) { + asc_dvc->bug_fix_cntl |= ASC_BUG_FIX_IF_NOT_DWB; + asc_dvc->bug_fix_cntl |= ASC_BUG_FIX_ASYN_USE_SYN; + } + } + } else if (asc_dvc->bus_type == ASC_IS_ISAPNP) { + if (AscGetChipVersion(iop_base, asc_dvc->bus_type) + == ASC_CHIP_VER_ASYN_BUG) { + asc_dvc->bug_fix_cntl |= ASC_BUG_FIX_ASYN_USE_SYN; + } + } + if (AscSetChipScsiID(iop_base, asc_dvc->cfg->chip_scsi_id) != + asc_dvc->cfg->chip_scsi_id) { + asc_dvc->err_code |= ASC_IERR_SET_SCSI_ID; + } + if (asc_dvc->bus_type & ASC_IS_ISA) { + AscSetIsaDmaChannel(iop_base, asc_dvc->cfg->isa_dma_channel); + AscSetIsaDmaSpeed(iop_base, asc_dvc->cfg->isa_dma_speed); + } + return (warn_code); +} + +ASC_INITFUNC( +STATIC ushort +AscInitAsc1000Driver( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + ushort warn_code; + PortAddr iop_base; + extern ushort _asc_mcode_size; + extern ulong _asc_mcode_chksum; + extern uchar _asc_mcode_buf[]; + + iop_base = asc_dvc->iop_base; + warn_code = 0; + if ((asc_dvc->dvc_cntl & ASC_CNTL_RESET_SCSI) && + !(asc_dvc->init_state & ASC_INIT_RESET_SCSI_DONE)) { + AscResetChipAndScsiBus(asc_dvc); + DvcSleepMilliSecond((ulong) ((ushort) asc_dvc->scsi_reset_wait * 1000)); + } + asc_dvc->init_state |= ASC_INIT_STATE_BEG_LOAD_MC; + if (asc_dvc->err_code != 0) + return (UW_ERR); + if (!AscFindSignature(asc_dvc->iop_base)) { + asc_dvc->err_code = ASC_IERR_BAD_SIGNATURE; + return (warn_code); + } + AscDisableInterrupt(iop_base); + warn_code |= AscInitLram(asc_dvc); + if (asc_dvc->err_code != 0) + return (UW_ERR); + if (AscLoadMicroCode(iop_base, 0, (ushort *) _asc_mcode_buf, + _asc_mcode_size) != _asc_mcode_chksum) { + asc_dvc->err_code |= ASC_IERR_MCODE_CHKSUM; + return (warn_code); + } + warn_code |= AscInitMicroCodeVar(asc_dvc); + asc_dvc->init_state |= ASC_INIT_STATE_END_LOAD_MC; + AscEnableInterrupt(iop_base); + return (warn_code); +} + +ASC_INITFUNC( +STATIC ushort +AscInitAscDvcVar( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + int i; + PortAddr iop_base; + ushort warn_code; + uchar chip_version; + + iop_base = asc_dvc->iop_base; + warn_code = 0; + asc_dvc->err_code = 0; + if ((asc_dvc->bus_type & + (ASC_IS_ISA | ASC_IS_PCI | ASC_IS_EISA | ASC_IS_VL)) == 0) { + asc_dvc->err_code |= ASC_IERR_NO_BUS_TYPE; + } + AscSetChipControl(iop_base, CC_HALT); + AscSetChipStatus(iop_base, 0); + asc_dvc->bug_fix_cntl = 0; + asc_dvc->pci_fix_asyn_xfer = 0; + asc_dvc->pci_fix_asyn_xfer_always = 0; + asc_dvc->init_state = 0; + asc_dvc->sdtr_done = 0; + asc_dvc->cur_total_qng = 0; + asc_dvc->is_in_int = 0; + asc_dvc->in_critical_cnt = 0; + asc_dvc->last_q_shortage = 0; + asc_dvc->use_tagged_qng = 0; + asc_dvc->no_scam = 0; + asc_dvc->unit_not_ready = 0; + asc_dvc->queue_full_or_busy = 0; + asc_dvc->redo_scam = 0 ; + asc_dvc->res2 = 0 ; + asc_dvc->host_init_sdtr_index = 0 ; + asc_dvc->res7 = 0 ; + asc_dvc->res8 = 0 ; + asc_dvc->cfg->can_tagged_qng = 0 ; + asc_dvc->cfg->cmd_qng_enabled = 0; + asc_dvc->dvc_cntl = ASC_DEF_DVC_CNTL; + asc_dvc->init_sdtr = 0; + asc_dvc->max_total_qng = ASC_DEF_MAX_TOTAL_QNG; + asc_dvc->scsi_reset_wait = 3; + asc_dvc->start_motor = ASC_SCSI_WIDTH_BIT_SET; + asc_dvc->max_dma_count = AscGetMaxDmaCount(asc_dvc->bus_type); + asc_dvc->cfg->sdtr_enable = ASC_SCSI_WIDTH_BIT_SET; + asc_dvc->cfg->disc_enable = ASC_SCSI_WIDTH_BIT_SET; + asc_dvc->cfg->chip_scsi_id = ASC_DEF_CHIP_SCSI_ID; + asc_dvc->cfg->lib_serial_no = ASC_LIB_SERIAL_NUMBER; + asc_dvc->cfg->lib_version = (ASC_LIB_VERSION_MAJOR << 8) | + ASC_LIB_VERSION_MINOR; + chip_version = AscGetChipVersion(iop_base, asc_dvc->bus_type); + asc_dvc->cfg->chip_version = chip_version; + asc_dvc->sdtr_period_tbl[0] = SYN_XFER_NS_0; + asc_dvc->sdtr_period_tbl[1] = SYN_XFER_NS_1; + asc_dvc->sdtr_period_tbl[2] = SYN_XFER_NS_2; + asc_dvc->sdtr_period_tbl[3] = SYN_XFER_NS_3; + asc_dvc->sdtr_period_tbl[4] = SYN_XFER_NS_4; + asc_dvc->sdtr_period_tbl[5] = SYN_XFER_NS_5; + asc_dvc->sdtr_period_tbl[6] = SYN_XFER_NS_6; + asc_dvc->sdtr_period_tbl[7] = SYN_XFER_NS_7; + asc_dvc->max_sdtr_index = 7; + if ((asc_dvc->bus_type & ASC_IS_PCI) && + (chip_version >= ASC_CHIP_VER_PCI_ULTRA_3150)) { + asc_dvc->bus_type = ASC_IS_PCI_ULTRA; + asc_dvc->sdtr_period_tbl[0] = SYN_ULTRA_XFER_NS_0; + asc_dvc->sdtr_period_tbl[1] = SYN_ULTRA_XFER_NS_1; + asc_dvc->sdtr_period_tbl[2] = SYN_ULTRA_XFER_NS_2; + asc_dvc->sdtr_period_tbl[3] = SYN_ULTRA_XFER_NS_3; + asc_dvc->sdtr_period_tbl[4] = SYN_ULTRA_XFER_NS_4; + asc_dvc->sdtr_period_tbl[5] = SYN_ULTRA_XFER_NS_5; + asc_dvc->sdtr_period_tbl[6] = SYN_ULTRA_XFER_NS_6; + asc_dvc->sdtr_period_tbl[7] = SYN_ULTRA_XFER_NS_7; + asc_dvc->sdtr_period_tbl[8] = SYN_ULTRA_XFER_NS_8; + asc_dvc->sdtr_period_tbl[9] = SYN_ULTRA_XFER_NS_9; + asc_dvc->sdtr_period_tbl[10] = SYN_ULTRA_XFER_NS_10; + asc_dvc->sdtr_period_tbl[11] = SYN_ULTRA_XFER_NS_11; + asc_dvc->sdtr_period_tbl[12] = SYN_ULTRA_XFER_NS_12; + asc_dvc->sdtr_period_tbl[13] = SYN_ULTRA_XFER_NS_13; + asc_dvc->sdtr_period_tbl[14] = SYN_ULTRA_XFER_NS_14; + asc_dvc->sdtr_period_tbl[15] = SYN_ULTRA_XFER_NS_15; + asc_dvc->max_sdtr_index = 15; + if (chip_version == ASC_CHIP_VER_PCI_ULTRA_3150) + { + AscSetExtraControl(iop_base, + (SEC_ACTIVE_NEGATE | SEC_SLEW_RATE)); + } else if (chip_version >= ASC_CHIP_VER_PCI_ULTRA_3050) { + AscSetExtraControl(iop_base, + (SEC_ACTIVE_NEGATE | SEC_ENABLE_FILTER)); + } + } + if (asc_dvc->bus_type == ASC_IS_PCI) { + AscSetExtraControl(iop_base, (SEC_ACTIVE_NEGATE | SEC_SLEW_RATE)); + } + + asc_dvc->cfg->isa_dma_speed = ASC_DEF_ISA_DMA_SPEED; + if (AscGetChipBusType(iop_base) == ASC_IS_ISAPNP) { + AscSetChipIFC(iop_base, IFC_INIT_DEFAULT); + asc_dvc->bus_type = ASC_IS_ISAPNP; + } + if ((asc_dvc->bus_type & ASC_IS_ISA) != 0) { + asc_dvc->cfg->isa_dma_channel = (uchar) AscGetIsaDmaChannel(iop_base); + } + for (i = 0; i <= ASC_MAX_TID; i++) { + asc_dvc->cur_dvc_qng[i] = 0; + asc_dvc->max_dvc_qng[i] = ASC_MAX_SCSI1_QNG; + asc_dvc->scsiq_busy_head[i] = (ASC_SCSI_Q *) 0L; + asc_dvc->scsiq_busy_tail[i] = (ASC_SCSI_Q *) 0L; + asc_dvc->cfg->max_tag_qng[i] = ASC_MAX_INRAM_TAG_QNG; + } + return (warn_code); +} + +ASC_INITFUNC( +STATIC ushort +AscInitFromEEP( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + ASCEEP_CONFIG eep_config_buf; + ASCEEP_CONFIG *eep_config; + PortAddr iop_base; + ushort chksum; + ushort warn_code; + ushort cfg_msw, cfg_lsw; + int i; + int write_eep = 0; + + iop_base = asc_dvc->iop_base; + warn_code = 0; + AscWriteLramWord(iop_base, ASCV_HALTCODE_W, 0x00FE); + AscStopQueueExe(iop_base); + if ((AscStopChip(iop_base) == FALSE) || + (AscGetChipScsiCtrl(iop_base) != 0)) { + asc_dvc->init_state |= ASC_INIT_RESET_SCSI_DONE; + AscResetChipAndScsiBus(asc_dvc); + DvcSleepMilliSecond((ulong) ((ushort) asc_dvc->scsi_reset_wait * 1000)); + } + if (AscIsChipHalted(iop_base) == FALSE) { + asc_dvc->err_code |= ASC_IERR_START_STOP_CHIP; + return (warn_code); + } + AscSetPCAddr(iop_base, ASC_MCODE_START_ADDR); + if (AscGetPCAddr(iop_base) != ASC_MCODE_START_ADDR) { + asc_dvc->err_code |= ASC_IERR_SET_PC_ADDR; + return (warn_code); + } + eep_config = (ASCEEP_CONFIG *) & eep_config_buf; + cfg_msw = AscGetChipCfgMsw(iop_base); + cfg_lsw = AscGetChipCfgLsw(iop_base); + if ((cfg_msw & ASC_CFG_MSW_CLR_MASK) != 0) { + cfg_msw &= (~(ASC_CFG_MSW_CLR_MASK)); + warn_code |= ASC_WARN_CFG_MSW_RECOVER; + AscSetChipCfgMsw(iop_base, cfg_msw); + } + chksum = AscGetEEPConfig(iop_base, eep_config, asc_dvc->bus_type); + if (chksum == 0) { + chksum = 0xaa55; + } + if (AscGetChipStatus(iop_base) & CSW_AUTO_CONFIG) { + warn_code |= ASC_WARN_AUTO_CONFIG; + if (asc_dvc->cfg->chip_version == 3) { + if (eep_config->cfg_lsw != cfg_lsw) { + warn_code |= ASC_WARN_EEPROM_RECOVER; + eep_config->cfg_lsw = AscGetChipCfgLsw(iop_base); + } + if (eep_config->cfg_msw != cfg_msw) { + warn_code |= ASC_WARN_EEPROM_RECOVER; + eep_config->cfg_msw = AscGetChipCfgMsw(iop_base); + } + } + } + eep_config->cfg_msw &= ~ASC_CFG_MSW_CLR_MASK; + eep_config->cfg_lsw |= ASC_CFG0_HOST_INT_ON; + if (chksum != eep_config->chksum) { + if (AscGetChipVersion(iop_base, asc_dvc->bus_type) == + ASC_CHIP_VER_PCI_ULTRA_3050 ) + { + eep_config->init_sdtr = 0xFF; + eep_config->disc_enable = 0xFF; + eep_config->start_motor = 0xFF; + eep_config->use_cmd_qng = 0; + eep_config->max_total_qng = 0xF0; + eep_config->max_tag_qng = 0x20; + eep_config->cntl = 0xBFFF; + eep_config->chip_scsi_id = 7; + eep_config->no_scam = 0; + eep_config->adapter_info[0] = 0; + eep_config->adapter_info[1] = 0; + eep_config->adapter_info[2] = 0; + eep_config->adapter_info[3] = 0; + eep_config->adapter_info[4] = 0; + /* Indicate EEPROM-less board. */ + eep_config->adapter_info[5] = 0xBB; + } else { + write_eep = 1 ; + warn_code |= ASC_WARN_EEPROM_CHKSUM ; + } + } + asc_dvc->cfg->sdtr_enable = eep_config->init_sdtr ; + asc_dvc->cfg->disc_enable = eep_config->disc_enable; + asc_dvc->cfg->cmd_qng_enabled = eep_config->use_cmd_qng; + asc_dvc->cfg->isa_dma_speed = eep_config->isa_dma_speed; + asc_dvc->start_motor = eep_config->start_motor; + asc_dvc->dvc_cntl = eep_config->cntl; + asc_dvc->no_scam = eep_config->no_scam; + asc_dvc->cfg->adapter_info[0] = eep_config->adapter_info[0]; + asc_dvc->cfg->adapter_info[1] = eep_config->adapter_info[1]; + asc_dvc->cfg->adapter_info[2] = eep_config->adapter_info[2]; + asc_dvc->cfg->adapter_info[3] = eep_config->adapter_info[3]; + asc_dvc->cfg->adapter_info[4] = eep_config->adapter_info[4]; + asc_dvc->cfg->adapter_info[5] = eep_config->adapter_info[5]; + if (!AscTestExternalLram(asc_dvc)) { + if (((asc_dvc->bus_type & ASC_IS_PCI_ULTRA) == ASC_IS_PCI_ULTRA)) { + eep_config->max_total_qng = ASC_MAX_PCI_ULTRA_INRAM_TOTAL_QNG; + eep_config->max_tag_qng = ASC_MAX_PCI_ULTRA_INRAM_TAG_QNG; + } else { + eep_config->cfg_msw |= 0x0800; + cfg_msw |= 0x0800; + AscSetChipCfgMsw(iop_base, cfg_msw); + eep_config->max_total_qng = ASC_MAX_PCI_INRAM_TOTAL_QNG; + eep_config->max_tag_qng = ASC_MAX_INRAM_TAG_QNG; + } + } else { + } + if (eep_config->max_total_qng < ASC_MIN_TOTAL_QNG) { + eep_config->max_total_qng = ASC_MIN_TOTAL_QNG; + } + if (eep_config->max_total_qng > ASC_MAX_TOTAL_QNG) { + eep_config->max_total_qng = ASC_MAX_TOTAL_QNG; + } + if (eep_config->max_tag_qng > eep_config->max_total_qng) { + eep_config->max_tag_qng = eep_config->max_total_qng; + } + if (eep_config->max_tag_qng < ASC_MIN_TAG_Q_PER_DVC) { + eep_config->max_tag_qng = ASC_MIN_TAG_Q_PER_DVC; + } + asc_dvc->max_total_qng = eep_config->max_total_qng; + if ((eep_config->use_cmd_qng & eep_config->disc_enable) != + eep_config->use_cmd_qng) { + eep_config->disc_enable = eep_config->use_cmd_qng; + warn_code |= ASC_WARN_CMD_QNG_CONFLICT; + } + if (asc_dvc->bus_type & (ASC_IS_ISA | ASC_IS_VL | ASC_IS_EISA)) { + asc_dvc->irq_no = AscGetChipIRQ(iop_base, asc_dvc->bus_type); + } + eep_config->chip_scsi_id &= ASC_MAX_TID; + asc_dvc->cfg->chip_scsi_id = eep_config->chip_scsi_id; + if (((asc_dvc->bus_type & ASC_IS_PCI_ULTRA) == ASC_IS_PCI_ULTRA) && + !(asc_dvc->dvc_cntl & ASC_CNTL_SDTR_ENABLE_ULTRA)) { + asc_dvc->host_init_sdtr_index = ASC_SDTR_ULTRA_PCI_10MB_INDEX; + } + + for (i = 0; i <= ASC_MAX_TID; i++) { + asc_dvc->dos_int13_table[i] = eep_config->dos_int13_table[i]; + asc_dvc->cfg->max_tag_qng[i] = eep_config->max_tag_qng; + asc_dvc->cfg->sdtr_period_offset[i] = + (uchar) (ASC_DEF_SDTR_OFFSET | + (asc_dvc->host_init_sdtr_index << 4)); + } + eep_config->cfg_msw = AscGetChipCfgMsw(iop_base); + if (write_eep) { + (void) AscSetEEPConfig(iop_base, eep_config, asc_dvc->bus_type); + } + return (warn_code); +} + +ASC_INITFUNC( +STATIC ushort +AscInitMicroCodeVar( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + int i; + ushort warn_code; + PortAddr iop_base; + ulong phy_addr; + + iop_base = asc_dvc->iop_base; + warn_code = 0; + for (i = 0; i <= ASC_MAX_TID; i++) { + AscPutMCodeInitSDTRAtID(iop_base, i, + asc_dvc->cfg->sdtr_period_offset[i] +); + } + AscInitQLinkVar(asc_dvc); + AscWriteLramByte(iop_base, ASCV_DISC_ENABLE_B, + asc_dvc->cfg->disc_enable); + AscWriteLramByte(iop_base, ASCV_HOSTSCSI_ID_B, + ASC_TID_TO_TARGET_ID(asc_dvc->cfg->chip_scsi_id)); + if ((phy_addr = AscGetOnePhyAddr(asc_dvc, + (uchar *) asc_dvc->cfg->overrun_buf, + ASC_OVERRUN_BSIZE)) == 0L) { + asc_dvc->err_code |= ASC_IERR_GET_PHY_ADDR; + } else { + phy_addr = (phy_addr & 0xFFFFFFF8UL) + 8; + AscWriteLramDWord(iop_base, ASCV_OVERRUN_PADDR_D, phy_addr); + AscWriteLramDWord(iop_base, ASCV_OVERRUN_BSIZE_D, + ASC_OVERRUN_BSIZE - 8); + } + asc_dvc->cfg->mcode_date = AscReadLramWord(iop_base, + (ushort) ASCV_MC_DATE_W); + asc_dvc->cfg->mcode_version = AscReadLramWord(iop_base, + (ushort) ASCV_MC_VER_W); + AscSetPCAddr(iop_base, ASC_MCODE_START_ADDR); + if (AscGetPCAddr(iop_base) != ASC_MCODE_START_ADDR) { + asc_dvc->err_code |= ASC_IERR_SET_PC_ADDR; + return (warn_code); + } + if (AscStartChip(iop_base) != 1) { + asc_dvc->err_code |= ASC_IERR_START_STOP_CHIP; + return (warn_code); + } + return (warn_code); +} + +ASC_INITFUNC( +STATIC int +AscTestExternalLram( + ASC_DVC_VAR asc_ptr_type * asc_dvc +) +) +{ + PortAddr iop_base; + ushort q_addr; + ushort saved_word; + int sta; + + iop_base = asc_dvc->iop_base; + sta = 0; + q_addr = ASC_QNO_TO_QADDR(241); + saved_word = AscReadLramWord(iop_base, q_addr); + AscSetChipLramAddr(iop_base, q_addr); + AscSetChipLramData(iop_base, 0x55AA); + DvcSleepMilliSecond(10); + AscSetChipLramAddr(iop_base, q_addr); + if (AscGetChipLramData(iop_base) == 0x55AA) { + sta = 1; + AscWriteLramWord(iop_base, q_addr, saved_word); + } + return (sta); +} + +ASC_INITFUNC( +STATIC int +AscWriteEEPCmdReg( + PortAddr iop_base, + uchar cmd_reg +) +) +{ + uchar read_back; + int retry; + + retry = 0; + while (TRUE) { + AscSetChipEEPCmd(iop_base, cmd_reg); + DvcSleepMilliSecond(1); + read_back = AscGetChipEEPCmd(iop_base); + if (read_back == cmd_reg) { + return (1); + } + if (retry++ > ASC_EEP_MAX_RETRY) { + return (0); + } + } +} + +ASC_INITFUNC( +STATIC int +AscWriteEEPDataReg( + PortAddr iop_base, + ushort data_reg +) +) +{ + ushort read_back; + int retry; + + retry = 0; + while (TRUE) { + AscSetChipEEPData(iop_base, data_reg); + DvcSleepMilliSecond(1); + read_back = AscGetChipEEPData(iop_base); + if (read_back == data_reg) { + return (1); + } + if (retry++ > ASC_EEP_MAX_RETRY) { + return (0); + } + } +} + +ASC_INITFUNC( +STATIC void +AscWaitEEPRead( + void +) +) +{ + DvcSleepMilliSecond(1); + return; +} + +ASC_INITFUNC( +STATIC void +AscWaitEEPWrite( + void +) +) +{ + DvcSleepMilliSecond(20); + return; +} + +ASC_INITFUNC( +STATIC ushort +AscReadEEPWord( + PortAddr iop_base, + uchar addr +) +) +{ + ushort read_wval; + uchar cmd_reg; + + AscWriteEEPCmdReg(iop_base, ASC_EEP_CMD_WRITE_DISABLE); + AscWaitEEPRead(); + cmd_reg = addr | ASC_EEP_CMD_READ; + AscWriteEEPCmdReg(iop_base, cmd_reg); + AscWaitEEPRead(); + read_wval = AscGetChipEEPData(iop_base); + AscWaitEEPRead(); + return (read_wval); +} + +ASC_INITFUNC( +STATIC ushort +AscWriteEEPWord( + PortAddr iop_base, + uchar addr, + ushort word_val +) +) +{ + ushort read_wval; + + read_wval = AscReadEEPWord(iop_base, addr); + if (read_wval != word_val) { + AscWriteEEPCmdReg(iop_base, ASC_EEP_CMD_WRITE_ABLE); + AscWaitEEPRead(); + AscWriteEEPDataReg(iop_base, word_val); + AscWaitEEPRead(); + AscWriteEEPCmdReg(iop_base, + (uchar) ((uchar) ASC_EEP_CMD_WRITE | addr)); + AscWaitEEPWrite(); + AscWriteEEPCmdReg(iop_base, ASC_EEP_CMD_WRITE_DISABLE); + AscWaitEEPRead(); + return (AscReadEEPWord(iop_base, addr)); + } + return (read_wval); +} + +ASC_INITFUNC( +STATIC ushort +AscGetEEPConfig( + PortAddr iop_base, + ASCEEP_CONFIG * cfg_buf, ushort bus_type +) +) +{ + ushort wval; + ushort sum; + ushort *wbuf; + int cfg_beg; + int cfg_end; + int s_addr; + int isa_pnp_wsize; + + wbuf = (ushort *) cfg_buf; + sum = 0; + isa_pnp_wsize = 0; + for (s_addr = 0; s_addr < (2 + isa_pnp_wsize); s_addr++, wbuf++) { + wval = AscReadEEPWord(iop_base, (uchar) s_addr); + sum += wval; + *wbuf = wval; + } + if (bus_type & ASC_IS_VL) { + cfg_beg = ASC_EEP_DVC_CFG_BEG_VL; + cfg_end = ASC_EEP_MAX_DVC_ADDR_VL; + } else { + cfg_beg = ASC_EEP_DVC_CFG_BEG; + cfg_end = ASC_EEP_MAX_DVC_ADDR; + } + for (s_addr = cfg_beg; s_addr <= (cfg_end - 1); + s_addr++, wbuf++) { + wval = AscReadEEPWord(iop_base, (uchar) s_addr); + sum += wval; + *wbuf = wval; + } + *wbuf = AscReadEEPWord(iop_base, (uchar) s_addr); + return (sum); +} + +ASC_INITFUNC( +STATIC int +AscSetEEPConfigOnce( + PortAddr iop_base, + ASCEEP_CONFIG * cfg_buf, ushort bus_type +) +) +{ + int n_error; + ushort *wbuf; + ushort sum; + int s_addr; + int cfg_beg; + int cfg_end; + + wbuf = (ushort *) cfg_buf; + n_error = 0; + sum = 0; + for (s_addr = 0; s_addr < 2; s_addr++, wbuf++) { + sum += *wbuf; + if (*wbuf != AscWriteEEPWord(iop_base, (uchar) s_addr, *wbuf)) { + n_error++; + } + } + if (bus_type & ASC_IS_VL) { + cfg_beg = ASC_EEP_DVC_CFG_BEG_VL; + cfg_end = ASC_EEP_MAX_DVC_ADDR_VL; + } else { + cfg_beg = ASC_EEP_DVC_CFG_BEG; + cfg_end = ASC_EEP_MAX_DVC_ADDR; + } + for (s_addr = cfg_beg; s_addr <= (cfg_end - 1); + s_addr++, wbuf++) { + sum += *wbuf; + if (*wbuf != AscWriteEEPWord(iop_base, (uchar) s_addr, *wbuf)) { + n_error++; + } + } + *wbuf = sum; + if (sum != AscWriteEEPWord(iop_base, (uchar) s_addr, sum)) { + n_error++; + } + wbuf = (ushort *) cfg_buf; + for (s_addr = 0; s_addr < 2; s_addr++, wbuf++) { + if (*wbuf != AscReadEEPWord(iop_base, (uchar) s_addr)) { + n_error++; + } + } + for (s_addr = cfg_beg; s_addr <= cfg_end; + s_addr++, wbuf++) { + if (*wbuf != AscReadEEPWord(iop_base, (uchar) s_addr)) { + n_error++; + } + } + return (n_error); +} + +ASC_INITFUNC( +STATIC int +AscSetEEPConfig( + PortAddr iop_base, + ASCEEP_CONFIG * cfg_buf, ushort bus_type +) +) +{ + int retry; + int n_error; + + retry = 0; + while (TRUE) { + if ((n_error = AscSetEEPConfigOnce(iop_base, cfg_buf, + bus_type)) == 0) { + break; + } + if (++retry > ASC_EEP_MAX_RETRY) { + break; + } + } + return (n_error); +} + +STATIC void +AscAsyncFix( + ASC_DVC_VAR asc_ptr_type *asc_dvc, + uchar tid_no, + ASC_SCSI_INQUIRY *inq) +{ + uchar dvc_type; + ASC_SCSI_BIT_ID_TYPE tid_bits; + + dvc_type = inq->byte0.peri_dvc_type; + tid_bits = ASC_TIX_TO_TARGET_ID(tid_no); + + if (asc_dvc->bug_fix_cntl & ASC_BUG_FIX_ASYN_USE_SYN) { + if (!(asc_dvc->init_sdtr & tid_bits)) { + if ((dvc_type == SCSI_TYPE_CDROM) && + (AscCompareString((uchar *) inq->vendor_id, + (uchar *) "HP ", 3) == 0)) { + asc_dvc->pci_fix_asyn_xfer_always |= tid_bits; + } + asc_dvc->pci_fix_asyn_xfer |= tid_bits; + if ((dvc_type == SCSI_TYPE_PROC) || + (dvc_type == SCSI_TYPE_SCANNER)) { + asc_dvc->pci_fix_asyn_xfer &= ~tid_bits; + } + if ((dvc_type == SCSI_TYPE_SASD) && + (AscCompareString((uchar *) inq->vendor_id, + (uchar *) "TANDBERG", 8) == 0) && + (AscCompareString((uchar *) inq->product_id, + (uchar *) " TDC 36", 7) == 0)) { + asc_dvc->pci_fix_asyn_xfer &= ~tid_bits; + } + if ((dvc_type == SCSI_TYPE_SASD) && + (AscCompareString((uchar *) inq->vendor_id, + (uchar *) "WANGTEK ", 8) == 0)) { + asc_dvc->pci_fix_asyn_xfer &= ~tid_bits; + } + + if ((dvc_type == SCSI_TYPE_CDROM) && + (AscCompareString((uchar *) inq->vendor_id, + (uchar *) "NEC ", 8) == 0) && + (AscCompareString((uchar *) inq->product_id, + (uchar *) "CD-ROM DRIVE ", 16) == 0)) { + asc_dvc->pci_fix_asyn_xfer &= ~tid_bits; + } + + if ((dvc_type == SCSI_TYPE_CDROM) && + (AscCompareString((uchar *) inq->vendor_id, + (uchar *) "YAMAHA", 6) == 0) && + (AscCompareString((uchar *) inq->product_id, + (uchar *) "CDR400", 6) == 0)) { + asc_dvc->pci_fix_asyn_xfer &= ~tid_bits; + } + if (asc_dvc->pci_fix_asyn_xfer & tid_bits) { + AscSetRunChipSynRegAtID(asc_dvc->iop_base, tid_no, + ASYN_SDTR_DATA_FIX_PCI_REV_AB); + } + } + } + return; +} + +STATIC int +AscTagQueuingSafe(ASC_SCSI_INQUIRY *inq) +{ + if ((inq->add_len >= 32) && + (AscCompareString((uchar *) inq->vendor_id, + (uchar *) "QUANTUM XP34301", 15) == 0) && + (AscCompareString((uchar *) inq->product_rev_level, + (uchar *) "1071", 4) == 0)) + { + return 0; + } + return 1; +} + +STATIC void +AscInquiryHandling(ASC_DVC_VAR asc_ptr_type *asc_dvc, + uchar tid_no, ASC_SCSI_INQUIRY *inq) +{ + ASC_SCSI_BIT_ID_TYPE tid_bit = ASC_TIX_TO_TARGET_ID(tid_no); + ASC_SCSI_BIT_ID_TYPE orig_init_sdtr, orig_use_tagged_qng; + + orig_init_sdtr = asc_dvc->init_sdtr; + orig_use_tagged_qng = asc_dvc->use_tagged_qng; + + asc_dvc->init_sdtr &= ~tid_bit; + asc_dvc->cfg->can_tagged_qng &= ~tid_bit; + asc_dvc->use_tagged_qng &= ~tid_bit; + + if (inq->byte3.rsp_data_fmt >= 2 || inq->byte2.ansi_apr_ver >= 2) { + if ((asc_dvc->cfg->sdtr_enable & tid_bit) && inq->byte7.Sync) { + asc_dvc->init_sdtr |= tid_bit; + } + if ((asc_dvc->cfg->cmd_qng_enabled & tid_bit) && inq->byte7.CmdQue) { + if (AscTagQueuingSafe(inq)) { + asc_dvc->use_tagged_qng |= tid_bit; + asc_dvc->cfg->can_tagged_qng |= tid_bit; + } + } + } + if (orig_use_tagged_qng != asc_dvc->use_tagged_qng) { + AscWriteLramByte(asc_dvc->iop_base, ASCV_DISC_ENABLE_B, + asc_dvc->cfg->disc_enable); + AscWriteLramByte(asc_dvc->iop_base, ASCV_USE_TAGGED_QNG_B, + asc_dvc->use_tagged_qng); + AscWriteLramByte(asc_dvc->iop_base, ASCV_CAN_TAGGED_QNG_B, + asc_dvc->cfg->can_tagged_qng); + + asc_dvc->max_dvc_qng[tid_no] = + asc_dvc->cfg->max_tag_qng[tid_no]; + AscWriteLramByte(asc_dvc->iop_base, + (ushort) (ASCV_MAX_DVC_QNG_BEG + tid_no), + asc_dvc->max_dvc_qng[tid_no]); + } + if (orig_init_sdtr != asc_dvc->init_sdtr) { + AscAsyncFix(asc_dvc, tid_no, inq); + } + return; +} + +STATIC int +AscCompareString( + ruchar * str1, + ruchar * str2, + int len +) +{ + int i; + int diff; + + for (i = 0; i < len; i++) { + diff = (int) (str1[i] - str2[i]); + if (diff != 0) + return (diff); + } + return (0); +} + +STATIC uchar +AscReadLramByte( + PortAddr iop_base, + ushort addr +) +{ + uchar byte_data; + ushort word_data; + + if (isodd_word(addr)) { + AscSetChipLramAddr(iop_base, addr - 1); + word_data = AscGetChipLramData(iop_base); + byte_data = (uchar) ((word_data >> 8) & 0xFF); + } else { + AscSetChipLramAddr(iop_base, addr); + word_data = AscGetChipLramData(iop_base); + byte_data = (uchar) (word_data & 0xFF); + } + return (byte_data); +} + +STATIC ushort +AscReadLramWord( + PortAddr iop_base, + ushort addr +) +{ + ushort word_data; + + AscSetChipLramAddr(iop_base, addr); + word_data = AscGetChipLramData(iop_base); + return (word_data); +} + +STATIC ulong +AscReadLramDWord( + PortAddr iop_base, + ushort addr +) +{ + ushort val_low, val_high; + ulong dword_data; + + AscSetChipLramAddr(iop_base, addr); + val_low = AscGetChipLramData(iop_base); + val_high = AscGetChipLramData(iop_base); + dword_data = ((ulong) val_high << 16) | (ulong) val_low; + return (dword_data); +} + +STATIC void +AscWriteLramWord( + PortAddr iop_base, + ushort addr, + ushort word_val +) +{ + AscSetChipLramAddr(iop_base, addr); + AscSetChipLramData(iop_base, word_val); + return; +} + +STATIC void +AscWriteLramDWord( + PortAddr iop_base, + ushort addr, + ulong dword_val +) +{ + ushort word_val; + + AscSetChipLramAddr(iop_base, addr); + word_val = (ushort) dword_val; + AscSetChipLramData(iop_base, word_val); + word_val = (ushort) (dword_val >> 16); + AscSetChipLramData(iop_base, word_val); + return; +} + +STATIC void +AscWriteLramByte( + PortAddr iop_base, + ushort addr, + uchar byte_val +) +{ + ushort word_data; + + if (isodd_word(addr)) { + addr--; + word_data = AscReadLramWord(iop_base, addr); + word_data &= 0x00FF; + word_data |= (((ushort) byte_val << 8) & 0xFF00); + } else { + word_data = AscReadLramWord(iop_base, addr); + word_data &= 0xFF00; + word_data |= ((ushort) byte_val & 0x00FF); + } + AscWriteLramWord(iop_base, addr, word_data); + return; +} + +STATIC void +AscMemWordCopyToLram( + PortAddr iop_base, + ushort s_addr, + ushort * s_buffer, + int words +) +{ + AscSetChipLramAddr(iop_base, s_addr); + DvcOutPortWords(iop_base + IOP_RAM_DATA, s_buffer, words); + return; +} + +STATIC void +AscMemDWordCopyToLram( + PortAddr iop_base, + ushort s_addr, + ulong * s_buffer, + int dwords +) +{ + AscSetChipLramAddr(iop_base, s_addr); + DvcOutPortDWords(iop_base + IOP_RAM_DATA, s_buffer, dwords); + return; +} + +STATIC void +AscMemWordCopyFromLram( + PortAddr iop_base, + ushort s_addr, + ushort * d_buffer, + int words +) +{ + AscSetChipLramAddr(iop_base, s_addr); + DvcInPortWords(iop_base + IOP_RAM_DATA, d_buffer, words); + return; +} + +STATIC ulong +AscMemSumLramWord( + PortAddr iop_base, + ushort s_addr, + rint words +) +{ + ulong sum; + int i; + + sum = 0L; + for (i = 0; i < words; i++, s_addr += 2) { + sum += AscReadLramWord(iop_base, s_addr); + } + return (sum); +} + +STATIC void +AscMemWordSetLram( + PortAddr iop_base, + ushort s_addr, + ushort set_wval, + rint words +) +{ + rint i; + + AscSetChipLramAddr(iop_base, s_addr); + for (i = 0; i < words; i++) { + AscSetChipLramData(iop_base, set_wval); + } + return; +} + + +/* + * --- Adv Library Functions + */ + +/* a_qswap.h */ +STATIC unsigned char _adv_mcode_buf[] ASC_INITDATA = { + 0x9C, 0xF0, 0x80, 0x01, 0x00, 0xF0, 0x44, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x01, 0xD6, 0x11, 0x00, 0x00, 0x70, 0x01, + 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x10, 0x2D, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x34, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0xF7, 0x70, 0x01, 0x0C, 0x1C, 0x06, 0xF7, 0x02, 0x00, 0x00, 0xF2, 0xD6, 0x0A, + 0x04, 0xF7, 0x70, 0x01, 0x06, 0xF7, 0x02, 0x00, 0x3E, 0x57, 0x3C, 0x56, 0x0C, 0x1C, 0x00, 0xFC, + 0xA6, 0x00, 0x01, 0x58, 0xAA, 0x13, 0x20, 0xF0, 0xA6, 0x03, 0x06, 0xEC, 0xB9, 0x00, 0x0E, 0x47, + 0x03, 0xE6, 0x10, 0x00, 0xCE, 0x45, 0x02, 0x13, 0x3E, 0x57, 0x06, 0xEA, 0xB9, 0x00, 0x47, 0x4B, + 0x03, 0xF6, 0xE0, 0x00, 0x00, 0xF2, 0x68, 0x0A, 0x01, 0x48, 0x4E, 0x12, 0x03, 0xF6, 0xC0, 0x00, + 0x00, 0xF2, 0x68, 0x0A, 0x41, 0x58, 0x03, 0xF6, 0xD0, 0x00, 0x00, 0xF2, 0x68, 0x0A, 0x49, 0x44, + 0x59, 0xF0, 0x0A, 0x02, 0x03, 0xF6, 0xE0, 0x00, 0x00, 0xF2, 0x68, 0x0A, 0x44, 0x58, 0x00, 0xF2, + 0xE2, 0x0D, 0x02, 0xCC, 0x4A, 0xE4, 0x01, 0x00, 0x55, 0xF0, 0x08, 0x03, 0x45, 0xF4, 0x02, 0x00, + 0x83, 0x5A, 0x04, 0xCC, 0x01, 0x4A, 0x12, 0x12, 0x00, 0xF2, 0xE2, 0x0D, 0x00, 0xCD, 0x48, 0xE4, + 0x01, 0x00, 0xE9, 0x13, 0x00, 0xF2, 0xC6, 0x0F, 0xFA, 0x10, 0x0E, 0x47, 0x03, 0xE6, 0x10, 0x00, + 0xCE, 0x45, 0x02, 0x13, 0x3E, 0x57, 0xCE, 0x47, 0x97, 0x13, 0x04, 0xEC, 0xB4, 0x00, 0x00, 0xF2, + 0xE2, 0x0D, 0x00, 0xCD, 0x48, 0xE4, 0x00, 0x00, 0x12, 0x12, 0x3E, 0x57, 0x06, 0xCC, 0x45, 0xF4, + 0x02, 0x00, 0x83, 0x5A, 0x00, 0xCC, 0x00, 0xEA, 0xB4, 0x00, 0x92, 0x10, 0x00, 0xF0, 0x8C, 0x01, + 0x43, 0xF0, 0x5C, 0x02, 0x44, 0xF0, 0x60, 0x02, 0x45, 0xF0, 0x64, 0x02, 0x46, 0xF0, 0x68, 0x02, + 0x47, 0xF0, 0x6E, 0x02, 0x48, 0xF0, 0x9E, 0x02, 0xB9, 0x54, 0x62, 0x10, 0x00, 0x1C, 0x5A, 0x10, + 0x02, 0x1C, 0x56, 0x10, 0x1E, 0x1C, 0x52, 0x10, 0x00, 0xF2, 0x1E, 0x11, 0x50, 0x10, 0x06, 0xFC, + 0xA8, 0x00, 0x03, 0xF6, 0xBE, 0x00, 0x00, 0xF2, 0x4E, 0x0A, 0x8C, 0x10, 0x01, 0xF6, 0x01, 0x00, + 0x01, 0xFA, 0xA8, 0x00, 0x00, 0xF2, 0x2C, 0x0B, 0x06, 0x10, 0xB9, 0x54, 0x01, 0xFA, 0xA8, 0x00, + 0x03, 0xF6, 0xBE, 0x00, 0x00, 0xF2, 0x58, 0x0A, 0x01, 0xFC, 0xA8, 0x00, 0x20, 0x10, 0x58, 0x1C, + 0x00, 0xF2, 0x1C, 0x0B, 0x5A, 0x1C, 0x01, 0xF6, 0x01, 0x00, 0x38, 0x54, 0x00, 0xFA, 0xA6, 0x00, + 0x01, 0xFA, 0xA8, 0x00, 0x20, 0x1C, 0x00, 0xF0, 0x72, 0x01, 0x01, 0xF6, 0x01, 0x00, 0x38, 0x54, + 0x00, 0xFA, 0xA6, 0x00, 0x01, 0xFA, 0xA8, 0x00, 0x20, 0x1C, 0x00, 0xF0, 0x80, 0x01, 0x03, 0xF6, + 0xE0, 0x00, 0x00, 0xF2, 0x68, 0x0A, 0x01, 0x48, 0x0A, 0x13, 0x00, 0xF2, 0x38, 0x10, 0x00, 0xF2, + 0x54, 0x0F, 0x24, 0x10, 0x03, 0xF6, 0xC0, 0x00, 0x00, 0xF2, 0x68, 0x0A, 0x02, 0xF6, 0xD0, 0x00, + 0x02, 0x57, 0x03, 0x59, 0x01, 0xCC, 0x49, 0x44, 0x5B, 0xF0, 0x04, 0x03, 0x00, 0xF2, 0x9C, 0x0F, + 0x00, 0xF0, 0x80, 0x01, 0x00, 0xF2, 0x14, 0x10, 0x0C, 0x1C, 0x02, 0x4B, 0xBF, 0x57, 0x9E, 0x43, + 0x77, 0x57, 0x07, 0x4B, 0x20, 0xF0, 0xA6, 0x03, 0x40, 0x1C, 0x1E, 0xF0, 0x30, 0x03, 0x26, 0xF0, + 0x2C, 0x03, 0xA0, 0xF0, 0x1A, 0x03, 0x11, 0xF0, 0xA6, 0x03, 0x12, 0x10, 0x9F, 0xF0, 0x3E, 0x03, + 0x46, 0x1C, 0x82, 0xE7, 0x05, 0x00, 0x9E, 0xE7, 0x11, 0x00, 0x00, 0xF0, 0x06, 0x0A, 0x0C, 0x1C, + 0x48, 0x1C, 0x46, 0x1C, 0x38, 0x54, 0x00, 0xEC, 0xBA, 0x00, 0x08, 0x44, 0x00, 0xEA, 0xBA, 0x00, + 0x03, 0xF6, 0xC0, 0x00, 0x00, 0xF2, 0x68, 0x0A, 0x08, 0x44, 0x00, 0x4C, 0x82, 0xE7, 0x02, 0x00, + 0x00, 0xF2, 0x12, 0x11, 0x00, 0xF2, 0x12, 0x11, 0x85, 0xF0, 0x70, 0x03, 0x00, 0xF2, 0x60, 0x0B, + 0x06, 0xF0, 0x80, 0x03, 0x09, 0xF0, 0x24, 0x09, 0x1E, 0xF0, 0xFC, 0x09, 0x00, 0xF0, 0x02, 0x0A, + 0x00, 0xFC, 0xBE, 0x00, 0x98, 0x57, 0x55, 0xF0, 0xAC, 0x04, 0x01, 0xE6, 0x0C, 0x00, 0x00, 0xF2, + 0x4E, 0x0D, 0x00, 0xF2, 0x12, 0x11, 0x00, 0xF2, 0xBC, 0x11, 0x00, 0xF2, 0xC8, 0x11, 0x01, 0xF0, + 0x7C, 0x02, 0x00, 0xF0, 0x8A, 0x02, 0x46, 0x1C, 0x0C, 0x1C, 0x67, 0x1B, 0xBF, 0x57, 0x77, 0x57, + 0x02, 0x4B, 0x48, 0x1C, 0x32, 0x1C, 0x00, 0xF2, 0x92, 0x0D, 0x30, 0x1C, 0x96, 0xF0, 0xBC, 0x03, + 0xB1, 0xF0, 0xC0, 0x03, 0x1E, 0xF0, 0xFC, 0x09, 0x85, 0xF0, 0x02, 0x0A, 0x00, 0xFC, 0xBE, 0x00, + 0x98, 0x57, 0x14, 0x12, 0x01, 0xE6, 0x0C, 0x00, 0x00, 0xF2, 0x4E, 0x0D, 0x00, 0xF2, 0x12, 0x11, + 0x01, 0xF0, 0x7C, 0x02, 0x00, 0xF0, 0x8A, 0x02, 0x03, 0xF6, 0xE0, 0x00, 0x00, 0xF2, 0x68, 0x0A, + 0x01, 0x48, 0x55, 0xF0, 0x98, 0x04, 0x03, 0x82, 0x03, 0xFC, 0xA0, 0x00, 0x9B, 0x57, 0x40, 0x12, + 0x69, 0x18, 0x00, 0xF2, 0x12, 0x11, 0x85, 0xF0, 0x42, 0x04, 0x69, 0x08, 0x00, 0xF2, 0x12, 0x11, + 0x85, 0xF0, 0x02, 0x0A, 0x68, 0x08, 0x4C, 0x44, 0x28, 0x12, 0x44, 0x48, 0x03, 0xF6, 0xE0, 0x00, + 0x00, 0xF2, 0x68, 0x0A, 0x45, 0x58, 0x00, 0xF2, 0xF6, 0x0D, 0x00, 0xCC, 0x01, 0x48, 0x55, 0xF0, + 0x98, 0x04, 0x4C, 0x44, 0xEF, 0x13, 0x00, 0xF2, 0xC6, 0x0F, 0x00, 0xF2, 0x14, 0x10, 0x08, 0x10, + 0x68, 0x18, 0x45, 0x5A, 0x00, 0xF2, 0xF6, 0x0D, 0x04, 0x80, 0x18, 0xE4, 0x10, 0x00, 0x28, 0x12, + 0x01, 0xE6, 0x06, 0x00, 0x04, 0x80, 0x18, 0xE4, 0x01, 0x00, 0x04, 0x12, 0x01, 0xE6, 0x0D, 0x00, + 0x00, 0xF2, 0x4E, 0x0D, 0x00, 0xF2, 0x12, 0x11, 0x04, 0xE6, 0x02, 0x00, 0x9E, 0xE7, 0x15, 0x00, + 0x01, 0xF0, 0x1C, 0x0A, 0x00, 0xF0, 0x02, 0x0A, 0x69, 0x08, 0x05, 0x80, 0x48, 0xE4, 0x00, 0x00, + 0x0C, 0x12, 0x00, 0xE6, 0x11, 0x00, 0x00, 0xEA, 0xB8, 0x00, 0x00, 0xF2, 0xB6, 0x10, 0x82, 0xE7, + 0x02, 0x00, 0x1C, 0x90, 0x40, 0x5C, 0x00, 0x16, 0x01, 0xE6, 0x06, 0x00, 0x00, 0xF2, 0x4E, 0x0D, + 0x01, 0xF0, 0x80, 0x01, 0x1E, 0xF0, 0x80, 0x01, 0x00, 0xF0, 0xA0, 0x04, 0x42, 0x5B, 0x06, 0xF7, + 0x03, 0x00, 0x46, 0x59, 0xBF, 0x57, 0x77, 0x57, 0x01, 0xE6, 0x80, 0x00, 0x07, 0x80, 0x31, 0x44, + 0x04, 0x80, 0x18, 0xE4, 0x20, 0x00, 0x56, 0x13, 0x20, 0x80, 0x48, 0xE4, 0x03, 0x00, 0x4E, 0x12, + 0x00, 0xFC, 0xA2, 0x00, 0x98, 0x57, 0x55, 0xF0, 0x1C, 0x05, 0x31, 0xE4, 0x40, 0x00, 0x00, 0xFC, + 0xA0, 0x00, 0x98, 0x57, 0x36, 0x12, 0x4C, 0x1C, 0x00, 0xF2, 0x12, 0x11, 0x89, 0x48, 0x00, 0xF2, + 0x12, 0x11, 0x86, 0xF0, 0x2E, 0x05, 0x82, 0xE7, 0x06, 0x00, 0x1B, 0x80, 0x48, 0xE4, 0x22, 0x00, + 0x5B, 0xF0, 0x0C, 0x05, 0x48, 0xE4, 0x20, 0x00, 0x59, 0xF0, 0x10, 0x05, 0x00, 0xE6, 0x20, 0x00, + 0x09, 0x48, 0x00, 0xF2, 0x12, 0x11, 0x86, 0xF0, 0x2E, 0x05, 0x83, 0x80, 0x04, 0x10, 0x00, 0xF2, + 0xA2, 0x0D, 0x00, 0xE6, 0x01, 0x00, 0x00, 0xEA, 0x26, 0x01, 0x01, 0xEA, 0x27, 0x01, 0x04, 0x80, + 0x18, 0xE4, 0x10, 0x00, 0x36, 0x12, 0xB9, 0x54, 0x00, 0xF2, 0xF6, 0x0E, 0x01, 0xE6, 0x06, 0x00, + 0x04, 0x80, 0x18, 0xE4, 0x01, 0x00, 0x04, 0x12, 0x01, 0xE6, 0x0D, 0x00, 0x00, 0xF2, 0x4E, 0x0D, + 0x00, 0xF2, 0x12, 0x11, 0x00, 0xF2, 0xBC, 0x11, 0x00, 0xF2, 0xC8, 0x11, 0x04, 0xE6, 0x02, 0x00, + 0x9E, 0xE7, 0x15, 0x00, 0x01, 0xF0, 0x1C, 0x0A, 0x00, 0xF0, 0x02, 0x0A, 0x00, 0xFC, 0x20, 0x01, + 0x98, 0x57, 0x34, 0x12, 0x00, 0xFC, 0x24, 0x01, 0x98, 0x57, 0x2C, 0x13, 0xB9, 0x54, 0x00, 0xF2, + 0xF6, 0x0E, 0x86, 0xF0, 0xA8, 0x05, 0x03, 0xF6, 0x01, 0x00, 0x00, 0xF2, 0x8C, 0x0E, 0x85, 0xF0, + 0x9E, 0x05, 0x82, 0xE7, 0x03, 0x00, 0x00, 0xF2, 0x60, 0x0B, 0x82, 0xE7, 0x02, 0x00, 0x00, 0xFC, + 0x24, 0x01, 0xB0, 0x57, 0x00, 0xFA, 0x24, 0x01, 0x00, 0xFC, 0x9E, 0x00, 0x98, 0x57, 0x5A, 0x12, + 0x00, 0xFC, 0xB6, 0x00, 0x98, 0x57, 0x52, 0x13, 0x03, 0xE6, 0x0C, 0x00, 0x00, 0xFC, 0x9C, 0x00, + 0x98, 0x57, 0x04, 0x13, 0x03, 0xE6, 0x19, 0x00, 0x05, 0xE6, 0x08, 0x00, 0x00, 0xF6, 0x00, 0x01, + 0x00, 0x57, 0x00, 0x57, 0x03, 0x58, 0x00, 0xDC, 0x18, 0xF4, 0x00, 0x80, 0x04, 0x13, 0x05, 0xE6, + 0x0F, 0x00, 0xB9, 0x54, 0x00, 0xF2, 0xF6, 0x0E, 0x86, 0xF0, 0x0A, 0x06, 0x00, 0xF2, 0xBA, 0x0E, + 0x85, 0xF0, 0x00, 0x06, 0x82, 0xE7, 0x03, 0x00, 0x00, 0xF2, 0x60, 0x0B, 0x82, 0xE7, 0x02, 0x00, + 0x00, 0xFC, 0xB6, 0x00, 0xB0, 0x57, 0x00, 0xFA, 0xB6, 0x00, 0x01, 0xF6, 0x01, 0x00, 0x00, 0xF2, + 0xF6, 0x0E, 0x9C, 0x32, 0x4E, 0x1C, 0x32, 0x1C, 0x00, 0xF2, 0x92, 0x0D, 0x30, 0x1C, 0x82, 0xE7, + 0x04, 0x00, 0xB1, 0xF0, 0x22, 0x06, 0x0A, 0xF0, 0x3E, 0x06, 0x05, 0xF0, 0xD6, 0x06, 0x06, 0xF0, + 0xDC, 0x06, 0x09, 0xF0, 0x24, 0x09, 0x1E, 0xF0, 0xFC, 0x09, 0x00, 0xF0, 0x02, 0x0A, 0x04, 0x80, + 0x18, 0xE4, 0x20, 0x00, 0x30, 0x12, 0x09, 0xE7, 0x03, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x21, 0x80, + 0x18, 0xE4, 0xE0, 0x00, 0x09, 0x48, 0x00, 0xF2, 0x12, 0x11, 0x09, 0xE7, 0x00, 0x00, 0x00, 0xF2, + 0x12, 0x11, 0x09, 0xE7, 0x00, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x99, 0xA4, 0x00, 0xF2, 0x12, 0x11, + 0x09, 0xE7, 0x00, 0x00, 0x9A, 0x10, 0x04, 0x80, 0x18, 0xE4, 0x02, 0x00, 0x34, 0x12, 0x09, 0xE7, + 0x1B, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x21, 0x80, 0x18, 0xE4, 0xE0, 0x00, 0x09, 0x48, 0x00, 0xF2, + 0x12, 0x11, 0x09, 0xE7, 0x00, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x09, 0xE7, 0x00, 0x00, 0x00, 0xF2, + 0x12, 0x11, 0x09, 0xE7, 0x01, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x09, 0xE7, 0x00, 0x00, 0x00, 0xF0, + 0x0C, 0x09, 0xBB, 0x55, 0x9A, 0x81, 0x03, 0xF7, 0x20, 0x00, 0x09, 0x6F, 0x93, 0x45, 0x55, 0xF0, + 0xE2, 0x06, 0xB1, 0xF0, 0xC2, 0x06, 0x0A, 0xF0, 0xBA, 0x06, 0x09, 0xF0, 0x24, 0x09, 0x1E, 0xF0, + 0xFC, 0x09, 0x00, 0xF0, 0x02, 0x0A, 0x00, 0xF2, 0x60, 0x0B, 0x47, 0x10, 0x09, 0xE7, 0x08, 0x00, + 0x41, 0x10, 0x05, 0x80, 0x48, 0xE4, 0x00, 0x00, 0x1E, 0x12, 0x00, 0xE6, 0x11, 0x00, 0x00, 0xEA, + 0xB8, 0x00, 0x00, 0xF2, 0xB6, 0x10, 0x2C, 0x90, 0xAE, 0x90, 0x08, 0x50, 0x8A, 0x50, 0x38, 0x54, + 0x1F, 0x40, 0x00, 0xF2, 0xB4, 0x0D, 0x08, 0x10, 0x08, 0x90, 0x8A, 0x90, 0x30, 0x50, 0xB2, 0x50, + 0x9C, 0x32, 0x0C, 0x92, 0x8E, 0x92, 0x38, 0x54, 0x04, 0x80, 0x30, 0xE4, 0x08, 0x00, 0x04, 0x40, + 0x0C, 0x1C, 0x00, 0xF6, 0x03, 0x00, 0xB1, 0xF0, 0x26, 0x07, 0x9E, 0xF0, 0x3A, 0x07, 0x01, 0x48, + 0x55, 0xF0, 0xFC, 0x09, 0x0C, 0x1C, 0x10, 0x44, 0xED, 0x10, 0x0B, 0xF0, 0x5E, 0x07, 0x0C, 0xF0, + 0x62, 0x07, 0x05, 0xF0, 0x52, 0x07, 0x06, 0xF0, 0x58, 0x07, 0x09, 0xF0, 0x24, 0x09, 0x00, 0xF0, + 0x02, 0x0A, 0x00, 0xF2, 0x60, 0x0B, 0xCF, 0x10, 0x09, 0xE7, 0x08, 0x00, 0xC9, 0x10, 0x2E, 0x1C, + 0x02, 0x10, 0x2C, 0x1C, 0xAA, 0xF0, 0x64, 0x07, 0xAC, 0xF0, 0x72, 0x07, 0x40, 0x10, 0x34, 0x1C, + 0xF3, 0x10, 0xAD, 0xF0, 0x7C, 0x07, 0xC8, 0x10, 0x36, 0x1C, 0xE9, 0x10, 0x2B, 0xF0, 0x82, 0x08, + 0x6B, 0x18, 0x18, 0xF4, 0x00, 0xFE, 0x20, 0x12, 0x01, 0x58, 0xD2, 0xF0, 0x82, 0x08, 0x76, 0x18, + 0x18, 0xF4, 0x03, 0x00, 0xEC, 0x12, 0x00, 0xFC, 0x22, 0x01, 0x18, 0xF4, 0x01, 0x00, 0xE2, 0x12, + 0x0B, 0xF0, 0x64, 0x07, 0x0C, 0xF0, 0x64, 0x07, 0x36, 0x1C, 0x34, 0x1C, 0xB7, 0x10, 0x38, 0x54, + 0xB9, 0x54, 0x84, 0x80, 0x19, 0xE4, 0x20, 0x00, 0xB2, 0x13, 0x85, 0x80, 0x81, 0x48, 0x66, 0x12, + 0x04, 0x80, 0x18, 0xE4, 0x08, 0x00, 0x58, 0x13, 0x1F, 0x80, 0x08, 0x44, 0xC8, 0x44, 0x9F, 0x12, + 0x1F, 0x40, 0x34, 0x91, 0xB6, 0x91, 0x44, 0x55, 0xE5, 0x55, 0x02, 0xEC, 0xB8, 0x00, 0x02, 0x49, + 0xBB, 0x55, 0x82, 0x81, 0xC0, 0x55, 0x48, 0xF4, 0x0F, 0x00, 0x5A, 0xF0, 0x1A, 0x08, 0x4A, 0xE4, + 0x17, 0x00, 0xD5, 0xF0, 0xFA, 0x07, 0x02, 0xF6, 0x0F, 0x00, 0x02, 0xF4, 0x02, 0x00, 0x02, 0xEA, + 0xB8, 0x00, 0x04, 0x91, 0x86, 0x91, 0x02, 0x4B, 0x2C, 0x90, 0x08, 0x50, 0x2E, 0x90, 0x0A, 0x50, + 0x2C, 0x51, 0xAE, 0x51, 0x00, 0xF2, 0xB6, 0x10, 0x38, 0x54, 0x00, 0xF2, 0xB4, 0x0D, 0x56, 0x10, + 0x34, 0x91, 0xB6, 0x91, 0x0C, 0x10, 0x04, 0x80, 0x18, 0xE4, 0x08, 0x00, 0x41, 0x12, 0x0C, 0x91, + 0x8E, 0x91, 0x04, 0x80, 0x18, 0xE4, 0xF7, 0x00, 0x04, 0x40, 0x30, 0x90, 0xB2, 0x90, 0x36, 0x10, + 0x02, 0x80, 0x48, 0xE4, 0x10, 0x00, 0x31, 0x12, 0x82, 0xE7, 0x10, 0x00, 0x84, 0x80, 0x19, 0xE4, + 0x20, 0x00, 0x10, 0x13, 0x0C, 0x90, 0x8E, 0x90, 0x5D, 0xF0, 0x78, 0x07, 0x0C, 0x58, 0x8D, 0x58, + 0x00, 0xF0, 0x64, 0x07, 0x38, 0x54, 0xB9, 0x54, 0x19, 0x80, 0xF1, 0x10, 0x3A, 0x55, 0x19, 0x81, + 0xBB, 0x55, 0x10, 0x90, 0x92, 0x90, 0x10, 0x58, 0x91, 0x58, 0x14, 0x59, 0x95, 0x59, 0x00, 0xF0, + 0x64, 0x07, 0x04, 0x80, 0x18, 0xE4, 0x20, 0x00, 0x06, 0x12, 0x6C, 0x19, 0x19, 0x41, 0x7C, 0x10, + 0x6C, 0x19, 0x0C, 0x51, 0xED, 0x19, 0x8E, 0x51, 0x6B, 0x18, 0x18, 0xF4, 0x00, 0xFF, 0x02, 0x13, + 0x6A, 0x10, 0x01, 0x58, 0xD2, 0xF0, 0xC0, 0x08, 0x76, 0x18, 0x18, 0xF4, 0x03, 0x00, 0x0A, 0x12, + 0x00, 0xFC, 0x22, 0x01, 0x18, 0xF4, 0x01, 0x00, 0x06, 0x13, 0x9E, 0xE7, 0x16, 0x00, 0x4C, 0x10, + 0xD1, 0xF0, 0xCA, 0x08, 0x9E, 0xE7, 0x17, 0x00, 0x42, 0x10, 0xD0, 0xF0, 0xD4, 0x08, 0x9E, 0xE7, + 0x19, 0x00, 0x38, 0x10, 0xCF, 0xF0, 0xDE, 0x08, 0x9E, 0xE7, 0x20, 0x00, 0x2E, 0x10, 0xCE, 0xF0, + 0xE8, 0x08, 0x9E, 0xE7, 0x21, 0x00, 0x24, 0x10, 0xCD, 0xF0, 0xF2, 0x08, 0x9E, 0xE7, 0x22, 0x00, + 0x1A, 0x10, 0xCC, 0xF0, 0x04, 0x09, 0x84, 0x80, 0x19, 0xE4, 0x04, 0x00, 0x06, 0x12, 0x9E, 0xE7, + 0x12, 0x00, 0x08, 0x10, 0xCB, 0xF0, 0x0C, 0x09, 0x9E, 0xE7, 0x24, 0x00, 0xB1, 0xF0, 0x0C, 0x09, + 0x05, 0xF0, 0x1E, 0x09, 0x09, 0xF0, 0x24, 0x09, 0x1E, 0xF0, 0xFC, 0x09, 0xE4, 0x10, 0x00, 0xF2, + 0x60, 0x0B, 0xE9, 0x10, 0x9C, 0x32, 0x82, 0xE7, 0x20, 0x00, 0x32, 0x1C, 0xE9, 0x09, 0x00, 0xF2, + 0x12, 0x11, 0x85, 0xF0, 0x02, 0x0A, 0x69, 0x08, 0x01, 0xF0, 0x44, 0x09, 0x1E, 0xF0, 0xFC, 0x09, + 0x00, 0xF0, 0x38, 0x09, 0x30, 0x44, 0x06, 0x12, 0x9E, 0xE7, 0x42, 0x00, 0xB8, 0x10, 0x04, 0xF6, + 0x01, 0x00, 0xB3, 0x45, 0x74, 0x12, 0x04, 0x80, 0x18, 0xE4, 0x20, 0x00, 0x22, 0x13, 0x4B, 0xE4, + 0x02, 0x00, 0x36, 0x12, 0x4B, 0xE4, 0x28, 0x00, 0xAC, 0x13, 0x00, 0xF2, 0xBC, 0x11, 0x00, 0xF2, + 0xC8, 0x11, 0x03, 0xF6, 0xD0, 0x00, 0xFA, 0x14, 0x82, 0xE7, 0x01, 0x00, 0x00, 0xF0, 0x80, 0x01, + 0x9E, 0xE7, 0x44, 0x00, 0x4B, 0xE4, 0x02, 0x00, 0x06, 0x12, 0x03, 0xE6, 0x02, 0x00, 0x76, 0x10, + 0x00, 0xF2, 0xA2, 0x0D, 0x03, 0xE6, 0x02, 0x00, 0x6C, 0x10, 0x00, 0xF2, 0xA2, 0x0D, 0x19, 0x82, + 0x34, 0x46, 0x0A, 0x13, 0x03, 0xE6, 0x02, 0x00, 0x9E, 0xE7, 0x43, 0x00, 0x68, 0x10, 0x04, 0x80, + 0x30, 0xE4, 0x20, 0x00, 0x04, 0x40, 0x00, 0xF2, 0xBC, 0x11, 0x00, 0xF2, 0xC8, 0x11, 0x82, 0xE7, + 0x01, 0x00, 0x06, 0xF7, 0x02, 0x00, 0x00, 0xF0, 0x08, 0x03, 0x04, 0x80, 0x18, 0xE4, 0x20, 0x00, + 0x06, 0x12, 0x03, 0xE6, 0x02, 0x00, 0x3E, 0x10, 0x04, 0x80, 0x18, 0xE4, 0x02, 0x00, 0x3A, 0x12, + 0x04, 0x80, 0x18, 0xE4, 0xFD, 0x00, 0x04, 0x40, 0x1C, 0x1C, 0x9D, 0xF0, 0xEA, 0x09, 0x1C, 0x1C, + 0x9D, 0xF0, 0xF0, 0x09, 0xC1, 0x10, 0x9E, 0xE7, 0x13, 0x00, 0x0A, 0x10, 0x9E, 0xE7, 0x41, 0x00, + 0x04, 0x10, 0x9E, 0xE7, 0x24, 0x00, 0x00, 0xFC, 0xBE, 0x00, 0x98, 0x57, 0xD5, 0xF0, 0x8A, 0x02, + 0x04, 0xE6, 0x04, 0x00, 0x06, 0x10, 0x04, 0xE6, 0x04, 0x00, 0x9D, 0x41, 0x1C, 0x42, 0x9F, 0xE7, + 0x00, 0x00, 0x06, 0xF7, 0x02, 0x00, 0x03, 0xF6, 0xE0, 0x00, 0x3C, 0x14, 0x44, 0x58, 0x45, 0x58, + 0x00, 0xF2, 0xF6, 0x0D, 0x00, 0xF2, 0x7E, 0x10, 0x00, 0xF2, 0xC6, 0x0F, 0x3C, 0x14, 0x1E, 0x1C, + 0x00, 0xF0, 0x80, 0x01, 0x12, 0x1C, 0x22, 0x1C, 0xD2, 0x14, 0x00, 0xF0, 0x72, 0x01, 0x83, 0x59, + 0x03, 0xDC, 0x73, 0x57, 0x80, 0x5D, 0x00, 0x16, 0x83, 0x59, 0x03, 0xDC, 0x38, 0x54, 0x70, 0x57, + 0x33, 0x54, 0x3B, 0x54, 0x80, 0x5D, 0x00, 0x16, 0x03, 0x57, 0x83, 0x59, 0x38, 0x54, 0x00, 0xCC, + 0x00, 0x16, 0x03, 0x57, 0x83, 0x59, 0x00, 0x4C, 0x00, 0x16, 0x02, 0x80, 0x48, 0xE4, 0x01, 0x00, + 0x0E, 0x12, 0x48, 0xE4, 0x05, 0x00, 0x08, 0x12, 0x00, 0xF2, 0xBC, 0x11, 0x00, 0xF2, 0xC8, 0x11, + 0xC1, 0x5A, 0x3A, 0x55, 0x02, 0xEC, 0xB5, 0x00, 0x45, 0x59, 0x00, 0xF2, 0xF6, 0x0D, 0x83, 0x58, + 0x30, 0xE7, 0x00, 0x00, 0x10, 0x4D, 0x30, 0xE7, 0x40, 0x00, 0x10, 0x4F, 0x38, 0x90, 0xBA, 0x90, + 0x10, 0x5C, 0x80, 0x5C, 0x83, 0x5A, 0x10, 0x4E, 0x04, 0xEA, 0xB5, 0x00, 0x43, 0x5B, 0x03, 0xF4, + 0xE0, 0x00, 0x83, 0x59, 0x04, 0xCC, 0x01, 0x4A, 0x0A, 0x12, 0x45, 0x5A, 0x00, 0xF2, 0xF6, 0x0D, + 0x00, 0xF2, 0x38, 0x10, 0x00, 0x16, 0x08, 0x1C, 0x00, 0xFC, 0xAC, 0x00, 0x06, 0x58, 0x67, 0x18, + 0x18, 0xF4, 0x8F, 0xE1, 0x01, 0xFC, 0xAE, 0x00, 0x19, 0xF4, 0x70, 0x1E, 0xB0, 0x54, 0x07, 0x58, + 0x00, 0xFC, 0xB0, 0x00, 0x08, 0x58, 0x00, 0xFC, 0xB2, 0x00, 0x09, 0x58, 0x0A, 0x1C, 0x00, 0xE6, + 0x0F, 0x00, 0x00, 0xEA, 0xB9, 0x00, 0x38, 0x54, 0x00, 0xFA, 0x24, 0x01, 0x00, 0xFA, 0xB6, 0x00, + 0x18, 0x1C, 0x14, 0x1C, 0x10, 0x1C, 0x32, 0x1C, 0x12, 0x1C, 0x00, 0x16, 0x3E, 0x57, 0x0C, 0x14, + 0x0E, 0x47, 0x07, 0xE6, 0x10, 0x00, 0xCE, 0x47, 0xF5, 0x13, 0x00, 0x16, 0x00, 0xF2, 0xA2, 0x0D, + 0x02, 0x4B, 0x03, 0xF6, 0xE0, 0x00, 0x00, 0xF2, 0x68, 0x0A, 0x01, 0x48, 0x20, 0x12, 0x44, 0x58, + 0x45, 0x58, 0x9E, 0xE7, 0x15, 0x00, 0x9C, 0xE7, 0x04, 0x00, 0x00, 0xF2, 0xF6, 0x0D, 0x00, 0xF2, + 0x7E, 0x10, 0x00, 0xF2, 0xC6, 0x0F, 0x00, 0xF2, 0x7A, 0x0A, 0x1E, 0x1C, 0xD5, 0x10, 0x00, 0x16, + 0x69, 0x08, 0x48, 0xE4, 0x04, 0x00, 0x64, 0x12, 0x48, 0xE4, 0x02, 0x00, 0x20, 0x12, 0x48, 0xE4, + 0x03, 0x00, 0x1A, 0x12, 0x48, 0xE4, 0x08, 0x00, 0x14, 0x12, 0x48, 0xE4, 0x01, 0x00, 0xF0, 0x12, + 0x48, 0xE4, 0x07, 0x00, 0x12, 0x12, 0x01, 0xE6, 0x07, 0x00, 0x00, 0xF2, 0x4E, 0x0D, 0x00, 0xF2, + 0x12, 0x11, 0x05, 0xF0, 0x60, 0x0B, 0x00, 0x16, 0x00, 0xE6, 0x01, 0x00, 0x00, 0xEA, 0x99, 0x00, + 0x02, 0x80, 0x48, 0xE4, 0x03, 0x00, 0xE7, 0x12, 0x48, 0xE4, 0x06, 0x00, 0xE1, 0x12, 0x01, 0xE6, + 0x06, 0x00, 0x00, 0xF2, 0x4E, 0x0D, 0x00, 0xF2, 0x12, 0x11, 0x04, 0xE6, 0x02, 0x00, 0x9E, 0xE7, + 0x15, 0x00, 0x01, 0xF0, 0x1C, 0x0A, 0x00, 0xF0, 0x02, 0x0A, 0x00, 0x16, 0x02, 0x80, 0x48, 0xE4, + 0x10, 0x00, 0x1C, 0x12, 0x82, 0xE7, 0x08, 0x00, 0x3C, 0x56, 0x03, 0x82, 0x00, 0xF2, 0xE2, 0x0D, + 0x30, 0xE7, 0x08, 0x00, 0x04, 0xF7, 0x70, 0x01, 0x06, 0xF7, 0x02, 0x00, 0x00, 0xF0, 0x80, 0x01, + 0x6C, 0x19, 0xED, 0x19, 0x5D, 0xF0, 0xD4, 0x0B, 0x44, 0x55, 0xE5, 0x55, 0x59, 0xF0, 0x52, 0x0C, + 0x04, 0x55, 0xA5, 0x55, 0x1F, 0x80, 0x01, 0xEC, 0xB8, 0x00, 0x82, 0x48, 0x82, 0x80, 0x49, 0x44, + 0x2E, 0x13, 0x01, 0xEC, 0xB8, 0x00, 0x41, 0xE4, 0x02, 0x00, 0x01, 0xEA, 0xB8, 0x00, 0x49, 0xE4, + 0x11, 0x00, 0x59, 0xF0, 0x2E, 0x0C, 0x01, 0xE6, 0x17, 0x00, 0x01, 0xEA, 0xB8, 0x00, 0x02, 0x4B, + 0x88, 0x90, 0xAC, 0x50, 0x8A, 0x90, 0xAE, 0x50, 0x01, 0xEC, 0xB8, 0x00, 0x82, 0x48, 0x82, 0x80, + 0x10, 0x44, 0x02, 0x4B, 0x1F, 0x40, 0xC0, 0x44, 0x00, 0xF2, 0xB4, 0x0D, 0x04, 0x55, 0xA5, 0x55, + 0x9F, 0x10, 0x0C, 0x51, 0x8E, 0x51, 0x30, 0x90, 0xB2, 0x90, 0x00, 0x56, 0xA1, 0x56, 0x30, 0x50, + 0xB2, 0x50, 0x34, 0x90, 0xB6, 0x90, 0x40, 0x56, 0xE1, 0x56, 0x34, 0x50, 0xB6, 0x50, 0x65, 0x10, + 0xB1, 0xF0, 0x70, 0x0C, 0x85, 0xF0, 0xCA, 0x0B, 0xE9, 0x09, 0x4B, 0xE4, 0x03, 0x00, 0x78, 0x12, + 0x4B, 0xE4, 0x02, 0x00, 0x01, 0x13, 0xB1, 0xF0, 0x86, 0x0C, 0x85, 0xF0, 0xCA, 0x0B, 0x69, 0x08, + 0x48, 0xE4, 0x03, 0x00, 0xD5, 0xF0, 0x86, 0x0B, 0x00, 0xF2, 0x12, 0x11, 0x85, 0xF0, 0xCA, 0x0B, + 0xE8, 0x09, 0x3C, 0x56, 0x00, 0xFC, 0x20, 0x01, 0x98, 0x57, 0x02, 0x13, 0xBB, 0x45, 0x4B, 0xE4, + 0x00, 0x00, 0x08, 0x12, 0x03, 0xE6, 0x01, 0x00, 0x04, 0xF6, 0x00, 0x80, 0xA8, 0x14, 0xD2, 0x14, + 0x30, 0x1C, 0x02, 0x80, 0x48, 0xE4, 0x03, 0x00, 0x10, 0x13, 0x00, 0xFC, 0xB6, 0x00, 0x98, 0x57, + 0x02, 0x13, 0x4C, 0x1C, 0x3E, 0x1C, 0x00, 0xF0, 0x8E, 0x0B, 0x00, 0xFC, 0x24, 0x01, 0xB0, 0x57, + 0x00, 0xFA, 0x24, 0x01, 0x4C, 0x1C, 0x3E, 0x1C, 0x00, 0xF2, 0x12, 0x11, 0x86, 0xF0, 0x8E, 0x0B, + 0x00, 0xF2, 0x8C, 0x0E, 0x00, 0xF0, 0x8E, 0x0B, 0xB1, 0xF0, 0xF8, 0x0C, 0x85, 0xF0, 0x86, 0x0B, + 0x69, 0x08, 0x48, 0xE4, 0x01, 0x00, 0xD5, 0xF0, 0x86, 0x0B, 0xFC, 0x14, 0x42, 0x58, 0x6C, 0x14, + 0x80, 0x14, 0x30, 0x1C, 0x4A, 0xF4, 0x02, 0x00, 0x55, 0xF0, 0x86, 0x0B, 0x4A, 0xF4, 0x01, 0x00, + 0x0E, 0x12, 0x02, 0x80, 0x48, 0xE4, 0x03, 0x00, 0x06, 0x13, 0x3E, 0x1C, 0x00, 0xF0, 0x8E, 0x0B, + 0x00, 0xFC, 0xB6, 0x00, 0xB0, 0x57, 0x00, 0xFA, 0xB6, 0x00, 0x4C, 0x1C, 0x3E, 0x1C, 0x00, 0xF2, + 0x12, 0x11, 0x86, 0xF0, 0x8E, 0x0B, 0x00, 0xF2, 0xBA, 0x0E, 0x00, 0xF0, 0x8E, 0x0B, 0x4C, 0x1C, + 0xB1, 0xF0, 0x50, 0x0D, 0x85, 0xF0, 0x5C, 0x0D, 0x69, 0x08, 0xF3, 0x10, 0x86, 0xF0, 0x64, 0x0D, + 0x4E, 0x1C, 0x89, 0x48, 0x00, 0x16, 0x00, 0xF6, 0x00, 0x01, 0x00, 0x57, 0x00, 0x57, 0x03, 0x58, + 0x00, 0xDC, 0x18, 0xF4, 0xFF, 0x7F, 0x30, 0x56, 0x00, 0x5C, 0x00, 0x16, 0x00, 0xF6, 0x00, 0x01, + 0x00, 0x57, 0x00, 0x57, 0x03, 0x58, 0x00, 0xDC, 0x18, 0xF4, 0x00, 0x80, 0x30, 0x56, 0x00, 0x5C, + 0x00, 0x16, 0x00, 0xF6, 0x00, 0x01, 0x00, 0x57, 0x00, 0x57, 0x03, 0x58, 0x00, 0xDC, 0x0B, 0x58, + 0x00, 0x16, 0x03, 0xF6, 0x24, 0x01, 0x00, 0xF2, 0x58, 0x0A, 0x03, 0xF6, 0xB6, 0x00, 0x00, 0xF2, + 0x58, 0x0A, 0x00, 0x16, 0x02, 0xEC, 0xB8, 0x00, 0x02, 0x49, 0x18, 0xF4, 0xFF, 0x00, 0x00, 0x54, + 0x00, 0x54, 0x00, 0x54, 0x00, 0xF4, 0x08, 0x00, 0xE1, 0x18, 0x80, 0x54, 0x03, 0x58, 0x00, 0xDD, + 0x01, 0xDD, 0x02, 0xDD, 0x03, 0xDC, 0x02, 0x4B, 0x30, 0x50, 0xB2, 0x50, 0x34, 0x51, 0xB6, 0x51, + 0x00, 0x16, 0x45, 0x5A, 0x1D, 0xF4, 0xFF, 0x00, 0x85, 0x56, 0x85, 0x56, 0x85, 0x56, 0x05, 0xF4, + 0x02, 0x12, 0x83, 0x5A, 0x00, 0x16, 0x1D, 0xF4, 0xFF, 0x00, 0x85, 0x56, 0x85, 0x56, 0x85, 0x56, + 0x05, 0xF4, 0x00, 0x12, 0x83, 0x5A, 0x00, 0x16, 0x38, 0x54, 0xBB, 0x55, 0x3C, 0x56, 0xBD, 0x56, + 0x00, 0xF2, 0x12, 0x11, 0x85, 0xF0, 0x82, 0x0E, 0xE9, 0x09, 0xC1, 0x59, 0x00, 0xF2, 0x12, 0x11, + 0x85, 0xF0, 0x82, 0x0E, 0xE8, 0x0A, 0x83, 0x55, 0x83, 0x55, 0x4B, 0xF4, 0x90, 0x01, 0x5C, 0xF0, + 0x36, 0x0E, 0xBD, 0x56, 0x40, 0x10, 0x4B, 0xF4, 0x30, 0x00, 0x59, 0xF0, 0x48, 0x0E, 0x01, 0xF6, + 0x0C, 0x00, 0x00, 0xF6, 0x01, 0x00, 0x2E, 0x10, 0x02, 0xFC, 0x9C, 0x00, 0x9A, 0x57, 0x14, 0x13, + 0x4B, 0xF4, 0x64, 0x00, 0x59, 0xF0, 0x64, 0x0E, 0x03, 0xF6, 0x64, 0x00, 0x01, 0xF6, 0x19, 0x00, + 0x00, 0xF6, 0x01, 0x00, 0x43, 0xF4, 0x33, 0x00, 0x56, 0xF0, 0x76, 0x0E, 0x04, 0xF4, 0x00, 0x01, + 0x43, 0xF4, 0x19, 0x00, 0xF3, 0x10, 0xB4, 0x56, 0xC3, 0x58, 0x02, 0xFC, 0x9E, 0x00, 0x9A, 0x57, + 0x08, 0x13, 0x3C, 0x56, 0x00, 0xF6, 0x02, 0x00, 0x00, 0x16, 0x00, 0x16, 0x09, 0xE7, 0x01, 0x00, + 0x00, 0xF2, 0x12, 0x11, 0x86, 0xF0, 0xB8, 0x0E, 0x09, 0xE7, 0x02, 0x00, 0x00, 0xF2, 0x12, 0x11, + 0x86, 0xF0, 0xB8, 0x0E, 0x09, 0xE7, 0x03, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x86, 0xF0, 0xB8, 0x0E, + 0x4E, 0x1C, 0x89, 0x49, 0x00, 0xF2, 0x12, 0x11, 0x00, 0x16, 0x09, 0xE7, 0x01, 0x00, 0x00, 0xF2, + 0x12, 0x11, 0x86, 0xF0, 0xF2, 0x0E, 0x09, 0xE7, 0x03, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x86, 0xF0, + 0xF2, 0x0E, 0x09, 0xE7, 0x01, 0x00, 0x00, 0xF2, 0x12, 0x11, 0x86, 0xF0, 0xF2, 0x0E, 0x89, 0x49, + 0x00, 0xF2, 0x12, 0x11, 0x86, 0xF0, 0xF2, 0x0E, 0x4E, 0x1C, 0x89, 0x4A, 0x00, 0xF2, 0x12, 0x11, + 0x00, 0x16, 0x3C, 0x56, 0x00, 0x16, 0x00, 0xEC, 0x26, 0x01, 0x48, 0xE4, 0x01, 0x00, 0x1E, 0x13, + 0x38, 0x44, 0x00, 0xEA, 0x26, 0x01, 0x49, 0xF4, 0x00, 0x00, 0x04, 0x12, 0x4E, 0x1C, 0x02, 0x10, + 0x4C, 0x1C, 0x01, 0xEC, 0x27, 0x01, 0x89, 0x48, 0x00, 0xF2, 0x12, 0x11, 0x02, 0x14, 0x00, 0x16, + 0x85, 0xF0, 0x52, 0x0F, 0x38, 0x54, 0x00, 0xEA, 0x99, 0x00, 0x00, 0xF2, 0x60, 0x0B, 0x02, 0x80, + 0x48, 0xE4, 0x06, 0x00, 0x1C, 0x13, 0x00, 0xEC, 0x99, 0x00, 0x48, 0xE4, 0x01, 0x00, 0x0A, 0x12, + 0x04, 0x80, 0x30, 0xE4, 0x01, 0x00, 0x04, 0x40, 0x08, 0x10, 0x04, 0x80, 0x18, 0xE4, 0xFE, 0x00, + 0x04, 0x40, 0x00, 0x16, 0x02, 0xF6, 0xE0, 0x00, 0x02, 0x57, 0x03, 0x59, 0x01, 0xCC, 0x81, 0x48, + 0x22, 0x12, 0x00, 0x4E, 0x83, 0x5A, 0x90, 0x4C, 0x20, 0xE7, 0x00, 0x00, 0xC3, 0x58, 0x1B, 0xF4, + 0xFF, 0x00, 0x83, 0x55, 0x83, 0x55, 0x83, 0x55, 0x03, 0xF4, 0x00, 0x12, 0x8B, 0x55, 0x83, 0x59, + 0x00, 0x4E, 0x00, 0x16, 0x00, 0x4E, 0x02, 0xF6, 0xF0, 0x00, 0x02, 0x57, 0x03, 0x59, 0x00, 0x4E, + 0x83, 0x5A, 0x30, 0xE7, 0x00, 0x00, 0x20, 0xE7, 0x00, 0x00, 0x00, 0x16, 0x02, 0xF6, 0xF0, 0x00, + 0x02, 0x57, 0x03, 0x59, 0x01, 0xCC, 0x00, 0x4E, 0x83, 0x5A, 0x30, 0xE7, 0x00, 0x00, 0x80, 0x4C, + 0xC3, 0x58, 0x1B, 0xF4, 0xFF, 0x00, 0x83, 0x55, 0x83, 0x55, 0x83, 0x55, 0x03, 0xF4, 0x00, 0x12, + 0x83, 0x59, 0x00, 0x4E, 0x00, 0x16, 0x03, 0xF6, 0xE0, 0x00, 0x03, 0x57, 0x83, 0x59, 0x3A, 0x55, + 0x02, 0xCC, 0x45, 0x5A, 0x00, 0xF2, 0xF6, 0x0D, 0xC0, 0x5A, 0x40, 0x5C, 0x38, 0x54, 0x00, 0xCD, + 0x01, 0xCC, 0x4A, 0x46, 0x0A, 0x13, 0x83, 0x59, 0x00, 0x4C, 0x01, 0x48, 0x16, 0x13, 0x0C, 0x10, + 0xC5, 0x58, 0x00, 0xF2, 0xF6, 0x0D, 0x00, 0x4C, 0x01, 0x48, 0x08, 0x13, 0x05, 0xF6, 0xF0, 0x00, + 0x05, 0x57, 0x08, 0x10, 0x45, 0x58, 0x00, 0xF2, 0xF6, 0x0D, 0x8D, 0x56, 0x83, 0x5A, 0x80, 0x4C, + 0x05, 0x17, 0x00, 0x16, 0x02, 0x4B, 0x06, 0xF7, 0x04, 0x00, 0x62, 0x0B, 0x03, 0x82, 0x00, 0xF2, + 0xE2, 0x0D, 0x02, 0x80, 0x00, 0x4C, 0x45, 0xF4, 0x02, 0x00, 0x52, 0x14, 0x06, 0xF7, 0x02, 0x00, + 0x06, 0x14, 0x00, 0xF2, 0x54, 0x0F, 0x00, 0x16, 0x02, 0x4B, 0x01, 0xF6, 0xFF, 0x00, 0x38, 0x1C, + 0x05, 0xF4, 0x04, 0x00, 0x83, 0x5A, 0x18, 0xDF, 0x19, 0xDF, 0x1D, 0xF7, 0x3C, 0x00, 0xB8, 0xF0, + 0x4E, 0x10, 0x9C, 0x14, 0x01, 0x48, 0x1C, 0x13, 0x0E, 0xF7, 0x3C, 0x00, 0x03, 0xF7, 0x04, 0x00, + 0xAF, 0x19, 0x03, 0x42, 0x45, 0xF4, 0x02, 0x00, 0x83, 0x5A, 0x02, 0xCC, 0x02, 0x41, 0x45, 0xF4, + 0x02, 0x00, 0x00, 0x16, 0x91, 0x44, 0xD5, 0xF0, 0x3E, 0x10, 0x00, 0xF0, 0x9E, 0x02, 0x01, 0xF6, + 0xFF, 0x00, 0x38, 0x1C, 0x05, 0xF4, 0x04, 0x00, 0x83, 0x5A, 0x18, 0xDF, 0x19, 0xDF, 0x0E, 0xF7, + 0x3C, 0x00, 0x03, 0xF7, 0x04, 0x00, 0x0F, 0x79, 0x1C, 0xF7, 0x3C, 0x00, 0xB8, 0xF0, 0x9C, 0x10, + 0x4E, 0x14, 0x01, 0x48, 0x06, 0x13, 0x45, 0xF4, 0x04, 0x00, 0x00, 0x16, 0x91, 0x44, 0xD5, 0xF0, + 0x82, 0x10, 0x00, 0xF0, 0x9E, 0x02, 0x02, 0xF6, 0xFF, 0x00, 0x38, 0x1C, 0x2C, 0xBC, 0xAE, 0xBC, + 0xE2, 0x08, 0x00, 0xEC, 0xB8, 0x00, 0x02, 0x48, 0x1D, 0xF7, 0x80, 0x00, 0xB8, 0xF0, 0xCC, 0x10, + 0x1E, 0x14, 0x01, 0x48, 0x0E, 0x13, 0x0E, 0xF7, 0x80, 0x00, 0x38, 0x54, 0x03, 0x58, 0xAF, 0x19, + 0x82, 0x48, 0x00, 0x16, 0x82, 0x48, 0x12, 0x45, 0xD5, 0xF0, 0xBA, 0x10, 0x00, 0xF0, 0x9E, 0x02, + 0x39, 0xF0, 0xF8, 0x10, 0x38, 0x44, 0x00, 0x16, 0x7E, 0x18, 0x18, 0xF4, 0x03, 0x00, 0x04, 0x13, + 0x61, 0x18, 0x00, 0x16, 0x38, 0x1C, 0x00, 0xFC, 0x22, 0x01, 0x18, 0xF4, 0x01, 0x00, 0xF1, 0x12, + 0xE3, 0x10, 0x30, 0x44, 0x30, 0x44, 0x30, 0x44, 0xB1, 0xF0, 0x18, 0x11, 0x00, 0x16, 0x3E, 0x57, + 0x03, 0xF6, 0xE0, 0x00, 0x03, 0x57, 0x83, 0x59, 0x04, 0xCC, 0x01, 0x4A, 0x6A, 0x12, 0x45, 0x5A, + 0x00, 0xF2, 0xF6, 0x0D, 0x02, 0x4B, 0x70, 0x14, 0x34, 0x13, 0x02, 0x80, 0x48, 0xE4, 0x08, 0x00, + 0x18, 0x12, 0x9C, 0xE7, 0x02, 0x00, 0x9E, 0xE7, 0x15, 0x00, 0x00, 0xF2, 0xC6, 0x0F, 0x00, 0xF2, + 0x7A, 0x0A, 0x1E, 0x1C, 0x01, 0xF6, 0x01, 0x00, 0x00, 0x16, 0x30, 0xE4, 0x10, 0x00, 0x04, 0x40, + 0x00, 0xF2, 0xE2, 0x0D, 0x20, 0xE7, 0x01, 0x00, 0x01, 0xF6, 0x01, 0x00, 0x00, 0x16, 0x04, 0xDC, + 0x01, 0x4A, 0x24, 0x12, 0x45, 0x5A, 0x00, 0xF2, 0xF6, 0x0D, 0x43, 0x5B, 0x06, 0xEC, 0x98, 0x00, + 0x00, 0xF2, 0x38, 0x10, 0xC6, 0x59, 0x20, 0x14, 0x0A, 0x13, 0x00, 0xF2, 0xC6, 0x0F, 0x00, 0xF2, + 0x14, 0x10, 0xA7, 0x10, 0x83, 0x5A, 0xD7, 0x10, 0x0E, 0x47, 0x07, 0xE6, 0x10, 0x00, 0xCE, 0x47, + 0x5A, 0xF0, 0x20, 0x11, 0xB9, 0x54, 0x00, 0x16, 0x14, 0x90, 0x96, 0x90, 0x02, 0xFC, 0xA8, 0x00, + 0x03, 0xFC, 0xAA, 0x00, 0x48, 0x55, 0x02, 0x13, 0xC9, 0x55, 0x00, 0x16, 0x00, 0xEC, 0xBA, 0x00, + 0x10, 0x44, 0x00, 0xEA, 0xBA, 0x00, 0x00, 0x16, 0x03, 0xF6, 0xC0, 0x00, 0x00, 0xF2, 0x68, 0x0A, + 0x10, 0x44, 0x00, 0x4C, 0x00, 0x16 +}; + +unsigned short _adv_mcode_size ASC_INITDATA = + sizeof(_adv_mcode_buf); /* 0x11D6 */ +unsigned long _adv_mcode_chksum ASC_INITDATA = 0x03494981UL; + +/* a_init.c */ +/* + * EEPROM Configuration. + * + * All drivers should use this structure to set the default EEPROM + * configuration. The BIOS now uses this structure when it is built. + * Additional structure information can be found in a_condor.h where + * the structure is defined. + */ +STATIC ADVEEP_CONFIG +Default_EEPROM_Config ASC_INITDATA = { + ADV_EEPROM_BIOS_ENABLE, /* cfg_msw */ + 0x0000, /* cfg_lsw */ + 0xFFFF, /* disc_enable */ + 0xFFFF, /* wdtr_able */ + 0xFFFF, /* sdtr_able */ + 0xFFFF, /* start_motor */ + 0xFFFF, /* tagqng_able */ + 0xFFFF, /* bios_scan */ + 0, /* scam_tolerant */ + 7, /* adapter_scsi_id */ + 0, /* bios_boot_delay */ + 3, /* scsi_reset_delay */ + 0, /* bios_id_lun */ + 0, /* termination */ + 0, /* reserved1 */ + 0xFFEF, /* bios_ctrl */ + 0xFFFF, /* ultra_able */ + 0, /* reserved2 */ + ASC_DEF_MAX_HOST_QNG, /* max_host_qng */ + ASC_DEF_MAX_DVC_QNG, /* max_dvc_qng */ + 0, /* dvc_cntl */ + 0, /* bug_fix */ + 0, /* serial_number_word1 */ + 0, /* serial_number_word2 */ + 0, /* serial_number_word3 */ + 0, /* check_sum */ + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }, /* oem_name[16] */ + 0, /* dvc_err_code */ + 0, /* adv_err_code */ + 0, /* adv_err_addr */ + 0, /* saved_dvc_err_code */ + 0, /* saved_adv_err_code */ + 0, /* saved_adv_err_addr */ + 0 /* num_of_err */ +}; + +/* + * Initialize the ADV_DVC_VAR structure. + * + * On failure set the ADV_DVC_VAR field 'err_code' and return ADV_ERROR. + * + * For a non-fatal error return a warning code. If there are no warnings + * then 0 is returned. + */ +ASC_INITFUNC( +int +AdvInitGetConfig(ADV_DVC_VAR *asc_dvc) +) +{ + ushort warn_code; + AdvPortAddr iop_base; + uchar pci_cmd_reg; + int status; + + warn_code = 0; + asc_dvc->err_code = 0; + iop_base = asc_dvc->iop_base; + + /* + * PCI Command Register + */ + + if (((pci_cmd_reg = DvcAdvReadPCIConfigByte(asc_dvc, + AscPCIConfigCommandRegister)) + & AscPCICmdRegBits_BusMastering) + != AscPCICmdRegBits_BusMastering) + { + pci_cmd_reg |= AscPCICmdRegBits_BusMastering; + + DvcAdvWritePCIConfigByte(asc_dvc, + AscPCIConfigCommandRegister, pci_cmd_reg); + + if (((DvcAdvReadPCIConfigByte(asc_dvc, AscPCIConfigCommandRegister)) + & AscPCICmdRegBits_BusMastering) + != AscPCICmdRegBits_BusMastering) + { + warn_code |= ASC_WARN_SET_PCI_CONFIG_SPACE; + } + } + + /* + * PCI Latency Timer + * + * If the "latency timer" register is 0x20 or above, then we don't need + * to change it. Otherwise, set it to 0x20 (i.e. set it to 0x20 if it + * comes up less than 0x20). + */ + if (DvcAdvReadPCIConfigByte(asc_dvc, AscPCIConfigLatencyTimer) < 0x20) { + DvcAdvWritePCIConfigByte(asc_dvc, AscPCIConfigLatencyTimer, 0x20); + if (DvcAdvReadPCIConfigByte(asc_dvc, AscPCIConfigLatencyTimer) < 0x20) + { + warn_code |= ASC_WARN_SET_PCI_CONFIG_SPACE; + } + } + + /* + * Save the state of the PCI Configuration Command Register + * "Parity Error Response Control" Bit. If the bit is clear (0), + * in AdvInitAsc3550Driver() tell the microcode to ignore DMA + * parity errors. + */ + asc_dvc->cfg->control_flag = 0; + if (((DvcAdvReadPCIConfigByte(asc_dvc, AscPCIConfigCommandRegister) + & AscPCICmdRegBits_ParErrRespCtrl)) == 0) + { + asc_dvc->cfg->control_flag |= CONTROL_FLAG_IGNORE_PERR; + } + + asc_dvc->cur_host_qng = 0; + + asc_dvc->cfg->lib_version = (ADV_LIB_VERSION_MAJOR << 8) | + ADV_LIB_VERSION_MINOR; + asc_dvc->cfg->chip_version = + AdvGetChipVersion(iop_base, asc_dvc->bus_type); + + /* + * Reset the chip to start and allow register writes. + */ + if (AdvFindSignature(iop_base) == 0) + { + asc_dvc->err_code = ASC_IERR_BAD_SIGNATURE; + return ADV_ERROR; + } + else { + + AdvResetChip(asc_dvc); + + if ((status = AdvInitFromEEP(asc_dvc)) == ADV_ERROR) + { + return ADV_ERROR; + } + warn_code |= status; + + /* + * Reset the SCSI Bus if the EEPROM indicates that SCSI Bus + * Resets should be performed. + */ + if (asc_dvc->bios_ctrl & BIOS_CTRL_RESET_SCSI_BUS) + { + AdvResetSCSIBus(asc_dvc); + } + } + + return warn_code; +} + +/* + * Initialize the ASC3550. + * + * On failure set the ADV_DVC_VAR field 'err_code' and return ADV_ERROR. + * + * For a non-fatal error return a warning code. If there are no warnings + * then 0 is returned. + */ +ASC_INITFUNC( +int +AdvInitAsc3550Driver(ADV_DVC_VAR *asc_dvc) +) +{ + AdvPortAddr iop_base; + ushort warn_code; + ulong sum; + int begin_addr; + int end_addr; + int code_sum; + int word; + int rql_addr; /* RISC Queue List address */ + int i; + ushort scsi_cfg1; + uchar biosmem[ASC_MC_BIOSLEN]; /* BIOS RISC Memory 0x40-0x8F. */ + + /* If there is already an error, don't continue. */ + if (asc_dvc->err_code != 0) + { + return ADV_ERROR; + } + + warn_code = 0; + iop_base = asc_dvc->iop_base; + + /* + * Save the RISC memory BIOS region before writing the microcode. + * The BIOS may already be loaded and using its RISC LRAM region + * so its region must be saved and restored. + * + * Note: This code makes the assumption, which is currently true, + * that a chip reset does not clear RISC LRAM. + */ + for (i = 0; i < ASC_MC_BIOSLEN; i++) + { + AdvReadByteLram(iop_base, ASC_MC_BIOSMEM + i, biosmem[i]); + } + + /* + * Load the Microcode + * + * Write the microcode image to RISC memory starting at address 0. + */ + AdvWriteWordRegister(iop_base, IOPW_RAM_ADDR, 0); + for (word = 0; word < _adv_mcode_size; word += 2) + { + AdvWriteWordAutoIncLram(iop_base, + *((ushort *) (&_adv_mcode_buf[word]))); + } + + /* + * Clear the rest of Condor's Internal RAM (8KB). + */ + for (; word < ADV_CONDOR_MEMSIZE; word += 2) + { + AdvWriteWordAutoIncLram(iop_base, 0); + } + + /* + * Verify the microcode checksum. + */ + sum = 0; + AdvWriteWordRegister(iop_base, IOPW_RAM_ADDR, 0); + for (word = 0; word < _adv_mcode_size; word += 2) + { + sum += AdvReadWordAutoIncLram(iop_base); + } + + if (sum != _adv_mcode_chksum) + { + asc_dvc->err_code |= ASC_IERR_MCODE_CHKSUM; + return ADV_ERROR; + } + + /* + * Restore the RISC memory BIOS region. + */ + for (i = 0; i < ASC_MC_BIOSLEN; i++) + { + AdvWriteByteLram(iop_base, ASC_MC_BIOSMEM + i, biosmem[i]); + } + + /* + * Calculate and write the microcode code checksum to the microcode + * code checksum location ASC_MC_CODE_CHK_SUM (0x2C). + */ + AdvReadWordLram(iop_base, ASC_MC_CODE_BEGIN_ADDR, begin_addr); + AdvReadWordLram(iop_base, ASC_MC_CODE_END_ADDR, end_addr); + code_sum = 0; + for (word = begin_addr; word < end_addr; word += 2) + { + code_sum += *((ushort *) (&_adv_mcode_buf[word])); + } + AdvWriteWordLram(iop_base, ASC_MC_CODE_CHK_SUM, code_sum); + + /* + * Read microcode version and date. + */ + AdvReadWordLram(iop_base, ASC_MC_VERSION_DATE, asc_dvc->cfg->mcode_date); + AdvReadWordLram(iop_base, ASC_MC_VERSION_NUM, asc_dvc->cfg->mcode_version); + + /* + * Initialize microcode operating variables + */ + AdvWriteWordLram(iop_base, ASC_MC_ADAPTER_SCSI_ID, + asc_dvc->chip_scsi_id); + + /* + * If the PCI Configuration Command Register "Parity Error Response + * Control" Bit was clear (0), then set the microcode variable + * 'control_flag' CONTROL_FLAG_IGNORE_PERR flag to tell the microcode + * to ignore DMA parity errors. + */ + if (asc_dvc->cfg->control_flag & CONTROL_FLAG_IGNORE_PERR) + { + /* + * Note: Don't remove the use of a temporary variable in + * the following code, otherwise the Microsoft C compiler + * will turn the following lines into a no-op. + */ + AdvReadWordLram(iop_base, ASC_MC_CONTROL_FLAG, word); + word |= CONTROL_FLAG_IGNORE_PERR; + AdvWriteWordLram(iop_base, ASC_MC_CONTROL_FLAG, word); + } + + /* + * Set default microcode operating variables for WDTR, SDTR, and + * command tag queuing based on the EEPROM configuration values. + * + * These ADV_DVC_VAR fields and the microcode variables will be + * changed in AdvInquiryHandling() if it is found a device is + * incapable of a particular feature. + */ + + /* + * Set the microcode ULTRA target mask from EEPROM value. The + * SDTR target mask overrides the ULTRA target mask in the + * microcode so it is safe to set this value without determining + * whether the device supports SDTR. + * + * Note: There is no way to know whether a device supports ULTRA + * speed without attempting a SDTR ULTRA speed negotiation with + * the device. The device will reject the speed if it does not + * support it by responding with an SDTR message containing a + * slower speed. + */ + AdvWriteWordLram(iop_base, ASC_MC_ULTRA_ABLE, asc_dvc->ultra_able); + AdvWriteWordLram(iop_base, ASC_MC_DISC_ENABLE, asc_dvc->cfg->disc_enable); + + + /* + * Set SCSI_CFG0 Microcode Default Value. + * + * The microcode will set the SCSI_CFG0 register using this value + * after it is started below. + */ + AdvWriteWordLram(iop_base, ASC_MC_DEFAULT_SCSI_CFG0, + PARITY_EN | SEL_TMO_LONG | OUR_ID_EN | asc_dvc->chip_scsi_id); + + /* + * Determine SCSI_CFG1 Microcode Default Value. + * + * The microcode will set the SCSI_CFG1 register using this value + * after it is started below. + */ + + /* Read current SCSI_CFG1 Register value. */ + scsi_cfg1 = AdvReadWordRegister(iop_base, IOPW_SCSI_CFG1); + + /* + * If all three connectors are in use, return an error. + */ + if ((scsi_cfg1 & CABLE_ILLEGAL_A) == 0 || + (scsi_cfg1 & CABLE_ILLEGAL_B) == 0) + { + asc_dvc->err_code |= ASC_IERR_ILLEGAL_CONNECTION; + return ADV_ERROR; + } + + /* + * If the internal narrow cable is reversed all of the SCSI_CTRL + * register signals will be set. Check for and return an error if + * this condition is found. + */ + if ((AdvReadWordRegister(iop_base, IOPW_SCSI_CTRL) & 0x3F07) == 0x3F07) + { + asc_dvc->err_code |= ASC_IERR_REVERSED_CABLE; + return ADV_ERROR; + } + + /* + * If this is a differential board and a single-ended device + * is attached to one of the connectors, return an error. + */ + if ((scsi_cfg1 & DIFF_MODE) && (scsi_cfg1 & DIFF_SENSE) == 0) + { + asc_dvc->err_code |= ASC_IERR_SINGLE_END_DEVICE; + return ADV_ERROR; + } + + /* + * If automatic termination control is enabled, then set the + * termination value based on a table listed in a_condor.h. + * + * If manual termination was specified with an EEPROM setting + * then 'termination' was set-up in AdvInitFromEEP() and + * is ready to be 'ored' into SCSI_CFG1. + */ + if (asc_dvc->cfg->termination == 0) + { + /* + * The software always controls termination by setting TERM_CTL_SEL. + * If TERM_CTL_SEL were set to 0, the hardware would set termination. + */ + asc_dvc->cfg->termination |= TERM_CTL_SEL; + + switch(scsi_cfg1 & CABLE_DETECT) + { + /* TERM_CTL_H: on, TERM_CTL_L: on */ + case 0x3: case 0x7: case 0xB: case 0xD: case 0xE: case 0xF: + asc_dvc->cfg->termination |= (TERM_CTL_H | TERM_CTL_L); + break; + + /* TERM_CTL_H: on, TERM_CTL_L: off */ + case 0x1: case 0x5: case 0x9: case 0xA: case 0xC: + asc_dvc->cfg->termination |= TERM_CTL_H; + break; + + /* TERM_CTL_H: off, TERM_CTL_L: off */ + case 0x2: case 0x6: + break; + } + } + + /* + * Clear any set TERM_CTL_H and TERM_CTL_L bits. + */ + scsi_cfg1 &= ~TERM_CTL; + + /* + * Invert the TERM_CTL_H and TERM_CTL_L bits and then + * set 'scsi_cfg1'. The TERM_POL bit does not need to be + * referenced, because the hardware internally inverts + * the Termination High and Low bits if TERM_POL is set. + */ + scsi_cfg1 |= (TERM_CTL_SEL | (~asc_dvc->cfg->termination & TERM_CTL)); + + /* + * Set SCSI_CFG1 Microcode Default Value + * + * Set filter value and possibly modified termination control + * bits in the Microcode SCSI_CFG1 Register Value. + * + * The microcode will set the SCSI_CFG1 register using this value + * after it is started below. + */ + AdvWriteWordLram(iop_base, ASC_MC_DEFAULT_SCSI_CFG1, + FLTR_11_TO_20NS | scsi_cfg1); + + /* + * Set SEL_MASK Microcode Default Value + * + * The microcode will set the SEL_MASK register using this value + * after it is started below. + */ + AdvWriteWordLram(iop_base, ASC_MC_DEFAULT_SEL_MASK, + ADV_TID_TO_TIDMASK(asc_dvc->chip_scsi_id)); + + /* + * Link all the RISC Queue Lists together in a doubly-linked + * NULL terminated list. + * + * Skip the NULL (0) queue which is not used. + */ + for (i = 1, rql_addr = ASC_MC_RISC_Q_LIST_BASE + ASC_MC_RISC_Q_LIST_SIZE; + i < ASC_MC_RISC_Q_TOTAL_CNT; + i++, rql_addr += ASC_MC_RISC_Q_LIST_SIZE) + { + /* + * Set the current RISC Queue List's RQL_FWD and RQL_BWD pointers + * in a one word write and set the state (RQL_STATE) to free. + */ + AdvWriteWordLram(iop_base, rql_addr, ((i + 1) + ((i - 1) << 8))); + AdvWriteByteLram(iop_base, rql_addr + RQL_STATE, ASC_MC_QS_FREE); + } + + /* + * Set the Host and RISC Queue List pointers. + * + * Both sets of pointers are initialized with the same values: + * ASC_MC_RISC_Q_FIRST(0x01) and ASC_MC_RISC_Q_LAST (0xFF). + */ + AdvWriteByteLram(iop_base, ASC_MC_HOST_NEXT_READY, ASC_MC_RISC_Q_FIRST); + AdvWriteByteLram(iop_base, ASC_MC_HOST_NEXT_DONE, ASC_MC_RISC_Q_LAST); + + AdvWriteByteLram(iop_base, ASC_MC_RISC_NEXT_READY, ASC_MC_RISC_Q_FIRST); + AdvWriteByteLram(iop_base, ASC_MC_RISC_NEXT_DONE, ASC_MC_RISC_Q_LAST); + + /* + * Finally, set up the last RISC Queue List (255) with + * a NULL forward pointer. + */ + AdvWriteWordLram(iop_base, rql_addr, (ASC_MC_NULL_Q + ((i - 1) << 8))); + AdvWriteByteLram(iop_base, rql_addr + RQL_STATE, ASC_MC_QS_FREE); + + AdvWriteByteRegister(iop_base, IOPB_INTR_ENABLES, + (ADV_INTR_ENABLE_HOST_INTR | ADV_INTR_ENABLE_GLOBAL_INTR)); + + /* + * Note: Don't remove the use of a temporary variable in + * the following code, otherwise the Microsoft C compiler + * will turn the following lines into a no-op. + */ + AdvReadWordLram(iop_base, ASC_MC_CODE_BEGIN_ADDR, word); + AdvWriteWordRegister(iop_base, IOPW_PC, word); + + /* finally, finally, gentlemen, start your engine */ + AdvWriteWordRegister(iop_base, IOPW_RISC_CSR, ADV_RISC_CSR_RUN); + + return warn_code; +} + +/* + * Read the board's EEPROM configuration. Set fields in ADV_DVC_VAR and + * ADV_DVC_CFG based on the EEPROM settings. The chip is stopped while + * all of this is done. + * + * On failure set the ADV_DVC_VAR field 'err_code' and return ADV_ERROR. + * + * For a non-fatal error return a warning code. If there are no warnings + * then 0 is returned. + * + * Note: Chip is stopped on entry. + */ +ASC_INITFUNC( +STATIC int +AdvInitFromEEP(ADV_DVC_VAR *asc_dvc) +) +{ + AdvPortAddr iop_base; + ushort warn_code; + ADVEEP_CONFIG eep_config; + int i; + + iop_base = asc_dvc->iop_base; + + warn_code = 0; + + /* + * Read the board's EEPROM configuration. + * + * Set default values if a bad checksum is found. + */ + if (AdvGetEEPConfig(iop_base, &eep_config) != eep_config.check_sum) + { + warn_code |= ASC_WARN_EEPROM_CHKSUM; + + /* + * Set EEPROM default values. + */ + for (i = 0; i < sizeof(ADVEEP_CONFIG); i++) + { + *((uchar *) &eep_config + i) = + *((uchar *) &Default_EEPROM_Config + i); + } + + /* + * Assume the 6 byte board serial number that was read + * from EEPROM is correct even if the EEPROM checksum + * failed. + */ + eep_config.serial_number_word3 = + AdvReadEEPWord(iop_base, ASC_EEP_DVC_CFG_END - 1); + eep_config.serial_number_word2 = + AdvReadEEPWord(iop_base, ASC_EEP_DVC_CFG_END - 2); + eep_config.serial_number_word1 = + AdvReadEEPWord(iop_base, ASC_EEP_DVC_CFG_END - 3); + AdvSetEEPConfig(iop_base, &eep_config); + } + + /* + * Set ADV_DVC_VAR and ADV_DVC_CFG variables from the + * EEPROM configuration that was read. + * + * This is the mapping of EEPROM fields to Adv Library fields. + */ + asc_dvc->wdtr_able = eep_config.wdtr_able; + asc_dvc->sdtr_able = eep_config.sdtr_able; + asc_dvc->ultra_able = eep_config.ultra_able; + asc_dvc->tagqng_able = eep_config.tagqng_able; + asc_dvc->cfg->disc_enable = eep_config.disc_enable; + asc_dvc->max_host_qng = eep_config.max_host_qng; + asc_dvc->max_dvc_qng = eep_config.max_dvc_qng; + asc_dvc->chip_scsi_id = (eep_config.adapter_scsi_id & ADV_MAX_TID); + asc_dvc->start_motor = eep_config.start_motor; + asc_dvc->scsi_reset_wait = eep_config.scsi_reset_delay; + asc_dvc->cfg->bios_boot_wait = eep_config.bios_boot_delay; + asc_dvc->bios_ctrl = eep_config.bios_ctrl; + asc_dvc->no_scam = eep_config.scam_tolerant; + asc_dvc->cfg->serial1 = eep_config.serial_number_word1; + asc_dvc->cfg->serial2 = eep_config.serial_number_word2; + asc_dvc->cfg->serial3 = eep_config.serial_number_word3; + + /* + * Set the host maximum queuing (max. 253, min. 16) and the per device + * maximum queuing (max. 63, min. 4). + */ + if (eep_config.max_host_qng > ASC_DEF_MAX_HOST_QNG) + { + eep_config.max_host_qng = ASC_DEF_MAX_HOST_QNG; + } else if (eep_config.max_host_qng < ASC_DEF_MIN_HOST_QNG) + { + /* If the value is zero, assume it is uninitialized. */ + if (eep_config.max_host_qng == 0) + { + eep_config.max_host_qng = ASC_DEF_MAX_HOST_QNG; + } else + { + eep_config.max_host_qng = ASC_DEF_MIN_HOST_QNG; + } + } + + if (eep_config.max_dvc_qng > ASC_DEF_MAX_DVC_QNG) + { + eep_config.max_dvc_qng = ASC_DEF_MAX_DVC_QNG; + } else if (eep_config.max_dvc_qng < ASC_DEF_MIN_DVC_QNG) + { + /* If the value is zero, assume it is uninitialized. */ + if (eep_config.max_dvc_qng == 0) + { + eep_config.max_dvc_qng = ASC_DEF_MAX_DVC_QNG; + } else + { + eep_config.max_dvc_qng = ASC_DEF_MIN_DVC_QNG; + } + } + + /* + * If 'max_dvc_qng' is greater than 'max_host_qng', then + * set 'max_dvc_qng' to 'max_host_qng'. + */ + if (eep_config.max_dvc_qng > eep_config.max_host_qng) + { + eep_config.max_dvc_qng = eep_config.max_host_qng; + } + + /* + * Set ADV_DVC_VAR 'max_host_qng' and ADV_DVC_CFG 'max_dvc_qng' + * values based on possibly adjusted EEPROM values. + */ + asc_dvc->max_host_qng = eep_config.max_host_qng; + asc_dvc->max_dvc_qng = eep_config.max_dvc_qng; + + + /* + * If the EEPROM 'termination' field is set to automatic (0), then set + * the ADV_DVC_CFG 'termination' field to automatic also. + * + * If the termination is specified with a non-zero 'termination' + * value check that a legal value is set and set the ADV_DVC_CFG + * 'termination' field appropriately. + */ + if (eep_config.termination == 0) + { + asc_dvc->cfg->termination = 0; /* auto termination */ + } else + { + /* Enable manual control with low off / high off. */ + if (eep_config.termination == 1) + { + asc_dvc->cfg->termination = TERM_CTL_SEL; + + /* Enable manual control with low off / high on. */ + } else if (eep_config.termination == 2) + { + asc_dvc->cfg->termination = TERM_CTL_SEL | TERM_CTL_H; + + /* Enable manual control with low on / high on. */ + } else if (eep_config.termination == 3) + { + asc_dvc->cfg->termination = TERM_CTL_SEL | TERM_CTL_H | TERM_CTL_L; + } else + { + /* + * The EEPROM 'termination' field contains a bad value. Use + * automatic termination instead. + */ + asc_dvc->cfg->termination = 0; + warn_code |= ASC_WARN_EEPROM_TERMINATION; + } + } + + return warn_code; +} + +/* + * Read EEPROM configuration into the specified buffer. + * + * Return a checksum based on the EEPROM configuration read. + */ +ASC_INITFUNC( +STATIC ushort +AdvGetEEPConfig(AdvPortAddr iop_base, ADVEEP_CONFIG *cfg_buf) +) +{ + ushort wval, chksum; + ushort *wbuf; + int eep_addr; + + wbuf = (ushort *) cfg_buf; + chksum = 0; + + for (eep_addr = ASC_EEP_DVC_CFG_BEGIN; + eep_addr < ASC_EEP_DVC_CFG_END; + eep_addr++, wbuf++) + { + wval = AdvReadEEPWord(iop_base, eep_addr); + chksum += wval; + *wbuf = wval; + } + *wbuf = AdvReadEEPWord(iop_base, eep_addr); + wbuf++; + for (eep_addr = ASC_EEP_DVC_CTL_BEGIN; + eep_addr < ASC_EEP_MAX_WORD_ADDR; + eep_addr++, wbuf++) + { + *wbuf = AdvReadEEPWord(iop_base, eep_addr); + } + return chksum; +} + +/* + * Read the EEPROM from specified location + */ +ASC_INITFUNC( +STATIC ushort +AdvReadEEPWord(AdvPortAddr iop_base, int eep_word_addr) +) +{ + AdvWriteWordRegister(iop_base, IOPW_EE_CMD, + ASC_EEP_CMD_READ | eep_word_addr); + AdvWaitEEPCmd(iop_base); + return AdvReadWordRegister(iop_base, IOPW_EE_DATA); +} + +/* + * Wait for EEPROM command to complete + */ +ASC_INITFUNC( +STATIC void +AdvWaitEEPCmd(AdvPortAddr iop_base) +) +{ + int eep_delay_ms; + + for (eep_delay_ms = 0; eep_delay_ms < ASC_EEP_DELAY_MS; eep_delay_ms++) + { + if (AdvReadWordRegister(iop_base, IOPW_EE_CMD) & ASC_EEP_CMD_DONE) + { + break; + } + DvcSleepMilliSecond(1); + } + if ((AdvReadWordRegister(iop_base, IOPW_EE_CMD) & ASC_EEP_CMD_DONE) == 0) + { + ADV_ASSERT(0); + } + return; +} + +/* + * Write the EEPROM from 'cfg_buf'. + */ +ASC_INITFUNC( +STATIC void +AdvSetEEPConfig(AdvPortAddr iop_base, ADVEEP_CONFIG *cfg_buf) +) +{ + ushort *wbuf; + ushort addr, chksum; + + wbuf = (ushort *) cfg_buf; + chksum = 0; + + AdvWriteWordRegister(iop_base, IOPW_EE_CMD, ASC_EEP_CMD_WRITE_ABLE); + AdvWaitEEPCmd(iop_base); + + /* + * Write EEPROM from word 0 to word 15 + */ + for (addr = ASC_EEP_DVC_CFG_BEGIN; + addr < ASC_EEP_DVC_CFG_END; addr++, wbuf++) + { + chksum += *wbuf; + AdvWriteWordRegister(iop_base, IOPW_EE_DATA, *wbuf); + AdvWriteWordRegister(iop_base, IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr); + AdvWaitEEPCmd(iop_base); + DvcSleepMilliSecond(ASC_EEP_DELAY_MS); + } + + /* + * Write EEPROM checksum at word 18 + */ + AdvWriteWordRegister(iop_base, IOPW_EE_DATA, chksum); + AdvWriteWordRegister(iop_base, IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr); + AdvWaitEEPCmd(iop_base); + wbuf++; /* skip over check_sum */ + + /* + * Write EEPROM OEM name at words 19 to 26 + */ + for (addr = ASC_EEP_DVC_CTL_BEGIN; + addr < ASC_EEP_MAX_WORD_ADDR; addr++, wbuf++) + { + AdvWriteWordRegister(iop_base, IOPW_EE_DATA, *wbuf); + AdvWriteWordRegister(iop_base, IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr); + AdvWaitEEPCmd(iop_base); + } + AdvWriteWordRegister(iop_base, IOPW_EE_CMD, ASC_EEP_CMD_WRITE_DISABLE); + AdvWaitEEPCmd(iop_base); + return; +} + +/* + * This function resets the chip and SCSI bus + * + * It is up to the caller to add a delay to let the bus settle after + * calling this function. + * + * The SCSI_CFG0, SCSI_CFG1, and MEM_CFG registers are set-up in + * AdvInitAsc3550Driver(). Here when doing a write to one of these + * registers read first and then write. + * + * Note: A SCSI Bus Reset can not be done until after the EEPROM + * configuration is read to determine whether SCSI Bus Resets + * should be performed. + */ +ASC_INITFUNC( +STATIC void +AdvResetChip(ADV_DVC_VAR *asc_dvc) +) +{ + AdvPortAddr iop_base; + ushort word; + uchar byte; + + iop_base = asc_dvc->iop_base; + + /* + * Reset Chip. + */ + AdvWriteWordRegister(iop_base, IOPW_CTRL_REG, ADV_CTRL_REG_CMD_RESET); + DvcSleepMilliSecond(100); + AdvWriteWordRegister(iop_base, IOPW_CTRL_REG, ADV_CTRL_REG_CMD_WR_IO_REG); + + /* + * Initialize Chip registers. + * + * Note: Don't remove the use of a temporary variable in the following + * code, otherwise the Microsoft C compiler will turn the following lines + * into a no-op. + */ + byte = AdvReadByteRegister(iop_base, IOPB_MEM_CFG); + byte |= RAM_SZ_8KB; + AdvWriteByteRegister(iop_base, IOPB_MEM_CFG, byte); + + word = AdvReadWordRegister(iop_base, IOPW_SCSI_CFG1); + word &= ~BIG_ENDIAN; + AdvWriteWordRegister(iop_base, IOPW_SCSI_CFG1, word); + + /* + * Setting the START_CTL_EMFU 3:2 bits sets a FIFO threshold + * of 128 bytes. This register is only accessible to the host. + */ + AdvWriteByteRegister(iop_base, IOPB_DMA_CFG0, + START_CTL_EMFU | READ_CMD_MRM); +} + +/* a_advlib.c */ +/* + * Description: + * Send a SCSI request to the ASC3550 chip + * + * If there is no SG list for the request, set 'sg_entry_cnt' to 0. + * + * If 'sg_real_addr' is non-zero on entry, AscGetSGList() will not be + * called. It is assumed the caller has already initialized 'sg_real_addr'. + * + * Return: + * ADV_SUCCESS(1) - the request is in the mailbox + * ADV_BUSY(0) - total request count > 253, try later + * ADV_ERROR(-1) - invalid scsi request Q + */ +STATIC int +AdvExeScsiQueue(ADV_DVC_VAR *asc_dvc, + ADV_SCSI_REQ_Q *scsiq) +{ + if (scsiq == (ADV_SCSI_REQ_Q *) 0L) + { + /* 'scsiq' should never be NULL. */ + ADV_ASSERT(0); + return ADV_ERROR; + } + + return AdvSendScsiCmd(asc_dvc, scsiq); +} + +/* + * Reset SCSI Bus and purge all outstanding requests. + * + * Return Value: + * ADV_TRUE(1) - All requests are purged and SCSI Bus is reset. + * + * Note: Should always return ADV_TRUE. + */ +STATIC int +AdvResetSB(ADV_DVC_VAR *asc_dvc) +{ + int status; + + status = AdvSendIdleCmd(asc_dvc, (ushort) IDLE_CMD_SCSI_RESET, 0L, 0); + + AdvResetSCSIBus(asc_dvc); + + return status; +} + +/* + * Reset SCSI Bus and delay. + */ +STATIC void +AdvResetSCSIBus(ADV_DVC_VAR *asc_dvc) +{ + AdvPortAddr iop_base; + ushort scsi_ctrl; + + iop_base = asc_dvc->iop_base; + + /* + * The microcode currently sets the SCSI Bus Reset signal while + * handling the AscSendIdleCmd() IDLE_CMD_SCSI_RESET command above. + * But the SCSI Bus Reset Hold Time in the microcode is not deterministic + * (it may in fact be for less than the SCSI Spec. minimum of 25 us). + * Therefore on return the Adv Library sets the SCSI Bus Reset signal + * for ASC_SCSI_RESET_HOLD_TIME_US, which is defined to be greater + * than 25 us. + */ + scsi_ctrl = AdvReadWordRegister(iop_base, IOPW_SCSI_CTRL); + AdvWriteWordRegister(iop_base, IOPW_SCSI_CTRL, + scsi_ctrl | ADV_SCSI_CTRL_RSTOUT); + DvcDelayMicroSecond(asc_dvc, (ushort) ASC_SCSI_RESET_HOLD_TIME_US); + AdvWriteWordRegister(iop_base, IOPW_SCSI_CTRL, + scsi_ctrl & ~ADV_SCSI_CTRL_RSTOUT); + + DvcSleepMilliSecond((ulong) asc_dvc->scsi_reset_wait * 1000); +} + + +/* + * Adv Library Interrupt Service Routine + * + * This function is called by a driver's interrupt service routine. + * The function disables and re-enables interrupts. + * + * When a microcode idle command is completed, the ADV_DVC_VAR + * 'idle_cmd_done' field is set to ADV_TRUE. + * + * Note: AdvISR() can be called when interrupts are disabled or even + * when there is no hardware interrupt condition present. It will + * always check for completed idle commands and microcode requests. + * This is an important feature that shouldn't be changed because it + * allows commands to be completed from polling mode loops. + * + * Return: + * ADV_TRUE(1) - interrupt was pending + * ADV_FALSE(0) - no interrupt was pending + */ +STATIC int +AdvISR(ADV_DVC_VAR *asc_dvc) +{ + AdvPortAddr iop_base; + uchar int_stat; + ushort next_done_loc, target_bit; + int completed_q; + int flags; + ADV_SCSI_REQ_Q *scsiq; + ASC_REQ_SENSE *sense_data; + int ret; + + flags = DvcEnterCritical(); + iop_base = asc_dvc->iop_base; + + if (AdvIsIntPending(iop_base)) + { + ret = ADV_TRUE; + } else + { + ret = ADV_FALSE; + } + + /* Reading the register clears the interrupt. */ + int_stat = AdvReadByteRegister(iop_base, IOPB_INTR_STATUS_REG); + + if (int_stat & ADV_INTR_STATUS_INTRB) + { + asc_dvc->idle_cmd_done = ADV_TRUE; + } + + /* + * Notify the driver of a hardware detected SCSI Bus Reset. + */ + if (int_stat & ADV_INTR_STATUS_INTRC) + { + if (asc_dvc->sbreset_callback != 0) + { + (*(ADV_SBRESET_CALLBACK) asc_dvc->sbreset_callback)(asc_dvc); + } + } + + /* + * ASC_MC_HOST_NEXT_DONE (0x129) is actually the last completed RISC + * Queue List request. Its forward pointer (RQL_FWD) points to the + * current completed RISC Queue List request. + */ + AdvReadByteLram(iop_base, ASC_MC_HOST_NEXT_DONE, next_done_loc); + next_done_loc = ASC_MC_RISC_Q_LIST_BASE + + (next_done_loc * ASC_MC_RISC_Q_LIST_SIZE) + RQL_FWD; + + AdvReadByteLram(iop_base, next_done_loc, completed_q); + + /* Loop until all completed Q's are processed. */ + while (completed_q != ASC_MC_NULL_Q) + { + AdvWriteByteLram(iop_base, ASC_MC_HOST_NEXT_DONE, completed_q); + + next_done_loc = ASC_MC_RISC_Q_LIST_BASE + + (completed_q * ASC_MC_RISC_Q_LIST_SIZE); + + /* + * Read the ADV_SCSI_REQ_Q virtual address pointer from + * the RISC list entry. The microcode has changed the + * ADV_SCSI_REQ_Q physical address to its virtual address. + * + * Refer to comments at the end of AdvSendScsiCmd() for + * more information on the RISC list structure. + */ + { + ushort lsw, msw; + AdvReadWordLram(iop_base, next_done_loc + RQL_PHYADDR, lsw); + AdvReadWordLram(iop_base, next_done_loc + RQL_PHYADDR + 2, msw); + + scsiq = (ADV_SCSI_REQ_Q *) (((ulong) msw << 16) | lsw); + } + ADV_ASSERT(scsiq != NULL); + + target_bit = ADV_TID_TO_TIDMASK(scsiq->target_id); + + /* + * Clear request microcode control flag. + */ + scsiq->cntl = 0; + + /* + * Check Condition handling + */ + if ((scsiq->done_status == QD_WITH_ERROR) && + (scsiq->scsi_status == SS_CHK_CONDITION) && + (sense_data = (ASC_REQ_SENSE *) scsiq->vsense_addr) != 0 && + (scsiq->orig_sense_len - scsiq->sense_len) >= ASC_MIN_SENSE_LEN) + { + /* + * Command returned with a check condition and valid + * sense data. + */ + } + /* + * If the command that completed was a SCSI INQUIRY and + * LUN 0 was sent the command, then process the INQUIRY + * command information for the device. + */ + else if (scsiq->done_status == QD_NO_ERROR && + scsiq->cdb[0] == SCSICMD_Inquiry && + scsiq->target_lun == 0) + { + AdvInquiryHandling(asc_dvc, scsiq); + } + + + /* Change the RISC Queue List state to free. */ + AdvWriteByteLram(iop_base, next_done_loc + RQL_STATE, ASC_MC_QS_FREE); + + /* Get the RISC Queue List forward pointer. */ + AdvReadByteLram(iop_base, next_done_loc + RQL_FWD, completed_q); + + /* + * Notify the driver of the completed request by passing + * the ADV_SCSI_REQ_Q pointer to its callback function. + */ + ADV_ASSERT(asc_dvc->cur_host_qng > 0); + asc_dvc->cur_host_qng--; + scsiq->a_flag |= ADV_SCSIQ_DONE; + (*(ADV_ISR_CALLBACK) asc_dvc->isr_callback)(asc_dvc, scsiq); + /* + * Note: After the driver callback function is called, 'scsiq' + * can no longer be referenced. + * + * Fall through and continue processing other completed + * requests... + */ + + /* + * Disable interrupts again in case the driver inadvertently + * enabled interrupts in its callback function. + * + * The DvcEnterCritical() return value is ignored, because + * the 'flags' saved when AdvISR() was first entered will be + * used to restore the interrupt flag on exit. + */ + (void) DvcEnterCritical(); + } + DvcLeaveCritical(flags); + return ret; +} + +/* + * Send an idle command to the chip and wait for completion. + * + * Interrupts do not have to be enabled on entry. + * + * Return Values: + * ADV_TRUE - command completed successfully + * ADV_FALSE - command failed + */ +STATIC int +AdvSendIdleCmd(ADV_DVC_VAR *asc_dvc, + ushort idle_cmd, + ulong idle_cmd_parameter, + int flags) +{ + int last_int_level; + ulong i; + AdvPortAddr iop_base; + int ret; + + asc_dvc->idle_cmd_done = 0; + + last_int_level = DvcEnterCritical(); + iop_base = asc_dvc->iop_base; + + /* + * Write the idle command value after the idle command parameter + * has been written to avoid a race condition. If the order is not + * followed, the microcode may process the idle command before the + * parameters have been written to LRAM. + */ + AdvWriteDWordLram(iop_base, ASC_MC_IDLE_PARA_STAT, idle_cmd_parameter); + AdvWriteWordLram(iop_base, ASC_MC_IDLE_CMD, idle_cmd); + DvcLeaveCritical(last_int_level); + + /* + * If the 'flags' argument contains the ADV_NOWAIT flag, then + * return with success. + */ + if (flags & ADV_NOWAIT) + { + return ADV_TRUE; + } + + for (i = 0; i < SCSI_WAIT_10_SEC * SCSI_MS_PER_SEC; i++) + { + /* + * 'idle_cmd_done' is set by AdvISR(). + */ + if (asc_dvc->idle_cmd_done) + { + break; + } + DvcSleepMilliSecond(1); + + /* + * If interrupts were disabled on entry to AdvSendIdleCmd(), + * then they will still be disabled here. Call AdvISR() to + * check for the idle command completion. + */ + (void) AdvISR(asc_dvc); + } + + last_int_level = DvcEnterCritical(); + + if (asc_dvc->idle_cmd_done == ADV_FALSE) + { + ADV_ASSERT(0); /* The idle command should never timeout. */ + return ADV_FALSE; + } else + { + AdvReadWordLram(iop_base, ASC_MC_IDLE_PARA_STAT, ret); + return ret; + } +} + +/* + * Send the SCSI request block to the adapter + * + * Each of the 255 Adv Library/Microcode RISC Lists or mailboxes has the + * following structure: + * + * 0: RQL_FWD - RISC list forward pointer (1 byte) + * 1: RQL_BWD - RISC list backward pointer (1 byte) + * 2: RQL_STATE - RISC list state byte - free, ready, done, aborted (1 byte) + * 3: RQL_TID - request target id (1 byte) + * 4: RQL_PHYADDR - ADV_SCSI_REQ_Q physical pointer (4 bytes) + * + * Return: + * ADV_SUCCESS(1) - the request is in the mailbox + * ADV_BUSY(0) - total request count > 253, try later + */ +STATIC int +AdvSendScsiCmd( + ADV_DVC_VAR *asc_dvc, + ADV_SCSI_REQ_Q *scsiq) +{ + ushort next_ready_loc; + uchar next_ready_loc_fwd; + int last_int_level; + AdvPortAddr iop_base; + long req_size; + ulong q_phy_addr; + + /* + * The ADV_SCSI_REQ_Q 'target_id' field should never be equal + * to the host adapter ID or exceed ADV_MAX_TID. + */ + if (scsiq->target_id == asc_dvc->chip_scsi_id || + scsiq->target_id > ADV_MAX_TID) + { + scsiq->host_status = QHSTA_M_INVALID_DEVICE; + scsiq->done_status = QD_WITH_ERROR; + return ADV_ERROR; + } + + iop_base = asc_dvc->iop_base; + + last_int_level = DvcEnterCritical(); + + if (asc_dvc->cur_host_qng >= asc_dvc->max_host_qng) + { + DvcLeaveCritical(last_int_level); + return ADV_BUSY; + } else + { + ADV_ASSERT(asc_dvc->cur_host_qng < ASC_MC_RISC_Q_TOTAL_CNT); + asc_dvc->cur_host_qng++; + } + + /* + * Clear the ADV_SCSI_REQ_Q done flag. + */ + scsiq->a_flag &= ~ADV_SCSIQ_DONE; + + /* + * Save the original sense buffer length. + * + * After the request completes 'sense_len' will be set to the residual + * byte count of the Auto-Request Sense if a command returns CHECK + * CONDITION and the Sense Data is valid indicated by 'host_status' not + * being set to QHSTA_M_AUTO_REQ_SENSE_FAIL. To determine the valid + * Sense Data Length subtract 'sense_len' from 'orig_sense_len'. + */ + scsiq->orig_sense_len = scsiq->sense_len; + + AdvReadByteLram(iop_base, ASC_MC_HOST_NEXT_READY, next_ready_loc); + next_ready_loc = ASC_MC_RISC_Q_LIST_BASE + + (next_ready_loc * ASC_MC_RISC_Q_LIST_SIZE); + + /* + * Write the physical address of the Q to the mailbox. + * We need to skip the first four bytes, because the microcode + * uses them internally for linking Q's together. + */ + req_size = sizeof(ADV_SCSI_REQ_Q); + q_phy_addr = DvcGetPhyAddr(asc_dvc, scsiq, + (uchar *) scsiq, &req_size, + ADV_IS_SCSIQ_FLAG); + ADV_ASSERT(ADV_DWALIGN(q_phy_addr) == q_phy_addr); + ADV_ASSERT(req_size >= sizeof(ADV_SCSI_REQ_Q)); + + scsiq->scsiq_ptr = (ADV_SCSI_REQ_Q *) scsiq; + + /* + * The RISC list structure, which 'next_ready_loc' is a pointer + * to in microcode LRAM, has the format detailed in the comment + * header for this function. + * + * Write the ADV_SCSI_REQ_Q physical pointer to 'next_ready_loc' request. + */ + AdvWriteDWordLram(iop_base, next_ready_loc + RQL_PHYADDR, q_phy_addr); + + /* Write target_id to 'next_ready_loc' request. */ + AdvWriteByteLram(iop_base, next_ready_loc + RQL_TID, scsiq->target_id); + + /* + * Set the ASC_MC_HOST_NEXT_READY (0x128) microcode variable to + * the 'next_ready_loc' request forward pointer. + * + * Do this *before* changing the 'next_ready_loc' queue to QS_READY. + * After the state is changed to QS_READY 'RQL_FWD' will be changed + * by the microcode. + * + * NOTE: The temporary variable 'next_ready_loc_fwd' is required to + * prevent some compilers from optimizing out 'AdvReadByteLram()' if + * it were used as the 3rd argument to 'AdvWriteByteLram()'. + */ + AdvReadByteLram(iop_base, next_ready_loc + RQL_FWD, next_ready_loc_fwd); + AdvWriteByteLram(iop_base, ASC_MC_HOST_NEXT_READY, next_ready_loc_fwd); + + /* + * Change the state of 'next_ready_loc' request from QS_FREE to + * QS_READY which will cause the microcode to pick it up and + * execute it. + * + * Can't reference 'next_ready_loc' after changing the request + * state to QS_READY. The microcode now owns the request. + */ + AdvWriteByteLram(iop_base, next_ready_loc + RQL_STATE, ASC_MC_QS_READY); + + DvcLeaveCritical(last_int_level); + return ADV_SUCCESS; +} + +/* + * Inquiry Information Byte 7 Handling + * + * Handle SCSI Inquiry Command information for a device by setting + * microcode operating variables that affect WDTR, SDTR, and Tag + * Queuing. + */ +STATIC void +AdvInquiryHandling( + ADV_DVC_VAR *asc_dvc, + ADV_SCSI_REQ_Q *scsiq) +{ + AdvPortAddr iop_base; + uchar tid; + ASC_SCSI_INQUIRY *inq; + ushort tidmask; + ushort cfg_word; + + /* + * AdvInquiryHandling() requires up to INQUIRY information Byte 7 + * to be available. + * + * If less than 8 bytes of INQUIRY information were requested or less + * than 8 bytes were transferred, then return. cdb[4] is the request + * length and the ADV_SCSI_REQ_Q 'data_cnt' field is set by the + * microcode to the transfer residual count. + */ + if (scsiq->cdb[4] < 8 || (scsiq->cdb[4] - scsiq->data_cnt) < 8) + { + return; + } + + iop_base = asc_dvc->iop_base; + tid = scsiq->target_id; + inq = (ASC_SCSI_INQUIRY *) scsiq->vdata_addr; + + /* + * WDTR, SDTR, and Tag Queuing cannot be enabled for old devices. + */ + if (inq->byte3.rsp_data_fmt < 2 && inq->byte2.ansi_apr_ver < 2) + { + return; + } else + { + /* + * INQUIRY Byte 7 Handling + * + * Use a device's INQUIRY byte 7 to determine whether it + * supports WDTR, SDTR, and Tag Queuing. If the feature + * is enabled in the EEPROM and the device supports the + * feature, then enable it in the microcode. + */ + + tidmask = ADV_TID_TO_TIDMASK(tid); + + /* + * Wide Transfers + * + * If the EEPROM enabled WDTR for the device and the device + * supports wide bus (16 bit) transfers, then turn on the + * device's 'wdtr_able' bit and write the new value to the + * microcode. + */ + if ((asc_dvc->wdtr_able & tidmask) && inq->byte7.WBus16) + { + AdvReadWordLram(iop_base, ASC_MC_WDTR_ABLE, cfg_word); + if ((cfg_word & tidmask) == 0) + { + cfg_word |= tidmask; + AdvWriteWordLram(iop_base, ASC_MC_WDTR_ABLE, cfg_word); + + /* + * Clear the microcode "WDTR negotiation" done indicator + * for the target to cause it to negotiate with the new + * setting set above. + */ + AdvReadWordLram(iop_base, ASC_MC_WDTR_DONE, cfg_word); + cfg_word &= ~tidmask; + AdvWriteWordLram(iop_base, ASC_MC_WDTR_DONE, cfg_word); + } + } + + /* + * Synchronous Transfers + * + * If the EEPROM enabled SDTR for the device and the device + * supports synchronous transfers, then turn on the device's + * 'sdtr_able' bit. Write the new value to the microcode. + */ + if ((asc_dvc->sdtr_able & tidmask) && inq->byte7.Sync) + { + AdvReadWordLram(iop_base, ASC_MC_SDTR_ABLE, cfg_word); + if ((cfg_word & tidmask) == 0) + { + cfg_word |= tidmask; + AdvWriteWordLram(iop_base, ASC_MC_SDTR_ABLE, cfg_word); + + /* + * Clear the microcode "SDTR negotiation" done indicator + * for the target to cause it to negotiate with the new + * setting set above. + */ + AdvReadWordLram(iop_base, ASC_MC_SDTR_DONE, cfg_word); + cfg_word &= ~tidmask; + AdvWriteWordLram(iop_base, ASC_MC_SDTR_DONE, cfg_word); + } + } + + /* + * If the EEPROM enabled Tag Queuing for device and the + * device supports Tag Queuing, then turn on the device's + * 'tagqng_enable' bit in the microcode and set the microcode + * maximum command count to the ADV_DVC_VAR 'max_dvc_qng' + * value. + * + * Tag Queuing is disabled for the BIOS which runs in polled + * mode and would see no benefit from Tag Queuing. Also by + * disabling Tag Queuing in the BIOS devices with Tag Queuing + * bugs will at least work with the BIOS. + */ + if ((asc_dvc->tagqng_able & tidmask) && inq->byte7.CmdQue) + { + AdvReadWordLram(iop_base, ASC_MC_TAGQNG_ABLE, cfg_word); + cfg_word |= tidmask; + AdvWriteWordLram(iop_base, ASC_MC_TAGQNG_ABLE, cfg_word); + AdvWriteByteLram(iop_base, ASC_MC_NUMBER_OF_MAX_CMD + tid, + asc_dvc->max_dvc_qng); + } + } +} diff --git a/linux/src/drivers/scsi/advansys.h b/linux/src/drivers/scsi/advansys.h new file mode 100644 index 00000000..72e8aefe --- /dev/null +++ b/linux/src/drivers/scsi/advansys.h @@ -0,0 +1,174 @@ +/* $Id: advansys.h,v 1.1 1999/04/26 05:54:08 tb Exp $ */ + +/* + * advansys.h - Linux Host Driver for AdvanSys SCSI Adapters + * + * Copyright (c) 1995-1998 Advanced System Products, Inc. + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that redistributions of source + * code retain the above copyright notice and this comment without + * modification. + * + * There is an AdvanSys Linux WWW page at: + * http://www.advansys.com/linux.html + * + * The latest version of the AdvanSys driver is available at: + * ftp://ftp.advansys.com/pub/linux + * + * Please send questions, comments, bug reports to: + * bobf@advansys.com (Bob Frey) + */ + +#ifndef _ADVANSYS_H +#define _ADVANSYS_H + +/* Convert Linux Version, Patch-level, Sub-level to LINUX_VERSION_CODE. */ +#define ASC_LINUX_VERSION(V, P, S) (((V) * 65536) + ((P) * 256) + (S)) + +#ifndef LINUX_VERSION_CODE +#include <linux/version.h> +#endif /* LINUX_VERSION_CODE */ + +/* + * Scsi_Host_Template function prototypes. + */ +int advansys_detect(Scsi_Host_Template *); +int advansys_release(struct Scsi_Host *); +const char *advansys_info(struct Scsi_Host *); +int advansys_command(Scsi_Cmnd *); +int advansys_queuecommand(Scsi_Cmnd *, void (* done)(Scsi_Cmnd *)); +int advansys_abort(Scsi_Cmnd *); +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,89) +int advansys_reset(Scsi_Cmnd *); +#else /* version >= v1.3.89 */ +int advansys_reset(Scsi_Cmnd *, unsigned int); +#endif /* version >= v1.3.89 */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) +int advansys_biosparam(Disk *, int, int[]); +#else /* version >= v1.3.0 */ +int advansys_biosparam(Disk *, kdev_t, int[]); +extern struct proc_dir_entry proc_scsi_advansys; +int advansys_proc_info(char *, char **, off_t, int, int, int); +#endif /* version >= v1.3.0 */ + +/* init/main.c setup function */ +void advansys_setup(char *, int *); + +/* + * AdvanSys Host Driver Scsi_Host_Template (struct SHT) from hosts.h. + */ +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,0) +#define ADVANSYS { \ + NULL, /* struct SHT *next */ \ + NULL, /* int *usage_count */ \ + "advansys", /* char *name */ \ + advansys_detect, /* int (*detect)(struct SHT *) */ \ + advansys_release, /* int (*release)(struct Scsi_Host *) */ \ + advansys_info, /* const char *(*info)(struct Scsi_Host *) */ \ + advansys_command, /* int (*command)(Scsi_Cmnd *) */ \ + advansys_queuecommand, \ + /* int (*queuecommand)(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)) */ \ + advansys_abort, /* int (*abort)(Scsi_Cmnd *) */ \ + advansys_reset, /* int (*reset)(Scsi_Cmnd *) */ \ + NULL, /* int (*slave_attach)(int, int) */ \ + advansys_biosparam, /* int (* bios_param)(Disk *, int, int []) */ \ + /* \ + * The following fields are set per adapter in advansys_detect(). \ + */ \ + 0, /* int can_queue */ \ + 0, /* int this_id */ \ + 0, /* short unsigned int sg_tablesize */ \ + 0, /* short cmd_per_lun */ \ + 0, /* unsigned char present */ \ + /* \ + * Because the driver may control an ISA adapter 'unchecked_isa_dma' \ + * must be set. The flag will be cleared in advansys_detect for non-ISA \ + * adapters. Refer to the comment in scsi_module.c for more information. \ + */ \ + 1, /* unsigned unchecked_isa_dma:1 */ \ + /* \ + * All adapters controlled by this driver are capable of large \ + * scatter-gather lists. According to the mid-level SCSI documentation \ + * this obviates any performance gain provided by setting \ + * 'use_clustering'. But empirically while CPU utilization is increased \ + * by enabling clustering, I/O throughput increases as well. \ + */ \ + ENABLE_CLUSTERING, /* unsigned use_clustering:1 */ \ +} +#elif LINUX_VERSION_CODE < ASC_LINUX_VERSION(2,1,75) +#define ADVANSYS { \ + NULL, /* struct SHT *next */ \ + NULL, \ + /* version < v2.1.23 long *usage_count */ \ + /* version >= v2.1.23 struct module * */ \ + &proc_scsi_advansys, /* struct proc_dir_entry *proc_dir */ \ + advansys_proc_info, \ + /* int (*proc_info)(char *, char **, off_t, int, int, int) */ \ + "advansys", /* const char *name */ \ + advansys_detect, /* int (*detect)(struct SHT *) */ \ + advansys_release, /* int (*release)(struct Scsi_Host *) */ \ + advansys_info, /* const char *(*info)(struct Scsi_Host *) */ \ + advansys_command, /* int (*command)(Scsi_Cmnd *) */ \ + advansys_queuecommand, \ + /* int (*queuecommand)(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)) */ \ + advansys_abort, /* int (*abort)(Scsi_Cmnd *) */ \ + advansys_reset, \ + /* version < v1.3.89 int (*reset)(Scsi_Cmnd *) */ \ + /* version >= v1.3.89 int (*reset)(Scsi_Cmnd *, unsigned int) */ \ + NULL, /* int (*slave_attach)(int, int) */ \ + advansys_biosparam, /* int (* bios_param)(Disk *, kdev_t, int []) */ \ + /* \ + * The following fields are set per adapter in advansys_detect(). \ + */ \ + 0, /* int can_queue */ \ + 0, /* int this_id */ \ + 0, /* short unsigned int sg_tablesize */ \ + 0, /* short cmd_per_lun */ \ + 0, /* unsigned char present */ \ + /* \ + * Because the driver may control an ISA adapter 'unchecked_isa_dma' \ + * must be set. The flag will be cleared in advansys_detect for non-ISA \ + * adapters. Refer to the comment in scsi_module.c for more information. \ + */ \ + 1, /* unsigned unchecked_isa_dma:1 */ \ + /* \ + * All adapters controlled by this driver are capable of large \ + * scatter-gather lists. According to the mid-level SCSI documentation \ + * this obviates any performance gain provided by setting \ + * 'use_clustering'. But empirically while CPU utilization is increased \ + * by enabling clustering, I/O throughput increases as well. \ + */ \ + ENABLE_CLUSTERING, /* unsigned use_clustering:1 */ \ +} +#else /* version >= v2.1.75 */ +#define ADVANSYS { \ + proc_dir: &proc_scsi_advansys, \ + proc_info: advansys_proc_info, \ + name: "advansys", \ + detect: advansys_detect, \ + release: advansys_release, \ + info: advansys_info, \ + command: advansys_command, \ + queuecommand: advansys_queuecommand, \ + abort: advansys_abort, \ + reset: advansys_reset, \ + bios_param: advansys_biosparam, \ + /* \ + * Because the driver may control an ISA adapter 'unchecked_isa_dma' \ + * must be set. The flag will be cleared in advansys_detect for non-ISA \ + * adapters. Refer to the comment in scsi_module.c for more information. \ + */ \ + unchecked_isa_dma: 1, \ + /* \ + * All adapters controlled by this driver are capable of large \ + * scatter-gather lists. According to the mid-level SCSI documentation \ + * this obviates any performance gain provided by setting \ + * 'use_clustering'. But empirically while CPU utilization is increased \ + * by enabling clustering, I/O throughput increases as well. \ + */ \ + use_clustering: ENABLE_CLUSTERING, \ +} +#endif /* version >= v2.1.75 */ +#endif /* _ADVANSYS_H */ diff --git a/linux/src/drivers/scsi/aha152x.c b/linux/src/drivers/scsi/aha152x.c new file mode 100644 index 00000000..e7c344d0 --- /dev/null +++ b/linux/src/drivers/scsi/aha152x.c @@ -0,0 +1,3277 @@ +/* aha152x.c -- Adaptec AHA-152x driver + * Author: Jürgen E. Fischer, fischer@et-inf.fho-emden.de + * Copyright 1993, 1994, 1995, 1996 Jürgen E. Fischer + * + * + * This driver is based on + * fdomain.c -- Future Domain TMC-16x0 driver + * which is + * Copyright 1992, 1993 Rickard E. Faith (faith@cs.unc.edu) + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * + * $Id: aha152x.c,v 1.1 1999/04/26 05:54:09 tb Exp $ + * + * $Log: aha152x.c,v $ + * Revision 1.18 1996/09/07 20:10:40 fischer + * - fixed can_queue handling (multiple outstanding commands working again) + * + * Revision 1.17 1996/08/17 16:05:14 fischer + * - biosparam improved + * - interrupt verification + * - updated documentation + * - cleanups + * + * Revision 1.16 1996/06/09 00:04:56 root + * - added configuration symbols for insmod (aha152x/aha152x1) + * + * Revision 1.15 1996/04/30 14:52:06 fischer + * - proc info fixed + * - support for extended translation for >1GB disks + * + * Revision 1.14 1996/01/17 15:11:20 fischer + * - fixed lockup in MESSAGE IN phase after reconnection + * + * Revision 1.13 1996/01/09 02:15:53 fischer + * - some cleanups + * - moved request_irq behind controller initialization + * (to avoid spurious interrupts) + * + * Revision 1.12 1995/12/16 12:26:07 fischer + * - barrier()s added + * - configurable RESET delay added + * + * Revision 1.11 1995/12/06 21:18:35 fischer + * - some minor updates + * + * Revision 1.10 1995/07/22 19:18:45 fischer + * - support for 2 controllers + * - started synchronous data transfers (not working yet) + * + * Revision 1.9 1995/03/18 09:20:24 root + * - patches for PCMCIA and modules + * + * Revision 1.8 1995/01/21 22:07:19 root + * - snarf_region => request_region + * - aha152x_intr interface change + * + * Revision 1.7 1995/01/02 23:19:36 root + * - updated COMMAND_SIZE to cmd_len + * - changed sti() to restore_flags() + * - fixed some #ifdef which generated warnings + * + * Revision 1.6 1994/11/24 20:35:27 root + * - problem with odd number of bytes in fifo fixed + * + * Revision 1.5 1994/10/30 14:39:56 root + * - abort code fixed + * - debugging improved + * + * Revision 1.4 1994/09/12 11:33:01 root + * - irqaction to request_irq + * - abortion updated + * + * Revision 1.3 1994/08/04 13:53:05 root + * - updates for mid-level-driver changes + * - accept unexpected BUSFREE phase as error condition + * - parity check now configurable + * + * Revision 1.2 1994/07/03 12:56:36 root + * - cleaned up debugging code + * - more tweaking on reset delays + * - updated abort/reset code (pretty untested...) + * + * Revision 1.1 1994/05/28 21:18:49 root + * - update for mid-level interface change (abort-reset) + * - delays after resets adjusted for some slow devices + * + * Revision 1.0 1994/03/25 12:52:00 root + * - Fixed "more data than expected" problem + * - added new BIOS signatures + * + * Revision 0.102 1994/01/31 20:44:12 root + * - minor changes in insw/outsw handling + * + * Revision 0.101 1993/12/13 01:16:27 root + * - fixed STATUS phase (non-GOOD stati were dropped sometimes; + * fixes problems with CD-ROM sector size detection & media change) + * + * Revision 0.100 1993/12/10 16:58:47 root + * - fix for unsuccessful selections in case of non-continuous id assignments + * on the scsi bus. + * + * Revision 0.99 1993/10/24 16:19:59 root + * - fixed DATA IN (rare read errors gone) + * + * Revision 0.98 1993/10/17 12:54:44 root + * - fixed some recent fixes (shame on me) + * - moved initialization of scratch area to aha152x_queue + * + * Revision 0.97 1993/10/09 18:53:53 root + * - DATA IN fixed. Rarely left data in the fifo. + * + * Revision 0.96 1993/10/03 00:53:59 root + * - minor changes on DATA IN + * + * Revision 0.95 1993/09/24 10:36:01 root + * - change handling of MSGI after reselection + * - fixed sti/cli + * - minor changes + * + * Revision 0.94 1993/09/18 14:08:22 root + * - fixed bug in multiple outstanding command code + * - changed detection + * - support for kernel command line configuration + * - reset corrected + * - changed message handling + * + * Revision 0.93 1993/09/15 20:41:19 root + * - fixed bugs with multiple outstanding commands + * + * Revision 0.92 1993/09/13 02:46:33 root + * - multiple outstanding commands work (no problems with IBM drive) + * + * Revision 0.91 1993/09/12 20:51:46 root + * added multiple outstanding commands + * (some problem with this $%&? IBM device remain) + * + * Revision 0.9 1993/09/12 11:11:22 root + * - corrected auto-configuration + * - changed the auto-configuration (added some '#define's) + * - added support for dis-/reconnection + * + * Revision 0.8 1993/09/06 23:09:39 root + * - added support for the drive activity light + * - minor changes + * + * Revision 0.7 1993/09/05 14:30:15 root + * - improved phase detection + * - now using the new snarf_region code of 0.99pl13 + * + * Revision 0.6 1993/09/02 11:01:38 root + * first public release; added some signatures and biosparam() + * + * Revision 0.5 1993/08/30 10:23:30 root + * fixed timing problems with my IBM drive + * + * Revision 0.4 1993/08/29 14:06:52 root + * fixed some problems with timeouts due incomplete commands + * + * Revision 0.3 1993/08/28 15:55:03 root + * writing data works too. mounted and worked on a dos partition + * + * Revision 0.2 1993/08/27 22:42:07 root + * reading data works. Mounted a msdos partition. + * + * Revision 0.1 1993/08/25 13:38:30 root + * first "damn thing doesn't work" version + * + * Revision 0.0 1993/08/14 19:54:25 root + * empty function bodies; detect() works. + * + * + ************************************************************************** + + + + DESCRIPTION: + + This is the Linux low-level SCSI driver for Adaptec AHA-1520/1522 SCSI + host adapters. + + + CONFIGURATION ARGUMENTS: + + IOPORT base io address (0x340/0x140) + IRQ interrupt level (9-12; default 11) + SCSI_ID scsi id of controller (0-7; default 7) + RECONNECT allow targets to disconnect from the bus (0/1; default 1 [on]) + PARITY enable parity checking (0/1; default 1 [on]) + SYNCHRONOUS enable synchronous transfers (0/1; default 0 [off]) + (NOT WORKING YET) + DELAY: bus reset delay (default 100) + EXT_TRANS: enable extended translation (0/1: default 0 [off]) + (see NOTES below) + + COMPILE TIME CONFIGURATION (put into AHA152X in drivers/scsi/Makefile): + + -DAUTOCONF + use configuration the controller reports (AHA-152x only) + + -DSKIP_BIOSTEST + Don't test for BIOS signature (AHA-1510 or disabled BIOS) + + -DSETUP0="{ IOPORT, IRQ, SCSI_ID, RECONNECT, PARITY, SYNCHRONOUS, DELAY, EXT_TRANS }" + override for the first controller + + -DSETUP1="{ IOPORT, IRQ, SCSI_ID, RECONNECT, PARITY, SYNCHRONOUS, DELAY, EXT_TRANS }" + override for the second controller + + + LILO COMMAND LINE OPTIONS: + + aha152x=<IOPORT>[,<IRQ>[,<SCSI-ID>[,<RECONNECT>[,<PARITY>[,<SYNCHRONOUS>[,<DELAY> [,<EXT_TRANS]]]]]]] + + The normal configuration can be overridden by specifying a command line. + When you do this, the BIOS test is skipped. Entered values have to be + valid (known). Don't use values that aren't supported under normal + operation. If you think that you need other values: contact me. + For two controllers use the aha152x statement twice. + + + SYMBOLS FOR MODULE CONFIGURATION: + + aha152x=IOPORT,IRQ,SCSI_ID,RECONNECT,PARITY,SYNCHRONOUS,DELAY,EXT_TRANS + configuration override of first controller + + + aha152x1=IOPORT,IRQ,SCSI_ID,RECONNECT,PARITY,SYNCHRONOUS,DELAY,EXT_TRANS + configuration override of second controller + + + NOTES ON EXT_TRANS: + + SCSI uses block numbers to address blocks/sectors on a device. + The BIOS uses a cylinder/head/sector addressing scheme (C/H/S) + scheme instead. DOS expects a BIOS or driver that understands this + C/H/S addressing. + + The number of cylinders/heads/sectors is called geometry and is required + as base for requests in C/H/S adressing. SCSI only knows about the + total capacity of disks in blocks (sectors). + + Therefore the SCSI BIOS/DOS driver has to calculate a logical/virtual + geometry just to be able to support that addressing scheme. The geometry + returned by the SCSI BIOS is a pure calculation and has nothing to + do with the real/physical geometry of the disk (which is usually + irrelevant anyway). + + Basically this has no impact at all on Linux, because it also uses block + instead of C/H/S addressing. Unfortunately C/H/S addressing is also used + in the partition table and therefore every operating system has to know + the right geometry to be able to interpret it. + + Moreover there are certain limitations to the C/H/S addressing scheme, + namely the address space is limited to upto 255 heads, upto 63 sectors + and a maximum of 1023 cylinders. + + The AHA-1522 BIOS calculates the geometry by fixing the number of heads + to 64, the number of sectors to 32 and by calculating the number of + cylinders by dividing the capacity reported by the disk by 64*32 (1 MB). + This is considered to be the default translation. + + With respect to the limit of 1023 cylinders using C/H/S you can only + address the first GB of your disk in the partition table. Therefore + BIOSes of some newer controllers based on the AIC-6260/6360 support + extended translation. This means that the BIOS uses 255 for heads, + 63 for sectors and then divides the capacity of the disk by 255*63 + (about 8 MB), as soon it sees a disk greater than 1 GB. That results + in a maximum of about 8 GB adressable diskspace in the partition table + (but there are already bigger disks out there today). + + To make it even more complicated the translation mode might/might + not be configurable in certain BIOS setups. + + This driver does some more or less failsafe guessing to get the + geometry right in most cases: + + - for disks<1GB: use default translation (C/32/64) + - for disks>1GB: + - take current geometry from the partition table + (using scsicam_bios_param and accept only `valid' geometries, + ie. either (C/32/64) or (C/63/255)). This can be extended + translation even if it's not enabled in the driver. + - if that fails, take extended translation if enabled by override, + kernel or module parameter, otherwise take default translation and + ask the user for verification. This might on not yet partitioned + disks or + + + REFERENCES USED: + + "AIC-6260 SCSI Chip Specification", Adaptec Corporation. + + "SCSI COMPUTER SYSTEM INTERFACE - 2 (SCSI-2)", X3T9.2/86-109 rev. 10h + + "Writing a SCSI device driver for Linux", Rik Faith (faith@cs.unc.edu) + + "Kernel Hacker's Guide", Michael K. Johnson (johnsonm@sunsite.unc.edu) + + "Adaptec 1520/1522 User's Guide", Adaptec Corporation. + + Michael K. Johnson (johnsonm@sunsite.unc.edu) + + Drew Eckhardt (drew@cs.colorado.edu) + + Eric Youngdale (ericy@cais.com) + + special thanks to Eric Youngdale for the free(!) supplying the + documentation on the chip. + + **************************************************************************/ + +#ifdef PCMCIA +#define MODULE +#endif + +#include <linux/module.h> + +#ifdef PCMCIA +#undef MODULE +#endif + +#include <linux/sched.h> +#include <asm/io.h> +#include <linux/blk.h> +#include "scsi.h" +#include "sd.h" +#include "hosts.h" +#include "constants.h" +#include <asm/system.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> + +#include "aha152x.h" +#include <linux/stat.h> + +#include <scsi/scsicam.h> + +struct proc_dir_entry proc_scsi_aha152x = { + PROC_SCSI_AHA152X, 7, "aha152x", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +/* DEFINES */ + +/* For PCMCIA cards, always use AUTOCONF */ +#if defined(PCMCIA) || defined(MODULE) +#if !defined(AUTOCONF) +#define AUTOCONF +#endif +#endif + +#if !defined(AUTOCONF) && !defined(SETUP0) +#error define AUTOCONF or SETUP0 +#endif + +#if defined(DEBUG_AHA152X) + +#undef SKIP_PORTS /* don't display ports */ + +#undef DEBUG_QUEUE /* debug queue() */ +#undef DEBUG_RESET /* debug reset() */ +#undef DEBUG_INTR /* debug intr() */ +#undef DEBUG_SELECTION /* debug selection part in intr() */ +#undef DEBUG_MSGO /* debug message out phase in intr() */ +#undef DEBUG_MSGI /* debug message in phase in intr() */ +#undef DEBUG_STATUS /* debug status phase in intr() */ +#undef DEBUG_CMD /* debug command phase in intr() */ +#undef DEBUG_DATAI /* debug data in phase in intr() */ +#undef DEBUG_DATAO /* debug data out phase in intr() */ +#undef DEBUG_ABORT /* debug abort() */ +#undef DEBUG_DONE /* debug done() */ +#undef DEBUG_BIOSPARAM /* debug biosparam() */ + +#undef DEBUG_RACE /* debug race conditions */ +#undef DEBUG_PHASES /* debug phases (useful to trace) */ +#undef DEBUG_QUEUES /* debug reselection */ + +/* recently used for debugging */ +#if 0 +#endif + +#define DEBUG_SELECTION +#define DEBUG_PHASES +#define DEBUG_RESET +#define DEBUG_ABORT + +#define DEBUG_DEFAULT (debug_reset|debug_abort) + +#endif + +/* END OF DEFINES */ + +extern unsigned long loops_per_sec; + +#define DELAY_DEFAULT 100 + +/* some additional "phases" for getphase() */ +#define P_BUSFREE 1 +#define P_PARITY 2 + +/* possible irq range */ +#define IRQ_MIN 9 +#define IRQ_MAX 12 +#define IRQS IRQ_MAX-IRQ_MIN+1 + +enum { + not_issued = 0x0001, + in_selection = 0x0002, + disconnected = 0x0004, + aborted = 0x0008, + sent_ident = 0x0010, + in_other = 0x0020, + in_sync = 0x0040, + sync_ok = 0x0080, +}; + +#if defined(MODULE) +#if defined(DEBUG_AHA152X) +int aha152x[] = { 0, 11, 7, 1, 1, 0, DELAY_DEFAULT, 0, DEBUG_DEFAULT }; +int aha152x1[] = { 0, 11, 7, 1, 1, 0, DELAY_DEFAULT, 0, DEBUG_DEFAULT }; +#else +int aha152x[] = { 0, 11, 7, 1, 1, 0, DELAY_DEFAULT, 0 }; +int aha152x1[] = { 0, 11, 7, 1, 1, 0, DELAY_DEFAULT, 0 }; +#endif +#endif + +/* set by aha152x_setup according to the command line */ +static int setup_count=0; +static struct aha152x_setup { + int io_port; + int irq; + int scsiid; + int reconnect; + int parity; + int synchronous; + int delay; + int ext_trans; +#ifdef DEBUG_AHA152X + int debug; +#endif + char *conf; +} setup[2]; + +static struct Scsi_Host *aha152x_host[IRQS]; + +#define HOSTDATA(shpnt) ((struct aha152x_hostdata *) &shpnt->hostdata) +#define CURRENT_SC (HOSTDATA(shpnt)->current_SC) +#define ISSUE_SC (HOSTDATA(shpnt)->issue_SC) +#define DISCONNECTED_SC (HOSTDATA(shpnt)->disconnected_SC) +#define DELAY (HOSTDATA(shpnt)->delay) +#define EXT_TRANS (HOSTDATA(shpnt)->ext_trans) +#define SYNCRATE (HOSTDATA(shpnt)->syncrate[CURRENT_SC->target]) +#define MSG(i) (HOSTDATA(shpnt)->message[i]) +#define MSGLEN (HOSTDATA(shpnt)->message_len) +#define ADDMSG(x) (MSG(MSGLEN++)=x) + +struct aha152x_hostdata { + Scsi_Cmnd *issue_SC; + Scsi_Cmnd *current_SC; + Scsi_Cmnd *disconnected_SC; + int aborting; + int abortion_complete; + int abort_result; + int commands; + + int reconnect; + int parity; + int synchronous; + int delay; + int ext_trans; + + int swint; + + unsigned char syncrate[8]; + + unsigned char message[256]; + int message_len; + +#ifdef DEBUG_AHA152X + int debug; +#endif +}; + +void aha152x_intr(int irq, void *dev_id, struct pt_regs *); +void aha152x_done(struct Scsi_Host *shpnt, int error); +void aha152x_setup(char *str, int *ints); +int aha152x_checksetup(struct aha152x_setup *setup); + +static void aha152x_reset_ports(struct Scsi_Host *shpnt); +static void aha152x_panic(struct Scsi_Host *shpnt, char *msg); + +static void disp_ports(struct Scsi_Host *shpnt); +static void show_command(Scsi_Cmnd *ptr); +static void show_queues(struct Scsi_Host *shpnt); +static void disp_enintr(struct Scsi_Host *shpnt); + +#if defined(DEBUG_RACE) +static void enter_driver(const char *); +static void leave_driver(const char *); +#endif + +/* possible i/o addresses for the AIC-6260 */ +static unsigned short ports[] = +{ + 0x340, /* default first */ + 0x140 +}; +#define PORT_COUNT (sizeof(ports) / sizeof(unsigned short)) + +#if !defined(SKIP_BIOSTEST) +/* possible locations for the Adaptec BIOS */ +static void *addresses[] = +{ + (void *) 0xdc000, /* default first */ + (void *) 0xc8000, + (void *) 0xcc000, + (void *) 0xd0000, + (void *) 0xd4000, + (void *) 0xd8000, + (void *) 0xe0000, + (void *) 0xeb800, /* VTech Platinum SMP */ + (void *) 0xf0000, +}; +#define ADDRESS_COUNT (sizeof(addresses) / sizeof(void *)) + +/* signatures for various AIC-6[23]60 based controllers. + The point in detecting signatures is to avoid useless and maybe + harmful probes on ports. I'm not sure that all listed boards pass + auto-configuration. For those which fail the BIOS signature is + obsolete, because user intervention to supply the configuration is + needed anyway. May be an information whether or not the BIOS supports + extended translation could be also useful here. */ +static struct signature { + char *signature; + int sig_offset; + int sig_length; +} signatures[] = +{ + { "Adaptec AHA-1520 BIOS", 0x102e, 21 }, /* Adaptec 152x */ + { "Adaptec AHA-1520B", 0x0b, 17 }, /* Adaptec 152x rev B */ + { "Adaptec AHA-1520B/1522B", 0x3e20, 23 }, /* Adaptec 1520B/1522B */ + { "Adaptec ASW-B626 BIOS", 0x1029, 21 }, /* on-board controller */ + { "Adaptec BIOS: ASW-B626", 0x0f, 22 }, /* on-board controller */ + { "Adaptec ASW-B626 S2", 0x2e6c, 19 }, /* on-board controller */ + { "Adaptec BIOS:AIC-6360", 0xc, 21 }, /* on-board controller */ + { "ScsiPro SP-360 BIOS", 0x2873, 19 }, /* ScsiPro-Controller */ + { "GA-400 LOCAL BUS SCSI BIOS", 0x102e, 26 }, /* Gigabyte Local-Bus-SCSI */ + { "Adaptec BIOS:AVA-282X", 0xc, 21 }, /* Adaptec 282x */ + { "Adaptec IBM Dock II SCSI", 0x2edd, 24 }, /* IBM Thinkpad Dock II */ + { "Adaptec BIOS:AHA-1532P", 0x1c, 22 }, /* IBM Thinkpad Dock II SCSI */ +}; +#define SIGNATURE_COUNT (sizeof(signatures) / sizeof(struct signature)) +#endif + + +static void do_pause(unsigned amount) /* Pause for amount*10 milliseconds */ +{ + unsigned long the_time = jiffies + amount; /* 0.01 seconds per jiffy */ + + while (jiffies < the_time) + barrier(); +} + +/* + * queue services: + */ +static inline void append_SC(Scsi_Cmnd **SC, Scsi_Cmnd *new_SC) +{ + Scsi_Cmnd *end; + + new_SC->host_scribble = (unsigned char *) NULL; + if(!*SC) + *SC=new_SC; + else { + for(end=*SC; end->host_scribble; end = (Scsi_Cmnd *) end->host_scribble) + ; + end->host_scribble = (unsigned char *) new_SC; + } +} + +static inline Scsi_Cmnd *remove_first_SC(Scsi_Cmnd **SC) +{ + Scsi_Cmnd *ptr; + + ptr=*SC; + if(ptr) + *SC= (Scsi_Cmnd *) (*SC)->host_scribble; + return ptr; +} + +static inline Scsi_Cmnd *remove_SC(Scsi_Cmnd **SC, int target, int lun) +{ + Scsi_Cmnd *ptr, *prev; + + for(ptr=*SC, prev=NULL; + ptr && ((ptr->target!=target) || (ptr->lun!=lun)); + prev = ptr, ptr = (Scsi_Cmnd *) ptr->host_scribble) + ; + + if(ptr){ + if(prev) + prev->host_scribble = ptr->host_scribble; + else + *SC= (Scsi_Cmnd *) ptr->host_scribble; + } + + return ptr; +} + +/* + * read inbound byte and wait for ACK to get low + */ +static void make_acklow(struct Scsi_Host *shpnt) +{ + SETPORT(SXFRCTL0, CH1|SPIOEN); + GETPORT(SCSIDAT); + SETPORT(SXFRCTL0, CH1); + + while(TESTHI(SCSISIG, ACKI)) + barrier(); +} + +/* + * detect current phase more reliable: + * phase is valid, when the target asserts REQ after we've deasserted ACK. + * + * return value is a valid phase or an error code. + * + * errorcodes: + * P_BUSFREE BUS FREE phase detected + * P_PARITY parity error in DATA phase + */ +static int getphase(struct Scsi_Host *shpnt) +{ + int phase, sstat1; + + while(1) { + do { + while(!((sstat1 = GETPORT(SSTAT1)) & (BUSFREE|SCSIRSTI|REQINIT))) + barrier(); + if(sstat1 & BUSFREE) + return P_BUSFREE; + if(sstat1 & SCSIRSTI) { + printk("aha152x: RESET IN\n"); + SETPORT(SSTAT1, SCSIRSTI); + } + } while(TESTHI(SCSISIG, ACKI) || TESTLO(SSTAT1, REQINIT)); + + SETPORT(SSTAT1, CLRSCSIPERR); + + phase = GETPORT(SCSISIG) & P_MASK ; + + if(TESTHI(SSTAT1, SCSIPERR)) { + if((phase & (CDO|MSGO))==0) /* DATA phase */ + return P_PARITY; + + make_acklow(shpnt); + } else + return phase; + } +} + +/* called from init/main.c */ +void aha152x_setup(char *str, int *ints) +{ + if(setup_count>2) + panic("aha152x: you can only configure up to two controllers\n"); + + setup[setup_count].conf = str; + setup[setup_count].io_port = ints[0] >= 1 ? ints[1] : 0x340; + setup[setup_count].irq = ints[0] >= 2 ? ints[2] : 11; + setup[setup_count].scsiid = ints[0] >= 3 ? ints[3] : 7; + setup[setup_count].reconnect = ints[0] >= 4 ? ints[4] : 1; + setup[setup_count].parity = ints[0] >= 5 ? ints[5] : 1; + setup[setup_count].synchronous = ints[0] >= 6 ? ints[6] : 0 /* FIXME: 1 */; + setup[setup_count].delay = ints[0] >= 7 ? ints[7] : DELAY_DEFAULT; + setup[setup_count].ext_trans = ints[0] >= 8 ? ints[8] : 0; +#ifdef DEBUG_AHA152X + setup[setup_count].debug = ints[0] >= 9 ? ints[9] : DEBUG_DEFAULT; + if(ints[0]>9) { + printk("aha152x: usage: aha152x=<IOBASE>[,<IRQ>[,<SCSI ID>" + "[,<RECONNECT>[,<PARITY>[,<SYNCHRONOUS>[,<DELAY>[,<EXT_TRANS>[,<DEBUG>]]]]]]]]\n"); +#else + if(ints[0]>8) { + printk("aha152x: usage: aha152x=<IOBASE>[,<IRQ>[,<SCSI ID>" + "[,<RECONNECT>[,<PARITY>[,<SYNCHRONOUS>[,<DELAY>[,<EXT_TRANS>]]]]]]]\n"); +#endif + } else + setup_count++; +} + +/* + * Test, if port_base is valid. + */ +static int aha152x_porttest(int io_port) +{ + int i; + + if(check_region(io_port, IO_RANGE)) + return 0; + + SETPORT(io_port+O_DMACNTRL1, 0); /* reset stack pointer */ + for(i=0; i<16; i++) + SETPORT(io_port+O_STACK, i); + + SETPORT(io_port+O_DMACNTRL1, 0); /* reset stack pointer */ + for(i=0; i<16 && GETPORT(io_port+O_STACK)==i; i++) + ; + + return(i==16); +} + +int aha152x_checksetup(struct aha152x_setup *setup) +{ + int i; + +#ifndef PCMCIA + for(i=0; i<PORT_COUNT && (setup->io_port != ports[i]); i++) + ; + + if(i==PORT_COUNT) + return 0; +#endif + + if(!aha152x_porttest(setup->io_port)) + return 0; + + if(setup->irq<IRQ_MIN && setup->irq>IRQ_MAX) + return 0; + + if((setup->scsiid < 0) || (setup->scsiid > 7)) + return 0; + + if((setup->reconnect < 0) || (setup->reconnect > 1)) + return 0; + + if((setup->parity < 0) || (setup->parity > 1)) + return 0; + + if((setup->synchronous < 0) || (setup->synchronous > 1)) + return 0; + + if((setup->ext_trans < 0) || (setup->ext_trans > 1)) + return 0; + + + return 1; +} + +void aha152x_swintr(int irqno, void *dev_id, struct pt_regs * regs) +{ + struct Scsi_Host *shpnt = aha152x_host[irqno-IRQ_MIN]; + + if(!shpnt) + panic("aha152x: catched software interrupt for unknown controller.\n"); + + HOSTDATA(shpnt)->swint++; +} + + +int aha152x_detect(Scsi_Host_Template * tpnt) +{ + int i, j, ok; +#if defined(AUTOCONF) + aha152x_config conf; +#endif + + tpnt->proc_dir = &proc_scsi_aha152x; + + for(i=0; i<IRQS; i++) + aha152x_host[i] = (struct Scsi_Host *) NULL; + + if(setup_count) { + printk("aha152x: processing commandline: "); + + for(i=0; i<setup_count; i++) + if(!aha152x_checksetup(&setup[i])) { + printk("\naha152x: %s\n", setup[i].conf); + printk("aha152x: invalid line (controller=%d)\n", i+1); + } + + printk("ok\n"); + } + +#ifdef SETUP0 + if(setup_count<2) { + struct aha152x_setup override = SETUP0; + + if(setup_count==0 || (override.io_port != setup[0].io_port)) + if(!aha152x_checksetup(&override)) { + printk("\naha152x: invalid override SETUP0={0x%x,%d,%d,%d,%d,%d,%d,%d}\n", + override.io_port, + override.irq, + override.scsiid, + override.reconnect, + override.parity, + override.synchronous, + override.delay, + override.ext_trans); + } else + setup[setup_count++] = override; + } +#endif + +#ifdef SETUP1 + if(setup_count<2) { + struct aha152x_setup override = SETUP1; + + if(setup_count==0 || (override.io_port != setup[0].io_port)) + if(!aha152x_checksetup(&override)) { + printk("\naha152x: invalid override SETUP1={0x%x,%d,%d,%d,%d,%d,%d,%d}\n", + override.io_port, + override.irq, + override.scsiid, + override.reconnect, + override.parity, + override.synchronous, + override.delay, + override.ext_trans); + } else + setup[setup_count++] = override; + } +#endif + +#if defined(MODULE) + if(setup_count<2 && aha152x[0]!=0) { + setup[setup_count].conf = ""; + setup[setup_count].io_port = aha152x[0]; + setup[setup_count].irq = aha152x[1]; + setup[setup_count].scsiid = aha152x[2]; + setup[setup_count].reconnect = aha152x[3]; + setup[setup_count].parity = aha152x[4]; + setup[setup_count].synchronous = aha152x[5]; + setup[setup_count].delay = aha152x[6]; + setup[setup_count].ext_trans = aha152x[7]; +#ifdef DEBUG_AHA152X + setup[setup_count].debug = aha152x[8]; +#endif + if(aha152x_checksetup(&setup[setup_count])) + setup_count++; + else + printk("\naha152x: invalid module argument aha152x=0x%x,%d,%d,%d,%d,%d,%d,%d\n", + setup[setup_count].io_port, + setup[setup_count].irq, + setup[setup_count].scsiid, + setup[setup_count].reconnect, + setup[setup_count].parity, + setup[setup_count].synchronous, + setup[setup_count].delay, + setup[setup_count].ext_trans); + } + + if(setup_count<2 && aha152x1[0]!=0) { + setup[setup_count].conf = ""; + setup[setup_count].io_port = aha152x1[0]; + setup[setup_count].irq = aha152x1[1]; + setup[setup_count].scsiid = aha152x1[2]; + setup[setup_count].reconnect = aha152x1[3]; + setup[setup_count].parity = aha152x1[4]; + setup[setup_count].synchronous = aha152x1[5]; + setup[setup_count].delay = aha152x1[6]; + setup[setup_count].ext_trans = aha152x1[7]; +#ifdef DEBUG_AHA152X + setup[setup_count].debug = aha152x1[8]; +#endif + if(aha152x_checksetup(&setup[setup_count])) + setup_count++; + else + printk("\naha152x: invalid module argument aha152x1=0x%x,%d,%d,%d,%d,%d,%d,%d\n", + setup[setup_count].io_port, + setup[setup_count].irq, + setup[setup_count].scsiid, + setup[setup_count].reconnect, + setup[setup_count].parity, + setup[setup_count].synchronous, + setup[setup_count].delay, + setup[setup_count].ext_trans); + } +#endif + +#if defined(AUTOCONF) + if(setup_count<2) { +#if !defined(SKIP_BIOSTEST) + ok=0; + for(i=0; i < ADDRESS_COUNT && !ok; i++) + for(j=0; (j < SIGNATURE_COUNT) && !ok; j++) + ok=!memcmp((void *) addresses[i]+signatures[j].sig_offset, + (void *) signatures[j].signature, + (int) signatures[j].sig_length); + + if(!ok && setup_count==0) + return 0; + + printk("aha152x: BIOS test: passed, "); +#else + printk("aha152x: "); +#endif /* !SKIP_BIOSTEST */ + + ok=0; + for(i=0; i<PORT_COUNT && setup_count<2; i++) { + if((setup_count==1) && (setup[0].io_port == ports[i])) + continue; + + if(aha152x_porttest(ports[i])) { + ok++; + setup[setup_count].io_port = ports[i]; + + conf.cf_port = + (GETPORT(ports[i]+O_PORTA)<<8) + GETPORT(ports[i]+O_PORTB); + + setup[setup_count].irq = IRQ_MIN + conf.cf_irq; + setup[setup_count].scsiid = conf.cf_id; + setup[setup_count].reconnect = conf.cf_tardisc; + setup[setup_count].parity = !conf.cf_parity; + setup[setup_count].synchronous = 0 /* FIXME: conf.cf_syncneg */; + setup[setup_count].delay = DELAY_DEFAULT; + setup[setup_count].ext_trans = 0; +#ifdef DEBUG_AHA152X + setup[setup_count].debug = DEBUG_DEFAULT; +#endif + setup_count++; + } + } + + if(ok) + printk("auto configuration: ok, "); + } +#endif + + printk("detected %d controller(s)\n", setup_count); + + for(i=0; i<setup_count; i++) { + struct Scsi_Host *shpnt; + unsigned long int the_time; + + shpnt = aha152x_host[setup[i].irq-IRQ_MIN] = + scsi_register(tpnt, sizeof(struct aha152x_hostdata)); + + shpnt->io_port = setup[i].io_port; + shpnt->n_io_port = IO_RANGE; + shpnt->irq = setup[i].irq; + + ISSUE_SC = (Scsi_Cmnd *) NULL; + CURRENT_SC = (Scsi_Cmnd *) NULL; + DISCONNECTED_SC = (Scsi_Cmnd *) NULL; + + HOSTDATA(shpnt)->reconnect = setup[i].reconnect; + HOSTDATA(shpnt)->parity = setup[i].parity; + HOSTDATA(shpnt)->synchronous = setup[i].synchronous; + HOSTDATA(shpnt)->delay = setup[i].delay; + HOSTDATA(shpnt)->ext_trans = setup[i].ext_trans; +#ifdef DEBUG_AHA152X + HOSTDATA(shpnt)->debug = setup[i].debug; +#endif + + HOSTDATA(shpnt)->aborting = 0; + HOSTDATA(shpnt)->abortion_complete = 0; + HOSTDATA(shpnt)->abort_result = 0; + HOSTDATA(shpnt)->commands = 0; + + HOSTDATA(shpnt)->message_len = 0; + + for(j=0; j<8; j++) + HOSTDATA(shpnt)->syncrate[j] = 0; + + SETPORT(SCSIID, setup[i].scsiid << 4); + shpnt->this_id=setup[i].scsiid; + + if(setup[i].reconnect) + shpnt->can_queue=AHA152X_MAXQUEUE; + + /* RESET OUT */ + SETBITS(SCSISEQ, SCSIRSTO); + do_pause(30); + CLRBITS(SCSISEQ, SCSIRSTO); + do_pause(setup[i].delay); + + aha152x_reset_ports(shpnt); + + printk("aha152x%d: vital data: PORTBASE=0x%03x, IRQ=%d, SCSI ID=%d," + " reconnect=%s, parity=%s, synchronous=%s, delay=%d, extended translation=%s\n", + i, + shpnt->io_port, + shpnt->irq, + shpnt->this_id, + HOSTDATA(shpnt)->reconnect ? "enabled" : "disabled", + HOSTDATA(shpnt)->parity ? "enabled" : "disabled", + HOSTDATA(shpnt)->synchronous ? "enabled" : "disabled", + HOSTDATA(shpnt)->delay, + HOSTDATA(shpnt)->ext_trans ? "enabled" : "disabled"); + + request_region(shpnt->io_port, IO_RANGE, "aha152x"); /* Register */ + + /* not expecting any interrupts */ + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, 0); + + SETBITS(DMACNTRL0, INTEN); + + ok = request_irq(shpnt->irq, aha152x_swintr, SA_INTERRUPT, "aha152x", NULL); + if(ok<0) { + if(ok == -EINVAL) + printk("aha152x%d: bad IRQ %d.\n", i, shpnt->irq); + else if(ok == -EBUSY) + printk("aha152x%d: IRQ %d already in use.\n", i, shpnt->irq); + else + printk("\naha152x%d: Unexpected error code %d on requesting IRQ %d.\n", i, ok, shpnt->irq); + printk("aha152x: driver needs an IRQ.\n"); + + scsi_unregister(shpnt); + shpnt=aha152x_host[shpnt->irq-IRQ_MIN]=0; + continue; + } + + HOSTDATA(shpnt)->swint=0; + + printk("aha152x: trying software interrupt, "); + SETBITS(DMACNTRL0, SWINT); + + the_time=jiffies+100; + while(!HOSTDATA(shpnt)->swint && jiffies<the_time) + barrier(); + + free_irq(shpnt->irq,0); + + if(!HOSTDATA(shpnt)->swint) { + if(TESTHI(DMASTAT, INTSTAT)) { + printk("lost.\n"); + } else { + printk("failed.\n"); + } + + printk("aha152x: IRQ %d possibly wrong. Please verify.\n", shpnt->irq); + + scsi_unregister(shpnt); + shpnt=aha152x_host[shpnt->irq-IRQ_MIN]=0; + continue; + } + + printk("ok.\n"); + + CLRBITS(DMACNTRL0, SWINT); + + /* clear interrupts */ + SETPORT(SSTAT0, 0x7f); + SETPORT(SSTAT1, 0xef); + + if(request_irq(shpnt->irq,aha152x_intr,SA_INTERRUPT,"aha152x",NULL)<0) { + printk("aha152x: failed to reassign interrupt.\n"); + } + } + + return (setup_count>0); +} + +/* + * Queue a command and setup interrupts for a free bus. + */ +int aha152x_queue(Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *)) +{ + struct Scsi_Host *shpnt = SCpnt->host; + unsigned long flags; + +#if defined(DEBUG_RACE) + enter_driver("queue"); +#else +#if defined(DEBUG_QUEUE) + if(HOSTDATA(shpnt)->debug & debug_queue) + printk("aha152x: queue(), "); +#endif +#endif + +#if defined(DEBUG_QUEUE) + if(HOSTDATA(shpnt)->debug & debug_queue) { + printk("SCpnt (target = %d lun = %d cmnd = ", + SCpnt->target, SCpnt->lun); + print_command(SCpnt->cmnd); + printk(", cmd_len=%d, pieces = %d size = %u), ", + SCpnt->cmd_len, SCpnt->use_sg, SCpnt->request_bufflen); + disp_ports(shpnt); + } +#endif + + SCpnt->scsi_done = done; + + /* setup scratch area + SCp.ptr : buffer pointer + SCp.this_residual : buffer length + SCp.buffer : next buffer + SCp.buffers_residual : left buffers in list + SCp.phase : current state of the command */ + SCpnt->SCp.phase = not_issued; + if (SCpnt->use_sg) { + SCpnt->SCp.buffer = (struct scatterlist *) SCpnt->request_buffer; + SCpnt->SCp.ptr = SCpnt->SCp.buffer->address; + SCpnt->SCp.this_residual = SCpnt->SCp.buffer->length; + SCpnt->SCp.buffers_residual = SCpnt->use_sg - 1; + } else { + SCpnt->SCp.ptr = (char *)SCpnt->request_buffer; + SCpnt->SCp.this_residual = SCpnt->request_bufflen; + SCpnt->SCp.buffer = NULL; + SCpnt->SCp.buffers_residual = 0; + } + + SCpnt->SCp.Status = CHECK_CONDITION; + SCpnt->SCp.Message = 0; + SCpnt->SCp.have_data_in = 0; + SCpnt->SCp.sent_command = 0; + + /* Turn led on, when this is the first command. */ + save_flags(flags); + cli(); + HOSTDATA(shpnt)->commands++; + if(HOSTDATA(shpnt)->commands==1) + SETPORT(PORTA, 1); + +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("i+ (%d), ", HOSTDATA(shpnt)->commands); +#endif + append_SC(&ISSUE_SC, SCpnt); + + /* Enable bus free interrupt, when we aren't currently on the bus */ + if(!CURRENT_SC) { + SETPORT(SIMODE0, DISCONNECTED_SC ? ENSELDI : 0); + SETPORT(SIMODE1, ISSUE_SC ? ENBUSFREE : 0); + } + restore_flags(flags); + +#if defined(DEBUG_RACE) + leave_driver("queue"); +#endif + + return 0; +} + +/* + * We only support commands in interrupt-driven fashion + */ +int aha152x_command(Scsi_Cmnd *SCpnt) +{ + printk("aha152x: interrupt driven driver; use aha152x_queue()\n"); + return -1; +} + +/* + * Abort a queued command + * (commands that are on the bus can't be aborted easily) + */ +int aha152x_abort(Scsi_Cmnd *SCpnt) +{ + struct Scsi_Host *shpnt = SCpnt->host; + unsigned long flags; + Scsi_Cmnd *ptr, *prev; + + save_flags(flags); + cli(); + +#if defined(DEBUG_ABORT) + if(HOSTDATA(shpnt)->debug & debug_abort) { + printk("aha152x: abort(), SCpnt=0x%08x, ", (unsigned int) SCpnt); + show_queues(shpnt); + } +#endif + + /* look for command in issue queue */ + for(ptr=ISSUE_SC, prev=NULL; + ptr && ptr!=SCpnt; + prev=ptr, ptr=(Scsi_Cmnd *) ptr->host_scribble) + ; + + if(ptr) { + /* dequeue */ + if(prev) + prev->host_scribble = ptr->host_scribble; + else + ISSUE_SC = (Scsi_Cmnd *) ptr->host_scribble; + + HOSTDATA(shpnt)->commands--; + + restore_flags(flags); + + ptr->host_scribble = NULL; + ptr->result = DID_ABORT << 16; + ptr->scsi_done(ptr); + + return SCSI_ABORT_SUCCESS; + } + + /* if the bus is busy or a command is currently processed, + we can't do anything more */ + if (TESTLO(SSTAT1, BUSFREE) || (CURRENT_SC && CURRENT_SC!=SCpnt)) { + /* fail abortion, if bus is busy */ + + if(!CURRENT_SC) + printk("bus busy w/o current command, "); + + restore_flags(flags); + + return SCSI_ABORT_BUSY; + } + + /* bus is free */ + + if(CURRENT_SC) { + HOSTDATA(shpnt)->commands--; + + /* target entered bus free before COMMAND COMPLETE, nothing to abort */ + restore_flags(flags); + CURRENT_SC->result = DID_ERROR << 16; + CURRENT_SC->scsi_done(CURRENT_SC); + CURRENT_SC = (Scsi_Cmnd *) NULL; + + return SCSI_ABORT_SUCCESS; + } + + /* look for command in disconnected queue */ + for(ptr=DISCONNECTED_SC, prev=NULL; + ptr && ptr!=SCpnt; + prev=ptr, ptr=(Scsi_Cmnd *) ptr->host_scribble) + ; + + if(!ptr) { + /* command wasn't found */ + printk("command not found\n"); + restore_flags(flags); + + return SCSI_ABORT_NOT_RUNNING; + } + + if(!HOSTDATA(shpnt)->aborting) { + /* dequeue */ + if(prev) + prev->host_scribble = ptr->host_scribble; + else + DISCONNECTED_SC = (Scsi_Cmnd *) ptr->host_scribble; + + HOSTDATA(shpnt)->commands--; + + /* set command current and initiate selection, + let the interrupt routine take care of the abortion */ + CURRENT_SC = ptr; + ptr->SCp.phase = in_selection|aborted; + SETPORT(SCSIID, (shpnt->this_id << OID_) | CURRENT_SC->target); + + ADDMSG(ABORT); + + /* enable interrupts for SELECTION OUT DONE and SELECTION TIME OUT */ + SETPORT(SIMODE0, ENSELDO | (DISCONNECTED_SC ? ENSELDI : 0)); + SETPORT(SIMODE1, ENSELTIMO); + + /* Enable SELECTION OUT sequence */ + SETBITS(SCSISEQ, ENSELO | ENAUTOATNO); + + SETBITS(DMACNTRL0, INTEN); + HOSTDATA(shpnt)->abort_result=SCSI_ABORT_SUCCESS; + HOSTDATA(shpnt)->aborting++; + HOSTDATA(shpnt)->abortion_complete=0; + + sti(); /* Hi Eric, guess what ;-) */ + + /* sleep until the abortion is complete */ + while(!HOSTDATA(shpnt)->abortion_complete) + barrier(); + HOSTDATA(shpnt)->aborting=0; + + return HOSTDATA(shpnt)->abort_result; + } else { + /* we're already aborting a command */ + restore_flags(flags); + + return SCSI_ABORT_BUSY; + } +} + +/* + * Restore default values to the AIC-6260 registers and reset the fifos + */ +static void aha152x_reset_ports(struct Scsi_Host *shpnt) +{ + /* disable interrupts */ + SETPORT(DMACNTRL0, RSTFIFO); + + SETPORT(SCSISEQ, 0); + + SETPORT(SXFRCTL1, 0); + SETPORT(SCSISIG, 0); + SETPORT(SCSIRATE, 0); + + /* clear all interrupt conditions */ + SETPORT(SSTAT0, 0x7f); + SETPORT(SSTAT1, 0xef); + + SETPORT(SSTAT4, SYNCERR|FWERR|FRERR); + + SETPORT(DMACNTRL0, 0); + SETPORT(DMACNTRL1, 0); + + SETPORT(BRSTCNTRL, 0xf1); + + /* clear SCSI fifo and transfer count */ + SETPORT(SXFRCTL0, CH1|CLRCH1|CLRSTCNT); + SETPORT(SXFRCTL0, CH1); + + /* enable interrupts */ + SETPORT(SIMODE0, DISCONNECTED_SC ? ENSELDI : 0); + SETPORT(SIMODE1, ISSUE_SC ? ENBUSFREE : 0); +} + +/* + * Reset registers, reset a hanging bus and + * kill active and disconnected commands for target w/o soft reset + */ +int aha152x_reset(Scsi_Cmnd *SCpnt, unsigned int unused) +{ + struct Scsi_Host *shpnt = SCpnt->host; + unsigned long flags; + Scsi_Cmnd *ptr, *prev, *next; + + aha152x_reset_ports(shpnt); + + /* Reset, if bus hangs */ + if(TESTLO(SSTAT1, BUSFREE)) { + CLRBITS(DMACNTRL0, INTEN); + +#if defined(DEBUG_RESET) + if(HOSTDATA(shpnt)->debug & debug_reset) { + printk("aha152x: reset(), bus not free: SCSI RESET OUT\n"); + show_queues(shpnt); + } +#endif + + ptr=CURRENT_SC; + if(ptr && !ptr->device->soft_reset) { + ptr->host_scribble = NULL; + ptr->result = DID_RESET << 16; + ptr->scsi_done(CURRENT_SC); + CURRENT_SC=NULL; + } + + save_flags(flags); + cli(); + prev=NULL; ptr=DISCONNECTED_SC; + while(ptr) { + if(!ptr->device->soft_reset) { + if(prev) + prev->host_scribble = ptr->host_scribble; + else + DISCONNECTED_SC = (Scsi_Cmnd *) ptr->host_scribble; + + next = (Scsi_Cmnd *) ptr->host_scribble; + + ptr->host_scribble = NULL; + ptr->result = DID_RESET << 16; + ptr->scsi_done(ptr); + + ptr = next; + } else { + prev=ptr; + ptr = (Scsi_Cmnd *) ptr->host_scribble; + } + } + restore_flags(flags); + +#if defined(DEBUG_RESET) + if(HOSTDATA(shpnt)->debug & debug_reset) { + printk("commands on targets w/ soft-resets:\n"); + show_queues(shpnt); + } +#endif + + /* RESET OUT */ + SETPORT(SCSISEQ, SCSIRSTO); + do_pause(30); + SETPORT(SCSISEQ, 0); + do_pause(DELAY); + + SETPORT(SIMODE0, DISCONNECTED_SC ? ENSELDI : 0); + SETPORT(SIMODE1, ISSUE_SC ? ENBUSFREE : 0); + + SETPORT(DMACNTRL0, INTEN); + } + + return SCSI_RESET_SUCCESS; +} + +/* + * Return the "logical geometry" + */ +int aha152x_biosparam(Scsi_Disk * disk, kdev_t dev, int *info_array) +{ + struct Scsi_Host *shpnt=disk->device->host; + +#if defined(DEBUG_BIOSPARAM) + if(HOSTDATA(shpnt)->debug & debug_biosparam) + printk("aha152x_biosparam: dev=%s, size=%d, ", + kdevname(dev), disk->capacity); +#endif + + /* try default translation */ + info_array[0]=64; + info_array[1]=32; + info_array[2]=disk->capacity / (64 * 32); + + /* for disks >1GB do some guessing */ + if(info_array[2]>=1024) { + int info[3]; + + /* try to figure out the geometry from the partition table */ + if(scsicam_bios_param(disk, dev, info)<0 || + !((info[0]==64 && info[1]==32) || (info[0]==255 && info[1]==63))) { + if(EXT_TRANS) { + printk("aha152x: unable to verify geometry for disk with >1GB.\n" + " using extended translation.\n"); + info_array[0] = 255; + info_array[1] = 63; + info_array[2] = disk->capacity / (255 * 63); + } else { + printk("aha152x: unable to verify geometry for disk with >1GB.\n" + " Using default translation. Please verify yourself.\n" + " Perhaps you need to enable extended translation in the driver.\n" + " See /usr/src/linux/drivers/scsi/aha152x.c for details.\n"); + } + } else { + info_array[0]=info[0]; + info_array[1]=info[1]; + info_array[2]=info[2]; + + if(info[0]==255 && !EXT_TRANS) { + printk("aha152x: current partition table is using extended translation.\n" + " using it also, although it's not explicty enabled.\n"); + } + } + } + +#if defined(DEBUG_BIOSPARAM) + if(HOSTDATA(shpnt)->debug & debug_biosparam) { + printk("bios geometry: head=%d, sec=%d, cyl=%d\n", + info_array[0], info_array[1], info_array[2]); + printk("WARNING: check, if the bios geometry is correct.\n"); + } +#endif + + return 0; +} + +/* + * Internal done function + */ +void aha152x_done(struct Scsi_Host *shpnt, int error) +{ + unsigned long flags; + Scsi_Cmnd *done_SC; + +#if defined(DEBUG_DONE) + if(HOSTDATA(shpnt)->debug & debug_done) { + printk("\naha152x: done(), "); + disp_ports(shpnt); + } +#endif + + if(CURRENT_SC) { +#if defined(DEBUG_DONE) + if(HOSTDATA(shpnt)->debug & debug_done) + printk("done(%x), ", error); +#endif + + save_flags(flags); + cli(); + + done_SC = CURRENT_SC; + CURRENT_SC = NULL; + + /* turn led off, when no commands are in the driver */ + HOSTDATA(shpnt)->commands--; + if(!HOSTDATA(shpnt)->commands) + SETPORT(PORTA, 0); /* turn led off */ + +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("ok (%d), ", HOSTDATA(shpnt)->commands); +#endif + restore_flags(flags); + + SETPORT(SIMODE0, DISCONNECTED_SC ? ENSELDI : 0); + SETPORT(SIMODE1, ISSUE_SC ? ENBUSFREE : 0); + +#if 0 +/* Why poll for the BUS FREE phase, when we have setup the interrupt!? */ +#if defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & debug_phases) + printk("BUS FREE loop, "); +#endif + while(TESTLO(SSTAT1, BUSFREE)) + barrier(); +#if defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & debug_phases) + printk("BUS FREE\n"); +#endif +#endif + + done_SC->result = error; + if(done_SC->scsi_done) { +#if defined(DEBUG_DONE) + if(HOSTDATA(shpnt)->debug & debug_done) + printk("calling scsi_done, "); +#endif + done_SC->scsi_done(done_SC); +#if defined(DEBUG_DONE) + if(HOSTDATA(shpnt)->debug & debug_done) + printk("done returned, "); +#endif + } else + panic("aha152x: current_SC->scsi_done() == NULL"); + } else + aha152x_panic(shpnt, "done() called outside of command"); +} + +/* + * Interrupts handler (main routine of the driver) + */ +void aha152x_intr(int irqno, void *dev_id, struct pt_regs * regs) +{ + struct Scsi_Host *shpnt = aha152x_host[irqno-IRQ_MIN]; + unsigned int flags; + int done=0, phase; + +#if defined(DEBUG_RACE) + enter_driver("intr"); +#else +#if defined(DEBUG_INTR) + if(HOSTDATA(shpnt)->debug & debug_intr) + printk("\naha152x: intr(), "); +#endif +#endif + + if(!shpnt) + panic("aha152x: catched interrupt for unknown controller.\n"); + + /* no more interrupts from the controller, while we're busy. + INTEN has to be restored, when we're ready to leave + intr(). To avoid race conditions, we have to return + immediately afterwards. */ + CLRBITS(DMACNTRL0, INTEN); + sti(); /* Yes, sti() really needs to be here */ + + /* disconnected target is trying to reconnect. + Only possible, if we have disconnected nexuses and + nothing is occupying the bus. + */ + if(TESTHI(SSTAT0, SELDI) && + DISCONNECTED_SC && + (!CURRENT_SC || (CURRENT_SC->SCp.phase & in_selection)) ) { + int identify_msg, target, i; + + /* Avoid conflicts when a target reconnects + while we are trying to connect to another. */ + if(CURRENT_SC) { +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("i+, "); +#endif + save_flags(flags); + cli(); + append_SC(&ISSUE_SC, CURRENT_SC); + CURRENT_SC=NULL; + restore_flags(flags); + } + + /* disable sequences */ + SETPORT(SCSISEQ, 0); + SETPORT(SSTAT0, CLRSELDI); + SETPORT(SSTAT1, CLRBUSFREE); + +#if defined(DEBUG_QUEUES) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_queues|debug_phases)) + printk("reselected, "); +#endif + + i = GETPORT(SELID) & ~(1 << shpnt->this_id); + target=0; + + if(i==0) + aha152x_panic(shpnt, "reconnecting target unknown"); + + for(; (i & 1)==0; target++, i>>=1) + ; + +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("SELID=%02x, target=%d, ", GETPORT(SELID), target); +#endif + SETPORT(SCSIID, (shpnt->this_id << OID_) | target); + SETPORT(SCSISEQ, ENRESELI); + + if(TESTLO(SSTAT0, SELDI)) + aha152x_panic(shpnt, "RESELI failed"); + + SETPORT(SCSIRATE, HOSTDATA(shpnt)->syncrate[target]&0x7f); + + SETPORT(SCSISIG, P_MSGI); + + /* Get identify message */ + if((i=getphase(shpnt))!=P_MSGI) { + printk("target doesn't enter MSGI to identify (phase=%02x)\n", i); + aha152x_panic(shpnt, "unknown lun"); + } + SETPORT(SCSISEQ, 0); + + SETPORT(SXFRCTL0, CH1); + + identify_msg = GETPORT(SCSIBUS); + + if(!(identify_msg & IDENTIFY_BASE)) { + printk("target=%d, inbound message (%02x) != IDENTIFY\n", + target, identify_msg); + aha152x_panic(shpnt, "unknown lun"); + } + + +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("identify=%02x, lun=%d, ", identify_msg, identify_msg & 0x3f); +#endif + + save_flags(flags); + cli(); + +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("d-, "); +#endif + CURRENT_SC = remove_SC(&DISCONNECTED_SC, target, identify_msg & 0x3f); + + if(!CURRENT_SC) { + printk("lun=%d, ", identify_msg & 0x3f); + aha152x_panic(shpnt, "no disconnected command for that lun"); + } + + CURRENT_SC->SCp.phase &= ~disconnected; + restore_flags(flags); + + make_acklow(shpnt); + if(getphase(shpnt)!=P_MSGI) { + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENPHASEMIS|ENBUSFREE); +#if defined(DEBUG_RACE) + leave_driver("(reselected) intr"); +#endif + SETBITS(DMACNTRL0, INTEN); + return; + } + } + + /* Check, if we aren't busy with a command */ + if(!CURRENT_SC) { + /* bus is free to issue a queued command */ + if(TESTHI(SSTAT1, BUSFREE) && ISSUE_SC) { + save_flags(flags); + cli(); +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("i-, "); +#endif + CURRENT_SC = remove_first_SC(&ISSUE_SC); + restore_flags(flags); + +#if defined(DEBUG_INTR) || defined(DEBUG_SELECTION) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_intr|debug_selection|debug_phases)) + printk("issuing command, "); +#endif + CURRENT_SC->SCp.phase = in_selection; + +#if defined(DEBUG_INTR) || defined(DEBUG_SELECTION) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_intr|debug_selection|debug_phases)) + printk("selecting %d, ", CURRENT_SC->target); +#endif + SETPORT(SCSIID, (shpnt->this_id << OID_) | CURRENT_SC->target); + + /* Enable interrupts for SELECTION OUT DONE and SELECTION OUT INITIATED */ + SETPORT(SXFRCTL1, HOSTDATA(shpnt)->parity ? (ENSPCHK|ENSTIMER) : ENSTIMER); + + /* enable interrupts for SELECTION OUT DONE and SELECTION TIME OUT */ + SETPORT(SIMODE0, ENSELDO | (DISCONNECTED_SC ? ENSELDI : 0)); + SETPORT(SIMODE1, ENSELTIMO); + + /* Enable SELECTION OUT sequence */ + SETBITS(SCSISEQ, ENSELO | ENAUTOATNO); + + } else { + /* No command we are busy with and no new to issue */ + printk("aha152x: ignoring spurious interrupt, nothing to do\n"); + if(TESTHI(DMACNTRL0, SWINT)) { + printk("aha152x: SWINT is set! Why?\n"); + CLRBITS(DMACNTRL0, SWINT); + } + show_queues(shpnt); + } + +#if defined(DEBUG_RACE) + leave_driver("(selecting) intr"); +#endif + SETBITS(DMACNTRL0, INTEN); + return; + } + + /* the bus is busy with something */ + +#if defined(DEBUG_INTR) + if(HOSTDATA(shpnt)->debug & debug_intr) + disp_ports(shpnt); +#endif + + /* we are waiting for the result of a selection attempt */ + if(CURRENT_SC->SCp.phase & in_selection) { + if(TESTLO(SSTAT1, SELTO)) { + /* no timeout */ + if(TESTHI(SSTAT0, SELDO)) { + /* clear BUS FREE interrupt */ + SETPORT(SSTAT1, CLRBUSFREE); + + /* Disable SELECTION OUT sequence */ + CLRBITS(SCSISEQ, ENSELO|ENAUTOATNO); + + /* Disable SELECTION OUT DONE interrupt */ + CLRBITS(SIMODE0, ENSELDO); + CLRBITS(SIMODE1, ENSELTIMO); + + if(TESTLO(SSTAT0, SELDO)) { + printk("aha152x: passing bus free condition\n"); + +#if defined(DEBUG_RACE) + leave_driver("(passing bus free) intr"); +#endif + SETBITS(DMACNTRL0, INTEN); + + if(CURRENT_SC->SCp.phase & aborted) { + HOSTDATA(shpnt)->abort_result=SCSI_ABORT_ERROR; + HOSTDATA(shpnt)->abortion_complete++; + } + + aha152x_done(shpnt, DID_NO_CONNECT << 16); + + return; + } +#if defined(DEBUG_SELECTION) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_selection|debug_phases)) + printk("SELDO (SELID=%x), ", GETPORT(SELID)); +#endif + + /* selection was done */ + SETPORT(SSTAT0, CLRSELDO); + +#if defined(DEBUG_ABORT) + if((HOSTDATA(shpnt)->debug & debug_abort) && (CURRENT_SC->SCp.phase & aborted)) + printk("(ABORT) target selected, "); +#endif + + CURRENT_SC->SCp.phase &= ~in_selection; + CURRENT_SC->SCp.phase |= in_other; + + ADDMSG(IDENTIFY(HOSTDATA(shpnt)->reconnect,CURRENT_SC->lun)); + + if(!(SYNCRATE&0x80) && HOSTDATA(shpnt)->synchronous) { + ADDMSG(EXTENDED_MESSAGE); + ADDMSG(3); + ADDMSG(EXTENDED_SDTR); + ADDMSG(50); + ADDMSG(8); + + printk("outbound SDTR: "); + print_msg(&MSG(MSGLEN-5)); + + SYNCRATE=0x80; + CURRENT_SC->SCp.phase |= in_sync; + } + +#if defined(DEBUG_RACE) + leave_driver("(SELDO) intr"); +#endif + SETPORT(SCSIRATE, SYNCRATE&0x7f); + + SETPORT(SCSISIG, P_MSGO); + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENREQINIT|ENBUSFREE); + SETBITS(DMACNTRL0, INTEN); + + return; + } else + aha152x_panic(shpnt, "neither timeout nor selection\007"); + } else { +#if defined(DEBUG_SELECTION) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_selection|debug_phases)) + printk("SELTO, "); +#endif + /* end selection attempt */ + CLRBITS(SCSISEQ, ENSELO|ENAUTOATNO); + + /* timeout */ + SETPORT(SSTAT1, CLRSELTIMO); + + SETPORT(SIMODE0, DISCONNECTED_SC ? ENSELDI : 0); + SETPORT(SIMODE1, ISSUE_SC ? ENBUSFREE : 0); + SETBITS(DMACNTRL0, INTEN); +#if defined(DEBUG_RACE) + leave_driver("(SELTO) intr"); +#endif + + if(CURRENT_SC->SCp.phase & aborted) { +#if defined(DEBUG_ABORT) + if(HOSTDATA(shpnt)->debug & debug_abort) + printk("(ABORT) selection timeout, "); +#endif + HOSTDATA(shpnt)->abort_result=SCSI_ABORT_ERROR; + HOSTDATA(shpnt)->abortion_complete++; + } + + if(TESTLO(SSTAT0, SELINGO)) + /* ARBITRATION not won */ + aha152x_done(shpnt, DID_BUS_BUSY << 16); + else + /* ARBITRATION won, but SELECTION failed */ + aha152x_done(shpnt, DID_NO_CONNECT << 16); + + return; + } + } + + /* enable interrupt, when target leaves current phase */ + phase = getphase(shpnt); + if(!(phase & ~P_MASK)) /* "real" phase */ + SETPORT(SCSISIG, phase); + SETPORT(SSTAT1, CLRPHASECHG); + CURRENT_SC->SCp.phase = + (CURRENT_SC->SCp.phase & ~((P_MASK|1)<<16)) | (phase << 16); + + /* information transfer phase */ + switch(phase) { + case P_MSGO: /* MESSAGE OUT */ + { + int i, identify=0, abort=0; + +#if defined(DEBUG_INTR) || defined(DEBUG_MSGO) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_intr|debug_msgo|debug_phases)) + printk("MESSAGE OUT, "); +#endif + if(MSGLEN==0) { + ADDMSG(MESSAGE_REJECT); +#if defined(DEBUG_MSGO) + if(HOSTDATA(shpnt)->debug & debug_msgo) + printk("unexpected MESSAGE OUT phase; rejecting, "); +#endif + } + + CLRBITS(SXFRCTL0, ENDMA); + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENPHASEMIS|ENREQINIT|ENBUSFREE); + + /* wait for data latch to become ready or a phase change */ + while(TESTLO(DMASTAT, INTSTAT)) + barrier(); + +#if defined(DEBUG_MSGO) + if(HOSTDATA(shpnt)->debug & debug_msgo) { + int i; + + printk("messages ("); + for(i=0; i<MSGLEN; i+=print_msg(&MSG(i)), printk(" ")) + ; + printk("), "); + } +#endif + + for(i=0; i<MSGLEN && TESTLO(SSTAT1, PHASEMIS); i++) { +#if defined(DEBUG_MSGO) + if(HOSTDATA(shpnt)->debug & debug_msgo) + printk("%x ", MSG(i)); +#endif + if(i==MSGLEN-1) { + /* Leave MESSAGE OUT after transfer */ + SETPORT(SSTAT1, CLRATNO); + } + + SETPORT(SCSIDAT, MSG(i)); + + make_acklow(shpnt); + getphase(shpnt); + + if(MSG(i)==IDENTIFY(HOSTDATA(shpnt)->reconnect,CURRENT_SC->lun)) + identify++; + + if(MSG(i)==ABORT) + abort++; + + } + + MSGLEN=0; + + if(identify) + CURRENT_SC->SCp.phase |= sent_ident; + + if(abort) { + /* revive abort(); abort() enables interrupts */ + HOSTDATA(shpnt)->abort_result=SCSI_ABORT_SUCCESS; + HOSTDATA(shpnt)->abortion_complete++; + + CURRENT_SC->SCp.phase &= ~(P_MASK<<16); + + /* exit */ + SETBITS(DMACNTRL0, INTEN); +#if defined(DEBUG_RACE) + leave_driver("(ABORT) intr"); +#endif + aha152x_done(shpnt, DID_ABORT<<16); + + return; + } + } + break; + + case P_CMD: /* COMMAND phase */ +#if defined(DEBUG_INTR) || defined(DEBUG_CMD) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_intr|debug_cmd|debug_phases)) + printk("COMMAND, "); +#endif + if(!(CURRENT_SC->SCp.sent_command)) { + int i; + + CLRBITS(SXFRCTL0, ENDMA); + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENPHASEMIS|ENREQINIT|ENBUSFREE); + + /* wait for data latch to become ready or a phase change */ + while(TESTLO(DMASTAT, INTSTAT)) + barrier(); + + for(i=0; i<CURRENT_SC->cmd_len && TESTLO(SSTAT1, PHASEMIS); i++) { + SETPORT(SCSIDAT, CURRENT_SC->cmnd[i]); + + make_acklow(shpnt); + getphase(shpnt); + } + + if(i<CURRENT_SC->cmd_len && TESTHI(SSTAT1, PHASEMIS)) + aha152x_panic(shpnt, "target left COMMAND"); + + CURRENT_SC->SCp.sent_command++; + } else + aha152x_panic(shpnt, "Nothing to send while in COMMAND"); + break; + + case P_MSGI: /* MESSAGE IN phase */ + { + int start_sync=0; + +#if defined(DEBUG_INTR) || defined(DEBUG_MSGI) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_intr|debug_msgi|debug_phases)) + printk("MESSAGE IN, "); +#endif + SETPORT(SXFRCTL0, CH1); + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENBUSFREE); + + while(phase == P_MSGI) { + CURRENT_SC->SCp.Message = GETPORT(SCSIDAT); + switch(CURRENT_SC->SCp.Message) { + case DISCONNECT: +#if defined(DEBUG_MSGI) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_msgi|debug_phases)) + printk("target disconnected, "); +#endif + CURRENT_SC->SCp.Message = 0; + CURRENT_SC->SCp.phase |= disconnected; + if(!HOSTDATA(shpnt)->reconnect) + aha152x_panic(shpnt, "target was not allowed to disconnect"); + + break; + + case COMMAND_COMPLETE: +#if defined(DEBUG_MSGI) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_msgi|debug_phases)) + printk("inbound message (COMMAND COMPLETE), "); +#endif + done++; + break; + + case MESSAGE_REJECT: + if(CURRENT_SC->SCp.phase & in_sync) { + CURRENT_SC->SCp.phase &= ~in_sync; + SYNCRATE=0x80; + printk("synchronous rejected, "); + } else + printk("inbound message (MESSAGE REJECT), "); +#if defined(DEBUG_MSGI) + if(HOSTDATA(shpnt)->debug & debug_msgi) + printk("inbound message (MESSAGE REJECT), "); +#endif + break; + + case SAVE_POINTERS: +#if defined(DEBUG_MSGI) + if(HOSTDATA(shpnt)->debug & debug_msgi) + printk("inbound message (SAVE DATA POINTERS), "); +#endif + break; + + case RESTORE_POINTERS: +#if defined(DEBUG_MSGI) + if(HOSTDATA(shpnt)->debug & debug_msgi) + printk("inbound message (RESTORE DATA POINTERS), "); +#endif + break; + + case EXTENDED_MESSAGE: + { + char buffer[16]; + int i; + +#if defined(DEBUG_MSGI) + if(HOSTDATA(shpnt)->debug & debug_msgi) + printk("inbound message (EXTENDED MESSAGE), "); +#endif + make_acklow(shpnt); + if(getphase(shpnt)!=P_MSGI) + break; + + buffer[0]=EXTENDED_MESSAGE; + buffer[1]=GETPORT(SCSIDAT); + + for(i=0; i<buffer[1] && + (make_acklow(shpnt), getphase(shpnt)==P_MSGI); i++) + buffer[2+i]=GETPORT(SCSIDAT); + +#if defined(DEBUG_MSGI) + if(HOSTDATA(shpnt)->debug & debug_msgi) + print_msg(buffer); +#endif + + switch(buffer [2]) { + case EXTENDED_SDTR: + { + long ticks; + + if(buffer[1]!=3) + aha152x_panic(shpnt, "SDTR message length != 3"); + + if(!HOSTDATA(shpnt)->synchronous) + break; + + printk("inbound SDTR: "); print_msg(buffer); + + ticks=(buffer[3]*4+49)/50; + + if(CURRENT_SC->SCp.phase & in_sync) { + /* we initiated SDTR */ + if(ticks>9 || buffer[4]<1 || buffer[4]>8) + aha152x_panic(shpnt, "received SDTR invalid"); + + SYNCRATE |= ((ticks-2)<<4) + buffer[4]; + } else if(ticks<=9 && buffer[4]>=1) { + if(buffer[4]>8) + buffer[4]=8; + + ADDMSG(EXTENDED_MESSAGE); + ADDMSG(3); + ADDMSG(EXTENDED_SDTR); + if(ticks<4) { + ticks=4; + ADDMSG(50); + } else + ADDMSG(buffer[3]); + + ADDMSG(buffer[4]); + + printk("outbound SDTR: "); + print_msg(&MSG(MSGLEN-5)); + + CURRENT_SC->SCp.phase |= in_sync; + + SYNCRATE |= ((ticks-2)<<4) + buffer[4]; + + start_sync++; + } else { + /* requested SDTR is too slow, do it asynchronously */ + ADDMSG(MESSAGE_REJECT); + SYNCRATE = 0; + } + + SETPORT(SCSIRATE, SYNCRATE&0x7f); + } + break; + + case EXTENDED_MODIFY_DATA_POINTER: + case EXTENDED_EXTENDED_IDENTIFY: + case EXTENDED_WDTR: + default: + ADDMSG(MESSAGE_REJECT); + break; + } + } + break; + + default: + printk("unsupported inbound message %x, ", CURRENT_SC->SCp.Message); + break; + + } + + make_acklow(shpnt); + phase=getphase(shpnt); + } + + if(start_sync) + CURRENT_SC->SCp.phase |= in_sync; + else + CURRENT_SC->SCp.phase &= ~in_sync; + + if(MSGLEN>0) + SETPORT(SCSISIG, P_MSGI|ATNO); + + /* clear SCSI fifo on BUSFREE */ + if(phase==P_BUSFREE) + SETPORT(SXFRCTL0, CH1|CLRCH1); + + if(CURRENT_SC->SCp.phase & disconnected) { + save_flags(flags); + cli(); +#if defined(DEBUG_QUEUES) + if(HOSTDATA(shpnt)->debug & debug_queues) + printk("d+, "); +#endif + append_SC(&DISCONNECTED_SC, CURRENT_SC); + CURRENT_SC->SCp.phase |= 1<<16; + CURRENT_SC = NULL; + restore_flags(flags); + + SETBITS(SCSISEQ, ENRESELI); + + SETPORT(SIMODE0, DISCONNECTED_SC ? ENSELDI : 0); + SETPORT(SIMODE1, ISSUE_SC ? ENBUSFREE : 0); + + SETBITS(DMACNTRL0, INTEN); + + return; + } + } + break; + + case P_STATUS: /* STATUS IN phase */ +#if defined(DEBUG_STATUS) || defined(DEBUG_INTR) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_status|debug_intr|debug_phases)) + printk("STATUS, "); +#endif + SETPORT(SXFRCTL0, CH1); + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENREQINIT|ENBUSFREE); + + if(TESTHI(SSTAT1, PHASEMIS)) + printk("aha152x: passing STATUS phase"); + + CURRENT_SC->SCp.Status = GETPORT(SCSIBUS); + make_acklow(shpnt); + getphase(shpnt); + +#if defined(DEBUG_STATUS) + if(HOSTDATA(shpnt)->debug & debug_status) { + printk("inbound status "); + print_status(CURRENT_SC->SCp.Status); + printk(", "); + } +#endif + break; + + case P_DATAI: /* DATA IN phase */ + { + int fifodata, data_count, done; + +#if defined(DEBUG_DATAI) || defined(DEBUG_INTR) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_datai|debug_intr|debug_phases)) + printk("DATA IN, "); +#endif + +#if 0 + if(GETPORT(FIFOSTAT) || GETPORT(SSTAT2) & (SFULL|SFCNT)) + printk("aha152x: P_DATAI: %d(%d) bytes left in FIFO, resetting\n", + GETPORT(FIFOSTAT), GETPORT(SSTAT2) & (SFULL|SFCNT)); +#endif + + /* reset host fifo */ + SETPORT(DMACNTRL0, RSTFIFO); + SETPORT(DMACNTRL0, RSTFIFO|ENDMA); + + SETPORT(SXFRCTL0, CH1|SCSIEN|DMAEN); + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENPHASEMIS|ENBUSFREE); + + /* done is set when the FIFO is empty after the target left DATA IN */ + done=0; + + /* while the target stays in DATA to transfer data */ + while (!done) { +#if defined(DEBUG_DATAI) + if(HOSTDATA(shpnt)->debug & debug_datai) + printk("expecting data, "); +#endif + /* wait for PHASEMIS or full FIFO */ + while(TESTLO(DMASTAT, DFIFOFULL|INTSTAT)) + barrier(); + +#if defined(DEBUG_DATAI) + if(HOSTDATA(shpnt)->debug & debug_datai) + printk("ok, "); +#endif + + if(TESTHI(DMASTAT, DFIFOFULL)) + fifodata=GETPORT(FIFOSTAT); + else { + /* wait for SCSI fifo to get empty */ + while(TESTLO(SSTAT2, SEMPTY)) + barrier(); + + /* rest of data in FIFO */ + fifodata=GETPORT(FIFOSTAT); +#if defined(DEBUG_DATAI) + if(HOSTDATA(shpnt)->debug & debug_datai) + printk("last transfer, "); +#endif + done=1; + } + +#if defined(DEBUG_DATAI) + if(HOSTDATA(shpnt)->debug & debug_datai) + printk("fifodata=%d, ", fifodata); +#endif + + while(fifodata && CURRENT_SC->SCp.this_residual) { + data_count=fifodata; + + /* limit data transfer to size of first sg buffer */ + if(data_count > CURRENT_SC->SCp.this_residual) + data_count = CURRENT_SC->SCp.this_residual; + + fifodata -= data_count; + +#if defined(DEBUG_DATAI) + if(HOSTDATA(shpnt)->debug & debug_datai) + printk("data_count=%d, ", data_count); +#endif + + if(data_count&1) { + /* get a single byte in byte mode */ + SETBITS(DMACNTRL0, _8BIT); + *CURRENT_SC->SCp.ptr++ = GETPORT(DATAPORT); + CURRENT_SC->SCp.this_residual--; + } + + if(data_count>1) { + CLRBITS(DMACNTRL0, _8BIT); + data_count >>= 1; /* Number of words */ + insw(DATAPORT, CURRENT_SC->SCp.ptr, data_count); +#if defined(DEBUG_DATAI) + if(HOSTDATA(shpnt)->debug & debug_datai) + /* show what comes with the last transfer */ + if(done) { +#if 0 + int i; + unsigned char *data; +#endif + + printk("data on last transfer (%d bytes) ", + 2*data_count); +#if 0 + printk("data on last transfer (%d bytes: ", + 2*data_count); + data = (unsigned char *) CURRENT_SC->SCp.ptr; + for(i=0; i<2*data_count; i++) + printk("%2x ", *data++); + printk("), "); +#endif + } +#endif + CURRENT_SC->SCp.ptr += 2 * data_count; + CURRENT_SC->SCp.this_residual -= 2 * data_count; + } + + /* if this buffer is full and there are more buffers left */ + if(!CURRENT_SC->SCp.this_residual && + CURRENT_SC->SCp.buffers_residual) { + /* advance to next buffer */ + CURRENT_SC->SCp.buffers_residual--; + CURRENT_SC->SCp.buffer++; + CURRENT_SC->SCp.ptr = CURRENT_SC->SCp.buffer->address; + CURRENT_SC->SCp.this_residual = CURRENT_SC->SCp.buffer->length; + } + } + + /* + * FIFO should be empty + */ + if(fifodata>0) { + printk("aha152x: more data than expected (%d bytes)\n", + GETPORT(FIFOSTAT)); + SETBITS(DMACNTRL0, _8BIT); + printk("aha152x: data ("); + while(fifodata--) + printk("%2x ", GETPORT(DATAPORT)); + printk(")\n"); + } + +#if defined(DEBUG_DATAI) + if(HOSTDATA(shpnt)->debug & debug_datai) + if(!fifodata) + printk("fifo empty, "); + else + printk("something left in fifo, "); +#endif + } + +#if defined(DEBUG_DATAI) + if((HOSTDATA(shpnt)->debug & debug_datai) && + (CURRENT_SC->SCp.buffers_residual || + CURRENT_SC->SCp.this_residual)) + printk("left buffers (buffers=%d, bytes=%d), ", + CURRENT_SC->SCp.buffers_residual, CURRENT_SC->SCp.this_residual); +#endif + /* transfer can be considered ended, when SCSIEN reads back zero */ + CLRBITS(SXFRCTL0, SCSIEN|DMAEN); + while(TESTHI(SXFRCTL0, SCSIEN)) + barrier(); + CLRBITS(DMACNTRL0, ENDMA); + +#if defined(DEBUG_DATAI) || defined(DEBUG_INTR) + if(HOSTDATA(shpnt)->debug & (debug_datai|debug_intr)) + printk("got %d bytes, ", GETSTCNT()); +#endif + + CURRENT_SC->SCp.have_data_in++; + } + break; + + case P_DATAO: /* DATA OUT phase */ + { + int data_count; + +#if defined(DEBUG_DATAO) || defined(DEBUG_INTR) || defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & (debug_datao|debug_intr|debug_phases)) + printk("DATA OUT, "); +#endif +#if defined(DEBUG_DATAO) + if(HOSTDATA(shpnt)->debug & debug_datao) + printk("got data to send (bytes=%d, buffers=%d), ", + CURRENT_SC->SCp.this_residual, + CURRENT_SC->SCp.buffers_residual); +#endif + + if(GETPORT(FIFOSTAT) || GETPORT(SSTAT2) & (SFULL|SFCNT)) { + printk("%d(%d) left in FIFO, ", + GETPORT(FIFOSTAT), GETPORT(SSTAT2) & (SFULL|SFCNT)); + aha152x_panic(shpnt, "FIFO should be empty"); + } + + SETPORT(SXFRCTL0, CH1|CLRSTCNT|CLRCH1); + SETPORT(SXFRCTL0, SCSIEN|DMAEN|CH1); + + SETPORT(DMACNTRL0, WRITE_READ|RSTFIFO); + SETPORT(DMACNTRL0, ENDMA|WRITE_READ); + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENPHASEMIS|ENBUSFREE); + + /* while current buffer is not empty or + there are more buffers to transfer */ + while(TESTLO(SSTAT1, PHASEMIS) && + (CURRENT_SC->SCp.this_residual || + CURRENT_SC->SCp.buffers_residual)) { +#if defined(DEBUG_DATAO) + if(HOSTDATA(shpnt)->debug & debug_datao) + printk("sending data (left: bytes=%d, buffers=%d), waiting, ", + CURRENT_SC->SCp.this_residual, + CURRENT_SC->SCp.buffers_residual); +#endif + /* transfer rest of buffer, but max. 128 byte */ + data_count = + CURRENT_SC->SCp.this_residual > 128 ? + 128 : CURRENT_SC->SCp.this_residual ; + +#if defined(DEBUG_DATAO) + if(HOSTDATA(shpnt)->debug & debug_datao) + printk("data_count=%d, ", data_count); +#endif + + if(data_count&1) { + /* put a single byte in byte mode */ + SETBITS(DMACNTRL0, _8BIT); + SETPORT(DATAPORT, *CURRENT_SC->SCp.ptr++); + CURRENT_SC->SCp.this_residual--; + } + if(data_count>1) { + CLRBITS(DMACNTRL0, _8BIT); + data_count >>= 1; /* number of words */ + outsw(DATAPORT, CURRENT_SC->SCp.ptr, data_count); + CURRENT_SC->SCp.ptr += 2 * data_count; + CURRENT_SC->SCp.this_residual -= 2 * data_count; + } + + /* wait for FIFO to get empty */ + while(TESTLO(DMASTAT, DFIFOEMP|INTSTAT)) + barrier(); + +#if defined(DEBUG_DATAO) + if(HOSTDATA(shpnt)->debug & debug_datao) + printk("fifo (%d bytes), transfered (%d bytes), ", + GETPORT(FIFOSTAT), GETSTCNT()); +#endif + + /* if this buffer is empty and there are more buffers left */ + if(TESTLO(SSTAT1, PHASEMIS) && + !CURRENT_SC->SCp.this_residual && + CURRENT_SC->SCp.buffers_residual) { + /* advance to next buffer */ + CURRENT_SC->SCp.buffers_residual--; + CURRENT_SC->SCp.buffer++; + CURRENT_SC->SCp.ptr = CURRENT_SC->SCp.buffer->address; + CURRENT_SC->SCp.this_residual = CURRENT_SC->SCp.buffer->length; + } + } + + if(CURRENT_SC->SCp.this_residual || CURRENT_SC->SCp.buffers_residual) { + /* target leaves DATA OUT for an other phase (perhaps disconnect) */ + + /* data in fifos has to be resend */ + data_count = GETPORT(SSTAT2) & (SFULL|SFCNT); + + data_count += GETPORT(FIFOSTAT) ; + CURRENT_SC->SCp.ptr -= data_count; + CURRENT_SC->SCp.this_residual += data_count; +#if defined(DEBUG_DATAO) + if(HOSTDATA(shpnt)->debug & debug_datao) + printk("left data (bytes=%d, buffers=%d), fifos (bytes=%d), " + "transfer incomplete, resetting fifo, ", + CURRENT_SC->SCp.this_residual, + CURRENT_SC->SCp.buffers_residual, + data_count); +#endif + SETPORT(DMACNTRL0, WRITE_READ|RSTFIFO); + CLRBITS(SXFRCTL0, SCSIEN|DMAEN); + CLRBITS(DMACNTRL0, ENDMA); + } else { +#if defined(DEBUG_DATAO) + if(HOSTDATA(shpnt)->debug & debug_datao) + printk("waiting for SCSI fifo to get empty, "); +#endif + /* wait for SCSI fifo to get empty */ + while(TESTLO(SSTAT2, SEMPTY)) + barrier(); +#if defined(DEBUG_DATAO) + if(HOSTDATA(shpnt)->debug & debug_datao) + printk("ok, left data (bytes=%d, buffers=%d) ", + CURRENT_SC->SCp.this_residual, + CURRENT_SC->SCp.buffers_residual); +#endif + CLRBITS(SXFRCTL0, SCSIEN|DMAEN); + + /* transfer can be considered ended, when SCSIEN reads back zero */ + while(TESTHI(SXFRCTL0, SCSIEN)) + barrier(); + + CLRBITS(DMACNTRL0, ENDMA); + } + +#if defined(DEBUG_DATAO) || defined(DEBUG_INTR) + if(HOSTDATA(shpnt)->debug & (debug_datao|debug_intr)) + printk("sent %d data bytes, ", GETSTCNT()); +#endif + } + break; + + case P_BUSFREE: /* BUSFREE */ +#if defined(DEBUG_RACE) + leave_driver("(BUSFREE) intr"); +#endif +#if defined(DEBUG_PHASES) + if(HOSTDATA(shpnt)->debug & debug_phases) + printk("unexpected BUS FREE, "); +#endif + CURRENT_SC->SCp.phase &= ~(P_MASK<<16); + + aha152x_done(shpnt, DID_ERROR << 16); /* Don't know any better */ + return; + break; + + case P_PARITY: /* parity error in DATA phase */ +#if defined(DEBUG_RACE) + leave_driver("(DID_PARITY) intr"); +#endif + printk("PARITY error in DATA phase, "); + + CURRENT_SC->SCp.phase &= ~(P_MASK<<16); + + SETBITS(DMACNTRL0, INTEN); + aha152x_done(shpnt, DID_PARITY << 16); + return; + break; + + default: + printk("aha152x: unexpected phase\n"); + break; + } + + if(done) { +#if defined(DEBUG_INTR) + if(HOSTDATA(shpnt)->debug & debug_intr) + printk("command done.\n"); +#endif +#if defined(DEBUG_RACE) + leave_driver("(done) intr"); +#endif + + SETPORT(SIMODE0, DISCONNECTED_SC ? ENSELDI : 0); + SETPORT(SIMODE1, ISSUE_SC ? ENBUSFREE : 0); + SETPORT(SCSISEQ, DISCONNECTED_SC ? ENRESELI : 0); + + SETBITS(DMACNTRL0, INTEN); + + aha152x_done(shpnt, + (CURRENT_SC->SCp.Status & 0xff) + | ((CURRENT_SC->SCp.Message & 0xff) << 8) + | (DID_OK << 16)); + +#if defined(DEBUG_RACE) + printk("done returned (DID_OK: Status=%x; Message=%x).\n", + CURRENT_SC->SCp.Status, CURRENT_SC->SCp.Message); +#endif + return; + } + + if(CURRENT_SC) + CURRENT_SC->SCp.phase |= 1<<16; + + SETPORT(SIMODE0, 0); + SETPORT(SIMODE1, ENPHASEMIS|ENBUSFREE); +#if defined(DEBUG_INTR) + if(HOSTDATA(shpnt)->debug & debug_intr) + disp_enintr(shpnt); +#endif +#if defined(DEBUG_RACE) + leave_driver("(PHASEEND) intr"); +#endif + + SETBITS(DMACNTRL0, INTEN); + return; +} + +/* + * Dump the current driver status and panic... + */ +static void aha152x_panic(struct Scsi_Host *shpnt, char *msg) +{ + printk("\naha152x: %s\n", msg); + show_queues(shpnt); + panic("aha152x panic"); +} + +/* + * Display registers of AIC-6260 + */ +static void disp_ports(struct Scsi_Host *shpnt) +{ +#ifdef DEBUG_AHA152X + int s; + +#ifdef SKIP_PORTS + if(HOSTDATA(shpnt)->debug & debug_skipports) + return; +#endif + + printk("\n%s: ", CURRENT_SC ? "on bus" : "waiting"); + + s=GETPORT(SCSISEQ); + printk("SCSISEQ ("); + if(s & TEMODEO) printk("TARGET MODE "); + if(s & ENSELO) printk("SELO "); + if(s & ENSELI) printk("SELI "); + if(s & ENRESELI) printk("RESELI "); + if(s & ENAUTOATNO) printk("AUTOATNO "); + if(s & ENAUTOATNI) printk("AUTOATNI "); + if(s & ENAUTOATNP) printk("AUTOATNP "); + if(s & SCSIRSTO) printk("SCSIRSTO "); + printk(");"); + + printk(" SCSISIG ("); + s=GETPORT(SCSISIG); + switch(s & P_MASK) { + case P_DATAO: + printk("DATA OUT"); + break; + case P_DATAI: + printk("DATA IN"); + break; + case P_CMD: + printk("COMMAND"); + break; + case P_STATUS: + printk("STATUS"); + break; + case P_MSGO: + printk("MESSAGE OUT"); + break; + case P_MSGI: + printk("MESSAGE IN"); + break; + default: + printk("*illegal*"); + break; + } + + printk("); "); + + printk("INTSTAT (%s); ", TESTHI(DMASTAT, INTSTAT) ? "hi" : "lo"); + + printk("SSTAT ("); + s=GETPORT(SSTAT0); + if(s & TARGET) printk("TARGET "); + if(s & SELDO) printk("SELDO "); + if(s & SELDI) printk("SELDI "); + if(s & SELINGO) printk("SELINGO "); + if(s & SWRAP) printk("SWRAP "); + if(s & SDONE) printk("SDONE "); + if(s & SPIORDY) printk("SPIORDY "); + if(s & DMADONE) printk("DMADONE "); + + s=GETPORT(SSTAT1); + if(s & SELTO) printk("SELTO "); + if(s & ATNTARG) printk("ATNTARG "); + if(s & SCSIRSTI) printk("SCSIRSTI "); + if(s & PHASEMIS) printk("PHASEMIS "); + if(s & BUSFREE) printk("BUSFREE "); + if(s & SCSIPERR) printk("SCSIPERR "); + if(s & PHASECHG) printk("PHASECHG "); + if(s & REQINIT) printk("REQINIT "); + printk("); "); + + + printk("SSTAT ("); + + s=GETPORT(SSTAT0) & GETPORT(SIMODE0); + + if(s & TARGET) printk("TARGET "); + if(s & SELDO) printk("SELDO "); + if(s & SELDI) printk("SELDI "); + if(s & SELINGO) printk("SELINGO "); + if(s & SWRAP) printk("SWRAP "); + if(s & SDONE) printk("SDONE "); + if(s & SPIORDY) printk("SPIORDY "); + if(s & DMADONE) printk("DMADONE "); + + s=GETPORT(SSTAT1) & GETPORT(SIMODE1); + + if(s & SELTO) printk("SELTO "); + if(s & ATNTARG) printk("ATNTARG "); + if(s & SCSIRSTI) printk("SCSIRSTI "); + if(s & PHASEMIS) printk("PHASEMIS "); + if(s & BUSFREE) printk("BUSFREE "); + if(s & SCSIPERR) printk("SCSIPERR "); + if(s & PHASECHG) printk("PHASECHG "); + if(s & REQINIT) printk("REQINIT "); + printk("); "); + + printk("SXFRCTL0 ("); + + s=GETPORT(SXFRCTL0); + if(s & SCSIEN) printk("SCSIEN "); + if(s & DMAEN) printk("DMAEN "); + if(s & CH1) printk("CH1 "); + if(s & CLRSTCNT) printk("CLRSTCNT "); + if(s & SPIOEN) printk("SPIOEN "); + if(s & CLRCH1) printk("CLRCH1 "); + printk("); "); + + printk("SIGNAL ("); + + s=GETPORT(SCSISIG); + if(s & ATNI) printk("ATNI "); + if(s & SELI) printk("SELI "); + if(s & BSYI) printk("BSYI "); + if(s & REQI) printk("REQI "); + if(s & ACKI) printk("ACKI "); + printk("); "); + + printk("SELID (%02x), ", GETPORT(SELID)); + + printk("SSTAT2 ("); + + s=GETPORT(SSTAT2); + if(s & SOFFSET) printk("SOFFSET "); + if(s & SEMPTY) printk("SEMPTY "); + if(s & SFULL) printk("SFULL "); + printk("); SFCNT (%d); ", s & (SFULL|SFCNT)); + + s=GETPORT(SSTAT3); + printk("SCSICNT (%d), OFFCNT(%d), ", (s&0xf0)>>4, s&0x0f); + + printk("SSTAT4 ("); + s=GETPORT(SSTAT4); + if(s & SYNCERR) printk("SYNCERR "); + if(s & FWERR) printk("FWERR "); + if(s & FRERR) printk("FRERR "); + printk("); "); + + printk("DMACNTRL0 ("); + s=GETPORT(DMACNTRL0); + printk("%s ", s & _8BIT ? "8BIT" : "16BIT"); + printk("%s ", s & DMA ? "DMA" : "PIO" ); + printk("%s ", s & WRITE_READ ? "WRITE" : "READ" ); + if(s & ENDMA) printk("ENDMA "); + if(s & INTEN) printk("INTEN "); + if(s & RSTFIFO) printk("RSTFIFO "); + if(s & SWINT) printk("SWINT "); + printk("); "); + + printk("DMASTAT ("); + s=GETPORT(DMASTAT); + if(s & ATDONE) printk("ATDONE "); + if(s & WORDRDY) printk("WORDRDY "); + if(s & DFIFOFULL) printk("DFIFOFULL "); + if(s & DFIFOEMP) printk("DFIFOEMP "); + printk(")"); + + printk("\n"); +#endif +} + +/* + * display enabled interrupts + */ +static void disp_enintr(struct Scsi_Host *shpnt) +{ + int s; + + printk("enabled interrupts ("); + + s=GETPORT(SIMODE0); + if(s & ENSELDO) printk("ENSELDO "); + if(s & ENSELDI) printk("ENSELDI "); + if(s & ENSELINGO) printk("ENSELINGO "); + if(s & ENSWRAP) printk("ENSWRAP "); + if(s & ENSDONE) printk("ENSDONE "); + if(s & ENSPIORDY) printk("ENSPIORDY "); + if(s & ENDMADONE) printk("ENDMADONE "); + + s=GETPORT(SIMODE1); + if(s & ENSELTIMO) printk("ENSELTIMO "); + if(s & ENATNTARG) printk("ENATNTARG "); + if(s & ENPHASEMIS) printk("ENPHASEMIS "); + if(s & ENBUSFREE) printk("ENBUSFREE "); + if(s & ENSCSIPERR) printk("ENSCSIPERR "); + if(s & ENPHASECHG) printk("ENPHASECHG "); + if(s & ENREQINIT) printk("ENREQINIT "); + printk(")\n"); +} + +#if defined(DEBUG_RACE) + +static const char *should_leave; +static int in_driver=0; + +/* + * Only one routine can be in the driver at once. + */ +static void enter_driver(const char *func) +{ + unsigned long flags; + + save_flags(flags); + cli(); + printk("aha152x: entering %s() (%x)\n", func, jiffies); + if(in_driver) { + printk("%s should leave first.\n", should_leave); + panic("aha152x: already in driver\n"); + } + + in_driver++; + should_leave=func; + restore_flags(flags); +} + +static void leave_driver(const char *func) +{ + unsigned long flags; + + save_flags(flags); + cli(); + printk("\naha152x: leaving %s() (%x)\n", func, jiffies); + if(!in_driver) { + printk("aha152x: %s already left.\n", should_leave); + panic("aha152x: %s already left driver.\n"); + } + + in_driver--; + should_leave=func; + restore_flags(flags); +} +#endif + +/* + * Show the command data of a command + */ +static void show_command(Scsi_Cmnd *ptr) +{ + printk("0x%08x: target=%d; lun=%d; cmnd=(", + (unsigned int) ptr, ptr->target, ptr->lun); + + print_command(ptr->cmnd); + + printk("); residual=%d; buffers=%d; phase |", + ptr->SCp.this_residual, ptr->SCp.buffers_residual); + + if(ptr->SCp.phase & not_issued ) printk("not issued|"); + if(ptr->SCp.phase & in_selection) printk("in selection|"); + if(ptr->SCp.phase & disconnected) printk("disconnected|"); + if(ptr->SCp.phase & aborted ) printk("aborted|"); + if(ptr->SCp.phase & sent_ident ) printk("send_ident|"); + if(ptr->SCp.phase & in_other) { + printk("; in other("); + switch((ptr->SCp.phase >> 16) & P_MASK) { + case P_DATAO: + printk("DATA OUT"); + break; + case P_DATAI: + printk("DATA IN"); + break; + case P_CMD: + printk("COMMAND"); + break; + case P_STATUS: + printk("STATUS"); + break; + case P_MSGO: + printk("MESSAGE OUT"); + break; + case P_MSGI: + printk("MESSAGE IN"); + break; + default: + printk("*illegal*"); + break; + } + printk(")"); + if(ptr->SCp.phase & (1<<16)) + printk("; phaseend"); + } + printk("; next=0x%08x\n", (unsigned int) ptr->host_scribble); +} + +/* + * Dump the queued data + */ +static void show_queues(struct Scsi_Host *shpnt) +{ + unsigned long flags; + Scsi_Cmnd *ptr; + + save_flags(flags); + cli(); + printk("QUEUE STATUS:\nissue_SC:\n"); + for(ptr=ISSUE_SC; ptr; ptr = (Scsi_Cmnd *) ptr->host_scribble) + show_command(ptr); + + printk("current_SC:\n"); + if(CURRENT_SC) + show_command(CURRENT_SC); + else + printk("none\n"); + + printk("disconnected_SC:\n"); + for(ptr=DISCONNECTED_SC; ptr; ptr = (Scsi_Cmnd *) ptr->host_scribble) + show_command(ptr); + + disp_ports(shpnt); + disp_enintr(shpnt); + restore_flags(flags); +} + +int aha152x_set_info(char *buffer, int length, struct Scsi_Host *shpnt) +{ + return(-ENOSYS); /* Currently this is a no-op */ +} + +#undef SPRINTF +#define SPRINTF(args...) pos += sprintf(pos, ## args) + +static int get_command(char *pos, Scsi_Cmnd *ptr) +{ + char *start = pos; + int i; + + SPRINTF("0x%08x: target=%d; lun=%d; cmnd=( ", + (unsigned int) ptr, ptr->target, ptr->lun); + + for(i=0; i<COMMAND_SIZE(ptr->cmnd[0]); i++) + SPRINTF("0x%02x ", ptr->cmnd[i]); + + SPRINTF("); residual=%d; buffers=%d; phase |", + ptr->SCp.this_residual, ptr->SCp.buffers_residual); + + if(ptr->SCp.phase & not_issued ) SPRINTF("not issued|"); + if(ptr->SCp.phase & in_selection) SPRINTF("in selection|"); + if(ptr->SCp.phase & disconnected) SPRINTF("disconnected|"); + if(ptr->SCp.phase & aborted ) SPRINTF("aborted|"); + if(ptr->SCp.phase & sent_ident ) SPRINTF("send_ident|"); + if(ptr->SCp.phase & in_other) { + SPRINTF("; in other("); + switch((ptr->SCp.phase >> 16) & P_MASK) { + case P_DATAO: + SPRINTF("DATA OUT"); + break; + case P_DATAI: + SPRINTF("DATA IN"); + break; + case P_CMD: + SPRINTF("COMMAND"); + break; + case P_STATUS: + SPRINTF("STATUS"); + break; + case P_MSGO: + SPRINTF("MESSAGE OUT"); + break; + case P_MSGI: + SPRINTF("MESSAGE IN"); + break; + default: + SPRINTF("*illegal*"); + break; + } + SPRINTF(")"); + if(ptr->SCp.phase & (1<<16)) + SPRINTF("; phaseend"); + } + SPRINTF("; next=0x%08x\n", (unsigned int) ptr->host_scribble); + + return(pos-start); +} + +static int get_ports(struct Scsi_Host *shpnt, char *pos) +{ + char *start = pos; + int s; + +#ifdef SKIP_PORTS + if(HOSTDATA(shpnt)->debug & debug_skipports) + return; +#endif + + SPRINTF("\n%s: ", CURRENT_SC ? "on bus" : "waiting"); + + s=GETPORT(SCSISEQ); + SPRINTF("SCSISEQ ("); + if(s & TEMODEO) SPRINTF("TARGET MODE "); + if(s & ENSELO) SPRINTF("SELO "); + if(s & ENSELI) SPRINTF("SELI "); + if(s & ENRESELI) SPRINTF("RESELI "); + if(s & ENAUTOATNO) SPRINTF("AUTOATNO "); + if(s & ENAUTOATNI) SPRINTF("AUTOATNI "); + if(s & ENAUTOATNP) SPRINTF("AUTOATNP "); + if(s & SCSIRSTO) SPRINTF("SCSIRSTO "); + SPRINTF(");"); + + SPRINTF(" SCSISIG ("); + s=GETPORT(SCSISIG); + switch(s & P_MASK) { + case P_DATAO: + SPRINTF("DATA OUT"); + break; + case P_DATAI: + SPRINTF("DATA IN"); + break; + case P_CMD: + SPRINTF("COMMAND"); + break; + case P_STATUS: + SPRINTF("STATUS"); + break; + case P_MSGO: + SPRINTF("MESSAGE OUT"); + break; + case P_MSGI: + SPRINTF("MESSAGE IN"); + break; + default: + SPRINTF("*illegal*"); + break; + } + + SPRINTF("); "); + + SPRINTF("INTSTAT (%s); ", TESTHI(DMASTAT, INTSTAT) ? "hi" : "lo"); + + SPRINTF("SSTAT ("); + s=GETPORT(SSTAT0); + if(s & TARGET) SPRINTF("TARGET "); + if(s & SELDO) SPRINTF("SELDO "); + if(s & SELDI) SPRINTF("SELDI "); + if(s & SELINGO) SPRINTF("SELINGO "); + if(s & SWRAP) SPRINTF("SWRAP "); + if(s & SDONE) SPRINTF("SDONE "); + if(s & SPIORDY) SPRINTF("SPIORDY "); + if(s & DMADONE) SPRINTF("DMADONE "); + + s=GETPORT(SSTAT1); + if(s & SELTO) SPRINTF("SELTO "); + if(s & ATNTARG) SPRINTF("ATNTARG "); + if(s & SCSIRSTI) SPRINTF("SCSIRSTI "); + if(s & PHASEMIS) SPRINTF("PHASEMIS "); + if(s & BUSFREE) SPRINTF("BUSFREE "); + if(s & SCSIPERR) SPRINTF("SCSIPERR "); + if(s & PHASECHG) SPRINTF("PHASECHG "); + if(s & REQINIT) SPRINTF("REQINIT "); + SPRINTF("); "); + + + SPRINTF("SSTAT ("); + + s=GETPORT(SSTAT0) & GETPORT(SIMODE0); + + if(s & TARGET) SPRINTF("TARGET "); + if(s & SELDO) SPRINTF("SELDO "); + if(s & SELDI) SPRINTF("SELDI "); + if(s & SELINGO) SPRINTF("SELINGO "); + if(s & SWRAP) SPRINTF("SWRAP "); + if(s & SDONE) SPRINTF("SDONE "); + if(s & SPIORDY) SPRINTF("SPIORDY "); + if(s & DMADONE) SPRINTF("DMADONE "); + + s=GETPORT(SSTAT1) & GETPORT(SIMODE1); + + if(s & SELTO) SPRINTF("SELTO "); + if(s & ATNTARG) SPRINTF("ATNTARG "); + if(s & SCSIRSTI) SPRINTF("SCSIRSTI "); + if(s & PHASEMIS) SPRINTF("PHASEMIS "); + if(s & BUSFREE) SPRINTF("BUSFREE "); + if(s & SCSIPERR) SPRINTF("SCSIPERR "); + if(s & PHASECHG) SPRINTF("PHASECHG "); + if(s & REQINIT) SPRINTF("REQINIT "); + SPRINTF("); "); + + SPRINTF("SXFRCTL0 ("); + + s=GETPORT(SXFRCTL0); + if(s & SCSIEN) SPRINTF("SCSIEN "); + if(s & DMAEN) SPRINTF("DMAEN "); + if(s & CH1) SPRINTF("CH1 "); + if(s & CLRSTCNT) SPRINTF("CLRSTCNT "); + if(s & SPIOEN) SPRINTF("SPIOEN "); + if(s & CLRCH1) SPRINTF("CLRCH1 "); + SPRINTF("); "); + + SPRINTF("SIGNAL ("); + + s=GETPORT(SCSISIG); + if(s & ATNI) SPRINTF("ATNI "); + if(s & SELI) SPRINTF("SELI "); + if(s & BSYI) SPRINTF("BSYI "); + if(s & REQI) SPRINTF("REQI "); + if(s & ACKI) SPRINTF("ACKI "); + SPRINTF("); "); + + SPRINTF("SELID (%02x), ", GETPORT(SELID)); + + SPRINTF("SSTAT2 ("); + + s=GETPORT(SSTAT2); + if(s & SOFFSET) SPRINTF("SOFFSET "); + if(s & SEMPTY) SPRINTF("SEMPTY "); + if(s & SFULL) SPRINTF("SFULL "); + SPRINTF("); SFCNT (%d); ", s & (SFULL|SFCNT)); + + s=GETPORT(SSTAT3); + SPRINTF("SCSICNT (%d), OFFCNT(%d), ", (s&0xf0)>>4, s&0x0f); + + SPRINTF("SSTAT4 ("); + s=GETPORT(SSTAT4); + if(s & SYNCERR) SPRINTF("SYNCERR "); + if(s & FWERR) SPRINTF("FWERR "); + if(s & FRERR) SPRINTF("FRERR "); + SPRINTF("); "); + + SPRINTF("DMACNTRL0 ("); + s=GETPORT(DMACNTRL0); + SPRINTF("%s ", s & _8BIT ? "8BIT" : "16BIT"); + SPRINTF("%s ", s & DMA ? "DMA" : "PIO" ); + SPRINTF("%s ", s & WRITE_READ ? "WRITE" : "READ" ); + if(s & ENDMA) SPRINTF("ENDMA "); + if(s & INTEN) SPRINTF("INTEN "); + if(s & RSTFIFO) SPRINTF("RSTFIFO "); + if(s & SWINT) SPRINTF("SWINT "); + SPRINTF("); "); + + SPRINTF("DMASTAT ("); + s=GETPORT(DMASTAT); + if(s & ATDONE) SPRINTF("ATDONE "); + if(s & WORDRDY) SPRINTF("WORDRDY "); + if(s & DFIFOFULL) SPRINTF("DFIFOFULL "); + if(s & DFIFOEMP) SPRINTF("DFIFOEMP "); + SPRINTF(")\n\n"); + + SPRINTF("enabled interrupts ("); + + s=GETPORT(SIMODE0); + if(s & ENSELDO) SPRINTF("ENSELDO "); + if(s & ENSELDI) SPRINTF("ENSELDI "); + if(s & ENSELINGO) SPRINTF("ENSELINGO "); + if(s & ENSWRAP) SPRINTF("ENSWRAP "); + if(s & ENSDONE) SPRINTF("ENSDONE "); + if(s & ENSPIORDY) SPRINTF("ENSPIORDY "); + if(s & ENDMADONE) SPRINTF("ENDMADONE "); + + s=GETPORT(SIMODE1); + if(s & ENSELTIMO) SPRINTF("ENSELTIMO "); + if(s & ENATNTARG) SPRINTF("ENATNTARG "); + if(s & ENPHASEMIS) SPRINTF("ENPHASEMIS "); + if(s & ENBUSFREE) SPRINTF("ENBUSFREE "); + if(s & ENSCSIPERR) SPRINTF("ENSCSIPERR "); + if(s & ENPHASECHG) SPRINTF("ENPHASECHG "); + if(s & ENREQINIT) SPRINTF("ENREQINIT "); + SPRINTF(")\n"); + + return (pos-start); +} + +#undef SPRINTF +#define SPRINTF(args...) do { if(pos < buffer + length) pos += sprintf(pos, ## args); } while(0) + +int aha152x_proc_info(char *buffer, char **start, + off_t offset, int length, int hostno, int inout) +{ + int i; + char *pos = buffer; + struct Scsi_Host *shpnt; + unsigned long flags; + Scsi_Cmnd *ptr; + + for(i=0, shpnt= (struct Scsi_Host *) NULL; i<IRQS; i++) + if(aha152x_host[i] && aha152x_host[i]->host_no == hostno) + shpnt=aha152x_host[i]; + + if(!shpnt) + return(-ESRCH); + + if(inout) /* Has data been written to the file ? */ + return(aha152x_set_info(buffer, length, shpnt)); + + SPRINTF(AHA152X_REVID "\n"); + + save_flags(flags); + cli(); + + SPRINTF("ioports 0x%04x to 0x%04x\n", + shpnt->io_port, shpnt->io_port+shpnt->n_io_port-1); + SPRINTF("interrupt 0x%02x\n", shpnt->irq); + SPRINTF("disconnection/reconnection %s\n", + HOSTDATA(shpnt)->reconnect ? "enabled" : "disabled"); + SPRINTF("parity checking %s\n", + HOSTDATA(shpnt)->parity ? "enabled" : "disabled"); + SPRINTF("synchronous transfers %s\n", + HOSTDATA(shpnt)->synchronous ? "enabled" : "disabled"); + SPRINTF("%d commands currently queued\n", HOSTDATA(shpnt)->commands); + + if(HOSTDATA(shpnt)->synchronous) { +#if 0 + SPRINTF("synchronously operating targets (tick=%ld ns):\n", + 250000000/loops_per_sec); + for(i=0; i<8; i++) + if(HOSTDATA(shpnt)->syncrate[i]&0x7f) + SPRINTF("target %d: period %dT/%ldns; req/ack offset %d\n", + i, + (((HOSTDATA(shpnt)->syncrate[i]&0x70)>>4)+2), + (((HOSTDATA(shpnt)->syncrate[i]&0x70)>>4)+2)* + 250000000/loops_per_sec, + HOSTDATA(shpnt)->syncrate[i]&0x0f); +#else + SPRINTF("synchronously operating targets (tick=50 ns):\n"); + for(i=0; i<8; i++) + if(HOSTDATA(shpnt)->syncrate[i]&0x7f) + SPRINTF("target %d: period %dT/%dns; req/ack offset %d\n", + i, + (((HOSTDATA(shpnt)->syncrate[i]&0x70)>>4)+2), + (((HOSTDATA(shpnt)->syncrate[i]&0x70)>>4)+2)*50, + HOSTDATA(shpnt)->syncrate[i]&0x0f); +#endif + } + +#ifdef DEBUG_AHA152X +#define PDEBUG(flags,txt) if(HOSTDATA(shpnt)->debug & flags) SPRINTF("(%s) ", txt); + + SPRINTF("enabled debugging options: "); + + PDEBUG(debug_skipports, "skip ports"); + PDEBUG(debug_queue, "queue"); + PDEBUG(debug_intr, "interrupt"); + PDEBUG(debug_selection, "selection"); + PDEBUG(debug_msgo, "message out"); + PDEBUG(debug_msgi, "message in"); + PDEBUG(debug_status, "status"); + PDEBUG(debug_cmd, "command"); + PDEBUG(debug_datai, "data in"); + PDEBUG(debug_datao, "data out"); + PDEBUG(debug_abort, "abort"); + PDEBUG(debug_done, "done"); + PDEBUG(debug_biosparam, "bios parameters"); + PDEBUG(debug_phases, "phases"); + PDEBUG(debug_queues, "queues"); + PDEBUG(debug_reset, "reset"); + + SPRINTF("\n"); +#endif + + SPRINTF("\nqueue status:\n"); + if(ISSUE_SC) { + SPRINTF("not yet issued commands:\n"); + for(ptr=ISSUE_SC; ptr; ptr = (Scsi_Cmnd *) ptr->host_scribble) + pos += get_command(pos, ptr); + } else + SPRINTF("no not yet issued commands\n"); + + if(CURRENT_SC) { + SPRINTF("current command:\n"); + pos += get_command(pos, CURRENT_SC); + } else + SPRINTF("no current command\n"); + + if(DISCONNECTED_SC) { + SPRINTF("disconnected commands:\n"); + for(ptr=DISCONNECTED_SC; ptr; ptr = (Scsi_Cmnd *) ptr->host_scribble) + pos += get_command(pos, ptr); + } else + SPRINTF("no disconnected commands\n"); + + restore_flags(flags); + + pos += get_ports(shpnt, pos); + + *start=buffer+offset; + if (pos - buffer < offset) + return 0; + else if (pos - buffer - offset < length) + return pos - buffer - offset; + else + return length; +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = AHA152X; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/aha152x.h b/linux/src/drivers/scsi/aha152x.h new file mode 100644 index 00000000..ca1a202d --- /dev/null +++ b/linux/src/drivers/scsi/aha152x.h @@ -0,0 +1,357 @@ +#ifndef _AHA152X_H +#define _AHA152X_H + +/* + * $Id: aha152x.h,v 1.1 1999/04/26 05:54:10 tb Exp $ + */ + +#if defined(__KERNEL__) + +#include <linux/blk.h> +#include "scsi.h" +#include <asm/io.h> + +int aha152x_detect(Scsi_Host_Template *); +int aha152x_command(Scsi_Cmnd *); +int aha152x_queue(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int aha152x_abort(Scsi_Cmnd *); +int aha152x_reset(Scsi_Cmnd *, unsigned int); +int aha152x_biosparam(Disk *, kdev_t, int*); +int aha152x_proc_info(char *buffer, char **start, off_t offset, int length, int hostno, int inout); + +/* number of queueable commands + (unless we support more than 1 cmd_per_lun this should do) */ +#define AHA152X_MAXQUEUE 7 + +#define AHA152X_REVID "Adaptec 152x SCSI driver; $Revision: 1.1 $" + +extern struct proc_dir_entry proc_scsi_aha152x; + +/* Initial value of Scsi_Host entry */ +#define AHA152X { /* next */ 0, \ + /* usage_count */ 0, \ + /* proc_dir */ &proc_scsi_aha152x, \ + /* proc_info */ aha152x_proc_info, \ + /* name */ AHA152X_REVID, \ + /* detect */ aha152x_detect, \ + /* release */ 0, \ + /* info */ 0, \ + /* command */ aha152x_command, \ + /* queuecommand */ aha152x_queue, \ + /* abort */ aha152x_abort, \ + /* reset */ aha152x_reset, \ + /* slave_attach */ 0, \ + /* bios_param */ aha152x_biosparam, \ + /* can_queue */ 1, \ + /* this_id */ 7, \ + /* sg_tablesize */ SG_ALL, \ + /* cmd_per_lun */ 1, \ + /* present */ 0, \ + /* unchecked_isa_dma */ 0, \ + /* use_clustering */ DISABLE_CLUSTERING } +#endif + + +/* port addresses */ +#define SCSISEQ (shpnt->io_port+0x00) /* SCSI sequence control */ +#define SXFRCTL0 (shpnt->io_port+0x01) /* SCSI transfer control 0 */ +#define SXFRCTL1 (shpnt->io_port+0x02) /* SCSI transfer control 1 */ +#define SCSISIG (shpnt->io_port+0x03) /* SCSI signal in/out */ +#define SCSIRATE (shpnt->io_port+0x04) /* SCSI rate control */ +#define SELID (shpnt->io_port+0x05) /* selection/reselection ID */ +#define SCSIID SELID /* SCSI ID */ +#define SCSIDAT (shpnt->io_port+0x06) /* SCSI latched data */ +#define SCSIBUS (shpnt->io_port+0x07) /* SCSI data bus */ +#define STCNT0 (shpnt->io_port+0x08) /* SCSI transfer count 0 */ +#define STCNT1 (shpnt->io_port+0x09) /* SCSI transfer count 1 */ +#define STCNT2 (shpnt->io_port+0x0a) /* SCSI transfer count 2 */ +#define SSTAT0 (shpnt->io_port+0x0b) /* SCSI interrupt status 0 */ +#define SSTAT1 (shpnt->io_port+0x0c) /* SCSI interrupt status 1 */ +#define SSTAT2 (shpnt->io_port+0x0d) /* SCSI interrupt status 2 */ +#define SCSITEST (shpnt->io_port+0x0e) /* SCSI test control */ +#define SSTAT3 SCSITEST /* SCSI interrupt status 3 */ +#define SSTAT4 (shpnt->io_port+0x0f) /* SCSI status 4 */ +#define SIMODE0 (shpnt->io_port+0x10) /* SCSI interrupt mode 0 */ +#define SIMODE1 (shpnt->io_port+0x11) /* SCSI interrupt mode 1 */ +#define DMACNTRL0 (shpnt->io_port+0x12) /* DMA control 0 */ +#define DMACNTRL1 (shpnt->io_port+0x13) /* DMA control 1 */ +#define DMASTAT (shpnt->io_port+0x14) /* DMA status */ +#define FIFOSTAT (shpnt->io_port+0x15) /* FIFO status */ +#define DATAPORT (shpnt->io_port+0x16) /* DATA port */ +#define BRSTCNTRL (shpnt->io_port+0x18) /* burst control */ +#define PORTA (shpnt->io_port+0x1a) /* PORT A */ +#define PORTB (shpnt->io_port+0x1b) /* PORT B */ +#define REV (shpnt->io_port+0x1c) /* revision */ +#define STACK (shpnt->io_port+0x1d) /* stack */ +#define TEST (shpnt->io_port+0x1e) /* test register */ + +/* used in aha152x_porttest */ +#define O_PORTA 0x1a /* PORT A */ +#define O_PORTB 0x1b /* PORT B */ +#define O_DMACNTRL1 0x13 /* DMA control 1 */ +#define O_STACK 0x1d /* stack */ +#define IO_RANGE 0x20 + +/* bits and bitmasks to ports */ + +/* SCSI sequence control */ +#define TEMODEO 0x80 +#define ENSELO 0x40 +#define ENSELI 0x20 +#define ENRESELI 0x10 +#define ENAUTOATNO 0x08 +#define ENAUTOATNI 0x04 +#define ENAUTOATNP 0x02 +#define SCSIRSTO 0x01 + +/* SCSI transfer control 0 */ +#define SCSIEN 0x80 +#define DMAEN 0x40 +#define CH1 0x20 +#define CLRSTCNT 0x10 +#define SPIOEN 0x08 +#define CLRCH1 0x02 + +/* SCSI transfer control 1 */ +#define BITBUCKET 0x80 +#define SWRAPEN 0x40 +#define ENSPCHK 0x20 +#define STIMESEL 0x18 /* mask */ +#define STIMESEL_ 3 +#define ENSTIMER 0x04 +#define BYTEALIGN 0x02 + +/* SCSI signal IN */ +#define CDI 0x80 +#define IOI 0x40 +#define MSGI 0x20 +#define ATNI 0x10 +#define SELI 0x08 +#define BSYI 0x04 +#define REQI 0x02 +#define ACKI 0x01 + +/* SCSI Phases */ +#define P_MASK (MSGI|CDI|IOI) +#define P_DATAO (0) +#define P_DATAI (IOI) +#define P_CMD (CDI) +#define P_STATUS (CDI|IOI) +#define P_MSGO (MSGI|CDI) +#define P_MSGI (MSGI|CDI|IOI) + +/* SCSI signal OUT */ +#define CDO 0x80 +#define IOO 0x40 +#define MSGO 0x20 +#define ATNO 0x10 +#define SELO 0x08 +#define BSYO 0x04 +#define REQO 0x02 +#define ACKO 0x01 + +/* SCSI rate control */ +#define SXFR 0x70 /* mask */ +#define SXFR_ 4 +#define SOFS 0x0f /* mask */ + +/* SCSI ID */ +#define OID 0x70 +#define OID_ 4 +#define TID 0x07 + +/* SCSI transfer count */ +#define GETSTCNT() ( (GETPORT(STCNT2)<<16) \ + + (GETPORT(STCNT1)<< 8) \ + + GETPORT(STCNT0) ) + +#define SETSTCNT(X) { SETPORT(STCNT2, ((X) & 0xFF0000) >> 16); \ + SETPORT(STCNT1, ((X) & 0x00FF00) >> 8); \ + SETPORT(STCNT0, ((X) & 0x0000FF) ); } + +/* SCSI interrupt status */ +#define TARGET 0x80 +#define SELDO 0x40 +#define SELDI 0x20 +#define SELINGO 0x10 +#define SWRAP 0x08 +#define SDONE 0x04 +#define SPIORDY 0x02 +#define DMADONE 0x01 + +#define SETSDONE 0x80 +#define CLRSELDO 0x40 +#define CLRSELDI 0x20 +#define CLRSELINGO 0x10 +#define CLRSWRAP 0x08 +#define CLRSDONE 0x04 +#define CLRSPIORDY 0x02 +#define CLRDMADONE 0x01 + +/* SCSI status 1 */ +#define SELTO 0x80 +#define ATNTARG 0x40 +#define SCSIRSTI 0x20 +#define PHASEMIS 0x10 +#define BUSFREE 0x08 +#define SCSIPERR 0x04 +#define PHASECHG 0x02 +#define REQINIT 0x01 + +#define CLRSELTIMO 0x80 +#define CLRATNO 0x40 +#define CLRSCSIRSTI 0x20 +#define CLRBUSFREE 0x08 +#define CLRSCSIPERR 0x04 +#define CLRPHASECHG 0x02 +#define CLRREQINIT 0x01 + +/* SCSI status 2 */ +#define SOFFSET 0x20 +#define SEMPTY 0x10 +#define SFULL 0x08 +#define SFCNT 0x07 /* mask */ + +/* SCSI status 3 */ +#define SCSICNT 0xf0 /* mask */ +#define SCSICNT_ 4 +#define OFFCNT 0x0f /* mask */ + +/* SCSI TEST control */ +#define SCTESTU 0x08 +#define SCTESTD 0x04 +#define STCTEST 0x01 + +/* SCSI status 4 */ +#define SYNCERR 0x04 +#define FWERR 0x02 +#define FRERR 0x01 + +#define CLRSYNCERR 0x04 +#define CLRFWERR 0x02 +#define CLRFRERR 0x01 + +/* SCSI interrupt mode 0 */ +#define ENSELDO 0x40 +#define ENSELDI 0x20 +#define ENSELINGO 0x10 +#define ENSWRAP 0x08 +#define ENSDONE 0x04 +#define ENSPIORDY 0x02 +#define ENDMADONE 0x01 + +/* SCSI interrupt mode 1 */ +#define ENSELTIMO 0x80 +#define ENATNTARG 0x40 +#define ENSCSIRST 0x20 +#define ENPHASEMIS 0x10 +#define ENBUSFREE 0x08 +#define ENSCSIPERR 0x04 +#define ENPHASECHG 0x02 +#define ENREQINIT 0x01 + +/* DMA control 0 */ +#define ENDMA 0x80 +#define _8BIT 0x40 +#define DMA 0x20 +#define WRITE_READ 0x08 +#define INTEN 0x04 +#define RSTFIFO 0x02 +#define SWINT 0x01 + +/* DMA control 1 */ +#define PWRDWN 0x80 +#define STK 0x07 /* mask */ + +/* DMA status */ +#define ATDONE 0x80 +#define WORDRDY 0x40 +#define INTSTAT 0x20 +#define DFIFOFULL 0x10 +#define DFIFOEMP 0x08 + +/* BURST control */ +#define BON 0xf0 +#define BOFF 0x0f + +/* TEST REGISTER */ +#define BOFFTMR 0x40 +#define BONTMR 0x20 +#define STCNTH 0x10 +#define STCNTM 0x08 +#define STCNTL 0x04 +#define SCSIBLK 0x02 +#define DMABLK 0x01 + +/* On the AHA-152x board PORTA and PORTB contain + some information about the board's configuration. */ +typedef union { + struct { + unsigned reserved:2; /* reserved */ + unsigned tardisc:1; /* Target disconnect: 0=disabled, 1=enabled */ + unsigned syncneg:1; /* Initial sync neg: 0=disabled, 1=enabled */ + unsigned msgclasses:2; /* Message classes + 0=#4 + 1=#0, #1, #2, #3, #4 + 2=#0, #3, #4 + 3=#0, #4 + */ + unsigned boot:1; /* boot: 0=disabled, 1=enabled */ + unsigned dma:1; /* Transfer mode: 0=PIO; 1=DMA */ + unsigned id:3; /* SCSI-id */ + unsigned irq:2; /* IRQ-Channel: 0,3=12, 1=10, 2=11 */ + unsigned dmachan:2; /* DMA-Channel: 0=0, 1=5, 2=6, 3=7 */ + unsigned parity:1; /* SCSI-parity: 1=enabled 0=disabled */ + } fields; + unsigned short port; +} aha152x_config ; + +#define cf_parity fields.parity +#define cf_dmachan fields.dmachan +#define cf_irq fields.irq +#define cf_id fields.id +#define cf_dma fields.dma +#define cf_boot fields.boot +#define cf_msgclasses fields.msgclasses +#define cf_syncneg fields.syncneg +#define cf_tardisc fields.tardisc +#define cf_port port + +/* Some macros to manipulate ports and their bits */ + +#define SETPORT(PORT, VAL) outb( (VAL), (PORT) ) +#define SETPORTP(PORT, VAL) outb_p( (VAL), (PORT) ) +#define SETPORTW(PORT, VAL) outw( (VAL), (PORT) ) + +#define GETPORT(PORT) inb( PORT ) +#define GETPORTW(PORT) inw( PORT ) + +#define SETBITS(PORT, BITS) outb( (inb(PORT) | (BITS)), (PORT) ) +#define CLRBITS(PORT, BITS) outb( (inb(PORT) & ~(BITS)), (PORT) ) +#define CLRSETBITS(PORT, CLR, SET) outb( (inb(PORT) & ~(CLR)) | (SET) , (PORT) ) + +#define TESTHI(PORT, BITS) ((inb(PORT) & (BITS)) == BITS) +#define TESTLO(PORT, BITS) ((inb(PORT) & (BITS)) == 0) + +#ifdef DEBUG_AHA152X +enum { + debug_skipports = 0x0001, + debug_queue = 0x0002, + debug_intr = 0x0004, + debug_selection = 0x0008, + debug_msgo = 0x0010, + debug_msgi = 0x0020, + debug_status = 0x0040, + debug_cmd = 0x0080, + debug_datai = 0x0100, + debug_datao = 0x0200, + debug_abort = 0x0400, + debug_done = 0x0800, + debug_biosparam = 0x1000, + debug_phases = 0x2000, + debug_queues = 0x4000, + debug_reset = 0x8000, +}; +#endif + +#endif /* _AHA152X_H */ diff --git a/linux/src/drivers/scsi/aha1542.c b/linux/src/drivers/scsi/aha1542.c new file mode 100644 index 00000000..0366fd31 --- /dev/null +++ b/linux/src/drivers/scsi/aha1542.c @@ -0,0 +1,1325 @@ +/* $Id: aha1542.c,v 1.1 1999/04/26 05:54:11 tb Exp $ + * linux/kernel/aha1542.c + * + * Copyright (C) 1992 Tommy Thorn + * Copyright (C) 1993, 1994, 1995 Eric Youngdale + * + * Modified by Eric Youngdale + * Use request_irq and request_dma to help prevent unexpected conflicts + * Set up on-board DMA controller, such that we do not have to + * have the bios enabled to use the aha1542. + * Modified by David Gentzel + * Don't call request_dma if dma mask is 0 (for BusLogic BT-445S VL-Bus + * controller). + * Modified by Matti Aarnio + * Accept parameters from LILO cmd-line. -- 1-Oct-94 + * Modified by Mike McLagan <mike.mclagan@linux.org> + * Recognise extended mode on AHA1542CP, different bit than 1542CF + * 1-Jan-97 + * Modified by Bjorn L. Thordarson and Einar Thor Einarsson + * Recognize that DMA0 is valid DMA channel -- 13-Jul-98 + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/head.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <asm/dma.h> +#include <asm/system.h> +#include <asm/io.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" + + +#include "aha1542.h" + +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_aha1542 = { + PROC_SCSI_AHA1542, 7, "aha1542", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#ifdef DEBUG +#define DEB(x) x +#else +#define DEB(x) +#endif +/* +static const char RCSid[] = "$Header: cvs/gnumach/linux/src/drivers/scsi/Attic/aha1542.c,v 1.1 1999/04/26 05:54:11 tb Exp $"; +*/ + +/* The adaptec can be configured for quite a number of addresses, but +I generally do not want the card poking around at random. We allow +two addresses - this allows people to use the Adaptec with a Midi +card, which also used 0x330 -- can be overridden with LILO! */ + +#define MAXBOARDS 2 /* Increase this and the sizes of the + arrays below, if you need more.. */ + +static unsigned int bases[MAXBOARDS]={0x330, 0x334}; + +/* set by aha1542_setup according to the command line */ +static int setup_called[MAXBOARDS] = {0,0}; +static int setup_buson[MAXBOARDS] = {0,0}; +static int setup_busoff[MAXBOARDS] = {0,0}; +static int setup_dmaspeed[MAXBOARDS] = {-1,-1}; + +static char *setup_str[MAXBOARDS] = {(char *)NULL,(char *)NULL}; + +/* + * LILO params: aha1542=<PORTBASE>[,<BUSON>,<BUSOFF>[,<DMASPEED>]] + * + * Where: <PORTBASE> is any of the valid AHA addresses: + * 0x130, 0x134, 0x230, 0x234, 0x330, 0x334 + * <BUSON> is the time (in microsecs) that AHA spends on the AT-bus + * when transferring data. 1542A power-on default is 11us, + * valid values are in range: 2..15 (decimal) + * <BUSOFF> is the time that AHA spends OFF THE BUS after while + * it is transferring data (not to monopolize the bus). + * Power-on default is 4us, valid range: 1..64 microseconds. + * <DMASPEED> Default is jumper selected (1542A: on the J1), + * but experimenter can alter it with this. + * Valid values: 5, 6, 7, 8, 10 (MB/s) + * Factory default is 5 MB/s. + */ + +#define BIOS_TRANSLATION_1632 0 /* Used by some old 1542A boards */ +#define BIOS_TRANSLATION_6432 1 /* Default case these days */ +#define BIOS_TRANSLATION_25563 2 /* Big disk case */ + +struct aha1542_hostdata{ + /* This will effectively start both of them at the first mailbox */ + int bios_translation; /* Mapping bios uses - for compatibility */ + int aha1542_last_mbi_used; + int aha1542_last_mbo_used; + Scsi_Cmnd * SCint[AHA1542_MAILBOXES]; + struct mailbox mb[2*AHA1542_MAILBOXES]; + struct ccb ccb[AHA1542_MAILBOXES]; +}; + +#define HOSTDATA(host) ((struct aha1542_hostdata *) &host->hostdata) + +static struct Scsi_Host * aha_host[7] = {NULL,}; /* One for each IRQ level (9-15) */ + + + + +#define WAITnexttimeout 3000000 + +static void setup_mailboxes(int base_io, struct Scsi_Host * shpnt); +static int aha1542_restart(struct Scsi_Host * shost); + +#define aha1542_intr_reset(base) outb(IRST, CONTROL(base)) + +#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; \ + } \ + } + +/* Similar to WAIT, except we use the udelay call to regulate the + amount of time we wait. */ +#define WAITd(port, mask, allof, noneof, timeout) \ + { register WAITbits; \ + register WAITtimeout = timeout; \ + while (1) { \ + WAITbits = inb(port) & (mask); \ + if ((WAITbits & (allof)) == (allof) && ((WAITbits & (noneof)) == 0)) \ + break; \ + udelay(1000); \ + if (--WAITtimeout == 0) goto fail; \ + } \ + } + +static void aha1542_stat(void) +{ +/* int s = inb(STATUS), i = inb(INTRFLAGS); + printk("status=%x intrflags=%x\n", s, i, WAITnexttimeout-WAITtimeout); */ +} + +/* This is a bit complicated, but we need to make sure that an interrupt + routine does not send something out while we are in the middle of this. + Fortunately, it is only at boot time that multi-byte messages + are ever sent. */ +static int aha1542_out(unsigned int base, unchar *cmdp, int len) +{ + unsigned long flags = 0; + + save_flags(flags); + if(len == 1) { + while(1==1){ + WAIT(STATUS(base), CDF, 0, CDF); + cli(); + if(inb(STATUS(base)) & CDF) {restore_flags(flags); continue;} + outb(*cmdp, DATA(base)); + restore_flags(flags); + return 0; + } + } else { + cli(); + while (len--) + { + WAIT(STATUS(base), CDF, 0, CDF); + outb(*cmdp++, DATA(base)); + } + restore_flags(flags); + } + return 0; + fail: + restore_flags(flags); + printk("aha1542_out failed(%d): ", len+1); aha1542_stat(); + return 1; +} + +/* Only used at boot time, so we do not need to worry about latency as much + here */ +static int aha1542_in(unsigned int base, unchar *cmdp, int len) +{ + unsigned long flags; + + save_flags(flags); + cli(); + while (len--) + { + WAIT(STATUS(base), DF, DF, 0); + *cmdp++ = inb(DATA(base)); + } + restore_flags(flags); + return 0; + fail: + restore_flags(flags); + printk("aha1542_in failed(%d): ", len+1); aha1542_stat(); + return 1; +} + +/* Similar to aha1542_in, except that we wait a very short period of time. + We use this if we know the board is alive and awake, but we are not sure + if the board will respond to the command we are about to send or not */ +static int aha1542_in1(unsigned int base, unchar *cmdp, int len) +{ + unsigned long flags; + + save_flags(flags); + cli(); + while (len--) + { + WAITd(STATUS(base), DF, DF, 0, 100); + *cmdp++ = inb(DATA(base)); + } + restore_flags(flags); + return 0; + fail: + restore_flags(flags); + return 1; +} + +static int makecode(unsigned hosterr, unsigned scsierr) +{ + switch (hosterr) { + case 0x0: + case 0xa: /* Linked command complete without error and linked normally */ + case 0xb: /* Linked command complete without error, interrupt generated */ + hosterr = 0; + break; + + case 0x11: /* Selection time out-The initiator selection or target + reselection was not complete within the SCSI Time out period */ + hosterr = DID_TIME_OUT; + break; + + case 0x12: /* Data overrun/underrun-The target attempted to transfer more data + than was allocated by the Data Length field or the sum of the + Scatter / Gather Data Length fields. */ + + case 0x13: /* Unexpected bus free-The target dropped the SCSI BSY at an unexpected time. */ + + case 0x15: /* MBO command was not 00, 01 or 02-The first byte of the CB was + invalid. This usually indicates a software failure. */ + + case 0x16: /* Invalid CCB Operation Code-The first byte of the CCB was invalid. + This usually indicates a software failure. */ + + case 0x17: /* Linked CCB does not have the same LUN-A subsequent CCB of a set + of linked CCB's does not specify the same logical unit number as + the first. */ + case 0x18: /* Invalid Target Direction received from Host-The direction of a + Target Mode CCB was invalid. */ + + case 0x19: /* Duplicate CCB Received in Target Mode-More than once CCB was + received to service data transfer between the same target LUN + and initiator SCSI ID in the same direction. */ + + case 0x1a: /* Invalid CCB or Segment List Parameter-A segment list with a zero + length segment or invalid segment list boundaries was received. + A CCB parameter was invalid. */ + DEB(printk("Aha1542: %x %x\n", hosterr, scsierr)); + hosterr = DID_ERROR; /* Couldn't find any better */ + break; + + case 0x14: /* Target bus phase sequence failure-An invalid bus phase or bus + phase sequence was requested by the target. The host adapter + will generate a SCSI Reset Condition, notifying the host with + a SCRD interrupt */ + hosterr = DID_RESET; + break; + default: + printk("makecode: unknown hoststatus %x\n", hosterr); + break; + } + return scsierr|(hosterr << 16); +} + +static int aha1542_test_port(int bse, struct Scsi_Host * shpnt) +{ + int i; + unchar inquiry_cmd[] = {CMD_INQUIRY }; + unchar inquiry_result[4]; + unchar *cmdp; + int len; + volatile int debug = 0; + + /* Quick and dirty test for presence of the card. */ + if(inb(STATUS(bse)) == 0xff) return 0; + + /* Reset the adapter. I ought to make a hard reset, but it's not really necessary */ + + /* DEB(printk("aha1542_test_port called \n")); */ + + /* In case some other card was probing here, reset interrupts */ + aha1542_intr_reset(bse); /* reset interrupts, so they don't block */ + + outb(SRST|IRST/*|SCRST*/, CONTROL(bse)); + + i = jiffies + 2; + while (i>jiffies); /* Wait a little bit for things to settle down. */ + + debug = 1; + /* Expect INIT and IDLE, any of the others are bad */ + WAIT(STATUS(bse), STATMASK, INIT|IDLE, STST|DIAGF|INVDCMD|DF|CDF); + + debug = 2; + /* Shouldn't have generated any interrupts during reset */ + if (inb(INTRFLAGS(bse))&INTRMASK) goto fail; + + + /* Perform a host adapter inquiry instead so we do not need to set + up the mailboxes ahead of time */ + + aha1542_out(bse, inquiry_cmd, 1); + + debug = 3; + len = 4; + cmdp = &inquiry_result[0]; + + while (len--) + { + WAIT(STATUS(bse), DF, DF, 0); + *cmdp++ = inb(DATA(bse)); + } + + debug = 8; + /* Reading port should reset DF */ + if (inb(STATUS(bse)) & DF) goto fail; + + debug = 9; + /* When HACC, command is completed, and we're though testing */ + WAIT(INTRFLAGS(bse), HACC, HACC, 0); + /* now initialize adapter */ + + debug = 10; + /* Clear interrupts */ + outb(IRST, CONTROL(bse)); + + debug = 11; + + return debug; /* 1 = ok */ + fail: + return 0; /* 0 = not ok */ +} + +/* A "high" level interrupt handler */ +static void aha1542_intr_handle(int irq, void *dev_id, struct pt_regs *regs) +{ + void (*my_done)(Scsi_Cmnd *) = NULL; + int errstatus, mbi, mbo, mbistatus; + int number_serviced; + unsigned int flags; + struct Scsi_Host * shost; + Scsi_Cmnd * SCtmp; + int flag; + int needs_restart; + struct mailbox * mb; + struct ccb *ccb; + + shost = aha_host[irq - 9]; + if(!shost) panic("Splunge!"); + + mb = HOSTDATA(shost)->mb; + ccb = HOSTDATA(shost)->ccb; + +#ifdef DEBUG + { + flag = inb(INTRFLAGS(shost->io_port)); + printk("aha1542_intr_handle: "); + if (!(flag&ANYINTR)) printk("no interrupt?"); + if (flag&MBIF) printk("MBIF "); + if (flag&MBOA) printk("MBOF "); + if (flag&HACC) printk("HACC "); + if (flag&SCRD) printk("SCRD "); + printk("status %02x\n", inb(STATUS(shost->io_port))); + }; +#endif + number_serviced = 0; + needs_restart = 0; + + while(1==1){ + flag = inb(INTRFLAGS(shost->io_port)); + + /* Check for unusual interrupts. If any of these happen, we should + probably do something special, but for now just printing a message + is sufficient. A SCSI reset detected is something that we really + need to deal with in some way. */ + if (flag & ~MBIF) { + if (flag&MBOA) printk("MBOF "); + if (flag&HACC) printk("HACC "); + if (flag&SCRD) { + needs_restart = 1; + printk("SCRD "); + } + } + + aha1542_intr_reset(shost->io_port); + + save_flags(flags); + cli(); + mbi = HOSTDATA(shost)->aha1542_last_mbi_used + 1; + if (mbi >= 2*AHA1542_MAILBOXES) mbi = AHA1542_MAILBOXES; + + do{ + if(mb[mbi].status != 0) break; + mbi++; + if (mbi >= 2*AHA1542_MAILBOXES) mbi = AHA1542_MAILBOXES; + } while (mbi != HOSTDATA(shost)->aha1542_last_mbi_used); + + if(mb[mbi].status == 0){ + restore_flags(flags); + /* Hmm, no mail. Must have read it the last time around */ + if (!number_serviced && !needs_restart) + printk("aha1542.c: interrupt received, but no mail.\n"); + /* We detected a reset. Restart all pending commands for + devices that use the hard reset option */ + if(needs_restart) aha1542_restart(shost); + return; + }; + + mbo = (scsi2int(mb[mbi].ccbptr) - ((unsigned int) &ccb[0])) / sizeof(struct ccb); + mbistatus = mb[mbi].status; + mb[mbi].status = 0; + HOSTDATA(shost)->aha1542_last_mbi_used = mbi; + restore_flags(flags); + +#ifdef DEBUG + { + if (ccb[mbo].tarstat|ccb[mbo].hastat) + printk("aha1542_command: returning %x (status %d)\n", + ccb[mbo].tarstat + ((int) ccb[mbo].hastat << 16), mb[mbi].status); + }; +#endif + + if(mbistatus == 3) continue; /* Aborted command not found */ + +#ifdef DEBUG + printk("...done %d %d\n",mbo, mbi); +#endif + + SCtmp = HOSTDATA(shost)->SCint[mbo]; + + if (!SCtmp || !SCtmp->scsi_done) { + printk("aha1542_intr_handle: Unexpected interrupt\n"); + printk("tarstat=%x, hastat=%x idlun=%x ccb#=%d \n", ccb[mbo].tarstat, + ccb[mbo].hastat, ccb[mbo].idlun, mbo); + return; + } + + my_done = SCtmp->scsi_done; + if (SCtmp->host_scribble) scsi_free(SCtmp->host_scribble, 512); + + /* Fetch the sense data, and tuck it away, in the required slot. The + Adaptec automatically fetches it, and there is no guarantee that + we will still have it in the cdb when we come back */ + if (ccb[mbo].tarstat == 2) + memcpy(SCtmp->sense_buffer, &ccb[mbo].cdb[ccb[mbo].cdblen], + sizeof(SCtmp->sense_buffer)); + + + /* is there mail :-) */ + + /* more error checking left out here */ + if (mbistatus != 1) + /* This is surely wrong, but I don't know what's right */ + errstatus = makecode(ccb[mbo].hastat, ccb[mbo].tarstat); + else + errstatus = 0; + +#ifdef DEBUG + if(errstatus) printk("(aha1542 error:%x %x %x) ",errstatus, + ccb[mbo].hastat, ccb[mbo].tarstat); +#endif + + if (ccb[mbo].tarstat == 2) { +#ifdef DEBUG + int i; +#endif + DEB(printk("aha1542_intr_handle: sense:")); +#ifdef DEBUG + for (i = 0; i < 12; i++) + printk("%02x ", ccb[mbo].cdb[ccb[mbo].cdblen+i]); + printk("\n"); +#endif + /* + DEB(printk("aha1542_intr_handle: buf:")); + for (i = 0; i < bufflen; i++) + printk("%02x ", ((unchar *)buff)[i]); + printk("\n"); + */ + } + DEB(if (errstatus) printk("aha1542_intr_handle: returning %6x\n", errstatus)); + SCtmp->result = errstatus; + HOSTDATA(shost)->SCint[mbo] = NULL; /* This effectively frees up the mailbox slot, as + far as queuecommand is concerned */ + my_done(SCtmp); + number_serviced++; + }; +} + +int aha1542_queuecommand(Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *)) +{ + unchar ahacmd = CMD_START_SCSI; + unchar direction; + unchar *cmd = (unchar *) SCpnt->cmnd; + unchar target = SCpnt->target; + unchar lun = SCpnt->lun; + unsigned long flags; + void *buff = SCpnt->request_buffer; + int bufflen = SCpnt->request_bufflen; + int mbo; + struct mailbox * mb; + struct ccb *ccb; + + DEB(int i); + + mb = HOSTDATA(SCpnt->host)->mb; + ccb = HOSTDATA(SCpnt->host)->ccb; + + DEB(if (target > 1) { + SCpnt->result = DID_TIME_OUT << 16; + done(SCpnt); return 0;}); + + if(*cmd == REQUEST_SENSE){ +#ifndef DEBUG + if (bufflen != sizeof(SCpnt->sense_buffer)) { + printk("Wrong buffer length supplied for request sense (%d)\n",bufflen); + }; +#endif + SCpnt->result = 0; + done(SCpnt); + return 0; + }; + +#ifdef DEBUG + if (*cmd == READ_10 || *cmd == WRITE_10) + i = xscsi2int(cmd+2); + else if (*cmd == READ_6 || *cmd == WRITE_6) + i = scsi2int(cmd+2); + else + i = -1; + if (done) + printk("aha1542_queuecommand: dev %d cmd %02x pos %d len %d ", target, *cmd, i, bufflen); + else + printk("aha1542_command: dev %d cmd %02x pos %d len %d ", target, *cmd, i, bufflen); + aha1542_stat(); + printk("aha1542_queuecommand: dumping scsi cmd:"); + for (i = 0; i < SCpnt->cmd_len; i++) printk("%02x ", cmd[i]); + printk("\n"); + if (*cmd == WRITE_10 || *cmd == WRITE_6) + return 0; /* we are still testing, so *don't* write */ +#endif +/* Use the outgoing mailboxes in a round-robin fashion, because this + is how the host adapter will scan for them */ + + save_flags(flags); + cli(); + mbo = HOSTDATA(SCpnt->host)->aha1542_last_mbo_used + 1; + if (mbo >= AHA1542_MAILBOXES) mbo = 0; + + do{ + if(mb[mbo].status == 0 && HOSTDATA(SCpnt->host)->SCint[mbo] == NULL) + break; + mbo++; + if (mbo >= AHA1542_MAILBOXES) mbo = 0; + } while (mbo != HOSTDATA(SCpnt->host)->aha1542_last_mbo_used); + + if(mb[mbo].status || HOSTDATA(SCpnt->host)->SCint[mbo]) + panic("Unable to find empty mailbox for aha1542.\n"); + + HOSTDATA(SCpnt->host)->SCint[mbo] = SCpnt; /* This will effectively prevent someone else from + screwing with this cdb. */ + + HOSTDATA(SCpnt->host)->aha1542_last_mbo_used = mbo; + restore_flags(flags); + +#ifdef DEBUG + printk("Sending command (%d %x)...",mbo, done); +#endif + + any2scsi(mb[mbo].ccbptr, &ccb[mbo]); /* This gets trashed for some reason*/ + + memset(&ccb[mbo], 0, sizeof(struct ccb)); + + ccb[mbo].cdblen = SCpnt->cmd_len; + + direction = 0; + if (*cmd == READ_10 || *cmd == READ_6) + direction = 8; + else if (*cmd == WRITE_10 || *cmd == WRITE_6) + direction = 16; + + memcpy(ccb[mbo].cdb, cmd, ccb[mbo].cdblen); + + if (SCpnt->use_sg) { + struct scatterlist * sgpnt; + struct chain * cptr; +#ifdef DEBUG + unsigned char * ptr; +#endif + int i; + ccb[mbo].op = 2; /* SCSI Initiator Command w/scatter-gather*/ + SCpnt->host_scribble = (unsigned char *) scsi_malloc(512); + sgpnt = (struct scatterlist *) SCpnt->request_buffer; + cptr = (struct chain *) SCpnt->host_scribble; + if (cptr == NULL) panic("aha1542.c: unable to allocate DMA memory\n"); + for(i=0; i<SCpnt->use_sg; i++) { + if(sgpnt[i].length == 0 || SCpnt->use_sg > 16 || + (((int)sgpnt[i].address) & 1) || (sgpnt[i].length & 1)){ + unsigned char * ptr; + printk("Bad segment list supplied to aha1542.c (%d, %d)\n",SCpnt->use_sg,i); + for(i=0;i<SCpnt->use_sg;i++){ + printk("%d: %x %x %d\n",i,(unsigned int) sgpnt[i].address, (unsigned int) sgpnt[i].alt_address, + sgpnt[i].length); + }; + printk("cptr %x: ",(unsigned int) cptr); + ptr = (unsigned char *) &cptr[i]; + for(i=0;i<18;i++) printk("%02x ", ptr[i]); + panic("Foooooooood fight!"); + }; + any2scsi(cptr[i].dataptr, sgpnt[i].address); + if(((unsigned int) sgpnt[i].address) & 0xff000000) goto baddma; + any2scsi(cptr[i].datalen, sgpnt[i].length); + }; + any2scsi(ccb[mbo].datalen, SCpnt->use_sg * sizeof(struct chain)); + any2scsi(ccb[mbo].dataptr, cptr); +#ifdef DEBUG + printk("cptr %x: ",cptr); + ptr = (unsigned char *) cptr; + for(i=0;i<18;i++) printk("%02x ", ptr[i]); +#endif + } else { + ccb[mbo].op = 0; /* SCSI Initiator Command */ + SCpnt->host_scribble = NULL; + any2scsi(ccb[mbo].datalen, bufflen); + if(((unsigned int) buff & 0xff000000)) goto baddma; + any2scsi(ccb[mbo].dataptr, buff); + }; + ccb[mbo].idlun = (target&7)<<5 | direction | (lun & 7); /*SCSI Target Id*/ + ccb[mbo].rsalen = 16; + ccb[mbo].linkptr[0] = ccb[mbo].linkptr[1] = ccb[mbo].linkptr[2] = 0; + ccb[mbo].commlinkid = 0; + +#ifdef DEBUG + { int i; + printk("aha1542_command: sending.. "); + for (i = 0; i < sizeof(ccb[mbo])-10; i++) + printk("%02x ", ((unchar *)&ccb[mbo])[i]); + }; +#endif + + if (done) { + DEB(printk("aha1542_queuecommand: now waiting for interrupt "); aha1542_stat()); + SCpnt->scsi_done = done; + mb[mbo].status = 1; + aha1542_out(SCpnt->host->io_port, &ahacmd, 1); /* start scsi command */ + DEB(aha1542_stat()); + } + else + printk("aha1542_queuecommand: done can't be NULL\n"); + + return 0; + baddma: + panic("Buffer at address > 16Mb used for 1542B"); +} + +static void internal_done(Scsi_Cmnd * SCpnt) +{ + SCpnt->SCp.Status++; +} + +int aha1542_command(Scsi_Cmnd * SCpnt) +{ + DEB(printk("aha1542_command: ..calling aha1542_queuecommand\n")); + + aha1542_queuecommand(SCpnt, internal_done); + + SCpnt->SCp.Status = 0; + while (!SCpnt->SCp.Status) + barrier(); + return SCpnt->result; +} + +/* Initialize mailboxes */ +static void setup_mailboxes(int bse, struct Scsi_Host * shpnt) +{ + int i; + struct mailbox * mb; + struct ccb *ccb; + + unchar cmd[5] = {CMD_MBINIT, AHA1542_MAILBOXES, 0, 0, 0}; + + mb = HOSTDATA(shpnt)->mb; + ccb = HOSTDATA(shpnt)->ccb; + + for(i=0; i<AHA1542_MAILBOXES; i++){ + mb[i].status = mb[AHA1542_MAILBOXES+i].status = 0; + any2scsi(mb[i].ccbptr, &ccb[i]); + }; + aha1542_intr_reset(bse); /* reset interrupts, so they don't block */ + any2scsi((cmd+2), mb); + aha1542_out(bse, cmd, 5); + WAIT(INTRFLAGS(bse), INTRMASK, HACC, 0); + while (0) { + fail: + printk("aha1542_detect: failed setting up mailboxes\n"); + } + aha1542_intr_reset(bse); +} + +static int aha1542_getconfig(int base_io, unsigned char * irq_level, unsigned char * dma_chan, unsigned char * scsi_id) +{ + unchar inquiry_cmd[] = {CMD_RETCONF }; + unchar inquiry_result[3]; + int i; + i = inb(STATUS(base_io)); + if (i & DF) { + i = inb(DATA(base_io)); + }; + aha1542_out(base_io, inquiry_cmd, 1); + aha1542_in(base_io, inquiry_result, 3); + WAIT(INTRFLAGS(base_io), INTRMASK, HACC, 0); + while (0) { + fail: + printk("aha1542_detect: query board settings\n"); + } + aha1542_intr_reset(base_io); + switch(inquiry_result[0]){ + case 0x80: + *dma_chan = 7; + break; + case 0x40: + *dma_chan = 6; + break; + case 0x20: + *dma_chan = 5; + break; + case 0x01: + *dma_chan = 0; + break; + case 0: + /* This means that the adapter, although Adaptec 1542 compatible, doesn't use a DMA channel. + Currently only aware of the BusLogic BT-445S VL-Bus adapter which needs this. */ + *dma_chan = 0xFF; + break; + default: + printk("Unable to determine Adaptec DMA priority. Disabling board\n"); + return -1; + }; + switch(inquiry_result[1]){ + case 0x40: + *irq_level = 15; + break; + case 0x20: + *irq_level = 14; + break; + case 0x8: + *irq_level = 12; + break; + case 0x4: + *irq_level = 11; + break; + case 0x2: + *irq_level = 10; + break; + case 0x1: + *irq_level = 9; + break; + default: + printk("Unable to determine Adaptec IRQ level. Disabling board\n"); + return -1; + }; + *scsi_id=inquiry_result[2] & 7; + return 0; +} + +/* This function should only be called for 1542C boards - we can detect + the special firmware settings and unlock the board */ + +static int aha1542_mbenable(int base) +{ + static unchar mbenable_cmd[3]; + static unchar mbenable_result[2]; + int retval; + + retval = BIOS_TRANSLATION_6432; + + mbenable_cmd[0]=CMD_EXTBIOS; + aha1542_out(base,mbenable_cmd,1); + if(aha1542_in1(base,mbenable_result,2)) + return retval; + WAITd(INTRFLAGS(base),INTRMASK,HACC,0,100); + aha1542_intr_reset(base); + + if ((mbenable_result[0] & 0x08) || mbenable_result[1]) { + mbenable_cmd[0]=CMD_MBENABLE; + mbenable_cmd[1]=0; + mbenable_cmd[2]=mbenable_result[1]; + + if((mbenable_result[0] & 0x08) && (mbenable_result[1] & 0x03)) retval = BIOS_TRANSLATION_25563; + + aha1542_out(base,mbenable_cmd,3); + WAIT(INTRFLAGS(base),INTRMASK,HACC,0); + }; + while(0) { +fail: + printk("aha1542_mbenable: Mailbox init failed\n"); + } +aha1542_intr_reset(base); +return retval; +} + +/* Query the board to find out if it is a 1542 or a 1740, or whatever. */ +static int aha1542_query(int base_io, int * transl) +{ + unchar inquiry_cmd[] = {CMD_INQUIRY }; + unchar inquiry_result[4]; + int i; + i = inb(STATUS(base_io)); + if (i & DF) { + i = inb(DATA(base_io)); + }; + aha1542_out(base_io, inquiry_cmd, 1); + aha1542_in(base_io, inquiry_result, 4); + WAIT(INTRFLAGS(base_io), INTRMASK, HACC, 0); + while (0) { + fail: + printk("aha1542_detect: query card type\n"); + } + aha1542_intr_reset(base_io); + + *transl = BIOS_TRANSLATION_6432; /* Default case */ + +/* For an AHA1740 series board, we ignore the board since there is a + hardware bug which can lead to wrong blocks being returned if the board + is operating in the 1542 emulation mode. Since there is an extended mode + driver, we simply ignore the board and let the 1740 driver pick it up. +*/ + + if (inquiry_result[0] == 0x43) { + printk("aha1542.c: Emulation mode not supported for AHA 174N hardware.\n"); + return 1; + }; + + /* Always call this - boards that do not support extended bios translation + will ignore the command, and we will set the proper default */ + + *transl = aha1542_mbenable(base_io); + + return 0; +} + +/* called from init/main.c */ +void aha1542_setup( char *str, int *ints) +{ + const char *ahausage = "aha1542: usage: aha1542=<PORTBASE>[,<BUSON>,<BUSOFF>[,<DMASPEED>]]\n"; + static int setup_idx = 0; + int setup_portbase; + + if(setup_idx >= MAXBOARDS) + { + printk("aha1542: aha1542_setup called too many times! Bad LILO params ?\n"); + printk(" Entryline 1: %s\n",setup_str[0]); + printk(" Entryline 2: %s\n",setup_str[1]); + printk(" This line: %s\n",str); + return; + } + if (ints[0] < 1 || ints[0] > 4) + { + printk("aha1542: %s\n", str ); + printk(ahausage); + printk("aha1542: Wrong parameters may cause system malfunction.. We try anyway..\n"); + } + + setup_called[setup_idx]=ints[0]; + setup_str[setup_idx]=str; + + setup_portbase = ints[0] >= 1 ? ints[1] : 0; /* Preserve the default value.. */ + setup_buson [setup_idx] = ints[0] >= 2 ? ints[2] : 7; + setup_busoff [setup_idx] = ints[0] >= 3 ? ints[3] : 5; + if (ints[0] >= 4) { + int atbt = -1; + switch (ints[4]) { + case 5: + atbt = 0x00; + break; + case 6: + atbt = 0x04; + break; + case 7: + atbt = 0x01; + break; + case 8: + atbt = 0x02; + break; + case 10: + atbt = 0x03; + break; + default: + printk("aha1542: %s\n", str ); + printk(ahausage); + printk("aha1542: Valid values for DMASPEED are 5-8, 10 MB/s. Using jumper defaults.\n"); + break; + } + setup_dmaspeed[setup_idx] = atbt; + } + + if (setup_portbase != 0) + bases[setup_idx] = setup_portbase; + + ++setup_idx; +} + +/* return non-zero on detection */ +int aha1542_detect(Scsi_Host_Template * tpnt) +{ + unsigned char dma_chan; + unsigned char irq_level; + unsigned char scsi_id; + unsigned long flags; + unsigned int base_io; + int trans; + struct Scsi_Host * shpnt = NULL; + int count = 0; + int indx; + + DEB(printk("aha1542_detect: \n")); + + tpnt->proc_dir = &proc_scsi_aha1542; + + for(indx = 0; indx < sizeof(bases)/sizeof(bases[0]); indx++) + if(bases[indx] != 0 && !check_region(bases[indx], 4)) { + shpnt = scsi_register(tpnt, + sizeof(struct aha1542_hostdata)); + + /* For now we do this - until kmalloc is more intelligent + we are resigned to stupid hacks like this */ + if ((unsigned int) shpnt > 0xffffff) { + printk("Invalid address for shpnt with 1542.\n"); + goto unregister; + } + + if(!aha1542_test_port(bases[indx], shpnt)) goto unregister; + + + base_io = bases[indx]; + + /* Set the Bus on/off-times as not to ruin floppy performance */ + { + unchar oncmd[] = {CMD_BUSON_TIME, 7}; + unchar offcmd[] = {CMD_BUSOFF_TIME, 5}; + + if(setup_called[indx]) + { + oncmd[1] = setup_buson[indx]; + offcmd[1] = setup_busoff[indx]; + } + + aha1542_intr_reset(base_io); + aha1542_out(base_io, oncmd, 2); + WAIT(INTRFLAGS(base_io), INTRMASK, HACC, 0); + aha1542_intr_reset(base_io); + aha1542_out(base_io, offcmd, 2); + WAIT(INTRFLAGS(base_io), INTRMASK, HACC, 0); + if (setup_dmaspeed[indx] >= 0) + { + unchar dmacmd[] = {CMD_DMASPEED, 0}; + dmacmd[1] = setup_dmaspeed[indx]; + aha1542_intr_reset(base_io); + aha1542_out(base_io, dmacmd, 2); + WAIT(INTRFLAGS(base_io), INTRMASK, HACC, 0); + } + while (0) { + fail: + printk("aha1542_detect: setting bus on/off-time failed\n"); + } + aha1542_intr_reset(base_io); + } + if(aha1542_query(base_io, &trans)) goto unregister; + + if (aha1542_getconfig(base_io, &irq_level, &dma_chan, &scsi_id) == -1) goto unregister; + + printk("Configuring Adaptec (SCSI-ID %d) at IO:%x, IRQ %d", scsi_id, base_io, irq_level); + if (dma_chan != 0xFF) + printk(", DMA priority %d", dma_chan); + printk("\n"); + + DEB(aha1542_stat()); + setup_mailboxes(base_io, shpnt); + + DEB(aha1542_stat()); + + DEB(printk("aha1542_detect: enable interrupt channel %d\n", irq_level)); + save_flags(flags); + cli(); + if (request_irq(irq_level,aha1542_intr_handle, 0, "aha1542", NULL)) { + printk("Unable to allocate IRQ for adaptec controller.\n"); + restore_flags(flags); + goto unregister; + } + + if (dma_chan != 0xFF) { + if (request_dma(dma_chan,"aha1542")) { + printk("Unable to allocate DMA channel for Adaptec.\n"); + free_irq(irq_level, NULL); + restore_flags(flags); + goto unregister; + } + + if (dma_chan == 0 || dma_chan >= 5) { + set_dma_mode(dma_chan, DMA_MODE_CASCADE); + enable_dma(dma_chan); + } + } + aha_host[irq_level - 9] = shpnt; + shpnt->this_id = scsi_id; + shpnt->unique_id = base_io; + shpnt->io_port = base_io; + shpnt->n_io_port = 4; /* Number of bytes of I/O space used */ + shpnt->dma_channel = dma_chan; + shpnt->irq = irq_level; + HOSTDATA(shpnt)->bios_translation = trans; + if(trans == BIOS_TRANSLATION_25563) + printk("aha1542.c: Using extended bios translation\n"); + HOSTDATA(shpnt)->aha1542_last_mbi_used = (2*AHA1542_MAILBOXES - 1); + HOSTDATA(shpnt)->aha1542_last_mbo_used = (AHA1542_MAILBOXES - 1); + memset(HOSTDATA(shpnt)->SCint, 0, sizeof(HOSTDATA(shpnt)->SCint)); + restore_flags(flags); +#if 0 + DEB(printk(" *** READ CAPACITY ***\n")); + + { + unchar buf[8]; + static unchar cmd[] = { READ_CAPACITY, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int i; + + for (i = 0; i < sizeof(buf); ++i) buf[i] = 0x87; + for (i = 0; i < 2; ++i) + if (!aha1542_command(i, cmd, buf, sizeof(buf))) { + printk("aha_detect: LU %d sector_size %d device_size %d\n", + i, xscsi2int(buf+4), xscsi2int(buf)); + } + } + + DEB(printk(" *** NOW RUNNING MY OWN TEST *** \n")); + + for (i = 0; i < 4; ++i) + { + unsigned char cmd[10]; + static buffer[512]; + + cmd[0] = READ_10; + cmd[1] = 0; + xany2scsi(cmd+2, i); + cmd[6] = 0; + cmd[7] = 0; + cmd[8] = 1; + cmd[9] = 0; + aha1542_command(0, cmd, buffer, 512); + } +#endif + request_region(bases[indx], 4,"aha1542"); /* Register the IO ports that we use */ + count++; + continue; + unregister: + scsi_unregister(shpnt); + continue; + + }; + + return count; +} + +static int aha1542_restart(struct Scsi_Host * shost) +{ + int i; + int count = 0; +#if 0 + unchar ahacmd = CMD_START_SCSI; +#endif + + for(i=0; i< AHA1542_MAILBOXES; i++) + if(HOSTDATA(shost)->SCint[i] && + !(HOSTDATA(shost)->SCint[i]->device->soft_reset)) + { +#if 0 + HOSTDATA(shost)->mb[i].status = 1; /* Indicate ready to restart... */ +#endif + count++; + } + + printk("Potential to restart %d stalled commands...\n", count); +#if 0 + /* start scsi command */ + if (count) aha1542_out(shost->io_port, &ahacmd, 1); +#endif + return 0; +} + +/* The abort command does not leave the device in a clean state where + it is available to be used again. Until this gets worked out, we will + leave it commented out. */ + +int aha1542_abort(Scsi_Cmnd * SCpnt) +{ +#if 0 + unchar ahacmd = CMD_START_SCSI; + unsigned long flags; + struct mailbox * mb; + int mbi, mbo, i; + + printk("In aha1542_abort: %x %x\n", + inb(STATUS(SCpnt->host->io_port)), + inb(INTRFLAGS(SCpnt->host->io_port))); + + save_flags(flags); + cli(); + mb = HOSTDATA(SCpnt->host)->mb; + mbi = HOSTDATA(SCpnt->host)->aha1542_last_mbi_used + 1; + if (mbi >= 2*AHA1542_MAILBOXES) mbi = AHA1542_MAILBOXES; + + do{ + if(mb[mbi].status != 0) break; + mbi++; + if (mbi >= 2*AHA1542_MAILBOXES) mbi = AHA1542_MAILBOXES; + } while (mbi != HOSTDATA(SCpnt->host)->aha1542_last_mbi_used); + restore_flags(flags); + + if(mb[mbi].status) { + printk("Lost interrupt discovered on irq %d - attempting to recover\n", + SCpnt->host->irq); + aha1542_intr_handle(SCpnt->host->irq, NULL); + return 0; + } + + /* OK, no lost interrupt. Try looking to see how many pending commands + we think we have. */ + + for(i=0; i< AHA1542_MAILBOXES; i++) + if(HOSTDATA(SCpnt->host)->SCint[i]) + { + if(HOSTDATA(SCpnt->host)->SCint[i] == SCpnt) { + printk("Timed out command pending for %s\n", + kdevname(SCpnt->request.rq_dev)); + if (HOSTDATA(SCpnt->host)->mb[i].status) { + printk("OGMB still full - restarting\n"); + aha1542_out(SCpnt->host->io_port, &ahacmd, 1); + }; + } else + printk("Other pending command %s\n", + kdevname(SCpnt->request.rq_dev)); + } + +#endif + + DEB(printk("aha1542_abort\n")); +#if 0 + save_flags(flags); + cli(); + for(mbo = 0; mbo < AHA1542_MAILBOXES; mbo++) + if (SCpnt == HOSTDATA(SCpnt->host)->SCint[mbo]){ + mb[mbo].status = 2; /* Abort command */ + aha1542_out(SCpnt->host->io_port, &ahacmd, 1); /* start scsi command */ + restore_flags(flags); + break; + }; +#endif + return SCSI_ABORT_SNOOZE; +} + +/* 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. + For a first go, we assume that the 1542 notifies us with all of the + pending commands (it does implement soft reset, after all). */ + +int aha1542_reset(Scsi_Cmnd * SCpnt, unsigned int reset_flags) +{ + unchar ahacmd = CMD_START_SCSI; + int i; + + /* + * See if a bus reset was suggested. + */ + if( reset_flags & SCSI_RESET_SUGGEST_BUS_RESET ) + { + /* + * This does a scsi reset for all devices on the bus. + * In principle, we could also reset the 1542 - should + * we do this? Try this first, and we can add that later + * if it turns out to be useful. + */ + outb(HRST | SCRST, CONTROL(SCpnt->host->io_port)); + + /* + * Wait for the thing to settle down a bit. Unfortunately + * this is going to basically lock up the machine while we + * wait for this to complete. To be 100% correct, we need to + * check for timeout, and if we are doing something like this + * we are pretty desperate anyways. + */ + WAIT(STATUS(SCpnt->host->io_port), + STATMASK, INIT|IDLE, STST|DIAGF|INVDCMD|DF|CDF); + + /* + * We need to do this too before the 1542 can interact with + * us again. + */ + setup_mailboxes(SCpnt->host->io_port, SCpnt->host); + + /* + * Now try to pick up the pieces. Restart all commands + * that are currently active on the bus, and reset all of + * the datastructures. We have some time to kill while + * things settle down, so print a nice message. + */ + printk("Sent BUS RESET to scsi host %d\n", SCpnt->host->host_no); + + for(i=0; i< AHA1542_MAILBOXES; i++) + if(HOSTDATA(SCpnt->host)->SCint[i] != NULL) + { + Scsi_Cmnd * SCtmp; + SCtmp = HOSTDATA(SCpnt->host)->SCint[i]; + SCtmp->result = DID_RESET << 16; + if (SCtmp->host_scribble) scsi_free(SCtmp->host_scribble, 512); + printk("Sending DID_RESET for target %d\n", SCpnt->target); + SCtmp->scsi_done(SCpnt); + + HOSTDATA(SCpnt->host)->SCint[i] = NULL; + HOSTDATA(SCpnt->host)->mb[i].status = 0; + } + /* + * Now tell the mid-level code what we did here. Since + * we have restarted all of the outstanding commands, + * then report SUCCESS. + */ + return (SCSI_RESET_SUCCESS | SCSI_RESET_BUS_RESET); +fail: + printk("aha1542.c: Unable to perform hard reset.\n"); + printk("Power cycle machine to reset\n"); + return (SCSI_RESET_ERROR | SCSI_RESET_BUS_RESET); + + + } + else + { + /* This does a selective reset of just the one device */ + /* First locate the ccb for this command */ + for(i=0; i< AHA1542_MAILBOXES; i++) + if(HOSTDATA(SCpnt->host)->SCint[i] == SCpnt) + { + HOSTDATA(SCpnt->host)->ccb[i].op = 0x81; /* BUS DEVICE RESET */ + /* Now tell the 1542 to flush all pending commands for this target */ + aha1542_out(SCpnt->host->io_port, &ahacmd, 1); + + /* Here is the tricky part. What to do next. Do we get an interrupt + for the commands that we aborted with the specified target, or + do we generate this on our own? Try it without first and see + what happens */ + printk("Sent BUS DEVICE RESET to target %d\n", SCpnt->target); + + /* If the first does not work, then try the second. I think the + first option is more likely to be correct. Free the command + block for all commands running on this target... */ + for(i=0; i< AHA1542_MAILBOXES; i++) + if(HOSTDATA(SCpnt->host)->SCint[i] && + HOSTDATA(SCpnt->host)->SCint[i]->target == SCpnt->target) + { + Scsi_Cmnd * SCtmp; + SCtmp = HOSTDATA(SCpnt->host)->SCint[i]; + SCtmp->result = DID_RESET << 16; + if (SCtmp->host_scribble) scsi_free(SCtmp->host_scribble, 512); + printk("Sending DID_RESET for target %d\n", SCpnt->target); + SCtmp->scsi_done(SCpnt); + + HOSTDATA(SCpnt->host)->SCint[i] = NULL; + HOSTDATA(SCpnt->host)->mb[i].status = 0; + } + return SCSI_RESET_SUCCESS; + } + } + /* No active command at this time, so this means that each time we got + some kind of response the last time through. Tell the mid-level code + to request sense information in order to decide what to do next. */ + return SCSI_RESET_PUNT; +} + +#include "sd.h" + +int aha1542_biosparam(Scsi_Disk * disk, kdev_t dev, int * ip) +{ + int translation_algorithm; + int size = disk->capacity; + + translation_algorithm = HOSTDATA(disk->device->host)->bios_translation; + + if((size>>11) > 1024 && translation_algorithm == BIOS_TRANSLATION_25563) { + /* Please verify that this is the same as what DOS returns */ + ip[0] = 255; + ip[1] = 63; + ip[2] = size /255/63; + } else { + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + } + + return 0; +} + + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = AHA1542; + +#include "scsi_module.c" +#endif + diff --git a/linux/src/drivers/scsi/aha1542.h b/linux/src/drivers/scsi/aha1542.h new file mode 100644 index 00000000..ee82833a --- /dev/null +++ b/linux/src/drivers/scsi/aha1542.h @@ -0,0 +1,171 @@ +#ifndef _AHA1542_H + +/* $Id: aha1542.h,v 1.1 1999/04/26 05:54:12 tb Exp $ + * + * Header file for the adaptec 1542 driver for Linux + * + * $Log: aha1542.h,v $ + * Revision 1.1 1992/07/24 06:27:38 root + * Initial revision + * + * Revision 1.2 1992/07/04 18:41:49 root + * Replaced distribution with current drivers + * + * Revision 1.3 1992/06/23 23:58:20 root + * Fixes. + * + * Revision 1.2 1992/05/26 22:13:23 root + * Changed bug that prevented DMA above first 2 mbytes. + * + * Revision 1.1 1992/05/22 21:00:29 root + * Initial revision + * + * Revision 1.1 1992/04/24 18:01:50 root + * Initial revision + * + * Revision 1.1 1992/04/02 03:23:13 drew + * Initial revision + * + * Revision 1.3 1992/01/27 14:46:29 tthorn + * *** empty log message *** + * + */ + +#include <linux/types.h> +#include <linux/kdev_t.h> + +/* I/O Port interface 4.2 */ +/* READ */ +#define STATUS(base) base +#define STST 0x80 /* Self Test in Progress */ +#define DIAGF 0x40 /* Internal Diagnostic Failure */ +#define INIT 0x20 /* Mailbox Initialization Required */ +#define IDLE 0x10 /* SCSI Host Adapter Idle */ +#define CDF 0x08 /* Command/Data Out Port Full */ +#define DF 0x04 /* Data In Port Full */ +#define INVDCMD 0x01 /* Invalid H A Command */ +#define STATMASK 0xfd /* 0x02 is reserved */ + +#define INTRFLAGS(base) (STATUS(base)+2) +#define ANYINTR 0x80 /* Any Interrupt */ +#define SCRD 0x08 /* SCSI Reset Detected */ +#define HACC 0x04 /* HA Command Complete */ +#define MBOA 0x02 /* MBO Empty */ +#define MBIF 0x01 /* MBI Full */ +#define INTRMASK 0x8f + +/* WRITE */ +#define CONTROL(base) STATUS(base) +#define HRST 0x80 /* Hard Reset */ +#define SRST 0x40 /* Soft Reset */ +#define IRST 0x20 /* Interrupt Reset */ +#define SCRST 0x10 /* SCSI Bus Reset */ + +/* READ/WRITE */ +#define DATA(base) (STATUS(base)+1) +#define CMD_NOP 0x00 /* No Operation */ +#define CMD_MBINIT 0x01 /* Mailbox Initialization */ +#define CMD_START_SCSI 0x02 /* Start SCSI Command */ +#define CMD_INQUIRY 0x04 /* Adapter Inquiry */ +#define CMD_EMBOI 0x05 /* Enable MailBox Out Interrupt */ +#define CMD_BUSON_TIME 0x07 /* Set Bus-On Time */ +#define CMD_BUSOFF_TIME 0x08 /* Set Bus-Off Time */ +#define CMD_DMASPEED 0x09 /* Set AT Bus Transfer Speed */ +#define CMD_RETDEVS 0x0a /* Return Installed Devices */ +#define CMD_RETCONF 0x0b /* Return Configuration Data */ +#define CMD_RETSETUP 0x0d /* Return Setup Data */ +#define CMD_ECHO 0x1f /* ECHO Command Data */ + +#define CMD_EXTBIOS 0x28 /* Return extend bios information only 1542C */ +#define CMD_MBENABLE 0x29 /* Set Mailbox Interface enable only 1542C */ + +/* Mailbox Definition 5.2.1 and 5.2.2 */ +struct mailbox { + unchar status; /* Command/Status */ + unchar ccbptr[3]; /* msb, .., lsb */ +}; + +/* This is used with scatter-gather */ +struct chain { + unchar datalen[3]; /* Size of this part of chain */ + unchar dataptr[3]; /* Location of data */ +}; + +/* These belong in scsi.h also */ +#define any2scsi(up, p) \ +(up)[0] = (((unsigned long)(p)) >> 16) ; \ +(up)[1] = (((unsigned long)(p)) >> 8); \ +(up)[2] = ((unsigned long)(p)); + +#define scsi2int(up) ( (((long)*(up)) << 16) + (((long)(up)[1]) << 8) + ((long)(up)[2]) ) + +#define xany2scsi(up, p) \ +(up)[0] = ((long)(p)) >> 24; \ +(up)[1] = ((long)(p)) >> 16; \ +(up)[2] = ((long)(p)) >> 8; \ +(up)[3] = ((long)(p)); + +#define xscsi2int(up) ( (((long)(up)[0]) << 24) + (((long)(up)[1]) << 16) \ + + (((long)(up)[2]) << 8) + ((long)(up)[3]) ) + +#define MAX_CDB 12 +#define MAX_SENSE 14 + +struct ccb { /* Command Control Block 5.3 */ + 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 cdblen; /* SCSI Command Length */ + unchar rsalen; /* Request Sense Allocation Length/Disable */ + unchar datalen[3]; /* Data Length (msb, .., lsb) */ + unchar dataptr[3]; /* Data Pointer */ + unchar linkptr[3]; /* Link Pointer */ + unchar commlinkid; /* Command Linking Identifier */ + unchar hastat; /* Host Adapter Status (HASTAT) */ + unchar tarstat; /* Target Device Status */ + unchar reserved[2]; + unchar cdb[MAX_CDB+MAX_SENSE];/* SCSI Command Descriptor Block */ + /* REQUEST SENSE */ +}; + +int aha1542_detect(Scsi_Host_Template *); +int aha1542_command(Scsi_Cmnd *); +int aha1542_queuecommand(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int aha1542_abort(Scsi_Cmnd *); +int aha1542_reset(Scsi_Cmnd *, unsigned int); +int aha1542_biosparam(Disk *, kdev_t, int*); + +#define AHA1542_MAILBOXES 8 +#define AHA1542_SCATTER 16 +#define AHA1542_CMDLUN 1 + +#ifndef NULL + #define NULL 0 +#endif + +extern struct proc_dir_entry proc_scsi_aha1542; + +#define AHA1542 { NULL, NULL, \ + &proc_scsi_aha1542,/* proc_dir_entry */ \ + NULL, \ + "Adaptec 1542", \ + aha1542_detect, \ + NULL, \ + NULL, \ + aha1542_command, \ + aha1542_queuecommand, \ + aha1542_abort, \ + aha1542_reset, \ + NULL, \ + aha1542_biosparam, \ + AHA1542_MAILBOXES, \ + 7, \ + AHA1542_SCATTER, \ + AHA1542_CMDLUN, \ + 0, \ + 1, \ + ENABLE_CLUSTERING} + +#endif diff --git a/linux/src/drivers/scsi/aha1740.c b/linux/src/drivers/scsi/aha1740.c new file mode 100644 index 00000000..013218ca --- /dev/null +++ b/linux/src/drivers/scsi/aha1740.c @@ -0,0 +1,614 @@ +/* $Id: aha1740.c,v 1.1 1999/04/26 05:54:13 tb Exp $ + * 1993/03/31 + * linux/kernel/aha1740.c + * + * Based loosely on aha1542.c which is + * Copyright (C) 1992 Tommy Thorn and + * Modified by Eric Youngdale + * + * This file is aha1740.c, written and + * Copyright (C) 1992,1993 Brad McLean + * + * Modifications to makecode and queuecommand + * for proper handling of multiple devices courteously + * provided by Michael Weller, March, 1993 + * + * Multiple adapter support, extended translation detection, + * update to current scsi subsystem changes, proc fs support, + * working (!) module support based on patches from Andreas Arens, + * by Andreas Degert <ad@papyrus.hamburg.com>, 2/1997 + * + * aha1740_makecode may still need even more work + * if it doesn't work for your devices, take a look. + */ + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <linux/kernel.h> +#include <linux/head.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <linux/sched.h> +#include <asm/dma.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" + +#include "aha1740.h" +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_aha1740 = { + PROC_SCSI_AHA1740, 7, "aha1740", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +/* IF YOU ARE HAVING PROBLEMS WITH THIS DRIVER, AND WANT TO WATCH + IT WORK, THEN: +#define DEBUG +*/ +#ifdef DEBUG +#define DEB(x) x +#else +#define DEB(x) +#endif + +/* +static const char RCSid[] = "$Header: cvs/gnumach/linux/src/drivers/scsi/Attic/aha1740.c,v 1.1 1999/04/26 05:54:13 tb Exp $"; +*/ + +struct aha1740_hostdata { + unsigned int slot; + unsigned int translation; + unsigned int last_ecb_used; + struct ecb ecb[AHA1740_ECBS]; +}; + +#define HOSTDATA(host) ((struct aha1740_hostdata *) &host->hostdata) + +/* One for each IRQ level (9-15) */ +static struct Scsi_Host * aha_host[8] = {NULL, }; + +int aha1740_proc_info(char *buffer, char **start, off_t offset, + int length, int hostno, int inout) +{ + int len; + struct Scsi_Host * shpnt; + struct aha1740_hostdata *host; + + if (inout) + return(-ENOSYS); + + for (len = 0; len < 8; len++) { + shpnt = aha_host[len]; + if (shpnt && shpnt->host_no == hostno) + break; + } + host = HOSTDATA(shpnt); + + len = sprintf(buffer, "aha174x at IO:%x, IRQ %d, SLOT %d.\n" + "Extended translation %sabled.\n", + shpnt->io_port, shpnt->irq, host->slot, + host->translation ? "en" : "dis"); + + if (offset > len) { + *start = buffer; + return 0; + } + + *start = buffer + offset; + len -= offset; + if (len > length) + len = length; + return len; +} + + +int aha1740_makecode(unchar *sense, unchar *status) +{ + struct statusword + { + ushort don:1, /* Command Done - No Error */ + du:1, /* Data underrun */ + :1, qf:1, /* Queue full */ + sc:1, /* Specification Check */ + dor:1, /* Data overrun */ + ch:1, /* Chaining Halted */ + intr:1, /* Interrupt issued */ + asa:1, /* Additional Status Available */ + sns:1, /* Sense information Stored */ + :1, ini:1, /* Initialization Required */ + me:1, /* Major error or exception */ + :1, eca:1, /* Extended Contingent alliance */ + :1; + } status_word; + int retval = DID_OK; + + status_word = * (struct statusword *) status; +#ifdef DEBUG + printk("makecode from %x,%x,%x,%x %x,%x,%x,%x", + status[0], status[1], status[2], status[3], + sense[0], sense[1], sense[2], sense[3]); +#endif + if (!status_word.don) /* Anything abnormal was detected */ + { + if ( (status[1]&0x18) || status_word.sc ) /*Additional info available*/ + { + /* Use the supplied info for further diagnostics */ + switch ( status[2] ) + { + case 0x12: + if ( status_word.dor ) + retval=DID_ERROR; /* It's an Overrun */ + /* If not overrun, assume underrun and ignore it! */ + case 0x00: /* No info, assume no error, should not occur */ + break; + case 0x11: + case 0x21: + retval=DID_TIME_OUT; + break; + case 0x0a: + retval=DID_BAD_TARGET; + break; + case 0x04: + case 0x05: + retval=DID_ABORT; + /* Either by this driver or the AHA1740 itself */ + break; + default: + retval=DID_ERROR; /* No further diagnostics possible */ + } + } + else + { /* Michael suggests, and Brad concurs: */ + if ( status_word.qf ) + { + retval = DID_TIME_OUT; /* forces a redo */ + /* I think this specific one should not happen -Brad */ + printk("aha1740.c: WARNING: AHA1740 queue overflow!\n"); + } + else if ( status[0]&0x60 ) + { + retval = DID_ERROR; /* Didn't find a better error */ + } + /* In any other case return DID_OK so for example + CONDITION_CHECKS make it through to the appropriate + device driver */ + } + } + /* Under all circumstances supply the target status -Michael */ + return status[3] | retval << 16; +} + +int aha1740_test_port(unsigned int base) +{ + char name[4], tmp; + + /* Okay, look for the EISA ID's */ + name[0]= 'A' -1 + ((tmp = inb(HID0(base))) >> 2); /* First character */ + name[1]= 'A' -1 + ((tmp & 3) << 3); + name[1]+= ((tmp = inb(HID1(base))) >> 5)&0x7; /* Second Character */ + name[2]= 'A' -1 + (tmp & 0x1f); /* Third Character */ + name[3]=0; + tmp = inb(HID2(base)); + if ( strcmp ( name, HID_MFG ) || inb(HID2(base)) != HID_PRD ) + return 0; /* Not an Adaptec 174x */ + +/* if ( inb(HID3(base)) != HID_REV ) + printk("aha174x: Warning; board revision of %d; expected %d\n", + inb(HID3(base)),HID_REV); */ + + if ( inb(EBCNTRL(base)) != EBCNTRL_VALUE ) + { + printk("aha174x: Board detected, but EBCNTRL = %x, so disabled it.\n", + inb(EBCNTRL(base))); + return 0; + } + + if ( inb(PORTADR(base)) & PORTADDR_ENH ) + return 1; /* Okay, we're all set */ + + printk("aha174x: Board detected, but not in enhanced mode, so disabled it.\n"); + return 0; +} + +/* A "high" level interrupt handler */ +void aha1740_intr_handle(int irq, void *dev_id, struct pt_regs * regs) +{ + void (*my_done)(Scsi_Cmnd *); + int errstatus, adapstat; + int number_serviced; + struct ecb *ecbptr; + Scsi_Cmnd *SCtmp; + unsigned int base; + + if (!aha_host[irq - 9]) + panic("aha1740.c: Irq from unknown host!\n"); + base = aha_host[irq - 9]->io_port; + number_serviced = 0; + + while(inb(G2STAT(base)) & G2STAT_INTPEND) + { + DEB(printk("aha1740_intr top of loop.\n")); + adapstat = inb(G2INTST(base)); + ecbptr = (struct ecb *) bus_to_virt(inl(MBOXIN0(base))); + outb(G2CNTRL_IRST,G2CNTRL(base)); /* interrupt reset */ + + switch ( adapstat & G2INTST_MASK ) + { + case G2INTST_CCBRETRY: + case G2INTST_CCBERROR: + case G2INTST_CCBGOOD: + /* Host Ready -> Mailbox in complete */ + outb(G2CNTRL_HRDY,G2CNTRL(base)); + if (!ecbptr) + { + printk("Aha1740 null ecbptr in interrupt (%x,%x,%x,%d)\n", + inb(G2STAT(base)),adapstat, + inb(G2INTST(base)), number_serviced++); + continue; + } + SCtmp = ecbptr->SCpnt; + if (!SCtmp) + { + printk("Aha1740 null SCtmp in interrupt (%x,%x,%x,%d)\n", + inb(G2STAT(base)),adapstat, + inb(G2INTST(base)), number_serviced++); + continue; + } + if (SCtmp->host_scribble) + scsi_free(SCtmp->host_scribble, 512); + /* Fetch the sense data, and tuck it away, in the required slot. + The Adaptec automatically fetches it, and there is no + guarantee that we will still have it in the cdb when we come + back */ + if ( (adapstat & G2INTST_MASK) == G2INTST_CCBERROR ) + { + memcpy(SCtmp->sense_buffer, ecbptr->sense, + sizeof(SCtmp->sense_buffer)); + errstatus = aha1740_makecode(ecbptr->sense,ecbptr->status); + } + else + errstatus = 0; + DEB(if (errstatus) printk("aha1740_intr_handle: returning %6x\n", + errstatus)); + SCtmp->result = errstatus; + my_done = ecbptr->done; + memset(ecbptr,0,sizeof(struct ecb)); + if ( my_done ) + my_done(SCtmp); + break; + case G2INTST_HARDFAIL: + printk(KERN_ALERT "aha1740 hardware failure!\n"); + panic("aha1740.c"); /* Goodbye */ + case G2INTST_ASNEVENT: + printk("aha1740 asynchronous event: %02x %02x %02x %02x %02x\n", + adapstat, inb(MBOXIN0(base)), inb(MBOXIN1(base)), + inb(MBOXIN2(base)), inb(MBOXIN3(base))); /* Say What? */ + /* Host Ready -> Mailbox in complete */ + outb(G2CNTRL_HRDY,G2CNTRL(base)); + break; + case G2INTST_CMDGOOD: + /* set immediate command success flag here: */ + break; + case G2INTST_CMDERROR: + /* Set immediate command failure flag here: */ + break; + } + number_serviced++; + } +} + +int aha1740_queuecommand(Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *)) +{ + unchar direction; + unchar *cmd = (unchar *) SCpnt->cmnd; + unchar target = SCpnt->target; + struct aha1740_hostdata *host = HOSTDATA(SCpnt->host); + unsigned long flags; + void *buff = SCpnt->request_buffer; + int bufflen = SCpnt->request_bufflen; + int ecbno; + DEB(int i); + + if(*cmd == REQUEST_SENSE) + { + if (bufflen != sizeof(SCpnt->sense_buffer)) + { + printk("Wrong buffer length supplied for request sense (%d)\n", + bufflen); + } + SCpnt->result = 0; + done(SCpnt); + return 0; + } + +#ifdef DEBUG + if (*cmd == READ_10 || *cmd == WRITE_10) + i = xscsi2int(cmd+2); + else if (*cmd == READ_6 || *cmd == WRITE_6) + i = scsi2int(cmd+2); + else + i = -1; + printk("aha1740_queuecommand: dev %d cmd %02x pos %d len %d ", + target, *cmd, i, bufflen); + printk("scsi cmd:"); + for (i = 0; i < SCpnt->cmd_len; i++) printk("%02x ", cmd[i]); + printk("\n"); +#endif + + /* locate an available ecb */ + save_flags(flags); + cli(); + ecbno = host->last_ecb_used + 1; /* An optimization */ + if (ecbno >= AHA1740_ECBS) + ecbno = 0; + do { + if (!host->ecb[ecbno].cmdw) + break; + ecbno++; + if (ecbno >= AHA1740_ECBS) + ecbno = 0; + } while (ecbno != host->last_ecb_used); + + if (host->ecb[ecbno].cmdw) + panic("Unable to find empty ecb for aha1740.\n"); + + host->ecb[ecbno].cmdw = AHA1740CMD_INIT; /* SCSI Initiator Command + doubles as reserved flag */ + + host->last_ecb_used = ecbno; + restore_flags(flags); + +#ifdef DEBUG + printk("Sending command (%d %x)...", ecbno, done); +#endif + + host->ecb[ecbno].cdblen = SCpnt->cmd_len; /* SCSI Command Descriptor Block Length */ + + direction = 0; + if (*cmd == READ_10 || *cmd == READ_6) + direction = 1; + else if (*cmd == WRITE_10 || *cmd == WRITE_6) + direction = 0; + + memcpy(host->ecb[ecbno].cdb, cmd, SCpnt->cmd_len); + + if (SCpnt->use_sg) + { + struct scatterlist * sgpnt; + struct aha1740_chain * cptr; + int i; + DEB(unsigned char * ptr); + + host->ecb[ecbno].sg = 1; /* SCSI Initiator Command w/scatter-gather*/ + SCpnt->host_scribble = (unsigned char *) scsi_malloc(512); + sgpnt = (struct scatterlist *) SCpnt->request_buffer; + cptr = (struct aha1740_chain *) SCpnt->host_scribble; + if (cptr == NULL) panic("aha1740.c: unable to allocate DMA memory\n"); + for(i=0; i<SCpnt->use_sg; i++) + { + cptr[i].datalen = sgpnt[i].length; + cptr[i].dataptr = virt_to_bus(sgpnt[i].address); + } + host->ecb[ecbno].datalen = SCpnt->use_sg * sizeof(struct aha1740_chain); + host->ecb[ecbno].dataptr = virt_to_bus(cptr); +#ifdef DEBUG + printk("cptr %x: ",cptr); + ptr = (unsigned char *) cptr; + for(i=0;i<24;i++) printk("%02x ", ptr[i]); +#endif + } + else + { + SCpnt->host_scribble = NULL; + host->ecb[ecbno].datalen = bufflen; + host->ecb[ecbno].dataptr = virt_to_bus(buff); + } + host->ecb[ecbno].lun = SCpnt->lun; + host->ecb[ecbno].ses = 1; /* Suppress underrun errors */ + host->ecb[ecbno].dir = direction; + host->ecb[ecbno].ars = 1; /* Yes, get the sense on an error */ + host->ecb[ecbno].senselen = 12; + host->ecb[ecbno].senseptr = virt_to_bus(host->ecb[ecbno].sense); + host->ecb[ecbno].statusptr = virt_to_bus(host->ecb[ecbno].status); + host->ecb[ecbno].done = done; + host->ecb[ecbno].SCpnt = SCpnt; +#ifdef DEBUG + { + int i; + printk("aha1740_command: sending.. "); + for (i = 0; i < sizeof(host->ecb[ecbno]) - 10; i++) + printk("%02x ", ((unchar *)&host->ecb[ecbno])[i]); + } + printk("\n"); +#endif + if (done) + { /* The Adaptec Spec says the card is so fast that the loops will + only be executed once in the code below. Even if this was true + with the fastest processors when the spec was written, it doesn't + seem to be true with todays fast processors. We print a warning + if the code is executed more often than LOOPCNT_WARN. If this + happens, it should be investigated. If the count reaches + LOOPCNT_MAX, we assume something is broken; since there is no + way to return an error (the return value is ignored by the + mid-level scsi layer) we have to panic (and maybe that's the + best thing we can do then anyhow). */ + +#define LOOPCNT_WARN 10 /* excessive mbxout wait -> syslog-msg */ +#define LOOPCNT_MAX 1000000 /* mbxout deadlock -> panic() after ~ 2 sec. */ + int loopcnt; + unsigned int base = SCpnt->host->io_port; + DEB(printk("aha1740[%d] critical section\n",ecbno)); + save_flags(flags); + cli(); + for (loopcnt = 0; ; loopcnt++) { + if (inb(G2STAT(base)) & G2STAT_MBXOUT) break; + if (loopcnt == LOOPCNT_WARN) { + printk("aha1740[%d]_mbxout wait!\n",ecbno); + cli(); /* printk may have done a sti()! */ + } + if (loopcnt == LOOPCNT_MAX) + panic("aha1740.c: mbxout busy!\n"); + } + outl(virt_to_bus(host->ecb + ecbno), MBOXOUT0(base)); + for (loopcnt = 0; ; loopcnt++) { + if (! (inb(G2STAT(base)) & G2STAT_BUSY)) break; + if (loopcnt == LOOPCNT_WARN) { + printk("aha1740[%d]_attn wait!\n",ecbno); + cli(); + } + if (loopcnt == LOOPCNT_MAX) + panic("aha1740.c: attn wait failed!\n"); + } + outb(ATTN_START | (target & 7), ATTN(base)); /* Start it up */ + restore_flags(flags); + DEB(printk("aha1740[%d] request queued.\n",ecbno)); + } + else + printk(KERN_ALERT "aha1740_queuecommand: done can't be NULL\n"); + return 0; +} + +static void internal_done(Scsi_Cmnd * SCpnt) +{ + SCpnt->SCp.Status++; +} + +int aha1740_command(Scsi_Cmnd * SCpnt) +{ + aha1740_queuecommand(SCpnt, internal_done); + SCpnt->SCp.Status = 0; + while (!SCpnt->SCp.Status) + barrier(); + return SCpnt->result; +} + +/* Query the board for its irq_level. Nothing else matters + in enhanced mode on an EISA bus. */ + +void aha1740_getconfig(unsigned int base, unsigned int *irq_level, + unsigned int *translation) +{ + static int intab[] = { 9, 10, 11, 12, 0, 14, 15, 0 }; + + *irq_level = intab[inb(INTDEF(base)) & 0x7]; + *translation = inb(RESV1(base)) & 0x1; + outb(inb(INTDEF(base)) | 0x10, INTDEF(base)); +} + +int aha1740_detect(Scsi_Host_Template * tpnt) +{ + int count = 0, slot; + + DEB(printk("aha1740_detect: \n")); + + for ( slot=MINEISA; slot <= MAXEISA; slot++ ) + { + int slotbase; + unsigned int irq_level, translation; + struct Scsi_Host *shpnt; + struct aha1740_hostdata *host; + slotbase = SLOTBASE(slot); + /* + * The ioports for eisa boards are generally beyond that used in the + * check/allocate region code, but this may change at some point, + * so we go through the motions. + */ + if (check_region(slotbase, SLOTSIZE)) /* See if in use */ + continue; + if (!aha1740_test_port(slotbase)) + continue; + aha1740_getconfig(slotbase,&irq_level,&translation); + if ((inb(G2STAT(slotbase)) & + (G2STAT_MBXOUT|G2STAT_BUSY)) != G2STAT_MBXOUT) + { /* If the card isn't ready, hard reset it */ + outb(G2CNTRL_HRST, G2CNTRL(slotbase)); + outb(0, G2CNTRL(slotbase)); + } + printk("Configuring aha174x at IO:%x, IRQ %d\n", slotbase, irq_level); + printk("aha174x: Extended translation %sabled.\n", + translation ? "en" : "dis"); + DEB(printk("aha1740_detect: enable interrupt channel %d\n",irq_level)); + if (request_irq(irq_level,aha1740_intr_handle,0,"aha1740",NULL)) { + printk("Unable to allocate IRQ for adaptec controller.\n"); + continue; + } + shpnt = scsi_register(tpnt, sizeof(struct aha1740_hostdata)); + request_region(slotbase, SLOTSIZE, "aha1740"); + shpnt->base = 0; + shpnt->io_port = slotbase; + shpnt->n_io_port = SLOTSIZE; + shpnt->irq = irq_level; + shpnt->dma_channel = 0xff; + host = HOSTDATA(shpnt); + host->slot = slot; + host->translation = translation; + aha_host[irq_level - 9] = shpnt; + count++; + } + return count; +} + +/* Note: They following two functions do not apply very well to the Adaptec, + which basically manages its own affairs quite well without our interference, + so I haven't put anything into them. I can faintly imagine someone with a + *very* badly behaved SCSI target (perhaps an old tape?) wanting the abort(), + but it hasn't happened yet, and doing aborts brings the Adaptec to its + knees. I cannot (at this moment in time) think of any reason to reset the + card once it's running. So there. */ + +int aha1740_abort(Scsi_Cmnd * SCpnt) +{ + DEB(printk("aha1740_abort called\n")); + return SCSI_ABORT_SNOOZE; +} + +/* 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 aha1740_reset(Scsi_Cmnd * SCpnt, unsigned int ignored) +{ + DEB(printk("aha1740_reset called\n")); + return SCSI_RESET_PUNT; +} + +int aha1740_biosparam(Disk * disk, kdev_t dev, int* ip) +{ + int size = disk->capacity; + int extended = HOSTDATA(disk->device->host)->translation; + + DEB(printk("aha1740_biosparam\n")); + if (extended && (ip[2] > 1024)) + { + ip[0] = 255; + ip[1] = 63; + ip[2] = size / (255 * 63); + } + else + { + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + } + return 0; +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = AHA1740; + +#include "scsi_module.c" +#endif + +/* Okay, you made it all the way through. As of this writing, 3/31/93, I'm +brad@saturn.gaylord.com or brad@bradpc.gaylord.com. I'll try to help as time +permits if you have any trouble with this driver. Happy Linuxing! */ diff --git a/linux/src/drivers/scsi/aha1740.h b/linux/src/drivers/scsi/aha1740.h new file mode 100644 index 00000000..478e59ad --- /dev/null +++ b/linux/src/drivers/scsi/aha1740.h @@ -0,0 +1,196 @@ +#ifndef _AHA1740_H + +/* $Id: aha1740.h,v 1.1 1999/04/26 05:54:13 tb Exp $ + * + * Header file for the adaptec 1740 driver for Linux + * + * With minor revisions 3/31/93 + * Written and (C) 1992,1993 Brad McLean. See aha1740.c + * for more info + * + */ + +#include <linux/types.h> +#include <linux/kdev_t.h> + +/* Eisa Enhanced mode operation - slot locating and addressing */ +#define MINEISA 1 /* I don't have an EISA Spec to know these ranges, so I */ +#define MAXEISA 8 /* Just took my machine's specifications. Adjust to fit.*/ + /* I just saw an ad, and bumped this from 6 to 8 */ +#define SLOTBASE(x) ((x << 12) + 0xc80) +#define SLOTSIZE 0x5c + +/* EISA configuration registers & values */ +#define HID0(base) (base + 0x0) +#define HID1(base) (base + 0x1) +#define HID2(base) (base + 0x2) +#define HID3(base) (base + 0x3) +#define EBCNTRL(base) (base + 0x4) +#define PORTADR(base) (base + 0x40) +#define BIOSADR(base) (base + 0x41) +#define INTDEF(base) (base + 0x42) +#define SCSIDEF(base) (base + 0x43) +#define BUSDEF(base) (base + 0x44) +#define RESV0(base) (base + 0x45) +#define RESV1(base) (base + 0x46) +#define RESV2(base) (base + 0x47) + +#define HID_MFG "ADP" +#define HID_PRD 0 +#define HID_REV 2 +#define EBCNTRL_VALUE 1 +#define PORTADDR_ENH 0x80 +/* READ */ +#define G2INTST(base) (base + 0x56) +#define G2STAT(base) (base + 0x57) +#define MBOXIN0(base) (base + 0x58) +#define MBOXIN1(base) (base + 0x59) +#define MBOXIN2(base) (base + 0x5a) +#define MBOXIN3(base) (base + 0x5b) +#define G2STAT2(base) (base + 0x5c) + +#define G2INTST_MASK 0xf0 /* isolate the status */ +#define G2INTST_CCBGOOD 0x10 /* CCB Completed */ +#define G2INTST_CCBRETRY 0x50 /* CCB Completed with a retry */ +#define G2INTST_HARDFAIL 0x70 /* Adapter Hardware Failure */ +#define G2INTST_CMDGOOD 0xa0 /* Immediate command success */ +#define G2INTST_CCBERROR 0xc0 /* CCB Completed with error */ +#define G2INTST_ASNEVENT 0xd0 /* Asynchronous Event Notification */ +#define G2INTST_CMDERROR 0xe0 /* Immediate command error */ + +#define G2STAT_MBXOUT 4 /* Mailbox Out Empty Bit */ +#define G2STAT_INTPEND 2 /* Interrupt Pending Bit */ +#define G2STAT_BUSY 1 /* Busy Bit (attention pending) */ + +#define G2STAT2_READY 0 /* Host Ready Bit */ + +/* WRITE (and ReadBack) */ +#define MBOXOUT0(base) (base + 0x50) +#define MBOXOUT1(base) (base + 0x51) +#define MBOXOUT2(base) (base + 0x52) +#define MBOXOUT3(base) (base + 0x53) +#define ATTN(base) (base + 0x54) +#define G2CNTRL(base) (base + 0x55) + +#define ATTN_IMMED 0x10 /* Immediate Command */ +#define ATTN_START 0x40 /* Start CCB */ +#define ATTN_ABORT 0x50 /* Abort CCB */ + +#define G2CNTRL_HRST 0x80 /* Hard Reset */ +#define G2CNTRL_IRST 0x40 /* Clear EISA Interrupt */ +#define G2CNTRL_HRDY 0x20 /* Sets HOST ready */ + +/* This is used with scatter-gather */ +struct aha1740_chain { + u32 dataptr; /* Location of data */ + u32 datalen; /* Size of this part of chain */ +}; + +/* These belong in scsi.h */ +#define any2scsi(up, p) \ +(up)[0] = (((unsigned long)(p)) >> 16) ; \ +(up)[1] = (((unsigned long)(p)) >> 8); \ +(up)[2] = ((unsigned long)(p)); + +#define scsi2int(up) ( (((long)*(up)) << 16) + (((long)(up)[1]) << 8) + ((long)(up)[2]) ) + +#define xany2scsi(up, p) \ +(up)[0] = ((long)(p)) >> 24; \ +(up)[1] = ((long)(p)) >> 16; \ +(up)[2] = ((long)(p)) >> 8; \ +(up)[3] = ((long)(p)); + +#define xscsi2int(up) ( (((long)(up)[0]) << 24) + (((long)(up)[1]) << 16) \ + + (((long)(up)[2]) << 8) + ((long)(up)[3]) ) + +#define MAX_CDB 12 +#define MAX_SENSE 14 +#define MAX_STATUS 32 + +struct ecb { /* Enhanced Control Block 6.1 */ + u16 cmdw; /* Command Word */ + /* Flag Word 1 */ + u16 cne:1, /* Control Block Chaining */ + :6, di:1, /* Disable Interrupt */ + :2, ses:1, /* Suppress Underrun error */ + :1, sg:1, /* Scatter/Gather */ + :1, dsb:1, /* Disable Status Block */ + ars:1; /* Automatic Request Sense */ + /* Flag Word 2 */ + u16 lun:3, /* Logical Unit */ + tag:1, /* Tagged Queuing */ + tt:2, /* Tag Type */ + nd:1, /* No Disconnect */ + :1, dat:1, /* Data transfer - check direction */ + dir:1, /* Direction of transfer 1 = datain */ + st:1, /* Suppress Transfer */ + chk:1, /* Calculate Checksum */ + :2, rec:1, :1; /* Error Recovery */ + u16 nil0; /* nothing */ + u32 dataptr; /* Data or Scatter List ptr */ + u32 datalen; /* Data or Scatter List len */ + u32 statusptr; /* Status Block ptr */ + u32 linkptr; /* Chain Address */ + u32 nil1; /* nothing */ + u32 senseptr; /* Sense Info Pointer */ + u8 senselen; /* Sense Length */ + u8 cdblen; /* CDB Length */ + u16 datacheck; /* Data checksum */ + u8 cdb[MAX_CDB]; /* CDB area */ +/* Hardware defined portion ends here, rest is driver defined */ + u8 sense[MAX_SENSE]; /* Sense area */ + u8 status[MAX_STATUS]; /* Status area */ + Scsi_Cmnd *SCpnt; /* Link to the SCSI Command Block */ + void (*done)(Scsi_Cmnd *); /* Completion Function */ +}; + +#define AHA1740CMD_NOP 0x00 /* No OP */ +#define AHA1740CMD_INIT 0x01 /* Initiator SCSI Command */ +#define AHA1740CMD_DIAG 0x05 /* Run Diagnostic Command */ +#define AHA1740CMD_SCSI 0x06 /* Initialize SCSI */ +#define AHA1740CMD_SENSE 0x08 /* Read Sense Information */ +#define AHA1740CMD_DOWN 0x09 /* Download Firmware (yeah, I bet!) */ +#define AHA1740CMD_RINQ 0x0a /* Read Host Adapter Inquiry Data */ +#define AHA1740CMD_TARG 0x10 /* Target SCSI Command */ + +int aha1740_detect(Scsi_Host_Template *); +int aha1740_command(Scsi_Cmnd *); +int aha1740_queuecommand(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int aha1740_abort(Scsi_Cmnd *); +int aha1740_reset(Scsi_Cmnd *, unsigned int); +int aha1740_biosparam(Disk *, kdev_t, int*); +int aha1740_proc_info(char *buffer, char **start, off_t offset, + int length, int hostno, int inout); + +#define AHA1740_ECBS 32 +#define AHA1740_SCATTER 16 +#define AHA1740_CMDLUN 1 + +#ifndef NULL + #define NULL 0 +#endif + +extern struct proc_dir_entry proc_scsi_aha1740; + +#define AHA1740 {NULL, NULL, \ + &proc_scsi_aha1740, \ + aha1740_proc_info, \ + "Adaptec 174x (EISA)", \ + aha1740_detect, \ + NULL, \ + NULL, \ + aha1740_command, \ + aha1740_queuecommand, \ + aha1740_abort, \ + aha1740_reset, \ + NULL, \ + aha1740_biosparam, \ + AHA1740_ECBS, \ + 7, \ + AHA1740_SCATTER, \ + AHA1740_CMDLUN, \ + 0, \ + 0, \ + ENABLE_CLUSTERING} + +#endif diff --git a/linux/src/drivers/scsi/aic7xxx.c b/linux/src/drivers/scsi/aic7xxx.c new file mode 100644 index 00000000..5cec782d --- /dev/null +++ b/linux/src/drivers/scsi/aic7xxx.c @@ -0,0 +1,11300 @@ +/*+M************************************************************************* + * Adaptec AIC7xxx device driver for Linux. + * + * Copyright (c) 1994 John Aycock + * The University of Calgary Department of Computer Science. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Sources include the Adaptec 1740 driver (aha1740.c), the Ultrastor 24F + * driver (ultrastor.c), various Linux kernel source, the Adaptec EISA + * config file (!adp7771.cfg), the Adaptec AHA-2740A Series User's Guide, + * the Linux Kernel Hacker's Guide, Writing a SCSI Device Driver for Linux, + * the Adaptec 1542 driver (aha1542.c), the Adaptec EISA overlay file + * (adp7770.ovl), the Adaptec AHA-2740 Series Technical Reference Manual, + * the Adaptec AIC-7770 Data Book, the ANSI SCSI specification, the + * ANSI SCSI-2 specification (draft 10c), ... + * + * -------------------------------------------------------------------------- + * + * Modifications by Daniel M. Eischen (deischen@iworks.InterWorks.org): + * + * Substantially modified to include support for wide and twin bus + * adapters, DMAing of SCBs, tagged queueing, IRQ sharing, bug fixes, + * SCB paging, and other rework of the code. + * + * Parts of this driver were also based on the FreeBSD driver by + * Justin T. Gibbs. His copyright follows: + * + * -------------------------------------------------------------------------- + * Copyright (c) 1994-1997 Justin Gibbs. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Where this Software is combined with software released under the terms of + * the GNU Public License ("GPL") and the terms of the GPL would require the + * combined work to also be released under the terms of the GPL, the terms + * and conditions of this License will apply in addition to those of the + * GPL with the exception of any terms or conditions of this License that + * conflict with, or are expressly prohibited by, the GPL. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: aic7xxx.c,v 1.1 1999/04/26 05:54:15 tb Exp $ + *--------------------------------------------------------------------------- + * + * Thanks also go to (in alphabetical order) the following: + * + * Rory Bolt - Sequencer bug fixes + * Jay Estabrook - Initial DEC Alpha support + * Doug Ledford - Much needed abort/reset bug fixes + * Kai Makisara - DMAing of SCBs + * + * A Boot time option was also added for not resetting the scsi bus. + * + * Form: aic7xxx=extended + * aic7xxx=no_reset + * aic7xxx=ultra + * aic7xxx=irq_trigger:[0,1] # 0 edge, 1 level + * aic7xxx=verbose + * + * Daniel M. Eischen, deischen@iworks.InterWorks.org, 1/23/97 + * + * $Id: aic7xxx.c,v 1.1 1999/04/26 05:54:15 tb Exp $ + *-M*************************************************************************/ + +/*+M************************************************************************** + * + * Further driver modifications made by Doug Ledford <dledford@redhat.com> + * + * Copyright (c) 1997-1998 Doug Ledford + * + * These changes are released under the same licensing terms as the FreeBSD + * driver written by Justin Gibbs. Please see his Copyright notice above + * for the exact terms and conditions covering my changes as well as the + * warranty statement. + * + * Modifications made to the aic7xxx.c,v 4.1 driver from Dan Eischen include + * but are not limited to: + * + * 1: Import of the latest FreeBSD sequencer code for this driver + * 2: Modification of kernel code to accomodate different sequencer semantics + * 3: Extensive changes throughout kernel portion of driver to improve + * abort/reset processing and error hanndling + * 4: Other work contributed by various people on the Internet + * 5: Changes to printk information and verbosity selection code + * 6: General reliability related changes, especially in IRQ management + * 7: Modifications to the default probe/attach order for supported cards + * 8: SMP friendliness has been improved + * + * Overall, this driver represents a significant departure from the official + * aic7xxx driver released by Dan Eischen in two ways. First, in the code + * itself. A diff between the two version of the driver is now a several + * thousand line diff. Second, in approach to solving the same problem. The + * problem is importing the FreeBSD aic7xxx driver code to linux can be a + * difficult and time consuming process, that also can be error prone. Dan + * Eischen's official driver uses the approach that the linux and FreeBSD + * drivers should be as identical as possible. To that end, his next version + * of this driver will be using a mid-layer code library that he is developing + * to moderate communications between the linux mid-level SCSI code and the + * low level FreeBSD driver. He intends to be able to essentially drop the + * FreeBSD driver into the linux kernel with only a few minor tweaks to some + * include files and the like and get things working, making for fast easy + * imports of the FreeBSD code into linux. + * + * I disagree with Dan's approach. Not that I don't think his way of doing + * things would be nice, easy to maintain, and create a more uniform driver + * between FreeBSD and Linux. I have no objection to those issues. My + * disagreement is on the needed functionality. There simply are certain + * things that are done differently in FreeBSD than linux that will cause + * problems for this driver regardless of any middle ware Dan implements. + * The biggest example of this at the moment is interrupt semantics. Linux + * doesn't provide the same protection techniques as FreeBSD does, nor can + * they be easily implemented in any middle ware code since they would truly + * belong in the kernel proper and would effect all drivers. For the time + * being, I see issues such as these as major stumbling blocks to the + * reliability of code based upon such middle ware. Therefore, I choose to + * use a different approach to importing the FreeBSD code that doesn't + * involve any middle ware type code. My approach is to import the sequencer + * code from FreeBSD wholesale. Then, to only make changes in the kernel + * portion of the driver as they are needed for the new sequencer semantics. + * In this way, the portion of the driver that speaks to the rest of the + * linux kernel is fairly static and can be changed/modified to solve + * any problems one might encounter without concern for the FreeBSD driver. + * + * Note: If time and experience should prove me wrong that the middle ware + * code Dan writes is reliable in its operation, then I'll retract my above + * statements. But, for those that don't know, I'm from Missouri (in the US) + * and our state motto is "The Show-Me State". Well, before I will put + * faith into it, you'll have to show me that it works :) + * + *_M*************************************************************************/ + +/* + * The next three defines are user configurable. These should be the only + * defines a user might need to get in here and change. There are other + * defines buried deeper in the code, but those really shouldn't need touched + * under normal conditions. + */ + +/* + * AIC7XXX_FAKE_NEGOTIATION_CMDS + * We now have two distinctly different methods of device negotiation + * in this code. The two methods are selected by either defining or not + * defining this option. The difference is as follows: + * + * With AIC7XXX_FAKE_NEGOTIATION_CMDS not set (commented out) + * When the driver is in need of issuing a negotiation command for any + * given device, it will add the negotiation message on to part of a + * regular SCSI command for the device. In the process, if the device + * is configured for and using tagged queueing, then the code will + * also issue that single command as a non-tagged command, attach the + * negotiation message to that one command, and use a temporary + * queue depth of one to keep the untagged and tagged commands from + * overlapping. + * Pros: This doesn't use any extra SCB structures, it's simple, it + * works most of the time (if not all of the time now), and + * since we get the device capability info frmo the INQUIRY data + * now, shouldn't cause any problems. + * Cons: When we need to send a negotiation command to a device, we + * must use a command that is being sent to LUN 0 of the device. + * If we try sending one to high LUN numbers, then some devices + * get noticeably upset. Since we have to wait for a command with + * LUN == 0 to come along, we may not be able to renegotiate when + * we want if the user is actually using say LUN 1 of a CD Changer + * instead of using LUN 0 for an extended period of time. + * + * With AIC7XXX_FAKE_NEGOTIATION_CMDS defined + * When we need to negotiate with a device, instead of attaching our + * negotiation message to an existing command, we insert our own + * fictional Scsi_Cmnd into the chain that has the negotiation message + * attached to it. We send this one command as untagged regardless + * of the device type, and we fiddle with the queue depth the same as + * we would with the option unset to avoid overlapping commands. The + * primary difference between this and the unset option is that the + * negotiation message is no longer attached to a specific command, + * instead it is its own command and is merely triggered by a + * combination of both A) We need to negotiate and B) The mid level + * SCSI code has sent us a command. We still don't do any negotiation + * unless there is a valid SCSI command to be processed. + * Pros: This fixes the problem above in the Cons section. Since we + * issue our own fake command, we can set the LUN to 0 regardless + * of what the LUN is in the real command. It also means that if + * the device get's nasty over negotiation issues, it won't be + * showing up on a regular command, so we won't get any SENSE buffer + * data or STATUS_BYTE returns to the mid level code that are caused + * by snits in the negotiation code. + * Cons: We add more code, and more complexity. This means more ways + * in which things could break. It means a larger driver. It means + * more resource consumption for the fake commands. However, the + * biggest problem is this. Take a system where there is a CD-ROM + * on the SCSI bus. Someone has a CD in the CD-ROM and is using it. + * For some reason the SCSI bus gets reset. We don't touch the + * CD-ROM again for quite a period of time (so we don't renegotiate + * after the reset until we do touch the CD-ROM again). In the + * time while we aren't using the CD-ROM, the current disc is + * removed and a new one put in. When we go to check that disc, we + * will first have to renegotiate. In so doing, we issue our fake + * SCSI command, which happens to be TEST_UNIT_READY. The CD-ROM + * negotiates with us, then responds to our fake command with a + * CHECK_CONDITION status. We REQUEST_SENSE from the CD-ROM, it + * then sends the SENSE data to our fake command to tell it that + * it has been through a disc change. There, now we've cleared out + * the SENSE data along with our negotiation command, and when the + * real command executes, it won't pick up that the CD was changed. + * That's the biggest Con to this approach. In the future, I could + * probably code around this problem though, so this option is still + * viable. + * + * So, which command style should you use? I would appreciate it if people + * could try out both types. I want to know about any cases where one + * method works and the other doesn't. If one method works on significantly + * more systems than another, then it will become the default. If the second + * option turns out to work best, then I'll find a way to work around that + * big con I listed. + * + * -- July 7, 02:33 + * OK...I just added some code that should make the Con listed for the + * fake commands a non issue now. However, it needs testing. For now, + * I'm going to make the default to use the fake commands, we'll see how + * it goes. + */ + +#define AIC7XXX_FAKE_NEGOTIATION_CMDS + +/* + * AIC7XXX_STRICT_PCI_SETUP + * Should we assume the PCI config options on our controllers are set with + * sane and proper values, or should we be anal about our PCI config + * registers and force them to what we want? The main advantage to + * defining this option is on non-Intel hardware where the BIOS may not + * have been run to set things up, or if you have one of the BIOSless + * Adaptec controllers, such as a 2910, that don't get set up by the + * BIOS. However, keep in mind that we really do set the most important + * items in the driver regardless of this setting, this only controls some + * of the more esoteric PCI options on these cards. In that sense, I + * would default to leaving this off. However, if people wish to try + * things both ways, that would also help me to know if there are some + * machines where it works one way but not another. + * + * -- July 7, 17:09 + * OK...I need this on my machine for testing, so the default is to + * leave it defined. + * + * -- July 7, 18:49 + * I needed it for testing, but it didn't make any difference, so back + * off she goes. + * + * -- July 16, 23:04 + * I turned it back on to try and compensate for the 2.1.x PCI code + * which no longer relies solely on the BIOS and now tries to set + * things itself. + */ + +#define AIC7XXX_STRICT_PCI_SETUP + +/* + * AIC7XXX_VERBOSE_DEBUGGING + * This option enables a lot of extra printk();s in the code, surrounded + * by if (aic7xxx_verbose ...) statements. Executing all of those if + * statements and the extra checks can get to where it actually does have + * an impact on CPU usage and such, as well as code size. Disabling this + * define will keep some of those from becoming part of the code. + * + * NOTE: Currently, this option has no real effect, I will be adding the + * various #ifdef's in the code later when I've decided a section is + * complete and no longer needs debugging. OK...a lot of things are now + * surrounded by this define, so turning this off does have an impact. + */ + +/* + * #define AIC7XXX_VERBOSE_DEBUGGING + */ + +#if defined(MODULE) || defined(PCMCIA) +#include <linux/module.h> +#endif + +#if defined(PCMCIA) +# undef MODULE +#endif + +#include <stdarg.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/byteorder.h> +#include <linux/version.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/blk.h> +#include <linux/tqueue.h> +#include <linux/tasks.h> +#include "sd.h" +#include "scsi.h" +#include "hosts.h" +#include "aic7xxx.h" + +#include "aic7xxx/sequencer.h" +#include "aic7xxx/scsi_message.h" +#include "aic7xxx_reg.h" + +#include <linux/stat.h> +#include <linux/malloc.h> /* for kmalloc() */ + +#include <linux/config.h> /* for CONFIG_PCI */ + +/* + * To generate the correct addresses for the controller to issue + * on the bus. Originally added for DEC Alpha support. + */ +#define VIRT_TO_BUS(a) (unsigned int)virt_to_bus((void *)(a)) + +struct proc_dir_entry proc_scsi_aic7xxx = { + PROC_SCSI_AIC7XXX, 7, "aic7xxx", + S_IFDIR | S_IRUGO | S_IXUGO, 2, + 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +#define AIC7XXX_C_VERSION "5.1.4" + +#define NUMBER(arr) (sizeof(arr) / sizeof(arr[0])) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define ALL_TARGETS -1 +#define ALL_CHANNELS -1 +#define ALL_LUNS -1 +#define MAX_TARGETS 16 +#define MAX_LUNS 8 +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +#ifndef KERNEL_VERSION +# define KERNEL_VERSION(x,y,z) (((x)<<16)+((y)<<8)+(z)) +#endif + +/* + * We need the bios32.h file if we are kernel version 2.1.92 or less. The + * full set of pci_* changes wasn't in place until 2.1.93 + */ + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,1,92) +# if defined(__sparc_v9__) || defined(__powerpc__) +# error "PPC and Sparc platforms are only support under 2.1.92 and above" +# endif +# include <linux/bios32.h> +#endif + +#if defined(__powerpc__) +# define MMAPIO +# ifdef mb +# undef mb +# endif +# define mb() \ + __asm__ __volatile__("eieio" ::: "memory") +#elif defined(__i386__) +# define MMAPIO +# ifdef mb +# undef mb +# endif +# define mb() \ + __asm__ __volatile__("lock ; addl $0,0(%%esp)": : :"memory") +#elif defined(__alpha__) +# ifdef mb +# undef mb +# endif +# define mb() \ + __asm__ __volatile__("mb": : :"memory") +#endif + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,0) +# include <asm/spinlock.h> +# include <linux/smp.h> +# define cpuid smp_processor_id() +# if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) +# define DRIVER_LOCK_INIT \ + spin_lock_init(&p->spin_lock); +# define DRIVER_LOCK \ + if(!p->cpu_lock_count[cpuid]) { \ + spin_lock_irqsave(&p->spin_lock, cpu_flags); \ + p->cpu_lock_count[cpuid]++; \ + } else { \ + p->cpu_lock_count[cpuid]++; \ + } +# define DRIVER_UNLOCK \ + if(--p->cpu_lock_count[cpuid] == 0) \ + spin_unlock_irqrestore(&p->spin_lock, cpu_flags); +# else +# define DRIVER_LOCK_INIT +# define DRIVER_LOCK +# define DRIVER_UNLOCK +# endif +#else +# define cpuid 0 +# define DRIVER_LOCK_INIT +# define DRIVER_LOCK \ + save_flags(cpu_flags); \ + cli(); +# define DRIVER_UNLOCK \ + restore_flags(cpu_flags); +# define le32_to_cpu(x) (x) +# define cpu_to_le32(x) (x) +#endif + +/* + * You can try raising me if tagged queueing is enabled, or lowering + * me if you only have 4 SCBs. + */ +#ifdef CONFIG_AIC7XXX_CMDS_PER_LUN +#define AIC7XXX_CMDS_PER_LUN CONFIG_AIC7XXX_CMDS_PER_LUN +#endif + +/* Set this to the delay in seconds after SCSI bus reset. */ +#ifdef CONFIG_AIC7XXX_RESET_DELAY +#define AIC7XXX_RESET_DELAY CONFIG_AIC7XXX_RESET_DELAY +#else +#define AIC7XXX_RESET_DELAY 5 +#endif + +/* + * Control collection of SCSI transfer statistics for the /proc filesystem. + * + * NOTE: Do NOT enable this when running on kernels version 1.2.x and below. + * NOTE: This does affect performance since it has to maintain statistics. + */ +#ifdef CONFIG_AIC7XXX_PROC_STATS +#define AIC7XXX_PROC_STATS +#endif + +/* + * NOTE: Uncommenting the define below no longer has any effect, the + * tagged queue value array is always active now. I've added + * a setup option to set this particular array and I'm hoping + * insmod will be smart enough to set it properly as well. It's + * by use of this array that a person can enable tagged queueing. + * The DEFAULT_TAG_COMMANDS define has been changed to disable + * tagged queueing by default, so if your devices can handle tagged + * queueing you will need to add a line to their lilo.conf file like: + * append="aic7xxx=verbose,tag_info:{{32,32,32,32},{32,32,32,32}}" + * which will result in the first four devices on the first two + * controllers being set to a tagged queue depth of 32. + * + * Set this for defining the number of tagged commands on a device + * by device, and controller by controller basis. The first set + * of tagged commands will be used for the first detected aic7xxx + * controller, the second set will be used for the second detected + * aic7xxx controller, and so on. These values will *only* be used + * for targets that are tagged queueing capable; these values will + * be ignored in all other cases. The tag_commands is an array of + * 16 to allow for wide and twin adapters. Twin adapters will use + * indexes 0-7 for channel 0, and indexes 8-15 for channel 1. + * + * *** Determining commands per LUN *** + * + * When AIC7XXX_CMDS_PER_LUN is not defined, the driver will use its + * own algorithm to determine the commands/LUN. If SCB paging is + * enabled, which is always now, the default is 8 commands per lun + * that indicates it supports tagged queueing. All non-tagged devices + * use an internal queue depth of 3, with no more than one of those + * three commands active at one time. + */ +/* #define AIC7XXX_TAGGED_QUEUEING_BY_DEVICE */ + +typedef struct +{ + unsigned char tag_commands[16]; /* Allow for wide/twin adapters. */ +} adapter_tag_info_t; + +/* + * Make a define that will tell the driver not to use tagged queueing + * by default. + */ +#define DEFAULT_TAG_COMMANDS {255, 255, 255, 255, 255, 255, 255, 255,\ + 255, 255, 255, 255, 255, 255, 255, 255} + +/* + * Modify this as you see fit for your system. By setting tag_commands + * to 0, the driver will use it's own algorithm for determining the + * number of commands to use (see above). When 255, the driver will + * not enable tagged queueing for that particular device. When positive + * (> 0) and (< 255) the values in the array are used for the queue_depth. + * Note that the maximum value for an entry is 254, but you're insane if + * you try to use that many commands on one device. + * + * In this example, the first line will disable tagged queueing for all + * the devices on the first probed aic7xxx adapter. + * + * The second line enables tagged queueing with 4 commands/LUN for IDs + * (1, 2-11, 13-15), disables tagged queueing for ID 12, and tells the + * driver to use its own algorithm for ID 1. + * + * The third line is the same as the first line. + * + * The fourth line disables tagged queueing for devices 0 and 3. It + * enables tagged queueing for the other IDs, with 16 commands/LUN + * for IDs 1 and 4, 127 commands/LUN for ID 8, and 4 commands/LUN for + * IDs 2, 5-7, and 9-15. + */ + +/* + * NOTE: The below structure is for reference only, the actual structure + * to modify in order to change things is located around line + * number 1305 +adapter_tag_info_t aic7xxx_tag_info[] = +{ + {DEFAULT_TAG_COMMANDS}, + {{4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 255, 4, 4, 4}}, + {DEFAULT_TAG_COMMANDS}, + {{255, 16, 4, 255, 16, 4, 4, 4, 127, 4, 4, 4, 4, 4, 4, 4}} +}; +*/ + +/* + * Define an array of board names that can be indexed by aha_type. + * Don't forget to change this when changing the types! + */ +static const char *board_names[] = { + "AIC-7xxx Unknown", /* AIC_NONE */ + "Adaptec AIC-7810 Hardware RAID Controller", /* AIC_7810 */ + "Adaptec AIC-7770 SCSI host adapter", /* AIC_7770 */ + "Adaptec AHA-274X SCSI host adapter", /* AIC_7771 */ + "Adaptec AHA-284X SCSI host adapter", /* AIC_284x */ + "Adaptec AIC-7850 SCSI host adapter", /* AIC_7850 */ + "Adaptec AIC-7855 SCSI host adapter", /* AIC_7855 */ + "Adaptec AIC-7860 Ultra SCSI host adapter", /* AIC_7860 */ + "Adaptec AHA-2940A Ultra SCSI host adapter", /* AIC_7861 */ + "Adaptec AIC-7870 SCSI host adapter", /* AIC_7870 */ + "Adaptec AHA-294X SCSI host adapter", /* AIC_7871 */ + "Adaptec AHA-394X SCSI host adapter", /* AIC_7872 */ + "Adaptec AHA-398X SCSI host adapter", /* AIC_7873 */ + "Adaptec AHA-2944 SCSI host adapter", /* AIC_7874 */ + "Adaptec AIC-7880 Ultra SCSI host adapter", /* AIC_7880 */ + "Adaptec AHA-294X Ultra SCSI host adapter", /* AIC_7881 */ + "Adaptec AHA-394X Ultra SCSI host adapter", /* AIC_7882 */ + "Adaptec AHA-398X Ultra SCSI host adapter", /* AIC_7883 */ + "Adaptec AHA-2944 Ultra SCSI host adapter", /* AIC_7884 */ + "Adaptec AIC-7895 Ultra SCSI host adapter", /* AIC_7895 */ + "Adaptec AIC-7890/1 Ultra2 SCSI host adapter", /* AIC_7890 */ + "Adaptec AHA-294X Ultra2 SCSI host adapter", /* AIC_7890 */ + "Adaptec AIC-7896/7 Ultra2 SCSI host adapter", /* AIC_7896 */ + "Adaptec AHA-394X Ultra2 SCSI host adapter" /* AIC_7897 */ +}; + +/* + * There should be a specific return value for this in scsi.h, but + * it seems that most drivers ignore it. + */ +#define DID_UNDERFLOW DID_ERROR + +/* + * What we want to do is have the higher level scsi driver requeue + * the command to us. There is no specific driver status for this + * condition, but the higher level scsi driver will requeue the + * command on a DID_BUS_BUSY error. + * + * Upon further inspection and testing, it seems that DID_BUS_BUSY + * will *always* retry the command. We can get into an infinite loop + * if this happens when we really want some sort of counter that + * will automatically abort/reset the command after so many retries. + * Using DID_ERROR will do just that. (Made by a suggestion by + * Doug Ledford 8/1/96) + */ +#define DID_RETRY_COMMAND DID_ERROR + +#define HSCSIID 0x07 +#define SCSI_RESET 0x040 + +/* + * EISA/VL-bus stuff + */ +#define MINSLOT 1 +#define MAXSLOT 15 +#define SLOTBASE(x) ((x) << 12) +#define BASE_TO_SLOT(x) ((x) >> 12) + +/* + * Standard EISA Host ID regs (Offset from slot base) + */ +#define AHC_HID0 0x80 /* 0,1: msb of ID2, 2-7: ID1 */ +#define AHC_HID1 0x81 /* 0-4: ID3, 5-7: LSB ID2 */ +#define AHC_HID2 0x82 /* product */ +#define AHC_HID3 0x83 /* firmware revision */ + +/* + * AIC-7770 I/O range to reserve for a card + */ +#define MINREG 0xC00 +#define MAXREG 0xCBF + +#define INTDEF 0x5C /* Interrupt Definition Register */ + +/* + * AIC-78X0 PCI registers + */ +#define CLASS_PROGIF_REVID 0x08 +#define DEVREVID 0x000000FFul +#define PROGINFC 0x0000FF00ul +#define SUBCLASS 0x00FF0000ul +#define BASECLASS 0xFF000000ul + +#define CSIZE_LATTIME 0x0C +#define CACHESIZE 0x0000003Ful /* only 5 bits */ +#define LATTIME 0x0000FF00ul + +#define DEVCONFIG 0x40 +#define SCBSIZE32 0x00010000ul /* aic789X only */ +#define MPORTMODE 0x00000400ul /* aic7870 only */ +#define RAMPSM 0x00000200ul /* aic7870 only */ +#define RAMPSM_ULTRA2 0x00000004 +#define VOLSENSE 0x00000100ul +#define SCBRAMSEL 0x00000080ul +#define SCBRAMSEL_ULTRA2 0x00000008 +#define MRDCEN 0x00000040ul +#define EXTSCBTIME 0x00000020ul /* aic7870 only */ +#define EXTSCBPEN 0x00000010ul /* aic7870 only */ +#define BERREN 0x00000008ul +#define DACEN 0x00000004ul +#define STPWLEVEL 0x00000002ul +#define DIFACTNEGEN 0x00000001ul /* aic7870 only */ + +#define SCAMCTL 0x1a /* Ultra2 only */ +#define CCSCBBADDR 0xf0 /* aic7895/6/7 */ + +/* + * Define the different types of SEEPROMs on aic7xxx adapters + * and make it also represent the address size used in accessing + * its registers. The 93C46 chips have 1024 bits organized into + * 64 16-bit words, while the 93C56 chips have 2048 bits organized + * into 128 16-bit words. The C46 chips use 6 bits to address + * each word, while the C56 and C66 (4096 bits) use 8 bits to + * address each word. + */ +typedef enum {C46 = 6, C56_66 = 8} seeprom_chip_type; + +/* + * + * Define the format of the SEEPROM registers (16 bits). + * + */ +struct seeprom_config { + +/* + * SCSI ID Configuration Flags + */ +#define CFXFER 0x0007 /* synchronous transfer rate */ +#define CFSYNCH 0x0008 /* enable synchronous transfer */ +#define CFDISC 0x0010 /* enable disconnection */ +#define CFWIDEB 0x0020 /* wide bus device (wide card) */ +#define CFSYNCHISULTRA 0x0040 /* CFSYNC is an ultra offset */ +#define CFNEWULTRAFORMAT 0x0080 /* Use the Ultra2 SEEPROM format */ +#define CFSTART 0x0100 /* send start unit SCSI command */ +#define CFINCBIOS 0x0200 /* include in BIOS scan */ +#define CFRNFOUND 0x0400 /* report even if not found */ +#define CFMULTILUN 0x0800 /* probe mult luns in BIOS scan */ +#define CFWBCACHEYES 0x4000 /* Enable W-Behind Cache on drive */ +#define CFWBCACHENC 0xc000 /* Don't change W-Behind Cache */ +/* UNUSED 0x3000 */ + unsigned short device_flags[16]; /* words 0-15 */ + +/* + * BIOS Control Bits + */ +#define CFSUPREM 0x0001 /* support all removable drives */ +#define CFSUPREMB 0x0002 /* support removable drives for boot only */ +#define CFBIOSEN 0x0004 /* BIOS enabled */ +/* UNUSED 0x0008 */ +#define CFSM2DRV 0x0010 /* support more than two drives */ +#define CF284XEXTEND 0x0020 /* extended translation (284x cards) */ +/* UNUSED 0x0040 */ +#define CFEXTEND 0x0080 /* extended translation enabled */ +/* UNUSED 0xFF00 */ + unsigned short bios_control; /* word 16 */ + +/* + * Host Adapter Control Bits + */ +#define CFAUTOTERM 0x0001 /* Perform Auto termination */ +#define CFULTRAEN 0x0002 /* Ultra SCSI speed enable (Ultra cards) */ +#define CF284XSELTO 0x0003 /* Selection timeout (284x cards) */ +#define CF284XFIFO 0x000C /* FIFO Threshold (284x cards) */ +#define CFSTERM 0x0004 /* SCSI low byte termination */ +#define CFWSTERM 0x0008 /* SCSI high byte termination (wide card) */ +#define CFSPARITY 0x0010 /* SCSI parity */ +#define CF284XSTERM 0x0020 /* SCSI low byte termination (284x cards) */ +#define CFRESETB 0x0040 /* reset SCSI bus at boot */ +#define CFBPRIMARY 0x0100 /* Channel B primary on 7895 chipsets */ +#define CFSEAUTOTERM 0x0400 /* aic7890 Perform SE Auto Term */ +#define CFLVDSTERM 0x0800 /* aic7890 LVD Termination */ +/* UNUSED 0xF280 */ + unsigned short adapter_control; /* word 17 */ + +/* + * Bus Release, Host Adapter ID + */ +#define CFSCSIID 0x000F /* host adapter SCSI ID */ +/* UNUSED 0x00F0 */ +#define CFBRTIME 0xFF00 /* bus release time */ + unsigned short brtime_id; /* word 18 */ + +/* + * Maximum targets + */ +#define CFMAXTARG 0x00FF /* maximum targets */ +/* UNUSED 0xFF00 */ + unsigned short max_targets; /* word 19 */ + + unsigned short res_1[11]; /* words 20-30 */ + unsigned short checksum; /* word 31 */ +}; + +#define SELBUS_MASK 0x0a +#define SELNARROW 0x00 +#define SELBUSB 0x08 +#define SINGLE_BUS 0x00 + +#define SCB_TARGET(scb) \ + (((scb)->hscb->target_channel_lun & TID) >> 4) +#define SCB_LUN(scb) \ + ((scb)->hscb->target_channel_lun & LID) +#define SCB_IS_SCSIBUS_B(scb) \ + (((scb)->hscb->target_channel_lun & SELBUSB) != 0) + +/* + * If an error occurs during a data transfer phase, run the command + * to completion - it's easier that way - making a note of the error + * condition in this location. This then will modify a DID_OK status + * into an appropriate error for the higher-level SCSI code. + */ +#define aic7xxx_error(cmd) ((cmd)->SCp.Status) + +/* + * Keep track of the targets returned status. + */ +#define aic7xxx_status(cmd) ((cmd)->SCp.sent_command) + +/* + * The position of the SCSI commands scb within the scb array. + */ +#define aic7xxx_position(cmd) ((cmd)->SCp.have_data_in) + +/* + * So we can keep track of our host structs + */ +static struct aic7xxx_host *first_aic7xxx = NULL; + +/* + * As of Linux 2.1, the mid-level SCSI code uses virtual addresses + * in the scatter-gather lists. We need to convert the virtual + * addresses to physical addresses. + */ +struct hw_scatterlist { + unsigned int address; + unsigned int length; +}; + +/* + * Maximum number of SG segments these cards can support. + */ +#define AIC7XXX_MAX_SG 128 + +/* + * The maximum number of SCBs we could have for ANY type + * of card. DON'T FORGET TO CHANGE THE SCB MASK IN THE + * SEQUENCER CODE IF THIS IS MODIFIED! + */ +#define AIC7XXX_MAXSCB 255 + + +struct aic7xxx_hwscb { +/* ------------ Begin hardware supported fields ---------------- */ +/* 0*/ unsigned char control; +/* 1*/ unsigned char target_channel_lun; /* 4/1/3 bits */ +/* 2*/ unsigned char target_status; +/* 3*/ unsigned char SG_segment_count; +/* 4*/ unsigned int SG_list_pointer; +/* 8*/ unsigned char residual_SG_segment_count; +/* 9*/ unsigned char residual_data_count[3]; +/*12*/ unsigned int data_pointer; +/*16*/ unsigned int data_count; +/*20*/ unsigned int SCSI_cmd_pointer; +/*24*/ unsigned char SCSI_cmd_length; +/*25*/ unsigned char tag; /* Index into our kernel SCB array. + * Also used as the tag for tagged I/O + */ +#define SCB_PIO_TRANSFER_SIZE 26 /* amount we need to upload/download + * via PIO to initialize a transaction. + */ +/*26*/ unsigned char next; /* Used to thread SCBs awaiting selection + * or disconnected down in the sequencer. + */ +/*27*/ unsigned char prev; +/*28*/ unsigned int pad; /* + * Unused by the kernel, but we require + * the padding so that the array of + * hardware SCBs is alligned on 32 byte + * boundaries so the sequencer can index + */ +}; + +typedef enum { + SCB_FREE = 0x0000, + SCB_WAITINGQ = 0x0002, + SCB_ACTIVE = 0x0004, + SCB_SENSE = 0x0008, + SCB_ABORT = 0x0010, + SCB_DEVICE_RESET = 0x0020, + SCB_RESET = 0x0040, + SCB_RECOVERY_SCB = 0x0080, + SCB_WAS_BUSY = 0x0100, + SCB_MSGOUT_SENT = 0x0200, + SCB_MSGOUT_SDTR = 0x0400, + SCB_MSGOUT_WDTR = 0x0800, + SCB_MSGOUT_BITS = SCB_MSGOUT_SENT | + SCB_MSGOUT_SDTR | + SCB_MSGOUT_WDTR, + SCB_QUEUED_ABORT = 0x1000, + SCB_QUEUED_FOR_DONE = 0x2000 +} scb_flag_type; + +typedef enum { + AHC_FNONE = 0x00000000, + AHC_PAGESCBS = 0x00000001, + AHC_CHANNEL_B_PRIMARY = 0x00000002, + AHC_USEDEFAULTS = 0x00000004, + AHC_INDIRECT_PAGING = 0x00000008, + AHC_CHNLB = 0x00000020, + AHC_CHNLC = 0x00000040, + AHC_EXTEND_TRANS_A = 0x00000100, + AHC_EXTEND_TRANS_B = 0x00000200, + AHC_TERM_ENB_A = 0x00000400, + AHC_TERM_ENB_SE_LOW = 0x00000400, + AHC_TERM_ENB_B = 0x00000800, + AHC_TERM_ENB_SE_HIGH = 0x00000800, + AHC_HANDLING_REQINITS = 0x00001000, + AHC_TARGETMODE = 0x00002000, + AHC_NEWEEPROM_FMT = 0x00004000, + /* + * Here ends the FreeBSD defined flags and here begins the linux defined + * flags. NOTE: I did not preserve the old flag name during this change + * specifically to force me to evaluate what flags were being used properly + * and what flags weren't. This way, I could clean up the flag usage on + * a use by use basis. Doug Ledford + */ + AHC_A_SCANNED = 0x00100000, + AHC_B_SCANNED = 0x00200000, + AHC_MULTI_CHANNEL = 0x00400000, + AHC_BIOS_ENABLED = 0x00800000, + AHC_SEEPROM_FOUND = 0x01000000, + AHC_TERM_ENB_LVD = 0x02000000, + AHC_ABORT_PENDING = 0x04000000, + AHC_RESET_PENDING = 0x08000000, +#define AHC_IN_ISR_BIT 28 + AHC_IN_ISR = 0x10000000, + AHC_IN_ABORT = 0x20000000, + AHC_IN_RESET = 0x40000000, + AHC_EXTERNAL_SRAM = 0x80000000 +} ahc_flag_type; + +typedef enum { + AHC_NONE = 0x0000, + AHC_CHIPID_MASK = 0x00ff, + AHC_AIC7770 = 0x0001, + AHC_AIC7850 = 0x0002, + AHC_AIC7860 = 0x0003, + AHC_AIC7870 = 0x0004, + AHC_AIC7880 = 0x0005, + AHC_AIC7890 = 0x0006, + AHC_AIC7895 = 0x0007, + AHC_AIC7896 = 0x0008, + AHC_VL = 0x0100, + AHC_EISA = 0x0200, + AHC_PCI = 0x0400, +} ahc_chip; + +typedef enum { + AHC_FENONE = 0x0000, + AHC_ULTRA = 0x0001, + AHC_ULTRA2 = 0x0002, + AHC_WIDE = 0x0004, + AHC_TWIN = 0x0008, + AHC_MORE_SRAM = 0x0010, + AHC_CMD_CHAN = 0x0020, + AHC_QUEUE_REGS = 0x0040, + AHC_SG_PRELOAD = 0x0080, + AHC_SPIOCAP = 0x0100, + AHC_AIC7770_FE = AHC_FENONE, + AHC_AIC7850_FE = AHC_SPIOCAP, + AHC_AIC7860_FE = AHC_ULTRA|AHC_SPIOCAP, + AHC_AIC7870_FE = AHC_FENONE, + AHC_AIC7880_FE = AHC_ULTRA, + AHC_AIC7890_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA2| + AHC_QUEUE_REGS|AHC_SG_PRELOAD, + AHC_AIC7895_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA, + AHC_AIC7896_FE = AHC_AIC7890_FE, +} ahc_feature; + +struct aic7xxx_scb { + struct aic7xxx_hwscb *hscb; /* corresponding hardware scb */ + Scsi_Cmnd *cmd; /* Scsi_Cmnd for this scb */ + struct aic7xxx_scb *q_next; /* next scb in queue */ + volatile scb_flag_type flags; /* current state of scb */ + struct hw_scatterlist *sg_list; /* SG list in adapter format */ + void *kmalloc_ptr; + unsigned char tag_action; + unsigned char sg_count; + unsigned char sense_cmd[6]; /* + * Allocate 6 characters for + * sense command. + */ + unsigned int sg_length; /* We init this during buildscb so we + * don't have to calculate anything + * during underflow/overflow/stat code + */ +}; + +/* + * Define a linked list of SCBs. + */ +typedef struct { + struct aic7xxx_scb *head; + struct aic7xxx_scb *tail; +} scb_queue_type; + +static struct { + unsigned char errno; + const char *errmesg; +} hard_error[] = { + { ILLHADDR, "Illegal Host Access" }, + { ILLSADDR, "Illegal Sequencer Address referenced" }, + { ILLOPCODE, "Illegal Opcode in sequencer program" }, + { SQPARERR, "Sequencer Ram Parity Error" }, + { DPARERR, "Data-Path Ram Parity Error" }, + { MPARERR, "Scratch Ram/SCB Array Ram Parity Error" }, + { PCIERRSTAT,"PCI Error detected" }, + { CIOPARERR, "CIOBUS Parity Error" } +}; + +static unsigned char +generic_sense[] = { REQUEST_SENSE, 0, 0, 0, 255, 0 }; + +typedef struct { + scb_queue_type free_scbs; /* + * SCBs assigned to free slot on + * card (no paging required) + */ + struct aic7xxx_scb *scb_array[AIC7XXX_MAXSCB]; + struct aic7xxx_hwscb *hscbs; + unsigned char numscbs; /* current number of scbs */ + unsigned char maxhscbs; /* hardware scbs */ + unsigned char maxscbs; /* max scbs including pageable scbs */ + void *hscb_kmalloc_ptr; +} scb_data_type; + +struct target_cmd { + unsigned char mesg_bytes[4]; + unsigned char command[28]; +}; + +#define AHC_TRANS_CUR 0x0001 +#define AHC_TRANS_ACTIVE 0x0002 +#define AHC_TRANS_GOAL 0x0004 +#define AHC_TRANS_USER 0x0008 +#define AHC_TRANS_QUITE 0x0010 +typedef struct { + unsigned char cur_width; + unsigned char goal_width; + unsigned char cur_period; + unsigned char goal_period; + unsigned char cur_offset; + unsigned char goal_offset; + unsigned char user_width; + unsigned char user_period; + unsigned char user_offset; +} transinfo_type; + +/* + * Define a structure used for each host adapter. Note, in order to avoid + * problems with architectures I can't test on (because I don't have one, + * such as the Alpha based systems) which happen to give faults for + * non-aligned memory accesses, care was taken to align this structure + * in a way that gauranteed all accesses larger than 8 bits were aligned + * on the appropriate boundary. It's also organized to try and be more + * cache line efficient. Be careful when changing this lest you might hurt + * overall performance and bring down the wrath of the masses. + */ +struct aic7xxx_host { + /* + * This is the first 64 bytes in the host struct + */ + + struct Scsi_Host *host; /* pointer to scsi host */ + struct aic7xxx_host *next; /* allow for multiple IRQs */ + int host_no; /* SCSI host number */ + unsigned long base; /* card base address */ + volatile unsigned char *maddr; /* memory mapped address */ + unsigned long mbase; /* I/O memory address */ + volatile ahc_flag_type flags; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,0) + spinlock_t spin_lock; +#endif + volatile unsigned char cpu_lock_count[NR_CPUS]; + ahc_chip chip; /* chip type */ + ahc_feature features; /* chip features */ + unsigned long last_reset; + unsigned long isr_count; /* Interrupt count */ + unsigned long spurious_int; + struct target_cmd *targetcmds; + unsigned int num_targetcmds; + unsigned short discenable; /* Targets allowed to disconnect */ + unsigned short tagenable; /* Targets using tagged I/O */ + unsigned short orderedtag; /* Ordered Q tags allowed */ + volatile unsigned char activescbs; /* active scbs */ + volatile unsigned char max_activescbs; + unsigned char unpause; /* unpause value for HCNTRL */ + unsigned char pause; /* pause value for HCNTRL */ + volatile unsigned char qoutfifonext; + volatile unsigned char qinfifonext; + + /* + * MAX_TARGETS is currently == 16, so that makes these entries the next + * 64 bytes + */ + +#define DEVICE_PRESENT 0x01 +#define BUS_DEVICE_RESET_PENDING 0x02 +#define DEVICE_TIMEOUT 0x04 +#define DEVICE_PRINT_SDTR 0x08 +#define DEVICE_PRINT_WDTR 0x10 +#define DEVICE_SUCCESS 0x20 +#define DEVICE_TAGGED_SUCCESS 0x40 +#define DEVICE_SCANNED 0x80 + volatile unsigned char dev_flags[MAX_TARGETS]; + volatile unsigned char dev_active_cmds[MAX_TARGETS]; + volatile unsigned char dev_temp_queue_depth[MAX_TARGETS]; + unsigned char dev_commands_sent[MAX_TARGETS]; + + /* + * The next 128 (or 256 on 64 bit machines).... + */ + Scsi_Cmnd *dev_wdtr_cmnd[MAX_TARGETS]; + Scsi_Cmnd *dev_sdtr_cmnd[MAX_TARGETS]; + + /* + * The next 64.... + */ + + long dev_last_reset[MAX_TARGETS]; + + /* + * The next 64.... + */ + + unsigned char dev_mid_level_queue_depth[MAX_TARGETS]; + unsigned char dev_last_queue_full[MAX_TARGETS]; + unsigned char dev_last_queue_full_count[MAX_TARGETS]; + unsigned char dev_max_queue_depth[MAX_TARGETS]; + + /* + * The next 128.... + */ + + volatile scb_queue_type delayed_scbs[MAX_TARGETS]; + + /* + * + */ + + struct timer_list dev_timer[MAX_TARGETS]; + + /* + * The next 64.... + */ + + unsigned char msg_buf[9]; /* The message for the target */ + unsigned char msg_type; +#define MSG_TYPE_NONE 0x00 +#define MSG_TYPE_INITIATOR_MSGOUT 0x01 +#define MSG_TYPE_INITIATOR_MSGIN 0x02 + unsigned char msg_len; /* Length of message */ + unsigned char msg_index; /* Index into msg_buf array */ + transinfo_type transinfo[MAX_TARGETS]; + volatile scb_queue_type waiting_scbs; /* + * SCBs waiting for space in + * the QINFIFO. + */ + scb_data_type *scb_data; + + struct aic7xxx_cmd_queue { + Scsi_Cmnd *head; + Scsi_Cmnd *tail; + } completeq; + + + /* + * We put the less frequently used host structure items after the more + * frequently used items to try and ease the burden on the cache subsystem. + * These entries are not *commonly* accessed, whereas the preceding entries + * are accessed very often. The only exceptions are the qinfifo, qoutfifo, + * and untagged_scbs array. But, they are often accessed only once and each + * access into these arrays is likely to blow a cache line, so they are put + * down here so we can minimize the number of cache lines required to hold + * the preceeding entries. + */ + + volatile unsigned char untagged_scbs[256]; + volatile unsigned char qoutfifo[256]; + volatile unsigned char qinfifo[256]; + unsigned int irq; /* IRQ for this adapter */ + volatile unsigned short needsdtr; + volatile unsigned short sdtr_pending; + volatile unsigned short needwdtr; + volatile unsigned short wdtr_pending; + int instance; /* aic7xxx instance number */ + int scsi_id; /* host adapter SCSI ID */ + int scsi_id_b; /* channel B for twin adapters */ + unsigned int bios_address; + int board_name_index; + unsigned long reset_start; + unsigned short needsdtr_copy; /* default config */ + unsigned short needwdtr_copy; /* default config */ + unsigned short ultraenb; /* Ultra mode target list */ + unsigned short bios_control; /* bios control - SEEPROM */ + unsigned short adapter_control; /* adapter control - SEEPROM */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + struct pci_dev *pdev; +#endif + unsigned char pci_bus; + unsigned char pci_device_fn; + struct seeprom_config sc; + unsigned short sc_type; + unsigned short sc_size; + + /* + * Statistics Kept: + * + * Total Xfers (count for each command that has a data xfer), + * broken down further by reads && writes. + * + * Binned sizes, writes && reads: + * < 512, 512, 1-2K, 2-4K, 4-8K, 8-16K, 16-32K, 32-64K, 64K-128K, > 128K + * + * Total amounts read/written above 512 bytes (amts under ignored) + * + * NOTE: Enabling this feature is likely to cause a noticeable performance + * decrease as the accesses into the stats structures blows apart multiple + * cache lines and is CPU time consuming. We keep the xfer count always + * for use by the aic7xxx_proc.c code, but only do the bins if the + * proc stats code is enabled. + */ + struct aic7xxx_xferstats { + long xfers; /* total xfer count */ + long w_total; /* total writes */ + long w_total512; /* 512 byte blocks written */ + long r_total; /* total reads */ + long r_total512; /* 512 byte blocks read */ +#ifdef AIC7XXX_PROC_STATS + long w_bins[10]; /* binned write */ + long r_bins[10]; /* binned reads */ +#endif /* AIC7XXX_PROC_STATS */ + } stats[MAX_TARGETS][MAX_LUNS]; /* [(channel << 3)|target][lun] */ +}; + +/* + * Valid SCSIRATE values. (p. 3-17) + * Provides a mapping of transfer periods in ns/4 to the proper value to + * stick in the SCSIRATE reg to use that transfer rate. + */ +#define AHC_SYNCRATE_ULTRA2 0 +#define AHC_SYNCRATE_ULTRA 2 +#define AHC_SYNCRATE_FAST 5 +static struct aic7xxx_syncrate { + /* Rates in Ultra mode have bit 8 of sxfr set */ +#define ULTRA_SXFR 0x100 + int sxfr_ultra2; + int sxfr; + unsigned char period; + const char *rate[2]; +} aic7xxx_syncrates[] = { + { 0x13, 0x000, 10, {"40.0", "80.0"} }, + { 0x14, 0x000, 11, {"33.0", "66.6"} }, + { 0x15, 0x100, 12, {"20.0", "40.0"} }, + { 0x16, 0x110, 15, {"16.0", "32.0"} }, + { 0x17, 0x120, 18, {"13.4", "26.8"} }, + { 0x18, 0x000, 25, {"10.0", "20.0"} }, + { 0x19, 0x010, 31, {"8.0", "16.0"} }, + { 0x1a, 0x020, 37, {"6.67", "13.3"} }, + { 0x1b, 0x030, 43, {"5.7", "11.4"} }, + { 0x10, 0x040, 50, {"5.0", "10.0"} }, + { 0x00, 0x050, 56, {"4.4", "8.8" } }, + { 0x00, 0x060, 62, {"4.0", "8.0" } }, + { 0x00, 0x070, 68, {"3.6", "7.2" } }, + { 0x00, 0x000, 0, {NULL, NULL} }, +}; + +#define CTL_OF_SCB(scb) (((scb->hscb)->target_channel_lun >> 3) & 0x1), \ + (((scb->hscb)->target_channel_lun >> 4) & 0xf), \ + ((scb->hscb)->target_channel_lun & 0x07) + +#define CTL_OF_CMD(cmd) ((cmd->channel) & 0x01), \ + ((cmd->target) & 0x0f), \ + ((cmd->lun) & 0x07) + +#define TARGET_INDEX(cmd) ((cmd)->target | ((cmd)->channel << 3)) + +/* + * A nice little define to make doing our printks a little easier + */ + +#define WARN_LEAD KERN_WARNING "(scsi%d:%d:%d:%d) " +#define INFO_LEAD KERN_INFO "(scsi%d:%d:%d:%d) " + +/* + * XXX - these options apply unilaterally to _all_ 274x/284x/294x + * cards in the system. This should be fixed. Exceptions to this + * rule are noted in the comments. + */ + + +/* + * Skip the scsi bus reset. Non 0 make us skip the reset at startup. This + * has no effect on any later resets that might occur due to things like + * SCSI bus timeouts. + */ +static unsigned int aic7xxx_no_reset = 0; +/* + * Certain PCI motherboards will scan PCI devices from highest to lowest, + * others scan from lowest to highest, and they tend to do all kinds of + * strange things when they come into contact with PCI bridge chips. The + * net result of all this is that the PCI card that is actually used to boot + * the machine is very hard to detect. Most motherboards go from lowest + * PCI slot number to highest, and the first SCSI controller found is the + * one you boot from. The only exceptions to this are when a controller + * has its BIOS disabled. So, we by default sort all of our SCSI controllers + * from lowest PCI slot number to highest PCI slot number. We also force + * all controllers with their BIOS disabled to the end of the list. This + * works on *almost* all computers. Where it doesn't work, we have this + * option. Setting this option to non-0 will reverse the order of the sort + * to highest first, then lowest, but will still leave cards with their BIOS + * disabled at the very end. That should fix everyone up unless there are + * really strange cirumstances. + */ +static int aic7xxx_reverse_scan = 0; +/* + * This setting enables a hack to fix the IRQ settings on buggy 7895 + * MB controller setups: + * -1 == Disable this hack + * 0 == Use the Channel A IRQ for both channels + * 1 == Use the Channel B IRQ for both channels + */ +static unsigned int aic7xxx_extended = 0; +/* + * The IRQ trigger method used on EISA controllers. Does not effect PCI cards. + * -1 = Use detected settings. + * 0 = Force Edge triggered mode. + * 1 = Force Level triggered mode. + */ +static int aic7xxx_irq_trigger = -1; +/* + * This variable is used to override the termination settings on a controller. + * This should not be used under normal conditions. However, in the case + * that a controller does not have a readable SEEPROM (so that we can't + * read the SEEPROM settings directly) and that a controller has a buggered + * version of the cable detection logic, this can be used to force the + * correct termination. It is preferable to use the manual termination + * settings in the BIOS if possible, but some motherboard controllers store + * those settings in a format we can't read. In other cases, auto term + * should also work, but the chipset was put together with no auto term + * logic (common on motherboard controllers). In those cases, we have + * 32 bits here to work with. That's good for 8 controllers/channels. The + * bits are organized as 4 bits per channel, with scsi0 getting the lowest + * 4 bits in the int. A 1 in a bit position indicates the termination setting + * that corresponds to that bit should be enabled, a 0 is disabled. + * It looks something like this: + * + * 0x0f = 1111-Single Ended Low Byte Termination on/off + * ||\-Single Ended High Byte Termination on/off + * |\-LVD Low Byte Termination on/off + * \-LVD High Byte Termination on/off + * + * For non-Ultra2 controllers, the upper 2 bits are not important. So, to + * enable both high byte and low byte termination on scsi0, I would need to + * make sure that the override_term variable was set to 0x03 (bits 0011). + * To make sure that all termination is enabled on an Ultra2 controller at + * scsi2 and only high byte termination on scsi1 and high and low byte + * termination on scsi0, I would set override_term=0xf23 (bits 1111 0010 0011) + * + * For the most part, users should never have to use this, that's why I + * left it fairly cryptic instead of easy to understand. If you need it, + * most likely someone will be telling you what your's needs to be set to. + */ +static int aic7xxx_override_term = -1; +/* + * Certain motherboard chipset controllers tend to screw + * up the polarity of the term enable output pin. Use this variable + * to force the correct polarity for your system. This is a bitfield variable + * similar to the previous one, but this one has one bit per channel instead + * of four. + * 0 = Force the setting to active low. + * 1 = Force setting to active high. + * Most Adaptec cards are active high, several motherboards are active low. + * To force a 2940 card at SCSI 0 to active high and a motherboard 7895 + * controller at scsi1 and scsi2 to active low, and a 2910 card at scsi3 + * to active high, you would need to set stpwlev=0x9 (bits 1001). + * + * People shouldn't need to use this, but if you are experiencing lots of + * SCSI timeout problems, this may help. There is one sure way to test what + * this option needs to be. Using a boot floppy to boot the system, configure + * your system to enable all SCSI termination (in the Adaptec SCSI BIOS) and + * if needed then also pass a value to override_term to make sure that the + * driver is enabling SCSI termination, then set this variable to either 0 + * or 1. When the driver boots, make sure there are *NO* SCSI cables + * connected to your controller. If it finds and inits the controller + * without problem, then the setting you passed to stpwlev was correct. If + * the driver goes into a reset loop and hangs the system, then you need the + * other setting for this variable. If neither setting lets the machine + * boot then you have definite termination problems that may not be fixable. + */ +static int aic7xxx_stpwlev = -1; +/* + * Set this to non-0 in order to force the driver to panic the kernel + * and print out debugging info on a SCSI abort or reset cycle. + */ +static int aic7xxx_panic_on_abort = 0; +/* + * PCI bus parity checking of the Adaptec controllers. This is somewhat + * dubious at best. To my knowledge, this option has never actually + * solved a PCI parity problem, but on certain machines with broken PCI + * chipset configurations, it can generate tons of false error messages. + * It's included in the driver for completeness. + * 0 = Shut off PCI parity check + * -1 = Normal polarity pci parity checking + * 1 = reverse polarity pci parity checking + * + * NOTE: you can't actually pass -1 on the lilo prompt. So, to set this + * variable to -1 you would actually want to simply pass the variable + * name without a number. That will invert the 0 which will result in + * -1. + */ +static int aic7xxx_pci_parity = 0; +/* + * Set this to any non-0 value to cause us to dump the contents of all + * the card's registers in a hex dump format tailored to each model of + * controller. + * + * NOTE: THE CONTROLLER IS LEFT IN AN UNUSEABLE STATE BY THIS OPTION. + * YOU CANNOT BOOT UP WITH THIS OPTION, IT IS FOR DEBUGGING PURPOSES + * ONLY + */ +static int aic7xxx_dump_card = 0; +/* + * Set this to a non-0 value to make us dump out the 32 bit instruction + * registers on the card after completing the sequencer download. This + * allows the actual sequencer download to be verified. It is possible + * to use this option and still boot up and run your system. This is + * only intended for debugging purposes. + */ +static int aic7xxx_dump_sequencer = 0; +/* + * Certain newer motherboards have put new PCI based devices into the + * IO spaces that used to typically be occupied by VLB or EISA cards. + * This overlap can cause these newer motherboards to lock up when scanned + * for older EISA and VLB devices. Setting this option to non-0 will + * cause the driver to skip scanning for any VLB or EISA controllers and + * only support the PCI controllers. NOTE: this means that if the kernel + * os compiled with PCI support disabled, then setting this to non-0 + * would result in never finding any devices :) + */ +static int aic7xxx_no_probe = 0; + +/* + * So that insmod can find the variable and make it point to something + */ +#ifdef MODULE +static char * aic7xxx = NULL; +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,18) +MODULE_PARM(aic7xxx, "s"); +#endif + +/* + * Just in case someone uses commas to separate items on the insmod + * command line, we define a dummy buffer here to avoid having insmod + * write wild stuff into our code segment + */ +static char dummy_buffer[60] = "Please don't trounce on me insmod!!\n"; + +#endif + +/* + * See the comments earlier in the file for what this item is all about + * If you have more than 4 controllers, you will need to increase the + * the number of items in the array below. Additionally, if you don't + * want to have lilo pass a humongous config line to the aic7xxx driver, + * then you can get in and manually adjust these instead of leaving them + * at the default. Pay attention to the comments earlier in this file + * concerning this array if you are going to hand modify these values. + */ +static adapter_tag_info_t aic7xxx_tag_info[] = +{ + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS}, + {DEFAULT_TAG_COMMANDS} +}; + +#define VERBOSE_NORMAL 0x0000 +#define VERBOSE_NEGOTIATION 0x0001 +#define VERBOSE_SEQINT 0x0002 +#define VERBOSE_SCSIINT 0x0004 +#define VERBOSE_PROBE 0x0008 +#define VERBOSE_PROBE2 0x0010 +#define VERBOSE_NEGOTIATION2 0x0020 +#define VERBOSE_MINOR_ERROR 0x0040 +#define VERBOSE_TRACING 0x0080 +#define VERBOSE_ABORT 0x0f00 +#define VERBOSE_ABORT_MID 0x0100 +#define VERBOSE_ABORT_FIND 0x0200 +#define VERBOSE_ABORT_PROCESS 0x0400 +#define VERBOSE_ABORT_RETURN 0x0800 +#define VERBOSE_RESET 0xf000 +#define VERBOSE_RESET_MID 0x1000 +#define VERBOSE_RESET_FIND 0x2000 +#define VERBOSE_RESET_PROCESS 0x4000 +#define VERBOSE_RESET_RETURN 0x8000 +static int aic7xxx_verbose = VERBOSE_NORMAL | VERBOSE_NEGOTIATION | + VERBOSE_PROBE; /* verbose messages */ + + +/**************************************************************************** + * + * We're going to start putting in function declarations so that order of + * functions is no longer important. As needed, they are added here. + * + ***************************************************************************/ + +static void aic7xxx_panic_abort(struct aic7xxx_host *p, Scsi_Cmnd *cmd); +static void aic7xxx_print_card(struct aic7xxx_host *p); +static void aic7xxx_print_scratch_ram(struct aic7xxx_host *p); +static void aic7xxx_print_sequencer(struct aic7xxx_host *p, int downloaded); +#ifdef AIC7XXX_VERBOSE_DEBUGGING +static void aic7xxx_check_scbs(struct aic7xxx_host *p, char *buffer); +#endif + +/**************************************************************************** + * + * These functions are now used. They happen to be wrapped in useless + * inb/outb port read/writes around the real reads and writes because it + * seems that certain very fast CPUs have a problem dealing with us when + * going at full speed. + * + ***************************************************************************/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0) +static inline void +mdelay(int milliseconds) +{ + int i; + + for(i=0; i<milliseconds; i++) + udelay(1000); +} +#endif + +static inline unsigned char +aic_inb(struct aic7xxx_host *p, long port) +{ +#ifdef MMAPIO + unsigned char x; + if(p->maddr) + { + x = p->maddr[port]; + } + else + { + x = inb(p->base + port); + } + mb(); + return(x); +#else + return(inb(p->base + port)); +#endif +} + +static inline void +aic_outb(struct aic7xxx_host *p, unsigned char val, long port) +{ +#ifdef MMAPIO + if(p->maddr) + { + p->maddr[port] = val; + } + else + { + outb(val, p->base + port); + } + mb(); +#else + outb(val, p->base + port); +#endif +} + +/*+F************************************************************************* + * Function: + * aic7xxx_setup + * + * Description: + * Handle Linux boot parameters. This routine allows for assigning a value + * to a parameter with a ':' between the parameter and the value. + * ie. aic7xxx=unpause:0x0A,extended + *-F*************************************************************************/ +void +aic7xxx_setup(char *s, int *dummy) +{ + int i, n; + char *p; + char *end; + + static struct { + const char *name; + unsigned int *flag; + } options[] = { + { "extended", &aic7xxx_extended }, + { "no_reset", &aic7xxx_no_reset }, + { "irq_trigger", &aic7xxx_irq_trigger }, + { "verbose", &aic7xxx_verbose }, + { "reverse_scan",&aic7xxx_reverse_scan }, + { "override_term", &aic7xxx_override_term }, + { "stpwlev", &aic7xxx_stpwlev }, + { "no_probe", &aic7xxx_no_probe }, + { "panic_on_abort", &aic7xxx_panic_on_abort }, + { "pci_parity", &aic7xxx_pci_parity }, + { "dump_card", &aic7xxx_dump_card }, + { "dump_sequencer", &aic7xxx_dump_sequencer }, + { "tag_info", NULL } + }; + + end = strchr(s, '\0'); + + for (p = strtok(s, ",."); p; p = strtok(NULL, ",.")) + { + for (i = 0; i < NUMBER(options); i++) + { + n = strlen(options[i].name); + if (!strncmp(options[i].name, p, n)) + { + if (!strncmp(p, "tag_info", n)) + { + if (p[n] == ':') + { + char *base; + char *tok, *tok_end, *tok_end2; + char tok_list[] = { '.', ',', '{', '}', '\0' }; + int i, instance = -1, device = -1; + unsigned char done = FALSE; + + base = p; + tok = base + n + 1; /* Forward us just past the ':' */ + tok_end = strchr(tok, '\0'); + if (tok_end < end) + *tok_end = ','; + while(!done) + { + switch(*tok) + { + case '{': + if (instance == -1) + instance = 0; + else if (device == -1) + device = 0; + tok++; + break; + case '}': + if (device != -1) + device = -1; + else if (instance != -1) + instance = -1; + tok++; + break; + case ',': + case '.': + if (instance == -1) + done = TRUE; + else if (device >= 0) + device++; + else if (instance >= 0) + instance++; + if ( (device >= MAX_TARGETS) || + (instance >= NUMBER(aic7xxx_tag_info)) ) + done = TRUE; + tok++; + if (!done) + { + base = tok; + } + break; + case '\0': + done = TRUE; + break; + default: + done = TRUE; + tok_end = strchr(tok, '\0'); + for(i=0; tok_list[i]; i++) + { + tok_end2 = strchr(tok, tok_list[i]); + if ( (tok_end2) && (tok_end2 < tok_end) ) + { + tok_end = tok_end2; + done = FALSE; + } + } + if ( (instance >= 0) && (device >= 0) && + (instance < NUMBER(aic7xxx_tag_info)) && + (device < MAX_TARGETS) ) + aic7xxx_tag_info[instance].tag_commands[device] = + simple_strtoul(tok, NULL, 0) & 0xff; + tok = tok_end; + break; + } + } + while((p != base) && (p != NULL)) + p = strtok(NULL, ",."); + } + } + else if (p[n] == ':') + { + *(options[i].flag) = simple_strtoul(p + n + 1, NULL, 0); + } + else if (!strncmp(p, "verbose", n)) + { + *(options[i].flag) = 0xff09; + } + else + { + *(options[i].flag) = ~(*(options[i].flag)); + } + } + } + } +} + +/*+F************************************************************************* + * Function: + * pause_sequencer + * + * Description: + * Pause the sequencer and wait for it to actually stop - this + * is important since the sequencer can disable pausing for critical + * sections. + *-F*************************************************************************/ +static inline void +pause_sequencer(struct aic7xxx_host *p) +{ + aic_outb(p, p->pause, HCNTRL); + while ((aic_inb(p, HCNTRL) & PAUSE) == 0) + { + ; + } +} + +/*+F************************************************************************* + * Function: + * unpause_sequencer + * + * Description: + * Unpause the sequencer. Unremarkable, yet done often enough to + * warrant an easy way to do it. + *-F*************************************************************************/ +static inline void +unpause_sequencer(struct aic7xxx_host *p, int unpause_always) +{ + if (unpause_always || + ( !(aic_inb(p, INTSTAT) & (SCSIINT | SEQINT | BRKADRINT)) && + !(p->flags & AHC_HANDLING_REQINITS) ) ) + { + aic_outb(p, p->unpause, HCNTRL); + } +} + +/*+F************************************************************************* + * Function: + * restart_sequencer + * + * Description: + * Restart the sequencer program from address zero. This assumes + * that the sequencer is already paused. + *-F*************************************************************************/ +static inline void +restart_sequencer(struct aic7xxx_host *p) +{ + aic_outb(p, 0, SEQADDR0); + aic_outb(p, 0, SEQADDR1); + aic_outb(p, FASTMODE, SEQCTL); +} + +/* + * We include the aic7xxx_seq.c file here so that the other defines have + * already been made, and so that it comes before the code that actually + * downloads the instructions (since we don't typically use function + * prototype, our code has to be ordered that way, it's a left-over from + * the original driver days.....I should fix it some time DL). + */ +#include "aic7xxx_seq.c" + +/*+F************************************************************************* + * Function: + * aic7xxx_check_patch + * + * Description: + * See if the next patch to download should be downloaded. + *-F*************************************************************************/ +static int +aic7xxx_check_patch(struct aic7xxx_host *p, + struct sequencer_patch **start_patch, int start_instr, int *skip_addr) +{ + struct sequencer_patch *cur_patch; + struct sequencer_patch *last_patch; + int num_patches; + + num_patches = sizeof(sequencer_patches)/sizeof(struct sequencer_patch); + last_patch = &sequencer_patches[num_patches]; + cur_patch = *start_patch; + + while ((cur_patch < last_patch) && (start_instr == cur_patch->begin)) + { + if (cur_patch->patch_func(p) == 0) + { + /* + * Start rejecting code. + */ + *skip_addr = start_instr + cur_patch->skip_instr; + cur_patch += cur_patch->skip_patch; + } + else + { + /* + * Found an OK patch. Advance the patch pointer to the next patch + * and wait for our instruction pointer to get here. + */ + cur_patch++; + } + } + + *start_patch = cur_patch; + if (start_instr < *skip_addr) + /* + * Still skipping + */ + return (0); + return(1); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_download_instr + * + * Description: + * Find the next patch to download. + *-F*************************************************************************/ +static void +aic7xxx_download_instr(struct aic7xxx_host *p, int instrptr, + unsigned char *dconsts) +{ + union ins_formats instr; + struct ins_format1 *fmt1_ins; + struct ins_format3 *fmt3_ins; + unsigned char opcode; + + instr = *(union ins_formats*) &seqprog[instrptr * 4]; + + instr.integer = le32_to_cpu(instr.integer); + + fmt1_ins = &instr.format1; + fmt3_ins = NULL; + + /* Pull the opcode */ + opcode = instr.format1.opcode; + switch (opcode) + { + case AIC_OP_JMP: + case AIC_OP_JC: + case AIC_OP_JNC: + case AIC_OP_CALL: + case AIC_OP_JNE: + case AIC_OP_JNZ: + case AIC_OP_JE: + case AIC_OP_JZ: + { + struct sequencer_patch *cur_patch; + int address_offset; + unsigned int address; + int skip_addr; + int i; + + fmt3_ins = &instr.format3; + address_offset = 0; + address = fmt3_ins->address; + cur_patch = sequencer_patches; + skip_addr = 0; + + for (i = 0; i < address;) + { + aic7xxx_check_patch(p, &cur_patch, i, &skip_addr); + if (skip_addr > i) + { + int end_addr; + + end_addr = MIN(address, skip_addr); + address_offset += end_addr - i; + i = skip_addr; + } + else + { + i++; + } + } + address -= address_offset; + fmt3_ins->address = address; + /* Fall Through to the next code section */ + } + case AIC_OP_OR: + case AIC_OP_AND: + case AIC_OP_XOR: + case AIC_OP_ADD: + case AIC_OP_ADC: + case AIC_OP_BMOV: + if (fmt1_ins->parity != 0) + { + fmt1_ins->immediate = dconsts[fmt1_ins->immediate]; + } + fmt1_ins->parity = 0; + /* Fall Through to the next code section */ + case AIC_OP_ROL: + if ((p->features & AHC_ULTRA2) != 0) + { + int i, count; + + /* Calculate odd parity for the instruction */ + for ( i=0, count=0; i < 31; i++) + { + unsigned int mask; + + mask = 0x01 << i; + if ((instr.integer & mask) != 0) + count++; + } + if (!(count & 0x01)) + instr.format1.parity = 1; + } + else + { + if (fmt3_ins != NULL) + { + instr.integer = fmt3_ins->immediate | + (fmt3_ins->source << 8) | + (fmt3_ins->address << 16) | + (fmt3_ins->opcode << 25); + } + else + { + instr.integer = fmt1_ins->immediate | + (fmt1_ins->source << 8) | + (fmt1_ins->destination << 16) | + (fmt1_ins->ret << 24) | + (fmt1_ins->opcode << 25); + } + } + aic_outb(p, (instr.integer & 0xff), SEQRAM); + aic_outb(p, ((instr.integer >> 8) & 0xff), SEQRAM); + aic_outb(p, ((instr.integer >> 16) & 0xff), SEQRAM); + aic_outb(p, ((instr.integer >> 24) & 0xff), SEQRAM); + break; + + default: + panic("aic7xxx: Unknown opcode encountered in sequencer program."); + break; + } +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_loadseq + * + * Description: + * Load the sequencer code into the controller memory. + *-F*************************************************************************/ +static void +aic7xxx_loadseq(struct aic7xxx_host *p) +{ + struct sequencer_patch *cur_patch; + int i; + int downloaded; + int skip_addr; + unsigned char download_consts[4] = {0, 0, 0, 0}; + + if (aic7xxx_verbose & VERBOSE_PROBE) + { + printk(KERN_INFO "(scsi%d) Downloading sequencer code...", p->host_no); + } + download_consts[TMODE_NUMCMDS] = p->num_targetcmds; + cur_patch = &sequencer_patches[0]; + downloaded = 0; + skip_addr = 0; + + aic_outb(p, PERRORDIS|LOADRAM|FAILDIS|FASTMODE, SEQCTL); + aic_outb(p, 0, SEQADDR0); + aic_outb(p, 0, SEQADDR1); + + for (i = 0; i < sizeof(seqprog) / 4; i++) + { + if (aic7xxx_check_patch(p, &cur_patch, i, &skip_addr) == 0) + { + /* Skip this instruction for this configuration. */ + continue; + } + aic7xxx_download_instr(p, i, &download_consts[0]); + downloaded++; + } + + aic_outb(p, 0, SEQADDR0); + aic_outb(p, 0, SEQADDR1); + aic_outb(p, FASTMODE | FAILDIS, SEQCTL); + unpause_sequencer(p, TRUE); + mdelay(1); + pause_sequencer(p); + aic_outb(p, FASTMODE, SEQCTL); + if (aic7xxx_verbose & VERBOSE_PROBE) + { + printk(" %d instructions downloaded\n", downloaded); + } + if (aic7xxx_dump_sequencer) + aic7xxx_print_sequencer(p, downloaded); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_print_sequencer + * + * Description: + * Print the contents of the sequencer memory to the screen. + *-F*************************************************************************/ +static void +aic7xxx_print_sequencer(struct aic7xxx_host *p, int downloaded) +{ + int i, k, temp; + + aic_outb(p, PERRORDIS|LOADRAM|FAILDIS|FASTMODE, SEQCTL); + aic_outb(p, 0, SEQADDR0); + aic_outb(p, 0, SEQADDR1); + + k = 0; + for (i=0; i < downloaded; i++) + { + if ( k == 0 ) + printk("%03x: ", i); + temp = aic_inb(p, SEQRAM); + temp |= (aic_inb(p, SEQRAM) << 8); + temp |= (aic_inb(p, SEQRAM) << 16); + temp |= (aic_inb(p, SEQRAM) << 24); + printk("%08x", temp); + if ( ++k == 8 ) + { + printk("\n"); + k = 0; + } + else + printk(" "); + } + aic_outb(p, 0, SEQADDR0); + aic_outb(p, 0, SEQADDR1); + aic_outb(p, FASTMODE | FAILDIS, SEQCTL); + unpause_sequencer(p, TRUE); + mdelay(1); + pause_sequencer(p); + aic_outb(p, FASTMODE, SEQCTL); + printk("\n"); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_delay + * + * Description: + * Delay for specified amount of time. We use mdelay because the timer + * interrupt is not guaranteed to be enabled. This will cause an + * infinite loop since jiffies (clock ticks) is not updated. + *-F*************************************************************************/ +static void +aic7xxx_delay(int seconds) +{ + mdelay(seconds * 1000); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_info + * + * Description: + * Return a string describing the driver. + *-F*************************************************************************/ +const char * +aic7xxx_info(struct Scsi_Host *dooh) +{ + static char buffer[256]; + char *bp; + struct aic7xxx_host *p; + + bp = &buffer[0]; + p = (struct aic7xxx_host *)dooh->hostdata; + memset(bp, 0, sizeof(buffer)); + strcpy(bp, "Adaptec AHA274x/284x/294x (EISA/VLB/PCI-Fast SCSI) "); + strcat(bp, AIC7XXX_C_VERSION); + strcat(bp, "/"); + strcat(bp, AIC7XXX_H_VERSION); + strcat(bp, "\n"); + strcat(bp, " <"); + strcat(bp, board_names[p->board_name_index]); + strcat(bp, ">"); + + return(bp); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_find_syncrate + * + * Description: + * Look up the valid period to SCSIRATE conversion in our table + *-F*************************************************************************/ +static struct aic7xxx_syncrate * +aic7xxx_find_syncrate(struct aic7xxx_host *p, unsigned int *period, + unsigned int maxsync) +{ + struct aic7xxx_syncrate *syncrate; + + syncrate = &aic7xxx_syncrates[maxsync]; + while ( (syncrate->rate[0] != NULL) && + (!(p->features & AHC_ULTRA2) || syncrate->sxfr_ultra2) ) + { + if ( *period <= syncrate->period ) + { + /* + * When responding to a target that requests sync, the requested rate + * may fall between two rates that we can output, but still be a rate + * that we can receive. Because of this, we want to respond with the + * same rate that it sent to us even if the persiod we use to send + * data to it is lower. Only lower the response period if we must. + */ + if(syncrate == &aic7xxx_syncrates[maxsync]) + { + *period = syncrate->period; + } + break; + } + syncrate++; + } + if ( (*period == 0) || (syncrate->rate[0] == NULL) || + ((p->features & AHC_ULTRA2) && (syncrate->sxfr_ultra2 == 0)) ) + { + /* + * Use async transfers for this target + */ + *period = 0; + syncrate = NULL; + } + return (syncrate); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_find_period + * + * Description: + * Look up the valid SCSIRATE to period conversion in our table + *-F*************************************************************************/ +static unsigned int +aic7xxx_find_period(struct aic7xxx_host *p, unsigned int scsirate, + unsigned int maxsync) +{ + struct aic7xxx_syncrate *syncrate; + + if ((p->features & AHC_ULTRA2) != 0) + { + scsirate &= SXFR_ULTRA2; + } + else + { + scsirate &= SXFR; + } + + syncrate = &aic7xxx_syncrates[maxsync]; + while (syncrate->rate[0] != NULL) + { + if ((p->features & AHC_ULTRA2) != 0) + { + if (syncrate->sxfr_ultra2 == 0) + break; + else if (scsirate == syncrate->sxfr_ultra2) + return (syncrate->period); + } + else if (scsirate == (syncrate->sxfr & ~ULTRA_SXFR)) + { + return (syncrate->period); + } + syncrate++; + } + return (0); /* async */ +} + +/*+F************************************************************************* + * Function: + * aic7xxx_validate_offset + * + * Description: + * Set a valid offset value for a particular card in use and transfer + * settings in use. + *-F*************************************************************************/ +static void +aic7xxx_validate_offset(struct aic7xxx_host *p, + struct aic7xxx_syncrate *syncrate, unsigned int *offset, int wide) +{ + unsigned int maxoffset; + + /* Limit offset to what the card (and device) can do */ + if (syncrate == NULL) + { + maxoffset = 0; + } + else if (p->features & AHC_ULTRA2) + { + maxoffset = MAX_OFFSET_ULTRA2; + } + else + { + if (wide) + maxoffset = MAX_OFFSET_16BIT; + else + maxoffset = MAX_OFFSET_8BIT; + } + *offset = MIN(*offset, maxoffset); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_set_syncrate + * + * Description: + * Set the actual syncrate down in the card and in our host structs + *-F*************************************************************************/ +static void +aic7xxx_set_syncrate(struct aic7xxx_host *p, struct aic7xxx_syncrate *syncrate, + int target, int channel, unsigned int period, unsigned int offset, + unsigned int type) +{ + unsigned char tindex; + unsigned short target_mask; + unsigned char lun; + unsigned int old_period, old_offset; + + tindex = target | (channel << 3); + target_mask = 0x01 << tindex; + lun = aic_inb(p, SCB_TCL) & 0x07; + + if (syncrate == NULL) + { + period = 0; + offset = 0; + } + + old_period = p->transinfo[tindex].cur_period; + old_offset = p->transinfo[tindex].cur_offset; + + + if (type & AHC_TRANS_CUR) + { + unsigned int scsirate; + + scsirate = aic_inb(p, TARG_SCSIRATE + tindex); + if (p->features & AHC_ULTRA2) + { + scsirate &= ~SXFR_ULTRA2; + if (syncrate != NULL) + { + scsirate |= syncrate->sxfr_ultra2; + } + if (type & AHC_TRANS_ACTIVE) + { + aic_outb(p, offset, SCSIOFFSET); + } + aic_outb(p, offset, TARG_OFFSET + tindex); + } + else /* Not an Ultra2 controller */ + { + scsirate &= ~(SXFR|SOFS); + p->ultraenb &= ~target_mask; + if (syncrate != NULL) + { + if (syncrate->sxfr & ULTRA_SXFR) + { + p->ultraenb |= target_mask; + } + scsirate |= (syncrate->sxfr & SXFR); + scsirate |= (offset & SOFS); + } + if (type & AHC_TRANS_ACTIVE) + { + unsigned char sxfrctl0; + + sxfrctl0 = aic_inb(p, SXFRCTL0); + sxfrctl0 &= ~FAST20; + if (p->ultraenb & target_mask) + sxfrctl0 |= FAST20; + aic_outb(p, sxfrctl0, SXFRCTL0); + } + aic_outb(p, p->ultraenb & 0xff, ULTRA_ENB); + aic_outb(p, (p->ultraenb >> 8) & 0xff, ULTRA_ENB + 1 ); + } + if (type & AHC_TRANS_ACTIVE) + { + aic_outb(p, scsirate, SCSIRATE); + } + aic_outb(p, scsirate, TARG_SCSIRATE + tindex); + p->transinfo[tindex].cur_period = period; + p->transinfo[tindex].cur_offset = offset; + if ( !(type & AHC_TRANS_QUITE) && + (aic7xxx_verbose & VERBOSE_NEGOTIATION) && + (p->dev_flags[tindex] & DEVICE_PRINT_SDTR) ) + { + if (offset) + { + int rate_mod = (scsirate & WIDEXFER) ? 1 : 0; + + printk(INFO_LEAD "Synchronous at %s Mbyte/sec, " + "offset %d.\n", p->host_no, channel, target, lun, + syncrate->rate[rate_mod], offset); + } + else + { + printk(INFO_LEAD "Using asynchronous transfers.\n", + p->host_no, channel, target, lun); + } + p->dev_flags[tindex] &= ~DEVICE_PRINT_SDTR; + } + } + + if (type & AHC_TRANS_GOAL) + { + p->transinfo[tindex].goal_period = period; + p->transinfo[tindex].goal_offset = offset; + } + + if (type & AHC_TRANS_USER) + { + p->transinfo[tindex].user_period = period; + p->transinfo[tindex].user_offset = offset; + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_set_width + * + * Description: + * Set the actual width down in the card and in our host structs + *-F*************************************************************************/ +static void +aic7xxx_set_width(struct aic7xxx_host *p, int target, int channel, int lun, + unsigned int width, unsigned int type) +{ + unsigned char tindex; + unsigned short target_mask; + unsigned int old_width, new_offset; + + tindex = target | (channel << 3); + target_mask = 1 << tindex; + + old_width = p->transinfo[tindex].cur_width; + + if (p->features & AHC_ULTRA2) + new_offset = MAX_OFFSET_ULTRA2; + else if (width == MSG_EXT_WDTR_BUS_16_BIT) + new_offset = MAX_OFFSET_16BIT; + else + new_offset = MAX_OFFSET_8BIT; + + if (type & AHC_TRANS_CUR) + { + unsigned char scsirate; + + scsirate = aic_inb(p, TARG_SCSIRATE + tindex); + + scsirate &= ~WIDEXFER; + if (width == MSG_EXT_WDTR_BUS_16_BIT) + scsirate |= WIDEXFER; + + aic_outb(p, scsirate, TARG_SCSIRATE + tindex); + + if (type & AHC_TRANS_ACTIVE) + aic_outb(p, scsirate, SCSIRATE); + + p->transinfo[tindex].cur_width = width; + + if ((aic7xxx_verbose & VERBOSE_NEGOTIATION2) && + (p->dev_flags[tindex] & DEVICE_PRINT_WDTR)) + { + printk(INFO_LEAD "Using %s transfers\n", p->host_no, channel, target, + lun, (scsirate & WIDEXFER) ? "Wide(16bit)" : "Narrow(8bit)" ); + p->dev_flags[tindex] &= ~DEVICE_PRINT_WDTR; + } + } + + if (type & AHC_TRANS_GOAL) + p->transinfo[tindex].goal_width = width; + if (type & AHC_TRANS_USER) + p->transinfo[tindex].user_width = width; + + /* + * Having just set the width, the SDTR should come next, and we need a valid + * offset for the SDTR. So, we make sure we put a valid one in here now as + * the goal_offset. + */ + if (p->transinfo[tindex].goal_offset) + p->transinfo[tindex].goal_offset = new_offset; + +} + +/*+F************************************************************************* + * Function: + * scbq_init + * + * Description: + * SCB queue initialization. + * + *-F*************************************************************************/ +static void +scbq_init(volatile scb_queue_type *queue) +{ + queue->head = NULL; + queue->tail = NULL; +} + +/*+F************************************************************************* + * Function: + * scbq_insert_head + * + * Description: + * Add an SCB to the head of the list. + * + *-F*************************************************************************/ +static inline void +scbq_insert_head(volatile scb_queue_type *queue, struct aic7xxx_scb *scb) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags; +#endif + + DRIVER_LOCK + scb->q_next = queue->head; + queue->head = scb; + if (queue->tail == NULL) /* If list was empty, update tail. */ + queue->tail = queue->head; + DRIVER_UNLOCK +} + +/*+F************************************************************************* + * Function: + * scbq_remove_head + * + * Description: + * Remove an SCB from the head of the list. + * + *-F*************************************************************************/ +static inline struct aic7xxx_scb * +scbq_remove_head(volatile scb_queue_type *queue) +{ + struct aic7xxx_scb * scbp; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags; +#endif + + DRIVER_LOCK + scbp = queue->head; + if (queue->head != NULL) + queue->head = queue->head->q_next; + if (queue->head == NULL) /* If list is now empty, update tail. */ + queue->tail = NULL; + DRIVER_UNLOCK + return(scbp); +} + +/*+F************************************************************************* + * Function: + * scbq_remove + * + * Description: + * Removes an SCB from the list. + * + *-F*************************************************************************/ +static inline void +scbq_remove(volatile scb_queue_type *queue, struct aic7xxx_scb *scb) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags; +#endif + + DRIVER_LOCK + if (queue->head == scb) + { + /* At beginning of queue, remove from head. */ + scbq_remove_head(queue); + } + else + { + struct aic7xxx_scb *curscb = queue->head; + + /* + * Search until the next scb is the one we're looking for, or + * we run out of queue. + */ + while ((curscb != NULL) && (curscb->q_next != scb)) + { + curscb = curscb->q_next; + } + if (curscb != NULL) + { + /* Found it. */ + curscb->q_next = scb->q_next; + if (scb->q_next == NULL) + { + /* Update the tail when removing the tail. */ + queue->tail = curscb; + } + } + } + DRIVER_UNLOCK +} + +/*+F************************************************************************* + * Function: + * scbq_insert_tail + * + * Description: + * Add an SCB at the tail of the list. + * + *-F*************************************************************************/ +static inline void +scbq_insert_tail(volatile scb_queue_type *queue, struct aic7xxx_scb *scb) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags; +#endif + + DRIVER_LOCK + scb->q_next = NULL; + if (queue->tail != NULL) /* Add the scb at the end of the list. */ + queue->tail->q_next = scb; + queue->tail = scb; /* Update the tail. */ + if (queue->head == NULL) /* If list was empty, update head. */ + queue->head = queue->tail; + DRIVER_UNLOCK +} + +/*+F************************************************************************* + * Function: + * aic7xxx_match_scb + * + * Description: + * Checks to see if an scb matches the target/channel as specified. + * If target is ALL_TARGETS (-1), then we're looking for any device + * on the specified channel; this happens when a channel is going + * to be reset and all devices on that channel must be aborted. + *-F*************************************************************************/ +static int +aic7xxx_match_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, + int target, int channel, int lun, unsigned char tag) +{ + int targ = (scb->hscb->target_channel_lun >> 4) & 0x0F; + int chan = (scb->hscb->target_channel_lun >> 3) & 0x01; + int slun = scb->hscb->target_channel_lun & 0x07; + int match; + + match = ((chan == channel) || (channel == ALL_CHANNELS)); + if (match != 0) + match = ((targ == target) || (target == ALL_TARGETS)); + if (match != 0) + match = ((lun == slun) || (lun == ALL_LUNS)); + if (match != 0) + match = ((tag == scb->hscb->tag) || (tag == SCB_LIST_NULL)); + + if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) + { + printk(KERN_INFO "(scsi%d:%d:%d:%d:tag%d) %s search criteria" + " (scsi%d:%d:%d:%d:tag%d)\n", p->host_no, CTL_OF_SCB(scb), + scb->hscb->tag, (match) ? "matches" : "doesn't match", + p->host_no, channel, target, lun, tag); + } + + return (match); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_add_curscb_to_free_list + * + * Description: + * Adds the current scb (in SCBPTR) to the list of free SCBs. + *-F*************************************************************************/ +static void +aic7xxx_add_curscb_to_free_list(struct aic7xxx_host *p) +{ + /* + * Invalidate the tag so that aic7xxx_find_scb doesn't think + * it's active + */ + aic_outb(p, SCB_LIST_NULL, SCB_TAG); + aic_outb(p, 0, SCB_CONTROL); + + aic_outb(p, aic_inb(p, FREE_SCBH), SCB_NEXT); + aic_outb(p, aic_inb(p, SCBPTR), FREE_SCBH); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_rem_scb_from_disc_list + * + * Description: + * Removes the current SCB from the disconnected list and adds it + * to the free list. + *-F*************************************************************************/ +static unsigned char +aic7xxx_rem_scb_from_disc_list(struct aic7xxx_host *p, unsigned char scbptr) +{ + unsigned char next; + unsigned char prev; + + aic_outb(p, scbptr, SCBPTR); + next = aic_inb(p, SCB_NEXT); + prev = aic_inb(p, SCB_PREV); + aic7xxx_add_curscb_to_free_list(p); + + if (prev != SCB_LIST_NULL) + { + aic_outb(p, prev, SCBPTR); + aic_outb(p, next, SCB_NEXT); + } + else + { + aic_outb(p, next, DISCONNECTED_SCBH); + } + + if (next != SCB_LIST_NULL) + { + aic_outb(p, next, SCBPTR); + aic_outb(p, prev, SCB_PREV); + } + return next; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_busy_target + * + * Description: + * Set the specified target busy. + *-F*************************************************************************/ +static inline void +aic7xxx_busy_target(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + p->untagged_scbs[scb->hscb->target_channel_lun] = scb->hscb->tag; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_index_busy_target + * + * Description: + * Returns the index of the busy target, and optionally sets the + * target inactive. + *-F*************************************************************************/ +static inline unsigned char +aic7xxx_index_busy_target(struct aic7xxx_host *p, unsigned char tcl, + int unbusy) +{ + unsigned char busy_scbid; + + busy_scbid = p->untagged_scbs[tcl]; + if (unbusy) + { + p->untagged_scbs[tcl] = SCB_LIST_NULL; + } + return (busy_scbid); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_find_scb + * + * Description: + * Look through the SCB array of the card and attempt to find the + * hardware SCB that corresponds to the passed in SCB. Return + * SCB_LIST_NULL if unsuccessful. This routine assumes that the + * card is already paused. + *-F*************************************************************************/ +static unsigned char +aic7xxx_find_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + unsigned char saved_scbptr; + unsigned char curindex; + + saved_scbptr = aic_inb(p, SCBPTR); + curindex = 0; + for (curindex = 0; curindex < p->scb_data->maxhscbs; curindex++) + { + aic_outb(p, curindex, SCBPTR); + if (aic_inb(p, SCB_TAG) == scb->hscb->tag) + { + break; + } + } + aic_outb(p, saved_scbptr, SCBPTR); + if (curindex >= p->scb_data->maxhscbs) + { + curindex = SCB_LIST_NULL; + } + + return (curindex); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_allocate_scb + * + * Description: + * Get an SCB from the free list or by allocating a new one. + *-F*************************************************************************/ +static int +aic7xxx_allocate_scb(struct aic7xxx_host *p) +{ + struct aic7xxx_scb *scbp = NULL; + int scb_size = sizeof(struct aic7xxx_scb) + + sizeof (struct hw_scatterlist) * AIC7XXX_MAX_SG; + int i; + int step = PAGE_SIZE / 1024; + unsigned long scb_count = 0; + struct hw_scatterlist *hsgp; + struct aic7xxx_scb *scb_ap; + unsigned long temp; + + + if (p->scb_data->numscbs < p->scb_data->maxscbs) + { + /* + * Calculate the optimal number of SCBs to allocate. + * + * NOTE: This formula works because the sizeof(sg_array) is always + * 1024. Therefore, scb_size * i would always be > PAGE_SIZE * + * (i/step). The (i-1) allows the left hand side of the equation + * to grow into the right hand side to a point of near perfect + * efficiency since scb_size * (i -1) is growing slightly faster + * than the right hand side. If the number of SG array elements + * is changed, this function may not be near so efficient any more. + */ + for ( i=step;; i *= 2 ) + { + if ( (scb_size * (i-1)) >= ( (PAGE_SIZE * (i/step)) - 64 ) ) + { + i /= 2; + break; + } + } + scb_count = MIN( (i-1), p->scb_data->maxscbs - p->scb_data->numscbs); + scb_ap = (struct aic7xxx_scb *)kmalloc(scb_size * scb_count, GFP_ATOMIC); + if (scb_ap != NULL) + { +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + { + if (p->scb_data->numscbs == 0) + printk(INFO_LEAD "Allocating initial %ld SCB structures.\n", + p->host_no, -1, -1, -1, scb_count); + else + printk(INFO_LEAD "Allocating %ld additional SCB structures.\n", + p->host_no, -1, -1, -1, scb_count); + } +#endif + memset(scb_ap, 0, scb_count * scb_size); + temp = (unsigned long) &scb_ap[scb_count]; + temp += 1023; + temp &= ~1023; + hsgp = (struct hw_scatterlist *)temp; + for (i=0; i < scb_count; i++) + { + scbp = &scb_ap[i]; + scbp->hscb = &p->scb_data->hscbs[p->scb_data->numscbs]; + scbp->sg_list = &hsgp[i * AIC7XXX_MAX_SG]; + memset(scbp->hscb, 0, sizeof(struct aic7xxx_hwscb)); + scbp->hscb->tag = p->scb_data->numscbs; + /* + * Place in the scb array; never is removed + */ + p->scb_data->scb_array[p->scb_data->numscbs++] = scbp; + scbq_insert_head(&p->scb_data->free_scbs, scbp); + } + scbp->kmalloc_ptr = scb_ap; + } + else + { + return(0); + } + } + return(scb_count); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_queue_cmd_complete + * + * Description: + * Due to race conditions present in the SCSI subsystem, it is easier + * to queue completed commands, then call scsi_done() on them when + * we're finished. This function queues the completed commands. + *-F*************************************************************************/ +static void +aic7xxx_queue_cmd_complete(struct aic7xxx_host *p, Scsi_Cmnd *cmd) +{ + cmd->host_scribble = (char *)p->completeq.head; + p->completeq.head = cmd; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_done_cmds_complete + * + * Description: + * Process the completed command queue. + *-F*************************************************************************/ +static void +aic7xxx_done_cmds_complete(struct aic7xxx_host *p) +{ + Scsi_Cmnd *cmd; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned int cpu_flags = 0; + + DRIVER_LOCK + while (p->completeq.head != NULL) + { + cmd = p->completeq.head; + p->completeq.head = (Scsi_Cmnd *)cmd->host_scribble; + cmd->host_scribble = NULL; + sti(); + cmd->scsi_done(cmd); + cli(); + } + DRIVER_UNLOCK +#else + while (p->completeq.head != NULL) + { + cmd = p->completeq.head; + p->completeq.head = (Scsi_Cmnd *)cmd->host_scribble; + cmd->host_scribble = NULL; + cmd->scsi_done(cmd); + } +#endif +} + +/*+F************************************************************************* + * Function: + * aic7xxx_free_scb + * + * Description: + * Free the scb and insert into the free scb list. + *-F*************************************************************************/ +static void +aic7xxx_free_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + + scb->flags = SCB_FREE; + scb->cmd = NULL; + scb->sg_count = 0; + scb->sg_length = 0; + scb->tag_action = 0; + scb->hscb->control = 0; + scb->hscb->target_status = 0; + scb->hscb->target_channel_lun = SCB_LIST_NULL; + + scbq_insert_head(&p->scb_data->free_scbs, scb); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_done + * + * Description: + * Calls the higher level scsi done function and frees the scb. + *-F*************************************************************************/ +static void +aic7xxx_done(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + Scsi_Cmnd *cmd = scb->cmd; + int tindex = TARGET_INDEX(cmd); + struct aic7xxx_scb *scbp; + unsigned char queue_depth; + + if (scb->flags & SCB_RECOVERY_SCB) + { + p->flags &= ~AHC_ABORT_PENDING; + } + if (scb->flags & SCB_RESET) + { + cmd->result = (DID_RESET << 16) | (cmd->result & 0xffff); + } + else if (scb->flags & SCB_ABORT) + { + cmd->result = (DID_RESET << 16) | (cmd->result & 0xffff); + } + else if (!(p->dev_flags[tindex] & DEVICE_SCANNED)) + { + if ( (cmd->cmnd[0] == INQUIRY) && (cmd->result == DID_OK) ) + { + char *buffer; + + if(cmd->use_sg) + { + struct scatterlist *sg; + + sg = (struct scatterlist *)cmd->request_buffer; + buffer = (char *)sg[0].address; + } + else + { + buffer = (char *)cmd->request_buffer; + } +#define WIDE_INQUIRY_BITS 0x60 +#define SYNC_INQUIRY_BITS 0x10 + if ( (buffer[7] & WIDE_INQUIRY_BITS) && + (p->features & AHC_WIDE) ) + { + p->needwdtr |= (1<<tindex); + p->needwdtr_copy |= (1<<tindex); + if ( (p->flags & AHC_SEEPROM_FOUND) && + (p->transinfo[tindex].user_width != MSG_EXT_WDTR_BUS_16_BIT) ) + p->transinfo[tindex].goal_width = MSG_EXT_WDTR_BUS_8_BIT; + else + p->transinfo[tindex].goal_width = MSG_EXT_WDTR_BUS_16_BIT; + } + else + { + p->needwdtr &= ~(1<<tindex); + p->needwdtr_copy &= ~(1<<tindex); + pause_sequencer(p); + aic7xxx_set_width(p, cmd->target, cmd->channel, cmd->lun, + MSG_EXT_WDTR_BUS_8_BIT, (AHC_TRANS_ACTIVE | + AHC_TRANS_GOAL | + AHC_TRANS_CUR) ); + unpause_sequencer(p, FALSE); + } + if (buffer[7] & SYNC_INQUIRY_BITS) + { + p->needsdtr |= (1<<tindex); + p->needsdtr_copy |= (1<<tindex); + + if (p->flags & AHC_SEEPROM_FOUND) + p->transinfo[tindex].goal_period = p->transinfo[tindex].user_period; + else if (p->features & AHC_ULTRA2) + p->transinfo[tindex].goal_period = + aic7xxx_syncrates[AHC_SYNCRATE_ULTRA2].period; + else if (p->features & AHC_ULTRA) + p->transinfo[tindex].goal_period = + aic7xxx_syncrates[AHC_SYNCRATE_ULTRA].period; + else + p->transinfo[tindex].goal_period = + aic7xxx_syncrates[AHC_SYNCRATE_FAST].period; + + if (p->features & AHC_ULTRA2) + p->transinfo[tindex].goal_offset = MAX_OFFSET_ULTRA2; + else if (p->transinfo[tindex].goal_width == MSG_EXT_WDTR_BUS_16_BIT) + p->transinfo[tindex].goal_offset = MAX_OFFSET_16BIT; + else + p->transinfo[tindex].goal_offset = MAX_OFFSET_8BIT; + } + else + { + p->needsdtr &= ~(1<<tindex); + p->needsdtr_copy &= ~(1<<tindex); + p->transinfo[tindex].goal_period = 0; + p->transinfo[tindex].goal_offset = 0; + } + p->dev_flags[tindex] |= DEVICE_SCANNED; + p->dev_flags[tindex] |= DEVICE_PRINT_WDTR | DEVICE_PRINT_SDTR; +#undef WIDE_INQUIRY_BITS +#undef SYNC_INQUIRY_BITS + } + } + else if ((scb->flags & (SCB_MSGOUT_WDTR | SCB_MSGOUT_SDTR)) != 0) + { + unsigned short mask; + int message_error = FALSE; + + mask = 0x01 << tindex; + + /* + * Check to see if we get an invalid message or a message error + * after failing to negotiate a wide or sync transfer message. + */ + if ((scb->flags & SCB_SENSE) && + ((scb->cmd->sense_buffer[12] == 0x43) || /* INVALID_MESSAGE */ + (scb->cmd->sense_buffer[12] == 0x49))) /* MESSAGE_ERROR */ + { + message_error = TRUE; + } + + if (scb->flags & SCB_MSGOUT_WDTR) + { + p->wdtr_pending &= ~mask; + if (message_error) + { + if ( (aic7xxx_verbose & VERBOSE_NEGOTIATION2) && + (p->dev_flags[tindex] & DEVICE_PRINT_WDTR) ) + { + printk(INFO_LEAD "Device failed to complete Wide Negotiation " + "processing and\n", p->host_no, CTL_OF_SCB(scb)); + printk(INFO_LEAD "returned a sense error code for invalid message, " + "disabling future\n", p->host_no, CTL_OF_SCB(scb)); + printk(INFO_LEAD "Wide negotiation to this device.\n", p->host_no, + CTL_OF_SCB(scb)); + p->dev_flags[tindex] &= ~DEVICE_PRINT_WDTR; + } + p->needwdtr &= ~mask; + p->needwdtr_copy &= ~mask; + } + } + if (scb->flags & SCB_MSGOUT_SDTR) + { + p->sdtr_pending &= ~mask; + if (message_error) + { + if ( (aic7xxx_verbose & VERBOSE_NEGOTIATION2) && + (p->dev_flags[tindex] & DEVICE_PRINT_SDTR) ) + { + printk(INFO_LEAD "Device failed to complete Sync Negotiation " + "processing and\n", p->host_no, CTL_OF_SCB(scb)); + printk(INFO_LEAD "returned a sense error code for invalid message, " + "disabling future\n", p->host_no, CTL_OF_SCB(scb)); + printk(INFO_LEAD "Sync negotiation to this device.\n", p->host_no, + CTL_OF_SCB(scb)); + p->dev_flags[tindex] &= ~DEVICE_PRINT_SDTR; + } + p->needsdtr &= ~mask; + p->needsdtr_copy &= ~mask; + } + } + } + queue_depth = p->dev_temp_queue_depth[tindex]; + if (queue_depth >= p->dev_active_cmds[tindex]) + { + scbp = scbq_remove_head(&p->delayed_scbs[tindex]); + if (scbp) + { + if (queue_depth == 1) + { + /* + * Give extra preference to untagged devices, such as CD-R devices + * This makes it more likely that a drive *won't* stuff up while + * waiting on data at a critical time, such as CD-R writing and + * audio CD ripping operations. Should also benefit tape drives. + */ + scbq_insert_head(&p->waiting_scbs, scbp); + } + else + { + scbq_insert_tail(&p->waiting_scbs, scbp); + } +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Moving SCB from delayed to waiting queue.\n", + p->host_no, CTL_OF_SCB(scbp)); +#endif + if (queue_depth > p->dev_active_cmds[tindex]) + { + scbp = scbq_remove_head(&p->delayed_scbs[tindex]); + if (scbp) + scbq_insert_tail(&p->waiting_scbs, scbp); + } + } + } + if ( !(scb->tag_action) && (p->tagenable & (1<<tindex)) ) + { + p->dev_temp_queue_depth[tindex] = p->dev_max_queue_depth[tindex]; + } + p->dev_active_cmds[tindex]--; + p->activescbs--; + + /* + * If this was an untagged I/O, unbusy the target so the sequencer won't + * mistake things later + */ + if (aic7xxx_index_busy_target(p, scb->hscb->target_channel_lun, FALSE) == + scb->hscb->tag) + { + aic7xxx_index_busy_target(p, scb->hscb->target_channel_lun, TRUE); + } + + { + int actual; + + /* + * XXX: we should actually know how much actually transferred + * XXX: for each command, but apparently that's too difficult. + * + * We set a lower limit of 512 bytes on the transfer length. We + * ignore anything less than this because we don't have a real + * reason to count it. Read/Writes to tapes are usually about 20K + * and disks are a minimum of 512 bytes unless you want to count + * non-read/write commands (such as TEST_UNIT_READY) which we don't + */ + actual = scb->sg_length; + if ((actual >= 512) && (((cmd->result >> 16) & 0xf) == DID_OK)) + { + struct aic7xxx_xferstats *sp; +#ifdef AIC7XXX_PROC_STATS + long *ptr; + int x; +#endif /* AIC7XXX_PROC_STATS */ + + sp = &p->stats[TARGET_INDEX(cmd)][cmd->lun & 0x7]; + sp->xfers++; +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if ( (sp->xfers > 16) && (aic7xxx_verbose > 0xffff) ) + aic7xxx_verbose &= 0xffff; +#endif + + /* + * For block devices, cmd->request.cmd is always == either READ or + * WRITE. For character devices, this isn't always set properly, so + * we check data_cmnd[0]. This catches the conditions for st.c, but + * I'm still not sure if request.cmd is valid for sg devices. + */ + if ( (cmd->request.cmd == WRITE) || (cmd->data_cmnd[0] == WRITE_6) || + (cmd->data_cmnd[0] == WRITE_FILEMARKS) ) + { + sp->w_total++; + sp->w_total512 += (actual >> 9); +#ifdef AIC7XXX_PROC_STATS + ptr = sp->w_bins; +#endif /* AIC7XXX_PROC_STATS */ + } + else + { + sp->r_total++; + sp->r_total512 += (actual >> 9); +#ifdef AIC7XXX_PROC_STATS + ptr = sp->r_bins; +#endif /* AIC7XXX_PROC_STATS */ + } +#ifdef AIC7XXX_PROC_STATS + for (x = 9; x <= 17; x++) + { + if (actual < (1 << x)) + { + ptr[x - 9]++; + break; + } + } + if (x > 17) + { + ptr[x - 9]++; + } +#endif /* AIC7XXX_PROC_STATS */ + } + } + aic7xxx_free_scb(p, scb); + aic7xxx_queue_cmd_complete(p, cmd); + +} + +/*+F************************************************************************* + * Function: + * aic7xxx_run_done_queue + * + * Description: + * Calls the aic7xxx_done() for the Scsi_Cmnd of each scb in the + * aborted list, and adds each scb to the free list. If complete + * is TRUE, we also process the commands complete list. + *-F*************************************************************************/ +static void +aic7xxx_run_done_queue(struct aic7xxx_host *p, /*complete*/ int complete) +{ + struct aic7xxx_scb *scb; + int i, found = 0; + + for (i = 0; i < p->scb_data->numscbs; i++) + { + scb = p->scb_data->scb_array[i]; + if (scb->flags & SCB_QUEUED_FOR_DONE) + { + if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) + printk(INFO_LEAD "Aborting scb %d\n", + p->host_no, CTL_OF_SCB(scb), scb->hscb->tag); + found++; + aic7xxx_done(p, scb); + } + } + if (aic7xxx_verbose & (VERBOSE_ABORT_RETURN | VERBOSE_RESET_RETURN)) + { + printk(INFO_LEAD "%d commands found and queued for " + "completion.\n", p->host_no, -1, -1, -1, found); + } + if (complete) + { + aic7xxx_done_cmds_complete(p); + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_abort_waiting_scb + * + * Description: + * Manipulate the waiting for selection list and return the + * scb that follows the one that we remove. + *-F*************************************************************************/ +static unsigned char +aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, + unsigned char scbpos, unsigned char prev) +{ + unsigned char curscb, next; + + /* + * Select the SCB we want to abort and pull the next pointer out of it. + */ + curscb = aic_inb(p, SCBPTR); + aic_outb(p, scbpos, SCBPTR); + next = aic_inb(p, SCB_NEXT); + + aic7xxx_add_curscb_to_free_list(p); + + /* + * Update the waiting list + */ + if (prev == SCB_LIST_NULL) + { + /* + * First in the list + */ + aic_outb(p, next, WAITING_SCBH); + } + else + { + /* + * Select the scb that pointed to us and update its next pointer. + */ + aic_outb(p, prev, SCBPTR); + aic_outb(p, next, SCB_NEXT); + } + /* + * Point us back at the original scb position and inform the SCSI + * system that the command has been aborted. + */ + aic_outb(p, curscb, SCBPTR); + return (next); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_search_qinfifo + * + * Description: + * Search the queue-in FIFO for matching SCBs and conditionally + * requeue. Returns the number of matching SCBs. + *-F*************************************************************************/ +static int +aic7xxx_search_qinfifo(struct aic7xxx_host *p, int target, int channel, + int lun, unsigned char tag, int flags, int requeue, + volatile scb_queue_type *queue) +{ + int found; + unsigned char qinpos, qintail; + struct aic7xxx_scb *scbp; + + found = 0; + qinpos = aic_inb(p, QINPOS); + qintail = p->qinfifonext; + + p->qinfifonext = qinpos; + + while (qinpos != qintail) + { + scbp = p->scb_data->scb_array[p->qinfifo[qinpos++]]; + if (aic7xxx_match_scb(p, scbp, target, channel, lun, tag)) + { + /* + * We found an scb that needs to be removed. + */ + if (requeue && (queue != NULL)) + { + if (scbp->flags & SCB_WAITINGQ) + { + scbq_remove(queue, scbp); + scbq_remove(&p->waiting_scbs, scbp); + scbq_remove(&p->delayed_scbs[TARGET_INDEX(scbp->cmd)], scbp); + p->dev_active_cmds[TARGET_INDEX(scbp->cmd)]++; + p->activescbs++; + } + scbq_insert_tail(queue, scbp); + p->dev_active_cmds[TARGET_INDEX(scbp->cmd)]--; + p->activescbs--; + scbp->flags |= SCB_WAITINGQ; + if ( !(scbp->tag_action & TAG_ENB) ) + { + aic7xxx_index_busy_target(p, scbp->hscb->target_channel_lun, + TRUE); + } + } + else if (requeue) + { + p->qinfifo[p->qinfifonext++] = scbp->hscb->tag; + } + else + { + /* + * Preserve any SCB_RECOVERY_SCB flags on this scb then set the + * flags we were called with, presumeably so aic7xxx_run_done_queue + * can find this scb + */ + scbp->flags = flags | (scbp->flags & SCB_RECOVERY_SCB); + if (aic7xxx_index_busy_target(p, scbp->hscb->target_channel_lun, + FALSE) == scbp->hscb->tag) + { + aic7xxx_index_busy_target(p, scbp->hscb->target_channel_lun, + TRUE); + } + } + found++; + } + else + { + p->qinfifo[p->qinfifonext++] = scbp->hscb->tag; + } + } + /* + * Now that we've done the work, clear out any left over commands in the + * qinfifo and update the KERNEL_QINPOS down on the card. + * + * NOTE: This routine expect the sequencer to already be paused when + * it is run....make sure it's that way! + */ + qinpos = p->qinfifonext; + while(qinpos != qintail) + { + p->qinfifo[qinpos++] = SCB_LIST_NULL; + } + if (p->features & AHC_QUEUE_REGS) + aic_outb(p, p->qinfifonext, HNSCB_QOFF); + else + aic_outb(p, p->qinfifonext, KERNEL_QINPOS); + + return (found); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_scb_on_qoutfifo + * + * Description: + * Is the scb that was passed to us currently on the qoutfifo? + *-F*************************************************************************/ +static int +aic7xxx_scb_on_qoutfifo(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + int i=0; + + while(p->qoutfifo[(p->qoutfifonext + i) & 0xff ] != SCB_LIST_NULL) + { + if(p->qoutfifo[(p->qoutfifonext + i) & 0xff ] == scb->hscb->tag) + return TRUE; + else + i++; + } + return FALSE; +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_reset_device + * + * Description: + * The device at the given target/channel has been reset. Abort + * all active and queued scbs for that target/channel. This function + * need not worry about linked next pointers because if was a MSG_ABORT_TAG + * then we had a tagged command (no linked next), if it was MSG_ABORT or + * MSG_BUS_DEV_RESET then the device won't know about any commands any more + * and no busy commands will exist, and if it was a bus reset, then nothing + * knows about any linked next commands any more. In all cases, we don't + * need to worry about the linked next or busy scb, we just need to clear + * them. + *-F*************************************************************************/ +static void +aic7xxx_reset_device(struct aic7xxx_host *p, int target, int channel, + int lun, unsigned char tag) +{ + struct aic7xxx_scb *scbp; + unsigned char active_scb, tcl; + int i = 0, j, init_lists = FALSE; + + /* + * Restore this when we're done + */ + active_scb = aic_inb(p, SCBPTR); + + if (aic7xxx_verbose & (VERBOSE_RESET_PROCESS | VERBOSE_ABORT_PROCESS)) + printk(INFO_LEAD "Reset device, active_scb %d\n", + p->host_no, channel, target, lun, active_scb); + /* + * Deal with the busy target and linked next issues. + */ + { + int min_target, max_target; + struct aic7xxx_scb *scbp, *prev_scbp; + + /* Make all targets 'relative' to bus A. */ + if (target == ALL_TARGETS) + { + switch (channel) + { + case 0: + min_target = 0; + max_target = (p->features & AHC_WIDE) ? 15 : 7; + break; + case 1: + min_target = 8; + max_target = 15; + break; + case ALL_CHANNELS: + default: + min_target = 0; + max_target = (p->features & (AHC_TWIN|AHC_WIDE)) ? 15 : 7; + break; + } + } + else + { + min_target = target | (channel << 3); + max_target = min_target; + } + + + for (i = min_target; i <= max_target; i++) + { + if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) + printk(INFO_LEAD "Cleaning up status information " + "and delayed_scbs.\n", p->host_no, channel, i, lun); + if ( !(p->dev_flags[i] & DEVICE_TAGGED_SUCCESS) && + (p->dev_active_cmds[i]) && + (p->tagenable & (0x01 << i)) ) + { + printk(INFO_LEAD "Device appears to be choking on tagged commands.\n", + p->host_no, channel, i, lun); + printk(INFO_LEAD "Will use untagged I/O instead.\n", p->host_no, + channel, i, lun); + p->dev_max_queue_depth[i] = 1; + p->dev_temp_queue_depth[i] = 1; + p->tagenable &= ~(0x01 << i); + p->orderedtag &= ~(0x01 << i); + } + p->dev_flags[i] &= ~BUS_DEVICE_RESET_PENDING; + if ( tag == SCB_LIST_NULL ) + { + p->dev_flags[i] |= DEVICE_PRINT_WDTR | DEVICE_PRINT_SDTR; + p->dev_last_reset[i] = jiffies; + p->dev_last_queue_full_count[i] = 0; + p->dev_last_queue_full[i] = 0; + p->dev_temp_queue_depth[i] = + p->dev_max_queue_depth[i]; + /* + * In case this isn't a full bus reset, we want to add a 4 second timer in + * here so that we can delay all re-sent commands for this device for the + * 4 seconds and then have our timer routine pick them back up. + */ + if(p->dev_timer[i].expires) + { + del_timer(&p->dev_timer[i]); + } + p->dev_timer[i].expires = jiffies + (4 * HZ); + add_timer(&p->dev_timer[i]); + } + for(j=0; j<MAX_LUNS; j++) + { + if (channel == 1) + tcl = ((i << 4) & 0x70) | (channel << 3) | j; + else + tcl = (i << 4) | (channel << 3) | j; + if ( (aic7xxx_index_busy_target(p, tcl, FALSE) == tag) || + (tag == SCB_LIST_NULL) ) + aic7xxx_index_busy_target(p, tcl, /* unbusy */ TRUE); + } + j = 0; + prev_scbp = NULL; + scbp = p->delayed_scbs[i].head; + while ( (scbp != NULL) && (j++ <= (p->scb_data->numscbs + 1)) ) + { + prev_scbp = scbp; + scbp = scbp->q_next; + if ( prev_scbp == scbp ) + { + if (aic7xxx_verbose & (VERBOSE_ABORT | VERBOSE_RESET)) + printk(WARN_LEAD "Yikes!! scb->q_next == scb " + "in the delayed_scbs queue!\n", p->host_no, channel, i, lun); + scbp = NULL; + prev_scbp->q_next = NULL; + p->delayed_scbs[i].tail = prev_scbp; + } + if (aic7xxx_match_scb(p, prev_scbp, target, channel, lun, tag)) + { + scbq_remove(&p->delayed_scbs[i], prev_scbp); + if (prev_scbp->flags & SCB_WAITINGQ) + { + p->dev_active_cmds[i]++; + p->activescbs++; + } + prev_scbp->flags &= ~(SCB_ACTIVE | SCB_WAITINGQ); + prev_scbp->flags |= SCB_RESET | SCB_QUEUED_FOR_DONE; + } + } + if ( j > (p->scb_data->maxscbs + 1) ) + { + if (aic7xxx_verbose & (VERBOSE_ABORT | VERBOSE_RESET)) + printk(WARN_LEAD "Yikes!! There's a loop in the " + "delayed_scbs queue!\n", p->host_no, channel, i, lun); + scbq_init(&p->delayed_scbs[i]); + } + if ( (p->delayed_scbs[i].head == NULL) && + (p->dev_timer[i].expires) ) + { + del_timer(&p->dev_timer[i]); + p->dev_timer[i].expires = 0; + } + } + } + + if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) + printk(INFO_LEAD "Cleaning QINFIFO.\n", p->host_no, channel, target, lun ); + aic7xxx_search_qinfifo(p, target, channel, lun, tag, + SCB_RESET | SCB_QUEUED_FOR_DONE, /* requeue */ FALSE, NULL); + +/* + * Search the waiting_scbs queue for matches, this catches any SCB_QUEUED + * ABORT/RESET commands. + */ + if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) + printk(INFO_LEAD "Cleaning waiting_scbs.\n", p->host_no, channel, + target, lun ); + { + struct aic7xxx_scb *scbp, *prev_scbp; + + j = 0; + prev_scbp = NULL; + scbp = p->waiting_scbs.head; + while ( (scbp != NULL) && (j++ <= (p->scb_data->numscbs + 1)) ) + { + prev_scbp = scbp; + scbp = scbp->q_next; + if ( prev_scbp == scbp ) + { + if (aic7xxx_verbose & (VERBOSE_ABORT | VERBOSE_RESET)) + printk(WARN_LEAD "Yikes!! scb->q_next == scb " + "in the waiting_scbs queue!\n", p->host_no, CTL_OF_SCB(scbp)); + scbp = NULL; + prev_scbp->q_next = NULL; + p->waiting_scbs.tail = prev_scbp; + } + if (aic7xxx_match_scb(p, prev_scbp, target, channel, lun, tag)) + { + scbq_remove(&p->waiting_scbs, prev_scbp); + if (prev_scbp->flags & SCB_WAITINGQ) + { + p->dev_active_cmds[TARGET_INDEX(prev_scbp->cmd)]++; + p->activescbs++; + } + prev_scbp->flags &= ~(SCB_ACTIVE | SCB_WAITINGQ); + prev_scbp->flags |= SCB_RESET | SCB_QUEUED_FOR_DONE; + } + } + if ( j > (p->scb_data->maxscbs + 1) ) + { + if (aic7xxx_verbose & (VERBOSE_ABORT | VERBOSE_RESET)) + printk(WARN_LEAD "Yikes!! There's a loop in the " + "waiting_scbs queue!\n", p->host_no, channel, target, lun); + scbq_init(&p->waiting_scbs); + } + } + + + /* + * Search waiting for selection list. + */ + if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) + printk(INFO_LEAD "Cleaning waiting for selection " + "list.\n", p->host_no, channel, target, lun); + { + unsigned char next, prev, scb_index; + + next = aic_inb(p, WAITING_SCBH); /* Start at head of list. */ + prev = SCB_LIST_NULL; + j = 0; + while ( (next != SCB_LIST_NULL) && (j++ <= (p->scb_data->maxscbs + 1)) ) + { + aic_outb(p, next, SCBPTR); + scb_index = aic_inb(p, SCB_TAG); + if (scb_index >= p->scb_data->numscbs) + { + /* + * No aic7xxx_verbose check here.....we want to see this since it + * means either the kernel driver or the sequencer screwed things up + */ + printk(WARN_LEAD "Waiting List inconsistency; SCB index=%d, " + "numscbs=%d\n", p->host_no, channel, target, lun, scb_index, + p->scb_data->numscbs); + next = aic_inb(p, SCB_NEXT); + aic7xxx_add_curscb_to_free_list(p); + } + else + { + scbp = p->scb_data->scb_array[scb_index]; + if (aic7xxx_match_scb(p, scbp, target, channel, lun, tag)) + { + next = aic7xxx_abort_waiting_scb(p, scbp, next, prev); + if (scbp->flags & SCB_WAITINGQ) + { + p->dev_active_cmds[TARGET_INDEX(scbp->cmd)]++; + p->activescbs++; + } + scbp->flags &= ~(SCB_ACTIVE | SCB_WAITINGQ); + scbp->flags |= SCB_RESET | SCB_QUEUED_FOR_DONE; + if (prev == SCB_LIST_NULL) + { + /* + * This is either the first scb on the waiting list, or we + * have already yanked the first and haven't left any behind. + * Either way, we need to turn off the selection hardware if + * it isn't already off. + */ + aic_outb(p, aic_inb(p, SCSISEQ) & ~ENSELO, SCSISEQ); + aic_outb(p, CLRSELTIMEO, CLRSINT1); + } + } + else + { + prev = next; + next = aic_inb(p, SCB_NEXT); + } + } + } + if ( j > (p->scb_data->maxscbs + 1) ) + { + printk(WARN_LEAD "Yikes!! There is a loop in the waiting for " + "selection list!\n", p->host_no, channel, target, lun); + init_lists = TRUE; + } + } + + /* + * Go through disconnected list and remove any entries we have queued + * for completion, zeroing their control byte too. + */ + if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) + printk(INFO_LEAD "Cleaning disconnected scbs " + "list.\n", p->host_no, channel, target, lun); + if (p->features & AHC_PAGESCBS) + { + unsigned char next, prev, scb_index; + + next = aic_inb(p, DISCONNECTED_SCBH); + prev = SCB_LIST_NULL; + j = 0; + while ( (next != SCB_LIST_NULL) && (j++ <= (p->scb_data->maxscbs + 1)) ) + { + aic_outb(p, next, SCBPTR); + scb_index = aic_inb(p, SCB_TAG); + if (scb_index > p->scb_data->numscbs) + { + printk(WARN_LEAD "Disconnected List inconsistency; SCB index=%d, " + "numscbs=%d\n", p->host_no, channel, target, lun, scb_index, + p->scb_data->numscbs); + next = aic7xxx_rem_scb_from_disc_list(p, next); + } + else + { + scbp = p->scb_data->scb_array[scb_index]; + if (aic7xxx_match_scb(p, scbp, target, channel, lun, tag)) + { + next = aic7xxx_rem_scb_from_disc_list(p, next); + if (scbp->flags & SCB_WAITINGQ) + { + p->dev_active_cmds[TARGET_INDEX(scbp->cmd)]++; + p->activescbs++; + } + scbp->flags &= ~(SCB_ACTIVE | SCB_WAITINGQ); + scbp->flags |= SCB_RESET | SCB_QUEUED_FOR_DONE; + scbp->hscb->control = 0; + } + else + { + prev = next; + next = aic_inb(p, SCB_NEXT); + } + } + } + if ( j > (p->scb_data->maxscbs + 1) ) + { + printk(WARN_LEAD "Yikes!! There is a loop in the disconnected list!\n", + p->host_no, channel, target, lun); + init_lists = TRUE; + } + } + + /* + * Walk the free list making sure no entries on the free list have + * a valid SCB_TAG value or SCB_CONTROL byte. + */ + if (p->features & AHC_PAGESCBS) + { + unsigned char next; + + j = 0; + next = aic_inb(p, FREE_SCBH); + if ( (next >= p->scb_data->maxhscbs) && (next != SCB_LIST_NULL) ) + { + printk(WARN_LEAD "Bogus FREE_SCBH!.\n", p->host_no, channel, + target, lun); + init_lists = TRUE; + next = SCB_LIST_NULL; + } + while ( (next != SCB_LIST_NULL) && (j++ <= (p->scb_data->maxscbs + 1)) ) + { + aic_outb(p, next, SCBPTR); + if (aic_inb(p, SCB_TAG) < p->scb_data->numscbs) + { + printk(WARN_LEAD "Free list inconsistency!.\n", p->host_no, channel, + target, lun); + init_lists = TRUE; + next = SCB_LIST_NULL; + } + else + { + aic_outb(p, SCB_LIST_NULL, SCB_TAG); + aic_outb(p, 0, SCB_CONTROL); + next = aic_inb(p, SCB_NEXT); + } + } + if ( j > (p->scb_data->maxscbs + 1) ) + { + printk(WARN_LEAD "Yikes!! There is a loop in the free list!\n", + p->host_no, channel, target, lun); + init_lists = TRUE; + } + } + + /* + * Go through the hardware SCB array looking for commands that + * were active but not on any list. + */ + if (init_lists) + { + aic_outb(p, SCB_LIST_NULL, FREE_SCBH); + aic_outb(p, SCB_LIST_NULL, WAITING_SCBH); + aic_outb(p, SCB_LIST_NULL, DISCONNECTED_SCBH); + } + for (i = p->scb_data->maxhscbs - 1; i >= 0; i--) + { + unsigned char scbid; + + aic_outb(p, i, SCBPTR); + if (init_lists) + { + aic_outb(p, SCB_LIST_NULL, SCB_TAG); + aic_outb(p, SCB_LIST_NULL, SCB_NEXT); + aic_outb(p, SCB_LIST_NULL, SCB_PREV); + aic_outb(p, 0, SCB_CONTROL); + aic7xxx_add_curscb_to_free_list(p); + } + else + { + scbid = aic_inb(p, SCB_TAG); + if (scbid < p->scb_data->numscbs) + { + scbp = p->scb_data->scb_array[scbid]; + if (aic7xxx_match_scb(p, scbp, target, channel, lun, tag)) + { + aic_outb(p, 0, SCB_CONTROL); + aic_outb(p, SCB_LIST_NULL, SCB_TAG); + aic7xxx_add_curscb_to_free_list(p); + } + } + } + } + + /* + * Go through the entire SCB array now and look for commands for + * for this target that are stillactive. These are other (most likely + * tagged) commands that were disconnected when the reset occurred. + * Any commands we find here we know this about, it wasn't on any queue, + * it wasn't in the qinfifo, it wasn't in the disconnected or waiting + * lists, so it really must have been a paged out SCB. In that case, + * we shouldn't need to bother with updating any counters, just mark + * the correct flags and go on. + */ + for (i = 0; i < p->scb_data->numscbs; i++) + { + scbp = p->scb_data->scb_array[i]; + if ((scbp->flags & SCB_ACTIVE) && + aic7xxx_match_scb(p, scbp, target, channel, lun, tag) && + !aic7xxx_scb_on_qoutfifo(p, scbp)) + { + if (scbp->flags & SCB_WAITINGQ) + { + scbq_remove(&p->waiting_scbs, scbp); + scbq_remove(&p->delayed_scbs[TARGET_INDEX(scbp->cmd)], scbp); + p->dev_active_cmds[TARGET_INDEX(scbp->cmd)]++; + p->activescbs++; + } + scbp->flags |= SCB_RESET | SCB_QUEUED_FOR_DONE; + scbp->flags &= ~(SCB_ACTIVE | SCB_WAITINGQ); + } + } + + aic_outb(p, active_scb, SCBPTR); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_clear_intstat + * + * Description: + * Clears the interrupt status. + *-F*************************************************************************/ +static void +aic7xxx_clear_intstat(struct aic7xxx_host *p) +{ + /* Clear any interrupt conditions this may have caused. */ + aic_outb(p, CLRSELDO | CLRSELDI | CLRSELINGO, CLRSINT0); + aic_outb(p, CLRSELTIMEO | CLRATNO | CLRSCSIRSTI | CLRBUSFREE | CLRSCSIPERR | + CLRPHASECHG | CLRREQINIT, CLRSINT1); + aic_outb(p, CLRSCSIINT | CLRSEQINT | CLRBRKADRINT | CLRPARERR, CLRINT); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_reset_current_bus + * + * Description: + * Reset the current SCSI bus. + *-F*************************************************************************/ +static void +aic7xxx_reset_current_bus(struct aic7xxx_host *p) +{ + + /* Disable reset interrupts. */ + aic_outb(p, aic_inb(p, SIMODE1) & ~ENSCSIRST, SIMODE1); + + /* Turn off the bus' current operations, after all, we shouldn't have any + * valid commands left to cause a RSELI and SELO once we've tossed the + * bus away with this reset, so we might as well shut down the sequencer + * until the bus is restarted as oppossed to saving the current settings + * and restoring them (which makes no sense to me). */ + + /* Turn on the bus reset. */ + aic_outb(p, aic_inb(p, SCSISEQ) | SCSIRSTO, SCSISEQ); + while ( (aic_inb(p, SCSISEQ) & SCSIRSTO) == 0) + mdelay(5); + + mdelay(10); + + /* Turn off the bus reset. */ + aic_outb(p, 0, SCSISEQ); + mdelay(5); + + aic7xxx_clear_intstat(p); + /* Re-enable reset interrupts. */ + aic_outb(p, aic_inb(p, SIMODE1) | ENSCSIRST, SIMODE1); + +} + +/*+F************************************************************************* + * Function: + * aic7xxx_reset_channel + * + * Description: + * Reset the channel. + *-F*************************************************************************/ +static void +aic7xxx_reset_channel(struct aic7xxx_host *p, int channel, int initiate_reset) +{ + unsigned long offset_min, offset_max; + unsigned char sblkctl; + int cur_channel; + + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Reset channel called, %s initiate reset.\n", + p->host_no, channel, -1, -1, (initiate_reset==TRUE) ? "will" : "won't" ); + + + if (channel == 1) + { + p->needsdtr |= (p->needsdtr_copy & 0xFF00); + p->sdtr_pending &= 0x00FF; + offset_min = 8; + offset_max = 16; + } + else + { + if (p->features & AHC_WIDE) + { + p->needsdtr = p->needsdtr_copy; + p->needwdtr = p->needwdtr_copy; + p->sdtr_pending = 0x0; + p->wdtr_pending = 0x0; + offset_min = 0; + offset_max = 16; + } + else + { + /* Channel A */ + p->needsdtr |= (p->needsdtr_copy & 0x00FF); + p->sdtr_pending &= 0xFF00; + offset_min = 0; + offset_max = 8; + } + } + + while (offset_min < offset_max) + { + /* + * Revert to async/narrow transfers until we renegotiate. + */ + aic_outb(p, 0, TARG_SCSIRATE + offset_min); + if (p->features & AHC_ULTRA2) + { + aic_outb(p, 0, TARG_OFFSET + offset_min); + } + offset_min++; + } + + /* + * Reset the bus and unpause/restart the controller + */ + sblkctl = aic_inb(p, SBLKCTL); + if ( (p->chip & AHC_CHIPID_MASK) == AHC_AIC7770 ) + cur_channel = (sblkctl & SELBUSB) >> 3; + else + cur_channel = 0; + if ( (cur_channel != channel) && (p->features & AHC_TWIN) ) + { + /* + * Case 1: Command for another bus is active + */ + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Stealthily resetting idle channel.\n", p->host_no, + channel, -1, -1); + /* + * Stealthily reset the other bus without upsetting the current bus. + */ + aic_outb(p, sblkctl ^ SELBUSB, SBLKCTL); + aic_outb(p, aic_inb(p, SIMODE1) & ~ENBUSFREE, SIMODE1); + if (initiate_reset) + { + aic7xxx_reset_current_bus(p); + } + aic_outb(p, aic_inb(p, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP), SCSISEQ); + aic7xxx_clear_intstat(p); + aic_outb(p, sblkctl, SBLKCTL); + } + else + { + /* + * Case 2: A command from this bus is active or we're idle. + */ + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Resetting currently active channel.\n", p->host_no, + channel, -1, -1); + aic_outb(p, aic_inb(p, SIMODE1) & ~(ENBUSFREE|ENREQINIT), + SIMODE1); + p->flags &= ~AHC_HANDLING_REQINITS; + p->msg_type = MSG_TYPE_NONE; + p->msg_len = 0; + if (initiate_reset) + { + aic7xxx_reset_current_bus(p); + } + aic_outb(p, aic_inb(p, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP), SCSISEQ); + aic7xxx_clear_intstat(p); + } + if (aic7xxx_verbose & VERBOSE_RESET_RETURN) + printk(INFO_LEAD "Channel reset\n", p->host_no, channel, -1, -1); + /* + * Clean up all the state information for the pending transactions + * on this bus. + */ + aic7xxx_reset_device(p, ALL_TARGETS, channel, ALL_LUNS, SCB_LIST_NULL); + + /* + * Convince Mid Level SCSI code to leave us be for a little bit... + */ + p->last_reset = jiffies; + p->host->last_reset = (jiffies + (HZ * AIC7XXX_RESET_DELAY)); + + if ( !(p->features & AHC_TWIN) ) + { + restart_sequencer(p); + } + + return; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_run_waiting_queues + * + * Description: + * Scan the awaiting_scbs queue downloading and starting as many + * scbs as we can. + *-F*************************************************************************/ +static void +aic7xxx_run_waiting_queues(struct aic7xxx_host *p) +{ + struct aic7xxx_scb *scb; + int tindex; + int sent; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags = 0; +#endif + + + if (p->waiting_scbs.head == NULL) + return; + + sent = 0; + + /* + * First handle SCBs that are waiting but have been assigned a slot. + */ + DRIVER_LOCK + while ((scb = scbq_remove_head(&p->waiting_scbs)) != NULL) + { + tindex = TARGET_INDEX(scb->cmd); + if ( !scb->tag_action && (p->tagenable & (1<<tindex)) ) + { +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Reducing Queue depth for untagged command.\n", + p->host_no, CTL_OF_SCB(scb)); +#endif + p->dev_temp_queue_depth[tindex] = 1; + } + if ( (p->dev_active_cmds[tindex] >= + p->dev_temp_queue_depth[tindex]) || + (p->dev_last_reset[tindex] >= (jiffies - (4 * HZ))) ) + { +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Moving SCB to Delayed Queue.\n", + p->host_no, CTL_OF_SCB(scb)); +#endif + scbq_insert_tail(&p->delayed_scbs[tindex], scb); + if ( !(p->dev_timer[tindex].expires) && + !(p->dev_active_cmds[tindex]) ) + { + p->dev_timer[tindex].expires = p->dev_last_reset[tindex] + (4 * HZ); + add_timer(&p->dev_timer[tindex]); + } + } + else + { + scb->flags &= ~SCB_WAITINGQ; +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Sending command %d/0x%x to QINFIFO\n", p->host_no, + CTL_OF_SCB(scb), scb->hscb->tag, scb->flags); +#endif + p->dev_active_cmds[tindex]++; + p->activescbs++; + if ( !(scb->tag_action) ) + { + aic7xxx_busy_target(p, scb); + } + p->qinfifo[p->qinfifonext++] = scb->hscb->tag; + sent++; + } + } + if (sent) + { +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + { + printk(INFO_LEAD "Sending commands to QINFIFO\n", p->host_no, + -1, -1, -1); + if ( (p->isr_count < 16) && (aic7xxx_panic_on_abort) && + (p->flags & AHC_PAGESCBS) ) + aic7xxx_check_scbs(p, "While sending commands to QINFIFO"); + } +#endif + if (p->features & AHC_QUEUE_REGS) + aic_outb(p, p->qinfifonext, HNSCB_QOFF); + else + { + pause_sequencer(p); + aic_outb(p, p->qinfifonext, KERNEL_QINPOS); + unpause_sequencer(p, FALSE); + } + if (p->activescbs > p->max_activescbs) + p->max_activescbs = p->activescbs; + } + DRIVER_UNLOCK +} + +#ifdef CONFIG_PCI + +#define DPE 0x80 +#define SSE 0x40 +#define RMA 0x20 +#define RTA 0x10 +#define STA 0x08 +#define DPR 0x01 + +/*+F************************************************************************* + * Function: + * aic7xxx_pci_intr + * + * Description: + * Check the scsi card for PCI errors and clear the interrupt + * + * NOTE: If you don't have this function and a 2940 card encounters + * a PCI error condition, the machine will end up locked as the + * interrupt handler gets slammed with non-stop PCI error interrupts + *-F*************************************************************************/ +static void +aic7xxx_pci_intr(struct aic7xxx_host *p) +{ + unsigned char status1; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + pci_read_config_byte(p->pdev, PCI_STATUS + 1, &status1); +#else + pcibios_read_config_byte(p->pci_bus, p->pci_device_fn, + PCI_STATUS + 1, &status1); +#endif + + if ( (status1 & DPE) && (aic7xxx_verbose & VERBOSE_MINOR_ERROR) ) + printk(WARN_LEAD "Data Parity Error during PCI address or PCI write" + "phase.\n", p->host_no, -1, -1, -1); + if ( (status1 & SSE) && (aic7xxx_verbose & VERBOSE_MINOR_ERROR) ) + printk(WARN_LEAD "Signal System Error Detected\n", p->host_no, + -1, -1, -1); + if ( (status1 & RMA) && (aic7xxx_verbose & VERBOSE_MINOR_ERROR) ) + printk(WARN_LEAD "Received a PCI Master Abort\n", p->host_no, + -1, -1, -1); + if ( (status1 & RTA) && (aic7xxx_verbose & VERBOSE_MINOR_ERROR) ) + printk(WARN_LEAD "Received a PCI Target Abort\n", p->host_no, + -1, -1, -1); + if ( (status1 & STA) && (aic7xxx_verbose & VERBOSE_MINOR_ERROR) ) + printk(WARN_LEAD "Signaled a PCI Target Abort\n", p->host_no, + -1, -1, -1); + if ( (status1 & DPR) && (aic7xxx_verbose & VERBOSE_MINOR_ERROR) ) + printk(WARN_LEAD "Data Parity Error has been reported via PCI pin " + "PERR#\n", p->host_no, -1, -1, -1); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + pci_write_config_byte(p->pdev, PCI_STATUS + 1, status1); +#else + pcibios_write_config_byte(p->pci_bus, p->pci_device_fn, + PCI_STATUS + 1, status1); +#endif + if (status1 & (DPR|RMA|RTA)) + aic_outb(p, CLRPARERR, CLRINT); + + if ( (aic7xxx_panic_on_abort) && (p->spurious_int > 500) ) + aic7xxx_panic_abort(p, NULL); + +} +#endif /* CONFIG_PCI */ + +/*+F************************************************************************* + * Function: + * aic7xxx_timer + * + * Description: + * Take expired extries off of delayed queues and place on waiting queue + * then run waiting queue to start commands. + ***************************************************************************/ +static void +aic7xxx_timer(struct aic7xxx_host *p) +{ + int i, j; + unsigned long cpu_flags = 0; + struct aic7xxx_scb *scb; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + DRIVER_LOCK +#else + spin_lock_irqsave(&io_request_lock, cpu_flags); +#endif + for(i=0; i<MAX_TARGETS; i++) + { + if ( (p->dev_timer[i].expires) && + (p->dev_timer[i].expires <= jiffies) ) + { + p->dev_timer[i].expires = 0; + if ( (p->dev_timer[i].prev != NULL) || + (p->dev_timer[i].next != NULL) ) + { + del_timer(&p->dev_timer[i]); + } + p->dev_temp_queue_depth[i] = p->dev_max_queue_depth[i]; + j = 0; + while ( ((scb = scbq_remove_head(&p->delayed_scbs[i])) != NULL) && + (j++ < p->scb_data->numscbs) ) + { + scbq_insert_tail(&p->waiting_scbs, scb); + } + if (j == p->scb_data->numscbs) + { + printk(INFO_LEAD "timer: Yikes, loop in delayed_scbs list.\n", + p->host_no, 0, i, -1); + scbq_init(&p->delayed_scbs[i]); + scbq_init(&p->waiting_scbs); + /* + * Well, things are screwed now, wait for a reset to clean the junk + * out. + */ + } + } + } + aic7xxx_run_waiting_queues(p); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + DRIVER_UNLOCK +#else + spin_unlock_irqrestore(&io_request_lock, cpu_flags); +#endif +} + +/*+F************************************************************************* + * Function: + * aic7xxx_construct_sdtr + * + * Description: + * Constucts a synchronous data transfer message in the message + * buffer on the sequencer. + *-F*************************************************************************/ +static void +aic7xxx_construct_sdtr(struct aic7xxx_host *p, unsigned char period, + unsigned char offset) +{ + p->msg_buf[p->msg_index++] = MSG_EXTENDED; + p->msg_buf[p->msg_index++] = MSG_EXT_SDTR_LEN; + p->msg_buf[p->msg_index++] = MSG_EXT_SDTR; + p->msg_buf[p->msg_index++] = period; + p->msg_buf[p->msg_index++] = offset; + p->msg_len += 5; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_construct_wdtr + * + * Description: + * Constucts a wide data transfer message in the message buffer + * on the sequencer. + *-F*************************************************************************/ +static void +aic7xxx_construct_wdtr(struct aic7xxx_host *p, unsigned char bus_width) +{ + p->msg_buf[p->msg_index++] = MSG_EXTENDED; + p->msg_buf[p->msg_index++] = MSG_EXT_WDTR_LEN; + p->msg_buf[p->msg_index++] = MSG_EXT_WDTR; + p->msg_buf[p->msg_index++] = bus_width; + p->msg_len += 4; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_calc_residual + * + * Description: + * Calculate the residual data not yet transferred. + *-F*************************************************************************/ +static void +aic7xxx_calculate_residual (struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + struct aic7xxx_hwscb *hscb; + Scsi_Cmnd *cmd; + int actual, i; + + cmd = scb->cmd; + hscb = scb->hscb; + + /* + * Don't destroy valid residual information with + * residual coming from a check sense operation. + */ + if (((scb->hscb->control & DISCONNECTED) == 0) && + (scb->flags & SCB_SENSE) == 0) + { + /* + * We had an underflow. At this time, there's only + * one other driver that bothers to check for this, + * and cmd->underflow seems to be set rather half- + * heartedly in the higher-level SCSI code. + */ + actual = scb->sg_length; + for (i=1; i < hscb->residual_SG_segment_count; i++) + { + actual -= scb->sg_list[scb->sg_count - i].length; + } + actual -= (hscb->residual_data_count[2] << 16) | + (hscb->residual_data_count[1] << 8) | + hscb->residual_data_count[0]; + + if (actual < cmd->underflow) + { + if (aic7xxx_verbose & VERBOSE_MINOR_ERROR) + printk(INFO_LEAD "Underflow - Wanted %u, %s %u, residual SG " + "count %d.\n", p->host_no, CTL_OF_SCB(scb), cmd->underflow, + (cmd->request.cmd == WRITE) ? "wrote" : "read", actual, + hscb->residual_SG_segment_count); + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + aic7xxx_status(cmd) = hscb->target_status; + } + } + + /* + * Clean out the residual information in the SCB for the + * next consumer. + */ + hscb->residual_data_count[2] = 0; + hscb->residual_data_count[1] = 0; + hscb->residual_data_count[0] = 0; + hscb->residual_SG_segment_count = 0; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_handle_device_reset + * + * Description: + * Interrupt handler for sequencer interrupts (SEQINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_device_reset(struct aic7xxx_host *p, int target, int channel) +{ + unsigned short targ_mask; + unsigned char tindex = target; + + tindex |= ((channel & 0x01) << 3); + + targ_mask = (0x01 << tindex); + /* + * Go back to async/narrow transfers and renegotiate. + */ + p->needsdtr |= (p->needsdtr_copy & targ_mask); + p->needwdtr |= (p->needwdtr_copy & targ_mask); + p->sdtr_pending &= ~targ_mask; + p->wdtr_pending &= ~targ_mask; + aic_outb(p, 0, TARG_SCSIRATE + tindex); + if (p->features & AHC_ULTRA2) + aic_outb(p, 0, TARG_OFFSET + tindex); + aic7xxx_reset_device(p, target, channel, ALL_LUNS, SCB_LIST_NULL); + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Bus Device Reset delivered.\n", p->host_no, channel, + target, -1); + aic7xxx_run_done_queue(p, /*complete*/ FALSE); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_handle_seqint + * + * Description: + * Interrupt handler for sequencer interrupts (SEQINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_seqint(struct aic7xxx_host *p, unsigned char intstat) +{ + struct aic7xxx_scb *scb; + unsigned short target_mask; + unsigned char target, lun, tindex; + unsigned char queue_flag = FALSE; + char channel; + + target = ((aic_inb(p, SAVED_TCL) >> 4) & 0x0f); + if ( (p->chip & AHC_CHIPID_MASK) == AHC_AIC7770 ) + channel = (aic_inb(p, SBLKCTL) & SELBUSB) >> 3; + else + channel = 0; + tindex = target + (channel << 3); + lun = aic_inb(p, SAVED_TCL) & 0x07; + target_mask = (0x01 << tindex); + + /* + * Go ahead and clear the SEQINT now, that avoids any interrupt race + * conditions later on in case we enable some other interrupt. + */ + aic_outb(p, CLRSEQINT, CLRINT); + switch (intstat & SEQINT_MASK) + { + case NO_MATCH: + { + aic_outb(p, aic_inb(p, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP), + SCSISEQ); + printk(WARN_LEAD "No active SCB for reconnecting target - Issuing " + "BUS DEVICE RESET.\n", p->host_no, channel, target, lun); + printk(WARN_LEAD " SAVED_TCL=0x%x, ARG_1=0x%x, SEQADDR=0x%x\n", + p->host_no, channel, target, lun, + aic_inb(p, SAVED_TCL), aic_inb(p, ARG_1), + (aic_inb(p, SEQADDR1) << 8) | aic_inb(p, SEQADDR0)); + } + break; + + case SEND_REJECT: + { + if (aic7xxx_verbose & VERBOSE_MINOR_ERROR) + printk(INFO_LEAD "Rejecting unknown message (0x%x) received from " + "target, SEQ_FLAGS=0x%x\n", p->host_no, channel, target, lun, + aic_inb(p, ACCUM), aic_inb(p, SEQ_FLAGS)); + } + break; + + case NO_IDENT: + { + /* + * The reconnecting target either did not send an identify + * message, or did, but we didn't find an SCB to match and + * before it could respond to our ATN/abort, it hit a dataphase. + * The only safe thing to do is to blow it away with a bus + * reset. + */ + if (aic7xxx_verbose & (VERBOSE_SEQINT | VERBOSE_RESET_MID)) + printk(INFO_LEAD "Target did not send an IDENTIFY message; " + "LASTPHASE 0x%x, SAVED_TCL 0x%x\n", p->host_no, channel, target, + lun, aic_inb(p, LASTPHASE), aic_inb(p, SAVED_TCL)); + + aic7xxx_reset_channel(p, channel, /*initiate reset*/ TRUE); + aic7xxx_run_done_queue(p, FALSE); + + } + break; + + case BAD_PHASE: + if (aic_inb(p, LASTPHASE) == P_BUSFREE) + { + if (aic7xxx_verbose & VERBOSE_SEQINT) + printk(INFO_LEAD "Missed busfree.\n", p->host_no, channel, + target, lun); + restart_sequencer(p); + } + else + { + if (aic7xxx_verbose & VERBOSE_SEQINT) + printk(INFO_LEAD "Unknown scsi bus phase, continuing\n", p->host_no, + channel, target, lun); + } + break; + + case EXTENDED_MSG: + { + p->msg_type = MSG_TYPE_INITIATOR_MSGIN; + p->msg_len = 0; + p->msg_index = 0; + +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Enabling REQINITs for MSG_IN\n", p->host_no, + channel, target, lun); +#endif + + /* + * To actually receive the message, simply turn on + * REQINIT interrupts and let our interrupt handler + * do the rest (REQINIT should already be true). + */ + p->flags |= AHC_HANDLING_REQINITS; + aic_outb(p, aic_inb(p, SIMODE1) | ENREQINIT, SIMODE1); + + /* + * We don't want the sequencer unpaused yet so we return early + */ + return; + } + + case REJECT_MSG: + { + /* + * What we care about here is if we had an outstanding SDTR + * or WDTR message for this target. If we did, this is a + * signal that the target is refusing negotiation. + */ + unsigned char scb_index; + unsigned char last_msg; + + scb_index = aic_inb(p, SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; + last_msg = aic_inb(p, LAST_MSG); + + if ( (last_msg == MSG_IDENTIFYFLAG) && + (scb->tag_action) && + !(scb->flags & SCB_MSGOUT_BITS) ) + { + if ((scb->tag_action == MSG_ORDERED_Q_TAG) && + (p->dev_flags[tindex] & DEVICE_TAGGED_SUCCESS)) + { + /* + * OK...the device seems able to accept tagged commands, but + * not ordered tag commands, only simple tag commands. So, we + * disable ordered tag commands and go on with life just like + * normal. + */ + p->orderedtag &= ~target_mask; + scb->tag_action = MSG_SIMPLE_Q_TAG; + scb->hscb->control &= ~SCB_TAG_TYPE; + scb->hscb->control |= MSG_SIMPLE_Q_TAG; + aic_outb(p, scb->hscb->control, SCB_CONTROL); + /* + * OK..we set the tag type to simple tag command, now we re-assert + * ATNO and hope this will take us into the identify phase again + * so we can resend the tag type and info to the device. + */ + aic_outb(p, MSG_IDENTIFYFLAG, MSG_OUT); + aic_outb(p, aic_inb(p, SCSISIGI) | ATNO, SCSISIGO); + } + else if ( (scb->tag_action == MSG_SIMPLE_Q_TAG) && + !(p->dev_flags[tindex] & DEVICE_TAGGED_SUCCESS) ) + { + unsigned char i, reset = 0; + struct aic7xxx_scb *scbp; + int old_verbose; + /* + * Hmmmm....the device is flaking out on tagged commands. The + * bad thing is that we already have tagged commands enabled in + * the device struct in the mid level code. We also have a queue + * set according to the tagged queue depth. Gonna have to live + * with it by controlling our queue depth internally and making + * sure we don't set the tagged command flag any more. + */ + p->tagenable &= ~target_mask; + p->orderedtag &= ~target_mask; + p->dev_max_queue_depth[tindex] = + p->dev_temp_queue_depth[tindex] = 1; + /* + * We set this command up as a bus device reset. However, we have + * to clear the tag type as it's causing us problems. We shouldnt + * have to worry about any other commands being active, since if + * the device is refusing tagged commands, this should be the + * first tagged command sent to the device, however, we do have + * to worry about any other tagged commands that may already be + * in the qinfifo. The easiest way to do this, is to issue a BDR, + * send all the commands back to the mid level code, then let them + * come back and get rebuilt as untagged commands. + */ + scb->tag_action = 0; + scb->hscb->control &= ~(TAG_ENB | SCB_TAG_TYPE); + aic_outb(p, scb->hscb->control, SCB_CONTROL); + + old_verbose = aic7xxx_verbose; + aic7xxx_verbose &= ~(VERBOSE_RESET|VERBOSE_ABORT); + for (i=0; i!=p->scb_data->numscbs; i++) + { + scbp = p->scb_data->scb_array[i]; + if ((scbp->flags & SCB_ACTIVE) && (scbp != scb)) + { + if (aic7xxx_match_scb(p, scbp, target, channel, lun, i)) + { + aic7xxx_reset_device(p, target, channel, lun, i); + reset++; + } + aic7xxx_run_done_queue(p, FALSE); + } + } + aic7xxx_verbose = old_verbose; + /* + * Wait until after the for loop to set the busy index since + * aic7xxx_reset_device will clear the busy index during its + * operation. + */ + aic7xxx_busy_target(p, scb); + printk(INFO_LEAD "Device is refusing tagged commands, using " + "untagged I/O.\n", p->host_no, channel, target, lun); + aic_outb(p, MSG_IDENTIFYFLAG, MSG_OUT); + aic_outb(p, aic_inb(p, SCSISIGI) | ATNO, SCSISIGO); + } + } + else if (scb->flags & SCB_MSGOUT_WDTR) + { + /* + * note 8bit xfers and clear flag + */ + p->needwdtr &= ~target_mask; + p->needwdtr_copy &= ~target_mask; + p->wdtr_pending &= ~target_mask; + scb->flags &= ~SCB_MSGOUT_BITS; + aic7xxx_set_width(p, target, channel, lun, MSG_EXT_WDTR_BUS_8_BIT, + (AHC_TRANS_ACTIVE|AHC_TRANS_GOAL|AHC_TRANS_CUR)); + aic7xxx_set_syncrate(p, NULL, target, channel, 0, 0, + AHC_TRANS_ACTIVE|AHC_TRANS_CUR|AHC_TRANS_QUITE); + if ( (p->needsdtr_copy & target_mask) && + !(p->sdtr_pending & target_mask) ) + { + p->sdtr_pending |= target_mask; + scb->flags |= SCB_MSGOUT_SDTR; + aic_outb(p, HOST_MSG, MSG_OUT); + aic_outb(p, aic_inb(p, SCSISIGO) | ATNO, SCSISIGO); + } + } + else if (scb->flags & SCB_MSGOUT_SDTR) + { + /* + * note asynch xfers and clear flag + */ + p->needsdtr &= ~target_mask; + p->needsdtr_copy &= ~target_mask; + p->sdtr_pending &= ~target_mask; + scb->flags &= ~SCB_MSGOUT_SDTR; + aic7xxx_set_syncrate(p, NULL, target, channel, 0, 0, + (AHC_TRANS_CUR|AHC_TRANS_ACTIVE|AHC_TRANS_GOAL)); + } + else if (aic7xxx_verbose & VERBOSE_SEQINT) + { + /* + * Otherwise, we ignore it. + */ + printk(INFO_LEAD "Received MESSAGE_REJECT for unknown cause. " + "Ignoring.\n", p->host_no, channel, target, lun); + } + } + break; + + case BAD_STATUS: + { + unsigned char scb_index; + struct aic7xxx_hwscb *hscb; + Scsi_Cmnd *cmd; + + /* The sequencer will notify us when a command has an error that + * would be of interest to the kernel. This allows us to leave + * the sequencer running in the common case of command completes + * without error. The sequencer will have DMA'd the SCB back + * up to us, so we can reference the drivers SCB array. + * + * Set the default return value to 0 indicating not to send + * sense. The sense code will change this if needed and this + * reduces code duplication. + */ + aic_outb(p, 0, RETURN_1); + scb_index = aic_inb(p, SCB_TAG); + if (scb_index > p->scb_data->numscbs) + { + printk(WARN_LEAD "Invalid SCB during SEQINT 0x%02x, SCB_TAG %d.\n", + p->host_no, channel, target, lun, intstat, scb_index); + break; + } + scb = p->scb_data->scb_array[scb_index]; + hscb = scb->hscb; + + if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk(WARN_LEAD "Invalid SCB during SEQINT 0x%x, scb %d, flags 0x%x," + " cmd 0x%lx.\n", p->host_no, channel, target, lun, intstat, + scb_index, scb->flags, (unsigned long) scb->cmd); + } + else + { + cmd = scb->cmd; + hscb->target_status = aic_inb(p, SCB_TARGET_STATUS); + aic7xxx_status(cmd) = hscb->target_status; + + cmd->result = hscb->target_status; + + switch (status_byte(hscb->target_status)) + { + case GOOD: + if (aic7xxx_verbose & VERBOSE_SEQINT) + printk(INFO_LEAD "Interrupted for status of GOOD???\n", + p->host_no, CTL_OF_SCB(scb)); + break; + + case COMMAND_TERMINATED: + case CHECK_CONDITION: + if ( !(scb->flags & SCB_SENSE) ) + { + /* + * XXX - How do we save the residual (if there is one). + */ + if ( hscb->residual_SG_segment_count != 0 ) + aic7xxx_calculate_residual(p, scb); + + /* + * Send a sense command to the requesting target. + * XXX - revisit this and get rid of the memcopys. + */ + memcpy(&scb->sense_cmd[0], &generic_sense[0], + sizeof(generic_sense)); + + scb->sense_cmd[1] = (cmd->lun << 5); + scb->sense_cmd[4] = sizeof(cmd->sense_buffer); + + scb->sg_list[0].address = + cpu_to_le32(VIRT_TO_BUS(&cmd->sense_buffer[0])); + scb->sg_list[0].length = + cpu_to_le32(sizeof(cmd->sense_buffer)); + + /* + * XXX - We should allow disconnection, but can't as it + * might allow overlapped tagged commands. + */ + /* hscb->control &= DISCENB; */ + hscb->control = 0; + hscb->target_status = 0; + hscb->SG_list_pointer = + cpu_to_le32(VIRT_TO_BUS(&scb->sg_list[0])); + hscb->data_pointer = scb->sg_list[0].address; + hscb->data_count = scb->sg_list[0].length; + hscb->SCSI_cmd_pointer = + cpu_to_le32(VIRT_TO_BUS(&scb->sense_cmd[0])); + hscb->SCSI_cmd_length = COMMAND_SIZE(scb->sense_cmd[0]); + hscb->residual_SG_segment_count = 0; + hscb->residual_data_count[0] = 0; + hscb->residual_data_count[1] = 0; + hscb->residual_data_count[2] = 0; + + scb->sg_count = hscb->SG_segment_count = 1; + scb->sg_length = sizeof(cmd->sense_buffer); + scb->tag_action = 0; + /* + * This problem could be caused if the target has lost power + * or found some other way to loose the negotiation settings, + * so if needed, we'll re-negotiate while doing the sense cmd. + * However, if this SCB already was attempting to negotiate, + * then we assume this isn't the problem and skip this part. + */ +#ifdef AIC7XXX_FAKE_NEGOTIATION_CMDS + if ( (scb->cmd->cmnd[0] != TEST_UNIT_READY) && + (p->dev_flags[tindex] & DEVICE_SCANNED) && + !(p->wdtr_pending & target_mask) && + !(p->sdtr_pending & target_mask) ) + { + p->needwdtr |= (p->needwdtr_copy & target_mask); + p->needsdtr |= (p->needsdtr_copy & target_mask); + } + else if ( (scb->cmd == p->dev_wdtr_cmnd[tindex]) || + (scb->cmd == p->dev_sdtr_cmnd[tindex]) ) + { + /* + * This is already a negotiation command, so we must have + * already done either WDTR or SDTR (or maybe both). So + * we simply check sdtr_pending and needsdtr to see if we + * should throw out SDTR on this command. + * + * Note: Don't check the needsdtr_copy here, instead just + * check to see if WDTR wiped out our SDTR and set needsdtr. + * Even if WDTR did wipe out SDTR and set needsdtr, if + * parse_msg() then turned around and started our SDTR + * in back to back fasion, then conclusion of that should + * have negated any needsdtr setting. That's why we only + * check needsdtr and sdtr_pending. + */ + scb->flags &= ~SCB_MSGOUT_BITS; + if ( (scb->cmd == p->dev_wdtr_cmnd[tindex]) && + !(p->sdtr_pending & target_mask) && + (p->needsdtr & target_mask) ) + { + p->sdtr_pending |= target_mask; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_SDTR; + } + + /* + * This is the important part though. We are getting sense + * info back from this device. It's going into a fake + * command. We need to put that into the real command + * instead so that the mid level SCSI code can act upon it. + * So, when we set up these fake commands, the next pointer + * is used to point to the real command. Use that to change + * the address of our sense_buffer[] to the real command. + * However, don't do this if the real command is also a + * TEST_UNIT_READY as it will most likely pull down its own + * SENSE information anyway. + */ + if (cmd->next->cmnd[0] != TEST_UNIT_READY) + { + scb->sg_list[0].address = + cpu_to_le32(VIRT_TO_BUS(&cmd->next->sense_buffer[0])); + hscb->data_pointer = scb->sg_list[0].address; + } + } +#else + if ( (scb->cmd->cmnd[0] != TEST_UNIT_READY) && + !(scb->flags & SCB_MSGOUT_BITS) && + (scb->cmd->lun == 0) && + (p->dev_flags[TARGET_INDEX(scb->cmd)] & DEVICE_SCANNED) ) + { + if ( (p->needwdtr_copy & target_mask) && + !(p->wdtr_pending & target_mask) && + !(p->sdtr_pending & target_mask) ) + { + p->needwdtr |= target_mask; + p->wdtr_pending |= target_mask; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_WDTR; + } + if ( p->needsdtr_copy & target_mask ) + { + p->needsdtr |= target_mask; + if ( !(p->wdtr_pending & target_mask) && + !(p->sdtr_pending & target_mask) ) + { + p->sdtr_pending |= target_mask; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_SDTR; + } + } + } + else + scb->flags &= ~SCB_MSGOUT_BITS; +#endif /* AIC7XXX_FAKE_NEGOTIATION_CMDS */ + scb->flags |= SCB_SENSE; + /* + * Ensure the target is busy since this will be an + * an untagged request. + */ +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + { + if (scb->flags & SCB_MSGOUT_BITS) + printk(INFO_LEAD "Requesting SENSE with %s\n", p->host_no, + CTL_OF_SCB(scb), (scb->flags & SCB_MSGOUT_SDTR) ? + "SDTR" : "WDTR"); + else + printk(INFO_LEAD "Requesting SENSE, no MSG\n", p->host_no, + CTL_OF_SCB(scb)); + } +#endif + aic7xxx_busy_target(p, scb); + aic_outb(p, SEND_SENSE, RETURN_1); + aic7xxx_error(cmd) = DID_OK; + break; + } /* first time sense, no errors */ + aic7xxx_error(cmd) = DID_OK; + scb->flags &= ~SCB_SENSE; + break; + + case QUEUE_FULL: + queue_flag = TRUE; /* Mark that this is a QUEUE_FULL and */ + case BUSY: /* drop through to here */ + { + struct aic7xxx_scb *next_scbp, *prev_scbp; + unsigned char active_hscb, next_hscb, prev_hscb, scb_index; + /* + * We have to look three places for queued commands: + * 1: QINFIFO + * 2: p->waiting_scbs queue + * 3: WAITING_SCBS list on card (for commands that are started + * but haven't yet made it to the device) + */ + aic7xxx_search_qinfifo(p, target, channel, lun, + SCB_LIST_NULL, 0, TRUE, + &p->delayed_scbs[tindex]); + next_scbp = p->waiting_scbs.head; + while ( next_scbp != NULL ) + { + prev_scbp = next_scbp; + next_scbp = next_scbp->q_next; + if ( aic7xxx_match_scb(p, prev_scbp, target, channel, lun, + SCB_LIST_NULL) ) + { + scbq_remove(&p->waiting_scbs, prev_scbp); + scbq_insert_tail(&p->delayed_scbs[tindex], + prev_scbp); + } + } + next_scbp = NULL; + active_hscb = aic_inb(p, SCBPTR); + prev_hscb = next_hscb = scb_index = SCB_LIST_NULL; + next_hscb = aic_inb(p, WAITING_SCBH); + while (next_hscb != SCB_LIST_NULL) + { + aic_outb(p, next_hscb, SCBPTR); + scb_index = aic_inb(p, SCB_TAG); + if (scb_index < p->scb_data->numscbs) + { + next_scbp = p->scb_data->scb_array[scb_index]; + if (aic7xxx_match_scb(p, next_scbp, target, channel, lun, + SCB_LIST_NULL) ) + { + if (next_scbp->flags & SCB_WAITINGQ) + { + p->dev_active_cmds[tindex]++; + p->activescbs--; + scbq_remove(&p->delayed_scbs[tindex], next_scbp); + scbq_remove(&p->waiting_scbs, next_scbp); + } + scbq_insert_head(&p->delayed_scbs[tindex], + next_scbp); + next_scbp->flags |= SCB_WAITINGQ; + p->dev_active_cmds[tindex]--; + p->activescbs--; + next_hscb = aic_inb(p, SCB_NEXT); + aic_outb(p, 0, SCB_CONTROL); + aic_outb(p, SCB_LIST_NULL, SCB_TAG); + aic7xxx_add_curscb_to_free_list(p); + if (prev_hscb == SCB_LIST_NULL) + { + /* We were first on the list, + * so we kill the selection + * hardware. Let the sequencer + * re-init the hardware itself + */ + aic_outb(p, aic_inb(p, SCSISEQ) & ~ENSELO, SCSISEQ); + aic_outb(p, CLRSELTIMEO, CLRSINT1); + aic_outb(p, next_hscb, WAITING_SCBH); + } + else + { + aic_outb(p, prev_hscb, SCBPTR); + aic_outb(p, next_hscb, SCB_NEXT); + } + } + else + { + prev_hscb = next_hscb; + next_hscb = aic_inb(p, SCB_NEXT); + } + } /* scb_index >= p->scb_data->numscbs */ + } + aic_outb(p, active_hscb, SCBPTR); + if (scb->flags & SCB_WAITINGQ) + { + scbq_remove(&p->delayed_scbs[tindex], scb); + scbq_remove(&p->waiting_scbs, scb); + p->dev_active_cmds[tindex]++; + p->activescbs++; + } + scbq_insert_head(&p->delayed_scbs[tindex], scb); + p->dev_active_cmds[tindex]--; + p->activescbs--; + scb->flags |= SCB_WAITINGQ | SCB_WAS_BUSY; + + if (p->dev_timer[tindex].expires == 0) + { + if ( p->dev_active_cmds[tindex] ) + { + p->dev_timer[tindex].expires = jiffies + (HZ * 2); + add_timer(&p->dev_timer[tindex]); + } + else + { + p->dev_timer[tindex].expires = jiffies + (HZ / 2); + add_timer(&p->dev_timer[tindex]); + } + } +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose & VERBOSE_MINOR_ERROR) + { + if (queue_flag) + printk(INFO_LEAD "Queue full received; queue depth %d, " + "active %d\n", p->host_no, CTL_OF_SCB(scb), + p->dev_max_queue_depth[tindex], + p->dev_active_cmds[tindex]); + else + printk(INFO_LEAD "Target busy\n", p->host_no, CTL_OF_SCB(scb)); + + } +#endif + if (queue_flag) + { + p->dev_temp_queue_depth[tindex] = + p->dev_active_cmds[tindex]; + if ( p->dev_last_queue_full[tindex] != + p->dev_active_cmds[tindex] ) + { + p->dev_last_queue_full[tindex] = + p->dev_active_cmds[tindex]; + p->dev_last_queue_full_count[tindex] = 0; + } + else + { + p->dev_last_queue_full_count[tindex]++; + } + if ( (p->dev_last_queue_full_count[tindex] > 14) && + (p->dev_active_cmds[tindex] > 4) ) + { + if (aic7xxx_verbose & VERBOSE_NEGOTIATION2) + printk(INFO_LEAD "Queue depth reduced to %d\n", p->host_no, + CTL_OF_SCB(scb), p->dev_active_cmds[tindex]); + p->dev_max_queue_depth[tindex] = + p->dev_active_cmds[tindex]; + p->dev_last_queue_full[tindex] = 0; + p->dev_last_queue_full_count[tindex] = 0; + } + } + break; + } + + default: + if (aic7xxx_verbose & VERBOSE_SEQINT) + printk(INFO_LEAD "Unexpected target status 0x%x.\n", p->host_no, + CTL_OF_SCB(scb), scb->hscb->target_status); + if (!aic7xxx_error(cmd)) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + break; + } /* end switch */ + } /* end else of */ + } + break; + + case AWAITING_MSG: + { + unsigned char scb_index, msg_out; + + scb_index = aic_inb(p, SCB_TAG); + msg_out = aic_inb(p, MSG_OUT); + scb = p->scb_data->scb_array[scb_index]; + p->msg_index = p->msg_len = 0; + /* + * This SCB had a MK_MESSAGE set in its control byte informing + * the sequencer that we wanted to send a special message to + * this target. + */ + + if ( !(scb->flags & SCB_DEVICE_RESET) && + (aic_inb(p, MSG_OUT) == MSG_IDENTIFYFLAG) && + (scb->hscb->control & TAG_ENB) ) + { + p->msg_buf[p->msg_index++] = scb->tag_action; + p->msg_buf[p->msg_index++] = scb->hscb->tag; + p->msg_len += 2; + } + + if (scb->flags & SCB_DEVICE_RESET) + { + p->msg_buf[p->msg_index++] = MSG_BUS_DEV_RESET; + p->msg_len++; + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Bus device reset mailed.\n", + p->host_no, CTL_OF_SCB(scb)); + } + else if (scb->flags & SCB_ABORT) + { + if (scb->tag_action) + { + p->msg_buf[p->msg_index++] = MSG_ABORT_TAG; + } + else + { + p->msg_buf[p->msg_index++] = MSG_ABORT; + } + p->msg_len++; + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "Abort message mailed.\n", p->host_no, + CTL_OF_SCB(scb)); + } + else if (scb->flags & SCB_MSGOUT_WDTR) + { +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Sending WDTR message.\n", p->host_no, + CTL_OF_SCB(scb)); +#endif + aic7xxx_construct_wdtr(p, + p->transinfo[TARGET_INDEX(scb->cmd)].goal_width); + } + else if (scb->flags & SCB_MSGOUT_SDTR) + { + unsigned int max_sync, period; + /* + * We need to set an accurate goal_offset instead of + * the ridiculously high one we default to. We should + * now know if we are wide. Plus, the WDTR code will + * set our goal_offset for us as well. + */ + if (p->transinfo[tindex].goal_offset) + { + if (p->features & AHC_ULTRA2) + p->transinfo[tindex].goal_offset = MAX_OFFSET_ULTRA2; + else if (p->transinfo[tindex].cur_width == MSG_EXT_WDTR_BUS_16_BIT) + p->transinfo[tindex].goal_offset = MAX_OFFSET_16BIT; + else + p->transinfo[tindex].goal_offset = MAX_OFFSET_8BIT; + } + /* + * Now that the device is selected, use the bits in SBLKCTL and + * SSTAT2 to determine the max sync rate for this device. + */ + if (p->features & AHC_ULTRA2) + { + if ( (aic_inb(p, SBLKCTL) & ENAB40) && + !(aic_inb(p, SSTAT2) & EXP_ACTIVE) ) + { + max_sync = AHC_SYNCRATE_ULTRA2; + } + else + { + max_sync = AHC_SYNCRATE_ULTRA; + } + } + else if (p->features & AHC_ULTRA) + { + max_sync = AHC_SYNCRATE_ULTRA; + } + else + { + max_sync = AHC_SYNCRATE_FAST; + } + period = p->transinfo[tindex].goal_period; + aic7xxx_find_syncrate(p, &period, max_sync); +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Sending SDTR %d/%d message.\n", p->host_no, + CTL_OF_SCB(scb), + p->transinfo[tindex].goal_period, + p->transinfo[tindex].goal_offset); +#endif + aic7xxx_construct_sdtr(p, period, + p->transinfo[tindex].goal_offset); + } + else + { + sti(); + panic("aic7xxx: AWAITING_MSG for an SCB that does " + "not have a waiting message.\n"); + } + /* + * We've set everything up to send our message, now to actually do + * so we need to enable reqinit interrupts and let the interrupt + * handler do the rest. We don't want to unpause the sequencer yet + * though so we'll return early. We also have to make sure that + * we clear the SEQINT *BEFORE* we set the REQINIT handler active + * or else it's possible on VLB cards to loose the first REQINIT + * interrupt. Edge triggered EISA cards could also loose this + * interrupt, although PCI and level triggered cards should not + * have this problem since they continually interrupt the kernel + * until we take care of the situation. + */ + scb->flags |= SCB_MSGOUT_SENT; + p->msg_index = 0; + p->msg_type = MSG_TYPE_INITIATOR_MSGOUT; + p->flags |= AHC_HANDLING_REQINITS; + aic_outb(p, aic_inb(p, SIMODE1) | ENREQINIT, SIMODE1); + return; + } + break; + + case DATA_OVERRUN: + { + unsigned char scb_index = aic_inb(p, SCB_TAG); + unsigned char lastphase = aic_inb(p, LASTPHASE); + unsigned int i; + + scb = (p->scb_data->scb_array[scb_index]); + /* + * XXX - What do we really want to do on an overrun? The + * mid-level SCSI code should handle this, but for now, + * we'll just indicate that the command should retried. + * If we retrieved sense info on this target, then the + * base SENSE info should have been saved prior to the + * overrun error. In that case, we return DID_OK and let + * the mid level code pick up on the sense info. Otherwise + * we return DID_ERROR so the command will get retried. + */ + if ( !(scb->flags & SCB_SENSE) ) + { + printk(WARN_LEAD "Data overrun detected in %s phase, tag %d;\n", + p->host_no, CTL_OF_SCB(scb), + (lastphase == P_DATAIN) ? "Data-In" : "Data-Out", scb->hscb->tag); + printk(KERN_WARNING " %s seen Data Phase. Length=%d, NumSGs=%d.\n", + (aic_inb(p, SEQ_FLAGS) & DPHASE) ? "Have" : "Haven't", + scb->sg_length, scb->sg_count); + for (i = 0; i < scb->sg_count; i++) + { + printk(KERN_WARNING " sg[%d] - Addr 0x%x : Length %d\n", + i, + le32_to_cpu(scb->sg_list[i].address), + le32_to_cpu(scb->sg_list[i].length) ); + } + aic7xxx_error(scb->cmd) = DID_ERROR; + } + else + printk(INFO_LEAD "Data Overrun during SEND_SENSE operation.\n", + p->host_no, CTL_OF_SCB(scb)); + } + break; + +#if AIC7XXX_NOT_YET + case TRACEPOINT: + { + printk(INFO_LEAD "Tracepoint #1 reached.\n", p->host_no, channel, + target, lun); + } + break; + + case TRACEPOINT2: + { + printk(INFO_LEAD "Tracepoint #2 reached.\n", p->host_no, channel, + target, lun); + } + break; + + /* XXX Fill these in later */ + case MSG_BUFFER_BUSY: + printk("aic7xxx: Message buffer busy.\n"); + break; + case MSGIN_PHASEMIS: + printk("aic7xxx: Message-in phasemis.\n"); + break; +#endif + + default: /* unknown */ + printk(WARN_LEAD "Unknown SEQINT, INTSTAT 0x%x, SCSISIGI 0x%x.\n", + p->host_no, channel, target, lun, intstat, + aic_inb(p, SCSISIGI)); + break; + } + + /* + * Clear the sequencer interrupt and unpause the sequencer. + */ + unpause_sequencer(p, /* unpause always */ TRUE); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_parse_msg + * + * Description: + * Parses incoming messages into actions on behalf of + * aic7xxx_handle_reqinit + *_F*************************************************************************/ +static int +aic7xxx_parse_msg(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + int reject, reply, done; + unsigned char target_scsirate, tindex; + unsigned short target_mask; + unsigned char target, channel, lun; + + target = scb->cmd->target; + channel = scb->cmd->channel; + lun = scb->cmd->lun; + reply = reject = done = FALSE; + tindex = TARGET_INDEX(scb->cmd); + target_scsirate = aic_inb(p, TARG_SCSIRATE + tindex); + target_mask = (0x01 << tindex); + + /* + * Parse as much of the message as is availible, + * rejecting it if we don't support it. When + * the entire message is availible and has been + * handled, return TRUE indicating that we have + * parsed an entire message. + */ + + if (p->msg_buf[0] != MSG_EXTENDED) + { + reject = TRUE; + } + + /* + * Just accept the length byte outright and perform + * more checking once we know the message type. + */ + + if ( !reject && (p->msg_len > 2) ) + { + switch(p->msg_buf[2]) + { + case MSG_EXT_SDTR: + { + unsigned int period, offset; + unsigned char maxsync, saved_offset; + struct aic7xxx_syncrate *syncrate; + + if (p->msg_buf[1] != MSG_EXT_SDTR_LEN) + { + reject = TRUE; + break; + } + + if (p->msg_len < (MSG_EXT_SDTR_LEN + 2)) + { + break; + } + + period = p->msg_buf[3]; + saved_offset = offset = p->msg_buf[4]; + + if (p->features & AHC_ULTRA2) + { + if ( (aic_inb(p, SBLKCTL) & ENAB40) && + !(aic_inb(p, SSTAT2) & EXP_ACTIVE) ) + { + maxsync = AHC_SYNCRATE_ULTRA2; + } + else + { + maxsync = AHC_SYNCRATE_ULTRA; + } + } + else if (p->features & AHC_ULTRA) + { + maxsync = AHC_SYNCRATE_ULTRA; + } + else + { + maxsync = AHC_SYNCRATE_FAST; + } + +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + { + printk(INFO_LEAD "Finished receipt of SDTR, parsing %d/%d\n", + p->host_no, CTL_OF_SCB(scb), period, offset); + syncrate = aic7xxx_find_syncrate(p, &period, maxsync); + printk(INFO_LEAD "After find_syncrate() %d/%d\n", + p->host_no, CTL_OF_SCB(scb), period, offset); + aic7xxx_validate_offset(p, syncrate, &offset, + target_scsirate & WIDEXFER); + printk(INFO_LEAD "After validate_offset() %d/%d\n", + p->host_no, CTL_OF_SCB(scb), period, offset); + aic7xxx_set_syncrate(p, syncrate, target, channel, period, + offset, AHC_TRANS_ACTIVE|AHC_TRANS_CUR); + printk(INFO_LEAD "Final values of Period/Offset as set: %d/%d\n", + p->host_no, CTL_OF_SCB(scb), period, offset); + } + else + { + syncrate = aic7xxx_find_syncrate(p, &period, maxsync); + aic7xxx_validate_offset(p, syncrate, &offset, + target_scsirate & WIDEXFER); + aic7xxx_set_syncrate(p, syncrate, target, channel, period, + offset, AHC_TRANS_ACTIVE|AHC_TRANS_CUR); + } +#else + syncrate = aic7xxx_find_syncrate(p, &period, maxsync); + aic7xxx_validate_offset(p, syncrate, &offset, + target_scsirate & WIDEXFER); + aic7xxx_set_syncrate(p, syncrate, target, channel, period, + offset, AHC_TRANS_ACTIVE|AHC_TRANS_CUR); +#endif + + if (offset == 0) + { + /* + * Uhh ohh, things fell through to async....update the goal + * items and the needsdtr_copy to reflect this... + */ + aic7xxx_set_syncrate(p, syncrate, target, channel, period, + offset, AHC_TRANS_GOAL|AHC_TRANS_QUITE); + p->needsdtr_copy &= ~target_mask; + } + /* + * Did we start this, if not, or if we went to low and had to + * go async, then send an SDTR back to the target + */ + p->needsdtr &= ~target_mask; + p->sdtr_pending &= ~target_mask; + if ( ((scb->flags & (SCB_MSGOUT_SENT|SCB_MSGOUT_SDTR)) == + (SCB_MSGOUT_SENT|SCB_MSGOUT_SDTR)) && + (offset == saved_offset) ) + { + scb->flags &= ~SCB_MSGOUT_BITS; + } + else + { + /* + * Send a reply SDTR back. Even if we sent the first one, it + * is valid to send another one out immediately to re-negotiate + * things, and a few devices don't like getting rejects after + * we already sent them one SDTR. Just send an SDTR for async + * this time if need be (or for the correct params if we didn't + * start all of this). If this is a Reject Reply type message, + * then we've put the async settings into the goal area for + * future reference (when we get the AWAITING_MSG interrupt). + * If this is a case where we are responding to the target's + * initiated SDTR, then leave our own goal and user values in + * place (unless the device hasn't been scanned yet, in which + * case, put the user values into the goal values so we don't + * send out an Async message). + */ + if ( !(p->dev_flags[tindex] & DEVICE_SCANNED) ) + { + p->transinfo[tindex].goal_width = + p->transinfo[tindex].user_width; + p->transinfo[tindex].goal_period = + p->transinfo[tindex].user_period; + p->transinfo[tindex].goal_offset = + p->transinfo[tindex].user_offset; + p->needwdtr_copy |= target_mask; + p->needsdtr_copy |= target_mask; + } + scb->flags &= ~SCB_MSGOUT_BITS; + scb->flags |= SCB_MSGOUT_SDTR; + aic_outb(p, HOST_MSG, MSG_OUT); + aic_outb(p, aic_inb(p, SCSISIGO) | ATNO, SCSISIGO); + } + done = TRUE; + break; + } + case MSG_EXT_WDTR: + { + unsigned char bus_width; + + if (p->msg_buf[1] != MSG_EXT_WDTR_LEN) + { + reject = TRUE; + break; + } + + if (p->msg_len < (MSG_EXT_WDTR_LEN + 2)) + { + break; + } + + bus_width = p->msg_buf[3]; + if ( (scb->flags & (SCB_MSGOUT_SENT|SCB_MSGOUT_WDTR)) == + (SCB_MSGOUT_SENT|SCB_MSGOUT_WDTR) ) + { + switch(bus_width) + { + default: + { + reject = TRUE; + if ( (aic7xxx_verbose & VERBOSE_NEGOTIATION2) && + ((p->dev_flags[tindex] & DEVICE_PRINT_WDTR) || + (aic7xxx_verbose > 0xffff)) ) + { + printk(INFO_LEAD "Requesting %d bit transfers, rejecting.\n", + p->host_no, CTL_OF_SCB(scb), 8 * (0x01 << bus_width)); + p->dev_flags[tindex] &= ~DEVICE_PRINT_WDTR; + } + } /* We fall through on purpose */ + case MSG_EXT_WDTR_BUS_8_BIT: + { + bus_width = MSG_EXT_WDTR_BUS_8_BIT; + p->needwdtr_copy &= ~target_mask; + break; + } + case MSG_EXT_WDTR_BUS_16_BIT: + { + break; + } + } + scb->flags &= ~SCB_MSGOUT_BITS; + p->wdtr_pending &= ~target_mask; + p->needwdtr &= ~target_mask; + } + else + { + scb->flags &= ~SCB_MSGOUT_BITS; + scb->flags |= SCB_MSGOUT_WDTR; + reply = TRUE; + if ( !(p->dev_flags[tindex] & DEVICE_SCANNED) ) + { + /* + * Well, we now know the WDTR and SYNC caps of this device since + * it contacted us first, mark it as such and copy the user stuff + * over to the goal stuff. + */ + p->transinfo[tindex].goal_width = + p->transinfo[tindex].user_width; + p->transinfo[tindex].goal_period = + p->transinfo[tindex].user_period; + p->transinfo[tindex].goal_offset = + p->transinfo[tindex].user_offset; + p->needwdtr_copy |= target_mask; + p->needsdtr_copy |= target_mask; + } + switch(bus_width) + { + default: + { + if ( (p->features & AHC_WIDE) && + (p->transinfo[tindex].goal_width == + MSG_EXT_WDTR_BUS_16_BIT) ) + { + bus_width = MSG_EXT_WDTR_BUS_16_BIT; + break; + } + } /* Fall through if we aren't a wide card */ + case MSG_EXT_WDTR_BUS_8_BIT: + { + p->needwdtr_copy &= ~target_mask; + bus_width = MSG_EXT_WDTR_BUS_8_BIT; + break; + } + } + p->needwdtr &= ~target_mask; + p->wdtr_pending &= ~target_mask; + aic_outb(p, HOST_MSG, MSG_OUT); + aic_outb(p, aic_inb(p, SCSISIGO) | ATNO, SCSISIGO); + } + aic7xxx_set_width(p, target, channel, lun, bus_width, + AHC_TRANS_ACTIVE|AHC_TRANS_CUR); + + /* + * By virtue of the SCSI spec, a WDTR message negates any existing + * SDTR negotiations. So, even if needsdtr isn't marked for this + * device, we still have to do a new SDTR message if the device + * supports SDTR at all. Therefore, we check needsdtr_copy instead + * of needstr. + */ + aic7xxx_set_syncrate(p, NULL, target, channel, 0, 0, + AHC_TRANS_ACTIVE|AHC_TRANS_CUR|AHC_TRANS_QUITE); + if ( (p->needsdtr_copy & target_mask) && + !(p->sdtr_pending & target_mask)) + { + p->needsdtr |= target_mask; + if ( !reject && !reply ) + { + scb->flags &= ~SCB_MSGOUT_WDTR; + if (p->transinfo[tindex].goal_period) + { + p->sdtr_pending |= target_mask; + scb->flags |= SCB_MSGOUT_SDTR; + aic_outb(p, HOST_MSG, MSG_OUT); + aic_outb(p, aic_inb(p, SCSISIGO) | ATNO, SCSISIGO); + } + } + } + done = TRUE; + break; + } + default: + { + reject = TRUE; + break; + } + } /* end of switch(p->msg_type) */ + } /* end of if (!reject && (p->msg_len > 2)) */ + + if (reject) + { + aic_outb(p, MSG_MESSAGE_REJECT, MSG_OUT); + aic_outb(p, aic_inb(p, SCSISIGO) | ATNO, SCSISIGO); + done = TRUE; + } + return(done); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_handle_reqinit + * + * Description: + * Interrupt handler for REQINIT interrupts (used to transfer messages to + * and from devices). + *_F*************************************************************************/ +static void +aic7xxx_handle_reqinit(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + unsigned char lastbyte; + unsigned char phasemis; + int done = FALSE; + + switch(p->msg_type) + { + case MSG_TYPE_INITIATOR_MSGOUT: + { + if (p->msg_len == 0) + panic("aic7xxx: REQINIT with no active message!\n"); + + lastbyte = (p->msg_index == (p->msg_len - 1)); + phasemis = ( aic_inb(p, SCSISIGI) & PHASE_MASK) != P_MESGOUT; + + if (lastbyte || phasemis) + { + /* Time to end the message */ + p->msg_len = 0; + p->msg_type = MSG_TYPE_NONE; + /* + * NOTE-TO-MYSELF: If you clear the REQINIT after you + * disable REQINITs, then cases of REJECT_MSG stop working + * and hang the bus + */ + aic_outb(p, aic_inb(p, SIMODE1) & ~ENREQINIT, SIMODE1); + aic_outb(p, CLRSCSIINT, CLRINT); + p->flags &= ~AHC_HANDLING_REQINITS; + + if (phasemis == 0) + { + aic_outb(p, p->msg_buf[p->msg_index], SINDEX); + aic_outb(p, 0, RETURN_1); +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Completed sending of REQINIT message.\n", + p->host_no, CTL_OF_SCB(scb)); +#endif + } + else + { + aic_outb(p, MSGOUT_PHASEMIS, RETURN_1); +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "PHASEMIS while sending REQINIT message.\n", + p->host_no, CTL_OF_SCB(scb)); +#endif + } + unpause_sequencer(p, TRUE); + } + else + { + /* + * Present the byte on the bus (clearing REQINIT) but don't + * unpause the sequencer. + */ + aic_outb(p, CLRREQINIT, CLRSINT1); + aic_outb(p, CLRSCSIINT, CLRINT); + aic_outb(p, p->msg_buf[p->msg_index++], SCSIDATL); + } + break; + } + case MSG_TYPE_INITIATOR_MSGIN: + { + phasemis = ( aic_inb(p, SCSISIGI) & PHASE_MASK ) != P_MESGIN; + + if (phasemis == 0) + { + p->msg_len++; + /* Pull the byte in without acking it */ + p->msg_buf[p->msg_index] = aic_inb(p, SCSIBUSL); + done = aic7xxx_parse_msg(p, scb); + /* Ack the byte */ + aic_outb(p, CLRREQINIT, CLRSINT1); + aic_outb(p, CLRSCSIINT, CLRINT); + aic_inb(p, SCSIDATL); + p->msg_index++; + } + if (phasemis || done) + { +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + { + if (phasemis) + printk(INFO_LEAD "PHASEMIS while receiving REQINIT message.\n", + p->host_no, CTL_OF_SCB(scb)); + else + printk(INFO_LEAD "Completed receipt of REQINIT message.\n", + p->host_no, CTL_OF_SCB(scb)); + } +#endif + /* Time to end our message session */ + p->msg_len = 0; + p->msg_type = MSG_TYPE_NONE; + aic_outb(p, aic_inb(p, SIMODE1) & ~ENREQINIT, SIMODE1); + aic_outb(p, CLRSCSIINT, CLRINT); + p->flags &= ~AHC_HANDLING_REQINITS; + unpause_sequencer(p, TRUE); + } + break; + } + default: + { + panic("aic7xxx: Unknown REQINIT message type.\n"); + break; + } + } /* End of switch(p->msg_type) */ +} + +/*+F************************************************************************* + * Function: + * aic7xxx_handle_scsiint + * + * Description: + * Interrupt handler for SCSI interrupts (SCSIINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_scsiint(struct aic7xxx_host *p, unsigned char intstat) +{ + unsigned char scb_index; + unsigned char status; + struct aic7xxx_scb *scb; + + scb_index = aic_inb(p, SCB_TAG); + status = aic_inb(p, SSTAT1); + + if (scb_index < p->scb_data->numscbs) + { + scb = p->scb_data->scb_array[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + { + scb = NULL; + } + } + else + { + scb = NULL; + } + + + if ((status & SCSIRSTI) != 0) + { + int channel; + + if ( (p->chip & AHC_CHIPID_MASK) == AHC_AIC7770 ) + channel = (aic_inb(p, SBLKCTL) & SELBUSB) >> 3; + else + channel = 0; + + if (aic7xxx_verbose & VERBOSE_RESET) + printk(WARN_LEAD "Someone else reset the channel!!\n", + p->host_no, channel, -1, -1); + /* + * Go through and abort all commands for the channel, but do not + * reset the channel again. + */ + aic7xxx_reset_channel(p, channel, /* Initiate Reset */ FALSE); + aic7xxx_run_done_queue(p, FALSE); + scb = NULL; + } + else if ( ((status & BUSFREE) != 0) && ((status & SELTO) == 0) ) + { + /* + * First look at what phase we were last in. If it's message-out, + * chances are pretty good that the bus free was in response to + * one of our abort requests. + */ + unsigned char lastphase = aic_inb(p, LASTPHASE); + unsigned char saved_tcl = aic_inb(p, SAVED_TCL); + unsigned char target = (saved_tcl >> 4) & 0x0F; + int channel; + int printerror = TRUE; + + if ( (p->chip & AHC_CHIPID_MASK) == AHC_AIC7770 ) + channel = (aic_inb(p, SBLKCTL) & SELBUSB) >> 3; + else + channel = 0; + + aic_outb(p, aic_inb(p, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP), + SCSISEQ); + if (lastphase == P_MESGOUT) + { + unsigned char message; + + message = aic_inb(p, SINDEX); + + if ((message == MSG_ABORT) || (message == MSG_ABORT_TAG)) + { + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "SCB %d abort delivered.\n", p->host_no, + CTL_OF_SCB(scb), scb->hscb->tag); + aic7xxx_reset_device(p, target, channel, ALL_LUNS, + (message == MSG_ABORT) ? SCB_LIST_NULL : scb->hscb->tag ); + aic7xxx_run_done_queue(p, FALSE); + scb = NULL; + printerror = 0; + } + else if (message == MSG_BUS_DEV_RESET) + { + aic7xxx_handle_device_reset(p, target, channel); + scb = NULL; + printerror = 0; + } + } + if (printerror != 0) + { + if (scb != NULL) + { + unsigned char tag; + + if ((scb->hscb->control & TAG_ENB) != 0) + { + tag = scb->hscb->tag; + } + else + { + tag = SCB_LIST_NULL; + } + aic7xxx_reset_device(p, target, channel, ALL_LUNS, tag); + aic7xxx_run_done_queue(p, FALSE); + } + printk(INFO_LEAD "Unexpected busfree, LASTPHASE = 0x%x, " + "SEQADDR = 0x%x\n", p->host_no, channel, target, -1, lastphase, + (aic_inb(p, SEQADDR1) << 8) | aic_inb(p, SEQADDR0)); + scb = NULL; + } + aic_outb(p, MSG_NOOP, MSG_OUT); + aic_outb(p, aic_inb(p, SIMODE1) & ~(ENBUSFREE|ENREQINIT), + SIMODE1); + p->flags &= ~AHC_HANDLING_REQINITS; + aic_outb(p, CLRBUSFREE, CLRSINT1); + aic_outb(p, CLRSCSIINT, CLRINT); + restart_sequencer(p); + unpause_sequencer(p, TRUE); + } + else if ((status & SELTO) != 0) + { + unsigned char scbptr; + unsigned char nextscb; + Scsi_Cmnd *cmd; + + scbptr = aic_inb(p, WAITING_SCBH); + if (scbptr > p->scb_data->maxhscbs) + { + /* + * I'm still trying to track down exactly how this happens, but until + * I find it, this code will make sure we aren't passing bogus values + * into the SCBPTR register, even if that register will just wrap + * things around, we still don't like having out of range variables. + * + * NOTE: Don't check the aic7xxx_verbose variable, I want this message + * to always be displayed. + */ + printk(INFO_LEAD "Invalid WAITING_SCBH value %d, improvising.\n", + p->host_no, -1, -1, -1, scbptr); + if (p->scb_data->maxhscbs > 4) + scbptr &= (p->scb_data->maxhscbs - 1); + else + scbptr &= 0x03; + } + aic_outb(p, scbptr, SCBPTR); + scb_index = aic_inb(p, SCB_TAG); + + scb = NULL; + if (scb_index < p->scb_data->numscbs) + { + scb = p->scb_data->scb_array[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + { + scb = NULL; + } + } + if (scb == NULL) + { + printk(WARN_LEAD "Referenced SCB %d not valid during SELTO.\n", + p->host_no, -1, -1, -1, scb_index); + printk(KERN_WARNING " SCSISEQ = 0x%x SEQADDR = 0x%x SSTAT0 = 0x%x " + "SSTAT1 = 0x%x\n", aic_inb(p, SCSISEQ), + aic_inb(p, SEQADDR0) | (aic_inb(p, SEQADDR1) << 8), + aic_inb(p, SSTAT0), aic_inb(p, SSTAT1)); + if (aic7xxx_panic_on_abort) + aic7xxx_panic_abort(p, NULL); + } + else + { + cmd = scb->cmd; + cmd->result = (DID_TIME_OUT << 16); + + /* + * Clear out this hardware SCB + */ + aic_outb(p, 0, SCB_CONTROL); + + /* + * Clear out a few values in the card that are in an undetermined + * state. + */ + aic_outb(p, MSG_NOOP, MSG_OUT); + + /* + * Shift the waiting for selection queue forward + */ + nextscb = aic_inb(p, SCB_NEXT); + aic_outb(p, nextscb, WAITING_SCBH); + + /* + * Put this SCB back on the free list. + */ + aic7xxx_add_curscb_to_free_list(p); + /* + * XXX - If we queued an abort tag, go clean up the disconnected list. + * We know that this particular SCB had to be the queued abort since + * the disconnected SCB would have gotten a reconnect instead. + * However, if this is an abort command, then DID_TIMEOUT isn't + * appropriate, neither is returning the command for that matter. + * What we need to do then is to let the command timeout again so + * we get a reset since this abort just failed. + */ +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Selection Timeout.\n", p->host_no, CTL_OF_SCB(scb)); +#endif + if (p->flags & SCB_QUEUED_ABORT) + { + cmd->result = 0; + scb->flags &= ~SCB_QUEUED_ABORT; + scb = NULL; + } + } + /* + * Restarting the sequencer will stop the selection and make sure devices + * are allowed to reselect in. + */ + aic_outb(p, 0, SCSISEQ); + aic_outb(p, aic_inb(p, SIMODE1) & ~(ENREQINIT|ENBUSFREE), SIMODE1); + p->flags &= ~AHC_HANDLING_REQINITS; + aic_outb(p, CLRSELTIMEO | CLRBUSFREE, CLRSINT1); + aic_outb(p, CLRSCSIINT, CLRINT); + restart_sequencer(p); + unpause_sequencer(p, TRUE); + } + else if (scb == NULL) + { + printk(WARN_LEAD "aic7xxx_isr - referenced scb not valid " + "during scsiint 0x%x scb(%d)\n" + " SIMODE0 0x%x, SIMODE1 0x%x, SSTAT0 0x%x, SEQADDR 0x%x\n", + p->host_no, -1, -1, -1, status, scb_index, aic_inb(p, SIMODE0), + aic_inb(p, SIMODE1), aic_inb(p, SSTAT0), + (aic_inb(p, SEQADDR1) << 8) | aic_inb(p, SEQADDR0)); + /* + * Turn off the interrupt and set status to zero, so that it + * falls through the rest of the SCSIINT code. + */ + aic_outb(p, status, CLRSINT1); + aic_outb(p, CLRSCSIINT, CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); + scb = NULL; + } + else if (status & SCSIPERR) + { + /* + * Determine the bus phase and queue an appropriate message. + */ + char *phase; + Scsi_Cmnd *cmd; + unsigned char mesg_out = MSG_NOOP; + unsigned char lastphase = aic_inb(p, LASTPHASE); + + cmd = scb->cmd; + switch (lastphase) + { + case P_DATAOUT: + phase = "Data-Out"; + break; + case P_DATAIN: + phase = "Data-In"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_COMMAND: + phase = "Command"; + break; + case P_MESGOUT: + phase = "Message-Out"; + break; + case P_STATUS: + phase = "Status"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_MESGIN: + phase = "Message-In"; + mesg_out = MSG_PARITY_ERROR; + break; + default: + phase = "unknown"; + break; + } + + /* + * A parity error has occurred during a data + * transfer phase. Flag it and continue. + */ + printk(WARN_LEAD "Parity error during %s phase.\n", + p->host_no, CTL_OF_SCB(scb), phase); + + /* + * We've set the hardware to assert ATN if we get a parity + * error on "in" phases, so all we need to do is stuff the + * message buffer with the appropriate message. "In" phases + * have set mesg_out to something other than MSG_NOP. + */ + if (mesg_out != MSG_NOOP) + { + aic_outb(p, mesg_out, MSG_OUT); + scb = NULL; + } + aic_outb(p, CLRSCSIPERR, CLRSINT1); + aic_outb(p, CLRSCSIINT, CLRINT); + unpause_sequencer(p, /* unpause_always */ TRUE); + } + else if ( (status & REQINIT) && + (p->flags & AHC_HANDLING_REQINITS) ) + { +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Handling REQINIT, SSTAT1=0x%x.\n", p->host_no, + CTL_OF_SCB(scb), aic_inb(p, SSTAT1)); +#endif + aic7xxx_handle_reqinit(p, scb); + return; + } + else + { + /* + * We don't know what's going on. Turn off the + * interrupt source and try to continue. + */ + if (aic7xxx_verbose & VERBOSE_SCSIINT) + printk(INFO_LEAD "Unknown SCSIINT status, SSTAT1(0x%x).\n", + p->host_no, -1, -1, -1, status); + aic_outb(p, status, CLRSINT1); + aic_outb(p, CLRSCSIINT, CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); + scb = NULL; + } + if (scb != NULL) + { + aic7xxx_done(p, scb); + } +} + +#ifdef AIC7XXX_VERBOSE_DEBUGGING +static void +aic7xxx_check_scbs(struct aic7xxx_host *p, char *buffer) +{ + unsigned char saved_scbptr, free_scbh, dis_scbh, wait_scbh, temp; + int i, bogus, lost; + static unsigned char scb_status[AIC7XXX_MAXSCB]; + +#define SCB_NO_LIST 0 +#define SCB_FREE_LIST 1 +#define SCB_WAITING_LIST 2 +#define SCB_DISCONNECTED_LIST 4 +#define SCB_CURRENTLY_ACTIVE 8 + + /* + * Note, these checks will fail on a regular basis once the machine moves + * beyond the bus scan phase. The problem is race conditions concerning + * the scbs and where they are linked in. When you have 30 or so commands + * outstanding on the bus, and run this twice with every interrupt, the + * chances get pretty good that you'll catch the sequencer with an SCB + * only partially linked in. Therefore, once we pass the scan phase + * of the bus, we really should disable this function. + */ + bogus = FALSE; + memset(&scb_status[0], 0, sizeof(scb_status)); + pause_sequencer(p); + saved_scbptr = aic_inb(p, SCBPTR); + if (saved_scbptr >= p->scb_data->maxhscbs) + { + printk("Bogus SCBPTR %d\n", saved_scbptr); + bogus = TRUE; + } + scb_status[saved_scbptr] = SCB_CURRENTLY_ACTIVE; + free_scbh = aic_inb(p, FREE_SCBH); + if ( (free_scbh != SCB_LIST_NULL) && + (free_scbh >= p->scb_data->maxhscbs) ) + { + printk("Bogus FREE_SCBH %d\n", free_scbh); + bogus = TRUE; + } + else + { + temp = free_scbh; + while( (temp != SCB_LIST_NULL) && (temp < p->scb_data->maxhscbs) ) + { + if(scb_status[temp] & 0x07) + { + printk("HSCB %d on multiple lists, status 0x%02x", temp, + scb_status[temp] | SCB_FREE_LIST); + bogus = TRUE; + } + scb_status[temp] |= SCB_FREE_LIST; + aic_outb(p, temp, SCBPTR); + temp = aic_inb(p, SCB_NEXT); + } + } + + dis_scbh = aic_inb(p, DISCONNECTED_SCBH); + if ( (dis_scbh != SCB_LIST_NULL) && + (dis_scbh >= p->scb_data->maxhscbs) ) + { + printk("Bogus DISCONNECTED_SCBH %d\n", dis_scbh); + bogus = TRUE; + } + else + { + temp = dis_scbh; + while( (temp != SCB_LIST_NULL) && (temp < p->scb_data->maxhscbs) ) + { + if(scb_status[temp] & 0x07) + { + printk("HSCB %d on multiple lists, status 0x%02x", temp, + scb_status[temp] | SCB_DISCONNECTED_LIST); + bogus = TRUE; + } + scb_status[temp] |= SCB_DISCONNECTED_LIST; + aic_outb(p, temp, SCBPTR); + temp = aic_inb(p, SCB_NEXT); + } + } + + wait_scbh = aic_inb(p, WAITING_SCBH); + if ( (wait_scbh != SCB_LIST_NULL) && + (wait_scbh >= p->scb_data->maxhscbs) ) + { + printk("Bogus WAITING_SCBH %d\n", wait_scbh); + bogus = TRUE; + } + else + { + temp = wait_scbh; + while( (temp != SCB_LIST_NULL) && (temp < p->scb_data->maxhscbs) ) + { + if(scb_status[temp] & 0x07) + { + printk("HSCB %d on multiple lists, status 0x%02x", temp, + scb_status[temp] | SCB_WAITING_LIST); + bogus = TRUE; + } + scb_status[temp] |= SCB_WAITING_LIST; + aic_outb(p, temp, SCBPTR); + temp = aic_inb(p, SCB_NEXT); + } + } + + lost=0; + for(i=0; i < p->scb_data->maxhscbs; i++) + { + aic_outb(p, i, SCBPTR); + temp = aic_inb(p, SCB_NEXT); + if ( ((temp != SCB_LIST_NULL) && + (temp >= p->scb_data->maxhscbs)) ) + { + printk("HSCB %d bad, SCB_NEXT invalid(%d).\n", i, temp); + bogus = TRUE; + } + if ( temp == i ) + { + printk("HSCB %d bad, SCB_NEXT points to self.\n", i); + bogus = TRUE; + } + temp = aic_inb(p, SCB_PREV); + if ((temp != SCB_LIST_NULL) && + (temp >= p->scb_data->maxhscbs)) + { + printk("HSCB %d bad, SCB_PREV invalid(%d).\n", i, temp); + bogus = TRUE; + } + if (scb_status[i] == 0) + lost++; + if (lost > 1) + { + printk("Too many lost scbs.\n"); + bogus=TRUE; + } + } + aic_outb(p, saved_scbptr, SCBPTR); + unpause_sequencer(p, FALSE); + if (bogus) + { + printk("Bogus parameters found in card SCB array structures.\n"); + printk("%s\n", buffer); + aic7xxx_panic_abort(p, NULL); + } + return; +} +#endif + +/*+F************************************************************************* + * Function: + * aic7xxx_isr + * + * Description: + * SCSI controller interrupt handler. + *-F*************************************************************************/ +static void +aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct aic7xxx_host *p; + unsigned char intstat; + + p = (struct aic7xxx_host *)dev_id; + + /* + * Just a few sanity checks. Make sure that we have an int pending. + * Also, if PCI, then we are going to check for a PCI bus error status + * should we get too many spurious interrupts. + */ + if (!((intstat = aic_inb(p, INTSTAT)) & INT_PEND)) + { +#ifdef CONFIG_PCI + if ( (p->chip & AHC_PCI) && (p->spurious_int > 500) && + !(p->flags & AHC_HANDLING_REQINITS) ) + { + if ( aic_inb(p, ERROR) & PCIERRSTAT ) + { + aic7xxx_pci_intr(p); + } + p->spurious_int = 0; + } + else if ( !(p->flags & AHC_HANDLING_REQINITS) ) + { + p->spurious_int++; + } +#endif + return; + } + + p->spurious_int = 0; + + /* + * Keep track of interrupts for /proc/scsi + */ + p->isr_count++; + +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if ( (p->isr_count < 16) && (aic7xxx_verbose > 0xffff) && + (aic7xxx_panic_on_abort) && (p->flags & AHC_PAGESCBS) ) + aic7xxx_check_scbs(p, "Bogus settings at start of interrupt."); +#endif + + /* + * Handle all the interrupt sources - especially for SCSI + * interrupts, we won't get a second chance at them. + */ + if (intstat & CMDCMPLT) + { + struct aic7xxx_scb *scb = NULL; + Scsi_Cmnd *cmd; + unsigned char scb_index; + +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if(aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Command Complete Int.\n", p->host_no, -1, -1, -1); +#endif + + /* + * Clear interrupt status before running the completion loop. + * This eliminates a race condition whereby a command could + * complete between the last check of qoutfifo and the + * CLRCMDINT statement. This would result in us thinking the + * qoutfifo was empty when it wasn't, and in actuality be a lost + * completion interrupt. With multiple devices or tagged queueing + * this could be very bad if we caught all but the last completion + * and no more are imediately sent. + */ + aic_outb(p, CLRCMDINT, CLRINT); + /* + * The sequencer will continue running when it + * issues this interrupt. There may be >1 commands + * finished, so loop until we've processed them all. + */ + + while (p->qoutfifo[p->qoutfifonext] != SCB_LIST_NULL) + { + scb_index = p->qoutfifo[p->qoutfifonext]; + p->qoutfifo[p->qoutfifonext++] = SCB_LIST_NULL; + if ( scb_index >= p->scb_data->numscbs ) + scb = NULL; + else + scb = p->scb_data->scb_array[scb_index]; + if (scb == NULL) + { + printk(WARN_LEAD "CMDCMPLT with invalid SCB index %d\n", p->host_no, + -1, -1, -1, scb_index); + continue; + } + else if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk(WARN_LEAD "CMDCMPLT without command for SCB %d, SCB flags " + "0x%x, cmd 0x%lx\n", p->host_no, -1, -1, -1, scb_index, scb->flags, + (unsigned long) scb->cmd); + continue; + } + else if (scb->flags & SCB_QUEUED_ABORT) + { + pause_sequencer(p); + if ( ((aic_inb(p, LASTPHASE) & PHASE_MASK) != P_BUSFREE) && + (aic_inb(p, SCB_TAG) == scb->hscb->tag) ) + { + unpause_sequencer(p, FALSE); + continue; + } + aic7xxx_reset_device(p, scb->cmd->target, scb->cmd->channel, + scb->cmd->lun, scb->hscb->tag); + scb->flags &= ~(SCB_QUEUED_FOR_DONE | SCB_RESET | SCB_ABORT | + SCB_QUEUED_ABORT); + unpause_sequencer(p, FALSE); + } + else if (scb->flags & SCB_ABORT) + { + /* + * We started to abort this, but it completed on us, let it + * through as successful + */ + scb->flags &= ~(SCB_ABORT|SCB_RESET); + } + switch (status_byte(scb->hscb->target_status)) + { + case QUEUE_FULL: + case BUSY: + scb->hscb->target_status = 0; + scb->cmd->result = 0; + aic7xxx_error(scb->cmd) = DID_OK; + break; + default: + cmd = scb->cmd; + if (scb->hscb->residual_SG_segment_count != 0) + { + aic7xxx_calculate_residual(p, scb); + } + cmd->result |= (aic7xxx_error(cmd) << 16); + if (scb->tag_action) + p->dev_flags[TARGET_INDEX(cmd)] |= + DEVICE_TAGGED_SUCCESS | DEVICE_SUCCESS | DEVICE_PRESENT; + else + p->dev_flags[TARGET_INDEX(cmd)] |= + DEVICE_SUCCESS | DEVICE_PRESENT; + aic7xxx_done(p, scb); + break; + } + } + } + + if (intstat & BRKADRINT) + { + int i; + unsigned char errno = aic_inb(p, ERROR); + + printk(KERN_ERR "(scsi%d) BRKADRINT error(0x%x):\n", p->host_no, errno); + for (i = 0; i < NUMBER(hard_error); i++) + { + if (errno & hard_error[i].errno) + { + printk(KERN_ERR " %s\n", hard_error[i].errmesg); + } + } + printk(KERN_ERR "(scsi%d) SEQADDR=0x%x\n", p->host_no, + (((aic_inb(p, SEQADDR1) << 8) & 0x100) | aic_inb(p, SEQADDR0))); + if (aic7xxx_panic_on_abort) + aic7xxx_panic_abort(p, NULL); +#ifdef CONFIG_PCI + if (errno & PCIERRSTAT) + aic7xxx_pci_intr(p); +#endif + if (errno & (SQPARERR | ILLOPCODE | ILLSADDR)) + { + sti(); + panic("aic7xxx: unrecoverable BRKADRINT.\n"); + } + if (errno & ILLHADDR) + { + printk(KERN_ERR "(scsi%d) BUG! Driver accessed chip without first " + "pausing controller!\n", p->host_no); + } +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (errno & DPARERR) + { + if (aic_inb(p, DMAPARAMS) & DIRECTION) + printk("(scsi%d) while DMAing SCB from host to card.\n", p->host_no); + else + printk("(scsi%d) while DMAing SCB from card to host.\n", p->host_no); + } +#endif + aic_outb(p, CLRPARERR | CLRBRKADRINT, CLRINT); + unpause_sequencer(p, FALSE); + } + + if (intstat & SEQINT) + { + aic7xxx_handle_seqint(p, intstat); + } + + if (intstat & SCSIINT) + { + aic7xxx_handle_scsiint(p, intstat); + } + +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if ( (p->isr_count < 16) && (aic7xxx_verbose > 0xffff) && + (aic7xxx_panic_on_abort) && (p->flags & AHC_PAGESCBS) ) + aic7xxx_check_scbs(p, "Bogus settings at end of interrupt."); +#endif + +} + +/*+F************************************************************************* + * Function: + * do_aic7xxx_isr + * + * Description: + * This is a gross hack to solve a problem in linux kernels 2.1.85 and + * above. Please, children, do not try this at home, and if you ever see + * anything like it, please inform the Gross Hack Police immediately + *-F*************************************************************************/ +static void +do_aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long cpu_flags; + struct aic7xxx_host *p; + + p = (struct aic7xxx_host *)dev_id; + if(!p) + return; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,95) + spin_lock_irqsave(&io_request_lock, cpu_flags); + if(test_and_set_bit(AHC_IN_ISR_BIT, &p->flags)) + { + return; + } + do + { + aic7xxx_isr(irq, dev_id, regs); + } while ( (aic_inb(p, INTSTAT) & INT_PEND) ); + aic7xxx_done_cmds_complete(p); + aic7xxx_run_waiting_queues(p); + clear_bit(AHC_IN_ISR_BIT, &p->flags); + spin_unlock_irqrestore(&io_request_lock, cpu_flags); +#else + if(set_bit(AHC_IN_ISR_BIT, (int *)&p->flags)) + { + return; + } + DRIVER_LOCK + do + { + aic7xxx_isr(irq, dev_id, regs); + } while ( (aic_inb(p, INTSTAT) & INT_PEND) ); + DRIVER_UNLOCK + aic7xxx_done_cmds_complete(p); + aic7xxx_run_waiting_queues(p); + clear_bit(AHC_IN_ISR_BIT, (int *)&p->flags); +#endif +} + +/*+F************************************************************************* + * Function: + * aic7xxx_device_queue_depth + * + * Description: + * Determines the queue depth for a given device. There are two ways + * a queue depth can be obtained for a tagged queueing device. One + * way is the default queue depth which is determined by whether + * AIC7XXX_CMDS_PER_LUN is defined. If it is defined, then it is used + * as the default queue depth. Otherwise, we use either 4 or 8 as the + * default queue depth (dependent on the number of hardware SCBs). + * The other way we determine queue depth is through the use of the + * aic7xxx_tag_info array which is enabled by defining + * AIC7XXX_TAGGED_QUEUEING_BY_DEVICE. This array can be initialized + * with queue depths for individual devices. It also allows tagged + * queueing to be [en|dis]abled for a specific adapter. + *-F*************************************************************************/ +static void +aic7xxx_device_queue_depth(struct aic7xxx_host *p, Scsi_Device *device) +{ + int default_depth = 3; + unsigned char tindex; + unsigned short target_mask; + + tindex = device->id | (device->channel << 3); + target_mask = (1 << tindex); + + device->queue_depth = default_depth; + p->dev_mid_level_queue_depth[tindex] = 3; + p->dev_temp_queue_depth[tindex] = 1; + p->dev_max_queue_depth[tindex] = 1; + p->tagenable &= ~target_mask; + + if (device->tagged_supported) + { + int tag_enabled = TRUE; + +#ifdef AIC7XXX_CMDS_PER_LUN + default_depth = AIC7XXX_CMDS_PER_LUN; +#else + default_depth = 8; /* Not many SCBs to work with. */ +#endif + + if (!(p->discenable & target_mask)) + { + if (aic7xxx_verbose & VERBOSE_NEGOTIATION2) + printk(INFO_LEAD "Disconnection disabled, unable to " + "enable tagged queueing.\n", + p->host_no, device->channel, device->id, device->lun); + } + else + { + if (p->instance >= NUMBER(aic7xxx_tag_info)) + { + static int print_warning = TRUE; + if(print_warning) + { + printk(KERN_INFO "aic7xxx: WARNING, insufficient tag_info instances for" + " installed controllers.\n"); + printk(KERN_INFO "aic7xxx: Please update the aic7xxx_tag_info array in" + " the aic7xxx.c source file.\n"); + print_warning = FALSE; + } + device->queue_depth = default_depth; + } + else + { + + if (aic7xxx_tag_info[p->instance].tag_commands[tindex] == 255) + { + tag_enabled = FALSE; + device->queue_depth = 3; /* Tagged queueing is disabled. */ + } + else if (aic7xxx_tag_info[p->instance].tag_commands[tindex] == 0) + { + device->queue_depth = default_depth; + } + else + { + device->queue_depth = + aic7xxx_tag_info[p->instance].tag_commands[tindex]; + } + } + if ((device->tagged_queue == 0) && tag_enabled) + { + if (aic7xxx_verbose & VERBOSE_NEGOTIATION2) + { + printk(INFO_LEAD "Enabled tagged queuing, queue depth %d.\n", + p->host_no, device->channel, device->id, + device->lun, device->queue_depth); + } + p->dev_max_queue_depth[tindex] = device->queue_depth; + p->dev_temp_queue_depth[tindex] = device->queue_depth; + p->dev_mid_level_queue_depth[tindex] = device->queue_depth; + p->tagenable |= target_mask; + p->orderedtag |= target_mask; + device->tagged_queue = 1; + device->current_tag = SCB_LIST_NULL; + } + } + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_select_queue_depth + * + * Description: + * Sets the queue depth for each SCSI device hanging off the input + * host adapter. We use a queue depth of 2 for devices that do not + * support tagged queueing. If AIC7XXX_CMDS_PER_LUN is defined, we + * use that for tagged queueing devices; otherwise we use our own + * algorithm for determining the queue depth based on the maximum + * SCBs for the controller. + *-F*************************************************************************/ +static void +aic7xxx_select_queue_depth(struct Scsi_Host *host, + Scsi_Device *scsi_devs) +{ + Scsi_Device *device; + struct aic7xxx_host *p = (struct aic7xxx_host *) host->hostdata; + int scbnum; + + scbnum = 0; + for (device = scsi_devs; device != NULL; device = device->next) + { + if (device->host == host) + { + aic7xxx_device_queue_depth(p, device); + scbnum += device->queue_depth; + } + } + while (scbnum > p->scb_data->numscbs) + { + /* + * Pre-allocate the needed SCBs to get around the possibility of having + * to allocate some when memory is more or less exhausted and we need + * the SCB in order to perform a swap operation (possible deadlock) + */ + if ( aic7xxx_allocate_scb(p) == 0 ) + return; + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_probe + * + * Description: + * Probing for EISA boards: it looks like the first two bytes + * are a manufacturer code - three characters, five bits each: + * + * BYTE 0 BYTE 1 BYTE 2 BYTE 3 + * ?1111122 22233333 PPPPPPPP RRRRRRRR + * + * The characters are baselined off ASCII '@', so add that value + * to each to get the real ASCII code for it. The next two bytes + * appear to be a product and revision number, probably vendor- + * specific. This is what is being searched for at each port, + * and what should probably correspond to the ID= field in the + * ECU's .cfg file for the card - if your card is not detected, + * make sure your signature is listed in the array. + * + * The fourth byte's lowest bit seems to be an enabled/disabled + * flag (rest of the bits are reserved?). + * + * NOTE: This function is only needed on Intel and Alpha platforms, + * the other platforms we support don't have EISA/VLB busses. So, + * we #ifdef this entire function to avoid compiler warnings about + * an unused function. + *-F*************************************************************************/ +#if defined(__i386__) || defined(__alpha__) +static int +aic7xxx_probe(int slot, int base, ahc_flag_type *flags) +{ + int i; + unsigned char buf[4]; + + static struct { + int n; + unsigned char signature[sizeof(buf)]; + ahc_chip type; + int bios_disabled; + } AIC7xxx[] = { + { 4, { 0x04, 0x90, 0x77, 0x70 }, + AHC_AIC7770|AHC_EISA, FALSE }, /* mb 7770 */ + { 4, { 0x04, 0x90, 0x77, 0x71 }, + AHC_AIC7770|AHC_EISA, FALSE }, /* host adapter 274x */ + { 4, { 0x04, 0x90, 0x77, 0x56 }, + AHC_AIC7770|AHC_VL, FALSE }, /* 284x BIOS enabled */ + { 4, { 0x04, 0x90, 0x77, 0x57 }, + AHC_AIC7770|AHC_VL, TRUE } /* 284x BIOS disabled */ + }; + + /* + * The VL-bus cards need to be primed by + * writing before a signature check. + */ + for (i = 0; i < sizeof(buf); i++) + { + outb(0x80 + i, base); + buf[i] = inb(base + i); + } + + for (i = 0; i < NUMBER(AIC7xxx); i++) + { + /* + * Signature match on enabled card? + */ + if (!memcmp(buf, AIC7xxx[i].signature, AIC7xxx[i].n)) + { + if (inb(base + 4) & 1) + { + if (AIC7xxx[i].bios_disabled) + { + *flags |= AHC_USEDEFAULTS; + } + else + { + *flags |= AHC_BIOS_ENABLED; + } + return (i); + } + + printk("aic7xxx: <Adaptec 7770 SCSI Host Adapter> " + "disabled at slot %d, ignored.\n", slot); + } + } + + return (-1); +} +#endif /* (__i386__) || (__alpha__) */ + + +/*+F************************************************************************* + * Function: + * read_2840_seeprom + * + * Description: + * Reads the 2840 serial EEPROM and returns 1 if successful and 0 if + * not successful. + * + * See read_seeprom (for the 2940) for the instruction set of the 93C46 + * chip. + * + * The 2840 interface to the 93C46 serial EEPROM is through the + * STATUS_2840 and SEECTL_2840 registers. The CS_2840, CK_2840, and + * DO_2840 bits of the SEECTL_2840 register are connected to the chip + * select, clock, and data out lines respectively of the serial EEPROM. + * The DI_2840 bit of the STATUS_2840 is connected to the data in line + * of the serial EEPROM. The EEPROM_TF bit of STATUS_2840 register is + * useful in that it gives us an 800 nsec timer. After a read from the + * SEECTL_2840 register the timing flag is cleared and goes high 800 nsec + * later. + *-F*************************************************************************/ +static int +read_284x_seeprom(struct aic7xxx_host *p, struct seeprom_config *sc) +{ + int i = 0, k = 0; + unsigned char temp; + unsigned short checksum = 0; + unsigned short *seeprom = (unsigned short *) sc; + struct seeprom_cmd { + unsigned char len; + unsigned char bits[3]; + }; + struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; + +#define CLOCK_PULSE(p) \ + while ((aic_inb(p, STATUS_2840) & EEPROM_TF) == 0) \ + { \ + ; /* Do nothing */ \ + } \ + (void) aic_inb(p, SEECTL_2840); + + /* + * Read the first 32 registers of the seeprom. For the 2840, + * the 93C46 SEEPROM is a 1024-bit device with 64 16-bit registers + * but only the first 32 are used by Adaptec BIOS. The loop + * will range from 0 to 31. + */ + for (k = 0; k < (sizeof(*sc) / 2); k++) + { + /* + * Send chip select for one clock cycle. + */ + aic_outb(p, CK_2840 | CS_2840, SEECTL_2840); + CLOCK_PULSE(p); + + /* + * Now we're ready to send the read command followed by the + * address of the 16-bit register we want to read. + */ + for (i = 0; i < seeprom_read.len; i++) + { + temp = CS_2840 | seeprom_read.bits[i]; + aic_outb(p, temp, SEECTL_2840); + CLOCK_PULSE(p); + temp = temp ^ CK_2840; + aic_outb(p, temp, SEECTL_2840); + CLOCK_PULSE(p); + } + /* + * Send the 6 bit address (MSB first, LSB last). + */ + for (i = 5; i >= 0; i--) + { + temp = k; + temp = (temp >> i) & 1; /* Mask out all but lower bit. */ + temp = CS_2840 | temp; + aic_outb(p, temp, SEECTL_2840); + CLOCK_PULSE(p); + temp = temp ^ CK_2840; + aic_outb(p, temp, SEECTL_2840); + CLOCK_PULSE(p); + } + + /* + * Now read the 16 bit register. An initial 0 precedes the + * register contents which begins with bit 15 (MSB) and ends + * with bit 0 (LSB). The initial 0 will be shifted off the + * top of our word as we let the loop run from 0 to 16. + */ + for (i = 0; i <= 16; i++) + { + temp = CS_2840; + aic_outb(p, temp, SEECTL_2840); + CLOCK_PULSE(p); + temp = temp ^ CK_2840; + seeprom[k] = (seeprom[k] << 1) | (aic_inb(p, STATUS_2840) & DI_2840); + aic_outb(p, temp, SEECTL_2840); + CLOCK_PULSE(p); + } + /* + * The serial EEPROM has a checksum in the last word. Keep a + * running checksum for all words read except for the last + * word. We'll verify the checksum after all words have been + * read. + */ + if (k < (sizeof(*sc) / 2) - 1) + { + checksum = checksum + seeprom[k]; + } + + /* + * Reset the chip select for the next command cycle. + */ + aic_outb(p, 0, SEECTL_2840); + CLOCK_PULSE(p); + aic_outb(p, CK_2840, SEECTL_2840); + CLOCK_PULSE(p); + aic_outb(p, 0, SEECTL_2840); + CLOCK_PULSE(p); + } + +#if 0 + printk("Computed checksum 0x%x, checksum read 0x%x\n", checksum, sc->checksum); + printk("Serial EEPROM:"); + for (k = 0; k < (sizeof(*sc) / 2); k++) + { + if (((k % 8) == 0) && (k != 0)) + { + printk("\n "); + } + printk(" 0x%x", seeprom[k]); + } + printk("\n"); +#endif + + if (checksum != sc->checksum) + { + printk("aic7xxx: SEEPROM checksum error, ignoring SEEPROM settings.\n"); + return (0); + } + + return (1); +#undef CLOCK_PULSE +} + +/*+F************************************************************************* + * Function: + * acquire_seeprom + * + * Description: + * Acquires access to the memory port on PCI controllers. + *-F*************************************************************************/ +static int +acquire_seeprom(struct aic7xxx_host *p) +{ + int wait; + + /* + * Request access of the memory port. When access is + * granted, SEERDY will go high. We use a 1 second + * timeout which should be near 1 second more than + * is needed. Reason: after the 7870 chip reset, there + * should be no contention. + */ + aic_outb(p, SEEMS, SEECTL); + wait = 1000; /* 1000 msec = 1 second */ + while ((wait > 0) && ((aic_inb(p, SEECTL) & SEERDY) == 0)) + { + wait--; + mdelay(1); /* 1 msec */ + } + if ((aic_inb(p, SEECTL) & SEERDY) == 0) + { + aic_outb(p, 0, SEECTL); + return (0); + } + return (1); +} + +/*+F************************************************************************* + * Function: + * release_seeprom + * + * Description: + * Releases access to the memory port on PCI controllers. + *-F*************************************************************************/ +static void +release_seeprom(struct aic7xxx_host *p) +{ + aic_outb(p, 0, SEECTL); +} + +/*+F************************************************************************* + * Function: + * read_seeprom + * + * Description: + * Reads the serial EEPROM and returns 1 if successful and 0 if + * not successful. + * + * The instruction set of the 93C46/56/66 chips is as follows: + * + * Start OP + * Function Bit Code Address Data Description + * ------------------------------------------------------------------- + * READ 1 10 A5 - A0 Reads data stored in memory, + * starting at specified address + * EWEN 1 00 11XXXX Write enable must precede + * all programming modes + * ERASE 1 11 A5 - A0 Erase register A5A4A3A2A1A0 + * WRITE 1 01 A5 - A0 D15 - D0 Writes register + * ERAL 1 00 10XXXX Erase all registers + * WRAL 1 00 01XXXX D15 - D0 Writes to all registers + * EWDS 1 00 00XXXX Disables all programming + * instructions + * *Note: A value of X for address is a don't care condition. + * *Note: The 93C56 and 93C66 have 8 address bits. + * + * + * The 93C46 has a four wire interface: clock, chip select, data in, and + * data out. In order to perform one of the above functions, you need + * to enable the chip select for a clock period (typically a minimum of + * 1 usec, with the clock high and low a minimum of 750 and 250 nsec + * respectively. While the chip select remains high, you can clock in + * the instructions (above) starting with the start bit, followed by the + * OP code, Address, and Data (if needed). For the READ instruction, the + * requested 16-bit register contents is read from the data out line but + * is preceded by an initial zero (leading 0, followed by 16-bits, MSB + * first). The clock cycling from low to high initiates the next data + * bit to be sent from the chip. + * + * The 78xx interface to the 93C46 serial EEPROM is through the SEECTL + * register. After successful arbitration for the memory port, the + * SEECS bit of the SEECTL register is connected to the chip select. + * The SEECK, SEEDO, and SEEDI are connected to the clock, data out, + * and data in lines respectively. The SEERDY bit of SEECTL is useful + * in that it gives us an 800 nsec timer. After a write to the SEECTL + * register, the SEERDY goes high 800 nsec later. The one exception + * to this is when we first request access to the memory port. The + * SEERDY goes high to signify that access has been granted and, for + * this case, has no implied timing. + *-F*************************************************************************/ +static int +read_seeprom(struct aic7xxx_host *p, int offset, + unsigned short *scarray, unsigned int len, seeprom_chip_type chip) +{ + int i = 0, k; + unsigned char temp; + unsigned short checksum = 0; + struct seeprom_cmd { + unsigned char len; + unsigned char bits[3]; + }; + struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; + +#define CLOCK_PULSE(p) \ + while ((aic_inb(p, SEECTL) & SEERDY) == 0) \ + { \ + ; /* Do nothing */ \ + } + + /* + * Request access of the memory port. + */ + if (acquire_seeprom(p) == 0) + { + return (0); + } + + /* + * Read 'len' registers of the seeprom. For the 7870, the 93C46 + * SEEPROM is a 1024-bit device with 64 16-bit registers but only + * the first 32 are used by Adaptec BIOS. Some adapters use the + * 93C56 SEEPROM which is a 2048-bit device. The loop will range + * from 0 to 'len' - 1. + */ + for (k = 0; k < len; k++) + { + /* + * Send chip select for one clock cycle. + */ + aic_outb(p, SEEMS | SEECK | SEECS, SEECTL); + CLOCK_PULSE(p); + + /* + * Now we're ready to send the read command followed by the + * address of the 16-bit register we want to read. + */ + for (i = 0; i < seeprom_read.len; i++) + { + temp = SEEMS | SEECS | (seeprom_read.bits[i] << 1); + aic_outb(p, temp, SEECTL); + CLOCK_PULSE(p); + temp = temp ^ SEECK; + aic_outb(p, temp, SEECTL); + CLOCK_PULSE(p); + } + /* + * Send the 6 or 8 bit address (MSB first, LSB last). + */ + for (i = ((int) chip - 1); i >= 0; i--) + { + temp = k + offset; + temp = (temp >> i) & 1; /* Mask out all but lower bit. */ + temp = SEEMS | SEECS | (temp << 1); + aic_outb(p, temp, SEECTL); + CLOCK_PULSE(p); + temp = temp ^ SEECK; + aic_outb(p, temp, SEECTL); + CLOCK_PULSE(p); + } + + /* + * Now read the 16 bit register. An initial 0 precedes the + * register contents which begins with bit 15 (MSB) and ends + * with bit 0 (LSB). The initial 0 will be shifted off the + * top of our word as we let the loop run from 0 to 16. + */ + for (i = 0; i <= 16; i++) + { + temp = SEEMS | SEECS; + aic_outb(p, temp, SEECTL); + CLOCK_PULSE(p); + temp = temp ^ SEECK; + scarray[k] = (scarray[k] << 1) | (aic_inb(p, SEECTL) & SEEDI); + aic_outb(p, temp, SEECTL); + CLOCK_PULSE(p); + } + + /* + * The serial EEPROM should have a checksum in the last word. + * Keep a running checksum for all words read except for the + * last word. We'll verify the checksum after all words have + * been read. + */ + if (k < (len - 1)) + { + checksum = checksum + scarray[k]; + } + + /* + * Reset the chip select for the next command cycle. + */ + aic_outb(p, SEEMS, SEECTL); + CLOCK_PULSE(p); + aic_outb(p, SEEMS | SEECK, SEECTL); + CLOCK_PULSE(p); + aic_outb(p, SEEMS, SEECTL); + CLOCK_PULSE(p); + } + + /* + * Release access to the memory port and the serial EEPROM. + */ + release_seeprom(p); + +#if 0 + printk("Computed checksum 0x%x, checksum read 0x%x\n", + checksum, scarray[len - 1]); + printk("Serial EEPROM:"); + for (k = 0; k < len; k++) + { + if (((k % 8) == 0) && (k != 0)) + { + printk("\n "); + } + printk(" 0x%x", scarray[k]); + } + printk("\n"); +#endif + if (checksum != scarray[len - 1]) + { + return (0); + } + + return (1); +#undef CLOCK_PULSE +} + +/*+F************************************************************************* + * Function: + * write_brdctl + * + * Description: + * Writes a value to the BRDCTL register. + *-F*************************************************************************/ +static void +write_brdctl(struct aic7xxx_host *p, unsigned char value) +{ + unsigned char brdctl; + + if ((p->chip & AHC_CHIPID_MASK) == AHC_AIC7895) + { + brdctl = BRDSTB; + if (p->flags & AHC_CHNLB) + brdctl |= BRDCS; + } + else if (p->features & AHC_ULTRA2) + brdctl = 0; + else + brdctl = BRDSTB | BRDCS; + aic_outb(p, brdctl, BRDCTL); + udelay(1); + brdctl |= value; + aic_outb(p, brdctl, BRDCTL); + udelay(1); + if (p->features & AHC_ULTRA2) + brdctl |= BRDSTB_ULTRA2; + else + brdctl &= ~BRDSTB; + aic_outb(p, brdctl, BRDCTL); + udelay(1); + if (p->features & AHC_ULTRA2) + brdctl = 0; + else + brdctl &= ~BRDCS; + aic_outb(p, brdctl, BRDCTL); + udelay(1); +} + +/*+F************************************************************************* + * Function: + * read_brdctl + * + * Description: + * Reads the BRDCTL register. + *-F*************************************************************************/ +static unsigned char +read_brdctl(struct aic7xxx_host *p) +{ + unsigned char brdctl, value; + + if ((p->chip & AHC_CHIPID_MASK) == AHC_AIC7895) + { + brdctl = BRDRW; + if (p->flags & AHC_CHNLB) + brdctl |= BRDCS; + } + else if (p->features & AHC_ULTRA2) + brdctl = BRDRW_ULTRA2; + else + brdctl = BRDRW | BRDCS; + aic_outb(p, brdctl, BRDCTL); + udelay(1); + value = aic_inb(p, BRDCTL); + aic_outb(p, 0, BRDCTL); + udelay(1); + return (value); +} + +/*+F************************************************************************* + * Function: + * aic785x_cable_detect + * + * Description: + * Detect the cables that are present on aic785x class controller chips + *-F*************************************************************************/ +static void +aic785x_cable_detect(struct aic7xxx_host *p, int *int_50, + int *ext_present, int *eeprom) +{ + unsigned char brdctl; + + aic_outb(p, BRDRW | BRDCS, BRDCTL); + udelay(1); + aic_outb(p, 0, BRDCTL); + udelay(1); + brdctl = aic_inb(p, BRDCTL); + udelay(1); + *int_50 = !(brdctl & BRDDAT5); + *ext_present = !(brdctl & BRDDAT6); + *eeprom = (aic_inb(p, SPIOCAP) & EEPROM); +} + +/*+F************************************************************************* + * Function: + * aic787x_cable_detect + * + * Description: + * Detect the cables that are present on aic787x class controller chips + * + * NOTE: This functions assumes the SEEPROM will have already been aquired + * prior to invocation of this function. + *-F*************************************************************************/ +static void +aic787x_cable_detect(struct aic7xxx_host *p, int *int_50, int *int_68, + int *ext_present, int *eeprom) +{ + unsigned char brdctl; + + /* + * First read the status of our cables. Set the rom bank to + * 0 since the bank setting serves as a multiplexor for the + * cable detection logic. BRDDAT5 controls the bank switch. + */ + write_brdctl(p, 0); + + /* + * Now we read the state of the two internal connectors. BRDDAT6 + * is internal 50, BRDDAT7 is internal 68. For each, the cable is + * present if the bit is 0 + */ + brdctl = read_brdctl(p); + *int_50 = !(brdctl & BRDDAT6); + *int_68 = !(brdctl & BRDDAT7); + + /* + * Set the bank bit in brdctl and then read the external cable state + * and the EEPROM status + */ + write_brdctl(p, BRDDAT5); + brdctl = read_brdctl(p); + + *ext_present = !(brdctl & BRDDAT6); + *eeprom = !(brdctl & BRDDAT7); + + /* + * We're done, the calling function will release the SEEPROM for us + */ +} + +/*+F************************************************************************* + * Function: + * aic787x_ultra2_term_detect + * + * Description: + * Detect the termination settings present on ultra2 class controllers + * + * NOTE: This functions assumes the SEEPROM will have already been aquired + * prior to invocation of this function. + *-F*************************************************************************/ +static void +aic7xxx_ultra2_term_detect(struct aic7xxx_host *p, int *enableSE_low, + int *enableSE_high, int *enableLVD_low, + int *enableLVD_high, int *eprom_present) +{ + unsigned char brdctl; + + brdctl = read_brdctl(p); + + *eprom_present = (brdctl & BRDDAT7); + *enableSE_high = (brdctl & BRDDAT6); + *enableSE_low = (brdctl & BRDDAT5); + *enableLVD_high = (brdctl & BRDDAT4); + *enableLVD_low = (brdctl & BRDDAT3); +} + +/*+F************************************************************************* + * Function: + * configure_termination + * + * Description: + * Configures the termination settings on PCI adapters that have + * SEEPROMs available. + *-F*************************************************************************/ +static void +configure_termination(struct aic7xxx_host *p) +{ + int internal50_present = 0; + int internal68_present = 0; + int external_present = 0; + int eprom_present = 0; + int enableSE_low = 0; + int enableSE_high = 0; + int enableLVD_low = 0; + int enableLVD_high = 0; + unsigned char brddat = 0; + unsigned char max_target = 0; + unsigned char sxfrctl1 = aic_inb(p, SXFRCTL1); + + if (acquire_seeprom(p)) + { + if (p->features & (AHC_WIDE|AHC_TWIN)) + max_target = 16; + else + max_target = 8; + aic_outb(p, SEEMS | SEECS, SEECTL); + sxfrctl1 &= ~STPWEN; + if ( (p->adapter_control & CFAUTOTERM) || + (p->features & AHC_ULTRA2) ) + { + if ( (p->adapter_control & CFAUTOTERM) && !(p->features & AHC_ULTRA2) ) + { + printk(KERN_INFO "(scsi%d) Warning - detected auto-termination\n", + p->host_no); + printk(KERN_INFO "(scsi%d) Please verify driver detected settings are " + "correct.\n", p->host_no); + printk(KERN_INFO "(scsi%d) If not, then please properly set the device " + "termination\n", p->host_no); + printk(KERN_INFO "(scsi%d) in the Adaptec SCSI BIOS by hitting CTRL-A " + "when prompted\n", p->host_no); + printk(KERN_INFO "(scsi%d) during machine bootup.\n", p->host_no); + } + /* Configure auto termination. */ + + if (p->features & AHC_ULTRA2) + { + if (aic7xxx_override_term == -1) + aic7xxx_ultra2_term_detect(p, &enableSE_low, &enableSE_high, + &enableLVD_low, &enableLVD_high, + &eprom_present); + if (!(p->adapter_control & CFSEAUTOTERM)) + { + enableSE_low = (p->adapter_control & CFSTERM); + enableSE_high = (p->adapter_control & CFWSTERM); + } + if (!(p->adapter_control & CFAUTOTERM)) + { + enableLVD_low = enableLVD_high = (p->adapter_control & CFLVDSTERM); + } + internal50_present = 0; + internal68_present = 1; + external_present = 1; + } + else if ( (p->chip & AHC_CHIPID_MASK) >= AHC_AIC7870 ) + { + aic787x_cable_detect(p, &internal50_present, &internal68_present, + &external_present, &eprom_present); + } + else + { + aic785x_cable_detect(p, &internal50_present, &external_present, + &eprom_present); + } + + if (max_target <= 8) + internal68_present = 0; + + if ( !(p->features & AHC_ULTRA2) ) + { + if (max_target > 8) + { + printk(KERN_INFO "(scsi%d) Cables present (Int-50 %s, Int-68 %s, " + "Ext-68 %s)\n", p->host_no, + internal50_present ? "YES" : "NO", + internal68_present ? "YES" : "NO", + external_present ? "YES" : "NO"); + } + else + { + printk(KERN_INFO "(scsi%d) Cables present (Int-50 %s, Ext-50 %s)\n", + p->host_no, + internal50_present ? "YES" : "NO", + external_present ? "YES" : "NO"); + } + } + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) EEPROM %s present.\n", p->host_no, + eprom_present ? "is" : "is not"); + + /* + * Now set the termination based on what we found. BRDDAT6 + * controls wide termination enable. + * Flash Enable = BRDDAT7 + * SE High Term Enable = BRDDAT6 + * SE Low Term Enable = BRDDAT5 (7890) + * LVD High Term Enable = BRDDAT4 (7890) + */ + if ( !(p->features & AHC_ULTRA2) && + (internal50_present && internal68_present && external_present) ) + { + printk(KERN_INFO "(scsi%d) Illegal cable configuration!! Only two\n", + p->host_no); + printk(KERN_INFO "(scsi%d) connectors on the SCSI controller may be " + "in use at a time!\n", p->host_no); + /* + * Force termination (low and high byte) on. This is safer than + * leaving it completely off, especially since this message comes + * most often from motherboard controllers that don't even have 3 + * connectors, but instead are failing the cable detection. + */ + internal50_present = external_present = 0; + enableSE_high = enableSE_low = 1; + } + + if ((max_target > 8) && + ((external_present == 0) || (internal68_present == 0) || + (enableSE_high != 0))) + { + brddat |= BRDDAT6; + p->flags |= AHC_TERM_ENB_SE_HIGH; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) SE High byte termination Enabled\n", + p->host_no); + } + + if ( (((internal50_present ? 1 : 0) + + (internal68_present ? 1 : 0) + + (external_present ? 1 : 0)) <= 1) || + (enableSE_low != 0) ) + { + if (p->features & AHC_ULTRA2) + brddat |= BRDDAT5; + else + sxfrctl1 |= STPWEN; + p->flags |= AHC_TERM_ENB_SE_LOW; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) SE Low byte termination Enabled\n", + p->host_no); + } + + if (enableLVD_low != 0) + { + sxfrctl1 |= STPWEN; + p->flags |= AHC_TERM_ENB_LVD; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) LVD Low byte termination Enabled\n", + p->host_no); + } + + if (enableLVD_high != 0) + { + brddat |= BRDDAT4; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) LVD High byte termination Enabled\n", + p->host_no); + } + } + else + { + if (p->adapter_control & CFSTERM) + { + if (p->features & AHC_ULTRA2) + brddat |= BRDDAT5; + else + sxfrctl1 |= STPWEN; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) SE Low byte termination Enabled\n", + p->host_no); + } + + if (p->adapter_control & CFWSTERM) + { + brddat |= BRDDAT6; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) SE High byte termination Enabled\n", + p->host_no); + } + } + write_brdctl(p, brddat); + release_seeprom(p); + aic_outb(p, sxfrctl1, SXFRCTL1); + } +} + +/*+F************************************************************************* + * Function: + * detect_maxscb + * + * Description: + * Detects the maximum number of SCBs for the controller and returns + * the count and a mask in p (p->maxscbs, p->qcntmask). + *-F*************************************************************************/ +static void +detect_maxscb(struct aic7xxx_host *p) +{ + int i; + + /* + * It's possible that we've already done this for multichannel + * adapters. + */ + if (p->scb_data->maxhscbs == 0) + { + /* + * We haven't initialized the SCB settings yet. Walk the SCBs to + * determince how many there are. + */ + aic_outb(p, 0, FREE_SCBH); + + for (i = 0; i < AIC7XXX_MAXSCB; i++) + { + aic_outb(p, i, SCBPTR); + aic_outb(p, i, SCB_CONTROL); + if (aic_inb(p, SCB_CONTROL) != i) + break; + aic_outb(p, 0, SCBPTR); + if (aic_inb(p, SCB_CONTROL) != 0) + break; + + aic_outb(p, i, SCBPTR); + aic_outb(p, 0, SCB_CONTROL); /* Clear the control byte. */ + aic_outb(p, i + 1, SCB_NEXT); /* Set the next pointer. */ + aic_outb(p, i - 1, SCB_PREV); /* Set the prev pointer. */ + aic_outb(p, SCB_LIST_NULL, SCB_TAG); /* Make the tag invalid. */ + aic_outb(p, SCB_LIST_NULL, SCB_BUSYTARGETS); /* no busy untagged */ + aic_outb(p, SCB_LIST_NULL, SCB_BUSYTARGETS+1);/* targets active yet */ + aic_outb(p, SCB_LIST_NULL, SCB_BUSYTARGETS+2); + aic_outb(p, SCB_LIST_NULL, SCB_BUSYTARGETS+3); + } + + /* Make sure the last SCB terminates the free list. */ + aic_outb(p, i - 1, SCBPTR); + aic_outb(p, SCB_LIST_NULL, SCB_NEXT); + + /* Ensure we clear the first (0) SCBs control byte. */ + aic_outb(p, 0, SCBPTR); + aic_outb(p, 0, SCB_CONTROL); + + p->scb_data->maxhscbs = i; + /* + * Use direct indexing instead for speed + */ + if ( i == AIC7XXX_MAXSCB ) + p->flags &= ~AHC_PAGESCBS; + } + +} + +/*+F************************************************************************* + * Function: + * aic7xxx_register + * + * Description: + * Register a Adaptec aic7xxx chip SCSI controller with the kernel. + *-F*************************************************************************/ +static int +aic7xxx_register(Scsi_Host_Template *template, struct aic7xxx_host *p, + int reset_delay) +{ + int i, result; + int max_targets; + int found = 1; + unsigned char term, scsi_conf; + struct Scsi_Host *host; + + /* + * Lock out other contenders for our i/o space. + */ + request_region(p->base, MAXREG - MINREG, "aic7xxx"); + + + host = p->host; + + p->scb_data->maxscbs = AIC7XXX_MAXSCB; + host->can_queue = AIC7XXX_MAXSCB; + host->cmd_per_lun = 3; + host->sg_tablesize = AIC7XXX_MAX_SG; + host->select_queue_depths = aic7xxx_select_queue_depth; + host->this_id = p->scsi_id; + host->io_port = p->base; + host->n_io_port = 0xFF; + host->base = (unsigned char *) p->mbase; + host->irq = p->irq; + if (p->features & AHC_WIDE) + { + host->max_id = 16; + } + if (p->features & AHC_TWIN) + { + host->max_channel = 1; + } + + p->host = host; + p->last_reset = 0; + p->host_no = host->host_no; + host->unique_id = p->instance; + p->isr_count = 0; + p->next = NULL; + p->completeq.head = NULL; + p->completeq.tail = NULL; + scbq_init(&p->scb_data->free_scbs); + scbq_init(&p->waiting_scbs); + + for (i = 0; i < NUMBER(p->untagged_scbs); i++) + { + p->untagged_scbs[i] = SCB_LIST_NULL; + p->qinfifo[i] = SCB_LIST_NULL; + p->qoutfifo[i] = SCB_LIST_NULL; + } + /* + * We currently have no commands of any type + */ + p->qinfifonext = 0; + p->qoutfifonext = 0; + + for (i = 0; i < MAX_TARGETS; i++) + { + p->dev_commands_sent[i] = 0; + p->dev_flags[i] = 0; + p->dev_active_cmds[i] = 0; + p->dev_last_reset[i] = 0; + p->dev_last_queue_full[i] = 0; + p->dev_last_queue_full_count[i] = 0; + p->dev_max_queue_depth[i] = 1; + p->dev_temp_queue_depth[i] = 1; + p->dev_mid_level_queue_depth[i] = 3; + scbq_init(&p->delayed_scbs[i]); + init_timer(&p->dev_timer[i]); + p->dev_timer[i].expires = 0; + p->dev_timer[i].data = (unsigned long)p; + p->dev_timer[i].function = (void *)aic7xxx_timer; + } + + printk(KERN_INFO "(scsi%d) <%s> found at ", p->host_no, + board_names[p->board_name_index]); + switch(p->chip) + { + case (AHC_AIC7770|AHC_EISA): + printk("EISA slot %d\n", p->pci_device_fn); + break; + case (AHC_AIC7770|AHC_VL): + printk("VLB slot %d\n", p->pci_device_fn); + break; + default: + printk("PCI %d/%d\n", PCI_SLOT(p->pci_device_fn), + PCI_FUNC(p->pci_device_fn)); + break; + } + if (p->features & AHC_TWIN) + { + printk(KERN_INFO "(scsi%d) Twin Channel, A SCSI ID %d, B SCSI ID %d, ", + p->host_no, p->scsi_id, p->scsi_id_b); + } + else + { + char *channel; + + channel = ""; + + if ((p->flags & AHC_MULTI_CHANNEL) != 0) + { + channel = " A"; + + if ( (p->flags & (AHC_CHNLB|AHC_CHNLC)) != 0 ) + { + channel = (p->flags & AHC_CHNLB) ? " B" : " C"; + } + } + if (p->features & AHC_WIDE) + { + printk(KERN_INFO "(scsi%d) Wide ", p->host_no); + } + else + { + printk(KERN_INFO "(scsi%d) Narrow ", p->host_no); + } + printk("Channel%s, SCSI ID=%d, ", channel, p->scsi_id); + } + aic_outb(p, 0, SEQ_FLAGS); + + /* + * Detect SCB parameters and initialize the SCB array. + */ + detect_maxscb(p); + printk("%d/%d SCBs\n", p->scb_data->maxhscbs, p->scb_data->maxscbs); + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk(KERN_INFO "(scsi%d) BIOS %sabled, IO Port 0x%lx, IRQ %d\n", + p->host_no, (p->flags & AHC_BIOS_ENABLED) ? "en" : "dis", + p->base, p->irq); + printk(KERN_INFO "(scsi%d) IO Memory at 0x%lx, MMAP Memory at 0x%lx\n", + p->host_no, p->mbase, (unsigned long)p->maddr); + } + +#ifdef CONFIG_PCI + /* + * Now that we know our instance number, we can set the flags we need to + * force termination if need be. + */ + if (aic7xxx_stpwlev != -1) + { + /* + * This option only applies to PCI controllers. + */ + if ( (p->chip & ~AHC_CHIPID_MASK) == AHC_PCI) + { + unsigned char devconfig; + +#if LINUX_KERNEL_VERSION > KERNEL_VERSION(2,1,92) + pci_read_config_byte(p->pdev, DEVCONFIG, &devconfig); +#else + pcibios_read_config_byte(p->pci_bus, p->pci_device_fn, + DEVCONFIG, &devconfig); +#endif + if ( (aic7xxx_stpwlev >> p->instance) & 0x01 ) + { + devconfig |= 0x02; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk("(scsi%d) Force setting STPWLEV bit\n", p->host_no); + } + else + { + devconfig &= ~0x02; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk("(scsi%d) Force clearing STPWLEV bit\n", p->host_no); + } +#if LINUX_KERNEL_VERSION > KERNEL_VERSION(2,1,92) + pci_write_config_byte(p->pdev, DEVCONFIG, devconfig); +#else + pcibios_write_config_byte(p->pci_bus, p->pci_device_fn, + DEVCONFIG, devconfig); +#endif + } + } +#endif + + /* + * That took care of devconfig and stpwlev, now for the actual termination + * settings. + */ + if (aic7xxx_override_term != -1) + { + /* + * Again, this only applies to PCI controllers. We don't have problems + * with the termination on 274x controllers to the best of my knowledge. + */ + if ( (p->chip & ~AHC_CHIPID_MASK) == AHC_PCI) + { + unsigned char term_override; + + term_override = ( (aic7xxx_override_term >> (p->instance * 4)) & 0x0f); + p->adapter_control &= + ~(CFSTERM|CFWSTERM|CFLVDSTERM|CFAUTOTERM|CFSEAUTOTERM); + if ( (p->features & AHC_ULTRA2) && (term_override & 0x0c) ) + { + p->adapter_control |= CFLVDSTERM; + } + if (term_override & 0x02) + { + p->adapter_control |= CFWSTERM; + } + if (term_override & 0x01) + { + p->adapter_control |= CFSTERM; + } + } + } + + if ( (p->flags & AHC_SEEPROM_FOUND) || (aic7xxx_override_term != -1) ) + { + if (p->features & AHC_SPIOCAP) + { + if ( aic_inb(p, SPIOCAP) & SSPIOCPS ) + /* + * Update the settings in sxfrctl1 to match the termination + * settings. + */ + configure_termination(p); + } + else if ((p->chip & AHC_CHIPID_MASK) >= AHC_AIC7870) + { + configure_termination(p); + } + } + + /* + * Clear out any possible pending interrupts. + */ + aic7xxx_clear_intstat(p); + + /* + * Set the SCSI Id, SXFRCTL0, SXFRCTL1, and SIMODE1, for both channels + */ + if (p->features & AHC_TWIN) + { + /* Select channel B */ + aic_outb(p, aic_inb(p, SBLKCTL) | SELBUSB, SBLKCTL); + + term = ((p->flags & AHC_TERM_ENB_B) != 0) ? STPWEN : 0; + aic_outb(p, p->scsi_id_b, SCSIID); + scsi_conf = aic_inb(p, SCSICONF + 1); + aic_outb(p, DFON | SPIOEN, SXFRCTL0); + aic_outb(p, (scsi_conf & ENSPCHK) | term | + ENSTIMER | ACTNEGEN, SXFRCTL1); + aic_outb(p, 0, SIMODE0); + aic_outb(p, ENSELTIMO | ENSCSIRST | ENSCSIPERR, SIMODE1); + aic_outb(p, 0, SCSIRATE); + + /* Select channel A */ + aic_outb(p, aic_inb(p, SBLKCTL) & ~SELBUSB, SBLKCTL); + } + + term = ((p->flags & AHC_TERM_ENB_SE_LOW) != 0) ? STPWEN : 0; + if (p->features & AHC_ULTRA2) + aic_outb(p, p->scsi_id, SCSIID_ULTRA2); + else + aic_outb(p, p->scsi_id, SCSIID); + scsi_conf = aic_inb(p, SCSICONF); + aic_outb(p, DFON | SPIOEN, SXFRCTL0); + aic_outb(p, (scsi_conf & ENSPCHK) | term | + ENSTIMER | ACTNEGEN, SXFRCTL1); + aic_outb(p, 0, SIMODE0); + aic_outb(p, ENSELTIMO | ENSCSIRST | ENSCSIPERR, SIMODE1); + aic_outb(p, 0, SCSIRATE); + if ( p->features & AHC_ULTRA2) + aic_outb(p, 0, SCSIOFFSET); + + /* + * Look at the information that board initialization or the board + * BIOS has left us. In the lower four bits of each target's + * scratch space any value other than 0 indicates that we should + * initiate synchronous transfers. If it's zero, the user or the + * BIOS has decided to disable synchronous negotiation to that + * target so we don't activate the needsdtr flag. + */ + if ((p->features & (AHC_TWIN|AHC_WIDE)) == 0) + { + max_targets = 8; + } + else + { + max_targets = 16; + } + + if (!(aic7xxx_no_reset)) + { + /* + * If we reset the bus, then clear the transfer settings, else leave + * them be + */ + for (i = 0; i < max_targets; i++) + { + aic_outb(p, 0, TARG_SCSIRATE + i); + if (p->features & AHC_ULTRA2) + { + aic_outb(p, 0, TARG_OFFSET + i); + } + p->transinfo[i].cur_offset = 0; + p->transinfo[i].cur_period = 0; + p->transinfo[i].cur_width = MSG_EXT_WDTR_BUS_8_BIT; + } + + /* + * If we reset the bus, then clear the transfer settings, else leave + * them be. + */ + aic_outb(p, 0, ULTRA_ENB); + aic_outb(p, 0, ULTRA_ENB + 1); + p->ultraenb = 0; + } + + /* + * Allocate enough hardware scbs to handle the maximum number of + * concurrent transactions we can have. We have to make sure that + * the allocated memory is contiguous memory. The Linux kmalloc + * routine should only allocate contiguous memory, but note that + * this could be a problem if kmalloc() is changed. + */ + { + size_t array_size; + unsigned int hscb_physaddr; + unsigned long temp; + + array_size = p->scb_data->maxscbs * sizeof(struct aic7xxx_hwscb); + if (p->scb_data->hscbs == NULL) + { + /* + * A little padding so we can align thing the way we want + */ + p->scb_data->hscbs = kmalloc(array_size + 0x1f, GFP_ATOMIC); + } + if (p->scb_data->hscbs == NULL) + { + printk("(scsi%d) Unable to allocate hardware SCB array; " + "failing detection.\n", p->host_no); + p->irq = 0; + return(0); + } + /* + * Save the actual kmalloc buffer pointer off, then align our + * buffer to a 32 byte boundary + */ + p->scb_data->hscb_kmalloc_ptr = p->scb_data->hscbs; + temp = (unsigned long)p->scb_data->hscbs; + temp += 0x1f; + temp &= ~0x1f; + p->scb_data->hscbs = (struct aic7xxx_hwscb *)temp; + /* At least the control byte of each SCB needs to be 0. */ + memset(p->scb_data->hscbs, 0, array_size); + + /* Tell the sequencer where it can find the hardware SCB array. */ + hscb_physaddr = VIRT_TO_BUS(p->scb_data->hscbs); + aic_outb(p, hscb_physaddr & 0xFF, HSCB_ADDR); + aic_outb(p, (hscb_physaddr >> 8) & 0xFF, HSCB_ADDR + 1); + aic_outb(p, (hscb_physaddr >> 16) & 0xFF, HSCB_ADDR + 2); + aic_outb(p, (hscb_physaddr >> 24) & 0xFF, HSCB_ADDR + 3); + + /* Set up the fifo areas at the same time */ + hscb_physaddr = VIRT_TO_BUS(&p->untagged_scbs[0]); + aic_outb(p, hscb_physaddr & 0xFF, SCBID_ADDR); + aic_outb(p, (hscb_physaddr >> 8) & 0xFF, SCBID_ADDR + 1); + aic_outb(p, (hscb_physaddr >> 16) & 0xFF, SCBID_ADDR + 2); + aic_outb(p, (hscb_physaddr >> 24) & 0xFF, SCBID_ADDR + 3); + } + + /* The Q-FIFOs we just set up are all empty */ + aic_outb(p, 0, QINPOS); + aic_outb(p, 0, KERNEL_QINPOS); + aic_outb(p, 0, QOUTPOS); + + if(p->features & AHC_QUEUE_REGS) + { + aic_outb(p, SCB_QSIZE_256, QOFF_CTLSTA); + aic_outb(p, 0, SDSCB_QOFF); + aic_outb(p, 0, SNSCB_QOFF); + aic_outb(p, 0, HNSCB_QOFF); + } + + /* + * We don't have any waiting selections or disconnected SCBs. + */ + aic_outb(p, SCB_LIST_NULL, WAITING_SCBH); + aic_outb(p, SCB_LIST_NULL, DISCONNECTED_SCBH); + + /* + * Message out buffer starts empty + */ + aic_outb(p, MSG_NOOP, MSG_OUT); + aic_outb(p, MSG_NOOP, LAST_MSG); + + /* + * Set all the other asundry items that haven't been set yet. + * This includes just dumping init values to a lot of registers simply + * to make sure they've been touched and are ready for use parity wise + * speaking. + */ + aic_outb(p, 0, TMODE_CMDADDR); + aic_outb(p, 0, TMODE_CMDADDR + 1); + aic_outb(p, 0, TMODE_CMDADDR + 2); + aic_outb(p, 0, TMODE_CMDADDR + 3); + aic_outb(p, 0, TMODE_CMDADDR_NEXT); + + /* + * Link us into the list of valid hosts + */ + p->next = first_aic7xxx; + first_aic7xxx = p; + + /* + * Clear out any possible pending interrupts, again. + */ + aic7xxx_clear_intstat(p); + + /* + * Allocate the first set of scbs for this controller. This is to stream- + * line code elsewhere in the driver. If we have to check for the existence + * of scbs in certain code sections, it slows things down. However, as + * soon as we register the IRQ for this card, we could get an interrupt that + * includes possibly the SCSI_RSTI interrupt. If we catch that interrupt + * then we are likely to segfault if we don't have at least one chunk of + * SCBs allocated or add checks all through the reset code to make sure + * that the SCBs have been allocated which is an invalid running condition + * and therefore I think it's preferable to simply pre-allocate the first + * chunk of SCBs. + */ + aic7xxx_allocate_scb(p); + + /* + * Load the sequencer program, then re-enable the board - + * resetting the AIC-7770 disables it, leaving the lights + * on with nobody home. + */ + aic7xxx_loadseq(p); + + if ( (p->chip & AHC_CHIPID_MASK) == AHC_AIC7770 ) + { + aic_outb(p, ENABLE, BCTL); /* Enable the boards BUS drivers. */ + } + + if ( !(aic7xxx_no_reset) ) + { + if (p->features & AHC_TWIN) + { + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(KERN_INFO "(scsi%d) Resetting channel B\n", p->host_no); + aic_outb(p, aic_inb(p, SBLKCTL) | SELBUSB, SBLKCTL); + aic7xxx_reset_current_bus(p); + aic_outb(p, aic_inb(p, SBLKCTL) & ~SELBUSB, SBLKCTL); + } + /* Reset SCSI bus A. */ + if (aic7xxx_verbose & VERBOSE_PROBE2) + { /* In case we are a 3940, 3985, or 7895, print the right channel */ + char *channel = ""; + if (p->flags & AHC_MULTI_CHANNEL) + { + channel = " A"; + if (p->flags & (AHC_CHNLB|AHC_CHNLC)) + channel = (p->flags & AHC_CHNLB) ? " B" : " C"; + } + printk(KERN_INFO "(scsi%d) Resetting channel%s\n", p->host_no, channel); + } + + /* + * Some of the new Ultra2 chipsets need a longer delay after a chip + * reset than just the init setup creates, so we have to delay here + * before we go into a reset in order to make the chips happy. + */ + if (p->features & AHC_ULTRA2) + mdelay(250); + aic7xxx_reset_current_bus(p); + + /* + * Delay for the reset delay. + */ + if (!reset_delay) + aic7xxx_delay(AIC7XXX_RESET_DELAY); + } + else + { + if (!reset_delay) + { + printk(KERN_INFO "(scsi%d) Not resetting SCSI bus. Note: Don't use " + "the no_reset\n", p->host_no); + printk(KERN_INFO "(scsi%d) option unless you have a verifiable need " + "for it.\n", p->host_no); + printk(KERN_INFO "(scsi%d) The no_reset option is known to break some " + "systems,\n", p->host_no); + printk(KERN_INFO "(scsi%d) and is not supported by the driver author\n", + p->host_no); + aic7xxx_delay(AIC7XXX_RESET_DELAY); + } + } + + /* + * Register IRQ with the kernel. Only allow sharing IRQs with + * PCI devices. + */ + if (!(p->chip & AHC_PCI)) + { + result = (request_irq(p->irq, do_aic7xxx_isr, 0, "aic7xxx", p)); + } + else + { + result = (request_irq(p->irq, do_aic7xxx_isr, SA_SHIRQ, + "aic7xxx", p)); + if (result < 0) + { + result = (request_irq(p->irq, do_aic7xxx_isr, SA_INTERRUPT | SA_SHIRQ, + "aic7xxx", p)); + } + } + if (result < 0) + { + printk(KERN_WARNING "(scsi%d) Couldn't register IRQ %d, ignoring " + "controller.\n", p->host_no, p->irq); + p->irq = 0; + return (0); + } + + unpause_sequencer(p, /* unpause_always */ TRUE); + + return (found); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_chip_reset + * + * Description: + * Perform a chip reset on the aic7xxx SCSI controller. The controller + * is paused upon return. + *-F*************************************************************************/ +int +aic7xxx_chip_reset(struct aic7xxx_host *p) +{ + unsigned char sblkctl; + int wait; + + /* + * For some 274x boards, we must clear the CHIPRST bit and pause + * the sequencer. For some reason, this makes the driver work. + */ + aic_outb(p, PAUSE | CHIPRST, HCNTRL); + + /* + * In the future, we may call this function as a last resort for + * error handling. Let's be nice and not do any unecessary delays. + */ + wait = 1000; /* 1 second (1000 * 1 msec) */ + while (--wait && !(aic_inb(p, HCNTRL) & CHIPRSTACK)) + { + mdelay(1); /* 1 msec */ + } + + pause_sequencer(p); + + sblkctl = aic_inb(p, SBLKCTL) & (SELBUSB|SELWIDE); + if (p->chip & AHC_PCI) + sblkctl &= ~SELBUSB; + switch( sblkctl ) + { + case 0: /* normal narrow card */ + break; + case 2: /* Wide card */ + p->features |= AHC_WIDE; + break; + case 8: /* Twin card */ + p->features |= AHC_TWIN; + p->flags |= AHC_MULTI_CHANNEL; + break; + default: /* hmmm...we don't know what this is */ + printk(KERN_WARNING "aic7xxx: Unsupported adapter type %d, ignoring.\n", + aic_inb(p, SBLKCTL) & 0x0a); + return(-1); + } + return(0); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_alloc + * + * Description: + * Allocate and initialize a host structure. Returns NULL upon error + * and a pointer to a aic7xxx_host struct upon success. + *-F*************************************************************************/ +static struct aic7xxx_host * +aic7xxx_alloc(Scsi_Host_Template *sht, struct aic7xxx_host *temp) +{ + struct aic7xxx_host *p = NULL; + struct Scsi_Host *host; + int i; + + /* + * Allocate a storage area by registering us with the mid-level + * SCSI layer. + */ + host = scsi_register(sht, sizeof(struct aic7xxx_host)); + + if (host != NULL) + { + p = (struct aic7xxx_host *) host->hostdata; + memset(p, 0, sizeof(struct aic7xxx_host)); + *p = *temp; + p->host = host; + + p->scb_data = kmalloc(sizeof(scb_data_type), GFP_ATOMIC); + if (p->scb_data != NULL) + { + memset(p->scb_data, 0, sizeof(scb_data_type)); + scbq_init (&p->scb_data->free_scbs); + } + else + { + /* + * For some reason we don't have enough memory. Free the + * allocated memory for the aic7xxx_host struct, and return NULL. + */ + release_region(p->base, MAXREG - MINREG); + scsi_unregister(host); + return(NULL); + } + p->host_no = host->host_no; + p->tagenable = 0; + p->orderedtag = 0; + for (i=0; i<MAX_TARGETS; i++) + { + p->transinfo[i].goal_period = 0; + p->transinfo[i].goal_offset = 0; + p->transinfo[i].goal_width = MSG_EXT_WDTR_BUS_8_BIT; + } + DRIVER_LOCK_INIT + } + return (p); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_free + * + * Description: + * Frees and releases all resources associated with an instance of + * the driver (struct aic7xxx_host *). + *-F*************************************************************************/ +static void +aic7xxx_free(struct aic7xxx_host *p) +{ + int i; + + /* + * Free the allocated hardware SCB space. + */ + if (p->scb_data != NULL) + { + if (p->scb_data->hscbs != NULL) + { + kfree(p->scb_data->hscb_kmalloc_ptr); + p->scb_data->hscbs = p->scb_data->hscb_kmalloc_ptr = NULL; + } + /* + * Free the driver SCBs. These were allocated on an as-need + * basis. We allocated these in groups depending on how many + * we could fit into a given amount of RAM. The tail SCB for + * these allocations has a pointer to the alloced area. + */ + for (i = 0; i < p->scb_data->numscbs; i++) + { + if (p->scb_data->scb_array[i]->kmalloc_ptr != NULL) + kfree(p->scb_data->scb_array[i]->kmalloc_ptr); + p->scb_data->scb_array[i] = NULL; + } + + /* + * Free the SCB data area. + */ + kfree(p->scb_data); + } + + /* + * Free any alloced Scsi_Cmnd structures that might be around for + * negotiation purposes.... + */ + for (i = 0; i < MAX_TARGETS; i++) + { + if(p->dev_wdtr_cmnd[i]) + kfree(p->dev_wdtr_cmnd[i]); + if(p->dev_sdtr_cmnd[i]) + kfree(p->dev_sdtr_cmnd[i]); + } + +} + +/*+F************************************************************************* + * Function: + * aic7xxx_load_seeprom + * + * Description: + * Load the seeprom and configure adapter and target settings. + * Returns 1 if the load was successful and 0 otherwise. + *-F*************************************************************************/ +static void +aic7xxx_load_seeprom(struct aic7xxx_host *p, unsigned char *sxfrctl1) +{ + int have_seeprom = 0; + int i, max_targets, mask; + unsigned char scsirate, scsi_conf; + unsigned short scarray[128]; + struct seeprom_config *sc = (struct seeprom_config *) scarray; + + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk(KERN_INFO "aic7xxx: Loading serial EEPROM..."); + } + switch (p->chip) + { + case (AHC_AIC7770|AHC_EISA): /* None of these adapters have seeproms. */ + if (aic_inb(p, SCSICONF) & TERM_ENB) + p->flags |= AHC_TERM_ENB_A; + if ( (p->features & AHC_TWIN) && (aic_inb(p, SCSICONF + 1) & TERM_ENB) ) + p->flags |= AHC_TERM_ENB_B; + break; + + case (AHC_AIC7770|AHC_VL): + have_seeprom = read_284x_seeprom(p, (struct seeprom_config *) scarray); + break; + + default: + have_seeprom = read_seeprom(p, (p->flags & (AHC_CHNLB|AHC_CHNLC)), + scarray, p->sc_size, p->sc_type); + if (!have_seeprom) + { + if(p->sc_type == C46) + have_seeprom = read_seeprom(p, (p->flags & (AHC_CHNLB|AHC_CHNLC)), + scarray, p->sc_size, C56_66); + else + have_seeprom = read_seeprom(p, (p->flags & (AHC_CHNLB|AHC_CHNLC)), + scarray, p->sc_size, C46); + } + break; + } + + if (!have_seeprom) + { + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("\naic7xxx: No SEEPROM available.\n"); + } + p->flags |= AHC_NEWEEPROM_FMT; + if (aic_inb(p, SCSISEQ) == 0) + { + p->flags |= AHC_USEDEFAULTS; + p->flags &= ~AHC_BIOS_ENABLED; + p->scsi_id = p->scsi_id_b = 7; + *sxfrctl1 |= STPWEN; + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("aic7xxx: Using default values.\n"); + } + } + else if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("aic7xxx: Using leftover BIOS values.\n"); + } + if ( *sxfrctl1 & STPWEN ) + { + p->flags |= AHC_TERM_ENB_SE_LOW | AHC_TERM_ENB_SE_HIGH; + sc->adapter_control &= ~CFAUTOTERM; + sc->adapter_control |= CFSTERM | CFWSTERM | CFLVDSTERM; + } + p->flags |= AHC_EXTEND_TRANS_A | AHC_EXTEND_TRANS_B; + } + else + { + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("done\n"); + } + + /* + * Note things in our flags + */ + p->flags |= AHC_SEEPROM_FOUND; + + /* + * Update the settings in sxfrctl1 to match the termination settings. + */ + *sxfrctl1 = 0; + + /* + * Get our SCSI ID from the SEEPROM setting... + */ + p->scsi_id = (sc->brtime_id & CFSCSIID); + + /* + * First process the settings that are different between the VLB + * and PCI adapter seeproms. + */ + if ((p->chip & AHC_CHIPID_MASK) == AHC_AIC7770) + { + /* VLB adapter seeproms */ + if (sc->bios_control & CF284XEXTEND) + p->flags |= AHC_EXTEND_TRANS_A; + + if (sc->adapter_control & CF284XSTERM) + { + *sxfrctl1 |= STPWEN; + p->flags |= AHC_TERM_ENB_SE_LOW | AHC_TERM_ENB_SE_HIGH; + } + } + else + { + /* PCI adapter seeproms */ + if (sc->bios_control & CFEXTEND) + p->flags |= AHC_EXTEND_TRANS_A; + if (sc->bios_control & CFBIOSEN) + p->flags |= AHC_BIOS_ENABLED; + else + p->flags &= ~AHC_BIOS_ENABLED; + + if (sc->adapter_control & CFSTERM) + { + *sxfrctl1 |= STPWEN; + p->flags |= AHC_TERM_ENB_SE_LOW | AHC_TERM_ENB_SE_HIGH; + } + } + p->sc = *sc; + } + + p->discenable = 0; + + /* + * Limit to 16 targets just in case. The 2842 for one is known to + * blow the max_targets setting, future cards might also. + */ + max_targets = MIN(sc->max_targets & CFMAXTARG, + ((p->features & (AHC_TWIN | AHC_WIDE)) ? 16 : 8)); + + if (have_seeprom) + { + for (i = 0; i < max_targets; i++) + { + if( ((p->features & AHC_ULTRA) && + !(sc->adapter_control & CFULTRAEN) && + (sc->device_flags[i] & CFSYNCHISULTRA)) || + (sc->device_flags[i] & CFNEWULTRAFORMAT) ) + { + p->flags |= AHC_NEWEEPROM_FMT; + break; + } + } + } + + for (i = 0; i < max_targets; i++) + { + mask = (0x01 << i); + if (!have_seeprom) + { + if(aic_inb(p, SCSISEQ) != 0) + { + /* + * OK...the BIOS set things up and left behind the settings we need. + * Just make our sc->device_flags[i] entry match what the card has + * set for this device. + */ + p->discenable = + ~(aic_inb(p, DISC_DSB) | (aic_inb(p, DISC_DSB + 1) << 8) ); + p->ultraenb = + (aic_inb(p, ULTRA_ENB) | (aic_inb(p, ULTRA_ENB + 1) << 8) ); + sc->device_flags[i] = (p->discenable & mask) ? CFDISC : 0; + if (aic_inb(p, TARG_SCSIRATE + i) & WIDEXFER) + sc->device_flags[i] |= CFWIDEB; + if (p->features & AHC_ULTRA2) + { + if (aic_inb(p, TARG_OFFSET + i)) + { + sc->device_flags[i] |= CFSYNCH; + sc->device_flags[i] |= (aic_inb(p, TARG_SCSIRATE + i) & 0x07); + if ( (aic_inb(p, TARG_SCSIRATE + i) & 0x18) == 0x18 ) + sc->device_flags[i] |= CFSYNCHISULTRA; + } + } + else + { + if (aic_inb(p, TARG_SCSIRATE + i) & ~WIDEXFER) + { + sc->device_flags[i] |= CFSYNCH; + if (p->features & AHC_ULTRA) + sc->device_flags[i] |= ((p->ultraenb & mask) ? + CFSYNCHISULTRA : 0); + } + } + } + else + { + /* + * Assume the BIOS has NOT been run on this card and nothing between + * the card and the devices is configured yet. + */ + sc->device_flags[i] = CFDISC; + if (p->features & AHC_WIDE) + sc->device_flags[i] |= CFWIDEB; + if (p->features & AHC_ULTRA2) + sc->device_flags[i] |= 3; + else if (p->features & AHC_ULTRA) + sc->device_flags[i] |= CFSYNCHISULTRA; + sc->device_flags[i] |= CFSYNCH; + aic_outb(p, 0, TARG_SCSIRATE + i); + if (p->features & AHC_ULTRA2) + aic_outb(p, 0, TARG_OFFSET + i); + } + } + if (sc->device_flags[i] & CFDISC) + { + p->discenable |= mask; + } + if (p->flags & AHC_NEWEEPROM_FMT) + { + if (sc->device_flags[i] & CFSYNCHISULTRA) + { + p->ultraenb |= mask; + } + else if (sc->device_flags[i] & CFNEWULTRAFORMAT) + { + if ( (sc->device_flags[i] & (CFSYNCHISULTRA | CFXFER)) == 0x03 ) + { + sc->device_flags[i] &= ~CFXFER; + sc->device_flags[i] |= CFSYNCHISULTRA; + p->ultraenb |= mask; + } + } + } + else if (sc->adapter_control & CFULTRAEN) + { + p->ultraenb |= mask; + } + if ( (sc->device_flags[i] & CFSYNCH) == 0) + { + sc->device_flags[i] &= ~CFXFER; + p->ultraenb &= ~mask; + p->transinfo[i].user_offset = 0; + p->transinfo[i].user_period = 0; + p->transinfo[i].cur_offset = 0; + p->transinfo[i].cur_period = 0; + p->needsdtr_copy &= ~mask; + } + else + { + if (p->features & AHC_ULTRA2) + { + p->transinfo[i].user_offset = MAX_OFFSET_ULTRA2; + p->transinfo[i].cur_offset = aic_inb(p, TARG_OFFSET + i); + scsirate = (sc->device_flags[i] & CFXFER) | + ((p->ultraenb & mask) ? 0x18 : 0x10); + p->transinfo[i].user_period = aic7xxx_find_period(p, scsirate, + AHC_SYNCRATE_ULTRA2); + p->transinfo[i].cur_period = aic7xxx_find_period(p, + aic_inb(p, TARG_SCSIRATE + i), + AHC_SYNCRATE_ULTRA2); + } + else + { + scsirate = (sc->device_flags[i] & CFXFER) << 4; + if (sc->device_flags[i] & CFWIDEB) + p->transinfo[i].user_offset = MAX_OFFSET_16BIT; + else + p->transinfo[i].user_offset = MAX_OFFSET_8BIT; + if (p->features & AHC_ULTRA) + { + short ultraenb; + ultraenb = aic_inb(p, ULTRA_ENB) | + (aic_inb(p, ULTRA_ENB + 1) << 8); + p->transinfo[i].user_period = aic7xxx_find_period(p, + scsirate, + (p->ultraenb & mask) ? + AHC_SYNCRATE_ULTRA : + AHC_SYNCRATE_FAST); + p->transinfo[i].cur_period = aic7xxx_find_period(p, + aic_inb(p, TARG_SCSIRATE + i), + (ultraenb & mask) ? + AHC_SYNCRATE_ULTRA : + AHC_SYNCRATE_FAST); + } + else + p->transinfo[i].user_period = aic7xxx_find_period(p, + scsirate, AHC_SYNCRATE_FAST); + } + p->needsdtr_copy |= mask; + } + if ( (sc->device_flags[i] & CFWIDEB) && (p->features & AHC_WIDE) ) + { + p->transinfo[i].user_width = MSG_EXT_WDTR_BUS_16_BIT; + p->needwdtr_copy |= mask; + } + else + { + p->transinfo[i].user_width = MSG_EXT_WDTR_BUS_8_BIT; + p->needwdtr_copy &= ~mask; + } + p->transinfo[i].cur_width = + (aic_inb(p, TARG_SCSIRATE + i) & WIDEXFER) ? + MSG_EXT_WDTR_BUS_16_BIT : MSG_EXT_WDTR_BUS_8_BIT; + } + aic_outb(p, ~(p->discenable & 0xFF), DISC_DSB); + aic_outb(p, ~((p->discenable >> 8) & 0xFF), DISC_DSB + 1); + p->needwdtr = p->needwdtr_copy; + p->needsdtr = p->needsdtr_copy; + p->wdtr_pending = p->sdtr_pending = 0; + + /* + * We set the p->ultraenb from the SEEPROM to begin with, but now we make + * it match what is already down in the card. If we are doing a reset + * on the card then this will get put back to a default state anyway. + * This allows us to not have to pre-emptively negotiate when using the + * no_reset option. + */ + if (p->features & AHC_ULTRA) + p->ultraenb = aic_inb(p, ULTRA_ENB) | (aic_inb(p, ULTRA_ENB + 1) << 8); + + + scsi_conf = (p->scsi_id & HSCSIID); + + if(have_seeprom) + { + p->adapter_control = sc->adapter_control; + p->bios_control = sc->bios_control; + + switch (p->chip & AHC_CHIPID_MASK) + { + case AHC_AIC7895: + case AHC_AIC7896: + if (p->adapter_control & CFBPRIMARY) + p->flags |= AHC_CHANNEL_B_PRIMARY; + default: + break; + } + + if (sc->adapter_control & CFSPARITY) + scsi_conf |= ENSPCHK; + } + else + { + scsi_conf |= ENSPCHK | RESET_SCSI; + } + + /* + * Only set the SCSICONF and SCSICONF + 1 registers if we are a PCI card. + * The 2842 and 2742 cards already have these registers set and we don't + * want to muck with them since we don't set all the bits they do. + */ + if ( (p->chip & ~AHC_CHIPID_MASK) == AHC_PCI ) + { + /* Set the host ID */ + aic_outb(p, scsi_conf, SCSICONF); + /* In case we are a wide card */ + aic_outb(p, p->scsi_id, SCSICONF + 1); + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_detect + * + * Description: + * Try to detect and register an Adaptec 7770 or 7870 SCSI controller. + * + * XXX - This should really be called aic7xxx_probe(). A sequence of + * probe(), attach()/detach(), and init() makes more sense than + * one do-it-all function. This may be useful when (and if) the + * mid-level SCSI code is overhauled. + *-F*************************************************************************/ +int +aic7xxx_detect(Scsi_Host_Template *template) +{ + struct aic7xxx_host *temp_p = NULL; + struct aic7xxx_host *current_p = NULL; + struct aic7xxx_host *list_p = NULL; + int found = 0; +#if defined(__i386__) || defined(__alpha__) + ahc_flag_type flags = 0; + int type; +#endif + unsigned char sxfrctl1; +#if defined(__i386__) || defined(__alpha__) + unsigned char hcntrl, hostconf; + unsigned int slot, base; +#endif + +#ifdef MODULE + /* + * If we are called as a module, the aic7xxx pointer may not be null + * and it would point to our bootup string, just like on the lilo + * command line. IF not NULL, then process this config string with + * aic7xxx_setup + */ + if(aic7xxx) + aic7xxx_setup(aic7xxx, NULL); + if(dummy_buffer[0] != 'P') + printk(KERN_WARNING "aic7xxx: Please read the file /usr/src/linux/drivers" + "/scsi/README.aic7xxx\n" + "aic7xxx: to see the proper way to specify options to the aic7xxx " + "module\n" + "aic7xxx: Specifically, don't use any commas when passing arguments to\n" + "aic7xxx: insmod or else it might trash certain memory areas.\n"); +#endif + + template->proc_dir = &proc_scsi_aic7xxx; + template->sg_tablesize = AIC7XXX_MAX_SG; + + +#if defined(__i386__) || defined(__alpha__) + /* + * EISA/VL-bus card signature probe. + */ + slot = MINSLOT; + while ( (slot <= MAXSLOT) && !(aic7xxx_no_probe) ) + { + base = SLOTBASE(slot) + MINREG; + + if (check_region(base, MAXREG - MINREG)) + { + /* + * Some other driver has staked a + * claim to this i/o region already. + */ + slot++; + continue; /* back to the beginning of the for loop */ + } + flags = 0; + type = aic7xxx_probe(slot, base + AHC_HID0, &flags); + if (type == -1) + { + slot++; + continue; + } + temp_p = kmalloc(sizeof(struct aic7xxx_host), GFP_ATOMIC); + if (temp_p == NULL) + { + printk(KERN_WARNING "aic7xxx: Unable to allocate device space.\n"); + slot++; + continue; /* back to the beginning of the while loop */ + } + /* + * Pause the card preserving the IRQ type. Allow the operator + * to override the IRQ trigger. + */ + if (aic7xxx_irq_trigger == 1) + hcntrl = IRQMS; /* Level */ + else if (aic7xxx_irq_trigger == 0) + hcntrl = 0; /* Edge */ + else + hcntrl = inb(base + HCNTRL) & IRQMS; /* Default */ + memset(temp_p, 0, sizeof(struct aic7xxx_host)); + temp_p->unpause = hcntrl | INTEN; + temp_p->pause = hcntrl | PAUSE | INTEN; + temp_p->base = base; + temp_p->mbase = 0; + temp_p->maddr = 0; + temp_p->pci_bus = 0; + temp_p->pci_device_fn = slot; + aic_outb(temp_p, hcntrl | PAUSE, HCNTRL); + while( (aic_inb(temp_p, HCNTRL) & PAUSE) == 0 ) ; + if (aic7xxx_chip_reset(temp_p) == -1) + temp_p->irq = 0; + else + temp_p->irq = aic_inb(temp_p, INTDEF) & 0x0F; + temp_p->flags |= AHC_PAGESCBS; + + switch (temp_p->irq) + { + case 9: + case 10: + case 11: + case 12: + case 14: + case 15: + break; + + default: + printk(KERN_WARNING "aic7xxx: Host adapter uses unsupported IRQ " + "level %d, ignoring.\n", temp_p->irq); + kfree(temp_p); + slot++; + continue; /* back to the beginning of the while loop */ + } + + /* + * We are commited now, everything has been checked and this card + * has been found, now we just set it up + */ + + /* + * Insert our new struct into the list at the end + */ + if (list_p == NULL) + { + list_p = current_p = temp_p; + } + else + { + current_p = list_p; + while (current_p->next != NULL) + current_p = current_p->next; + current_p->next = temp_p; + } + if (aic7xxx_extended) + { + temp_p->flags |= AHC_EXTEND_TRANS_A; + if (temp_p->flags & AHC_MULTI_CHANNEL) + temp_p->flags |= AHC_EXTEND_TRANS_B; + } + + switch (type) + { + case 0: + temp_p->board_name_index = 2; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk("aic7xxx: <%s> at EISA %d\n", + board_names[2], slot); + /* FALLTHROUGH */ + case 1: + { + temp_p->chip = AHC_AIC7770 | AHC_EISA; + temp_p->features |= AHC_AIC7770_FE; + temp_p->bios_control = aic_inb(temp_p, HA_274_BIOSCTRL); + + /* + * Get the primary channel information. Right now we don't + * do anything with this, but someday we will be able to inform + * the mid-level SCSI code which channel is primary. + */ + if (temp_p->board_name_index == 0) + { + temp_p->board_name_index = 3; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk("aic7xxx: <%s> at EISA %d\n", + board_names[3], slot); + } + if (temp_p->bios_control & CHANNEL_B_PRIMARY) + { + temp_p->flags |= AHC_CHANNEL_B_PRIMARY; + } + + if ((temp_p->bios_control & BIOSMODE) == BIOSDISABLED) + { + temp_p->flags &= ~AHC_BIOS_ENABLED; + } + else + { + temp_p->flags &= ~AHC_USEDEFAULTS; + temp_p->flags |= AHC_BIOS_ENABLED; + if ( (temp_p->bios_control & 0x20) == 0 ) + { + temp_p->bios_address = 0xcc000; + temp_p->bios_address += (0x4000 * (temp_p->bios_control & 0x07)); + } + else + { + temp_p->bios_address = 0xd0000; + temp_p->bios_address += (0x8000 * (temp_p->bios_control & 0x06)); + } + } + temp_p->adapter_control = aic_inb(temp_p, SCSICONF) << 8; + temp_p->adapter_control |= aic_inb(temp_p, SCSICONF + 1); + if (temp_p->features & AHC_WIDE) + { + temp_p->scsi_id = temp_p->adapter_control & HWSCSIID; + temp_p->scsi_id_b = temp_p->scsi_id; + } + else + { + temp_p->scsi_id = (temp_p->adapter_control >> 8) & HSCSIID; + temp_p->scsi_id_b = temp_p->adapter_control & HSCSIID; + } + aic7xxx_load_seeprom(temp_p, &sxfrctl1); + break; + } + + case 2: + case 3: + temp_p->chip = AHC_AIC7770 | AHC_VL; + temp_p->features |= AHC_AIC7770_FE; + if (type == 2) + temp_p->flags |= AHC_BIOS_ENABLED; + else + temp_p->flags &= ~AHC_BIOS_ENABLED; + if (aic_inb(temp_p, SCSICONF) & TERM_ENB) + sxfrctl1 = STPWEN; + aic7xxx_load_seeprom(temp_p, &sxfrctl1); + temp_p->board_name_index = 4; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk("aic7xxx: <%s> at VLB %d\n", + board_names[2], slot); + switch( aic_inb(temp_p, STATUS_2840) & BIOS_SEL ) + { + case 0x00: + temp_p->bios_address = 0xe0000; + break; + case 0x20: + temp_p->bios_address = 0xc8000; + break; + case 0x40: + temp_p->bios_address = 0xd0000; + break; + case 0x60: + temp_p->bios_address = 0xd8000; + break; + default: + break; /* can't get here */ + } + break; + + default: /* Won't get here. */ + break; + } + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk(KERN_INFO "aic7xxx: BIOS %sabled, IO Port 0x%lx, IRQ %d (%s)\n", + (temp_p->flags & AHC_USEDEFAULTS) ? "dis" : "en", temp_p->base, + temp_p->irq, + (temp_p->pause & IRQMS) ? "level sensitive" : "edge triggered"); + printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", + (temp_p->flags & AHC_EXTEND_TRANS_A) ? "en" : "dis"); + } + + /* + * Set the FIFO threshold and the bus off time. + */ + hostconf = aic_inb(temp_p, HOSTCONF); + aic_outb(temp_p, hostconf & DFTHRSH, BUSSPD); + aic_outb(temp_p, (hostconf << 2) & BOFF, BUSTIME); + slot++; + found++; + } + +#endif /* defined(__i386__) || defined(__alpha__) */ + +#ifdef CONFIG_PCI + /* + * PCI-bus probe. + */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + if (pci_present()) +#else + if (pcibios_present()) +#endif + { + struct + { + unsigned short vendor_id; + unsigned short device_id; + ahc_chip chip; + ahc_flag_type flags; + ahc_feature features; + int board_name_index; + unsigned short seeprom_size; + unsigned short seeprom_type; + } const aic_pdevs[] = { + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7810, AHC_NONE, + AHC_FNONE, AHC_FENONE, 1, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7850, AHC_AIC7850, + AHC_PAGESCBS, AHC_AIC7850_FE, 5, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7855, AHC_AIC7850, + AHC_PAGESCBS, AHC_AIC7850_FE, 6, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7860, AHC_AIC7860, + AHC_PAGESCBS | AHC_NEWEEPROM_FMT | AHC_BIOS_ENABLED, + AHC_AIC7860_FE, 7, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7861, AHC_AIC7860, + AHC_PAGESCBS | AHC_NEWEEPROM_FMT | AHC_BIOS_ENABLED, + AHC_AIC7860_FE, 8, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7870, AHC_AIC7870, + AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7870_FE, 9, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7871, AHC_AIC7870, + AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7870_FE, 10, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7872, AHC_AIC7870, + AHC_PAGESCBS | AHC_BIOS_ENABLED | AHC_MULTI_CHANNEL, + AHC_AIC7870_FE, 11, + 32, C56_66 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7873, AHC_AIC7870, + AHC_PAGESCBS | AHC_BIOS_ENABLED | AHC_MULTI_CHANNEL, + AHC_AIC7870_FE, 12, + 32, C56_66 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7874, AHC_AIC7870, + AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7870_FE, 13, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7880, AHC_AIC7880, + AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7880_FE, 14, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7881, AHC_AIC7880, + AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7880_FE, 15, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7882, AHC_AIC7880, + AHC_PAGESCBS | AHC_BIOS_ENABLED | AHC_MULTI_CHANNEL, + AHC_AIC7880_FE, 16, + 32, C56_66 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7883, AHC_AIC7880, + AHC_PAGESCBS | AHC_BIOS_ENABLED | AHC_MULTI_CHANNEL, + AHC_AIC7880_FE, 17, + 32, C56_66 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7884, AHC_AIC7880, + AHC_PAGESCBS | AHC_BIOS_ENABLED, AHC_AIC7880_FE, 18, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7895, AHC_AIC7895, + AHC_PAGESCBS | AHC_NEWEEPROM_FMT | AHC_BIOS_ENABLED | AHC_MULTI_CHANNEL, + AHC_AIC7895_FE, 19, + 32, C56_66 }, + {PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_7890, AHC_AIC7890, + AHC_PAGESCBS | AHC_NEWEEPROM_FMT | AHC_BIOS_ENABLED, + AHC_AIC7890_FE, 20, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_2940U2, AHC_AIC7890, + AHC_PAGESCBS | AHC_NEWEEPROM_FMT | AHC_BIOS_ENABLED, + AHC_AIC7890_FE, 21, + 32, C46 }, + {PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_7896, AHC_AIC7896, + AHC_PAGESCBS | AHC_NEWEEPROM_FMT | AHC_BIOS_ENABLED | AHC_MULTI_CHANNEL, + AHC_AIC7896_FE, 22, + 32, C56_66 }, + {PCI_VENDOR_ID_ADAPTEC2, PCI_DEVICE_ID_ADAPTEC2_3940U2, AHC_AIC7896, + AHC_PAGESCBS | AHC_NEWEEPROM_FMT | AHC_BIOS_ENABLED | AHC_MULTI_CHANNEL, + AHC_AIC7896_FE, 23, + 32, C56_66 }, + }; + + unsigned short command; + unsigned int devconfig, i, oldverbose; +#ifdef MMAPIO + unsigned long page_offset, base; +#endif +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + struct pci_dev *pdev = NULL; +#else + int index; + unsigned int piobase, mmapbase; + unsigned char pci_bus, pci_devfn, pci_irq; +#endif + + for (i = 0; i < NUMBER(aic_pdevs); i++) + { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + pdev = NULL; + while ((pdev = pci_find_device(aic_pdevs[i].vendor_id, + aic_pdevs[i].device_id, + pdev))) +#else + index = 0; + while (!(pcibios_find_device(aic_pdevs[i].vendor_id, + aic_pdevs[i].device_id, + index++, &pci_bus, &pci_devfn)) ) +#endif + { + if ( i == 0 ) /* We found one, but it's the 7810 RAID cont. */ + { + if (aic7xxx_verbose & (VERBOSE_PROBE|VERBOSE_PROBE2)) + { + printk(KERN_INFO "aic7xxx: The 7810 RAID controller is not " + "supported by\n"); + printk(KERN_INFO " this driver, we are ignoring it.\n"); + } + } + else if ( (temp_p = kmalloc(sizeof(struct aic7xxx_host), + GFP_ATOMIC)) != NULL ) + { + memset(temp_p, 0, sizeof(struct aic7xxx_host)); + temp_p->chip = aic_pdevs[i].chip | AHC_PCI; + temp_p->flags = aic_pdevs[i].flags; + temp_p->features = aic_pdevs[i].features; + temp_p->board_name_index = aic_pdevs[i].board_name_index; + temp_p->sc_size = aic_pdevs[i].seeprom_size; + temp_p->sc_type = aic_pdevs[i].seeprom_type; + + /* + * Read sundry information from PCI BIOS. + */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + temp_p->irq = pdev->irq; + temp_p->pdev = pdev; + temp_p->pci_bus = pdev->bus->number; + temp_p->pci_device_fn = pdev->devfn; + temp_p->base = pdev->base_address[0]; + temp_p->mbase = pdev->base_address[1]; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk("aic7xxx: <%s> at PCI %d/%d\n", + board_names[aic_pdevs[i].board_name_index], + PCI_SLOT(temp_p->pdev->devfn), + PCI_FUNC(temp_p->pdev->devfn)); + pci_read_config_word(pdev, PCI_COMMAND, &command); + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("aic7xxx: Initial PCI_COMMAND value was 0x%x\n", + (int)command); + } +#ifdef AIC7XXX_STRICT_PCI_SETUP + command |= PCI_COMMAND_SERR | PCI_COMMAND_PARITY | + PCI_COMMAND_INVALIDATE | PCI_COMMAND_MASTER | + PCI_COMMAND_MEMORY | PCI_COMMAND_IO; +#else + command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO; +#endif + if (aic7xxx_pci_parity == 0) + command &= ~(PCI_COMMAND_SERR | PCI_COMMAND_PARITY); + pci_write_config_word(pdev, PCI_COMMAND, command); +#ifdef AIC7XXX_STRICT_PCI_SETUP + pci_read_config_dword(pdev, DEVCONFIG, &devconfig); + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("aic7xxx: Initial DEVCONFIG value was 0x%x\n", devconfig); + } + devconfig |= 0x80000000; + if ((aic7xxx_pci_parity == 0) || (aic7xxx_pci_parity == -1)) + { + devconfig &= ~(0x00000008); + } + else + { + devconfig |= 0x00000008; + } + pci_write_config_dword(pdev, DEVCONFIG, devconfig); +#endif /* AIC7XXX_STRICT_PCI_SETUP */ +#else /* LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) */ + temp_p->pci_bus = pci_bus; + temp_p->pci_device_fn = pci_devfn; + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk("aic7xxx: <%s> at PCI %d/%d\n", + board_names[aic_pdevs[i].board_name_index], + PCI_SLOT(temp_p->pci_device_fn), + PCI_FUNC(temp_p->pci_device_fn)); + pcibios_read_config_byte(pci_bus, pci_devfn, PCI_INTERRUPT_LINE, + &pci_irq); + temp_p->irq = pci_irq; + pcibios_read_config_dword(pci_bus, pci_devfn, PCI_BASE_ADDRESS_0, + &piobase); + temp_p->base = piobase; + pcibios_read_config_dword(pci_bus, pci_devfn, PCI_BASE_ADDRESS_1, + &mmapbase); + temp_p->mbase = mmapbase; + pcibios_read_config_word(pci_bus, pci_devfn, PCI_COMMAND, &command); + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("aic7xxx: Initial PCI_COMMAND value was 0x%x\n", + (int)command); + } +#ifdef AIC7XXX_STRICT_PCI_SETUP + command |= PCI_COMMAND_SERR | PCI_COMMAND_PARITY | + PCI_COMMAND_INVALIDATE | PCI_COMMAND_MASTER | + PCI_COMMAND_MEMORY | PCI_COMMAND_IO; +#else + command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO; +#endif + if (aic7xxx_pci_parity == 0) + command &= ~(PCI_COMMAND_SERR | PCI_COMMAND_PARITY); + pcibios_write_config_word(pci_bus, pci_devfn, PCI_COMMAND, command); +#ifdef AIC7XXX_STRICT_PCI_SETUP + pcibios_read_config_dword(pci_bus, pci_devfn, DEVCONFIG, &devconfig); + if (aic7xxx_verbose & VERBOSE_PROBE2) + { + printk("aic7xxx: Initial DEVCONFIG value was 0x%x\n", devconfig); + } + devconfig |= 0x80000000; + if ((aic7xxx_pci_parity == 0) || (aic7xxx_pci_parity == -1)) + { + devconfig &= ~(0x00000008); + } + else + { + devconfig |= 0x00000008; + } + pcibios_write_config_dword(pci_bus, pci_devfn, DEVCONFIG, devconfig); +#endif /* AIC7XXX_STRICT_PCI_SETUP */ +#endif /* LINUIX_VERSION_CODE > KERNEL_VERSION(2,1,92) */ + + /* + * The first bit (LSB) of PCI_BASE_ADDRESS_0 is always set, so + * we mask it off. + */ + temp_p->base &= PCI_BASE_ADDRESS_IO_MASK; + temp_p->mbase &= PCI_BASE_ADDRESS_MEM_MASK; + temp_p->unpause = INTEN; + temp_p->pause = temp_p->unpause | PAUSE; + +#ifdef MMAPIO + base = temp_p->mbase & PAGE_MASK; + page_offset = temp_p->mbase - base; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,0) + temp_p->maddr = ioremap_nocache(base, page_offset + 256); +#else + temp_p->maddr = vremap(base, page_offset + 256); +#endif + if(temp_p->maddr) + { + temp_p->maddr += page_offset; + } +#endif + + pause_sequencer(temp_p); + + /* + * Clear out any pending PCI error status messages. Also set + * verbose to 0 so that we don't emit strange PCI error messages + * while cleaning out the current status bits. + */ + oldverbose = aic7xxx_verbose; + aic7xxx_verbose = 0; + aic7xxx_pci_intr(temp_p); + aic7xxx_verbose = oldverbose; + + temp_p->bios_address = 0; + + /* + * Remember how the card was setup in case there is no seeprom. + */ + if (temp_p->features & AHC_ULTRA2) + temp_p->scsi_id = aic_inb(temp_p, SCSIID_ULTRA2) & OID; + else + temp_p->scsi_id = aic_inb(temp_p, SCSIID) & OID; + /* + * Get current termination setting + */ + sxfrctl1 = aic_inb(temp_p, SXFRCTL1) & STPWEN; + + if (aic7xxx_chip_reset(temp_p) == -1) + { + kfree(temp_p); + temp_p = NULL; + continue; + } + + /* + * Doing a switch based upon i is really gross, but since Justin + * changed around the chip ID stuff, we can't use that any more. + * Since we don't scan the devices the same way as FreeBSD, we end + * up doing this gross hack in order to avoid totally splitting + * away from Justin's init code in ahc_pci.c + */ + switch (i) + { + case 7: /* 3940 */ + case 12: /* 3940-Ultra */ + switch(PCI_SLOT(temp_p->pci_device_fn)) + { + case 5: + temp_p->flags |= AHC_CHNLB; + break; + default: + break; + } + break; + + case 8: /* 3985 */ + case 13: /* 3985-Ultra */ + switch(PCI_SLOT(temp_p->pci_device_fn)) + { + case 8: + temp_p->flags |= AHC_CHNLB; + break; + case 12: + temp_p->flags |= AHC_CHNLC; + break; + default: + break; + } + break; + + case 15: + case 18: + case 19: +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + if (PCI_FUNC(temp_p->pdev->devfn) != 0) + { + temp_p->flags |= AHC_CHNLB; + } + /* + * The 7895 is the only chipset that sets the SCBSIZE32 param + * in the DEVCONFIG register. The Ultra2 chipsets use + * the DSCOMMAND0 register instead. + */ + if ((temp_p->chip & AHC_CHIPID_MASK) == AHC_AIC7895) + { + pci_read_config_dword(pdev, DEVCONFIG, &devconfig); + devconfig |= SCBSIZE32; + pci_write_config_dword(pdev, DEVCONFIG, devconfig); + } +#else + if (PCI_FUNC(temp_p->pci_device_fn) != 0) + { + temp_p->flags |= AHC_CHNLB; + } + /* + * The 7895 is the only chipset that sets the SCBSIZE32 param + * in the DEVCONFIG register. The Ultra2 chipsets use + * the DSCOMMAND0 register instead. + */ + if ((temp_p->chip & AHC_CHIPID_MASK) == AHC_AIC7895) + { + pcibios_read_config_dword(pci_bus, pci_devfn, DEVCONFIG, + &devconfig); + devconfig |= SCBSIZE32; + pcibios_write_config_dword(pci_bus, pci_devfn, DEVCONFIG, + devconfig); + } +#endif + break; + default: + break; + } + + /* + * Loading of the SEEPROM needs to come after we've set the flags + * to indicate possible CHNLB and CHNLC assigments. Otherwise, + * on 394x and 398x cards we'll end up reading the wrong settings + * for channels B and C + */ + switch (temp_p->chip & AHC_CHIPID_MASK) + { + case AHC_AIC7890: + case AHC_AIC7896: + aic_outb(temp_p, 0, SCAMCTL); + /* + * We used to set DPARCKEN in this register, but after talking + * to a tech from Adaptec, I found out they don't use that + * particular bit in their own register settings, and when you + * combine that with the fact that I determined that we were + * seeing Data-Path Parity Errors on things we shouldn't see + * them on, I think there is a bug in the silicon and the way + * to work around it is to disable this particular check. Also + * This bug only showed up on certain commands, so it seems to + * be pattern related or some such. The commands we would + * typically send as a linux TEST_UNIT_READY or INQUIRY command + * could cause it to be triggered, while regular commands that + * actually made reasonable use of the SG array capabilities + * seemed not to cause the problem. + */ + /* + aic_outb(temp_p, aic_inb(temp_p, DSCOMMAND0) | + CACHETHEN | DPARCKEN | MPARCKEN | + USCBSIZE32 | CIOPARCKEN, + DSCOMMAND0); + */ + aic_outb(temp_p, (aic_inb(temp_p, DSCOMMAND0) | + CACHETHEN | MPARCKEN | USCBSIZE32 | + CIOPARCKEN) & ~DPARCKEN, DSCOMMAND0); + /* FALLTHROUGH */ + default: + /* + * We attempt to read a SEEPROM on *everything*. If we fail, + * then we fail, but this covers things like 2910c cards that + * now have SEEPROMs with their 7856 chipset that we would + * otherwise ignore. They still don't have a BIOS, but they + * have a SEEPROM that the SCSISelect utility on the Adaptec + * diskettes can configure. + */ + aic7xxx_load_seeprom(temp_p, &sxfrctl1); + break; + case AHC_AIC7850: + case AHC_AIC7860: + /* + * Set the DSCOMMAND0 register on these cards different from + * on the 789x cards. Also, read the SEEPROM as well. + */ + aic_outb(temp_p, (aic_inb(temp_p, DSCOMMAND0) | + CACHETHEN | MPARCKEN) & ~DPARCKEN, + DSCOMMAND0); + aic7xxx_load_seeprom(temp_p, &sxfrctl1); + break; + case AHC_AIC7880: + /* + * Only set the DSCOMMAND0 register if this is a Rev B. + * chipset. For those, we also enable Ultra mode by + * force due to brain-damage on the part of some BIOSes + * We overload the devconfig variable here since we can. + */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + pci_read_config_dword(pdev, DEVCONFIG, &devconfig); +#else + pcibios_read_config_dword(pci_bus, pci_devfn, DEVCONFIG, + &devconfig); +#endif + if ((devconfig & 0xff) >= 1) + { + aic_outb(temp_p, (aic_inb(temp_p, DSCOMMAND0) | + CACHETHEN | MPARCKEN) & ~DPARCKEN, + DSCOMMAND0); + } + aic7xxx_load_seeprom(temp_p, &sxfrctl1); + break; + } + + + /* + * and then we need another switch based on the type in order to + * make sure the channel B primary flag is set properly on 7895 + * controllers....Arrrgggghhh!!! We also have to catch the fact + * that when you disable the BIOS on the 7895 on the Intel DK440LX + * motherboard, and possibly others, it only sets the BIOS disabled + * bit on the A channel...I think I'm starting to lean towards + * going postal.... + */ + switch(temp_p->chip & AHC_CHIPID_MASK) + { + case AHC_AIC7895: + case AHC_AIC7896: + current_p = list_p; + while(current_p != NULL) + { + if ( (current_p->pci_bus == temp_p->pci_bus) && + (PCI_SLOT(current_p->pci_device_fn) == + PCI_SLOT(temp_p->pci_device_fn)) ) + { + if ( PCI_FUNC(current_p->pci_device_fn) == 0 ) + { + temp_p->flags |= + (current_p->flags & AHC_CHANNEL_B_PRIMARY); + temp_p->flags &= ~(AHC_BIOS_ENABLED|AHC_USEDEFAULTS); + temp_p->flags |= + (current_p->flags & (AHC_BIOS_ENABLED|AHC_USEDEFAULTS)); + } + else + { + current_p->flags |= + (temp_p->flags & AHC_CHANNEL_B_PRIMARY); + current_p->flags &= ~(AHC_BIOS_ENABLED|AHC_USEDEFAULTS); + current_p->flags |= + (temp_p->flags & (AHC_BIOS_ENABLED|AHC_USEDEFAULTS)); + } + } + current_p = current_p->next; + } + break; + default: + break; + } + + /* + * We do another switch based on i so that we can exclude all + * 3895 devices from the next option since the 3895 cards use + * shared external SCB RAM while all other cards have dedicated + * external SCB RAM per channel. Also exclude the 7850 and + * 7860 based stuff since they can have garbage in the bit + * that indicates external RAM and get some of this stuff + * wrong as a result. + */ + switch(temp_p->chip & AHC_CHIPID_MASK) + { + default: + break; + case AHC_AIC7895: + case AHC_AIC7896: +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + pci_read_config_dword(pdev, DEVCONFIG, &devconfig); +#else + pcibios_read_config_dword(pci_bus, pci_devfn, DEVCONFIG, + &devconfig); +#endif + if (temp_p->features & AHC_ULTRA2) + { + if (aic_inb(temp_p, DSCOMMAND0) & RAMPSM_ULTRA2) + { + aic_outb(temp_p, + aic_inb(temp_p, DSCOMMAND0) & ~SCBRAMSEL_ULTRA2, + DSCOMMAND0); + temp_p->flags |= AHC_EXTERNAL_SRAM; + devconfig |= EXTSCBPEN; + } + } + else if (devconfig & RAMPSM) + { + devconfig &= ~SCBRAMSEL; + devconfig |= EXTSCBPEN; + temp_p->flags |= AHC_EXTERNAL_SRAM; + } +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + pci_write_config_dword(pdev, DEVCONFIG, devconfig); +#else + pcibios_write_config_dword(pci_bus, pci_devfn, DEVCONFIG, + devconfig); +#endif + if ( (temp_p->flags & AHC_EXTERNAL_SRAM) && + (temp_p->flags & AHC_CHNLB) ) + aic_outb(temp_p, 1, CCSCBBADDR); + break; + } + + /* + * Take the LED out of diagnostic mode + */ + aic_outb(temp_p, + (aic_inb(temp_p, SBLKCTL) & ~(DIAGLEDEN | DIAGLEDON)), + SBLKCTL); + + /* + * We don't know where this is set in the SEEPROM or by the + * BIOS, so we default to 100%. On Ultra2 controllers, use 75% + * instead. + */ + if (temp_p->features & AHC_ULTRA2) + { + aic_outb(temp_p, RD_DFTHRSH_75 | WR_DFTHRSH_75, DFF_THRSH); + } + else + { + aic_outb(temp_p, DFTHRSH_100, DSPCISTATUS); + } + + if (aic7xxx_extended) + temp_p->flags |= AHC_EXTEND_TRANS_A; + + if ( list_p == NULL ) + { + list_p = current_p = temp_p; + } + else + { + current_p = list_p; + while(current_p->next != NULL) + current_p = current_p->next; + current_p->next = temp_p; + } + temp_p->next = NULL; + found++; + } /* Found an Adaptec PCI device. */ + else /* Well, we found one, but we couldn't get any memory */ + { + printk("aic7xxx: Found <%s>\n", + board_names[aic_pdevs[i].board_name_index]); + printk(KERN_INFO "aic7xxx: Unable to allocate device memory, " + "skipping.\n"); + } + } /* while(pdev=....) */ + } /* for PCI_DEVICES */ + } /* PCI BIOS present */ +#endif CONFIG_PCI + /* + * Now, we re-order the probed devices by BIOS address and BUS class. + * In general, we follow this algorithm to make the adapters show up + * in the same order under linux that the computer finds them. + * 1: All VLB/EISA cards with BIOS_ENABLED first, according to BIOS + * address, going from lowest to highest. + * 2: All PCI controllers with BIOS_ENABLED next, according to BIOS + * address, going from lowest to highest. + * 3: Remaining VLB/EISA controllers going in slot order. + * 4: Remaining PCI controllers, going in PCI device order (reversable) + */ + + { + struct aic7xxx_host *sort_list[4] = { NULL, NULL, NULL, NULL }; + struct aic7xxx_host *vlb, *pci; + struct aic7xxx_host *prev_p; + struct aic7xxx_host *p; + unsigned char left; + + prev_p = vlb = pci = NULL; + + temp_p = list_p; + while (temp_p != NULL) + { + switch(temp_p->chip & ~AHC_CHIPID_MASK) + { + case AHC_EISA: + case AHC_VL: + { + p = temp_p; + if (p->flags & AHC_BIOS_ENABLED) + vlb = sort_list[0]; + else + vlb = sort_list[2]; + + if (vlb == NULL) + { + vlb = temp_p; + temp_p = temp_p->next; + vlb->next = NULL; + } + else + { + current_p = vlb; + prev_p = NULL; + while ( (current_p != NULL) && + (current_p->bios_address < temp_p->bios_address)) + { + prev_p = current_p; + current_p = current_p->next; + } + if (prev_p != NULL) + { + prev_p->next = temp_p; + temp_p = temp_p->next; + prev_p->next->next = current_p; + } + else + { + vlb = temp_p; + temp_p = temp_p->next; + vlb->next = current_p; + } + } + + if (p->flags & AHC_BIOS_ENABLED) + sort_list[0] = vlb; + else + sort_list[2] = vlb; + + break; + } + default: /* All PCI controllers fall through to default */ + { + + p = temp_p; + if (p->flags & AHC_BIOS_ENABLED) + pci = sort_list[1]; + else + pci = sort_list[3]; + + if (pci == NULL) + { + pci = temp_p; + temp_p = temp_p->next; + pci->next = NULL; + } + else + { + current_p = pci; + prev_p = NULL; + if (!aic7xxx_reverse_scan) + { + while ( (current_p != NULL) && + ( (PCI_SLOT(current_p->pci_device_fn) | + (current_p->pci_bus << 8)) < + (PCI_SLOT(temp_p->pci_device_fn) | + (temp_p->pci_bus << 8)) ) ) + { + prev_p = current_p; + current_p = current_p->next; + } + } + else + { + while ( (current_p != NULL) && + ( (PCI_SLOT(current_p->pci_device_fn) | + (current_p->pci_bus << 8)) > + (PCI_SLOT(temp_p->pci_device_fn) | + (temp_p->pci_bus << 8)) ) ) + { + prev_p = current_p; + current_p = current_p->next; + } + } + /* + * Are we dealing with a 7985 where we need to sort the + * channels as well, if so, the bios_address values should + * be the same + */ + if ( (current_p) && (temp_p->flags & AHC_MULTI_CHANNEL) && + (temp_p->pci_bus == current_p->pci_bus) && + (PCI_SLOT(temp_p->pci_device_fn) == + PCI_SLOT(current_p->pci_device_fn)) ) + { + if (temp_p->flags & AHC_CHNLB) + { + if ( !(temp_p->flags & AHC_CHANNEL_B_PRIMARY) ) + { + prev_p = current_p; + current_p = current_p->next; + } + } + else + { + if (temp_p->flags & AHC_CHANNEL_B_PRIMARY) + { + prev_p = current_p; + current_p = current_p->next; + } + } + } + if (prev_p != NULL) + { + prev_p->next = temp_p; + temp_p = temp_p->next; + prev_p->next->next = current_p; + } + else + { + pci = temp_p; + temp_p = temp_p->next; + pci->next = current_p; + } + } + + if (p->flags & AHC_BIOS_ENABLED) + sort_list[1] = pci; + else + sort_list[3] = pci; + + break; + } + } /* End of switch(temp_p->type) */ + } /* End of while (temp_p != NULL) */ + /* + * At this point, the cards have been broken into 4 sorted lists, now + * we run through the lists in order and register each controller + */ + { + int i; + + left = found; + for (i=0; i<NUMBER(sort_list); i++) + { + temp_p = sort_list[i]; + while(temp_p != NULL) + { + template->name = board_names[temp_p->board_name_index]; + p = aic7xxx_alloc(template, temp_p); + if (p != NULL) + { + p->instance = found - left; + if (aic7xxx_register(template, p, (--left)) == 0) + { + found--; + aic7xxx_release(p->host); + scsi_unregister(p->host); + } + else if (aic7xxx_dump_card) + { + pause_sequencer(p); + aic7xxx_print_card(p); + aic7xxx_print_scratch_ram(p); + unpause_sequencer(p, TRUE); + } + } + current_p = temp_p; + temp_p = (struct aic7xxx_host *)temp_p->next; + kfree(current_p); + } + } + } + } + return (found); +} + +#ifdef AIC7XXX_FAKE_NEGOTIATION_CMDS + +/*+F************************************************************************* + * Function: + * aic7xxx_negotiation_complete + * + * Description: + * Handle completion events for our Negotiation commands. Clear out the + * struct and get it ready for its next use. + *-F*************************************************************************/ +static void +aic7xxx_negotiation_complete(Scsi_Cmnd *cmd) +{ + return; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_build_negotiation_command + * + * Description: + * Build a Scsi_Cmnd structure to perform negotiation with or else send + * a pre-built command specifically for this purpose. + *-F*************************************************************************/ +static void +aic7xxx_build_negotiation_cmnd(struct aic7xxx_host *p, Scsi_Cmnd *old_cmd, + int tindex) +{ + + if ( (p->needwdtr & (1<<tindex)) && !(p->wdtr_pending & (1<<tindex)) ) + { + if(p->dev_wdtr_cmnd[tindex] == NULL) + { + Scsi_Cmnd *cmd; + + if (!(p->dev_wdtr_cmnd[tindex] = kmalloc(sizeof(Scsi_Cmnd), GFP_ATOMIC)) ) + { + return; + } + cmd = p->dev_wdtr_cmnd[tindex]; + memset(cmd, 0, sizeof(Scsi_Cmnd)); + memcpy(cmd, old_cmd, sizeof(Scsi_Cmnd)); + memset(&cmd->cmnd[0], 0, sizeof(cmd->cmnd)); + memset(&cmd->data_cmnd[0], 0, sizeof(cmd->data_cmnd)); + cmd->lun = 0; + cmd->request_bufflen = 0; + cmd->request_buffer = NULL; + cmd->use_sg = cmd->old_use_sg = cmd->sglist_len = 0; + cmd->bufflen = 0; + cmd->buffer = NULL; + cmd->underflow = 0; + cmd->cmd_len = 6; + } + /* + * Before sending this thing out, we also amke the cmd->next pointer + * point to the real command so we can stuff any possible SENSE data + * intp the real command instead of this fake command. This has to be + * done each time the command is built, not just the first time, hence + * it's outside of the above if()... + */ + p->dev_wdtr_cmnd[tindex]->next = old_cmd; + aic7xxx_queue(p->dev_wdtr_cmnd[tindex], + aic7xxx_negotiation_complete); + } + else if ( (p->needsdtr & (1<<tindex)) && !(p->sdtr_pending & (1<<tindex)) && + !(p->wdtr_pending & (1<<tindex)) ) + { + if(p->dev_sdtr_cmnd[tindex] == NULL) + { + Scsi_Cmnd *cmd; + + if (!(p->dev_sdtr_cmnd[tindex] = kmalloc(sizeof(Scsi_Cmnd), GFP_ATOMIC)) ) + { + return; + } + cmd = p->dev_sdtr_cmnd[tindex]; + memset(cmd, 0, sizeof(Scsi_Cmnd)); + memcpy(cmd, old_cmd, sizeof(Scsi_Cmnd)); + memset(&cmd->cmnd[0], 0, sizeof(cmd->cmnd)); + memset(&cmd->data_cmnd[0], 0, sizeof(cmd->data_cmnd)); + cmd->lun = 0; + cmd->request_bufflen = 0; + cmd->request_buffer = NULL; + cmd->use_sg = cmd->old_use_sg = cmd->sglist_len = 0; + cmd->bufflen = 0; + cmd->buffer = NULL; + cmd->underflow = 0; + cmd->cmd_len = 6; + } + /* + * Before sending this thing out, we also amke the cmd->next pointer + * point to the real command so we can stuff any possible SENSE data + * intp the real command instead of this fake command. This has to be + * done each time the command is built, not just the first time, hence + * it's outside of the above if()... + */ + p->dev_sdtr_cmnd[tindex]->next = old_cmd; + aic7xxx_queue(p->dev_sdtr_cmnd[tindex], + aic7xxx_negotiation_complete); + } +} + +#endif + +#ifdef AIC7XXX_VERBOSE_DEBUGGING +/*+F************************************************************************* + * Function: + * aic7xxx_print_scb + * + * Description: + * Dump the byte codes for an about to be sent SCB. + *-F*************************************************************************/ +static void +aic7xxx_print_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + int i; + unsigned char *x; + + x = (unsigned char *)&scb->hscb->control; + + for(i=0; i<32; i++) + { + printk("%02x ", x[i]); + } + printk("\n"); +} +#endif + +/*+F************************************************************************* + * Function: + * aic7xxx_buildscb + * + * Description: + * Build a SCB. + *-F*************************************************************************/ +static void +aic7xxx_buildscb(struct aic7xxx_host *p, Scsi_Cmnd *cmd, + struct aic7xxx_scb *scb) +{ + unsigned short mask; + struct aic7xxx_hwscb *hscb; + + mask = (0x01 << TARGET_INDEX(cmd)); + hscb = scb->hscb; + + /* + * Setup the control byte if we need negotiation and have not + * already requested it. + */ + hscb->control = 0; + scb->tag_action = 0; + if (p->discenable & mask) + { + hscb->control |= DISCENB; + if (p->tagenable & mask) + { + cmd->tag = hscb->tag; + p->dev_commands_sent[TARGET_INDEX(cmd)]++; + if (p->dev_commands_sent[TARGET_INDEX(cmd)] < 200) + { + hscb->control |= MSG_SIMPLE_Q_TAG; + scb->tag_action = MSG_SIMPLE_Q_TAG; + } + else + { + if (p->orderedtag & mask) + { + hscb->control |= MSG_ORDERED_Q_TAG; + scb->tag_action = MSG_ORDERED_Q_TAG; + } + else + { + hscb->control |= MSG_SIMPLE_Q_TAG; + scb->tag_action = MSG_SIMPLE_Q_TAG; + } + p->dev_commands_sent[TARGET_INDEX(cmd)] = 0; + } + } + } + if (p->dev_flags[TARGET_INDEX(cmd)] & DEVICE_SCANNED) + { +#ifdef AIC7XXX_FAKE_NEGOTIATION_CMDS + if ( (p->needwdtr & mask) && !(p->wdtr_pending & mask) ) + { + if (cmd == p->dev_wdtr_cmnd[TARGET_INDEX(cmd)]) + { + p->wdtr_pending |= mask; + scb->flags |= SCB_MSGOUT_WDTR; + hscb->control &= DISCENB; + hscb->control |= MK_MESSAGE; + scb->tag_action = 0; + } + else + { + aic7xxx_build_negotiation_cmnd(p, cmd, TARGET_INDEX(cmd)); + } + } + else if ( (p->needsdtr & mask) && !(p->sdtr_pending & mask) && + !(p->wdtr_pending & mask) ) + { + if (cmd == p->dev_sdtr_cmnd[TARGET_INDEX(cmd)]) + { + p->sdtr_pending |= mask; + scb->flags |= SCB_MSGOUT_SDTR; + hscb->control &= DISCENB; + hscb->control |= MK_MESSAGE; + scb->tag_action = 0; + } + else if (cmd != p->dev_wdtr_cmnd[TARGET_INDEX(cmd)]) + { + aic7xxx_build_negotiation_cmnd(p, cmd, TARGET_INDEX(cmd)); + } + } +#else + if ( (p->needwdtr & mask) && !(p->wdtr_pending & mask) && + !(p->sdtr_pending & mask) && (cmd->lun == 0) ) + { + p->wdtr_pending |= mask; + scb->flags |= SCB_MSGOUT_WDTR; + hscb->control &= DISCENB; + hscb->control |= MK_MESSAGE; + scb->tag_action = 0; +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Building WDTR command.\n", p->host_no, + CTL_OF_CMD(cmd)); +#endif + } + else if ( (p->needsdtr & mask) && !(p->wdtr_pending & mask) && + !(p->sdtr_pending & mask) && (cmd->lun == 0) ) + { + p->sdtr_pending |= mask; + scb->flags |= SCB_MSGOUT_SDTR; + hscb->control &= DISCENB; + hscb->control |= MK_MESSAGE; + scb->tag_action = 0; +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (aic7xxx_verbose > 0xffff) + printk(INFO_LEAD "Building SDTR command.\n", p->host_no, + CTL_OF_CMD(cmd)); +#endif + } +#endif + } + hscb->target_channel_lun = ((cmd->target << 4) & 0xF0) | + ((cmd->channel & 0x01) << 3) | (cmd->lun & 0x07); + + /* + * The interpretation of request_buffer and request_bufflen + * changes depending on whether or not use_sg is zero; a + * non-zero use_sg indicates the number of elements in the + * scatter-gather array. + */ + + /* + * XXX - this relies on the host data being stored in a + * little-endian format. + */ + hscb->SCSI_cmd_length = cmd->cmd_len; + hscb->SCSI_cmd_pointer = cpu_to_le32(VIRT_TO_BUS(cmd->cmnd)); + + if (cmd->use_sg) + { + struct scatterlist *sg; /* Must be mid-level SCSI code scatterlist */ + + /* + * We must build an SG list in adapter format, as the kernel's SG list + * cannot be used directly because of data field size (__alpha__) + * differences and the kernel SG list uses virtual addresses where + * we need physical addresses. + */ + int i; + + sg = (struct scatterlist *)cmd->request_buffer; + scb->sg_length = 0; + /* + * Copy the segments into the SG array. NOTE!!! - We used to + * have the first entry both in the data_pointer area and the first + * SG element. That has changed somewhat. We still have the first + * entry in both places, but now we download the address of + * scb->sg_list[1] instead of 0 to the sg pointer in the hscb. + */ + for (i = 0; i < cmd->use_sg; i++) + { + scb->sg_list[i].address = cpu_to_le32(VIRT_TO_BUS(sg[i].address)); + scb->sg_list[i].length = cpu_to_le32(sg[i].length); + scb->sg_length += sg[i].length; + } + /* Copy the first SG into the data pointer area. */ + hscb->data_pointer = scb->sg_list[0].address; + hscb->data_count = scb->sg_list[0].length; + scb->sg_count = cmd->use_sg; + hscb->SG_segment_count = cmd->use_sg; + hscb->SG_list_pointer = cpu_to_le32(VIRT_TO_BUS(&scb->sg_list[1])); + + } + else + { + if (cmd->request_bufflen) + { + scb->sg_count = 1; + scb->sg_list[0].address = cpu_to_le32(VIRT_TO_BUS(cmd->request_buffer)); + scb->sg_list[0].length = cpu_to_le32(cmd->request_bufflen); + scb->sg_length = cmd->request_bufflen; + hscb->SG_segment_count = 1; + hscb->SG_list_pointer = cpu_to_le32(VIRT_TO_BUS(&scb->sg_list[0])); + hscb->data_count = scb->sg_list[0].length; + hscb->data_pointer = scb->sg_list[0].address; + } + else + { + scb->sg_count = 0; + scb->sg_length = 0; + hscb->SG_segment_count = 0; + hscb->SG_list_pointer = 0; + hscb->data_count = 0; + hscb->data_pointer = 0; + } + } +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if((cmd->cmnd[0] == TEST_UNIT_READY) && (aic7xxx_verbose & VERBOSE_PROBE2)) + { + aic7xxx_print_scb(p, scb); + } +#endif +} + +/*+F************************************************************************* + * Function: + * aic7xxx_queue + * + * Description: + * Queue a SCB to the controller. + *-F*************************************************************************/ +int +aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *)) +{ + struct aic7xxx_host *p; + struct aic7xxx_scb *scb; +#ifdef AIC7XXX_VERBOSE_DEBUGGING + int tindex = TARGET_INDEX(cmd); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags = 0; +#endif + + p = (struct aic7xxx_host *) cmd->host->hostdata; + /* + * Check to see if channel was scanned. + */ + +#ifdef AIC7XXX_VERBOSE_DEBUGGING + if (!(p->flags & AHC_A_SCANNED) && (cmd->channel == 0)) + { + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(INFO_LEAD "Scanning channel for devices.\n", + p->host_no, 0, -1, -1); + p->flags |= AHC_A_SCANNED; + } + else + { + if (!(p->flags & AHC_B_SCANNED) && (cmd->channel == 1)) + { + if (aic7xxx_verbose & VERBOSE_PROBE2) + printk(INFO_LEAD "Scanning channel for devices.\n", + p->host_no, 1, -1, -1); + p->flags |= AHC_B_SCANNED; + } + } + + if (p->dev_active_cmds[tindex] > (cmd->device->queue_depth + 1)) + { + printk(WARN_LEAD "Commands queued exceeds queue " + "depth, active=%d\n", + p->host_no, CTL_OF_CMD(cmd), + p->dev_active_cmds[tindex]); + if ( p->dev_active_cmds[tindex] > 220 ) + p->dev_active_cmds[tindex] = 0; + } +#endif + + scb = scbq_remove_head(&p->scb_data->free_scbs); + if (scb == NULL) + { + DRIVER_LOCK + aic7xxx_allocate_scb(p); + DRIVER_UNLOCK + scb = scbq_remove_head(&p->scb_data->free_scbs); + } + if (scb == NULL) + { + printk(WARN_LEAD "Couldn't get a free SCB.\n", p->host_no, + CTL_OF_CMD(cmd)); + cmd->result = (DID_BUS_BUSY << 16); + DRIVER_LOCK + aic7xxx_queue_cmd_complete(p, cmd); + DRIVER_UNLOCK + return 0; + } + else + { + scb->cmd = cmd; + aic7xxx_position(cmd) = scb->hscb->tag; + + /* + * Construct the SCB beforehand, so the sequencer is + * paused a minimal amount of time. + */ + aic7xxx_buildscb(p, cmd, scb); + + /* + * Make sure the Scsi_Cmnd pointer is saved, the struct it points to + * is set up properly, and the parity error flag is reset, then send + * the SCB to the sequencer and watch the fun begin. + */ + cmd->scsi_done = fn; + cmd->result = DID_OK; + memset(cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); + aic7xxx_error(cmd) = DID_OK; + aic7xxx_status(cmd) = 0; + cmd->host_scribble = NULL; + + scb->flags |= SCB_ACTIVE | SCB_WAITINGQ; + + DRIVER_LOCK + scbq_insert_tail(&p->waiting_scbs, scb); + if ( (p->flags & (AHC_IN_ISR | AHC_IN_ABORT | AHC_IN_RESET)) == 0) + { + aic7xxx_run_waiting_queues(p); + } + DRIVER_UNLOCK + } + return (0); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_bus_device_reset + * + * Description: + * Abort or reset the current SCSI command(s). If the scb has not + * previously been aborted, then we attempt to send a BUS_DEVICE_RESET + * message to the target. If the scb has previously been unsuccessfully + * aborted, then we will reset the channel and have all devices renegotiate. + * Returns an enumerated type that indicates the status of the operation. + *-F*************************************************************************/ +static int +aic7xxx_bus_device_reset(struct aic7xxx_host *p, Scsi_Cmnd *cmd) +{ + struct aic7xxx_scb *scb; + struct aic7xxx_hwscb *hscb; + int result = -1; + int channel; + unsigned char saved_scbptr, lastphase; + unsigned char hscb_index; + int disconnected; + + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); + hscb = scb->hscb; + + lastphase = aic_inb(p, LASTPHASE); + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + { + printk(INFO_LEAD "Bus Device reset, scb flags 0x%x, ", + p->host_no, CTL_OF_SCB(scb), scb->flags); + switch (lastphase) + { + case P_DATAOUT: + printk("Data-Out phase\n"); + break; + case P_DATAIN: + printk("Data-In phase\n"); + break; + case P_COMMAND: + printk("Command phase\n"); + break; + case P_MESGOUT: + printk("Message-Out phase\n"); + break; + case P_STATUS: + printk("Status phase\n"); + break; + case P_MESGIN: + printk("Message-In phase\n"); + break; + default: + /* + * We're not in a valid phase, so assume we're idle. + */ + printk("while idle, LASTPHASE = 0x%x\n", lastphase); + break; + } + printk(INFO_LEAD "SCSISIGI 0x%x, SEQADDR 0x%x, SSTAT0 0x%x, SSTAT1 " + "0x%x\n", p->host_no, CTL_OF_SCB(scb), + aic_inb(p, SCSISIGI), + aic_inb(p, SEQADDR0) | (aic_inb(p, SEQADDR1) << 8), + aic_inb(p, SSTAT0), aic_inb(p, SSTAT1)); + } + + channel = cmd->channel; + + /* + * Send a Device Reset Message: + * The target that is holding up the bus may not be the same as + * the one that triggered this timeout (different commands have + * different timeout lengths). Our strategy here is to queue an + * abort message to the timed out target if it is disconnected. + * Otherwise, if we have an active target we stuff the message buffer + * with an abort message and assert ATN in the hopes that the target + * will let go of the bus and go to the mesgout phase. If this + * fails, we'll get another timeout a few seconds later which will + * attempt a bus reset. + */ + saved_scbptr = aic_inb(p, SCBPTR); + disconnected = FALSE; + + if (lastphase != P_BUSFREE) + { + if (aic_inb(p, SCB_TAG) >= p->scb_data->numscbs) + { + printk(WARN_LEAD "Invalid SCB ID %d is active, " + "SCB flags = 0x%x.\n", p->host_no, + CTL_OF_CMD(cmd), scb->hscb->tag, scb->flags); + return(SCSI_RESET_ERROR); + } + if (scb->hscb->tag == aic_inb(p, SCB_TAG)) + { + if ( (lastphase != P_MESGOUT) && (lastphase != P_MESGIN) ) + { + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Device reset message in " + "message buffer\n", p->host_no, CTL_OF_SCB(scb)); + scb->flags |= SCB_RESET | SCB_DEVICE_RESET; + aic7xxx_error(scb->cmd) = DID_RESET; + p->dev_flags[TARGET_INDEX(scb->cmd)] &= + ~DEVICE_SUCCESS; + p->dev_flags[TARGET_INDEX(scb->cmd)] |= + BUS_DEVICE_RESET_PENDING; + /* Send the abort message to the active SCB. */ + aic_outb(p, HOST_MSG, MSG_OUT); + aic_outb(p, lastphase | ATNO, SCSISIGO); + return(SCSI_RESET_PENDING); + } + else + { + /* We want to send out the message, but it could screw an already */ + /* in place and being used message. Instead, we return an error */ + /* to try and start the bus reset phase since this command is */ + /* probably hung (aborts failed, and now reset is failing). We */ + /* also make sure to set BUS_DEVICE_RESET_PENDING so we won't try */ + /* any more on this device, but instead will escalate to a bus or */ + /* host reset (additionally, we won't try to abort any more). */ + printk(WARN_LEAD "Device reset, Message buffer " + "in use\n", p->host_no, CTL_OF_SCB(scb)); + scb->flags |= SCB_RESET | SCB_DEVICE_RESET; + aic7xxx_error(scb->cmd) = DID_RESET; + p->dev_flags[TARGET_INDEX(scb->cmd)] &= + ~DEVICE_SUCCESS; + p->dev_flags[TARGET_INDEX(scb->cmd)] |= + BUS_DEVICE_RESET_PENDING; + return(SCSI_RESET_ERROR); + } + } + } /* if (last_phase != P_BUSFREE).....indicates we are idle and can work */ + hscb_index = aic7xxx_find_scb(p, scb); + if (hscb_index == SCB_LIST_NULL) + { + disconnected = (aic7xxx_scb_on_qoutfifo(p, scb)) ? FALSE : TRUE; + } + else + { + aic_outb(p, hscb_index, SCBPTR); + if (aic_inb(p, SCB_CONTROL) & DISCONNECTED) + { + disconnected = TRUE; + } + } + if (disconnected) + { + /* + * Simply set the MK_MESSAGE flag and the SEQINT handler will do + * the rest on a reconnect. + */ + scb->hscb->control |= MK_MESSAGE; + scb->flags |= SCB_RESET | SCB_DEVICE_RESET; + p->dev_flags[TARGET_INDEX(scb->cmd)] &= ~DEVICE_SUCCESS; + p->dev_flags[TARGET_INDEX(scb->cmd)] |= + BUS_DEVICE_RESET_PENDING; + if (hscb_index != SCB_LIST_NULL) + { + unsigned char scb_control; + + aic_outb(p, hscb_index, SCBPTR); + scb_control = aic_inb(p, SCB_CONTROL); + aic_outb(p, scb_control | MK_MESSAGE, SCB_CONTROL); + } + /* + * Actually requeue this SCB in case we can select the + * device before it reconnects. If the transaction we + * want to abort is not tagged, then this will be the only + * outstanding command and we can simply shove it on the + * qoutfifo and be done. If it is tagged, then it goes right + * in with all the others, no problem :) We need to add it + * to the qinfifo and let the sequencer know it is there. + * Now, the only problem left to deal with is, *IF* this + * command completes, in spite of the MK_MESSAGE bit in the + * control byte, then we need to pick that up in the interrupt + * routine and clean things up. This *shouldn't* ever happen. + */ + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Queueing device reset " + "command.\n", p->host_no, CTL_OF_SCB(scb)); + p->qinfifo[p->qinfifonext++] = scb->hscb->tag; + if (p->features & AHC_QUEUE_REGS) + aic_outb(p, p->qinfifonext, HNSCB_QOFF); + else + aic_outb(p, p->qinfifonext, KERNEL_QINPOS); + scb->flags |= SCB_QUEUED_ABORT; + result = SCSI_RESET_PENDING; + } + else if (result == -1) + { + result = SCSI_RESET_ERROR; + } + aic_outb(p, saved_scbptr, SCBPTR); + return (result); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_panic_abort + * + * Description: + * Abort the current SCSI command(s). + *-F*************************************************************************/ +void +aic7xxx_panic_abort(struct aic7xxx_host *p, Scsi_Cmnd *cmd) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0) + int i, mask, found, need_tag; + struct aic7xxx_scb *scb; + unsigned char qinpos, hscbp; + + found = FALSE; +#endif + + printk("aic7xxx driver version %s/%s\n", AIC7XXX_C_VERSION, + UTS_RELEASE); + printk("Controller type:\n %s\n", board_names[p->board_name_index]); + printk("p->flags=0x%x, p->chip=0x%x, p->features=0x%x, " + "sequencer %s paused\n", + p->flags, p->chip, p->features, + (aic_inb(p, HCNTRL) & PAUSE) ? "is" : "isn't" ); + pause_sequencer(p); + disable_irq(p->irq); + aic7xxx_print_card(p); + aic7xxx_print_scratch_ram(p); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0) + for(i=0; i<MAX_TARGETS; i++) + { + if(p->dev_flags[i] & DEVICE_PRESENT) + { + mask = (0x01 << i); + printk(INFO_LEAD "dev_flags=0x%x, WDTR:%c/%c/%c, SDTR:%c/%c/%c," + " q_depth=%d:%d:%d\n", + p->host_no, 0, i, 0, p->dev_flags[i], + (p->wdtr_pending & mask) ? 'Y' : 'N', + (p->needwdtr & mask) ? 'Y' : 'N', + (p->needwdtr_copy & mask) ? 'Y' : 'N', + (p->sdtr_pending & mask) ? 'Y' : 'N', + (p->needsdtr & mask) ? 'Y' : 'N', + (p->needsdtr_copy & mask) ? 'Y' : 'N', + p->dev_active_cmds[i], + p->dev_max_queue_depth[i], p->dev_mid_level_queue_depth[i]); + printk(INFO_LEAD "targ_scsirate=0x%x", p->host_no, 0, i, 0, + aic_inb(p, TARG_SCSIRATE + i)); + if (p->features & AHC_ULTRA2) + printk(", targ_offset=%d", aic_inb(p, TARG_OFFSET + i)); + printk("\n"); + } + } + /* + * Search for this command and see if we can't track it down, it's the + * one causing the timeout. Print out this command first, then all other + * active commands afterwords. + */ + need_tag = -1; + if ( cmd ) + { + scb = p->scb_data->scb_array[aic7xxx_position(cmd)]; + if ( (scb->flags & SCB_ACTIVE) && (scb->cmd == cmd) ) + { + printk("Timed out command is scb #%d:\n", scb->hscb->tag); + printk("Tag%d: flags=0x%x, control=0x%x, TCL=0x%x, %s\n", scb->hscb->tag, + scb->flags, scb->hscb->control, scb->hscb->target_channel_lun, + (scb->flags & SCB_WAITINGQ) ? "WAITINGQ" : "Sent" ); + need_tag = scb->hscb->tag; + if (scb->flags & SCB_WAITINGQ) found=TRUE; + } + } + printk("QINFIFO: (TAG) "); + qinpos = aic_inb(p, QINPOS); + while ( qinpos != p->qinfifonext ) + { + if (p->qinfifo[qinpos] == need_tag) + found=TRUE; + printk("%d ", p->qinfifo[qinpos++]); + } + printk("\n"); + printk("Current SCB: (SCBPTR/TAG/CONTROL) %d/%d/0x%x\n", aic_inb(p, SCBPTR), + aic_inb(p, SCB_TAG), aic_inb(p, SCB_CONTROL) ); + if (aic_inb(p, SCB_TAG) == need_tag) found=TRUE; + printk("WAITING_SCBS: (SCBPTR/TAG/CONTROL) %d->", + hscbp = aic_inb(p, WAITING_SCBH)); + while (hscbp != SCB_LIST_NULL) + { + aic_outb(p, hscbp, SCBPTR); + printk("%d/%d/0x%x ", hscbp, aic_inb(p, SCB_TAG), aic_inb(p, SCB_CONTROL)); + hscbp = aic_inb(p, SCB_NEXT); + if (aic_inb(p, SCB_TAG) == need_tag) found=TRUE; + } + printk("\n"); + printk("DISCONNECTED_SCBS: (SCBPTR/TAG/CONTROL) %d->", + hscbp = aic_inb(p, DISCONNECTED_SCBH)); + while (hscbp != SCB_LIST_NULL) + { + aic_outb(p, hscbp, SCBPTR); + printk("%d/%d/0x%x ", hscbp, aic_inb(p, SCB_TAG), aic_inb(p, SCB_CONTROL)); + hscbp = aic_inb(p, SCB_NEXT); + if (aic_inb(p, SCB_TAG) == need_tag) found=TRUE; + } + printk("\n"); + printk("FREE_SCBS: (SCBPTR/TAG/CONTROL) %d->", + hscbp = aic_inb(p, FREE_SCBH)); + while (hscbp != SCB_LIST_NULL) + { + aic_outb(p, hscbp, SCBPTR); + printk("%d/%d/0x%x ", hscbp, aic_inb(p, SCB_TAG), aic_inb(p, SCB_CONTROL)); + hscbp = aic_inb(p, SCB_NEXT); + } + printk("\n"); + + if (found == FALSE) + { + /* + * We haven't found the offending SCB yet, and it should be around + * somewhere, so go look for it in the cards SCBs. + */ + printk("SCBPTR CONTROL TAG PREV NEXT\n"); + for(i=0; i<p->scb_data->maxhscbs; i++) + { + aic_outb(p, i, SCBPTR); + printk(" %3d %02x %02x %02x %02x\n", i, + aic_inb(p, SCB_CONTROL), aic_inb(p, SCB_TAG), + aic_inb(p, SCB_PREV), aic_inb(p, SCB_NEXT)); + } + } + + + for (i=0; i < p->scb_data->numscbs; i++) + { + scb = p->scb_data->scb_array[i]; + if ( (scb->flags & SCB_ACTIVE) && (scb->cmd != cmd) ) + { + printk("Tag%d: flags=0x%x, control=0x%x, TCL=0x%x, %s\n", scb->hscb->tag, + scb->flags, scb->hscb->control, scb->hscb->target_channel_lun, + (scb->flags & SCB_WAITINGQ) ? "WAITINGQ" : "Sent" ); + } + } +#endif + sti(); + for(;;) barrier(); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_abort + * + * Description: + * Abort the current SCSI command(s). + *-F*************************************************************************/ +int +aic7xxx_abort(Scsi_Cmnd *cmd) +{ + struct aic7xxx_scb *scb = NULL; + struct aic7xxx_host *p; + int result, found=0; + unsigned char tmp_char, saved_hscbptr, next_hscbptr, prev_hscbptr; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags = 0; +#endif + Scsi_Cmnd *cmd_next, *cmd_prev; + + p = (struct aic7xxx_host *) cmd->host->hostdata; + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); + + /* + * I added a new config option to the driver: "panic_on_abort" that will + * cause the driver to panic and the machine to stop on the first abort + * or reset call into the driver. At that point, it prints out a lot of + * usefull information for me which I can then use to try and debug the + * problem. Simply enable the boot time prompt in order to activate this + * code. + */ + if (aic7xxx_panic_on_abort) + aic7xxx_panic_abort(p, cmd); + + DRIVER_LOCK + +/* + * Run the isr to grab any command in the QOUTFIFO and any other misc. + * assundry tasks. This should also set up the bh handler if there is + * anything to be done, but it won't run until we are done here since + * we are following a straight code path without entering the scheduler + * code. + */ + + pause_sequencer(p); + while ( (aic_inb(p, INTSTAT) & INT_PEND) && !(p->flags & AHC_IN_ISR)) + { + aic7xxx_isr(p->irq, p, (void *)NULL); + pause_sequencer(p); + aic7xxx_done_cmds_complete(p); + } + + if ((scb == NULL) || (cmd->serial_number != cmd->serial_number_at_timeout)) + /* Totally bogus cmd since it points beyond our */ + { /* valid SCB range or doesn't even match it's own*/ + /* timeout serial number. */ + if (aic7xxx_verbose & VERBOSE_ABORT_MID) + printk(INFO_LEAD "Abort called with bogus Scsi_Cmnd " + "pointer.\n", p->host_no, CTL_OF_CMD(cmd)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_ABORT_NOT_RUNNING); + } + if (scb->cmd != cmd) /* Hmmm...either this SCB is currently free with a */ + { /* NULL cmd pointer (NULLed out when freed) or it */ + /* has already been recycled for another command */ + /* Either way, this SCB has nothing to do with this*/ + /* command and we need to deal with cmd without */ + /* touching the SCB. */ + /* The theory here is to return a value that will */ + /* make the queued for complete command actually */ + /* finish successfully, or to indicate that we */ + /* don't have this cmd any more and the mid level */ + /* code needs to find it. */ + cmd_next = p->completeq.head; + cmd_prev = NULL; + while (cmd_next != NULL) + { + if (cmd_next == cmd) + { + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "Abort called for command " + "on completeq, completing.\n", p->host_no, CTL_OF_CMD(cmd)); + if ( cmd_prev == NULL ) + p->completeq.head = (Scsi_Cmnd *)cmd_next->host_scribble; + else + cmd_prev->host_scribble = cmd_next->host_scribble; + cmd_next->scsi_done(cmd_next); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_ABORT_NOT_RUNNING); /* It's already back as a successful + * completion */ + } + cmd_prev = cmd_next; + cmd_next = (Scsi_Cmnd *)cmd_next->host_scribble; + } + if (aic7xxx_verbose & VERBOSE_ABORT_MID) + printk(INFO_LEAD "Abort called for already completed" + " command.\n", p->host_no, CTL_OF_CMD(cmd)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_ABORT_NOT_RUNNING); + } + +/* At this point we know the following: + * the SCB pointer is valid + * the command pointer passed in to us and the scb->cmd pointer match + * this then means that the command we need to abort is the same as the + * command held by the scb pointer and is a valid abort request. + * Now, we just have to figure out what to do from here. Current plan is: + * if we have already been here on this command, escalate to a reset + * if scb is on waiting list or QINFIFO, send it back as aborted, but + * we also need to be aware of the possibility that we could be using + * a faked negotiation command that is holding this command up, if + * so we need to take care of that command instead, which means we + * would then treat this one like it was sitting around disconnected + * instead. + * if scb is on WAITING_SCB list in sequencer, free scb and send back + * if scb is disconnected and not completed, abort with abort message + * if scb is currently running, then it may be causing the bus to hang + * so we want a return value that indicates a reset would be appropriate + * if the command does not finish shortly + * if scb is already complete but not on completeq, we're screwed because + * this can't happen (except if the command is in the QOUTFIFO, in which + * case we would like it to complete successfully instead of having to + * to be re-done) + * All other scenarios already dealt with by previous code. + */ + + if ( scb->flags & (SCB_ABORT | SCB_RESET | SCB_QUEUED_ABORT) ) + { + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "SCB aborted once already, " + "escalating.\n", p->host_no, CTL_OF_SCB(scb)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_ABORT_SNOOZE); + } + if ( (p->flags & (AHC_RESET_PENDING | AHC_ABORT_PENDING)) || + (p->dev_flags[TARGET_INDEX(scb->cmd)] & + BUS_DEVICE_RESET_PENDING) ) + { + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "Reset/Abort pending for this " + "device, not wasting our time.\n", p->host_no, CTL_OF_SCB(scb)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_ABORT_PENDING); + } + + found = 0; + p->flags |= AHC_IN_ABORT; + if (aic7xxx_verbose & VERBOSE_ABORT) + printk(INFO_LEAD "Aborting scb %d, flags 0x%x\n", + p->host_no, CTL_OF_SCB(scb), scb->hscb->tag, scb->flags); + +/* + * First, let's check to see if the currently running command is our target + * since if it is, the return is fairly easy and quick since we don't want + * to touch the command in case it might complete, but we do want a timeout + * in case it's actually hung, so we really do nothing, but tell the mid + * level code to reset the timeout. + */ + + if ( scb->hscb->tag == aic_inb(p, SCB_TAG) ) + { + /* + * Check to see if the sequencer is just sitting on this command, or + * if it's actively being run. + */ + result = aic_inb(p, LASTPHASE); + switch (result) + { + case P_DATAOUT: /* For any of these cases, we can assume we are */ + case P_DATAIN: /* an active command and act according. For */ + case P_COMMAND: /* anything else we are going to fall on through*/ + case P_STATUS: /* The SCSI_ABORT_SNOOZE will give us two abort */ + case P_MESGOUT: /* chances to finish and then escalate to a */ + case P_MESGIN: /* reset call */ + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "SCB is currently active. " + "Waiting on completion.\n", p->host_no, CTL_OF_SCB(scb)); + unpause_sequencer(p, FALSE); + p->flags &= ~AHC_IN_ABORT; + scb->flags |= SCB_RECOVERY_SCB; /* Note the fact that we've been */ + p->flags |= AHC_ABORT_PENDING; /* here so we will know not to */ + DRIVER_UNLOCK /* muck with other SCBs if this */ + return(SCSI_ABORT_PENDING); /* one doesn't complete and clear */ + break; /* out. */ + default: + break; + } + } + + if ((found == 0) && (scb->flags & SCB_WAITINGQ)) + { + int tindex = TARGET_INDEX(cmd); +#ifdef AIC7XXX_FAKE_NEGOTIATION_CMDS + unsigned short mask; + + mask = (1 << tindex); + + if (p->wdtr_pending & mask) + { + if (p->dev_wdtr_cmnd[tindex]->next != cmd) + found = 1; + else + found = 0; + } + else if (p->sdtr_pending & mask) + { + if (p->dev_sdtr_cmnd[tindex]->next != cmd) + found = 1; + else + found = 0; + } + else + { + found = 1; + } + if (found == 0) + { + /* + * OK..this means the command we are currently getting an abort + * for has an outstanding negotiation command in front of it. + * We don't really have a way to tie back into the negotiation + * commands, so we just send this back as pending, then it + * will get reset in 2 seconds. + */ + unpause_sequencer(p, TRUE); + scb->flags |= SCB_ABORT; + DRIVER_UNLOCK + return(SCSI_ABORT_PENDING); + } +#endif + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "SCB found on waiting list and " + "aborted.\n", p->host_no, CTL_OF_SCB(scb)); + scbq_remove(&p->waiting_scbs, scb); + scbq_remove(&p->delayed_scbs[tindex], scb); + p->dev_active_cmds[tindex]++; + p->activescbs++; + scb->flags &= ~(SCB_WAITINGQ | SCB_ACTIVE); + scb->flags |= SCB_ABORT | SCB_QUEUED_FOR_DONE; + found = 1; + } + +/* + * We just checked the waiting_q, now for the QINFIFO + */ + if ( found == 0 ) + { + if ( ((found = aic7xxx_search_qinfifo(p, cmd->target, + cmd->channel, + cmd->lun, scb->hscb->tag, SCB_ABORT | SCB_QUEUED_FOR_DONE, + FALSE, NULL)) != 0) && + (aic7xxx_verbose & VERBOSE_ABORT_PROCESS)) + printk(INFO_LEAD "SCB found in QINFIFO and " + "aborted.\n", p->host_no, CTL_OF_SCB(scb)); + } + +/* + * QINFIFO, waitingq, completeq done. Next, check WAITING_SCB list in card + */ + + if ( found == 0 ) + { + unsigned char scb_next_ptr; + prev_hscbptr = SCB_LIST_NULL; + saved_hscbptr = aic_inb(p, SCBPTR); + next_hscbptr = aic_inb(p, WAITING_SCBH); + while ( next_hscbptr != SCB_LIST_NULL ) + { + aic_outb(p, next_hscbptr, SCBPTR ); + if ( scb->hscb->tag == aic_inb(p, SCB_TAG) ) + { + found = 1; + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "SCB found on hardware waiting" + " list and aborted.\n", p->host_no, CTL_OF_SCB(scb)); + if ( prev_hscbptr == SCB_LIST_NULL ) + { + aic_outb(p, aic_inb(p, SCB_NEXT), WAITING_SCBH); + /* stop the selection since we just + * grabbed the scb out from under the + * card + */ + aic_outb(p, aic_inb(p, SCSISEQ) & ~ENSELO, SCSISEQ); + aic_outb(p, CLRSELTIMEO, CLRSINT1); + } + else + { + scb_next_ptr = aic_inb(p, SCB_NEXT); + aic_outb(p, prev_hscbptr, SCBPTR); + aic_outb(p, scb_next_ptr, SCB_NEXT); + aic_outb(p, next_hscbptr, SCBPTR); + } + aic_outb(p, SCB_LIST_NULL, SCB_TAG); + aic_outb(p, 0, SCB_CONTROL); + aic7xxx_add_curscb_to_free_list(p); + scb->flags = SCB_ABORT | SCB_QUEUED_FOR_DONE; + break; + } + prev_hscbptr = next_hscbptr; + next_hscbptr = aic_inb(p, SCB_NEXT); + } + aic_outb(p, saved_hscbptr, SCBPTR ); + } + +/* + * Hmmm...completeq, QOUTFIFO, QINFIFO, WAITING_SCBH, waitingq all checked. + * OK...the sequencer's paused, interrupts are off, and we haven't found the + * command anyplace where it could be easily aborted. Time for the hard + * work. We also know the command is valid. This essentially means the + * command is disconnected, or connected but not into any phases yet, which + * we know due to the tests we ran earlier on the current active scb phase. + * At this point we can queue the abort tag and go on with life. + */ + + if ( found == 0 ) + { + p->flags |= AHC_ABORT_PENDING; + scb->flags |= SCB_QUEUED_ABORT | SCB_ABORT | SCB_RECOVERY_SCB; + scb->hscb->control |= MK_MESSAGE; + result=aic7xxx_find_scb(p, scb); + if ( result != SCB_LIST_NULL ) + { + saved_hscbptr = aic_inb(p, SCBPTR); + aic_outb(p, result, SCBPTR); + tmp_char = aic_inb(p, SCB_CONTROL); + aic_outb(p, tmp_char | MK_MESSAGE, SCB_CONTROL); + aic_outb(p, saved_hscbptr, SCBPTR); + } + if (aic7xxx_verbose & VERBOSE_ABORT_PROCESS) + printk(INFO_LEAD "SCB disconnected. Queueing Abort" + " SCB.\n", p->host_no, CTL_OF_SCB(scb)); + p->qinfifo[p->qinfifonext++] = scb->hscb->tag; + if (p->features & AHC_QUEUE_REGS) + aic_outb(p, p->qinfifonext, HNSCB_QOFF); + else + aic_outb(p, p->qinfifonext, KERNEL_QINPOS); + } + if (found) + { + aic7xxx_run_done_queue(p, TRUE); + aic7xxx_run_waiting_queues(p); + } + p->flags &= ~AHC_IN_ABORT; + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + +/* + * On the return value. If we found the command and aborted it, then we know + * it's already sent back and there is no reason for a further timeout, so + * we use SCSI_ABORT_SUCCESS. On the queued abort side, we aren't so certain + * there hasn't been a bus hang or something that might keep the abort from + * from completing. Therefore, we use SCSI_ABORT_PENDING. The first time this + * is passed back, the timeout on the command gets extended, the second time + * we pass this back, the mid level SCSI code calls our reset function, which + * would shake loose a hung bus. + */ + if ( found != 0 ) + return(SCSI_ABORT_SUCCESS); + else + return(SCSI_ABORT_PENDING); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_reset + * + * Description: + * Resetting the bus always succeeds - is has to, otherwise the + * kernel will panic! Try a surgical technique - sending a BUS + * DEVICE RESET message - on the offending target before pulling + * the SCSI bus reset line. + *-F*************************************************************************/ +int +aic7xxx_reset(Scsi_Cmnd *cmd, unsigned int flags) +{ + struct aic7xxx_scb *scb = NULL; + struct aic7xxx_host *p; + int tindex; + int result = -1; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,95) + unsigned long cpu_flags = 0; +#endif +#define DEVICE_RESET 0x01 +#define BUS_RESET 0x02 +#define HOST_RESET 0x04 +#define FAIL 0x08 +#define RESET_DELAY 0x10 + int action; + Scsi_Cmnd *cmd_prev, *cmd_next; + + + if ( cmd == NULL ) + { + printk(KERN_WARNING "(scsi?:?:?:?) Reset called with NULL Scsi_Cmnd " + "pointer, failing.\n"); + return(SCSI_RESET_SNOOZE); + } + + p = (struct aic7xxx_host *) cmd->host->hostdata; + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); + tindex = TARGET_INDEX(cmd); + + /* + * I added a new config option to the driver: "panic_on_abort" that will + * cause the driver to panic and the machine to stop on the first abort + * or reset call into the driver. At that point, it prints out a lot of + * usefull information for me which I can then use to try and debug the + * problem. Simply enable the boot time prompt in order to activate this + * code. + */ + if (aic7xxx_panic_on_abort) + aic7xxx_panic_abort(p, cmd); + + DRIVER_LOCK + + pause_sequencer(p); + while ( (aic_inb(p, INTSTAT) & INT_PEND) && !(p->flags & AHC_IN_ISR)) + { + aic7xxx_isr(p->irq, p, (void *)NULL ); + pause_sequencer(p); + aic7xxx_done_cmds_complete(p); + } + + if (scb == NULL) + { + if (aic7xxx_verbose & VERBOSE_RESET_MID) + printk(INFO_LEAD "Reset called with bogus Scsi_Cmnd" + "->SCB mapping, improvising.\n", p->host_no, CTL_OF_CMD(cmd)); + if ( flags & SCSI_RESET_SUGGEST_HOST_RESET ) + { + action = HOST_RESET; + } + else + { + action = BUS_RESET; + } + } + else if (scb->cmd != cmd) + { + if (aic7xxx_verbose & VERBOSE_RESET_MID) + printk(INFO_LEAD "Reset called with recycled SCB " + "for cmd.\n", p->host_no, CTL_OF_CMD(cmd)); + cmd_prev = NULL; + cmd_next = p->completeq.head; + while ( cmd_next != NULL ) + { + if (cmd_next == cmd) + { + if (aic7xxx_verbose & VERBOSE_RESET_RETURN) + printk(INFO_LEAD "Reset, found cmd on completeq" + ", completing.\n", p->host_no, CTL_OF_CMD(cmd)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_RESET_NOT_RUNNING); + } + cmd_prev = cmd_next; + cmd_next = (Scsi_Cmnd *)cmd_next->host_scribble; + } + if ( !(flags & SCSI_RESET_SYNCHRONOUS) ) + { + if (aic7xxx_verbose & VERBOSE_RESET_RETURN) + printk(INFO_LEAD "Reset, cmd not found," + " failing.\n", p->host_no, CTL_OF_CMD(cmd)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_RESET_NOT_RUNNING); + } + else + { + if (aic7xxx_verbose & VERBOSE_RESET_MID) + printk(INFO_LEAD "Reset called, no scb, " + "flags 0x%x\n", p->host_no, CTL_OF_CMD(cmd), flags); + scb = NULL; + action = HOST_RESET; + } + } + else + { + if (aic7xxx_verbose & VERBOSE_RESET_MID) + printk(INFO_LEAD "Reset called, scb %d, flags " + "0x%x\n", p->host_no, CTL_OF_SCB(scb), scb->hscb->tag, scb->flags); + if ( aic7xxx_scb_on_qoutfifo(p, scb) ) + { + if(aic7xxx_verbose & VERBOSE_RESET_RETURN) + printk(INFO_LEAD "SCB on qoutfifo, returning.\n", p->host_no, + CTL_OF_SCB(scb)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_RESET_NOT_RUNNING); + } + if ( flags & SCSI_RESET_SUGGEST_HOST_RESET ) + { + action = HOST_RESET; + } + else if ( flags & SCSI_RESET_SUGGEST_BUS_RESET ) + { + action = BUS_RESET; + } + else + { + action = DEVICE_RESET; + } + } + if ( (action & DEVICE_RESET) && + (p->dev_flags[tindex] & BUS_DEVICE_RESET_PENDING) ) + { + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Bus device reset already sent to " + "device, escalating.\n", p->host_no, CTL_OF_CMD(cmd)); + action = BUS_RESET; + } + if ( (action & DEVICE_RESET) && + (scb->flags & SCB_QUEUED_ABORT) ) + { + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + { + printk(INFO_LEAD "Have already attempted to reach " + "device with queued\n", p->host_no, CTL_OF_CMD(cmd)); + printk(INFO_LEAD "message, will escalate to bus " + "reset.\n", p->host_no, CTL_OF_CMD(cmd)); + } + action = BUS_RESET; + } + if ( (action & DEVICE_RESET) && + (p->flags & (AHC_RESET_PENDING | AHC_ABORT_PENDING)) ) + { + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Bus device reset stupid when " + "other action has failed.\n", p->host_no, CTL_OF_CMD(cmd)); + action = BUS_RESET; + } + if ( (action & BUS_RESET) && !(p->features & AHC_TWIN) ) + { + action = HOST_RESET; + } + if ( ((jiffies - p->dev_last_reset[tindex]) < (HZ * 3)) && + !(action & (HOST_RESET | BUS_RESET))) + { + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + { + printk(INFO_LEAD "Reset called too soon after last " + "reset without requesting\n", p->host_no, CTL_OF_CMD(cmd)); + printk(INFO_LEAD "bus or host reset, escalating.\n", p->host_no, + CTL_OF_CMD(cmd)); + } + action = BUS_RESET; + } + if ( ((jiffies - p->last_reset) < (HZ * 3)) && + (action & (HOST_RESET | BUS_RESET)) ) + { + if (aic7xxx_verbose & VERBOSE_RESET_PROCESS) + printk(INFO_LEAD "Reset called too soon after " + "last bus reset, delaying.\n", p->host_no, CTL_OF_CMD(cmd)); + action = RESET_DELAY; + } + if ( (action & (BUS_RESET | HOST_RESET)) && (p->flags & AHC_IN_RESET) + && ((jiffies - p->reset_start) > (2 * HZ * 3)) ) + { + printk(KERN_ERR "(scsi%d:%d:%d:%d) Yikes!! Card must have left to go " + "back to Adaptec!!\n", p->host_no, CTL_OF_CMD(cmd)); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_RESET_SNOOZE); + } +/* + * By this point, we want to already know what we are going to do and + * only have the following code implement our course of action. + */ + switch (action) + { + case RESET_DELAY: + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_RESET_PENDING); + break; + case FAIL: + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(SCSI_RESET_ERROR); + break; + case DEVICE_RESET: + p->flags |= AHC_IN_RESET; + result = aic7xxx_bus_device_reset(p, cmd); + aic7xxx_run_done_queue(p, TRUE); + /* We can't rely on run_waiting_queues to unpause the sequencer for + * PCI based controllers since we use AAP */ + aic7xxx_run_waiting_queues(p); + unpause_sequencer(p, FALSE); + p->flags &= ~AHC_IN_RESET; + DRIVER_UNLOCK + return(result); + break; + case BUS_RESET: + case HOST_RESET: + default: + p->reset_start = jiffies; + p->flags |= AHC_IN_RESET; + aic7xxx_reset_channel(p, cmd->channel, TRUE); + if ( (p->features & AHC_TWIN) && (action & HOST_RESET) ) + { + aic7xxx_reset_channel(p, cmd->channel ^ 0x01, TRUE); + restart_sequencer(p); + } + p->last_reset = jiffies; + if (action != HOST_RESET) + result = SCSI_RESET_SUCCESS | SCSI_RESET_BUS_RESET; + else + { + result = SCSI_RESET_SUCCESS | SCSI_RESET_HOST_RESET; + aic_outb(p, aic_inb(p, SIMODE1) & ~(ENREQINIT|ENBUSFREE), + SIMODE1); + aic7xxx_clear_intstat(p); + p->flags &= ~AHC_HANDLING_REQINITS; + p->msg_type = MSG_TYPE_NONE; + p->msg_index = 0; + p->msg_len = 0; + } + aic7xxx_run_done_queue(p, TRUE); + p->flags &= ~AHC_IN_RESET; + /* We can't rely on run_waiting_queues to unpause the sequencer for + * PCI based controllers since we use AAP */ + aic7xxx_run_waiting_queues(p); + unpause_sequencer(p, FALSE); + DRIVER_UNLOCK + return(result); + break; + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_biosparam + * + * Description: + * Return the disk geometry for the given SCSI device. + *-F*************************************************************************/ +int +aic7xxx_biosparam(Disk *disk, kdev_t dev, int geom[]) +{ + int heads, sectors, cylinders; + struct aic7xxx_host *p; + + p = (struct aic7xxx_host *) disk->device->host->hostdata; + + /* + * XXX - if I could portably find the card's configuration + * information, then this could be autodetected instead + * of left to a boot-time switch. + */ + heads = 64; + sectors = 32; + cylinders = disk->capacity / (heads * sectors); + + if ((p->flags & AHC_EXTEND_TRANS_A) && (cylinders > 1024)) + { + heads = 255; + sectors = 63; + cylinders = disk->capacity / (heads * sectors); + } + + geom[0] = heads; + geom[1] = sectors; + geom[2] = cylinders; + + return (0); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_release + * + * Description: + * Free the passed in Scsi_Host memory structures prior to unloading the + * module. + *-F*************************************************************************/ +int +aic7xxx_release(struct Scsi_Host *host) +{ + struct aic7xxx_host *p = (struct aic7xxx_host *) host->hostdata; + struct aic7xxx_host *next, *prev; + + if(p->irq) + free_irq(p->irq, p); + release_region(p->base, MAXREG - MINREG); +#ifdef MMAPIO + if(p->maddr) + { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,0) + vfree((void *) (((unsigned long) p->maddr) & PAGE_MASK)); +#else + iounmap((void *) (((unsigned long) p->maddr) & PAGE_MASK)); +#endif + } +#endif /* MMAPIO */ + prev = NULL; + next = first_aic7xxx; + while(next != NULL) + { + if(next == p) + { + if(prev == NULL) + first_aic7xxx = next->next; + else + prev->next = next->next; + } + else + { + prev = next; + } + next = next->next; + } + aic7xxx_free(p); + return(0); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_print_card + * + * Description: + * Print out all of the control registers on the card + * + * NOTE: This function is not yet safe for use on the VLB and EISA + * controllers, so it isn't used on those controllers at all. + *-F*************************************************************************/ +static void +aic7xxx_print_card(struct aic7xxx_host *p) +{ + int i, j, k, chip; + static struct register_ranges { + int num_ranges; + int range_val[32]; + } cards_ds[] = { + { 0, {0,} }, /* none */ + {10, {0x00, 0x05, 0x08, 0x11, 0x18, 0x19, 0x1f, 0x1f, 0x60, 0x60, /*7771*/ + 0x62, 0x66, 0x80, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9b, 0x9f} }, + { 9, {0x00, 0x05, 0x08, 0x11, 0x18, 0x1f, 0x60, 0x60, 0x62, 0x66, /*7850*/ + 0x80, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9a, 0x9f} }, + { 9, {0x00, 0x05, 0x08, 0x11, 0x18, 0x1f, 0x60, 0x60, 0x62, 0x66, /*7860*/ + 0x80, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9a, 0x9f} }, + {10, {0x00, 0x05, 0x08, 0x11, 0x18, 0x19, 0x1c, 0x1f, 0x60, 0x60, /*7870*/ + 0x62, 0x66, 0x80, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9a, 0x9f} }, + {10, {0x00, 0x05, 0x08, 0x11, 0x18, 0x1a, 0x1c, 0x1f, 0x60, 0x60, /*7880*/ + 0x62, 0x66, 0x80, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9a, 0x9f} }, + {16, {0x00, 0x05, 0x08, 0x11, 0x18, 0x1f, 0x60, 0x60, 0x62, 0x66, /*7890*/ + 0x84, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9a, 0x9a, 0x9f, 0x9f, + 0xe0, 0xf1, 0xf4, 0xf4, 0xf6, 0xf6, 0xf8, 0xf8, 0xfa, 0xfc, + 0xfe, 0xff} }, + {12, {0x00, 0x05, 0x08, 0x11, 0x18, 0x19, 0x1b, 0x1f, 0x60, 0x60, /*7895*/ + 0x62, 0x66, 0x80, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9a, 0x9a, + 0x9f, 0x9f, 0xe0, 0xf1} }, + {16, {0x00, 0x05, 0x08, 0x11, 0x18, 0x1f, 0x60, 0x60, 0x62, 0x66, /*7896*/ + 0x84, 0x8e, 0x90, 0x95, 0x97, 0x97, 0x9a, 0x9a, 0x9f, 0x9f, + 0xe0, 0xf1, 0xf4, 0xf4, 0xf6, 0xf6, 0xf8, 0xf8, 0xfa, 0xfc, + 0xfe, 0xff} }, + }; +#ifdef CONFIG_PCI + static struct register_ranges cards_ns[] = { + { 0, {0,} }, /* none */ + { 0, {0,} }, /* 7771 */ + { 7, {0x04, 0x08, 0x0c, 0x0e, 0x10, 0x17, 0x28, 0x2b, 0x30, 0x33, + 0x3c, 0x41, 0x43, 0x47} }, + { 7, {0x04, 0x08, 0x0c, 0x0e, 0x10, 0x17, 0x28, 0x2b, 0x30, 0x33, + 0x3c, 0x41, 0x43, 0x47} }, + { 5, {0x04, 0x08, 0x0c, 0x0e, 0x10, 0x17, 0x30, 0x33, 0x3c, 0x41} }, + { 5, {0x04, 0x08, 0x0c, 0x0e, 0x10, 0x17, 0x30, 0x34, 0x3c, 0x47} }, + { 5, {0x04, 0x08, 0x0c, 0x1b, 0x30, 0x34, 0x3c, 0x43, 0xdc, 0xe3} }, + { 6, {0x04, 0x08, 0x0c, 0x0e, 0x10, 0x17, 0x30, 0x34, 0x3c, 0x47, + 0xdc, 0xe3} }, + { 6, {0x04, 0x08, 0x0c, 0x1b, 0x30, 0x34, 0x3c, 0x43, 0xdc, 0xe3, + 0xff, 0xff} } + }; +#endif + chip = p->chip & AHC_CHIPID_MASK; + /* + * Let's run through the PCI space first.... + */ + printk("%s at ", + board_names[p->board_name_index]); + switch(p->chip & ~AHC_CHIPID_MASK) + { + case AHC_VL: + printk("VLB Slot %d.\n", p->pci_device_fn); + break; + case AHC_EISA: + printk("EISA Slot %d.\n", p->pci_device_fn); + break; + case AHC_PCI: + default: + printk("PCI %d/%d.\n", PCI_SLOT(p->pci_device_fn), + PCI_FUNC(p->pci_device_fn)); + break; + } + +#ifdef CONFIG_PCI + { + unsigned char temp; + + printk("PCI Dump:\n"); + k=0; + for(i=0; i<cards_ns[chip].num_ranges; i++) + { + for(j = cards_ns[chip].range_val[ i * 2 ]; + j <= cards_ns[chip].range_val[ i * 2 + 1 ] ; + j++) + { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,92) + pci_read_config_byte(p->pdev, j, &temp); +#else + pcibios_read_config_byte(p->pci_bus, p->pci_device_fn, j, &temp); +#endif + printk("%02x:%02x ", j, temp); + if(++k == 13) + { + printk("\n"); + k = 0; + } + } + } + } + if(k != 0) + printk("\n"); +#endif /* CONFIG_PCI */ + + /* + * Now the registers on the card.... + */ + printk("Card Dump:\n"); + k = 0; + for(i=0; i<cards_ds[chip].num_ranges; i++) + { + for(j = cards_ds[chip].range_val[ i * 2 ]; + j <= cards_ds[chip].range_val[ i * 2 + 1 ] ; + j++) + { + printk("%02x:%02x ", j, aic_inb(p, j)); + if(++k == 13) + { + printk("\n"); + k=0; + } + } + } + if(k != 0) + printk("\n"); + if (p->flags & AHC_SEEPROM_FOUND) + { + unsigned short *sc1; + sc1 = (unsigned short *)&p->sc; + + printk("SEEPROM dump.\n"); + for(i=1; i<=32; i++) + { + printk("0x%04x", sc1[i-1]); + if ( (i % 8) == 0 ) + printk("\n"); + else + printk(" "); + } + } + + /* + * If this was an Ultra2 controller, then we just hosed the card in terms + * of the QUEUE REGS. This function is only called at init time or by + * the panic_abort function, so it's safe to assume a generic init time + * setting here + */ + + if(p->features & AHC_QUEUE_REGS) + { + aic_outb(p, 0, SDSCB_QOFF); + aic_outb(p, 0, SNSCB_QOFF); + aic_outb(p, 0, HNSCB_QOFF); + } + +} + +/*+F************************************************************************* + * Function: + * aic7xxx_print_scratch_ram + * + * Description: + * Print out the scratch RAM values on the card. + *-F*************************************************************************/ +static void +aic7xxx_print_scratch_ram(struct aic7xxx_host *p) +{ + int i, k; + + k = 0; + printk("Scratch RAM:\n"); + for(i = SRAM_BASE; i < SEQCTL; i++) + { + printk("%02x:%02x ", i, aic_inb(p, i)); + if(++k == 13) + { + printk("\n"); + k=0; + } + } + if (p->features & AHC_MORE_SRAM) + { + for(i = TARG_OFFSET; i < 0x80; i++) + { + printk("%02x:%02x ", i, aic_inb(p, i)); + if(++k == 13) + { + printk("\n"); + k=0; + } + } + } + printk("\n"); +} + + +#include "aic7xxx_proc.c" + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = AIC7XXX; + +#include "scsi_module.c" +#endif + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 2 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -2 + * c-argdecl-indent: 2 + * c-label-offset: -2 + * c-continued-statement-offset: 2 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/aic7xxx.h b/linux/src/drivers/scsi/aic7xxx.h new file mode 100644 index 00000000..2386d1f4 --- /dev/null +++ b/linux/src/drivers/scsi/aic7xxx.h @@ -0,0 +1,114 @@ +/*+M************************************************************************* + * Adaptec AIC7xxx device driver for Linux. + * + * Copyright (c) 1994 John Aycock + * The University of Calgary Department of Computer Science. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: aic7xxx.h,v 1.1 1999/04/26 05:54:18 tb Exp $ + *-M*************************************************************************/ +#ifndef _aic7xxx_h +#define _aic7xxx_h + +#define AIC7XXX_H_VERSION "3.2.4" + +#ifndef LINUX_VERSION_CODE +#include <linux/version.h> +#endif + +#ifndef KERNEL_VERSION +#define KERNEL_VERSION(x,y,z) (((x)<<16)+((y)<<8)+(z)) +#endif + +#if defined(__i386__) +# define AIC7XXX_BIOSPARAM aic7xxx_biosparam +#else +# define AIC7XXX_BIOSPARAM NULL +#endif + +/* + * Scsi_Host_Template (see hosts.h) for AIC-7xxx - some fields + * to do with card config are filled in after the card is detected. + */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,1,65) +#define AIC7XXX { \ + next: NULL, \ + module: NULL, \ + proc_dir: NULL, \ + proc_info: aic7xxx_proc_info, \ + name: NULL, \ + detect: aic7xxx_detect, \ + release: aic7xxx_release, \ + info: aic7xxx_info, \ + command: NULL, \ + queuecommand: aic7xxx_queue, \ + eh_strategy_handler: NULL, \ + eh_abort_handler: NULL, \ + eh_device_reset_handler: NULL, \ + eh_bus_reset_handler: NULL, \ + eh_host_reset_handler: NULL, \ + abort: aic7xxx_abort, \ + reset: aic7xxx_reset, \ + slave_attach: NULL, \ + bios_param: AIC7XXX_BIOSPARAM, \ + can_queue: 255, /* max simultaneous cmds */\ + this_id: -1, /* scsi id of host adapter */\ + sg_tablesize: 0, /* max scatter-gather cmds */\ + cmd_per_lun: 3, /* cmds per lun (linked cmds) */\ + present: 0, /* number of 7xxx's present */\ + unchecked_isa_dma: 0, /* no memory DMA restrictions */\ + use_clustering: ENABLE_CLUSTERING, \ + use_new_eh_code: 0 \ +} +#else +#define AIC7XXX { \ + next: NULL, \ + usage_count: NULL, \ + proc_dir: NULL, \ + proc_info: aic7xxx_proc_info, \ + name: NULL, \ + detect: aic7xxx_detect, \ + release: aic7xxx_release, \ + info: aic7xxx_info, \ + command: NULL, \ + queuecommand: aic7xxx_queue, \ + abort: aic7xxx_abort, \ + reset: aic7xxx_reset, \ + slave_attach: NULL, \ + bios_param: AIC7XXX_BIOSPARAM, \ + can_queue: 255, /* max simultaneous cmds */\ + this_id: -1, /* scsi id of host adapter */\ + sg_tablesize: 0, /* max scatter-gather cmds */\ + cmd_per_lun: 3, /* cmds per lun (linked cmds) */\ + present: 0, /* number of 7xxx's present */\ + unchecked_isa_dma: 0, /* no memory DMA restrictions */\ + use_clustering: ENABLE_CLUSTERING \ +} +#endif + +extern int aic7xxx_queue(Scsi_Cmnd *, void (*)(Scsi_Cmnd *)); +extern int aic7xxx_biosparam(Disk *, kdev_t, int[]); +extern int aic7xxx_detect(Scsi_Host_Template *); +extern int aic7xxx_command(Scsi_Cmnd *); +extern int aic7xxx_reset(Scsi_Cmnd *, unsigned int); +extern int aic7xxx_abort(Scsi_Cmnd *); +extern int aic7xxx_release(struct Scsi_Host *); + +extern const char *aic7xxx_info(struct Scsi_Host *); + +extern int aic7xxx_proc_info(char *, char **, off_t, int, int, int); + +#endif /* _aic7xxx_h */ diff --git a/linux/src/drivers/scsi/aic7xxx/scsi_message.h b/linux/src/drivers/scsi/aic7xxx/scsi_message.h new file mode 100644 index 00000000..16c40138 --- /dev/null +++ b/linux/src/drivers/scsi/aic7xxx/scsi_message.h @@ -0,0 +1,41 @@ +/* Messages (1 byte) */ /* I/T (M)andatory or (O)ptional */ +#define MSG_CMDCOMPLETE 0x00 /* M/M */ +#define MSG_EXTENDED 0x01 /* O/O */ +#define MSG_SAVEDATAPOINTER 0x02 /* O/O */ +#define MSG_RESTOREPOINTERS 0x03 /* O/O */ +#define MSG_DISCONNECT 0x04 /* O/O */ +#define MSG_INITIATOR_DET_ERR 0x05 /* M/M */ +#define MSG_ABORT 0x06 /* O/M */ +#define MSG_MESSAGE_REJECT 0x07 /* M/M */ +#define MSG_NOOP 0x08 /* M/M */ +#define MSG_PARITY_ERROR 0x09 /* M/M */ +#define MSG_LINK_CMD_COMPLETE 0x0a /* O/O */ +#define MSG_LINK_CMD_COMPLETEF 0x0b /* O/O */ +#define MSG_BUS_DEV_RESET 0x0c /* O/M */ +#define MSG_ABORT_TAG 0x0d /* O/O */ +#define MSG_CLEAR_QUEUE 0x0e /* O/O */ +#define MSG_INIT_RECOVERY 0x0f /* O/O */ +#define MSG_REL_RECOVERY 0x10 /* O/O */ +#define MSG_TERM_IO_PROC 0x11 /* O/O */ + +/* Messages (2 byte) */ +#define MSG_SIMPLE_Q_TAG 0x20 /* O/O */ +#define MSG_HEAD_OF_Q_TAG 0x21 /* O/O */ +#define MSG_ORDERED_Q_TAG 0x22 /* O/O */ +#define MSG_IGN_WIDE_RESIDUE 0x23 /* O/O */ + +/* Identify message */ /* M/M */ +#define MSG_IDENTIFYFLAG 0x80 +#define MSG_IDENTIFY_DISCFLAG 0x40 +#define MSG_IDENTIFY(lun, disc) (((disc) ? 0xc0 : MSG_IDENTIFYFLAG) | (lun)) +#define MSG_ISIDENTIFY(m) ((m) & MSG_IDENTIFYFLAG) + +/* Extended messages (opcode and length) */ +#define MSG_EXT_SDTR 0x01 +#define MSG_EXT_SDTR_LEN 0x03 + +#define MSG_EXT_WDTR 0x03 +#define MSG_EXT_WDTR_LEN 0x02 +#define MSG_EXT_WDTR_BUS_8_BIT 0x00 +#define MSG_EXT_WDTR_BUS_16_BIT 0x01 +#define MSG_EXT_WDTR_BUS_32_BIT 0x02 diff --git a/linux/src/drivers/scsi/aic7xxx/sequencer.h b/linux/src/drivers/scsi/aic7xxx/sequencer.h new file mode 100644 index 00000000..7c0121e0 --- /dev/null +++ b/linux/src/drivers/scsi/aic7xxx/sequencer.h @@ -0,0 +1,135 @@ +/* + * Instruction formats for the sequencer program downloaded to + * Aic7xxx SCSI host adapters + * + * Copyright (c) 1997, 1998 Justin T. Gibbs. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Where this Software is combined with software released under the terms of + * the GNU Public License ("GPL") and the terms of the GPL would require the + * combined work to also be released under the terms of the GPL, the terms + * and conditions of this License will apply in addition to those of the + * GPL with the exception of any terms or conditions of this License that + * conflict with, or are expressly prohibited by, the GPL. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: sequencer.h,v 1.1 1999/04/26 05:55:33 tb Exp $ + */ + +#ifdef __LITTLE_ENDIAN_BITFIELD +struct ins_format1 { + unsigned int + immediate : 8, + source : 9, + destination : 9, + ret : 1, + opcode : 4, + parity : 1; +}; + +struct ins_format2 { + unsigned int + shift_control : 8, + source : 9, + destination : 9, + ret : 1, + opcode : 4, + parity : 1; +}; + +struct ins_format3 { + unsigned int + immediate : 8, + source : 9, + address : 10, + opcode : 4, + parity : 1; +}; +#elif defined(__BIG_ENDIAN_BITFIELD) +struct ins_format1 { + unsigned int + parity : 1, + opcode : 4, + ret : 1, + destination : 9, + source : 9, + immediate : 8; +}; + +struct ins_format2 { + unsigned int + parity : 1, + opcode : 4, + ret : 1, + destination : 9, + source : 9, + shift_control : 8; +}; + +struct ins_format3 { + unsigned int + parity : 1, + opcode : 4, + address : 10, + source : 9, + immediate : 8; +}; +#endif + +union ins_formats { + struct ins_format1 format1; + struct ins_format2 format2; + struct ins_format3 format3; + unsigned char bytes[4]; + unsigned int integer; +}; +struct instruction { + union ins_formats format; + unsigned int srcline; + struct symbol *patch_label; + struct { + struct instruction *stqe_next; + } links; +}; + +#define AIC_OP_OR 0x0 +#define AIC_OP_AND 0x1 +#define AIC_OP_XOR 0x2 +#define AIC_OP_ADD 0x3 +#define AIC_OP_ADC 0x4 +#define AIC_OP_ROL 0x5 +#define AIC_OP_BMOV 0x6 + +#define AIC_OP_JMP 0x8 +#define AIC_OP_JC 0x9 +#define AIC_OP_JNC 0xa +#define AIC_OP_CALL 0xb +#define AIC_OP_JNE 0xc +#define AIC_OP_JNZ 0xd +#define AIC_OP_JE 0xe +#define AIC_OP_JZ 0xf + +/* Pseudo Ops */ +#define AIC_OP_SHL 0x10 +#define AIC_OP_SHR 0x20 +#define AIC_OP_ROR 0x30 diff --git a/linux/src/drivers/scsi/aic7xxx_proc.c b/linux/src/drivers/scsi/aic7xxx_proc.c new file mode 100644 index 00000000..451ad99c --- /dev/null +++ b/linux/src/drivers/scsi/aic7xxx_proc.c @@ -0,0 +1,397 @@ +/*+M************************************************************************* + * Adaptec AIC7xxx device driver proc support for Linux. + * + * Copyright (c) 1995, 1996 Dean W. Gehnert + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * ---------------------------------------------------------------- + * o Modified from the EATA-DMA /proc support. + * o Additional support for device block statistics provided by + * Matthew Jacob. + * o Correction of overflow by Heinz Mauelshagen + * o Adittional corrections by Doug Ledford + * + * Dean W. Gehnert, deang@teleport.com, 05/01/96 + * + * $Id: aic7xxx_proc.c,v 1.1 1999/04/26 05:54:19 tb Exp $ + *-M*************************************************************************/ + +#define BLS (&aic7xxx_buffer[size]) +#define HDRB \ +" < 512 512-1K 1-2K 2-4K 4-8K 8-16K 16-32K 32-64K 64-128K >128K" + +#ifdef PROC_DEBUG +extern int vsprintf(char *, const char *, va_list); + +static void +proc_debug(const char *fmt, ...) +{ + va_list ap; + char buf[256]; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + printk(buf); + va_end(ap); +} +#else /* PROC_DEBUG */ +# define proc_debug(fmt, args...) +#endif /* PROC_DEBUG */ + +static int aic7xxx_buffer_size = 0; +static char *aic7xxx_buffer = NULL; + + +/*+F************************************************************************* + * Function: + * aic7xxx_set_info + * + * Description: + * Set parameters for the driver from the /proc filesystem. + *-F*************************************************************************/ +int +aic7xxx_set_info(char *buffer, int length, struct Scsi_Host *HBAptr) +{ + proc_debug("aic7xxx_set_info(): %s\n", buffer); + return (-ENOSYS); /* Currently this is a no-op */ +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_proc_info + * + * Description: + * Return information to handle /proc support for the driver. + *-F*************************************************************************/ +int +aic7xxx_proc_info ( char *buffer, char **start, off_t offset, int length, + int hostno, int inout) +{ + struct Scsi_Host *HBAptr; + struct aic7xxx_host *p; + int size = 0; + unsigned char i; + struct aic7xxx_xferstats *sp; + unsigned char target, lun; + + HBAptr = NULL; + + for(p=first_aic7xxx; p->host->host_no != hostno; p=p->next) + ; + + if (!p) + { + size += sprintf(buffer, "Can't find adapter for host number %d\n", hostno); + if (size > length) + { + return (size); + } + else + { + return (length); + } + } + + HBAptr = p->host; + + if (inout == TRUE) /* Has data been written to the file? */ + { + return (aic7xxx_set_info(buffer, length, HBAptr)); + } + + p = (struct aic7xxx_host *) HBAptr->hostdata; + + /* + * It takes roughly 1K of space to hold all relevant card info, not + * counting any proc stats, so we start out with a 1.5k buffer size and + * if proc_stats is defined, then we sweep the stats structure to see + * how many drives we will be printing out for and add 384 bytes per + * device with active stats. + * + * Hmmmm...that 1.5k seems to keep growing as items get added so they + * can be easily viewed for debugging purposes. So, we bumped that + * 1.5k to 4k so we can quit having to bump it all the time. + */ + + size = 4096; + for (target = 0; target < MAX_TARGETS; target++) + { + for (lun = 0; lun < MAX_LUNS; lun++) + { + if (p->stats[target][lun].xfers != 0) +#ifdef AIC7XXX_PROC_STATS + size += 512; +#else + size += 256; +#endif + } + } + if (aic7xxx_buffer_size != size) + { + if (aic7xxx_buffer != NULL) + { + kfree(aic7xxx_buffer); + aic7xxx_buffer_size = 0; + } + aic7xxx_buffer = kmalloc(size, GFP_KERNEL); + } + if (aic7xxx_buffer == NULL) + { + size = sprintf(buffer, "AIC7xxx - kmalloc error at line %d\n", + __LINE__); + return size; + } + aic7xxx_buffer_size = size; + + size = 0; + size += sprintf(BLS, "Adaptec AIC7xxx driver version: "); + size += sprintf(BLS, "%s/", AIC7XXX_C_VERSION); + size += sprintf(BLS, "%s", AIC7XXX_H_VERSION); + size += sprintf(BLS, "\n"); + size += sprintf(BLS, "Compile Options:\n"); +#ifdef AIC7XXX_RESET_DELAY + size += sprintf(BLS, " AIC7XXX_RESET_DELAY : %d\n", AIC7XXX_RESET_DELAY); +#endif + size += sprintf(BLS, " AIC7XXX_TAGGED_QUEUEING: Adapter Support Enabled\n"); + size += sprintf(BLS, " Check below to see " + "which\n" + " devices use tagged " + "queueing\n"); + size += sprintf(BLS, " AIC7XXX_PAGE_ENABLE : Enabled (This is no longer " + "an option)\n"); +#ifdef AIC7XXX_PROC_STATS + size += sprintf(BLS, " AIC7XXX_PROC_STATS : Enabled\n"); +#else + size += sprintf(BLS, " AIC7XXX_PROC_STATS : Disabled\n"); +#endif + size += sprintf(BLS, "\n"); + size += sprintf(BLS, "Adapter Configuration:\n"); + size += sprintf(BLS, " SCSI Adapter: %s\n", + board_names[p->board_name_index]); + if (p->flags & AHC_TWIN) + size += sprintf(BLS, " Twin Channel\n"); + else + { + char *channel = ""; + char *ultra = ""; + char *wide = "Narrow "; + if (p->flags & AHC_MULTI_CHANNEL) + { + channel = " Channel A"; + if (p->flags & (AHC_CHNLB|AHC_CHNLC)) + channel = (p->flags & AHC_CHNLB) ? " Channel B" : " Channel C"; + } + if (p->features & AHC_WIDE) + wide = "Wide "; + if (p->features & AHC_ULTRA2) + ultra = "Ultra2-LVD/SE "; + else if (p->features & AHC_ULTRA) + ultra = "Ultra "; + size += sprintf(BLS, " %s%sController%s\n", + ultra, wide, channel); + } + if( !(p->maddr) ) + { + size += sprintf(BLS, " Programmed I/O Base: %lx\n", p->base); + } + else + { + size += sprintf(BLS, " PCI MMAPed I/O Base: 0x%lx\n", p->mbase); + } + if( (p->chip & (AHC_VL | AHC_EISA)) ) + { + size += sprintf(BLS, " BIOS Memory Address: 0x%08x\n", p->bios_address); + } + size += sprintf(BLS, " Adapter SEEPROM Config: %s\n", + (p->flags & AHC_SEEPROM_FOUND) ? "SEEPROM found and used." : + ((p->flags & AHC_USEDEFAULTS) ? "SEEPROM not found, using defaults." : + "SEEPROM not found, using leftover BIOS values.") ); + size += sprintf(BLS, " Adaptec SCSI BIOS: %s\n", + (p->flags & AHC_BIOS_ENABLED) ? "Enabled" : "Disabled"); + size += sprintf(BLS, " IRQ: %d\n", HBAptr->irq); + size += sprintf(BLS, " SCBs: Active %d, Max Active %d,\n", + p->activescbs, p->max_activescbs); + size += sprintf(BLS, " Allocated %d, HW %d, " + "Page %d\n", p->scb_data->numscbs, p->scb_data->maxhscbs, + p->scb_data->maxscbs); + if (p->flags & AHC_EXTERNAL_SRAM) + size += sprintf(BLS, " Using External SCB SRAM\n"); + size += sprintf(BLS, " Interrupts: %ld", p->isr_count); + if (p->chip & AHC_EISA) + { + size += sprintf(BLS, " %s\n", + (p->pause & IRQMS) ? "(Level Sensitive)" : "(Edge Triggered)"); + } + else + { + size += sprintf(BLS, "\n"); + } + size += sprintf(BLS, " BIOS Control Word: 0x%04x\n", + p->bios_control); + size += sprintf(BLS, " Adapter Control Word: 0x%04x\n", + p->adapter_control); + size += sprintf(BLS, " Extended Translation: %sabled\n", + (p->flags & AHC_EXTEND_TRANS_A) ? "En" : "Dis"); + size += sprintf(BLS, "Disconnect Enable Flags: 0x%04x\n", p->discenable); + if (p->features & (AHC_ULTRA | AHC_ULTRA2)) + { + size += sprintf(BLS, " Ultra Enable Flags: 0x%04x\n", p->ultraenb); + } + size += sprintf(BLS, " Tag Queue Enable Flags: 0x%04x\n", p->tagenable); + size += sprintf(BLS, "Ordered Queue Tag Flags: 0x%04x\n", p->orderedtag); +#ifdef AIC7XXX_CMDS_PER_LUN + size += sprintf(BLS, "Default Tag Queue Depth: %d\n", AIC7XXX_CMDS_PER_LUN); +#else + size += sprintf(BLS, "Default Tag Queue Depth: %d\n", 8); +#endif + size += sprintf(BLS, " Tagged Queue By Device array for aic7xxx host " + "instance %d:\n", p->instance); + size += sprintf(BLS, " {"); + for(i=0; i < (MAX_TARGETS - 1); i++) + size += sprintf(BLS, "%d,",aic7xxx_tag_info[p->instance].tag_commands[i]); + size += sprintf(BLS, "%d}\n",aic7xxx_tag_info[p->instance].tag_commands[i]); + size += sprintf(BLS, " Actual queue depth per device for aic7xxx host " + "instance %d:\n", p->instance); + size += sprintf(BLS, " {"); + for(i=0; i < (MAX_TARGETS - 1); i++) + size += sprintf(BLS, "%d,", p->dev_max_queue_depth[i]); + size += sprintf(BLS, "%d}\n", p->dev_max_queue_depth[i]); + + size += sprintf(BLS, "\n"); + size += sprintf(BLS, "Statistics:\n"); + for (target = 0; target < MAX_TARGETS; target++) + { + for (lun = 0; lun < MAX_LUNS; lun++) + { + sp = &p->stats[target][lun]; + if (sp->xfers == 0) + { + continue; + } + if (p->features & AHC_TWIN) + { + size += sprintf(BLS, "(scsi%d:%d:%d:%d)\n", + p->host_no, (target >> 3), (target & 0x7), lun); + } + else + { + size += sprintf(BLS, "(scsi%d:%d:%d:%d)\n", + p->host_no, 0, target, lun); + } + size += sprintf(BLS, " Device using %s/%s\n", + (p->transinfo[target].cur_width == MSG_EXT_WDTR_BUS_16_BIT) ? + "Wide" : "Narrow", + (p->transinfo[target].cur_offset != 0) ? + "Sync transfers at" : "Async transfers." ); + if (p->transinfo[target].cur_offset != 0) + { + struct aic7xxx_syncrate *sync_rate; + int period = p->transinfo[target].cur_period; + int rate = (p->transinfo[target].cur_width == + MSG_EXT_WDTR_BUS_16_BIT) ? 1 : 0; + + sync_rate = aic7xxx_find_syncrate(p, &period, AHC_SYNCRATE_ULTRA2); + if (sync_rate != NULL) + { + size += sprintf(BLS, " %s MByte/sec, offset %d\n", + sync_rate->rate[rate], + p->transinfo[target].cur_offset ); + } + else + { + size += sprintf(BLS, " 3.3 MByte/sec, offset %d\n", + p->transinfo[target].cur_offset ); + } + } + size += sprintf(BLS, " Device Negotiation Settings\n"); + size += sprintf(BLS, " Period Offset Bus Width\n"); + size += sprintf(BLS, "User %03d %03d %d\n", + p->transinfo[target].user_period, + p->transinfo[target].user_offset, + p->transinfo[target].user_width); + size += sprintf(BLS, "Goal %03d %03d %d\n", + p->transinfo[target].goal_period, + p->transinfo[target].goal_offset, + p->transinfo[target].goal_width); + size += sprintf(BLS, "Current %03d %03d %d\n", + p->transinfo[target].cur_period, + p->transinfo[target].cur_offset, + p->transinfo[target].cur_width); + size += sprintf(BLS, " Total transfers %ld (%ld read;%ld written)\n", + sp->xfers, sp->r_total, sp->w_total); + size += sprintf(BLS, " blks(512) rd=%ld; blks(512) wr=%ld\n", + sp->r_total512, sp->w_total512); +#ifdef AIC7XXX_PROC_STATS + size += sprintf(BLS, "%s\n", HDRB); + size += sprintf(BLS, " Reads:"); + for (i = 0; i < NUMBER(sp->r_bins); i++) + { + size += sprintf(BLS, "%6ld ", sp->r_bins[i]); + } + size += sprintf(BLS, "\n"); + size += sprintf(BLS, "Writes:"); + for (i = 0; i < NUMBER(sp->w_bins); i++) + { + size += sprintf(BLS, "%6ld ", sp->w_bins[i]); + } +#endif /* AIC7XXX_PROC_STATS */ + size += sprintf(BLS, "\n\n"); + } + } + + if (size >= aic7xxx_buffer_size) + { + printk(KERN_WARNING "aic7xxx: Overflow in aic7xxx_proc.c\n"); + } + + if (offset > size - 1) + { + kfree(aic7xxx_buffer); + aic7xxx_buffer = NULL; + aic7xxx_buffer_size = length = 0; + *start = NULL; + } + else + { + *start = &aic7xxx_buffer[offset]; /* Start of wanted data */ + if (size - offset < length) + { + length = size - offset; + } + } + + return (length); +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 2 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -2 + * c-argdecl-indent: 2 + * c-label-offset: -2 + * c-continued-statement-offset: 2 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/aic7xxx_reg.h b/linux/src/drivers/scsi/aic7xxx_reg.h new file mode 100644 index 00000000..d12d1b6e --- /dev/null +++ b/linux/src/drivers/scsi/aic7xxx_reg.h @@ -0,0 +1,587 @@ +/* + * DO NOT EDIT - This file is automatically generated. + */ + +#define SCSISEQ 0x00 +#define TEMODE 0x80 +#define ENSELO 0x40 +#define ENSELI 0x20 +#define ENRSELI 0x10 +#define ENAUTOATNO 0x08 +#define ENAUTOATNI 0x04 +#define ENAUTOATNP 0x02 +#define SCSIRSTO 0x01 + +#define SXFRCTL0 0x01 +#define DFON 0x80 +#define DFPEXP 0x40 +#define FAST20 0x20 +#define CLRSTCNT 0x10 +#define SPIOEN 0x08 +#define SCAMEN 0x04 +#define CLRCHN 0x02 + +#define SXFRCTL1 0x02 +#define BITBUCKET 0x80 +#define SWRAPEN 0x40 +#define ENSPCHK 0x20 +#define STIMESEL 0x18 +#define ENSTIMER 0x04 +#define ACTNEGEN 0x02 +#define STPWEN 0x01 + +#define SCSISIGO 0x03 +#define CDO 0x80 +#define IOO 0x40 +#define MSGO 0x20 +#define ATNO 0x10 +#define SELO 0x08 +#define BSYO 0x04 +#define REQO 0x02 +#define ACKO 0x01 + +#define SCSISIGI 0x03 +#define ATNI 0x10 +#define SELI 0x08 +#define BSYI 0x04 +#define REQI 0x02 +#define ACKI 0x01 + +#define SCSIRATE 0x04 +#define WIDEXFER 0x80 +#define SXFR_ULTRA2 0x7f +#define SXFR 0x70 +#define SOFS 0x0f + +#define SCSIID 0x05 +#define SCSIOFFSET 0x05 +#define SOFS_ULTRA2 0x7f + +#define SCSIDATL 0x06 + +#define SCSIDATH 0x07 + +#define STCNT 0x08 + +#define CLRSINT0 0x0b +#define CLRSELDO 0x40 +#define CLRSELDI 0x20 +#define CLRSELINGO 0x10 +#define CLRSWRAP 0x08 +#define CLRSPIORDY 0x02 + +#define SSTAT0 0x0b +#define TARGET 0x80 +#define SELDO 0x40 +#define SELDI 0x20 +#define SELINGO 0x10 +#define IOERR 0x08 +#define SWRAP 0x08 +#define SDONE 0x04 +#define SPIORDY 0x02 +#define DMADONE 0x01 + +#define CLRSINT1 0x0c +#define CLRSELTIMEO 0x80 +#define CLRATNO 0x40 +#define CLRSCSIRSTI 0x20 +#define CLRBUSFREE 0x08 +#define CLRSCSIPERR 0x04 +#define CLRPHASECHG 0x02 +#define CLRREQINIT 0x01 + +#define SSTAT1 0x0c +#define SELTO 0x80 +#define ATNTARG 0x40 +#define SCSIRSTI 0x20 +#define PHASEMIS 0x10 +#define BUSFREE 0x08 +#define SCSIPERR 0x04 +#define PHASECHG 0x02 +#define REQINIT 0x01 + +#define SSTAT2 0x0d +#define OVERRUN 0x80 +#define SFCNT 0x1f +#define EXP_ACTIVE 0x10 + +#define SSTAT3 0x0e +#define SCSICNT 0xf0 +#define OFFCNT 0x0f + +#define SCSIID_ULTRA2 0x0f +#define OID 0x0f + +#define SIMODE0 0x10 +#define ENSELDO 0x40 +#define ENSELDI 0x20 +#define ENSELINGO 0x10 +#define ENIOERR 0x08 +#define ENSWRAP 0x08 +#define ENSDONE 0x04 +#define ENSPIORDY 0x02 +#define ENDMADONE 0x01 + +#define SIMODE1 0x11 +#define ENSELTIMO 0x80 +#define ENATNTARG 0x40 +#define ENSCSIRST 0x20 +#define ENPHASEMIS 0x10 +#define ENBUSFREE 0x08 +#define ENSCSIPERR 0x04 +#define ENPHASECHG 0x02 +#define ENREQINIT 0x01 + +#define SCSIBUSL 0x12 + +#define SCSIBUSH 0x13 + +#define SHADDR 0x14 + +#define SELTIMER 0x18 +#define STAGE6 0x20 +#define STAGE5 0x10 +#define STAGE4 0x08 +#define STAGE3 0x04 +#define STAGE2 0x02 +#define STAGE1 0x01 + +#define SELID 0x19 +#define SELID_MASK 0xf0 +#define ONEBIT 0x08 + +#define SPIOCAP 0x1b +#define SOFT1 0x80 +#define SOFT0 0x40 +#define SOFTCMDEN 0x20 +#define HAS_BRDCTL 0x10 +#define SEEPROM 0x08 +#define EEPROM 0x04 +#define ROM 0x02 +#define SSPIOCPS 0x01 + +#define BRDCTL 0x1d +#define BRDDAT7 0x80 +#define BRDDAT6 0x40 +#define BRDDAT5 0x20 +#define BRDDAT4 0x10 +#define BRDSTB 0x10 +#define BRDCS 0x08 +#define BRDDAT3 0x08 +#define BRDDAT2 0x04 +#define BRDRW 0x04 +#define BRDRW_ULTRA2 0x02 +#define BRDCTL1 0x02 +#define BRDSTB_ULTRA2 0x01 +#define BRDCTL0 0x01 + +#define SEECTL 0x1e +#define EXTARBACK 0x80 +#define EXTARBREQ 0x40 +#define SEEMS 0x20 +#define SEERDY 0x10 +#define SEECS 0x08 +#define SEECK 0x04 +#define SEEDO 0x02 +#define SEEDI 0x01 + +#define SBLKCTL 0x1f +#define DIAGLEDEN 0x80 +#define DIAGLEDON 0x40 +#define AUTOFLUSHDIS 0x20 +#define ENAB40 0x08 +#define ENAB20 0x04 +#define SELWIDE 0x02 +#define XCVR 0x01 + +#define SRAM_BASE 0x20 + +#define TARG_SCSIRATE 0x20 + +#define ULTRA_ENB 0x30 + +#define DISC_DSB 0x32 + +#define MSG_OUT 0x34 + +#define DMAPARAMS 0x35 +#define PRELOADEN 0x80 +#define WIDEODD 0x40 +#define SCSIEN 0x20 +#define SDMAENACK 0x10 +#define SDMAEN 0x10 +#define HDMAEN 0x08 +#define HDMAENACK 0x08 +#define DIRECTION 0x04 +#define FIFOFLUSH 0x02 +#define FIFORESET 0x01 + +#define SEQ_FLAGS 0x36 +#define IDENTIFY_SEEN 0x80 +#define SCBPTR_VALID 0x20 +#define DPHASE 0x10 +#define AMTARGET 0x08 +#define WIDE_BUS 0x02 +#define TWIN_BUS 0x01 + +#define SAVED_TCL 0x37 + +#define SG_COUNT 0x38 + +#define SG_NEXT 0x39 + +#define LASTPHASE 0x3d +#define P_MESGIN 0xe0 +#define PHASE_MASK 0xe0 +#define P_STATUS 0xc0 +#define P_MESGOUT 0xa0 +#define P_COMMAND 0x80 +#define CDI 0x80 +#define IOI 0x40 +#define P_DATAIN 0x40 +#define MSGI 0x20 +#define P_BUSFREE 0x01 +#define P_DATAOUT 0x00 + +#define WAITING_SCBH 0x3e + +#define DISCONNECTED_SCBH 0x3f + +#define FREE_SCBH 0x40 + +#define HSCB_ADDR 0x41 + +#define SCBID_ADDR 0x45 + +#define TMODE_CMDADDR 0x49 + +#define KERNEL_QINPOS 0x4d + +#define QINPOS 0x4e + +#define QOUTPOS 0x4f + +#define TMODE_CMDADDR_NEXT 0x50 + +#define ARG_1 0x51 +#define RETURN_1 0x51 +#define SEND_MSG 0x80 +#define SEND_SENSE 0x40 +#define SEND_REJ 0x20 +#define MSGOUT_PHASEMIS 0x10 + +#define ARG_2 0x52 +#define RETURN_2 0x52 + +#define LAST_MSG 0x53 + +#define PREFETCH_CNT 0x54 + +#define SCSICONF 0x5a +#define TERM_ENB 0x80 +#define RESET_SCSI 0x40 +#define HWSCSIID 0x0f +#define HSCSIID 0x07 + +#define HOSTCONF 0x5d + +#define HA_274_BIOSCTRL 0x5f +#define BIOSMODE 0x30 +#define BIOSDISABLED 0x30 +#define CHANNEL_B_PRIMARY 0x08 + +#define SEQCTL 0x60 +#define PERRORDIS 0x80 +#define PAUSEDIS 0x40 +#define FAILDIS 0x20 +#define FASTMODE 0x10 +#define BRKADRINTEN 0x08 +#define STEP 0x04 +#define SEQRESET 0x02 +#define LOADRAM 0x01 + +#define SEQRAM 0x61 + +#define SEQADDR0 0x62 + +#define SEQADDR1 0x63 +#define SEQADDR1_MASK 0x01 + +#define ACCUM 0x64 + +#define SINDEX 0x65 + +#define DINDEX 0x66 + +#define ALLONES 0x69 + +#define ALLZEROS 0x6a + +#define NONE 0x6a + +#define FLAGS 0x6b +#define ZERO 0x02 +#define CARRY 0x01 + +#define SINDIR 0x6c + +#define DINDIR 0x6d + +#define FUNCTION1 0x6e + +#define STACK 0x6f + +#define TARG_OFFSET 0x70 + +#define BCTL 0x84 +#define ACE 0x08 +#define ENABLE 0x01 + +#define DSCOMMAND0 0x84 +#define INTSCBRAMSEL 0x08 +#define RAMPS 0x04 +#define USCBSIZE32 0x02 +#define CIOPARCKEN 0x01 + +#define DSCOMMAND 0x84 +#define CACHETHEN 0x80 +#define DPARCKEN 0x40 +#define MPARCKEN 0x20 +#define EXTREQLCK 0x10 + +#define BUSTIME 0x85 +#define BOFF 0xf0 +#define BON 0x0f + +#define BUSSPD 0x86 +#define DFTHRSH 0xc0 +#define STBOFF 0x38 +#define STBON 0x07 + +#define DSPCISTATUS 0x86 +#define DFTHRSH_100 0xc0 + +#define HCNTRL 0x87 +#define POWRDN 0x40 +#define SWINT 0x10 +#define IRQMS 0x08 +#define PAUSE 0x04 +#define INTEN 0x02 +#define CHIPRST 0x01 +#define CHIPRSTACK 0x01 + +#define HADDR 0x88 + +#define HCNT 0x8c + +#define SCBPTR 0x90 + +#define INTSTAT 0x91 +#define SEQINT_MASK 0xf1 +#define DATA_OVERRUN 0xe1 +#define MSGIN_PHASEMIS 0xd1 +#define TRACEPOINT2 0xc1 +#define TRACEPOINT 0xb1 +#define AWAITING_MSG 0xa1 +#define RESIDUAL 0x81 +#define BAD_STATUS 0x71 +#define REJECT_MSG 0x61 +#define ABORT_REQUESTED 0x51 +#define EXTENDED_MSG 0x41 +#define NO_MATCH 0x31 +#define NO_IDENT 0x21 +#define SEND_REJECT 0x11 +#define INT_PEND 0x0f +#define BRKADRINT 0x08 +#define SCSIINT 0x04 +#define CMDCMPLT 0x02 +#define BAD_PHASE 0x01 +#define SEQINT 0x01 + +#define CLRINT 0x92 +#define CLRPARERR 0x10 +#define CLRBRKADRINT 0x08 +#define CLRSCSIINT 0x04 +#define CLRCMDINT 0x02 +#define CLRSEQINT 0x01 + +#define ERROR 0x92 +#define CIOPARERR 0x80 +#define PCIERRSTAT 0x40 +#define MPARERR 0x20 +#define DPARERR 0x10 +#define SQPARERR 0x08 +#define ILLOPCODE 0x04 +#define ILLSADDR 0x02 +#define ILLHADDR 0x01 + +#define DFCNTRL 0x93 + +#define DFSTATUS 0x94 +#define PRELOAD_AVAIL 0x80 +#define DWORDEMP 0x20 +#define MREQPEND 0x10 +#define HDONE 0x08 +#define DFTHRESH 0x04 +#define FIFOFULL 0x02 +#define FIFOEMP 0x01 + +#define DFDAT 0x99 + +#define SCBCNT 0x9a +#define SCBAUTO 0x80 +#define SCBCNT_MASK 0x1f + +#define QINFIFO 0x9b + +#define QINCNT 0x9c + +#define QOUTFIFO 0x9d + +#define QOUTCNT 0x9e + +#define SFUNCT 0x9f + +#define SCB_CONTROL 0xa0 +#define MK_MESSAGE 0x80 +#define DISCENB 0x40 +#define TAG_ENB 0x20 +#define DISCONNECTED 0x04 +#define SCB_TAG_TYPE 0x03 + +#define SCB_BASE 0xa0 + +#define SCB_TCL 0xa1 +#define TID 0xf0 +#define SELBUSB 0x08 +#define LID 0x07 + +#define SCB_TARGET_STATUS 0xa2 + +#define SCB_SGCOUNT 0xa3 + +#define SCB_SGPTR 0xa4 + +#define SCB_RESID_SGCNT 0xa8 + +#define SCB_RESID_DCNT 0xa9 + +#define SCB_DATAPTR 0xac + +#define SCB_DATACNT 0xb0 + +#define SCB_CMDPTR 0xb4 + +#define SCB_CMDLEN 0xb8 + +#define SCB_TAG 0xb9 + +#define SCB_NEXT 0xba + +#define SCB_PREV 0xbb + +#define SCB_BUSYTARGETS 0xbc + +#define SEECTL_2840 0xc0 +#define CS_2840 0x04 +#define CK_2840 0x02 +#define DO_2840 0x01 + +#define STATUS_2840 0xc1 +#define EEPROM_TF 0x80 +#define BIOS_SEL 0x60 +#define ADSEL 0x1e +#define DI_2840 0x01 + +#define CCHADDR 0xe0 + +#define CCHCNT 0xe8 + +#define CCSGRAM 0xe9 + +#define CCSGADDR 0xea + +#define CCSGCTL 0xeb +#define CCSGDONE 0x80 +#define CCSGEN 0x08 +#define FLAG 0x02 +#define CCSGRESET 0x01 + +#define CCSCBRAM 0xec + +#define CCSCBADDR 0xed + +#define CCSCBCTL 0xee +#define CCSCBDONE 0x80 +#define ARRDONE 0x40 +#define CCARREN 0x10 +#define CCSCBEN 0x08 +#define CCSCBDIR 0x04 +#define CCSCBRESET 0x01 + +#define CCSCBCNT 0xef + +#define CCSCBPTR 0xf1 + +#define HNSCB_QOFF 0xf4 + +#define SNSCB_QOFF 0xf6 + +#define SDSCB_QOFF 0xf8 + +#define QOFF_CTLSTA 0xfa +#define SCB_AVAIL 0x40 +#define SNSCB_ROLLOVER 0x20 +#define SDSCB_ROLLOVER 0x10 +#define SCB_QSIZE 0x07 +#define SCB_QSIZE_256 0x06 + +#define DFF_THRSH 0xfb +#define WR_DFTHRSH 0x70 +#define WR_DFTHRSH_MAX 0x70 +#define WR_DFTHRSH_90 0x60 +#define WR_DFTHRSH_85 0x50 +#define WR_DFTHRSH_75 0x40 +#define WR_DFTHRSH_63 0x30 +#define WR_DFTHRSH_50 0x20 +#define WR_DFTHRSH_25 0x10 +#define RD_DFTHRSH_MAX 0x07 +#define RD_DFTHRSH 0x07 +#define RD_DFTHRSH_90 0x06 +#define RD_DFTHRSH_85 0x05 +#define RD_DFTHRSH_75 0x04 +#define RD_DFTHRSH_63 0x03 +#define RD_DFTHRSH_50 0x02 +#define RD_DFTHRSH_25 0x01 +#define WR_DFTHRSH_MIN 0x00 +#define RD_DFTHRSH_MIN 0x00 + +#define SG_CACHEPTR 0xfc +#define SG_USER_DATA 0xfc +#define LAST_SEG 0x02 +#define LAST_SEG_DONE 0x01 + + +#define CMD_GROUP2_BYTE_DELTA 0xfa +#define MAX_OFFSET_8BIT 0x0f +#define BUS_16_BIT 0x01 +#define QINFIFO_OFFSET 0x02 +#define CMD_GROUP5_BYTE_DELTA 0x0b +#define CMD_GROUP_CODE_SHIFT 0x05 +#define MAX_OFFSET_ULTRA2 0x7f +#define MAX_OFFSET_16BIT 0x08 +#define BUS_8_BIT 0x00 +#define QOUTFIFO_OFFSET 0x01 +#define UNTAGGEDSCB_OFFSET 0x00 +#define CCSGRAM_MAXSEGS 0x10 +#define SCB_LIST_NULL 0xff +#define SG_SIZEOF 0x08 +#define CMD_GROUP4_BYTE_DELTA 0x04 +#define CMD_GROUP0_BYTE_DELTA 0xfc +#define HOST_MSG 0xff +#define BUS_32_BIT 0x02 +#define CCSGADDR_MAX 0x80 + + +/* Downloaded Constant Definitions */ +#define TMODE_NUMCMDS 0x00 diff --git a/linux/src/drivers/scsi/aic7xxx_seq.c b/linux/src/drivers/scsi/aic7xxx_seq.c new file mode 100644 index 00000000..9205cc4a --- /dev/null +++ b/linux/src/drivers/scsi/aic7xxx_seq.c @@ -0,0 +1,769 @@ +/* + * DO NOT EDIT - This file is automatically generated. + */ +static unsigned char seqprog[] = { + 0xff, 0x6a, 0x06, 0x08, + 0x32, 0x6a, 0x00, 0x00, + 0x12, 0x6a, 0x00, 0x00, + 0xff, 0x6a, 0xd6, 0x09, + 0xff, 0x6a, 0xdc, 0x09, + 0x00, 0x65, 0x38, 0x59, + 0xf7, 0x01, 0x02, 0x08, + 0xff, 0x4e, 0xc8, 0x08, + 0xbf, 0x60, 0xc0, 0x08, + 0x60, 0x0b, 0x7c, 0x68, + 0x40, 0x00, 0x0e, 0x68, + 0x08, 0x1f, 0x3e, 0x10, + 0x60, 0x0b, 0x7c, 0x68, + 0x40, 0x00, 0x0e, 0x68, + 0x08, 0x1f, 0x3e, 0x10, + 0xff, 0x3e, 0x3e, 0x60, + 0x40, 0xfa, 0x10, 0x78, + 0xff, 0xf6, 0xd4, 0x08, + 0x01, 0x4e, 0x9c, 0x18, + 0x40, 0x60, 0xc0, 0x00, + 0x00, 0x4d, 0x10, 0x70, + 0x01, 0x4e, 0x9c, 0x18, + 0xbf, 0x60, 0xc0, 0x08, + 0x00, 0x6a, 0x72, 0x5c, + 0xff, 0x4e, 0xc8, 0x18, + 0x02, 0x6a, 0x88, 0x5b, + 0xff, 0x52, 0x20, 0x09, + 0x0d, 0x6a, 0x6a, 0x00, + 0x00, 0x52, 0xfe, 0x5b, + 0xff, 0x3e, 0x74, 0x09, + 0xff, 0x90, 0x7c, 0x08, + 0xff, 0x3e, 0x20, 0x09, + 0x00, 0x65, 0x44, 0x58, + 0x00, 0x65, 0x0e, 0x40, + 0xf7, 0x1f, 0xca, 0x08, + 0x08, 0xa1, 0xc8, 0x08, + 0x00, 0x65, 0xca, 0x00, + 0xff, 0x65, 0x3e, 0x08, + 0xf0, 0xa1, 0xc8, 0x08, + 0x0f, 0x0f, 0x1e, 0x08, + 0x00, 0x0f, 0x1e, 0x00, + 0xf0, 0xa1, 0xc8, 0x08, + 0x0f, 0x05, 0x0a, 0x08, + 0x00, 0x05, 0x0a, 0x00, + 0x5a, 0x6a, 0x00, 0x04, + 0x12, 0x65, 0xc8, 0x00, + 0x00, 0x01, 0x02, 0x00, + 0x31, 0x6a, 0xca, 0x00, + 0x80, 0x37, 0x64, 0x68, + 0xff, 0x65, 0xca, 0x18, + 0xff, 0x37, 0xdc, 0x08, + 0xff, 0x6e, 0xc8, 0x08, + 0x00, 0x6c, 0x6c, 0x78, + 0x20, 0x01, 0x02, 0x00, + 0x4c, 0x37, 0xc8, 0x28, + 0x08, 0x1f, 0x74, 0x78, + 0x08, 0x37, 0x6e, 0x00, + 0x08, 0x64, 0xc8, 0x00, + 0x70, 0x64, 0xca, 0x18, + 0xff, 0x6c, 0x0a, 0x08, + 0x20, 0x64, 0xca, 0x18, + 0xff, 0x6c, 0x08, 0x0c, + 0x40, 0x0b, 0x04, 0x69, + 0x80, 0x0b, 0xf6, 0x78, + 0xa4, 0x6a, 0x06, 0x00, + 0x40, 0x6a, 0x16, 0x00, + 0x10, 0x03, 0xf2, 0x78, + 0xff, 0x50, 0xc8, 0x08, + 0x88, 0x6a, 0xcc, 0x00, + 0x49, 0x6a, 0xee, 0x5b, + 0x01, 0x6a, 0x26, 0x01, + 0xff, 0x6a, 0xca, 0x08, + 0x08, 0x01, 0x02, 0x00, + 0x02, 0x0b, 0x92, 0x78, + 0xf7, 0x01, 0x02, 0x08, + 0xff, 0x06, 0xcc, 0x08, + 0xff, 0x66, 0x32, 0x09, + 0x01, 0x65, 0xca, 0x18, + 0x80, 0x66, 0xa0, 0x78, + 0xff, 0x66, 0xa2, 0x08, + 0x10, 0x03, 0x90, 0x68, + 0xfc, 0x65, 0xc8, 0x18, + 0x00, 0x65, 0xa8, 0x48, + 0xff, 0x6a, 0x32, 0x01, + 0x01, 0x64, 0x18, 0x19, + 0xff, 0x6a, 0x1a, 0x09, + 0xff, 0x6a, 0x1c, 0x09, + 0x84, 0x6a, 0x06, 0x00, + 0x08, 0x01, 0x02, 0x00, + 0x02, 0x0b, 0xb2, 0x78, + 0xff, 0x06, 0xc8, 0x08, + 0xff, 0x64, 0x32, 0x09, + 0xff, 0x6a, 0xca, 0x08, + 0x5b, 0x64, 0xc8, 0x28, + 0x00, 0x62, 0xc4, 0x18, + 0xfc, 0x65, 0xca, 0x18, + 0xff, 0x6a, 0xd4, 0x08, + 0xfa, 0x65, 0xca, 0x18, + 0xff, 0x6a, 0xd4, 0x08, + 0x04, 0x65, 0xca, 0x18, + 0x0b, 0x65, 0xca, 0x18, + 0xff, 0x65, 0xc8, 0x08, + 0x00, 0x8c, 0x18, 0x19, + 0x02, 0x0b, 0xce, 0x78, + 0x01, 0x65, 0xd4, 0x60, + 0xf7, 0x01, 0x02, 0x08, + 0xff, 0x06, 0x32, 0x09, + 0xff, 0x65, 0xca, 0x18, + 0xff, 0x65, 0xce, 0x68, + 0x0a, 0x93, 0x26, 0x01, + 0x00, 0x65, 0x64, 0x5c, + 0x40, 0x51, 0xe6, 0x78, + 0xe4, 0x6a, 0x06, 0x00, + 0x08, 0x01, 0x02, 0x00, + 0x04, 0x6a, 0x18, 0x5b, + 0x01, 0x50, 0xa0, 0x18, + 0x00, 0x50, 0xec, 0xe0, + 0xff, 0x6a, 0xa0, 0x08, + 0xff, 0x6a, 0x3a, 0x01, + 0x02, 0x6a, 0x22, 0x01, + 0x40, 0x51, 0xf2, 0x68, + 0xff, 0x6a, 0x06, 0x08, + 0x00, 0x65, 0x0e, 0x40, + 0x20, 0x6a, 0x16, 0x00, + 0xf0, 0x19, 0x6e, 0x08, + 0x08, 0x6a, 0x18, 0x00, + 0x08, 0x11, 0x22, 0x00, + 0x08, 0x6a, 0x5a, 0x58, + 0x08, 0x6a, 0x68, 0x00, + 0x00, 0x65, 0x18, 0x41, + 0x12, 0x6a, 0x00, 0x00, + 0x40, 0x6a, 0x16, 0x00, + 0xff, 0x3e, 0x20, 0x09, + 0xff, 0xba, 0x7c, 0x08, + 0xff, 0xa1, 0x6e, 0x08, + 0x08, 0x6a, 0x18, 0x00, + 0x08, 0x11, 0x22, 0x00, + 0x08, 0x6a, 0x5a, 0x58, + 0x80, 0x6a, 0x68, 0x00, + 0x80, 0x36, 0x6c, 0x00, + 0x00, 0x65, 0xd2, 0x5b, + 0xff, 0x3d, 0xc8, 0x08, + 0xbf, 0x64, 0x48, 0x79, + 0x80, 0x64, 0xf0, 0x71, + 0xa0, 0x64, 0x0e, 0x72, + 0xc0, 0x64, 0x08, 0x72, + 0xe0, 0x64, 0x52, 0x72, + 0x01, 0x6a, 0x22, 0x01, + 0x00, 0x65, 0x18, 0x41, + 0xf7, 0x11, 0x22, 0x08, + 0x00, 0x65, 0x38, 0x59, + 0xff, 0x06, 0xd4, 0x08, + 0xf7, 0x01, 0x02, 0x08, + 0x09, 0x0c, 0x32, 0x79, + 0x08, 0x0c, 0x0e, 0x68, + 0x01, 0x6a, 0x22, 0x01, + 0xff, 0x6a, 0x26, 0x09, + 0xff, 0x6a, 0x08, 0x08, + 0xdf, 0x01, 0x02, 0x08, + 0x01, 0x6a, 0x7a, 0x00, + 0x03, 0x36, 0x6c, 0x0c, + 0x08, 0x6a, 0xcc, 0x00, + 0xa9, 0x6a, 0xe8, 0x5b, + 0x00, 0x65, 0x66, 0x41, + 0xa8, 0x6a, 0x6a, 0x00, + 0x79, 0x6a, 0x6a, 0x00, + 0x40, 0x3d, 0x50, 0x69, + 0x04, 0x35, 0x6a, 0x00, + 0x00, 0x65, 0x3a, 0x5b, + 0x80, 0x6a, 0xd4, 0x01, + 0x10, 0x36, 0x42, 0x69, + 0x10, 0x36, 0x6c, 0x00, + 0x07, 0xac, 0x10, 0x31, + 0x88, 0x6a, 0xcc, 0x00, + 0xac, 0x6a, 0xe0, 0x5b, + 0x00, 0x65, 0xda, 0x5b, + 0xff, 0xa3, 0x70, 0x08, + 0x39, 0x6a, 0xcc, 0x00, + 0xa4, 0x6a, 0xe6, 0x5b, + 0xff, 0x38, 0x74, 0x69, + 0x80, 0x02, 0x04, 0x00, + 0xe7, 0x35, 0x6a, 0x08, + 0x03, 0x69, 0x18, 0x31, + 0xff, 0x6a, 0x10, 0x00, + 0xff, 0x6a, 0x12, 0x00, + 0xff, 0x6a, 0x14, 0x00, + 0x01, 0x38, 0x7a, 0x61, + 0x02, 0xfc, 0xf8, 0x01, + 0xbf, 0x35, 0x6a, 0x08, + 0xff, 0x69, 0xca, 0x08, + 0xff, 0x35, 0x26, 0x09, + 0x04, 0x0b, 0x7e, 0x69, + 0x04, 0x0b, 0x8a, 0x69, + 0x10, 0x0c, 0x80, 0x79, + 0x04, 0x0b, 0x88, 0x69, + 0xff, 0x6a, 0xca, 0x08, + 0x00, 0x35, 0x22, 0x5b, + 0x80, 0x02, 0xd6, 0x69, + 0xff, 0x65, 0xc8, 0x79, + 0xff, 0x38, 0x70, 0x18, + 0xff, 0x38, 0xc8, 0x79, + 0x80, 0xea, 0xaa, 0x61, + 0xef, 0x38, 0xc8, 0x18, + 0x80, 0x6a, 0xc8, 0x00, + 0x00, 0x65, 0x9c, 0x49, + 0x33, 0x38, 0xc8, 0x28, + 0xff, 0x64, 0xd0, 0x09, + 0x04, 0x39, 0xc0, 0x31, + 0x09, 0x6a, 0xd6, 0x01, + 0x80, 0xeb, 0xa2, 0x79, + 0xf7, 0xeb, 0xd6, 0x09, + 0x08, 0xeb, 0xa6, 0x69, + 0x01, 0x6a, 0xd6, 0x01, + 0x08, 0xe9, 0x10, 0x31, + 0x88, 0x6a, 0xcc, 0x00, + 0x39, 0x6a, 0xe6, 0x5b, + 0x08, 0x6a, 0x18, 0x01, + 0xff, 0x6a, 0x1a, 0x09, + 0xff, 0x6a, 0x1c, 0x09, + 0x0d, 0x93, 0x26, 0x01, + 0x00, 0x65, 0x64, 0x5c, + 0x88, 0x6a, 0x54, 0x5c, + 0x00, 0x65, 0xda, 0x5b, + 0xff, 0x6a, 0xc8, 0x08, + 0x08, 0x39, 0x72, 0x18, + 0x00, 0x3a, 0x74, 0x20, + 0x10, 0x0c, 0x66, 0x79, + 0x80, 0x93, 0x26, 0x01, + 0x00, 0x65, 0xe0, 0x59, + 0xff, 0x08, 0x52, 0x09, + 0xff, 0x09, 0x54, 0x09, + 0xff, 0x0a, 0x56, 0x09, + 0xff, 0x38, 0x50, 0x09, + 0x12, 0x01, 0x02, 0x00, + 0x00, 0x65, 0x18, 0x41, + 0x00, 0x65, 0xe0, 0x59, + 0x12, 0x01, 0x02, 0x00, + 0x7f, 0x02, 0x04, 0x08, + 0xe1, 0x6a, 0x22, 0x01, + 0x00, 0x65, 0x18, 0x41, + 0x04, 0x93, 0xea, 0x69, + 0xdf, 0x93, 0x26, 0x09, + 0x20, 0x93, 0xe4, 0x69, + 0x02, 0x93, 0x26, 0x01, + 0x01, 0x94, 0xe6, 0x79, + 0xd7, 0x93, 0x26, 0x09, + 0x08, 0x93, 0xec, 0x69, + 0xff, 0x6a, 0xd4, 0x0c, + 0x00, 0x65, 0x3a, 0x5b, + 0x02, 0xfc, 0xf8, 0x01, + 0x05, 0xb4, 0x10, 0x31, + 0x02, 0x6a, 0x1a, 0x31, + 0x88, 0x6a, 0xcc, 0x00, + 0xb4, 0x6a, 0xe4, 0x5b, + 0xff, 0x6a, 0x1a, 0x09, + 0xff, 0x6a, 0x1c, 0x09, + 0x00, 0x65, 0xda, 0x5b, + 0x3d, 0x6a, 0x22, 0x5b, + 0xac, 0x6a, 0x22, 0x5b, + 0x00, 0x65, 0x18, 0x41, + 0x00, 0x65, 0x3a, 0x5b, + 0xff, 0x06, 0x44, 0x09, + 0x00, 0x65, 0x18, 0x41, + 0xff, 0x34, 0xca, 0x08, + 0x80, 0x65, 0x32, 0x62, + 0x0f, 0xa1, 0xca, 0x08, + 0x07, 0xa1, 0xca, 0x08, + 0x40, 0xa0, 0xc8, 0x08, + 0x00, 0x65, 0xca, 0x00, + 0x80, 0x65, 0xca, 0x00, + 0x80, 0xa0, 0x22, 0x7a, + 0xff, 0x65, 0x0c, 0x08, + 0x00, 0x65, 0x34, 0x42, + 0x20, 0xa0, 0x3a, 0x7a, + 0xff, 0x65, 0x0c, 0x08, + 0x00, 0x65, 0xd2, 0x5b, + 0xa0, 0x3d, 0x46, 0x62, + 0x23, 0xa0, 0x0c, 0x08, + 0x00, 0x65, 0xd2, 0x5b, + 0xa0, 0x3d, 0x46, 0x62, + 0x00, 0xb9, 0x3a, 0x42, + 0xff, 0x65, 0x3a, 0x62, + 0xa1, 0x6a, 0x22, 0x01, + 0xff, 0x6a, 0xd4, 0x08, + 0x10, 0x51, 0x46, 0x72, + 0x40, 0x6a, 0x18, 0x00, + 0xff, 0x65, 0x0c, 0x08, + 0x00, 0x65, 0xd2, 0x5b, + 0xa0, 0x3d, 0x46, 0x62, + 0x10, 0x3d, 0x06, 0x00, + 0x00, 0x65, 0x0e, 0x42, + 0x40, 0x6a, 0x18, 0x00, + 0xff, 0x34, 0xa6, 0x08, + 0x80, 0x34, 0x4e, 0x62, + 0x7f, 0xa0, 0x40, 0x09, + 0x08, 0x6a, 0x68, 0x00, + 0x00, 0x65, 0x18, 0x41, + 0x64, 0x6a, 0x12, 0x5b, + 0x80, 0x64, 0xbe, 0x6a, + 0x04, 0x64, 0xa4, 0x72, + 0x02, 0x64, 0xaa, 0x72, + 0x00, 0x6a, 0x6c, 0x72, + 0x03, 0x64, 0xba, 0x72, + 0x01, 0x64, 0xa0, 0x72, + 0x07, 0x64, 0x00, 0x73, + 0x08, 0x64, 0x68, 0x72, + 0x11, 0x6a, 0x22, 0x01, + 0x07, 0x6a, 0x04, 0x5b, + 0xff, 0x06, 0xd4, 0x08, + 0x00, 0x65, 0x18, 0x41, + 0xff, 0xa8, 0x70, 0x6a, + 0xff, 0xa2, 0x88, 0x7a, + 0x01, 0x6a, 0x6a, 0x00, + 0x00, 0xb9, 0xfe, 0x5b, + 0xff, 0xa2, 0x88, 0x7a, + 0x71, 0x6a, 0x22, 0x01, + 0xff, 0x6a, 0xd4, 0x08, + 0x40, 0x51, 0x88, 0x62, + 0x0d, 0x6a, 0x6a, 0x00, + 0x00, 0xb9, 0xfe, 0x5b, + 0xff, 0x3e, 0x74, 0x09, + 0xff, 0x90, 0x7c, 0x08, + 0x00, 0x65, 0x44, 0x58, + 0x00, 0x65, 0x2a, 0x41, + 0x20, 0xa0, 0x90, 0x6a, + 0xff, 0x37, 0xc8, 0x08, + 0x00, 0x6a, 0xa8, 0x5b, + 0xff, 0x6a, 0xbe, 0x5b, + 0xff, 0xf8, 0xc8, 0x08, + 0xff, 0x4f, 0xc8, 0x08, + 0x01, 0x6a, 0xa8, 0x5b, + 0x00, 0xb9, 0xbe, 0x5b, + 0x01, 0x4f, 0x9e, 0x18, + 0x02, 0x6a, 0x22, 0x01, + 0x00, 0x65, 0x6c, 0x5c, + 0x00, 0x65, 0x2a, 0x41, + 0x41, 0x6a, 0x22, 0x01, + 0x00, 0x65, 0x18, 0x41, + 0x04, 0xa0, 0x40, 0x01, + 0x00, 0x65, 0x84, 0x5c, + 0x00, 0x65, 0x2a, 0x41, + 0x10, 0x36, 0x68, 0x7a, + 0xff, 0x38, 0x46, 0x09, + 0xa4, 0x6a, 0xcc, 0x00, + 0x39, 0x6a, 0xe6, 0x5b, + 0xac, 0x6a, 0xcc, 0x00, + 0x14, 0x6a, 0xe6, 0x5b, + 0xa9, 0x6a, 0xe8, 0x5b, + 0x00, 0x65, 0x68, 0x42, + 0xef, 0x36, 0x6c, 0x08, + 0x00, 0x65, 0x68, 0x42, + 0x0f, 0x64, 0xc8, 0x08, + 0x07, 0x64, 0xc8, 0x08, + 0x00, 0x37, 0x6e, 0x00, + 0x00, 0x65, 0x78, 0x5b, + 0xff, 0x51, 0xce, 0x72, + 0x20, 0x36, 0xde, 0x7a, + 0x00, 0x90, 0x5c, 0x5b, + 0x00, 0x65, 0xe0, 0x42, + 0xff, 0x06, 0xd4, 0x08, + 0x00, 0x65, 0xd2, 0x5b, + 0xe0, 0x3d, 0xfa, 0x62, + 0x20, 0x12, 0xfa, 0x62, + 0x51, 0x6a, 0x08, 0x5b, + 0xff, 0x51, 0x20, 0x09, + 0x20, 0xa0, 0xfa, 0x7a, + 0x00, 0x90, 0x5c, 0x5b, + 0x00, 0x65, 0x56, 0x5b, + 0xff, 0x37, 0xc8, 0x08, + 0x00, 0xa1, 0xf2, 0x62, + 0x04, 0xa0, 0xf2, 0x7a, + 0xfb, 0xa0, 0x40, 0x09, + 0x80, 0x36, 0x6c, 0x00, + 0x80, 0xa0, 0x68, 0x7a, + 0x7f, 0xa0, 0x40, 0x09, + 0xff, 0x6a, 0x04, 0x5b, + 0x00, 0x65, 0x68, 0x42, + 0x04, 0xa0, 0xf8, 0x7a, + 0x00, 0x65, 0x84, 0x5c, + 0x00, 0x65, 0xfa, 0x42, + 0x00, 0x65, 0x6c, 0x5c, + 0x31, 0x6a, 0x22, 0x01, + 0x0c, 0x6a, 0x04, 0x5b, + 0x00, 0x65, 0x68, 0x42, + 0x61, 0x6a, 0x22, 0x01, + 0x00, 0x65, 0x68, 0x42, + 0x10, 0x3d, 0x06, 0x00, + 0xff, 0x65, 0x68, 0x0c, + 0xff, 0x06, 0xd4, 0x08, + 0x01, 0x0c, 0x0a, 0x7b, + 0x04, 0x0c, 0x0a, 0x6b, + 0xe0, 0x03, 0x7a, 0x08, + 0xe0, 0x3d, 0x1e, 0x63, + 0xff, 0x65, 0xcc, 0x08, + 0xff, 0x12, 0xda, 0x0c, + 0xff, 0x06, 0xd4, 0x0c, + 0xff, 0x65, 0x0c, 0x08, + 0x02, 0x0b, 0x1a, 0x7b, + 0xff, 0x6a, 0xd4, 0x0c, + 0xd1, 0x6a, 0x22, 0x01, + 0x00, 0x65, 0x18, 0x41, + 0xff, 0x65, 0x26, 0x09, + 0x01, 0x0b, 0x32, 0x6b, + 0x10, 0x0c, 0x24, 0x7b, + 0x04, 0x0b, 0x2c, 0x6b, + 0xff, 0x6a, 0xca, 0x08, + 0x04, 0x93, 0x30, 0x6b, + 0x01, 0x94, 0x2e, 0x7b, + 0x10, 0x94, 0x30, 0x6b, + 0xc7, 0x93, 0x26, 0x09, + 0xff, 0x99, 0xd4, 0x08, + 0x08, 0x93, 0x34, 0x6b, + 0xff, 0x6a, 0xd4, 0x0c, + 0x80, 0x36, 0x38, 0x6b, + 0x21, 0x6a, 0x22, 0x05, + 0xff, 0x65, 0x20, 0x09, + 0xff, 0x51, 0x46, 0x63, + 0xff, 0x37, 0xc8, 0x08, + 0xa1, 0x6a, 0x50, 0x43, + 0xff, 0x51, 0xc8, 0x08, + 0xb9, 0x6a, 0x50, 0x43, + 0xff, 0xba, 0x54, 0x73, + 0xff, 0xba, 0x20, 0x09, + 0xff, 0x65, 0xca, 0x18, + 0x00, 0x6c, 0x4a, 0x63, + 0xff, 0x90, 0xca, 0x0c, + 0xff, 0x6a, 0xca, 0x04, + 0x20, 0x36, 0x72, 0x7b, + 0x00, 0x90, 0x3e, 0x5b, + 0xff, 0x65, 0x72, 0x73, + 0xff, 0xba, 0x66, 0x73, + 0xff, 0xbb, 0xcc, 0x08, + 0xff, 0xba, 0x20, 0x09, + 0xff, 0x66, 0x76, 0x09, + 0xff, 0x65, 0x20, 0x09, + 0xff, 0xbb, 0x70, 0x73, + 0xff, 0xba, 0xcc, 0x08, + 0xff, 0xbb, 0x20, 0x09, + 0xff, 0x66, 0x74, 0x09, + 0xff, 0x65, 0x20, 0x0d, + 0xff, 0xba, 0x7e, 0x0c, + 0x00, 0x6a, 0x72, 0x5c, + 0x0d, 0x6a, 0x6a, 0x00, + 0x00, 0x51, 0xfe, 0x43, + 0xff, 0x3f, 0xcc, 0x73, + 0xff, 0x6a, 0xa2, 0x00, + 0x00, 0x3f, 0x3e, 0x5b, + 0xff, 0x65, 0xcc, 0x73, + 0x20, 0x36, 0x6c, 0x00, + 0x20, 0xa0, 0x86, 0x6b, + 0xff, 0xb9, 0xa2, 0x0c, + 0xff, 0x6a, 0xa2, 0x04, + 0xff, 0x65, 0xa4, 0x08, + 0xe0, 0x6a, 0xcc, 0x00, + 0x45, 0x6a, 0xf2, 0x5b, + 0x01, 0x6a, 0xd0, 0x01, + 0x09, 0x6a, 0xd6, 0x01, + 0x80, 0xeb, 0x92, 0x7b, + 0x01, 0x6a, 0xd6, 0x01, + 0x01, 0xe9, 0xa4, 0x34, + 0x88, 0x6a, 0xcc, 0x00, + 0x45, 0x6a, 0xf2, 0x5b, + 0x01, 0x6a, 0x18, 0x01, + 0xff, 0x6a, 0x1a, 0x09, + 0xff, 0x6a, 0x1c, 0x09, + 0x0d, 0x6a, 0x26, 0x01, + 0x00, 0x65, 0x64, 0x5c, + 0xff, 0x99, 0xa4, 0x0c, + 0xff, 0x65, 0xa4, 0x08, + 0xe0, 0x6a, 0xcc, 0x00, + 0x45, 0x6a, 0xf2, 0x5b, + 0x01, 0x6a, 0xd0, 0x01, + 0x01, 0x6a, 0xdc, 0x05, + 0x88, 0x6a, 0xcc, 0x00, + 0x45, 0x6a, 0xf2, 0x5b, + 0x01, 0x6a, 0x18, 0x01, + 0xff, 0x6a, 0x1a, 0x09, + 0xff, 0x6a, 0x1c, 0x09, + 0x01, 0x6a, 0x26, 0x05, + 0x01, 0x65, 0xd8, 0x31, + 0x09, 0xee, 0xdc, 0x01, + 0x80, 0xee, 0xc2, 0x7b, + 0xff, 0x6a, 0xdc, 0x0d, + 0xff, 0x65, 0x32, 0x09, + 0x0a, 0x93, 0x26, 0x01, + 0x00, 0x65, 0x64, 0x44, + 0xff, 0x37, 0xc8, 0x08, + 0x00, 0x6a, 0x88, 0x5b, + 0xff, 0x52, 0xa2, 0x0c, + 0x01, 0x0c, 0xd2, 0x7b, + 0x04, 0x0c, 0xd2, 0x6b, + 0xe0, 0x03, 0x7a, 0x08, + 0xff, 0x3d, 0x06, 0x0c, + 0xff, 0x8c, 0x10, 0x08, + 0xff, 0x8d, 0x12, 0x08, + 0xff, 0x8e, 0x14, 0x0c, + 0xff, 0x6c, 0xda, 0x08, + 0xff, 0x6c, 0xda, 0x08, + 0xff, 0x6c, 0xda, 0x08, + 0xff, 0x6c, 0xda, 0x08, + 0xff, 0x6c, 0xda, 0x08, + 0xff, 0x6c, 0xda, 0x08, + 0xff, 0x6c, 0xda, 0x0c, + 0x3d, 0x64, 0xa4, 0x28, + 0x55, 0x64, 0xc8, 0x28, + 0x00, 0x6c, 0xda, 0x18, + 0xff, 0x52, 0xc8, 0x08, + 0x00, 0x6c, 0xda, 0x20, + 0xff, 0x6a, 0xc8, 0x08, + 0x00, 0x6c, 0xda, 0x20, + 0x00, 0x6c, 0xda, 0x24, + 0xff, 0x65, 0xc8, 0x08, + 0xe0, 0x6a, 0xcc, 0x00, + 0x41, 0x6a, 0xee, 0x5b, + 0xff, 0x90, 0xe2, 0x09, + 0x20, 0x6a, 0xd0, 0x01, + 0x04, 0x35, 0x10, 0x7c, + 0x1d, 0x6a, 0xdc, 0x01, + 0xdc, 0xee, 0x0c, 0x64, + 0x00, 0x65, 0x1c, 0x44, + 0x01, 0x6a, 0xdc, 0x01, + 0x20, 0xa0, 0xd8, 0x31, + 0x09, 0xee, 0xdc, 0x01, + 0x80, 0xee, 0x16, 0x7c, + 0x19, 0x6a, 0xdc, 0x01, + 0xd8, 0xee, 0x1a, 0x64, + 0xff, 0x6a, 0xdc, 0x09, + 0x18, 0xee, 0x1e, 0x6c, + 0xff, 0x6a, 0xd4, 0x0c, + 0x88, 0x6a, 0xcc, 0x00, + 0x41, 0x6a, 0xee, 0x5b, + 0x20, 0x6a, 0x18, 0x01, + 0xff, 0x6a, 0x1a, 0x09, + 0xff, 0x6a, 0x1c, 0x09, + 0xff, 0x35, 0x26, 0x09, + 0x04, 0x35, 0x48, 0x6c, + 0xa0, 0x6a, 0xca, 0x00, + 0x20, 0x65, 0xc8, 0x18, + 0xff, 0x6c, 0x32, 0x09, + 0xff, 0x6c, 0x32, 0x09, + 0xff, 0x6c, 0x32, 0x09, + 0xff, 0x6c, 0x32, 0x09, + 0xff, 0x6c, 0x32, 0x09, + 0xff, 0x6c, 0x32, 0x09, + 0xff, 0x6c, 0x32, 0x09, + 0xff, 0x6c, 0x32, 0x09, + 0x00, 0x65, 0x34, 0x64, + 0x0a, 0x93, 0x26, 0x01, + 0x00, 0x65, 0x64, 0x5c, + 0x04, 0x35, 0x38, 0x7b, + 0xa0, 0x6a, 0x54, 0x5c, + 0x00, 0x65, 0x56, 0x5c, + 0x00, 0x65, 0x56, 0x5c, + 0x00, 0x65, 0x56, 0x44, + 0xff, 0x65, 0xcc, 0x08, + 0xff, 0x99, 0xda, 0x08, + 0xff, 0x99, 0xda, 0x08, + 0xff, 0x99, 0xda, 0x08, + 0xff, 0x99, 0xda, 0x08, + 0xff, 0x99, 0xda, 0x08, + 0xff, 0x99, 0xda, 0x08, + 0xff, 0x99, 0xda, 0x0c, + 0x08, 0x94, 0x64, 0x7c, + 0xf7, 0x93, 0x26, 0x09, + 0x08, 0x93, 0x68, 0x6c, + 0xff, 0x6a, 0xd4, 0x0c, + 0xff, 0x40, 0x74, 0x09, + 0xff, 0x90, 0x80, 0x08, + 0xff, 0x6a, 0x72, 0x05, + 0xff, 0x40, 0x80, 0x64, + 0xff, 0x3f, 0x78, 0x64, + 0xff, 0x6a, 0xca, 0x04, + 0xff, 0x3f, 0x20, 0x09, + 0x01, 0x6a, 0x6a, 0x00, + 0x00, 0xb9, 0xfe, 0x5b, + 0x00, 0x90, 0x5c, 0x43, + 0xff, 0x40, 0x20, 0x09, + 0xff, 0xba, 0x80, 0x0c, + 0xff, 0x6a, 0x76, 0x01, + 0xff, 0x3f, 0x74, 0x09, + 0xff, 0x90, 0x7e, 0x08, + 0xff, 0xba, 0x38, 0x73, + 0xff, 0xba, 0x20, 0x09, + 0xff, 0x3f, 0x76, 0x09, + 0xff, 0x3f, 0x20, 0x0d, +}; + +static int aic7xxx_patch12_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch12_func(struct aic7xxx_host *p) +{ + return ((p->chip & AHC_CHIPID_MASK) == AHC_AIC7895); +} + +static int aic7xxx_patch11_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch11_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_WIDE) != 0); +} + +static int aic7xxx_patch10_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch10_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_ULTRA2) == 0); +} + +static int aic7xxx_patch9_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch9_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_ULTRA) != 0); +} + +static int aic7xxx_patch8_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch8_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_ULTRA2) != 0); +} + +static int aic7xxx_patch7_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch7_func(struct aic7xxx_host *p) +{ + return ((p->flags & AHC_PAGESCBS) == 0); +} + +static int aic7xxx_patch6_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch6_func(struct aic7xxx_host *p) +{ + return ((p->flags & AHC_PAGESCBS) != 0); +} + +static int aic7xxx_patch5_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch5_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_QUEUE_REGS) != 0); +} + +static int aic7xxx_patch4_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch4_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_TWIN) != 0); +} + +static int aic7xxx_patch3_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch3_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_QUEUE_REGS) == 0); +} + +static int aic7xxx_patch2_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch2_func(struct aic7xxx_host *p) +{ + return ((p->features & AHC_CMD_CHAN) != 0); +} + +static int aic7xxx_patch1_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch1_func(struct aic7xxx_host *p) +{ + return ((p->flags & AHC_TARGETMODE) != 0); +} + +static int aic7xxx_patch0_func(struct aic7xxx_host *p); + +static int +aic7xxx_patch0_func(struct aic7xxx_host *p) +{ + return (0); +} + +struct sequencer_patch { + int (*patch_func)(struct aic7xxx_host *); + unsigned int begin :10, + skip_instr :10, + skip_patch :12; +} sequencer_patches[] = { + { aic7xxx_patch1_func, 1, 1, 2 }, + { aic7xxx_patch0_func, 2, 1, 1 }, + { aic7xxx_patch2_func, 3, 2, 1 }, + { aic7xxx_patch3_func, 7, 1, 1 }, + { aic7xxx_patch3_func, 8, 1, 1 }, + { aic7xxx_patch4_func, 11, 4, 1 }, + { aic7xxx_patch5_func, 16, 3, 2 }, + { aic7xxx_patch0_func, 19, 4, 1 }, + { aic7xxx_patch6_func, 23, 1, 1 }, + { aic7xxx_patch7_func, 26, 1, 1 }, + { aic7xxx_patch4_func, 34, 4, 1 }, + { aic7xxx_patch8_func, 38, 3, 2 }, + { aic7xxx_patch0_func, 41, 3, 1 }, + { aic7xxx_patch9_func, 47, 7, 1 }, + { aic7xxx_patch4_func, 55, 3, 1 }, + { aic7xxx_patch8_func, 58, 2, 1 }, + { aic7xxx_patch1_func, 63, 60, 1 }, + { aic7xxx_patch8_func, 164, 1, 2 }, + { aic7xxx_patch0_func, 165, 1, 1 }, + { aic7xxx_patch2_func, 169, 1, 1 }, + { aic7xxx_patch2_func, 172, 1, 2 }, + { aic7xxx_patch0_func, 173, 2, 1 }, + { aic7xxx_patch10_func, 175, 1, 1 }, + { aic7xxx_patch8_func, 182, 1, 2 }, + { aic7xxx_patch0_func, 183, 3, 1 }, + { aic7xxx_patch8_func, 187, 1, 2 }, + { aic7xxx_patch0_func, 188, 1, 1 }, + { aic7xxx_patch8_func, 189, 7, 2 }, + { aic7xxx_patch0_func, 196, 1, 1 }, + { aic7xxx_patch2_func, 201, 13, 2 }, + { aic7xxx_patch0_func, 214, 8, 1 }, + { aic7xxx_patch10_func, 222, 1, 1 }, + { aic7xxx_patch8_func, 227, 1, 1 }, + { aic7xxx_patch8_func, 228, 1, 1 }, + { aic7xxx_patch8_func, 233, 1, 1 }, + { aic7xxx_patch8_func, 235, 2, 1 }, + { aic7xxx_patch8_func, 240, 8, 1 }, + { aic7xxx_patch8_func, 249, 1, 1 }, + { aic7xxx_patch2_func, 250, 2, 2 }, + { aic7xxx_patch0_func, 252, 4, 1 }, + { aic7xxx_patch10_func, 256, 2, 2 }, + { aic7xxx_patch0_func, 258, 1, 1 }, + { aic7xxx_patch11_func, 265, 1, 2 }, + { aic7xxx_patch0_func, 266, 1, 1 }, + { aic7xxx_patch5_func, 328, 1, 2 }, + { aic7xxx_patch0_func, 329, 1, 1 }, + { aic7xxx_patch3_func, 332, 1, 1 }, + { aic7xxx_patch11_func, 351, 1, 2 }, + { aic7xxx_patch0_func, 352, 1, 1 }, + { aic7xxx_patch6_func, 356, 1, 1 }, + { aic7xxx_patch7_func, 364, 3, 2 }, + { aic7xxx_patch0_func, 367, 1, 1 }, + { aic7xxx_patch1_func, 396, 3, 1 }, + { aic7xxx_patch10_func, 410, 1, 1 }, + { aic7xxx_patch2_func, 453, 7, 2 }, + { aic7xxx_patch0_func, 460, 8, 1 }, + { aic7xxx_patch2_func, 469, 4, 2 }, + { aic7xxx_patch0_func, 473, 6, 1 }, + { aic7xxx_patch2_func, 479, 4, 2 }, + { aic7xxx_patch0_func, 483, 3, 1 }, + { aic7xxx_patch2_func, 512, 17, 4 }, + { aic7xxx_patch12_func, 520, 4, 2 }, + { aic7xxx_patch0_func, 524, 2, 1 }, + { aic7xxx_patch0_func, 529, 33, 1 }, + { aic7xxx_patch6_func, 566, 2, 1 }, + { aic7xxx_patch6_func, 569, 9, 1 }, + +}; diff --git a/linux/src/drivers/scsi/constants.c b/linux/src/drivers/scsi/constants.c new file mode 100644 index 00000000..1495a5d8 --- /dev/null +++ b/linux/src/drivers/scsi/constants.c @@ -0,0 +1,683 @@ +/* + * ASCII values for a number of symbolic constants, printing functions, + * etc. + */ + +/* + * Don't import our own symbols, as this would severely mess up our + * symbol tables. + */ +#define _SCSI_SYMS_VER_ +#define __NO_VERSION__ +#include <linux/module.h> + +#include <linux/config.h> +#include <linux/blk.h> +#include <linux/kernel.h> +#include "scsi.h" +#include "hosts.h" + +#define CONST_COMMAND 0x01 +#define CONST_STATUS 0x02 +#define CONST_SENSE 0x04 +#define CONST_XSENSE 0x08 +#define CONST_CMND 0x10 +#define CONST_MSG 0x20 +#define CONST_HOST 0x40 +#define CONST_DRIVER 0x80 + +static const char unknown[] = "UNKNOWN"; + +#ifdef CONFIG_SCSI_CONSTANTS +#ifdef CONSTANTS +#undef CONSTANTS +#endif +#define CONSTANTS (CONST_COMMAND | CONST_STATUS | CONST_SENSE | CONST_XSENSE \ + | CONST_CMND | CONST_MSG | CONST_HOST | CONST_DRIVER) +#endif + +#if (CONSTANTS & CONST_COMMAND) +static const char * group_0_commands[] = { +/* 00-03 */ "Test Unit Ready", "Rezero Unit", unknown, "Request Sense", +/* 04-07 */ "Format Unit", "Read Block Limits", unknown, "Reasssign Blocks", +/* 08-0d */ "Read (6)", unknown, "Write (6)", "Seek (6)", unknown, unknown, +/* 0e-12 */ unknown, "Read Reverse", "Write Filemarks", "Space", "Inquiry", +/* 13-16 */ unknown, "Recover Buffered Data", "Mode Select", "Reserve", +/* 17-1b */ "Release", "Copy", "Erase", "Mode Sense", "Start/Stop Unit", +/* 1c-1d */ "Receive Diagnostic", "Send Diagnostic", +/* 1e-1f */ "Prevent/Allow Medium Removal", unknown, +}; + + +static const char *group_1_commands[] = { +/* 20-22 */ unknown, unknown, unknown, +/* 23-28 */ unknown, unknown, "Read Capacity", unknown, unknown, "Read (10)", +/* 29-2d */ unknown, "Write (10)", "Seek (10)", unknown, unknown, +/* 2e-31 */ "Write Verify","Verify", "Search High", "Search Equal", +/* 32-34 */ "Search Low", "Set Limits", "Prefetch or Read Position", +/* 35-37 */ "Synchronize Cache","Lock/Unlock Cache", "Read Defect Data", +/* 38-3c */ "Medium Scan", "Compare","Copy Verify", "Write Buffer", "Read Buffer", +/* 3d-3f */ "Update Block", "Read Long", "Write Long", +}; + + +static const char *group_2_commands[] = { +/* 40-41 */ "Change Definition", "Write Same", +/* 42-48 */ unknown, "Read TOC", unknown, unknown, unknown, unknown, unknown, +/* 49-4f */ unknown, unknown, unknown, "Log Select", "Log Sense", unknown, unknown, +/* 50-55 */ unknown, unknown, unknown, unknown, unknown, "Mode Select (10)", +/* 56-5b */ unknown, unknown, unknown, unknown, "Mode Sense (10)", unknown, +/* 5c-5f */ unknown, unknown, unknown, +}; + + + +#define group(opcode) (((opcode) >> 5) & 7) + +#define RESERVED_GROUP 0 +#define VENDOR_GROUP 1 +#define NOTEXT_GROUP 2 + +static const char **commands[] = { + group_0_commands, group_1_commands, group_2_commands, + (const char **) RESERVED_GROUP, (const char **) RESERVED_GROUP, + (const char **) NOTEXT_GROUP, (const char **) VENDOR_GROUP, + (const char **) VENDOR_GROUP +}; + +static const char reserved[] = "RESERVED"; +static const char vendor[] = "VENDOR SPECIFIC"; + +static void print_opcode(int opcode) { + const char **table = commands[ group(opcode) ]; + switch ((unsigned long) table) { + case RESERVED_GROUP: + printk("%s(0x%02x) ", reserved, opcode); + break; + case NOTEXT_GROUP: + printk("%s(0x%02x) ", unknown, opcode); + break; + case VENDOR_GROUP: + printk("%s(0x%02x) ", vendor, opcode); + break; + default: + if (table[opcode & 0x1f] != unknown) + printk("%s ",table[opcode & 0x1f]); + else + printk("%s(0x%02x) ", unknown, opcode); + break; + } +} +#else /* CONST & CONST_COMMAND */ +static void print_opcode(int opcode) { + printk("0x%02x ", opcode); +} +#endif + +void print_command (unsigned char *command) { + int i,s; + print_opcode(command[0]); + for ( i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i) + printk("%02x ", command[i]); + printk("\n"); +} + +#if (CONSTANTS & CONST_STATUS) +static const char * statuses[] = { +/* 0-4 */ "Good", "Check Condition", "Condition Good", unknown, "Busy", +/* 5-9 */ unknown, unknown, unknown, "Intermediate Good", unknown, +/* a-d */ "Intermediate Good", unknown, "Reservation Conflict", unknown, +/* e-f */ unknown, unknown, +}; +#endif + +void print_status (int status) { + status = (status >> 1) & 0xf; +#if (CONSTANTS & CONST_STATUS) + printk("%s ",statuses[status]); +#else + printk("0x%0x ", status); +#endif +} + +#if (CONSTANTS & CONST_XSENSE) +#define D 0x001 /* DIRECT ACCESS DEVICE (disk) */ +#define T 0x002 /* SEQUENTIAL ACCESS DEVICE (tape) */ +#define L 0x004 /* PRINTER DEVICE */ +#define P 0x008 /* PROCESSOR DEVICE */ +#define W 0x010 /* WRITE ONCE READ MULTIPLE DEVICE */ +#define R 0x020 /* READ ONLY (CD-ROM) DEVICE */ +#define S 0x040 /* SCANNER DEVICE */ +#define O 0x080 /* OPTICAL MEMORY DEVICE */ +#define M 0x100 /* MEDIA CHANGER DEVICE */ +#define C 0x200 /* COMMUNICATION DEVICE */ + +struct error_info{ + unsigned char code1, code2; + unsigned short int devices; + const char * text; +}; + +struct error_info2{ + unsigned char code1, code2_min, code2_max; + unsigned short int devices; + const char * text; +}; + +static struct error_info2 additional2[] = +{ + {0x40,0x00,0x7f,D,"Ram failure (%x)"}, + {0x40,0x80,0xff,D|T|L|P|W|R|S|O|M|C,"Diagnostic failure on component (%x)"}, + {0x41,0x00,0xff,D,"Data path failure (%x)"}, + {0x42,0x00,0xff,D,"Power-on or self-test failure (%x)"}, + {0, 0, 0, 0, NULL} +}; + +static struct error_info additional[] = +{ + {0x00,0x01,T,"Filemark detected"}, + {0x00,0x02,T|S,"End-of-partition/medium detected"}, + {0x00,0x03,T,"Setmark detected"}, + {0x00,0x04,T|S,"Beginning-of-partition/medium detected"}, + {0x00,0x05,T|S,"End-of-data detected"}, + {0x00,0x06,D|T|L|P|W|R|S|O|M|C,"I/O process terminated"}, + {0x00,0x11,R,"Audio play operation in progress"}, + {0x00,0x12,R,"Audio play operation paused"}, + {0x00,0x13,R,"Audio play operation successfully completed"}, + {0x00,0x14,R,"Audio play operation stopped due to error"}, + {0x00,0x15,R,"No current audio status to return"}, + {0x01,0x00,D|W|O,"No index/sector signal"}, + {0x02,0x00,D|W|R|O|M,"No seek complete"}, + {0x03,0x00,D|T|L|W|S|O,"Peripheral device write fault"}, + {0x03,0x01,T,"No write current"}, + {0x03,0x02,T,"Excessive write errors"}, + {0x04,0x00,D|T|L|P|W|R|S|O|M|C, + "Logical unit not ready, cause not reportable"}, + {0x04,0x01,D|T|L|P|W|R|S|O|M|C, + "Logical unit is in process of becoming ready"}, + {0x04,0x02,D|T|L|P|W|R|S|O|M|C, + "Logical unit not ready, initializing command required"}, + {0x04,0x03,D|T|L|P|W|R|S|O|M|C, + "Logical unit not ready, manual intervention required"}, + {0x04,0x04,D|T|L|O,"Logical unit not ready, format in progress"}, + {0x05,0x00,D|T|L|W|R|S|O|M|C,"Logical unit does not respond to selection"}, + {0x06,0x00,D|W|R|O|M,"No reference position found"}, + {0x07,0x00,D|T|L|W|R|S|O|M,"Multiple peripheral devices selected"}, + {0x08,0x00,D|T|L|W|R|S|O|M|C,"Logical unit communication failure"}, + {0x08,0x01,D|T|L|W|R|S|O|M|C,"Logical unit communication time-out"}, + {0x08,0x02,D|T|L|W|R|S|O|M|C,"Logical unit communication parity error"}, + {0x09,0x00,D|T|W|R|O,"Track following error"}, + {0x09,0x01,W|R|O,"Tracking servo failure"}, + {0x09,0x02,W|R|O,"Focus servo failure"}, + {0x09,0x03,W|R|O,"Spindle servo failure"}, + {0x0A,0x00,D|T|L|P|W|R|S|O|M|C,"Error log overflow"}, + {0x0C,0x00,T|S,"Write error"}, + {0x0C,0x01,D|W|O,"Write error recovered with auto reallocation"}, + {0x0C,0x02,D|W|O,"Write error - auto reallocation failed"}, + {0x10,0x00,D|W|O,"Id crc or ecc error"}, + {0x11,0x00,D|T|W|R|S|O,"Unrecovered read error"}, + {0x11,0x01,D|T|W|S|O,"Read retries exhausted"}, + {0x11,0x02,D|T|W|S|O,"Error too long to correct"}, + {0x11,0x03,D|T|W|S|O,"Multiple read errors"}, + {0x11,0x04,D|W|O,"Unrecovered read error - auto reallocate failed"}, + {0x11,0x05,W|R|O,"L-ec uncorrectable error"}, + {0x11,0x06,W|R|O,"Circ unrecovered error"}, + {0x11,0x07,W|O,"Data resynchronization error"}, + {0x11,0x08,T,"Incomplete block read"}, + {0x11,0x09,T,"No gap found"}, + {0x11,0x0A,D|T|O,"Miscorrected error"}, + {0x11,0x0B,D|W|O,"Unrecovered read error - recommend reassignment"}, + {0x11,0x0C,D|W|O,"Unrecovered read error - recommend rewrite the data"}, + {0x12,0x00,D|W|O,"Address mark not found for id field"}, + {0x13,0x00,D|W|O,"Address mark not found for data field"}, + {0x14,0x00,D|T|L|W|R|S|O,"Recorded entity not found"}, + {0x14,0x01,D|T|W|R|O,"Record not found"}, + {0x14,0x02,T,"Filemark or setmark not found"}, + {0x14,0x03,T,"End-of-data not found"}, + {0x14,0x04,T,"Block sequence error"}, + {0x15,0x00,D|T|L|W|R|S|O|M,"Random positioning error"}, + {0x15,0x01,D|T|L|W|R|S|O|M,"Mechanical positioning error"}, + {0x15,0x02,D|T|W|R|O,"Positioning error detected by read of medium"}, + {0x16,0x00,D|W|O,"Data synchronization mark error"}, + {0x17,0x00,D|T|W|R|S|O,"Recovered data with no error correction applied"}, + {0x17,0x01,D|T|W|R|S|O,"Recovered data with retries"}, + {0x17,0x02,D|T|W|R|O,"Recovered data with positive head offset"}, + {0x17,0x03,D|T|W|R|O,"Recovered data with negative head offset"}, + {0x17,0x04,W|R|O,"Recovered data with retries and/or circ applied"}, + {0x17,0x05,D|W|R|O,"Recovered data using previous sector id"}, + {0x17,0x06,D|W|O,"Recovered data without ecc - data auto-reallocated"}, + {0x17,0x07,D|W|O,"Recovered data without ecc - recommend reassignment"}, + {0x18,0x00,D|T|W|R|O,"Recovered data with error correction applied"}, + {0x18,0x01,D|W|R|O,"Recovered data with error correction and retries applied"}, + {0x18,0x02,D|W|R|O,"Recovered data - data auto-reallocated"}, + {0x18,0x03,R,"Recovered data with circ"}, + {0x18,0x04,R,"Recovered data with lec"}, + {0x18,0x05,D|W|R|O,"Recovered data - recommend reassignment"}, + {0x19,0x00,D|O,"Defect list error"}, + {0x19,0x01,D|O,"Defect list not available"}, + {0x19,0x02,D|O,"Defect list error in primary list"}, + {0x19,0x03,D|O,"Defect list error in grown list"}, + {0x1A,0x00,D|T|L|P|W|R|S|O|M|C,"Parameter list length error"}, + {0x1B,0x00,D|T|L|P|W|R|S|O|M|C,"Synchronous data transfer error"}, + {0x1C,0x00,D|O,"Defect list not found"}, + {0x1C,0x01,D|O,"Primary defect list not found"}, + {0x1C,0x02,D|O,"Grown defect list not found"}, + {0x1D,0x00,D|W|O,"Miscompare during verify operation"}, + {0x1E,0x00,D|W|O,"Recovered id with ecc correction"}, + {0x20,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid command operation code"}, + {0x21,0x00,D|T|W|R|O|M,"Logical block address out of range"}, + {0x21,0x01,M,"Invalid element address"}, + {0x22,0x00,D,"Illegal function (should use 20 00, 24 00, or 26 00)"}, + {0x24,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid field in cdb"}, + {0x25,0x00,D|T|L|P|W|R|S|O|M|C,"Logical unit not supported"}, + {0x26,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid field in parameter list"}, + {0x26,0x01,D|T|L|P|W|R|S|O|M|C,"Parameter not supported"}, + {0x26,0x02,D|T|L|P|W|R|S|O|M|C,"Parameter value invalid"}, + {0x26,0x03,D|T|L|P|W|R|S|O|M|C,"Threshold parameters not supported"}, + {0x27,0x00,D|T|W|O,"Write protected"}, + {0x28,0x00,D|T|L|P|W|R|S|O|M|C,"Not ready to ready transition (medium may have changed)"}, + {0x28,0x01,M,"Import or export element accessed"}, + {0x29,0x00,D|T|L|P|W|R|S|O|M|C,"Power on, reset, or bus device reset occurred"}, + {0x2A,0x00,D|T|L|W|R|S|O|M|C,"Parameters changed"}, + {0x2A,0x01,D|T|L|W|R|S|O|M|C,"Mode parameters changed"}, + {0x2A,0x02,D|T|L|W|R|S|O|M|C,"Log parameters changed"}, + {0x2B,0x00,D|T|L|P|W|R|S|O|C,"Copy cannot execute since host cannot disconnect"}, + {0x2C,0x00,D|T|L|P|W|R|S|O|M|C,"Command sequence error"}, + {0x2C,0x01,S,"Too many windows specified"}, + {0x2C,0x02,S,"Invalid combination of windows specified"}, + {0x2D,0x00,T,"Overwrite error on update in place"}, + {0x2F,0x00,D|T|L|P|W|R|S|O|M|C,"Commands cleared by another initiator"}, + {0x30,0x00,D|T|W|R|O|M,"Incompatible medium installed"}, + {0x30,0x01,D|T|W|R|O,"Cannot read medium - unknown format"}, + {0x30,0x02,D|T|W|R|O,"Cannot read medium - incompatible format"}, + {0x30,0x03,D|T,"Cleaning cartridge installed"}, + {0x31,0x00,D|T|W|O,"Medium format corrupted"}, + {0x31,0x01,D|L|O,"Format command failed"}, + {0x32,0x00,D|W|O,"No defect spare location available"}, + {0x32,0x01,D|W|O,"Defect list update failure"}, + {0x33,0x00,T,"Tape length error"}, + {0x36,0x00,L,"Ribbon, ink, or toner failure"}, + {0x37,0x00,D|T|L|W|R|S|O|M|C,"Rounded parameter"}, + {0x39,0x00,D|T|L|W|R|S|O|M|C,"Saving parameters not supported"}, + {0x3A,0x00,D|T|L|W|R|S|O|M,"Medium not present"}, + {0x3B,0x00,T|L,"Sequential positioning error"}, + {0x3B,0x01,T,"Tape position error at beginning-of-medium"}, + {0x3B,0x02,T,"Tape position error at end-of-medium"}, + {0x3B,0x03,L,"Tape or electronic vertical forms unit not ready"}, + {0x3B,0x04,L,"Slew failure"}, + {0x3B,0x05,L,"Paper jam"}, + {0x3B,0x06,L,"Failed to sense top-of-form"}, + {0x3B,0x07,L,"Failed to sense bottom-of-form"}, + {0x3B,0x08,T,"Reposition error"}, + {0x3B,0x09,S,"Read past end of medium"}, + {0x3B,0x0A,S,"Read past beginning of medium"}, + {0x3B,0x0B,S,"Position past end of medium"}, + {0x3B,0x0C,S,"Position past beginning of medium"}, + {0x3B,0x0D,M,"Medium destination element full"}, + {0x3B,0x0E,M,"Medium source element empty"}, + {0x3D,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid bits in identify message"}, + {0x3E,0x00,D|T|L|P|W|R|S|O|M|C,"Logical unit has not self-configured yet"}, + {0x3F,0x00,D|T|L|P|W|R|S|O|M|C,"Target operating conditions have changed"}, + {0x3F,0x01,D|T|L|P|W|R|S|O|M|C,"Microcode has been changed"}, + {0x3F,0x02,D|T|L|P|W|R|S|O|M|C,"Changed operating definition"}, + {0x3F,0x03,D|T|L|P|W|R|S|O|M|C,"Inquiry data has changed"}, + {0x43,0x00,D|T|L|P|W|R|S|O|M|C,"Message error"}, + {0x44,0x00,D|T|L|P|W|R|S|O|M|C,"Internal target failure"}, + {0x45,0x00,D|T|L|P|W|R|S|O|M|C,"Select or reselect failure"}, + {0x46,0x00,D|T|L|P|W|R|S|O|M|C,"Unsuccessful soft reset"}, + {0x47,0x00,D|T|L|P|W|R|S|O|M|C,"Scsi parity error"}, + {0x48,0x00,D|T|L|P|W|R|S|O|M|C,"Initiator detected error message received"}, + {0x49,0x00,D|T|L|P|W|R|S|O|M|C,"Invalid message error"}, + {0x4A,0x00,D|T|L|P|W|R|S|O|M|C,"Command phase error"}, + {0x4B,0x00,D|T|L|P|W|R|S|O|M|C,"Data phase error"}, + {0x4C,0x00,D|T|L|P|W|R|S|O|M|C,"Logical unit failed self-configuration"}, + {0x4E,0x00,D|T|L|P|W|R|S|O|M|C,"Overlapped commands attempted"}, + {0x50,0x00,T,"Write append error"}, + {0x50,0x01,T,"Write append position error"}, + {0x50,0x02,T,"Position error related to timing"}, + {0x51,0x00,T|O,"Erase failure"}, + {0x52,0x00,T,"Cartridge fault"}, + {0x53,0x00,D|T|L|W|R|S|O|M,"Media load or eject failed"}, + {0x53,0x01,T,"Unload tape failure"}, + {0x53,0x02,D|T|W|R|O|M,"Medium removal prevented"}, + {0x54,0x00,P,"Scsi to host system interface failure"}, + {0x55,0x00,P,"System resource failure"}, + {0x57,0x00,R,"Unable to recover table-of-contents"}, + {0x58,0x00,O,"Generation does not exist"}, + {0x59,0x00,O,"Updated block read"}, + {0x5A,0x00,D|T|L|P|W|R|S|O|M,"Operator request or state change input (unspecified)"}, + {0x5A,0x01,D|T|W|R|O|M,"Operator medium removal request"}, + {0x5A,0x02,D|T|W|O,"Operator selected write protect"}, + {0x5A,0x03,D|T|W|O,"Operator selected write permit"}, + {0x5B,0x00,D|T|L|P|W|R|S|O|M,"Log exception"}, + {0x5B,0x01,D|T|L|P|W|R|S|O|M,"Threshold condition met"}, + {0x5B,0x02,D|T|L|P|W|R|S|O|M,"Log counter at maximum"}, + {0x5B,0x03,D|T|L|P|W|R|S|O|M,"Log list codes exhausted"}, + {0x5C,0x00,D|O,"Rpl status change"}, + {0x5C,0x01,D|O,"Spindles synchronized"}, + {0x5C,0x02,D|O,"Spindles not synchronized"}, + {0x60,0x00,S,"Lamp failure"}, + {0x61,0x00,S,"Video acquisition error"}, + {0x61,0x01,S,"Unable to acquire video"}, + {0x61,0x02,S,"Out of focus"}, + {0x62,0x00,S,"Scan head positioning error"}, + {0x63,0x00,R,"End of user area encountered on this track"}, + {0x64,0x00,R,"Illegal mode for this track"}, + {0, 0, 0, NULL} +}; +#endif + +#if (CONSTANTS & CONST_SENSE) +static const char *snstext[] = { + "None", /* There is no sense information */ + "Recovered Error", /* The last command completed successfully + but used error correction */ + "Not Ready", /* The addressed target is not ready */ + "Medium Error", /* Data error detected on the medium */ + "Hardware Error", /* Controller or device failure */ + "Illegal Request", + "Unit Attention", /* Removable medium was changed, or + the target has been reset */ + "Data Protect", /* Access to the data is blocked */ + "Blank Check", /* Reached unexpected written or unwritten + region of the medium */ + "Key=9", /* Vendor specific */ + "Copy Aborted", /* COPY or COMPARE was aborted */ + "Aborted Command", /* The target aborted the command */ + "Equal", /* A SEARCH DATA command found data equal */ + "Volume Overflow", /* Medium full with still data to be written */ + "Miscompare", /* Source data and data on the medium + do not agree */ + "Key=15" /* Reserved */ +}; +#endif + +/* Print sense information */ +void print_sense(const char * devclass, Scsi_Cmnd * SCpnt) +{ + int i, s; + int sense_class, valid, code; + unsigned char * sense_buffer = SCpnt->sense_buffer; + const char * error = NULL; + + sense_class = (sense_buffer[0] >> 4) & 0x07; + code = sense_buffer[0] & 0xf; + valid = sense_buffer[0] & 0x80; + + if (sense_class == 7) { /* extended sense data */ + s = sense_buffer[7] + 8; + if(s > sizeof(SCpnt->sense_buffer)) + s = sizeof(SCpnt->sense_buffer); + + if (!valid) + printk("extra data not valid "); + + if (sense_buffer[2] & 0x80) + printk( "FMK "); /* current command has read a filemark */ + if (sense_buffer[2] & 0x40) + printk( "EOM "); /* end-of-medium condition exists */ + if (sense_buffer[2] & 0x20) + printk( "ILI "); /* incorrect block length requested */ + + switch (code) { + case 0x0: + error = "Current"; /* error concerns current command */ + break; + case 0x1: + error = "Deferred"; /* error concerns some earlier command */ + /* e.g., an earlier write to disk cache succeeded, but + now the disk discovers that it cannot write the data */ + break; + default: + error = "Invalid"; + } + + printk("%s error ", error); + +#if (CONSTANTS & CONST_SENSE) + printk( "%s%s: sense key %s\n", devclass, + kdevname(SCpnt->request.rq_dev), snstext[sense_buffer[2] & 0x0f]); +#else + printk("%s%s: sns = %2x %2x\n", devclass, + kdevname(SCpnt->request.rq_dev), sense_buffer[0], sense_buffer[2]); +#endif + + /* Check to see if additional sense information is available */ + if(sense_buffer[7] + 7 < 13 || + (sense_buffer[12] == 0 && sense_buffer[13] == 0)) goto done; + +#if (CONSTANTS & CONST_XSENSE) + for(i=0; additional[i].text; i++) + if(additional[i].code1 == sense_buffer[12] && + additional[i].code2 == sense_buffer[13]) + printk("Additional sense indicates %s\n", additional[i].text); + + for(i=0; additional2[i].text; i++) + if(additional2[i].code1 == sense_buffer[12] && + additional2[i].code2_min >= sense_buffer[13] && + additional2[i].code2_max <= sense_buffer[13]) { + printk("Additional sense indicates "); + printk(additional2[i].text, sense_buffer[13]); + printk("\n"); + }; +#else + printk("ASC=%2x ASCQ=%2x\n", sense_buffer[12], sense_buffer[13]); +#endif + } else { /* non-extended sense data */ + + /* + * Standard says: + * sense_buffer[0] & 0200 : address valid + * sense_buffer[0] & 0177 : vendor-specific error code + * sense_buffer[1] & 0340 : vendor-specific + * sense_buffer[1..3] : 21-bit logical block address + */ + +#if (CONSTANTS & CONST_SENSE) + if (sense_buffer[0] < 15) + printk("%s%s: old sense key %s\n", devclass, + kdevname(SCpnt->request.rq_dev), snstext[sense_buffer[0] & 0x0f]); + else +#endif + printk("%s%s: sns = %2x %2x\n", devclass, + kdevname(SCpnt->request.rq_dev), sense_buffer[0], sense_buffer[2]); + + printk("Non-extended sense class %d code 0x%0x ", sense_class, code); + s = 4; + } + + done: +#if !(CONSTANTS & CONST_SENSE) + printk("Raw sense data:"); + for (i = 0; i < s; ++i) + printk("0x%02x ", sense_buffer[i]); + printk("\n"); +#endif + return; +} + +#if (CONSTANTS & CONST_MSG) +static const char *one_byte_msgs[] = { +/* 0x00 */ "Command Complete", NULL, "Save Pointers", +/* 0x03 */ "Restore Pointers", "Disconnect", "Initiator Error", +/* 0x06 */ "Abort", "Message Reject", "Nop", "Message Parity Error", +/* 0x0a */ "Linked Command Complete", "Linked Command Complete w/flag", +/* 0x0c */ "Bus device reset", "Abort Tag", "Clear Queue", +/* 0x0f */ "Initiate Recovery", "Release Recovery" +}; + +#define NO_ONE_BYTE_MSGS (sizeof(one_byte_msgs) / sizeof (const char *)) + +static const char *two_byte_msgs[] = { +/* 0x20 */ "Simple Queue Tag", "Head of Queue Tag", "Ordered Queue Tag" +/* 0x23 */ "Ignore Wide Residue" +}; + +#define NO_TWO_BYTE_MSGS (sizeof(two_byte_msgs) / sizeof (const char *)) + +static const char *extended_msgs[] = { +/* 0x00 */ "Modify Data Pointer", "Synchronous Data Transfer Request", +/* 0x02 */ "SCSI-I Extended Identify", "Wide Data Transfer Request" +}; + +#define NO_EXTENDED_MSGS (sizeof(two_byte_msgs) / sizeof (const char *)) +#endif /* (CONSTANTS & CONST_MSG) */ + +int print_msg (const unsigned char *msg) { + int len = 0, i; + if (msg[0] == EXTENDED_MESSAGE) { + len = 3 + msg[1]; +#if (CONSTANTS & CONST_MSG) + if (msg[2] < NO_EXTENDED_MSGS) + printk ("%s ", extended_msgs[msg[2]]); + else + printk ("Extended Message, reserved code (0x%02x) ", (int) msg[2]); + switch (msg[2]) { + case EXTENDED_MODIFY_DATA_POINTER: + printk("pointer = %d", (int) (msg[3] << 24) | (msg[4] << 16) | + (msg[5] << 8) | msg[6]); + break; + case EXTENDED_SDTR: + printk("period = %d ns, offset = %d", (int) msg[3] * 4, (int) + msg[4]); + break; + case EXTENDED_WDTR: + printk("width = 2^%d bytes", msg[3]); + break; + default: + for (i = 2; i < len; ++i) + printk("%02x ", msg[i]); + } +#else + for (i = 0; i < len; ++i) + printk("%02x ", msg[i]); +#endif + /* Identify */ + } else if (msg[0] & 0x80) { +#if (CONSTANTS & CONST_MSG) + printk("Identify disconnect %sallowed %s %d ", + (msg[0] & 0x40) ? "" : "not ", + (msg[0] & 0x20) ? "target routine" : "lun", + msg[0] & 0x7); +#else + printk("%02x ", msg[0]); +#endif + len = 1; + /* Normal One byte */ + } else if (msg[0] < 0x1f) { +#if (CONSTANTS & CONST_MSG) + if (msg[0] < NO_ONE_BYTE_MSGS) + printk(one_byte_msgs[msg[0]]); + else + printk("reserved (%02x) ", msg[0]); +#else + printk("%02x ", msg[0]); +#endif + len = 1; + /* Two byte */ + } else if (msg[0] <= 0x2f) { +#if (CONSTANTS & CONST_MSG) + if ((msg[0] - 0x20) < NO_TWO_BYTE_MSGS) + printk("%s %02x ", two_byte_msgs[msg[0] - 0x20], + msg[1]); + else + printk("reserved two byte (%02x %02x) ", + msg[0], msg[1]); +#else + printk("%02x %02x", msg[0], msg[1]); +#endif + len = 2; + } else +#if (CONSTANTS & CONST_MSG) + printk(reserved); +#else + printk("%02x ", msg[0]); +#endif + return len; +} + +void print_Scsi_Cmnd (Scsi_Cmnd *cmd) { + printk("scsi%d : destination target %d, lun %d\n", + cmd->host->host_no, + cmd->target, + cmd->lun); + printk(" command = "); + print_command (cmd->cmnd); +} + +#if (CONSTANTS & CONST_HOST) +static const char * hostbyte_table[]={ +"DID_OK", "DID_NO_CONNECT", "DID_BUS_BUSY", "DID_TIME_OUT", "DID_BAD_TARGET", +"DID_ABORT", "DID_PARITY", "DID_ERROR", "DID_RESET", "DID_BAD_INTR",NULL}; + +void print_hostbyte(int scsiresult) +{ static int maxcode=0; + int i; + + if(!maxcode) { + for(i=0;hostbyte_table[i];i++) ; + maxcode=i-1; + } + printk("Hostbyte=0x%02x",host_byte(scsiresult)); + if(host_byte(scsiresult)>maxcode) { + printk("is invalid "); + return; + } + printk("(%s) ",hostbyte_table[host_byte(scsiresult)]); +} +#else +void print_hostbyte(int scsiresult) +{ printk("Hostbyte=0x%02x ",host_byte(scsiresult)); +} +#endif + +#if (CONSTANTS & CONST_DRIVER) +static const char * driverbyte_table[]={ +"DRIVER_OK", "DRIVER_BUSY", "DRIVER_SOFT", "DRIVER_MEDIA", "DRIVER_ERROR", +"DRIVER_INVALID", "DRIVER_TIMEOUT", "DRIVER_HARD",NULL }; + +static const char * driversuggest_table[]={"SUGGEST_OK", +"SUGGEST_RETRY", "SUGGEST_ABORT", "SUGGEST_REMAP", "SUGGEST_DIE", +unknown,unknown,unknown, "SUGGEST_SENSE",NULL}; + + +void print_driverbyte(int scsiresult) +{ static int driver_max=0,suggest_max=0; + int i,dr=driver_byte(scsiresult)&DRIVER_MASK, + su=(driver_byte(scsiresult)&SUGGEST_MASK)>>4; + + if(!driver_max) { + for(i=0;driverbyte_table[i];i++) ; + driver_max=i; + for(i=0;driversuggest_table[i];i++) ; + suggest_max=i; + } + printk("Driverbyte=0x%02x",driver_byte(scsiresult)); + printk("(%s,%s) ", + dr<driver_max ? driverbyte_table[dr]:"invalid", + su<suggest_max ? driversuggest_table[su]:"invalid"); +} +#else +void print_driverbyte(int scsiresult) +{ printk("Driverbyte=0x%02x ",driver_byte(scsiresult)); +} +#endif + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/constants.h b/linux/src/drivers/scsi/constants.h new file mode 100644 index 00000000..e10527ea --- /dev/null +++ b/linux/src/drivers/scsi/constants.h @@ -0,0 +1,6 @@ +#ifndef _CONSTANTS_H +#define _CONSTANTS_H +extern int print_msg(unsigned char *); +extern void print_status(int); +extern void print_Scsi_Cmnd (Scsi_Cmnd *); +#endif /* def _CONSTANTS_H */ diff --git a/linux/src/drivers/scsi/dc390.h b/linux/src/drivers/scsi/dc390.h new file mode 100644 index 00000000..18c7e037 --- /dev/null +++ b/linux/src/drivers/scsi/dc390.h @@ -0,0 +1,147 @@ +/*********************************************************************** + * FILE NAME : DC390.H * + * BY : C.L. Huang * + * Description: Device Driver for Tekram DC-390(T) PCI SCSI * + * Bus Master Host Adapter * + ***********************************************************************/ + +/* Kernel version autodetection */ + +#include <linux/version.h> +/* Convert Linux Version, Patch-level, Sub-level to LINUX_VERSION_CODE. */ +#define ASC_LINUX_VERSION(V, P, S) (((V) * 65536) + ((P) * 256) + (S)) + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,50) +#define VERSION_ELF_1_2_13 +#elseif LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,95) +#define VERSION_1_3_85 +#else +#define VERSION_2_0_0 +#endif + +/* + * AMD 53C974 driver, header file + */ + +#ifndef DC390_H +#define DC390_H + +#if defined(HOSTS_C) || defined(MODULE) + +#ifdef VERSION_2_0_0 +#include <scsi/scsicam.h> +#else +#include <linux/scsicam.h> +#endif + +extern int DC390_detect(Scsi_Host_Template *psht); +extern int DC390_queue_command(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)); +extern int DC390_abort(Scsi_Cmnd *cmd); + +#ifdef VERSION_2_0_0 +extern int DC390_reset(Scsi_Cmnd *cmd, unsigned int resetFlags); +#else +extern int DC390_reset(Scsi_Cmnd *cmd); +#endif + +#ifdef VERSION_ELF_1_2_13 +extern int DC390_bios_param(Disk *disk, int devno, int geom[]); +#else +extern int DC390_bios_param(Disk *disk, kdev_t devno, int geom[]); +#endif + +#ifdef MODULE +static int DC390_release(struct Scsi_Host *); +#else +#define DC390_release NULL +#endif + +#ifndef VERSION_ELF_1_2_13 +extern struct proc_dir_entry proc_scsi_tmscsim; +extern int tmscsim_proc_info(char *buffer, char **start, off_t offset, int length, int hostno, int inout); +#endif + +#ifdef VERSION_2_0_0 + +#define DC390_T { \ + NULL, /* *next */ \ + NULL, /* *usage_count */ \ + &proc_scsi_tmscsim, /* *proc_dir */ \ + tmscsim_proc_info, /* (*proc_info)() */ \ + "Tekram DC390(T) V1.11 Feb-05-1997", /* *name */ \ + DC390_detect, \ + DC390_release, /* (*release)() */ \ + NULL, /* *(*info)() */ \ + NULL, /* (*command)() */ \ + DC390_queue_command, \ + DC390_abort, \ + DC390_reset, \ + NULL, /* slave attach */\ + DC390_bios_param, \ + 10,/* can queue(-1) */ \ + 7, /* id(-1) */ \ + SG_ALL, \ + 2, /* cmd per lun(2) */ \ + 0, /* present */ \ + 0, /* unchecked isa dma */ \ + DISABLE_CLUSTERING \ + } +#endif + + +#ifdef VERSION_1_3_85 + +#define DC390_T { \ + NULL, /* *next */ \ + NULL, /* *usage_count */ \ + &proc_scsi_tmscsim, /* *proc_dir */ \ + tmscsim_proc_info, /* (*proc_info)() */ \ + "Tekram DC390(T) V1.11 Feb-05-1997", /* *name */ \ + DC390_detect, \ + DC390_release, /* (*release)() */ \ + NULL, /* *(*info)() */ \ + NULL, /* (*command)() */ \ + DC390_queue_command, \ + DC390_abort, \ + DC390_reset, \ + NULL, /* slave attach */\ + DC390_bios_param, \ + 10,/* can queue(-1) */ \ + 7, /* id(-1) */ \ + SG_ALL, \ + 2, /* cmd per lun(2) */ \ + 0, /* present */ \ + 0, /* unchecked isa dma */ \ + DISABLE_CLUSTERING \ + } +#endif + + +#ifdef VERSION_ELF_1_2_13 + +#define DC390_T { \ + NULL, \ + NULL, \ + "Tekram DC390(T) V1.11 Feb-05-1997",\ + DC390_detect, \ + DC390_release, \ + NULL, /* info */ \ + NULL, /* command, deprecated */ \ + DC390_queue_command, \ + DC390_abort, \ + DC390_reset, \ + NULL, /* slave attach */\ + DC390_bios_param, \ + 10,/* can queue(-1) */ \ + 7, /* id(-1) */ \ + 16,/* old (SG_ALL) */ \ + 2, /* cmd per lun(2) */ \ + 0, /* present */ \ + 0, /* unchecked isa dma */ \ + DISABLE_CLUSTERING \ + } +#endif + +#endif /* defined(HOSTS_C) || defined(MODULE) */ + +#endif /* DC390_H */ diff --git a/linux/src/drivers/scsi/dc390w.h b/linux/src/drivers/scsi/dc390w.h new file mode 100644 index 00000000..7e4030a7 --- /dev/null +++ b/linux/src/drivers/scsi/dc390w.h @@ -0,0 +1,145 @@ +/*********************************************************************** + * FILE NAME : DC390W.H * + * BY : C.L. Huang * + * Description: Device Driver for Tekram DC-390W/U/F (T) PCI SCSI * + * Bus Master Host Adapter * + ***********************************************************************/ + +/* Kernel version autodetection */ + +#include <linux/version.h> +/* Convert Linux Version, Patch-level, Sub-level to LINUX_VERSION_CODE. */ +#define ASC_LINUX_VERSION(V, P, S) (((V) * 65536) + ((P) * 256) + (S)) + +#if LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,50) +#define VERSION_ELF_1_2_13 +#elseif LINUX_VERSION_CODE < ASC_LINUX_VERSION(1,3,95) +#define VERSION_1_3_85 +#else +#define VERSION_2_0_0 +#endif + +/* + * NCR 53c825A,875 driver, header file + */ + +#ifndef DC390W_H +#define DC390W_H + +#if defined(HOSTS_C) || defined(MODULE) + +#ifdef VERSION_2_0_0 +#include <scsi/scsicam.h> +#else +#include <linux/scsicam.h> +#endif + +extern int DC390W_detect(Scsi_Host_Template *psht); +extern int DC390W_queue_command(Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)); +extern int DC390W_abort(Scsi_Cmnd *cmd); + +#ifdef VERSION_2_0_0 +extern int DC390W_reset(Scsi_Cmnd *cmd, unsigned int resetFlags); +#else +extern int DC390W_reset(Scsi_Cmnd *cmd); +#endif + +#ifdef VERSION_ELF_1_2_13 +extern int DC390W_bios_param(Disk *disk, int devno, int geom[]); +#else +extern int DC390W_bios_param(Disk *disk, kdev_t devno, int geom[]); +#endif + +#ifdef MODULE +static int DC390W_release(struct Scsi_Host *); +#else +#define DC390W_release NULL +#endif + +#ifndef VERSION_ELF_1_2_13 +extern struct proc_dir_entry proc_scsi_tmscsiw; +extern int tmscsiw_proc_info(char*, char**, off_t, int, int, int); +#endif + +#ifdef VERSION_2_0_0 + +#define DC390WUF { \ + NULL, /* *next */ \ + NULL, /* *usage_count */ \ + &proc_scsi_tmscsiw, /* *proc_dir */ \ + tmscsiw_proc_info, /* (*proc_info)() */ \ + "Tekram DC390WUF(T) V1.12 Feb-17-1997", /* *name */ \ + DC390W_detect, \ + DC390W_release, /* (*release)() */ \ + NULL, /* *(*info)() */ \ + NULL, /* (*command)() */ \ + DC390W_queue_command, \ + DC390W_abort, \ + DC390W_reset, \ + NULL, /* slave attach */\ + DC390W_bios_param, \ + 10,/* can queue(-1) */ \ + 7, /* id(-1) */ \ + 32,/* old (SG_ALL) */ \ + 2, /* cmd per lun(2) */ \ + 0, /* present */ \ + 0, /* unchecked isa dma */ \ + ENABLE_CLUSTERING \ + } +#endif + +#ifdef VERSION_1_3_85 + +#define DC390WUF { \ + NULL, /* *next */ \ + NULL, /* *usage_count */ \ + &proc_scsi_tmscsiw, /* *proc_dir */ \ + tmscsiw_proc_info, /* (*proc_info)() */ \ + "Tekram DC390WUF(T) V1.12 Feb-17-1997", /* *name */ \ + DC390W_detect, \ + DC390W_release, /* (*release)() */ \ + NULL, /* *(*info)() */ \ + NULL, /* (*command)() */ \ + DC390W_queue_command, \ + DC390W_abort, \ + DC390W_reset, \ + NULL, /* slave attach */\ + DC390W_bios_param, \ + 10,/* can queue(-1) */ \ + 7, /* id(-1) */ \ + 16,/* old (SG_ALL) */ \ + 2, /* cmd per lun(2) */ \ + 0, /* present */ \ + 0, /* unchecked isa dma */ \ + ENABLE_CLUSTERING \ + } +#endif + +#ifdef VERSION_ELF_1_2_13 + +#define DC390WUF { \ + NULL, \ + NULL, \ + "Tekram DC390WUF(T) V1.12 Feb-17-1997",\ + DC390W_detect, \ + DC390W_release, \ + NULL, /* info */ \ + NULL, /* command, deprecated */ \ + DC390W_queue_command, \ + DC390W_abort, \ + DC390W_reset, \ + NULL, /* slave attach */\ + DC390W_bios_param, \ + 10,/* can queue(-1) */ \ + 7, /* id(-1) */ \ + 16,/* old (SG_ALL) */ \ + 2, /* cmd per lun(2) */ \ + 0, /* present */ \ + 0, /* unchecked isa dma */ \ + ENABLE_CLUSTERING \ + } +#endif + +#endif /* defined(HOSTS_C) || defined(MODULE) */ + +#endif /* DC390W_H */ diff --git a/linux/src/drivers/scsi/dtc.c b/linux/src/drivers/scsi/dtc.c new file mode 100644 index 00000000..94c3e333 --- /dev/null +++ b/linux/src/drivers/scsi/dtc.c @@ -0,0 +1,400 @@ + +#define AUTOSENSE +#define PSEUDO_DMA +#define DONT_USE_INTR +#define UNSAFE /* Leave interrupts enabled during pseudo-dma I/O */ +#define xNDEBUG (NDEBUG_INTR+NDEBUG_RESELECTION+\ + NDEBUG_SELECTION+NDEBUG_ARBITRATION) +#define DMA_WORKS_RIGHT + + +/* + * DTC 3180/3280 driver, by + * Ray Van Tassle rayvt@comm.mot.com + * + * taken from ... + * Trantor T128/T128F/T228 driver by... + * + * Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * DISTRIBUTION RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook +*/ + +/* + * Options : + * AUTOSENSE - if defined, REQUEST SENSE will be performed automatically + * for commands that return with a CHECK CONDITION status. + * + * PSEUDO_DMA - enables PSEUDO-DMA hardware, should give a 3-4X performance + * increase compared to polled I/O. + * + * PARITY - enable parity checking. Not supported. + * + * UNSAFE - leave interrupts enabled during pseudo-DMA transfers. + * You probably want this. + * + * The card is detected and initialized in one of several ways : + * 1. Autoprobe (default) - since the board is memory mapped, + * a BIOS signature is scanned for to locate the registers. + * An interrupt is triggered to autoprobe for the interrupt + * line. + * + * 2. With command line overrides - dtc=address,irq may be + * used on the LILO command line to override the defaults. + * +*/ + +/*----------------------------------------------------------------*/ +/* the following will set the monitor border color (useful to find + where something crashed or gets stuck at */ +/* 1 = blue + 2 = green + 3 = cyan + 4 = red + 5 = magenta + 6 = yellow + 7 = white +*/ +#if 0 +#define rtrc(i) {inb(0x3da); outb(0x31, 0x3c0); outb((i), 0x3c0);} +#else +#define rtrc(i) {} +#endif + + +#include <asm/system.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/blk.h> +#include <asm/io.h> +#include "scsi.h" +#include "hosts.h" +#include "dtc.h" +#define AUTOPROBE_IRQ +#include "NCR5380.h" +#include "constants.h" +#include "sd.h" +#include<linux/stat.h> +#include<linux/string.h> + +struct proc_dir_entry proc_scsi_dtc = { + PROC_SCSI_T128, 7, "dtc3x80", + S_IFDIR | S_IRUGO | S_IXUGO, 2 + }; + + +static struct override { + __u32 address; + int irq; +} overrides +#ifdef OVERRIDE +[] = OVERRIDE; +#else +[4] = {{0, IRQ_AUTO}, {0, IRQ_AUTO}, {0, IRQ_AUTO}, + {0, IRQ_AUTO}}; +#endif + +#define NO_OVERRIDES (sizeof(overrides) / sizeof(struct override)) + +static struct base { + __u32 address; + int noauto; +} bases[] = {{0xcc000, 0}, {0xc8000, 0}, +{0xdc000, 0}, {0xd8000, 0}}; + +#define NO_BASES (sizeof (bases) / sizeof (struct base)) + +static const struct signature { + const char *string; + int offset; +} signatures[] = { {"DATA TECHNOLOGY CORPORATION BIOS", 0x25}, }; + +#define NO_SIGNATURES (sizeof (signatures) / sizeof (struct signature)) + +/* + * Function : dtc_setup(char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + * +*/ + +void dtc_setup(char *str, int *ints) { + static int commandline_current = 0; + int i; + if (ints[0] != 2) + printk("dtc_setup: usage dtc=address,irq\n"); + else + if (commandline_current < NO_OVERRIDES) { + overrides[commandline_current].address = ints[1]; + overrides[commandline_current].irq = ints[2]; + for (i = 0; i < NO_BASES; ++i) + if (bases[i].address == ints[1]) { + bases[i].noauto = 1; + break; + } + ++commandline_current; + } +} + +/* + * Function : int dtc_detect(Scsi_Host_Template * tpnt) + * + * Purpose : detects and initializes DTC 3180/3280 controllers + * that were autoprobed, overridden on the LILO command line, + * or specified at compile time. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * +*/ + + +int dtc_detect(Scsi_Host_Template * tpnt) { + static int current_override = 0, current_base = 0; + struct Scsi_Host *instance; + unsigned char *base; + int sig, count; + + tpnt->proc_dir = &proc_scsi_dtc; + tpnt->proc_info = &dtc_proc_info; + + for (count = 0; current_override < NO_OVERRIDES; ++current_override) { + base = NULL; + + if (overrides[current_override].address) + base = (unsigned char *)overrides[current_override].address; + else + for (; !base && (current_base < NO_BASES); ++current_base) { +#if (DTCDEBUG & DTCDEBUG_INIT) + printk("scsi : probing address %08x\n", (unsigned int) bases[current_base].address); +#endif + for (sig = 0; sig < NO_SIGNATURES; ++sig) + if (!bases[current_base].noauto && !memcmp + ((unsigned char *)(bases[current_base].address + signatures[sig].offset), + signatures[sig].string, strlen(signatures[sig].string))) { + base = (unsigned char *)bases[current_base].address; +#if (DTCDEBUG & DTCDEBUG_INIT) + printk("scsi-dtc : detected board.\n"); +#endif + break; + } + } + +#if defined(DTCDEBUG) && (DTCDEBUG & DTCDEBUG_INIT) + printk("scsi-dtc : base = %08x\n", (unsigned int) base); +#endif + + if (!base) + break; + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->base = base; + + NCR5380_init(instance, 0); + + NCR5380_write(DTC_CONTROL_REG, CSR_5380_INTR); /* Enable int's */ + if (overrides[current_override].irq != IRQ_AUTO) + instance->irq = overrides[current_override].irq; + else + instance->irq = NCR5380_probe_irq(instance, DTC_IRQS); + +#ifndef DONT_USE_INTR +/* With interrupts enabled, it will sometimes hang when doing heavy + * reads. So better not enable them until I finger it out. */ + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, dtc_intr, SA_INTERRUPT, "dtc")) { + printk("scsi%d : IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq == IRQ_NONE) { + printk("scsi%d : interrupts not enabled. for better interactive performance,\n", instance->host_no); + printk("scsi%d : please jumper the board for a free IRQ.\n", instance->host_no); + } +#else + if (instance->irq != IRQ_NONE) + printk("scsi%d : interrupts not used. Might as well not jumper it.\n", + instance->host_no); + instance->irq = IRQ_NONE; +#endif +#if defined(DTCDEBUG) && (DTCDEBUG & DTCDEBUG_INIT) + printk("scsi%d : irq = %d\n", instance->host_no, instance->irq); +#endif + + printk("scsi%d : at 0x%05X", instance->host_no, (int)instance->base); + if (instance->irq == IRQ_NONE) + printk (" interrupts disabled"); + else + printk (" irq %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, DTC_PUBLIC_RELEASE); + NCR5380_print_options(instance); + printk("\n"); + + ++current_override; + ++count; + } + return count; +} + +/* + * Function : int dtc_biosparam(Disk * disk, kdev_t dev, int *ip) + * + * Purpose : Generates a BIOS / DOS compatible H-C-S mapping for + * the specified device / size. + * + * Inputs : size = size of device in sectors (512 bytes), dev = block device + * major / minor, ip[] = {heads, sectors, cylinders} + * + * Returns : always 0 (success), initializes ip + * +*/ + +/* + * XXX Most SCSI boards use this mapping, I could be incorrect. Some one + * using hard disks on a trantor should verify that this mapping corresponds + * to that used by the BIOS / ASPI driver by running the linux fdisk program + * and matching the H_C_S coordinates to what DOS uses. +*/ + +int dtc_biosparam(Disk * disk, kdev_t dev, int * ip) +{ + int size = disk->capacity; + + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + return 0; +} + +/**************************************************************** + * Function : int NCR5380_pread (struct Scsi_Host *instance, + * unsigned char *dst, int len) + * + * Purpose : Fast 5380 pseudo-dma read function, reads len bytes to + * dst + * + * Inputs : dst = destination, len = length in bytes + * + * Returns : 0 on success, non zero on a failure such as a watchdog + * timeout. +*/ + +static int dtc_maxi = 0; +static int dtc_wmaxi = 0; + +static inline int NCR5380_pread (struct Scsi_Host *instance, + unsigned char *dst, int len) + { + unsigned char *d = dst; + int i; /* For counting time spent in the poll-loop */ + NCR5380_local_declare(); + NCR5380_setup(instance); + + i = 0; + NCR5380_read(RESET_PARITY_INTERRUPT_REG); + NCR5380_write(MODE_REG, MR_ENABLE_EOP_INTR | MR_DMA_MODE); + if (instance->irq == IRQ_NONE) + NCR5380_write(DTC_CONTROL_REG, CSR_DIR_READ); + else + NCR5380_write(DTC_CONTROL_REG, CSR_DIR_READ | CSR_INT_BASE); + NCR5380_write(DTC_BLK_CNT, len >> 7); /* Block count */ + rtrc(1); + while (len > 0) { + rtrc(2); + while (NCR5380_read(DTC_CONTROL_REG) & CSR_HOST_BUF_NOT_RDY) + ++i; + rtrc(3); + memcpy(d, (char *)(base + DTC_DATA_BUF), 128); + d += 128; + len -= 128; + rtrc(7); /*** with int's on, it sometimes hangs after here. + * Looks like something makes HBNR go away. */ + } + rtrc(4); + while ( !(NCR5380_read(DTC_CONTROL_REG) & D_CR_ACCESS)) + ++i; + NCR5380_write(MODE_REG, 0); /* Clear the operating mode */ + rtrc(0); + NCR5380_read(RESET_PARITY_INTERRUPT_REG); + if (i > dtc_maxi) + dtc_maxi = i; + return(0); +} + +/**************************************************************** + * Function : int NCR5380_pwrite (struct Scsi_Host *instance, + * unsigned char *src, int len) + * + * Purpose : Fast 5380 pseudo-dma write function, transfers len bytes from + * src + * + * Inputs : src = source, len = length in bytes + * + * Returns : 0 on success, non zero on a failure such as a watchdog + * timeout. +*/ + +static inline int NCR5380_pwrite (struct Scsi_Host *instance, + unsigned char *src, int len) { + int i; + NCR5380_local_declare(); + NCR5380_setup(instance); + + NCR5380_read(RESET_PARITY_INTERRUPT_REG); + NCR5380_write(MODE_REG, MR_ENABLE_EOP_INTR | MR_DMA_MODE); + /* set direction (write) */ + if (instance->irq == IRQ_NONE) + NCR5380_write(DTC_CONTROL_REG, 0); + else + NCR5380_write(DTC_CONTROL_REG, CSR_5380_INTR); + NCR5380_write(DTC_BLK_CNT, len >> 7); /* Block count */ + for (i = 0; len > 0; ++i) { + rtrc(5); + /* Poll until the host buffer can accept data. */ + while (NCR5380_read(DTC_CONTROL_REG) & CSR_HOST_BUF_NOT_RDY) + ++i; + rtrc(3); + memcpy((char *)(base + DTC_DATA_BUF), src, 128); + src += 128; + len -= 128; + } + rtrc(4); + while ( !(NCR5380_read(DTC_CONTROL_REG) & D_CR_ACCESS)) + ++i; + rtrc(6); + /* Wait until the last byte has been sent to the disk */ + while (!(NCR5380_read(TARGET_COMMAND_REG) & TCR_LAST_BYTE_SENT)) + ++i; + rtrc(7); + /* Check for parity error here. fixme. */ + NCR5380_write(MODE_REG, 0); /* Clear the operating mode */ + rtrc(0); + if (i > dtc_wmaxi) + dtc_wmaxi = i; + return (0); +} + +#include "NCR5380.c" + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = DTC3x80; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/dtc.h b/linux/src/drivers/scsi/dtc.h new file mode 100644 index 00000000..4c41237b --- /dev/null +++ b/linux/src/drivers/scsi/dtc.h @@ -0,0 +1,169 @@ +/* + * DTC controller, taken from T128 driver by... + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * DISTRIBUTION RELEASE 1. + * + * For more information, please consult + * + * + * + * and + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +#ifndef DTC3280_H +#define DTC3280_H + +#define DTC_PUBLIC_RELEASE 1 + +/*#define DTCDEBUG 0x1*/ +#define DTCDEBUG_INIT 0x1 +#define DTCDEBUG_TRANSFER 0x2 + +/* + * The DTC3180 & 3280 boards are memory mapped. + * + */ + +/* + */ +/* Offset from DTC_5380_OFFSET */ +#define DTC_CONTROL_REG 0x100 /* rw */ +#define D_CR_ACCESS 0x80 /* ro set=can access 3280 registers */ +#define CSR_DIR_READ 0x40 /* rw direction, 1 = read 0 = write */ + +#define CSR_RESET 0x80 /* wo Resets 53c400 */ +#define CSR_5380_REG 0x80 /* ro 5380 registers can be accessed */ +#define CSR_TRANS_DIR 0x40 /* rw Data transfer direction */ +#define CSR_SCSI_BUFF_INTR 0x20 /* rw Enable int on transfer ready */ +#define CSR_5380_INTR 0x10 /* rw Enable 5380 interrupts */ +#define CSR_SHARED_INTR 0x08 /* rw Interrupt sharing */ +#define CSR_HOST_BUF_NOT_RDY 0x04 /* ro Host buffer not ready */ +#define CSR_SCSI_BUF_RDY 0x02 /* ro SCSI buffer ready */ +#define CSR_GATED_5380_IRQ 0x01 /* ro Last block xferred */ +#define CSR_INT_BASE (CSR_SCSI_BUFF_INTR | CSR_5380_INTR) + + +#define DTC_BLK_CNT 0x101 /* rw + * # of 128-byte blocks to transfer */ + + +#define D_CR_ACCESS 0x80 /* ro set=can access 3280 registers */ + +#define DTC_SWITCH_REG 0x3982 /* ro - DIP switches */ +#define DTC_RESUME_XFER 0x3982 /* wo - resume data xfer + * after disconnect/reconnect*/ + +#define DTC_5380_OFFSET 0x3880 /* 8 registers here, see NCR5380.h */ + +/*!!!! for dtc, it's a 128 byte buffer at 3900 !!! */ +#define DTC_DATA_BUF 0x3900 /* rw 128 bytes long */ + + +#ifndef ASM +int dtc_abort(Scsi_Cmnd *); +int dtc_biosparam(Disk *, kdev_t, int*); +int dtc_detect(Scsi_Host_Template *); +int dtc_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int dtc_reset(Scsi_Cmnd *, unsigned int reset_flags); +int dtc_proc_info (char *buffer, char **start, off_t offset, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 32 +#endif + +/* + * I hadn't thought of this with the earlier drivers - but to prevent + * macro definition conflicts, we shouldn't define all of the internal + * macros when this is being used solely for the host stub. + */ + +#if defined(HOSTS_C) || defined(MODULE) + +#define DTC3x80 {NULL, NULL, NULL, NULL, \ + "DTC 3180/3280 ", dtc_detect, NULL, \ + NULL, \ + NULL, dtc_queue_command, dtc_abort, dtc_reset, NULL, \ + dtc_biosparam, \ + /* can queue */ CAN_QUEUE, /* id */ 7, SG_ALL, \ + /* cmd per lun */ CMD_PER_LUN , 0, 0, DISABLE_CLUSTERING} + +#endif + +#ifndef HOSTS_C + +#define NCR5380_implementation_fields \ + volatile unsigned char *base + +#define NCR5380_local_declare() \ + volatile unsigned char *base + +#define NCR5380_setup(instance) \ + base = (volatile unsigned char *) (instance)->base + +#define DTC_address(reg) (base + DTC_5380_OFFSET + reg) + +#define dbNCR5380_read(reg) \ + (rval=*(DTC_address(reg)), \ + (((unsigned char) printk("DTC : read register %d at addr %08x is: %02x\n"\ + , (reg), (int)DTC_address(reg), rval)), rval ) ) + +#define dbNCR5380_write(reg, value) do { \ + printk("DTC : write %02x to register %d at address %08x\n", \ + (value), (reg), (int)DTC_address(reg)); \ + *(DTC_address(reg)) = (value);} while(0) + + +#if !(DTCDEBUG & DTCDEBUG_TRANSFER) +#define NCR5380_read(reg) (*(DTC_address(reg))) +#define NCR5380_write(reg, value) (*(DTC_address(reg)) = (value)) +#else +#define NCR5380_read(reg) (*(DTC_address(reg))) +#define xNCR5380_read(reg) \ + (((unsigned char) printk("DTC : read register %d at address %08x\n"\ + , (reg), DTC_address(reg))), *(DTC_address(reg))) + +#define NCR5380_write(reg, value) do { \ + printk("DTC : write %02x to register %d at address %08x\n", \ + (value), (reg), (int)DTC_address(reg)); \ + *(DTC_address(reg)) = (value); } while(0) +#endif + +#define NCR5380_intr dtc_intr +#define NCR5380_queue_command dtc_queue_command +#define NCR5380_abort dtc_abort +#define NCR5380_reset dtc_reset +#define NCR5380_proc_info dtc_proc_info + +/* 15 12 11 10 + 1001 1100 0000 0000 */ + +#define DTC_IRQS 0x9c00 + + +#endif /* else def HOSTS_C */ +#endif /* ndef ASM */ +#endif /* DTC3280_H */ diff --git a/linux/src/drivers/scsi/eata.c b/linux/src/drivers/scsi/eata.c new file mode 100644 index 00000000..8d7ba083 --- /dev/null +++ b/linux/src/drivers/scsi/eata.c @@ -0,0 +1,2329 @@ +/* + * eata.c - Low-level driver for EATA/DMA SCSI host adapters. + * + * 26 Jul 1998 Rev. 4.33 for linux 2.0.35 and 2.1.111 + * + Added command line option (rs:[y|n]) to reverse the scan order + * of PCI boards. The default is rs:y, which reverses the BIOS order + * while registering PCI boards. The default value rs:y generates + * the same order of all previous revisions of this driver. + * Pls. note that "BIOS order" might have been reversed itself + * after the 2.1.9x PCI modifications in the linux kernel. + * The rs value is ignored when the explicit list of addresses + * is used by the "eata=port0,port1,..." command line option. + * + Added command line option (et:[y|n]) to force use of extended + * translation (255 heads, 63 sectors) as disk geometry. + * The default is et:n, which uses the disk geometry returned + * by scsicam_bios_param. The default value et:n is compatible with + * all previous revisions of this driver. + * + * 28 May 1998 Rev. 4.32 for linux 2.0.33 and 2.1.104 + * Increased busy timeout from 10 msec. to 200 msec. while + * processing interrupts. + * + * 16 May 1998 Rev. 4.31 for linux 2.0.33 and 2.1.102 + * Improved abort handling during the eh recovery process. + * + * 13 May 1998 Rev. 4.30 for linux 2.0.33 and 2.1.101 + * The driver is now fully SMP safe, including the + * abort and reset routines. + * Added command line options (eh:[y|n]) to choose between + * new_eh_code and the old scsi code. + * If linux version >= 2.1.101 the default is eh:y, while the eh + * option is ignored for previous releases and the old scsi code + * is used. + * + * 18 Apr 1998 Rev. 4.20 for linux 2.0.33 and 2.1.97 + * Reworked interrupt handler. + * + * 11 Apr 1998 rev. 4.05 for linux 2.0.33 and 2.1.95 + * Major reliability improvement: when a batch with overlapping + * requests is detected, requests are queued one at a time + * eliminating any possible board or drive reordering. + * + * 10 Apr 1998 rev. 4.04 for linux 2.0.33 and 2.1.95 + * Improved SMP support (if linux version >= 2.1.95). + * + * 9 Apr 1998 rev. 4.03 for linux 2.0.33 and 2.1.94 + * Added support for new PCI code and IO-APIC remapping of irqs. + * Performance improvement: when sequential i/o is detected, + * always use direct sort instead of reverse sort. + * + * 4 Apr 1998 rev. 4.02 for linux 2.0.33 and 2.1.92 + * io_port is now unsigned long. + * + * 17 Mar 1998 rev. 4.01 for linux 2.0.33 and 2.1.88 + * Use new scsi error handling code (if linux version >= 2.1.88). + * Use new interrupt code. + * + * 12 Sep 1997 rev. 3.11 for linux 2.0.30 and 2.1.55 + * Use of udelay inside the wait loops to avoid timeout + * problems with fast cpus. + * Removed check about useless calls to the interrupt service + * routine (reported on SMP systems only). + * At initialization time "sorted/unsorted" is displayed instead + * of "linked/unlinked" to reinforce the fact that "linking" is + * nothing but "elevator sorting" in the actual implementation. + * + * 17 May 1997 rev. 3.10 for linux 2.0.30 and 2.1.38 + * Use of serial_number_at_timeout in abort and reset processing. + * Use of the __initfunc and __initdata macro in setup code. + * Minor cleanups in the list_statistics code. + * Increased controller busy timeout in order to better support + * slow SCSI devices. + * + * 24 Feb 1997 rev. 3.00 for linux 2.0.29 and 2.1.26 + * When loading as a module, parameter passing is now supported + * both in 2.0 and in 2.1 style. + * Fixed data transfer direction for some SCSI opcodes. + * Immediate acknowledge to request sense commands. + * Linked commands to each disk device are now reordered by elevator + * sorting. Rare cases in which reordering of write requests could + * cause wrong results are managed. + * Fixed spurious timeouts caused by long simple queue tag sequences. + * New command line option (tm:[0-3]) to choose the type of tags: + * 0 -> mixed (default); 1 -> simple; 2 -> head; 3 -> ordered. + * + * 18 Jan 1997 rev. 2.60 for linux 2.1.21 and 2.0.28 + * Added command line options to enable/disable linked commands + * (lc:[y|n]), tagged commands (tc:[y|n]) and to set the max queue + * depth (mq:xx). Default is "eata=lc:n,tc:n,mq:16". + * Improved command linking. + * Documented how to setup RAID-0 with DPT SmartRAID boards. + * + * 8 Jan 1997 rev. 2.50 for linux 2.1.20 and 2.0.27 + * Added linked command support. + * Improved detection of PCI boards using ISA base addresses. + * + * 3 Dec 1996 rev. 2.40 for linux 2.1.14 and 2.0.27 + * Added support for tagged commands and queue depth adjustment. + * + * 22 Nov 1996 rev. 2.30 for linux 2.1.12 and 2.0.26 + * When CONFIG_PCI is defined, BIOS32 is used to include in the + * list of i/o ports to be probed all the PCI SCSI controllers. + * The list of i/o ports to be probed can be overwritten by the + * "eata=port0,port1,...." boot command line option. + * Scatter/gather lists are now allocated by a number of kmalloc + * calls, in order to avoid the previous size limit of 64Kb. + * + * 16 Nov 1996 rev. 2.20 for linux 2.1.10 and 2.0.25 + * Added support for EATA 2.0C, PCI, multichannel and wide SCSI. + * + * 27 Sep 1996 rev. 2.12 for linux 2.1.0 + * Portability cleanups (virtual/bus addressing, little/big endian + * support). + * + * 09 Jul 1996 rev. 2.11 for linux 2.0.4 + * Number of internal retries is now limited. + * + * 16 Apr 1996 rev. 2.10 for linux 1.3.90 + * New argument "reset_flags" to the reset routine. + * + * 6 Jul 1995 rev. 2.01 for linux 1.3.7 + * Update required by the new /proc/scsi support. + * + * 11 Mar 1995 rev. 2.00 for linux 1.2.0 + * Fixed a bug which prevented media change detection for removable + * disk drives. + * + * 23 Feb 1995 rev. 1.18 for linux 1.1.94 + * Added a check for scsi_register returning NULL. + * + * 11 Feb 1995 rev. 1.17 for linux 1.1.91 + * Now DEBUG_RESET is disabled by default. + * Register a board even if it does not assert DMA protocol support + * (DPT SK2011B does not report correctly the dmasup bit). + * + * 9 Feb 1995 rev. 1.16 for linux 1.1.90 + * Use host->wish_block instead of host->block. + * New list of Data Out SCSI commands. + * + * 8 Feb 1995 rev. 1.15 for linux 1.1.89 + * Cleared target_time_out counter while performing a reset. + * All external symbols renamed to avoid possible name conflicts. + * + * 28 Jan 1995 rev. 1.14 for linux 1.1.86 + * Added module support. + * Log and do a retry when a disk drive returns a target status + * different from zero on a recovered error. + * + * 24 Jan 1995 rev. 1.13 for linux 1.1.85 + * Use optimized board configuration, with a measured performance + * increase in the range 10%-20% on i/o throughput. + * + * 16 Jan 1995 rev. 1.12 for linux 1.1.81 + * Fix mscp structure comments (no functional change). + * Display a message if check_region detects a port address + * already in use. + * + * 17 Dec 1994 rev. 1.11 for linux 1.1.74 + * Use the scsicam_bios_param routine. This allows an easy + * migration path from disk partition tables created using + * different SCSI drivers and non optimal disk geometry. + * + * 15 Dec 1994 rev. 1.10 for linux 1.1.74 + * Added support for ISA EATA boards (DPT PM2011, DPT PM2021). + * The host->block flag is set for all the detected ISA boards. + * The detect routine no longer enforces LEVEL triggering + * for EISA boards, it just prints a warning message. + * + * 30 Nov 1994 rev. 1.09 for linux 1.1.68 + * Redo i/o on target status CHECK_CONDITION for TYPE_DISK only. + * Added optional support for using a single board at a time. + * + * 18 Nov 1994 rev. 1.08 for linux 1.1.64 + * Forces sg_tablesize = 64 and can_queue = 64 if these + * values are not correctly detected (DPT PM2012). + * + * 14 Nov 1994 rev. 1.07 for linux 1.1.63 Final BETA release. + * 04 Aug 1994 rev. 1.00 for linux 1.1.39 First BETA release. + * + * + * This driver is based on the CAM (Common Access Method Committee) + * EATA (Enhanced AT Bus Attachment) rev. 2.0A, using DMA protocol. + * + * Copyright (C) 1994-1998 Dario Ballabio (dario@milano.europe.dg.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that redistributions of source + * code retain the above copyright notice and this comment without + * modification. + * + */ + +/* + * + * Here is a brief description of the DPT SCSI host adapters. + * All these boards provide an EATA/DMA compatible programming interface + * and are fully supported by this driver in any configuration, including + * multiple SCSI channels: + * + * PM2011B/9X - Entry Level ISA + * PM2021A/9X - High Performance ISA + * PM2012A Old EISA + * PM2012B Old EISA + * PM2022A/9X - Entry Level EISA + * PM2122A/9X - High Performance EISA + * PM2322A/9X - Extra High Performance EISA + * PM3021 - SmartRAID Adapter for ISA + * PM3222 - SmartRAID Adapter for EISA (PM3222W is 16-bit wide SCSI) + * PM3224 - SmartRAID Adapter for PCI (PM3224W is 16-bit wide SCSI) + * + * The above list is just an indication: as a matter of fact all DPT + * boards using the EATA/DMA protocol are supported by this driver, + * since they use exactely the same programming interface. + * + * The DPT PM2001 provides only the EATA/PIO interface and hence is not + * supported by this driver. + * + * This code has been tested with up to 3 Distributed Processing Technology + * PM2122A/9X (DPT SCSI BIOS v002.D1, firmware v05E.0) EISA controllers, + * in any combination of private and shared IRQ. + * PCI support has been tested using up to 2 DPT PM3224W (DPT SCSI BIOS + * v003.D0, firmware v07G.0). + * + * DPT SmartRAID boards support "Hardware Array" - a group of disk drives + * which are all members of the same RAID-0, RAID-1 or RAID-5 array implemented + * in host adapter hardware. Hardware Arrays are fully compatible with this + * driver, since they look to it as a single disk drive. + * + * WARNING: to create a RAID-0 "Hardware Array" you must select "Other Unix" + * as the current OS in the DPTMGR "Initial System Installation" menu. + * Otherwise RAID-0 is generated as an "Array Group" (i.e. software RAID-0), + * which is not supported by the actual SCSI subsystem. + * To get the "Array Group" functionality, the Linux MD driver must be used + * instead of the DPT "Array Group" feature. + * + * Multiple ISA, EISA and PCI boards can be configured in the same system. + * It is suggested to put all the EISA boards on the same IRQ level, all + * the PCI boards on another IRQ level, while ISA boards cannot share + * interrupts. + * + * If you configure multiple boards on the same IRQ, the interrupt must + * be _level_ triggered (not _edge_ triggered). + * + * This driver detects EATA boards by probes at fixed port addresses, + * so no BIOS32 or PCI BIOS support is required. + * The suggested way to detect a generic EATA PCI board is to force on it + * any unused EISA address, even if there are other controllers on the EISA + * bus, or even if you system has no EISA bus at all. + * Do not force any ISA address on EATA PCI boards. + * + * If PCI bios support is configured into the kernel, BIOS32 is used to + * include in the list of i/o ports to be probed all the PCI SCSI controllers. + * + * Due to a DPT BIOS "feature", it might not be possible to force an EISA + * address on more then a single DPT PCI board, so in this case you have to + * let the PCI BIOS assign the addresses. + * + * The sequence of detection probes is: + * + * - ISA 0x1F0; + * - PCI SCSI controllers (only if BIOS32 is available); + * - EISA/PCI 0x1C88 through 0xFC88 (corresponding to EISA slots 1 to 15); + * - ISA 0x170, 0x230, 0x330. + * + * The above list of detection probes can be totally replaced by the + * boot command line option: "eata=port0,port1,port2,...", where the + * port0, port1... arguments are ISA/EISA/PCI addresses to be probed. + * For example using "eata=0x7410,0x7450,0x230", the driver probes + * only the two PCI addresses 0x7410 and 0x7450 and the ISA address 0x230, + * in this order; "eata=0" totally disables this driver. + * + * After the optional list of detection probes, other possible command line + * options are: + * + * eh:y use new scsi code (linux 2.2 only); + * eh:n use old scsi code; + * et:y force use of extended translation (255 heads, 63 sectors); + * et:n use disk geometry detected by scsicam_bios_param; + * rs:y reverse scan order while detecting PCI boards; + * rs:n use BIOS order while detecting PCI boards; + * lc:y enables linked commands; + * lc:n disables linked commands; + * tc:y enables tagged commands; + * tc:n disables tagged commands; + * tm:0 use head/simple/ordered queue tag sequences; + * tm:1 use only simple queue tags; + * tm:2 use only head of queue tags; + * tm:3 use only ordered queue tags; + * mq:xx set the max queue depth to the value xx (2 <= xx <= 32). + * + * The default value is: "eata=lc:n,tc:n,mq:16,tm:0,et:n,rs:n". + * An example using the list of detection probes could be: + * "eata=0x7410,0x230,lc:y,tc:n,mq:4,eh:n,et:n". + * + * When loading as a module, parameters can be specified as well. + * The above example would be (use 1 in place of y and 0 in place of n): + * + * modprobe eata io_port=0x7410,0x230 linked_comm=1 tagged_comm=0 \ + * max_queue_depth=4 tag_mode=0 use_new_eh_code=0 \ + * ext_tran=0 rev_scan=1 + * + * ---------------------------------------------------------------------------- + * In this implementation, linked commands are designed to work with any DISK + * or CD-ROM, since this linking has only the intent of clustering (time-wise) + * and reordering by elevator sorting commands directed to each device, + * without any relation with the actual SCSI protocol between the controller + * and the device. + * If Q is the queue depth reported at boot time for each device (also named + * cmds/lun) and Q > 2, whenever there is already an active command to the + * device all other commands to the same device (up to Q-1) are kept waiting + * in the elevator sorting queue. When the active command completes, the + * commands in this queue are sorted by sector address. The sort is chosen + * between increasing or decreasing by minimizing the seek distance between + * the sector of the commands just completed and the sector of the first + * command in the list to be sorted. + * Trivial math assures that the unsorted average seek distance when doing + * random seeks over S sectors is S/3. + * When (Q-1) requests are uniformly distributed over S sectors, the average + * distance between two adjacent requests is S/((Q-1) + 1), so the sorted + * average seek distance for (Q-1) random requests over S sectors is S/Q. + * The elevator sorting hence divides the seek distance by a factor Q/3. + * The above pure geometric remarks are valid in all cases and the + * driver effectively reduces the seek distance by the predicted factor + * when there are Q concurrent read i/o operations on the device, but this + * does not necessarily results in a noticeable performance improvement: + * your mileage may vary.... + * + * Note: command reordering inside a batch of queued commands could cause + * wrong results only if there is at least one write request and the + * intersection (sector-wise) of all requests is not empty. + * When the driver detects a batch including overlapping requests + * (a really rare event) strict serial (pid) order is enforced. + * ---------------------------------------------------------------------------- + * The extended translation option (et:y) is useful when using large physical + * disks/arrays. It could also be useful when switching between Adaptec boards + * and DPT boards without reformatting the disk. + * When a boot disk is partitioned with extended translation, in order to + * be able to boot it with a DPT board is could be necessary to add to + * lilo.conf additional commands as in the following example: + * + * fix-table + * disk=/dev/sda bios=0x80 sectors=63 heads=128 cylindres=546 + * + * where the above geometry should be replaced with the one reported at + * power up by the DPT controller. + * ---------------------------------------------------------------------------- + * + * The boards are named EATA0, EATA1,... according to the detection order. + * + * In order to support multiple ISA boards in a reliable way, + * the driver sets host->wish_block = TRUE for all ISA boards. + */ + +#include <linux/version.h> + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) +#define MAX_INT_PARAM 10 + +#if defined(MODULE) +#include <linux/module.h> + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,26) +MODULE_PARM(io_port, "1-" __MODULE_STRING(MAX_INT_PARAM) "i"); +MODULE_PARM(linked_comm, "i"); +MODULE_PARM(tagged_comm, "i"); +MODULE_PARM(link_statistics, "i"); +MODULE_PARM(max_queue_depth, "i"); +MODULE_PARM(tag_mode, "i"); +MODULE_PARM(use_new_eh_code, "i"); +MODULE_PARM(ext_tran, "i"); +MODULE_PARM(rev_scan, "i"); +MODULE_AUTHOR("Dario Ballabio"); +#endif + +#endif + +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include <linux/proc_fs.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" +#include <asm/dma.h> +#include <asm/irq.h> +#include "eata.h" +#include <linux/stat.h> +#include <linux/config.h> +#include <linux/pci.h> + +#if LINUX_VERSION_CODE < LinuxVersionCode(2,1,93) +#include <linux/bios32.h> +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,36) +#include <linux/init.h> +#else +#define __initfunc(A) A +#define __initdata +#define __init +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) +#include <asm/spinlock.h> +#define IRQ_FLAGS +#define IRQ_LOCK +#define IRQ_LOCK_SAVE +#define IRQ_UNLOCK +#define IRQ_UNLOCK_RESTORE +#define SPIN_FLAGS unsigned long spin_flags; +#define SPIN_LOCK spin_lock_irq(&io_request_lock); +#define SPIN_LOCK_SAVE spin_lock_irqsave(&io_request_lock, spin_flags); +#define SPIN_UNLOCK spin_unlock_irq(&io_request_lock); +#define SPIN_UNLOCK_RESTORE \ + spin_unlock_irqrestore(&io_request_lock, spin_flags); +static int use_new_eh_code = TRUE; +#else +#define IRQ_FLAGS unsigned long irq_flags; +#define IRQ_LOCK cli(); +#define IRQ_LOCK_SAVE do {save_flags(irq_flags); cli();} while (0); +#define IRQ_UNLOCK sti(); +#define IRQ_UNLOCK_RESTORE do {restore_flags(irq_flags);} while (0); +#define SPIN_FLAGS +#define SPIN_LOCK +#define SPIN_LOCK_SAVE +#define SPIN_UNLOCK +#define SPIN_UNLOCK_RESTORE +static int use_new_eh_code = FALSE; +#endif + +struct proc_dir_entry proc_scsi_eata2x = { + PROC_SCSI_EATA2X, 6, "eata2x", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +/* Subversion values */ +#define ISA 0 +#define ESA 1 + +#undef FORCE_CONFIG + +#undef DEBUG_LINKED_COMMANDS +#undef DEBUG_DETECT +#undef DEBUG_PCI_DETECT +#undef DEBUG_INTERRUPT +#undef DEBUG_RESET +#undef DEBUG_GENERATE_ERRORS +#undef DEBUG_GENERATE_ABORTS +#undef DEBUG_GEOMETRY + +#define MAX_ISA 4 +#define MAX_VESA 0 +#define MAX_EISA 15 +#define MAX_PCI 16 +#define MAX_BOARDS (MAX_ISA + MAX_VESA + MAX_EISA + MAX_PCI) +#define MAX_CHANNEL 4 +#define MAX_LUN 32 +#define MAX_TARGET 32 +#define MAX_MAILBOXES 64 +#define MAX_SGLIST 64 +#define MAX_LARGE_SGLIST 122 +#define MAX_INTERNAL_RETRIES 64 +#define MAX_CMD_PER_LUN 2 +#define MAX_TAGGED_CMD_PER_LUN (MAX_MAILBOXES - MAX_CMD_PER_LUN) + +#define SKIP ULONG_MAX +#define FALSE 0 +#define TRUE 1 +#define FREE 0 +#define IN_USE 1 +#define LOCKED 2 +#define IN_RESET 3 +#define IGNORE 4 +#define READY 5 +#define ABORTING 6 +#define NO_DMA 0xff +#define MAXLOOP 10000 +#define TAG_MIXED 0 +#define TAG_SIMPLE 1 +#define TAG_HEAD 2 +#define TAG_ORDERED 3 + +#define REG_CMD 7 +#define REG_STATUS 7 +#define REG_AUX_STATUS 8 +#define REG_DATA 0 +#define REG_DATA2 1 +#define REG_SEE 6 +#define REG_LOW 2 +#define REG_LM 3 +#define REG_MID 4 +#define REG_MSB 5 +#define REGION_SIZE 9 +#define MAX_ISA_ADDR 0x03ff +#define MIN_EISA_ADDR 0x1c88 +#define MAX_EISA_ADDR 0xfc88 +#define BSY_ASSERTED 0x80 +#define DRQ_ASSERTED 0x08 +#define ABSY_ASSERTED 0x01 +#define IRQ_ASSERTED 0x02 +#define READ_CONFIG_PIO 0xf0 +#define SET_CONFIG_PIO 0xf1 +#define SEND_CP_PIO 0xf2 +#define RECEIVE_SP_PIO 0xf3 +#define TRUNCATE_XFR_PIO 0xf4 +#define RESET_PIO 0xf9 +#define READ_CONFIG_DMA 0xfd +#define SET_CONFIG_DMA 0xfe +#define SEND_CP_DMA 0xff +#define ASOK 0x00 +#define ASST 0x01 + +#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr)[0]) +#define YESNO(a) ((a) ? 'y' : 'n') +#define TLDEV(type) ((type) == TYPE_DISK || (type) == TYPE_ROM) + +/* "EATA", in Big Endian format */ +#define EATA_SIGNATURE 0x41544145 + +/* Number of valid bytes in the board config structure for EATA 2.0x */ +#define EATA_2_0A_SIZE 28 +#define EATA_2_0B_SIZE 30 +#define EATA_2_0C_SIZE 34 + +/* Board info structure */ +struct eata_info { + ulong data_len; /* Number of valid bytes after this field */ + ulong sign; /* ASCII "EATA" signature */ + unchar :4, /* unused low nibble */ + version:4; /* EATA version, should be 0x1 */ + unchar ocsena:1, /* Overlap Command Support Enabled */ + tarsup:1, /* Target Mode Supported */ + trnxfr:1, /* Truncate Transfer Cmd NOT Necessary */ + morsup:1, /* More Supported */ + dmasup:1, /* DMA Supported */ + drqvld:1, /* DRQ Index (DRQX) is valid */ + ata:1, /* This is an ATA device */ + haaval:1; /* Host Adapter Address Valid */ + ushort cp_pad_len; /* Number of pad bytes after cp_len */ + unchar host_addr[4]; /* Host Adapter SCSI ID for channels 3, 2, 1, 0 */ + ulong cp_len; /* Number of valid bytes in cp */ + ulong sp_len; /* Number of valid bytes in sp */ + ushort queue_size; /* Max number of cp that can be queued */ + ushort unused; + ushort scatt_size; /* Max number of entries in scatter/gather table */ + unchar irq:4, /* Interrupt Request assigned to this controller */ + irq_tr:1, /* 0 for edge triggered, 1 for level triggered */ + second:1, /* 1 if this is a secondary (not primary) controller */ + drqx:2; /* DRQ Index (0=DMA0, 1=DMA7, 2=DMA6, 3=DMA5) */ + unchar sync; /* 1 if scsi target id 7...0 is running sync scsi */ + + /* Structure extension defined in EATA 2.0B */ + unchar isaena:1, /* ISA i/o addressing is disabled/enabled */ + forcaddr:1, /* Port address has been forced */ + large_sg:1, /* 1 if large SG lists are supported */ + res1:1, + :4; + unchar max_id:5, /* Max SCSI target ID number */ + max_chan:3; /* Max SCSI channel number on this board */ + + /* Structure extension defined in EATA 2.0C */ + unchar max_lun; /* Max SCSI LUN number */ + unchar :4, + m1:1, /* This is a PCI with an M1 chip installed */ + idquest:1, /* RAIDNUM returned is questionable */ + pci:1, /* This board is PCI */ + eisa:1; /* This board is EISA */ + unchar raidnum; /* Uniquely identifies this HBA in a system */ + unchar notused; + + ushort ipad[247]; + }; + +/* Board config structure */ +struct eata_config { + ushort len; /* Number of bytes following this field */ + unchar edis:1, /* Disable EATA interface after config command */ + ocena:1, /* Overlapped Commands Enabled */ + mdpena:1, /* Transfer all Modified Data Pointer Messages */ + tarena:1, /* Target Mode Enabled for this controller */ + :4; + unchar cpad[511]; + }; + +/* Returned status packet structure */ +struct mssp { + unchar adapter_status:7, /* State related to current command */ + eoc:1; /* End Of Command (1 = command completed) */ + unchar target_status; /* SCSI status received after data transfer */ + unchar unused[2]; + ulong inv_res_len; /* Number of bytes not transferred */ + struct mscp *cpp; /* Address set in cp */ + char mess[12]; + }; + +struct sg_list { + unsigned int address; /* Segment Address */ + unsigned int num_bytes; /* Segment Length */ + }; + +/* MailBox SCSI Command Packet */ +struct mscp { + unchar sreset:1, /* SCSI Bus Reset Signal should be asserted */ + init:1, /* Re-initialize controller and self test */ + reqsen:1, /* Transfer Request Sense Data to addr using DMA */ + sg:1, /* Use Scatter/Gather */ + :1, + interp:1, /* The controller interprets cp, not the target */ + dout:1, /* Direction of Transfer is Out (Host to Target) */ + din:1; /* Direction of Transfer is In (Target to Host) */ + unchar sense_len; /* Request Sense Length */ + unchar unused[3]; + unchar fwnest:1, /* Send command to a component of an Array Group */ + :7; + unchar phsunit:1, /* Send to Target Physical Unit (bypass RAID) */ + iat:1, /* Inhibit Address Translation */ + hbaci:1, /* Inhibit HBA Caching for this command */ + :5; + unchar target:5, /* SCSI target ID */ + channel:3; /* SCSI channel number */ + unchar lun:5, /* SCSI logical unit number */ + luntar:1, /* This cp is for Target (not LUN) */ + dispri:1, /* Disconnect Privilege granted */ + one:1; /* 1 */ + unchar mess[3]; /* Massage to/from Target */ + unchar cdb[12]; /* Command Descriptor Block */ + ulong data_len; /* If sg=0 Data Length, if sg=1 sglist length */ + struct mscp *cpp; /* Address to be returned in sp */ + ulong data_address; /* If sg=0 Data Address, if sg=1 sglist address */ + ulong sp_addr; /* Address where sp is DMA'ed when cp completes */ + ulong sense_addr; /* Address where Sense Data is DMA'ed on error */ + Scsi_Cmnd *SCpnt; + unsigned int index; /* cp index */ + struct sg_list *sglist; + }; + +struct hostdata { + struct mscp cp[MAX_MAILBOXES]; /* Mailboxes for this board */ + unsigned int cp_stat[MAX_MAILBOXES]; /* FREE, IN_USE, LOCKED, IN_RESET */ + unsigned int last_cp_used; /* Index of last mailbox used */ + unsigned int iocount; /* Total i/o done for this board */ + int board_number; /* Number of this board */ + char board_name[16]; /* Name of this board */ + char board_id[256]; /* data from INQUIRY on this board */ + int in_reset; /* True if board is doing a reset */ + int target_to[MAX_TARGET][MAX_CHANNEL]; /* N. of timeout errors on target */ + int target_redo[MAX_TARGET][MAX_CHANNEL]; /* If TRUE redo i/o on target */ + unsigned int retries; /* Number of internal retries */ + unsigned long last_retried_pid; /* Pid of last retried command */ + unsigned char subversion; /* Bus type, either ISA or EISA/PCI */ + unsigned char protocol_rev; /* EATA 2.0 rev., 'A' or 'B' or 'C' */ + struct mssp sp[2]; /* Returned status for this board */ + }; + +static struct Scsi_Host *sh[MAX_BOARDS + 1]; +static const char *driver_name = "EATA"; +static char sha[MAX_BOARDS]; + +/* Initialize num_boards so that ihdlr can work while detect is in progress */ +static unsigned int num_boards = MAX_BOARDS; + +static unsigned long io_port[] __initdata = { + + /* Space for MAX_INT_PARAM ports usable while loading as a module */ + SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, + SKIP, SKIP, + + /* First ISA */ + 0x1f0, + + /* Space for MAX_PCI ports possibly reported by PCI_BIOS */ + SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, + SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, + + /* MAX_EISA ports */ + 0x1c88, 0x2c88, 0x3c88, 0x4c88, 0x5c88, 0x6c88, 0x7c88, 0x8c88, + 0x9c88, 0xac88, 0xbc88, 0xcc88, 0xdc88, 0xec88, 0xfc88, + + /* Other (MAX_ISA - 1) ports */ + 0x170, 0x230, 0x330, + + /* End of list */ + 0x0 + }; + +#define HD(board) ((struct hostdata *) &sh[board]->hostdata) +#define BN(board) (HD(board)->board_name) + +#define H2DEV(x) htonl(x) +#define DEV2H(x) H2DEV(x) +#define V2DEV(addr) ((addr) ? H2DEV(virt_to_bus((void *)addr)) : 0) +#define DEV2V(addr) ((addr) ? DEV2H(bus_to_virt((unsigned long)addr)) : 0) + +static void do_interrupt_handler(int, void *, struct pt_regs *); +static void flush_dev(Scsi_Device *, unsigned long, unsigned int, unsigned int); +static int do_trace = FALSE; +static int setup_done = FALSE; +static int link_statistics = 0; +static int tag_mode = TAG_MIXED; +static int ext_tran = FALSE; +static int rev_scan = TRUE; + +#if defined(CONFIG_SCSI_EATA_TAGGED_QUEUE) +static int tagged_comm = TRUE; +#else +static int tagged_comm = FALSE; +#endif + +#if defined(CONFIG_SCSI_EATA_LINKED_COMMANDS) +static int linked_comm = TRUE; +#else +static int linked_comm = FALSE; +#endif + +#if defined(CONFIG_SCSI_EATA_MAX_TAGS) +static int max_queue_depth = CONFIG_SCSI_EATA_MAX_TAGS; +#else +static int max_queue_depth = MAX_CMD_PER_LUN; +#endif + +static void select_queue_depths(struct Scsi_Host *host, Scsi_Device *devlist) { + Scsi_Device *dev; + int j, ntag = 0, nuntag = 0, tqd, utqd; + IRQ_FLAGS + + IRQ_LOCK_SAVE + j = ((struct hostdata *) host->hostdata)->board_number; + + for(dev = devlist; dev; dev = dev->next) { + + if (dev->host != host) continue; + + if (TLDEV(dev->type) && (dev->tagged_supported || linked_comm)) + ntag++; + else + nuntag++; + } + + utqd = MAX_CMD_PER_LUN; + + tqd = (host->can_queue - utqd * nuntag) / (ntag ? ntag : 1); + + if (tqd > max_queue_depth) tqd = max_queue_depth; + + if (tqd < MAX_CMD_PER_LUN) tqd = MAX_CMD_PER_LUN; + + for(dev = devlist; dev; dev = dev->next) { + char *tag_suffix = "", *link_suffix = ""; + + if (dev->host != host) continue; + + if (TLDEV(dev->type) && (dev->tagged_supported || linked_comm)) + dev->queue_depth = tqd; + else + dev->queue_depth = utqd; + + if (TLDEV(dev->type)) { + if (linked_comm && dev->queue_depth > 2) + link_suffix = ", sorted"; + else + link_suffix = ", unsorted"; + } + + if (tagged_comm && dev->tagged_supported && TLDEV(dev->type)) { + dev->tagged_queue = 1; + dev->current_tag = 1; + } + + if (dev->tagged_supported && TLDEV(dev->type) && dev->tagged_queue) + tag_suffix = ", tagged"; + else if (dev->tagged_supported && TLDEV(dev->type)) + tag_suffix = ", untagged"; + + printk("%s: scsi%d, channel %d, id %d, lun %d, cmds/lun %d%s%s.\n", + BN(j), host->host_no, dev->channel, dev->id, dev->lun, + dev->queue_depth, link_suffix, tag_suffix); + } + + IRQ_UNLOCK_RESTORE + return; +} + +static inline int wait_on_busy(unsigned long iobase, unsigned int loop) { + + while (inb(iobase + REG_AUX_STATUS) & ABSY_ASSERTED) { + udelay(1L); + if (--loop == 0) return TRUE; + } + + return FALSE; +} + +static inline int do_dma(unsigned long iobase, unsigned int addr, unchar cmd) { + + if (wait_on_busy(iobase, (addr ? MAXLOOP * 100 : MAXLOOP))) return TRUE; + + if ((addr = V2DEV(addr))) { + outb((char) (addr >> 24), iobase + REG_LOW); + outb((char) (addr >> 16), iobase + REG_LM); + outb((char) (addr >> 8), iobase + REG_MID); + outb((char) addr, iobase + REG_MSB); + } + + outb(cmd, iobase + REG_CMD); + return FALSE; +} + +static inline int read_pio(unsigned long iobase, ushort *start, ushort *end) { + unsigned int loop = MAXLOOP; + ushort *p; + + for (p = start; p <= end; p++) { + + while (!(inb(iobase + REG_STATUS) & DRQ_ASSERTED)) { + udelay(1L); + if (--loop == 0) return TRUE; + } + + loop = MAXLOOP; + *p = inw(iobase); + } + + return FALSE; +} + +__initfunc (static inline int + get_pci_irq(unsigned long port_base, unsigned char *apic_irq)) { + +#if defined(CONFIG_PCI) + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,93) + + unsigned int addr; + struct pci_dev *dev = NULL; + + if (!pci_present()) return FALSE; + + while((dev = pci_find_class(PCI_CLASS_STORAGE_SCSI << 8, dev))) { + + if (pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &addr)) continue; + +#if defined(DEBUG_PCI_DETECT) + printk("%s: get_pci_irq, bus %d, devfn 0x%x, addr 0x%x, apic_irq %u.\n", + driver_name, dev->bus->number, dev->devfn, addr, dev->irq); +#endif + + if ((addr & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_IO) + continue; + + if ((addr & PCI_BASE_ADDRESS_IO_MASK) + PCI_BASE_ADDRESS_0 == port_base) { + *apic_irq = dev->irq; + return TRUE; + } + + } + +#endif /* end new style PCI code */ + +#endif /* end CONFIG_PCI */ + + return FALSE; +} + +__initfunc (static inline int port_detect \ + (unsigned long port_base, unsigned int j, Scsi_Host_Template *tpnt)) { + unsigned char irq, dma_channel, subversion, i; + unsigned char protocol_rev, apic_irq; + struct eata_info info; + char *bus_type, dma_name[16], tag_type; + + /* Allowed DMA channels for ISA (0 indicates reserved) */ + unsigned char dma_channel_table[4] = { 5, 6, 7, 0 }; + + char name[16]; + + sprintf(name, "%s%d", driver_name, j); + + if(check_region(port_base, REGION_SIZE)) { + printk("%s: address 0x%03lx in use, skipping probe.\n", name, port_base); + return FALSE; + } + + if (do_dma(port_base, 0, READ_CONFIG_PIO)) return FALSE; + + /* Read the info structure */ + if (read_pio(port_base, (ushort *)&info, (ushort *)&info.ipad[0])) + return FALSE; + + /* Check the controller "EATA" signature */ + if (info.sign != EATA_SIGNATURE) return FALSE; + + if (DEV2H(info.data_len) < EATA_2_0A_SIZE) { + printk("%s: config structure size (%ld bytes) too short, detaching.\n", + name, DEV2H(info.data_len)); + return FALSE; + } + else if (DEV2H(info.data_len) == EATA_2_0A_SIZE) + protocol_rev = 'A'; + else if (DEV2H(info.data_len) == EATA_2_0B_SIZE) + protocol_rev = 'B'; + else + protocol_rev = 'C'; + + if (!setup_done && j > 0 && j <= MAX_PCI) { + bus_type = "PCI"; + subversion = ESA; + } + else if (port_base > MAX_EISA_ADDR || (protocol_rev == 'C' && info.pci)) { + bus_type = "PCI"; + subversion = ESA; + } + else if (port_base >= MIN_EISA_ADDR || (protocol_rev == 'C' && info.eisa)) { + bus_type = "EISA"; + subversion = ESA; + } + else if (protocol_rev == 'C' && !info.eisa && !info.pci) { + bus_type = "ISA"; + subversion = ISA; + } + else if (port_base > MAX_ISA_ADDR) { + bus_type = "PCI"; + subversion = ESA; + } + else { + bus_type = "ISA"; + subversion = ISA; + } + + if (!info.haaval || info.ata) { + printk("%s: address 0x%03lx, unusable %s board (%d%d), detaching.\n", + name, port_base, bus_type, info.haaval, info.ata); + return FALSE; + } + + if (info.drqvld) { + + if (subversion == ESA) + printk("%s: warning, weird %s board using DMA.\n", name, bus_type); + + subversion = ISA; + dma_channel = dma_channel_table[3 - info.drqx]; + } + else { + + if (subversion == ISA) + printk("%s: warning, weird %s board not using DMA.\n", name, bus_type); + + subversion = ESA; + dma_channel = NO_DMA; + } + + if (!info.dmasup) + printk("%s: warning, DMA protocol support not asserted.\n", name); + + irq = info.irq; + + if (subversion == ESA && !info.irq_tr) + printk("%s: warning, LEVEL triggering is suggested for IRQ %u.\n", + name, irq); + + if (get_pci_irq(port_base, &apic_irq) && (irq != apic_irq)) { + printk("%s: IRQ %u mapped to IO-APIC IRQ %u.\n", name, irq, apic_irq); + irq = apic_irq; + } + + /* Board detected, allocate its IRQ */ + if (request_irq(irq, do_interrupt_handler, + SA_INTERRUPT | ((subversion == ESA) ? SA_SHIRQ : 0), + driver_name, (void *) &sha[j])) { + printk("%s: unable to allocate IRQ %u, detaching.\n", name, irq); + return FALSE; + } + + if (subversion == ISA && request_dma(dma_channel, driver_name)) { + printk("%s: unable to allocate DMA channel %u, detaching.\n", + name, dma_channel); + free_irq(irq, &sha[j]); + return FALSE; + } + +#if defined(FORCE_CONFIG) + { + struct eata_config config; + + /* Set board configuration */ + memset((char *)&config, 0, sizeof(struct eata_config)); + config.len = (ushort) htons((ushort)510); + config.ocena = TRUE; + + if (do_dma(port_base, (unsigned int)&config, SET_CONFIG_DMA)) { + printk("%s: busy timeout sending configuration, detaching.\n", name); + return FALSE; + } + } +#endif + + sh[j] = scsi_register(tpnt, sizeof(struct hostdata)); + + if (sh[j] == NULL) { + printk("%s: unable to register host, detaching.\n", name); + + free_irq(irq, &sha[j]); + + if (subversion == ISA) free_dma(dma_channel); + + return FALSE; + } + + sh[j]->io_port = port_base; + sh[j]->unique_id = port_base; + sh[j]->n_io_port = REGION_SIZE; + sh[j]->dma_channel = dma_channel; + sh[j]->irq = irq; + sh[j]->sg_tablesize = (ushort) ntohs(info.scatt_size); + sh[j]->this_id = (ushort) info.host_addr[3]; + sh[j]->can_queue = (ushort) ntohs(info.queue_size); + sh[j]->cmd_per_lun = MAX_CMD_PER_LUN; + sh[j]->select_queue_depths = select_queue_depths; + + /* Register the I/O space that we use */ + request_region(sh[j]->io_port, sh[j]->n_io_port, driver_name); + + memset(HD(j), 0, sizeof(struct hostdata)); + HD(j)->subversion = subversion; + HD(j)->protocol_rev = protocol_rev; + HD(j)->board_number = j; + + if (HD(j)->subversion == ESA) + sh[j]->unchecked_isa_dma = FALSE; + else { + sh[j]->wish_block = TRUE; + sh[j]->unchecked_isa_dma = TRUE; + disable_dma(dma_channel); + clear_dma_ff(dma_channel); + set_dma_mode(dma_channel, DMA_MODE_CASCADE); + enable_dma(dma_channel); + } + + strcpy(BN(j), name); + + /* DPT PM2012 does not allow to detect sg_tablesize correctly */ + if (sh[j]->sg_tablesize > MAX_SGLIST || sh[j]->sg_tablesize < 2) { + printk("%s: detect, wrong n. of SG lists %d, fixed.\n", + BN(j), sh[j]->sg_tablesize); + sh[j]->sg_tablesize = MAX_SGLIST; + } + + /* DPT PM2012 does not allow to detect can_queue correctly */ + if (sh[j]->can_queue > MAX_MAILBOXES || sh[j]->can_queue < 2) { + printk("%s: detect, wrong n. of mbox %d, fixed.\n", + BN(j), sh[j]->can_queue); + sh[j]->can_queue = MAX_MAILBOXES; + } + + if (protocol_rev != 'A') { + + if (info.max_chan > 0 && info.max_chan < MAX_CHANNEL) + sh[j]->max_channel = info.max_chan; + + if (info.max_id > 7 && info.max_id < MAX_TARGET) + sh[j]->max_id = info.max_id + 1; + + if (info.large_sg && sh[j]->sg_tablesize == MAX_SGLIST) + sh[j]->sg_tablesize = MAX_LARGE_SGLIST; + } + + if (protocol_rev == 'C') { + + if (info.max_lun > 7 && info.max_lun < MAX_LUN) + sh[j]->max_lun = info.max_lun + 1; + } + + if (dma_channel == NO_DMA) sprintf(dma_name, "%s", "BMST"); + else sprintf(dma_name, "DMA %u", dma_channel); + + for (i = 0; i < sh[j]->can_queue; i++) + if (! ((&HD(j)->cp[i])->sglist = kmalloc( + sh[j]->sg_tablesize * sizeof(struct sg_list), + (sh[j]->unchecked_isa_dma ? GFP_DMA : 0) | GFP_ATOMIC))) { + printk("%s: kmalloc SGlist failed, mbox %d, detaching.\n", BN(j), i); + eata2x_release(sh[j]); + return FALSE; + } + + if (max_queue_depth > MAX_TAGGED_CMD_PER_LUN) + max_queue_depth = MAX_TAGGED_CMD_PER_LUN; + + if (max_queue_depth < MAX_CMD_PER_LUN) max_queue_depth = MAX_CMD_PER_LUN; + + if (tagged_comm) { + if (tag_mode == TAG_SIMPLE) tag_type = '1'; + else if (tag_mode == TAG_HEAD) tag_type = '2'; + else if (tag_mode == TAG_ORDERED) tag_type = '3'; + else tag_type = 'y'; + } + else tag_type = 'n'; + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + sh[j]->hostt->use_new_eh_code = use_new_eh_code; +#else + use_new_eh_code = FALSE; +#endif + + if (j == 0) { + printk("EATA/DMA 2.0x: Copyright (C) 1994-1998 Dario Ballabio.\n"); + printk("%s config options -> tc:%c, lc:%c, mq:%d, eh:%c, rs:%c, et:%c.\n", + driver_name, tag_type, YESNO(linked_comm), max_queue_depth, + YESNO(use_new_eh_code), YESNO(rev_scan), YESNO(ext_tran)); + } + + printk("%s: 2.0%c, %s 0x%03lx, IRQ %u, %s, SG %d, MB %d.\n", + BN(j), HD(j)->protocol_rev, bus_type, (unsigned long)sh[j]->io_port, + sh[j]->irq, dma_name, sh[j]->sg_tablesize, sh[j]->can_queue); + + if (sh[j]->max_id > 8 || sh[j]->max_lun > 8) + printk("%s: wide SCSI support enabled, max_id %u, max_lun %u.\n", + BN(j), sh[j]->max_id, sh[j]->max_lun); + + for (i = 0; i <= sh[j]->max_channel; i++) + printk("%s: SCSI channel %u enabled, host target ID %d.\n", + BN(j), i, info.host_addr[3 - i]); + +#if defined(DEBUG_DETECT) + printk("%s: Vers. 0x%x, ocs %u, tar %u, trnxfr %u, more %u, SYNC 0x%x, "\ + "sec. %u, infol %ld, cpl %ld spl %ld.\n", name, info.version, + info.ocsena, info.tarsup, info.trnxfr, info.morsup, info.sync, + info.second, DEV2H(info.data_len), DEV2H(info.cp_len), + DEV2H(info.sp_len)); + + if (protocol_rev == 'B' || protocol_rev == 'C') + printk("%s: isaena %u, forcaddr %u, max_id %u, max_chan %u, "\ + "large_sg %u, res1 %u.\n", name, info.isaena, info.forcaddr, + info.max_id, info.max_chan, info.large_sg, info.res1); + + if (protocol_rev == 'C') + printk("%s: max_lun %u, m1 %u, idquest %u, pci %u, eisa %u, "\ + "raidnum %u.\n", name, info.max_lun, info.m1, info.idquest, + info.pci, info.eisa, info.raidnum); +#endif + + return TRUE; +} + +__initfunc (void eata2x_setup(char *str, int *ints)) { + int i, argc = ints[0]; + char *cur = str, *pc; + + if (argc > 0) { + + if (argc > MAX_INT_PARAM) argc = MAX_INT_PARAM; + + for (i = 0; i < argc; i++) io_port[i] = ints[i + 1]; + + io_port[i] = 0; + setup_done = TRUE; + } + + while (cur && (pc = strchr(cur, ':'))) { + int val = 0, c = *++pc; + + if (c == 'n' || c == 'N') val = FALSE; + else if (c == 'y' || c == 'Y') val = TRUE; + else val = (int) simple_strtoul(pc, NULL, 0); + + if (!strncmp(cur, "lc:", 3)) linked_comm = val; + else if (!strncmp(cur, "tc:", 3)) tagged_comm = val; + else if (!strncmp(cur, "tm:", 3)) tag_mode = val; + else if (!strncmp(cur, "mq:", 3)) max_queue_depth = val; + else if (!strncmp(cur, "ls:", 3)) link_statistics = val; + else if (!strncmp(cur, "eh:", 3)) use_new_eh_code = val; + else if (!strncmp(cur, "et:", 3)) ext_tran = val; + else if (!strncmp(cur, "rs:", 3)) rev_scan = val; + + if ((cur = strchr(cur, ','))) ++cur; + } + + return; +} + +__initfunc (static void add_pci_ports(void)) { + +#if defined(CONFIG_PCI) + + unsigned int addr, k; + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,93) + + struct pci_dev *dev = NULL; + + if (!pci_present()) return; + + for (k = 0; k < MAX_PCI; k++) { + + if (!(dev = pci_find_class(PCI_CLASS_STORAGE_SCSI << 8, dev))) break; + + if (pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &addr)) continue; + +#if defined(DEBUG_PCI_DETECT) + printk("%s: detect, seq. %d, bus %d, devfn 0x%x, addr 0x%x.\n", + driver_name, k, dev->bus->number, dev->devfn, addr); +#endif + + if ((addr & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_IO) + continue; + + /* Order addresses according to rev_scan value */ + io_port[MAX_INT_PARAM + (rev_scan ? (MAX_PCI - k) : (1 + k))] = + (addr & PCI_BASE_ADDRESS_IO_MASK) + PCI_BASE_ADDRESS_0; + } + +#else /* else old style PCI code */ + + unsigned short i = 0; + unsigned char bus, devfn; + + if (!pcibios_present()) return; + + for (k = 0; k < MAX_PCI; k++) { + + if (pcibios_find_class(PCI_CLASS_STORAGE_SCSI << 8, i++, &bus, &devfn) + != PCIBIOS_SUCCESSFUL) break; + + if (pcibios_read_config_dword(bus, devfn, PCI_BASE_ADDRESS_0, &addr) + != PCIBIOS_SUCCESSFUL) continue; + +#if defined(DEBUG_PCI_DETECT) + printk("%s: detect, seq. %d, bus %d, devfn 0x%x, addr 0x%x.\n", + driver_name, k, bus, devfn, addr); +#endif + + if ((addr & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_IO) + continue; + + /* Order addresses according to rev_scan value */ + io_port[MAX_INT_PARAM + (rev_scan ? (MAX_PCI - k) : (1 + k))] = + (addr & PCI_BASE_ADDRESS_IO_MASK) + PCI_BASE_ADDRESS_0; + } + +#endif /* end old style PCI code */ + +#endif /* end CONFIG_PCI */ + + return; +} + +__initfunc (int eata2x_detect(Scsi_Host_Template *tpnt)) { + unsigned int j = 0, k; + IRQ_FLAGS + + IRQ_LOCK_SAVE + tpnt->proc_dir = &proc_scsi_eata2x; + +#if defined(MODULE) + /* io_port could have been modified when loading as a module */ + if(io_port[0] != SKIP) { + setup_done = TRUE; + io_port[MAX_INT_PARAM] = 0; + } +#endif + + for (k = 0; k < MAX_BOARDS + 1; k++) sh[k] = NULL; + + if (!setup_done) add_pci_ports(); + + for (k = 0; io_port[k]; k++) { + + if (io_port[k] == SKIP) continue; + + if (j < MAX_BOARDS && port_detect(io_port[k], j, tpnt)) j++; + } + + num_boards = j; + IRQ_UNLOCK_RESTORE + return j; +} + +static inline void build_sg_list(struct mscp *cpp, Scsi_Cmnd *SCpnt) { + unsigned int k; + struct scatterlist *sgpnt; + + sgpnt = (struct scatterlist *) SCpnt->request_buffer; + + for (k = 0; k < SCpnt->use_sg; k++) { + cpp->sglist[k].address = V2DEV(sgpnt[k].address); + cpp->sglist[k].num_bytes = H2DEV(sgpnt[k].length); + } + + cpp->data_address = V2DEV(cpp->sglist); + cpp->data_len = H2DEV((SCpnt->use_sg * sizeof(struct sg_list))); +} + +static inline int do_qcomm(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) { + unsigned int i, j, k; + struct mscp *cpp; + struct mssp *spp; + + static const unsigned char data_out_cmds[] = { + 0x0a, 0x2a, 0x15, 0x55, 0x04, 0x07, 0x18, 0x1d, 0x24, 0x2e, + 0x30, 0x31, 0x32, 0x38, 0x39, 0x3a, 0x3b, 0x3d, 0x3f, 0x40, + 0x41, 0x4c, 0xaa, 0xae, 0xb0, 0xb1, 0xb2, 0xb6, 0xea, 0x1b + }; + + static const unsigned char data_none_cmds[] = { + 0x01, 0x0b, 0x10, 0x11, 0x13, 0x16, 0x17, 0x19, 0x2b, 0x1e, + 0x2c, 0xac, 0x2f, 0xaf, 0x33, 0xb3, 0x35, 0x36, 0x45, 0x47, + 0x48, 0x49, 0xa9, 0x4b, 0xa5, 0xa6, 0xb5 + }; + + /* j is the board number */ + j = ((struct hostdata *) SCpnt->host->hostdata)->board_number; + + if (SCpnt->host_scribble) + panic("%s: qcomm, pid %ld, SCpnt %p already active.\n", + BN(j), SCpnt->pid, SCpnt); + + /* i is the mailbox number, look for the first free mailbox + starting from last_cp_used */ + i = HD(j)->last_cp_used + 1; + + for (k = 0; k < sh[j]->can_queue; k++, i++) { + + if (i >= sh[j]->can_queue) i = 0; + + if (HD(j)->cp_stat[i] == FREE) { + HD(j)->last_cp_used = i; + break; + } + } + + if (k == sh[j]->can_queue) { + printk("%s: qcomm, no free mailbox.\n", BN(j)); + return 1; + } + + /* Set pointer to control packet structure */ + cpp = &HD(j)->cp[i]; + + memset(cpp, 0, sizeof(struct mscp) - sizeof(struct sg_list *)); + + /* Set pointer to status packet structure */ + spp = &HD(j)->sp[0]; + + /* The EATA protocol uses Big Endian format */ + cpp->sp_addr = V2DEV(spp); + + cpp->cpp = cpp; + SCpnt->scsi_done = done; + cpp->index = i; + SCpnt->host_scribble = (unsigned char *) &cpp->index; + + if (do_trace) printk("%s: qcomm, mbox %d, target %d.%d:%d, pid %ld.\n", + BN(j), i, SCpnt->channel, SCpnt->target, + SCpnt->lun, SCpnt->pid); + + for (k = 0; k < ARRAY_SIZE(data_out_cmds); k++) + if (SCpnt->cmnd[0] == data_out_cmds[k]) { + cpp->dout = TRUE; + break; + } + + if ((cpp->din = !cpp->dout)) + for (k = 0; k < ARRAY_SIZE(data_none_cmds); k++) + if (SCpnt->cmnd[0] == data_none_cmds[k]) { + cpp->din = FALSE; + break; + } + + cpp->reqsen = TRUE; + cpp->dispri = TRUE; +#if 0 + if (SCpnt->device->type == TYPE_TAPE) cpp->hbaci = TRUE; +#endif + cpp->one = TRUE; + cpp->channel = SCpnt->channel; + cpp->target = SCpnt->target; + cpp->lun = SCpnt->lun; + cpp->SCpnt = SCpnt; + cpp->sense_addr = V2DEV(SCpnt->sense_buffer); + cpp->sense_len = sizeof SCpnt->sense_buffer; + + if (SCpnt->device->tagged_queue) { + + if (HD(j)->target_redo[SCpnt->target][SCpnt->channel] || + HD(j)->target_to[SCpnt->target][SCpnt->channel]) + cpp->mess[0] = ORDERED_QUEUE_TAG; + else if (tag_mode == TAG_SIMPLE) cpp->mess[0] = SIMPLE_QUEUE_TAG; + else if (tag_mode == TAG_HEAD) cpp->mess[0] = HEAD_OF_QUEUE_TAG; + else if (tag_mode == TAG_ORDERED) cpp->mess[0] = ORDERED_QUEUE_TAG; + else if (SCpnt->device->current_tag == 0) + cpp->mess[0] = ORDERED_QUEUE_TAG; + else if (SCpnt->device->current_tag == 1) + cpp->mess[0] = HEAD_OF_QUEUE_TAG; + else + cpp->mess[0] = SIMPLE_QUEUE_TAG; + + cpp->mess[1] = SCpnt->device->current_tag++; + } + + if (SCpnt->use_sg) { + cpp->sg = TRUE; + build_sg_list(cpp, SCpnt); + } + else { + cpp->data_address = V2DEV(SCpnt->request_buffer); + cpp->data_len = H2DEV(SCpnt->request_bufflen); + } + + memcpy(cpp->cdb, SCpnt->cmnd, SCpnt->cmd_len); + + if (linked_comm && SCpnt->device->queue_depth > 2 + && TLDEV(SCpnt->device->type)) { + HD(j)->cp_stat[i] = READY; + flush_dev(SCpnt->device, SCpnt->request.sector, j, FALSE); + return 0; + } + + /* Send control packet to the board */ + if (do_dma(sh[j]->io_port, (unsigned int) cpp, SEND_CP_DMA)) { + SCpnt->host_scribble = NULL; + printk("%s: qcomm, target %d.%d:%d, pid %ld, adapter busy.\n", + BN(j), SCpnt->channel, SCpnt->target, SCpnt->lun, SCpnt->pid); + return 1; + } + + HD(j)->cp_stat[i] = IN_USE; + return 0; +} + +int eata2x_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) { + int rtn; + IRQ_FLAGS + + IRQ_LOCK_SAVE + rtn = do_qcomm(SCpnt, done); + IRQ_UNLOCK_RESTORE + return rtn; +} + +static inline int do_old_abort(Scsi_Cmnd *SCarg) { + unsigned int i, j; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + + if (SCarg->host_scribble == NULL || + (SCarg->serial_number_at_timeout && + (SCarg->serial_number != SCarg->serial_number_at_timeout))) { + printk("%s: abort, target %d.%d:%d, pid %ld inactive.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + return SCSI_ABORT_NOT_RUNNING; + } + + i = *(unsigned int *)SCarg->host_scribble; + printk("%s: abort, mbox %d, target %d.%d:%d, pid %ld.\n", + BN(j), i, SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (i >= sh[j]->can_queue) + panic("%s: abort, invalid SCarg->host_scribble.\n", BN(j)); + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: abort, timeout error.\n", BN(j)); + return SCSI_ABORT_ERROR; + } + + if (HD(j)->cp_stat[i] == FREE) { + printk("%s: abort, mbox %d is free.\n", BN(j), i); + return SCSI_ABORT_NOT_RUNNING; + } + + if (HD(j)->cp_stat[i] == IN_USE) { + printk("%s: abort, mbox %d is in use.\n", BN(j), i); + + if (SCarg != HD(j)->cp[i].SCpnt) + panic("%s: abort, mbox %d, SCarg %p, cp SCpnt %p.\n", + BN(j), i, SCarg, HD(j)->cp[i].SCpnt); + + if (inb(sh[j]->io_port + REG_AUX_STATUS) & IRQ_ASSERTED) + printk("%s: abort, mbox %d, interrupt pending.\n", BN(j), i); + + return SCSI_ABORT_SNOOZE; + } + + if (HD(j)->cp_stat[i] == IN_RESET) { + printk("%s: abort, mbox %d is in reset.\n", BN(j), i); + return SCSI_ABORT_ERROR; + } + + if (HD(j)->cp_stat[i] == LOCKED) { + printk("%s: abort, mbox %d is locked.\n", BN(j), i); + return SCSI_ABORT_NOT_RUNNING; + } + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + SCarg->result = DID_ABORT << 16; + SCarg->host_scribble = NULL; + HD(j)->cp_stat[i] = FREE; + printk("%s, abort, mbox %d ready, DID_ABORT, pid %ld done.\n", + BN(j), i, SCarg->pid); + SCarg->scsi_done(SCarg); + return SCSI_ABORT_SUCCESS; + } + + panic("%s: abort, mbox %d, invalid cp_stat.\n", BN(j), i); +} + +int eata2x_old_abort(Scsi_Cmnd *SCarg) { + int rtn; + IRQ_FLAGS + + IRQ_LOCK_SAVE + rtn = do_old_abort(SCarg); + IRQ_UNLOCK_RESTORE + return rtn; +} + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + +static inline int do_abort(Scsi_Cmnd *SCarg) { + unsigned int i, j; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + + if (SCarg->host_scribble == NULL) { + printk("%s: abort, target %d.%d:%d, pid %ld inactive.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + return SUCCESS; + } + + i = *(unsigned int *)SCarg->host_scribble; + printk("%s: abort, mbox %d, target %d.%d:%d, pid %ld.\n", + BN(j), i, SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (i >= sh[j]->can_queue) + panic("%s: abort, invalid SCarg->host_scribble.\n", BN(j)); + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: abort, timeout error.\n", BN(j)); + return FAILED; + } + + if (HD(j)->cp_stat[i] == FREE) { + printk("%s: abort, mbox %d is free.\n", BN(j), i); + return SUCCESS; + } + + if (HD(j)->cp_stat[i] == IN_USE) { + printk("%s: abort, mbox %d is in use.\n", BN(j), i); + + if (SCarg != HD(j)->cp[i].SCpnt) + panic("%s: abort, mbox %d, SCarg %p, cp SCpnt %p.\n", + BN(j), i, SCarg, HD(j)->cp[i].SCpnt); + + if (inb(sh[j]->io_port + REG_AUX_STATUS) & IRQ_ASSERTED) + printk("%s: abort, mbox %d, interrupt pending.\n", BN(j), i); + + if (SCarg->eh_state == SCSI_STATE_TIMEOUT) { + SCarg->host_scribble = NULL; + HD(j)->cp_stat[i] = FREE; + printk("%s, abort, mbox %d, eh_state timeout, pid %ld.\n", + BN(j), i, SCarg->pid); + return SUCCESS; + } + + return FAILED; + } + + if (HD(j)->cp_stat[i] == IN_RESET) { + printk("%s: abort, mbox %d is in reset.\n", BN(j), i); + return FAILED; + } + + if (HD(j)->cp_stat[i] == LOCKED) { + printk("%s: abort, mbox %d is locked.\n", BN(j), i); + return SUCCESS; + } + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + SCarg->result = DID_ABORT << 16; + SCarg->host_scribble = NULL; + HD(j)->cp_stat[i] = FREE; + printk("%s, abort, mbox %d ready, DID_ABORT, pid %ld done.\n", + BN(j), i, SCarg->pid); + SCarg->scsi_done(SCarg); + return SUCCESS; + } + + panic("%s: abort, mbox %d, invalid cp_stat.\n", BN(j), i); +} + +int eata2x_abort(Scsi_Cmnd *SCarg) { + + return do_abort(SCarg); +} + +#endif /* new_eh_code */ + +static inline int do_old_reset(Scsi_Cmnd *SCarg) { + unsigned int i, j, time, k, c, limit = 0; + int arg_done = FALSE; + Scsi_Cmnd *SCpnt; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + printk("%s: reset, enter, target %d.%d:%d, pid %ld.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (SCarg->host_scribble == NULL) + printk("%s: reset, pid %ld inactive.\n", BN(j), SCarg->pid); + + if (SCarg->serial_number_at_timeout && + (SCarg->serial_number != SCarg->serial_number_at_timeout)) { + printk("%s: reset, pid %ld, reset not running.\n", BN(j), SCarg->pid); + return SCSI_RESET_NOT_RUNNING; + } + + if (HD(j)->in_reset) { + printk("%s: reset, exit, already in reset.\n", BN(j)); + return SCSI_RESET_ERROR; + } + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: reset, exit, timeout error.\n", BN(j)); + return SCSI_RESET_ERROR; + } + + HD(j)->retries = 0; + + for (c = 0; c <= sh[j]->max_channel; c++) + for (k = 0; k < sh[j]->max_id; k++) { + HD(j)->target_redo[k][c] = TRUE; + HD(j)->target_to[k][c] = 0; + } + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == FREE) continue; + + if (HD(j)->cp_stat[i] == LOCKED) { + HD(j)->cp_stat[i] = FREE; + printk("%s: reset, locked mbox %d forced free.\n", BN(j), i); + continue; + } + + if (!(SCpnt = HD(j)->cp[i].SCpnt)) + panic("%s: reset, mbox %d, SCpnt == NULL.\n", BN(j), i); + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + HD(j)->cp_stat[i] = ABORTING; + printk("%s: reset, mbox %d aborting, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + else { + HD(j)->cp_stat[i] = IN_RESET; + printk("%s: reset, mbox %d in reset, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + if (SCpnt->host_scribble == NULL) + panic("%s: reset, mbox %d, garbled SCpnt.\n", BN(j), i); + + if (*(unsigned int *)SCpnt->host_scribble != i) + panic("%s: reset, mbox %d, index mismatch.\n", BN(j), i); + + if (SCpnt->scsi_done == NULL) + panic("%s: reset, mbox %d, SCpnt->scsi_done == NULL.\n", BN(j), i); + + if (SCpnt == SCarg) arg_done = TRUE; + } + + if (do_dma(sh[j]->io_port, 0, RESET_PIO)) { + printk("%s: reset, cannot reset, timeout error.\n", BN(j)); + return SCSI_RESET_ERROR; + } + + printk("%s: reset, board reset done, enabling interrupts.\n", BN(j)); + +#if defined(DEBUG_RESET) + do_trace = TRUE; +#endif + + HD(j)->in_reset = TRUE; + SPIN_UNLOCK + IRQ_UNLOCK + time = jiffies; + while ((jiffies - time) < (10 * HZ) && limit++ < 200000) udelay(100L); + IRQ_LOCK + SPIN_LOCK + printk("%s: reset, interrupts disabled, loops %d.\n", BN(j), limit); + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == IN_RESET) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox is still waiting for its interrupt */ + HD(j)->cp_stat[i] = LOCKED; + + printk("%s, reset, mbox %d locked, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else if (HD(j)->cp_stat[i] == ABORTING) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox was never queued to the adapter */ + HD(j)->cp_stat[i] = FREE; + + printk("%s, reset, mbox %d aborting, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else + + /* Any other mailbox has already been set free by interrupt */ + continue; + + SCpnt->scsi_done(SCpnt); + IRQ_LOCK + } + + HD(j)->in_reset = FALSE; + do_trace = FALSE; + + if (arg_done) { + printk("%s: reset, exit, success.\n", BN(j)); + return SCSI_RESET_SUCCESS; + } + else { + printk("%s: reset, exit, wakeup.\n", BN(j)); + return SCSI_RESET_PUNT; + } +} + +int eata2x_old_reset(Scsi_Cmnd *SCarg, unsigned int reset_flags) { + int rtn; + IRQ_FLAGS + + IRQ_LOCK_SAVE + rtn = do_old_reset(SCarg); + IRQ_UNLOCK_RESTORE + return rtn; +} + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + +static inline int do_reset(Scsi_Cmnd *SCarg) { + unsigned int i, j, time, k, c, limit = 0; + int arg_done = FALSE; + Scsi_Cmnd *SCpnt; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + printk("%s: reset, enter, target %d.%d:%d, pid %ld.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (SCarg->host_scribble == NULL) + printk("%s: reset, pid %ld inactive.\n", BN(j), SCarg->pid); + + if (HD(j)->in_reset) { + printk("%s: reset, exit, already in reset.\n", BN(j)); + return FAILED; + } + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: reset, exit, timeout error.\n", BN(j)); + return FAILED; + } + + HD(j)->retries = 0; + + for (c = 0; c <= sh[j]->max_channel; c++) + for (k = 0; k < sh[j]->max_id; k++) { + HD(j)->target_redo[k][c] = TRUE; + HD(j)->target_to[k][c] = 0; + } + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == FREE) continue; + + if (HD(j)->cp_stat[i] == LOCKED) { + HD(j)->cp_stat[i] = FREE; + printk("%s: reset, locked mbox %d forced free.\n", BN(j), i); + continue; + } + + if (!(SCpnt = HD(j)->cp[i].SCpnt)) + panic("%s: reset, mbox %d, SCpnt == NULL.\n", BN(j), i); + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + HD(j)->cp_stat[i] = ABORTING; + printk("%s: reset, mbox %d aborting, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + else { + HD(j)->cp_stat[i] = IN_RESET; + printk("%s: reset, mbox %d in reset, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + if (SCpnt->host_scribble == NULL) + panic("%s: reset, mbox %d, garbled SCpnt.\n", BN(j), i); + + if (*(unsigned int *)SCpnt->host_scribble != i) + panic("%s: reset, mbox %d, index mismatch.\n", BN(j), i); + + if (SCpnt->scsi_done == NULL) + panic("%s: reset, mbox %d, SCpnt->scsi_done == NULL.\n", BN(j), i); + + if (SCpnt == SCarg) arg_done = TRUE; + } + + if (do_dma(sh[j]->io_port, 0, RESET_PIO)) { + printk("%s: reset, cannot reset, timeout error.\n", BN(j)); + return FAILED; + } + + printk("%s: reset, board reset done, enabling interrupts.\n", BN(j)); + +#if defined(DEBUG_RESET) + do_trace = TRUE; +#endif + + HD(j)->in_reset = TRUE; + SPIN_UNLOCK + IRQ_UNLOCK + time = jiffies; + while ((jiffies - time) < (10 * HZ) && limit++ < 200000) udelay(100L); + IRQ_LOCK + SPIN_LOCK + printk("%s: reset, interrupts disabled, loops %d.\n", BN(j), limit); + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == IN_RESET) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox is still waiting for its interrupt */ + HD(j)->cp_stat[i] = LOCKED; + + printk("%s, reset, mbox %d locked, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else if (HD(j)->cp_stat[i] == ABORTING) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox was never queued to the adapter */ + HD(j)->cp_stat[i] = FREE; + + printk("%s, reset, mbox %d aborting, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else + + /* Any other mailbox has already been set free by interrupt */ + continue; + + SCpnt->scsi_done(SCpnt); + IRQ_LOCK + } + + HD(j)->in_reset = FALSE; + do_trace = FALSE; + + if (arg_done) printk("%s: reset, exit, pid %ld done.\n", BN(j), SCarg->pid); + else printk("%s: reset, exit.\n", BN(j)); + + return SUCCESS; +} + +int eata2x_reset(Scsi_Cmnd *SCarg) { + + return do_reset(SCarg); +} + +#endif /* new_eh_code */ + +int eata2x_biosparam(Disk *disk, kdev_t dev, int *dkinfo) { + int size = disk->capacity; + + if (ext_tran || (scsicam_bios_param(disk, dev, dkinfo) < 0)) { + dkinfo[0] = 255; + dkinfo[1] = 63; + dkinfo[2] = size / (dkinfo[0] * dkinfo[1]); + } + +#if defined (DEBUG_GEOMETRY) + printk ("%s: biosparam, head=%d, sec=%d, cyl=%d.\n", driver_name, + dkinfo[0], dkinfo[1], dkinfo[2]); +#endif + + return FALSE; +} + +static void sort(unsigned long sk[], unsigned int da[], unsigned int n, + unsigned int rev) { + unsigned int i, j, k, y; + unsigned long x; + + for (i = 0; i < n - 1; i++) { + k = i; + + for (j = k + 1; j < n; j++) + if (rev) { + if (sk[j] > sk[k]) k = j; + } + else { + if (sk[j] < sk[k]) k = j; + } + + if (k != i) { + x = sk[k]; sk[k] = sk[i]; sk[i] = x; + y = da[k]; da[k] = da[i]; da[i] = y; + } + } + + return; + } + +static inline int reorder(unsigned int j, unsigned long cursec, + unsigned int ihdlr, unsigned int il[], unsigned int n_ready) { + Scsi_Cmnd *SCpnt; + struct mscp *cpp; + unsigned int k, n; + unsigned int rev = FALSE, s = TRUE, r = TRUE; + unsigned int input_only = TRUE, overlap = FALSE; + unsigned long sl[n_ready], pl[n_ready], ll[n_ready]; + unsigned long maxsec = 0, minsec = ULONG_MAX, seek = 0, iseek = 0; + unsigned long ioseek = 0; + + static unsigned int flushcount = 0, batchcount = 0, sortcount = 0; + static unsigned int readycount = 0, ovlcount = 0, inputcount = 0; + static unsigned int readysorted = 0, revcount = 0; + static unsigned long seeksorted = 0, seeknosort = 0; + + if (link_statistics && !(++flushcount % link_statistics)) + printk("fc %d bc %d ic %d oc %d rc %d rs %d sc %d re %d"\ + " av %ldK as %ldK.\n", flushcount, batchcount, inputcount, + ovlcount, readycount, readysorted, sortcount, revcount, + seeknosort / (readycount + 1), + seeksorted / (readycount + 1)); + + if (n_ready <= 1) return FALSE; + + for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + + if (!cpp->din) input_only = FALSE; + + if (SCpnt->request.sector < minsec) minsec = SCpnt->request.sector; + if (SCpnt->request.sector > maxsec) maxsec = SCpnt->request.sector; + + sl[n] = SCpnt->request.sector; + ioseek += SCpnt->request.nr_sectors; + + if (!n) continue; + + if (sl[n] < sl[n - 1]) s = FALSE; + if (sl[n] > sl[n - 1]) r = FALSE; + + if (link_statistics) { + if (sl[n] > sl[n - 1]) + seek += sl[n] - sl[n - 1]; + else + seek += sl[n - 1] - sl[n]; + } + + } + + if (link_statistics) { + if (cursec > sl[0]) seek += cursec - sl[0]; else seek += sl[0] - cursec; + } + + if (cursec > ((maxsec + minsec) / 2)) rev = TRUE; + + if (ioseek > ((maxsec - minsec) / 2)) rev = FALSE; + + if (!((rev && r) || (!rev && s))) sort(sl, il, n_ready, rev); + + if (!input_only) for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + ll[n] = SCpnt->request.nr_sectors; pl[n] = SCpnt->pid; + + if (!n) continue; + + if ((sl[n] == sl[n - 1]) || (!rev && ((sl[n - 1] + ll[n - 1]) > sl[n])) + || (rev && ((sl[n] + ll[n]) > sl[n - 1]))) overlap = TRUE; + } + + if (overlap) sort(pl, il, n_ready, FALSE); + + if (link_statistics) { + if (cursec > sl[0]) iseek = cursec - sl[0]; else iseek = sl[0] - cursec; + batchcount++; readycount += n_ready, seeknosort += seek / 1024; + if (input_only) inputcount++; + if (overlap) { ovlcount++; seeksorted += iseek / 1024; } + else seeksorted += (iseek + maxsec - minsec) / 1024; + if (rev && !r) { revcount++; readysorted += n_ready; } + if (!rev && !s) { sortcount++; readysorted += n_ready; } + } + +#if defined(DEBUG_LINKED_COMMANDS) + if (link_statistics && (overlap || !(flushcount % link_statistics))) + for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + printk("%s %d.%d:%d pid %ld mb %d fc %d nr %d sec %ld ns %ld"\ + " cur %ld s:%c r:%c rev:%c in:%c ov:%c xd %d.\n", + (ihdlr ? "ihdlr" : "qcomm"), SCpnt->channel, SCpnt->target, + SCpnt->lun, SCpnt->pid, k, flushcount, n_ready, + SCpnt->request.sector, SCpnt->request.nr_sectors, cursec, + YESNO(s), YESNO(r), YESNO(rev), YESNO(input_only), + YESNO(overlap), cpp->din); + } +#endif + return overlap; +} + +static void flush_dev(Scsi_Device *dev, unsigned long cursec, unsigned int j, + unsigned int ihdlr) { + Scsi_Cmnd *SCpnt; + struct mscp *cpp; + unsigned int k, n, n_ready = 0, il[MAX_MAILBOXES]; + + for (k = 0; k < sh[j]->can_queue; k++) { + + if (HD(j)->cp_stat[k] != READY && HD(j)->cp_stat[k] != IN_USE) continue; + + cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + + if (SCpnt->device != dev) continue; + + if (HD(j)->cp_stat[k] == IN_USE) return; + + il[n_ready++] = k; + } + + if (reorder(j, cursec, ihdlr, il, n_ready)) n_ready = 1; + + for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + + if (do_dma(sh[j]->io_port, (unsigned int) cpp, SEND_CP_DMA)) { + printk("%s: %s, target %d.%d:%d, pid %ld, mbox %d, adapter"\ + " busy, will abort.\n", BN(j), (ihdlr ? "ihdlr" : "qcomm"), + SCpnt->channel, SCpnt->target, SCpnt->lun, SCpnt->pid, k); + HD(j)->cp_stat[k] = ABORTING; + continue; + } + + HD(j)->cp_stat[k] = IN_USE; + } + +} + +static inline void ihdlr(int irq, unsigned int j) { + Scsi_Cmnd *SCpnt; + unsigned int i, k, c, status, tstatus, reg; + struct mssp *dspp, *spp; + struct mscp *cpp; + + if (sh[j]->irq != irq) + panic("%s: ihdlr, irq %d, sh[j]->irq %d.\n", BN(j), irq, sh[j]->irq); + + /* Check if this board need to be serviced */ + if (!(inb(sh[j]->io_port + REG_AUX_STATUS) & IRQ_ASSERTED)) return; + + HD(j)->iocount++; + + if (do_trace) printk("%s: ihdlr, enter, irq %d, count %d.\n", BN(j), irq, + HD(j)->iocount); + + /* Check if this board is still busy */ + if (wait_on_busy(sh[j]->io_port, 20 * MAXLOOP)) { + reg = inb(sh[j]->io_port + REG_STATUS); + printk("%s: ihdlr, busy timeout error, irq %d, reg 0x%x, count %d.\n", + BN(j), irq, reg, HD(j)->iocount); + return; + } + + dspp = &HD(j)->sp[0]; + spp = &HD(j)->sp[1]; + + /* Make a local copy just before clearing the interrupt indication */ + memcpy(spp, dspp, sizeof(struct mssp)); + + /* Clear the completion flag and cp pointer on the dynamic copy of sp */ + memset(dspp, 0, sizeof(struct mssp)); + + /* Read the status register to clear the interrupt indication */ + reg = inb(sh[j]->io_port + REG_STATUS); + + /* Reject any sp with supspect data */ + if (spp->eoc == FALSE) + printk("%s: ihdlr, spp->eoc == FALSE, irq %d, reg 0x%x, count %d.\n", + BN(j), irq, reg, HD(j)->iocount); + if (spp->cpp == NULL) + printk("%s: ihdlr, spp->cpp == NULL, irq %d, reg 0x%x, count %d.\n", + BN(j), irq, reg, HD(j)->iocount); + if (spp->eoc == FALSE || spp->cpp == NULL) return; + + cpp = spp->cpp; + +#if defined(DEBUG_GENERATE_ABORTS) + if ((HD(j)->iocount > 500) && ((HD(j)->iocount % 500) < 3)) return; +#endif + + /* Find the mailbox to be serviced on this board */ + i = cpp - HD(j)->cp; + + if (cpp < HD(j)->cp || cpp >= HD(j)->cp + sh[j]->can_queue + || i >= sh[j]->can_queue) + panic("%s: ihdlr, invalid mscp bus address %p, cp0 %p.\n", BN(j), + cpp, HD(j)->cp); + + if (HD(j)->cp_stat[i] == IGNORE) { + HD(j)->cp_stat[i] = FREE; + return; + } + else if (HD(j)->cp_stat[i] == LOCKED) { + HD(j)->cp_stat[i] = FREE; + printk("%s: ihdlr, mbox %d unlocked, count %d.\n", BN(j), i, + HD(j)->iocount); + return; + } + else if (HD(j)->cp_stat[i] == FREE) { + printk("%s: ihdlr, mbox %d is free, count %d.\n", BN(j), i, + HD(j)->iocount); + return; + } + else if (HD(j)->cp_stat[i] == IN_RESET) + printk("%s: ihdlr, mbox %d is in reset.\n", BN(j), i); + else if (HD(j)->cp_stat[i] != IN_USE) + panic("%s: ihdlr, mbox %d, invalid cp_stat: %d.\n", + BN(j), i, HD(j)->cp_stat[i]); + + HD(j)->cp_stat[i] = FREE; + SCpnt = cpp->SCpnt; + + if (SCpnt == NULL) panic("%s: ihdlr, mbox %d, SCpnt == NULL.\n", BN(j), i); + + if (SCpnt->host_scribble == NULL) + panic("%s: ihdlr, mbox %d, pid %ld, SCpnt %p garbled.\n", BN(j), i, + SCpnt->pid, SCpnt); + + if (*(unsigned int *)SCpnt->host_scribble != i) + panic("%s: ihdlr, mbox %d, pid %ld, index mismatch %d.\n", + BN(j), i, SCpnt->pid, *(unsigned int *)SCpnt->host_scribble); + + if (linked_comm && SCpnt->device->queue_depth > 2 + && TLDEV(SCpnt->device->type)) + flush_dev(SCpnt->device, SCpnt->request.sector, j, TRUE); + + tstatus = status_byte(spp->target_status); + +#if defined(DEBUG_GENERATE_ERRORS) + if ((HD(j)->iocount > 500) && ((HD(j)->iocount % 200) < 2)) + spp->adapter_status = 0x01; +#endif + + switch (spp->adapter_status) { + case ASOK: /* status OK */ + + /* Forces a reset if a disk drive keeps returning BUSY */ + if (tstatus == BUSY && SCpnt->device->type != TYPE_TAPE) + status = DID_ERROR << 16; + + /* If there was a bus reset, redo operation on each target */ + else if (tstatus != GOOD && SCpnt->device->type == TYPE_DISK + && HD(j)->target_redo[SCpnt->target][SCpnt->channel]) + status = DID_BUS_BUSY << 16; + + /* Works around a flaw in scsi.c */ + else if (tstatus == CHECK_CONDITION + && SCpnt->device->type == TYPE_DISK + && (SCpnt->sense_buffer[2] & 0xf) == RECOVERED_ERROR) + status = DID_BUS_BUSY << 16; + + else + status = DID_OK << 16; + + if (tstatus == GOOD) + HD(j)->target_redo[SCpnt->target][SCpnt->channel] = FALSE; + + if (spp->target_status && SCpnt->device->type == TYPE_DISK) + printk("%s: ihdlr, target %d.%d:%d, pid %ld, "\ + "target_status 0x%x, sense key 0x%x.\n", BN(j), + SCpnt->channel, SCpnt->target, SCpnt->lun, + SCpnt->pid, spp->target_status, + SCpnt->sense_buffer[2]); + + HD(j)->target_to[SCpnt->target][SCpnt->channel] = 0; + + if (HD(j)->last_retried_pid == SCpnt->pid) HD(j)->retries = 0; + + break; + case ASST: /* Selection Time Out */ + case 0x02: /* Command Time Out */ + + if (HD(j)->target_to[SCpnt->target][SCpnt->channel] > 1) + status = DID_ERROR << 16; + else { + status = DID_TIME_OUT << 16; + HD(j)->target_to[SCpnt->target][SCpnt->channel]++; + } + + break; + + /* Perform a limited number of internal retries */ + case 0x03: /* SCSI Bus Reset Received */ + case 0x04: /* Initial Controller Power-up */ + + for (c = 0; c <= sh[j]->max_channel; c++) + for (k = 0; k < sh[j]->max_id; k++) + HD(j)->target_redo[k][c] = TRUE; + + if (SCpnt->device->type != TYPE_TAPE + && HD(j)->retries < MAX_INTERNAL_RETRIES) { + +#if defined(DID_SOFT_ERROR) + status = DID_SOFT_ERROR << 16; +#else + status = DID_BUS_BUSY << 16; +#endif + HD(j)->retries++; + HD(j)->last_retried_pid = SCpnt->pid; + } + else + status = DID_ERROR << 16; + + break; + case 0x05: /* Unexpected Bus Phase */ + case 0x06: /* Unexpected Bus Free */ + case 0x07: /* Bus Parity Error */ + case 0x08: /* SCSI Hung */ + case 0x09: /* Unexpected Message Reject */ + case 0x0a: /* SCSI Bus Reset Stuck */ + case 0x0b: /* Auto Request-Sense Failed */ + case 0x0c: /* Controller Ram Parity Error */ + default: + status = DID_ERROR << 16; + break; + } + + SCpnt->result = status | spp->target_status; + +#if defined(DEBUG_INTERRUPT) + if (SCpnt->result || do_trace) +#else + if ((spp->adapter_status != ASOK && HD(j)->iocount > 1000) || + (spp->adapter_status != ASOK && + spp->adapter_status != ASST && HD(j)->iocount <= 1000) || + do_trace || msg_byte(spp->target_status)) +#endif + printk("%s: ihdlr, mbox %2d, err 0x%x:%x,"\ + " target %d.%d:%d, pid %ld, reg 0x%x, count %d.\n", + BN(j), i, spp->adapter_status, spp->target_status, + SCpnt->channel, SCpnt->target, SCpnt->lun, SCpnt->pid, + reg, HD(j)->iocount); + + /* Set the command state to inactive */ + SCpnt->host_scribble = NULL; + + SCpnt->scsi_done(SCpnt); + + if (do_trace) printk("%s: ihdlr, exit, irq %d, count %d.\n", BN(j), irq, + HD(j)->iocount); + + return; +} + +static void do_interrupt_handler(int irq, void *shap, struct pt_regs *regs) { + unsigned int j; + IRQ_FLAGS + SPIN_FLAGS + + /* Check if the interrupt must be processed by this handler */ + if ((j = (unsigned int)((char *)shap - sha)) >= num_boards) return; + + SPIN_LOCK_SAVE + IRQ_LOCK_SAVE + ihdlr(irq, j); + IRQ_UNLOCK_RESTORE + SPIN_UNLOCK_RESTORE +} + +int eata2x_release(struct Scsi_Host *shpnt) { + unsigned int i, j; + IRQ_FLAGS + + IRQ_LOCK_SAVE + + for (j = 0; sh[j] != NULL && sh[j] != shpnt; j++); + + if (sh[j] == NULL) panic("%s: release, invalid Scsi_Host pointer.\n", + driver_name); + + for (i = 0; i < sh[j]->can_queue; i++) + if ((&HD(j)->cp[i])->sglist) kfree((&HD(j)->cp[i])->sglist); + + free_irq(sh[j]->irq, &sha[j]); + + if (sh[j]->dma_channel != NO_DMA) free_dma(sh[j]->dma_channel); + + release_region(sh[j]->io_port, sh[j]->n_io_port); + scsi_unregister(sh[j]); + IRQ_UNLOCK_RESTORE + return FALSE; +} + +#if defined(MODULE) +Scsi_Host_Template driver_template = EATA; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/eata.h b/linux/src/drivers/scsi/eata.h new file mode 100644 index 00000000..f1641f42 --- /dev/null +++ b/linux/src/drivers/scsi/eata.h @@ -0,0 +1,60 @@ +/* + * eata.h - used by the low-level driver for EATA/DMA SCSI host adapters. + */ +#ifndef _EATA_H +#define _EATA_H + +#include <scsi/scsicam.h> +#include <linux/version.h> + +int eata2x_detect(Scsi_Host_Template *); +int eata2x_release(struct Scsi_Host *); +int eata2x_queuecommand(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int eata2x_abort(Scsi_Cmnd *); +int eata2x_old_abort(Scsi_Cmnd *); +int eata2x_reset(Scsi_Cmnd *); +int eata2x_old_reset(Scsi_Cmnd *, unsigned int); +int eata2x_biosparam(Disk *, kdev_t, int *); + +#define EATA_VERSION "4.33.00" + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + +#define EATA { \ + name: "EATA/DMA 2.0x rev. " EATA_VERSION " ", \ + detect: eata2x_detect, \ + release: eata2x_release, \ + queuecommand: eata2x_queuecommand, \ + abort: eata2x_old_abort, \ + reset: eata2x_old_reset, \ + eh_abort_handler: eata2x_abort, \ + eh_device_reset_handler: NULL, \ + eh_bus_reset_handler: NULL, \ + eh_host_reset_handler: eata2x_reset, \ + bios_param: eata2x_biosparam, \ + this_id: 7, \ + unchecked_isa_dma: 1, \ + use_clustering: ENABLE_CLUSTERING, \ + use_new_eh_code: 1 /* Enable new error code */ \ + } + +#else /* Use old scsi code */ + +#define EATA { \ + name: "EATA/DMA 2.0x rev. " EATA_VERSION " ", \ + detect: eata2x_detect, \ + release: eata2x_release, \ + queuecommand: eata2x_queuecommand, \ + abort: eata2x_old_abort, \ + reset: eata2x_old_reset, \ + bios_param: eata2x_biosparam, \ + this_id: 7, \ + unchecked_isa_dma: 1, \ + use_clustering: ENABLE_CLUSTERING \ + } + +#endif + +#endif diff --git a/linux/src/drivers/scsi/eata_dma.c b/linux/src/drivers/scsi/eata_dma.c new file mode 100644 index 00000000..2b7da3db --- /dev/null +++ b/linux/src/drivers/scsi/eata_dma.c @@ -0,0 +1,1603 @@ +/************************************************************ + * * + * Linux EATA SCSI driver * + * * + * based on the CAM document CAM/89-004 rev. 2.0c, * + * DPT's driver kit, some internal documents and source, * + * and several other Linux scsi drivers and kernel docs. * + * * + * The driver currently: * + * -supports all ISA based EATA-DMA boards * + * like PM2011, PM2021, PM2041, PM3021 * + * -supports all EISA based EATA-DMA boards * + * like PM2012B, PM2022, PM2122, PM2322, PM2042, * + * PM3122, PM3222, PM3332 * + * -supports all PCI based EATA-DMA boards * + * like PM2024, PM2124, PM2044, PM2144, PM3224, * + * PM3334 * + * -supports the Wide, Ultra Wide and Differential * + * versions of the boards * + * -supports multiple HBAs with & without IRQ sharing * + * -supports all SCSI channels on multi channel boards * + * -supports ix86 and MIPS, untested on ALPHA * + * -needs identical IDs on all channels of a HBA * + * -can be loaded as module * + * -displays statistical and hardware information * + * in /proc/scsi/eata_dma * + * -provides rudimentary latency measurement * + * possibilities via /proc/scsi/eata_dma/<hostnum> * + * * + * (c)1993-96 Michael Neuffer * + * mike@i-Connect.Net * + * neuffer@mail.uni-mainz.de * + * * + * This program is free software; you can redistribute it * + * and/or modify it under the terms of the GNU General * + * Public License as published by the Free Software * + * Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be * + * useful, but WITHOUT ANY WARRANTY; without even the * + * implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License * + * for more details. * + * * + * You should have received a copy of the GNU General * + * Public License along with this kernel; if not, write to * + * the Free Software Foundation, Inc., 675 Mass Ave, * + * Cambridge, MA 02139, USA. * + * * + * I have to thank DPT for their excellent support. I took * + * me almost a year and a stopover at their HQ, on my first * + * trip to the USA, to get it, but since then they've been * + * very helpful and tried to give me all the infos and * + * support I need. * + * * + * Thanks also to Simon Shapiro, Greg Hosler and Mike * + * Jagdis who did a lot of testing and found quite a number * + * of bugs during the development. * + ************************************************************ + * last change: 96/10/21 OS: Linux 2.0.23 * + ************************************************************/ + +/* Look in eata_dma.h for configuration and revision information */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/in.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <asm/byteorder.h> +#include <asm/types.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/pgtable.h> +#ifdef __mips__ +#include <asm/cachectl.h> +#endif +#include <linux/blk.h> +#include "scsi.h" +#include "sd.h" +#include "hosts.h" +#include "eata_dma.h" +#include "eata_dma_proc.h" + +#include <linux/stat.h> +#include <linux/config.h> /* for CONFIG_PCI */ + +struct proc_dir_entry proc_scsi_eata_dma = { + PROC_SCSI_EATA, 8, "eata_dma", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +static u32 ISAbases[] = +{0x1F0, 0x170, 0x330, 0x230}; +static unchar EISAbases[] = +{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; +static uint registered_HBAs = 0; +static struct Scsi_Host *last_HBA = NULL; +static struct Scsi_Host *first_HBA = NULL; +static unchar reg_IRQ[] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static unchar reg_IRQL[] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static struct eata_sp *status = 0; /* Statuspacket array */ +static void *dma_scratch = 0; + +static struct eata_register *fake_int_base; +static int fake_int_result; +static int fake_int_happened; + +static ulong int_counter = 0; +static ulong queue_counter = 0; + +void eata_scsi_done (Scsi_Cmnd * scmd) +{ + scmd->request.rq_status = RQ_SCSI_DONE; + + if (scmd->request.sem != NULL) + up(scmd->request.sem); + + return; +} + +void eata_fake_int_handler(s32 irq, void *dev_id, struct pt_regs * regs) +{ + fake_int_result = inb((ulong)fake_int_base + HA_RSTATUS); + fake_int_happened = TRUE; + DBG(DBG_INTR3, printk("eata_fake_int_handler called irq%d base %p" + " res %#x\n", irq, fake_int_base, fake_int_result)); + return; +} + +#include "eata_dma_proc.c" + +#ifdef MODULE +int eata_release(struct Scsi_Host *sh) +{ + uint i; + if (sh->irq && reg_IRQ[sh->irq] == 1) free_irq(sh->irq, NULL); + else reg_IRQ[sh->irq]--; + + scsi_init_free((void *)status, 512); + scsi_init_free((void *)dma_scratch - 4, 1024); + for (i = 0; i < sh->can_queue; i++){ /* Free all SG arrays */ + if(SD(sh)->ccb[i].sg_list != NULL) + scsi_init_free((void *) SD(sh)->ccb[i].sg_list, + sh->sg_tablesize * sizeof(struct eata_sg_list)); + } + + if (SD(sh)->channel == 0) { + if (sh->dma_channel != BUSMASTER) free_dma(sh->dma_channel); + if (sh->io_port && sh->n_io_port) + release_region(sh->io_port, sh->n_io_port); + } + return(TRUE); +} +#endif + + +inline void eata_latency_in(struct eata_ccb *cp, hostdata *hd) +{ + uint time; + time = jiffies - cp->timestamp; + if(hd->all_lat[1] > time) + hd->all_lat[1] = time; + if(hd->all_lat[2] < time) + hd->all_lat[2] = time; + hd->all_lat[3] += time; + hd->all_lat[0]++; + if((cp->rw_latency) == WRITE) { /* was WRITE */ + if(hd->writes_lat[cp->sizeindex][1] > time) + hd->writes_lat[cp->sizeindex][1] = time; + if(hd->writes_lat[cp->sizeindex][2] < time) + hd->writes_lat[cp->sizeindex][2] = time; + hd->writes_lat[cp->sizeindex][3] += time; + hd->writes_lat[cp->sizeindex][0]++; + } else if((cp->rw_latency) == READ) { + if(hd->reads_lat[cp->sizeindex][1] > time) + hd->reads_lat[cp->sizeindex][1] = time; + if(hd->reads_lat[cp->sizeindex][2] < time) + hd->reads_lat[cp->sizeindex][2] = time; + hd->reads_lat[cp->sizeindex][3] += time; + hd->reads_lat[cp->sizeindex][0]++; + } +} + +inline void eata_latency_out(struct eata_ccb *cp, Scsi_Cmnd *cmd) +{ + int x, z; + short *sho; + long *lon; + x = 0; /* just to keep GCC quiet */ + cp->timestamp = jiffies; /* For latency measurements */ + switch(cmd->cmnd[0]) { + case WRITE_6: + x = cmd->cmnd[4]/2; + cp->rw_latency = WRITE; + break; + case READ_6: + x = cmd->cmnd[4]/2; + cp->rw_latency = READ; + break; + case WRITE_10: + sho = (short *) &cmd->cmnd[7]; + x = ntohs(*sho)/2; + cp->rw_latency = WRITE; + break; + case READ_10: + sho = (short *) &cmd->cmnd[7]; + x = ntohs(*sho)/2; + cp->rw_latency = READ; + break; + case WRITE_12: + lon = (long *) &cmd->cmnd[6]; + x = ntohl(*lon)/2; + cp->rw_latency = WRITE; + break; + case READ_12: + lon = (long *) &cmd->cmnd[6]; + x = ntohl(*lon)/2; + cp->rw_latency = READ; + break; + default: + cp->rw_latency = OTHER; + break; + } + if (cmd->cmnd[0] == WRITE_6 || cmd->cmnd[0] == WRITE_10 || + cmd->cmnd[0] == WRITE_12 || cmd->cmnd[0] == READ_6 || + cmd->cmnd[0] == READ_10 || cmd->cmnd[0] == READ_12) { + for(z = 0; (x > (1 << z)) && (z <= 11); z++) + /* nothing */; + cp->sizeindex = z; + } +} + + +void eata_int_handler(int irq, void *dev_id, struct pt_regs * regs) +{ + uint i, result = 0; + uint hba_stat, scsi_stat, eata_stat; + Scsi_Cmnd *cmd; + struct eata_ccb *ccb; + struct eata_sp *sp; + uint base; + uint x; + struct Scsi_Host *sh; + + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if (sh->irq != irq) + continue; + + while(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + + int_counter++; + + sp = &SD(sh)->sp; +#ifdef __mips__ + sys_cacheflush(sp, sizeof(struct eata_sp), 2); +#endif + ccb = sp->ccb; + + if(ccb == NULL) { + eata_stat = inb((uint)sh->base + HA_RSTATUS); + printk("eata_dma: int_handler, Spurious IRQ %d " + "received. CCB pointer not set.\n", irq); + break; + } + + cmd = ccb->cmd; + base = (uint) cmd->host->base; + hba_stat = sp->hba_stat; + + scsi_stat = (sp->scsi_stat >> 1) & 0x1f; + + if (sp->EOC == FALSE) { + eata_stat = inb(base + HA_RSTATUS); + printk(KERN_WARNING "eata_dma: int_handler, board: %x cmd %lx " + "returned unfinished.\n" + "EATA: %x HBA: %x SCSI: %x spadr %lx spadrirq %lx, " + "irq%d\n", base, (long)ccb, eata_stat, hba_stat, + scsi_stat,(long)&status, (long)&status[irq], irq); + cmd->result = DID_ERROR << 16; + ccb->status = FREE; + cmd->scsi_done(cmd); + break; + } + + sp->EOC = FALSE; /* Clean out this flag */ + + if (ccb->status == LOCKED || ccb->status == RESET) { + printk("eata_dma: int_handler, reseted command pid %ld returned" + "\n", cmd->pid); + DBG(DBG_INTR && DBG_DELAY, DELAY(1)); + } + + eata_stat = inb(base + HA_RSTATUS); + DBG(DBG_INTR, printk("IRQ %d received, base %#.4x, pid %ld, " + "target: %x, lun: %x, ea_s: %#.2x, hba_s: " + "%#.2x \n", irq, base, cmd->pid, cmd->target, + cmd->lun, eata_stat, hba_stat)); + + switch (hba_stat) { + case HA_NO_ERROR: /* NO Error */ + if(HD(cmd)->do_latency == TRUE && ccb->timestamp) + eata_latency_in(ccb, HD(cmd)); + result = DID_OK << 16; + break; + case HA_ERR_SEL_TO: /* Selection Timeout */ + case HA_ERR_CMD_TO: /* Command Timeout */ + result = DID_TIME_OUT << 16; + break; + case HA_BUS_RESET: /* SCSI Bus Reset Received */ + result = DID_RESET << 16; + DBG(DBG_STATUS, printk(KERN_WARNING "scsi%d: BUS RESET " + "received on cmd %ld\n", + HD(cmd)->HBA_number, cmd->pid)); + break; + case HA_INIT_POWERUP: /* Initial Controller Power-up */ + if (cmd->device->type != TYPE_TAPE) + result = DID_BUS_BUSY << 16; + else + result = DID_ERROR << 16; + + for (i = 0; i < MAXTARGET; i++) + DBG(DBG_STATUS, printk(KERN_DEBUG "scsi%d: cmd pid %ld " + "returned with INIT_POWERUP\n", + HD(cmd)->HBA_number, cmd->pid)); + break; + case HA_CP_ABORT_NA: + case HA_CP_ABORTED: + result = DID_ABORT << 16; + DBG(DBG_STATUS, printk(KERN_WARNING "scsi%d: aborted cmd " + "returned\n", HD(cmd)->HBA_number)); + break; + case HA_CP_RESET_NA: + case HA_CP_RESET: + HD(cmd)->resetlevel[cmd->channel] = 0; + result = DID_RESET << 16; + DBG(DBG_STATUS, printk(KERN_WARNING "scsi%d: reseted cmd " + "pid %ldreturned\n", + HD(cmd)->HBA_number, cmd->pid)); + case HA_SCSI_HUNG: /* SCSI Hung */ + printk(KERN_ERR "scsi%d: SCSI hung\n", HD(cmd)->HBA_number); + result = DID_ERROR << 16; + break; + case HA_RSENSE_FAIL: /* Auto Request-Sense Failed */ + DBG(DBG_STATUS, printk(KERN_ERR "scsi%d: Auto Request Sense " + "Failed\n", HD(cmd)->HBA_number)); + result = DID_ERROR << 16; + break; + case HA_UNX_BUSPHASE: /* Unexpected Bus Phase */ + case HA_UNX_BUS_FREE: /* Unexpected Bus Free */ + case HA_BUS_PARITY: /* Bus Parity Error */ + case HA_UNX_MSGRJCT: /* Unexpected Message Reject */ + case HA_RESET_STUCK: /* SCSI Bus Reset Stuck */ + case HA_PARITY_ERR: /* Controller Ram Parity */ + default: + result = DID_ERROR << 16; + break; + } + cmd->result = result | (scsi_stat << 1); + +#if DBG_INTR2 + if (scsi_stat || result || hba_stat || eata_stat != 0x50 + || cmd->scsi_done == NULL || cmd->device->id == 7) + printk("HBA: %d, channel %d, id: %d, lun %d, pid %ld:\n" + "eata_stat %#x, hba_stat %#.2x, scsi_stat %#.2x, " + "sense_key: %#x, result: %#.8x\n", x, + cmd->device->channel, cmd->device->id, cmd->device->lun, + cmd->pid, eata_stat, hba_stat, scsi_stat, + cmd->sense_buffer[2] & 0xf, cmd->result); + DBG(DBG_INTR&&DBG_DELAY,DELAY(1)); +#endif + + ccb->status = FREE; /* now we can release the slot */ + cmd->scsi_done(cmd); + } + } + + return; +} + +inline int eata_send_command(u32 addr, u32 base, u8 command) +{ + long loop = R_LIMIT; + + while (inb(base + HA_RAUXSTAT) & HA_ABUSY) + if (--loop == 0) + return(FALSE); + + if(addr != (u32) NULL) + addr = virt_to_bus((void *)addr); + + /* + * This is overkill.....but the MIPSen seem to need this + * and it will be optimized away for i86 and ALPHA machines. + */ + flush_cache_all(); + + /* And now the address in nice little byte chunks */ +#ifdef __LITTLE_ENDIAN + outb(addr, base + HA_WDMAADDR); + outb(addr >> 8, base + HA_WDMAADDR + 1); + outb(addr >> 16, base + HA_WDMAADDR + 2); + outb(addr >> 24, base + HA_WDMAADDR + 3); +#else + outb(addr >> 24, base + HA_WDMAADDR); + outb(addr >> 16, base + HA_WDMAADDR + 1); + outb(addr >> 8, base + HA_WDMAADDR + 2); + outb(addr, base + HA_WDMAADDR + 3); +#endif + outb(command, base + HA_WCOMMAND); + return(TRUE); +} + +inline int eata_send_immediate(u32 base, u32 addr, u8 ifc, u8 code, u8 code2) +{ + if(addr != (u32) NULL) + addr = virt_to_bus((void *)addr); + + /* + * This is overkill.....but the MIPSen seem to need this + * and it will be optimized away for i86 and ALPHA machines. + */ + flush_cache_all(); + + outb(0x0, base + HA_WDMAADDR - 1); + if(addr){ +#ifdef __LITTLE_ENDIAN + outb(addr, base + HA_WDMAADDR); + outb(addr >> 8, base + HA_WDMAADDR + 1); + outb(addr >> 16, base + HA_WDMAADDR + 2); + outb(addr >> 24, base + HA_WDMAADDR + 3); +#else + outb(addr >> 24, base + HA_WDMAADDR); + outb(addr >> 16, base + HA_WDMAADDR + 1); + outb(addr >> 8, base + HA_WDMAADDR + 2); + outb(addr, base + HA_WDMAADDR + 3); +#endif + } else { + outb(0x0, base + HA_WDMAADDR); + outb(0x0, base + HA_WDMAADDR + 1); + outb(code2, base + HA_WCODE2); + outb(code, base + HA_WCODE); + } + + outb(ifc, base + HA_WIFC); + outb(EATA_CMD_IMMEDIATE, base + HA_WCOMMAND); + return(TRUE); +} + +int eata_queue(Scsi_Cmnd * cmd, void (* done) (Scsi_Cmnd *)) +{ + unsigned int i, x, y; + ulong flags; + hostdata *hd; + struct Scsi_Host *sh; + struct eata_ccb *ccb; + struct scatterlist *sl; + + + save_flags(flags); + cli(); + +#if 0 + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + printk("eata_dma: scsi%d interrupt pending in eata_queue.\n" + " Calling interrupt handler.\n", sh->host_no); + eata_int_handler(sh->irq, 0, 0); + } + } +#endif + + queue_counter++; + + hd = HD(cmd); + sh = cmd->host; + + if (cmd->cmnd[0] == REQUEST_SENSE && cmd->sense_buffer[0] != 0) { + DBG(DBG_REQSENSE, printk(KERN_DEBUG "Tried to REQUEST SENSE\n")); + cmd->result = DID_OK << 16; + done(cmd); + + return(0); + } + + /* check for free slot */ + for (y = hd->last_ccb + 1, x = 0; x < sh->can_queue; x++, y++) { + if (y >= sh->can_queue) + y = 0; + if (hd->ccb[y].status == FREE) + break; + } + + hd->last_ccb = y; + + if (x >= sh->can_queue) { + cmd->result = DID_BUS_BUSY << 16; + DBG(DBG_QUEUE && DBG_ABNORM, + printk(KERN_CRIT "eata_queue pid %ld, HBA QUEUE FULL..., " + "returning DID_BUS_BUSY\n", cmd->pid)); + done(cmd); + restore_flags(flags); + return(0); + } + ccb = &hd->ccb[y]; + + memset(ccb, 0, sizeof(struct eata_ccb) - sizeof(struct eata_sg_list *)); + + ccb->status = USED; /* claim free slot */ + + restore_flags(flags); + + DBG(DBG_QUEUE, printk("eata_queue pid %ld, target: %x, lun: %x, y %d\n", + cmd->pid, cmd->target, cmd->lun, y)); + DBG(DBG_QUEUE && DBG_DELAY, DELAY(1)); + + if(hd->do_latency == TRUE) + eata_latency_out(ccb, cmd); + + cmd->scsi_done = (void *)done; + + switch (cmd->cmnd[0]) { + case CHANGE_DEFINITION: case COMPARE: case COPY: + case COPY_VERIFY: case LOG_SELECT: case MODE_SELECT: + case MODE_SELECT_10: case SEND_DIAGNOSTIC: case WRITE_BUFFER: + case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE: + case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW: + case WRITE_6: case WRITE_10: case WRITE_VERIFY: + case UPDATE_BLOCK: case WRITE_LONG: case WRITE_SAME: + case SEARCH_HIGH_12: case SEARCH_EQUAL_12: case SEARCH_LOW_12: + case WRITE_12: case WRITE_VERIFY_12: case SET_WINDOW: + case MEDIUM_SCAN: case SEND_VOLUME_TAG: + case 0xea: /* alternate number for WRITE LONG */ + ccb->DataOut = TRUE; /* Output mode */ + break; + case TEST_UNIT_READY: + default: + ccb->DataIn = TRUE; /* Input mode */ + } + + /* FIXME: This will have to be changed once the midlevel driver + * allows different HBA IDs on every channel. + */ + if (cmd->target == sh->this_id) + ccb->Interpret = TRUE; /* Interpret command */ + + if (cmd->use_sg) { + ccb->scatter = TRUE; /* SG mode */ + if (ccb->sg_list == NULL) { + ccb->sg_list = kmalloc(sh->sg_tablesize * sizeof(struct eata_sg_list), + GFP_ATOMIC | GFP_DMA); + } + if (ccb->sg_list == NULL) + panic("eata_dma: Run out of DMA memory for SG lists !\n"); + ccb->cp_dataDMA = htonl(virt_to_bus(ccb->sg_list)); + + ccb->cp_datalen = htonl(cmd->use_sg * sizeof(struct eata_sg_list)); + sl=(struct scatterlist *)cmd->request_buffer; + for(i = 0; i < cmd->use_sg; i++, sl++){ + ccb->sg_list[i].data = htonl(virt_to_bus(sl->address)); + ccb->sg_list[i].len = htonl((u32) sl->length); + } + } else { + ccb->scatter = FALSE; + ccb->cp_datalen = htonl(cmd->request_bufflen); + ccb->cp_dataDMA = htonl(virt_to_bus(cmd->request_buffer)); + } + + ccb->Auto_Req_Sen = TRUE; + ccb->cp_reqDMA = htonl(virt_to_bus(cmd->sense_buffer)); + ccb->reqlen = sizeof(cmd->sense_buffer); + + ccb->cp_id = cmd->target; + ccb->cp_channel = cmd->channel; + ccb->cp_lun = cmd->lun; + ccb->cp_dispri = TRUE; + ccb->cp_identify = TRUE; + memcpy(ccb->cp_cdb, cmd->cmnd, cmd->cmd_len); + + ccb->cp_statDMA = htonl(virt_to_bus(&(hd->sp))); + + ccb->cp_viraddr = ccb; /* This will be passed thru, so we don't need to + * convert it */ + ccb->cmd = cmd; + cmd->host_scribble = (char *)&hd->ccb[y]; + + if(eata_send_command((u32) ccb, (u32) sh->base, EATA_CMD_DMA_SEND_CP) == FALSE) { + cmd->result = DID_BUS_BUSY << 16; + DBG(DBG_QUEUE && DBG_ABNORM, + printk("eata_queue target %d, pid %ld, HBA busy, " + "returning DID_BUS_BUSY\n",cmd->target, cmd->pid)); + ccb->status = FREE; + done(cmd); + return(0); + } + DBG(DBG_QUEUE, printk("Queued base %#.4x pid: %ld target: %x lun: %x " + "slot %d irq %d\n", (s32)sh->base, cmd->pid, + cmd->target, cmd->lun, y, sh->irq)); + DBG(DBG_QUEUE && DBG_DELAY, DELAY(1)); + + return(0); +} + + +int eata_abort(Scsi_Cmnd * cmd) +{ + ulong loop = HZ / 2; + ulong flags; + int x; + struct Scsi_Host *sh; + + save_flags(flags); + cli(); + + DBG(DBG_ABNORM, printk("eata_abort called pid: %ld target: %x lun: %x" + " reason %x\n", cmd->pid, cmd->target, cmd->lun, + cmd->abort_reason)); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + /* Some interrupt controllers seem to loose interrupts */ + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + printk("eata_dma: scsi%d interrupt pending in eata_abort.\n" + " Calling interrupt handler.\n", sh->host_no); + eata_int_handler(sh->irq, 0, 0); + } + } + + while (inb((u32)(cmd->host->base) + HA_RAUXSTAT) & HA_ABUSY) { + if (--loop == 0) { + printk("eata_dma: abort, timeout error.\n"); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + restore_flags(flags); + return (SCSI_ABORT_ERROR); + } + } + if (CD(cmd)->status == RESET) { + printk("eata_dma: abort, command reset error.\n"); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + restore_flags(flags); + return (SCSI_ABORT_ERROR); + } + if (CD(cmd)->status == LOCKED) { + DBG(DBG_ABNORM, printk("eata_dma: abort, queue slot locked.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + restore_flags(flags); + return (SCSI_ABORT_NOT_RUNNING); + } + if (CD(cmd)->status == USED) { + DBG(DBG_ABNORM, printk("Returning: SCSI_ABORT_BUSY\n")); + restore_flags(flags); + return (SCSI_ABORT_BUSY); /* SNOOZE */ + } + if (CD(cmd)->status == FREE) { + DBG(DBG_ABNORM, printk("Returning: SCSI_ABORT_NOT_RUNNING\n")); + restore_flags(flags); + return (SCSI_ABORT_NOT_RUNNING); + } + restore_flags(flags); + panic("eata_dma: abort: invalid slot status\n"); +} + +int eata_reset(Scsi_Cmnd * cmd, unsigned int resetflags) +{ + uint x; + ulong loop = loops_per_sec / 3; + ulong flags; + unchar success = FALSE; + Scsi_Cmnd *sp; + struct Scsi_Host *sh; + + save_flags(flags); + cli(); + + DBG(DBG_ABNORM, printk("eata_reset called pid:%ld target: %x lun: %x" + " reason %x\n", cmd->pid, cmd->target, cmd->lun, + cmd->abort_reason)); + + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->next) { + if(inb((uint)sh->base + HA_RAUXSTAT) & HA_AIRQ) { + printk("eata_dma: scsi%d interrupt pending in eata_reset.\n" + " Calling interrupt handler.\n", sh->host_no); + eata_int_handler(sh->irq, 0, 0); + } + } + + if (HD(cmd)->state == RESET) { + printk("eata_reset: exit, already in reset.\n"); + restore_flags(flags); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_ERROR); + } + + while (inb((u32)(cmd->host->base) + HA_RAUXSTAT) & HA_ABUSY) + if (--loop == 0) { + printk("eata_reset: exit, timeout error.\n"); + restore_flags(flags); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_ERROR); + } + + for (x = 0; x < cmd->host->can_queue; x++) { + if (HD(cmd)->ccb[x].status == FREE) + continue; + + if (HD(cmd)->ccb[x].status == LOCKED) { + HD(cmd)->ccb[x].status = FREE; + printk("eata_reset: locked slot %d forced free.\n", x); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + continue; + } + + + sp = HD(cmd)->ccb[x].cmd; + HD(cmd)->ccb[x].status = RESET; + + if (sp == NULL) + panic("eata_reset: slot %d, sp==NULL.\n", x); + + printk("eata_reset: slot %d in reset, pid %ld.\n", x, sp->pid); + + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + if (sp == cmd) + success = TRUE; + } + + /* hard reset the HBA */ + inb((u32) (cmd->host->base) + HA_RSTATUS); /* This might cause trouble */ + eata_send_command(0, (u32) cmd->host->base, EATA_CMD_RESET); + + HD(cmd)->state = RESET; + + DBG(DBG_ABNORM, printk("eata_reset: board reset done, enabling " + "interrupts.\n")); + + DELAY(2); /* In theorie we should get interrupts and set free all + * used queueslots */ + + DBG(DBG_ABNORM, printk("eata_reset: interrupts disabled again.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + for (x = 0; x < cmd->host->can_queue; x++) { + + /* Skip slots already set free by interrupt and those that + * are still LOCKED from the last reset */ + if (HD(cmd)->ccb[x].status != RESET) + continue; + + sp = HD(cmd)->ccb[x].cmd; + sp->result = DID_RESET << 16; + + /* This mailbox is still waiting for its interrupt */ + HD(cmd)->ccb[x].status = LOCKED; + + printk("eata_reset: slot %d locked, DID_RESET, pid %ld done.\n", + x, sp->pid); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + sp->scsi_done(sp); + } + + HD(cmd)->state = FALSE; + restore_flags(flags); + + if (success) { + DBG(DBG_ABNORM, printk("eata_reset: exit, pending.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_PENDING); + } else { + DBG(DBG_ABNORM, printk("eata_reset: exit, wakeup.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_PUNT); + } +} + +/* Here we try to determine the optimum queue depth for + * each attached device. + * + * At the moment the algorithm is rather simple + */ +static void eata_select_queue_depths(struct Scsi_Host *host, + Scsi_Device *devicelist) +{ + Scsi_Device *device; + int devcount = 0; + int factor = 0; + +#if CRIPPLE_QUEUE + for(device = devicelist; device != NULL; device = device->next) { + if(device->host == host) + device->queue_depth = 2; + } +#else + /* First we do a sample run go find out what we have */ + for(device = devicelist; device != NULL; device = device->next) { + if (device->host == host) { + devcount++; + switch(device->type) { + case TYPE_DISK: + case TYPE_MOD: + factor += TYPE_DISK_QUEUE; + break; + case TYPE_TAPE: + factor += TYPE_TAPE_QUEUE; + break; + case TYPE_WORM: + case TYPE_ROM: + factor += TYPE_ROM_QUEUE; + break; + case TYPE_PROCESSOR: + case TYPE_SCANNER: + default: + factor += TYPE_OTHER_QUEUE; + break; + } + } + } + + DBG(DBG_REGISTER, printk(KERN_DEBUG "scsi%d: needed queueslots %d\n", + host->host_no, factor)); + + if(factor == 0) /* We don't want to get a DIV BY ZERO error */ + factor = 1; + + factor = (SD(host)->queuesize * 10) / factor; + + DBG(DBG_REGISTER, printk(KERN_DEBUG "scsi%d: using factor %dE-1\n", + host->host_no, factor)); + + /* Now that have the factor we can set the individual queuesizes */ + for(device = devicelist; device != NULL; device = device->next) { + if(device->host == host) { + if(SD(device->host)->bustype != IS_ISA){ + switch(device->type) { + case TYPE_DISK: + case TYPE_MOD: + device->queue_depth = (TYPE_DISK_QUEUE * factor) / 10; + break; + case TYPE_TAPE: + device->queue_depth = (TYPE_TAPE_QUEUE * factor) / 10; + break; + case TYPE_WORM: + case TYPE_ROM: + device->queue_depth = (TYPE_ROM_QUEUE * factor) / 10; + break; + case TYPE_PROCESSOR: + case TYPE_SCANNER: + default: + device->queue_depth = (TYPE_OTHER_QUEUE * factor) / 10; + break; + } + } else /* ISA forces us to limit the queue depth because of the + * bounce buffer memory overhead. I know this is cruel */ + device->queue_depth = 2; + + /* + * It showed that we need to set an upper limit of commands + * we can allow to queue for a single device on the bus. + * If we get above that limit, the broken midlevel SCSI code + * will produce bogus timeouts and aborts en masse. :-( + */ + if(device->queue_depth > UPPER_DEVICE_QUEUE_LIMIT) + device->queue_depth = UPPER_DEVICE_QUEUE_LIMIT; + if(device->queue_depth == 0) + device->queue_depth = 1; + + printk(KERN_INFO "scsi%d: queue depth for target %d on channel %d " + "set to %d\n", host->host_no, device->id, device->channel, + device->queue_depth); + } + } +#endif +} + +#if CHECK_BLINK +int check_blink_state(long base) +{ + ushort loops = 10; + u32 blinkindicator; + u32 state = 0x12345678; + u32 oldstate = 0; + + blinkindicator = htonl(0x54504442); + while ((loops--) && (state != oldstate)) { + oldstate = state; + state = inl((uint) base + 1); + } + + DBG(DBG_BLINK, printk("Did Blink check. Status: %d\n", + (state == oldstate) && (state == blinkindicator))); + + if ((state == oldstate) && (state == blinkindicator)) + return(TRUE); + else + return (FALSE); +} +#endif + +char * get_board_data(u32 base, u32 irq, u32 id) +{ + struct eata_ccb *cp; + struct eata_sp *sp; + static char *buff; + ulong i; + + cp = (struct eata_ccb *) scsi_init_malloc(sizeof(struct eata_ccb), + GFP_ATOMIC | GFP_DMA); + sp = (struct eata_sp *) scsi_init_malloc(sizeof(struct eata_sp), + GFP_ATOMIC | GFP_DMA); + + buff = dma_scratch; + + memset(cp, 0, sizeof(struct eata_ccb)); + memset(sp, 0, sizeof(struct eata_sp)); + memset(buff, 0, 256); + + cp->DataIn = TRUE; + cp->Interpret = TRUE; /* Interpret command */ + cp->cp_dispri = TRUE; + cp->cp_identify = TRUE; + + cp->cp_datalen = htonl(56); + cp->cp_dataDMA = htonl(virt_to_bus(buff)); + cp->cp_statDMA = htonl(virt_to_bus(sp)); + cp->cp_viraddr = cp; + + cp->cp_id = id; + cp->cp_lun = 0; + + cp->cp_cdb[0] = INQUIRY; + cp->cp_cdb[1] = 0; + cp->cp_cdb[2] = 0; + cp->cp_cdb[3] = 0; + cp->cp_cdb[4] = 56; + cp->cp_cdb[5] = 0; + + fake_int_base = (struct eata_register *) base; + fake_int_result = FALSE; + fake_int_happened = FALSE; + + eata_send_command((u32) cp, (u32) base, EATA_CMD_DMA_SEND_CP); + + i = jiffies + (3 * HZ); + while (fake_int_happened == FALSE && jiffies <= i) + barrier(); + + DBG(DBG_INTR3, printk(KERN_DEBUG "fake_int_result: %#x hbastat %#x " + "scsistat %#x, buff %p sp %p\n", + fake_int_result, (u32) (sp->hba_stat /*& 0x7f*/), + (u32) sp->scsi_stat, buff, sp)); + + scsi_init_free((void *)cp, sizeof(struct eata_ccb)); + scsi_init_free((void *)sp, sizeof(struct eata_sp)); + + if ((fake_int_result & HA_SERROR) || jiffies > i){ + printk(KERN_WARNING "eata_dma: trying to reset HBA at %x to clear " + "possible blink state\n", base); + /* hard reset the HBA */ + inb((u32) (base) + HA_RSTATUS); + eata_send_command(0, base, EATA_CMD_RESET); + DELAY(1); + return (NULL); + } else + return (buff); +} + + +int get_conf_PIO(u32 base, struct get_conf *buf) +{ + ulong loop = R_LIMIT; + u16 *p; + + if(check_region(base, 9)) + return (FALSE); + + memset(buf, 0, sizeof(struct get_conf)); + + while (inb(base + HA_RSTATUS) & HA_SBUSY) + if (--loop == 0) + return (FALSE); + + fake_int_base = (struct eata_register *) base; + fake_int_result = FALSE; + fake_int_happened = FALSE; + + DBG(DBG_PIO && DBG_PROBE, + printk("Issuing PIO READ CONFIG to HBA at %#x\n", base)); + eata_send_command(0, base, EATA_CMD_PIO_READ_CONFIG); + + loop = R_LIMIT; + for (p = (u16 *) buf; + (long)p <= ((long)buf + (sizeof(struct get_conf) / 2)); p++) { + while (!(inb(base + HA_RSTATUS) & HA_SDRQ)) + if (--loop == 0) + return (FALSE); + + loop = R_LIMIT; + *p = inw(base + HA_RDATA); + } + + if (!(inb(base + HA_RSTATUS) & HA_SERROR)) { /* Error ? */ + if (htonl(EATA_SIGNATURE) == buf->signature) { + DBG(DBG_PIO&&DBG_PROBE, printk("EATA Controller found at %x " + "EATA Level: %x\n", (uint) base, + (uint) (buf->version))); + + while (inb(base + HA_RSTATUS) & HA_SDRQ) + inw(base + HA_RDATA); + return (TRUE); + } + } else { + DBG(DBG_PROBE, printk("eata_dma: get_conf_PIO, error during transfer " + "for HBA at %lx\n", (long)base)); + } + return (FALSE); +} + + +void print_config(struct get_conf *gc) +{ + printk("LEN: %d ver:%d OCS:%d TAR:%d TRNXFR:%d MORES:%d DMAS:%d\n", + (u32) ntohl(gc->len), gc->version, + gc->OCS_enabled, gc->TAR_support, gc->TRNXFR, gc->MORE_support, + gc->DMA_support); + printk("DMAV:%d HAAV:%d SCSIID0:%d ID1:%d ID2:%d QUEUE:%d SG:%d SEC:%d\n", + gc->DMA_valid, gc->HAA_valid, gc->scsi_id[3], gc->scsi_id[2], + gc->scsi_id[1], ntohs(gc->queuesiz), ntohs(gc->SGsiz), gc->SECOND); + printk("IRQ:%d IRQT:%d DMAC:%d FORCADR:%d SG_64K:%d SG_UAE:%d MID:%d " + "MCH:%d MLUN:%d\n", + gc->IRQ, gc->IRQ_TR, (8 - gc->DMA_channel) & 7, gc->FORCADR, + gc->SG_64K, gc->SG_UAE, gc->MAX_ID, gc->MAX_CHAN, gc->MAX_LUN); + printk("RIDQ:%d PCI:%d EISA:%d\n", + gc->ID_qest, gc->is_PCI, gc->is_EISA); + DBG(DPT_DEBUG, DELAY(14)); +} + +short register_HBA(u32 base, struct get_conf *gc, Scsi_Host_Template * tpnt, + u8 bustype) +{ + ulong size = 0; + unchar dma_channel = 0; + char *buff = 0; + unchar bugs = 0; + struct Scsi_Host *sh; + hostdata *hd; + int x; + + + DBG(DBG_REGISTER, print_config(gc)); + + if (gc->DMA_support == FALSE) { + printk("The EATA HBA at %#.4x does not support DMA.\n" + "Please use the EATA-PIO driver.\n", base); + return (FALSE); + } + if(gc->HAA_valid == FALSE || ntohl(gc->len) < 0x22) + gc->MAX_CHAN = 0; + + if (reg_IRQ[gc->IRQ] == FALSE) { /* Interrupt already registered ? */ + if (!request_irq(gc->IRQ, (void *) eata_fake_int_handler, SA_INTERRUPT, + "eata_dma", NULL)){ + reg_IRQ[gc->IRQ]++; + if (!gc->IRQ_TR) + reg_IRQL[gc->IRQ] = TRUE; /* IRQ is edge triggered */ + } else { + printk("Couldn't allocate IRQ %d, Sorry.", gc->IRQ); + return (FALSE); + } + } else { /* More than one HBA on this IRQ */ + if (reg_IRQL[gc->IRQ] == TRUE) { + printk("Can't support more than one HBA on this IRQ,\n" + " if the IRQ is edge triggered. Sorry.\n"); + return (FALSE); + } else + reg_IRQ[gc->IRQ]++; + } + + + /* If DMA is supported but DMA_valid isn't set to indicate that + * the channel number is given we must have pre 2.0 firmware (1.7?) + * which leaves us to guess since the "newer ones" also don't set the + * DMA_valid bit. + */ + if (gc->DMA_support && !gc->DMA_valid && gc->DMA_channel) { + printk(KERN_WARNING "eata_dma: If you are using a pre 2.0 firmware " + "please update it !\n" + " You can get new firmware releases from ftp.dpt.com\n"); + gc->DMA_channel = (base == 0x1f0 ? 3 /* DMA=5 */ : 2 /* DMA=6 */); + gc->DMA_valid = TRUE; + } + + /* if gc->DMA_valid it must be an ISA HBA and we have to register it */ + dma_channel = BUSMASTER; + if (gc->DMA_valid) { + if (request_dma(dma_channel = (8 - gc->DMA_channel) & 7, "eata_dma")) { + printk(KERN_WARNING "Unable to allocate DMA channel %d for ISA HBA" + " at %#.4x.\n", dma_channel, base); + reg_IRQ[gc->IRQ]--; + if (reg_IRQ[gc->IRQ] == 0) + free_irq(gc->IRQ, NULL); + if (gc->IRQ_TR == FALSE) + reg_IRQL[gc->IRQ] = FALSE; + return (FALSE); + } + } + + if (dma_channel != BUSMASTER) { + disable_dma(dma_channel); + clear_dma_ff(dma_channel); + set_dma_mode(dma_channel, DMA_MODE_CASCADE); + enable_dma(dma_channel); + } + + if (bustype != IS_EISA && bustype != IS_ISA) + buff = get_board_data(base, gc->IRQ, gc->scsi_id[3]); + + if (buff == NULL) { + if (bustype == IS_EISA || bustype == IS_ISA) { + bugs = bugs || BROKEN_INQUIRY; + } else { + if (gc->DMA_support == FALSE) + printk(KERN_WARNING "HBA at %#.4x doesn't support DMA. " + "Sorry\n", base); + else + printk(KERN_WARNING "HBA at %#.4x does not react on INQUIRY. " + "Sorry.\n", base); + if (gc->DMA_valid) + free_dma(dma_channel); + reg_IRQ[gc->IRQ]--; + if (reg_IRQ[gc->IRQ] == 0) + free_irq(gc->IRQ, NULL); + if (gc->IRQ_TR == FALSE) + reg_IRQL[gc->IRQ] = FALSE; + return (FALSE); + } + } + + if (gc->DMA_support == FALSE && buff != NULL) + printk(KERN_WARNING "HBA %.12sat %#.4x doesn't set the DMA_support " + "flag correctly.\n", &buff[16], base); + + request_region(base, 9, "eata_dma"); /* We already checked the + * availability, so this + * should not fail. + */ + + if(ntohs(gc->queuesiz) == 0) { + gc->queuesiz = ntohs(64); + printk(KERN_WARNING "Warning: Queue size has to be corrected. Assuming" + " 64 queueslots\n" + " This might be a PM2012B with a defective Firmware\n" + " Contact DPT support@dpt.com for an upgrade\n"); + } + + size = sizeof(hostdata) + ((sizeof(struct eata_ccb) + sizeof(long)) + * ntohs(gc->queuesiz)); + + DBG(DBG_REGISTER, printk("scsi_register size: %ld\n", size)); + + sh = scsi_register(tpnt, size); + + if(sh != NULL) { + + hd = SD(sh); + + memset(hd->reads, 0, sizeof(u32) * 26); + + sh->select_queue_depths = eata_select_queue_depths; + + hd->bustype = bustype; + + /* + * If we are using a ISA board, we can't use extended SG, + * because we would need excessive amounts of memory for + * bounce buffers. + */ + if (gc->SG_64K==TRUE && ntohs(gc->SGsiz)==64 && hd->bustype!=IS_ISA){ + sh->sg_tablesize = SG_SIZE_BIG; + } else { + sh->sg_tablesize = ntohs(gc->SGsiz); + if (sh->sg_tablesize > SG_SIZE || sh->sg_tablesize == 0) { + if (sh->sg_tablesize == 0) + printk(KERN_WARNING "Warning: SG size had to be fixed.\n" + "This might be a PM2012 with a defective Firmware" + "\nContact DPT support@dpt.com for an upgrade\n"); + sh->sg_tablesize = SG_SIZE; + } + } + hd->sgsize = sh->sg_tablesize; + } + + if(sh != NULL) { + sh->can_queue = hd->queuesize = ntohs(gc->queuesiz); + sh->cmd_per_lun = 0; + } + + if(sh == NULL) { + DBG(DBG_REGISTER, printk(KERN_NOTICE "eata_dma: couldn't register HBA" + " at%x \n", base)); + scsi_unregister(sh); + if (gc->DMA_valid) + free_dma(dma_channel); + + reg_IRQ[gc->IRQ]--; + if (reg_IRQ[gc->IRQ] == 0) + free_irq(gc->IRQ, NULL); + if (gc->IRQ_TR == FALSE) + reg_IRQL[gc->IRQ] = FALSE; + return (FALSE); + } + + + hd->broken_INQUIRY = (bugs & BROKEN_INQUIRY); + + if(hd->broken_INQUIRY == TRUE) { + strcpy(hd->vendor, "DPT"); + strcpy(hd->name, "??????????"); + strcpy(hd->revision, "???.?"); + hd->firmware_revision = 0; + } else { + strncpy(hd->vendor, &buff[8], 8); + hd->vendor[8] = 0; + strncpy(hd->name, &buff[16], 17); + hd->name[17] = 0; + hd->revision[0] = buff[32]; + hd->revision[1] = buff[33]; + hd->revision[2] = buff[34]; + hd->revision[3] = '.'; + hd->revision[4] = buff[35]; + hd->revision[5] = 0; + hd->firmware_revision = (buff[32] << 24) + (buff[33] << 16) + + (buff[34] << 8) + buff[35]; + } + + if (hd->firmware_revision >= (('0'<<24) + ('7'<<16) + ('G'<< 8) + '0')) + hd->immediate_support = 1; + else + hd->immediate_support = 0; + + switch (ntohl(gc->len)) { + case 0x1c: + hd->EATA_revision = 'a'; + break; + case 0x1e: + hd->EATA_revision = 'b'; + break; + case 0x22: + hd->EATA_revision = 'c'; + break; + case 0x24: + hd->EATA_revision = 'z'; + default: + hd->EATA_revision = '?'; + } + + + if(ntohl(gc->len) >= 0x22) { + sh->max_id = gc->MAX_ID + 1; + sh->max_lun = gc->MAX_LUN + 1; + } else { + sh->max_id = 8; + sh->max_lun = 8; + } + + hd->HBA_number = sh->host_no; + hd->channel = gc->MAX_CHAN; + sh->max_channel = gc->MAX_CHAN; + sh->unique_id = base; + sh->base = (char *) base; + sh->io_port = base; + sh->n_io_port = 9; + sh->irq = gc->IRQ; + sh->dma_channel = dma_channel; + + /* FIXME: + * SCSI midlevel code should support different HBA ids on every channel + */ + sh->this_id = gc->scsi_id[3]; + + if (gc->SECOND) + hd->primary = FALSE; + else + hd->primary = TRUE; + + sh->wish_block = FALSE; + + if (hd->bustype != IS_ISA) { + sh->unchecked_isa_dma = FALSE; + } else { + sh->unchecked_isa_dma = TRUE; /* We're doing ISA DMA */ + } + + for(x = 0; x <= 11; x++){ /* Initialize min. latency */ + hd->writes_lat[x][1] = 0xffffffff; + hd->reads_lat[x][1] = 0xffffffff; + } + hd->all_lat[1] = 0xffffffff; + + hd->next = NULL; /* build a linked list of all HBAs */ + hd->prev = last_HBA; + if(hd->prev != NULL) + SD(hd->prev)->next = sh; + last_HBA = sh; + if (first_HBA == NULL) + first_HBA = sh; + registered_HBAs++; + + return (TRUE); +} + + + +void find_EISA(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + u32 base; + int i; + +#if CHECKPAL + u8 pal1, pal2, pal3; +#endif + + for (i = 0; i < MAXEISA; i++) { + if (EISAbases[i] == TRUE) { /* Still a possibility ? */ + + base = 0x1c88 + (i * 0x1000); +#if CHECKPAL + pal1 = inb((u16)base - 8); + pal2 = inb((u16)base - 7); + pal3 = inb((u16)base - 6); + + if (((pal1 == DPT_ID1) && (pal2 == DPT_ID2)) || + ((pal1 == NEC_ID1) && (pal2 == NEC_ID2) && (pal3 == NEC_ID3))|| + ((pal1 == ATT_ID1) && (pal2 == ATT_ID2) && (pal3 == ATT_ID3))){ + DBG(DBG_PROBE, printk("EISA EATA id tags found: %x %x %x \n", + (int)pal1, (int)pal2, (int)pal3)); +#endif + if (get_conf_PIO(base, buf) == TRUE) { + if (buf->IRQ) { + DBG(DBG_EISA, printk("Registering EISA HBA\n")); + register_HBA(base, buf, tpnt, IS_EISA); + } else + printk("eata_dma: No valid IRQ. HBA removed from list\n"); + } +#if CHECK_BLINK + else { + if (check_blink_state(base)) + printk("HBA is in BLINK state. Consult your HBAs " + "Manual to correct this.\n"); + } +#endif + /* Nothing found here so we take it from the list */ + EISAbases[i] = 0; +#if CHECKPAL + } +#endif + } + } + return; +} + +void find_ISA(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + int i; + + for (i = 0; i < MAXISA; i++) { + if (ISAbases[i]) { + if (get_conf_PIO(ISAbases[i],buf) == TRUE){ + DBG(DBG_ISA, printk("Registering ISA HBA\n")); + register_HBA(ISAbases[i], buf, tpnt, IS_ISA); + } +#if CHECK_BLINK + else { + if (check_blink_state(ISAbases[i])) + printk("HBA is in BLINK state. Consult your HBAs " + "Manual to correct this.\n"); + } +#endif + ISAbases[i] = 0; + } + } + return; +} + +void find_PCI(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + +#ifndef CONFIG_PCI + printk("eata_dma: kernel PCI support not enabled. Skipping scan for PCI HBAs.\n"); +#else + + u8 pci_bus, pci_device_fn; + static s16 pci_index = 0; /* Device index to PCI BIOS calls */ + u32 base = 0; + u16 com_adr; + u16 rev_device; + u32 error, i, x; + u8 pal1, pal2, pal3; + + if (pcibios_present()) { + for (i = 0; i <= MAXPCI; ++i, ++pci_index) { + if (pcibios_find_device(PCI_VENDOR_ID_DPT, PCI_DEVICE_ID_DPT, + pci_index, &pci_bus, &pci_device_fn)) + break; + DBG(DBG_PROBE && DBG_PCI, + printk("eata_dma: find_PCI, HBA at bus %d, device %d," + " function %d, index %d\n", (s32)pci_bus, + (s32)((pci_device_fn & 0xf8) >> 3), + (s32)(pci_device_fn & 7), pci_index)); + + if (!(error = pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_CLASS_DEVICE, &rev_device))) { + if (rev_device == PCI_CLASS_STORAGE_SCSI) { + if (!(error = pcibios_read_config_word(pci_bus, + pci_device_fn, PCI_COMMAND, + (u16 *) & com_adr))) { + if (!((com_adr & PCI_COMMAND_IO) && + (com_adr & PCI_COMMAND_MASTER))) { + printk("eata_dma: find_PCI, HBA has IO or" + " BUSMASTER mode disabled\n"); + continue; + } + } else + printk("eata_dma: find_PCI, error %x while reading " + "PCI_COMMAND\n", error); + } else + printk("eata_dma: find_PCI, DEVICECLASSID %x didn't match\n", + rev_device); + } else { + printk("eata_dma: find_PCI, error %x while reading " + "PCI_CLASS_BASE\n", + error); + continue; + } + + if (!(error = pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, (int *) &base))){ + + /* Check if the address is valid */ + if (base & 0x01) { + base &= 0xfffffffe; + /* EISA tag there ? */ + pal1 = inb(base); + pal2 = inb(base + 1); + pal3 = inb(base + 2); + if (((pal1 == DPT_ID1) && (pal2 == DPT_ID2)) || + ((pal1 == NEC_ID1) && (pal2 == NEC_ID2) && + (pal3 == NEC_ID3)) || + ((pal1 == ATT_ID1) && (pal2 == ATT_ID2) && + (pal3 == ATT_ID3))) + base += 0x08; + else + base += 0x10; /* Now, THIS is the real address */ + + if (base != 0x1f8) { + /* We didn't find it in the primary search */ + if (get_conf_PIO(base, buf) == TRUE) { + + /* OK. We made it till here, so we can go now + * and register it. We only have to check and + * eventually remove it from the EISA and ISA list + */ + DBG(DBG_PCI, printk("Registering PCI HBA\n")); + register_HBA(base, buf, tpnt, IS_PCI); + + if (base < 0x1000) { + for (x = 0; x < MAXISA; ++x) { + if (ISAbases[x] == base) { + ISAbases[x] = 0; + break; + } + } + } else if ((base & 0x0fff) == 0x0c88) + EISAbases[(base >> 12) & 0x0f] = 0; + continue; /* break; */ + } +#if CHECK_BLINK + else if (check_blink_state(base) == TRUE) { + printk("eata_dma: HBA is in BLINK state.\n" + "Consult your HBAs manual to correct this.\n"); + } +#endif + } + } + } else { + printk("eata_dma: error %x while reading " + "PCI_BASE_ADDRESS_0\n", error); + } + } + } else { + printk("eata_dma: No BIOS32 extensions present. This driver release " + "still depends on it.\n" + " Skipping scan for PCI HBAs. \n"); + } +#endif /* #ifndef CONFIG_PCI */ + return; +} + +int eata_detect(Scsi_Host_Template * tpnt) +{ + struct Scsi_Host *HBA_ptr; + struct get_conf gc; + int i; + + DBG((DBG_PROBE && DBG_DELAY) || DPT_DEBUG, + printk("Using lots of delays to let you read the debugging output\n")); + + tpnt->proc_dir = &proc_scsi_eata_dma; + + status = scsi_init_malloc(512, GFP_ATOMIC | GFP_DMA); + dma_scratch = scsi_init_malloc(1024, GFP_ATOMIC | GFP_DMA); + + if(status == NULL || dma_scratch == NULL) { + printk("eata_dma: can't allocate enough memory to probe for hosts !\n"); + return(0); + } + + dma_scratch += 4; + + find_PCI(&gc, tpnt); + + find_EISA(&gc, tpnt); + + find_ISA(&gc, tpnt); + + for (i = 0; i <= MAXIRQ; i++) { /* Now that we know what we have, we */ + if (reg_IRQ[i] >= 1){ /* exchange the interrupt handler which */ + free_irq(i, NULL); /* we used for probing with the real one */ + request_irq(i, (void *)(eata_int_handler), SA_INTERRUPT|SA_SHIRQ, + "eata_dma", NULL); + } + } + + HBA_ptr = first_HBA; + + if (registered_HBAs != 0) { + printk("EATA (Extended Attachment) driver version: %d.%d%s" + "\ndeveloped in co-operation with DPT\n" + "(c) 1993-96 Michael Neuffer, mike@i-Connect.Net\n", + VER_MAJOR, VER_MINOR, VER_SUB); + printk("Registered HBAs:"); + printk("\nHBA no. Boardtype Revis EATA Bus BaseIO IRQ" + " DMA Ch ID Pr QS S/G IS\n"); + for (i = 1; i <= registered_HBAs; i++) { + printk("scsi%-2d: %.12s v%s 2.0%c %s %#.4x %2d", + HBA_ptr->host_no, SD(HBA_ptr)->name, SD(HBA_ptr)->revision, + SD(HBA_ptr)->EATA_revision, (SD(HBA_ptr)->bustype == 'P')? + "PCI ":(SD(HBA_ptr)->bustype == 'E')?"EISA":"ISA ", + (u32) HBA_ptr->base, HBA_ptr->irq); + if(HBA_ptr->dma_channel != BUSMASTER) + printk(" %2x ", HBA_ptr->dma_channel); + else + printk(" %s", "BMST"); + printk(" %d %d %c %3d %3d %c\n", + SD(HBA_ptr)->channel+1, HBA_ptr->this_id, + (SD(HBA_ptr)->primary == TRUE)?'Y':'N', + HBA_ptr->can_queue, HBA_ptr->sg_tablesize, + (SD(HBA_ptr)->immediate_support == TRUE)?'Y':'N'); + HBA_ptr = SD(HBA_ptr)->next; + } + } else { + scsi_init_free((void *)status, 512); + } + + scsi_init_free((void *)dma_scratch - 4, 1024); + + DBG(DPT_DEBUG, DELAY(12)); + + return(registered_HBAs); +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = EATA_DMA; +#include "scsi_module.c" +#endif + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/eata_dma.h b/linux/src/drivers/scsi/eata_dma.h new file mode 100644 index 00000000..a23931bf --- /dev/null +++ b/linux/src/drivers/scsi/eata_dma.h @@ -0,0 +1,128 @@ +/******************************************************** +* Header file for eata_dma.c Linux EATA-DMA SCSI driver * +* (c) 1993-96 Michael Neuffer * +* mike@i-Connect.Net * +* neuffer@mail.uni-mainz.de * +********************************************************* +* last change: 96/10/14 * +********************************************************/ + +#ifndef _EATA_DMA_H +#define _EATA_DMA_H + +#ifndef HOSTS_C + +#include "eata_generic.h" + + +#define VER_MAJOR 2 +#define VER_MINOR 5 +#define VER_SUB "9b" + + +/************************************************************************ + * Here you can switch parts of the code on and of * + ************************************************************************/ + +#define CHECKPAL 0 /* EISA pal checking on/off */ +#define CHECK_BLINK 1 /* Switch Blink state check off, might * + * be nessessary for some MIPS machines*/ +#define CRIPPLE_QUEUE 0 /* Only enable this if the interrupt + * controller on your motherboard is + * broken and you are experiencing + * massive interrupt losses */ + +/************************************************************************ + * Debug options. * + * Enable DEBUG and whichever options you require. * + ************************************************************************/ +#define DEBUG_EATA 1 /* Enable debug code. */ +#define DPT_DEBUG 0 /* Bobs special */ +#define DBG_DELAY 0 /* Build in delays so debug messages can be + * be read before they vanish of the top of + * the screen! */ +#define DBG_PROBE 0 /* Debug probe routines. */ +#define DBG_PCI 0 /* Trace PCI routines */ +#define DBG_EISA 0 /* Trace EISA routines */ +#define DBG_ISA 0 /* Trace ISA routines */ +#define DBG_BLINK 0 /* Trace Blink check */ +#define DBG_PIO 0 /* Trace get_config_PIO */ +#define DBG_COM 0 /* Trace command call */ +#define DBG_QUEUE 0 /* Trace command queueing. */ +#define DBG_QUEUE2 0 /* Trace command queueing SG. */ +#define DBG_INTR 0 /* Trace interrupt service routine. */ +#define DBG_INTR2 0 /* Trace interrupt service routine. */ +#define DBG_INTR3 0 /* Trace get_board_data interrupts. */ +#define DBG_REQSENSE 0 /* Trace request sense commands */ +#define DBG_RESET 0 /* Trace reset calls */ +#define DBG_STATUS 0 /* Trace status generation */ +#define DBG_PROC 0 /* Debug proc-fs related statistics */ +#define DBG_PROC_WRITE 0 +#define DBG_REGISTER 0 /* */ +#define DBG_ABNORM 1 /* Debug abnormal actions (reset, abort)*/ + +#if DEBUG_EATA +#define DBG(x, y) if ((x)) {y;} +#else +#define DBG(x, y) +#endif + +#endif /* !HOSTS_C */ + +int eata_detect(Scsi_Host_Template *); +const char *eata_info(struct Scsi_Host *); +int eata_command(Scsi_Cmnd *); +int eata_queue(Scsi_Cmnd *, void (* done)(Scsi_Cmnd *)); +int eata_abort(Scsi_Cmnd *); +int eata_reset(Scsi_Cmnd *, unsigned int); +int eata_proc_info(char *, char **, off_t, int, int, int); +#ifdef MODULE +int eata_release(struct Scsi_Host *); +#else +#define eata_release NULL +#endif + +#include <scsi/scsicam.h> + +#define EATA_DMA { \ + NULL, NULL, \ + NULL, /* proc_dir_entry */ \ + eata_proc_info, /* procinfo */ \ + "EATA (Extended Attachment) HBA driver", \ + eata_detect, \ + eata_release, \ + NULL, NULL, \ + eata_queue, \ + eata_abort, \ + eata_reset, \ + NULL, /* Slave attach */ \ + scsicam_bios_param, \ + 0, /* Canqueue */ \ + 0, /* this_id */ \ + 0, /* sg_tablesize */ \ + 0, /* cmd_per_lun */ \ + 0, /* present */ \ + 1, /* True if ISA */ \ + ENABLE_CLUSTERING } + + +#endif /* _EATA_DMA_H */ + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/eata_dma_proc.c b/linux/src/drivers/scsi/eata_dma_proc.c new file mode 100644 index 00000000..3a3a19aa --- /dev/null +++ b/linux/src/drivers/scsi/eata_dma_proc.c @@ -0,0 +1,493 @@ + +void swap_statistics(u8 *p) +{ + u32 y; + u32 *lp, h_lp; + u16 *sp, h_sp; + u8 *bp; + + lp = (u32 *)p; + sp = ((short *)lp) + 1; /* Convert Header */ + h_sp = *sp = ntohs(*sp); + lp++; + + do { + sp = (u16 *)lp; /* Convert SubHeader */ + *sp = ntohs(*sp); + bp = (u8 *) lp; + y = *(bp + 3); + lp++; + for (h_lp = (u32)lp; (u32)lp < h_lp + ((u32)*(bp + 3)); lp++) + *lp = ntohl(*lp); + }while ((u32)lp < ((u32)p) + 4 + h_sp); + +} + +/* + * eata_set_info + * buffer : pointer to the data that has been written to the hostfile + * length : number of bytes written to the hostfile + * HBA_ptr: pointer to the Scsi_Host struct + */ +int eata_set_info(char *buffer, int length, struct Scsi_Host *HBA_ptr) +{ + int orig_length = length; + + if (length >= 8 && strncmp(buffer, "eata_dma", 8) == 0) { + buffer += 9; + length -= 9; + if(length >= 8 && strncmp(buffer, "latency", 7) == 0) { + SD(HBA_ptr)->do_latency = TRUE; + return(orig_length); + } + + if(length >=10 && strncmp(buffer, "nolatency", 9) == 0) { + SD(HBA_ptr)->do_latency = FALSE; + return(orig_length); + } + + printk("Unknown command:%s length: %d\n", buffer, length); + } else + printk("Wrong Signature:%10s\n", buffer); + + return(-EINVAL); +} + +/* + * eata_proc_info + * inout : decides on the direction of the dataflow and the meaning of the + * variables + * buffer: If inout==FALSE data is being written to it else read from it + * *start: If inout==FALSE start of the valid data in the buffer + * offset: If inout==FALSE offset from the beginning of the imaginary file + * from which we start writing into the buffer + * length: If inout==FALSE max number of bytes to be written into the buffer + * else number of bytes in the buffer + */ +int eata_proc_info(char *buffer, char **start, off_t offset, int length, + int hostno, int inout) +{ + + Scsi_Device *scd, SDev; + struct Scsi_Host *HBA_ptr; + Scsi_Cmnd scmd; + char cmnd[10]; + static u8 buff[512]; + static u8 buff2[512]; + hst_cmd_stat *rhcs, *whcs; + coco *cc; + scsitrans *st; + scsimod *sm; + hobu *hb; + scbu *sb; + boty *bt; + memco *mc; + firm *fm; + subinf *si; + pcinf *pi; + arrlim *al; + int i, x; + int size, len = 0; + off_t begin = 0; + off_t pos = 0; + scd = NULL; + + HBA_ptr = first_HBA; + for (i = 1; i <= registered_HBAs; i++) { + if (HBA_ptr->host_no == hostno) + break; + HBA_ptr = SD(HBA_ptr)->next; + } + + if(inout == TRUE) /* Has data been written to the file ? */ + return(eata_set_info(buffer, length, HBA_ptr)); + + if (offset == 0) + memset(buff, 0, sizeof(buff)); + + cc = (coco *) (buff + 0x148); + st = (scsitrans *)(buff + 0x164); + sm = (scsimod *) (buff + 0x16c); + hb = (hobu *) (buff + 0x172); + sb = (scbu *) (buff + 0x178); + bt = (boty *) (buff + 0x17e); + mc = (memco *) (buff + 0x186); + fm = (firm *) (buff + 0x18e); + si = (subinf *) (buff + 0x196); + pi = (pcinf *) (buff + 0x19c); + al = (arrlim *) (buff + 0x1a2); + + size = sprintf(buffer+len, "EATA (Extended Attachment) driver version: " + "%d.%d%s\n",VER_MAJOR, VER_MINOR, VER_SUB); + len += size; pos = begin + len; + size = sprintf(buffer + len, "queued commands: %10ld\n" + "processed interrupts:%10ld\n", queue_counter, int_counter); + len += size; pos = begin + len; + + size = sprintf(buffer + len, "\nscsi%-2d: HBA %.10s\n", + HBA_ptr->host_no, SD(HBA_ptr)->name); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Firmware revision: v%s\n", + SD(HBA_ptr)->revision); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Hardware Configuration:\n"); + len += size; + pos = begin + len; + + if(SD(HBA_ptr)->broken_INQUIRY == TRUE) { + if (HBA_ptr->dma_channel == BUSMASTER) + size = sprintf(buffer + len, "DMA: BUSMASTER\n"); + else + size = sprintf(buffer + len, "DMA: %d\n", HBA_ptr->dma_channel); + len += size; + pos = begin + len; + + size = sprintf(buffer + len, "Base IO : %#.4x\n", (u32) HBA_ptr->base); + len += size; + pos = begin + len; + + size = sprintf(buffer + len, "Host Bus: EISA\n"); + len += size; + pos = begin + len; + + } else { + memset(&SDev, 0, sizeof(Scsi_Device)); + memset(&scmd, 0, sizeof(Scsi_Cmnd)); + + SDev.host = HBA_ptr; + SDev.id = HBA_ptr->this_id; + SDev.lun = 0; + SDev.channel = 0; + + cmnd[0] = LOG_SENSE; + cmnd[1] = 0; + cmnd[2] = 0x33 + (3<<6); + cmnd[3] = 0; + cmnd[4] = 0; + cmnd[5] = 0; + cmnd[6] = 0; + cmnd[7] = 0x00; + cmnd[8] = 0x66; + cmnd[9] = 0; + + scmd.cmd_len = 10; + + scmd.host = HBA_ptr; + scmd.device = &SDev; + scmd.target = HBA_ptr->this_id; + scmd.lun = 0; + scmd.channel = 0; + scmd.use_sg = 0; + + /* + * Do the command and wait for it to finish. + */ + { + struct semaphore sem = MUTEX_LOCKED; + scmd.request.rq_status = RQ_SCSI_BUSY; + scmd.request.sem = &sem; + scsi_do_cmd (&scmd, cmnd, buff + 0x144, 0x66, + eata_scsi_done, 1 * HZ, 1); + down(&sem); + } + + size = sprintf(buffer + len, "IRQ: %2d, %s triggered\n", cc->interrupt, + (cc->intt == TRUE)?"level":"edge"); + len += size; + pos = begin + len; + if (HBA_ptr->dma_channel == 0xff) + size = sprintf(buffer + len, "DMA: BUSMASTER\n"); + else + size = sprintf(buffer + len, "DMA: %d\n", HBA_ptr->dma_channel); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "CPU: MC680%02d %dMHz\n", bt->cpu_type, + bt->cpu_speed); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Base IO : %#.4x\n", (u32) HBA_ptr->base); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Host Bus: %s\n", + (SD(HBA_ptr)->bustype == IS_PCI)?"PCI ": + (SD(HBA_ptr)->bustype == IS_EISA)?"EISA":"ISA "); + + len += size; + pos = begin + len; + size = sprintf(buffer + len, "SCSI Bus:%s%s Speed: %sMB/sec. %s\n", + (sb->wide == TRUE)?" WIDE":"", + (sb->dif == TRUE)?" DIFFERENTIAL":"", + (sb->speed == 0)?"5":(sb->speed == 1)?"10":"20", + (sb->ext == TRUE)?"With external cable detection":""); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "SCSI channel expansion Module: %s present\n", + (bt->sx1 == TRUE)?"SX1 (one channel)": + ((bt->sx2 == TRUE)?"SX2 (two channels)":"not")); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "SmartRAID hardware: %spresent.\n", + (cc->srs == TRUE)?"":"not "); + len += size; + pos = begin + len; + size = sprintf(buffer + len, " Type: %s\n", + ((cc->key == TRUE)?((bt->dmi == TRUE)?"integrated" + :((bt->dm4 == TRUE)?"DM401X" + :(bt->dm4k == TRUE)?"DM4000" + :"-")) + :"-")); + len += size; + pos = begin + len; + + size = sprintf(buffer + len, " Max array groups: %d\n", + (al->code == 0x0e)?al->max_groups:7); + len += size; + pos = begin + len; + size = sprintf(buffer + len, " Max drives per RAID 0 array: %d\n", + (al->code == 0x0e)?al->raid0_drv:7); + len += size; + pos = begin + len; + size = sprintf(buffer + len, " Max drives per RAID 3/5 array: %d\n", + (al->code == 0x0e)?al->raid35_drv:7); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Cache Module: %spresent.\n", + (cc->csh)?"":"not "); + len += size; + pos = begin + len; + size = sprintf(buffer + len, " Type: %s\n", + ((cc->csh == TRUE)?((bt->cmi == TRUE)?"integrated" + :((bt->cm4 == TRUE)?"CM401X" + :((bt->cm4k == TRUE)?"CM4000" + :"-"))) + :"-")); + len += size; + pos = begin + len; + for (x = 0; x <= 3; x++) { + size = sprintf(buffer + len, " Bank%d: %dMB with%s ECC\n",x, + mc->banksize[x] & 0x7f, + (mc->banksize[x] & 0x80)?"":"out"); + len += size; + pos = begin + len; + } + size = sprintf(buffer + len, "Timer Mod.: %spresent\n", + (cc->tmr == TRUE)?"":"not "); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "NVRAM : %spresent\n", + (cc->nvr == TRUE)?"":"not "); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "SmartROM : %sabled\n", + (bt->srom == TRUE)?"dis":"en"); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Alarm : %s\n", + (bt->alrm == TRUE)?"on":"off"); + len += size; + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + + if(SD(HBA_ptr)->do_latency == FALSE) { + + cmnd[0] = LOG_SENSE; + cmnd[1] = 0; + cmnd[2] = 0x32 + (3<<6); + cmnd[3] = 0; + cmnd[4] = 0; + cmnd[5] = 0; + cmnd[6] = 0; + cmnd[7] = 0x01; + cmnd[8] = 0x44; + cmnd[9] = 0; + + scmd.cmd_len = 10; + + /* + * Do the command and wait for it to finish. + */ + { + struct semaphore sem = MUTEX_LOCKED; + scmd.request.rq_status = RQ_SCSI_BUSY; + scmd.request.sem = &sem; + scsi_do_cmd (&scmd, cmnd, buff2, 0x144, + eata_scsi_done, 1 * HZ, 1); + down(&sem); + } + + swap_statistics(buff2); + rhcs = (hst_cmd_stat *)(buff2 + 0x2c); + whcs = (hst_cmd_stat *)(buff2 + 0x8c); + + for (x = 0; x <= 11; x++) { + SD(HBA_ptr)->reads[x] += rhcs->sizes[x]; + SD(HBA_ptr)->writes[x] += whcs->sizes[x]; + SD(HBA_ptr)->reads[12] += rhcs->sizes[x]; + SD(HBA_ptr)->writes[12] += whcs->sizes[x]; + } + size = sprintf(buffer + len, "Host<->Disk command statistics:\n" + " Reads: Writes:\n"); + len += size; + pos = begin + len; + for (x = 0; x <= 10; x++) { + size = sprintf(buffer+len,"%5dk:%12u %12u\n", 1 << x, + SD(HBA_ptr)->reads[x], + SD(HBA_ptr)->writes[x]); + len += size; + pos = begin + len; + } + size = sprintf(buffer+len,">1024k:%12u %12u\n", + SD(HBA_ptr)->reads[11], + SD(HBA_ptr)->writes[11]); + len += size; + pos = begin + len; + size = sprintf(buffer+len,"Sum :%12u %12u\n", + SD(HBA_ptr)->reads[12], + SD(HBA_ptr)->writes[12]); + len += size; + pos = begin + len; + } + } + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + + if(SD(HBA_ptr)->do_latency == TRUE) { + int factor = 1024/HZ; + size = sprintf(buffer + len, "Host Latency Command Statistics:\n" + "Current timer resolution: %2dms\n" + " Reads: Min:(ms) Max:(ms) Ave:(ms)\n", + factor); + len += size; + pos = begin + len; + for (x = 0; x <= 10; x++) { + size = sprintf(buffer+len,"%5dk:%12u %12u %12u %12u\n", + 1 << x, + SD(HBA_ptr)->reads_lat[x][0], + (SD(HBA_ptr)->reads_lat[x][1] == 0xffffffff) + ? 0:(SD(HBA_ptr)->reads_lat[x][1] * factor), + SD(HBA_ptr)->reads_lat[x][2] * factor, + SD(HBA_ptr)->reads_lat[x][3] * factor / + ((SD(HBA_ptr)->reads_lat[x][0]) + ? SD(HBA_ptr)->reads_lat[x][0]:1)); + len += size; + pos = begin + len; + } + size = sprintf(buffer+len,">1024k:%12u %12u %12u %12u\n", + SD(HBA_ptr)->reads_lat[11][0], + (SD(HBA_ptr)->reads_lat[11][1] == 0xffffffff) + ? 0:(SD(HBA_ptr)->reads_lat[11][1] * factor), + SD(HBA_ptr)->reads_lat[11][2] * factor, + SD(HBA_ptr)->reads_lat[11][3] * factor / + ((SD(HBA_ptr)->reads_lat[x][0]) + ? SD(HBA_ptr)->reads_lat[x][0]:1)); + len += size; + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + + size = sprintf(buffer + len, + " Writes: Min:(ms) Max:(ms) Ave:(ms)\n"); + len += size; + pos = begin + len; + for (x = 0; x <= 10; x++) { + size = sprintf(buffer+len,"%5dk:%12u %12u %12u %12u\n", + 1 << x, + SD(HBA_ptr)->writes_lat[x][0], + (SD(HBA_ptr)->writes_lat[x][1] == 0xffffffff) + ? 0:(SD(HBA_ptr)->writes_lat[x][1] * factor), + SD(HBA_ptr)->writes_lat[x][2] * factor, + SD(HBA_ptr)->writes_lat[x][3] * factor / + ((SD(HBA_ptr)->writes_lat[x][0]) + ? SD(HBA_ptr)->writes_lat[x][0]:1)); + len += size; + pos = begin + len; + } + size = sprintf(buffer+len,">1024k:%12u %12u %12u %12u\n", + SD(HBA_ptr)->writes_lat[11][0], + (SD(HBA_ptr)->writes_lat[11][1] == 0xffffffff) + ? 0:(SD(HBA_ptr)->writes_lat[x][1] * factor), + SD(HBA_ptr)->writes_lat[11][2] * factor, + SD(HBA_ptr)->writes_lat[11][3] * factor / + ((SD(HBA_ptr)->writes_lat[x][0]) + ? SD(HBA_ptr)->writes_lat[x][0]:1)); + len += size; + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + } + +#if 0 + scd = scsi_devices; + + size = sprintf(buffer+len,"Attached devices: %s\n", (scd)?"":"none"); + len += size; + pos = begin + len; + + while (scd) { + if (scd->host == HBA_ptr) { + proc_print_scsidevice(scd, buffer, &size, len); + len += size; + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + } + scd = scd->next; + } +#endif + + stop_output: + DBG(DBG_PROC, printk("2pos: %ld offset: %ld len: %d\n", pos, offset, len)); + *start=buffer+(offset-begin); /* Start of wanted data */ + len-=(offset-begin); /* Start slop */ + if(len>length) + len = length; /* Ending slop */ + DBG(DBG_PROC, printk("3pos: %ld offset: %ld len: %d\n", pos, offset, len)); + + return (len); +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/eata_dma_proc.h b/linux/src/drivers/scsi/eata_dma_proc.h new file mode 100644 index 00000000..d49f348e --- /dev/null +++ b/linux/src/drivers/scsi/eata_dma_proc.h @@ -0,0 +1,260 @@ + +struct lun_map { + __u8 id:5, + chan:3; + __u8 lun; +}; + +typedef struct emul_pp { + __u8 p_code:6, + null:1, + p_save:1; + __u8 p_length; + __u16 cylinder; + __u8 heads; + __u8 sectors; + __u8 null2; + __u8 s_lunmap:4, + ems:1; + __u16 drive_type; /* In Little Endian ! */ + struct lun_map lunmap[4]; +}emulpp; + + +/* Log Sense pages */ + +typedef struct log_sheader { + __u8 page_code, + reserved; + __u16 length; +}logsh; + + +/* Log Sense Statistics */ + +typedef struct read_command_statistics { + __u16 code; /* 0x01 */ + __u8 flags; + __u8 length; /* 0x24 */ + __u32 h_commands, + uncached, + la_cmds, + la_blks, + la_hits, + missed, + hits, + seq_la_blks, + seq_la_hits; +}r_cmd_stat; + +typedef struct write_command_statistics { + __u16 code; /* 0x03 */ + __u8 flags; + __u8 length; /* 0x28 */ + __u32 h_commands, + uncached, + thru, + bypass, + soft_err, + hits, + b_idle, + b_activ, + b_blks, + b_blks_clean; +}w_cmd_stat; + +typedef struct host_command_statistics { + __u16 code; /* 0x02, 0x04 */ + __u8 flags; + __u8 length; /* 0x30 */ + __u32 sizes[12]; +}hst_cmd_stat; + +typedef struct physical_command_statistics { + __u16 code; /* 0x06, 0x07 */ + __u8 flags; + __u8 length; /* 0x34 */ + __u32 sizes[13]; +}phy_cmd_stat; + +typedef struct misc_device_statistics { + __u16 code; /* 0x05 */ + __u8 flags; + __u8 length; /* 0x10 */ + __u32 disconnect, + pass_thru, + sg_commands, + stripe_boundary_crosses; +}msc_stats; + +/* Configuration Pages */ + +typedef struct controller_configuration { + __u16 code; /* 0x01 */ + __u8 flags; + __u8 length; /* 0x02 */ + __u8 intt:1, + sec:1, + csh:1, + key:1, + tmr:1, + srs:1, + nvr:1; + __u8 interrupt; +}coco; + +typedef struct controller_hardware_errors { + __u16 code; /* 0x02 */ + __u8 flags; + __u8 length; /* 0x02 */ + __u8 unused:1, + per:1; + __u8 interrupt; +}coher; + +typedef struct memory_map { + __u16 code; /* 0x03, 0x04 */ + __u8 flags; + __u8 length; /* 0x04 */ + __u32 memory_map; +}mema; + +typedef struct scsi_transfer { + __u16 code; /* 0x05 */ + __u8 flags; + __u8 length; /* 0x04 */ + __u8 offset, + period; + __u16 speed; +}scsitrans; + +typedef struct scsi_modes { + __u16 code; /* 0x06 */ + __u8 flags; + __u8 length; /* 0x02 */ + __u8 que:1, + cdis:1, + wtru:1, + dasd:1, + ncr:1, + awre:1; + __u8 reserved; +}scsimod; + +typedef struct host_bus { + __u16 code; /* 0x07 */ + __u8 flags; + __u8 length; /* 0x02 */ + __u8 speed:6, + pci:1, + eisa:1; + __u8 reserved; +}hobu; + +typedef struct scsi_bus { + __u16 code; /* 0x08 */ + __u8 flags; + __u8 length; /* 0x02 */ + __u8 speed:4, + res:1, + ext:1, + wide:1, + dif:1; + __u8 busnum; +}scbu; + +typedef struct board_type { + __u16 code; /* 0x09 */ + __u8 flags; + __u8 length; /* 0x04 */ + __u8 unused:1, + cmi:1, + dmi:1, + cm4k:1, + cm4:1, + dm4k:1, + dm4:1, + hba:1; + __u8 cpu_type, + cpu_speed; + __u8 sx1:1, + sx2:1, + unused2:4, + alrm:1, + srom:1; +}boty; + +typedef struct memory_config { + __u16 code; /* 0x0a */ + __u8 flags; + __u8 length; /* 0x04 */ + __u8 banksize[4]; +}memco; + +typedef struct firmware_info { + __u16 code; /* 0x0b */ + __u8 flags; + __u8 length; /* 0x04 */ + __u8 dnld:1, + bs528:1, + fmt:1, + fw528:1; + __u8 unused1, + fw_type, + unused; +}firm; + +typedef struct subsystem_info { + __u16 code; /* 0x0c */ + __u8 flags; + __u8 length; /* 0x02 */ + __u8 shlf:1, + swap:1, + noss:1; + __u8 reserved; +}subinf; + +typedef struct per_channel_info { + __u16 code; /* 0x0d */ + __u8 flags; + __u8 length; /* 0x02 */ + __u8 channel; + __u8 shlf:1, + swap:1, + noss:1, + srs:1, + que:1, + ext:1, + wide:1, + diff:1; +}pcinf; + +typedef struct array_limits { + __u16 code; /* 0x0e */ + __u8 flags; + __u8 length; /* 0x04 */ + __u8 max_groups, + raid0_drv, + raid35_drv, + unused; +}arrlim; + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ + diff --git a/linux/src/drivers/scsi/eata_generic.h b/linux/src/drivers/scsi/eata_generic.h new file mode 100644 index 00000000..c884def0 --- /dev/null +++ b/linux/src/drivers/scsi/eata_generic.h @@ -0,0 +1,414 @@ +/******************************************************** +* Header file for eata_dma.c and eata_pio.c * +* Linux EATA SCSI drivers * +* (c) 1993-96 Michael Neuffer * +* mike@i-Connect.Net * +* neuffer@mail.uni-mainz.de * +********************************************************* +* last change: 96/08/14 * +********************************************************/ + + +#ifndef _EATA_GENERIC_H +#define _EATA_GENERIC_H + + + +/********************************************* + * Misc. definitions * + *********************************************/ + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define min(a,b) ((a<b)?(a):(b)) + +#define R_LIMIT 0x20000 + +#define MAXISA 4 +#define MAXEISA 16 +#define MAXPCI 16 +#define MAXIRQ 16 +#define MAXTARGET 16 +#define MAXCHANNEL 3 + +#define IS_ISA 'I' +#define IS_EISA 'E' +#define IS_PCI 'P' + +#define BROKEN_INQUIRY 1 + +#define BUSMASTER 0xff +#define PIO 0xfe + +#define EATA_SIGNATURE 0x45415441 /* BIG ENDIAN coded "EATA" sig. */ + +#define DPT_ID1 0x12 +#define DPT_ID2 0x14 + +#define ATT_ID1 0x06 +#define ATT_ID2 0x94 +#define ATT_ID3 0x0 + +#define NEC_ID1 0x38 +#define NEC_ID2 0xa3 +#define NEC_ID3 0x82 + + +#define EATA_CP_SIZE 44 + +#define MAX_PCI_DEVICES 32 /* Maximum # Of Devices Per Bus */ +#define MAX_METHOD_2 16 /* Max Devices For Method 2 */ +#define MAX_PCI_BUS 16 /* Maximum # Of Busses Allowed */ + +#define SG_SIZE 64 +#define SG_SIZE_BIG 252 /* max. 8096 elements, 64k */ + +#define UPPER_DEVICE_QUEUE_LIMIT 64 /* The limit we have to set for the + * device queue to keep the broken + * midlevel SCSI code from producing + * bogus timeouts + */ + +#define TYPE_DISK_QUEUE 16 +#define TYPE_TAPE_QUEUE 4 +#define TYPE_ROM_QUEUE 4 +#define TYPE_OTHER_QUEUE 2 + +#define FREE 0 +#define OK 0 +#define NO_TIMEOUT 0 +#define USED 1 +#define TIMEOUT 2 +#define RESET 4 +#define LOCKED 8 +#define ABORTED 16 + +#define READ 0 +#define WRITE 1 +#define OTHER 2 + +#define HD(cmd) ((hostdata *)&(cmd->host->hostdata)) +#define CD(cmd) ((struct eata_ccb *)(cmd->host_scribble)) +#define SD(host) ((hostdata *)&(host->hostdata)) + +#define DELAY(x) { ulong flags, i; \ + save_flags(flags); sti(); \ + i = jiffies + (x * HZ); \ + while (jiffies < i); \ + restore_flags(flags); } + +/*********************************************** + * EATA Command & Register definitions * + ***********************************************/ +#define PCI_REG_DPTconfig 0x40 +#define PCI_REG_PumpModeAddress 0x44 +#define PCI_REG_PumpModeData 0x48 +#define PCI_REG_ConfigParam1 0x50 +#define PCI_REG_ConfigParam2 0x54 + + +#define EATA_CMD_PIO_SETUPTEST 0xc6 +#define EATA_CMD_PIO_READ_CONFIG 0xf0 +#define EATA_CMD_PIO_SET_CONFIG 0xf1 +#define EATA_CMD_PIO_SEND_CP 0xf2 +#define EATA_CMD_PIO_RECEIVE_SP 0xf3 +#define EATA_CMD_PIO_TRUNC 0xf4 + +#define EATA_CMD_RESET 0xf9 +#define EATA_CMD_IMMEDIATE 0xfa + +#define EATA_CMD_DMA_READ_CONFIG 0xfd +#define EATA_CMD_DMA_SET_CONFIG 0xfe +#define EATA_CMD_DMA_SEND_CP 0xff + +#define ECS_EMULATE_SENSE 0xd4 + +#define EATA_GENERIC_ABORT 0x00 +#define EATA_SPECIFIC_RESET 0x01 +#define EATA_BUS_RESET 0x02 +#define EATA_SPECIFIC_ABORT 0x03 +#define EATA_QUIET_INTR 0x04 +#define EATA_COLD_BOOT_HBA 0x06 /* Only as a last resort */ +#define EATA_FORCE_IO 0x07 + +#define HA_CTRLREG 0x206 /* control register for HBA */ +#define HA_CTRL_DISINT 0x02 /* CTRLREG: disable interrupts */ +#define HA_CTRL_RESCPU 0x04 /* CTRLREG: reset processor */ +#define HA_CTRL_8HEADS 0x08 /* CTRLREG: set for drives with* + * >=8 heads (WD1003 rudimentary :-) */ + +#define HA_WCOMMAND 0x07 /* command register offset */ +#define HA_WIFC 0x06 /* immediate command offset */ +#define HA_WCODE 0x05 +#define HA_WCODE2 0x04 +#define HA_WDMAADDR 0x02 /* DMA address LSB offset */ +#define HA_RAUXSTAT 0x08 /* aux status register offset*/ +#define HA_RSTATUS 0x07 /* status register offset */ +#define HA_RDATA 0x00 /* data register (16bit) */ +#define HA_WDATA 0x00 /* data register (16bit) */ + +#define HA_ABUSY 0x01 /* aux busy bit */ +#define HA_AIRQ 0x02 /* aux IRQ pending bit */ +#define HA_SERROR 0x01 /* pr. command ended in error*/ +#define HA_SMORE 0x02 /* more data soon to come */ +#define HA_SCORR 0x04 /* data corrected */ +#define HA_SDRQ 0x08 /* data request active */ +#define HA_SSC 0x10 /* seek complete */ +#define HA_SFAULT 0x20 /* write fault */ +#define HA_SREADY 0x40 /* drive ready */ +#define HA_SBUSY 0x80 /* drive busy */ +#define HA_SDRDY HA_SSC+HA_SREADY+HA_SDRQ + +/********************************************** + * Message definitions * + **********************************************/ + +#define HA_NO_ERROR 0x00 /* No Error */ +#define HA_ERR_SEL_TO 0x01 /* Selection Timeout */ +#define HA_ERR_CMD_TO 0x02 /* Command Timeout */ +#define HA_BUS_RESET 0x03 /* SCSI Bus Reset Received */ +#define HA_INIT_POWERUP 0x04 /* Initial Controller Power-up */ +#define HA_UNX_BUSPHASE 0x05 /* Unexpected Bus Phase */ +#define HA_UNX_BUS_FREE 0x06 /* Unexpected Bus Free */ +#define HA_BUS_PARITY 0x07 /* Bus Parity Error */ +#define HA_SCSI_HUNG 0x08 /* SCSI Hung */ +#define HA_UNX_MSGRJCT 0x09 /* Unexpected Message Rejected */ +#define HA_RESET_STUCK 0x0a /* SCSI Bus Reset Stuck */ +#define HA_RSENSE_FAIL 0x0b /* Auto Request-Sense Failed */ +#define HA_PARITY_ERR 0x0c /* Controller Ram Parity Error */ +#define HA_CP_ABORT_NA 0x0d /* Abort Message sent to non-active cmd */ +#define HA_CP_ABORTED 0x0e /* Abort Message sent to active cmd */ +#define HA_CP_RESET_NA 0x0f /* Reset Message sent to non-active cmd */ +#define HA_CP_RESET 0x10 /* Reset Message sent to active cmd */ +#define HA_ECC_ERR 0x11 /* Controller Ram ECC Error */ +#define HA_PCI_PARITY 0x12 /* PCI Parity Error */ +#define HA_PCI_MABORT 0x13 /* PCI Master Abort */ +#define HA_PCI_TABORT 0x14 /* PCI Target Abort */ +#define HA_PCI_STABORT 0x15 /* PCI Signaled Target Abort */ + +/********************************************** + * Other definitions * + **********************************************/ + +struct reg_bit { /* reading this one will clear the interrupt */ + __u8 error:1; /* previous command ended in an error */ + __u8 more:1; /* more DATA coming soon, poll BSY & DRQ (PIO) */ + __u8 corr:1; /* data read was successfully corrected with ECC*/ + __u8 drq:1; /* data request active */ + __u8 sc:1; /* seek complete */ + __u8 fault:1; /* write fault */ + __u8 ready:1; /* drive ready */ + __u8 busy:1; /* controller busy */ +}; + +struct reg_abit { /* reading this won't clear the interrupt */ + __u8 abusy:1; /* auxiliary busy */ + __u8 irq:1; /* set when drive interrupt is asserted */ + __u8 dummy:6; +}; + +struct eata_register { /* EATA register set */ + __u8 data_reg[2]; /* R, couldn't figure this one out */ + __u8 cp_addr[4]; /* W, CP address register */ + union { + __u8 command; /* W, command code: [read|set] conf, send CP*/ + struct reg_bit status; /* R, see register_bit1 */ + __u8 statusbyte; + } ovr; + struct reg_abit aux_stat; /* R, see register_bit2 */ +}; + +struct get_conf { /* Read Configuration Array */ + __u32 len; /* Should return 0x22, 0x24, etc */ + __u32 signature; /* Signature MUST be "EATA" */ + __u8 version2:4, + version:4; /* EATA Version level */ + __u8 OCS_enabled:1, /* Overlap Command Support enabled */ + TAR_support:1, /* SCSI Target Mode supported */ + TRNXFR:1, /* Truncate Transfer Cmd not necessary * + * Only used in PIO Mode */ + MORE_support:1, /* MORE supported (only PIO Mode) */ + DMA_support:1, /* DMA supported Driver uses only * + * this mode */ + DMA_valid:1, /* DRQ value in Byte 30 is valid */ + ATA:1, /* ATA device connected (not supported) */ + HAA_valid:1; /* Hostadapter Address is valid */ + + __u16 cppadlen; /* Number of pad bytes send after CD data * + * set to zero for DMA commands */ + __u8 scsi_id[4]; /* SCSI ID of controller 2-0 Byte 0 res. * + * if not, zero is returned */ + __u32 cplen; /* CP length: number of valid cp bytes */ + __u32 splen; /* Number of bytes returned after * + * Receive SP command */ + __u16 queuesiz; /* max number of queueable CPs */ + __u16 dummy; + __u16 SGsiz; /* max number of SG table entries */ + __u8 IRQ:4, /* IRQ used this HA */ + IRQ_TR:1, /* IRQ Trigger: 0=edge, 1=level */ + SECOND:1, /* This is a secondary controller */ + DMA_channel:2; /* DRQ index, DRQ is 2comp of DRQX */ + __u8 sync; /* device at ID 7 tru 0 is running in * + * synchronous mode, this will disappear */ + __u8 DSBLE:1, /* ISA i/o addressing is disabled */ + FORCADR:1, /* i/o address has been forced */ + SG_64K:1, + SG_UAE:1, + :4; + __u8 MAX_ID:5, /* Max number of SCSI target IDs */ + MAX_CHAN:3; /* Number of SCSI busses on HBA */ + __u8 MAX_LUN; /* Max number of LUNs */ + __u8 :3, + AUTOTRM:1, + M1_inst:1, + ID_qest:1, /* Raidnum ID is questionable */ + is_PCI:1, /* HBA is PCI */ + is_EISA:1; /* HBA is EISA */ + __u8 RAIDNUM; /* unique HBA identifier */ + __u8 unused[474]; +}; + +struct eata_sg_list +{ + __u32 data; + __u32 len; +}; + +struct eata_ccb { /* Send Command Packet structure */ + + __u8 SCSI_Reset:1, /* Cause a SCSI Bus reset on the cmd */ + HBA_Init:1, /* Cause Controller to reinitialize */ + Auto_Req_Sen:1, /* Do Auto Request Sense on errors */ + scatter:1, /* Data Ptr points to a SG Packet */ + Resrvd:1, /* RFU */ + Interpret:1, /* Interpret the SCSI cdb of own use */ + DataOut:1, /* Data Out phase with command */ + DataIn:1; /* Data In phase with command */ + __u8 reqlen; /* Request Sense Length * + * Valid if Auto_Req_Sen=1 */ + __u8 unused[3]; + __u8 FWNEST:1, /* send cmd to phys RAID component */ + unused2:7; + __u8 Phsunit:1, /* physical unit on mirrored pair */ + I_AT:1, /* inhibit address translation */ + I_HBA_C:1, /* HBA inhibit caching */ + unused3:5; + + __u8 cp_id:5, /* SCSI Device ID of target */ + cp_channel:3; /* SCSI Channel # of HBA */ + __u8 cp_lun:3, + :2, + cp_luntar:1, /* CP is for target ROUTINE */ + cp_dispri:1, /* Grant disconnect privilege */ + cp_identify:1; /* Always TRUE */ + __u8 cp_msg1; /* Message bytes 0-3 */ + __u8 cp_msg2; + __u8 cp_msg3; + __u8 cp_cdb[12]; /* Command Descriptor Block */ + __u32 cp_datalen; /* Data Transfer Length * + * If scatter=1 len of sg package */ + void *cp_viraddr; /* address of this ccb */ + __u32 cp_dataDMA; /* Data Address, if scatter=1 * + * address of scatter packet */ + __u32 cp_statDMA; /* address for Status Packet */ + __u32 cp_reqDMA; /* Request Sense Address, used if * + * CP command ends with error */ + /* Additional CP info begins here */ + __u32 timestamp; /* Needed to measure command latency */ + __u32 timeout; + __u8 sizeindex; + __u8 rw_latency; + __u8 retries; + __u8 status; /* status of this queueslot */ + Scsi_Cmnd *cmd; /* address of cmd */ + struct eata_sg_list *sg_list; +}; + + +struct eata_sp { + __u8 hba_stat:7, /* HBA status */ + EOC:1; /* True if command finished */ + __u8 scsi_stat; /* Target SCSI status */ + __u8 reserved[2]; + __u32 residue_len; /* Number of bytes not transferred */ + struct eata_ccb *ccb; /* Address set in COMMAND PACKET */ + __u8 msg[12]; +}; + +typedef struct hstd { + __u8 vendor[9]; + __u8 name[18]; + __u8 revision[6]; + __u8 EATA_revision; + __u32 firmware_revision; + __u8 HBA_number; + __u8 bustype; /* bustype of HBA */ + __u8 channel; /* # of avail. scsi channels */ + __u8 state; /* state of HBA */ + __u8 primary; /* true if primary */ + __u8 more_support:1, /* HBA supports MORE flag */ + immediate_support:1, /* HBA supports IMMEDIATE CMDs*/ + broken_INQUIRY:1; /* This is an EISA HBA with * + * broken INQUIRY */ + __u8 do_latency; /* Latency measurement flag */ + __u32 reads[13]; + __u32 writes[13]; + __u32 reads_lat[12][4]; + __u32 writes_lat[12][4]; + __u32 all_lat[4]; + __u8 resetlevel[MAXCHANNEL]; + __u32 last_ccb; /* Last used ccb */ + __u32 cplen; /* size of CP in words */ + __u16 cppadlen; /* pad length of cp in words */ + __u16 queuesize; + __u16 sgsize; /* # of entries in the SG list*/ + __u16 devflags; /* bits set for detected devices */ + __u8 hostid; /* SCSI ID of HBA */ + __u8 moresupport; /* HBA supports MORE flag */ + struct Scsi_Host *next; + struct Scsi_Host *prev; + struct eata_sp sp; /* status packet */ + struct eata_ccb ccb[0]; /* ccb array begins here */ +}hostdata; + +/* structure for max. 2 emulated drives */ +struct drive_geom_emul { + __u8 trans; /* translation flag 1=transl */ + __u8 channel; /* SCSI channel number */ + __u8 HBA; /* HBA number (prim/sec) */ + __u8 id; /* drive id */ + __u8 lun; /* drive lun */ + __u32 heads; /* number of heads */ + __u32 sectors; /* number of sectors */ + __u32 cylinder; /* number of cylinders */ +}; + +struct geom_emul { + __u8 bios_drives; /* number of emulated drives */ + struct drive_geom_emul drv[2]; /* drive structures */ +}; + +#endif /* _EATA_GENERIC_H */ + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/eata_pio.c b/linux/src/drivers/scsi/eata_pio.c new file mode 100644 index 00000000..90dc81f3 --- /dev/null +++ b/linux/src/drivers/scsi/eata_pio.c @@ -0,0 +1,1042 @@ +/************************************************************ + * * + * Linux EATA SCSI PIO driver * + * * + * based on the CAM document CAM/89-004 rev. 2.0c, * + * DPT's driver kit, some internal documents and source, * + * and several other Linux scsi drivers and kernel docs. * + * * + * The driver currently: * + * -supports all EATA-PIO boards * + * -only supports DASD devices * + * * + * (c)1993-96 Michael Neuffer, Alfred Arnold * + * neuffer@goofy.zdv.uni-mainz.de * + * a.arnold@kfa-juelich.de * + * * + * This program is free software; you can redistribute it * + * and/or modify it under the terms of the GNU General * + * Public License as published by the Free Software * + * Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be * + * useful, but WITHOUT ANY WARRANTY; without even the * + * implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License * + * for more details. * + * * + * You should have received a copy of the GNU General * + * Public License along with this kernel; if not, write to * + * the Free Software Foundation, Inc., 675 Mass Ave, * + * Cambridge, MA 02139, USA. * + * * + ************************************************************ + * last change: 96/07/16 OS: Linux 2.0.8 * + ************************************************************/ + +/* Look in eata_pio.h for configuration information */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/in.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <asm/io.h> +#include "eata_pio.h" +#include "eata_dma_proc.h" +#include "scsi.h" +#include "sd.h" + +#include <linux/stat.h> +#include <linux/config.h> /* for CONFIG_PCI */ + +struct proc_dir_entry proc_scsi_eata_pio = { + PROC_SCSI_EATA_PIO, 9, "eata_pio", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +static uint ISAbases[MAXISA] = +{0x1F0, 0x170, 0x330, 0x230}; +static uint ISAirqs[MAXISA] = +{14,12,15,11}; +static unchar EISAbases[] = +{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; +static uint registered_HBAs = 0; +static struct Scsi_Host *last_HBA = NULL; +static struct Scsi_Host *first_HBA = NULL; +static unchar reg_IRQ[] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +static unchar reg_IRQL[] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +static ulong int_counter = 0; +static ulong queue_counter = 0; + +#include "eata_pio_proc.c" + +#ifdef MODULE +int eata_pio_release(struct Scsi_Host *sh) +{ + if (sh->irq && reg_IRQ[sh->irq] == 1) free_irq(sh->irq, NULL); + else reg_IRQ[sh->irq]--; + if (SD(sh)->channel == 0) { + if (sh->io_port && sh->n_io_port) + release_region(sh->io_port, sh->n_io_port); + } + return(TRUE); +} +#endif + +void IncStat(Scsi_Pointer *SCp, uint Increment) +{ + SCp->ptr+=Increment; + if ((SCp->this_residual-=Increment)==0) + { + if ((--SCp->buffers_residual)==0) SCp->Status=FALSE; + else + { + SCp->buffer++; + SCp->ptr=SCp->buffer->address; + SCp->this_residual=SCp->buffer->length; + } + } +} + +void eata_pio_int_handler(int irq, void *dev_id, struct pt_regs * regs) +{ + uint eata_stat = 0xfffff; + Scsi_Cmnd *cmd; + hostdata *hd; + struct eata_ccb *cp; + uint base; + ulong flags; + uint x,z; + struct Scsi_Host *sh; + ushort zwickel=0; + unchar stat,odd; + + save_flags(flags); + cli(); + + for (x = 1, sh = first_HBA; x <= registered_HBAs; x++, sh = SD(sh)->prev) { + if (sh->irq != irq) + continue; + if (inb((uint)sh->base + HA_RSTATUS) & HA_SBUSY) + continue; + + int_counter++; + + hd=SD(sh); + + cp = &hd->ccb[0]; + cmd = cp->cmd; + base = (uint) cmd->host->base; + + do + { + stat=inb(base+HA_RSTATUS); + if (stat&HA_SDRQ) + if (cp->DataIn) + { + z=256; odd=FALSE; + while ((cmd->SCp.Status)&&((z>0)||(odd))) + { + if (odd) + { + *(cmd->SCp.ptr)=zwickel>>8; + IncStat(&cmd->SCp,1); + odd=FALSE; + } + x=min(z,cmd->SCp.this_residual/2); + insw(base+HA_RDATA,cmd->SCp.ptr,x); + z-=x; + IncStat(&cmd->SCp,2*x); + if ((z>0)&&(cmd->SCp.this_residual==1)) + { + zwickel=inw(base+HA_RDATA); + *(cmd->SCp.ptr)=zwickel&0xff; + IncStat(&cmd->SCp,1); z--; + odd=TRUE; + } + } + while (z>0) { + zwickel=inw(base+HA_RDATA); + z--; + } + } + else /* cp->DataOut */ + { + odd=FALSE; z=256; + while ((cmd->SCp.Status)&&((z>0)||(odd))) + { + if (odd) + { + zwickel+=*(cmd->SCp.ptr)<<8; + IncStat(&cmd->SCp,1); + outw(zwickel,base+HA_RDATA); + z--; + odd=FALSE; + } + x=min(z,cmd->SCp.this_residual/2); + outsw(base+HA_RDATA,cmd->SCp.ptr,x); + z-=x; + IncStat(&cmd->SCp,2*x); + if ((z>0)&&(cmd->SCp.this_residual==1)) + { + zwickel=*(cmd->SCp.ptr); + zwickel&=0xff; + IncStat(&cmd->SCp,1); + odd=TRUE; + } + } + while (z>0||odd) { + outw(zwickel,base+HA_RDATA); + z--; + odd=FALSE; + } + } + } + while ((stat&HA_SDRQ)||((stat&HA_SMORE)&&hd->moresupport)); + + /* terminate handler if HBA goes busy again, i.e. transfers + * more data */ + + if (stat&HA_SBUSY) break; + + /* OK, this is quite stupid, but I haven't found any correct + * way to get HBA&SCSI status so far */ + + if (!(inb(base+HA_RSTATUS)&HA_SERROR)) + { + cmd->result=(DID_OK<<16); + hd->devflags|=(1<<cp->cp_id); + } + else if (hd->devflags&1<<cp->cp_id) + cmd->result=(DID_OK<<16)+0x02; + else cmd->result=(DID_NO_CONNECT<<16); + + if (cp->status == LOCKED) { + cp->status = FREE; + eata_stat = inb(base + HA_RSTATUS); + printk(KERN_NOTICE "eata_pio: int_handler, freeing locked " + "queueslot\n"); + DBG(DBG_INTR&&DBG_DELAY,DELAY(1)); + restore_flags(flags); + return; + } + +#if DBG_INTR2 + if (stat != 0x50) + printk(KERN_DEBUG "stat: %#.2x, result: %#.8x\n", stat, + cmd->result); + DBG(DBG_INTR&&DBG_DELAY,DELAY(1)); +#endif + + cp->status = FREE; /* now we can release the slot */ + + restore_flags(flags); + cmd->scsi_done(cmd); + save_flags(flags); + cli(); + } + restore_flags(flags); + + return; +} + +inline uint eata_pio_send_command(uint base, unchar command) +{ + uint loop = HZ/2; + + while (inb(base + HA_RSTATUS) & HA_SBUSY) + if (--loop == 0) + return(TRUE); + + /* Enable interrupts for HBA. It is not the best way to do it at this + * place, but I hope that it doesn't interfere with the IDE driver + * initialization this way */ + + outb(HA_CTRL_8HEADS,base+HA_CTRLREG); + + outb(command, base + HA_WCOMMAND); + return(FALSE); +} + +int eata_pio_queue(Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *)) +{ + uint x, y; + long flags; + uint base; + + hostdata *hd; + struct Scsi_Host *sh; + struct eata_ccb *cp; + + save_flags(flags); + cli(); + + queue_counter++; + + hd = HD(cmd); + sh = cmd->host; + base = (uint) sh->base; + + /* use only slot 0, as 2001 can handle only one cmd at a time */ + + y = x = 0; + + if (hd->ccb[y].status!=FREE) { + + DBG(DBG_QUEUE, printk(KERN_EMERG "can_queue %d, x %d, y %d\n", + sh->can_queue,x,y)); +#if DEBUG_EATA + panic(KERN_EMERG "eata_pio: run out of queue slots cmdno:%ld " + "intrno: %ld\n", queue_counter, int_counter); +#else + panic(KERN_EMERG "eata_pio: run out of queue slots....\n"); +#endif + } + + cp = &hd->ccb[y]; + + memset(cp, 0, sizeof(struct eata_ccb)); + memset(cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); + + cp->status = USED; /* claim free slot */ + + DBG(DBG_QUEUE, printk(KERN_DEBUG "eata_pio_queue pid %ld, target: %x, lun:" + " %x, y %d\n", cmd->pid, cmd->target, cmd->lun, y)); + DBG(DBG_QUEUE && DBG_DELAY, DELAY(1)); + + cmd->scsi_done = (void *)done; + + switch (cmd->cmnd[0]) { + case CHANGE_DEFINITION: case COMPARE: case COPY: + case COPY_VERIFY: case LOG_SELECT: case MODE_SELECT: + case MODE_SELECT_10: case SEND_DIAGNOSTIC: case WRITE_BUFFER: + case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE: + case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW: + case WRITE_6: case WRITE_10: case WRITE_VERIFY: + case UPDATE_BLOCK: case WRITE_LONG: case WRITE_SAME: + case SEARCH_HIGH_12: case SEARCH_EQUAL_12: case SEARCH_LOW_12: + case WRITE_12: case WRITE_VERIFY_12: case SET_WINDOW: + case MEDIUM_SCAN: case SEND_VOLUME_TAG: + case 0xea: /* alternate number for WRITE LONG */ + cp->DataOut = TRUE; /* Output mode */ + break; + case TEST_UNIT_READY: + default: + cp->DataIn = TRUE; /* Input mode */ + } + + cp->Interpret = (cmd->target == hd->hostid); + cp->cp_datalen = htonl((ulong)cmd->request_bufflen); + cp->Auto_Req_Sen = FALSE; + cp->cp_reqDMA = htonl(0); + cp->reqlen = 0; + + cp->cp_id = cmd->target; + cp->cp_lun = cmd->lun; + cp->cp_dispri = FALSE; + cp->cp_identify = TRUE; + memcpy(cp->cp_cdb, cmd->cmnd, COMMAND_SIZE(*cmd->cmnd)); + + cp->cp_statDMA = htonl(0); + + cp->cp_viraddr = cp; + cp->cmd = cmd; + cmd->host_scribble = (char *)&hd->ccb[y]; + + if (cmd->use_sg == 0) + { + cmd->SCp.buffers_residual=1; + cmd->SCp.ptr = cmd->request_buffer; + cmd->SCp.this_residual = cmd->request_bufflen; + cmd->SCp.buffer = NULL; + } else { + cmd->SCp.buffer = cmd->request_buffer; + cmd->SCp.buffers_residual = cmd->use_sg; + cmd->SCp.ptr = cmd->SCp.buffer->address; + cmd->SCp.this_residual = cmd->SCp.buffer->length; + } + cmd->SCp.Status = (cmd->SCp.this_residual != 0); /* TRUE as long as bytes + * are to transfer */ + + if (eata_pio_send_command(base, EATA_CMD_PIO_SEND_CP)) + { + cmd->result = DID_BUS_BUSY << 16; + printk(KERN_NOTICE "eata_pio_queue target %d, pid %ld, HBA busy, " + "returning DID_BUS_BUSY, done.\n", cmd->target, cmd->pid); + done(cmd); + cp->status = FREE; + restore_flags(flags); + return (0); + } + while (!(inb(base + HA_RSTATUS) & HA_SDRQ)); + outsw(base + HA_RDATA, cp, hd->cplen); + outb(EATA_CMD_PIO_TRUNC, base + HA_WCOMMAND); + for (x = 0; x < hd->cppadlen; x++) outw(0, base + HA_RDATA); + + DBG(DBG_QUEUE,printk(KERN_DEBUG "Queued base %#.4lx pid: %ld target: %x " + "lun: %x slot %d irq %d\n", (long)sh->base, cmd->pid, + cmd->target, cmd->lun, y, sh->irq)); + DBG(DBG_QUEUE && DBG_DELAY, DELAY(1)); + + restore_flags(flags); + return (0); +} + +int eata_pio_abort(Scsi_Cmnd * cmd) +{ + ulong flags; + uint loop = HZ; + + save_flags(flags); + cli(); + + DBG(DBG_ABNORM, printk(KERN_WARNING "eata_pio_abort called pid: %ld " + "target: %x lun: %x reason %x\n", cmd->pid, + cmd->target, cmd->lun, cmd->abort_reason)); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + + while (inb((uint)(cmd->host->base) + HA_RAUXSTAT) & HA_ABUSY) + if (--loop == 0) { + printk(KERN_WARNING "eata_pio: abort, timeout error.\n"); + restore_flags(flags); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_ABORT_ERROR); + } + if (CD(cmd)->status == FREE) { + DBG(DBG_ABNORM, printk(KERN_WARNING "Returning: SCSI_ABORT_NOT_RUNNING\n")); + restore_flags(flags); + return (SCSI_ABORT_NOT_RUNNING); + } + if (CD(cmd)->status == USED) { + DBG(DBG_ABNORM, printk(KERN_WARNING "Returning: SCSI_ABORT_BUSY\n")); + restore_flags(flags); + return (SCSI_ABORT_BUSY); /* SNOOZE */ + } + if (CD(cmd)->status == RESET) { + restore_flags(flags); + printk(KERN_WARNING "eata_pio: abort, command reset error.\n"); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_ABORT_ERROR); + } + if (CD(cmd)->status == LOCKED) { + restore_flags(flags); + DBG(DBG_ABNORM, printk(KERN_WARNING "eata_pio: abort, queue slot " + "locked.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_ABORT_NOT_RUNNING); + } + restore_flags(flags); + panic("eata_pio: abort: invalid slot status\n"); +} + +int eata_pio_reset(Scsi_Cmnd * cmd, unsigned int dummy) +{ + uint x, time, limit = 0; + ulong flags; + unchar success = FALSE; + Scsi_Cmnd *sp; + + save_flags(flags); + cli(); + DBG(DBG_ABNORM, printk(KERN_WARNING "eata_pio_reset called pid:%ld target:" + " %x lun: %x reason %x\n", cmd->pid, cmd->target, + cmd->lun, cmd->abort_reason)); + + if (HD(cmd)->state == RESET) { + printk(KERN_WARNING "eata_pio_reset: exit, already in reset.\n"); + restore_flags(flags); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_ERROR); + } + + /* force all slots to be free */ + + for (x = 0; x < cmd->host->can_queue; x++) { + + if (HD(cmd)->ccb[x].status == FREE) + continue; + + sp = HD(cmd)->ccb[x].cmd; + HD(cmd)->ccb[x].status = RESET; + printk(KERN_WARNING "eata_pio_reset: slot %d in reset, pid %ld.\n", x, + sp->pid); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + if (sp == NULL) + panic("eata_pio_reset: slot %d, sp==NULL.\n", x); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + } + + /* hard reset the HBA */ + outb(EATA_CMD_RESET, (uint) cmd->host->base+HA_WCOMMAND); + + DBG(DBG_ABNORM, printk(KERN_WARNING "eata_pio_reset: board reset done.\n")); + HD(cmd)->state = RESET; + + time = jiffies; + while (jiffies < (time + (3 * HZ)) && limit++ < 10000000); + + DBG(DBG_ABNORM, printk(KERN_WARNING "eata_pio_reset: interrupts disabled, " + "loops %d.\n", limit)); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + + for (x = 0; x < cmd->host->can_queue; x++) { + + /* Skip slots already set free by interrupt */ + if (HD(cmd)->ccb[x].status != RESET) + continue; + + sp = HD(cmd)->ccb[x].cmd; + sp->result = DID_RESET << 16; + + /* This mailbox is terminated */ + printk(KERN_WARNING "eata_pio_reset: reset ccb %d.\n",x); + HD(cmd)->ccb[x].status = FREE; + + restore_flags(flags); + sp->scsi_done(sp); + cli(); + } + + HD(cmd)->state = FALSE; + restore_flags(flags); + + if (success) { /* hmmm... */ + DBG(DBG_ABNORM, printk(KERN_WARNING "eata_pio_reset: exit, success.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_SUCCESS); + } else { + DBG(DBG_ABNORM, printk(KERN_WARNING "eata_pio_reset: exit, wakeup.\n")); + DBG(DBG_ABNORM && DBG_DELAY, DELAY(1)); + return (SCSI_RESET_PUNT); + } +} + +char * get_pio_board_data(ulong base, uint irq, uint id, ulong cplen, ushort cppadlen) +{ + struct eata_ccb cp; + static char buff[256]; + int z; + + memset(&cp, 0, sizeof(struct eata_ccb)); + memset(buff, 0, sizeof(buff)); + + cp.DataIn = TRUE; + cp.Interpret = TRUE; /* Interpret command */ + + cp.cp_datalen = htonl(254); + cp.cp_dataDMA = htonl(0); + + cp.cp_id = id; + cp.cp_lun = 0; + + cp.cp_cdb[0] = INQUIRY; + cp.cp_cdb[1] = 0; + cp.cp_cdb[2] = 0; + cp.cp_cdb[3] = 0; + cp.cp_cdb[4] = 254; + cp.cp_cdb[5] = 0; + + if (eata_pio_send_command((uint) base, EATA_CMD_PIO_SEND_CP)) + return (NULL); + while (!(inb(base + HA_RSTATUS) & HA_SDRQ)); + outsw(base + HA_RDATA, &cp, cplen); + outb(EATA_CMD_PIO_TRUNC, base + HA_WCOMMAND); + for (z = 0; z < cppadlen; z++) outw(0, base + HA_RDATA); + + while (inb(base + HA_RSTATUS) & HA_SBUSY); + if (inb(base + HA_RSTATUS) & HA_SERROR) + return (NULL); + else if (!(inb(base + HA_RSTATUS) & HA_SDRQ)) + return (NULL); + else + { + insw(base+HA_RDATA, &buff, 127); + while (inb(base + HA_RSTATUS)&HA_SDRQ) inw(base + HA_RDATA); + return (buff); + } +} + +int get_pio_conf_PIO(u32 base, struct get_conf *buf) +{ + ulong loop = HZ/2; + int z; + ushort *p; + + if(check_region(base, 9)) + return (FALSE); + + memset(buf, 0, sizeof(struct get_conf)); + + while (inb(base + HA_RSTATUS) & HA_SBUSY) + if (--loop == 0) + return (FALSE); + + DBG(DBG_PIO && DBG_PROBE, + printk(KERN_DEBUG "Issuing PIO READ CONFIG to HBA at %#x\n", base)); + eata_pio_send_command(base, EATA_CMD_PIO_READ_CONFIG); + + loop = HZ/2; + for (p = (ushort *) buf; + (long)p <= ((long)buf + (sizeof(struct get_conf) / 2)); p++) { + while (!(inb(base + HA_RSTATUS) & HA_SDRQ)) + if (--loop == 0) + return (FALSE); + + loop = HZ/2; + *p = inw(base + HA_RDATA); + } + if (!(inb(base + HA_RSTATUS) & HA_SERROR)) { /* Error ? */ + if (htonl(EATA_SIGNATURE) == buf->signature) { + DBG(DBG_PIO&&DBG_PROBE, printk(KERN_NOTICE "EATA Controller found " + "at %#4x EATA Level: %x\n", base, + (uint) (buf->version))); + + while (inb(base + HA_RSTATUS) & HA_SDRQ) + inw(base + HA_RDATA); + if(ALLOW_DMA_BOARDS == FALSE) { + for (z = 0; z < MAXISA; z++) + if (base == ISAbases[z]) { + buf->IRQ = ISAirqs[z]; + break; + } + } + return (TRUE); + } + } else { + DBG(DBG_PROBE, printk("eata_dma: get_conf_PIO, error during transfer " + "for HBA at %x\n", base)); + } + return (FALSE); +} + +void print_pio_config(struct get_conf *gc) +{ + printk("Please check values: (read config data)\n"); + printk("LEN: %d ver:%d OCS:%d TAR:%d TRNXFR:%d MORES:%d\n", + (uint) ntohl(gc->len), gc->version, + gc->OCS_enabled, gc->TAR_support, gc->TRNXFR, gc->MORE_support); + printk("HAAV:%d SCSIID0:%d ID1:%d ID2:%d QUEUE:%d SG:%d SEC:%d\n", + gc->HAA_valid, gc->scsi_id[3], gc->scsi_id[2], + gc->scsi_id[1], ntohs(gc->queuesiz), ntohs(gc->SGsiz), gc->SECOND); + printk("IRQ:%d IRQT:%d FORCADR:%d MCH:%d RIDQ:%d\n", + gc->IRQ, gc->IRQ_TR, gc->FORCADR, + gc->MAX_CHAN, gc->ID_qest); + DBG(DPT_DEBUG, DELAY(14)); +} + +static uint print_selftest(uint base) +{ + unchar buffer[512]; +#ifdef VERBOSE_SETUP + int z; +#endif + + printk("eata_pio: executing controller self test & setup...\n"); + while (inb(base + HA_RSTATUS) & HA_SBUSY); + outb(EATA_CMD_PIO_SETUPTEST, base + HA_WCOMMAND); + do { + while (inb(base + HA_RSTATUS) & HA_SBUSY) + /* nothing */ ; + if (inb(base + HA_RSTATUS) & HA_SDRQ) + { + insw(base + HA_RDATA, &buffer, 256); +#ifdef VERBOSE_SETUP + /* no beeps please... */ + for (z = 0; z < 511 && buffer[z]; z++) + if (buffer[z] != 7) printk("%c", buffer[z]); +#endif + } + } while (inb(base+HA_RSTATUS) & (HA_SBUSY|HA_SDRQ)); + + return (!(inb(base+HA_RSTATUS) & HA_SERROR)); +} + +int register_pio_HBA(long base, struct get_conf *gc, Scsi_Host_Template * tpnt) +{ + ulong size = 0; + char *buff; + ulong cplen; + ushort cppadlen; + struct Scsi_Host *sh; + hostdata *hd; + + DBG(DBG_REGISTER, print_pio_config(gc)); + + if (gc->DMA_support == TRUE) { + printk("HBA at %#.4lx supports DMA. Please use EATA-DMA driver.\n",base); + if(ALLOW_DMA_BOARDS == FALSE) + return (FALSE); + } + + if ((buff = get_pio_board_data((uint)base, gc->IRQ, gc->scsi_id[3], + cplen =(htonl(gc->cplen )+1)/2, + cppadlen=(htons(gc->cppadlen)+1)/2)) == NULL) + { + printk("HBA at %#lx didn't react on INQUIRY. Sorry.\n", (ulong) base); + return (FALSE); + } + + if (print_selftest(base) == FALSE && ALLOW_DMA_BOARDS == FALSE) + { + printk("HBA at %#lx failed while performing self test & setup.\n", + (ulong) base); + return (FALSE); + } + + if (!reg_IRQ[gc->IRQ]) { /* Interrupt already registered ? */ + if (!request_irq(gc->IRQ, eata_pio_int_handler, SA_INTERRUPT, + "EATA-PIO", NULL)){ + reg_IRQ[gc->IRQ]++; + if (!gc->IRQ_TR) + reg_IRQL[gc->IRQ] = TRUE; /* IRQ is edge triggered */ + } else { + printk("Couldn't allocate IRQ %d, Sorry.\n", gc->IRQ); + return (FALSE); + } + } else { /* More than one HBA on this IRQ */ + if (reg_IRQL[gc->IRQ] == TRUE) { + printk("Can't support more than one HBA on this IRQ,\n" + " if the IRQ is edge triggered. Sorry.\n"); + return (FALSE); + } else + reg_IRQ[gc->IRQ]++; + } + + request_region(base, 8, "eata_pio"); + + size = sizeof(hostdata) + (sizeof(struct eata_ccb) * ntohs(gc->queuesiz)); + + sh = scsi_register(tpnt, size); + hd = SD(sh); + + memset(hd->ccb, 0, (sizeof(struct eata_ccb) * ntohs(gc->queuesiz))); + memset(hd->reads, 0, sizeof(ulong) * 26); + + strncpy(SD(sh)->vendor, &buff[8], 8); + SD(sh)->vendor[8] = 0; + strncpy(SD(sh)->name, &buff[16], 17); + SD(sh)->name[17] = 0; + SD(sh)->revision[0] = buff[32]; + SD(sh)->revision[1] = buff[33]; + SD(sh)->revision[2] = buff[34]; + SD(sh)->revision[3] = '.'; + SD(sh)->revision[4] = buff[35]; + SD(sh)->revision[5] = 0; + + switch (ntohl(gc->len)) { + case 0x1c: + SD(sh)->EATA_revision = 'a'; + break; + case 0x1e: + SD(sh)->EATA_revision = 'b'; + break; + case 0x22: + SD(sh)->EATA_revision = 'c'; + break; + case 0x24: + SD(sh)->EATA_revision = 'z'; + default: + SD(sh)->EATA_revision = '?'; + } + + if(ntohl(gc->len) >= 0x22) { + if (gc->is_PCI == TRUE) + hd->bustype = IS_PCI; + else if (gc->is_EISA == TRUE) + hd->bustype = IS_EISA; + else + hd->bustype = IS_ISA; + } else { + if (buff[21] == '4') + hd->bustype = IS_PCI; + else if (buff[21] == '2') + hd->bustype = IS_EISA; + else + hd->bustype = IS_ISA; + } + + SD(sh)->cplen=cplen; + SD(sh)->cppadlen=cppadlen; + SD(sh)->hostid=gc->scsi_id[3]; + SD(sh)->devflags=1<<gc->scsi_id[3]; + SD(sh)->moresupport=gc->MORE_support; + sh->unique_id = base; + sh->base = (char *) base; + sh->io_port = base; + sh->n_io_port = 8; + sh->irq = gc->IRQ; + sh->dma_channel = PIO; + sh->this_id = gc->scsi_id[3]; + sh->can_queue = 1; + sh->cmd_per_lun = 1; + sh->sg_tablesize = SG_ALL; + + hd->channel = 0; + + sh->max_id = 8; + sh->max_lun = 8; + + if (gc->SECOND) + hd->primary = FALSE; + else + hd->primary = TRUE; + + sh->unchecked_isa_dma = FALSE; /* We can only do PIO */ + + hd->next = NULL; /* build a linked list of all HBAs */ + hd->prev = last_HBA; + if(hd->prev != NULL) + SD(hd->prev)->next = sh; + last_HBA = sh; + if (first_HBA == NULL) + first_HBA = sh; + registered_HBAs++; + return (1); +} + +void find_pio_ISA(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + int i; + + for (i = 0; i < MAXISA; i++) { + if (ISAbases[i]) { + if (get_pio_conf_PIO(ISAbases[i], buf) == TRUE){ + register_pio_HBA(ISAbases[i], buf, tpnt); + } + ISAbases[i] = 0; + } + } + return; +} + +void find_pio_EISA(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + u32 base; + int i; + +#if CHECKPAL + u8 pal1, pal2, pal3; +#endif + + for (i = 0; i < MAXEISA; i++) { + if (EISAbases[i] == TRUE) { /* Still a possibility ? */ + + base = 0x1c88 + (i * 0x1000); +#if CHECKPAL + pal1 = inb((u16)base - 8); + pal2 = inb((u16)base - 7); + pal3 = inb((u16)base - 6); + + if (((pal1 == 0x12) && (pal2 == 0x14)) || + ((pal1 == 0x38) && (pal2 == 0xa3) && (pal3 == 0x82)) || + ((pal1 == 0x06) && (pal2 == 0x94) && (pal3 == 0x24))) { + DBG(DBG_PROBE, printk(KERN_NOTICE "EISA EATA id tags found: " + "%x %x %x \n", + (int)pal1, (int)pal2, (int)pal3)); +#endif + if (get_pio_conf_PIO(base, buf) == TRUE) { + DBG(DBG_PROBE && DBG_EISA, print_pio_config(buf)); + if (buf->IRQ) { + register_pio_HBA(base, buf, tpnt); + } else + printk(KERN_NOTICE "eata_dma: No valid IRQ. HBA " + "removed from list\n"); + } + /* Nothing found here so we take it from the list */ + EISAbases[i] = 0; +#if CHECKPAL + } +#endif + } + } + return; +} + +void find_pio_PCI(struct get_conf *buf, Scsi_Host_Template * tpnt) +{ + +#ifndef CONFIG_PCI + printk(KERN_ERR "eata_pio: kernel PCI support not enabled. Skipping scan " + "for PCI HBAs.\n"); +#else + + u8 pci_bus, pci_device_fn; + static s16 pci_index = 0; /* Device index to PCI BIOS calls */ + u32 base = 0; + u16 com_adr; + u16 rev_device; + u32 error, i, x; + + if (pcibios_present()) { + for (i = 0; i <= MAXPCI; ++i, ++pci_index) { + if (pcibios_find_device(PCI_VENDOR_ID_DPT, PCI_DEVICE_ID_DPT, + pci_index, &pci_bus, &pci_device_fn)) + break; + DBG(DBG_PROBE && DBG_PCI, + printk("eata_pio: HBA at bus %d, device %d," + " function %d, index %d\n", (s32)pci_bus, + (s32)((pci_device_fn & 0xf8) >> 3), + (s32)(pci_device_fn & 7), pci_index)); + + if (!(error = pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_CLASS_DEVICE, &rev_device))) { + if (rev_device == PCI_CLASS_STORAGE_SCSI) { + if (!(error = pcibios_read_config_word(pci_bus, + pci_device_fn, PCI_COMMAND, + (u16 *) & com_adr))) { + if (!((com_adr & PCI_COMMAND_IO) && + (com_adr & PCI_COMMAND_MASTER))) { + printk("HBA has IO or BUSMASTER mode disabled\n"); + continue; + } + } else + printk("eata_pio: error %x while reading " + "PCI_COMMAND\n", error); + } else + printk("DEVICECLASSID %x didn't match\n", rev_device); + } else { + printk("eata_pio: error %x while reading PCI_CLASS_BASE\n", + error); + continue; + } + + if (!(error = pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, (int *) &base))){ + + /* Check if the address is valid */ + if (base & 0x01) { + base &= 0xfffffffe; + /* EISA tag there ? */ + if ((inb(base) == 0x12) && (inb(base + 1) == 0x14)) + continue; /* Jep, it's forced, so move on */ + base += 0x10; /* Now, THIS is the real address */ + if (base != 0x1f8) { + /* We didn't find it in the primary search */ + if (get_pio_conf_PIO(base, buf) == TRUE) { + if (buf->FORCADR) /* If the address is forced */ + continue; /* we'll find it later */ + + /* OK. We made it till here, so we can go now + * and register it. We only have to check and + * eventually remove it from the EISA and ISA list + */ + + register_pio_HBA(base, buf, tpnt); + + if (base < 0x1000) { + for (x = 0; x < MAXISA; ++x) { + if (ISAbases[x] == base) { + ISAbases[x] = 0; + break; + } + } + } else if ((base & 0x0fff) == 0x0c88) { + x = (base >> 12) & 0x0f; + EISAbases[x] = 0; + } + continue; /* break; */ + } + } + } + } else + printk("eata_pio: error %x while reading " + "PCI_BASE_ADDRESS_0\n", error); + } + } else + printk("eata_pio: No BIOS32 extensions present. This driver release " + "still depends on it.\n" + " Skipping scan for PCI HBAs.\n"); +#endif /* #ifndef CONFIG_PCI */ + return; +} + + +int eata_pio_detect(Scsi_Host_Template * tpnt) +{ + struct Scsi_Host *HBA_ptr; + struct get_conf gc; + int i; + + DBG((DBG_PROBE && DBG_DELAY) || DPT_DEBUG, + printk("Using lots of delays to let you read the debugging output\n")); + + tpnt->proc_dir = &proc_scsi_eata_pio; + + find_pio_PCI(&gc, tpnt); + + find_pio_EISA(&gc, tpnt); + + find_pio_ISA(&gc, tpnt); + + for (i = 0; i <= MAXIRQ; i++) + if (reg_IRQ[i]) + request_irq(i, eata_pio_int_handler, SA_INTERRUPT, "EATA-PIO", NULL); + + HBA_ptr = first_HBA; + + if (registered_HBAs != 0) { + printk("EATA (Extended Attachment) PIO driver version: %d.%d%s\n" + "(c) 1993-95 Michael Neuffer, neuffer@goofy.zdv.uni-mainz.de\n" + " Alfred Arnold, a.arnold@kfa-juelich.de\n" + "This release only supports DASD devices (harddisks)\n", + VER_MAJOR, VER_MINOR, VER_SUB); + + printk("Registered HBAs:\n"); + printk("HBA no. Boardtype: Revis: EATA: Bus: BaseIO: IRQ: Ch: ID: Pr:" + " QS: SG: CPL:\n"); + for (i = 1; i <= registered_HBAs; i++) { + printk("scsi%-2d: %.10s v%s 2.0%c %s %#.4x %2d %d %d %c" + " %2d %2d %2d\n", + HBA_ptr->host_no, SD(HBA_ptr)->name, SD(HBA_ptr)->revision, + SD(HBA_ptr)->EATA_revision, (SD(HBA_ptr)->bustype == 'P')? + "PCI ":(SD(HBA_ptr)->bustype == 'E')?"EISA":"ISA ", + (uint) HBA_ptr->base, HBA_ptr->irq, SD(HBA_ptr)->channel, + HBA_ptr->this_id, (SD(HBA_ptr)->primary == TRUE)?'Y':'N', + HBA_ptr->can_queue, HBA_ptr->sg_tablesize, + HBA_ptr->cmd_per_lun); + HBA_ptr = SD(HBA_ptr)->next; + } + } + DBG(DPT_DEBUG,DELAY(12)); + + return (registered_HBAs); +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = EATA_PIO; + +#include "scsi_module.c" +#endif + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/eata_pio.h b/linux/src/drivers/scsi/eata_pio.h new file mode 100644 index 00000000..333f7c4a --- /dev/null +++ b/linux/src/drivers/scsi/eata_pio.h @@ -0,0 +1,116 @@ +/******************************************************** +* Header file for eata_pio.c Linux EATA-PIO SCSI driver * +* (c) 1993-96 Michael Neuffer * +********************************************************* +* last change: 96/05/05 * +********************************************************/ + + +#ifndef _EATA_PIO_H +#define _EATA_PIO_H + +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include <scsi/scsicam.h> + +#ifndef HOSTS_C +#include "eata_generic.h" + +#define VER_MAJOR 0 +#define VER_MINOR 0 +#define VER_SUB "1b" + +/************************************************************************ + * Here you can switch parts of the code on and of * + ************************************************************************/ + +#define VERBOSE_SETUP /* show startup screen of 2001 */ +#define ALLOW_DMA_BOARDS 1 + +/************************************************************************ + * Debug options. * + * Enable DEBUG and whichever options you require. * + ************************************************************************/ +#define DEBUG_EATA 1 /* Enable debug code. */ +#define DPT_DEBUG 0 /* Bobs special */ +#define DBG_DELAY 0 /* Build in delays so debug messages can be + * be read before they vanish of the top of + * the screen! + */ +#define DBG_PROBE 0 /* Debug probe routines. */ +#define DBG_ISA 0 /* Trace ISA routines */ +#define DBG_EISA 0 /* Trace EISA routines */ +#define DBG_PCI 0 /* Trace PCI routines */ +#define DBG_PIO 0 /* Trace get_config_PIO */ +#define DBG_COM 0 /* Trace command call */ +#define DBG_QUEUE 0 /* Trace command queueing. */ +#define DBG_INTR 0 /* Trace interrupt service routine. */ +#define DBG_INTR2 0 /* Trace interrupt service routine. */ +#define DBG_PROC 0 /* Debug proc-fs related statistics */ +#define DBG_PROC_WRITE 0 +#define DBG_REGISTER 0 /* */ +#define DBG_ABNORM 1 /* Debug abnormal actions (reset, abort) */ + +#if DEBUG_EATA +#define DBG(x, y) if ((x)) {y;} +#else +#define DBG(x, y) +#endif + +#endif /* !HOSTS_C */ + +int eata_pio_detect(Scsi_Host_Template *); +const char *eata_pio_info(struct Scsi_Host *); +int eata_pio_command(Scsi_Cmnd *); +int eata_pio_queue(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int eata_pio_abort(Scsi_Cmnd *); +int eata_pio_reset(Scsi_Cmnd *, unsigned int); +int eata_pio_proc_info(char *, char **, off_t, int, int, int); +#ifdef MODULE +int eata_pio_release(struct Scsi_Host *); +#else +#define eata_pio_release NULL +#endif + + +#define EATA_PIO { \ + NULL, NULL, \ + NULL, /* proc_dir_entry */ \ + eata_pio_proc_info, /* procinfo */ \ + "EATA (Extended Attachment) PIO driver", \ + eata_pio_detect, \ + eata_pio_release, \ + NULL, NULL, \ + eata_pio_queue, \ + eata_pio_abort, \ + eata_pio_reset, \ + NULL, /* Slave attach */ \ + scsicam_bios_param, \ + 0, /* Canqueue */ \ + 0, /* this_id */ \ + 0, /* sg_tablesize */ \ + 0, /* cmd_per_lun */ \ + 0, /* present */ \ + 1, /* True if ISA */ \ + ENABLE_CLUSTERING } + +#endif /* _EATA_PIO_H */ + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/eata_pio_proc.c b/linux/src/drivers/scsi/eata_pio_proc.c new file mode 100644 index 00000000..54783f2c --- /dev/null +++ b/linux/src/drivers/scsi/eata_pio_proc.c @@ -0,0 +1,135 @@ + +/* + * eata_set_info + * buffer : pointer to the data that has been written to the hostfile + * length : number of bytes written to the hostfile + * HBA_ptr: pointer to the Scsi_Host struct + */ +int eata_pio_set_info(char *buffer, int length, struct Scsi_Host *HBA_ptr) +{ + DBG(DBG_PROC_WRITE, printk("%s\n", buffer)); + return(-ENOSYS); /* Currently this is a no-op */ +} + +/* + * eata_proc_info + * inout : decides on the direction of the dataflow and the meaning of the + * variables + * buffer: If inout==FALSE data is being written to it else read from it + * *start: If inout==FALSE start of the valid data in the buffer + * offset: If inout==FALSE offset from the beginning of the imaginary file + * from which we start writing into the buffer + * length: If inout==FALSE max number of bytes to be written into the buffer + * else number of bytes in the buffer + */ +int eata_pio_proc_info(char *buffer, char **start, off_t offset, int length, + int hostno, int inout) +{ + + Scsi_Device *scd; + struct Scsi_Host *HBA_ptr; + static u8 buff[512]; + int i; + int size, len = 0; + off_t begin = 0; + off_t pos = 0; + + HBA_ptr = first_HBA; + for (i = 1; i <= registered_HBAs; i++) { + if (HBA_ptr->host_no == hostno) + break; + HBA_ptr = SD(HBA_ptr)->next; + } + + if(inout == TRUE) /* Has data been written to the file ? */ + return(eata_pio_set_info(buffer, length, HBA_ptr)); + + if (offset == 0) + memset(buff, 0, sizeof(buff)); + + size = sprintf(buffer+len, "EATA (Extended Attachment) PIO driver version: " + "%d.%d%s\n",VER_MAJOR, VER_MINOR, VER_SUB); + len += size; pos = begin + len; + size = sprintf(buffer + len, "queued commands: %10ld\n" + "processed interrupts:%10ld\n", queue_counter, int_counter); + len += size; pos = begin + len; + + size = sprintf(buffer + len, "\nscsi%-2d: HBA %.10s\n", + HBA_ptr->host_no, SD(HBA_ptr)->name); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Firmware revision: v%s\n", + SD(HBA_ptr)->revision); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "IO: PIO\n"); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Base IO : %#.4x\n", (u32) HBA_ptr->base); + len += size; + pos = begin + len; + size = sprintf(buffer + len, "Host Bus: %s\n", + (SD(HBA_ptr)->bustype == 'P')?"PCI ": + (SD(HBA_ptr)->bustype == 'E')?"EISA":"ISA "); + + len += size; + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + + scd = scsi_devices; + + size = sprintf(buffer+len,"Attached devices: %s\n", (scd)?"":"none"); + len += size; + pos = begin + len; + + while (scd) { + if (scd->host == HBA_ptr) { + proc_print_scsidevice(scd, buffer, &size, len); + len += size; + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + } + scd = scd->next; + } + + stop_output: + DBG(DBG_PROC, printk("2pos: %ld offset: %ld len: %d\n", pos, offset, len)); + *start=buffer+(offset-begin); /* Start of wanted data */ + len-=(offset-begin); /* Start slop */ + if(len>length) + len = length; /* Ending slop */ + DBG(DBG_PROC, printk("3pos: %ld offset: %ld len: %d\n", pos, offset, len)); + + return (len); +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * tab-width: 8 + * End: + */ + diff --git a/linux/src/drivers/scsi/fdomain.c b/linux/src/drivers/scsi/fdomain.c new file mode 100644 index 00000000..df71f5ce --- /dev/null +++ b/linux/src/drivers/scsi/fdomain.c @@ -0,0 +1,2082 @@ +/* fdomain.c -- Future Domain TMC-16x0 SCSI driver + * Created: Sun May 3 18:53:19 1992 by faith@cs.unc.edu + * Revised: Sat Nov 2 09:27:47 1996 by root@cs.unc.edu + * Author: Rickard E. Faith, faith@cs.unc.edu + * Copyright 1992, 1993, 1994, 1995, 1996 Rickard E. Faith + * + * $Id: fdomain.c,v 1.1 1999/04/26 05:54:32 tb Exp $ + + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + + ************************************************************************** + + SUMMARY: + + Future Domain BIOS versions supported for autodetect: + 2.0, 3.0, 3.2, 3.4 (1.0), 3.5 (2.0), 3.6, 3.61 + Chips are supported: + TMC-1800, TMC-18C50, TMC-18C30, TMC-36C70 + Boards supported: + Future Domain TMC-1650, TMC-1660, TMC-1670, TMC-1680, TMC-1610M/MER/MEX + Future Domain TMC-3260 (PCI) + Quantum ISA-200S, ISA-250MG + Adaptec AHA-2920 (PCI) + IBM ? + LILO command-line options: + fdomain=<PORT_BASE>,<IRQ>[,<ADAPTER_ID>] + + + + DESCRIPTION: + + This is the Linux low-level SCSI driver for Future Domain TMC-1660/1680 + TMC-1650/1670, and TMC-3260 SCSI host adapters. The 1650 and 1670 have a + 25-pin external connector, whereas the 1660 and 1680 have a SCSI-2 50-pin + high-density external connector. The 1670 and 1680 have floppy disk + controllers built in. The TMC-3260 is a PCI bus card. + + Future Domain's older boards are based on the TMC-1800 chip, and this + driver was originally written for a TMC-1680 board with the TMC-1800 chip. + More recently, boards are being produced with the TMC-18C50 and TMC-18C30 + chips. The latest and greatest board may not work with this driver. If + you have to patch this driver so that it will recognize your board's BIOS + signature, then the driver may fail to function after the board is + detected. + + Please note that the drive ordering that Future Domain implemented in BIOS + versions 3.4 and 3.5 is the opposite of the order (currently) used by the + rest of the SCSI industry. If you have BIOS version 3.4 or 3.5, and have + more then one drive, then the drive ordering will be the reverse of that + which you see under DOS. For example, under DOS SCSI ID 0 will be D: and + SCSI ID 1 will be C: (the boot device). Under Linux, SCSI ID 0 will be + /dev/sda and SCSI ID 1 will be /dev/sdb. The Linux ordering is consistent + with that provided by all the other SCSI drivers for Linux. If you want + this changed, you will probably have to patch the higher level SCSI code. + If you do so, please send me patches that are protected by #ifdefs. + + If you have a TMC-8xx or TMC-9xx board, then this is not the driver for + your board. Please refer to the Seagate driver for more information and + possible support. + + + + HISTORY: + + Linux Driver Driver + Version Version Date Support/Notes + + 0.0 3 May 1992 V2.0 BIOS; 1800 chip + 0.97 1.9 28 Jul 1992 + 0.98.6 3.1 27 Nov 1992 + 0.99 3.2 9 Dec 1992 + + 0.99.3 3.3 10 Jan 1993 V3.0 BIOS + 0.99.5 3.5 18 Feb 1993 + 0.99.10 3.6 15 May 1993 V3.2 BIOS; 18C50 chip + 0.99.11 3.17 3 Jul 1993 (now under RCS) + 0.99.12 3.18 13 Aug 1993 + 0.99.14 5.6 31 Oct 1993 (reselection code removed) + + 0.99.15 5.9 23 Jan 1994 V3.4 BIOS (preliminary) + 1.0.8/1.1.1 5.15 1 Apr 1994 V3.4 BIOS; 18C30 chip (preliminary) + 1.0.9/1.1.3 5.16 7 Apr 1994 V3.4 BIOS; 18C30 chip + 1.1.38 5.18 30 Jul 1994 36C70 chip (PCI version of 18C30) + 1.1.62 5.20 2 Nov 1994 V3.5 BIOS + 1.1.73 5.22 7 Dec 1994 Quantum ISA-200S board; V2.0 BIOS + + 1.1.82 5.26 14 Jan 1995 V3.5 BIOS; TMC-1610M/MER/MEX board + 1.2.10 5.28 5 Jun 1995 Quantum ISA-250MG board; V2.0, V2.01 BIOS + 1.3.4 5.31 23 Jun 1995 PCI BIOS-32 detection (preliminary) + 1.3.7 5.33 4 Jul 1995 PCI BIOS-32 detection + 1.3.28 5.36 17 Sep 1995 V3.61 BIOS; LILO command-line support + 1.3.34 5.39 12 Oct 1995 V3.60 BIOS; /proc + 1.3.72 5.39 8 Feb 1996 Adaptec AHA-2920 board + 1.3.85 5.41 4 Apr 1996 + 2.0.12 5.44 8 Aug 1996 Use ID 7 for all PCI cards + + + + REFERENCES USED: + + "TMC-1800 SCSI Chip Specification (FDC-1800T)", Future Domain Corporation, + 1990. + + "Technical Reference Manual: 18C50 SCSI Host Adapter Chip", Future Domain + Corporation, January 1992. + + "LXT SCSI Products: Specifications and OEM Technical Manual (Revision + B/September 1991)", Maxtor Corporation, 1991. + + "7213S product Manual (Revision P3)", Maxtor Corporation, 1992. + + "Draft Proposed American National Standard: Small Computer System + Interface - 2 (SCSI-2)", Global Engineering Documents. (X3T9.2/86-109, + revision 10h, October 17, 1991) + + Private communications, Drew Eckhardt (drew@cs.colorado.edu) and Eric + Youngdale (ericy@cais.com), 1992. + + Private communication, Tuong Le (Future Domain Engineering department), + 1994. (Disk geometry computations for Future Domain BIOS version 3.4, and + TMC-18C30 detection.) + + Hogan, Thom. The Programmer's PC Sourcebook. Microsoft Press, 1988. Page + 60 (2.39: Disk Partition Table Layout). + + "18C30 Technical Reference Manual", Future Domain Corporation, 1993, page + 6-1. + + + + NOTES ON REFERENCES: + + The Maxtor manuals were free. Maxtor telephone technical support is + great! + + The Future Domain manuals were $25 and $35. They document the chip, not + the TMC-16x0 boards, so some information I had to guess at. In 1992, + Future Domain sold DOS BIOS source for $250 and the UN*X driver source was + $750, but these required a non-disclosure agreement, so even if I could + have afforded them, they would *not* have been useful for writing this + publically distributable driver. Future Domain technical support has + provided some information on the phone and have sent a few useful FAXs. + They have been much more helpful since they started to recognize that the + word "Linux" refers to an operating system :-). + + + + ALPHA TESTERS: + + There are many other alpha testers that come and go as the driver + develops. The people listed here were most helpful in times of greatest + need (mostly early on -- I've probably left out a few worthy people in + more recent times): + + Todd Carrico (todd@wutc.wustl.edu), Dan Poirier (poirier@cs.unc.edu ), Ken + Corey (kenc@sol.acs.unt.edu), C. de Bruin (bruin@bruin@sterbbs.nl), Sakari + Aaltonen (sakaria@vipunen.hit.fi), John Rice (rice@xanth.cs.odu.edu), Brad + Yearwood (brad@optilink.com), and Ray Toy (toy@soho.crd.ge.com). + + Special thanks to Tien-Wan Yang (twyang@cs.uh.edu), who graciously lent me + his 18C50-based card for debugging. He is the sole reason that this + driver works with the 18C50 chip. + + Thanks to Dave Newman (dnewman@crl.com) for providing initial patches for + the version 3.4 BIOS. + + Thanks to James T. McKinley (mckinley@msupa.pa.msu.edu) for providing + patches that support the TMC-3260, a PCI bus card with the 36C70 chip. + The 36C70 chip appears to be "completely compatible" with the 18C30 chip. + + Thanks to Eric Kasten (tigger@petroglyph.cl.msu.edu) for providing the + patch for the version 3.5 BIOS. + + Thanks for Stephen Henson (shenson@nyx10.cs.du.edu) for providing the + patch for the Quantum ISA-200S SCSI adapter. + + Thanks to Adam Bowen for the signature to the 1610M/MER/MEX scsi cards, to + Martin Andrews (andrewm@ccfadm.eeg.ccf.org) for the signature to some + random TMC-1680 repackaged by IBM; and to Mintak Ng (mintak@panix.com) for + the version 3.61 BIOS signature. + + Thanks for Mark Singer (elf@netcom.com) and Richard Simpson + (rsimpson@ewrcsdra.demon.co.uk) for more Quantum signatures and detective + work on the Quantum RAM layout. + + Special thanks to James T. McKinley (mckinley@msupa.pa.msu.edu) for + providing patches for proper PCI BIOS32-mediated detection of the TMC-3260 + card (a PCI bus card with the 36C70 chip). Please send James PCI-related + bug reports. + + Thanks to Tom Cavin (tec@usa1.com) for preliminary command-line option + patches. + + All of the alpha testers deserve much thanks. + + + + NOTES ON USER DEFINABLE OPTIONS: + + DEBUG: This turns on the printing of various debug information. + + ENABLE_PARITY: This turns on SCSI parity checking. With the current + driver, all attached devices must support SCSI parity. If none of your + devices support parity, then you can probably get the driver to work by + turning this option off. I have no way of testing this, however, and it + would appear that no one ever uses this option. + + FIFO_COUNT: The host adapter has an 8K cache (host adapters based on the + 18C30 chip have a 2k cache). When this many 512 byte blocks are filled by + the SCSI device, an interrupt will be raised. Therefore, this could be as + low as 0, or as high as 16. Note, however, that values which are too high + or too low seem to prevent any interrupts from occurring, and thereby lock + up the machine. I have found that 2 is a good number, but throughput may + be increased by changing this value to values which are close to 2. + Please let me know if you try any different values. + + DO_DETECT: This activates some old scan code which was needed before the + high level drivers got fixed. If you are having trouble with the driver, + turning this on should not hurt, and might help. Please let me know if + this is the case, since this code will be removed from future drivers. + + RESELECTION: This is no longer an option, since I gave up trying to + implement it in version 4.x of this driver. It did not improve + performance at all and made the driver unstable (because I never found one + of the two race conditions which were introduced by the multiple + outstanding command code). The instability seems a very high price to pay + just so that you don't have to wait for the tape to rewind. If you want + this feature implemented, send me patches. I'll be happy to send a copy + of my (broken) driver to anyone who would like to see a copy. + + **************************************************************************/ + +#ifdef PCMCIA +#define MODULE +#endif + +#ifdef MODULE +#include <linux/module.h> +#endif + +#ifdef PCMCIA +#undef MODULE +#endif + +#include <linux/sched.h> +#include <asm/io.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "fdomain.h" +#include <asm/system.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/stat.h> + +#include <linux/config.h> /* for CONFIG_PCI */ + +struct proc_dir_entry proc_scsi_fdomain = { + PROC_SCSI_FDOMAIN, 7, "fdomain", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#define VERSION "$Revision: 1.1 $" + +/* START OF USER DEFINABLE OPTIONS */ + +#define DEBUG 1 /* Enable debugging output */ +#define ENABLE_PARITY 1 /* Enable SCSI Parity */ +#define FIFO_COUNT 2 /* Number of 512 byte blocks before INTR */ +#define DO_DETECT 0 /* Do device detection here (see scsi.c) */ + +/* END OF USER DEFINABLE OPTIONS */ + +#if DEBUG +#define EVERY_ACCESS 0 /* Write a line on every scsi access */ +#define ERRORS_ONLY 1 /* Only write a line if there is an error */ +#define DEBUG_DETECT 0 /* Debug fdomain_16x0_detect() */ +#define DEBUG_MESSAGES 1 /* Debug MESSAGE IN phase */ +#define DEBUG_ABORT 1 /* Debug abort() routine */ +#define DEBUG_RESET 1 /* Debug reset() routine */ +#define DEBUG_RACE 1 /* Debug interrupt-driven race condition */ +#else +#define EVERY_ACCESS 0 /* LEAVE THESE ALONE--CHANGE THE ONES ABOVE */ +#define ERRORS_ONLY 0 +#define DEBUG_DETECT 0 +#define DEBUG_MESSAGES 0 +#define DEBUG_ABORT 0 +#define DEBUG_RESET 0 +#define DEBUG_RACE 0 +#endif + +/* Errors are reported on the line, so we don't need to report them again */ +#if EVERY_ACCESS +#undef ERRORS_ONLY +#define ERRORS_ONLY 0 +#endif + +#if ENABLE_PARITY +#define PARITY_MASK 0x08 +#else +#define PARITY_MASK 0x00 +#endif + +enum chip_type { + unknown = 0x00, + tmc1800 = 0x01, + tmc18c50 = 0x02, + tmc18c30 = 0x03, +}; + +enum { + in_arbitration = 0x02, + in_selection = 0x04, + in_other = 0x08, + disconnect = 0x10, + aborted = 0x20, + sent_ident = 0x40, +}; + +enum in_port_type { + Read_SCSI_Data = 0, + SCSI_Status = 1, + TMC_Status = 2, + FIFO_Status = 3, /* tmc18c50/tmc18c30 only */ + Interrupt_Cond = 4, /* tmc18c50/tmc18c30 only */ + LSB_ID_Code = 5, + MSB_ID_Code = 6, + Read_Loopback = 7, + SCSI_Data_NoACK = 8, + Interrupt_Status = 9, + Configuration1 = 10, + Configuration2 = 11, /* tmc18c50/tmc18c30 only */ + Read_FIFO = 12, + FIFO_Data_Count = 14 +}; + +enum out_port_type { + Write_SCSI_Data = 0, + SCSI_Cntl = 1, + Interrupt_Cntl = 2, + SCSI_Mode_Cntl = 3, + TMC_Cntl = 4, + Memory_Cntl = 5, /* tmc18c50/tmc18c30 only */ + Write_Loopback = 7, + IO_Control = 11, /* tmc18c30 only */ + Write_FIFO = 12 +}; + +static int port_base = 0; +static void *bios_base = NULL; +static int bios_major = 0; +static int bios_minor = 0; +static int PCI_bus = 0; +static int Quantum = 0; /* Quantum board variant */ +static int interrupt_level = 0; +static volatile int in_command = 0; +static Scsi_Cmnd *current_SC = NULL; +static enum chip_type chip = unknown; +static int adapter_mask = 0; +static int this_id = 0; +static int setup_called = 0; + +#if DEBUG_RACE +static volatile int in_interrupt_flag = 0; +#endif + +static int SCSI_Mode_Cntl_port; +static int FIFO_Data_Count_port; +static int Interrupt_Cntl_port; +static int Interrupt_Status_port; +static int Read_FIFO_port; +static int Read_SCSI_Data_port; +static int SCSI_Cntl_port; +static int SCSI_Data_NoACK_port; +static int SCSI_Status_port; +static int TMC_Cntl_port; +static int TMC_Status_port; +static int Write_FIFO_port; +static int Write_SCSI_Data_port; + +static int FIFO_Size = 0x2000; /* 8k FIFO for + pre-tmc18c30 chips */ + +extern void fdomain_16x0_intr( int irq, void *dev_id, struct pt_regs * regs ); + +static void *addresses[] = { + (void *)0xc8000, + (void *)0xca000, + (void *)0xce000, + (void *)0xde000, + (void *)0xcc000, /* Extra addresses for PCI boards */ + (void *)0xd0000, + (void *)0xe0000, +}; +#define ADDRESS_COUNT (sizeof( addresses ) / sizeof( unsigned )) + +static unsigned short ports[] = { 0x140, 0x150, 0x160, 0x170 }; +#define PORT_COUNT (sizeof( ports ) / sizeof( unsigned short )) + +static unsigned short ints[] = { 3, 5, 10, 11, 12, 14, 15, 0 }; + +/* + + READ THIS BEFORE YOU ADD A SIGNATURE! + + READING THIS SHORT NOTE CAN SAVE YOU LOTS OF TIME! + + READ EVERY WORD, ESPECIALLY THE WORD *NOT* + + This driver works *ONLY* for Future Domain cards using the TMC-1800, + TMC-18C50, or TMC-18C30 chip. This includes models TMC-1650, 1660, 1670, + and 1680. + + The following BIOS signature signatures are for boards which do *NOT* + work with this driver (these TMC-8xx and TMC-9xx boards may work with the + Seagate driver): + + FUTURE DOMAIN CORP. (C) 1986-1988 V4.0I 03/16/88 + FUTURE DOMAIN CORP. (C) 1986-1989 V5.0C2/14/89 + FUTURE DOMAIN CORP. (C) 1986-1989 V6.0A7/28/89 + FUTURE DOMAIN CORP. (C) 1986-1990 V6.0105/31/90 + FUTURE DOMAIN CORP. (C) 1986-1990 V6.0209/18/90 + FUTURE DOMAIN CORP. (C) 1986-1990 V7.009/18/90 + FUTURE DOMAIN CORP. (C) 1992 V8.00.004/02/92 + +*/ + +struct signature { + const char *signature; + int sig_offset; + int sig_length; + int major_bios_version; + int minor_bios_version; + int flag; /* 1 == PCI_bus, 2 == ISA_200S, 3 == ISA_250MG, 4 == ISA_200S */ +} signatures[] = { + /* 1 2 3 4 5 6 */ + /* 123456789012345678901234567890123456789012345678901234567890 */ + { "FUTURE DOMAIN CORP. (C) 1986-1990 1800-V2.07/28/89", 5, 50, 2, 0, 0 }, + { "FUTURE DOMAIN CORP. (C) 1986-1990 1800-V1.07/28/89", 5, 50, 2, 0, 0 }, + { "FUTURE DOMAIN CORP. (C) 1986-1990 1800-V2.07/28/89", 72, 50, 2, 0, 2 }, + { "FUTURE DOMAIN CORP. (C) 1986-1990 1800-V2.0", 73, 43, 2, 0, 3 }, + { "FUTURE DOMAIN CORP. (C) 1991 1800-V2.0.", 72, 39, 2, 0, 4 }, + { "FUTURE DOMAIN CORP. (C) 1992 V3.00.004/02/92", 5, 44, 3, 0, 0 }, + { "FUTURE DOMAIN TMC-18XX (C) 1993 V3.203/12/93", 5, 44, 3, 2, 0 }, + { "IBM F1 P2 BIOS v1.0104/29/93", 5, 28, 3, -1, 0 }, + { "Future Domain Corp. V1.0008/18/93", 5, 33, 3, 4, 0 }, + { "Future Domain Corp. V1.0008/18/93", 26, 33, 3, 4, 1 }, + { "Adaptec AHA-2920 PCI-SCSI Card", 42, 31, 3, -1, 1 }, + { "IBM F1 P264/32", 5, 14, 3, -1, 1 }, + /* This next signature may not be a 3.5 bios */ + { "Future Domain Corp. V2.0108/18/93", 5, 33, 3, 5, 0 }, + { "FUTURE DOMAIN CORP. V3.5008/18/93", 5, 34, 3, 5, 0 }, + { "FUTURE DOMAIN 18c30/18c50/1800 (C) 1994 V3.5", 5, 44, 3, 5, 0 }, + { "FUTURE DOMAIN CORP. V3.6008/18/93", 5, 34, 3, 6, 0 }, + { "FUTURE DOMAIN CORP. V3.6108/18/93", 5, 34, 3, 6, 0 }, + { "FUTURE DOMAIN TMC-18XX", 5, 22, -1, -1, 0 }, + + /* READ NOTICE ABOVE *BEFORE* YOU WASTE YOUR TIME ADDING A SIGNATURE + Also, fix the disk geometry code for your signature and send your + changes for faith@cs.unc.edu. Above all, do *NOT* change any old + signatures! + + Note that the last line will match a "generic" 18XX bios. Because + Future Domain has changed the host SCSI ID and/or the location of the + geometry information in the on-board RAM area for each of the first + three BIOS's, it is still important to enter a fully qualified + signature in the table for any new BIOS's (after the host SCSI ID and + geometry location are verified). */ +}; + +#define SIGNATURE_COUNT (sizeof( signatures ) / sizeof( struct signature )) + +static void print_banner( struct Scsi_Host *shpnt ) +{ + if (!shpnt) return; /* This won't ever happen */ + + if (bios_major < 0 && bios_minor < 0) { + printk( "scsi%d <fdomain>: No BIOS; using scsi id %d\n", + shpnt->host_no, shpnt->this_id ); + } else { + printk( "scsi%d <fdomain>: BIOS version ", shpnt->host_no ); + + if (bios_major >= 0) printk( "%d.", bios_major ); + else printk( "?." ); + + if (bios_minor >= 0) printk( "%d", bios_minor ); + else printk( "?." ); + + printk( " at 0x%x using scsi id %d\n", + (unsigned)bios_base, shpnt->this_id ); + } + + /* If this driver works for later FD PCI + boards, we will have to modify banner + for additional PCI cards, but for now if + it's PCI it's a TMC-3260 - JTM */ + printk( "scsi%d <fdomain>: %s chip at 0x%x irq ", + shpnt->host_no, + chip == tmc1800 ? "TMC-1800" + : (chip == tmc18c50 ? "TMC-18C50" + : (chip == tmc18c30 ? + (PCI_bus ? "TMC-36C70 (PCI bus)" : "TMC-18C30") + : "Unknown")), + port_base ); + + if (interrupt_level) printk( "%d", interrupt_level ); + else printk( "<none>" ); + + printk( "\n" ); +} + +void fdomain_setup( char *str, int *ints ) +{ + if (setup_called++ || ints[0] < 2 || ints[0] > 3) { + printk( "fdomain: usage: fdomain=<PORT_BASE>,<IRQ>[,<ADAPTER_ID>]\n" ); + printk( "fdomain: bad LILO parameters?\n" ); + } + + port_base = ints[0] >= 1 ? ints[1] : 0; + interrupt_level = ints[0] >= 2 ? ints[2] : 0; + this_id = ints[0] >= 3 ? ints[3] : 0; + + bios_major = bios_minor = -1; /* Use geometry for BIOS version >= 3.4 */ +} + + +static void do_pause( unsigned amount ) /* Pause for amount*10 milliseconds */ +{ + unsigned long the_time = jiffies + amount; /* 0.01 seconds per jiffy */ + + while (jiffies < the_time); +} + +inline static void fdomain_make_bus_idle( void ) +{ + outb( 0, SCSI_Cntl_port ); + outb( 0, SCSI_Mode_Cntl_port ); + if (chip == tmc18c50 || chip == tmc18c30) + outb( 0x21 | PARITY_MASK, TMC_Cntl_port ); /* Clear forced intr. */ + else + outb( 0x01 | PARITY_MASK, TMC_Cntl_port ); +} + +static int fdomain_is_valid_port( int port ) +{ +#if DEBUG_DETECT + printk( " (%x%x),", + inb( port + MSB_ID_Code ), inb( port + LSB_ID_Code ) ); +#endif + + /* The MCA ID is a unique id for each MCA compatible board. We + are using ISA boards, but Future Domain provides the MCA ID + anyway. We can use this ID to ensure that this is a Future + Domain TMC-1660/TMC-1680. + */ + + if (inb( port + LSB_ID_Code ) != 0xe9) { /* test for 0x6127 id */ + if (inb( port + LSB_ID_Code ) != 0x27) return 0; + if (inb( port + MSB_ID_Code ) != 0x61) return 0; + chip = tmc1800; + } else { /* test for 0xe960 id */ + if (inb( port + MSB_ID_Code ) != 0x60) return 0; + chip = tmc18c50; + +#if 0 + + /* Try to toggle 32-bit mode. This only + works on an 18c30 chip. (User reports + say this works, so we should switch to + it in the near future.) */ + + outb( 0x80, port + IO_Control ); + if ((inb( port + Configuration2 ) & 0x80) == 0x80) { + outb( 0x00, port + IO_Control ); + if ((inb( port + Configuration2 ) & 0x80) == 0x00) { + chip = tmc18c30; + FIFO_Size = 0x800; /* 2k FIFO */ + } + } +#else + + /* That should have worked, but appears to + have problems. Let's assume it is an + 18c30 if the RAM is disabled. */ + + if (inb( port + Configuration2 ) & 0x02) { + chip = tmc18c30; + FIFO_Size = 0x800; /* 2k FIFO */ + } +#endif + /* If that failed, we are an 18c50. */ + } + + return 1; +} + +static int fdomain_test_loopback( void ) +{ + int i; + int result; + + for (i = 0; i < 255; i++) { + outb( i, port_base + Write_Loopback ); + result = inb( port_base + Read_Loopback ); + if (i != result) + return 1; + } + return 0; +} + +/* fdomain_get_irq assumes that we have a valid MCA ID for a + TMC-1660/TMC-1680 Future Domain board. Now, check to be sure the + bios_base matches these ports. If someone was unlucky enough to have + purchased more than one Future Domain board, then they will have to + modify this code, as we only detect one board here. [The one with the + lowest bios_base.] + + Note that this routine is only used for systems without a PCI BIOS32 + (e.g., ISA bus). For PCI bus systems, this routine will likely fail + unless one of the IRQs listed in the ints array is used by the board. + Sometimes it is possible to use the computer's BIOS setup screen to + configure a PCI system so that one of these IRQs will be used by the + Future Domain card. */ + +static int fdomain_get_irq( int base ) +{ + int options = inb( base + Configuration1 ); + +#if DEBUG_DETECT + printk( " Options = %x\n", options ); +#endif + + /* Check for board with lowest bios_base -- + this isn't valid for the 18c30 or for + boards on the PCI bus, so just assume we + have the right board. */ + + if (chip != tmc18c30 + && !PCI_bus + && addresses[ (options & 0xc0) >> 6 ] != bios_base) return 0; + + return ints[ (options & 0x0e) >> 1 ]; +} + +static int fdomain_isa_detect( int *irq, int *iobase ) +{ + int i; + int base; + int flag = 0; + + if (bios_major == 2) { + /* The TMC-1660/TMC-1680 has a RAM area just after the BIOS ROM. + Assuming the ROM is enabled (otherwise we wouldn't have been + able to read the ROM signature :-), then the ROM sets up the + RAM area with some magic numbers, such as a list of port + base addresses and a list of the disk "geometry" reported to + DOS (this geometry has nothing to do with physical geometry). + */ + + switch (Quantum) { + case 2: /* ISA_200S */ + case 3: /* ISA_250MG */ + base = *((char *)bios_base + 0x1fa2) + + (*((char *)bios_base + 0x1fa3) << 8); + break; + case 4: /* ISA_200S (another one) */ + base = *((char *)bios_base + 0x1fa3) + + (*((char *)bios_base + 0x1fa4) << 8); + break; + default: + base = *((char *)bios_base + 0x1fcc) + + (*((char *)bios_base + 0x1fcd) << 8); + break; + } + +#if DEBUG_DETECT + printk( " %x,", base ); +#endif + + for (flag = 0, i = 0; !flag && i < PORT_COUNT; i++) { + if (base == ports[i]) + ++flag; + } + + if (flag && fdomain_is_valid_port( base )) { + *irq = fdomain_get_irq( base ); + *iobase = base; + return 1; + } + + /* This is a bad sign. It usually means that someone patched the + BIOS signature list (the signatures variable) to contain a BIOS + signature for a board *OTHER THAN* the TMC-1660/TMC-1680. */ + +#if DEBUG_DETECT + printk( " RAM FAILED, " ); +#endif + } + + /* Anyway, the alternative to finding the address in the RAM is to just + search through every possible port address for one that is attached + to the Future Domain card. Don't panic, though, about reading all + these random port addresses -- there are rumors that the Future + Domain BIOS does something very similar. + + Do not, however, check ports which the kernel knows are being used by + another driver. */ + + for (i = 0; i < PORT_COUNT; i++) { + base = ports[i]; + if (check_region( base, 0x10 )) { +#if DEBUG_DETECT + printk( " (%x inuse),", base ); +#endif + continue; + } +#if DEBUG_DETECT + printk( " %x,", base ); +#endif + if ((flag = fdomain_is_valid_port( base ))) break; + } + + if (!flag) return 0; /* iobase not found */ + + *irq = fdomain_get_irq( base ); + *iobase = base; + + return 1; /* success */ +} + +static int fdomain_pci_nobios_detect( int *irq, int *iobase ) +{ + int i; + int flag = 0; + + /* The proper way of doing this is to use ask the PCI bus for the device + IRQ and interrupt level. But we can't do that if PCI BIOS32 support + isn't compiled into the kernel, or if a PCI BIOS32 isn't present. + + Instead, we scan down a bunch of addresses (Future Domain tech + support says we will probably find the address before we get to + 0xf800). This works fine on some systems -- other systems may have + to scan more addresses. If you have to modify this section for your + installation, please send mail to faith@cs.unc.edu. */ + + for (i = 0xfff8; i > 0xe000; i -= 8) { + if (check_region( i, 0x10 )) { +#if DEBUG_DETECT + printk( " (%x inuse)," , i ); +#endif + continue; + } + if ((flag = fdomain_is_valid_port( i ))) break; + } + + if (!flag) return 0; /* iobase not found */ + + *irq = fdomain_get_irq( i ); + *iobase = i; + + return 1; /* success */ +} + +/* PCI detection function: int fdomain_pci_bios_detect(int* irq, int* + iobase) This function gets the Interrupt Level and I/O base address from + the PCI configuration registers. The I/O base address is masked with + 0xfff8 since on my card the address read from the PCI config registers + is off by one from the actual I/O base address necessary for accessing + the status and control registers on the card (PCI config register gives + 0xf801, actual address is 0xf800). This is likely a bug in the FD + config code that writes to the PCI registers, however using a mask + should be safe since I think the scan done by the card to determine the + I/O base is done in increments of 8 (i.e., 0xf800, 0xf808, ...), at + least the old scan code we used to use to get the I/O base did... Also, + the device ID from the PCI config registers is 0x0 and should be 0x60e9 + as it is in the status registers (offset 5 from I/O base). If this is + changed in future hardware/BIOS changes it will need to be fixed in this + detection function. Comments, bug reports, etc... on this function + should be sent to mckinley@msupa.pa.msu.edu - James T. McKinley. */ + +#ifdef CONFIG_PCI +static int fdomain_pci_bios_detect( int *irq, int *iobase ) +{ + int error; + unsigned char pci_bus, pci_dev_fn; /* PCI bus & device function */ + unsigned char pci_irq; /* PCI interrupt line */ + unsigned int pci_base; /* PCI I/O base address */ + unsigned short pci_vendor, pci_device; /* PCI vendor & device IDs */ + + /* If the PCI BIOS doesn't exist, use the old-style detection routines. + Otherwise, get the I/O base address and interrupt from the PCI config + registers. */ + + if (!pcibios_present()) return fdomain_pci_nobios_detect( irq, iobase ); + +#if DEBUG_DETECT + /* Tell how to print a list of the known PCI devices from bios32 and + list vendor and device IDs being used if in debug mode. */ + + printk( "\nINFO: cat /proc/pci to see list of PCI devices from bios32\n" ); + printk( "\nTMC-3260 detect:" + " Using PCI Vendor ID: 0x%x, PCI Device ID: 0x%x\n", + PCI_VENDOR_ID_FD, + PCI_DEVICE_ID_FD_36C70 ); +#endif + + /* We will have to change this if more than 1 PCI bus is present and the + FD scsi host is not on the first bus (i.e., a PCI to PCI bridge, + which is not supported by bios32 right now anyway). This should + probably be done by a call to pcibios_find_device but I can't get it + to work... Also the device ID reported from the PCI config registers + does not match the device ID quoted in the tech manual or available + from offset 5 from the I/O base address. It should be 0x60E9, but it + is 0x0 if read from the PCI config registers. I guess the FD folks + neglected to write it to the PCI registers... This loop is necessary + to get the device function (at least until someone can get + pcibios_find_device to work, I cannot but 53c7,8xx.c uses it...). */ + + pci_bus = 0; + + for (pci_dev_fn = 0x0; pci_dev_fn < 0xff; pci_dev_fn++) { + pcibios_read_config_word( pci_bus, + pci_dev_fn, + PCI_VENDOR_ID, + &pci_vendor ); + + if (pci_vendor == PCI_VENDOR_ID_FD) { + pcibios_read_config_word( pci_bus, + pci_dev_fn, + PCI_DEVICE_ID, + &pci_device ); + + if (pci_device == PCI_DEVICE_ID_FD_36C70) { + /* Break out once we have the correct device. If other FD + PCI devices are added to this driver we will need to add + an or of the other PCI_DEVICE_ID_FD_XXXXX's here. */ + break; + } else { + /* If we can't find an FD scsi card we give up. */ + return 0; + } + } + } + +#if DEBUG_DETECT + printk( "Future Domain 36C70 : at PCI bus %u, device %u, function %u\n", + pci_bus, + (pci_dev_fn & 0xf8) >> 3, + pci_dev_fn & 7 ); +#endif + + /* We now have the appropriate device function for the FD board so we + just read the PCI config info from the registers. */ + + if ((error = pcibios_read_config_dword( pci_bus, + pci_dev_fn, + PCI_BASE_ADDRESS_0, + &pci_base )) + || (error = pcibios_read_config_byte( pci_bus, + pci_dev_fn, + PCI_INTERRUPT_LINE, + &pci_irq ))) { + printk ( "PCI ERROR: Future Domain 36C70 not initializing" + " due to error reading configuration space\n" ); + return 0; + } else { +#if DEBUG_DETECT + printk( "TMC-3260 PCI: IRQ = %u, I/O base = 0x%lx\n", + pci_irq, pci_base ); +#endif + + /* Now we have the I/O base address and interrupt from the PCI + configuration registers. Unfortunately it seems that the I/O base + address is off by one on my card so I mask it with 0xfff8. This + must be some kind of goof in the FD code that does the autoconfig + and writes to the PCI registers (or maybe I just don't understand + something). If they fix it in later versions of the card or BIOS + we may have to adjust the address based on the signature or + something... */ + + *irq = pci_irq; + *iobase = (pci_base & 0xfff8); + +#if DEBUG_DETECT + printk( "TMC-3260 fix: Masking I/O base address with 0xff00.\n" ); + printk( "TMC-3260: IRQ = %d, I/O base = 0x%x\n", *irq, *iobase ); +#endif + + if (!fdomain_is_valid_port( *iobase )) return 0; + return 1; + } + return 0; +} +#endif + +int fdomain_16x0_detect( Scsi_Host_Template *tpnt ) +{ + int i, j; + int retcode; + struct Scsi_Host *shpnt; +#if DO_DETECT + const int buflen = 255; + Scsi_Cmnd SCinit; + unsigned char do_inquiry[] = { INQUIRY, 0, 0, 0, buflen, 0 }; + unsigned char do_request_sense[] = { REQUEST_SENSE, 0, 0, 0, buflen, 0 }; + unsigned char do_read_capacity[] = { READ_CAPACITY, + 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + unsigned char buf[buflen]; +#endif + +#if DEBUG_DETECT + printk( "fdomain_16x0_detect()," ); +#endif + tpnt->proc_dir = &proc_scsi_fdomain; + + if (setup_called) { +#if DEBUG_DETECT + printk( "no BIOS, using port_base = 0x%x, irq = %d\n", + port_base, interrupt_level ); +#endif + if (!fdomain_is_valid_port( port_base )) { + printk( "fdomain: cannot locate chip at port base 0x%x\n", + port_base ); + printk( "fdomain: bad LILO parameters?\n" ); + return 0; + } + } else { + int flag = 0; + + for (i = 0; !bios_base && i < ADDRESS_COUNT; i++) { +#if DEBUG_DETECT + printk( " %x(%x),", (unsigned)addresses[i], (unsigned)bios_base ); +#endif + for (j = 0; !bios_base && j < SIGNATURE_COUNT; j++) { + if (!memcmp( ((char *)addresses[i] + signatures[j].sig_offset), + signatures[j].signature, signatures[j].sig_length )) { + bios_major = signatures[j].major_bios_version; + bios_minor = signatures[j].minor_bios_version; + PCI_bus = (signatures[j].flag == 1); + Quantum = (signatures[j].flag > 1) ? signatures[j].flag : 0; + bios_base = addresses[i]; + } + } + } + + if (!bios_base) { +#if DEBUG_DETECT + printk( " FAILED: NO BIOS\n" ); +#endif + return 0; + } + + if (!PCI_bus) { + flag = fdomain_isa_detect( &interrupt_level, &port_base ); + } else { +#ifdef CONFIG_PCI + flag = fdomain_pci_bios_detect( &interrupt_level, &port_base ); +#else + flag = fdomain_pci_nobios_detect( &interrupt_level, &port_base ); +#endif + } + + if (!flag) { +#if DEBUG_DETECT + printk( " FAILED: NO PORT\n" ); +#endif +#ifdef CONFIG_PCI + printk( "\nTMC-3260 36C70 PCI scsi chip detection failed.\n" ); + printk( "Send mail to mckinley@msupa.pa.msu.edu.\n" ); +#endif + return 0; /* Cannot find valid set of ports */ + } + } + + SCSI_Mode_Cntl_port = port_base + SCSI_Mode_Cntl; + FIFO_Data_Count_port = port_base + FIFO_Data_Count; + Interrupt_Cntl_port = port_base + Interrupt_Cntl; + Interrupt_Status_port = port_base + Interrupt_Status; + Read_FIFO_port = port_base + Read_FIFO; + Read_SCSI_Data_port = port_base + Read_SCSI_Data; + SCSI_Cntl_port = port_base + SCSI_Cntl; + SCSI_Data_NoACK_port = port_base + SCSI_Data_NoACK; + SCSI_Status_port = port_base + SCSI_Status; + TMC_Cntl_port = port_base + TMC_Cntl; + TMC_Status_port = port_base + TMC_Status; + Write_FIFO_port = port_base + Write_FIFO; + Write_SCSI_Data_port = port_base + Write_SCSI_Data; + + fdomain_16x0_reset( NULL, 0 ); + + if (fdomain_test_loopback()) { +#if DEBUG_DETECT + printk( "fdomain: LOOPBACK TEST FAILED, FAILING DETECT!\n" ); +#endif + if (setup_called) { + printk( "fdomain: loopback test failed at port base 0x%x\n", + port_base ); + printk( "fdomain: bad LILO parameters?\n" ); + } + return 0; + } + + if (this_id) { + tpnt->this_id = (this_id & 0x07); + adapter_mask = (1 << tpnt->this_id); + } else { + if (PCI_bus || (bios_major == 3 && bios_minor >= 2) || bios_major < 0) { + tpnt->this_id = 7; + adapter_mask = 0x80; + } else { + tpnt->this_id = 6; + adapter_mask = 0x40; + } + } + + /* Print out a banner here in case we can't + get resources. */ + + shpnt = scsi_register( tpnt, 0 ); + shpnt->irq = interrupt_level; + shpnt->io_port = port_base; + shpnt->n_io_port = 0x10; + print_banner( shpnt ); + + /* Log IRQ with kernel */ + if (!interrupt_level) { + panic( "fdomain: *NO* interrupt level selected!\n" ); + } else { + /* Register the IRQ with the kernel */ + + retcode = request_irq( interrupt_level, + fdomain_16x0_intr, SA_INTERRUPT, "fdomain", NULL); + + if (retcode < 0) { + if (retcode == -EINVAL) { + printk( "fdomain: IRQ %d is bad!\n", interrupt_level ); + printk( " This shouldn't happen!\n" ); + printk( " Send mail to faith@cs.unc.edu\n" ); + } else if (retcode == -EBUSY) { + printk( "fdomain: IRQ %d is already in use!\n", interrupt_level ); + printk( " Please use another IRQ!\n" ); + } else { + printk( "fdomain: Error getting IRQ %d\n", interrupt_level ); + printk( " This shouldn't happen!\n" ); + printk( " Send mail to faith@cs.unc.edu\n" ); + } + panic( "fdomain: Driver requires interruptions\n" ); + } + } + + /* Log I/O ports with kernel */ + request_region( port_base, 0x10, "fdomain" ); + +#if DO_DETECT + + /* These routines are here because of the way the SCSI bus behaves after + a reset. This appropriate behavior was not handled correctly by the + higher level SCSI routines when I first wrote this driver. Now, + however, correct scan routines are part of scsi.c and these routines + are no longer needed. However, this code is still good for + debugging. */ + + SCinit.request_buffer = SCinit.buffer = buf; + SCinit.request_bufflen = SCinit.bufflen = sizeof(buf)-1; + SCinit.use_sg = 0; + SCinit.lun = 0; + + printk( "fdomain: detection routine scanning for devices:\n" ); + for (i = 0; i < 8; i++) { + SCinit.target = i; + if (i == tpnt->this_id) /* Skip host adapter */ + continue; + memcpy(SCinit.cmnd, do_request_sense, sizeof(do_request_sense)); + retcode = fdomain_16x0_command(&SCinit); + if (!retcode) { + memcpy(SCinit.cmnd, do_inquiry, sizeof(do_inquiry)); + retcode = fdomain_16x0_command(&SCinit); + if (!retcode) { + printk( " SCSI ID %d: ", i ); + for (j = 8; j < (buf[4] < 32 ? buf[4] : 32); j++) + printk( "%c", buf[j] >= 20 ? buf[j] : ' ' ); + memcpy(SCinit.cmnd, do_read_capacity, sizeof(do_read_capacity)); + retcode = fdomain_16x0_command(&SCinit); + if (!retcode) { + unsigned long blocks, size, capacity; + + blocks = (buf[0] << 24) | (buf[1] << 16) + | (buf[2] << 8) | buf[3]; + size = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + capacity = +( +(blocks / 1024L) * +(size * 10L)) / 1024L; + + printk( "%lu MB (%lu byte blocks)", + ((capacity + 5L) / 10L), size ); + } else { + memcpy(SCinit.cmnd, do_request_sense, sizeof(do_request_sense)); + retcode = fdomain_16x0_command(&SCinit); + } + printk ("\n" ); + } else { + memcpy(SCinit.cmnd, do_request_sense, sizeof(do_request_sense)); + retcode = fdomain_16x0_command(&SCinit); + } + } + } +#endif + + return 1; /* Maximum of one adapter will be detected. */ +} + +const char *fdomain_16x0_info( struct Scsi_Host *ignore ) +{ + static char buffer[80]; + char *pt; + + strcpy( buffer, "Future Domain TMC-16x0 SCSI driver, version" ); + if (strchr( VERSION, ':')) { /* Assume VERSION is an RCS Revision string */ + strcat( buffer, strchr( VERSION, ':' ) + 1 ); + pt = strrchr( buffer, '$') - 1; + if (!pt) /* Stripped RCS Revision string? */ + pt = buffer + strlen( buffer ) - 1; + if (*pt != ' ') + ++pt; + *pt = '\0'; + } else { /* Assume VERSION is a number */ + strcat( buffer, " " VERSION ); + } + + return buffer; +} + + /* First pass at /proc information routine. */ +/* + * inout : decides on the direction of the dataflow and the meaning of the + * variables + * buffer: If inout==FALSE data is being written to it else read from it + * *start: If inout==FALSE start of the valid data in the buffer + * offset: If inout==FALSE offset from the beginning of the imaginary file + * from which we start writing into the buffer + * length: If inout==FALSE max number of bytes to be written into the buffer + * else number of bytes in the buffer + */ +int fdomain_16x0_proc_info( char *buffer, char **start, off_t offset, + int length, int hostno, int inout ) +{ + const char *info = fdomain_16x0_info( NULL ); + int len; + int pos; + int begin; + + if (inout) return(-ENOSYS); + + begin = 0; + strcpy( buffer, info ); + strcat( buffer, "\n" ); + + pos = len = strlen( buffer ); + + if(pos < offset) { + len = 0; + begin = pos; + } + + *start = buffer + (offset - begin); /* Start of wanted data */ + len -= (offset - begin); + if(len > length) len = length; + + return(len); +} + +#if 0 +static int fdomain_arbitrate( void ) +{ + int status = 0; + unsigned long timeout; + +#if EVERY_ACCESS + printk( "fdomain_arbitrate()\n" ); +#endif + + outb( 0x00, SCSI_Cntl_port ); /* Disable data drivers */ + outb( adapter_mask, port_base + SCSI_Data_NoACK ); /* Set our id bit */ + outb( 0x04 | PARITY_MASK, TMC_Cntl_port ); /* Start arbitration */ + + timeout = jiffies + 50; /* 500 mS */ + while (jiffies < timeout) { + status = inb( TMC_Status_port ); /* Read adapter status */ + if (status & 0x02) /* Arbitration complete */ + return 0; + } + + /* Make bus idle */ + fdomain_make_bus_idle(); + +#if EVERY_ACCESS + printk( "Arbitration failed, status = %x\n", status ); +#endif +#if ERRORS_ONLY + printk( "fdomain: Arbitration failed, status = %x\n", status ); +#endif + return 1; +} +#endif + +static int fdomain_select( int target ) +{ + int status; + unsigned long timeout; + static int flag = 0; + + + outb( 0x82, SCSI_Cntl_port ); /* Bus Enable + Select */ + outb( adapter_mask | (1 << target), SCSI_Data_NoACK_port ); + + /* Stop arbitration and enable parity */ + outb( PARITY_MASK, TMC_Cntl_port ); + + timeout = jiffies + 35; /* 350mS -- because of timeouts + (was 250mS) */ + + while (jiffies < timeout) { + status = inb( SCSI_Status_port ); /* Read adapter status */ + if (status & 1) { /* Busy asserted */ + /* Enable SCSI Bus (on error, should make bus idle with 0) */ + outb( 0x80, SCSI_Cntl_port ); + return 0; + } + } + /* Make bus idle */ + fdomain_make_bus_idle(); +#if EVERY_ACCESS + if (!target) printk( "Selection failed\n" ); +#endif +#if ERRORS_ONLY + if (!target) { + if (!flag) /* Skip first failure for all chips. */ + ++flag; + else + printk( "fdomain: Selection failed\n" ); + } +#endif + return 1; +} + +void my_done( int error ) +{ + if (in_command) { + in_command = 0; + outb( 0x00, Interrupt_Cntl_port ); + fdomain_make_bus_idle(); + current_SC->result = error; + if (current_SC->scsi_done) + current_SC->scsi_done( current_SC ); + else panic( "fdomain: current_SC->scsi_done() == NULL" ); + } else { + panic( "fdomain: my_done() called outside of command\n" ); + } +#if DEBUG_RACE + in_interrupt_flag = 0; +#endif +} + +void fdomain_16x0_intr( int irq, void *dev_id, struct pt_regs * regs ) +{ + int status; + int done = 0; + unsigned data_count; + + /* The fdomain_16x0_intr is only called via + the interrupt handler. The goal of the + sti() here is to allow other + interruptions while this routine is + running. */ + + sti(); /* Yes, we really want sti() here */ + + outb( 0x00, Interrupt_Cntl_port ); + + /* We usually have one spurious interrupt after each command. Ignore it. */ + if (!in_command || !current_SC) { /* Spurious interrupt */ +#if EVERY_ACCESS + printk( "Spurious interrupt, in_command = %d, current_SC = %x\n", + in_command, current_SC ); +#endif + return; + } + + /* Abort calls my_done, so we do nothing here. */ + if (current_SC->SCp.phase & aborted) { +#if DEBUG_ABORT + printk( "Interrupt after abort, ignoring\n" ); +#endif + /* + return; */ + } + +#if DEBUG_RACE + ++in_interrupt_flag; +#endif + + if (current_SC->SCp.phase & in_arbitration) { + status = inb( TMC_Status_port ); /* Read adapter status */ + if (!(status & 0x02)) { +#if EVERY_ACCESS + printk( " AFAIL " ); +#endif + my_done( DID_BUS_BUSY << 16 ); + return; + } + current_SC->SCp.phase = in_selection; + + outb( 0x40 | FIFO_COUNT, Interrupt_Cntl_port ); + + outb( 0x82, SCSI_Cntl_port ); /* Bus Enable + Select */ + outb( adapter_mask | (1 << current_SC->target), SCSI_Data_NoACK_port ); + + /* Stop arbitration and enable parity */ + outb( 0x10 | PARITY_MASK, TMC_Cntl_port ); +#if DEBUG_RACE + in_interrupt_flag = 0; +#endif + return; + } else if (current_SC->SCp.phase & in_selection) { + status = inb( SCSI_Status_port ); + if (!(status & 0x01)) { + /* Try again, for slow devices */ + if (fdomain_select( current_SC->target )) { +#if EVERY_ACCESS + printk( " SFAIL " ); +#endif + my_done( DID_NO_CONNECT << 16 ); + return; + } else { +#if EVERY_ACCESS + printk( " AltSel " ); +#endif + /* Stop arbitration and enable parity */ + outb( 0x10 | PARITY_MASK, TMC_Cntl_port ); + } + } + current_SC->SCp.phase = in_other; + outb( 0x90 | FIFO_COUNT, Interrupt_Cntl_port ); + outb( 0x80, SCSI_Cntl_port ); +#if DEBUG_RACE + in_interrupt_flag = 0; +#endif + return; + } + + /* current_SC->SCp.phase == in_other: this is the body of the routine */ + + status = inb( SCSI_Status_port ); + + if (status & 0x10) { /* REQ */ + + switch (status & 0x0e) { + + case 0x08: /* COMMAND OUT */ + outb( current_SC->cmnd[current_SC->SCp.sent_command++], + Write_SCSI_Data_port ); +#if EVERY_ACCESS + printk( "CMD = %x,", + current_SC->cmnd[ current_SC->SCp.sent_command - 1] ); +#endif + break; + case 0x00: /* DATA OUT -- tmc18c50/tmc18c30 only */ + if (chip != tmc1800 && !current_SC->SCp.have_data_in) { + current_SC->SCp.have_data_in = -1; + outb( 0xd0 | PARITY_MASK, TMC_Cntl_port ); + } + break; + case 0x04: /* DATA IN -- tmc18c50/tmc18c30 only */ + if (chip != tmc1800 && !current_SC->SCp.have_data_in) { + current_SC->SCp.have_data_in = 1; + outb( 0x90 | PARITY_MASK, TMC_Cntl_port ); + } + break; + case 0x0c: /* STATUS IN */ + current_SC->SCp.Status = inb( Read_SCSI_Data_port ); +#if EVERY_ACCESS + printk( "Status = %x, ", current_SC->SCp.Status ); +#endif +#if ERRORS_ONLY + if (current_SC->SCp.Status + && current_SC->SCp.Status != 2 + && current_SC->SCp.Status != 8) { + printk( "fdomain: target = %d, command = %x, status = %x\n", + current_SC->target, + current_SC->cmnd[0], + current_SC->SCp.Status ); + } +#endif + break; + case 0x0a: /* MESSAGE OUT */ + outb( MESSAGE_REJECT, Write_SCSI_Data_port ); /* Reject */ + break; + case 0x0e: /* MESSAGE IN */ + current_SC->SCp.Message = inb( Read_SCSI_Data_port ); +#if EVERY_ACCESS + printk( "Message = %x, ", current_SC->SCp.Message ); +#endif + if (!current_SC->SCp.Message) ++done; +#if DEBUG_MESSAGES || EVERY_ACCESS + if (current_SC->SCp.Message) { + printk( "fdomain: message = %x\n", current_SC->SCp.Message ); + } +#endif + break; + } + } + + if (chip == tmc1800 + && !current_SC->SCp.have_data_in + && (current_SC->SCp.sent_command + >= current_SC->cmd_len)) { + /* We have to get the FIFO direction + correct, so I've made a table based + on the SCSI Standard of which commands + appear to require a DATA OUT phase. + */ + /* + p. 94: Command for all device types + CHANGE DEFINITION 40 DATA OUT + COMPARE 39 DATA OUT + COPY 18 DATA OUT + COPY AND VERIFY 3a DATA OUT + INQUIRY 12 + LOG SELECT 4c DATA OUT + LOG SENSE 4d + MODE SELECT (6) 15 DATA OUT + MODE SELECT (10) 55 DATA OUT + MODE SENSE (6) 1a + MODE SENSE (10) 5a + READ BUFFER 3c + RECEIVE DIAGNOSTIC RESULTS 1c + REQUEST SENSE 03 + SEND DIAGNOSTIC 1d DATA OUT + TEST UNIT READY 00 + WRITE BUFFER 3b DATA OUT + + p.178: Commands for direct-access devices (not listed on p. 94) + FORMAT UNIT 04 DATA OUT + LOCK-UNLOCK CACHE 36 + PRE-FETCH 34 + PREVENT-ALLOW MEDIUM REMOVAL 1e + READ (6)/RECEIVE 08 + READ (10) 3c + READ CAPACITY 25 + READ DEFECT DATA (10) 37 + READ LONG 3e + REASSIGN BLOCKS 07 DATA OUT + RELEASE 17 + RESERVE 16 DATA OUT + REZERO UNIT/REWIND 01 + SEARCH DATA EQUAL (10) 31 DATA OUT + SEARCH DATA HIGH (10) 30 DATA OUT + SEARCH DATA LOW (10) 32 DATA OUT + SEEK (6) 0b + SEEK (10) 2b + SET LIMITS (10) 33 + START STOP UNIT 1b + SYNCHRONIZE CACHE 35 + VERIFY (10) 2f + WRITE (6)/PRINT/SEND 0a DATA OUT + WRITE (10)/SEND 2a DATA OUT + WRITE AND VERIFY (10) 2e DATA OUT + WRITE LONG 3f DATA OUT + WRITE SAME 41 DATA OUT ? + + p. 261: Commands for sequential-access devices (not previously listed) + ERASE 19 + LOAD UNLOAD 1b + LOCATE 2b + READ BLOCK LIMITS 05 + READ POSITION 34 + READ REVERSE 0f + RECOVER BUFFERED DATA 14 + SPACE 11 + WRITE FILEMARKS 10 ? + + p. 298: Commands for printer devices (not previously listed) + ****** NOT SUPPORTED BY THIS DRIVER, since 0b is SEEK (6) ***** + SLEW AND PRINT 0b DATA OUT -- same as seek + STOP PRINT 1b + SYNCHRONIZE BUFFER 10 + + p. 315: Commands for processor devices (not previously listed) + + p. 321: Commands for write-once devices (not previously listed) + MEDIUM SCAN 38 + READ (12) a8 + SEARCH DATA EQUAL (12) b1 DATA OUT + SEARCH DATA HIGH (12) b0 DATA OUT + SEARCH DATA LOW (12) b2 DATA OUT + SET LIMITS (12) b3 + VERIFY (12) af + WRITE (12) aa DATA OUT + WRITE AND VERIFY (12) ae DATA OUT + + p. 332: Commands for CD-ROM devices (not previously listed) + PAUSE/RESUME 4b + PLAY AUDIO (10) 45 + PLAY AUDIO (12) a5 + PLAY AUDIO MSF 47 + PLAY TRACK RELATIVE (10) 49 + PLAY TRACK RELATIVE (12) a9 + READ HEADER 44 + READ SUB-CHANNEL 42 + READ TOC 43 + + p. 370: Commands for scanner devices (not previously listed) + GET DATA BUFFER STATUS 34 + GET WINDOW 25 + OBJECT POSITION 31 + SCAN 1b + SET WINDOW 24 DATA OUT + + p. 391: Commands for optical memory devices (not listed) + ERASE (10) 2c + ERASE (12) ac + MEDIUM SCAN 38 DATA OUT + READ DEFECT DATA (12) b7 + READ GENERATION 29 + READ UPDATED BLOCK 2d + UPDATE BLOCK 3d DATA OUT + + p. 419: Commands for medium changer devices (not listed) + EXCHANGE MEDIUM 46 + INITIALIZE ELEMENT STATUS 07 + MOVE MEDIUM a5 + POSITION TO ELEMENT 2b + READ ELEMENT STATUS b8 + REQUEST VOL. ELEMENT ADDRESS b5 + SEND VOLUME TAG b6 DATA OUT + + p. 454: Commands for communications devices (not listed previously) + GET MESSAGE (6) 08 + GET MESSAGE (10) 28 + GET MESSAGE (12) a8 + */ + + switch (current_SC->cmnd[0]) { + case CHANGE_DEFINITION: case COMPARE: case COPY: + case COPY_VERIFY: case LOG_SELECT: case MODE_SELECT: + case MODE_SELECT_10: case SEND_DIAGNOSTIC: case WRITE_BUFFER: + + case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE: + case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW: + case WRITE_6: case WRITE_10: case WRITE_VERIFY: + case 0x3f: case 0x41: + + case 0xb1: case 0xb0: case 0xb2: + case 0xaa: case 0xae: + + case 0x24: + + case 0x38: case 0x3d: + + case 0xb6: + + case 0xea: /* alternate number for WRITE LONG */ + + current_SC->SCp.have_data_in = -1; + outb( 0xd0 | PARITY_MASK, TMC_Cntl_port ); + break; + + case 0x00: + default: + + current_SC->SCp.have_data_in = 1; + outb( 0x90 | PARITY_MASK, TMC_Cntl_port ); + break; + } + } + + if (current_SC->SCp.have_data_in == -1) { /* DATA OUT */ + while ( (data_count = FIFO_Size - inw( FIFO_Data_Count_port )) > 512 ) { +#if EVERY_ACCESS + printk( "DC=%d, ", data_count ) ; +#endif + if (data_count > current_SC->SCp.this_residual) + data_count = current_SC->SCp.this_residual; + if (data_count > 0) { +#if EVERY_ACCESS + printk( "%d OUT, ", data_count ); +#endif + if (data_count == 1) { + outb( *current_SC->SCp.ptr++, Write_FIFO_port ); + --current_SC->SCp.this_residual; + } else { + data_count >>= 1; + outsw( Write_FIFO_port, current_SC->SCp.ptr, data_count ); + current_SC->SCp.ptr += 2 * data_count; + current_SC->SCp.this_residual -= 2 * data_count; + } + } + if (!current_SC->SCp.this_residual) { + if (current_SC->SCp.buffers_residual) { + --current_SC->SCp.buffers_residual; + ++current_SC->SCp.buffer; + current_SC->SCp.ptr = current_SC->SCp.buffer->address; + current_SC->SCp.this_residual = current_SC->SCp.buffer->length; + } else + break; + } + } + } + + if (current_SC->SCp.have_data_in == 1) { /* DATA IN */ + while ((data_count = inw( FIFO_Data_Count_port )) > 0) { +#if EVERY_ACCESS + printk( "DC=%d, ", data_count ); +#endif + if (data_count > current_SC->SCp.this_residual) + data_count = current_SC->SCp.this_residual; + if (data_count) { +#if EVERY_ACCESS + printk( "%d IN, ", data_count ); +#endif + if (data_count == 1) { + *current_SC->SCp.ptr++ = inb( Read_FIFO_port ); + --current_SC->SCp.this_residual; + } else { + data_count >>= 1; /* Number of words */ + insw( Read_FIFO_port, current_SC->SCp.ptr, data_count ); + current_SC->SCp.ptr += 2 * data_count; + current_SC->SCp.this_residual -= 2 * data_count; + } + } + if (!current_SC->SCp.this_residual + && current_SC->SCp.buffers_residual) { + --current_SC->SCp.buffers_residual; + ++current_SC->SCp.buffer; + current_SC->SCp.ptr = current_SC->SCp.buffer->address; + current_SC->SCp.this_residual = current_SC->SCp.buffer->length; + } + } + } + + if (done) { +#if EVERY_ACCESS + printk( " ** IN DONE %d ** ", current_SC->SCp.have_data_in ); +#endif + +#if ERRORS_ONLY + if (current_SC->cmnd[0] == REQUEST_SENSE && !current_SC->SCp.Status) { + if ((unsigned char)(*((char *)current_SC->request_buffer+2)) & 0x0f) { + unsigned char key; + unsigned char code; + unsigned char qualifier; + + key = (unsigned char)(*((char *)current_SC->request_buffer + 2)) + & 0x0f; + code = (unsigned char)(*((char *)current_SC->request_buffer + 12)); + qualifier = (unsigned char)(*((char *)current_SC->request_buffer + + 13)); + + if (key != UNIT_ATTENTION + && !(key == NOT_READY + && code == 0x04 + && (!qualifier || qualifier == 0x02 || qualifier == 0x01)) + && !(key == ILLEGAL_REQUEST && (code == 0x25 + || code == 0x24 + || !code))) + + printk( "fdomain: REQUEST SENSE " + "Key = %x, Code = %x, Qualifier = %x\n", + key, code, qualifier ); + } + } +#endif +#if EVERY_ACCESS + printk( "BEFORE MY_DONE. . ." ); +#endif + my_done( (current_SC->SCp.Status & 0xff) + | ((current_SC->SCp.Message & 0xff) << 8) | (DID_OK << 16) ); +#if EVERY_ACCESS + printk( "RETURNING.\n" ); +#endif + + } else { + if (current_SC->SCp.phase & disconnect) { + outb( 0xd0 | FIFO_COUNT, Interrupt_Cntl_port ); + outb( 0x00, SCSI_Cntl_port ); + } else { + outb( 0x90 | FIFO_COUNT, Interrupt_Cntl_port ); + } + } +#if DEBUG_RACE + in_interrupt_flag = 0; +#endif + return; +} + +int fdomain_16x0_queue( Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *)) +{ + if (in_command) { + panic( "fdomain: fdomain_16x0_queue() NOT REENTRANT!\n" ); + } +#if EVERY_ACCESS + printk( "queue: target = %d cmnd = 0x%02x pieces = %d size = %u\n", + SCpnt->target, + *(unsigned char *)SCpnt->cmnd, + SCpnt->use_sg, + SCpnt->request_bufflen ); +#endif + + fdomain_make_bus_idle(); + + current_SC = SCpnt; /* Save this for the done function */ + current_SC->scsi_done = done; + + /* Initialize static data */ + + if (current_SC->use_sg) { + current_SC->SCp.buffer = + (struct scatterlist *)current_SC->request_buffer; + current_SC->SCp.ptr = current_SC->SCp.buffer->address; + current_SC->SCp.this_residual = current_SC->SCp.buffer->length; + current_SC->SCp.buffers_residual = current_SC->use_sg - 1; + } else { + current_SC->SCp.ptr = (char *)current_SC->request_buffer; + current_SC->SCp.this_residual = current_SC->request_bufflen; + current_SC->SCp.buffer = NULL; + current_SC->SCp.buffers_residual = 0; + } + + + current_SC->SCp.Status = 0; + current_SC->SCp.Message = 0; + current_SC->SCp.have_data_in = 0; + current_SC->SCp.sent_command = 0; + current_SC->SCp.phase = in_arbitration; + + /* Start arbitration */ + outb( 0x00, Interrupt_Cntl_port ); + outb( 0x00, SCSI_Cntl_port ); /* Disable data drivers */ + outb( adapter_mask, SCSI_Data_NoACK_port ); /* Set our id bit */ + ++in_command; + outb( 0x20, Interrupt_Cntl_port ); + outb( 0x14 | PARITY_MASK, TMC_Cntl_port ); /* Start arbitration */ + + return 0; +} + +/* The following code, which simulates the old-style command function, was + taken from Tommy Thorn's aha1542.c file. This code is Copyright (C) + 1992 Tommy Thorn. */ + +static volatile int internal_done_flag = 0; +static volatile int internal_done_errcode = 0; + +static void internal_done( Scsi_Cmnd *SCpnt ) +{ + internal_done_errcode = SCpnt->result; + ++internal_done_flag; +} + +int fdomain_16x0_command( Scsi_Cmnd *SCpnt ) +{ + fdomain_16x0_queue( SCpnt, internal_done ); + + while (!internal_done_flag) + ; + internal_done_flag = 0; + return internal_done_errcode; +} + +/* End of code derived from Tommy Thorn's work. */ + +void print_info( Scsi_Cmnd *SCpnt ) +{ + unsigned int imr; + unsigned int irr; + unsigned int isr; + + if (!SCpnt || !SCpnt->host) { + printk( "fdomain: cannot provide detailed information\n" ); + } + + printk( "%s\n", fdomain_16x0_info( SCpnt->host ) ); + print_banner( SCpnt->host ); + switch (SCpnt->SCp.phase) { + case in_arbitration: printk( "arbitration " ); break; + case in_selection: printk( "selection " ); break; + case in_other: printk( "other " ); break; + default: printk( "unknown " ); break; + } + + printk( "(%d), target = %d cmnd = 0x%02x pieces = %d size = %u\n", + SCpnt->SCp.phase, + SCpnt->target, + *(unsigned char *)SCpnt->cmnd, + SCpnt->use_sg, + SCpnt->request_bufflen ); + printk( "sent_command = %d, have_data_in = %d, timeout = %d\n", + SCpnt->SCp.sent_command, + SCpnt->SCp.have_data_in, + SCpnt->timeout ); +#if DEBUG_RACE + printk( "in_interrupt_flag = %d\n", in_interrupt_flag ); +#endif + + imr = (inb( 0x0a1 ) << 8) + inb( 0x21 ); + outb( 0x0a, 0xa0 ); + irr = inb( 0xa0 ) << 8; + outb( 0x0a, 0x20 ); + irr += inb( 0x20 ); + outb( 0x0b, 0xa0 ); + isr = inb( 0xa0 ) << 8; + outb( 0x0b, 0x20 ); + isr += inb( 0x20 ); + + /* Print out interesting information */ + printk( "IMR = 0x%04x", imr ); + if (imr & (1 << interrupt_level)) + printk( " (masked)" ); + printk( ", IRR = 0x%04x, ISR = 0x%04x\n", irr, isr ); + + printk( "SCSI Status = 0x%02x\n", inb( SCSI_Status_port ) ); + printk( "TMC Status = 0x%02x", inb( TMC_Status_port ) ); + if (inb( TMC_Status_port & 1)) + printk( " (interrupt)" ); + printk( "\n" ); + printk( "Interrupt Status = 0x%02x", inb( Interrupt_Status_port ) ); + if (inb( Interrupt_Status_port ) & 0x08) + printk( " (enabled)" ); + printk( "\n" ); + if (chip == tmc18c50 || chip == tmc18c30) { + printk( "FIFO Status = 0x%02x\n", inb( port_base + FIFO_Status ) ); + printk( "Int. Condition = 0x%02x\n", + inb( port_base + Interrupt_Cond ) ); + } + printk( "Configuration 1 = 0x%02x\n", inb( port_base + Configuration1 ) ); + if (chip == tmc18c50 || chip == tmc18c30) + printk( "Configuration 2 = 0x%02x\n", + inb( port_base + Configuration2 ) ); +} + +int fdomain_16x0_abort( Scsi_Cmnd *SCpnt) +{ + unsigned long flags; +#if EVERY_ACCESS || ERRORS_ONLY || DEBUG_ABORT + printk( "fdomain: abort " ); +#endif + + save_flags( flags ); + cli(); + if (!in_command) { +#if EVERY_ACCESS || ERRORS_ONLY + printk( " (not in command)\n" ); +#endif + restore_flags( flags ); + return SCSI_ABORT_NOT_RUNNING; + } else printk( "\n" ); + +#if DEBUG_ABORT + print_info( SCpnt ); +#endif + + fdomain_make_bus_idle(); + + current_SC->SCp.phase |= aborted; + + current_SC->result = DID_ABORT << 16; + + restore_flags( flags ); + + /* Aborts are not done well. . . */ + my_done( DID_ABORT << 16 ); + + return SCSI_ABORT_SUCCESS; +} + +int fdomain_16x0_reset( Scsi_Cmnd *SCpnt, unsigned int flags ) +{ +#if DEBUG_RESET + static int called_once = 0; +#endif + +#if ERRORS_ONLY + if (SCpnt) printk( "fdomain: SCSI Bus Reset\n" ); +#endif + +#if DEBUG_RESET + if (called_once) print_info( current_SC ); + called_once = 1; +#endif + + outb( 1, SCSI_Cntl_port ); + do_pause( 2 ); + outb( 0, SCSI_Cntl_port ); + do_pause( 115 ); + outb( 0, SCSI_Mode_Cntl_port ); + outb( PARITY_MASK, TMC_Cntl_port ); + + /* Unless this is the very first call (i.e., SCPnt == NULL), everything + is probably hosed at this point. We will, however, try to keep + things going by informing the high-level code that we need help. */ + + return SCSI_RESET_WAKEUP; +} + +#include "sd.h" +#include <scsi/scsi_ioctl.h> + +int fdomain_16x0_biosparam( Scsi_Disk *disk, kdev_t dev, int *info_array ) +{ + int drive; + unsigned char buf[512 + sizeof( int ) * 2]; + int size = disk->capacity; + int *sizes = (int *)buf; + unsigned char *data = (unsigned char *)(sizes + 2); + unsigned char do_read[] = { READ_6, 0, 0, 0, 1, 0 }; + int retcode; + struct drive_info { + unsigned short cylinders; + unsigned char heads; + unsigned char sectors; + } *i; + + /* NOTES: + The RAM area starts at 0x1f00 from the bios_base address. + + For BIOS Version 2.0: + + The drive parameter table seems to start at 0x1f30. + The first byte's purpose is not known. + Next is the cylinder, head, and sector information. + The last 4 bytes appear to be the drive's size in sectors. + The other bytes in the drive parameter table are unknown. + If anyone figures them out, please send me mail, and I will + update these notes. + + Tape drives do not get placed in this table. + + There is another table at 0x1fea: + If the byte is 0x01, then the SCSI ID is not in use. + If the byte is 0x18 or 0x48, then the SCSI ID is in use, + although tapes don't seem to be in this table. I haven't + seen any other numbers (in a limited sample). + + 0x1f2d is a drive count (i.e., not including tapes) + + The table at 0x1fcc are I/O ports addresses for the various + operations. I calculate these by hand in this driver code. + + + + For the ISA-200S version of BIOS Version 2.0: + + The drive parameter table starts at 0x1f33. + + WARNING: Assume that the table entry is 25 bytes long. Someone needs + to check this for the Quantum ISA-200S card. + + + + For BIOS Version 3.2: + + The drive parameter table starts at 0x1f70. Each entry is + 0x0a bytes long. Heads are one less than we need to report. + */ + + drive = MINOR(dev) / 16; + + if (bios_major == 2) { + switch (Quantum) { + case 2: /* ISA_200S */ + /* The value of 25 has never been verified. + It should probably be 15. */ + i = (struct drive_info *)( (char *)bios_base + 0x1f33 + drive * 25 ); + break; + case 3: /* ISA_250MG */ + i = (struct drive_info *)( (char *)bios_base + 0x1f36 + drive * 15 ); + break; + case 4: /* ISA_200S (another one) */ + i = (struct drive_info *)( (char *)bios_base + 0x1f34 + drive * 15 ); + break; + default: + i = (struct drive_info *)( (char *)bios_base + 0x1f31 + drive * 25 ); + break; + } + info_array[0] = i->heads; + info_array[1] = i->sectors; + info_array[2] = i->cylinders; + } else if (bios_major == 3 + && bios_minor >= 0 + && bios_minor < 4) { /* 3.0 and 3.2 BIOS */ + i = (struct drive_info *)( (char *)bios_base + 0x1f71 + drive * 10 ); + info_array[0] = i->heads + 1; + info_array[1] = i->sectors; + info_array[2] = i->cylinders; + } else { /* 3.4 BIOS (and up?) */ + /* This algorithm was provided by Future Domain (much thanks!). */ + + sizes[0] = 0; /* zero bytes out */ + sizes[1] = 512; /* one sector in */ + memcpy( data, do_read, sizeof( do_read ) ); + retcode = kernel_scsi_ioctl( disk->device, + SCSI_IOCTL_SEND_COMMAND, + (void *)buf ); + if (!retcode /* SCSI command ok */ + && data[511] == 0xaa && data[510] == 0x55 /* Partition table valid */ + && data[0x1c2]) { /* Partition type */ + + /* The partition table layout is as follows: + + Start: 0x1b3h + Offset: 0 = partition status + 1 = starting head + 2 = starting sector and cylinder (word, encoded) + 4 = partition type + 5 = ending head + 6 = ending sector and cylinder (word, encoded) + 8 = starting absolute sector (double word) + c = number of sectors (double word) + Signature: 0x1fe = 0x55aa + + So, this algorithm assumes: + 1) the first partition table is in use, + 2) the data in the first entry is correct, and + 3) partitions never divide cylinders + + Note that (1) may be FALSE for NetBSD (and other BSD flavors), + as well as for Linux. Note also, that Linux doesn't pay any + attention to the fields that are used by this algorithm -- it + only uses the absolute sector data. Recent versions of Linux's + fdisk(1) will fill this data in correctly, and forthcoming + versions will check for consistency. + + Checking for a non-zero partition type is not part of the + Future Domain algorithm, but it seemed to be a reasonable thing + to do, especially in the Linux and BSD worlds. */ + + info_array[0] = data[0x1c3] + 1; /* heads */ + info_array[1] = data[0x1c4] & 0x3f; /* sectors */ + } else { + + /* Note that this new method guarantees that there will always be + less than 1024 cylinders on a platter. This is good for drives + up to approximately 7.85GB (where 1GB = 1024 * 1024 kB). */ + + if ((unsigned int)size >= 0x7e0000U) { + info_array[0] = 0xff; /* heads = 255 */ + info_array[1] = 0x3f; /* sectors = 63 */ + } else if ((unsigned int)size >= 0x200000U) { + info_array[0] = 0x80; /* heads = 128 */ + info_array[1] = 0x3f; /* sectors = 63 */ + } else { + info_array[0] = 0x40; /* heads = 64 */ + info_array[1] = 0x20; /* sectors = 32 */ + } + } + /* For both methods, compute the cylinders */ + info_array[2] = (unsigned int)size / (info_array[0] * info_array[1] ); + } + + return 0; +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = FDOMAIN_16X0; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/fdomain.h b/linux/src/drivers/scsi/fdomain.h new file mode 100644 index 00000000..bea9998b --- /dev/null +++ b/linux/src/drivers/scsi/fdomain.h @@ -0,0 +1,61 @@ +/* fdomain.h -- Header for Future Domain TMC-16x0 driver + * Created: Sun May 3 18:47:33 1992 by faith@cs.unc.edu + * Revised: Thu Oct 12 13:21:35 1995 by r.faith@ieee.org + * Author: Rickard E. Faith, faith@cs.unc.edu + * Copyright 1992, 1993, 1994, 1995 Rickard E. Faith + * + * $Id: fdomain.h,v 1.1 1999/04/26 05:54:33 tb Exp $ + + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + + */ + +#ifndef _FDOMAIN_H +#define _FDOMAIN_H + +int fdomain_16x0_detect( Scsi_Host_Template * ); +int fdomain_16x0_command( Scsi_Cmnd * ); +int fdomain_16x0_abort( Scsi_Cmnd * ); +const char *fdomain_16x0_info( struct Scsi_Host * ); +int fdomain_16x0_reset( Scsi_Cmnd *, unsigned int ); +int fdomain_16x0_queue( Scsi_Cmnd *, void (*done)(Scsi_Cmnd *) ); +int fdomain_16x0_biosparam( Disk *, kdev_t, int * ); +int fdomain_16x0_proc_info( char *buffer, char **start, off_t offset, + int length, int hostno, int inout ); + +extern struct proc_dir_entry proc_scsi_fdomain; + +#define FDOMAIN_16X0 { NULL, \ + NULL, \ + NULL, \ + fdomain_16x0_proc_info, \ + NULL, \ + fdomain_16x0_detect, \ + NULL, \ + fdomain_16x0_info, \ + fdomain_16x0_command, \ + fdomain_16x0_queue, \ + fdomain_16x0_abort, \ + fdomain_16x0_reset, \ + NULL, \ + fdomain_16x0_biosparam, \ + 1, \ + 6, \ + 64, \ + 1, \ + 0, \ + 0, \ + DISABLE_CLUSTERING } +#endif diff --git a/linux/src/drivers/scsi/g_NCR5380.c b/linux/src/drivers/scsi/g_NCR5380.c new file mode 100644 index 00000000..0d05c65a --- /dev/null +++ b/linux/src/drivers/scsi/g_NCR5380.c @@ -0,0 +1,733 @@ +/* + * Generic Generic NCR5380 driver + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * NCR53C400 extensions (c) 1994,1995,1996, Kevin Lentin + * K.Lentin@cs.monash.edu.au + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * TODO : flesh out DMA support, find some one actually using this (I have + * a memory mapped Trantor board that works fine) + */ + +/* + * Options : + * + * PARITY - enable parity checking. Not supported. + * + * SCSI2 - enable support for SCSI-II tagged queueing. Untested. + * + * USLEEP - enable support for devices that don't disconnect. Untested. + * + * The card is detected and initialized in one of several ways : + * 1. With command line overrides - NCR5380=port,irq may be + * used on the LILO command line to override the defaults. + * + * 2. With the GENERIC_NCR5380_OVERRIDE compile time define. This is + * specified as an array of address, irq, dma, board tuples. Ie, for + * one board at 0x350, IRQ5, no dma, I could say + * -DGENERIC_NCR5380_OVERRIDE={{0xcc000, 5, DMA_NONE, BOARD_NCR5380}} + * + * -1 should be specified for no or DMA interrupt, -2 to autoprobe for an + * IRQ line if overridden on the command line. + * + * 3. When included as a module, with arguments passed on the command line: + * ncr_irq=xx the interrupt + * ncr_addr=xx the port or base address (for port or memory + * mapped, resp.) + * ncr_dma=xx the DMA + * ncr_5380=1 to set up for a NCR5380 board + * ncr_53c400=1 to set up for a NCR53C400 board + * e.g. + * modprobe g_NCR5380 ncr_irq=5 ncr_addr=0x350 ncr_5380=1 + * for a port mapped NCR5380 board or + * modprobe g_NCR5380 ncr_irq=255 ncr_addr=0xc8000 ncr_53c400=1 + * for a memory mapped NCR53C400 board with interrupts disabled. + * + * 255 should be specified for no or DMA interrupt, 254 to autoprobe for an + * IRQ line if overridden on the command line. + * + */ + +/* + * $Log: generic_NCR5380.c,v $ + */ + +#define AUTOPROBE_IRQ +#define AUTOSENSE + +#include <linux/config.h> + +#ifdef CONFIG_SCSI_GENERIC_NCR53C400 +#define NCR53C400_PSEUDO_DMA 1 +#define PSEUDO_DMA +#define NCR53C400 +#define NCR5380_STATS +#undef NCR5380_STAT_LIMIT +#endif +#if defined(CONFIG_SCSI_G_NCR5380_PORT) && defined(CONFIG_SCSI_G_NCR5380_MEM) +#error You can not configure the Generic NCR 5380 SCSI Driver for memory mapped I/O and port mapped I/O at the same time (yet) +#endif +#if !defined(CONFIG_SCSI_G_NCR5380_PORT) && !defined(CONFIG_SCSI_G_NCR5380_MEM) +#error You must configure the Generic NCR 5380 SCSI Driver for one of memory mapped I/O and port mapped I/O. +#endif + +#include <asm/system.h> +#include <asm/io.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "g_NCR5380.h" +#include "NCR5380.h" +#include "constants.h" +#include "sd.h" +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_g_ncr5380 = { + PROC_SCSI_GENERIC_NCR5380, 9, "g_NCR5380", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#define NCR_NOT_SET 0 +static int ncr_irq=NCR_NOT_SET; +static int ncr_dma=NCR_NOT_SET; +static int ncr_addr=NCR_NOT_SET; +static int ncr_5380=NCR_NOT_SET; +static int ncr_53c400=NCR_NOT_SET; + +static struct override { + NCR5380_implementation_fields; + int irq; + int dma; + int board; /* Use NCR53c400, Ricoh, etc. extensions ? */ +} overrides +#ifdef GENERIC_NCR5380_OVERRIDE + [] = GENERIC_NCR5380_OVERRIDE +#else + [1] = {{0,},}; +#endif + +#define NO_OVERRIDES (sizeof(overrides) / sizeof(struct override)) + +/* + * Function : static internal_setup(int board, char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : board - either BOARD_NCR5380 for a normal NCR5380 board, + * or BOARD_NCR53C400 for a NCR53C400 board. str - unused, ints - + * array of integer parameters with ints[0] equal to the number of ints. + * + */ + +static void internal_setup(int board, char *str, int *ints) { + static int commandline_current = 0; + switch (board) { + case BOARD_NCR5380: + if (ints[0] != 2 && ints[0] != 3) { + printk("generic_NCR5380_setup : usage ncr5380=" STRVAL(NCR5380_map_name) ",irq,dma\n"); + return; + } + case BOARD_NCR53C400: + if (ints[0] != 2) { + printk("generic_NCR53C400_setup : usage ncr53c400=" STRVAL(NCR5380_map_name) ",irq\n"); + return; + } + } + + if (commandline_current < NO_OVERRIDES) { + overrides[commandline_current].NCR5380_map_name = (NCR5380_map_type)ints[1]; + overrides[commandline_current].irq = ints[2]; + if (ints[0] == 3) + overrides[commandline_current].dma = ints[3]; + else + overrides[commandline_current].dma = DMA_NONE; + overrides[commandline_current].board = board; + ++commandline_current; + } +} + +/* + * Function : generic_NCR5380_setup (char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + */ + +void generic_NCR5380_setup (char *str, int *ints) { + internal_setup (BOARD_NCR5380, str, ints); +} + +/* + * Function : generic_NCR53C400_setup (char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + */ + +void generic_NCR53C400_setup (char *str, int *ints) { + internal_setup (BOARD_NCR53C400, str, ints); +} + +/* + * Function : int generic_NCR5380_detect(Scsi_Host_Template * tpnt) + * + * Purpose : initializes generic NCR5380 driver based on the + * command line / compile time port and irq definitions. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * + */ + +int generic_NCR5380_detect(Scsi_Host_Template * tpnt) { + static int current_override = 0; + int count; + int flags = 0; + struct Scsi_Host *instance; + + if (ncr_irq != NCR_NOT_SET) + overrides[0].irq=ncr_irq; + if (ncr_dma != NCR_NOT_SET) + overrides[0].dma=ncr_dma; + if (ncr_addr != NCR_NOT_SET) + overrides[0].NCR5380_map_name=(NCR5380_map_type)ncr_addr; + if (ncr_5380 != NCR_NOT_SET) + overrides[0].board=BOARD_NCR5380; + else if (ncr_53c400 != NCR_NOT_SET) + overrides[0].board=BOARD_NCR53C400; + + tpnt->proc_dir = &proc_scsi_g_ncr5380; + + for (count = 0; current_override < NO_OVERRIDES; ++current_override) { + if (!(overrides[current_override].NCR5380_map_name)) + continue; + + switch (overrides[current_override].board) { + case BOARD_NCR5380: + flags = FLAG_NO_PSEUDO_DMA; + break; + case BOARD_NCR53C400: + flags = FLAG_NCR53C400; + break; + } + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->NCR5380_instance_name = overrides[current_override].NCR5380_map_name; + + NCR5380_init(instance, flags); + + if (overrides[current_override].irq != IRQ_AUTO) + instance->irq = overrides[current_override].irq; + else + instance->irq = NCR5380_probe_irq(instance, 0xffff); + + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, generic_NCR5380_intr, SA_INTERRUPT, "NCR5380", NULL)) { + printk("scsi%d : IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq == IRQ_NONE) { + printk("scsi%d : interrupts not enabled. for better interactive performance,\n", instance->host_no); + printk("scsi%d : please jumper the board for a free IRQ.\n", instance->host_no); + } + + printk("scsi%d : at " STRVAL(NCR5380_map_name) " 0x%x", instance->host_no, (unsigned int)instance->NCR5380_instance_name); + if (instance->irq == IRQ_NONE) + printk (" interrupts disabled"); + else + printk (" irq %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, GENERIC_NCR5380_PUBLIC_RELEASE); + NCR5380_print_options(instance); + printk("\n"); + + ++current_override; + ++count; + } + return count; +} + +const char * generic_NCR5380_info (struct Scsi_Host* host) { + static const char string[]="Generic NCR5380/53C400 Driver"; + return string; +} + +int generic_NCR5380_release_resources(struct Scsi_Host * instance) +{ + NCR5380_local_declare(); + + NCR5380_setup(instance); + + if (instance->irq != IRQ_NONE) + free_irq(instance->irq, NULL); + + return 0; +} + +#ifdef BIOSPARAM +/* + * Function : int generic_NCR5380_biosparam(Disk * disk, kdev_t dev, int *ip) + * + * Purpose : Generates a BIOS / DOS compatible H-C-S mapping for + * the specified device / size. + * + * Inputs : size = size of device in sectors (512 bytes), dev = block device + * major / minor, ip[] = {heads, sectors, cylinders} + * + * Returns : always 0 (success), initializes ip + * + */ + +/* + * XXX Most SCSI boards use this mapping, I could be incorrect. Some one + * using hard disks on a trantor should verify that this mapping corresponds + * to that used by the BIOS / ASPI driver by running the linux fdisk program + * and matching the H_C_S coordinates to what DOS uses. + */ + +int generic_NCR5380_biosparam(Disk * disk, kdev_t dev, int *ip) +{ + int size = disk->capacity; + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + return 0; +} +#endif + +#if NCR53C400_PSEUDO_DMA +static inline int NCR5380_pread (struct Scsi_Host *instance, unsigned char *dst, int len) +{ + int blocks = len / 128; + int start = 0; + int i; + int bl; + NCR5380_local_declare(); + + NCR5380_setup(instance); + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: About to read %d blocks for %d bytes\n", blocks, len); +#endif + + NCR5380_write(C400_CONTROL_STATUS_REG, CSR_BASE | CSR_TRANS_DIR); + NCR5380_write(C400_BLOCK_COUNTER_REG, blocks); + while (1) { + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: %d blocks left\n", blocks); +#endif + + if ((bl=NCR5380_read(C400_BLOCK_COUNTER_REG)) == 0) { +#if (NDEBUG & NDEBUG_C400_PREAD) + if (blocks) + printk("53C400r: blocks still == %d\n", blocks); + else + printk("53C400r: Exiting loop\n"); +#endif + break; + } + +#if 1 + if (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ) { + printk("53C400r: Got 53C80_IRQ start=%d, blocks=%d\n", start, blocks); + return -1; + } +#endif + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Waiting for buffer, bl=%d\n", bl); +#endif + + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Transferring 128 bytes\n"); +#endif + +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + dst[start+i] = NCR5380_read(C400_HOST_BUFFER); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(dst+start,NCR53C400_host_buffer+NCR5380_map_name,128); +#endif + start+=128; + blocks--; + } + + if (blocks) { +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: EXTRA: Waiting for buffer\n"); +#endif + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Transferring EXTRA 128 bytes\n"); +#endif +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + dst[start+i] = NCR5380_read(C400_HOST_BUFFER); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(dst+start,NCR53C400_host_buffer+NCR5380_map_name,128); +#endif + start+=128; + blocks--; + } +#if (NDEBUG & NDEBUG_C400_PREAD) + else + printk("53C400r: No EXTRA required\n"); +#endif + +#if (NDEBUG & NDEBUG_C400_PREAD) + printk("53C400r: Final values: blocks=%d start=%d\n", blocks, start); +#endif + + if (!(NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ)) + printk("53C400r: no 53C80 gated irq after transfer"); +#if (NDEBUG & NDEBUG_C400_PREAD) + else + printk("53C400r: Got 53C80 interrupt and tried to clear it\n"); +#endif + +/* DON'T DO THIS - THEY NEVER ARRIVE! + printk("53C400r: Waiting for 53C80 registers\n"); + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_53C80_REG) + ; +*/ + + if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_END_DMA_TRANSFER)) + printk("53C400r: no end dma signal\n"); +#if (NDEBUG & NDEBUG_C400_PREAD) + else + printk("53C400r: end dma as expected\n"); +#endif + + NCR5380_write(MODE_REG, MR_BASE); + NCR5380_read(RESET_PARITY_INTERRUPT_REG); + return 0; +} + +static inline int NCR5380_pwrite (struct Scsi_Host *instance, unsigned char *src, int len) +{ + int blocks = len / 128; + int start = 0; + int i; + int bl; + NCR5380_local_declare(); + + NCR5380_setup(instance); + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: About to write %d blocks for %d bytes\n", blocks, len); +#endif + + NCR5380_write(C400_CONTROL_STATUS_REG, CSR_BASE); + NCR5380_write(C400_BLOCK_COUNTER_REG, blocks); + while (1) { + if (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ) { + printk("53C400w: Got 53C80_IRQ start=%d, blocks=%d\n", start, blocks); + return -1; + } + + if ((bl=NCR5380_read(C400_BLOCK_COUNTER_REG)) == 0) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + if (blocks) + printk("53C400w: exiting loop, blocks still == %d\n", blocks); + else + printk("53C400w: exiting loop\n"); +#endif + break; + } + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: %d blocks left\n", blocks); + + printk("53C400w: waiting for buffer, bl=%d\n", bl); +#endif + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: transferring 128 bytes\n"); +#endif +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + NCR5380_write(C400_HOST_BUFFER, src[start+i]); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(NCR53C400_host_buffer+NCR5380_map_name,src+start,128); +#endif + start+=128; + blocks--; + } + if (blocks) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: EXTRA waiting for buffer\n"); +#endif + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_HOST_BUF_NOT_RDY) + ; + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: transferring EXTRA 128 bytes\n"); +#endif +#ifdef CONFIG_SCSI_G_NCR5380_PORT + for (i=0; i<128; i++) + NCR5380_write(C400_HOST_BUFFER, src[start+i]); +#else + /* implies CONFIG_SCSI_G_NCR5380_MEM */ + memmove(NCR53C400_host_buffer+NCR5380_map_name,src+start,128); +#endif + start+=128; + blocks--; + } +#if (NDEBUG & NDEBUG_C400_PWRITE) + else + printk("53C400w: No EXTRA required\n"); +#endif + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: Final values: blocks=%d start=%d\n", blocks, start); +#endif + +#if 0 + printk("53C400w: waiting for registers to be available\n"); + THEY NEVER DO! + while (NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_53C80_REG) + ; + printk("53C400w: Got em\n"); +#endif + + /* Let's wait for this instead - could be ugly */ + /* All documentation says to check for this. Maybe my hardware is too + * fast. Waiting for it seems to work fine! KLL + */ + while (!(i = NCR5380_read(C400_CONTROL_STATUS_REG) & CSR_GATED_53C80_IRQ)) + ; + + /* + * I know. i is certainly != 0 here but the loop is new. See previous + * comment. + */ + if (i) { +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: got 53C80 gated irq (last block)\n"); +#endif + if (!((i=NCR5380_read(BUS_AND_STATUS_REG)) & BASR_END_DMA_TRANSFER)) + printk("53C400w: No END OF DMA bit - WHOOPS! BASR=%0x\n",i); +#if (NDEBUG & NDEBUG_C400_PWRITE) + else + printk("53C400w: Got END OF DMA\n"); +#endif + } + else + printk("53C400w: no 53C80 gated irq after transfer (last block)\n"); + +#if 0 + if (!(NCR5380_read(BUS_AND_STATUS_REG) & BASR_END_DMA_TRANSFER)) { + printk("53C400w: no end dma signal\n"); + } +#endif + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: waiting for last byte...\n"); +#endif + while (!(NCR5380_read(TARGET_COMMAND_REG) & TCR_LAST_BYTE_SENT)) + ; + +#if (NDEBUG & NDEBUG_C400_PWRITE) + printk("53C400w: got last byte.\n"); + printk("53C400w: pwrite exiting with status 0, whoopee!\n"); +#endif + return 0; +} +#endif /* PSEUDO_DMA */ + +#include "NCR5380.c" + +#define PRINTP(x) len += sprintf(buffer+len, x) +#define ANDP , + +static int sprint_opcode(char* buffer, int len, int opcode) { + int start = len; + PRINTP("0x%02x " ANDP opcode); + return len-start; +} + +static int sprint_command (char* buffer, int len, unsigned char *command) { + int i,s,start=len; + len += sprint_opcode(buffer, len, command[0]); + for ( i = 1, s = COMMAND_SIZE(command[0]); i < s; ++i) + PRINTP("%02x " ANDP command[i]); + PRINTP("\n"); + return len-start; +} + +static int sprint_Scsi_Cmnd (char* buffer, int len, Scsi_Cmnd *cmd) { + int start = len; + PRINTP("host number %d destination target %d, lun %d\n" ANDP + cmd->host->host_no ANDP + cmd->target ANDP + cmd->lun); + PRINTP(" command = "); + len += sprint_command (buffer, len, cmd->cmnd); + return len-start; +} + +int generic_NCR5380_proc_info(char* buffer, char** start, off_t offset, int length, int hostno, int inout) +{ + int len = 0; + NCR5380_local_declare(); + unsigned char status; + int i; + struct Scsi_Host *scsi_ptr; + Scsi_Cmnd *ptr; + Scsi_Device *dev; + struct NCR5380_hostdata *hostdata; + + cli(); + + for (scsi_ptr = first_instance; scsi_ptr; scsi_ptr=scsi_ptr->next) + if (scsi_ptr->host_no == hostno) + break; + NCR5380_setup(scsi_ptr); + hostdata = (struct NCR5380_hostdata *)scsi_ptr->hostdata; + + PRINTP("SCSI host number %d : %s\n" ANDP scsi_ptr->host_no ANDP scsi_ptr->hostt->name); + PRINTP("Generic NCR5380 driver version %d\n" ANDP GENERIC_NCR5380_PUBLIC_RELEASE); + PRINTP("NCR5380 core version %d\n" ANDP NCR5380_PUBLIC_RELEASE); +#ifdef NCR53C400 + PRINTP("NCR53C400 extension version %d\n" ANDP NCR53C400_PUBLIC_RELEASE); + PRINTP("NCR53C400 card%s detected\n" ANDP (((struct NCR5380_hostdata *)scsi_ptr->hostdata)->flags & FLAG_NCR53C400)?"":" not"); +# if NCR53C400_PSEUDO_DMA + PRINTP("NCR53C400 pseudo DMA used\n"); +# endif +#else + PRINTP("NO NCR53C400 driver extensions\n"); +#endif + PRINTP("Using %s mapping at %s 0x%x, " ANDP STRVAL(NCR5380_map_config) ANDP STRVAL(NCR5380_map_name) ANDP scsi_ptr->NCR5380_instance_name); + if (scsi_ptr->irq == IRQ_NONE) + PRINTP("no interrupt\n"); + else + PRINTP("on interrupt %d\n" ANDP scsi_ptr->irq); + +#ifdef NCR5380_STATS + if (hostdata->connected || hostdata->issue_queue || hostdata->disconnected_queue) + PRINTP("There are commands pending, transfer rates may be crud\n"); + if (hostdata->pendingr) + PRINTP(" %d pending reads" ANDP hostdata->pendingr); + if (hostdata->pendingw) + PRINTP(" %d pending writes" ANDP hostdata->pendingw); + if (hostdata->pendingr || hostdata->pendingw) + PRINTP("\n"); + for (dev = scsi_devices; dev; dev=dev->next) { + if (dev->host == scsi_ptr) { + unsigned long br = hostdata->bytes_read[dev->id]; + unsigned long bw = hostdata->bytes_write[dev->id]; + long tr = hostdata->time_read[dev->id] / HZ; + long tw = hostdata->time_write[dev->id] / HZ; + + PRINTP(" T:%d %s " ANDP dev->id ANDP (dev->type < MAX_SCSI_DEVICE_CODE) ? scsi_device_types[(int)dev->type] : "Unknown"); + for (i=0; i<8; i++) + if (dev->vendor[i] >= 0x20) + *(buffer+(len++)) = dev->vendor[i]; + *(buffer+(len++)) = ' '; + for (i=0; i<16; i++) + if (dev->model[i] >= 0x20) + *(buffer+(len++)) = dev->model[i]; + *(buffer+(len++)) = ' '; + for (i=0; i<4; i++) + if (dev->rev[i] >= 0x20) + *(buffer+(len++)) = dev->rev[i]; + *(buffer+(len++)) = ' '; + + PRINTP("\n%10ld kb read in %5ld secs" ANDP br/1024 ANDP tr); + if (tr) + PRINTP(" @ %5ld bps" ANDP br / tr); + + PRINTP("\n%10ld kb written in %5ld secs" ANDP bw/1024 ANDP tw); + if (tw) + PRINTP(" @ %5ld bps" ANDP bw / tw); + PRINTP("\n"); + } + } +#endif + + status = NCR5380_read(STATUS_REG); + if (!(status & SR_REQ)) + PRINTP("REQ not asserted, phase unknown.\n"); + else { + for (i = 0; (phases[i].value != PHASE_UNKNOWN) && + (phases[i].value != (status & PHASE_MASK)); ++i) + ; + PRINTP("Phase %s\n" ANDP phases[i].name); + } + + if (!hostdata->connected) { + PRINTP("No currently connected command\n"); + } else { + len += sprint_Scsi_Cmnd (buffer, len, (Scsi_Cmnd *) hostdata->connected); + } + + PRINTP("issue_queue\n"); + + for (ptr = (Scsi_Cmnd *) hostdata->issue_queue; ptr; + ptr = (Scsi_Cmnd *) ptr->host_scribble) + len += sprint_Scsi_Cmnd (buffer, len, ptr); + + PRINTP("disconnected_queue\n"); + + for (ptr = (Scsi_Cmnd *) hostdata->disconnected_queue; ptr; + ptr = (Scsi_Cmnd *) ptr->host_scribble) + len += sprint_Scsi_Cmnd (buffer, len, ptr); + + *start = buffer + offset; + len -= offset; + if (len > length) + len = length; + sti(); + return len; +} + +#undef PRINTP +#undef ANDP + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = GENERIC_NCR5380; + +#include <linux/module.h> +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/g_NCR5380.h b/linux/src/drivers/scsi/g_NCR5380.h new file mode 100644 index 00000000..2730723e --- /dev/null +++ b/linux/src/drivers/scsi/g_NCR5380.h @@ -0,0 +1,166 @@ +/* + * Generic Generic NCR5380 driver defines + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * NCR53C400 extensions (c) 1994,1995,1996, Kevin Lentin + * K.Lentin@cs.monash.edu.au + * + * ALPHA RELEASE 1. + * + * For more information, please consult + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * $Log: generic_NCR5380.h,v $ + */ + +#ifndef GENERIC_NCR5380_H +#define GENERIC_NCR5380_H + +#include <linux/config.h> + +#define GENERIC_NCR5380_PUBLIC_RELEASE 1 + +#ifdef NCR53C400 +#define BIOSPARAM +#define NCR5380_BIOSPARAM generic_NCR5380_biosparam +#else +#define NCR5380_BIOSPARAM NULL +#endif + +#ifndef ASM +int generic_NCR5380_abort(Scsi_Cmnd *); +int generic_NCR5380_detect(Scsi_Host_Template *); +int generic_NCR5380_release_resources(struct Scsi_Host *); +int generic_NCR5380_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int generic_NCR5380_reset(Scsi_Cmnd *, unsigned int); +int notyet_generic_proc_info (char *buffer ,char **start, off_t offset, + int length, int hostno, int inout); +const char* generic_NCR5380_info(struct Scsi_Host *); +#ifdef BIOSPARAM +int generic_NCR5380_biosparam(Disk *, kdev_t, int *); +#endif + +int generic_NCR5380_proc_info(char* buffer, char** start, off_t offset, int length, int hostno, int inout); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 16 +#endif + +#if defined(HOSTS_C) || defined(MODULE) + +#define GENERIC_NCR5380 {NULL, NULL, NULL, \ + generic_NCR5380_proc_info, \ + "Generic NCR5380/NCR53C400 Scsi Driver", \ + generic_NCR5380_detect, generic_NCR5380_release_resources, \ + (void *)generic_NCR5380_info, NULL, \ + generic_NCR5380_queue_command, generic_NCR5380_abort, \ + generic_NCR5380_reset, NULL, \ + NCR5380_BIOSPARAM, \ + /* can queue */ CAN_QUEUE, /* id */ 7, SG_ALL, \ + /* cmd per lun */ CMD_PER_LUN , 0, 0, DISABLE_CLUSTERING} + +#endif + +#ifndef HOSTS_C + +#define __STRVAL(x) #x +#define STRVAL(x) __STRVAL(x) + +#ifdef CONFIG_SCSI_G_NCR5380_PORT + +#define NCR5380_map_config port + +#define NCR5380_map_type int + +#define NCR5380_map_name port + +#define NCR5380_instance_name io_port + +#define NCR53C400_register_offset 0 + +#define NCR53C400_address_adjust 8 + +#ifdef NCR53C400 +#define NCR5380_region_size 16 +#else +#define NCR5380_region_size 8 +#endif + +#define NCR5380_read(reg) (inb(NCR5380_map_name + (reg))) +#define NCR5380_write(reg, value) (outb((value), (NCR5380_map_name + (reg)))) + +#else +/* therefore CONFIG_SCSI_G_NCR5380_MEM */ + +#define NCR5380_map_config memory + +#define NCR5380_map_type volatile unsigned char* + +#define NCR5380_map_name base + +#define NCR5380_instance_name base + +#define NCR53C400_register_offset 0x108 + +#define NCR53C400_address_adjust 0 + +#define NCR53C400_mem_base 0x3880 + +#define NCR53C400_host_buffer 0x3900 + +#define NCR5380_region_size 0x3a00 + + +#define NCR5380_read(reg) (*(NCR5380_map_name + NCR53C400_mem_base + (reg))) +#define NCR5380_write(reg, value) (*(NCR5380_map_name + NCR53C400_mem_base + (reg)) = value) + +#endif + +#define NCR5380_implementation_fields \ + NCR5380_map_type NCR5380_map_name + +#define NCR5380_local_declare() \ + register NCR5380_implementation_fields + +#define NCR5380_setup(instance) \ + NCR5380_map_name = (NCR5380_map_type)((instance)->NCR5380_instance_name) + +#define NCR5380_intr generic_NCR5380_intr +#define NCR5380_queue_command generic_NCR5380_queue_command +#define NCR5380_abort generic_NCR5380_abort +#define NCR5380_reset generic_NCR5380_reset +#define NCR5380_pread generic_NCR5380_pread +#define NCR5380_pwrite generic_NCR5380_pwrite +#define NCR5380_proc_info notyet_generic_proc_info + +#define BOARD_NCR5380 0 +#define BOARD_NCR53C400 1 + +#endif /* else def HOSTS_C */ +#endif /* ndef ASM */ +#endif /* GENERIC_NCR5380_H */ + diff --git a/linux/src/drivers/scsi/gdth.c b/linux/src/drivers/scsi/gdth.c new file mode 100644 index 00000000..a150b873 --- /dev/null +++ b/linux/src/drivers/scsi/gdth.c @@ -0,0 +1,3598 @@ +/************************************************************************ + * GDT ISA/EISA/PCI Disk Array Controller driver for Linux * + * * + * gdth.c * + * Copyright (C) 1995-98 ICP vortex Computersysteme GmbH, Achim Leubner * + * * + * <achim@vortex.de> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published * + * by the Free Software Foundation; either version 2 of the License, * + * or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this kernel; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + * Tested with Linux 1.2.13, ..., 2.1.103 * + * * + * $Log: gdth.c,v $ + * Revision 1.16 1998/09/28 16:08:46 achim + * GDT_PCIMPR: DPMEM remapping, if required + * mdelay() added + * + * Revision 1.15 1998/06/03 14:54:06 achim + * gdth_delay(), gdth_flush() implemented + * Bugfix: gdth_release() changed + * + * Revision 1.14 1998/05/22 10:01:17 achim + * mj: pcibios_strerror() removed + * Improved SMP support (if version >= 2.1.95) + * gdth_halt(): halt_called flag added (if version < 2.1) + * + * Revision 1.13 1998/04/16 09:14:57 achim + * Reserve drives (for raw service) implemented + * New error handling code enabled + * Get controller name from board_info() IOCTL + * Final round of PCI device driver patches by Martin Mares + * + * Revision 1.12 1998/03/03 09:32:37 achim + * Fibre channel controller support added + * + * Revision 1.11 1998/01/27 16:19:14 achim + * SA_SHIRQ added + * add_timer()/del_timer() instead of GDTH_TIMER + * scsi_add_timer()/scsi_del_timer() instead of SCSI_TIMER + * New error handling included + * + * Revision 1.10 1997/10/31 12:29:57 achim + * Read heads/sectors from host drive + * + * Revision 1.9 1997/09/04 10:07:25 achim + * IO-mapping with virt_to_bus(), readb(), writeb(), ... + * register_reboot_notifier() to get a notify on shutdown used + * + * Revision 1.8 1997/04/02 12:14:30 achim + * Version 1.00 (see gdth.h), tested with kernel 2.0.29 + * + * Revision 1.7 1997/03/12 13:33:37 achim + * gdth_reset() changed, new async. events + * + * Revision 1.6 1997/03/04 14:01:11 achim + * Shutdown routine gdth_halt() implemented + * + * Revision 1.5 1997/02/21 09:08:36 achim + * New controller included (RP, RP1, RP2 series) + * IOCTL interface implemented + * + * Revision 1.4 1996/07/05 12:48:55 achim + * Function gdth_bios_param() implemented + * New constant GDTH_MAXC_P_L inserted + * GDT_WRITE_THR, GDT_EXT_INFO implemented + * Function gdth_reset() changed + * + * Revision 1.3 1996/05/10 09:04:41 achim + * Small changes for Linux 1.2.13 + * + * Revision 1.2 1996/05/09 12:45:27 achim + * Loadable module support implemented + * /proc support corrections made + * + * Revision 1.1 1996/04/11 07:35:57 achim + * Initial revision + * + * + * $Id: gdth.c,v 1.1 1999/04/26 05:54:35 tb Exp $ + ************************************************************************/ + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/head.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/in.h> +#include <linux/proc_fs.h> +#include <linux/time.h> +#include <linux/timer.h> +#if LINUX_VERSION_CODE >= 0x020100 +#include <linux/reboot.h> +#else +#include <linux/bios32.h> +#endif + +#include <asm/dma.h> +#include <asm/system.h> +#include <asm/io.h> +#if LINUX_VERSION_CODE >= 0x02015F +#include <asm/spinlock.h> +#endif + +#if LINUX_VERSION_CODE >= 0x010300 +#include <linux/blk.h> +#else +#include "../block/blk.h" +#endif +#include "scsi.h" +#include "hosts.h" +#include "sd.h" + +#include "gdth.h" + +/****************************************************************************/ + +/* LILO params: gdth=<IRQ> + * + * Where: <IRQ> is any of the valid IRQs for EISA controllers (10,11,12,14) + * Sets the IRQ of the GDT3000/3020 EISA controller to this value, + * if the IRQ can not automat. detect (controller BIOS disabled) + * See gdth_init_eisa() + * + * You can use the command line gdth=0 to disable the driver + */ +static unchar irqs[MAXHA] = {0xff}; +static unchar disable_gdth_scan = FALSE; + +/* Reserve drives for raw service: Fill the following structure with the + * appropriate values: Controller number, Channel, Target ID + */ +static gdth_reserve_str reserve_list[] = { + /* { 0, 1, 4 }, Example: Controller 0, Channel B, ID 4 */ + { 0xff, 0xff, 0xff } /* end of list */ +}; + +/****************************************************************************/ + +#if LINUX_VERSION_CODE >= 0x02015F +static void gdth_interrupt(int irq,void *dev_id,struct pt_regs *regs); +static void do_gdth_interrupt(int irq,void *dev_id,struct pt_regs *regs); +#elif LINUX_VERSION_CODE >= 0x010346 +static void gdth_interrupt(int irq,void *dev_id,struct pt_regs *regs); +#else +static void gdth_interrupt(int irq,struct pt_regs *regs); +#endif +static int gdth_sync_event(int hanum,int service,unchar index,Scsi_Cmnd *scp); +static int gdth_async_event(int hanum,int service); + +static void gdth_putq(int hanum,Scsi_Cmnd *scp,unchar priority); +static void gdth_next(int hanum); +static int gdth_fill_raw_cmd(int hanum,Scsi_Cmnd *scp,unchar b); +static int gdth_special_cmd(int hanum,Scsi_Cmnd *scp,unchar b); +static gdth_evt_str *gdth_store_event(ushort source, ushort idx, + gdth_evt_data *evt); +static int gdth_read_event(int handle, gdth_evt_str *estr); +static void gdth_readapp_event(unchar application, gdth_evt_str *estr); +static void gdth_clear_events(void); + +static void gdth_copy_internal_data(Scsi_Cmnd *scp,char *buffer,ushort count); +static int gdth_internal_cache_cmd(int hanum,Scsi_Cmnd *scp, + unchar b,ulong *flags); +static int gdth_fill_cache_cmd(int hanum,Scsi_Cmnd *scp,ushort hdrive); + +static int gdth_search_eisa(ushort eisa_adr); +static int gdth_search_isa(ulong bios_adr); +static int gdth_search_pci(ushort device_id,ushort index,gdth_pci_str *pcistr); +static int gdth_init_eisa(ushort eisa_adr,gdth_ha_str *ha); +static int gdth_init_isa(ulong bios_adr,gdth_ha_str *ha); +static int gdth_init_pci(gdth_pci_str *pcistr,gdth_ha_str *ha); + +static void gdth_enable_int(int hanum); +static int gdth_get_status(unchar *pIStatus,int irq); +static int gdth_test_busy(int hanum); +static int gdth_get_cmd_index(int hanum); +static void gdth_release_event(int hanum); +static int gdth_wait(int hanum,int index,ulong time); +static int gdth_internal_cmd(int hanum,unchar service,ushort opcode,ulong p1, + ulong p2,ulong p3); +static int gdth_search_drives(int hanum); + +static void *gdth_mmap(ulong paddr, ulong size); +static void gdth_munmap(void *addr); + +static const char *gdth_ctr_name(int hanum); + +static void gdth_flush(int hanum); +#if LINUX_VERSION_CODE >= 0x020100 +static int gdth_halt(struct notifier_block *nb, ulong event, void *buf); +#else +static int halt_called = FALSE; +void gdth_halt(void); +#endif + +#ifdef DEBUG_GDTH +static unchar DebugState = DEBUG_GDTH; +extern int sys_syslog(int,char*,int); +#define LOGEN sys_syslog(7,NULL,0) + +#ifdef __SERIAL__ +#define MAX_SERBUF 160 +static void ser_init(void); +static void ser_puts(char *str); +static void ser_putc(char c); +static int ser_printk(const char *fmt, ...); +static char strbuf[MAX_SERBUF+1]; +#ifdef __COM2__ +#define COM_BASE 0x2f8 +#else +#define COM_BASE 0x3f8 +#endif +static void ser_init() +{ + unsigned port=COM_BASE; + + outb(0x80,port+3); + outb(0,port+1); + /* 19200 Baud, if 9600: outb(12,port) */ + outb(6, port); + outb(3,port+3); + outb(0,port+1); + /* + ser_putc('I'); + ser_putc(' '); + */ +} + +static void ser_puts(char *str) +{ + char *ptr; + + ser_init(); + for (ptr=str;*ptr;++ptr) + ser_putc(*ptr); +} + +static void ser_putc(char c) +{ + unsigned port=COM_BASE; + + while ((inb(port+5) & 0x20)==0); + outb(c,port); + if (c==0x0a) + { + while ((inb(port+5) & 0x20)==0); + outb(0x0d,port); + } +} + +static int ser_printk(const char *fmt, ...) +{ + va_list args; + int i; + + va_start(args,fmt); + i = vsprintf(strbuf,fmt,args); + ser_puts(strbuf); + va_end(args); + return i; +} + +#define TRACE(a) {if (DebugState==1) {ser_printk a;}} +#define TRACE2(a) {if (DebugState==1 || DebugState==2) {ser_printk a;}} +#define TRACE3(a) {if (DebugState!=0) {ser_printk a;}} + +#else /* !__SERIAL__ */ +#define TRACE(a) {if (DebugState==1) {LOGEN;printk a;}} +#define TRACE2(a) {if (DebugState==1 || DebugState==2) {LOGEN;printk a;}} +#define TRACE3(a) {if (DebugState!=0) {LOGEN;printk a;}} +#endif + +#else /* !DEBUG */ +#define TRACE(a) +#define TRACE2(a) +#define TRACE3(a) +#endif + +#ifdef GDTH_STATISTICS +static ulong max_rq=0, max_index=0, max_sg=0; +static ulong act_ints=0, act_ios=0, act_stats=0, act_rq=0; +static struct timer_list gdth_timer; +#endif + +#define PTR2USHORT(a) (ushort)(ulong)(a) +#define GDTOFFSOF(a,b) (size_t)&(((a*)0)->b) +#define INDEX_OK(i,t) ((i)<sizeof(t)/sizeof((t)[0])) + +#define NUMDATA(a) ( (gdth_num_str *)((a)->hostdata)) +#define HADATA(a) (&((gdth_ext_str *)((a)->hostdata))->haext) +#define CMDDATA(a) (&((gdth_ext_str *)((a)->hostdata))->cmdext) +#define DMADATA(a) (&((gdth_ext_str *)((a)->hostdata))->dmaext) + + +#if LINUX_VERSION_CODE < 0x010300 +static void *gdth_mmap(ulong paddr, ulong size) +{ + if (paddr >= high_memory) + return NULL; + else + return (void *)paddr; +} +static void gdth_munmap(void *addr) +{ +} +inline ulong virt_to_phys(volatile void *addr) +{ + return (ulong)addr; +} +inline void *phys_to_virt(ulong addr) +{ + return (void *)addr; +} +#define virt_to_bus virt_to_phys +#define bus_to_virt phys_to_virt +#define readb(addr) (*(volatile unchar *)(addr)) +#define readw(addr) (*(volatile ushort *)(addr)) +#define readl(addr) (*(volatile ulong *)(addr)) +#define writeb(b,addr) (*(volatile unchar *)(addr) = (b)) +#define writew(b,addr) (*(volatile ushort *)(addr) = (b)) +#define writel(b,addr) (*(volatile ulong *)(addr) = (b)) +#define memset_io(a,b,c) memset((void *)(a),(b),(c)) +#define memcpy_fromio(a,b,c) memcpy((a),(void *)(b),(c)) +#define memcpy_toio(a,b,c) memcpy((void *)(a),(b),(c)) + +#elif LINUX_VERSION_CODE < 0x020100 +static int remapped = FALSE; +static void *gdth_mmap(ulong paddr, ulong size) +{ + if ( paddr >= high_memory) { + remapped = TRUE; + return vremap(paddr, size); + } else { + return (void *)paddr; + } +} +static void gdth_munmap(void *addr) +{ + if (remapped) + vfree(addr); + remapped = FALSE; +} +#else +static void *gdth_mmap(ulong paddr, ulong size) +{ + return ioremap(paddr, size); +} +static void gdth_munmap(void *addr) +{ + return iounmap(addr); +} +#endif + + +static unchar gdth_drq_tab[4] = {5,6,7,7}; /* DRQ table */ +static unchar gdth_irq_tab[6] = {0,10,11,12,14,0}; /* IRQ table */ +static unchar gdth_polling; /* polling if TRUE */ +static unchar gdth_from_wait = FALSE; /* gdth_wait() */ +static int wait_index,wait_hanum; /* gdth_wait() */ +static int gdth_ctr_count = 0; /* controller count */ +static int gdth_ctr_vcount = 0; /* virt. ctr. count */ +static int gdth_ctr_released = 0; /* gdth_release() */ +static struct Scsi_Host *gdth_ctr_tab[MAXHA]; /* controller table */ +static struct Scsi_Host *gdth_ctr_vtab[MAXHA*MAXBUS]; /* virt. ctr. table */ +static unchar gdth_write_through = FALSE; /* write through */ +static char *gdth_ioctl_tab[4][MAXHA]; /* ioctl buffer */ +static gdth_evt_str ebuffer[MAX_EVENTS]; /* event buffer */ +static int elastidx; +static int eoldidx; + +static struct { + Scsi_Cmnd *cmnd; /* pending request */ + ushort service; /* service */ +} gdth_cmd_tab[GDTH_MAXCMDS][MAXHA]; /* table of pend. requests */ + +#define DIN 1 /* IN data direction */ +#define DOU 2 /* OUT data direction */ +#define DNO DIN /* no data transfer */ +#define DUN DIN /* unknown data direction */ +static unchar gdth_direction_tab[0x100] = { + DNO,DNO,DIN,DIN,DOU,DIN,DIN,DOU,DIN,DUN,DOU,DOU,DUN,DUN,DUN,DIN, + DNO,DIN,DIN,DOU,DIN,DOU,DNO,DNO,DOU,DNO,DIN,DNO,DIN,DOU,DNO,DUN, + DIN,DUN,DIN,DUN,DOU,DIN,DUN,DUN,DIN,DIN,DIN,DUN,DUN,DIN,DIN,DIN, + DIN,DIN,DIN,DNO,DIN,DNO,DNO,DIN,DIN,DIN,DIN,DIN,DIN,DIN,DIN,DIN, + DIN,DIN,DIN,DIN,DIN,DNO,DUN,DNO,DNO,DNO,DUN,DNO,DIN,DIN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DIN,DUN,DUN,DUN,DUN,DIN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DNO,DNO,DUN,DIN,DNO,DIN,DUN,DNO,DUN,DIN,DIN, + DIN,DIN,DIN,DNO,DUN,DIN,DIN,DIN,DIN,DUN,DUN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DOU,DUN,DUN,DUN,DUN,DUN, + DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN,DUN +}; + +/* __initfunc, __initdata macros */ +#if LINUX_VERSION_CODE >= 0x020126 +#include <linux/init.h> +#else +#define __initfunc(A) A +#define __initdata +#define __init +#endif + +/* /proc support */ +#if LINUX_VERSION_CODE >= 0x010300 +#include <linux/stat.h> +struct proc_dir_entry proc_scsi_gdth = { + PROC_SCSI_GDTH, 4, "gdth", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; +#include "gdth_proc.h" +#include "gdth_proc.c" +#endif + +#if LINUX_VERSION_CODE >= 0x020100 +/* notifier block to get a notify on system shutdown/halt/reboot */ +static struct notifier_block gdth_notifier = { + gdth_halt, NULL, 0 +}; +#endif + +static void gdth_delay(int milliseconds) +{ + if (milliseconds == 0) { + udelay(1); + } else { +#if LINUX_VERSION_CODE >= 0x020168 + mdelay(milliseconds); +#else + int i; + for (i = 0; i < milliseconds; ++i) + udelay(1000); +#endif + } +} + +/* controller search and initialization functions */ + +__initfunc (static int gdth_search_eisa(ushort eisa_adr)) +{ + ulong id; + + TRACE(("gdth_search_eisa() adr. %x\n",eisa_adr)); + id = inl(eisa_adr+ID0REG); + if (id == GDT3A_ID || id == GDT3B_ID) { /* GDT3000A or GDT3000B */ + if ((inb(eisa_adr+EISAREG) & 8) == 0) + return 0; /* not EISA configured */ + return 1; + } + if (id == GDT3_ID) /* GDT3000 */ + return 1; + + return 0; +} + + +__initfunc (static int gdth_search_isa(ulong bios_adr)) +{ + void *addr; + ulong id; + + TRACE(("gdth_search_isa() bios adr. %lx\n",bios_adr)); + if ((addr = gdth_mmap(bios_adr+BIOS_ID_OFFS, sizeof(ulong))) != NULL) { + id = readl(addr); + gdth_munmap(addr); + if (id == GDT2_ID) /* GDT2000 */ + return 1; + } + return 0; +} + + +__initfunc (static int gdth_search_pci(ushort device_id,ushort index,gdth_pci_str *pcistr)) +{ + int error; + ulong base0,base1,base2; + + TRACE(("gdth_search_pci() device_id %d, index %d\n", + device_id,index)); + +#if LINUX_VERSION_CODE >= 0x20155 + if (!pci_present()) + return 0; +#else + if (!pcibios_present()) + return 0; +#endif + + if (pcibios_find_device(PCI_VENDOR_ID_VORTEX,device_id,index, + &pcistr->bus,&pcistr->device_fn)) + return 0; + + /* GDT PCI controller found, now read resources from config space */ +#if LINUX_VERSION_CODE >= 0x20155 + { + struct pci_dev *pdev = pci_find_slot(pcistr->bus, pcistr->device_fn); + base0 = pdev->base_address[0]; + base1 = pdev->base_address[1]; + base2 = pdev->base_address[2]; + if ((error = pcibios_read_config_dword(pcistr->bus,pcistr->device_fn, + PCI_ROM_ADDRESS, + (int *) &pcistr->bios))) { + printk("GDT-PCI: error %d reading configuration space", error); + return -1; + } + pcistr->irq = pdev->irq; + } +#else +#if LINUX_VERSION_CODE >= 0x010300 +#define GDTH_BASEP (int *) +#else +#define GDTH_BASEP +#endif + if ((error = pcibios_read_config_dword(pcistr->bus,pcistr->device_fn, + PCI_BASE_ADDRESS_0, + GDTH_BASEP&base0)) || + (error = pcibios_read_config_dword(pcistr->bus,pcistr->device_fn, + PCI_BASE_ADDRESS_1, + GDTH_BASEP&base1)) || + (error = pcibios_read_config_dword(pcistr->bus,pcistr->device_fn, + PCI_BASE_ADDRESS_2, + GDTH_BASEP&base2)) || + (error = pcibios_read_config_dword(pcistr->bus,pcistr->device_fn, + PCI_ROM_ADDRESS, + GDTH_BASEP&pcistr->bios)) || + (error = pcibios_read_config_byte(pcistr->bus,pcistr->device_fn, + PCI_INTERRUPT_LINE,&pcistr->irq))) { + printk("GDT-PCI: error %d reading configuration space", error); + return -1; + } +#endif + + pcistr->device_id = device_id; + if (device_id <= PCI_DEVICE_ID_VORTEX_GDT6000B || /* GDT6000 or GDT6000B */ + device_id >= PCI_DEVICE_ID_VORTEX_GDT6x17RP) { /* MPR */ + if ((base0 & PCI_BASE_ADDRESS_SPACE)!=PCI_BASE_ADDRESS_SPACE_MEMORY) + return -1; + pcistr->dpmem = base0 & PCI_BASE_ADDRESS_MEM_MASK; + } else { /* GDT6110, GDT6120, .. */ + if ((base0 & PCI_BASE_ADDRESS_SPACE)!=PCI_BASE_ADDRESS_SPACE_MEMORY || + (base2 & PCI_BASE_ADDRESS_SPACE)!=PCI_BASE_ADDRESS_SPACE_MEMORY || + (base1 & PCI_BASE_ADDRESS_SPACE)!=PCI_BASE_ADDRESS_SPACE_IO) + return -1; + pcistr->dpmem = base2 & PCI_BASE_ADDRESS_MEM_MASK; + pcistr->io_mm = base0 & PCI_BASE_ADDRESS_MEM_MASK; + pcistr->io = base1 & PCI_BASE_ADDRESS_IO_MASK; + } + return 1; +} + + +__initfunc (static int gdth_init_eisa(ushort eisa_adr,gdth_ha_str *ha)) +{ + ulong retries,id; + unchar prot_ver,eisacf,i,irq_found; + + TRACE(("gdth_init_eisa() adr. %x\n",eisa_adr)); + + /* disable board interrupts, deinitialize services */ + outb(0xff,eisa_adr+EDOORREG); + outb(0x00,eisa_adr+EDENABREG); + outb(0x00,eisa_adr+EINTENABREG); + + outb(0xff,eisa_adr+LDOORREG); + retries = INIT_RETRIES; + gdth_delay(20); + while (inb(eisa_adr+EDOORREG) != 0xff) { + if (--retries == 0) { + printk("GDT-EISA: Initialization error (DEINIT failed)\n"); + return 0; + } + gdth_delay(1); + TRACE2(("wait for DEINIT: retries=%ld\n",retries)); + } + prot_ver = inb(eisa_adr+MAILBOXREG); + outb(0xff,eisa_adr+EDOORREG); + if (prot_ver != PROTOCOL_VERSION) { + printk("GDT-EISA: Illegal protocol version\n"); + return 0; + } + ha->bmic = eisa_adr; + ha->brd_phys = (ulong)eisa_adr >> 12; + + outl(0,eisa_adr+MAILBOXREG); + outl(0,eisa_adr+MAILBOXREG+4); + outl(0,eisa_adr+MAILBOXREG+8); + outl(0,eisa_adr+MAILBOXREG+12); + + /* detect IRQ */ + if ((id = inl(eisa_adr+ID0REG)) == GDT3_ID) { + ha->type = GDT_EISA; + ha->stype = id; + outl(1,eisa_adr+MAILBOXREG+8); + outb(0xfe,eisa_adr+LDOORREG); + retries = INIT_RETRIES; + gdth_delay(20); + while (inb(eisa_adr+EDOORREG) != 0xfe) { + if (--retries == 0) { + printk("GDT-EISA: Initialization error (get IRQ failed)\n"); + return 0; + } + gdth_delay(1); + } + ha->irq = inb(eisa_adr+MAILBOXREG); + outb(0xff,eisa_adr+EDOORREG); + TRACE2(("GDT3000/3020: IRQ=%d\n",ha->irq)); + /* check the result */ + if (ha->irq == 0) { + TRACE2(("Unknown IRQ, check IRQ table from cmd line !\n")); + for (i=0,irq_found=FALSE; i<MAXHA && irqs[i]!=0xff; ++i) { + if (irqs[i]!=0) { + irq_found=TRUE; + break; + } + } + if (irq_found) { + ha->irq = irqs[i]; + irqs[i] = 0; + printk("GDT-EISA: Can not detect controller IRQ,\n"); + printk("Use IRQ setting from command line (IRQ = %d)\n", + ha->irq); + } else { + printk("GDT-EISA: Initialization error (unknown IRQ), Enable\n"); + printk("the controller BIOS or use command line parameters\n"); + return 0; + } + } + } else { + eisacf = inb(eisa_adr+EISAREG) & 7; + if (eisacf > 4) /* level triggered */ + eisacf -= 4; + ha->irq = gdth_irq_tab[eisacf]; + ha->type = GDT_EISA; + ha->stype= id; + } + return 1; +} + + +__initfunc (static int gdth_init_isa(ulong bios_adr,gdth_ha_str *ha)) +{ + register gdt2_dpram_str *dp2_ptr; + int i; + unchar irq_drq,prot_ver; + ulong retries; + + TRACE(("gdth_init_isa() bios adr. %lx\n",bios_adr)); + + ha->brd = gdth_mmap(bios_adr, sizeof(gdt2_dpram_str)); + if (ha->brd == NULL) { + printk("GDT-ISA: Initialization error (DPMEM remap error)\n"); + return 0; + } + dp2_ptr = (gdt2_dpram_str *)ha->brd; + writeb(1, &dp2_ptr->io.memlock); /* switch off write protection */ + /* reset interface area */ + memset_io((char *)&dp2_ptr->u,0,sizeof(dp2_ptr->u)); + if (readl(&dp2_ptr->u) != 0) { + printk("GDT-PCI: Initialization error (DPMEM write error)\n"); + gdth_munmap(ha->brd); + return 0; + } + + /* disable board interrupts, read DRQ and IRQ */ + writeb(0xff, &dp2_ptr->io.irqdel); + writeb(0x00, &dp2_ptr->io.irqen); + writeb(0x00, &dp2_ptr->u.ic.S_Status); + writeb(0x00, &dp2_ptr->u.ic.Cmd_Index); + + irq_drq = readb(&dp2_ptr->io.rq); + for (i=0; i<3; ++i) { + if ((irq_drq & 1)==0) + break; + irq_drq >>= 1; + } + ha->drq = gdth_drq_tab[i]; + + irq_drq = readb(&dp2_ptr->io.rq) >> 3; + for (i=1; i<5; ++i) { + if ((irq_drq & 1)==0) + break; + irq_drq >>= 1; + } + ha->irq = gdth_irq_tab[i]; + + /* deinitialize services */ + writel(bios_adr, &dp2_ptr->u.ic.S_Info[0]); + writeb(0xff, &dp2_ptr->u.ic.S_Cmd_Indx); + writeb(0, &dp2_ptr->io.event); + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp2_ptr->u.ic.S_Status) != 0xff) { + if (--retries == 0) { + printk("GDT-ISA: Initialization error (DEINIT failed)\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + prot_ver = (unchar)readl(&dp2_ptr->u.ic.S_Info[0]); + writeb(0, &dp2_ptr->u.ic.Status); + writeb(0xff, &dp2_ptr->io.irqdel); + if (prot_ver != PROTOCOL_VERSION) { + printk("GDT-ISA: Illegal protocol version\n"); + gdth_munmap(ha->brd); + return 0; + } + + ha->type = GDT_ISA; + ha->ic_all_size = sizeof(dp2_ptr->u); + ha->stype= GDT2_ID; + ha->brd_phys = bios_adr >> 4; + + /* special request to controller BIOS */ + writel(0x00, &dp2_ptr->u.ic.S_Info[0]); + writel(0x00, &dp2_ptr->u.ic.S_Info[1]); + writel(0x01, &dp2_ptr->u.ic.S_Info[2]); + writel(0x00, &dp2_ptr->u.ic.S_Info[3]); + writeb(0xfe, &dp2_ptr->u.ic.S_Cmd_Indx); + writeb(0, &dp2_ptr->io.event); + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp2_ptr->u.ic.S_Status) != 0xfe) { + if (--retries == 0) { + printk("GDT-ISA: Initialization error\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + writeb(0, &dp2_ptr->u.ic.Status); + writeb(0xff, &dp2_ptr->io.irqdel); + return 1; +} + + +__initfunc (static int gdth_init_pci(gdth_pci_str *pcistr,gdth_ha_str *ha)) +{ + register gdt6_dpram_str *dp6_ptr; + register gdt6c_dpram_str *dp6c_ptr; + register gdt6m_dpram_str *dp6m_ptr; + ulong retries; + unchar prot_ver; + int i, found = FALSE; + + TRACE(("gdth_init_pci()\n")); + + ha->brd_phys = (pcistr->bus << 8) | (pcistr->device_fn & 0xf8); + ha->stype = (ulong)pcistr->device_id; + ha->irq = pcistr->irq; + + if (ha->stype <= PCI_DEVICE_ID_VORTEX_GDT6000B) { /* GDT6000 or GDT6000B */ + TRACE2(("init_pci() dpmem %lx irq %d\n",pcistr->dpmem,ha->irq)); + ha->brd = gdth_mmap(pcistr->dpmem, sizeof(gdt6_dpram_str)); + if (ha->brd == NULL) { + printk("GDT-PCI: Initialization error (DPMEM remap error)\n"); + return 0; + } + dp6_ptr = (gdt6_dpram_str *)ha->brd; + /* reset interface area */ + memset_io((char *)&dp6_ptr->u,0,sizeof(dp6_ptr->u)); + if (readl(&dp6_ptr->u) != 0) { + printk("GDT-PCI: Initialization error (DPMEM write error)\n"); + gdth_munmap(ha->brd); + return 0; + } + + /* disable board interrupts, deinit services */ + writeb(0xff, &dp6_ptr->io.irqdel); + writeb(0x00, &dp6_ptr->io.irqen);; + writeb(0x00, &dp6_ptr->u.ic.S_Status); + writeb(0x00, &dp6_ptr->u.ic.Cmd_Index); + + writel(pcistr->dpmem, &dp6_ptr->u.ic.S_Info[0]); + writeb(0xff, &dp6_ptr->u.ic.S_Cmd_Indx); + writeb(0, &dp6_ptr->io.event); + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp6_ptr->u.ic.S_Status) != 0xff) { + if (--retries == 0) { + printk("GDT-PCI: Initialization error (DEINIT failed)\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + prot_ver = (unchar)readl(&dp6_ptr->u.ic.S_Info[0]); + writeb(0, &dp6_ptr->u.ic.S_Status); + writeb(0xff, &dp6_ptr->io.irqdel); + if (prot_ver != PROTOCOL_VERSION) { + printk("GDT-PCI: Illegal protocol version\n"); + gdth_munmap(ha->brd); + return 0; + } + + ha->type = GDT_PCI; + ha->ic_all_size = sizeof(dp6_ptr->u); + + /* special command to controller BIOS */ + writel(0x00, &dp6_ptr->u.ic.S_Info[0]); + writel(0x00, &dp6_ptr->u.ic.S_Info[1]); + writel(0x01, &dp6_ptr->u.ic.S_Info[2]); + writel(0x00, &dp6_ptr->u.ic.S_Info[3]); + writeb(0xfe, &dp6_ptr->u.ic.S_Cmd_Indx); + writeb(0, &dp6_ptr->io.event); + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp6_ptr->u.ic.S_Status) != 0xfe) { + if (--retries == 0) { + printk("GDT-PCI: Initialization error\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + writeb(0, &dp6_ptr->u.ic.S_Status); + writeb(0xff, &dp6_ptr->io.irqdel); + + } else if (ha->stype <= PCI_DEVICE_ID_VORTEX_GDT6555) { /* GDT6110, GDT6120, .. */ + ha->plx = (gdt6c_plx_regs *)pcistr->io; + TRACE2(("init_pci_new() dpmem %lx io %lx irq %d\n", + pcistr->dpmem,(ulong)ha->plx,ha->irq)); + ha->brd = gdth_mmap(pcistr->dpmem, sizeof(gdt6c_dpram_str)); + if (ha->brd == NULL) { + printk("GDT-PCI: Initialization error (DPMEM remap error)\n"); + gdth_munmap(ha->brd); + return 0; + } + dp6c_ptr = (gdt6c_dpram_str *)ha->brd; + /* reset interface area */ + memset_io((char *)&dp6c_ptr->u,0,sizeof(dp6c_ptr->u)); + if (readl(&dp6c_ptr->u) != 0) { + printk("GDT-PCI: Initialization error (DPMEM write error)\n"); + gdth_munmap(ha->brd); + return 0; + } + + /* disable board interrupts, deinit services */ + outb(0x00,PTR2USHORT(&ha->plx->control1)); + outb(0xff,PTR2USHORT(&ha->plx->edoor_reg)); + + writeb(0x00, &dp6c_ptr->u.ic.S_Status); + writeb(0x00, &dp6c_ptr->u.ic.Cmd_Index); + + writel(pcistr->dpmem, &dp6c_ptr->u.ic.S_Info[0]); + writeb(0xff, &dp6c_ptr->u.ic.S_Cmd_Indx); + + outb(1,PTR2USHORT(&ha->plx->ldoor_reg)); + + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp6c_ptr->u.ic.S_Status) != 0xff) { + if (--retries == 0) { + printk("GDT-PCI: Initialization error (DEINIT failed)\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + prot_ver = (unchar)readl(&dp6c_ptr->u.ic.S_Info[0]); + writeb(0, &dp6c_ptr->u.ic.Status); + if (prot_ver != PROTOCOL_VERSION) { + printk("GDT-PCI: Illegal protocol version\n"); + gdth_munmap(ha->brd); + return 0; + } + + ha->type = GDT_PCINEW; + ha->ic_all_size = sizeof(dp6c_ptr->u); + + /* special command to controller BIOS */ + writel(0x00, &dp6c_ptr->u.ic.S_Info[0]); + writel(0x00, &dp6c_ptr->u.ic.S_Info[1]); + writel(0x01, &dp6c_ptr->u.ic.S_Info[2]); + writel(0x00, &dp6c_ptr->u.ic.S_Info[3]); + writeb(0xfe, &dp6c_ptr->u.ic.S_Cmd_Indx); + + outb(1,PTR2USHORT(&ha->plx->ldoor_reg)); + + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp6c_ptr->u.ic.S_Status) != 0xfe) { + if (--retries == 0) { + printk("GDT-PCI: Initialization error\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + writeb(0, &dp6c_ptr->u.ic.S_Status); + + } else { /* MPR */ + TRACE2(("init_pci_mpr() dpmem %lx irq %d\n",pcistr->dpmem,ha->irq)); + ha->brd = gdth_mmap(pcistr->dpmem, sizeof(gdt6m_dpram_str)); + if (ha->brd == NULL) { + printk("GDT-PCI: Initialization error (DPMEM remap error)\n"); + return 0; + } + + /* check and reset interface area */ + dp6m_ptr = (gdt6m_dpram_str *)ha->brd; + writel(DPMEM_MAGIC, &dp6m_ptr->u); + if (readl(&dp6m_ptr->u) != DPMEM_MAGIC) { + printk("GDT-PCI: Cannot access DPMEM at 0x%x (shadowed?)\n", + (int)ha->brd); + found = FALSE; + for (i = 0xC8000; i < 0xE8000; i += 0x4000) { + pcibios_write_config_dword( pcistr->bus, pcistr->device_fn, + PCI_BASE_ADDRESS_0, i ); + gdth_munmap( ha->brd ); + ha->brd = gdth_mmap(i, sizeof(gdt6m_dpram_str)); + if (ha->brd == NULL) { + printk("GDT-PCI: Initialization error (DPMEM remap error)\n"); + return 0; + } + dp6m_ptr = (gdt6m_dpram_str *)ha->brd; + writel(DPMEM_MAGIC, &dp6m_ptr->u); + if (readl(&dp6m_ptr->u) == DPMEM_MAGIC) { + printk("GDT-PCI: Use free address at 0x%x\n", + (int)ha->brd); + found = TRUE; + break; + } + } + if (!found) { + printk("GDT-PCI: No free address found!\n"); + gdth_munmap( ha->brd ); + return 0; + } + } + memset_io((char *)&dp6m_ptr->u,0,sizeof(dp6m_ptr->u)); + + /* disable board interrupts, deinit services */ + writeb(readb(&dp6m_ptr->i960r.edoor_en_reg) | 4, + &dp6m_ptr->i960r.edoor_en_reg); + writeb(0xff, &dp6m_ptr->i960r.edoor_reg); + writeb(0x00, &dp6m_ptr->u.ic.S_Status); + writeb(0x00, &dp6m_ptr->u.ic.Cmd_Index); + + writel(pcistr->dpmem, &dp6m_ptr->u.ic.S_Info[0]); + writeb(0xff, &dp6m_ptr->u.ic.S_Cmd_Indx); + writeb(1, &dp6m_ptr->i960r.ldoor_reg); + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp6m_ptr->u.ic.S_Status) != 0xff) { + if (--retries == 0) { + printk("GDT-PCI: Initialization error (DEINIT failed)\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + prot_ver = (unchar)readl(&dp6m_ptr->u.ic.S_Info[0]); + writeb(0, &dp6m_ptr->u.ic.S_Status); + if (prot_ver != PROTOCOL_VERSION) { + printk("GDT-PCI: Illegal protocol version\n"); + gdth_munmap(ha->brd); + return 0; + } + + ha->type = GDT_PCIMPR; + ha->ic_all_size = sizeof(dp6m_ptr->u); + + /* special command to controller BIOS */ + writel(0x00, &dp6m_ptr->u.ic.S_Info[0]); + writel(0x00, &dp6m_ptr->u.ic.S_Info[1]); + writel(0x01, &dp6m_ptr->u.ic.S_Info[2]); + writel(0x00, &dp6m_ptr->u.ic.S_Info[3]); + writeb(0xfe, &dp6m_ptr->u.ic.S_Cmd_Indx); + writeb(1, &dp6m_ptr->i960r.ldoor_reg); + retries = INIT_RETRIES; + gdth_delay(20); + while (readb(&dp6m_ptr->u.ic.S_Status) != 0xfe) { + if (--retries == 0) { + printk("GDT-PCI: Initialization error\n"); + gdth_munmap(ha->brd); + return 0; + } + gdth_delay(1); + } + writeb(0, &dp6m_ptr->u.ic.S_Status); + } + + return 1; +} + + +/* controller protocol functions */ + +__initfunc (static void gdth_enable_int(int hanum)) +{ + gdth_ha_str *ha; + ulong flags; + gdt2_dpram_str *dp2_ptr; + gdt6_dpram_str *dp6_ptr; + gdt6m_dpram_str *dp6m_ptr; + + TRACE(("gdth_enable_int() hanum %d\n",hanum)); + ha = HADATA(gdth_ctr_tab[hanum]); + + save_flags(flags); + cli(); + + if (ha->type == GDT_EISA) { + outb(0xff, ha->bmic + EDOORREG); + outb(0xff, ha->bmic + EDENABREG); + outb(0x01, ha->bmic + EINTENABREG); + } else if (ha->type == GDT_ISA) { + dp2_ptr = (gdt2_dpram_str *)ha->brd; + writeb(1, &dp2_ptr->io.irqdel); + writeb(0, &dp2_ptr->u.ic.Cmd_Index); + writeb(1, &dp2_ptr->io.irqen); + } else if (ha->type == GDT_PCI) { + dp6_ptr = (gdt6_dpram_str *)ha->brd; + writeb(1, &dp6_ptr->io.irqdel); + writeb(0, &dp6_ptr->u.ic.Cmd_Index); + writeb(1, &dp6_ptr->io.irqen); + } else if (ha->type == GDT_PCINEW) { + outb(0xff, PTR2USHORT(&ha->plx->edoor_reg)); + outb(0x03, PTR2USHORT(&ha->plx->control1)); + } else if (ha->type == GDT_PCIMPR) { + dp6m_ptr = (gdt6m_dpram_str *)ha->brd; + writeb(0xff, &dp6m_ptr->i960r.edoor_reg); + writeb(readb(&dp6m_ptr->i960r.edoor_en_reg) & ~4, + &dp6m_ptr->i960r.edoor_en_reg); + } + restore_flags(flags); +} + + +static int gdth_get_status(unchar *pIStatus,int irq) +{ + register gdth_ha_str *ha; + int i; + + TRACE(("gdth_get_status() irq %d ctr_count %d\n", + irq,gdth_ctr_count)); + + *pIStatus = 0; + for (i=0; i<gdth_ctr_count; ++i) { + ha = HADATA(gdth_ctr_tab[i]); + if (ha->irq != (unchar)irq) /* check IRQ */ + continue; + if (ha->type == GDT_EISA) + *pIStatus = inb((ushort)ha->bmic + EDOORREG); + else if (ha->type == GDT_ISA) + *pIStatus = readb(&((gdt2_dpram_str *)ha->brd)->u.ic.Cmd_Index); + else if (ha->type == GDT_PCI) + *pIStatus = readb(&((gdt6_dpram_str *)ha->brd)->u.ic.Cmd_Index); + else if (ha->type == GDT_PCINEW) + *pIStatus = inb(PTR2USHORT(&ha->plx->edoor_reg)); + else if (ha->type == GDT_PCIMPR) + *pIStatus = readb(&((gdt6m_dpram_str *)ha->brd)->i960r.edoor_reg); + + if (*pIStatus) + return i; /* board found */ + } + return -1; +} + + +static int gdth_test_busy(int hanum) +{ + register gdth_ha_str *ha; + register int gdtsema0 = 0; + + TRACE(("gdth_test_busy() hanum %d\n",hanum)); + + ha = HADATA(gdth_ctr_tab[hanum]); + if (ha->type == GDT_EISA) + gdtsema0 = (int)inb(ha->bmic + SEMA0REG); + else if (ha->type == GDT_ISA) + gdtsema0 = (int)readb(&((gdt2_dpram_str *)ha->brd)->u.ic.Sema0); + else if (ha->type == GDT_PCI) + gdtsema0 = (int)readb(&((gdt6_dpram_str *)ha->brd)->u.ic.Sema0); + else if (ha->type == GDT_PCINEW) + gdtsema0 = (int)inb(PTR2USHORT(&ha->plx->sema0_reg)); + else if (ha->type == GDT_PCIMPR) + gdtsema0 = (int)readb(&((gdt6m_dpram_str *)ha->brd)->i960r.sema0_reg); + + return (gdtsema0 & 1); +} + + +static int gdth_get_cmd_index(int hanum) +{ + register gdth_ha_str *ha; + int i; + + TRACE(("gdth_get_cmd_index() hanum %d\n",hanum)); + + ha = HADATA(gdth_ctr_tab[hanum]); + for (i=0; i<GDTH_MAXCMDS; ++i) { + if (gdth_cmd_tab[i][hanum].cmnd == UNUSED_CMND) { + gdth_cmd_tab[i][hanum].cmnd = ha->pccb->RequestBuffer; + gdth_cmd_tab[i][hanum].service = ha->pccb->Service; + ha->pccb->CommandIndex = (ulong)i+2; + return (i+2); + } + } + return 0; +} + + +static void gdth_set_sema0(int hanum) +{ + register gdth_ha_str *ha; + + TRACE(("gdth_set_sema0() hanum %d\n",hanum)); + + ha = HADATA(gdth_ctr_tab[hanum]); + if (ha->type == GDT_EISA) + outb(1, ha->bmic + SEMA0REG); + else if (ha->type == GDT_ISA) + writeb(1, &((gdt2_dpram_str *)ha->brd)->u.ic.Sema0); + else if (ha->type == GDT_PCI) + writeb(1, &((gdt6_dpram_str *)ha->brd)->u.ic.Sema0); + else if (ha->type == GDT_PCINEW) + outb(1, PTR2USHORT(&ha->plx->sema0_reg)); + else if (ha->type == GDT_PCIMPR) + writeb(1, &((gdt6m_dpram_str *)ha->brd)->i960r.sema0_reg); + +} + + +static void gdth_copy_command(int hanum) +{ + register gdth_ha_str *ha; + register gdth_cmd_str *cmd_ptr; + register gdt6m_dpram_str *dp6m_ptr; + register gdt6c_dpram_str *dp6c_ptr; + gdt6_dpram_str *dp6_ptr; + gdt2_dpram_str *dp2_ptr; + ushort cp_count,dp_offset,cmd_no; + + TRACE(("gdth_copy_command() hanum %d\n",hanum)); + + ha = HADATA(gdth_ctr_tab[hanum]); + cp_count = ha->cmd_len; + dp_offset= ha->cmd_offs_dpmem; + cmd_no = ha->cmd_cnt; + cmd_ptr = ha->pccb; + + ++ha->cmd_cnt; + if (ha->type == GDT_EISA) + return; /* no DPMEM, no copy */ + + /* set cpcount dword aligned */ + if (cp_count & 3) + cp_count += (4 - (cp_count & 3)); + + ha->cmd_offs_dpmem += cp_count; + + /* set offset and service, copy command to DPMEM */ + if (ha->type == GDT_ISA) { + dp2_ptr = (gdt2_dpram_str *)ha->brd; + writew(dp_offset + DPMEM_COMMAND_OFFSET, + &dp2_ptr->u.ic.comm_queue[cmd_no].offset); + writew((ushort)cmd_ptr->Service, + &dp2_ptr->u.ic.comm_queue[cmd_no].serv_id); + memcpy_toio(&dp2_ptr->u.ic.gdt_dpr_cmd[dp_offset],cmd_ptr,cp_count); + } else if (ha->type == GDT_PCI) { + dp6_ptr = (gdt6_dpram_str *)ha->brd; + writew(dp_offset + DPMEM_COMMAND_OFFSET, + &dp6_ptr->u.ic.comm_queue[cmd_no].offset); + writew((ushort)cmd_ptr->Service, + &dp6_ptr->u.ic.comm_queue[cmd_no].serv_id); + memcpy_toio(&dp6_ptr->u.ic.gdt_dpr_cmd[dp_offset],cmd_ptr,cp_count); + } else if (ha->type == GDT_PCINEW) { + dp6c_ptr = (gdt6c_dpram_str *)ha->brd; + writew(dp_offset + DPMEM_COMMAND_OFFSET, + &dp6c_ptr->u.ic.comm_queue[cmd_no].offset); + writew((ushort)cmd_ptr->Service, + &dp6c_ptr->u.ic.comm_queue[cmd_no].serv_id); + memcpy_toio(&dp6c_ptr->u.ic.gdt_dpr_cmd[dp_offset],cmd_ptr,cp_count); + } else if (ha->type == GDT_PCIMPR) { + dp6m_ptr = (gdt6m_dpram_str *)ha->brd; + writew(dp_offset + DPMEM_COMMAND_OFFSET, + &dp6m_ptr->u.ic.comm_queue[cmd_no].offset); + writew((ushort)cmd_ptr->Service, + &dp6m_ptr->u.ic.comm_queue[cmd_no].serv_id); + memcpy_toio(&dp6m_ptr->u.ic.gdt_dpr_cmd[dp_offset],cmd_ptr,cp_count); + } +} + + +static void gdth_release_event(int hanum) +{ + register gdth_ha_str *ha; + +#ifdef GDTH_STATISTICS + ulong i,j; + for (i=0,j=0; j<GDTH_MAXCMDS; ++j) { + if (gdth_cmd_tab[j][hanum].cmnd != UNUSED_CMND) + ++i; + } + if (max_index < i) { + max_index = i; + TRACE3(("GDT: max_index = %d\n",(ushort)i)); + } +#endif + + TRACE(("gdth_release_event() hanum %d\n",hanum)); + ha = HADATA(gdth_ctr_tab[hanum]); + + if (ha->pccb->OpCode == GDT_INIT) + ha->pccb->Service |= 0x80; + + if (ha->type == GDT_EISA) { + outb(ha->pccb->Service, ha->bmic + LDOORREG); + if (ha->pccb->OpCode == GDT_INIT) /* store DMA buffer */ + outl((ulong)ha->pccb, ha->bmic + MAILBOXREG); + } else if (ha->type == GDT_ISA) + writeb(0, &((gdt2_dpram_str *)ha->brd)->io.event); + else if (ha->type == GDT_PCI) + writeb(0, &((gdt6_dpram_str *)ha->brd)->io.event); + else if (ha->type == GDT_PCINEW) + outb(1, PTR2USHORT(&ha->plx->ldoor_reg)); + else if (ha->type == GDT_PCIMPR) + writeb(1, &((gdt6m_dpram_str *)ha->brd)->i960r.ldoor_reg); +} + + +static int gdth_wait(int hanum,int index,ulong time) +{ + gdth_ha_str *ha; + int answer_found = FALSE; + + TRACE(("gdth_wait() hanum %d index %d time %ld\n",hanum,index,time)); + + ha = HADATA(gdth_ctr_tab[hanum]); + if (index == 0) + return 1; /* no wait required */ + + gdth_from_wait = TRUE; + do { +#if LINUX_VERSION_CODE >= 0x010346 + gdth_interrupt((int)ha->irq,NULL,NULL); +#else + gdth_interrupt((int)ha->irq,NULL); +#endif + if (wait_hanum==hanum && wait_index==index) { + answer_found = TRUE; + break; + } + gdth_delay(1); + } while (--time); + gdth_from_wait = FALSE; + + while (gdth_test_busy(hanum)) + gdth_delay(0); + + return (answer_found); +} + + +static int gdth_internal_cmd(int hanum,unchar service,ushort opcode,ulong p1, + ulong p2,ulong p3) +{ + register gdth_ha_str *ha; + register gdth_cmd_str *cmd_ptr; + int retries,index; + + TRACE2(("gdth_internal_cmd() service %d opcode %d\n",service,opcode)); + + ha = HADATA(gdth_ctr_tab[hanum]); + cmd_ptr = ha->pccb; + memset((char*)cmd_ptr,0,sizeof(gdth_cmd_str)); + + /* make command */ + for (retries = INIT_RETRIES;;) { + cmd_ptr->Service = service; + cmd_ptr->RequestBuffer = INTERNAL_CMND; + if (!(index=gdth_get_cmd_index(hanum))) { + TRACE(("GDT: No free command index found\n")); + return 0; + } + gdth_set_sema0(hanum); + cmd_ptr->OpCode = opcode; + cmd_ptr->BoardNode = LOCALBOARD; + if (service == CACHESERVICE) { + if (opcode == GDT_IOCTL) { + cmd_ptr->u.ioctl.subfunc = p1; + cmd_ptr->u.ioctl.channel = p2; + cmd_ptr->u.ioctl.param_size = (ushort)p3; + cmd_ptr->u.ioctl.p_param = virt_to_bus(ha->pscratch); + } else { + cmd_ptr->u.cache.DeviceNo = (ushort)p1; + cmd_ptr->u.cache.BlockNo = p2; + } + } else if (service == SCSIRAWSERVICE) { + cmd_ptr->u.raw.direction = p1; + cmd_ptr->u.raw.bus = (unchar)p2; + cmd_ptr->u.raw.target = (unchar)p3; + cmd_ptr->u.raw.lun = 0; + } + ha->cmd_len = sizeof(gdth_cmd_str); + ha->cmd_offs_dpmem = 0; + ha->cmd_cnt = 0; + gdth_copy_command(hanum); + gdth_release_event(hanum); + gdth_delay(20); + if (!gdth_wait(hanum,index,INIT_TIMEOUT)) { + printk("GDT: Initialization error (timeout service %d)\n",service); + return 0; + } + if (ha->status != S_BSY || --retries == 0) + break; + gdth_delay(1); + } + + return (ha->status != S_OK ? 0:1); +} + + +/* search for devices */ + +__initfunc (static int gdth_search_drives(int hanum)) +{ + register gdth_ha_str *ha; + ushort cdev_cnt,i; + unchar b,t,pos_found; + ulong drv_cyls, drv_hds, drv_secs; + ulong bus_no; + gdth_getch_str *chn; + gdth_iochan_str *ioc; + + TRACE(("gdth_search_drives() hanum %d\n",hanum)); + ha = HADATA(gdth_ctr_tab[hanum]); + + /* initialize controller services, at first: screen service */ + if (!gdth_internal_cmd(hanum,SCREENSERVICE,GDT_INIT,0,0,0)) { + printk("GDT: Initialization error screen service (code %d)\n", + ha->status); + return 0; + } + TRACE2(("gdth_search_drives(): SCREENSERVICE initialized\n")); + + /* initialize cache service */ + if (!gdth_internal_cmd(hanum,CACHESERVICE,GDT_INIT,LINUX_OS,0,0)) { + printk("GDT: Initialization error cache service (code %d)\n", + ha->status); + return 0; + } + TRACE2(("gdth_search_drives(): CACHESERVICE initialized\n")); + cdev_cnt = (ushort)ha->info; + + /* mount all cache devices */ + gdth_internal_cmd(hanum,CACHESERVICE,GDT_MOUNT,0xffff,1,0); + TRACE2(("gdth_search_drives(): mountall CACHESERVICE OK\n")); + + /* initialize cache service after mountall */ + if (!gdth_internal_cmd(hanum,CACHESERVICE,GDT_INIT,LINUX_OS,0,0)) { + printk("GDT: Initialization error cache service (code %d)\n", + ha->status); + return 0; + } + TRACE2(("gdth_search_drives() CACHES. init. after mountall\n")); + cdev_cnt = (ushort)ha->info; + + /* detect number of SCSI buses - try new IOCTL */ + ioc = (gdth_iochan_str *)DMADATA(gdth_ctr_tab[hanum]); + ioc->version = -1UL; + ioc->list_entries = MAXBUS; + ioc->first_chan = 0; + ioc->last_chan = MAXBUS-1; + ioc->list_offset = GDTOFFSOF(gdth_iochan_str, list[0]); + if (gdth_internal_cmd(hanum,CACHESERVICE,GDT_IOCTL,GET_IOCHAN_DESC, + INVALID_CHANNEL,sizeof(gdth_iochan_str))) { + TRACE2(("GET_IOCHAN_DESC supported!\n")); + ha->bus_cnt = ioc->chan_count; + for (bus_no = 0; bus_no < ha->bus_cnt; ++bus_no) + if (ioc->list[bus_no].proc_id < MAXID) + ha->id[bus_no][ioc->list[bus_no].proc_id].type = SIOP_DTYP; + } else { + /* old method */ + chn = (gdth_getch_str *)DMADATA(gdth_ctr_tab[hanum]); + for (bus_no = 0; bus_no < MAXBUS; ++bus_no) { + chn->channel_no = bus_no; + if (!gdth_internal_cmd(hanum,CACHESERVICE,GDT_IOCTL, + SCSI_CHAN_CNT | L_CTRL_PATTERN, + IO_CHANNEL | INVALID_CHANNEL, + sizeof(gdth_getch_str))) { + if (bus_no == 0) { + printk("GDT: Error detecting SCSI channel count (0x%x)\n", + ha->status); + return 0; + } + break; + } + if (chn->siop_id < MAXID) + ha->id[bus_no][chn->siop_id].type = SIOP_DTYP; + } + ha->bus_cnt = (unchar)bus_no; + } + TRACE2(("gdth_search_drives() %d SCSI channels\n",ha->bus_cnt)); + + /* read cache configuration */ + if (!gdth_internal_cmd(hanum,CACHESERVICE,GDT_IOCTL,CACHE_INFO, + INVALID_CHANNEL,sizeof(gdth_cinfo_str))) { + printk("GDT: Initialization error cache service (code %d)\n", + ha->status); + return 0; + } + ha->cpar = ((gdth_cinfo_str *)DMADATA(gdth_ctr_tab[hanum]))->cpar; + TRACE2(("gdth_search_drives() cinfo: vs %lx sta %d str %d dw %d b %d\n", + ha->cpar.version,ha->cpar.state,ha->cpar.strategy, + ha->cpar.write_back,ha->cpar.block_size)); + + /* read board info, fill ctr_name[] */ + if (gdth_internal_cmd(hanum,CACHESERVICE,GDT_IOCTL,BOARD_INFO, + INVALID_CHANNEL,sizeof(gdth_binfo_str))) { + TRACE2(("BOARD_INFO supported!\n")); + strcpy(ha->ctr_name, ((gdth_binfo_str *)DMADATA(gdth_ctr_tab[hanum]))->type_string); + } else { + strcpy(ha->ctr_name, gdth_ctr_name(hanum)); + } + TRACE2(("Controller name: %s\n",ha->ctr_name)); + + /* initialize raw service */ + if (!gdth_internal_cmd(hanum,SCSIRAWSERVICE,GDT_INIT,0,0,0)) { + printk("GDT: Initialization error raw service (code %d)\n", + ha->status); + return 0; + } + TRACE2(("gdth_search_drives(): RAWSERVICE initialized\n")); + + /* set/get features raw service (scatter/gather) */ + ha->raw_feat = 0; + if (gdth_internal_cmd(hanum,SCSIRAWSERVICE,GDT_SET_FEAT,SCATTER_GATHER, + 0,0)) { + TRACE2(("gdth_search_drives(): set features RAWSERVICE OK\n")); + if (gdth_internal_cmd(hanum,SCSIRAWSERVICE,GDT_GET_FEAT,0,0,0)) { + TRACE2(("gdth_search_dr(): get feat RAWSERVICE %ld\n", + ha->info)); + ha->raw_feat = (ushort)ha->info; + } + } + + /* set/get features cache service (equal to raw service) */ + if (gdth_internal_cmd(hanum,CACHESERVICE,GDT_SET_FEAT,0, + SCATTER_GATHER,0)) { + TRACE2(("gdth_search_drives(): set features CACHESERVICE OK\n")); + if (gdth_internal_cmd(hanum,CACHESERVICE,GDT_GET_FEAT,0,0,0)) { + TRACE2(("gdth_search_dr(): get feat CACHESERV. %ld\n", + ha->info)); + ha->cache_feat = (ushort)ha->info; + } + } + + /* reserve drives for raw service */ + for (i = 0; reserve_list[i].hanum != 0xff; ++i) { + if (reserve_list[i].hanum < MAXHA && reserve_list[i].hanum == hanum && + reserve_list[i].bus < MAXBUS && reserve_list[i].id < MAXID) { + TRACE2(("gdth_search_drives(): reserve ha %d bus %d id %d\n", + reserve_list[i].hanum, reserve_list[i].bus, + reserve_list[i].id)); + if (!gdth_internal_cmd(hanum,SCSIRAWSERVICE,GDT_RESERVE,0, + reserve_list[i].bus, reserve_list[i].id)) { + printk("GDT: Error raw service (RESERVE, code %d)\n", + ha->status); + } + } + } + + /* scanning for raw devices */ + for (b=0; b<ha->bus_cnt; ++b) { + for (t=0; t<MAXID; ++t) { + TRACE(("gdth_search_drives() rawd. bus %d id %d\n",b,t)); + if (ha->id[b][t].type != SIOP_DTYP && + gdth_internal_cmd(hanum,SCSIRAWSERVICE,GDT_INFO,0,b,t)) { + ha->id[b][t].type = RAW_DTYP; + } + } + } + + /* scanning for cache devices */ + for (i=0; i<cdev_cnt && i<MAX_HDRIVES; ++i) { + TRACE(("gdth_search_drives() cachedev. %d\n",i)); + if (gdth_internal_cmd(hanum,CACHESERVICE,GDT_INFO,i,0,0)) { + /* dynamic relation between host drive number and Bus/ID */ + /* search free position */ + pos_found = FALSE; + for (b=0,t=0; b<ha->bus_cnt; ++b) { + for (t=0; t<MAXID; ++t) { + if (ha->id[b][t].type == EMPTY_DTYP) { + pos_found = TRUE; + break; + } + } + if (pos_found) + break; + } + TRACE(("gdth_search_dr() drive %d free pos at bus/id %d/%d\n", + i,b,t)); + + ha->id[b][t].type = CACHE_DTYP; + ha->id[b][t].devtype = 0; + ha->id[b][t].size = ha->info; + ha->id[b][t].hostdrive = i; + + /* evaluate mapping (sectors per head, heads per cylinder) */ + ha->id[b][t].size &= ~SECS32; + if (ha->info2 == 0) { + drv_cyls = ha->id[b][t].size /HEADS/SECS; + if (drv_cyls <= MAXCYLS) { + drv_hds = HEADS; + drv_secs= SECS; + } else { /* too high for 64*32 */ + drv_cyls = ha->id[b][t].size /MEDHEADS/MEDSECS; + if (drv_cyls <= MAXCYLS) { + drv_hds = MEDHEADS; + drv_secs= MEDSECS; + } else { /* too high for 127*63 */ + drv_cyls = ha->id[b][t].size /BIGHEADS/BIGSECS; + drv_hds = BIGHEADS; + drv_secs= BIGSECS; + } + } + } else { + drv_hds = ha->info2 & 0xff; + drv_secs = (ha->info2 >> 8) & 0xff; + drv_cyls = ha->id[b][t].size /drv_hds/drv_secs; + } + ha->id[b][t].heads = (unchar)drv_hds; + ha->id[b][t].secs = (unchar)drv_secs; + /* round size */ + ha->id[b][t].size = drv_cyls * drv_hds * drv_secs; + TRACE2(("gdth_search_dr() cdr. %d size %ld hds %ld scs %ld\n", + i,ha->id[b][t].size,drv_hds,drv_secs)); + + /* get informations about device */ + if (gdth_internal_cmd(hanum,CACHESERVICE,GDT_DEVTYPE,i, + 0,0)) { + TRACE(("gdth_search_dr() cache drive %d devtype %ld\n", + i,ha->info)); + ha->id[b][t].devtype = (ushort)ha->info; + } + } + } + + TRACE(("gdth_search_drives() OK\n")); + return 1; +} + + +/* command queueing/sending functions */ + +static void gdth_putq(int hanum,Scsi_Cmnd *scp,unchar priority) +{ + register gdth_ha_str *ha; + register Scsi_Cmnd *pscp; + register Scsi_Cmnd *nscp; + ulong flags; + unchar b, t; + + TRACE(("gdth_putq() priority %d\n",priority)); + save_flags(flags); + cli(); + + ha = HADATA(gdth_ctr_tab[hanum]); + scp->SCp.this_residual = (int)priority; + gdth_update_timeout(hanum, scp, scp->timeout_per_command * 6); +#if LINUX_VERSION_CODE >= 0x020000 + b = scp->channel; +#else + b = NUMDATA(nscp->host)->busnum; +#endif + t = scp->target; +#if LINUX_VERSION_CODE >= 0x010300 + if (priority >= DEFAULT_PRI && ha->id[b][t].lock) { + TRACE2(("gdth_putq(): locked IO -> update_timeout()\n")); + scp->SCp.buffers_residual = gdth_update_timeout(hanum, scp, 0); + } +#endif + + if (ha->req_first==NULL) { + ha->req_first = scp; /* queue was empty */ + scp->SCp.ptr = NULL; + } else { /* queue not empty */ + pscp = ha->req_first; + nscp = (Scsi_Cmnd *)pscp->SCp.ptr; + /* priority: 0-highest,..,0xff-lowest */ + while (nscp && (unchar)nscp->SCp.this_residual <= priority) { + pscp = nscp; + nscp = (Scsi_Cmnd *)pscp->SCp.ptr; + } + pscp->SCp.ptr = (char *)scp; + scp->SCp.ptr = (char *)nscp; + } + restore_flags(flags); + +#ifdef GDTH_STATISTICS + flags = 0; + for (nscp=ha->req_first; nscp; nscp=(Scsi_Cmnd*)nscp->SCp.ptr) + ++flags; + if (max_rq < flags) { + max_rq = flags; + TRACE3(("GDT: max_rq = %d\n",(ushort)max_rq)); + } +#endif +} + +static void gdth_next(int hanum) +{ + register gdth_ha_str *ha; + register Scsi_Cmnd *pscp; + register Scsi_Cmnd *nscp; + unchar b, t, next_cmd, firsttime; + ushort hdrive; + ulong flags; + int cmd_index; + + TRACE(("gdth_next() hanum %d\n",hanum)); + save_flags(flags); + cli(); + + ha = HADATA(gdth_ctr_tab[hanum]); + ha->cmd_cnt = ha->cmd_offs_dpmem = 0; + next_cmd = firsttime = TRUE; + cmd_index = 0; + + for (nscp = pscp = ha->req_first; nscp; nscp = (Scsi_Cmnd *)nscp->SCp.ptr) { + if (nscp != pscp && nscp != (Scsi_Cmnd *)pscp->SCp.ptr) + pscp = (Scsi_Cmnd *)pscp->SCp.ptr; +#if LINUX_VERSION_CODE >= 0x020000 + b = nscp->channel; +#else + b = NUMDATA(nscp->host)->busnum; +#endif + t = nscp->target; + if (nscp->SCp.this_residual < DEFAULT_PRI || !ha->id[b][t].lock) { + + if (firsttime) { + if (gdth_test_busy(hanum)) { /* controller busy ? */ + TRACE(("gdth_next() controller %d busy !\n",hanum)); + if (!gdth_polling) { + restore_flags(flags); + return; + } + while (gdth_test_busy(hanum)) + gdth_delay(1); + } + firsttime = FALSE; + } + +#if LINUX_VERSION_CODE >= 0x010300 + if (nscp->done == gdth_scsi_done) { + if (!(cmd_index=gdth_special_cmd(hanum,nscp,b))) + next_cmd = FALSE; + } else +#endif + if (ha->id[b][t].type != CACHE_DTYP) { + if (!(cmd_index=gdth_fill_raw_cmd(hanum,nscp,b))) + next_cmd = FALSE; + } else { + hdrive = ha->id[b][t].hostdrive; + switch (nscp->cmnd[0]) { + case TEST_UNIT_READY: + case INQUIRY: + case REQUEST_SENSE: + case READ_CAPACITY: + case VERIFY: + case START_STOP: + case MODE_SENSE: + TRACE2(("cache cmd %x/%x/%x/%x/%x/%x\n",nscp->cmnd[0], + nscp->cmnd[1],nscp->cmnd[2],nscp->cmnd[3], + nscp->cmnd[4],nscp->cmnd[5])); + gdth_internal_cache_cmd(hanum,nscp,b,&flags); + break; + + case ALLOW_MEDIUM_REMOVAL: + TRACE2(("cache cmd %x/%x/%x/%x/%x/%x\n",nscp->cmnd[0], + nscp->cmnd[1],nscp->cmnd[2],nscp->cmnd[3], + nscp->cmnd[4],nscp->cmnd[5])); + if ( (nscp->cmnd[4]&1) && !(ha->id[b][t].devtype&1) ) { + TRACE2(("Prevent r. nonremov. drive->do nothing\n")); + nscp->result = DID_OK << 16; + restore_flags( flags ); + if (!nscp->SCp.have_data_in) + nscp->SCp.have_data_in++; + else + nscp->scsi_done(nscp); + save_flags( flags ); + cli(); + } else { + nscp->cmnd[3] = (ha->id[b][t].devtype&1) ? 1:0; + TRACE2(("Prevent/allow r. %d rem. drive %d\n", + nscp->cmnd[4],nscp->cmnd[3])); + if (!(cmd_index=gdth_fill_cache_cmd(hanum,nscp,hdrive))) + next_cmd = FALSE; + } + break; + + case READ_6: + case WRITE_6: + case READ_10: + case WRITE_10: + if (!(cmd_index=gdth_fill_cache_cmd(hanum,nscp,hdrive))) + next_cmd = FALSE; + break; + + default: + TRACE2(("cache cmd %x/%x/%x/%x/%x/%x\n",nscp->cmnd[0], + nscp->cmnd[1],nscp->cmnd[2],nscp->cmnd[3], + nscp->cmnd[4],nscp->cmnd[5])); + printk("GDT: Unknown SCSI command 0x%x to cache service !\n", + nscp->cmnd[0]); + nscp->result = DID_ABORT << 16; + restore_flags( flags ); + if (!nscp->SCp.have_data_in) + nscp->SCp.have_data_in++; + else + nscp->scsi_done(nscp); + save_flags( flags ); + cli(); + break; + } + } + + if (!next_cmd) + break; + if (nscp == ha->req_first) + ha->req_first = pscp = (Scsi_Cmnd *)nscp->SCp.ptr; + else + pscp->SCp.ptr = nscp->SCp.ptr; + if (gdth_polling) + break; + } + } + + if (ha->cmd_cnt > 0) { + gdth_release_event(hanum); + } + + restore_flags(flags); + + if (gdth_polling && ha->cmd_cnt > 0) { + if (!gdth_wait(hanum,cmd_index,POLL_TIMEOUT)) + printk("GDT: Controller %d: Command %d timed out !\n", + hanum,cmd_index); + } +} + +static void gdth_copy_internal_data(Scsi_Cmnd *scp,char *buffer,ushort count) +{ + ushort cpcount,i; + ushort cpsum,cpnow; + struct scatterlist *sl; + + cpcount = count<=(ushort)scp->bufflen ? count:(ushort)scp->bufflen; + if (scp->use_sg) { + sl = (struct scatterlist *)scp->request_buffer; + for (i=0,cpsum=0; i<scp->use_sg; ++i,++sl) { + cpnow = (ushort)sl->length; + TRACE(("copy_internal() now %d sum %d count %d %d\n", + cpnow,cpsum,cpcount,(ushort)scp->bufflen)); + if (cpsum+cpnow > cpcount) + cpnow = cpcount - cpsum; + cpsum += cpnow; + memcpy((char*)sl->address,buffer,cpnow); + if (cpsum == cpcount) + break; + buffer += cpnow; + } + } else { + TRACE(("copy_internal() count %d\n",cpcount)); + memcpy((char*)scp->request_buffer,buffer,cpcount); + } +} + +static int gdth_internal_cache_cmd(int hanum,Scsi_Cmnd *scp, + unchar b,ulong *flags) +{ + register gdth_ha_str *ha; + ushort hdrive; + unchar t; + gdth_inq_data inq; + gdth_rdcap_data rdc; + gdth_sense_data sd; + gdth_modep_data mpd; + + ha = HADATA(gdth_ctr_tab[hanum]); + t = scp->target; + hdrive = ha->id[b][t].hostdrive; + TRACE(("gdth_internal_cache_cmd() cmd 0x%x hdrive %d\n", + scp->cmnd[0],hdrive)); + + if (scp->lun !=0) + scp->result = DID_BAD_TARGET << 16; + else { + switch (scp->cmnd[0]) { + case TEST_UNIT_READY: + case VERIFY: + case START_STOP: + TRACE2(("Test/Verify/Start hdrive %d\n",hdrive)); + break; + + case INQUIRY: + TRACE2(("Inquiry hdrive %d devtype %d\n", + hdrive,ha->id[b][t].devtype)); + inq.type_qual = (ha->id[b][t].devtype&4) ? TYPE_ROM:TYPE_DISK; + /* you can here set all disks to removable, if you want to do + a flush using the ALLOW_MEDIUM_REMOVAL command */ + inq.modif_rmb = ha->id[b][t].devtype&1 ? 0x80:0x00; + inq.version = 2; + inq.resp_aenc = 2; + inq.add_length= 32; + strcpy(inq.vendor,"ICP "); + sprintf(inq.product,"Host Drive #%02d",hdrive); + strcpy(inq.revision," "); + gdth_copy_internal_data(scp,(char*)&inq,sizeof(gdth_inq_data)); + break; + + case REQUEST_SENSE: + TRACE2(("Request sense hdrive %d\n",hdrive)); + sd.errorcode = 0x70; + sd.segno = 0x00; + sd.key = NO_SENSE; + sd.info = 0; + sd.add_length= 0; + gdth_copy_internal_data(scp,(char*)&sd,sizeof(gdth_sense_data)); + break; + + case MODE_SENSE: + TRACE2(("Mode sense hdrive %d\n",hdrive)); + memset((char*)&mpd,0,sizeof(gdth_modep_data)); + mpd.hd.data_length = sizeof(gdth_modep_data); + mpd.hd.dev_par = (ha->id[b][t].devtype&2) ? 0x80:0; + mpd.hd.bd_length = sizeof(mpd.bd); + mpd.bd.block_length[0] = (SECTOR_SIZE & 0x00ff0000) >> 16; + mpd.bd.block_length[1] = (SECTOR_SIZE & 0x0000ff00) >> 8; + mpd.bd.block_length[2] = (SECTOR_SIZE & 0x000000ff); + gdth_copy_internal_data(scp,(char*)&mpd,sizeof(gdth_modep_data)); + break; + + case READ_CAPACITY: + TRACE2(("Read capacity hdrive %d\n",hdrive)); + rdc.last_block_no = ntohl(ha->id[b][t].size-1); + rdc.block_length = ntohl(SECTOR_SIZE); + gdth_copy_internal_data(scp,(char*)&rdc,sizeof(gdth_rdcap_data)); + break; + + default: + TRACE2(("Internal cache cmd 0x%x unknown\n",scp->cmnd[0])); + break; + } + scp->result = DID_OK << 16; + } + + restore_flags(*flags); + if (!scp->SCp.have_data_in) + scp->SCp.have_data_in++; + else + scp->scsi_done(scp); + save_flags(*flags); + cli(); + return 1; +} + +static int gdth_fill_cache_cmd(int hanum,Scsi_Cmnd *scp,ushort hdrive) +{ + register gdth_ha_str *ha; + register gdth_cmd_str *cmdp; + struct scatterlist *sl; + ushort i; + int cmd_index; + + ha = HADATA(gdth_ctr_tab[hanum]); + cmdp = ha->pccb; + TRACE(("gdth_fill_cache_cmd() cmd 0x%x cmdsize %d hdrive %d\n", + scp->cmnd[0],scp->cmd_len,hdrive)); + + if (ha->type==GDT_EISA && ha->cmd_cnt>0) + return 0; + + cmdp->Service = CACHESERVICE; + cmdp->RequestBuffer = scp; + /* search free command index */ + if (!(cmd_index=gdth_get_cmd_index(hanum))) { + TRACE(("GDT: No free command index found\n")); + return 0; + } + /* if it's the first command, set command semaphore */ + if (ha->cmd_cnt == 0) + gdth_set_sema0(hanum); + + /* fill command */ + if (scp->cmnd[0]==ALLOW_MEDIUM_REMOVAL) { + if (scp->cmnd[4] & 1) /* prevent ? */ + cmdp->OpCode = GDT_MOUNT; + else if (scp->cmnd[3] & 1) /* removable drive ? */ + cmdp->OpCode = GDT_UNMOUNT; + else + cmdp->OpCode = GDT_FLUSH; + } else { + if (scp->cmnd[0]==WRITE_6 || scp->cmnd[0]==WRITE_10) { + if (gdth_write_through) + cmdp->OpCode = GDT_WRITE_THR; + else + cmdp->OpCode = GDT_WRITE; + } else { + cmdp->OpCode = GDT_READ; + } + } + + cmdp->BoardNode = LOCALBOARD; + cmdp->u.cache.DeviceNo = hdrive; + + if (scp->cmnd[0]==ALLOW_MEDIUM_REMOVAL) { + cmdp->u.cache.BlockNo = 1; + cmdp->u.cache.sg_canz = 0; + } else { + if (scp->cmd_len != 6) { + cmdp->u.cache.BlockNo = ntohl(*(ulong*)&scp->cmnd[2]); + cmdp->u.cache.BlockCnt= (ulong)ntohs(*(ushort*)&scp->cmnd[7]); + } else { + cmdp->u.cache.BlockNo = ntohl(*(ulong*)&scp->cmnd[0]) & 0x001fffffUL; + cmdp->u.cache.BlockCnt= scp->cmnd[4]==0 ? 0x100 : scp->cmnd[4]; + } + + if (scp->use_sg) { + cmdp->u.cache.DestAddr= -1UL; + sl = (struct scatterlist *)scp->request_buffer; + for (i=0; i<scp->use_sg; ++i,++sl) { + cmdp->u.cache.sg_lst[i].sg_ptr = virt_to_bus(sl->address); + cmdp->u.cache.sg_lst[i].sg_len = (ulong)sl->length; + } + cmdp->u.cache.sg_canz = (ulong)i; + +#ifdef GDTH_STATISTICS + if (max_sg < (ulong)i) { + max_sg = (ulong)i; + TRACE3(("GDT: max_sg = %d\n",i)); + } +#endif + if (i<GDTH_MAXSG) + cmdp->u.cache.sg_lst[i].sg_len = 0; + } else { + if (ha->cache_feat & SCATTER_GATHER) { + cmdp->u.cache.DestAddr = -1UL; + cmdp->u.cache.sg_canz = 1; + cmdp->u.cache.sg_lst[0].sg_ptr = virt_to_bus(scp->request_buffer); + cmdp->u.cache.sg_lst[0].sg_len = scp->request_bufflen; + cmdp->u.cache.sg_lst[1].sg_len = 0; + } else { + cmdp->u.cache.DestAddr = virt_to_bus(scp->request_buffer); + cmdp->u.cache.sg_canz= 0; + } + } + } + TRACE(("cache cmd: addr. %lx sganz %lx sgptr0 %lx sglen0 %lx\n", + cmdp->u.cache.DestAddr,cmdp->u.cache.sg_canz, + cmdp->u.cache.sg_lst[0].sg_ptr, + cmdp->u.cache.sg_lst[0].sg_len)); + TRACE(("cache cmd: cmd %d blockno. %ld, blockcnt %ld\n", + cmdp->OpCode,cmdp->u.cache.BlockNo,cmdp->u.cache.BlockCnt)); + + /* evaluate command size, check space */ + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.cache.sg_lst) + + (ushort)cmdp->u.cache.sg_canz * sizeof(gdth_sg_str); + if (ha->cmd_len & 3) + ha->cmd_len += (4 - (ha->cmd_len & 3)); + + if (ha->cmd_cnt > 0) { + if ((ha->cmd_offs_dpmem + ha->cmd_len + DPMEM_COMMAND_OFFSET) > + ha->ic_all_size) { + TRACE2(("gdth_fill_cache() DPMEM overflow\n")); + gdth_cmd_tab[cmd_index-2][hanum].cmnd = UNUSED_CMND; + return 0; + } + } + + /* copy command */ + gdth_copy_command(hanum); + return cmd_index; +} + +static int gdth_fill_raw_cmd(int hanum,Scsi_Cmnd *scp,unchar b) +{ + register gdth_ha_str *ha; + register gdth_cmd_str *cmdp; + struct scatterlist *sl; + ushort i; + int cmd_index; + unchar t,l; + + ha = HADATA(gdth_ctr_tab[hanum]); + t = scp->target; + l = scp->lun; + cmdp = ha->pccb; + TRACE(("gdth_fill_raw_cmd() cmd 0x%x bus %d ID %d LUN %d\n", + scp->cmnd[0],b,t,l)); + + if (ha->type==GDT_EISA && ha->cmd_cnt>0) + return 0; + + cmdp->Service = SCSIRAWSERVICE; + cmdp->RequestBuffer = scp; + /* search free command index */ + if (!(cmd_index=gdth_get_cmd_index(hanum))) { + TRACE(("GDT: No free command index found\n")); + return 0; + } + /* if it's the first command, set command semaphore */ + if (ha->cmd_cnt == 0) + gdth_set_sema0(hanum); + + /* fill command */ + cmdp->OpCode = GDT_WRITE; /* always */ + cmdp->BoardNode = LOCALBOARD; + cmdp->u.raw.reserved = 0; + cmdp->u.raw.mdisc_time = 0; + cmdp->u.raw.mcon_time = 0; + cmdp->u.raw.clen = scp->cmd_len; + cmdp->u.raw.target = t; + cmdp->u.raw.lun = l; + cmdp->u.raw.bus = b; + cmdp->u.raw.priority = 0; + cmdp->u.raw.link_p = NULL; + cmdp->u.raw.sdlen = scp->request_bufflen; + cmdp->u.raw.sense_len = 16; + cmdp->u.raw.sense_data = virt_to_bus(scp->sense_buffer); + cmdp->u.raw.direction = + gdth_direction_tab[scp->cmnd[0]]==DOU ? DATA_OUT : DATA_IN; + memcpy(cmdp->u.raw.cmd,scp->cmnd,12); + + if (scp->use_sg) { + cmdp->u.raw.sdata = -1UL; + sl = (struct scatterlist *)scp->request_buffer; + for (i=0; i<scp->use_sg; ++i,++sl) { + cmdp->u.raw.sg_lst[i].sg_ptr = virt_to_bus(sl->address); + cmdp->u.raw.sg_lst[i].sg_len = (ulong)sl->length; + } + cmdp->u.raw.sg_ranz = (ulong)i; + +#ifdef GDTH_STATISTICS + if (max_sg < (ulong)i) { + max_sg = (ulong)i; + TRACE3(("GDT: max_sg = %d\n",i)); + } +#endif + if (i<GDTH_MAXSG) + cmdp->u.raw.sg_lst[i].sg_len = 0; + } else { + if (ha->raw_feat & SCATTER_GATHER) { + cmdp->u.raw.sdata = -1UL; + cmdp->u.raw.sg_ranz= 1; + cmdp->u.raw.sg_lst[0].sg_ptr = virt_to_bus(scp->request_buffer); + cmdp->u.raw.sg_lst[0].sg_len = scp->request_bufflen; + cmdp->u.raw.sg_lst[1].sg_len = 0; + } else { + cmdp->u.raw.sdata = virt_to_bus(scp->request_buffer); + cmdp->u.raw.sg_ranz= 0; + } + } + TRACE(("raw cmd: addr. %lx sganz %lx sgptr0 %lx sglen0 %lx\n", + cmdp->u.raw.sdata,cmdp->u.raw.sg_ranz, + cmdp->u.raw.sg_lst[0].sg_ptr, + cmdp->u.raw.sg_lst[0].sg_len)); + + /* evaluate command size, check space */ + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.raw.sg_lst) + + (ushort)cmdp->u.raw.sg_ranz * sizeof(gdth_sg_str); + if (ha->cmd_len & 3) + ha->cmd_len += (4 - (ha->cmd_len & 3)); + + if (ha->cmd_cnt > 0) { + if ((ha->cmd_offs_dpmem + ha->cmd_len + DPMEM_COMMAND_OFFSET) > + ha->ic_all_size) { + TRACE2(("gdth_fill_raw() DPMEM overflow\n")); + gdth_cmd_tab[cmd_index-2][hanum].cmnd = UNUSED_CMND; + return 0; + } + } + + /* copy command */ + gdth_copy_command(hanum); + return cmd_index; +} + +static int gdth_special_cmd(int hanum,Scsi_Cmnd *scp,unchar b) +{ + register gdth_ha_str *ha; + register gdth_cmd_str *cmdp; + int cmd_index; + + ha = HADATA(gdth_ctr_tab[hanum]); + cmdp= ha->pccb; + TRACE2(("gdth_special_cmd(): ")); + + if (ha->type==GDT_EISA && ha->cmd_cnt>0) + return 0; + + memcpy( cmdp, scp->request_buffer, sizeof(gdth_cmd_str)); + cmdp->RequestBuffer = scp; + + /* search free command index */ + if (!(cmd_index=gdth_get_cmd_index(hanum))) { + TRACE(("GDT: No free command index found\n")); + return 0; + } + + /* if it's the first command, set command semaphore */ + if (ha->cmd_cnt == 0) + gdth_set_sema0(hanum); + + /* evaluate command size, check space */ + if (cmdp->OpCode == GDT_IOCTL) { + TRACE2(("IOCTL\n")); + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.ioctl.p_param) + sizeof(ulong); + } else if (cmdp->Service == CACHESERVICE) { + TRACE2(("cache command %d\n",cmdp->OpCode)); + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.cache.sg_lst) + sizeof(gdth_sg_str); + } else if (cmdp->Service == SCSIRAWSERVICE) { + TRACE2(("raw command %d/%d\n",cmdp->OpCode,cmdp->u.raw.cmd[0])); + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.raw.sg_lst) + sizeof(gdth_sg_str); + } + + if (ha->cmd_len & 3) + ha->cmd_len += (4 - (ha->cmd_len & 3)); + + if (ha->cmd_cnt > 0) { + if ((ha->cmd_offs_dpmem + ha->cmd_len + DPMEM_COMMAND_OFFSET) > + ha->ic_all_size) { + TRACE2(("gdth_special_cmd() DPMEM overflow\n")); + gdth_cmd_tab[cmd_index-2][hanum].cmnd = UNUSED_CMND; + return 0; + } + } + + /* copy command */ + gdth_copy_command(hanum); + return cmd_index; +} + + +/* Controller event handling functions */ +static gdth_evt_str *gdth_store_event(ushort source, ushort idx, + gdth_evt_data *evt) +{ + gdth_evt_str *e; + ulong flags; + struct timeval tv; + + TRACE2(("gdth_store_event() source %d idx %d\n", source, idx)); + if (source == 0) /* no source -> no event */ + return 0; + + save_flags(flags); + cli(); + if (ebuffer[elastidx].event_source == source && + ebuffer[elastidx].event_idx == idx && + !memcmp((char *)&ebuffer[elastidx].event_data.eu, + (char *)&evt->eu, evt->size)) { + e = &ebuffer[elastidx]; + do_gettimeofday(&tv); + e->last_stamp = tv.tv_sec; + ++e->same_count; + } else { + if (ebuffer[elastidx].event_source != 0) { /* entry not free ? */ + ++elastidx; + if (elastidx == MAX_EVENTS) + elastidx = 0; + if (elastidx == eoldidx) { /* reached mark ? */ + ++eoldidx; + if (eoldidx == MAX_EVENTS) + eoldidx = 0; + } + } + e = &ebuffer[elastidx]; + e->event_source = source; + e->event_idx = idx; + do_gettimeofday(&tv); + e->first_stamp = e->last_stamp = tv.tv_sec; + e->same_count = 1; + e->event_data = *evt; + } + restore_flags(flags); + return e; +} + +static int gdth_read_event(int handle, gdth_evt_str *estr) +{ + gdth_evt_str *e; + int eindex; + ulong flags; + + TRACE2(("gdth_read_event() handle %d\n", handle)); + save_flags(flags); + cli(); + if (handle == -1) + eindex = eoldidx; + else + eindex = handle; + estr->event_source = 0; + + if (eindex >= MAX_EVENTS) { + restore_flags(flags); + return eindex; + } + e = &ebuffer[eindex]; + if (e->event_source != 0) { + if (eindex != elastidx) { + if (++eindex == MAX_EVENTS) + eindex = 0; + } else { + eindex = -1; + } + memcpy(estr, e, sizeof(gdth_evt_str)); + } + restore_flags(flags); + return eindex; +} + +static void gdth_readapp_event(unchar application, gdth_evt_str *estr) +{ + gdth_evt_str *e; + int eindex; + ulong flags; + unchar found = FALSE; + + TRACE2(("gdth_readapp_event() app. %d\n", application)); + save_flags(flags); + cli(); + eindex = eoldidx; + for (;;) { + e = &ebuffer[eindex]; + if (e->event_source == 0) + break; + if ((e->application & application) == 0) { + e->application |= application; + found = TRUE; + break; + } + if (eindex == elastidx) + break; + if (++eindex == MAX_EVENTS) + eindex = 0; + } + if (found) + memcpy(estr, e, sizeof(gdth_evt_str)); + else + estr->event_source = 0; + restore_flags(flags); +} + +static void gdth_clear_events() +{ + ulong flags; + + TRACE(("gdth_clear_events()")); + save_flags(flags); + cli(); + + eoldidx = elastidx = 0; + ebuffer[0].event_source = 0; + restore_flags(flags); +} + + +/* SCSI interface functions */ + +#if LINUX_VERSION_CODE >= 0x02015F +static void do_gdth_interrupt(int irq,void *dev_id,struct pt_regs *regs) +{ + ulong flags; + + spin_lock_irqsave(&io_request_lock, flags); + gdth_interrupt(irq, dev_id, regs); + spin_unlock_irqrestore(&io_request_lock, flags); +} +#endif + +#if LINUX_VERSION_CODE >= 0x010346 +static void gdth_interrupt(int irq,void *dev_id,struct pt_regs *regs) +#else +static void gdth_interrupt(int irq,struct pt_regs *regs) +#endif +{ + register gdth_ha_str *ha; + gdt6m_dpram_str *dp6m_ptr; + gdt6_dpram_str *dp6_ptr; + gdt2_dpram_str *dp2_ptr; + Scsi_Cmnd *scp; + int hanum; + unchar IStatus; + ushort CmdStatus, Service = 0; + ulong InfoBytes, InfoBytes2 = 0; + gdth_evt_data dvr; + + TRACE(("gdth_interrupt() IRQ %d\n",irq)); + + /* if polling and not from gdth_wait() -> return */ + if (gdth_polling) { + if (!gdth_from_wait) { + return; + } + } + + wait_index = 0; + + /* search controller */ + if ((hanum = gdth_get_status(&IStatus,irq)) == -1) { + /* + TRACE2(("gdth_interrupt(): Spurious interrupt received\n")); + */ + return; + } + +#ifdef GDTH_STATISTICS + ++act_ints; +#endif + + ha = HADATA(gdth_ctr_tab[hanum]); + if (ha->type == GDT_EISA) { + if (IStatus & 0x80) { /* error flag */ + IStatus &= ~0x80; + CmdStatus = inw(ha->bmic + MAILBOXREG+8); + TRACE2(("gdth_interrupt() error %d/%d\n",IStatus,CmdStatus)); + if (IStatus == ASYNCINDEX) { /* async. event ? */ + Service = inw(ha->bmic + MAILBOXREG+10); + InfoBytes2 = inl(ha->bmic + MAILBOXREG+4); + } + } else /* no error */ + CmdStatus = S_OK; + InfoBytes = inl(ha->bmic + MAILBOXREG+12); + if (gdth_polling) /* init. -> more info */ + InfoBytes2 = inl(ha->bmic + MAILBOXREG+4); + outb(0xff, ha->bmic + EDOORREG); /* acknowledge interrupt */ + outb(0x00, ha->bmic + SEMA1REG); /* reset status semaphore */ + } else if (ha->type == GDT_ISA) { + dp2_ptr = (gdt2_dpram_str *)ha->brd; + if (IStatus & 0x80) { /* error flag */ + IStatus &= ~0x80; + CmdStatus = readw(&dp2_ptr->u.ic.Status); + TRACE2(("gdth_interrupt() error %d/%d\n",IStatus,CmdStatus)); + if (IStatus == ASYNCINDEX) { /* async. event ? */ + Service = readw(&dp2_ptr->u.ic.Service); + InfoBytes2 = readl(&dp2_ptr->u.ic.Info[1]); + } + } else /* no error */ + CmdStatus = S_OK; + InfoBytes = readl(&dp2_ptr->u.ic.Info[0]); + if (gdth_polling) /* init. -> more info */ + InfoBytes2 = readl(&dp2_ptr->u.ic.Info[1]); + writeb(0xff, &dp2_ptr->io.irqdel); /* acknowledge interrupt */ + writeb(0, &dp2_ptr->u.ic.Cmd_Index); /* reset command index */ + writeb(0, &dp2_ptr->io.Sema1); /* reset status semaphore */ + } else if (ha->type == GDT_PCI) { + dp6_ptr = (gdt6_dpram_str *)ha->brd; + if (IStatus & 0x80) { /* error flag */ + IStatus &= ~0x80; + CmdStatus = readw(&dp6_ptr->u.ic.Status); + TRACE2(("gdth_interrupt() error %d/%d\n",IStatus,CmdStatus)); + if (IStatus == ASYNCINDEX) { /* async. event ? */ + Service = readw(&dp6_ptr->u.ic.Service); + InfoBytes2 = readl(&dp6_ptr->u.ic.Info[1]); + } + } else /* no error */ + CmdStatus = S_OK; + InfoBytes = readl(&dp6_ptr->u.ic.Info[0]); + if (gdth_polling) /* init. -> more info */ + InfoBytes2 = readl(&dp6_ptr->u.ic.Info[1]); + writeb(0xff, &dp6_ptr->io.irqdel); /* acknowledge interrupt */ + writeb(0, &dp6_ptr->u.ic.Cmd_Index); /* reset command index */ + writeb(0, &dp6_ptr->io.Sema1); /* reset status semaphore */ + } else if (ha->type == GDT_PCINEW) { + if (IStatus & 0x80) { /* error flag */ + IStatus &= ~0x80; + CmdStatus = inw(PTR2USHORT(&ha->plx->status)); + TRACE2(("gdth_interrupt() error %d/%d\n",IStatus,CmdStatus)); + if (IStatus == ASYNCINDEX) { /* async. event ? */ + Service = inw(PTR2USHORT(&ha->plx->service)); + InfoBytes2 = inl(PTR2USHORT(&ha->plx->info[1])); + } + } else + CmdStatus = S_OK; + + InfoBytes = inl(PTR2USHORT(&ha->plx->info[0])); + if (gdth_polling) /* init. -> more info */ + InfoBytes2 = inl(PTR2USHORT(&ha->plx->info[1])); + outb(0xff, PTR2USHORT(&ha->plx->edoor_reg)); + outb(0x00, PTR2USHORT(&ha->plx->sema1_reg)); + } else if (ha->type == GDT_PCIMPR) { + dp6m_ptr = (gdt6m_dpram_str *)ha->brd; + if (IStatus & 0x80) { /* error flag */ + IStatus &= ~0x80; + CmdStatus = readw(&dp6m_ptr->i960r.status); + TRACE2(("gdth_interrupt() error %d/%d\n",IStatus,CmdStatus)); + if (IStatus == ASYNCINDEX) { /* async. event ? */ + Service = readw(&dp6m_ptr->i960r.service); + InfoBytes2 = readl(&dp6m_ptr->i960r.info[1]); + } + } else /* no error */ + CmdStatus = S_OK; + InfoBytes = readl(&dp6m_ptr->i960r.info[0]); + if (gdth_polling) /* init. -> more info */ + InfoBytes2 = readl(&dp6m_ptr->i960r.info[1]); + writeb(0xff, &dp6m_ptr->i960r.edoor_reg); + writeb(0, &dp6m_ptr->i960r.sema1_reg); + } else { + TRACE2(("gdth_interrupt() unknown controller type\n")); + return; + } + + TRACE(("gdth_interrupt() index %d stat %d info %ld\n", + IStatus,CmdStatus,InfoBytes)); + ha->status = CmdStatus; + ha->info = InfoBytes; + ha->info2 = InfoBytes2; + + if (gdth_from_wait) { + wait_hanum = hanum; + wait_index = (int)IStatus; + } + + if (IStatus == ASYNCINDEX) { + TRACE2(("gdth_interrupt() async. event\n")); + gdth_async_event(hanum,Service); + } else { + if (IStatus == SPEZINDEX) { + TRACE2(("Service unknown or not initialized !\n")); + dvr.size = sizeof(dvr.eu.driver); + dvr.eu.driver.ionode = hanum; + gdth_store_event(ES_DRIVER, 4, &dvr); + return; + } + scp = gdth_cmd_tab[IStatus-2][hanum].cmnd; + Service = gdth_cmd_tab[IStatus-2][hanum].service; + gdth_cmd_tab[IStatus-2][hanum].cmnd = UNUSED_CMND; + if (scp == UNUSED_CMND) { + TRACE2(("gdth_interrupt() index to unused command (%d)\n",IStatus)); + dvr.size = sizeof(dvr.eu.driver); + dvr.eu.driver.ionode = hanum; + dvr.eu.driver.index = IStatus; + gdth_store_event(ES_DRIVER, 1, &dvr); + return; + } + if (scp == INTERNAL_CMND) { + TRACE(("gdth_interrupt() answer to internal command\n")); + return; + } + TRACE(("gdth_interrupt() sync. status\n")); + gdth_sync_event(hanum,Service,IStatus,scp); + } + gdth_next(hanum); +} + +static int gdth_sync_event(int hanum,int service,unchar index,Scsi_Cmnd *scp) +{ + register gdth_ha_str *ha; + gdth_msg_str *msg; + gdth_cmd_str *cmdp; + char c='\r'; + ushort i; + gdth_evt_data dvr; + + ha = HADATA(gdth_ctr_tab[hanum]); + cmdp = ha->pccb; + TRACE(("gdth_sync_event() scp %lx serv %d status %d\n", + (ulong)scp,service,ha->status)); + + if (service == SCREENSERVICE) { + msg = (gdth_msg_str *)ha->pscratch; + TRACE(("len: %ld, answer: %d, ext: %d, alen: %ld\n", + msg->msg_len,msg->msg_answer,msg->msg_ext,msg->msg_alen)); + if (msg->msg_len) + if (!(msg->msg_answer && msg->msg_ext)) { + msg->msg_text[msg->msg_len] = '\0'; + printk("%s",msg->msg_text); + } + + if (msg->msg_ext && !msg->msg_answer) { + while (gdth_test_busy(hanum)) + gdth_delay(0); + cmdp->Service = SCREENSERVICE; + cmdp->RequestBuffer = SCREEN_CMND; + gdth_get_cmd_index(hanum); + gdth_set_sema0(hanum); + cmdp->OpCode = GDT_READ; + cmdp->BoardNode = LOCALBOARD; + cmdp->u.screen.reserved = 0; + cmdp->u.screen.msg_handle= msg->msg_handle; + cmdp->u.screen.msg_addr = (ulong)msg; + ha->cmd_offs_dpmem = 0; + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.screen.msg_addr) + + sizeof(ulong); + ha->cmd_cnt = 0; + gdth_copy_command(hanum); + gdth_release_event(hanum); + return 1; + } + + if (msg->msg_answer && msg->msg_alen) { + for (i=0; i<msg->msg_alen && i<MSGLEN; ++i) { + /* getchar() ?? */ + /* .. */ + if (c == '\r') + break; + msg->msg_text[i] = c; + } + msg->msg_alen -= i; + if (c!='\r' && msg->msg_alen!=0) { + msg->msg_answer = 1; + msg->msg_ext = 1; + } else { + msg->msg_ext = 0; + msg->msg_answer = 0; + } + msg->msg_len = i; + while (gdth_test_busy(hanum)) + gdth_delay(0); + cmdp->Service = SCREENSERVICE; + cmdp->RequestBuffer = SCREEN_CMND; + gdth_get_cmd_index(hanum); + gdth_set_sema0(hanum); + cmdp->OpCode = GDT_WRITE; + cmdp->BoardNode = LOCALBOARD; + cmdp->u.screen.reserved = 0; + cmdp->u.screen.msg_handle= msg->msg_handle; + cmdp->u.screen.msg_addr = (ulong)msg; + ha->cmd_offs_dpmem = 0; + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.screen.msg_addr) + + sizeof(ulong); + ha->cmd_cnt = 0; + gdth_copy_command(hanum); + gdth_release_event(hanum); + return 1; + } + printk("\n"); + + } else { + scp->SCp.Message = (int)ha->status; + /* cache or raw service */ + if (ha->status == S_OK) { + scp->result = DID_OK << 16; + } else if (ha->status == S_BSY) { + TRACE2(("Controller busy -> retry !\n")); + gdth_putq(hanum,scp,scp->SCp.this_residual); + return 1; + } else { + if (service == CACHESERVICE) { + memset((char*)scp->sense_buffer,0,16); + scp->sense_buffer[0] = 0x70; + scp->sense_buffer[2] = NOT_READY; + scp->result = (DID_OK << 16) | (CHECK_CONDITION << 1); + + if (scp->done != gdth_scsi_done) { + dvr.size = sizeof(dvr.eu.sync); + dvr.eu.sync.ionode = hanum; + dvr.eu.sync.service = service; + dvr.eu.sync.status = ha->status; + dvr.eu.sync.info = ha->info; + dvr.eu.sync.hostdrive = +#if LINUX_VERSION_CODE >= 0x020000 + ha->id[scp->channel][scp->target].hostdrive; +#else + ha->id[NUMDATA(scp->host)->busnum][scp->target].hostdrive; +#endif + if (ha->status >= 0x8000) + gdth_store_event(ES_SYNC, 0, &dvr); + else + gdth_store_event(ES_SYNC, service, &dvr); + } + } else { + if (ha->status!=S_RAW_SCSI || ha->status==S_RAW_ILL || ha->info>=0x100) { + scp->result = DID_BAD_TARGET << 16; + } else { + scp->result = (DID_OK << 16) | ha->info; + } + } + } + if (!scp->SCp.have_data_in) + scp->SCp.have_data_in++; + else + scp->scsi_done(scp); + } + + return 1; +} + +static char *async_cache_tab[] = { +/* 0*/ "\011\000\002\002\002\004\002\006\004" + "GDT HA %u, service %u, async. status %u/%lu unknown", +/* 1*/ "\011\000\002\002\002\004\002\006\004" + "GDT HA %u, service %u, async. status %u/%lu unknown", +/* 2*/ "\005\000\002\006\004" + "GDT HA %u, Host Drive %lu not ready", +/* 3*/ "\005\000\002\006\004" + "GDT HA %u, Host Drive %lu: REASSIGN not successful and/or data error on reassigned blocks. Drive may crash in the future and should be replaced", +/* 4*/ "\005\000\002\006\004" + "GDT HA %u, mirror update on Host Drive %lu failed", +/* 5*/ "\005\000\002\006\004" + "GDT HA %u, Mirror Drive %lu failed", +/* 6*/ "\005\000\002\006\004" + "GDT HA %u, Mirror Drive %lu: REASSIGN not successful and/or data error on reassigned blocks. Drive may crash in the future and should be replaced", +/* 7*/ "\005\000\002\006\004" + "GDT HA %u, Host Drive %lu write protected", +/* 8*/ "\005\000\002\006\004" + "GDT HA %u, media changed in Host Drive %lu", +/* 9*/ "\005\000\002\006\004" + "GDT HA %u, Host Drive %lu is offline", +/*10*/ "\005\000\002\006\004" + "GDT HA %u, media change of Mirror Drive %lu", +/*11*/ "\005\000\002\006\004" + "GDT HA %u, Mirror Drive %lu is write protected", +/*12*/ "\005\000\002\006\004" + "GDT HA %u, general error on Host Drive %lu. Please check the devices of this drive!", +/*13*/ "\007\000\002\006\002\010\002" + "GDT HA %u, Array Drive %u: Cache Drive %u failed", +/*14*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: FAIL state entered", +/*15*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: error", +/*16*/ "\007\000\002\006\002\010\002" + "GDT HA %u, Array Drive %u: failed drive replaced by Cache Drive %u", +/*17*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: parity build failed", +/*18*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: drive rebuild failed", +/*19*/ "\007\000\002\010\002" + "GDT HA %u, Test of Hot Fix %u failed", +/*20*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: drive build finished successfully", +/*21*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: drive rebuild finished successfully", +/*22*/ "\007\000\002\006\002\010\002" + "GDT HA %u, Array Drive %u: Hot Fix %u activated", +/*23*/ "\005\000\002\006\002" + "GDT HA %u, Host Drive %u: processing of i/o aborted due to serious drive error", +/*24*/ "\005\000\002\010\002" + "GDT HA %u, mirror update on Cache Drive %u completed", +/*25*/ "\005\000\002\010\002" + "GDT HA %u, mirror update on Cache Drive %lu failed", +/*26*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: drive rebuild started", +/*27*/ "\005\000\002\012\001" + "GDT HA %u, Fault bus %u: SHELF OK detected", +/*28*/ "\005\000\002\012\001" + "GDT HA %u, Fault bus %u: SHELF not OK detected", +/*29*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: Auto Hot Plug started", +/*30*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: new disk detected", +/*31*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: old disk detected", +/*32*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: plugging an active disk is illegal", +/*33*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: illegal device detected", +/*34*/ "\011\000\002\012\001\013\001\006\004" + "GDT HA %u, Fault bus %u, ID %u: insufficient disk capacity (%lu MB required)", +/*35*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: disk write protected", +/*36*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: disk not available", +/*37*/ "\007\000\002\012\001\006\004" + "GDT HA %u, Fault bus %u: swap detected (%lu)", +/*38*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: Auto Hot Plug finished successfully", +/*39*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: Auto Hot Plug aborted due to user Hot Plug", +/*40*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: Auto Hot Plug aborted", +/*41*/ "\007\000\002\012\001\013\001" + "GDT HA %u, Fault bus %u, ID %u: Auto Hot Plug for Hot Fix started", +/*42*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: drive build started", +/*43*/ "\003\000\002" + "GDT HA %u, DRAM parity error detected", +/*44*/ "\005\000\002\006\002" + "GDT HA %u, Mirror Drive %u: update started", +/*45*/ "\007\000\002\006\002\010\002" + "GDT HA %u, Mirror Drive %u: Hot Fix %u activated", +/*46*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: no matching Pool Hot Fix Drive available", +/*47*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: Pool Hot Fix Drive available", +/*48*/ "\005\000\002\006\002" + "GDT HA %u, Mirror Drive %u: no matching Pool Hot Fix Drive available", +/*49*/ "\005\000\002\006\002" + "GDT HA %u, Mirror Drive %u: Pool Hot Fix Drive available", +/*50*/ "\007\000\002\012\001\013\001" + "GDT HA %u, SCSI bus %u, ID %u: IGNORE_WIDE_RESIDUE message received", +/*51*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: expand started", +/*52*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: expand finished successfully", +/*53*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: expand failed", +/*54*/ "\003\000\002" + "GDT HA %u, CPU temperature critical", +/*55*/ "\003\000\002" + "GDT HA %u, CPU temperature OK", +/*56*/ "\005\000\002\006\004" + "GDT HA %u, Host drive %lu created", +/*57*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: expand restarted", +/*58*/ "\005\000\002\006\002" + "GDT HA %u, Array Drive %u: expand stopped", +}; + + +static int gdth_async_event(int hanum,int service) +{ + gdth_stackframe stack; + gdth_evt_data dvr; + char *f = NULL; + int i,j; + gdth_ha_str *ha; + gdth_msg_str *msg; + gdth_cmd_str *cmdp; + int cmd_index; + + ha = HADATA(gdth_ctr_tab[hanum]); + cmdp= ha->pccb; + msg = (gdth_msg_str *)ha->pscratch; + TRACE2(("gdth_async_event() ha %d serv %d\n", + hanum,service)); + + if (service == SCREENSERVICE) { + if (ha->status == MSG_REQUEST) { + while (gdth_test_busy(hanum)) + gdth_delay(0); + cmdp->Service = SCREENSERVICE; + cmdp->RequestBuffer = SCREEN_CMND; + cmd_index = gdth_get_cmd_index(hanum); + gdth_set_sema0(hanum); + cmdp->OpCode = GDT_READ; + cmdp->BoardNode = LOCALBOARD; + cmdp->u.screen.reserved = 0; + cmdp->u.screen.msg_handle= MSG_INV_HANDLE; + cmdp->u.screen.msg_addr = (ulong)msg; + ha->cmd_offs_dpmem = 0; + ha->cmd_len = GDTOFFSOF(gdth_cmd_str,u.screen.msg_addr) + + sizeof(ulong); + ha->cmd_cnt = 0; + gdth_copy_command(hanum); + if (ha->type == GDT_EISA) + printk("[EISA slot %d] ",(ushort)ha->brd_phys); + else if (ha->type == GDT_ISA) + printk("[DPMEM 0x%4X] ",(ushort)ha->brd_phys); + else + printk("[PCI %d/%d] ",(ushort)(ha->brd_phys>>8), + (ushort)((ha->brd_phys>>3)&0x1f)); + gdth_release_event(hanum); + } + + } else { + dvr.size = sizeof(dvr.eu.async); + dvr.eu.async.ionode = hanum; + dvr.eu.async.service = service; + dvr.eu.async.status = ha->status; + dvr.eu.async.info = ha->info; + *(ulong *)dvr.eu.async.scsi_coord = ha->info2; + gdth_store_event(ES_ASYNC, service, &dvr); + + if (service==CACHESERVICE && INDEX_OK(ha->status,async_cache_tab)) { + TRACE2(("GDT: Async. event cache service, event no.: %d\n", + ha->status)); + + f = async_cache_tab[ha->status]; + + /* i: parameter to push, j: stack element to fill */ + for (j=0,i=1; i < f[0]; i+=2) { + switch (f[i+1]) { + case 4: + stack.b[j++] = *(ulong*)&dvr.eu.stream[(int)f[i]]; + break; + case 2: + stack.b[j++] = *(ushort*)&dvr.eu.stream[(int)f[i]]; + break; + case 1: + stack.b[j++] = *(unchar*)&dvr.eu.stream[(int)f[i]]; + break; + default: + break; + } + } + + printk(&f[f[0]],stack); printk("\n"); + + } else { + printk("GDT: Unknown async. event service %d event no. %d\n", + service,ha->status); + } + } + return 1; +} + + +#ifdef GDTH_STATISTICS +void gdth_timeout(ulong data) +{ + ulong flags,i; + Scsi_Cmnd *nscp; + gdth_ha_str *ha; + int hanum = 0; + + save_flags(flags); + cli(); + + for (act_stats=0,i=0; i<GDTH_MAXCMDS; ++i) + if (gdth_cmd_tab[i][hanum].cmnd != UNUSED_CMND) + ++act_stats; + + ha = HADATA(gdth_ctr_tab[hanum]); + for (act_rq=0,nscp=ha->req_first; nscp; nscp=(Scsi_Cmnd*)nscp->SCp.ptr) + ++act_rq; + + TRACE2(("gdth_to(): ints %ld, ios %ld, act_stats %ld, act_rq %ld\n", + act_ints, act_ios, act_stats, act_rq)); + act_ints = act_ios = 0; + + gdth_timer.expires = jiffies + 30 * HZ; + add_timer(&gdth_timer); + restore_flags(flags); +} +#endif + + +__initfunc (int gdth_detect(Scsi_Host_Template *shtp)) +{ + struct Scsi_Host *shp; + gdth_ha_str *ha; + unsigned long flags; + ulong isa_bios; + ushort eisa_slot,device_id,index; + gdth_pci_str pcistr; + int i,j,hanum; + unchar b; + + +#ifdef DEBUG_GDTH + printk("GDT: This driver contains debugging information !! Trace level = %d\n", + DebugState); + printk(" Destination of debugging information: "); +#ifdef __SERIAL__ +#ifdef __COM2__ + printk("Serial port COM2\n"); +#else + printk("Serial port COM1\n"); +#endif +#else + printk("Console\n"); +#endif + gdth_delay(3000); +#endif + + TRACE(("gdth_detect()\n")); + + if (disable_gdth_scan) { + printk("GDT: Controller driver disabled from command line !\n"); + return 0; + } + + /* initializations */ + gdth_polling = TRUE; b = 0; + for (i=0; i<GDTH_MAXCMDS; ++i) + for (j=0; j<MAXHA; ++j) + gdth_cmd_tab[i][j].cmnd = UNUSED_CMND; + for (i=0; i<4; ++i) + for (j=0; j<MAXHA; ++j) + gdth_ioctl_tab[i][j] = NULL; + gdth_clear_events(); + + /* scanning for controllers, at first: ISA controller */ + for (isa_bios=0xc8000UL; isa_bios<=0xd8000UL; isa_bios+=0x8000UL) { + if (gdth_search_isa(isa_bios)) { /* controller found */ + shp = scsi_register(shtp,sizeof(gdth_ext_str)); + ha = HADATA(shp); + if (!gdth_init_isa(isa_bios,ha)) { + scsi_unregister(shp); + continue; + } + /* controller found and initialized */ + printk("Configuring GDT-ISA HA at BIOS 0x%05lX IRQ %u DRQ %u\n", + isa_bios,ha->irq,ha->drq); + + save_flags(flags); + cli(); +#if LINUX_VERSION_CODE >= 0x02015F + if (request_irq(ha->irq,do_gdth_interrupt,SA_INTERRUPT,"gdth",NULL)) +#elif LINUX_VERSION_CODE >= 0x010346 + if (request_irq(ha->irq,gdth_interrupt,SA_INTERRUPT,"gdth",NULL)) +#else + if (request_irq(ha->irq,gdth_interrupt,SA_INTERRUPT,"gdth")) +#endif + { + printk("GDT-ISA: Unable to allocate IRQ\n"); + restore_flags(flags); + scsi_unregister(shp); + continue; + } + if (request_dma(ha->drq,"gdth")) { + printk("GDT-ISA: Unable to allocate DMA channel\n"); +#if LINUX_VERSION_CODE >= 0x010346 + free_irq(ha->irq,NULL); +#else + free_irq(ha->irq); +#endif + restore_flags(flags); + scsi_unregister(shp); + continue; + } + set_dma_mode(ha->drq,DMA_MODE_CASCADE); + enable_dma(ha->drq); + shp->unchecked_isa_dma = 1; + shp->irq = ha->irq; + shp->dma_channel = ha->drq; + for (i=0; i<MAXID; ++i) { + if (ha->id[0][i].type==SIOP_DTYP) { + shp->this_id = i; + break; + } + } + hanum = gdth_ctr_count; + gdth_ctr_tab[gdth_ctr_count++] = shp; + gdth_ctr_vtab[gdth_ctr_vcount++] = shp; + + NUMDATA(shp)->hanum = (ushort)hanum; + NUMDATA(shp)->busnum= 0; + + ha->pccb = CMDDATA(shp); + ha->pscratch = DMADATA(shp); + ha->req_first = NULL; + for (i=0; i<MAXBUS; ++i) { + for (j=0; j<MAXID; ++j) { + ha->id[i][j].type = EMPTY_DTYP; + ha->id[i][j].lock = 0; + ha->id[i][j].heads = 0; + } + } + restore_flags(flags); + + if (!gdth_search_drives(hanum)) { + printk("GDT-ISA: Error during device scan\n"); + --gdth_ctr_count; + --gdth_ctr_vcount; + save_flags(flags); + cli(); +#if LINUX_VERSION_CODE >= 0x010346 + free_irq(ha->irq,NULL); +#else + free_irq(ha->irq); +#endif + restore_flags(flags); + scsi_unregister(shp); + continue; + } + +#if LINUX_VERSION_CODE >= 0x020000 + shp->max_id = 8; + shp->max_lun = MAXLUN; + shp->max_channel = ha->bus_cnt - 1; +#else + /* register addit. SCSI channels as virtual controllers */ + for (b=1; b<ha->bus_cnt; ++b) { + shp = scsi_register(shtp,sizeof(gdth_num_str)); + shp->unchecked_isa_dma = 1; + shp->irq = ha->irq; + shp->dma_channel = ha->drq; + for (i=0; i<MAXID; ++i) { + if (ha->id[b][i].type==SIOP_DTYP) { + shp->this_id = i; + break; + } + } + gdth_ctr_vtab[gdth_ctr_vcount++] = shp; + NUMDATA(shp)->hanum = (ushort)hanum; + NUMDATA(shp)->busnum = b; + } +#endif + + gdth_enable_int(hanum); + } + } + + /* scanning for EISA controllers */ + for (eisa_slot=0x1000; eisa_slot<=0x8000; eisa_slot+=0x1000) { + if (gdth_search_eisa(eisa_slot)) { /* controller found */ + shp = scsi_register(shtp,sizeof(gdth_ext_str)); + ha = HADATA(shp); + if (!gdth_init_eisa(eisa_slot,ha)) { + scsi_unregister(shp); + continue; + } + /* controller found and initialized */ + printk("Configuring GDT-EISA HA at Slot %d IRQ %u\n", + eisa_slot>>12,ha->irq); + + save_flags(flags); + cli(); +#if LINUX_VERSION_CODE >= 0x02015F + if (request_irq(ha->irq,do_gdth_interrupt,SA_INTERRUPT,"gdth",NULL)) +#elif LINUX_VERSION_CODE >= 0x010346 + if (request_irq(ha->irq,gdth_interrupt,SA_INTERRUPT,"gdth",NULL)) +#else + if (request_irq(ha->irq,gdth_interrupt,SA_INTERRUPT,"gdth")) +#endif + { + printk("GDT-EISA: Unable to allocate IRQ\n"); + restore_flags(flags); + scsi_unregister(shp); + continue; + } + shp->unchecked_isa_dma = 0; + shp->irq = ha->irq; + shp->dma_channel = 0xff; + for (i=0; i<MAXID; ++i) { + if (ha->id[0][i].type==SIOP_DTYP) { + shp->this_id = i; + break; + } + } + hanum = gdth_ctr_count; + gdth_ctr_tab[gdth_ctr_count++] = shp; + gdth_ctr_vtab[gdth_ctr_vcount++] = shp; + + NUMDATA(shp)->hanum = (ushort)hanum; + NUMDATA(shp)->busnum= 0; + TRACE2(("EISA detect Bus 0: shp %lx hanum %d\n", + (ulong)shp,NUMDATA(shp)->hanum)); + + ha->pccb = CMDDATA(shp); + ha->pscratch = DMADATA(shp); + ha->req_first = NULL; + for (i=0; i<MAXBUS; ++i) { + for (j=0; j<MAXID; ++j) { + ha->id[i][j].type = EMPTY_DTYP; + ha->id[i][j].lock = 0; + ha->id[i][j].heads = 0; + } + } + restore_flags(flags); + + if (!gdth_search_drives(hanum)) { + printk("GDT-EISA: Error during device scan\n"); + --gdth_ctr_count; + --gdth_ctr_vcount; + save_flags(flags); + cli(); +#if LINUX_VERSION_CODE >= 0x010346 + free_irq(ha->irq,NULL); +#else + free_irq(ha->irq); +#endif + restore_flags(flags); + scsi_unregister(shp); + continue; + } + +#if LINUX_VERSION_CODE >= 0x020000 + shp->max_id = 8; + shp->max_lun = MAXLUN; + shp->max_channel = ha->bus_cnt - 1; +#else + /* register addit. SCSI channels as virtual controllers */ + for (b=1; b<ha->bus_cnt; ++b) { + shp = scsi_register(shtp,sizeof(gdth_num_str)); + shp->unchecked_isa_dma = 0; + shp->irq = ha->irq; + shp->dma_channel = 0xff; + for (i=0; i<MAXID; ++i) { + if (ha->id[b][i].type==SIOP_DTYP) { + shp->this_id = i; + break; + } + } + gdth_ctr_vtab[gdth_ctr_vcount++] = shp; + NUMDATA(shp)->hanum = (ushort)hanum; + NUMDATA(shp)->busnum = b; + TRACE2(("EISA detect Bus %d: shp %lx hanum %d\n", + NUMDATA(shp)->busnum,(ulong)shp, + NUMDATA(shp)->hanum)); + } +#endif + + gdth_enable_int(hanum); + } + } + + /* scanning for PCI controllers */ + for (device_id = 0; device_id <= PCI_DEVICE_ID_VORTEX_GDTMAXRP; ++device_id) { + if (device_id > PCI_DEVICE_ID_VORTEX_GDT6555 && + device_id < PCI_DEVICE_ID_VORTEX_GDT6x17RP) + continue; + for (index = 0; ; ++index) { + if (!gdth_search_pci(device_id,index,&pcistr)) + break; /* next device_id */ + shp = scsi_register(shtp,sizeof(gdth_ext_str)); + ha = HADATA(shp); + if (!gdth_init_pci(&pcistr,ha)) { + scsi_unregister(shp); + continue; + } + /* controller found and initialized */ + printk("Configuring GDT-PCI HA at %d/%d IRQ %u\n", + pcistr.bus,pcistr.device_fn>>3,ha->irq); + + save_flags(flags); + cli(); +#if LINUX_VERSION_CODE >= 0x02015F + if (request_irq(ha->irq,do_gdth_interrupt,SA_INTERRUPT|SA_SHIRQ,"gdth",NULL)) +#elif LINUX_VERSION_CODE >= 0x010346 + if (request_irq(ha->irq,gdth_interrupt,SA_INTERRUPT|SA_SHIRQ,"gdth",NULL)) +#else + if (request_irq(ha->irq,gdth_interrupt,SA_INTERRUPT|SA_SHIRQ,"gdth")) +#endif + { + printk("GDT-PCI: Unable to allocate IRQ\n"); + restore_flags(flags); + scsi_unregister(shp); + continue; + } + shp->unchecked_isa_dma = 0; + shp->irq = ha->irq; + shp->dma_channel = 0xff; + for (i=0; i<MAXID; ++i) { + if (ha->id[0][i].type==SIOP_DTYP) { + shp->this_id = i; + break; + } + } + hanum = gdth_ctr_count; + gdth_ctr_tab[gdth_ctr_count++] = shp; + gdth_ctr_vtab[gdth_ctr_vcount++] = shp; + + NUMDATA(shp)->hanum = (ushort)hanum; + NUMDATA(shp)->busnum= 0; + + ha->pccb = CMDDATA(shp); + ha->pscratch = DMADATA(shp); + ha->req_first = NULL; + for (i=0; i<MAXBUS; ++i) { + for (j=0; j<MAXID; ++j) { + ha->id[i][j].type = EMPTY_DTYP; + ha->id[i][j].lock = 0; + ha->id[i][j].heads = 0; + } + } + restore_flags(flags); + + if (!gdth_search_drives(hanum)) { + printk("GDT-PCI: Error during device scan\n"); + --gdth_ctr_count; + --gdth_ctr_vcount; + save_flags(flags); + cli(); +#if LINUX_VERSION_CODE >= 0x010346 + free_irq(ha->irq,NULL); +#else + free_irq(ha->irq); +#endif + restore_flags(flags); + scsi_unregister(shp); + continue; + } + +#if LINUX_VERSION_CODE >= 0x020000 + shp->max_id = MAXID; + shp->max_lun = MAXLUN; + shp->max_channel = ha->bus_cnt - 1; +#else + /* register addit. SCSI channels as virtual controllers */ + for (b=1; b<ha->bus_cnt; ++b) { + shp = scsi_register(shtp,sizeof(gdth_num_str)); + shp->unchecked_isa_dma = 0; + shp->irq = ha->irq; + shp->dma_channel = 0xff; + for (i=0; i<MAXID; ++i) { + if (ha->id[b][i].type==SIOP_DTYP) { + shp->this_id = i; + break; + } + } + gdth_ctr_vtab[gdth_ctr_vcount++] = shp; + NUMDATA(shp)->hanum = (ushort)hanum; + NUMDATA(shp)->busnum = b; + } +#endif + + gdth_enable_int(hanum); + } + } + + TRACE2(("gdth_detect() %d controller detected\n",gdth_ctr_count)); + if (gdth_ctr_count > 0) { +#ifdef GDTH_STATISTICS + TRACE2(("gdth_detect(): Initializing timer !\n")); + init_timer(&gdth_timer); + gdth_timer.expires = jiffies + HZ; + gdth_timer.data = 0L; + gdth_timer.function = gdth_timeout; + add_timer(&gdth_timer); +#endif +#if LINUX_VERSION_CODE >= 0x020100 + register_reboot_notifier(&gdth_notifier); +#endif + } + gdth_polling = FALSE; + return gdth_ctr_vcount; +} + + +int gdth_release(struct Scsi_Host *shp) +{ + unsigned long flags; + + TRACE2(("gdth_release()\n")); + + if (NUMDATA(shp)->busnum == 0) { + gdth_flush(NUMDATA(shp)->hanum); + + save_flags(flags); + cli(); + if (shp->irq) { +#if LINUX_VERSION_CODE >= 0x010346 + free_irq(shp->irq,NULL); +#else + free_irq(shp->irq); +#endif + } + if (shp->dma_channel != 0xff) { + free_dma(shp->dma_channel); + } + restore_flags(flags); + gdth_ctr_released++; + TRACE2(("gdth_release(): HA %d of %d\n", + gdth_ctr_released, gdth_ctr_count)); + + if (gdth_ctr_released == gdth_ctr_count) { +#ifdef GDTH_STATISTICS + del_timer(&gdth_timer); +#endif +#if LINUX_VERSION_CODE >= 0x020100 + unregister_reboot_notifier(&gdth_notifier); +#endif + } + } + + scsi_unregister(shp); + return 0; +} + + +static const char *gdth_ctr_name(int hanum) +{ + gdth_ha_str *ha; + + TRACE2(("gdth_ctr_name()\n")); + + ha = HADATA(gdth_ctr_tab[hanum]); + + if (ha->type == GDT_EISA) { + switch (ha->stype) { + case GDT3_ID: + return("GDT3000/3020"); + case GDT3A_ID: + return("GDT3000A/3020A/3050A"); + case GDT3B_ID: + return("GDT3000B/3010A"); + } + } else if (ha->type == GDT_ISA) { + return("GDT2000/2020"); + } else if (ha->type == GDT_PCI) { + switch (ha->stype) { + case PCI_DEVICE_ID_VORTEX_GDT60x0: + return("GDT6000/6020/6050"); + case PCI_DEVICE_ID_VORTEX_GDT6000B: + return("GDT6000B/6010"); + } + } + /* new controllers (GDT_PCINEW, GDT_PCIMPR, ..) use board_info IOCTL! */ + + return(""); +} + +const char *gdth_info(struct Scsi_Host *shp) +{ + int hanum; + gdth_ha_str *ha; + + TRACE2(("gdth_info()\n")); + hanum = NUMDATA(shp)->hanum; + ha = HADATA(gdth_ctr_tab[hanum]); + + return ((const char *)ha->ctr_name); +} + +/* old error handling */ +int gdth_abort(Scsi_Cmnd *scp) +{ + TRACE2(("gdth_abort() reason %d\n",scp->abort_reason)); + return SCSI_ABORT_SNOOZE; +} + +#if LINUX_VERSION_CODE >= 0x010346 +int gdth_reset(Scsi_Cmnd *scp, unsigned int reset_flags) +#else +int gdth_reset(Scsi_Cmnd *scp) +#endif +{ + TRACE2(("gdth_reset()\n")); + return SCSI_RESET_PUNT; +} + +#if LINUX_VERSION_CODE >= 0x02015F +/* new error handling */ +int gdth_eh_abort(Scsi_Cmnd *scp) +{ + TRACE2(("gdth_eh_abort()\n")); + return FAILED; +} + +int gdth_eh_device_reset(Scsi_Cmnd *scp) +{ + TRACE2(("gdth_eh_device_reset()\n")); + return FAILED; +} + +int gdth_eh_bus_reset(Scsi_Cmnd *scp) +{ + TRACE2(("gdth_eh_bus_reset()\n")); + return FAILED; +} + +int gdth_eh_host_reset(Scsi_Cmnd *scp) +{ + TRACE2(("gdth_eh_host_reset()\n")); + return FAILED; +} +#endif + +#if LINUX_VERSION_CODE >= 0x010300 +int gdth_bios_param(Disk *disk,kdev_t dev,int *ip) +#else +int gdth_bios_param(Disk *disk,int dev,int *ip) +#endif +{ + unchar b, t; + int hanum; + gdth_ha_str *ha; + int drv_hds, drv_secs; + + hanum = NUMDATA(disk->device->host)->hanum; + b = disk->device->channel; + t = disk->device->id; + TRACE2(("gdth_bios_param() ha %d bus %d target %d\n", hanum, b, t)); + ha = HADATA(gdth_ctr_tab[hanum]); + + if (ha->id[b][t].heads == 0) { + /* raw device: evaluate mapping (sectors per head, heads per cylinder) */ + if (disk->capacity /HEADS/SECS <= MAXCYLS) { + drv_hds = HEADS; + drv_secs= SECS; + } else if (disk->capacity /MEDHEADS/MEDSECS <= MAXCYLS) { + drv_hds = MEDHEADS; + drv_secs= MEDSECS; + } else { + drv_hds = BIGHEADS; + drv_secs= BIGSECS; + } + ha->id[b][t].heads = drv_hds; + ha->id[b][t].secs = drv_secs; + TRACE2(("gdth_bios_param(): raw device -> params evaluated\n")); + } + + ip[0] = ha->id[b][t].heads; + ip[1] = ha->id[b][t].secs; + ip[2] = disk->capacity / ip[0] / ip[1]; + + TRACE2(("gdth_bios_param(): %d heads, %d secs, %d cyls\n", + ip[0],ip[1],ip[2])); + return 0; +} + + +static void internal_done(Scsi_Cmnd *scp) +{ + scp->SCp.sent_command++; +} + +int gdth_command(Scsi_Cmnd *scp) +{ + TRACE2(("gdth_command()\n")); + + scp->SCp.sent_command = 0; + gdth_queuecommand(scp,internal_done); + + while (!scp->SCp.sent_command) + barrier(); + return scp->result; +} + + +int gdth_queuecommand(Scsi_Cmnd *scp,void (*done)(Scsi_Cmnd *)) +{ + int hanum; + int priority; + + TRACE(("gdth_queuecommand() cmd 0x%x id %d lun %d\n", + scp->cmnd[0],scp->target,scp->lun)); + + scp->scsi_done = (void *)done; + scp->SCp.have_data_in = 1; + hanum = NUMDATA(scp->host)->hanum; +#ifdef GDTH_STATISTICS + ++act_ios; +#endif + + priority = DEFAULT_PRI; +#if LINUX_VERSION_CODE >= 0x010300 + if (scp->done == gdth_scsi_done) + priority = scp->SCp.this_residual; +#endif + gdth_putq( hanum, scp, priority ); + gdth_next( hanum ); + return 0; +} + +/* flush routine */ +static void gdth_flush(int hanum) +{ + int i, j; + gdth_ha_str *ha; + Scsi_Cmnd scp; + Scsi_Device sdev; + gdth_cmd_str gdtcmd; + char cmnd[12]; + + TRACE2(("gdth_flush() hanum %d\n",hanum)); + ha = HADATA(gdth_ctr_tab[hanum]); + memset(&sdev,0,sizeof(Scsi_Device)); + memset(&scp, 0,sizeof(Scsi_Cmnd)); + sdev.host = gdth_ctr_tab[hanum]; + sdev.id = sdev.host->this_id; + scp.cmd_len = 12; + scp.host = gdth_ctr_tab[hanum]; + scp.target = sdev.host->this_id; + scp.device = &sdev; + scp.use_sg = 0; + + for (i = 0; i < MAXBUS; ++i) { + for (j = 0; j < MAXID; ++j) { + if (ha->id[i][j].type == CACHE_DTYP) { + gdtcmd.BoardNode = LOCALBOARD; + gdtcmd.Service = CACHESERVICE; + gdtcmd.OpCode = GDT_FLUSH; + gdtcmd.u.cache.DeviceNo = ha->id[i][j].hostdrive; + gdtcmd.u.cache.BlockNo = 1; + gdtcmd.u.cache.sg_canz = 0; + TRACE2(("gdth_flush(): flush ha %d drive %d\n", + hanum, ha->id[i][j].hostdrive)); + { + struct semaphore sem = MUTEX_LOCKED; + scp.request.rq_status = RQ_SCSI_BUSY; + scp.request.sem = &sem; + scp.SCp.this_residual = IOCTL_PRI; + scsi_do_cmd(&scp, cmnd, &gdtcmd, + sizeof(gdth_cmd_str), gdth_scsi_done, + 30*HZ, 1); + down(&sem); + } + } + } + } +} + +/* shutdown routine */ +#if LINUX_VERSION_CODE >= 0x020100 +static int gdth_halt(struct notifier_block *nb, ulong event, void *buf) +#else +void gdth_halt(void) +#endif +{ + int hanum; + Scsi_Cmnd scp; + Scsi_Device sdev; + gdth_cmd_str gdtcmd; + char cmnd[12]; + +#if LINUX_VERSION_CODE >= 0x020100 + TRACE2(("gdth_halt() event %d\n",event)); + if (event != SYS_RESTART && event != SYS_HALT && event != SYS_POWER_OFF) + return NOTIFY_DONE; +#else + TRACE2(("gdth_halt()\n")); + if (halt_called) { + TRACE2(("already called\n")); + return; + } + halt_called = TRUE; +#endif + printk("GDT: Flushing all host drives .. "); + for (hanum = 0; hanum < gdth_ctr_count; ++hanum) { + gdth_flush(hanum); + + /* controller reset */ + memset(&sdev,0,sizeof(Scsi_Device)); + memset(&scp, 0,sizeof(Scsi_Cmnd)); + sdev.host = gdth_ctr_tab[hanum]; + sdev.id = sdev.host->this_id; + scp.cmd_len = 12; + scp.host = gdth_ctr_tab[hanum]; + scp.target = sdev.host->this_id; + scp.device = &sdev; + scp.use_sg = 0; + + gdtcmd.BoardNode = LOCALBOARD; + gdtcmd.Service = CACHESERVICE; + gdtcmd.OpCode = GDT_RESET; + TRACE2(("gdth_halt(): reset controller %d\n", hanum)); + { + struct semaphore sem = MUTEX_LOCKED; + scp.request.rq_status = RQ_SCSI_BUSY; + scp.request.sem = &sem; + scp.SCp.this_residual = IOCTL_PRI; + scsi_do_cmd(&scp, cmnd, &gdtcmd, + sizeof(gdth_cmd_str), gdth_scsi_done, + 10*HZ, 1); + down(&sem); + } + } + printk("Done.\n"); + +#ifdef GDTH_STATISTICS + del_timer(&gdth_timer); +#endif +#if LINUX_VERSION_CODE >= 0x020100 + unregister_reboot_notifier(&gdth_notifier); + return NOTIFY_OK; +#endif +} + + +/* called from init/main.c */ +__initfunc (void gdth_setup(char *str,int *ints)) +{ + static size_t setup_idx = 0; + + TRACE2(("gdth_setup() str %s ints[0] %d ints[1] %d\n", + str ? str:"NULL", ints[0], + ints[0] ? ints[1]:0)); + + if (setup_idx >= MAXHA) { + printk("GDT: gdth_setup() called too many times. Bad LILO params ?\n"); + return; + } + if (ints[0] != 1) { + printk("GDT: Illegal command line !\n"); + printk("Usage: gdth=<IRQ>\n"); + printk("Where: <IRQ>: valid EISA controller IRQ (10,11,12,14)\n"); + printk(" or 0 to disable controller driver\n"); + return; + } + if (ints[1] == 10 || ints[1] == 11 || ints[1] == 12 || ints[1] == 14) { + irqs[setup_idx++] = ints[1]; + irqs[setup_idx] = 0xff; + return; + } + if (ints[1] == 0) { + disable_gdth_scan = TRUE; + return; + } + printk("GDT: Invalid IRQ (%d) specified\n",ints[1]); +} + + +#ifdef MODULE +Scsi_Host_Template driver_template = GDTH; +#include "scsi_module.c" +#endif + diff --git a/linux/src/drivers/scsi/gdth.h b/linux/src/drivers/scsi/gdth.h new file mode 100644 index 00000000..6eafd1f8 --- /dev/null +++ b/linux/src/drivers/scsi/gdth.h @@ -0,0 +1,819 @@ +#ifndef _GDTH_H +#define _GDTH_H + +/* + * Header file for the GDT ISA/EISA/PCI Disk Array Controller driver for Linux + * + * gdth.h Copyright (C) 1995-98 ICP vortex Computersysteme GmbH, Achim Leubner + * See gdth.c for further informations and + * below for supported controller types + * + * <achim@vortex.de> + * + * $Id: gdth.h,v 1.1 1999/04/26 05:54:37 tb Exp $ + */ + +#include <linux/version.h> +#include <linux/types.h> + +#ifndef NULL +#define NULL 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/* defines, macros */ + +/* driver version */ +#define GDTH_VERSION_STR "1.07" +#define GDTH_VERSION 1 +#define GDTH_SUBVERSION 7 + +/* protocol version */ +#define PROTOCOL_VERSION 1 + +/* controller classes */ +#define GDT_ISA 0x01 /* ISA controller */ +#define GDT_EISA 0x02 /* EISA controller */ +#define GDT_PCI 0x03 /* PCI controller */ +#define GDT_PCINEW 0x04 /* new PCI controller */ +#define GDT_PCIMPR 0x05 /* PCI MPR controller */ +/* GDT_EISA, controller subtypes EISA */ +#define GDT3_ID 0x0130941c /* GDT3000/3020 */ +#define GDT3A_ID 0x0230941c /* GDT3000A/3020A/3050A */ +#define GDT3B_ID 0x0330941c /* GDT3000B/3010A */ +/* GDT_ISA */ +#define GDT2_ID 0x0120941c /* GDT2000/2020 */ +/* vendor ID, device IDs (PCI) */ +/* these defines should already exist in <linux/pci.h> */ +#ifndef PCI_VENDOR_ID_VORTEX +#define PCI_VENDOR_ID_VORTEX 0x1119 /* PCI controller vendor ID */ +#endif +#ifndef PCI_DEVICE_ID_VORTEX_GDT60x0 +/* GDT_PCI */ +#define PCI_DEVICE_ID_VORTEX_GDT60x0 0 /* GDT6000/6020/6050 */ +#define PCI_DEVICE_ID_VORTEX_GDT6000B 1 /* GDT6000B/6010 */ +/* GDT_PCINEW */ +#define PCI_DEVICE_ID_VORTEX_GDT6x10 2 /* GDT6110/6510 */ +#define PCI_DEVICE_ID_VORTEX_GDT6x20 3 /* GDT6120/6520 */ +#define PCI_DEVICE_ID_VORTEX_GDT6530 4 /* GDT6530 */ +#define PCI_DEVICE_ID_VORTEX_GDT6550 5 /* GDT6550 */ +/* GDT_PCINEW, wide/ultra SCSI controllers */ +#define PCI_DEVICE_ID_VORTEX_GDT6x17 6 /* GDT6117/6517 */ +#define PCI_DEVICE_ID_VORTEX_GDT6x27 7 /* GDT6127/6527 */ +#define PCI_DEVICE_ID_VORTEX_GDT6537 8 /* GDT6537 */ +#define PCI_DEVICE_ID_VORTEX_GDT6557 9 /* GDT6557/6557-ECC */ +/* GDT_PCINEW, wide SCSI controllers */ +#define PCI_DEVICE_ID_VORTEX_GDT6x15 10 /* GDT6115/6515 */ +#define PCI_DEVICE_ID_VORTEX_GDT6x25 11 /* GDT6125/6525 */ +#define PCI_DEVICE_ID_VORTEX_GDT6535 12 /* GDT6535 */ +#define PCI_DEVICE_ID_VORTEX_GDT6555 13 /* GDT6555/6555-ECC */ +#endif + +#ifndef PCI_DEVICE_ID_VORTEX_GDT6x17RP +/* GDT_MPR, RP series, wide/ultra SCSI */ +#define PCI_DEVICE_ID_VORTEX_GDT6x17RP 0x100 /* GDT6117RP/GDT6517RP */ +#define PCI_DEVICE_ID_VORTEX_GDT6x27RP 0x101 /* GDT6127RP/GDT6527RP */ +#define PCI_DEVICE_ID_VORTEX_GDT6537RP 0x102 /* GDT6537RP */ +#define PCI_DEVICE_ID_VORTEX_GDT6557RP 0x103 /* GDT6557RP */ +/* GDT_MPR, RP series, narrow/ultra SCSI */ +#define PCI_DEVICE_ID_VORTEX_GDT6x11RP 0x104 /* GDT6111RP/GDT6511RP */ +#define PCI_DEVICE_ID_VORTEX_GDT6x21RP 0x105 /* GDT6121RP/GDT6521RP */ +/* GDT_MPR, RP1 series, wide/ultra SCSI */ +#define PCI_DEVICE_ID_VORTEX_GDT6x17RP1 0x110 /* GDT6117RP1/GDT6517RP1 */ +#define PCI_DEVICE_ID_VORTEX_GDT6x27RP1 0x111 /* GDT6127RP1/GDT6527RP1 */ +#define PCI_DEVICE_ID_VORTEX_GDT6537RP1 0x112 /* GDT6537RP1 */ +#define PCI_DEVICE_ID_VORTEX_GDT6557RP1 0x113 /* GDT6557RP1 */ +/* GDT_MPR, RP1 series, narrow/ultra SCSI */ +#define PCI_DEVICE_ID_VORTEX_GDT6x11RP1 0x114 /* GDT6111RP1/GDT6511RP1 */ +#define PCI_DEVICE_ID_VORTEX_GDT6x21RP1 0x115 /* GDT6121RP1/GDT6521RP1 */ +/* GDT_MPR, RP2 series, wide/ultra SCSI */ +#define PCI_DEVICE_ID_VORTEX_GDT6x17RP2 0x120 /* GDT6117RP2/GDT6517RP2 */ +#define PCI_DEVICE_ID_VORTEX_GDT6x27RP2 0x121 /* GDT6127RP2/GDT6527RP2 */ +#define PCI_DEVICE_ID_VORTEX_GDT6537RP2 0x122 /* GDT6537RP2 */ +#define PCI_DEVICE_ID_VORTEX_GDT6557RP2 0x123 /* GDT6557RP2 */ +/* GDT_MPR, RP2 series, narrow/ultra SCSI */ +#define PCI_DEVICE_ID_VORTEX_GDT6x11RP2 0x124 /* GDT6111RP2/GDT6511RP2 */ +#define PCI_DEVICE_ID_VORTEX_GDT6x21RP2 0x125 /* GDT6121RP2/GDT6521RP2 */ +#endif + +#ifndef PCI_DEVICE_ID_VORTEX_GDT6519RD +/* GDT_MPR, Fibre Channel */ +#define PCI_DEVICE_ID_VORTEX_GDT6519RD 0x210 /* GDT6519RD */ +#define PCI_DEVICE_ID_VORTEX_GDT6529RD 0x211 /* GDT6529RD */ +#endif + +#ifndef PCI_DEVICE_ID_VORTEX_GDTMAXRP +/* GDT_MPR, last device ID */ +#define PCI_DEVICE_ID_VORTEX_GDTMAXRP 0x2ff +#endif + +/* limits */ +#define GDTH_SCRATCH 4096 /* 4KB scratch buffer */ +#define GDTH_MAXCMDS 124 +#define GDTH_MAXC_P_L 16 /* max. cmds per lun */ +#define MAXOFFSETS 128 +#define MAXHA 8 +#define MAXID 16 +#define MAXLUN 8 +#define MAXBUS 6 +#define MAX_HDRIVES 35 /* max. host drive count */ +#define MAX_EVENTS 100 /* event buffer count */ +#define MAXCYLS 1024 +#define HEADS 64 +#define SECS 32 /* mapping 64*32 */ +#define MEDHEADS 127 +#define MEDSECS 63 /* mapping 127*63 */ +#define BIGHEADS 255 +#define BIGSECS 63 /* mapping 255*63 */ + +/* special command ptr. */ +#define UNUSED_CMND ((Scsi_Cmnd *)-1) +#define INTERNAL_CMND ((Scsi_Cmnd *)-2) +#define SCREEN_CMND ((Scsi_Cmnd *)-3) +#define SPECIAL_SCP(p) (p==UNUSED_CMND || p==INTERNAL_CMND || p==SCREEN_CMND) + +/* device types */ +#define EMPTY_DTYP 0 +#define CACHE_DTYP 1 +#define RAW_DTYP 2 +#define SIOP_DTYP 3 /* the SCSI processor */ + +/* controller services */ +#define SCSIRAWSERVICE 3 +#define CACHESERVICE 9 +#define SCREENSERVICE 11 + +/* screenservice defines */ +#define MSG_INV_HANDLE -1 /* special message handle */ +#define MSGLEN 16 /* size of message text */ +#define MSG_SIZE 34 /* size of message structure */ +#define MSG_REQUEST 0 /* async. event: message */ + +/* cacheservice defines */ +#define SECTOR_SIZE 0x200 /* always 512 bytes per sector */ + +/* DPMEM constants */ +#define DPMEM_MAGIC 0xC0FFEE11 +#define IC_HEADER_BYTES 48 +#define IC_QUEUE_BYTES 4 +#define DPMEM_COMMAND_OFFSET IC_HEADER_BYTES+IC_QUEUE_BYTES*MAXOFFSETS + +/* service commands */ +#define GDT_INIT 0 /* service initialization */ +#define GDT_READ 1 /* read command */ +#define GDT_WRITE 2 /* write command */ +#define GDT_INFO 3 /* information about devices */ +#define GDT_FLUSH 4 /* flush dirty cache buffers */ +#define GDT_IOCTL 5 /* ioctl command */ +#define GDT_DEVTYPE 9 /* additional information */ +#define GDT_MOUNT 10 /* mount cache device */ +#define GDT_UNMOUNT 11 /* unmount cache device */ +#define GDT_SET_FEAT 12 /* set feat. (scatter/gather) */ +#define GDT_GET_FEAT 13 /* get features */ +#define GDT_RESERVE 14 /* reserve dev. to raw service */ +#define GDT_WRITE_THR 16 /* write through */ +#define GDT_EXT_INFO 18 /* extended info */ +#define GDT_RESET 19 /* controller reset */ + +/* IOCTL command defines */ +#define SCSI_CHAN_CNT 5 /* subfunctions */ +#define GET_IOCHAN_DESC 0x5e +#define L_CTRL_PATTERN 0x20000000L +#define CACHE_INFO 4 +#define CACHE_CONFIG 5 +#define BOARD_INFO 0x28 +#define IO_CHANNEL 0x00020000L /* channels */ +#define INVALID_CHANNEL 0x0000ffffL + +/* IOCTLs */ +#define GDTIOCTL_MASK ('J'<<8) +#define GDTIOCTL_GENERAL (GDTIOCTL_MASK | 0) /* general IOCTL */ +#define GDTIOCTL_DRVERS (GDTIOCTL_MASK | 1) /* get driver version */ +#define GDTIOCTL_CTRTYPE (GDTIOCTL_MASK | 2) /* get controller type */ +#define GDTIOCTL_CTRCNT (GDTIOCTL_MASK | 5) /* get controller count */ +#define GDTIOCTL_LOCKDRV (GDTIOCTL_MASK | 6) /* lock host drive */ +#define GDTIOCTL_LOCKCHN (GDTIOCTL_MASK | 7) /* lock channel */ +#define GDTIOCTL_EVENT (GDTIOCTL_MASK | 8) /* read controller events */ + +/* service errors */ +#define S_OK 1 /* no error */ +#define S_BSY 7 /* controller busy */ +#define S_RAW_SCSI 12 /* raw serv.: target error */ +#define S_RAW_ILL 0xff /* raw serv.: illegal */ + +/* timeout values */ +#define INIT_RETRIES 10000 /* 10000 * 1ms = 10s */ +#define INIT_TIMEOUT 100000 /* 1000 * 1ms = 1s */ +#define POLL_TIMEOUT 10000 /* 10000 * 1ms = 10s */ + +/* priorities */ +#define DEFAULT_PRI 0x20 +#define IOCTL_PRI 0x10 + +/* data directions */ +#define DATA_IN 0x01000000L /* data from target */ +#define DATA_OUT 0x00000000L /* data to target */ + +/* BMIC registers (EISA controllers) */ +#define ID0REG 0x0c80 /* board ID */ +#define EINTENABREG 0x0c89 /* interrupt enable */ +#define SEMA0REG 0x0c8a /* command semaphore */ +#define SEMA1REG 0x0c8b /* status semaphore */ +#define LDOORREG 0x0c8d /* local doorbell */ +#define EDENABREG 0x0c8e /* EISA system doorbell enable */ +#define EDOORREG 0x0c8f /* EISA system doorbell */ +#define MAILBOXREG 0x0c90 /* mailbox reg. (16 bytes) */ +#define EISAREG 0x0cc0 /* EISA configuration */ + +/* other defines */ +#define LINUX_OS 8 /* used for cache optim. */ +#define SCATTER_GATHER 1 /* s/g feature */ +#define GDTH_MAXSG 32 /* max. s/g elements */ +#define SECS32 0x1f /* round capacity */ +#define BIOS_ID_OFFS 0x10 /* offset contr. ID in ISABIOS */ +#define LOCALBOARD 0 /* board node always 0 */ +#define ASYNCINDEX 0 /* cmd index async. event */ +#define SPEZINDEX 1 /* cmd index unknown service */ +#define GDT_WR_THROUGH 0x100 /* WRITE_THROUGH supported */ + +/* typedefs */ + +#pragma pack(1) + +typedef struct { + char buffer[GDTH_SCRATCH]; /* scratch buffer */ +} gdth_scratch_str; + +/* screenservice message */ +typedef struct { + ulong msg_handle; /* message handle */ + ulong msg_len; /* size of message */ + ulong msg_alen; /* answer length */ + unchar msg_answer; /* answer flag */ + unchar msg_ext; /* more messages */ + unchar msg_reserved[2]; + char msg_text[MSGLEN+2]; /* the message text */ +} gdth_msg_str; + +/* get channel count IOCTL */ +typedef struct { + ulong channel_no; /* number of channel */ + ulong drive_cnt; /* number of drives */ + unchar siop_id; /* SCSI processor ID */ + unchar siop_state; /* SCSI processor state */ +} gdth_getch_str; + +/* get raw channel count IOCTL (NEW!) */ +typedef struct { + ulong version; /* version of information (-1UL: newest) */ + unchar list_entries; /* list entry count */ + unchar first_chan; /* first channel number */ + unchar last_chan; /* last channel number */ + unchar chan_count; /* (R) channel count */ + ulong list_offset; /* offset of list[0] */ + struct { + unchar proc_id; /* processor id */ + unchar proc_defect; /* defect ? */ + unchar reserved[2]; + } list[MAXBUS]; +} gdth_iochan_str; + +/* cache info/config IOCTL */ +typedef struct { + ulong version; /* firmware version */ + ushort state; /* cache state (on/off) */ + ushort strategy; /* cache strategy */ + ushort write_back; /* write back state (on/off) */ + ushort block_size; /* cache block size */ +} gdth_cpar_str; + +typedef struct { + ulong csize; /* cache size */ + ulong read_cnt; /* read/write counter */ + ulong write_cnt; + ulong tr_hits; /* hits */ + ulong sec_hits; + ulong sec_miss; /* misses */ +} gdth_cstat_str; + +typedef struct { + gdth_cpar_str cpar; + gdth_cstat_str cstat; +} gdth_cinfo_str; + +/* board info IOCTL */ +typedef struct { + ulong ser_no; /* serial no. */ + unchar oem_id[2]; /* OEM ID */ + ushort ep_flags; /* eprom flags */ + ulong proc_id; /* processor ID */ + ulong memsize; /* memory size (bytes) */ + unchar mem_banks; /* memory banks */ + unchar chan_type; /* channel type */ + unchar chan_count; /* channel count */ + unchar rdongle_pres; /* dongle present? */ + ulong epr_fw_ver; /* (eprom) firmware version */ + ulong upd_fw_ver; /* (update) firmware version */ + ulong upd_revision; /* update revision */ + char type_string[16]; /* controller name */ + char raid_string[16]; /* RAID firmware name */ + unchar update_pres; /* update present? */ + unchar xor_pres; /* XOR engine present? */ + unchar prom_type; /* ROM type (eprom/flash eprom) */ + unchar prom_count; /* number of ROM devices */ + ulong dup_pres; /* duplexing module present? */ + ulong chan_pres; /* number of expansion channels */ + ulong mem_pres; /* memory expansion installed? */ + unchar ft_bus_system; /* fault bus supported? */ + unchar subtype_valid; /* board_subtype valid? */ + unchar board_subtype; /* controller subtype/hardware level */ + unchar ramparity_pres; /* RAM parity check hardware present? */ +} gdth_binfo_str; + +/* scatter/gather element */ +typedef struct { + ulong sg_ptr; /* address */ + ulong sg_len; /* length */ +} gdth_sg_str; + +/* command structure */ +typedef struct { + ulong BoardNode; /* board node (always 0) */ + ulong CommandIndex; /* command number */ + ushort OpCode; /* the command (READ,..) */ + union { + struct { + ushort DeviceNo; /* number of cache drive */ + ulong BlockNo; /* block number */ + ulong BlockCnt; /* block count */ + ulong DestAddr; /* dest. addr. (if s/g: -1) */ + ulong sg_canz; /* s/g element count */ + gdth_sg_str sg_lst[GDTH_MAXSG]; /* s/g list */ + } cache; /* cache service cmd. str. */ + struct { + ushort param_size; /* size of p_param buffer */ + ulong subfunc; /* IOCTL function */ + ulong channel; /* device */ + ulong p_param; /* buffer */ + } ioctl; /* IOCTL command structure */ + struct { + ushort reserved; + ulong msg_handle; /* message handle */ + ulong msg_addr; /* message buffer address */ + } screen; /* screen service cmd. str. */ + struct { + ushort reserved; + ulong direction; /* data direction */ + ulong mdisc_time; /* disc. time (0: no timeout)*/ + ulong mcon_time; /* connect time(0: no to.) */ + ulong sdata; /* dest. addr. (if s/g: -1) */ + ulong sdlen; /* data length (bytes) */ + ulong clen; /* SCSI cmd. length(6,10,12) */ + unchar cmd[12]; /* SCSI command */ + unchar target; /* target ID */ + unchar lun; /* LUN */ + unchar bus; /* SCSI bus number */ + unchar priority; /* only 0 used */ + ulong sense_len; /* sense data length */ + ulong sense_data; /* sense data addr. */ + struct raw *link_p; /* linked cmds (not supp.) */ + ulong sg_ranz; /* s/g element count */ + gdth_sg_str sg_lst[GDTH_MAXSG]; /* s/g list */ + } raw; /* raw service cmd. struct. */ + } u; + /* additional variables */ + unchar Service; /* controller service */ + ushort Status; /* command result */ + ulong Info; /* additional information */ + Scsi_Cmnd *RequestBuffer; /* request buffer */ +} gdth_cmd_str; + +/* controller event structure */ +#define ES_ASYNC 1 +#define ES_DRIVER 2 +#define ES_TEST 3 +#define ES_SYNC 4 +typedef struct { + ushort size; /* size of structure */ + union { + char stream[16]; + struct { + ushort ionode; + ushort service; + ulong index; + } driver; + struct { + ushort ionode; + ushort service; + ushort status; + ulong info; + unchar scsi_coord[3]; + } async; + struct { + ushort ionode; + ushort service; + ushort status; + ulong info; + ushort hostdrive; + unchar scsi_coord[3]; + unchar sense_key; + } sync; + struct { + ulong l1, l2, l3, l4; + } test; + } eu; +} gdth_evt_data; + +typedef struct { + ulong first_stamp; + ulong last_stamp; + ushort same_count; + ushort event_source; + ushort event_idx; + unchar application; + unchar reserved; + gdth_evt_data event_data; +} gdth_evt_str; + + +/* DPRAM structures */ + +/* interface area ISA/PCI */ +typedef struct { + unchar S_Cmd_Indx; /* special command */ + unchar volatile S_Status; /* status special command */ + ushort reserved1; + ulong S_Info[4]; /* add. info special command */ + unchar volatile Sema0; /* command semaphore */ + unchar reserved2[3]; + unchar Cmd_Index; /* command number */ + unchar reserved3[3]; + ushort volatile Status; /* command status */ + ushort Service; /* service(for async.events) */ + ulong Info[2]; /* additional info */ + struct { + ushort offset; /* command offs. in the DPRAM*/ + ushort serv_id; /* service */ + } comm_queue[MAXOFFSETS]; /* command queue */ + ulong bios_reserved[2]; + unchar gdt_dpr_cmd[1]; /* commands */ +} gdt_dpr_if; + +/* SRAM structure PCI controllers */ +typedef struct { + ulong magic; /* controller ID from BIOS */ + ushort need_deinit; /* switch betw. BIOS/driver */ + unchar switch_support; /* see need_deinit */ + unchar padding[9]; + unchar os_used[16]; /* OS code per service */ + unchar unused[28]; + unchar fw_magic; /* contr. ID from firmware */ +} gdt_pci_sram; + +/* SRAM structure EISA controllers (but NOT GDT3000/3020) */ +typedef struct { + unchar os_used[16]; /* OS code per service */ + ushort need_deinit; /* switch betw. BIOS/driver */ + unchar switch_support; /* see need_deinit */ + unchar padding; +} gdt_eisa_sram; + + +/* DPRAM ISA controllers */ +typedef struct { + union { + struct { + unchar bios_used[0x3c00-32]; /* 15KB - 32Bytes BIOS */ + ulong magic; /* controller (EISA) ID */ + ushort need_deinit; /* switch betw. BIOS/driver */ + unchar switch_support; /* see need_deinit */ + unchar padding[9]; + unchar os_used[16]; /* OS code per service */ + } dp_sram; + unchar bios_area[0x4000]; /* 16KB reserved for BIOS */ + } bu; + union { + gdt_dpr_if ic; /* interface area */ + unchar if_area[0x3000]; /* 12KB for interface */ + } u; + struct { + unchar memlock; /* write protection DPRAM */ + unchar event; /* release event */ + unchar irqen; /* board interrupts enable */ + unchar irqdel; /* acknowledge board int. */ + unchar volatile Sema1; /* status semaphore */ + unchar rq; /* IRQ/DRQ configuration */ + } io; +} gdt2_dpram_str; + +/* DPRAM PCI controllers */ +typedef struct { + union { + gdt_dpr_if ic; /* interface area */ + unchar if_area[0xff0-sizeof(gdt_pci_sram)]; + } u; + gdt_pci_sram gdt6sr; /* SRAM structure */ + struct { + unchar unused0[1]; + unchar volatile Sema1; /* command semaphore */ + unchar unused1[3]; + unchar irqen; /* board interrupts enable */ + unchar unused2[2]; + unchar event; /* release event */ + unchar unused3[3]; + unchar irqdel; /* acknowledge board int. */ + unchar unused4[3]; + } io; +} gdt6_dpram_str; + +/* PLX register structure (new PCI controllers) */ +typedef struct { + unchar cfg_reg; /* DPRAM cfg.(2:below 1MB,0:anywhere)*/ + unchar unused1[0x3f]; + unchar volatile sema0_reg; /* command semaphore */ + unchar volatile sema1_reg; /* status semaphore */ + unchar unused2[2]; + ushort volatile status; /* command status */ + ushort service; /* service */ + ulong info[2]; /* additional info */ + unchar unused3[0x10]; + unchar ldoor_reg; /* PCI to local doorbell */ + unchar unused4[3]; + unchar volatile edoor_reg; /* local to PCI doorbell */ + unchar unused5[3]; + unchar control0; /* control0 register(unused) */ + unchar control1; /* board interrupts enable */ + unchar unused6[0x16]; +} gdt6c_plx_regs; + +/* DPRAM new PCI controllers */ +typedef struct { + union { + gdt_dpr_if ic; /* interface area */ + unchar if_area[0x4000-sizeof(gdt_pci_sram)]; + } u; + gdt_pci_sram gdt6sr; /* SRAM structure */ +} gdt6c_dpram_str; + +/* i960 register structure (PCI MPR controllers) */ +typedef struct { + unchar unused1[16]; + unchar volatile sema0_reg; /* command semaphore */ + unchar unused2; + unchar volatile sema1_reg; /* status semaphore */ + unchar unused3; + ushort volatile status; /* command status */ + ushort service; /* service */ + ulong info[2]; /* additional info */ + unchar ldoor_reg; /* PCI to local doorbell */ + unchar unused4[11]; + unchar volatile edoor_reg; /* local to PCI doorbell */ + unchar unused5[7]; + unchar edoor_en_reg; /* board interrupts enable */ + unchar unused6[27]; + ulong unused7[1004]; /* size: 4 KB */ +} gdt6m_i960_regs; + +/* DPRAM PCI MPR controllers */ +typedef struct { + gdt6m_i960_regs i960r; /* 4KB i960 registers */ + union { + gdt_dpr_if ic; /* interface area */ + unchar if_area[0x3000-sizeof(gdt_pci_sram)]; + } u; + gdt_pci_sram gdt6sr; /* SRAM structure */ +} gdt6m_dpram_str; + + +/* PCI resources */ +typedef struct { + ushort device_id; /* device ID (0,..,9) */ + unchar bus; /* PCI bus */ + unchar device_fn; /* PCI device/function no. */ + ulong dpmem; /* DPRAM address */ + ulong io; /* IO address */ + ulong io_mm; /* IO address mem. mapped */ + ulong bios; /* BIOS address */ + unchar irq; /* IRQ */ +} gdth_pci_str; + + +/* controller information structure */ +typedef struct { + unchar bus_cnt; /* SCSI bus count */ + unchar type; /* controller class */ + ushort raw_feat; /* feat. raw service (s/g,..) */ + ulong stype; /* controller subtype */ + ushort cache_feat; /* feat. cache serv. (s/g,..) */ + ushort bmic; /* BMIC address (EISA) */ + void *brd; /* DPRAM address */ + ulong brd_phys; /* slot number/BIOS address */ + gdt6c_plx_regs *plx; /* PLX regs (new PCI contr.) */ + gdth_cmd_str *pccb; /* address command structure */ + gdth_scratch_str *pscratch; + unchar irq; /* IRQ */ + unchar drq; /* DRQ (ISA controllers) */ + ushort status; /* command status */ + ulong info; + ulong info2; /* additional info */ + Scsi_Cmnd *req_first; /* top of request queue */ + struct { + unchar type; /* device type */ + unchar heads; /* mapping */ + unchar secs; + unchar lock; /* drive locked ? (hot plug) */ + ushort hostdrive; /* host drive number */ + ushort devtype; /* further information */ + ulong size; /* capacity */ + } id[MAXBUS][MAXID]; + ushort cmd_cnt; /* command count in DPRAM */ + ushort cmd_len; /* length of actual command */ + ushort cmd_offs_dpmem; /* actual offset in DPRAM */ + ushort ic_all_size; /* sizeof DPRAM interf. area */ + unchar reserved; + unchar mode; /* information from /proc */ + ushort param_size; + gdth_cpar_str cpar; /* controller cache par. */ + char ctr_name[16]; /* controller name */ +} gdth_ha_str; + +/* structure for scsi_register(), SCSI bus != 0 */ +typedef struct { + ushort hanum; + ushort busnum; +} gdth_num_str; + +/* structure for scsi_register() */ +typedef struct { + gdth_num_str numext; /* must be the first element */ + gdth_ha_str haext; + gdth_cmd_str cmdext; + gdth_scratch_str dmaext; +} gdth_ext_str; + + +/* INQUIRY data format */ +typedef struct { + unchar type_qual; + unchar modif_rmb; + unchar version; + unchar resp_aenc; + unchar add_length; + unchar reserved1; + unchar reserved2; + unchar misc; + unchar vendor[8]; + unchar product[16]; + unchar revision[4]; +} gdth_inq_data; + +/* READ_CAPACITY data format */ +typedef struct { + ulong last_block_no; + ulong block_length; +} gdth_rdcap_data; + +/* REQUEST_SENSE data format */ +typedef struct { + unchar errorcode; + unchar segno; + unchar key; + ulong info; + unchar add_length; + ulong cmd_info; + unchar adsc; + unchar adsq; + unchar fruc; + unchar key_spec[3]; +} gdth_sense_data; + +/* MODE_SENSE data format */ +typedef struct { + struct { + unchar data_length; + unchar med_type; + unchar dev_par; + unchar bd_length; + } hd; + struct { + unchar dens_code; + unchar block_count[3]; + unchar reserved; + unchar block_length[3]; + } bd; +} gdth_modep_data; + +/* stack frame */ +typedef struct { + ulong b[10]; /* 32 bit compiler ! */ +} gdth_stackframe; + +#pragma pack() + + +/* data structure for reserve drives */ +typedef struct { + unchar hanum; + unchar bus; + unchar id; +} gdth_reserve_str; + + +/* function prototyping */ + +int gdth_detect(Scsi_Host_Template *); +int gdth_release(struct Scsi_Host *); +int gdth_command(Scsi_Cmnd *); +int gdth_queuecommand(Scsi_Cmnd *,void (*done)(Scsi_Cmnd *)); +int gdth_abort(Scsi_Cmnd *); +#if LINUX_VERSION_CODE >= 0x010346 +int gdth_reset(Scsi_Cmnd *, unsigned int reset_flags); +#else +int gdth_reset(Scsi_Cmnd *); +#endif +const char *gdth_info(struct Scsi_Host *); + +#if LINUX_VERSION_CODE >= 0x02015F +int gdth_bios_param(Disk *,kdev_t,int *); +extern struct proc_dir_entry proc_scsi_gdth; +int gdth_proc_info(char *,char **,off_t,int,int,int); +int gdth_eh_abort(Scsi_Cmnd *scp); +int gdth_eh_device_reset(Scsi_Cmnd *scp); +int gdth_eh_bus_reset(Scsi_Cmnd *scp); +int gdth_eh_host_reset(Scsi_Cmnd *scp); +#define GDTH { proc_dir: &proc_scsi_gdth, \ + proc_info: gdth_proc_info, \ + name: "GDT SCSI Disk Array Controller",\ + detect: gdth_detect, \ + release: gdth_release, \ + info: gdth_info, \ + command: gdth_command, \ + queuecommand: gdth_queuecommand, \ + eh_abort_handler: gdth_eh_abort, \ + eh_device_reset_handler: gdth_eh_device_reset, \ + eh_bus_reset_handler: gdth_eh_bus_reset, \ + eh_host_reset_handler: gdth_eh_host_reset, \ + abort: gdth_abort, \ + reset: gdth_reset, \ + bios_param: gdth_bios_param, \ + can_queue: GDTH_MAXCMDS, \ + this_id: -1, \ + sg_tablesize: GDTH_MAXSG, \ + cmd_per_lun: GDTH_MAXC_P_L, \ + present: 0, \ + unchecked_isa_dma: 1, \ + use_clustering: ENABLE_CLUSTERING, \ + use_new_eh_code: 1 /* use new error code */ } +#elif LINUX_VERSION_CODE >= 0x010300 +int gdth_bios_param(Disk *,kdev_t,int *); +extern struct proc_dir_entry proc_scsi_gdth; +int gdth_proc_info(char *,char **,off_t,int,int,int); +#define GDTH { NULL, NULL, \ + &proc_scsi_gdth, \ + gdth_proc_info, \ + "GDT SCSI Disk Array Controller", \ + gdth_detect, \ + gdth_release, \ + gdth_info, \ + gdth_command, \ + gdth_queuecommand, \ + gdth_abort, \ + gdth_reset, \ + NULL, \ + gdth_bios_param, \ + GDTH_MAXCMDS, \ + -1, \ + GDTH_MAXSG, \ + GDTH_MAXC_P_L, \ + 0, \ + 1, \ + ENABLE_CLUSTERING} +#else +int gdth_bios_param(Disk *,int,int *); +#define GDTH { NULL, NULL, \ + "GDT SCSI Disk Array Controller", \ + gdth_detect, \ + gdth_release, \ + gdth_info, \ + gdth_command, \ + gdth_queuecommand, \ + gdth_abort, \ + gdth_reset, \ + NULL, \ + gdth_bios_param, \ + GDTH_MAXCMDS, \ + -1, \ + GDTH_MAXSG, \ + GDTH_MAXC_P_L, \ + 0, \ + 1, \ + ENABLE_CLUSTERING} +#endif + +#endif + diff --git a/linux/src/drivers/scsi/gdth_ioctl.h b/linux/src/drivers/scsi/gdth_ioctl.h new file mode 100644 index 00000000..bf155542 --- /dev/null +++ b/linux/src/drivers/scsi/gdth_ioctl.h @@ -0,0 +1,86 @@ +#ifndef _GDTH_IOCTL_H +#define _GDTH_IOCTL_H + +/* gdth_ioctl.h + * $Id: gdth_ioctl.h,v 1.1 1999/04/26 05:54:37 tb Exp $ + */ + +/* IOCTLs */ +#define GDTIOCTL_MASK ('J'<<8) +#define GDTIOCTL_GENERAL (GDTIOCTL_MASK | 0) /* general IOCTL */ +#define GDTIOCTL_DRVERS (GDTIOCTL_MASK | 1) /* get driver version */ +#define GDTIOCTL_CTRTYPE (GDTIOCTL_MASK | 2) /* get controller type */ +#define GDTIOCTL_OSVERS (GDTIOCTL_MASK | 3) /* get OS version */ +#define GDTIOCTL_CTRCNT (GDTIOCTL_MASK | 5) /* get controller count */ +#define GDTIOCTL_LOCKDRV (GDTIOCTL_MASK | 6) /* lock host drive */ +#define GDTIOCTL_LOCKCHN (GDTIOCTL_MASK | 7) /* lock channel */ +#define GDTIOCTL_EVENT (GDTIOCTL_MASK | 8) /* read controller events */ + +#define GDTIOCTL_MAGIC 0x06030f07UL + + +/* IOCTL structure (write) */ +typedef struct { + ulong magic; /* IOCTL magic */ + ushort ioctl; /* IOCTL */ + ushort ionode; /* controller number */ + ushort service; /* controller service */ + ushort timeout; /* timeout */ + union { + struct { + unchar command[512]; /* controller command */ + unchar data[1]; /* add. data */ + } general; + struct { + unchar lock; /* lock/unlock */ + unchar drive_cnt; /* drive count */ + ushort drives[35]; /* drives */ + } lockdrv; + struct { + unchar lock; /* lock/unlock */ + unchar channel; /* channel */ + } lockchn; + struct { + int erase; /* erase event ? */ + int handle; + } event; + } iu; +} gdth_iowr_str; + +/* IOCTL structure (read) */ +typedef struct { + ulong size; /* buffer size */ + ulong status; /* IOCTL error code */ + union { + struct { + unchar data[1]; /* data */ + } general; + struct { + ushort version; /* driver version */ + } drvers; + struct { + unchar type; /* controller type */ + ushort info; /* slot etc. */ + ushort oem_id; /* OEM ID */ + ushort bios_ver; /* not used */ + ushort access; /* not used */ + ushort ext_type; /* extended type */ + } ctrtype; + struct { + unchar version; /* OS version */ + unchar subversion; /* OS subversion */ + ushort revision; /* revision */ + } osvers; + struct { + ushort count; /* controller count */ + } ctrcnt; + struct { + int handle; + unchar evt[32]; /* event structure */ + } event; + } iu; +} gdth_iord_str; + + +#endif + diff --git a/linux/src/drivers/scsi/gdth_proc.c b/linux/src/drivers/scsi/gdth_proc.c new file mode 100644 index 00000000..8764d558 --- /dev/null +++ b/linux/src/drivers/scsi/gdth_proc.c @@ -0,0 +1,656 @@ +/* gdth_proc.c + * $Id: gdth_proc.c,v 1.1 1999/04/26 05:54:38 tb Exp $ + */ + +#include "gdth_ioctl.h" + +int gdth_proc_info(char *buffer,char **start,off_t offset,int length, + int hostno,int inout) +{ + int hanum,busnum,i; + + TRACE2(("gdth_proc_info() length %d ha %d offs %d inout %d\n", + length,hostno,(int)offset,inout)); + + for (i=0; i<gdth_ctr_vcount; ++i) { + if (gdth_ctr_vtab[i]->host_no == hostno) + break; + } + if (i==gdth_ctr_vcount) + return(-EINVAL); + + hanum = NUMDATA(gdth_ctr_vtab[i])->hanum; + busnum= NUMDATA(gdth_ctr_vtab[i])->busnum; + + if (inout) + return(gdth_set_info(buffer,length,i,hanum,busnum)); + else + return(gdth_get_info(buffer,start,offset,length,i,hanum,busnum)); +} + +static int gdth_set_info(char *buffer,int length,int vh,int hanum,int busnum) +{ + int ret_val; + Scsi_Cmnd scp; + Scsi_Device sdev; + gdth_iowr_str *piowr; + + TRACE2(("gdth_set_info() ha %d bus %d\n",hanum,busnum)); + piowr = (gdth_iowr_str *)buffer; + + memset(&sdev,0,sizeof(Scsi_Device)); + memset(&scp, 0,sizeof(Scsi_Cmnd)); + sdev.host = gdth_ctr_vtab[vh]; + sdev.id = sdev.host->this_id; + scp.cmd_len = 12; + scp.host = gdth_ctr_vtab[vh]; + scp.target = sdev.host->this_id; + scp.device = &sdev; + scp.use_sg = 0; + + if (length >= 4) { + if (strncmp(buffer,"gdth",4) == 0) { + buffer += 5; + length -= 5; + ret_val = gdth_set_asc_info( buffer, length, hanum, scp ); + } else if (piowr->magic == GDTIOCTL_MAGIC) { + ret_val = gdth_set_bin_info( buffer, length, hanum, scp ); + } else { + printk("GDT: Wrong signature: %6s\n",buffer); + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + return ret_val; +} + +static int gdth_set_asc_info(char *buffer,int length,int hanum,Scsi_Cmnd scp) +{ + int orig_length, drive, wb_mode; + char cmnd[12]; + int i, j, found; + gdth_ha_str *ha; + gdth_cmd_str gdtcmd; + gdth_cpar_str *pcpar; + + TRACE2(("gdth_set_asc_info() ha %d\n",hanum)); + ha = HADATA(gdth_ctr_tab[hanum]); + memset(cmnd, 0,10); + orig_length = length + 5; + drive = -1; + wb_mode = 0; + found = FALSE; + + if (length >= 5 && strncmp(buffer,"flush",5)==0) { + buffer += 6; + length -= 6; + if (length && *buffer>='0' && *buffer<='9') { + drive = (int)(*buffer-'0'); + ++buffer; --length; + if (length && *buffer>='0' && *buffer<='9') { + drive = drive*10 + (int)(*buffer-'0'); + ++buffer; --length; + } + printk("GDT: Flushing host drive %d .. ",drive); + } else { + printk("GDT: Flushing all host drives .. "); + } + for (i = 0; i < MAXBUS; ++i) { + for (j = 0; j < MAXID; ++j) { + if (ha->id[i][j].type == CACHE_DTYP) { + if (drive != -1 && + ha->id[i][j].hostdrive != (ushort)drive) + continue; + found = TRUE; + gdtcmd.BoardNode = LOCALBOARD; + gdtcmd.Service = CACHESERVICE; + gdtcmd.OpCode = GDT_FLUSH; + gdtcmd.u.cache.DeviceNo = ha->id[i][j].hostdrive; + gdtcmd.u.cache.BlockNo = 1; + gdtcmd.u.cache.sg_canz = 0; + { + struct semaphore sem = MUTEX_LOCKED; + scp.request.rq_status = RQ_SCSI_BUSY; + scp.request.sem = &sem; + scp.SCp.this_residual = IOCTL_PRI; + scsi_do_cmd(&scp, cmnd, &gdtcmd, + sizeof(gdth_cmd_str), gdth_scsi_done, + 30*HZ, 1); + down(&sem); + } + } + } + } + if (!found) + printk("\nNo host drive found !\n"); + else + printk("Done.\n"); + return(orig_length); + } + + if (length >= 7 && strncmp(buffer,"wbp_off",7)==0) { + buffer += 8; + length -= 8; + printk("GDT: Disabling write back permanently .. "); + wb_mode = 1; + } else if (length >= 6 && strncmp(buffer,"wbp_on",6)==0) { + buffer += 7; + length -= 7; + printk("GDT: Enabling write back permanently .. "); + wb_mode = 2; + } else if (length >= 6 && strncmp(buffer,"wb_off",6)==0) { + buffer += 7; + length -= 7; + printk("GDT: Disabling write back commands .. "); + if (ha->cache_feat & GDT_WR_THROUGH) { + gdth_write_through = TRUE; + printk("Done.\n"); + } else { + printk("Not supported !\n"); + } + return(orig_length); + } else if (length >= 5 && strncmp(buffer,"wb_on",5)==0) { + buffer += 6; + length -= 6; + printk("GDT: Enabling write back commands .. "); + gdth_write_through = FALSE; + printk("Done.\n"); + return(orig_length); + } + + if (wb_mode) { + pcpar = (gdth_cpar_str *)kmalloc( sizeof(gdth_cpar_str), + GFP_ATOMIC | GFP_DMA ); + if (pcpar == NULL) { + TRACE2(("gdth_set_info(): Unable to allocate memory.\n")); + printk("Unable to allocate memory.\n"); + return(-EINVAL); + } + memcpy( pcpar, &ha->cpar, sizeof(gdth_cpar_str) ); + gdtcmd.BoardNode = LOCALBOARD; + gdtcmd.Service = CACHESERVICE; + gdtcmd.OpCode = GDT_IOCTL; + gdtcmd.u.ioctl.p_param = virt_to_bus(pcpar); + gdtcmd.u.ioctl.param_size = sizeof(gdth_cpar_str); + gdtcmd.u.ioctl.subfunc = CACHE_CONFIG; + gdtcmd.u.ioctl.channel = INVALID_CHANNEL; + pcpar->write_back = wb_mode==1 ? 0:1; + { + struct semaphore sem = MUTEX_LOCKED; + scp.request.rq_status = RQ_SCSI_BUSY; + scp.request.sem = &sem; + scp.SCp.this_residual = IOCTL_PRI; + scsi_do_cmd(&scp, cmnd, &gdtcmd, sizeof(gdth_cmd_str), + gdth_scsi_done, 30*HZ, 1); + down(&sem); + } + kfree( pcpar ); + printk("Done.\n"); + return(orig_length); + } + + printk("GDT: Unknown command: %s Length: %d\n",buffer,length); + return(-EINVAL); +} + +static int gdth_set_bin_info(char *buffer,int length,int hanum,Scsi_Cmnd scp) +{ + char cmnd[12]; + int id; + unchar i, j, k, found; + gdth_ha_str *ha; + gdth_iowr_str *piowr; + gdth_iord_str *piord; + gdth_cmd_str *pcmd; + ulong *ppadd; + ulong add_size, flags; + + + TRACE2(("gdth_set_bin_info() ha %d\n",hanum)); + ha = HADATA(gdth_ctr_tab[hanum]); + memset(cmnd, 0,10); + piowr = (gdth_iowr_str *)buffer; + piord = NULL; + pcmd = NULL; + + if (length < GDTOFFSOF(gdth_iowr_str,iu)) + return(-EINVAL); + + switch (piowr->ioctl) { + case GDTIOCTL_GENERAL: + if (length < GDTOFFSOF(gdth_iowr_str,iu.general.data[0])) + return(-EINVAL); + pcmd = (gdth_cmd_str *)piowr->iu.general.command; + pcmd->Service = piowr->service; + if (pcmd->OpCode == GDT_IOCTL) { + ppadd = &pcmd->u.ioctl.p_param; + add_size = pcmd->u.ioctl.param_size; + } else if (piowr->service == CACHESERVICE) { + add_size = pcmd->u.cache.BlockCnt * SECTOR_SIZE; + if (ha->cache_feat & SCATTER_GATHER) { + ppadd = &pcmd->u.cache.sg_lst[0].sg_ptr; + pcmd->u.cache.DestAddr = -1UL; + pcmd->u.cache.sg_lst[0].sg_len = add_size; + pcmd->u.cache.sg_canz = 1; + } else { + ppadd = &pcmd->u.cache.DestAddr; + pcmd->u.cache.sg_canz = 0; + } + } else if (piowr->service == SCSIRAWSERVICE) { + add_size = pcmd->u.raw.sdlen; + if (ha->raw_feat & SCATTER_GATHER) { + ppadd = &pcmd->u.raw.sg_lst[0].sg_ptr; + pcmd->u.raw.sdata = -1UL; + pcmd->u.raw.sg_lst[0].sg_len = add_size; + pcmd->u.raw.sg_ranz = 1; + } else { + ppadd = &pcmd->u.raw.sdata; + pcmd->u.raw.sg_ranz = 0; + } + } else { + return(-EINVAL); + } + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) + add_size ); + if (id == -1) + return(-EBUSY); + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + + piord->size = sizeof(gdth_iord_str) + add_size; + if (add_size > 0) { + memcpy(piord->iu.general.data, piowr->iu.general.data, add_size); + *ppadd = virt_to_bus(piord->iu.general.data); + } + /* do IOCTL */ + { + struct semaphore sem = MUTEX_LOCKED; + scp.request.rq_status = RQ_SCSI_BUSY; + scp.request.sem = &sem; + scp.SCp.this_residual = IOCTL_PRI; + scsi_do_cmd(&scp, cmnd, pcmd, + sizeof(gdth_cmd_str), gdth_scsi_done, + piowr->timeout*HZ, 1); + down(&sem); + piord->status = (ulong)scp.SCp.Message; + } + break; + + case GDTIOCTL_DRVERS: + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) ); + if (id == -1) + return(-EBUSY); + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + piord->size = sizeof(gdth_iord_str); + piord->status = S_OK; + piord->iu.drvers.version = (GDTH_VERSION<<8) | GDTH_SUBVERSION; + break; + + case GDTIOCTL_CTRTYPE: + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) ); + if (id == -1) + return(-EBUSY); + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + piord->size = sizeof(gdth_iord_str); + piord->status = S_OK; + if (ha->type == GDT_ISA || ha->type == GDT_EISA) { + piord->iu.ctrtype.type = (unchar)((ha->stype>>20) - 0x10); + } else if (ha->type != GDT_PCIMPR) { + piord->iu.ctrtype.type = (unchar)((ha->stype<<8) + 6); + } else { + piord->iu.ctrtype.type = 0xfe; + piord->iu.ctrtype.ext_type = 0x6000 | ha->stype; + } + piord->iu.ctrtype.info = ha->brd_phys; + piord->iu.ctrtype.oem_id = (ushort)GDT3_ID; + break; + + case GDTIOCTL_CTRCNT: + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) ); + if (id == -1) + return(-EBUSY); + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + piord->size = sizeof(gdth_iord_str); + piord->status = S_OK; + piord->iu.ctrcnt.count = (ushort)gdth_ctr_count; + break; + + case GDTIOCTL_OSVERS: + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) ); + if (id == -1) + return(-EBUSY); + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + piord->size = sizeof(gdth_iord_str); + piord->status = S_OK; + piord->iu.osvers.version = (unchar)(LINUX_VERSION_CODE >> 16); + piord->iu.osvers.subversion = (unchar)(LINUX_VERSION_CODE >> 8); + piord->iu.osvers.revision = (ushort)(LINUX_VERSION_CODE & 0xff); + break; + + case GDTIOCTL_LOCKDRV: + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) ); + if (id == -1) + return(-EBUSY); + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + for (i = k = 0; i < piowr->iu.lockdrv.drive_cnt; ++i) { + found = FALSE; + for (j = 0; j < ha->bus_cnt; ++j) { + for (k = 0; k < MAXID; ++k) { + if (ha->id[j][k].type == CACHE_DTYP && + ha->id[j][k].hostdrive == piowr->iu.lockdrv.drives[i]) { + found = TRUE; + break; + } + } + if (found) + break; + } + if (!found) + continue; + + if (piowr->iu.lockdrv.lock) { + save_flags( flags ); + cli(); + ha->id[j][k].lock = 1; + restore_flags( flags ); + gdth_wait_completion( hanum, j, k ); + gdth_stop_timeout( hanum, j, k ); + } else { + save_flags( flags ); + cli(); + ha->id[j][k].lock = 0; + restore_flags( flags ); + gdth_start_timeout( hanum, j, k ); + gdth_next( hanum ); + } + } + piord->size = sizeof(gdth_iord_str); + piord->status = S_OK; + break; + + case GDTIOCTL_LOCKCHN: + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) ); + if (id == -1) + return(-EBUSY); + for (k = 0, j = piowr->iu.lockchn.channel; k < MAXID; ++k) { + if (ha->id[j][k].type != RAW_DTYP) + continue; + + if (piowr->iu.lockchn.lock) { + save_flags( flags ); + cli(); + ha->id[j][k].lock = 1; + restore_flags( flags ); + gdth_wait_completion( hanum, j, k ); + gdth_stop_timeout( hanum, j, k ); + } else { + save_flags( flags ); + cli(); + ha->id[j][k].lock = 0; + restore_flags( flags ); + gdth_start_timeout( hanum, j, k ); + gdth_next( hanum ); + } + } + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + piord->size = sizeof(gdth_iord_str); + piord->status = S_OK; + break; + + case GDTIOCTL_EVENT: + id = gdth_ioctl_alloc( hanum, sizeof(gdth_iord_str) ); + if (id == -1) + return(-EBUSY); + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + if (piowr->iu.event.erase == 0) { + piord->iu.event.handle = gdth_read_event( piowr->iu.event.handle, + (gdth_evt_str *)piord->iu.event.evt ); + } else { + piord->iu.event.handle = piowr->iu.event.handle; + gdth_readapp_event( (unchar)piowr->iu.event.erase, + (gdth_evt_str *)piord->iu.event.evt ); + } + piord->size = sizeof(gdth_iord_str); + piord->status = S_OK; + break; + + default: + return(-EINVAL); + } + /* we return a buffer ID to detect the right buffer during READ-IOCTL */ + return id; +} + +static int gdth_get_info(char *buffer,char **start,off_t offset, + int length,int vh,int hanum,int busnum) +{ + int size = 0,len = 0; + off_t begin = 0,pos = 0; + gdth_ha_str *ha; + gdth_iord_str *piord; + int id; + + TRACE2(("gdth_get_info() ha %d bus %d\n",hanum,busnum)); + ha = HADATA(gdth_ctr_tab[hanum]); + id = length; + + /* look for buffer ID in length */ + if (id > 4) { +#if LINUX_VERSION_CODE >= 0x020000 + size = sprintf(buffer+len, + "%s SCSI Disk Array Controller\n", + ha->ctr_name); +#else + size = sprintf(buffer+len, + "%s SCSI Disk Array Controller (SCSI Bus %d)\n", + ha->ctr_name,busnum); +#endif + len += size; pos = begin + len; + size = sprintf(buffer+len, + "Firmware Version: %d.%2d\tDriver Version: %s\n", + (unchar)(ha->cpar.version>>8), + (unchar)(ha->cpar.version),GDTH_VERSION_STR); + len += size; pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + + } else { + piord = (gdth_iord_str *)gdth_ioctl_tab[id-1][hanum]; + if (piord == NULL) + goto stop_output; + length = piord->size; + memcpy(buffer+len, (char *)piord, length); + gdth_ioctl_free(hanum, id); + len += length; pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + } + +stop_output: + *start = buffer +(offset-begin); + len -= (offset-begin); + if (len > length) + len = length; + TRACE2(("get_info() len %d pos %d begin %d offset %d length %d size %d\n", + len,(int)pos,(int)begin,(int)offset,length,size)); + return(len); +} + + +void gdth_scsi_done(Scsi_Cmnd *scp) +{ + TRACE2(("gdth_scsi_done()\n")); + + scp->request.rq_status = RQ_SCSI_DONE; + + if (scp->request.sem != NULL) + up(scp->request.sem); +} + +static int gdth_ioctl_alloc(int hanum, ushort size) +{ + ulong flags; + int i; + + if (size == 0) + return -1; + + save_flags(flags); + cli(); + + for (i = 0; i < 4; ++i) { + if (gdth_ioctl_tab[i][hanum] == NULL) { + gdth_ioctl_tab[i][hanum] = kmalloc( size, GFP_ATOMIC | GFP_DMA ); + break; + } + } + + restore_flags(flags); + if (i == 4 || gdth_ioctl_tab[i][hanum] == NULL) + return -1; + return (i+1); +} + +static void gdth_ioctl_free(int hanum, int idx) +{ + ulong flags; + + save_flags(flags); + cli(); + + kfree( gdth_ioctl_tab[idx-1][hanum] ); + gdth_ioctl_tab[idx-1][hanum] = NULL; + + restore_flags(flags); +} + +static void gdth_wait_completion(int hanum, int busnum, int id) +{ + ulong flags; + int i; + Scsi_Cmnd *scp; + + save_flags(flags); + cli(); + + for (i = 0; i < GDTH_MAXCMDS; ++i) { + scp = gdth_cmd_tab[i][hanum].cmnd; +#if LINUX_VERSION_CODE >= 0x020000 + if (!SPECIAL_SCP(scp) && scp->target == (unchar)id && + scp->channel == (unchar)busnum) +#else + if (!SPECIAL_SCP(scp) && scp->target == (unchar)id && + NUMDATA(scp->host)->busnum == (unchar)busnum) +#endif + { + scp->SCp.have_data_in = 0; + restore_flags(flags); + while (!scp->SCp.have_data_in) + barrier(); + scp->scsi_done(scp); + save_flags(flags); + cli(); + } + } + restore_flags(flags); +} + +static void gdth_stop_timeout(int hanum, int busnum, int id) +{ + ulong flags; + Scsi_Cmnd *scp; + gdth_ha_str *ha; + + save_flags(flags); + cli(); + ha = HADATA(gdth_ctr_tab[hanum]); + + for (scp = ha->req_first; scp; scp = (Scsi_Cmnd *)scp->SCp.ptr) { +#if LINUX_VERSION_CODE >= 0x020000 + if (scp->target == (unchar)id && + scp->channel == (unchar)busnum) +#else + if (scp->target == (unchar)id && + NUMDATA(scp->host)->busnum == (unchar)busnum) +#endif + { + TRACE2(("gdth_stop_timeout(): update_timeout()\n")); + scp->SCp.buffers_residual = gdth_update_timeout(hanum, scp, 0); + } + } + restore_flags(flags); +} + +static void gdth_start_timeout(int hanum, int busnum, int id) +{ + ulong flags; + Scsi_Cmnd *scp; + gdth_ha_str *ha; + + save_flags(flags); + cli(); + ha = HADATA(gdth_ctr_tab[hanum]); + + for (scp = ha->req_first; scp; scp = (Scsi_Cmnd *)scp->SCp.ptr) { +#if LINUX_VERSION_CODE >= 0x020000 + if (scp->target == (unchar)id && + scp->channel == (unchar)busnum) +#else + if (scp->target == (unchar)id && + NUMDATA(scp->host)->busnum == (unchar)busnum) +#endif + { + TRACE2(("gdth_start_timeout(): update_timeout()\n")); + gdth_update_timeout(hanum, scp, scp->SCp.buffers_residual); + } + } + restore_flags(flags); +} + +static int gdth_update_timeout(int hanum, Scsi_Cmnd *scp, int timeout) +{ + ulong flags; + int oldto; + + save_flags(flags); + cli(); + oldto = scp->timeout_per_command; + scp->timeout_per_command = timeout; + +#if LINUX_VERSION_CODE >= 0x02014B + if (timeout == 0) { + del_timer(&scp->eh_timeout); + scp->eh_timeout.data = (unsigned long) NULL; + scp->eh_timeout.expires = 0; + } else { + if (scp->eh_timeout.data != (unsigned long) NULL) + del_timer(&scp->eh_timeout); + scp->eh_timeout.data = (unsigned long) scp; + scp->eh_timeout.expires = jiffies + timeout; + add_timer(&scp->eh_timeout); + } +#else + if (timeout > 0) { + if (timer_table[SCSI_TIMER].expires == 0) { + timer_table[SCSI_TIMER].expires = jiffies + timeout; + timer_active |= 1 << SCSI_TIMER; + } else { + if (jiffies + timeout < timer_table[SCSI_TIMER].expires) + timer_table[SCSI_TIMER].expires = jiffies + timeout; + } + } +#endif + + restore_flags(flags); + return oldto; +} + diff --git a/linux/src/drivers/scsi/gdth_proc.h b/linux/src/drivers/scsi/gdth_proc.h new file mode 100644 index 00000000..708b077d --- /dev/null +++ b/linux/src/drivers/scsi/gdth_proc.h @@ -0,0 +1,24 @@ +#ifndef _GDTH_PROC_H +#define _GDTH_PROC_H + +/* gdth_proc.h + * $Id: gdth_proc.h,v 1.1 1999/04/26 05:54:39 tb Exp $ + */ + +static int gdth_set_info(char *buffer,int length,int vh,int hanum,int busnum); +static int gdth_set_asc_info(char *buffer,int length,int hanum,Scsi_Cmnd scp); +static int gdth_set_bin_info(char *buffer,int length,int hanum,Scsi_Cmnd scp); +static int gdth_get_info(char *buffer,char **start,off_t offset, + int length,int vh,int hanum,int busnum); + +static int gdth_ioctl_alloc(int hanum, ushort size); +static void gdth_ioctl_free(int hanum, int id); +static void gdth_wait_completion(int hanum, int busnum, int id); +static void gdth_stop_timeout(int hanum, int busnum, int id); +static void gdth_start_timeout(int hanum, int busnum, int id); +static int gdth_update_timeout(int hanum, Scsi_Cmnd *scp, int timeout); + +void gdth_scsi_done(Scsi_Cmnd *scp); + +#endif + diff --git a/linux/src/drivers/scsi/hosts.c b/linux/src/drivers/scsi/hosts.c new file mode 100644 index 00000000..07646041 --- /dev/null +++ b/linux/src/drivers/scsi/hosts.c @@ -0,0 +1,545 @@ +/* + * hosts.c Copyright (C) 1992 Drew Eckhardt + * Copyright (C) 1993, 1994, 1995 Eric Youngdale + * + * mid to lowlevel SCSI driver interface + * Initial versions: Drew Eckhardt + * Subsequent revisions: Eric Youngdale + * + * <drew@colorado.edu> + */ + + +/* + * This file contains the medium level SCSI + * host interface initialization, as well as the scsi_hosts array of SCSI + * hosts currently present in the system. + */ + +/* + * Don't import our own symbols, as this would severely mess up our + * symbol tables. + */ +#define _SCSI_SYMS_VER_ +#define __NO_VERSION__ +#include <linux/module.h> + +#include <linux/config.h> +#include <linux/blk.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/proc_fs.h> + +#include "scsi.h" + +#ifndef NULL +#define NULL 0L +#endif + +#define HOSTS_C + +#include "hosts.h" + +#ifdef CONFIG_A3000_SCSI +#include "a3000.h" +#endif + +#ifdef CONFIG_A2091_SCSI +#include "a2091.h" +#endif + +#ifdef CONFIG_GVP11_SCSI +#include "gvp11.h" +#endif + +#ifdef CONFIG_ATARI_SCSI +#include "atari_scsi.h" +#endif + +#ifdef CONFIG_SCSI_ADVANSYS +#include "advansys.h" +#endif + +#ifdef CONFIG_SCSI_AHA152X +#include "aha152x.h" +#endif + +#ifdef CONFIG_SCSI_AHA1542 +#include "aha1542.h" +#endif + +#ifdef CONFIG_SCSI_AHA1740 +#include "aha1740.h" +#endif + +#ifdef CONFIG_SCSI_AIC7XXX +#include "aic7xxx.h" +#endif + +#ifdef CONFIG_SCSI_BUSLOGIC +#include "BusLogic.h" +#endif + +#ifdef CONFIG_SCSI_EATA_DMA +#include "eata_dma.h" +#endif + +#ifdef CONFIG_SCSI_EATA_PIO +#include "eata_pio.h" +#endif + +#ifdef CONFIG_SCSI_U14_34F +#include "u14-34f.h" +#endif + +#ifdef CONFIG_SCSI_FUTURE_DOMAIN +#include "fdomain.h" +#endif + +#ifdef CONFIG_SCSI_GENERIC_NCR5380 +#include "g_NCR5380.h" +#endif + +#ifdef CONFIG_SCSI_IN2000 +#include "in2000.h" +#endif + +#ifdef CONFIG_SCSI_PAS16 +#include "pas16.h" +#endif + +#ifdef CONFIG_SCSI_QLOGIC_FAS +#include "qlogicfas.h" +#endif + +#ifdef CONFIG_SCSI_QLOGIC_ISP +#include "qlogicisp.h" +#endif + +#ifdef CONFIG_SCSI_SEAGATE +#include "seagate.h" +#endif + +#ifdef CONFIG_SCSI_T128 +#include "t128.h" +#endif + +#ifdef CONFIG_SCSI_DTC3280 +#include "dtc.h" +#endif + +#ifdef CONFIG_SCSI_NCR53C7xx +#include "53c7,8xx.h" +#endif + +#ifdef CONFIG_SCSI_NCR53C8XX +#include "ncr53c8xx.h" +#endif + +#ifdef CONFIG_SCSI_ULTRASTOR +#include "ultrastor.h" +#endif + +#ifdef CONFIG_SCSI_7000FASST +#include "wd7000.h" +#endif + +#ifdef CONFIG_SCSI_EATA +#include "eata.h" +#endif + +#ifdef CONFIG_SCSI_NCR53C406A +#include "NCR53c406a.h" +#endif + +#ifdef CONFIG_SCSI_DC390T +#include "dc390.h" +#endif + +#ifdef CONFIG_SCSI_AM53C974 +#include "AM53C974.h" +#endif + +#ifdef CONFIG_SCSI_MEGARAID +#include "megaraid.h" +#endif + +#ifdef CONFIG_SCSI_PPA +#include "ppa.h" +#endif + +#ifdef CONFIG_SCSI_SUNESP +#include "esp.h" +#endif + +#ifdef CONFIG_BLK_DEV_IDESCSI +#include "ide-scsi.h" +#endif + +#ifdef CONFIG_SCSI_GDTH +#include "gdth.h" +#endif + +#ifdef CONFIG_SCSI_DEBUG +#include "scsi_debug.h" +#endif + + +/* +static const char RCSid[] = "$Header: cvs/gnumach/linux/src/drivers/scsi/Attic/hosts.c,v 1.1 1999/04/26 05:54:40 tb Exp $"; +*/ + +/* + * The scsi host entries should be in the order you wish the + * cards to be detected. A driver may appear more than once IFF + * it can deal with being detected (and therefore initialized) + * with more than one simultaneous host number, can handle being + * reentrant, etc. + * + * They may appear in any order, as each SCSI host is told which host + * number it is during detection. + */ + +/* This is a placeholder for controllers that are not configured into + * the system - we do this to ensure that the controller numbering is + * always consistent, no matter how the kernel is configured. */ + +#define NO_CONTROLLER {NULL, NULL, NULL, NULL, NULL, NULL, NULL, \ + NULL, NULL, 0, 0, 0, 0, 0, 0} + +/* + * When figure is run, we don't want to link to any object code. Since + * the macro for each host will contain function pointers, we cannot + * use it and instead must use a "blank" that does no such + * idiocy. + */ + +Scsi_Host_Template * scsi_hosts = NULL; + +static Scsi_Host_Template builtin_scsi_hosts[] = +{ +#ifdef CONFIG_AMIGA +#ifdef CONFIG_A3000_SCSI + A3000_SCSI, +#endif +#ifdef CONFIG_A2091_SCSI + A2091_SCSI, +#endif +#ifdef CONFIG_GVP11_SCSI + GVP11_SCSI, +#endif +#endif + +#ifdef CONFIG_ATARI +#ifdef CONFIG_ATARI_SCSI + ATARI_SCSI, +#endif +#endif + +#ifdef CONFIG_SCSI_ADVANSYS + ADVANSYS, +#endif +/* BusLogic must come before aha1542.c */ +#ifdef CONFIG_SCSI_BUSLOGIC + BUSLOGIC, +#endif +#ifdef CONFIG_SCSI_U14_34F + ULTRASTOR_14_34F, +#endif +#ifdef CONFIG_SCSI_ULTRASTOR + ULTRASTOR_14F, +#endif +#ifdef CONFIG_SCSI_AHA152X + AHA152X, +#endif +#ifdef CONFIG_SCSI_AHA1542 + AHA1542, +#endif +#ifdef CONFIG_SCSI_AHA1740 + AHA1740, +#endif +#ifdef CONFIG_SCSI_AIC7XXX + AIC7XXX, +#endif +#ifdef CONFIG_SCSI_FUTURE_DOMAIN + FDOMAIN_16X0, +#endif +#ifdef CONFIG_SCSI_IN2000 + IN2000, +#endif +#ifdef CONFIG_SCSI_GENERIC_NCR5380 + GENERIC_NCR5380, +#endif +#ifdef CONFIG_SCSI_NCR53C406A /* 53C406A should come before QLOGIC */ + NCR53c406a, +#endif +#ifdef CONFIG_SCSI_QLOGIC_FAS + QLOGICFAS, +#endif +#ifdef CONFIG_SCSI_QLOGIC_ISP + QLOGICISP, +#endif +#ifdef CONFIG_SCSI_PAS16 + MV_PAS16, +#endif +#ifdef CONFIG_SCSI_SEAGATE + SEAGATE_ST0X, +#endif +#ifdef CONFIG_SCSI_T128 + TRANTOR_T128, +#endif +#ifdef CONFIG_SCSI_DTC3280 + DTC3x80, +#endif +#ifdef CONFIG_SCSI_DC390T + DC390_T, +#endif +#ifdef CONFIG_SCSI_NCR53C7xx + NCR53c7xx, +#endif +#ifdef CONFIG_SCSI_NCR53C8XX + NCR53C8XX, +#endif +#ifdef CONFIG_SCSI_EATA_DMA + EATA_DMA, +#endif +#ifdef CONFIG_SCSI_EATA_PIO + EATA_PIO, +#endif +#ifdef CONFIG_SCSI_7000FASST + WD7000, +#endif +#ifdef CONFIG_SCSI_EATA + EATA, +#endif +#ifdef CONFIG_SCSI_AM53C974 + AM53C974, +#endif +#ifdef CONFIG_SCSI_MEGARAID + MEGARAID, +#endif +#ifdef CONFIG_SCSI_PPA + PPA, +#endif +#ifdef CONFIG_SCSI_SUNESP + SCSI_SPARC_ESP, +#endif +#ifdef CONFIG_SCSI_GDTH + GDTH, +#endif +#ifdef CONFIG_BLK_DEV_IDESCSI + IDESCSI, +#endif +#ifdef CONFIG_SCSI_DEBUG + SCSI_DEBUG, +#endif +}; + +#define MAX_SCSI_HOSTS (sizeof(builtin_scsi_hosts) / sizeof(Scsi_Host_Template)) + + +/* + * Our semaphores and timeout counters, where size depends on + * MAX_SCSI_HOSTS here. + */ + +struct Scsi_Host * scsi_hostlist = NULL; +struct Scsi_Device_Template * scsi_devicelist = NULL; + +int max_scsi_hosts = 0; +int next_scsi_host = 0; + +void +scsi_unregister(struct Scsi_Host * sh){ + struct Scsi_Host * shpnt; + + if(scsi_hostlist == sh) + scsi_hostlist = sh->next; + else { + shpnt = scsi_hostlist; + while(shpnt->next != sh) shpnt = shpnt->next; + shpnt->next = shpnt->next->next; + } + + /* If we are removing the last host registered, it is safe to reuse + * its host number (this avoids "holes" at boot time) (DB) + * It is also safe to reuse those of numbers directly below which have + * been released earlier (to avoid some holes in numbering). + */ + if(sh->host_no == max_scsi_hosts - 1) { + while(--max_scsi_hosts >= next_scsi_host) { + shpnt = scsi_hostlist; + while(shpnt && shpnt->host_no != max_scsi_hosts - 1) + shpnt = shpnt->next; + if(shpnt) + break; + } + } + next_scsi_host--; + scsi_init_free((char *) sh, sizeof(struct Scsi_Host) + sh->extra_bytes); +} + +/* We call this when we come across a new host adapter. We only do this + * once we are 100% sure that we want to use this host adapter - it is a + * pain to reverse this, so we try to avoid it + */ + +struct Scsi_Host * scsi_register(Scsi_Host_Template * tpnt, int j){ + struct Scsi_Host * retval, *shpnt; + retval = (struct Scsi_Host *)scsi_init_malloc(sizeof(struct Scsi_Host) + j, + (tpnt->unchecked_isa_dma && j ? GFP_DMA : 0) | GFP_ATOMIC); + retval->host_busy = 0; + retval->block = NULL; + retval->wish_block = 0; + if(j > 0xffff) panic("Too many extra bytes requested\n"); + retval->extra_bytes = j; + retval->loaded_as_module = scsi_loadable_module_flag; + retval->host_no = max_scsi_hosts++; /* never reuse host_no (DB) */ + next_scsi_host++; + retval->host_queue = NULL; + retval->host_wait = NULL; + retval->last_reset = 0; + retval->irq = 0; + retval->dma_channel = 0xff; + + /* These three are default values which can be overridden */ + retval->max_channel = 0; + retval->max_id = 8; + retval->max_lun = 8; + + retval->unique_id = 0; + retval->io_port = 0; + retval->hostt = tpnt; + retval->next = NULL; +#ifdef DEBUG + printk("Register %x %x: %d\n", (int)retval, (int)retval->hostt, j); +#endif + + /* The next six are the default values which can be overridden + * if need be */ + retval->this_id = tpnt->this_id; + retval->can_queue = tpnt->can_queue; + retval->sg_tablesize = tpnt->sg_tablesize; + retval->cmd_per_lun = tpnt->cmd_per_lun; + retval->unchecked_isa_dma = tpnt->unchecked_isa_dma; + retval->use_clustering = tpnt->use_clustering; + + retval->select_queue_depths = NULL; + + if(!scsi_hostlist) + scsi_hostlist = retval; + else + { + shpnt = scsi_hostlist; + while(shpnt->next) shpnt = shpnt->next; + shpnt->next = retval; + } + + return retval; +} + +int +scsi_register_device(struct Scsi_Device_Template * sdpnt) +{ + if(sdpnt->next) panic("Device already registered"); + sdpnt->next = scsi_devicelist; + scsi_devicelist = sdpnt; + return 0; +} + +unsigned int scsi_init() +{ + static int called = 0; + int i, pcount; + Scsi_Host_Template * tpnt; + struct Scsi_Host * shpnt; + const char * name; + + if(called) return 0; + + called = 1; + for (tpnt = &builtin_scsi_hosts[0], i = 0; i < MAX_SCSI_HOSTS; ++i, tpnt++) + { + /* + * Initialize our semaphores. -1 is interpreted to mean + * "inactive" - where as 0 will indicate a time out condition. + */ + + pcount = next_scsi_host; + if ((tpnt->detect) && + (tpnt->present = + tpnt->detect(tpnt))) + { + /* The only time this should come up is when people use + * some kind of patched driver of some kind or another. */ + if(pcount == next_scsi_host) { + if(tpnt->present > 1) + panic("Failure to register low-level scsi driver"); + /* The low-level driver failed to register a driver. We + * can do this now. */ + scsi_register(tpnt,0); + } + tpnt->next = scsi_hosts; + scsi_hosts = tpnt; + + /* Add the driver to /proc/scsi */ +#if CONFIG_PROC_FS + build_proc_dir_entries(tpnt); +#endif + } + } + + for(shpnt=scsi_hostlist; shpnt; shpnt = shpnt->next) + { + if(shpnt->hostt->info) + name = shpnt->hostt->info(shpnt); + else + name = shpnt->hostt->name; + printk ("scsi%d : %s\n", /* And print a little message */ + shpnt->host_no, name); + } + + printk ("scsi : %d host%s.\n", next_scsi_host, + (next_scsi_host == 1) ? "" : "s"); + + scsi_make_blocked_list(); + + /* Now attach the high level drivers */ +#ifdef CONFIG_BLK_DEV_SD + scsi_register_device(&sd_template); +#endif +#ifdef CONFIG_BLK_DEV_SR + scsi_register_device(&sr_template); +#endif +#ifdef CONFIG_CHR_DEV_ST + scsi_register_device(&st_template); +#endif +#ifdef CONFIG_CHR_DEV_SG + scsi_register_device(&sg_template); +#endif + +#if 0 + max_scsi_hosts = next_scsi_host; +#endif + return 0; +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/hosts.h b/linux/src/drivers/scsi/hosts.h new file mode 100644 index 00000000..8f3f4e0e --- /dev/null +++ b/linux/src/drivers/scsi/hosts.h @@ -0,0 +1,405 @@ +/* + * hosts.h Copyright (C) 1992 Drew Eckhardt + * Copyright (C) 1993, 1994, 1995 Eric Youngdale + * + * mid to low-level SCSI driver interface header + * Initial versions: Drew Eckhardt + * Subsequent revisions: Eric Youngdale + * + * <drew@colorado.edu> + * + * Modified by Eric Youngdale eric@aib.com to + * add scatter-gather, multiple outstanding request, and other + * enhancements. + * + * Further modified by Eric Youngdale to support multiple host adapters + * of the same type. + */ + +#ifndef _HOSTS_H +#define _HOSTS_H + +/* + $Header: cvs/gnumach/linux/src/drivers/scsi/Attic/hosts.h,v 1.1 1999/04/26 05:54:41 tb Exp $ +*/ + +#include <linux/proc_fs.h> + +/* It is senseless to set SG_ALL any higher than this - the performance + * does not get any better, and it wastes memory + */ +#define SG_NONE 0 +#define SG_ALL 0xff + +#define DISABLE_CLUSTERING 0 +#define ENABLE_CLUSTERING 1 + +/* The various choices mean: + * NONE: Self evident. Host adapter is not capable of scatter-gather. + * ALL: Means that the host adapter module can do scatter-gather, + * and that there is no limit to the size of the table to which + * we scatter/gather data. + * Anything else: Indicates the maximum number of chains that can be + * used in one scatter-gather request. + */ + +/* + * The Scsi_Host_Template type has all that is needed to interface with a SCSI + * host in a device independent matter. There is one entry for each different + * type of host adapter that is supported on the system. + */ + +typedef struct scsi_disk Disk; + +typedef struct SHT +{ + + /* Used with loadable modules so we can construct a linked list. */ + struct SHT * next; + + /* Used with loadable modules so that we know when it is safe to unload */ + long * usage_count; + + /* The pointer to the /proc/scsi directory entry */ + struct proc_dir_entry *proc_dir; + + /* proc-fs info function. + * Can be used to export driver statistics and other infos to the world + * outside the kernel ie. userspace and it also provides an interface + * to feed the driver with information. Check eata_dma_proc.c for reference + */ + int (*proc_info)(char *, char **, off_t, int, int, int); + + /* + * The name pointer is a pointer to the name of the SCSI + * device detected. + */ + const char *name; + + /* + * The detect function shall return non zero on detection, + * indicating the number of host adapters of this particular + * type were found. It should also + * initialize all data necessary for this particular + * SCSI driver. It is passed the host number, so this host + * knows where the first entry is in the scsi_hosts[] array. + * + * Note that the detect routine MUST not call any of the mid level + * functions to queue commands because things are not guaranteed + * to be set up yet. The detect routine can send commands to + * the host adapter as long as the program control will not be + * passed to scsi.c in the processing of the command. Note + * especially that scsi_malloc/scsi_free must not be called. + */ + int (* detect)(struct SHT *); + + /* Used with loadable modules to unload the host structures. Note: + * there is a default action built into the modules code which may + * be sufficient for most host adapters. Thus you may not have to supply + * this at all. + */ + int (*release)(struct Scsi_Host *); + + /* + * The info function will return whatever useful + * information the developer sees fit. If not provided, then + * the name field will be used instead. + */ + const char *(* info)(struct Scsi_Host *); + + /* + * The command function takes a target, a command (this is a SCSI + * command formatted as per the SCSI spec, nothing strange), a + * data buffer pointer, and data buffer length pointer. The return + * is a status int, bit fielded as follows : + * Byte What + * 0 SCSI status code + * 1 SCSI 1 byte message + * 2 host error return. + * 3 mid level error return + */ + int (* command)(Scsi_Cmnd *); + + /* + * The QueueCommand function works in a similar manner + * to the command function. It takes an additional parameter, + * void (* done)(int host, int code) which is passed the host + * # and exit result when the command is complete. + * Host number is the POSITION IN THE hosts array of THIS + * host adapter. + */ + int (* queuecommand)(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); + + /* + * Since the mid level driver handles time outs, etc, we want to + * be able to abort the current command. Abort returns 0 if the + * abortion was successful. The field SCpnt->abort reason + * can be filled in with the appropriate reason why we wanted + * the abort in the first place, and this will be used + * in the mid-level code instead of the host_byte(). + * If non-zero, the code passed to it + * will be used as the return code, otherwise + * DID_ABORT should be returned. + * + * Note that the scsi driver should "clean up" after itself, + * resetting the bus, etc. if necessary. + */ + int (* abort)(Scsi_Cmnd *); + + /* + * The reset function will reset the SCSI bus. Any executing + * commands should fail with a DID_RESET in the host byte. + * The Scsi_Cmnd is passed so that the reset routine can figure + * out which host adapter should be reset, and also which command + * within the command block was responsible for the reset in + * the first place. Some hosts do not implement a reset function, + * and these hosts must call scsi_request_sense(SCpnt) to keep + * the command alive. + */ + int (* reset)(Scsi_Cmnd *, unsigned int); + + /* + * This function is used to select synchronous communications, + * which will result in a higher data throughput. Not implemented + * yet. + */ + int (* slave_attach)(int, int); + + /* + * This function determines the bios parameters for a given + * harddisk. These tend to be numbers that are made up by + * the host adapter. Parameters: + * size, device number, list (heads, sectors, cylinders) + */ + int (* bios_param)(Disk *, kdev_t, int []); + + /* + * This determines if we will use a non-interrupt driven + * or an interrupt driven scheme, It is set to the maximum number + * of simultaneous commands a given host adapter will accept. + */ + int can_queue; + + /* + * In many instances, especially where disconnect / reconnect are + * supported, our host also has an ID on the SCSI bus. If this is + * the case, then it must be reserved. Please set this_id to -1 if + * your setup is in single initiator mode, and the host lacks an + * ID. + */ + int this_id; + + /* + * This determines the degree to which the host adapter is capable + * of scatter-gather. + */ + short unsigned int sg_tablesize; + + /* + * True if this host adapter can make good use of linked commands. + * This will allow more than one command to be queued to a given + * unit on a given host. Set this to the maximum number of command + * blocks to be provided for each device. Set this to 1 for one + * command block per lun, 2 for two, etc. Do not set this to 0. + * You should make sure that the host adapter will do the right thing + * before you try setting this above 1. + */ + short cmd_per_lun; + + /* + * present contains counter indicating how many boards of this + * type were found when we did the scan. + */ + unsigned char present; + + /* + * true if this host adapter uses unchecked DMA onto an ISA bus. + */ + unsigned unchecked_isa_dma:1; + + /* + * true if this host adapter can make good use of clustering. + * I originally thought that if the tablesize was large that it + * was a waste of CPU cycles to prepare a cluster list, but + * it works out that the Buslogic is faster if you use a smaller + * number of segments (i.e. use clustering). I guess it is + * inefficient. + */ + unsigned use_clustering:1; + +} Scsi_Host_Template; + +/* + * The scsi_hosts array is the array containing the data for all + * possible <supported> scsi hosts. This is similar to the + * Scsi_Host_Template, except that we have one entry for each + * actual physical host adapter on the system, stored as a linked + * list. Note that if there are 2 aha1542 boards, then there will + * be two Scsi_Host entries, but only 1 Scsi_Host_Template entry. + */ + +struct Scsi_Host +{ + struct Scsi_Host * next; + unsigned short extra_bytes; + volatile unsigned char host_busy; + char host_no; /* Used for IOCTL_GET_IDLUN, /proc/scsi et al. */ + unsigned long last_reset; + struct wait_queue *host_wait; + Scsi_Cmnd *host_queue; + Scsi_Host_Template * hostt; + + /* + * These three parameters can be used to allow for wide scsi, + * and for host adapters that support multiple busses + * The first two should be set to 1 more than the actual max id + * or lun (i.e. 8 for normal systems). + */ + unsigned int max_id; + unsigned int max_lun; + unsigned int max_channel; + + /* + * Pointer to a circularly linked list - this indicates the hosts + * that should be locked out of performing I/O while we have an active + * command on this host. + */ + struct Scsi_Host * block; + unsigned wish_block:1; + + /* These parameters should be set by the detect routine */ + unsigned char *base; + unsigned int io_port; + unsigned char n_io_port; + unsigned char irq; + unsigned char dma_channel; + + /* + * This is a unique identifier that must be assigned so that we + * have some way of identifying each detected host adapter properly + * and uniquely. For hosts that do not support more than one card + * in the system at one time, this does not need to be set. It is + * initialized to 0 in scsi_register. + */ + unsigned int unique_id; + + /* + * The rest can be copied from the template, or specifically + * initialized, as required. + */ + + int this_id; + int can_queue; + short cmd_per_lun; + short unsigned int sg_tablesize; + unsigned unchecked_isa_dma:1; + unsigned use_clustering:1; + /* + * True if this host was loaded as a loadable module + */ + unsigned loaded_as_module:1; + + void (*select_queue_depths)(struct Scsi_Host *, Scsi_Device *); + + unsigned long hostdata[0]; /* Used for storage of host specific stuff */ +}; + +extern struct Scsi_Host * scsi_hostlist; +extern struct Scsi_Device_Template * scsi_devicelist; + +extern Scsi_Host_Template * scsi_hosts; + +extern void build_proc_dir_entries(Scsi_Host_Template *); + + +/* + * scsi_init initializes the scsi hosts. + */ + +/* + * We use these goofy things because the MM is not set up when we init + * the scsi subsystem. By using these functions we can write code that + * looks normal. Also, it makes it possible to use the same code for a + * loadable module. + */ + +extern void * scsi_init_malloc(unsigned int size, int priority); +extern void scsi_init_free(char * ptr, unsigned int size); + +extern int next_scsi_host; + +extern int scsi_loadable_module_flag; +unsigned int scsi_init(void); +extern struct Scsi_Host * scsi_register(Scsi_Host_Template *, int j); +extern void scsi_unregister(struct Scsi_Host * i); + +#define BLANK_HOST {"", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +struct Scsi_Device_Template +{ + struct Scsi_Device_Template * next; + const char * name; + const char * tag; + long * usage_count; /* Used for loadable modules */ + unsigned char scsi_type; + unsigned char major; + unsigned char nr_dev; /* Number currently attached */ + unsigned char dev_noticed; /* Number of devices detected. */ + unsigned char dev_max; /* Current size of arrays */ + unsigned blk:1; /* 0 if character device */ + int (*detect)(Scsi_Device *); /* Returns 1 if we can attach this device */ + int (*init)(void); /* Sizes arrays based upon number of devices + * detected */ + void (*finish)(void); /* Perform initialization after attachment */ + int (*attach)(Scsi_Device *); /* Attach devices to arrays */ + void (*detach)(Scsi_Device *); +}; + +extern struct Scsi_Device_Template sd_template; +extern struct Scsi_Device_Template st_template; +extern struct Scsi_Device_Template sr_template; +extern struct Scsi_Device_Template sg_template; + +int scsi_register_device(struct Scsi_Device_Template * sdpnt); + +/* These are used by loadable modules */ +extern int scsi_register_module(int, void *); +extern void scsi_unregister_module(int, void *); + +/* The different types of modules that we can load and unload */ +#define MODULE_SCSI_HA 1 +#define MODULE_SCSI_CONST 2 +#define MODULE_SCSI_IOCTL 3 +#define MODULE_SCSI_DEV 4 + + +/* + * This is an ugly hack. If we expect to be able to load devices at run time, + * we need to leave extra room in some of the data structures. Doing a + * realloc to enlarge the structures would be riddled with race conditions, + * so until a better solution is discovered, we use this crude approach + */ +#define SD_EXTRA_DEVS 2 +#define ST_EXTRA_DEVS 2 +#define SR_EXTRA_DEVS 2 +#define SG_EXTRA_DEVS (SD_EXTRA_DEVS + SR_EXTRA_DEVS + ST_EXTRA_DEVS) + +#endif +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/in2000.c b/linux/src/drivers/scsi/in2000.c new file mode 100644 index 00000000..04ecc151 --- /dev/null +++ b/linux/src/drivers/scsi/in2000.c @@ -0,0 +1,2376 @@ +/* + * in2000.c - Linux device driver for the + * Always IN2000 ISA SCSI card. + * + * Copyright (c) 1996 John Shifflett, GeoLog Consulting + * john@geolog.com + * jshiffle@netcom.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * Drew Eckhardt's excellent 'Generic NCR5380' sources provided + * much of the inspiration and some of the code for this driver. + * The Linux IN2000 driver distributed in the Linux kernels through + * version 1.2.13 was an extremely valuable reference on the arcane + * (and still mysterious) workings of the IN2000's fifo. It also + * is where I lifted in2000_biosparam(), the gist of the card + * detection scheme, and other bits of code. Many thanks to the + * talented and courageous people who wrote, contributed to, and + * maintained that driver (including Brad McLean, Shaun Savage, + * Bill Earnest, Larry Doolittle, Roger Sunshine, John Luckey, + * Matt Postiff, Peter Lu, zerucha@shell.portal.com, and Eric + * Youngdale). I should also mention the driver written by + * Hamish Macdonald for the (GASP!) Amiga A2091 card, included + * in the Linux-m68k distribution; it gave me a good initial + * understanding of the proper way to run a WD33c93 chip, and I + * ended up stealing lots of code from it. + * + * _This_ driver is (I feel) an improvement over the old one in + * several respects: + * - All problems relating to the data size of a SCSI request are + * gone (as far as I know). The old driver couldn't handle + * swapping to partitions because that involved 4k blocks, nor + * could it deal with the st.c tape driver unmodified, because + * that usually involved 4k - 32k blocks. The old driver never + * quite got away from a morbid dependence on 2k block sizes - + * which of course is the size of the card's fifo. + * + * - Target Disconnection/Reconnection is now supported. Any + * system with more than one device active on the SCSI bus + * will benefit from this. The driver defaults to what I'm + * calling 'adaptive disconnect' - meaning that each command + * is evaluated individually as to whether or not it should + * be run with the option to disconnect/reselect (if the + * device chooses), or as a "SCSI-bus-hog". + * + * - Synchronous data transfers are now supported. Because there + * are a few devices (and many improperly terminated systems) + * that choke when doing sync, the default is sync DISABLED + * for all devices. This faster protocol can (and should!) + * be enabled on selected devices via the command-line. + * + * - Runtime operating parameters can now be specified through + * either the LILO or the 'insmod' command line. For LILO do: + * "in2000=blah,blah,blah" + * and with insmod go like: + * "insmod /usr/src/linux/modules/in2000.o setup_strings=blah,blah" + * The defaults should be good for most people. See the comment + * for 'setup_strings' below for more details. + * + * - The old driver relied exclusively on what the Western Digital + * docs call "Combination Level 2 Commands", which are a great + * idea in that the CPU is relieved of a lot of interrupt + * overhead. However, by accepting a certain (user-settable) + * amount of additional interrupts, this driver achieves + * better control over the SCSI bus, and data transfers are + * almost as fast while being much easier to define, track, + * and debug. + * + * - You can force detection of a card whose BIOS has been disabled. + * + * - Multiple IN2000 cards might almost be supported. I've tried to + * keep it in mind, but have no way to test... + * + * + * TODO: + * tagged queuing. multiple cards. + * + * + * NOTE: + * When using this or any other SCSI driver as a module, you'll + * find that with the stock kernel, at most _two_ SCSI hard + * drives will be linked into the device list (ie, usable). + * If your IN2000 card has more than 2 disks on its bus, you + * might want to change the define of 'SD_EXTRA_DEVS' in the + * 'hosts.h' file from 2 to whatever is appropriate. It took + * me a while to track down this surprisingly obscure and + * undocumented little "feature". + * + * + * People with bug reports, wish-lists, complaints, comments, + * or improvements are asked to pah-leeez email me (John Shifflett) + * at john@geolog.com or jshiffle@netcom.com! I'm anxious to get + * this thing into as good a shape as possible, and I'm positive + * there are lots of lurking bugs and "Stupid Places". + * + */ + +#include <linux/module.h> + +#include <asm/system.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <asm/io.h> +#include <linux/ioport.h> +#include <linux/blkdev.h> + +#include <linux/blk.h> +#include <linux/stat.h> + +#include "scsi.h" +#include "sd.h" +#include "hosts.h" + +#define IN2000_VERSION "1.33" +#define IN2000_DATE "26/August/1998" + +#include "in2000.h" + + +/* + * 'setup_strings' is a single string used to pass operating parameters and + * settings from the kernel/module command-line to the driver. 'setup_args[]' + * is an array of strings that define the compile-time default values for + * these settings. If Linux boots with a LILO or insmod command-line, those + * settings are combined with 'setup_args[]'. Note that LILO command-lines + * are prefixed with "in2000=" while insmod uses a "setup_strings=" prefix. + * The driver recognizes the following keywords (lower case required) and + * arguments: + * + * - ioport:addr -Where addr is IO address of a (usually ROM-less) card. + * - noreset -No optional args. Prevents SCSI bus reset at boot time. + * - nosync:x -x is a bitmask where the 1st 7 bits correspond with + * the 7 possible SCSI devices (bit 0 for device #0, etc). + * Set a bit to PREVENT sync negotiation on that device. + * The driver default is sync DISABLED on all devices. + * - period:ns -ns is the minimum # of nanoseconds in a SCSI data transfer + * period. Default is 500; acceptable values are 250 - 1000. + * - disconnect:x -x = 0 to never allow disconnects, 2 to always allow them. + * x = 1 does 'adaptive' disconnects, which is the default + * and generally the best choice. + * - debug:x -If 'DEBUGGING_ON' is defined, x is a bitmask that causes + * various types of debug output to printed - see the DB_xxx + * defines in in2000.h + * - proc:x -If 'PROC_INTERFACE' is defined, x is a bitmask that + * determines how the /proc interface works and what it + * does - see the PR_xxx defines in in2000.h + * + * Syntax Notes: + * - Numeric arguments can be decimal or the '0x' form of hex notation. There + * _must_ be a colon between a keyword and its numeric argument, with no + * spaces. + * - Keywords are separated by commas, no spaces, in the standard kernel + * command-line manner. + * - A keyword in the 'nth' comma-separated command-line member will overwrite + * the 'nth' element of setup_args[]. A blank command-line member (in + * other words, a comma with no preceding keyword) will _not_ overwrite + * the corresponding setup_args[] element. + * + * A few LILO examples (for insmod, use 'setup_strings' instead of 'in2000'): + * - in2000=ioport:0x220,noreset + * - in2000=period:250,disconnect:2,nosync:0x03 + * - in2000=debug:0x1e + * - in2000=proc:3 + */ + +/* Normally, no defaults are specified... */ +static char *setup_args[] = + {"","","","","","","","",""}; + +/* filled in by 'insmod' */ +static char *setup_strings = 0; + +#ifdef MODULE_PARM +MODULE_PARM(setup_strings, "s"); +#endif + + +static struct Scsi_Host *instance_list = 0; + + + +static inline uchar read_3393(struct IN2000_hostdata *hostdata, uchar reg_num) +{ + write1_io(reg_num,IO_WD_ADDR); + return read1_io(IO_WD_DATA); +} + + +#define READ_AUX_STAT() read1_io(IO_WD_ASR) + + +static inline void write_3393(struct IN2000_hostdata *hostdata, uchar reg_num, uchar value) +{ + write1_io(reg_num,IO_WD_ADDR); + write1_io(value,IO_WD_DATA); +} + + +static inline void write_3393_cmd(struct IN2000_hostdata *hostdata, uchar cmd) +{ +/* while (READ_AUX_STAT() & ASR_CIP) + printk("|");*/ + write1_io(WD_COMMAND,IO_WD_ADDR); + write1_io(cmd,IO_WD_DATA); +} + + +static uchar read_1_byte(struct IN2000_hostdata *hostdata) +{ +uchar asr, x = 0; + + write_3393(hostdata,WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_POLLED); + write_3393_cmd(hostdata,WD_CMD_TRANS_INFO|0x80); + do { + asr = READ_AUX_STAT(); + if (asr & ASR_DBR) + x = read_3393(hostdata,WD_DATA); + } while (!(asr & ASR_INT)); + return x; +} + + +static void write_3393_count(struct IN2000_hostdata *hostdata, unsigned long value) +{ + write1_io(WD_TRANSFER_COUNT_MSB,IO_WD_ADDR); + write1_io((value >> 16),IO_WD_DATA); + write1_io((value >> 8),IO_WD_DATA); + write1_io(value,IO_WD_DATA); +} + + +static unsigned long read_3393_count(struct IN2000_hostdata *hostdata) +{ +unsigned long value; + + write1_io(WD_TRANSFER_COUNT_MSB,IO_WD_ADDR); + value = read1_io(IO_WD_DATA) << 16; + value |= read1_io(IO_WD_DATA) << 8; + value |= read1_io(IO_WD_DATA); + return value; +} + + +/* The 33c93 needs to be told which direction a command transfers its + * data; we use this function to figure it out. Returns true if there + * will be a DATA_OUT phase with this command, false otherwise. + * (Thanks to Joerg Dorchain for the research and suggestion.) + */ +static int is_dir_out(Scsi_Cmnd *cmd) +{ + switch (cmd->cmnd[0]) { + case WRITE_6: case WRITE_10: case WRITE_12: + case WRITE_LONG: case WRITE_SAME: case WRITE_BUFFER: + case WRITE_VERIFY: case WRITE_VERIFY_12: + case COMPARE: case COPY: case COPY_VERIFY: + case SEARCH_EQUAL: case SEARCH_HIGH: case SEARCH_LOW: + case SEARCH_EQUAL_12: case SEARCH_HIGH_12: case SEARCH_LOW_12: + case FORMAT_UNIT: case REASSIGN_BLOCKS: case RESERVE: + case MODE_SELECT: case MODE_SELECT_10: case LOG_SELECT: + case SEND_DIAGNOSTIC: case CHANGE_DEFINITION: case UPDATE_BLOCK: + case SET_WINDOW: case MEDIUM_SCAN: case SEND_VOLUME_TAG: + case 0xea: + return 1; + default: + return 0; + } +} + + + +static struct sx_period sx_table[] = { + { 1, 0x20}, + {252, 0x20}, + {376, 0x30}, + {500, 0x40}, + {624, 0x50}, + {752, 0x60}, + {876, 0x70}, + {1000,0x00}, + {0, 0} }; + +static int round_period(unsigned int period) +{ +int x; + + for (x=1; sx_table[x].period_ns; x++) { + if ((period <= sx_table[x-0].period_ns) && + (period > sx_table[x-1].period_ns)) { + return x; + } + } + return 7; +} + +static uchar calc_sync_xfer(unsigned int period, unsigned int offset) +{ +uchar result; + + period *= 4; /* convert SDTR code to ns */ + result = sx_table[round_period(period)].reg_value; + result |= (offset < OPTIMUM_SX_OFF)?offset:OPTIMUM_SX_OFF; + return result; +} + + + +static void in2000_execute(struct Scsi_Host *instance); + +int in2000_queuecommand (Scsi_Cmnd *cmd, void (*done)(Scsi_Cmnd *)) +{ +struct IN2000_hostdata *hostdata; +Scsi_Cmnd *tmp; +unsigned long flags; + + hostdata = (struct IN2000_hostdata *)cmd->host->hostdata; + +DB(DB_QUEUE_COMMAND,printk("Q-%d-%02x-%ld(",cmd->target,cmd->cmnd[0],cmd->pid)) + +/* Set up a few fields in the Scsi_Cmnd structure for our own use: + * - host_scribble is the pointer to the next cmd in the input queue + * - scsi_done points to the routine we call when a cmd is finished + * - result is what you'd expect + */ + + cmd->host_scribble = NULL; + cmd->scsi_done = done; + cmd->result = 0; + +/* We use the Scsi_Pointer structure that's included with each command + * as a scratchpad (as it's intended to be used!). The handy thing about + * the SCp.xxx fields is that they're always associated with a given + * cmd, and are preserved across disconnect-reselect. This means we + * can pretty much ignore SAVE_POINTERS and RESTORE_POINTERS messages + * if we keep all the critical pointers and counters in SCp: + * - SCp.ptr is the pointer into the RAM buffer + * - SCp.this_residual is the size of that buffer + * - SCp.buffer points to the current scatter-gather buffer + * - SCp.buffers_residual tells us how many S.G. buffers there are + * - SCp.have_data_in helps keep track of >2048 byte transfers + * - SCp.sent_command is not used + * - SCp.phase records this command's SRCID_ER bit setting + */ + + if (cmd->use_sg) { + cmd->SCp.buffer = (struct scatterlist *)cmd->buffer; + cmd->SCp.buffers_residual = cmd->use_sg - 1; + cmd->SCp.ptr = (char *)cmd->SCp.buffer->address; + cmd->SCp.this_residual = cmd->SCp.buffer->length; + } + else { + cmd->SCp.buffer = NULL; + cmd->SCp.buffers_residual = 0; + cmd->SCp.ptr = (char *)cmd->request_buffer; + cmd->SCp.this_residual = cmd->request_bufflen; + } + cmd->SCp.have_data_in = 0; + +/* We don't set SCp.phase here - that's done in in2000_execute() */ + +/* WD docs state that at the conclusion of a "LEVEL2" command, the + * status byte can be retrieved from the LUN register. Apparently, + * this is the case only for *uninterrupted* LEVEL2 commands! If + * there are any unexpected phases entered, even if they are 100% + * legal (different devices may choose to do things differently), + * the LEVEL2 command sequence is exited. This often occurs prior + * to receiving the status byte, in which case the driver does a + * status phase interrupt and gets the status byte on its own. + * While such a command can then be "resumed" (ie restarted to + * finish up as a LEVEL2 command), the LUN register will NOT be + * a valid status byte at the command's conclusion, and we must + * use the byte obtained during the earlier interrupt. Here, we + * preset SCp.Status to an illegal value (0xff) so that when + * this command finally completes, we can tell where the actual + * status byte is stored. + */ + + cmd->SCp.Status = ILLEGAL_STATUS_BYTE; + +/* We need to disable interrupts before messing with the input + * queue and calling in2000_execute(). + */ + + save_flags(flags); + cli(); + + /* + * Add the cmd to the end of 'input_Q'. Note that REQUEST_SENSE + * commands are added to the head of the queue so that the desired + * sense data is not lost before REQUEST_SENSE executes. + */ + + if (!(hostdata->input_Q) || (cmd->cmnd[0] == REQUEST_SENSE)) { + cmd->host_scribble = (uchar *)hostdata->input_Q; + hostdata->input_Q = cmd; + } + else { /* find the end of the queue */ + for (tmp=(Scsi_Cmnd *)hostdata->input_Q; tmp->host_scribble; + tmp=(Scsi_Cmnd *)tmp->host_scribble) + ; + tmp->host_scribble = (uchar *)cmd; + } + +/* We know that there's at least one command in 'input_Q' now. + * Go see if any of them are runnable! + */ + + in2000_execute(cmd->host); + +DB(DB_QUEUE_COMMAND,printk(")Q-%ld ",cmd->pid)) + + restore_flags(flags); + return 0; +} + + + +/* + * This routine attempts to start a scsi command. If the host_card is + * already connected, we give up immediately. Otherwise, look through + * the input_Q, using the first command we find that's intended + * for a currently non-busy target/lun. + * Note that this function is always called with interrupts already + * disabled (either from in2000_queuecommand() or in2000_intr()). + */ +static void in2000_execute (struct Scsi_Host *instance) +{ +struct IN2000_hostdata *hostdata; +Scsi_Cmnd *cmd, *prev; +int i; +unsigned short *sp; +unsigned short f; +unsigned short flushbuf[16]; + + + hostdata = (struct IN2000_hostdata *)instance->hostdata; + +DB(DB_EXECUTE,printk("EX(")) + + if (hostdata->selecting || hostdata->connected) { + +DB(DB_EXECUTE,printk(")EX-0 ")) + + return; + } + + /* + * Search through the input_Q for a command destined + * for an idle target/lun. + */ + + cmd = (Scsi_Cmnd *)hostdata->input_Q; + prev = 0; + while (cmd) { + if (!(hostdata->busy[cmd->target] & (1 << cmd->lun))) + break; + prev = cmd; + cmd = (Scsi_Cmnd *)cmd->host_scribble; + } + + /* quit if queue empty or all possible targets are busy */ + + if (!cmd) { + +DB(DB_EXECUTE,printk(")EX-1 ")) + + return; + } + + /* remove command from queue */ + + if (prev) + prev->host_scribble = cmd->host_scribble; + else + hostdata->input_Q = (Scsi_Cmnd *)cmd->host_scribble; + +#ifdef PROC_STATISTICS + hostdata->cmd_cnt[cmd->target]++; +#endif + +/* + * Start the selection process + */ + + if (is_dir_out(cmd)) + write_3393(hostdata,WD_DESTINATION_ID, cmd->target); + else + write_3393(hostdata,WD_DESTINATION_ID, cmd->target | DSTID_DPD); + +/* Now we need to figure out whether or not this command is a good + * candidate for disconnect/reselect. We guess to the best of our + * ability, based on a set of hierarchical rules. When several + * devices are operating simultaneously, disconnects are usually + * an advantage. In a single device system, or if only 1 device + * is being accessed, transfers usually go faster if disconnects + * are not allowed: + * + * + Commands should NEVER disconnect if hostdata->disconnect = + * DIS_NEVER (this holds for tape drives also), and ALWAYS + * disconnect if hostdata->disconnect = DIS_ALWAYS. + * + Tape drive commands should always be allowed to disconnect. + * + Disconnect should be allowed if disconnected_Q isn't empty. + * + Commands should NOT disconnect if input_Q is empty. + * + Disconnect should be allowed if there are commands in input_Q + * for a different target/lun. In this case, the other commands + * should be made disconnect-able, if not already. + * + * I know, I know - this code would flunk me out of any + * "C Programming 101" class ever offered. But it's easy + * to change around and experiment with for now. + */ + + cmd->SCp.phase = 0; /* assume no disconnect */ + if (hostdata->disconnect == DIS_NEVER) + goto no; + if (hostdata->disconnect == DIS_ALWAYS) + goto yes; + if (cmd->device->type == 1) /* tape drive? */ + goto yes; + if (hostdata->disconnected_Q) /* other commands disconnected? */ + goto yes; + if (!(hostdata->input_Q)) /* input_Q empty? */ + goto no; + for (prev=(Scsi_Cmnd *)hostdata->input_Q; prev; + prev=(Scsi_Cmnd *)prev->host_scribble) { + if ((prev->target != cmd->target) || (prev->lun != cmd->lun)) { + for (prev=(Scsi_Cmnd *)hostdata->input_Q; prev; + prev=(Scsi_Cmnd *)prev->host_scribble) + prev->SCp.phase = 1; + goto yes; + } + } + goto no; + +yes: + cmd->SCp.phase = 1; + +#ifdef PROC_STATISTICS + hostdata->disc_allowed_cnt[cmd->target]++; +#endif + +no: + write_3393(hostdata,WD_SOURCE_ID,((cmd->SCp.phase)?SRCID_ER:0)); + + write_3393(hostdata,WD_TARGET_LUN, cmd->lun); + write_3393(hostdata,WD_SYNCHRONOUS_TRANSFER,hostdata->sync_xfer[cmd->target]); + hostdata->busy[cmd->target] |= (1 << cmd->lun); + + if ((hostdata->level2 <= L2_NONE) || + (hostdata->sync_stat[cmd->target] == SS_UNSET)) { + + /* + * Do a 'Select-With-ATN' command. This will end with + * one of the following interrupts: + * CSR_RESEL_AM: failure - can try again later. + * CSR_TIMEOUT: failure - give up. + * CSR_SELECT: success - proceed. + */ + + hostdata->selecting = cmd; + +/* Every target has its own synchronous transfer setting, kept in + * the sync_xfer array, and a corresponding status byte in sync_stat[]. + * Each target's sync_stat[] entry is initialized to SS_UNSET, and its + * sync_xfer[] entry is initialized to the default/safe value. SS_UNSET + * means that the parameters are undetermined as yet, and that we + * need to send an SDTR message to this device after selection is + * complete. We set SS_FIRST to tell the interrupt routine to do so, + * unless we don't want to even _try_ synchronous transfers: In this + * case we set SS_SET to make the defaults final. + */ + if (hostdata->sync_stat[cmd->target] == SS_UNSET) { + if (hostdata->sync_off & (1 << cmd->target)) + hostdata->sync_stat[cmd->target] = SS_SET; + else + hostdata->sync_stat[cmd->target] = SS_FIRST; + } + hostdata->state = S_SELECTING; + write_3393_count(hostdata,0); /* this guarantees a DATA_PHASE interrupt */ + write_3393_cmd(hostdata,WD_CMD_SEL_ATN); + } + + else { + + /* + * Do a 'Select-With-ATN-Xfer' command. This will end with + * one of the following interrupts: + * CSR_RESEL_AM: failure - can try again later. + * CSR_TIMEOUT: failure - give up. + * anything else: success - proceed. + */ + + hostdata->connected = cmd; + write_3393(hostdata,WD_COMMAND_PHASE, 0); + + /* copy command_descriptor_block into WD chip + * (take advantage of auto-incrementing) + */ + + write1_io(WD_CDB_1, IO_WD_ADDR); + for (i=0; i<cmd->cmd_len; i++) + write1_io(cmd->cmnd[i], IO_WD_DATA); + + /* The wd33c93 only knows about Group 0, 1, and 5 commands when + * it's doing a 'select-and-transfer'. To be safe, we write the + * size of the CDB into the OWN_ID register for every case. This + * way there won't be problems with vendor-unique, audio, etc. + */ + + write_3393(hostdata, WD_OWN_ID, cmd->cmd_len); + + /* When doing a non-disconnect command, we can save ourselves a DATA + * phase interrupt later by setting everything up now. With writes we + * need to pre-fill the fifo; if there's room for the 32 flush bytes, + * put them in there too - that'll avoid a fifo interrupt. Reads are + * somewhat simpler. + * KLUDGE NOTE: It seems that you can't completely fill the fifo here: + * This results in the IO_FIFO_COUNT register rolling over to zero, + * and apparently the gate array logic sees this as empty, not full, + * so the 3393 chip is never signalled to start reading from the + * fifo. Or maybe it's seen as a permanent fifo interrupt condition. + * Regardless, we fix this by temporarily pretending that the fifo + * is 16 bytes smaller. (I see now that the old driver has a comment + * about "don't fill completely" in an analogous place - must be the + * same deal.) This results in CDROM, swap partitions, and tape drives + * needing an extra interrupt per write command - I think we can live + * with that! + */ + + if (!(cmd->SCp.phase)) { + write_3393_count(hostdata, cmd->SCp.this_residual); + write_3393(hostdata,WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_BUS); + write1_io(0, IO_FIFO_WRITE); /* clear fifo counter, write mode */ + + if (is_dir_out(cmd)) { + hostdata->fifo = FI_FIFO_WRITING; + if ((i = cmd->SCp.this_residual) > (IN2000_FIFO_SIZE - 16) ) + i = IN2000_FIFO_SIZE - 16; + cmd->SCp.have_data_in = i; /* this much data in fifo */ + i >>= 1; /* Gulp. Assuming modulo 2. */ + sp = (unsigned short *)cmd->SCp.ptr; + f = hostdata->io_base + IO_FIFO; + +#ifdef FAST_WRITE_IO + + FAST_WRITE2_IO(); +#else + while (i--) + write2_io(*sp++,IO_FIFO); + +#endif + + /* Is there room for the flush bytes? */ + + if (cmd->SCp.have_data_in <= ((IN2000_FIFO_SIZE - 16) - 32)) { + sp = flushbuf; + i = 16; + +#ifdef FAST_WRITE_IO + + FAST_WRITE2_IO(); +#else + while (i--) + write2_io(0,IO_FIFO); + +#endif + + } + } + + else { + write1_io(0, IO_FIFO_READ); /* put fifo in read mode */ + hostdata->fifo = FI_FIFO_READING; + cmd->SCp.have_data_in = 0; /* nothing transfered yet */ + } + + } + else { + write_3393_count(hostdata,0); /* this guarantees a DATA_PHASE interrupt */ + } + hostdata->state = S_RUNNING_LEVEL2; + write_3393_cmd(hostdata,WD_CMD_SEL_ATN_XFER); + } + + /* + * Since the SCSI bus can handle only 1 connection at a time, + * we get out of here now. If the selection fails, or when + * the command disconnects, we'll come back to this routine + * to search the input_Q again... + */ + +DB(DB_EXECUTE,printk("%s%ld)EX-2 ",(cmd->SCp.phase)?"d:":"",cmd->pid)) + +} + + + +static void transfer_pio(uchar *buf, int cnt, + int data_in_dir, struct IN2000_hostdata *hostdata) +{ +uchar asr; + +DB(DB_TRANSFER,printk("(%p,%d,%s)",buf,cnt,data_in_dir?"in":"out")) + + write_3393(hostdata,WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_POLLED); + write_3393_count(hostdata,cnt); + write_3393_cmd(hostdata,WD_CMD_TRANS_INFO); + if (data_in_dir) { + do { + asr = READ_AUX_STAT(); + if (asr & ASR_DBR) + *buf++ = read_3393(hostdata,WD_DATA); + } while (!(asr & ASR_INT)); + } + else { + do { + asr = READ_AUX_STAT(); + if (asr & ASR_DBR) + write_3393(hostdata,WD_DATA, *buf++); + } while (!(asr & ASR_INT)); + } + + /* Note: we are returning with the interrupt UN-cleared. + * Since (presumably) an entire I/O operation has + * completed, the bus phase is probably different, and + * the interrupt routine will discover this when it + * responds to the uncleared int. + */ + +} + + + +static void transfer_bytes(Scsi_Cmnd *cmd, int data_in_dir) +{ +struct IN2000_hostdata *hostdata; +unsigned short *sp; +unsigned short f; +int i; + + hostdata = (struct IN2000_hostdata *)cmd->host->hostdata; + +/* Normally, you'd expect 'this_residual' to be non-zero here. + * In a series of scatter-gather transfers, however, this + * routine will usually be called with 'this_residual' equal + * to 0 and 'buffers_residual' non-zero. This means that a + * previous transfer completed, clearing 'this_residual', and + * now we need to setup the next scatter-gather buffer as the + * source or destination for THIS transfer. + */ + if (!cmd->SCp.this_residual && cmd->SCp.buffers_residual) { + ++cmd->SCp.buffer; + --cmd->SCp.buffers_residual; + cmd->SCp.this_residual = cmd->SCp.buffer->length; + cmd->SCp.ptr = cmd->SCp.buffer->address; + } + +/* Set up hardware registers */ + + write_3393(hostdata,WD_SYNCHRONOUS_TRANSFER,hostdata->sync_xfer[cmd->target]); + write_3393_count(hostdata,cmd->SCp.this_residual); + write_3393(hostdata,WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_BUS); + write1_io(0,IO_FIFO_WRITE); /* zero counter, assume write */ + +/* Reading is easy. Just issue the command and return - we'll + * get an interrupt later when we have actual data to worry about. + */ + + if (data_in_dir) { + write1_io(0,IO_FIFO_READ); + if ((hostdata->level2 >= L2_DATA) || + (hostdata->level2 == L2_BASIC && cmd->SCp.phase == 0)) { + write_3393(hostdata,WD_COMMAND_PHASE,0x45); + write_3393_cmd(hostdata,WD_CMD_SEL_ATN_XFER); + hostdata->state = S_RUNNING_LEVEL2; + } + else + write_3393_cmd(hostdata,WD_CMD_TRANS_INFO); + hostdata->fifo = FI_FIFO_READING; + cmd->SCp.have_data_in = 0; + return; + } + +/* Writing is more involved - we'll start the WD chip and write as + * much data to the fifo as we can right now. Later interrupts will + * write any bytes that don't make it at this stage. + */ + + if ((hostdata->level2 >= L2_DATA) || + (hostdata->level2 == L2_BASIC && cmd->SCp.phase == 0)) { + write_3393(hostdata,WD_COMMAND_PHASE,0x45); + write_3393_cmd(hostdata,WD_CMD_SEL_ATN_XFER); + hostdata->state = S_RUNNING_LEVEL2; + } + else + write_3393_cmd(hostdata,WD_CMD_TRANS_INFO); + hostdata->fifo = FI_FIFO_WRITING; + sp = (unsigned short *)cmd->SCp.ptr; + + if ((i = cmd->SCp.this_residual) > IN2000_FIFO_SIZE) + i = IN2000_FIFO_SIZE; + cmd->SCp.have_data_in = i; + i >>= 1; /* Gulp. We assume this_residual is modulo 2 */ + f = hostdata->io_base + IO_FIFO; + +#ifdef FAST_WRITE_IO + + FAST_WRITE2_IO(); +#else + while (i--) + write2_io(*sp++,IO_FIFO); + +#endif + +} + + +/* We need to use spin_lock_irqsave() & spin_unlock_irqrestore() in this + * function in order to work in an SMP environment. (I'd be surprised + * if the driver is ever used by anyone on a real multi-CPU motherboard, + * but it _does_ need to be able to compile and run in an SMP kernel.) + */ + +static void in2000_intr (int irqnum, void * dev_id, struct pt_regs *ptregs) +{ +struct Scsi_Host *instance; +struct IN2000_hostdata *hostdata; +Scsi_Cmnd *patch, *cmd; +uchar asr, sr, phs, id, lun, *ucp, msg; +int i,j; +unsigned long length; +unsigned short *sp; +unsigned short f; +unsigned long flags; + + for (instance = instance_list; instance; instance = instance->next) { + if (instance->irq == irqnum) + break; + } + if (!instance) { + printk("*** Hmm... interrupts are screwed up! ***\n"); + return; + } + hostdata = (struct IN2000_hostdata *)instance->hostdata; + +/* Get the spin_lock and disable further ints, for SMP */ + + CLISPIN_LOCK(flags); + +#ifdef PROC_STATISTICS + hostdata->int_cnt++; +#endif + +/* The IN2000 card has 2 interrupt sources OR'ed onto its IRQ line - the + * WD3393 chip and the 2k fifo (which is actually a dual-port RAM combined + * with a big logic array, so it's a little different than what you might + * expect). As far as I know, there's no reason that BOTH can't be active + * at the same time, but there's a problem: while we can read the 3393 + * to tell if _it_ wants an interrupt, I don't know of a way to ask the + * fifo the same question. The best we can do is check the 3393 and if + * it _isn't_ the source of the interrupt, then we can be pretty sure + * that the fifo is the culprit. + * UPDATE: I have it on good authority (Bill Earnest) that bit 0 of the + * IO_FIFO_COUNT register mirrors the fifo interrupt state. I + * assume that bit clear means interrupt active. As it turns + * out, the driver really doesn't need to check for this after + * all, so my remarks above about a 'problem' can safely be + * ignored. The way the logic is set up, there's no advantage + * (that I can see) to worrying about it. + * + * It seems that the fifo interrupt signal is negated when we extract + * bytes during read or write bytes during write. + * - fifo will interrupt when data is moving from it to the 3393, and + * there are 31 (or less?) bytes left to go. This is sort of short- + * sighted: what if you don't WANT to do more? In any case, our + * response is to push more into the fifo - either actual data or + * dummy bytes if need be. Note that we apparently have to write at + * least 32 additional bytes to the fifo after an interrupt in order + * to get it to release the ones it was holding on to - writing fewer + * than 32 will result in another fifo int. + * UPDATE: Again, info from Bill Earnest makes this more understandable: + * 32 bytes = two counts of the fifo counter register. He tells + * me that the fifo interrupt is a non-latching signal derived + * from a straightforward boolean interpretation of the 7 + * highest bits of the fifo counter and the fifo-read/fifo-write + * state. Who'd a thought? + */ + + write1_io(0, IO_LED_ON); + asr = READ_AUX_STAT(); + if (!(asr & ASR_INT)) { /* no WD33c93 interrupt? */ + +/* Ok. This is definitely a FIFO-only interrupt. + * + * If FI_FIFO_READING is set, there are up to 2048 bytes waiting to be read, + * maybe more to come from the SCSI bus. Read as many as we can out of the + * fifo and into memory at the location of SCp.ptr[SCp.have_data_in], and + * update have_data_in afterwards. + * + * If we have FI_FIFO_WRITING, the FIFO has almost run out of bytes to move + * into the WD3393 chip (I think the interrupt happens when there are 31 + * bytes left, but it may be fewer...). The 3393 is still waiting, so we + * shove some more into the fifo, which gets things moving again. If the + * original SCSI command specified more than 2048 bytes, there may still + * be some of that data left: fine - use it (from SCp.ptr[SCp.have_data_in]). + * Don't forget to update have_data_in. If we've already written out the + * entire buffer, feed 32 dummy bytes to the fifo - they're needed to + * push out the remaining real data. + * (Big thanks to Bill Earnest for getting me out of the mud in here.) + */ + + cmd = (Scsi_Cmnd *)hostdata->connected; /* assume we're connected */ +CHECK_NULL(cmd,"fifo_int") + + if (hostdata->fifo == FI_FIFO_READING) { + +DB(DB_FIFO,printk("{R:%02x} ",read1_io(IO_FIFO_COUNT))) + + sp = (unsigned short *)(cmd->SCp.ptr + cmd->SCp.have_data_in); + i = read1_io(IO_FIFO_COUNT) & 0xfe; + i <<= 2; /* # of words waiting in the fifo */ + f = hostdata->io_base + IO_FIFO; + +#ifdef FAST_READ_IO + + FAST_READ2_IO(); +#else + while (i--) + *sp++ = read2_io(IO_FIFO); + +#endif + + i = sp - (unsigned short *)(cmd->SCp.ptr + cmd->SCp.have_data_in); + i <<= 1; + cmd->SCp.have_data_in += i; + } + + else if (hostdata->fifo == FI_FIFO_WRITING) { + +DB(DB_FIFO,printk("{W:%02x} ",read1_io(IO_FIFO_COUNT))) + +/* If all bytes have been written to the fifo, flush out the stragglers. + * Note that while writing 16 dummy words seems arbitrary, we don't + * have another choice that I can see. What we really want is to read + * the 3393 transfer count register (that would tell us how many bytes + * needed flushing), but the TRANSFER_INFO command hasn't completed + * yet (not enough bytes!) and that register won't be accessible. So, + * we use 16 words - a number obtained through trial and error. + * UPDATE: Bill says this is exactly what Always does, so there. + * More thanks due him for help in this section. + */ + + if (cmd->SCp.this_residual == cmd->SCp.have_data_in) { + i = 16; + while (i--) /* write 32 dummy bytes */ + write2_io(0,IO_FIFO); + } + +/* If there are still bytes left in the SCSI buffer, write as many as we + * can out to the fifo. + */ + + else { + sp = (unsigned short *)(cmd->SCp.ptr + cmd->SCp.have_data_in); + i = cmd->SCp.this_residual - cmd->SCp.have_data_in; /* bytes yet to go */ + j = read1_io(IO_FIFO_COUNT) & 0xfe; + j <<= 2; /* how many words the fifo has room for */ + if ((j << 1) > i) + j = (i >> 1); + while (j--) + write2_io(*sp++,IO_FIFO); + + i = sp - (unsigned short *)(cmd->SCp.ptr + cmd->SCp.have_data_in); + i <<= 1; + cmd->SCp.have_data_in += i; + } + } + + else { + printk("*** Spurious FIFO interrupt ***"); + } + + write1_io(0, IO_LED_OFF); + +/* release the SMP spin_lock and restore irq state */ + CLISPIN_UNLOCK(flags); + return; + } + +/* This interrupt was triggered by the WD33c93 chip. The fifo interrupt + * may also be asserted, but we don't bother to check it: we get more + * detailed info from FIFO_READING and FIFO_WRITING (see below). + */ + + cmd = (Scsi_Cmnd *)hostdata->connected; /* assume we're connected */ + sr = read_3393(hostdata,WD_SCSI_STATUS); /* clear the interrupt */ + phs = read_3393(hostdata,WD_COMMAND_PHASE); + + if (!cmd && (sr != CSR_RESEL_AM && sr != CSR_TIMEOUT && sr != CSR_SELECT)) { + printk("\nNR:wd-intr-1\n"); + write1_io(0, IO_LED_OFF); + +/* release the SMP spin_lock and restore irq state */ + CLISPIN_UNLOCK(flags); + return; + } + +DB(DB_INTR,printk("{%02x:%02x-",asr,sr)) + +/* After starting a FIFO-based transfer, the next _WD3393_ interrupt is + * guaranteed to be in response to the completion of the transfer. + * If we were reading, there's probably data in the fifo that needs + * to be copied into RAM - do that here. Also, we have to update + * 'this_residual' and 'ptr' based on the contents of the + * TRANSFER_COUNT register, in case the device decided to do an + * intermediate disconnect (a device may do this if it has to + * do a seek, or just to be nice and let other devices have + * some bus time during long transfers). + * After doing whatever is necessary with the fifo, we go on and + * service the WD3393 interrupt normally. + */ + + if (hostdata->fifo == FI_FIFO_READING) { + +/* buffer index = start-of-buffer + #-of-bytes-already-read */ + + sp = (unsigned short *)(cmd->SCp.ptr + cmd->SCp.have_data_in); + +/* bytes remaining in fifo = (total-wanted - #-not-got) - #-already-read */ + + i = (cmd->SCp.this_residual - read_3393_count(hostdata)) - cmd->SCp.have_data_in; + i >>= 1; /* Gulp. We assume this will always be modulo 2 */ + f = hostdata->io_base + IO_FIFO; + +#ifdef FAST_READ_IO + + FAST_READ2_IO(); +#else + while (i--) + *sp++ = read2_io(IO_FIFO); + +#endif + + hostdata->fifo = FI_FIFO_UNUSED; + length = cmd->SCp.this_residual; + cmd->SCp.this_residual = read_3393_count(hostdata); + cmd->SCp.ptr += (length - cmd->SCp.this_residual); + +DB(DB_TRANSFER,printk("(%p,%d)",cmd->SCp.ptr,cmd->SCp.this_residual)) + + } + + else if (hostdata->fifo == FI_FIFO_WRITING) { + hostdata->fifo = FI_FIFO_UNUSED; + length = cmd->SCp.this_residual; + cmd->SCp.this_residual = read_3393_count(hostdata); + cmd->SCp.ptr += (length - cmd->SCp.this_residual); + +DB(DB_TRANSFER,printk("(%p,%d)",cmd->SCp.ptr,cmd->SCp.this_residual)) + + } + +/* Respond to the specific WD3393 interrupt - there are quite a few! */ + + switch (sr) { + + case CSR_TIMEOUT: +DB(DB_INTR,printk("TIMEOUT")) + + if (hostdata->state == S_RUNNING_LEVEL2) + hostdata->connected = NULL; + else { + cmd = (Scsi_Cmnd *)hostdata->selecting; /* get a valid cmd */ +CHECK_NULL(cmd,"csr_timeout") + hostdata->selecting = NULL; + } + + cmd->result = DID_NO_CONNECT << 16; + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + hostdata->state = S_UNCONNECTED; + cmd->scsi_done(cmd); + +/* We are not connected to a target - check to see if there + * are commands waiting to be executed. + */ + + in2000_execute(instance); + break; + + +/* Note: this interrupt should not occur in a LEVEL2 command */ + + case CSR_SELECT: +DB(DB_INTR,printk("SELECT")) + hostdata->connected = cmd = (Scsi_Cmnd *)hostdata->selecting; +CHECK_NULL(cmd,"csr_select") + hostdata->selecting = NULL; + + /* construct an IDENTIFY message with correct disconnect bit */ + + hostdata->outgoing_msg[0] = (0x80 | 0x00 | cmd->lun); + if (cmd->SCp.phase) + hostdata->outgoing_msg[0] |= 0x40; + + if (hostdata->sync_stat[cmd->target] == SS_FIRST) { +#ifdef SYNC_DEBUG +printk(" sending SDTR "); +#endif + + hostdata->sync_stat[cmd->target] = SS_WAITING; + + /* tack on a 2nd message to ask about synchronous transfers */ + + hostdata->outgoing_msg[1] = EXTENDED_MESSAGE; + hostdata->outgoing_msg[2] = 3; + hostdata->outgoing_msg[3] = EXTENDED_SDTR; + hostdata->outgoing_msg[4] = OPTIMUM_SX_PER/4; + hostdata->outgoing_msg[5] = OPTIMUM_SX_OFF; + hostdata->outgoing_len = 6; + } + else + hostdata->outgoing_len = 1; + + hostdata->state = S_CONNECTED; + break; + + + case CSR_XFER_DONE|PHS_DATA_IN: + case CSR_UNEXP |PHS_DATA_IN: + case CSR_SRV_REQ |PHS_DATA_IN: +DB(DB_INTR,printk("IN-%d.%d",cmd->SCp.this_residual,cmd->SCp.buffers_residual)) + transfer_bytes(cmd, DATA_IN_DIR); + if (hostdata->state != S_RUNNING_LEVEL2) + hostdata->state = S_CONNECTED; + break; + + + case CSR_XFER_DONE|PHS_DATA_OUT: + case CSR_UNEXP |PHS_DATA_OUT: + case CSR_SRV_REQ |PHS_DATA_OUT: +DB(DB_INTR,printk("OUT-%d.%d",cmd->SCp.this_residual,cmd->SCp.buffers_residual)) + transfer_bytes(cmd, DATA_OUT_DIR); + if (hostdata->state != S_RUNNING_LEVEL2) + hostdata->state = S_CONNECTED; + break; + + +/* Note: this interrupt should not occur in a LEVEL2 command */ + + case CSR_XFER_DONE|PHS_COMMAND: + case CSR_UNEXP |PHS_COMMAND: + case CSR_SRV_REQ |PHS_COMMAND: +DB(DB_INTR,printk("CMND-%02x,%ld",cmd->cmnd[0],cmd->pid)) + transfer_pio(cmd->cmnd, cmd->cmd_len, DATA_OUT_DIR, hostdata); + hostdata->state = S_CONNECTED; + break; + + + case CSR_XFER_DONE|PHS_STATUS: + case CSR_UNEXP |PHS_STATUS: + case CSR_SRV_REQ |PHS_STATUS: +DB(DB_INTR,printk("STATUS=")) + + cmd->SCp.Status = read_1_byte(hostdata); +DB(DB_INTR,printk("%02x",cmd->SCp.Status)) + if (hostdata->level2 >= L2_BASIC) { + sr = read_3393(hostdata,WD_SCSI_STATUS); /* clear interrupt */ + hostdata->state = S_RUNNING_LEVEL2; + write_3393(hostdata,WD_COMMAND_PHASE, 0x50); + write_3393_cmd(hostdata,WD_CMD_SEL_ATN_XFER); + } + else { + hostdata->state = S_CONNECTED; + } + break; + + + case CSR_XFER_DONE|PHS_MESS_IN: + case CSR_UNEXP |PHS_MESS_IN: + case CSR_SRV_REQ |PHS_MESS_IN: +DB(DB_INTR,printk("MSG_IN=")) + + msg = read_1_byte(hostdata); + sr = read_3393(hostdata,WD_SCSI_STATUS); /* clear interrupt */ + + hostdata->incoming_msg[hostdata->incoming_ptr] = msg; + if (hostdata->incoming_msg[0] == EXTENDED_MESSAGE) + msg = EXTENDED_MESSAGE; + else + hostdata->incoming_ptr = 0; + + cmd->SCp.Message = msg; + switch (msg) { + + case COMMAND_COMPLETE: +DB(DB_INTR,printk("CCMP-%ld",cmd->pid)) + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_PRE_CMP_DISC; + break; + + case SAVE_POINTERS: +DB(DB_INTR,printk("SDP")) + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + break; + + case RESTORE_POINTERS: +DB(DB_INTR,printk("RDP")) + if (hostdata->level2 >= L2_BASIC) { + write_3393(hostdata,WD_COMMAND_PHASE, 0x45); + write_3393_cmd(hostdata,WD_CMD_SEL_ATN_XFER); + hostdata->state = S_RUNNING_LEVEL2; + } + else { + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + } + break; + + case DISCONNECT: +DB(DB_INTR,printk("DIS")) + cmd->device->disconnect = 1; + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_PRE_TMP_DISC; + break; + + case MESSAGE_REJECT: +DB(DB_INTR,printk("REJ")) +#ifdef SYNC_DEBUG +printk("-REJ-"); +#endif + if (hostdata->sync_stat[cmd->target] == SS_WAITING) + hostdata->sync_stat[cmd->target] = SS_SET; + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + break; + + case EXTENDED_MESSAGE: +DB(DB_INTR,printk("EXT")) + + ucp = hostdata->incoming_msg; + +#ifdef SYNC_DEBUG +printk("%02x",ucp[hostdata->incoming_ptr]); +#endif + /* Is this the last byte of the extended message? */ + + if ((hostdata->incoming_ptr >= 2) && + (hostdata->incoming_ptr == (ucp[1] + 1))) { + + switch (ucp[2]) { /* what's the EXTENDED code? */ + case EXTENDED_SDTR: + id = calc_sync_xfer(ucp[3],ucp[4]); + if (hostdata->sync_stat[cmd->target] != SS_WAITING) { + +/* A device has sent an unsolicited SDTR message; rather than go + * through the effort of decoding it and then figuring out what + * our reply should be, we're just gonna say that we have a + * synchronous fifo depth of 0. This will result in asynchronous + * transfers - not ideal but so much easier. + * Actually, this is OK because it assures us that if we don't + * specifically ask for sync transfers, we won't do any. + */ + + write_3393_cmd(hostdata,WD_CMD_ASSERT_ATN); /* want MESS_OUT */ + hostdata->outgoing_msg[0] = EXTENDED_MESSAGE; + hostdata->outgoing_msg[1] = 3; + hostdata->outgoing_msg[2] = EXTENDED_SDTR; + hostdata->outgoing_msg[3] = hostdata->default_sx_per/4; + hostdata->outgoing_msg[4] = 0; + hostdata->outgoing_len = 5; + hostdata->sync_xfer[cmd->target] = + calc_sync_xfer(hostdata->default_sx_per/4,0); + } + else { + hostdata->sync_xfer[cmd->target] = id; + } +#ifdef SYNC_DEBUG +printk("sync_xfer=%02x",hostdata->sync_xfer[cmd->target]); +#endif + hostdata->sync_stat[cmd->target] = SS_SET; + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + break; + case EXTENDED_WDTR: + write_3393_cmd(hostdata,WD_CMD_ASSERT_ATN); /* want MESS_OUT */ + printk("sending WDTR "); + hostdata->outgoing_msg[0] = EXTENDED_MESSAGE; + hostdata->outgoing_msg[1] = 2; + hostdata->outgoing_msg[2] = EXTENDED_WDTR; + hostdata->outgoing_msg[3] = 0; /* 8 bit transfer width */ + hostdata->outgoing_len = 4; + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + break; + default: + write_3393_cmd(hostdata,WD_CMD_ASSERT_ATN); /* want MESS_OUT */ + printk("Rejecting Unknown Extended Message(%02x). ",ucp[2]); + hostdata->outgoing_msg[0] = MESSAGE_REJECT; + hostdata->outgoing_len = 1; + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + break; + } + hostdata->incoming_ptr = 0; + } + + /* We need to read more MESS_IN bytes for the extended message */ + + else { + hostdata->incoming_ptr++; + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + } + break; + + default: + printk("Rejecting Unknown Message(%02x) ",msg); + write_3393_cmd(hostdata,WD_CMD_ASSERT_ATN); /* want MESS_OUT */ + hostdata->outgoing_msg[0] = MESSAGE_REJECT; + hostdata->outgoing_len = 1; + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + hostdata->state = S_CONNECTED; + } + break; + + +/* Note: this interrupt will occur only after a LEVEL2 command */ + + case CSR_SEL_XFER_DONE: + +/* Make sure that reselection is enabled at this point - it may + * have been turned off for the command that just completed. + */ + + write_3393(hostdata,WD_SOURCE_ID, SRCID_ER); + if (phs == 0x60) { +DB(DB_INTR,printk("SX-DONE-%ld",cmd->pid)) + cmd->SCp.Message = COMMAND_COMPLETE; + lun = read_3393(hostdata,WD_TARGET_LUN); +DB(DB_INTR,printk(":%d.%d",cmd->SCp.Status,lun)) + hostdata->connected = NULL; + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + hostdata->state = S_UNCONNECTED; + if (cmd->SCp.Status == ILLEGAL_STATUS_BYTE) + cmd->SCp.Status = lun; + if (cmd->cmnd[0] == REQUEST_SENSE && cmd->SCp.Status != GOOD) + cmd->result = (cmd->result & 0x00ffff) | (DID_ERROR << 16); + else + cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8); + cmd->scsi_done(cmd); + +/* We are no longer connected to a target - check to see if + * there are commands waiting to be executed. + */ + + in2000_execute(instance); + } + else { + printk("%02x:%02x:%02x-%ld: Unknown SEL_XFER_DONE phase!!---",asr,sr,phs,cmd->pid); + } + break; + + +/* Note: this interrupt will occur only after a LEVEL2 command */ + + case CSR_SDP: +DB(DB_INTR,printk("SDP")) + hostdata->state = S_RUNNING_LEVEL2; + write_3393(hostdata,WD_COMMAND_PHASE, 0x41); + write_3393_cmd(hostdata,WD_CMD_SEL_ATN_XFER); + break; + + + case CSR_XFER_DONE|PHS_MESS_OUT: + case CSR_UNEXP |PHS_MESS_OUT: + case CSR_SRV_REQ |PHS_MESS_OUT: +DB(DB_INTR,printk("MSG_OUT=")) + +/* To get here, we've probably requested MESSAGE_OUT and have + * already put the correct bytes in outgoing_msg[] and filled + * in outgoing_len. We simply send them out to the SCSI bus. + * Sometimes we get MESSAGE_OUT phase when we're not expecting + * it - like when our SDTR message is rejected by a target. Some + * targets send the REJECT before receiving all of the extended + * message, and then seem to go back to MESSAGE_OUT for a byte + * or two. Not sure why, or if I'm doing something wrong to + * cause this to happen. Regardless, it seems that sending + * NOP messages in these situations results in no harm and + * makes everyone happy. + */ + + if (hostdata->outgoing_len == 0) { + hostdata->outgoing_len = 1; + hostdata->outgoing_msg[0] = NOP; + } + transfer_pio(hostdata->outgoing_msg, hostdata->outgoing_len, + DATA_OUT_DIR, hostdata); +DB(DB_INTR,printk("%02x",hostdata->outgoing_msg[0])) + hostdata->outgoing_len = 0; + hostdata->state = S_CONNECTED; + break; + + + case CSR_UNEXP_DISC: + +/* I think I've seen this after a request-sense that was in response + * to an error condition, but not sure. We certainly need to do + * something when we get this interrupt - the question is 'what?'. + * Let's think positively, and assume some command has finished + * in a legal manner (like a command that provokes a request-sense), + * so we treat it as a normal command-complete-disconnect. + */ + + +/* Make sure that reselection is enabled at this point - it may + * have been turned off for the command that just completed. + */ + + write_3393(hostdata,WD_SOURCE_ID, SRCID_ER); + if (cmd == NULL) { + printk(" - Already disconnected! "); + hostdata->state = S_UNCONNECTED; + +/* release the SMP spin_lock and restore irq state */ + CLISPIN_UNLOCK(flags); + return; + } +DB(DB_INTR,printk("UNEXP_DISC-%ld",cmd->pid)) + hostdata->connected = NULL; + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + hostdata->state = S_UNCONNECTED; + if (cmd->cmnd[0] == REQUEST_SENSE && cmd->SCp.Status != GOOD) + cmd->result = (cmd->result & 0x00ffff) | (DID_ERROR << 16); + else + cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8); + cmd->scsi_done(cmd); + +/* We are no longer connected to a target - check to see if + * there are commands waiting to be executed. + */ + + in2000_execute(instance); + break; + + + case CSR_DISC: + +/* Make sure that reselection is enabled at this point - it may + * have been turned off for the command that just completed. + */ + + write_3393(hostdata,WD_SOURCE_ID, SRCID_ER); +DB(DB_INTR,printk("DISC-%ld",cmd->pid)) + if (cmd == NULL) { + printk(" - Already disconnected! "); + hostdata->state = S_UNCONNECTED; + } + switch (hostdata->state) { + case S_PRE_CMP_DISC: + hostdata->connected = NULL; + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + hostdata->state = S_UNCONNECTED; +DB(DB_INTR,printk(":%d",cmd->SCp.Status)) + if (cmd->cmnd[0] == REQUEST_SENSE && cmd->SCp.Status != GOOD) + cmd->result = (cmd->result & 0x00ffff) | (DID_ERROR << 16); + else + cmd->result = cmd->SCp.Status | (cmd->SCp.Message << 8); + cmd->scsi_done(cmd); + break; + case S_PRE_TMP_DISC: + case S_RUNNING_LEVEL2: + cmd->host_scribble = (uchar *)hostdata->disconnected_Q; + hostdata->disconnected_Q = cmd; + hostdata->connected = NULL; + hostdata->state = S_UNCONNECTED; + +#ifdef PROC_STATISTICS + hostdata->disc_done_cnt[cmd->target]++; +#endif + + break; + default: + printk("*** Unexpected DISCONNECT interrupt! ***"); + hostdata->state = S_UNCONNECTED; + } + +/* We are no longer connected to a target - check to see if + * there are commands waiting to be executed. + */ + + in2000_execute(instance); + break; + + + case CSR_RESEL_AM: +DB(DB_INTR,printk("RESEL")) + + /* First we have to make sure this reselection didn't */ + /* happen during Arbitration/Selection of some other device. */ + /* If yes, put losing command back on top of input_Q. */ + + if (hostdata->level2 <= L2_NONE) { + + if (hostdata->selecting) { + cmd = (Scsi_Cmnd *)hostdata->selecting; + hostdata->selecting = NULL; + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + cmd->host_scribble = (uchar *)hostdata->input_Q; + hostdata->input_Q = cmd; + } + } + + else { + + if (cmd) { + if (phs == 0x00) { + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + cmd->host_scribble = (uchar *)hostdata->input_Q; + hostdata->input_Q = cmd; + } + else { + printk("---%02x:%02x:%02x-TROUBLE: Intrusive ReSelect!---",asr,sr,phs); + while (1) + printk("\r"); + } + } + + } + + /* OK - find out which device reselected us. */ + + id = read_3393(hostdata,WD_SOURCE_ID); + id &= SRCID_MASK; + + /* and extract the lun from the ID message. (Note that we don't + * bother to check for a valid message here - I guess this is + * not the right way to go, but....) + */ + + lun = read_3393(hostdata,WD_DATA); + if (hostdata->level2 < L2_RESELECT) + write_3393_cmd(hostdata,WD_CMD_NEGATE_ACK); + lun &= 7; + + /* Now we look for the command that's reconnecting. */ + + cmd = (Scsi_Cmnd *)hostdata->disconnected_Q; + patch = NULL; + while (cmd) { + if (id == cmd->target && lun == cmd->lun) + break; + patch = cmd; + cmd = (Scsi_Cmnd *)cmd->host_scribble; + } + + /* Hmm. Couldn't find a valid command.... What to do? */ + + if (!cmd) { + printk("---TROUBLE: target %d.%d not in disconnect queue---",id,lun); + break; + } + + /* Ok, found the command - now start it up again. */ + + if (patch) + patch->host_scribble = cmd->host_scribble; + else + hostdata->disconnected_Q = (Scsi_Cmnd *)cmd->host_scribble; + hostdata->connected = cmd; + + /* We don't need to worry about 'initialize_SCp()' or 'hostdata->busy[]' + * because these things are preserved over a disconnect. + * But we DO need to fix the DPD bit so it's correct for this command. + */ + + if (is_dir_out(cmd)) + write_3393(hostdata,WD_DESTINATION_ID,cmd->target); + else + write_3393(hostdata,WD_DESTINATION_ID,cmd->target | DSTID_DPD); + if (hostdata->level2 >= L2_RESELECT) { + write_3393_count(hostdata,0); /* we want a DATA_PHASE interrupt */ + write_3393(hostdata,WD_COMMAND_PHASE, 0x45); + write_3393_cmd(hostdata,WD_CMD_SEL_ATN_XFER); + hostdata->state = S_RUNNING_LEVEL2; + } + else + hostdata->state = S_CONNECTED; + +DB(DB_INTR,printk("-%ld",cmd->pid)) + break; + + default: + printk("--UNKNOWN INTERRUPT:%02x:%02x:%02x--",asr,sr,phs); + } + + write1_io(0, IO_LED_OFF); + +DB(DB_INTR,printk("} ")) + +/* release the SMP spin_lock and restore irq state */ + CLISPIN_UNLOCK(flags); + +} + + + +#define RESET_CARD 0 +#define RESET_CARD_AND_BUS 1 +#define B_FLAG 0x80 + +static int reset_hardware(struct Scsi_Host *instance, int type) +{ +struct IN2000_hostdata *hostdata; +int qt,x; +unsigned long flags; + + hostdata = (struct IN2000_hostdata *)instance->hostdata; + + write1_io(0, IO_LED_ON); + if (type == RESET_CARD_AND_BUS) { + write1_io(0,IO_CARD_RESET); + x = read1_io(IO_HARDWARE); + } + x = read_3393(hostdata,WD_SCSI_STATUS); /* clear any WD intrpt */ + write_3393(hostdata,WD_OWN_ID, instance->this_id | + OWNID_EAF | OWNID_RAF | OWNID_FS_8); + write_3393(hostdata,WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_POLLED); + write_3393(hostdata,WD_SYNCHRONOUS_TRANSFER, + calc_sync_xfer(hostdata->default_sx_per/4,DEFAULT_SX_OFF)); + save_flags(flags); + cli(); + write1_io(0,IO_FIFO_WRITE); /* clear fifo counter */ + write1_io(0,IO_FIFO_READ); /* start fifo out in read mode */ + write_3393(hostdata,WD_COMMAND, WD_CMD_RESET); + while (!(READ_AUX_STAT() & ASR_INT)) + ; /* wait for RESET to complete */ + + x = read_3393(hostdata,WD_SCSI_STATUS); /* clear interrupt */ + restore_flags(flags); + write_3393(hostdata,WD_QUEUE_TAG,0xa5); /* any random number */ + qt = read_3393(hostdata,WD_QUEUE_TAG); + if (qt == 0xa5) { + x |= B_FLAG; + write_3393(hostdata,WD_QUEUE_TAG,0); + } + write_3393(hostdata,WD_TIMEOUT_PERIOD, TIMEOUT_PERIOD_VALUE); + write_3393(hostdata,WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_POLLED); + write1_io(0, IO_LED_OFF); + return x; +} + + + +int in2000_reset(Scsi_Cmnd *cmd, unsigned int reset_flags) +{ +unsigned long flags; +struct Scsi_Host *instance; +struct IN2000_hostdata *hostdata; +int x; + + instance = cmd->host; + hostdata = (struct IN2000_hostdata *)instance->hostdata; + + printk("scsi%d: Reset. ", instance->host_no); + save_flags(flags); + cli(); + + /* do scsi-reset here */ + + reset_hardware(instance, RESET_CARD_AND_BUS); + for (x = 0; x < 8; x++) { + hostdata->busy[x] = 0; + hostdata->sync_xfer[x] = calc_sync_xfer(DEFAULT_SX_PER/4,DEFAULT_SX_OFF); + hostdata->sync_stat[x] = SS_UNSET; /* using default sync values */ + } + hostdata->input_Q = NULL; + hostdata->selecting = NULL; + hostdata->connected = NULL; + hostdata->disconnected_Q = NULL; + hostdata->state = S_UNCONNECTED; + hostdata->fifo = FI_FIFO_UNUSED; + hostdata->incoming_ptr = 0; + hostdata->outgoing_len = 0; + + cmd->result = DID_RESET << 16; + restore_flags(flags); + return 0; +} + + + +int in2000_abort (Scsi_Cmnd *cmd) +{ +struct Scsi_Host *instance; +struct IN2000_hostdata *hostdata; +Scsi_Cmnd *tmp, *prev; +unsigned long flags; +uchar sr, asr; +unsigned long timeout; + + save_flags (flags); + cli(); + + instance = cmd->host; + hostdata = (struct IN2000_hostdata *)instance->hostdata; + + printk ("scsi%d: Abort-", instance->host_no); + printk("(asr=%02x,count=%ld,resid=%d,buf_resid=%d,have_data=%d,FC=%02x)- ", + READ_AUX_STAT(),read_3393_count(hostdata),cmd->SCp.this_residual,cmd->SCp.buffers_residual, + cmd->SCp.have_data_in,read1_io(IO_FIFO_COUNT)); + +/* + * Case 1 : If the command hasn't been issued yet, we simply remove it + * from the inout_Q. + */ + + tmp = (Scsi_Cmnd *)hostdata->input_Q; + prev = 0; + while (tmp) { + if (tmp == cmd) { + if (prev) + prev->host_scribble = cmd->host_scribble; + cmd->host_scribble = NULL; + cmd->result = DID_ABORT << 16; + printk("scsi%d: Abort - removing command %ld from input_Q. ", + instance->host_no, cmd->pid); + cmd->scsi_done(cmd); + restore_flags(flags); + return SCSI_ABORT_SUCCESS; + } + prev = tmp; + tmp = (Scsi_Cmnd *)tmp->host_scribble; + } + +/* + * Case 2 : If the command is connected, we're going to fail the abort + * and let the high level SCSI driver retry at a later time or + * issue a reset. + * + * Timeouts, and therefore aborted commands, will be highly unlikely + * and handling them cleanly in this situation would make the common + * case of noresets less efficient, and would pollute our code. So, + * we fail. + */ + + if (hostdata->connected == cmd) { + + printk("scsi%d: Aborting connected command %ld - ", + instance->host_no, cmd->pid); + + printk("sending wd33c93 ABORT command - "); + write_3393(hostdata, WD_CONTROL, CTRL_IDI | CTRL_EDI | CTRL_POLLED); + write_3393_cmd(hostdata, WD_CMD_ABORT); + +/* Now we have to attempt to flush out the FIFO... */ + + printk("flushing fifo - "); + timeout = 1000000; + do { + asr = READ_AUX_STAT(); + if (asr & ASR_DBR) + read_3393(hostdata, WD_DATA); + } while (!(asr & ASR_INT) && timeout-- > 0); + sr = read_3393(hostdata, WD_SCSI_STATUS); + printk("asr=%02x, sr=%02x, %ld bytes un-transferred (timeout=%ld) - ", + asr, sr, read_3393_count(hostdata), timeout); + + /* + * Abort command processed. + * Still connected. + * We must disconnect. + */ + + printk("sending wd33c93 DISCONNECT command - "); + write_3393_cmd(hostdata, WD_CMD_DISCONNECT); + + timeout = 1000000; + asr = READ_AUX_STAT(); + while ((asr & ASR_CIP) && timeout-- > 0) + asr = READ_AUX_STAT(); + sr = read_3393(hostdata, WD_SCSI_STATUS); + printk("asr=%02x, sr=%02x.",asr,sr); + + hostdata->busy[cmd->target] &= ~(1 << cmd->lun); + hostdata->connected = NULL; + hostdata->state = S_UNCONNECTED; + cmd->result = DID_ABORT << 16; + cmd->scsi_done(cmd); + + in2000_execute (instance); + + restore_flags(flags); + return SCSI_ABORT_SUCCESS; + } + +/* + * Case 3: If the command is currently disconnected from the bus, + * we're not going to expend much effort here: Let's just return + * an ABORT_SNOOZE and hope for the best... + */ + + for (tmp=(Scsi_Cmnd *)hostdata->disconnected_Q; tmp; + tmp=(Scsi_Cmnd *)tmp->host_scribble) + if (cmd == tmp) { + restore_flags(flags); + printk("Sending ABORT_SNOOZE. "); + return SCSI_ABORT_SNOOZE; + } + +/* + * Case 4 : If we reached this point, the command was not found in any of + * the queues. + * + * We probably reached this point because of an unlikely race condition + * between the command completing successfully and the abortion code, + * so we won't panic, but we will notify the user in case something really + * broke. + */ + + in2000_execute (instance); + + restore_flags(flags); + printk("scsi%d: warning : SCSI command probably completed successfully" + " before abortion. ", instance->host_no); + return SCSI_ABORT_NOT_RUNNING; +} + + + +#define MAX_IN2000_HOSTS 3 +#define MAX_SETUP_ARGS (sizeof(setup_args) / sizeof(char *)) +#define SETUP_BUFFER_SIZE 200 +static char setup_buffer[SETUP_BUFFER_SIZE]; +static char setup_used[MAX_SETUP_ARGS]; +static int done_setup = 0; + +in2000__INITFUNC( void in2000_setup (char *str, int *ints) ) +{ +int i; +char *p1,*p2; + + strncpy(setup_buffer,str,SETUP_BUFFER_SIZE); + setup_buffer[SETUP_BUFFER_SIZE - 1] = '\0'; + p1 = setup_buffer; + i = 0; + while (*p1 && (i < MAX_SETUP_ARGS)) { + p2 = strchr(p1, ','); + if (p2) { + *p2 = '\0'; + if (p1 != p2) + setup_args[i] = p1; + p1 = p2 + 1; + i++; + } + else { + setup_args[i] = p1; + break; + } + } + for (i=0; i<MAX_SETUP_ARGS; i++) + setup_used[i] = 0; + done_setup = 1; +} + + +/* check_setup_args() returns index if key found, 0 if not + */ + +in2000__INITFUNC( static int check_setup_args(char *key, int *flags, int *val, char *buf) ) +{ +int x; +char *cp; + + for (x=0; x<MAX_SETUP_ARGS; x++) { + if (setup_used[x]) + continue; + if (!strncmp(setup_args[x], key, strlen(key))) + break; + } + if (x == MAX_SETUP_ARGS) + return 0; + setup_used[x] = 1; + cp = setup_args[x] + strlen(key); + *val = -1; + if (*cp != ':') + return ++x; + cp++; + if ((*cp >= '0') && (*cp <= '9')) { + *val = simple_strtoul(cp,NULL,0); + } + return ++x; +} + + + +/* The "correct" (ie portable) way to access memory-mapped hardware + * such as the IN2000 EPROM and dip switch is through the use of + * special macros declared in 'asm/io.h'. We use readb() and readl() + * when reading from the card's BIOS area in in2000_detect(). + */ +static const unsigned int *bios_tab[] in2000__INITDATA = { + (unsigned int *)0xc8000, + (unsigned int *)0xd0000, + (unsigned int *)0xd8000, + 0 + }; + +static const unsigned short base_tab[] in2000__INITDATA = { + 0x220, + 0x200, + 0x110, + 0x100, + }; + +static const int int_tab[] in2000__INITDATA = { + 15, + 14, + 11, + 10 + }; + + +in2000__INITFUNC( int in2000_detect(Scsi_Host_Template * tpnt) ) +{ +struct Scsi_Host *instance; +struct IN2000_hostdata *hostdata; +int detect_count; +int bios; +int x; +unsigned short base; +uchar switches; +uchar hrev; +int flags; +int val; +char buf[32]; + +/* Thanks to help from Bill Earnest, probing for IN2000 cards is a + * pretty straightforward and fool-proof operation. There are 3 + * possible locations for the IN2000 EPROM in memory space - if we + * find a BIOS signature, we can read the dip switch settings from + * the byte at BIOS+32 (shadowed in by logic on the card). From 2 + * of the switch bits we get the card's address in IO space. There's + * an image of the dip switch there, also, so we have a way to back- + * check that this really is an IN2000 card. Very nifty. Use the + * 'ioport:xx' command-line parameter if your BIOS EPROM is absent + * or disabled. + */ + + if (!done_setup && setup_strings) + in2000_setup(setup_strings,0); + + detect_count = 0; + for (bios = 0; bios_tab[bios]; bios++) { + if (check_setup_args("ioport",&flags,&val,buf)) { + base = val; + switches = ~inb(base + IO_SWITCHES) & 0xff; + printk("Forcing IN2000 detection at IOport 0x%x ",base); + bios = 2; + } +/* + * There have been a couple of BIOS versions with different layouts + * for the obvious ID strings. We look for the 2 most common ones and + * hope that they cover all the cases... + */ + else if (readl(bios_tab[bios]+0x04) == 0x41564f4e || + readl(bios_tab[bios]+0x0c) == 0x61776c41) { + printk("Found IN2000 BIOS at 0x%x ",(unsigned int)bios_tab[bios]); + +/* Read the switch image that's mapped into EPROM space */ + + switches = ~((readb(bios_tab[bios]+0x08) & 0xff)); + +/* Find out where the IO space is */ + + x = switches & (SW_ADDR0 | SW_ADDR1); + base = base_tab[x]; + +/* Check for the IN2000 signature in IO space. */ + + x = ~inb(base + IO_SWITCHES) & 0xff; + if (x != switches) { + printk("Bad IO signature: %02x vs %02x.\n",x,switches); + continue; + } + } + else + continue; + +/* OK. We have a base address for the IO ports - run a few safety checks */ + + if (!(switches & SW_BIT7)) { /* I _think_ all cards do this */ + printk("There is no IN-2000 SCSI card at IOport 0x%03x!\n",base); + continue; + } + +/* Let's assume any hardware version will work, although the driver + * has only been tested on 0x21, 0x22, 0x25, 0x26, and 0x27. We'll + * print out the rev number for reference later, but accept them all. + */ + + hrev = inb(base + IO_HARDWARE); + + /* Bit 2 tells us if interrupts are disabled */ + if (switches & SW_DISINT) { + printk("The IN-2000 SCSI card at IOport 0x%03x ",base); + printk("is not configured for interrupt operation!\n"); + printk("This driver requires an interrupt: cancelling detection.\n"); + continue; + } + +/* Ok. We accept that there's an IN2000 at ioaddr 'base'. Now + * initialize it. + */ + + tpnt->proc_dir = &proc_scsi_in2000; /* done more than once? harmless. */ + detect_count++; + instance = scsi_register(tpnt, sizeof(struct IN2000_hostdata)); + if (!instance_list) + instance_list = instance; + hostdata = (struct IN2000_hostdata *)instance->hostdata; + instance->io_port = hostdata->io_base = base; + hostdata->dip_switch = switches; + hostdata->hrev = hrev; + + write1_io(0,IO_FIFO_WRITE); /* clear fifo counter */ + write1_io(0,IO_FIFO_READ); /* start fifo out in read mode */ + write1_io(0,IO_INTR_MASK); /* allow all ints */ + x = int_tab[(switches & (SW_INT0 | SW_INT1)) >> SW_INT_SHIFT]; + if (request_irq(x, in2000_intr, SA_INTERRUPT, "in2000", NULL)) { + printk("in2000_detect: Unable to allocate IRQ.\n"); + detect_count--; + continue; + } + instance->irq = x; + instance->n_io_port = 13; + request_region(base, 13, "in2000"); /* lock in this IO space for our use */ + + for (x = 0; x < 8; x++) { + hostdata->busy[x] = 0; + hostdata->sync_xfer[x] = calc_sync_xfer(DEFAULT_SX_PER/4,DEFAULT_SX_OFF); + hostdata->sync_stat[x] = SS_UNSET; /* using default sync values */ +#ifdef PROC_STATISTICS + hostdata->cmd_cnt[x] = 0; + hostdata->disc_allowed_cnt[x] = 0; + hostdata->disc_done_cnt[x] = 0; +#endif + } + hostdata->input_Q = NULL; + hostdata->selecting = NULL; + hostdata->connected = NULL; + hostdata->disconnected_Q = NULL; + hostdata->state = S_UNCONNECTED; + hostdata->fifo = FI_FIFO_UNUSED; + hostdata->level2 = L2_BASIC; + hostdata->disconnect = DIS_ADAPTIVE; + hostdata->args = DEBUG_DEFAULTS; + hostdata->incoming_ptr = 0; + hostdata->outgoing_len = 0; + hostdata->default_sx_per = DEFAULT_SX_PER; + +/* Older BIOS's had a 'sync on/off' switch - use its setting */ + + if (readl(bios_tab[bios]+0x04) == 0x41564f4e && (switches & SW_SYNC_DOS5)) + hostdata->sync_off = 0x00; /* sync defaults to on */ + else + hostdata->sync_off = 0xff; /* sync defaults to off */ + +#ifdef PROC_INTERFACE + hostdata->proc = PR_VERSION|PR_INFO|PR_STATISTICS| + PR_CONNECTED|PR_INPUTQ|PR_DISCQ| + PR_STOP; +#ifdef PROC_STATISTICS + hostdata->int_cnt = 0; +#endif +#endif + + if (check_setup_args("nosync",&flags,&val,buf)) + hostdata->sync_off = val; + + if (check_setup_args("period",&flags,&val,buf)) + hostdata->default_sx_per = sx_table[round_period((unsigned int)val)].period_ns; + + if (check_setup_args("disconnect",&flags,&val,buf)) { + if ((val >= DIS_NEVER) && (val <= DIS_ALWAYS)) + hostdata->disconnect = val; + else + hostdata->disconnect = DIS_ADAPTIVE; + } + + if (check_setup_args("noreset",&flags,&val,buf)) + hostdata->args ^= A_NO_SCSI_RESET; + + if (check_setup_args("level2",&flags,&val,buf)) + hostdata->level2 = val; + + if (check_setup_args("debug",&flags,&val,buf)) + hostdata->args = (val & DB_MASK); + +#ifdef PROC_INTERFACE + if (check_setup_args("proc",&flags,&val,buf)) + hostdata->proc = val; +#endif + + + x = reset_hardware(instance,(hostdata->args & A_NO_SCSI_RESET)?RESET_CARD:RESET_CARD_AND_BUS); + + hostdata->microcode = read_3393(hostdata,WD_CDB_1); + if (x & 0x01) { + if (x & B_FLAG) + hostdata->chip = C_WD33C93B; + else + hostdata->chip = C_WD33C93A; + } + else + hostdata->chip = C_WD33C93; + + printk("dip_switch=%02x irq=%d ioport=%02x floppy=%s sync/DOS5=%s ", + (switches & 0x7f), + instance->irq,hostdata->io_base, + (switches & SW_FLOPPY)?"Yes":"No", + (switches & SW_SYNC_DOS5)?"Yes":"No"); + printk("hardware_ver=%02x chip=%s microcode=%02x\n", + hrev, + (hostdata->chip==C_WD33C93)?"WD33c93": + (hostdata->chip==C_WD33C93A)?"WD33c93A": + (hostdata->chip==C_WD33C93B)?"WD33c93B":"unknown", + hostdata->microcode); +#ifdef DEBUGGING_ON + printk("setup_args = "); + for (x=0; x<MAX_SETUP_ARGS; x++) + printk("%s,",setup_args[x]); + printk("\n"); +#endif + if (hostdata->sync_off == 0xff) + printk("Sync-transfer DISABLED on all devices: ENABLE from command-line\n"); + printk("IN2000 driver version %s - %s\n",IN2000_VERSION,IN2000_DATE); + } + + return detect_count; +} + + +/* NOTE: I lifted this function straight out of the old driver, + * and have not tested it. Presumably it does what it's + * supposed to do... + */ + +int in2000_biosparam(Disk *disk, kdev_t dev, int *iinfo) +{ +int size; + + size = disk->capacity; + iinfo[0] = 64; + iinfo[1] = 32; + iinfo[2] = size >> 11; + +/* This should approximate the large drive handling that the DOS ASPI manager + uses. Drives very near the boundaries may not be handled correctly (i.e. + near 2.0 Gb and 4.0 Gb) */ + + if (iinfo[2] > 1024) { + iinfo[0] = 64; + iinfo[1] = 63; + iinfo[2] = disk->capacity / (iinfo[0] * iinfo[1]); + } + if (iinfo[2] > 1024) { + iinfo[0] = 128; + iinfo[1] = 63; + iinfo[2] = disk->capacity / (iinfo[0] * iinfo[1]); + } + if (iinfo[2] > 1024) { + iinfo[0] = 255; + iinfo[1] = 63; + iinfo[2] = disk->capacity / (iinfo[0] * iinfo[1]); + } + return 0; +} + + + +struct proc_dir_entry proc_scsi_in2000 = { + PROC_SCSI_IN2000, 6, "in2000", + S_IFDIR | S_IRUGO | S_IXUGO, 2 + }; + + +int in2000_proc_info(char *buf, char **start, off_t off, int len, int hn, int in) +{ + +#ifdef PROC_INTERFACE + +char *bp; +char tbuf[128]; +unsigned long flags; +struct Scsi_Host *instance; +struct IN2000_hostdata *hd; +Scsi_Cmnd *cmd; +int x,i; +static int stop = 0; + + for (instance=instance_list; instance; instance=instance->next) { + if (instance->host_no == hn) + break; + } + if (!instance) { + printk("*** Hmm... Can't find host #%d!\n",hn); + return (-ESRCH); + } + hd = (struct IN2000_hostdata *)instance->hostdata; + +/* If 'in' is TRUE we need to _read_ the proc file. We accept the following + * keywords (same format as command-line, but only ONE per read): + * debug + * disconnect + * period + * resync + * proc + */ + + if (in) { + buf[len] = '\0'; + bp = buf; + if (!strncmp(bp,"debug:",6)) { + bp += 6; + hd->args = simple_strtoul(bp,NULL,0) & DB_MASK; + } + else if (!strncmp(bp,"disconnect:",11)) { + bp += 11; + x = simple_strtoul(bp,NULL,0); + if (x < DIS_NEVER || x > DIS_ALWAYS) + x = DIS_ADAPTIVE; + hd->disconnect = x; + } + else if (!strncmp(bp,"period:",7)) { + bp += 7; + x = simple_strtoul(bp,NULL,0); + hd->default_sx_per = sx_table[round_period((unsigned int)x)].period_ns; + } + else if (!strncmp(bp,"resync:",7)) { + bp += 7; + x = simple_strtoul(bp,NULL,0); + for (i=0; i<7; i++) + if (x & (1<<i)) + hd->sync_stat[i] = SS_UNSET; + } + else if (!strncmp(bp,"proc:",5)) { + bp += 5; + hd->proc = simple_strtoul(bp,NULL,0); + } + else if (!strncmp(bp,"level2:",7)) { + bp += 7; + hd->level2 = simple_strtoul(bp,NULL,0); + } + return len; + } + + save_flags(flags); + cli(); + bp = buf; + *bp = '\0'; + if (hd->proc & PR_VERSION) { + sprintf(tbuf,"\nVersion %s - %s. Compiled %s %s", + IN2000_VERSION,IN2000_DATE,__DATE__,__TIME__); + strcat(bp,tbuf); + } + if (hd->proc & PR_INFO) { + sprintf(tbuf,"\ndip_switch=%02x: irq=%d io=%02x floppy=%s sync/DOS5=%s", + (hd->dip_switch & 0x7f), instance->irq, hd->io_base, + (hd->dip_switch & 0x40)?"Yes":"No", + (hd->dip_switch & 0x20)?"Yes":"No"); + strcat(bp,tbuf); + strcat(bp,"\nsync_xfer[] = "); + for (x=0; x<7; x++) { + sprintf(tbuf,"\t%02x",hd->sync_xfer[x]); + strcat(bp,tbuf); + } + strcat(bp,"\nsync_stat[] = "); + for (x=0; x<7; x++) { + sprintf(tbuf,"\t%02x",hd->sync_stat[x]); + strcat(bp,tbuf); + } + } +#ifdef PROC_STATISTICS + if (hd->proc & PR_STATISTICS) { + strcat(bp,"\ncommands issued: "); + for (x=0; x<7; x++) { + sprintf(tbuf,"\t%ld",hd->cmd_cnt[x]); + strcat(bp,tbuf); + } + strcat(bp,"\ndisconnects allowed:"); + for (x=0; x<7; x++) { + sprintf(tbuf,"\t%ld",hd->disc_allowed_cnt[x]); + strcat(bp,tbuf); + } + strcat(bp,"\ndisconnects done: "); + for (x=0; x<7; x++) { + sprintf(tbuf,"\t%ld",hd->disc_done_cnt[x]); + strcat(bp,tbuf); + } + sprintf(tbuf,"\ninterrupts: \t%ld",hd->int_cnt); + strcat(bp,tbuf); + } +#endif + if (hd->proc & PR_CONNECTED) { + strcat(bp,"\nconnected: "); + if (hd->connected) { + cmd = (Scsi_Cmnd *)hd->connected; + sprintf(tbuf," %ld-%d:%d(%02x)", + cmd->pid, cmd->target, cmd->lun, cmd->cmnd[0]); + strcat(bp,tbuf); + } + } + if (hd->proc & PR_INPUTQ) { + strcat(bp,"\ninput_Q: "); + cmd = (Scsi_Cmnd *)hd->input_Q; + while (cmd) { + sprintf(tbuf," %ld-%d:%d(%02x)", + cmd->pid, cmd->target, cmd->lun, cmd->cmnd[0]); + strcat(bp,tbuf); + cmd = (Scsi_Cmnd *)cmd->host_scribble; + } + } + if (hd->proc & PR_DISCQ) { + strcat(bp,"\ndisconnected_Q:"); + cmd = (Scsi_Cmnd *)hd->disconnected_Q; + while (cmd) { + sprintf(tbuf," %ld-%d:%d(%02x)", + cmd->pid, cmd->target, cmd->lun, cmd->cmnd[0]); + strcat(bp,tbuf); + cmd = (Scsi_Cmnd *)cmd->host_scribble; + } + } + if (hd->proc & PR_TEST) { + ; /* insert your own custom function here */ + } + strcat(bp,"\n"); + restore_flags(flags); + *start = buf; + if (stop) { + stop = 0; + return 0; /* return 0 to signal end-of-file */ + } + if (off > 0x40000) /* ALWAYS stop after 256k bytes have been read */ + stop = 1;; + if (hd->proc & PR_STOP) /* stop every other time */ + stop = 1; + return strlen(bp); + +#else /* PROC_INTERFACE */ + + return 0; + +#endif /* PROC_INTERFACE */ + +} + + +#ifdef MODULE + +Scsi_Host_Template driver_template = IN2000; + +#include "scsi_module.c" + +#endif + diff --git a/linux/src/drivers/scsi/in2000.h b/linux/src/drivers/scsi/in2000.h new file mode 100644 index 00000000..2a6ad294 --- /dev/null +++ b/linux/src/drivers/scsi/in2000.h @@ -0,0 +1,460 @@ +/* + * in2000.h - Linux device driver definitions for the + * Always IN2000 ISA SCSI card. + * + * IMPORTANT: This file is for version 1.33 - 26/Aug/1998 + * + * Copyright (c) 1996 John Shifflett, GeoLog Consulting + * john@geolog.com + * jshiffle@netcom.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef IN2000_H +#define IN2000_H + +#include <asm/io.h> + +#define PROC_INTERFACE /* add code for /proc/scsi/in2000/xxx interface */ +#ifdef PROC_INTERFACE +#define PROC_STATISTICS /* add code for keeping various real time stats */ +#endif + +#define SYNC_DEBUG /* extra info on sync negotiation printed */ +#define DEBUGGING_ON /* enable command-line debugging bitmask */ +#define DEBUG_DEFAULTS 0 /* default bitmask - change from command-line */ + +#define FAST_READ_IO /* No problems with these on my machine */ +#define FAST_WRITE_IO + +#ifdef DEBUGGING_ON +#define DB(f,a) if (hostdata->args & (f)) a; +#define CHECK_NULL(p,s) /* if (!(p)) {printk("\n"); while (1) printk("NP:%s\r",(s));} */ +#else +#define DB(f,a) +#define CHECK_NULL(p,s) +#endif + +#define uchar unsigned char + +#define read1_io(a) (inb(hostdata->io_base+(a))) +#define read2_io(a) (inw(hostdata->io_base+(a))) +#define write1_io(b,a) (outb((b),hostdata->io_base+(a))) +#define write2_io(w,a) (outw((w),hostdata->io_base+(a))) + +/* These inline assembly defines are derived from a patch + * sent to me by Bill Earnest. He's done a lot of very + * valuable thinking, testing, and coding during his effort + * to squeeze more speed out of this driver. I really think + * that we are doing IO at close to the maximum now with + * the fifo. (And yes, insw uses 'edi' while outsw uses + * 'esi'. Thanks Bill!) + */ + +#define FAST_READ2_IO() \ + __asm__ __volatile__ ("\n \ + cld \n \ + orl %%ecx, %%ecx \n \ + jz 1f \n \ + rep \n \ + insw %%dx \n \ +1: " \ + : "=D" (sp) /* output */ \ + : "d" (f), "D" (sp), "c" (i) /* input */ \ + : "edx", "ecx", "edi" ) /* trashed */ + +#define FAST_WRITE2_IO() \ + __asm__ __volatile__ ("\n \ + cld \n \ + orl %%ecx, %%ecx \n \ + jz 1f \n \ + rep \n \ + outsw %%dx \n \ +1: " \ + : "=S" (sp) /* output */ \ + : "d" (f), "S" (sp), "c" (i) /* input */ \ + : "edx", "ecx", "esi" ) /* trashed */ + + +/* IN2000 io_port offsets */ +#define IO_WD_ASR 0x00 /* R - 3393 auxstat reg */ +#define ASR_INT 0x80 +#define ASR_LCI 0x40 +#define ASR_BSY 0x20 +#define ASR_CIP 0x10 +#define ASR_PE 0x02 +#define ASR_DBR 0x01 +#define IO_WD_ADDR 0x00 /* W - 3393 address reg */ +#define IO_WD_DATA 0x01 /* R/W - rest of 3393 regs */ +#define IO_FIFO 0x02 /* R/W - in2000 dual-port fifo (16 bits) */ +#define IN2000_FIFO_SIZE 2048 /* fifo capacity in bytes */ +#define IO_CARD_RESET 0x03 /* W - in2000 start master reset */ +#define IO_FIFO_COUNT 0x04 /* R - in2000 fifo counter */ +#define IO_FIFO_WRITE 0x05 /* W - clear fifo counter, start write */ +#define IO_FIFO_READ 0x07 /* W - start fifo read */ +#define IO_LED_OFF 0x08 /* W - turn off in2000 activity LED */ +#define IO_SWITCHES 0x08 /* R - read in2000 dip switch */ +#define SW_ADDR0 0x01 /* bit 0 = bit 0 of index to io addr */ +#define SW_ADDR1 0x02 /* bit 1 = bit 1 of index io addr */ +#define SW_DISINT 0x04 /* bit 2 true if ints disabled */ +#define SW_INT0 0x08 /* bit 3 = bit 0 of index to interrupt */ +#define SW_INT1 0x10 /* bit 4 = bit 1 of index to interrupt */ +#define SW_INT_SHIFT 3 /* shift right this amount to right justify int bits */ +#define SW_SYNC_DOS5 0x20 /* bit 5 used by Always BIOS */ +#define SW_FLOPPY 0x40 /* bit 6 true if floppy enabled */ +#define SW_BIT7 0x80 /* bit 7 hardwired true (ground) */ +#define IO_LED_ON 0x09 /* W - turn on in2000 activity LED */ +#define IO_HARDWARE 0x0a /* R - read in2000 hardware rev, stop reset */ +#define IO_INTR_MASK 0x0c /* W - in2000 interrupt mask reg */ +#define IMASK_WD 0x01 /* WD33c93 interrupt mask */ +#define IMASK_FIFO 0x02 /* FIFO interrupt mask */ + +/* wd register names */ +#define WD_OWN_ID 0x00 +#define WD_CONTROL 0x01 +#define WD_TIMEOUT_PERIOD 0x02 +#define WD_CDB_1 0x03 +#define WD_CDB_2 0x04 +#define WD_CDB_3 0x05 +#define WD_CDB_4 0x06 +#define WD_CDB_5 0x07 +#define WD_CDB_6 0x08 +#define WD_CDB_7 0x09 +#define WD_CDB_8 0x0a +#define WD_CDB_9 0x0b +#define WD_CDB_10 0x0c +#define WD_CDB_11 0x0d +#define WD_CDB_12 0x0e +#define WD_TARGET_LUN 0x0f +#define WD_COMMAND_PHASE 0x10 +#define WD_SYNCHRONOUS_TRANSFER 0x11 +#define WD_TRANSFER_COUNT_MSB 0x12 +#define WD_TRANSFER_COUNT 0x13 +#define WD_TRANSFER_COUNT_LSB 0x14 +#define WD_DESTINATION_ID 0x15 +#define WD_SOURCE_ID 0x16 +#define WD_SCSI_STATUS 0x17 +#define WD_COMMAND 0x18 +#define WD_DATA 0x19 +#define WD_QUEUE_TAG 0x1a +#define WD_AUXILIARY_STATUS 0x1f + +/* WD commands */ +#define WD_CMD_RESET 0x00 +#define WD_CMD_ABORT 0x01 +#define WD_CMD_ASSERT_ATN 0x02 +#define WD_CMD_NEGATE_ACK 0x03 +#define WD_CMD_DISCONNECT 0x04 +#define WD_CMD_RESELECT 0x05 +#define WD_CMD_SEL_ATN 0x06 +#define WD_CMD_SEL 0x07 +#define WD_CMD_SEL_ATN_XFER 0x08 +#define WD_CMD_SEL_XFER 0x09 +#define WD_CMD_RESEL_RECEIVE 0x0a +#define WD_CMD_RESEL_SEND 0x0b +#define WD_CMD_WAIT_SEL_RECEIVE 0x0c +#define WD_CMD_TRANS_ADDR 0x18 +#define WD_CMD_TRANS_INFO 0x20 +#define WD_CMD_TRANSFER_PAD 0x21 +#define WD_CMD_SBT_MODE 0x80 + +/* SCSI Bus Phases */ +#define PHS_DATA_OUT 0x00 +#define PHS_DATA_IN 0x01 +#define PHS_COMMAND 0x02 +#define PHS_STATUS 0x03 +#define PHS_MESS_OUT 0x06 +#define PHS_MESS_IN 0x07 + +/* Command Status Register definitions */ + + /* reset state interrupts */ +#define CSR_RESET 0x00 +#define CSR_RESET_AF 0x01 + + /* successful completion interrupts */ +#define CSR_RESELECT 0x10 +#define CSR_SELECT 0x11 +#define CSR_SEL_XFER_DONE 0x16 +#define CSR_XFER_DONE 0x18 + + /* paused or aborted interrupts */ +#define CSR_MSGIN 0x20 +#define CSR_SDP 0x21 +#define CSR_SEL_ABORT 0x22 +#define CSR_RESEL_ABORT 0x25 +#define CSR_RESEL_ABORT_AM 0x27 +#define CSR_ABORT 0x28 + + /* terminated interrupts */ +#define CSR_INVALID 0x40 +#define CSR_UNEXP_DISC 0x41 +#define CSR_TIMEOUT 0x42 +#define CSR_PARITY 0x43 +#define CSR_PARITY_ATN 0x44 +#define CSR_BAD_STATUS 0x45 +#define CSR_UNEXP 0x48 + + /* service required interrupts */ +#define CSR_RESEL 0x80 +#define CSR_RESEL_AM 0x81 +#define CSR_DISC 0x85 +#define CSR_SRV_REQ 0x88 + + /* Own ID/CDB Size register */ +#define OWNID_EAF 0x08 +#define OWNID_EHP 0x10 +#define OWNID_RAF 0x20 +#define OWNID_FS_8 0x00 +#define OWNID_FS_12 0x40 +#define OWNID_FS_16 0x80 + + /* Control register */ +#define CTRL_HSP 0x01 +#define CTRL_HA 0x02 +#define CTRL_IDI 0x04 +#define CTRL_EDI 0x08 +#define CTRL_HHP 0x10 +#define CTRL_POLLED 0x00 +#define CTRL_BURST 0x20 +#define CTRL_BUS 0x40 +#define CTRL_DMA 0x80 + + /* Timeout Period register */ +#define TIMEOUT_PERIOD_VALUE 20 /* results in 200 ms. */ + + /* Synchronous Transfer Register */ +#define STR_FSS 0x80 + + /* Destination ID register */ +#define DSTID_DPD 0x40 +#define DATA_OUT_DIR 0 +#define DATA_IN_DIR 1 +#define DSTID_SCC 0x80 + + /* Source ID register */ +#define SRCID_MASK 0x07 +#define SRCID_SIV 0x08 +#define SRCID_DSP 0x20 +#define SRCID_ES 0x40 +#define SRCID_ER 0x80 + + + +#define ILLEGAL_STATUS_BYTE 0xff + + +#define DEFAULT_SX_PER 500 /* (ns) fairly safe */ +#define DEFAULT_SX_OFF 0 /* aka async */ + +#define OPTIMUM_SX_PER 252 /* (ns) best we can do (mult-of-4) */ +#define OPTIMUM_SX_OFF 12 /* size of in2000 fifo */ + +struct sx_period { + unsigned int period_ns; + uchar reg_value; + }; + + +struct IN2000_hostdata { + struct Scsi_Host *next; + uchar chip; /* what kind of wd33c93 chip? */ + uchar microcode; /* microcode rev if 'B' */ + unsigned short io_base; /* IO port base */ + unsigned int dip_switch; /* dip switch settings */ + unsigned int hrev; /* hardware revision of card */ + volatile uchar busy[8]; /* index = target, bit = lun */ + volatile Scsi_Cmnd *input_Q; /* commands waiting to be started */ + volatile Scsi_Cmnd *selecting; /* trying to select this command */ + volatile Scsi_Cmnd *connected; /* currently connected command */ + volatile Scsi_Cmnd *disconnected_Q;/* commands waiting for reconnect */ + uchar state; /* what we are currently doing */ + uchar fifo; /* what the FIFO is up to */ + uchar level2; /* extent to which Level-2 commands are used */ + uchar disconnect; /* disconnect/reselect policy */ + unsigned int args; /* set from command-line argument */ + uchar incoming_msg[8]; /* filled during message_in phase */ + int incoming_ptr; /* mainly used with EXTENDED messages */ + uchar outgoing_msg[8]; /* send this during next message_out */ + int outgoing_len; /* length of outgoing message */ + unsigned int default_sx_per; /* default transfer period for SCSI bus */ + uchar sync_xfer[8]; /* sync_xfer reg settings per target */ + uchar sync_stat[8]; /* status of sync negotiation per target */ + uchar sync_off; /* bit mask: don't use sync with these targets */ +#ifdef PROC_INTERFACE + uchar proc; /* bit mask: what's in proc output */ +#ifdef PROC_STATISTICS + unsigned long cmd_cnt[8]; /* # of commands issued per target */ + unsigned long int_cnt; /* # of interrupts serviced */ + unsigned long disc_allowed_cnt[8]; /* # of disconnects allowed per target */ + unsigned long disc_done_cnt[8]; /* # of disconnects done per target*/ +#endif +#endif + }; + + +/* defines for hostdata->chip */ + +#define C_WD33C93 0 +#define C_WD33C93A 1 +#define C_WD33C93B 2 +#define C_UNKNOWN_CHIP 100 + +/* defines for hostdata->state */ + +#define S_UNCONNECTED 0 +#define S_SELECTING 1 +#define S_RUNNING_LEVEL2 2 +#define S_CONNECTED 3 +#define S_PRE_TMP_DISC 4 +#define S_PRE_CMP_DISC 5 + +/* defines for hostdata->fifo */ + +#define FI_FIFO_UNUSED 0 +#define FI_FIFO_READING 1 +#define FI_FIFO_WRITING 2 + +/* defines for hostdata->level2 */ +/* NOTE: only the first 3 are trustworthy at this point - + * having trouble when more than 1 device is reading/writing + * at the same time... + */ + +#define L2_NONE 0 /* no combination commands - we get lots of ints */ +#define L2_SELECT 1 /* start with SEL_ATN_XFER, but never resume it */ +#define L2_BASIC 2 /* resume after STATUS ints & RDP messages */ +#define L2_DATA 3 /* resume after DATA_IN/OUT ints */ +#define L2_MOST 4 /* resume after anything except a RESELECT int */ +#define L2_RESELECT 5 /* resume after everything, including RESELECT ints */ +#define L2_ALL 6 /* always resume */ + +/* defines for hostdata->disconnect */ + +#define DIS_NEVER 0 +#define DIS_ADAPTIVE 1 +#define DIS_ALWAYS 2 + +/* defines for hostdata->args */ + +#define DB_TEST 1<<0 +#define DB_FIFO 1<<1 +#define DB_QUEUE_COMMAND 1<<2 +#define DB_EXECUTE 1<<3 +#define DB_INTR 1<<4 +#define DB_TRANSFER 1<<5 +#define DB_MASK 0x3f + +#define A_NO_SCSI_RESET 1<<15 + + +/* defines for hostdata->sync_xfer[] */ + +#define SS_UNSET 0 +#define SS_FIRST 1 +#define SS_WAITING 2 +#define SS_SET 3 + +/* defines for hostdata->proc */ + +#define PR_VERSION 1<<0 +#define PR_INFO 1<<1 +#define PR_STATISTICS 1<<2 +#define PR_CONNECTED 1<<3 +#define PR_INPUTQ 1<<4 +#define PR_DISCQ 1<<5 +#define PR_TEST 1<<6 +#define PR_STOP 1<<7 + + +#include <linux/version.h> + +#if LINUX_VERSION_CODE < 0x020100 /* 2.0.xx */ +# define in2000__INITFUNC(function) function +# define in2000__INIT +# define in2000__INITDATA +# define CLISPIN_LOCK(flags) do { save_flags(flags); cli(); } while(0) +# define CLISPIN_UNLOCK(flags) restore_flags(flags) +#else /* 2.1.xxx */ +# include <linux/init.h> +# include <asm/spinlock.h> +# define in2000__INITFUNC(function) __initfunc(function) +# define in2000__INIT __init +# define in2000__INITDATA __initdata +# define CLISPIN_LOCK(flags) spin_lock_irqsave(&io_request_lock, flags) +# define CLISPIN_UNLOCK(flags) spin_unlock_irqrestore(&io_request_lock, flags) +#endif + + +int in2000_detect(Scsi_Host_Template *) in2000__INIT; +int in2000_queuecommand(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int in2000_abort(Scsi_Cmnd *); +void in2000_setup(char *, int *) in2000__INIT; +int in2000_proc_info(char *, char **, off_t, int, int, int); +struct proc_dir_entry proc_scsi_in2000; +int in2000_biosparam(struct scsi_disk *, kdev_t, int *); +int in2000_reset(Scsi_Cmnd *, unsigned int); + + +#define IN2000_CAN_Q 16 +#define IN2000_SG SG_ALL +#define IN2000_CPL 2 +#define IN2000_HOST_ID 7 + +#if LINUX_VERSION_CODE < 0x020100 /* 2.0.xx */ + +#define IN2000 { NULL, /* link pointer for modules */ \ + NULL, /* usage_count for modules */ \ + &proc_scsi_in2000, /* pointer to /proc/scsi directory entry */ \ + in2000_proc_info, /* pointer to proc info function */ \ + "Always IN2000", /* device name */ \ + in2000_detect, /* returns number of in2000's found */ \ + NULL, /* optional unload function for modules */ \ + NULL, /* optional misc info function */ \ + NULL, /* send scsi command, wait for completion */ \ + in2000_queuecommand, /* queue scsi command, don't wait */ \ + in2000_abort, /* abort current command */ \ + in2000_reset, /* reset scsi bus */ \ + NULL, /* slave_attach - unused */ \ + in2000_biosparam, /* figures out BIOS parameters for lilo, etc */ \ + IN2000_CAN_Q, /* max commands we can queue up */ \ + IN2000_HOST_ID, /* host-adapter scsi id */ \ + IN2000_SG, /* scatter-gather table size */ \ + IN2000_CPL, /* commands per lun */ \ + 0, /* board counter */ \ + 0, /* unchecked dma */ \ + DISABLE_CLUSTERING \ + } + +#else /* 2.1.xxx */ + +#define IN2000 { proc_dir: &proc_scsi_in2000, /* pointer to /proc/scsi directory entry */ \ + proc_info: in2000_proc_info, /* pointer to proc info function */ \ + name: "Always IN2000", /* device name */ \ + detect: in2000_detect, /* returns number of in2000's found */ \ + queuecommand: in2000_queuecommand, /* queue scsi command, don't wait */ \ + abort: in2000_abort, /* abort current command */ \ + reset: in2000_reset, /* reset scsi bus */ \ + bios_param: in2000_biosparam, /* figures out BIOS parameters for lilo, etc */ \ + can_queue: IN2000_CAN_Q, /* max commands we can queue up */ \ + this_id: IN2000_HOST_ID, /* host-adapter scsi id */ \ + sg_tablesize: IN2000_SG, /* scatter-gather table size */ \ + cmd_per_lun: IN2000_CPL, /* commands per lun */ \ + use_clustering: DISABLE_CLUSTERING, /* ENABLE_CLUSTERING may speed things up */ \ + use_new_eh_code: 0 /* new error code - not using it yet */ \ + } + +#endif + + +#endif /* IN2000_H */ diff --git a/linux/src/drivers/scsi/ncr53c8xx.c b/linux/src/drivers/scsi/ncr53c8xx.c new file mode 100644 index 00000000..1be3d9fe --- /dev/null +++ b/linux/src/drivers/scsi/ncr53c8xx.c @@ -0,0 +1,10793 @@ +/****************************************************************************** +** Device driver for the PCI-SCSI NCR538XX controller family. +** +** Copyright (C) 1994 Wolfgang Stanglmeier +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +**----------------------------------------------------------------------------- +** +** This driver has been ported to Linux from the FreeBSD NCR53C8XX driver +** and is currently maintained by +** +** Gerard Roudier <groudier@club-internet.fr> +** +** Being given that this driver originates from the FreeBSD version, and +** in order to keep synergy on both, any suggested enhancements and corrections +** received on Linux are automatically a potential candidate for the FreeBSD +** version. +** +** The original driver has been written for 386bsd and FreeBSD by +** Wolfgang Stanglmeier <wolf@cologne.de> +** Stefan Esser <se@mi.Uni-Koeln.de> +** +** And has been ported to NetBSD by +** Charles M. Hannum <mycroft@gnu.ai.mit.edu> +** +**----------------------------------------------------------------------------- +** +** Brief history +** +** December 10 1995 by Gerard Roudier: +** Initial port to Linux. +** +** June 23 1996 by Gerard Roudier: +** Support for 64 bits architectures (Alpha). +** +** November 30 1996 by Gerard Roudier: +** Support for Fast-20 scsi. +** Support for large DMA fifo and 128 dwords bursting. +** +** February 27 1997 by Gerard Roudier: +** Support for Fast-40 scsi. +** Support for on-Board RAM. +** +** May 3 1997 by Gerard Roudier: +** Full support for scsi scripts instructions pre-fetching. +** +** May 19 1997 by Richard Waltham <dormouse@farsrobt.demon.co.uk>: +** Support for NvRAM detection and reading. +** +** August 18 1997 by Cort <cort@cs.nmt.edu>: +** Support for Power/PC (Big Endian). +** +******************************************************************************* +*/ + +/* +** 30 January 1998, version 2.5f.1 +** +** Supported SCSI-II features: +** Synchronous negotiation +** Wide negotiation (depends on the NCR Chip) +** Enable disconnection +** Tagged command queuing +** Parity checking +** Etc... +** +** Supported NCR chips: +** 53C810 (8 bits, Fast SCSI-2, no rom BIOS) +** 53C815 (8 bits, Fast SCSI-2, on board rom BIOS) +** 53C820 (Wide, Fast SCSI-2, no rom BIOS) +** 53C825 (Wide, Fast SCSI-2, on board rom BIOS) +** 53C860 (8 bits, Fast 20, no rom BIOS) +** 53C875 (Wide, Fast 20, on board rom BIOS) +** 53C895 (Wide, Fast 40, on board rom BIOS) +** +** Other features: +** Memory mapped IO (linux-1.3.X and above only) +** Module +** Shared IRQ (since linux-1.3.72) +*/ + +#define SCSI_NCR_DEBUG_FLAGS (0) + +#define NCR_GETCC_WITHMSG + +/*========================================================== +** +** Include files +** +**========================================================== +*/ + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/system.h> +#include <linux/delay.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/string.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/stat.h> + +#include <linux/version.h> +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) +#include <linux/blk.h> +#else +#include "../block/blk.h" +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,35) +#include <linux/init.h> +#else +#ifndef __initdata +#define __initdata +#endif +#ifndef __initfunc +#define __initfunc(__arginit) __arginit +#endif +#endif + +#include "scsi.h" +#include "hosts.h" +#include "constants.h" +#include "sd.h" + +#include <linux/types.h> + +/* +** Define the BSD style u_int32 type +*/ +typedef u32 u_int32; + +#include "ncr53c8xx.h" + +/*========================================================== +** +** Configuration and Debugging +** +**========================================================== +*/ + +/* +** SCSI address of this device. +** The boot routines should have set it. +** If not, use this. +*/ + +#ifndef SCSI_NCR_MYADDR +#define SCSI_NCR_MYADDR (7) +#endif + +/* +** The maximum number of tags per logic unit. +** Used only for disk devices that support tags. +*/ + +#ifndef SCSI_NCR_MAX_TAGS +#define SCSI_NCR_MAX_TAGS (4) +#endif + +/* +** Number of targets supported by the driver. +** n permits target numbers 0..n-1. +** Default is 7, meaning targets #0..#6. +** #7 .. is myself. +*/ + +#ifdef SCSI_NCR_MAX_TARGET +#define MAX_TARGET (SCSI_NCR_MAX_TARGET) +#else +#define MAX_TARGET (16) +#endif + +/* +** Number of logic units supported by the driver. +** n enables logic unit numbers 0..n-1. +** The common SCSI devices require only +** one lun, so take 1 as the default. +*/ + +#ifdef SCSI_NCR_MAX_LUN +#define MAX_LUN SCSI_NCR_MAX_LUN +#else +#define MAX_LUN (1) +#endif + +/* +** Asynchronous pre-scaler (ns). Shall be 40 +*/ + +#ifndef SCSI_NCR_MIN_ASYNC +#define SCSI_NCR_MIN_ASYNC (40) +#endif + +/* +** The maximum number of jobs scheduled for starting. +** There should be one slot per target, and one slot +** for each tag of each target in use. +** The calculation below is actually quite silly ... +*/ + +#ifdef SCSI_NCR_CAN_QUEUE +#define MAX_START (SCSI_NCR_CAN_QUEUE + 4) +#else +#define MAX_START (MAX_TARGET + 7 * SCSI_NCR_MAX_TAGS) +#endif + +/* +** The maximum number of segments a transfer is split into. +*/ + +#define MAX_SCATTER (SCSI_NCR_MAX_SCATTER) + +/* +** Io mapped or memory mapped. +*/ + +#if defined(SCSI_NCR_IOMAPPED) +#define NCR_IOMAPPED +#endif + +/* +** other +*/ + +#define NCR_SNOOP_TIMEOUT (1000000) + +/*========================================================== +** +** Defines for Linux. +** +** Linux and Bsd kernel functions are quite different. +** These defines allow a minimum change of the original +** code. +** +**========================================================== +*/ + + /* + ** Obvious definitions + */ + +#define printf printk +#define u_char unsigned char +#define u_short unsigned short +#define u_int unsigned int +#define u_long unsigned long + +typedef u_long vm_offset_t; +typedef int vm_size_t; + +#define bcopy(s, d, n) memcpy((d), (s), (n)) +#define bzero(d, n) memset((d), 0, (n)) + +#ifndef offsetof +#define offsetof(t, m) ((size_t) (&((t *)0)->m)) +#endif + +/* +** Address translation +** +** On Linux 1.3.X, virt_to_bus() must be used to translate +** virtual memory addresses of the kernel data segment into +** IO bus adresses. +** On i386 architecture, IO bus addresses match the physical +** addresses. But on other architectures they can be different. +** In the original Bsd driver, vtophys() is called to translate +** data addresses to IO bus addresses. In order to minimize +** change, I decide to define vtophys() as virt_to_bus(). +*/ + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) +#define vtophys(p) virt_to_bus(p) + +/* +** Memory mapped IO +** +** Since linux-2.1, we must use ioremap() to map the io memory space. +** iounmap() to unmap it. That allows portability. +** Linux 1.3.X and 2.0.X allow to remap physical pages addresses greater +** than the highest physical memory address to kernel virtual pages with +** vremap() / vfree(). That was not portable but worked with i386 +** architecture. +*/ + +#ifndef NCR_IOMAPPED +__initfunc( +static vm_offset_t remap_pci_mem(u_long base, u_long size) +) +{ + u_long page_base = ((u_long) base) & PAGE_MASK; + u_long page_offs = ((u_long) base) - page_base; +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,0) + u_long page_remapped = (u_long) ioremap(page_base, page_offs+size); +#else + u_long page_remapped = (u_long) vremap(page_base, page_offs+size); +#endif + + return (vm_offset_t) (page_remapped ? (page_remapped + page_offs) : 0UL); +} + +__initfunc( +static void unmap_pci_mem(vm_offset_t vaddr, u_long size) +) +{ + if (vaddr) +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,0) + iounmap((void *) (vaddr & PAGE_MASK)); +#else + vfree((void *) (vaddr & PAGE_MASK)); +#endif +} +#endif /* !NCR_IOMAPPED */ + +#else /* linux-1.2.13 */ + +/* +** Linux 1.2.X assumes that addresses (virtual, physical, bus) +** are the same. +** +** I have not found how to do MMIO. It seems that only processes can +** map high physical pages to virtual (Xservers can do MMIO). +*/ + +#define vtophys(p) ((u_long) (p)) +#endif + +/* +** Insert a delay in micro-seconds. +*/ + +static void DELAY(long us) +{ + for (;us>1000;us-=1000) udelay(1000); + if (us) udelay(us); +} + +/* +** Internal data structure allocation. +** +** Linux scsi memory poor pool is adjusted for the need of +** middle-level scsi driver. +** We allocate our control blocks in the kernel memory pool +** to avoid scsi pool shortage. +** I notice that kmalloc() returns NULL during host attach under +** Linux 1.2.13. But this ncr driver is reliable enough to +** accomodate with this joke. +** +** kmalloc() only ensure 8 bytes boundary alignment. +** The NCR need better alignment for cache line bursting. +** The global header is moved betewen the NCB and CCBs and need +** origin and destination addresses to have same lower four bits. +** +** We use 32 boundary alignment for NCB and CCBs and offset multiple +** of 32 for global header fields. That's too much but at least enough. +*/ + +#define ALIGN_SIZE(shift) (1UL << shift) +#define ALIGN_MASK(shift) (~(ALIGN_SIZE(shift)-1)) + +#define NCB_ALIGN_SHIFT 5 +#define CCB_ALIGN_SHIFT 5 +#define LCB_ALIGN_SHIFT 5 +#define SCR_ALIGN_SHIFT 5 + +#define NCB_ALIGN_SIZE ALIGN_SIZE(NCB_ALIGN_SHIFT) +#define NCB_ALIGN_MASK ALIGN_MASK(NCB_ALIGN_SHIFT) +#define CCB_ALIGN_SIZE ALIGN_SIZE(CCB_ALIGN_SHIFT) +#define CCB_ALIGN_MASK ALIGN_MASK(CCB_ALIGN_SHIFT) +#define SCR_ALIGN_SIZE ALIGN_SIZE(SCR_ALIGN_SHIFT) +#define SCR_ALIGN_MASK ALIGN_MASK(SCR_ALIGN_SHIFT) + +static void *m_alloc(int size, int a_shift) +{ + u_long addr; + void *ptr; + u_long a_size, a_mask; + + if (a_shift < 3) + a_shift = 3; + + a_size = ALIGN_SIZE(a_shift); + a_mask = ALIGN_MASK(a_shift); + + ptr = (void *) kmalloc(size + a_size, GFP_ATOMIC); + if (ptr) { + addr = (((u_long) ptr) + a_size) & a_mask; + *((void **) (addr - sizeof(void *))) = ptr; + ptr = (void *) addr; + } + + return ptr; +} + +#ifdef MODULE +static void m_free(void *ptr, int size) +{ + u_long addr; + + if (ptr) { + addr = (u_long) ptr; + ptr = *((void **) (addr - sizeof(void *))); + + kfree(ptr); + } +} +#endif + +/* +** Transfer direction +** +** Low-level scsi drivers under Linux do not receive the expected +** data transfer direction from upper scsi drivers. +** The driver will only check actual data direction for common +** scsi opcodes. Other ones may cause problem, since they may +** depend on device type or be vendor specific. +** I would prefer to never trust the device for data direction, +** but that is not possible. +** +** The original driver requires the expected direction to be known. +** The Linux version of the driver has been enhanced in order to +** be able to transfer data in the direction choosen by the target. +*/ + +#define XferNone 0 +#define XferIn 1 +#define XferOut 2 +#define XferBoth 3 +static int guess_xfer_direction(int opcode); + +/* +** Head of list of NCR boards +** +** For kernel version < 1.3.70, host is retrieved by its irq level. +** For later kernels, the internal host control block address +** (struct ncb) is used as device id parameter of the irq stuff. +*/ + +static struct Scsi_Host *first_host = NULL; +static Scsi_Host_Template *the_template = NULL; + + +/* +** /proc directory entry and proc_info function +*/ + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) +struct proc_dir_entry proc_scsi_ncr53c8xx = { + PROC_SCSI_NCR53C8XX, 9, "ncr53c8xx", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; +# ifdef SCSI_NCR_PROC_INFO_SUPPORT +int ncr53c8xx_proc_info(char *buffer, char **start, off_t offset, + int length, int hostno, int func); +# endif +#endif + +/* +** Table of target capabilities. +** +** This bitmap is anded with the byte 7 of inquiry data on completion of +** INQUIRY command. +** The driver never see zeroed bits and will ignore the corresponding +** capabilities of the target. +*/ + +static struct { + unsigned char and_map[MAX_TARGET]; +} target_capabilities[SCSI_NCR_MAX_HOST] = { NCR53C8XX_TARGET_CAPABILITIES }; + +/* +** Driver setup. +** +** This structure is initialized from linux config options. +** It can be overridden at boot-up by the boot command line. +*/ +struct ncr_driver_setup { + unsigned master_parity : 1; + unsigned scsi_parity : 1; + unsigned disconnection : 1; + unsigned special_features : 2; + unsigned ultra_scsi : 2; + unsigned force_sync_nego: 1; + unsigned reverse_probe: 1; + unsigned pci_fix_up: 4; + u_char use_nvram; + u_char verbose; + u_char default_tags; + u_short default_sync; + u_short debug; + u_char burst_max; + u_char led_pin; + u_char max_wide; + u_char settle_delay; + u_char diff_support; + u_char irqm; + u_char bus_check; +}; + +static struct ncr_driver_setup + driver_setup = SCSI_NCR_DRIVER_SETUP; + +#ifdef SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT +static struct ncr_driver_setup + driver_safe_setup __initdata = SCSI_NCR_DRIVER_SAFE_SETUP; +#ifdef MODULE +char *ncr53c8xx = 0; /* command line passed by insmod */ +#endif +#endif + +/* +** Other Linux definitions +*/ + +#define ScsiResult(host_code, scsi_code) (((host_code) << 16) + ((scsi_code) & 0x7f)) + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,0,0) +static void ncr53c8xx_select_queue_depths(struct Scsi_Host *host, struct scsi_device *devlist); +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,70) +static void ncr53c8xx_intr(int irq, void *dev_id, struct pt_regs * regs); +#else +static void ncr53c8xx_intr(int irq, struct pt_regs * regs); +#endif + +static void ncr53c8xx_timeout(unsigned long np); + +#define initverbose (driver_setup.verbose) +#define bootverbose (np->verbose) + +#ifdef SCSI_NCR_NVRAM_SUPPORT +/* +** Symbios NvRAM data format +*/ +#define SYMBIOS_NVRAM_SIZE 368 +#define SYMBIOS_NVRAM_ADDRESS 0x100 + +struct Symbios_nvram { +/* Header 6 bytes */ + u_short start_marker; /* 0x0000 */ + u_short byte_count; /* excluding header/trailer */ + u_short checksum; + +/* Controller set up 20 bytes */ + u_short word0; /* 0x3000 */ + u_short word2; /* 0x0000 */ + u_short word4; /* 0x0000 */ + u_short flags; +#define SYMBIOS_SCAM_ENABLE (1) +#define SYMBIOS_PARITY_ENABLE (1<<1) +#define SYMBIOS_VERBOSE_MSGS (1<<2) + u_short flags1; +#define SYMBIOS_SCAN_HI_LO (1) + u_short word10; /* 0x00 */ + u_short flags3; /* 0x00 */ +#define SYMBIOS_REMOVABLE_FLAGS (3) /* 0=none, 1=bootable, 2=all */ + u_char host_id; + u_char byte15; /* 0x04 */ + u_short word16; /* 0x0410 */ + u_short word18; /* 0x0000 */ + +/* Boot order 14 bytes * 4 */ + struct Symbios_host{ + u_char word0; /* 0x0004:ok / 0x0000:nok */ + u_short device_id; /* PCI device id */ + u_short vendor_id; /* PCI vendor id */ + u_char byte6; /* 0x00 */ + u_char device_fn; /* PCI device/function number << 3*/ + u_short word8; + u_short flags; +#define SYMBIOS_INIT_SCAN_AT_BOOT (1) + u_short io_port; /* PCI io_port address */ + } host[4]; + +/* Targets 8 bytes * 16 */ + struct Symbios_target { + u_short flags; +#define SYMBIOS_DISCONNECT_ENABLE (1) +#define SYMBIOS_SCAN_AT_BOOT_TIME (1<<1) +#define SYMBIOS_SCAN_LUNS (1<<2) +#define SYMBIOS_QUEUE_TAGS_ENABLED (1<<3) + u_char bus_width; /* 0x08/0x10 */ + u_char sync_offset; + u_char sync_period; /* 4*period factor */ + u_char byte6; /* 0x00 */ + u_short timeout; + } target[16]; + u_char spare_devices[19*8]; + u_char trailer[6]; /* 0xfe 0xfe 0x00 0x00 0x00 0x00 */ +}; +typedef struct Symbios_nvram Symbios_nvram; +typedef struct Symbios_host Symbios_host; +typedef struct Symbios_target Symbios_target; + +/* +** Tekram NvRAM data format. +*/ +#define TEKRAM_NVRAM_SIZE 64 +#define TEKRAM_NVRAM_ADDRESS 0 + +struct Tekram_nvram { + struct Tekram_target { + u_char flags; +#define TEKRAM_PARITY_CHECK (1) +#define TEKRAM_SYNC_NEGO (1<<1) +#define TEKRAM_DISCONNECT_ENABLE (1<<2) +#define TEKRAM_START_CMD (1<<3) +#define TEKRAM_TAGGED_COMMANDS (1<<4) +#define TEKRAM_WIDE_NEGO (1<<5) + u_char sync_index; + u_short word2; + } target[16]; + u_char host_id; + u_char flags; +#define TEKRAM_MORE_THAN_2_DRIVES (1) +#define TEKRAM_DRIVES_SUP_1GB (1<<1) +#define TEKRAM_RESET_ON_POWER_ON (1<<2) +#define TEKRAM_ACTIVE_NEGATION (1<<3) +#define TEKRAM_IMMEDIATE_SEEK (1<<4) +#define TEKRAM_SCAN_LUNS (1<<5) +#define TEKRAM_REMOVABLE_FLAGS (3<<6) /* 0: disable; 1: boot device; 2:all */ + u_char boot_delay_index; + u_char max_tags_index; + u_short flags1; +#define TEKRAM_F2_F6_ENABLED (1) + u_short spare[29]; +}; +typedef struct Tekram_nvram Tekram_nvram; +typedef struct Tekram_target Tekram_target; + +static u_char Tekram_sync[12] __initdata = {25,31,37,43,50,62,75,125,12,15,18,21}; + +#endif /* SCSI_NCR_NVRAM_SUPPORT */ + +/* +** Structures used by ncr53c8xx_detect/ncr53c8xx_pci_init to +** transmit device configuration to the ncr_attach() function. +*/ +typedef struct { + int bus; + u_char device_fn; + u_int base; + u_int base_2; + u_int io_port; + int irq; +/* port and reg fields to use INB, OUTB macros */ + u_int port; + volatile struct ncr_reg *reg; +} ncr_slot; + +typedef struct { + int type; +#define SCSI_NCR_SYMBIOS_NVRAM (1) +#define SCSI_NCR_TEKRAM_NVRAM (2) +#ifdef SCSI_NCR_NVRAM_SUPPORT + union { + Symbios_nvram Symbios; + Tekram_nvram Tekram; + } data; +#endif +} ncr_nvram; + +/* +** Structure used by ncr53c8xx_detect/ncr53c8xx_pci_init +** to save data on each detected board for ncr_attach(). +*/ +typedef struct { + ncr_slot slot; + ncr_chip chip; + ncr_nvram *nvram; + int attach_done; +} ncr_device; + +/*========================================================== +** +** Debugging tags +** +**========================================================== +*/ + +#define DEBUG_ALLOC (0x0001) +#define DEBUG_PHASE (0x0002) +#define DEBUG_POLL (0x0004) +#define DEBUG_QUEUE (0x0008) +#define DEBUG_RESULT (0x0010) +#define DEBUG_SCATTER (0x0020) +#define DEBUG_SCRIPT (0x0040) +#define DEBUG_TINY (0x0080) +#define DEBUG_TIMING (0x0100) +#define DEBUG_NEGO (0x0200) +#define DEBUG_TAGS (0x0400) +#define DEBUG_FREEZE (0x0800) +#define DEBUG_RESTART (0x1000) + +/* +** Enable/Disable debug messages. +** Can be changed at runtime too. +*/ + +#ifdef SCSI_NCR_DEBUG_INFO_SUPPORT + #define DEBUG_FLAGS ncr_debug +#else + #define DEBUG_FLAGS SCSI_NCR_DEBUG_FLAGS +#endif + + + +/*========================================================== +** +** assert () +** +**========================================================== +** +** modified copy from 386bsd:/usr/include/sys/assert.h +** +**---------------------------------------------------------- +*/ + +#define assert(expression) { \ + if (!(expression)) { \ + (void)printf(\ + "assertion \"%s\" failed: file \"%s\", line %d\n", \ + #expression, \ + __FILE__, __LINE__); \ + } \ +} + +/*========================================================== +** +** Big/Little endian support. +** +**========================================================== +*/ + +/* +** If the NCR uses big endian addressing mode over the +** PCI, actual io register addresses for byte and word +** accesses must be changed according to lane routing. +** Btw, ncr_offb() and ncr_offw() macros only apply to +** constants and so donnot generate bloated code. +*/ + +#if defined(SCSI_NCR_BIG_ENDIAN) + +#define ncr_offb(o) (((o)&~3)+((~((o)&3))&3)) +#define ncr_offw(o) (((o)&~3)+((~((o)&3))&2)) + +#else + +#define ncr_offb(o) (o) +#define ncr_offw(o) (o) + +#endif + +/* +** If the CPU and the NCR use same endian-ness adressing, +** no byte reordering is needed for script patching. +** Macro cpu_to_scr() is to be used for script patching. +** Macro scr_to_cpu() is to be used for getting a DWORD +** from the script. +*/ + +#if defined(__BIG_ENDIAN) && !defined(SCSI_NCR_BIG_ENDIAN) + +#define cpu_to_scr(dw) cpu_to_le32(dw) +#define scr_to_cpu(dw) le32_to_cpu(dw) + +#elif defined(__LITTLE_ENDIAN) && defined(SCSI_NCR_BIG_ENDIAN) + +#define cpu_to_scr(dw) cpu_to_be32(dw) +#define scr_to_cpu(dw) be32_to_cpu(dw) + +#else + +#define cpu_to_scr(dw) (dw) +#define scr_to_cpu(dw) (dw) + +#endif + +/*========================================================== +** +** Access to the controller chip. +** +** If NCR_IOMAPPED is defined, only IO are used by the driver. +** +**========================================================== +*/ + +/* +** If the CPU and the NCR use same endian-ness adressing, +** no byte reordering is needed for accessing chip io +** registers. Functions suffixed by '_raw' are assumed +** to access the chip over the PCI without doing byte +** reordering. Functions suffixed by '_l2b' are +** assumed to perform little-endian to big-endian byte +** reordering, those suffixed by '_b2l' blah, blah, +** blah, ... +*/ + +#if defined(NCR_IOMAPPED) + +/* +** IO mapped only input / ouput +*/ + +#define INB_OFF(o) inb (np->port + ncr_offb(o)) +#define OUTB_OFF(o, val) outb ((val), np->port + ncr_offb(o)) + +#if defined(__BIG_ENDIAN) && !defined(SCSI_NCR_BIG_ENDIAN) + +#define INW_OFF(o) inw_l2b (np->port + ncr_offw(o)) +#define INL_OFF(o) inl_l2b (np->port + (o)) + +#define OUTW_OFF(o, val) outw_b2l ((val), np->port + ncr_offw(o)) +#define OUTL_OFF(o, val) outl_b2l ((val), np->port + (o)) + +#elif defined(__LITTLE_ENDIAN) && defined(SCSI_NCR_BIG_ENDIAN) + +#define INW_OFF(o) inw_b2l (np->port + ncr_offw(o)) +#define INL_OFF(o) inl_b2l (np->port + (o)) + +#define OUTW_OFF(o, val) outw_l2b ((val), np->port + ncr_offw(o)) +#define OUTL_OFF(o, val) outl_l2b ((val), np->port + (o)) + +#else + +#define INW_OFF(o) inw_raw (np->port + ncr_offw(o)) +#define INL_OFF(o) inl_raw (np->port + (o)) + +#define OUTW_OFF(o, val) outw_raw ((val), np->port + ncr_offw(o)) +#define OUTL_OFF(o, val) outl_raw ((val), np->port + (o)) + +#endif /* ENDIANs */ + +#else /* defined NCR_IOMAPPED */ + +/* +** MEMORY mapped IO input / output +*/ + +#define INB_OFF(o) readb((char *)np->reg + ncr_offb(o)) +#define OUTB_OFF(o, val) writeb((val), (char *)np->reg + ncr_offb(o)) + +#if defined(__BIG_ENDIAN) && !defined(SCSI_NCR_BIG_ENDIAN) + +#define INW_OFF(o) readw_l2b((char *)np->reg + ncr_offw(o)) +#define INL_OFF(o) readl_l2b((char *)np->reg + (o)) + +#define OUTW_OFF(o, val) writew_b2l((val), (char *)np->reg + ncr_offw(o)) +#define OUTL_OFF(o, val) writel_b2l((val), (char *)np->reg + (o)) + +#elif defined(__LITTLE_ENDIAN) && defined(SCSI_NCR_BIG_ENDIAN) + +#define INW_OFF(o) readw_b2l((char *)np->reg + ncr_offw(o)) +#define INL_OFF(o) readl_b2l((char *)np->reg + (o)) + +#define OUTW_OFF(o, val) writew_l2b((val), (char *)np->reg + ncr_offw(o)) +#define OUTL_OFF(o, val) writel_l2b((val), (char *)np->reg + (o)) + +#else + +#define INW_OFF(o) readw_raw((char *)np->reg + ncr_offw(o)) +#define INL_OFF(o) readl_raw((char *)np->reg + (o)) + +#define OUTW_OFF(o, val) writew_raw((val), (char *)np->reg + ncr_offw(o)) +#define OUTL_OFF(o, val) writel_raw((val), (char *)np->reg + (o)) + +#endif + +#endif /* defined NCR_IOMAPPED */ + +#define INB(r) INB_OFF (offsetof(struct ncr_reg,r)) +#define INW(r) INW_OFF (offsetof(struct ncr_reg,r)) +#define INL(r) INL_OFF (offsetof(struct ncr_reg,r)) + +#define OUTB(r, val) OUTB_OFF (offsetof(struct ncr_reg,r), (val)) +#define OUTW(r, val) OUTW_OFF (offsetof(struct ncr_reg,r), (val)) +#define OUTL(r, val) OUTL_OFF (offsetof(struct ncr_reg,r), (val)) + +/* +** Set bit field ON, OFF +*/ + +#define OUTONB(r, m) OUTB(r, INB(r) | (m)) +#define OUTOFFB(r, m) OUTB(r, INB(r) & ~(m)) +#define OUTONW(r, m) OUTW(r, INW(r) | (m)) +#define OUTOFFW(r, m) OUTW(r, INW(r) & ~(m)) +#define OUTONL(r, m) OUTL(r, INL(r) | (m)) +#define OUTOFFL(r, m) OUTL(r, INL(r) & ~(m)) + + +/*========================================================== +** +** Command control block states. +** +**========================================================== +*/ + +#define HS_IDLE (0) +#define HS_BUSY (1) +#define HS_NEGOTIATE (2) /* sync/wide data transfer*/ +#define HS_DISCONNECT (3) /* Disconnected by target */ + +#define HS_COMPLETE (4) +#define HS_SEL_TIMEOUT (5) /* Selection timeout */ +#define HS_RESET (6) /* SCSI reset */ +#define HS_ABORTED (7) /* Transfer aborted */ +#define HS_TIMEOUT (8) /* Software timeout */ +#define HS_FAIL (9) /* SCSI or PCI bus errors */ +#define HS_UNEXPECTED (10) /* Unexpected disconnect */ + +#define HS_DONEMASK (0xfc) + +/*========================================================== +** +** Software Interrupt Codes +** +**========================================================== +*/ + +#define SIR_SENSE_RESTART (1) +#define SIR_SENSE_FAILED (2) +#define SIR_STALL_RESTART (3) +#define SIR_STALL_QUEUE (4) +#define SIR_NEGO_SYNC (5) +#define SIR_NEGO_WIDE (6) +#define SIR_NEGO_FAILED (7) +#define SIR_NEGO_PROTO (8) +#define SIR_REJECT_RECEIVED (9) +#define SIR_REJECT_SENT (10) +#define SIR_IGN_RESIDUE (11) +#define SIR_MISSING_SAVE (12) +#define SIR_DATA_IO_IS_OUT (13) +#define SIR_DATA_IO_IS_IN (14) +#define SIR_MAX (14) + +/*========================================================== +** +** Extended error codes. +** xerr_status field of struct ccb. +** +**========================================================== +*/ + +#define XE_OK (0) +#define XE_EXTRA_DATA (1) /* unexpected data phase */ +#define XE_BAD_PHASE (2) /* illegal phase (4/5) */ + +/*========================================================== +** +** Negotiation status. +** nego_status field of struct ccb. +** +**========================================================== +*/ + +#define NS_SYNC (1) +#define NS_WIDE (2) + +/*========================================================== +** +** "Special features" of targets. +** quirks field of struct tcb. +** actualquirks field of struct ccb. +** +**========================================================== +*/ + +#define QUIRK_AUTOSAVE (0x01) +#define QUIRK_NOMSG (0x02) +#define QUIRK_NOSYNC (0x10) +#define QUIRK_NOWIDE16 (0x20) +#define QUIRK_UPDATE (0x80) + +/*========================================================== +** +** Capability bits in Inquire response byte 7. +** +**========================================================== +*/ + +#define INQ7_QUEUE (0x02) +#define INQ7_SYNC (0x10) +#define INQ7_WIDE16 (0x20) + +/*========================================================== +** +** Misc. +** +**========================================================== +*/ + +#define CCB_MAGIC (0xf2691ad2) + +/*========================================================== +** +** Declaration of structs. +** +**========================================================== +*/ + +struct tcb; +struct lcb; +struct ccb; +struct ncb; +struct script; + +typedef struct ncb * ncb_p; +typedef struct tcb * tcb_p; +typedef struct lcb * lcb_p; +typedef struct ccb * ccb_p; + +struct link { + ncrcmd l_cmd; + ncrcmd l_paddr; +}; + +struct usrcmd { + u_long target; + u_long lun; + u_long data; + u_long cmd; +}; + +#define UC_SETSYNC 10 +#define UC_SETTAGS 11 +#define UC_SETDEBUG 12 +#define UC_SETORDER 13 +#define UC_SETWIDE 14 +#define UC_SETFLAG 15 +#define UC_CLEARPROF 16 + +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT +#define UC_DEBUG_ERROR_RECOVERY 17 +#endif + +#define UF_TRACE (0x01) +#define UF_NODISC (0x02) +#define UF_NOSCAN (0x04) + +/*--------------------------------------- +** +** Timestamps for profiling +** +**--------------------------------------- +*/ + +struct tstamp { + u_long start; + u_long end; + u_long select; + u_long command; + u_long status; + u_long disconnect; + u_long reselect; +}; + +/* +** profiling data (per device) +*/ + +struct profile { + u_long num_trans; + u_long num_kbytes; + u_long rest_bytes; + u_long num_disc; + u_long num_break; + u_long num_int; + u_long num_fly; + u_long ms_setup; + u_long ms_data; + u_long ms_disc; + u_long ms_post; +}; + +/*========================================================== +** +** Declaration of structs: target control block +** +**========================================================== +*/ + +struct tcb { + /* + ** during reselection the ncr jumps to this point + ** with SFBR set to the encoded target number + ** with bit 7 set. + ** if it's not this target, jump to the next. + ** + ** JUMP IF (SFBR != #target#) + ** @(next tcb) + */ + + struct link jump_tcb; + + /* + ** load the actual values for the sxfer and the scntl3 + ** register (sync/wide mode). + ** + ** SCR_COPY (1); + ** @(sval field of this tcb) + ** @(sxfer register) + ** SCR_COPY (1); + ** @(wval field of this tcb) + ** @(scntl3 register) + */ + + ncrcmd getscr[6]; + + /* + ** if next message is "identify" + ** then load the message to SFBR, + ** else load 0 to SFBR. + ** + ** CALL + ** <RESEL_LUN> + */ + + struct link call_lun; + + /* + ** now look for the right lun. + ** + ** JUMP + ** @(first ccb of this lun) + */ + + struct link jump_lcb; + + /* + ** pointer to interrupted getcc ccb + */ + + ccb_p hold_cp; + + /* + ** pointer to ccb used for negotiating. + ** Avoid to start a nego for all queued commands + ** when tagged command queuing is enabled. + */ + + ccb_p nego_cp; + + /* + ** statistical data + */ + + u_long transfers; + u_long bytes; + + /* + ** user settable limits for sync transfer + ** and tagged commands. + ** These limits are read from the NVRAM if present. + */ + + u_char usrsync; + u_char usrwide; + u_char usrtags; + u_char usrflag; + + u_char numtags; + u_char maxtags; + u_short num_good; + + /* + ** negotiation of wide and synch transfer. + ** device quirks. + */ + +/*0*/ u_char minsync; +/*1*/ u_char sval; +/*2*/ u_short period; +/*0*/ u_char maxoffs; + +/*1*/ u_char quirks; + +/*2*/ u_char widedone; +/*3*/ u_char wval; + /* + ** inquire data + */ +#define MAX_INQUIRE 36 + u_char inqdata[MAX_INQUIRE]; + + /* + ** the lcb's of this tcb + */ + + lcb_p lp[MAX_LUN]; +}; + +/*========================================================== +** +** Declaration of structs: lun control block +** +**========================================================== +*/ + +struct lcb { + /* + ** during reselection the ncr jumps to this point + ** with SFBR set to the "Identify" message. + ** if it's not this lun, jump to the next. + ** + ** JUMP IF (SFBR != #lun#) + ** @(next lcb of this target) + */ + + struct link jump_lcb; + + /* + ** if next message is "simple tag", + ** then load the tag to SFBR, + ** else load 0 to SFBR. + ** + ** CALL + ** <RESEL_TAG> + */ + + struct link call_tag; + + /* + ** now look for the right ccb. + ** + ** JUMP + ** @(first ccb of this lun) + */ + + struct link jump_ccb; + + /* + ** start of the ccb chain + */ + + ccb_p next_ccb; + + /* + ** Control of tagged queueing + */ + + u_char reqccbs; + u_char actccbs; + u_char reqlink; + u_char actlink; + u_char usetags; + u_char lasttag; + + /* + ** Linux specific fields: + ** Number of active commands and current credit. + ** Should be managed by the generic scsi driver + */ + + u_char active; + u_char opennings; + + /*----------------------------------------------- + ** Flag to force M_ORDERED_TAG on next command + ** in order to avoid spurious timeout when + ** M_SIMPLE_TAG is used for all operations. + **----------------------------------------------- + */ + u_char force_ordered_tag; +#define NCR_TIMEOUT_INCREASE (5*HZ) +}; + +/*========================================================== +** +** Declaration of structs: COMMAND control block +** +**========================================================== +** +** This substructure is copied from the ccb to a +** global address after selection (or reselection) +** and copied back before disconnect. +** +** These fields are accessible to the script processor. +** +**---------------------------------------------------------- +*/ + +struct head { + /* + ** Execution of a ccb starts at this point. + ** It's a jump to the "SELECT" label + ** of the script. + ** + ** After successful selection the script + ** processor overwrites it with a jump to + ** the IDLE label of the script. + */ + + struct link launch; + + /* + ** Saved data pointer. + ** Points to the position in the script + ** responsible for the actual transfer + ** of data. + ** It's written after reception of a + ** "SAVE_DATA_POINTER" message. + ** The goalpointer points after + ** the last transfer command. + */ + + u_int32 savep; + u_int32 lastp; + u_int32 goalp; + + /* + ** The virtual address of the ccb + ** containing this header. + */ + + ccb_p cp; + + /* + ** space for some timestamps to gather + ** profiling data about devices and this driver. + */ + + struct tstamp stamp; + + /* + ** status fields. + */ + + u_char scr_st[4]; /* script status */ + u_char status[4]; /* host status. Must be the last */ + /* DWORD of the CCB header */ +}; + +/* +** The status bytes are used by the host and the script processor. +** +** The byte corresponding to the host_status must be stored in the +** last DWORD of the CCB header since it is used for command +** completion (ncr_wakeup()). Doing so, we are sure that the header +** has been entirely copied back to the CCB when the host_status is +** seen complete by the CPU. +** +** The last four bytes (status[4]) are copied to the scratchb register +** (declared as scr0..scr3 in ncr_reg.h) just after the select/reselect, +** and copied back just after disconnecting. +** Inside the script the XX_REG are used. +** +** The first four bytes (scr_st[4]) are used inside the script by +** "COPY" commands. +** Because source and destination must have the same alignment +** in a DWORD, the fields HAVE to be at the choosen offsets. +** xerr_st 0 (0x34) scratcha +** sync_st 1 (0x05) sxfer +** wide_st 3 (0x03) scntl3 +*/ + +/* +** Last four bytes (script) +*/ +#define QU_REG scr0 +#define HS_REG scr1 +#define HS_PRT nc_scr1 +#define SS_REG scr2 +#define PS_REG scr3 + +/* +** Last four bytes (host) +*/ +#define actualquirks phys.header.status[0] +#define host_status phys.header.status[1] +#define scsi_status phys.header.status[2] +#define parity_status phys.header.status[3] + +/* +** First four bytes (script) +*/ +#define xerr_st header.scr_st[0] +#define sync_st header.scr_st[1] +#define nego_st header.scr_st[2] +#define wide_st header.scr_st[3] + +/* +** First four bytes (host) +*/ +#define xerr_status phys.xerr_st +#define sync_status phys.sync_st +#define nego_status phys.nego_st +#define wide_status phys.wide_st + +/*========================================================== +** +** Declaration of structs: Data structure block +** +**========================================================== +** +** During execution of a ccb by the script processor, +** the DSA (data structure address) register points +** to this substructure of the ccb. +** This substructure contains the header with +** the script-processor-changable data and +** data blocks for the indirect move commands. +** +**---------------------------------------------------------- +*/ + +struct dsb { + + /* + ** Header. + ** Has to be the first entry, + ** because it's jumped to by the + ** script processor + */ + + struct head header; + + /* + ** Table data for Script + */ + + struct scr_tblsel select; + struct scr_tblmove smsg ; + struct scr_tblmove smsg2 ; + struct scr_tblmove cmd ; + struct scr_tblmove scmd ; + struct scr_tblmove sense ; + struct scr_tblmove data [MAX_SCATTER]; +}; + +/*========================================================== +** +** Declaration of structs: Command control block. +** +**========================================================== +** +** During execution of a ccb by the script processor, +** the DSA (data structure address) register points +** to this substructure of the ccb. +** This substructure contains the header with +** the script-processor-changable data and then +** data blocks for the indirect move commands. +** +**---------------------------------------------------------- +*/ + + +struct ccb { + /* + ** This field forces 32 bytes alignement for phys.header, + ** in order to use cache line bursting when copying it + ** to the ncb. + */ + + struct link filler[2]; + + /* + ** during reselection the ncr jumps to this point. + ** If a "SIMPLE_TAG" message was received, + ** then SFBR is set to the tag. + ** else SFBR is set to 0 + ** If looking for another tag, jump to the next ccb. + ** + ** JUMP IF (SFBR != #TAG#) + ** @(next ccb of this lun) + */ + + struct link jump_ccb; + + /* + ** After execution of this call, the return address + ** (in the TEMP register) points to the following + ** data structure block. + ** So copy it to the DSA register, and start + ** processing of this data structure. + ** + ** CALL + ** <RESEL_TMP> + */ + + struct link call_tmp; + + /* + ** This is the data structure which is + ** to be executed by the script processor. + */ + + struct dsb phys; + + /* + ** If a data transfer phase is terminated too early + ** (after reception of a message (i.e. DISCONNECT)), + ** we have to prepare a mini script to transfer + ** the rest of the data. + */ + + ncrcmd patch[8]; + + /* + ** The general SCSI driver provides a + ** pointer to a control block. + */ + + Scsi_Cmnd *cmd; + int data_len; + + /* + ** We prepare a message to be sent after selection, + ** and a second one to be sent after getcc selection. + ** Contents are IDENTIFY and SIMPLE_TAG. + ** While negotiating sync or wide transfer, + ** a SDTM or WDTM message is appended. + */ + + u_char scsi_smsg [8]; + u_char scsi_smsg2[8]; + + /* + ** Lock this ccb. + ** Flag is used while looking for a free ccb. + */ + + u_long magic; + + /* + ** Physical address of this instance of ccb + */ + + u_long p_ccb; + + /* + ** Completion time out for this job. + ** It's set to time of start + allowed number of seconds. + */ + + u_long tlimit; + + /* + ** All ccbs of one hostadapter are chained. + */ + + ccb_p link_ccb; + + /* + ** All ccbs of one target/lun are chained. + */ + + ccb_p next_ccb; + + /* + ** Sense command + */ + + u_char sensecmd[6]; + + /* + ** Tag for this transfer. + ** It's patched into jump_ccb. + ** If it's not zero, a SIMPLE_TAG + ** message is included in smsg. + */ + + u_char tag; + + /* + ** Number of segments of the scatter list. + ** Used for recalculation of savep/goalp/lastp on + ** SIR_DATA_IO_IS_OUT interrupt. + */ + + u_char segments; +}; + +#define CCB_PHYS(cp,lbl) (cp->p_ccb + offsetof(struct ccb, lbl)) + +/*========================================================== +** +** Declaration of structs: NCR device descriptor +** +**========================================================== +*/ + +struct ncb { + /* + ** The global header. + ** Accessible to both the host and the + ** script-processor. + ** Is 32 bytes aligned since ncb is, in order to + ** allow cache line bursting when copying it from or + ** to ccbs. + */ + struct head header; + + /*----------------------------------------------- + ** Specific Linux fields + **----------------------------------------------- + */ + int unit; /* Unit number */ + char chip_name[8]; /* Chip name */ + char inst_name[16]; /* Instance name */ + struct timer_list timer; /* Timer link header */ + int ncr_cache; /* Cache test variable */ + Scsi_Cmnd *waiting_list; /* Waiting list header for commands */ + /* that we can't put into the squeue */ + u_long settle_time; /* Reset in progess */ + u_char release_stage; /* Synchronisation stage on release */ + u_char verbose; /* Boot verbosity for this controller*/ +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + u_char debug_error_recovery; + u_char stalling; + u_char assert_atn; +#endif + + /*----------------------------------------------- + ** Added field to support differences + ** between ncr chips. + ** sv_xxx are some io register bit value at start-up and + ** so assumed to have been set by the sdms bios. + ** rv_xxx are the bit fields of io register that will keep + ** the features used by the driver. + **----------------------------------------------- + */ + u_short device_id; + u_char revision_id; + + u_char sv_scntl0; + u_char sv_scntl3; + u_char sv_dmode; + u_char sv_dcntl; + u_char sv_ctest3; + u_char sv_ctest4; + u_char sv_ctest5; + u_char sv_gpcntl; + u_char sv_stest2; + u_char sv_stest4; + + u_char rv_scntl0; + u_char rv_scntl3; + u_char rv_dmode; + u_char rv_dcntl; + u_char rv_ctest3; + u_char rv_ctest4; + u_char rv_ctest5; + u_char rv_stest2; + + u_char scsi_mode; + + /*----------------------------------------------- + ** Scripts .. + **----------------------------------------------- + ** + ** During reselection the ncr jumps to this point. + ** The SFBR register is loaded with the encoded target id. + ** + ** Jump to the first target. + ** + ** JUMP + ** @(next tcb) + */ + struct link jump_tcb; + + /*----------------------------------------------- + ** Configuration .. + **----------------------------------------------- + ** + ** virtual and physical addresses + ** of the 53c810 chip. + */ + vm_offset_t vaddr; + vm_offset_t paddr; + + vm_offset_t vaddr2; + vm_offset_t paddr2; + + /* + ** pointer to the chip's registers. + */ + volatile + struct ncr_reg* reg; + + /* + ** A copy of the scripts, relocated for this ncb. + */ + struct script *script0; + struct scripth *scripth0; + + /* + ** Scripts instance virtual address. + */ + struct script *script; + struct scripth *scripth; + + /* + ** Scripts instance physical address. + */ + u_long p_script; + u_long p_scripth; + + /* + ** The SCSI address of the host adapter. + */ + u_char myaddr; + + /* + ** Max dwords burst supported by the adapter. + */ + u_char maxburst; /* log base 2 of dwords burst */ + + /* + ** timing parameters + */ + u_char minsync; /* Minimum sync period factor */ + u_char maxsync; /* Maximum sync period factor */ + u_char maxoffs; /* Max scsi offset */ + u_char multiplier; /* Clock multiplier (1,2,4) */ + u_char clock_divn; /* Number of clock divisors */ + u_long clock_khz; /* SCSI clock frequency in KHz */ + u_int features; /* Chip features map */ + + + /*----------------------------------------------- + ** Link to the generic SCSI driver + **----------------------------------------------- + */ + + /* struct scsi_link sc_link; */ + + /*----------------------------------------------- + ** Job control + **----------------------------------------------- + ** + ** Commands from user + */ + struct usrcmd user; + u_char order; + + /* + ** Target data + */ + struct tcb target[MAX_TARGET]; + + /* + ** Start queue. + */ + u_int32 squeue [MAX_START]; + u_short squeueput; + u_short actccbs; + + /* + ** Timeout handler + */ +#if 0 + u_long heartbeat; + u_short ticks; + u_short latetime; +#endif + u_long lasttime; + + /*----------------------------------------------- + ** Debug and profiling + **----------------------------------------------- + ** + ** register dump + */ + struct ncr_reg regdump; + u_long regtime; + + /* + ** Profiling data + */ + struct profile profile; + u_long disc_phys; + u_long disc_ref; + + /* + ** The global control block. + ** It's used only during the configuration phase. + ** A target control block will be created + ** after the first successful transfer. + */ + struct ccb *ccb; + + /* + ** message buffers. + ** Should be longword aligned, + ** because they're written with a + ** COPY script command. + */ + u_char msgout[8]; + u_char msgin [8]; + u_int32 lastmsg; + + /* + ** Buffer for STATUS_IN phase. + */ + u_char scratch; + + /* + ** controller chip dependent maximal transfer width. + */ + u_char maxwide; + + /* + ** option for M_IDENTIFY message: enables disconnecting + */ + u_char disc; + + /* + ** address of the ncr control registers in io space + */ + u_int port; + + /* + ** irq level + */ + u_short irq; +}; + +#define NCB_SCRIPT_PHYS(np,lbl) (np->p_script + offsetof (struct script, lbl)) +#define NCB_SCRIPTH_PHYS(np,lbl) (np->p_scripth + offsetof (struct scripth, lbl)) + +/*========================================================== +** +** +** Script for NCR-Processor. +** +** Use ncr_script_fill() to create the variable parts. +** Use ncr_script_copy_and_bind() to make a copy and +** bind to physical addresses. +** +** +**========================================================== +** +** We have to know the offsets of all labels before +** we reach them (for forward jumps). +** Therefore we declare a struct here. +** If you make changes inside the script, +** DON'T FORGET TO CHANGE THE LENGTHS HERE! +** +**---------------------------------------------------------- +*/ + +/* +** Script fragments which are loaded into the on-board RAM +** of 825A, 875 and 895 chips. +*/ +struct script { + ncrcmd start [ 4]; + ncrcmd start0 [ 2]; + ncrcmd start1 [ 3]; + ncrcmd startpos [ 1]; + ncrcmd trysel [ 8]; + ncrcmd skip [ 8]; + ncrcmd skip2 [ 3]; + ncrcmd idle [ 2]; + ncrcmd select [ 22]; + ncrcmd prepare [ 4]; + ncrcmd loadpos [ 14]; + ncrcmd prepare2 [ 24]; + ncrcmd setmsg [ 5]; + ncrcmd clrack [ 2]; + ncrcmd dispatch [ 38]; + ncrcmd no_data [ 17]; + ncrcmd checkatn [ 10]; + ncrcmd command [ 15]; + ncrcmd status [ 27]; + ncrcmd msg_in [ 26]; + ncrcmd msg_bad [ 6]; + ncrcmd complete [ 13]; + ncrcmd cleanup [ 12]; + ncrcmd cleanup0 [ 11]; + ncrcmd signal [ 10]; + ncrcmd save_dp [ 5]; + ncrcmd restore_dp [ 5]; + ncrcmd disconnect [ 12]; + ncrcmd disconnect0 [ 5]; + ncrcmd disconnect1 [ 23]; + ncrcmd msg_out [ 9]; + ncrcmd msg_out_done [ 7]; + ncrcmd badgetcc [ 6]; + ncrcmd reselect [ 8]; + ncrcmd reselect1 [ 8]; + ncrcmd reselect2 [ 8]; + ncrcmd resel_tmp [ 5]; + ncrcmd resel_lun [ 18]; + ncrcmd resel_tag [ 24]; + ncrcmd data_io [ 6]; + ncrcmd data_in [MAX_SCATTER * 4 + 4]; +}; + +/* +** Script fragments which stay in main memory for all chips. +*/ +struct scripth { + ncrcmd tryloop [MAX_START*5+2]; + ncrcmd msg_parity [ 6]; + ncrcmd msg_reject [ 8]; + ncrcmd msg_ign_residue [ 32]; + ncrcmd msg_extended [ 18]; + ncrcmd msg_ext_2 [ 18]; + ncrcmd msg_wdtr [ 27]; + ncrcmd msg_ext_3 [ 18]; + ncrcmd msg_sdtr [ 27]; + ncrcmd msg_out_abort [ 10]; + ncrcmd getcc [ 4]; + ncrcmd getcc1 [ 5]; +#ifdef NCR_GETCC_WITHMSG + ncrcmd getcc2 [ 33]; +#else + ncrcmd getcc2 [ 14]; +#endif + ncrcmd getcc3 [ 10]; + ncrcmd data_out [MAX_SCATTER * 4 + 4]; + ncrcmd aborttag [ 4]; + ncrcmd abort [ 22]; + ncrcmd snooptest [ 9]; + ncrcmd snoopend [ 2]; +}; + +/*========================================================== +** +** +** Function headers. +** +** +**========================================================== +*/ + +static void ncr_alloc_ccb (ncb_p np, u_long t, u_long l); +static void ncr_complete (ncb_p np, ccb_p cp); +static void ncr_exception (ncb_p np); +static void ncr_free_ccb (ncb_p np, ccb_p cp, u_long t, u_long l); +static void ncr_getclock (ncb_p np, int mult); +static void ncr_selectclock (ncb_p np, u_char scntl3); +static ccb_p ncr_get_ccb (ncb_p np, u_long t,u_long l); +static void ncr_init (ncb_p np, int reset, char * msg, u_long code); +static int ncr_int_sbmc (ncb_p np); +static int ncr_int_par (ncb_p np); +static void ncr_int_ma (ncb_p np); +static void ncr_int_sir (ncb_p np); +static void ncr_int_sto (ncb_p np); +static u_long ncr_lookup (char* id); +static void ncr_negotiate (struct ncb* np, struct tcb* tp); +static void ncr_opennings (ncb_p np, lcb_p lp, Scsi_Cmnd * xp); + +#ifdef SCSI_NCR_PROFILE_SUPPORT +static void ncb_profile (ncb_p np, ccb_p cp); +#endif + +static void ncr_script_copy_and_bind + (ncb_p np, ncrcmd *src, ncrcmd *dst, int len); +static void ncr_script_fill (struct script * scr, struct scripth * scripth); +static int ncr_scatter (ccb_p cp, Scsi_Cmnd *cmd); +static void ncr_setmaxtags (ncb_p np, tcb_p tp, u_long numtags); +static void ncr_getsync (ncb_p np, u_char sfac, u_char *fakp, u_char *scntl3p); +static void ncr_setsync (ncb_p np, ccb_p cp, u_char scntl3, u_char sxfer); +static void ncr_settags (tcb_p tp, lcb_p lp); +static void ncr_setwide (ncb_p np, ccb_p cp, u_char wide, u_char ack); +static int ncr_show_msg (u_char * msg); +static int ncr_snooptest (ncb_p np); +static void ncr_timeout (ncb_p np); +static void ncr_wakeup (ncb_p np, u_long code); +static void ncr_start_reset (ncb_p np, int settle_delay); +static int ncr_reset_scsi_bus (ncb_p np, int enab_int, int settle_delay); + +#ifdef SCSI_NCR_USER_COMMAND_SUPPORT +static void ncr_usercmd (ncb_p np); +#endif + +static int ncr_attach (Scsi_Host_Template *tpnt, int unit, ncr_device *device); + +static void insert_into_waiting_list(ncb_p np, Scsi_Cmnd *cmd); +static Scsi_Cmnd *retrieve_from_waiting_list(int to_remove, ncb_p np, Scsi_Cmnd *cmd); +static void process_waiting_list(ncb_p np, int sts); + +#define remove_from_waiting_list(np, cmd) \ + retrieve_from_waiting_list(1, (np), (cmd)) +#define requeue_waiting_list(np) process_waiting_list((np), DID_OK) +#define reset_waiting_list(np) process_waiting_list((np), DID_RESET) + +#ifdef SCSI_NCR_NVRAM_SUPPORT +static int ncr_get_Symbios_nvram (ncr_slot *np, Symbios_nvram *nvram); +static int ncr_get_Tekram_nvram (ncr_slot *np, Tekram_nvram *nvram); +#endif + +/*========================================================== +** +** +** Global static data. +** +** +**========================================================== +*/ + +#ifdef SCSI_NCR_DEBUG_INFO_SUPPORT +static int ncr_debug = SCSI_NCR_DEBUG_FLAGS; +#endif + +static inline char *ncr_name (ncb_p np) +{ + return np->inst_name; +} + + +/*========================================================== +** +** +** Scripts for NCR-Processor. +** +** Use ncr_script_bind for binding to physical addresses. +** +** +**========================================================== +** +** NADDR generates a reference to a field of the controller data. +** PADDR generates a reference to another part of the script. +** RADDR generates a reference to a script processor register. +** FADDR generates a reference to a script processor register +** with offset. +** +**---------------------------------------------------------- +*/ + +#define RELOC_SOFTC 0x40000000 +#define RELOC_LABEL 0x50000000 +#define RELOC_REGISTER 0x60000000 +#define RELOC_KVAR 0x70000000 +#define RELOC_LABELH 0x80000000 +#define RELOC_MASK 0xf0000000 + +#define NADDR(label) (RELOC_SOFTC | offsetof(struct ncb, label)) +#define PADDR(label) (RELOC_LABEL | offsetof(struct script, label)) +#define PADDRH(label) (RELOC_LABELH | offsetof(struct scripth, label)) +#define RADDR(label) (RELOC_REGISTER | REG(label)) +#define FADDR(label,ofs)(RELOC_REGISTER | ((REG(label))+(ofs))) +#define KVAR(which) (RELOC_KVAR | (which)) + +#define SCRIPT_KVAR_JIFFIES (0) + +#define SCRIPT_KVAR_FIRST SCRIPT_KVAR_JIFFIES +#define SCRIPT_KVAR_LAST SCRIPT_KVAR_JIFFIES + +/* + * Kernel variables referenced in the scripts. + * THESE MUST ALL BE ALIGNED TO A 4-BYTE BOUNDARY. + */ +static void *script_kvars[] __initdata = + { (void *)&jiffies }; + +static struct script script0 __initdata = { +/*--------------------------< START >-----------------------*/ { +#if 0 + /* + ** Claim to be still alive ... + */ + SCR_COPY (sizeof (((struct ncb *)0)->heartbeat)), + KVAR(SCRIPT_KVAR_JIFFIES), + NADDR (heartbeat), +#endif + /* + ** Make data structure address invalid. + ** clear SIGP. + */ + SCR_LOAD_REG (dsa, 0xff), + 0, + SCR_FROM_REG (ctest2), + 0, +}/*-------------------------< START0 >----------------------*/,{ + /* + ** Hook for interrupted GetConditionCode. + ** Will be patched to ... IFTRUE by + ** the interrupt handler. + */ + SCR_INT ^ IFFALSE (0), + SIR_SENSE_RESTART, + +}/*-------------------------< START1 >----------------------*/,{ + /* + ** Hook for stalled start queue. + ** Will be patched to IFTRUE by the interrupt handler. + */ + SCR_INT ^ IFFALSE (0), + SIR_STALL_RESTART, + /* + ** Then jump to a certain point in tryloop. + ** Due to the lack of indirect addressing the code + ** is self modifying here. + */ + SCR_JUMP, +}/*-------------------------< STARTPOS >--------------------*/,{ + PADDRH(tryloop), +}/*-------------------------< TRYSEL >----------------------*/,{ + /* + ** Now: + ** DSA: Address of a Data Structure + ** or Address of the IDLE-Label. + ** + ** TEMP: Address of a script, which tries to + ** start the NEXT entry. + ** + ** Save the TEMP register into the SCRATCHA register. + ** Then copy the DSA to TEMP and RETURN. + ** This is kind of an indirect jump. + ** (The script processor has NO stack, so the + ** CALL is actually a jump and link, and the + ** RETURN is an indirect jump.) + ** + ** If the slot was empty, DSA contains the address + ** of the IDLE part of this script. The processor + ** jumps to IDLE and waits for a reselect. + ** It will wake up and try the same slot again + ** after the SIGP bit becomes set by the host. + ** + ** If the slot was not empty, DSA contains + ** the address of the phys-part of a ccb. + ** The processor jumps to this address. + ** phys starts with head, + ** head starts with launch, + ** so actually the processor jumps to + ** the lauch part. + ** If the entry is scheduled for execution, + ** then launch contains a jump to SELECT. + ** If it's not scheduled, it contains a jump to IDLE. + */ + SCR_COPY (4), + RADDR (temp), + RADDR (scratcha), + SCR_COPY (4), + RADDR (dsa), + RADDR (temp), + SCR_RETURN, + 0 + +}/*-------------------------< SKIP >------------------------*/,{ + /* + ** This entry has been canceled. + ** Next time use the next slot. + */ + SCR_COPY (4), + RADDR (scratcha), + PADDR (startpos), + /* + ** patch the launch field. + ** should look like an idle process. + */ + SCR_COPY_F (4), + RADDR (dsa), + PADDR (skip2), + SCR_COPY (8), + PADDR (idle), +}/*-------------------------< SKIP2 >-----------------------*/,{ + 0, + SCR_JUMP, + PADDR(start), +}/*-------------------------< IDLE >------------------------*/,{ + /* + ** Nothing to do? + ** Wait for reselect. + */ + SCR_JUMP, + PADDR(reselect), + +}/*-------------------------< SELECT >----------------------*/,{ + /* + ** DSA contains the address of a scheduled + ** data structure. + ** + ** SCRATCHA contains the address of the script, + ** which starts the next entry. + ** + ** Set Initiator mode. + ** + ** (Target mode is left as an exercise for the reader) + */ + + SCR_CLR (SCR_TRG), + 0, + SCR_LOAD_REG (HS_REG, 0xff), + 0, + + /* + ** And try to select this target. + */ + SCR_SEL_TBL_ATN ^ offsetof (struct dsb, select), + PADDR (reselect), + + /* + ** Now there are 4 possibilities: + ** + ** (1) The ncr looses arbitration. + ** This is ok, because it will try again, + ** when the bus becomes idle. + ** (But beware of the timeout function!) + ** + ** (2) The ncr is reselected. + ** Then the script processor takes the jump + ** to the RESELECT label. + ** + ** (3) The ncr completes the selection. + ** Then it will execute the next statement. + ** + ** (4) There is a selection timeout. + ** Then the ncr should interrupt the host and stop. + ** Unfortunately, it seems to continue execution + ** of the script. But it will fail with an + ** IID-interrupt on the next WHEN. + */ + + SCR_JUMPR ^ IFTRUE (WHEN (SCR_MSG_IN)), + 0, + + /* + ** Save target id to ctest0 register + */ + + SCR_FROM_REG (sdid), + 0, + SCR_TO_REG (ctest0), + 0, + /* + ** Send the IDENTIFY and SIMPLE_TAG messages + ** (and the M_X_SYNC_REQ message) + */ + SCR_MOVE_TBL ^ SCR_MSG_OUT, + offsetof (struct dsb, smsg), +#ifdef undef /* XXX better fail than try to deal with this ... */ + SCR_JUMPR ^ IFTRUE (WHEN (SCR_MSG_OUT)), + -16, +#endif + SCR_CLR (SCR_ATN), + 0, + SCR_COPY (1), + RADDR (sfbr), + NADDR (lastmsg), + /* + ** Selection complete. + ** Next time use the next slot. + */ + SCR_COPY (4), + RADDR (scratcha), + PADDR (startpos), +}/*-------------------------< PREPARE >----------------------*/,{ + /* + ** The ncr doesn't have an indirect load + ** or store command. So we have to + ** copy part of the control block to a + ** fixed place, where we can access it. + ** + ** We patch the address part of a + ** COPY command with the DSA-register. + */ + SCR_COPY_F (4), + RADDR (dsa), + PADDR (loadpos), + /* + ** then we do the actual copy. + */ + SCR_COPY (sizeof (struct head)), + /* + ** continued after the next label ... + */ + +}/*-------------------------< LOADPOS >---------------------*/,{ + 0, + NADDR (header), + /* + ** Mark this ccb as not scheduled. + */ + SCR_COPY (8), + PADDR (idle), + NADDR (header.launch), + /* + ** Set a time stamp for this selection + */ + SCR_COPY (sizeof (u_long)), + KVAR(SCRIPT_KVAR_JIFFIES), + NADDR (header.stamp.select), + /* + ** load the savep (saved pointer) into + ** the TEMP register (actual pointer) + */ + SCR_COPY (4), + NADDR (header.savep), + RADDR (temp), + /* + ** Initialize the status registers + */ + SCR_COPY (4), + NADDR (header.status), + RADDR (scr0), + +}/*-------------------------< PREPARE2 >---------------------*/,{ + /* + ** Load the synchronous mode register + */ + SCR_COPY (1), + NADDR (sync_st), + RADDR (sxfer), + /* + ** Load the wide mode and timing register + */ + SCR_COPY (1), + NADDR (wide_st), + RADDR (scntl3), + /* + ** Initialize the msgout buffer with a NOOP message. + */ + SCR_LOAD_REG (scratcha, M_NOOP), + 0, + SCR_COPY (1), + RADDR (scratcha), + NADDR (msgout), + SCR_COPY (1), + RADDR (scratcha), + NADDR (msgin), + /* + ** Message in phase ? + */ + SCR_JUMP ^ IFFALSE (WHEN (SCR_MSG_IN)), + PADDR (dispatch), + /* + ** Extended or reject message ? + */ + SCR_FROM_REG (sbdl), + 0, + SCR_JUMP ^ IFTRUE (DATA (M_EXTENDED)), + PADDR (msg_in), + SCR_JUMP ^ IFTRUE (DATA (M_REJECT)), + PADDRH (msg_reject), + /* + ** normal processing + */ + SCR_JUMP, + PADDR (dispatch), +}/*-------------------------< SETMSG >----------------------*/,{ + SCR_COPY (1), + RADDR (scratcha), + NADDR (msgout), + SCR_SET (SCR_ATN), + 0, +}/*-------------------------< CLRACK >----------------------*/,{ + /* + ** Terminate possible pending message phase. + */ + SCR_CLR (SCR_ACK), + 0, + +}/*-----------------------< DISPATCH >----------------------*/,{ + SCR_FROM_REG (HS_REG), + 0, + SCR_INT ^ IFTRUE (DATA (HS_NEGOTIATE)), + SIR_NEGO_FAILED, + /* + ** remove bogus output signals + */ + SCR_REG_REG (socl, SCR_AND, CACK|CATN), + 0, + SCR_RETURN ^ IFTRUE (WHEN (SCR_DATA_OUT)), + 0, + /* + ** DEL 397 - 53C875 Rev 3 - Part Number 609-0392410 - ITEM 4. + ** Possible data corruption during Memory Write and Invalidate. + ** This work-around resets the addressing logic prior to the + ** start of the first MOVE of a DATA IN phase. + ** (See README.ncr53c8xx for more information) + */ + SCR_JUMPR ^ IFFALSE (IF (SCR_DATA_IN)), + 20, + SCR_COPY (4), + RADDR (scratcha), + RADDR (scratcha), + SCR_RETURN, + 0, + + SCR_JUMP ^ IFTRUE (IF (SCR_MSG_OUT)), + PADDR (msg_out), + SCR_JUMP ^ IFTRUE (IF (SCR_MSG_IN)), + PADDR (msg_in), + SCR_JUMP ^ IFTRUE (IF (SCR_COMMAND)), + PADDR (command), + SCR_JUMP ^ IFTRUE (IF (SCR_STATUS)), + PADDR (status), + /* + ** Discard one illegal phase byte, if required. + */ + SCR_LOAD_REG (scratcha, XE_BAD_PHASE), + 0, + SCR_COPY (1), + RADDR (scratcha), + NADDR (xerr_st), + SCR_JUMPR ^ IFFALSE (IF (SCR_ILG_OUT)), + 8, + SCR_MOVE_ABS (1) ^ SCR_ILG_OUT, + NADDR (scratch), + SCR_JUMPR ^ IFFALSE (IF (SCR_ILG_IN)), + 8, + SCR_MOVE_ABS (1) ^ SCR_ILG_IN, + NADDR (scratch), + SCR_JUMP, + PADDR (dispatch), + +}/*-------------------------< NO_DATA >--------------------*/,{ + /* + ** The target wants to tranfer too much data + ** or in the wrong direction. + ** Remember that in extended error. + */ + SCR_LOAD_REG (scratcha, XE_EXTRA_DATA), + 0, + SCR_COPY (1), + RADDR (scratcha), + NADDR (xerr_st), + /* + ** Discard one data byte, if required. + */ + SCR_JUMPR ^ IFFALSE (WHEN (SCR_DATA_OUT)), + 8, + SCR_MOVE_ABS (1) ^ SCR_DATA_OUT, + NADDR (scratch), + SCR_JUMPR ^ IFFALSE (IF (SCR_DATA_IN)), + 8, + SCR_MOVE_ABS (1) ^ SCR_DATA_IN, + NADDR (scratch), + /* + ** .. and repeat as required. + */ + SCR_CALL, + PADDR (dispatch), + SCR_JUMP, + PADDR (no_data), +}/*-------------------------< CHECKATN >--------------------*/,{ + /* + ** If AAP (bit 1 of scntl0 register) is set + ** and a parity error is detected, + ** the script processor asserts ATN. + ** + ** The target should switch to a MSG_OUT phase + ** to get the message. + */ + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFFALSE (MASK (CATN, CATN)), + PADDR (dispatch), + /* + ** count it + */ + SCR_REG_REG (PS_REG, SCR_ADD, 1), + 0, + /* + ** Prepare a M_ID_ERROR message + ** (initiator detected error). + ** The target should retry the transfer. + */ + SCR_LOAD_REG (scratcha, M_ID_ERROR), + 0, + SCR_JUMP, + PADDR (setmsg), + +}/*-------------------------< COMMAND >--------------------*/,{ + /* + ** If this is not a GETCC transfer ... + */ + SCR_FROM_REG (SS_REG), + 0, +/*<<<*/ SCR_JUMPR ^ IFTRUE (DATA (S_CHECK_COND)), + 28, + /* + ** ... set a timestamp ... + */ + SCR_COPY (sizeof (u_long)), + KVAR(SCRIPT_KVAR_JIFFIES), + NADDR (header.stamp.command), + /* + ** ... and send the command + */ + SCR_MOVE_TBL ^ SCR_COMMAND, + offsetof (struct dsb, cmd), + SCR_JUMP, + PADDR (dispatch), + /* + ** Send the GETCC command + */ +/*>>>*/ SCR_MOVE_TBL ^ SCR_COMMAND, + offsetof (struct dsb, scmd), + SCR_JUMP, + PADDR (dispatch), + +}/*-------------------------< STATUS >--------------------*/,{ + /* + ** set the timestamp. + */ + SCR_COPY (sizeof (u_long)), + KVAR(SCRIPT_KVAR_JIFFIES), + NADDR (header.stamp.status), + /* + ** If this is a GETCC transfer, + */ + SCR_FROM_REG (SS_REG), + 0, +/*<<<*/ SCR_JUMPR ^ IFFALSE (DATA (S_CHECK_COND)), + 40, + /* + ** get the status + */ + SCR_MOVE_ABS (1) ^ SCR_STATUS, + NADDR (scratch), + /* + ** Save status to scsi_status. + ** Mark as complete. + ** And wait for disconnect. + */ + SCR_TO_REG (SS_REG), + 0, + SCR_REG_REG (SS_REG, SCR_OR, S_SENSE), + 0, + SCR_LOAD_REG (HS_REG, HS_COMPLETE), + 0, + SCR_JUMP, + PADDR (checkatn), + /* + ** If it was no GETCC transfer, + ** save the status to scsi_status. + */ +/*>>>*/ SCR_MOVE_ABS (1) ^ SCR_STATUS, + NADDR (scratch), + SCR_TO_REG (SS_REG), + 0, + /* + ** if it was no check condition ... + */ + SCR_JUMP ^ IFTRUE (DATA (S_CHECK_COND)), + PADDR (checkatn), + /* + ** ... mark as complete. + */ + SCR_LOAD_REG (HS_REG, HS_COMPLETE), + 0, + SCR_JUMP, + PADDR (checkatn), + +}/*-------------------------< MSG_IN >--------------------*/,{ + /* + ** Get the first byte of the message + ** and save it to SCRATCHA. + ** + ** The script processor doesn't negate the + ** ACK signal after this transfer. + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin[0]), + /* + ** Check for message parity error. + */ + SCR_TO_REG (scratcha), + 0, + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFTRUE (MASK (CATN, CATN)), + PADDRH (msg_parity), + SCR_FROM_REG (scratcha), + 0, + /* + ** Parity was ok, handle this message. + */ + SCR_JUMP ^ IFTRUE (DATA (M_COMPLETE)), + PADDR (complete), + SCR_JUMP ^ IFTRUE (DATA (M_SAVE_DP)), + PADDR (save_dp), + SCR_JUMP ^ IFTRUE (DATA (M_RESTORE_DP)), + PADDR (restore_dp), + SCR_JUMP ^ IFTRUE (DATA (M_DISCONNECT)), + PADDR (disconnect), + SCR_JUMP ^ IFTRUE (DATA (M_EXTENDED)), + PADDRH (msg_extended), + SCR_JUMP ^ IFTRUE (DATA (M_NOOP)), + PADDR (clrack), + SCR_JUMP ^ IFTRUE (DATA (M_REJECT)), + PADDRH (msg_reject), + SCR_JUMP ^ IFTRUE (DATA (M_IGN_RESIDUE)), + PADDRH (msg_ign_residue), + /* + ** Rest of the messages left as + ** an exercise ... + ** + ** Unimplemented messages: + ** fall through to MSG_BAD. + */ +}/*-------------------------< MSG_BAD >------------------*/,{ + /* + ** unimplemented message - reject it. + */ + SCR_INT, + SIR_REJECT_SENT, + SCR_LOAD_REG (scratcha, M_REJECT), + 0, + SCR_JUMP, + PADDR (setmsg), + +}/*-------------------------< COMPLETE >-----------------*/,{ + /* + ** Complete message. + ** + ** If it's not the get condition code, + ** copy TEMP register to LASTP in header. + */ + SCR_FROM_REG (SS_REG), + 0, +/*<<<*/ SCR_JUMPR ^ IFTRUE (MASK (S_SENSE, S_SENSE)), + 12, + SCR_COPY (4), + RADDR (temp), + NADDR (header.lastp), +/*>>>*/ /* + ** When we terminate the cycle by clearing ACK, + ** the target may disconnect immediately. + ** + ** We don't want to be told of an + ** "unexpected disconnect", + ** so we disable this feature. + */ + SCR_REG_REG (scntl2, SCR_AND, 0x7f), + 0, + /* + ** Terminate cycle ... + */ + SCR_CLR (SCR_ACK|SCR_ATN), + 0, + /* + ** ... and wait for the disconnect. + */ + SCR_WAIT_DISC, + 0, +}/*-------------------------< CLEANUP >-------------------*/,{ + /* + ** dsa: Pointer to ccb + ** or xxxxxxFF (no ccb) + ** + ** HS_REG: Host-Status (<>0!) + */ + SCR_FROM_REG (dsa), + 0, + SCR_JUMP ^ IFTRUE (DATA (0xff)), + PADDR (signal), + /* + ** dsa is valid. + ** save the status registers + */ + SCR_COPY (4), + RADDR (scr0), + NADDR (header.status), + /* + ** and copy back the header to the ccb. + */ + SCR_COPY_F (4), + RADDR (dsa), + PADDR (cleanup0), + SCR_COPY (sizeof (struct head)), + NADDR (header), +}/*-------------------------< CLEANUP0 >--------------------*/,{ + 0, + + /* + ** If command resulted in "check condition" + ** status and is not yet completed, + ** try to get the condition code. + */ + SCR_FROM_REG (HS_REG), + 0, +/*<<<*/ SCR_JUMPR ^ IFFALSE (MASK (0, HS_DONEMASK)), + 16, + SCR_FROM_REG (SS_REG), + 0, + SCR_JUMP ^ IFTRUE (DATA (S_CHECK_COND)), + PADDRH(getcc2), + /* + ** And make the DSA register invalid. + */ +/*>>>*/ SCR_LOAD_REG (dsa, 0xff), /* invalid */ + 0, +}/*-------------------------< SIGNAL >----------------------*/,{ + /* + ** if status = queue full, + ** reinsert in startqueue and stall queue. + */ + SCR_FROM_REG (SS_REG), + 0, + SCR_INT ^ IFTRUE (DATA (S_QUEUE_FULL)), + SIR_STALL_QUEUE, + /* + ** if job completed ... + */ + SCR_FROM_REG (HS_REG), + 0, + /* + ** ... signal completion to the host + */ + SCR_INT_FLY ^ IFFALSE (MASK (0, HS_DONEMASK)), + 0, + /* + ** Auf zu neuen Schandtaten! + */ + SCR_JUMP, + PADDR(start), + +}/*-------------------------< SAVE_DP >------------------*/,{ + /* + ** SAVE_DP message: + ** Copy TEMP register to SAVEP in header. + */ + SCR_COPY (4), + RADDR (temp), + NADDR (header.savep), + SCR_JUMP, + PADDR (clrack), +}/*-------------------------< RESTORE_DP >---------------*/,{ + /* + ** RESTORE_DP message: + ** Copy SAVEP in header to TEMP register. + */ + SCR_COPY (4), + NADDR (header.savep), + RADDR (temp), + SCR_JUMP, + PADDR (clrack), + +}/*-------------------------< DISCONNECT >---------------*/,{ + /* + ** If QUIRK_AUTOSAVE is set, + ** do an "save pointer" operation. + */ + SCR_FROM_REG (QU_REG), + 0, +/*<<<*/ SCR_JUMPR ^ IFFALSE (MASK (QUIRK_AUTOSAVE, QUIRK_AUTOSAVE)), + 12, + /* + ** like SAVE_DP message: + ** Copy TEMP register to SAVEP in header. + */ + SCR_COPY (4), + RADDR (temp), + NADDR (header.savep), +/*>>>*/ /* + ** Check if temp==savep or temp==goalp: + ** if not, log a missing save pointer message. + ** In fact, it's a comparison mod 256. + ** + ** Hmmm, I hadn't thought that I would be urged to + ** write this kind of ugly self modifying code. + ** + ** It's unbelievable, but the ncr53c8xx isn't able + ** to subtract one register from another. + */ + SCR_FROM_REG (temp), + 0, + /* + ** You are not expected to understand this .. + ** + ** CAUTION: only little endian architectures supported! XXX + */ + SCR_COPY_F (1), + NADDR (header.savep), + PADDR (disconnect0), +}/*-------------------------< DISCONNECT0 >--------------*/,{ +/*<<<*/ SCR_JUMPR ^ IFTRUE (DATA (1)), + 20, + /* + ** neither this + */ + SCR_COPY_F (1), + NADDR (header.goalp), + PADDR (disconnect1), +}/*-------------------------< DISCONNECT1 >--------------*/,{ + SCR_INT ^ IFFALSE (DATA (1)), + SIR_MISSING_SAVE, +/*>>>*/ + + /* + ** DISCONNECTing ... + ** + ** disable the "unexpected disconnect" feature, + ** and remove the ACK signal. + */ + SCR_REG_REG (scntl2, SCR_AND, 0x7f), + 0, + SCR_CLR (SCR_ACK|SCR_ATN), + 0, + /* + ** Wait for the disconnect. + */ + SCR_WAIT_DISC, + 0, + /* + ** Profiling: + ** Set a time stamp, + ** and count the disconnects. + */ + SCR_COPY (sizeof (u_long)), + KVAR(SCRIPT_KVAR_JIFFIES), + NADDR (header.stamp.disconnect), + SCR_COPY (4), + NADDR (disc_phys), + RADDR (temp), + SCR_REG_REG (temp, SCR_ADD, 0x01), + 0, + SCR_COPY (4), + RADDR (temp), + NADDR (disc_phys), + /* + ** Status is: DISCONNECTED. + */ + SCR_LOAD_REG (HS_REG, HS_DISCONNECT), + 0, + SCR_JUMP, + PADDR (cleanup), + +}/*-------------------------< MSG_OUT >-------------------*/,{ + /* + ** The target requests a message. + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_OUT, + NADDR (msgout), + SCR_COPY (1), + RADDR (sfbr), + NADDR (lastmsg), + /* + ** If it was no ABORT message ... + */ + SCR_JUMP ^ IFTRUE (DATA (M_ABORT)), + PADDRH (msg_out_abort), + /* + ** ... wait for the next phase + ** if it's a message out, send it again, ... + */ + SCR_JUMP ^ IFTRUE (WHEN (SCR_MSG_OUT)), + PADDR (msg_out), +}/*-------------------------< MSG_OUT_DONE >--------------*/,{ + /* + ** ... else clear the message ... + */ + SCR_LOAD_REG (scratcha, M_NOOP), + 0, + SCR_COPY (4), + RADDR (scratcha), + NADDR (msgout), + /* + ** ... and process the next phase + */ + SCR_JUMP, + PADDR (dispatch), +}/*------------------------< BADGETCC >---------------------*/,{ + /* + ** If SIGP was set, clear it and try again. + */ + SCR_FROM_REG (ctest2), + 0, + SCR_JUMP ^ IFTRUE (MASK (CSIGP,CSIGP)), + PADDRH (getcc2), + SCR_INT, + SIR_SENSE_FAILED, +}/*-------------------------< RESELECT >--------------------*/,{ + /* + ** This NOP will be patched with LED OFF + ** SCR_REG_REG (gpreg, SCR_OR, 0x01) + */ + SCR_NO_OP, + 0, + /* + ** make the DSA invalid. + */ + SCR_LOAD_REG (dsa, 0xff), + 0, + SCR_CLR (SCR_TRG), + 0, + /* + ** Sleep waiting for a reselection. + ** If SIGP is set, special treatment. + ** + ** Zu allem bereit .. + */ + SCR_WAIT_RESEL, + PADDR(reselect2), +}/*-------------------------< RESELECT1 >--------------------*/,{ + /* + ** This NOP will be patched with LED ON + ** SCR_REG_REG (gpreg, SCR_AND, 0xfe) + */ + SCR_NO_OP, + 0, + /* + ** ... zu nichts zu gebrauchen ? + ** + ** load the target id into the SFBR + ** and jump to the control block. + ** + ** Look at the declarations of + ** - struct ncb + ** - struct tcb + ** - struct lcb + ** - struct ccb + ** to understand what's going on. + */ + SCR_REG_SFBR (ssid, SCR_AND, 0x8F), + 0, + SCR_TO_REG (ctest0), + 0, + SCR_JUMP, + NADDR (jump_tcb), +}/*-------------------------< RESELECT2 >-------------------*/,{ + /* + ** This NOP will be patched with LED ON + ** SCR_REG_REG (gpreg, SCR_AND, 0xfe) + */ + SCR_NO_OP, + 0, + /* + ** If it's not connected :( + ** -> interrupted by SIGP bit. + ** Jump to start. + */ + SCR_FROM_REG (ctest2), + 0, + SCR_JUMP ^ IFTRUE (MASK (CSIGP,CSIGP)), + PADDR (start), + SCR_JUMP, + PADDR (reselect), + +}/*-------------------------< RESEL_TMP >-------------------*/,{ + /* + ** The return address in TEMP + ** is in fact the data structure address, + ** so copy it to the DSA register. + */ + SCR_COPY (4), + RADDR (temp), + RADDR (dsa), + SCR_JUMP, + PADDR (prepare), + +}/*-------------------------< RESEL_LUN >-------------------*/,{ + /* + ** come back to this point + ** to get an IDENTIFY message + ** Wait for a msg_in phase. + */ +/*<<<*/ SCR_JUMPR ^ IFFALSE (WHEN (SCR_MSG_IN)), + 48, + /* + ** message phase + ** It's not a sony, it's a trick: + ** read the data without acknowledging it. + */ + SCR_FROM_REG (sbdl), + 0, +/*<<<*/ SCR_JUMPR ^ IFFALSE (MASK (M_IDENTIFY, 0x98)), + 32, + /* + ** It WAS an Identify message. + ** get it and ack it! + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin), + SCR_CLR (SCR_ACK), + 0, + /* + ** Mask out the lun. + */ + SCR_REG_REG (sfbr, SCR_AND, 0x07), + 0, + SCR_RETURN, + 0, + /* + ** No message phase or no IDENTIFY message: + ** return 0. + */ +/*>>>*/ SCR_LOAD_SFBR (0), + 0, + SCR_RETURN, + 0, + +}/*-------------------------< RESEL_TAG >-------------------*/,{ + /* + ** come back to this point + ** to get a SIMPLE_TAG message + ** Wait for a MSG_IN phase. + */ +/*<<<*/ SCR_JUMPR ^ IFFALSE (WHEN (SCR_MSG_IN)), + 64, + /* + ** message phase + ** It's a trick - read the data + ** without acknowledging it. + */ + SCR_FROM_REG (sbdl), + 0, +/*<<<*/ SCR_JUMPR ^ IFFALSE (DATA (M_SIMPLE_TAG)), + 48, + /* + ** It WAS a SIMPLE_TAG message. + ** get it and ack it! + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin), + SCR_CLR (SCR_ACK), + 0, + /* + ** Wait for the second byte (the tag) + */ +/*<<<*/ SCR_JUMPR ^ IFFALSE (WHEN (SCR_MSG_IN)), + 24, + /* + ** Get it and ack it! + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin), + SCR_CLR (SCR_ACK|SCR_CARRY), + 0, + SCR_RETURN, + 0, + /* + ** No message phase or no SIMPLE_TAG message + ** or no second byte: return 0. + */ +/*>>>*/ SCR_LOAD_SFBR (0), + 0, + SCR_SET (SCR_CARRY), + 0, + SCR_RETURN, + 0, + +}/*-------------------------< DATA_IO >--------------------*/,{ +/* +** Because Linux does not provide xfer data direction +** to low-level scsi drivers, we must trust the target +** for actual data direction when we cannot guess it. +** The programmed interrupt patches savep, lastp, goalp, +** etc.., and restarts the scsi script at data_out/in. +*/ + SCR_INT ^ IFTRUE (WHEN (SCR_DATA_OUT)), + SIR_DATA_IO_IS_OUT, + SCR_INT ^ IFTRUE (WHEN (SCR_DATA_IN)), + SIR_DATA_IO_IS_IN, + SCR_JUMP, + PADDR (no_data), + +}/*-------------------------< DATA_IN >--------------------*/,{ +/* +** Because the size depends on the +** #define MAX_SCATTER parameter, +** it is filled in at runtime. +** +** ##===========< i=0; i<MAX_SCATTER >========= +** || SCR_CALL ^ IFFALSE (WHEN (SCR_DATA_IN)), +** || PADDR (checkatn), +** || SCR_MOVE_TBL ^ SCR_DATA_IN, +** || offsetof (struct dsb, data[ i]), +** ##========================================== +** +** SCR_CALL, +** PADDR (checkatn), +** SCR_JUMP, +** PADDR (no_data), +*/ +0 +}/*--------------------------------------------------------*/ +}; + +static struct scripth scripth0 __initdata = { +/*-------------------------< TRYLOOP >---------------------*/{ +/* +** Load an entry of the start queue into dsa +** and try to start it by jumping to TRYSEL. +** +** Because the size depends on the +** #define MAX_START parameter, it is filled +** in at runtime. +** +**----------------------------------------------------------- +** +** ##===========< I=0; i<MAX_START >=========== +** || SCR_COPY (4), +** || NADDR (squeue[i]), +** || RADDR (dsa), +** || SCR_CALL, +** || PADDR (trysel), +** ##========================================== +** +** SCR_JUMP, +** PADDRH(tryloop), +** +**----------------------------------------------------------- +*/ +0 +},/*-------------------------< MSG_PARITY >---------------*/{ + /* + ** count it + */ + SCR_REG_REG (PS_REG, SCR_ADD, 0x01), + 0, + /* + ** send a "message parity error" message. + */ + SCR_LOAD_REG (scratcha, M_PARITY), + 0, + SCR_JUMP, + PADDR (setmsg), +}/*-------------------------< MSG_REJECT >---------------*/,{ + /* + ** If a negotiation was in progress, + ** negotiation failed. + */ + SCR_FROM_REG (HS_REG), + 0, + SCR_INT ^ IFTRUE (DATA (HS_NEGOTIATE)), + SIR_NEGO_FAILED, + /* + ** else make host log this message + */ + SCR_INT ^ IFFALSE (DATA (HS_NEGOTIATE)), + SIR_REJECT_RECEIVED, + SCR_JUMP, + PADDR (clrack), + +}/*-------------------------< MSG_IGN_RESIDUE >----------*/,{ + /* + ** Terminate cycle + */ + SCR_CLR (SCR_ACK), + 0, + SCR_JUMP ^ IFFALSE (WHEN (SCR_MSG_IN)), + PADDR (dispatch), + /* + ** get residue size. + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin[1]), + /* + ** Check for message parity error. + */ + SCR_TO_REG (scratcha), + 0, + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFTRUE (MASK (CATN, CATN)), + PADDRH (msg_parity), + SCR_FROM_REG (scratcha), + 0, + /* + ** Size is 0 .. ignore message. + */ + SCR_JUMP ^ IFTRUE (DATA (0)), + PADDR (clrack), + /* + ** Size is not 1 .. have to interrupt. + */ +/*<<<*/ SCR_JUMPR ^ IFFALSE (DATA (1)), + 40, + /* + ** Check for residue byte in swide register + */ + SCR_FROM_REG (scntl2), + 0, +/*<<<*/ SCR_JUMPR ^ IFFALSE (MASK (WSR, WSR)), + 16, + /* + ** There IS data in the swide register. + ** Discard it. + */ + SCR_REG_REG (scntl2, SCR_OR, WSR), + 0, + SCR_JUMP, + PADDR (clrack), + /* + ** Load again the size to the sfbr register. + */ +/*>>>*/ SCR_FROM_REG (scratcha), + 0, +/*>>>*/ SCR_INT, + SIR_IGN_RESIDUE, + SCR_JUMP, + PADDR (clrack), + +}/*-------------------------< MSG_EXTENDED >-------------*/,{ + /* + ** Terminate cycle + */ + SCR_CLR (SCR_ACK), + 0, + SCR_JUMP ^ IFFALSE (WHEN (SCR_MSG_IN)), + PADDR (dispatch), + /* + ** get length. + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin[1]), + /* + ** Check for message parity error. + */ + SCR_TO_REG (scratcha), + 0, + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFTRUE (MASK (CATN, CATN)), + PADDRH (msg_parity), + SCR_FROM_REG (scratcha), + 0, + /* + */ + SCR_JUMP ^ IFTRUE (DATA (3)), + PADDRH (msg_ext_3), + SCR_JUMP ^ IFFALSE (DATA (2)), + PADDR (msg_bad), +}/*-------------------------< MSG_EXT_2 >----------------*/,{ + SCR_CLR (SCR_ACK), + 0, + SCR_JUMP ^ IFFALSE (WHEN (SCR_MSG_IN)), + PADDR (dispatch), + /* + ** get extended message code. + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin[2]), + /* + ** Check for message parity error. + */ + SCR_TO_REG (scratcha), + 0, + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFTRUE (MASK (CATN, CATN)), + PADDRH (msg_parity), + SCR_FROM_REG (scratcha), + 0, + SCR_JUMP ^ IFTRUE (DATA (M_X_WIDE_REQ)), + PADDRH (msg_wdtr), + /* + ** unknown extended message + */ + SCR_JUMP, + PADDR (msg_bad) +}/*-------------------------< MSG_WDTR >-----------------*/,{ + SCR_CLR (SCR_ACK), + 0, + SCR_JUMP ^ IFFALSE (WHEN (SCR_MSG_IN)), + PADDR (dispatch), + /* + ** get data bus width + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin[3]), + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFTRUE (MASK (CATN, CATN)), + PADDRH (msg_parity), + /* + ** let the host do the real work. + */ + SCR_INT, + SIR_NEGO_WIDE, + /* + ** let the target fetch our answer. + */ + SCR_SET (SCR_ATN), + 0, + SCR_CLR (SCR_ACK), + 0, + + SCR_INT ^ IFFALSE (WHEN (SCR_MSG_OUT)), + SIR_NEGO_PROTO, + /* + ** Send the M_X_WIDE_REQ + */ + SCR_MOVE_ABS (4) ^ SCR_MSG_OUT, + NADDR (msgout), + SCR_CLR (SCR_ATN), + 0, + SCR_COPY (1), + RADDR (sfbr), + NADDR (lastmsg), + SCR_JUMP, + PADDR (msg_out_done), + +}/*-------------------------< MSG_EXT_3 >----------------*/,{ + SCR_CLR (SCR_ACK), + 0, + SCR_JUMP ^ IFFALSE (WHEN (SCR_MSG_IN)), + PADDR (dispatch), + /* + ** get extended message code. + */ + SCR_MOVE_ABS (1) ^ SCR_MSG_IN, + NADDR (msgin[2]), + /* + ** Check for message parity error. + */ + SCR_TO_REG (scratcha), + 0, + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFTRUE (MASK (CATN, CATN)), + PADDRH (msg_parity), + SCR_FROM_REG (scratcha), + 0, + SCR_JUMP ^ IFTRUE (DATA (M_X_SYNC_REQ)), + PADDRH (msg_sdtr), + /* + ** unknown extended message + */ + SCR_JUMP, + PADDR (msg_bad) + +}/*-------------------------< MSG_SDTR >-----------------*/,{ + SCR_CLR (SCR_ACK), + 0, + SCR_JUMP ^ IFFALSE (WHEN (SCR_MSG_IN)), + PADDR (dispatch), + /* + ** get period and offset + */ + SCR_MOVE_ABS (2) ^ SCR_MSG_IN, + NADDR (msgin[3]), + SCR_FROM_REG (socl), + 0, + SCR_JUMP ^ IFTRUE (MASK (CATN, CATN)), + PADDRH (msg_parity), + /* + ** let the host do the real work. + */ + SCR_INT, + SIR_NEGO_SYNC, + /* + ** let the target fetch our answer. + */ + SCR_SET (SCR_ATN), + 0, + SCR_CLR (SCR_ACK), + 0, + + SCR_INT ^ IFFALSE (WHEN (SCR_MSG_OUT)), + SIR_NEGO_PROTO, + /* + ** Send the M_X_SYNC_REQ + */ + SCR_MOVE_ABS (5) ^ SCR_MSG_OUT, + NADDR (msgout), + SCR_CLR (SCR_ATN), + 0, + SCR_COPY (1), + RADDR (sfbr), + NADDR (lastmsg), + SCR_JUMP, + PADDR (msg_out_done), + +}/*-------------------------< MSG_OUT_ABORT >-------------*/,{ + /* + ** After ABORT message, + ** + ** expect an immediate disconnect, ... + */ + SCR_REG_REG (scntl2, SCR_AND, 0x7f), + 0, + SCR_CLR (SCR_ACK|SCR_ATN), + 0, + SCR_WAIT_DISC, + 0, + /* + ** ... and set the status to "ABORTED" + */ + SCR_LOAD_REG (HS_REG, HS_ABORTED), + 0, + SCR_JUMP, + PADDR (cleanup), + +}/*-------------------------< GETCC >-----------------------*/,{ + /* + ** The ncr doesn't have an indirect load + ** or store command. So we have to + ** copy part of the control block to a + ** fixed place, where we can modify it. + ** + ** We patch the address part of a COPY command + ** with the address of the dsa register ... + */ + SCR_COPY_F (4), + RADDR (dsa), + PADDRH (getcc1), + /* + ** ... then we do the actual copy. + */ + SCR_COPY (sizeof (struct head)), +}/*-------------------------< GETCC1 >----------------------*/,{ + 0, + NADDR (header), + /* + ** Initialize the status registers + */ + SCR_COPY (4), + NADDR (header.status), + RADDR (scr0), +}/*-------------------------< GETCC2 >----------------------*/,{ + /* + ** Get the condition code from a target. + ** + ** DSA points to a data structure. + ** Set TEMP to the script location + ** that receives the condition code. + ** + ** Because there is no script command + ** to load a longword into a register, + ** we use a CALL command. + */ +/*<<<*/ SCR_CALLR, + 24, + /* + ** Get the condition code. + */ + SCR_MOVE_TBL ^ SCR_DATA_IN, + offsetof (struct dsb, sense), + /* + ** No data phase may follow! + */ + SCR_CALL, + PADDR (checkatn), + SCR_JUMP, + PADDR (no_data), +/*>>>*/ + + /* + ** The CALL jumps to this point. + ** Prepare for a RESTORE_POINTER message. + ** Save the TEMP register into the saved pointer. + */ + SCR_COPY (4), + RADDR (temp), + NADDR (header.savep), + /* + ** Load scratcha, because in case of a selection timeout, + ** the host will expect a new value for startpos in + ** the scratcha register. + */ + SCR_COPY (4), + PADDR (startpos), + RADDR (scratcha), +#ifdef NCR_GETCC_WITHMSG + /* + ** If QUIRK_NOMSG is set, select without ATN. + ** and don't send a message. + */ + SCR_FROM_REG (QU_REG), + 0, + SCR_JUMP ^ IFTRUE (MASK (QUIRK_NOMSG, QUIRK_NOMSG)), + PADDRH(getcc3), + /* + ** Then try to connect to the target. + ** If we are reselected, special treatment + ** of the current job is required before + ** accepting the reselection. + */ + SCR_SEL_TBL_ATN ^ offsetof (struct dsb, select), + PADDR(badgetcc), + /* + ** save target id. + */ + SCR_FROM_REG (sdid), + 0, + SCR_TO_REG (ctest0), + 0, + /* + ** Send the IDENTIFY message. + ** In case of short transfer, remove ATN. + */ + SCR_MOVE_TBL ^ SCR_MSG_OUT, + offsetof (struct dsb, smsg2), + SCR_CLR (SCR_ATN), + 0, + /* + ** save the first byte of the message. + */ + SCR_COPY (1), + RADDR (sfbr), + NADDR (lastmsg), + SCR_JUMP, + PADDR (prepare2), + +#endif +}/*-------------------------< GETCC3 >----------------------*/,{ + /* + ** Try to connect to the target. + ** If we are reselected, special treatment + ** of the current job is required before + ** accepting the reselection. + ** + ** Silly target won't accept a message. + ** Select without ATN. + */ + SCR_SEL_TBL ^ offsetof (struct dsb, select), + PADDR(badgetcc), + /* + ** save target id. + */ + SCR_FROM_REG (sdid), + 0, + SCR_TO_REG (ctest0), + 0, + /* + ** Force error if selection timeout + */ + SCR_JUMPR ^ IFTRUE (WHEN (SCR_MSG_IN)), + 0, + /* + ** don't negotiate. + */ + SCR_JUMP, + PADDR (prepare2), + +}/*-------------------------< DATA_OUT >-------------------*/,{ +/* +** Because the size depends on the +** #define MAX_SCATTER parameter, +** it is filled in at runtime. +** +** ##===========< i=0; i<MAX_SCATTER >========= +** || SCR_CALL ^ IFFALSE (WHEN (SCR_DATA_OUT)), +** || PADDR (dispatch), +** || SCR_MOVE_TBL ^ SCR_DATA_OUT, +** || offsetof (struct dsb, data[ i]), +** ##========================================== +** +** SCR_CALL, +** PADDR (dispatch), +** SCR_JUMP, +** PADDR (no_data), +** +**--------------------------------------------------------- +*/ +0 +}/*-------------------------< ABORTTAG >-------------------*/,{ + /* + ** Abort a bad reselection. + ** Set the message to ABORT vs. ABORT_TAG + */ + SCR_LOAD_REG (scratcha, M_ABORT_TAG), + 0, + SCR_JUMPR ^ IFFALSE (CARRYSET), + 8, +}/*-------------------------< ABORT >----------------------*/,{ + SCR_LOAD_REG (scratcha, M_ABORT), + 0, + SCR_COPY (1), + RADDR (scratcha), + NADDR (msgout), + SCR_SET (SCR_ATN), + 0, + SCR_CLR (SCR_ACK), + 0, + /* + ** and send it. + ** we expect an immediate disconnect + */ + SCR_REG_REG (scntl2, SCR_AND, 0x7f), + 0, + SCR_MOVE_ABS (1) ^ SCR_MSG_OUT, + NADDR (msgout), + SCR_COPY (1), + RADDR (sfbr), + NADDR (lastmsg), + SCR_CLR (SCR_ACK|SCR_ATN), + 0, + SCR_WAIT_DISC, + 0, + SCR_JUMP, + PADDR (start), +}/*-------------------------< SNOOPTEST >-------------------*/,{ + /* + ** Read the variable. + */ + SCR_COPY (4), + NADDR(ncr_cache), + RADDR (scratcha), + /* + ** Write the variable. + */ + SCR_COPY (4), + RADDR (temp), + NADDR(ncr_cache), + /* + ** Read back the variable. + */ + SCR_COPY (4), + NADDR(ncr_cache), + RADDR (temp), +}/*-------------------------< SNOOPEND >-------------------*/,{ + /* + ** And stop. + */ + SCR_INT, + 99, +}/*--------------------------------------------------------*/ +}; + +/*========================================================== +** +** +** Fill in #define dependent parts of the script +** +** +**========================================================== +*/ + +__initfunc( +void ncr_script_fill (struct script * scr, struct scripth * scrh) +) +{ + int i; + ncrcmd *p; + + p = scrh->tryloop; + for (i=0; i<MAX_START; i++) { + *p++ =SCR_COPY (4); + *p++ =NADDR (squeue[i]); + *p++ =RADDR (dsa); + *p++ =SCR_CALL; + *p++ =PADDR (trysel); + }; + *p++ =SCR_JUMP; + *p++ =PADDRH(tryloop); + + assert ((u_long)p == (u_long)&scrh->tryloop + sizeof (scrh->tryloop)); + + p = scr->data_in; + + for (i=0; i<MAX_SCATTER; i++) { + *p++ =SCR_CALL ^ IFFALSE (WHEN (SCR_DATA_IN)); + *p++ =PADDR (checkatn); + *p++ =SCR_MOVE_TBL ^ SCR_DATA_IN; + *p++ =offsetof (struct dsb, data[i]); + }; + + *p++ =SCR_CALL; + *p++ =PADDR (checkatn); + *p++ =SCR_JUMP; + *p++ =PADDR (no_data); + + assert ((u_long)p == (u_long)&scr->data_in + sizeof (scr->data_in)); + + p = scrh->data_out; + + for (i=0; i<MAX_SCATTER; i++) { + *p++ =SCR_CALL ^ IFFALSE (WHEN (SCR_DATA_OUT)); + *p++ =PADDR (dispatch); + *p++ =SCR_MOVE_TBL ^ SCR_DATA_OUT; + *p++ =offsetof (struct dsb, data[i]); + }; + + *p++ =SCR_CALL; + *p++ =PADDR (dispatch); + *p++ =SCR_JUMP; + *p++ =PADDR (no_data); + + assert ((u_long)p == (u_long)&scrh->data_out + sizeof (scrh->data_out)); +} + +/*========================================================== +** +** +** Copy and rebind a script. +** +** +**========================================================== +*/ + +__initfunc( +static void ncr_script_copy_and_bind (ncb_p np, ncrcmd *src, ncrcmd *dst, int len) +) +{ + ncrcmd opcode, new, old, tmp1, tmp2; + ncrcmd *start, *end; + int relocs; + int opchanged = 0; + + start = src; + end = src + len/4; + + while (src < end) { + + opcode = *src++; + *dst++ = cpu_to_scr(opcode); + + /* + ** If we forget to change the length + ** in struct script, a field will be + ** padded with 0. This is an illegal + ** command. + */ + + if (opcode == 0) { + printf ("%s: ERROR0 IN SCRIPT at %d.\n", + ncr_name(np), (int) (src-start-1)); + DELAY (1000000); + }; + + if (DEBUG_FLAGS & DEBUG_SCRIPT) + printf ("%p: <%x>\n", + (src-1), (unsigned)opcode); + + /* + ** We don't have to decode ALL commands + */ + switch (opcode >> 28) { + + case 0xc: + /* + ** COPY has TWO arguments. + */ + relocs = 2; + tmp1 = src[0]; + if ((tmp1 & RELOC_MASK) == RELOC_KVAR) + tmp1 = 0; + tmp2 = src[1]; + if ((tmp2 & RELOC_MASK) == RELOC_KVAR) + tmp2 = 0; + if ((tmp1 ^ tmp2) & 3) { + printf ("%s: ERROR1 IN SCRIPT at %d.\n", + ncr_name(np), (int) (src-start-1)); + DELAY (1000000); + } + /* + ** If PREFETCH feature not enabled, remove + ** the NO FLUSH bit if present. + */ + if ((opcode & SCR_NO_FLUSH) && !(np->features & FE_PFEN)) { + dst[-1] = cpu_to_scr(opcode & ~SCR_NO_FLUSH); + ++opchanged; + } + break; + + case 0x0: + /* + ** MOVE (absolute address) + */ + relocs = 1; + break; + + case 0x8: + /* + ** JUMP / CALL + ** don't relocate if relative :-) + */ + if (opcode & 0x00800000) + relocs = 0; + else + relocs = 1; + break; + + case 0x4: + case 0x5: + case 0x6: + case 0x7: + relocs = 1; + break; + + default: + relocs = 0; + break; + }; + + if (relocs) { + while (relocs--) { + old = *src++; + + switch (old & RELOC_MASK) { + case RELOC_REGISTER: + new = (old & ~RELOC_MASK) + np->paddr; + break; + case RELOC_LABEL: + new = (old & ~RELOC_MASK) + np->p_script; + break; + case RELOC_LABELH: + new = (old & ~RELOC_MASK) + np->p_scripth; + break; + case RELOC_SOFTC: + new = (old & ~RELOC_MASK) + vtophys(np); + break; + case RELOC_KVAR: + if (((old & ~RELOC_MASK) < + SCRIPT_KVAR_FIRST) || + ((old & ~RELOC_MASK) > + SCRIPT_KVAR_LAST)) + panic("ncr KVAR out of range"); + new = vtophys(script_kvars[old & + ~RELOC_MASK]); + break; + case 0: + /* Don't relocate a 0 address. */ + if (old == 0) { + new = old; + break; + } + /* fall through */ + default: + panic("ncr_script_copy_and_bind: weird relocation %x\n", old); + break; + } + + *dst++ = cpu_to_scr(new); + } + } else + *dst++ = cpu_to_scr(*src++); + + }; + if (bootverbose > 1 && opchanged) + printf("%s: NO FLUSH bit removed from %d script instructions\n", + ncr_name(np), opchanged); +} + +/*========================================================== +** +** +** Auto configuration: attach and init a host adapter. +** +** +**========================================================== +*/ + +/* +** Linux host data structure +** +** The script area is allocated in the host data structure +** because kmalloc() returns NULL during scsi initialisations +** with Linux 1.2.X +*/ + +struct host_data { + struct ncb *ncb; + + char ncb_align[NCB_ALIGN_SIZE-1]; /* Filler for alignment */ + struct ncb _ncb_data; + + char ccb_align[CCB_ALIGN_SIZE-1]; /* Filler for alignment */ + struct ccb _ccb_data; + + char scr_align[SCR_ALIGN_SIZE-1]; /* Filler for alignment */ + struct script script_data; + + struct scripth scripth_data; +}; + +/* +** Print something which allow to retrieve the controler type, unit, +** target, lun concerned by a kernel message. +*/ + +#define PRINT_LUN(np, target, lun) \ +printf(KERN_INFO "%s-<%d,%d>: ", ncr_name(np), (int) (target), (int) (lun)) + +static void PRINT_ADDR(Scsi_Cmnd *cmd) +{ + struct host_data *host_data = (struct host_data *) cmd->host->hostdata; + ncb_p np = host_data->ncb; + if (np) PRINT_LUN(np, cmd->target, cmd->lun); +} + +/*========================================================== +** +** NCR chip clock divisor table. +** Divisors are multiplied by 10,000,000 in order to make +** calculations more simple. +** +**========================================================== +*/ + +#define _5M 5000000 +static u_long div_10M[] = + {2*_5M, 3*_5M, 4*_5M, 6*_5M, 8*_5M, 12*_5M, 16*_5M}; + + +/*=============================================================== +** +** Prepare io register values used by ncr_init() according +** to selected and supported features. +** +** NCR chips allow burst lengths of 2, 4, 8, 16, 32, 64, 128 +** transfers. 32,64,128 are only supported by 875 and 895 chips. +** We use log base 2 (burst length) as internal code, with +** value 0 meaning "burst disabled". +** +**=============================================================== +*/ + +/* + * Burst length from burst code. + */ +#define burst_length(bc) (!(bc))? 0 : 1 << (bc) + +/* + * Burst code from io register bits. + */ +#define burst_code(dmode, ctest4, ctest5) \ + (ctest4) & 0x80? 0 : (((dmode) & 0xc0) >> 6) + ((ctest5) & 0x04) + 1 + +/* + * Set initial io register bits from burst code. + */ +static inline void ncr_init_burst(ncb_p np, u_char bc) +{ + np->rv_ctest4 &= ~0x80; + np->rv_dmode &= ~(0x3 << 6); + np->rv_ctest5 &= ~0x4; + + if (!bc) { + np->rv_ctest4 |= 0x80; + } + else { + --bc; + np->rv_dmode |= ((bc & 0x3) << 6); + np->rv_ctest5 |= (bc & 0x4); + } +} + +#ifdef SCSI_NCR_NVRAM_SUPPORT + +/* +** Get target set-up from Symbios format NVRAM. +*/ + +__initfunc( +static void + ncr_Symbios_setup_target(ncb_p np, int target, Symbios_nvram *nvram) +) +{ + tcb_p tp = &np->target[target]; + Symbios_target *tn = &nvram->target[target]; + + tp->usrsync = tn->sync_period ? (tn->sync_period + 3) / 4 : 255; + tp->usrwide = tn->bus_width == 0x10 ? 1 : 0; + tp->usrtags = + (tn->flags & SYMBIOS_QUEUE_TAGS_ENABLED)? SCSI_NCR_MAX_TAGS : 0; + + if (!(tn->flags & SYMBIOS_DISCONNECT_ENABLE)) + tp->usrflag |= UF_NODISC; + if (!(tn->flags & SYMBIOS_SCAN_AT_BOOT_TIME)) + tp->usrflag |= UF_NOSCAN; +} + +/* +** Get target set-up from Tekram format NVRAM. +*/ + +__initfunc( +static void + ncr_Tekram_setup_target(ncb_p np, int target, Tekram_nvram *nvram) +) +{ + tcb_p tp = &np->target[target]; + struct Tekram_target *tn = &nvram->target[target]; + int i; + + if (tn->flags & TEKRAM_SYNC_NEGO) { + i = tn->sync_index & 0xf; + tp->usrsync = i < 12 ? Tekram_sync[i] : 255; + } + + tp->usrwide = (tn->flags & TEKRAM_WIDE_NEGO) ? 1 : 0; + + if (tn->flags & TEKRAM_TAGGED_COMMANDS) { + tp->usrtags = 2 << nvram->max_tags_index; + if (tp->usrtags > SCSI_NCR_MAX_TAGS) + tp->usrtags = SCSI_NCR_MAX_TAGS; + } + + if (!(tn->flags & TEKRAM_DISCONNECT_ENABLE)) + tp->usrflag = UF_NODISC; + + /* If any device does not support parity, we will not use this option */ + if (!(tn->flags & TEKRAM_PARITY_CHECK)) + np->rv_scntl0 &= ~0x0a; /* SCSI parity checking disabled */ +} +#endif /* SCSI_NCR_NVRAM_SUPPORT */ + +__initfunc( +static int ncr_prepare_setting(ncb_p np, ncr_nvram *nvram) +) +{ + u_char burst_max; + u_long period; + int i; + + /* + ** Save assumed BIOS setting + */ + + np->sv_scntl0 = INB(nc_scntl0) & 0x0a; + np->sv_scntl3 = INB(nc_scntl3) & 0x07; + np->sv_dmode = INB(nc_dmode) & 0xce; + np->sv_dcntl = INB(nc_dcntl) & 0xa8; + np->sv_ctest3 = INB(nc_ctest3) & 0x01; + np->sv_ctest4 = INB(nc_ctest4) & 0x80; + np->sv_ctest5 = INB(nc_ctest5) & 0x24; + np->sv_gpcntl = INB(nc_gpcntl); + np->sv_stest2 = INB(nc_stest2) & 0x20; + np->sv_stest4 = INB(nc_stest4); + + /* + ** Wide ? + */ + + np->maxwide = (np->features & FE_WIDE)? 1 : 0; + + /* + ** Get the frequency of the chip's clock. + ** Find the right value for scntl3. + */ + + if (np->features & FE_QUAD) + np->multiplier = 4; + else if (np->features & FE_DBLR) + np->multiplier = 2; + else + np->multiplier = 1; + + np->clock_khz = (np->features & FE_CLK80)? 80000 : 40000; + np->clock_khz *= np->multiplier; + + if (np->clock_khz != 40000) + ncr_getclock(np, np->multiplier); + + /* + * Divisor to be used for async (timer pre-scaler). + */ + i = np->clock_divn - 1; + while (i >= 0) { + --i; + if (10ul * SCSI_NCR_MIN_ASYNC * np->clock_khz > div_10M[i]) { + ++i; + break; + } + } + np->rv_scntl3 = i+1; + + /* + * Minimum synchronous period factor supported by the chip. + * Btw, 'period' is in tenths of nanoseconds. + */ + + period = (4 * div_10M[0] + np->clock_khz - 1) / np->clock_khz; + if (period <= 250) np->minsync = 10; + else if (period <= 303) np->minsync = 11; + else if (period <= 500) np->minsync = 12; + else np->minsync = (period + 40 - 1) / 40; + + /* + * Check against chip SCSI standard support (SCSI-2,ULTRA,ULTRA2). + */ + + if (np->minsync < 25 && !(np->features & (FE_ULTRA|FE_ULTRA2))) + np->minsync = 25; + else if (np->minsync < 12 && !(np->features & FE_ULTRA2)) + np->minsync = 12; + + /* + * Maximum synchronous period factor supported by the chip. + */ + + period = (11 * div_10M[np->clock_divn - 1]) / (4 * np->clock_khz); + np->maxsync = period > 2540 ? 254 : period / 10; + + /* + ** Prepare initial value of other IO registers + */ +#if defined SCSI_NCR_TRUST_BIOS_SETTING + np->rv_scntl0 = np->sv_scntl0; + np->rv_dmode = np->sv_dmode; + np->rv_dcntl = np->sv_dcntl; + np->rv_ctest3 = np->sv_ctest3; + np->rv_ctest4 = np->sv_ctest4; + np->rv_ctest5 = np->sv_ctest5; + burst_max = burst_code(np->sv_dmode, np->sv_ctest4, np->sv_ctest5); +#else + + /* + ** Select burst length (dwords) + */ + burst_max = driver_setup.burst_max; + if (burst_max == 255) + burst_max = burst_code(np->sv_dmode, np->sv_ctest4, np->sv_ctest5); + if (burst_max > 7) + burst_max = 7; + if (burst_max > np->maxburst) + burst_max = np->maxburst; + + /* + ** Select all supported special features + */ + if (np->features & FE_ERL) + np->rv_dmode |= ERL; /* Enable Read Line */ + if (np->features & FE_BOF) + np->rv_dmode |= BOF; /* Burst Opcode Fetch */ + if (np->features & FE_ERMP) + np->rv_dmode |= ERMP; /* Enable Read Multiple */ + if (np->features & FE_PFEN) + np->rv_dcntl |= PFEN; /* Prefetch Enable */ + if (np->features & FE_CLSE) + np->rv_dcntl |= CLSE; /* Cache Line Size Enable */ + if (np->features & FE_WRIE) + np->rv_ctest3 |= WRIE; /* Write and Invalidate */ + if (np->features & FE_DFS) + np->rv_ctest5 |= DFS; /* Dma Fifo Size */ + + /* + ** Select some other + */ + if (driver_setup.master_parity) + np->rv_ctest4 |= MPEE; /* Master parity checking */ + if (driver_setup.scsi_parity) + np->rv_scntl0 |= 0x0a; /* full arb., ena parity, par->ATN */ + +#ifdef SCSI_NCR_NVRAM_SUPPORT + /* + ** Get parity checking, host ID and verbose mode from NVRAM + **/ + if (nvram) { + switch(nvram->type) { + case SCSI_NCR_TEKRAM_NVRAM: + np->myaddr = nvram->data.Tekram.host_id & 0x0f; + break; + case SCSI_NCR_SYMBIOS_NVRAM: + if (!(nvram->data.Symbios.flags & SYMBIOS_PARITY_ENABLE)) + np->rv_scntl0 &= ~0x0a; + np->myaddr = nvram->data.Symbios.host_id & 0x0f; + if (nvram->data.Symbios.flags & SYMBIOS_VERBOSE_MSGS) + np->verbose += 1; + break; + } + } +#endif + /* + ** Get SCSI addr of host adapter (set by bios?). + */ + if (!np->myaddr) np->myaddr = INB(nc_scid) & 0x07; + if (!np->myaddr) np->myaddr = SCSI_NCR_MYADDR; + + +#endif /* SCSI_NCR_TRUST_BIOS_SETTING */ + + /* + * Prepare initial io register bits for burst length + */ + ncr_init_burst(np, burst_max); + + /* + ** Set differential mode and LED support. + ** Ignore these features for boards known to use a + ** specific GPIO wiring (Tekram only for now). + ** Probe initial setting of GPREG and GPCNTL for + ** other ones. + */ + if (!nvram || nvram->type != SCSI_NCR_TEKRAM_NVRAM) { + switch(driver_setup.diff_support) { + case 3: + if (INB(nc_gpreg) & 0x08) + break; + case 2: + np->rv_stest2 |= 0x20; + break; + case 1: + np->rv_stest2 |= (np->sv_stest2 & 0x20); + break; + default: + break; + } + } + if ((driver_setup.led_pin || + (nvram && nvram->type == SCSI_NCR_SYMBIOS_NVRAM)) && + !(np->sv_gpcntl & 0x01)) + np->features |= FE_LED0; + + /* + ** Set irq mode. + */ + switch(driver_setup.irqm) { + case 2: + np->rv_dcntl |= IRQM; + break; + case 1: + np->rv_dcntl |= (np->sv_dcntl & IRQM); + break; + default: + break; + } + + /* + ** Configure targets according to driver setup. + ** If NVRAM present get targets setup from NVRAM. + ** Allow to override sync, wide and NOSCAN from + ** boot command line. + */ + for (i = 0 ; i < MAX_TARGET ; i++) { + tcb_p tp = &np->target[i]; + + tp->usrsync = 255; +#ifdef SCSI_NCR_NVRAM_SUPPORT + if (nvram) { + switch(nvram->type) { + case SCSI_NCR_TEKRAM_NVRAM: + ncr_Tekram_setup_target(np, i, &nvram->data.Tekram); + break; + case SCSI_NCR_SYMBIOS_NVRAM: + ncr_Symbios_setup_target(np, i, &nvram->data.Symbios); + break; + } + if (driver_setup.use_nvram & 0x2) + tp->usrsync = driver_setup.default_sync; + if (driver_setup.use_nvram & 0x4) + tp->usrwide = driver_setup.max_wide; + if (driver_setup.use_nvram & 0x8) + tp->usrflag &= ~UF_NOSCAN; + } + else { +#else + if (1) { +#endif + tp->usrsync = driver_setup.default_sync; + tp->usrwide = driver_setup.max_wide; + tp->usrtags = driver_setup.default_tags; + if (!driver_setup.disconnection) + np->target[i].usrflag = UF_NODISC; + } + } + + /* + ** Announce all that stuff to user. + */ + + i = nvram ? nvram->type : 0; + printf(KERN_INFO "%s: %sID %d, Fast-%d%s%s\n", ncr_name(np), + i == SCSI_NCR_SYMBIOS_NVRAM ? "Symbios format NVRAM, " : + (i == SCSI_NCR_TEKRAM_NVRAM ? "Tekram format NVRAM, " : ""), + np->myaddr, + np->minsync < 12 ? 40 : (np->minsync < 25 ? 20 : 10), + (np->rv_scntl0 & 0xa) ? ", Parity Checking" : ", NO Parity", + (np->rv_stest2 & 0x20) ? ", Differential" : ""); + + if (bootverbose > 1) { + printf ("%s: initial SCNTL3/DMODE/DCNTL/CTEST3/4/5 = " + "(hex) %02x/%02x/%02x/%02x/%02x/%02x\n", + ncr_name(np), np->sv_scntl3, np->sv_dmode, np->sv_dcntl, + np->sv_ctest3, np->sv_ctest4, np->sv_ctest5); + + printf ("%s: final SCNTL3/DMODE/DCNTL/CTEST3/4/5 = " + "(hex) %02x/%02x/%02x/%02x/%02x/%02x\n", + ncr_name(np), np->rv_scntl3, np->rv_dmode, np->rv_dcntl, + np->rv_ctest3, np->rv_ctest4, np->rv_ctest5); + } + + if (bootverbose && np->paddr2) + printf (KERN_INFO "%s: on-board RAM at 0x%lx\n", + ncr_name(np), np->paddr2); + + return 0; +} + + +#ifdef SCSI_NCR_DEBUG_NVRAM + +__initfunc( +void ncr_display_Symbios_nvram(ncb_p np, Symbios_nvram *nvram) +) +{ + int i; + + /* display Symbios nvram host data */ + printf("%s: HOST ID=%d%s%s%s%s\n", + ncr_name(np), nvram->host_id & 0x0f, + (nvram->flags & SYMBIOS_SCAM_ENABLE) ? " SCAM" :"", + (nvram->flags & SYMBIOS_PARITY_ENABLE) ? " PARITY" :"", + (nvram->flags & SYMBIOS_VERBOSE_MSGS) ? " VERSBOSE" :"", + (nvram->flags1 & SYMBIOS_SCAN_HI_LO) ? " HI_LO" :""); + + /* display Symbios nvram drive data */ + for (i = 0 ; i < 15 ; i++) { + struct Symbios_target *tn = &nvram->target[i]; + printf("%s-%d:%s%s%s%s WIDTH=%d SYNC=%d TMO=%d\n", + ncr_name(np), i, + (tn->flags & SYMBIOS_DISCONNECT_ENABLE) ? " DISC" : "", + (tn->flags & SYMBIOS_SCAN_AT_BOOT_TIME) ? " SCAN_BOOT" : "", + (tn->flags & SYMBIOS_SCAN_LUNS) ? " SCAN_LUNS" : "", + (tn->flags & SYMBIOS_QUEUE_TAGS_ENABLED)? " TCQ" : "", + tn->bus_width, + tn->sync_period / 4, + tn->timeout); + } +} + +static u_char Tekram_boot_delay[7] __initdata = {3, 5, 10, 20, 30, 60, 120}; + +__initfunc( +void ncr_display_Tekram_nvram(ncb_p np, Tekram_nvram *nvram) +) +{ + int i, tags, boot_delay; + char *rem; + + /* display Tekram nvram host data */ + tags = 2 << nvram->max_tags_index; + boot_delay = 0; + if (nvram->boot_delay_index < 6) + boot_delay = Tekram_boot_delay[nvram->boot_delay_index]; + switch((nvram->flags & TEKRAM_REMOVABLE_FLAGS) >> 6) { + default: + case 0: rem = ""; break; + case 1: rem = " REMOVABLE=boot device"; break; + case 2: rem = " REMOVABLE=all"; break; + } + + printf("%s: HOST ID=%d%s%s%s%s%s%s%s%s%s BOOT DELAY=%d tags=%d\n", + ncr_name(np), nvram->host_id & 0x0f, + (nvram->flags1 & SYMBIOS_SCAM_ENABLE) ? " SCAM" :"", + (nvram->flags & TEKRAM_MORE_THAN_2_DRIVES) ? " >2DRIVES" :"", + (nvram->flags & TEKRAM_DRIVES_SUP_1GB) ? " >1GB" :"", + (nvram->flags & TEKRAM_RESET_ON_POWER_ON) ? " RESET" :"", + (nvram->flags & TEKRAM_ACTIVE_NEGATION) ? " ACT_NEG" :"", + (nvram->flags & TEKRAM_IMMEDIATE_SEEK) ? " IMM_SEEK" :"", + (nvram->flags & TEKRAM_SCAN_LUNS) ? " SCAN_LUNS" :"", + (nvram->flags1 & TEKRAM_F2_F6_ENABLED) ? " F2_F6" :"", + rem, boot_delay, tags); + + /* display Tekram nvram drive data */ + for (i = 0; i <= 15; i++) { + int sync, j; + struct Tekram_target *tn = &nvram->target[i]; + j = tn->sync_index & 0xf; + sync = j < 12 ? Tekram_sync[j] : 255; + printf("%s-%d:%s%s%s%s%s%s PERIOD=%d\n", + ncr_name(np), i, + (tn->flags & TEKRAM_PARITY_CHECK) ? " PARITY" : "", + (tn->flags & TEKRAM_SYNC_NEGO) ? " SYNC" : "", + (tn->flags & TEKRAM_DISCONNECT_ENABLE) ? " DISC" : "", + (tn->flags & TEKRAM_START_CMD) ? " START" : "", + (tn->flags & TEKRAM_TAGGED_COMMANDS) ? " TCQ" : "", + (tn->flags & TEKRAM_WIDE_NEGO) ? " WIDE" : "", + sync); + } +} +#endif /* SCSI_NCR_DEBUG_NVRAM */ + +/* +** Host attach and initialisations. +** +** Allocate host data and ncb structure. +** Request IO region and remap MMIO region. +** Do chip initialization. +** If all is OK, install interrupt handling and +** start the timer daemon. +*/ + +__initfunc( +static int ncr_attach (Scsi_Host_Template *tpnt, int unit, ncr_device *device) +) +{ + struct host_data *host_data; + ncb_p np; + struct Scsi_Host *instance = 0; + u_long flags = 0; + ncr_nvram *nvram = device->nvram; + +printf(KERN_INFO "ncr53c%s-%d: rev=0x%02x, base=0x%x, io_port=0x%x, irq=%d\n", + device->chip.name, unit, device->chip.revision_id, device->slot.base, + device->slot.io_port, device->slot.irq); + + /* + ** Allocate host_data structure + */ + if (!(instance = scsi_register(tpnt, sizeof(*host_data)))) + goto attach_error; + + /* + ** Initialize structure. + */ + host_data = (struct host_data *) instance->hostdata; + + /* + ** Align np and first ccb to 32 boundary for cache line + ** bursting when copying the global header. + */ + np = (ncb_p) (((u_long) &host_data->_ncb_data) & NCB_ALIGN_MASK); + host_data->ncb = np; + bzero (np, sizeof (*np)); + + np->ccb = (ccb_p) (((u_long) &host_data->_ccb_data) & CCB_ALIGN_MASK); + bzero (np->ccb, sizeof (*np->ccb)); + + /* + ** Store input informations in the host data structure. + */ + strncpy(np->chip_name, device->chip.name, sizeof(np->chip_name) - 1); + np->unit = unit; + np->verbose = driver_setup.verbose; + sprintf(np->inst_name, "ncr53c%s-%d", np->chip_name, np->unit); + np->device_id = device->chip.device_id; + np->revision_id = device->chip.revision_id; + np->features = device->chip.features; + np->clock_divn = device->chip.nr_divisor; + np->maxoffs = device->chip.offset_max; + np->maxburst = device->chip.burst_max; + + np->script0 = + (struct script *) (((u_long) &host_data->script_data) & SCR_ALIGN_MASK); + np->scripth0 = &host_data->scripth_data; + + /* + ** Initialize timer structure + ** + */ + init_timer(&np->timer); + np->timer.data = (unsigned long) np; + np->timer.function = ncr53c8xx_timeout; + + /* + ** Try to map the controller chip to + ** virtual and physical memory. + */ + + np->paddr = device->slot.base; + np->paddr2 = (np->features & FE_RAM)? device->slot.base_2 : 0; + +#ifndef NCR_IOMAPPED + np->vaddr = remap_pci_mem((u_long) np->paddr, (u_long) 128); + if (!np->vaddr) { + printf("%s: can't map memory mapped IO region\n", ncr_name(np)); + goto attach_error; + } + else + if (bootverbose > 1) + printf("%s: using memory mapped IO at virtual address 0x%lx\n", ncr_name(np), (u_long) np->vaddr); + + /* + ** Make the controller's registers available. + ** Now the INB INW INL OUTB OUTW OUTL macros + ** can be used safely. + */ + + np->reg = (struct ncr_reg*) np->vaddr; + +#endif /* !defined NCR_IOMAPPED */ + + /* + ** Try to map the controller chip into iospace. + */ + + request_region(device->slot.io_port, 128, "ncr53c8xx"); + np->port = device->slot.io_port; + +#ifdef SCSI_NCR_NVRAM_SUPPORT + if (nvram) { + switch(nvram->type) { + case SCSI_NCR_SYMBIOS_NVRAM: +#ifdef SCSI_NCR_DEBUG_NVRAM + ncr_display_Symbios_nvram(np, &nvram->data.Symbios); +#endif + break; + case SCSI_NCR_TEKRAM_NVRAM: +#ifdef SCSI_NCR_DEBUG_NVRAM + ncr_display_Tekram_nvram(np, &nvram->data.Tekram); +#endif + break; + default: + nvram = 0; +#ifdef SCSI_NCR_DEBUG_NVRAM + printf("%s: NVRAM: None or invalid data.\n", ncr_name(np)); +#endif + } + } +#endif + + /* + ** Do chip dependent initialization. + */ + (void)ncr_prepare_setting(np, nvram); + +#ifndef NCR_IOMAPPED + if (np->paddr2 && sizeof(struct script) <= 4096) { + np->vaddr2 = remap_pci_mem((u_long) np->paddr2, (u_long) 4096); + if (!np->vaddr2) { + printf("%s: can't map memory mapped IO region\n", ncr_name(np)); + goto attach_error; + } + else + if (bootverbose > 1) + printf("%s: on-board ram mapped at virtual address 0x%lx\n", ncr_name(np), (u_long) np->vaddr2); + } +#endif /* !defined NCR_IOMAPPED */ + + /* + ** Fill Linux host instance structure + */ +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) + instance->max_channel = 0; + instance->max_id = np->maxwide ? 16 : 8; + instance->max_lun = SCSI_NCR_MAX_LUN; +#endif +#ifndef NCR_IOMAPPED + instance->base = (char *) np->reg; +#endif + instance->irq = device->slot.irq; + instance->io_port = device->slot.io_port; + instance->n_io_port = 128; + instance->dma_channel = 0; +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,0,0) + instance->select_queue_depths = ncr53c8xx_select_queue_depths; +#endif + + /* + ** Patch script to physical addresses + */ + ncr_script_fill (&script0, &scripth0); + + np->scripth = np->scripth0; + np->p_scripth = vtophys(np->scripth); + + np->script = (np->vaddr2) ? (struct script *) np->vaddr2 : np->script0; + np->p_script = (np->vaddr2) ? np->paddr2 : vtophys(np->script0); + + ncr_script_copy_and_bind (np, (ncrcmd *) &script0, (ncrcmd *) np->script0, sizeof(struct script)); + ncr_script_copy_and_bind (np, (ncrcmd *) &scripth0, (ncrcmd *) np->scripth0, sizeof(struct scripth)); + np->ccb->p_ccb = vtophys (np->ccb); + + /* + ** Patch the script for LED support. + */ + + if (np->features & FE_LED0) { + np->script0->reselect[0] = + cpu_to_scr(SCR_REG_REG(gpreg, SCR_OR, 0x01)); + np->script0->reselect1[0] = + cpu_to_scr(SCR_REG_REG(gpreg, SCR_AND, 0xfe)); + np->script0->reselect2[0] = + cpu_to_scr(SCR_REG_REG(gpreg, SCR_AND, 0xfe)); + } + + /* + ** init data structure + */ + + np->jump_tcb.l_cmd = cpu_to_scr(SCR_JUMP); + np->jump_tcb.l_paddr = cpu_to_scr(NCB_SCRIPTH_PHYS (np, abort)); + + /* + ** Reset chip. + */ + + OUTB (nc_istat, SRST); + DELAY (1000); + OUTB (nc_istat, 0 ); + + /* + ** Now check the cache handling of the pci chipset. + */ + + if (ncr_snooptest (np)) { + printf ("CACHE INCORRECTLY CONFIGURED.\n"); + goto attach_error; + }; + + /* + ** Install the interrupt handler. + */ +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,70) +#ifdef SCSI_NCR_SHARE_IRQ + if (bootverbose > 1) + printf("%s: requesting shared irq %d (dev_id=0x%lx)\n", + ncr_name(np), device->slot.irq, (u_long) np); + if (request_irq(device->slot.irq, ncr53c8xx_intr, + SA_INTERRUPT|SA_SHIRQ, "ncr53c8xx", np)) { +#else + if (request_irq(device->slot.irq, ncr53c8xx_intr, + SA_INTERRUPT, "ncr53c8xx", np)) { +#endif +#else + if (request_irq(device->slot.irq, ncr53c8xx_intr, + SA_INTERRUPT, "ncr53c8xx")) { +#endif + printf("%s: request irq %d failure\n", ncr_name(np), device->slot.irq); + goto attach_error; + } + np->irq = device->slot.irq; + + /* + ** After SCSI devices have been opened, we cannot + ** reset the bus safely, so we do it here. + ** Interrupt handler does the real work. + ** Process the reset exception, + ** if interrupts are not enabled yet. + ** Then enable disconnects. + */ + save_flags(flags); cli(); + if (ncr_reset_scsi_bus(np, 0, driver_setup.settle_delay) != 0) { + printf("%s: FATAL ERROR: CHECK SCSI BUS - CABLES, TERMINATION, DEVICE POWER etc.!\n", ncr_name(np)); + restore_flags(flags); + goto attach_error; + } + ncr_exception (np); + restore_flags(flags); + + np->disc = 1; + + /* + ** The middle-level SCSI driver does not + ** wait devices to settle. + ** Wait synchronously if more than 2 seconds. + */ + if (driver_setup.settle_delay > 2) { + printf("%s: waiting %d seconds for scsi devices to settle...\n", + ncr_name(np), driver_setup.settle_delay); + DELAY(1000000UL * driver_setup.settle_delay); + } + + /* + ** Now let the generic SCSI driver + ** look for the SCSI devices on the bus .. + */ + + /* + ** start the timeout daemon + */ + np->lasttime=0; + ncr_timeout (np); + + /* + ** use SIMPLE TAG messages by default + */ +#ifdef SCSI_NCR_ALWAYS_SIMPLE_TAG + np->order = M_SIMPLE_TAG; +#endif + + /* + ** Done. + */ + if (!the_template) { + the_template = instance->hostt; + first_host = instance; + } + + return 0; + +attach_error: + if (!instance) return -1; + printf("%s: detaching...\n", ncr_name(np)); +#ifndef NCR_IOMAPPED + if (np->vaddr) { +#ifdef DEBUG_NCR53C8XX + printf("%s: releasing memory mapped IO region %lx[%d]\n", ncr_name(np), (u_long) np->vaddr, 128); +#endif + unmap_pci_mem((vm_offset_t) np->vaddr, (u_long) 128); + } + if (np->vaddr2) { +#ifdef DEBUG_NCR53C8XX + printf("%s: releasing memory mapped IO region %lx[%d]\n", ncr_name(np), (u_long) np->vaddr2, 4096); +#endif + unmap_pci_mem((vm_offset_t) np->vaddr2, (u_long) 4096); + } +#endif + if (np->port) { +#ifdef DEBUG_NCR53C8XX + printf("%s: releasing IO region %x[%d]\n", ncr_name(np), np->port, 128); +#endif + release_region(np->port, 128); + } + if (np->irq) { +#ifdef DEBUG_NCR53C8XX + printf("%s: freeing irq %d\n", ncr_name(np), np->irq); +#endif +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,70) + free_irq(np->irq, np); +#else + free_irq(np->irq); +#endif + } + scsi_unregister(instance); + + return -1; + } + +/*========================================================== +** +** +** Start execution of a SCSI command. +** This is called from the generic SCSI driver. +** +** +**========================================================== +*/ +int ncr_queue_command (Scsi_Cmnd *cmd, void (* done)(Scsi_Cmnd *)) +{ + struct Scsi_Host *host = cmd->host; +/* Scsi_Device *device = cmd->device; */ + struct host_data *host_data = (struct host_data *) host->hostdata; + ncb_p np = host_data->ncb; + tcb_p tp = &np->target[cmd->target]; + + ccb_p cp; + lcb_p lp; + + int segments; + u_char qidx, nego, idmsg, *msgptr; + u_int msglen, msglen2; + u_long flags; + int xfer_direction; + + cmd->scsi_done = done; + cmd->host_scribble = NULL; + cmd->SCp.ptr = NULL; + cmd->SCp.buffer = NULL; + + /*--------------------------------------------- + ** + ** Some shortcuts ... + ** + **--------------------------------------------- + */ + if ((cmd->target == np->myaddr ) || + (cmd->target >= MAX_TARGET) || + (cmd->lun >= MAX_LUN )) { + return(DID_BAD_TARGET); + } + + /*--------------------------------------------- + ** + ** Complete the 1st TEST UNIT READY command + ** with error condition if the device is + ** flagged NOSCAN, in order to speed up + ** the boot. + ** + **--------------------------------------------- + */ + if (cmd->cmnd[0] == 0 && (tp->usrflag & UF_NOSCAN)) { + tp->usrflag &= ~UF_NOSCAN; + return DID_BAD_TARGET; + } + + if (DEBUG_FLAGS & DEBUG_TINY) { + PRINT_ADDR(cmd); + printf ("CMD=%x ", cmd->cmnd[0]); + } + + /*--------------------------------------------------- + ** + ** Assign a ccb / bind cmd. + ** If resetting, shorten settle_time if necessary + ** in order to avoid spurious timeouts. + ** If resetting or no free ccb, + ** insert cmd into the waiting list. + ** + **---------------------------------------------------- + */ + save_flags(flags); cli(); + + if (np->settle_time && cmd->timeout_per_command >= HZ && + np->settle_time > jiffies + cmd->timeout_per_command - HZ) { + np->settle_time = jiffies + cmd->timeout_per_command - HZ; + } + + if (np->settle_time || !(cp=ncr_get_ccb (np, cmd->target, cmd->lun))) { + insert_into_waiting_list(np, cmd); + restore_flags(flags); + return(DID_OK); + } + cp->cmd = cmd; + + /*--------------------------------------------------- + ** + ** Enable tagged queue if asked by scsi ioctl + ** + **---------------------------------------------------- + */ + if (!tp->usrtags && cmd->device && cmd->device->tagged_queue) { + tp->usrtags = SCSI_NCR_MAX_TAGS; + ncr_setmaxtags (np, tp, SCSI_NCR_MAX_TAGS); + } + + /*--------------------------------------------------- + ** + ** timestamp + ** + **---------------------------------------------------- + */ +#ifdef SCSI_NCR_PROFILE_SUPPORT + bzero (&cp->phys.header.stamp, sizeof (struct tstamp)); + cp->phys.header.stamp.start = jiffies; +#endif + + /*---------------------------------------------------- + ** + ** Get device quirks from a speciality table. + ** + ** @GENSCSI@ + ** This should be a part of the device table + ** in "scsi_conf.c". + ** + **---------------------------------------------------- + */ + if (tp->quirks & QUIRK_UPDATE) { + tp->quirks = ncr_lookup ((char*) &tp->inqdata[0]); +#ifndef NCR_GETCC_WITHMSG + if (tp->quirks) { + PRINT_ADDR(cmd); + printf ("quirks=%x.\n", tp->quirks); + } +#endif + } + + /*--------------------------------------------------- + ** + ** negotiation required? + ** + ** Only SCSI-II devices. + ** To negotiate with SCSI-I devices is dangerous, since + ** Synchronous Negotiation protocol is optional, and + ** INQUIRY data do not contains capabilities in byte 7. + **---------------------------------------------------- + */ + + nego = 0; + + if (cmd->lun == 0 && !tp->nego_cp && + (tp->inqdata[2] & 0x7) >= 2 && tp->inqdata[7]) { + /* + ** negotiate wide transfers ? + */ + + if (!tp->widedone) { + if (tp->inqdata[7] & INQ7_WIDE16) { + nego = NS_WIDE; + } else + tp->widedone=1; + }; + + /* + ** negotiate synchronous transfers? + */ + + if (!nego && !tp->period) { + if ( 1 +#if defined (CDROM_ASYNC) + && ((tp->inqdata[0] & 0x1f) != 5) +#endif + && (tp->inqdata[7] & INQ7_SYNC)) { + nego = NS_SYNC; + } else { + tp->period =0xffff; + tp->sval = 0xe0; + PRINT_ADDR(cmd); + printf ("asynchronous.\n"); + }; + }; + + /* + ** remember nego is pending for the target. + ** Avoid to start a nego for all queued commands + ** when tagged command queuing is enabled. + */ + + if (nego) + tp->nego_cp = cp; + }; + + /*--------------------------------------------------- + ** + ** choose a new tag ... + ** + **---------------------------------------------------- + */ + + if ((lp = tp->lp[cmd->lun]) && (lp->usetags)) { + /* + ** assign a tag to this ccb! + */ + while (!cp->tag) { + ccb_p cp2 = lp->next_ccb; + lp->lasttag = lp->lasttag % 255 + 1; + while (cp2 && cp2->tag != lp->lasttag) + cp2 = cp2->next_ccb; + if (cp2) continue; + cp->tag=lp->lasttag; + if (DEBUG_FLAGS & DEBUG_TAGS) { + PRINT_ADDR(cmd); + printf ("using tag #%d.\n", cp->tag); + } + } + } else { + cp->tag=0; + } + + /*---------------------------------------------------- + ** + ** Build the identify / tag / sdtr message + ** + **---------------------------------------------------- + */ + + idmsg = M_IDENTIFY | cmd->lun; + + if (cp != np->ccb && ((np->disc && !(tp->usrflag & UF_NODISC)) || cp->tag)) + idmsg |= 0x40; + + msgptr = cp->scsi_smsg; + msglen = 0; + msgptr[msglen++] = idmsg; + + if (cp->tag) { + char tag; + + tag = np->order; + if (tag == 0) { + /* + ** Ordered write ops, unordered read ops. + */ + switch (cmd->cmnd[0]) { + case 0x08: /* READ_SMALL (6) */ + case 0x28: /* READ_BIG (10) */ + case 0xa8: /* READ_HUGE (12) */ + tag = M_SIMPLE_TAG; + break; + default: + tag = M_ORDERED_TAG; + } + } + /* + ** Have to force ordered tag to avoid timeouts + */ + if ((lp = tp->lp[cmd->lun]) && (lp->force_ordered_tag)) { + tag = M_ORDERED_TAG; + lp->force_ordered_tag = 0; + if (DEBUG_FLAGS & DEBUG_TAGS) { + PRINT_ADDR(cmd); + printf ("Ordered Queue Tag forced\n"); + } + } + msgptr[msglen++] = tag; + msgptr[msglen++] = cp -> tag; + } + + switch (nego) { + case NS_SYNC: + msgptr[msglen++] = M_EXTENDED; + msgptr[msglen++] = 3; + msgptr[msglen++] = M_X_SYNC_REQ; + msgptr[msglen++] = tp->maxoffs ? tp->minsync : 0; + msgptr[msglen++] = tp->maxoffs; + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("sync msgout: "); + ncr_show_msg (&cp->scsi_smsg [msglen-5]); + printf (".\n"); + }; + break; + case NS_WIDE: + msgptr[msglen++] = M_EXTENDED; + msgptr[msglen++] = 2; + msgptr[msglen++] = M_X_WIDE_REQ; + msgptr[msglen++] = tp->usrwide; + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("wide msgout: "); + ncr_show_msg (&cp->scsi_smsg [msglen-4]); + printf (".\n"); + }; + break; + }; + + /*---------------------------------------------------- + ** + ** Build the identify message for getcc. + ** + **---------------------------------------------------- + */ + + cp -> scsi_smsg2 [0] = idmsg; + msglen2 = 1; + + /*---------------------------------------------------- + ** + ** Build the data descriptors + ** + **---------------------------------------------------- + */ + + segments = ncr_scatter (cp, cp->cmd); + + if (segments < 0) { + ncr_free_ccb(np, cp, cmd->target, cmd->lun); + restore_flags(flags); + return(DID_ERROR); + } + + /*---------------------------------------------------- + ** + ** Guess xfer direction. + ** Spare some CPU by testing here frequently opcode. + ** + **---------------------------------------------------- + */ + switch((int) cmd->cmnd[0]) { + case 0x08: /* READ(6) 08 */ + case 0x28: /* READ(10) 28 */ + case 0xA8: /* READ(12) A8 */ + xfer_direction = XferIn; + break; + case 0x0A: /* WRITE(6) 0A */ + case 0x2A: /* WRITE(10) 2A */ + case 0xAA: /* WRITE(12) AA */ + xfer_direction = XferOut; + break; + default: + xfer_direction = guess_xfer_direction((int) cmd->cmnd[0]); + break; + } + + /*---------------------------------------------------- + ** + ** Set the SAVED_POINTER. + ** + **---------------------------------------------------- + */ + + cp->segments = segments; + if (!cp->data_len) + xfer_direction = XferNone; + + switch (xfer_direction) { + u_long endp; + default: + case XferBoth: + cp->phys.header.savep = + cpu_to_scr(NCB_SCRIPT_PHYS (np, data_io)); + cp->phys.header.goalp = cp->phys.header.savep; + break; + case XferIn: + endp = NCB_SCRIPT_PHYS (np, data_in) + MAX_SCATTER*16; + cp->phys.header.goalp = cpu_to_scr(endp + 8); + cp->phys.header.savep = cpu_to_scr(endp - segments*16); + break; + case XferOut: + endp = NCB_SCRIPTH_PHYS (np, data_out) + MAX_SCATTER*16; + cp->phys.header.goalp = cpu_to_scr(endp + 8); + cp->phys.header.savep = cpu_to_scr(endp - segments*16); + break; + case XferNone: + cp->phys.header.savep = + cpu_to_scr(NCB_SCRIPT_PHYS (np, no_data)); + cp->phys.header.goalp = cp->phys.header.savep; + break; + } + + cp->phys.header.lastp = cp->phys.header.savep; + + /*---------------------------------------------------- + ** + ** fill in ccb + ** + **---------------------------------------------------- + ** + ** + ** physical -> virtual backlink + ** Generic SCSI command + */ + cp->phys.header.cp = cp; + /* + ** Startqueue + */ + cp->phys.header.launch.l_paddr = + cpu_to_scr(NCB_SCRIPT_PHYS (np, select)); + cp->phys.header.launch.l_cmd = cpu_to_scr(SCR_JUMP); + /* + ** select + */ + cp->phys.select.sel_id = cmd->target; + cp->phys.select.sel_scntl3 = tp->wval; + cp->phys.select.sel_sxfer = tp->sval; + /* + ** message + */ + cp->phys.smsg.addr = cpu_to_scr(CCB_PHYS (cp, scsi_smsg)); + cp->phys.smsg.size = cpu_to_scr(msglen); + + cp->phys.smsg2.addr = cpu_to_scr(CCB_PHYS (cp, scsi_smsg2)); + cp->phys.smsg2.size = cpu_to_scr(msglen2); + /* + ** command + */ + cp->phys.cmd.addr = cpu_to_scr(vtophys (&cmd->cmnd[0])); + cp->phys.cmd.size = cpu_to_scr(cmd->cmd_len); + /* + ** sense command + */ + cp->phys.scmd.addr = cpu_to_scr(CCB_PHYS (cp, sensecmd)); + cp->phys.scmd.size = cpu_to_scr(6); + /* + ** patch requested size into sense command + */ + cp->sensecmd[0] = 0x03; + cp->sensecmd[1] = cmd->lun << 5; + cp->sensecmd[4] = sizeof(cmd->sense_buffer); + /* + ** sense data + */ + cp->phys.sense.addr = + cpu_to_scr(vtophys (&cmd->sense_buffer[0])); + cp->phys.sense.size = cpu_to_scr(sizeof(cmd->sense_buffer)); + /* + ** status + */ + cp->actualquirks = tp->quirks; + cp->host_status = nego ? HS_NEGOTIATE : HS_BUSY; + cp->scsi_status = S_ILLEGAL; + cp->parity_status = 0; + + cp->xerr_status = XE_OK; + cp->sync_status = tp->sval; + cp->nego_status = nego; + cp->wide_status = tp->wval; + + /*---------------------------------------------------- + ** + ** Critical region: start this job. + ** + **---------------------------------------------------- + */ + + /* + ** reselect pattern and activate this job. + */ + + cp->jump_ccb.l_cmd = + cpu_to_scr((SCR_JUMP ^ IFFALSE (DATA (cp->tag)))); + + /* Compute a time limit greater than the middle-level driver one */ + if (cmd->timeout_per_command > 0) + cp->tlimit = jiffies + cmd->timeout_per_command + NCR_TIMEOUT_INCREASE; + else + cp->tlimit = jiffies + 3600 * HZ; /* No timeout=one hour */ + cp->magic = CCB_MAGIC; + + /* + ** insert into start queue. + */ + + qidx = np->squeueput + 1; + if (qidx >= MAX_START) qidx=0; + np->squeue [qidx ] = cpu_to_scr(NCB_SCRIPT_PHYS (np, idle)); + np->squeue [np->squeueput] = cpu_to_scr(CCB_PHYS (cp, phys)); + np->squeueput = qidx; + + if(DEBUG_FLAGS & DEBUG_QUEUE) + printf ("%s: queuepos=%d tryoffset=%d.\n", ncr_name (np), + np->squeueput, + (unsigned)(scr_to_cpu(np->script->startpos[0]) - + (NCB_SCRIPTH_PHYS (np, tryloop)))); + + /* + ** Script processor may be waiting for reselect. + ** Wake it up. + */ +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + if (!np->stalling) +#endif + OUTB (nc_istat, SIGP); + + /* + ** and reenable interrupts + */ + restore_flags(flags); + + /* + ** Command is successfully queued. + */ + + return(DID_OK); +} + +/*========================================================== +** +** +** Start reset process. +** If reset in progress do nothing. +** The interrupt handler will reinitialize the chip. +** The timeout handler will wait for settle_time before +** clearing it and so resuming command processing. +** +** +**========================================================== +*/ +static void ncr_start_reset(ncb_p np, int settle_delay) +{ + u_long flags; + + save_flags(flags); cli(); + + if (!np->settle_time) { + (void) ncr_reset_scsi_bus(np, 1, settle_delay); + } + restore_flags(flags); +} + +static int ncr_reset_scsi_bus(ncb_p np, int enab_int, int settle_delay) +{ + u_int32 term; + int retv = 0; + + np->settle_time = jiffies + settle_delay * HZ; + + if (bootverbose > 1) + printf("%s: resetting, " + "command processing suspended for %d seconds\n", + ncr_name(np), settle_delay); + + OUTB (nc_istat, SRST); + DELAY (1000); + OUTB (nc_istat, 0); + if (enab_int) + OUTW (nc_sien, RST); + /* + ** Enable Tolerant, reset IRQD if present and + ** properly set IRQ mode, prior to resetting the bus. + */ + OUTB (nc_stest3, TE); + OUTB (nc_dcntl, (np->rv_dcntl & IRQM)); + OUTB (nc_scntl1, CRST); + DELAY (100); + + if (!driver_setup.bus_check) + goto out; + /* + ** Check for no terminators or SCSI bus shorts to ground. + ** Read SCSI data bus, data parity bits and control signals. + ** We are expecting RESET to be TRUE and other signals to be + ** FALSE. + */ + term = INB(nc_sstat0); /* rst, sdp0 */ + term = ((term & 2) << 7) + ((term & 1) << 16); + term |= ((INB(nc_sstat2) & 0x01) << 25) | /* sdp1 */ + (INW(nc_sbdl) << 9) | /* d15-0 */ + INB(nc_sbcl); /* req, ack, bsy, sel, atn, msg, cd, io */ + + if (!(np->features & FE_WIDE)) + term &= 0x3ffff; + + if (term != (2<<7)) { + printf("%s: suspicious SCSI data while resetting the BUS.\n", + ncr_name(np)); + printf("%s: %sdp0,d7-0,rst,req,ack,bsy,sel,atn,msg,c/d,i/o = " + "0x%lx, expecting 0x%lx\n", + ncr_name(np), + (np->features & FE_WIDE) ? "dp1,d15-8," : "", + (u_long)term, (u_long)(2<<7)); + if (driver_setup.bus_check == 1) + retv = 1; + } +out: + OUTB (nc_scntl1, 0); + return retv; +} + +/*========================================================== +** +** +** Reset the SCSI BUS. +** This is called from the generic SCSI driver. +** +** +**========================================================== +*/ +int ncr_reset_bus (Scsi_Cmnd *cmd, int sync_reset) +{ + struct Scsi_Host *host = cmd->host; +/* Scsi_Device *device = cmd->device; */ + struct host_data *host_data = (struct host_data *) host->hostdata; + ncb_p np = host_data->ncb; + ccb_p cp; + u_long flags; + int found; + +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + if (np->stalling) + np->stalling = 0; +#endif + + save_flags(flags); cli(); +/* + * Return immediately if reset is in progress. + */ + if (np->settle_time) { + restore_flags(flags); + return SCSI_RESET_PUNT; + } +/* + * Start the reset process. + * The script processor is then assumed to be stopped. + * Commands will now be queued in the waiting list until a settle + * delay of 2 seconds will be completed. + */ + ncr_start_reset(np, driver_setup.settle_delay); +/* + * First, look in the wakeup list + */ + for (found=0, cp=np->ccb; cp; cp=cp->link_ccb) { + /* + ** look for the ccb of this command. + */ + if (cp->host_status == HS_IDLE) continue; + if (cp->cmd == cmd) { + found = 1; + break; + } + } +/* + * Then, look in the waiting list + */ + if (!found && retrieve_from_waiting_list(0, np, cmd)) + found = 1; +/* + * Wake-up all awaiting commands with DID_RESET. + */ + reset_waiting_list(np); +/* + * Wake-up all pending commands with HS_RESET -> DID_RESET. + */ + ncr_wakeup(np, HS_RESET); +/* + * If the involved command was not in a driver queue, and the + * scsi driver told us reset is synchronous, and the command is not + * currently in the waiting list, complete it with DID_RESET status, + * in order to keep it alive. + */ + if (!found && sync_reset && !retrieve_from_waiting_list(0, np, cmd)) { + cmd->result = ScsiResult(DID_RESET, 0); + cmd->scsi_done(cmd); + } + + restore_flags(flags); + + return SCSI_RESET_SUCCESS; +} + +/*========================================================== +** +** +** Abort an SCSI command. +** This is called from the generic SCSI driver. +** +** +**========================================================== +*/ +static int ncr_abort_command (Scsi_Cmnd *cmd) +{ + struct Scsi_Host *host = cmd->host; +/* Scsi_Device *device = cmd->device; */ + struct host_data *host_data = (struct host_data *) host->hostdata; + ncb_p np = host_data->ncb; + ccb_p cp; + u_long flags; + int found; + int retv; + +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + if (np->stalling == 2) + np->stalling = 0; +#endif + + save_flags(flags); cli(); +/* + * First, look for the scsi command in the waiting list + */ + if (remove_from_waiting_list(np, cmd)) { + cmd->result = ScsiResult(DID_ABORT, 0); + cmd->scsi_done(cmd); + restore_flags(flags); + return SCSI_ABORT_SUCCESS; + } + +/* + * Then, look in the wakeup list + */ + for (found=0, cp=np->ccb; cp; cp=cp->link_ccb) { + /* + ** look for the ccb of this command. + */ + if (cp->host_status == HS_IDLE) continue; + if (cp->cmd == cmd) { + found = 1; + break; + } + } + + if (!found) { + restore_flags(flags); + return SCSI_ABORT_NOT_RUNNING; + } + + if (np->settle_time) { + restore_flags(flags); + return SCSI_ABORT_SNOOZE; + } + + /* + ** Disable reselect. + ** Remove it from startqueue. + ** Set cp->tlimit to 0. The ncr_timeout() handler will use + ** this condition in order to complete the canceled command + ** after the script skipped the ccb, if necessary. + */ + cp->jump_ccb.l_cmd = cpu_to_scr(SCR_JUMP); + if (cp->phys.header.launch.l_paddr == + cpu_to_scr(NCB_SCRIPT_PHYS (np, select))) { + printf ("%s: abort ccb=%p (skip)\n", ncr_name (np), cp); + cp->phys.header.launch.l_paddr = + cpu_to_scr(NCB_SCRIPT_PHYS (np, skip)); + } + + cp->tlimit = 0; + retv = SCSI_ABORT_PENDING; + + /* + ** If there are no requests, the script + ** processor will sleep on SEL_WAIT_RESEL. + ** Let's wake it up, since it may have to work. + */ +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + if (!np->stalling) +#endif + OUTB (nc_istat, SIGP); + + restore_flags(flags); + + return retv; +} + +/*========================================================== +** +** Linux release module stuff. +** +** Called before unloading the module +** Detach the host. +** We have to free resources and halt the NCR chip +** +**========================================================== +*/ + +#ifdef MODULE +static int ncr_detach(ncb_p np) +{ + ccb_p cp; + tcb_p tp; + lcb_p lp; + int target, lun; + int i; + + printf("%s: releasing host resources\n", ncr_name(np)); + +/* +** Stop the ncr_timeout process +** Set release_stage to 1 and wait that ncr_timeout() set it to 2. +*/ + +#ifdef DEBUG_NCR53C8XX + printf("%s: stopping the timer\n", ncr_name(np)); +#endif + np->release_stage = 1; + for (i = 50 ; i && np->release_stage != 2 ; i--) DELAY(100000); + if (np->release_stage != 2) + printf("%s: the timer seems to be already stopped\n", ncr_name(np)); + else np->release_stage = 2; + +/* +** Disable chip interrupts +*/ + +#ifdef DEBUG_NCR53C8XX + printf("%s: disabling chip interrupts\n", ncr_name(np)); +#endif + OUTW (nc_sien , 0); + OUTB (nc_dien , 0); + +/* +** Free irq +*/ + +#ifdef DEBUG_NCR53C8XX + printf("%s: freeing irq %d\n", ncr_name(np), np->irq); +#endif +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,70) + free_irq(np->irq, np); +#else + free_irq(np->irq); +#endif + + /* + ** Reset NCR chip + ** Restore bios setting for automatic clock detection. + */ + + printf("%s: resetting chip\n", ncr_name(np)); + OUTB (nc_istat, SRST); + DELAY (1000); + OUTB (nc_istat, 0 ); + + OUTB(nc_dmode, np->sv_dmode); + OUTB(nc_dcntl, np->sv_dcntl); + OUTB(nc_ctest3, np->sv_ctest3); + OUTB(nc_ctest4, np->sv_ctest4); + OUTB(nc_ctest5, np->sv_ctest5); + OUTB(nc_gpcntl, np->sv_gpcntl); + OUTB(nc_stest2, np->sv_stest2); + + ncr_selectclock(np, np->sv_scntl3); + + /* + ** Release Memory mapped IO region and IO mapped region + */ + +#ifndef NCR_IOMAPPED +#ifdef DEBUG_NCR53C8XX + printf("%s: releasing memory mapped IO region %lx[%d]\n", ncr_name(np), (u_long) np->vaddr, 128); +#endif + unmap_pci_mem((vm_offset_t) np->vaddr, (u_long) 128); +#ifdef DEBUG_NCR53C8XX + printf("%s: releasing memory mapped IO region %lx[%d]\n", ncr_name(np), (u_long) np->vaddr2, 4096); +#endif + unmap_pci_mem((vm_offset_t) np->vaddr2, (u_long) 4096); +#endif + +#ifdef DEBUG_NCR53C8XX + printf("%s: releasing IO region %x[%d]\n", ncr_name(np), np->port, 128); +#endif + release_region(np->port, 128); + + /* + ** Free allocated ccb(s) + */ + + while ((cp=np->ccb->link_ccb) != NULL) { + np->ccb->link_ccb = cp->link_ccb; + if (cp->host_status) { + printf("%s: shall free an active ccb (host_status=%d)\n", + ncr_name(np), cp->host_status); + } +#ifdef DEBUG_NCR53C8XX + printf("%s: freeing ccb (%lx)\n", ncr_name(np), (u_long) cp); +#endif + m_free(cp, sizeof(*cp)); + } + + /* + ** Free allocated tp(s) + */ + + for (target = 0; target < MAX_TARGET ; target++) { + tp=&np->target[target]; + for (lun = 0 ; lun < MAX_LUN ; lun++) { + lp = tp->lp[lun]; + if (lp) { +#ifdef DEBUG_NCR53C8XX + printf("%s: freeing lp (%lx)\n", ncr_name(np), (u_long) lp); +#endif + m_free(lp, sizeof(*lp)); + } + } + } + + printf("%s: host resources successfully released\n", ncr_name(np)); + + return 1; +} +#endif + +/*========================================================== +** +** +** Complete execution of a SCSI command. +** Signal completion to the generic SCSI driver. +** +** +**========================================================== +*/ + +void ncr_complete (ncb_p np, ccb_p cp) +{ + Scsi_Cmnd *cmd; + tcb_p tp; + lcb_p lp; + + /* + ** Sanity check + */ + + if (!cp || (cp->magic!=CCB_MAGIC) || !cp->cmd) return; + cp->magic = 1; + cp->tlimit= 0; + cmd = cp->cmd; + + /* + ** No Reselect anymore. + */ + cp->jump_ccb.l_cmd = cpu_to_scr(SCR_JUMP); + + /* + ** No starting. + */ + cp->phys.header.launch.l_paddr = cpu_to_scr(NCB_SCRIPT_PHYS (np, idle)); + + /* + ** timestamp + ** Optional, spare some CPU time + */ +#ifdef SCSI_NCR_PROFILE_SUPPORT + ncb_profile (np, cp); +#endif + + if (DEBUG_FLAGS & DEBUG_TINY) + printf ("CCB=%lx STAT=%x/%x\n", (unsigned long)cp & 0xfff, + cp->host_status,cp->scsi_status); + + cmd = cp->cmd; + cp->cmd = NULL; + tp = &np->target[cmd->target]; + lp = tp->lp[cmd->lun]; + + /* + ** We donnot queue more than 1 ccb per target + ** with negotiation at any time. If this ccb was + ** used for negotiation, clear this info in the tcb. + */ + + if (cp == tp->nego_cp) + tp->nego_cp = 0; + + /* + ** Check for parity errors. + */ + + if (cp->parity_status) { + PRINT_ADDR(cmd); + printf ("%d parity error(s), fallback.\n", cp->parity_status); + /* + ** fallback to asynch transfer. + */ + tp->usrsync=255; + tp->period = 0; + } + + /* + ** Check for extended errors. + */ + + if (cp->xerr_status != XE_OK) { + PRINT_ADDR(cmd); + switch (cp->xerr_status) { + case XE_EXTRA_DATA: + printf ("extraneous data discarded.\n"); + break; + case XE_BAD_PHASE: + printf ("illegal scsi phase (4/5).\n"); + break; + default: + printf ("extended error %d.\n", cp->xerr_status); + break; + } + if (cp->host_status==HS_COMPLETE) + cp->host_status = HS_FAIL; + } + + /* + ** Check the status. + */ + if ( (cp->host_status == HS_COMPLETE) + && (cp->scsi_status == S_GOOD || + cp->scsi_status == S_COND_MET)) { + /* + ** All went well (GOOD status). + ** CONDITION MET status is returned on + ** `Pre-Fetch' or `Search data' success. + */ + cmd->result = ScsiResult(DID_OK, cp->scsi_status); + + /* + ** if (cp->phys.header.lastp != cp->phys.header.goalp)... + ** + ** @RESID@ + ** Could dig out the correct value for resid, + ** but it would be quite complicated. + ** + ** The ah1542.c driver sets it to 0 too ... + */ + + /* + ** Try to assign a ccb to this nexus + */ + ncr_alloc_ccb (np, cmd->target, cmd->lun); + + /* + ** On inquire cmd (0x12) save some data. + ** Clear questionnable capacities. + */ + if (cmd->lun == 0 && cmd->cmnd[0] == 0x12) { + if (np->unit < SCSI_NCR_MAX_HOST) { + if (driver_setup.force_sync_nego) + ((char *) cmd->request_buffer)[7] |= INQ7_SYNC; + else + ((char *) cmd->request_buffer)[7] &= + (target_capabilities[np->unit].and_map[cmd->target]); + } + bcopy ( cmd->request_buffer, + &tp->inqdata, + sizeof (tp->inqdata)); + + /* + ** set number of tags + */ + ncr_setmaxtags (np, tp, driver_setup.default_tags); + /* + ** prepare negotiation of synch and wide. + */ + ncr_negotiate (np, tp); + + /* + ** force quirks update before next command start + */ + tp->quirks |= QUIRK_UPDATE; + } + + /* + ** Announce changes to the generic driver. + */ + if (lp) { + ncr_settags (tp, lp); + if (lp->reqlink != lp->actlink) + ncr_opennings (np, lp, cmd); + }; + + tp->bytes += cp->data_len; + tp->transfers ++; + + /* + ** If tags was reduced due to queue full, + ** increase tags if 100 good status received. + */ + if (tp->numtags < tp->maxtags) { + ++tp->num_good; + if (tp->num_good >= 100) { + tp->num_good = 0; + ++tp->numtags; + if (tp->numtags == 1) { + PRINT_ADDR(cmd); + printf("tagged command queueing resumed\n"); + } + } + } + } else if ((cp->host_status == HS_COMPLETE) + && (cp->scsi_status == (S_SENSE|S_GOOD) || + cp->scsi_status == (S_SENSE|S_CHECK_COND))) { + + /* + ** Check condition code + */ + cmd->result = ScsiResult(DID_OK, S_CHECK_COND); + + if (DEBUG_FLAGS & (DEBUG_RESULT|DEBUG_TINY)) { + u_char * p = (u_char*) & cmd->sense_buffer; + int i; + printf ("\n%s: sense data:", ncr_name (np)); + for (i=0; i<14; i++) printf (" %x", *p++); + printf (".\n"); + } + + } else if ((cp->host_status == HS_COMPLETE) + && (cp->scsi_status == S_BUSY || + cp->scsi_status == S_CONFLICT)) { + + /* + ** Target is busy. + */ + cmd->result = ScsiResult(DID_OK, cp->scsi_status); + + } else if ((cp->host_status == HS_COMPLETE) + && (cp->scsi_status == S_QUEUE_FULL)) { + + /* + ** Target is stuffed. + */ + cmd->result = ScsiResult(DID_OK, cp->scsi_status); + + /* + ** Suspend tagged queuing and start good status counter. + ** Announce changes to the generic driver. + */ + if (tp->numtags) { + PRINT_ADDR(cmd); + printf("QUEUE FULL! suspending tagged command queueing\n"); + tp->numtags = 0; + tp->num_good = 0; + if (lp) { + ncr_settags (tp, lp); + if (lp->reqlink != lp->actlink) + ncr_opennings (np, lp, cmd); + }; + } + } else if ((cp->host_status == HS_SEL_TIMEOUT) + || (cp->host_status == HS_TIMEOUT)) { + + /* + ** No response + */ + cmd->result = ScsiResult(DID_TIME_OUT, cp->scsi_status); + + } else if (cp->host_status == HS_RESET) { + + /* + ** SCSI bus reset + */ + cmd->result = ScsiResult(DID_RESET, cp->scsi_status); + + } else if (cp->host_status == HS_ABORTED) { + + /* + ** Transfer aborted + */ + cmd->result = ScsiResult(DID_ABORT, cp->scsi_status); + + } else { + + /* + ** Other protocol messes + */ + PRINT_ADDR(cmd); + printf ("COMMAND FAILED (%x %x) @%p.\n", + cp->host_status, cp->scsi_status, cp); + + cmd->result = ScsiResult(DID_ERROR, cp->scsi_status); + } + + /* + ** trace output + */ + + if (tp->usrflag & UF_TRACE) { + u_char * p; + int i; + PRINT_ADDR(cmd); + printf (" CMD:"); + p = (u_char*) &cmd->cmnd[0]; + for (i=0; i<cmd->cmd_len; i++) printf (" %x", *p++); + + if (cp->host_status==HS_COMPLETE) { + switch (cp->scsi_status) { + case S_GOOD: + printf (" GOOD"); + break; + case S_CHECK_COND: + printf (" SENSE:"); + p = (u_char*) &cmd->sense_buffer; + for (i=0; i<14; i++) + printf (" %x", *p++); + break; + default: + printf (" STAT: %x\n", cp->scsi_status); + break; + } + } else printf (" HOSTERROR: %x", cp->host_status); + printf ("\n"); + } + + /* + ** Free this ccb + */ + ncr_free_ccb (np, cp, cmd->target, cmd->lun); + + /* + ** requeue awaiting scsi commands + */ + if (np->waiting_list) requeue_waiting_list(np); + + /* + ** signal completion to generic driver. + */ + cmd->scsi_done (cmd); +} + +/*========================================================== +** +** +** Signal all (or one) control block done. +** +** +**========================================================== +*/ + +void ncr_wakeup (ncb_p np, u_long code) +{ + /* + ** Starting at the default ccb and following + ** the links, complete all jobs with a + ** host_status greater than "disconnect". + ** + ** If the "code" parameter is not zero, + ** complete all jobs that are not IDLE. + */ + + ccb_p cp = np->ccb; + while (cp) { + switch (cp->host_status) { + + case HS_IDLE: + break; + + case HS_DISCONNECT: + if(DEBUG_FLAGS & DEBUG_TINY) printf ("D"); + /* fall through */ + + case HS_BUSY: + case HS_NEGOTIATE: + if (!code) break; + cp->host_status = code; + + /* fall through */ + + default: + ncr_complete (np, cp); + break; + }; + cp = cp -> link_ccb; + }; +} + +/*========================================================== +** +** +** Start NCR chip. +** +** +**========================================================== +*/ + +void ncr_init (ncb_p np, int reset, char * msg, u_long code) +{ + int i; + + /* + ** Reset chip if asked, otherwise just clear fifos. + */ + if (reset) { + OUTB (nc_istat, SRST); + DELAY (10000); + } + else { + OUTB (nc_stest3, TE|CSF); + OUTONB (nc_ctest3, CLF); + } + + /* + ** Message. + */ + + if (msg) printf (KERN_INFO "%s: restart (%s).\n", ncr_name (np), msg); + + /* + ** Clear Start Queue + */ + for (i=0;i<MAX_START;i++) + np -> squeue [i] = cpu_to_scr(NCB_SCRIPT_PHYS (np, idle)); + + /* + ** Start at first entry. + */ + np->squeueput = 0; + np->script0->startpos[0] = cpu_to_scr(NCB_SCRIPTH_PHYS (np, tryloop)); + np->script0->start0 [0] = cpu_to_scr(SCR_INT ^ IFFALSE (0)); + + /* + ** Wakeup all pending jobs. + */ + ncr_wakeup (np, code); + + /* + ** Init chip. + */ + + OUTB (nc_istat, 0x00 ); /* Remove Reset, abort */ + OUTB (nc_scntl0, np->rv_scntl0 | 0xc0); + /* full arb., ena parity, par->ATN */ + OUTB (nc_scntl1, 0x00); /* odd parity, and remove CRST!! */ + + ncr_selectclock(np, np->rv_scntl3); /* Select SCSI clock */ + + OUTB (nc_scid , RRE|np->myaddr); /* Adapter SCSI address */ + OUTW (nc_respid, 1ul<<np->myaddr); /* Id to respond to */ + OUTB (nc_istat , SIGP ); /* Signal Process */ + OUTB (nc_dmode , np->rv_dmode); /* Burst length, dma mode */ + OUTB (nc_ctest5, np->rv_ctest5); /* Large fifo + large burst */ + + OUTB (nc_dcntl , NOCOM|np->rv_dcntl); /* Protect SFBR */ + OUTB (nc_ctest3, np->rv_ctest3); /* Write and invalidate */ + OUTB (nc_ctest4, np->rv_ctest4); /* Master parity checking */ + + OUTB (nc_stest2, EXT|np->rv_stest2); /* Extended Sreq/Sack filtering */ + OUTB (nc_stest3, TE); /* TolerANT enable */ + OUTB (nc_stime0, 0x0d ); /* HTH disabled STO 0.4 sec. */ + + /* + ** Disable disconnects. + */ + + np->disc = 0; + + /* + ** Enable GPIO0 pin for writing if LED support. + */ + + if (np->features & FE_LED0) { + OUTOFFB (nc_gpcntl, 0x01); + } + + /* + ** Upload the script into on-board RAM + */ + if (np->vaddr2) { + if (bootverbose) + printf ("%s: copying script fragments into the on-board RAM ...\n", ncr_name(np)); +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,0,0) + memcpy_toio(np->script, np->script0, sizeof(struct script)); +#else + memcpy(np->script, np->script0, sizeof(struct script)); +#endif + } + + /* + ** enable ints + */ + + OUTW (nc_sien , STO|HTH|MA|SGE|UDC|RST); + OUTB (nc_dien , MDPE|BF|ABRT|SSI|SIR|IID); + + /* + ** For 895/6 enable SBMC interrupt and save current SCSI bus mode. + */ + if (np->features & FE_ULTRA2) { + OUTONW (nc_sien, SBMC); + np->scsi_mode = INB (nc_stest4) & SMODE; + } + + /* + ** Fill in target structure. + ** Reinitialize usrsync. + ** Reinitialize usrwide. + ** Prepare sync negotiation according to actual SCSI bus mode. + */ + + for (i=0;i<MAX_TARGET;i++) { + tcb_p tp = &np->target[i]; + + tp->sval = 0; + tp->wval = np->rv_scntl3; + + if (tp->usrsync != 255) { + if (tp->usrsync <= np->maxsync) { + if (tp->usrsync < np->minsync) { + tp->usrsync = np->minsync; + } + } + else + tp->usrsync = 255; + }; + + if (tp->usrwide > np->maxwide) + tp->usrwide = np->maxwide; + + ncr_negotiate (np, tp); + } + + /* + ** Start script processor. + */ + + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, start)); +} + +/*========================================================== +** +** Prepare the negotiation values for wide and +** synchronous transfers. +** +**========================================================== +*/ + +static void ncr_negotiate (struct ncb* np, struct tcb* tp) +{ + /* + ** minsync unit is 4ns ! + */ + + u_long minsync = tp->usrsync; + + /* + ** SCSI bus mode limit + */ + + if (np->scsi_mode && np->scsi_mode == SMODE_SE) { + if (minsync < 12) minsync = 12; + } + + /* + ** if not scsi 2 + ** don't believe FAST! + */ + + if ((minsync < 50) && (tp->inqdata[2] & 0x0f) < 2) + minsync=50; + + /* + ** our limit .. + */ + + if (minsync < np->minsync) + minsync = np->minsync; + + /* + ** divider limit + */ + + if (minsync > np->maxsync) + minsync = 255; + + tp->minsync = minsync; + tp->maxoffs = (minsync<255 ? np->maxoffs : 0); + + /* + ** period=0: has to negotiate sync transfer + */ + + tp->period=0; + + /* + ** widedone=0: has to negotiate wide transfer + */ + tp->widedone=0; +} + +/*========================================================== +** +** Get clock factor and sync divisor for a given +** synchronous factor period. +** Returns the clock factor (in sxfer) and scntl3 +** synchronous divisor field. +** +**========================================================== +*/ + +static void ncr_getsync(ncb_p np, u_char sfac, u_char *fakp, u_char *scntl3p) +{ + u_long clk = np->clock_khz; /* SCSI clock frequency in kHz */ + int div = np->clock_divn; /* Number of divisors supported */ + u_long fak; /* Sync factor in sxfer */ + u_long per; /* Period in tenths of ns */ + u_long kpc; /* (per * clk) */ + + /* + ** Compute the synchronous period in tenths of nano-seconds + */ + if (sfac <= 10) per = 250; + else if (sfac == 11) per = 303; + else if (sfac == 12) per = 500; + else per = 40 * sfac; + + /* + ** Look for the greatest clock divisor that allows an + ** input speed faster than the period. + */ + kpc = per * clk; + while (--div >= 0) + if (kpc >= (div_10M[div] << 2)) break; + + /* + ** Calculate the lowest clock factor that allows an output + ** speed not faster than the period. + */ + fak = (kpc - 1) / div_10M[div] + 1; + +#if 0 /* This optimization does not seem very usefull */ + + per = (fak * div_10M[div]) / clk; + + /* + ** Why not to try the immediate lower divisor and to choose + ** the one that allows the fastest output speed ? + ** We don't want input speed too much greater than output speed. + */ + if (div >= 1 && fak < 8) { + u_long fak2, per2; + fak2 = (kpc - 1) / div_10M[div-1] + 1; + per2 = (fak2 * div_10M[div-1]) / clk; + if (per2 < per && fak2 <= 8) { + fak = fak2; + per = per2; + --div; + } + } +#endif + + if (fak < 4) fak = 4; /* Should never happen, too bad ... */ + + /* + ** Compute and return sync parameters for the ncr + */ + *fakp = fak - 4; + *scntl3p = ((div+1) << 4) + (sfac < 25 ? 0x80 : 0); +} + + +/*========================================================== +** +** Set actual values, sync status and patch all ccbs of +** a target according to new sync/wide agreement. +** +**========================================================== +*/ + +static void ncr_set_sync_wide_status (ncb_p np, u_char target) +{ + ccb_p cp; + tcb_p tp = &np->target[target]; + + /* + ** set actual value and sync_status + */ + OUTB (nc_sxfer, tp->sval); + np->sync_st = tp->sval; + OUTB (nc_scntl3, tp->wval); + np->wide_st = tp->wval; + + /* + ** patch ALL ccbs of this target. + */ + for (cp = np->ccb; cp; cp = cp->link_ccb) { + if (!cp->cmd) continue; + if (cp->cmd->target != target) continue; + cp->sync_status = tp->sval; + cp->wide_status = tp->wval; + }; +} + +/*========================================================== +** +** Switch sync mode for current job and it's target +** +**========================================================== +*/ + +static void ncr_setsync (ncb_p np, ccb_p cp, u_char scntl3, u_char sxfer) +{ + Scsi_Cmnd *cmd; + tcb_p tp; + u_char target = INB (nc_ctest0) & 0x0f; + u_char idiv; + + assert (cp); + if (!cp) return; + + cmd = cp->cmd; + assert (cmd); + if (!cmd) return; + assert (target == (cmd->target & 0xf)); + + tp = &np->target[target]; + + if (!scntl3 || !(sxfer & 0x1f)) + scntl3 = np->rv_scntl3; + scntl3 = (scntl3 & 0xf0) | (tp->wval & EWS) | (np->rv_scntl3 & 0x07); + + /* + ** Deduce the value of controller sync period from scntl3. + ** period is in tenths of nano-seconds. + */ + + idiv = ((scntl3 >> 4) & 0x7); + if ((sxfer & 0x1f) && idiv) + tp->period = (((sxfer>>5)+4)*div_10M[idiv-1])/np->clock_khz; + else + tp->period = 0xffff; + + /* + ** Stop there if sync parameters are unchanged + */ + if (tp->sval == sxfer && tp->wval == scntl3) return; + tp->sval = sxfer; + tp->wval = scntl3; + + /* + ** Bells and whistles ;-) + */ + PRINT_ADDR(cmd); + if (sxfer & 0x01f) { + unsigned f10 = 100000 << (tp->widedone ? tp->widedone -1 : 0); + unsigned mb10 = (f10 + tp->period/2) / tp->period; + char *scsi; + + /* + ** Disable extended Sreq/Sack filtering + */ + if (tp->period <= 2000) OUTOFFB (nc_stest2, EXT); + + /* + ** Bells and whistles ;-) + */ + if (tp->period < 500) scsi = "FAST-40"; + else if (tp->period < 1000) scsi = "FAST-20"; + else if (tp->period < 2000) scsi = "FAST-10"; + else scsi = "FAST-5"; + + printf ("%s %sSCSI %d.%d MB/s (%d ns, offset %d)\n", scsi, + tp->widedone > 1 ? "WIDE " : "", + mb10 / 10, mb10 % 10, tp->period / 10, sxfer & 0x1f); + } else + printf ("%sasynchronous.\n", tp->widedone > 1 ? "wide " : ""); + + /* + ** set actual value and sync_status + ** patch ALL ccbs of this target. + */ + ncr_set_sync_wide_status(np, target); +} + +/*========================================================== +** +** Switch wide mode for current job and it's target +** SCSI specs say: a SCSI device that accepts a WDTR +** message shall reset the synchronous agreement to +** asynchronous mode. +** +**========================================================== +*/ + +static void ncr_setwide (ncb_p np, ccb_p cp, u_char wide, u_char ack) +{ + Scsi_Cmnd *cmd; + u_short target = INB (nc_ctest0) & 0x0f; + tcb_p tp; + u_char scntl3; + u_char sxfer; + + assert (cp); + if (!cp) return; + + cmd = cp->cmd; + assert (cmd); + if (!cmd) return; + assert (target == (cmd->target & 0xf)); + + tp = &np->target[target]; + tp->widedone = wide+1; + scntl3 = (tp->wval & (~EWS)) | (wide ? EWS : 0); + + sxfer = ack ? 0 : tp->sval; + + /* + ** Stop there if sync/wide parameters are unchanged + */ + if (tp->sval == sxfer && tp->wval == scntl3) return; + tp->sval = sxfer; + tp->wval = scntl3; + + /* + ** Bells and whistles ;-) + */ + if (bootverbose >= 2) { + PRINT_ADDR(cmd); + if (scntl3 & EWS) + printf ("WIDE SCSI (16 bit) enabled.\n"); + else + printf ("WIDE SCSI disabled.\n"); + } + + /* + ** set actual value and sync_status + ** patch ALL ccbs of this target. + */ + ncr_set_sync_wide_status(np, target); +} + +/*========================================================== +** +** Switch tagged mode for a target. +** +**========================================================== +*/ + +static void ncr_setmaxtags (ncb_p np, tcb_p tp, u_long numtags) +{ + int l; + if (numtags > tp->usrtags) + numtags = tp->usrtags; + tp->numtags = numtags; + tp->maxtags = numtags; + + for (l=0; l<MAX_LUN; l++) { + lcb_p lp; + u_char wastags; + + if (!tp) break; + lp=tp->lp[l]; + if (!lp) continue; + + wastags = lp->usetags; + ncr_settags (tp, lp); + + if (numtags > 1 && lp->reqccbs > 1) { + PRINT_LUN(np, tp - np->target, l); + printf("using tagged command queueing, up to %ld cmds/lun\n", numtags); + } + else if (numtags <= 1 && wastags) { + PRINT_LUN(np, tp - np->target, l); + printf("disabling tagged command queueing\n"); + } + }; +} + +static void ncr_settags (tcb_p tp, lcb_p lp) +{ + u_char reqtags, tmp; + + if ((!tp) || (!lp)) return; + + /* + ** only devices conformant to ANSI Version >= 2 + ** only devices capable of tagges commands + ** only disk devices + ** only if enabled by user .. + */ + if (( tp->inqdata[2] & 0x7) >= 2 && + ( tp->inqdata[7] & INQ7_QUEUE) && ((tp->inqdata[0] & 0x1f)==0x00) + && tp->numtags > 1) { + reqtags = tp->numtags; + if (lp->actlink <= 1) + lp->usetags=reqtags; + } else { + reqtags = 1; + if (lp->actlink <= 1) + lp->usetags=0; + }; + + /* + ** don't announce more than available. + */ + tmp = lp->actccbs; + if (tmp > reqtags) tmp = reqtags; + lp->reqlink = tmp; + + /* + ** don't discard if announced. + */ + tmp = lp->actlink; + if (tmp < reqtags) tmp = reqtags; + lp->reqccbs = tmp; +} + +/*---------------------------------------------------- +** +** handle user commands +** +**---------------------------------------------------- +*/ + +#ifdef SCSI_NCR_USER_COMMAND_SUPPORT + +static void ncr_usercmd (ncb_p np) +{ + u_char t; + tcb_p tp; + + switch (np->user.cmd) { + + case 0: return; + + case UC_SETSYNC: + for (t=0; t<MAX_TARGET; t++) { + if (!((np->user.target>>t)&1)) continue; + tp = &np->target[t]; + tp->usrsync = np->user.data; + ncr_negotiate (np, tp); + }; + break; + + case UC_SETTAGS: + if (np->user.data > SCSI_NCR_MAX_TAGS) + np->user.data = SCSI_NCR_MAX_TAGS; + for (t=0; t<MAX_TARGET; t++) { + if (!((np->user.target>>t)&1)) continue; + np->target[t].usrtags = np->user.data; + ncr_setmaxtags (np, &np->target[t], np->user.data); + }; + break; + + case UC_SETDEBUG: +#ifdef SCSI_NCR_DEBUG_INFO_SUPPORT + ncr_debug = np->user.data; +#endif + break; + + case UC_SETORDER: + np->order = np->user.data; + break; + + case UC_SETWIDE: + for (t=0; t<MAX_TARGET; t++) { + u_long size; + if (!((np->user.target>>t)&1)) continue; + tp = &np->target[t]; + size = np->user.data; + if (size > np->maxwide) size=np->maxwide; + tp->usrwide = size; + ncr_negotiate (np, tp); + }; + break; + + case UC_SETFLAG: + for (t=0; t<MAX_TARGET; t++) { + if (!((np->user.target>>t)&1)) continue; + tp = &np->target[t]; + tp->usrflag = np->user.data; + }; + break; + + case UC_CLEARPROF: + bzero(&np->profile, sizeof(np->profile)); + break; +#ifdef UC_DEBUG_ERROR_RECOVERY + case UC_DEBUG_ERROR_RECOVERY: + np->debug_error_recovery = np->user.data; + break; +#endif + } + np->user.cmd=0; +} +#endif + + +/*===================================================================== +** +** Embedded error recovery debugging code. +** +**===================================================================== +** +** This code is conditionned by SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT. +** It only can be enabled after boot-up with a control command. +** +** Every 30 seconds the timer handler of the driver decides to +** change the behaviour of the driver in order to trigger errors. +** +** If last command was "debug_error_recovery sge", the driver +** sets sync offset of all targets that use sync transfers to 2, +** and so hopes a SCSI gross error at the next read operation. +** +** If last command was "debug_error_recovery abort", the driver +** does not signal new scsi commands to the script processor, until +** it is asked to abort or reset a command by the mid-level driver. +** +** If last command was "debug_error_recovery reset", the driver +** does not signal new scsi commands to the script processor, until +** it is asked to reset a command by the mid-level driver. +** +** If last command was "debug_error_recovery parity", the driver +** will assert ATN on the next DATA IN phase mismatch, and so will +** behave as if a parity error had been detected. +** +** The command "debug_error_recovery none" makes the driver behave +** normaly. +** +**===================================================================== +*/ + +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT +static void ncr_trigger_errors (ncb_p np) +{ + /* + ** If np->debug_error_recovery is not zero, we want to + ** simulate common errors in order to test error recovery. + */ + do { + static u_long last = 0l; + + if (!np->debug_error_recovery) + break; + if (!last) + last = jiffies; + else if (jiffies < last + 30*HZ) + break; + last = jiffies; + /* + * This one triggers SCSI gross errors. + */ + if (np->debug_error_recovery == 1) { + int i; + printf("%s: testing error recovery from SCSI gross error...\n", ncr_name(np)); + for (i = 0 ; i < MAX_TARGET ; i++) { + if (np->target[i].sval & 0x1f) { + np->target[i].sval &= ~0x1f; + np->target[i].sval += 2; + } + } + } + /* + * This one triggers abort from the mid-level driver. + */ + else if (np->debug_error_recovery == 2) { + printf("%s: testing error recovery from mid-level driver abort()...\n", ncr_name(np)); + np->stalling = 2; + } + /* + * This one triggers reset from the mid-level driver. + */ + else if (np->debug_error_recovery == 3) { + printf("%s: testing error recovery from mid-level driver reset()...\n", ncr_name(np)); + np->stalling = 3; + } + /* + * This one set ATN on phase mismatch in DATA IN phase and so + * will behave as on scsi parity error detected. + */ + else if (np->debug_error_recovery == 4) { + printf("%s: testing data in parity error...\n", ncr_name(np)); + np->assert_atn = 1; + } + } while (0); +} +#endif + +/*========================================================== +** +** +** ncr timeout handler. +** +** +**========================================================== +** +** Misused to keep the driver running when +** interrupts are not configured correctly. +** +**---------------------------------------------------------- +*/ + +static void ncr_timeout (ncb_p np) +{ + u_long thistime = jiffies; + u_long count = 0; + ccb_p cp; + u_long flags; + + /* + ** If release process in progress, let's go + ** Set the release stage from 1 to 2 to synchronize + ** with the release process. + */ + + if (np->release_stage) { + if (np->release_stage == 1) np->release_stage = 2; + return; + } + + np->timer.expires = +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) + jiffies + +#endif + SCSI_NCR_TIMER_INTERVAL; + + add_timer(&np->timer); + +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + ncr_trigger_errors (np); +#endif + + /* + ** If we are resetting the ncr, wait for settle_time before + ** clearing it. Then command processing will be resumed. + */ + if (np->settle_time) { + if (np->settle_time <= thistime) { + if (bootverbose > 1) + printf("%s: command processing resumed\n", ncr_name(np)); + save_flags(flags); cli(); + np->settle_time = 0; + np->disc = 1; + requeue_waiting_list(np); + restore_flags(flags); + } + return; + } + + /* + ** Since the generic scsi driver only allows us 0.5 second + ** to perform abort of a command, we must look at ccbs about + ** every 0.25 second. + */ + if (np->lasttime + (HZ>>2) <= thistime) { + /* + ** block ncr interrupts + */ + save_flags(flags); cli(); + + np->lasttime = thistime; + + /* + ** Reset profile data to avoid ugly overflow + ** (Limited to 1024 GB for 32 bit architecture) + */ + if (np->profile.num_kbytes > (~0UL >> 2)) + bzero(&np->profile, sizeof(np->profile)); + + /*---------------------------------------------------- + ** + ** handle ncr chip timeouts + ** + ** Assumption: + ** We have a chance to arbitrate for the + ** SCSI bus at least every 10 seconds. + ** + **---------------------------------------------------- + */ +#if 0 + if (thistime < np->heartbeat + HZ + HZ) + np->latetime = 0; + else + np->latetime++; +#endif + + /*---------------------------------------------------- + ** + ** handle ccb timeouts + ** + **---------------------------------------------------- + */ + + for (cp=np->ccb; cp; cp=cp->link_ccb) { + /* + ** look for timed out ccbs. + */ + if (!cp->host_status) continue; + count++; + /* + ** Have to force ordered tag to avoid timeouts + */ + if (cp->cmd && cp->tlimit && cp->tlimit <= + thistime + NCR_TIMEOUT_INCREASE + SCSI_NCR_TIMEOUT_ALERT) { + lcb_p lp; + lp = np->target[cp->cmd->target].lp[cp->cmd->lun]; + if (lp && !lp->force_ordered_tag) { + lp->force_ordered_tag = 1; + } + } + /* + ** ncr_abort_command() cannot complete canceled + ** commands immediately. It sets tlimit to zero + ** and ask the script to skip the scsi process if + ** necessary. We have to complete this work here. + */ + + if (cp->tlimit) continue; + + switch (cp->host_status) { + + case HS_BUSY: + case HS_NEGOTIATE: + /* + ** still in start queue ? + */ + if (cp->phys.header.launch.l_paddr == + cpu_to_scr(NCB_SCRIPT_PHYS (np, skip))) + continue; + + /* fall through */ + case HS_DISCONNECT: + cp->host_status=HS_ABORTED; + }; + cp->tag = 0; + + /* + ** wakeup this ccb. + */ + ncr_complete (np, cp); + +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + if (!np->stalling) +#endif + OUTB (nc_istat, SIGP); + } + restore_flags(flags); + } + +#ifdef SCSI_NCR_BROKEN_INTR + if (INB(nc_istat) & (INTF|SIP|DIP)) { + + /* + ** Process pending interrupts. + */ + save_flags(flags); cli(); + if (DEBUG_FLAGS & DEBUG_TINY) printf ("{"); + ncr_exception (np); + if (DEBUG_FLAGS & DEBUG_TINY) printf ("}"); + restore_flags(flags); + } +#endif /* SCSI_NCR_BROKEN_INTR */ +} + +/*========================================================== +** +** log message for real hard errors +** +** "ncr0 targ 0?: ERROR (ds:si) (so-si-sd) (sxfer/scntl3) @ name (dsp:dbc)." +** " reg: r0 r1 r2 r3 r4 r5 r6 ..... rf." +** +** exception register: +** ds: dstat +** si: sist +** +** SCSI bus lines: +** so: control lines as driver by NCR. +** si: control lines as seen by NCR. +** sd: scsi data lines as seen by NCR. +** +** wide/fastmode: +** sxfer: (see the manual) +** scntl3: (see the manual) +** +** current script command: +** dsp: script adress (relative to start of script). +** dbc: first word of script command. +** +** First 16 register of the chip: +** r0..rf +** +**========================================================== +*/ + +static void ncr_log_hard_error(ncb_p np, u_short sist, u_char dstat) +{ + u_int32 dsp; + int script_ofs; + int script_size; + char *script_name; + u_char *script_base; + int i; + + dsp = INL (nc_dsp); + + if (dsp > np->p_script && dsp <= np->p_script + sizeof(struct script)) { + script_ofs = dsp - np->p_script; + script_size = sizeof(struct script); + script_base = (u_char *) np->script; + script_name = "script"; + } + else if (np->p_scripth < dsp && + dsp <= np->p_scripth + sizeof(struct scripth)) { + script_ofs = dsp - np->p_scripth; + script_size = sizeof(struct scripth); + script_base = (u_char *) np->scripth; + script_name = "scripth"; + } else { + script_ofs = dsp; + script_size = 0; + script_base = 0; + script_name = "mem"; + } + + printf ("%s:%d: ERROR (%x:%x) (%x-%x-%x) (%x/%x) @ (%s %x:%08x).\n", + ncr_name (np), (unsigned)INB (nc_ctest0)&0x0f, dstat, sist, + (unsigned)INB (nc_socl), (unsigned)INB (nc_sbcl), (unsigned)INB (nc_sbdl), + (unsigned)INB (nc_sxfer),(unsigned)INB (nc_scntl3), script_name, script_ofs, + (unsigned)INL (nc_dbc)); + + if (((script_ofs & 3) == 0) && + (unsigned)script_ofs < script_size) { + printf ("%s: script cmd = %08x\n", ncr_name(np), + (int) *(ncrcmd *)(script_base + script_ofs)); + } + + printf ("%s: regdump:", ncr_name(np)); + for (i=0; i<16;i++) + printf (" %02x", (unsigned)INB_OFF(i)); + printf (".\n"); +} + +/*============================================================ +** +** ncr chip exception handler. +** +**============================================================ +** +** In normal cases, interrupt conditions occur one at a +** time. The ncr is able to stack in some extra registers +** other interrupts that will occurs after the first one. +** But severall interrupts may occur at the same time. +** +** We probably should only try to deal with the normal +** case, but it seems that multiple interrupts occur in +** some cases that are not abnormal at all. +** +** The most frequent interrupt condition is Phase Mismatch. +** We should want to service this interrupt quickly. +** A SCSI parity error may be delivered at the same time. +** The SIR interrupt is not very frequent in this driver, +** since the INTFLY is likely used for command completion +** signaling. +** The Selection Timeout interrupt may be triggered with +** IID and/or UDC. +** The SBMC interrupt (SCSI Bus Mode Change) may probably +** occur at any time. +** +** This handler try to deal as cleverly as possible with all +** the above. +** +**============================================================ +*/ + +void ncr_exception (ncb_p np) +{ + u_char istat, dstat; + u_short sist; + int i; + + /* + ** interrupt on the fly ? + ** Since the global header may be copied back to a CCB + ** using a posted PCI memory write, the last operation on + ** the istat register is a READ in order to flush posted + ** PCI commands (Btw, the 'do' loop is probably useless). + */ + istat = INB (nc_istat); + if (istat & INTF) { + do { + OUTB (nc_istat, (istat & SIGP) | INTF); + istat = INB (nc_istat); + } while (istat & INTF); + if (DEBUG_FLAGS & DEBUG_TINY) printf ("F "); + np->profile.num_fly++; + ncr_wakeup (np, 0); + }; + + if (!(istat & (SIP|DIP))) + return; + + np->profile.num_int++; + + if (istat & CABRT) + OUTB (nc_istat, CABRT); + + /* + ** Steinbach's Guideline for Systems Programming: + ** Never test for an error condition you don't know how to handle. + */ + + sist = (istat & SIP) ? INW (nc_sist) : 0; + dstat = (istat & DIP) ? INB (nc_dstat) : 0; + + if (DEBUG_FLAGS & DEBUG_TINY) + printf ("<%d|%x:%x|%x:%x>", + (int)INB(nc_scr0), + dstat,sist, + (unsigned)INL(nc_dsp), + (unsigned)INL(nc_dbc)); + + /*======================================================== + ** First, interrupts we want to service cleanly. + ** + ** Phase mismatch is the most frequent interrupt, and + ** so we have to service it as quickly and as cleanly + ** as possible. + ** Programmed interrupts are rarely used in this driver, + ** but we must handle them cleanly anyway. + ** We try to deal with PAR and SBMC combined with + ** some other interrupt(s). + **========================================================= + */ + + if (!(sist & (STO|GEN|HTH|SGE|UDC|RST)) && + !(dstat & (MDPE|BF|ABRT|IID))) { + if ((sist & SBMC) && ncr_int_sbmc (np)) + return; + if ((sist & PAR) && ncr_int_par (np)) + return; + if (sist & MA) { + ncr_int_ma (np); + return; + } + if (dstat & SIR) { + ncr_int_sir (np); + return; + } + /* + ** DEL 397 - 53C875 Rev 3 - Part Number 609-0392410 - ITEM 2. + */ + if (!(sist & (SBMC|PAR)) && !(dstat & SSI)) { + printf( "%s: unknown interrupt(s) ignored, " + "ISTAT=%x DSTAT=%x SIST=%x\n", + ncr_name(np), istat, dstat, sist); + return; + } + + OUTONB (nc_dcntl, (STD|NOCOM)); + return; + }; + + /*======================================================== + ** Now, interrupts that need some fixing up. + ** Order and multiple interrupts is so less important. + ** + ** If SRST has been asserted, we just reset the chip. + ** + ** Selection is intirely handled by the chip. If the + ** chip says STO, we trust it. Seems some other + ** interrupts may occur at the same time (UDC, IID), so + ** we ignore them. In any case we do enough fix-up + ** in the service routine. + ** We just exclude some fatal dma errors. + **========================================================= + */ + + if (sist & RST) { + ncr_init (np, 1, bootverbose ? "scsi reset" : NULL, HS_RESET); + return; + }; + + if ((sist & STO) && + !(dstat & (MDPE|BF|ABRT))) { + /* + ** DEL 397 - 53C875 Rev 3 - Part Number 609-0392410 - ITEM 1. + */ + OUTONB (nc_ctest3, CLF); + + ncr_int_sto (np); + return; + }; + + /*========================================================= + ** Now, interrupts we are not able to recover cleanly. + ** (At least for the moment). + ** + ** Do the register dump. + ** Log message for real hard errors. + ** Clear all fifos. + ** For MDPE, BF, ABORT, IID, SGE and HTH we reset the + ** BUS and the chip. + ** We are more soft for UDC. + **========================================================= + */ + if (jiffies - np->regtime > 10*HZ) { + np->regtime = jiffies; + for (i = 0; i<sizeof(np->regdump); i++) + ((char*)&np->regdump)[i] = INB_OFF(i); + np->regdump.nc_dstat = dstat; + np->regdump.nc_sist = sist; + }; + + ncr_log_hard_error(np, sist, dstat); + + printf ("%s: have to clear fifos.\n", ncr_name (np)); + OUTB (nc_stest3, TE|CSF); + OUTONB (nc_ctest3, CLF); + + if ((sist & (SGE)) || + (dstat & (MDPE|BF|ABORT|IID))) { + ncr_start_reset(np, driver_setup.settle_delay); + return; + }; + + if (sist & HTH) { + printf ("%s: handshake timeout\n", ncr_name(np)); + ncr_start_reset(np, driver_setup.settle_delay); + return; + }; + + if (sist & UDC) { + printf ("%s: unexpected disconnect\n", ncr_name(np)); + if (INB (nc_scr1) != 0xff) { + OUTB (nc_scr1, HS_UNEXPECTED); + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, cleanup)); + }; + ncr_start_reset(np, driver_setup.settle_delay); + return; + }; + + /*========================================================= + ** We just miss the cause of the interrupt. :( + ** Print a message. The timeout will do the real work. + **========================================================= + */ + printf ("%s: unknown interrupt\n", ncr_name(np)); +} + +/*========================================================== +** +** ncr chip exception handler for selection timeout +** +**========================================================== +** +** There seems to be a bug in the 53c810. +** Although a STO-Interrupt is pending, +** it continues executing script commands. +** But it will fail and interrupt (IID) on +** the next instruction where it's looking +** for a valid phase. +** +**---------------------------------------------------------- +*/ + +void ncr_int_sto (ncb_p np) +{ + u_long dsa, scratcha, diff; + ccb_p cp; + if (DEBUG_FLAGS & DEBUG_TINY) printf ("T"); + + /* + ** look for ccb and set the status. + */ + + dsa = INL (nc_dsa); + cp = np->ccb; + while (cp && (CCB_PHYS (cp, phys) != dsa)) + cp = cp->link_ccb; + + if (cp) { + cp-> host_status = HS_SEL_TIMEOUT; + ncr_complete (np, cp); + }; + + /* + ** repair start queue + */ + + scratcha = INL (nc_scratcha); + diff = scratcha - NCB_SCRIPTH_PHYS (np, tryloop); + +/* assert ((diff <= MAX_START * 20) && !(diff % 20));*/ + + if ((diff <= MAX_START * 20) && !(diff % 20)) { + np->script->startpos[0] = cpu_to_scr(scratcha); + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, start)); + return; + }; + ncr_init (np, 1, "selection timeout", HS_FAIL); + np->disc = 1; +} + +/*========================================================== +** +** ncr chip exception handler for SCSI bus mode change +** +**========================================================== +** +** spi2-r12 11.2.3 says a transceiver mode change must +** generate a reset event and a device that detects a reset +** event shall initiate a hard reset. It says also that a +** device that detects a mode change shall set data transfer +** mode to eight bit asynchronous, etc... +** So, just resetting should be enough. +** +** +**---------------------------------------------------------- +*/ + +static int ncr_int_sbmc (ncb_p np) +{ + u_char scsi_mode = INB (nc_stest4) & SMODE; + + printf("%s: SCSI bus mode change from %x to %x.\n", + ncr_name(np), np->scsi_mode, scsi_mode); + + np->scsi_mode = scsi_mode; + + /* + ** Suspend command processing for 1 second and + ** reinitialize all except the chip. + */ + np->settle_time = jiffies + HZ; + ncr_init (np, 0, bootverbose ? "scsi mode change" : NULL, HS_RESET); + + return 1; +} + +/*========================================================== +** +** ncr chip exception handler for SCSI parity error. +** +**========================================================== +** +** SCSI parity errors are handled by the SCSI script. +** So, we just print some message. +** +**---------------------------------------------------------- +*/ + +static int ncr_int_par (ncb_p np) +{ + printf("%s: SCSI parity error detected\n", ncr_name(np)); + return 0; +} + +/*========================================================== +** +** +** ncr chip exception handler for phase errors. +** +** +**========================================================== +** +** We have to construct a new transfer descriptor, +** to transfer the rest of the current block. +** +**---------------------------------------------------------- +*/ + +static void ncr_int_ma (ncb_p np) +{ + u_int32 dbc; + u_int32 rest; + u_int32 dsp; + u_int32 dsa; + u_int32 nxtdsp; + u_int32 *vdsp; + u_int32 oadr, olen; + u_int32 *tblp; + ncrcmd *newcmd; + u_char cmd, sbcl; + ccb_p cp; + + dsp = INL (nc_dsp); + dbc = INL (nc_dbc); + sbcl = INB (nc_sbcl); + + cmd = dbc >> 24; + rest = dbc & 0xffffff; + + /* + ** Take into account dma fifo and various buffers and latches, + ** only if the interrupted phase is an OUTPUT phase. + */ + + if ((cmd & 1) == 0) { + u_char ctest5, ss0, ss2; + u_short delta; + + ctest5 = (np->rv_ctest5 & DFS) ? INB (nc_ctest5) : 0; + if (ctest5 & DFS) + delta=(((ctest5 << 8) | (INB (nc_dfifo) & 0xff)) - rest) & 0x3ff; + else + delta=(INB (nc_dfifo) - rest) & 0x7f; + + /* + ** The data in the dma fifo has not been transfered to + ** the target -> add the amount to the rest + ** and clear the data. + ** Check the sstat2 register in case of wide transfer. + */ + + rest += delta; + ss0 = INB (nc_sstat0); + if (ss0 & OLF) rest++; + if (ss0 & ORF) rest++; + if (INB(nc_scntl3) & EWS) { + ss2 = INB (nc_sstat2); + if (ss2 & OLF1) rest++; + if (ss2 & ORF1) rest++; + }; + + OUTONB (nc_ctest3, CLF ); /* clear dma fifo */ + OUTB (nc_stest3, TE|CSF); /* clear scsi fifo */ + + if (DEBUG_FLAGS & (DEBUG_TINY|DEBUG_PHASE)) + printf ("P%x%x RL=%d D=%d SS0=%x ", cmd&7, sbcl&7, + (unsigned) rest, (unsigned) delta, ss0); + + } else { + if (DEBUG_FLAGS & (DEBUG_TINY|DEBUG_PHASE)) + printf ("P%x%x RL=%d ", cmd&7, sbcl&7, rest); + if ((cmd & 7) != 1) { + OUTONB (nc_ctest3, CLF ); + OUTB (nc_stest3, TE|CSF); + } + } + + /* + ** locate matching cp + */ + dsa = INL (nc_dsa); + cp = np->ccb; + while (cp && (CCB_PHYS (cp, phys) != dsa)) + cp = cp->link_ccb; + + if (!cp) { + printf ("%s: SCSI phase error fixup: CCB already dequeued (0x%08lx)\n", + ncr_name (np), (u_long) np->header.cp); + return; + } + if (cp != np->header.cp) { + printf ("%s: SCSI phase error fixup: CCB address mismatch (0x%08lx != 0x%08lx)\n", + ncr_name (np), (u_long) cp, (u_long) np->header.cp); +/* return;*/ + } + + /* + ** find the interrupted script command, + ** and the address at which to continue. + */ + + if (dsp == vtophys (&cp->patch[2])) { + vdsp = &cp->patch[0]; + nxtdsp = vdsp[3]; + } else if (dsp == vtophys (&cp->patch[6])) { + vdsp = &cp->patch[4]; + nxtdsp = vdsp[3]; + } else if (dsp > np->p_script && dsp <= np->p_script + sizeof(struct script)) { + vdsp = (u_int32 *) ((char*)np->script - np->p_script + dsp -8); + nxtdsp = dsp; + } else { + vdsp = (u_int32 *) ((char*)np->scripth - np->p_scripth + dsp -8); + nxtdsp = dsp; + }; + + /* + ** log the information + */ + + if (DEBUG_FLAGS & DEBUG_PHASE) { + printf ("\nCP=%p CP2=%p DSP=%x NXT=%x VDSP=%p CMD=%x ", + cp, np->header.cp, + (unsigned)dsp, + (unsigned)nxtdsp, vdsp, cmd); + }; + + /* + ** get old startaddress and old length. + */ + + oadr = scr_to_cpu(vdsp[1]); + + if (cmd & 0x10) { /* Table indirect */ + tblp = (u_int32 *) ((char*) &cp->phys + oadr); + olen = scr_to_cpu(tblp[0]); + oadr = scr_to_cpu(tblp[1]); + } else { + tblp = (u_int32 *) 0; + olen = scr_to_cpu(vdsp[0]) & 0xffffff; + }; + + if (DEBUG_FLAGS & DEBUG_PHASE) { + printf ("OCMD=%x\nTBLP=%p OLEN=%x OADR=%x\n", + (unsigned) (scr_to_cpu(vdsp[0]) >> 24), + tblp, + (unsigned) olen, + (unsigned) oadr); + }; + + /* + ** check cmd against assumed interrupted script command. + */ + + if (cmd != (scr_to_cpu(vdsp[0]) >> 24)) { + PRINT_ADDR(cp->cmd); + printf ("internal error: cmd=%02x != %02x=(vdsp[0] >> 24)\n", + (unsigned)cmd, (unsigned)scr_to_cpu(vdsp[0]) >> 24); + + return; + } + +#ifdef SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT + if ((cmd & 7) == 1 && np->assert_atn) { + np->assert_atn = 0; + OUTONB(nc_socl, CATN); + } +#endif + + /* + ** if old phase not dataphase, leave here. + */ + + if (cmd & 0x06) { + PRINT_ADDR(cp->cmd); + printf ("phase change %x-%x %d@%08x resid=%d.\n", + cmd&7, sbcl&7, (unsigned)olen, + (unsigned)oadr, (unsigned)rest); + + OUTONB (nc_dcntl, (STD|NOCOM)); + return; + }; + + /* + ** choose the correct patch area. + ** if savep points to one, choose the other. + */ + + newcmd = cp->patch; + if (cp->phys.header.savep == cpu_to_scr(vtophys (newcmd))) newcmd+=4; + + /* + ** fillin the commands + */ + + newcmd[0] = cpu_to_scr(((cmd & 0x0f) << 24) | rest); + newcmd[1] = cpu_to_scr(oadr + olen - rest); + newcmd[2] = cpu_to_scr(SCR_JUMP); + newcmd[3] = cpu_to_scr(nxtdsp); + + if (DEBUG_FLAGS & DEBUG_PHASE) { + PRINT_ADDR(cp->cmd); + printf ("newcmd[%d] %x %x %x %x.\n", + (int) (newcmd - cp->patch), + (unsigned)scr_to_cpu(newcmd[0]), + (unsigned)scr_to_cpu(newcmd[1]), + (unsigned)scr_to_cpu(newcmd[2]), + (unsigned)scr_to_cpu(newcmd[3])); + } + /* + ** fake the return address (to the patch). + ** and restart script processor at dispatcher. + */ + np->profile.num_break++; + OUTL (nc_temp, vtophys (newcmd)); + if ((cmd & 7) == 0) + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, dispatch)); + else + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, checkatn)); +} + +/*========================================================== +** +** +** ncr chip exception handler for programmed interrupts. +** +** +**========================================================== +*/ + +static int ncr_show_msg (u_char * msg) +{ + u_char i; + printf ("%x",*msg); + if (*msg==M_EXTENDED) { + for (i=1;i<8;i++) { + if (i-1>msg[1]) break; + printf ("-%x",msg[i]); + }; + return (i+1); + } else if ((*msg & 0xf0) == 0x20) { + printf ("-%x",msg[1]); + return (2); + }; + return (1); +} + +void ncr_int_sir (ncb_p np) +{ + u_char scntl3; + u_char chg, ofs, per, fak, wide; + u_char num = INB (nc_dsps); + ccb_p cp=0; + u_long dsa; + u_char target = INB (nc_ctest0) & 0x0f; + tcb_p tp = &np->target[target]; + int i; + if (DEBUG_FLAGS & DEBUG_TINY) printf ("I#%d", num); + + switch (num) { + case SIR_SENSE_RESTART: + case SIR_STALL_RESTART: + break; + case SIR_STALL_QUEUE: /* Ignore, just restart the script */ + goto out; + + default: + /* + ** lookup the ccb + */ + dsa = INL (nc_dsa); + cp = np->ccb; + while (cp && (CCB_PHYS (cp, phys) != dsa)) + cp = cp->link_ccb; + + assert (cp); + if (!cp) + goto out; + assert (cp == np->header.cp); + if (cp != np->header.cp) + goto out; + } + + switch (num) { + u_long endp; + case SIR_DATA_IO_IS_OUT: + case SIR_DATA_IO_IS_IN: +/* +** We did not guess the direction of transfer. We have to wait for +** actual data direction driven by the target before setting +** pointers. We must patch the global header too. +*/ + if (num == SIR_DATA_IO_IS_OUT) { + endp = NCB_SCRIPTH_PHYS (np, data_out) + MAX_SCATTER*16; + cp->phys.header.goalp = cpu_to_scr(endp + 8); + cp->phys.header.savep = + cpu_to_scr(endp - cp->segments*16); + } else { + endp = NCB_SCRIPT_PHYS (np, data_in) + MAX_SCATTER*16; + cp->phys.header.goalp = cpu_to_scr(endp + 8); + cp->phys.header.savep = + cpu_to_scr(endp - cp->segments*16); + } + + cp->phys.header.lastp = cp->phys.header.savep; + np->header.savep = cp->phys.header.savep; + np->header.goalp = cp->phys.header.goalp; + np->header.lastp = cp->phys.header.lastp; + + OUTL (nc_temp, scr_to_cpu(np->header.savep)); + OUTL (nc_dsp, scr_to_cpu(np->header.savep)); + return; + /* break; */ + +/*-------------------------------------------------------------------- +** +** Processing of interrupted getcc selects +** +**-------------------------------------------------------------------- +*/ + + case SIR_SENSE_RESTART: + /*------------------------------------------ + ** Script processor is idle. + ** Look for interrupted "check cond" + **------------------------------------------ + */ + + if (DEBUG_FLAGS & DEBUG_RESTART) + printf ("%s: int#%d",ncr_name (np),num); + cp = (ccb_p) 0; + for (i=0; i<MAX_TARGET; i++) { + if (DEBUG_FLAGS & DEBUG_RESTART) printf (" t%d", i); + tp = &np->target[i]; + if (DEBUG_FLAGS & DEBUG_RESTART) printf ("+"); + cp = tp->hold_cp; + if (!cp) continue; + if (DEBUG_FLAGS & DEBUG_RESTART) printf ("+"); + if ((cp->host_status==HS_BUSY) && + (cp->scsi_status==S_CHECK_COND)) + break; + if (DEBUG_FLAGS & DEBUG_RESTART) printf ("- (remove)"); + tp->hold_cp = cp = (ccb_p) 0; + }; + + if (cp) { + if (DEBUG_FLAGS & DEBUG_RESTART) + printf ("+ restart job ..\n"); + OUTL (nc_dsa, CCB_PHYS (cp, phys)); + OUTL (nc_dsp, NCB_SCRIPTH_PHYS (np, getcc)); + return; + }; + + /* + ** no job, resume normal processing + */ + if (DEBUG_FLAGS & DEBUG_RESTART) printf (" -- remove trap\n"); + np->script->start0[0] = cpu_to_scr(SCR_INT ^ IFFALSE (0)); + break; + + case SIR_SENSE_FAILED: + /*------------------------------------------- + ** While trying to select for + ** getting the condition code, + ** a target reselected us. + **------------------------------------------- + */ + if (DEBUG_FLAGS & DEBUG_RESTART) { + PRINT_ADDR(cp->cmd); + printf ("in getcc reselect by t%d.\n", + (int)INB(nc_ssid) & 0x0f); + } + + /* + ** Mark this job + */ + cp->host_status = HS_BUSY; + cp->scsi_status = S_CHECK_COND; + np->target[cp->cmd->target].hold_cp = cp; + + /* + ** And patch code to restart it. + */ + np->script->start0[0] = cpu_to_scr(SCR_INT); + break; + +/*----------------------------------------------------------------------------- +** +** Was Sie schon immer ueber transfermode negotiation wissen wollten ... +** +** We try to negotiate sync and wide transfer only after +** a successfull inquire command. We look at byte 7 of the +** inquire data to determine the capabilities of the target. +** +** When we try to negotiate, we append the negotiation message +** to the identify and (maybe) simple tag message. +** The host status field is set to HS_NEGOTIATE to mark this +** situation. +** +** If the target doesn't answer this message immidiately +** (as required by the standard), the SIR_NEGO_FAIL interrupt +** will be raised eventually. +** The handler removes the HS_NEGOTIATE status, and sets the +** negotiated value to the default (async / nowide). +** +** If we receive a matching answer immediately, we check it +** for validity, and set the values. +** +** If we receive a Reject message immediately, we assume the +** negotiation has failed, and fall back to standard values. +** +** If we receive a negotiation message while not in HS_NEGOTIATE +** state, it's a target initiated negotiation. We prepare a +** (hopefully) valid answer, set our parameters, and send back +** this answer to the target. +** +** If the target doesn't fetch the answer (no message out phase), +** we assume the negotiation has failed, and fall back to default +** settings. +** +** When we set the values, we adjust them in all ccbs belonging +** to this target, in the controller's register, and in the "phys" +** field of the controller's struct ncb. +** +** Possible cases: hs sir msg_in value send goto +** We try to negotiate: +** -> target doesnt't msgin NEG FAIL noop defa. - dispatch +** -> target rejected our msg NEG FAIL reject defa. - dispatch +** -> target answered (ok) NEG SYNC sdtr set - clrack +** -> target answered (!ok) NEG SYNC sdtr defa. REJ--->msg_bad +** -> target answered (ok) NEG WIDE wdtr set - clrack +** -> target answered (!ok) NEG WIDE wdtr defa. REJ--->msg_bad +** -> any other msgin NEG FAIL noop defa. - dispatch +** +** Target tries to negotiate: +** -> incoming message --- SYNC sdtr set SDTR - +** -> incoming message --- WIDE wdtr set WDTR - +** We sent our answer: +** -> target doesn't msgout --- PROTO ? defa. - dispatch +** +**----------------------------------------------------------------------------- +*/ + + case SIR_NEGO_FAILED: + /*------------------------------------------------------- + ** + ** Negotiation failed. + ** Target doesn't send an answer message, + ** or target rejected our message. + ** + ** Remove negotiation request. + ** + **------------------------------------------------------- + */ + OUTB (HS_PRT, HS_BUSY); + + /* fall through */ + + case SIR_NEGO_PROTO: + /*------------------------------------------------------- + ** + ** Negotiation failed. + ** Target doesn't fetch the answer message. + ** + **------------------------------------------------------- + */ + + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("negotiation failed sir=%x status=%x.\n", + num, cp->nego_status); + }; + + /* + ** any error in negotiation: + ** fall back to default mode. + */ + switch (cp->nego_status) { + + case NS_SYNC: + ncr_setsync (np, cp, 0, 0xe0); + break; + + case NS_WIDE: + ncr_setwide (np, cp, 0, 0); + break; + + }; + np->msgin [0] = M_NOOP; + np->msgout[0] = M_NOOP; + cp->nego_status = 0; + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, dispatch)); + break; + + case SIR_NEGO_SYNC: + /* + ** Synchronous request message received. + */ + + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("sync msgin: "); + (void) ncr_show_msg (np->msgin); + printf (".\n"); + }; + + /* + ** get requested values. + */ + + chg = 0; + per = np->msgin[3]; + ofs = np->msgin[4]; + if (ofs==0) per=255; + + /* + ** if target sends SDTR message, + ** it CAN transfer synch. + */ + + if (ofs) + tp->inqdata[7] |= INQ7_SYNC; + + /* + ** check values against driver limits. + */ + + if (per < np->minsync) + {chg = 1; per = np->minsync;} + if (per < tp->minsync) + {chg = 1; per = tp->minsync;} + if (ofs > tp->maxoffs) + {chg = 1; ofs = tp->maxoffs;} + + /* + ** Check against controller limits. + */ + fak = 7; + scntl3 = 0; + if (ofs != 0) { + ncr_getsync(np, per, &fak, &scntl3); + if (fak > 7) { + chg = 1; + ofs = 0; + } + } + if (ofs == 0) { + fak = 7; + per = 0; + scntl3 = 0; + tp->minsync = 0; + } + + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("sync: per=%d scntl3=0x%x ofs=%d fak=%d chg=%d.\n", + per, scntl3, ofs, fak, chg); + } + + if (INB (HS_PRT) == HS_NEGOTIATE) { + OUTB (HS_PRT, HS_BUSY); + switch (cp->nego_status) { + + case NS_SYNC: + /* + ** This was an answer message + */ + if (chg) { + /* + ** Answer wasn't acceptable. + */ + ncr_setsync (np, cp, 0, 0xe0); + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, msg_bad)); + } else { + /* + ** Answer is ok. + */ + ncr_setsync (np, cp, scntl3, (fak<<5)|ofs); + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, clrack)); + }; + return; + + case NS_WIDE: + ncr_setwide (np, cp, 0, 0); + break; + }; + }; + + /* + ** It was a request. + ** Check against the table of target capabilities. + ** If target not capable force M_REJECT and asynchronous. + */ + if (np->unit < SCSI_NCR_MAX_HOST) { + tp->inqdata[7] &= + (target_capabilities[np->unit].and_map[target]); + if (!(tp->inqdata[7] & INQ7_SYNC)) { + ofs = 0; + fak = 7; + } + } + + /* + ** It was a request. Set value and + ** prepare an answer message + */ + + ncr_setsync (np, cp, scntl3, (fak<<5)|ofs); + + np->msgout[0] = M_EXTENDED; + np->msgout[1] = 3; + np->msgout[2] = M_X_SYNC_REQ; + np->msgout[3] = per; + np->msgout[4] = ofs; + + cp->nego_status = NS_SYNC; + + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("sync msgout: "); + (void) ncr_show_msg (np->msgout); + printf (".\n"); + } + + if (!ofs) { + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, msg_bad)); + return; + } + np->msgin [0] = M_NOOP; + + break; + + case SIR_NEGO_WIDE: + /* + ** Wide request message received. + */ + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("wide msgin: "); + (void) ncr_show_msg (np->msgin); + printf (".\n"); + }; + + /* + ** get requested values. + */ + + chg = 0; + wide = np->msgin[3]; + + /* + ** if target sends WDTR message, + ** it CAN transfer wide. + */ + + if (wide) + tp->inqdata[7] |= INQ7_WIDE16; + + /* + ** check values against driver limits. + */ + + if (wide > tp->usrwide) + {chg = 1; wide = tp->usrwide;} + + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("wide: wide=%d chg=%d.\n", wide, chg); + } + + if (INB (HS_PRT) == HS_NEGOTIATE) { + OUTB (HS_PRT, HS_BUSY); + switch (cp->nego_status) { + + case NS_WIDE: + /* + ** This was an answer message + */ + if (chg) { + /* + ** Answer wasn't acceptable. + */ + ncr_setwide (np, cp, 0, 1); + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, msg_bad)); + } else { + /* + ** Answer is ok. + */ + ncr_setwide (np, cp, wide, 1); + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, clrack)); + }; + return; + + case NS_SYNC: + ncr_setsync (np, cp, 0, 0xe0); + break; + }; + }; + + /* + ** It was a request, set value and + ** prepare an answer message + */ + + ncr_setwide (np, cp, wide, 1); + + np->msgout[0] = M_EXTENDED; + np->msgout[1] = 2; + np->msgout[2] = M_X_WIDE_REQ; + np->msgout[3] = wide; + + np->msgin [0] = M_NOOP; + + cp->nego_status = NS_WIDE; + + if (DEBUG_FLAGS & DEBUG_NEGO) { + PRINT_ADDR(cp->cmd); + printf ("wide msgout: "); + (void) ncr_show_msg (np->msgin); + printf (".\n"); + } + break; + +/*-------------------------------------------------------------------- +** +** Processing of special messages +** +**-------------------------------------------------------------------- +*/ + + case SIR_REJECT_RECEIVED: + /*----------------------------------------------- + ** + ** We received a M_REJECT message. + ** + **----------------------------------------------- + */ + + PRINT_ADDR(cp->cmd); + printf ("M_REJECT received (%x:%x).\n", + (unsigned)scr_to_cpu(np->lastmsg), np->msgout[0]); + break; + + case SIR_REJECT_SENT: + /*----------------------------------------------- + ** + ** We received an unknown message + ** + **----------------------------------------------- + */ + + PRINT_ADDR(cp->cmd); + printf ("M_REJECT sent for "); + (void) ncr_show_msg (np->msgin); + printf (".\n"); + break; + +/*-------------------------------------------------------------------- +** +** Processing of special messages +** +**-------------------------------------------------------------------- +*/ + + case SIR_IGN_RESIDUE: + /*----------------------------------------------- + ** + ** We received an IGNORE RESIDUE message, + ** which couldn't be handled by the script. + ** + **----------------------------------------------- + */ + + PRINT_ADDR(cp->cmd); + printf ("M_IGN_RESIDUE received, but not yet implemented.\n"); + break; + + case SIR_MISSING_SAVE: + /*----------------------------------------------- + ** + ** We received an DISCONNECT message, + ** but the datapointer wasn't saved before. + ** + **----------------------------------------------- + */ + + PRINT_ADDR(cp->cmd); + printf ("M_DISCONNECT received, but datapointer not saved: " + "data=%x save=%x goal=%x.\n", + (unsigned) INL (nc_temp), + (unsigned) scr_to_cpu(np->header.savep), + (unsigned) scr_to_cpu(np->header.goalp)); + break; + +#if 0 /* This stuff does not work */ +/*-------------------------------------------------------------------- +** +** Processing of a "S_QUEUE_FULL" status. +** +** The current command has been rejected, +** because there are too many in the command queue. +** We have started too many commands for that target. +** +** If possible, reinsert at head of queue. +** Stall queue until there are no disconnected jobs +** (ncr is REALLY idle). Then restart processing. +** +** We should restart the current job after the controller +** has become idle. But this is not yet implemented. +** +**-------------------------------------------------------------------- +*/ + case SIR_STALL_QUEUE: + /*----------------------------------------------- + ** + ** Stall the start queue. + ** + **----------------------------------------------- + */ + PRINT_ADDR(cp->cmd); + printf ("queue full.\n"); + + np->script->start1[0] = cpu_to_scr(SCR_INT); + + /* + ** Try to disable tagged transfers. + */ + ncr_setmaxtags (np, &np->target[target], 0); + + /* + ** @QUEUE@ + ** + ** Should update the launch field of the + ** current job to be able to restart it. + ** Then prepend it to the start queue. + */ + + /* fall through */ + + case SIR_STALL_RESTART: + /*----------------------------------------------- + ** + ** Enable selecting again, + ** if NO disconnected jobs. + ** + **----------------------------------------------- + */ + /* + ** Look for a disconnected job. + */ + cp = np->ccb; + while (cp && cp->host_status != HS_DISCONNECT) + cp = cp->link_ccb; + + /* + ** if there is one, ... + */ + if (cp) { + /* + ** wait for reselection + */ + OUTL (nc_dsp, NCB_SCRIPT_PHYS (np, reselect)); + return; + }; + + /* + ** else remove the interrupt. + */ + + printf ("%s: queue empty.\n", ncr_name (np)); + np->script->start1[0] = cpu_to_scr(SCR_INT ^ IFFALSE (0)); + break; +#endif /* This stuff does not work */ + }; + +out: + OUTONB (nc_dcntl, (STD|NOCOM)); +} + +/*========================================================== +** +** +** Aquire a control block +** +** +**========================================================== +*/ + +static ccb_p ncr_get_ccb + (ncb_p np, u_long target, u_long lun) +{ + lcb_p lp; + ccb_p cp = (ccb_p) 0; + + /* + ** Lun structure available ? + */ + + lp = np->target[target].lp[lun]; + + if (lp && lp->opennings && (!lp->active || lp->active < lp->reqlink)) { + + cp = lp->next_ccb; + + /* + ** Look for free CCB + */ + + while (cp && cp->magic) cp = cp->next_ccb; + + /* + ** Increment active commands and decrement credit. + */ + + if (cp) { + ++lp->active; + --lp->opennings; + } + } + + /* + ** if nothing available, take the default. + ** DANGEROUS, because this ccb is not suitable for + ** reselection. + ** If lp->actccbs > 0 wait for a suitable ccb to be free. + */ + if ((!cp) && lp && lp->actccbs > 0) + return ((ccb_p) 0); + + if (!cp) cp = np->ccb; + + /* + ** Wait until available. + */ +#if 0 + while (cp->magic) { + if (flags & SCSI_NOSLEEP) break; + if (tsleep ((caddr_t)cp, PRIBIO|PCATCH, "ncr", 0)) + break; + }; +#endif + + if (cp->magic) + return ((ccb_p) 0); + + cp->magic = 1; + return (cp); +} + +/*========================================================== +** +** +** Release one control block +** +** +**========================================================== +*/ + +void ncr_free_ccb (ncb_p np, ccb_p cp, u_long target, u_long lun) +{ + lcb_p lp; + + /* + ** sanity + */ + + assert (cp != NULL); + + /* + ** Decrement active commands and increment credit. + */ + + lp = np->target[target].lp[lun]; + if (lp) { + --lp->active; + ++lp->opennings; + } + + cp -> host_status = HS_IDLE; + cp -> magic = 0; +#if 0 + if (cp == np->ccb) + wakeup ((caddr_t) cp); +#endif +} + +/*========================================================== +** +** +** Allocation of resources for Targets/Luns/Tags. +** +** +**========================================================== +*/ + +static void ncr_alloc_ccb (ncb_p np, u_long target, u_long lun) +{ + tcb_p tp; + lcb_p lp; + ccb_p cp; + + assert (np != NULL); + + if (target>=MAX_TARGET) return; + if (lun >=MAX_LUN ) return; + + tp=&np->target[target]; + + if (!tp->jump_tcb.l_cmd) { + /* + ** initialize it. + */ + tp->jump_tcb.l_cmd = + cpu_to_scr((SCR_JUMP^IFFALSE (DATA (0x80 + target)))); + tp->jump_tcb.l_paddr = np->jump_tcb.l_paddr; + + tp->getscr[0] = (np->features & FE_PFEN) ? + cpu_to_scr(SCR_COPY(1)):cpu_to_scr(SCR_COPY_F(1)); + tp->getscr[1] = cpu_to_scr(vtophys (&tp->sval)); + tp->getscr[2] = + cpu_to_scr(np->paddr + offsetof (struct ncr_reg, nc_sxfer)); + + tp->getscr[3] = (np->features & FE_PFEN) ? + cpu_to_scr(SCR_COPY(1)):cpu_to_scr(SCR_COPY_F(1)); + tp->getscr[4] = cpu_to_scr(vtophys (&tp->wval)); + tp->getscr[5] = + cpu_to_scr(np->paddr + offsetof (struct ncr_reg, nc_scntl3)); + + assert (( (offsetof(struct ncr_reg, nc_sxfer) ^ + offsetof(struct tcb , sval )) &3) == 0); + assert (( (offsetof(struct ncr_reg, nc_scntl3) ^ + offsetof(struct tcb , wval )) &3) == 0); + + tp->call_lun.l_cmd = cpu_to_scr(SCR_CALL); + tp->call_lun.l_paddr = + cpu_to_scr(NCB_SCRIPT_PHYS (np, resel_lun)); + + tp->jump_lcb.l_cmd = cpu_to_scr(SCR_JUMP); + tp->jump_lcb.l_paddr = cpu_to_scr(NCB_SCRIPTH_PHYS (np, abort)); + np->jump_tcb.l_paddr = cpu_to_scr(vtophys (&tp->jump_tcb)); + } + + /* + ** Logic unit control block + */ + lp = tp->lp[lun]; + if (!lp) { + /* + ** Allocate a lcb + */ + lp = (lcb_p) m_alloc (sizeof (struct lcb), LCB_ALIGN_SHIFT); + if (!lp) return; + + if (DEBUG_FLAGS & DEBUG_ALLOC) { + PRINT_LUN(np, target, lun); + printf ("new lcb @%p.\n", lp); + } + + /* + ** Initialize it + */ + bzero (lp, sizeof (*lp)); + lp->jump_lcb.l_cmd = + cpu_to_scr(SCR_JUMP ^ IFFALSE (DATA (lun))); + lp->jump_lcb.l_paddr = tp->jump_lcb.l_paddr; + + lp->call_tag.l_cmd = cpu_to_scr(SCR_CALL); + lp->call_tag.l_paddr = + cpu_to_scr(NCB_SCRIPT_PHYS (np, resel_tag)); + + lp->jump_ccb.l_cmd = cpu_to_scr(SCR_JUMP); + lp->jump_ccb.l_paddr = + cpu_to_scr(NCB_SCRIPTH_PHYS (np, aborttag)); + + lp->actlink = 1; + + lp->active = 1; + + /* + ** Chain into LUN list + */ + tp->jump_lcb.l_paddr = cpu_to_scr(vtophys (&lp->jump_lcb)); + tp->lp[lun] = lp; + + ncr_setmaxtags (np, tp, driver_setup.default_tags); + } + + /* + ** Allocate ccbs up to lp->reqccbs. + */ + + /* + ** Limit possible number of ccbs. + ** + ** If tagged command queueing is enabled, + ** can use more than one ccb. + */ + if (np->actccbs >= MAX_START-2) return; + if (lp->actccbs && (lp->actccbs >= lp->reqccbs)) + return; + + /* + ** Allocate a ccb + */ + cp = (ccb_p) m_alloc (sizeof (struct ccb), CCB_ALIGN_SHIFT); + if (!cp) + return; + + if (DEBUG_FLAGS & DEBUG_ALLOC) { + PRINT_LUN(np, target, lun); + printf ("new ccb @%p.\n", cp); + } + + /* + ** Count it + */ + lp->actccbs++; + np->actccbs++; + + /* + ** Initialize it + */ + bzero (cp, sizeof (*cp)); + + /* + ** Fill in physical addresses + */ + + cp->p_ccb = vtophys (cp); + + /* + ** Chain into reselect list + */ + cp->jump_ccb.l_cmd = cpu_to_scr(SCR_JUMP); + cp->jump_ccb.l_paddr = lp->jump_ccb.l_paddr; + lp->jump_ccb.l_paddr = cpu_to_scr(CCB_PHYS (cp, jump_ccb)); + cp->call_tmp.l_cmd = cpu_to_scr(SCR_CALL); + cp->call_tmp.l_paddr = cpu_to_scr(NCB_SCRIPT_PHYS (np, resel_tmp)); + + /* + ** Chain into wakeup list + */ + cp->link_ccb = np->ccb->link_ccb; + np->ccb->link_ccb = cp; + + /* + ** Chain into CCB list + */ + cp->next_ccb = lp->next_ccb; + lp->next_ccb = cp; +} + +/*========================================================== +** +** +** Announce the number of ccbs/tags to the scsi driver. +** +** +**========================================================== +*/ + +static void ncr_opennings (ncb_p np, lcb_p lp, Scsi_Cmnd * cmd) +{ + /* + ** want to reduce the number ... + */ + if (lp->actlink > lp->reqlink) { + + /* + ** Try to reduce the count. + ** We assume to run at splbio .. + */ + u_char diff = lp->actlink - lp->reqlink; + + if (!diff) return; + + if (diff > lp->opennings) + diff = lp->opennings; + + lp->opennings -= diff; + + lp->actlink -= diff; + if (DEBUG_FLAGS & DEBUG_TAGS) + printf ("%s: actlink: diff=%d, new=%d, req=%d\n", + ncr_name(np), diff, lp->actlink, lp->reqlink); + return; + }; + + /* + ** want to increase the number ? + */ + if (lp->reqlink > lp->actlink) { + u_char diff = lp->reqlink - lp->actlink; + + lp->opennings += diff; + + lp->actlink += diff; +#if 0 + wakeup ((caddr_t) xp->sc_link); +#endif + if (DEBUG_FLAGS & DEBUG_TAGS) + printf ("%s: actlink: diff=%d, new=%d, req=%d\n", + ncr_name(np), diff, lp->actlink, lp->reqlink); + }; +} + +/*========================================================== +** +** +** Build Scatter Gather Block +** +** +**========================================================== +** +** The transfer area may be scattered among +** several non adjacent physical pages. +** +** We may use MAX_SCATTER blocks. +** +**---------------------------------------------------------- +*/ + +/* +** We try to reduce the number of interrupts caused +** by unexpected phase changes due to disconnects. +** A typical harddisk may disconnect before ANY block. +** If we wanted to avoid unexpected phase changes at all +** we had to use a break point every 512 bytes. +** Of course the number of scatter/gather blocks is +** limited. +** Under Linux, the scatter/gatter blocks are provided by +** the generic driver. We just have to copy addresses and +** sizes to the data segment array. +*/ + +static int ncr_scatter(ccb_p cp, Scsi_Cmnd *cmd) +{ + struct scr_tblmove *data; + int segment = 0; + int use_sg = (int) cmd->use_sg; + +#if 0 + bzero (cp->phys.data, sizeof (cp->phys.data)); +#endif + data = cp->phys.data; + cp->data_len = 0; + + if (!use_sg) { + if (cmd->request_bufflen) { + data = &data[MAX_SCATTER - 1]; + data[0].addr = cpu_to_scr(vtophys(cmd->request_buffer)); + data[0].size = cpu_to_scr(cmd->request_bufflen); + cp->data_len = cmd->request_bufflen; + segment = 1; + } + } + else if (use_sg <= MAX_SCATTER) { + struct scatterlist *scatter = (struct scatterlist *)cmd->buffer; + + data = &data[MAX_SCATTER - use_sg]; + while (segment < use_sg) { + data[segment].addr = + cpu_to_scr(vtophys(scatter[segment].address)); + data[segment].size = + cpu_to_scr(scatter[segment].length); + cp->data_len += scatter[segment].length; + ++segment; + } + } + else { + return -1; + } + + return segment; +} + +/*========================================================== +** +** +** Test the pci bus snoop logic :-( +** +** Has to be called with interrupts disabled. +** +** +**========================================================== +*/ + +#ifndef NCR_IOMAPPED +__initfunc( +static int ncr_regtest (struct ncb* np) +) +{ + register volatile u_long data; + /* + ** ncr registers may NOT be cached. + ** write 0xffffffff to a read only register area, + ** and try to read it back. + */ + data = 0xffffffff; + OUTL_OFF(offsetof(struct ncr_reg, nc_dstat), data); + data = INL_OFF(offsetof(struct ncr_reg, nc_dstat)); +#if 1 + if (data == 0xffffffff) { +#else + if ((data & 0xe2f0fffd) != 0x02000080) { +#endif + printf ("CACHE TEST FAILED: reg dstat-sstat2 readback %x.\n", + (unsigned) data); + return (0x10); + }; + return (0); +} +#endif + +__initfunc( +static int ncr_snooptest (struct ncb* np) +) +{ + u_long ncr_rd, ncr_wr, ncr_bk, host_rd, host_wr, pc, err=0; + int i; +#ifndef NCR_IOMAPPED + if (np->reg) { + err |= ncr_regtest (np); + if (err) return (err); + } +#endif + /* + ** init + */ + pc = NCB_SCRIPTH_PHYS (np, snooptest); + host_wr = 1; + ncr_wr = 2; + /* + ** Set memory and register. + */ + np->ncr_cache = cpu_to_scr(host_wr); + OUTL (nc_temp, ncr_wr); + /* + ** Start script (exchange values) + */ + OUTL (nc_dsp, pc); + /* + ** Wait 'til done (with timeout) + */ + for (i=0; i<NCR_SNOOP_TIMEOUT; i++) + if (INB(nc_istat) & (INTF|SIP|DIP)) + break; + /* + ** Save termination position. + */ + pc = INL (nc_dsp); + /* + ** Read memory and register. + */ + host_rd = scr_to_cpu(np->ncr_cache); + ncr_rd = INL (nc_scratcha); + ncr_bk = INL (nc_temp); + /* + ** Reset ncr chip + */ + OUTB (nc_istat, SRST); + DELAY (1000); + OUTB (nc_istat, 0 ); + /* + ** check for timeout + */ + if (i>=NCR_SNOOP_TIMEOUT) { + printf ("CACHE TEST FAILED: timeout.\n"); + return (0x20); + }; + /* + ** Check termination position. + */ + if (pc != NCB_SCRIPTH_PHYS (np, snoopend)+8) { + printf ("CACHE TEST FAILED: script execution failed.\n"); + printf ("start=%08lx, pc=%08lx, end=%08lx\n", + (u_long) NCB_SCRIPTH_PHYS (np, snooptest), pc, + (u_long) NCB_SCRIPTH_PHYS (np, snoopend) +8); + return (0x40); + }; + /* + ** Show results. + */ + if (host_wr != ncr_rd) { + printf ("CACHE TEST FAILED: host wrote %d, ncr read %d.\n", + (int) host_wr, (int) ncr_rd); + err |= 1; + }; + if (host_rd != ncr_wr) { + printf ("CACHE TEST FAILED: ncr wrote %d, host read %d.\n", + (int) ncr_wr, (int) host_rd); + err |= 2; + }; + if (ncr_bk != ncr_wr) { + printf ("CACHE TEST FAILED: ncr wrote %d, read back %d.\n", + (int) ncr_wr, (int) ncr_bk); + err |= 4; + }; + return (err); +} + +/*========================================================== +** +** +** Profiling the drivers and targets performance. +** +** +**========================================================== +*/ + +#ifdef SCSI_NCR_PROFILE_SUPPORT + +/* +** Compute the difference in jiffies ticks. +*/ + +#define ncr_delta(from, to) \ + ( ((to) && (from))? (to) - (from) : -1 ) + +#define PROFILE cp->phys.header.stamp +static void ncb_profile (ncb_p np, ccb_p cp) +{ + int co, st, en, di, se, post,work,disc; + u_long diff; + + PROFILE.end = jiffies; + + st = ncr_delta (PROFILE.start,PROFILE.status); + if (st<0) return; /* status not reached */ + + co = ncr_delta (PROFILE.start,PROFILE.command); + if (co<0) return; /* command not executed */ + + en = ncr_delta (PROFILE.start,PROFILE.end), + di = ncr_delta (PROFILE.start,PROFILE.disconnect), + se = ncr_delta (PROFILE.start,PROFILE.select); + post = en - st; + + /* + ** @PROFILE@ Disconnect time invalid if multiple disconnects + */ + + if (di>=0) disc = se-di; else disc = 0; + + work = (st - co) - disc; + + diff = (np->disc_phys - np->disc_ref) & 0xff; + np->disc_ref += diff; + + np->profile.num_trans += 1; + if (cp->cmd) { + np->profile.num_kbytes += (cp->cmd->request_bufflen >> 10); + np->profile.rest_bytes += (cp->cmd->request_bufflen & (0x400-1)); + if (np->profile.rest_bytes >= 0x400) { + ++np->profile.num_kbytes; + np->profile.rest_bytes -= 0x400; + } + } + np->profile.num_disc += diff; + np->profile.ms_setup += co; + np->profile.ms_data += work; + np->profile.ms_disc += disc; + np->profile.ms_post += post; +} +#undef PROFILE + +#endif /* SCSI_NCR_PROFILE_SUPPORT */ + +/*========================================================== +** +** +** Device lookup. +** +** @GENSCSI@ should be integrated to scsiconf.c +** +** +**========================================================== +*/ + +struct table_entry { + char * manufacturer; + char * model; + char * version; + u_long info; +}; + +static struct table_entry device_tab[] = +{ +#ifdef NCR_GETCC_WITHMSG + {"", "", "", QUIRK_NOMSG}, + {"SONY", "SDT-5000", "3.17", QUIRK_NOMSG}, + {"WangDAT", "Model 2600", "01.7", QUIRK_NOMSG}, + {"WangDAT", "Model 3200", "02.2", QUIRK_NOMSG}, + {"WangDAT", "Model 1300", "02.4", QUIRK_NOMSG}, +#endif + {"", "", "", 0} /* catch all: must be last entry. */ +}; + +static u_long ncr_lookup(char * id) +{ + struct table_entry * p = device_tab; + char *d, *r, c; + + for (;;p++) { + + d = id+8; + r = p->manufacturer; + while ((c=*r++)) if (c!=*d++) break; + if (c) continue; + + d = id+16; + r = p->model; + while ((c=*r++)) if (c!=*d++) break; + if (c) continue; + + d = id+32; + r = p->version; + while ((c=*r++)) if (c!=*d++) break; + if (c) continue; + + return (p->info); + } +} + +/*========================================================== +** +** Determine the ncr's clock frequency. +** This is essential for the negotiation +** of the synchronous transfer rate. +** +**========================================================== +** +** Note: we have to return the correct value. +** THERE IS NO SAVE DEFAULT VALUE. +** +** Most NCR/SYMBIOS boards are delivered with a 40 Mhz clock. +** 53C860 and 53C875 rev. 1 support fast20 transfers but +** do not have a clock doubler and so are provided with a +** 80 MHz clock. All other fast20 boards incorporate a doubler +** and so should be delivered with a 40 MHz clock. +** The future fast40 chips (895/895) use a 40 Mhz base clock +** and provide a clock quadrupler (160 Mhz). The code below +** tries to deal as cleverly as possible with all this stuff. +** +**---------------------------------------------------------- +*/ + +/* + * Select NCR SCSI clock frequency + */ +static void ncr_selectclock(ncb_p np, u_char scntl3) +{ + if (np->multiplier < 2) { + OUTB(nc_scntl3, scntl3); + return; + } + + if (bootverbose >= 2) + printf ("%s: enabling clock multiplier\n", ncr_name(np)); + + OUTB(nc_stest1, DBLEN); /* Enable clock multiplier */ + if (np->multiplier > 2) { /* Poll bit 5 of stest4 for quadrupler */ + int i = 20; + while (!(INB(nc_stest4) & LCKFRQ) && --i > 0) + DELAY(20); + if (!i) + printf("%s: the chip cannot lock the frequency\n", ncr_name(np)); + } else /* Wait 20 micro-seconds for doubler */ + DELAY(20); + OUTB(nc_stest3, HSC); /* Halt the scsi clock */ + OUTB(nc_scntl3, scntl3); + OUTB(nc_stest1, (DBLEN|DBLSEL));/* Select clock multiplier */ + OUTB(nc_stest3, 0x00|TE); /* Restart scsi clock */ +} + + +/* + * calculate NCR SCSI clock frequency (in KHz) + */ +__initfunc( +static unsigned ncrgetfreq (ncb_p np, int gen) +) +{ + unsigned ms = 0; + + /* + * Measure GEN timer delay in order + * to calculate SCSI clock frequency + * + * This code will never execute too + * many loop iterations (if DELAY is + * reasonably correct). It could get + * too low a delay (too high a freq.) + * if the CPU is slow executing the + * loop for some reason (an NMI, for + * example). For this reason we will + * if multiple measurements are to be + * performed trust the higher delay + * (lower frequency returned). + */ + OUTB (nc_stest1, 0); /* make sure clock doubler is OFF */ + OUTW (nc_sien , 0); /* mask all scsi interrupts */ + (void) INW (nc_sist); /* clear pending scsi interrupt */ + OUTB (nc_dien , 0); /* mask all dma interrupts */ + (void) INW (nc_sist); /* another one, just to be sure :) */ + OUTB (nc_scntl3, 4); /* set pre-scaler to divide by 3 */ + OUTB (nc_stime1, 0); /* disable general purpose timer */ + OUTB (nc_stime1, gen); /* set to nominal delay of 1<<gen * 125us */ + while (!(INW(nc_sist) & GEN) && ms++ < 100000) + DELAY(1000); /* count ms */ + OUTB (nc_stime1, 0); /* disable general purpose timer */ + /* + * set prescaler to divide by whatever 0 means + * 0 ought to choose divide by 2, but appears + * to set divide by 3.5 mode in my 53c810 ... + */ + OUTB (nc_scntl3, 0); + + if (bootverbose >= 2) + printf ("%s: Delay (GEN=%d): %u msec\n", ncr_name(np), gen, ms); + /* + * adjust for prescaler, and convert into KHz + */ + return ms ? ((1 << gen) * 4340) / ms : 0; +} + +/* + * Get/probe NCR SCSI clock frequency + */ +__initfunc( +static void ncr_getclock (ncb_p np, int mult) +) +{ + unsigned char scntl3 = INB(nc_scntl3); + unsigned char stest1 = INB(nc_stest1); + unsigned f1; + + np->multiplier = 1; + f1 = 40000; + + /* + ** True with 875 or 895 with clock multiplier selected + */ + if (mult > 1 && (stest1 & (DBLEN+DBLSEL)) == DBLEN+DBLSEL) { + if (bootverbose >= 2) + printf ("%s: clock multiplier found\n", ncr_name(np)); + np->multiplier = mult; + } + + /* + ** If multiplier not found or scntl3 not 7,5,3, + ** reset chip and get frequency from general purpose timer. + ** Otherwise trust scntl3 BIOS setting. + */ + if (np->multiplier != mult || (scntl3 & 7) < 3 || !(scntl3 & 1)) { + unsigned f2; + + OUTB(nc_istat, SRST); DELAY(5); OUTB(nc_istat, 0); + + (void) ncrgetfreq (np, 11); /* throw away first result */ + f1 = ncrgetfreq (np, 11); + f2 = ncrgetfreq (np, 11); + + if (bootverbose) + printf ("%s: NCR clock is %uKHz, %uKHz\n", ncr_name(np), f1, f2); + + if (f1 > f2) f1 = f2; /* trust lower result */ + + if (f1 < 45000) f1 = 40000; + else if (f1 < 55000) f1 = 50000; + else f1 = 80000; + + if (f1 < 80000 && mult > 1) { + if (bootverbose >= 2) + printf ("%s: clock multiplier assumed\n", ncr_name(np)); + np->multiplier = mult; + } + } else { + if ((scntl3 & 7) == 3) f1 = 40000; + else if ((scntl3 & 7) == 5) f1 = 80000; + else f1 = 160000; + + f1 /= np->multiplier; + } + + /* + ** Compute controller synchronous parameters. + */ + f1 *= np->multiplier; + np->clock_khz = f1; +} + +/*===================== LINUX ENTRY POINTS SECTION ==========================*/ + +#ifndef uchar +#define uchar unsigned char +#endif + +#ifndef ushort +#define ushort unsigned short +#endif + +#ifndef ulong +#define ulong unsigned long +#endif + +/* --------------------------------------------------------------------- +** +** Driver setup from the boot command line +** +** --------------------------------------------------------------------- +*/ + +__initfunc( +void ncr53c8xx_setup(char *str, int *ints) +) +{ +#ifdef SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT + char *cur = str; + char *pc, *pv; + int val; + int base; + int c; + + while (cur != NULL && (pc = strchr(cur, ':')) != NULL) { + val = 0; + pv = pc; + c = *++pv; + if (c == 'n') + val = 0; + else if (c == 'y') + val = 1; + else { + base = 0; +#if 0 + if (c == '0') { + c = *pv++; + base = 8; + } + if (c == 'x') { + ++pv; + base = 16; + } + else if (c >= '0' && c <= '9') + base = 10; + else + break; +#endif + val = (int) simple_strtoul(pv, NULL, base); + } + + if (!strncmp(cur, "mpar:", 5)) + driver_setup.master_parity = val; + else if (!strncmp(cur, "spar:", 5)) + driver_setup.scsi_parity = val; + else if (!strncmp(cur, "disc:", 5)) + driver_setup.disconnection = val; + else if (!strncmp(cur, "specf:", 6)) + driver_setup.special_features = val; + else if (!strncmp(cur, "ultra:", 6)) + driver_setup.ultra_scsi = val; + else if (!strncmp(cur, "fsn:", 4)) + driver_setup.force_sync_nego = val; + else if (!strncmp(cur, "revprob:", 8)) + driver_setup.reverse_probe = val; + else if (!strncmp(cur, "tags:", 5)) { + if (val > SCSI_NCR_MAX_TAGS) + val = SCSI_NCR_MAX_TAGS; + driver_setup.default_tags = val; + } + else if (!strncmp(cur, "sync:", 5)) + driver_setup.default_sync = val; + else if (!strncmp(cur, "verb:", 5)) + driver_setup.verbose = val; + else if (!strncmp(cur, "debug:", 6)) + driver_setup.debug = val; + else if (!strncmp(cur, "burst:", 6)) + driver_setup.burst_max = val; + else if (!strncmp(cur, "led:", 4)) + driver_setup.led_pin = val; + else if (!strncmp(cur, "wide:", 5)) + driver_setup.max_wide = val? 1:0; + else if (!strncmp(cur, "settle:", 7)) + driver_setup.settle_delay= val; + else if (!strncmp(cur, "diff:", 5)) + driver_setup.diff_support= val; + else if (!strncmp(cur, "irqm:", 5)) + driver_setup.irqm = val; + else if (!strncmp(cur, "pcifix:", 7)) + driver_setup.pci_fix_up = val; + else if (!strncmp(cur, "buschk:", 7)) + driver_setup.bus_check = val; +#ifdef SCSI_NCR_NVRAM_SUPPORT + else if (!strncmp(cur, "nvram:", 6)) + driver_setup.use_nvram = val; +#endif + + else if (!strncmp(cur, "safe:", 5) && val) + memcpy(&driver_setup, &driver_safe_setup, sizeof(driver_setup)); + else + printf("ncr53c8xx_setup: unexpected boot option '%.*s' ignored\n", (int)(pc-cur+1), cur); + +#ifdef MODULE + if ((cur = strchr(cur, ' ')) != NULL) +#else + if ((cur = strchr(cur, ',')) != NULL) +#endif + ++cur; + } +#endif /* SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT */ +} + +static int ncr53c8xx_pci_init(Scsi_Host_Template *tpnt, + uchar bus, uchar device_fn, ncr_device *device); + +/* +** Linux entry point for NCR53C8XX devices detection routine. +** +** Called by the middle-level scsi drivers at initialization time, +** or at module installation. +** +** Read the PCI configuration and try to attach each +** detected NCR board. +** +** If NVRAM is present, try to attach boards according to +** the used defined boot order. +** +** Returns the number of boards successfully attached. +*/ + +__initfunc( +static void ncr_print_driver_setup(void) +) +{ +#define YesNo(y) y ? 'y' : 'n' + printk("ncr53c8xx: setup=disc:%c,specf:%d,ultra:%c,tags:%d,sync:%d,burst:%d,wide:%c,diff:%d\n", + YesNo(driver_setup.disconnection), + driver_setup.special_features, + YesNo(driver_setup.ultra_scsi), + driver_setup.default_tags, + driver_setup.default_sync, + driver_setup.burst_max, + YesNo(driver_setup.max_wide), + driver_setup.diff_support); + printk("ncr53c8xx: setup=mpar:%c,spar:%c,fsn=%c,verb:%d,debug:0x%x,led:%c,settle:%d,irqm:%d\n", + YesNo(driver_setup.master_parity), + YesNo(driver_setup.scsi_parity), + YesNo(driver_setup.force_sync_nego), + driver_setup.verbose, + driver_setup.debug, + YesNo(driver_setup.led_pin), + driver_setup.settle_delay, + driver_setup.irqm); +#undef YesNo +} + +/* +** NCR53C8XX devices description table and chip ids list. +*/ + +static ncr_chip ncr_chip_table[] __initdata = SCSI_NCR_CHIP_TABLE; +static ushort ncr_chip_ids[] __initdata = SCSI_NCR_CHIP_IDS; + +#ifdef SCSI_NCR_NVRAM_SUPPORT +__initfunc( +static int +ncr_attach_using_nvram(Scsi_Host_Template *tpnt, int nvram_index, int count, ncr_device device[]) +) +{ + int i, j; + int attach_count = 0; + ncr_nvram *nvram; + ncr_device *devp; + + if (!nvram_index) + return 0; + + /* find first Symbios NVRAM if there is one as we need to check it for host boot order */ + for (i = 0, nvram_index = -1; i < count; i++) { + devp = &device[i]; + nvram = devp->nvram; + if (!nvram) + continue; + if (nvram->type == SCSI_NCR_SYMBIOS_NVRAM) { + if (nvram_index == -1) + nvram_index = i; +#ifdef SCSI_NCR_DEBUG_NVRAM + printf("ncr53c8xx: NVRAM: Symbios format Boot Block, 53c%s, PCI bus %d, device %d, function %d\n", + devp->chip.name, devp->slot.bus, + (int) (devp->slot.device_fn & 0xf8) >> 3, + (int) devp->slot.device_fn & 7); + for (j = 0 ; j < 4 ; j++) { + Symbios_host *h = &nvram->data.Symbios.host[j]; + printf("ncr53c8xx: BOOT[%d] device_id=%04x vendor_id=%04x device_fn=%02x io_port=%04x %s\n", + j, h->device_id, h->vendor_id, + h->device_fn, h->io_port, + (h->flags & SYMBIOS_INIT_SCAN_AT_BOOT) ? "SCAN AT BOOT" : ""); + } + } + else if (nvram->type == SCSI_NCR_TEKRAM_NVRAM) { + /* display Tekram nvram data */ + printf("ncr53c8xx: NVRAM: Tekram format data, 53c%s, PCI bus %d, device %d, function %d\n", + devp->chip.name, devp->slot.bus, + (int) (devp->slot.device_fn & 0xf8) >> 3, + (int) devp->slot.device_fn & 7); +#endif + } + } + + if (nvram_index >= 0 && nvram_index < count) + nvram = device[nvram_index].nvram; + else + nvram = 0; + + if (!nvram) + goto out; + + /* + ** check devices in the boot record against devices detected. + ** attach devices if we find a match. boot table records that + ** do not match any detected devices will be ignored. + ** devices that do not match any boot table will not be attached + ** here but will attempt to be attached during the device table + ** rescan. + */ + for (i = 0; i < 4; i++) { + Symbios_host *h = &nvram->data.Symbios.host[i]; + for (j = 0 ; j < count ; j++) { + devp = &device[j]; + if (h->device_fn == devp->slot.device_fn && +#if 0 /* bus number location in nvram ? */ + h->bus == devp->slot.bus && +#endif + h->device_id == devp->chip.device_id) + break; + } + if (j < count && !devp->attach_done) { + if (!ncr_attach (tpnt, attach_count, devp)) + attach_count++; + devp->attach_done = 1; + } + } + +out: + return attach_count; +} +#endif /* SCSI_NCR_NVRAM_SUPPORT */ + +__initfunc( +int ncr53c8xx_detect(Scsi_Host_Template *tpnt) +) +{ + int i, j; + int chips; + int count = 0; + uchar bus, device_fn; + short index; + int attach_count = 0; + ncr_device device[8]; +#ifdef SCSI_NCR_NVRAM_SUPPORT + ncr_nvram nvram[4]; + int k, nvrams; +#endif + int hosts; + +#ifdef SCSI_NCR_NVRAM_SUPPORT + int nvram_index = 0; +#endif + if (initverbose >= 2) + ncr_print_driver_setup(); + +#ifdef SCSI_NCR_DEBUG_INFO_SUPPORT + ncr_debug = driver_setup.debug; +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) + tpnt->proc_dir = &proc_scsi_ncr53c8xx; +# ifdef SCSI_NCR_PROC_INFO_SUPPORT + tpnt->proc_info = ncr53c8xx_proc_info; +# endif +#endif + +#if defined(SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT) && defined(MODULE) +if (ncr53c8xx) + ncr53c8xx_setup(ncr53c8xx, (int *) 0); +#endif + + /* + ** Detect all 53c8xx hosts and then attach them. + ** + ** If we are using NVRAM, once all hosts are detected, we need to check + ** any NVRAM for boot order in case detect and boot order differ and + ** attach them using the order in the NVRAM. + ** + ** If no NVRAM is found or data appears invalid attach boards in the + ** the order they are detected. + */ + + if (!pcibios_present()) + return 0; + + chips = sizeof(ncr_chip_ids) / sizeof(ncr_chip_ids[0]); + hosts = sizeof(device) / sizeof(device[0]); +#ifdef SCSI_NCR_NVRAM_SUPPORT + k = 0; + if (driver_setup.use_nvram & 0x1) + nvrams = sizeof(nvram) / sizeof(nvram[0]); + else + nvrams = 0; +#endif + + for (j = 0; j < chips ; ++j) { + i = driver_setup.reverse_probe ? chips-1 - j : j; + for (index = 0; ; index++) { + char *msg = ""; + if ((pcibios_find_device(PCI_VENDOR_ID_NCR, ncr_chip_ids[i], + index, &bus, &device_fn)) || + (count == hosts)) + break; +#ifdef SCSI_NCR_NVRAM_SUPPORT + device[count].nvram = k < nvrams ? &nvram[k] : 0; +#else + device[count].nvram = 0; +#endif + if (ncr53c8xx_pci_init(tpnt, bus, device_fn, &device[count])) { + device[count].nvram = 0; + continue; + } +#ifdef SCSI_NCR_NVRAM_SUPPORT + if (device[count].nvram) { + ++k; + nvram_index |= device[count].nvram->type; + switch (device[count].nvram->type) { + case SCSI_NCR_TEKRAM_NVRAM: + msg = "with Tekram NVRAM"; + break; + case SCSI_NCR_SYMBIOS_NVRAM: + msg = "with Symbios NVRAM"; + break; + default: + msg = ""; + device[count].nvram = 0; + --k; + } + } +#endif + printf(KERN_INFO "ncr53c8xx: 53c%s detected %s\n", + device[count].chip.name, msg); + ++count; + } + } +#ifdef SCSI_NCR_NVRAM_SUPPORT + attach_count = ncr_attach_using_nvram(tpnt, nvram_index, count, device); +#endif + /* + ** rescan device list to make sure all boards attached. + ** devices without boot records will not be attached yet + ** so try to attach them here. + */ + for (i= 0; i < count; i++) { + if (!device[i].attach_done && + !ncr_attach (tpnt, attach_count, &device[i])) { + attach_count++; + } + } + + return attach_count; +} + +/* +** Read and check the PCI configuration for any detected NCR +** boards and save data for attaching after all boards have +** been detected. +*/ + +__initfunc( +static int ncr53c8xx_pci_init(Scsi_Host_Template *tpnt, + uchar bus, uchar device_fn, ncr_device *device) +) +{ + ushort vendor_id, device_id, command; + uchar cache_line_size, latency_timer; + uchar irq, revision; +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) + uint base, base_2, io_port; +#else + ulong base, base_2; +#endif + int i; + +#ifdef SCSI_NCR_NVRAM_SUPPORT + ncr_nvram *nvram = device->nvram; +#endif + ncr_chip *chip; + + printk(KERN_INFO "ncr53c8xx: at PCI bus %d, device %d, function %d\n", + bus, (int) (device_fn & 0xf8) >> 3, (int) device_fn & 7); + /* + * Read info from the PCI config space. + * pcibios_read_config_xxx() functions are assumed to be used for + * successfully detected PCI devices. + * Expecting error conditions from them is just paranoia, + * thus void cast. + */ + (void) pcibios_read_config_word(bus, device_fn, + PCI_VENDOR_ID, &vendor_id); + (void) pcibios_read_config_word(bus, device_fn, + PCI_DEVICE_ID, &device_id); + (void) pcibios_read_config_word(bus, device_fn, + PCI_COMMAND, &command); + (void) pcibios_read_config_dword(bus, device_fn, + PCI_BASE_ADDRESS_0, &io_port); + (void) pcibios_read_config_dword(bus, device_fn, + PCI_BASE_ADDRESS_1, &base); + (void) pcibios_read_config_dword(bus, device_fn, + PCI_BASE_ADDRESS_2, &base_2); + (void) pcibios_read_config_byte(bus, device_fn, + PCI_CLASS_REVISION,&revision); + (void) pcibios_read_config_byte(bus, device_fn, + PCI_INTERRUPT_LINE, &irq); + (void) pcibios_read_config_byte(bus, device_fn, + PCI_CACHE_LINE_SIZE, &cache_line_size); + (void) pcibios_read_config_byte(bus, device_fn, + PCI_LATENCY_TIMER, &latency_timer); + + /* + * Check if the chip is supported + */ + chip = 0; + for (i = 0; i < sizeof(ncr_chip_table)/sizeof(ncr_chip_table[0]); i++) { + if (device_id != ncr_chip_table[i].device_id) + continue; + if (revision > ncr_chip_table[i].revision_id) + continue; + chip = &device->chip; + memcpy(chip, &ncr_chip_table[i], sizeof(*chip)); + chip->revision_id = revision; + break; + } + if (!chip) { + printk("ncr53c8xx: not initializing, device not supported\n"); + return -1; + } + +#ifdef __powerpc__ + /* + * Severall fix-up for power/pc. + * Should not be performed by the driver. + */ + if ((command & + (PCI_COMMAND_MASTER|PCI_COMMAND_IO|PCI_COMMAND_MEMORY)) != + (PCI_COMMAND_MASTER|PCI_COMMAND_IO|PCI_COMMAND_MEMORY)) { + printk("ncr53c8xx : setting PCI master/io/command bit\n"); + command |= PCI_COMMAND_MASTER|PCI_COMMAND_IO|PCI_COMMAND_MEMORY; + pcibios_write_config_word(bus, device_fn, PCI_COMMAND, command); + } + if (io_port >= 0x10000000) { + io_port = (io_port & 0x00FFFFFF) | 0x01000000; + pcibios_write_config_dword(bus, device_fn, PCI_BASE_ADDRESS_0, io_port); + } + if (base >= 0x10000000) { + base = (base & 0x00FFFFFF) | 0x01000000; + pcibios_write_config_dword(bus, device_fn, PCI_BASE_ADDRESS_1, base); + } +#endif + + /* + * Check availability of IO space, memory space and master capability. + */ + if (command & PCI_COMMAND_IO) { + if ((io_port & 3) != 1) { + printk("ncr53c8xx: disabling I/O mapping since base address 0 (0x%x)\n" + " bits 0..1 indicate a non-IO mapping\n", (int) io_port); + io_port = 0; + } + else + io_port &= PCI_BASE_ADDRESS_IO_MASK; + } + else + io_port = 0; + + if (command & PCI_COMMAND_MEMORY) { + if ((base & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) { + printk("ncr53c8xx: disabling memory mapping since base address 1\n" + " contains a non-memory mapping\n"); + base = 0; + } + else + base &= PCI_BASE_ADDRESS_MEM_MASK; + } + else + base = 0; + + if (!io_port && !base) { + printk("ncr53c8xx: not initializing, both I/O and memory mappings disabled\n"); + return -1; + } + + base_2 &= PCI_BASE_ADDRESS_MEM_MASK; + + if (io_port && check_region (io_port, 128)) { + printk("ncr53c8xx: IO region 0x%x to 0x%x is in use\n", + (int) io_port, (int) (io_port + 127)); + return -1; + } + + if (!(command & PCI_COMMAND_MASTER)) { + printk("ncr53c8xx: not initializing, BUS MASTERING was disabled\n"); + return -1; + } + + /* + * Fix some features according to driver setup. + */ + if (!(driver_setup.special_features & 1)) + chip->features &= ~FE_SPECIAL_SET; + else { + if (driver_setup.special_features & 2) + chip->features &= ~FE_WRIE; + } + if (driver_setup.ultra_scsi < 2 && (chip->features & FE_ULTRA2)) { + chip->features |= FE_ULTRA; + chip->features &= ~FE_ULTRA2; + } + if (driver_setup.ultra_scsi < 1) + chip->features &= ~FE_ULTRA; + if (!driver_setup.max_wide) + chip->features &= ~FE_WIDE; + + +#ifdef SCSI_NCR_PCI_FIX_UP_SUPPORT + + /* + * Try to fix up PCI config according to wished features. + */ +#if defined(__i386) && !defined(MODULE) + if ((driver_setup.pci_fix_up & 1) && + (chip->features & FE_CLSE) && cache_line_size == 0) { +#if LINUX_VERSION_CODE < LinuxVersionCode(2,1,75) + extern char x86; + switch(x86) { +#else + switch(boot_cpu_data.x86) { +#endif + case 4: cache_line_size = 4; break; + case 5: cache_line_size = 8; break; + } + if (cache_line_size) + (void) pcibios_write_config_byte(bus, device_fn, + PCI_CACHE_LINE_SIZE, cache_line_size); + if (initverbose) + printk("ncr53c8xx: setting PCI_CACHE_LINE_SIZE to %d (fix-up).\n", cache_line_size); + } + + if ((driver_setup.pci_fix_up & 2) && cache_line_size && + (chip->features & FE_WRIE) && !(command & PCI_COMMAND_INVALIDATE)) { + command |= PCI_COMMAND_INVALIDATE; + (void) pcibios_write_config_word(bus, device_fn, + PCI_COMMAND, command); + if (initverbose) + printk("ncr53c8xx: setting PCI_COMMAND_INVALIDATE bit (fix-up).\n"); + } +#endif + /* + * Fix up for old chips that support READ LINE but not CACHE LINE SIZE. + * - If CACHE LINE SIZE is unknown, set burst max to 32 bytes = 8 dwords + * and donnot enable READ LINE. + * - Otherwise set it to the CACHE LINE SIZE (power of 2 assumed). + */ + + if (!(chip->features & FE_CLSE)) { + int burst_max = chip->burst_max; + if (cache_line_size == 0) { + chip->features &= ~FE_ERL; + if (burst_max > 3) + burst_max = 3; + } + else { + while (cache_line_size < (1 << burst_max)) + --burst_max; + } + chip->burst_max = burst_max; + } + + /* + * Tune PCI LATENCY TIMER according to burst max length transfer. + * (latency timer >= burst length + 6, we add 10 to be quite sure) + * If current value is zero, the device has probably been configured + * for no bursting due to some broken hardware. + */ + + if (latency_timer == 0 && chip->burst_max) + printk("ncr53c8xx: PCI_LATENCY_TIMER=0, bursting should'nt be allowed.\n"); + + if ((driver_setup.pci_fix_up & 4) && chip->burst_max) { + uchar lt = (1 << chip->burst_max) + 6 + 10; + if (latency_timer < lt) { + latency_timer = lt; + if (initverbose) + printk("ncr53c8xx: setting PCI_LATENCY_TIMER to %d bus clocks (fix-up).\n", latency_timer); + (void) pcibios_write_config_byte(bus, device_fn, + PCI_LATENCY_TIMER, latency_timer); + } + } + + /* + * Fix up for recent chips that support CACHE LINE SIZE. + * If PCI config space is not OK, remove features that shall not be + * used by the chip. No need to trigger possible chip bugs. + */ + + if ((chip->features & FE_CLSE) && cache_line_size == 0) { + chip->features &= ~FE_CACHE_SET; + printk("ncr53c8xx: PCI_CACHE_LINE_SIZE not set, features based on CACHE LINE SIZE not used.\n"); + } + + if ((chip->features & FE_WRIE) && !(command & PCI_COMMAND_INVALIDATE)) { + chip->features &= ~FE_WRIE; + printk("ncr53c8xx: PCI_COMMAND_INVALIDATE not set, WRITE AND INVALIDATE not used\n"); + } + +#endif /* SCSI_NCR_PCI_FIX_UP_SUPPORT */ + + /* initialise ncr_device structure with items required by ncr_attach */ + device->slot.bus = bus; + device->slot.device_fn = device_fn; + device->slot.base = base; + device->slot.base_2 = base_2; + device->slot.io_port = io_port; + device->slot.irq = irq; + device->attach_done = 0; +#ifdef SCSI_NCR_NVRAM_SUPPORT + if (!nvram) + goto out; + + /* + ** Get access to chip IO registers + */ +#ifdef NCR_IOMAPPED + request_region(io_port, 128, "ncr53c8xx"); + device->slot.port = io_port; +#else + device->slot.reg = (struct ncr_reg *) remap_pci_mem((ulong) base, 128); + if (!device->slot.reg) + goto out; +#endif + + /* + ** Try to read SYMBIOS nvram. + ** Data can be used to order booting of boards. + ** + ** Data is saved in ncr_device structure if NVRAM found. This + ** is then used to find drive boot order for ncr_attach(). + ** + ** NVRAM data is passed to Scsi_Host_Template later during ncr_attach() + ** for any device set up. + ** + ** Try to read TEKRAM nvram if Symbios nvram not found. + */ + + if (!ncr_get_Symbios_nvram(&device->slot, &nvram->data.Symbios)) + nvram->type = SCSI_NCR_SYMBIOS_NVRAM; + else if (!ncr_get_Tekram_nvram(&device->slot, &nvram->data.Tekram)) + nvram->type = SCSI_NCR_TEKRAM_NVRAM; + else + nvram->type = 0; +out: + /* + ** Release access to chip IO registers + */ +#ifdef NCR_IOMAPPED + release_region(device->slot.port, 128); +#else + unmap_pci_mem((vm_offset_t) device->slot.reg, (u_long) 128); +#endif + +#endif /* SCSI_NCR_NVRAM_SUPPORT */ + return 0; +} + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,0,0) +/* +** Linux select queue depths function +*/ +static void ncr53c8xx_select_queue_depths(struct Scsi_Host *host, struct scsi_device *devlist) +{ + struct scsi_device *device; + + for (device = devlist; device; device = device->next) { + if (device->host == host) { +#if SCSI_NCR_MAX_TAGS > 1 + if (device->tagged_supported) { + device->queue_depth = SCSI_NCR_MAX_TAGS; + } + else { + device->queue_depth = 2; + } +#else + device->queue_depth = 1; +#endif + +#ifdef DEBUG_NCR53C8XX +printk("ncr53c8xx_select_queue_depth: id=%d, lun=%d, queue_depth=%d\n", + device->id, device->lun, device->queue_depth); +#endif + } + } +} +#endif + +/* +** Linux entry point of queuecommand() function +*/ + +int ncr53c8xx_queue_command (Scsi_Cmnd *cmd, void (* done)(Scsi_Cmnd *)) +{ + int sts; +#ifdef DEBUG_NCR53C8XX +printk("ncr53c8xx_queue_command\n"); +#endif + + if ((sts = ncr_queue_command(cmd, done)) != DID_OK) { + cmd->result = ScsiResult(sts, 0); + done(cmd); +#ifdef DEBUG_NCR53C8XX +printk("ncr53c8xx : command not queued - result=%d\n", sts); +#endif + return sts; + } +#ifdef DEBUG_NCR53C8XX +printk("ncr53c8xx : command successfully queued\n"); +#endif + return sts; +} + +/* +** Linux entry point of the interrupt handler. +** Fort linux versions > 1.3.70, we trust the kernel for +** passing the internal host descriptor as 'dev_id'. +** Otherwise, we scan the host list and call the interrupt +** routine for each host that uses this IRQ. +*/ + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,70) +static void ncr53c8xx_intr(int irq, void *dev_id, struct pt_regs * regs) +{ +#ifdef DEBUG_NCR53C8XX + printk("ncr53c8xx : interrupt received\n"); +#endif + + if (DEBUG_FLAGS & DEBUG_TINY) printf ("["); + ncr_exception((ncb_p) dev_id); + if (DEBUG_FLAGS & DEBUG_TINY) printf ("]\n"); +} + +#else +static void ncr53c8xx_intr(int irq, struct pt_regs * regs) +{ + struct Scsi_Host *host; + struct host_data *host_data; + + for (host = first_host; host; host = host->next) { + if (host->hostt == the_template && host->irq == irq) { + host_data = (struct host_data *) host->hostdata; + if (DEBUG_FLAGS & DEBUG_TINY) printf ("["); + ncr_exception(host_data->ncb); + if (DEBUG_FLAGS & DEBUG_TINY) printf ("]\n"); + } + } +} +#endif + +/* +** Linux entry point of the timer handler +*/ + +static void ncr53c8xx_timeout(unsigned long np) +{ + ncr_timeout((ncb_p) np); +} + +/* +** Linux entry point of reset() function +*/ + +#if defined SCSI_RESET_SYNCHRONOUS && defined SCSI_RESET_ASYNCHRONOUS + +int ncr53c8xx_reset(Scsi_Cmnd *cmd, unsigned int reset_flags) +{ + int sts; + unsigned long flags; + + printk("ncr53c8xx_reset: pid=%lu reset_flags=%x serial_number=%ld serial_number_at_timeout=%ld\n", + cmd->pid, reset_flags, cmd->serial_number, cmd->serial_number_at_timeout); + + save_flags(flags); cli(); + + /* + * We have to just ignore reset requests in some situations. + */ +#if defined SCSI_RESET_NOT_RUNNING + if (cmd->serial_number != cmd->serial_number_at_timeout) { + sts = SCSI_RESET_NOT_RUNNING; + goto out; + } +#endif + /* + * If the mid-level driver told us reset is synchronous, it seems + * that we must call the done() callback for the involved command, + * even if this command was not queued to the low-level driver, + * before returning SCSI_RESET_SUCCESS. + */ + + sts = ncr_reset_bus(cmd, + (reset_flags & (SCSI_RESET_SYNCHRONOUS | SCSI_RESET_ASYNCHRONOUS)) == SCSI_RESET_SYNCHRONOUS); + /* + * Since we always reset the controller, when we return success, + * we add this information to the return code. + */ +#if defined SCSI_RESET_HOST_RESET + if (sts == SCSI_RESET_SUCCESS) + sts |= SCSI_RESET_HOST_RESET; +#endif + +out: + restore_flags(flags); + return sts; +} +#else +int ncr53c8xx_reset(Scsi_Cmnd *cmd) +{ + printk("ncr53c8xx_reset: command pid %lu\n", cmd->pid); + return ncr_reset_bus(cmd, 1); +} +#endif + +/* +** Linux entry point of abort() function +*/ + +#if defined SCSI_RESET_SYNCHRONOUS && defined SCSI_RESET_ASYNCHRONOUS + +int ncr53c8xx_abort(Scsi_Cmnd *cmd) +{ + int sts; + unsigned long flags; + + printk("ncr53c8xx_abort: pid=%lu serial_number=%ld serial_number_at_timeout=%ld\n", + cmd->pid, cmd->serial_number, cmd->serial_number_at_timeout); + + save_flags(flags); cli(); + + /* + * We have to just ignore abort requests in some situations. + */ + if (cmd->serial_number != cmd->serial_number_at_timeout) { + sts = SCSI_ABORT_NOT_RUNNING; + goto out; + } + + sts = ncr_abort_command(cmd); +out: + restore_flags(flags); + return sts; +} +#else +int ncr53c8xx_abort(Scsi_Cmnd *cmd) +{ + printk("ncr53c8xx_abort: command pid %lu\n", cmd->pid); + return ncr_abort_command(cmd); +} +#endif + +#ifdef MODULE +int ncr53c8xx_release(struct Scsi_Host *host) +{ +#ifdef DEBUG_NCR53C8XX +printk("ncr53c8xx : release\n"); +#endif + ncr_detach(((struct host_data *) host->hostdata)->ncb); + + return 1; +} +#endif + + +/* +** Scsi command waiting list management. +** +** It may happen that we cannot insert a scsi command into the start queue, +** in the following circumstances. +** Too few preallocated ccb(s), +** maxtags < cmd_per_lun of the Linux host control block, +** etc... +** Such scsi commands are inserted into a waiting list. +** When a scsi command complete, we try to requeue the commands of the +** waiting list. +*/ + +#define next_wcmd host_scribble + +static void insert_into_waiting_list(ncb_p np, Scsi_Cmnd *cmd) +{ + Scsi_Cmnd *wcmd; + +#ifdef DEBUG_WAITING_LIST + printf("%s: cmd %lx inserted into waiting list\n", ncr_name(np), (u_long) cmd); +#endif + cmd->next_wcmd = 0; + if (!(wcmd = np->waiting_list)) np->waiting_list = cmd; + else { + while ((wcmd->next_wcmd) != 0) + wcmd = (Scsi_Cmnd *) wcmd->next_wcmd; + wcmd->next_wcmd = (char *) cmd; + } +} + +static Scsi_Cmnd *retrieve_from_waiting_list(int to_remove, ncb_p np, Scsi_Cmnd *cmd) +{ + Scsi_Cmnd *wcmd; + + if (!(wcmd = np->waiting_list)) return 0; + while (wcmd->next_wcmd) { + if (cmd == (Scsi_Cmnd *) wcmd->next_wcmd) { + if (to_remove) { + wcmd->next_wcmd = cmd->next_wcmd; + cmd->next_wcmd = 0; + } +#ifdef DEBUG_WAITING_LIST + printf("%s: cmd %lx retrieved from waiting list\n", ncr_name(np), (u_long) cmd); +#endif + return cmd; + } + } + return 0; +} + +static void process_waiting_list(ncb_p np, int sts) +{ + Scsi_Cmnd *waiting_list, *wcmd; + + waiting_list = np->waiting_list; + np->waiting_list = 0; + +#ifdef DEBUG_WAITING_LIST + if (waiting_list) printf("%s: waiting_list=%lx processing sts=%d\n", ncr_name(np), (u_long) waiting_list, sts); +#endif + while ((wcmd = waiting_list) != 0) { + waiting_list = (Scsi_Cmnd *) wcmd->next_wcmd; + wcmd->next_wcmd = 0; + if (sts == DID_OK) { +#ifdef DEBUG_WAITING_LIST + printf("%s: cmd %lx trying to requeue\n", ncr_name(np), (u_long) wcmd); +#endif + sts = ncr_queue_command(wcmd, wcmd->scsi_done); + } + if (sts != DID_OK) { +#ifdef DEBUG_WAITING_LIST + printf("%s: cmd %lx done forced sts=%d\n", ncr_name(np), (u_long) wcmd, sts); +#endif + wcmd->result = ScsiResult(sts, 0); + wcmd->scsi_done(wcmd); + } + } +} + +#undef next_wcmd + +/* +** Returns data transfer direction for common op-codes. +*/ + +static int guess_xfer_direction(int opcode) +{ + int d; + + switch(opcode) { + case 0x12: /* INQUIRY 12 */ + case 0x4D: /* LOG SENSE 4D */ + case 0x5A: /* MODE SENSE(10) 5A */ + case 0x1A: /* MODE SENSE(6) 1A */ + case 0x3C: /* READ BUFFER 3C */ + case 0x1C: /* RECEIVE DIAGNOSTIC RESULTS 1C */ + case 0x03: /* REQUEST SENSE 03 */ + d = XferIn; + break; + case 0x39: /* COMPARE 39 */ + case 0x3A: /* COPY AND VERIFY 3A */ + case 0x18: /* COPY 18 */ + case 0x4C: /* LOG SELECT 4C */ + case 0x55: /* MODE SELECT(10) 55 */ + case 0x3B: /* WRITE BUFFER 3B */ + case 0x1D: /* SEND DIAGNOSTIC 1D */ + case 0x40: /* CHANGE DEFINITION 40 */ + case 0x15: /* MODE SELECT(6) 15 */ + d = XferOut; + break; + case 0x00: /* TEST UNIT READY 00 */ + d = XferNone; + break; + default: + d = XferBoth; + break; + } + + return d; +} + + +#ifdef SCSI_NCR_PROC_INFO_SUPPORT + +/*========================================================================= +** Proc file system stuff +** +** A read operation returns profile information. +** A write operation is a control command. +** The string is parsed in the driver code and the command is passed +** to the ncr_usercmd() function. +**========================================================================= +*/ + +#ifdef SCSI_NCR_USER_COMMAND_SUPPORT + +#define is_digit(c) ((c) >= '0' && (c) <= '9') +#define digit_to_bin(c) ((c) - '0') +#define is_space(c) ((c) == ' ' || (c) == '\t') + +static int skip_spaces(char *ptr, int len) +{ + int cnt, c; + + for (cnt = len; cnt > 0 && (c = *ptr++) && is_space(c); cnt--); + + return (len - cnt); +} + +static int get_int_arg(char *ptr, int len, u_long *pv) +{ + int cnt, c; + u_long v; + + for (v = 0, cnt = len; cnt > 0 && (c = *ptr++) && is_digit(c); cnt--) { + v = (v * 10) + digit_to_bin(c); + } + + if (pv) + *pv = v; + + return (len - cnt); +} + +static int is_keyword(char *ptr, int len, char *verb) +{ + int verb_len = strlen(verb); + + if (len >= strlen(verb) && !memcmp(verb, ptr, verb_len)) + return verb_len; + else + return 0; + +} + +#define SKIP_SPACES(min_spaces) \ + if ((arg_len = skip_spaces(ptr, len)) < (min_spaces)) \ + return -EINVAL; \ + ptr += arg_len; len -= arg_len; + +#define GET_INT_ARG(v) \ + if (!(arg_len = get_int_arg(ptr, len, &(v)))) \ + return -EINVAL; \ + ptr += arg_len; len -= arg_len; + + +/* +** Parse a control command +*/ + +static int ncr_user_command(ncb_p np, char *buffer, int length) +{ + char *ptr = buffer; + int len = length; + struct usrcmd *uc = &np->user; + int arg_len; + u_long target; + + bzero(uc, sizeof(*uc)); + + if (len > 0 && ptr[len-1] == '\n') + --len; + + if ((arg_len = is_keyword(ptr, len, "setsync")) != 0) + uc->cmd = UC_SETSYNC; + else if ((arg_len = is_keyword(ptr, len, "settags")) != 0) + uc->cmd = UC_SETTAGS; + else if ((arg_len = is_keyword(ptr, len, "setorder")) != 0) + uc->cmd = UC_SETORDER; + else if ((arg_len = is_keyword(ptr, len, "setwide")) != 0) + uc->cmd = UC_SETWIDE; + else if ((arg_len = is_keyword(ptr, len, "setdebug")) != 0) + uc->cmd = UC_SETDEBUG; + else if ((arg_len = is_keyword(ptr, len, "setflag")) != 0) + uc->cmd = UC_SETFLAG; + else if ((arg_len = is_keyword(ptr, len, "clearprof")) != 0) + uc->cmd = UC_CLEARPROF; +#ifdef UC_DEBUG_ERROR_RECOVERY + else if ((arg_len = is_keyword(ptr, len, "debug_error_recovery")) != 0) + uc->cmd = UC_DEBUG_ERROR_RECOVERY; +#endif + else + arg_len = 0; + +#ifdef DEBUG_PROC_INFO +printf("ncr_user_command: arg_len=%d, cmd=%ld\n", arg_len, uc->cmd); +#endif + + if (!arg_len) + return -EINVAL; + ptr += arg_len; len -= arg_len; + + switch(uc->cmd) { + case UC_SETSYNC: + case UC_SETTAGS: + case UC_SETWIDE: + case UC_SETFLAG: + SKIP_SPACES(1); + if ((arg_len = is_keyword(ptr, len, "all")) != 0) { + ptr += arg_len; len -= arg_len; + uc->target = ~0; + } else { + GET_INT_ARG(target); + uc->target = (1<<target); +#ifdef DEBUG_PROC_INFO +printf("ncr_user_command: target=%ld\n", target); +#endif + } + break; + } + + switch(uc->cmd) { + case UC_SETSYNC: + case UC_SETTAGS: + case UC_SETWIDE: + SKIP_SPACES(1); + GET_INT_ARG(uc->data); +#ifdef DEBUG_PROC_INFO +printf("ncr_user_command: data=%ld\n", uc->data); +#endif + break; + case UC_SETORDER: + SKIP_SPACES(1); + if ((arg_len = is_keyword(ptr, len, "simple"))) + uc->data = M_SIMPLE_TAG; + else if ((arg_len = is_keyword(ptr, len, "ordered"))) + uc->data = M_ORDERED_TAG; + else if ((arg_len = is_keyword(ptr, len, "default"))) + uc->data = 0; + else + return -EINVAL; + break; + case UC_SETDEBUG: + while (len > 0) { + SKIP_SPACES(1); + if ((arg_len = is_keyword(ptr, len, "alloc"))) + uc->data |= DEBUG_ALLOC; + else if ((arg_len = is_keyword(ptr, len, "phase"))) + uc->data |= DEBUG_PHASE; + else if ((arg_len = is_keyword(ptr, len, "poll"))) + uc->data |= DEBUG_POLL; + else if ((arg_len = is_keyword(ptr, len, "queue"))) + uc->data |= DEBUG_QUEUE; + else if ((arg_len = is_keyword(ptr, len, "result"))) + uc->data |= DEBUG_RESULT; + else if ((arg_len = is_keyword(ptr, len, "scatter"))) + uc->data |= DEBUG_SCATTER; + else if ((arg_len = is_keyword(ptr, len, "script"))) + uc->data |= DEBUG_SCRIPT; + else if ((arg_len = is_keyword(ptr, len, "tiny"))) + uc->data |= DEBUG_TINY; + else if ((arg_len = is_keyword(ptr, len, "timing"))) + uc->data |= DEBUG_TIMING; + else if ((arg_len = is_keyword(ptr, len, "nego"))) + uc->data |= DEBUG_NEGO; + else if ((arg_len = is_keyword(ptr, len, "tags"))) + uc->data |= DEBUG_TAGS; + else if ((arg_len = is_keyword(ptr, len, "freeze"))) + uc->data |= DEBUG_FREEZE; + else if ((arg_len = is_keyword(ptr, len, "restart"))) + uc->data |= DEBUG_RESTART; + else + return -EINVAL; + ptr += arg_len; len -= arg_len; + } +#ifdef DEBUG_PROC_INFO +printf("ncr_user_command: data=%ld\n", uc->data); +#endif + break; + case UC_SETFLAG: + while (len > 0) { + SKIP_SPACES(1); + if ((arg_len = is_keyword(ptr, len, "trace"))) + uc->data |= UF_TRACE; + else if ((arg_len = is_keyword(ptr, len, "no_disc"))) + uc->data |= UF_NODISC; + else + return -EINVAL; + ptr += arg_len; len -= arg_len; + } + break; +#ifdef UC_DEBUG_ERROR_RECOVERY + case UC_DEBUG_ERROR_RECOVERY: + SKIP_SPACES(1); + if ((arg_len = is_keyword(ptr, len, "sge"))) + uc->data = 1; + else if ((arg_len = is_keyword(ptr, len, "abort"))) + uc->data = 2; + else if ((arg_len = is_keyword(ptr, len, "reset"))) + uc->data = 3; + else if ((arg_len = is_keyword(ptr, len, "parity"))) + uc->data = 4; + else if ((arg_len = is_keyword(ptr, len, "none"))) + uc->data = 0; + else + return -EINVAL; + ptr += arg_len; len -= arg_len; + break; +#endif + default: + break; + } + + if (len) + return -EINVAL; + else { + long flags; + + save_flags(flags); cli(); + ncr_usercmd (np); + restore_flags(flags); + } + return length; +} + +#endif /* SCSI_NCR_USER_COMMAND_SUPPORT */ + +#ifdef SCSI_NCR_USER_INFO_SUPPORT + +struct info_str +{ + char *buffer; + int length; + int offset; + int pos; +}; + +static void copy_mem_info(struct info_str *info, char *data, int len) +{ + if (info->pos + len > info->length) + len = info->length - info->pos; + + if (info->pos + len < info->offset) { + info->pos += len; + return; + } + if (info->pos < info->offset) { + data += (info->offset - info->pos); + len -= (info->offset - info->pos); + } + + if (len > 0) { + memcpy(info->buffer + info->pos, data, len); + info->pos += len; + } +} + +static int copy_info(struct info_str *info, char *fmt, ...) +{ + va_list args; + char buf[81]; + int len; + + va_start(args, fmt); + len = vsprintf(buf, fmt, args); + va_end(args); + + copy_mem_info(info, buf, len); + return len; +} + +/* +** Copy formatted profile information into the input buffer. +*/ + +#define to_ms(t) ((t) * 1000 / HZ) + +static int ncr_host_info(ncb_p np, char *ptr, off_t offset, int len) +{ + struct info_str info; + + info.buffer = ptr; + info.length = len; + info.offset = offset; + info.pos = 0; + + copy_info(&info, "General information:\n"); + copy_info(&info, " Chip NCR53C%s, ", np->chip_name); + copy_info(&info, "device id 0x%x, ", np->device_id); + copy_info(&info, "revision id 0x%x\n", np->revision_id); + + copy_info(&info, " IO port address 0x%lx, ", (u_long) np->port); + copy_info(&info, "IRQ number %d\n", (int) np->irq); + +#ifndef NCR_IOMAPPED + if (np->reg) + copy_info(&info, " Using memory mapped IO at virtual address 0x%lx\n", + (u_long) np->reg); +#endif + copy_info(&info, " Synchronous period factor %d, ", (int) np->minsync); + copy_info(&info, "max commands per lun %d\n", SCSI_NCR_MAX_TAGS); + + if (driver_setup.debug || driver_setup.verbose > 1) { + copy_info(&info, " Debug flags 0x%x, ", driver_setup.debug); + copy_info(&info, "verbosity level %d\n", driver_setup.verbose); + } + +#ifdef SCSI_NCR_PROFILE_SUPPORT + copy_info(&info, "Profiling information:\n"); + copy_info(&info, " %-12s = %lu\n", "num_trans",np->profile.num_trans); + copy_info(&info, " %-12s = %lu\n", "num_kbytes",np->profile.num_kbytes); + copy_info(&info, " %-12s = %lu\n", "num_disc", np->profile.num_disc); + copy_info(&info, " %-12s = %lu\n", "num_break",np->profile.num_break); + copy_info(&info, " %-12s = %lu\n", "num_int", np->profile.num_int); + copy_info(&info, " %-12s = %lu\n", "num_fly", np->profile.num_fly); + copy_info(&info, " %-12s = %lu\n", "ms_setup", to_ms(np->profile.ms_setup)); + copy_info(&info, " %-12s = %lu\n", "ms_data", to_ms(np->profile.ms_data)); + copy_info(&info, " %-12s = %lu\n", "ms_disc", to_ms(np->profile.ms_disc)); + copy_info(&info, " %-12s = %lu\n", "ms_post", to_ms(np->profile.ms_post)); +#endif + + return info.pos > info.offset? info.pos - info.offset : 0; +} + +#endif /* SCSI_NCR_USER_INFO_SUPPORT */ + +/* +** Entry point of the scsi proc fs of the driver. +** - func = 0 means read (returns profile data) +** - func = 1 means write (parse user control command) +*/ + +int ncr53c8xx_proc_info(char *buffer, char **start, off_t offset, + int length, int hostno, int func) +{ + struct Scsi_Host *host; + struct host_data *host_data; + ncb_p ncb = 0; + int retv; + +#ifdef DEBUG_PROC_INFO +printf("ncr53c8xx_proc_info: hostno=%d, func=%d\n", hostno, func); +#endif + + for (host = first_host; host; host = host->next) { + if (host->hostt == the_template && host->host_no == hostno) { + host_data = (struct host_data *) host->hostdata; + ncb = host_data->ncb; + break; + } + } + + if (!ncb) + return -EINVAL; + + if (func) { +#ifdef SCSI_NCR_USER_COMMAND_SUPPORT + retv = ncr_user_command(ncb, buffer, length); +#else + retv = -EINVAL; +#endif + } + else { + if (start) + *start = buffer; +#ifdef SCSI_NCR_USER_INFO_SUPPORT + retv = ncr_host_info(ncb, buffer, offset, length); +#else + retv = -EINVAL; +#endif + } + + return retv; +} + + +/*========================================================================= +** End of proc file system stuff +**========================================================================= +*/ +#endif + + +#ifdef SCSI_NCR_NVRAM_SUPPORT + +/* --------------------------------------------------------------------- +** +** Try reading Symbios format nvram +** +** --------------------------------------------------------------------- +** +** GPOI0 - data in/data out +** GPIO1 - clock +** +** return 0 if NVRAM data OK, 1 if NVRAM data not OK +** --------------------------------------------------------------------- +*/ + +#define SET_BIT 0 +#define CLR_BIT 1 +#define SET_CLK 2 +#define CLR_CLK 3 + +static u_short nvram_read_data(ncr_slot *np, u_char *data, int len, u_char *gpreg, u_char *gpcntl); +static void nvram_start(ncr_slot *np, u_char *gpreg); +static void nvram_write_byte(ncr_slot *np, u_char *ack_data, u_char write_data, u_char *gpreg, u_char *gpcntl); +static void nvram_read_byte(ncr_slot *np, u_char *read_data, u_char ack_data, u_char *gpreg, u_char *gpcntl); +static void nvram_readAck(ncr_slot *np, u_char *read_bit, u_char *gpreg, u_char *gpcntl); +static void nvram_writeAck(ncr_slot *np, u_char write_bit, u_char *gpreg, u_char *gpcntl); +static void nvram_doBit(ncr_slot *np, u_char *read_bit, u_char write_bit, u_char *gpreg); +static void nvram_stop(ncr_slot *np, u_char *gpreg); +static void nvram_setBit(ncr_slot *np, u_char write_bit, u_char *gpreg, int bit_mode); + +__initfunc( +static int ncr_get_Symbios_nvram (ncr_slot *np, Symbios_nvram *nvram) +) +{ + static u_char Symbios_trailer[6] = {0xfe, 0xfe, 0, 0, 0, 0}; + u_char gpcntl, gpreg; + u_char old_gpcntl, old_gpreg; + u_short csum; + u_char ack_data; + int retv = 1; + + /* save current state of GPCNTL and GPREG */ + old_gpreg = INB (nc_gpreg); + old_gpcntl = INB (nc_gpcntl); + gpcntl = old_gpcntl & 0xfc; + + /* set up GPREG & GPCNTL to set GPIO0 and GPIO1 in to known state */ + OUTB (nc_gpreg, old_gpreg); + OUTB (nc_gpcntl, gpcntl); + + /* this is to set NVRAM into a known state with GPIO0/1 both low */ + gpreg = old_gpreg; + nvram_setBit(np, 0, &gpreg, CLR_CLK); + nvram_setBit(np, 0, &gpreg, CLR_BIT); + + /* now set NVRAM inactive with GPIO0/1 both high */ + nvram_stop(np, &gpreg); + + /* activate NVRAM */ + nvram_start(np, &gpreg); + + /* write device code and random address MSB */ + nvram_write_byte(np, &ack_data, + 0xa0 | ((SYMBIOS_NVRAM_ADDRESS >> 7) & 0x0e), &gpreg, &gpcntl); + if (ack_data & 0x01) + goto out; + + /* write random address LSB */ + nvram_write_byte(np, &ack_data, + (SYMBIOS_NVRAM_ADDRESS & 0x7f) << 1, &gpreg, &gpcntl); + if (ack_data & 0x01) + goto out; + + /* regenerate START state to set up for reading */ + nvram_start(np, &gpreg); + + /* rewrite device code and address MSB with read bit set (lsb = 0x01) */ + nvram_write_byte(np, &ack_data, + 0xa1 | ((SYMBIOS_NVRAM_ADDRESS >> 7) & 0x0e), &gpreg, &gpcntl); + if (ack_data & 0x01) + goto out; + + /* now set up GPIO0 for inputting data */ + gpcntl |= 0x01; + OUTB (nc_gpcntl, gpcntl); + + /* input all active data - only part of total NVRAM */ + csum = nvram_read_data(np, + (u_char *) nvram, sizeof(*nvram), &gpreg, &gpcntl); + + /* finally put NVRAM back in inactive mode */ + gpcntl &= 0xfe; + OUTB (nc_gpcntl, gpcntl); + nvram_stop(np, &gpreg); + +#ifdef SCSI_NCR_DEBUG_NVRAM +printf("ncr53c8xx: NvRAM marker=%x trailer=%x %x %x %x %x %x byte_count=%d/%d checksum=%x/%x\n", + nvram->start_marker, + nvram->trailer[0], nvram->trailer[1], nvram->trailer[2], + nvram->trailer[3], nvram->trailer[4], nvram->trailer[5], + nvram->byte_count, sizeof(*nvram) - 12, + nvram->checksum, csum); +#endif + + /* check valid NVRAM signature, verify byte count and checksum */ + if (nvram->start_marker == 0 && + !memcmp(nvram->trailer, Symbios_trailer, 6) && + nvram->byte_count == sizeof(*nvram) - 12 && + csum == nvram->checksum) + retv = 0; +out: + /* return GPIO0/1 to original states after having accessed NVRAM */ + OUTB (nc_gpcntl, old_gpcntl); + OUTB (nc_gpreg, old_gpreg); + + return retv; +} + +/* + * Read Symbios NvRAM data and compute checksum. + */ +__initfunc( +static u_short nvram_read_data(ncr_slot *np, u_char *data, int len, u_char *gpreg, u_char *gpcntl) +) +{ + int x; + u_short csum; + + for (x = 0; x < len; x++) + nvram_read_byte(np, &data[x], (x == (len - 1)), gpreg, gpcntl); + + for (x = 6, csum = 0; x < len - 6; x++) + csum += data[x]; + + return csum; +} + +/* + * Send START condition to NVRAM to wake it up. + */ +__initfunc( +static void nvram_start(ncr_slot *np, u_char *gpreg) +) +{ + nvram_setBit(np, 1, gpreg, SET_BIT); + nvram_setBit(np, 0, gpreg, SET_CLK); + nvram_setBit(np, 0, gpreg, CLR_BIT); + nvram_setBit(np, 0, gpreg, CLR_CLK); +} + +/* + * WRITE a byte to the NVRAM and then get an ACK to see it was accepted OK, + * GPIO0 must already be set as an output + */ +__initfunc( +static void nvram_write_byte(ncr_slot *np, u_char *ack_data, u_char write_data, u_char *gpreg, u_char *gpcntl) +) +{ + int x; + + for (x = 0; x < 8; x++) + nvram_doBit(np, 0, (write_data >> (7 - x)) & 0x01, gpreg); + + nvram_readAck(np, ack_data, gpreg, gpcntl); +} + +/* + * READ a byte from the NVRAM and then send an ACK to say we have got it, + * GPIO0 must already be set as an input + */ +__initfunc( +static void nvram_read_byte(ncr_slot *np, u_char *read_data, u_char ack_data, u_char *gpreg, u_char *gpcntl) +) +{ + int x; + u_char read_bit; + + *read_data = 0; + for (x = 0; x < 8; x++) { + nvram_doBit(np, &read_bit, 1, gpreg); + *read_data |= ((read_bit & 0x01) << (7 - x)); + } + + nvram_writeAck(np, ack_data, gpreg, gpcntl); +} + +/* + * Output an ACK to the NVRAM after reading, + * change GPIO0 to output and when done back to an input + */ +__initfunc( +static void nvram_writeAck(ncr_slot *np, u_char write_bit, u_char *gpreg, u_char *gpcntl) +) +{ + OUTB (nc_gpcntl, *gpcntl & 0xfe); + nvram_doBit(np, 0, write_bit, gpreg); + OUTB (nc_gpcntl, *gpcntl); +} + +/* + * Input an ACK from NVRAM after writing, + * change GPIO0 to input and when done back to an output + */ +__initfunc( +static void nvram_readAck(ncr_slot *np, u_char *read_bit, u_char *gpreg, u_char *gpcntl) +) +{ + OUTB (nc_gpcntl, *gpcntl | 0x01); + nvram_doBit(np, read_bit, 1, gpreg); + OUTB (nc_gpcntl, *gpcntl); +} + +/* + * Read or write a bit to the NVRAM, + * read if GPIO0 input else write if GPIO0 output + */ +__initfunc( +static void nvram_doBit(ncr_slot *np, u_char *read_bit, u_char write_bit, u_char *gpreg) +) +{ + nvram_setBit(np, write_bit, gpreg, SET_BIT); + nvram_setBit(np, 0, gpreg, SET_CLK); + if (read_bit) + *read_bit = INB (nc_gpreg); + nvram_setBit(np, 0, gpreg, CLR_CLK); + nvram_setBit(np, 0, gpreg, CLR_BIT); +} + +/* + * Send STOP condition to NVRAM - puts NVRAM to sleep... ZZzzzz!! + */ +__initfunc( +static void nvram_stop(ncr_slot *np, u_char *gpreg) +) +{ + nvram_setBit(np, 0, gpreg, SET_CLK); + nvram_setBit(np, 1, gpreg, SET_BIT); +} + +/* + * Set/clear data/clock bit in GPIO0 + */ +__initfunc( +static void nvram_setBit(ncr_slot *np, u_char write_bit, u_char *gpreg, int bit_mode) +) +{ + DELAY(5); + switch (bit_mode){ + case SET_BIT: + *gpreg |= write_bit; + break; + case CLR_BIT: + *gpreg &= 0xfe; + break; + case SET_CLK: + *gpreg |= 0x02; + break; + case CLR_CLK: + *gpreg &= 0xfd; + break; + + } + OUTB (nc_gpreg, *gpreg); + DELAY(5); +} + +#undef SET_BIT 0 +#undef CLR_BIT 1 +#undef SET_CLK 2 +#undef CLR_CLK 3 + + +/* --------------------------------------------------------------------- +** +** Try reading Tekram format nvram +** +** --------------------------------------------------------------------- +** +** GPOI0 - data in +** GPIO1 - data out +** GPIO2 - clock +** GPIO4 - chip select +** +** return 0 if NVRAM data OK, 1 if NVRAM data not OK +** --------------------------------------------------------------------- +*/ + +static u_short Tnvram_read_data(ncr_slot *np, u_short *data, int len, u_char *gpreg); +static void Tnvram_Send_Command(ncr_slot *np, u_short write_data, u_char *read_bit, u_char *gpreg); +static void Tnvram_Read_Word(ncr_slot *np, u_short *nvram_data, u_char *gpreg); +static void Tnvram_Read_Bit(ncr_slot *np, u_char *read_bit, u_char *gpreg); +static void Tnvram_Write_Bit(ncr_slot *np, u_char write_bit, u_char *gpreg); +static void Tnvram_Stop(ncr_slot *np, u_char *gpreg); +static void Tnvram_Clk(ncr_slot *np, u_char *gpreg); + +__initfunc( +static int ncr_get_Tekram_nvram (ncr_slot *np, Tekram_nvram *nvram) +) +{ + u_char gpcntl, gpreg; + u_char old_gpcntl, old_gpreg; + u_short csum; + + /* save current state of GPCNTL and GPREG */ + old_gpreg = INB (nc_gpreg); + old_gpcntl = INB (nc_gpcntl); + + /* set up GPREG & GPCNTL to set GPIO0/1/2/4 in to known state, 0 in, + 1/2/4 out */ + gpreg = old_gpreg & 0xe9; + OUTB (nc_gpreg, gpreg); + gpcntl = (old_gpcntl & 0xe9) | 0x09; + OUTB (nc_gpcntl, gpcntl); + + /* input all of NVRAM, 64 words */ + csum = Tnvram_read_data(np, (u_short *) nvram, + sizeof(*nvram) / sizeof(short), &gpreg); + + /* return GPIO0/1/2/4 to original states after having accessed NVRAM */ + OUTB (nc_gpcntl, old_gpcntl); + OUTB (nc_gpreg, old_gpreg); + + /* check data valid */ + if (csum != 0x1234) + return 1; + + return 0; +} + +/* + * Read Tekram NvRAM data and compute checksum. + */ +__initfunc( +static u_short Tnvram_read_data(ncr_slot *np, u_short *data, int len, u_char *gpreg) +) +{ + u_char read_bit; + u_short csum; + int x; + + for (x = 0, csum = 0; x < len; x++) { + + /* output read command and address */ + Tnvram_Send_Command(np, 0x180 | x, &read_bit, gpreg); + if (read_bit & 0x01) + return 0; /* Force bad checksum */ + + Tnvram_Read_Word(np, &data[x], gpreg); + csum += data[x]; + + Tnvram_Stop(np, gpreg); + } + + return csum; +} + +/* + * Send read command and address to NVRAM + */ +__initfunc( +static void Tnvram_Send_Command(ncr_slot *np, u_short write_data, u_char *read_bit, u_char *gpreg) +) +{ + int x; + + /* send 9 bits, start bit (1), command (2), address (6) */ + for (x = 0; x < 9; x++) + Tnvram_Write_Bit(np, (u_char) (write_data >> (8 - x)), gpreg); + + *read_bit = INB (nc_gpreg); +} + +/* + * READ a byte from the NVRAM + */ +__initfunc( +static void Tnvram_Read_Word(ncr_slot *np, u_short *nvram_data, u_char *gpreg) +) +{ + int x; + u_char read_bit; + + *nvram_data = 0; + for (x = 0; x < 16; x++) { + Tnvram_Read_Bit(np, &read_bit, gpreg); + + if (read_bit & 0x01) + *nvram_data |= (0x01 << (15 - x)); + else + *nvram_data &= ~(0x01 << (15 - x)); + } +} + +/* + * Read bit from NVRAM + */ +__initfunc( +static void Tnvram_Read_Bit(ncr_slot *np, u_char *read_bit, u_char *gpreg) +) +{ + DELAY(2); + Tnvram_Clk(np, gpreg); + *read_bit = INB (nc_gpreg); +} + +/* + * Write bit to GPIO0 + */ +__initfunc( +static void Tnvram_Write_Bit(ncr_slot *np, u_char write_bit, u_char *gpreg) +) +{ + if (write_bit & 0x01) + *gpreg |= 0x02; + else + *gpreg &= 0xfd; + + *gpreg |= 0x10; + + OUTB (nc_gpreg, *gpreg); + DELAY(2); + + Tnvram_Clk(np, gpreg); +} + +/* + * Send STOP condition to NVRAM - puts NVRAM to sleep... ZZZzzz!! + */ +__initfunc( +static void Tnvram_Stop(ncr_slot *np, u_char *gpreg) +) +{ + *gpreg &= 0xef; + OUTB (nc_gpreg, *gpreg); + DELAY(2); + + Tnvram_Clk(np, gpreg); +} + +/* + * Pulse clock bit in GPIO0 + */ +__initfunc( +static void Tnvram_Clk(ncr_slot *np, u_char *gpreg) +) +{ + OUTB (nc_gpreg, *gpreg | 0x04); + DELAY(2); + OUTB (nc_gpreg, *gpreg); +} + +#endif /* SCSI_NCR_NVRAM_SUPPORT */ + +/* +** Module stuff +*/ + +#ifdef MODULE +Scsi_Host_Template driver_template = NCR53C8XX; +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/ncr53c8xx.h b/linux/src/drivers/scsi/ncr53c8xx.h new file mode 100644 index 00000000..cc009ca6 --- /dev/null +++ b/linux/src/drivers/scsi/ncr53c8xx.h @@ -0,0 +1,1220 @@ +/****************************************************************************** +** Device driver for the PCI-SCSI NCR538XX controller family. +** +** Copyright (C) 1994 Wolfgang Stanglmeier +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +** +**----------------------------------------------------------------------------- +** +** This driver has been ported to Linux from the FreeBSD NCR53C8XX driver +** and is currently maintained by +** +** Gerard Roudier <groudier@club-internet.fr> +** +** Being given that this driver originates from the FreeBSD version, and +** in order to keep synergy on both, any suggested enhancements and corrections +** received on Linux are automatically a potential candidate for the FreeBSD +** version. +** +** The original driver has been written for 386bsd and FreeBSD by +** Wolfgang Stanglmeier <wolf@cologne.de> +** Stefan Esser <se@mi.Uni-Koeln.de> +** +** And has been ported to NetBSD by +** Charles M. Hannum <mycroft@gnu.ai.mit.edu> +** +******************************************************************************* +*/ + +#ifndef NCR53C8XX_H +#define NCR53C8XX_H + +/* +** Name and revision of the driver +*/ +#define SCSI_NCR_DRIVER_NAME "ncr53c8xx - revision 2.5f.1" + +/* +** Check supported Linux versions +*/ + +#if !defined(LINUX_VERSION_CODE) +#include <linux/version.h> +#endif +#include <linux/config.h> + +/* +** During make dep of linux-1.2.13, LINUX_VERSION_CODE is undefined +** Under linux-1.3.X, all seems to be OK. +** So, we have only to define it under 1.2.13 +*/ + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) + +#if !defined(LINUX_VERSION_CODE) +#define LINUX_VERSION_CODE LinuxVersionCode(1,2,13) +#endif + +/* +** Normal IO or memory mapped IO. +** +** Memory mapped IO only works with linux-1.3.X +** If your motherboard does not work with memory mapped IO, +** define SCSI_NCR_IOMAPPED for PATCHLEVEL 3 too. +*/ + +#if LINUX_VERSION_CODE < LinuxVersionCode(1,3,0) +# define SCSI_NCR_IOMAPPED +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) +# define SCSI_NCR_PROC_INFO_SUPPORT +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,72) +# define SCSI_NCR_SHARE_IRQ +#endif + +/* +** If you want a driver as small as possible, donnot define the +** following options. +*/ + +#define SCSI_NCR_BOOT_COMMAND_LINE_SUPPORT +#define SCSI_NCR_DEBUG_INFO_SUPPORT +#define SCSI_NCR_PCI_FIX_UP_SUPPORT +#ifdef SCSI_NCR_PROC_INFO_SUPPORT +# define SCSI_NCR_PROFILE_SUPPORT +# define SCSI_NCR_USER_COMMAND_SUPPORT +# define SCSI_NCR_USER_INFO_SUPPORT +/* # define SCSI_NCR_DEBUG_ERROR_RECOVERY_SUPPORT */ +#endif + +/*========================================================== +** +** nvram settings - #define SCSI_NCR_NVRAM_SUPPORT to enable +** +**========================================================== +*/ + +#ifdef CONFIG_SCSI_NCR53C8XX_NVRAM_DETECT +#define SCSI_NCR_NVRAM_SUPPORT +/* #define SCSI_NCR_DEBUG_NVRAM */ +#endif + +/* --------------------------------------------------------------------- +** Take into account kernel configured parameters. +** Most of these options can be overridden at startup by a command line. +** --------------------------------------------------------------------- +*/ + +/* + * For Ultra2 SCSI support option, use special features and allow 40Mhz + * synchronous data transfers. + */ +#define SCSI_NCR_SETUP_SPECIAL_FEATURES (3) +#define SCSI_NCR_SETUP_ULTRA_SCSI (2) +#define SCSI_NCR_MAX_SYNC (40) + +/* + * Allow tags from 2 to 12, default 4 + */ +#ifdef CONFIG_SCSI_NCR53C8XX_MAX_TAGS +#if CONFIG_SCSI_NCR53C8XX_MAX_TAGS < 2 +#define SCSI_NCR_MAX_TAGS (2) +#elif CONFIG_SCSI_NCR53C8XX_MAX_TAGS > 12 +#define SCSI_NCR_MAX_TAGS (12) +#else +#define SCSI_NCR_MAX_TAGS CONFIG_SCSI_NCR53C8XX_MAX_TAGS +#endif +#else +#define SCSI_NCR_MAX_TAGS (4) +#endif + +/* + * Allow tagged command queuing support if configured with default number + * of tags set to max (see above). + */ +#ifdef CONFIG_SCSI_NCR53C8XX_TAGGED_QUEUE +#define SCSI_NCR_SETUP_DEFAULT_TAGS SCSI_NCR_MAX_TAGS +#else +#define SCSI_NCR_SETUP_DEFAULT_TAGS (0) +#endif + +/* + * Use normal IO if configured. Forced for alpha and ppc. + */ +#if defined(CONFIG_SCSI_NCR53C8XX_IOMAPPED) +#define SCSI_NCR_IOMAPPED +#elif defined(__alpha__) || defined(__powerpc__) +#define SCSI_NCR_IOMAPPED +#endif + +/* + * Sync transfer frequency at startup. + * Allow from 5Mhz to 40Mhz default 10 Mhz. + */ +#ifndef CONFIG_SCSI_NCR53C8XX_SYNC +#define CONFIG_SCSI_NCR53C8XX_SYNC (5) +#elif CONFIG_SCSI_NCR53C8XX_SYNC > SCSI_NCR_MAX_SYNC +#define SCSI_NCR_SETUP_DEFAULT_SYNC SCSI_NCR_MAX_SYNC +#endif + +#if CONFIG_SCSI_NCR53C8XX_SYNC == 0 +#define SCSI_NCR_SETUP_DEFAULT_SYNC (255) +#elif CONFIG_SCSI_NCR53C8XX_SYNC <= 5 +#define SCSI_NCR_SETUP_DEFAULT_SYNC (50) +#elif CONFIG_SCSI_NCR53C8XX_SYNC <= 20 +#define SCSI_NCR_SETUP_DEFAULT_SYNC (250/(CONFIG_SCSI_NCR53C8XX_SYNC)) +#elif CONFIG_SCSI_NCR53C8XX_SYNC <= 33 +#define SCSI_NCR_SETUP_DEFAULT_SYNC (11) +#else +#define SCSI_NCR_SETUP_DEFAULT_SYNC (10) +#endif + +/* + * Disallow disconnections at boot-up + */ +#ifdef CONFIG_SCSI_NCR53C8XX_NO_DISCONNECT +#define SCSI_NCR_SETUP_DISCONNECTION (0) +#else +#define SCSI_NCR_SETUP_DISCONNECTION (1) +#endif + +/* + * Force synchronous negotiation for all targets + */ +#ifdef CONFIG_SCSI_NCR53C8XX_FORCE_SYNC_NEGO +#define SCSI_NCR_SETUP_FORCE_SYNC_NEGO (1) +#else +#define SCSI_NCR_SETUP_FORCE_SYNC_NEGO (0) +#endif + +/* + * Disable master parity checking (flawed hardwares need that) + */ +#ifdef CONFIG_SCSI_NCR53C8XX_DISABLE_MPARITY_CHECK +#define SCSI_NCR_SETUP_MASTER_PARITY (0) +#else +#define SCSI_NCR_SETUP_MASTER_PARITY (1) +#endif + +/* + * Disable scsi parity checking (flawed devices may need that) + */ +#ifdef CONFIG_SCSI_NCR53C8XX_DISABLE_PARITY_CHECK +#define SCSI_NCR_SETUP_SCSI_PARITY (0) +#else +#define SCSI_NCR_SETUP_SCSI_PARITY (1) +#endif + +/* + * Vendor specific stuff + */ +#ifdef CONFIG_SCSI_NCR53C8XX_SYMBIOS_COMPAT +#define SCSI_NCR_SETUP_LED_PIN (1) +#define SCSI_NCR_SETUP_DIFF_SUPPORT (3) +#else +#define SCSI_NCR_SETUP_LED_PIN (0) +#define SCSI_NCR_SETUP_DIFF_SUPPORT (0) +#endif + +/* + * Settle time after reset at boot-up + */ +#define SCSI_NCR_SETUP_SETTLE_TIME (2) + +/* +** Other parameters not configurable with "make config" +** Avoid to change these constants, unless you know what you are doing. +*/ + +#define SCSI_NCR_ALWAYS_SIMPLE_TAG +#define SCSI_NCR_MAX_SCATTER (127) +#define SCSI_NCR_MAX_TARGET (16) +#define SCSI_NCR_MAX_HOST (2) +#define SCSI_NCR_TIMEOUT_ALERT (3*HZ) + +#define SCSI_NCR_CAN_QUEUE (7*SCSI_NCR_MAX_TAGS) +#define SCSI_NCR_CMD_PER_LUN (SCSI_NCR_MAX_TAGS) +#define SCSI_NCR_SG_TABLESIZE (SCSI_NCR_MAX_SCATTER) + +#define SCSI_NCR_TIMER_INTERVAL ((HZ+5-1)/5) + +#if 1 /* defined CONFIG_SCSI_MULTI_LUN */ +#define SCSI_NCR_MAX_LUN (8) +#else +#define SCSI_NCR_MAX_LUN (1) +#endif + +/* +** Define Scsi_Host_Template parameters +** +** Used by hosts.c and ncr53c8xx.c with module configuration. +*/ + +#if defined(HOSTS_C) || defined(MODULE) + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,98) +#include <scsi/scsicam.h> +#else +#include <linux/scsicam.h> +#endif + +int ncr53c8xx_abort(Scsi_Cmnd *); +int ncr53c8xx_detect(Scsi_Host_Template *tpnt); +int ncr53c8xx_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,98) +int ncr53c8xx_reset(Scsi_Cmnd *, unsigned int); +#else +int ncr53c8xx_reset(Scsi_Cmnd *); +#endif + +#ifdef MODULE +int ncr53c8xx_release(struct Scsi_Host *); +#else +#define ncr53c8xx_release NULL +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,75) + +#define NCR53C8XX { name: SCSI_NCR_DRIVER_NAME, \ + detect: ncr53c8xx_detect, \ + release: ncr53c8xx_release, \ + queuecommand: ncr53c8xx_queue_command,\ + abort: ncr53c8xx_abort, \ + reset: ncr53c8xx_reset, \ + bios_param: scsicam_bios_param, \ + can_queue: SCSI_NCR_CAN_QUEUE, \ + this_id: 7, \ + sg_tablesize: SCSI_NCR_SG_TABLESIZE, \ + cmd_per_lun: SCSI_NCR_CMD_PER_LUN, \ + use_clustering: DISABLE_CLUSTERING} + +#elif LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) + +#define NCR53C8XX { NULL, NULL, NULL, NULL, \ + SCSI_NCR_DRIVER_NAME, ncr53c8xx_detect, \ + ncr53c8xx_release, NULL, NULL, \ + ncr53c8xx_queue_command,ncr53c8xx_abort, \ + ncr53c8xx_reset, NULL, scsicam_bios_param, \ + SCSI_NCR_CAN_QUEUE, 7, \ + SCSI_NCR_SG_TABLESIZE, SCSI_NCR_CMD_PER_LUN, \ + 0, 0, DISABLE_CLUSTERING} + +#else + +#define NCR53C8XX { NULL, NULL, \ + SCSI_NCR_DRIVER_NAME, ncr53c8xx_detect, \ + ncr53c8xx_release, NULL, NULL, \ + ncr53c8xx_queue_command,ncr53c8xx_abort, \ + ncr53c8xx_reset, NULL, scsicam_bios_param, \ + SCSI_NCR_CAN_QUEUE, 7, \ + SCSI_NCR_SG_TABLESIZE, SCSI_NCR_CMD_PER_LUN, \ + 0, 0, DISABLE_CLUSTERING} + +#endif /* LINUX_VERSION_CODE */ + +#endif /* defined(HOSTS_C) || defined(MODULE) */ + + +#ifndef HOSTS_C + +/* +** IO functions definition for big/little endian support. +** For now, the NCR is only supported in little endian addressing mode, +** and big endian byte ordering is only supported for the PPC. +** MMIO is not used on PPC. +*/ + +#ifdef __BIG_ENDIAN + +#if LINUX_VERSION_CODE < LinuxVersionCode(2,1,0) +#error "BIG ENDIAN byte ordering needs kernel version >= 2.1.0" +#endif + +#ifdef __powerpc__ +#define inw_l2b inw +#define inl_l2b inl +#define outw_b2l outw +#define outl_b2l outl +#else +#error "Support for BIG ENDIAN is only available for the PowerPC" +#endif + +#else /* Assumed x86 or alpha */ + +#define inw_raw inw +#define inl_raw inl +#define outw_raw outw +#define outl_raw outl +#define readw_raw readw +#define readl_raw readl +#define writew_raw writew +#define writel_raw writel + +#endif + +#ifdef SCSI_NCR_BIG_ENDIAN +#error "The NCR in BIG ENDIAN adressing mode is not (yet) supported" +#endif + +/* +** NCR53C8XX Device Ids +*/ + +#ifndef PCI_DEVICE_ID_NCR_53C810 +#define PCI_DEVICE_ID_NCR_53C810 1 +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C810AP +#define PCI_DEVICE_ID_NCR_53C810AP 5 +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C815 +#define PCI_DEVICE_ID_NCR_53C815 4 +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C820 +#define PCI_DEVICE_ID_NCR_53C820 2 +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C825 +#define PCI_DEVICE_ID_NCR_53C825 3 +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C860 +#define PCI_DEVICE_ID_NCR_53C860 6 +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C875 +#define PCI_DEVICE_ID_NCR_53C875 0xf +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C875J +#define PCI_DEVICE_ID_NCR_53C875J 0x8f +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C885 +#define PCI_DEVICE_ID_NCR_53C885 0xd +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C895 +#define PCI_DEVICE_ID_NCR_53C895 0xc +#endif + +#ifndef PCI_DEVICE_ID_NCR_53C896 +#define PCI_DEVICE_ID_NCR_53C896 0xb +#endif + +/* +** NCR53C8XX devices features table. +*/ +typedef struct { + unsigned short device_id; + unsigned short revision_id; + char *name; + unsigned char burst_max; + unsigned char offset_max; + unsigned char nr_divisor; + unsigned int features; +#define FE_LED0 (1<<0) +#define FE_WIDE (1<<1) +#define FE_ULTRA (1<<2) +#define FE_ULTRA2 (1<<3) +#define FE_DBLR (1<<4) +#define FE_QUAD (1<<5) +#define FE_ERL (1<<6) +#define FE_CLSE (1<<7) +#define FE_WRIE (1<<8) +#define FE_ERMP (1<<9) +#define FE_BOF (1<<10) +#define FE_DFS (1<<11) +#define FE_PFEN (1<<12) +#define FE_LDSTR (1<<13) +#define FE_RAM (1<<14) +#define FE_CLK80 (1<<15) +#define FE_CACHE_SET (FE_ERL|FE_CLSE|FE_WRIE|FE_ERMP) +#define FE_SCSI_SET (FE_WIDE|FE_ULTRA|FE_ULTRA2|FE_DBLR|FE_QUAD|F_CLK80) +#define FE_SPECIAL_SET (FE_CACHE_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM) +} ncr_chip; + +/* +** DEL 397 - 53C875 Rev 3 - Part Number 609-0392410 - ITEM 3. +** Memory Read transaction terminated by a retry followed by +** Memory Read Line command. +*/ +#define FE_CACHE0_SET (FE_CACHE_SET & ~FE_ERL) + +/* +** DEL 397 - 53C875 Rev 3 - Part Number 609-0392410 - ITEM 5. +** On paper, this errata is harmless. But it is a good reason for +** using a shorter programmed burst length (64 DWORDS instead of 128). +*/ + +#define SCSI_NCR_CHIP_TABLE \ +{ \ + {PCI_DEVICE_ID_NCR_53C810, 0x0f, "810", 4, 8, 4, \ + FE_ERL} \ + , \ + {PCI_DEVICE_ID_NCR_53C810, 0xff, "810a", 4, 8, 4, \ + FE_CACHE_SET|FE_LDSTR|FE_PFEN|FE_BOF} \ + , \ + {PCI_DEVICE_ID_NCR_53C815, 0xff, "815", 4, 8, 4, \ + FE_ERL|FE_BOF} \ + , \ + {PCI_DEVICE_ID_NCR_53C820, 0xff, "820", 4, 8, 4, \ + FE_WIDE|FE_ERL} \ + , \ + {PCI_DEVICE_ID_NCR_53C825, 0x0f, "825", 4, 8, 4, \ + FE_WIDE|FE_ERL|FE_BOF} \ + , \ + {PCI_DEVICE_ID_NCR_53C825, 0xff, "825a", 6, 8, 4, \ + FE_WIDE|FE_CACHE0_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM} \ + , \ + {PCI_DEVICE_ID_NCR_53C860, 0xff, "860", 4, 8, 5, \ + FE_ULTRA|FE_CLK80|FE_CACHE_SET|FE_BOF|FE_LDSTR|FE_PFEN} \ + , \ + {PCI_DEVICE_ID_NCR_53C875, 0x01, "875", 6, 16, 5, \ + FE_WIDE|FE_ULTRA|FE_CLK80|FE_CACHE0_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM}\ + , \ + {PCI_DEVICE_ID_NCR_53C875, 0xff, "875", 6, 16, 5, \ + FE_WIDE|FE_ULTRA|FE_DBLR|FE_CACHE0_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM}\ + , \ + {PCI_DEVICE_ID_NCR_53C875J,0xff, "875J", 6, 16, 5, \ + FE_WIDE|FE_ULTRA|FE_DBLR|FE_CACHE0_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM}\ + , \ + {PCI_DEVICE_ID_NCR_53C885, 0xff, "885", 6, 16, 5, \ + FE_WIDE|FE_ULTRA|FE_DBLR|FE_CACHE0_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM}\ + , \ + {PCI_DEVICE_ID_NCR_53C895, 0xff, "895", 7, 31, 7, \ + FE_WIDE|FE_ULTRA2|FE_QUAD|FE_CACHE_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM}\ + , \ + {PCI_DEVICE_ID_NCR_53C896, 0xff, "896", 7, 31, 7, \ + FE_WIDE|FE_ULTRA2|FE_QUAD|FE_CACHE_SET|FE_BOF|FE_DFS|FE_LDSTR|FE_PFEN|FE_RAM}\ +} + +/* + * List of supported NCR chip ids + */ +#define SCSI_NCR_CHIP_IDS \ +{ \ + PCI_DEVICE_ID_NCR_53C810, \ + PCI_DEVICE_ID_NCR_53C815, \ + PCI_DEVICE_ID_NCR_53C820, \ + PCI_DEVICE_ID_NCR_53C825, \ + PCI_DEVICE_ID_NCR_53C860, \ + PCI_DEVICE_ID_NCR_53C875, \ + PCI_DEVICE_ID_NCR_53C875J, \ + PCI_DEVICE_ID_NCR_53C885, \ + PCI_DEVICE_ID_NCR_53C895, \ + PCI_DEVICE_ID_NCR_53C896 \ +} + +/* +** Initial setup. +** Can be overriden at startup by a command line. +*/ +#define SCSI_NCR_DRIVER_SETUP \ +{ \ + SCSI_NCR_SETUP_MASTER_PARITY, \ + SCSI_NCR_SETUP_SCSI_PARITY, \ + SCSI_NCR_SETUP_DISCONNECTION, \ + SCSI_NCR_SETUP_SPECIAL_FEATURES, \ + SCSI_NCR_SETUP_ULTRA_SCSI, \ + SCSI_NCR_SETUP_FORCE_SYNC_NEGO, \ + 0, \ + 0, \ + 1, \ + 1, \ + SCSI_NCR_SETUP_DEFAULT_TAGS, \ + SCSI_NCR_SETUP_DEFAULT_SYNC, \ + 0x00, \ + 7, \ + SCSI_NCR_SETUP_LED_PIN, \ + 1, \ + SCSI_NCR_SETUP_SETTLE_TIME, \ + SCSI_NCR_SETUP_DIFF_SUPPORT, \ + 0, \ + 1 \ +} + +/* +** Boot fail safe setup. +** Override initial setup from boot command line: +** ncr53c8xx=safe:y +*/ +#define SCSI_NCR_DRIVER_SAFE_SETUP \ +{ \ + 0, \ + 1, \ + 0, \ + 0, \ + 0, \ + 0, \ + 0, \ + 0, \ + 1, \ + 2, \ + 0, \ + 255, \ + 0x00, \ + 255, \ + 0, \ + 0, \ + 10, \ + 1, \ + 1, \ + 1 \ +} + +/* +** Define the table of target capabilities by host and target +** +** If you have problems with a scsi device, note the host unit and the +** corresponding target number. +** +** Edit the corresponding entry of the table below and try successively: +** NQ7_Questionnable +** NQ7_IdeLike +** +** This bitmap is anded with the byte 7 of inquiry data on completion of +** INQUIRY command. +** The driver never see the zeroed bits and will ignore the corresponding +** capabilities of the target. +*/ + +#define INQ7_SftRe 1 +#define INQ7_CmdQueue (1<<1) /* Tagged Command */ +#define INQ7_Reserved (1<<2) +#define INQ7_Linked (1<<3) +#define INQ7_Sync (1<<4) /* Synchronous Negotiation */ +#define INQ7_WBus16 (1<<5) +#define INQ7_WBus32 (1<<6) +#define INQ7_RelAdr (1<<7) + +#define INQ7_IdeLike 0 +#define INQ7_Scsi1Like INQ7_IdeLike +#define INQ7_Perfect 0xff +#define INQ7_Questionnable ~(INQ7_CmdQueue|INQ7_Sync) +#define INQ7_VeryQuestionnable \ + ~(INQ7_CmdQueue|INQ7_Sync|INQ7_WBus16|INQ7_WBus32) + +#define INQ7_Default INQ7_Perfect + +#define NCR53C8XX_TARGET_CAPABILITIES \ +/* Host 0 */ \ +{ \ + { \ + /* Target 0 */ INQ7_Default, \ + /* Target 1 */ INQ7_Default, \ + /* Target 2 */ INQ7_Default, \ + /* Target 3 */ INQ7_Default, \ + /* Target 4 */ INQ7_Default, \ + /* Target 5 */ INQ7_Default, \ + /* Target 6 */ INQ7_Default, \ + /* Target 7 */ INQ7_Default, \ + /* Target 8 */ INQ7_Default, \ + /* Target 9 */ INQ7_Default, \ + /* Target 10 */ INQ7_Default, \ + /* Target 11 */ INQ7_Default, \ + /* Target 12 */ INQ7_Default, \ + /* Target 13 */ INQ7_Default, \ + /* Target 14 */ INQ7_Default, \ + /* Target 15 */ INQ7_Default, \ + } \ +}, \ +/* Host 1 */ \ +{ \ + { \ + /* Target 0 */ INQ7_Default, \ + /* Target 1 */ INQ7_Default, \ + /* Target 2 */ INQ7_Default, \ + /* Target 3 */ INQ7_Default, \ + /* Target 4 */ INQ7_Default, \ + /* Target 5 */ INQ7_Default, \ + /* Target 6 */ INQ7_Default, \ + /* Target 7 */ INQ7_Default, \ + /* Target 8 */ INQ7_Default, \ + /* Target 9 */ INQ7_Default, \ + /* Target 10 */ INQ7_Default, \ + /* Target 11 */ INQ7_Default, \ + /* Target 12 */ INQ7_Default, \ + /* Target 13 */ INQ7_Default, \ + /* Target 14 */ INQ7_Default, \ + /* Target 15 */ INQ7_Default, \ + } \ +} + +/* +** Replace the proc_dir_entry of the standard ncr driver. +*/ + +#if LINUX_VERSION_CODE >= LinuxVersionCode(1,3,0) +#if defined(CONFIG_SCSI_NCR53C7xx) || !defined(CONFIG_SCSI_NCR53C8XX) +#define PROC_SCSI_NCR53C8XX PROC_SCSI_NCR53C7xx +#endif +#endif + +/**************** ORIGINAL CONTENT of ncrreg.h from FreeBSD ******************/ + +/*----------------------------------------------------------------- +** +** The ncr 53c810 register structure. +** +**----------------------------------------------------------------- +*/ + +struct ncr_reg { +/*00*/ u_char nc_scntl0; /* full arb., ena parity, par->ATN */ + +/*01*/ u_char nc_scntl1; /* no reset */ + #define ISCON 0x10 /* connected to scsi */ + #define CRST 0x08 /* force reset */ + +/*02*/ u_char nc_scntl2; /* no disconnect expected */ + #define SDU 0x80 /* cmd: disconnect will raise error */ + #define CHM 0x40 /* sta: chained mode */ + #define WSS 0x08 /* sta: wide scsi send [W]*/ + #define WSR 0x01 /* sta: wide scsi received [W]*/ + +/*03*/ u_char nc_scntl3; /* cnf system clock dependent */ + #define EWS 0x08 /* cmd: enable wide scsi [W]*/ + #define ULTRA 0x80 /* cmd: ULTRA enable */ + +/*04*/ u_char nc_scid; /* cnf host adapter scsi address */ + #define RRE 0x40 /* r/w:e enable response to resel. */ + #define SRE 0x20 /* r/w:e enable response to select */ + +/*05*/ u_char nc_sxfer; /* ### Sync speed and count */ + +/*06*/ u_char nc_sdid; /* ### Destination-ID */ + +/*07*/ u_char nc_gpreg; /* ??? IO-Pins */ + +/*08*/ u_char nc_sfbr; /* ### First byte in phase */ + +/*09*/ u_char nc_socl; + #define CREQ 0x80 /* r/w: SCSI-REQ */ + #define CACK 0x40 /* r/w: SCSI-ACK */ + #define CBSY 0x20 /* r/w: SCSI-BSY */ + #define CSEL 0x10 /* r/w: SCSI-SEL */ + #define CATN 0x08 /* r/w: SCSI-ATN */ + #define CMSG 0x04 /* r/w: SCSI-MSG */ + #define CC_D 0x02 /* r/w: SCSI-C_D */ + #define CI_O 0x01 /* r/w: SCSI-I_O */ + +/*0a*/ u_char nc_ssid; + +/*0b*/ u_char nc_sbcl; + +/*0c*/ u_char nc_dstat; + #define DFE 0x80 /* sta: dma fifo empty */ + #define MDPE 0x40 /* int: master data parity error */ + #define BF 0x20 /* int: script: bus fault */ + #define ABRT 0x10 /* int: script: command aborted */ + #define SSI 0x08 /* int: script: single step */ + #define SIR 0x04 /* int: script: interrupt instruct. */ + #define IID 0x01 /* int: script: illegal instruct. */ + +/*0d*/ u_char nc_sstat0; + #define ILF 0x80 /* sta: data in SIDL register lsb */ + #define ORF 0x40 /* sta: data in SODR register lsb */ + #define OLF 0x20 /* sta: data in SODL register lsb */ + #define AIP 0x10 /* sta: arbitration in progress */ + #define LOA 0x08 /* sta: arbitration lost */ + #define WOA 0x04 /* sta: arbitration won */ + #define IRST 0x02 /* sta: scsi reset signal */ + #define SDP 0x01 /* sta: scsi parity signal */ + +/*0e*/ u_char nc_sstat1; + #define FF3210 0xf0 /* sta: bytes in the scsi fifo */ + +/*0f*/ u_char nc_sstat2; + #define ILF1 0x80 /* sta: data in SIDL register msb[W]*/ + #define ORF1 0x40 /* sta: data in SODR register msb[W]*/ + #define OLF1 0x20 /* sta: data in SODL register msb[W]*/ + #define DM 0x04 /* sta: DIFFSENS mismatch (895/6 only) */ + #define LDSC 0x02 /* sta: disconnect & reconnect */ + +/*10*/ u_int32 nc_dsa; /* --> Base page */ + +/*14*/ u_char nc_istat; /* --> Main Command and status */ + #define CABRT 0x80 /* cmd: abort current operation */ + #define SRST 0x40 /* mod: reset chip */ + #define SIGP 0x20 /* r/w: message from host to ncr */ + #define SEM 0x10 /* r/w: message between host + ncr */ + #define CON 0x08 /* sta: connected to scsi */ + #define INTF 0x04 /* sta: int on the fly (reset by wr)*/ + #define SIP 0x02 /* sta: scsi-interrupt */ + #define DIP 0x01 /* sta: host/script interrupt */ + +/*15*/ u_char nc_15_; +/*16*/ u_char nc_16_; +/*17*/ u_char nc_17_; + +/*18*/ u_char nc_ctest0; +/*19*/ u_char nc_ctest1; + +/*1a*/ u_char nc_ctest2; + #define CSIGP 0x40 + +/*1b*/ u_char nc_ctest3; + #define FLF 0x08 /* cmd: flush dma fifo */ + #define CLF 0x04 /* cmd: clear dma fifo */ + #define FM 0x02 /* mod: fetch pin mode */ + #define WRIE 0x01 /* mod: write and invalidate enable */ + +/*1c*/ u_int32 nc_temp; /* ### Temporary stack */ + +/*20*/ u_char nc_dfifo; +/*21*/ u_char nc_ctest4; + #define BDIS 0x80 /* mod: burst disable */ + #define MPEE 0x08 /* mod: master parity error enable */ + +/*22*/ u_char nc_ctest5; + #define DFS 0x20 /* mod: dma fifo size */ +/*23*/ u_char nc_ctest6; + +/*24*/ u_int32 nc_dbc; /* ### Byte count and command */ +/*28*/ u_int32 nc_dnad; /* ### Next command register */ +/*2c*/ u_int32 nc_dsp; /* --> Script Pointer */ +/*30*/ u_int32 nc_dsps; /* --> Script pointer save/opcode#2 */ +/*34*/ u_int32 nc_scratcha; /* ??? Temporary register a */ + +/*38*/ u_char nc_dmode; + #define BL_2 0x80 /* mod: burst length shift value +2 */ + #define BL_1 0x40 /* mod: burst length shift value +1 */ + #define ERL 0x08 /* mod: enable read line */ + #define ERMP 0x04 /* mod: enable read multiple */ + #define BOF 0x02 /* mod: burst op code fetch */ + +/*39*/ u_char nc_dien; +/*3a*/ u_char nc_dwt; + +/*3b*/ u_char nc_dcntl; /* --> Script execution control */ + + #define CLSE 0x80 /* mod: cache line size enable */ + #define PFF 0x40 /* cmd: pre-fetch flush */ + #define PFEN 0x20 /* mod: pre-fetch enable */ + #define SSM 0x10 /* mod: single step mode */ + #define IRQM 0x08 /* mod: irq mode (1 = totem pole !) */ + #define STD 0x04 /* cmd: start dma mode */ + #define IRQD 0x02 /* mod: irq disable */ + #define NOCOM 0x01 /* cmd: protect sfbr while reselect */ + +/*3c*/ u_int32 nc_adder; + +/*40*/ u_short nc_sien; /* -->: interrupt enable */ +/*42*/ u_short nc_sist; /* <--: interrupt status */ + #define SBMC 0x1000/* sta: SCSI Bus Mode Change (895/6 only) */ + #define STO 0x0400/* sta: timeout (select) */ + #define GEN 0x0200/* sta: timeout (general) */ + #define HTH 0x0100/* sta: timeout (handshake) */ + #define MA 0x80 /* sta: phase mismatch */ + #define CMP 0x40 /* sta: arbitration complete */ + #define SEL 0x20 /* sta: selected by another device */ + #define RSL 0x10 /* sta: reselected by another device*/ + #define SGE 0x08 /* sta: gross error (over/underflow)*/ + #define UDC 0x04 /* sta: unexpected disconnect */ + #define RST 0x02 /* sta: scsi bus reset detected */ + #define PAR 0x01 /* sta: scsi parity error */ + +/*44*/ u_char nc_slpar; +/*45*/ u_char nc_swide; +/*46*/ u_char nc_macntl; +/*47*/ u_char nc_gpcntl; +/*48*/ u_char nc_stime0; /* cmd: timeout for select&handshake*/ +/*49*/ u_char nc_stime1; /* cmd: timeout user defined */ +/*4a*/ u_short nc_respid; /* sta: Reselect-IDs */ + +/*4c*/ u_char nc_stest0; + +/*4d*/ u_char nc_stest1; + #define DBLEN 0x08 /* clock doubler running */ + #define DBLSEL 0x04 /* clock doubler selected */ + + +/*4e*/ u_char nc_stest2; + #define ROF 0x40 /* reset scsi offset (after gross error!) */ + #define EXT 0x02 /* extended filtering */ + +/*4f*/ u_char nc_stest3; + #define TE 0x80 /* c: tolerAnt enable */ + #define HSC 0x20 /* c: Halt SCSI Clock */ + #define CSF 0x02 /* c: clear scsi fifo */ + +/*50*/ u_short nc_sidl; /* Lowlevel: latched from scsi data */ +/*52*/ u_char nc_stest4; + #define SMODE 0xc0 /* SCSI bus mode (895/6 only) */ + #define SMODE_HVD 0x40 /* High Voltage Differential */ + #define SMODE_SE 0x80 /* Single Ended */ + #define SMODE_LVD 0xc0 /* Low Voltage Differential */ + #define LCKFRQ 0x20 /* Frequency Lock (895/6 only) */ + +/*53*/ u_char nc_53_; +/*54*/ u_short nc_sodl; /* Lowlevel: data out to scsi data */ +/*56*/ u_short nc_56_; +/*58*/ u_short nc_sbdl; /* Lowlevel: data from scsi data */ +/*5a*/ u_short nc_5a_; +/*5c*/ u_char nc_scr0; /* Working register B */ +/*5d*/ u_char nc_scr1; /* */ +/*5e*/ u_char nc_scr2; /* */ +/*5f*/ u_char nc_scr3; /* */ +/*60*/ +}; + +/*----------------------------------------------------------- +** +** Utility macros for the script. +** +**----------------------------------------------------------- +*/ + +#define REGJ(p,r) (offsetof(struct ncr_reg, p ## r)) +#define REG(r) REGJ (nc_, r) + +#ifndef TARGET_MODE +#define TARGET_MODE 0 +#endif + +typedef u_int32 ncrcmd; + +/*----------------------------------------------------------- +** +** SCSI phases +** +**----------------------------------------------------------- +*/ + +#define SCR_DATA_OUT 0x00000000 +#define SCR_DATA_IN 0x01000000 +#define SCR_COMMAND 0x02000000 +#define SCR_STATUS 0x03000000 +#define SCR_ILG_OUT 0x04000000 +#define SCR_ILG_IN 0x05000000 +#define SCR_MSG_OUT 0x06000000 +#define SCR_MSG_IN 0x07000000 + +/*----------------------------------------------------------- +** +** Data transfer via SCSI. +** +**----------------------------------------------------------- +** +** MOVE_ABS (LEN) +** <<start address>> +** +** MOVE_IND (LEN) +** <<dnad_offset>> +** +** MOVE_TBL +** <<dnad_offset>> +** +**----------------------------------------------------------- +*/ + +#define SCR_MOVE_ABS(l) ((0x08000000 ^ (TARGET_MODE << 1ul)) | (l)) +#define SCR_MOVE_IND(l) ((0x28000000 ^ (TARGET_MODE << 1ul)) | (l)) +#define SCR_MOVE_TBL (0x18000000 ^ (TARGET_MODE << 1ul)) + +struct scr_tblmove { + u_int32 size; + u_int32 addr; +}; + +/*----------------------------------------------------------- +** +** Selection +** +**----------------------------------------------------------- +** +** SEL_ABS | SCR_ID (0..7) [ | REL_JMP] +** <<alternate_address>> +** +** SEL_TBL | << dnad_offset>> [ | REL_JMP] +** <<alternate_address>> +** +**----------------------------------------------------------- +*/ + +#define SCR_SEL_ABS 0x40000000 +#define SCR_SEL_ABS_ATN 0x41000000 +#define SCR_SEL_TBL 0x42000000 +#define SCR_SEL_TBL_ATN 0x43000000 + +struct scr_tblsel { + u_char sel_0; + u_char sel_sxfer; + u_char sel_id; + u_char sel_scntl3; +}; + +#define SCR_JMP_REL 0x04000000 +#define SCR_ID(id) (((u_int32)(id)) << 16) + +/*----------------------------------------------------------- +** +** Waiting for Disconnect or Reselect +** +**----------------------------------------------------------- +** +** WAIT_DISC +** dummy: <<alternate_address>> +** +** WAIT_RESEL +** <<alternate_address>> +** +**----------------------------------------------------------- +*/ + +#define SCR_WAIT_DISC 0x48000000 +#define SCR_WAIT_RESEL 0x50000000 + +/*----------------------------------------------------------- +** +** Bit Set / Reset +** +**----------------------------------------------------------- +** +** SET (flags {|.. }) +** +** CLR (flags {|.. }) +** +**----------------------------------------------------------- +*/ + +#define SCR_SET(f) (0x58000000 | (f)) +#define SCR_CLR(f) (0x60000000 | (f)) + +#define SCR_CARRY 0x00000400 +#define SCR_TRG 0x00000200 +#define SCR_ACK 0x00000040 +#define SCR_ATN 0x00000008 + + + + +/*----------------------------------------------------------- +** +** Memory to memory move +** +**----------------------------------------------------------- +** +** COPY (bytecount) +** << source_address >> +** << destination_address >> +** +** SCR_COPY sets the NO FLUSH option by default. +** SCR_COPY_F does not set this option. +** +** For chips which do not support this option, +** ncr_copy_and_bind() will remove this bit. +**----------------------------------------------------------- +*/ + +#define SCR_NO_FLUSH 0x01000000 + +#define SCR_COPY(n) (0xc0000000 | SCR_NO_FLUSH | (n)) +#define SCR_COPY_F(n) (0xc0000000 | (n)) + +/*----------------------------------------------------------- +** +** Register move and binary operations +** +**----------------------------------------------------------- +** +** SFBR_REG (reg, op, data) reg = SFBR op data +** << 0 >> +** +** REG_SFBR (reg, op, data) SFBR = reg op data +** << 0 >> +** +** REG_REG (reg, op, data) reg = reg op data +** << 0 >> +** +**----------------------------------------------------------- +*/ + +#define SCR_REG_OFS(ofs) ((ofs) << 16ul) + +#define SCR_SFBR_REG(reg,op,data) \ + (0x68000000 | (SCR_REG_OFS(REG(reg))) | (op) | ((data)<<8ul)) + +#define SCR_REG_SFBR(reg,op,data) \ + (0x70000000 | (SCR_REG_OFS(REG(reg))) | (op) | ((data)<<8ul)) + +#define SCR_REG_REG(reg,op,data) \ + (0x78000000 | (SCR_REG_OFS(REG(reg))) | (op) | ((data)<<8ul)) + + +#define SCR_LOAD 0x00000000 +#define SCR_SHL 0x01000000 +#define SCR_OR 0x02000000 +#define SCR_XOR 0x03000000 +#define SCR_AND 0x04000000 +#define SCR_SHR 0x05000000 +#define SCR_ADD 0x06000000 +#define SCR_ADDC 0x07000000 + +/*----------------------------------------------------------- +** +** FROM_REG (reg) reg = SFBR +** << 0 >> +** +** TO_REG (reg) SFBR = reg +** << 0 >> +** +** LOAD_REG (reg, data) reg = <data> +** << 0 >> +** +** LOAD_SFBR(data) SFBR = <data> +** << 0 >> +** +**----------------------------------------------------------- +*/ + +#define SCR_FROM_REG(reg) \ + SCR_REG_SFBR(reg,SCR_OR,0) + +#define SCR_TO_REG(reg) \ + SCR_SFBR_REG(reg,SCR_OR,0) + +#define SCR_LOAD_REG(reg,data) \ + SCR_REG_REG(reg,SCR_LOAD,data) + +#define SCR_LOAD_SFBR(data) \ + (SCR_REG_SFBR (gpreg, SCR_LOAD, data)) + +/*----------------------------------------------------------- +** +** Waiting for Disconnect or Reselect +** +**----------------------------------------------------------- +** +** JUMP [ | IFTRUE/IFFALSE ( ... ) ] +** <<address>> +** +** JUMPR [ | IFTRUE/IFFALSE ( ... ) ] +** <<distance>> +** +** CALL [ | IFTRUE/IFFALSE ( ... ) ] +** <<address>> +** +** CALLR [ | IFTRUE/IFFALSE ( ... ) ] +** <<distance>> +** +** RETURN [ | IFTRUE/IFFALSE ( ... ) ] +** <<dummy>> +** +** INT [ | IFTRUE/IFFALSE ( ... ) ] +** <<ident>> +** +** INT_FLY [ | IFTRUE/IFFALSE ( ... ) ] +** <<ident>> +** +** Conditions: +** WHEN (phase) +** IF (phase) +** CARRY +** DATA (data, mask) +** +**----------------------------------------------------------- +*/ + +#define SCR_NO_OP 0x80000000 +#define SCR_JUMP 0x80080000 +#define SCR_JUMPR 0x80880000 +#define SCR_CALL 0x88080000 +#define SCR_CALLR 0x88880000 +#define SCR_RETURN 0x90080000 +#define SCR_INT 0x98080000 +#define SCR_INT_FLY 0x98180000 + +#define IFFALSE(arg) (0x00080000 | (arg)) +#define IFTRUE(arg) (0x00000000 | (arg)) + +#define WHEN(phase) (0x00030000 | (phase)) +#define IF(phase) (0x00020000 | (phase)) + +#define DATA(D) (0x00040000 | ((D) & 0xff)) +#define MASK(D,M) (0x00040000 | (((M ^ 0xff) & 0xff) << 8ul)|((D) & 0xff)) + +#define CARRYSET (0x00200000) + +/*----------------------------------------------------------- +** +** SCSI constants. +** +**----------------------------------------------------------- +*/ + +/* +** Messages +*/ + +#define M_COMPLETE (0x00) +#define M_EXTENDED (0x01) +#define M_SAVE_DP (0x02) +#define M_RESTORE_DP (0x03) +#define M_DISCONNECT (0x04) +#define M_ID_ERROR (0x05) +#define M_ABORT (0x06) +#define M_REJECT (0x07) +#define M_NOOP (0x08) +#define M_PARITY (0x09) +#define M_LCOMPLETE (0x0a) +#define M_FCOMPLETE (0x0b) +#define M_RESET (0x0c) +#define M_ABORT_TAG (0x0d) +#define M_CLEAR_QUEUE (0x0e) +#define M_INIT_REC (0x0f) +#define M_REL_REC (0x10) +#define M_TERMINATE (0x11) +#define M_SIMPLE_TAG (0x20) +#define M_HEAD_TAG (0x21) +#define M_ORDERED_TAG (0x22) +#define M_IGN_RESIDUE (0x23) +#define M_IDENTIFY (0x80) + +#define M_X_MODIFY_DP (0x00) +#define M_X_SYNC_REQ (0x01) +#define M_X_WIDE_REQ (0x03) + +/* +** Status +*/ + +#define S_GOOD (0x00) +#define S_CHECK_COND (0x02) +#define S_COND_MET (0x04) +#define S_BUSY (0x08) +#define S_INT (0x10) +#define S_INT_COND_MET (0x14) +#define S_CONFLICT (0x18) +#define S_TERMINATED (0x20) +#define S_QUEUE_FULL (0x28) +#define S_ILLEGAL (0xff) +#define S_SENSE (0x80) + +/* + * End of ncrreg from FreeBSD + */ + +#endif /* !defined HOSTS_C */ + +#endif /* defined NCR53C8XX_H */ diff --git a/linux/src/drivers/scsi/pas16.c b/linux/src/drivers/scsi/pas16.c new file mode 100644 index 00000000..bd964204 --- /dev/null +++ b/linux/src/drivers/scsi/pas16.c @@ -0,0 +1,576 @@ +#define AUTOSENSE +#define PSEUDO_DMA +#define FOO +#define UNSAFE /* Not unsafe for PAS16 -- use it */ + +/* + * This driver adapted from Drew Eckhardt's Trantor T128 driver + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 666-5836 + * + * ( Based on T128 - DISTRIBUTION RELEASE 3. ) + * + * Modified to work with the Pro Audio Spectrum/Studio 16 + * by John Weidman. + * + * + * For more information, please consult + * + * Media Vision + * (510) 770-8600 + * (800) 348-7116 + * + * and + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * Options : + * AUTOSENSE - if defined, REQUEST SENSE will be performed automatically + * for commands that return with a CHECK CONDITION status. + * + * LIMIT_TRANSFERSIZE - if defined, limit the pseudo-dma transfers to 512 + * bytes at a time. Since interrupts are disabled by default during + * these transfers, we might need this to give reasonable interrupt + * service time if the transfer size gets too large. + * + * PSEUDO_DMA - enables PSEUDO-DMA hardware, should give a 3-4X performance + * increase compared to polled I/O. + * + * PARITY - enable parity checking. Not supported. + * + * SCSI2 - enable support for SCSI-II tagged queueing. Untested. + * + * UNSAFE - leave interrupts enabled during pseudo-DMA transfers. This + * parameter comes from the NCR5380 code. It is NOT unsafe with + * the PAS16 and you should use it. If you don't you will have + * a problem with dropped characters during high speed + * communications during SCSI transfers. If you really don't + * want to use UNSAFE you can try defining LIMIT_TRANSFERSIZE or + * twiddle with the transfer size in the high level code. + * + * USLEEP - enable support for devices that don't disconnect. Untested. + * + * The card is detected and initialized in one of several ways : + * 1. Autoprobe (default) - There are many different models of + * the Pro Audio Spectrum/Studio 16, and I only have one of + * them, so this may require a little tweaking. An interrupt + * is triggered to autoprobe for the interrupt line. Note: + * with the newer model boards, the interrupt is set via + * software after reset using the default_irq for the + * current board number. + * + * + * 2. With command line overrides - pas16=port,irq may be + * used on the LILO command line to override the defaults. + * + * 3. With the PAS16_OVERRIDE compile time define. This is + * specified as an array of address, irq tuples. Ie, for + * one board at the default 0x388 address, IRQ10, I could say + * -DPAS16_OVERRIDE={{0x388, 10}} + * NOTE: Untested. + * + * Note that if the override methods are used, place holders must + * be specified for other boards in the system. + * + * + * Configuration notes : + * The current driver does not support interrupt sharing with the + * sound portion of the card. If you use the same irq for the + * scsi port and sound you will have problems. Either use + * a different irq for the scsi port or don't use interrupts + * for the scsi port. + * + * If you have problems with your card not being recognized, use + * the LILO command line override. Try to get it recognized without + * interrupts. Ie, for a board at the default 0x388 base port, + * boot: linux pas16=0x388,255 + * + * (255 is the IRQ_NONE constant in NCR5380.h) + */ + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <asm/system.h> +#include <linux/signal.h> +#include <linux/proc_fs.h> +#include <linux/sched.h> +#include <asm/io.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "pas16.h" +#define AUTOPROBE_IRQ +#include "NCR5380.h" +#include "constants.h" +#include "sd.h" + +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_pas16 = { + PROC_SCSI_PAS16, 5, "pas16", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; +static int pas_maxi = 0; +static int pas_wmaxi = 0; + + +int scsi_irq_translate[] = + { 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 7, 8, 9, 0, 10, 11 }; + +/* The default_irqs array contains values used to set the irq into the + * board via software (as must be done on newer model boards without + * irq jumpers on the board). The first value in the array will be + * assigned to logical board 0, the next to board 1, etc. + */ +int default_irqs[] = { PAS16_DEFAULT_BOARD_1_IRQ, + PAS16_DEFAULT_BOARD_2_IRQ, + PAS16_DEFAULT_BOARD_3_IRQ, + PAS16_DEFAULT_BOARD_4_IRQ + }; + +static struct override { + unsigned short io_port; + int irq; +} overrides +#ifdef PAS16_OVERRIDE + [] = PAS16_OVERRIDE; +#else + [4] = {{0,IRQ_AUTO}, {0,IRQ_AUTO}, {0,IRQ_AUTO}, + {0,IRQ_AUTO}}; +#endif + +#define NO_OVERRIDES (sizeof(overrides) / sizeof(struct override)) + +static struct base { + unsigned short io_port; + int noauto; +} bases[] = { {PAS16_DEFAULT_BASE_1, 0}, + {PAS16_DEFAULT_BASE_2, 0}, + {PAS16_DEFAULT_BASE_3, 0}, + {PAS16_DEFAULT_BASE_4, 0} + }; + +#define NO_BASES (sizeof (bases) / sizeof (struct base)) + +unsigned short pas16_offset[ 8 ] = + { + 0x1c00, /* OUTPUT_DATA_REG */ + 0x1c01, /* INITIATOR_COMMAND_REG */ + 0x1c02, /* MODE_REG */ + 0x1c03, /* TARGET_COMMAND_REG */ + 0x3c00, /* STATUS_REG ro, SELECT_ENABLE_REG wo */ + 0x3c01, /* BUS_AND_STATUS_REG ro, START_DMA_SEND_REG wo */ + 0x3c02, /* INPUT_DATA_REGISTER ro, (N/A on PAS16 ?) + * START_DMA_TARGET_RECEIVE_REG wo + */ + 0x3c03, /* RESET_PARITY_INTERRUPT_REG ro, + * START_DMA_INITIATOR_RECEIVE_REG wo + */ + }; +/*----------------------------------------------------------------*/ +/* the following will set the monitor border color (useful to find + where something crashed or gets stuck at */ +/* 1 = blue + 2 = green + 3 = cyan + 4 = red + 5 = magenta + 6 = yellow + 7 = white +*/ +#if 1 +#define rtrc(i) {inb(0x3da); outb(0x31, 0x3c0); outb((i), 0x3c0);} +#else +#define rtrc(i) {} +#endif + + +/* + * Function : enable_board( int board_num, unsigned short port ) + * + * Purpose : set address in new model board + * + * Inputs : board_num - logical board number 0-3, port - base address + * + */ + +void enable_board( int board_num, unsigned short port ) +{ + outb( 0xbc + board_num, MASTER_ADDRESS_PTR ); + outb( port >> 2, MASTER_ADDRESS_PTR ); +} + + + +/* + * Function : init_board( unsigned short port, int irq ) + * + * Purpose : Set the board up to handle the SCSI interface + * + * Inputs : port - base address of the board, + * irq - irq to assign to the SCSI port + * force_irq - set it even if it conflicts with sound driver + * + */ + +void init_board( unsigned short io_port, int irq, int force_irq ) +{ + unsigned int tmp; + unsigned int pas_irq_code; + + /* Initialize the SCSI part of the board */ + + outb( 0x30, io_port + P_TIMEOUT_COUNTER_REG ); /* Timeout counter */ + outb( 0x01, io_port + P_TIMEOUT_STATUS_REG_OFFSET ); /* Reset TC */ + outb( 0x01, io_port + WAIT_STATE ); /* 1 Wait state */ + + NCR5380_read( RESET_PARITY_INTERRUPT_REG ); + + /* Set the SCSI interrupt pointer without mucking up the sound + * interrupt pointer in the same byte. + */ + pas_irq_code = ( irq < 16 ) ? scsi_irq_translate[irq] : 0; + tmp = inb( io_port + IO_CONFIG_3 ); + + if( (( tmp & 0x0f ) == pas_irq_code) && pas_irq_code > 0 + && !force_irq ) + { + printk( "pas16: WARNING: Can't use same irq as sound " + "driver -- interrupts disabled\n" ); + /* Set up the drive parameters, disable 5380 interrupts */ + outb( 0x4d, io_port + SYS_CONFIG_4 ); + } + else + { + tmp = ( tmp & 0x0f ) | ( pas_irq_code << 4 ); + outb( tmp, io_port + IO_CONFIG_3 ); + + /* Set up the drive parameters and enable 5380 interrupts */ + outb( 0x6d, io_port + SYS_CONFIG_4 ); + } +} + + +/* + * Function : pas16_hw_detect( unsigned short board_num ) + * + * Purpose : determine if a pas16 board is present + * + * Inputs : board_num - logical board number ( 0 - 3 ) + * + * Returns : 0 if board not found, 1 if found. + */ + +int pas16_hw_detect( unsigned short board_num ) +{ + unsigned char board_rev, tmp; + unsigned short io_port = bases[ board_num ].io_port; + + /* See if we can find a PAS16 board at the address associated + * with this logical board number. + */ + + /* First, attempt to take a newer model board out of reset and + * give it a base address. This shouldn't affect older boards. + */ + enable_board( board_num, io_port ); + + /* Now see if it looks like a PAS16 board */ + board_rev = inb( io_port + PCB_CONFIG ); + + if( board_rev == 0xff ) + return 0; + + tmp = board_rev ^ 0xe0; + + outb( tmp, io_port + PCB_CONFIG ); + tmp = inb( io_port + PCB_CONFIG ); + outb( board_rev, io_port + PCB_CONFIG ); + + if( board_rev != tmp ) /* Not a PAS-16 */ + return 0; + + if( ( inb( io_port + OPERATION_MODE_1 ) & 0x03 ) != 0x03 ) + return 0; /* return if no SCSI interface found */ + + /* Mediavision has some new model boards that return ID bits + * that indicate a SCSI interface, but they're not (LMS). We'll + * put in an additional test to try to weed them out. + */ + + outb( 0x01, io_port + WAIT_STATE ); /* 1 Wait state */ + NCR5380_write( MODE_REG, 0x20 ); /* Is it really SCSI? */ + if( NCR5380_read( MODE_REG ) != 0x20 ) /* Write to a reg. */ + return 0; /* and try to read */ + NCR5380_write( MODE_REG, 0x00 ); /* it back. */ + if( NCR5380_read( MODE_REG ) != 0x00 ) + return 0; + + return 1; +} + + +/* + * Function : pas16_setup(char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + * + */ + +void pas16_setup(char *str, int *ints) { + static int commandline_current = 0; + int i; + if (ints[0] != 2) + printk("pas16_setup : usage pas16=io_port,irq\n"); + else + if (commandline_current < NO_OVERRIDES) { + overrides[commandline_current].io_port = (unsigned short) ints[1]; + overrides[commandline_current].irq = ints[2]; + for (i = 0; i < NO_BASES; ++i) + if (bases[i].io_port == (unsigned short) ints[1]) { + bases[i].noauto = 1; + break; + } + ++commandline_current; + } +} + +/* + * Function : int pas16_detect(Scsi_Host_Template * tpnt) + * + * Purpose : detects and initializes PAS16 controllers + * that were autoprobed, overridden on the LILO command line, + * or specified at compile time. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * + */ + +int pas16_detect(Scsi_Host_Template * tpnt) { + static int current_override = 0; + static unsigned short current_base = 0; + struct Scsi_Host *instance; + unsigned short io_port; + int count; + + tpnt->proc_dir = &proc_scsi_pas16; + tpnt->proc_info = &pas16_proc_info; + + for (count = 0; current_override < NO_OVERRIDES; ++current_override) { + io_port = 0; + + if (overrides[current_override].io_port) + { + io_port = overrides[current_override].io_port; + enable_board( current_override, io_port ); + init_board( io_port, overrides[current_override].irq, 1 ); + } + else + for (; !io_port && (current_base < NO_BASES); ++current_base) { +#if (PDEBUG & PDEBUG_INIT) + printk("scsi-pas16 : probing io_port %04x\n", (unsigned int) bases[current_base].io_port); +#endif + if ( !bases[current_base].noauto && + pas16_hw_detect( current_base ) ){ + io_port = bases[current_base].io_port; + init_board( io_port, default_irqs[ current_base ], 0 ); +#if (PDEBUG & PDEBUG_INIT) + printk("scsi-pas16 : detected board.\n"); +#endif + } + } + + +#if defined(PDEBUG) && (PDEBUG & PDEBUG_INIT) + printk("scsi-pas16 : io_port = %04x\n", (unsigned int) io_port); +#endif + + if (!io_port) + break; + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->io_port = io_port; + + NCR5380_init(instance, 0); + + if (overrides[current_override].irq != IRQ_AUTO) + instance->irq = overrides[current_override].irq; + else + instance->irq = NCR5380_probe_irq(instance, PAS16_IRQS); + + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, pas16_intr, SA_INTERRUPT, "pas16", NULL)) { + printk("scsi%d : IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq == IRQ_NONE) { + printk("scsi%d : interrupts not enabled. for better interactive performance,\n", instance->host_no); + printk("scsi%d : please jumper the board for a free IRQ.\n", instance->host_no); + /* Disable 5380 interrupts, leave drive params the same */ + outb( 0x4d, io_port + SYS_CONFIG_4 ); + outb( (inb(io_port + IO_CONFIG_3) & 0x0f), io_port + IO_CONFIG_3 ); + } + +#if defined(PDEBUG) && (PDEBUG & PDEBUG_INIT) + printk("scsi%d : irq = %d\n", instance->host_no, instance->irq); +#endif + + printk("scsi%d : at 0x%04x", instance->host_no, (int) + instance->io_port); + if (instance->irq == IRQ_NONE) + printk (" interrupts disabled"); + else + printk (" irq %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, PAS16_PUBLIC_RELEASE); + NCR5380_print_options(instance); + printk("\n"); + + ++current_override; + ++count; + } + return count; +} + +/* + * Function : int pas16_biosparam(Disk *disk, kdev_t dev, int *ip) + * + * Purpose : Generates a BIOS / DOS compatible H-C-S mapping for + * the specified device / size. + * + * Inputs : size = size of device in sectors (512 bytes), dev = block device + * major / minor, ip[] = {heads, sectors, cylinders} + * + * Returns : always 0 (success), initializes ip + * + */ + +/* + * XXX Most SCSI boards use this mapping, I could be incorrect. Some one + * using hard disks on a trantor should verify that this mapping corresponds + * to that used by the BIOS / ASPI driver by running the linux fdisk program + * and matching the H_C_S coordinates to what DOS uses. + */ + +int pas16_biosparam(Disk * disk, kdev_t dev, int * ip) +{ + int size = disk->capacity; + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; /* I think I have it as /(32*64) */ + if( ip[2] > 1024 ) { /* yes, >, not >= */ + ip[0]=255; + ip[1]=63; + ip[2]=size/(63*255); + if( ip[2] > 1023 ) /* yes >1023... */ + ip[2] = 1023; + } + + return 0; +} + +/* + * Function : int NCR5380_pread (struct Scsi_Host *instance, + * unsigned char *dst, int len) + * + * Purpose : Fast 5380 pseudo-dma read function, transfers len bytes to + * dst + * + * Inputs : dst = destination, len = length in bytes + * + * Returns : 0 on success, non zero on a failure such as a watchdog + * timeout. + */ + +static inline int NCR5380_pread (struct Scsi_Host *instance, unsigned char *dst, + int len) { + register unsigned char *d = dst; + register unsigned short reg = (unsigned short) (instance->io_port + + P_DATA_REG_OFFSET); + register int i = len; + int ii = 0; + + while ( !(inb(instance->io_port + P_STATUS_REG_OFFSET) & P_ST_RDY) ) + ++ii; + + insb( reg, d, i ); + + if ( inb(instance->io_port + P_TIMEOUT_STATUS_REG_OFFSET) & P_TS_TIM) { + outb( P_TS_CT, instance->io_port + P_TIMEOUT_STATUS_REG_OFFSET); + printk("scsi%d : watchdog timer fired in NCR5380_pread()\n", + instance->host_no); + return -1; + } + if (ii > pas_maxi) + pas_maxi = ii; + return 0; +} + +/* + * Function : int NCR5380_pwrite (struct Scsi_Host *instance, + * unsigned char *src, int len) + * + * Purpose : Fast 5380 pseudo-dma write function, transfers len bytes from + * src + * + * Inputs : src = source, len = length in bytes + * + * Returns : 0 on success, non zero on a failure such as a watchdog + * timeout. + */ + +static inline int NCR5380_pwrite (struct Scsi_Host *instance, unsigned char *src, + int len) { + register unsigned char *s = src; + register unsigned short reg = (instance->io_port + P_DATA_REG_OFFSET); + register int i = len; + int ii = 0; + + while ( !((inb(instance->io_port + P_STATUS_REG_OFFSET)) & P_ST_RDY) ) + ++ii; + + outsb( reg, s, i ); + + if (inb(instance->io_port + P_TIMEOUT_STATUS_REG_OFFSET) & P_TS_TIM) { + outb( P_TS_CT, instance->io_port + P_TIMEOUT_STATUS_REG_OFFSET); + printk("scsi%d : watchdog timer fired in NCR5380_pwrite()\n", + instance->host_no); + return -1; + } + if (ii > pas_maxi) + pas_wmaxi = ii; + return 0; +} + +#include "NCR5380.c" + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = MV_PAS16; + +#include <linux/module.h> +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/pas16.h b/linux/src/drivers/scsi/pas16.h new file mode 100644 index 00000000..a1bda1fa --- /dev/null +++ b/linux/src/drivers/scsi/pas16.h @@ -0,0 +1,196 @@ +/* + * This driver adapted from Drew Eckhardt's Trantor T128 driver + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 666-5836 + * + * ( Based on T128 - DISTRIBUTION RELEASE 3. ) + * + * Modified to work with the Pro Audio Spectrum/Studio 16 + * by John Weidman. + * + * + * For more information, please consult + * + * Media Vision + * (510) 770-8600 + * (800) 348-7116 + * + * and + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + + +#ifndef PAS16_H +#define PAS16_H + +#define PAS16_PUBLIC_RELEASE 3 + +#define PDEBUG_INIT 0x1 +#define PDEBUG_TRANSFER 0x2 + +#define PAS16_DEFAULT_BASE_1 0x388 +#define PAS16_DEFAULT_BASE_2 0x384 +#define PAS16_DEFAULT_BASE_3 0x38c +#define PAS16_DEFAULT_BASE_4 0x288 + +#define PAS16_DEFAULT_BOARD_1_IRQ 10 +#define PAS16_DEFAULT_BOARD_2_IRQ 12 +#define PAS16_DEFAULT_BOARD_3_IRQ 14 +#define PAS16_DEFAULT_BOARD_4_IRQ 15 + + +/* + * The Pro Audio Spectrum boards are I/O mapped. They use a Zilog 5380 + * SCSI controller, which is the equivalent of NCR's 5380. "Pseudo-DMA" + * architecture is used, where a PAL drives the DMA signals on the 5380 + * allowing fast, blind transfers with proper handshaking. + */ + + +/* The Time-out Counter register is used to safe-guard against a stuck + * bus (in the case of RDY driven handshake) or a stuck byte (if 16-Bit + * DMA conversion is used). The counter uses a 28.224MHz clock + * divided by 14 as its clock source. In the case of a stuck byte in + * the holding register, an interrupt is generated (and mixed with the + * one with the drive) using the CD-ROM interrupt pointer. + */ + +#define P_TIMEOUT_COUNTER_REG 0x4000 +#define P_TC_DISABLE 0x80 /* Set to 0 to enable timeout int. */ + /* Bits D6-D0 contain timeout count */ + + +#define P_TIMEOUT_STATUS_REG_OFFSET 0x4001 +#define P_TS_TIM 0x80 /* check timeout status */ + /* Bits D6-D4 N/U */ +#define P_TS_ARM_DRQ_INT 0x08 /* Arm DRQ Int. When set high, + * the next rising edge will + * cause a CD-ROM interrupt. + * When set low, the interrupt + * will be cleared. There is + * no status available for + * this interrupt. + */ +#define P_TS_ENABLE_TO_ERR_INTERRUPT /* Enable timeout error int. */ +#define P_TS_ENABLE_WAIT /* Enable Wait */ + +#define P_TS_CT 0x01 /* clear timeout. Note: writing + * to this register clears the + * timeout error int. or status + */ + + +/* + * The data register reads/writes to/from the 5380 in pseudo-DMA mode + */ + +#define P_DATA_REG_OFFSET 0x5c00 /* rw */ + +#define P_STATUS_REG_OFFSET 0x5c01 /* ro */ +#define P_ST_RDY 0x80 /* 5380 DDRQ Status */ + +#define P_IRQ_STATUS 0x5c03 +#define P_IS_IRQ 0x80 /* DIRQ status */ + +#define PCB_CONFIG 0x803 +#define MASTER_ADDRESS_PTR 0x9a01 /* Fixed position - no relo */ +#define SYS_CONFIG_4 0x8003 +#define WAIT_STATE 0xbc00 +#define OPERATION_MODE_1 0xec03 +#define IO_CONFIG_3 0xf002 + + +#ifndef ASM +int pas16_abort(Scsi_Cmnd *); +int pas16_biosparam(Disk *, kdev_t, int*); +int pas16_detect(Scsi_Host_Template *); +int pas16_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int pas16_reset(Scsi_Cmnd *, unsigned int); +int pas16_proc_info (char *buffer ,char **start, off_t offset, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 32 +#endif + +/* + * I hadn't thought of this with the earlier drivers - but to prevent + * macro definition conflicts, we shouldn't define all of the internal + * macros when this is being used solely for the host stub. + */ + +#if defined(HOSTS_C) || defined(MODULE) + +#define MV_PAS16 {NULL, NULL, NULL, NULL, \ + "Pro Audio Spectrum-16 SCSI", \ + pas16_detect, NULL, NULL, \ + NULL, pas16_queue_command, pas16_abort, pas16_reset, NULL, \ + pas16_biosparam, \ + /* can queue */ CAN_QUEUE, /* id */ 7, SG_ALL, \ + /* cmd per lun */ CMD_PER_LUN , 0, 0, DISABLE_CLUSTERING} + +#endif +#ifndef HOSTS_C + +#define NCR5380_implementation_fields \ + volatile unsigned short io_port + +#define NCR5380_local_declare() \ + volatile unsigned short io_port + +#define NCR5380_setup(instance) \ + io_port = (instance)->io_port + +#define PAS16_io_port(reg) ( io_port + pas16_offset[(reg)] ) + +#if !(PDEBUG & PDEBUG_TRANSFER) +#define NCR5380_read(reg) ( inb(PAS16_io_port(reg)) ) +#define NCR5380_write(reg, value) ( outb((value),PAS16_io_port(reg)) ) +#else +#define NCR5380_read(reg) \ + (((unsigned char) printk("scsi%d : read register %d at io_port %04x\n"\ + , instance->hostno, (reg), PAS16_io_port(reg))), inb( PAS16_io_port(reg)) ) + +#define NCR5380_write(reg, value) \ + (printk("scsi%d : write %02x to register %d at io_port %04x\n", \ + instance->hostno, (value), (reg), PAS16_io_port(reg)), \ + outb( (value),PAS16_io_port(reg) ) ) + +#endif + + +#define NCR5380_intr pas16_intr +#define NCR5380_queue_command pas16_queue_command +#define NCR5380_abort pas16_abort +#define NCR5380_reset pas16_reset +#define NCR5380_proc_info pas16_proc_info + +/* 15 14 12 10 7 5 3 + 1101 0100 1010 1000 */ + +#define PAS16_IRQS 0xd4a8 + +#endif /* else def HOSTS_C */ +#endif /* ndef ASM */ +#endif /* PAS16_H */ diff --git a/linux/src/drivers/scsi/ppa.c b/linux/src/drivers/scsi/ppa.c new file mode 100644 index 00000000..98749dd7 --- /dev/null +++ b/linux/src/drivers/scsi/ppa.c @@ -0,0 +1,1550 @@ +/* ppa.c -- low level driver for the IOMEGA PPA3 + * parallel port SCSI host adapter. + * + * (The PPA3 is the embedded controller in the ZIP drive.) + * + * (c) 1995,1996 Grant R. Guenther, grant@torque.net, + * under the terms of the GNU Public License. + * + * Current Maintainer: David Campbell (Perth, Western Australia) + * campbell@gear.torque.net + * dcampbel@p01.as17.honeywell.com.au + * + * My unoffical company acronym list is 21 pages long: + * FLA: Four letter acronym with built in facility for + * future expansion to five letters. + */ + +#include <linux/config.h> + +/* The following #define is to avoid a clash with hosts.c */ +#define PPA_CODE 1 +#ifndef HAVE_PC87332 +#define HAVE_PC87332 0 +#endif +#define PPA_PROBE_SPP 0x0001 +#define PPA_PROBE_PS2 0x0002 +#define PPA_PROBE_ECR 0x0010 +#define PPA_PROBE_EPP17 0x0100 +#define PPA_PROBE_EPP19 0x0200 +int port_probe(unsigned short); + +#include <linux/blk.h> +#include "sd.h" +#include "hosts.h" +typedef struct { + int base; /* Actual port address */ + int mode; /* Transfer mode */ + int host; /* Host number (for proc) */ + Scsi_Cmnd *cur_cmd; /* Current queued command */ + struct tq_struct ppa_tq; /* Polling interupt stuff */ + unsigned long jstart; /* Jiffies at start */ + unsigned failed:1; /* Failure flag */ +} ppa_struct; + +#define PPA_EMPTY \ +{-1, /* base */ \ +PPA_AUTODETECT, /* mode */ \ +-1, /* host */ \ +NULL, /* cur_cmd */ \ +{0, 0, ppa_interrupt, NULL}, \ +0, /* jstart */ \ +0 /* failed */ \ +} + +#include "ppa.h" +#undef CONFIG_PARPORT +#define NO_HOSTS 4 +static ppa_struct ppa_hosts[NO_HOSTS] = +{PPA_EMPTY, PPA_EMPTY, PPA_EMPTY, PPA_EMPTY}; + +#define PPA_BASE(x) ppa_hosts[(x)].base + +int base[NO_HOSTS] = +{0x03bc, 0x0378, 0x0278, 0x0000}; +#define parbus_base base +#define parbus_no NO_HOSTS + +static inline int ppa_pb_claim(int host_no) +{ + if (ppa_hosts[host_no].cur_cmd) + ppa_hosts[host_no].cur_cmd->SCp.phase++; + return 0; +} + +/*************************************************************************** + * Parallel port probing routines * + ***************************************************************************/ + +#ifndef MODULE +/* + * Command line parameters (for built-in driver): + * + * Syntax: ppa=base[,mode[,use_sg]] + * + * For example: ppa=0x378 or ppa=0x378,0,3 + * + */ + +void ppa_setup(char *str, int *ints) +{ + static int x = 0; + + if (x == 0) { /* Disable ALL known ports */ + int i; + + for (i = 0; i < NO_HOSTS; i++) + parbus_base[i] = 0x0000; + } + switch (ints[0]) { + case 3: + ppa_sg = ints[3]; + case 2: + ppa_hosts[x].mode = ints[2]; + parbus_base[x] = ints[1]; + break; + default: + printk("PPA: I only use between 2 to 3 parameters.\n"); + break; + } + x++; + } +#else +Scsi_Host_Template driver_template = PPA; +#include "scsi_module.c" +#endif + +/* + * Start of Chipset kludges + */ + +#if HAVE_PC87332 > 0 +#warning PC87332 Kludge code included +static inline int pc87332_port(int host_no) +{ + /* A routine to detect and kludge pc87332 chipsets into the + * "optimum" mode for parallel port data transfer. + * This assumes EPP is better than ECP... + * (Which it is for disk drives but not printers and scanners) + */ + int base = ppa_hosts[host_no].base; + + /* This is where an pc87332 can hide */ + unsigned short index_addr[4] = + { + 0x0398, 0x026e, 0x015c, 0x002e + }; + + /* Bits 0&1 of FAR (Function Address Register) which specify where + * the LPT port will show up at. + */ + unsigned short port_ref[4] = + { + 0x378, 0x3bc, 0x278, 0xffff + }; + + unsigned char a; + int loop; + + for (loop = 0; loop < 4; loop++) { + /* Clear the "wax" out of the pc87332, only needed after hard + * reset. + */ + inb(index_addr[loop]); + inb(index_addr[loop]); + inb(index_addr[loop]); + inb(index_addr[loop]); + + /* Anyone home ?? */ + outb(0xff, index_addr[loop]); + a = inb(index_addr[loop]); + switch (a) { + case (0x0f): /* PC87732 */ + break; + case (0x1f): /* PC87306 */ + break; + case (0x7f): /* PC87??? */ + break; + default: + continue; + } /* Is this pc87332 on the desired port */ + outb(0x01, index_addr[loop]); + a = inb(index_addr[loop] + 1); + if (port_ref[a & 0x03] != base) + continue; + + /* Found a pc87332 */ + printk("NatSemi PC87332 (or variant) at 0x%04x\n", base); + + /* Try to enable EPP modes + * with hardware data direction + */ + if (base != 0x3bc) { + /* EPP 1.9 */ + outb(0x04, index_addr[loop]); + a = inb(index_addr[loop] + 1); + printk("Old reg1 = %02x\n", a); + /* 0x01 for EPP 1.7, 0x03 for EPP 1.9, 0x0c for ECP */ + a = (a & 0xf0) | 0x03; + outb(a, index_addr[loop] + 1); + outb(a, index_addr[loop] + 1); + + /* Software data direction selection */ + outb(0x02, index_addr[loop]); + a = inb(index_addr[loop] + 1); + printk("Old reg2 = %02x\n", a); + /* 0x80 for software, 0x00 for hardware */ + a = (a & 0x7f) | 0x80; + outb(a, index_addr[loop] + 1); + outb(a, index_addr[loop] + 1); + ppa_hosts[host_no].mode = PPA_EPP_32; + } else { + /* There is not enough address space for the 0x3bc port + * to have EPP registers so we will kludge it into an + * ECP + * port to allow bi-directional byte mode... + */ + /* ECP */ + outb(0x04, index_addr[loop]); + a = inb(index_addr[loop] + 1); + a = (a & 0xfb) | 0x06; + outb(a, index_addr[loop] + 1); + outb(a, index_addr[loop] + 1); + ppa_hosts[host_no].mode = PPA_PS2; + } + + outb(0x04, index_addr[loop]); + a = inb(index_addr[loop] + 1); + return ppa_hosts[host_no].mode; + } + return 0; + } +#else +#define pc87332_port(x) +#endif /* HAVE_PC87332 */ + +static inline int generic_port(int host_no) +{ + /* Generic parallel port detection + * This will try to discover if the port is + * EPP, ECP, PS/2 or NIBBLE (In that order, approx....) + */ + unsigned int save_ctr, save_ecr, r; + int ppb = PPA_BASE(host_no); + + save_ctr = r_ctr(ppb); + save_ecr = r_ecr(ppb); + r = port_probe(ppb); + w_ecr(ppb, save_ecr); + w_ctr(ppb, save_ctr); + + if (r & PPA_PROBE_SPP) + ppa_hosts[host_no].mode = PPA_NIBBLE; + + if (r & PPA_PROBE_PS2) { + ppa_hosts[host_no].mode = PPA_PS2; + if (r & PPA_PROBE_ECR) + w_ecr(ppb, 0x20); + } + if ((r & PPA_PROBE_EPP17) || (r & PPA_PROBE_EPP19)) { + /* ppa_hosts[host_no].mode = PPA_EPP_32; */ + if (r & PPA_PROBE_ECR) + w_ecr(ppb, 0x80); + } + return ppa_hosts[host_no].mode; +} + +int ppa_detect(Scsi_Host_Template * host) +{ + struct Scsi_Host *hreg; + int ports; + int i, nhosts; + unsigned short ppb; + + printk("ppa: Version %s\n", PPA_VERSION); + nhosts = 0; + + for (i = 0; i < parbus_no; i++) { + if (parbus_base[i] == 0x0000) + continue; + ppb = ppa_hosts[i].base = parbus_base[i]; + + /* sanity checks */ + if (check_region(parbus_base[i], + (parbus_base[i] == 0x03bc) ? 3 : 8)) + continue; + + pc87332_port(i); + if (!generic_port(i)) + continue; + + if (ppa_init(i)) + continue; + + /* now the glue ... */ + switch (ppa_hosts[i].mode) { + case PPA_NIBBLE: + case PPA_PS2: + ports = 3; + break; + case PPA_EPP_8: + case PPA_EPP_16: + case PPA_EPP_32: + ports = 8; + break; + default: /* Never gets here */ + continue; + } + request_region(ppa_hosts[i].base, ports, "ppa"); + host->can_queue = PPA_CAN_QUEUE; + host->sg_tablesize = ppa_sg; + hreg = scsi_register(host, 0); + hreg->io_port = ppa_hosts[i].base; + hreg->n_io_port = ports; + hreg->dma_channel = -1; + hreg->unique_id = i; + ppa_hosts[i].host = hreg->host_no; + nhosts++; + } + if (nhosts == 0) + return 0; + else + return 1; /* return number of hosts detected */ +} + +/* This is to give the ppa driver a way to modify the timings (and other + * parameters) by writing to the /proc/scsi/ppa/0 file. + * Very simple method really... (To simple, no error checking :( ) + * Reason: Kernel hackers HATE having to unload and reload modules for + * testing... + * Also gives a method to use a script to obtain optimum timings (TODO) + */ + +static inline int ppa_strncmp(const char *a, const char *b, int len) +{ + int loop; + for (loop = 0; loop < len; loop++) + if (a[loop] != b[loop]) + return 1; + + return 0; +} +static inline int ppa_proc_write(int hostno, char *buffer, int length) +{ + unsigned long x; + + if ((length > 5) && (ppa_strncmp(buffer, "mode=", 5) == 0)) { + x = simple_strtoul(buffer + 5, NULL, 0); + ppa_hosts[hostno].mode = x; + return length; + } + printk("ppa /proc: invalid variable\n"); + return (-EINVAL); +} + +int ppa_proc_info(char *buffer, char **start, off_t offset, + int length, int hostno, int inout) +{ + int i; + int len = 0; + + for (i = 0; i < 4; i++) + if (ppa_hosts[i].host == hostno) + break; + + if (inout) + return ppa_proc_write(i, buffer, length); + + len += sprintf(buffer + len, "Version : %s\n", PPA_VERSION); + len += sprintf(buffer + len, "Port : 0x%04x\n", ppa_hosts[i].base); + len += sprintf(buffer + len, "Mode : %s\n", PPA_MODE_STRING[ppa_hosts[i].mode]); + + /* Request for beyond end of buffer */ + if (offset > len) + return 0; + + *start = buffer + offset; + len -= offset; + if (len > length) + len = length; + return len; +} /* end of ppa.c */ +static int device_check(int host_no); + +#if PPA_DEBUG > 0 +#define ppa_fail(x,y) printk("ppa: ppa_fail(%i) from %s at line %d\n",\ + y, __FUNCTION__, __LINE__); ppa_fail_func(x,y); +static inline void ppa_fail_func(int host_no, int error_code) +#else +static inline void ppa_fail(int host_no, int error_code) + #endif +{ + /* If we fail a device then we trash status / message bytes */ + if (ppa_hosts[host_no].cur_cmd) { + ppa_hosts[host_no].cur_cmd->result = error_code << 16; + ppa_hosts[host_no].failed = 1; + } +} + +/* + * Wait for the high bit to be set. + * + * In principle, this could be tied to an interrupt, but the adapter + * doesn't appear to be designed to support interrupts. We spin on + * the 0x80 ready bit. + */ +static unsigned char ppa_wait(int host_no) +{ + int k; + unsigned short ppb = PPA_BASE(host_no); + unsigned char r; + + k = PPA_SPIN_TMO; + do { + r = r_str(ppb); + k--; + udelay(1); + } + while (!(r & 0x80) && (k)); + + /* + * return some status information. + * Semantics: 0xc0 = ZIP wants more data + * 0xd0 = ZIP wants to send more data + * 0xe0 = ZIP is expecting SCSI command data + * 0xf0 = end of transfer, ZIP is sending status + */ + if (k) + return (r & 0xf0); + + /* Counter expired - Time out occurred */ + ppa_fail(host_no, DID_TIME_OUT); + printk("ppa timeout in ppa_wait\n"); + return 0; /* command timed out */ +} + +/* + * output a string, in whatever mode is available, according to the + * PPA protocol. + */ +static inline void epp_reset(unsigned short ppb) +{ + int i; + + i = r_str(ppb); + w_str(ppb, i); + w_str(ppb, i & 0xfe); +} + +static inline void ecp_sync(unsigned short ppb) +{ + int i; + + if ((r_ecr(ppb) & 0xe0) != 0x80) + return; + + for (i = 0; i < 100; i++) { + if (r_ecr(ppb) & 0x01) + return; + udelay(5); + } + printk("ppa: ECP sync failed as data still present in FIFO.\n"); +} + +/* + * Here is the asm code for the SPP/PS2 protocols for the i386. + * This has been optimised for speed on 386/486 machines. There will + * be very little improvement on the current 586+ machines as it is the + * IO statements which will limit throughput. + */ +#ifdef __i386__ +#define BYTE_OUT(reg) \ + " movb " #reg ",%%al\n" \ + " outb %%al,(%%dx)\n" \ + " addl $2,%%edx\n" \ + " movb $0x0e,%%al\n" \ + " outb %%al,(%%dx)\n" \ + " movb $0x0c,%%al\n" \ + " outb %%al,(%%dx)\n" \ + " subl $2,%%edx\n" + +static inline int ppa_byte_out(unsigned short base, char *buffer, unsigned int len) +{ + /* + * %eax scratch + * %ebx Data to transfer + * %ecx Counter (Don't touch!!) + * %edx Port + * %esi Source buffer (mem pointer) + * + * In case you are wondering what the last line of the asm does... + * <output allocation> : <input allocation> : <trashed registers> + */ + asm("shr $2,%%ecx\n" \ + " jz .no_more_bulk_bo\n" \ + " .align 4\n" \ + ".loop_bulk_bo:\n" \ + " movl (%%esi),%%ebx\n" \ + BYTE_OUT(%%bl) \ + BYTE_OUT(%%bh) \ + " rorl $16,%%ebx\n" \ + BYTE_OUT(%%bl) \ + BYTE_OUT(%%bh) \ + " addl $4,%%esi\n" \ + " loop .loop_bulk_bo\n" \ + " .align 4\n" \ + ".no_more_bulk_bo:" \ + : "=S"(buffer): "c"(len), "d"(base), "S"(buffer):"eax", "ebx", "ecx"); + + asm("andl $3,%%ecx\n" \ + " jz .no_more_loose_bo\n" \ + " .align 4\n" \ + ".loop_loose_bo:\n" \ + BYTE_OUT((%%esi)) \ + " incl %%esi\n" \ + " loop .loop_loose_bo\n" \ + ".no_more_loose_bo:\n" \ + : /* no output */ : "c"(len), "d"(base), "S"(buffer):"eax", "ebx", "ecx"); + return 1; /* All went well - we hope! */ +} + +#define BYTE_IN(reg) \ + " inb (%%dx),%%al\n" \ + " movb %%al," #reg "\n" \ + " addl $2,%%edx\n" \ + " movb $0x27,%%al\n" \ + " outb %%al,(%%dx)\n" \ + " movb $0x25,%%al\n" \ + " outb %%al,(%%dx)\n" \ + " subl $2,%%edx\n" + +static inline int ppa_byte_in(unsigned short base, char *buffer, int len) +{ + /* + * %eax scratch + * %ebx Data to transfer + * %ecx Counter (Don't touch!!) + * %edx Port + * %esi Source buffer (mem pointer) + * + * In case you are wondering what the last line of the asm does... + * <output allocation> : <input allocation> : <trashed registers> + */ + asm("shr $2,%%ecx\n" \ + " jz .no_more_bulk_bi\n" \ + " .align 4\n" \ + ".loop_bulk_bi:\n" \ + BYTE_IN(%%bl) \ + BYTE_IN(%%bh) \ + " rorl $16,%%ebx\n" \ + BYTE_IN(%%bl) \ + BYTE_IN(%%bh) \ + " rorl $16,%%ebx\n" \ + " movl %%ebx,(%%esi)\n" \ + " addl $4,%%esi\n" \ + " loop .loop_bulk_bi\n" \ + " .align 4\n" \ + ".no_more_bulk_bi:" \ + : "=S"(buffer): "c"(len), "d"(base), "S"(buffer):"eax", "ebx", "ecx"); + + asm("andl $3,%%ecx\n" \ + " jz .no_more_loose_bi\n" \ + " .align 4\n" \ + ".loop_loose_bi:\n" \ + BYTE_IN((%%esi)) \ + " incl %%esi\n" \ + " loop .loop_loose_bi\n" \ + ".no_more_loose_bi:\n" \ + : /* no output */ : "c"(len), "d"(base), "S"(buffer):"eax", "ebx", "ecx"); + return 1; /* All went well - we hope! */ +} + +#define NIBBLE_IN(reg) \ + " incl %%edx\n" \ + " movb $0x04,%%al\n" \ + " outb %%al,(%%dx)\n" \ + " decl %%edx\n" \ + " inb (%%dx),%%al\n" \ + " andb $0xf0,%%al\n" \ + " movb %%al," #reg "\n" \ + " incl %%edx\n" \ + " movb $0x06,%%al\n" \ + " outb %%al,(%%dx)\n" \ + " decl %%edx\n" \ + " inb (%%dx),%%al\n" \ + " shrb $4,%%al\n" \ + " orb %%al," #reg "\n" + +static inline int ppa_nibble_in(unsigned short str_p, char *buffer, int len) +{ + /* + * %eax scratch + * %ebx Data to transfer + * %ecx Counter (Don't touch!!) + * %edx Port + * %esi Source buffer (mem pointer) + * + * In case you are wondering what the last line of the asm does... + * <output allocation> : <input allocation> : <trashed registers> + */ + asm("shr $2,%%ecx\n" \ + " jz .no_more_bulk_ni\n" \ + " .align 4\n" \ + ".loop_bulk_ni:\n" \ + NIBBLE_IN(%%bl) \ + NIBBLE_IN(%%bh) \ + " rorl $16,%%ebx\n" \ + NIBBLE_IN(%%bl) \ + NIBBLE_IN(%%bh) \ + " rorl $16,%%ebx\n" \ + " movl %%ebx,(%%esi)\n" \ + " addl $4,%%esi\n" \ + " loop .loop_bulk_ni\n" \ + " .align 4\n" \ + ".no_more_bulk_ni:" \ + : "=S"(buffer): "c"(len), "d"(str_p), "S"(buffer):"eax", "ebx", "ecx"); + + asm("andl $3,%%ecx\n" \ + " jz .no_more_loose_ni\n" \ + " .align 4\n" \ + ".loop_loose_ni:\n" \ + NIBBLE_IN((%%esi)) \ + " incl %%esi\n" \ + " loop .loop_loose_ni\n" \ + ".no_more_loose_ni:\n" \ + : /* no output */ : "c"(len), "d"(str_p), "S"(buffer):"eax", "ebx", "ecx"); + return 1; /* All went well - we hope! */ +} +#else /* Old style C routines */ + +static inline int ppa_byte_out(unsigned short base, const char *buffer, int len) +{ + unsigned short ctr_p = base + 2; + int i; + + for (i = len; i; i--) { + outb(*buffer++, base); + outb(0xe, ctr_p); + outb(0xc, ctr_p); + } + return 1; /* All went well - we hope! */ +} + +static inline int ppa_byte_in(unsigned short base, char *buffer, int len) +{ + unsigned short ctr_p = base + 2; + int i; + + for (i = len; i; i--) { + *buffer++ = inb(base); + outb(0x27, ctr_p); + outb(0x25, ctr_p); + } + return 1; /* All went well - we hope! */ +} + +static inline int ppa_nibble_in(unsigned short str_p, char *buffer, int len) +{ + unsigned short ctr_p = str_p + 1; + unsigned char h, l; + int i; + + for (i = len; i; i--) { + outb(0x4, ctr_p); + h = inb(str_p); + outb(0x6, ctr_p); + l = inb(str_p); + *buffer++ = (h & 0xf0) | ((l & 0xf0) >> 4); + } + return 1; /* All went well - we hope! */ + } + #endif + +static inline int ppa_epp_out(unsigned short epp_p, unsigned short str_p, const char *buffer, int len) +{ + int i; + for (i = len; i; i--) { + outb(*buffer++, epp_p); +#ifdef CONFIG_SCSI_PPA_HAVE_PEDANTIC + if (inb(str_p) & 0x01) + return 0; + #endif + } + return 1; + } + +static int ppa_out(int host_no, char *buffer, int len) +{ + int r; + unsigned short ppb = PPA_BASE(host_no); + + r = ppa_wait(host_no); + + if ((r & 0x50) != 0x40) { + ppa_fail(host_no, DID_ERROR); + return 0; + } + switch (ppa_hosts[host_no].mode) { + case PPA_NIBBLE: + case PPA_PS2: + /* 8 bit output, with a loop */ + r = ppa_byte_out(ppb, buffer, len); + break; + + case PPA_EPP_32: + case PPA_EPP_16: + case PPA_EPP_8: + epp_reset(ppb); + w_ctr(ppb, 0x4); +#ifdef CONFIG_SCSI_PPA_HAVE_PEDANTIC + r = ppa_epp_out(ppb + 4, ppb + 1, buffer, len); +#else + if (!(((long) buffer | len) & 0x03)) + outsl(ppb + 4, buffer, len >> 2); + else + outsb(ppb + 4, buffer, len); + w_ctr(ppb, 0xc); + r = !(r_str(ppb) & 0x01); +#endif + w_ctr(ppb, 0xc); + ecp_sync(ppb); + break; + + default: + printk("PPA: bug in ppa_out()\n"); + r = 0; + } + return r; +} + +static inline int ppa_epp_in(int epp_p, int str_p, char *buffer, int len) +{ + int i; + for (i = len; i; i--) { + *buffer++ = inb(epp_p); +#ifdef CONFIG_SCSI_PPA_HAVE_PEDANTIC + if (inb(str_p) & 0x01) + return 0; +#endif + } + return 1; + } + +static int ppa_in(int host_no, char *buffer, int len) +{ + int r; + unsigned short ppb = PPA_BASE(host_no); + + r = ppa_wait(host_no); + + if ((r & 0x50) != 0x50) { + ppa_fail(host_no, DID_ERROR); + return 0; + } + switch (ppa_hosts[host_no].mode) { + case PPA_NIBBLE: + /* 4 bit input, with a loop */ + r = ppa_nibble_in(ppb + 1, buffer, len); + w_ctr(ppb, 0xc); + break; + + case PPA_PS2: + /* 8 bit input, with a loop */ + w_ctr(ppb, 0x25); + r = ppa_byte_in(ppb, buffer, len); + w_ctr(ppb, 0x4); + w_ctr(ppb, 0xc); + break; + + case PPA_EPP_32: + case PPA_EPP_16: + case PPA_EPP_8: + epp_reset(ppb); + w_ctr(ppb, 0x24); +#ifdef CONFIG_SCSI_PPA_HAVE_PEDANTIC + r = ppa_epp_in(ppb + 4, ppb + 1, buffer, len); + #else + if (!(((long) buffer | len) & 0x03)) + insl(ppb + 4, buffer, len >> 2); + else + insb(ppb + 4, buffer, len); + w_ctr(ppb, 0x2c); + r = !(r_str(ppb) & 0x01); +#endif + w_ctr(ppb, 0x2c); + ecp_sync(ppb); + break; + + default: + printk("PPA: bug in ppa_ins()\n"); + r = 0; + break; + } + return r; +} + +/* end of ppa_io.h */ +static inline void ppa_d_pulse(unsigned short ppb, unsigned char b) +{ + w_dtr(ppb, b); + w_ctr(ppb, 0xc); + w_ctr(ppb, 0xe); + w_ctr(ppb, 0xc); + w_ctr(ppb, 0x4); + w_ctr(ppb, 0xc); +} + +static void ppa_disconnect(int host_no) +{ + unsigned short ppb = PPA_BASE(host_no); + + ppa_d_pulse(ppb, 0); + ppa_d_pulse(ppb, 0x3c); + ppa_d_pulse(ppb, 0x20); + ppa_d_pulse(ppb, 0xf); +} + +static inline void ppa_c_pulse(unsigned short ppb, unsigned char b) +{ + w_dtr(ppb, b); + w_ctr(ppb, 0x4); + w_ctr(ppb, 0x6); + w_ctr(ppb, 0x4); + w_ctr(ppb, 0xc); +} + +static inline void ppa_connect(int host_no, int flag) +{ + unsigned short ppb = PPA_BASE(host_no); + + ppa_c_pulse(ppb, 0); + ppa_c_pulse(ppb, 0x3c); + ppa_c_pulse(ppb, 0x20); + if ((flag == CONNECT_EPP_MAYBE) && + IN_EPP_MODE(ppa_hosts[host_no].mode)) + ppa_c_pulse(ppb, 0xcf); + else + ppa_c_pulse(ppb, 0x8f); +} + +static int ppa_select(int host_no, int target) +{ + int k; + unsigned short ppb = PPA_BASE(host_no); + + /* + * Bit 6 (0x40) is the device selected bit. + * First we must wait till the current device goes off line... + */ + k = PPA_SELECT_TMO; + do { + k--; + } while ((r_str(ppb) & 0x40) && (k)); + if (!k) + return 0; + + w_dtr(ppb, (1 << target)); + w_ctr(ppb, 0xe); + w_ctr(ppb, 0xc); + w_dtr(ppb, 0x80); /* This is NOT the initator */ + w_ctr(ppb, 0x8); + + k = PPA_SELECT_TMO; + do { + k--; + } + while (!(r_str(ppb) & 0x40) && (k)); + if (!k) + return 0; + + return 1; +} + +/* + * This is based on a trace of what the Iomega DOS 'guest' driver does. + * I've tried several different kinds of parallel ports with guest and + * coded this to react in the same ways that it does. + * + * The return value from this function is just a hint about where the + * handshaking failed. + * + */ +static int ppa_init(int host_no) +{ + int retv; + unsigned short ppb = PPA_BASE(host_no); + + ppa_disconnect(host_no); + ppa_connect(host_no, CONNECT_NORMAL); + + retv = 2; /* Failed */ + + w_ctr(ppb, 0xe); + if ((r_str(ppb) & 0x08) == 0x08) + retv--; + + w_ctr(ppb, 0xc); + if ((r_str(ppb) & 0x08) == 0x00) + retv--; + + /* This is a SCSI BUS reset signal */ + if (!retv) { + w_dtr(ppb, 0x40); + w_ctr(ppb, 0x08); + udelay(30); + w_ctr(ppb, 0x0c); + udelay(1000); /* Allow devices to settle down */ + } + ppa_disconnect(host_no); + udelay(1000); /* Another delay to allow devices to settle */ + + if (!retv) + retv = device_check(host_no); + + return retv; +} + +static inline int ppa_send_command(Scsi_Cmnd * cmd) +{ + int host_no = cmd->host->unique_id; + int k; + + w_ctr(PPA_BASE(host_no), 0x0c); + + for (k = 0; k < cmd->cmd_len; k++) + if (!ppa_out(host_no, &cmd->cmnd[k], 1)) + return 0; + return 1; +} + +/* + * The bulk flag enables some optimisations in the data transfer loops, + * it should be true for any command that transfers data in integral + * numbers of sectors. + * + * The driver appears to remain stable if we speed up the parallel port + * i/o in this function, but not elsewhere. + */ +static int ppa_completion(Scsi_Cmnd * cmd) +{ + /* Return codes: + * -1 Error + * 0 Told to schedule + * 1 Finished data transfer + */ + int host_no = cmd->host->unique_id; + unsigned short ppb = PPA_BASE(host_no); + unsigned long start_jiffies = jiffies; + + unsigned char r, v; + int fast, bulk, status; + + v = cmd->cmnd[0]; + bulk = ((v == READ_6) || + (v == READ_10) || + (v == WRITE_6) || + (v == WRITE_10)); + + /* + * We only get here if the drive is ready to comunicate, + * hence no need for a full ppa_wait. + */ + r = (r_str(ppb) & 0xf0); + + while (r != (unsigned char) 0xf0) { + /* + * If we have been running for more than a full timer tick + * then take a rest. + */ + if (jiffies > start_jiffies + 1) + return 0; + + if (((r & 0xc0) != 0xc0) || (cmd->SCp.this_residual <= 0)) { + ppa_fail(host_no, DID_ERROR); + return -1; /* ERROR_RETURN */ + } + /* determine if we should use burst I/O */ fast = (bulk && (cmd->SCp.this_residual >= PPA_BURST_SIZE)) + ? PPA_BURST_SIZE : 1; + + if (r == (unsigned char) 0xc0) + status = ppa_out(host_no, cmd->SCp.ptr, fast); + else + status = ppa_in(host_no, cmd->SCp.ptr, fast); + + cmd->SCp.ptr += fast; + cmd->SCp.this_residual -= fast; + + if (!status) { + ppa_fail(host_no, DID_BUS_BUSY); + return -1; /* ERROR_RETURN */ + } + if (cmd->SCp.buffer && !cmd->SCp.this_residual) { + /* if scatter/gather, advance to the next segment */ + if (cmd->SCp.buffers_residual--) { + cmd->SCp.buffer++; + cmd->SCp.this_residual = cmd->SCp.buffer->length; + cmd->SCp.ptr = cmd->SCp.buffer->address; + } + } + /* Now check to see if the drive is ready to comunicate */ + r = (r_str(ppb) & 0xf0); + /* If not, drop back down to the scheduler and wait a timer tick */ + if (!(r & 0x80)) + return 0; + } + return 1; /* FINISH_RETURN */ +} + +/* + * Since the PPA itself doesn't generate interrupts, we use + * the scheduler's task queue to generate a stream of call-backs and + * complete the request when the drive is ready. + */ +static void ppa_interrupt(void *data) +{ + ppa_struct *tmp = (ppa_struct *) data; + Scsi_Cmnd *cmd = tmp->cur_cmd; + + if (!cmd) { + printk("PPA: bug in ppa_interrupt\n"); + return; + } + if (ppa_engine(tmp, cmd)) { + tmp->ppa_tq.data = (void *) tmp; + tmp->ppa_tq.sync = 0; + queue_task(&tmp->ppa_tq, &tq_timer); + return; + } + /* Command must of completed hence it is safe to let go... */ +#if PPA_DEBUG > 0 + switch ((cmd->result >> 16) & 0xff) { + case DID_OK: + break; + case DID_NO_CONNECT: + printk("ppa: no device at SCSI ID %i\n", cmd->target); + break; + case DID_BUS_BUSY: + printk("ppa: BUS BUSY - EPP timeout detected\n"); + break; + case DID_TIME_OUT: + printk("ppa: unknown timeout\n"); + break; + case DID_ABORT: + printk("ppa: told to abort\n"); + break; + case DID_PARITY: + printk("ppa: parity error (???)\n"); + break; + case DID_ERROR: + printk("ppa: internal driver error\n"); + break; + case DID_RESET: + printk("ppa: told to reset device\n"); + break; + case DID_BAD_INTR: + printk("ppa: bad interrupt (???)\n"); + break; + default: + printk("ppa: bad return code (%02x)\n", (cmd->result >> 16) & 0xff); + } + #endif + + if (cmd->SCp.phase > 1) + ppa_disconnect(cmd->host->unique_id); + + tmp->cur_cmd = 0; + cmd->scsi_done(cmd); + return; +} + +static int ppa_engine(ppa_struct * tmp, Scsi_Cmnd * cmd) +{ + int host_no = cmd->host->unique_id; + unsigned short ppb = PPA_BASE(host_no); + unsigned char l = 0, h = 0; + int retv; + + /* First check for any errors that may have occurred + * Here we check for internal errors + */ + if (tmp->failed) + return 0; + + switch (cmd->SCp.phase) { + case 0: /* Phase 0 - Waiting for parport */ + if ((jiffies - tmp->jstart) > HZ) { + /* + * We waited more than a second + * for parport to call us + */ + ppa_fail(host_no, DID_BUS_BUSY); + return 0; + } + return 1; /* wait until ppa_wakeup claims parport */ + case 1: /* Phase 1 - Connected */ + { /* Perform a sanity check for cable unplugged */ + int retv = 2; /* Failed */ + + ppa_connect(host_no, CONNECT_EPP_MAYBE); + + w_ctr(ppb, 0xe); + if ((r_str(ppb) & 0x08) == 0x08) + retv--; + + w_ctr(ppb, 0xc); + if ((r_str(ppb) & 0x08) == 0x00) + retv--; + + if (retv) + if ((jiffies - tmp->jstart) > (1 * HZ)) { + printk("ppa: Parallel port cable is unplugged!!\n"); + ppa_fail(host_no, DID_BUS_BUSY); + return 0; + } else { + ppa_disconnect(host_no); + return 1; /* Try again in a jiffy */ + } + cmd->SCp.phase++; + } + + case 2: /* Phase 2 - We are now talking to the scsi bus */ + if (!ppa_select(host_no, cmd->target)) { + ppa_fail(host_no, DID_NO_CONNECT); + return 0; + } + cmd->SCp.phase++; + + case 3: /* Phase 3 - Ready to accept a command */ + w_ctr(ppb, 0x0c); + if (!(r_str(ppb) & 0x80)) + return 1; + + if (!ppa_send_command(cmd)) + return 0; + cmd->SCp.phase++; + + case 4: /* Phase 4 - Setup scatter/gather buffers */ + if (cmd->use_sg) { + /* if many buffers are available, start filling the first */ + cmd->SCp.buffer = (struct scatterlist *) cmd->request_buffer; + cmd->SCp.this_residual = cmd->SCp.buffer->length; + cmd->SCp.ptr = cmd->SCp.buffer->address; + } else { + /* else fill the only available buffer */ + cmd->SCp.buffer = NULL; + cmd->SCp.this_residual = cmd->request_bufflen; + cmd->SCp.ptr = cmd->request_buffer; + } + cmd->SCp.buffers_residual = cmd->use_sg; + cmd->SCp.phase++; + + case 5: /* Phase 5 - Data transfer stage */ + w_ctr(ppb, 0x0c); + if (!(r_str(ppb) & 0x80)) + return 1; + + retv = ppa_completion(cmd); + if (retv == -1) + return 0; + if (retv == 0) + return 1; + cmd->SCp.phase++; + + case 6: /* Phase 6 - Read status/message */ + cmd->result = DID_OK << 16; + /* Check for data overrun */ + if (ppa_wait(host_no) != (unsigned char) 0xf0) { + ppa_fail(host_no, DID_ERROR); + return 0; + } + if (ppa_in(host_no, &l, 1)) { /* read status byte */ + /* Check for optional message byte */ + if (ppa_wait(host_no) == (unsigned char) 0xf0) + ppa_in(host_no, &h, 1); + cmd->result = (DID_OK << 16) + (h << 8) + (l & STATUS_MASK); + } + return 0; /* Finished */ + break; + + default: + printk("ppa: Invalid scsi phase\n"); + } + return 0; +} + +int ppa_queuecommand(Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *)) +{ + int host_no = cmd->host->unique_id; + + if (ppa_hosts[host_no].cur_cmd) { + printk("PPA: bug in ppa_queuecommand\n"); + return 0; + } + ppa_hosts[host_no].failed = 0; + ppa_hosts[host_no].jstart = jiffies; + ppa_hosts[host_no].cur_cmd = cmd; + cmd->scsi_done = done; + cmd->result = DID_ERROR << 16; /* default return code */ + cmd->SCp.phase = 0; /* bus free */ + + ppa_pb_claim(host_no); + + ppa_hosts[host_no].ppa_tq.data = ppa_hosts + host_no; + ppa_hosts[host_no].ppa_tq.sync = 0; + queue_task(&ppa_hosts[host_no].ppa_tq, &tq_immediate); + mark_bh(IMMEDIATE_BH); + + return 0; +} + +/* + * Apparently the disk->capacity attribute is off by 1 sector + * for all disk drives. We add the one here, but it should really + * be done in sd.c. Even if it gets fixed there, this will still + * work. + */ +int ppa_biosparam(Disk * disk, kdev_t dev, int ip[]) +{ + ip[0] = 0x40; + ip[1] = 0x20; + ip[2] = (disk->capacity + 1) / (ip[0] * ip[1]); + if (ip[2] > 1024) { + ip[0] = 0xff; + ip[1] = 0x3f; + ip[2] = (disk->capacity + 1) / (ip[0] * ip[1]); + if (ip[2] > 1023) + ip[2] = 1023; + } + return 0; +} + +int ppa_abort(Scsi_Cmnd * cmd) +{ + /* + * There is no method for aborting commands since Iomega + * have tied the SCSI_MESSAGE line high in the interface + */ + + switch (cmd->SCp.phase) { + case 0: /* Do not have access to parport */ + case 1: /* Have not connected to interface */ + cmd->result = DID_ABORT; + cmd->done(cmd); + return SCSI_ABORT_SUCCESS; + break; + default: /* SCSI command sent, can not abort */ + return SCSI_ABORT_BUSY; + break; + } +} + +int ppa_reset(Scsi_Cmnd * cmd, unsigned int x) +{ + int host_no = cmd->host->unique_id; + int ppb = PPA_BASE(host_no); + + /* + * PHASE1: + * Bring the interface crashing down on whatever is running + * hopefully this will kill the request. + * Bring back up the interface, reset the drive (and anything + * attached for that manner) + */ + if (cmd) + if (cmd->SCp.phase) + ppa_disconnect(cmd->host->unique_id); + + ppa_connect(host_no, CONNECT_NORMAL); + w_dtr(ppb, 0x40); + w_ctr(ppb, 0x8); + udelay(30); + w_ctr(ppb, 0xc); + udelay(1000); /* delay for devices to settle down */ + ppa_disconnect(host_no); + udelay(1000); /* Additional delay to allow devices to settle down */ + + /* + * PHASE2: + * Sanity check for the sake of mid-level driver + */ + if (!cmd) { + printk("ppa bus reset called for invalid command.\n"); + return SCSI_RESET_NOT_RUNNING; + } + /* + * PHASE3: + * Flag the current command as having died due to reset + */ + ppa_connect(host_no, CONNECT_NORMAL); + ppa_fail(host_no, DID_RESET); + + /* Since the command was already on the timer queue ppa_interrupt + * will be called shortly. + */ + return SCSI_RESET_PENDING; +} + +static int device_check(int host_no) +{ + /* This routine looks for a device and then attempts to use EPP + to send a command. If all goes as planned then EPP is available. */ + + static char cmd[6] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + int loop, old_mode, status, k, ppb = PPA_BASE(host_no); + unsigned char l; + + old_mode = ppa_hosts[host_no].mode; + for (loop = 0; loop < 8; loop++) { + /* Attempt to use EPP for Test Unit Ready */ + if ((ppb & 0x0007) == 0x0000) + ppa_hosts[host_no].mode = PPA_EPP_32; + + second_pass: + ppa_connect(host_no, CONNECT_EPP_MAYBE); + /* Select SCSI device */ + if (!ppa_select(host_no, loop)) { + ppa_disconnect(host_no); + continue; + } + printk("ppa: Found device at ID %i, Attempting to use %s\n", loop, + PPA_MODE_STRING[ppa_hosts[host_no].mode]); + + /* Send SCSI command */ + status = 1; + w_ctr(ppb, 0x0c); + for (l = 0; (l < 6) && (status); l++) + status = ppa_out(host_no, cmd, 1); + + if (!status) { + ppa_disconnect(host_no); + ppa_connect(host_no, CONNECT_EPP_MAYBE); + w_dtr(ppb, 0x40); + w_ctr(ppb, 0x08); + udelay(30); + w_ctr(ppb, 0x0c); + udelay(1000); + ppa_disconnect(host_no); + udelay(1000); + if (ppa_hosts[host_no].mode == PPA_EPP_32) { + ppa_hosts[host_no].mode = old_mode; + goto second_pass; + } + printk("ppa: Unable to establish communication, aborting driver load.\n"); + return 1; + } + w_ctr(ppb, 0x0c); + k = 1000000; /* 1 Second */ + do { + l = r_str(ppb); + k--; + udelay(1); + } while (!(l & 0x80) && (k)); + + l &= 0xf0; + + if (l != 0xf0) { + ppa_disconnect(host_no); + ppa_connect(host_no, CONNECT_EPP_MAYBE); + w_dtr(ppb, 0x40); + w_ctr(ppb, 0x08); + udelay(30); + w_ctr(ppb, 0x0c); + udelay(1000); + ppa_disconnect(host_no); + udelay(1000); + if (ppa_hosts[host_no].mode == PPA_EPP_32) { + ppa_hosts[host_no].mode = old_mode; + goto second_pass; + } + printk("ppa: Unable to establish communication, aborting driver load.\n"); + return 1; + } + ppa_disconnect(host_no); + printk("ppa: Communication established with ID %i using %s\n", loop, + PPA_MODE_STRING[ppa_hosts[host_no].mode]); + return 0; + } + printk("ppa: No devices found, aborting driver load.\n"); + return 1; +} + +#define PPA_ID "ppa: " + +int port_probe(unsigned short port) +{ + int retv = 0; + unsigned char a, b, c; + unsigned int i, j; + + + printk(PPA_ID "Probing port %04x\n", port); + +/* ##### ###### ###### + * # # # # # # + * # # # # # + * ##### ###### ###### + * # # # + * # # # # + * ##### # # + */ + + outb(0x0c, port + 0x402); + outb(0x0c, port + 0x002); + outb(0x55, port); + a = inb(port); + if (a != 0x55) + return retv; + printk(PPA_ID " SPP port present\n"); + + retv += PPA_PROBE_SPP; + +/* ####### ##### ###### + * # # # # # + * # # # # + * ##### # ###### + * # # # + * # # # # + * ####### ##### # + */ + + for (i = 1024; i > 0; i--) { /* clear at most 1k of data from FIFO */ + a = inb(port + 0x402); + if ((a & 0x03) == 0x03) + goto no_ecp; + if (a & 0x01) + break; + inb(port + 0x400); /* Remove byte from FIFO */ + } + + if (i <= 0) + goto no_ecp; + + b = a ^ 3; + outb(b, port + 0x402); + c = inb(port + 0x402); + + if (a == c) { + outb(0xc0, port + 0x402); /* FIFO test */ + j = 0; + while (!(inb(port + 0x402) & 0x01) && (j < 1024)) { + inb(port + 0x400); + j++; + } + if (j >= 1024) + goto no_ecp; + i = 0; + j = 0; + while (!(inb(port + 0x402) & 0x02) && (j < 1024)) { + outb(0x00, port + 0x400); + i++; + j++; + } + if (j >= 1024) + goto no_ecp; + j = 0; + while (!(inb(port + 0x402) & 0x01) && (j < 1024)) { + inb(port + 0x400); + j++; + } + if (j >= 1024) + goto no_ecp; + printk(PPA_ID " ECP with a %i byte FIFO present\n", i); + + retv += PPA_PROBE_ECR; + } +/* ###### ##### ##### + * # # # # # # + * # # # # + * ###### ##### ##### + * # # # + * # # # # + * # ##### ####### + */ + + no_ecp: + if (retv & PPA_PROBE_ECR) + outb(0x20, port + 0x402); + + outb(0x55, port); + outb(0x0c, port + 2); + a = inb(port); + outb(0x55, port); + outb(0x2c, port + 2); + b = inb(port); + if (a != b) { + printk(PPA_ID " PS/2 bidirectional port present\n"); + retv += PPA_PROBE_PS2; + } +/* ####### ###### ###### + * # # # # # + * # # # # # + * ##### ###### ###### + * # # # + * # # # + * ####### # # + */ + + if (port & 0x007) { + printk(PPA_ID " EPP not supported at this address\n"); + return retv; + } + if (retv & PPA_PROBE_ECR) { + for (i = 0x00; i < 0x80; i += 0x20) { + outb(i, port + 0x402); + + a = inb(port + 1); + outb(a, port + 1); + outb(a & 0xfe, port + 1); + a = inb(port + 1); + if (!(a & 0x01)) { + printk(PPA_ID " Failed Intel bug check. (Phony EPP in ECP)\n"); + return retv; + } + } + printk(PPA_ID " Passed Intel bug check.\n"); + outb(0x80, port + 0x402); + } + a = inb(port + 1); + outb(a, port + 1); + outb(a & 0xfe, port + 1); + a = inb(port + 1); + + if (a & 0x01) { + outb(0x0c, port + 0x402); + outb(0x0c, port + 0x002); + return retv; + } + + outb(0x04, port + 2); + inb(port + 4); + a = inb(port + 1); + outb(a, port + 1); + outb(a & 0xfe, port + 1); + + if (a & 0x01) { + printk(PPA_ID " EPP 1.9 with hardware direction protocol\n"); + retv += PPA_PROBE_EPP19; + } else { + /* The EPP timeout bit was not set, this could either be: + * EPP 1.7 + * EPP 1.9 with software direction + */ + outb(0x24, port + 2); + inb(port + 4); + a = inb(port + 1); + outb(a, port + 1); + outb(a & 0xfe, port + 1); + if (a & 0x01) { + printk(PPA_ID " EPP 1.9 with software direction protocol\n"); + retv += PPA_PROBE_EPP19; + } else { + printk(PPA_ID " EPP 1.7\n"); + retv += PPA_PROBE_EPP17; + } + } + + outb(0x0c, port + 0x402); + outb(0x0c, port + 0x002); + return retv; +} diff --git a/linux/src/drivers/scsi/ppa.h b/linux/src/drivers/scsi/ppa.h new file mode 100644 index 00000000..1497c208 --- /dev/null +++ b/linux/src/drivers/scsi/ppa.h @@ -0,0 +1,176 @@ +/* Driver for the PPA3 parallel port SCSI HBA embedded in + * the Iomega ZIP drive + * + * (c) 1996 Grant R. Guenther grant@torque.net + * David Campbell campbell@torque.net + * + * All comments to David. + */ + +#include <linux/config.h> /* CONFIG_SCSI_PPA_HAVE_PEDANTIC */ +#ifndef _PPA_H +#define _PPA_H + +#define PPA_VERSION "1.42" + +#if 0 +/* Use the following to enable certain chipset support + * Default is PEDANTIC = 3 + */ +#ifndef CONFIG_SCSI_PPA_HAVE_PEDANTIC +#define CONFIG_SCSI_PPA_HAVE_PEDANTIC 3 +#endif +#endif + +/* + * this driver has been hacked by Matteo Frigo (athena@theory.lcs.mit.edu) + * to support EPP and scatter-gather. [0.26-athena] + * + * additional hacks by David Campbell + * in response to this driver "mis-behaving" on his machine. + * Fixed EPP to handle "software" changing of EPP port data direction. + * Chased down EPP timeouts + * Made this driver "kernel version friendly" [0.28-athena] + * + * [ Stuff removed ] + * + * Compiled against 2.1.53. + * Rebuilt ppa_abort() function, should handle unplugged cable. + * [1.35s] + * + * PPA now auto probes for EPP on base address which are aligned on + * 8 byte boundaries (0x278 & 0x378) using the attached devices. + * This hopefully avoids the nasty problem of trying to detect EPP. + * Tested on 2.1.53 [1.36] + * + * The id_probe utility no longer performs read/write tests. + * Additional code included for checking the Intel ECP bug + * (Bit 0 of STR stuck low which fools the EPP detection routine) + * [1.37] + * + * Oops! Got the bit sign mixed up for the Intel bug check. + * Found that an additional delay is required during SCSI resets + * to allow devices to settle down. + * [1.38] + * + * Fixed all problems in the parport sharing scheme. Now ppa can be safe + * used with lp or other parport devices on the same parallel port. + * 1997 by Andrea Arcangeli + * [1.39] + * + * Little fix in ppa engine to ensure that ppa don' t release parport + * or disconnect in wrong cases. + * 1997 by Andrea Arcangeli + * [1.40] + * + * Corrected ppa.h for 2.1.x kernels (>=2.1.85) + * Modified "Nat Semi Kludge" for extended chipsets + * [1.41] + * + * Fixed id_probe for EPP 1.9 chipsets (misdetected as EPP 1.7) + * [1.42] + */ +/* ------ END OF USER CONFIGURABLE PARAMETERS ----- */ + +#ifdef PPA_CODE +#include <linux/stddef.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/tqueue.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/blk.h> +#include <linux/sched.h> +#include <linux/interrupt.h> + +#include <asm/io.h> +#include "sd.h" +#include "hosts.h" +/* batteries not included :-) */ + +/* + * modes in which the driver can operate + */ +#define PPA_AUTODETECT 0 /* Autodetect mode */ +#define PPA_NIBBLE 1 /* work in standard 4 bit mode */ +#define PPA_PS2 2 /* PS/2 byte mode */ +#define PPA_EPP_8 3 /* EPP mode, 8 bit */ +#define PPA_EPP_16 4 /* EPP mode, 16 bit */ +#define PPA_EPP_32 5 /* EPP mode, 32 bit */ +#define PPA_UNKNOWN 6 /* Just in case... */ + +static char *PPA_MODE_STRING[] = +{ + "Autodetect", + "SPP", + "PS/2", + "EPP 8 bit", + "EPP 16 bit", + "EPP 32 bit", + "Unknown"}; + +/* This is a global option */ +int ppa_sg = SG_ALL; /* enable/disable scatter-gather. */ + +/* other options */ +#define PPA_CAN_QUEUE 1 /* use "queueing" interface */ +#define PPA_BURST_SIZE 512 /* data burst size */ +#define PPA_SELECT_TMO 5000 /* how long to wait for target ? */ +#define PPA_SPIN_TMO 50000 /* ppa_wait loop limiter */ +#define PPA_DEBUG 0 /* debuging option */ +#define IN_EPP_MODE(x) (x == PPA_EPP_8 || x == PPA_EPP_16 || x == PPA_EPP_32) + +/* args to ppa_connect */ +#define CONNECT_EPP_MAYBE 1 +#define CONNECT_NORMAL 0 + +#define r_dtr(x) (unsigned char)inb((x)) +#define r_str(x) (unsigned char)inb((x)+1) +#define r_ctr(x) (unsigned char)inb((x)+2) +#define r_epp(x) (unsigned char)inb((x)+4) +#define r_fifo(x) (unsigned char)inb((x)+0x400) +#define r_ecr(x) (unsigned char)inb((x)+0x402) + +#define w_dtr(x,y) outb(y, (x)) +#define w_str(x,y) outb(y, (x)+1) +#define w_ctr(x,y) outb(y, (x)+2) +#define w_epp(x,y) outb(y, (x)+4) +#define w_fifo(x,y) outb(y, (x)+0x400) +#define w_ecr(x,y) outb(y, (x)+0x402) + +static int ppa_engine(ppa_struct *, Scsi_Cmnd *); +static int ppa_in(int, char *, int); +static int ppa_init(int); +static void ppa_interrupt(void *); +static int ppa_out(int, char *, int); + +struct proc_dir_entry proc_scsi_ppa = +{PROC_SCSI_PPA, 3, "ppa", S_IFDIR | S_IRUGO | S_IXUGO, 2}; +#else +extern struct proc_dir_entry proc_scsi_ppa; +#endif + +int ppa_detect(Scsi_Host_Template *); +const char *ppa_info(struct Scsi_Host *); +int ppa_queuecommand(Scsi_Cmnd *, void (*done) (Scsi_Cmnd *)); +int ppa_abort(Scsi_Cmnd *); +int ppa_reset(Scsi_Cmnd *, unsigned int); +int ppa_proc_info(char *, char **, off_t, int, int, int); +int ppa_biosparam(Disk *, kdev_t, int *); + +#define PPA { proc_dir: &proc_scsi_ppa, \ + proc_info: ppa_proc_info, \ + name: "Iomega parport ZIP drive", \ + detect: ppa_detect, \ + queuecommand: ppa_queuecommand, \ + abort: ppa_abort, \ + reset: ppa_reset, \ + bios_param: ppa_biosparam, \ + this_id: -1, \ + sg_tablesize: SG_ALL, \ + cmd_per_lun: 1, \ + use_clustering: ENABLE_CLUSTERING \ +} +#endif /* _PPA_H */ diff --git a/linux/src/drivers/scsi/qlogicfas.c b/linux/src/drivers/scsi/qlogicfas.c new file mode 100644 index 00000000..b5cb9dd0 --- /dev/null +++ b/linux/src/drivers/scsi/qlogicfas.c @@ -0,0 +1,679 @@ +/*----------------------------------------------------------------*/ +/* + Qlogic linux driver - work in progress. No Warranty express or implied. + Use at your own risk. Support Tort Reform so you won't have to read all + these silly disclaimers. + + Copyright 1994, Tom Zerucha. + zerucha@shell.portal.com + + Additional Code, and much appreciated help by + Michael A. Griffith + grif@cs.ucr.edu + + Thanks to Eric Youngdale and Dave Hinds for loadable module and PCMCIA + help respectively, and for suffering through my foolishness during the + debugging process. + + Reference Qlogic FAS408 Technical Manual, 53408-510-00A, May 10, 1994 + (you can reference it, but it is incomplete and inaccurate in places) + + Version 0.45 6/9/96 - kernel 1.2.0+ + + Functions as standalone, loadable, and PCMCIA driver, the latter from + Dave Hind's PCMCIA package. + + Redistributable under terms of the GNU Public License + +*/ +/*----------------------------------------------------------------*/ +/* Configuration */ + +/* Set the following to 2 to use normal interrupt (active high/totempole- + tristate), otherwise use 0 (REQUIRED FOR PCMCIA) for active low, open + drain */ +#define QL_INT_ACTIVE_HIGH 2 + +/* Set the following to 1 to enable the use of interrupts. Note that 0 tends + to be more stable, but slower (or ties up the system more) */ +#define QL_USE_IRQ 1 + +/* Set the following to max out the speed of the PIO PseudoDMA transfers, + again, 0 tends to be slower, but more stable. */ +#define QL_TURBO_PDMA 1 + +/* This should be 1 to enable parity detection */ +#define QL_ENABLE_PARITY 1 + +/* This will reset all devices when the driver is initialized (during bootup). + The other linux drivers don't do this, but the DOS drivers do, and after + using DOS or some kind of crash or lockup this will bring things back + without requiring a cold boot. It does take some time to recover from a + reset, so it is slower, and I have seen timeouts so that devices weren't + recognized when this was set. */ +#define QL_RESET_AT_START 0 + +/* crystal frequency in megahertz (for offset 5 and 9) + Please set this for your card. Most Qlogic cards are 40 Mhz. The + Control Concepts ISA (not VLB) is 24 Mhz */ +#define XTALFREQ 40 + +/**********/ +/* DANGER! modify these at your own risk */ +/* SLOWCABLE can usually be reset to zero if you have a clean setup and + proper termination. The rest are for synchronous transfers and other + advanced features if your device can transfer faster than 5Mb/sec. + If you are really curious, email me for a quick howto until I have + something official */ +/**********/ + +/*****/ +/* config register 1 (offset 8) options */ +/* This needs to be set to 1 if your cabling is long or noisy */ +#define SLOWCABLE 1 + +/*****/ +/* offset 0xc */ +/* This will set fast (10Mhz) synchronous timing when set to 1 + For this to have an effect, FASTCLK must also be 1 */ +#define FASTSCSI 0 + +/* This when set to 1 will set a faster sync transfer rate */ +#define FASTCLK 0 +/*(XTALFREQ>25?1:0)*/ + +/*****/ +/* offset 6 */ +/* This is the sync transfer divisor, XTALFREQ/X will be the maximum + achievable data rate (assuming the rest of the system is capable + and set properly) */ +#define SYNCXFRPD 5 +/*(XTALFREQ/5)*/ + +/*****/ +/* offset 7 */ +/* This is the count of how many synchronous transfers can take place + i.e. how many reqs can occur before an ack is given. + The maximum value for this is 15, the upper bits can modify + REQ/ACK assertion and deassertion during synchronous transfers + If this is 0, the bus will only transfer asynchronously */ +#define SYNCOFFST 0 +/* for the curious, bits 7&6 control the deassertion delay in 1/2 cycles + of the 40Mhz clock. If FASTCLK is 1, specifying 01 (1/2) will + cause the deassertion to be early by 1/2 clock. Bits 5&4 control + the assertion delay, also in 1/2 clocks (FASTCLK is ignored here). */ + +/*----------------------------------------------------------------*/ +#ifdef PCMCIA +#undef QL_INT_ACTIVE_HIGH +#define QL_INT_ACTIVE_HIGH 0 +#define MODULE +#endif + +#include <linux/module.h> + +#ifdef PCMCIA +#undef MODULE +#endif + +#include <linux/blk.h> /* to get disk capacity */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/proc_fs.h> +#include <linux/unistd.h> +#include <asm/io.h> +#include <asm/irq.h> +#include "sd.h" +#include "hosts.h" +#include "qlogicfas.h" +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_qlogicfas = { + PROC_SCSI_QLOGICFAS, 6, "qlogicfas", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +/*----------------------------------------------------------------*/ +/* driver state info, local to driver */ +static int qbase = 0; /* Port */ +static int qinitid; /* initiator ID */ +static int qabort; /* Flag to cause an abort */ +static int qlirq = -1; /* IRQ being used */ +static char qinfo[80]; /* description */ +static Scsi_Cmnd *qlcmd; /* current command being processed */ + +static int qlcfg5 = ( XTALFREQ << 5 ); /* 15625/512 */ +static int qlcfg6 = SYNCXFRPD; +static int qlcfg7 = SYNCOFFST; +static int qlcfg8 = ( SLOWCABLE << 7 ) | ( QL_ENABLE_PARITY << 4 ); +static int qlcfg9 = ( ( XTALFREQ + 4 ) / 5 ); +static int qlcfgc = ( FASTCLK << 3 ) | ( FASTSCSI << 4 ); + +/*----------------------------------------------------------------*/ +/* The qlogic card uses two register maps - These macros select which one */ +#define REG0 ( outb( inb( qbase + 0xd ) & 0x7f , qbase + 0xd ), outb( 4 , qbase + 0xd )) +#define REG1 ( outb( inb( qbase + 0xd ) | 0x80 , qbase + 0xd ), outb( 0xb4 | QL_INT_ACTIVE_HIGH , qbase + 0xd )) + +/* following is watchdog timeout in microseconds */ +#define WATCHDOG 5000000 + +/*----------------------------------------------------------------*/ +/* the following will set the monitor border color (useful to find + where something crashed or gets stuck at and as a simple profiler) */ + +#if 0 +#define rtrc(i) {inb(0x3da);outb(0x31,0x3c0);outb((i),0x3c0);} +#else +#define rtrc(i) {} +#endif + +/*----------------------------------------------------------------*/ +/* local functions */ +/*----------------------------------------------------------------*/ +static void ql_zap(void); +/* error recovery - reset everything */ +void ql_zap() +{ +int x; +unsigned long flags; + save_flags( flags ); + cli(); + x = inb(qbase + 0xd); + REG0; + outb(3, qbase + 3); /* reset SCSI */ + outb(2, qbase + 3); /* reset chip */ + if (x & 0x80) + REG1; + restore_flags( flags ); +} + +/*----------------------------------------------------------------*/ +/* do pseudo-dma */ +static int ql_pdma(int phase, char *request, int reqlen) +{ +int j; + j = 0; + if (phase & 1) { /* in */ +#if QL_TURBO_PDMA +rtrc(4) + /* empty fifo in large chunks */ + if( reqlen >= 128 && (inb( qbase + 8 ) & 2) ) { /* full */ + insl( qbase + 4, request, 32 ); + reqlen -= 128; + request += 128; + } + while( reqlen >= 84 && !( j & 0xc0 ) ) /* 2/3 */ + if( (j=inb( qbase + 8 )) & 4 ) { + insl( qbase + 4, request, 21 ); + reqlen -= 84; + request += 84; + } + if( reqlen >= 44 && (inb( qbase + 8 ) & 8) ) { /* 1/3 */ + insl( qbase + 4, request, 11 ); + reqlen -= 44; + request += 44; + } +#endif + /* until both empty and int (or until reclen is 0) */ +rtrc(7) + j = 0; + while( reqlen && !( (j & 0x10) && (j & 0xc0) ) ) { + /* while bytes to receive and not empty */ + j &= 0xc0; + while ( reqlen && !( (j=inb(qbase + 8)) & 0x10 ) ) { + *request++ = inb(qbase + 4); + reqlen--; + } + if( j & 0x10 ) + j = inb(qbase+8); + + } + } + else { /* out */ +#if QL_TURBO_PDMA +rtrc(4) + if( reqlen >= 128 && inb( qbase + 8 ) & 0x10 ) { /* empty */ + outsl(qbase + 4, request, 32 ); + reqlen -= 128; + request += 128; + } + while( reqlen >= 84 && !( j & 0xc0 ) ) /* 1/3 */ + if( !((j=inb( qbase + 8 )) & 8) ) { + outsl( qbase + 4, request, 21 ); + reqlen -= 84; + request += 84; + } + if( reqlen >= 40 && !(inb( qbase + 8 ) & 4 ) ) { /* 2/3 */ + outsl( qbase + 4, request, 10 ); + reqlen -= 40; + request += 40; + } +#endif + /* until full and int (or until reclen is 0) */ +rtrc(7) + j = 0; + while( reqlen && !( (j & 2) && (j & 0xc0) ) ) { + /* while bytes to send and not full */ + while ( reqlen && !( (j=inb(qbase + 8)) & 2 ) ) { + outb(*request++, qbase + 4); + reqlen--; + } + if( j & 2 ) + j = inb(qbase+8); + } + } +/* maybe return reqlen */ + return inb( qbase + 8 ) & 0xc0; +} + +/*----------------------------------------------------------------*/ +/* wait for interrupt flag (polled - not real hardware interrupt) */ +static int ql_wai(void) +{ +int i,k; + k = 0; + i = jiffies + WATCHDOG; + while ( i > jiffies && !qabort && !((k = inb(qbase + 4)) & 0xe0)) + barrier(); + if (i <= jiffies) + return (DID_TIME_OUT); + if (qabort) + return (qabort == 1 ? DID_ABORT : DID_RESET); + if (k & 0x60) + ql_zap(); + if (k & 0x20) + return (DID_PARITY); + if (k & 0x40) + return (DID_ERROR); + return 0; +} + +/*----------------------------------------------------------------*/ +/* initiate scsi command - queueing handler */ +static void ql_icmd(Scsi_Cmnd * cmd) +{ +unsigned int i; +unsigned long flags; + + qabort = 0; + + save_flags( flags ); + cli(); + REG0; +/* clearing of interrupts and the fifo is needed */ + inb(qbase + 5); /* clear interrupts */ + if (inb(qbase + 5)) /* if still interrupting */ + outb(2, qbase + 3); /* reset chip */ + else if (inb(qbase + 7) & 0x1f) + outb(1, qbase + 3); /* clear fifo */ + while (inb(qbase + 5)); /* clear ints */ + REG1; + outb(1, qbase + 8); /* set for PIO pseudo DMA */ + outb(0, qbase + 0xb); /* disable ints */ + inb(qbase + 8); /* clear int bits */ + REG0; + outb(0x40, qbase + 0xb); /* enable features */ + +/* configurables */ + outb( qlcfgc , qbase + 0xc); +/* config: no reset interrupt, (initiator) bus id */ + outb( 0x40 | qlcfg8 | qinitid, qbase + 8); + outb( qlcfg7 , qbase + 7 ); + outb( qlcfg6 , qbase + 6 ); +/**/ + outb(qlcfg5, qbase + 5); /* select timer */ + outb(qlcfg9 & 7, qbase + 9); /* prescaler */ +/* outb(0x99, qbase + 5); */ + outb(cmd->target, qbase + 4); + + for (i = 0; i < cmd->cmd_len; i++) + outb(cmd->cmnd[i], qbase + 2); + qlcmd = cmd; + outb(0x41, qbase + 3); /* select and send command */ + restore_flags( flags ); +} +/*----------------------------------------------------------------*/ +/* process scsi command - usually after interrupt */ +static unsigned int ql_pcmd(Scsi_Cmnd * cmd) +{ +unsigned int i, j, k; +unsigned int result; /* ultimate return result */ +unsigned int status; /* scsi returned status */ +unsigned int message; /* scsi returned message */ +unsigned int phase; /* recorded scsi phase */ +unsigned int reqlen; /* total length of transfer */ +struct scatterlist *sglist; /* scatter-gather list pointer */ +unsigned int sgcount; /* sg counter */ + +rtrc(1) + j = inb(qbase + 6); + i = inb(qbase + 5); + if (i == 0x20) { + return (DID_NO_CONNECT << 16); + } + i |= inb(qbase + 5); /* the 0x10 bit can be set after the 0x08 */ + if (i != 0x18) { + printk("Ql:Bad Interrupt status:%02x\n", i); + ql_zap(); + return (DID_BAD_INTR << 16); + } + j &= 7; /* j = inb( qbase + 7 ) >> 5; */ +/* correct status is supposed to be step 4 */ +/* it sometimes returns step 3 but with 0 bytes left to send */ +/* We can try stuffing the FIFO with the max each time, but we will get a + sequence of 3 if any bytes are left (but we do flush the FIFO anyway */ + if(j != 3 && j != 4) { + printk("Ql:Bad sequence for command %d, int %02X, cmdleft = %d\n", j, i, inb( qbase+7 ) & 0x1f ); + ql_zap(); + return (DID_ERROR << 16); + } + result = DID_OK; + if (inb(qbase + 7) & 0x1f) /* if some bytes in fifo */ + outb(1, qbase + 3); /* clear fifo */ +/* note that request_bufflen is the total xfer size when sg is used */ + reqlen = cmd->request_bufflen; +/* note that it won't work if transfers > 16M are requested */ + if (reqlen && !((phase = inb(qbase + 4)) & 6)) { /* data phase */ +rtrc(2) + outb(reqlen, qbase); /* low-mid xfer cnt */ + outb(reqlen >> 8, qbase+1); /* low-mid xfer cnt */ + outb(reqlen >> 16, qbase + 0xe); /* high xfer cnt */ + outb(0x90, qbase + 3); /* command do xfer */ +/* PIO pseudo DMA to buffer or sglist */ + REG1; + if (!cmd->use_sg) + ql_pdma(phase, cmd->request_buffer, cmd->request_bufflen); + else { + sgcount = cmd->use_sg; + sglist = cmd->request_buffer; + while (sgcount--) { + if (qabort) { + REG0; + return ((qabort == 1 ? DID_ABORT : DID_RESET) << 16); + } + if (ql_pdma(phase, sglist->address, sglist->length)) + break; + sglist++; + } + } + REG0; +rtrc(2) +/* wait for irq (split into second state of irq handler if this can take time) */ + if ((k = ql_wai())) + return (k << 16); + k = inb(qbase + 5); /* should be 0x10, bus service */ + } +/*** Enter Status (and Message In) Phase ***/ + k = jiffies + WATCHDOG; + while ( k > jiffies && !qabort && !(inb(qbase + 4) & 6)); /* wait for status phase */ + if ( k <= jiffies ) { + ql_zap(); + return (DID_TIME_OUT << 16); + } + while (inb(qbase + 5)); /* clear pending ints */ + if (qabort) + return ((qabort == 1 ? DID_ABORT : DID_RESET) << 16); + outb(0x11, qbase + 3); /* get status and message */ + if ((k = ql_wai())) + return (k << 16); + i = inb(qbase + 5); /* get chip irq stat */ + j = inb(qbase + 7) & 0x1f; /* and bytes rec'd */ + status = inb(qbase + 2); + message = inb(qbase + 2); +/* should get function complete int if Status and message, else bus serv if only status */ + if (!((i == 8 && j == 2) || (i == 0x10 && j == 1))) { + printk("Ql:Error during status phase, int=%02X, %d bytes recd\n", i, j); + result = DID_ERROR; + } + outb(0x12, qbase + 3); /* done, disconnect */ +rtrc(1) + if ((k = ql_wai())) + return (k << 16); +/* should get bus service interrupt and disconnect interrupt */ + i = inb(qbase + 5); /* should be bus service */ + while (!qabort && ((i & 0x20) != 0x20)) { + barrier(); + i |= inb(qbase + 5); + } +rtrc(0) + if (qabort) + return ((qabort == 1 ? DID_ABORT : DID_RESET) << 16); + return (result << 16) | (message << 8) | (status & STATUS_MASK); +} + +#if QL_USE_IRQ +/*----------------------------------------------------------------*/ +/* interrupt handler */ +static void ql_ihandl(int irq, void *dev_id, struct pt_regs * regs) +{ +Scsi_Cmnd *icmd; + REG0; + if (!(inb(qbase + 4) & 0x80)) /* false alarm? */ + return; + if (qlcmd == NULL) { /* no command to process? */ + int i; + i = 16; + while (i-- && inb(qbase + 5)); /* maybe also ql_zap() */ + return; + } + icmd = qlcmd; + icmd->result = ql_pcmd(icmd); + qlcmd = NULL; +/* if result is CHECK CONDITION done calls qcommand to request sense */ + (icmd->scsi_done) (icmd); +} +#endif + +/*----------------------------------------------------------------*/ +/* global functions */ +/*----------------------------------------------------------------*/ +/* non queued command */ +#if QL_USE_IRQ +static void qlidone(Scsi_Cmnd * cmd) {}; /* null function */ +#endif + +/* command process */ +int qlogicfas_command(Scsi_Cmnd * cmd) +{ +int k; +#if QL_USE_IRQ + if (qlirq >= 0) { + qlogicfas_queuecommand(cmd, qlidone); + while (qlcmd != NULL); + return cmd->result; + } +#endif +/* non-irq version */ + if (cmd->target == qinitid) + return (DID_BAD_TARGET << 16); + ql_icmd(cmd); + if ((k = ql_wai())) + return (k << 16); + return ql_pcmd(cmd); + +} + +#if QL_USE_IRQ +/*----------------------------------------------------------------*/ +/* queued command */ +int qlogicfas_queuecommand(Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *)) +{ + if(cmd->target == qinitid) { + cmd->result = DID_BAD_TARGET << 16; + done(cmd); + return 0; + } + + cmd->scsi_done = done; +/* wait for the last command's interrupt to finish */ + while (qlcmd != NULL) + barrier(); + ql_icmd(cmd); + return 0; +} +#else +int qlogicfas_queuecommand(Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *)) +{ + return 1; +} +#endif + +#ifdef PCMCIA +/*----------------------------------------------------------------*/ +/* allow PCMCIA code to preset the port */ +/* port should be 0 and irq to -1 respectively for autoprobing */ +void qlogicfas_preset(int port, int irq) +{ + qbase=port; + qlirq=irq; +} +#endif + +/*----------------------------------------------------------------*/ +/* look for qlogic card and init if found */ +int qlogicfas_detect(Scsi_Host_Template * host) +{ +int i, j; /* these are only used by IRQ detect */ +int qltyp; /* type of chip */ +struct Scsi_Host *hreg; /* registered host structure */ +unsigned long flags; + +host->proc_dir = &proc_scsi_qlogicfas; + +/* Qlogic Cards only exist at 0x230 or 0x330 (the chip itself decodes the + address - I check 230 first since MIDI cards are typically at 330 + + Theoretically, two Qlogic cards can coexist in the same system. This + should work by simply using this as a loadable module for the second + card, but I haven't tested this. +*/ + + if( !qbase ) { + for (qbase = 0x230; qbase < 0x430; qbase += 0x100) { + if( check_region( qbase , 0x10 ) ) + continue; + REG1; + if ( ( (inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7 ) + && ( (inb(qbase + 0xe) ^ inb(qbase + 0xe)) == 7 ) ) + break; + } + if (qbase == 0x430) + return 0; + } + else + printk( "Ql: Using preset base address of %03x\n", qbase ); + + qltyp = inb(qbase + 0xe) & 0xf8; + qinitid = host->this_id; + if (qinitid < 0) + qinitid = 7; /* if no ID, use 7 */ + outb(1, qbase + 8); /* set for PIO pseudo DMA */ + REG0; + outb(0x40 | qlcfg8 | qinitid, qbase + 8); /* (ini) bus id, disable scsi rst */ + outb(qlcfg5, qbase + 5); /* select timer */ + outb(qlcfg9, qbase + 9); /* prescaler */ +#if QL_RESET_AT_START + outb( 3 , qbase + 3 ); + REG1; + while( inb( qbase + 0xf ) & 4 ); + REG0; +#endif +#if QL_USE_IRQ +/* IRQ probe - toggle pin and check request pending */ + + if( qlirq == -1 ) { + save_flags( flags ); + cli(); + i = 0xffff; + j = 3; + outb(0x90, qbase + 3); /* illegal command - cause interrupt */ + REG1; + outb(10, 0x20); /* access pending interrupt map */ + outb(10, 0xa0); + while (j--) { + outb(0xb0 | QL_INT_ACTIVE_HIGH , qbase + 0xd); /* int pin off */ + i &= ~(inb(0x20) | (inb(0xa0) << 8)); /* find IRQ off */ + outb(0xb4 | QL_INT_ACTIVE_HIGH , qbase + 0xd); /* int pin on */ + i &= inb(0x20) | (inb(0xa0) << 8); /* find IRQ on */ + } + REG0; + while (inb(qbase + 5)); /* purge int */ + j = -1; + while (i) /* find on bit */ + i >>= 1, j++; /* should check for exactly 1 on */ + qlirq = j; + restore_flags( flags ); + } + else + printk( "Ql: Using preset IRQ %d\n", qlirq ); + + if (qlirq >= 0 && !request_irq(qlirq, ql_ihandl, 0, "qlogicfas", NULL)) + host->can_queue = 1; +#endif + request_region( qbase , 0x10 ,"qlogicfas"); + hreg = scsi_register( host , 0 ); /* no host data */ + hreg->io_port = qbase; + hreg->n_io_port = 16; + hreg->dma_channel = -1; + if( qlirq != -1 ) + hreg->irq = qlirq; + + sprintf(qinfo, "Qlogicfas Driver version 0.45, chip %02X at %03X, IRQ %d, TPdma:%d", + qltyp, qbase, qlirq, QL_TURBO_PDMA ); + host->name = qinfo; + + return 1; +} + +/*----------------------------------------------------------------*/ +/* return bios parameters */ +int qlogicfas_biosparam(Disk * disk, kdev_t dev, int ip[]) +{ +/* This should mimic the DOS Qlogic driver's behavior exactly */ + ip[0] = 0x40; + ip[1] = 0x20; + ip[2] = disk->capacity / (ip[0] * ip[1]); + if (ip[2] > 1024) { + ip[0] = 0xff; + ip[1] = 0x3f; + ip[2] = disk->capacity / (ip[0] * ip[1]); + if (ip[2] > 1023) + ip[2] = 1023; + } + return 0; +} + +/*----------------------------------------------------------------*/ +/* abort command in progress */ +int qlogicfas_abort(Scsi_Cmnd * cmd) +{ + qabort = 1; + ql_zap(); + return 0; +} + +/*----------------------------------------------------------------*/ +/* reset SCSI bus */ +int qlogicfas_reset(Scsi_Cmnd * cmd, unsigned int flags) +{ + qabort = 2; + ql_zap(); + return 1; +} + +/*----------------------------------------------------------------*/ +/* return info string */ +const char *qlogicfas_info(struct Scsi_Host * host) +{ + return qinfo; +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = QLOGICFAS; + +#include "scsi_module.c" +#endif + diff --git a/linux/src/drivers/scsi/qlogicfas.h b/linux/src/drivers/scsi/qlogicfas.h new file mode 100644 index 00000000..5a1dfdbf --- /dev/null +++ b/linux/src/drivers/scsi/qlogicfas.h @@ -0,0 +1,43 @@ +#ifndef _QLOGICFAS_H +#define _QLOGICFAS_H + +int qlogicfas_detect(Scsi_Host_Template * ); +const char * qlogicfas_info(struct Scsi_Host *); +int qlogicfas_command(Scsi_Cmnd *); +int qlogicfas_queuecommand(Scsi_Cmnd *, void (* done)(Scsi_Cmnd *)); +int qlogicfas_abort(Scsi_Cmnd *); +int qlogicfas_reset(Scsi_Cmnd *, unsigned int flags); +int qlogicfas_biosparam(Disk *, kdev_t, int[]); + +#ifndef NULL +#define NULL (0) +#endif + +#define QLOGICFAS { \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + qlogicfas_detect, \ + NULL, \ + qlogicfas_info, \ + qlogicfas_command, \ + qlogicfas_queuecommand, \ + qlogicfas_abort, \ + qlogicfas_reset, \ + NULL, \ + qlogicfas_biosparam, \ + 0, \ + -1, \ + SG_ALL, \ + 1, \ + 0, \ + 0, \ + DISABLE_CLUSTERING \ +} + +#endif /* _QLOGICFAS_H */ + + + diff --git a/linux/src/drivers/scsi/qlogicisp.c b/linux/src/drivers/scsi/qlogicisp.c new file mode 100644 index 00000000..72aa5431 --- /dev/null +++ b/linux/src/drivers/scsi/qlogicisp.c @@ -0,0 +1,1768 @@ +/* + * QLogic ISP1020 Intelligent SCSI Processor Driver (PCI) + * Written by Erik H. Moe, ehm@cris.com + * Copyright 1995, Erik H. Moe + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +/* Renamed and updated to 1.3.x by Michael Griffith <grif@cs.ucr.edu> */ + +/* + * $Date: 1999/04/26 05:54:52 $ + * $Revision: 1.1 $ + * + * $Log: isp1020.c,v $ + * Revision 0.5 1995/09/22 02:23:15 root + * do auto request sense + * + * Revision 0.4 1995/08/07 04:44:33 root + * supply firmware with driver. + * numerous bug fixes/general cleanup of code. + * + * Revision 0.3 1995/07/16 16:15:39 root + * added reset/abort code. + * + * Revision 0.2 1995/06/29 03:14:19 root + * fixed biosparam. + * added queue protocol. + * + * Revision 0.1 1995/06/25 01:55:45 root + * Initial release. + * + */ + +#include <linux/blk.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/unistd.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include "sd.h" +#include "hosts.h" +#include "qlogicisp.h" + +/* Configuration section *****************************************************/ + +/* Set the following macro to 1 to reload the ISP1020's firmware. This is + the latest firmware provided by QLogic. This may be an earlier/later + revision than supplied by your board. */ + +#define RELOAD_FIRMWARE 1 + +/* Set the following macro to 1 to reload the ISP1020's defaults from nvram. + If you are not sure of your settings, leave this alone, the driver will + use a set of 'safe' defaults */ + +#define USE_NVRAM_DEFAULTS 0 + +/* Macros used for debugging */ + +#define DEBUG_ISP1020 0 +#define DEBUG_ISP1020_INT 0 +#define DEBUG_ISP1020_SETUP 0 +#define TRACE_ISP 0 + +#define DEFAULT_LOOP_COUNT 1000000 + +/* End Configuration section *************************************************/ + +#include <linux/module.h> + +#if TRACE_ISP + +# define TRACE_BUF_LEN (32*1024) + +struct { + u_long next; + struct { + u_long time; + u_int index; + u_int addr; + u_char * name; + } buf[TRACE_BUF_LEN]; +} trace; + +#define TRACE(w, i, a) \ +{ \ + unsigned long flags; \ + \ + save_flags(flags); \ + cli(); \ + trace.buf[trace.next].name = (w); \ + trace.buf[trace.next].time = jiffies; \ + trace.buf[trace.next].index = (i); \ + trace.buf[trace.next].addr = (long) (a); \ + trace.next = (trace.next + 1) & (TRACE_BUF_LEN - 1); \ + restore_flags(flags); \ +} + +#else +# define TRACE(w, i, a) +#endif + +#if DEBUG_ISP1020 +#define ENTER(x) printk("isp1020 : entering %s()\n", x); +#define LEAVE(x) printk("isp1020 : leaving %s()\n", x); +#define DEBUG(x) x +#else +#define ENTER(x) +#define LEAVE(x) +#define DEBUG(x) +#endif /* DEBUG_ISP1020 */ + +#if DEBUG_ISP1020_INTR +#define ENTER_INTR(x) printk("isp1020 : entering %s()\n", x); +#define LEAVE_INTR(x) printk("isp1020 : leaving %s()\n", x); +#define DEBUG_INTR(x) x +#else +#define ENTER_INTR(x) +#define LEAVE_INTR(x) +#define DEBUG_INTR(x) +#endif /* DEBUG ISP1020_INTR */ + +#define ISP1020_REV_ID 1 + +#define MAX_TARGETS 16 +#define MAX_LUNS 8 + +/* host configuration and control registers */ +#define HOST_HCCR 0xc0 /* host command and control */ + +/* pci bus interface registers */ +#define PCI_ID_LOW 0x00 /* vendor id */ +#define PCI_ID_HIGH 0x02 /* device id */ +#define ISP_CFG0 0x04 /* configuration register #0 */ +#define ISP_CFG1 0x06 /* configuration register #1 */ +#define PCI_INTF_CTL 0x08 /* pci interface control */ +#define PCI_INTF_STS 0x0a /* pci interface status */ +#define PCI_SEMAPHORE 0x0c /* pci semaphore */ +#define PCI_NVRAM 0x0e /* pci nvram interface */ + +/* mailbox registers */ +#define MBOX0 0x70 /* mailbox 0 */ +#define MBOX1 0x72 /* mailbox 1 */ +#define MBOX2 0x74 /* mailbox 2 */ +#define MBOX3 0x76 /* mailbox 3 */ +#define MBOX4 0x78 /* mailbox 4 */ +#define MBOX5 0x7a /* mailbox 5 */ + +/* mailbox command complete status codes */ +#define MBOX_COMMAND_COMPLETE 0x4000 +#define INVALID_COMMAND 0x4001 +#define HOST_INTERFACE_ERROR 0x4002 +#define TEST_FAILED 0x4003 +#define COMMAND_ERROR 0x4005 +#define COMMAND_PARAM_ERROR 0x4006 + +/* async event status codes */ +#define ASYNC_SCSI_BUS_RESET 0x8001 +#define SYSTEM_ERROR 0x8002 +#define REQUEST_TRANSFER_ERROR 0x8003 +#define RESPONSE_TRANSFER_ERROR 0x8004 +#define REQUEST_QUEUE_WAKEUP 0x8005 +#define EXECUTION_TIMEOUT_RESET 0x8006 + +struct Entry_header { + u_char entry_type; + u_char entry_cnt; + u_char sys_def_1; + u_char flags; +}; + +/* entry header type commands */ +#define ENTRY_COMMAND 1 +#define ENTRY_CONTINUATION 2 +#define ENTRY_STATUS 3 +#define ENTRY_MARKER 4 +#define ENTRY_EXTENDED_COMMAND 5 + +/* entry header flag definitions */ +#define EFLAG_CONTINUATION 1 +#define EFLAG_BUSY 2 +#define EFLAG_BAD_HEADER 4 +#define EFLAG_BAD_PAYLOAD 8 + +struct dataseg { + u_int d_base; + u_int d_count; +}; + +struct Command_Entry { + struct Entry_header hdr; + u_int handle; + u_char target_lun; + u_char target_id; + u_short cdb_length; + u_short control_flags; + u_short rsvd; + u_short time_out; + u_short segment_cnt; + u_char cdb[12]; + struct dataseg dataseg[4]; +}; + +/* command entry control flag definitions */ +#define CFLAG_NODISC 0x01 +#define CFLAG_HEAD_TAG 0x02 +#define CFLAG_ORDERED_TAG 0x04 +#define CFLAG_SIMPLE_TAG 0x08 +#define CFLAG_TAR_RTN 0x10 +#define CFLAG_READ 0x20 +#define CFLAG_WRITE 0x40 + +struct Ext_Command_Entry { + struct Entry_header hdr; + u_int handle; + u_char target_lun; + u_char target_id; + u_short cdb_length; + u_short control_flags; + u_short rsvd; + u_short time_out; + u_short segment_cnt; + u_char cdb[44]; +}; + +struct Continuation_Entry { + struct Entry_header hdr; + u_int reserved; + struct dataseg dataseg[7]; +}; + +struct Marker_Entry { + struct Entry_header hdr; + u_int reserved; + u_char target_lun; + u_char target_id; + u_char modifier; + u_char rsvd; + u_char rsvds[52]; +}; + +/* marker entry modifier definitions */ +#define SYNC_DEVICE 0 +#define SYNC_TARGET 1 +#define SYNC_ALL 2 + +struct Status_Entry { + struct Entry_header hdr; + u_int handle; + u_short scsi_status; + u_short completion_status; + u_short state_flags; + u_short status_flags; + u_short time; + u_short req_sense_len; + u_int residual; + u_char rsvd[8]; + u_char req_sense_data[32]; +}; + +/* status entry completion status definitions */ +#define CS_COMPLETE 0x0000 +#define CS_INCOMPLETE 0x0001 +#define CS_DMA_ERROR 0x0002 +#define CS_TRANSPORT_ERROR 0x0003 +#define CS_RESET_OCCURRED 0x0004 +#define CS_ABORTED 0x0005 +#define CS_TIMEOUT 0x0006 +#define CS_DATA_OVERRUN 0x0007 +#define CS_COMMAND_OVERRUN 0x0008 +#define CS_STATUS_OVERRUN 0x0009 +#define CS_BAD_MESSAGE 0x000a +#define CS_NO_MESSAGE_OUT 0x000b +#define CS_EXT_ID_FAILED 0x000c +#define CS_IDE_MSG_FAILED 0x000d +#define CS_ABORT_MSG_FAILED 0x000e +#define CS_REJECT_MSG_FAILED 0x000f +#define CS_NOP_MSG_FAILED 0x0010 +#define CS_PARITY_ERROR_MSG_FAILED 0x0011 +#define CS_DEVICE_RESET_MSG_FAILED 0x0012 +#define CS_ID_MSG_FAILED 0x0013 +#define CS_UNEXP_BUS_FREE 0x0014 +/* as per app note #83120-514-06a: */ +#define CS_DATA_UNDERRUN 0x0015 +#define CS_INVALID_ENTRY_TYPE 0x001b +#define CS_DEVICE_QUEUE_FULL 0x001c +#define CS_SCSI_PHASE_SKIPPED 0x001d +#define CS_ARS_FAILED 0x001e /* auto Req. Sense failed */ + +/* status entry state flag definitions */ +#define SF_GOT_BUS 0x0100 +#define SF_GOT_TARGET 0x0200 +#define SF_SENT_CDB 0x0400 +#define SF_TRANSFERRED_DATA 0x0800 +#define SF_GOT_STATUS 0x1000 +#define SF_GOT_SENSE 0x2000 + +/* status entry status flag definitions */ +#define STF_DISCONNECT 0x0001 +#define STF_SYNCHRONOUS 0x0002 +#define STF_PARITY_ERROR 0x0004 +#define STF_BUS_RESET 0x0008 +#define STF_DEVICE_RESET 0x0010 +#define STF_ABORTED 0x0020 +#define STF_TIMEOUT 0x0040 +#define STF_NEGOTIATION 0x0080 + +/* interface control commands */ +#define ISP_RESET 0x0001 +#define ISP_EN_INT 0x0002 +#define ISP_EN_RISC 0x0004 + +/* host control commands */ +#define HCCR_NOP 0x0000 +#define HCCR_RESET 0x1000 +#define HCCR_PAUSE 0x2000 +#define HCCR_RELEASE 0x3000 +#define HCCR_SINGLE_STEP 0x4000 +#define HCCR_SET_HOST_INTR 0x5000 +#define HCCR_CLEAR_HOST_INTR 0x6000 +#define HCCR_CLEAR_RISC_INTR 0x7000 +#define HCCR_BP_ENABLE 0x8000 +#define HCCR_BIOS_DISABLE 0x9000 +#define HCCR_TEST_MODE 0xf000 + +#define RISC_BUSY 0x0004 + +/* mailbox commands */ +#define MBOX_NO_OP 0x0000 +#define MBOX_LOAD_RAM 0x0001 +#define MBOX_EXEC_FIRMWARE 0x0002 +#define MBOX_DUMP_RAM 0x0003 +#define MBOX_WRITE_RAM_WORD 0x0004 +#define MBOX_READ_RAM_WORD 0x0005 +#define MBOX_MAILBOX_REG_TEST 0x0006 +#define MBOX_VERIFY_CHECKSUM 0x0007 +#define MBOX_ABOUT_FIRMWARE 0x0008 +#define MBOX_CHECK_FIRMWARE 0x000e +#define MBOX_INIT_REQ_QUEUE 0x0010 +#define MBOX_INIT_RES_QUEUE 0x0011 +#define MBOX_EXECUTE_IOCB 0x0012 +#define MBOX_WAKE_UP 0x0013 +#define MBOX_STOP_FIRMWARE 0x0014 +#define MBOX_ABORT 0x0015 +#define MBOX_ABORT_DEVICE 0x0016 +#define MBOX_ABORT_TARGET 0x0017 +#define MBOX_BUS_RESET 0x0018 +#define MBOX_STOP_QUEUE 0x0019 +#define MBOX_START_QUEUE 0x001a +#define MBOX_SINGLE_STEP_QUEUE 0x001b +#define MBOX_ABORT_QUEUE 0x001c +#define MBOX_GET_DEV_QUEUE_STATUS 0x001d +#define MBOX_GET_FIRMWARE_STATUS 0x001f +#define MBOX_GET_INIT_SCSI_ID 0x0020 +#define MBOX_GET_SELECT_TIMEOUT 0x0021 +#define MBOX_GET_RETRY_COUNT 0x0022 +#define MBOX_GET_TAG_AGE_LIMIT 0x0023 +#define MBOX_GET_CLOCK_RATE 0x0024 +#define MBOX_GET_ACT_NEG_STATE 0x0025 +#define MBOX_GET_ASYNC_DATA_SETUP_TIME 0x0026 +#define MBOX_GET_PCI_PARAMS 0x0027 +#define MBOX_GET_TARGET_PARAMS 0x0028 +#define MBOX_GET_DEV_QUEUE_PARAMS 0x0029 +#define MBOX_SET_INIT_SCSI_ID 0x0030 +#define MBOX_SET_SELECT_TIMEOUT 0x0031 +#define MBOX_SET_RETRY_COUNT 0x0032 +#define MBOX_SET_TAG_AGE_LIMIT 0x0033 +#define MBOX_SET_CLOCK_RATE 0x0034 +#define MBOX_SET_ACTIVE_NEG_STATE 0x0035 +#define MBOX_SET_ASYNC_DATA_SETUP_TIME 0x0036 +#define MBOX_SET_PCI_CONTROL_PARAMS 0x0037 +#define MBOX_SET_TARGET_PARAMS 0x0038 +#define MBOX_SET_DEV_QUEUE_PARAMS 0x0039 +#define MBOX_RETURN_BIOS_BLOCK_ADDR 0x0040 +#define MBOX_WRITE_FOUR_RAM_WORDS 0x0041 +#define MBOX_EXEC_BIOS_IOCB 0x0042 + +#include "qlogicisp_asm.c" + +#define PACKB(a, b) (((a)<<4)|(b)) + +const u_char mbox_param[] = { + PACKB(1, 1), /* MBOX_NO_OP */ + PACKB(5, 5), /* MBOX_LOAD_RAM */ + PACKB(2, 0), /* MBOX_EXEC_FIRMWARE */ + PACKB(5, 5), /* MBOX_DUMP_RAM */ + PACKB(3, 3), /* MBOX_WRITE_RAM_WORD */ + PACKB(2, 3), /* MBOX_READ_RAM_WORD */ + PACKB(6, 6), /* MBOX_MAILBOX_REG_TEST */ + PACKB(2, 3), /* MBOX_VERIFY_CHECKSUM */ + PACKB(1, 3), /* MBOX_ABOUT_FIRMWARE */ + PACKB(0, 0), /* 0x0009 */ + PACKB(0, 0), /* 0x000a */ + PACKB(0, 0), /* 0x000b */ + PACKB(0, 0), /* 0x000c */ + PACKB(0, 0), /* 0x000d */ + PACKB(1, 2), /* MBOX_CHECK_FIRMWARE */ + PACKB(0, 0), /* 0x000f */ + PACKB(5, 5), /* MBOX_INIT_REQ_QUEUE */ + PACKB(6, 6), /* MBOX_INIT_RES_QUEUE */ + PACKB(4, 4), /* MBOX_EXECUTE_IOCB */ + PACKB(2, 2), /* MBOX_WAKE_UP */ + PACKB(1, 6), /* MBOX_STOP_FIRMWARE */ + PACKB(4, 4), /* MBOX_ABORT */ + PACKB(2, 2), /* MBOX_ABORT_DEVICE */ + PACKB(3, 3), /* MBOX_ABORT_TARGET */ + PACKB(2, 2), /* MBOX_BUS_RESET */ + PACKB(2, 3), /* MBOX_STOP_QUEUE */ + PACKB(2, 3), /* MBOX_START_QUEUE */ + PACKB(2, 3), /* MBOX_SINGLE_STEP_QUEUE */ + PACKB(2, 3), /* MBOX_ABORT_QUEUE */ + PACKB(2, 4), /* MBOX_GET_DEV_QUEUE_STATUS */ + PACKB(0, 0), /* 0x001e */ + PACKB(1, 3), /* MBOX_GET_FIRMWARE_STATUS */ + PACKB(1, 2), /* MBOX_GET_INIT_SCSI_ID */ + PACKB(1, 2), /* MBOX_GET_SELECT_TIMEOUT */ + PACKB(1, 3), /* MBOX_GET_RETRY_COUNT */ + PACKB(1, 2), /* MBOX_GET_TAG_AGE_LIMIT */ + PACKB(1, 2), /* MBOX_GET_CLOCK_RATE */ + PACKB(1, 2), /* MBOX_GET_ACT_NEG_STATE */ + PACKB(1, 2), /* MBOX_GET_ASYNC_DATA_SETUP_TIME */ + PACKB(1, 3), /* MBOX_GET_PCI_PARAMS */ + PACKB(2, 4), /* MBOX_GET_TARGET_PARAMS */ + PACKB(2, 4), /* MBOX_GET_DEV_QUEUE_PARAMS */ + PACKB(0, 0), /* 0x002a */ + PACKB(0, 0), /* 0x002b */ + PACKB(0, 0), /* 0x002c */ + PACKB(0, 0), /* 0x002d */ + PACKB(0, 0), /* 0x002e */ + PACKB(0, 0), /* 0x002f */ + PACKB(2, 2), /* MBOX_SET_INIT_SCSI_ID */ + PACKB(2, 2), /* MBOX_SET_SELECT_TIMEOUT */ + PACKB(3, 3), /* MBOX_SET_RETRY_COUNT */ + PACKB(2, 2), /* MBOX_SET_TAG_AGE_LIMIT */ + PACKB(2, 2), /* MBOX_SET_CLOCK_RATE */ + PACKB(2, 2), /* MBOX_SET_ACTIVE_NEG_STATE */ + PACKB(2, 2), /* MBOX_SET_ASYNC_DATA_SETUP_TIME */ + PACKB(3, 3), /* MBOX_SET_PCI_CONTROL_PARAMS */ + PACKB(4, 4), /* MBOX_SET_TARGET_PARAMS */ + PACKB(4, 4), /* MBOX_SET_DEV_QUEUE_PARAMS */ + PACKB(0, 0), /* 0x003a */ + PACKB(0, 0), /* 0x003b */ + PACKB(0, 0), /* 0x003c */ + PACKB(0, 0), /* 0x003d */ + PACKB(0, 0), /* 0x003e */ + PACKB(0, 0), /* 0x003f */ + PACKB(1, 2), /* MBOX_RETURN_BIOS_BLOCK_ADDR */ + PACKB(6, 1), /* MBOX_WRITE_FOUR_RAM_WORDS */ + PACKB(2, 3) /* MBOX_EXEC_BIOS_IOCB */ +}; + +#define MAX_MBOX_COMMAND (sizeof(mbox_param)/sizeof(u_short)) + +struct host_param { + u_short fifo_threshold; + u_short host_adapter_enable; + u_short initiator_scsi_id; + u_short bus_reset_delay; + u_short retry_count; + u_short retry_delay; + u_short async_data_setup_time; + u_short req_ack_active_negation; + u_short data_line_active_negation; + u_short data_dma_burst_enable; + u_short command_dma_burst_enable; + u_short tag_aging; + u_short selection_timeout; + u_short max_queue_depth; +}; + +/* + * Device Flags: + * + * Bit Name + * --------- + * 7 Disconnect Privilege + * 6 Parity Checking + * 5 Wide Data Transfers + * 4 Synchronous Data Transfers + * 3 Tagged Queuing + * 2 Automatic Request Sense + * 1 Stop Queue on Check Condition + * 0 Renegotiate on Error + */ + +struct dev_param { + u_short device_flags; + u_short execution_throttle; + u_short synchronous_period; + u_short synchronous_offset; + u_short device_enable; + u_short reserved; /* pad */ +}; + +/* + * The result queue can be quite a bit smaller since continuation entries + * do not show up there: + */ +#define RES_QUEUE_LEN ((QLOGICISP_REQ_QUEUE_LEN + 1) / 8 - 1) +#define QUEUE_ENTRY_LEN 64 + +struct isp1020_hostdata { + u_char bus; + u_char revision; + u_char device_fn; + struct host_param host_param; + struct dev_param dev_param[MAX_TARGETS]; + + /* result and request queues (shared with isp1020): */ + u_int req_in_ptr; /* index of next request slot */ + u_int res_out_ptr; /* index of next result slot */ + + /* this is here so the queues are nicely aligned */ + long send_marker; /* do we need to send a marker? */ + + char res[RES_QUEUE_LEN+1][QUEUE_ENTRY_LEN]; + char req[QLOGICISP_REQ_QUEUE_LEN+1][QUEUE_ENTRY_LEN]; +}; + +/* queue length's _must_ be power of two: */ +#define QUEUE_DEPTH(in, out, ql) ((in - out) & (ql)) +#define REQ_QUEUE_DEPTH(in, out) QUEUE_DEPTH(in, out, \ + QLOGICISP_REQ_QUEUE_LEN) +#define RES_QUEUE_DEPTH(in, out) QUEUE_DEPTH(in, out, RES_QUEUE_LEN) + +struct Scsi_Host *irq2host[NR_IRQS]; + +static void isp1020_enable_irqs(struct Scsi_Host *); +static void isp1020_disable_irqs(struct Scsi_Host *); +static int isp1020_init(struct Scsi_Host *); +static int isp1020_reset_hardware(struct Scsi_Host *); +static int isp1020_set_defaults(struct Scsi_Host *); +static int isp1020_load_parameters(struct Scsi_Host *); +static int isp1020_mbox_command(struct Scsi_Host *, u_short []); +static int isp1020_return_status(struct Status_Entry *); +static void isp1020_intr_handler(int, void *, struct pt_regs *); + +#if USE_NVRAM_DEFAULTS +static int isp1020_get_defaults(struct Scsi_Host *); +static int isp1020_verify_nvram(struct Scsi_Host *); +static u_short isp1020_read_nvram_word(struct Scsi_Host *, u_short); +#endif + +#if DEBUG_ISP1020 +static void isp1020_print_scsi_cmd(Scsi_Cmnd *); +#endif +#if DEBUG_ISP1020_INTR +static void isp1020_print_status_entry(struct Status_Entry *); +#endif + +static struct proc_dir_entry proc_scsi_isp1020 = { + PROC_SCSI_QLOGICISP, 7, "isp1020", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + + +static inline void isp1020_enable_irqs(struct Scsi_Host *host) +{ + outw(ISP_EN_INT|ISP_EN_RISC, host->io_port + PCI_INTF_CTL); +} + + +static inline void isp1020_disable_irqs(struct Scsi_Host *host) +{ + outw(0x0, host->io_port + PCI_INTF_CTL); +} + + +int isp1020_detect(Scsi_Host_Template *tmpt) +{ + int hosts = 0; + u_short index; + u_char bus, device_fn; + struct Scsi_Host *host; + struct isp1020_hostdata *hostdata; + + ENTER("isp1020_detect"); + + tmpt->proc_dir = &proc_scsi_isp1020; + + if (pcibios_present() == 0) { + printk("qlogicisp : PCI bios not present\n"); + return 0; + } + + memset(irq2host, 0, sizeof(irq2host)); + + for (index = 0; pcibios_find_device(PCI_VENDOR_ID_QLOGIC, + PCI_DEVICE_ID_QLOGIC_ISP1020, + index, &bus, &device_fn) == 0; + index++) + { + host = scsi_register(tmpt, sizeof(struct isp1020_hostdata)); + hostdata = (struct isp1020_hostdata *) host->hostdata; + + memset(hostdata, 0, sizeof(struct isp1020_hostdata)); + hostdata->bus = bus; + hostdata->device_fn = device_fn; + + if (isp1020_init(host) || isp1020_reset_hardware(host) +#if USE_NVRAM_DEFAULTS + || isp1020_get_defaults(host) +#else + || isp1020_set_defaults(host) +#endif /* USE_NVRAM_DEFAULTS */ + || isp1020_load_parameters(host)) { + scsi_unregister(host); + continue; + } + + host->this_id = hostdata->host_param.initiator_scsi_id; + + if (request_irq(host->irq, isp1020_intr_handler, SA_INTERRUPT, + "qlogicisp", NULL)) + { + printk("qlogicisp : interrupt %d already in use\n", + host->irq); + scsi_unregister(host); + continue; + } + + if (check_region(host->io_port, 0xff)) { + printk("qlogicisp : i/o region 0x%04x-0x%04x already " + "in use\n", + host->io_port, host->io_port + 0xff); + free_irq(host->irq, NULL); + scsi_unregister(host); + continue; + } + + request_region(host->io_port, 0xff, "qlogicisp"); + irq2host[host->irq] = host; + + outw(0x0, host->io_port + PCI_SEMAPHORE); + outw(HCCR_CLEAR_RISC_INTR, host->io_port + HOST_HCCR); + isp1020_enable_irqs(host); + + hosts++; + } + + LEAVE("isp1020_detect"); + + return hosts; +} + + +int isp1020_release(struct Scsi_Host *host) +{ + struct isp1020_hostdata *hostdata; + + ENTER("isp1020_release"); + + hostdata = (struct isp1020_hostdata *) host->hostdata; + + outw(0x0, host->io_port + PCI_INTF_CTL); + free_irq(host->irq, NULL); + + release_region(host->io_port, 0xff); + + LEAVE("isp1020_release"); + + return 0; +} + + +const char *isp1020_info(struct Scsi_Host *host) +{ + static char buf[80]; + struct isp1020_hostdata *hostdata; + + ENTER("isp1020_info"); + + hostdata = (struct isp1020_hostdata *) host->hostdata; + sprintf(buf, + "QLogic ISP1020 SCSI on PCI bus %d device %d irq %d base 0x%x", + hostdata->bus, (hostdata->device_fn & 0xf8) >> 3, host->irq, + host->io_port); + + LEAVE("isp1020_info"); + + return buf; +} + + +/* + * The middle SCSI layer ensures that queuecommand never gets invoked + * concurrently with itself or the interrupt handler (though the + * interrupt handler may call this routine as part of + * request-completion handling). + */ +int isp1020_queuecommand(Scsi_Cmnd *Cmnd, void (*done)(Scsi_Cmnd *)) +{ + int i, sg_count, n, num_free; + u_int in_ptr, out_ptr; + struct dataseg * ds; + struct scatterlist *sg; + struct Command_Entry *cmd; + struct Continuation_Entry *cont; + struct Scsi_Host *host; + struct isp1020_hostdata *hostdata; + + ENTER("isp1020_queuecommand"); + + host = Cmnd->host; + hostdata = (struct isp1020_hostdata *) host->hostdata; + Cmnd->scsi_done = done; + + DEBUG(isp1020_print_scsi_cmd(Cmnd)); + + out_ptr = inw(host->io_port + MBOX4); + in_ptr = hostdata->req_in_ptr; + + DEBUG(printk("qlogicisp : request queue depth %d\n", + REQ_QUEUE_DEPTH(in_ptr, out_ptr))); + + cmd = (struct Command_Entry *) &hostdata->req[in_ptr][0]; + in_ptr = (in_ptr + 1) & QLOGICISP_REQ_QUEUE_LEN; + if (in_ptr == out_ptr) { + printk("qlogicisp : request queue overflow\n"); + return 1; + } + + if (hostdata->send_marker) { + struct Marker_Entry *marker; + + TRACE("queue marker", in_ptr, 0); + + DEBUG(printk("qlogicisp : adding marker entry\n")); + marker = (struct Marker_Entry *) cmd; + memset(marker, 0, sizeof(struct Marker_Entry)); + + marker->hdr.entry_type = ENTRY_MARKER; + marker->hdr.entry_cnt = 1; + marker->modifier = SYNC_ALL; + + hostdata->send_marker = 0; + + if (((in_ptr + 1) & QLOGICISP_REQ_QUEUE_LEN) == out_ptr) { + outw(in_ptr, host->io_port + MBOX4); + hostdata->req_in_ptr = in_ptr; + printk("qlogicisp : request queue overflow\n"); + return 1; + } + cmd = (struct Command_Entry *) &hostdata->req[in_ptr][0]; + in_ptr = (in_ptr + 1) & QLOGICISP_REQ_QUEUE_LEN; + } + + TRACE("queue command", in_ptr, Cmnd); + + memset(cmd, 0, sizeof(struct Command_Entry)); + + cmd->hdr.entry_type = ENTRY_COMMAND; + cmd->hdr.entry_cnt = 1; + + cmd->handle = (u_int) virt_to_bus(Cmnd); + cmd->target_lun = Cmnd->lun; + cmd->target_id = Cmnd->target; + cmd->cdb_length = Cmnd->cmd_len; + cmd->control_flags = CFLAG_READ | CFLAG_WRITE; + cmd->time_out = 30; + + memcpy(cmd->cdb, Cmnd->cmnd, Cmnd->cmd_len); + + if (Cmnd->use_sg) { + cmd->segment_cnt = sg_count = Cmnd->use_sg; + sg = (struct scatterlist *) Cmnd->request_buffer; + ds = cmd->dataseg; + + /* fill in first four sg entries: */ + n = sg_count; + if (n > 4) + n = 4; + for (i = 0; i < n; i++) { + ds[i].d_base = (u_int) virt_to_bus(sg->address); + ds[i].d_count = sg->length; + ++sg; + } + sg_count -= 4; + + while (sg_count > 0) { + ++cmd->hdr.entry_cnt; + cont = (struct Continuation_Entry *) + &hostdata->req[in_ptr][0]; + in_ptr = (in_ptr + 1) & QLOGICISP_REQ_QUEUE_LEN; + if (in_ptr == out_ptr) { + printk("isp1020: unexpected request queue " + "overflow\n"); + return 1; + } + TRACE("queue continuation", in_ptr, 0); + cont->hdr.entry_type = ENTRY_CONTINUATION; + cont->hdr.entry_cnt = 0; + cont->hdr.sys_def_1 = 0; + cont->hdr.flags = 0; + cont->reserved = 0; + ds = cont->dataseg; + n = sg_count; + if (n > 7) + n = 7; + for (i = 0; i < n; ++i) { + ds[i].d_base = (u_int)virt_to_bus(sg->address); + ds[i].d_count = sg->length; + ++sg; + } + sg_count -= n; + } + } else { + cmd->dataseg[0].d_base = + (u_int) virt_to_bus(Cmnd->request_buffer); + cmd->dataseg[0].d_count = + (u_int) Cmnd->request_bufflen; + cmd->segment_cnt = 1; + } + + outw(in_ptr, host->io_port + MBOX4); + hostdata->req_in_ptr = in_ptr; + + num_free = QLOGICISP_REQ_QUEUE_LEN - REQ_QUEUE_DEPTH(in_ptr, out_ptr); + host->can_queue = host->host_busy + num_free; + host->sg_tablesize = QLOGICISP_MAX_SG(num_free); + + LEAVE("isp1020_queuecommand"); + + return 0; +} + + +#define ASYNC_EVENT_INTERRUPT 0x01 + +void isp1020_intr_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + Scsi_Cmnd *Cmnd; + struct Status_Entry *sts; + struct Scsi_Host *host; + struct isp1020_hostdata *hostdata; + u_int in_ptr, out_ptr; + u_short status; + + ENTER_INTR("isp1020_intr_handler"); + + host = irq2host[irq]; + if (!host) { + printk("qlogicisp : unexpected interrupt on line %d\n", irq); + return; + } + hostdata = (struct isp1020_hostdata *) host->hostdata; + + DEBUG_INTR(printk("qlogicisp : interrupt on line %d\n", irq)); + + if (!(inw(host->io_port + PCI_INTF_STS) & 0x04)) { + /* spurious interrupts can happen legally */ + DEBUG_INTR(printk("qlogicisp: got spurious interrupt\n")); + return; + } + in_ptr = inw(host->io_port + MBOX5); + outw(HCCR_CLEAR_RISC_INTR, host->io_port + HOST_HCCR); + + if ((inw(host->io_port + PCI_SEMAPHORE) & ASYNC_EVENT_INTERRUPT)) { + status = inw(host->io_port + MBOX0); + + DEBUG_INTR(printk("qlogicisp : mbox completion status: %x\n", + status)); + + switch (status) { + case ASYNC_SCSI_BUS_RESET: + case EXECUTION_TIMEOUT_RESET: + hostdata->send_marker = 1; + break; + case INVALID_COMMAND: + case HOST_INTERFACE_ERROR: + case COMMAND_ERROR: + case COMMAND_PARAM_ERROR: + printk("qlogicisp : bad mailbox return status\n"); + break; + } + outw(0x0, host->io_port + PCI_SEMAPHORE); + } + out_ptr = hostdata->res_out_ptr; + + DEBUG_INTR(printk("qlogicisp : response queue update\n")); + DEBUG_INTR(printk("qlogicisp : response queue depth %d\n", + QUEUE_DEPTH(in_ptr, out_ptr))); + + while (out_ptr != in_ptr) { + sts = (struct Status_Entry *) &hostdata->res[out_ptr][0]; + out_ptr = (out_ptr + 1) & RES_QUEUE_LEN; + + Cmnd = (Scsi_Cmnd *) bus_to_virt(sts->handle); + + TRACE("done", out_ptr, Cmnd); + + if (sts->completion_status == CS_RESET_OCCURRED + || sts->completion_status == CS_ABORTED + || (sts->status_flags & STF_BUS_RESET)) + hostdata->send_marker = 1; + + if (sts->state_flags & SF_GOT_SENSE) + memcpy(Cmnd->sense_buffer, sts->req_sense_data, + sizeof(Cmnd->sense_buffer)); + + DEBUG_INTR(isp1020_print_status_entry(sts)); + + if (sts->hdr.entry_type == ENTRY_STATUS) + Cmnd->result = isp1020_return_status(sts); + else + Cmnd->result = DID_ERROR << 16; + + outw(out_ptr, host->io_port + MBOX5); + (*Cmnd->scsi_done)(Cmnd); + } + hostdata->res_out_ptr = out_ptr; + + LEAVE_INTR("isp1020_intr_handler"); +} + + +static int isp1020_return_status(struct Status_Entry *sts) +{ + int host_status = DID_ERROR; +#if DEBUG_ISP1020_INTR + static char *reason[] = { + "DID_OK", + "DID_NO_CONNECT", + "DID_BUS_BUSY", + "DID_TIME_OUT", + "DID_BAD_TARGET", + "DID_ABORT", + "DID_PARITY", + "DID_ERROR", + "DID_RESET", + "DID_BAD_INTR" + }; +#endif /* DEBUG_ISP1020_INTR */ + + ENTER("isp1020_return_status"); + + DEBUG(printk("qlogicisp : completion status = 0x%04x\n", + sts->completion_status)); + + switch(sts->completion_status) { + case CS_COMPLETE: + host_status = DID_OK; + break; + case CS_INCOMPLETE: + if (!(sts->state_flags & SF_GOT_BUS)) + host_status = DID_NO_CONNECT; + else if (!(sts->state_flags & SF_GOT_TARGET)) + host_status = DID_BAD_TARGET; + else if (!(sts->state_flags & SF_SENT_CDB)) + host_status = DID_ERROR; + else if (!(sts->state_flags & SF_TRANSFERRED_DATA)) + host_status = DID_ERROR; + else if (!(sts->state_flags & SF_GOT_STATUS)) + host_status = DID_ERROR; + else if (!(sts->state_flags & SF_GOT_SENSE)) + host_status = DID_ERROR; + break; + case CS_DMA_ERROR: + case CS_TRANSPORT_ERROR: + host_status = DID_ERROR; + break; + case CS_RESET_OCCURRED: + host_status = DID_RESET; + break; + case CS_ABORTED: + host_status = DID_ABORT; + break; + case CS_TIMEOUT: + host_status = DID_TIME_OUT; + break; + case CS_DATA_OVERRUN: + case CS_COMMAND_OVERRUN: + case CS_STATUS_OVERRUN: + case CS_BAD_MESSAGE: + case CS_NO_MESSAGE_OUT: + case CS_EXT_ID_FAILED: + case CS_IDE_MSG_FAILED: + case CS_ABORT_MSG_FAILED: + case CS_NOP_MSG_FAILED: + case CS_PARITY_ERROR_MSG_FAILED: + case CS_DEVICE_RESET_MSG_FAILED: + case CS_ID_MSG_FAILED: + case CS_UNEXP_BUS_FREE: + case CS_INVALID_ENTRY_TYPE: + case CS_DEVICE_QUEUE_FULL: + case CS_SCSI_PHASE_SKIPPED: + case CS_ARS_FAILED: + host_status = DID_ERROR; + break; + case CS_DATA_UNDERRUN: + host_status = DID_OK; + break; + default: + printk("qlogicisp : unknown completion status 0x%04x\n", + sts->completion_status); + host_status = DID_ERROR; + break; + } + + DEBUG_INTR(printk("qlogicisp : host status (%s) scsi status %x\n", + reason[host_status], sts->scsi_status)); + + LEAVE("isp1020_return_status"); + + return (sts->scsi_status & STATUS_MASK) | (host_status << 16); +} + + +int isp1020_abort(Scsi_Cmnd *Cmnd) +{ + u_short param[6]; + struct Scsi_Host *host; + struct isp1020_hostdata *hostdata; + int return_status = SCSI_ABORT_SUCCESS; + u_int cmdaddr = virt_to_bus(Cmnd); + + ENTER("isp1020_abort"); + + host = Cmnd->host; + hostdata = (struct isp1020_hostdata *) host->hostdata; + + isp1020_disable_irqs(host); + + param[0] = MBOX_ABORT; + param[1] = (((u_short) Cmnd->target) << 8) | Cmnd->lun; + param[2] = cmdaddr >> 16; + param[3] = cmdaddr & 0xffff; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + printk("qlogicisp : scsi abort failure: %x\n", param[0]); + return_status = SCSI_ABORT_ERROR; + } + + isp1020_enable_irqs(host); + + LEAVE("isp1020_abort"); + + return return_status; +} + + +int isp1020_reset(Scsi_Cmnd *Cmnd, unsigned int reset_flags) +{ + u_short param[6]; + struct Scsi_Host *host; + struct isp1020_hostdata *hostdata; + int return_status = SCSI_RESET_SUCCESS; + + ENTER("isp1020_reset"); + + host = Cmnd->host; + hostdata = (struct isp1020_hostdata *) host->hostdata; + + param[0] = MBOX_BUS_RESET; + param[1] = hostdata->host_param.bus_reset_delay; + + isp1020_disable_irqs(host); + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + printk("qlogicisp : scsi bus reset failure: %x\n", param[0]); + return_status = SCSI_RESET_ERROR; + } + + isp1020_enable_irqs(host); + + LEAVE("isp1020_reset"); + + return return_status;; +} + + +int isp1020_biosparam(Disk *disk, kdev_t n, int ip[]) +{ + int size = disk->capacity; + + ENTER("isp1020_biosparam"); + + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + if (ip[2] > 1024) { + ip[0] = 255; + ip[1] = 63; + ip[2] = size / (ip[0] * ip[1]); + if (ip[2] > 1023) + ip[2] = 1023; + } + + LEAVE("isp1020_biosparam"); + + return 0; +} + + +static int isp1020_reset_hardware(struct Scsi_Host *host) +{ + u_short param[6]; + int loop_count; + + ENTER("isp1020_reset_hardware"); + + outw(ISP_RESET, host->io_port + PCI_INTF_CTL); + outw(HCCR_RESET, host->io_port + HOST_HCCR); + outw(HCCR_RELEASE, host->io_port + HOST_HCCR); + outw(HCCR_BIOS_DISABLE, host->io_port + HOST_HCCR); + + loop_count = DEFAULT_LOOP_COUNT; + while (--loop_count && inw(host->io_port + HOST_HCCR) == RISC_BUSY) + barrier(); + if (!loop_count) + printk("qlogicisp: reset_hardware loop timeout\n"); + + outw(0, host->io_port + ISP_CFG1); + +#if DEBUG_ISP1020 + printk("qlogicisp : mbox 0 0x%04x \n", inw(host->io_port + MBOX0)); + printk("qlogicisp : mbox 1 0x%04x \n", inw(host->io_port + MBOX1)); + printk("qlogicisp : mbox 2 0x%04x \n", inw(host->io_port + MBOX2)); + printk("qlogicisp : mbox 3 0x%04x \n", inw(host->io_port + MBOX3)); + printk("qlogicisp : mbox 4 0x%04x \n", inw(host->io_port + MBOX4)); + printk("qlogicisp : mbox 5 0x%04x \n", inw(host->io_port + MBOX5)); +#endif /* DEBUG_ISP1020 */ + + DEBUG(printk("qlogicisp : loading risc ram\n")); + +#if RELOAD_FIRMWARE + { + int i; + for (i = 0; i < risc_code_length01; i++) { + param[0] = MBOX_WRITE_RAM_WORD; + param[1] = risc_code_addr01 + i; + param[2] = risc_code01[i]; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + printk("qlogicisp : firmware load failure\n"); + return 1; + } + } + } +#endif /* RELOAD_FIRMWARE */ + + DEBUG(printk("qlogicisp : verifying checksum\n")); + + param[0] = MBOX_VERIFY_CHECKSUM; + param[1] = risc_code_addr01; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + printk("qlogicisp : ram checksum failure\n"); + return 1; + } + + DEBUG(printk("qlogicisp : executing firmware\n")); + + param[0] = MBOX_EXEC_FIRMWARE; + param[1] = risc_code_addr01; + + isp1020_mbox_command(host, param); + + param[0] = MBOX_ABOUT_FIRMWARE; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + printk("qlogicisp : about firmware failure\n"); + return 1; + } + + DEBUG(printk("qlogicisp : firmware major revision %d\n", param[1])); + DEBUG(printk("qlogicisp : firmware minor revision %d\n", param[2])); + + LEAVE("isp1020_reset_hardware"); + + return 0; +} + + +static int isp1020_init(struct Scsi_Host *sh) +{ + u_int io_base; + struct isp1020_hostdata *hostdata; + u_char bus, device_fn, revision, irq; + u_short vendor_id, device_id, command; + + ENTER("isp1020_init"); + + hostdata = (struct isp1020_hostdata *) sh->hostdata; + bus = hostdata->bus; + device_fn = hostdata->device_fn; + + if (pcibios_read_config_word(bus, device_fn, PCI_VENDOR_ID, &vendor_id) + || pcibios_read_config_word(bus, device_fn, + PCI_DEVICE_ID, &device_id) + || pcibios_read_config_word(bus, device_fn, + PCI_COMMAND, &command) + || pcibios_read_config_dword(bus, device_fn, + PCI_BASE_ADDRESS_0, &io_base) + || pcibios_read_config_byte(bus, device_fn, + PCI_CLASS_REVISION, &revision) + || pcibios_read_config_byte(bus, device_fn, + PCI_INTERRUPT_LINE, &irq)) + { + printk("qlogicisp : error reading PCI configuration\n"); + return 1; + } + + if (vendor_id != PCI_VENDOR_ID_QLOGIC) { + printk("qlogicisp : 0x%04x is not QLogic vendor ID\n", + vendor_id); + return 1; + } + + if (device_id != PCI_DEVICE_ID_QLOGIC_ISP1020) { + printk("qlogicisp : 0x%04x does not match ISP1020 device id\n", + device_id); + return 1; + } + + if (command & PCI_COMMAND_IO && (io_base & 3) == 1) + io_base &= PCI_BASE_ADDRESS_IO_MASK; + else { + printk("qlogicisp : i/o mapping is disabled\n"); + return 1; + } + + if (!(command & PCI_COMMAND_MASTER)) { + printk("qlogicisp : bus mastering is disabled\n"); + return 1; + } + + if (revision != ISP1020_REV_ID) + printk("qlogicisp : new isp1020 revision ID (%d)\n", revision); + + if (inw(io_base + PCI_ID_LOW) != PCI_VENDOR_ID_QLOGIC + || inw(io_base + PCI_ID_HIGH) != PCI_DEVICE_ID_QLOGIC_ISP1020) + { + printk("qlogicisp : can't decode i/o address space at 0x%x\n", + io_base); + return 1; + } + + hostdata->revision = revision; + + sh->irq = irq; + sh->io_port = io_base; + + LEAVE("isp1020_init"); + + return 0; +} + + +#if USE_NVRAM_DEFAULTS + +static int isp1020_get_defaults(struct Scsi_Host *host) +{ + int i; + u_short value; + struct isp1020_hostdata *hostdata = + (struct isp1020_hostdata *) host->hostdata; + + ENTER("isp1020_get_defaults"); + + if (!isp1020_verify_nvram(host)) { + printk("qlogicisp : nvram checksum failure\n"); + printk("qlogicisp : attempting to use default parameters\n"); + return isp1020_set_defaults(host); + } + + value = isp1020_read_nvram_word(host, 2); + hostdata->host_param.fifo_threshold = (value >> 8) & 0x03; + hostdata->host_param.host_adapter_enable = (value >> 11) & 0x01; + hostdata->host_param.initiator_scsi_id = (value >> 12) & 0x0f; + + value = isp1020_read_nvram_word(host, 3); + hostdata->host_param.bus_reset_delay = value & 0xff; + hostdata->host_param.retry_count = value >> 8; + + value = isp1020_read_nvram_word(host, 4); + hostdata->host_param.retry_delay = value & 0xff; + hostdata->host_param.async_data_setup_time = (value >> 8) & 0x0f; + hostdata->host_param.req_ack_active_negation = (value >> 12) & 0x01; + hostdata->host_param.data_line_active_negation = (value >> 13) & 0x01; + hostdata->host_param.data_dma_burst_enable = (value >> 14) & 0x01; + hostdata->host_param.command_dma_burst_enable = (value >> 15); + + value = isp1020_read_nvram_word(host, 5); + hostdata->host_param.tag_aging = value & 0xff; + + value = isp1020_read_nvram_word(host, 6); + hostdata->host_param.selection_timeout = value & 0xffff; + + value = isp1020_read_nvram_word(host, 7); + hostdata->host_param.max_queue_depth = value & 0xffff; + +#if DEBUG_ISP1020_SETUP + printk("qlogicisp : fifo threshold=%d\n", + hostdata->host_param.fifo_threshold); + printk("qlogicisp : initiator scsi id=%d\n", + hostdata->host_param.initiator_scsi_id); + printk("qlogicisp : bus reset delay=%d\n", + hostdata->host_param.bus_reset_delay); + printk("qlogicisp : retry count=%d\n", + hostdata->host_param.retry_count); + printk("qlogicisp : retry delay=%d\n", + hostdata->host_param.retry_delay); + printk("qlogicisp : async data setup time=%d\n", + hostdata->host_param.async_data_setup_time); + printk("qlogicisp : req/ack active negation=%d\n", + hostdata->host_param.req_ack_active_negation); + printk("qlogicisp : data line active negation=%d\n", + hostdata->host_param.data_line_active_negation); + printk("qlogicisp : data DMA burst enable=%d\n", + hostdata->host_param.data_dma_burst_enable); + printk("qlogicisp : command DMA burst enable=%d\n", + hostdata->host_param.command_dma_burst_enable); + printk("qlogicisp : tag age limit=%d\n", + hostdata->host_param.tag_aging); + printk("qlogicisp : selection timeout limit=%d\n", + hostdata->host_param.selection_timeout); + printk("qlogicisp : max queue depth=%d\n", + hostdata->host_param.max_queue_depth); +#endif /* DEBUG_ISP1020_SETUP */ + + for (i = 0; i < MAX_TARGETS; i++) { + + value = isp1020_read_nvram_word(host, 14 + i * 3); + hostdata->dev_param[i].device_flags = value & 0xff; + hostdata->dev_param[i].execution_throttle = value >> 8; + + value = isp1020_read_nvram_word(host, 15 + i * 3); + hostdata->dev_param[i].synchronous_period = value & 0xff; + hostdata->dev_param[i].synchronous_offset = (value >> 8) & 0x0f; + hostdata->dev_param[i].device_enable = (value >> 12) & 0x01; + +#if DEBUG_ISP1020_SETUP + printk("qlogicisp : target 0x%02x\n", i); + printk("qlogicisp : device flags=0x%02x\n", + hostdata->dev_param[i].device_flags); + printk("qlogicisp : execution throttle=%d\n", + hostdata->dev_param[i].execution_throttle); + printk("qlogicisp : synchronous period=%d\n", + hostdata->dev_param[i].synchronous_period); + printk("qlogicisp : synchronous offset=%d\n", + hostdata->dev_param[i].synchronous_offset); + printk("qlogicisp : device enable=%d\n", + hostdata->dev_param[i].device_enable); +#endif /* DEBUG_ISP1020_SETUP */ + } + + LEAVE("isp1020_get_defaults"); + + return 0; +} + + +#define ISP1020_NVRAM_LEN 0x40 +#define ISP1020_NVRAM_SIG1 0x5349 +#define ISP1020_NVRAM_SIG2 0x2050 + +static int isp1020_verify_nvram(struct Scsi_Host *host) +{ + int i; + u_short value; + u_char checksum = 0; + + for (i = 0; i < ISP1020_NVRAM_LEN; i++) { + value = isp1020_read_nvram_word(host, i); + + switch (i) { + case 0: + if (value != ISP1020_NVRAM_SIG1) return 0; + break; + case 1: + if (value != ISP1020_NVRAM_SIG2) return 0; + break; + case 2: + if ((value & 0xff) != 0x02) return 0; + break; + } + checksum += value & 0xff; + checksum += value >> 8; + } + + return (checksum == 0); +} + +#define NVRAM_DELAY() udelay(2) /* 2 microsecond delay */ + + +u_short isp1020_read_nvram_word(struct Scsi_Host *host, u_short byte) +{ + int i; + u_short value, output, input; + + byte &= 0x3f; byte |= 0x0180; + + for (i = 8; i >= 0; i--) { + output = ((byte >> i) & 0x1) ? 0x4 : 0x0; + outw(output | 0x2, host->io_port + PCI_NVRAM); NVRAM_DELAY(); + outw(output | 0x3, host->io_port + PCI_NVRAM); NVRAM_DELAY(); + outw(output | 0x2, host->io_port + PCI_NVRAM); NVRAM_DELAY(); + } + + for (i = 0xf, value = 0; i >= 0; i--) { + value <<= 1; + outw(0x3, host->io_port + PCI_NVRAM); NVRAM_DELAY(); + input = inw(host->io_port + PCI_NVRAM); NVRAM_DELAY(); + outw(0x2, host->io_port + PCI_NVRAM); NVRAM_DELAY(); + if (input & 0x8) value |= 1; + } + + outw(0x0, host->io_port + PCI_NVRAM); NVRAM_DELAY(); + + return value; +} + +#endif /* USE_NVRAM_DEFAULTS */ + + +static int isp1020_set_defaults(struct Scsi_Host *host) +{ + struct isp1020_hostdata *hostdata = + (struct isp1020_hostdata *) host->hostdata; + int i; + + ENTER("isp1020_set_defaults"); + + hostdata->host_param.fifo_threshold = 2; + hostdata->host_param.host_adapter_enable = 1; + hostdata->host_param.initiator_scsi_id = 7; + hostdata->host_param.bus_reset_delay = 3; + hostdata->host_param.retry_count = 0; + hostdata->host_param.retry_delay = 1; + hostdata->host_param.async_data_setup_time = 6; + hostdata->host_param.req_ack_active_negation = 1; + hostdata->host_param.data_line_active_negation = 1; + hostdata->host_param.data_dma_burst_enable = 1; + hostdata->host_param.command_dma_burst_enable = 1; + hostdata->host_param.tag_aging = 8; + hostdata->host_param.selection_timeout = 250; + hostdata->host_param.max_queue_depth = 256; + + for (i = 0; i < MAX_TARGETS; i++) { + hostdata->dev_param[i].device_flags = 0xfd; + hostdata->dev_param[i].execution_throttle = 16; + hostdata->dev_param[i].synchronous_period = 25; + hostdata->dev_param[i].synchronous_offset = 12; + hostdata->dev_param[i].device_enable = 1; + } + + LEAVE("isp1020_set_defaults"); + + return 0; +} + + +static int isp1020_load_parameters(struct Scsi_Host *host) +{ + int i, k; + u_int queue_addr; + u_short param[6]; + u_short isp_cfg1; + unsigned long flags; + struct isp1020_hostdata *hostdata = + (struct isp1020_hostdata *) host->hostdata; + + ENTER("isp1020_load_parameters"); + + save_flags(flags); + cli(); + + outw(hostdata->host_param.fifo_threshold, host->io_port + ISP_CFG1); + + param[0] = MBOX_SET_INIT_SCSI_ID; + param[1] = hostdata->host_param.initiator_scsi_id; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set initiator id failure\n"); + return 1; + } + + param[0] = MBOX_SET_RETRY_COUNT; + param[1] = hostdata->host_param.retry_count; + param[2] = hostdata->host_param.retry_delay; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set retry count failure\n"); + return 1; + } + + param[0] = MBOX_SET_ASYNC_DATA_SETUP_TIME; + param[1] = hostdata->host_param.async_data_setup_time; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : async data setup time failure\n"); + return 1; + } + + param[0] = MBOX_SET_ACTIVE_NEG_STATE; + param[1] = (hostdata->host_param.req_ack_active_negation << 4) + | (hostdata->host_param.data_line_active_negation << 5); + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set active negation state failure\n"); + return 1; + } + + param[0] = MBOX_SET_PCI_CONTROL_PARAMS; + param[1] = hostdata->host_param.data_dma_burst_enable << 1; + param[2] = hostdata->host_param.command_dma_burst_enable << 1; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set pci control parameter failure\n"); + return 1; + } + + isp_cfg1 = inw(host->io_port + ISP_CFG1); + + if (hostdata->host_param.data_dma_burst_enable + || hostdata->host_param.command_dma_burst_enable) + isp_cfg1 |= 0x0004; + else + isp_cfg1 &= 0xfffb; + + outw(isp_cfg1, host->io_port + ISP_CFG1); + + param[0] = MBOX_SET_TAG_AGE_LIMIT; + param[1] = hostdata->host_param.tag_aging; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set tag age limit failure\n"); + return 1; + } + + param[0] = MBOX_SET_SELECT_TIMEOUT; + param[1] = hostdata->host_param.selection_timeout; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set selection timeout failure\n"); + return 1; + } + + for (i = 0; i < MAX_TARGETS; i++) { + + if (!hostdata->dev_param[i].device_enable) + continue; + + param[0] = MBOX_SET_TARGET_PARAMS; + param[1] = i << 8; + param[2] = hostdata->dev_param[i].device_flags << 8; + param[3] = (hostdata->dev_param[i].synchronous_offset << 8) + | hostdata->dev_param[i].synchronous_period; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set target parameter failure\n"); + return 1; + } + + for (k = 0; k < MAX_LUNS; k++) { + + param[0] = MBOX_SET_DEV_QUEUE_PARAMS; + param[1] = (i << 8) | k; + param[2] = hostdata->host_param.max_queue_depth; + param[3] = hostdata->dev_param[i].execution_throttle; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set device queue " + "parameter failure\n"); + return 1; + } + } + } + + queue_addr = (u_int) virt_to_bus(&hostdata->res[0][0]); + + param[0] = MBOX_INIT_RES_QUEUE; + param[1] = RES_QUEUE_LEN + 1; + param[2] = (u_short) (queue_addr >> 16); + param[3] = (u_short) (queue_addr & 0xffff); + param[4] = 0; + param[5] = 0; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set response queue failure\n"); + return 1; + } + + queue_addr = (u_int) virt_to_bus(&hostdata->req[0][0]); + + param[0] = MBOX_INIT_REQ_QUEUE; + param[1] = QLOGICISP_REQ_QUEUE_LEN + 1; + param[2] = (u_short) (queue_addr >> 16); + param[3] = (u_short) (queue_addr & 0xffff); + param[4] = 0; + + isp1020_mbox_command(host, param); + + if (param[0] != MBOX_COMMAND_COMPLETE) { + restore_flags(flags); + printk("qlogicisp : set request queue failure\n"); + return 1; + } + + restore_flags(flags); + + LEAVE("isp1020_load_parameters"); + + return 0; +} + + +/* + * currently, this is only called during initialization or abort/reset, + * at which times interrupts are disabled, so polling is OK, I guess... + */ +static int isp1020_mbox_command(struct Scsi_Host *host, u_short param[]) +{ + int loop_count; + + if (mbox_param[param[0]] == 0) + return 1; + + loop_count = DEFAULT_LOOP_COUNT; + while (--loop_count && inw(host->io_port + HOST_HCCR) & 0x0080) + barrier(); + if (!loop_count) + printk("qlogicisp: mbox_command loop timeout #1\n"); + + switch(mbox_param[param[0]] >> 4) { + case 6: outw(param[5], host->io_port + MBOX5); + case 5: outw(param[4], host->io_port + MBOX4); + case 4: outw(param[3], host->io_port + MBOX3); + case 3: outw(param[2], host->io_port + MBOX2); + case 2: outw(param[1], host->io_port + MBOX1); + case 1: outw(param[0], host->io_port + MBOX0); + } + + outw(0x0, host->io_port + PCI_SEMAPHORE); + outw(HCCR_CLEAR_RISC_INTR, host->io_port + HOST_HCCR); + outw(HCCR_SET_HOST_INTR, host->io_port + HOST_HCCR); + + loop_count = DEFAULT_LOOP_COUNT; + while (--loop_count && !(inw(host->io_port + PCI_INTF_STS) & 0x04)) + barrier(); + if (!loop_count) + printk("qlogicisp: mbox_command loop timeout #2\n"); + + loop_count = DEFAULT_LOOP_COUNT; + while (--loop_count && inw(host->io_port + MBOX0) == 0x04) + barrier(); + if (!loop_count) + printk("qlogicisp: mbox_command loop timeout #3\n"); + + switch(mbox_param[param[0]] & 0xf) { + case 6: param[5] = inw(host->io_port + MBOX5); + case 5: param[4] = inw(host->io_port + MBOX4); + case 4: param[3] = inw(host->io_port + MBOX3); + case 3: param[2] = inw(host->io_port + MBOX2); + case 2: param[1] = inw(host->io_port + MBOX1); + case 1: param[0] = inw(host->io_port + MBOX0); + } + + outw(0x0, host->io_port + PCI_SEMAPHORE); + outw(HCCR_CLEAR_RISC_INTR, host->io_port + HOST_HCCR); + + return 0; +} + + +#if DEBUG_ISP1020_INTR + +void isp1020_print_status_entry(struct Status_Entry *status) +{ + int i; + + printk("qlogicisp : entry count = 0x%02x, type = 0x%02x, flags = 0x%02x\n", + status->hdr.entry_cnt, status->hdr.entry_type, status->hdr.flags); + printk("qlogicisp : scsi status = 0x%04x, completion status = 0x%04x\n", + status->scsi_status, status->completion_status); + printk("qlogicisp : state flags = 0x%04x, status flags = 0x%04x\n", + status->state_flags, status->status_flags); + printk("qlogicisp : time = 0x%04x, request sense length = 0x%04x\n", + status->time, status->req_sense_len); + printk("qlogicisp : residual transfer length = 0x%08x\n", status->residual); + + for (i = 0; i < status->req_sense_len; i++) + printk("qlogicisp : sense data = 0x%02x\n", status->req_sense_data[i]); +} + +#endif /* DEBUG_ISP1020_INTR */ + + +#if DEBUG_ISP1020 + +void isp1020_print_scsi_cmd(Scsi_Cmnd *cmd) +{ + int i; + + printk("qlogicisp : target = 0x%02x, lun = 0x%02x, cmd_len = 0x%02x\n", + cmd->target, cmd->lun, cmd->cmd_len); + printk("qlogicisp : command = "); + for (i = 0; i < cmd->cmd_len; i++) + printk("0x%02x ", cmd->cmnd[i]); + printk("\n"); +} + +#endif /* DEBUG_ISP1020 */ + + +#ifdef MODULE +Scsi_Host_Template driver_template = QLOGICISP; + +#include "scsi_module.c" +#endif /* MODULE */ diff --git a/linux/src/drivers/scsi/qlogicisp.h b/linux/src/drivers/scsi/qlogicisp.h new file mode 100644 index 00000000..e5da4a2c --- /dev/null +++ b/linux/src/drivers/scsi/qlogicisp.h @@ -0,0 +1,99 @@ +/* + * QLogic ISP1020 Intelligent SCSI Processor Driver (PCI) + * Written by Erik H. Moe, ehm@cris.com + * Copyright 1995, Erik H. Moe + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +/* Renamed and updated to 1.3.x by Michael Griffith <grif@cs.ucr.edu> */ + +/* + * $Date: 1999/04/26 05:54:53 $ + * $Revision: 1.1 $ + * + * $Log: isp1020.h,v $ + * Revision 0.5 1995/09/22 02:32:56 root + * do auto request sense + * + * Revision 0.4 1995/08/07 04:48:28 root + * supply firmware with driver. + * numerous bug fixes/general cleanup of code. + * + * Revision 0.3 1995/07/16 16:17:16 root + * added reset/abort code. + * + * Revision 0.2 1995/06/29 03:19:43 root + * fixed biosparam. + * added queue protocol. + * + * Revision 0.1 1995/06/25 01:56:13 root + * Initial release. + * + */ + +#ifndef _QLOGICISP_H +#define _QLOGICISP_H + +/* + * With the qlogic interface, every queue slot can hold a SCSI + * command with up to 4 scatter/gather entries. If we need more + * than 4 entries, continuation entries can be used that hold + * another 7 entries each. Unlike for other drivers, this means + * that the maximum number of scatter/gather entries we can + * support at any given time is a function of the number of queue + * slots available. That is, host->can_queue and host->sg_tablesize + * are dynamic and _not_ independent. This all works fine because + * requests are queued serially and the scatter/gather limit is + * determined for each queue request anew. + */ +#define QLOGICISP_REQ_QUEUE_LEN 63 /* must be power of two - 1 */ +#define QLOGICISP_MAX_SG(ql) (4 + ((ql) > 0) ? 7*((ql) - 1) : 0) + +int isp1020_detect(Scsi_Host_Template *); +int isp1020_release(struct Scsi_Host *); +const char * isp1020_info(struct Scsi_Host *); +int isp1020_queuecommand(Scsi_Cmnd *, void (* done)(Scsi_Cmnd *)); +int isp1020_abort(Scsi_Cmnd *); +int isp1020_reset(Scsi_Cmnd *, unsigned int); +int isp1020_biosparam(Disk *, kdev_t, int[]); + +#ifndef NULL +#define NULL (0) +#endif + +extern struct proc_dir_entry proc_scsi_isp1020; + +#define QLOGICISP { \ + /* next */ NULL, \ + /* usage_count */ NULL, \ + /* proc dir */ NULL, \ + /* procfs info */ NULL, \ + /* name */ NULL, \ + /* detect */ isp1020_detect, \ + /* release */ isp1020_release, \ + /* info */ isp1020_info, \ + /* command */ NULL, \ + /* queuecommand */ isp1020_queuecommand, \ + /* abort */ isp1020_abort, \ + /* reset */ isp1020_reset, \ + /* slave_attach */ NULL, \ + /* bios_param */ isp1020_biosparam, \ + /* can_queue */ QLOGICISP_REQ_QUEUE_LEN, \ + /* this_id */ -1, \ + /* sg_tablesize */ QLOGICISP_MAX_SG(QLOGICISP_REQ_QUEUE_LEN), \ + /* cmd_per_lun */ 1, \ + /* present */ 0, \ + /* unchecked_isa_dma */ 0, \ + /* use_clustering */ DISABLE_CLUSTERING \ +} + +#endif /* _QLOGICISP_H */ diff --git a/linux/src/drivers/scsi/qlogicisp_asm.c b/linux/src/drivers/scsi/qlogicisp_asm.c new file mode 100644 index 00000000..1fafc7ec --- /dev/null +++ b/linux/src/drivers/scsi/qlogicisp_asm.c @@ -0,0 +1,1304 @@ +/* + * Version 2.10 Initiator Firmware (16:13 Oct 18, 1995) + */ + +unsigned short risc_code_version = 2*1024+10; + +unsigned short risc_code_addr01 = 0x1000 ; + +#if RELOAD_FIRMWARE + +unsigned short risc_code01[] = { + 0x0078, 0x1041, 0x0000, 0x283a, 0x0000, 0x2043, 0x4f50, 0x5952, + 0x4947, 0x4854, 0x2031, 0x3939, 0x312c, 0x3139, 0x3932, 0x2c31, + 0x3939, 0x332c, 0x3139, 0x3934, 0x2051, 0x4c4f, 0x4749, 0x4320, + 0x434f, 0x5250, 0x4f52, 0x4154, 0x494f, 0x4e00, 0x2049, 0x5350, + 0x3130, 0x3230, 0x2046, 0x6972, 0x6d77, 0x6172, 0x6520, 0x2056, + 0x6572, 0x7369, 0x6f6e, 0x2030, 0x322e, 0x3130, 0x2020, 0x2043, + 0x7573, 0x746f, 0x6d65, 0x7220, 0x4e6f, 0x2e20, 0x3030, 0x2050, + 0x726f, 0x6475, 0x6374, 0x204e, 0x6f2e, 0x2020, 0x3030, 0x2020, + 0x2400, 0x20b9, 0x1212, 0x2071, 0x0010, 0x70c3, 0x0004, 0x20c9, + 0x43ff, 0x2089, 0x115b, 0x70c7, 0x4953, 0x70cb, 0x5020, 0x70cf, + 0x2020, 0x70d3, 0x0002, 0x3f00, 0x70d6, 0x20c1, 0x0008, 0x2019, + 0x0000, 0x2009, 0xfeff, 0x2100, 0x200b, 0xa5a5, 0xa1ec, 0x7fff, + 0x2d64, 0x206b, 0x0a0a, 0xaddc, 0x3fff, 0x2b54, 0x205b, 0x5050, + 0x2114, 0xa286, 0xa5a5, 0x0040, 0x10b3, 0xa386, 0x000f, 0x0040, + 0x1079, 0x2c6a, 0x2a5a, 0x20c1, 0x0000, 0x2019, 0x000f, 0x0078, + 0x1059, 0x2c6a, 0x2a5a, 0x20c1, 0x0008, 0x2009, 0x7fff, 0x2148, + 0x2944, 0x204b, 0x0a0a, 0xa9bc, 0x3fff, 0x2734, 0x203b, 0x5050, + 0x2114, 0xa286, 0x0a0a, 0x0040, 0x109d, 0x284a, 0x263a, 0x20c1, + 0x0004, 0x2009, 0x3fff, 0x2134, 0x200b, 0x5050, 0x2114, 0xa286, + 0x5050, 0x0040, 0x109e, 0x0078, 0x1163, 0x284a, 0x263a, 0x98c0, + 0xa188, 0x1000, 0x212c, 0x200b, 0xa5a5, 0x2114, 0xa286, 0xa5a5, + 0x0040, 0x10b0, 0x250a, 0xa18a, 0x1000, 0x98c1, 0x0078, 0x10b5, + 0x250a, 0x0078, 0x10b5, 0x2c6a, 0x2a5a, 0x2130, 0xa18a, 0x0040, + 0x2128, 0xa1a2, 0x3900, 0x8424, 0x8424, 0x8424, 0x8424, 0x8424, + 0x8424, 0xa192, 0x4400, 0x2009, 0x0000, 0x2001, 0x002f, 0x1078, + 0x1a70, 0x2218, 0x2079, 0x3900, 0x2fa0, 0x2408, 0x2011, 0x0000, + 0x20a9, 0x0040, 0x42a4, 0x8109, 0x00c0, 0x10d0, 0x7eea, 0x7dde, + 0x8528, 0x7dda, 0x7ce2, 0x7be6, 0x787b, 0x0000, 0x2031, 0x0030, + 0x78c3, 0x0101, 0x780b, 0x0002, 0x780f, 0x0002, 0x784f, 0x0003, + 0x2069, 0x3940, 0x681b, 0x0028, 0x6807, 0x0007, 0x680b, 0x00fa, + 0x680f, 0x0008, 0x6813, 0x0005, 0x681f, 0x0000, 0x6823, 0x0006, + 0x6817, 0x0008, 0x6827, 0x0000, 0x2069, 0x3a00, 0x2011, 0x0020, + 0x2009, 0x0010, 0x680b, 0x0c19, 0x680f, 0x0019, 0x6803, 0xfd00, + 0x6807, 0x0018, 0x6a1a, 0x2d00, 0xa0e8, 0x0008, 0xa290, 0x0004, + 0x8109, 0x00c0, 0x1102, 0x2069, 0x3a80, 0x20a9, 0x0080, 0x680b, + 0x0040, 0x7be8, 0xa386, 0xfeff, 0x00c0, 0x1124, 0x6817, 0x0100, + 0x681f, 0x0064, 0x0078, 0x1128, 0x6817, 0x0064, 0x681f, 0x0002, + 0xade8, 0x0010, 0x0070, 0x112e, 0x0078, 0x1117, 0x1078, 0x1d15, + 0x1078, 0x3366, 0x1078, 0x18a4, 0x1078, 0x37fc, 0x3200, 0xa085, + 0x000d, 0x2090, 0x70c3, 0x0000, 0x0090, 0x1145, 0x70c0, 0xa086, + 0x0002, 0x00c0, 0x1145, 0x1078, 0x1274, 0x1078, 0x1186, 0x78c0, + 0xa005, 0x00c0, 0x1151, 0x1078, 0x1a99, 0x0068, 0x1155, 0x1078, + 0x1c6f, 0x0068, 0x1155, 0x1078, 0x1997, 0x00e0, 0x1145, 0x1078, + 0x369a, 0x0078, 0x1145, 0x1163, 0x1165, 0x1ebb, 0x1ebb, 0x33d7, + 0x33d7, 0x1ebb, 0x1ebb, 0x0078, 0x1163, 0x0078, 0x1165, 0x0078, + 0x1167, 0x0078, 0x1169, 0x2009, 0x0022, 0x2104, 0xa086, 0x4000, + 0x0040, 0x1181, 0x7008, 0x800b, 0x00c8, 0x1181, 0x7007, 0x0002, + 0xa08c, 0x0060, 0x00c0, 0x1182, 0xa084, 0x0008, 0x0040, 0x1181, + 0x087a, 0x097a, 0x70c3, 0x4002, 0x0078, 0x1277, 0x0068, 0x11f1, + 0x2061, 0x0000, 0x6018, 0xa084, 0x0001, 0x00c0, 0x11f1, 0x7814, + 0xa005, 0x00c0, 0x1197, 0x0010, 0x11f2, 0x0078, 0x11f1, 0x2009, + 0x3968, 0x2104, 0xa005, 0x00c0, 0x11f1, 0x2009, 0x3971, 0x200b, + 0x0000, 0x7914, 0xa186, 0x0042, 0x00c0, 0x11bc, 0x7816, 0x2009, + 0x396f, 0x2164, 0x200b, 0x0000, 0x6018, 0x70c6, 0x6014, 0x70ca, + 0x611c, 0xa18c, 0xff00, 0x6020, 0xa084, 0x00ff, 0xa105, 0x70ce, + 0x1078, 0x1896, 0x0078, 0x11ef, 0x7814, 0xa086, 0x0018, 0x00c0, + 0x11c3, 0x1078, 0x1622, 0x7817, 0x0000, 0x2009, 0x396f, 0x2104, + 0xa065, 0x0040, 0x11df, 0x0c7e, 0x609c, 0x2060, 0x1078, 0x18f6, + 0x0c7f, 0x609f, 0x0000, 0x1078, 0x16e9, 0x2009, 0x001c, 0x6087, + 0x0103, 0x1078, 0x181d, 0x00c0, 0x11eb, 0x1078, 0x1896, 0x2009, + 0x396f, 0x200b, 0x0000, 0x2009, 0x3969, 0x2104, 0x200b, 0x0000, + 0xa005, 0x0040, 0x11ef, 0x2001, 0x4005, 0x0078, 0x1276, 0x0078, + 0x1274, 0x007c, 0x70c3, 0x0000, 0x70c7, 0x0000, 0x70cb, 0x0000, + 0x70cf, 0x0000, 0x70c0, 0xa0bc, 0xffc0, 0x00c0, 0x1242, 0x2038, + 0x0079, 0x1202, 0x1274, 0x12cf, 0x1293, 0x12cf, 0x1338, 0x1338, + 0x128a, 0x16fd, 0x1343, 0x1282, 0x1297, 0x1299, 0x129b, 0x129d, + 0x1702, 0x1282, 0x1355, 0x1380, 0x163a, 0x16f7, 0x129f, 0x1569, + 0x158b, 0x15a1, 0x15be, 0x1526, 0x1534, 0x1548, 0x155c, 0x13f3, + 0x1282, 0x13a1, 0x13a7, 0x13ac, 0x13b1, 0x13b7, 0x13bc, 0x13c1, + 0x13c6, 0x13cb, 0x13cf, 0x13e4, 0x13f0, 0x1282, 0x1282, 0x1282, + 0x1282, 0x13ff, 0x1408, 0x1417, 0x143d, 0x1447, 0x144e, 0x1474, + 0x1483, 0x1492, 0x14a4, 0x1506, 0x1516, 0x1282, 0x1282, 0x1282, + 0x1282, 0x151b, 0xa0bc, 0xffa0, 0x00c0, 0x1282, 0x2038, 0xa084, + 0x001f, 0x0079, 0x124b, 0x1719, 0x171c, 0x172c, 0x17a8, 0x17e1, + 0x1282, 0x1282, 0x1282, 0x1282, 0x1282, 0x1282, 0x1282, 0x1282, + 0x1282, 0x1282, 0x1282, 0x12c5, 0x132e, 0x134b, 0x1376, 0x1630, + 0x1282, 0x1282, 0x1282, 0x1282, 0x1282, 0x17f9, 0x1803, 0x1807, + 0x1815, 0x1282, 0x1282, 0x72ca, 0x71c6, 0x2001, 0x4006, 0x0078, + 0x1276, 0x73ce, 0x72ca, 0x71c6, 0x2001, 0x4000, 0x70c2, 0x0068, + 0x1277, 0x2061, 0x0000, 0x601b, 0x0001, 0x2091, 0x5000, 0x2091, + 0x4080, 0x007c, 0x70c3, 0x4001, 0x0078, 0x1277, 0x70c3, 0x4006, + 0x0078, 0x1277, 0x2099, 0x0041, 0x20a1, 0x0041, 0x20a9, 0x0005, + 0x53a3, 0x0078, 0x1274, 0x70c4, 0x70c3, 0x0004, 0x007a, 0x0078, + 0x1274, 0x0078, 0x1274, 0x0078, 0x1274, 0x0078, 0x1274, 0x2091, + 0x8000, 0x70c3, 0x0000, 0x70c7, 0x4953, 0x70cb, 0x5020, 0x70cf, + 0x2020, 0x70d3, 0x0002, 0x3f00, 0x70d6, 0x2079, 0x0000, 0x781b, + 0x0001, 0x2031, 0x0030, 0x2059, 0x1000, 0x2029, 0x0457, 0x2051, + 0x0470, 0x2061, 0x0472, 0x20b9, 0xffff, 0x20c1, 0x0000, 0x2091, + 0x5000, 0x2091, 0x4080, 0x0078, 0x0455, 0x1078, 0x1a04, 0x00c0, + 0x1286, 0x75d8, 0x74dc, 0x75da, 0x74de, 0x0078, 0x12d2, 0x2029, + 0x0000, 0x2520, 0x71d0, 0x72c8, 0x73cc, 0x70c4, 0x20a0, 0x2098, + 0x2031, 0x0030, 0x81ff, 0x0040, 0x1274, 0x7007, 0x0004, 0x731a, + 0x721e, 0x7422, 0x7526, 0x2051, 0x0012, 0x2049, 0x130d, 0x2041, + 0x1274, 0x7003, 0x0002, 0xa786, 0x0001, 0x0040, 0x12f5, 0xa786, + 0x0050, 0x0040, 0x12f5, 0x0078, 0x12fb, 0x2049, 0x131a, 0x2041, + 0x1326, 0x7003, 0x0003, 0x7017, 0x0000, 0x810b, 0x7112, 0x00c8, + 0x1303, 0x7017, 0x0001, 0x7007, 0x0001, 0xa786, 0x0001, 0x0040, + 0x131a, 0xa786, 0x0050, 0x0040, 0x131a, 0x700c, 0xa084, 0x007f, + 0x2009, 0x0040, 0xa102, 0x8004, 0x094a, 0x20a8, 0x26a0, 0x53a6, + 0x0078, 0x116b, 0x700c, 0xa084, 0x007f, 0x0040, 0x131a, 0x80ac, + 0x0048, 0x131a, 0x2698, 0x53a5, 0x0078, 0x116b, 0x700c, 0xa084, + 0x007f, 0x80ac, 0x2698, 0x53a5, 0x0078, 0x1274, 0x1078, 0x1a04, + 0x00c0, 0x1286, 0x75d8, 0x74dc, 0x75da, 0x74de, 0x0078, 0x12d2, + 0x71c4, 0x70c8, 0x2114, 0xa79e, 0x0004, 0x00c0, 0x1340, 0x200a, + 0x72ca, 0x0078, 0x1273, 0x70c7, 0x0002, 0x70cb, 0x000a, 0x70cf, + 0x0000, 0x0078, 0x1274, 0x1078, 0x1a04, 0x00c0, 0x1286, 0x75d8, + 0x76dc, 0x75da, 0x76de, 0x0078, 0x1358, 0x2029, 0x0000, 0x2530, + 0x70c4, 0x72c8, 0x73cc, 0x74d0, 0x70c6, 0x72ca, 0x73ce, 0x74d2, + 0xa005, 0x0040, 0x1370, 0x8001, 0x788a, 0x7a92, 0x7b96, 0x7d9a, + 0x7e9e, 0x7c8e, 0x78c0, 0xa084, 0xfffc, 0x78c2, 0x0078, 0x1374, + 0x78c0, 0xa085, 0x0001, 0x78c2, 0x0078, 0x1274, 0x1078, 0x1a04, + 0x00c0, 0x1286, 0x75d8, 0x76dc, 0x75da, 0x76de, 0x0078, 0x1383, + 0x2029, 0x0000, 0x2530, 0x70c4, 0x72c8, 0x73cc, 0x74d4, 0x70c6, + 0x72ca, 0x73ce, 0x74d6, 0xa005, 0x0040, 0x139b, 0x8001, 0x78a6, + 0x7aae, 0x7bb2, 0x7db6, 0x7eba, 0x7caa, 0x78c0, 0xa084, 0xfcff, + 0x78c2, 0x0078, 0x139f, 0x78c0, 0xa085, 0x0100, 0x78c2, 0x0078, + 0x1274, 0x2009, 0x395f, 0x210c, 0x7ae4, 0x0078, 0x1272, 0x2009, + 0x3941, 0x210c, 0x0078, 0x1273, 0x2009, 0x3942, 0x210c, 0x0078, + 0x1273, 0x2061, 0x3940, 0x610c, 0x6210, 0x0078, 0x1272, 0x2009, + 0x3945, 0x210c, 0x0078, 0x1273, 0x2009, 0x3946, 0x210c, 0x0078, + 0x1273, 0x2009, 0x3947, 0x210c, 0x0078, 0x1273, 0x2009, 0x3948, + 0x210c, 0x0078, 0x1273, 0x7908, 0x7a0c, 0x0078, 0x1272, 0x71c4, + 0x8107, 0xa084, 0x000f, 0x8003, 0x8003, 0x8003, 0xa0e8, 0x3a00, + 0x6a00, 0x6804, 0xa084, 0x0008, 0x0040, 0x13e1, 0x6b08, 0x0078, + 0x13e2, 0x6b0c, 0x0078, 0x1271, 0x77c4, 0x1078, 0x18b4, 0x2091, + 0x8000, 0x6b1c, 0x6a14, 0x2091, 0x8001, 0x2708, 0x0078, 0x1271, + 0x794c, 0x0078, 0x1273, 0x77c4, 0x1078, 0x18b4, 0x2091, 0x8000, + 0x6908, 0x6a18, 0x6b10, 0x2091, 0x8001, 0x0078, 0x1271, 0x71c4, + 0xa182, 0x0010, 0x00c8, 0x126c, 0x1078, 0x1d9b, 0x0078, 0x1271, + 0x71c4, 0xa182, 0x0010, 0x00c8, 0x126c, 0x2011, 0x3941, 0x2204, + 0x007e, 0x2112, 0x1078, 0x1d54, 0x017f, 0x0078, 0x1273, 0x71c4, + 0x2011, 0x1435, 0x20a9, 0x0008, 0x2204, 0xa106, 0x0040, 0x1427, + 0x8210, 0x0070, 0x1425, 0x0078, 0x141c, 0x0078, 0x126c, 0xa292, + 0x1435, 0x027e, 0x2011, 0x3942, 0x2204, 0x2112, 0x017f, 0x007e, + 0x1078, 0x1d60, 0x017f, 0x0078, 0x1273, 0x03e8, 0x00fa, 0x01f4, + 0x02ee, 0x0064, 0x0019, 0x0032, 0x004b, 0x2061, 0x3940, 0x610c, + 0x6210, 0x70c4, 0x600e, 0x70c8, 0x6012, 0x0078, 0x1272, 0x2061, + 0x3940, 0x6114, 0x70c4, 0x6016, 0x0078, 0x1273, 0x71c4, 0x2011, + 0x0004, 0x2019, 0x1212, 0xa186, 0x0028, 0x0040, 0x1467, 0x2011, + 0x0005, 0x2019, 0x1212, 0xa186, 0x0032, 0x0040, 0x1467, 0x2011, + 0x0006, 0x2019, 0x2323, 0xa186, 0x003c, 0x00c0, 0x126c, 0x2061, + 0x3940, 0x6018, 0x007e, 0x611a, 0x23b8, 0x1078, 0x1d71, 0x1078, + 0x37fc, 0x017f, 0x0078, 0x1273, 0x71c4, 0xa184, 0xffcf, 0x00c0, + 0x126c, 0x2011, 0x3947, 0x2204, 0x2112, 0x007e, 0x1078, 0x1d93, + 0x017f, 0x0078, 0x1273, 0x71c4, 0xa182, 0x0010, 0x00c8, 0x126c, + 0x2011, 0x3948, 0x2204, 0x007e, 0x2112, 0x1078, 0x1d82, 0x017f, + 0x0078, 0x1273, 0x71c4, 0x72c8, 0xa184, 0xfffd, 0x00c0, 0x126b, + 0xa284, 0xfffd, 0x00c0, 0x126b, 0x2100, 0x7908, 0x780a, 0x2200, + 0x7a0c, 0x780e, 0x0078, 0x1272, 0x71c4, 0x8107, 0xa084, 0x000f, + 0x8003, 0x8003, 0x8003, 0xa0e8, 0x3a00, 0x2019, 0x0000, 0x72c8, + 0x6800, 0x007e, 0xa226, 0x0040, 0x14d3, 0x6a02, 0xa484, 0x2000, + 0x0040, 0x14bc, 0xa39d, 0x0010, 0xa484, 0x1000, 0x0040, 0x14c2, + 0xa39d, 0x0008, 0xa484, 0x4000, 0x0040, 0x14d3, 0x810f, 0xa284, + 0x4000, 0x0040, 0x14cf, 0x1078, 0x1db5, 0x0078, 0x14d3, 0x1078, + 0x1da7, 0x0078, 0x14d3, 0x72cc, 0x82ff, 0x0040, 0x14ff, 0x6808, + 0xa206, 0x0040, 0x14ff, 0xa2a4, 0x00ff, 0x2061, 0x3940, 0x6118, + 0xa186, 0x0028, 0x0040, 0x14ec, 0xa186, 0x0032, 0x0040, 0x14f2, + 0xa186, 0x003c, 0x0040, 0x14f8, 0xa482, 0x0064, 0x00c8, 0x126d, + 0x0078, 0x14fc, 0xa482, 0x0050, 0x00c8, 0x126d, 0x0078, 0x14fc, + 0xa482, 0x0043, 0x00c8, 0x126d, 0x6a0a, 0xa39d, 0x000a, 0x6804, + 0xa305, 0x6806, 0x027f, 0x6b0c, 0x0078, 0x1271, 0x77c4, 0x1078, + 0x18b4, 0x2091, 0x8000, 0x6a14, 0x6b1c, 0x2091, 0x8001, 0x70c8, + 0x6816, 0x70cc, 0x681e, 0x2708, 0x0078, 0x1271, 0x70c4, 0x794c, + 0x784e, 0x0078, 0x1273, 0x71c4, 0x72c8, 0x73cc, 0xa182, 0x0010, + 0x00c8, 0x126c, 0x1078, 0x1dc3, 0x0078, 0x1271, 0x77c4, 0x1078, + 0x18b4, 0x2091, 0x8000, 0x6a08, 0xa295, 0x0002, 0x6a0a, 0x2091, + 0x8001, 0x2708, 0x0078, 0x1272, 0x77c4, 0x1078, 0x18b4, 0x2091, + 0x8000, 0x6a08, 0xa294, 0xfff9, 0x6a0a, 0x6804, 0xa005, 0x0040, + 0x1543, 0x1078, 0x1cf6, 0x2091, 0x8001, 0x2708, 0x0078, 0x1272, + 0x77c4, 0x1078, 0x18b4, 0x2091, 0x8000, 0x6a08, 0xa295, 0x0004, + 0x6a0a, 0x6804, 0xa005, 0x0040, 0x1557, 0x1078, 0x1cf6, 0x2091, + 0x8001, 0x2708, 0x0078, 0x1272, 0x77c4, 0x2041, 0x0001, 0x2049, + 0x0005, 0x2051, 0x0020, 0x1078, 0x18c1, 0x2708, 0x6a08, 0x0078, + 0x1272, 0x77c4, 0x73c8, 0x72cc, 0x77c6, 0x73ca, 0x72ce, 0x1078, + 0x193c, 0x00c0, 0x1587, 0x6818, 0xa005, 0x0040, 0x1581, 0x2708, + 0x1078, 0x1dd3, 0x00c0, 0x1581, 0x7817, 0x0015, 0x2091, 0x8001, + 0x007c, 0x2091, 0x8001, 0x2001, 0x4005, 0x0078, 0x1276, 0x2091, + 0x8001, 0x0078, 0x1274, 0x77c4, 0x77c6, 0x2061, 0x3940, 0x60a3, + 0x0003, 0x67b6, 0x60c7, 0x000f, 0x2041, 0x0021, 0x2049, 0x0005, + 0x2051, 0x0020, 0x1078, 0x18c1, 0x7817, 0x0016, 0x1078, 0x1cf6, + 0x007c, 0x77c4, 0x77c6, 0xa7bc, 0xff00, 0x2061, 0x3940, 0x60a3, + 0x0002, 0x67b6, 0x60c7, 0x000f, 0x7817, 0x0017, 0x1078, 0x1cf6, + 0x2041, 0x0021, 0x2049, 0x0004, 0x2051, 0x0010, 0x1078, 0x18c1, + 0x8738, 0xa784, 0x0007, 0x00c0, 0x15b6, 0x007c, 0x78c0, 0xa084, + 0x0003, 0x00c0, 0x15e2, 0x2039, 0x0000, 0x2041, 0x0021, 0x2049, + 0x0004, 0x2051, 0x0008, 0x1078, 0x18b4, 0x2091, 0x8000, 0x6808, + 0xa80d, 0x690a, 0x2091, 0x8001, 0x8738, 0xa784, 0x0007, 0x00c0, + 0x15cb, 0xa7bc, 0xff00, 0x873f, 0x8738, 0x873f, 0xa784, 0x0f00, + 0x00c0, 0x15cb, 0x2091, 0x8000, 0x2069, 0x0100, 0x6830, 0xa084, + 0x0040, 0x0040, 0x160b, 0x684b, 0x0004, 0x20a9, 0x0014, 0x6848, + 0xa084, 0x0004, 0x0040, 0x15f8, 0x0070, 0x15f8, 0x0078, 0x15ef, + 0x684b, 0x0009, 0x20a9, 0x0014, 0x6848, 0xa084, 0x0001, 0x0040, + 0x1605, 0x0070, 0x1605, 0x0078, 0x15fc, 0x20a9, 0x00fa, 0x0070, + 0x160b, 0x0078, 0x1607, 0x2079, 0x3900, 0x7817, 0x0018, 0x2061, + 0x3940, 0x60a3, 0x0001, 0x60c7, 0x000f, 0x78c0, 0xa085, 0x0002, + 0x78c2, 0x6808, 0xa084, 0xfffd, 0x680a, 0x681b, 0x0047, 0x2091, + 0x8001, 0x007c, 0x78c0, 0xa084, 0xfffd, 0x78c2, 0xa084, 0x0001, + 0x00c0, 0x162c, 0x1078, 0x197e, 0x71c4, 0x71c6, 0x794a, 0x007c, + 0x1078, 0x1a04, 0x00c0, 0x1286, 0x75d8, 0x74dc, 0x75da, 0x74de, + 0x0078, 0x163d, 0x2029, 0x0000, 0x2520, 0x71c4, 0x73c8, 0x72cc, + 0x71c6, 0x73ca, 0x72ce, 0x2079, 0x3900, 0x1078, 0x188d, 0x0040, + 0x16e5, 0x20a9, 0x0005, 0x20a1, 0x3916, 0x41a1, 0x2009, 0x0040, + 0x1078, 0x1857, 0x0040, 0x1658, 0x1078, 0x1896, 0x0078, 0x16e5, + 0x6004, 0xa084, 0xff00, 0x8007, 0x8009, 0x0040, 0x16b9, 0x0c7e, + 0x2c68, 0x1078, 0x188d, 0x0040, 0x1688, 0x2c00, 0x689e, 0x8109, + 0x00c0, 0x1660, 0x609f, 0x0000, 0x0c7f, 0x0c7e, 0x7218, 0x731c, + 0x7420, 0x7524, 0x2c68, 0x689c, 0xa065, 0x0040, 0x16b8, 0x2009, + 0x0040, 0x1078, 0x1857, 0x00c0, 0x16a1, 0x6004, 0xa084, 0x00ff, + 0xa086, 0x0002, 0x00c0, 0x1688, 0x2d00, 0x6002, 0x0078, 0x166e, + 0x0c7f, 0x0c7e, 0x609c, 0x2060, 0x1078, 0x18f6, 0x0c7f, 0x609f, + 0x0000, 0x1078, 0x16e9, 0x2009, 0x001c, 0x6008, 0xa085, 0x0200, + 0x600a, 0x6004, 0x6086, 0x1078, 0x181d, 0x1078, 0x1896, 0x0078, + 0x16e5, 0x0c7f, 0x0c7e, 0x609c, 0x2060, 0x1078, 0x18f6, 0x0c7f, + 0x609f, 0x0000, 0x1078, 0x16e9, 0x2009, 0x001c, 0x6087, 0x0103, + 0x601b, 0x0003, 0x1078, 0x181d, 0x1078, 0x1896, 0x0078, 0x16e5, + 0x0c7f, 0x74c4, 0x73c8, 0x72cc, 0x6014, 0x7817, 0x0012, 0x0e7e, + 0x2071, 0x3940, 0x70a3, 0x0005, 0x70a7, 0x0000, 0x73aa, 0x72ae, + 0x74b2, 0x70b6, 0x70bb, 0x0000, 0x2c00, 0x70be, 0x70c3, 0x0000, + 0xa02e, 0x2530, 0x611c, 0xa184, 0x0060, 0x0040, 0x16d9, 0x1078, + 0x330a, 0x0e7f, 0x6596, 0x65a6, 0x669a, 0x669a, 0x60af, 0x0000, + 0x60b3, 0x0000, 0x1078, 0x1cf6, 0x007c, 0x70c3, 0x4005, 0x0078, + 0x1277, 0x20a9, 0x0005, 0x2099, 0x3916, 0x530a, 0x2100, 0xa210, + 0xa399, 0x0000, 0xa4a1, 0x0000, 0xa5a9, 0x0000, 0x007c, 0x71c4, + 0x70c7, 0x0000, 0x7906, 0x0078, 0x1274, 0x71c4, 0x71c6, 0x2168, + 0x0078, 0x1704, 0x2069, 0x1000, 0x690c, 0xa016, 0x2d04, 0xa210, + 0x8d68, 0x8109, 0x00c0, 0x1706, 0xa285, 0x0000, 0x00c0, 0x1714, + 0x70c3, 0x4000, 0x0078, 0x1716, 0x70c3, 0x4003, 0x70ca, 0x0078, + 0x1277, 0x79d8, 0x0078, 0x1273, 0x71c4, 0x71c6, 0x2198, 0x20a1, + 0x0042, 0x20a9, 0x0004, 0x53a3, 0x21a0, 0x2099, 0x0042, 0x20a9, + 0x0004, 0x53a3, 0x0078, 0x1274, 0x70c4, 0x2068, 0x2079, 0x3900, + 0x1078, 0x188d, 0x0040, 0x17a4, 0x6007, 0x0001, 0x600b, 0x0000, + 0x602b, 0x0000, 0x601b, 0x0006, 0x6a10, 0xa28c, 0x0007, 0xa284, + 0x00f0, 0x8003, 0x8003, 0x8003, 0x8003, 0xa105, 0x6016, 0xa284, + 0x0800, 0x0040, 0x174f, 0x601b, 0x000a, 0x0078, 0x1755, 0xa284, + 0x1000, 0x0040, 0x1755, 0x601b, 0x000c, 0xa284, 0x0300, 0x0040, + 0x175e, 0x602b, 0x0001, 0x8004, 0x8004, 0x8004, 0xa085, 0x0001, + 0x601e, 0x6023, 0x0000, 0x6027, 0x000a, 0xa284, 0x0400, 0x0040, + 0x176b, 0x602b, 0x0000, 0x20a9, 0x0006, 0xac80, 0x000b, 0x20a0, + 0xad80, 0x0005, 0x2098, 0x53a3, 0xa284, 0x0300, 0x00c0, 0x1780, + 0x6046, 0x604a, 0x604e, 0x6052, 0x6096, 0x609a, 0x0078, 0x178a, + 0x6800, 0x6046, 0x6804, 0x604a, 0x6e08, 0x664e, 0x6d0c, 0x6552, + 0x6596, 0x669a, 0x6014, 0x7817, 0x0042, 0x2c08, 0x2061, 0x3940, + 0x60a3, 0x0005, 0x60a7, 0x0000, 0x60ab, 0x0000, 0x60af, 0x0000, + 0x60b3, 0x0000, 0x60b6, 0x61be, 0xa284, 0x0400, 0x60c2, 0x1078, + 0x32f5, 0x1078, 0x1cf6, 0x007c, 0x70c3, 0x4005, 0x0078, 0x1277, + 0x78f0, 0xa005, 0x0040, 0x1282, 0x2091, 0x8000, 0x70c4, 0x800a, + 0x2011, 0x0010, 0x810c, 0x0048, 0x17ba, 0x3a00, 0xa084, 0xfff7, + 0x0078, 0x17bd, 0x3a00, 0xa085, 0x0008, 0x20d0, 0x0005, 0x0005, + 0xa084, 0xfffb, 0x20d0, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0xa085, 0x0004, 0x20d0, 0x0005, 0x0005, + 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x0005, 0x8211, 0x00c0, + 0x17b2, 0x3a00, 0xa085, 0x0008, 0x20d0, 0x2091, 0x8001, 0x0078, + 0x1274, 0x2011, 0x04fd, 0x2204, 0xa082, 0x0004, 0x0048, 0x17f5, + 0x78f3, 0x0001, 0x2009, 0xff01, 0x200a, 0x2001, 0x000c, 0x20d8, + 0x2001, 0x000c, 0x20d0, 0x0078, 0x1274, 0x2001, 0x4005, 0x0078, + 0x1276, 0x7978, 0x71c6, 0x71c4, 0xa182, 0x0003, 0x00c8, 0x126c, + 0x797a, 0x0078, 0x1274, 0x7978, 0x71c6, 0x0078, 0x1274, 0x796c, + 0x71c6, 0x71c4, 0x796e, 0x7970, 0x71ca, 0x71c8, 0x7972, 0x7974, + 0x71ce, 0x71cc, 0x7976, 0x0078, 0x1274, 0x796c, 0x71c6, 0x7970, + 0x71ca, 0x7974, 0x71ce, 0x0078, 0x1274, 0x700c, 0xa084, 0x00ff, + 0x0040, 0x1829, 0x7007, 0x0004, 0x7004, 0xa084, 0x0004, 0x00c0, + 0x1824, 0x7017, 0x0000, 0x7112, 0x721a, 0x731e, 0x7422, 0x7526, + 0xac80, 0x0001, 0x8108, 0x810c, 0x81a9, 0x8098, 0x20a1, 0x0030, + 0x6084, 0x20a2, 0x53a6, 0x780c, 0xa085, 0x0000, 0x7002, 0x7007, + 0x0001, 0x2009, 0x0022, 0x2104, 0xa084, 0x4000, 0x00c0, 0x1841, + 0x7108, 0x8103, 0x00c8, 0x1841, 0x7014, 0xa005, 0x0040, 0x1841, + 0x7007, 0x0002, 0xa184, 0x0060, 0x7003, 0x0000, 0x007c, 0x700c, + 0xa084, 0x00ff, 0x0040, 0x1863, 0x7007, 0x0004, 0x7004, 0xa084, + 0x0004, 0x00c0, 0x185e, 0x7017, 0x0000, 0x7112, 0x721a, 0x7422, + 0x7526, 0x731e, 0x2099, 0x0030, 0x8108, 0x81ac, 0x780c, 0xa085, + 0x0001, 0x7002, 0x7007, 0x0001, 0x2009, 0x0022, 0x2104, 0xa084, + 0x4000, 0x00c0, 0x1874, 0x7008, 0x800b, 0x00c8, 0x1874, 0x7007, + 0x0002, 0xa08c, 0x0060, 0x00c0, 0x188a, 0xac80, 0x0001, 0x20a0, + 0x53a5, 0xa006, 0x7003, 0x0000, 0x007c, 0x7850, 0xa065, 0x0040, + 0x1895, 0x2c04, 0x7852, 0x2063, 0x0000, 0x007c, 0x0f7e, 0x2079, + 0x3900, 0x7850, 0x2062, 0x2c00, 0xa005, 0x00c0, 0x18a1, 0x1078, + 0x1eac, 0x7852, 0x0f7f, 0x007c, 0x2011, 0x4400, 0x7a52, 0x7be4, + 0x8319, 0x0040, 0x18b1, 0xa280, 0x002f, 0x2012, 0x2010, 0x0078, + 0x18a8, 0x2013, 0x0000, 0x007c, 0xa784, 0x0f00, 0x800c, 0xa784, + 0x0007, 0x8003, 0x8003, 0x8003, 0x8003, 0xa105, 0xa0e8, 0x3a80, + 0x007c, 0x1078, 0x18b4, 0x2900, 0x682a, 0x2a00, 0x682e, 0x6808, + 0xa084, 0xffef, 0xa80d, 0x690a, 0x2091, 0x8000, 0x2009, 0x394f, + 0x210c, 0x6804, 0xa005, 0x0040, 0x18de, 0xa116, 0x00c0, 0x18de, + 0x2060, 0x6000, 0x6806, 0x017e, 0x0078, 0x18e1, 0x2009, 0x0000, + 0x017e, 0x6804, 0xa065, 0x0040, 0x18f0, 0x6000, 0x6806, 0x1078, + 0x1903, 0x1078, 0x1a14, 0x6810, 0x8001, 0x6812, 0x00c0, 0x18e1, + 0x017f, 0x6902, 0x6906, 0x2091, 0x8001, 0x007c, 0xa065, 0x0040, + 0x1902, 0x609c, 0x609f, 0x0000, 0x2008, 0x1078, 0x1896, 0x2100, + 0x0078, 0x18f6, 0x007c, 0x6007, 0x0103, 0x20a9, 0x001c, 0xac80, + 0x0005, 0x20a0, 0x2001, 0x0000, 0x40a4, 0x6828, 0x601a, 0x682c, + 0x6022, 0x007c, 0x0e7e, 0x2071, 0x3940, 0x7040, 0xa08c, 0x0080, + 0x00c0, 0x1920, 0xa088, 0x3980, 0x2d0a, 0x8000, 0x7042, 0xa006, + 0x0e7f, 0x007c, 0x0e7e, 0x2071, 0x3940, 0x2009, 0x3980, 0x7240, + 0x8221, 0x8211, 0x0048, 0x193a, 0x2104, 0x8108, 0xad06, 0x00c0, + 0x1929, 0x8119, 0x211e, 0x8108, 0x8318, 0x8211, 0x00c8, 0x1932, + 0x7442, 0xa006, 0x0e7f, 0x007c, 0x1078, 0x18b4, 0x2091, 0x8000, + 0x6804, 0x781e, 0xa065, 0x0040, 0x197d, 0x0078, 0x194d, 0x2c00, + 0x781e, 0x6000, 0xa065, 0x0040, 0x197d, 0x6010, 0xa306, 0x00c0, + 0x1947, 0x600c, 0xa206, 0x00c0, 0x1947, 0x2c28, 0x6804, 0xac06, + 0x00c0, 0x1964, 0x6000, 0x2060, 0x6806, 0xa005, 0x00c0, 0x1964, + 0x6803, 0x0000, 0x0078, 0x196e, 0x6400, 0x781c, 0x2060, 0x6402, + 0xa486, 0x0000, 0x00c0, 0x196e, 0x2c00, 0x6802, 0x2560, 0x1078, + 0x1903, 0x601b, 0x0005, 0x6023, 0x0020, 0x1078, 0x1a14, 0x6810, + 0x8001, 0x6812, 0x2001, 0xffff, 0xa005, 0x007c, 0x2039, 0x0000, + 0x2041, 0x0021, 0x2049, 0x0004, 0x2051, 0x0008, 0x1078, 0x18c1, + 0x8738, 0xa784, 0x0007, 0x00c0, 0x1986, 0xa7bc, 0xff00, 0x873f, + 0x8738, 0x873f, 0xa784, 0x0f00, 0x00c0, 0x1986, 0x007c, 0x2061, + 0x0000, 0x6018, 0xa084, 0x0001, 0x00c0, 0x19a8, 0x2091, 0x8000, + 0x78d4, 0x78d7, 0x0000, 0x2091, 0x8001, 0xa005, 0x00c0, 0x19a9, + 0x007c, 0xa08c, 0xfff0, 0x0040, 0x19af, 0x1078, 0x1eac, 0x0079, + 0x19b1, 0x19c1, 0x19c3, 0x19c9, 0x19cd, 0x19c1, 0x19d1, 0x19c1, + 0x19d8, 0x19dc, 0x19e0, 0x1a0a, 0x1a0e, 0x19c1, 0x19c1, 0x19c1, + 0x19c1, 0x1078, 0x1eac, 0x1078, 0x197e, 0x2001, 0x8001, 0x0078, + 0x1276, 0x2001, 0x8003, 0x0078, 0x1276, 0x2001, 0x8004, 0x0078, + 0x1276, 0x1078, 0x197e, 0x2001, 0x8006, 0x007c, 0x0078, 0x1276, + 0x2001, 0x8008, 0x0078, 0x1276, 0x2001, 0x8009, 0x0078, 0x1276, + 0x2091, 0x8000, 0x2069, 0x3940, 0x6800, 0xa086, 0x0000, 0x0040, + 0x19ee, 0x2091, 0x8001, 0x78d7, 0x0009, 0x007c, 0x68b4, 0xa0bc, + 0xff00, 0x2091, 0x8000, 0x2041, 0x0021, 0x2049, 0x0004, 0x2051, + 0x0010, 0x1078, 0x18c1, 0x8738, 0xa784, 0x0007, 0x00c0, 0x19f9, + 0x2001, 0x800a, 0x0078, 0x1276, 0x2001, 0x04fd, 0x2004, 0xa086, + 0x0004, 0x007c, 0x2001, 0x800c, 0x0078, 0x1276, 0x1078, 0x197e, + 0x2001, 0x800d, 0x0078, 0x1276, 0x6004, 0x6086, 0x2c08, 0x2063, + 0x0000, 0x787c, 0x8000, 0x787e, 0x7880, 0xa005, 0x7982, 0x0040, + 0x1a24, 0x2c02, 0x0078, 0x1a25, 0x7986, 0x007c, 0x0c7e, 0x2061, + 0x3900, 0x6887, 0x0103, 0x2d08, 0x206b, 0x0000, 0x607c, 0x8000, + 0x607e, 0x6080, 0xa005, 0x6182, 0x0040, 0x1a39, 0x2d02, 0x0078, + 0x1a3a, 0x6186, 0x0c7f, 0x007c, 0x1078, 0x1a4d, 0x0040, 0x1a4c, + 0x0c7e, 0x609c, 0xa065, 0x0040, 0x1a47, 0x1078, 0x18f6, 0x0c7f, + 0x609f, 0x0000, 0x1078, 0x1896, 0x007c, 0x7884, 0xa065, 0x0040, + 0x1a5f, 0x2091, 0x8000, 0x787c, 0x8001, 0x787e, 0x2c04, 0x7886, + 0xa005, 0x00c0, 0x1a5d, 0x7882, 0x8000, 0x2091, 0x8001, 0x007c, + 0x20a9, 0x0010, 0xa006, 0x8004, 0x8086, 0x818e, 0x00c8, 0x1a69, + 0xa200, 0x0070, 0x1a6d, 0x0078, 0x1a64, 0x8086, 0x818e, 0x007c, + 0x157e, 0x20a9, 0x0010, 0xa005, 0x0040, 0x1a93, 0xa11a, 0x00c8, + 0x1a93, 0x8213, 0x818d, 0x0048, 0x1a84, 0xa11a, 0x00c8, 0x1a85, + 0x0070, 0x1a8b, 0x0078, 0x1a79, 0xa11a, 0x2308, 0x8210, 0x0070, + 0x1a8b, 0x0078, 0x1a79, 0x007e, 0x3200, 0xa084, 0xf7ff, 0x2080, + 0x007f, 0x157f, 0x007c, 0x007e, 0x3200, 0xa085, 0x0800, 0x0078, + 0x1a8f, 0x798c, 0x70d0, 0x007e, 0x007f, 0xa106, 0x0040, 0x1ae9, + 0x2091, 0x8000, 0x2071, 0x0020, 0x7004, 0xa005, 0x00c0, 0x1ae9, + 0x7008, 0x7208, 0xa206, 0x00c0, 0x1ae9, 0xa286, 0x0008, 0x00c0, + 0x1ae9, 0x2071, 0x0010, 0x1078, 0x188d, 0x0040, 0x1ae9, 0x7a94, + 0x7b90, 0x7c9c, 0x7d98, 0x8107, 0x8004, 0x8004, 0xa210, 0xa399, + 0x0000, 0x2009, 0x0040, 0x1078, 0x1857, 0x2091, 0x8001, 0x0040, + 0x1ae0, 0x1078, 0x1896, 0x78a0, 0x8000, 0x78a2, 0xa086, 0x0002, + 0x00c0, 0x1ae9, 0x2091, 0x8000, 0x78d7, 0x0002, 0x78a3, 0x0000, + 0x78c0, 0xa085, 0x0003, 0x78c2, 0x2091, 0x8001, 0x0078, 0x1ae9, + 0x78a3, 0x0000, 0x1078, 0x1c38, 0x6004, 0xa084, 0x000f, 0x0079, + 0x1aee, 0x2071, 0x0010, 0x2091, 0x8001, 0x007c, 0x1afe, 0x1b20, + 0x1b46, 0x1afe, 0x1b58, 0x1b0d, 0x1afe, 0x1afe, 0x1afe, 0x1b1a, + 0x1b40, 0x1afe, 0x1afe, 0x1afe, 0x1afe, 0x1afe, 0x2039, 0x0400, + 0x78d0, 0xa705, 0x78d2, 0x6008, 0xa705, 0x600a, 0x1078, 0x1b96, + 0x609c, 0x78ce, 0x1078, 0x1c20, 0x007c, 0x78d0, 0xa084, 0x0100, + 0x0040, 0x1b14, 0x0078, 0x1afe, 0x601c, 0xa085, 0x0080, 0x601e, + 0x0078, 0x1b27, 0x1078, 0x1a04, 0x00c0, 0x1afe, 0x1078, 0x1c52, + 0x78d0, 0xa084, 0x0100, 0x0040, 0x1b27, 0x0078, 0x1afe, 0x78d3, + 0x0000, 0x6004, 0x8007, 0xa084, 0x00ff, 0x78c6, 0x8001, 0x609f, + 0x0000, 0x0040, 0x1b3d, 0x1078, 0x1b96, 0x0040, 0x1b3d, 0x78d0, + 0xa085, 0x0100, 0x78d2, 0x0078, 0x1b3f, 0x1078, 0x1bba, 0x007c, + 0x1078, 0x1a04, 0x00c0, 0x1afe, 0x1078, 0x1c4e, 0x78d0, 0xa08c, + 0x0e00, 0x00c0, 0x1b4f, 0xa084, 0x0100, 0x00c0, 0x1b51, 0x0078, + 0x1afe, 0x1078, 0x1b96, 0x00c0, 0x1b57, 0x1078, 0x1bba, 0x007c, + 0x78d0, 0xa084, 0x0100, 0x0040, 0x1b5f, 0x0078, 0x1afe, 0x78d3, + 0x0000, 0x6714, 0x20a9, 0x0001, 0x6018, 0xa005, 0x0040, 0x1b7a, + 0xa7bc, 0xff00, 0x20a9, 0x0008, 0xa08e, 0x0001, 0x0040, 0x1b7a, + 0x2039, 0x0000, 0x20a9, 0x0080, 0xa08e, 0x0002, 0x0040, 0x1b7a, + 0x0078, 0x1b93, 0x1078, 0x18b4, 0x2d00, 0xa088, 0x0002, 0x2091, + 0x8000, 0x2168, 0x682b, 0x0000, 0x682f, 0x0000, 0x2104, 0xa084, + 0xffde, 0x200a, 0x2100, 0xa088, 0x0010, 0x2091, 0x8001, 0x0070, + 0x1b93, 0x0078, 0x1b7f, 0x1078, 0x1896, 0x007c, 0x78c8, 0xa06d, + 0x00c0, 0x1ba1, 0x2c00, 0x78ca, 0x78ce, 0x609f, 0x0000, 0x0078, + 0x1bad, 0x2c00, 0x689e, 0x609f, 0x0000, 0x78ca, 0x2d00, 0x6002, + 0x78cc, 0xad06, 0x00c0, 0x1bad, 0x6002, 0x78c4, 0x8001, 0x78c6, + 0x00c0, 0x1bb9, 0x78d0, 0xa084, 0x0000, 0x78d2, 0x78cc, 0x2060, + 0xa006, 0x007c, 0xa02e, 0x2530, 0x611c, 0x61a2, 0xa184, 0xc1ff, + 0x601e, 0xa184, 0x0060, 0x0040, 0x1bc9, 0x0e7e, 0x1078, 0x330a, + 0x0e7f, 0x6596, 0x669a, 0x6714, 0x1078, 0x18b4, 0x2091, 0x8000, + 0x6808, 0xa084, 0x0001, 0x0040, 0x1be5, 0x2091, 0x8001, 0x1078, + 0x1903, 0x2091, 0x8000, 0x1078, 0x1a14, 0x2091, 0x8001, 0x78cb, + 0x0000, 0x78cf, 0x0000, 0x0078, 0x1c1f, 0x6024, 0xa096, 0x0001, + 0x00c0, 0x1bec, 0x8000, 0x6026, 0x6a10, 0x6814, 0x2091, 0x8001, + 0xa202, 0x0048, 0x1bfb, 0x0040, 0x1bfb, 0x2039, 0x0200, 0x1078, + 0x1c20, 0x0078, 0x1c1f, 0x2c08, 0x2091, 0x8000, 0x6800, 0xa065, + 0x0040, 0x1c03, 0x6102, 0x6902, 0x00c0, 0x1c07, 0x6906, 0x2160, + 0x6003, 0x0000, 0x6810, 0x8000, 0x6812, 0x2091, 0x8001, 0x6808, + 0xa08c, 0x0040, 0x0040, 0x1c19, 0xa086, 0x0040, 0x680a, 0x1078, + 0x1912, 0x1078, 0x1cf6, 0x78cf, 0x0000, 0x78cb, 0x0000, 0x007c, + 0x6008, 0xa705, 0x600a, 0x2091, 0x8000, 0x1078, 0x1a14, 0x2091, + 0x8001, 0x78cc, 0xa065, 0x0040, 0x1c33, 0x609c, 0x78ce, 0x609f, + 0x0000, 0x0078, 0x1c23, 0x78cb, 0x0000, 0x78cf, 0x0000, 0x007c, + 0x7988, 0x788c, 0x8000, 0xa10a, 0x00c8, 0x1c3f, 0xa006, 0x788e, + 0x70d2, 0x7804, 0xa005, 0x0040, 0x1c4d, 0x8001, 0x7806, 0x00c0, + 0x1c4d, 0x0068, 0x1c4d, 0x2091, 0x4080, 0x007c, 0x2039, 0x1c66, + 0x0078, 0x1c54, 0x2039, 0x1c6c, 0x2704, 0xa005, 0x0040, 0x1c65, + 0xac00, 0x2068, 0x6b08, 0x6c0c, 0x6910, 0x6a14, 0x690a, 0x6a0e, + 0x6b12, 0x6c16, 0x8738, 0x0078, 0x1c54, 0x007c, 0x0003, 0x0009, + 0x000f, 0x0015, 0x001b, 0x0000, 0x0015, 0x001b, 0x0000, 0x0068, + 0x1c87, 0x2029, 0x0000, 0x7884, 0xa065, 0x0040, 0x1c82, 0x1078, + 0x1c88, 0x0040, 0x1c82, 0x1078, 0x1c99, 0x00c0, 0x1c82, 0x8528, + 0x0078, 0x1c73, 0x85ff, 0x0040, 0x1c87, 0x2091, 0x4080, 0x007c, + 0x7ba4, 0x79a8, 0x70d4, 0x007e, 0x007f, 0xa102, 0x00c0, 0x1c93, + 0x2300, 0xa005, 0x007c, 0x0048, 0x1c97, 0xa302, 0x007c, 0x8002, + 0x007c, 0x2091, 0x8000, 0x2071, 0x0020, 0x7004, 0xa005, 0x00c0, + 0x1cdd, 0x7008, 0x7208, 0xa206, 0x00c0, 0x1cdd, 0xa286, 0x0008, + 0x00c0, 0x1cdd, 0x2071, 0x0010, 0x1078, 0x1ce2, 0x2009, 0x001c, + 0x6028, 0xa005, 0x0040, 0x1cb6, 0x2009, 0x0040, 0x1078, 0x181d, + 0x0040, 0x1ccf, 0x78bc, 0x8000, 0x78be, 0xa086, 0x0002, 0x00c0, + 0x1cdd, 0x2091, 0x8000, 0x78d7, 0x0003, 0x78bf, 0x0000, 0x78c0, + 0xa085, 0x0300, 0x78c2, 0x2091, 0x8001, 0x0078, 0x1cdd, 0x78bf, + 0x0000, 0x1078, 0x1a3c, 0x79a4, 0x78a8, 0x8000, 0xa10a, 0x00c8, + 0x1cda, 0xa006, 0x78aa, 0x70d6, 0xa006, 0x2071, 0x0010, 0x2091, + 0x8001, 0x007c, 0x8107, 0x8004, 0x8004, 0x7ab0, 0x7bac, 0x7cb8, + 0x7db4, 0xa210, 0xa399, 0x0000, 0xa4a1, 0x0000, 0xa5a9, 0x0000, + 0x007c, 0x2009, 0x3968, 0x2091, 0x8000, 0x200a, 0x0f7e, 0x2079, + 0x0100, 0x2009, 0x3940, 0x2091, 0x8000, 0x2104, 0xa086, 0x0000, + 0x00c0, 0x1d11, 0x2009, 0x3912, 0x2104, 0xa005, 0x00c0, 0x1d11, + 0x7830, 0xa084, 0x00c0, 0x00c0, 0x1d11, 0x0018, 0x1d11, 0x781b, + 0x0045, 0x2091, 0x8001, 0x0f7f, 0x007c, 0x127e, 0x2091, 0x2300, + 0x2071, 0x3940, 0x2079, 0x0100, 0x784b, 0x000f, 0x2019, 0x3205, + 0x20a1, 0x012b, 0x2304, 0xa005, 0x0040, 0x1d2f, 0x789a, 0x8318, + 0x23ac, 0x8318, 0x2398, 0x53a6, 0x3318, 0x0078, 0x1d22, 0x789b, + 0x0020, 0x20a9, 0x0010, 0x78af, 0x0000, 0x78af, 0x0020, 0x0070, + 0x1d3b, 0x0078, 0x1d33, 0x7003, 0x0000, 0x1078, 0x1e40, 0x7004, + 0xa084, 0x000f, 0xa085, 0x6280, 0x7806, 0x780f, 0x9200, 0x7843, + 0x00d8, 0x7853, 0x0080, 0x780b, 0x0008, 0x7047, 0x397f, 0x7043, + 0x0000, 0x127f, 0x2000, 0x007c, 0xa18c, 0x000f, 0x2011, 0x0101, + 0x2204, 0xa084, 0xfff0, 0xa105, 0x2012, 0x1078, 0x1e40, 0x007c, + 0x2011, 0x0101, 0x20a9, 0x0009, 0x810b, 0x0070, 0x1d69, 0x0078, + 0x1d64, 0xa18c, 0x0e00, 0x2204, 0xa084, 0xf1ff, 0xa105, 0x2012, + 0x007c, 0x2009, 0x0101, 0x20a9, 0x0005, 0x8213, 0x0070, 0x1d7a, + 0x0078, 0x1d75, 0xa294, 0x00e0, 0x2104, 0xa084, 0xff1f, 0xa205, + 0x200a, 0x007c, 0x2011, 0x0101, 0x20a9, 0x000c, 0x810b, 0x0070, + 0x1d8b, 0x0078, 0x1d86, 0xa18c, 0xf000, 0x2204, 0xa084, 0x0fff, + 0xa105, 0x2012, 0x007c, 0x2011, 0x0102, 0x2204, 0xa084, 0xffcf, + 0xa105, 0x2012, 0x007c, 0x8103, 0x8003, 0xa080, 0x0020, 0x0c7e, + 0x2061, 0x0100, 0x609a, 0x62ac, 0x63ac, 0x0c7f, 0x007c, 0x8103, + 0x8003, 0xa080, 0x0022, 0x0c7e, 0x2061, 0x0100, 0x609a, 0x60a4, + 0xa084, 0xffdf, 0x60ae, 0x0c7f, 0x007c, 0x8103, 0x8003, 0xa080, + 0x0022, 0x0c7e, 0x2061, 0x0100, 0x609a, 0x60a4, 0xa085, 0x0020, + 0x60ae, 0x0c7f, 0x007c, 0x8103, 0x8003, 0xa080, 0x0020, 0x0c7e, + 0x2061, 0x0100, 0x609a, 0x60a4, 0x62ae, 0x2010, 0x60a4, 0x63ae, + 0x2018, 0x0c7f, 0x007c, 0x0c7e, 0x0e7e, 0x6818, 0xa005, 0x0040, + 0x1e1c, 0x2061, 0x4380, 0x1078, 0x1e22, 0x0040, 0x1e06, 0x20a9, + 0x0000, 0x2061, 0x4280, 0x0c7e, 0x1078, 0x1e22, 0x0040, 0x1df0, + 0x0c7f, 0x8c60, 0x0070, 0x1dee, 0x0078, 0x1de3, 0x0078, 0x1e1c, + 0x007f, 0xa082, 0x4280, 0x2071, 0x3940, 0x70ba, 0x6020, 0xa085, + 0x0800, 0x6022, 0x2091, 0x8001, 0x71b6, 0x2001, 0x0004, 0x70a2, + 0x70c7, 0x000f, 0x1078, 0x1cf1, 0x0078, 0x1e18, 0x2071, 0x3940, + 0x6020, 0xa085, 0x0800, 0x6022, 0x2091, 0x8001, 0x71b6, 0x2c00, + 0x70be, 0x2001, 0x0006, 0x70a2, 0x70c7, 0x000f, 0x1078, 0x1cf1, + 0x2001, 0x0000, 0x0078, 0x1e1e, 0x2001, 0x0001, 0xa005, 0x0e7f, + 0x0c7f, 0x007c, 0x2091, 0x8000, 0x2c04, 0xa005, 0x0040, 0x1e3b, + 0x2060, 0x6010, 0xa306, 0x00c0, 0x1e38, 0x600c, 0xa206, 0x00c0, + 0x1e38, 0x6014, 0xa106, 0x00c0, 0x1e38, 0xa006, 0x0078, 0x1e3f, + 0x6000, 0x0078, 0x1e25, 0xa085, 0x0001, 0x2091, 0x8001, 0x007c, + 0x2011, 0x3941, 0x220c, 0xa18c, 0x000f, 0x2011, 0x013b, 0x2204, + 0xa084, 0x0100, 0x0040, 0x1e56, 0x2021, 0xff04, 0x2122, 0x810b, + 0x810b, 0x810b, 0x810b, 0xa18d, 0x0f00, 0x2104, 0x007c, 0x0e7e, + 0x68e4, 0xa08c, 0x0020, 0x0040, 0x1eaa, 0xa084, 0x0006, 0x00c0, + 0x1eaa, 0x6014, 0x8007, 0xa084, 0x000f, 0x8003, 0x8003, 0x8003, + 0xa0f0, 0x3a00, 0x7004, 0xa084, 0x000a, 0x00c0, 0x1eaa, 0x7108, + 0xa194, 0xff00, 0x0040, 0x1eaa, 0xa18c, 0x00ff, 0x2001, 0x000c, + 0xa106, 0x0040, 0x1e91, 0x2001, 0x0012, 0xa106, 0x0040, 0x1e95, + 0x2001, 0x0014, 0xa106, 0x0040, 0x1e99, 0x2001, 0x0019, 0xa106, + 0x0040, 0x1e9d, 0x2001, 0x0032, 0xa106, 0x0040, 0x1ea1, 0x0078, + 0x1ea5, 0x2009, 0x0012, 0x0078, 0x1ea7, 0x2009, 0x0014, 0x0078, + 0x1ea7, 0x2009, 0x0019, 0x0078, 0x1ea7, 0x2009, 0x0020, 0x0078, + 0x1ea7, 0x2009, 0x003f, 0x0078, 0x1ea7, 0x2011, 0x0000, 0x2100, + 0xa205, 0x700a, 0x0e7f, 0x007c, 0x2071, 0x0010, 0x70ca, 0x007f, + 0x70c6, 0x70c3, 0x8002, 0x2071, 0x0000, 0x701b, 0x0001, 0x2091, + 0x4080, 0x0078, 0x1eb9, 0x107e, 0x007e, 0x127e, 0x2091, 0x2300, + 0x7f3c, 0x7e58, 0x7c30, 0x7d38, 0x2009, 0x3974, 0x78a0, 0x200a, + 0x8108, 0x250a, 0x8108, 0x240a, 0x8108, 0x260a, 0x8108, 0x270a, + 0xa594, 0x003f, 0xa484, 0x4000, 0x0040, 0x1edc, 0xa784, 0x007c, + 0x00c0, 0x318f, 0x1078, 0x1eac, 0xa49c, 0x000f, 0xa382, 0x0004, + 0x0050, 0x1ee4, 0x1078, 0x1eac, 0x8507, 0xa084, 0x000f, 0x0079, + 0x1ee9, 0x236e, 0x240d, 0x242e, 0x2699, 0x28dd, 0x293b, 0x2984, + 0x29f0, 0x2a8d, 0x2b1a, 0x1f11, 0x1ef9, 0x21c3, 0x2288, 0x28bc, + 0x1ef9, 0x1078, 0x1eac, 0x0018, 0x1ec0, 0x127f, 0x2091, 0x8001, + 0x007f, 0x107f, 0x007c, 0x7003, 0x0000, 0x703f, 0x0000, 0x7030, + 0xa005, 0x0040, 0x1f0d, 0x7033, 0x0000, 0x1078, 0x316a, 0x0018, + 0x1ec0, 0x2009, 0x390f, 0x200b, 0x0000, 0x705c, 0xa005, 0x00c0, + 0x1fe2, 0x70a0, 0xa084, 0x0007, 0x0079, 0x1f1e, 0x2005, 0x1f26, + 0x1f34, 0x1f51, 0x1f73, 0x1fc0, 0x1f99, 0x1f26, 0x7808, 0xa084, + 0xfffd, 0x780a, 0x2009, 0x0047, 0x1078, 0x27c1, 0x00c0, 0x1f32, + 0x7003, 0x0004, 0x0078, 0x1efb, 0x1078, 0x3151, 0x00c0, 0x1f4f, + 0x70b4, 0x8007, 0x7882, 0x789b, 0x0010, 0x78ab, 0x000c, 0x789b, + 0x0060, 0x78ab, 0x0001, 0x785b, 0x0004, 0x2009, 0x00fb, 0x1078, + 0x27bf, 0x00c0, 0x1f4f, 0x7003, 0x0004, 0x70c7, 0x000f, 0x0078, + 0x1efb, 0x1078, 0x3151, 0x00c0, 0x1f71, 0x71b4, 0x8107, 0x7882, + 0x789b, 0x0010, 0xa18c, 0x0007, 0xa18d, 0x00c0, 0x79aa, 0x78ab, + 0x0006, 0x789b, 0x0060, 0x78ab, 0x0002, 0x785b, 0x0004, 0x2009, + 0x00fb, 0x1078, 0x27bf, 0x00c0, 0x1f71, 0x7003, 0x0004, 0x70c7, + 0x000f, 0x0078, 0x1efb, 0x1078, 0x3151, 0x00c0, 0x1f97, 0x71b4, + 0x8107, 0x7882, 0x789b, 0x0010, 0xa18c, 0x0007, 0xa18d, 0x00c0, + 0x79aa, 0x78ab, 0x0020, 0x71b8, 0x79aa, 0x78ab, 0x000d, 0x789b, + 0x0060, 0x78ab, 0x0004, 0x785b, 0x0004, 0x2009, 0x00fb, 0x1078, + 0x27bf, 0x00c0, 0x1f97, 0x7003, 0x0004, 0x70c7, 0x000f, 0x0078, + 0x1efb, 0x1078, 0x3151, 0x00c0, 0x1fbe, 0x71b4, 0x8107, 0x7882, + 0x789b, 0x0010, 0xa18c, 0x0007, 0xa18d, 0x00c0, 0x79aa, 0x78ab, + 0x0006, 0x789b, 0x0060, 0x78ab, 0x0002, 0x785b, 0x0004, 0x2009, + 0x00fb, 0x1078, 0x27bf, 0x00c0, 0x1fbe, 0x70bc, 0x70bf, 0x0000, + 0x2068, 0x703e, 0x7003, 0x0002, 0x70c7, 0x000f, 0x0078, 0x1efb, + 0x1078, 0x3151, 0x00c0, 0x1efb, 0x70bc, 0x2068, 0x1078, 0x31f3, + 0x789b, 0x0010, 0x6814, 0xa084, 0x0007, 0xa085, 0x0080, 0x007e, + 0x007f, 0x78aa, 0x6e1c, 0x067e, 0x067f, 0x2041, 0x0001, 0x70c0, + 0xa084, 0x0400, 0x2001, 0x0004, 0x0040, 0x1fe0, 0x2001, 0x0006, + 0x0078, 0x20e1, 0x1078, 0x3151, 0x00c0, 0x1efb, 0x789b, 0x0010, + 0x705c, 0x2068, 0x1078, 0x31f3, 0x6f14, 0x1078, 0x3099, 0x6008, + 0xa085, 0x0010, 0x600a, 0xad80, 0x0009, 0x2003, 0x0005, 0x6814, + 0xa084, 0x0007, 0xa085, 0x0080, 0x78aa, 0x2031, 0x0020, 0x2041, + 0x0001, 0x2001, 0x0003, 0x0078, 0x20e1, 0x0018, 0x1ec0, 0x7440, + 0xa485, 0x0000, 0x0040, 0x201f, 0xa080, 0x3980, 0x2030, 0x7144, + 0x8108, 0xa12a, 0x0048, 0x2016, 0x2009, 0x3980, 0x2164, 0x6504, + 0x85ff, 0x00c0, 0x202c, 0x8421, 0x00c0, 0x2010, 0x7146, 0x7003, + 0x0000, 0x703f, 0x0000, 0x0078, 0x1efb, 0x7640, 0xa6b0, 0x3980, + 0x7144, 0x2600, 0x0078, 0x201b, 0x7146, 0x2568, 0x2558, 0x753e, + 0x2c50, 0x6708, 0x7736, 0xa784, 0x013f, 0x0040, 0x2059, 0xa784, + 0x0021, 0x00c0, 0x2029, 0xa784, 0x0002, 0x0040, 0x2046, 0xa784, + 0x0004, 0x0040, 0x2029, 0xa7bc, 0xfffb, 0x670a, 0xa784, 0x0008, + 0x00c0, 0x2029, 0xa784, 0x0010, 0x00c0, 0x2029, 0xa784, 0x0100, + 0x0040, 0x2059, 0x6018, 0xa005, 0x00c0, 0x2029, 0xa7bc, 0xfeff, + 0x670a, 0x6823, 0x0000, 0x6e1c, 0xa684, 0x000e, 0x6118, 0x0040, + 0x2069, 0x601c, 0xa102, 0x0048, 0x206c, 0x0040, 0x206c, 0x0078, + 0x2025, 0x81ff, 0x00c0, 0x2025, 0xa784, 0x0080, 0x00c0, 0x2072, + 0x700c, 0x6022, 0x1078, 0x31f3, 0x0018, 0x1ec0, 0x789b, 0x0010, + 0xa046, 0x1078, 0x3151, 0x00c0, 0x1efb, 0x6b14, 0xa39c, 0x0007, + 0xa39d, 0x00c0, 0x704c, 0xa084, 0x8000, 0x0040, 0x208b, 0xa684, + 0x0001, 0x0040, 0x208d, 0xa39c, 0xffbf, 0xa684, 0x0010, 0x0040, + 0x2093, 0xa39d, 0x0020, 0x7baa, 0x8840, 0xa684, 0x000e, 0x00c0, + 0x209e, 0xa7bd, 0x0010, 0x670a, 0x0078, 0x20df, 0x714c, 0xa18c, + 0x0800, 0x0040, 0x2cfc, 0x2011, 0x0021, 0x8004, 0x8004, 0x0048, + 0x20b5, 0x2011, 0x0022, 0x8004, 0x0048, 0x20b5, 0x2011, 0x0020, + 0x8004, 0x0048, 0x20b5, 0x0040, 0x20df, 0x7aaa, 0x8840, 0x1078, + 0x316a, 0x6a14, 0x610c, 0x8108, 0xa18c, 0x00ff, 0xa1e0, 0x4280, + 0x2c64, 0x8cff, 0x0040, 0x20d6, 0x6014, 0xa206, 0x00c0, 0x20c0, + 0x60b8, 0x8001, 0x60ba, 0x00c0, 0x20bb, 0x0c7e, 0x2a60, 0x6008, + 0xa085, 0x0100, 0x600a, 0x0c7f, 0x0078, 0x2005, 0x1078, 0x3151, + 0x00c0, 0x1efb, 0x2a60, 0x610e, 0x79aa, 0x8840, 0x712e, 0x2001, + 0x0001, 0x007e, 0x7150, 0xa184, 0x0018, 0x0040, 0x20fc, 0xa184, + 0x0010, 0x0040, 0x20ef, 0x1078, 0x2ee3, 0x00c0, 0x211f, 0xa184, + 0x0008, 0x0040, 0x20fc, 0x69a0, 0xa184, 0x0600, 0x00c0, 0x20fc, + 0x1078, 0x2ddf, 0x0078, 0x211f, 0x69a0, 0xa184, 0x0800, 0x0040, + 0x2113, 0x0c7e, 0x027e, 0x2960, 0x6000, 0xa085, 0x2000, 0x6002, + 0x6104, 0xa18d, 0x0010, 0x6106, 0x027f, 0x0c7f, 0x1078, 0x2ee3, + 0x00c0, 0x211f, 0x69a0, 0xa184, 0x0200, 0x0040, 0x211b, 0x1078, + 0x2e2e, 0x0078, 0x211f, 0xa184, 0x0400, 0x00c0, 0x20f8, 0x69a0, + 0xa184, 0x1000, 0x0040, 0x212a, 0x6914, 0xa18c, 0xff00, 0x810f, + 0x1078, 0x1da7, 0x007f, 0x7002, 0xa68c, 0x00e0, 0xa684, 0x0060, + 0x0040, 0x2138, 0xa086, 0x0060, 0x00c0, 0x2138, 0xa18d, 0x4000, + 0x88ff, 0x0040, 0x213d, 0xa18d, 0x0004, 0x795a, 0x69b6, 0x789b, + 0x0060, 0x2800, 0x78aa, 0x789b, 0x0061, 0x6818, 0xa08d, 0x8000, + 0xa084, 0x7fff, 0x691a, 0xa68c, 0x0080, 0x0040, 0x215c, 0x70cb, + 0x0000, 0xa08a, 0x000d, 0x0050, 0x215a, 0xa08a, 0x000c, 0x71ca, + 0x2001, 0x000c, 0x800c, 0x71ce, 0x78aa, 0x8008, 0x810c, 0x0040, + 0x2d07, 0xa18c, 0x00f8, 0x00c0, 0x2d07, 0x157e, 0x137e, 0x147e, + 0x20a1, 0x012b, 0x789b, 0x0000, 0x8000, 0x80ac, 0xad80, 0x000b, + 0x2098, 0x53a6, 0x147f, 0x137f, 0x157f, 0x6814, 0x8007, 0x7882, + 0x6d94, 0x7dd6, 0x7dde, 0x6e98, 0x7ed2, 0x7eda, 0x7830, 0xa084, + 0x00c0, 0x00c0, 0x2185, 0x0098, 0x218d, 0x6008, 0xa084, 0xffef, + 0x600a, 0x1078, 0x316a, 0x0078, 0x1f03, 0x7200, 0xa284, 0x0007, + 0xa086, 0x0001, 0x00c0, 0x219a, 0x781b, 0x004a, 0x1078, 0x316a, + 0x0078, 0x21ab, 0x6ab4, 0xa295, 0x2000, 0x7a5a, 0x781b, 0x004a, + 0x1078, 0x316a, 0x7200, 0x2500, 0xa605, 0x0040, 0x21ab, 0xa284, + 0x0007, 0x1079, 0x21b9, 0xa284, 0x0007, 0xa086, 0x0001, 0x00c0, + 0x1efb, 0x6018, 0x8000, 0x601a, 0xad80, 0x0009, 0x7032, 0x0078, + 0x1efb, 0x21c1, 0x3571, 0x3571, 0x3560, 0x3571, 0x21c1, 0x3560, + 0x21c1, 0x1078, 0x1eac, 0x7808, 0xa084, 0xfffd, 0x780a, 0x0f7e, + 0x2079, 0x3900, 0x78c0, 0x0f7f, 0xa084, 0x0001, 0x0040, 0x21e9, + 0x70a0, 0xa086, 0x0001, 0x00c0, 0x21d8, 0x70a2, 0x0078, 0x226c, + 0x70a0, 0xa086, 0x0005, 0x00c0, 0x21e7, 0x70bc, 0x2068, 0x681b, + 0x0004, 0x6817, 0x0000, 0x6820, 0xa085, 0x0008, 0x6822, 0x70a3, + 0x0000, 0x157e, 0x2011, 0x0004, 0x71a0, 0xa186, 0x0001, 0x0040, + 0x2207, 0xa186, 0x0007, 0x00c0, 0x21fb, 0x2009, 0x3935, 0x200b, + 0x0005, 0x0078, 0x2207, 0x2009, 0x3913, 0x2104, 0x2009, 0x3912, + 0x200a, 0x2009, 0x3935, 0x200b, 0x0001, 0x0078, 0x2209, 0x70a3, + 0x0000, 0x1078, 0x32f5, 0x20a9, 0x0010, 0x2039, 0x0000, 0x1078, + 0x2f9a, 0xa7b8, 0x0100, 0x0070, 0x2217, 0x0078, 0x220f, 0x7000, + 0x0079, 0x221a, 0x2248, 0x2231, 0x2231, 0x2224, 0x2248, 0x2248, + 0x2248, 0x2222, 0x1078, 0x1eac, 0x2021, 0x3957, 0x2404, 0xa005, + 0x0040, 0x2248, 0xad06, 0x00c0, 0x2231, 0x6800, 0x2022, 0x0078, + 0x2241, 0x6820, 0xa084, 0x0001, 0x00c0, 0x223d, 0x6f14, 0x1078, + 0x3099, 0x1078, 0x2cc9, 0x0078, 0x2241, 0x7054, 0x2060, 0x6800, + 0x6002, 0x6a1a, 0x6820, 0xa085, 0x0008, 0x6822, 0x1078, 0x1a26, + 0x2021, 0x4380, 0x1078, 0x2272, 0x2021, 0x3957, 0x1078, 0x2272, + 0x20a9, 0x0000, 0x2021, 0x4280, 0x1078, 0x2272, 0x8420, 0x0070, + 0x225b, 0x0078, 0x2254, 0x20a9, 0x0080, 0x2061, 0x3a80, 0x6018, + 0x6110, 0xa102, 0x6012, 0x601b, 0x0000, 0xace0, 0x0010, 0x0070, + 0x226b, 0x0078, 0x225f, 0x157f, 0x7003, 0x0000, 0x703f, 0x0000, + 0x0078, 0x1efb, 0x047e, 0x2404, 0xa005, 0x0040, 0x2284, 0x2068, + 0x6800, 0x007e, 0x6a1a, 0x6820, 0xa085, 0x0008, 0x6822, 0x1078, + 0x1a26, 0x007f, 0x0078, 0x2274, 0x047f, 0x2023, 0x0000, 0x007c, + 0xa282, 0x0003, 0x0050, 0x228e, 0x1078, 0x1eac, 0x2300, 0x0079, + 0x2291, 0x2294, 0x2311, 0x232e, 0xa282, 0x0002, 0x0040, 0x229a, + 0x1078, 0x1eac, 0x70a0, 0x70a3, 0x0000, 0x70c7, 0x0000, 0x0079, + 0x22a1, 0x22a9, 0x22a9, 0x22ab, 0x22e9, 0x2d0d, 0x22a9, 0x22e9, + 0x22a9, 0x1078, 0x1eac, 0x77b4, 0x1078, 0x2f9a, 0x77b4, 0xa7bc, + 0x0f00, 0x1078, 0x3099, 0x6018, 0xa005, 0x0040, 0x22e0, 0x2021, + 0x4380, 0x2009, 0x0004, 0x2011, 0x0010, 0x1078, 0x2349, 0x0040, + 0x22e0, 0x157e, 0x20a9, 0x0000, 0x2021, 0x4280, 0x047e, 0x2009, + 0x0004, 0x2011, 0x0010, 0x1078, 0x2349, 0x047f, 0x0040, 0x22d5, + 0x8420, 0x0070, 0x22d5, 0x0078, 0x22c6, 0x157f, 0x2021, 0x3957, + 0x2009, 0x0004, 0x2011, 0x0010, 0x1078, 0x2349, 0x0040, 0x22e0, + 0x8738, 0xa784, 0x0007, 0x00c0, 0x22b1, 0x0078, 0x1f03, 0x0078, + 0x1f03, 0x77b4, 0x1078, 0x3099, 0x6018, 0xa005, 0x0040, 0x230f, + 0x2021, 0x4380, 0x2009, 0x0005, 0x2011, 0x0020, 0x1078, 0x2349, + 0x0040, 0x230f, 0x157e, 0x20a9, 0x0000, 0x2021, 0x4280, 0x047e, + 0x2009, 0x0005, 0x2011, 0x0020, 0x1078, 0x2349, 0x047f, 0x0040, + 0x230e, 0x8420, 0x0070, 0x230e, 0x0078, 0x22ff, 0x157f, 0x0078, + 0x1f03, 0x2200, 0x0079, 0x2314, 0x2317, 0x2319, 0x2319, 0x1078, + 0x1eac, 0x2009, 0x0012, 0x70a0, 0xa086, 0x0002, 0x0040, 0x2322, + 0x2009, 0x000e, 0x6818, 0xa084, 0x8000, 0x0040, 0x2328, 0x691a, + 0x70a3, 0x0000, 0x70a7, 0x0001, 0x0078, 0x311c, 0x2200, 0x0079, + 0x2331, 0x2336, 0x2319, 0x2334, 0x1078, 0x1eac, 0x1078, 0x27ce, + 0x7000, 0xa086, 0x0001, 0x00c0, 0x2c9f, 0x1078, 0x2cdf, 0x6008, + 0xa084, 0xffef, 0x600a, 0x1078, 0x2c92, 0x0040, 0x2c9f, 0x0078, + 0x2005, 0x2404, 0xa005, 0x0040, 0x236a, 0x2068, 0x2d04, 0x007e, + 0x6814, 0xa706, 0x0040, 0x2358, 0x2d20, 0x007f, 0x0078, 0x234a, + 0x007f, 0x2022, 0x691a, 0x6820, 0xa205, 0x6822, 0x1078, 0x1a26, + 0x6010, 0x8001, 0x6012, 0x6008, 0xa084, 0xffef, 0x600a, 0x1078, + 0x2cdf, 0x007c, 0xa085, 0x0001, 0x0078, 0x2369, 0x2300, 0x0079, + 0x2371, 0x2376, 0x2374, 0x23c2, 0x1078, 0x1eac, 0x78e4, 0xa005, + 0x00d0, 0x238a, 0x0018, 0x238a, 0xa084, 0x0007, 0x0079, 0x2380, + 0x239b, 0x23a8, 0x238e, 0x2388, 0x3144, 0x3144, 0x2388, 0x23b5, + 0x1078, 0x1eac, 0x2001, 0x0003, 0x0078, 0x26ad, 0x6818, 0xa084, + 0x8000, 0x0040, 0x2395, 0x681b, 0x001d, 0x1078, 0x2f7d, 0x781b, + 0x0053, 0x0078, 0x1efb, 0x6818, 0xa084, 0x8000, 0x0040, 0x23a2, + 0x681b, 0x001d, 0x1078, 0x2f7d, 0x781b, 0x00de, 0x0078, 0x1efb, + 0x6818, 0xa084, 0x8000, 0x0040, 0x23af, 0x681b, 0x001d, 0x1078, + 0x2f7d, 0x781b, 0x00e5, 0x0078, 0x1efb, 0x6818, 0xa084, 0x8000, + 0x0040, 0x23bc, 0x681b, 0x001d, 0x1078, 0x2f7d, 0x781b, 0x009c, + 0x0078, 0x1efb, 0xa584, 0x000f, 0x00c0, 0x23e1, 0x1078, 0x27ce, + 0x7000, 0x0079, 0x23cb, 0x23d3, 0x23d5, 0x23d3, 0x2c9f, 0x2c9f, + 0x2c9f, 0x2c9f, 0x23d3, 0x1078, 0x1eac, 0x1078, 0x2cdf, 0x6008, + 0xa084, 0xffef, 0x600a, 0x1078, 0x2c92, 0x0040, 0x2c9f, 0x0078, + 0x2005, 0x79e4, 0xa005, 0x00d0, 0x238a, 0x0018, 0x238a, 0xa184, + 0x0007, 0x0079, 0x23eb, 0x23fb, 0x2401, 0x23f5, 0x23f3, 0x3144, + 0x3144, 0x23f3, 0x313c, 0x1078, 0x1eac, 0x1078, 0x2f85, 0x781b, + 0x0053, 0x0078, 0x1efb, 0x1078, 0x2f85, 0x781b, 0x00de, 0x0078, + 0x1efb, 0x1078, 0x2f85, 0x781b, 0x00e5, 0x0078, 0x1efb, 0x1078, + 0x2f85, 0x781b, 0x009c, 0x0078, 0x1efb, 0x2300, 0x0079, 0x2410, + 0x2415, 0x2413, 0x2417, 0x1078, 0x1eac, 0x0078, 0x29f0, 0x681b, + 0x0008, 0x78a3, 0x0000, 0x79e4, 0xa184, 0x0030, 0x0040, 0x29f0, + 0xa184, 0x0007, 0x0079, 0x2424, 0x242c, 0x2401, 0x238e, 0x311c, + 0x3144, 0x3144, 0x242c, 0x313c, 0x1078, 0x1eac, 0xa282, 0x0005, + 0x0050, 0x2434, 0x1078, 0x1eac, 0x2300, 0x0079, 0x2437, 0x243a, + 0x265e, 0x266a, 0x2200, 0x0079, 0x243d, 0x2457, 0x2444, 0x2457, + 0x2442, 0x2643, 0x1078, 0x1eac, 0x789b, 0x0018, 0x78a8, 0xa084, + 0x00ff, 0xa082, 0x0020, 0x0048, 0x2f69, 0xa08a, 0x0004, 0x00c8, + 0x2f69, 0x0079, 0x2453, 0x2f69, 0x2f69, 0x2f69, 0x2f23, 0x789b, + 0x0018, 0x79a8, 0xa184, 0x0080, 0x0040, 0x246c, 0xa184, 0x0018, + 0x0040, 0x2468, 0x0078, 0x2f69, 0x7000, 0xa005, 0x00c0, 0x2462, + 0x2011, 0x0004, 0x0078, 0x2b28, 0xa184, 0x00ff, 0xa08a, 0x0010, + 0x00c8, 0x2f69, 0x0079, 0x2474, 0x2486, 0x2484, 0x249e, 0x24a2, + 0x255a, 0x2f69, 0x2f69, 0x255c, 0x2f69, 0x2f69, 0x263f, 0x263f, + 0x2f69, 0x2f69, 0x2f69, 0x2641, 0x1078, 0x1eac, 0xa684, 0x1000, + 0x0040, 0x2493, 0x2001, 0x0300, 0x8000, 0x8000, 0x783a, 0x781b, + 0x0099, 0x0078, 0x1efb, 0x6818, 0xa084, 0x8000, 0x0040, 0x249c, + 0x681b, 0x001d, 0x0078, 0x248a, 0x0078, 0x311c, 0x681b, 0x001d, + 0x0078, 0x2f75, 0x6920, 0xa184, 0x8000, 0x00c0, 0x24ae, 0x68af, + 0x0000, 0x68b3, 0x0000, 0xa18d, 0x8000, 0x6922, 0xa684, 0x1800, + 0x00c0, 0x24ed, 0x6820, 0xa084, 0x0001, 0x00c0, 0x24f3, 0x6818, + 0xa086, 0x0008, 0x00c0, 0x24be, 0x681b, 0x0000, 0xa684, 0x0400, + 0x0040, 0x2556, 0xa684, 0x0080, 0x0040, 0x24e9, 0x70cb, 0x0000, + 0x6818, 0xa084, 0x003f, 0xa08a, 0x000d, 0x0050, 0x24e9, 0xa08a, + 0x000c, 0x71ca, 0x2001, 0x000c, 0x800c, 0x71ce, 0x789b, 0x0061, + 0x78aa, 0x157e, 0x137e, 0x147e, 0x20a1, 0x012b, 0x789b, 0x0000, + 0x8000, 0x80ac, 0xad80, 0x000b, 0x2098, 0x53a6, 0x147f, 0x137f, + 0x157f, 0x781b, 0x0056, 0x0078, 0x1efb, 0xa684, 0x1000, 0x0040, + 0x24f3, 0x0078, 0x1efb, 0xa684, 0x0060, 0x0040, 0x2552, 0xa684, + 0x0800, 0x0040, 0x2552, 0xa684, 0x8000, 0x00c0, 0x2503, 0x69b0, + 0x6aac, 0x0078, 0x251d, 0xa6b4, 0x7fff, 0x7e5a, 0x6eb6, 0x789b, + 0x0074, 0x7aac, 0x79ac, 0x78ac, 0x801b, 0x00c8, 0x2510, 0x8000, + 0xa084, 0x003f, 0xa108, 0xa291, 0x0000, 0x6b98, 0x2100, 0xa302, + 0x68b2, 0x6b94, 0x2200, 0xa303, 0x68ae, 0xa684, 0x4000, 0x0040, + 0x2525, 0xa6b4, 0xbfff, 0x7e5a, 0x6eb6, 0xa006, 0x1078, 0x362f, + 0x6ab0, 0x69ac, 0x6c98, 0x6b94, 0x2200, 0xa105, 0x0040, 0x2534, + 0x2200, 0xa422, 0x2100, 0xa31b, 0x6caa, 0x7cd2, 0x6ba6, 0x7bd6, + 0x2300, 0xa405, 0x00c0, 0x2544, 0xa6b5, 0x4000, 0x7e5a, 0x6eb6, + 0x781b, 0x0065, 0x0078, 0x1efb, 0x781b, 0x0065, 0x2200, 0xa115, + 0x00c0, 0x254e, 0x1078, 0x3571, 0x0078, 0x1efb, 0x1078, 0x35a6, + 0x0078, 0x1efb, 0x781b, 0x0068, 0x0078, 0x1efb, 0x781b, 0x0056, + 0x0078, 0x1efb, 0x1078, 0x1eac, 0x0078, 0x25b1, 0x6920, 0xa184, + 0x0100, 0x0040, 0x2570, 0xa18c, 0xfeff, 0x6922, 0x0c7e, 0x7048, + 0x2060, 0x6004, 0xa084, 0xfff5, 0x6006, 0x0c7f, 0x0078, 0x25a0, + 0xa184, 0x0200, 0x0040, 0x25a0, 0xa18c, 0xfdff, 0x6922, 0x0c7e, + 0x7048, 0x2060, 0x6004, 0xa084, 0xffef, 0x6006, 0x2008, 0x2c48, + 0x0c7f, 0xa184, 0x0008, 0x0040, 0x25a0, 0x1078, 0x3095, 0x1078, + 0x2ddf, 0x88ff, 0x0040, 0x25a0, 0x789b, 0x0060, 0x2800, 0x78aa, + 0x7e58, 0xa6b5, 0x0004, 0x7e5a, 0xa684, 0x0400, 0x00c0, 0x259c, + 0x781b, 0x0053, 0x0078, 0x1efb, 0x781b, 0x0067, 0x0078, 0x1efb, + 0x7e58, 0xa684, 0x0400, 0x00c0, 0x25a9, 0x781b, 0x0056, 0x0078, + 0x1efb, 0x781b, 0x0068, 0x0078, 0x1efb, 0x0078, 0x2f6f, 0x0078, + 0x2f6f, 0x2019, 0x0000, 0x7990, 0xa18c, 0x0007, 0x0040, 0x25af, + 0x789b, 0x0010, 0x78a8, 0xa094, 0x00ff, 0xa286, 0x0001, 0x00c0, + 0x25d4, 0x2300, 0x7ca8, 0xa400, 0x2018, 0xa102, 0x0040, 0x25cc, + 0x0048, 0x25cc, 0x0078, 0x25ce, 0x0078, 0x255e, 0x24a8, 0x7aa8, + 0x00f0, 0x25ce, 0x0078, 0x25ba, 0xa284, 0x00f0, 0xa086, 0x0020, + 0x00c0, 0x2630, 0x8318, 0x8318, 0x2300, 0xa102, 0x0040, 0x25e4, + 0x0048, 0x25e4, 0x0078, 0x262d, 0xa286, 0x0023, 0x0040, 0x25af, + 0x681c, 0xa084, 0xfff1, 0x681e, 0x7e58, 0xa684, 0xfff1, 0xa085, + 0x0010, 0x2030, 0x7e5a, 0x6008, 0xa085, 0x0010, 0x600a, 0x0c7e, + 0x7048, 0x2060, 0x6004, 0x2008, 0x2c48, 0x0c7f, 0xa184, 0x0010, + 0x0040, 0x2608, 0x1078, 0x3095, 0x1078, 0x2ee3, 0x0078, 0x2617, + 0x0c7e, 0x7048, 0x2060, 0x6004, 0x2008, 0x2c48, 0x0c7f, 0xa184, + 0x0008, 0x0040, 0x25a0, 0x1078, 0x3095, 0x1078, 0x2ddf, 0x88ff, + 0x0040, 0x25a0, 0x789b, 0x0060, 0x2800, 0x78aa, 0xa6b5, 0x0004, + 0x7e5a, 0xa684, 0x0400, 0x00c0, 0x2629, 0x781b, 0x0053, 0x0078, + 0x1efb, 0x781b, 0x0067, 0x0078, 0x1efb, 0x7aa8, 0x0078, 0x25ba, + 0x8318, 0x2300, 0xa102, 0x0040, 0x2639, 0x0048, 0x2639, 0x0078, + 0x25ba, 0xa284, 0x0080, 0x00c0, 0x2f75, 0x0078, 0x2f6f, 0x0078, + 0x2f75, 0x0078, 0x2f69, 0x789b, 0x0018, 0x78a8, 0xa084, 0x00ff, + 0xa08e, 0x0001, 0x0040, 0x264e, 0x1078, 0x1eac, 0x7aa8, 0xa294, + 0x00ff, 0x78a8, 0xa084, 0x00ff, 0xa08a, 0x0004, 0x00c8, 0x2f69, + 0x0079, 0x265a, 0x2f69, 0x2d32, 0x2f69, 0x2e7e, 0xa282, 0x0000, + 0x00c0, 0x2664, 0x1078, 0x1eac, 0x1078, 0x2f7d, 0x781b, 0x0067, + 0x0078, 0x1efb, 0xa282, 0x0003, 0x00c0, 0x2670, 0x1078, 0x1eac, + 0xa484, 0x8000, 0x00c0, 0x2693, 0x70a0, 0xa005, 0x0040, 0x267a, + 0x1078, 0x1eac, 0x6f14, 0x77b6, 0xa7bc, 0x0f00, 0x1078, 0x3099, + 0x6008, 0xa085, 0x0021, 0x600a, 0x8738, 0xa784, 0x0007, 0x00c0, + 0x267e, 0x1078, 0x2f81, 0x70a3, 0x0002, 0x2009, 0x3935, 0x200b, + 0x0009, 0x0078, 0x2695, 0x1078, 0x2f8d, 0x781b, 0x0067, 0x0078, + 0x1efb, 0xa282, 0x0004, 0x0050, 0x269f, 0x1078, 0x1eac, 0x2300, + 0x0079, 0x26a2, 0x26a5, 0x2781, 0x27a9, 0xa286, 0x0003, 0x0040, + 0x26ab, 0x1078, 0x1eac, 0x2001, 0x0000, 0x703a, 0x7000, 0xa084, + 0x0007, 0x0079, 0x26b3, 0x26bb, 0x26bd, 0x26bd, 0x2871, 0x28a2, + 0x1f03, 0x28a2, 0x26bb, 0x1078, 0x1eac, 0xa684, 0x1000, 0x00c0, + 0x26c5, 0x1078, 0x32f5, 0x0040, 0x275b, 0x7868, 0xa08c, 0x00ff, + 0x0040, 0x270d, 0xa186, 0x0008, 0x00c0, 0x26dc, 0x1078, 0x2cdf, + 0x6008, 0xa084, 0xffef, 0x600a, 0x1078, 0x2c92, 0x0040, 0x270d, + 0x1078, 0x32f5, 0x0078, 0x26f4, 0xa186, 0x0028, 0x00c0, 0x270d, + 0x1078, 0x32f5, 0x6008, 0xa084, 0xffef, 0x600a, 0x6018, 0xa005, + 0x0040, 0x26f4, 0x8001, 0x601a, 0xa005, 0x0040, 0x26f4, 0x8001, + 0xa005, 0x0040, 0x26f4, 0x601e, 0x6820, 0xa084, 0x0001, 0x0040, + 0x1f03, 0x6820, 0xa084, 0xfffe, 0x6822, 0x7054, 0x0c7e, 0x2060, + 0x6800, 0x6002, 0x0c7f, 0x6004, 0x6802, 0xa005, 0x2d00, 0x00c0, + 0x270a, 0x6002, 0x6006, 0x0078, 0x1f03, 0x017e, 0x1078, 0x27ce, + 0x017f, 0xa684, 0xdf00, 0x681e, 0x682b, 0x0000, 0x6f14, 0x81ff, + 0x0040, 0x275b, 0xa186, 0x0002, 0x00c0, 0x2753, 0xa684, 0x0800, + 0x00c0, 0x272a, 0xa684, 0x0060, 0x0040, 0x272a, 0x78d8, 0x7adc, + 0x682e, 0x6a32, 0x6820, 0xa084, 0x0800, 0x00c0, 0x275b, 0x8717, + 0xa294, 0x000f, 0x8213, 0x8213, 0x8213, 0xa290, 0x3a00, 0xa290, + 0x0000, 0x221c, 0x8210, 0x2204, 0xa085, 0x0018, 0x2012, 0x8211, + 0xa384, 0x0400, 0x0040, 0x274d, 0x68a0, 0xa084, 0x0100, 0x00c0, + 0x274d, 0x1078, 0x2830, 0x0078, 0x1f03, 0x6008, 0xa085, 0x0002, + 0x600a, 0x0078, 0x275b, 0xa186, 0x0018, 0x0040, 0x275b, 0xa186, + 0x0014, 0x0040, 0x1f03, 0x6916, 0x6818, 0xa084, 0x8000, 0x0040, + 0x2763, 0x7038, 0x681a, 0xa68c, 0xdf00, 0x691e, 0x1078, 0x2cd0, + 0x1078, 0x2cdf, 0x00c0, 0x2770, 0x6008, 0xa084, 0xffef, 0x600a, + 0x6820, 0xa084, 0x0001, 0x00c0, 0x2779, 0x1078, 0x2cc9, 0x0078, + 0x277d, 0x7054, 0x2060, 0x6800, 0x6002, 0x1078, 0x1a26, 0x0078, + 0x1f03, 0xa282, 0x0004, 0x0048, 0x2787, 0x1078, 0x1eac, 0x2200, + 0x0079, 0x278a, 0x2785, 0x278e, 0x2794, 0x278e, 0x1078, 0x2f7d, + 0x781b, 0x0067, 0x0078, 0x1efb, 0x7890, 0x8007, 0x8001, 0xa084, + 0x0007, 0xa080, 0x0018, 0x789a, 0x79a8, 0xa18c, 0x00ff, 0xa186, + 0x0003, 0x0040, 0x27a5, 0x0078, 0x2f69, 0x781b, 0x0068, 0x0078, + 0x1efb, 0x6820, 0xa085, 0x0004, 0x6822, 0x82ff, 0x00c0, 0x27b4, + 0x1078, 0x2f7d, 0x0078, 0x27bb, 0x8211, 0x0040, 0x27b9, 0x1078, + 0x1eac, 0x1078, 0x2f8d, 0x781b, 0x0067, 0x0078, 0x1efb, 0x1078, + 0x316a, 0x7830, 0xa084, 0x00c0, 0x00c0, 0x27cb, 0x0018, 0x27cb, + 0x791a, 0xa006, 0x007c, 0xa085, 0x0001, 0x007c, 0xa684, 0x0060, + 0x00c0, 0x27d8, 0x682f, 0x0000, 0x6833, 0x0000, 0x0078, 0x282f, + 0xa684, 0x0800, 0x00c0, 0x27e8, 0x6998, 0x6a94, 0x692e, 0x6a32, + 0x7000, 0xa086, 0x0006, 0x0040, 0x27e7, 0x1078, 0x32f5, 0x007c, + 0xa684, 0x0020, 0x0040, 0x2802, 0xa684, 0x4000, 0x0040, 0x27f6, + 0x682f, 0x0000, 0x6833, 0x0000, 0x0078, 0x27e0, 0x7038, 0xa005, + 0x00c0, 0x27fc, 0x703b, 0x0015, 0x79d8, 0x7adc, 0x692e, 0x6a32, + 0x0078, 0x27e0, 0xa684, 0x4000, 0x0040, 0x280c, 0x682f, 0x0000, + 0x6833, 0x0000, 0x0078, 0x27e0, 0x7038, 0xa005, 0x00c0, 0x2812, + 0x703b, 0x0015, 0x79d8, 0x7adc, 0x78d0, 0x80fb, 0x00c8, 0x2819, + 0x8000, 0xa084, 0x003f, 0xa108, 0xa291, 0x0000, 0x692e, 0x6a32, + 0x2100, 0xa205, 0x00c0, 0x2826, 0x0078, 0x27e0, 0x7000, 0xa086, + 0x0006, 0x0040, 0x282f, 0x1078, 0x362f, 0x0078, 0x27e0, 0x007c, + 0xa384, 0x0200, 0x0040, 0x2838, 0x6008, 0xa085, 0x0002, 0x600a, + 0x681b, 0x0006, 0x6a30, 0x692c, 0x6a3e, 0x6942, 0x682f, 0x0003, + 0x6833, 0x0000, 0x6837, 0x0020, 0x6897, 0x0000, 0x689b, 0x0020, + 0x7000, 0x0079, 0x284b, 0x2853, 0x2855, 0x285e, 0x2853, 0x2853, + 0x2853, 0x2853, 0x2853, 0x1078, 0x1eac, 0x6820, 0xa084, 0x0001, + 0x00c0, 0x285e, 0x1078, 0x2cc9, 0x0078, 0x2864, 0x7054, 0x2c50, + 0x2060, 0x6800, 0x6002, 0x2a60, 0x2021, 0x3957, 0x2404, 0xa005, + 0x0040, 0x286d, 0x2020, 0x0078, 0x2866, 0x2d22, 0x206b, 0x0000, + 0x007c, 0x1078, 0x2cd0, 0x1078, 0x2cdf, 0x682b, 0x0000, 0x789b, + 0x000e, 0x6f14, 0x6817, 0x0002, 0x1078, 0x366b, 0xa684, 0x0800, + 0x0040, 0x2886, 0x691c, 0xa18d, 0x2000, 0x691e, 0x6818, 0xa084, + 0x8000, 0x0040, 0x2896, 0x7868, 0xa08c, 0x00ff, 0x0040, 0x2894, + 0x681b, 0x001e, 0x0078, 0x2896, 0x681b, 0x0000, 0x2021, 0x3957, + 0x6800, 0x2022, 0x6a3c, 0x6940, 0x6a32, 0x692e, 0x1078, 0x1a26, + 0x0078, 0x1f03, 0x1078, 0x27ce, 0x682b, 0x0000, 0x789b, 0x000e, + 0x6f14, 0x1078, 0x316f, 0xa08c, 0x00ff, 0x6916, 0x6818, 0xa084, + 0x8000, 0x0040, 0x28b5, 0x7038, 0x681a, 0xa68c, 0xdf00, 0x691e, + 0x70a3, 0x0000, 0x0078, 0x1f03, 0xa006, 0x1078, 0x32f5, 0x6817, + 0x0000, 0x681b, 0x0001, 0xa68c, 0xdf00, 0x691e, 0x682b, 0x0000, + 0x7000, 0x0079, 0x28cb, 0x28d3, 0x28d5, 0x28d5, 0x28d7, 0x28d7, + 0x28d7, 0x28d7, 0x28d3, 0x1078, 0x1eac, 0x1078, 0x2cdf, 0x6008, + 0xa084, 0xffef, 0x600a, 0x0078, 0x2caa, 0x2300, 0x0079, 0x28e0, + 0x28e3, 0x28e5, 0x2939, 0x1078, 0x1eac, 0x7000, 0x0079, 0x28e8, + 0x28f0, 0x28f2, 0x28f2, 0x290d, 0x28f2, 0x291a, 0x290d, 0x28f0, + 0x1078, 0x1eac, 0xa684, 0x0060, 0xa086, 0x0060, 0x00c0, 0x2909, + 0xa6b4, 0xffdf, 0xa6b4, 0xbfff, 0xa6b5, 0x2000, 0x7e5a, 0x681c, + 0xa084, 0xffdf, 0x681e, 0x1078, 0x32f5, 0x1078, 0x3571, 0x0078, + 0x311c, 0xa684, 0x2000, 0x0040, 0x28fc, 0x6818, 0xa084, 0x8000, + 0x0040, 0x291a, 0x681b, 0x0015, 0xa684, 0x4000, 0x0040, 0x291a, + 0x681b, 0x0007, 0x2009, 0x391e, 0x210c, 0xa186, 0x0000, 0x0040, + 0x292f, 0xa186, 0x0001, 0x0040, 0x2933, 0x2009, 0x3935, 0x200b, + 0x000b, 0x70a3, 0x0001, 0x781b, 0x0047, 0x0078, 0x1efb, 0x781b, + 0x00df, 0x0078, 0x1efb, 0x2009, 0x3935, 0x200b, 0x000a, 0x0078, + 0x1efb, 0x1078, 0x1eac, 0x2300, 0x0079, 0x293e, 0x2941, 0x2943, + 0x2976, 0x1078, 0x1eac, 0x7000, 0x0079, 0x2946, 0x294e, 0x2950, + 0x2950, 0x296b, 0x2950, 0x2972, 0x296b, 0x294e, 0x1078, 0x1eac, + 0xa684, 0x0060, 0xa086, 0x0060, 0x00c0, 0x2967, 0xa6b4, 0xffbf, + 0xa6b4, 0xbfff, 0xa6b5, 0x2000, 0x7e5a, 0x681c, 0xa084, 0xffbf, + 0x681e, 0x1078, 0x32f5, 0x1078, 0x3571, 0x0078, 0x311c, 0xa684, + 0x2000, 0x0040, 0x295a, 0x6818, 0xa084, 0x8000, 0x0040, 0x2972, + 0x681b, 0x0007, 0x781b, 0x00e6, 0x0078, 0x1efb, 0x6820, 0xa085, + 0x0004, 0x6822, 0x1078, 0x30e7, 0xa6b5, 0x0800, 0x1078, 0x2f7d, + 0x781b, 0x0067, 0x0078, 0x1efb, 0x2300, 0x0079, 0x2987, 0x298a, + 0x298c, 0x298e, 0x1078, 0x1eac, 0x1078, 0x1eac, 0xa684, 0x0400, + 0x00c0, 0x29ae, 0x782b, 0x3009, 0x6920, 0xa18c, 0xfdff, 0xa18c, + 0xfeff, 0x6922, 0x789b, 0x0060, 0x78ab, 0x0000, 0xa684, 0xfffb, + 0x785a, 0x79e4, 0xa184, 0x0020, 0x00c0, 0x29aa, 0x2001, 0x0014, + 0x0078, 0x26ad, 0xa184, 0x0007, 0x0079, 0x29e6, 0x7a90, 0xa294, + 0x0007, 0x789b, 0x0060, 0x79a8, 0x81ff, 0x0040, 0x29e4, 0x789b, + 0x0010, 0x7ba8, 0xa384, 0x0001, 0x00c0, 0x29d5, 0x7ba8, 0x7ba8, + 0xa386, 0x0001, 0x00c0, 0x29c8, 0x2009, 0xfff7, 0x0078, 0x29ce, + 0xa386, 0x0003, 0x00c0, 0x29d5, 0x2009, 0xffef, 0x0c7e, 0x7048, + 0x2060, 0x6004, 0xa104, 0x6006, 0x0c7f, 0x789b, 0x0060, 0x78ab, + 0x0000, 0xa684, 0xfffb, 0x785a, 0x782b, 0x3009, 0x6920, 0xa18c, + 0xfdff, 0xa18c, 0xfeff, 0x6922, 0x0078, 0x311c, 0x239b, 0x23a8, + 0x3124, 0x3124, 0x29ee, 0x29ee, 0x29ee, 0x3124, 0x1078, 0x1eac, + 0x79e4, 0xa184, 0x0030, 0x00c0, 0x2a06, 0x70a0, 0xa086, 0x0002, + 0x00c0, 0x29fe, 0x2011, 0x0002, 0x0078, 0x2288, 0x6818, 0xa085, + 0x8000, 0x681a, 0x2001, 0x0014, 0x0078, 0x26ad, 0xa184, 0x0007, + 0x0079, 0x2a0a, 0x311c, 0x311c, 0x2a12, 0x311c, 0x3144, 0x3144, + 0x311c, 0x311c, 0xa684, 0x0080, 0x0040, 0x2a41, 0x71c8, 0x81ff, + 0x0040, 0x2a41, 0xa182, 0x000d, 0x00d0, 0x2a22, 0x70cb, 0x0000, + 0x0078, 0x2a27, 0xa182, 0x000c, 0x70ca, 0x2009, 0x000c, 0x789b, + 0x0061, 0x79aa, 0x157e, 0x137e, 0x147e, 0x70cc, 0x8114, 0xa210, + 0x72ce, 0xa080, 0x000b, 0xad00, 0x2098, 0x20a1, 0x012b, 0x789b, + 0x0000, 0x8108, 0x81ac, 0x53a6, 0x147f, 0x137f, 0x157f, 0x0078, + 0x3124, 0xa684, 0x0400, 0x00c0, 0x2a82, 0x6820, 0xa084, 0x0001, + 0x0040, 0x3124, 0xa68c, 0x0060, 0xa684, 0x0060, 0x0040, 0x2a56, + 0xa086, 0x0060, 0x00c0, 0x2a56, 0xa18d, 0x4000, 0xa18c, 0xfffb, + 0x795a, 0x69b6, 0x789b, 0x0060, 0x78ab, 0x0000, 0x789b, 0x0061, + 0x6818, 0xa085, 0x8000, 0x681a, 0x78aa, 0x8008, 0x810c, 0x0040, + 0x2d07, 0xa18c, 0x00f8, 0x00c0, 0x2d07, 0x157e, 0x137e, 0x147e, + 0x20a1, 0x012b, 0x789b, 0x0000, 0x8000, 0x80ac, 0xad80, 0x000b, + 0x2098, 0x53a6, 0x147f, 0x137f, 0x157f, 0x6814, 0x8007, 0x7882, + 0x0078, 0x3124, 0x6818, 0xa084, 0x8000, 0x0040, 0x2a89, 0x681b, + 0x0008, 0x781b, 0x00da, 0x0078, 0x1efb, 0x2300, 0x0079, 0x2a90, + 0x2a95, 0x2b18, 0x2a93, 0x1078, 0x1eac, 0x7000, 0xa084, 0x0007, + 0x0079, 0x2a9a, 0x2aa2, 0x2aa4, 0x2ac0, 0x2aa2, 0x2aa2, 0x1f03, + 0x2aa2, 0x2aa2, 0x1078, 0x1eac, 0x6920, 0xa18d, 0x0001, 0x6922, + 0x6800, 0x6006, 0xa005, 0x00c0, 0x2aae, 0x6002, 0x681c, 0xa084, + 0x000e, 0x0040, 0x2aba, 0x7014, 0x68ba, 0x712c, 0xa188, 0x4280, + 0x0078, 0x2abc, 0x2009, 0x4380, 0x2104, 0x6802, 0x2d0a, 0x7156, + 0x6920, 0xa184, 0x8000, 0x00c0, 0x2acc, 0x68af, 0x0000, 0x68b3, + 0x0000, 0xa18d, 0x8000, 0x6922, 0x6eb6, 0xa684, 0x0060, 0x0040, + 0x2b16, 0xa684, 0x0800, 0x00c0, 0x2add, 0x6894, 0x68a6, 0x6898, + 0x68aa, 0x1078, 0x32f5, 0x0078, 0x2b16, 0xa684, 0x0020, 0x0040, + 0x2aea, 0xa006, 0x1078, 0x362f, 0x79d8, 0x7adc, 0x69aa, 0x6aa6, + 0x0078, 0x2af0, 0x1078, 0x30a6, 0x69aa, 0x6aa6, 0x1078, 0x362f, + 0xa684, 0x8000, 0x0040, 0x2b16, 0xa684, 0x7fff, 0x68b6, 0x789b, + 0x0074, 0x1078, 0x316f, 0x2010, 0x1078, 0x316f, 0x2008, 0xa684, + 0x0020, 0x00c0, 0x2b0e, 0x1078, 0x316f, 0x801b, 0x00c8, 0x2b09, + 0x8000, 0xa084, 0x003f, 0xa108, 0xa291, 0x0000, 0x6b98, 0x2100, + 0xa302, 0x68b2, 0x6b94, 0x2200, 0xa303, 0x68ae, 0x0078, 0x1f03, + 0x0078, 0x2f75, 0x7033, 0x0000, 0xa282, 0x0006, 0x0050, 0x2b22, + 0x1078, 0x1eac, 0x2300, 0x0079, 0x2b25, 0x2b28, 0x2b4e, 0x2b72, + 0x2200, 0x0079, 0x2b2b, 0x2b31, 0x2f75, 0x2b33, 0x2b31, 0x2b9c, + 0x2bed, 0x1078, 0x1eac, 0x7003, 0x0005, 0x2001, 0x4390, 0x2068, + 0x703e, 0x157e, 0x20a9, 0x002f, 0x2003, 0x0000, 0x8000, 0x0070, + 0x2b43, 0x0078, 0x2b3c, 0x157f, 0x6817, 0x0000, 0x68b7, 0x0700, + 0x6823, 0x0800, 0x6827, 0x0003, 0x0078, 0x2f69, 0x7000, 0xa086, + 0x0001, 0x00c0, 0x2b5b, 0x1078, 0x2cdf, 0x1078, 0x32f5, 0x7034, + 0x600a, 0x0078, 0x2b60, 0x7000, 0xa086, 0x0003, 0x0040, 0x2b55, + 0x7003, 0x0005, 0x2001, 0x4390, 0x2068, 0x703e, 0x7032, 0x2200, + 0x0079, 0x2b6a, 0x2f75, 0x2b70, 0x2b70, 0x2b9c, 0x2b70, 0x2f75, + 0x1078, 0x1eac, 0x7000, 0xa086, 0x0001, 0x00c0, 0x2b7f, 0x1078, + 0x2cdf, 0x1078, 0x32f5, 0x7034, 0x600a, 0x0078, 0x2b84, 0x7000, + 0xa086, 0x0003, 0x0040, 0x2b79, 0x7003, 0x0005, 0x2001, 0x4390, + 0x2068, 0x703e, 0x7032, 0x2200, 0x0079, 0x2b8e, 0x2b96, 0x2b94, + 0x2b94, 0x2b96, 0x2b94, 0x2b96, 0x1078, 0x1eac, 0x1078, 0x2f8d, + 0x781b, 0x0067, 0x0078, 0x1efb, 0x7000, 0xa086, 0x0001, 0x00c0, + 0x2ba9, 0x1078, 0x2cdf, 0x1078, 0x32f5, 0x7034, 0x600a, 0x0078, + 0x2bae, 0x7000, 0xa086, 0x0003, 0x0040, 0x2ba3, 0x7003, 0x0002, + 0x7a80, 0xa294, 0x0f00, 0x789b, 0x0018, 0x7ca8, 0xa484, 0x0007, + 0xa215, 0x2069, 0x4380, 0x2d04, 0x2d08, 0x7156, 0x2068, 0xa005, + 0x0040, 0x2bc9, 0x6814, 0xa206, 0x0040, 0x2be2, 0x6800, 0x0078, + 0x2bbc, 0x7003, 0x0005, 0x2001, 0x4390, 0x2068, 0x703e, 0x7032, + 0x157e, 0x20a9, 0x002f, 0x2003, 0x0000, 0x8000, 0x0070, 0x2bda, + 0x0078, 0x2bd3, 0x157f, 0x6a16, 0x68b7, 0x0700, 0x6823, 0x0800, + 0x6827, 0x0003, 0x6eb4, 0x7e5a, 0x6820, 0xa084, 0x0c00, 0x0040, + 0x2c4b, 0x1078, 0x2f85, 0x0078, 0x2c4b, 0x7000, 0xa086, 0x0001, + 0x00c0, 0x2bfa, 0x1078, 0x2cdf, 0x1078, 0x32f5, 0x7034, 0x600a, + 0x0078, 0x2bff, 0x7000, 0xa086, 0x0003, 0x0040, 0x2bf4, 0x7003, + 0x0002, 0x7a80, 0xa294, 0x0f00, 0x789b, 0x0018, 0x7ca8, 0xa484, + 0x0007, 0xa215, 0x79a8, 0x79a8, 0xa18c, 0x00ff, 0xa1e8, 0x4280, + 0x2d04, 0x2d08, 0x7156, 0x2068, 0xa005, 0x0040, 0x2c1e, 0x6814, + 0xa206, 0x0040, 0x2c36, 0x6800, 0x0078, 0x2c11, 0x7003, 0x0005, + 0x2001, 0x4390, 0x2068, 0x703e, 0x157e, 0x20a9, 0x002f, 0x2003, + 0x0000, 0x8000, 0x0070, 0x2c2e, 0x0078, 0x2c27, 0x157f, 0x6a16, + 0x68b7, 0x0700, 0x6823, 0x0800, 0x6827, 0x0003, 0x6eb4, 0x7e5a, + 0x6820, 0xa084, 0x0c00, 0x0040, 0x2c4b, 0xa084, 0x0800, 0x0040, + 0x2c45, 0x1078, 0x2f89, 0x0078, 0x2c4b, 0x1078, 0x2f85, 0x70bf, + 0x0000, 0x0078, 0x2c4b, 0x027e, 0x8207, 0xa084, 0x000f, 0x8003, + 0x8003, 0x8003, 0xa080, 0x3a00, 0x2060, 0x704a, 0x6000, 0x704e, + 0x6004, 0x7052, 0xa684, 0x0060, 0x0040, 0x2c64, 0x68a8, 0x78d2, + 0x78da, 0x68a4, 0x78d6, 0x78de, 0x077f, 0x1078, 0x3099, 0x2009, + 0x0068, 0xa684, 0x0008, 0x0040, 0x2c6f, 0x2009, 0x0067, 0xa6b5, + 0x2000, 0x7e5a, 0x791a, 0xa684, 0x0060, 0x0040, 0x2c85, 0xa684, + 0x0800, 0x00c0, 0x2c7f, 0x1078, 0x3571, 0x0078, 0x2c85, 0xa684, + 0x4000, 0x00c0, 0x2c85, 0x1078, 0x3502, 0x2d00, 0x703e, 0x8207, + 0xa084, 0x000f, 0x8003, 0x8003, 0x8003, 0xa080, 0x3a00, 0x2048, + 0x0078, 0x1efb, 0x6020, 0xa005, 0x0040, 0x2c9e, 0x8001, 0x6022, + 0x6008, 0xa085, 0x0008, 0x600a, 0x7010, 0x6026, 0x007c, 0xa006, + 0x1078, 0x32f5, 0x6817, 0x0000, 0x681b, 0x0001, 0x6823, 0x0040, + 0x681f, 0x0100, 0x7000, 0xa084, 0x0007, 0x0079, 0x2caf, 0x2cb7, + 0x2cb9, 0x2cb9, 0x2cc5, 0x2cc1, 0x2cb7, 0x2cc1, 0x2cb7, 0x1078, + 0x1eac, 0x1078, 0x2cd0, 0x1078, 0x2cc9, 0x1078, 0x1a26, 0x0078, + 0x1f03, 0x70a3, 0x0000, 0x0078, 0x1f03, 0x681b, 0x0000, 0x0078, + 0x2871, 0x6800, 0xa005, 0x00c0, 0x2cce, 0x6002, 0x6006, 0x007c, + 0x6010, 0xa005, 0x0040, 0x2cd9, 0x8001, 0x00d0, 0x2cd9, 0x1078, + 0x1eac, 0x6012, 0x6008, 0xa084, 0xffef, 0x600a, 0x007c, 0x6018, + 0xa005, 0x0040, 0x2ce5, 0x8001, 0x601a, 0x007c, 0x1078, 0x316a, + 0x6818, 0xa084, 0x8000, 0x0040, 0x2cef, 0x681b, 0x0018, 0x0078, + 0x2d26, 0x1078, 0x316a, 0x6818, 0xa084, 0x8000, 0x0040, 0x2cfa, + 0x681b, 0x0019, 0x0078, 0x2d26, 0x1078, 0x316a, 0x6818, 0xa084, + 0x8000, 0x0040, 0x2d05, 0x681b, 0x001a, 0x0078, 0x2d26, 0x1078, + 0x316a, 0x681b, 0x0003, 0x0078, 0x2d26, 0x71b8, 0xa18c, 0x00ff, + 0xa1e8, 0x4280, 0x2d04, 0x2d08, 0x2068, 0xa005, 0x00c0, 0x2d1a, + 0x0078, 0x1f03, 0x6814, 0x72b4, 0xa206, 0x0040, 0x2d22, 0x6800, + 0x0078, 0x2d13, 0x6800, 0x200a, 0x681b, 0x0005, 0x681f, 0x0000, + 0x6823, 0x0020, 0x1078, 0x2cd0, 0x1078, 0x2cc9, 0x1078, 0x1a26, + 0x0078, 0x1f03, 0xa282, 0x0003, 0x00c0, 0x2f69, 0x7da8, 0xa5ac, + 0x00ff, 0x7ea8, 0xa6b4, 0x00ff, 0x6920, 0xa18d, 0x0080, 0x6922, + 0xa184, 0x0100, 0x0040, 0x2d92, 0xa18c, 0xfeff, 0x6922, 0xa6b4, + 0x00ff, 0x0040, 0x2d7c, 0xa682, 0x000c, 0x0048, 0x2d53, 0x0040, + 0x2d53, 0x2031, 0x000c, 0x852b, 0x852b, 0x1078, 0x3018, 0x0040, + 0x2d5d, 0x1078, 0x2e4a, 0x0078, 0x2d85, 0x1078, 0x2fd3, 0x0c7e, + 0x2960, 0x6004, 0xa084, 0xfff5, 0x6006, 0x1078, 0x2e6e, 0x0c7f, + 0x6920, 0xa18d, 0x0100, 0x6922, 0x7e58, 0xa6b5, 0x0004, 0x7e5a, + 0xa684, 0x0400, 0x00c0, 0x2d78, 0x781b, 0x0053, 0x0078, 0x1efb, + 0x781b, 0x0067, 0x0078, 0x1efb, 0x0c7e, 0x2960, 0x6004, 0xa084, + 0xfff5, 0x6006, 0x1078, 0x2e6e, 0x0c7f, 0x7e58, 0xa684, 0x0400, + 0x00c0, 0x2d8e, 0x781b, 0x0056, 0x0078, 0x1efb, 0x781b, 0x0068, + 0x0078, 0x1efb, 0x0c7e, 0x7048, 0x2060, 0x6100, 0xa18c, 0x1000, + 0x0040, 0x2dd2, 0x6208, 0x8217, 0xa294, 0x00ff, 0xa282, 0x000c, + 0x0048, 0x2da6, 0x0040, 0x2da6, 0x2011, 0x000c, 0x2600, 0xa202, + 0x00c8, 0x2dab, 0x2230, 0x6208, 0xa294, 0x00ff, 0x7018, 0xa086, + 0x0028, 0x00c0, 0x2dbb, 0xa282, 0x0019, 0x00c8, 0x2dc1, 0x2011, + 0x0019, 0x0078, 0x2dc1, 0xa282, 0x000c, 0x00c8, 0x2dc1, 0x2011, + 0x000c, 0x2200, 0xa502, 0x00c8, 0x2dc6, 0x2228, 0x1078, 0x2fd7, + 0x852b, 0x852b, 0x1078, 0x3018, 0x0040, 0x2dd2, 0x1078, 0x2e4a, + 0x0078, 0x2dd6, 0x1078, 0x2fd3, 0x1078, 0x2e6e, 0x7858, 0xa085, + 0x0004, 0x785a, 0x0c7f, 0x781b, 0x0067, 0x0078, 0x1efb, 0x0c7e, + 0x2960, 0x6000, 0xa084, 0x1000, 0x00c0, 0x2df7, 0x6010, 0xa084, + 0x000f, 0x00c0, 0x2df1, 0x6104, 0xa18c, 0xfff5, 0x6106, 0x0c7f, + 0x007c, 0x2011, 0x0032, 0x2019, 0x0000, 0x0078, 0x2e1e, 0x68a0, + 0xa084, 0x0200, 0x00c0, 0x2df1, 0x6208, 0xa294, 0x00ff, 0x7018, + 0xa086, 0x0028, 0x00c0, 0x2e0c, 0xa282, 0x0019, 0x00c8, 0x2e12, + 0x2011, 0x0019, 0x0078, 0x2e12, 0xa282, 0x000c, 0x00c8, 0x2e12, + 0x2011, 0x000c, 0x6308, 0x831f, 0xa39c, 0x00ff, 0xa382, 0x000c, + 0x0048, 0x2e1e, 0x0040, 0x2e1e, 0x2019, 0x000c, 0x78ab, 0x0001, + 0x78ab, 0x0003, 0x78ab, 0x0001, 0x7aaa, 0x7baa, 0xa8c0, 0x0005, + 0x6820, 0xa085, 0x0100, 0x6822, 0x0c7f, 0x007c, 0x0c7e, 0x2960, + 0x6104, 0xa18c, 0xfff5, 0x6106, 0x2011, 0x0032, 0x2019, 0x0000, + 0x0078, 0x2e3a, 0x78ab, 0x0001, 0x78ab, 0x0003, 0x78ab, 0x0001, + 0x7aaa, 0x7baa, 0xa8c0, 0x0005, 0x6820, 0xa085, 0x0100, 0x6822, + 0x0c7f, 0x007c, 0x0c7e, 0x7148, 0x2160, 0x2008, 0xa084, 0xfff0, + 0xa635, 0x7e86, 0x6018, 0x789a, 0x7eae, 0x6612, 0x78a4, 0xa084, + 0xfff8, 0xa18c, 0x0007, 0xa105, 0x78a6, 0x6016, 0x788a, 0xa6b4, + 0x000f, 0x8637, 0x8204, 0x8004, 0xa084, 0x00ff, 0xa605, 0x600e, + 0x6004, 0xa084, 0xfff5, 0x6006, 0x0c7f, 0x007c, 0x0c7e, 0x7048, + 0x2060, 0x6018, 0x789a, 0x78a4, 0xa084, 0xfff0, 0x78a6, 0x6012, + 0x7884, 0xa084, 0xfff0, 0x7886, 0x0c7f, 0x007c, 0xa282, 0x0002, + 0x00c0, 0x2f69, 0x7aa8, 0x6920, 0xa18d, 0x0080, 0x6922, 0xa184, + 0x0200, 0x0040, 0x2ec3, 0xa18c, 0xfdff, 0x6922, 0xa294, 0x00ff, + 0xa282, 0x0002, 0x00c8, 0x2f69, 0x1078, 0x2f0a, 0x1078, 0x2e6e, + 0xa980, 0x0001, 0x200c, 0x1078, 0x3095, 0x1078, 0x2ddf, 0x88ff, + 0x0040, 0x2eb6, 0x789b, 0x0060, 0x2800, 0x78aa, 0x7e58, 0xa6b5, + 0x0004, 0x7e5a, 0xa684, 0x0400, 0x00c0, 0x2eb2, 0x781b, 0x0053, + 0x0078, 0x1efb, 0x781b, 0x0067, 0x0078, 0x1efb, 0x7e58, 0xa684, + 0x0400, 0x00c0, 0x2ebf, 0x781b, 0x0056, 0x0078, 0x1efb, 0x781b, + 0x0068, 0x0078, 0x1efb, 0xa282, 0x0002, 0x00c8, 0x2ecb, 0xa284, + 0x0001, 0x0040, 0x2ed5, 0x7148, 0xa188, 0x0000, 0x210c, 0xa18c, + 0x2000, 0x00c0, 0x2ed5, 0x2011, 0x0000, 0x1078, 0x2fc5, 0x1078, + 0x2f0a, 0x1078, 0x2e6e, 0x7858, 0xa085, 0x0004, 0x785a, 0x781b, + 0x0067, 0x0078, 0x1efb, 0x0c7e, 0x027e, 0x2960, 0x6000, 0x2011, + 0x0001, 0xa084, 0x2000, 0x00c0, 0x2efa, 0x6014, 0xa084, 0x0040, + 0x00c0, 0x2ef8, 0xa18c, 0xffef, 0x6106, 0xa006, 0x0078, 0x2f07, + 0x2011, 0x0000, 0x78ab, 0x0001, 0x78ab, 0x0002, 0x78ab, 0x0003, + 0x7aaa, 0xa8c0, 0x0004, 0x6820, 0xa085, 0x0200, 0x6822, 0x027f, + 0x0c7f, 0x007c, 0x0c7e, 0x7048, 0x2060, 0x82ff, 0x0040, 0x2f12, + 0x2011, 0x0040, 0x6018, 0xa080, 0x0002, 0x789a, 0x78a4, 0xa084, + 0xffbf, 0xa205, 0x78a6, 0x6016, 0x788a, 0x6004, 0xa084, 0xffef, + 0x6006, 0x0c7f, 0x007c, 0xa684, 0x0020, 0x0040, 0x2f65, 0x7888, + 0xa084, 0x0040, 0x0040, 0x2f65, 0x7bb8, 0xa384, 0x003f, 0x831b, + 0x00c8, 0x2f33, 0x8000, 0xa005, 0x0040, 0x2f4c, 0x831b, 0x00c8, + 0x2f3c, 0x8001, 0x0040, 0x2f61, 0xa684, 0x4000, 0x0040, 0x2f4c, + 0x78b8, 0x801b, 0x00c8, 0x2f45, 0x8000, 0xa084, 0x003f, 0x00c0, + 0x2f61, 0xa6b4, 0xbfff, 0x7e5a, 0x79d8, 0x7adc, 0x2001, 0x0001, + 0xa108, 0x00c8, 0x2f55, 0xa291, 0x0000, 0x79d2, 0x79da, 0x7ad6, + 0x7ade, 0x1078, 0x362f, 0x781b, 0x0065, 0x1078, 0x3502, 0x0078, + 0x1efb, 0x781b, 0x0065, 0x0078, 0x1efb, 0x781b, 0x0068, 0x0078, + 0x1efb, 0x1078, 0x2f91, 0x781b, 0x0067, 0x0078, 0x1efb, 0x1078, + 0x2f7d, 0x781b, 0x0067, 0x0078, 0x1efb, 0x6827, 0x0002, 0x1078, + 0x2f85, 0x781b, 0x0067, 0x0078, 0x1efb, 0x2001, 0x0005, 0x0078, + 0x2f93, 0x2001, 0x000c, 0x0078, 0x2f93, 0x2001, 0x0006, 0x0078, + 0x2f93, 0x2001, 0x000d, 0x0078, 0x2f93, 0x2001, 0x0009, 0x0078, + 0x2f93, 0x2001, 0x0007, 0x789b, 0x007e, 0x78aa, 0xa6b5, 0x0008, + 0x7e5a, 0x007c, 0x077e, 0x873f, 0xa7bc, 0x000f, 0x873b, 0x873b, + 0x8703, 0xa0e0, 0x3a00, 0xa7b8, 0x0020, 0x7f9a, 0x79a4, 0xa184, + 0x000f, 0x0040, 0x2fb3, 0xa184, 0xfff0, 0x78a6, 0x6012, 0x6004, + 0xa085, 0x0008, 0x6006, 0x8738, 0x8738, 0x7f9a, 0x79a4, 0xa184, + 0x0040, 0x0040, 0x2fc3, 0xa184, 0xffbf, 0x78a6, 0x6016, 0x6004, + 0xa085, 0x0010, 0x6006, 0x077f, 0x007c, 0x789b, 0x0010, 0x78ab, + 0x0001, 0x78ab, 0x0002, 0x78ab, 0x0003, 0x7aaa, 0x789b, 0x0060, + 0x78ab, 0x0004, 0x007c, 0x2031, 0x0000, 0x2029, 0x0032, 0x789b, + 0x0010, 0x78ab, 0x0001, 0x78ab, 0x0003, 0x78ab, 0x0001, 0x7daa, + 0x7eaa, 0x789b, 0x0060, 0x78ab, 0x0005, 0x007c, 0x157e, 0x8007, + 0xa084, 0x00ff, 0x8003, 0x8003, 0xa080, 0x0020, 0x789a, 0x79a4, + 0xa18c, 0xfff0, 0x2001, 0x3946, 0x2004, 0xa082, 0x0028, 0x0040, + 0x3001, 0x2021, 0x307c, 0x2019, 0x0014, 0x20a9, 0x000c, 0x0078, + 0x3007, 0x2021, 0x3088, 0x2019, 0x0019, 0x20a9, 0x000d, 0x2011, + 0x0064, 0x2404, 0xa084, 0xfff0, 0xa106, 0x0040, 0x3016, 0x8420, + 0x2300, 0xa210, 0x0070, 0x3016, 0x0078, 0x3009, 0x157f, 0x007c, + 0x157e, 0x2011, 0x3946, 0x2214, 0xa282, 0x0032, 0x0048, 0x302c, + 0x0040, 0x3030, 0x2021, 0x306e, 0x2019, 0x0011, 0x20a9, 0x000e, + 0x2011, 0x0032, 0x0078, 0x3042, 0xa282, 0x0028, 0x0040, 0x303a, + 0x2021, 0x307c, 0x2019, 0x0014, 0x20a9, 0x000c, 0x2011, 0x0064, + 0x0078, 0x3042, 0x2021, 0x3088, 0x2019, 0x0019, 0x20a9, 0x000d, + 0x2011, 0x0064, 0x2200, 0xa502, 0x0040, 0x3052, 0x0048, 0x3052, + 0x8420, 0x2300, 0xa210, 0x0070, 0x304f, 0x0078, 0x3042, 0x157f, + 0xa006, 0x007c, 0x157f, 0x7a08, 0xa582, 0x0064, 0x00c8, 0x305d, + 0xa285, 0x0040, 0x780a, 0x0078, 0x305d, 0x78ec, 0xa084, 0x0300, + 0x0040, 0x306b, 0x2404, 0xa09e, 0x2002, 0x00c0, 0x306b, 0x2001, + 0x2101, 0x0078, 0x306c, 0x2404, 0xa015, 0x007c, 0x2002, 0x3002, + 0x3202, 0x4203, 0x4403, 0x5404, 0x5604, 0x6605, 0x6805, 0x7806, + 0x7a06, 0x0a07, 0x0c07, 0x0e07, 0x3202, 0x4202, 0x5202, 0x6202, + 0x7202, 0x6605, 0x7605, 0x7805, 0x7a05, 0x7c05, 0x7e05, 0x7f05, + 0x2202, 0x3202, 0x4202, 0x5202, 0x5404, 0x6404, 0x7404, 0x7604, + 0x7804, 0x7a04, 0x7c04, 0x7e04, 0x7f04, 0x789b, 0x0010, 0xa046, + 0x007c, 0xa784, 0x0f00, 0x800c, 0xa784, 0x0007, 0x8003, 0x8003, + 0x8003, 0x8003, 0xa105, 0xa0e0, 0x3a80, 0x007c, 0x79d8, 0x7adc, + 0x78d0, 0x801b, 0x00c8, 0x30ad, 0x8000, 0xa084, 0x003f, 0xa108, + 0xa291, 0x0000, 0x007c, 0x0f7e, 0x2079, 0x0100, 0x2009, 0x3940, + 0x2091, 0x8000, 0x2104, 0x0079, 0x30bd, 0x30e3, 0x30c7, 0x30c7, + 0x30c7, 0x30c7, 0x30c7, 0x30c7, 0x30c5, 0x1078, 0x1eac, 0x784b, + 0x0004, 0x68b4, 0xa085, 0x4000, 0x68b6, 0x7858, 0xa085, 0x4000, + 0x785a, 0x7830, 0xa084, 0x0080, 0x00c0, 0x30e3, 0x0018, 0x30e3, + 0x681c, 0xa084, 0x0020, 0x00c0, 0x30e1, 0x781b, 0x00df, 0x0078, + 0x30e3, 0x781b, 0x00e6, 0x2091, 0x8001, 0x0f7f, 0x007c, 0x0c7e, + 0x6814, 0x8007, 0xa084, 0x000f, 0x8003, 0x8003, 0x8003, 0xa0e0, + 0x3a00, 0x6004, 0xa084, 0x000a, 0x00c0, 0x311a, 0x6108, 0xa194, + 0xff00, 0x0040, 0x311a, 0xa18c, 0x00ff, 0x2001, 0x0019, 0xa106, + 0x0040, 0x3109, 0x2001, 0x0032, 0xa106, 0x0040, 0x310d, 0x0078, + 0x3111, 0x2009, 0x0020, 0x0078, 0x3113, 0x2009, 0x003f, 0x0078, + 0x3113, 0x2011, 0x0000, 0x2100, 0xa205, 0x600a, 0x6004, 0xa085, + 0x0002, 0x6006, 0x0c7f, 0x007c, 0x781b, 0x0068, 0x0078, 0x1efb, + 0x781b, 0x0067, 0x0078, 0x1efb, 0x781b, 0x0056, 0x0078, 0x1efb, + 0x781b, 0x0053, 0x0078, 0x1efb, 0x781b, 0x00df, 0x0078, 0x1efb, + 0x781b, 0x00de, 0x0078, 0x1efb, 0x781b, 0x00e6, 0x0078, 0x1efb, + 0x781b, 0x00e5, 0x0078, 0x1efb, 0x781b, 0x009d, 0x0078, 0x1efb, + 0x781b, 0x009c, 0x0078, 0x1efb, 0x6818, 0xa084, 0x8000, 0x0040, + 0x314b, 0x681b, 0x001d, 0x70a3, 0x0001, 0x781b, 0x0047, 0x0078, + 0x1efb, 0x007e, 0x7830, 0xa084, 0x00c0, 0x00c0, 0x3168, 0x7808, + 0xa084, 0xfffd, 0x780a, 0x0005, 0x0005, 0x0005, 0x0005, 0x78ec, + 0xa084, 0x0021, 0x0040, 0x3168, 0x7808, 0xa085, 0x0002, 0x780a, + 0x007f, 0x007c, 0x7808, 0xa085, 0x0002, 0x780a, 0x007c, 0x7830, + 0xa084, 0x0040, 0x00c0, 0x316f, 0x0098, 0x3178, 0x78ac, 0x007c, + 0x7808, 0xa084, 0xfffd, 0x780a, 0x0005, 0x0005, 0x0005, 0x0005, + 0x78ec, 0xa084, 0x0021, 0x0040, 0x3187, 0x0098, 0x3185, 0x78ac, + 0x007e, 0x7808, 0xa085, 0x0002, 0x780a, 0x007f, 0x007c, 0xa784, + 0x0070, 0x0040, 0x319b, 0x0c7e, 0x2d60, 0x2f68, 0x1078, 0x1e57, + 0x2d78, 0x2c68, 0x0c7f, 0xa784, 0x0008, 0x0040, 0x31a8, 0x784b, + 0x0008, 0x78ec, 0xa084, 0x0003, 0x0040, 0x1f03, 0x0078, 0x311c, + 0xa784, 0x0004, 0x0040, 0x31db, 0x78b8, 0xa084, 0x4001, 0x0040, + 0x31db, 0x784b, 0x0008, 0x78ec, 0xa084, 0x0003, 0x0040, 0x1f03, + 0x78e4, 0xa084, 0x0007, 0xa086, 0x0001, 0x00c0, 0x31db, 0x78c0, + 0xa685, 0x4800, 0x2030, 0x7e5a, 0x781b, 0x00e6, 0x0078, 0x1efb, + 0x784b, 0x0008, 0x6818, 0xa084, 0x8000, 0x0040, 0x31d7, 0x681b, + 0x0015, 0xa684, 0x4000, 0x0040, 0x31d7, 0x681b, 0x0007, 0x781b, + 0x00df, 0x0078, 0x1efb, 0x681b, 0x0003, 0x7858, 0xa084, 0x3f00, + 0x681e, 0x682f, 0x0000, 0x6833, 0x0000, 0x784b, 0x0008, 0x78e4, + 0xa005, 0x00d0, 0x238a, 0xa084, 0x0020, 0x0040, 0x238a, 0x0018, + 0x238a, 0x0078, 0x2f6f, 0x6b14, 0x8307, 0xa084, 0x000f, 0x8003, + 0x8003, 0x8003, 0xa080, 0x3a00, 0x2060, 0x2048, 0x704a, 0x6000, + 0x704e, 0x6004, 0x7052, 0x2a60, 0x007c, 0x0020, 0x0020, 0x0000, + 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0000, + 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0000, + 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0000, + 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0000, 0x0020, 0x0062, + 0x0009, 0x0014, 0x0014, 0x9848, 0x0014, 0x0014, 0x98f9, 0x98e9, + 0x0014, 0x0014, 0x0080, 0x00c0, 0x0100, 0x0402, 0x2008, 0xf880, + 0x0018, 0xa20a, 0x0014, 0x300b, 0xa20c, 0x0014, 0xa200, 0x8838, + 0x3806, 0x8839, 0x28c2, 0x9cc2, 0xa805, 0x0864, 0xa83d, 0x3008, + 0x28c1, 0x9cc2, 0xa201, 0x300c, 0x2847, 0x8161, 0x846a, 0x8000, + 0x84a4, 0x1856, 0x883a, 0xa808, 0x28e2, 0x9c9f, 0xa8f3, 0x0864, + 0xa82b, 0x300c, 0xa801, 0x3008, 0x28e1, 0x9c9f, 0x280d, 0xa204, + 0x64c0, 0x67a0, 0x6fc0, 0x1814, 0x883b, 0x7023, 0x8576, 0x8677, + 0xa80f, 0x786e, 0x883e, 0xa80c, 0x282b, 0xa205, 0x64a0, 0x67a0, + 0x6fc0, 0x1814, 0x883b, 0x7023, 0x8576, 0x8677, 0xa801, 0x883e, + 0x206b, 0x28c1, 0x9cc2, 0x2044, 0x2103, 0x20a2, 0x2081, 0xa8dc, + 0xa207, 0x2901, 0xa80a, 0x0014, 0xa203, 0x8000, 0x85a4, 0x1872, + 0x879a, 0x883c, 0x1fe2, 0xf601, 0xa208, 0x856e, 0x866f, 0x0704, + 0x3008, 0x9c9f, 0x0014, 0xa202, 0x8000, 0x85a4, 0x3009, 0x84a8, + 0x19e2, 0xf848, 0x8174, 0x86eb, 0x85eb, 0x872e, 0x87a9, 0x883f, + 0x08e6, 0xa8f1, 0xf861, 0xa8e8, 0xf801, 0x0014, 0xf881, 0x0016, + 0x85b2, 0x80f0, 0x9532, 0xfaa2, 0x1de2, 0x0014, 0x8532, 0xf221, + 0x0014, 0x1de2, 0x84a8, 0xd6e0, 0x1fe6, 0x0014, 0xa206, 0x6865, + 0x817e, 0x842a, 0x1dc1, 0x8823, 0x0016, 0x6042, 0x8008, 0xa8fa, + 0x8000, 0x84a4, 0x8160, 0x842a, 0xf021, 0x3008, 0x84a8, 0x11d6, + 0x7042, 0x20dd, 0x0011, 0x20d4, 0x8822, 0x0016, 0x8000, 0x2848, + 0x1011, 0xa8fc, 0x3008, 0x8000, 0xa000, 0x2802, 0x1011, 0xa8fd, + 0xa883, 0x3008, 0x283d, 0x1011, 0xa8fd, 0xa209, 0x0017, 0x300c, + 0x8000, 0x85a4, 0x1de2, 0xdac1, 0x0014, 0xd301, 0x0014, 0x26e0, + 0x873a, 0xfaa2, 0x19f2, 0x1fe2, 0x0014, 0xa20b, 0x0014, 0xa20d, + 0x3806, 0x0210, 0x9ccc, 0x0704, 0x0000, 0x127e, 0x2091, 0x2200, + 0x2049, 0x32f5, 0x7000, 0x7204, 0xa205, 0x720c, 0xa215, 0x7008, + 0xa084, 0xfff7, 0xa205, 0x0040, 0x3307, 0x1078, 0x3380, 0x127f, + 0x2000, 0x007c, 0x6428, 0x84ff, 0x0040, 0x3336, 0x2c70, 0x7004, + 0xa0bc, 0x000f, 0xa7b8, 0x3346, 0x273c, 0x87fb, 0x00c0, 0x3324, + 0x0048, 0x331c, 0x1078, 0x1eac, 0x609c, 0xa075, 0x0040, 0x3336, + 0x0078, 0x330f, 0x2039, 0x333b, 0x2704, 0xae68, 0x6808, 0xa630, + 0x680c, 0xa529, 0x8421, 0x0040, 0x3336, 0x8738, 0x2704, 0xa005, + 0x00c0, 0x3325, 0x709c, 0xa075, 0x00c0, 0x330f, 0x007c, 0x0000, + 0x0005, 0x0009, 0x000d, 0x0011, 0x0015, 0x0019, 0x001d, 0x0000, + 0x0003, 0x0009, 0x000f, 0x0015, 0x001b, 0x0000, 0x0000, 0x333b, + 0x3338, 0x0000, 0x0000, 0x8000, 0x0000, 0x333b, 0x0000, 0x3343, + 0x3340, 0x0000, 0x0000, 0x0000, 0x0000, 0x3343, 0x0000, 0x333e, + 0x333e, 0x0000, 0x0000, 0x8000, 0x0000, 0x333e, 0x0000, 0x3344, + 0x3344, 0x0000, 0x0000, 0x0000, 0x0000, 0x3344, 0x127e, 0x2091, + 0x2200, 0x2079, 0x3900, 0x2071, 0x0010, 0x7007, 0x000a, 0x7007, + 0x0002, 0x7003, 0x0000, 0x2071, 0x0020, 0x7007, 0x000a, 0x7007, + 0x0002, 0x7003, 0x0000, 0x2049, 0x0000, 0x127f, 0x2000, 0x007c, + 0x2049, 0x3380, 0x2019, 0x0000, 0x7004, 0x8004, 0x00c8, 0x33b2, + 0x7007, 0x0012, 0x7108, 0x7008, 0xa106, 0x00c0, 0x338a, 0xa184, + 0x01e0, 0x0040, 0x3395, 0x1078, 0x1eac, 0xa184, 0x4000, 0x00c0, + 0x338a, 0xa19c, 0x300c, 0xa386, 0x2004, 0x0040, 0x33a7, 0xa386, + 0x0008, 0x0040, 0x33b2, 0xa386, 0x200c, 0x00c0, 0x338a, 0x7200, + 0x8204, 0x0048, 0x33b2, 0x730c, 0xa384, 0x00ff, 0x0040, 0x33b2, + 0x1078, 0x1eac, 0x7007, 0x0012, 0x7000, 0xa084, 0x0001, 0x00c0, + 0x33c3, 0x7310, 0x7014, 0xa305, 0x0040, 0x33c3, 0x700c, 0xa084, + 0x00ff, 0x00c0, 0x3380, 0x7007, 0x0012, 0x7007, 0x0008, 0x7004, + 0xa084, 0x0008, 0x00c0, 0x33c7, 0x7007, 0x0012, 0x7108, 0x8103, + 0x0048, 0x33cc, 0x7003, 0x0000, 0x2049, 0x0000, 0x007c, 0x107e, + 0x007e, 0x127e, 0x157e, 0x2091, 0x2200, 0x7108, 0x1078, 0x33e7, + 0x157f, 0x127f, 0x2091, 0x8001, 0x007f, 0x107f, 0x007c, 0x7204, + 0x7500, 0x730c, 0xa384, 0x0300, 0x00c0, 0x3426, 0xa184, 0x0060, + 0x00c0, 0x3442, 0x7008, 0x7108, 0xa106, 0x00c0, 0x33f2, 0xa184, + 0x01e0, 0x00c0, 0x3442, 0xa184, 0x4000, 0x00c0, 0x33f2, 0xa986, + 0x362f, 0x00c0, 0x341a, 0xa19c, 0x300c, 0xa386, 0x2004, 0x0040, + 0x3411, 0xa386, 0x0008, 0x0040, 0x341a, 0xa386, 0x200c, 0x00c0, + 0x33f2, 0x7200, 0x8204, 0x0048, 0x341a, 0x730c, 0xa384, 0x00ff, + 0x00c0, 0x3426, 0xa184, 0x0007, 0x0079, 0x341e, 0x3428, 0x3436, + 0x3426, 0x3436, 0x3426, 0x348f, 0x3426, 0x348d, 0x1078, 0x1eac, + 0x7007, 0x0002, 0x8aff, 0x00c0, 0x3431, 0x2049, 0x0000, 0x0078, + 0x3435, 0x1078, 0x3606, 0x00c0, 0x3431, 0x007c, 0x7007, 0x0002, + 0x8aff, 0x00c0, 0x343d, 0x0078, 0x3441, 0x1078, 0x3606, 0x00c0, + 0x343d, 0x007c, 0x7108, 0x7008, 0xa106, 0x00c0, 0x3442, 0xa184, + 0x4000, 0x00c0, 0x3442, 0x7007, 0x0012, 0x7108, 0x7008, 0xa106, + 0x00c0, 0x344d, 0xa184, 0x4000, 0x00c0, 0x344d, 0x00e0, 0x3456, + 0x2091, 0x6000, 0x00e0, 0x345a, 0x2091, 0x6000, 0x7007, 0x0012, + 0x7007, 0x0008, 0x7004, 0xa084, 0x0008, 0x00c0, 0x3462, 0x7007, + 0x0012, 0x7108, 0x8103, 0x0048, 0x3467, 0x7003, 0x0000, 0x7000, + 0xa005, 0x00c0, 0x347b, 0x7004, 0xa005, 0x00c0, 0x347b, 0x700c, + 0xa005, 0x0040, 0x347d, 0x0078, 0x345e, 0x2049, 0x0000, 0x1078, + 0x30b3, 0x6818, 0xa084, 0x8000, 0x0040, 0x3488, 0x681b, 0x0002, + 0x007c, 0x1078, 0x1eac, 0x1078, 0x1eac, 0x1078, 0x34ed, 0x7210, + 0x7114, 0x700c, 0xa09c, 0x00ff, 0x2800, 0xa300, 0xa211, 0xa189, + 0x0000, 0x1078, 0x34ed, 0x2704, 0x2c58, 0xac60, 0x6308, 0x2200, + 0xa322, 0x630c, 0x2100, 0xa31b, 0x2400, 0xa305, 0x0040, 0x34b2, + 0x00c8, 0x34b2, 0x8412, 0x8210, 0x830a, 0xa189, 0x0000, 0x2b60, + 0x0078, 0x3499, 0x2b60, 0x8a07, 0x007e, 0x6004, 0xa084, 0x0008, + 0x0040, 0x34be, 0xa7ba, 0x3340, 0x0078, 0x34c0, 0xa7ba, 0x3338, + 0x007f, 0xa73d, 0x2c00, 0x6886, 0x6f8a, 0x6c92, 0x6b8e, 0x1078, + 0x3380, 0x007c, 0x8738, 0x2704, 0xa005, 0x00c0, 0x34dd, 0x609c, + 0xa005, 0x0040, 0x34ea, 0x2060, 0x6004, 0xa084, 0x000f, 0xa080, + 0x3346, 0x203c, 0x87fb, 0x1040, 0x1eac, 0x8a51, 0x0040, 0x34e9, + 0x7008, 0x7508, 0xa52e, 0x00c0, 0x34e0, 0xa084, 0x0003, 0xa086, + 0x0003, 0x007c, 0x2051, 0x0000, 0x007c, 0x8a50, 0x8739, 0x2704, + 0xa004, 0x00c0, 0x3501, 0x6000, 0xa064, 0x00c0, 0x34f8, 0x2d60, + 0x6004, 0xa084, 0x000f, 0xa080, 0x3356, 0x203c, 0x87fb, 0x1040, + 0x1eac, 0x007c, 0x127e, 0x0d7e, 0x2091, 0x2200, 0x0d7f, 0x6884, + 0x2060, 0x6888, 0x6b8c, 0x6c90, 0x8057, 0xaad4, 0x00ff, 0xa084, + 0x00ff, 0x007e, 0x6804, 0xa084, 0x0008, 0x007f, 0x0040, 0x351c, + 0xa0b8, 0x3340, 0x0078, 0x351e, 0xa0b8, 0x3338, 0x7e08, 0xa6b5, + 0x000c, 0x681c, 0xa084, 0x0040, 0x0040, 0x3528, 0xa6b5, 0x0001, + 0x7007, 0x0004, 0x7004, 0xa084, 0x0004, 0x00c0, 0x352a, 0x2400, + 0xa305, 0x00c0, 0x3535, 0x0078, 0x3559, 0x2c58, 0x2704, 0x6104, + 0xac60, 0x6000, 0xa400, 0x701a, 0x6004, 0xa301, 0x701e, 0xa184, + 0x0008, 0x0040, 0x3549, 0x6010, 0xa001, 0x7022, 0x6014, 0xa001, + 0x7026, 0x6208, 0x2400, 0xa202, 0x7012, 0x620c, 0x2300, 0xa203, + 0x7016, 0x7602, 0x7007, 0x0001, 0x2b60, 0x1078, 0x34ca, 0x0078, + 0x355b, 0x1078, 0x3606, 0x00c0, 0x3559, 0x127f, 0x2000, 0x007c, + 0x127e, 0x0d7e, 0x2091, 0x2200, 0x0d7f, 0x7007, 0x0004, 0x7004, + 0xa084, 0x0004, 0x00c0, 0x3567, 0x7003, 0x0008, 0x127f, 0x2000, + 0x007c, 0x127e, 0x0d7e, 0x2091, 0x2200, 0x0d7f, 0x2049, 0x3571, + 0x7007, 0x0004, 0x7004, 0xa084, 0x0004, 0x00c0, 0x357a, 0x7e08, + 0xa6b5, 0x000c, 0x681c, 0xa084, 0x0020, 0x00c0, 0x3589, 0xa6b5, + 0x0001, 0x6828, 0x2050, 0x2d60, 0x6004, 0xa0bc, 0x000f, 0xa7b8, + 0x3346, 0x273c, 0x87fb, 0x00c0, 0x359f, 0x0048, 0x3599, 0x1078, + 0x1eac, 0x689c, 0xa065, 0x0040, 0x35a3, 0x0078, 0x358c, 0x1078, + 0x3606, 0x00c0, 0x359f, 0x127f, 0x2000, 0x007c, 0x127e, 0x007e, + 0x017e, 0x0d7e, 0x2091, 0x2200, 0x0d7f, 0x037f, 0x047f, 0x7e08, + 0xa6b5, 0x000c, 0x681c, 0xa084, 0x0040, 0x0040, 0x35b9, 0xa6b5, + 0x0001, 0x2049, 0x35a6, 0x6828, 0xa055, 0x0040, 0x3603, 0x2d70, + 0x2e60, 0x7004, 0xa0bc, 0x000f, 0xa7b8, 0x3346, 0x273c, 0x87fb, + 0x00c0, 0x35d5, 0x0048, 0x35ce, 0x1078, 0x1eac, 0x709c, 0xa075, + 0x2060, 0x0040, 0x3603, 0x0078, 0x35c1, 0x2704, 0xae68, 0x6808, + 0xa422, 0x680c, 0xa31b, 0x0048, 0x35f0, 0x8a51, 0x00c0, 0x35e2, + 0x1078, 0x1eac, 0x8738, 0x2704, 0xa005, 0x00c0, 0x35d6, 0x709c, + 0xa075, 0x2060, 0x0040, 0x3603, 0x2039, 0x3338, 0x0078, 0x35c1, + 0x8422, 0x8420, 0x831a, 0xa399, 0x0000, 0x6908, 0x2400, 0xa122, + 0x690c, 0x2300, 0xa11b, 0x00c8, 0x35ff, 0x1078, 0x1eac, 0x2071, + 0x0020, 0x0078, 0x3528, 0x127f, 0x2000, 0x007c, 0x7008, 0x7508, + 0xa52e, 0x00c0, 0x3606, 0xa084, 0x0003, 0xa086, 0x0003, 0x0040, + 0x362e, 0x2704, 0xac08, 0x2104, 0x701a, 0x8108, 0x2104, 0x701e, + 0x8108, 0x2104, 0x7012, 0x8108, 0x2104, 0x7016, 0x6004, 0xa084, + 0x0008, 0x0040, 0x3629, 0x8108, 0x2104, 0x7022, 0x8108, 0x2104, + 0x7026, 0x7602, 0x7007, 0x0001, 0x1078, 0x34ca, 0x007c, 0x127e, + 0x007e, 0x0d7e, 0x2091, 0x2200, 0x2049, 0x362f, 0x0d7f, 0x087f, + 0x7108, 0x7008, 0xa106, 0x00c0, 0x3638, 0xa184, 0x4000, 0x00c0, + 0x3638, 0xa184, 0x0003, 0x00c0, 0x364f, 0x6828, 0xa005, 0x0040, + 0x365d, 0x0020, 0x364f, 0x1078, 0x348f, 0x0078, 0x365d, 0x00a0, + 0x3656, 0x7108, 0x1078, 0x33e7, 0x0078, 0x3638, 0x7007, 0x0010, + 0x00a0, 0x3658, 0x7108, 0x1078, 0x33e7, 0x7008, 0xa086, 0x0008, + 0x00c0, 0x3638, 0x7000, 0xa005, 0x00c0, 0x3638, 0x2049, 0x0000, + 0x127f, 0x2000, 0x007c, 0x127e, 0x147e, 0x137e, 0x157e, 0x0d7e, + 0x2091, 0x2200, 0x0d7f, 0x2049, 0x366b, 0xad80, 0x0011, 0x20a0, + 0x2099, 0x0031, 0x700c, 0xa084, 0x00ff, 0x682a, 0x7007, 0x0008, + 0x7007, 0x0002, 0x7003, 0x0001, 0x0040, 0x3689, 0x8000, 0x80ac, + 0x53a5, 0x7007, 0x0004, 0x7004, 0xa084, 0x0004, 0x00c0, 0x368b, + 0x2049, 0x0000, 0x7003, 0x0000, 0x157f, 0x137f, 0x147f, 0x127f, + 0x2000, 0x007c, 0x2091, 0x6000, 0x78c0, 0xa005, 0x0040, 0x36af, + 0x798c, 0x70d0, 0xa106, 0x00c0, 0x36af, 0x7804, 0xa005, 0x0040, + 0x36af, 0x7807, 0x0000, 0x0068, 0x36af, 0x2091, 0x4080, 0x7820, + 0x8001, 0x7822, 0x00c0, 0x370f, 0x7824, 0x7822, 0x2091, 0x8000, + 0x78f0, 0xa005, 0x0040, 0x36dc, 0x78d4, 0xa005, 0x00c0, 0x36dc, + 0x3a10, 0xa284, 0x0002, 0x00c0, 0x36cc, 0x78d7, 0x0007, 0x2009, + 0xff01, 0x200a, 0x0078, 0x36dc, 0xa284, 0x0001, 0x00c0, 0x36d4, + 0x78ef, 0x0000, 0x0078, 0x36dc, 0x78ec, 0xa005, 0x00c0, 0x36dc, + 0x78d7, 0x0008, 0x78ef, 0x0001, 0x2069, 0x3940, 0x6800, 0xa084, + 0x0007, 0x0040, 0x36f3, 0xa086, 0x0002, 0x0040, 0x36f3, 0x6830, + 0xa00d, 0x0040, 0x36f3, 0x2104, 0xa005, 0x0040, 0x36f3, 0x8001, + 0x200a, 0x0040, 0x37b1, 0x7848, 0xa005, 0x0040, 0x3703, 0x8001, + 0x784a, 0x00c0, 0x3703, 0x0f7e, 0x2079, 0x0100, 0x1078, 0x316a, + 0x0f7f, 0x1078, 0x1cf6, 0x68c4, 0xa005, 0x0040, 0x370f, 0x8001, + 0x68c6, 0x00c0, 0x370f, 0x68a3, 0x0000, 0x68a7, 0x0001, 0x1078, + 0x3716, 0x1078, 0x373b, 0x2091, 0x8001, 0x007c, 0x7834, 0x8001, + 0x7836, 0x00c0, 0x373a, 0x7838, 0x7836, 0x2091, 0x8000, 0x7844, + 0xa005, 0x00c0, 0x3725, 0x2001, 0x0101, 0x8001, 0x7846, 0xa080, + 0x4280, 0x2040, 0x2004, 0xa065, 0x0040, 0x373a, 0x6024, 0xa005, + 0x0040, 0x3736, 0x8001, 0x6026, 0x0040, 0x376a, 0x6000, 0x2c40, + 0x0078, 0x372b, 0x007c, 0x7828, 0x8001, 0x782a, 0x00c0, 0x3769, + 0x782c, 0x782a, 0x7830, 0xa005, 0x00c0, 0x3748, 0x2001, 0x0080, + 0x8001, 0x7832, 0x8003, 0x8003, 0x8003, 0x8003, 0xa090, 0x3a80, + 0xa298, 0x0002, 0x2304, 0xa084, 0x0008, 0x0040, 0x3769, 0xa290, + 0x0009, 0x2204, 0xa005, 0x0040, 0x3761, 0x8001, 0x2012, 0x00c0, + 0x3769, 0x2304, 0xa084, 0xfff7, 0xa085, 0x0080, 0x201a, 0x1078, + 0x1cf6, 0x007c, 0x2069, 0x3940, 0x6800, 0xa005, 0x0040, 0x3774, + 0x683c, 0xac06, 0x0040, 0x37b1, 0x601b, 0x0006, 0x60b4, 0xa084, + 0x3f00, 0x601e, 0x6020, 0xa084, 0x00ff, 0xa085, 0x0060, 0x6022, + 0x6000, 0x2042, 0x6714, 0x6fb6, 0x1078, 0x18b4, 0x6818, 0xa005, + 0x0040, 0x378c, 0x8001, 0x681a, 0x6808, 0xa084, 0xffef, 0x680a, + 0x6810, 0x8001, 0x00d0, 0x3796, 0x1078, 0x1eac, 0x6812, 0x602f, + 0x0000, 0x6033, 0x0000, 0x2c68, 0x1078, 0x1a26, 0x2069, 0x3940, + 0x2001, 0x0006, 0x68a2, 0x7944, 0xa184, 0x0100, 0x00c0, 0x37ac, + 0x69ba, 0x2001, 0x0004, 0x68a2, 0x1078, 0x1cf1, 0x2091, 0x8001, + 0x007c, 0x2009, 0x394f, 0x2164, 0x2069, 0x0100, 0x1078, 0x1e57, + 0x601b, 0x0006, 0x6858, 0xa084, 0x3f00, 0x601e, 0x6020, 0xa084, + 0x00ff, 0xa085, 0x0048, 0x6022, 0x602f, 0x0000, 0x6033, 0x0000, + 0x6830, 0xa084, 0x0040, 0x0040, 0x37ed, 0x684b, 0x0004, 0x20a9, + 0x0014, 0x6848, 0xa084, 0x0004, 0x0040, 0x37da, 0x0070, 0x37da, + 0x0078, 0x37d1, 0x684b, 0x0009, 0x20a9, 0x0014, 0x6848, 0xa084, + 0x0001, 0x0040, 0x37e7, 0x0070, 0x37e7, 0x0078, 0x37de, 0x20a9, + 0x00fa, 0x0070, 0x37ed, 0x0078, 0x37e9, 0x6808, 0xa084, 0xfffd, + 0x680a, 0x681b, 0x0047, 0x2009, 0x3968, 0x200b, 0x0007, 0x784c, + 0x784a, 0x2091, 0x8001, 0x007c, 0x2079, 0x3900, 0x1078, 0x3827, + 0x1078, 0x380b, 0x1078, 0x3819, 0x7833, 0x0000, 0x7847, 0x0000, + 0x784b, 0x0000, 0x007c, 0x2019, 0x000c, 0x2011, 0x3946, 0x2204, + 0xa086, 0x003c, 0x0040, 0x3816, 0x2019, 0x0008, 0x7b2a, 0x7b2e, + 0x007c, 0x2019, 0x0039, 0x2011, 0x3946, 0x2204, 0xa086, 0x003c, + 0x0040, 0x3824, 0x2019, 0x0027, 0x7b36, 0x7b3a, 0x007c, 0x2019, + 0x3971, 0x2011, 0x3946, 0x2204, 0xa086, 0x003c, 0x0040, 0x3832, + 0x2019, 0x2626, 0x7b22, 0x7b26, 0x783f, 0x0000, 0x7843, 0x000a, + 0x007c, 0x8e59 +}; + +#endif /* RELOAD_FIRMWARE */ + +unsigned short risc_code_length01 = 0x283a; diff --git a/linux/src/drivers/scsi/scripts.h b/linux/src/drivers/scsi/scripts.h new file mode 100644 index 00000000..482b0c24 --- /dev/null +++ b/linux/src/drivers/scsi/scripts.h @@ -0,0 +1,1357 @@ +/*********************************************************************** +;* File Name : SCRIPTS.H * +;* Description:SCRIPT language for NCR53c825A,875 SCRIPT processor* +;* * +;*********************************************************************** + +;========================================================== +; NCR 53C810,53C815,53C820,53C825,53C825A,53C860,53C875 +; Script language definition for assembly programming +;========================================================== + +;========================================================== +; DMA Command +;========================================================== +*/ +#define DCMD_BLOCK_MOVE 0 +#define DCMD_IO 0x040000000 /*;BIT30 */ +#define DCMD_RD_WRT 0x040000000 /*;BIT30 */ +#define DCMD_XFER_CTRL 0x080000000 /*;BIT31 */ +#define DCMD_MEM_MOVE 0x0C0000000 /*;(BIT31+BIT30) */ +#define DCMD_LOAD_STORE 0x0E0000000 /*;(BIT31+BIT30+BIT29) */ +/*;==========================================================*/ +#define INDIRECT_ADDR 0x20000000 /*;BIT29 */ +#define TABLE_INDIRECT 0x10000000 /*;BIT28 */ +#define BLOCK_MOVE 0x08000000 /*;BIT27 */ +#define CHAIN_MOVE 0 +/*; SCSI phase definition */ +#define DATA_OUT_ 0x00000000 /*;data out phase */ +#define DATA_IN_ 0x01000000 /*;BIT24 ; data in phase */ +#define COMMAND_ 0x02000000 /*;BIT25 ; command phase */ +#define STATUS_ 0x03000000 /*;(BIT25+BIT24) ; status phase */ +#define RESERVED_OUT 0x04000000 /*;BIT26 */ +#define RESERVED_IN 0x05000000 /*;(BIT26+BIT24) */ +#define MSG_OUT_ 0x06000000 /*;(BIT26+BIT25) ; message in phase */ +#define MSG_IN_ 0x07000000 /*;(BIT26+BIT25+BIT24);message out phase */ +/*;----------------------------------------------------------*/ +#define DCMD_SELECT 0x40000000 /*;DCMD_IO+0 */ +#define DCMD_SELECT_ATN 0x41000000 /*;(DCMD_IO+BIT24) */ +#define DCMD_WAIT_DISC 0x48000000 /*;(DCMD_IO+BIT27) */ +#define DCMD_WAIT_RESEL 0x50000000 /*;(DCMD_IO+BIT28) */ +#define DCMD_SET_CARRY 0x58000400 /*;(DCMD_IO+BIT28+BIT27+BIT10) */ +#define DCMD_SET_ACK 0x58000040 /*;(DCMD_IO+BIT28+BIT27+BIT6) */ +#define DCMD_SET_ATN 0x58000008 /*;(DCMD_IO+BIT28+BIT27+BIT3) */ +#define DCMD_CLR_CARRY 0x60000400 /*;(DCMD_IO+BIT29+BIT10) */ +#define DCMD_CLR_ACK 0x60000040 /*;(DCMD_IO+BIT29+BIT6) */ +#define DCMD_CLR_ATN 0x60000008 /*;(DCMD_IO+BIT29+BIT3) */ +#define RELATIVE_ADDR 0x04000000 /*;BIT26 */ +#define IO_TABLE_INDIR 0x02000000 /*;BIT25 */ +/*;----------------------------------------------------------*/ +#define MOVE_FROM_SFBR 0x68000000 /*;(DCMD_RD_WRT+BIT29+BIT27) */ +#define MOVE_TO_SFBR 0x70000000 /*;(DCMD_RD_WRT+BIT29+BIT28) */ +#define RD_MODIFY_WRT 0x78000000 /*;(DCMD_RD_WRT+BIT29+BIT28+BIT27) */ +#define OP_MOVE_DATA 0 +#define OP_SHIFT_LEFT_C 0x01000000 /*;BIT24 */ +#define OP_OR 0x02000000 /*;BIT25 */ +#define OP_XOR 0x03000000 /*;(BIT25+BIT24) */ +#define OP_AND 0x04000000 /*;BIT26 */ +#define OP_SHIFT_RIGHT_C 0x05000000 /*;(BIT26+BIT24) */ +#define OP_ADD_DATA 0x06000000 /*;(BIT26+BIT25) */ +#define OP_ADD_DATA_C 0x07000000 /*;(BIT26+BIT25+BIT24) */ +#define USE_SFBR 0x00800000 /*;BIT23 */ +/*;----------------------------------------------------------*/ +#define DCMD_JUMP 0x80000000 /*;DCMD_XFER_CTRL+0 */ +#define DCMD_CALL 0x88000000 /*;(DCMD_XFER_CTRL+BIT27) */ +#define DCMD_RETURN 0x90000000 /*;(DCMD_XFER_CTRL+BIT28) */ +#define DCMD_INT 0x98000000 /*;(DCMD_XFER_CTRL+BIT28+BIT27) */ +#define RELATIVE_ 0x00800000 /*;BIT23 */ +#define IF_CARRY 0x00200000 /*;BIT21 */ +#define INT_ON_FLY_ 0x00100000 /*;BIT20 */ +#define IF_TRUE 0x00080000 /*;BIT19 */ +#define IF_NOT 0 +#define DATA_CMP 0x00040000 /*;BIT18 */ +#define PHASE_CMP 0x00020000 /*;BIT17 */ +#define WAIT_PHASE_VALID 0x00010000 /*;BIT16 */ +/*;----------------------------------------------------------*/ +#define DSA_RELATIVE 0x10000000 /*;BIT28 */ +#define FLUSH_PREFETCH 0x02000000 /*;BIT25 */ +#define DCMD_LOAD 0x0E1000000 /*;(DCMD_LOAD_STORE+BIT24) */ +#define DCMD_STORE 0x0E0000000 /*;DCMD_LOAD_STORE */ +/* +;========================================================== +; SCSI message EQUATES +;========================================================== +*/ +#define CMD_COMPLETE 0 +#define EXT_MSG 1 +#define SAVE_PTR 2 +#define RESTORE_PTR 3 +#define DISCONNECTMSG 4 +#define INITIATOR_ERR 5 +#define ABORTMSG 6 +#define MSG_REJECT 7 +#define NOPMSG 8 +#define MSG_PARITY 9 +#define LINK_CMD_CPL 0x0a +#define LINK_CMD_FLAG 0x0b +#define RESET_DEVICE 0x0c +#define IDENTIFYMSG 0x80 +#define SIMPLE_TAG 0x20 +#define IGNORE_WIDE_RES 0x23 +/* +;========================================================== +; Operation assumption +; 1. If phase mismatch during Xfer PAD ==> do nothing +; Else compute FIXUP needed +; 2. After phase mismatch ==> Set to Xfer PAD +; 3. At disconnection ==> Modify return address +; 4. 1st restore ptr after reselection is ignored +; 5. If Xfer PAD is done ==> Error +;========================================================== +*/ +/* static start_script + static reselected + static reselecttag + static select0 + static select1 + static check_phase + static status1_phase + static command_phase + static jump_table0 + static jump_tableB + static din_phase + static din_phaseB + static din_pad_0 + static din_pad_addrB + static dout_phase + static dout_phaseB + static dout_pad_0 + static dout_pad_addrB + static jump_tablew + static jump_tableW + static din_phase1 + static din_phaseW + static din_pad_1 + static din_pad_addrW + static dout_phase1 + static dout_phaseW + static dout_pad_1 + static dout_pad_addrW + static mout_phase + static status_phase + static min_phase + static set_atn + static clr_atn + static end_script + static start_mov + static SrcPhysAddr + static DesPhysAddr +*/ +ULONG start_script[]={ +/* +;========================================================== +; Wait for reselection +;========================================================== +*/ + DCMD_WAIT_RESEL + }; +ULONG jmp_select0[]={ + 0 /* offset select0 */ + }; +ULONG reselected[]={ + RD_MODIFY_WRT+OP_OR+0x200+0x340000, /* (2 shl 8) or (__scratcha shl 16) */ + 0, + + DCMD_INT+WAIT_PHASE_VALID+IF_NOT+PHASE_CMP+MSG_IN_, + __RESELECTED, + + BLOCK_MOVE+MSG_IN_+1 /* ;move in ID byte */ + }; +ULONG ACB_msgin123_1[]={ + 0, /* offset ACB.msgin123,*/ + + DCMD_INT+IF_TRUE, + __RESELECTED1 + }; +ULONG reselecttag[]={ + DCMD_CLR_ACK, + 0, + + BLOCK_MOVE+MSG_IN_+2 /* ;move 2 msg bytes */ + }; +ULONG ACB_msgin123_2[]={ + 0, /* offset ACB.msgin123,*/ + + DCMD_INT+IF_TRUE, + __RESELECTEDT + }; +/* +;========================================================== +; Select +; Case 1 : Only identify message is to be sent +; Case 2 : Synchronous negotiation is requested +;========================================================== +*/ +ULONG select0[]={ + DCMD_INT+IF_TRUE, + __SIGNAL + }; +ULONG select1[]={ /* ; Select with ATN */ + + DCMD_SELECT_ATN+IO_TABLE_INDIR /* +offset SRB.__select ;4200h or 0100H */ + }; +ULONG jmp_reselected[]={ + 0, /* offset reselected, */ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_NOT+PHASE_CMP+MSG_OUT_ + }; +ULONG jmp_check_phase[]={ + 0, /* offset check_phase, */ + + TABLE_INDIRECT+BLOCK_MOVE+MSG_OUT_ + }; +ULONG SRB_msgout0[]={ + 0 /* offset SRB.__msgout0 */ + }; +ULONG check_phase[]={ + DCMD_RETURN+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, + + DCMD_RETURN+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0 + }; +ULONG status1_phase[]={ + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+STATUS_ + }; +ULONG jmp_status_phase[]={ + 0, /* offset status_phase,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+COMMAND_ + }; +ULONG jmp_command_phase[]={ + 0, /* offset command_phase, */ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+MSG_IN_ + }; +ULONG jmp_min_phase[]={ + 0, /* offset min_phase,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+MSG_OUT_ + }; +ULONG jmp_mout_phase[]={ + 0, /* offset mout_phase,*/ + + DCMD_INT+IF_TRUE, + __FATALERROR + }; +/* +;========================================================== +; Command phase +;========================================================== +*/ +ULONG command_phase[]={ + DCMD_CLR_ATN, + 0, + TABLE_INDIRECT+BLOCK_MOVE+COMMAND_ + }; +ULONG SRB_command[]={ + 0, /* offset SRB.__command,*/ + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_check_phase1[]={ + 0 /* offset check_phase */ + }; +/* +;========================================================== +; Data phase jump table for 8 bit operation +;========================================================== +*/ +ULONG jmp_dio_phaseB[]={ + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseB+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseB+ 128,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0 /* offset dout_phaseB+ 128 */ + }; +ULONG jump_table0[]={ + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_ + }; +ULONG jmp_din_pad_0[]={ + 0, /* offset din_pad_0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_ + }; +ULONG jmp_dout_pad_0[]={ + 0 /* offset dout_pad_0 */ + }; + +#define jump_tableB jump_table0 +/* +;========================================================== +; Data in phase +;========================================================== +*/ +ULONG din_phaseB[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment15,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment15,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_, + 0, /* offset SRB.Segment16,*/ + + RD_MODIFY_WRT+OP_OR+0x100+0x340000, /*;(1 shl 8) or (__scratcha shl 16)*/ + 0, + + DCMD_JUMP+WAIT_PHASE_VALID+IF_NOT+PHASE_CMP+DATA_IN_ + }; +ULONG jmp_status1_phase[]={ + 0 /* offset status1_phase */ + }; + +#define din_phase din_phaseB + +ULONG din_pad_0[]={ + RD_MODIFY_WRT+OP_OR+0x340000+0x400, /*;(4 shl 8) or (__scratcha shl 16)*/ + 0 + }; +ULONG din_pad_addrB[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_IN_ + }; +ULONG SRB_SegmentPad[]={ + 0, /* offset SRB.SegmentPad,*/ + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_din_pad_addrB[]={ + 0 /* offset din_pad_addrB */ + }; +/* +;========================================================== +; Data out phase +;========================================================== +*/ +ULONG dout_phaseB[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment15,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment15,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment16,*/ + + RD_MODIFY_WRT+OP_OR+0x100+0x340000, /*;(1 shl 8) or (__scratcha shl 16)*/ + 0, + + DCMD_JUMP+WAIT_PHASE_VALID+IF_NOT+PHASE_CMP+DATA_OUT_ + }; +ULONG jmp_status1_phase1[]={ + 0 /* offset status1_phase */ + }; + +#define dout_phase dout_phaseB + +ULONG dout_pad_0[]={ + RD_MODIFY_WRT+OP_OR+0x340000+0x400, /*;(4 shl 8) or (__scratcha shl 16)*/ + 0 + }; +ULONG dout_pad_addrB[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_ + }; +ULONG SRB_SegmentPad1[]={ + 0, /* offset SRB.SegmentPad,*/ + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_dout_pad_addrB[]={ + 0 /* offset dout_pad_addrB */ + }; +/* +;========================================================== +; Data phase jump table for WIDE SCSI operation +;========================================================== +*/ +ULONG jmp_dio_phaseW[]={ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 0,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 8,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 16,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 24,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 32,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 40,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 48,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 56,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 64,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 72,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 80,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 88,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 96,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 104,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 112,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0, /* offset dout_phaseW+ 120,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_, + 0, /* offset din_phaseW+ 128,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_, + 0 /* offset dout_phaseW+ 128 */ + }; +ULONG jump_tablew[]={ + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_IN_ + }; +ULONG jmp_din_pad_1[]={ + 0, /* offset din_pad_1,*/ + + DCMD_JUMP+WAIT_PHASE_VALID+IF_TRUE+PHASE_CMP+DATA_OUT_ + }; +ULONG jmp_dout_pad_1[]={ + 0 /* offset dout_pad_1 */ + }; + +#define jump_tableW jump_tablew +/* +;========================================================== +; Data in phase +;========================================================== +*/ +ULONG din_phaseW[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment15,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment15,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_, + 0, /* offset SRB.Segment16,*/ + + RD_MODIFY_WRT+OP_OR+0x340000+0x100, /*;(1 shl 8) or (__scratcha shl 16)*/ + 0, + + DCMD_JUMP+WAIT_PHASE_VALID+IF_NOT+PHASE_CMP+DATA_IN_ + }; +ULONG jmp_status1_phase2[]={ + 0 /* offset status1_phase */ + }; + +#define din_phase1 din_phaseW + +ULONG din_pad_1[]={ + RD_MODIFY_WRT+OP_OR+0x340000+0x400, /*;(4 shl 8) or (__scratcha shl 16)*/ + 0 + }; +ULONG din_pad_addrW[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_IN_ + }; +ULONG SRB_SegmentPad2[]={ + 0, /* offset SRB.SegmentPad,*/ + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_din_pad_addrW[]={ + 0 /* offset din_pad_addrW */ + }; +/* +;========================================================== +; Data out phase +;========================================================== +*/ +ULONG dout_phaseW[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment15,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment0,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment1,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment2,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment3,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment4,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment5,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment6,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment7,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment8,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment9,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment10,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment11,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment12,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment13,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment14,*/ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+CHAIN_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment15,*/ +/*; 18000000h or DATA_OUT_ */ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_, + 0, /* offset SRB.Segment16,*/ + + RD_MODIFY_WRT+OP_OR+0x340000+0x100, /*;(1 shl 8) or (__scratcha shl 16)*/ + 0, + + DCMD_JUMP+WAIT_PHASE_VALID+IF_NOT+PHASE_CMP+DATA_OUT_ + }; +ULONG jmp_status1_phase3[]={ + 0 /* offset status1_phase */ + }; + +#define dout_phase1 dout_phaseW + +ULONG dout_pad_1[]={ + RD_MODIFY_WRT+OP_OR+0x340000+0x400, /*;(4 shl 8) or (__scratcha shl 16)*/ + 0 + }; +ULONG dout_pad_addrW[]={ + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+DATA_OUT_ + }; +ULONG SRB_SegmentPad3[]={ + 0, /* offset SRB.SegmentPad,*/ + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_dout_pad_addrW[]={ + 0 /* offset dout_pad_addrW */ + }; +/* +;========================================================== +; message out phase +;========================================================== +*/ +ULONG mout_phase[]={ + DCMD_SET_ATN, + 0, + + DCMD_BLOCK_MOVE+TABLE_INDIRECT+BLOCK_MOVE+MSG_OUT_ + }; +ULONG SRB_msgout01[]={ + 0, /* offset SRB.__msgout0,*/ + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_check_phase2[]={ + 0 /* offset check_phase */ + }; +/* +;========================================================== +; Status phase process +;========================================================== +*/ +ULONG status_phase[]={ + DCMD_BLOCK_MOVE+BLOCK_MOVE+STATUS_+1 + }; +ULONG ACB_status[]={ + 0 /* offset ACB.status */ + }; +/* +;========================================================== +; message in phase +;========================================================== +*/ +ULONG min_phase[]={ + DCMD_BLOCK_MOVE+BLOCK_MOVE+MSG_IN_+1 + }; +ULONG ACB_msgin123_3[]={ + 0, /* offset ACB.msgin123,*/ + + DCMD_JUMP+IF_NOT+DATA_CMP+CMD_COMPLETE + }; +ULONG jmp_jump_msgok[]={ + 0 /* offset jump_msgok */ + }; +/* +;========================================================== +; command complete message +;========================================================== +*/ +ULONG msg__0[]={ + RD_MODIFY_WRT+OP_AND+0x20000+0x7F00, /*;(7FH shl 8) or (__scntl2 shl 16)*/ + 0, + + DCMD_CLR_ACK, + 0, + + DCMD_WAIT_DISC, + 0, + + DCMD_INT+IF_TRUE, + __COMPLETE + }; +/* +;========================================================== +; Other message +;========================================================== +*/ +ULONG jump_msgok[]={ + DCMD_JUMP+IF_TRUE+DATA_CMP+SAVE_PTR + }; +ULONG jmp_msg__a[]={ + 0, /* offset msg__a,*/ + + DCMD_JUMP+IF_TRUE+DATA_CMP+RESTORE_PTR + }; +ULONG jmp_msg__3[]={ + 0, /* offset msg__3,*/ + + DCMD_JUMP+IF_TRUE+DATA_CMP+DISCONNECTMSG + }; +ULONG jmp_msg__4[]={ + 0, /* offset msg__4,*/ + + DCMD_JUMP+IF_TRUE+DATA_CMP+EXT_MSG + }; +ULONG jmp_msg__1[]={ + 0, /* offset msg__1,*/ + + DCMD_INT+IF_TRUE+DATA_CMP+MSG_REJECT, + __MSGREJECT, + + DCMD_JUMP+IF_TRUE+DATA_CMP+LINK_CMD_CPL + }; +ULONG jmp_msg__a1[]={ + 0, /* offset msg__a,*/ + + DCMD_JUMP+IF_TRUE+DATA_CMP+LINK_CMD_FLAG + }; +ULONG jmp_msg__a2[]={ + 0, /* offset msg__a,*/ + + DCMD_JUMP+IF_TRUE+DATA_CMP+IGNORE_WIDE_RES + }; +ULONG jmp_msg__23[]={ + 0, /* offset msg__23,*/ + + DCMD_INT+IF_TRUE, + __MSGUNKNOWN + }; +/* +;========================================================== +; Extended message +;========================================================== +*/ +ULONG msg__1[]={ + DCMD_CLR_ACK, + 0, + + DCMD_BLOCK_MOVE+BLOCK_MOVE+MSG_IN_+1 /* ;ext msg len */ + }; +ULONG ACB_msgin123_4[]={ + 0, /* offset ACB.msgin123,*/ + + DCMD_JUMP+IF_TRUE+DATA_CMP+3 + }; +ULONG jmp_msg___3[]={ + 0, /* offset msg___3,*/ + + DCMD_JUMP+IF_TRUE+DATA_CMP+2 + }; +ULONG jmp_msg___2[]={ + 0, /* offset msg___2,*/ + + DCMD_INT+IF_TRUE, + __MSGEXTEND + }; + +ULONG msg___3[]={ + DCMD_CLR_ACK, + 0, + + DCMD_BLOCK_MOVE+BLOCK_MOVE+MSG_IN_+3 + }; +ULONG ACB_msgin123_5[]={ + 0, /* offset ACB.msgin123,*/ + + DCMD_INT+IF_TRUE, + __MSGSYNC + }; + +ULONG msg___2[]={ + DCMD_CLR_ACK, + 0, + + DCMD_BLOCK_MOVE+BLOCK_MOVE+MSG_IN_+2 + }; +ULONG ACB_msgin123_6[]={ + 0, /* offset ACB.msgin123,*/ + + DCMD_INT+IF_TRUE, + __MSGWIDE + }; +/* +;############################################################ +; for synchronous negotiation +; 1. Active ==> INT3, restart at data__1_2 +; 2. Passive ==> INT3, prepare message out, restart at data__1_1 +; 3. Disable ==> INT3, prepare message out, restart at data__1_1 +;############################################################ +*/ +ULONG set_atn[]={ + DCMD_SET_ATN, + 0 + }; +ULONG msg__a[]={ + DCMD_CLR_ACK, + 0, + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_check_phase3[]={ + 0 /* offset check_phase */ + }; + +ULONG msg__23[]={ /* ; ignore wide residue */ + DCMD_CLR_ACK, + 0, + + DCMD_BLOCK_MOVE+BLOCK_MOVE+MSG_IN_+1 + }; +ULONG ACB_msgin123_7[]={ + 0, /* offset ACB.msgin123,*/ + + DCMD_CLR_ACK, + 0, + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_check_phase4[]={ + 0 /* offset check_phase */ + }; + +ULONG msg__3[]={ + DCMD_CLR_ACK, + 0, + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_check_phase5[]={ + 0 /* offset check_phase */ + }; + +ULONG msg__4[]={ /* ; disconnect */ + RD_MODIFY_WRT+OP_AND+0x20000+0x7F00, /*;(7FH shl 8) or (__scntl2 shl 16)*/ + 0, + + DCMD_CLR_ACK, + 0, + + DCMD_WAIT_DISC, + 0, + + DCMD_INT+IF_TRUE, + __DISCONNECTED + }; + +ULONG clr_atn[]={ + DCMD_CLR_ATN, + 0, + + DCMD_JUMP+IF_TRUE + }; +ULONG jmp_check_phase6[]={ + 0 /* offset check_phase */ + }; +/* +;========================================================== +; Used for script operation +;========================================================== +*/ +ULONG start_mov[]={ +/*; DCMD_MEM_MOVE+(OFFSET DGROUP:end_script - OFFSET DGROUP:start_script) ;Memory move SCRIPTS instruction*/ + DCMD_MEM_MOVE+0x1000 /*;Memory move SCRIPTS instruction ( 4K )*/ + }; +ULONG SrcPhysAddr[]={ + 0 /*; source */ + }; +ULONG DesPhysAddr[]={ + 0, /*; destination */ + + DCMD_INT+IF_TRUE, /*; script interrupt, */ + 0, + + DCMD_INT+IF_NOT, /*; script interrupt */ + 0 + }; +ULONG end_script[]={0}; +/***********************************************************************/ + diff --git a/linux/src/drivers/scsi/scsi.c b/linux/src/drivers/scsi/scsi.c new file mode 100644 index 00000000..62c4b10c --- /dev/null +++ b/linux/src/drivers/scsi/scsi.c @@ -0,0 +1,3585 @@ +/* + * scsi.c Copyright (C) 1992 Drew Eckhardt + * Copyright (C) 1993, 1994, 1995 Eric Youngdale + * + * generic mid-level SCSI driver + * Initial versions: Drew Eckhardt + * Subsequent revisions: Eric Youngdale + * + * <drew@colorado.edu> + * + * Bug correction thanks go to : + * Rik Faith <faith@cs.unc.edu> + * Tommy Thorn <tthorn> + * Thomas Wuensche <tw@fgb1.fgb.mw.tu-muenchen.de> + * + * Modified by Eric Youngdale eric@aib.com to + * add scatter-gather, multiple outstanding request, and other + * enhancements. + * + * Native multichannel, wide scsi, /proc/scsi and hot plugging + * support added by Michael Neuffer <mike@i-connect.net> + * + * Added request_module("scsi_hostadapter") for kerneld: + * (Put an "alias scsi_hostadapter your_hostadapter" in /etc/conf.modules) + * Bjorn Ekwall <bj0rn@blox.se> + * + * Major improvements to the timeout, abort, and reset processing, + * as well as performance modifications for large queue depths by + * Leonard N. Zubkoff <lnz@dandelion.com> + */ + +/* + * Don't import our own symbols, as this would severely mess up our + * symbol tables. + */ +#define _SCSI_SYMS_VER_ + +#include <linux/config.h> +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/malloc.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/stat.h> +#include <linux/blk.h> +#include <linux/interrupt.h> +#include <linux/delay.h> + +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/dma.h> + +#include "scsi.h" +#include "hosts.h" +#include "constants.h" + +#ifdef CONFIG_KERNELD +#include <linux/kerneld.h> +#endif + +#undef USE_STATIC_SCSI_MEMORY + +/* +static const char RCSid[] = "$Header: cvs/gnumach/linux/src/drivers/scsi/Attic/scsi.c,v 1.1 1999/04/26 05:54:57 tb Exp $"; +*/ + + +/* Command groups 3 and 4 are reserved and should never be used. */ +const unsigned char scsi_command_size[8] = { 6, 10, 10, 12, 12, 12, 10, 10 }; + +#define INTERNAL_ERROR (panic ("Internal error in file %s, line %d.\n", __FILE__, __LINE__)) + +/* + * PAGE_SIZE must be a multiple of the sector size (512). True + * for all reasonably recent architectures (even the VAX...). + */ +#define SECTOR_SIZE 512 +#define SECTORS_PER_PAGE (PAGE_SIZE/SECTOR_SIZE) + +#if SECTORS_PER_PAGE <= 8 + typedef unsigned char FreeSectorBitmap; +#elif SECTORS_PER_PAGE <= 32 + typedef unsigned int FreeSectorBitmap; +#else +# error You lose. +#endif + +static void scsi_done (Scsi_Cmnd *SCpnt); +static int update_timeout (Scsi_Cmnd *, int); +static void print_inquiry(unsigned char *data); +static void scsi_times_out (Scsi_Cmnd * SCpnt); +static int scan_scsis_single (int channel,int dev,int lun,int * max_scsi_dev , + int * sparse_lun, Scsi_Device ** SDpnt, Scsi_Cmnd * SCpnt, + struct Scsi_Host *shpnt, char * scsi_result); +void scsi_build_commandblocks(Scsi_Device * SDpnt); + +#ifdef CONFIG_MODULES +extern struct symbol_table scsi_symbol_table; +#endif + +static FreeSectorBitmap * dma_malloc_freelist = NULL; +static int scsi_need_isa_bounce_buffers; +static unsigned int dma_sectors = 0; +unsigned int dma_free_sectors = 0; +unsigned int need_isa_buffer = 0; +static unsigned char ** dma_malloc_pages = NULL; + +static int time_start; +static int time_elapsed; +static volatile struct Scsi_Host * host_active = NULL; +#define SCSI_BLOCK(HOST) ((HOST->block && host_active && HOST != host_active) \ + || (HOST->can_queue && HOST->host_busy >= HOST->can_queue)) + +const char *const scsi_device_types[MAX_SCSI_DEVICE_CODE] = +{ + "Direct-Access ", + "Sequential-Access", + "Printer ", + "Processor ", + "WORM ", + "CD-ROM ", + "Scanner ", + "Optical Device ", + "Medium Changer ", + "Communications " +}; + + +/* + * global variables : + * scsi_devices an array of these specifying the address for each + * (host, id, LUN) + */ + +Scsi_Device * scsi_devices = NULL; + +/* Process ID of SCSI commands */ +unsigned long scsi_pid = 0; + +static unsigned long serial_number = 0; + +static unsigned char generic_sense[6] = {REQUEST_SENSE, 0,0,0, 255, 0}; +static void resize_dma_pool(void); + +/* This variable is merely a hook so that we can debug the kernel with gdb. */ +Scsi_Cmnd * last_cmnd = NULL; + +/* This is the pointer to the /proc/scsi code. + * It is only initialized to !=0 if the scsi code is present + */ +#if CONFIG_PROC_FS +extern int (* dispatch_scsi_info_ptr)(int ino, char *buffer, char **start, + off_t offset, int length, int inout); +extern int dispatch_scsi_info(int ino, char *buffer, char **start, + off_t offset, int length, int inout); + +struct proc_dir_entry proc_scsi_scsi = { + PROC_SCSI_SCSI, 4, "scsi", + S_IFREG | S_IRUGO | S_IWUSR, 1, 0, 0, 0, + NULL, + NULL, NULL, + NULL, NULL, NULL +}; +#endif + +/* + * This is the number of clock ticks we should wait before we time out + * and abort the command. This is for where the scsi.c module generates + * the command, not where it originates from a higher level, in which + * case the timeout is specified there. + * + * ABORT_TIMEOUT and RESET_TIMEOUT are the timeouts for RESET and ABORT + * respectively. + */ + +#ifdef DEBUG_TIMEOUT +static void scsi_dump_status(void); +#endif + + +#ifdef DEBUG + #define SCSI_TIMEOUT (5*HZ) +#else + #define SCSI_TIMEOUT (2*HZ) +#endif + +#ifdef DEBUG + #define SENSE_TIMEOUT SCSI_TIMEOUT + #define ABORT_TIMEOUT SCSI_TIMEOUT + #define RESET_TIMEOUT SCSI_TIMEOUT +#else + #define SENSE_TIMEOUT (5*HZ/10) + #define RESET_TIMEOUT (5*HZ/10) + #define ABORT_TIMEOUT (5*HZ/10) +#endif + +#define MIN_RESET_DELAY (2*HZ) + +/* Do not call reset on error if we just did a reset within 15 sec. */ +#define MIN_RESET_PERIOD (15*HZ) + +/* The following devices are known not to tolerate a lun != 0 scan for + * one reason or another. Some will respond to all luns, others will + * lock up. + */ + +#define BLIST_NOLUN 0x01 +#define BLIST_FORCELUN 0x02 +#define BLIST_BORKEN 0x04 +#define BLIST_KEY 0x08 +#define BLIST_SINGLELUN 0x10 +#define BLIST_NOTQ 0x20 +#define BLIST_SPARSELUN 0x40 +#define BLIST_MAX5LUN 0x80 + +struct dev_info{ + const char * vendor; + const char * model; + const char * revision; /* Latest revision known to be bad. Not used yet */ + unsigned flags; +}; + +/* + * This is what was previously known as the blacklist. The concept + * has been expanded so that we can specify other types of things we + * need to be aware of. + */ +static struct dev_info device_list[] = +{ +{"TEAC","CD-R55S","1.0H", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"CHINON","CD-ROM CDS-431","H42", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"CHINON","CD-ROM CDS-535","Q14", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"DENON","DRD-25X","V", BLIST_NOLUN}, /* Locks up if probed for lun != 0 */ +{"HITACHI","DK312C","CM81", BLIST_NOLUN}, /* Responds to all lun - dtg */ +{"HITACHI","DK314C","CR21" , BLIST_NOLUN}, /* responds to all lun */ +{"IMS", "CDD521/10","2.06", BLIST_NOLUN}, /* Locks-up when LUN>0 polled. */ +{"MAXTOR","XT-3280","PR02", BLIST_NOLUN}, /* Locks-up when LUN>0 polled. */ +{"MAXTOR","XT-4380S","B3C", BLIST_NOLUN}, /* Locks-up when LUN>0 polled. */ +{"MAXTOR","MXT-1240S","I1.2", BLIST_NOLUN}, /* Locks up when LUN>0 polled */ +{"MAXTOR","XT-4170S","B5A", BLIST_NOLUN}, /* Locks-up sometimes when LUN>0 polled. */ +{"MAXTOR","XT-8760S","B7B", BLIST_NOLUN}, /* guess what? */ +{"MEDIAVIS","RENO CD-ROMX2A","2.03",BLIST_NOLUN},/*Responds to all lun */ +{"MICROP", "4110", "*", BLIST_NOTQ}, /* Buggy Tagged Queuing */ +{"NEC","CD-ROM DRIVE:841","1.0", BLIST_NOLUN}, /* Locks-up when LUN>0 polled. */ +{"RODIME","RO3000S","2.33", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"SANYO", "CRD-250S", "1.20", BLIST_NOLUN}, /* causes failed REQUEST SENSE on lun 1 + * for aha152x controller, which causes + * SCSI code to reset bus.*/ +{"SEAGATE", "ST157N", "\004|j", BLIST_NOLUN}, /* causes failed REQUEST SENSE on lun 1 + * for aha152x controller, which causes + * SCSI code to reset bus.*/ +{"SEAGATE", "ST296","921", BLIST_NOLUN}, /* Responds to all lun */ +{"SEAGATE","ST1581","6538",BLIST_NOLUN}, /* Responds to all lun */ +{"SONY","CD-ROM CDU-541","4.3d", BLIST_NOLUN}, +{"SONY","CD-ROM CDU-55S","1.0i", BLIST_NOLUN}, +{"SONY","CD-ROM CDU-561","1.7x", BLIST_NOLUN}, +{"TANDBERG","TDC 3600","U07", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"TEAC","CD-ROM","1.06", BLIST_NOLUN}, /* causes failed REQUEST SENSE on lun 1 + * for seagate controller, which causes + * SCSI code to reset bus.*/ +{"TEXEL","CD-ROM","1.06", BLIST_NOLUN}, /* causes failed REQUEST SENSE on lun 1 + * for seagate controller, which causes + * SCSI code to reset bus.*/ +{"QUANTUM","LPS525S","3110", BLIST_NOLUN}, /* Locks sometimes if polled for lun != 0 */ +{"QUANTUM","PD1225S","3110", BLIST_NOLUN}, /* Locks sometimes if polled for lun != 0 */ +{"MEDIAVIS","CDR-H93MV","1.31", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"SANKYO", "CP525","6.64", BLIST_NOLUN}, /* causes failed REQ SENSE, extra reset */ +{"HP", "C1750A", "3226", BLIST_NOLUN}, /* scanjet iic */ +{"HP", "C1790A", "", BLIST_NOLUN}, /* scanjet iip */ +{"HP", "C2500A", "", BLIST_NOLUN}, /* scanjet iicx */ + +/* + * Other types of devices that have special flags. + */ +{"SONY","CD-ROM CDU-8001","*", BLIST_BORKEN}, +{"TEXEL","CD-ROM","1.06", BLIST_BORKEN}, +{"IOMEGA","Io20S *F","*", BLIST_KEY}, +{"INSITE","Floptical F*8I","*", BLIST_KEY}, +{"INSITE","I325VM","*", BLIST_KEY}, +{"NRC","MBR-7","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"NRC","MBR-7.4","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"REGAL","CDC-4X","*", BLIST_MAX5LUN | BLIST_SINGLELUN}, +{"NAKAMICH","MJ-4.8S","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"NAKAMICH","MJ-5.16S","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"PIONEER","CD-ROM DRM-600","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"PIONEER","CD-ROM DRM-602X","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"PIONEER","CD-ROM DRM-604X","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"EMULEX","MD21/S2 ESDI","*", BLIST_SINGLELUN}, +{"CANON","IPUBJD","*", BLIST_SPARSELUN}, +{"MATSHITA","PD","*", BLIST_FORCELUN | BLIST_SINGLELUN}, +{"YAMAHA","CDR100","1.00", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"YAMAHA","CDR102","1.00", BLIST_NOLUN}, /* Locks up if polled for lun != 0 */ +{"nCipher","Fastness Crypto","*", BLIST_FORCELUN}, +/* + * Must be at end of list... + */ +{NULL, NULL, NULL} +}; + +static int get_device_flags(unsigned char * response_data){ + int i = 0; + unsigned char * pnt; + for(i=0; 1; i++){ + if(device_list[i].vendor == NULL) return 0; + pnt = &response_data[8]; + while(*pnt && *pnt == ' ') pnt++; + if(memcmp(device_list[i].vendor, pnt, + strlen(device_list[i].vendor))) continue; + pnt = &response_data[16]; + while(*pnt && *pnt == ' ') pnt++; + if(memcmp(device_list[i].model, pnt, + strlen(device_list[i].model))) continue; + return device_list[i].flags; + } + return 0; +} + +void scsi_make_blocked_list(void) { + int block_count = 0, index; + unsigned long flags; + struct Scsi_Host * sh[128], * shpnt; + + /* + * Create a circular linked list from the scsi hosts which have + * the "wish_block" field in the Scsi_Host structure set. + * The blocked list should include all the scsi hosts using ISA DMA. + * In some systems, using two dma channels simultaneously causes + * unpredictable results. + * Among the scsi hosts in the blocked list, only one host at a time + * is allowed to have active commands queued. The transition from + * one active host to the next one is allowed only when host_busy == 0 + * for the active host (which implies host_busy == 0 for all the hosts + * in the list). Moreover for block devices the transition to a new + * active host is allowed only when a request is completed, since a + * block device request can be divided into multiple scsi commands + * (when there are few sg lists or clustering is disabled). + * + * (DB, 4 Feb 1995) + */ + + save_flags(flags); + cli(); + host_active = NULL; + + for(shpnt=scsi_hostlist; shpnt; shpnt = shpnt->next) { + +#if 0 + /* + * Is this is a candidate for the blocked list? + * Useful to put into the blocked list all the hosts whose driver + * does not know about the host->block feature. + */ + if (shpnt->unchecked_isa_dma) shpnt->wish_block = 1; +#endif + + if (shpnt->wish_block) sh[block_count++] = shpnt; + } + + if (block_count == 1) sh[0]->block = NULL; + + else if (block_count > 1) { + + for(index = 0; index < block_count - 1; index++) { + sh[index]->block = sh[index + 1]; + printk("scsi%d : added to blocked host list.\n", + sh[index]->host_no); + } + + sh[block_count - 1]->block = sh[0]; + printk("scsi%d : added to blocked host list.\n", + sh[index]->host_no); + } + + restore_flags(flags); +} + +static void scan_scsis_done (Scsi_Cmnd * SCpnt) +{ + +#ifdef DEBUG + printk ("scan_scsis_done(%p, %06x)\n", SCpnt->host, SCpnt->result); +#endif + SCpnt->request.rq_status = RQ_SCSI_DONE; + + if (SCpnt->request.sem != NULL) + up(SCpnt->request.sem); +} + +#ifdef CONFIG_SCSI_MULTI_LUN +static int max_scsi_luns = 8; +#else +static int max_scsi_luns = 1; +#endif + +void scsi_luns_setup(char *str, int *ints) { + if (ints[0] != 1) + printk("scsi_luns_setup : usage max_scsi_luns=n (n should be between 1 and 8)\n"); + else + max_scsi_luns = ints[1]; +} + +/* + * Detecting SCSI devices : + * We scan all present host adapter's busses, from ID 0 to ID (max_id). + * We use the INQUIRY command, determine device type, and pass the ID / + * lun address of all sequential devices to the tape driver, all random + * devices to the disk driver. + */ +static void scan_scsis (struct Scsi_Host *shpnt, unchar hardcoded, + unchar hchannel, unchar hid, unchar hlun) +{ + int dev, lun, channel; + unsigned char scsi_result0[256]; + unsigned char *scsi_result; + Scsi_Device *SDpnt; + int max_dev_lun, sparse_lun; + Scsi_Cmnd *SCpnt; + + SCpnt = (Scsi_Cmnd *) scsi_init_malloc (sizeof (Scsi_Cmnd), GFP_ATOMIC | GFP_DMA); + SDpnt = (Scsi_Device *) scsi_init_malloc (sizeof (Scsi_Device), GFP_ATOMIC); + memset (SCpnt, 0, sizeof (Scsi_Cmnd)); + + + /* Make sure we have something that is valid for DMA purposes */ + scsi_result = ( ( !shpnt->unchecked_isa_dma ) + ? &scsi_result0[0] : scsi_init_malloc (512, GFP_DMA)); + + if (scsi_result == NULL) { + printk ("Unable to obtain scsi_result buffer\n"); + goto leave; + } + + /* We must chain ourself in the host_queue, so commands can time out */ + if(shpnt->host_queue) + shpnt->host_queue->prev = SCpnt; + SCpnt->next = shpnt->host_queue; + SCpnt->prev = NULL; + shpnt->host_queue = SCpnt; + + + if (hardcoded == 1) { + Scsi_Device *oldSDpnt=SDpnt; + struct Scsi_Device_Template * sdtpnt; + channel = hchannel; + if(channel > shpnt->max_channel) goto leave; + dev = hid; + if(dev >= shpnt->max_id) goto leave; + lun = hlun; + if(lun >= shpnt->max_lun) goto leave; + scan_scsis_single (channel, dev, lun, &max_dev_lun, &sparse_lun, + &SDpnt, SCpnt, shpnt, scsi_result); + if(SDpnt!=oldSDpnt) { + + /* it could happen the blockdevice hasn't yet been inited */ + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->init && sdtpnt->dev_noticed) (*sdtpnt->init)(); + + oldSDpnt->scsi_request_fn = NULL; + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->attach) { + (*sdtpnt->attach)(oldSDpnt); + if(oldSDpnt->attached) scsi_build_commandblocks(oldSDpnt);} + resize_dma_pool(); + + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) { + if(sdtpnt->finish && sdtpnt->nr_dev) + {(*sdtpnt->finish)();} + } + } + + } + else { + for (channel = 0; channel <= shpnt->max_channel; channel++) { + for (dev = 0; dev < shpnt->max_id; ++dev) { + if (shpnt->this_id != dev) { + + /* + * We need the for so our continue, etc. work fine. We put this in + * a variable so that we can override it during the scan if we + * detect a device *KNOWN* to have multiple logical units. + */ + max_dev_lun = (max_scsi_luns < shpnt->max_lun ? + max_scsi_luns : shpnt->max_lun); + sparse_lun = 0; + for (lun = 0; lun < max_dev_lun; ++lun) { + if (!scan_scsis_single (channel, dev, lun, &max_dev_lun, + &sparse_lun, &SDpnt, SCpnt, shpnt, + scsi_result) + && !sparse_lun) + break; /* break means don't probe further for luns!=0 */ + } /* for lun ends */ + } /* if this_id != id ends */ + } /* for dev ends */ + } /* for channel ends */ + } /* if/else hardcoded */ + + leave: + + {/* Unchain SCpnt from host_queue */ + Scsi_Cmnd *prev, *next, *hqptr; + for(hqptr = shpnt->host_queue; hqptr != SCpnt; hqptr = hqptr->next) ; + if(hqptr) { + prev = hqptr->prev; + next = hqptr->next; + if(prev) + prev->next = next; + else + shpnt->host_queue = next; + if(next) next->prev = prev; + } + } + + /* Last device block does not exist. Free memory. */ + if (SDpnt != NULL) + scsi_init_free ((char *) SDpnt, sizeof (Scsi_Device)); + + if (SCpnt != NULL) + scsi_init_free ((char *) SCpnt, sizeof (Scsi_Cmnd)); + + /* If we allocated a buffer so we could do DMA, free it now */ + if (scsi_result != &scsi_result0[0] && scsi_result != NULL) + scsi_init_free (scsi_result, 512); + +} + +/* + * The worker for scan_scsis. + * Returning 0 means Please don't ask further for lun!=0, 1 means OK go on. + * Global variables used : scsi_devices(linked list) + */ +int scan_scsis_single (int channel, int dev, int lun, int *max_dev_lun, + int *sparse_lun, Scsi_Device **SDpnt2, Scsi_Cmnd * SCpnt, + struct Scsi_Host * shpnt, char *scsi_result) +{ + unsigned char scsi_cmd[12]; + struct Scsi_Device_Template *sdtpnt; + Scsi_Device * SDtail, *SDpnt=*SDpnt2; + int bflags, type=-1; + + SDtail = scsi_devices; + if (scsi_devices) + while (SDtail->next) + SDtail = SDtail->next; + + memset (SDpnt, 0, sizeof (Scsi_Device)); + SDpnt->host = shpnt; + SDpnt->id = dev; + SDpnt->lun = lun; + SDpnt->channel = channel; + + /* Some low level driver could use device->type (DB) */ + SDpnt->type = -1; + + /* + * Assume that the device will have handshaking problems, and then fix this + * field later if it turns out it doesn't + */ + SDpnt->borken = 1; + SDpnt->was_reset = 0; + SDpnt->expecting_cc_ua = 0; + + scsi_cmd[0] = TEST_UNIT_READY; + scsi_cmd[1] = lun << 5; + scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[4] = scsi_cmd[5] = 0; + + SCpnt->host = SDpnt->host; + SCpnt->device = SDpnt; + SCpnt->target = SDpnt->id; + SCpnt->lun = SDpnt->lun; + SCpnt->channel = SDpnt->channel; + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.sem = &sem; + SCpnt->request.rq_status = RQ_SCSI_BUSY; + scsi_do_cmd (SCpnt, (void *) scsi_cmd, + (void *) scsi_result, + 256, scan_scsis_done, SCSI_TIMEOUT + 4 * HZ, 5); + down (&sem); + } + +#if defined(DEBUG) || defined(DEBUG_INIT) + printk ("scsi: scan_scsis_single id %d lun %d. Return code 0x%08x\n", + dev, lun, SCpnt->result); + print_driverbyte(SCpnt->result); print_hostbyte(SCpnt->result); + printk("\n"); +#endif + + if (SCpnt->result) { + if (((driver_byte (SCpnt->result) & DRIVER_SENSE) || + (status_byte (SCpnt->result) & CHECK_CONDITION)) && + ((SCpnt->sense_buffer[0] & 0x70) >> 4) == 7) { + if (((SCpnt->sense_buffer[2] & 0xf) != NOT_READY) && + ((SCpnt->sense_buffer[2] & 0xf) != UNIT_ATTENTION) && + ((SCpnt->sense_buffer[2] & 0xf) != ILLEGAL_REQUEST || lun > 0)) + return 1; + } + else + return 0; + } + +#if defined (DEBUG) || defined(DEBUG_INIT) + printk ("scsi: performing INQUIRY\n"); +#endif + /* + * Build an INQUIRY command block. + */ + scsi_cmd[0] = INQUIRY; + scsi_cmd[1] = (lun << 5) & 0xe0; + scsi_cmd[2] = 0; + scsi_cmd[3] = 0; + scsi_cmd[4] = 255; + scsi_cmd[5] = 0; + SCpnt->cmd_len = 0; + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.sem = &sem; + SCpnt->request.rq_status = RQ_SCSI_BUSY; + scsi_do_cmd (SCpnt, (void *) scsi_cmd, + (void *) scsi_result, + 256, scan_scsis_done, SCSI_TIMEOUT, 3); + down (&sem); + } + +#if defined(DEBUG) || defined(DEBUG_INIT) + printk ("scsi: INQUIRY %s with code 0x%x\n", + SCpnt->result ? "failed" : "successful", SCpnt->result); +#endif + + if (SCpnt->result) + return 0; /* assume no peripheral if any sort of error */ + + /* + * Check the peripheral qualifier field - this tells us whether LUNS + * are supported here or not. + */ + if( (scsi_result[0] >> 5) == 3 ) + { + return 0; /* assume no peripheral if any sort of error */ + } + + /* + * It would seem some TOSHIBA CDROM gets things wrong + */ + if (!strncmp (scsi_result + 8, "TOSHIBA", 7) && + !strncmp (scsi_result + 16, "CD-ROM", 6) && + scsi_result[0] == TYPE_DISK) { + scsi_result[0] = TYPE_ROM; + scsi_result[1] |= 0x80; /* removable */ + } + + if (!strncmp (scsi_result + 8, "NEC", 3)) { + if (!strncmp (scsi_result + 16, "CD-ROM DRIVE:84 ", 16) || + !strncmp (scsi_result + 16, "CD-ROM DRIVE:25", 15)) + SDpnt->manufacturer = SCSI_MAN_NEC_OLDCDR; + else + SDpnt->manufacturer = SCSI_MAN_NEC; + } + else if (!strncmp (scsi_result + 8, "TOSHIBA", 7)) + SDpnt->manufacturer = SCSI_MAN_TOSHIBA; + else if (!strncmp (scsi_result + 8, "SONY", 4)) + SDpnt->manufacturer = SCSI_MAN_SONY; + else if (!strncmp (scsi_result + 8, "PIONEER", 7)) + SDpnt->manufacturer = SCSI_MAN_PIONEER; + else + SDpnt->manufacturer = SCSI_MAN_UNKNOWN; + + memcpy (SDpnt->vendor, scsi_result + 8, 8); + memcpy (SDpnt->model, scsi_result + 16, 16); + memcpy (SDpnt->rev, scsi_result + 32, 4); + + SDpnt->removable = (0x80 & scsi_result[1]) >> 7; + SDpnt->lockable = SDpnt->removable; + SDpnt->changed = 0; + SDpnt->access_count = 0; + SDpnt->busy = 0; + SDpnt->has_cmdblocks = 0; + /* + * Currently, all sequential devices are assumed to be tapes, all random + * devices disk, with the appropriate read only flags set for ROM / WORM + * treated as RO. + */ + switch (type = (scsi_result[0] & 0x1f)) { + case TYPE_TAPE: + case TYPE_DISK: + case TYPE_MOD: + case TYPE_PROCESSOR: + case TYPE_SCANNER: + case TYPE_MEDIUM_CHANGER: + SDpnt->writeable = 1; + break; + case TYPE_WORM: + case TYPE_ROM: + SDpnt->writeable = 0; + break; + default: + printk ("scsi: unknown type %d\n", type); + } + + SDpnt->single_lun = 0; + SDpnt->soft_reset = + (scsi_result[7] & 1) && ((scsi_result[3] & 7) == 2); + SDpnt->random = (type == TYPE_TAPE) ? 0 : 1; + SDpnt->type = (type & 0x1f); + + print_inquiry (scsi_result); + + for (sdtpnt = scsi_devicelist; sdtpnt; + sdtpnt = sdtpnt->next) + if (sdtpnt->detect) + SDpnt->attached += + (*sdtpnt->detect) (SDpnt); + + SDpnt->scsi_level = scsi_result[2] & 0x07; + if (SDpnt->scsi_level >= 2 || + (SDpnt->scsi_level == 1 && + (scsi_result[3] & 0x0f) == 1)) + SDpnt->scsi_level++; + + /* + * Accommodate drivers that want to sleep when they should be in a polling + * loop. + */ + SDpnt->disconnect = 0; + + /* + * Get any flags for this device. + */ + bflags = get_device_flags (scsi_result); + + /* + * Set the tagged_queue flag for SCSI-II devices that purport to support + * tagged queuing in the INQUIRY data. + */ + SDpnt->tagged_queue = 0; + if ((SDpnt->scsi_level >= SCSI_2) && + (scsi_result[7] & 2) && + !(bflags & BLIST_NOTQ)) { + SDpnt->tagged_supported = 1; + SDpnt->current_tag = 0; + } + + /* + * Some revisions of the Texel CD ROM drives have handshaking problems when + * used with the Seagate controllers. Before we know what type of device + * we're talking to, we assume it's borken and then change it here if it + * turns out that it isn't a TEXEL drive. + */ + if ((bflags & BLIST_BORKEN) == 0) + SDpnt->borken = 0; + + /* + * If we want to only allow I/O to one of the luns attached to this device + * at a time, then we set this flag. + */ + if (bflags & BLIST_SINGLELUN) + SDpnt->single_lun = 1; + + /* + * These devices need this "key" to unlock the devices so we can use it + */ + if ((bflags & BLIST_KEY) != 0) { + printk ("Unlocked floptical drive.\n"); + SDpnt->lockable = 0; + scsi_cmd[0] = MODE_SENSE; + scsi_cmd[1] = (lun << 5) & 0xe0; + scsi_cmd[2] = 0x2e; + scsi_cmd[3] = 0; + scsi_cmd[4] = 0x2a; + scsi_cmd[5] = 0; + SCpnt->cmd_len = 0; + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.rq_status = RQ_SCSI_BUSY; + SCpnt->request.sem = &sem; + scsi_do_cmd (SCpnt, (void *) scsi_cmd, + (void *) scsi_result, 0x2a, + scan_scsis_done, SCSI_TIMEOUT, 3); + down (&sem); + } + } + /* Add this device to the linked list at the end */ + if (SDtail) + SDtail->next = SDpnt; + else + scsi_devices = SDpnt; + SDtail = SDpnt; + + SDpnt = (Scsi_Device *) scsi_init_malloc (sizeof (Scsi_Device), GFP_ATOMIC); + *SDpnt2=SDpnt; + if (!SDpnt) + printk ("scsi: scan_scsis_single: Cannot malloc\n"); + + + /* + * Some scsi devices cannot be polled for lun != 0 due to firmware bugs + */ + if (bflags & BLIST_NOLUN) + return 0; /* break; */ + + /* + * If this device is known to support sparse multiple units, override the + * other settings, and scan all of them. + */ + if (bflags & BLIST_SPARSELUN) { + *max_dev_lun = 8; + *sparse_lun = 1; + return 1; + } + + /* + * If this device is known to support multiple units, override the other + * settings, and scan all of them. + */ + if (bflags & BLIST_FORCELUN) { + *max_dev_lun = 8; + return 1; + } + + /* + * REGAL CDC-4X: avoid hang after LUN 4 + */ + if (bflags & BLIST_MAX5LUN) { + *max_dev_lun = 5; + return 1; + } + + /* + * We assume the device can't handle lun!=0 if: - it reports scsi-0 (ANSI + * SCSI Revision 0) (old drives like MAXTOR XT-3280) or - it reports scsi-1 + * (ANSI SCSI Revision 1) and Response Data Format 0 + */ + if (((scsi_result[2] & 0x07) == 0) + || + ((scsi_result[2] & 0x07) == 1 && + (scsi_result[3] & 0x0f) == 0)) + return 0; + return 1; +} + +/* + * Flag bits for the internal_timeout array + */ +#define NORMAL_TIMEOUT 0 +#define IN_ABORT 1 +#define IN_RESET 2 +#define IN_RESET2 4 +#define IN_RESET3 8 + +/* + * This is our time out function, called when the timer expires for a + * given host adapter. It will attempt to abort the currently executing + * command, that failing perform a kernel panic. + */ + +static void scsi_times_out (Scsi_Cmnd * SCpnt) +{ + + switch (SCpnt->internal_timeout & (IN_ABORT | IN_RESET | IN_RESET2 | IN_RESET3)) + { + case NORMAL_TIMEOUT: + { +#ifdef DEBUG_TIMEOUT + scsi_dump_status(); +#endif + } + + if (!scsi_abort (SCpnt, DID_TIME_OUT)) + return; + case IN_ABORT: + printk("SCSI host %d abort (pid %ld) timed out - resetting\n", + SCpnt->host->host_no, SCpnt->pid); + if (!scsi_reset (SCpnt, SCSI_RESET_ASYNCHRONOUS)) + return; + case IN_RESET: + case (IN_ABORT | IN_RESET): + /* This might be controversial, but if there is a bus hang, + * you might conceivably want the machine up and running + * esp if you have an ide disk. + */ + printk("SCSI host %d channel %d reset (pid %ld) timed out - " + "trying harder\n", + SCpnt->host->host_no, SCpnt->channel, SCpnt->pid); + SCpnt->internal_timeout &= ~IN_RESET; + SCpnt->internal_timeout |= IN_RESET2; + scsi_reset (SCpnt, + SCSI_RESET_ASYNCHRONOUS | SCSI_RESET_SUGGEST_BUS_RESET); + return; + case IN_RESET2: + case (IN_ABORT | IN_RESET2): + /* Obviously the bus reset didn't work. + * Let's try even harder and call for an HBA reset. + * Maybe the HBA itself crashed and this will shake it loose. + */ + printk("SCSI host %d reset (pid %ld) timed out - trying to shake it loose\n", + SCpnt->host->host_no, SCpnt->pid); + SCpnt->internal_timeout &= ~(IN_RESET | IN_RESET2); + SCpnt->internal_timeout |= IN_RESET3; + scsi_reset (SCpnt, + SCSI_RESET_ASYNCHRONOUS | SCSI_RESET_SUGGEST_HOST_RESET); + return; + + default: + printk("SCSI host %d reset (pid %ld) timed out again -\n", + SCpnt->host->host_no, SCpnt->pid); + printk("probably an unrecoverable SCSI bus or device hang.\n"); + return; + + } + +} + + +/* This function takes a quick look at a request, and decides if it + * can be queued now, or if there would be a stall while waiting for + * something else to finish. This routine assumes that interrupts are + * turned off when entering the routine. It is the responsibility + * of the calling code to ensure that this is the case. + */ + +Scsi_Cmnd * request_queueable (struct request * req, Scsi_Device * device) +{ + Scsi_Cmnd * SCpnt = NULL; + int tablesize; + Scsi_Cmnd * found = NULL; + struct buffer_head * bh, *bhp; + + if (!device) + panic ("No device passed to request_queueable().\n"); + + if (req && req->rq_status == RQ_INACTIVE) + panic("Inactive in request_queueable"); + + /* + * Look for a free command block. If we have been instructed not to queue + * multiple commands to multi-lun devices, then check to see what else is + * going for this device first. + */ + + if (!device->single_lun) { + SCpnt = device->device_queue; + while(SCpnt){ + if(SCpnt->request.rq_status == RQ_INACTIVE) break; + SCpnt = SCpnt->device_next; + } + } else { + SCpnt = device->host->host_queue; + while(SCpnt){ + if(SCpnt->channel == device->channel + && SCpnt->target == device->id) { + if (SCpnt->lun == device->lun) { + if(found == NULL + && SCpnt->request.rq_status == RQ_INACTIVE) + { + found=SCpnt; + } + } + if(SCpnt->request.rq_status != RQ_INACTIVE) { + /* + * I think that we should really limit things to one + * outstanding command per device - this is what tends + * to trip up buggy firmware. + */ + return NULL; + } + } + SCpnt = SCpnt->next; + } + SCpnt = found; + } + + if (!SCpnt) return NULL; + + if (SCSI_BLOCK(device->host)) return NULL; + + if (req) { + memcpy(&SCpnt->request, req, sizeof(struct request)); + tablesize = device->host->sg_tablesize; + bhp = bh = req->bh; + if(!tablesize) bh = NULL; + /* Take a quick look through the table to see how big it is. + * We already have our copy of req, so we can mess with that + * if we want to. + */ + while(req->nr_sectors && bh){ + bhp = bhp->b_reqnext; + if(!bhp || !CONTIGUOUS_BUFFERS(bh,bhp)) tablesize--; + req->nr_sectors -= bh->b_size >> 9; + req->sector += bh->b_size >> 9; + if(!tablesize) break; + bh = bhp; + } + if(req->nr_sectors && bh && bh->b_reqnext){ /* Any leftovers? */ + SCpnt->request.bhtail = bh; + req->bh = bh->b_reqnext; /* Divide request */ + bh->b_reqnext = NULL; + bh = req->bh; + + /* Now reset things so that req looks OK */ + SCpnt->request.nr_sectors -= req->nr_sectors; + req->current_nr_sectors = bh->b_size >> 9; + req->buffer = bh->b_data; + SCpnt->request.sem = NULL; /* Wait until whole thing done */ + } else { + req->rq_status = RQ_INACTIVE; + wake_up(&wait_for_request); + } + } else { + SCpnt->request.rq_status = RQ_SCSI_BUSY; /* Busy, but no request */ + SCpnt->request.sem = NULL; /* And no one is waiting for the device + * either */ + } + + SCpnt->use_sg = 0; /* Reset the scatter-gather flag */ + SCpnt->old_use_sg = 0; + SCpnt->transfersize = 0; + SCpnt->underflow = 0; + SCpnt->cmd_len = 0; + +/* Since not everyone seems to set the device info correctly + * before Scsi_Cmnd gets send out to scsi_do_command, we do it here. + */ + SCpnt->channel = device->channel; + SCpnt->lun = device->lun; + SCpnt->target = device->id; + + return SCpnt; +} + +/* This function returns a structure pointer that will be valid for + * the device. The wait parameter tells us whether we should wait for + * the unit to become free or not. We are also able to tell this routine + * not to return a descriptor if the host is unable to accept any more + * commands for the time being. We need to keep in mind that there is no + * guarantee that the host remain not busy. Keep in mind the + * request_queueable function also knows the internal allocation scheme + * of the packets for each device + */ + +Scsi_Cmnd * allocate_device (struct request ** reqp, Scsi_Device * device, + int wait) +{ + kdev_t dev; + struct request * req = NULL; + int tablesize; + unsigned long flags; + struct buffer_head * bh, *bhp; + struct Scsi_Host * host; + Scsi_Cmnd * SCpnt = NULL; + Scsi_Cmnd * SCwait = NULL; + Scsi_Cmnd * found = NULL; + + if (!device) + panic ("No device passed to allocate_device().\n"); + + if (reqp) req = *reqp; + + /* See if this request has already been queued by an interrupt routine */ + if (req) { + if(req->rq_status == RQ_INACTIVE) return NULL; + dev = req->rq_dev; + } else + dev = 0; /* unused */ + + host = device->host; + + if (intr_count && SCSI_BLOCK(host)) return NULL; + + while (1==1){ + if (!device->single_lun) { + SCpnt = device->device_queue; + while(SCpnt){ + SCwait = SCpnt; + if(SCpnt->request.rq_status == RQ_INACTIVE) break; + SCpnt = SCpnt->device_next; + } + } else { + SCpnt = device->host->host_queue; + while(SCpnt){ + if(SCpnt->channel == device->channel + && SCpnt->target == device->id) { + if (SCpnt->lun == device->lun) { + SCwait = SCpnt; + if(found == NULL + && SCpnt->request.rq_status == RQ_INACTIVE) + { + found=SCpnt; + } + } + if(SCpnt->request.rq_status != RQ_INACTIVE) { + /* + * I think that we should really limit things to one + * outstanding command per device - this is what tends + * to trip up buggy firmware. + */ + found = NULL; + break; + } + } + SCpnt = SCpnt->next; + } + SCpnt = found; + } + + save_flags(flags); + cli(); + /* See if this request has already been queued by an interrupt routine + */ + if (req && (req->rq_status == RQ_INACTIVE || req->rq_dev != dev)) { + restore_flags(flags); + return NULL; + } + if (!SCpnt || SCpnt->request.rq_status != RQ_INACTIVE) /* Might have changed */ + { +#if 1 /* NEW CODE */ + if (wait && SCwait && SCwait->request.rq_status != RQ_INACTIVE){ + sleep_on(&device->device_wait); + restore_flags(flags); + } else { + restore_flags(flags); + if (!wait) return NULL; + if (!SCwait) { + printk("Attempt to allocate device channel %d," + " target %d, lun %d\n", device->channel, + device->id, device->lun); + panic("No device found in allocate_device\n"); + } + } +#else /* ORIGINAL CODE */ + restore_flags(flags); + if(!wait) return NULL; + if (!SCwait) { + printk("Attempt to allocate device channel %d, target" + " %d, lun %d\n", device->channel, device->id, + device->lun); + panic("No device found in allocate_device\n"); + } + SCSI_SLEEP(&device->device_wait, + (SCwait->request.rq_status != RQ_INACTIVE)); +#endif + } else { + if (req) { + memcpy(&SCpnt->request, req, sizeof(struct request)); + tablesize = device->host->sg_tablesize; + bhp = bh = req->bh; + if(!tablesize) bh = NULL; + /* Take a quick look through the table to see how big it is. + * We already have our copy of req, so we can mess with that + * if we want to. + */ + while(req->nr_sectors && bh){ + bhp = bhp->b_reqnext; + if(!bhp || !CONTIGUOUS_BUFFERS(bh,bhp)) tablesize--; + req->nr_sectors -= bh->b_size >> 9; + req->sector += bh->b_size >> 9; + if(!tablesize) break; + bh = bhp; + } + if(req->nr_sectors && bh && bh->b_reqnext){/* Any leftovers? */ + SCpnt->request.bhtail = bh; + req->bh = bh->b_reqnext; /* Divide request */ + bh->b_reqnext = NULL; + bh = req->bh; + /* Now reset things so that req looks OK */ + SCpnt->request.nr_sectors -= req->nr_sectors; + req->current_nr_sectors = bh->b_size >> 9; + req->buffer = bh->b_data; + SCpnt->request.sem = NULL; /* Wait until whole thing done*/ + } + else + { + req->rq_status = RQ_INACTIVE; + *reqp = req->next; + wake_up(&wait_for_request); + } + } else { + SCpnt->request.rq_status = RQ_SCSI_BUSY; + SCpnt->request.sem = NULL; /* And no one is waiting for this + * to complete */ + } + restore_flags(flags); + break; + } + } + + SCpnt->use_sg = 0; /* Reset the scatter-gather flag */ + SCpnt->old_use_sg = 0; + SCpnt->transfersize = 0; /* No default transfer size */ + SCpnt->cmd_len = 0; + + SCpnt->underflow = 0; /* Do not flag underflow conditions */ + + /* Since not everyone seems to set the device info correctly + * before Scsi_Cmnd gets send out to scsi_do_command, we do it here. + */ + SCpnt->channel = device->channel; + SCpnt->lun = device->lun; + SCpnt->target = device->id; + + return SCpnt; +} + +/* + * This is inline because we have stack problemes if we recurse to deeply. + */ + +inline void internal_cmnd (Scsi_Cmnd * SCpnt) +{ + unsigned long flags, timeout; + struct Scsi_Host * host; +#ifdef DEBUG_DELAY + unsigned long clock; +#endif + +#if DEBUG + unsigned long *ret = 0; +#ifdef __mips__ + __asm__ __volatile__ ("move\t%0,$31":"=r"(ret)); +#else + ret = __builtin_return_address(0); +#endif +#endif + + host = SCpnt->host; + + save_flags(flags); + cli(); + /* Assign a unique nonzero serial_number. */ + if (++serial_number == 0) serial_number = 1; + SCpnt->serial_number = serial_number; + + /* + * We will wait MIN_RESET_DELAY clock ticks after the last reset so + * we can avoid the drive not being ready. + */ + timeout = host->last_reset + MIN_RESET_DELAY; + if (jiffies < timeout) { + int ticks_remaining = timeout - jiffies; + /* + * NOTE: This may be executed from within an interrupt + * handler! This is bad, but for now, it'll do. The irq + * level of the interrupt handler has been masked out by the + * platform dependent interrupt handling code already, so the + * sti() here will not cause another call to the SCSI host's + * interrupt handler (assuming there is one irq-level per + * host). + */ + sti(); + while (--ticks_remaining >= 0) udelay(1000000/HZ); + host->last_reset = jiffies - MIN_RESET_DELAY; + } + restore_flags(flags); + + update_timeout(SCpnt, SCpnt->timeout_per_command); + + /* + * We will use a queued command if possible, otherwise we will emulate the + * queuing and calling of completion function ourselves. + */ +#ifdef DEBUG + printk("internal_cmnd (host = %d, channel = %d, target = %d, " + "command = %p, buffer = %p, \nbufflen = %d, done = %p)\n", + SCpnt->host->host_no, SCpnt->channel, SCpnt->target, SCpnt->cmnd, + SCpnt->buffer, SCpnt->bufflen, SCpnt->done); +#endif + + if (host->can_queue) + { +#ifdef DEBUG + printk("queuecommand : routine at %p\n", + host->hostt->queuecommand); +#endif + /* This locking tries to prevent all sorts of races between + * queuecommand and the interrupt code. In effect, + * we are only allowed to be in queuecommand once at + * any given time, and we can only be in the interrupt + * handler and the queuecommand function at the same time + * when queuecommand is called while servicing the + * interrupt. + */ + + if(!intr_count && SCpnt->host->irq) + disable_irq(SCpnt->host->irq); + + host->hostt->queuecommand (SCpnt, scsi_done); + + if(!intr_count && SCpnt->host->irq) + enable_irq(SCpnt->host->irq); + } + else + { + int temp; + +#ifdef DEBUG + printk("command() : routine at %p\n", host->hostt->command); +#endif + temp = host->hostt->command (SCpnt); + SCpnt->result = temp; +#ifdef DEBUG_DELAY + clock = jiffies + 4 * HZ; + while (jiffies < clock) barrier(); + printk("done(host = %d, result = %04x) : routine at %p\n", + host->host_no, temp, host->hostt->command); +#endif + scsi_done(SCpnt); + } +#ifdef DEBUG + printk("leaving internal_cmnd()\n"); +#endif +} + +static void scsi_request_sense (Scsi_Cmnd * SCpnt) +{ + unsigned long flags; + + save_flags(flags); + cli(); + SCpnt->flags |= WAS_SENSE | ASKED_FOR_SENSE; + update_timeout(SCpnt, SENSE_TIMEOUT); + restore_flags(flags); + + + memcpy ((void *) SCpnt->cmnd , (void *) generic_sense, + sizeof(generic_sense)); + + SCpnt->cmnd[1] = SCpnt->lun << 5; + SCpnt->cmnd[4] = sizeof(SCpnt->sense_buffer); + + SCpnt->request_buffer = &SCpnt->sense_buffer; + SCpnt->request_bufflen = sizeof(SCpnt->sense_buffer); + SCpnt->use_sg = 0; + SCpnt->cmd_len = COMMAND_SIZE(SCpnt->cmnd[0]); + internal_cmnd (SCpnt); +} + + + +/* + * scsi_do_cmd sends all the commands out to the low-level driver. It + * handles the specifics required for each low level driver - ie queued + * or non queued. It also prevents conflicts when different high level + * drivers go for the same host at the same time. + */ + +void scsi_do_cmd (Scsi_Cmnd * SCpnt, const void *cmnd , + void *buffer, unsigned bufflen, void (*done)(Scsi_Cmnd *), + int timeout, int retries) +{ + unsigned long flags; + struct Scsi_Host * host = SCpnt->host; + +#ifdef DEBUG + { + int i; + int target = SCpnt->target; + printk ("scsi_do_cmd (host = %d, channel = %d target = %d, " + "buffer =%p, bufflen = %d, done = %p, timeout = %d, " + "retries = %d)\n" + "command : " , host->host_no, SCpnt->channel, target, buffer, + bufflen, done, timeout, retries); + for (i = 0; i < 10; ++i) + printk ("%02x ", ((unsigned char *) cmnd)[i]); + printk("\n"); + } +#endif + + if (!host) + { + panic ("Invalid or not present host.\n"); + } + + + /* + * We must prevent reentrancy to the lowlevel host driver. This prevents + * it - we enter a loop until the host we want to talk to is not busy. + * Race conditions are prevented, as interrupts are disabled in between the + * time we check for the host being not busy, and the time we mark it busy + * ourselves. + */ + + save_flags(flags); + cli(); + SCpnt->pid = scsi_pid++; + + while (SCSI_BLOCK(host)) { + restore_flags(flags); + SCSI_SLEEP(&host->host_wait, SCSI_BLOCK(host)); + cli(); + } + + if (host->block) host_active = host; + + host->host_busy++; + restore_flags(flags); + + /* + * Our own function scsi_done (which marks the host as not busy, disables + * the timeout counter, etc) will be called by us or by the + * scsi_hosts[host].queuecommand() function needs to also call + * the completion function for the high level driver. + */ + + memcpy ((void *) SCpnt->data_cmnd , (const void *) cmnd, 12); +#if 0 + SCpnt->host = host; + SCpnt->channel = channel; + SCpnt->target = target; + SCpnt->lun = (SCpnt->data_cmnd[1] >> 5); +#endif + SCpnt->reset_chain = NULL; + SCpnt->serial_number = 0; + SCpnt->bufflen = bufflen; + SCpnt->buffer = buffer; + SCpnt->flags = 0; + SCpnt->retries = 0; + SCpnt->allowed = retries; + SCpnt->done = done; + SCpnt->timeout_per_command = timeout; + + memcpy ((void *) SCpnt->cmnd , (const void *) cmnd, 12); + /* Zero the sense buffer. Some host adapters automatically request + * sense on error. 0 is not a valid sense code. + */ + memset ((void *) SCpnt->sense_buffer, 0, sizeof SCpnt->sense_buffer); + SCpnt->request_buffer = buffer; + SCpnt->request_bufflen = bufflen; + SCpnt->old_use_sg = SCpnt->use_sg; + if (SCpnt->cmd_len == 0) + SCpnt->cmd_len = COMMAND_SIZE(SCpnt->cmnd[0]); + SCpnt->old_cmd_len = SCpnt->cmd_len; + + /* Start the timer ticking. */ + + SCpnt->internal_timeout = NORMAL_TIMEOUT; + SCpnt->abort_reason = 0; + internal_cmnd (SCpnt); + +#ifdef DEBUG + printk ("Leaving scsi_do_cmd()\n"); +#endif +} + +static int check_sense (Scsi_Cmnd * SCpnt) +{ + /* If there is no sense information, request it. If we have already + * requested it, there is no point in asking again - the firmware must + * be confused. + */ + if (((SCpnt->sense_buffer[0] & 0x70) >> 4) != 7) { + if(!(SCpnt->flags & ASKED_FOR_SENSE)) + return SUGGEST_SENSE; + else + return SUGGEST_RETRY; + } + + SCpnt->flags &= ~ASKED_FOR_SENSE; + +#ifdef DEBUG_INIT + printk("scsi%d, channel%d : ", SCpnt->host->host_no, SCpnt->channel); + print_sense("", SCpnt); + printk("\n"); +#endif + if (SCpnt->sense_buffer[2] & 0xe0) + return SUGGEST_ABORT; + + switch (SCpnt->sense_buffer[2] & 0xf) + { + case NO_SENSE: + return 0; + case RECOVERED_ERROR: + return SUGGEST_IS_OK; + + case ABORTED_COMMAND: + return SUGGEST_RETRY; + case NOT_READY: + case UNIT_ATTENTION: + /* + * If we are expecting a CC/UA because of a bus reset that we + * performed, treat this just as a retry. Otherwise this is + * information that we should pass up to the upper-level driver + * so that we can deal with it there. + */ + if( SCpnt->device->expecting_cc_ua ) + { + SCpnt->device->expecting_cc_ua = 0; + return SUGGEST_RETRY; + } + return SUGGEST_ABORT; + + /* these three are not supported */ + case COPY_ABORTED: + case VOLUME_OVERFLOW: + case MISCOMPARE: + + case MEDIUM_ERROR: + return SUGGEST_REMAP; + case BLANK_CHECK: + case DATA_PROTECT: + case HARDWARE_ERROR: + case ILLEGAL_REQUEST: + default: + return SUGGEST_ABORT; + } +} + +/* This function is the mid-level interrupt routine, which decides how + * to handle error conditions. Each invocation of this function must + * do one and *only* one of the following: + * + * (1) Call last_cmnd[host].done. This is done for fatal errors and + * normal completion, and indicates that the handling for this + * request is complete. + * (2) Call internal_cmnd to requeue the command. This will result in + * scsi_done being called again when the retry is complete. + * (3) Call scsi_request_sense. This asks the host adapter/drive for + * more information about the error condition. When the information + * is available, scsi_done will be called again. + * (4) Call reset(). This is sort of a last resort, and the idea is that + * this may kick things loose and get the drive working again. reset() + * automatically calls scsi_request_sense, and thus scsi_done will be + * called again once the reset is complete. + * + * If none of the above actions are taken, the drive in question + * will hang. If more than one of the above actions are taken by + * scsi_done, then unpredictable behavior will result. + */ +static void scsi_done (Scsi_Cmnd * SCpnt) +{ + int status=0; + int exit=0; + int checked; + int oldto; + struct Scsi_Host * host = SCpnt->host; + int result = SCpnt->result; + SCpnt->serial_number = 0; + oldto = update_timeout(SCpnt, 0); + +#ifdef DEBUG_TIMEOUT + if(result) printk("Non-zero result in scsi_done %x %d:%d\n", + result, SCpnt->target, SCpnt->lun); +#endif + + /* If we requested an abort, (and we got it) then fix up the return + * status to say why + */ + if(host_byte(result) == DID_ABORT && SCpnt->abort_reason) + SCpnt->result = result = (result & 0xff00ffff) | + (SCpnt->abort_reason << 16); + + +#define FINISHED 0 +#define MAYREDO 1 +#define REDO 3 +#define PENDING 4 + +#ifdef DEBUG + printk("In scsi_done(host = %d, result = %06x)\n", host->host_no, result); +#endif + + if(SCpnt->flags & WAS_SENSE) + { + SCpnt->use_sg = SCpnt->old_use_sg; + SCpnt->cmd_len = SCpnt->old_cmd_len; + } + + switch (host_byte(result)) + { + case DID_OK: + if (status_byte(result) && (SCpnt->flags & WAS_SENSE)) + /* Failed to obtain sense information */ + { + SCpnt->flags &= ~WAS_SENSE; +#if 0 /* This cannot possibly be correct. */ + SCpnt->internal_timeout &= ~SENSE_TIMEOUT; +#endif + + if (!(SCpnt->flags & WAS_RESET)) + { + printk("scsi%d : channel %d target %d lun %d request sense" + " failed, performing reset.\n", + SCpnt->host->host_no, SCpnt->channel, SCpnt->target, + SCpnt->lun); + scsi_reset(SCpnt, SCSI_RESET_SYNCHRONOUS); + return; + } + else + { + exit = (DRIVER_HARD | SUGGEST_ABORT); + status = FINISHED; + } + } + else switch(msg_byte(result)) + { + case COMMAND_COMPLETE: + switch (status_byte(result)) + { + case GOOD: + if (SCpnt->flags & WAS_SENSE) + { +#ifdef DEBUG + printk ("In scsi_done, GOOD status, COMMAND COMPLETE, " + "parsing sense information.\n"); +#endif + SCpnt->flags &= ~WAS_SENSE; +#if 0 /* This cannot possibly be correct. */ + SCpnt->internal_timeout &= ~SENSE_TIMEOUT; +#endif + + switch (checked = check_sense(SCpnt)) + { + case SUGGEST_SENSE: + case 0: +#ifdef DEBUG + printk("NO SENSE. status = REDO\n"); +#endif + update_timeout(SCpnt, oldto); + status = REDO; + break; + case SUGGEST_IS_OK: + break; + case SUGGEST_REMAP: +#ifdef DEBUG + printk("SENSE SUGGEST REMAP - status = FINISHED\n"); +#endif + status = FINISHED; + exit = DRIVER_SENSE | SUGGEST_ABORT; + break; + case SUGGEST_RETRY: +#ifdef DEBUG + printk("SENSE SUGGEST RETRY - status = MAYREDO\n"); +#endif + status = MAYREDO; + exit = DRIVER_SENSE | SUGGEST_RETRY; + break; + case SUGGEST_ABORT: +#ifdef DEBUG + printk("SENSE SUGGEST ABORT - status = FINISHED"); +#endif + status = FINISHED; + exit = DRIVER_SENSE | SUGGEST_ABORT; + break; + default: + printk ("Internal error %s %d \n", __FILE__, + __LINE__); + } + } /* end WAS_SENSE */ + else + { +#ifdef DEBUG + printk("COMMAND COMPLETE message returned, " + "status = FINISHED. \n"); +#endif + exit = DRIVER_OK; + status = FINISHED; + } + break; + + case CHECK_CONDITION: + case COMMAND_TERMINATED: + switch (check_sense(SCpnt)) + { + case 0: + update_timeout(SCpnt, oldto); + status = REDO; + break; + case SUGGEST_REMAP: + status = FINISHED; + exit = DRIVER_SENSE | SUGGEST_ABORT; + break; + case SUGGEST_RETRY: + status = MAYREDO; + exit = DRIVER_SENSE | SUGGEST_RETRY; + break; + case SUGGEST_ABORT: + status = FINISHED; + exit = DRIVER_SENSE | SUGGEST_ABORT; + break; + case SUGGEST_SENSE: + scsi_request_sense (SCpnt); + status = PENDING; + break; + } + break; + + case CONDITION_GOOD: + case INTERMEDIATE_GOOD: + case INTERMEDIATE_C_GOOD: + break; + + case BUSY: + case QUEUE_FULL: + update_timeout(SCpnt, oldto); + status = REDO; + break; + + case RESERVATION_CONFLICT: + printk("scsi%d, channel %d : RESERVATION CONFLICT performing" + " reset.\n", SCpnt->host->host_no, SCpnt->channel); + scsi_reset(SCpnt, SCSI_RESET_SYNCHRONOUS); + return; +#if 0 + exit = DRIVER_SOFT | SUGGEST_ABORT; + status = MAYREDO; + break; +#endif + default: + printk ("Internal error %s %d \n" + "status byte = %d \n", __FILE__, + __LINE__, status_byte(result)); + + } + break; + default: + panic("scsi: unsupported message byte %d received\n", + msg_byte(result)); + } + break; + case DID_TIME_OUT: +#ifdef DEBUG + printk("Host returned DID_TIME_OUT - "); +#endif + + if (SCpnt->flags & WAS_TIMEDOUT) + { +#ifdef DEBUG + printk("Aborting\n"); +#endif + /* + Allow TEST_UNIT_READY and INQUIRY commands to timeout early + without causing resets. All other commands should be retried. + */ + if (SCpnt->cmnd[0] != TEST_UNIT_READY && + SCpnt->cmnd[0] != INQUIRY) + status = MAYREDO; + exit = (DRIVER_TIMEOUT | SUGGEST_ABORT); + } + else + { +#ifdef DEBUG + printk ("Retrying.\n"); +#endif + SCpnt->flags |= WAS_TIMEDOUT; + SCpnt->internal_timeout &= ~IN_ABORT; + status = REDO; + } + break; + case DID_BUS_BUSY: + case DID_PARITY: + status = REDO; + break; + case DID_NO_CONNECT: +#ifdef DEBUG + printk("Couldn't connect.\n"); +#endif + exit = (DRIVER_HARD | SUGGEST_ABORT); + break; + case DID_ERROR: + status = MAYREDO; + exit = (DRIVER_HARD | SUGGEST_ABORT); + break; + case DID_BAD_TARGET: + case DID_ABORT: + exit = (DRIVER_INVALID | SUGGEST_ABORT); + break; + case DID_RESET: + if (SCpnt->flags & IS_RESETTING) + { + SCpnt->flags &= ~IS_RESETTING; + status = REDO; + break; + } + + if(msg_byte(result) == GOOD && + status_byte(result) == CHECK_CONDITION) { + switch (check_sense(SCpnt)) { + case 0: + update_timeout(SCpnt, oldto); + status = REDO; + break; + case SUGGEST_REMAP: + case SUGGEST_RETRY: + status = MAYREDO; + exit = DRIVER_SENSE | SUGGEST_RETRY; + break; + case SUGGEST_ABORT: + status = FINISHED; + exit = DRIVER_SENSE | SUGGEST_ABORT; + break; + case SUGGEST_SENSE: + scsi_request_sense (SCpnt); + status = PENDING; + break; + } + } else { + status=REDO; + exit = SUGGEST_RETRY; + } + break; + default : + exit = (DRIVER_ERROR | SUGGEST_DIE); + } + + switch (status) + { + case FINISHED: + case PENDING: + break; + case MAYREDO: +#ifdef DEBUG + printk("In MAYREDO, allowing %d retries, have %d\n", + SCpnt->allowed, SCpnt->retries); +#endif + if ((++SCpnt->retries) < SCpnt->allowed) + { + if ((SCpnt->retries >= (SCpnt->allowed >> 1)) + && !(SCpnt->host->last_reset > 0 && + jiffies < SCpnt->host->last_reset + MIN_RESET_PERIOD) + && !(SCpnt->flags & WAS_RESET)) + { + printk("scsi%d channel %d : resetting for second half of retries.\n", + SCpnt->host->host_no, SCpnt->channel); + scsi_reset(SCpnt, SCSI_RESET_SYNCHRONOUS); + break; + } + + } + else + { + status = FINISHED; + break; + } + /* fall through to REDO */ + + case REDO: + + if (SCpnt->flags & WAS_SENSE) + scsi_request_sense(SCpnt); + else + { + memcpy ((void *) SCpnt->cmnd, + (void*) SCpnt->data_cmnd, + sizeof(SCpnt->data_cmnd)); + SCpnt->request_buffer = SCpnt->buffer; + SCpnt->request_bufflen = SCpnt->bufflen; + SCpnt->use_sg = SCpnt->old_use_sg; + SCpnt->cmd_len = SCpnt->old_cmd_len; + internal_cmnd (SCpnt); + } + break; + default: + INTERNAL_ERROR; + } + + if (status == FINISHED) { +#ifdef DEBUG + printk("Calling done function - at address %p\n", SCpnt->done); +#endif + host->host_busy--; /* Indicate that we are free */ + + if (host->block && host->host_busy == 0) { + host_active = NULL; + + /* For block devices "wake_up" is done in end_scsi_request */ + if (MAJOR(SCpnt->request.rq_dev) != SCSI_DISK_MAJOR && + MAJOR(SCpnt->request.rq_dev) != SCSI_CDROM_MAJOR) { + struct Scsi_Host * next; + + for (next = host->block; next != host; next = next->block) + wake_up(&next->host_wait); + } + + } + + wake_up(&host->host_wait); + SCpnt->result = result | ((exit & 0xff) << 24); + SCpnt->use_sg = SCpnt->old_use_sg; + SCpnt->cmd_len = SCpnt->old_cmd_len; + SCpnt->done (SCpnt); + } + +#undef FINISHED +#undef REDO +#undef MAYREDO +#undef PENDING +} + +/* + * The scsi_abort function interfaces with the abort() function of the host + * we are aborting, and causes the current command to not complete. The + * caller should deal with any error messages or status returned on the + * next call. + * + * This will not be called reentrantly for a given host. + */ + +/* + * Since we're nice guys and specified that abort() and reset() + * can be non-reentrant. The internal_timeout flags are used for + * this. + */ + + +int scsi_abort (Scsi_Cmnd * SCpnt, int why) +{ + int oldto; + unsigned long flags; + struct Scsi_Host * host = SCpnt->host; + + while(1) + { + save_flags(flags); + cli(); + + /* + * Protect against races here. If the command is done, or we are + * on a different command forget it. + */ + if (SCpnt->serial_number != SCpnt->serial_number_at_timeout) { + restore_flags(flags); + return 0; + } + + if (SCpnt->internal_timeout & IN_ABORT) + { + restore_flags(flags); + while (SCpnt->internal_timeout & IN_ABORT) + barrier(); + } + else + { + SCpnt->internal_timeout |= IN_ABORT; + oldto = update_timeout(SCpnt, ABORT_TIMEOUT); + + if ((SCpnt->flags & IS_RESETTING) && SCpnt->device->soft_reset) { + /* OK, this command must have died when we did the + * reset. The device itself must have lied. + */ + printk("Stale command on %d %d:%d appears to have died when" + " the bus was reset\n", + SCpnt->channel, SCpnt->target, SCpnt->lun); + } + + restore_flags(flags); + if (!host->host_busy) { + SCpnt->internal_timeout &= ~IN_ABORT; + update_timeout(SCpnt, oldto); + return 0; + } + printk("scsi : aborting command due to timeout : pid %lu, scsi%d," + " channel %d, id %d, lun %d ", + SCpnt->pid, SCpnt->host->host_no, (int) SCpnt->channel, + (int) SCpnt->target, (int) SCpnt->lun); + print_command (SCpnt->cmnd); + if (SCpnt->serial_number != SCpnt->serial_number_at_timeout) + return 0; + SCpnt->abort_reason = why; + switch(host->hostt->abort(SCpnt)) { + /* We do not know how to abort. Try waiting another + * time increment and see if this helps. Set the + * WAS_TIMEDOUT flag set so we do not try this twice + */ + case SCSI_ABORT_BUSY: /* Tough call - returning 1 from + * this is too severe + */ + case SCSI_ABORT_SNOOZE: + if(why == DID_TIME_OUT) { + save_flags(flags); + cli(); + SCpnt->internal_timeout &= ~IN_ABORT; + if(SCpnt->flags & WAS_TIMEDOUT) { + restore_flags(flags); + return 1; /* Indicate we cannot handle this. + * We drop down into the reset handler + * and try again + */ + } else { + SCpnt->flags |= WAS_TIMEDOUT; + oldto = SCpnt->timeout_per_command; + update_timeout(SCpnt, oldto); + } + restore_flags(flags); + } + return 0; + case SCSI_ABORT_PENDING: + if(why != DID_TIME_OUT) { + save_flags(flags); + cli(); + update_timeout(SCpnt, oldto); + restore_flags(flags); + } + return 0; + case SCSI_ABORT_SUCCESS: + /* We should have already aborted this one. No + * need to adjust timeout + */ + SCpnt->internal_timeout &= ~IN_ABORT; + return 0; + case SCSI_ABORT_NOT_RUNNING: + SCpnt->internal_timeout &= ~IN_ABORT; + update_timeout(SCpnt, 0); + return 0; + case SCSI_ABORT_ERROR: + default: + SCpnt->internal_timeout &= ~IN_ABORT; + return 1; + } + } + } +} + + +/* Mark a single SCSI Device as having been reset. */ + +static inline void scsi_mark_device_reset(Scsi_Device *Device) +{ + Device->was_reset = 1; + Device->expecting_cc_ua = 1; +} + + +/* Mark all SCSI Devices on a specific Host as having been reset. */ + +void scsi_mark_host_reset(struct Scsi_Host *Host) +{ + Scsi_Cmnd *SCpnt; + for (SCpnt = Host->host_queue; SCpnt; SCpnt = SCpnt->next) + scsi_mark_device_reset(SCpnt->device); +} + + +/* Mark all SCSI Devices on a specific Host Bus as having been reset. */ + +void scsi_mark_bus_reset(struct Scsi_Host *Host, int channel) +{ + Scsi_Cmnd *SCpnt; + for (SCpnt = Host->host_queue; SCpnt; SCpnt = SCpnt->next) + if (SCpnt->channel == channel) + scsi_mark_device_reset(SCpnt->device); +} + + +int scsi_reset (Scsi_Cmnd * SCpnt, unsigned int reset_flags) +{ + int temp; + unsigned long flags; + Scsi_Cmnd * SCpnt1; + struct Scsi_Host * host = SCpnt->host; + + printk("SCSI bus is being reset for host %d channel %d.\n", + host->host_no, SCpnt->channel); + +#if 0 + /* + * First of all, we need to make a recommendation to the low-level + * driver as to whether a BUS_DEVICE_RESET should be performed, + * or whether we should do a full BUS_RESET. There is no simple + * algorithm here - we basically use a series of heuristics + * to determine what we should do. + */ + SCpnt->host->suggest_bus_reset = FALSE; + + /* + * First see if all of the active devices on the bus have + * been jammed up so that we are attempting resets. If so, + * then suggest a bus reset. Forcing a bus reset could + * result in some race conditions, but no more than + * you would usually get with timeouts. We will cross + * that bridge when we come to it. + * + * This is actually a pretty bad idea, since a sequence of + * commands will often timeout together and this will cause a + * Bus Device Reset followed immediately by a SCSI Bus Reset. + * If all of the active devices really are jammed up, the + * Bus Device Reset will quickly timeout and scsi_times_out + * will follow up with a SCSI Bus Reset anyway. + */ + SCpnt1 = host->host_queue; + while(SCpnt1) { + if( SCpnt1->request.rq_status != RQ_INACTIVE + && (SCpnt1->flags & (WAS_RESET | IS_RESETTING)) == 0 ) + break; + SCpnt1 = SCpnt1->next; + } + if( SCpnt1 == NULL ) { + reset_flags |= SCSI_RESET_SUGGEST_BUS_RESET; + } + + /* + * If the code that called us is suggesting a hard reset, then + * definitely request it. This usually occurs because a + * BUS_DEVICE_RESET times out. + * + * Passing reset_flags along takes care of this automatically. + */ + if( reset_flags & SCSI_RESET_SUGGEST_BUS_RESET ) { + SCpnt->host->suggest_bus_reset = TRUE; + } +#endif + + while (1) { + save_flags(flags); + cli(); + + /* + * Protect against races here. If the command is done, or we are + * on a different command forget it. + */ + if (reset_flags & SCSI_RESET_ASYNCHRONOUS) + if (SCpnt->serial_number != SCpnt->serial_number_at_timeout) { + restore_flags(flags); + return 0; + } + + if (SCpnt->internal_timeout & IN_RESET) + { + restore_flags(flags); + while (SCpnt->internal_timeout & IN_RESET) + barrier(); + } + else + { + SCpnt->internal_timeout |= IN_RESET; + update_timeout(SCpnt, RESET_TIMEOUT); + + if (host->host_busy) + { + restore_flags(flags); + SCpnt1 = host->host_queue; + while(SCpnt1) { + if (SCpnt1->request.rq_status != RQ_INACTIVE) { +#if 0 + if (!(SCpnt1->flags & IS_RESETTING) && + !(SCpnt1->internal_timeout & IN_ABORT)) + scsi_abort(SCpnt1, DID_RESET); +#endif + SCpnt1->flags |= (WAS_RESET | IS_RESETTING); + } + SCpnt1 = SCpnt1->next; + } + + host->last_reset = jiffies; + temp = host->hostt->reset(SCpnt, reset_flags); + /* + This test allows the driver to introduce an additional bus + settle time delay by setting last_reset up to 20 seconds in + the future. In the normal case where the driver does not + modify last_reset, it must be assumed that the actual bus + reset occurred immediately prior to the return to this code, + and so last_reset must be updated to the current time, so + that the delay in internal_cmnd will guarantee at least a + MIN_RESET_DELAY bus settle time. + */ + if ((host->last_reset < jiffies) || + (host->last_reset > (jiffies + 20 * HZ))) + host->last_reset = jiffies; + } + else + { + if (!host->block) host->host_busy++; + restore_flags(flags); + host->last_reset = jiffies; + SCpnt->flags |= (WAS_RESET | IS_RESETTING); + temp = host->hostt->reset(SCpnt, reset_flags); + if ((host->last_reset < jiffies) || + (host->last_reset > (jiffies + 20 * HZ))) + host->last_reset = jiffies; + if (!host->block) host->host_busy--; + } + +#ifdef DEBUG + printk("scsi reset function returned %d\n", temp); +#endif + + /* + * Now figure out what we need to do, based upon + * what the low level driver said that it did. + * If the result is SCSI_RESET_SUCCESS, SCSI_RESET_PENDING, + * or SCSI_RESET_WAKEUP, then the low level driver did a + * bus device reset or bus reset, so we should go through + * and mark one or all of the devices on that bus + * as having been reset. + */ + switch(temp & SCSI_RESET_ACTION) { + case SCSI_RESET_SUCCESS: + if (temp & SCSI_RESET_HOST_RESET) + scsi_mark_host_reset(host); + else if (temp & SCSI_RESET_BUS_RESET) + scsi_mark_bus_reset(host, SCpnt->channel); + else scsi_mark_device_reset(SCpnt->device); + save_flags(flags); + cli(); + SCpnt->internal_timeout &= ~(IN_RESET|IN_RESET2|IN_RESET3); + restore_flags(flags); + return 0; + case SCSI_RESET_PENDING: + if (temp & SCSI_RESET_HOST_RESET) + scsi_mark_host_reset(host); + else if (temp & SCSI_RESET_BUS_RESET) + scsi_mark_bus_reset(host, SCpnt->channel); + else scsi_mark_device_reset(SCpnt->device); + case SCSI_RESET_NOT_RUNNING: + return 0; + case SCSI_RESET_PUNT: + SCpnt->internal_timeout &= ~(IN_RESET|IN_RESET2|IN_RESET3); + scsi_request_sense (SCpnt); + return 0; + case SCSI_RESET_WAKEUP: + if (temp & SCSI_RESET_HOST_RESET) + scsi_mark_host_reset(host); + else if (temp & SCSI_RESET_BUS_RESET) + scsi_mark_bus_reset(host, SCpnt->channel); + else scsi_mark_device_reset(SCpnt->device); + SCpnt->internal_timeout &= ~(IN_RESET|IN_RESET2|IN_RESET3); + scsi_request_sense (SCpnt); + /* + * If a bus reset was performed, we + * need to wake up each and every command + * that was active on the bus or if it was a HBA + * reset all active commands on all channels + */ + if( temp & SCSI_RESET_HOST_RESET ) + { + SCpnt1 = host->host_queue; + while(SCpnt1) { + if (SCpnt1->request.rq_status != RQ_INACTIVE + && SCpnt1 != SCpnt) + scsi_request_sense (SCpnt1); + SCpnt1 = SCpnt1->next; + } + } else if( temp & SCSI_RESET_BUS_RESET ) { + SCpnt1 = host->host_queue; + while(SCpnt1) { + if(SCpnt1->request.rq_status != RQ_INACTIVE + && SCpnt1 != SCpnt + && SCpnt1->channel == SCpnt->channel) + scsi_request_sense (SCpnt); + SCpnt1 = SCpnt1->next; + } + } + return 0; + case SCSI_RESET_SNOOZE: + /* In this case, we set the timeout field to 0 + * so that this command does not time out any more, + * and we return 1 so that we get a message on the + * screen. + */ + save_flags(flags); + cli(); + SCpnt->internal_timeout &= ~(IN_RESET|IN_RESET2|IN_RESET3); + update_timeout(SCpnt, 0); + restore_flags(flags); + /* If you snooze, you lose... */ + case SCSI_RESET_ERROR: + default: + return 1; + } + + return temp; + } + } +} + + +static void scsi_main_timeout(void) +{ + /* + * We must not enter update_timeout with a timeout condition still pending. + */ + + int timed_out; + unsigned long flags; + struct Scsi_Host * host; + Scsi_Cmnd * SCpnt = NULL; + + save_flags(flags); + cli(); + + update_timeout(NULL, 0); + + /* + * Find all timers such that they have 0 or negative (shouldn't happen) + * time remaining on them. + */ + timed_out = 0; + for (host = scsi_hostlist; host; host = host->next) { + for (SCpnt = host->host_queue; SCpnt; SCpnt = SCpnt->next) + if (SCpnt->timeout == -1) + { + SCpnt->timeout = 0; + SCpnt->serial_number_at_timeout = SCpnt->serial_number; + ++timed_out; + } + } + if (timed_out > 0) { + for (host = scsi_hostlist; host; host = host->next) { + for (SCpnt = host->host_queue; SCpnt; SCpnt = SCpnt->next) + if (SCpnt->serial_number_at_timeout > 0 && + SCpnt->serial_number_at_timeout == SCpnt->serial_number) + { + restore_flags(flags); + scsi_times_out(SCpnt); + SCpnt->serial_number_at_timeout = 0; + cli(); + } + } + } + restore_flags(flags); +} + +/* + * The strategy is to cause the timer code to call scsi_times_out() + * when the soonest timeout is pending. + * The arguments are used when we are queueing a new command, because + * we do not want to subtract the time used from this time, but when we + * set the timer, we want to take this value into account. + */ + +static int update_timeout(Scsi_Cmnd * SCset, int timeout) +{ + unsigned int least, used; + unsigned int oldto; + unsigned long flags; + struct Scsi_Host * host; + Scsi_Cmnd * SCpnt = NULL; + + save_flags(flags); + cli(); + + oldto = 0; + + /* + * This routine can be a performance bottleneck under high loads, since + * it is called twice per SCSI operation: once when internal_cmnd is + * called, and again when scsi_done completes the command. To limit + * the load this routine can cause, we shortcut processing if no clock + * ticks have occurred since the last time it was called. + */ + + if (jiffies == time_start && timer_table[SCSI_TIMER].expires > 0) { + if(SCset){ + oldto = SCset->timeout; + SCset->timeout = timeout; + if (timeout > 0 && + jiffies + timeout < timer_table[SCSI_TIMER].expires) + timer_table[SCSI_TIMER].expires = jiffies + timeout; + } + restore_flags(flags); + return oldto; + } + + /* + * Figure out how much time has passed since the last time the timeouts + * were updated + */ + used = (time_start) ? (jiffies - time_start) : 0; + + /* + * Find out what is due to timeout soonest, and adjust all timeouts for + * the amount of time that has passed since the last time we called + * update_timeout. + */ + + oldto = 0; + + if(SCset){ + oldto = SCset->timeout - used; + SCset->timeout = timeout; + } + + least = 0xffffffff; + + for(host = scsi_hostlist; host; host = host->next) + for(SCpnt = host->host_queue; SCpnt; SCpnt = SCpnt->next) + if (SCpnt->timeout > 0) { + if (SCpnt != SCset) + SCpnt->timeout -= used; + if(SCpnt->timeout <= 0) SCpnt->timeout = -1; + if(SCpnt->timeout > 0 && SCpnt->timeout < least) + least = SCpnt->timeout; + } + + /* + * If something is due to timeout again, then we will set the next timeout + * interrupt to occur. Otherwise, timeouts are disabled. + */ + + if (least != 0xffffffff) + { + time_start = jiffies; + timer_table[SCSI_TIMER].expires = (time_elapsed = least) + jiffies; + timer_active |= 1 << SCSI_TIMER; + } + else + { + timer_table[SCSI_TIMER].expires = time_start = time_elapsed = 0; + timer_active &= ~(1 << SCSI_TIMER); + } + restore_flags(flags); + return oldto; +} + +#ifdef CONFIG_MODULES +static int scsi_register_host(Scsi_Host_Template *); +static void scsi_unregister_host(Scsi_Host_Template *); +#endif + +void *scsi_malloc(unsigned int len) +{ + unsigned int nbits, mask; + unsigned long flags; + int i, j; + if(len % SECTOR_SIZE != 0 || len > PAGE_SIZE) + return NULL; + + save_flags(flags); + cli(); + nbits = len >> 9; + mask = (1 << nbits) - 1; + + for(i=0;i < dma_sectors / SECTORS_PER_PAGE; i++) + for(j=0; j<=SECTORS_PER_PAGE - nbits; j++){ + if ((dma_malloc_freelist[i] & (mask << j)) == 0){ + dma_malloc_freelist[i] |= (mask << j); + restore_flags(flags); + dma_free_sectors -= nbits; +#ifdef DEBUG + printk("SMalloc: %d %p\n",len, dma_malloc_pages[i] + (j << 9)); +#endif + return (void *) ((unsigned long) dma_malloc_pages[i] + (j << 9)); + } + } + restore_flags(flags); + return NULL; /* Nope. No more */ +} + +int scsi_free(void *obj, unsigned int len) +{ + unsigned int page, sector, nbits, mask; + unsigned long flags; + +#ifdef DEBUG + unsigned long ret = 0; + +#ifdef __mips__ + __asm__ __volatile__ ("move\t%0,$31":"=r"(ret)); +#else + ret = __builtin_return_address(0); +#endif + printk("scsi_free %p %d\n",obj, len); +#endif + + for (page = 0; page < dma_sectors / SECTORS_PER_PAGE; page++) { + unsigned long page_addr = (unsigned long) dma_malloc_pages[page]; + if ((unsigned long) obj >= page_addr && + (unsigned long) obj < page_addr + PAGE_SIZE) + { + sector = (((unsigned long) obj) - page_addr) >> 9; + + nbits = len >> 9; + mask = (1 << nbits) - 1; + + if ((mask << sector) >= (1 << SECTORS_PER_PAGE)) + panic ("scsi_free:Bad memory alignment"); + + save_flags(flags); + cli(); + if((dma_malloc_freelist[page] & + (mask << sector)) != (mask<<sector)){ +#ifdef DEBUG + printk("scsi_free(obj=%p, len=%d) called from %08lx\n", + obj, len, ret); +#endif + panic("scsi_free:Trying to free unused memory"); + } + dma_free_sectors += nbits; + dma_malloc_freelist[page] &= ~(mask << sector); + restore_flags(flags); + return 0; + } + } + panic("scsi_free:Bad offset"); +} + + +int scsi_loadable_module_flag; /* Set after we scan builtin drivers */ + +void * scsi_init_malloc(unsigned int size, int priority) +{ + void * retval; + + /* + * For buffers used by the DMA pool, we assume page aligned + * structures. + */ + if ((size % PAGE_SIZE) == 0) { + int order, a_size; + for (order = 0, a_size = PAGE_SIZE; + a_size < size; order++, a_size <<= 1) + ; + retval = (void *) __get_dma_pages(priority & GFP_LEVEL_MASK, + order); + } else + retval = kmalloc(size, priority); + + if (retval) + memset(retval, 0, size); + return retval; +} + + +void scsi_init_free(char * ptr, unsigned int size) +{ + /* + * We need this special code here because the DMA pool assumes + * page aligned data. Besides, it is wasteful to allocate + * page sized chunks with kmalloc. + */ + if ((size % PAGE_SIZE) == 0) { + int order, a_size; + + for (order = 0, a_size = PAGE_SIZE; + a_size < size; order++, a_size <<= 1) + ; + free_pages((unsigned long)ptr, order); + } else + kfree(ptr); +} + +void scsi_build_commandblocks(Scsi_Device * SDpnt) +{ + struct Scsi_Host *host = SDpnt->host; + int j; + Scsi_Cmnd * SCpnt; + + if (SDpnt->queue_depth == 0) + SDpnt->queue_depth = host->cmd_per_lun; + SDpnt->device_queue = NULL; + + for(j=0;j<SDpnt->queue_depth;j++){ + SCpnt = (Scsi_Cmnd *) + scsi_init_malloc(sizeof(Scsi_Cmnd), + GFP_ATOMIC | + (host->unchecked_isa_dma ? GFP_DMA : 0)); + SCpnt->host = host; + SCpnt->device = SDpnt; + SCpnt->target = SDpnt->id; + SCpnt->lun = SDpnt->lun; + SCpnt->channel = SDpnt->channel; + SCpnt->request.rq_status = RQ_INACTIVE; + SCpnt->use_sg = 0; + SCpnt->old_use_sg = 0; + SCpnt->old_cmd_len = 0; + SCpnt->timeout = 0; + SCpnt->underflow = 0; + SCpnt->transfersize = 0; + SCpnt->serial_number = 0; + SCpnt->serial_number_at_timeout = 0; + SCpnt->host_scribble = NULL; + if(host->host_queue) + host->host_queue->prev = SCpnt; + SCpnt->next = host->host_queue; + SCpnt->prev = NULL; + host->host_queue = SCpnt; + SCpnt->device_next = SDpnt->device_queue; + SDpnt->device_queue = SCpnt; + } + SDpnt->has_cmdblocks = 1; +} + +/* + * scsi_dev_init() is our initialization routine, which in turn calls host + * initialization, bus scanning, and sd/st initialization routines. + */ + +int scsi_dev_init(void) +{ + Scsi_Device * SDpnt; + struct Scsi_Host * shpnt; + struct Scsi_Device_Template * sdtpnt; +#ifdef FOO_ON_YOU + return; +#endif + + /* Yes we're here... */ +#if CONFIG_PROC_FS + dispatch_scsi_info_ptr = dispatch_scsi_info; +#endif + + /* Init a few things so we can "malloc" memory. */ + scsi_loadable_module_flag = 0; + + timer_table[SCSI_TIMER].fn = scsi_main_timeout; + timer_table[SCSI_TIMER].expires = 0; + +#ifdef CONFIG_MODULES + register_symtab(&scsi_symbol_table); +#endif + + /* Register the /proc/scsi/scsi entry */ +#if CONFIG_PROC_FS + proc_scsi_register(0, &proc_scsi_scsi); +#endif + + /* initialize all hosts */ + scsi_init(); + + scsi_devices = (Scsi_Device *) NULL; + + for (shpnt = scsi_hostlist; shpnt; shpnt = shpnt->next) { + scan_scsis(shpnt,0,0,0,0); /* scan for scsi devices */ + if (shpnt->select_queue_depths != NULL) + (shpnt->select_queue_depths)(shpnt, scsi_devices); + } + + printk("scsi : detected "); + for (sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if (sdtpnt->dev_noticed && sdtpnt->name) + printk("%d SCSI %s%s ", sdtpnt->dev_noticed, sdtpnt->name, + (sdtpnt->dev_noticed != 1) ? "s" : ""); + printk("total.\n"); + + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->init && sdtpnt->dev_noticed) (*sdtpnt->init)(); + + for (SDpnt=scsi_devices; SDpnt; SDpnt = SDpnt->next) { + SDpnt->scsi_request_fn = NULL; + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->attach) (*sdtpnt->attach)(SDpnt); + if(SDpnt->attached) scsi_build_commandblocks(SDpnt); + } + + + /* + * This should build the DMA pool. + */ + resize_dma_pool(); + + /* + * OK, now we finish the initialization by doing spin-up, read + * capacity, etc, etc + */ + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->finish && sdtpnt->nr_dev) + (*sdtpnt->finish)(); + + scsi_loadable_module_flag = 1; + + return 0; +} + +static void print_inquiry(unsigned char *data) +{ + int i; + + printk(" Vendor: "); + for (i = 8; i < 16; i++) + { + if (data[i] >= 0x20 && i < data[4] + 5) + printk("%c", data[i]); + else + printk(" "); + } + + printk(" Model: "); + for (i = 16; i < 32; i++) + { + if (data[i] >= 0x20 && i < data[4] + 5) + printk("%c", data[i]); + else + printk(" "); + } + + printk(" Rev: "); + for (i = 32; i < 36; i++) + { + if (data[i] >= 0x20 && i < data[4] + 5) + printk("%c", data[i]); + else + printk(" "); + } + + printk("\n"); + + i = data[0] & 0x1f; + + printk(" Type: %s ", + i < MAX_SCSI_DEVICE_CODE ? scsi_device_types[i] : "Unknown " ); + printk(" ANSI SCSI revision: %02x", data[2] & 0x07); + if ((data[2] & 0x07) == 1 && (data[3] & 0x0f) == 1) + printk(" CCS\n"); + else + printk("\n"); +} + + +#ifdef CONFIG_PROC_FS +int scsi_proc_info(char *buffer, char **start, off_t offset, int length, + int hostno, int inout) +{ + Scsi_Cmnd *SCpnt; + struct Scsi_Device_Template *SDTpnt; + Scsi_Device *scd, *scd_h = NULL; + struct Scsi_Host *HBA_ptr; + char *p; + int host, channel, id, lun; + int size, len = 0; + off_t begin = 0; + off_t pos = 0; + + scd = scsi_devices; + HBA_ptr = scsi_hostlist; + + if(inout == 0) { + size = sprintf(buffer+len,"Attached devices: %s\n", (scd)?"":"none"); + len += size; + pos = begin + len; + while (HBA_ptr) { +#if 0 + size += sprintf(buffer+len,"scsi%2d: %s\n", (int) HBA_ptr->host_no, + HBA_ptr->hostt->procname); + len += size; + pos = begin + len; +#endif + scd = scsi_devices; + while (scd) { + if (scd->host == HBA_ptr) { + proc_print_scsidevice(scd, buffer, &size, len); + len += size; + pos = begin + len; + + if (pos < offset) { + len = 0; + begin = pos; + } + if (pos > offset + length) + goto stop_output; + } + scd = scd->next; + } + HBA_ptr = HBA_ptr->next; + } + + stop_output: + *start=buffer+(offset-begin); /* Start of wanted data */ + len-=(offset-begin); /* Start slop */ + if(len>length) + len = length; /* Ending slop */ + return (len); + } + + if(!buffer || length < 25 || strncmp("scsi", buffer, 4)) + return(-EINVAL); + + /* + * Usage: echo "scsi add-single-device 0 1 2 3" >/proc/scsi/scsi + * with "0 1 2 3" replaced by your "Host Channel Id Lun". + * Consider this feature BETA. + * CAUTION: This is not for hotplugging your peripherals. As + * SCSI was not designed for this you could damage your + * hardware ! + * However perhaps it is legal to switch on an + * already connected device. It is perhaps not + * guaranteed this device doesn't corrupt an ongoing data transfer. + */ + if(!strncmp("add-single-device", buffer + 5, 17)) { + p = buffer + 23; + + host = simple_strtoul(p, &p, 0); + channel = simple_strtoul(p+1, &p, 0); + id = simple_strtoul(p+1, &p, 0); + lun = simple_strtoul(p+1, &p, 0); + + printk("scsi singledevice %d %d %d %d\n", host, channel, + id, lun); + + while(scd && (scd->host->host_no != host + || scd->channel != channel + || scd->id != id + || scd->lun != lun)) { + scd = scd->next; + } + if(scd) + return(-ENOSYS); /* We do not yet support unplugging */ + while(HBA_ptr && HBA_ptr->host_no != host) + HBA_ptr = HBA_ptr->next; + + if(!HBA_ptr) + return(-ENXIO); + + scan_scsis (HBA_ptr, 1, channel, id, lun); + return(length); + + } + + /* + * Usage: echo "scsi remove-single-device 0 1 2 3" >/proc/scsi/scsi + * with "0 1 2 3" replaced by your "Host Channel Id Lun". + * + * Consider this feature pre-BETA. + * + * CAUTION: This is not for hotplugging your peripherals. As + * SCSI was not designed for this you could damage your + * hardware and thoroughly confuse the SCSI subsystem. + * + */ + else if(!strncmp("remove-single-device", buffer + 5, 20)) { + p = buffer + 26; + + host = simple_strtoul(p, &p, 0); + channel = simple_strtoul(p+1, &p, 0); + id = simple_strtoul(p+1, &p, 0); + lun = simple_strtoul(p+1, &p, 0); + + while(scd != NULL) { + if(scd->host->host_no == host + && scd->channel == channel + && scd->id == id + && scd->lun == lun){ + break; + } + scd_h = scd; + scd = scd->next; + } + + if(scd == NULL) + return(-ENODEV); /* there is no such device attached */ + + if(scd->access_count) + return(-EBUSY); + + SDTpnt = scsi_devicelist; + while(SDTpnt != NULL) { + if(SDTpnt->detach) (*SDTpnt->detach)(scd); + SDTpnt = SDTpnt->next; + } + + if(scd->attached == 0) { + /* + * Nobody is using this device any more. + * Free all of the command structures. + */ + for(SCpnt=scd->host->host_queue; SCpnt; SCpnt = SCpnt->next){ + if(SCpnt->device == scd) { + if(SCpnt->prev != NULL) + SCpnt->prev->next = SCpnt->next; + if(SCpnt->next != NULL) + SCpnt->next->prev = SCpnt->prev; + if(SCpnt == scd->host->host_queue) + scd->host->host_queue = SCpnt->next; + scsi_init_free((char *) SCpnt, sizeof(*SCpnt)); + } + } + /* Now we can remove the device structure */ + if(scd_h != NULL) { + scd_h->next = scd->next; + } else if (scsi_devices == scd) { + /* We had a hit on the first entry of the device list */ + scsi_devices = scd->next; + } + scsi_init_free((char *) scd, sizeof(Scsi_Device)); + } else { + return(-EBUSY); + } + return(0); + } + return(-EINVAL); +} +#endif + +/* + * Go through the device list and recompute the most appropriate size + * for the dma pool. Then grab more memory (as required). + */ +static void resize_dma_pool(void) +{ + int i; + unsigned long size; + struct Scsi_Host * shpnt; + struct Scsi_Host * host = NULL; + Scsi_Device * SDpnt; + unsigned long flags; + FreeSectorBitmap * new_dma_malloc_freelist = NULL; + unsigned int new_dma_sectors = 0; + unsigned int new_need_isa_buffer = 0; + unsigned char ** new_dma_malloc_pages = NULL; + + if( !scsi_devices ) + { + /* + * Free up the DMA pool. + */ + if( dma_free_sectors != dma_sectors ) + panic("SCSI DMA pool memory leak %d %d\n",dma_free_sectors,dma_sectors); + + for(i=0; i < dma_sectors / SECTORS_PER_PAGE; i++) + scsi_init_free(dma_malloc_pages[i], PAGE_SIZE); + if (dma_malloc_pages) + scsi_init_free((char *) dma_malloc_pages, + (dma_sectors / SECTORS_PER_PAGE)*sizeof(*dma_malloc_pages)); + dma_malloc_pages = NULL; + if (dma_malloc_freelist) + scsi_init_free((char *) dma_malloc_freelist, + (dma_sectors / SECTORS_PER_PAGE)*sizeof(*dma_malloc_freelist)); + dma_malloc_freelist = NULL; + dma_sectors = 0; + dma_free_sectors = 0; + return; + } + /* Next, check to see if we need to extend the DMA buffer pool */ + + new_dma_sectors = 2*SECTORS_PER_PAGE; /* Base value we use */ + + if (high_memory-1 > ISA_DMA_THRESHOLD) + scsi_need_isa_bounce_buffers = 1; + else + scsi_need_isa_bounce_buffers = 0; + + if (scsi_devicelist) + for(shpnt=scsi_hostlist; shpnt; shpnt = shpnt->next) + new_dma_sectors += SECTORS_PER_PAGE; /* Increment for each host */ + + for (SDpnt=scsi_devices; SDpnt; SDpnt = SDpnt->next) { + host = SDpnt->host; + + /* + * sd and sr drivers allocate scatterlists. + * sr drivers may allocate for each command 1x2048 or 2x1024 extra + * buffers for 2k sector size and 1k fs. + * sg driver allocates buffers < 4k. + * st driver does not need buffers from the dma pool. + * estimate 4k buffer/command for devices of unknown type (should panic). + */ + if (SDpnt->type == TYPE_WORM || SDpnt->type == TYPE_ROM || + SDpnt->type == TYPE_DISK || SDpnt->type == TYPE_MOD) { + new_dma_sectors += ((host->sg_tablesize * + sizeof(struct scatterlist) + 511) >> 9) * + SDpnt->queue_depth; + if (SDpnt->type == TYPE_WORM || SDpnt->type == TYPE_ROM) + new_dma_sectors += (2048 >> 9) * SDpnt->queue_depth; + } + else if (SDpnt->type == TYPE_SCANNER || + SDpnt->type == TYPE_PROCESSOR || + SDpnt->type == TYPE_MEDIUM_CHANGER) { + new_dma_sectors += (4096 >> 9) * SDpnt->queue_depth; + } + else { + if (SDpnt->type != TYPE_TAPE) { + printk("resize_dma_pool: unknown device type %d\n", SDpnt->type); + new_dma_sectors += (4096 >> 9) * SDpnt->queue_depth; + } + } + + if(host->unchecked_isa_dma && + scsi_need_isa_bounce_buffers && + SDpnt->type != TYPE_TAPE) { + new_dma_sectors += (PAGE_SIZE >> 9) * host->sg_tablesize * + SDpnt->queue_depth; + new_need_isa_buffer++; + } + } + +#ifdef DEBUG_INIT + printk("resize_dma_pool: needed dma sectors = %d\n", new_dma_sectors); +#endif + + /* limit DMA memory to 32MB: */ + new_dma_sectors = (new_dma_sectors + 15) & 0xfff0; + + /* + * We never shrink the buffers - this leads to + * race conditions that I would rather not even think + * about right now. + */ + if( new_dma_sectors < dma_sectors ) + new_dma_sectors = dma_sectors; + + if (new_dma_sectors) + { + size = (new_dma_sectors / SECTORS_PER_PAGE)*sizeof(FreeSectorBitmap); + new_dma_malloc_freelist = (FreeSectorBitmap *) scsi_init_malloc(size, GFP_ATOMIC); + memset(new_dma_malloc_freelist, 0, size); + + size = (new_dma_sectors / SECTORS_PER_PAGE)*sizeof(*new_dma_malloc_pages); + new_dma_malloc_pages = (unsigned char **) scsi_init_malloc(size, GFP_ATOMIC); + memset(new_dma_malloc_pages, 0, size); + } + + /* + * If we need more buffers, expand the list. + */ + if( new_dma_sectors > dma_sectors ) { + for(i=dma_sectors / SECTORS_PER_PAGE; i< new_dma_sectors / SECTORS_PER_PAGE; i++) + new_dma_malloc_pages[i] = (unsigned char *) + scsi_init_malloc(PAGE_SIZE, GFP_ATOMIC | GFP_DMA); + } + + /* When we dick with the actual DMA list, we need to + * protect things + */ + save_flags(flags); + cli(); + if (dma_malloc_freelist) + { + size = (dma_sectors / SECTORS_PER_PAGE)*sizeof(FreeSectorBitmap); + memcpy(new_dma_malloc_freelist, dma_malloc_freelist, size); + scsi_init_free((char *) dma_malloc_freelist, size); + } + dma_malloc_freelist = new_dma_malloc_freelist; + + if (dma_malloc_pages) + { + size = (dma_sectors / SECTORS_PER_PAGE)*sizeof(*dma_malloc_pages); + memcpy(new_dma_malloc_pages, dma_malloc_pages, size); + scsi_init_free((char *) dma_malloc_pages, size); + } + + dma_free_sectors += new_dma_sectors - dma_sectors; + dma_malloc_pages = new_dma_malloc_pages; + dma_sectors = new_dma_sectors; + need_isa_buffer = new_need_isa_buffer; + restore_flags(flags); + +#ifdef DEBUG_INIT + printk("resize_dma_pool: dma free sectors = %d\n", dma_free_sectors); + printk("resize_dma_pool: dma sectors = %d\n", dma_sectors); + printk("resize_dma_pool: need isa buffers = %d\n", need_isa_buffer); +#endif +} + +#ifdef CONFIG_MODULES /* a big #ifdef block... */ + +/* + * This entry point should be called by a loadable module if it is trying + * add a low level scsi driver to the system. + */ +static int scsi_register_host(Scsi_Host_Template * tpnt) +{ + int pcount; + struct Scsi_Host * shpnt; + Scsi_Device * SDpnt; + struct Scsi_Device_Template * sdtpnt; + const char * name; + + if (tpnt->next || !tpnt->detect) return 1;/* Must be already loaded, or + * no detect routine available + */ + pcount = next_scsi_host; + if ((tpnt->present = tpnt->detect(tpnt))) + { + if(pcount == next_scsi_host) { + if(tpnt->present > 1) { + printk("Failure to register low-level scsi driver"); + scsi_unregister_host(tpnt); + return 1; + } + /* The low-level driver failed to register a driver. We + * can do this now. + */ + scsi_register(tpnt,0); + } + tpnt->next = scsi_hosts; /* Add to the linked list */ + scsi_hosts = tpnt; + + /* Add the new driver to /proc/scsi */ +#if CONFIG_PROC_FS + build_proc_dir_entries(tpnt); +#endif + + for(shpnt=scsi_hostlist; shpnt; shpnt = shpnt->next) + if(shpnt->hostt == tpnt) + { + if(tpnt->info) + name = tpnt->info(shpnt); + else + name = tpnt->name; + printk ("scsi%d : %s\n", /* And print a little message */ + shpnt->host_no, name); + } + + printk ("scsi : %d host%s.\n", next_scsi_host, + (next_scsi_host == 1) ? "" : "s"); + + scsi_make_blocked_list(); + + /* The next step is to call scan_scsis here. This generates the + * Scsi_Devices entries + */ + + for(shpnt=scsi_hostlist; shpnt; shpnt = shpnt->next) + if(shpnt->hostt == tpnt) { + scan_scsis(shpnt,0,0,0,0); + if (shpnt->select_queue_depths != NULL) + (shpnt->select_queue_depths)(shpnt, scsi_devices); + } + + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->init && sdtpnt->dev_noticed) (*sdtpnt->init)(); + + /* Next we create the Scsi_Cmnd structures for this host */ + + for(SDpnt = scsi_devices; SDpnt; SDpnt = SDpnt->next) + if(SDpnt->host->hostt == tpnt) + { + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->attach) (*sdtpnt->attach)(SDpnt); + if(SDpnt->attached) scsi_build_commandblocks(SDpnt); + } + + /* + * Now that we have all of the devices, resize the DMA pool, + * as required. */ + resize_dma_pool(); + + + /* This does any final handling that is required. */ + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->finish && sdtpnt->nr_dev) + (*sdtpnt->finish)(); + } + +#if defined(USE_STATIC_SCSI_MEMORY) + printk ("SCSI memory: total %ldKb, used %ldKb, free %ldKb.\n", + (scsi_memory_upper_value - scsi_memory_lower_value) / 1024, + (scsi_init_memory_start - scsi_memory_lower_value) / 1024, + (scsi_memory_upper_value - scsi_init_memory_start) / 1024); +#endif + + MOD_INC_USE_COUNT; + return 0; +} + +/* + * Similarly, this entry point should be called by a loadable module if it + * is trying to remove a low level scsi driver from the system. + */ +static void scsi_unregister_host(Scsi_Host_Template * tpnt) +{ + Scsi_Host_Template * SHT, *SHTp; + Scsi_Device *sdpnt, * sdppnt, * sdpnt1; + Scsi_Cmnd * SCpnt; + unsigned long flags; + struct Scsi_Device_Template * sdtpnt; + struct Scsi_Host * shpnt, *sh1; + int pcount; + + /* First verify that this host adapter is completely free with no pending + * commands */ + + for(sdpnt = scsi_devices; sdpnt; sdpnt = sdpnt->next) + if(sdpnt->host->hostt == tpnt && sdpnt->host->hostt->usage_count + && *sdpnt->host->hostt->usage_count) return; + + for(shpnt = scsi_hostlist; shpnt; shpnt = shpnt->next) + { + if (shpnt->hostt != tpnt) continue; + for(SCpnt = shpnt->host_queue; SCpnt; SCpnt = SCpnt->next) + { + save_flags(flags); + cli(); + if(SCpnt->request.rq_status != RQ_INACTIVE) { + restore_flags(flags); + for(SCpnt = shpnt->host_queue; SCpnt; SCpnt = SCpnt->next) + if(SCpnt->request.rq_status == RQ_SCSI_DISCONNECTING) + SCpnt->request.rq_status = RQ_INACTIVE; + printk("Device busy???\n"); + return; + } + SCpnt->request.rq_status = RQ_SCSI_DISCONNECTING; /* Mark as busy */ + restore_flags(flags); + } + } + /* Next we detach the high level drivers from the Scsi_Device structures */ + + for(sdpnt = scsi_devices; sdpnt; sdpnt = sdpnt->next) + if(sdpnt->host->hostt == tpnt) + { + for(sdtpnt = scsi_devicelist; sdtpnt; sdtpnt = sdtpnt->next) + if(sdtpnt->detach) (*sdtpnt->detach)(sdpnt); + /* If something still attached, punt */ + if (sdpnt->attached) { + printk("Attached usage count = %d\n", sdpnt->attached); + return; + } + } + + /* Next we free up the Scsi_Cmnd structures for this host */ + + for(sdpnt = scsi_devices; sdpnt; sdpnt = sdpnt->next) + if(sdpnt->host->hostt == tpnt) + while (sdpnt->host->host_queue) { + SCpnt = sdpnt->host->host_queue->next; + scsi_init_free((char *) sdpnt->host->host_queue, sizeof(Scsi_Cmnd)); + sdpnt->host->host_queue = SCpnt; + if (SCpnt) SCpnt->prev = NULL; + sdpnt->has_cmdblocks = 0; + } + + /* Next free up the Scsi_Device structures for this host */ + + sdppnt = NULL; + for(sdpnt = scsi_devices; sdpnt; sdpnt = sdpnt1) + { + sdpnt1 = sdpnt->next; + if (sdpnt->host->hostt == tpnt) { + if (sdppnt) + sdppnt->next = sdpnt->next; + else + scsi_devices = sdpnt->next; + scsi_init_free((char *) sdpnt, sizeof (Scsi_Device)); + } else + sdppnt = sdpnt; + } + + /* Next we go through and remove the instances of the individual hosts + * that were detected */ + + shpnt = scsi_hostlist; + while(shpnt) { + sh1 = shpnt->next; + if(shpnt->hostt == tpnt) { + if(shpnt->loaded_as_module) { + pcount = next_scsi_host; + /* Remove the /proc/scsi directory entry */ +#if CONFIG_PROC_FS + proc_scsi_unregister(tpnt->proc_dir, + shpnt->host_no + PROC_SCSI_FILE); +#endif + if(tpnt->release) + (*tpnt->release)(shpnt); + else { + /* This is the default case for the release function. + * It should do the right thing for most correctly + * written host adapters. + */ + if (shpnt->irq) free_irq(shpnt->irq, NULL); + if (shpnt->dma_channel != 0xff) free_dma(shpnt->dma_channel); + if (shpnt->io_port && shpnt->n_io_port) + release_region(shpnt->io_port, shpnt->n_io_port); + } + if(pcount == next_scsi_host) scsi_unregister(shpnt); + tpnt->present--; + } + } + shpnt = sh1; + } + + /* + * If there are absolutely no more hosts left, it is safe + * to completely nuke the DMA pool. The resize operation will + * do the right thing and free everything. + */ + if( !scsi_devices ) + resize_dma_pool(); + + printk ("scsi : %d host%s.\n", next_scsi_host, + (next_scsi_host == 1) ? "" : "s"); + +#if defined(USE_STATIC_SCSI_MEMORY) + printk ("SCSI memory: total %ldKb, used %ldKb, free %ldKb.\n", + (scsi_memory_upper_value - scsi_memory_lower_value) / 1024, + (scsi_init_memory_start - scsi_memory_lower_value) / 1024, + (scsi_memory_upper_value - scsi_init_memory_start) / 1024); +#endif + + scsi_make_blocked_list(); + + /* There were some hosts that were loaded at boot time, so we cannot + do any more than this */ + if (tpnt->present) return; + + /* OK, this is the very last step. Remove this host adapter from the + linked list. */ + for(SHTp=NULL, SHT=scsi_hosts; SHT; SHTp=SHT, SHT=SHT->next) + if(SHT == tpnt) { + if(SHTp) + SHTp->next = SHT->next; + else + scsi_hosts = SHT->next; + SHT->next = NULL; + break; + } + + /* Rebuild the /proc/scsi directory entries */ +#if CONFIG_PROC_FS + proc_scsi_unregister(tpnt->proc_dir, tpnt->proc_dir->low_ino); +#endif + MOD_DEC_USE_COUNT; +} + +/* + * This entry point should be called by a loadable module if it is trying + * add a high level scsi driver to the system. + */ +static int scsi_register_device_module(struct Scsi_Device_Template * tpnt) +{ + Scsi_Device * SDpnt; + + if (tpnt->next) return 1; + + scsi_register_device(tpnt); + /* + * First scan the devices that we know about, and see if we notice them. + */ + + for(SDpnt = scsi_devices; SDpnt; SDpnt = SDpnt->next) + if(tpnt->detect) SDpnt->attached += (*tpnt->detect)(SDpnt); + + /* + * If any of the devices would match this driver, then perform the + * init function. + */ + if(tpnt->init && tpnt->dev_noticed) + if ((*tpnt->init)()) return 1; + + /* + * Now actually connect the devices to the new driver. + */ + for(SDpnt = scsi_devices; SDpnt; SDpnt = SDpnt->next) + { + if(tpnt->attach) (*tpnt->attach)(SDpnt); + /* + * If this driver attached to the device, and we no longer + * have anything attached, release the scsi command blocks. + */ + if(SDpnt->attached && SDpnt->has_cmdblocks == 0) + scsi_build_commandblocks(SDpnt); + } + + /* + * This does any final handling that is required. + */ + if(tpnt->finish && tpnt->nr_dev) (*tpnt->finish)(); + MOD_INC_USE_COUNT; + return 0; +} + +static int scsi_unregister_device(struct Scsi_Device_Template * tpnt) +{ + Scsi_Device * SDpnt; + Scsi_Cmnd * SCpnt; + struct Scsi_Device_Template * spnt; + struct Scsi_Device_Template * prev_spnt; + + /* + * If we are busy, this is not going to fly. + */ + if( *tpnt->usage_count != 0) return 0; + /* + * Next, detach the devices from the driver. + */ + + for(SDpnt = scsi_devices; SDpnt; SDpnt = SDpnt->next) + { + if(tpnt->detach) (*tpnt->detach)(SDpnt); + if(SDpnt->attached == 0) + { + /* + * Nobody is using this device any more. Free all of the + * command structures. + */ + for(SCpnt = SDpnt->host->host_queue; SCpnt; SCpnt = SCpnt->next) + { + if(SCpnt->device == SDpnt) + { + if(SCpnt->prev != NULL) + SCpnt->prev->next = SCpnt->next; + if(SCpnt->next != NULL) + SCpnt->next->prev = SCpnt->prev; + if(SCpnt == SDpnt->host->host_queue) + SDpnt->host->host_queue = SCpnt->next; + scsi_init_free((char *) SCpnt, sizeof(*SCpnt)); + } + } + SDpnt->has_cmdblocks = 0; + } + } + /* + * Extract the template from the linked list. + */ + spnt = scsi_devicelist; + prev_spnt = NULL; + while(spnt != tpnt) + { + prev_spnt = spnt; + spnt = spnt->next; + } + if(prev_spnt == NULL) + scsi_devicelist = tpnt->next; + else + prev_spnt->next = spnt->next; + + MOD_DEC_USE_COUNT; + /* + * Final cleanup for the driver is done in the driver sources in the + * cleanup function. + */ + return 0; +} + + +int scsi_register_module(int module_type, void * ptr) +{ + switch(module_type){ + case MODULE_SCSI_HA: + return scsi_register_host((Scsi_Host_Template *) ptr); + + /* Load upper level device handler of some kind */ + case MODULE_SCSI_DEV: +#ifdef CONFIG_KERNELD + if (scsi_hosts == NULL) + request_module("scsi_hostadapter"); +#endif + return scsi_register_device_module((struct Scsi_Device_Template *) ptr); + /* The rest of these are not yet implemented */ + + /* Load constants.o */ + case MODULE_SCSI_CONST: + + /* Load specialized ioctl handler for some device. Intended for + * cdroms that have non-SCSI2 audio command sets. */ + case MODULE_SCSI_IOCTL: + + default: + return 1; + } +} + +void scsi_unregister_module(int module_type, void * ptr) +{ + switch(module_type) { + case MODULE_SCSI_HA: + scsi_unregister_host((Scsi_Host_Template *) ptr); + break; + case MODULE_SCSI_DEV: + scsi_unregister_device((struct Scsi_Device_Template *) ptr); + break; + /* The rest of these are not yet implemented. */ + case MODULE_SCSI_CONST: + case MODULE_SCSI_IOCTL: + break; + default: + } + return; +} + +#endif /* CONFIG_MODULES */ + +#ifdef DEBUG_TIMEOUT +static void +scsi_dump_status(void) +{ + int i; + struct Scsi_Host * shpnt; + Scsi_Cmnd * SCpnt; + printk("Dump of scsi parameters:\n"); + i = 0; + for(shpnt = scsi_hostlist; shpnt; shpnt = shpnt->next) + for(SCpnt=shpnt->host_queue; SCpnt; SCpnt = SCpnt->next) + { + /* (0) 0:0:0:0 (802 123434 8 8 0) (3 3 2) (%d %d %d) %d %x */ + printk("(%d) %d:%d:%d:%d (%s %ld %ld %ld %d) (%d %d %x) (%d %d %d) %x %x %x\n", + i++, SCpnt->host->host_no, + SCpnt->channel, + SCpnt->target, + SCpnt->lun, + kdevname(SCpnt->request.rq_dev), + SCpnt->request.sector, + SCpnt->request.nr_sectors, + SCpnt->request.current_nr_sectors, + SCpnt->use_sg, + SCpnt->retries, + SCpnt->allowed, + SCpnt->flags, + SCpnt->timeout_per_command, + SCpnt->timeout, + SCpnt->internal_timeout, + SCpnt->cmnd[0], + SCpnt->sense_buffer[2], + SCpnt->result); + } + printk("wait_for_request = %p\n", wait_for_request); + /* Now dump the request lists for each block device */ + printk("Dump of pending block device requests\n"); + for(i=0; i<MAX_BLKDEV; i++) + if(blk_dev[i].current_request) + { + struct request * req; + printk("%d: ", i); + req = blk_dev[i].current_request; + while(req) { + printk("(%s %d %ld %ld %ld) ", + kdevname(req->rq_dev), + req->cmd, + req->sector, + req->nr_sectors, + req->current_nr_sectors); + req = req->next; + } + printk("\n"); + } +} +#endif + +#ifdef MODULE + +int init_module(void) { + unsigned long size; + + /* + * This makes /proc/scsi visible. + */ +#if CONFIG_PROC_FS + dispatch_scsi_info_ptr = dispatch_scsi_info; +#endif + + timer_table[SCSI_TIMER].fn = scsi_main_timeout; + timer_table[SCSI_TIMER].expires = 0; + register_symtab(&scsi_symbol_table); + scsi_loadable_module_flag = 1; + + /* Register the /proc/scsi/scsi entry */ +#if CONFIG_PROC_FS + proc_scsi_register(0, &proc_scsi_scsi); +#endif + + + dma_sectors = PAGE_SIZE / SECTOR_SIZE; + dma_free_sectors= dma_sectors; + /* + * Set up a minimal DMA buffer list - this will be used during scan_scsis + * in some cases. + */ + + /* One bit per sector to indicate free/busy */ + size = (dma_sectors / SECTORS_PER_PAGE)*sizeof(FreeSectorBitmap); + dma_malloc_freelist = (unsigned char *) scsi_init_malloc(size, GFP_ATOMIC); + memset(dma_malloc_freelist, 0, size); + + /* One pointer per page for the page list */ + dma_malloc_pages = (unsigned char **) + scsi_init_malloc((dma_sectors / SECTORS_PER_PAGE)*sizeof(*dma_malloc_pages), GFP_ATOMIC); + dma_malloc_pages[0] = (unsigned char *) + scsi_init_malloc(PAGE_SIZE, GFP_ATOMIC | GFP_DMA); + return 0; +} + +void cleanup_module( void) +{ +#if CONFIG_PROC_FS + proc_scsi_unregister(0, PROC_SCSI_SCSI); + + /* No, we're not here anymore. Don't show the /proc/scsi files. */ + dispatch_scsi_info_ptr = 0L; +#endif + + /* + * Free up the DMA pool. + */ + resize_dma_pool(); + + timer_table[SCSI_TIMER].fn = NULL; + timer_table[SCSI_TIMER].expires = 0; +} +#endif /* MODULE */ + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/scsi.h b/linux/src/drivers/scsi/scsi.h new file mode 100644 index 00000000..c3dfcd45 --- /dev/null +++ b/linux/src/drivers/scsi/scsi.h @@ -0,0 +1,632 @@ +/* + * scsi.h Copyright (C) 1992 Drew Eckhardt + * Copyright (C) 1993, 1994, 1995 Eric Youngdale + * generic SCSI package header file by + * Initial versions: Drew Eckhardt + * Subsequent revisions: Eric Youngdale + * + * <drew@colorado.edu> + * + * Modified by Eric Youngdale eric@aib.com to + * add scatter-gather, multiple outstanding request, and other + * enhancements. + */ + +#ifndef _SCSI_H +#define _SCSI_H + +/* + * Some of the public constants are being moved to this file. + * We include it here so that what came from where is transparent. + */ +#include <scsi/scsi.h> + +#include <linux/random.h> + + +/* + * Some defs, in case these are not defined elsewhere. + */ +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + + +extern void scsi_make_blocked_list(void); +extern volatile int in_scan_scsis; +extern const unsigned char scsi_command_size[8]; +#define COMMAND_SIZE(opcode) scsi_command_size[((opcode) >> 5) & 7] +#define IDENTIFY_BASE 0x80 +#define IDENTIFY(can_disconnect, lun) (IDENTIFY_BASE |\ + ((can_disconnect) ? 0x40 : 0) |\ + ((lun) & 0x07)) +#define MAX_SCSI_DEVICE_CODE 10 +extern const char *const scsi_device_types[MAX_SCSI_DEVICE_CODE]; + + + +/* + * the return of the status word will be in the following format : + * The low byte is the status returned by the SCSI command, + * with vendor specific bits masked. + * + * The next byte is the message which followed the SCSI status. + * This allows a stos to be used, since the Intel is a little + * endian machine. + * + * The final byte is a host return code, which is one of the following. + * + * IE + * lsb msb + * status msg host code + * + * Our errors returned by OUR driver, NOT SCSI message. Or'd with + * SCSI message passed back to driver <IF any>. + */ + + +#define DID_OK 0x00 /* NO error */ +#define DID_NO_CONNECT 0x01 /* Couldn't connect before timeout period */ +#define DID_BUS_BUSY 0x02 /* BUS stayed busy through time out period */ +#define DID_TIME_OUT 0x03 /* TIMED OUT for other reason */ +#define DID_BAD_TARGET 0x04 /* BAD target. */ +#define DID_ABORT 0x05 /* Told to abort for some other reason */ +#define DID_PARITY 0x06 /* Parity error */ +#define DID_ERROR 0x07 /* Internal error */ +#define DID_RESET 0x08 /* Reset by somebody. */ +#define DID_BAD_INTR 0x09 /* Got an interrupt we weren't expecting. */ +#define DRIVER_OK 0x00 /* Driver status */ + +/* + * These indicate the error that occurred, and what is available. + */ + +#define DRIVER_BUSY 0x01 +#define DRIVER_SOFT 0x02 +#define DRIVER_MEDIA 0x03 +#define DRIVER_ERROR 0x04 + +#define DRIVER_INVALID 0x05 +#define DRIVER_TIMEOUT 0x06 +#define DRIVER_HARD 0x07 +#define DRIVER_SENSE 0x08 + +#define SUGGEST_RETRY 0x10 +#define SUGGEST_ABORT 0x20 +#define SUGGEST_REMAP 0x30 +#define SUGGEST_DIE 0x40 +#define SUGGEST_SENSE 0x80 +#define SUGGEST_IS_OK 0xff + +#define DRIVER_MASK 0x0f +#define SUGGEST_MASK 0xf0 + +#define MAX_COMMAND_SIZE 12 + +/* + * SCSI command sets + */ + +#define SCSI_UNKNOWN 0 +#define SCSI_1 1 +#define SCSI_1_CCS 2 +#define SCSI_2 3 + +/* + * Every SCSI command starts with a one byte OP-code. + * The next byte's high three bits are the LUN of the + * device. Any multi-byte quantities are stored high byte + * first, and may have a 5 bit MSB in the same byte + * as the LUN. + */ + +/* + * Manufacturers list + */ + +#define SCSI_MAN_UNKNOWN 0 +#define SCSI_MAN_NEC 1 +#define SCSI_MAN_TOSHIBA 2 +#define SCSI_MAN_NEC_OLDCDR 3 +#define SCSI_MAN_SONY 4 +#define SCSI_MAN_PIONEER 5 + +/* + * As the scsi do command functions are intelligent, and may need to + * redo a command, we need to keep track of the last command + * executed on each one. + */ + +#define WAS_RESET 0x01 +#define WAS_TIMEDOUT 0x02 +#define WAS_SENSE 0x04 +#define IS_RESETTING 0x08 +#define IS_ABORTING 0x10 +#define ASKED_FOR_SENSE 0x20 + +/* + * The scsi_device struct contains what we know about each given scsi + * device. + */ + +typedef struct scsi_device { + struct scsi_device * next; /* Used for linked list */ + + unsigned char id, lun, channel; + + unsigned int manufacturer; /* Manufacturer of device, for using + * vendor-specific cmd's */ + int attached; /* # of high level drivers attached to + * this */ + int access_count; /* Count of open channels/mounts */ + struct wait_queue * device_wait;/* Used to wait if device is busy */ + struct Scsi_Host * host; + void (*scsi_request_fn)(void); /* Used to jumpstart things after an + * ioctl */ + struct scsi_cmnd *device_queue; /* queue of SCSI Command structures */ + void *hostdata; /* available to low-level driver */ + char type; + char scsi_level; + char vendor[8], model[16], rev[4]; + unsigned char current_tag; /* current tag */ + unsigned char sync_min_period; /* Not less than this period */ + unsigned char sync_max_offset; /* Not greater than this offset */ + unsigned char queue_depth; /* How deep a queue to use */ + + unsigned writeable:1; + unsigned removable:1; + unsigned random:1; + unsigned has_cmdblocks:1; + unsigned changed:1; /* Data invalid due to media change */ + unsigned busy:1; /* Used to prevent races */ + unsigned lockable:1; /* Able to prevent media removal */ + unsigned borken:1; /* Tell the Seagate driver to be + * painfully slow on this device */ + unsigned tagged_supported:1; /* Supports SCSI-II tagged queuing */ + unsigned tagged_queue:1; /* SCSI-II tagged queuing enabled */ + unsigned disconnect:1; /* can disconnect */ + unsigned soft_reset:1; /* Uses soft reset option */ + unsigned sync:1; /* Negotiate for sync transfers */ + unsigned single_lun:1; /* Indicates we should only allow I/O to + * one of the luns for the device at a + * time. */ + unsigned was_reset:1; /* There was a bus reset on the bus for + * this device */ + unsigned expecting_cc_ua:1; /* Expecting a CHECK_CONDITION/UNIT_ATTN + * because we did a bus reset. */ +} Scsi_Device; + +/* + * Use these to separate status msg and our bytes + */ + +#define status_byte(result) (((result) >> 1) & 0x1f) +#define msg_byte(result) (((result) >> 8) & 0xff) +#define host_byte(result) (((result) >> 16) & 0xff) +#define driver_byte(result) (((result) >> 24) & 0xff) +#define suggestion(result) (driver_byte(result) & SUGGEST_MASK) + +#define sense_class(sense) (((sense) >> 4) & 0x7) +#define sense_error(sense) ((sense) & 0xf) +#define sense_valid(sense) ((sense) & 0x80); + +/* + * These are the SCSI devices available on the system. + */ + +extern Scsi_Device * scsi_devices; + +extern struct hd_struct * sd; + +#if defined(MAJOR_NR) && (MAJOR_NR == SCSI_DISK_MAJOR) +extern struct hd_struct * sd; +#endif + +/* + * Initializes all SCSI devices. This scans all scsi busses. + */ + +extern int scsi_dev_init (void); + +struct scatterlist { + char * address; /* Location data is to be transferred to */ + char * alt_address; /* Location of actual if address is a + * dma indirect buffer. NULL otherwise */ + unsigned int length; +}; + +#ifdef __alpha__ +# define ISA_DMA_THRESHOLD (~0UL) +#else +# define ISA_DMA_THRESHOLD (0x00ffffff) +#endif +#define CONTIGUOUS_BUFFERS(X,Y) ((X->b_data+X->b_size) == Y->b_data) + + +/* + * These are the return codes for the abort and reset functions. The mid-level + * code uses these to decide what to do next. Each of the low level abort + * and reset functions must correctly indicate what it has done. + * The descriptions are written from the point of view of the mid-level code, + * so that the return code is telling the mid-level drivers exactly what + * the low level driver has already done, and what remains to be done. + */ + +/* We did not do anything. + * Wait some more for this command to complete, and if this does not work, + * try something more serious. */ +#define SCSI_ABORT_SNOOZE 0 + +/* This means that we were able to abort the command. We have already + * called the mid-level done function, and do not expect an interrupt that + * will lead to another call to the mid-level done function for this command */ +#define SCSI_ABORT_SUCCESS 1 + +/* We called for an abort of this command, and we should get an interrupt + * when this succeeds. Thus we should not restore the timer for this + * command in the mid-level abort function. */ +#define SCSI_ABORT_PENDING 2 + +/* Unable to abort - command is currently on the bus. Grin and bear it. */ +#define SCSI_ABORT_BUSY 3 + +/* The command is not active in the low level code. Command probably + * finished. */ +#define SCSI_ABORT_NOT_RUNNING 4 + +/* Something went wrong. The low level driver will indicate the correct + * error condition when it calls scsi_done, so the mid-level abort function + * can simply wait until this comes through */ +#define SCSI_ABORT_ERROR 5 + +/* We do not know how to reset the bus, or we do not want to. Bummer. + * Anyway, just wait a little more for the command in question, and hope that + * it eventually finishes. If it never finishes, the SCSI device could + * hang, so use this with caution. */ +#define SCSI_RESET_SNOOZE 0 + +/* We do not know how to reset the bus, or we do not want to. Bummer. + * We have given up on this ever completing. The mid-level code will + * request sense information to decide how to proceed from here. */ +#define SCSI_RESET_PUNT 1 + +/* This means that we were able to reset the bus. We have restarted all of + * the commands that should be restarted, and we should be able to continue + * on normally from here. We do not expect any interrupts that will return + * DID_RESET to any of the other commands in the host_queue, and the mid-level + * code does not need to do anything special to keep the commands alive. + * If a hard reset was performed then all outstanding commands on the + * bus have been restarted. */ +#define SCSI_RESET_SUCCESS 2 + +/* We called for a reset of this bus, and we should get an interrupt + * when this succeeds. Each command should get its own status + * passed up to scsi_done, but this has not happened yet. + * If a hard reset was performed, then we expect an interrupt + * for *each* of the outstanding commands that will have the + * effect of restarting the commands. + */ +#define SCSI_RESET_PENDING 3 + +/* We did a reset, but do not expect an interrupt to signal DID_RESET. + * This tells the upper level code to request the sense info, and this + * should keep the command alive. */ +#define SCSI_RESET_WAKEUP 4 + +/* The command is not active in the low level code. Command probably + finished. */ +#define SCSI_RESET_NOT_RUNNING 5 + +/* Something went wrong, and we do not know how to fix it. */ +#define SCSI_RESET_ERROR 6 + +#define SCSI_RESET_SYNCHRONOUS 0x01 +#define SCSI_RESET_ASYNCHRONOUS 0x02 +#define SCSI_RESET_SUGGEST_BUS_RESET 0x04 +#define SCSI_RESET_SUGGEST_HOST_RESET 0x08 +/* + * This is a bitmask that is ored with one of the above codes. + * It tells the mid-level code that we did a hard reset. + */ +#define SCSI_RESET_BUS_RESET 0x100 +/* + * This is a bitmask that is ored with one of the above codes. + * It tells the mid-level code that we did a host adapter reset. + */ +#define SCSI_RESET_HOST_RESET 0x200 +/* + * Used to mask off bits and to obtain the basic action that was + * performed. + */ +#define SCSI_RESET_ACTION 0xff + +void * scsi_malloc(unsigned int); +int scsi_free(void *, unsigned int); +extern unsigned int dma_free_sectors; /* How much room do we have left */ +extern unsigned int need_isa_buffer; /* True if some devices need indirection + * buffers */ + +/* + * The Scsi_Cmnd structure is used by scsi.c internally, and for communication + * with low level drivers that support multiple outstanding commands. + */ +typedef struct scsi_pointer { + char * ptr; /* data pointer */ + int this_residual; /* left in this buffer */ + struct scatterlist *buffer; /* which buffer */ + int buffers_residual; /* how many buffers left */ + + volatile int Status; + volatile int Message; + volatile int have_data_in; + volatile int sent_command; + volatile int phase; +} Scsi_Pointer; + +typedef struct scsi_cmnd { + struct Scsi_Host * host; + Scsi_Device * device; + unsigned char target, lun, channel; + unsigned char cmd_len; + unsigned char old_cmd_len; + struct scsi_cmnd *next, *prev, *device_next, *reset_chain; + + /* These elements define the operation we are about to perform */ + unsigned char cmnd[12]; + unsigned request_bufflen; /* Actual request size */ + + void * request_buffer; /* Actual requested buffer */ + + /* These elements define the operation we ultimately want to perform */ + unsigned char data_cmnd[12]; + unsigned short old_use_sg; /* We save use_sg here when requesting + * sense info */ + unsigned short use_sg; /* Number of pieces of scatter-gather */ + unsigned short sglist_len; /* size of malloc'd scatter-gather list */ + unsigned short abort_reason;/* If the mid-level code requests an + * abort, this is the reason. */ + unsigned bufflen; /* Size of data buffer */ + void *buffer; /* Data buffer */ + + unsigned underflow; /* Return error if less than this amount is + * transfered */ + + unsigned transfersize; /* How much we are guaranteed to transfer with + * each SCSI transfer (ie, between disconnect / + * reconnects. Probably == sector size */ + + + struct request request; /* A copy of the command we are working on */ + + unsigned char sense_buffer[16]; /* Sense for this command, if needed */ + + /* + A SCSI Command is assigned a nonzero serial_number when internal_cmnd + passes it to the driver's queue command function. The serial_number + is cleared when scsi_done is entered indicating that the command has + been completed. If a timeout occurs, the serial number at the moment + of timeout is copied into serial_number_at_timeout. By subsequently + comparing the serial_number and serial_number_at_timeout fields + during abort or reset processing, we can detect whether the command + has already completed. This also detects cases where the command has + completed and the SCSI Command structure has already being reused + for another command, so that we can avoid incorrectly aborting or + resetting the new command. + */ + + unsigned long serial_number; + unsigned long serial_number_at_timeout; + + int retries; + int allowed; + int timeout_per_command, timeout_total, timeout; + + /* + * We handle the timeout differently if it happens when a reset, + * abort, etc are in process. + */ + unsigned volatile char internal_timeout; + + unsigned flags; + + /* These variables are for the cdrom only. Once we have variable size + * buffers in the buffer cache, they will go away. */ + int this_count; + /* End of special cdrom variables */ + + /* Low-level done function - can be used by low-level driver to point + * to completion function. Not used by mid/upper level code. */ + void (*scsi_done)(struct scsi_cmnd *); + void (*done)(struct scsi_cmnd *); /* Mid-level done function */ + + /* + * The following fields can be written to by the host specific code. + * Everything else should be left alone. + */ + + Scsi_Pointer SCp; /* Scratchpad used by some host adapters */ + + unsigned char * host_scribble; /* The host adapter is allowed to + * call scsi_malloc and get some memory + * and hang it here. The host adapter + * is also expected to call scsi_free + * to release this memory. (The memory + * obtained by scsi_malloc is guaranteed + * to be at an address < 16Mb). */ + + int result; /* Status code from lower level driver */ + + unsigned char tag; /* SCSI-II queued command tag */ + unsigned long pid; /* Process ID, starts at 0 */ +} Scsi_Cmnd; + +/* + * scsi_abort aborts the current command that is executing on host host. + * The error code, if non zero is returned in the host byte, otherwise + * DID_ABORT is returned in the hostbyte. + */ + +extern int scsi_abort (Scsi_Cmnd *, int code); + +extern void scsi_do_cmd (Scsi_Cmnd *, const void *cmnd , + void *buffer, unsigned bufflen, + void (*done)(struct scsi_cmnd *), + int timeout, int retries); + + +extern Scsi_Cmnd * allocate_device(struct request **, Scsi_Device *, int); + +extern Scsi_Cmnd * request_queueable(struct request *, Scsi_Device *); +extern int scsi_reset (Scsi_Cmnd *, unsigned int); + +extern int max_scsi_hosts; + +extern void proc_print_scsidevice(Scsi_Device *, char *, int *, int); + +extern void print_command(unsigned char *); +extern void print_sense(const char *, Scsi_Cmnd *); +extern void print_driverbyte(int scsiresult); +extern void print_hostbyte(int scsiresult); + +extern void scsi_mark_host_reset(struct Scsi_Host *Host); +extern void scsi_mark_bus_reset(struct Scsi_Host *Host, int channel); + +#if defined(MAJOR_NR) && (MAJOR_NR != SCSI_TAPE_MAJOR) +#include "hosts.h" + +static Scsi_Cmnd * end_scsi_request(Scsi_Cmnd * SCpnt, int uptodate, int sectors) +{ + struct request * req; + struct buffer_head * bh; + + req = &SCpnt->request; + req->errors = 0; + if (!uptodate) { +#if defined(MAJOR_NR) && (MAJOR_NR == SCSI_DISK_MAJOR) + printk(DEVICE_NAME " I/O error: dev %s, sector %lu, absolute sector %lu\n", + kdevname(req->rq_dev), req->sector, + req->sector + sd[MINOR(SCpnt->request.rq_dev)].start_sect); +#else + printk(DEVICE_NAME " I/O error: dev %s, sector %lu\n", + kdevname(req->rq_dev), req->sector); +#endif + } + + do { + if ((bh = req->bh) != NULL) { + req->bh = bh->b_reqnext; + req->nr_sectors -= bh->b_size >> 9; + req->sector += bh->b_size >> 9; + bh->b_reqnext = NULL; + /* + * This is our 'MD IO has finished' event handler. + * note that b_state should be cached in a register + * anyways, so the overhead if this checking is almost + * zero. But anyways .. we never get OO for free :) + */ + if (test_bit(BH_MD, &bh->b_state)) { + struct md_personality * pers=(struct md_personality *)bh->personality; + pers->end_request(bh,uptodate); + } + /* + * the normal (nonmirrored and no RAID5) case: + */ + else { + mark_buffer_uptodate(bh, uptodate); + unlock_buffer(bh); + } + sectors -= bh->b_size >> 9; + if ((bh = req->bh) != NULL) { + req->current_nr_sectors = bh->b_size >> 9; + if (req->nr_sectors < req->current_nr_sectors) { + req->nr_sectors = req->current_nr_sectors; + printk("end_scsi_request: buffer-list destroyed\n"); + } + } + } + } while(sectors && bh); + if (req->bh){ + req->buffer = bh->b_data; + return SCpnt; + } + DEVICE_OFF(req->rq_dev); + if (req->sem != NULL) { + up(req->sem); + } + add_blkdev_randomness(MAJOR(req->rq_dev)); + + if (SCpnt->host->block) { + struct Scsi_Host * next; + + for (next = SCpnt->host->block; next != SCpnt->host; + next = next->block) + wake_up(&next->host_wait); + } + + req->rq_status = RQ_INACTIVE; + wake_up(&wait_for_request); + wake_up(&SCpnt->device->device_wait); + return NULL; +} + + +/* This is just like INIT_REQUEST, but we need to be aware of the fact + * that an interrupt may start another request, so we run this with interrupts + * turned off + */ +#define INIT_SCSI_REQUEST \ + if (!CURRENT) { \ + CLEAR_INTR; \ + restore_flags(flags); \ + return; \ + } \ + if (MAJOR(CURRENT->rq_dev) != MAJOR_NR) \ + panic(DEVICE_NAME ": request list destroyed");\ + if (CURRENT->bh) { \ + if (!buffer_locked(CURRENT->bh)) \ + panic(DEVICE_NAME ": block not locked"); \ + } +#endif + +#define SCSI_SLEEP(QUEUE, CONDITION) { \ + if (CONDITION) { \ + struct wait_queue wait = { current, NULL}; \ + add_wait_queue(QUEUE, &wait); \ + for(;;) { \ + current->state = TASK_UNINTERRUPTIBLE; \ + if (CONDITION) { \ + if (intr_count) \ + panic("scsi: trying to call schedule() in interrupt" \ + ", file %s, line %d.\n", __FILE__, __LINE__); \ + schedule(); \ + } \ + else \ + break; \ + } \ + remove_wait_queue(QUEUE, &wait);\ + current->state = TASK_RUNNING; \ + }; } + +#endif + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/scsi_ioctl.c b/linux/src/drivers/scsi/scsi_ioctl.c new file mode 100644 index 00000000..76918595 --- /dev/null +++ b/linux/src/drivers/scsi/scsi_ioctl.c @@ -0,0 +1,452 @@ +/* + * Don't import our own symbols, as this would severely mess up our + * symbol tables. + */ +#define _SCSI_SYMS_VER_ +#define __NO_VERSION__ +#include <linux/module.h> + +#include <asm/io.h> +#include <asm/segment.h> +#include <asm/system.h> +#include <asm/page.h> + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/string.h> + +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include <scsi/scsi_ioctl.h> + +#define NORMAL_RETRIES 5 +#define NORMAL_TIMEOUT (10 * HZ) +#define FORMAT_UNIT_TIMEOUT (2 * 60 * 60 * HZ) +#define START_STOP_TIMEOUT (60 * HZ) +#define MOVE_MEDIUM_TIMEOUT (5 * 60 * HZ) +#define READ_ELEMENT_STATUS_TIMEOUT (5 * 60 * HZ) + +#define MAX_BUF PAGE_SIZE + +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +/* + * If we are told to probe a host, we will return 0 if the host is not + * present, 1 if the host is present, and will return an identifying + * string at *arg, if arg is non null, filling to the length stored at + * (int *) arg + */ + +static int ioctl_probe(struct Scsi_Host * host, void *buffer) +{ + int temp, result; + unsigned int len,slen; + const char * string; + + if ((temp = host->hostt->present) && buffer) { + result = verify_area(VERIFY_READ, buffer, sizeof(long)); + if (result) return result; + + len = get_user ((unsigned int *) buffer); + if(host->hostt->info) + string = host->hostt->info(host); + else + string = host->hostt->name; + if(string) { + slen = strlen(string); + if (len > slen) + len = slen + 1; + result = verify_area(VERIFY_WRITE, buffer, len); + if (result) return result; + + memcpy_tofs (buffer, string, len); + } + } + return temp; +} + +/* + * + * The SCSI_IOCTL_SEND_COMMAND ioctl sends a command out to the SCSI host. + * The NORMAL_TIMEOUT and NORMAL_RETRIES variables are used. + * + * dev is the SCSI device struct ptr, *(int *) arg is the length of the + * input data, if any, not including the command string & counts, + * *((int *)arg + 1) is the output buffer size in bytes. + * + * *(char *) ((int *) arg)[2] the actual command byte. + * + * Note that if more than MAX_BUF bytes are requested to be transfered, + * the ioctl will fail with error EINVAL. MAX_BUF can be increased in + * the future by increasing the size that scsi_malloc will accept. + * + * This size *does not* include the initial lengths that were passed. + * + * The SCSI command is read from the memory location immediately after the + * length words, and the input data is right after the command. The SCSI + * routines know the command size based on the opcode decode. + * + * The output area is then filled in starting from the command byte. + */ + +static void scsi_ioctl_done (Scsi_Cmnd * SCpnt) +{ + struct request * req; + + req = &SCpnt->request; + req->rq_status = RQ_SCSI_DONE; /* Busy, but indicate request done */ + + if (req->sem != NULL) { + up(req->sem); + } +} + +static int ioctl_internal_command(Scsi_Device *dev, char * cmd, + int timeout, int retries) +{ + int result; + Scsi_Cmnd * SCpnt; + + SCpnt = allocate_device(NULL, dev, 1); + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.sem = &sem; + scsi_do_cmd(SCpnt, cmd, NULL, 0, scsi_ioctl_done, timeout, retries); + down(&sem); + } + + if(driver_byte(SCpnt->result) != 0) + switch(SCpnt->sense_buffer[2] & 0xf) { + case ILLEGAL_REQUEST: + if(cmd[0] == ALLOW_MEDIUM_REMOVAL) dev->lockable = 0; + else printk("SCSI device (ioctl) reports ILLEGAL REQUEST.\n"); + break; + case NOT_READY: /* This happens if there is no disc in drive */ + if(dev->removable){ + printk(KERN_INFO "Device not ready. Make sure there is a disc in the drive.\n"); + break; + }; + case UNIT_ATTENTION: + if (dev->removable){ + dev->changed = 1; + SCpnt->result = 0; /* This is no longer considered an error */ + printk(KERN_INFO "Disc change detected.\n"); + break; + }; + default: /* Fall through for non-removable media */ + printk("SCSI error: host %d id %d lun %d return code = %x\n", + dev->host->host_no, + dev->id, + dev->lun, + SCpnt->result); + printk("\tSense class %x, sense error %x, extended sense %x\n", + sense_class(SCpnt->sense_buffer[0]), + sense_error(SCpnt->sense_buffer[0]), + SCpnt->sense_buffer[2] & 0xf); + + }; + + result = SCpnt->result; + SCpnt->request.rq_status = RQ_INACTIVE; + + if (!SCpnt->device->was_reset && SCpnt->device->scsi_request_fn) + (*SCpnt->device->scsi_request_fn)(); + + wake_up(&SCpnt->device->device_wait); + return result; +} + +/* + * This interface is depreciated - users should use the scsi generics + * interface instead, as this is a more flexible approach to performing + * generic SCSI commands on a device. + */ +int scsi_ioctl_send_command(Scsi_Device *dev, void *buffer) +{ + char * buf; + unsigned char cmd[12]; + char * cmd_in; + Scsi_Cmnd * SCpnt; + unsigned char opcode; + int inlen, outlen, cmdlen; + int needed, buf_needed; + int timeout, retries, result; + + if (!buffer) + return -EINVAL; + + + /* + * Verify that we can read at least this much. + */ + result = verify_area(VERIFY_READ, buffer, 2*sizeof(long) + 1); + if (result) return result; + + /* + * The structure that we are passed should look like: + * + * struct sdata { + * unsigned int inlen; + * unsigned int outlen; + * unsigned char cmd[]; # However many bytes are used for cmd. + * unsigned char data[]; + * }; + */ + inlen = get_user((unsigned int *) buffer); + outlen = get_user( ((unsigned int *) buffer) + 1); + + /* + * We do not transfer more than MAX_BUF with this interface. + * If the user needs to transfer more data than this, they + * should use scsi_generics instead. + */ + if( inlen > MAX_BUF ) return -EINVAL; + if( outlen > MAX_BUF ) return -EINVAL; + + cmd_in = (char *) ( ((int *)buffer) + 2); + opcode = get_user(cmd_in); + + needed = buf_needed = (inlen > outlen ? inlen : outlen); + if(buf_needed){ + buf_needed = (buf_needed + 511) & ~511; + if (buf_needed > MAX_BUF) buf_needed = MAX_BUF; + buf = (char *) scsi_malloc(buf_needed); + if (!buf) return -ENOMEM; + memset(buf, 0, buf_needed); + } else + buf = NULL; + + /* + * Obtain the command from the user's address space. + */ + cmdlen = COMMAND_SIZE(opcode); + + result = verify_area(VERIFY_READ, cmd_in, + cmdlen + inlen > MAX_BUF ? MAX_BUF : inlen); + if (result) return result; + + memcpy_fromfs ((void *) cmd, cmd_in, cmdlen); + + /* + * Obtain the data to be sent to the device (if any). + */ + memcpy_fromfs ((void *) buf, + (void *) (cmd_in + cmdlen), + inlen); + + /* + * Set the lun field to the correct value. + */ + cmd[1] = ( cmd[1] & 0x1f ) | (dev->lun << 5); + + switch (opcode) + { + case FORMAT_UNIT: + timeout = FORMAT_UNIT_TIMEOUT; + retries = 1; + break; + case START_STOP: + timeout = START_STOP_TIMEOUT; + retries = NORMAL_RETRIES; + break; + case MOVE_MEDIUM: + timeout = MOVE_MEDIUM_TIMEOUT; + retries = NORMAL_RETRIES; + break; + case READ_ELEMENT_STATUS: + timeout = READ_ELEMENT_STATUS_TIMEOUT; + retries = NORMAL_RETRIES; + break; + default: + timeout = NORMAL_TIMEOUT; + retries = NORMAL_RETRIES; + break; + } + +#ifndef DEBUG_NO_CMD + + SCpnt = allocate_device(NULL, dev, 1); + + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.sem = &sem; + scsi_do_cmd(SCpnt, cmd, buf, needed, scsi_ioctl_done, + timeout, retries); + down(&sem); + } + + /* + * If there was an error condition, pass the info back to the user. + */ + if(SCpnt->result) { + result = verify_area(VERIFY_WRITE, + cmd_in, + sizeof(SCpnt->sense_buffer)); + if (result) return result; + memcpy_tofs((void *) cmd_in, + SCpnt->sense_buffer, + sizeof(SCpnt->sense_buffer)); + } else { + result = verify_area(VERIFY_WRITE, cmd_in, outlen); + if (result) return result; + memcpy_tofs ((void *) cmd_in, buf, outlen); + } + result = SCpnt->result; + + SCpnt->request.rq_status = RQ_INACTIVE; + + if (buf) scsi_free(buf, buf_needed); + + if(SCpnt->device->scsi_request_fn) + (*SCpnt->device->scsi_request_fn)(); + + wake_up(&SCpnt->device->device_wait); + return result; +#else + { + int i; + printk("scsi_ioctl : device %d. command = ", dev->id); + for (i = 0; i < 12; ++i) + printk("%02x ", cmd[i]); + printk("\nbuffer ="); + for (i = 0; i < 20; ++i) + printk("%02x ", buf[i]); + printk("\n"); + printk("inlen = %d, outlen = %d, cmdlen = %d\n", + inlen, outlen, cmdlen); + printk("buffer = %d, cmd_in = %d\n", buffer, cmd_in); + } + return 0; +#endif +} + +/* + * the scsi_ioctl() function differs from most ioctls in that it does + * not take a major/minor number as the dev field. Rather, it takes + * a pointer to a scsi_devices[] element, a structure. + */ +int scsi_ioctl (Scsi_Device *dev, int cmd, void *arg) +{ + int result; + char scsi_cmd[12]; + + /* No idea how this happens.... */ + if (!dev) return -ENXIO; + + switch (cmd) { + case SCSI_IOCTL_GET_IDLUN: + result = verify_area(VERIFY_WRITE, (void *) arg, 2*sizeof(long)); + if (result) return result; + + put_user(dev->id + + (dev->lun << 8) + + (dev->channel << 16) + + ((dev->host->hostt->proc_dir->low_ino & 0xff) << 24), + (unsigned long *) arg); + put_user( dev->host->unique_id, (unsigned long *) arg+1); + return 0; + case SCSI_IOCTL_GET_BUS_NUMBER: + result = verify_area(VERIFY_WRITE, (void *) arg, sizeof(int)); + if (result) return result; + put_user( dev->host->host_no, (int *) arg); + return 0; + case SCSI_IOCTL_TAGGED_ENABLE: + if(!suser()) return -EACCES; + if(!dev->tagged_supported) return -EINVAL; + dev->tagged_queue = 1; + dev->current_tag = 1; + return 0; + case SCSI_IOCTL_TAGGED_DISABLE: + if(!suser()) return -EACCES; + if(!dev->tagged_supported) return -EINVAL; + dev->tagged_queue = 0; + dev->current_tag = 0; + return 0; + case SCSI_IOCTL_PROBE_HOST: + return ioctl_probe(dev->host, arg); + case SCSI_IOCTL_SEND_COMMAND: + if(!suser() || securelevel > 0) return -EACCES; + return scsi_ioctl_send_command((Scsi_Device *) dev, arg); + case SCSI_IOCTL_DOORLOCK: + if (!dev->removable || !dev->lockable) return 0; + scsi_cmd[0] = ALLOW_MEDIUM_REMOVAL; + scsi_cmd[1] = dev->lun << 5; + scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0; + scsi_cmd[4] = SCSI_REMOVAL_PREVENT; + return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd, + NORMAL_TIMEOUT, NORMAL_RETRIES); + break; + case SCSI_IOCTL_DOORUNLOCK: + if (!dev->removable || !dev->lockable) return 0; + scsi_cmd[0] = ALLOW_MEDIUM_REMOVAL; + scsi_cmd[1] = dev->lun << 5; + scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0; + scsi_cmd[4] = SCSI_REMOVAL_ALLOW; + return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd, + NORMAL_TIMEOUT, NORMAL_RETRIES); + case SCSI_IOCTL_TEST_UNIT_READY: + scsi_cmd[0] = TEST_UNIT_READY; + scsi_cmd[1] = dev->lun << 5; + scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0; + scsi_cmd[4] = 0; + return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd, + NORMAL_TIMEOUT, NORMAL_RETRIES); + break; + case SCSI_IOCTL_START_UNIT: + scsi_cmd[0] = START_STOP; + scsi_cmd[1] = dev->lun << 5; + scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0; + scsi_cmd[4] = 1; + return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd, + START_STOP_TIMEOUT, NORMAL_RETRIES); + break; + case SCSI_IOCTL_STOP_UNIT: + scsi_cmd[0] = START_STOP; + scsi_cmd[1] = dev->lun << 5; + scsi_cmd[2] = scsi_cmd[3] = scsi_cmd[5] = 0; + scsi_cmd[4] = 0; + return ioctl_internal_command((Scsi_Device *) dev, scsi_cmd, + START_STOP_TIMEOUT, NORMAL_RETRIES); + break; + default : + return -EINVAL; + } + return -EINVAL; +} + +/* + * Just like scsi_ioctl, only callable from kernel space with no + * fs segment fiddling. + */ + +int kernel_scsi_ioctl (Scsi_Device *dev, int cmd, void *arg) { + unsigned long oldfs; + int tmp; + oldfs = get_fs(); + set_fs(get_ds()); + tmp = scsi_ioctl (dev, cmd, arg); + set_fs(oldfs); + return tmp; +} + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/scsi_proc.c b/linux/src/drivers/scsi/scsi_proc.c new file mode 100644 index 00000000..d1fa28d0 --- /dev/null +++ b/linux/src/drivers/scsi/scsi_proc.c @@ -0,0 +1,302 @@ +/* + * linux/drivers/scsi/scsi_proc.c + * + * The functions in this file provide an interface between + * the PROC file system and the SCSI device drivers + * It is mainly used for debugging, statistics and to pass + * information directly to the lowlevel driver. + * + * (c) 1995 Michael Neuffer neuffer@goofy.zdv.uni-mainz.de + * Version: 0.99.8 last change: 95/09/13 + * + * generic command parser provided by: + * Andreas Heilwagen <crashcar@informatik.uni-koblenz.de> + */ + +/* + * Don't import our own symbols, as this would severely mess up our + * symbol tables. + */ +#define _SCSI_SYMS_VER_ +#define __NO_VERSION__ +#include <linux/module.h> + +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/malloc.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> +#include <linux/stat.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +extern int scsi_proc_info(char *, char **, off_t, int, int, int); + +struct scsi_dir { + struct proc_dir_entry entry; + char name[4]; +}; + + +/* generic_proc_info + * Used if the driver currently has no own support for /proc/scsi + */ +int generic_proc_info(char *buffer, char **start, off_t offset, + int length, int inode, int inout) +{ + int len, pos, begin; + + if(inout == TRUE) + return(-ENOSYS); /* This is a no-op */ + + begin = 0; + pos = len = sprintf(buffer, + "The driver does not yet support the proc-fs\n"); + if(pos < offset) { + len = 0; + begin = pos; + } + + *start = buffer + (offset - begin); /* Start of wanted data */ + len -= (offset - begin); + if(len > length) + len = length; + + return(len); +} + +/* dispatch_scsi_info is the central dispatcher + * It is the interface between the proc-fs and the SCSI subsystem code + */ +extern int dispatch_scsi_info(int ino, char *buffer, char **start, + off_t offset, int length, int func) +{ + struct Scsi_Host *hpnt = scsi_hostlist; + + if(ino == PROC_SCSI_SCSI) { + /* + * This is for the scsi core, rather than any specific + * lowlevel driver. + */ + return(scsi_proc_info(buffer, start, offset, length, 0, func)); + } + + while(hpnt) { + if (ino == (hpnt->host_no + PROC_SCSI_FILE)) { + if(hpnt->hostt->proc_info == NULL) + return generic_proc_info(buffer, start, offset, length, + hpnt->host_no, func); + else + return(hpnt->hostt->proc_info(buffer, start, offset, + length, hpnt->host_no, func)); + } + hpnt = hpnt->next; + } + return(-EBADF); +} + +void build_proc_dir_entries(Scsi_Host_Template *tpnt) +{ + struct Scsi_Host *hpnt; + + struct scsi_dir *scsi_hba_dir; + + proc_scsi_register(0, tpnt->proc_dir); + + hpnt = scsi_hostlist; + while (hpnt) { + if (tpnt == hpnt->hostt) { + scsi_hba_dir = scsi_init_malloc(sizeof(struct scsi_dir), GFP_KERNEL); + if(scsi_hba_dir == NULL) + panic("Not enough memory to register SCSI HBA in /proc/scsi !\n"); + memset(scsi_hba_dir, 0, sizeof(struct scsi_dir)); + scsi_hba_dir->entry.low_ino = PROC_SCSI_FILE + hpnt->host_no; + scsi_hba_dir->entry.namelen = sprintf(scsi_hba_dir->name,"%d", + hpnt->host_no); + scsi_hba_dir->entry.name = scsi_hba_dir->name; + scsi_hba_dir->entry.mode = S_IFREG | S_IRUGO | S_IWUSR; + proc_scsi_register(tpnt->proc_dir, &scsi_hba_dir->entry); + } + hpnt = hpnt->next; + } +} + +/* + * parseHandle *parseInit(char *buf, char *cmdList, int cmdNum); + * gets a pointer to a null terminated data buffer + * and a list of commands with blanks as delimiter + * in between. + * The commands have to be alphanumerically sorted. + * cmdNum has to contain the number of commands. + * On success, a pointer to a handle structure + * is returned, NULL on failure + * + * int parseOpt(parseHandle *handle, char **param); + * processes the next parameter. On success, the + * index of the appropriate command in the cmdList + * is returned, starting with zero. + * param points to the null terminated parameter string. + * On failure, -1 is returned. + * + * The databuffer buf may only contain pairs of commands + * options, separated by blanks: + * <Command> <Parameter> [<Command> <Parameter>]* + */ + +typedef struct +{ + char *buf, /* command buffer */ + *cmdList, /* command list */ + *bufPos, /* actual position */ + **cmdPos, /* cmdList index */ + cmdNum; /* cmd number */ +} parseHandle; + + +inline int parseFree (parseHandle *handle) /* free memory */ +{ + kfree (handle->cmdPos); + kfree (handle); + + return(-1); +} + + +parseHandle *parseInit(char *buf, char *cmdList, int cmdNum) +{ + char *ptr; /* temp pointer */ + parseHandle *handle; /* new handle */ + + if (!buf || !cmdList) /* bad input ? */ + return(NULL); + if ((handle = (parseHandle*) kmalloc(sizeof(parseHandle), GFP_KERNEL)) == 0) + return(NULL); /* out of memory */ + if ((handle->cmdPos = (char**) kmalloc(sizeof(int) * cmdNum, GFP_KERNEL)) == 0) { + kfree(handle); + return(NULL); /* out of memory */ + } + + handle->buf = handle->bufPos = buf; /* init handle */ + handle->cmdList = cmdList; + handle->cmdNum = cmdNum; + + handle->cmdPos[cmdNum = 0] = cmdList; + for (ptr = cmdList; *ptr; ptr++) { /* scan command string */ + if(*ptr == ' ') { /* and insert zeroes */ + *ptr++ = 0; + handle->cmdPos[++cmdNum] = ptr++; + } + } + return(handle); +} + + +int parseOpt(parseHandle *handle, char **param) +{ + int cmdIndex = 0, + cmdLen = 0; + char *startPos; + + if (!handle) /* invalid handle */ + return(parseFree(handle)); + /* skip spaces */ + for (; *(handle->bufPos) && *(handle->bufPos) == ' '; handle->bufPos++); + if (!*(handle->bufPos)) + return(parseFree(handle)); /* end of data */ + + startPos = handle->bufPos; /* store cmd start */ + for (; handle->cmdPos[cmdIndex][cmdLen] && *(handle->bufPos); handle->bufPos++) + { /* no string end? */ + for (;;) + { + if (*(handle->bufPos) == handle->cmdPos[cmdIndex][cmdLen]) + break; /* char matches ? */ + else + if (memcmp(startPos, (char*)(handle->cmdPos[++cmdIndex]), cmdLen)) + return(parseFree(handle)); /* unknown command */ + + if (cmdIndex >= handle->cmdNum) + return(parseFree(handle)); /* unknown command */ + } + + cmdLen++; /* next char */ + } + + /* Get param. First skip all blanks, then insert zero after param */ + + for (; *(handle->bufPos) && *(handle->bufPos) == ' '; handle->bufPos++); + *param = handle->bufPos; + + for (; *(handle->bufPos) && *(handle->bufPos) != ' '; handle->bufPos++); + *(handle->bufPos++) = 0; + + return(cmdIndex); +} + +void proc_print_scsidevice(Scsi_Device *scd, char *buffer, int *size, int len) +{ + int x, y = *size; + + y = sprintf(buffer + len, + "Host: scsi%d Channel: %02d Id: %02d Lun: %02d\n Vendor: ", + scd->host->host_no, scd->channel, scd->id, scd->lun); + for (x = 0; x < 8; x++) { + if (scd->vendor[x] >= 0x20) + y += sprintf(buffer + len + y, "%c", scd->vendor[x]); + else + y += sprintf(buffer + len + y," "); + } + y += sprintf(buffer + len + y, " Model: "); + for (x = 0; x < 16; x++) { + if (scd->model[x] >= 0x20) + y += sprintf(buffer + len + y, "%c", scd->model[x]); + else + y += sprintf(buffer + len + y, " "); + } + y += sprintf(buffer + len + y, " Rev: "); + for (x = 0; x < 4; x++) { + if (scd->rev[x] >= 0x20) + y += sprintf(buffer + len + y, "%c", scd->rev[x]); + else + y += sprintf(buffer + len + y, " "); + } + y += sprintf(buffer + len + y, "\n"); + + y += sprintf(buffer + len + y, " Type: %s ", + scd->type < MAX_SCSI_DEVICE_CODE ? + scsi_device_types[(int)scd->type] : "Unknown " ); + y += sprintf(buffer + len + y, " ANSI" + " SCSI revision: %02x", (scd->scsi_level < 3)?1:2); + if (scd->scsi_level == 2) + y += sprintf(buffer + len + y, " CCS\n"); + else + y += sprintf(buffer + len + y, "\n"); + + *size = y; + return; +} + +/* + * Overrides for Emacs so that we get a uniform tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/scsicam.c b/linux/src/drivers/scsi/scsicam.c new file mode 100644 index 00000000..2c6f6377 --- /dev/null +++ b/linux/src/drivers/scsi/scsicam.c @@ -0,0 +1,230 @@ +/* + * scsicam.c - SCSI CAM support functions, use for HDIO_GETGEO, etc. + * + * Copyright 1993, 1994 Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@Colorado.EDU + * +1 (303) 786-7975 + * + * For more information, please consult the SCSI-CAM draft. + */ + +/* + * Don't import our own symbols, as this would severely mess up our + * symbol tables. + */ +#define _SCSI_SYMS_VER_ +#define __NO_VERSION__ +#include <linux/module.h> + +#include <linux/fs.h> +#include <linux/genhd.h> +#include <linux/kernel.h> +#include <linux/blk.h> +#include <asm/unaligned.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" + +static int partsize(struct buffer_head *bh, unsigned long capacity, + unsigned int *cyls, unsigned int *hds, unsigned int *secs); +static int setsize(unsigned long capacity,unsigned int *cyls,unsigned int *hds, + unsigned int *secs); + +/* + * Function : int scsicam_bios_param (Disk *disk, int dev, int *ip) + * + * Purpose : to determine the BIOS mapping used for a drive in a + * SCSI-CAM system, storing the results in ip as required + * by the HDIO_GETGEO ioctl(). + * + * Returns : -1 on failure, 0 on success. + * + */ + +int scsicam_bios_param (Disk *disk, /* SCSI disk */ + kdev_t dev, /* Device major, minor */ + int *ip /* Heads, sectors, cylinders in that order */) { + + struct buffer_head *bh; + int ret_code; + int size = disk->capacity; + unsigned long temp_cyl; + + if (!(bh = bread(MKDEV(MAJOR(dev), MINOR(dev)&~0xf), 0, 1024))) + return -1; + + /* try to infer mapping from partition table */ + ret_code = partsize (bh, (unsigned long) size, (unsigned int *) ip + 2, + (unsigned int *) ip + 0, (unsigned int *) ip + 1); + brelse (bh); + + if (ret_code == -1) { + /* pick some standard mapping with at most 1024 cylinders, + and at most 62 sectors per track - this works up to + 7905 MB */ + ret_code = setsize ((unsigned long) size, (unsigned int *) ip + 2, + (unsigned int *) ip + 0, (unsigned int *) ip + 1); + } + + /* if something went wrong, then apparently we have to return + a geometry with more than 1024 cylinders */ + if (ret_code || ip[0] > 255 || ip[1] > 63) { + ip[0] = 64; + ip[1] = 32; + temp_cyl = size / (ip[0] * ip[1]); + if (temp_cyl > 65534) { + ip[0] = 255; + ip[1] = 63; + } + ip[2] = size / (ip[0] * ip[1]); + } + + return 0; +} + +/* + * Function : static int partsize(struct buffer_head *bh, unsigned long + * capacity,unsigned int *cyls, unsigned int *hds, unsigned int *secs); + * + * Purpose : to determine the BIOS mapping used to create the partition + * table, storing the results in *cyls, *hds, and *secs + * + * Returns : -1 on failure, 0 on success. + * + */ + +static int partsize(struct buffer_head *bh, unsigned long capacity, + unsigned int *cyls, unsigned int *hds, unsigned int *secs) { + struct partition *p, *largest = NULL; + int i, largest_cyl; + int cyl, ext_cyl, end_head, end_cyl, end_sector; + unsigned int logical_end, physical_end, ext_physical_end; + + + if (*(unsigned short *) (bh->b_data+510) == 0xAA55) { + for (largest_cyl = -1, p = (struct partition *) + (0x1BE + bh->b_data), i = 0; i < 4; ++i, ++p) { + if (!p->sys_ind) + continue; +#ifdef DEBUG + printk ("scsicam_bios_param : partition %d has system \n", + i); +#endif + cyl = p->cyl + ((p->sector & 0xc0) << 2); + if (cyl > largest_cyl) { + largest_cyl = cyl; + largest = p; + } + } + } + + if (largest) { + end_cyl = largest->end_cyl + ((largest->end_sector & 0xc0) << 2); + end_head = largest->end_head; + end_sector = largest->end_sector & 0x3f; + + if( end_head + 1 == 0 || end_sector == 0 ) return -1; + +#ifdef DEBUG + printk ("scsicam_bios_param : end at h = %d, c = %d, s = %d\n", + end_head, end_cyl, end_sector); +#endif + + physical_end = end_cyl * (end_head + 1) * end_sector + + end_head * end_sector + end_sector; + + /* This is the actual _sector_ number at the end */ + logical_end = get_unaligned(&largest->start_sect) + + get_unaligned(&largest->nr_sects); + + /* This is for >1023 cylinders */ + ext_cyl= (logical_end-(end_head * end_sector + end_sector)) + /(end_head + 1) / end_sector; + ext_physical_end = ext_cyl * (end_head + 1) * end_sector + + end_head * end_sector + end_sector; + +#ifdef DEBUG + printk("scsicam_bios_param : logical_end=%d physical_end=%d ext_physical_end=%d ext_cyl=%d\n" + ,logical_end,physical_end,ext_physical_end,ext_cyl); +#endif + + if ((logical_end == physical_end) || + (end_cyl==1023 && ext_physical_end==logical_end)) { + *secs = end_sector; + *hds = end_head + 1; + *cyls = capacity / ((end_head + 1) * end_sector); + return 0; + } + +#ifdef DEBUG + printk ("scsicam_bios_param : logical (%u) != physical (%u)\n", + logical_end, physical_end); +#endif + } + return -1; +} + +/* + * Function : static int setsize(unsigned long capacity,unsigned int *cyls, + * unsigned int *hds, unsigned int *secs); + * + * Purpose : to determine a near-optimal int 0x13 mapping for a + * SCSI disk in terms of lost space of size capacity, storing + * the results in *cyls, *hds, and *secs. + * + * Returns : -1 on failure, 0 on success. + * + * Extracted from + * + * WORKING X3T9.2 + * DRAFT 792D + * + * + * Revision 6 + * 10-MAR-94 + * Information technology - + * SCSI-2 Common access method + * transport and SCSI interface module + * + * ANNEX A : + * + * setsize() converts a read capacity value to int 13h + * head-cylinder-sector requirements. It minimizes the value for + * number of heads and maximizes the number of cylinders. This + * will support rather large disks before the number of heads + * will not fit in 4 bits (or 6 bits). This algorithm also + * minimizes the number of sectors that will be unused at the end + * of the disk while allowing for very large disks to be + * accommodated. This algorithm does not use physical geometry. + */ + +static int setsize(unsigned long capacity,unsigned int *cyls,unsigned int *hds, + unsigned int *secs) { + unsigned int rv = 0; + unsigned long heads, sectors, cylinders, temp; + + cylinders = 1024L; /* Set number of cylinders to max */ + sectors = 62L; /* Maximize sectors per track */ + + temp = cylinders * sectors; /* Compute divisor for heads */ + heads = capacity / temp; /* Compute value for number of heads */ + if (capacity % temp) { /* If no remainder, done! */ + heads++; /* Else, increment number of heads */ + temp = cylinders * heads; /* Compute divisor for sectors */ + sectors = capacity / temp; /* Compute value for sectors per + track */ + if (capacity % temp) { /* If no remainder, done! */ + sectors++; /* Else, increment number of sectors */ + temp = heads * sectors; /* Compute divisor for cylinders */ + cylinders = capacity / temp;/* Compute number of cylinders */ + } + } + if (cylinders == 0) rv=(unsigned)-1;/* Give error if 0 cylinders */ + + *cyls = (unsigned int) cylinders; /* Stuff return values */ + *secs = (unsigned int) sectors; + *hds = (unsigned int) heads; + return(rv); +} diff --git a/linux/src/drivers/scsi/scsiio.c b/linux/src/drivers/scsi/scsiio.c new file mode 100644 index 00000000..cea68b8d --- /dev/null +++ b/linux/src/drivers/scsi/scsiio.c @@ -0,0 +1,1537 @@ +/*********************************************************************** + * FILE NAME : SCSIIO.C * + * BY : C.L. Huang * + * Description: Device Driver for Tekram DC-390W/U/F (T) PCI SCSI * + * Bus Master Host Adapter * + ***********************************************************************/ + + +static void +PrepareSG( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + ULONG retAddr,wlval; + USHORT wval,i; + PSGL psgl; + PSGE psge; + + + retAddr = pACB->jmp_table8; + if(pDCB->DCBscntl3 & EN_WIDE_SCSI) + retAddr += jmp_table16; + wval = (USHORT)(pSRB->SGcount); + wval <<= 4; /* 16 bytes per entry, datain=8, dataout=8 */ + /* (4 bytes for count, 4 bytes for addr) */ + retAddr -= (ULONG)wval; + pSRB->ReturnAddr = retAddr; /* return address for SCRIPT */ + if(wval) + { + wval >>= 1; + wlval = (ULONG) pSRB->SegmentPad; + wlval -= (ULONG)wval; + wval >>= 3; + psge = (PSGE) wlval; + psgl = pSRB->pSegmentList; + for(i=0; i<wval; i++) + { +#ifndef VERSION_ELF_1_2_13 + psge->SGXPtr = virt_to_phys( psgl->address ); +#else + psge->SGXPtr = (ULONG) psgl->address; +#endif + psge->SGXLen = psgl->length; + psge++; + psgl++; + } + } +} + + +static void +DC390W_StartSCSI( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + USHORT ioport; + UCHAR bval; + + pSRB->TagNumber = 31; + ioport = pACB->IOPortBase; + bval = SIGNAL_PROC; + outb(bval,ioport+ISTAT); + pACB->pActiveDCB = pDCB; + pDCB->pActiveSRB = pSRB; + return; +} + + +#ifndef VERSION_ELF_1_2_13 +static void +DC390W_Interrupt( int irq, void *dev_id, struct pt_regs *regs) +#else +static void +DC390W_Interrupt( int irq, struct pt_regs *regs) +#endif +{ + PACB pACB; + PDCB pDCB; + ULONG wlval; + USHORT ioport = 0; + USHORT wval, i; + void (*stateV)( PACB ); + UCHAR istat = 0; + UCHAR bval; + + pACB = pACB_start; + if( pACB == NULL ) + return; + for( i=0; i < adapterCnt; i++ ) + { + if( pACB->IRQLevel == (UCHAR) irq ) + { + ioport = pACB->IOPortBase; + istat = inb( ioport+ISTAT ); + if( istat & (ABORT_OP+SCSI_INT_PENDING+DMA_INT_PENDING) ) + break; + else + pACB = pACB->pNextACB; + } + else + { + pACB = pACB->pNextACB; + } + } + + if( pACB == (PACB )-1 ) + { + printk("DC390W_intr: Spurious interrupt detected!\n"); + return; + } + + +#ifdef DC390W_DEBUG1 + printk("Istate=%2x,",istat); +#endif + /* if Abort operation occurred, reset abort bit before reading DMA status + to prevent further aborted interrupt. */ + + if(istat & ABORT_OP) + { + istat &= ~ABORT_OP; + outb(istat,ioport+ISTAT); + } + + pDCB = pACB->pActiveDCB; + bval = inb(ioport+CTEST2); /* Clear Signal Bit */ + + /* If Scsi Interrupt, then clear Interrupt Status by reading + Scsi interrupt status register 0. */ + + wlval = 0; + if(istat & SCSI_INT_PENDING) + { + wlval = (ULONG)inw( ioport+SIST0 ); + wlval <<= 8; + } + + /* If DMA Interrupt, then read the DMA status register to see what happen */ + + if(istat & DMA_INT_PENDING) + { + bval = inb(ioport+DSTAT); + wlval |= (ULONG) bval; + } + +#ifdef DC390W_DEBUG1 + printk("IDstate=%8x,",(UINT) wlval); +#endif + if(wlval & ( (SEL_TIMEOUT << 16)+ + ((SCSI_GERROR+UNEXPECT_DISC+SCSI_RESET) << 8)+ + ILLEGAL_INSTRUC+ABORT_) ) + { + ExceptionHandler( wlval, pACB, pDCB ); + } + else if( wlval & SCRIPTS_INT ) + { + wval = inw( ioport+DSPS ); + stateV = (void *) IntVector[wval]; + stateV( pACB ); + } + else if( wlval & ( PARITY_ERROR << 8) ) + ParityError( pACB, pDCB ); + else if( wlval & ( PHASE_MISMATCH << 8) ) + PhaseMismatch( pACB ); + return; +} + + +static void +ExceptionHandler(ULONG wlval, PACB pACB, PDCB pDCB) +{ + PSRB pSRB; + UCHAR bval; + USHORT ioport; + +/* disconnect/scsi reset/illegal instruction */ + + ioport = pACB->IOPortBase; + if(wlval & ( (SCSI_RESET+SCSI_GERROR) << 8) ) + DC390W_ScsiRstDetect( pACB ); + else if(wlval & ABORT_) + { +#ifdef DC390W_DEBUG0 + printk("AboRst,"); +#endif + if( !InitialTime ) + DC390W_ResetSCSIBus2( pACB ); + } + else if(wlval & (SEL_TIMEOUT << 16) ) + { + pACB->status = SCSI_STAT_SEL_TIMEOUT; +#ifdef DC390W_DEBUG1 + printk("Selto,"); +#endif + DC390W_CmdCompleted( pACB ); + } + else if(wlval & (UNEXPECT_DISC << 8) ) + { + bval = inb(ioport+STEST3); + bval |= CLR_SCSI_FIFO; + outb(bval,ioport+STEST3); + bval = CLR_DMA_FIFO; + outb(bval,ioport+CTEST3); + pSRB = pDCB->pActiveSRB; + if( pSRB->SRBState & DO_SYNC_NEGO ) + { + pDCB->DevMode &= ~SYNC_NEGO_; + pACB->status = SCSI_STAT_CHECKCOND; + DC390W_CmdCompleted( pACB ); + } + else if( pSRB->SRBState & DO_WIDE_NEGO ) + { + pDCB->DevMode &= ~WIDE_NEGO_; + pACB->status = SCSI_STAT_CHECKCOND; + DC390W_CmdCompleted( pACB ); + } + else + { + pACB->status = SCSI_STAT_UNEXP_BUS_F; + DC390W_CmdCompleted( pACB ); + } +#ifdef DC390W_DEBUG0 + printk("Uxpbf,"); +#endif + } + else + { +#ifdef DC390W_DEBUG0 + printk("Except,"); +#endif + DC390W_ResetSCSIBus( pACB ); + } +} + + +static void +ParityError( PACB pACB, PDCB pDCB ) +{ + ULONG ioport; + UCHAR bval,msg; + ULONG wlval; + PSRB pSRB; + + ioport = pACB->IOPortBase; + bval = inb(ioport+SCRATCHA); + if(bval & RE_SELECTED_) + { +#ifdef DC390W_DEBUG0 + printk("ParityErr,"); +#endif + DC390W_ResetSCSIBus( pACB ); + return; + } + else + { + pSRB = pDCB->pActiveSRB; + bval = inb(ioport+STEST3); + bval |= CLR_SCSI_FIFO; + outb(bval,ioport+STEST3); + bval = CLR_DMA_FIFO; + outb(bval,ioport+CTEST3); + + bval = inb(ioport+DCMD); + bval &= 0x07; /* get phase bits */ + if(bval == 0x07) /* message in phase */ + { + msg = MSG_PARITY_ERROR; + wlval = pACB->jmp_clear_ack; + } + else + { + msg = MSG_INITIATOR_ERROR; + wlval = pACB->jmp_next; + } + pSRB->__msgout0[0] = 1; + pSRB->MsgOutBuf[0] = msg; + outl(wlval,(ioport+DSP)); + return; + } +} + + +static void +DC390W_Signal( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB; + USHORT ioport; + ULONG wlval, flags; + UCHAR bval,msgcnt,tagnum; + + save_flags(flags); + cli(); + ioport = pACB->IOPortBase; + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; +#ifdef DC390W_DEBUG0 + printk("Signal,Cmd=%2x", pSRB->CmdBlock[0]); +#endif + wlval = pSRB->PhysSRB; + outl(wlval,(ioport+DSA)); + wlval = pSRB->ReturnAddr; + outl(wlval,(ioport+TEMP)); + msgcnt = 1; + bval = pDCB->IdentifyMsg; + pSRB->MsgOutBuf[0] = bval; + if( (pSRB->CmdBlock[0] != INQUIRY) && + (pSRB->CmdBlock[0] != REQUEST_SENSE) ) + { + if(pDCB->MaxCommand > 1) + { + wlval = 1; + tagnum = 0; + while( wlval & pDCB->TagMask ) + { + wlval = wlval << 1; + tagnum++; + } + pDCB->TagMask |= wlval; + pSRB->TagNumber = tagnum; + pSRB->MsgOutBuf[1] = MSG_SIMPLE_QTAG; + pSRB->MsgOutBuf[2] = tagnum; + msgcnt = 3; + } + } + else + { + pSRB->MsgOutBuf[0] &= 0xBF; /* Diable Disconnected */ + if(pSRB->CmdBlock[0] == INQUIRY) + { + if(bval & 0x07) + goto type_6_3; + } + if(pDCB->DevMode & WIDE_NEGO_) + { + msgcnt = 5; + *((PULONG) &(pSRB->MsgOutBuf[1])) = 0x01030201; + pSRB->SRBState |= DO_WIDE_NEGO; + } + else if(pDCB->DevMode & SYNC_NEGO_) + { + msgcnt = 6; + *((PULONG) &(pSRB->MsgOutBuf[1])) = 0x00010301; + pSRB->MsgOutBuf[4] = pDCB->NegoPeriod; + pSRB->MsgOutBuf[5] = SYNC_NEGO_OFFSET; + pSRB->SRBState |= DO_SYNC_NEGO; + } + } +type_6_3: + pSRB->__msgout0[0] = (ULONG) msgcnt; + wlval = 0; + outl(wlval,(ioport+SCRATCHA)); + bval = pDCB->DCBscntl0; + outb(bval,ioport+SCNTL0); + pSRB->__select = *((PULONG) &(pDCB->DCBselect)); +#ifdef DC390W_DEBUG0 + printk("__sel=%8x,", (UINT)(pSRB->__select)); +#endif + wlval = pACB->jmp_select; + outl(wlval,(ioport+DSP)); + restore_flags(flags); + return; +} + + +static void +DC390W_MessageWide( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB; + PUCHAR msgoutPtr; + USHORT ioport; + ULONG wlval; + UCHAR bval,msgcnt; + + +#ifdef DC390W_DEBUG0 + printk("MsgWide,"); +#endif + ioport = pACB->IOPortBase; + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; + msgcnt = 0; + pDCB->DCBscntl3 &= ~EN_WIDE_SCSI; + msgoutPtr = pSRB->MsgOutBuf; + if( pSRB->SRBState & DO_WIDE_NEGO ) + { + pSRB->SRBState &= ~DO_WIDE_NEGO; + if( pACB->msgin123[0] == 3 ) + { + bval = pACB->msgin123[1]; + if(bval == 1) + { + pDCB->DCBscntl3 |= EN_WIDE_SCSI; + goto x5; + } + if(bval < 1) + goto x5; + } + } + +/*type_11_1:*/ + msgcnt = 1; + *msgoutPtr = MSG_REJECT_; + msgoutPtr++; +x5: + bval = pDCB->DCBscntl3; + outb(bval,ioport+SCNTL3); + AdjustTemp(pACB,pDCB,pSRB); + SetXferRate(pACB,pDCB); + if( pDCB->DevMode & SYNC_NEGO_ ) + { + *((PULONG)msgoutPtr) = 0x00010301; + *(msgoutPtr + 3) = pDCB->NegoPeriod; + *(msgoutPtr + 4) = SYNC_NEGO_OFFSET; + msgcnt += 5; + pSRB->SRBState |= DO_SYNC_NEGO; + } + + pSRB->__msgout0[0] = (ULONG) msgcnt; + wlval = pACB->jmp_clear_ack; + if(msgcnt) + wlval = pACB->jmp_set_atn; + outl(wlval,(ioport+DSP)); + return; +} + + +static void +DC390W_MessageSync( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB; + USHORT ioport; + ULONG wlval; + USHORT wval,wval1; + UCHAR bval,bval1; + +#ifdef DC390W_DEBUG0 + printk("MsgSync,"); +#endif + ioport = pACB->IOPortBase; + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; + if( !(pSRB->SRBState & DO_SYNC_NEGO) ) + goto MessageExtnd; + pSRB->SRBState &= ~DO_SYNC_NEGO; + if(pACB->msgin123[0] != 1) + { +MessageExtnd: + pSRB->__msgout0[0] = 1; + pSRB->MsgOutBuf[0] = MSG_REJECT_; + wlval = pACB->jmp_set_atn; + outl(wlval,(ioport+DSP)); + return; + } + bval = pACB->msgin123[2]; /* offset */ +asyncx: + pDCB->DCBsxfer = bval; + if(bval == 0) /* if offset or period == 0, async */ + { + if( pACB->AdaptType == DC390W ) + bval = SYNC_CLK_F2+ASYNC_CLK_F2; + else + bval = SYNC_CLK_F4+ASYNC_CLK_F4; + pDCB->DCBscntl3 = bval; + } + else + { + bval = pACB->msgin123[1]; + if(bval == 0) + goto asyncx; + pDCB->SyncPeriod = bval; + wval = (USHORT)bval; + wval <<= 3; + bval = pDCB->DCBscntl3; + bval &= 0x0f; + if(wval < 200) /* < 100 ns ==> Fast-20 */ + { + bval |= 0x90; /* Fast-20 and div 1 */ + bval1 = 25; /* 12.5 ns */ + } + else if(wval < 400) + { + bval |= 0x30; /* 1 cycle = 25ns */ + bval1 = 50; + } + else /* Non Fast */ + { + bval |= 0x50; /* 1 cycle = 50ns */ + bval1 = 100; + } + if( pACB->AdaptType == DC390W ) + bval -= 0x20; /* turn down to 40Mhz scsi clock */ + /* assume 390W will not receive fast-20 */ + wval1 = wval; + wval /= bval1; + if(wval * bval1 < wval1) + wval++; + /* XFERP TP2 TP1 TP0 */ + wval -= 4; /* 4 0 0 0 */ + /* 5 0 0 1 */ + wval <<= 5; + pDCB->DCBsxfer |= (UCHAR)wval; + pDCB->DCBscntl3 = bval; + } +/*sync_2:*/ + SetXferRate( pACB,pDCB ); + wlval = pACB->jmp_clear_ack; +/*sync_3:*/ + bval = pDCB->DCBscntl3; + outb(bval,ioport+SCNTL3); + bval = pDCB->DCBsxfer; + outb(bval,ioport+SXFER); + outl(wlval,(ioport+DSP)); + return; +} + + +static void +DC390W_MsgReject( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB; + ULONG wlval; + USHORT ioport; + UCHAR bval; + +#ifdef DC390W_DEBUG0 + printk("Msgrjt,"); +#endif + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; + wlval = pACB->jmp_clear_ack; + if(pSRB->SRBState & DO_WIDE_NEGO) + { + pSRB->SRBState &= ~DO_WIDE_NEGO; + pDCB->DCBscntl3 &= ~EN_WIDE_SCSI; + AdjustTemp( pACB, pDCB, pSRB ); + SetXferRate( pACB, pDCB ); + if( pDCB->DevMode & SYNC_NEGO_ ) + { + *((PULONG) &(pSRB->MsgOutBuf[0])) = 0x00010301; + pSRB->MsgOutBuf[3] = pDCB->NegoPeriod; + pSRB->MsgOutBuf[4] = SYNC_NEGO_OFFSET; + pSRB->__msgout0[0] = 5; + pSRB->SRBState |= DO_SYNC_NEGO; + wlval = pACB->jmp_set_atn; + } + } + else + { + if(pSRB->SRBState & DO_SYNC_NEGO) + { + pSRB->SRBState &= ~DO_SYNC_NEGO; + pDCB->DCBsxfer = 0; /* reject sync msg, set aync */ + if( pACB->AdaptType == DC390W ) + bval = SYNC_CLK_F2+ASYNC_CLK_F2; + else + bval = SYNC_CLK_F4+ASYNC_CLK_F4; + pDCB->DCBscntl3 = bval; + SetXferRate(pACB,pDCB); + wlval = pACB->jmp_clear_ack; + } + } + ioport = pACB->IOPortBase; + bval = pDCB->DCBscntl3; + outb(bval,ioport+SCNTL3); + bval = pDCB->DCBsxfer; + outb(bval,ioport+SXFER); + outl(wlval,(ioport+DSP)); + return; +} + + +static void +AdjustTemp( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + USHORT ioport; + ULONG wlval; + + wlval = pSRB->ReturnAddr; + if(wlval <= pACB->jmp_table8) + { + if(pDCB->DCBscntl3 & EN_WIDE_SCSI) + wlval += jmp_table16; + } + else + { + if((pDCB->DCBscntl3 & EN_WIDE_SCSI) == 0) + wlval -= jmp_table16; + } + pSRB->ReturnAddr = wlval; + ioport = pACB->IOPortBase; + outl(wlval,(ioport+TEMP)); + return; +} + + +static void +SetXferRate( PACB pACB, PDCB pDCB ) +{ + UCHAR bval; + USHORT cnt, i; + PDCB ptr; + + if( !(pDCB->IdentifyMsg & 0x07) ) + { + if( pACB->scan_devices ) + { + CurrDCBscntl3 = pDCB->DCBscntl3; + } + else + { + ptr = pACB->pLinkDCB; + cnt = pACB->DeviceCnt; + bval = pDCB->UnitSCSIID; + for(i=0; i<cnt; i++) + { + if( ptr->UnitSCSIID == bval ) + { + ptr->DCBsxfer = pDCB->DCBsxfer; + ptr->DCBscntl3 = pDCB->DCBscntl3; + } + ptr = ptr->pNextDCB; + } + } + } + return; +} + + +static void +DC390W_UnknownMsg( PACB pACB ) +{ + PSRB pSRB; + ULONG wlval; + USHORT ioport; + + pSRB = pACB->pActiveDCB->pActiveSRB; + pSRB->__msgout0[0] = 1; + pSRB->MsgOutBuf[0] = MSG_REJECT_; + wlval = pACB->jmp_set_atn; + ioport = pACB->IOPortBase; + outl(wlval,(ioport+DSP)); + return; +} + + +static void +DC390W_MessageExtnd( PACB pACB ) +{ + DC390W_UnknownMsg( pACB ); +} + + +static void +DC390W_Disconnected( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB; + ULONG wlval, flags; + USHORT ioport; + UCHAR bval; + +#ifdef DC390W_DEBUG0 + printk("Discnet,"); +#endif + save_flags(flags); + cli(); + pDCB = pACB->pActiveDCB; + if (! pDCB) + { +#ifdef DC390W_DEBUG0 + printk("ACB:%08lx->ActiveDCB:%08lx !,", (ULONG)pACB, (ULONG)pDCB); +#endif + restore_flags(flags); return; + } + + pSRB = pDCB->pActiveSRB; + + ioport = pACB->IOPortBase; + bval = inb(ioport+SCRATCHA); + pSRB->ScratchABuf = bval; + pSRB->SRBState |= SRB_DISCONNECT; /* 1.02 */ + wlval = pACB->jmp_reselect; + outl(wlval,(ioport+DSP)); + pACB->pActiveDCB = 0; + DoWaitingSRB( pACB ); + restore_flags(flags); + return; +} + + +static void +DC390W_Reselected( PACB pACB ) +{ +#ifdef DC390W_DEBUG0 + printk("Rsel,"); +#endif + pACB->msgin123[0] = 0x80; /* set identify byte 80h */ + DC390W_Reselected1(pACB); + return; +} + + +static void +DC390W_Reselected1( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB; + USHORT ioport, wval; + ULONG wlval, flags; + UCHAR bval; + + +#ifdef DC390W_DEBUG0 + printk("Rsel1,"); +#endif + ioport = pACB->IOPortBase; + pDCB = pACB->pActiveDCB; + if(pDCB) + { + pSRB = pDCB->pActiveSRB; + RewaitSRB( pDCB, pSRB ); + } + + wval = (USHORT) (pACB->msgin123[0]); + wval = (wval & 7) << 8; /* get LUN */ + wval |= (USHORT) (inb(ioport+SSID) & 0x0f); /* get ID */ + pDCB = pACB->pLinkDCB; + while( *((PUSHORT) &pDCB->UnitSCSIID) != wval ) + pDCB = pDCB->pNextDCB; + pACB->pActiveDCB = pDCB; + bval = pDCB->DCBscntl3; + outb(bval,ioport+SCNTL3); + bval = pDCB->DCBsxfer; + outb(bval,ioport+SXFER); + bval = pDCB->DCBscntl0; + outb(bval,ioport+SCNTL0); + if(pDCB->MaxCommand > 1) + { + wlval = pACB->jmp_reselecttag; + outl(wlval,(ioport+DSP)); + } + else + { + pSRB = pDCB->pActiveSRB; + if( !pSRB || !(pSRB->SRBState & SRB_DISCONNECT) ) + { + save_flags(flags); + cli(); + pSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB->pNextSRB; + restore_flags(flags); + pSRB->SRBState = SRB_UNEXPECT_RESEL; + pDCB->pActiveSRB = pSRB; + pSRB->MsgOutBuf[0] = MSG_ABORT; + pSRB->__msgout0[0] = 1; + } + pSRB->SRBState &= ~SRB_DISCONNECT; + wlval = pSRB->PhysSRB; + outl(wlval,(ioport+DSA)); + wlval = pSRB->ReturnAddr; + outl(wlval,(ioport+TEMP)); + bval = pSRB->ScratchABuf; + outb(bval,ioport+SCRATCHA); + if( pSRB->SRBState & SRB_UNEXPECT_RESEL ) + wlval = pACB->jmp_set_atn; + else + wlval = pACB->jmp_clear_ack; + outl(wlval,(ioport+DSP)); + } + return; +} + + +static void +DC390W_ReselectedT( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB, psrb1; + USHORT ioport; + ULONG wlval, flags; + UCHAR bval; + +#ifdef DC390W_DEBUG0 + printk("RselT,"); +#endif + ioport = pACB->IOPortBase; + bval = pACB->msgin123[1]; + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pGoingSRB; + psrb1 = pDCB->pGoingLast; + if( !pSRB ) + goto UXP_RSL; + for(;;) + { + if(pSRB->TagNumber != bval) + { + if( pSRB != psrb1 ) + pSRB = pSRB->pNextSRB; + else + goto UXP_RSL; + } + else + break; + } + if( !(pSRB->SRBState & SRB_DISCONNECT) ) + { +UXP_RSL: + save_flags(flags); + cli(); + pSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB->pNextSRB; + restore_flags(flags); + pSRB->SRBState = SRB_UNEXPECT_RESEL; + pDCB->pActiveSRB = pSRB; + pSRB->MsgOutBuf[0] = MSG_ABORT_TAG; + pSRB->__msgout0[0] = 1; + } + else + { + pSRB->SRBState &= ~SRB_DISCONNECT; + pDCB->pActiveSRB = pSRB; + } + wlval = pSRB->PhysSRB; + outl(wlval,(ioport+DSA)); + wlval = pSRB->ReturnAddr; + outl(wlval,(ioport+TEMP)); + bval = pSRB->ScratchABuf; + outb(bval,ioport+SCRATCHA); + if( pSRB->SRBState & SRB_UNEXPECT_RESEL ) + wlval = pACB->jmp_set_atn; + else + wlval = pACB->jmp_clear_ack; + outl(wlval,(ioport+DSP)); + return; +} + + +static void +DC390W_RestorePtr( PACB pACB ) +{ + PSRB pSRB; + USHORT ioport; + ULONG wlval; + + pSRB = pACB->pActiveDCB->pActiveSRB; + wlval = pSRB->ReturnAddr; + ioport = pACB->IOPortBase; + outl(wlval,(ioport+TEMP)); + wlval = inl(ioport+DSP); + outl(wlval,(ioport+DSP)); + return; +} + + +static void +PhaseMismatch( PACB pACB ) +{ + USHORT ioport; + ULONG wlval,swlval; + USHORT wval; + UCHAR bval,phase; + PDCB pDCB; + +#ifdef DC390W_DEBUG0 + printk("Mismatch,"); +#endif + ioport = pACB->IOPortBase; + bval = inb(ioport+SCRATCHA); + if(bval & OVER_RUN_) /* xfer PAD */ + { + bval = inb(ioport+STEST3); + bval |= CLR_SCSI_FIFO; + outb(bval,ioport+STEST3); + bval = CLR_DMA_FIFO; + outb(bval,ioport+CTEST3); + wlval = pACB->jmp_next; /* check phase */ + outl(wlval,(ioport+DSP)); + return; + } + pDCB = pACB->pActiveDCB; + wlval = inl(ioport+DBC); + phase = (UCHAR)((wlval & 0x07000000) >> 24); + wlval &= 0xffffff; /* bytes not xferred */ + if( phase == SCSI_DATA_IN ) + { + swlval = pACB->jmp_din8; + if( pDCB->DCBscntl3 & EN_WIDE_SCSI ) + swlval += jmp_din16; + DataIOcommon(pACB,swlval,wlval); + } + else if( phase == SCSI_DATA_OUT ) + { + wval = (USHORT)inb(ioport+CTEST5); + wval <<= 8; + bval = inb(ioport+DFIFO); + wval |= (USHORT) bval; + wval -= ((USHORT)(wlval & 0xffff)); + wval &= 0x3ff; + wlval += (ULONG)wval; /* # of bytes remains in FIFO */ + bval = inb(ioport+SSTAT0); + if(bval & SODR_LSB_FULL) + wlval++; /* data left in Scsi Output Data Buffer */ + if(bval & SODL_LSB_FULL) + wlval++; /* data left in Scsi Output Data Latch */ + swlval = pACB->jmp_dout8; + if(pDCB->DCBscntl3 & EN_WIDE_SCSI) + { + swlval += jmp_dout16; + bval = inb(ioport+SSTAT2); + if(bval & SODR_MSB_FULL) + wlval++; + if(bval & SODL_MSB_FULL) + wlval++; + } + bval = inb(ioport+STEST3); + bval |= CLR_SCSI_FIFO; + outb(bval,ioport+STEST3); + bval = CLR_DMA_FIFO; + outb(bval,ioport+CTEST3); + DataIOcommon(pACB,swlval,wlval); + } + else + { + bval = inb(ioport+STEST3); + bval |= CLR_SCSI_FIFO; + outb(bval,ioport+STEST3); + bval = CLR_DMA_FIFO; + outb(bval,ioport+CTEST3); + if(phase == SCSI_MSG_OUT) + wlval = pACB->jmp_clear_atn; + else + wlval = pACB->jmp_next; /* check phase */ + outl(wlval,(ioport+DSP)); + } + return; +} + + +static void +DataIOcommon( PACB pACB, ULONG Swlval, ULONG Cwlval ) +{ + /* Swlval - script address */ + /* Cwlval - bytes not xferred */ + PDCB pDCB; + PSRB pSRB; + PSGE Segptr; + USHORT ioport; + ULONG wlval,swlval,dataXferCnt; + UCHAR bval,bvald; + + ioport = pACB->IOPortBase; + wlval = inl((ioport+DSP)); + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; + wlval -= Swlval; + bval = inb(ioport+SBCL); + bval &= 0x07; + if(bval == SCSI_MSG_IN) + { + bval = pDCB->DCBscntl3; + bval &= ~EN_WIDE_SCSI; + outb(bval,ioport+SCNTL3); + bval = inb(ioport+SBDL); + bvald = pDCB->DCBscntl3; /* enable WIDE SCSI */ + outb(bvald,ioport+SCNTL3); + if(bval == MSG_DISCONNECT || bval == MSG_SAVE_PTR) + { + Segptr = (PSGE)((ULONG) &(pSRB->Segment0[0][0]) + wlval); + dataXferCnt = Segptr->SGXLen - Cwlval; + Segptr->SGXLen = Cwlval; /* modified count */ + Segptr->SGXPtr += dataXferCnt; /* modified address */ + swlval = pACB->jmp_table8; + if(pDCB->DCBscntl3 & EN_WIDE_SCSI) + swlval += jmp_table16; + wlval <<= 1; + swlval += wlval; + swlval = swlval - ((MAX_SG_LIST_BUF+1) * 16); + pSRB->ReturnAddr = swlval; + } + } + else if( Cwlval ) /* Remaining not xferred -- UNDER_RUN */ + { + Segptr = (PSGE)((ULONG) &(pSRB->Segment0[0][0]) + wlval); + dataXferCnt = Segptr->SGXLen - Cwlval; + Segptr->SGXLen = Cwlval; /* modified count */ + Segptr->SGXPtr += dataXferCnt; /* modified address */ + swlval = pACB->jmp_table8; + if(pDCB->DCBscntl3 & EN_WIDE_SCSI) + swlval += jmp_table16; + wlval <<= 1; + swlval += wlval; + swlval = swlval - ((MAX_SG_LIST_BUF+1) * 16); + pSRB->RemainSegPtr = swlval; + } +/* pm__1: */ + wlval = pSRB->ReturnAddr; + outl(wlval,(ioport+TEMP)); + wlval = pACB->jmp_next; + outl(wlval,(ioport+DSP)); + return; +} + + +static void +DC390W_CmdCompleted( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB; + USHORT ioport; + ULONG wlval, flags; + UCHAR bval; + +#ifdef DC390W_DEBUG0 + printk("Cmplete,"); +#endif + save_flags(flags); + cli(); + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; + pDCB->pActiveSRB = NULL; + ioport = pACB->IOPortBase; + + bval = inb(ioport+SCRATCHA); + pSRB->ScratchABuf = bval; /* save status */ + bval = pSRB->TagNumber; + if(pDCB->MaxCommand > 1) + pDCB->TagMask &= (~(1 << bval)); /* free tag mask */ + pACB->pActiveDCB = NULL; /* no active device */ + wlval = pACB->jmp_reselect; /* enable reselection */ + outl(wlval,(ioport+DSP)); + SRBdone( pACB, pDCB, pSRB); + restore_flags(flags); + return; +} + + +static void +SRBdone( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + PSRB psrb; + UCHAR bval, bval1, i, j, status; + PSCSICMD pcmd; + PSCSI_INQDATA ptr; + USHORT disable_tag; + ULONG flags; + PSGE ptr1; + PSGL ptr2; + ULONG wlval,swlval; + + pcmd = pSRB->pcmd; + status = pACB->status; + if(pSRB->SRBFlag & AUTO_REQSENSE) + { + pSRB->SRBFlag &= ~AUTO_REQSENSE; + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = SCSI_STAT_CHECKCOND; + if(status == SCSI_STAT_CHECKCOND) + { + pcmd->result = DID_BAD_TARGET << 16; + goto ckc_e; + } + if(pSRB->RetryCnt == 0) + { + *((PULONG) &(pSRB->CmdBlock[0])) = pSRB->Segment0[0][0]; + pSRB->XferredLen = pSRB->Segment0[2][1]; + if( (pSRB->XferredLen) && + (pSRB->XferredLen >= pcmd->underflow) ) + { + pcmd->result |= (DID_OK << 16); + } + else + pcmd->result = (DRIVER_SENSE << 24) | (DRIVER_OK << 16) | + SCSI_STAT_CHECKCOND; + goto ckc_e; + } + else + { + pSRB->RetryCnt--; + pSRB->TargetStatus = 0; + *((PULONG) &(pSRB->CmdBlock[0])) = pSRB->Segment0[0][0]; + *((PULONG) &(pSRB->CmdBlock[4])) = pSRB->Segment0[0][1]; + *((PULONG) &(pSRB->CmdBlock[8])) = pSRB->Segment0[1][0]; + pSRB->__command[0] = pSRB->Segment0[1][1] & 0xff; + pSRB->SGcount = (UCHAR) (pSRB->Segment0[1][1] >> 8); + *((PULONG) &(pSRB->pSegmentList))= pSRB->Segment0[2][0]; + if( pSRB->CmdBlock[0] == TEST_UNIT_READY ) + { + pcmd->result = (DRIVER_SENSE << 24) | (DRIVER_OK << 16) | + SCSI_STAT_CHECKCOND; + goto ckc_e; + } + pcmd->result |= (DRIVER_SENSE << 24); + PrepareSG(pACB,pDCB,pSRB); + pSRB->XferredLen = 0; + DC390W_StartSCSI( pACB, pDCB, pSRB ); + return; + } + } + if( status ) + { + if( status == SCSI_STAT_CHECKCOND) + { + if( !(pSRB->ScratchABuf & SRB_OK) && (pSRB->SGcount) && (pSRB->RemainSegPtr) ) + { + wlval = pSRB->RemainSegPtr; + swlval = pACB->jmp_table8; + if(pDCB->DCBscntl3 & EN_WIDE_SCSI) + swlval += jmp_table16; + swlval -= wlval; + swlval >>= 4; + bval = (UCHAR) swlval; + wlval = 0; + ptr1 = (PSGE) &pSRB->Segment0[MAX_SG_LIST_BUF+1][0]; + for( i=0; i< bval; i++) + { + wlval += ptr1->SGXLen; + ptr1--; + } + + bval = pSRB->SGcount; + swlval = 0; + ptr2 = pSRB->pSegmentList; + for( i=0; i< bval; i++) + { + swlval += ptr2->length; + ptr2++; + } + pSRB->XferredLen = swlval - wlval; + pSRB->RemainSegPtr = 0; +#ifdef DC390W_DEBUG0 + printk("XferredLen=%8x,NotXferLen=%8x,",(UINT) pSRB->XferredLen,(UINT) wlval); +#endif + } + RequestSense( pACB, pDCB, pSRB ); + return; + } + else if( status == SCSI_STAT_QUEUEFULL ) + { + bval = (UCHAR) pDCB->GoingSRBCnt; + bval--; + pDCB->MaxCommand = bval; + RewaitSRB( pDCB, pSRB ); + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + return; + } + else if(status == SCSI_STAT_SEL_TIMEOUT) + { + pSRB->AdaptStatus = H_SEL_TIMEOUT; + pSRB->TargetStatus = 0; + pcmd->result = DID_BAD_TARGET << 16; + } + else if(status == SCSI_STAT_UNEXP_BUS_F) + { + pSRB->AdaptStatus = H_UNEXP_BUS_FREE; + pSRB->TargetStatus = 0; + pcmd->result |= DID_NO_CONNECT << 16; + } + else if(status == SCSI_STAT_BUS_RST_DETECT ) + { + pSRB->AdaptStatus = H_ABORT; + pSRB->TargetStatus = 0; + pcmd->result = DID_RESET << 16; + } + else + { + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = status; + if( pSRB->RetryCnt ) + { + pSRB->RetryCnt--; + pSRB->TargetStatus = 0; + PrepareSG(pACB,pDCB,pSRB); + pSRB->XferredLen = 0; + DC390W_StartSCSI( pACB, pDCB, pSRB ); + return; + } + else + { + pcmd->result |= (DID_ERROR << 16) | (ULONG) (pACB->msgin123[0] << 8) | + (ULONG) status; + } + } + } + else + { + status = pSRB->ScratchABuf; + if(status & OVER_RUN_) + { + pSRB->AdaptStatus = H_OVER_UNDER_RUN; + pSRB->TargetStatus = 0; + pcmd->result |= (DID_OK << 16) | (pACB->msgin123[0] << 8); + } + else /* No error */ + { + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + pcmd->result |= (DID_OK << 16); + } + } +ckc_e: + + if( pACB->scan_devices ) + { + if( pSRB->CmdBlock[0] == TEST_UNIT_READY ) + { + if(pcmd->result != (DID_OK << 16)) + { + if( pcmd->result & SCSI_STAT_CHECKCOND ) + { + goto RTN_OK; + } + else + { + pACB->DCBmap[pcmd->target] &= ~(1 << pcmd->lun); + pPrevDCB->pNextDCB = pACB->pLinkDCB; + if( (pcmd->target == pACB->max_id) && + ((pcmd->lun == 0) || (pcmd->lun == pACB->max_lun)) ) + { + pACB->scan_devices = 0; + } + } + } + else + { +RTN_OK: + pPrevDCB->pNextDCB = pDCB; + pDCB->pNextDCB = pACB->pLinkDCB; + if( (pcmd->target == pACB->max_id) && (pcmd->lun == pACB->max_lun) ) + pACB->scan_devices = END_SCAN; + } + } + else if( pSRB->CmdBlock[0] == INQUIRY ) + { + if( (pcmd->target == pACB->max_id) && + (pcmd->lun == pACB->max_lun) ) + { + pACB->scan_devices = 0; + } + ptr = (PSCSI_INQDATA) (pcmd->request_buffer); + if( pcmd->use_sg ) + ptr = (PSCSI_INQDATA) (((PSGL) ptr)->address); + bval1 = ptr->DevType & SCSI_DEVTYPE; + if(bval1 == SCSI_NODEV) + { + pACB->DCBmap[pcmd->target] &= ~(1 << pcmd->lun); + pPrevDCB->pNextDCB = pACB->pLinkDCB; + } + else + { + pACB->DeviceCnt++; + pPrevDCB = pDCB; + pACB->pDCB_free = (PDCB) ((ULONG) (pACB->pDCB_free) + sizeof( DC390W_DCB )); + pDCB->DevType = bval1; + if(bval1 == TYPE_DISK || bval1 == TYPE_MOD) + { + if( (((ptr->Vers & 0x07) >= 2) || ((ptr->RDF & 0x0F) == 2)) && + (ptr->Flags & SCSI_INQ_CMDQUEUE) && + (pDCB->DevMode & TAG_QUEUING_) && + (pDCB->DevMode & EN_DISCONNECT_) ) + { + disable_tag = 0; + for(i=0; i<BADDEVCNT; i++) + { + for(j=0; j<28; j++) + { + if( ((PUCHAR)ptr)[8+j] != baddevname[i][j]) + break; + } + if(j == 28) + { + disable_tag = 1; + break; + } + } + + if( !disable_tag ) + { + pDCB->MaxCommand = pACB->TagMaxNum; + pDCB->TagMask = 0; + } + } + } + } + } + } + + save_flags( flags ); + cli(); +/* ReleaseSRB( pDCB, pSRB ); */ + + if(pSRB == pDCB->pGoingSRB ) + { + pDCB->pGoingSRB = pSRB->pNextSRB; + } + else + { + psrb = pDCB->pGoingSRB; + while( psrb->pNextSRB != pSRB ) + psrb = psrb->pNextSRB; + psrb->pNextSRB = pSRB->pNextSRB; + if( pSRB == pDCB->pGoingLast ) + pDCB->pGoingLast = psrb; + } + pSRB->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB; + pDCB->GoingSRBCnt--; + + DoWaitingSRB( pACB ); + restore_flags(flags); + +/* Notify cmd done */ + pcmd->scsi_done( pcmd ); + + if( pDCB->QIORBCnt ) + DoNextCmd( pACB, pDCB ); + return; +} + + +static void +DoingSRB_Done( PACB pACB ) +{ + PDCB pDCB, pdcb; + PSRB psrb, psrb2; + USHORT cnt, i; + PSCSICMD pcmd; + + pDCB = pACB->pLinkDCB; + pdcb = pDCB; + do + { + cnt = pdcb->GoingSRBCnt; + psrb = pdcb->pGoingSRB; + for( i=0; i<cnt; i++) + { + psrb2 = psrb->pNextSRB; + pcmd = psrb->pcmd; + pcmd->result = DID_RESET << 16; + +/* ReleaseSRB( pDCB, pSRB ); */ + + psrb->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = psrb; + + pcmd->scsi_done( pcmd ); + psrb = psrb2; + } + pdcb->GoingSRBCnt = 0;; + pdcb->pGoingSRB = NULL; + pdcb->TagMask = 0; + pdcb = pdcb->pNextDCB; + } + while( pdcb != pDCB ); +} + + +static void +DC390W_ResetSCSIBus( PACB pACB ) +{ + USHORT ioport; + UCHAR bval; + ULONG flags; + + save_flags(flags); + cli(); + pACB->ACBFlag |= RESET_DEV; + ioport = pACB->IOPortBase; + bval = ABORT_OP; + outb(bval,ioport+ISTAT); + udelay(25); + bval = 0; + outb(bval,ioport+ISTAT); + + bval = ASSERT_RST; + outb(bval,ioport+SCNTL1); + udelay(25); /* 25 us */ + bval = 0; + outb(bval,ioport+SCNTL1); + restore_flags(flags); + return; +} + + + +static void +DC390W_ResetSCSIBus2( PACB pACB ) +{ + USHORT ioport; + UCHAR bval; + ULONG flags; + + save_flags(flags); + cli(); + ioport = pACB->IOPortBase; + bval = ASSERT_RST; + outb(bval,ioport+SCNTL1); + udelay(25); /* 25 us */ + bval = 0; + outb(bval,ioport+SCNTL1); + restore_flags(flags); + return; +} + + + +static void +DC390W_ScsiRstDetect( PACB pACB ) +{ + ULONG wlval, flags; + USHORT ioport; + UCHAR bval; + + save_flags(flags); + sti(); +#ifdef DC390W_DEBUG0 + printk("Reset_Detect0,"); +#endif +/* delay 1 sec */ + wlval = jiffies + HZ; + while( jiffies < wlval ); +/* USHORT i; + for( i=0; i<1000; i++ ) + udelay(1000); */ + + cli(); + ioport = pACB->IOPortBase; + bval = inb(ioport+STEST3); + bval |= CLR_SCSI_FIFO; + outb(bval,ioport+STEST3); + bval = CLR_DMA_FIFO; + outb(bval,ioport+CTEST3); + + if( pACB->ACBFlag & RESET_DEV ) + pACB->ACBFlag |= RESET_DONE; + else + { + pACB->ACBFlag |= RESET_DETECT; + + ResetDevParam( pACB ); +/* DoingSRB_Done( pACB ); ???? */ + RecoverSRB( pACB ); + pACB->pActiveDCB = NULL; + wlval = pACB->jmp_reselect; + outl(wlval,(ioport+DSP)); + pACB->ACBFlag = 0; + DoWaitingSRB( pACB ); + } + restore_flags(flags); + return; +} + + +static void +RequestSense( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + PSCSICMD pcmd; + + pSRB->SRBFlag |= AUTO_REQSENSE; + pSRB->Segment0[0][0] = *((PULONG) &(pSRB->CmdBlock[0])); + pSRB->Segment0[0][1] = *((PULONG) &(pSRB->CmdBlock[4])); + pSRB->Segment0[1][0] = *((PULONG) &(pSRB->CmdBlock[8])); + pSRB->Segment0[1][1] = pSRB->__command[0] | (pSRB->SGcount << 8); + pSRB->Segment0[2][0] = *((PULONG) &(pSRB->pSegmentList)); + pSRB->Segment0[2][1] = pSRB->XferredLen; + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + + pcmd = pSRB->pcmd; + + pSRB->Segmentx.address = (PUCHAR) &(pcmd->sense_buffer); + pSRB->Segmentx.length = sizeof(pcmd->sense_buffer); + pSRB->pSegmentList = &pSRB->Segmentx; + pSRB->SGcount = 1; + + *((PULONG) &(pSRB->CmdBlock[0])) = 0x00000003; + pSRB->CmdBlock[1] = pDCB->IdentifyMsg << 5; + *((PUSHORT) &(pSRB->CmdBlock[4])) = sizeof(pcmd->sense_buffer); + pSRB->__command[0] = 6; + PrepareSG( pACB, pDCB, pSRB ); + pSRB->XferredLen = 0; + DC390W_StartSCSI( pACB, pDCB, pSRB ); + return; +} + + +static void +DC390W_MessageOut( PACB pACB ) +{ + DC390W_FatalError( pACB ); +} + + +static void +DC390W_FatalError( PACB pACB ) +{ + PSRB pSRB; + PDCB pDCB; + ULONG flags; + +#ifdef DC390W_DEBUG0 + printk("DC390W: Fatal Error!!\n"); +#endif + + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; + if( pSRB->SRBState & SRB_UNEXPECT_RESEL ) + { + save_flags(flags); + cli(); + pSRB->SRBState &= ~SRB_UNEXPECT_RESEL; + pSRB->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB; + pACB->pActiveDCB = NULL; + pDCB->pActiveSRB = NULL; + restore_flags(flags); + DoWaitingSRB( pACB ); + } + else + DC390W_ResetSCSIBus(pACB); + return; +} + + +static void +DC390W_Debug( PACB pACB ) +{ + ULONG wlval; + USHORT ioport; + + ioport = pACB->IOPortBase; + wlval = inl(ioport+DSP); + outl(wlval,(ioport+DSP)); + return; +} + + diff --git a/linux/src/drivers/scsi/scsiiom.c b/linux/src/drivers/scsi/scsiiom.c new file mode 100644 index 00000000..97801d75 --- /dev/null +++ b/linux/src/drivers/scsi/scsiiom.c @@ -0,0 +1,1540 @@ +/*********************************************************************** + * FILE NAME : SCSIIOM.C * + * BY : C.L. Huang, ching@tekram.com.tw * + * Description: Device Driver for Tekram DC-390 (T) PCI SCSI * + * Bus Master Host Adapter * + ***********************************************************************/ + + +static USHORT +DC390_StartSCSI( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + USHORT ioport, rc; + UCHAR bval, bval1, i, cnt; + PUCHAR ptr; + ULONG wlval; + + pSRB->TagNumber = 31; + ioport = pACB->IOPortBase; + bval = pDCB->UnitSCSIID; + outb(bval,ioport+Scsi_Dest_ID); + bval = pDCB->SyncPeriod; + outb(bval,ioport+Sync_Period); + bval = pDCB->SyncOffset; + outb(bval,ioport+Sync_Offset); + bval = pDCB->CtrlR1; + outb(bval,ioport+CtrlReg1); + bval = pDCB->CtrlR3; + outb(bval,ioport+CtrlReg3); + bval = pDCB->CtrlR4; + outb(bval,ioport+CtrlReg4); + bval = CLEAR_FIFO_CMD; /* Flush FIFO */ + outb(bval,ioport+ScsiCmd); + + pSRB->ScsiPhase = SCSI_NOP0; + bval = pDCB->IdentifyMsg; + if( !(pDCB->SyncMode & EN_ATN_STOP) ) + { + if( (pSRB->CmdBlock[0] == INQUIRY) || + (pSRB->CmdBlock[0] == REQUEST_SENSE) || + (pSRB->SRBFlag & AUTO_REQSENSE) ) + { + bval &= 0xBF; /* NO disconnection */ + outb(bval,ioport+ScsiFifo); + bval1 = SELECT_W_ATN; + pSRB->SRBState = SRB_START_; + if( pDCB->SyncMode & SYNC_ENABLE ) + { + if( !(pDCB->IdentifyMsg & 7) || + (pSRB->CmdBlock[0] != INQUIRY) ) + { + bval1 = SEL_W_ATN_STOP; + pSRB->SRBState = SRB_MSGOUT; + } + } + } + else + { + if(pDCB->SyncMode & EN_TAG_QUEUING) + { + outb(bval,ioport+ScsiFifo); + bval = MSG_SIMPLE_QTAG; + outb(bval,ioport+ScsiFifo); + wlval = 1; + bval = 0; + while( wlval & pDCB->TagMask ) + { + wlval = wlval << 1; + bval++; + } + outb(bval,ioport+ScsiFifo); + pDCB->TagMask |= wlval; + pSRB->TagNumber = bval; + bval1 = SEL_W_ATN2; + pSRB->SRBState = SRB_START_; + } + else + { + outb(bval,ioport+ScsiFifo); + bval1 = SELECT_W_ATN; + pSRB->SRBState = SRB_START_; + } + } + + if( pSRB->SRBFlag & AUTO_REQSENSE ) + { + bval = REQUEST_SENSE; + outb(bval,ioport+ScsiFifo); + bval = pDCB->IdentifyMsg << 5; + outb(bval,ioport+ScsiFifo); + bval = 0; + outb(bval,ioport+ScsiFifo); + outb(bval,ioport+ScsiFifo); + bval = sizeof(pSRB->pcmd->sense_buffer); + outb(bval,ioport+ScsiFifo); + bval = 0; + outb(bval,ioport+ScsiFifo); + } + else + { + cnt = pSRB->ScsiCmdLen; + ptr = (PUCHAR) pSRB->CmdBlock; + for(i=0; i<cnt; i++) + { + bval = *ptr++; + outb(bval,ioport+ScsiFifo); + } + } + } + else /* ATN_STOP */ + { + if( (pSRB->CmdBlock[0] == INQUIRY) || + (pSRB->CmdBlock[0] == REQUEST_SENSE) || + (pSRB->SRBFlag & AUTO_REQSENSE) ) + { + bval &= 0xBF; + outb(bval,ioport+ScsiFifo); + bval1 = SELECT_W_ATN; + pSRB->SRBState = SRB_START_; + if( pDCB->SyncMode & SYNC_ENABLE ) + { + if( !(pDCB->IdentifyMsg & 7) || + (pSRB->CmdBlock[0] != INQUIRY) ) + { + bval1 = SEL_W_ATN_STOP; + pSRB->SRBState = SRB_MSGOUT; + } + } + } + else + { + if(pDCB->SyncMode & EN_TAG_QUEUING) + { + outb(bval,ioport+ScsiFifo); + pSRB->MsgOutBuf[0] = MSG_SIMPLE_QTAG; + wlval = 1; + bval = 0; + while( wlval & pDCB->TagMask ) + { + wlval = wlval << 1; + bval++; + } + pDCB->TagMask |= wlval; + pSRB->TagNumber = bval; + pSRB->MsgOutBuf[1] = bval; + pSRB->MsgCnt = 2; + bval1 = SEL_W_ATN_STOP; + pSRB->SRBState = SRB_START_; + } + else + { + outb(bval,ioport+ScsiFifo); + pSRB->MsgOutBuf[0] = MSG_NOP; + pSRB->MsgCnt = 1; + pSRB->SRBState = SRB_START_; + bval1 = SEL_W_ATN_STOP; + } + } + } + bval = inb( ioport+Scsi_Status ); + if( bval & INTERRUPT ) + { + pSRB->SRBState = SRB_READY; + pDCB->TagMask &= ~( 1 << pSRB->TagNumber ); + rc = 1; + } + else + { + pSRB->ScsiPhase = SCSI_NOP1; + pACB->pActiveDCB = pDCB; + pDCB->pActiveSRB = pSRB; + rc = 0; + outb(bval1,ioport+ScsiCmd); + } + return( rc ); +} + + +#ifndef VERSION_ELF_1_2_13 +static void +DC390_Interrupt( int irq, void *dev_id, struct pt_regs *regs) +#else +static void +DC390_Interrupt( int irq, struct pt_regs *regs) +#endif +{ + PACB pACB; + PDCB pDCB; + PSRB pSRB; + USHORT ioport = 0; + USHORT phase, i; + void (*stateV)( PACB, PSRB, PUCHAR ); + UCHAR istate = 0; + UCHAR sstatus=0, istatus; + + pACB = pACB_start; + if( pACB == NULL ) + return; + for( i=0; i < adapterCnt; i++ ) + { + if( pACB->IRQLevel == (UCHAR) irq ) + { + ioport = pACB->IOPortBase; + sstatus = inb( ioport+Scsi_Status ); + if( sstatus & INTERRUPT ) + break; + else + pACB = pACB->pNextACB; + } + else + { + pACB = pACB->pNextACB; + } + } + +#ifdef DC390_DEBUG1 + printk("sstatus=%2x,",sstatus); +#endif + + if( pACB == (PACB )-1 ) + { + printk("DC390: Spurious interrupt detected!\n"); + return; + } + + istate = inb( ioport+Intern_State ); + istatus = inb( ioport+INT_Status ); + +#ifdef DC390_DEBUG1 + printk("Istatus=%2x,",istatus); +#endif + + if(istatus & DISCONNECTED) + { + DC390_Disconnect( pACB ); + return; + } + + if(istatus & RESELECTED) + { + DC390_Reselect( pACB ); + return; + } + + if(istatus & INVALID_CMD) + { + DC390_InvalidCmd( pACB ); + return; + } + + if(istatus & SCSI_RESET) + { + DC390_ScsiRstDetect( pACB ); + return; + } + + if( istatus & (SUCCESSFUL_OP+SERVICE_REQUEST) ) + { + pDCB = pACB->pActiveDCB; + pSRB = pDCB->pActiveSRB; + if( pDCB ) + { + if( pDCB->DCBFlag & ABORT_DEV_ ) + EnableMsgOut( pACB, pSRB ); + } + + phase = (USHORT) pSRB->ScsiPhase; + stateV = (void *) DC390_phase0[phase]; + stateV( pACB, pSRB, &sstatus ); + + pSRB->ScsiPhase = sstatus & 7; + phase = (USHORT) sstatus & 7; + stateV = (void *) DC390_phase1[phase]; + stateV( pACB, pSRB, &sstatus ); + } +} + + +static void +DC390_DataOut_0( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR sstatus, bval; + USHORT ioport; + PSGL psgl; + ULONG ResidCnt, xferCnt; + + ioport = pACB->IOPortBase; + sstatus = *psstatus; + + if( !(pSRB->SRBState & SRB_XFERPAD) ) + { + if( sstatus & PARITY_ERR ) + pSRB->SRBStatus |= PARITY_ERROR; + + if( sstatus & COUNT_2_ZERO ) + { + bval = inb(ioport+DMA_Status); + while( !(bval & DMA_XFER_DONE) ) + bval = inb(ioport+DMA_Status); + pSRB->TotalXferredLen += pSRB->SGToBeXferLen; + pSRB->SGIndex++; + if( pSRB->SGIndex < pSRB->SGcount ) + { + pSRB->pSegmentList++; + psgl = pSRB->pSegmentList; + +#ifndef VERSION_ELF_1_2_13 + pSRB->SGPhysAddr = virt_to_phys( psgl->address ); +#else + pSRB->SGPhysAddr = (ULONG) psgl->address; +#endif + pSRB->SGToBeXferLen = (ULONG) psgl->length; + } + else + pSRB->SGToBeXferLen = 0; + } + else + { + bval = inb( ioport+Current_Fifo ); + bval &= 0x1f; + ResidCnt = (ULONG) inb(ioport+CtcReg_High); + ResidCnt = ResidCnt << 8; + ResidCnt |= (ULONG) inb(ioport+CtcReg_Mid); + ResidCnt = ResidCnt << 8; + ResidCnt |= (ULONG) inb(ioport+CtcReg_Low); + ResidCnt += (ULONG) bval; + + xferCnt = pSRB->SGToBeXferLen - ResidCnt; + pSRB->SGPhysAddr += xferCnt; + pSRB->TotalXferredLen += xferCnt; + pSRB->SGToBeXferLen = ResidCnt; + } + } + bval = WRITE_DIRECTION+DMA_IDLE_CMD; + outb( bval, ioport+DMA_Cmd); +} + +static void +DC390_DataIn_0( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR sstatus, bval; + USHORT i, ioport, residual; + PSGL psgl; + ULONG ResidCnt, xferCnt; + PUCHAR ptr; + + + ioport = pACB->IOPortBase; + sstatus = *psstatus; + + if( !(pSRB->SRBState & SRB_XFERPAD) ) + { + if( sstatus & PARITY_ERR ) + pSRB->SRBStatus |= PARITY_ERROR; + + if( sstatus & COUNT_2_ZERO ) + { + bval = inb(ioport+DMA_Status); + while( !(bval & DMA_XFER_DONE) ) + bval = inb(ioport+DMA_Status); + + bval = READ_DIRECTION+DMA_IDLE_CMD; + outb( bval, ioport+DMA_Cmd); + + pSRB->TotalXferredLen += pSRB->SGToBeXferLen; + pSRB->SGIndex++; + if( pSRB->SGIndex < pSRB->SGcount ) + { + pSRB->pSegmentList++; + psgl = pSRB->pSegmentList; + +#ifndef VERSION_ELF_1_2_13 + pSRB->SGPhysAddr = virt_to_phys( psgl->address ); +#else + pSRB->SGPhysAddr = (ULONG) psgl->address; +#endif + pSRB->SGToBeXferLen = (ULONG) psgl->length; + } + else + pSRB->SGToBeXferLen = 0; + } + else /* phase changed */ + { + residual = 0; + bval = inb(ioport+Current_Fifo); + while( bval & 0x1f ) + { + if( (bval & 0x1f) == 1 ) + { + for(i=0; i< 0x100; i++) + { + bval = inb(ioport+Current_Fifo); + if( !(bval & 0x1f) ) + goto din_1; + else if( i == 0x0ff ) + { + residual = 1; /* ;1 residual byte */ + goto din_1; + } + } + } + else + bval = inb(ioport+Current_Fifo); + } +din_1: + bval = READ_DIRECTION+DMA_BLAST_CMD; + outb(bval, ioport+DMA_Cmd); + for(i=0; i<0x8000; i++) + { + bval = inb(ioport+DMA_Status); + if(bval & BLAST_COMPLETE) + break; + } + bval = READ_DIRECTION+DMA_IDLE_CMD; + outb(bval, ioport+DMA_Cmd); + + ResidCnt = (ULONG) inb(ioport+CtcReg_High); + ResidCnt = ResidCnt << 8; + ResidCnt |= (ULONG) inb(ioport+CtcReg_Mid); + ResidCnt = ResidCnt << 8; + ResidCnt |= (ULONG) inb(ioport+CtcReg_Low); + + xferCnt = pSRB->SGToBeXferLen - ResidCnt; + pSRB->SGPhysAddr += xferCnt; + pSRB->TotalXferredLen += xferCnt; + pSRB->SGToBeXferLen = ResidCnt; + + if( residual ) + { + bval = inb(ioport+ScsiFifo); /* get residual byte */ +#ifndef VERSION_ELF_1_2_13 + ptr = (PUCHAR) phys_to_virt( pSRB->SGPhysAddr ); +#else + ptr = (PUCHAR) pSRB->SGPhysAddr; +#endif + *ptr = bval; + pSRB->SGPhysAddr++; + pSRB->TotalXferredLen++; + pSRB->SGToBeXferLen--; + } + } + } +} + +static void +DC390_Command_0( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ +} + +static void +DC390_Status_0( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR bval; + USHORT ioport; + + ioport = pACB->IOPortBase; + bval = inb(ioport+ScsiFifo); + pSRB->TargetStatus = bval; + bval++; + bval = inb(ioport+ScsiFifo); /* get message */ + pSRB->EndMessage = bval; + + *psstatus = SCSI_NOP0; + pSRB->SRBState = SRB_COMPLETED; + bval = MSG_ACCEPTED_CMD; + outb(bval, ioport+ScsiCmd); +} + +static void +DC390_MsgOut_0( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + if( pSRB->SRBState & (SRB_UNEXPECT_RESEL+SRB_ABORT_SENT) ) + *psstatus = SCSI_NOP0; +} + +static void +DC390_MsgIn_0( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR bval; + USHORT ioport, wval, wval1; + PDCB pDCB; + PSRB psrb; + + ioport = pACB->IOPortBase; + pDCB = pACB->pActiveDCB; + + bval = inb( ioport+ScsiFifo ); + if( !(pSRB->SRBState & SRB_MSGIN_MULTI) ) + { + if(bval == MSG_DISCONNECT) + { + pSRB->SRBState = SRB_DISCONNECT; + } + else if( bval == MSG_SAVE_PTR ) + goto min6; + else if( (bval == MSG_EXTENDED) || ((bval >= MSG_SIMPLE_QTAG) && + (bval <= MSG_ORDER_QTAG)) ) + { + pSRB->SRBState |= SRB_MSGIN_MULTI; + pSRB->MsgInBuf[0] = bval; + pSRB->MsgCnt = 1; + pSRB->pMsgPtr = &pSRB->MsgInBuf[1]; + } + else if(bval == MSG_REJECT_) + { + bval = RESET_ATN_CMD; + outb(bval, ioport+ScsiCmd); + if( pSRB->SRBState & DO_SYNC_NEGO) + goto set_async; + } + else if( bval == MSG_RESTORE_PTR) + goto min6; + else + goto min6; + } + else + { /* minx: */ + + *pSRB->pMsgPtr = bval; + pSRB->MsgCnt++; + pSRB->pMsgPtr++; + if( (pSRB->MsgInBuf[0] >= MSG_SIMPLE_QTAG) && + (pSRB->MsgInBuf[0] <= MSG_ORDER_QTAG) ) + { + if( pSRB->MsgCnt == 2) + { + pSRB->SRBState = 0; + bval = pSRB->MsgInBuf[1]; + pSRB = pDCB->pGoingSRB; + psrb = pDCB->pGoingLast; + if( pSRB ) + { + for( ;; ) + { + if(pSRB->TagNumber != bval) + { + if( pSRB == psrb ) + goto mingx0; + pSRB = pSRB->pNextSRB; + } + else + break; + } + if( pDCB->DCBFlag & ABORT_DEV_ ) + { + pSRB->SRBState = SRB_ABORT_SENT; + EnableMsgOut( pACB, pSRB ); + } + if( !(pSRB->SRBState & SRB_DISCONNECT) ) + goto mingx0; + pDCB->pActiveSRB = pSRB; + pSRB->SRBState = SRB_DATA_XFER; + } + else + { +mingx0: + pSRB = pACB->pTmpSRB; + pSRB->SRBState = SRB_UNEXPECT_RESEL; + pDCB->pActiveSRB = pSRB; + pSRB->MsgOutBuf[0] = MSG_ABORT_TAG; + EnableMsgOut2( pACB, pSRB ); + } + } + } + else if( (pSRB->MsgInBuf[0] == MSG_EXTENDED) && (pSRB->MsgCnt == 5) ) + { + pSRB->SRBState &= ~(SRB_MSGIN_MULTI+DO_SYNC_NEGO); + if( (pSRB->MsgInBuf[1] != 3) || (pSRB->MsgInBuf[2] != 1) ) + { /* reject_msg: */ + pSRB->MsgCnt = 1; + pSRB->MsgInBuf[0] = MSG_REJECT_; + bval = SET_ATN_CMD; + outb(bval, ioport+ScsiCmd); + } + else if( !(pSRB->MsgInBuf[3]) || !(pSRB->MsgInBuf[4]) ) + { +set_async: + pDCB = pSRB->pSRBDCB; + pDCB->SyncMode &= ~(SYNC_ENABLE+SYNC_NEGO_DONE); + pDCB->SyncPeriod = 0; + pDCB->SyncOffset = 0; + pDCB->CtrlR3 = FAST_CLK; /* ;non_fast */ + pDCB->CtrlR4 &= 0x3f; + pDCB->CtrlR4 |= EATER_25NS; /* ; 25ns glitch eater */ + goto re_prog; + } + else + { /* set_sync: */ + + pDCB = pSRB->pSRBDCB; + pDCB->SyncMode |= SYNC_ENABLE+SYNC_NEGO_DONE; + pDCB->SyncOffset &= 0x0f0; + pDCB->SyncOffset |= pSRB->MsgInBuf[4]; + pDCB->NegoPeriod = pSRB->MsgInBuf[3]; + wval = (USHORT) pSRB->MsgInBuf[3]; + wval = wval << 2; + wval--; + wval1 = wval / 25; + if( (wval1 * 25) != wval) + wval1++; + bval = FAST_CLK+FAST_SCSI; + pDCB->CtrlR4 &= 0x3f; + if(wval1 >= 8) + { + wval1--; + bval = FAST_CLK; /* ;fast clock/normal scsi */ + pDCB->CtrlR4 |= EATER_25NS; /* ;25 ns glitch eater */ + } + pDCB->CtrlR3 = bval; + pDCB->SyncPeriod = (UCHAR)wval1; +re_prog: + bval = pDCB->SyncPeriod; + outb(bval, ioport+Sync_Period); + bval = pDCB->SyncOffset; + outb(bval, ioport+Sync_Offset); + bval = pDCB->CtrlR3; + outb(bval, ioport+CtrlReg3); + bval = pDCB->CtrlR4; + outb(bval, ioport+CtrlReg4); + SetXferRate( pACB, pDCB); + } + } + } +min6: + *psstatus = SCSI_NOP0; + bval = MSG_ACCEPTED_CMD; + outb(bval, ioport+ScsiCmd); +} + +static void +DataIO_Comm( PACB pACB, PSRB pSRB, UCHAR ioDir) +{ + PSGL psgl; + UCHAR bval; + USHORT ioport; + ULONG lval; + + + ioport = pACB->IOPortBase; + if( pSRB->SGIndex < pSRB->SGcount ) + { + bval = DMA_IDLE_CMD | ioDir; /* ;+EN_DMA_INT */ + outb( bval, ioport+DMA_Cmd); + if( !pSRB->SGToBeXferLen ) + { + psgl = pSRB->pSegmentList; +#ifndef VERSION_ELF_1_2_13 + pSRB->SGPhysAddr = virt_to_phys( psgl->address ); +#else + pSRB->SGPhysAddr = (ULONG) psgl->address; +#endif + pSRB->SGToBeXferLen = (ULONG) psgl->length; + } + lval = pSRB->SGToBeXferLen; + bval = (UCHAR) lval; + outb(bval,ioport+CtcReg_Low); + lval = lval >> 8; + bval = (UCHAR) lval; + outb(bval,ioport+CtcReg_Mid); + lval = lval >> 8; + bval = (UCHAR) lval; + outb(bval,ioport+CtcReg_High); + + lval = pSRB->SGToBeXferLen; + outl(lval, ioport+DMA_XferCnt); + + lval = pSRB->SGPhysAddr; + outl( lval, ioport+DMA_XferAddr); + + bval = DMA_COMMAND+INFO_XFER_CMD; + outb(bval, ioport+ScsiCmd); + + pSRB->SRBState = SRB_DATA_XFER; + + bval = DMA_IDLE_CMD | ioDir; /* ;+EN_DMA_INT */ + outb(bval, ioport+DMA_Cmd); + + bval = DMA_START_CMD | ioDir; /* ;+EN_DMA_INT */ + outb(bval, ioport+DMA_Cmd); + } + else /* xfer pad */ + { + if( pSRB->SGcount ) + { + pSRB->AdaptStatus = H_OVER_UNDER_RUN; + pSRB->SRBStatus |= OVER_RUN; + } + bval = 0; + outb(bval,ioport+CtcReg_Low); + outb(bval,ioport+CtcReg_Mid); + outb(bval,ioport+CtcReg_High); + + pSRB->SRBState |= SRB_XFERPAD; + bval = DMA_COMMAND+XFER_PAD_BYTE; + outb(bval, ioport+ScsiCmd); +/* + bval = DMA_IDLE_CMD | ioDir; ;+EN_DMA_INT + outb(bval, ioport+DMA_Cmd); + bval = DMA_START_CMD | ioDir; ;+EN_DMA_INT + outb(bval, ioport+DMA_Cmd); +*/ + } +} + + +static void +DC390_DataOutPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR ioDir; + + ioDir = WRITE_DIRECTION; + DataIO_Comm( pACB, pSRB, ioDir); +} + +static void +DC390_DataInPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR ioDir; + + ioDir = READ_DIRECTION; + DataIO_Comm( pACB, pSRB, ioDir); +} + +static void +DC390_CommandPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + PDCB pDCB; + UCHAR bval; + PUCHAR ptr; + USHORT ioport, i, cnt; + + + ioport = pACB->IOPortBase; + bval = RESET_ATN_CMD; + outb(bval, ioport+ScsiCmd); + bval = CLEAR_FIFO_CMD; + outb(bval, ioport+ScsiCmd); + if( !(pSRB->SRBFlag & AUTO_REQSENSE) ) + { + cnt = (USHORT) pSRB->ScsiCmdLen; + ptr = (PUCHAR) pSRB->CmdBlock; + for(i=0; i < cnt; i++) + { + outb(*ptr, ioport+ScsiFifo); + ptr++; + } + } + else + { + bval = REQUEST_SENSE; + outb(bval, ioport+ScsiFifo); + pDCB = pACB->pActiveDCB; + bval = pDCB->IdentifyMsg << 5; + outb(bval, ioport+ScsiFifo); + bval = 0; + outb(bval, ioport+ScsiFifo); + outb(bval, ioport+ScsiFifo); + bval = sizeof(pSRB->pcmd->sense_buffer); + outb(bval, ioport+ScsiFifo); + bval = 0; + outb(bval, ioport+ScsiFifo); + } + pSRB->SRBState = SRB_COMMAND; + bval = INFO_XFER_CMD; + outb(bval, ioport+ScsiCmd); +} + +static void +DC390_StatusPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR bval; + USHORT ioport; + + ioport = pACB->IOPortBase; + bval = CLEAR_FIFO_CMD; + outb(bval, ioport+ScsiCmd); + pSRB->SRBState = SRB_STATUS; + bval = INITIATOR_CMD_CMPLTE; + outb(bval, ioport+ScsiCmd); +} + +static void +DC390_MsgOutPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR bval; + USHORT ioport, i, cnt; + PUCHAR ptr; + PDCB pDCB; + + ioport = pACB->IOPortBase; + bval = CLEAR_FIFO_CMD; + outb(bval, ioport+ScsiCmd); + pDCB = pACB->pActiveDCB; + if( !(pSRB->SRBState & SRB_MSGOUT) ) + { + cnt = pSRB->MsgCnt; + if( cnt ) + { + ptr = (PUCHAR) pSRB->MsgOutBuf; + for(i=0; i < cnt; i++) + { + outb(*ptr, ioport+ScsiFifo); + ptr++; + } + pSRB->MsgCnt = 0; + if( (pDCB->DCBFlag & ABORT_DEV_) && + (pSRB->MsgOutBuf[0] == MSG_ABORT) ) + pSRB->SRBState = SRB_ABORT_SENT; + } + else + { + bval = MSG_ABORT; /* ??? MSG_NOP */ + if( (pSRB->CmdBlock[0] == INQUIRY ) || + (pSRB->CmdBlock[0] == REQUEST_SENSE) || + (pSRB->SRBFlag & AUTO_REQSENSE) ) + { + if( pDCB->SyncMode & SYNC_ENABLE ) + goto mop1; + } + outb(bval, ioport+ScsiFifo); + } + bval = INFO_XFER_CMD; + outb( bval, ioport+ScsiCmd); + } + else + { +mop1: + bval = MSG_EXTENDED; + outb(bval, ioport+ScsiFifo); + bval = 3; /* ;length of extended msg */ + outb(bval, ioport+ScsiFifo); + bval = 1; /* ; sync nego */ + outb(bval, ioport+ScsiFifo); + bval = pDCB->NegoPeriod; + outb(bval, ioport+ScsiFifo); + bval = SYNC_NEGO_OFFSET; + outb(bval, ioport+ScsiFifo); + pSRB->SRBState |= DO_SYNC_NEGO; + bval = INFO_XFER_CMD; + outb(bval, ioport+ScsiCmd); + } +} + +static void +DC390_MsgInPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ + UCHAR bval; + USHORT ioport; + + ioport = pACB->IOPortBase; + bval = CLEAR_FIFO_CMD; + outb(bval, ioport+ScsiCmd); + if( !(pSRB->SRBState & SRB_MSGIN) ) + { + pSRB->SRBState &= SRB_DISCONNECT; + pSRB->SRBState |= SRB_MSGIN; + } + bval = INFO_XFER_CMD; + outb(bval, ioport+ScsiCmd); +} + +static void +DC390_Nop_0( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ +} + +static void +DC390_Nop_1( PACB pACB, PSRB pSRB, PUCHAR psstatus) +{ +} + + +static void +SetXferRate( PACB pACB, PDCB pDCB ) +{ + UCHAR bval; + USHORT cnt, i; + PDCB ptr; + + if( !(pDCB->IdentifyMsg & 0x07) ) + { + if( pACB->scan_devices ) + { + CurrSyncOffset = pDCB->SyncOffset; + } + else + { + ptr = pACB->pLinkDCB; + cnt = pACB->DeviceCnt; + bval = pDCB->UnitSCSIID; + for(i=0; i<cnt; i++) + { + if( ptr->UnitSCSIID == bval ) + { + ptr->SyncPeriod = pDCB->SyncPeriod; + ptr->SyncOffset = pDCB->SyncOffset; + ptr->CtrlR3 = pDCB->CtrlR3; + ptr->CtrlR4 = pDCB->CtrlR4; + ptr->SyncMode = pDCB->SyncMode; + } + ptr = ptr->pNextDCB; + } + } + } + return; +} + + +static void +DC390_Disconnect( PACB pACB ) +{ + PDCB pDCB; + PSRB pSRB, psrb; + ULONG flags; + USHORT ioport, i, cnt; + UCHAR bval; + +#ifdef DC390_DEBUG0 + printk("DISC,"); +#endif + + save_flags(flags); + cli(); + ioport = pACB->IOPortBase; + pDCB = pACB->pActiveDCB; + if (!pDCB) + { +#ifdef DC390_DEBUG0 + printk("ACB:%08lx->ActiveDCB:%08lx !,",(ULONG)pACB,(ULONG)pDCB); +#endif + restore_flags(flags); return; + } + pSRB = pDCB->pActiveSRB; + pACB->pActiveDCB = 0; + pSRB->ScsiPhase = SCSI_NOP0; + bval = EN_SEL_RESEL; + outb(bval, ioport+ScsiCmd); + if( pSRB->SRBState & SRB_UNEXPECT_RESEL ) + { + pSRB->SRBState = 0; + DoWaitingSRB( pACB ); + } + else if( pSRB->SRBState & SRB_ABORT_SENT ) + { + pDCB->TagMask = 0; + pDCB->DCBFlag = 0; + cnt = pDCB->GoingSRBCnt; + pDCB->GoingSRBCnt = 0; + pSRB = pDCB->pGoingSRB; + for( i=0; i < cnt; i++) + { + psrb = pSRB->pNextSRB; + pSRB->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB; + pSRB = psrb; + } + pDCB->pGoingSRB = 0; + DoWaitingSRB( pACB ); + } + else + { + if( (pSRB->SRBState & (SRB_START_+SRB_MSGOUT)) || + !(pSRB->SRBState & (SRB_DISCONNECT+SRB_COMPLETED)) ) + { /* Selection time out */ + if( !(pACB->scan_devices) ) + { + pSRB->SRBState = SRB_READY; + RewaitSRB( pDCB, pSRB); + } + else + { + pSRB->TargetStatus = SCSI_STAT_SEL_TIMEOUT; + goto disc1; + } + } + else if( pSRB->SRBState & SRB_DISCONNECT ) + { + DoWaitingSRB( pACB ); + } + else if( pSRB->SRBState & SRB_COMPLETED ) + { +disc1: + if(pDCB->MaxCommand > 1) + { + bval = pSRB->TagNumber; + pDCB->TagMask &= (~(1 << bval)); /* free tag mask */ + } + pDCB->pActiveSRB = 0; + pSRB->SRBState = SRB_FREE; + SRBdone( pACB, pDCB, pSRB); + } + } + restore_flags(flags); + return; +} + + +static void +DC390_Reselect( PACB pACB ) +{ + PDCB pDCB, pdcb; + PSRB pSRB; + USHORT ioport, wval; + UCHAR bval, bval1; + + +#ifdef DC390_DEBUG0 + printk("RSEL,"); +#endif + ioport = pACB->IOPortBase; + pDCB = pACB->pActiveDCB; + if( pDCB ) + { /* Arbitration lost but Reselection win */ + pSRB = pDCB->pActiveSRB; + if( !( pACB->scan_devices ) ) + { + pSRB->SRBState = SRB_READY; + RewaitSRB( pDCB, pSRB); + } + } + bval = inb(ioport+ScsiFifo); /* get ID */ + bval = bval ^ pACB->HostID_Bit; + wval = 0; + bval1 = 1; + for(;;) + { + if( !(bval & bval1) ) + { + bval1 = bval1 << 1; + wval++; + } + else + break; + } + wval |= ( (USHORT) inb(ioport+ScsiFifo) & 7) << 8; /* get LUN */ + pDCB = pACB->pLinkDCB; + pdcb = pDCB; + while( wval != *((PUSHORT) &pDCB->UnitSCSIID) ) + { + pDCB = pDCB->pNextDCB; + if( pDCB == pdcb ) + return; + } + pACB->pActiveDCB = pDCB; + if( pDCB->SyncMode & EN_TAG_QUEUING ) + { + pSRB = pACB->pTmpSRB; + pDCB->pActiveSRB = pSRB; + } + else + { + pSRB = pDCB->pActiveSRB; + if( !pSRB || !(pSRB->SRBState & SRB_DISCONNECT) ) + { + pSRB= pACB->pTmpSRB; + pSRB->SRBState = SRB_UNEXPECT_RESEL; + pDCB->pActiveSRB = pSRB; + EnableMsgOut( pACB, pSRB ); + } + else + { + if( pDCB->DCBFlag & ABORT_DEV_ ) + { + pSRB->SRBState = SRB_ABORT_SENT; + EnableMsgOut( pACB, pSRB ); + } + else + pSRB->SRBState = SRB_DATA_XFER; + } + } + pSRB->ScsiPhase = SCSI_NOP0; + bval = pDCB->UnitSCSIID; + outb( bval, ioport+Scsi_Dest_ID); + bval = pDCB->SyncPeriod; + outb(bval, ioport+Sync_Period); + bval = pDCB->SyncOffset; + outb( bval, ioport+Sync_Offset); + bval = pDCB->CtrlR1; + outb(bval, ioport+CtrlReg1); + bval = pDCB->CtrlR3; + outb(bval, ioport+CtrlReg3); + bval = pDCB->CtrlR4; /* ; Glitch eater */ + outb(bval, ioport+CtrlReg4); + bval = MSG_ACCEPTED_CMD; /* ;to rls the /ACK signal */ + outb(bval, ioport+ScsiCmd); +} + + +static void +SRBdone( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + PSRB psrb; + UCHAR bval, bval1, i, j, status; + PSCSICMD pcmd; + PSCSI_INQDATA ptr; + USHORT disable_tag; + ULONG flags; + PSGL ptr2; + ULONG swlval; + + pcmd = pSRB->pcmd; + status = pSRB->TargetStatus; + if(pSRB->SRBFlag & AUTO_REQSENSE) + { + pSRB->SRBFlag &= ~AUTO_REQSENSE; + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = SCSI_STAT_CHECKCOND; + if(status == SCSI_STAT_CHECKCOND) + { + pcmd->result = DID_BAD_TARGET << 16; + goto ckc_e; + } + if(pSRB->RetryCnt == 0) + { + *((PULONG) &(pSRB->CmdBlock[0])) = pSRB->Segment0[0]; + pSRB->TotalXferredLen = pSRB->Segment1[1]; + if( (pSRB->TotalXferredLen) && + (pSRB->TotalXferredLen >= pcmd->underflow) ) + pcmd->result |= (DID_OK << 16); + else + pcmd->result = (DRIVER_SENSE << 24) | (DRIVER_OK << 16) | + SCSI_STAT_CHECKCOND; +#ifdef DC390_DEBUG0 + printk("Cmd=%2x,Result=%8x,XferL=%8x,",pSRB->CmdBlock[0], + (UINT) pcmd->result, (UINT) pSRB->TotalXferredLen); +#endif + goto ckc_e; + } + else + { + pSRB->RetryCnt--; + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + *((PULONG) &(pSRB->CmdBlock[0])) = pSRB->Segment0[0]; + *((PULONG) &(pSRB->CmdBlock[4])) = pSRB->Segment0[1]; + if( pSRB->CmdBlock[0] == TEST_UNIT_READY ) + { + pcmd->result = (DRIVER_SENSE << 24) | (DRIVER_OK << 16) | + SCSI_STAT_CHECKCOND; + goto ckc_e; + } + pcmd->result |= (DRIVER_SENSE << 24); + pSRB->SGcount = (UCHAR) pSRB->Segment1[0]; + pSRB->ScsiCmdLen = (UCHAR) (pSRB->Segment1[0] >> 8); + pSRB->SGIndex = 0; + pSRB->TotalXferredLen = 0; + pSRB->SGToBeXferLen = 0; + if( pcmd->use_sg ) + pSRB->pSegmentList = (PSGL) pcmd->request_buffer; + else if( pcmd->request_buffer ) + { + pSRB->pSegmentList = (PSGL) &pSRB->Segmentx; + pSRB->Segmentx.address = (PUCHAR) pcmd->request_buffer; + pSRB->Segmentx.length = pcmd->request_bufflen; + } + if( DC390_StartSCSI( pACB, pDCB, pSRB ) ) + RewaitSRB( pDCB, pSRB ); + return; + } + } + if( status ) + { + if( status == SCSI_STAT_CHECKCOND) + { + if( (pSRB->SGIndex < pSRB->SGcount) && (pSRB->SGcount) && (pSRB->SGToBeXferLen) ) + { + bval = pSRB->SGcount; + swlval = 0; + ptr2 = pSRB->pSegmentList; + for( i=pSRB->SGIndex; i < bval; i++) + { + swlval += ptr2->length; + ptr2++; + } +#ifdef DC390_DEBUG0 + printk("XferredLen=%8x,NotXferLen=%8x,", + (UINT) pSRB->TotalXferredLen, (UINT) swlval); +#endif + } + RequestSense( pACB, pDCB, pSRB ); + return; + } + else if( status == SCSI_STAT_QUEUEFULL ) + { + bval = (UCHAR) pDCB->GoingSRBCnt; + bval--; + pDCB->MaxCommand = bval; + RewaitSRB( pDCB, pSRB ); + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + return; + } + else if(status == SCSI_STAT_SEL_TIMEOUT) + { + pSRB->AdaptStatus = H_SEL_TIMEOUT; + pSRB->TargetStatus = 0; + pcmd->result = DID_BAD_TARGET << 16; + } + else + { + pSRB->AdaptStatus = 0; + if( pSRB->RetryCnt ) + { + pSRB->RetryCnt--; + pSRB->TargetStatus = 0; + pSRB->SGIndex = 0; + pSRB->TotalXferredLen = 0; + pSRB->SGToBeXferLen = 0; + if( pcmd->use_sg ) + pSRB->pSegmentList = (PSGL) pcmd->request_buffer; + else if( pcmd->request_buffer ) + { + pSRB->pSegmentList = (PSGL) &pSRB->Segmentx; + pSRB->Segmentx.address = (PUCHAR) pcmd->request_buffer; + pSRB->Segmentx.length = pcmd->request_bufflen; + } + if( DC390_StartSCSI( pACB, pDCB, pSRB ) ) + RewaitSRB( pDCB, pSRB ); + return; + } + else + { + pcmd->result |= (DID_ERROR << 16) | (ULONG) (pSRB->EndMessage << 8) | + (ULONG) status; + } + } + } + else + { + status = pSRB->AdaptStatus; + if(status & H_OVER_UNDER_RUN) + { + pSRB->TargetStatus = 0; + pcmd->result |= (DID_OK << 16) | (pSRB->EndMessage << 8); + } + else if( pSRB->SRBStatus & PARITY_ERROR) + { + pcmd->result |= (DID_PARITY << 16) | (pSRB->EndMessage << 8); + } + else /* No error */ + { + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + pcmd->result |= (DID_OK << 16); + } + } + +ckc_e: + if( pACB->scan_devices ) + { + if( pSRB->CmdBlock[0] == TEST_UNIT_READY ) + { + if(pcmd->result != (DID_OK << 16)) + { + if( pcmd->result & SCSI_STAT_CHECKCOND ) + { + goto RTN_OK; + } + else + { + pACB->DCBmap[pcmd->target] &= ~(1 << pcmd->lun); + pPrevDCB->pNextDCB = pACB->pLinkDCB; + if( (pcmd->target == pACB->max_id) && + ((pcmd->lun == 0) || (pcmd->lun == pACB->max_lun)) ) + { + pACB->scan_devices = 0; + } + } + } + else + { +RTN_OK: + pPrevDCB->pNextDCB = pDCB; + pDCB->pNextDCB = pACB->pLinkDCB; + if( (pcmd->target == pACB->max_id) && (pcmd->lun == pACB->max_lun) ) + pACB->scan_devices = END_SCAN; + } + } + else if( pSRB->CmdBlock[0] == INQUIRY ) + { + if( (pcmd->target == pACB->max_id) && + (pcmd->lun == pACB->max_lun) ) + { + pACB->scan_devices = 0; + } + ptr = (PSCSI_INQDATA) (pcmd->request_buffer); + if( pcmd->use_sg ) + ptr = (PSCSI_INQDATA) (((PSGL) ptr)->address); + bval1 = ptr->DevType & SCSI_DEVTYPE; + if(bval1 == SCSI_NODEV) + { + pACB->DCBmap[pcmd->target] &= ~(1 << pcmd->lun); + pPrevDCB->pNextDCB = pACB->pLinkDCB; + } + else + { + pACB->DeviceCnt++; + pPrevDCB = pDCB; + pACB->pDCB_free = (PDCB) ((ULONG) (pACB->pDCB_free) + sizeof( DC390_DCB )); + pDCB->DevType = bval1; + if(bval1 == TYPE_DISK || bval1 == TYPE_MOD) + { + if( (((ptr->Vers & 0x07) >= 2) || ((ptr->RDF & 0x0F) == 2)) && + (ptr->Flags & SCSI_INQ_CMDQUEUE) && + (pDCB->DevMode & TAG_QUEUING_) && + (pDCB->DevMode & EN_DISCONNECT_) ) + { + disable_tag = 0; + for(i=0; i<BADDEVCNT; i++) + { + for(j=0; j<28; j++) + { + if( ((PUCHAR)ptr)[8+j] != baddevname1[i][j]) + break; + } + if(j == 28) + { + disable_tag = 1; + break; + } + } + + if( !disable_tag ) + { + pDCB->MaxCommand = pACB->TagMaxNum; + pDCB->SyncMode |= EN_TAG_QUEUING; + pDCB->TagMask = 0; + } + else + { + pDCB->SyncMode |= EN_ATN_STOP; + } + } + } + } + } + } + + save_flags( flags ); + cli(); +/* ReleaseSRB( pDCB, pSRB ); */ + + if(pSRB == pDCB->pGoingSRB ) + { + pDCB->pGoingSRB = pSRB->pNextSRB; + } + else + { + psrb = pDCB->pGoingSRB; + while( psrb->pNextSRB != pSRB ) + psrb = psrb->pNextSRB; + psrb->pNextSRB = pSRB->pNextSRB; + if( pSRB == pDCB->pGoingLast ) + pDCB->pGoingLast = psrb; + } + pSRB->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB; + pDCB->GoingSRBCnt--; + + DoWaitingSRB( pACB ); + restore_flags(flags); + +/* Notify cmd done */ + pcmd->scsi_done( pcmd ); + + if( pDCB->QIORBCnt ) + DoNextCmd( pACB, pDCB ); + return; +} + + +static void +DoingSRB_Done( PACB pACB ) +{ + PDCB pDCB, pdcb; + PSRB psrb, psrb2; + USHORT cnt, i; + PSCSICMD pcmd; + + pDCB = pACB->pLinkDCB; + pdcb = pDCB; + do + { + cnt = pdcb->GoingSRBCnt; + psrb = pdcb->pGoingSRB; + for( i=0; i<cnt; i++) + { + psrb2 = psrb->pNextSRB; + pcmd = psrb->pcmd; + pcmd->result = DID_RESET << 16; + +/* ReleaseSRB( pDCB, pSRB ); */ + + psrb->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = psrb; + + pcmd->scsi_done( pcmd ); + psrb = psrb2; + } + pdcb->GoingSRBCnt = 0;; + pdcb->pGoingSRB = NULL; + pdcb->TagMask = 0; + pdcb = pdcb->pNextDCB; + } + while( pdcb != pDCB ); +} + + +static void +DC390_ResetSCSIBus( PACB pACB ) +{ + USHORT ioport; + UCHAR bval; + ULONG flags; + + save_flags(flags); + cli(); + pACB->ACBFlag |= RESET_DEV; + ioport = pACB->IOPortBase; + + bval = DMA_IDLE_CMD; + outb(bval,ioport+DMA_Cmd); + + bval = RST_SCSI_BUS_CMD; + outb(bval,ioport+ScsiCmd); + + restore_flags(flags); + return; +} + + +static void +DC390_ScsiRstDetect( PACB pACB ) +{ + ULONG wlval, flags; + USHORT ioport; + UCHAR bval; + +#ifdef DC390_DEBUG0 + printk("RST_DETEC"); +#endif + save_flags(flags); + sti(); + wlval = jiffies + HZ; + while( jiffies < wlval ); /* delay 1 sec */ + + cli(); + ioport = pACB->IOPortBase; + bval = DMA_IDLE_CMD; + outb(bval,ioport+DMA_Cmd); + bval = CLEAR_FIFO_CMD; + outb(bval,ioport+ScsiCmd); + + if( pACB->ACBFlag & RESET_DEV ) + pACB->ACBFlag |= RESET_DONE; + else + { + pACB->ACBFlag |= RESET_DETECT; + + ResetDevParam( pACB ); +/* DoingSRB_Done( pACB ); ???? */ + RecoverSRB( pACB ); + pACB->pActiveDCB = NULL; + pACB->ACBFlag = 0; + DoWaitingSRB( pACB ); + } + restore_flags(flags); + return; +} + + +static void +RequestSense( PACB pACB, PDCB pDCB, PSRB pSRB ) +{ + PSCSICMD pcmd; + + pSRB->SRBFlag |= AUTO_REQSENSE; + pSRB->Segment0[0] = *((PULONG) &(pSRB->CmdBlock[0])); + pSRB->Segment0[1] = *((PULONG) &(pSRB->CmdBlock[4])); + pSRB->Segment1[0] = (ULONG) ((pSRB->ScsiCmdLen << 8) + pSRB->SGcount); + pSRB->Segment1[1] = pSRB->TotalXferredLen; + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + + pcmd = pSRB->pcmd; + + pSRB->Segmentx.address = (PUCHAR) &(pcmd->sense_buffer); + pSRB->Segmentx.length = sizeof(pcmd->sense_buffer); + pSRB->pSegmentList = &pSRB->Segmentx; + pSRB->SGcount = 1; + pSRB->SGIndex = 0; + + *((PULONG) &(pSRB->CmdBlock[0])) = 0x00000003; + pSRB->CmdBlock[1] = pDCB->IdentifyMsg << 5; + *((PUSHORT) &(pSRB->CmdBlock[4])) = sizeof(pcmd->sense_buffer); + pSRB->ScsiCmdLen = 6; + + pSRB->TotalXferredLen = 0; + pSRB->SGToBeXferLen = 0; + if( DC390_StartSCSI( pACB, pDCB, pSRB ) ) + RewaitSRB( pDCB, pSRB ); +} + + +static void +EnableMsgOut2( PACB pACB, PSRB pSRB ) +{ + USHORT ioport; + UCHAR bval; + + ioport = pACB->IOPortBase; + pSRB->MsgCnt = 1; + bval = SET_ATN_CMD; + outb(bval, ioport+ScsiCmd); +} + + +static void +EnableMsgOut( PACB pACB, PSRB pSRB ) +{ + pSRB->MsgOutBuf[0] = MSG_ABORT; + EnableMsgOut2( pACB, pSRB ); +} + + +static void +DC390_InvalidCmd( PACB pACB ) +{ + UCHAR bval; + USHORT ioport; + PSRB pSRB; + + pSRB = pACB->pActiveDCB->pActiveSRB; + if( pSRB->SRBState & (SRB_START_+SRB_MSGOUT) ) + { + ioport = pACB->IOPortBase; + bval = CLEAR_FIFO_CMD; + outb(bval,(ioport+ScsiCmd)); + } +} + diff --git a/linux/src/drivers/scsi/sd.c b/linux/src/drivers/scsi/sd.c new file mode 100644 index 00000000..c647ab44 --- /dev/null +++ b/linux/src/drivers/scsi/sd.c @@ -0,0 +1,1635 @@ +/* + * sd.c Copyright (C) 1992 Drew Eckhardt + * Copyright (C) 1993, 1994, 1995 Eric Youngdale + * + * Linux scsi disk driver + * Initial versions: Drew Eckhardt + * Subsequent revisions: Eric Youngdale + * + * <drew@colorado.edu> + * + * Modified by Eric Youngdale ericy@cais.com to + * add scatter-gather, multiple outstanding request, and other + * enhancements. + * + * Modified by Eric Youngdale eric@aib.com to support loadable + * low-level scsi drivers. + */ + +#include <linux/module.h> +#ifdef MODULE +/* + * This is a variable in scsi.c that is set when we are processing something + * after boot time. By definition, this is true when we are a loadable module + * ourselves. + */ +#define MODULE_FLAG 1 +#else +#define MODULE_FLAG scsi_loadable_module_flag +#endif /* MODULE */ + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/interrupt.h> + +#include <asm/system.h> + +#define MAJOR_NR SCSI_DISK_MAJOR +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" +#include <scsi/scsi_ioctl.h> +#include "constants.h" + +#include <linux/genhd.h> + +/* + * static const char RCSid[] = "$Header:"; + */ + +#define MAX_RETRIES 5 + +/* + * Time out in seconds for disks and Magneto-opticals (which are slower). + */ + +#define SD_TIMEOUT (20 * HZ) +#define SD_MOD_TIMEOUT (25 * HZ) + +#define CLUSTERABLE_DEVICE(SC) (SC->host->use_clustering && \ + SC->device->type != TYPE_MOD) + +struct hd_struct * sd; + +Scsi_Disk * rscsi_disks = NULL; +static int * sd_sizes; +static int * sd_blocksizes; +static int * sd_hardsizes; /* Hardware sector size */ + +extern int sd_ioctl(struct inode *, struct file *, unsigned int, unsigned long); + +static int check_scsidisk_media_change(kdev_t); +static int fop_revalidate_scsidisk(kdev_t); + +static int sd_init_onedisk(int); + +static void requeue_sd_request (Scsi_Cmnd * SCpnt); + +static int sd_init(void); +static void sd_finish(void); +static int sd_attach(Scsi_Device *); +static int sd_detect(Scsi_Device *); +static void sd_detach(Scsi_Device *); + +struct Scsi_Device_Template sd_template = +{ NULL, "disk", "sd", NULL, TYPE_DISK, + SCSI_DISK_MAJOR, 0, 0, 0, 1, + sd_detect, sd_init, + sd_finish, sd_attach, sd_detach +}; + +static int sd_open(struct inode * inode, struct file * filp) +{ + int target; + target = DEVICE_NR(inode->i_rdev); + + if(target >= sd_template.dev_max || !rscsi_disks[target].device) + return -ENXIO; /* No such device */ + + /* + * Make sure that only one process can do a check_change_disk at one time. + * This is also used to lock out further access when the partition table + * is being re-read. + */ + + while (rscsi_disks[target].device->busy) + barrier(); + if(rscsi_disks[target].device->removable) { + check_disk_change(inode->i_rdev); + + /* + * If the drive is empty, just let the open fail. + */ + if ( !rscsi_disks[target].ready ) + return -ENXIO; + + /* + * Similarly, if the device has the write protect tab set, + * have the open fail if the user expects to be able to write + * to the thing. + */ + if ( (rscsi_disks[target].write_prot) && (filp->f_mode & 2) ) + return -EROFS; + } + + /* + * See if we are requesting a non-existent partition. Do this + * after checking for disk change. + */ + if(sd_sizes[MINOR(inode->i_rdev)] == 0) + return -ENXIO; + + if(rscsi_disks[target].device->removable) + if(!rscsi_disks[target].device->access_count) + sd_ioctl(inode, NULL, SCSI_IOCTL_DOORLOCK, 0); + + rscsi_disks[target].device->access_count++; + if (rscsi_disks[target].device->host->hostt->usage_count) + (*rscsi_disks[target].device->host->hostt->usage_count)++; + if(sd_template.usage_count) (*sd_template.usage_count)++; + return 0; +} + +static void sd_release(struct inode * inode, struct file * file) +{ + int target; + fsync_dev(inode->i_rdev); + + target = DEVICE_NR(inode->i_rdev); + + rscsi_disks[target].device->access_count--; + if (rscsi_disks[target].device->host->hostt->usage_count) + (*rscsi_disks[target].device->host->hostt->usage_count)--; + if(sd_template.usage_count) (*sd_template.usage_count)--; + + if(rscsi_disks[target].device->removable) { + if(!rscsi_disks[target].device->access_count) + sd_ioctl(inode, NULL, SCSI_IOCTL_DOORUNLOCK, 0); + } +} + +static void sd_geninit(struct gendisk *); + +static struct file_operations sd_fops = { + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + sd_ioctl, /* ioctl */ + NULL, /* mmap */ + sd_open, /* open code */ + sd_release, /* release */ + block_fsync, /* fsync */ + NULL, /* fasync */ + check_scsidisk_media_change, /* Disk change */ + fop_revalidate_scsidisk /* revalidate */ +}; + +static struct gendisk sd_gendisk = { + MAJOR_NR, /* Major number */ + "sd", /* Major name */ + 4, /* Bits to shift to get real from partition */ + 1 << 4, /* Number of partitions per real */ + 0, /* maximum number of real */ + sd_geninit, /* init function */ + NULL, /* hd struct */ + NULL, /* block sizes */ + 0, /* number */ + NULL, /* internal */ + NULL /* next */ +}; + +static void sd_geninit (struct gendisk *ignored) +{ + int i; + + for (i = 0; i < sd_template.dev_max; ++i) + if(rscsi_disks[i].device) + sd[i << 4].nr_sects = rscsi_disks[i].capacity; +#if 0 + /* No longer needed - we keep track of this as we attach/detach */ + sd_gendisk.nr_real = sd_template.dev_max; +#endif +} + +/* + * rw_intr is the interrupt routine for the device driver. + * It will be notified on the end of a SCSI read / write, and + * will take one of several actions based on success or failure. + */ + +static void rw_intr (Scsi_Cmnd *SCpnt) +{ + int result = SCpnt->result; + int this_count = SCpnt->bufflen >> 9; + int good_sectors = (result == 0 ? this_count : 0); + int block_sectors = 1; + +#ifdef DEBUG + printk("sd%c : rw_intr(%d, %d)\n", 'a' + MINOR(SCpnt->request.rq_dev), + SCpnt->host->host_no, result); +#endif + + /* + Handle MEDIUM ERRORs that indicate partial success. Since this is a + relatively rare error condition, no care is taken to avoid unnecessary + additional work such as memcpy's that could be avoided. + */ + + if (driver_byte(result) != 0 && /* An error occurred */ + SCpnt->sense_buffer[0] == 0xF0 && /* Sense data is valid */ + SCpnt->sense_buffer[2] == MEDIUM_ERROR) + { + long error_sector = (SCpnt->sense_buffer[3] << 24) | + (SCpnt->sense_buffer[4] << 16) | + (SCpnt->sense_buffer[5] << 8) | + SCpnt->sense_buffer[6]; + int sector_size = + rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].sector_size; + if (SCpnt->request.bh != NULL) + block_sectors = SCpnt->request.bh->b_size >> 9; + if (sector_size == 1024) + { + error_sector <<= 1; + if (block_sectors < 2) block_sectors = 2; + } + else if (sector_size == 256) + error_sector >>= 1; + error_sector -= sd[MINOR(SCpnt->request.rq_dev)].start_sect; + error_sector &= ~ (block_sectors - 1); + good_sectors = error_sector - SCpnt->request.sector; + if (good_sectors < 0 || good_sectors >= this_count) + good_sectors = 0; + } + + /* + * Handle RECOVERED ERRORs that indicate success after recovery action + * by the target device. + */ + + if (SCpnt->sense_buffer[0] == 0xF0 && /* Sense data is valid */ + SCpnt->sense_buffer[2] == RECOVERED_ERROR) + { + printk("scsidisk recovered I/O error: dev %s, sector %lu, absolute sector %lu\n", + kdevname(SCpnt->request.rq_dev), SCpnt->request.sector, + SCpnt->request.sector + sd[MINOR(SCpnt->request.rq_dev)].start_sect); + good_sectors = this_count; + result = 0; + } + + /* + * First case : we assume that the command succeeded. One of two things + * will happen here. Either we will be finished, or there will be more + * sectors that we were unable to read last time. + */ + + if (good_sectors > 0) { + +#ifdef DEBUG + printk("sd%c : %d sectors remain.\n", 'a' + MINOR(SCpnt->request.rq_dev), + SCpnt->request.nr_sectors); + printk("use_sg is %d\n ",SCpnt->use_sg); +#endif + if (SCpnt->use_sg) { + struct scatterlist * sgpnt; + int i; + sgpnt = (struct scatterlist *) SCpnt->buffer; + for(i=0; i<SCpnt->use_sg; i++) { +#ifdef DEBUG + printk(":%x %x %d\n",sgpnt[i].alt_address, sgpnt[i].address, + sgpnt[i].length); +#endif + if (sgpnt[i].alt_address) { + if (SCpnt->request.cmd == READ) + memcpy(sgpnt[i].alt_address, sgpnt[i].address, + sgpnt[i].length); + scsi_free(sgpnt[i].address, sgpnt[i].length); + } + } + + /* Free list of scatter-gather pointers */ + scsi_free(SCpnt->buffer, SCpnt->sglist_len); + } else { + if (SCpnt->buffer != SCpnt->request.buffer) { +#ifdef DEBUG + printk("nosg: %x %x %d\n",SCpnt->request.buffer, SCpnt->buffer, + SCpnt->bufflen); +#endif + if (SCpnt->request.cmd == READ) + memcpy(SCpnt->request.buffer, SCpnt->buffer, + SCpnt->bufflen); + scsi_free(SCpnt->buffer, SCpnt->bufflen); + } + } + /* + * If multiple sectors are requested in one buffer, then + * they will have been finished off by the first command. + * If not, then we have a multi-buffer command. + */ + if (SCpnt->request.nr_sectors > this_count) + { + SCpnt->request.errors = 0; + + if (!SCpnt->request.bh) + { +#ifdef DEBUG + printk("sd%c : handling page request, no buffer\n", + 'a' + MINOR(SCpnt->request.rq_dev)); +#endif + /* + * The SCpnt->request.nr_sectors field is always done in + * 512 byte sectors, even if this really isn't the case. + */ + panic("sd.c: linked page request (%lx %x)", + SCpnt->request.sector, this_count); + } + } + SCpnt = end_scsi_request(SCpnt, 1, good_sectors); + if (result == 0) + { + requeue_sd_request(SCpnt); + return; + } + } + + if (good_sectors == 0) { + + /* Free up any indirection buffers we allocated for DMA purposes. */ + if (SCpnt->use_sg) { + struct scatterlist * sgpnt; + int i; + sgpnt = (struct scatterlist *) SCpnt->buffer; + for(i=0; i<SCpnt->use_sg; i++) { +#ifdef DEBUG + printk("err: %x %x %d\n",SCpnt->request.buffer, SCpnt->buffer, + SCpnt->bufflen); +#endif + if (sgpnt[i].alt_address) { + scsi_free(sgpnt[i].address, sgpnt[i].length); + } + } + scsi_free(SCpnt->buffer, SCpnt->sglist_len); /* Free list of scatter-gather pointers */ + } else { +#ifdef DEBUG + printk("nosgerr: %x %x %d\n",SCpnt->request.buffer, SCpnt->buffer, + SCpnt->bufflen); +#endif + if (SCpnt->buffer != SCpnt->request.buffer) + scsi_free(SCpnt->buffer, SCpnt->bufflen); + } + } + + /* + * Now, if we were good little boys and girls, Santa left us a request + * sense buffer. We can extract information from this, so we + * can choose a block to remap, etc. + */ + + if (driver_byte(result) != 0) { + if (suggestion(result) == SUGGEST_REMAP) { +#ifdef REMAP + /* + * Not yet implemented. A read will fail after being remapped, + * a write will call the strategy routine again. + */ + if rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].remap + { + result = 0; + } + else +#endif + } + + if ((SCpnt->sense_buffer[0] & 0x7f) == 0x70) { + if ((SCpnt->sense_buffer[2] & 0xf) == UNIT_ATTENTION) { + if(rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].device->removable) { + /* detected disc change. set a bit and quietly refuse + * further access. + */ + rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].device->changed = 1; + SCpnt = end_scsi_request(SCpnt, 0, this_count); + requeue_sd_request(SCpnt); + return; + } + else + { + /* + * Must have been a power glitch, or a bus reset. + * Could not have been a media change, so we just retry + * the request and see what happens. + */ + requeue_sd_request(SCpnt); + return; + } + } + } + + + /* If we had an ILLEGAL REQUEST returned, then we may have + * performed an unsupported command. The only thing this should be + * would be a ten byte read where only a six byte read was supported. + * Also, on a system where READ CAPACITY failed, we have read past + * the end of the disk. + */ + + if (SCpnt->sense_buffer[2] == ILLEGAL_REQUEST) { + if (rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].ten) { + rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].ten = 0; + requeue_sd_request(SCpnt); + result = 0; + } else { + /* ???? */ + } + } + + if (SCpnt->sense_buffer[2] == MEDIUM_ERROR) { + printk("scsi%d: MEDIUM ERROR on channel %d, id %d, lun %d, CDB: ", + SCpnt->host->host_no, (int) SCpnt->channel, + (int) SCpnt->target, (int) SCpnt->lun); + print_command(SCpnt->cmnd); + print_sense("sd", SCpnt); + SCpnt = end_scsi_request(SCpnt, 0, block_sectors); + requeue_sd_request(SCpnt); + return; + } + } /* driver byte != 0 */ + if (result) { + printk("SCSI disk error : host %d channel %d id %d lun %d return code = %x\n", + rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].device->host->host_no, + rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].device->channel, + rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].device->id, + rscsi_disks[DEVICE_NR(SCpnt->request.rq_dev)].device->lun, result); + + if (driver_byte(result) & DRIVER_SENSE) + print_sense("sd", SCpnt); + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.current_nr_sectors); + requeue_sd_request(SCpnt); + return; + } +} + +/* + * requeue_sd_request() is the request handler function for the sd driver. + * Its function in life is to take block device requests, and translate + * them to SCSI commands. + */ + +static void do_sd_request (void) +{ + Scsi_Cmnd * SCpnt = NULL; + Scsi_Device * SDev; + struct request * req = NULL; + unsigned long flags; + int flag = 0; + + save_flags(flags); + while (1==1){ + cli(); + if (CURRENT != NULL && CURRENT->rq_status == RQ_INACTIVE) { + restore_flags(flags); + return; + } + + INIT_SCSI_REQUEST; + SDev = rscsi_disks[DEVICE_NR(CURRENT->rq_dev)].device; + + /* + * I am not sure where the best place to do this is. We need + * to hook in a place where we are likely to come if in user + * space. + */ + if( SDev->was_reset ) + { + /* + * We need to relock the door, but we might + * be in an interrupt handler. Only do this + * from user space, since we do not want to + * sleep from an interrupt. + */ + if( SDev->removable && !intr_count ) + { + scsi_ioctl(SDev, SCSI_IOCTL_DOORLOCK, 0); + /* scsi_ioctl may allow CURRENT to change, so start over. */ + SDev->was_reset = 0; + continue; + } + SDev->was_reset = 0; + } + + /* We have to be careful here. allocate_device will get a free pointer, + * but there is no guarantee that it is queueable. In normal usage, + * we want to call this, because other types of devices may have the + * host all tied up, and we want to make sure that we have at least + * one request pending for this type of device. We can also come + * through here while servicing an interrupt, because of the need to + * start another command. If we call allocate_device more than once, + * then the system can wedge if the command is not queueable. The + * request_queueable function is safe because it checks to make sure + * that the host is able to take another command before it returns + * a pointer. + */ + + if (flag++ == 0) + SCpnt = allocate_device(&CURRENT, + rscsi_disks[DEVICE_NR(CURRENT->rq_dev)].device, 0); + else SCpnt = NULL; + + /* + * The following restore_flags leads to latency problems. FIXME. + * Using a "sti()" gets rid of the latency problems but causes + * race conditions and crashes. + */ + restore_flags(flags); + + /* This is a performance enhancement. We dig down into the request + * list and try to find a queueable request (i.e. device not busy, + * and host able to accept another command. If we find one, then we + * queue it. This can make a big difference on systems with more than + * one disk drive. We want to have the interrupts off when monkeying + * with the request list, because otherwise the kernel might try to + * slip in a request in between somewhere. + */ + + if (!SCpnt && sd_template.nr_dev > 1){ + struct request *req1; + req1 = NULL; + cli(); + req = CURRENT; + while(req){ + SCpnt = request_queueable(req, + rscsi_disks[DEVICE_NR(req->rq_dev)].device); + if(SCpnt) break; + req1 = req; + req = req->next; + } + if (SCpnt && req->rq_status == RQ_INACTIVE) { + if (req == CURRENT) + CURRENT = CURRENT->next; + else + req1->next = req->next; + } + restore_flags(flags); + } + + if (!SCpnt) return; /* Could not find anything to do */ + + /* Queue command */ + requeue_sd_request(SCpnt); + } /* While */ +} + +static void requeue_sd_request (Scsi_Cmnd * SCpnt) +{ + int dev, devm, block, this_count; + unsigned char cmd[10]; + int bounce_size, contiguous; + int max_sg; + struct buffer_head * bh, *bhp; + char * buff, *bounce_buffer; + + repeat: + + if(!SCpnt || SCpnt->request.rq_status == RQ_INACTIVE) { + do_sd_request(); + return; + } + + devm = MINOR(SCpnt->request.rq_dev); + dev = DEVICE_NR(SCpnt->request.rq_dev); + + block = SCpnt->request.sector; + this_count = 0; + +#ifdef DEBUG + printk("Doing sd request, dev = %d, block = %d\n", devm, block); +#endif + + if (devm >= (sd_template.dev_max << 4) || + !rscsi_disks[dev].device || + block + SCpnt->request.nr_sectors > sd[devm].nr_sects) + { + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + goto repeat; + } + + block += sd[devm].start_sect; + + if (rscsi_disks[dev].device->changed) + { + /* + * quietly refuse to do anything to a changed disc until the changed + * bit has been reset + */ + /* printk("SCSI disk has been changed. Prohibiting further I/O.\n"); */ + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + goto repeat; + } + +#ifdef DEBUG + printk("sd%c : real dev = /dev/sd%c, block = %d\n", + 'a' + devm, dev, block); +#endif + + /* + * If we have a 1K hardware sectorsize, prevent access to single + * 512 byte sectors. In theory we could handle this - in fact + * the scsi cdrom driver must be able to handle this because + * we typically use 1K blocksizes, and cdroms typically have + * 2K hardware sectorsizes. Of course, things are simpler + * with the cdrom, since it is read-only. For performance + * reasons, the filesystems should be able to handle this + * and not force the scsi disk driver to use bounce buffers + * for this. + */ + if (rscsi_disks[dev].sector_size == 1024) + if((block & 1) || (SCpnt->request.nr_sectors & 1)) { + printk("sd.c:Bad block number requested"); + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + goto repeat; + } + + switch (SCpnt->request.cmd) + { + case WRITE : + if (!rscsi_disks[dev].device->writeable) + { + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + goto repeat; + } + cmd[0] = WRITE_6; + break; + case READ : + cmd[0] = READ_6; + break; + default : + panic ("Unknown sd command %d\n", SCpnt->request.cmd); + } + + SCpnt->this_count = 0; + + /* If the host adapter can deal with very large scatter-gather + * requests, it is a waste of time to cluster + */ + contiguous = (!CLUSTERABLE_DEVICE(SCpnt) ? 0 :1); + bounce_buffer = NULL; + bounce_size = (SCpnt->request.nr_sectors << 9); + + /* First see if we need a bounce buffer for this request. If we do, make + * sure that we can allocate a buffer. Do not waste space by allocating + * a bounce buffer if we are straddling the 16Mb line + */ + if (contiguous && SCpnt->request.bh && + ((long) SCpnt->request.bh->b_data) + + (SCpnt->request.nr_sectors << 9) - 1 > ISA_DMA_THRESHOLD + && SCpnt->host->unchecked_isa_dma) { + if(((long) SCpnt->request.bh->b_data) > ISA_DMA_THRESHOLD) + bounce_buffer = (char *) scsi_malloc(bounce_size); + if(!bounce_buffer) contiguous = 0; + } + + if(contiguous && SCpnt->request.bh && SCpnt->request.bh->b_reqnext) + for(bh = SCpnt->request.bh, bhp = bh->b_reqnext; bhp; bh = bhp, + bhp = bhp->b_reqnext) { + if(!CONTIGUOUS_BUFFERS(bh,bhp)) { + if(bounce_buffer) scsi_free(bounce_buffer, bounce_size); + contiguous = 0; + break; + } + } + if (!SCpnt->request.bh || contiguous) { + + /* case of page request (i.e. raw device), or unlinked buffer */ + this_count = SCpnt->request.nr_sectors; + buff = SCpnt->request.buffer; + SCpnt->use_sg = 0; + + } else if (SCpnt->host->sg_tablesize == 0 || + (need_isa_buffer && dma_free_sectors <= 10)) { + + /* Case of host adapter that cannot scatter-gather. We also + * come here if we are running low on DMA buffer memory. We set + * a threshold higher than that we would need for this request so + * we leave room for other requests. Even though we would not need + * it all, we need to be conservative, because if we run low enough + * we have no choice but to panic. + */ + if (SCpnt->host->sg_tablesize != 0 && + need_isa_buffer && + dma_free_sectors <= 10) + printk("Warning: SCSI DMA buffer space running low. Using non scatter-gather I/O.\n"); + + this_count = SCpnt->request.current_nr_sectors; + buff = SCpnt->request.buffer; + SCpnt->use_sg = 0; + + } else { + + /* Scatter-gather capable host adapter */ + struct scatterlist * sgpnt; + int count, this_count_max; + int counted; + + bh = SCpnt->request.bh; + this_count = 0; + this_count_max = (rscsi_disks[dev].ten ? 0xffff : 0xff); + count = 0; + bhp = NULL; + while(bh) { + if ((this_count + (bh->b_size >> 9)) > this_count_max) break; + if(!bhp || !CONTIGUOUS_BUFFERS(bhp,bh) || + !CLUSTERABLE_DEVICE(SCpnt) || + (SCpnt->host->unchecked_isa_dma && + ((unsigned long) bh->b_data-1) == ISA_DMA_THRESHOLD)) { + if (count < SCpnt->host->sg_tablesize) count++; + else break; + } + this_count += (bh->b_size >> 9); + bhp = bh; + bh = bh->b_reqnext; + } +#if 0 + if(SCpnt->host->unchecked_isa_dma && + ((unsigned int) SCpnt->request.bh->b_data-1) == ISA_DMA_THRESHOLD) count--; +#endif + SCpnt->use_sg = count; /* Number of chains */ + /* scsi_malloc can only allocate in chunks of 512 bytes */ + count = (SCpnt->use_sg * sizeof(struct scatterlist) + 511) & ~511; + + SCpnt->sglist_len = count; + max_sg = count / sizeof(struct scatterlist); + if(SCpnt->host->sg_tablesize < max_sg) + max_sg = SCpnt->host->sg_tablesize; + sgpnt = (struct scatterlist * ) scsi_malloc(count); + if (!sgpnt) { + printk("Warning - running *really* short on DMA buffers\n"); + SCpnt->use_sg = 0; /* No memory left - bail out */ + this_count = SCpnt->request.current_nr_sectors; + buff = SCpnt->request.buffer; + } else { + memset(sgpnt, 0, count); /* Zero so it is easy to fill, but only + * if memory is available + */ + buff = (char *) sgpnt; + counted = 0; + for(count = 0, bh = SCpnt->request.bh, bhp = bh->b_reqnext; + count < SCpnt->use_sg && bh; + count++, bh = bhp) { + + bhp = bh->b_reqnext; + + if(!sgpnt[count].address) sgpnt[count].address = bh->b_data; + sgpnt[count].length += bh->b_size; + counted += bh->b_size >> 9; + + if (((long) sgpnt[count].address) + sgpnt[count].length - 1 > + ISA_DMA_THRESHOLD && (SCpnt->host->unchecked_isa_dma) && + !sgpnt[count].alt_address) { + sgpnt[count].alt_address = sgpnt[count].address; + /* We try to avoid exhausting the DMA pool, since it is + * easier to control usage here. In other places we might + * have a more pressing need, and we would be screwed if + * we ran out */ + if(dma_free_sectors < (sgpnt[count].length >> 9) + 10) { + sgpnt[count].address = NULL; + } else { + sgpnt[count].address = + (char *) scsi_malloc(sgpnt[count].length); + } + /* If we start running low on DMA buffers, we abort the + * scatter-gather operation, and free all of the memory + * we have allocated. We want to ensure that all scsi + * operations are able to do at least a non-scatter/gather + * operation */ + if(sgpnt[count].address == NULL){ /* Out of dma memory */ +#if 0 + printk("Warning: Running low on SCSI DMA buffers"); + /* Try switching back to a non s-g operation. */ + while(--count >= 0){ + if(sgpnt[count].alt_address) + scsi_free(sgpnt[count].address, + sgpnt[count].length); + } + this_count = SCpnt->request.current_nr_sectors; + buff = SCpnt->request.buffer; + SCpnt->use_sg = 0; + scsi_free(sgpnt, SCpnt->sglist_len); +#endif + SCpnt->use_sg = count; + this_count = counted -= bh->b_size >> 9; + break; + } + } + + /* Only cluster buffers if we know that we can supply DMA + * buffers large enough to satisfy the request. Do not cluster + * a new request if this would mean that we suddenly need to + * start using DMA bounce buffers */ + if(bhp && CONTIGUOUS_BUFFERS(bh,bhp) + && CLUSTERABLE_DEVICE(SCpnt)) { + char * tmp; + + if (((long) sgpnt[count].address) + sgpnt[count].length + + bhp->b_size - 1 > ISA_DMA_THRESHOLD && + (SCpnt->host->unchecked_isa_dma) && + !sgpnt[count].alt_address) continue; + + if(!sgpnt[count].alt_address) {count--; continue; } + if(dma_free_sectors > 10) + tmp = (char *) scsi_malloc(sgpnt[count].length + + bhp->b_size); + else { + tmp = NULL; + max_sg = SCpnt->use_sg; + } + if(tmp){ + scsi_free(sgpnt[count].address, sgpnt[count].length); + sgpnt[count].address = tmp; + count--; + continue; + } + + /* If we are allowed another sg chain, then increment + * counter so we can insert it. Otherwise we will end + up truncating */ + + if (SCpnt->use_sg < max_sg) SCpnt->use_sg++; + } /* contiguous buffers */ + } /* for loop */ + + /* This is actually how many we are going to transfer */ + this_count = counted; + + if(count < SCpnt->use_sg || SCpnt->use_sg + > SCpnt->host->sg_tablesize){ + bh = SCpnt->request.bh; + printk("Use sg, count %d %x %d\n", + SCpnt->use_sg, count, dma_free_sectors); + printk("maxsg = %x, counted = %d this_count = %d\n", + max_sg, counted, this_count); + while(bh){ + printk("[%p %lx] ", bh->b_data, bh->b_size); + bh = bh->b_reqnext; + } + if(SCpnt->use_sg < 16) + for(count=0; count<SCpnt->use_sg; count++) + printk("{%d:%p %p %d} ", count, + sgpnt[count].address, + sgpnt[count].alt_address, + sgpnt[count].length); + panic("Ooops"); + } + + if (SCpnt->request.cmd == WRITE) + for(count=0; count<SCpnt->use_sg; count++) + if(sgpnt[count].alt_address) + memcpy(sgpnt[count].address, sgpnt[count].alt_address, + sgpnt[count].length); + } /* Able to malloc sgpnt */ + } /* Host adapter capable of scatter-gather */ + + /* Now handle the possibility of DMA to addresses > 16Mb */ + + if(SCpnt->use_sg == 0){ + if (((long) buff) + (this_count << 9) - 1 > ISA_DMA_THRESHOLD && + (SCpnt->host->unchecked_isa_dma)) { + if(bounce_buffer) + buff = bounce_buffer; + else + buff = (char *) scsi_malloc(this_count << 9); + if(buff == NULL) { /* Try backing off a bit if we are low on mem*/ + this_count = SCpnt->request.current_nr_sectors; + buff = (char *) scsi_malloc(this_count << 9); + if(!buff) panic("Ran out of DMA buffers."); + } + if (SCpnt->request.cmd == WRITE) + memcpy(buff, (char *)SCpnt->request.buffer, this_count << 9); + } + } +#ifdef DEBUG + printk("sd%c : %s %d/%d 512 byte blocks.\n", + 'a' + devm, + (SCpnt->request.cmd == WRITE) ? "writing" : "reading", + this_count, SCpnt->request.nr_sectors); +#endif + + cmd[1] = (SCpnt->lun << 5) & 0xe0; + + if (rscsi_disks[dev].sector_size == 1024){ + if(block & 1) panic("sd.c:Bad block number requested"); + if(this_count & 1) panic("sd.c:Bad block number requested"); + block = block >> 1; + this_count = this_count >> 1; + } + + if (rscsi_disks[dev].sector_size == 256){ + block = block << 1; + this_count = this_count << 1; + } + + if (((this_count > 0xff) || (block > 0x1fffff)) && rscsi_disks[dev].ten) + { + if (this_count > 0xffff) + this_count = 0xffff; + + cmd[0] += READ_10 - READ_6 ; + cmd[2] = (unsigned char) (block >> 24) & 0xff; + cmd[3] = (unsigned char) (block >> 16) & 0xff; + cmd[4] = (unsigned char) (block >> 8) & 0xff; + cmd[5] = (unsigned char) block & 0xff; + cmd[6] = cmd[9] = 0; + cmd[7] = (unsigned char) (this_count >> 8) & 0xff; + cmd[8] = (unsigned char) this_count & 0xff; + } + else + { + if (this_count > 0xff) + this_count = 0xff; + + cmd[1] |= (unsigned char) ((block >> 16) & 0x1f); + cmd[2] = (unsigned char) ((block >> 8) & 0xff); + cmd[3] = (unsigned char) block & 0xff; + cmd[4] = (unsigned char) this_count; + cmd[5] = 0; + } + + /* + * We shouldn't disconnect in the middle of a sector, so with a dumb + * host adapter, it's safe to assume that we can at least transfer + * this many bytes between each connect / disconnect. + */ + + SCpnt->transfersize = rscsi_disks[dev].sector_size; + SCpnt->underflow = this_count << 9; + scsi_do_cmd (SCpnt, (void *) cmd, buff, + this_count * rscsi_disks[dev].sector_size, + rw_intr, + (SCpnt->device->type == TYPE_DISK ? + SD_TIMEOUT : SD_MOD_TIMEOUT), + MAX_RETRIES); +} + +static int check_scsidisk_media_change(kdev_t full_dev){ + int retval; + int target; + struct inode inode; + int flag = 0; + + target = DEVICE_NR(full_dev); + + if (target >= sd_template.dev_max || + !rscsi_disks[target].device) { + printk("SCSI disk request error: invalid device.\n"); + return 0; + } + + if(!rscsi_disks[target].device->removable) return 0; + + inode.i_rdev = full_dev; /* This is all we really need here */ + + /* Using Start/Stop enables differentiation between drive with + * no cartridge loaded - NOT READY, drive with changed cartridge - + * UNIT ATTENTION, or with same cartridge - GOOD STATUS. + * This also handles drives that auto spin down. eg iomega jaz 1GB + * as this will spin up the drive. + */ + retval = sd_ioctl(&inode, NULL, SCSI_IOCTL_START_UNIT, 0); + + if(retval){ /* Unable to test, unit probably not ready. This usually + * means there is no disc in the drive. Mark as changed, + * and we will figure it out later once the drive is + * available again. */ + + rscsi_disks[target].ready = 0; + rscsi_disks[target].device->changed = 1; + return 1; /* This will force a flush, if called from + * check_disk_change */ + } + + /* + * for removable scsi disk ( FLOPTICAL ) we have to recognise the + * presence of disk in the drive. This is kept in the Scsi_Disk + * struct and tested at open ! Daniel Roche ( dan@lectra.fr ) + */ + + rscsi_disks[target].ready = 1; /* FLOPTICAL */ + + retval = rscsi_disks[target].device->changed; + if(!flag) rscsi_disks[target].device->changed = 0; + return retval; +} + +static void sd_init_done (Scsi_Cmnd * SCpnt) +{ + struct request * req; + + req = &SCpnt->request; + req->rq_status = RQ_SCSI_DONE; /* Busy, but indicate request done */ + + if (req->sem != NULL) { + up(req->sem); + } +} + +static int sd_init_onedisk(int i) +{ + unsigned char cmd[10]; + unsigned char *buffer; + unsigned long spintime; + int the_result, retries; + Scsi_Cmnd * SCpnt; + + /* We need to retry the READ_CAPACITY because a UNIT_ATTENTION is + * considered a fatal error, and many devices report such an error + * just after a scsi bus reset. + */ + + SCpnt = allocate_device(NULL, rscsi_disks[i].device, 1); + buffer = (unsigned char *) scsi_malloc(512); + + spintime = 0; + + /* Spin up drives, as required. Only do this at boot time */ + /* Spinup needs to be done for module loads too. */ + do{ + retries = 0; + while(retries < 3) + { + cmd[0] = TEST_UNIT_READY; + cmd[1] = (rscsi_disks[i].device->lun << 5) & 0xe0; + memset ((void *) &cmd[2], 0, 8); + SCpnt->cmd_len = 0; + SCpnt->sense_buffer[0] = 0; + SCpnt->sense_buffer[2] = 0; + + { + struct semaphore sem = MUTEX_LOCKED; + /* Mark as really busy again */ + SCpnt->request.rq_status = RQ_SCSI_BUSY; + SCpnt->request.sem = &sem; + scsi_do_cmd (SCpnt, + (void *) cmd, (void *) buffer, + 512, sd_init_done, SD_TIMEOUT, + MAX_RETRIES); + down(&sem); + } + + the_result = SCpnt->result; + retries++; + if( the_result == 0 + || SCpnt->sense_buffer[2] != UNIT_ATTENTION) + break; + } + + /* Look for non-removable devices that return NOT_READY. + * Issue command to spin up drive for these cases. */ + if(the_result && !rscsi_disks[i].device->removable && + SCpnt->sense_buffer[2] == NOT_READY) { + unsigned long time1; + if(!spintime){ + printk( "sd%c: Spinning up disk...", 'a' + i ); + cmd[0] = START_STOP; + cmd[1] = (rscsi_disks[i].device->lun << 5) & 0xe0; + cmd[1] |= 1; /* Return immediately */ + memset ((void *) &cmd[2], 0, 8); + cmd[4] = 1; /* Start spin cycle */ + SCpnt->cmd_len = 0; + SCpnt->sense_buffer[0] = 0; + SCpnt->sense_buffer[2] = 0; + + { + struct semaphore sem = MUTEX_LOCKED; + /* Mark as really busy again */ + SCpnt->request.rq_status = RQ_SCSI_BUSY; + SCpnt->request.sem = &sem; + scsi_do_cmd (SCpnt, + (void *) cmd, (void *) buffer, + 512, sd_init_done, SD_TIMEOUT, + MAX_RETRIES); + down(&sem); + } + + spintime = jiffies; + } + + time1 = jiffies + HZ; + while(jiffies < time1); /* Wait 1 second for next try */ + printk( "." ); + } + } while(the_result && spintime && spintime+100*HZ > jiffies); + if (spintime) { + if (the_result) + printk( "not responding...\n" ); + else + printk( "ready\n" ); + } + + retries = 3; + do { + cmd[0] = READ_CAPACITY; + cmd[1] = (rscsi_disks[i].device->lun << 5) & 0xe0; + memset ((void *) &cmd[2], 0, 8); + memset ((void *) buffer, 0, 8); + SCpnt->cmd_len = 0; + SCpnt->sense_buffer[0] = 0; + SCpnt->sense_buffer[2] = 0; + + { + struct semaphore sem = MUTEX_LOCKED; + /* Mark as really busy again */ + SCpnt->request.rq_status = RQ_SCSI_BUSY; + SCpnt->request.sem = &sem; + scsi_do_cmd (SCpnt, + (void *) cmd, (void *) buffer, + 8, sd_init_done, SD_TIMEOUT, + MAX_RETRIES); + down(&sem); /* sleep until it is ready */ + } + + the_result = SCpnt->result; + retries--; + + } while(the_result && retries); + + SCpnt->request.rq_status = RQ_INACTIVE; /* Mark as not busy */ + + wake_up(&SCpnt->device->device_wait); + + /* Wake up a process waiting for device */ + + /* + * The SCSI standard says: + * "READ CAPACITY is necessary for self configuring software" + * While not mandatory, support of READ CAPACITY is strongly encouraged. + * We used to die if we couldn't successfully do a READ CAPACITY. + * But, now we go on about our way. The side effects of this are + * + * 1. We can't know block size with certainty. I have said "512 bytes + * is it" as this is most common. + * + * 2. Recovery from when some one attempts to read past the end of the + * raw device will be slower. + */ + + if (the_result) + { + printk ("sd%c : READ CAPACITY failed.\n" + "sd%c : status = %x, message = %02x, host = %d, driver = %02x \n", + 'a' + i, 'a' + i, + status_byte(the_result), + msg_byte(the_result), + host_byte(the_result), + driver_byte(the_result) + ); + if (driver_byte(the_result) & DRIVER_SENSE) + printk("sd%c : extended sense code = %1x \n", + 'a' + i, SCpnt->sense_buffer[2] & 0xf); + else + printk("sd%c : sense not available. \n", 'a' + i); + + printk("sd%c : block size assumed to be 512 bytes, disk size 1GB. \n", + 'a' + i); + rscsi_disks[i].capacity = 0x1fffff; + rscsi_disks[i].sector_size = 512; + + /* Set dirty bit for removable devices if not ready - sometimes drives + * will not report this properly. */ + if(rscsi_disks[i].device->removable && + SCpnt->sense_buffer[2] == NOT_READY) + rscsi_disks[i].device->changed = 1; + + } + else + { + /* + * FLOPTICAL , if read_capa is ok , drive is assumed to be ready + */ + rscsi_disks[i].ready = 1; + + rscsi_disks[i].capacity = 1 + ((buffer[0] << 24) | + (buffer[1] << 16) | + (buffer[2] << 8) | + buffer[3]); + + rscsi_disks[i].sector_size = (buffer[4] << 24) | + (buffer[5] << 16) | (buffer[6] << 8) | buffer[7]; + + if (rscsi_disks[i].sector_size == 0) { + rscsi_disks[i].sector_size = 512; + printk("sd%c : sector size 0 reported, assuming 512.\n", 'a' + i); + } + + + if (rscsi_disks[i].sector_size != 512 && + rscsi_disks[i].sector_size != 1024 && + rscsi_disks[i].sector_size != 256) + { + printk ("sd%c : unsupported sector size %d.\n", + 'a' + i, rscsi_disks[i].sector_size); + if(rscsi_disks[i].device->removable){ + rscsi_disks[i].capacity = 0; + } else { + printk ("scsi : deleting disk entry.\n"); + rscsi_disks[i].device = NULL; + sd_template.nr_dev--; + sd_gendisk.nr_real--; + return i; + } + } + { + /* + * The msdos fs needs to know the hardware sector size + * So I have created this table. See ll_rw_blk.c + * Jacques Gelinas (Jacques@solucorp.qc.ca) + */ + int m, mb; + int sz_quot, sz_rem; + int hard_sector = rscsi_disks[i].sector_size; + /* There are 16 minors allocated for each major device */ + for (m=i<<4; m<((i+1)<<4); m++){ + sd_hardsizes[m] = hard_sector; + } + mb = rscsi_disks[i].capacity / 1024 * hard_sector / 1024; + /* sz = div(m/100, 10); this seems to not be in the libr */ + m = (mb + 50) / 100; + sz_quot = m / 10; + sz_rem = m - (10 * sz_quot); + printk ("SCSI device sd%c: hdwr sector= %d bytes." + " Sectors= %d [%d MB] [%d.%1d GB]\n", + i+'a', hard_sector, rscsi_disks[i].capacity, + mb, sz_quot, sz_rem); + } + if(rscsi_disks[i].sector_size == 1024) + rscsi_disks[i].capacity <<= 1; /* Change into 512 byte sectors */ + if(rscsi_disks[i].sector_size == 256) + rscsi_disks[i].capacity >>= 1; /* Change into 512 byte sectors */ + } + + + /* + * Unless otherwise specified, this is not write protected. + */ + rscsi_disks[i].write_prot = 0; + if ( rscsi_disks[i].device->removable && rscsi_disks[i].ready ) { + /* FLOPTICAL */ + + /* + * for removable scsi disk ( FLOPTICAL ) we have to recognise + * the Write Protect Flag. This flag is kept in the Scsi_Disk struct + * and tested at open ! + * Daniel Roche ( dan@lectra.fr ) + */ + + memset ((void *) &cmd[0], 0, 8); + cmd[0] = MODE_SENSE; + cmd[1] = (rscsi_disks[i].device->lun << 5) & 0xe0; + cmd[2] = 1; /* page code 1 ?? */ + cmd[4] = 12; + SCpnt->cmd_len = 0; + SCpnt->sense_buffer[0] = 0; + SCpnt->sense_buffer[2] = 0; + + /* same code as READCAPA !! */ + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.rq_status = RQ_SCSI_BUSY; /* Mark as really busy again */ + SCpnt->request.sem = &sem; + scsi_do_cmd (SCpnt, + (void *) cmd, (void *) buffer, + 512, sd_init_done, SD_TIMEOUT, + MAX_RETRIES); + down(&sem); + } + + the_result = SCpnt->result; + SCpnt->request.rq_status = RQ_INACTIVE; /* Mark as not busy */ + wake_up(&SCpnt->device->device_wait); + + if ( the_result ) { + printk ("sd%c: test WP failed, assume Write Protected\n",i+'a'); + rscsi_disks[i].write_prot = 1; + } else { + rscsi_disks[i].write_prot = ((buffer[2] & 0x80) != 0); + printk ("sd%c: Write Protect is %s\n",i+'a', + rscsi_disks[i].write_prot ? "on" : "off"); + } + + } /* check for write protect */ + + rscsi_disks[i].ten = 1; + rscsi_disks[i].remap = 1; + scsi_free(buffer, 512); + return i; +} + +/* + * The sd_init() function looks at all SCSI drives present, determines + * their size, and reads partition table entries for them. + */ + +static int sd_registered = 0; + +static int sd_init() +{ + int i; + + if (sd_template.dev_noticed == 0) return 0; + + if(!sd_registered) { + if (register_blkdev(MAJOR_NR,"sd",&sd_fops)) { + printk("Unable to get major %d for SCSI disk\n",MAJOR_NR); + return 1; + } + sd_registered++; + } + + /* We do not support attaching loadable devices yet. */ + if(rscsi_disks) return 0; + + sd_template.dev_max = sd_template.dev_noticed + SD_EXTRA_DEVS; + + rscsi_disks = (Scsi_Disk *) + scsi_init_malloc(sd_template.dev_max * sizeof(Scsi_Disk), GFP_ATOMIC); + memset(rscsi_disks, 0, sd_template.dev_max * sizeof(Scsi_Disk)); + + sd_sizes = (int *) scsi_init_malloc((sd_template.dev_max << 4) * + sizeof(int), GFP_ATOMIC); + memset(sd_sizes, 0, (sd_template.dev_max << 4) * sizeof(int)); + + sd_blocksizes = (int *) scsi_init_malloc((sd_template.dev_max << 4) * + sizeof(int), GFP_ATOMIC); + + sd_hardsizes = (int *) scsi_init_malloc((sd_template.dev_max << 4) * + sizeof(int), GFP_ATOMIC); + + for(i=0;i<(sd_template.dev_max << 4);i++){ + sd_blocksizes[i] = 1024; + sd_hardsizes[i] = 512; + } + blksize_size[MAJOR_NR] = sd_blocksizes; + hardsect_size[MAJOR_NR] = sd_hardsizes; + sd = (struct hd_struct *) scsi_init_malloc((sd_template.dev_max << 4) * + sizeof(struct hd_struct), + GFP_ATOMIC); + + + sd_gendisk.max_nr = sd_template.dev_max; + sd_gendisk.part = sd; + sd_gendisk.sizes = sd_sizes; + sd_gendisk.real_devices = (void *) rscsi_disks; + return 0; +} + +static void sd_finish(void) +{ + struct gendisk *gendisk; + int i; + + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + + for (gendisk = gendisk_head; gendisk != NULL; gendisk = gendisk->next) + if (gendisk == &sd_gendisk) + break; + if (gendisk == NULL) + { + sd_gendisk.next = gendisk_head; + gendisk_head = &sd_gendisk; + } + + for (i = 0; i < sd_template.dev_max; ++i) + if (!rscsi_disks[i].capacity && + rscsi_disks[i].device) + { + if (MODULE_FLAG + && !rscsi_disks[i].has_part_table) { + sd_sizes[i << 4] = rscsi_disks[i].capacity; + /* revalidate does sd_init_onedisk via MAYBE_REINIT*/ + revalidate_scsidisk(MKDEV(MAJOR_NR, i << 4), 0); + } + else + i=sd_init_onedisk(i); + rscsi_disks[i].has_part_table = 1; + } + + /* If our host adapter is capable of scatter-gather, then we increase + * the read-ahead to 16 blocks (32 sectors). If not, we use + * a two block (4 sector) read ahead. + */ + if(rscsi_disks[0].device && rscsi_disks[0].device->host->sg_tablesize) + read_ahead[MAJOR_NR] = 120; /* 120 sector read-ahead */ + else + read_ahead[MAJOR_NR] = 4; /* 4 sector read-ahead */ + + return; +} + +static int sd_detect(Scsi_Device * SDp){ + if(SDp->type != TYPE_DISK && SDp->type != TYPE_MOD) return 0; + + printk("Detected scsi %sdisk sd%c at scsi%d, channel %d, id %d, lun %d\n", + SDp->removable ? "removable " : "", + 'a'+ (sd_template.dev_noticed++), + SDp->host->host_no, SDp->channel, SDp->id, SDp->lun); + + return 1; +} + +static int sd_attach(Scsi_Device * SDp){ + Scsi_Disk * dpnt; + int i; + + if(SDp->type != TYPE_DISK && SDp->type != TYPE_MOD) return 0; + + if(sd_template.nr_dev >= sd_template.dev_max) { + SDp->attached--; + return 1; + } + + for(dpnt = rscsi_disks, i=0; i<sd_template.dev_max; i++, dpnt++) + if(!dpnt->device) break; + + if(i >= sd_template.dev_max) panic ("scsi_devices corrupt (sd)"); + + SDp->scsi_request_fn = do_sd_request; + rscsi_disks[i].device = SDp; + rscsi_disks[i].has_part_table = 0; + sd_template.nr_dev++; + sd_gendisk.nr_real++; + return 0; +} + +#define DEVICE_BUSY rscsi_disks[target].device->busy +#define USAGE rscsi_disks[target].device->access_count +#define CAPACITY rscsi_disks[target].capacity +#define MAYBE_REINIT sd_init_onedisk(target) +#define GENDISK_STRUCT sd_gendisk + +/* This routine is called to flush all partitions and partition tables + * for a changed scsi disk, and then re-read the new partition table. + * If we are revalidating a disk because of a media change, then we + * enter with usage == 0. If we are using an ioctl, we automatically have + * usage == 1 (we need an open channel to use an ioctl :-), so this + * is our limit. + */ +int revalidate_scsidisk(kdev_t dev, int maxusage){ + int target; + struct gendisk * gdev; + unsigned long flags; + int max_p; + int start; + int i; + + target = DEVICE_NR(dev); + gdev = &GENDISK_STRUCT; + + save_flags(flags); + cli(); + if (DEVICE_BUSY || USAGE > maxusage) { + restore_flags(flags); + printk("Device busy for revalidation (usage=%d)\n", USAGE); + return -EBUSY; + } + DEVICE_BUSY = 1; + restore_flags(flags); + + max_p = gdev->max_p; + start = target << gdev->minor_shift; + + for (i=max_p - 1; i >=0 ; i--) { + int minor = start+i; + kdev_t devi = MKDEV(MAJOR_NR, minor); + sync_dev(devi); + invalidate_inodes(devi); + invalidate_buffers(devi); + gdev->part[minor].start_sect = 0; + gdev->part[minor].nr_sects = 0; + /* + * Reset the blocksize for everything so that we can read + * the partition table. + */ + blksize_size[MAJOR_NR][minor] = 1024; + } + +#ifdef MAYBE_REINIT + MAYBE_REINIT; +#endif + + gdev->part[start].nr_sects = CAPACITY; + resetup_one_dev(gdev, target); + + DEVICE_BUSY = 0; + return 0; +} + +static int fop_revalidate_scsidisk(kdev_t dev){ + return revalidate_scsidisk(dev, 0); +} + + +static void sd_detach(Scsi_Device * SDp) +{ + Scsi_Disk * dpnt; + int i; + int max_p; + int start; + + for(dpnt = rscsi_disks, i=0; i<sd_template.dev_max; i++, dpnt++) + if(dpnt->device == SDp) { + + /* If we are disconnecting a disk driver, sync and invalidate + * everything */ + max_p = sd_gendisk.max_p; + start = i << sd_gendisk.minor_shift; + + for (i=max_p - 1; i >=0 ; i--) { + int minor = start+i; + kdev_t devi = MKDEV(MAJOR_NR, minor); + sync_dev(devi); + invalidate_inodes(devi); + invalidate_buffers(devi); + sd_gendisk.part[minor].start_sect = 0; + sd_gendisk.part[minor].nr_sects = 0; + sd_sizes[minor] = 0; + } + + dpnt->has_part_table = 0; + dpnt->device = NULL; + dpnt->capacity = 0; + SDp->attached--; + sd_template.dev_noticed--; + sd_template.nr_dev--; + sd_gendisk.nr_real--; + return; + } + return; +} + +#ifdef MODULE + +int init_module(void) { + sd_template.usage_count = &mod_use_count_; + return scsi_register_module(MODULE_SCSI_DEV, &sd_template); +} + +void cleanup_module( void) +{ + struct gendisk * prev_sdgd; + struct gendisk * sdgd; + + scsi_unregister_module(MODULE_SCSI_DEV, &sd_template); + unregister_blkdev(SCSI_DISK_MAJOR, "sd"); + sd_registered--; + if( rscsi_disks != NULL ) + { + scsi_init_free((char *) rscsi_disks, + (sd_template.dev_noticed + SD_EXTRA_DEVS) + * sizeof(Scsi_Disk)); + + scsi_init_free((char *) sd_sizes, sd_template.dev_max * sizeof(int)); + scsi_init_free((char *) sd_blocksizes, sd_template.dev_max * sizeof(int)); + scsi_init_free((char *) sd_hardsizes, sd_template.dev_max * sizeof(int)); + scsi_init_free((char *) sd, + (sd_template.dev_max << 4) * sizeof(struct hd_struct)); + /* + * Now remove sd_gendisk from the linked list + */ + sdgd = gendisk_head; + prev_sdgd = NULL; + while(sdgd != &sd_gendisk) + { + prev_sdgd = sdgd; + sdgd = sdgd->next; + } + + if(sdgd != &sd_gendisk) + printk("sd_gendisk not in disk chain.\n"); + else { + if(prev_sdgd != NULL) + prev_sdgd->next = sdgd->next; + else + gendisk_head = sdgd->next; + } + } + + blksize_size[MAJOR_NR] = NULL; + blk_dev[MAJOR_NR].request_fn = NULL; + blk_size[MAJOR_NR] = NULL; + hardsect_size[MAJOR_NR] = NULL; + read_ahead[MAJOR_NR] = 0; + sd_template.dev_max = 0; +} +#endif /* MODULE */ + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/sd.h b/linux/src/drivers/scsi/sd.h new file mode 100644 index 00000000..02b3437d --- /dev/null +++ b/linux/src/drivers/scsi/sd.h @@ -0,0 +1,65 @@ +/* + * sd.h Copyright (C) 1992 Drew Eckhardt + * SCSI disk driver header file by + * Drew Eckhardt + * + * <drew@colorado.edu> + * + * Modified by Eric Youngdale eric@aib.com to + * add scatter-gather, multiple outstanding request, and other + * enhancements. + */ +#ifndef _SD_H +#define _SD_H +/* + $Header: cvs/gnumach/linux/src/drivers/scsi/Attic/sd.h,v 1.1 1999/04/26 05:55:03 tb Exp $ +*/ + +#ifndef _SCSI_H +#include "scsi.h" +#endif + +#ifndef _GENDISK_H +#include <linux/genhd.h> +#endif + +extern struct hd_struct * sd; + +typedef struct scsi_disk { + unsigned capacity; /* size in blocks */ + unsigned sector_size; /* size in bytes */ + Scsi_Device *device; + unsigned char ready; /* flag ready for FLOPTICAL */ + unsigned char write_prot; /* flag write_protect for rmvable dev */ + unsigned char sector_bit_size; /* sector_size = 2 to the bit size power */ + unsigned char sector_bit_shift; /* power of 2 sectors per FS block */ + unsigned ten:1; /* support ten byte read / write */ + unsigned remap:1; /* support remapping */ + unsigned has_part_table:1; /* has partition table */ +} Scsi_Disk; + +extern Scsi_Disk * rscsi_disks; + +extern int revalidate_scsidisk(kdev_t dev, int maxusage); + +#endif + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ + diff --git a/linux/src/drivers/scsi/sd_ioctl.c b/linux/src/drivers/scsi/sd_ioctl.c new file mode 100644 index 00000000..52fd754d --- /dev/null +++ b/linux/src/drivers/scsi/sd_ioctl.c @@ -0,0 +1,119 @@ +/* + * drivers/scsi/sd_ioctl.c + * + * ioctl handling for SCSI disks + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/hdreg.h> +#include <linux/errno.h> + +#include <asm/segment.h> + +#include <linux/blk.h> +#include "scsi.h" +#include <scsi/scsi_ioctl.h> +#include "hosts.h" +#include "sd.h" +#include <scsi/scsicam.h> /* must follow "hosts.h" */ + +int sd_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) +{ + kdev_t dev = inode->i_rdev; + int error; + struct Scsi_Host * host; + int diskinfo[4]; + struct hd_geometry *loc = (struct hd_geometry *) arg; + + switch (cmd) { + case HDIO_GETGEO: /* Return BIOS disk parameters */ + if (!loc) return -EINVAL; + error = verify_area(VERIFY_WRITE, loc, sizeof(*loc)); + if (error) + return error; + host = rscsi_disks[MINOR(dev) >> 4].device->host; + +/* default to most commonly used values */ + + diskinfo[0] = 0x40; + diskinfo[1] = 0x20; + diskinfo[2] = rscsi_disks[MINOR(dev) >> 4].capacity >> 11; + +/* override with calculated, extended default, or driver values */ + + if(host->hostt->bios_param != NULL) + host->hostt->bios_param(&rscsi_disks[MINOR(dev) >> 4], + dev, + &diskinfo[0]); + else scsicam_bios_param(&rscsi_disks[MINOR(dev) >> 4], + dev, &diskinfo[0]); + + put_user(diskinfo[0], &loc->heads); + put_user(diskinfo[1], &loc->sectors); + put_user(diskinfo[2], &loc->cylinders); + put_user(sd[MINOR(inode->i_rdev)].start_sect, &loc->start); + return 0; + case BLKGETSIZE: /* Return device size */ + if (!arg) return -EINVAL; + error = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long)); + if (error) + return error; + put_user(sd[MINOR(inode->i_rdev)].nr_sects, + (long *) arg); + return 0; + + case BLKRASET: + if (!suser()) + return -EACCES; + if(!(inode->i_rdev)) return -EINVAL; + if(arg > 0xff) return -EINVAL; + read_ahead[MAJOR(inode->i_rdev)] = arg; + return 0; + + case BLKRAGET: + if (!arg) + return -EINVAL; + error = verify_area(VERIFY_WRITE, (int *) arg, sizeof(int)); + if (error) + return error; + put_user(read_ahead[MAJOR(inode->i_rdev)], (int *) arg); + return 0; + + case BLKFLSBUF: + if(!suser()) return -EACCES; + if(!(inode->i_rdev)) return -EINVAL; + fsync_dev(inode->i_rdev); + invalidate_buffers(inode->i_rdev); + return 0; + + case BLKRRPART: /* Re-read partition tables */ + return revalidate_scsidisk(dev, 1); + + RO_IOCTLS(dev, arg); + + default: + return scsi_ioctl(rscsi_disks[MINOR(dev) >> 4].device , cmd, (void *) arg); + } +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/seagate.c b/linux/src/drivers/scsi/seagate.c new file mode 100644 index 00000000..9e942746 --- /dev/null +++ b/linux/src/drivers/scsi/seagate.c @@ -0,0 +1,1755 @@ +/* + * seagate.c Copyright (C) 1992, 1993 Drew Eckhardt + * low level scsi driver for ST01/ST02, Future Domain TMC-885, + * TMC-950 by + * + * Drew Eckhardt + * + * <drew@colorado.edu> + * + * Note : TMC-880 boards don't work because they have two bits in + * the status register flipped, I'll fix this "RSN" + * + * This card does all the I/O via memory mapped I/O, so there is no need + * to check or allocate a region of the I/O address space. + */ + +/* + * Configuration : + * To use without BIOS -DOVERRIDE=base_address -DCONTROLLER=FD or SEAGATE + * -DIRQ will override the default of 5. + * Note: You can now set these options from the kernel's "command line". + * The syntax is: + * + * st0x=ADDRESS,IRQ (for a Seagate controller) + * or: + * tmc8xx=ADDRESS,IRQ (for a TMC-8xx or TMC-950 controller) + * eg: + * tmc8xx=0xC8000,15 + * + * will configure the driver for a TMC-8xx style controller using IRQ 15 + * with a base address of 0xC8000. + * + * -DFAST or -DFAST32 will use blind transfers where possible + * + * -DARBITRATE will cause the host adapter to arbitrate for the + * bus for better SCSI-II compatibility, rather than just + * waiting for BUS FREE and then doing its thing. Should + * let us do one command per Lun when I integrate my + * reorganization changes into the distribution sources. + * + * -DSLOW_HANDSHAKE will allow compatibility with broken devices that don't + * handshake fast enough (ie, some CD ROM's) for the Seagate + * code. + * + * -DSLOW_RATE=x, x some number will let you specify a default + * transfer rate if handshaking isn't working correctly. + */ + +#include <linux/module.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/config.h> +#include <linux/proc_fs.h> + +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "seagate.h" +#include "constants.h" +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_seagate = { + PROC_SCSI_SEAGATE, 7, "seagate", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + + +#ifndef IRQ +#define IRQ 5 +#endif + +#if (defined(FAST32) && !defined(FAST)) +#define FAST +#endif + +#if defined(SLOW_RATE) && !defined(SLOW_HANDSHAKE) +#define SLOW_HANDSHAKE +#endif + +#if defined(SLOW_HANDSHAKE) && !defined(SLOW_RATE) +#define SLOW_RATE 50 +#endif + + +#if defined(LINKED) +#undef LINKED /* Linked commands are currently broken ! */ +#endif + +static int internal_command(unsigned char target, unsigned char lun, + const void *cmnd, + void *buff, int bufflen, int reselect); + +static int incommand; /* + set if arbitration has finished and we are + in some command phase. + */ + +static const void *base_address = NULL; /* + Where the card ROM starts, + used to calculate memory mapped + register location. + */ +#ifdef notyet +static volatile int abort_confirm = 0; +#endif + +static volatile void *st0x_cr_sr; /* + control register write, + status register read. + 256 bytes in length. + + Read is status of SCSI BUS, + as per STAT masks. + + */ + + +static volatile void *st0x_dr; /* + data register, read write + 256 bytes in length. + */ + + +static volatile int st0x_aborted=0; /* + set when we are aborted, ie by a time out, etc. + */ + +static unsigned char controller_type = 0; /* set to SEAGATE for ST0x boards or FD for TMC-8xx boards */ +static unsigned char irq = IRQ; + +#define retcode(result) (((result) << 16) | (message << 8) | status) +#define STATUS (*(volatile unsigned char *) st0x_cr_sr) +#define CONTROL STATUS +#define DATA (*(volatile unsigned char *) st0x_dr) + +void st0x_setup (char *str, int *ints) { + controller_type = SEAGATE; + base_address = (void *) ints[1]; + irq = ints[2]; +} + +void tmc8xx_setup (char *str, int *ints) { + controller_type = FD; + base_address = (void *) ints[1]; + irq = ints[2]; +} + + +#ifndef OVERRIDE +static const char * seagate_bases[] = { + (char *) 0xc8000, (char *) 0xca000, (char *) 0xcc000, + (char *) 0xce000, (char *) 0xdc000, (char *) 0xde000 +}; + +typedef struct { + const char *signature ; + unsigned offset; + unsigned length; + unsigned char type; +} Signature; + +static const Signature signatures[] = { +#ifdef CONFIG_SCSI_SEAGATE +{"ST01 v1.7 (C) Copyright 1987 Seagate", 15, 37, SEAGATE}, +{"SCSI BIOS 2.00 (C) Copyright 1987 Seagate", 15, 40, SEAGATE}, + +/* + * The following two lines are NOT mistakes. One detects ROM revision + * 3.0.0, the other 3.2. Since seagate has only one type of SCSI adapter, + * and this is not going to change, the "SEAGATE" and "SCSI" together + * are probably "good enough" + */ + +{"SEAGATE SCSI BIOS ",16, 17, SEAGATE}, +{"SEAGATE SCSI BIOS ",17, 17, SEAGATE}, + +/* + * However, future domain makes several incompatible SCSI boards, so specific + * signatures must be used. + */ + +{"FUTURE DOMAIN CORP. (C) 1986-1989 V5.0C2/14/89", 5, 46, FD}, +{"FUTURE DOMAIN CORP. (C) 1986-1989 V6.0A7/28/89", 5, 46, FD}, +{"FUTURE DOMAIN CORP. (C) 1986-1990 V6.0105/31/90",5, 47, FD}, +{"FUTURE DOMAIN CORP. (C) 1986-1990 V6.0209/18/90",5, 47, FD}, +{"FUTURE DOMAIN CORP. (C) 1986-1990 V7.009/18/90", 5, 46, FD}, +{"FUTURE DOMAIN CORP. (C) 1992 V8.00.004/02/92", 5, 44, FD}, +{"IBM F1 BIOS V1.1004/30/92", 5, 25, FD}, +{"FUTURE DOMAIN TMC-950", 5, 21, FD}, +#endif /* CONFIG_SCSI_SEAGATE */ +} +; + +#define NUM_SIGNATURES (sizeof(signatures) / sizeof(Signature)) +#endif /* n OVERRIDE */ + +/* + * hostno stores the hostnumber, as told to us by the init routine. + */ + +static int hostno = -1; +static void seagate_reconnect_intr(int, void *, struct pt_regs *); + +#ifdef FAST +static int fast = 1; +#endif + +#ifdef SLOW_HANDSHAKE +/* + * Support for broken devices : + * The Seagate board has a handshaking problem. Namely, a lack + * thereof for slow devices. You can blast 600K/second through + * it if you are polling for each byte, more if you do a blind + * transfer. In the first case, with a fast device, REQ will + * transition high-low or high-low-high before your loop restarts + * and you'll have no problems. In the second case, the board + * will insert wait states for up to 13.2 usecs for REQ to + * transition low->high, and everything will work. + * + * However, there's nothing in the state machine that says + * you *HAVE* to see a high-low-high set of transitions before + * sending the next byte, and slow things like the Trantor CD ROMS + * will break because of this. + * + * So, we need to slow things down, which isn't as simple as it + * seems. We can't slow things down period, because then people + * who don't recompile their kernels will shoot me for ruining + * their performance. We need to do it on a case per case basis. + * + * The best for performance will be to, only for borken devices + * (this is stored on a per-target basis in the scsi_devices array) + * + * Wait for a low->high transition before continuing with that + * transfer. If we timeout, continue anyways. We don't need + * a long timeout, because REQ should only be asserted until the + * corresponding ACK is received and processed. + * + * Note that we can't use the system timer for this, because of + * resolution, and we *really* can't use the timer chip since + * gettimeofday() and the beeper routines use that. So, + * the best thing for us to do will be to calibrate a timing + * loop in the initialization code using the timer chip before + * gettimeofday() can screw with it. + */ + +static int borken_calibration = 0; +static void borken_init (void) { + register int count = 0, start = jiffies + 1, stop = start + 25; + + while (jiffies < start); + for (;jiffies < stop; ++count); + +/* + * Ok, we now have a count for .25 seconds. Convert to a + * count per second and divide by transfer rate in K. + */ + + borken_calibration = (count * 4) / (SLOW_RATE*1024); + + if (borken_calibration < 1) + borken_calibration = 1; +#if (DEBUG & DEBUG_BORKEN) + printk("scsi%d : borken calibrated to %dK/sec, %d cycles per transfer\n", + hostno, BORKEN_RATE, borken_calibration); +#endif +} + +static inline void borken_wait(void) { + register int count; + for (count = borken_calibration; count && (STATUS & STAT_REQ); + --count); +#if (DEBUG & DEBUG_BORKEN) + if (count) + printk("scsi%d : borken timeout\n", hostno); +#endif +} + +#endif /* def SLOW_HANDSHAKE */ + +int seagate_st0x_detect (Scsi_Host_Template * tpnt) + { + struct Scsi_Host *instance; +#ifndef OVERRIDE + int i,j; +#endif + + tpnt->proc_dir = &proc_scsi_seagate; +/* + * First, we try for the manual override. + */ +#ifdef DEBUG + printk("Autodetecting ST0x / TMC-8xx\n"); +#endif + + if (hostno != -1) + { + printk ("ERROR : seagate_st0x_detect() called twice.\n"); + return 0; + } + + /* If the user specified the controller type from the command line, + controller_type will be non-zero, so don't try to detect one */ + + if (!controller_type) { +#ifdef OVERRIDE + base_address = (void *) OVERRIDE; + +/* CONTROLLER is used to override controller (SEAGATE or FD). PM: 07/01/93 */ +#ifdef CONTROLLER + controller_type = CONTROLLER; +#else +#error Please use -DCONTROLLER=SEAGATE or -DCONTROLLER=FD to override controller type +#endif /* CONTROLLER */ +#ifdef DEBUG + printk("Base address overridden to %x, controller type is %s\n", + base_address,controller_type == SEAGATE ? "SEAGATE" : "FD"); +#endif +#else /* OVERRIDE */ +/* + * To detect this card, we simply look for the signature + * from the BIOS version notice in all the possible locations + * of the ROM's. This has a nice side effect of not trashing + * any register locations that might be used by something else. + * + * XXX - note that we probably should be probing the address + * space for the on-board RAM instead. + */ + + for (i = 0; i < (sizeof (seagate_bases) / sizeof (char * )); ++i) + for (j = 0; !base_address && j < NUM_SIGNATURES; ++j) + if (!memcmp ((const void *) (seagate_bases[i] + + signatures[j].offset), (const void *) signatures[j].signature, + signatures[j].length)) { + base_address = (const void *) seagate_bases[i]; + controller_type = signatures[j].type; + } +#endif /* OVERRIDE */ + } /* (! controller_type) */ + + tpnt->this_id = (controller_type == SEAGATE) ? 7 : 6; + tpnt->name = (controller_type == SEAGATE) ? ST0X_ID_STR : FD_ID_STR; + + if (base_address) + { + st0x_cr_sr =(void *) (((const unsigned char *) base_address) + (controller_type == SEAGATE ? 0x1a00 : 0x1c00)); + st0x_dr = (void *) (((const unsigned char *) base_address ) + (controller_type == SEAGATE ? 0x1c00 : 0x1e00)); +#ifdef DEBUG + printk("%s detected. Base address = %x, cr = %x, dr = %x\n", tpnt->name, base_address, st0x_cr_sr, st0x_dr); +#endif +/* + * At all times, we will use IRQ 5. Should also check for IRQ3 if we + * loose our first interrupt. + */ + instance = scsi_register(tpnt, 0); + hostno = instance->host_no; + if (request_irq((int) irq, seagate_reconnect_intr, SA_INTERRUPT, + (controller_type == SEAGATE) ? "seagate" : "tmc-8xx", NULL)) { + printk("scsi%d : unable to allocate IRQ%d\n", + hostno, (int) irq); + return 0; + } + instance->irq = irq; + instance->io_port = (unsigned int) base_address; +#ifdef SLOW_HANDSHAKE + borken_init(); +#endif + + printk("%s options:" +#ifdef ARBITRATE + " ARBITRATE" +#endif +#ifdef SLOW_HANDSHAKE + " SLOW_HANDSHAKE" +#endif +#ifdef FAST +#ifdef FAST32 + " FAST32" +#else + " FAST" +#endif +#endif +#ifdef LINKED + " LINKED" +#endif + "\n", tpnt->name); + return 1; + } + else + { +#ifdef DEBUG + printk("ST0x / TMC-8xx not detected.\n"); +#endif + return 0; + } + } + +const char *seagate_st0x_info(struct Scsi_Host * shpnt) { + static char buffer[64]; + sprintf(buffer, "%s at irq %d, address 0x%05X", + (controller_type == SEAGATE) ? ST0X_ID_STR : FD_ID_STR, + irq, (unsigned int)base_address); + return buffer; +} + +int seagate_st0x_proc_info(char *buffer, char **start, off_t offset, + int length, int hostno, int inout) +{ + const char *info = seagate_st0x_info(NULL); + int len; + int pos; + int begin; + + if (inout) return(-ENOSYS); + + begin = 0; + strcpy(buffer,info); + strcat(buffer,"\n"); + + pos = len = strlen(buffer); + + if (pos<offset) { + len = 0; + begin = pos; + } + + *start = buffer + (offset - begin); + len -= (offset - begin); + if ( len > length ) len = length; + return(len); +} + +/* + * These are our saved pointers for the outstanding command that is + * waiting for a reconnect + */ + +static unsigned char current_target, current_lun; +static unsigned char *current_cmnd, *current_data; +static int current_nobuffs; +static struct scatterlist *current_buffer; +static int current_bufflen; + +#ifdef LINKED + +/* + * linked_connected indicates whether or not we are currently connected to + * linked_target, linked_lun and in an INFORMATION TRANSFER phase, + * using linked commands. + */ + +static int linked_connected = 0; +static unsigned char linked_target, linked_lun; +#endif + + +static void (*done_fn)(Scsi_Cmnd *) = NULL; +static Scsi_Cmnd * SCint = NULL; + +/* + * These control whether or not disconnect / reconnect will be attempted, + * or are being attempted. + */ + +#define NO_RECONNECT 0 +#define RECONNECT_NOW 1 +#define CAN_RECONNECT 2 + +#ifdef LINKED + +/* + * LINKED_RIGHT indicates that we are currently connected to the correct target + * for this command, LINKED_WRONG indicates that we are connected to the wrong + * target. Note that these imply CAN_RECONNECT. + */ + +#define LINKED_RIGHT 3 +#define LINKED_WRONG 4 +#endif + +/* + * This determines if we are expecting to reconnect or not. + */ + +static int should_reconnect = 0; + +/* + * The seagate_reconnect_intr routine is called when a target reselects the + * host adapter. This occurs on the interrupt triggered by the target + * asserting SEL. + */ + +static void seagate_reconnect_intr(int irq, void *dev_id, struct pt_regs *regs) + { + int temp; + Scsi_Cmnd * SCtmp; + +/* enable all other interrupts. */ + sti(); +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : seagate_reconnect_intr() called\n", hostno); +#endif + + if (!should_reconnect) + printk("scsi%d: unexpected interrupt.\n", hostno); + else { + should_reconnect = 0; + +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : internal_command(" + "%d, %08x, %08x, %d, RECONNECT_NOW\n", hostno, + current_target, current_data, current_bufflen); +#endif + + temp = internal_command (current_target, current_lun, + current_cmnd, current_data, current_bufflen, + RECONNECT_NOW); + + if (msg_byte(temp) != DISCONNECT) { + if (done_fn) { +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : done_fn(%d,%08x)", hostno, + hostno, temp); +#endif + if(!SCint) panic("SCint == NULL in seagate"); + SCtmp = SCint; + SCint = NULL; + SCtmp->result = temp; + done_fn (SCtmp); + } else + printk("done_fn() not defined.\n"); + } + } + } + +/* + * The seagate_st0x_queue_command() function provides a queued interface + * to the seagate SCSI driver. Basically, it just passes control onto the + * seagate_command() function, after fixing it so that the done_fn() + * is set to the one passed to the function. We have to be very careful, + * because there are some commands on some devices that do not disconnect, + * and if we simply call the done_fn when the command is done then another + * command is started and queue_command is called again... We end up + * overflowing the kernel stack, and this tends not to be such a good idea. + */ + +static int recursion_depth = 0; + +int seagate_st0x_queue_command (Scsi_Cmnd * SCpnt, void (*done)(Scsi_Cmnd *)) + { + int result, reconnect; + Scsi_Cmnd * SCtmp; + + done_fn = done; + current_target = SCpnt->target; + current_lun = SCpnt->lun; + (const void *) current_cmnd = SCpnt->cmnd; + current_data = (unsigned char *) SCpnt->request_buffer; + current_bufflen = SCpnt->request_bufflen; + SCint = SCpnt; + if(recursion_depth) { + return 0; + }; + recursion_depth++; + do{ +#ifdef LINKED +/* + * Set linked command bit in control field of SCSI command. + */ + + current_cmnd[SCpnt->cmd_len] |= 0x01; + if (linked_connected) { +#if (DEBUG & DEBUG_LINKED) + printk("scsi%d : using linked commands, current I_T_L nexus is ", + hostno); +#endif + if ((linked_target == current_target) && + (linked_lun == current_lun)) { +#if (DEBUG & DEBUG_LINKED) + printk("correct\n"); +#endif + reconnect = LINKED_RIGHT; + } else { +#if (DEBUG & DEBUG_LINKED) + printk("incorrect\n"); +#endif + reconnect = LINKED_WRONG; + } + } else +#endif /* LINKED */ + reconnect = CAN_RECONNECT; + + + + + + result = internal_command (SCint->target, SCint->lun, SCint->cmnd, SCint->request_buffer, + SCint->request_bufflen, + reconnect); + if (msg_byte(result) == DISCONNECT) break; + SCtmp = SCint; + SCint = NULL; + SCtmp->result = result; + done_fn (SCtmp); + } while(SCint); + recursion_depth--; + return 0; + } + +int seagate_st0x_command (Scsi_Cmnd * SCpnt) { + return internal_command (SCpnt->target, SCpnt->lun, SCpnt->cmnd, SCpnt->request_buffer, + SCpnt->request_bufflen, + (int) NO_RECONNECT); +} + +static int internal_command(unsigned char target, unsigned char lun, const void *cmnd, + void *buff, int bufflen, int reselect) { + int len = 0; + unsigned char *data = NULL; + struct scatterlist *buffer = NULL; + int nobuffs = 0; + int clock; + int temp; +#ifdef SLOW_HANDSHAKE + int borken; /* Does the current target require Very Slow I/O ? */ +#endif + + +#if (DEBUG & PHASE_DATAIN) || (DEBUG & PHASE_DATOUT) + int transfered = 0; +#endif + +#if (((DEBUG & PHASE_ETC) == PHASE_ETC) || (DEBUG & PRINT_COMMAND) || \ + (DEBUG & PHASE_EXIT)) + int i; +#endif + +#if ((DEBUG & PHASE_ETC) == PHASE_ETC) + int phase=0, newphase; +#endif + + int done = 0; + unsigned char status = 0; + unsigned char message = 0; + register unsigned char status_read; + + unsigned transfersize = 0, underflow = 0; + + incommand = 0; + st0x_aborted = 0; + +#ifdef SLOW_HANDSHAKE + borken = (int) SCint->device->borken; +#endif + +#if (DEBUG & PRINT_COMMAND) + printk ("scsi%d : target = %d, command = ", hostno, target); + print_command((unsigned char *) cmnd); + printk("\n"); +#endif + +#if (DEBUG & PHASE_RESELECT) + switch (reselect) { + case RECONNECT_NOW : + printk("scsi%d : reconnecting\n", hostno); + break; +#ifdef LINKED + case LINKED_RIGHT : + printk("scsi%d : connected, can reconnect\n", hostno); + break; + case LINKED_WRONG : + printk("scsi%d : connected to wrong target, can reconnect\n", + hostno); + break; +#endif + case CAN_RECONNECT : + printk("scsi%d : allowed to reconnect\n", hostno); + break; + default : + printk("scsi%d : not allowed to reconnect\n", hostno); + } +#endif + + + if (target == (controller_type == SEAGATE ? 7 : 6)) + return DID_BAD_TARGET; + +/* + * We work it differently depending on if this is "the first time," + * or a reconnect. If this is a reselect phase, then SEL will + * be asserted, and we must skip selection / arbitration phases. + */ + + switch (reselect) { + case RECONNECT_NOW: +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : phase RESELECT \n", hostno); +#endif + +/* + * At this point, we should find the logical or of our ID and the original + * target's ID on the BUS, with BSY, SEL, and I/O signals asserted. + * + * After ARBITRATION phase is completed, only SEL, BSY, and the + * target ID are asserted. A valid initiator ID is not on the bus + * until IO is asserted, so we must wait for that. + */ + clock = jiffies + 10; + for (;;) { + temp = STATUS; + if ((temp & STAT_IO) && !(temp & STAT_BSY)) + break; + + if (jiffies > clock) { +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : RESELECT timed out while waiting for IO .\n", + hostno); +#endif + return (DID_BAD_INTR << 16); + } + } + +/* + * After I/O is asserted by the target, we can read our ID and its + * ID off of the BUS. + */ + + if (!((temp = DATA) & (controller_type == SEAGATE ? 0x80 : 0x40))) + { +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : detected reconnect request to different target.\n" + "\tData bus = %d\n", hostno, temp); +#endif + return (DID_BAD_INTR << 16); + } + + if (!(temp & (1 << current_target))) + { + printk("scsi%d : Unexpected reselect interrupt. Data bus = %d\n", + hostno, temp); + return (DID_BAD_INTR << 16); + } + + buffer=current_buffer; + cmnd=current_cmnd; /* WDE add */ + data=current_data; /* WDE add */ + len=current_bufflen; /* WDE add */ + nobuffs=current_nobuffs; + +/* + * We have determined that we have been selected. At this point, + * we must respond to the reselection by asserting BSY ourselves + */ + +#if 1 + CONTROL = (BASE_CMD | CMD_DRVR_ENABLE | CMD_BSY); +#else + CONTROL = (BASE_CMD | CMD_BSY); +#endif + +/* + * The target will drop SEL, and raise BSY, at which time we must drop + * BSY. + */ + + for (clock = jiffies + 10; (jiffies < clock) && (STATUS & STAT_SEL);); + + if (jiffies >= clock) + { + CONTROL = (BASE_CMD | CMD_INTR); +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : RESELECT timed out while waiting for SEL.\n", + hostno); +#endif + return (DID_BAD_INTR << 16); + } + + CONTROL = BASE_CMD; + +/* + * At this point, we have connected with the target and can get + * on with our lives. + */ + break; + case CAN_RECONNECT: + +#ifdef LINKED +/* + * This is a bletcherous hack, just as bad as the Unix #! interpreter stuff. + * If it turns out we are using the wrong I_T_L nexus, the easiest way to deal + * with it is to go into our INFORMATION TRANSFER PHASE code, send a ABORT + * message on MESSAGE OUT phase, and then loop back to here. + */ + +connect_loop : + +#endif + +#if (DEBUG & PHASE_BUS_FREE) + printk ("scsi%d : phase = BUS FREE \n", hostno); +#endif + +/* + * BUS FREE PHASE + * + * On entry, we make sure that the BUS is in a BUS FREE + * phase, by insuring that both BSY and SEL are low for + * at least one bus settle delay. Several reads help + * eliminate wire glitch. + */ + + clock = jiffies + ST0X_BUS_FREE_DELAY; + +#if !defined (ARBITRATE) + while (((STATUS | STATUS | STATUS) & + (STAT_BSY | STAT_SEL)) && + (!st0x_aborted) && (jiffies < clock)); + + if (jiffies > clock) + return retcode(DID_BUS_BUSY); + else if (st0x_aborted) + return retcode(st0x_aborted); +#endif + +#if (DEBUG & PHASE_SELECTION) + printk("scsi%d : phase = SELECTION\n", hostno); +#endif + + clock = jiffies + ST0X_SELECTION_DELAY; + +/* + * Arbitration/selection procedure : + * 1. Disable drivers + * 2. Write HOST adapter address bit + * 3. Set start arbitration. + * 4. We get either ARBITRATION COMPLETE or SELECT at this + * point. + * 5. OR our ID and targets on bus. + * 6. Enable SCSI drivers and asserted SEL and ATTN + */ + +#if defined(ARBITRATE) + cli(); + CONTROL = 0; + DATA = (controller_type == SEAGATE) ? 0x80 : 0x40; + CONTROL = CMD_START_ARB; + sti(); + while (!((status_read = STATUS) & (STAT_ARB_CMPL | STAT_SEL)) && + (jiffies < clock) && !st0x_aborted); + + if (!(status_read & STAT_ARB_CMPL)) { +#if (DEBUG & PHASE_SELECTION) + if (status_read & STAT_SEL) + printk("scsi%d : arbitration lost\n", hostno); + else + printk("scsi%d : arbitration timeout.\n", hostno); +#endif + CONTROL = BASE_CMD; + return retcode(DID_NO_CONNECT); + }; + +#if (DEBUG & PHASE_SELECTION) + printk("scsi%d : arbitration complete\n", hostno); +#endif +#endif + + +/* + * When the SCSI device decides that we're gawking at it, it will + * respond by asserting BUSY on the bus. + * + * Note : the Seagate ST-01/02 product manual says that we should + * twiddle the DATA register before the control register. However, + * this does not work reliably so we do it the other way around. + * + * Probably could be a problem with arbitration too, we really should + * try this with a SCSI protocol or logic analyzer to see what is + * going on. + */ + cli(); + DATA = (unsigned char) ((1 << target) | (controller_type == SEAGATE ? 0x80 : 0x40)); + CONTROL = BASE_CMD | CMD_DRVR_ENABLE | CMD_SEL | + (reselect ? CMD_ATTN : 0); + sti(); + while (!((status_read = STATUS) & STAT_BSY) && + (jiffies < clock) && !st0x_aborted) + +#if 0 && (DEBUG & PHASE_SELECTION) + { + temp = clock - jiffies; + + if (!(jiffies % 5)) + printk("seagate_st0x_timeout : %d \r",temp); + + } + printk("Done. \n"); + printk("scsi%d : status = %02x, seagate_st0x_timeout = %d, aborted = %02x \n", + hostno, status_read, temp, st0x_aborted); +#else + ; +#endif + + + if ((jiffies >= clock) && !(status_read & STAT_BSY)) + { +#if (DEBUG & PHASE_SELECTION) + printk ("scsi%d : NO CONNECT with target %d, status = %x \n", + hostno, target, STATUS); +#endif + return retcode(DID_NO_CONNECT); + } + +/* + * If we have been aborted, and we have a command in progress, IE the + * target still has BSY asserted, then we will reset the bus, and + * notify the midlevel driver to expect sense. + */ + + if (st0x_aborted) { + CONTROL = BASE_CMD; + if (STATUS & STAT_BSY) { + printk("scsi%d : BST asserted after we've been aborted.\n", + hostno); + seagate_st0x_reset(NULL, 0); + return retcode(DID_RESET); + } + return retcode(st0x_aborted); + } + +/* Establish current pointers. Take into account scatter / gather */ + + if ((nobuffs = SCint->use_sg)) { +#if (DEBUG & DEBUG_SG) + { + int i; + printk("scsi%d : scatter gather requested, using %d buffers.\n", + hostno, nobuffs); + for (i = 0; i < nobuffs; ++i) + printk("scsi%d : buffer %d address = %08x length = %d\n", + hostno, i, buffer[i].address, buffer[i].length); + } +#endif + + buffer = (struct scatterlist *) SCint->buffer; + len = buffer->length; + data = (unsigned char *) buffer->address; + } else { +#if (DEBUG & DEBUG_SG) + printk("scsi%d : scatter gather not requested.\n", hostno); +#endif + buffer = NULL; + len = SCint->request_bufflen; + data = (unsigned char *) SCint->request_buffer; + } + +#if (DEBUG & (PHASE_DATAIN | PHASE_DATAOUT)) + printk("scsi%d : len = %d\n", hostno, len); +#endif + + break; +#ifdef LINKED + case LINKED_RIGHT: + break; + case LINKED_WRONG: + break; +#endif + } + +/* + * There are several conditions under which we wish to send a message : + * 1. When we are allowing disconnect / reconnect, and need to establish + * the I_T_L nexus via an IDENTIFY with the DiscPriv bit set. + * + * 2. When we are doing linked commands, are have the wrong I_T_L nexus + * established and want to send an ABORT message. + */ + + + CONTROL = BASE_CMD | CMD_DRVR_ENABLE | + (((reselect == CAN_RECONNECT) +#ifdef LINKED + || (reselect == LINKED_WRONG) +#endif + ) ? CMD_ATTN : 0) ; + +/* + * INFORMATION TRANSFER PHASE + * + * The nasty looking read / write inline assembler loops we use for + * DATAIN and DATAOUT phases are approximately 4-5 times as fast as + * the 'C' versions - since we're moving 1024 bytes of data, this + * really adds up. + */ + +#if ((DEBUG & PHASE_ETC) == PHASE_ETC) + printk("scsi%d : phase = INFORMATION TRANSFER\n", hostno); +#endif + + incommand = 1; + transfersize = SCint->transfersize; + underflow = SCint->underflow; + + +/* + * Now, we poll the device for status information, + * and handle any requests it makes. Note that since we are unsure of + * how much data will be flowing across the system, etc and cannot + * make reasonable timeouts, that we will instead have the midlevel + * driver handle any timeouts that occur in this phase. + */ + + while (((status_read = STATUS) & STAT_BSY) && !st0x_aborted && !done) + { +#ifdef PARITY + if (status_read & STAT_PARITY) + { + printk("scsi%d : got parity error\n", hostno); + st0x_aborted = DID_PARITY; + } +#endif + + if (status_read & STAT_REQ) + { +#if ((DEBUG & PHASE_ETC) == PHASE_ETC) + if ((newphase = (status_read & REQ_MASK)) != phase) + { + phase = newphase; + switch (phase) + { + case REQ_DATAOUT: + printk("scsi%d : phase = DATA OUT\n", + hostno); + break; + case REQ_DATAIN : + printk("scsi%d : phase = DATA IN\n", + hostno); + break; + case REQ_CMDOUT : + printk("scsi%d : phase = COMMAND OUT\n", + hostno); + break; + case REQ_STATIN : + printk("scsi%d : phase = STATUS IN\n", + hostno); + break; + case REQ_MSGOUT : + printk("scsi%d : phase = MESSAGE OUT\n", + hostno); + break; + case REQ_MSGIN : + printk("scsi%d : phase = MESSAGE IN\n", + hostno); + break; + default : + printk("scsi%d : phase = UNKNOWN\n", + hostno); + st0x_aborted = DID_ERROR; + } + } +#endif + switch (status_read & REQ_MASK) + { + case REQ_DATAOUT : +/* + * If we are in fast mode, then we simply splat the data out + * in word-sized chunks as fast as we can. + */ + +#ifdef FAST +if (!len) { +#if 0 + printk("scsi%d: underflow to target %d lun %d \n", + hostno, target, lun); + st0x_aborted = DID_ERROR; + fast = 0; +#endif + break; +} + +if (fast && transfersize && !(len % transfersize) && (len >= transfersize) +#ifdef FAST32 + && !(transfersize % 4) +#endif + ) { +#if (DEBUG & DEBUG_FAST) + printk("scsi%d : FAST transfer, underflow = %d, transfersize = %d\n" + " len = %d, data = %08x\n", hostno, SCint->underflow, + SCint->transfersize, len, data); +#endif + + __asm__(" + cld; +" +#ifdef FAST32 +" shr $2, %%ecx; +1: lodsl; + movl %%eax, (%%edi); +" +#else +"1: lodsb; + movb %%al, (%%edi); +" +#endif +" loop 1b;" : : + /* input */ + "D" (st0x_dr), "S" (data), "c" (SCint->transfersize) : + /* clobbered */ + "eax", "ecx", "esi" ); + + len -= transfersize; + data += transfersize; + +#if (DEBUG & DEBUG_FAST) + printk("scsi%d : FAST transfer complete len = %d data = %08x\n", + hostno, len, data); +#endif + + +} else +#endif + +{ +/* + * We loop as long as we are in a data out phase, there is data to send, + * and BSY is still active. + */ + __asm__ ( + +/* + Local variables : + len = ecx + data = esi + st0x_cr_sr = ebx + st0x_dr = edi + + Test for any data here at all. +*/ + "\torl %%ecx, %%ecx + jz 2f + + cld + + movl " SYMBOL_NAME_STR(st0x_cr_sr) ", %%ebx + movl " SYMBOL_NAME_STR(st0x_dr) ", %%edi + +1: movb (%%ebx), %%al\n" +/* + Test for BSY +*/ + + "\ttest $1, %%al + jz 2f\n" + +/* + Test for data out phase - STATUS & REQ_MASK should be REQ_DATAOUT, which is 0. +*/ + "\ttest $0xe, %%al + jnz 2f \n" +/* + Test for REQ +*/ + "\ttest $0x10, %%al + jz 1b + lodsb + movb %%al, (%%edi) + loop 1b + +2: + ": +/* output */ +"=S" (data), "=c" (len) : +/* input */ +"0" (data), "1" (len) : +/* clobbered */ +"eax", "ebx", "edi"); +} + + if (!len && nobuffs) { + --nobuffs; + ++buffer; + len = buffer->length; + data = (unsigned char *) buffer->address; +#if (DEBUG & DEBUG_SG) + printk("scsi%d : next scatter-gather buffer len = %d address = %08x\n", + hostno, len, data); +#endif + } + break; + + case REQ_DATAIN : +#ifdef SLOW_HANDSHAKE + if (borken) { +#if (DEBUG & (PHASE_DATAIN)) + transfered += len; +#endif + for (; len && (STATUS & (REQ_MASK | STAT_REQ)) == (REQ_DATAIN | + STAT_REQ); --len) { + *data++ = DATA; + borken_wait(); +} +#if (DEBUG & (PHASE_DATAIN)) + transfered -= len; +#endif + } else +#endif +#ifdef FAST +if (fast && transfersize && !(len % transfersize) && (len >= transfersize) +#ifdef FAST32 + && !(transfersize % 4) +#endif + ) { +#if (DEBUG & DEBUG_FAST) + printk("scsi%d : FAST transfer, underflow = %d, transfersize = %d\n" + " len = %d, data = %08x\n", hostno, SCint->underflow, + SCint->transfersize, len, data); +#endif + __asm__(" + cld; +" +#ifdef FAST32 +" shr $2, %%ecx; +1: movl (%%esi), %%eax; + stosl; +" +#else +"1: movb (%%esi), %%al; + stosb; +" +#endif + +" loop 1b;" : : + /* input */ + "S" (st0x_dr), "D" (data), "c" (SCint->transfersize) : + /* clobbered */ + "eax", "ecx", "edi"); + + len -= transfersize; + data += transfersize; + +#if (DEBUG & PHASE_DATAIN) + printk("scsi%d: transfered += %d\n", hostno, transfersize); + transfered += transfersize; +#endif + +#if (DEBUG & DEBUG_FAST) + printk("scsi%d : FAST transfer complete len = %d data = %08x\n", + hostno, len, data); +#endif + +} else +#endif +{ + +#if (DEBUG & PHASE_DATAIN) + printk("scsi%d: transfered += %d\n", hostno, len); + transfered += len; /* Assume we'll transfer it all, then + subtract what we *didn't* transfer */ +#endif + +/* + * We loop as long as we are in a data in phase, there is room to read, + * and BSY is still active + */ + + __asm__ ( +/* + Local variables : + ecx = len + edi = data + esi = st0x_cr_sr + ebx = st0x_dr + + Test for room to read +*/ + "\torl %%ecx, %%ecx + jz 2f + + cld + movl " SYMBOL_NAME_STR(st0x_cr_sr) ", %%esi + movl " SYMBOL_NAME_STR(st0x_dr) ", %%ebx + +1: movb (%%esi), %%al\n" +/* + Test for BSY +*/ + + "\ttest $1, %%al + jz 2f\n" + +/* + Test for data in phase - STATUS & REQ_MASK should be REQ_DATAIN, = STAT_IO, which is 4. +*/ + "\tmovb $0xe, %%ah + andb %%al, %%ah + cmpb $0x04, %%ah + jne 2f\n" + +/* + Test for REQ +*/ + "\ttest $0x10, %%al + jz 1b + + movb (%%ebx), %%al + stosb + loop 1b\n" + +"2:\n" + : +/* output */ +"=D" (data), "=c" (len) : +/* input */ +"0" (data), "1" (len) : +/* clobbered */ +"eax","ebx", "esi"); + +#if (DEBUG & PHASE_DATAIN) + printk("scsi%d: transfered -= %d\n", hostno, len); + transfered -= len; /* Since we assumed all of Len got + * transfered, correct our mistake */ +#endif +} + + if (!len && nobuffs) { + --nobuffs; + ++buffer; + len = buffer->length; + data = (unsigned char *) buffer->address; +#if (DEBUG & DEBUG_SG) + printk("scsi%d : next scatter-gather buffer len = %d address = %08x\n", + hostno, len, data); +#endif + } + + break; + + case REQ_CMDOUT : + while (((status_read = STATUS) & STAT_BSY) && + ((status_read & REQ_MASK) == REQ_CMDOUT)) + if (status_read & STAT_REQ) { + DATA = *(const unsigned char *) cmnd; + cmnd = 1+(const unsigned char *) cmnd; +#ifdef SLOW_HANDSHAKE + if (borken) + borken_wait(); +#endif + } + break; + + case REQ_STATIN : + status = DATA; + break; + + case REQ_MSGOUT : +/* + * We can only have sent a MSG OUT if we requested to do this + * by raising ATTN. So, we must drop ATTN. + */ + + CONTROL = BASE_CMD | CMD_DRVR_ENABLE; +/* + * If we are reconnecting, then we must send an IDENTIFY message in + * response to MSGOUT. + */ + switch (reselect) { + case CAN_RECONNECT: + DATA = IDENTIFY(1, lun); + +#if (DEBUG & (PHASE_RESELECT | PHASE_MSGOUT)) + printk("scsi%d : sent IDENTIFY message.\n", hostno); +#endif + break; +#ifdef LINKED + case LINKED_WRONG: + DATA = ABORT; + linked_connected = 0; + reselect = CAN_RECONNECT; + goto connect_loop; +#if (DEBUG & (PHASE_MSGOUT | DEBUG_LINKED)) + printk("scsi%d : sent ABORT message to cancel incorrect I_T_L nexus.\n", hostno); +#endif +#endif /* LINKED */ +#if (DEBUG & DEBUG_LINKED) + printk("correct\n"); +#endif + default: + DATA = NOP; + printk("scsi%d : target %d requested MSGOUT, sent NOP message.\n", hostno, target); + } + break; + + case REQ_MSGIN : + switch (message = DATA) { + case DISCONNECT : + should_reconnect = 1; + current_data = data; /* WDE add */ + current_buffer = buffer; + current_bufflen = len; /* WDE add */ + current_nobuffs = nobuffs; +#ifdef LINKED + linked_connected = 0; +#endif + done=1; +#if (DEBUG & (PHASE_RESELECT | PHASE_MSGIN)) + printk("scsi%d : disconnected.\n", hostno); +#endif + break; + +#ifdef LINKED + case LINKED_CMD_COMPLETE: + case LINKED_FLG_CMD_COMPLETE: +#endif + case COMMAND_COMPLETE : +/* + * Note : we should check for underflow here. + */ +#if (DEBUG & PHASE_MSGIN) + printk("scsi%d : command complete.\n", hostno); +#endif + done = 1; + break; + case ABORT : +#if (DEBUG & PHASE_MSGIN) + printk("scsi%d : abort message.\n", hostno); +#endif + done=1; + break; + case SAVE_POINTERS : + current_buffer = buffer; + current_bufflen = len; /* WDE add */ + current_data = data; /* WDE mod */ + current_nobuffs = nobuffs; +#if (DEBUG & PHASE_MSGIN) + printk("scsi%d : pointers saved.\n", hostno); +#endif + break; + case RESTORE_POINTERS: + buffer=current_buffer; + cmnd=current_cmnd; + data=current_data; /* WDE mod */ + len=current_bufflen; + nobuffs=current_nobuffs; +#if (DEBUG & PHASE_MSGIN) + printk("scsi%d : pointers restored.\n", hostno); +#endif + break; + default: + +/* + * IDENTIFY distinguishes itself from the other messages by setting the + * high byte. + * + * Note : we need to handle at least one outstanding command per LUN, + * and need to hash the SCSI command for that I_T_L nexus based on the + * known ID (at this point) and LUN. + */ + + if (message & 0x80) { +#if (DEBUG & PHASE_MSGIN) + printk("scsi%d : IDENTIFY message received from id %d, lun %d.\n", + hostno, target, message & 7); +#endif + } else { + +/* + * We should go into a MESSAGE OUT phase, and send a MESSAGE_REJECT + * if we run into a message that we don't like. The seagate driver + * needs some serious restructuring first though. + */ + +#if (DEBUG & PHASE_MSGIN) + printk("scsi%d : unknown message %d from target %d.\n", + hostno, message, target); +#endif + } + } + break; + + default : + printk("scsi%d : unknown phase.\n", hostno); + st0x_aborted = DID_ERROR; + } + +#ifdef SLOW_HANDSHAKE +/* + * I really don't care to deal with borken devices in each single + * byte transfer case (ie, message in, message out, status), so + * I'll do the wait here if necessary. + */ + if (borken) + borken_wait(); +#endif + + } /* if ends */ + } /* while ends */ + +#if (DEBUG & (PHASE_DATAIN | PHASE_DATAOUT | PHASE_EXIT)) + printk("scsi%d : Transfered %d bytes\n", hostno, transfered); +#endif + +#if (DEBUG & PHASE_EXIT) +#if 0 /* Doesn't work for scatter / gather */ + printk("Buffer : \n"); + for (i = 0; i < 20; ++i) + printk ("%02x ", ((unsigned char *) data)[i]); /* WDE mod */ + printk("\n"); +#endif + printk("scsi%d : status = ", hostno); + print_status(status); + printk("message = %02x\n", message); +#endif + + +/* We shouldn't reach this until *after* BSY has been deasserted */ +#ifdef notyet + if (st0x_aborted) { + if (STATUS & STAT_BSY) { + seagate_st0x_reset(NULL); + st0x_aborted = DID_RESET; + } + abort_confirm = 1; + } +#endif + +#ifdef LINKED +else { +/* + * Fix the message byte so that unsuspecting high level drivers don't + * puke when they see a LINKED COMMAND message in place of the COMMAND + * COMPLETE they may be expecting. Shouldn't be necessary, but it's + * better to be on the safe side. + * + * A non LINKED* message byte will indicate that the command completed, + * and we are now disconnected. + */ + + switch (message) { + case LINKED_CMD_COMPLETE : + case LINKED_FLG_CMD_COMPLETE : + message = COMMAND_COMPLETE; + linked_target = current_target; + linked_lun = current_lun; + linked_connected = 1; +#if (DEBUG & DEBUG_LINKED) + printk("scsi%d : keeping I_T_L nexus established for linked command.\n", + hostno); +#endif +/* + * We also will need to adjust status to accommodate intermediate conditions. + */ + if ((status == INTERMEDIATE_GOOD) || + (status == INTERMEDIATE_C_GOOD)) + status = GOOD; + + break; +/* + * We should also handle what are "normal" termination messages + * here (ABORT, BUS_DEVICE_RESET?, and COMMAND_COMPLETE individually, + * and flake if things aren't right. + */ + + default : +#if (DEBUG & DEBUG_LINKED) + printk("scsi%d : closing I_T_L nexus.\n", hostno); +#endif + linked_connected = 0; + } + } +#endif /* LINKED */ + + + + + if (should_reconnect) { +#if (DEBUG & PHASE_RESELECT) + printk("scsi%d : exiting seagate_st0x_queue_command() with reconnect enabled.\n", + hostno); +#endif + CONTROL = BASE_CMD | CMD_INTR ; + } else + CONTROL = BASE_CMD; + + return retcode (st0x_aborted); + } + +int seagate_st0x_abort (Scsi_Cmnd * SCpnt) + { + st0x_aborted = DID_ABORT; + + return SCSI_ABORT_PENDING; + } + +/* + the seagate_st0x_reset function resets the SCSI bus +*/ + +int seagate_st0x_reset (Scsi_Cmnd * SCpnt, unsigned int reset_flags) + { + unsigned clock; + /* + No timeouts - this command is going to fail because + it was reset. + */ + +#ifdef DEBUG + printk("In seagate_st0x_reset()\n"); +#endif + + + /* assert RESET signal on SCSI bus. */ + + CONTROL = BASE_CMD | CMD_RST; + clock=jiffies+2; + + + /* Wait. */ + + while (jiffies < clock); + + CONTROL = BASE_CMD; + + st0x_aborted = DID_RESET; + +#ifdef DEBUG + printk("SCSI bus reset.\n"); +#endif + return SCSI_RESET_WAKEUP; + } + +#include <asm/segment.h> +#include "sd.h" +#include <scsi/scsi_ioctl.h> + +int seagate_st0x_biosparam(Disk * disk, kdev_t dev, int* ip) { + unsigned char buf[256 + sizeof(int) * 2], cmd[6], *data, *page; + int *sizes, result, formatted_sectors, total_sectors; + int cylinders, heads, sectors; + int capacity; + +/* + * Only SCSI-I CCS drives and later implement the necessary mode sense + * pages. + */ + + if (disk->device->scsi_level < 2) + return -1; + + sizes = (int *) buf; + data = (unsigned char *) (sizes + 2); + + cmd[0] = MODE_SENSE; + cmd[1] = (disk->device->lun << 5) & 0xe5; + cmd[2] = 0x04; /* Read page 4, rigid disk geometry page current values */ + cmd[3] = 0; + cmd[4] = 255; + cmd[5] = 0; + +/* + * We are transferring 0 bytes in the out direction, and expect to get back + * 24 bytes for each mode page. + */ + + sizes[0] = 0; + sizes[1] = 256; + + memcpy (data, cmd, 6); + + if (!(result = kernel_scsi_ioctl (disk->device, SCSI_IOCTL_SEND_COMMAND, (void *) buf))) { +/* + * The mode page lies beyond the MODE SENSE header, with length 4, and + * the BLOCK DESCRIPTOR, with length header[3]. + */ + + page = data + 4 + data[3]; + heads = (int) page[5]; + cylinders = (page[2] << 16) | (page[3] << 8) | page[4]; + + cmd[2] = 0x03; /* Read page 3, format page current values */ + memcpy (data, cmd, 6); + + if (!(result = kernel_scsi_ioctl (disk->device, SCSI_IOCTL_SEND_COMMAND, (void *) buf))) { + page = data + 4 + data[3]; + sectors = (page[10] << 8) | page[11]; + + +/* + * Get the total number of formatted sectors from the block descriptor, + * so we can tell how many are being used for alternates. + */ + + formatted_sectors = (data[4 + 1] << 16) | (data[4 + 2] << 8) | + data[4 + 3] ; + + total_sectors = (heads * cylinders * sectors); + +/* + * Adjust the real geometry by subtracting + * (spare sectors / (heads * tracks)) cylinders from the number of cylinders. + * + * It appears that the CE cylinder CAN be a partial cylinder. + */ + + +printk("scsi%d : heads = %d cylinders = %d sectors = %d total = %d formatted = %d\n", + hostno, heads, cylinders, sectors, total_sectors, formatted_sectors); + + if (!heads || !sectors || !cylinders) + result = -1; + else + cylinders -= ((total_sectors - formatted_sectors) / (heads * sectors)); + +/* + * Now, we need to do a sanity check on the geometry to see if it is + * BIOS compatible. The maximum BIOS geometry is 1024 cylinders * + * 256 heads * 64 sectors. + */ + + if ((cylinders > 1024) || (sectors > 64)) { + /* The Seagate's seem to have some mapping + * Multiple heads * sectors * cyl to get capacity + * Then start rounding down. */ + capacity = heads * sectors * cylinders; + sectors = 17; /* Old MFM Drives use this, so does the Seagate */ + heads = 2; + capacity = capacity / sectors; + while (cylinders > 1024) + { + heads *= 2; /* For some reason, they go in multiples */ + cylinders = capacity / heads; + } + } + ip[0] = heads; + ip[1] = sectors; + ip[2] = cylinders; + +/* + * There should be an alternate mapping for things the seagate doesn't + * understand, but I couldn't say what it is with reasonable certainty. + */ + + } + } + + return result; +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = SEAGATE_ST0X; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/seagate.h b/linux/src/drivers/scsi/seagate.h new file mode 100644 index 00000000..da18dbea --- /dev/null +++ b/linux/src/drivers/scsi/seagate.h @@ -0,0 +1,139 @@ +/* + * seagate.h Copyright (C) 1992 Drew Eckhardt + * low level scsi driver header for ST01/ST02 by + * Drew Eckhardt + * + * <drew@colorado.edu> + */ + +#ifndef _SEAGATE_H + #define SEAGATE_H +/* + $Header +*/ +#ifndef ASM +int seagate_st0x_detect(Scsi_Host_Template *); +int seagate_st0x_command(Scsi_Cmnd *); +int seagate_st0x_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); + +int seagate_st0x_abort(Scsi_Cmnd *); +const char *seagate_st0x_info(struct Scsi_Host *); +int seagate_st0x_reset(Scsi_Cmnd *, unsigned int); +int seagate_st0x_proc_info(char *,char **,off_t,int,int,int); + +#ifndef NULL + #define NULL 0 +#endif + +#include <linux/kdev_t.h> +int seagate_st0x_biosparam(Disk *, kdev_t, int*); + +#define SEAGATE_ST0X { NULL, NULL, NULL, seagate_st0x_proc_info, \ + NULL, seagate_st0x_detect, \ + NULL, \ + seagate_st0x_info, seagate_st0x_command, \ + seagate_st0x_queue_command, seagate_st0x_abort, \ + seagate_st0x_reset, NULL, seagate_st0x_biosparam, \ + 1, 7, SG_ALL, 1, 0, 0, DISABLE_CLUSTERING} +#endif + + +/* + defining PARITY causes parity data to be checked +*/ + +#define PARITY + + +/* + Thanks to Brian Antoine for the example code in his Messy-Loss ST-01 + driver, and Mitsugu Suzuki for information on the ST-01 + SCSI host. +*/ + +/* + CONTROL defines +*/ + +#define CMD_RST 0x01 +#define CMD_SEL 0x02 +#define CMD_BSY 0x04 +#define CMD_ATTN 0x08 +#define CMD_START_ARB 0x10 +#define CMD_EN_PARITY 0x20 +#define CMD_INTR 0x40 +#define CMD_DRVR_ENABLE 0x80 + +/* + STATUS +*/ + +#define STAT_BSY 0x01 +#define STAT_MSG 0x02 +#define STAT_IO 0x04 +#define STAT_CD 0x08 +#define STAT_REQ 0x10 +#define STAT_SEL 0x20 +#define STAT_PARITY 0x40 +#define STAT_ARB_CMPL 0x80 + +/* + REQUESTS +*/ + +#define REQ_MASK (STAT_CD | STAT_IO | STAT_MSG) +#define REQ_DATAOUT 0 +#define REQ_DATAIN STAT_IO +#define REQ_CMDOUT STAT_CD +#define REQ_STATIN (STAT_CD | STAT_IO) +#define REQ_MSGOUT (STAT_MSG | STAT_CD) +#define REQ_MSGIN (STAT_MSG | STAT_CD | STAT_IO) + +extern volatile int seagate_st0x_timeout; + +#ifdef PARITY + #define BASE_CMD CMD_EN_PARITY +#else + #define BASE_CMD 0 +#endif + +/* + Debugging code +*/ + +#define PHASE_BUS_FREE 1 +#define PHASE_ARBITRATION 2 +#define PHASE_SELECTION 4 +#define PHASE_DATAIN 8 +#define PHASE_DATAOUT 0x10 +#define PHASE_CMDOUT 0x20 +#define PHASE_MSGIN 0x40 +#define PHASE_MSGOUT 0x80 +#define PHASE_STATUSIN 0x100 +#define PHASE_ETC (PHASE_DATAIN | PHASE_DATA_OUT | PHASE_CMDOUT | PHASE_MSGIN | PHASE_MSGOUT | PHASE_STATUSIN) +#define PRINT_COMMAND 0x200 +#define PHASE_EXIT 0x400 +#define PHASE_RESELECT 0x800 +#define DEBUG_FAST 0x1000 +#define DEBUG_SG 0x2000 +#define DEBUG_LINKED 0x4000 +#define DEBUG_BORKEN 0x8000 + +/* + * Control options - these are timeouts specified in .01 seconds. + */ + +/* 30, 20 work */ +#define ST0X_BUS_FREE_DELAY 25 +#define ST0X_SELECTION_DELAY 25 + +#define eoi() __asm__("push %%eax\nmovb $0x20, %%al\noutb %%al, $0x20\npop %%eax"::) + +#define SEAGATE 1 /* these determine the type of the controller */ +#define FD 2 + +#define ST0X_ID_STR "Seagate ST-01/ST-02" +#define FD_ID_STR "TMC-8XX/TMC-950" + +#endif + diff --git a/linux/src/drivers/scsi/sr.c b/linux/src/drivers/scsi/sr.c new file mode 100644 index 00000000..446a221b --- /dev/null +++ b/linux/src/drivers/scsi/sr.c @@ -0,0 +1,1281 @@ +/* + * sr.c Copyright (C) 1992 David Giller + * Copyright (C) 1993, 1994, 1995 Eric Youngdale + * + * adapted from: + * sd.c Copyright (C) 1992 Drew Eckhardt + * Linux scsi disk driver by + * Drew Eckhardt <drew@colorado.edu> + * + * Modified by Eric Youngdale ericy@cais.com to + * add scatter-gather, multiple outstanding request, and other + * enhancements. + * + * Modified by Eric Youngdale eric@aib.com to support loadable + * low-level scsi drivers. + * + * Modified by Thomas Quinot thomas@melchior.cuivre.fdn.fr to + * provide auto-eject. + * + */ + +#include <linux/module.h> + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/cdrom.h> +#include <linux/interrupt.h> +#include <asm/system.h> + +#define MAJOR_NR SCSI_CDROM_MAJOR +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "sr.h" +#include <scsi/scsi_ioctl.h> /* For the door lock/unlock commands */ +#include "constants.h" + +#define MAX_RETRIES 3 +#define SR_TIMEOUT (30 * HZ) + +static int sr_init(void); +static void sr_finish(void); +static int sr_attach(Scsi_Device *); +static int sr_detect(Scsi_Device *); +static void sr_detach(Scsi_Device *); + +struct Scsi_Device_Template sr_template = {NULL, "cdrom", "sr", NULL, TYPE_ROM, + SCSI_CDROM_MAJOR, 0, 0, 0, 1, + sr_detect, sr_init, + sr_finish, sr_attach, sr_detach}; + +Scsi_CD * scsi_CDs = NULL; +static int * sr_sizes; + +static int * sr_blocksizes; + +static int sr_open(struct inode *, struct file *); +void get_sectorsize(int); +void sr_photocd(struct inode *); + +extern int sr_ioctl(struct inode *, struct file *, unsigned int, unsigned long); + +void requeue_sr_request (Scsi_Cmnd * SCpnt); +static int check_cdrom_media_change(kdev_t); + +static void sr_release(struct inode * inode, struct file * file) +{ + sync_dev(inode->i_rdev); + if(! --scsi_CDs[MINOR(inode->i_rdev)].device->access_count) + { + sr_ioctl(inode, NULL, SCSI_IOCTL_DOORUNLOCK, 0); + if (scsi_CDs[MINOR(inode->i_rdev)].auto_eject) + sr_ioctl(inode, NULL, CDROMEJECT, 0); + } + if (scsi_CDs[MINOR(inode->i_rdev)].device->host->hostt->usage_count) + (*scsi_CDs[MINOR(inode->i_rdev)].device->host->hostt->usage_count)--; + if(sr_template.usage_count) (*sr_template.usage_count)--; +} + +static struct file_operations sr_fops = +{ + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + sr_ioctl, /* ioctl */ + NULL, /* mmap */ + sr_open, /* special open code */ + sr_release, /* release */ + NULL, /* fsync */ + NULL, /* fasync */ + check_cdrom_media_change, /* Disk change */ + NULL /* revalidate */ +}; + +/* + * This function checks to see if the media has been changed in the + * CDROM drive. It is possible that we have already sensed a change, + * or the drive may have sensed one and not yet reported it. We must + * be ready for either case. This function always reports the current + * value of the changed bit. If flag is 0, then the changed bit is reset. + * This function could be done as an ioctl, but we would need to have + * an inode for that to work, and we do not always have one. + */ + +int check_cdrom_media_change(kdev_t full_dev){ + int retval, target; + struct inode inode; + int flag = 0; + + target = MINOR(full_dev); + + if (target >= sr_template.nr_dev) { + printk("CD-ROM request error: invalid device.\n"); + return 0; + }; + + inode.i_rdev = full_dev; /* This is all we really need here */ + retval = sr_ioctl(&inode, NULL, SCSI_IOCTL_TEST_UNIT_READY, 0); + + if(retval){ /* Unable to test, unit probably not ready. This usually + * means there is no disc in the drive. Mark as changed, + * and we will figure it out later once the drive is + * available again. */ + + scsi_CDs[target].device->changed = 1; + return 1; /* This will force a flush, if called from + * check_disk_change */ + }; + + retval = scsi_CDs[target].device->changed; + if(!flag) { + scsi_CDs[target].device->changed = 0; + /* If the disk changed, the capacity will now be different, + * so we force a re-read of this information */ + if (retval) scsi_CDs[target].needs_sector_size = 1; + }; + return retval; +} + +/* + * rw_intr is the interrupt routine for the device driver. It will be notified on the + * end of a SCSI read / write, and will take on of several actions based on success or failure. + */ + +static void rw_intr (Scsi_Cmnd * SCpnt) +{ + int result = SCpnt->result; + int this_count = SCpnt->this_count; + int good_sectors = (result == 0 ? this_count : 0); + int block_sectors = 0; + +#ifdef DEBUG + printk("sr.c done: %x %x\n",result, SCpnt->request.bh->b_data); +#endif + /* + Handle MEDIUM ERRORs or VOLUME OVERFLOWs that indicate partial success. + Since this is a relatively rare error condition, no care is taken to + avoid unnecessary additional work such as memcpy's that could be avoided. + */ + + if (driver_byte(result) != 0 && /* An error occurred */ + SCpnt->sense_buffer[0] == 0xF0 && /* Sense data is valid */ + (SCpnt->sense_buffer[2] == MEDIUM_ERROR || + SCpnt->sense_buffer[2] == VOLUME_OVERFLOW || + SCpnt->sense_buffer[2] == ILLEGAL_REQUEST)) + { + long error_sector = (SCpnt->sense_buffer[3] << 24) | + (SCpnt->sense_buffer[4] << 16) | + (SCpnt->sense_buffer[5] << 8) | + SCpnt->sense_buffer[6]; + int device_nr = DEVICE_NR(SCpnt->request.rq_dev); + if (SCpnt->request.bh != NULL) + block_sectors = SCpnt->request.bh->b_size >> 9; + if (block_sectors < 4) block_sectors = 4; + if (scsi_CDs[device_nr].sector_size == 2048) + error_sector <<= 2; + error_sector &= ~ (block_sectors - 1); + good_sectors = error_sector - SCpnt->request.sector; + if (good_sectors < 0 || good_sectors >= this_count) + good_sectors = 0; + /* + The SCSI specification allows for the value returned by READ + CAPACITY to be up to 75 2K sectors past the last readable + block. Therefore, if we hit a medium error within the last + 75 2K sectors, we decrease the saved size value. + */ + if ((error_sector >> 1) < sr_sizes[device_nr] && + scsi_CDs[device_nr].capacity - error_sector < 4*75) + sr_sizes[device_nr] = error_sector >> 1; + } + + if (good_sectors > 0) + { /* Some sectors were read successfully. */ + if (SCpnt->use_sg == 0) { + if (SCpnt->buffer != SCpnt->request.buffer) + { + int offset; + offset = (SCpnt->request.sector % 4) << 9; + memcpy((char *)SCpnt->request.buffer, + (char *)SCpnt->buffer + offset, + good_sectors << 9); + /* Even though we are not using scatter-gather, we look + * ahead and see if there is a linked request for the + * other half of this buffer. If there is, then satisfy + * it. */ + if((offset == 0) && good_sectors == 2 && + SCpnt->request.nr_sectors > good_sectors && + SCpnt->request.bh && + SCpnt->request.bh->b_reqnext && + SCpnt->request.bh->b_reqnext->b_size == 1024) { + memcpy((char *)SCpnt->request.bh->b_reqnext->b_data, + (char *)SCpnt->buffer + 1024, + 1024); + good_sectors += 2; + }; + + scsi_free(SCpnt->buffer, 2048); + } + } else { + struct scatterlist * sgpnt; + int i; + sgpnt = (struct scatterlist *) SCpnt->buffer; + for(i=0; i<SCpnt->use_sg; i++) { + if (sgpnt[i].alt_address) { + if (sgpnt[i].alt_address != sgpnt[i].address) { + memcpy(sgpnt[i].alt_address, sgpnt[i].address, sgpnt[i].length); + }; + scsi_free(sgpnt[i].address, sgpnt[i].length); + }; + }; + scsi_free(SCpnt->buffer, SCpnt->sglist_len); /* Free list of scatter-gather pointers */ + if(SCpnt->request.sector % 4) good_sectors -= 2; + /* See if there is a padding record at the end that needs to be removed */ + if(good_sectors > SCpnt->request.nr_sectors) + good_sectors -= 2; + }; + +#ifdef DEBUG + printk("(%x %x %x) ",SCpnt->request.bh, SCpnt->request.nr_sectors, + good_sectors); +#endif + if (SCpnt->request.nr_sectors > this_count) + { + SCpnt->request.errors = 0; + if (!SCpnt->request.bh) + panic("sr.c: linked page request (%lx %x)", + SCpnt->request.sector, this_count); + } + + SCpnt = end_scsi_request(SCpnt, 1, good_sectors); /* All done */ + if (result == 0) + { + requeue_sr_request(SCpnt); + return; + } + } + + if (good_sectors == 0) { + /* We only come through here if no sectors were read successfully. */ + + /* Free up any indirection buffers we allocated for DMA purposes. */ + if (SCpnt->use_sg) { + struct scatterlist * sgpnt; + int i; + sgpnt = (struct scatterlist *) SCpnt->buffer; + for(i=0; i<SCpnt->use_sg; i++) { + if (sgpnt[i].alt_address) { + scsi_free(sgpnt[i].address, sgpnt[i].length); + } + } + scsi_free(SCpnt->buffer, SCpnt->sglist_len); /* Free list of scatter-gather pointers */ + } else { + if (SCpnt->buffer != SCpnt->request.buffer) + scsi_free(SCpnt->buffer, SCpnt->bufflen); + } + + } + + if (driver_byte(result) != 0) { + if ((SCpnt->sense_buffer[0] & 0x7f) == 0x70) { + if ((SCpnt->sense_buffer[2] & 0xf) == UNIT_ATTENTION) { + /* detected disc change. set a bit and quietly refuse + * further access. */ + + scsi_CDs[DEVICE_NR(SCpnt->request.rq_dev)].device->changed = 1; + SCpnt = end_scsi_request(SCpnt, 0, this_count); + requeue_sr_request(SCpnt); + return; + } + } + + if (SCpnt->sense_buffer[2] == ILLEGAL_REQUEST) { + printk("CD-ROM error: "); + print_sense("sr", SCpnt); + printk("command was: "); + print_command(SCpnt->cmnd); + if (scsi_CDs[DEVICE_NR(SCpnt->request.rq_dev)].ten) { + scsi_CDs[DEVICE_NR(SCpnt->request.rq_dev)].ten = 0; + requeue_sr_request(SCpnt); + result = 0; + return; + } else { + SCpnt = end_scsi_request(SCpnt, 0, this_count); + requeue_sr_request(SCpnt); /* Do next request */ + return; + } + + } + + if (SCpnt->sense_buffer[2] == NOT_READY) { + printk("CD-ROM not ready. Make sure you have a disc in the drive.\n"); + SCpnt = end_scsi_request(SCpnt, 0, this_count); + requeue_sr_request(SCpnt); /* Do next request */ + return; + } + + if (SCpnt->sense_buffer[2] == MEDIUM_ERROR) { + printk("scsi%d: MEDIUM ERROR on " + "channel %d, id %d, lun %d, CDB: ", + SCpnt->host->host_no, (int) SCpnt->channel, + (int) SCpnt->target, (int) SCpnt->lun); + print_command(SCpnt->cmnd); + print_sense("sr", SCpnt); + SCpnt = end_scsi_request(SCpnt, 0, block_sectors); + requeue_sr_request(SCpnt); + return; + } + + if (SCpnt->sense_buffer[2] == VOLUME_OVERFLOW) { + printk("scsi%d: VOLUME OVERFLOW on " + "channel %d, id %d, lun %d, CDB: ", + SCpnt->host->host_no, (int) SCpnt->channel, + (int) SCpnt->target, (int) SCpnt->lun); + print_command(SCpnt->cmnd); + print_sense("sr", SCpnt); + SCpnt = end_scsi_request(SCpnt, 0, block_sectors); + requeue_sr_request(SCpnt); + return; + } + } + + /* We only get this far if we have an error we have not recognized */ + if(result) { + printk("SCSI CD error : host %d id %d lun %d return code = %03x\n", + scsi_CDs[DEVICE_NR(SCpnt->request.rq_dev)].device->host->host_no, + scsi_CDs[DEVICE_NR(SCpnt->request.rq_dev)].device->id, + scsi_CDs[DEVICE_NR(SCpnt->request.rq_dev)].device->lun, + result); + + if (status_byte(result) == CHECK_CONDITION) + print_sense("sr", SCpnt); + + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.current_nr_sectors); + requeue_sr_request(SCpnt); + } +} + +/* + * Here I tried to implement support for multisession-CD's + * + * Much of this has do be done with vendor-specific SCSI-commands, because + * multisession is newer than the SCSI-II standard. + * So I have to complete it step by step. Useful information is welcome. + * + * Actually works: + * - NEC: Detection and support of multisession CD's. Special handling + * for XA-disks is not necessary. + * + * - TOSHIBA: setting density is done here now, mounting PhotoCD's should + * work now without running the program "set_density" + * Multisession CD's are supported too. + * + * Gerd Knorr <kraxel@cs.tu-berlin.de> + */ +/* + * 19950704 operator@melchior.cuivre.fdn.fr (Thomas Quinot) + * + * - SONY: Same as Nec. + * + * - PIONEER: works with SONY code (may be others too ?) + */ + +void sr_photocd(struct inode *inode) +{ + unsigned long sector,min,sec,frame; + unsigned char buf[40]; /* the buffer for the ioctl */ + unsigned char *cmd; /* the scsi-command */ + unsigned char *send; /* the data we send to the drive ... */ + unsigned char *rec; /* ... and get back */ + int rc,is_xa,no_multi; + + if (scsi_CDs[MINOR(inode->i_rdev)].xa_flags & 0x02) { +#ifdef DEBUG + printk(KERN_DEBUG "sr_photocd: CDROM and/or driver do not support multisession CD's"); +#endif + return; + } + + if (!suser()) { + /* I'm not the superuser, so SCSI_IOCTL_SEND_COMMAND isn't allowed + * for me. That's why mpcd_sector will be initialized with zero, + * because I'm not able to get the right value. Necessary only if + * access_count is 1, else no disk change happened since the last + * call of this function and we can keep the old value. + */ + if (1 == scsi_CDs[MINOR(inode->i_rdev)].device->access_count) { + scsi_CDs[MINOR(inode->i_rdev)].mpcd_sector = 0; + scsi_CDs[MINOR(inode->i_rdev)].xa_flags &= ~0x01; + } + return; + } + + sector = 0; + is_xa = 0; + no_multi = 0; + cmd = rec = &buf[8]; + + switch(scsi_CDs[MINOR(inode->i_rdev)].device->manufacturer) { + + case SCSI_MAN_NEC: +#ifdef DEBUG + printk(KERN_DEBUG "sr_photocd: use NEC code\n"); +#endif + memset(buf,0,40); + *((unsigned int*)buf) = 0x0; /* we send nothing... */ + *((unsigned int*)buf+1) = 0x16; /* and receive 0x16 bytes */ + cmd[0] = 0xde; + cmd[1] = 0x03; + cmd[2] = 0xb0; + rc = kernel_scsi_ioctl(scsi_CDs[MINOR(inode->i_rdev)].device, + SCSI_IOCTL_SEND_COMMAND, buf); + if (rc != 0) { + if (rc != 0x28000002) /* drop "not ready" */ + printk(KERN_WARNING"sr_photocd: ioctl error (NEC): 0x%x\n",rc); + break; + } + if (rec[14] != 0 && rec[14] != 0xb0) { + printk(KERN_INFO"sr_photocd: (NEC) Hmm, seems the CDROM doesn't support multisession CD's\n"); + no_multi = 1; + break; + } + min = (unsigned long) rec[15]/16*10 + (unsigned long) rec[15]%16; + sec = (unsigned long) rec[16]/16*10 + (unsigned long) rec[16]%16; + frame = (unsigned long) rec[17]/16*10 + (unsigned long) rec[17]%16; + sector = min*CD_SECS*CD_FRAMES + sec*CD_FRAMES + frame; + is_xa = (rec[14] == 0xb0); +#ifdef DEBUG + if (sector) { + printk(KERN_DEBUG "sr_photocd: multisession CD detected. start: %lu\n",sector); + } +#endif + break; + + case SCSI_MAN_TOSHIBA: +#ifdef DEBUG + printk(KERN_DEBUG "sr_photocd: use TOSHIBA code\n"); +#endif + + /* we request some disc information (is it a XA-CD ?, + * where starts the last session ?) */ + memset(buf,0,40); + *((unsigned int*)buf) = (unsigned int) 0; + *((unsigned int*)buf+1) = (unsigned int) 4; /* receive 4 bytes */ + cmd[0] = (unsigned char) 0x00c7; + cmd[1] = (unsigned char) 3; + rc = kernel_scsi_ioctl(scsi_CDs[MINOR(inode->i_rdev)].device, + SCSI_IOCTL_SEND_COMMAND, buf); + if (rc != 0) { + if (rc == 0x28000002) { + /* Got a "not ready" - error. No chance to find out if this is + * because there is no CD in the drive or because the drive + * don't knows multisession CD's. So I need to do an extra + * check... */ + if (!kernel_scsi_ioctl(scsi_CDs[MINOR(inode->i_rdev)].device, + SCSI_IOCTL_TEST_UNIT_READY, NULL)) { + printk(KERN_INFO "sr_photocd: (TOSHIBA) Hmm, seems the CDROM doesn't support multisession CD's\n"); + no_multi = 1; + } + } else + printk(KERN_INFO"sr_photocd: ioctl error (TOSHIBA #1): 0x%x\n",rc); + break; /* if the first ioctl fails, we don't call the second one */ + } + is_xa = (rec[0] == 0x20); + min = (unsigned long) rec[1]/16*10 + (unsigned long) rec[1]%16; + sec = (unsigned long) rec[2]/16*10 + (unsigned long) rec[2]%16; + frame = (unsigned long) rec[3]/16*10 + (unsigned long) rec[3]%16; + sector = min*CD_SECS*CD_FRAMES + sec*CD_FRAMES + frame; + if (sector) { + sector -= CD_BLOCK_OFFSET; +#ifdef DEBUG + printk(KERN_DEBUG "sr_photocd: multisession CD detected: start: %lu\n",sector); +#endif + } + + /* now we do a get_density... */ + memset(buf,0,40); + *((unsigned int*)buf) = (unsigned int) 0; + *((unsigned int*)buf+1) = (unsigned int) 12; + cmd[0] = (unsigned char) MODE_SENSE; + cmd[2] = (unsigned char) 1; + cmd[4] = (unsigned char) 12; + rc = kernel_scsi_ioctl(scsi_CDs[MINOR(inode->i_rdev)].device, + SCSI_IOCTL_SEND_COMMAND, buf); + if (rc != 0) { + printk(KERN_WARNING "sr_photocd: ioctl error (TOSHIBA #2): 0x%x\n",rc); + break; + } +#ifdef DEBUG + printk(KERN_DEBUG "sr_photocd: get_density: 0x%x\n",rec[4]); +#endif + + /* ...and only if necessary a set_density */ + if ((rec[4] != 0x81 && is_xa) || (rec[4] != 0 && !is_xa)) { +#ifdef DEBUG + printk(KERN_DEBUG "sr_photocd: doing set_density\n"); +#endif + memset(buf,0,40); + *((unsigned int*)buf) = (unsigned int) 12; /* send 12 bytes */ + *((unsigned int*)buf+1) = (unsigned int) 0; + cmd[0] = (unsigned char) MODE_SELECT; + cmd[1] = (unsigned char) (1 << 4); + cmd[4] = (unsigned char) 12; + send = &cmd[6]; /* this is a 6-Byte command */ + send[ 3] = (unsigned char) 0x08; /* data for cmd */ + /* density 0x81 for XA, 0 else */ + send[ 4] = (is_xa) ? + (unsigned char) 0x81 : (unsigned char) 0; + send[10] = (unsigned char) 0x08; + rc = kernel_scsi_ioctl(scsi_CDs[MINOR(inode->i_rdev)].device, + SCSI_IOCTL_SEND_COMMAND, buf); + if (rc != 0) { + printk(KERN_WARNING "sr_photocd: ioctl error (TOSHIBA #3): 0x%x\n",rc); + } + /* The set_density command may have changed the + * sector size or capacity. */ + scsi_CDs[MINOR(inode->i_rdev)].needs_sector_size = 1; + } + break; + + case SCSI_MAN_SONY: /* Thomas QUINOT <thomas@melchior.cuivre.fdn.fr> */ + case SCSI_MAN_PIONEER: + case SCSI_MAN_UNKNOWN: +#ifdef DEBUG + printk(KERN_DEBUG "sr_photocd: use SONY/PIONEER code\n"); +#endif + get_sectorsize(MINOR(inode->i_rdev)); /* spinup (avoid timeout) */ + memset(buf,0,40); + *((unsigned int*)buf) = 0x0; /* we send nothing... */ + *((unsigned int*)buf+1) = 0x0c; /* and receive 0x0c bytes */ + cmd[0] = READ_TOC; + cmd[8] = 0x0c; + cmd[9] = 0x40; + rc = kernel_scsi_ioctl(scsi_CDs[MINOR(inode->i_rdev)].device, + SCSI_IOCTL_SEND_COMMAND, buf); + + if (rc != 0) { + if (rc != 0x28000002) /* drop "not ready" */ + printk(KERN_WARNING "sr_photocd: ioctl error (SONY/PIONEER): 0x%x\n",rc); + break; + } + if ((rec[0] << 8) + rec[1] < 0x0a) { + printk(KERN_INFO "sr_photocd: (SONY/PIONEER) Hmm, seems the CDROM doesn't support multisession CD's\n"); + no_multi = 1; + break; + } + sector = rec[11] + (rec[10] << 8) + (rec[9] << 16) + (rec[8] << 24); + is_xa = !!sector; +#ifdef DEBUG + if (sector) + printk (KERN_DEBUG "sr_photocd: multisession CD detected. start: %lu\n",sector); +#endif + break; + + case SCSI_MAN_NEC_OLDCDR: + default: + sector = 0; + no_multi = 1; + break; } + + scsi_CDs[MINOR(inode->i_rdev)].mpcd_sector = sector; + if (is_xa) + scsi_CDs[MINOR(inode->i_rdev)].xa_flags |= 0x01; + else + scsi_CDs[MINOR(inode->i_rdev)].xa_flags &= ~0x01; + if (no_multi) + scsi_CDs[MINOR(inode->i_rdev)].xa_flags |= 0x02; + return; +} + +static int sr_open(struct inode * inode, struct file * filp) +{ + if(MINOR(inode->i_rdev) >= sr_template.nr_dev || + !scsi_CDs[MINOR(inode->i_rdev)].device) return -ENXIO; /* No such device */ + + if (filp->f_mode & 2) + return -EROFS; + + if(sr_template.usage_count) (*sr_template.usage_count)++; + + sr_ioctl(inode,filp,CDROMCLOSETRAY,0); + check_disk_change(inode->i_rdev); + + if(!scsi_CDs[MINOR(inode->i_rdev)].device->access_count++) + sr_ioctl(inode, NULL, SCSI_IOCTL_DOORLOCK, 0); + if (scsi_CDs[MINOR(inode->i_rdev)].device->host->hostt->usage_count) + (*scsi_CDs[MINOR(inode->i_rdev)].device->host->hostt->usage_count)++; + + sr_photocd(inode); + + /* If this device did not have media in the drive at boot time, then + * we would have been unable to get the sector size. Check to see if + * this is the case, and try again. + */ + + if(scsi_CDs[MINOR(inode->i_rdev)].needs_sector_size) + get_sectorsize(MINOR(inode->i_rdev)); + + return 0; +} + + +/* + * do_sr_request() is the request handler function for the sr driver. + * Its function in life is to take block device requests, and + * translate them to SCSI commands. + */ + +static void do_sr_request (void) +{ + Scsi_Cmnd * SCpnt = NULL; + struct request * req = NULL; + Scsi_Device * SDev; + unsigned long flags; + int flag = 0; + + while (1==1){ + save_flags(flags); + cli(); + if (CURRENT != NULL && CURRENT->rq_status == RQ_INACTIVE) { + restore_flags(flags); + return; + }; + + INIT_SCSI_REQUEST; + + SDev = scsi_CDs[DEVICE_NR(CURRENT->rq_dev)].device; + + /* + * I am not sure where the best place to do this is. We need + * to hook in a place where we are likely to come if in user + * space. + */ + if( SDev->was_reset ) + { + /* + * We need to relock the door, but we might + * be in an interrupt handler. Only do this + * from user space, since we do not want to + * sleep from an interrupt. + */ + if( SDev->removable && !intr_count ) + { + scsi_ioctl(SDev, SCSI_IOCTL_DOORLOCK, 0); + } + SDev->was_reset = 0; + } + + if (flag++ == 0) + SCpnt = allocate_device(&CURRENT, + scsi_CDs[DEVICE_NR(CURRENT->rq_dev)].device, 0); + else SCpnt = NULL; + restore_flags(flags); + + /* This is a performance enhancement. We dig down into the request list and + * try to find a queueable request (i.e. device not busy, and host able to + * accept another command. If we find one, then we queue it. This can + * make a big difference on systems with more than one disk drive. We want + * to have the interrupts off when monkeying with the request list, because + * otherwise the kernel might try to slip in a request in between somewhere. */ + + if (!SCpnt && sr_template.nr_dev > 1){ + struct request *req1; + req1 = NULL; + save_flags(flags); + cli(); + req = CURRENT; + while(req){ + SCpnt = request_queueable(req, + scsi_CDs[DEVICE_NR(req->rq_dev)].device); + if(SCpnt) break; + req1 = req; + req = req->next; + }; + if (SCpnt && req->rq_status == RQ_INACTIVE) { + if (req == CURRENT) + CURRENT = CURRENT->next; + else + req1->next = req->next; + }; + restore_flags(flags); + }; + + if (!SCpnt) + return; /* Could not find anything to do */ + + wake_up(&wait_for_request); + + /* Queue command */ + requeue_sr_request(SCpnt); + }; /* While */ +} + +void requeue_sr_request (Scsi_Cmnd * SCpnt) +{ + unsigned int dev, block, realcount; + unsigned char cmd[10], *buffer, tries; + int this_count, start, end_rec; + + tries = 2; + + repeat: + if(!SCpnt || SCpnt->request.rq_status == RQ_INACTIVE) { + do_sr_request(); + return; + } + + dev = MINOR(SCpnt->request.rq_dev); + block = SCpnt->request.sector; + buffer = NULL; + this_count = 0; + + if (dev >= sr_template.nr_dev) { + /* printk("CD-ROM request error: invalid device.\n"); */ + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + tries = 2; + goto repeat; + } + + if (!scsi_CDs[dev].use) { + /* printk("CD-ROM request error: device marked not in use.\n"); */ + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + tries = 2; + goto repeat; + } + + if (scsi_CDs[dev].device->changed) { + /* + * quietly refuse to do anything to a changed disc + * until the changed bit has been reset + */ + /* printk("CD-ROM has been changed. Prohibiting further I/O.\n"); */ + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + tries = 2; + goto repeat; + } + + switch (SCpnt->request.cmd) + { + case WRITE: + SCpnt = end_scsi_request(SCpnt, 0, SCpnt->request.nr_sectors); + goto repeat; + break; + case READ : + cmd[0] = READ_6; + break; + default : + panic ("Unknown sr command %d\n", SCpnt->request.cmd); + } + + cmd[1] = (SCpnt->lun << 5) & 0xe0; + + /* + * Now do the grungy work of figuring out which sectors we need, and + * where in memory we are going to put them. + * + * The variables we need are: + * + * this_count= number of 512 byte sectors being read + * block = starting cdrom sector to read. + * realcount = # of cdrom sectors to read + * + * The major difference between a scsi disk and a scsi cdrom + * is that we will always use scatter-gather if we can, because we can + * work around the fact that the buffer cache has a block size of 1024, + * and we have 2048 byte sectors. This code should work for buffers that + * are any multiple of 512 bytes long. + */ + + SCpnt->use_sg = 0; + + if (SCpnt->host->sg_tablesize > 0 && + (!need_isa_buffer || + dma_free_sectors >= 10)) { + struct buffer_head * bh; + struct scatterlist * sgpnt; + int count, this_count_max; + bh = SCpnt->request.bh; + this_count = 0; + count = 0; + this_count_max = (scsi_CDs[dev].ten ? 0xffff : 0xff) << 4; + /* Calculate how many links we can use. First see if we need + * a padding record at the start */ + this_count = SCpnt->request.sector % 4; + if(this_count) count++; + while(bh && count < SCpnt->host->sg_tablesize) { + if ((this_count + (bh->b_size >> 9)) > this_count_max) break; + this_count += (bh->b_size >> 9); + count++; + bh = bh->b_reqnext; + }; + /* Fix up in case of an odd record at the end */ + end_rec = 0; + if(this_count % 4) { + if (count < SCpnt->host->sg_tablesize) { + count++; + end_rec = (4 - (this_count % 4)) << 9; + this_count += 4 - (this_count % 4); + } else { + count--; + this_count -= (this_count % 4); + }; + }; + SCpnt->use_sg = count; /* Number of chains */ + /* scsi_malloc can only allocate in chunks of 512 bytes */ + count = (SCpnt->use_sg * sizeof(struct scatterlist) + 511) & ~511; + + SCpnt->sglist_len = count; + sgpnt = (struct scatterlist * ) scsi_malloc(count); + if (!sgpnt) { + printk("Warning - running *really* short on DMA buffers\n"); + SCpnt->use_sg = 0; /* No memory left - bail out */ + } else { + buffer = (unsigned char *) sgpnt; + count = 0; + bh = SCpnt->request.bh; + if(SCpnt->request.sector % 4) { + sgpnt[count].length = (SCpnt->request.sector % 4) << 9; + sgpnt[count].address = (char *) scsi_malloc(sgpnt[count].length); + if(!sgpnt[count].address) panic("SCSI DMA pool exhausted."); + sgpnt[count].alt_address = sgpnt[count].address; /* Flag to delete + if needed */ + count++; + }; + for(bh = SCpnt->request.bh; count < SCpnt->use_sg; + count++, bh = bh->b_reqnext) { + if (bh) { /* Need a placeholder at the end of the record? */ + sgpnt[count].address = bh->b_data; + sgpnt[count].length = bh->b_size; + sgpnt[count].alt_address = NULL; + } else { + sgpnt[count].address = (char *) scsi_malloc(end_rec); + if(!sgpnt[count].address) panic("SCSI DMA pool exhausted."); + sgpnt[count].length = end_rec; + sgpnt[count].alt_address = sgpnt[count].address; + if (count+1 != SCpnt->use_sg) panic("Bad sr request list"); + break; + }; + if (((long) sgpnt[count].address) + sgpnt[count].length - 1 > + ISA_DMA_THRESHOLD && SCpnt->host->unchecked_isa_dma) { + sgpnt[count].alt_address = sgpnt[count].address; + /* We try to avoid exhausting the DMA pool, since it is easier + * to control usage here. In other places we might have a more + * pressing need, and we would be screwed if we ran out */ + if(dma_free_sectors < (sgpnt[count].length >> 9) + 5) { + sgpnt[count].address = NULL; + } else { + sgpnt[count].address = (char *) scsi_malloc(sgpnt[count].length); + }; + /* If we start running low on DMA buffers, we abort the scatter-gather + * operation, and free all of the memory we have allocated. We want to + * ensure that all scsi operations are able to do at least a non-scatter/gather + * operation */ + if(sgpnt[count].address == NULL){ /* Out of dma memory */ + printk("Warning: Running low on SCSI DMA buffers"); + /* Try switching back to a non scatter-gather operation. */ + while(--count >= 0){ + if(sgpnt[count].alt_address) + scsi_free(sgpnt[count].address, sgpnt[count].length); + }; + SCpnt->use_sg = 0; + scsi_free(buffer, SCpnt->sglist_len); + break; + }; /* if address == NULL */ + }; /* if need DMA fixup */ + }; /* for loop to fill list */ +#ifdef DEBUG + printk("SR: %d %d %d %d %d *** ",SCpnt->use_sg, SCpnt->request.sector, + this_count, + SCpnt->request.current_nr_sectors, + SCpnt->request.nr_sectors); + for(count=0; count<SCpnt->use_sg; count++) + printk("SGlist: %d %x %x %x\n", count, + sgpnt[count].address, + sgpnt[count].alt_address, + sgpnt[count].length); +#endif + }; /* Able to allocate scatter-gather list */ + }; + + if (SCpnt->use_sg == 0){ + /* We cannot use scatter-gather. Do this the old fashion way */ + if (!SCpnt->request.bh) + this_count = SCpnt->request.nr_sectors; + else + this_count = (SCpnt->request.bh->b_size >> 9); + + start = block % 4; + if (start) + { + this_count = ((this_count > 4 - start) ? + (4 - start) : (this_count)); + buffer = (unsigned char *) scsi_malloc(2048); + } + else if (this_count < 4) + { + buffer = (unsigned char *) scsi_malloc(2048); + } + else + { + this_count -= this_count % 4; + buffer = (unsigned char *) SCpnt->request.buffer; + if (((long) buffer) + (this_count << 9) > ISA_DMA_THRESHOLD && + SCpnt->host->unchecked_isa_dma) + buffer = (unsigned char *) scsi_malloc(this_count << 9); + } + }; + + if (scsi_CDs[dev].sector_size == 2048) + block = block >> 2; /* These are the sectors that the cdrom uses */ + else + block = block & 0xfffffffc; + + realcount = (this_count + 3) / 4; + + if (scsi_CDs[dev].sector_size == 512) realcount = realcount << 2; + + if (((realcount > 0xff) || (block > 0x1fffff)) && scsi_CDs[dev].ten) + { + if (realcount > 0xffff) + { + realcount = 0xffff; + this_count = realcount * (scsi_CDs[dev].sector_size >> 9); + } + + cmd[0] += READ_10 - READ_6 ; + cmd[2] = (unsigned char) (block >> 24) & 0xff; + cmd[3] = (unsigned char) (block >> 16) & 0xff; + cmd[4] = (unsigned char) (block >> 8) & 0xff; + cmd[5] = (unsigned char) block & 0xff; + cmd[6] = cmd[9] = 0; + cmd[7] = (unsigned char) (realcount >> 8) & 0xff; + cmd[8] = (unsigned char) realcount & 0xff; + } + else + { + if (realcount > 0xff) + { + realcount = 0xff; + this_count = realcount * (scsi_CDs[dev].sector_size >> 9); + } + + cmd[1] |= (unsigned char) ((block >> 16) & 0x1f); + cmd[2] = (unsigned char) ((block >> 8) & 0xff); + cmd[3] = (unsigned char) block & 0xff; + cmd[4] = (unsigned char) realcount; + cmd[5] = 0; + } + +#ifdef DEBUG + { + int i; + printk("ReadCD: %d %d %d %d\n",block, realcount, buffer, this_count); + printk("Use sg: %d\n", SCpnt->use_sg); + printk("Dumping command: "); + for(i=0; i<12; i++) printk("%2.2x ", cmd[i]); + printk("\n"); + }; +#endif + + /* Some dumb host adapters can speed transfers by knowing the + * minimum transfersize in advance. + * + * We shouldn't disconnect in the middle of a sector, but the cdrom + * sector size can be larger than the size of a buffer and the + * transfer may be split to the size of a buffer. So it's safe to + * assume that we can at least transfer the minimum of the buffer + * size (1024) and the sector size between each connect / disconnect. + */ + + SCpnt->transfersize = (scsi_CDs[dev].sector_size > 1024) ? + 1024 : scsi_CDs[dev].sector_size; + + SCpnt->this_count = this_count; + scsi_do_cmd (SCpnt, (void *) cmd, buffer, + realcount * scsi_CDs[dev].sector_size, + rw_intr, SR_TIMEOUT, MAX_RETRIES); +} + +static int sr_detect(Scsi_Device * SDp){ + + if(SDp->type != TYPE_ROM && SDp->type != TYPE_WORM) return 0; + + printk("Detected scsi CD-ROM sr%d at scsi%d, channel %d, id %d, lun %d\n", + sr_template.dev_noticed++, + SDp->host->host_no, SDp->channel, SDp->id, SDp->lun); + + return 1; +} + +static int sr_attach(Scsi_Device * SDp){ + Scsi_CD * cpnt; + int i; + + if(SDp->type != TYPE_ROM && SDp->type != TYPE_WORM) return 1; + + if (sr_template.nr_dev >= sr_template.dev_max) + { + SDp->attached--; + return 1; + } + + for(cpnt = scsi_CDs, i=0; i<sr_template.dev_max; i++, cpnt++) + if(!cpnt->device) break; + + if(i >= sr_template.dev_max) panic ("scsi_devices corrupt (sr)"); + + SDp->scsi_request_fn = do_sr_request; + scsi_CDs[i].device = SDp; + sr_template.nr_dev++; + if(sr_template.nr_dev > sr_template.dev_max) + panic ("scsi_devices corrupt (sr)"); + return 0; +} + + +static void sr_init_done (Scsi_Cmnd * SCpnt) +{ + struct request * req; + + req = &SCpnt->request; + req->rq_status = RQ_SCSI_DONE; /* Busy, but indicate request done */ + + if (req->sem != NULL) { + up(req->sem); + } +} + +void get_sectorsize(int i){ + unsigned char cmd[10]; + unsigned char *buffer; + int the_result, retries; + Scsi_Cmnd * SCpnt; + + buffer = (unsigned char *) scsi_malloc(512); + SCpnt = allocate_device(NULL, scsi_CDs[i].device, 1); + + retries = 3; + do { + cmd[0] = READ_CAPACITY; + cmd[1] = (scsi_CDs[i].device->lun << 5) & 0xe0; + memset ((void *) &cmd[2], 0, 8); + SCpnt->request.rq_status = RQ_SCSI_BUSY; /* Mark as really busy */ + SCpnt->cmd_len = 0; + + memset(buffer, 0, 8); + + /* Do the command and wait.. */ + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.sem = &sem; + scsi_do_cmd (SCpnt, + (void *) cmd, (void *) buffer, + 512, sr_init_done, SR_TIMEOUT, + MAX_RETRIES); + down(&sem); + } + + the_result = SCpnt->result; + retries--; + + } while(the_result && retries); + + SCpnt->request.rq_status = RQ_INACTIVE; /* Mark as not busy */ + + wake_up(&SCpnt->device->device_wait); + + if (the_result) { + scsi_CDs[i].capacity = 0x1fffff; + scsi_CDs[i].sector_size = 2048; /* A guess, just in case */ + scsi_CDs[i].needs_sector_size = 1; + } else { + scsi_CDs[i].capacity = 1 + ((buffer[0] << 24) | + (buffer[1] << 16) | + (buffer[2] << 8) | + buffer[3]); + scsi_CDs[i].sector_size = (buffer[4] << 24) | + (buffer[5] << 16) | (buffer[6] << 8) | buffer[7]; + switch (scsi_CDs[i].sector_size) { + /* + * HP 4020i CD-Recorder reports 2340 byte sectors + * Philips CD-Writers report 2352 byte sectors + * + * Use 2k sectors for them.. + */ + case 0: case 2340: case 2352: + scsi_CDs[i].sector_size = 2048; + /* fall through */ + case 2048: + scsi_CDs[i].capacity *= 4; + /* fall through */ + case 512: + break; + default: + printk ("scd%d : unsupported sector size %d.\n", + i, scsi_CDs[i].sector_size); + scsi_CDs[i].capacity = 0; + scsi_CDs[i].needs_sector_size = 1; + } + scsi_CDs[i].needs_sector_size = 0; + sr_sizes[i] = scsi_CDs[i].capacity >> (BLOCK_SIZE_BITS - 9); + }; + scsi_free(buffer, 512); +} + +static int sr_registered = 0; + +static int sr_init() +{ + int i; + + if(sr_template.dev_noticed == 0) return 0; + + if(!sr_registered) { + if (register_blkdev(MAJOR_NR,"sr",&sr_fops)) { + printk("Unable to get major %d for SCSI-CD\n",MAJOR_NR); + return 1; + } + sr_registered++; + } + + + if (scsi_CDs) return 0; + sr_template.dev_max = sr_template.dev_noticed + SR_EXTRA_DEVS; + scsi_CDs = (Scsi_CD *) scsi_init_malloc(sr_template.dev_max * sizeof(Scsi_CD), GFP_ATOMIC); + memset(scsi_CDs, 0, sr_template.dev_max * sizeof(Scsi_CD)); + + sr_sizes = (int *) scsi_init_malloc(sr_template.dev_max * sizeof(int), GFP_ATOMIC); + memset(sr_sizes, 0, sr_template.dev_max * sizeof(int)); + + sr_blocksizes = (int *) scsi_init_malloc(sr_template.dev_max * + sizeof(int), GFP_ATOMIC); + for(i=0;i<sr_template.dev_max;i++) sr_blocksizes[i] = 2048; + blksize_size[MAJOR_NR] = sr_blocksizes; + return 0; +} + +void sr_finish() +{ + int i; + + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + blk_size[MAJOR_NR] = sr_sizes; + + for (i = 0; i < sr_template.nr_dev; ++i) + { + /* If we have already seen this, then skip it. Comes up + * with loadable modules. */ + if (scsi_CDs[i].capacity) continue; + scsi_CDs[i].capacity = 0x1fffff; + scsi_CDs[i].sector_size = 2048; /* A guess, just in case */ + scsi_CDs[i].needs_sector_size = 1; +#if 0 + /* seems better to leave this for later */ + get_sectorsize(i); + printk("Scd sectorsize = %d bytes.\n", scsi_CDs[i].sector_size); +#endif + scsi_CDs[i].use = 1; + scsi_CDs[i].ten = 1; + scsi_CDs[i].remap = 1; + scsi_CDs[i].auto_eject = 0; /* Default is not to eject upon unmount. */ + sr_sizes[i] = scsi_CDs[i].capacity >> (BLOCK_SIZE_BITS - 9); + } + + + /* If our host adapter is capable of scatter-gather, then we increase + * the read-ahead to 16 blocks (32 sectors). If not, we use + * a two block (4 sector) read ahead. */ + if(scsi_CDs[0].device && scsi_CDs[0].device->host->sg_tablesize) + read_ahead[MAJOR_NR] = 32; /* 32 sector read-ahead. Always removable. */ + else + read_ahead[MAJOR_NR] = 4; /* 4 sector read-ahead */ + + return; +} + +static void sr_detach(Scsi_Device * SDp) +{ + Scsi_CD * cpnt; + int i; + + for(cpnt = scsi_CDs, i=0; i<sr_template.dev_max; i++, cpnt++) + if(cpnt->device == SDp) { + kdev_t devi = MKDEV(MAJOR_NR, i); + + /* + * Since the cdrom is read-only, no need to sync the device. + * We should be kind to our buffer cache, however. + */ + invalidate_inodes(devi); + invalidate_buffers(devi); + + /* + * Reset things back to a sane state so that one can re-load a new + * driver (perhaps the same one). + */ + cpnt->device = NULL; + cpnt->capacity = 0; + SDp->attached--; + sr_template.nr_dev--; + sr_template.dev_noticed--; + sr_sizes[i] = 0; + return; + } + return; +} + + +#ifdef MODULE + +int init_module(void) { + sr_template.usage_count = &mod_use_count_; + return scsi_register_module(MODULE_SCSI_DEV, &sr_template); +} + +void cleanup_module( void) +{ + scsi_unregister_module(MODULE_SCSI_DEV, &sr_template); + unregister_blkdev(SCSI_CDROM_MAJOR, "sr"); + sr_registered--; + if(scsi_CDs != NULL) { + scsi_init_free((char *) scsi_CDs, + (sr_template.dev_noticed + SR_EXTRA_DEVS) + * sizeof(Scsi_CD)); + + scsi_init_free((char *) sr_sizes, sr_template.dev_max * sizeof(int)); + scsi_init_free((char *) sr_blocksizes, sr_template.dev_max * sizeof(int)); + } + + blksize_size[MAJOR_NR] = NULL; + blk_dev[MAJOR_NR].request_fn = NULL; + blk_size[MAJOR_NR] = NULL; + read_ahead[MAJOR_NR] = 0; + + sr_template.dev_max = 0; +} +#endif /* MODULE */ + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/sr.h b/linux/src/drivers/scsi/sr.h new file mode 100644 index 00000000..381678a6 --- /dev/null +++ b/linux/src/drivers/scsi/sr.h @@ -0,0 +1,40 @@ +/* + * sr.h by David Giller + * CD-ROM disk driver header file + * + * adapted from: + * sd.h Copyright (C) 1992 Drew Eckhardt + * SCSI disk driver header file by + * Drew Eckhardt + * + * <drew@colorado.edu> + * + * Modified by Eric Youngdale eric@aib.com to + * add scatter-gather, multiple outstanding request, and other + * enhancements. + */ + +#ifndef _SR_H +#define _SR_H + +#include "scsi.h" + +typedef struct + { + unsigned capacity; /* size in blocks */ + unsigned sector_size; /* size in bytes */ + Scsi_Device *device; + unsigned long mpcd_sector; /* for reading multisession-CD's */ + char xa_flags; /* some flags for handling XA-CD's */ + unsigned char sector_bit_size; /* sector size = 2^sector_bit_size */ + unsigned char sector_bit_shift; /* sectors/FS block = 2^sector_bit_shift*/ + unsigned needs_sector_size:1; /* needs to get sector size */ + unsigned ten:1; /* support ten byte commands */ + unsigned remap:1; /* support remapping */ + unsigned use:1; /* is this device still supportable */ + unsigned auto_eject:1; /* auto-eject medium on last release. */ + } Scsi_CD; + +extern Scsi_CD * scsi_CDs; + +#endif diff --git a/linux/src/drivers/scsi/sr_ioctl.c b/linux/src/drivers/scsi/sr_ioctl.c new file mode 100644 index 00000000..be62eb8c --- /dev/null +++ b/linux/src/drivers/scsi/sr_ioctl.c @@ -0,0 +1,607 @@ +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/fs.h> +#include <asm/segment.h> +#include <linux/errno.h> +#include <linux/string.h> + +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "sr.h" +#include <scsi/scsi_ioctl.h> + +#include <linux/cdrom.h> + +extern void get_sectorsize(int); +extern void sr_photocd(struct inode *); + +#define IOCTL_RETRIES 3 +/* The CDROM is fairly slow, so we need a little extra time */ +/* In fact, it is very slow if it has to spin up first */ +#define IOCTL_TIMEOUT 3000 + +static void sr_ioctl_done(Scsi_Cmnd * SCpnt) +{ + struct request * req; + + req = &SCpnt->request; + req->rq_status = RQ_SCSI_DONE; /* Busy, but indicate request done */ + + if (req->sem != NULL) { + up(req->sem); + } +} + +/* We do our own retries because we want to know what the specific + error code is. Normally the UNIT_ATTENTION code will automatically + clear after one error */ + +static int do_ioctl(int target, unsigned char * sr_cmd, void * buffer, unsigned buflength) +{ + Scsi_Cmnd * SCpnt; + int result; + + SCpnt = allocate_device(NULL, scsi_CDs[target].device, 1); + { + struct semaphore sem = MUTEX_LOCKED; + SCpnt->request.sem = &sem; + scsi_do_cmd(SCpnt, + (void *) sr_cmd, buffer, buflength, sr_ioctl_done, + IOCTL_TIMEOUT, IOCTL_RETRIES); + down(&sem); + } + + result = SCpnt->result; + + /* Minimal error checking. Ignore cases we know about, and report the rest. */ + if(driver_byte(result) != 0) + switch(SCpnt->sense_buffer[2] & 0xf) { + case UNIT_ATTENTION: + scsi_CDs[target].device->changed = 1; + printk("Disc change detected.\n"); + break; + case NOT_READY: /* This happens if there is no disc in drive */ + printk("CDROM not ready. Make sure there is a disc in the drive.\n"); + break; + case ILLEGAL_REQUEST: + /* CDROMCLOSETRAY should not print an error for caddy drives. */ + if (!(sr_cmd[0] == START_STOP && sr_cmd[4] == 0x03)) + printk("CDROM (ioctl) reports ILLEGAL REQUEST.\n"); + break; + default: + printk("SCSI CD error: host %d id %d lun %d return code = %03x\n", + scsi_CDs[target].device->host->host_no, + scsi_CDs[target].device->id, + scsi_CDs[target].device->lun, + result); + printk("\tSense class %x, sense error %x, extended sense %x\n", + sense_class(SCpnt->sense_buffer[0]), + sense_error(SCpnt->sense_buffer[0]), + SCpnt->sense_buffer[2] & 0xf); + + }; + + result = SCpnt->result; + SCpnt->request.rq_status = RQ_INACTIVE; /* Deallocate */ + wake_up(&SCpnt->device->device_wait); + /* Wake up a process waiting for device*/ + return result; +} + +int sr_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) +{ + u_char sr_cmd[10]; + + kdev_t dev = inode->i_rdev; + int result, target, err; + + target = MINOR(dev); + + if (target >= sr_template.nr_dev || + !scsi_CDs[target].device) return -ENXIO; + + switch (cmd) + { + /* Sun-compatible */ + case CDROMPAUSE: + + sr_cmd[0] = SCMD_PAUSE_RESUME; + sr_cmd[1] = scsi_CDs[target].device->lun << 5; + sr_cmd[2] = sr_cmd[3] = sr_cmd[4] = 0; + sr_cmd[5] = sr_cmd[6] = sr_cmd[7] = 0; + sr_cmd[8] = 0; + sr_cmd[9] = 0; + + result = do_ioctl(target, sr_cmd, NULL, 255); + return result; + + case CDROMRESUME: + + sr_cmd[0] = SCMD_PAUSE_RESUME; + sr_cmd[1] = scsi_CDs[target].device->lun << 5; + sr_cmd[2] = sr_cmd[3] = sr_cmd[4] = 0; + sr_cmd[5] = sr_cmd[6] = sr_cmd[7] = 0; + sr_cmd[8] = 1; + sr_cmd[9] = 0; + + result = do_ioctl(target, sr_cmd, NULL, 255); + + return result; + + case CDROMPLAYMSF: + { + struct cdrom_msf msf; + + err = verify_area (VERIFY_READ, (void *) arg, sizeof (msf)); + if (err) return err; + + memcpy_fromfs(&msf, (void *) arg, sizeof(msf)); + + sr_cmd[0] = SCMD_PLAYAUDIO_MSF; + sr_cmd[1] = scsi_CDs[target].device->lun << 5; + sr_cmd[2] = 0; + sr_cmd[3] = msf.cdmsf_min0; + sr_cmd[4] = msf.cdmsf_sec0; + sr_cmd[5] = msf.cdmsf_frame0; + sr_cmd[6] = msf.cdmsf_min1; + sr_cmd[7] = msf.cdmsf_sec1; + sr_cmd[8] = msf.cdmsf_frame1; + sr_cmd[9] = 0; + + result = do_ioctl(target, sr_cmd, NULL, 255); + return result; + } + + case CDROMPLAYBLK: + { + struct cdrom_blk blk; + + err = verify_area (VERIFY_READ, (void *) arg, sizeof (blk)); + if (err) return err; + + memcpy_fromfs(&blk, (void *) arg, sizeof(blk)); + + sr_cmd[0] = SCMD_PLAYAUDIO10; + sr_cmd[1] = scsi_CDs[target].device->lun << 5; + sr_cmd[2] = blk.from >> 24; + sr_cmd[3] = blk.from >> 16; + sr_cmd[4] = blk.from >> 8; + sr_cmd[5] = blk.from; + sr_cmd[6] = 0; + sr_cmd[7] = blk.len >> 8; + sr_cmd[8] = blk.len; + sr_cmd[9] = 0; + + result = do_ioctl(target, sr_cmd, NULL, 255); + return result; + } + + case CDROMPLAYTRKIND: + { + struct cdrom_ti ti; + + err = verify_area (VERIFY_READ, (void *) arg, sizeof (ti)); + if (err) return err; + + memcpy_fromfs(&ti, (void *) arg, sizeof(ti)); + + sr_cmd[0] = SCMD_PLAYAUDIO_TI; + sr_cmd[1] = scsi_CDs[target].device->lun << 5; + sr_cmd[2] = 0; + sr_cmd[3] = 0; + sr_cmd[4] = ti.cdti_trk0; + sr_cmd[5] = ti.cdti_ind0; + sr_cmd[6] = 0; + sr_cmd[7] = ti.cdti_trk1; + sr_cmd[8] = ti.cdti_ind1; + sr_cmd[9] = 0; + + result = do_ioctl(target, sr_cmd, NULL, 255); + + return result; + } + + case CDROMREADTOCHDR: + { + struct cdrom_tochdr tochdr; + char * buffer; + + sr_cmd[0] = SCMD_READ_TOC; + sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5); + sr_cmd[2] = sr_cmd[3] = sr_cmd[4] = sr_cmd[5] = 0; + sr_cmd[6] = 0; + sr_cmd[7] = 0; /* MSB of length (12) */ + sr_cmd[8] = 12; /* LSB of length */ + sr_cmd[9] = 0; + + buffer = (unsigned char *) scsi_malloc(512); + if(!buffer) return -ENOMEM; + + result = do_ioctl(target, sr_cmd, buffer, 12); + + tochdr.cdth_trk0 = buffer[2]; + tochdr.cdth_trk1 = buffer[3]; + + scsi_free(buffer, 512); + + err = verify_area (VERIFY_WRITE, (void *) arg, sizeof (struct cdrom_tochdr)); + if (err) + return err; + memcpy_tofs ((void *) arg, &tochdr, sizeof (struct cdrom_tochdr)); + + return result; + } + + case CDROMREADTOCENTRY: + { + struct cdrom_tocentry tocentry; + unsigned char * buffer; + + err = verify_area (VERIFY_READ, (void *) arg, sizeof (struct cdrom_tocentry)); + if (err) return err; + + memcpy_fromfs (&tocentry, (void *) arg, sizeof (struct cdrom_tocentry)); + + sr_cmd[0] = SCMD_READ_TOC; + sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5) | + (tocentry.cdte_format == CDROM_MSF ? 0x02 : 0); + sr_cmd[2] = sr_cmd[3] = sr_cmd[4] = sr_cmd[5] = 0; + sr_cmd[6] = tocentry.cdte_track; + sr_cmd[7] = 0; /* MSB of length (12) */ + sr_cmd[8] = 12; /* LSB of length */ + sr_cmd[9] = 0; + + buffer = (unsigned char *) scsi_malloc(512); + if(!buffer) return -ENOMEM; + + result = do_ioctl (target, sr_cmd, buffer, 12); + + tocentry.cdte_ctrl = buffer[5] & 0xf; + tocentry.cdte_adr = buffer[5] >> 4; + tocentry.cdte_datamode = (tocentry.cdte_ctrl & 0x04) ? 1 : 0; + if (tocentry.cdte_format == CDROM_MSF) { + tocentry.cdte_addr.msf.minute = buffer[9]; + tocentry.cdte_addr.msf.second = buffer[10]; + tocentry.cdte_addr.msf.frame = buffer[11]; + } + else + tocentry.cdte_addr.lba = (((((buffer[8] << 8) + buffer[9]) << 8) + + buffer[10]) << 8) + buffer[11]; + + scsi_free(buffer, 512); + + err = verify_area (VERIFY_WRITE, (void *) arg, sizeof (struct cdrom_tocentry)); + if (err) + return err; + memcpy_tofs ((void *) arg, &tocentry, sizeof (struct cdrom_tocentry)); + + return result; + } + + case CDROMSTOP: + sr_cmd[0] = START_STOP; + sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5) | 1; + sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0; + sr_cmd[4] = 0; + + result = do_ioctl(target, sr_cmd, NULL, 255); + return result; + + case CDROMSTART: + sr_cmd[0] = START_STOP; + sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5) | 1; + sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0; + sr_cmd[4] = 1; + + result = do_ioctl(target, sr_cmd, NULL, 255); + return result; + + case CDROMCLOSETRAY: + sr_cmd[0] = START_STOP; + sr_cmd[1] = ((scsi_CDs[target].device -> lun) << 5); + sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0; + sr_cmd[4] = 0x03; + + if ((result = do_ioctl(target, sr_cmd, NULL, 255))) + return result; + + /* Gather information about newly inserted disc */ + check_disk_change (inode->i_rdev); + sr_ioctl (inode, NULL, SCSI_IOCTL_DOORLOCK, 0); + sr_photocd (inode); + + if (scsi_CDs[MINOR(inode->i_rdev)].needs_sector_size) + get_sectorsize (MINOR(inode->i_rdev)); + + return 0; + + case CDROMEJECT: + /* + * Allow 0 for access count for auto-eject feature. + */ + if (scsi_CDs[target].device -> access_count > 1) + return -EBUSY; + + sr_ioctl (inode, NULL, SCSI_IOCTL_DOORUNLOCK, 0); + sr_cmd[0] = START_STOP; + sr_cmd[1] = ((scsi_CDs[target].device -> lun) << 5) | 1; + sr_cmd[2] = sr_cmd[3] = sr_cmd[5] = 0; + sr_cmd[4] = 0x02; + + if (!(result = do_ioctl(target, sr_cmd, NULL, 255))) + scsi_CDs[target].device -> changed = 1; + + return result; + + case CDROMEJECT_SW: + scsi_CDs[target].auto_eject = !!arg; + return 0; + + case CDROMVOLCTRL: + { + char * buffer, * mask; + struct cdrom_volctrl volctrl; + + err = verify_area (VERIFY_READ, (void *) arg, sizeof (struct cdrom_volctrl)); + if (err) return err; + + memcpy_fromfs (&volctrl, (void *) arg, sizeof (struct cdrom_volctrl)); + + /* First we get the current params so we can just twiddle the volume */ + + sr_cmd[0] = MODE_SENSE; + sr_cmd[1] = (scsi_CDs[target].device -> lun) << 5; + sr_cmd[2] = 0xe; /* Want mode page 0xe, CDROM audio params */ + sr_cmd[3] = 0; + sr_cmd[4] = 28; + sr_cmd[5] = 0; + + buffer = (unsigned char *) scsi_malloc(512); + if(!buffer) return -ENOMEM; + + if ((result = do_ioctl (target, sr_cmd, buffer, 28))) { + printk ("Hosed while obtaining audio mode page\n"); + scsi_free(buffer, 512); + return result; + } + + sr_cmd[0] = MODE_SENSE; + sr_cmd[1] = (scsi_CDs[target].device -> lun) << 5; + sr_cmd[2] = 0x4e; /* Want the mask for mode page 0xe */ + sr_cmd[3] = 0; + sr_cmd[4] = 28; + sr_cmd[5] = 0; + + mask = (unsigned char *) scsi_malloc(512); + if(!mask) { + scsi_free(buffer, 512); + return -ENOMEM; + }; + + if ((result = do_ioctl (target, sr_cmd, mask, 28))) { + printk ("Hosed while obtaining mask for audio mode page\n"); + scsi_free(buffer, 512); + scsi_free(mask, 512); + return result; + } + + /* Now mask and substitute our own volume and reuse the rest */ + buffer[0] = 0; /* Clear reserved field */ + + buffer[21] = volctrl.channel0 & mask[21]; + buffer[23] = volctrl.channel1 & mask[23]; + buffer[25] = volctrl.channel2 & mask[25]; + buffer[27] = volctrl.channel3 & mask[27]; + + sr_cmd[0] = MODE_SELECT; + sr_cmd[1] = ((scsi_CDs[target].device -> lun) << 5) | 0x10; /* Params are SCSI-2 */ + sr_cmd[2] = sr_cmd[3] = 0; + sr_cmd[4] = 28; + sr_cmd[5] = 0; + + result = do_ioctl (target, sr_cmd, buffer, 28); + scsi_free(buffer, 512); + scsi_free(mask, 512); + return result; + } + + case CDROMVOLREAD: + { + char * buffer; + struct cdrom_volctrl volctrl; + + err = verify_area (VERIFY_WRITE, (void *) arg, sizeof (struct cdrom_volctrl)); + if (err) return err; + + /* Get the current params */ + + sr_cmd[0] = MODE_SENSE; + sr_cmd[1] = (scsi_CDs[target].device -> lun) << 5; + sr_cmd[2] = 0xe; /* Want mode page 0xe, CDROM audio params */ + sr_cmd[3] = 0; + sr_cmd[4] = 28; + sr_cmd[5] = 0; + + buffer = (unsigned char *) scsi_malloc(512); + if(!buffer) return -ENOMEM; + + if ((result = do_ioctl (target, sr_cmd, buffer, 28))) { + printk ("(CDROMVOLREAD) Hosed while obtaining audio mode page\n"); + scsi_free(buffer, 512); + return result; + } + + volctrl.channel0 = buffer[21]; + volctrl.channel1 = buffer[23]; + volctrl.channel2 = buffer[25]; + volctrl.channel3 = buffer[27]; + + memcpy_tofs ((void *) arg, &volctrl, sizeof (struct cdrom_volctrl)); + + scsi_free(buffer, 512); + + return 0; + } + + case CDROMSUBCHNL: + { + struct cdrom_subchnl subchnl; + char * buffer; + + sr_cmd[0] = SCMD_READ_SUBCHANNEL; + sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5) | 0x02; /* MSF format */ + sr_cmd[2] = 0x40; /* I do want the subchannel info */ + sr_cmd[3] = 0x01; /* Give me current position info */ + sr_cmd[4] = sr_cmd[5] = 0; + sr_cmd[6] = 0; + sr_cmd[7] = 0; + sr_cmd[8] = 16; + sr_cmd[9] = 0; + + buffer = (unsigned char*) scsi_malloc(512); + if(!buffer) return -ENOMEM; + + result = do_ioctl(target, sr_cmd, buffer, 16); + + subchnl.cdsc_audiostatus = buffer[1]; + subchnl.cdsc_format = CDROM_MSF; + subchnl.cdsc_ctrl = buffer[5] & 0xf; + subchnl.cdsc_trk = buffer[6]; + subchnl.cdsc_ind = buffer[7]; + + subchnl.cdsc_reladdr.msf.minute = buffer[13]; + subchnl.cdsc_reladdr.msf.second = buffer[14]; + subchnl.cdsc_reladdr.msf.frame = buffer[15]; + subchnl.cdsc_absaddr.msf.minute = buffer[9]; + subchnl.cdsc_absaddr.msf.second = buffer[10]; + subchnl.cdsc_absaddr.msf.frame = buffer[11]; + + scsi_free(buffer, 512); + + err = verify_area (VERIFY_WRITE, (void *) arg, sizeof (struct cdrom_subchnl)); + if (err) + return err; + memcpy_tofs ((void *) arg, &subchnl, sizeof (struct cdrom_subchnl)); + return result; + } + + case CDROM_GET_UPC: + { + struct cdrom_mcn mcn; + char * buffer; + + sr_cmd[0] = SCMD_READ_SUBCHANNEL; + sr_cmd[1] = ((scsi_CDs[target].device->lun) << 5); + sr_cmd[2] = 0x40; /* I do want the subchannel info */ + sr_cmd[3] = 0x02; /* Give me medium catalog number info */ + sr_cmd[4] = sr_cmd[5] = 0; + sr_cmd[6] = 0; + sr_cmd[7] = 0; + sr_cmd[8] = 24; + sr_cmd[9] = 0; + + buffer = (unsigned char*) scsi_malloc(512); + if(!buffer) return -ENOMEM; + + result = do_ioctl(target, sr_cmd, buffer, 24); + + memcpy (mcn.medium_catalog_number, buffer + 9, 13); + mcn.medium_catalog_number[13] = 0; + + scsi_free(buffer, 512); + + err = verify_area (VERIFY_WRITE, (void *) arg, sizeof (struct cdrom_mcn)); + if (err) + return err; + memcpy_tofs ((void *) arg, &mcn, sizeof (struct cdrom_mcn)); + return result; + } + + /* these are compatible with the ide-cd driver */ + case CDROMREADRAW: + case CDROMREADMODE1: + case CDROMREADMODE2: + return -EINVAL; + + /* block-copy from ../block/sbpcd.c with some adjustments... */ + case CDROMMULTISESSION: /* tell start-of-last-session to user */ + { + struct cdrom_multisession ms_info; + long lba; + + err = verify_area(VERIFY_READ, (void *) arg, + sizeof(struct cdrom_multisession)); + if (err) return (err); + + memcpy_fromfs(&ms_info, (void *) arg, sizeof(struct cdrom_multisession)); + + if (ms_info.addr_format==CDROM_MSF) { /* MSF-bin requested */ + lba = scsi_CDs[target].mpcd_sector+CD_BLOCK_OFFSET; + ms_info.addr.msf.minute = lba / (CD_SECS*CD_FRAMES); + lba %= CD_SECS*CD_FRAMES; + ms_info.addr.msf.second = lba / CD_FRAMES; + ms_info.addr.msf.frame = lba % CD_FRAMES; + } else if (ms_info.addr_format==CDROM_LBA) /* lba requested */ + ms_info.addr.lba=scsi_CDs[target].mpcd_sector; + else return (-EINVAL); + + ms_info.xa_flag=scsi_CDs[target].xa_flags & 0x01; + + err=verify_area(VERIFY_WRITE,(void *) arg, + sizeof(struct cdrom_multisession)); + if (err) return (err); + + memcpy_tofs((void *) arg, &ms_info, sizeof(struct cdrom_multisession)); + return (0); + } + + case BLKRAGET: + if (!arg) + return -EINVAL; + err = verify_area(VERIFY_WRITE, (int *) arg, sizeof(int)); + if (err) + return err; + put_user(read_ahead[MAJOR(inode->i_rdev)], (int *) arg); + return 0; + + case BLKRASET: + if(!suser()) + return -EACCES; + if(!(inode->i_rdev)) + return -EINVAL; + if(arg > 0xff) + return -EINVAL; + read_ahead[MAJOR(inode->i_rdev)] = arg; + return 0; + + RO_IOCTLS(dev,arg); + + case CDROMRESET: + invalidate_buffers(inode->i_rdev); + return 0; + + default: + return scsi_ioctl(scsi_CDs[target].device,cmd,(void *) arg); + } +} + +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 4 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -4 + * c-argdecl-indent: 4 + * c-label-offset: -4 + * c-continued-statement-offset: 4 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/linux/src/drivers/scsi/t128.c b/linux/src/drivers/scsi/t128.c new file mode 100644 index 00000000..06c6d203 --- /dev/null +++ b/linux/src/drivers/scsi/t128.c @@ -0,0 +1,404 @@ +#define AUTOSENSE +#define PSEUDO_DMA + +/* + * Trantor T128/T128F/T228 driver + * Note : architecturally, the T100 and T130 are different and won't + * work + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * DISTRIBUTION RELEASE 3. + * + * For more information, please consult + * + * Trantor Systems, Ltd. + * T128/T128F/T228 SCSI Host Adapter + * Hardware Specifications + * + * Trantor Systems, Ltd. + * 5415 Randall Place + * Fremont, CA 94538 + * 1+ (415) 770-1400, FAX 1+ (415) 770-9910 + * + * and + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * Options : + * AUTOSENSE - if defined, REQUEST SENSE will be performed automatically + * for commands that return with a CHECK CONDITION status. + * + * PSEUDO_DMA - enables PSEUDO-DMA hardware, should give a 3-4X performance + * increase compared to polled I/O. + * + * PARITY - enable parity checking. Not supported. + * + * SCSI2 - enable support for SCSI-II tagged queueing. Untested. + * + * + * UNSAFE - leave interrupts enabled during pseudo-DMA transfers. You + * only really want to use this if you're having a problem with + * dropped characters during high speed communications, and even + * then, you're going to be better off twiddling with transfersize. + * + * USLEEP - enable support for devices that don't disconnect. Untested. + * + * The card is detected and initialized in one of several ways : + * 1. Autoprobe (default) - since the board is memory mapped, + * a BIOS signature is scanned for to locate the registers. + * An interrupt is triggered to autoprobe for the interrupt + * line. + * + * 2. With command line overrides - t128=address,irq may be + * used on the LILO command line to override the defaults. + * + * 3. With the T128_OVERRIDE compile time define. This is + * specified as an array of address, irq tuples. Ie, for + * one board at the default 0xcc000 address, IRQ5, I could say + * -DT128_OVERRIDE={{0xcc000, 5}} + * + * Note that if the override methods are used, place holders must + * be specified for other boards in the system. + * + * T128/T128F jumper/dipswitch settings (note : on my sample, the switches + * were epoxy'd shut, meaning I couldn't change the 0xcc000 base address) : + * + * T128 Sw7 Sw8 Sw6 = 0ws Sw5 = boot + * T128F Sw6 Sw7 Sw5 = 0ws Sw4 = boot Sw8 = floppy disable + * cc000 off off + * c8000 off on + * dc000 on off + * d8000 on on + * + * + * Interrupts + * There is a 12 pin jumper block, jp1, numbered as follows : + * T128 (JP1) T128F (J5) + * 2 4 6 8 10 12 11 9 7 5 3 1 + * 1 3 5 7 9 11 12 10 8 6 4 2 + * + * 3 2-4 + * 5 1-3 + * 7 3-5 + * T128F only + * 10 8-10 + * 12 7-9 + * 14 10-12 + * 15 9-11 + */ + +/* + * $Log: t128.c,v $ + */ + +#include <asm/system.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <asm/io.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "t128.h" +#define AUTOPROBE_IRQ +#include "NCR5380.h" +#include "constants.h" +#include "sd.h" +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_t128 = { + PROC_SCSI_T128, 4, "t128", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + + +static struct override { + unsigned char *address; + int irq; +} overrides +#ifdef T128_OVERRIDE + [] = T128_OVERRIDE; +#else + [4] = {{NULL,IRQ_AUTO}, {NULL,IRQ_AUTO}, {NULL,IRQ_AUTO}, + {NULL,IRQ_AUTO}}; +#endif + +#define NO_OVERRIDES (sizeof(overrides) / sizeof(struct override)) + +static struct base { + unsigned char *address; + int noauto; +} bases[] = {{(unsigned char *) 0xcc000, 0}, {(unsigned char *) 0xc8000, 0}, + {(unsigned char *) 0xdc000, 0}, {(unsigned char *) 0xd8000, 0}}; + +#define NO_BASES (sizeof (bases) / sizeof (struct base)) + +static const struct signature { + const char *string; + int offset; +} signatures[] = { +{"TSROM: SCSI BIOS, Version 1.12", 0x36}, +}; + +#define NO_SIGNATURES (sizeof (signatures) / sizeof (struct signature)) + +/* + * Function : t128_setup(char *str, int *ints) + * + * Purpose : LILO command line initialization of the overrides array, + * + * Inputs : str - unused, ints - array of integer parameters with ints[0] + * equal to the number of ints. + * + */ + +void t128_setup(char *str, int *ints) { + static int commandline_current = 0; + int i; + if (ints[0] != 2) + printk("t128_setup : usage t128=address,irq\n"); + else + if (commandline_current < NO_OVERRIDES) { + overrides[commandline_current].address = (unsigned char *) ints[1]; + overrides[commandline_current].irq = ints[2]; + for (i = 0; i < NO_BASES; ++i) + if (bases[i].address == (unsigned char *) ints[1]) { + bases[i].noauto = 1; + break; + } + ++commandline_current; + } +} + +/* + * Function : int t128_detect(Scsi_Host_Template * tpnt) + * + * Purpose : detects and initializes T128,T128F, or T228 controllers + * that were autoprobed, overridden on the LILO command line, + * or specified at compile time. + * + * Inputs : tpnt - template for this SCSI adapter. + * + * Returns : 1 if a host adapter was found, 0 if not. + * + */ + +int t128_detect(Scsi_Host_Template * tpnt) { + static int current_override = 0, current_base = 0; + struct Scsi_Host *instance; + unsigned char *base; + int sig, count; + + tpnt->proc_dir = &proc_scsi_t128; + tpnt->proc_info = &t128_proc_info; + + for (count = 0; current_override < NO_OVERRIDES; ++current_override) { + base = NULL; + + if (overrides[current_override].address) + base = overrides[current_override].address; + else + for (; !base && (current_base < NO_BASES); ++current_base) { +#if (TDEBUG & TDEBUG_INIT) + printk("scsi : probing address %08x\n", (unsigned int) bases[current_base].address); +#endif + for (sig = 0; sig < NO_SIGNATURES; ++sig) + if (!bases[current_base].noauto && !memcmp + (bases[current_base].address + signatures[sig].offset, + signatures[sig].string, strlen(signatures[sig].string))) { + base = bases[current_base].address; +#if (TDEBUG & TDEBUG_INIT) + printk("scsi-t128 : detected board.\n"); +#endif + break; + } + } + +#if defined(TDEBUG) && (TDEBUG & TDEBUG_INIT) + printk("scsi-t128 : base = %08x\n", (unsigned int) base); +#endif + + if (!base) + break; + + instance = scsi_register (tpnt, sizeof(struct NCR5380_hostdata)); + instance->base = base; + + NCR5380_init(instance, 0); + + if (overrides[current_override].irq != IRQ_AUTO) + instance->irq = overrides[current_override].irq; + else + instance->irq = NCR5380_probe_irq(instance, T128_IRQS); + + if (instance->irq != IRQ_NONE) + if (request_irq(instance->irq, t128_intr, SA_INTERRUPT, "t128", NULL)) { + printk("scsi%d : IRQ%d not free, interrupts disabled\n", + instance->host_no, instance->irq); + instance->irq = IRQ_NONE; + } + + if (instance->irq == IRQ_NONE) { + printk("scsi%d : interrupts not enabled. for better interactive performance,\n", instance->host_no); + printk("scsi%d : please jumper the board for a free IRQ.\n", instance->host_no); + } + +#if defined(TDEBUG) && (TDEBUG & TDEBUG_INIT) + printk("scsi%d : irq = %d\n", instance->host_no, instance->irq); +#endif + + printk("scsi%d : at 0x%08x", instance->host_no, (int) + instance->base); + if (instance->irq == IRQ_NONE) + printk (" interrupts disabled"); + else + printk (" irq %d", instance->irq); + printk(" options CAN_QUEUE=%d CMD_PER_LUN=%d release=%d", + CAN_QUEUE, CMD_PER_LUN, T128_PUBLIC_RELEASE); + NCR5380_print_options(instance); + printk("\n"); + + ++current_override; + ++count; + } + return count; +} + +/* + * Function : int t128_biosparam(Disk * disk, kdev_t dev, int *ip) + * + * Purpose : Generates a BIOS / DOS compatible H-C-S mapping for + * the specified device / size. + * + * Inputs : size = size of device in sectors (512 bytes), dev = block device + * major / minor, ip[] = {heads, sectors, cylinders} + * + * Returns : always 0 (success), initializes ip + * + */ + +/* + * XXX Most SCSI boards use this mapping, I could be incorrect. Some one + * using hard disks on a trantor should verify that this mapping corresponds + * to that used by the BIOS / ASPI driver by running the linux fdisk program + * and matching the H_C_S coordinates to what DOS uses. + */ + +int t128_biosparam(Disk * disk, kdev_t dev, int * ip) +{ + int size = disk->capacity; + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + return 0; +} + +/* + * Function : int NCR5380_pread (struct Scsi_Host *instance, + * unsigned char *dst, int len) + * + * Purpose : Fast 5380 pseudo-dma read function, transfers len bytes to + * dst + * + * Inputs : dst = destination, len = length in bytes + * + * Returns : 0 on success, non zero on a failure such as a watchdog + * timeout. + */ + +static inline int NCR5380_pread (struct Scsi_Host *instance, unsigned char *dst, + int len) { + register unsigned char *reg = (unsigned char *) (instance->base + + T_DATA_REG_OFFSET), *d = dst; + register int i = len; + + +#if 0 + for (; i; --i) { + while (!(instance->base[T_STATUS_REG_OFFSET]) & T_ST_RDY) barrier(); +#else + while (!(instance->base[T_STATUS_REG_OFFSET]) & T_ST_RDY) barrier(); + for (; i; --i) { +#endif + *d++ = *reg; + } + + if (*(instance->base + T_STATUS_REG_OFFSET) & T_ST_TIM) { + unsigned char tmp; + volatile unsigned char *foo; + foo = instance->base + T_CONTROL_REG_OFFSET; + tmp = *foo; + *foo = tmp | T_CR_CT; + *foo = tmp; + printk("scsi%d : watchdog timer fired in NCR5380_pread()\n", + instance->host_no); + return -1; + } else + return 0; +} + +/* + * Function : int NCR5380_pwrite (struct Scsi_Host *instance, + * unsigned char *src, int len) + * + * Purpose : Fast 5380 pseudo-dma write function, transfers len bytes from + * src + * + * Inputs : src = source, len = length in bytes + * + * Returns : 0 on success, non zero on a failure such as a watchdog + * timeout. + */ + +static inline int NCR5380_pwrite (struct Scsi_Host *instance, unsigned char *src, + int len) { + register unsigned char *reg = (unsigned char *) (instance->base + + T_DATA_REG_OFFSET), *s = src; + register int i = len; + +#if 0 + for (; i; --i) { + while (!(instance->base[T_STATUS_REG_OFFSET]) & T_ST_RDY) barrier(); +#else + while (!(instance->base[T_STATUS_REG_OFFSET]) & T_ST_RDY) barrier(); + for (; i; --i) { +#endif + *reg = *s++; + } + + if (*(instance->base + T_STATUS_REG_OFFSET) & T_ST_TIM) { + unsigned char tmp; + volatile unsigned char *foo; + foo = instance->base + T_CONTROL_REG_OFFSET; + tmp = *foo; + *foo = tmp | T_CR_CT; + *foo = tmp; + printk("scsi%d : watchdog timer fired in NCR5380_pwrite()\n", + instance->host_no); + return -1; + } else + return 0; +} + +#include "NCR5380.c" + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = TRANTOR_T128; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/t128.h b/linux/src/drivers/scsi/t128.h new file mode 100644 index 00000000..43a89820 --- /dev/null +++ b/linux/src/drivers/scsi/t128.h @@ -0,0 +1,173 @@ +/* + * Trantor T128/T128F/T228 defines + * Note : architecturally, the T100 and T128 are different and won't work + * + * Copyright 1993, Drew Eckhardt + * Visionary Computing + * (Unix and Linux consulting and custom programming) + * drew@colorado.edu + * +1 (303) 440-4894 + * + * DISTRIBUTION RELEASE 3. + * + * For more information, please consult + * + * Trantor Systems, Ltd. + * T128/T128F/T228 SCSI Host Adapter + * Hardware Specifications + * + * Trantor Systems, Ltd. + * 5415 Randall Place + * Fremont, CA 94538 + * 1+ (415) 770-1400, FAX 1+ (415) 770-9910 + * + * and + * + * NCR 5380 Family + * SCSI Protocol Controller + * Databook + * + * NCR Microelectronics + * 1635 Aeroplaza Drive + * Colorado Springs, CO 80916 + * 1+ (719) 578-3400 + * 1+ (800) 334-5454 + */ + +/* + * $Log: t128.h,v $ + */ + +#ifndef T128_H +#define T128_H + +#define T128_PUBLIC_RELEASE 3 + +#define TDEBUG_INIT 0x1 +#define TDEBUG_TRANSFER 0x2 + +/* + * The trantor boards are memory mapped. They use an NCR5380 or + * equivalent (my sample board had part second sourced from ZILOG). + * NCR's recommended "Pseudo-DMA" architecture is used, where + * a PAL drives the DMA signals on the 5380 allowing fast, blind + * transfers with proper handshaking. + */ + +/* + * Note : a boot switch is provided for the purpose of informing the + * firmware to boot or not boot from attached SCSI devices. So, I imagine + * there are fewer people who've yanked the ROM like they do on the Seagate + * to make bootup faster, and I'll probably use this for autodetection. + */ +#define T_ROM_OFFSET 0 + +/* + * Note : my sample board *WAS NOT* populated with the SRAM, so this + * can't be used for autodetection without a ROM present. + */ +#define T_RAM_OFFSET 0x1800 + +/* + * All of the registers are allocated 32 bytes of address space, except + * for the data register (read/write to/from the 5380 in pseudo-DMA mode) + */ +#define T_CONTROL_REG_OFFSET 0x1c00 /* rw */ +#define T_CR_INT 0x10 /* Enable interrupts */ +#define T_CR_CT 0x02 /* Reset watchdog timer */ + +#define T_STATUS_REG_OFFSET 0x1c20 /* ro */ +#define T_ST_BOOT 0x80 /* Boot switch */ +#define T_ST_S3 0x40 /* User settable switches, */ +#define T_ST_S2 0x20 /* read 0 when switch is on, 1 off */ +#define T_ST_S1 0x10 +#define T_ST_PS2 0x08 /* Set for Microchannel 228 */ +#define T_ST_RDY 0x04 /* 5380 DRQ */ +#define T_ST_TIM 0x02 /* indicates 40us watchdog timer fired */ +#define T_ST_ZERO 0x01 /* Always zero */ + +#define T_5380_OFFSET 0x1d00 /* 8 registers here, see NCR5380.h */ + +#define T_DATA_REG_OFFSET 0x1e00 /* rw 512 bytes long */ + +#ifndef ASM +int t128_abort(Scsi_Cmnd *); +int t128_biosparam(Disk *, kdev_t, int*); +int t128_detect(Scsi_Host_Template *); +int t128_queue_command(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int t128_reset(Scsi_Cmnd *, unsigned int reset_flags); +int t128_proc_info (char *buffer, char **start, off_t offset, + int length, int hostno, int inout); + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef CMD_PER_LUN +#define CMD_PER_LUN 2 +#endif + +#ifndef CAN_QUEUE +#define CAN_QUEUE 32 +#endif + +/* + * I hadn't thought of this with the earlier drivers - but to prevent + * macro definition conflicts, we shouldn't define all of the internal + * macros when this is being used solely for the host stub. + */ + +#if defined(HOSTS_C) || defined(MODULE) + +#define TRANTOR_T128 {NULL, NULL, NULL, NULL, \ + "Trantor T128/T128F/T228", t128_detect, NULL, \ + NULL, \ + NULL, t128_queue_command, t128_abort, t128_reset, NULL, \ + t128_biosparam, \ + /* can queue */ CAN_QUEUE, /* id */ 7, SG_ALL, \ + /* cmd per lun */ CMD_PER_LUN , 0, 0, DISABLE_CLUSTERING} + +#endif + +#ifndef HOSTS_C + +#define NCR5380_implementation_fields \ + volatile unsigned char *base + +#define NCR5380_local_declare() \ + volatile unsigned char *base + +#define NCR5380_setup(instance) \ + base = (volatile unsigned char *) (instance)->base + +#define T128_address(reg) (base + T_5380_OFFSET + ((reg) * 0x20)) + +#if !(TDEBUG & TDEBUG_TRANSFER) +#define NCR5380_read(reg) (*(T128_address(reg))) +#define NCR5380_write(reg, value) (*(T128_address(reg)) = (value)) +#else +#define NCR5380_read(reg) \ + (((unsigned char) printk("scsi%d : read register %d at address %08x\n"\ + , instance->hostno, (reg), T128_address(reg))), *(T128_address(reg))) + +#define NCR5380_write(reg, value) { \ + printk("scsi%d : write %02x to register %d at address %08x\n", \ + instance->hostno, (value), (reg), T128_address(reg)); \ + *(T128_address(reg)) = (value); \ +} +#endif + +#define NCR5380_intr t128_intr +#define NCR5380_queue_command t128_queue_command +#define NCR5380_abort t128_abort +#define NCR5380_reset t128_reset +#define NCR5380_proc_info t128_proc_info + +/* 15 14 12 10 7 5 3 + 1101 0100 1010 1000 */ + +#define T128_IRQS 0xc4a8 + +#endif /* else def HOSTS_C */ +#endif /* ndef ASM */ +#endif /* T128_H */ diff --git a/linux/src/drivers/scsi/tmscsim.c b/linux/src/drivers/scsi/tmscsim.c new file mode 100644 index 00000000..be986ff0 --- /dev/null +++ b/linux/src/drivers/scsi/tmscsim.c @@ -0,0 +1,1930 @@ +/*********************************************************************** + * FILE NAME : TMSCSIM.C * + * BY : C.L. Huang, ching@tekram.com.tw * + * Description: Device Driver for Tekram DC-390(T) PCI SCSI * + * Bus Master Host Adapter * + * (C)Copyright 1995-1996 Tekram Technology Co., Ltd. * + ***********************************************************************/ +/* Minor enhancements and bugfixes by * + * Kurt Garloff <K.Garloff@ping.de> * + ***********************************************************************/ +/* HISTORY: * + * * + * REV# DATE NAME DESCRIPTION * + * 1.00 04/24/96 CLH First release * + * 1.01 06/12/96 CLH Fixed bug of Media Change for Removable * + * Device, scan all LUN. Support Pre2.0.10 * + * 1.02 06/18/96 CLH Fixed bug of Command timeout ... * + * 1.03 09/25/96 KG Added tmscsim_proc_info() * + * 1.04 10/11/96 CLH Updating for support KV 2.0.x * + * 1.05 10/18/96 KG Fixed bug in DC390_abort(null ptr deref)* + * 1.06 10/25/96 KG Fixed module support * + * 1.07 11/09/96 KG Fixed tmscsim_proc_info() * + * 1.08 11/18/96 KG Fixed null ptr in DC390_Disconnect() * + * 1.09 11/30/96 KG Added register the allocated IO space * + * 1.10 12/05/96 CLH Modified tmscsim_proc_info(), and reset * + * pending interrupt in DC390_detect() * + * 1.11 02/05/97 KG/CLH Fixeds problem with partitions greater * + * than 1GB * + ***********************************************************************/ + + +#define DC390_DEBUG + +#define SCSI_MALLOC + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/system.h> +#include <linux/delay.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/config.h> + +#include <linux/version.h> +#if LINUX_VERSION_CODE < 66354 /* 1.3.50 */ +#include "../block/blk.h" +#else +#include <linux/blk.h> +#endif + +#include "scsi.h" +#include "hosts.h" +#include "tmscsim.h" +#include "constants.h" +#include "sd.h" +#include <linux/stat.h> + +#include "dc390.h" + +#define PCI_DEVICE_ID_AMD53C974 PCI_DEVICE_ID_AMD_SCSI + + +#ifndef VERSION_ELF_1_2_13 +struct proc_dir_entry proc_scsi_tmscsim ={ + PROC_SCSI_DC390T, 7 ,"tmscsim", + S_IFDIR | S_IRUGO | S_IXUGO, 2 + }; +#endif + +static USHORT DC390_StartSCSI( PACB pACB, PDCB pDCB, PSRB pSRB ); +static void DC390_DataOut_0( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_DataIn_0( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_Command_0( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_Status_0( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_MsgOut_0( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_MsgIn_0( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_DataOutPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_DataInPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_CommandPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_StatusPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_MsgOutPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_MsgInPhase( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_Nop_0( PACB pACB, PSRB pSRB, PUCHAR psstatus); +static void DC390_Nop_1( PACB pACB, PSRB pSRB, PUCHAR psstatus); + +static void SetXferRate( PACB pACB, PDCB pDCB ); +static void DC390_Disconnect( PACB pACB ); +static void DC390_Reselect( PACB pACB ); +static void SRBdone( PACB pACB, PDCB pDCB, PSRB pSRB ); +static void DoingSRB_Done( PACB pACB ); +static void DC390_ScsiRstDetect( PACB pACB ); +static void DC390_ResetSCSIBus( PACB pACB ); +static void RequestSense( PACB pACB, PDCB pDCB, PSRB pSRB ); +static void EnableMsgOut2( PACB pACB, PSRB pSRB ); +static void EnableMsgOut( PACB pACB, PSRB pSRB ); +static void DC390_InvalidCmd( PACB pACB ); + +int DC390_initAdapter( PSH psh, ULONG io_port, UCHAR Irq, USHORT index ); +void DC390_initDCB( PACB pACB, PDCB pDCB, PSCSICMD cmd ); + +#ifdef MODULE +static int DC390_release(struct Scsi_Host *host); +static int DC390_shutdown (struct Scsi_Host *host); +#endif + + +static PSHT pSHT_start = NULL; +static PSH pSH_start = NULL; +static PSH pSH_current = NULL; +static PACB pACB_start= NULL; +static PACB pACB_current = NULL; +static PDCB pPrevDCB = NULL; +static USHORT adapterCnt = 0; +static USHORT InitialTime = 0; +static USHORT CurrSyncOffset = 0; +static ULONG mech1addr; +static UCHAR mech2bus, mech2Agent, mech2CfgSPenR; + +static PVOID DC390_phase0[]={ + DC390_DataOut_0, + DC390_DataIn_0, + DC390_Command_0, + DC390_Status_0, + DC390_Nop_0, + DC390_Nop_0, + DC390_MsgOut_0, + DC390_MsgIn_0, + DC390_Nop_1 + }; + +static PVOID DC390_phase1[]={ + DC390_DataOutPhase, + DC390_DataInPhase, + DC390_CommandPhase, + DC390_StatusPhase, + DC390_Nop_0, + DC390_Nop_0, + DC390_MsgOutPhase, + DC390_MsgInPhase, + DC390_Nop_1, + }; + +UCHAR eepromBuf[MAX_ADAPTER_NUM][128]; + + +UCHAR clock_period1[] = {4, 5, 6, 7, 8, 10, 13, 20}; + +UCHAR baddevname1[2][28] ={ + "SEAGATE ST3390N 9546", + "HP C3323-300 4269"}; + +#define BADDEVCNT 2 + + +/*********************************************************************** + * + * + * + **********************************************************************/ +static void +QLinkcmd( PSCSICMD cmd, PDCB pDCB ) +{ + ULONG flags; + PSCSICMD pcmd; + + save_flags(flags); + cli(); + + if( !pDCB->QIORBCnt ) + { + pDCB->pQIORBhead = cmd; + pDCB->pQIORBtail = cmd; + pDCB->QIORBCnt++; + cmd->next = NULL; + } + else + { + pcmd = pDCB->pQIORBtail; + pcmd->next = cmd; + pDCB->pQIORBtail = cmd; + pDCB->QIORBCnt++; + cmd->next = NULL; + } + + restore_flags(flags); +} + + +static PSCSICMD +Getcmd( PDCB pDCB ) +{ + ULONG flags; + PSCSICMD pcmd; + + save_flags(flags); + cli(); + + pcmd = pDCB->pQIORBhead; + pDCB->pQIORBhead = pcmd->next; + pcmd->next = NULL; + pDCB->QIORBCnt--; + + restore_flags(flags); + return( pcmd ); +} + + +static PSRB +GetSRB( PACB pACB ) +{ + ULONG flags; + PSRB pSRB; + + save_flags(flags); + cli(); + + pSRB = pACB->pFreeSRB; + if( pSRB ) + { + pACB->pFreeSRB = pSRB->pNextSRB; + pSRB->pNextSRB = NULL; + } + restore_flags(flags); + return( pSRB ); +} + + +static void +RewaitSRB0( PDCB pDCB, PSRB pSRB ) +{ + PSRB psrb1; + ULONG flags; + + save_flags(flags); + cli(); + + if( (psrb1 = pDCB->pWaitingSRB) ) + { + pSRB->pNextSRB = psrb1; + pDCB->pWaitingSRB = pSRB; + } + else + { + pSRB->pNextSRB = NULL; + pDCB->pWaitingSRB = pSRB; + pDCB->pWaitLast = pSRB; + } + restore_flags(flags); +} + + +static void +RewaitSRB( PDCB pDCB, PSRB pSRB ) +{ + PSRB psrb1; + ULONG flags; + UCHAR bval; + + save_flags(flags); + cli(); + pDCB->GoingSRBCnt--; + psrb1 = pDCB->pGoingSRB; + if( pSRB == psrb1 ) + { + pDCB->pGoingSRB = psrb1->pNextSRB; + } + else + { + while( pSRB != psrb1->pNextSRB ) + psrb1 = psrb1->pNextSRB; + psrb1->pNextSRB = pSRB->pNextSRB; + if( pSRB == pDCB->pGoingLast ) + pDCB->pGoingLast = psrb1; + } + if( (psrb1 = pDCB->pWaitingSRB) ) + { + pSRB->pNextSRB = psrb1; + pDCB->pWaitingSRB = pSRB; + } + else + { + pSRB->pNextSRB = NULL; + pDCB->pWaitingSRB = pSRB; + pDCB->pWaitLast = pSRB; + } + + bval = pSRB->TagNumber; + pDCB->TagMask &= (~(1 << bval)); /* Free TAG number */ + restore_flags(flags); +} + + +static void +DoWaitingSRB( PACB pACB ) +{ + ULONG flags; + PDCB ptr, ptr1; + PSRB pSRB; + + save_flags(flags); + cli(); + + if( !(pACB->pActiveDCB) && !(pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV) ) ) + { + ptr = pACB->pDCBRunRobin; + if( !ptr ) + { + ptr = pACB->pLinkDCB; + pACB->pDCBRunRobin = ptr; + } + ptr1 = ptr; + for( ;ptr1; ) + { + pACB->pDCBRunRobin = ptr1->pNextDCB; + if( !( ptr1->MaxCommand > ptr1->GoingSRBCnt ) || + !( pSRB = ptr1->pWaitingSRB ) ) + { + if(pACB->pDCBRunRobin == ptr) + break; + ptr1 = ptr1->pNextDCB; + } + else + { + if( !DC390_StartSCSI(pACB, ptr1, pSRB) ) + { + ptr1->GoingSRBCnt++; + if( ptr1->pWaitLast == pSRB ) + { + ptr1->pWaitingSRB = NULL; + ptr1->pWaitLast = NULL; + } + else + { + ptr1->pWaitingSRB = pSRB->pNextSRB; + } + pSRB->pNextSRB = NULL; + + if( ptr1->pGoingSRB ) + ptr1->pGoingLast->pNextSRB = pSRB; + else + ptr1->pGoingSRB = pSRB; + ptr1->pGoingLast = pSRB; + } + break; + } + } + } + restore_flags(flags); + return; +} + + +static void +SRBwaiting( PDCB pDCB, PSRB pSRB) +{ + if( pDCB->pWaitingSRB ) + { + pDCB->pWaitLast->pNextSRB = pSRB; + pDCB->pWaitLast = pSRB; + pSRB->pNextSRB = NULL; + } + else + { + pDCB->pWaitingSRB = pSRB; + pDCB->pWaitLast = pSRB; + } +} + + +static void +SendSRB( PSCSICMD pcmd, PACB pACB, PSRB pSRB ) +{ + ULONG flags; + PDCB pDCB; + + save_flags(flags); + cli(); + + pDCB = pSRB->pSRBDCB; + if( !(pDCB->MaxCommand > pDCB->GoingSRBCnt) || (pACB->pActiveDCB) || + (pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV)) ) + { + SRBwaiting(pDCB, pSRB); + goto SND_EXIT; + } + + if( pDCB->pWaitingSRB ) + { + SRBwaiting(pDCB, pSRB); +/* pSRB = GetWaitingSRB(pDCB); */ + pSRB = pDCB->pWaitingSRB; + pDCB->pWaitingSRB = pSRB->pNextSRB; + pSRB->pNextSRB = NULL; + } + + if( !DC390_StartSCSI(pACB, pDCB, pSRB) ) + { + pDCB->GoingSRBCnt++; + if( pDCB->pGoingSRB ) + { + pDCB->pGoingLast->pNextSRB = pSRB; + pDCB->pGoingLast = pSRB; + } + else + { + pDCB->pGoingSRB = pSRB; + pDCB->pGoingLast = pSRB; + } + } + else + RewaitSRB0( pDCB, pSRB ); + +SND_EXIT: + restore_flags(flags); + return; +} + + +/*********************************************************************** + * Function : static int DC390_queue_command (Scsi_Cmnd *cmd, + * void (*done)(Scsi_Cmnd *)) + * + * Purpose : enqueues a SCSI command + * + * Inputs : cmd - SCSI command, done - function called on completion, with + * a pointer to the command descriptor. + * + * Returns : 0 + * + ***********************************************************************/ + +int +DC390_queue_command (Scsi_Cmnd *cmd, void (* done)(Scsi_Cmnd *)) +{ + USHORT ioport, i; + Scsi_Cmnd *pcmd; + struct Scsi_Host *psh; + PACB pACB; + PDCB pDCB; + PSRB pSRB; + ULONG flags; + PUCHAR ptr,ptr1; + + psh = cmd->host; + pACB = (PACB ) psh->hostdata; + ioport = pACB->IOPortBase; + +#ifdef DC390_DEBUG0 +/* if(pACB->scan_devices) */ + printk("Cmd=%2x,ID=%d,LUN=%d,",cmd->cmnd[0],cmd->target,cmd->lun); +#endif + + if( (pACB->scan_devices == END_SCAN) && (cmd->cmnd[0] != INQUIRY) ) + { + pACB->scan_devices = 0; + pPrevDCB->pNextDCB = pACB->pLinkDCB; + } + else if( (pACB->scan_devices) && (cmd->cmnd[0] == 8) ) + { + pACB->scan_devices = 0; + pPrevDCB->pNextDCB = pACB->pLinkDCB; + } + + if ( ( cmd->target > pACB->max_id ) || (cmd->lun > pACB->max_lun) ) + { +/* printk("DC390: Ignore target %d lun %d\n", + cmd->target, cmd->lun); */ + cmd->result = (DID_BAD_TARGET << 16); + done(cmd); + return( 0 ); + } + + if( (pACB->scan_devices) && !(pACB->DCBmap[cmd->target] & (1 << cmd->lun)) ) + { + if( pACB->DeviceCnt < MAX_DEVICES ) + { + pACB->DCBmap[cmd->target] |= (1 << cmd->lun); + pDCB = pACB->pDCB_free; +#ifdef DC390_DEBUG0 + printk("pDCB=%8x,ID=%2x,", (UINT) pDCB, cmd->target); +#endif + DC390_initDCB( pACB, pDCB, cmd ); + } + else /* ???? */ + { +/* printk("DC390: Ignore target %d lun %d\n", + cmd->target, cmd->lun); */ + cmd->result = (DID_BAD_TARGET << 16); + done(cmd); + return(0); + } + } + else if( !(pACB->scan_devices) && !(pACB->DCBmap[cmd->target] & (1 << cmd->lun)) ) + { +/* printk("DC390: Ignore target %d lun %d\n", + cmd->target, cmd->lun); */ + cmd->result = (DID_BAD_TARGET << 16); + done(cmd); + return(0); + } + else + { + pDCB = pACB->pLinkDCB; + while( (pDCB->UnitSCSIID != cmd->target) || + (pDCB->UnitSCSILUN != cmd->lun) ) + { + pDCB = pDCB->pNextDCB; + } +#ifdef DC390_DEBUG0 + printk("pDCB=%8x,ID=%2x,", (UINT) pDCB, cmd->target); +#endif + } + + cmd->scsi_done = done; + cmd->result = 0; + + save_flags(flags); + cli(); + + if( pDCB->QIORBCnt ) + { + QLinkcmd( cmd, pDCB ); + pcmd = Getcmd( pDCB ); + } + else + pcmd = cmd; + + pSRB = GetSRB( pACB ); + + if( !pSRB ) + { + QLinkcmd( pcmd, pDCB ); + restore_flags(flags); + return(0); + } + +/* BuildSRB(pSRB); */ + + pSRB->pSRBDCB = pDCB; + pSRB->pcmd = pcmd; + ptr = (PUCHAR) pSRB->CmdBlock; + ptr1 = (PUCHAR) pcmd->cmnd; + pSRB->ScsiCmdLen = pcmd->cmd_len; + for(i=0; i< pcmd->cmd_len; i++) + { + *ptr = *ptr1; + ptr++; + ptr1++; + } + if( pcmd->use_sg ) + { + pSRB->SGcount = (UCHAR) pcmd->use_sg; + pSRB->pSegmentList = (PSGL) pcmd->request_buffer; + } + else if( pcmd->request_buffer ) + { + pSRB->SGcount = 1; + pSRB->pSegmentList = (PSGL) &pSRB->Segmentx; + pSRB->Segmentx.address = (PUCHAR) pcmd->request_buffer; + pSRB->Segmentx.length = pcmd->request_bufflen; + } + else + pSRB->SGcount = 0; + + pSRB->SGIndex = 0; + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + pSRB->MsgCnt = 0; + if( pDCB->DevType != TYPE_TAPE ) + pSRB->RetryCnt = 1; + else + pSRB->RetryCnt = 0; + pSRB->SRBStatus = 0; + pSRB->SRBFlag = 0; + pSRB->SRBState = 0; + pSRB->TotalXferredLen = 0; + pSRB->SGPhysAddr = 0; + pSRB->SGToBeXferLen = 0; + pSRB->ScsiPhase = 0; + pSRB->EndMessage = 0; + SendSRB( pcmd, pACB, pSRB ); + + restore_flags(flags); + return(0); +} + + +static void +DoNextCmd( PACB pACB, PDCB pDCB ) +{ + Scsi_Cmnd *pcmd; + PSRB pSRB; + ULONG flags; + PUCHAR ptr,ptr1; + USHORT i; + + + if( pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV) ) + return; + save_flags(flags); + cli(); + + pcmd = Getcmd( pDCB ); + pSRB = GetSRB( pACB ); + if( !pSRB ) + { + QLinkcmd( pcmd, pDCB ); + restore_flags(flags); + return; + } + + pSRB->pSRBDCB = pDCB; + pSRB->pcmd = pcmd; + ptr = (PUCHAR) pSRB->CmdBlock; + ptr1 = (PUCHAR) pcmd->cmnd; + pSRB->ScsiCmdLen = pcmd->cmd_len; + for(i=0; i< pcmd->cmd_len; i++) + { + *ptr = *ptr1; + ptr++; + ptr1++; + } + if( pcmd->use_sg ) + { + pSRB->SGcount = (UCHAR) pcmd->use_sg; + pSRB->pSegmentList = (PSGL) pcmd->request_buffer; + } + else if( pcmd->request_buffer ) + { + pSRB->SGcount = 1; + pSRB->pSegmentList = (PSGL) &pSRB->Segmentx; + pSRB->Segmentx.address = (PUCHAR) pcmd->request_buffer; + pSRB->Segmentx.length = pcmd->request_bufflen; + } + else + pSRB->SGcount = 0; + + pSRB->SGIndex = 0; + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + pSRB->MsgCnt = 0; + if( pDCB->DevType != TYPE_TAPE ) + pSRB->RetryCnt = 1; + else + pSRB->RetryCnt = 0; + pSRB->SRBStatus = 0; + pSRB->SRBFlag = 0; + pSRB->SRBState = 0; + pSRB->TotalXferredLen = 0; + pSRB->SGPhysAddr = 0; + pSRB->SGToBeXferLen = 0; + pSRB->ScsiPhase = 0; + pSRB->EndMessage = 0; + SendSRB( pcmd, pACB, pSRB ); + + restore_flags(flags); + return; +} + + +/*********************************************************************** + * Function: + * DC390_bios_param + * + * Description: + * Return the disk geometry for the given SCSI device. + ***********************************************************************/ +#ifdef VERSION_ELF_1_2_13 +int DC390_bios_param(Disk *disk, int devno, int geom[]) +#else +int DC390_bios_param(Disk *disk, kdev_t devno, int geom[]) +#endif +{ + int heads, sectors, cylinders; + PACB pACB; + + pACB = (PACB) disk->device->host->hostdata; + heads = 64; + sectors = 32; + cylinders = disk->capacity / (heads * sectors); + + if ( (pACB->Gmode2 & GREATER_1G) && (cylinders > 1024) ) + { + heads = 255; + sectors = 63; + cylinders = disk->capacity / (heads * sectors); + } + + geom[0] = heads; + geom[1] = sectors; + geom[2] = cylinders; + + return (0); +} + + +/*********************************************************************** + * Function : int DC390_abort (Scsi_Cmnd *cmd) + * + * Purpose : Abort an errant SCSI command + * + * Inputs : cmd - command to abort + * + * Returns : 0 on success, -1 on failure. + ***********************************************************************/ + +int +DC390_abort (Scsi_Cmnd *cmd) +{ + ULONG flags; + PACB pACB; + PDCB pDCB, pdcb; + PSRB pSRB, psrb; + USHORT count, i; + PSCSICMD pcmd, pcmd1; + int status; + + +#ifdef DC390_DEBUG0 + printk("DC390 : Abort Cmd."); +#endif + + save_flags(flags); + cli(); + + pACB = (PACB) cmd->host->hostdata; + pDCB = pACB->pLinkDCB; + pdcb = pDCB; + while( (pDCB->UnitSCSIID != cmd->target) || + (pDCB->UnitSCSILUN != cmd->lun) ) + { + pDCB = pDCB->pNextDCB; + if( pDCB == pdcb ) + goto NOT_RUN; + } + + if( pDCB->QIORBCnt ) + { + pcmd = pDCB->pQIORBhead; + if( pcmd == cmd ) + { + pDCB->pQIORBhead = pcmd->next; + pcmd->next = NULL; + pDCB->QIORBCnt--; + status = SCSI_ABORT_SUCCESS; + goto ABO_X; + } + for( count = pDCB->QIORBCnt, i=0; i<count-1; i++) + { + if( pcmd->next == cmd ) + { + pcmd1 = pcmd->next; + pcmd->next = pcmd1->next; + pcmd1->next = NULL; + pDCB->QIORBCnt--; + status = SCSI_ABORT_SUCCESS; + goto ABO_X; + } + else + { + pcmd = pcmd->next; + } + } + } + + pSRB = pDCB->pWaitingSRB; + if( !pSRB ) + goto ON_GOING; + if( pSRB->pcmd == cmd ) + { + pDCB->pWaitingSRB = pSRB->pNextSRB; + goto IN_WAIT; + } + else + { + psrb = pSRB; + if( !(psrb->pNextSRB) ) + goto ON_GOING; + while( psrb->pNextSRB->pcmd != cmd ) + { + psrb = psrb->pNextSRB; + if( !(psrb->pNextSRB) ) + goto ON_GOING; + } + pSRB = psrb->pNextSRB; + psrb->pNextSRB = pSRB->pNextSRB; + if( pSRB == pDCB->pWaitLast ) + pDCB->pWaitLast = psrb; /* No check for psrb == NULL ? */ +IN_WAIT: + pSRB->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB; + cmd->next = NULL; + status = SCSI_ABORT_SUCCESS; + goto ABO_X; + } + +ON_GOING: + pSRB = pDCB->pGoingSRB; + for( count = pDCB->GoingSRBCnt, i=0; i<count; i++) + { + if( pSRB->pcmd != cmd ) + pSRB = pSRB->pNextSRB; + else + { + if( (pACB->pActiveDCB == pDCB) && (pDCB->pActiveSRB == pSRB) ) + { + status = SCSI_ABORT_BUSY; + goto ABO_X; + } + else + { + status = SCSI_ABORT_SNOOZE; + goto ABO_X; + } + } + } + +NOT_RUN: + status = SCSI_ABORT_NOT_RUNNING; + +ABO_X: + cmd->result = DID_ABORT << 16; + cmd->scsi_done(cmd); + restore_flags(flags); + return( status ); +} + + +static void +ResetDevParam( PACB pACB ) +{ + PDCB pDCB, pdcb; + + pDCB = pACB->pLinkDCB; + if( pDCB == NULL ) + return; + pdcb = pDCB; + do + { + pDCB->SyncMode &= ~SYNC_NEGO_DONE; + pDCB->SyncPeriod = 0; + pDCB->SyncOffset = 0; + pDCB->CtrlR3 = FAST_CLK; + pDCB->CtrlR4 &= NEGATE_REQACKDATA; + pDCB->CtrlR4 |= EATER_25NS; + pDCB = pDCB->pNextDCB; + } + while( pdcb != pDCB ); +} + + +static void +RecoverSRB( PACB pACB ) +{ + PDCB pDCB, pdcb; + PSRB psrb, psrb2; + USHORT cnt, i; + + pDCB = pACB->pLinkDCB; + if( pDCB == NULL ) + return; + pdcb = pDCB; + do + { + cnt = pdcb->GoingSRBCnt; + psrb = pdcb->pGoingSRB; + for (i=0; i<cnt; i++) + { + psrb2 = psrb; + psrb = psrb->pNextSRB; +/* RewaitSRB( pDCB, psrb ); */ + if( pdcb->pWaitingSRB ) + { + psrb2->pNextSRB = pdcb->pWaitingSRB; + pdcb->pWaitingSRB = psrb2; + } + else + { + pdcb->pWaitingSRB = psrb2; + pdcb->pWaitLast = psrb2; + psrb2->pNextSRB = NULL; + } + } + pdcb->GoingSRBCnt = 0; + pdcb->pGoingSRB = NULL; + pdcb->TagMask = 0; + pdcb = pdcb->pNextDCB; + } + while( pdcb != pDCB ); +} + + +/*********************************************************************** + * Function : int DC390_reset (Scsi_Cmnd *cmd, ...) + * + * Purpose : perform a hard reset on the SCSI bus + * + * Inputs : cmd - command which caused the SCSI RESET + * + * Returns : 0 on success. + ***********************************************************************/ + +#ifdef VERSION_2_0_0 +int DC390_reset(Scsi_Cmnd *cmd, unsigned int resetFlags) +#else +int DC390_reset (Scsi_Cmnd *cmd) +#endif +{ + USHORT ioport; + unsigned long flags; + PACB pACB; + UCHAR bval; + USHORT i; + + +#ifdef DC390_DEBUG1 + printk("DC390: RESET,"); +#endif + + pACB = (PACB ) cmd->host->hostdata; + ioport = pACB->IOPortBase; + save_flags(flags); + cli(); + bval = inb(ioport+CtrlReg1); + bval |= DIS_INT_ON_SCSI_RST; + outb(bval,ioport+CtrlReg1); /* disable interrupt */ + DC390_ResetSCSIBus( pACB ); + for( i=0; i<500; i++ ) + udelay(1000); + bval = inb(ioport+CtrlReg1); + bval &= ~DIS_INT_ON_SCSI_RST; + outb(bval,ioport+CtrlReg1); /* re-enable interrupt */ + + bval = DMA_IDLE_CMD; + outb(bval,ioport+DMA_Cmd); + bval = CLEAR_FIFO_CMD; + outb(bval,ioport+ScsiCmd); + + ResetDevParam( pACB ); + DoingSRB_Done( pACB ); + pACB->pActiveDCB = NULL; + + pACB->ACBFlag = 0; + DoWaitingSRB( pACB ); + + restore_flags(flags); +#ifdef DC390_DEBUG1 + printk("DC390: RESET1,"); +#endif + return( SCSI_RESET_SUCCESS ); +} + + +#include "scsiiom.c" + + +/*********************************************************************** + * Function : static void DC390_initDCB + * + * Purpose : initialize the internal structures for a given DCB + * + * Inputs : cmd - pointer to this scsi cmd request block structure + * + ***********************************************************************/ +void DC390_initDCB( PACB pACB, PDCB pDCB, PSCSICMD cmd ) +{ + PEEprom prom; + UCHAR bval; + USHORT index; + + if( pACB->DeviceCnt == 0 ) + { + pACB->pLinkDCB = pDCB; + pACB->pDCBRunRobin = pDCB; + pDCB->pNextDCB = pDCB; + pPrevDCB = pDCB; + } + else + pPrevDCB->pNextDCB = pDCB; + + pDCB->pDCBACB = pACB; + pDCB->QIORBCnt = 0; + pDCB->UnitSCSIID = cmd->target; + pDCB->UnitSCSILUN = cmd->lun; + pDCB->pWaitingSRB = NULL; + pDCB->pGoingSRB = NULL; + pDCB->GoingSRBCnt = 0; + pDCB->pActiveSRB = NULL; + pDCB->TagMask = 0; + pDCB->MaxCommand = 1; + pDCB->AdaptIndex = pACB->AdapterIndex; + index = pACB->AdapterIndex; + pDCB->DCBFlag = 0; + + prom = (PEEprom) &eepromBuf[index][cmd->target << 2]; + pDCB->DevMode = prom->EE_MODE1; + pDCB->AdpMode = eepromBuf[index][EE_MODE2]; + + if( pDCB->DevMode & EN_DISCONNECT_ ) + bval = 0xC0; + else + bval = 0x80; + bval |= cmd->lun; + pDCB->IdentifyMsg = bval; + + pDCB->SyncMode = 0; + if( pDCB->DevMode & SYNC_NEGO_ ) + { + if( !(cmd->lun) || CurrSyncOffset ) + pDCB->SyncMode = SYNC_ENABLE; + } + + pDCB->SyncPeriod = 0; + pDCB->SyncOffset = 0; + pDCB->NegoPeriod = (clock_period1[prom->EE_SPEED] * 25) >> 2; + + pDCB->CtrlR1 = pACB->AdaptSCSIID; + if( pDCB->DevMode & PARITY_CHK_ ) + pDCB->CtrlR1 |= PARITY_ERR_REPO; + + pDCB->CtrlR3 = FAST_CLK; + + pDCB->CtrlR4 = EATER_25NS; + if( pDCB->AdpMode & ACTIVE_NEGATION) + pDCB->CtrlR4 |= NEGATE_REQACKDATA; +} + + +/*********************************************************************** + * Function : static void DC390_initSRB + * + * Purpose : initialize the internal structures for a given SRB + * + * Inputs : psrb - pointer to this scsi request block structure + * + ***********************************************************************/ +void DC390_initSRB( PSRB psrb ) +{ +#ifndef VERSION_ELF_1_2_13 +#ifdef DC390_DEBUG0 + printk("DC390 init: %08lx %08lx,",(ULONG)psrb,(ULONG)virt_to_bus(psrb)); +#endif + psrb->PhysSRB = virt_to_bus( psrb ); +#else + psrb->PhysSRB = (ULONG) psrb; +#endif +} + + +void DC390_linkSRB( PACB pACB ) +{ + USHORT count, i; + PSRB psrb; + + count = pACB->SRBCount; + + for( i=0; i< count; i++) + { + if( i != count - 1) + pACB->SRB_array[i].pNextSRB = &pACB->SRB_array[i+1]; + else + pACB->SRB_array[i].pNextSRB = NULL; + psrb = (PSRB) &pACB->SRB_array[i]; + DC390_initSRB( psrb ); + } +} + + +/*********************************************************************** + * Function : static void DC390_initACB + * + * Purpose : initialize the internal structures for a given SCSI host + * + * Inputs : psh - pointer to this host adapter's structure + * + ***********************************************************************/ +void DC390_initACB( PSH psh, ULONG io_port, UCHAR Irq, USHORT index ) +{ + PACB pACB; + USHORT i; + + psh->can_queue = MAX_CMD_QUEUE; + psh->cmd_per_lun = MAX_CMD_PER_LUN; + psh->this_id = (int) eepromBuf[index][EE_ADAPT_SCSI_ID]; + psh->io_port = io_port; + psh->n_io_port = 0x80; + psh->irq = Irq; + + pACB = (PACB) psh->hostdata; + +#ifndef VERSION_ELF_1_2_13 + psh->max_id = 8; +#ifdef CONFIG_SCSI_MULTI_LUN + if( eepromBuf[index][EE_MODE2] & LUN_CHECK ) + psh->max_lun = 8; + else +#endif + psh->max_lun = 1; +#endif + + pACB->max_id = 7; + if( pACB->max_id == eepromBuf[index][EE_ADAPT_SCSI_ID] ) + pACB->max_id--; +#ifdef CONFIG_SCSI_MULTI_LUN + if( eepromBuf[index][EE_MODE2] & LUN_CHECK ) + pACB->max_lun = 7; + else +#endif + pACB->max_lun = 0; + + pACB->pScsiHost = psh; + pACB->IOPortBase = (USHORT) io_port; + pACB->pLinkDCB = NULL; + pACB->pDCBRunRobin = NULL; + pACB->pActiveDCB = NULL; + pACB->pFreeSRB = pACB->SRB_array; + pACB->SRBCount = MAX_SRB_CNT; + pACB->AdapterIndex = index; + pACB->status = 0; + pACB->AdaptSCSIID = eepromBuf[index][EE_ADAPT_SCSI_ID]; + pACB->HostID_Bit = (1 << pACB->AdaptSCSIID); + pACB->AdaptSCSILUN = 0; + pACB->DeviceCnt = 0; + pACB->IRQLevel = Irq; + pACB->TagMaxNum = eepromBuf[index][EE_TAG_CMD_NUM] << 2; + pACB->ACBFlag = 0; + pACB->scan_devices = 1; + pACB->Gmode2 = eepromBuf[index][EE_MODE2]; + if( eepromBuf[index][EE_MODE2] & LUN_CHECK ) + pACB->LUNchk = 1; + pACB->pDCB_free = &pACB->DCB_array[0]; + DC390_linkSRB( pACB ); + pACB->pTmpSRB = &pACB->TmpSRB; + DC390_initSRB( pACB->pTmpSRB ); + for(i=0; i<MAX_SCSI_ID; i++) + pACB->DCBmap[i] = 0; +} + + +/*********************************************************************** + * Function : static int DC390_initAdapter + * + * Purpose : initialize the SCSI chip ctrl registers + * + * Inputs : psh - pointer to this host adapter's structure + * + ***********************************************************************/ +int DC390_initAdapter( PSH psh, ULONG io_port, UCHAR Irq, USHORT index ) +{ + USHORT ioport; + UCHAR bval; + PACB pACB, pacb; + USHORT used_irq = 0; + + pacb = pACB_start; + if( pacb != NULL ) + { + for ( ; (pacb != (PACB) -1) ; ) + { + if( pacb->IRQLevel == Irq ) + { + used_irq = 1; + break; + } + else + pacb = pacb->pNextACB; + } + } + + if( !used_irq ) + { +#ifdef VERSION_ELF_1_2_13 + if( request_irq(Irq, DC390_Interrupt, SA_INTERRUPT, "tmscsim")) +#else + if( request_irq(Irq, DC390_Interrupt, SA_INTERRUPT | SA_SHIRQ, "tmscsim", NULL)) +#endif + { + printk("DC390: register IRQ error!\n"); + return( -1 ); + } + } + + request_region(io_port,psh->n_io_port,"tmscsim"); + + ioport = (USHORT) io_port; + + pACB = (PACB) psh->hostdata; + bval = SEL_TIMEOUT; /* 250ms selection timeout */ + outb(bval,ioport+Scsi_TimeOut); + + bval = CLK_FREQ_40MHZ; /* Conversion factor = 0 , 40MHz clock */ + outb(bval,ioport+Clk_Factor); + + bval = NOP_CMD; /* NOP cmd - clear command register */ + outb(bval,ioport+ScsiCmd); + + bval = EN_FEATURE+EN_SCSI2_CMD; /* Enable Feature and SCSI-2 */ + outb(bval,ioport+CtrlReg2); + + bval = FAST_CLK; /* fast clock */ + outb(bval,ioport+CtrlReg3); + + bval = EATER_25NS; + if( eepromBuf[index][EE_MODE2] & ACTIVE_NEGATION ) + bval |= NEGATE_REQACKDATA; + outb(bval,ioport+CtrlReg4); + + bval = DIS_INT_ON_SCSI_RST; /* Disable SCSI bus reset interrupt */ + outb(bval,ioport+CtrlReg1); + + return(0); +} + + +void +DC390_EnableCfg( USHORT mechnum, UCHAR regval ) +{ + ULONG wlval; + + if(mechnum == 2) + { + outb(mech2bus, PCI_CFG2_FORWARD_REG); + outb(mech2CfgSPenR, PCI_CFG2_ENABLE_REG); + } + else + { + regval &= 0xFC; + wlval = mech1addr; + wlval |= (((ULONG)regval) & 0xff); + outl(wlval, PCI_CFG1_ADDRESS_REG); + } +} + + +void +DC390_DisableCfg( USHORT mechnum ) +{ + + if(mechnum == 2) + outb(0, PCI_CFG2_ENABLE_REG); + else + outl(0, PCI_CFG1_ADDRESS_REG); +} + + +UCHAR +DC390_inByte( USHORT mechnum, UCHAR regval ) +{ + UCHAR bval; + ULONG wval; + ULONG flags; + + save_flags(flags); + cli(); + DC390_EnableCfg( mechnum, regval ); + if(mechnum == 2) + { + wval = mech2Agent; + wval <<= 8; + wval |= ((USHORT) regval) & 0xff; + bval = inb(wval); + } + else + { + regval &= 3; + bval = inb(PCI_CFG1_DATA_REG | regval); + } + DC390_DisableCfg(mechnum); + restore_flags(flags); + return(bval); +} + + +USHORT +DC390_inWord( USHORT mechnum, UCHAR regval ) +{ + USHORT wval; + ULONG flags; + + save_flags(flags); + cli(); + DC390_EnableCfg(mechnum,regval); + if(mechnum == 2) + { + wval = mech2Agent; + wval <<= 8; + wval |= regval; + wval = inw(wval); + } + else + { + regval &= 3; + wval = inw(PCI_CFG1_DATA_REG | regval); + } + DC390_DisableCfg(mechnum); + restore_flags(flags); + return(wval); +} + + +ULONG +DC390_inDword(USHORT mechnum, UCHAR regval ) +{ + ULONG wlval; + ULONG flags; + USHORT wval; + + save_flags(flags); + cli(); + DC390_EnableCfg(mechnum,regval); + if(mechnum == 2) + { + wval = mech2Agent; + wval <<= 8; + wval |= regval; + wlval = inl(wval); + } + else + { + wlval = inl(PCI_CFG1_DATA_REG); + } + DC390_DisableCfg(mechnum); + restore_flags(flags); + return(wlval); +} + + +void +DC390_OutB(USHORT mechnum, UCHAR regval, UCHAR bval ) +{ + + USHORT wval; + ULONG flags; + + save_flags(flags); + cli(); + DC390_EnableCfg(mechnum,regval); + if(mechnum == 2) + { + wval = mech2Agent; + wval <<= 8; + wval |= regval; + outb(bval, wval); + } + else + { + regval &= 3; + outb(bval, PCI_CFG1_DATA_REG | regval); + } + DC390_DisableCfg(mechnum); + restore_flags(flags); +} + + +void +DC390_EnDisableCE( UCHAR mode, USHORT mechnum, PUCHAR regval ) +{ + + UCHAR bval; + + bval = 0; + if(mode == ENABLE_CE) + *regval = 0xc0; + else + *regval = 0x80; + DC390_OutB(mechnum,*regval,bval); + if(mode == DISABLE_CE) + DC390_OutB(mechnum,*regval,bval); + udelay(160); +} + + +void +DC390_EEpromOutDI( USHORT mechnum, PUCHAR regval, USHORT Carry ) +{ + UCHAR bval; + + bval = 0; + if(Carry) + { + bval = 0x40; + *regval = 0x80; + DC390_OutB(mechnum,*regval,bval); + } + udelay(160); + bval |= 0x80; + DC390_OutB(mechnum,*regval,bval); + udelay(160); + bval = 0; + DC390_OutB(mechnum,*regval,bval); + udelay(160); +} + + +UCHAR +DC390_EEpromInDO( USHORT mechnum ) +{ + UCHAR bval,regval; + + regval = 0x80; + bval = 0x80; + DC390_OutB(mechnum,regval,bval); + udelay(160); + bval = 0x40; + DC390_OutB(mechnum,regval,bval); + udelay(160); + regval = 0x0; + bval = DC390_inByte(mechnum,regval); + if(bval == 0x22) + return(1); + else + return(0); +} + + +USHORT +EEpromGetData1( USHORT mechnum ) +{ + UCHAR i; + UCHAR carryFlag; + USHORT wval; + + wval = 0; + for(i=0; i<16; i++) + { + wval <<= 1; + carryFlag = DC390_EEpromInDO(mechnum); + wval |= carryFlag; + } + return(wval); +} + + +void +DC390_Prepare( USHORT mechnum, PUCHAR regval, UCHAR EEpromCmd ) +{ + UCHAR i,j; + USHORT carryFlag; + + carryFlag = 1; + j = 0x80; + for(i=0; i<9; i++) + { + DC390_EEpromOutDI(mechnum,regval,carryFlag); + carryFlag = (EEpromCmd & j) ? 1 : 0; + j >>= 1; + } +} + + +void +DC390_ReadEEprom( USHORT mechnum, USHORT index ) +{ + UCHAR regval,cmd; + PUSHORT ptr; + USHORT i; + + ptr = (PUSHORT) &eepromBuf[index][0]; + cmd = EEPROM_READ; + for(i=0; i<0x40; i++) + { + DC390_EnDisableCE(ENABLE_CE, mechnum, ®val); + DC390_Prepare(mechnum, ®val, cmd); + *ptr = EEpromGetData1(mechnum); + ptr++; + cmd++; + DC390_EnDisableCE(DISABLE_CE,mechnum,®val); + } +} + + +USHORT +DC390_CheckEEpromCheckSum( USHORT MechNum, USHORT index ) +{ + USHORT wval, rc, *ptr; + UCHAR i; + + DC390_ReadEEprom( MechNum, index ); + wval = 0; + ptr = (PUSHORT) &eepromBuf[index][0]; + for(i=0; i<128 ;i+=2, ptr++) + wval += *ptr; + if( wval == 0x1234 ) + rc = 0; + else + rc = -1; + return( rc ); +} + + +USHORT +DC390_ToMech( USHORT Mechnum, USHORT BusDevFunNum ) +{ + USHORT devnum; + + devnum = BusDevFunNum; + + if(Mechnum == 2) + { + if(devnum & 0x80) + return(-1); + mech2bus = (UCHAR)((devnum & 0xff00) >> 8); /* Bus num */ + mech2Agent = ((UCHAR)(devnum & 0xff)) >> 3; /* Dev num */ + mech2Agent |= 0xc0; + mech2CfgSPenR = ((UCHAR)(devnum & 0xff)) & 0x07; /* Fun num */ + mech2CfgSPenR = (mech2CfgSPenR << 1) | 0x20; + } + else /* use mech #1 method */ + { + mech1addr = 0x80000000 | ((ULONG)devnum << 8); + } + return(0); +} + +/*********************************************************************** + * Function : static int DC390_init (struct Scsi_Host *host) + * + * Purpose : initialize the internal structures for a given SCSI host + * + * Inputs : host - pointer to this host adapter's structure/ + * + * Preconditions : when this function is called, the chip_type + * field of the pACB structure MUST have been set. + ***********************************************************************/ + +static int +DC390_init (PSHT psht, ULONG io_port, UCHAR Irq, USHORT index, USHORT MechNum) +{ + PSH psh; + PACB pACB; + + if( !DC390_CheckEEpromCheckSum( MechNum, index) ) + { + psh = scsi_register( psht, sizeof(DC390_ACB) ); + if( !psh ) + return( -1 ); + if( !pSH_start ) + { + pSH_start = psh; + pSH_current = psh; + } + else + { + pSH_current->next = psh; + pSH_current = psh; + } + +#ifdef DC390_DEBUG0 + printk("DC390: pSH = %8x,", (UINT) psh); + printk("DC390: Index %02i,", index); +#endif + + DC390_initACB( psh, io_port, Irq, index ); + if( !DC390_initAdapter( psh, io_port, Irq, index ) ) + { + pACB = (PACB) psh->hostdata; + if( !pACB_start ) + { + pACB_start = pACB; + pACB_current = pACB; + pACB->pNextACB = (PACB) -1; + } + else + { + pACB_current->pNextACB = pACB; + pACB_current = pACB; + pACB->pNextACB = (PACB) -1; + } + +#ifdef DC390_DEBUG0 + printk("DC390: pACB = %8x, pDCB_array = %8x, pSRB_array = %8x\n", + (UINT) pACB, (UINT) pACB->DCB_array, (UINT) pACB->SRB_array); + printk("DC390: ACB size= %4x, DCB size= %4x, SRB size= %4x\n", + sizeof(DC390_ACB), sizeof(DC390_DCB), sizeof(DC390_SRB) ); +#endif + + } + else + { + pSH_start = NULL; + scsi_unregister( psh ); + return( -1 ); + } + return( 0 ); + } + else + { + printk("DC390_init: EEPROM reading error!\n"); + return( -1 ); + } +} + + +/*********************************************************************** + * Function : int DC390_detect(Scsi_Host_Template *psht) + * + * Purpose : detects and initializes AMD53C974 SCSI chips + * that were autoprobed, overridden on the LILO command line, + * or specified at compile time. + * + * Inputs : psht - template for this SCSI adapter + * + * Returns : number of host adapters detected + * + ***********************************************************************/ + +int +DC390_detect(Scsi_Host_Template *psht) +{ +#ifdef FOR_PCI_OK + UCHAR pci_bus, pci_device_fn; + int error = 0; + USHORT chipType = 0; + USHORT i; +#endif + + UCHAR irq; + UCHAR istatus; +#ifndef VERSION_ELF_1_2_13 + UINT io_port; +#else + ULONG io_port; +#endif + USHORT adaptCnt = 0; /* Number of boards detected */ + USHORT pci_index = 0; /* Device index to PCI BIOS calls */ + USHORT MechNum, BusDevFunNum; + ULONG wlval; + +#ifndef VERSION_ELF_1_2_13 + psht->proc_dir = &proc_scsi_tmscsim; +#endif + + InitialTime = 1; + pSHT_start = psht; + pACB_start = NULL; + + MechNum = 1; + for( ; (MechNum < 3) && (!adaptCnt); MechNum++) + { + BusDevFunNum = 0; + for (; adaptCnt < MAX_ADAPTER_NUM ;) + { + if( !DC390_ToMech( MechNum, BusDevFunNum) ) + { + wlval = DC390_inDword( MechNum, PCI_VENDOR_ID); + if(wlval == ( (PCI_DEVICE_ID_AMD53C974 << 16)+ + PCI_VENDOR_ID_AMD) ) + { + io_port =DC390_inDword(MechNum,PCI_BASE_ADDRESS_0) & 0xFFFE; + irq = DC390_inByte( MechNum, PCI_INTERRUPT_LINE); +#ifdef DC390_DEBUG0 + printk("DC390: IO_PORT=%4x,IRQ=%x,\n",(UINT) io_port, irq); +#endif + if( !DC390_init(psht, io_port, irq, pci_index, MechNum) ) + { + adaptCnt++; + pci_index++; + istatus = inb( (USHORT)io_port+INT_Status ); /* Reset Pending INT */ +#ifdef DC390_DEBUG0 + printk("DC390: Mech=%2x,\n",(UCHAR) MechNum); +#endif + } + } + } + if( BusDevFunNum != 0xfff8 ) + BusDevFunNum += 8; /* next device # */ + else + break; + } + } + +#ifdef FOR_PCI_OK + if ( pcibios_present() ) + { + for (i = 0; i < MAX_ADAPTER_NUM; ++i) + { + if( !pcibios_find_device( PCI_VENDOR_ID_AMD, + PCI_DEVICE_ID_AMD53C974, + pci_index, &pci_bus, &pci_device_fn) ) + { + chipType = PCI_DEVICE_ID_AMD53C974; + pci_index++; + } + + if( chipType ) + { + + error = pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &io_port); + error |= pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &irq); + if( error ) + { + printk("DC390_detect: reading configuration registers error!\n"); + InitialTime = 0; + return( 0 ); + } + + (USHORT) io_port = (USHORT) io_port & 0xFFFE; +#ifdef DC390_DEBUG0 + printk("DC390: IO_PORT=%4x,IRQ=%x,\n",(UINT) io_port, irq); +#endif + if( !DC390_init(psht, io_port, irq, i) ) + adaptCnt++; + chipType = 0; + } + else + break; + } + } +#endif + + InitialTime = 0; + adapterCnt = adaptCnt; + return( adaptCnt ); +} + + +#ifndef VERSION_ELF_1_2_13 + +/******************************************************************** + * Function: tmscsim_set_info() + * + * Purpose: Set adapter info (!) + * + * Not yet implemented + * + *******************************************************************/ + +int tmscsim_set_info(char *buffer, int length, struct Scsi_Host *shpnt) +{ + return(-ENOSYS); /* Currently this is a no-op */ +} + +/******************************************************************** + * Function: tmscsim_proc_info(char* buffer, char **start, + * off_t offset, int length, int hostno, int inout) + * + * Purpose: return SCSI Adapter/Device Info + * + * Input: buffer: Pointer to a buffer where to write info + * start : + * offset: + * hostno: Host adapter index + * inout : Read (=0) or set(!=0) info + * + * Output: buffer: contains info + * length; length of info in buffer + * + * return value: length + * + ********************************************************************/ + +/* KG: proc_info taken from driver aha152x.c */ + +#undef SPRINTF +#define SPRINTF(args...) pos += sprintf(pos, ## args) + +#define YESNO(YN)\ +if (YN) SPRINTF(" Yes ");\ +else SPRINTF(" No ") + +int tmscsim_proc_info(char *buffer, char **start, + off_t offset, int length, int hostno, int inout) +{ + int dev, spd, spd1; + char *pos = buffer; + PSH shpnt; + PACB acbpnt; + PDCB dcbpnt; + unsigned long flags; +/* Scsi_Cmnd *ptr; */ + + acbpnt = pACB_start; + + while(acbpnt != (PACB)-1) + { + shpnt = acbpnt->pScsiHost; + if (shpnt->host_no == hostno) break; + acbpnt = acbpnt->pNextACB; + } + + if (acbpnt == (PACB)-1) return(-ESRCH); + if(!shpnt) return(-ESRCH); + + if(inout) /* Has data been written to the file ? */ + return(tmscsim_set_info(buffer, length, shpnt)); + + SPRINTF("Tekram DC390(T) PCI SCSI Host Adadpter, "); + SPRINTF("Driver Version 1.10, 1996/12/05\n"); + + save_flags(flags); + cli(); + + SPRINTF("SCSI Host Nr %i, ", shpnt->host_no); + SPRINTF("DC390 Adapter Nr %i\n", acbpnt->AdapterIndex); + SPRINTF("IOPortBase 0x%04x, ", acbpnt->IOPortBase); + SPRINTF("IRQLevel 0x%02x\n", acbpnt->IRQLevel); + + SPRINTF("MaxID %i, MaxLUN %i, ",acbpnt->max_id, acbpnt->max_lun); + SPRINTF("AdapterID %i, AdapterLUN %i\n", acbpnt->AdaptSCSIID, acbpnt->AdaptSCSILUN); + + SPRINTF("TagMaxNum %i, Status %i\n", acbpnt->TagMaxNum, acbpnt->status); + + SPRINTF("Nr of attached devices: %i\n", acbpnt->DeviceCnt); + + SPRINTF("Un ID LUN Prty Sync DsCn SndS TagQ NegoPeriod SyncSpeed SyncOffs\n"); + + dcbpnt = acbpnt->pLinkDCB; + for (dev = 0; dev < acbpnt->DeviceCnt; dev++) + { + SPRINTF("%02i %02i %02i ", dev, dcbpnt->UnitSCSIID, dcbpnt->UnitSCSILUN); + YESNO(dcbpnt->DevMode & PARITY_CHK_); + YESNO(dcbpnt->SyncMode & SYNC_NEGO_DONE); + YESNO(dcbpnt->DevMode & EN_DISCONNECT_); + YESNO(dcbpnt->DevMode & SEND_START_); + YESNO(dcbpnt->SyncMode & EN_TAG_QUEUING); + SPRINTF(" %03i ns ", (dcbpnt->NegoPeriod) << 2); + if (dcbpnt->SyncOffset & 0x0f) + { + spd = 1000/(dcbpnt->NegoPeriod <<2); + spd1 = 1000%(dcbpnt->NegoPeriod <<2); + spd1 = (spd1 * 10)/(dcbpnt->NegoPeriod <<2); + SPRINTF(" %2i.%1i M %02i\n", spd, spd1, (dcbpnt->SyncOffset & 0x0f)); + } + else SPRINTF("\n"); + /* Add more info ...*/ + dcbpnt = dcbpnt->pNextDCB; + } + + restore_flags(flags); + *start = buffer + offset; + + if (pos - buffer < offset) + return 0; + else if (pos - buffer - offset < length) + return pos - buffer - offset; + else + return length; +} +#endif /* VERSION_ELF_1_2_13 */ + + +#ifdef MODULE + +/*********************************************************************** + * Function : static int DC390_shutdown (struct Scsi_Host *host) + * + * Purpose : does a clean (we hope) shutdown of the SCSI chip. + * Use prior to dumping core, unloading the driver, etc. + * + * Returns : 0 on success + ***********************************************************************/ +static int +DC390_shutdown (struct Scsi_Host *host) +{ + UCHAR bval; + USHORT ioport; + unsigned long flags; + PACB pACB = (PACB)(host->hostdata); + + ioport = (unsigned int) pACB->IOPortBase; + + save_flags (flags); + cli(); + +/* pACB->soft_reset(host); */ + +#ifdef DC390_DEBUG0 + printk("DC390: shutdown,"); +#endif + + bval = inb(ioport+CtrlReg1); + bval |= DIS_INT_ON_SCSI_RST; + outb(bval,ioport+CtrlReg1); /* disable interrupt */ + DC390_ResetSCSIBus( pACB ); + + restore_flags (flags); + return( 0 ); +} + + +int DC390_release(struct Scsi_Host *host) +{ + int irq_count; + struct Scsi_Host *tmp; + + DC390_shutdown (host); + + if (host->irq != IRQ_NONE) + { + for (irq_count = 0, tmp = pSH_start; tmp; tmp = tmp->next) + { + if ( tmp->irq == host->irq ) + ++irq_count; + } + if (irq_count == 1) + { +#ifdef DC390_DEBUG0 + printk("DC390: Free IRQ %i.",host->irq); +#endif +#ifndef VERSION_ELF_1_2_13 + free_irq(host->irq,NULL); +#else + free_irq(host->irq); +#endif + } + } + + release_region(host->io_port,host->n_io_port); + + return( 1 ); +} + +Scsi_Host_Template driver_template = DC390_T; +#include "scsi_module.c" +#endif /* def MODULE */ + diff --git a/linux/src/drivers/scsi/tmscsim.h b/linux/src/drivers/scsi/tmscsim.h new file mode 100644 index 00000000..361c488a --- /dev/null +++ b/linux/src/drivers/scsi/tmscsim.h @@ -0,0 +1,680 @@ +/*********************************************************************** +;* File Name : TMSCSIM.H * +;* TEKRAM DC-390(T) PCI SCSI Bus Master Host Adapter * +;* Device Driver * +;***********************************************************************/ + +#ifndef TMSCSIM_H +#define TMSCSIM_H + +#define IRQ_NONE 255 + +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef unsigned long ULONG; +typedef unsigned int UINT; + +typedef UCHAR *PUCHAR; +typedef USHORT *PUSHORT; +typedef ULONG *PULONG; +typedef Scsi_Host_Template *PSHT; +typedef struct Scsi_Host *PSH; +typedef Scsi_Device *PSCSIDEV; +typedef Scsi_Cmnd *PSCSICMD; +typedef void *PVOID; +typedef struct scatterlist *PSGL, SGL; + + +/*;-----------------------------------------------------------------------*/ +typedef struct _SyncMsg +{ +UCHAR ExtendMsg; +UCHAR ExtMsgLen; +UCHAR SyncXferReq; +UCHAR Period; +UCHAR ReqOffset; +} SyncMsg; +/*;-----------------------------------------------------------------------*/ +typedef struct _Capacity +{ +ULONG BlockCount; +ULONG BlockLength; +} Capacity; +/*;-----------------------------------------------------------------------*/ +typedef struct _SGentry +{ +ULONG SGXferDataPtr; +ULONG SGXferDataLen; +} SGentry; + +typedef struct _SGentry1 +{ +ULONG SGXLen; +ULONG SGXPtr; +} SGentry1, *PSGE; + + +#define MAX_ADAPTER_NUM 4 +#define MAX_DEVICES 10 +#define MAX_SG_LIST_BUF 16 +#define MAX_CMD_QUEUE 20 +#define MAX_CMD_PER_LUN 8 +#define MAX_SCSI_ID 8 +#define MAX_SRB_CNT MAX_CMD_QUEUE+4 +#define END_SCAN 2 + +#define SEL_TIMEOUT 153 /* 250 ms selection timeout (@ 40 MHz) */ + +/* +;----------------------------------------------------------------------- +; SCSI Request Block +;----------------------------------------------------------------------- +*/ +struct _SRB +{ +UCHAR CmdBlock[12]; + +struct _SRB *pNextSRB; +struct _DCB *pSRBDCB; +PSCSICMD pcmd; +PSGL pSegmentList; + +ULONG PhysSRB; +ULONG TotalXferredLen; +ULONG SGPhysAddr; /*;a segment starting address */ +ULONG SGToBeXferLen; /*; to be xfer length */ + +SGL Segmentx; /* make a one entry of S/G list table */ + +PUCHAR pMsgPtr; +USHORT SRBState; +USHORT Revxx2; /* ??? */ + +UCHAR MsgInBuf[6]; +UCHAR MsgOutBuf[6]; + +UCHAR AdaptStatus; +UCHAR TargetStatus; +UCHAR MsgCnt; +UCHAR EndMessage; +UCHAR TagNumber; +UCHAR SGcount; +UCHAR SGIndex; +UCHAR IORBFlag; /*;81h-Reset, 2-retry */ + +UCHAR SRBStatus; +UCHAR RetryCnt; +UCHAR SRBFlag; /*; b0-AutoReqSense,b6-Read,b7-write */ + /*; b4-settimeout,b5-Residual valid */ +UCHAR ScsiCmdLen; +UCHAR ScsiPhase; +UCHAR Reserved3[3]; /*;for dword alignment */ +ULONG Segment0[2]; +ULONG Segment1[2]; +}; + +typedef struct _SRB DC390_SRB, *PSRB; + +/* +;----------------------------------------------------------------------- +; Device Control Block +;----------------------------------------------------------------------- +*/ +struct _DCB +{ +struct _DCB *pNextDCB; +struct _ACB *pDCBACB; + +PSCSICMD pQIORBhead; +PSCSICMD pQIORBtail; +PSCSICMD AboIORBhead; +PSCSICMD AboIORBtail; +USHORT QIORBCnt; +USHORT AboIORBcnt; + +PSRB pWaitingSRB; +PSRB pWaitLast; +PSRB pGoingSRB; +PSRB pGoingLast; +PSRB pActiveSRB; +USHORT GoingSRBCnt; +USHORT WaitSRBCnt; /* ??? */ + +ULONG TagMask; + +USHORT MaxCommand; +USHORT AdaptIndex; /*; UnitInfo struc start */ +USHORT UnitIndex; /*; nth Unit on this card */ +UCHAR UnitSCSIID; /*; SCSI Target ID (SCSI Only) */ +UCHAR UnitSCSILUN; /*; SCSI Log. Unit (SCSI Only) */ + +UCHAR IdentifyMsg; +UCHAR CtrlR1; +UCHAR CtrlR3; +UCHAR CtrlR4; + +UCHAR InqDataBuf[8]; +UCHAR CapacityBuf[8]; +UCHAR DevMode; +UCHAR AdpMode; +UCHAR SyncMode; /*; 0:async mode */ +UCHAR NegoPeriod; /*;for nego. */ +UCHAR SyncPeriod; /*;for reg. */ +UCHAR SyncOffset; /*;for reg. and nego.(low nibble) */ +UCHAR UnitCtrlFlag; +UCHAR DCBFlag; +UCHAR DevType; +UCHAR Reserved2[3]; /*;for dword alignment */ +}; + +typedef struct _DCB DC390_DCB, *PDCB; +/* +;----------------------------------------------------------------------- +; Adapter Control Block +;----------------------------------------------------------------------- +*/ +struct _ACB +{ +ULONG PhysACB; +PSH pScsiHost; +struct _ACB *pNextACB; +USHORT IOPortBase; +USHORT Revxx1; /* ??? */ + +PDCB pLinkDCB; +PDCB pDCBRunRobin; +PDCB pActiveDCB; +PDCB pDCB_free; +PSRB pFreeSRB; +PSRB pTmpSRB; +USHORT SRBCount; +USHORT AdapterIndex; /*; nth Adapter this driver */ +USHORT max_id; +USHORT max_lun; + +UCHAR msgin123[4]; +UCHAR status; +UCHAR AdaptSCSIID; /*; Adapter SCSI Target ID */ +UCHAR AdaptSCSILUN; /*; Adapter SCSI LUN */ +UCHAR DeviceCnt; +UCHAR IRQLevel; +UCHAR TagMaxNum; +UCHAR ACBFlag; +UCHAR Gmode2; +UCHAR LUNchk; +UCHAR scan_devices; +UCHAR HostID_Bit; +UCHAR Reserved1[1]; /*;for dword alignment */ +UCHAR DCBmap[MAX_SCSI_ID]; +DC390_DCB DCB_array[MAX_DEVICES]; /* +74h, Len=3E8 */ +DC390_SRB SRB_array[MAX_SRB_CNT]; /* +45Ch, Len= */ +DC390_SRB TmpSRB; +}; + +typedef struct _ACB DC390_ACB, *PACB; + +/*;-----------------------------------------------------------------------*/ + + +#define BIT31 0x80000000 +#define BIT30 0x40000000 +#define BIT29 0x20000000 +#define BIT28 0x10000000 +#define BIT27 0x08000000 +#define BIT26 0x04000000 +#define BIT25 0x02000000 +#define BIT24 0x01000000 +#define BIT23 0x00800000 +#define BIT22 0x00400000 +#define BIT21 0x00200000 +#define BIT20 0x00100000 +#define BIT19 0x00080000 +#define BIT18 0x00040000 +#define BIT17 0x00020000 +#define BIT16 0x00010000 +#define BIT15 0x00008000 +#define BIT14 0x00004000 +#define BIT13 0x00002000 +#define BIT12 0x00001000 +#define BIT11 0x00000800 +#define BIT10 0x00000400 +#define BIT9 0x00000200 +#define BIT8 0x00000100 +#define BIT7 0x00000080 +#define BIT6 0x00000040 +#define BIT5 0x00000020 +#define BIT4 0x00000010 +#define BIT3 0x00000008 +#define BIT2 0x00000004 +#define BIT1 0x00000002 +#define BIT0 0x00000001 + +/*;---UnitCtrlFlag */ +#define UNIT_ALLOCATED BIT0 +#define UNIT_INFO_CHANGED BIT1 +#define FORMATING_MEDIA BIT2 +#define UNIT_RETRY BIT3 + +/*;---UnitFlags */ +#define DASD_SUPPORT BIT0 +#define SCSI_SUPPORT BIT1 +#define ASPI_SUPPORT BIT2 + +/*;----SRBState machine definition */ +#define SRB_FREE 0 +#define SRB_WAIT BIT0 +#define SRB_READY BIT1 +#define SRB_MSGOUT BIT2 /*;arbitration+msg_out 1st byte*/ +#define SRB_MSGIN BIT3 +#define SRB_MSGIN_MULTI BIT4 +#define SRB_COMMAND BIT5 +#define SRB_START_ BIT6 /*;arbitration+msg_out+command_out*/ +#define SRB_DISCONNECT BIT7 +#define SRB_DATA_XFER BIT8 +#define SRB_XFERPAD BIT9 +#define SRB_STATUS BIT10 +#define SRB_COMPLETED BIT11 +#define SRB_ABORT_SENT BIT12 +#define DO_SYNC_NEGO BIT13 +#define SRB_UNEXPECT_RESEL BIT14 + +/*;---ACBFlag */ +#define RESET_DEV BIT0 +#define RESET_DETECT BIT1 +#define RESET_DONE BIT2 + +/*;---DCBFlag */ +#define ABORT_DEV_ BIT0 + +/*;---SRBstatus */ +#define SRB_OK BIT0 +#define ABORTION BIT1 +#define OVER_RUN BIT2 +#define UNDER_RUN BIT3 +#define PARITY_ERROR BIT4 +#define SRB_ERROR BIT5 + +/*;---SRBFlag */ +#define DATAOUT BIT7 +#define DATAIN BIT6 +#define RESIDUAL_VALID BIT5 +#define ENABLE_TIMER BIT4 +#define RESET_DEV0 BIT2 +#define ABORT_DEV BIT1 +#define AUTO_REQSENSE BIT0 + +/*;---Adapter status */ +#define H_STATUS_GOOD 0 +#define H_SEL_TIMEOUT 0x11 +#define H_OVER_UNDER_RUN 0x12 +#define H_UNEXP_BUS_FREE 0x13 +#define H_TARGET_PHASE_F 0x14 +#define H_INVALID_CCB_OP 0x16 +#define H_LINK_CCB_BAD 0x17 +#define H_BAD_TARGET_DIR 0x18 +#define H_DUPLICATE_CCB 0x19 +#define H_BAD_CCB_OR_SG 0x1A +#define H_ABORT 0x0FF + +/*; SCSI Status byte codes*/ +#define SCSI_STAT_GOOD 0x0 /*; Good status */ +#define SCSI_STAT_CHECKCOND 0x02 /*; SCSI Check Condition */ +#define SCSI_STAT_CONDMET 0x04 /*; Condition Met */ +#define SCSI_STAT_BUSY 0x08 /*; Target busy status */ +#define SCSI_STAT_INTER 0x10 /*; Intermediate status */ +#define SCSI_STAT_INTERCONDMET 0x14 /*; Intermediate condition met */ +#define SCSI_STAT_RESCONFLICT 0x18 /*; Reservation conflict */ +#define SCSI_STAT_CMDTERM 0x22 /*; Command Terminated */ +#define SCSI_STAT_QUEUEFULL 0x28 /*; Queue Full */ + +#define SCSI_STAT_UNEXP_BUS_F 0xFD /*; Unexpect Bus Free */ +#define SCSI_STAT_BUS_RST_DETECT 0xFE /*; Scsi Bus Reset detected */ +#define SCSI_STAT_SEL_TIMEOUT 0xFF /*; Selection Time out */ + +/*;---Sync_Mode */ +#define SYNC_DISABLE 0 +#define SYNC_ENABLE BIT0 +#define SYNC_NEGO_DONE BIT1 +#define WIDE_ENABLE BIT2 +#define WIDE_NEGO_DONE BIT3 +#define EN_TAG_QUEUING BIT4 +#define EN_ATN_STOP BIT5 + +#define SYNC_NEGO_OFFSET 15 + +/*;---SCSI bus phase*/ +#define SCSI_DATA_OUT 0 +#define SCSI_DATA_IN 1 +#define SCSI_COMMAND 2 +#define SCSI_STATUS_ 3 +#define SCSI_NOP0 4 +#define SCSI_NOP1 5 +#define SCSI_MSG_OUT 6 +#define SCSI_MSG_IN 7 + +/*;----SCSI MSG BYTE*/ +#define MSG_COMPLETE 0x00 +#define MSG_EXTENDED 0x01 +#define MSG_SAVE_PTR 0x02 +#define MSG_RESTORE_PTR 0x03 +#define MSG_DISCONNECT 0x04 +#define MSG_INITIATOR_ERROR 0x05 +#define MSG_ABORT 0x06 +#define MSG_REJECT_ 0x07 +#define MSG_NOP 0x08 +#define MSG_PARITY_ERROR 0x09 +#define MSG_LINK_CMD_COMPL 0x0A +#define MSG_LINK_CMD_COMPL_FLG 0x0B +#define MSG_BUS_RESET 0x0C +#define MSG_ABORT_TAG 0x0D +#define MSG_SIMPLE_QTAG 0x20 +#define MSG_HEAD_QTAG 0x21 +#define MSG_ORDER_QTAG 0x22 +#define MSG_IDENTIFY 0x80 +#define MSG_HOST_ID 0x0C0 + +/*;----SCSI STATUS BYTE*/ +#define STATUS_GOOD 0x00 +#define CHECK_CONDITION_ 0x02 +#define STATUS_BUSY 0x08 +#define STATUS_INTERMEDIATE 0x10 +#define RESERVE_CONFLICT 0x18 + +/* cmd->result */ +#define STATUS_MASK_ 0xFF +#define MSG_MASK 0xFF00 +#define RETURN_MASK 0xFF0000 + +/* +** Inquiry Data format +*/ + +typedef struct _SCSIInqData { /* INQ */ + + UCHAR DevType; /* Periph Qualifier & Periph Dev Type*/ + UCHAR RMB_TypeMod; /* rem media bit & Dev Type Modifier */ + UCHAR Vers; /* ISO, ECMA, & ANSI versions */ + UCHAR RDF; /* AEN, TRMIOP, & response data format*/ + UCHAR AddLen; /* length of additional data */ + UCHAR Res1; /* reserved */ + UCHAR Res2; /* reserved */ + UCHAR Flags; /* RelADr,Wbus32,Wbus16,Sync,etc. */ + UCHAR VendorID[8]; /* Vendor Identification */ + UCHAR ProductID[16]; /* Product Identification */ + UCHAR ProductRev[4]; /* Product Revision */ + + +} SCSI_INQDATA, *PSCSI_INQDATA; + + +/* Inquiry byte 0 masks */ + + +#define SCSI_DEVTYPE 0x1F /* Peripheral Device Type */ +#define SCSI_PERIPHQUAL 0xE0 /* Peripheral Qualifier */ + + +/* Inquiry byte 1 mask */ + +#define SCSI_REMOVABLE_MEDIA 0x80 /* Removable Media bit (1=removable) */ + + +/* Peripheral Device Type definitions */ + +#define SCSI_DASD 0x00 /* Direct-access Device */ +#define SCSI_SEQACESS 0x01 /* Sequential-access device */ +#define SCSI_PRINTER 0x02 /* Printer device */ +#define SCSI_PROCESSOR 0x03 /* Processor device */ +#define SCSI_WRITEONCE 0x04 /* Write-once device */ +#define SCSI_CDROM 0x05 /* CD-ROM device */ +#define SCSI_SCANNER 0x06 /* Scanner device */ +#define SCSI_OPTICAL 0x07 /* Optical memory device */ +#define SCSI_MEDCHGR 0x08 /* Medium changer device */ +#define SCSI_COMM 0x09 /* Communications device */ +#define SCSI_NODEV 0x1F /* Unknown or no device type */ + +/* +** Inquiry flag definitions (Inq data byte 7) +*/ + +#define SCSI_INQ_RELADR 0x80 /* device supports relative addressing*/ +#define SCSI_INQ_WBUS32 0x40 /* device supports 32 bit data xfers */ +#define SCSI_INQ_WBUS16 0x20 /* device supports 16 bit data xfers */ +#define SCSI_INQ_SYNC 0x10 /* device supports synchronous xfer */ +#define SCSI_INQ_LINKED 0x08 /* device supports linked commands */ +#define SCSI_INQ_CMDQUEUE 0x02 /* device supports command queueing */ +#define SCSI_INQ_SFTRE 0x01 /* device supports soft resets */ + + +/* +;========================================================== +; EEPROM byte offset +;========================================================== +*/ +typedef struct _EEprom +{ +UCHAR EE_MODE1; +UCHAR EE_SPEED; +UCHAR xx1; +UCHAR xx2; +} EEprom, *PEEprom; + +#define EE_ADAPT_SCSI_ID 64 +#define EE_MODE2 65 +#define EE_DELAY 66 +#define EE_TAG_CMD_NUM 67 + +/*; EE_MODE1 bits definition*/ +#define PARITY_CHK_ BIT0 +#define SYNC_NEGO_ BIT1 +#define EN_DISCONNECT_ BIT2 +#define SEND_START_ BIT3 +#define TAG_QUEUING_ BIT4 + +/*; EE_MODE2 bits definition*/ +#define MORE2_DRV BIT0 +#define GREATER_1G BIT1 +#define RST_SCSI_BUS BIT2 +#define ACTIVE_NEGATION BIT3 +#define NO_SEEK BIT4 +#define LUN_CHECK BIT5 + +#define ENABLE_CE 1 +#define DISABLE_CE 0 +#define EEPROM_READ 0x80 + +/* +;========================================================== +; AMD 53C974 Registers bit Definition +;========================================================== +*/ +/* +;==================== +; SCSI Register +;==================== +*/ + +/*; Command Reg.(+0CH) */ +#define DMA_COMMAND BIT7 +#define NOP_CMD 0 +#define CLEAR_FIFO_CMD 1 +#define RST_DEVICE_CMD 2 +#define RST_SCSI_BUS_CMD 3 +#define INFO_XFER_CMD 0x10 +#define INITIATOR_CMD_CMPLTE 0x11 +#define MSG_ACCEPTED_CMD 0x12 +#define XFER_PAD_BYTE 0x18 +#define SET_ATN_CMD 0x1A +#define RESET_ATN_CMD 0x1B +#define SELECT_W_ATN 0x42 +#define SEL_W_ATN_STOP 0x43 +#define EN_SEL_RESEL 0x44 +#define SEL_W_ATN2 0x46 +#define DATA_XFER_CMD INFO_XFER_CMD + + +/*; SCSI Status Reg.(+10H) */ +#define INTERRUPT BIT7 +#define ILLEGAL_OP_ERR BIT6 +#define PARITY_ERR BIT5 +#define COUNT_2_ZERO BIT4 +#define GROUP_CODE_VALID BIT3 +#define SCSI_PHASE_MASK (BIT2+BIT1+BIT0) + +/*; Interrupt Status Reg.(+14H) */ +#define SCSI_RESET BIT7 +#define INVALID_CMD BIT6 +#define DISCONNECTED BIT5 +#define SERVICE_REQUEST BIT4 +#define SUCCESSFUL_OP BIT3 +#define RESELECTED BIT2 +#define SEL_ATTENTION BIT1 +#define SELECTED BIT0 + +/*; Internal State Reg.(+18H) */ +#define SYNC_OFFSET_FLAG BIT3 +#define INTRN_STATE_MASK (BIT2+BIT1+BIT0) + +/*; Clock Factor Reg.(+24H) */ +#define CLK_FREQ_40MHZ 0 +#define CLK_FREQ_35MHZ (BIT2+BIT1+BIT0) +#define CLK_FREQ_30MHZ (BIT2+BIT1) +#define CLK_FREQ_25MHZ (BIT2+BIT0) +#define CLK_FREQ_20MHZ BIT2 +#define CLK_FREQ_15MHZ (BIT1+BIT0) +#define CLK_FREQ_10MHZ BIT1 + +/*; Control Reg. 1(+20H) */ +#define EXTENDED_TIMING BIT7 +#define DIS_INT_ON_SCSI_RST BIT6 +#define PARITY_ERR_REPO BIT4 +#define SCSI_ID_ON_BUS (BIT2+BIT1+BIT0) + +/*; Control Reg. 2(+2CH) */ +#define EN_FEATURE BIT6 +#define EN_SCSI2_CMD BIT3 + +/*; Control Reg. 3(+30H) */ +#define ID_MSG_CHECK BIT7 +#define EN_QTAG_MSG BIT6 +#define EN_GRP2_CMD BIT5 +#define FAST_SCSI BIT4 /* ;10MB/SEC */ +#define FAST_CLK BIT3 /* ;25 - 40 MHZ */ + +/*; Control Reg. 4(+34H) */ +#define EATER_12NS 0 +#define EATER_25NS BIT7 +#define EATER_35NS BIT6 +#define EATER_0NS (BIT7+BIT6) +#define NEGATE_REQACKDATA BIT2 +#define NEGATE_REQACK BIT3 +/* +;==================== +; DMA Register +;==================== +*/ +/*; DMA Command Reg.(+40H) */ +#define READ_DIRECTION BIT7 +#define WRITE_DIRECTION 0 +#define EN_DMA_INT BIT6 +#define MAP_TO_MDL BIT5 +#define DIAGNOSTIC BIT4 +#define DMA_IDLE_CMD 0 +#define DMA_BLAST_CMD BIT0 +#define DMA_ABORT_CMD BIT1 +#define DMA_START_CMD (BIT1+BIT0) + +/*; DMA Status Reg.(+54H) */ +#define PCI_MS_ABORT BIT6 +#define BLAST_COMPLETE BIT5 +#define SCSI_INTERRUPT BIT4 +#define DMA_XFER_DONE BIT3 +#define DMA_XFER_ABORT BIT2 +#define DMA_XFER_ERROR BIT1 +#define POWER_DOWN BIT0 + +/* +; DMA SCSI Bus and Ctrl.(+70H) +;EN_INT_ON_PCI_ABORT +*/ + +/* +;========================================================== +; SCSI Chip register address offset +;========================================================== +*/ +#define CtcReg_Low 0x00 +#define CtcReg_Mid 0x04 +#define ScsiFifo 0x08 +#define ScsiCmd 0x0C +#define Scsi_Status 0x10 +#define INT_Status 0x14 +#define Sync_Period 0x18 +#define Sync_Offset 0x1C +#define CtrlReg1 0x20 +#define Clk_Factor 0x24 +#define CtrlReg2 0x2C +#define CtrlReg3 0x30 +#define CtrlReg4 0x34 +#define CtcReg_High 0x38 +#define DMA_Cmd 0x40 +#define DMA_XferCnt 0x44 +#define DMA_XferAddr 0x48 +#define DMA_Wk_ByteCntr 0x4C +#define DMA_Wk_AddrCntr 0x50 +#define DMA_Status 0x54 +#define DMA_MDL_Addr 0x58 +#define DMA_Wk_MDL_Cntr 0x5C +#define DMA_ScsiBusCtrl 0x70 + +#define StcReg_Low CtcReg_Low +#define StcReg_Mid CtcReg_Mid +#define Scsi_Dest_ID Scsi_Status +#define Scsi_TimeOut INT_Status +#define Intern_State Sync_Period +#define Current_Fifo Sync_Offset +#define StcReg_High CtcReg_High + +#define am_target Scsi_Status +#define am_timeout INT_Status +#define am_seq_step Sync_Period +#define am_fifo_count Sync_Offset + + +#define DC390_read8(address) \ + inb(DC390_ioport + (address))) + +#define DC390_read16(address) \ + inw(DC390_ioport + (address))) + +#define DC390_read32(address) \ + inl(DC390_ioport + (address))) + +#define DC390_write8(address,value) \ + outb((value), DC390_ioport + (address))) + +#define DC390_write16(address,value) \ + outw((value), DC390_ioport + (address))) + +#define DC390_write32(address,value) \ + outl((value), DC390_ioport + (address))) + + +/* Configuration method #1 */ +#define PCI_CFG1_ADDRESS_REG 0xcf8 +#define PCI_CFG1_DATA_REG 0xcfc +#define PCI_CFG1_ENABLE 0x80000000 +#define PCI_CFG1_TUPPLE(bus, device, function, register) \ + (PCI_CFG1_ENABLE | (((bus) << 16) & 0xff0000) | \ + (((device) << 11) & 0xf800) | (((function) << 8) & 0x700)| \ + (((register) << 2) & 0xfc)) + +/* Configuration method #2 */ +#define PCI_CFG2_ENABLE_REG 0xcf8 +#define PCI_CFG2_FORWARD_REG 0xcfa +#define PCI_CFG2_ENABLE 0x0f0 +#define PCI_CFG2_TUPPLE(function) \ + (PCI_CFG2_ENABLE | (((function) << 1) & 0xe)) + + +#endif /* TMSCSIM_H */ diff --git a/linux/src/drivers/scsi/tmscsiw.c b/linux/src/drivers/scsi/tmscsiw.c new file mode 100644 index 00000000..d32b9fc0 --- /dev/null +++ b/linux/src/drivers/scsi/tmscsiw.c @@ -0,0 +1,2096 @@ +/*********************************************************************** + * FILE NAME : TMSCSIW.C * + * BY : C.L. Huang (ching@tekram.com.tw) * + * Description: Device Driver for Tekram DC-390W/U/F (T) PCI SCSI * + * Bus Master Host Adapter * + * (C)Copyright 1995-1996 Tekram Technology Co., Ltd. * + ***********************************************************************/ +/* Minor enhancements and bugfixes by * + * Kurt Garloff <K.Garloff@ping.de> * + ***********************************************************************/ +/* HISTORY: * + * * + * REV# DATE NAME DESCRIPTION * + * 1.00 04/03/96 CLH First release * + * 1.01 04/11/96 CLH Maximum support up to 4 Adapters, * + * support KV 1_3_85 * + * 1.02 04/26/96 CLH fixed bug about EEpromBuf when >1 HA * + * 1.03 06/12/96 CLH fixed bug of Media Change for Removable * + * Device, scan all LUN. Support Pre2.0.10 * + * 1.04 06/18/96 CLH fixed bug of Command timeout .... * + * 1.05 10/04/96 CLH Updating for support KV 2.0.0, 2.0.20 * + * 1.06 10/30/96 KG Fixed bug in DC390W_abort(), module * + * support, added tmscsiw_proc_info() * + * 1.07 11/09/96 KG Fixed bug in tmscsiw_proc_info() * + * 1.08 11/18/96 CLH/KG ditto, null ptr in DC390W_Disconnect() * + * 1.09 11/30/96 KG Fixed bug in CheckEEpromCheckSum(), * + * add register the allocated IO space * + * 1.10 12/05/96 CLH Modify in tmscsiw_proc_info() and add * + * in DC390W_initAdapter() for 53C875 * + * Rev. F with double clock. * + * 1.11 02/04/97 CLH Fixed bug of Formatting a partition that* + * across 1GB boundary, with bad sector * + * checking. * + * 1.12 02/17/97 CLH Fixed bug in CheckEEpromCheckSum() * + ***********************************************************************/ + + +#define DC390W_DEBUG +/* #define CHK_UNDER_RUN */ + +#define SCSI_MALLOC + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/system.h> +#include <linux/delay.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/bios32.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/config.h> + +#include <linux/version.h> +#if LINUX_VERSION_CODE < 66354 /* 1.3.50 */ +#include "../block/blk.h" +#else +#include <linux/blk.h> +#endif + +#include "scsi.h" +#include "hosts.h" +#include "tmscsiw.h" +#include "constants.h" +#include "sd.h" +#include "scripts.h" +#include <linux/stat.h> + +#include "dc390w.h" + +#ifndef VERSION_ELF_1_2_13 +struct proc_dir_entry proc_scsi_tmscsiw ={ + PROC_SCSI_DC390WUF, 7 ,"tmscsiw", + S_IFDIR | S_IRUGO | S_IXUGO, 2 + }; +#endif + +static void DC390W_StartSCSI( PACB pACB, PDCB pDCB, PSRB pSRB ); +static void PrepareSG( PACB pACB, PDCB pDCB, PSRB pSRB ); +static void DoingSRB_Done( PACB pACB ); +static void ExceptionHandler(ULONG wlval, PACB pACB, PDCB pDCB); +static void ParityError( PACB pACB, PDCB pDCB ); +static void PhaseMismatch( PACB pACB ); +static void DC390W_ScsiRstDetect( PACB pACB ); +static void DC390W_ResetSCSIBus( PACB pACB ); +static void DC390W_ResetSCSIBus2( PACB pACB ); +static void AdjustTemp( PACB pACB, PDCB pDCB, PSRB pSRB ); +static void SetXferRate( PACB pACB, PDCB pDCB ); +static void DataIOcommon( PACB pACB, ULONG Swlval, ULONG Cwlval ); +static void SRBdone( PACB pACB, PDCB pDCB, PSRB pSRB ); +static void RequestSense( PACB pACB, PDCB pDCB, PSRB pSRB ); + +static void DC390W_CmdCompleted( PACB pACB ); +static void DC390W_Reselected( PACB pACB ); +static void DC390W_Reselected1( PACB pACB ); +static void DC390W_ReselectedT( PACB pACB ); +static void DC390W_Disconnected( PACB pACB ); +static void DC390W_MessageExtnd( PACB pACB ); +static void DC390W_Signal( PACB pACB ); +static void DC390W_UnknownMsg( PACB pACB ); +static void DC390W_MessageOut( PACB pACB ); +static void DC390W_FatalError( PACB pACB ); +static void DC390W_MessageSync( PACB pACB ); +static void DC390W_MessageWide( PACB pACB ); +static void DC390W_RestorePtr( PACB pACB ); +static void DC390W_MsgReject( PACB pACB ); +static void DC390W_Debug( PACB pACB ); +static void DC390W_download_script (struct Scsi_Host *host); + +int DC390W_initAdapter( PSH psh, ULONG io_port, UCHAR Irq, USHORT index); +void DC390W_initDCB( PACB pACB, PDCB pDCB, PSCSICMD cmd ); +void MyDelay( void ); +void EnDisableCE( UCHAR Flag, USHORT scsiIOPort ); +void EEpromOutDI( USHORT Carry, USHORT scsiIOPort ); +void EEpromPrepare( UCHAR EEpromCmd, USHORT scsiIOPort ); +void ReadEEprom( PUCHAR EEpromBuf, USHORT scsiIOPort ); +UCHAR EEpromInDo(USHORT scsiIOPort); +USHORT EEpromGetData(USHORT scsiIOPort); +USHORT CheckEEpromCheckSum( PUCHAR EEpromBuf, USHORT scsiIOPort); + +#ifdef MODULE +static int DC390W_release(struct Scsi_Host *host); +static int DC390W_shutdown (struct Scsi_Host *host); +#endif + + +static ULONG jmp_table16; +static ULONG jmp_din16; +static ULONG jmp_dout16; +static PSHT pSHT_start = NULL; +static PSH pSH_start = NULL; +static PSH pSH_current = NULL; +static PACB pACB_start= NULL; +static PACB pACB_current = NULL; +static PDCB pPrevDCB = NULL; +static USHORT adapterCnt = 0; +static USHORT InitialTime = 0; +static USHORT CurrDCBscntl3 = 0; +static UCHAR pad_buffer[128]; + +static PVOID IntVector[]={ + DC390W_CmdCompleted, + DC390W_Reselected, + DC390W_Reselected1, + DC390W_ReselectedT, + DC390W_Disconnected, + DC390W_MessageExtnd, + DC390W_Signal, + DC390W_UnknownMsg, + DC390W_MessageOut, + DC390W_FatalError, + DC390W_MessageSync, + DC390W_MessageWide, + DC390W_RestorePtr, + DC390W_MsgReject, + DC390W_Debug, + DC390W_FatalError + }; + +UCHAR eepromBuf[MAX_ADAPTER_NUM][128]; + +UCHAR clock_period[12] = {25, 31, 37, 43, 50, 62, 75, 125, 12, 15, 18, 21}; +UCHAR baddevname[2][28] ={ + "SEAGATE ST3390N 9546", + "SEAGATE ST3390N ??? 0399"}; + +#define BADDEVCNT 2 + +/*********************************************************************** + * + * + * + **********************************************************************/ +static void +QLinkcmd( PSCSICMD cmd, PDCB pDCB ) +{ + ULONG flags; + PSCSICMD pcmd; + + save_flags(flags); + cli(); + + if( !pDCB->QIORBCnt ) + { + pDCB->pQIORBhead = cmd; + pDCB->pQIORBtail = cmd; + pDCB->QIORBCnt++; + cmd->next = NULL; + } + else + { + pcmd = pDCB->pQIORBtail; + pcmd->next = cmd; + pDCB->pQIORBtail = cmd; + pDCB->QIORBCnt++; + cmd->next = NULL; + } + + restore_flags(flags); +} + + +static PSCSICMD +Getcmd( PDCB pDCB ) +{ + ULONG flags; + PSCSICMD pcmd; + + save_flags(flags); + cli(); + + pcmd = pDCB->pQIORBhead; + pDCB->pQIORBhead = pcmd->next; + pcmd->next = NULL; + pDCB->QIORBCnt--; + + restore_flags(flags); + return( pcmd ); +} + + +static PSRB +GetSRB( PACB pACB ) +{ + ULONG flags; + PSRB pSRB; + + save_flags(flags); + cli(); + + pSRB = pACB->pFreeSRB; + if( pSRB ) + { + pACB->pFreeSRB = pSRB->pNextSRB; + pSRB->pNextSRB = NULL; + } + restore_flags(flags); + return( pSRB ); +} + + +static void +RewaitSRB( PDCB pDCB, PSRB pSRB ) +{ + PSRB psrb1; + ULONG flags; + UCHAR bval; + + save_flags(flags); + cli(); + pDCB->GoingSRBCnt--; + psrb1 = pDCB->pGoingSRB; + if( pSRB == psrb1 ) + { + pDCB->pGoingSRB = psrb1->pNextSRB; + } + else + { + while( pSRB != psrb1->pNextSRB ) + psrb1 = psrb1->pNextSRB; + psrb1->pNextSRB = pSRB->pNextSRB; + if( pSRB == pDCB->pGoingLast ) + pDCB->pGoingLast = psrb1; + } + if( (psrb1 = pDCB->pWaitingSRB) ) + { + pSRB->pNextSRB = psrb1; + pDCB->pWaitingSRB = pSRB; + } + else + { + pSRB->pNextSRB = NULL; + pDCB->pWaitingSRB = pSRB; + pDCB->pWaitLast = pSRB; + } + + bval = pSRB->TagNumber; + pDCB->TagMask &= (~(1 << bval)); /* Free TAG number */ + restore_flags(flags); +} + + +static void +DoWaitingSRB( PACB pACB ) +{ + ULONG flags; + PDCB ptr, ptr1; + PSRB pSRB; + + save_flags(flags); + cli(); + + if( !(pACB->pActiveDCB) && !(pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV) ) ) + { + ptr = pACB->pDCBRunRobin; + if( !ptr ) + { + ptr = pACB->pLinkDCB; + pACB->pDCBRunRobin = ptr; + } + ptr1 = ptr; + for( ;ptr1; ) + { + pACB->pDCBRunRobin = ptr1->pNextDCB; + if( !( ptr1->MaxCommand > ptr1->GoingSRBCnt ) || + !( pSRB = ptr1->pWaitingSRB ) ) + { + if(pACB->pDCBRunRobin == ptr) + break; + ptr1 = ptr1->pNextDCB; + } + else + { + DC390W_StartSCSI(pACB, ptr1, pSRB); + ptr1->GoingSRBCnt++; + if( ptr1->pWaitLast == pSRB ) + { + ptr1->pWaitingSRB = NULL; + ptr1->pWaitLast = NULL; + } + else + { + ptr1->pWaitingSRB = pSRB->pNextSRB; + } + pSRB->pNextSRB = NULL; + + if( ptr1->pGoingSRB ) + ptr1->pGoingLast->pNextSRB = pSRB; + else + ptr1->pGoingSRB = pSRB; + ptr1->pGoingLast = pSRB; + + break; + } + } + } + restore_flags(flags); + return; +} + + +static void +SRBwaiting( PDCB pDCB, PSRB pSRB) +{ + if( pDCB->pWaitingSRB ) + { + pDCB->pWaitLast->pNextSRB = pSRB; + pDCB->pWaitLast = pSRB; + pSRB->pNextSRB = NULL; + } + else + { + pDCB->pWaitingSRB = pSRB; + pDCB->pWaitLast = pSRB; + } +} + + +static void +SendSRB( PSCSICMD pcmd, PACB pACB, PSRB pSRB ) +{ + ULONG flags; + PDCB pDCB; + + save_flags(flags); + cli(); + + pDCB = pSRB->pSRBDCB; + PrepareSG( pACB, pDCB, pSRB ); + if( !(pDCB->MaxCommand > pDCB->GoingSRBCnt) || (pACB->pActiveDCB) || + (pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV)) ) + { + SRBwaiting(pDCB, pSRB); + goto SND_EXIT; + } + + if( pDCB->pWaitingSRB ) + { + SRBwaiting(pDCB, pSRB); +/* pSRB = GetWaitingSRB(pDCB); */ + pSRB = pDCB->pWaitingSRB; + pDCB->pWaitingSRB = pSRB->pNextSRB; + pSRB->pNextSRB = NULL; + } + + DC390W_StartSCSI(pACB, pDCB, pSRB); + pDCB->GoingSRBCnt++; + if( pDCB->pGoingSRB ) + { + pDCB->pGoingLast->pNextSRB = pSRB; + pDCB->pGoingLast = pSRB; + } + else + { + pDCB->pGoingSRB = pSRB; + pDCB->pGoingLast = pSRB; + } + +SND_EXIT: + restore_flags(flags); + return; +} + + +/*********************************************************************** + * Function : static int DC390W_queue_command (Scsi_Cmnd *cmd, + * void (*done)(Scsi_Cmnd *)) + * + * Purpose : enqueues a SCSI command + * + * Inputs : cmd - SCSI command, done - function called on completion, with + * a pointer to the command descriptor. + * + * Returns : 0 + * + ***********************************************************************/ + +int +DC390W_queue_command (Scsi_Cmnd *cmd, void (* done)(Scsi_Cmnd *)) +{ + USHORT ioport, i; + Scsi_Cmnd *pcmd; + struct Scsi_Host *psh; + PACB pACB; + PDCB pDCB; + PSRB pSRB; + ULONG flags; + PUCHAR ptr,ptr1; + + psh = cmd->host; + pACB = (PACB ) psh->hostdata; + ioport = pACB->IOPortBase; + +#ifdef DC390W_DEBUG0 + printk("Cmd=%x,",cmd->cmnd[0]); +#endif + + if( (pACB->scan_devices == END_SCAN) && (cmd->cmnd[0] != INQUIRY) ) + { + pACB->scan_devices = 0; + pPrevDCB->pNextDCB = pACB->pLinkDCB; + } + else if( (pACB->scan_devices) && (cmd->cmnd[0] == 8) ) + { + pACB->scan_devices = 0; + pPrevDCB->pNextDCB = pACB->pLinkDCB; + } + + if ( ( cmd->target > pACB->max_id ) || (cmd->lun > pACB->max_lun) ) + { +/* printk("DC390W: Ignore target %d lun %d\n", + cmd->target, cmd->lun); */ + cmd->result = (DID_BAD_TARGET << 16); + done(cmd); + return( 0 ); + } + + if( (pACB->scan_devices) && !(pACB->DCBmap[cmd->target] & (1 << cmd->lun)) ) + { + if( pACB->DeviceCnt < MAX_DEVICES ) + { + pACB->DCBmap[cmd->target] |= (1 << cmd->lun); + pDCB = pACB->pDCB_free; +#ifdef DC390W_DEBUG0 + printk("pDCB=%8x,ID=%2x,", (UINT) pDCB, cmd->target); +#endif + DC390W_initDCB( pACB, pDCB, cmd ); + } + else /* ???? */ + { +/* printk("DC390W: Ignore target %d lun %d\n", + cmd->target, cmd->lun); */ + cmd->result = (DID_BAD_TARGET << 16); + done(cmd); + return(0); + } + } + else if( !(pACB->scan_devices) && !(pACB->DCBmap[cmd->target] & (1 << cmd->lun)) ) + { +/* printk("DC390W: Ignore target %d lun %d\n", + cmd->target, cmd->lun); */ + cmd->result = (DID_BAD_TARGET << 16); + done(cmd); + return(0); + } + else + { + pDCB = pACB->pLinkDCB; + while( (pDCB->UnitSCSIID != cmd->target) || + (pDCB->UnitSCSILUN != cmd->lun) ) + { + pDCB = pDCB->pNextDCB; + } +#ifdef DC390W_DEBUG0 + printk("pDCB=%8x,ID=%2x,Scan=%1x", (UINT) pDCB, cmd->target, + pACB->scan_devices); +#endif + } + + cmd->scsi_done = done; + cmd->result = 0; + + save_flags(flags); + cli(); + + if( pDCB->QIORBCnt ) + { + QLinkcmd( cmd, pDCB ); + pcmd = Getcmd( pDCB ); + } + else + pcmd = cmd; + + pSRB = GetSRB( pACB ); + + if( !pSRB ) + { + QLinkcmd( pcmd, pDCB ); + restore_flags(flags); + return(0); + } + +/* BuildSRB(pSRB); */ + + pSRB->pSRBDCB = pDCB; + pSRB->pcmd = pcmd; + ptr = (PUCHAR) pSRB->CmdBlock; + ptr1 = (PUCHAR) pcmd->cmnd; + (UCHAR) pSRB->__command[0] = pcmd->cmd_len; + for(i=0; i< pcmd->cmd_len; i++) + { + *ptr = *ptr1; + ptr++; + ptr1++; + } + if( pcmd->use_sg ) + { + pSRB->SGcount = (UCHAR) pcmd->use_sg; + pSRB->pSegmentList = (PSGL) pcmd->request_buffer; + } + else if( pcmd->request_buffer ) + { + pSRB->SGcount = 1; + pSRB->pSegmentList = (PSGL) &pSRB->Segmentx; + pSRB->Segmentx.address = (PUCHAR) pcmd->request_buffer; + pSRB->Segmentx.length = pcmd->request_bufflen; + } + else + pSRB->SGcount = 0; + + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + pSRB->MsgCnt = 0; + if( pDCB->DevType != TYPE_TAPE ) + pSRB->RetryCnt = 1; + else + pSRB->RetryCnt = 0; + pSRB->SRBStatus = 0; + pSRB->SRBFlag = 0; + pSRB->ScratchABuf = 0; + pSRB->SRBState = 0; + pSRB->RemainSegPtr = 0; + pSRB->XferredLen = 0; + SendSRB( pcmd, pACB, pSRB ); + + restore_flags(flags); + return(0); +} + + +static void +DoNextCmd( PACB pACB, PDCB pDCB ) +{ + Scsi_Cmnd *pcmd; + PSRB pSRB; + ULONG flags; + PUCHAR ptr,ptr1; + USHORT i; + + + if( pACB->ACBFlag & (RESET_DETECT+RESET_DONE+RESET_DEV) ) + return; + save_flags(flags); + cli(); + + pcmd = Getcmd( pDCB ); + pSRB = GetSRB( pACB ); + if( !pSRB ) + { + QLinkcmd( pcmd, pDCB ); + restore_flags(flags); + return; + } + + pSRB->pSRBDCB = pDCB; + pSRB->pcmd = pcmd; + ptr = (PUCHAR) pSRB->CmdBlock; + ptr1 = (PUCHAR) pcmd->cmnd; + (UCHAR) pSRB->__command[0] = pcmd->cmd_len; + for(i=0; i< pcmd->cmd_len; i++) + { + *ptr = *ptr1; + ptr++; + ptr1++; + } + if( pcmd->use_sg ) + { + pSRB->SGcount = (UCHAR) pcmd->use_sg; + pSRB->pSegmentList = (PSGL) pcmd->request_buffer; + } + else if( pcmd->request_buffer ) + { + pSRB->SGcount = 1; + pSRB->pSegmentList = (PSGL) &pSRB->Segmentx; + pSRB->Segmentx.address = (PUCHAR) pcmd->request_buffer; + pSRB->Segmentx.length = pcmd->request_bufflen; + } + else + pSRB->SGcount = 0; + + pSRB->AdaptStatus = 0; + pSRB->TargetStatus = 0; + pSRB->MsgCnt = 0; + if( pDCB->DevType != TYPE_TAPE ) + pSRB->RetryCnt = 1; + else + pSRB->RetryCnt = 0; + pSRB->SRBStatus = 0; + pSRB->SRBFlag = 0; + pSRB->ScratchABuf = 0; + pSRB->SRBState = 0; + SendSRB( pcmd, pACB, pSRB ); + + restore_flags(flags); + return; +} + + +/*********************************************************************** + * Function: + * DC390W_bios_param + * + * Description: + * Return the disk geometry for the given SCSI device. + ***********************************************************************/ +#ifdef VERSION_ELF_1_2_13 +int DC390W_bios_param(Disk *disk, int devno, int geom[]) +#else +int DC390W_bios_param(Disk *disk, kdev_t devno, int geom[]) +#endif +{ + int heads, sectors, cylinders; + PACB pACB; + + pACB = (PACB) disk->device->host->hostdata; + heads = 64; + sectors = 32; + cylinders = disk->capacity / (heads * sectors); + + if ( (pACB->Gmode2 & GREATER_1G) && (cylinders > 1024) ) + { + heads = 255; + sectors = 63; + cylinders = disk->capacity / (heads * sectors); + } + + geom[0] = heads; + geom[1] = sectors; + geom[2] = cylinders; + + return (0); +} + + +/*********************************************************************** + * Function : int DC390W_abort (Scsi_Cmnd *cmd) + * + * Purpose : Abort an errant SCSI command + * + * Inputs : cmd - command to abort + * + * Returns : 0 on success, -1 on failure. + ***********************************************************************/ + +int +DC390W_abort (Scsi_Cmnd *cmd) +{ + ULONG flags; + PACB pACB; + PDCB pDCB, pdcb; + PSRB pSRB, psrb; + USHORT count, i; + PSCSICMD pcmd, pcmd1; + int status; + + +#ifdef DC390W_DEBUG0 + printk("DC390W : Abort Cmd."); +#endif + + save_flags(flags); + cli(); + + pACB = (PACB) cmd->host->hostdata; + pDCB = pACB->pLinkDCB; + pdcb = pDCB; + while( (pDCB->UnitSCSIID != cmd->target) || + (pDCB->UnitSCSILUN != cmd->lun) ) + { + pDCB = pDCB->pNextDCB; + if( pDCB == pdcb ) + goto NOT_RUN; + } + + if( pDCB->QIORBCnt ) + { + pcmd = pDCB->pQIORBhead; + if( pcmd == cmd ) + { + pDCB->pQIORBhead = pcmd->next; + pcmd->next = NULL; + pDCB->QIORBCnt--; + status = SCSI_ABORT_SUCCESS; + goto ABO_X; + } + for( count = pDCB->QIORBCnt, i=0; i<count-1; i++) + { + if( pcmd->next == cmd ) + { + pcmd1 = pcmd->next; + pcmd->next = pcmd1->next; + pcmd1->next = NULL; + pDCB->QIORBCnt--; + status = SCSI_ABORT_SUCCESS; + goto ABO_X; + } + else + { + pcmd = pcmd->next; + } + } + } + + pSRB = pDCB->pWaitingSRB; + if( !pSRB ) + goto ON_GOING; + if( pSRB->pcmd == cmd ) + { + pDCB->pWaitingSRB = pSRB->pNextSRB; + goto IN_WAIT; + } + else + { + psrb = pSRB; + if( !(psrb->pNextSRB) ) + goto ON_GOING; + while( psrb->pNextSRB->pcmd != cmd ) + { + psrb = psrb->pNextSRB; + if( !(psrb->pNextSRB) ) + goto ON_GOING; + } + pSRB = psrb->pNextSRB; + psrb->pNextSRB = pSRB->pNextSRB; + if( pSRB == pDCB->pWaitLast ) + pDCB->pWaitLast = psrb; +IN_WAIT: + pSRB->pNextSRB = pACB->pFreeSRB; + pACB->pFreeSRB = pSRB; + cmd->next = NULL; + status = SCSI_ABORT_SUCCESS; + goto ABO_X; + } + +ON_GOING: + pSRB = pDCB->pGoingSRB; + for( count = pDCB->GoingSRBCnt, i=0; i<count; i++) + { + if( pSRB->pcmd != cmd ) + pSRB = pSRB->pNextSRB; + else + { + if( (pACB->pActiveDCB == pDCB) && (pDCB->pActiveSRB == pSRB) ) + { + status = SCSI_ABORT_BUSY; + goto ABO_X; + } + else + { + status = SCSI_ABORT_SNOOZE; + goto ABO_X; + } + } + } + +NOT_RUN: + status = SCSI_ABORT_NOT_RUNNING; + +ABO_X: + cmd->result = DID_ABORT << 16; + cmd->scsi_done(cmd); + restore_flags(flags); + return( status ); +} + + +static void +ResetDevParam( PACB pACB ) +{ + PDCB pDCB, pdcb; + + pDCB = pACB->pLinkDCB; + if( pDCB == NULL ) + return; + pdcb = pDCB; + do + { + if( pACB->AdaptType == DC390W ) + pdcb->DCBscntl3 = SYNC_CLK_F2+ASYNC_CLK_F2; + else + pdcb->DCBscntl3 = SYNC_CLK_F4+ASYNC_CLK_F4; + pdcb->DCBsxfer = 0; + pdcb = pdcb->pNextDCB; + } + while( pdcb != pDCB ); +} + + +static void +RecoverSRB( PACB pACB ) +{ + PDCB pDCB, pdcb; + PSRB psrb, psrb2; + USHORT cnt, i; + + pDCB = pACB->pLinkDCB; + if( pDCB == NULL ) + return; + pdcb = pDCB; + do + { + cnt = pdcb->GoingSRBCnt; + psrb = pdcb->pGoingSRB; + for (i=0; i<cnt; i++) + { + PrepareSG( pACB, pdcb, psrb ); + psrb2 = psrb; + psrb = psrb->pNextSRB; +/* RewaitSRB( pDCB, psrb ); */ + if( pdcb->pWaitingSRB ) + { + psrb2->pNextSRB = pdcb->pWaitingSRB; + pdcb->pWaitingSRB = psrb2; + } + else + { + pdcb->pWaitingSRB = psrb2; + pdcb->pWaitLast = psrb2; + psrb2->pNextSRB = NULL; + } + } + pdcb->GoingSRBCnt = 0; + pdcb->pGoingSRB = NULL; + pdcb->TagMask = 0; + pdcb = pdcb->pNextDCB; + } + while( pdcb != pDCB ); +} + + +/*********************************************************************** + * Function : int DC390W_reset (Scsi_Cmnd *cmd, ...) + * + * Purpose : perform a hard reset on the SCSI bus( and NCR chip). + * + * Inputs : cmd - command which caused the SCSI RESET + * + * Returns : 0 on success. + ***********************************************************************/ + +#ifdef VERSION_2_0_0 +int DC390W_reset(Scsi_Cmnd *cmd, unsigned int resetFlags) +#else +int DC390W_reset (Scsi_Cmnd *cmd) +#endif +{ + USHORT ioport; + unsigned long flags; + PACB pACB; + ULONG wlval; + UCHAR bval; + USHORT wval; + USHORT i; + + +#ifdef DC390W_DEBUG0 + printk("DC390W : Reset Cmd0,"); +#endif + + pACB = (PACB ) cmd->host->hostdata; + ioport = pACB->IOPortBase; + save_flags(flags); + cli(); + bval = inb(ioport+DCNTL); + bval |= IRQ_DISABLE; + outb(bval,ioport+DCNTL); /* disable interrupt */ + DC390W_ResetSCSIBus( pACB ); + for( i=0; i<500; i++ ) + udelay(1000); + for(;;) + { + bval = inb(ioport+ISTAT); + if( bval & SCSI_INT_PENDING ) + { + wval = inw( ioport+SIST0 ); + if( wval & (SCSI_RESET+SCSI_GERROR) ) + break; + } + if(bval & DMA_INT_PENDING) + { + bval = inb(ioport+DSTAT); + if(bval & ABORT_) + { + wval = inw( ioport+SIST0 ); + break; + } + } + } + bval = inb(ioport+DCNTL); + bval &= ~IRQ_DISABLE; + outb(bval,ioport+DCNTL); /* re-enable interrupt */ + + ioport = pACB->IOPortBase; + bval = inb(ioport+STEST3); + bval |= CLR_SCSI_FIFO; + outb(bval,ioport+STEST3); + bval = CLR_DMA_FIFO; + outb(bval,ioport+CTEST3); + ResetDevParam( pACB ); + DoingSRB_Done( pACB ); + pACB->pActiveDCB = NULL; + wlval = pACB->jmp_reselect; + outl(wlval,(ioport+DSP)); + + pACB->ACBFlag = 0; + DoWaitingSRB( pACB ); + restore_flags(flags); + return( SCSI_RESET_SUCCESS ); +} + + +#include "scsiio.c" + + +/*********************************************************************** + * Function : static void DC390W_initDCB + * + * Purpose : initialize the internal structures for a given DCB + * + * Inputs : cmd - pointer to this scsi cmd request block structure + * + ***********************************************************************/ +void DC390W_initDCB( PACB pACB, PDCB pDCB, PSCSICMD cmd ) +{ + PEEprom prom; + UCHAR bval; + USHORT index; + + if( pACB->DeviceCnt == 0 ) + { + pACB->pLinkDCB = pDCB; + pACB->pDCBRunRobin = pDCB; + pDCB->pNextDCB = pDCB; + pPrevDCB = pDCB; + } + else + pPrevDCB->pNextDCB = pDCB; + + pDCB->pDCBACB = pACB; + pDCB->QIORBCnt = 0; + pDCB->DCBselect = 0; + pDCB->DCBsxfer = 0; + pDCB->DCBsdid = cmd->target; + pDCB->UnitSCSIID = cmd->target; + pDCB->UnitSCSILUN = cmd->lun; + pDCB->pWaitingSRB = NULL; + pDCB->GoingSRBCnt = 0; + pDCB->TagMask = 0; + + pDCB->MaxCommand = 1; + pDCB->AdaptIndex = pACB->AdapterIndex; + index = pACB->AdapterIndex; + + prom = (PEEprom) &eepromBuf[index][cmd->target << 2]; + pDCB->DevMode = prom->EE_MODE1; + pDCB->NegoPeriod = clock_period[prom->EE_SPEED]; + + if( pACB->AdaptType == DC390W ) + pDCB->DCBscntl3 = SYNC_CLK_F2+ASYNC_CLK_F2; + else + pDCB->DCBscntl3 = SYNC_CLK_F4+ASYNC_CLK_F4; + + if( pDCB->DevMode & PARITY_CHK_ ) + pDCB->DCBscntl0 = EN_PARITY_CHK+SATN_IF_PARITY_ERR+FULL_ARBITRATION; + else + pDCB->DCBscntl0 = FULL_ARBITRATION; + + pDCB->AdpMode = eepromBuf[index][EE_MODE2]; + + if( pDCB->DevMode & EN_DISCONNECT_ ) + bval = 0xC0; + else + bval = 0x80; + bval |= cmd->lun; + pDCB->IdentifyMsg = bval; + + if( pDCB->DevMode & SYNC_NEGO_ ) + { + pDCB->SyncMode = SYNC_ENABLE; + pDCB->SyncOffset = SYNC_NEGO_OFFSET; + } + + if( pDCB->DevMode & WIDE_NEGO_ ) + { + if( cmd->lun ) + { + if( !(CurrDCBscntl3 & EN_WIDE_SCSI) ) + pDCB->DevMode &= ~WIDE_NEGO_; + } + else + CurrDCBscntl3 = 0; + } + pDCB->DCBFlag = 0; +} + + +/*********************************************************************** + * Function : static void DC390W_initSRB + * + * Purpose : initialize the internal structures for a given SRB + * + * Inputs : psrb - pointer to this scsi request block structure + * + ***********************************************************************/ +void DC390W_initSRB( PSRB psrb ) +{ +#ifndef VERSION_ELF_1_2_13 + psrb->PhysSRB = virt_to_phys( psrb ); + psrb->__command[1] = virt_to_phys( psrb->CmdBlock ); + psrb->__msgout0[0] = 1; + psrb->__msgout0[1] = virt_to_phys( psrb->MsgOutBuf ); + psrb->SegmentPad[0] = 16; + psrb->SegmentPad[1] = virt_to_phys( pad_buffer ); +#else + psrb->PhysSRB = (ULONG) psrb; + psrb->__command[1] = (ULONG) psrb->CmdBlock; + psrb->__msgout0[0] = 1; + psrb->__msgout0[1] = (ULONG) psrb->MsgOutBuf; + psrb->SegmentPad[0] = 16; + psrb->SegmentPad[1] = (ULONG) pad_buffer; +#endif +} + + +void DC390W_linkSRB( PACB pACB ) +{ + USHORT count, i; + PSRB psrb; + + count = pACB->SRBCount; + + for( i=0; i< count; i++) + { + if( i != count - 1) + pACB->SRB_array[i].pNextSRB = &pACB->SRB_array[i+1]; + else + pACB->SRB_array[i].pNextSRB = NULL; + psrb = (PSRB) &pACB->SRB_array[i]; + DC390W_initSRB( psrb ); + } +} + + +/*********************************************************************** + * Function : static void DC390W_initACB + * + * Purpose : initialize the internal structures for a given SCSI host + * + * Inputs : psh - pointer to this host adapter's structure + * + ***********************************************************************/ +void DC390W_initACB( PSH psh, USHORT chipType, ULONG io_port, UCHAR Irq, USHORT index ) +{ + PACB pACB; + USHORT i; + UCHAR adaptType, bval; + + + psh->can_queue = MAX_CMD_QUEUE; + psh->cmd_per_lun = MAX_CMD_PER_LUN; + psh->this_id = (int) eepromBuf[index][EE_ADAPT_SCSI_ID]; + psh->io_port = io_port; + psh->n_io_port = 0x80; + psh->irq = Irq; + + if( chipType == PCI_DEVICE_ID_NCR53C825A ) + adaptType = DC390W; + else + { + outb( 2, io_port+GPREG ); + bval = inb( io_port+GPREG ); + if( bval & 8 ) + adaptType = DC390U; + else + adaptType = DC390F; + } + + pACB = (PACB) psh->hostdata; + +#ifndef VERSION_ELF_1_2_13 + if( adaptType == DC390U ) + { + psh->max_id = 8; + pACB->max_id = 7; + } + else + { + psh->max_id = 16; + pACB->max_id = 15; + } + +#ifdef CONFIG_SCSI_MULTI_LUN + if( eepromBuf[index][EE_MODE2] & LUN_CHECK ) + psh->max_lun = 8; + else +#endif + psh->max_lun = 1; + +#else + pACB->max_id = 7; +#endif + if( pACB->max_id == eepromBuf[index][EE_ADAPT_SCSI_ID] ) + pACB->max_id--; + +#ifdef CONFIG_SCSI_MULTI_LUN + if( eepromBuf[index][EE_MODE2] & LUN_CHECK ) + pACB->max_lun = 7; + else +#endif + pACB->max_lun = 0; + + pACB->pScsiHost = psh; + pACB->IOPortBase = (USHORT) io_port; + pACB->pLinkDCB = NULL; + pACB->pDCBRunRobin = NULL; + pACB->pActiveDCB = NULL; + pACB->pFreeSRB = pACB->SRB_array; + pACB->SRBCount = MAX_SRB_CNT; + pACB->AdapterIndex = index; + pACB->status = 0; + pACB->AdaptSCSIID = eepromBuf[index][EE_ADAPT_SCSI_ID]; + pACB->AdaptSCSILUN = 0; + pACB->DeviceCnt = 0; + pACB->IRQLevel = Irq; + pACB->AdaptType = adaptType; + pACB->TagMaxNum = eepromBuf[index][EE_TAG_CMD_NUM] << 2; + pACB->ACBFlag = 0; + pACB->scan_devices = 1; + pACB->Gmode2 = eepromBuf[index][EE_MODE2]; + if( eepromBuf[index][EE_MODE2] & LUN_CHECK ) + pACB->LUNchk = 1; + pACB->pDCB_free = &pACB->DCB_array[0]; + DC390W_linkSRB( pACB ); + for(i=0; i<MAX_SCSI_ID; i++) + pACB->DCBmap[i] = 0; +} + + +/*********************************************************************** + * Function : static int DC390W_initAdapter + * + * Purpose : initialize the SCSI chip ctrl registers + * + * Inputs : psh - pointer to this host adapter's structure + * + ***********************************************************************/ +int DC390W_initAdapter( PSH psh, ULONG io_port, UCHAR Irq, USHORT index ) +{ + USHORT ioport, wval; + UCHAR bval; + PACB pACB, pacb; + USHORT used_irq = 0; + + pacb = pACB_start; + if( pacb != NULL ) + { + for ( ; (pacb != (PACB) -1) ; ) + { + if( pacb->IRQLevel == Irq ) + { + used_irq = 1; + break; + } + else + pacb = pacb->pNextACB; + } + } + + if( !used_irq ) + { +#ifdef VERSION_ELF_1_2_13 + if( request_irq(Irq, DC390W_Interrupt, SA_INTERRUPT, "tmscsiw")) +#else + if( request_irq(Irq, DC390W_Interrupt, SA_INTERRUPT | SA_SHIRQ, "tmscsiw", NULL)) +#endif + { + printk("DC390W : register IRQ error!\n"); + return( -1 ); + } + } + request_region(io_port,psh->n_io_port,"tmscsiw"); + + ioport = (USHORT) io_port; + outb(IRQ_DISABLE, ioport+DCNTL); + outb(ABORT_OP, ioport+ISTAT); + udelay(100000); + outb(0, ioport+ISTAT); + bval = inb(ioport+DSTAT); + bval = inb(ioport+ISTAT); + wval = inw(ioport+SIST0); + + pACB = (PACB) psh->hostdata; + bval = pACB->AdaptSCSIID; + bval |= ENABLE_RESEL; + outb(bval,ioport+SCID); + + if(pACB->AdaptType == DC390W) + bval = SYNC_CLK_F2+ASYNC_CLK_F2; + else + { /* @1.09 */ + bval = inb(ioport+CTEST3); + if( (bval & CHIP_REV_MASK) < 0x30 ) /* 53C875 Rev. F or later ? */ + goto REVF; + bval = inb(ioport+STEST1); + if( (bval & 0x0C) == 0x0C ) /* double clock already enable ? */ + goto REVF; + outb(8,ioport+STEST1); /* enable clock doubler */ + udelay(20); + outb(HALT_SCSI_CLK,ioport+STEST3); /* halt clock */ + outb(0x0C,ioport+STEST1); /* select double SCSI clock */ + outb(0,ioport+STEST3); /* re-enable clock */ +REVF: + bval = SYNC_CLK_F4+ASYNC_CLK_F4; + } + outb(bval,ioport+SCNTL3); + + bval = SYNC_PERIOD_F4+ASYNCHRONOUS; /* set to async */ + outb(bval,ioport+SXFER); + + bval = WRT_EN_INVALIDATE; /* Enable write and invalidate */ + outb(bval,ioport+CTEST3); + + bval = EN_DMA_FIFO_536+BURST_LEN_MSB; /* select 536 bytes DMA FIFO, burst len bit2=1 */ + outb(bval,ioport+CTEST5); + + bval = BURST_LEN8+EN_READ_LINE+EN_READ_MULTIPLE+BURST_OPCODE_FETCH+AUTO_START; /* set DMA parameter */ + outb(bval,ioport+DMODE); + + bval = EN_ABORTED+EN_SCRIPT_INT+EN_ILLEGAL_INST; /* enable DMA interrupt */ + outb(bval,ioport+DIEN); + + bval = EN_CACHE_LINE_SIZE+EN_PRE_FETCH+TOTEM_POLE_IRQ+COMPATIBLE_700; + outb(bval,ioport+DCNTL); + + bval = EN_PHASE_MISMATCH+EN_SCSI_GERROR+EN_UNEXPECT_DISC+EN_SCSI_RESET+EN_PARITY_ERROR; + outb(bval,ioport+SIEN0); + + bval = EN_SEL_TIMEOUT+EN_GENERAL_TIMEOUT; + outb(bval,ioport+SIEN1); + + bval = SEL_TO_204ms; /* 250ms selection timeout */ + outb(bval,ioport+STIME0); + + wval = 1 << (eepromBuf[index][EE_ADAPT_SCSI_ID]); /* @1.11 */ + outw(wval,ioport+RESPID0); + + bval = DIS_SINGLE_INIT; + if( eepromBuf[index][EE_MODE2] & ACTIVE_NEGATION ) + bval |= ACTIVE_NEGATION_; + outb(bval,ioport+STEST3); + + return(0); +} + + +/*********************************************************************** + * Function : static int DC390W_init (struct Scsi_Host *host) + * + * Purpose : initialize the internal structures for a given SCSI host + * + * Inputs : host - pointer to this host adapter's structure/ + * + * Preconditions : when this function is called, the chip_type + * field of the pACB structure MUST have been set. + ***********************************************************************/ + +static int +DC390W_init (PSHT psht, USHORT chipType, ULONG io_port, UCHAR Irq, USHORT index) +{ + PSH psh; + PACB pACB; + + if( ! CheckEEpromCheckSum( &eepromBuf[index][0], (USHORT) io_port) ) + { + psh = scsi_register( psht, sizeof(DC390W_ACB) ); + if( !psh ) + return( -1 ); + if( !pSH_start ) + { + pSH_start = psh; + pSH_current = psh; + } + else + { + pSH_current->next = psh; + pSH_current = psh; + } + +#ifdef DC390W_DEBUG0 + printk("DC390W : pSH = %8x,", (UINT) psh); +#endif + + DC390W_initACB( psh, chipType, io_port, Irq, index ); + if( !DC390W_initAdapter( psh, io_port, Irq, index ) ) + { + pACB = (PACB) psh->hostdata; + if( !pACB_start ) + { + pACB_start = pACB; + pACB_current = pACB; + pACB->pNextACB = (PACB) -1; + } + else + { + pACB_current->pNextACB = pACB; + pACB_current = pACB; + pACB->pNextACB = (PACB) -1; + } + +#ifdef DC390W_DEBUG0 + printk("DC390W : pACB = %8x, pDCB_array = %8x, pSRB_array = %8x\n", + (UINT) pACB, (UINT) pACB->DCB_array, (UINT) pACB->SRB_array); + printk("DC390W : ACB size= %4x, DCB size= %4x, SRB size= %4x\n", + sizeof(DC390W_ACB), sizeof(DC390W_DCB), sizeof(DC390W_SRB) ); +#endif + + } + else + { + pSH_start = NULL; + scsi_unregister( psh ); + return( -1 ); + } + DC390W_download_script( psh ); + return( 0 ); + } + else + { + printk("DC390W_init: EEPROM reading error!\n"); + return( -1 ); + } +} + + +void MyDelay( void ) +{ + UCHAR i,j; + + j = inb(0x61) & 0x10; + + for(;;) + { + i = inb(0x61) & 0x10; + if( j ^ i) + break; + } +} + + +void EnDisableCE( UCHAR Flag, USHORT scsiIOPort ) +{ + + UCHAR bval; + USHORT port; + + port = (scsiIOPort & 0xff00) + GPREG; + if(Flag == ENABLE_CE) + bval = 0x10; + else + bval = 0x00; + outb(bval,port); + udelay(8); /* Delay();*/ +} + + +void EEpromOutDI( USHORT Carry, USHORT scsiIOPort ) +{ + UCHAR bval; + USHORT port; + + port = (scsiIOPort & 0xff00) + GPREG; + bval = 0x10; + if(Carry) + bval |= 0x02; /* SK=0, DI */ + outb(bval,port); + udelay(8); /* Delay();*/ + bval |= 0x04; /* SK=1, DI */ + outb(bval,port); + udelay(8); /* Delay();*/ + bval &= 0xfb; /* SK=0, DI */ + outb(bval,port); + udelay(8); /* Delay();*/ +} + + +void EEpromPrepare( UCHAR EEpromCmd, USHORT scsiIOPort ) +{ + UCHAR i,j; + USHORT carryFlag; + + carryFlag = 1; + j = 0x80; + for(i=0;i<9;i++) + { + EEpromOutDI(carryFlag,scsiIOPort); + carryFlag = (EEpromCmd & j) ? 1 : 0; + j >>= 1; + } +} + + +UCHAR EEpromInDo(USHORT scsiIOPort) +{ + UCHAR bval; + USHORT port; + + port = (scsiIOPort & 0xff00) + GPREG; + bval = 0x14; /* SK=1 */ + outb(bval,port); + udelay(8); /* Delay();*/ + bval = 0x10; /* SK=0 */ + outb(bval,port); + udelay(8); /* Delay();*/ + bval = inb(port); + if(bval & 0x01) + return( 1 ); + else + return( 0 ); +} + + +USHORT EEpromGetData(USHORT scsiIOPort) +{ + UCHAR i; + UCHAR carryFlag; + USHORT wval; + + wval = 0; + for(i=0;i<16;i++) + { + wval <<= 1; + carryFlag = EEpromInDo(scsiIOPort); + wval |= carryFlag; + } + return( wval ); +} + + +void ReadEEprom( PUCHAR EEpromBuf, USHORT scsiIOPort ) +{ + UCHAR cmd; + + cmd = EEPROM_READ; +loop_rd: + EnDisableCE(ENABLE_CE, scsiIOPort); + EEpromPrepare(cmd, scsiIOPort); + *((PUSHORT)EEpromBuf) = EEpromGetData(scsiIOPort); + EEpromBuf++; + EEpromBuf++; + cmd++; + EnDisableCE(DISABLE_CE, scsiIOPort); + if(cmd & 0x3f) + goto loop_rd; +} + + +USHORT CheckEEpromCheckSum( PUCHAR EEpromBuf, USHORT scsiIOPort) +{ + USHORT wval,port, *ptr; + UCHAR i,bval; + + port = (scsiIOPort & 0xff00) + GPCNTL; + bval = 0x09; /* configure IO Pin */ + outb(bval,port); + ReadEEprom(EEpromBuf,scsiIOPort); /* read eeprom data */ + wval = 0; + ptr = (PUSHORT) EEpromBuf; + for(i=0; i<128 ;i+=2, ptr++) + wval += *ptr; + return( (wval == 0x1234) ? 0 : -1); +} + + + +static void +DC390W_download_script (struct Scsi_Host *host) +{ + ULONG wlval, wlval1, length, alignm; + USHORT j, k, m; + USHORT ioport; + UCHAR bval; + PACB pACB; + PSRB pSRB; + void *pSrc, *pSrc1; + ULONG *pStart; + ULONG Ent_reselected; + ULONG Ent_reselecttag; + ULONG Ent_select0; + ULONG Ent_select1; + ULONG Ent_check_phase; + ULONG Ent_status1_phase; + ULONG Ent_command_phase; + ULONG Ent_jump_table0; + ULONG Ent_din_phaseB; + ULONG Ent_dout_phaseB; + ULONG Ent_din_pad_0; + ULONG Ent_dout_pad_0; + ULONG Ent_jump_tablew; + ULONG Ent_din_pad_1; + ULONG Ent_dout_pad_1; + ULONG Ent_mout_phase; + ULONG Ent_status_phase; + ULONG Ent_min_phase; + ULONG Ent_jump_msgok; + ULONG Ent_msg__1; + ULONG Ent_msg___3; + ULONG Ent_msg___2; + ULONG Ent_set_atn; + ULONG Ent_msg__a; + ULONG Ent_msg__23; + ULONG Ent_msg__3; + ULONG Ent_msg__4; + ULONG Ent_clr_atn; + ULONG Ent_din_phaseW; + ULONG Ent_dout_phaseW; + ULONG Ent_din_pad_addrB; + ULONG Ent_dout_pad_addrB; + ULONG Ent_din_pad_addrW; + ULONG Ent_dout_pad_addrW; + + + pACB = (PACB) host->hostdata; + ioport = pACB->IOPortBase; + bval = SCRATCHAB_AS_BASE; /* set scratchB contains 4K RAM base address */ + outb(bval,ioport+CTEST2); + + wlval = inl((ioport+SCRATCHB)); /* get starting address of 4K RAM */ +/* wlval += 0x800; */ /* point to Upper 2K RAM */ + DesPhysAddr[0] = wlval; /* destination address */ + +#ifdef DC390W_DEBUG0 + printk("DesAddr=%8x,",(UINT) wlval); +#endif + bval = 0; /* set Scratch_A and Scratch_B to normal mode */ + outb(bval,ioport+CTEST2); + + /*------------------------------------------------------------------- + * patch the label in jump instruction: using offset relative + * to start_script + *------------------------------------------------------------------*/ + + Ent_reselected = (ULONG) reselected - (ULONG) start_script; + Ent_reselecttag = (ULONG) reselecttag - (ULONG) start_script; + Ent_select0 = (ULONG) select0 - (ULONG) start_script; + Ent_select1 = (ULONG) select1 - (ULONG) start_script; + Ent_check_phase = (ULONG) check_phase - (ULONG) start_script; + Ent_status1_phase = (ULONG) status1_phase - (ULONG) start_script; + Ent_command_phase = (ULONG) command_phase - (ULONG) start_script; + Ent_din_phaseB = (ULONG) din_phaseB - (ULONG) start_script; + Ent_dout_phaseB = (ULONG) dout_phaseB - (ULONG) start_script; + Ent_din_phaseW = (ULONG) din_phaseW - (ULONG) start_script; + Ent_dout_phaseW = (ULONG) dout_phaseW - (ULONG) start_script; + Ent_jump_table0 = (ULONG) jump_table0 - (ULONG) start_script; + Ent_din_pad_0 = (ULONG) din_pad_0 - (ULONG) start_script; + Ent_din_pad_addrB = (ULONG) din_pad_addrB - (ULONG) start_script; + Ent_dout_pad_0 = (ULONG) dout_pad_0 - (ULONG) start_script; + Ent_dout_pad_addrB = (ULONG) dout_pad_addrB - (ULONG) start_script; + Ent_jump_tablew = (ULONG) jump_tablew - (ULONG) start_script; + Ent_din_pad_1 = (ULONG) din_pad_1 - (ULONG) start_script; + Ent_din_pad_addrW = (ULONG) din_pad_addrW - (ULONG) start_script; + Ent_dout_pad_1 = (ULONG) dout_pad_1 - (ULONG) start_script; + Ent_dout_pad_addrW = (ULONG) dout_pad_addrW - (ULONG) start_script; + Ent_mout_phase = (ULONG) mout_phase - (ULONG) start_script; + Ent_status_phase = (ULONG) status_phase - (ULONG) start_script; + Ent_min_phase = (ULONG) min_phase - (ULONG) start_script; + Ent_jump_msgok = (ULONG) jump_msgok - (ULONG) start_script; + Ent_msg__1 = (ULONG) msg__1 - (ULONG) start_script; + Ent_msg___3 = (ULONG) msg___3 - (ULONG) start_script; + Ent_msg___2 = (ULONG) msg___2 - (ULONG) start_script; + Ent_set_atn = (ULONG) set_atn - (ULONG) start_script; + Ent_msg__a = (ULONG) msg__a - (ULONG) start_script; + Ent_msg__23 = (ULONG) msg__23 - (ULONG) start_script; + Ent_msg__3 = (ULONG) msg__3 - (ULONG) start_script; + Ent_msg__4 = (ULONG) msg__4 - (ULONG) start_script; + Ent_clr_atn = (ULONG) clr_atn - (ULONG) start_script; + + jmp_select0[0] = Ent_select0 + wlval; + jmp_reselected[0] = Ent_reselected + wlval; + jmp_check_phase[0] = Ent_check_phase + wlval; + jmp_check_phase1[0] = Ent_check_phase + wlval; + jmp_check_phase2[0] = Ent_check_phase + wlval; + jmp_check_phase3[0] = Ent_check_phase + wlval; + jmp_check_phase4[0] = Ent_check_phase + wlval; + jmp_check_phase5[0] = Ent_check_phase + wlval; + jmp_check_phase6[0] = Ent_check_phase + wlval; + jmp_status1_phase[0] = Ent_status1_phase + wlval; + jmp_status1_phase1[0] = Ent_status1_phase + wlval; + jmp_status1_phase2[0] = Ent_status1_phase + wlval; + jmp_status1_phase3[0] = Ent_status1_phase + wlval; + jmp_command_phase[0] = Ent_command_phase + wlval; + for(j=0,k=1,m=0; j< (MAX_SG_LIST_BUF+1); j++) + { + jmp_dio_phaseB[k] = Ent_din_phaseB + m + wlval; + jmp_dio_phaseW[k] = Ent_din_phaseW + m + wlval; + k += 2; + jmp_dio_phaseB[k] = Ent_dout_phaseB + m + wlval; + jmp_dio_phaseW[k] = Ent_dout_phaseW + m + wlval; + k += 2; + m += 8; + } + jmp_din_pad_0[0] = Ent_din_pad_0 + wlval; + jmp_dout_pad_0[0] = Ent_dout_pad_0 + wlval; + jmp_din_pad_addrB[0] = Ent_din_pad_addrB + wlval; + jmp_dout_pad_addrB[0] = Ent_dout_pad_addrB + wlval; + jmp_din_pad_addrW[0] = Ent_din_pad_addrW + wlval; + jmp_dout_pad_addrW[0] = Ent_dout_pad_addrW + wlval; + jmp_din_pad_1[0] = Ent_din_pad_1 + wlval; + jmp_dout_pad_1[0] = Ent_dout_pad_1 + wlval; + jmp_status_phase[0] = Ent_status_phase + wlval; + jmp_min_phase[0] = Ent_min_phase + wlval; + jmp_mout_phase[0] = Ent_mout_phase + wlval; + jmp_jump_msgok[0] = Ent_jump_msgok + wlval; + jmp_msg__1[0] = Ent_msg__1 + wlval; + jmp_msg___3[0] = Ent_msg___3 + wlval; + jmp_msg___2[0] = Ent_msg___2 + wlval; + jmp_msg__a[0] = Ent_msg__a + wlval; + jmp_msg__a1[0] = Ent_msg__a + wlval; + jmp_msg__a2[0] = Ent_msg__a + wlval; + jmp_msg__23[0] = Ent_msg__23 + wlval; + jmp_msg__3[0] = Ent_msg__3 + wlval; + jmp_msg__4[0] = Ent_msg__4 + wlval; + + /*-------------------------------------------------------------------- + // patch the element in ACB struct: using Physical address + //-------------------------------------------------------------------*/ + +#ifndef VERSION_ELF_1_2_13 + wlval1 = virt_to_phys( pACB->msgin123 ); +#else + wlval1 = (ULONG) pACB->msgin123; +#endif + ACB_msgin123_1[0] = wlval1; + ACB_msgin123_2[0] = wlval1; + ACB_msgin123_3[0] = wlval1; + ACB_msgin123_4[0] = wlval1; + ACB_msgin123_5[0] = wlval1; + ACB_msgin123_6[0] = wlval1; + ACB_msgin123_7[0] = wlval1; + +#ifndef VERSION_ELF_1_2_13 + ACB_status[0] = virt_to_phys( &pACB->status ); +#else + ACB_status[0] = (ULONG) &pACB->status; +#endif + /*-------------------------------------------------------------------- + // patch the element in SRB struct: using offset in struct + //-------------------------------------------------------------------*/ + + pSRB = (PSRB) pACB->SRB_array; + select1[0] = (select1[0] & 0xffff0000) + ((ULONG) &pSRB->__select - (ULONG) &pSRB->CmdBlock); + SRB_msgout0[0] = (ULONG) &pSRB->__msgout0 - (ULONG) &pSRB->CmdBlock; + SRB_msgout01[0] = (ULONG) &pSRB->__msgout0 - (ULONG) &pSRB->CmdBlock; + SRB_command[0] = (ULONG) &pSRB->__command - (ULONG) &pSRB->CmdBlock; + SRB_SegmentPad[0] = (ULONG) &pSRB->SegmentPad - (ULONG) &pSRB->CmdBlock; + SRB_SegmentPad1[0] = (ULONG) &pSRB->SegmentPad - (ULONG) &pSRB->CmdBlock; + SRB_SegmentPad2[0] = (ULONG) &pSRB->SegmentPad - (ULONG) &pSRB->CmdBlock; + SRB_SegmentPad3[0] = (ULONG) &pSRB->SegmentPad - (ULONG) &pSRB->CmdBlock; + wlval = (ULONG) &pSRB->Segment0 - (ULONG) &pSRB->CmdBlock; + for(j=0,k=1; j<(MAX_SG_LIST_BUF+1); j++) + { + din_phaseB[k] = wlval; + dout_phaseB[k] = wlval; + din_phaseW[k] = wlval; + dout_phaseW[k] = wlval; + k += 2; + wlval += 8; + } + + + bval = inb(ioport+DCNTL); + bval |= IRQ_DISABLE; + outb(bval,ioport+DCNTL); /* disable interrupt */ + +/* pSrc = scsi_init_malloc( 2048, GFP_ATOMIC); */ + pSrc = scsi_init_malloc( 4096, GFP_ATOMIC); /* 1.11 */ +#ifdef DC390W_DEBUG0 + printk("SrcAlloc=%8x,",(UINT) pSrc); +#endif + alignm = 4 - (((ULONG) pSrc) & 3); + pSrc1 = (void *)(((ULONG) pSrc) + alignm); + length = (ULONG) end_script - (ULONG) start_script; + memcpy( pSrc1, (void *) start_script, length); + pStart = (ULONG *) ((ULONG) start_mov - (ULONG) start_script); + pStart =(ULONG *) (((ULONG) pStart) + ((ULONG) pSrc1)); + +#ifdef DC390W_DEBUG0 + printk("SrcAddr=%8x,\n",(UINT) pSrc1); +#endif +#ifndef VERSION_ELF_1_2_13 + (ULONG *)pStart[1] = virt_to_phys( pSrc1 ); +#else + (ULONG *)pStart[1] = (ULONG) pSrc1; +#endif + +/* wlval = virt_to_phys( start_script ); */ /* physical address of start_script */ +/* SrcPhysAddr[0] = wlval; */ /* sources address */ + +/* start to download SCRIPT instruction to the RAM of NCR53c825A,875 */ + +/* wlval = virt_to_phys( start_mov ); */ + +#ifndef VERSION_ELF_1_2_13 + wlval = virt_to_phys( pStart ); +#else + wlval = (ULONG) pStart; +#endif + + outl(wlval,ioport+DSP); + + bval = inb(ioport+ISTAT); + while(!(bval & DMA_INT_PENDING)) /* check load start_script is finished? */ + bval = inb(ioport+ISTAT); + + bval = inb(ioport+DSTAT); /* clear interrupt */ + + bval = inb(ioport+DCNTL); + bval &= ~IRQ_DISABLE; + outb(bval,ioport+DCNTL); /* re-enable interrupt */ + + scsi_init_free((char *) pSrc, 4096); + + wlval = DesPhysAddr[0]; /* starting addr of RAM */ + wlval -= (ULONG) start_script; + + pACB->jmp_reselect = wlval + (ULONG) start_script; + pACB->jmp_select = wlval + (ULONG) select1; + pACB->jmp_table8 = wlval + (ULONG) jump_table0; + pACB->jmp_set_atn = wlval + (ULONG) set_atn; + pACB->jmp_clear_ack = wlval + (ULONG) msg__a; + pACB->jmp_next = wlval + (ULONG) check_phase; + pACB->jmp_din8 = wlval + (ULONG) din_phaseB+8; + pACB->jmp_dout8 = wlval + (ULONG) dout_phaseB+8; + pACB->jmp_clear_atn = wlval + (ULONG) clr_atn; + pACB->jmp_reselecttag = wlval + (ULONG) reselecttag; + + wlval = pACB->jmp_reselect; + outl(wlval,(ioport+DSP)); + return; +} + + +/*********************************************************************** + * Function : int DC390W_detect(Scsi_Host_Template *psht) + * + * Purpose : detects and initializes NCR53c825A,875 SCSI chips + * that were autoprobed, overridden on the LILO command line, + * or specified at compile time. + * + * Inputs : psht - template for this SCSI adapter + * + * Returns : number of host adapters detected + * + ***********************************************************************/ + +int +DC390W_detect(Scsi_Host_Template *psht) +{ + UCHAR pci_bus, pci_device_fn, irq; +#ifndef VERSION_ELF_1_2_13 + UINT io_port, ram_base; +#else + ULONG io_port, ram_base; +#endif + USHORT i; + int error = 0; + USHORT adaptCnt = 0; /* Number of boards detected */ + USHORT pci_index = 0; /* Device index to PCI BIOS calls */ + USHORT pci_index2 = 0; /* Device index to PCI BIOS calls */ + USHORT chipType = 0; + + +#ifndef VERSION_ELF_1_2_13 + psht->proc_dir = &proc_scsi_tmscsiw; +#endif + + InitialTime = 1; + pSHT_start = psht; + jmp_table16 = (ULONG) jump_tablew - (ULONG) jump_table0; + jmp_din16 = (ULONG) din_phaseW - (ULONG) din_phaseB; + jmp_dout16 = (ULONG) dout_phaseW - (ULONG) dout_phaseB; + pACB_start = NULL; + + if ( pcibios_present() ) + { + for (i = 0; i < MAX_ADAPTER_NUM; ++i) + { + if( !pcibios_find_device( PCI_VENDOR_ID_NCR, + PCI_DEVICE_ID_NCR53C825A, + pci_index, &pci_bus, &pci_device_fn) ) + { + chipType = PCI_DEVICE_ID_NCR53C825A; + pci_index++; + } + else if( !pcibios_find_device( PCI_VENDOR_ID_NCR, + PCI_DEVICE_ID_NCR53C875, + pci_index2, &pci_bus, &pci_device_fn) ) + { + chipType = PCI_DEVICE_ID_NCR53C875; + pci_index2++; + } + + if( chipType ) + { + error = pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &io_port); + error |= pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_2, &ram_base); + error |= pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &irq); + if( error ) + { + printk("DC390W_detect: reading configuration registers error!\n"); + InitialTime = 0; + return( 0 ); + } + + (USHORT) io_port = (USHORT) io_port & 0xFFFE; +#ifdef DC390W_DEBUG0 + printk("DC390W : IO_PORT=%4x,RAM_BASE=%8x,IRQ=%x,CHIPID=%x,\n", + (UINT) io_port, (UINT) ram_base, irq, (UCHAR)chipType); +#endif + + if( !DC390W_init(psht, chipType, io_port, irq, i) ) + adaptCnt++; + chipType = 0; + } + else + break; + } + } + InitialTime = 0; + adapterCnt = adaptCnt; + return( adaptCnt ); +} + +#ifndef VERSION_ELF_1_2_13 + +/******************************************************************** + * Function: tmscsiw_set_info() + * + * Purpose: Set adapter info (!) + * + * Not yet implemented + * + *******************************************************************/ + +int tmscsiw_set_info(char *buffer, int length, struct Scsi_Host *shpnt) +{ + return(-ENOSYS); /* Currently this is a no-op */ +} + +/******************************************************************** + * Function: tmscsiw_proc_info(char* buffer, char **start, + * off_t offset, int length, int hostno, int inout) + * + * Purpose: return SCSI Adapter/Device Info + * + * Input: buffer: Pointer to a buffer where to write info + * start : + * offset: + * hostno: Host adapter index + * inout : Read (=0) or set(!=0) info + * + * Output: buffer: contains info + * length; length of info in buffer + * + * return value: length + * + ********************************************************************/ + +/* KG: proc_info taken from driver aha152x.c */ + +#undef SPRINTF +#define SPRINTF(args...) pos += sprintf(pos, ## args) + +#define YESNO(YN)\ +if (YN) SPRINTF(" Yes ");\ +else SPRINTF(" No ") + +int tmscsiw_proc_info(char *buffer, char **start, + off_t offset, int length, int hostno, int inout) +{ + int dev, spd, spd1; + char *pos = buffer; + PSH shpnt; + PACB acbpnt; + PDCB dcbpnt; + unsigned long flags; +/* Scsi_Cmnd *ptr; */ + + acbpnt = pACB_start; + + while(acbpnt != (PACB)-1) + { + shpnt = acbpnt->pScsiHost; + if (shpnt->host_no == hostno) break; + acbpnt = acbpnt->pNextACB; + } + + if (acbpnt == (PACB)-1) return(-ESRCH); + if (!shpnt) return(-ESRCH); + + if(inout) /* Has data been written to the file ? */ + return(tmscsiw_set_info(buffer, length, shpnt)); + + SPRINTF("Tekram DC390W/U/F (T) PCI SCSI Host Adadpter, "); + SPRINTF("Driver Version 1.12, 1997/02/17\n"); + + save_flags(flags); + cli(); + + SPRINTF("SCSI Host Nr %i, ", hostno); + SPRINTF("DC390WUF Adapter Nr %i\n", acbpnt->AdapterIndex); + SPRINTF("IOPortBase 0x%04x, ", acbpnt -> IOPortBase); + SPRINTF("IRQLevel 0x%02x\n",acbpnt -> IRQLevel); + + SPRINTF("Adapter Type: "); + switch(acbpnt->AdaptType) + { + case DC390W: SPRINTF("DC390W, Fast Wide SCSI \n"); break; + case DC390U: SPRINTF("DC390U, Ultra SCSI\n"); break; + case DC390F: SPRINTF("DC390F, Ultra Wide SCSI\n"); break; + default: SPRINTF("Unknown !\n"); + } + + SPRINTF("MaxID %i, MaxLUN %i, ", acbpnt->max_id, acbpnt->max_lun); + SPRINTF("AdapterID %i, AdapterLUN %i\n", acbpnt->AdaptSCSIID, acbpnt->AdaptSCSILUN); + + SPRINTF("TagMaxNum %i, Status %i\n", acbpnt->TagMaxNum, acbpnt->status); + + SPRINTF("Nr of attached devices: %i\n", acbpnt->DeviceCnt); + + SPRINTF("Un ID LUN Prty Sync DsCn SndS TagQ Wide NegoPeriod SyncSpeed SyncOffs\n"); + dcbpnt = acbpnt->pLinkDCB; + + for (dev = 0; dev < acbpnt->DeviceCnt; dev++) + { + SPRINTF("%02i %02i %02i ", dev, dcbpnt->UnitSCSIID, dcbpnt->UnitSCSILUN); + YESNO(dcbpnt->DevMode & PARITY_CHK_); + YESNO(dcbpnt->DCBsxfer & OFFSET_MASK); + YESNO(dcbpnt->DevMode & EN_DISCONNECT_); + YESNO(dcbpnt->DevMode & SEND_START_); + YESNO(dcbpnt->MaxCommand > 1); + YESNO(dcbpnt->DCBscntl3 & EN_WIDE_SCSI); + SPRINTF(" %03i ns ", (dcbpnt->NegoPeriod) << 2); + if (dcbpnt->DCBsxfer & OFFSET_MASK) + { + spd = 1000/(dcbpnt->SyncPeriod <<2); + spd1 = 1000%(dcbpnt->SyncPeriod <<2); + spd1 = (spd1 * 10)/(dcbpnt->SyncPeriod <<2); + SPRINTF(" %2i.%1i M %02i\n", spd, spd1, dcbpnt->DCBsxfer & OFFSET_MASK); + } + else SPRINTF("\n"); + /* Add more info ...*/ + dcbpnt = dcbpnt->pNextDCB; + } + + restore_flags(flags); + *start = buffer + offset; + + if (pos - buffer < offset) + return 0; + else if (pos - buffer - offset < length) + return pos - buffer - offset; + else + return length; +} +#endif /* VERSION_ELF_1_2_13 */ + +#ifdef MODULE + +/*********************************************************************** + * Function : static int DC390W_shutdown (struct Scsi_Host *host) + * + * Purpose : does a clean (we hope) shutdown of the NCR SCSI chip. + * Use prior to dumping core, unloading the NCR driver, etc. + * + * Returns : 0 on success + ***********************************************************************/ +static int +DC390W_shutdown (struct Scsi_Host *host) +{ + USHORT ioport; + unsigned long flags; + PACB pACB = (PACB) host->hostdata; + + ioport = (unsigned int) pACB->IOPortBase; + + save_flags (flags); + cli(); + +/* pACB->soft_reset(host); */ +/* + * For now, we take the simplest solution : reset the SCSI bus. Eventually, + * - If a command is connected, kill it with an ABORT message + * - If commands are disconnected, connect to each target/LUN and + * do a ABORT, followed by a SOFT reset, followed by a hard + * reset. + */ + +#ifdef DC390W_DEBUG0 + printk("DC390W: shutdown,"); +#endif + outb(ASSERT_RST, ioport+SCNTL1); + udelay(25); /* Minimum amount of time to assert RST */ + outb(0, ioport+SCNTL1); + restore_flags (flags); + return( 0 ); +} + + +int DC390W_release(struct Scsi_Host *host) +{ + int irq_count; + struct Scsi_Host *tmp; + + DC390W_shutdown (host); + + if (host->irq != IRQ_NONE) + { + for (irq_count = 0, tmp = pSH_start; tmp; tmp = tmp->next) + { + if ( tmp->irq == host->irq ) + ++irq_count; + } + if (irq_count == 1) + { +#ifdef DC390W_DEBUG0 + printk("DC390W: Free IRQ %i.",host->irq); +#endif +#ifndef VERSION_ELF_1_2_13 + free_irq(host->irq,NULL); +#else + free_irq(host->irq); +#endif + } + } + + release_region(host->io_port,host->n_io_port); + + return( 1 ); +} + +Scsi_Host_Template driver_template = DC390WUF; +#include "scsi_module.c" +#endif /* def MODULE */ + diff --git a/linux/src/drivers/scsi/tmscsiw.h b/linux/src/drivers/scsi/tmscsiw.h new file mode 100644 index 00000000..4ebbe4aa --- /dev/null +++ b/linux/src/drivers/scsi/tmscsiw.h @@ -0,0 +1,1082 @@ +/*********************************************************************** +;* File Name : TMSCSIW.H * +;* TEKRAM DC-390 PCI Wide SCSI Bus Master Host Adapter* +;* Device Driver * +;***********************************************************************/ + +#ifndef TMSCSIW_H +#define TMSCSIW_H + +#define IRQ_NONE 255 + +typedef unsigned char UCHAR; +typedef unsigned short USHORT; +typedef unsigned long ULONG; +typedef unsigned int UINT; + +typedef UCHAR *PUCHAR; +typedef USHORT *PUSHORT; +typedef ULONG *PULONG; +typedef Scsi_Host_Template *PSHT; +typedef struct Scsi_Host *PSH; +typedef Scsi_Device *PSCSIDEV; +typedef Scsi_Cmnd *PSCSICMD; +typedef void *PVOID; +typedef struct scatterlist *PSGL, SGL; + + +typedef struct _AddWkSpace +{ +USHORT WKIOCtrlFlag; /* ;b7-Done, b6-wrtVerify, b0-in process */ +USHORT XferredBlkCnt; +USHORT WKToXferBlkCnt; +USHORT WKcSGListDone; +USHORT WKMaxSGIndex; +ULONG WKToXferLen; +USHORT NxtSGoffset; +} AddWkSpace; + +/*;-----------------------------------------------------------------------*/ +typedef struct _SyncMsg +{ +UCHAR ExtendMsg; +UCHAR ExtMsgLen; +UCHAR SyncXferReq; +UCHAR Period; +UCHAR ReqOffset; +} SyncMsg; +/*;-----------------------------------------------------------------------*/ +typedef struct _Capacity +{ +ULONG BlockCount; +ULONG BlockLength; +} Capacity; +/*;-----------------------------------------------------------------------*/ +typedef struct _SGentry +{ +ULONG SGXferDataPtr; +ULONG SGXferDataLen; +} SGentry; + +typedef struct _SGentry1 +{ +ULONG SGXLen; +ULONG SGXPtr; +} SGentry1, *PSGE; + + +#define MAX_ADAPTER_NUM 4 +#define MAX_DEVICES 10 +#define MAX_SG_LIST_BUF 32 +#define MAX_CMD_QUEUE 20 +#define MAX_CMD_PER_LUN 8 +#define MAX_SCSI_ID 16 +#define MAX_SRB_CNT MAX_CMD_QUEUE+4 +#define END_SCAN 2 + +/* +;----------------------------------------------------------------------- +; SCSI Request Block +;----------------------------------------------------------------------- +*/ +struct _SRB +{ +UCHAR CmdBlock[12]; +ULONG Segment0[MAX_SG_LIST_BUF+1][2]; +ULONG SegmentPad[2]; + +ULONG __select; +ULONG __command[2]; /* ;len,ptr */ +ULONG __msgout0[2]; /* ;len,ptr */ + +ULONG PhysSRB; +ULONG ReturnAddr; +ULONG RemainSegPtr; +PSCSICMD pcmd; +struct _SRB *pNextSRB; +struct _DCB *pSRBDCB; +PSGL pSegmentList; +ULONG XferredLen; + +ULONG SGPhysAddr; /*;a segment starting address */ +ULONG XferredLen1; + +SGL Segmentx; /* make a one entry of S/G list table */ + +PUCHAR pMsgPtr; +USHORT SRBState; +USHORT Revxx2; /* ??? */ + +UCHAR MsgInBuf[6]; +UCHAR MsgOutBuf[6]; +UCHAR SenseDataBuf[0x12]; +UCHAR AdaptStatus; +UCHAR TargetStatus; + +UCHAR MsgCnt; +UCHAR EndMessage; +UCHAR TagNumber; +UCHAR InternalReq; /*; 1-ADD internal request, 0-DMD request */ + +UCHAR SGcount; +UCHAR SGIndex; +UCHAR IORBFlag; /*;81h-Reset, 2-retry */ +UCHAR SRBStatus; + +UCHAR RetryCnt; +UCHAR SRBFlag; /*; b0-AutoReqSense,b6-Read,b7-write */ + /*; b4-settimeout,b5-Residual valid */ +UCHAR ScratchABuf; +UCHAR Reserved3[1]; /*;for dword alignment */ +}; + +typedef struct _SRB DC390W_SRB, *PSRB; + +/* +;----------------------------------------------------------------------- +; Device Control Block +;----------------------------------------------------------------------- +*/ +struct _DCB +{ +UCHAR DCBselect; +UCHAR DCBsxfer; +UCHAR DCBsdid; +UCHAR DCBscntl3; + +UCHAR DCBscntl0; +UCHAR IdentifyMsg; +UCHAR DevMode; +UCHAR AdpMode; + +struct _DCB *pNextDCB; +struct _ACB *pDCBACB; + +PSCSICMD pQIORBhead; +PSCSICMD pQIORBtail; +PSCSICMD AboIORBhead; +PSCSICMD AboIORBtail; +USHORT QIORBCnt; +USHORT AboIORBcnt; + +PSRB pWaitingSRB; +PSRB pWaitLast; +PSRB pGoingSRB; +PSRB pGoingLast; +PSRB pActiveSRB; +USHORT GoingSRBCnt; +USHORT WaitSRBCnt; /* ??? */ + +ULONG TagMask; + +USHORT MaxCommand; +USHORT AdaptIndex; /*; UnitInfo struc start */ +USHORT UnitIndex; /*; nth Unit on this card */ +UCHAR UnitSCSIID; /*; SCSI Target ID (SCSI Only) */ +UCHAR UnitSCSILUN; /*; SCSI Log. Unit (SCSI Only) */ + +UCHAR InqDataBuf[8]; +UCHAR CapacityBuf[8]; +UCHAR SyncMode; /*; 0:async mode */ +UCHAR NegoPeriod; /*;for nego. */ +UCHAR SyncPeriod; /*;for reg. */ +UCHAR SyncOffset; /*;for reg. and nego.(low nibble) */ +UCHAR UnitCtrlFlag; +UCHAR DCBFlag; +UCHAR DevType; +UCHAR Reserved2[1]; /*;for dword alignment */ +}; + +typedef struct _DCB DC390W_DCB, *PDCB; +/* +;----------------------------------------------------------------------- +; Adapter Control Block +;----------------------------------------------------------------------- +*/ +struct _ACB +{ +ULONG PhysACB; +ULONG DevVendorID; +PSH pScsiHost; +struct _ACB *pNextACB; +USHORT IOPortBase; +USHORT Revxx1; /* ??? */ + +PDCB pLinkDCB; +PDCB pDCBRunRobin; +PDCB pActiveDCB; +PDCB pDCB_free; +PSRB pFreeSRB; +USHORT SRBCount; +USHORT AdapterIndex; /*; nth Adapter this driver */ +USHORT max_id; +USHORT max_lun; + +ULONG jmp_reselect; +ULONG jmp_select; +ULONG jmp_table8; +ULONG jmp_set_atn; +ULONG jmp_clear_ack; +ULONG jmp_next; +ULONG jmp_din8; +ULONG jmp_dout8; +ULONG jmp_clear_atn; +ULONG jmp_reselecttag; + +UCHAR msgin123[4]; +UCHAR status; +UCHAR AdaptSCSIID; /*; Adapter SCSI Target ID */ +UCHAR AdaptSCSILUN; /*; Adapter SCSI LUN */ +UCHAR DeviceCnt; +UCHAR IRQLevel; +UCHAR AdaptType; /*;1:390W, 2:390U, 3:390F */ +UCHAR TagMaxNum; +UCHAR ACBFlag; +UCHAR Gmode2; +UCHAR LUNchk; +UCHAR scan_devices; +UCHAR Reserved1[1]; /*;for dword alignment */ +UCHAR DCBmap[MAX_SCSI_ID]; +DC390W_DCB DCB_array[MAX_DEVICES]; /* +74h, Len=3E8 */ +DC390W_SRB SRB_array[MAX_SRB_CNT]; /* +45Ch, Len= */ +}; + +typedef struct _ACB DC390W_ACB, *PACB; + +/*;-----------------------------------------------------------------------*/ + +#define PCI_DEVICE_ID_NCR53C825A 0x0003 +#define PCI_DEVICE_ID_NCR53C875 0x000f + +#define CHIP810_ID 0x00011000 +#define CHIP820_ID 0x00021000 +#define CHIP825_ID 0x00031000 +#define CHIP815_ID 0x00041000 +#define CHIP875_ID 0x000f1000 + +#define DC390W 1 /*;825A - 16 BIT*/ +#define DC390U 2 /*;875 - 8 BIT*/ +#define DC390F 3 /*;875 - 16 BIT*/ + +#define BIT31 0x80000000 +#define BIT30 0x40000000 +#define BIT29 0x20000000 +#define BIT28 0x10000000 +#define BIT27 0x08000000 +#define BIT26 0x04000000 +#define BIT25 0x02000000 +#define BIT24 0x01000000 +#define BIT23 0x00800000 +#define BIT22 0x00400000 +#define BIT21 0x00200000 +#define BIT20 0x00100000 +#define BIT19 0x00080000 +#define BIT18 0x00040000 +#define BIT17 0x00020000 +#define BIT16 0x00010000 +#define BIT15 0x00008000 +#define BIT14 0x00004000 +#define BIT13 0x00002000 +#define BIT12 0x00001000 +#define BIT11 0x00000800 +#define BIT10 0x00000400 +#define BIT9 0x00000200 +#define BIT8 0x00000100 +#define BIT7 0x00000080 +#define BIT6 0x00000040 +#define BIT5 0x00000020 +#define BIT4 0x00000010 +#define BIT3 0x00000008 +#define BIT2 0x00000004 +#define BIT1 0x00000002 +#define BIT0 0x00000001 + +/*;---WKIOCtrlFlag */ +#define IN_PROCESSING BIT0 +#define BAD_WRITE_V_CMD BIT1 +#define VERIFY_CMD BIT2 +#define RESIDUAL_SG BIT3 + +/*;---UnitCtrlFlag */ +#define UNIT_ALLOCATED BIT0 +#define UNIT_INFO_CHANGED BIT1 +#define FORMATING_MEDIA BIT2 +#define UNIT_RETRY BIT3 + +/*;---UnitFlags */ +#define DASD_SUPPORT BIT0 +#define SCSI_SUPPORT BIT1 +#define ASPI_SUPPORT BIT2 + +/*;----SRBState machine definition */ +#define SRB_FREE 0 +#define SRB_WAIT BIT0 +#define SRB_READY BIT1 +#define SRB_MSGOUT BIT2 /*;arbitration+msg_out 1st byte*/ +#define SRB_MSGIN BIT3 +#define SRB_MSGIN_MULTI BIT4 +#define SRB_COMMAND BIT5 +#define SRB_START_ BIT6 /*;arbitration+msg_out+command_out*/ +#define SRB_DISCONNECT BIT7 +#define SRB_DATA_XFER BIT8 +#define SRB_XFERPAD BIT9 +#define SRB_STATUS BIT10 +#define SRB_COMPLETED BIT11 + +#define DO_WIDE_NEGO BIT12 +#define DO_SYNC_NEGO BIT13 +#define SRB_UNEXPECT_RESEL BIT14 + +/*;---ACBFlag */ +#define RESET_DEV BIT0 +#define RESET_DETECT BIT1 +#define RESET_DONE BIT2 + +/*;---DCBFlag */ +#define ABORT_DEV_ BIT0 + +/*;---SRBstatus */ +#define SRB_OK BIT0 +#define ABORTION BIT1 +#define OVER_RUN BIT2 +#define UNDER_RUN BIT3 +#define SRB_ERROR BIT4 + +/*;---SRBFlag */ +#define DATAOUT BIT7 +#define DATAIN BIT6 +#define RESIDUAL_VALID BIT5 +#define ENABLE_TIMER BIT4 +#define RESET_DEV0 BIT2 +#define ABORT_DEV BIT1 +#define AUTO_REQSENSE BIT0 + +/*;---Adapter status */ +#define H_STATUS_GOOD 0 +#define H_SEL_TIMEOUT 0x11 +#define H_OVER_UNDER_RUN 0x12 +#define H_UNEXP_BUS_FREE 0x13 +#define H_TARGET_PHASE_F 0x14 +#define H_INVALID_CCB_OP 0x16 +#define H_LINK_CCB_BAD 0x17 +#define H_BAD_TARGET_DIR 0x18 +#define H_DUPLICATE_CCB 0x19 +#define H_BAD_CCB_OR_SG 0x1A +#define H_ABORT 0x0FF + +/*; SCSI Status byte codes*/ +#define SCSI_STAT_GOOD 0x0 /*; Good status */ +#define SCSI_STAT_CHECKCOND 0x02 /*; SCSI Check Condition */ +#define SCSI_STAT_CONDMET 0x04 /*; Condition Met */ +#define SCSI_STAT_BUSY 0x08 /*; Target busy status */ +#define SCSI_STAT_INTER 0x10 /*; Intermediate status */ +#define SCSI_STAT_INTERCONDMET 0x14 /*; Intermediate condition met */ +#define SCSI_STAT_RESCONFLICT 0x18 /*; Reservation conflict */ +#define SCSI_STAT_CMDTERM 0x22 /*; Command Terminated */ +#define SCSI_STAT_QUEUEFULL 0x28 /*; Queue Full */ + +#define SCSI_STAT_UNEXP_BUS_F 0xFD /*; Unexpect Bus Free */ +#define SCSI_STAT_BUS_RST_DETECT 0xFE /*; Scsi Bus Reset detected */ +#define SCSI_STAT_SEL_TIMEOUT 0xFF /*; Selection Time out */ + +/*;---Sync_Mode */ +#define SYNC_DISABLE 0 +#define SYNC_ENABLE BIT0 +#define SYNC_NEGO_DONE BIT1 +#define WIDE_ENABLE BIT2 +#define WIDE_NEGO_DONE BIT3 +#define EN_TAG_QUEUING BIT4 + +#define SYNC_NEGO_OFFSET 16 + +/*;---SCSI bus phase*/ +#define SCSI_DATA_OUT 0 +#define SCSI_DATA_IN 1 +#define SCSI_COMMAND 2 +#define SCSI_STATUS_ 3 +#define SCSI_NOP0 4 +#define SCSI_NOP1 5 +#define SCSI_MSG_OUT 6 +#define SCSI_MSG_IN 7 + +/*;----SCSI MSG BYTE*/ +#define MSG_COMPLETE 0x00 +#define MSG_EXTENDED 0x01 +#define MSG_SAVE_PTR 0x02 +#define MSG_RESTORE_PTR 0x03 +#define MSG_DISCONNECT 0x04 +#define MSG_INITIATOR_ERROR 0x05 +#define MSG_ABORT 0x06 +#define MSG_REJECT_ 0x07 +#define MSG_NOP 0x08 +#define MSG_PARITY_ERROR 0x09 +#define MSG_LINK_CMD_COMPL 0x0A +#define MSG_LINK_CMD_COMPL_FLG 0x0B +#define MSG_BUS_RESET 0x0C +#define MSG_ABORT_TAG 0x0D +#define MSG_SIMPLE_QTAG 0x20 +#define MSG_HEAD_QTAG 0x21 +#define MSG_ORDER_QTAG 0x22 +#define MSG_IDENTIFY 0x80 +#define MSG_HOST_ID 0x0C0 + +/*;----SCSI STATUS BYTE*/ +#define STATUS_GOOD 0x00 +#define CHECK_CONDITION_ 0x02 +#define STATUS_BUSY 0x08 +#define STATUS_INTERMEDIATE 0x10 +#define RESERVE_CONFLICT 0x18 + +/* cmd->result */ +#define STATUS_MASK_ 0xFF +#define MSG_MASK 0xFF00 +#define RETURN_MASK 0xFF0000 + +/* +** Inquiry Data format +*/ + +typedef struct _SCSIInqData { /* INQ */ + + UCHAR DevType; /* Periph Qualifier & Periph Dev Type*/ + UCHAR RMB_TypeMod; /* rem media bit & Dev Type Modifier */ + UCHAR Vers; /* ISO, ECMA, & ANSI versions */ + UCHAR RDF; /* AEN, TRMIOP, & response data format*/ + UCHAR AddLen; /* length of additional data */ + UCHAR Res1; /* reserved */ + UCHAR Res2; /* reserved */ + UCHAR Flags; /* RelADr,Wbus32,Wbus16,Sync,etc. */ + UCHAR VendorID[8]; /* Vendor Identification */ + UCHAR ProductID[16]; /* Product Identification */ + UCHAR ProductRev[4]; /* Product Revision */ + + +} SCSI_INQDATA, *PSCSI_INQDATA; + + +/* Inquiry byte 0 masks */ + + +#define SCSI_DEVTYPE 0x1F /* Peripheral Device Type */ +#define SCSI_PERIPHQUAL 0xE0 /* Peripheral Qualifier */ + + +/* Inquiry byte 1 mask */ + +#define SCSI_REMOVABLE_MEDIA 0x80 /* Removable Media bit (1=removable) */ + + +/* Peripheral Device Type definitions */ + +#define SCSI_DASD 0x00 /* Direct-access Device */ +#define SCSI_SEQACESS 0x01 /* Sequential-access device */ +#define SCSI_PRINTER 0x02 /* Printer device */ +#define SCSI_PROCESSOR 0x03 /* Processor device */ +#define SCSI_WRITEONCE 0x04 /* Write-once device */ +#define SCSI_CDROM 0x05 /* CD-ROM device */ +#define SCSI_SCANNER 0x06 /* Scanner device */ +#define SCSI_OPTICAL 0x07 /* Optical memory device */ +#define SCSI_MEDCHGR 0x08 /* Medium changer device */ +#define SCSI_COMM 0x09 /* Communications device */ +#define SCSI_NODEV 0x1F /* Unknown or no device type */ + +/* +** Inquiry flag definitions (Inq data byte 7) +*/ + +#define SCSI_INQ_RELADR 0x80 /* device supports relative addressing*/ +#define SCSI_INQ_WBUS32 0x40 /* device supports 32 bit data xfers */ +#define SCSI_INQ_WBUS16 0x20 /* device supports 16 bit data xfers */ +#define SCSI_INQ_SYNC 0x10 /* device supports synchronous xfer */ +#define SCSI_INQ_LINKED 0x08 /* device supports linked commands */ +#define SCSI_INQ_CMDQUEUE 0x02 /* device supports command queueing */ +#define SCSI_INQ_SFTRE 0x01 /* device supports soft resets */ + + +/* +;========================================================== +; EEPROM byte offset +;========================================================== +*/ +typedef struct _EEprom +{ +UCHAR EE_MODE1; +UCHAR EE_SPEED; +UCHAR xx1; +UCHAR xx2; +} EEprom, *PEEprom; + +#define EE_ADAPT_SCSI_ID 64 +#define EE_MODE2 65 +#define EE_DELAY 66 +#define EE_TAG_CMD_NUM 67 + +/*; EE_MODE1 bits definition*/ +#define PARITY_CHK_ BIT0 +#define SYNC_NEGO_ BIT1 +#define EN_DISCONNECT_ BIT2 +#define SEND_START_ BIT3 +#define TAG_QUEUING_ BIT4 +#define WIDE_NEGO_ BIT5 + +/*; EE_MODE2 bits definition*/ +#define MORE2_DRV BIT0 +#define GREATER_1G BIT1 +#define RST_SCSI_BUS BIT2 +#define ACTIVE_NEGATION BIT3 +#define NO_SEEK BIT4 +#define LUN_CHECK BIT5 + +#define ENABLE_CE 1 +#define DISABLE_CE 0 +#define EEPROM_READ 0x80 + +/* +;========================================================== +; NCR 53C825 Registers Structure +;========================================================== +*/ +typedef struct _OperatingReg +{ +UCHAR SCSICtrl0; /*; (+00)*/ +UCHAR SCSICtrl1; /*; (+01)*/ +UCHAR SCSICtrl2; /*; (+02)*/ +UCHAR SCSICtrl3; /*; (+03)*/ +UCHAR SCSIChipID; /*; (+04)*/ +UCHAR SCSIXfer; /*; (+05)*/ +UCHAR SCSIDestID; /*; (+06)*/ +UCHAR GeneralPReg; /*; (+07)*/ +UCHAR SCSI1stByteR; /*; (+08)*/ +UCHAR SCSIOPCtrlL; /*; (+09)*/ +UCHAR SCSISelID; /*; (+0A)*/ +UCHAR SCSIBusCtrlLin; /*; (+0B)*/ +UCHAR DMAStatus; /*; (+0C)*/ +UCHAR SCSIStatus0; /*; (+0D)*/ +UCHAR SCSIStatus1; /*; (+0E)*/ +UCHAR SCSIStatus2; /*; (+0F)*/ +ULONG DataStrucAddr; /*; (+10)*/ +UCHAR InterruptStatus;/*; (+14)*/ +UCHAR Reser1; /*; (+15)*/ +UCHAR Reser2; /*; (+16)*/ +UCHAR Reser3; /*; (+17)*/ +UCHAR ChipTest0; /*; (+18)*/ +UCHAR ChipTest1; /*; (+19)*/ +UCHAR ChipTest2; /*; (+1A)*/ +UCHAR ChipTest3; /*; (+1B)*/ +ULONG TempStack; /*; (+1C)*/ +UCHAR DMAFIFO; /*; (+20)*/ +UCHAR ChipTest4; /*; (+21)*/ +UCHAR ChipTest5; /*; (+22)*/ +UCHAR ChipTest6; /*; (+23)*/ +UCHAR DMAByteCnt[3]; /*; (+24)*/ +UCHAR DMACommand; /*; (+27)*/ +ULONG DMANextAddr; /*; (+28)*/ +ULONG DMAScriptsPtr; /*; (+2C)*/ +ULONG DMAScriptPtrSav;/*; (+30)*/ +ULONG ScratchA; /*; (+34)*/ +UCHAR DMAMode; /*; (+38)*/ +UCHAR DMAIntEnable; /*; (+39)*/ +UCHAR DMAWatchDog; /*; (+3A)*/ +UCHAR DMACtrl; /*; (+3B)*/ +ULONG ADDer; /*; (+3C)*/ +UCHAR SCSIIntEnable0; /*; (+40)*/ +UCHAR SCSIIntEnable1; /*; (+41)*/ +UCHAR SCSIIntStatus0; /*; (+42)*/ +UCHAR SCSIIntStatus1; /*; (+43)*/ +UCHAR SCSILongParity; /*; (+44)*/ +UCHAR WideResiData; /*; (+45)*/ +UCHAR MemAccessCtrl; /*; (+46)*/ +UCHAR GeneralPCtrl; /*; (+47)*/ +UCHAR SCSITimer0; /*; (+48)*/ +UCHAR SCSITimer1; /*; (+49)*/ +UCHAR ResponseID0; /*; (+4A)*/ +UCHAR ResponseID1; /*; (+4B)*/ +UCHAR SCSITest0; /*; (+4C)*/ +UCHAR SCSITest1; /*; (+4D)*/ +UCHAR SCSITest2; /*; (+4E)*/ +UCHAR SCSITest3; /*; (+4F)*/ +USHORT SCSIIPDataL; /*; (+50)*/ +USHORT Reser4; /*; (+52)*/ +USHORT SCSIOPDataL; /*; (+54)*/ +USHORT Reser5; /*; (+56)*/ +USHORT SCSIBusDataLin; /*; (+58)*/ +USHORT Reser6; /*; (+5A)*/ +ULONG ScratchB; /*; (+5C)*/ +ULONG ScratchC; /*; (+60)*/ +ULONG ScratchD; /*; (+64)*/ +ULONG ScratchE; /*; (+68)*/ +ULONG ScratchF; /*; (+6C)*/ +ULONG ScratchG; /*; (+70)*/ +ULONG ScratchH; /*; (+74)*/ +ULONG ScratchI; /*; (+78)*/ +ULONG ScratchJ; /*; (+7C)*/ +} OperatingReg; +/* +;========================================================== +; NCR 53C825 Registers bit Definition +;========================================================== +*/ +/*; SCSICtrl0 (+00)*/ +#define FULL_ARBITRATION (BIT7+BIT6) +#define START_ARBIT BIT5 +#define SEL_W_SATN BIT4 +#define EN_PARITY_CHK BIT3 +#define SATN_IF_PARITY_ERR BIT1 +#define TARGET_MODE BIT0 + +/*; SCSICtrl1 (+01)*/ +#define ASSERT_RST BIT3 +#define ASSERT_EVEN_P BIT2 +#define START_SCSI_XFER BIT0 + +/*; SCSICtrl2 (+02)*/ +#define DISC_UNEXPECTED BIT7 +#define CHAIN_MODE BIT6 +#define WIDE_SCSI_SEND BIT3 +#define WIDE_SCSI_RECV BIT0 + +/*; SCSICtrl3 (+03)*/ +#define EN_FAST_20 BIT7 +#define SYNC_CLK_F4 (BIT6+BIT4) +#define SYNC_CLK_F3 BIT6 +#define SYNC_CLK_F2 (BIT5+BIT4) +#define SYNC_CLK_F1_5 BIT5 +#define SYNC_CLK_F1 BIT4 +#define EN_WIDE_SCSI BIT3 +#define ASYNC_CLK_F4 (BIT2+BIT0) +#define ASYNC_CLK_F3 BIT2 +#define ASYNC_CLK_F2 (BIT1+BIT0) +#define ASYNC_CLK_F1_5 BIT1 +#define ASYNC_CLK_F1 BIT0 + +/*; SCSIChipID (+04)*/ +#define ENABLE_RESEL BIT6 +#define ENABLE_SEL BIT5 +#define CHIP_ID_MASK 0FH + +/*; SCSIXfer (+05)*/ +#define PERIOD_MASK (BIT7+BIT6+BIT5) +#define SYNC_PERIOD_F11 (BIT7+BIT6+BIT5) +#define SYNC_PERIOD_F10 (BIT7+BIT6) +#define SYNC_PERIOD_F9 (BIT7+BIT5) +#define SYNC_PERIOD_F8 BIT7 +#define SYNC_PERIOD_F7 (BIT6+BIT5) +#define SYNC_PERIOD_F6 BIT6 +#define SYNC_PERIOD_F5 BIT5 +#define SYNC_PERIOD_F4 0 +#define OFFSET_MASK (BIT4+BIT3+BIT2+BIT1+BIT0) +#define SYNC_OFFSET_16 BIT4 +#define SYNC_OFFSET_8 BIT3 +#define SYNC_OFFSET_7 (BIT2+BIT1+BIT0) +#define SYNC_OFFSET_6 (BIT2+BIT1) +#define SYNC_OFFSET_5 (BIT2+BIT0) +#define SYNC_OFFSET_4 BIT2 +#define SYNC_OFFSET_3 (BIT1+BIT0) +#define SYNC_OFFSET_2 BIT1 +#define SYNC_OFFSET_1 BIT0 +#define SYNC_OFFSET_0 0 +#define ASYNCHRONOUS SYNC_OFFSET_0 +/* +; SCSIDestID (+06) + +; GeneralPReg (+07) + +; SCSI1stByteR (+08) +*/ +/*; SCSIOPCtrlL (+09)*/ +#define ASSERT_REQ BIT7 +#define ASSERT_ACK BIT6 +#define ASSERT_BSY BIT5 +#define ASSERT_SEL BIT4 +#define ASSERT_ATN BIT3 +#define ASSERT_MSG BIT2 +#define ASSERT_C_D BIT1 +#define ASSERT_I_O BIT0 + +/*; SCSISelID (+0A) Read*/ +#define SCSI_ID_VALID BIT7 + +/*; SCSIBusCtrlLin (+0B) Read*/ +#define REQ_SIGNAL BIT7 +#define ACK_SIGNAL BIT6 +#define BSY_SIGNAL BIT5 +#define SEL_SIGNAL BIT4 +#define ATN_SIGNAL BIT3 +#define MSG_SIGNAL BIT2 +#define C_D_SIGNAL BIT1 +#define I_O_SIGNAL BIT0 + +/*; DMAStatus (+0C) Read*/ +#define DMA_FIFO_EMPTY BIT7 +#define MASTER_PARITY_ERR BIT6 +#define BUS_FAULT BIT5 +#define ABORT_ BIT4 +#define SINGLE_STEP_INT BIT3 +#define SCRIPTS_INT BIT2 +#define ILLEGAL_INSTRUC BIT0 + +/*; SCSIStatus0 (+0D) Read*/ +#define SIDL_LSB_FULL BIT7 +#define SODR_LSB_FULL BIT6 +#define SODL_LSB_FULL BIT5 +#define IN_ARBITRATION BIT4 +#define LOST_ARBITRATION BIT3 +#define WIN_ARBITRATION BIT2 +#define RST_SIGNAL BIT1 +#define PARITY_SIGNAL BIT0 + +/*; SCSIStatus1 (+0E) Read*/ +#define SCSI_FIFO_MASK (BIT7+BIT6+BIT5+BIT4) + +/*; SCSIStatus2 (+0F) Read*/ +#define SIDL_MSB_FULL BIT7 +#define SODR_MSB_FULL BIT6 +#define SODL_MSB_FULL BIT5 +#define SCSI_FIFO_MSB BIT4 + +/*; DataStrucAddr (+10)*/ + +/*; InterruptStatus (+14)*/ +#define ABORT_OP BIT7 +#define SOFTWARE_RST BIT6 +#define SIGNAL_PROC BIT5 +#define SEMAPHORE BIT4 +#define CONNECTED BIT3 +#define INT_ON_FLY BIT2 +#define SCSI_INT_PENDING BIT1 +#define DMA_INT_PENDING BIT0 + +/*; ChipTest0 (+18)*/ +/*; ChipTest1 (+19)*/ +#define FIFO_BYTE_EMPTY (BIT7+BIT6+BIT5+BIT4) +#define FIFO_BYTE_FULL (BIT3+BIT2+BIT1+BIT0) + +/*; ChipTest2 (+1A)*/ +#define READ_DIR BIT7 +#define WRITE_DIR 0 +#define SIGNAL_PROC_ BIT6 +#define CFG_AS_IO_MAP BIT5 +#define CFG_AS_MEM_MAP BIT4 +#define SCRATCHAB_AS_BASE BIT3 +#define TRUE_EOP BIT2 +#define INTERNAL_DREQ BIT1 +#define INTERNAL_DACK BIT0 + +/*; ChipTest3 (+1B)*/ +#define CHIP_REV_MASK (BIT7+BIT6+BIT5+BIT4) +#define FLUSH_DMA_FIFO BIT3 +#define CLR_DMA_FIFO BIT2 +#define FETCH_PIN_MODE BIT1 +#define WRT_EN_INVALIDATE BIT0 + +/*; TempStack (+1C)*/ + +/*; DMAFIFO (+20)*/ +/*; 2 upper bits in ChipTest5*/ + +/*; ChipTest4 (+21)*/ +#define BURST_DISABLE BIT7 + +/*; ChipTest5 (+22) */ +#define EN_DMA_FIFO_536 BIT5 +#define BURST_LEN_MSB BIT2 +#define DMAFIFO_MS2B (BIT1+BIT0) +/* +; ChipTest6 (+23) +; DMAByteCnt (+24) +; DMACommand (+27) +; DMANextAddr (+28) +; DMAScriptsPtr (+2C) +; DMAScriptPtrSav (+30) +*/ +/*; ScratchA (+34)*/ +#define COMPLETED_OK_ BIT0 +#define RE_SELECTED_ BIT1 +#define OVER_RUN_ BIT2 + +/*; DMAMode (+38)*/ +#define BURST_LEN16 (BIT7+BIT6) +#define BURST_LEN8 BIT7 +#define BURST_LEN4 BIT6 +#define BURST_LEN2 0 +#define SRC_IO_MAP BIT5 +#define SRC_MEM_MAP 0 +#define DEST_IO_MAP BIT4 +#define DEST_MEM_MAP 0 +#define EN_READ_LINE BIT3 +#define EN_READ_MULTIPLE BIT2 +#define BURST_OPCODE_FETCH BIT1 +#define MANUAL_START BIT0 +#define AUTO_START 0 + +/*; DMAIntEnable (+39)*/ +#define EN_MDPE BIT6 +#define EN_BUS_FAULT BIT5 +#define EN_ABORTED BIT4 +#define EN_SINGLE_STEP BIT3 +#define EN_SCRIPT_INT BIT2 +#define EN_ILLEGAL_INST BIT0 + + +/*; DMAWatchDog (+3A)*/ + +/*; DMACtrl (+3B)*/ +#define EN_CACHE_LINE_SIZE BIT7 +#define PRE_FETCH_FLUSH BIT6 +#define EN_PRE_FETCH BIT5 +#define SINGLE_STEP_MIDE BIT4 +#define TOTEM_POLE_IRQ BIT3 +#define OPEN_DRAIN_IRQ 0 +#define START_DMA BIT2 +#define IRQ_DISABLE BIT1 +#define COMPATIBLE_700 BIT0 + +/*; ADDer (+3C)*/ + +/*; SCSIIntEnable0 (+40)*/ +#define EN_PHASE_MISMATCH BIT7 +#define EN_ARB_SEL_DONE BIT6 +#define EN_BE_SELECTED BIT5 +#define EN_BE_RESELECTED BIT4 +#define EN_SCSI_GERROR BIT3 +#define EN_UNEXPECT_DISC BIT2 +#define EN_SCSI_RESET BIT1 +#define EN_PARITY_ERROR BIT0 + +/*; SCSIIntEnable1 (+41)*/ +#define EN_SEL_TIMEOUT BIT2 +#define EN_GENERAL_TIMEOUT BIT1 +#define EN_HANDSHAKE_TIMEOUT BIT0 + +/*; SCSIIntStatus0 (+42)*/ +#define PHASE_MISMATCH BIT7 +#define ARB_SEL_DONE BIT6 +#define BE_SELECTED BIT5 +#define BE_RESELECTED BIT4 +#define SCSI_GERROR BIT3 +#define UNEXPECT_DISC BIT2 +#define SCSI_RESET BIT1 +#define PARITY_ERROR BIT0 + +/*; SCSIIntStatus1 (+43)*/ +#define SEL_TIMEOUT BIT2 +#define GENERAL_TIMEOUT BIT1 +#define HANDSHAKE_TIMEOUT BIT0 +/* +; SCSILongParity (+44) +; WideResiData (+45) +; MemAccessCtrl (+46) +; GeneralPCtrl (+47) +*/ +/*; SCSITimer0 (+48)*/ +#define HTH_TO_DISABLE 0 +#define SEL_TO_204ms (BIT3+BIT2) +/* +; SCSITimer1 (+49) +; ResponseID0 (+4A) +; ResponseID1 (+4B) +; SCSITest0 (+4C) +; SCSITest1 (+4D) +; SCSITest2 (+4E) +*/ +/*; SCSITest3 (+4F)*/ +#define ACTIVE_NEGATION_ BIT7 +#define FIFO_TEST_READ BIT6 +#define HALT_SCSI_CLK BIT5 +#define DIS_SINGLE_INIT BIT4 +#define TIMER_TEST_MODE BIT2 +#define CLR_SCSI_FIFO BIT1 +#define FIFO_TEST_WRITE BIT0 +/* +; SCSIIPDataL (+50) +; SCSIOPDataL (+54) +; SCSIBusDataLin (+58) +; ScratchB (+5C) +; ScratchC (+60) +; ScratchD (+64) +; ScratchE (+68) +; ScratchF (+6C) +; ScratchG (+70) +; ScratchH (+74) +; ScratchI (+78) +; ScratchJ (+7C) +*/ + +/*********************************************************************** + * NCR53C825 I/O Address Map + * base address is stored at offset 0x10 in the PCI configuration space + *----------------------------------------------------------------------- + * SCSI Core Registers Offset + ***********************************************************************/ +#define SCNTL0 0x00 /* Scsi Control 0 R/W */ +#define SCNTL1 0x01 /* Scsi Control 1 R/W */ +#define SCNTL2 0x02 /* Scsi Control 2 R/W */ +#define SCNTL3 0x03 /* Scsi Control 3 R/W */ +#define SCID 0x04 /* Scsi Chip ID R/W */ +#define SXFER 0x05 /* Scsi Transfer R/W */ +#define SDID 0x06 /* Scsi Destination ID R/W */ +#define GPREG 0x07 /* General Purpose Bits R/W */ +#define SFBR 0x08 /* Scsi First Byte Received R/W */ +#define SOCL 0x09 /* Scsi Output Control Latch R/W */ +#define SSID 0x0A /* Scsi Selector ID R */ +#define SBCL 0x0B /* Scsi Bus Control Lines R/W */ +#define DSTAT 0x0C /* DMA Status R */ +#define SSTAT0 0x0D /* Scsi Status 0 R */ +#define SSTAT1 0x0E /* Scsi Status 1 R */ +#define SSTAT2 0x0F /* Scsi Status 2 R */ +#define DSA 0x10 /* Data Structure Address R/W */ +#define ISTAT 0x14 /* Interrupt Status R/W */ +#define RES15 0x15 /* Reserved */ +#define CTEST0 0x18 /* Reserved */ +#define CTEST1 0x19 /* Chip Test 1 R/W */ +#define CTEST2 0x1A /* Chip Test 2 R */ +#define CTEST3 0x1B /* Chip Test 3 R */ +#define TEMP 0x1C /* Temperary Stack R/W */ +#define DFIFO 0x20 /* DMA FIFO R/W */ +#define CTEST4 0x21 /* Chip Test 4 R/W */ +#define CTEST5 0x22 /* Chip Test 5 R/W */ +#define CTEST6 0x23 /* Chip Test 6 R/W */ +#define DBC 0x24 /* DMA Byte Counter R/W */ +#define DCMD 0x27 /* DMA Command R/W */ +#define DNAD 0x28 /* DMA Next Address For Data R/W */ +#define DSP 0x2C /* DMA SCIPTS Pointer R/W */ +#define DSPS 0x30 /* DMA SCIPTS Pointer Saves R/W */ +#define SCRATCHA 0x34 /* General Purpose Scratch Pad A R/W */ +#define DMODE 0x38 /* DMA Mode R/W */ +#define DIEN 0x39 /* DMA Interrupt Enable R/W */ +#define DWT 0x3A /* DMA Watchdog Timer R/W */ +#define DCNTL 0x3B /* DMA Control R/W */ +#define ADDER 0x3C /* Sum output of Internal adder R */ +#define SIEN0 0x40 /* Scsi Interrupt Enable 0 R/W */ +#define SIEN1 0x41 /* Scsi Interrupt Enable 1 R/W */ +#define SIST0 0x42 /* Scsi Interrupt Status 0 R */ +#define SIST1 0x43 /* Scsi Interrupt Status 1 R */ +#define SLPAR 0x44 /* Scsi Longitudinal Parity R/W */ +#define SWIDE 0x45 /* Reserved */ +#define MACNTL 0x46 /* Memory Access Control R/W */ +#define GPCNTL 0x47 /* General Purpose Control R/W */ +#define STIME0 0x48 /* Scsi Timer 0 R/W */ +#define STIME1 0x49 /* Scsi Timer 1 R/W */ +#define RESPID0 0x4A /* Response ID 0 R/W */ +#define RESPID1 0x4B /* Response ID 1 R/W */ +#define STEST0 0x4C /* Scsi Test 0 R */ +#define STEST1 0x4D /* Scsi Test 1 R */ +#define STEST2 0x4E /* Scsi Test 2 R/W */ +#define STEST3 0x4F /* Scsi Test 3 R/W */ +#define SIDL 0x50 /* Scsi Input Data Latch R */ +#define RES51 0x51 /* Reserved */ +#define SODL 0x54 /* Scsi Output Data Latch R/W */ +#define RES55 0x55 /* Reserved */ +#define SBDL 0x58 /* Scsi Bus Data Lines R */ +#define RES59 0x59 /* Reserved */ +#define SCRATCHB 0x5C /* General Purpose Scratch Pad B R/W */ + + +/* +;========================================================== +; structure for 53C825A register ( I/O ) +;========================================================== +*/ +#define __scntl0 0 +#define __scntl1 1 +#define __scntl2 2 +#define __scntl3 3 +#define __scid 4 +#define __sxfer 5 +#define __sdid 6 +#define __gpreg 7 +#define __sfbr 8 +#define __socl 9 +#define __ssid 0x0A +#define __sbcl 0x0B +#define __dstat 0x0C +#define __sstat0 0x0D +#define __sstat1 0x0E +#define __sstat2 0x0F +#define __dsa 0x10 +#define __istat 0x14 +/*#define 0x15*/ +#define __ctest0 0x18 +#define __ctest1 0x19 +#define __ctest2 0x1A +#define __ctest3 0x1B +#define __temp 0x1C +#define __dfifo 0x20 +#define __ctest4 0x21 +#define __ctest5 0x22 +#define __ctest6 0x23 +#define __dbc 0x24 +#define __dcmd 0x27 +#define __dnad 0x28 +#define __dsp 0x2C +#define __dsps 0x30 +#define __scratcha 0x34 +#define __dmode 0x38 +#define __dien 0x39 +#define __dwt 0x3A +#define __dcntl 0x3B +#define __adder 0x3C +#define __sien0 0x40 +#define __sien1 0x41 +#define __sist0 0x42 +#define __sist1 0x43 +#define __slpar 0x44 +#define __swide 0x45 +#define __mactrl 0x46 +#define __gpctrl 0x47 +#define __stime0 0x48 +#define __stime1 0x49 +#define __respid0 0x4A +#define __respid1 0x4B +#define __stest0 0x4C +#define __stest1 0x4D +#define __stest2 0x4E +#define __stest3 0x4F +#define __sidl 0x50 /*; For wide SCSI, this register*/ +/*#define 0x51 ; contains 2 bytes*/ +#define __sodl 0x54 +/*#define 0x55*/ +#define __sbdl 0x58 +/*#define 0x59*/ +#define __scratchb 0x5C +/* +;========================================================== +; Script interrupt code(Vector) +;========================================================== +*/ +#define __COMPLETE 0 /*; command complete*/ +#define __RESELECTED 1 /*; reselected, without MESSAGE*/ +#define __RESELECTED1 2 /*; reselected with idenntify bytes*/ +#define __RESELECTEDT 3 /*; reselected with TAGS*/ +#define __DISCONNECTED 4 /*; disconnected*/ +#define __MSGEXTEND 5 /*; Extended msgin*/ +#define __SIGNAL 6 /*; signaled, need to clear SIG*/ +#define __MSGUNKNOWN 7 /*; Unknown message*/ +#define __MSGOUT 8 /*; Message out phase detected*/ +#define __FATALERROR 9 /*; Fatal error*/ +#define __MSGSYNC 10 /*; Sync nego input*/ +#define __MSGWIDE 11 /*; Wide nego input*/ +#define __RESTOREPTR 12 /*; restore pointer received*/ +#define __MSGREJECT 13 /*; Reject message received*/ +#define __DEBUG 14 /*; For debug*/ + + +#define DC390W_read8(address) \ + inb(DC390W_ioport + (address))) + +#define DC390W_read16(address) \ + inw(DC390W_ioport + (address))) + +#define DC390W_read32(address) \ + inl(DC390W_ioport + (address))) + +#define DC390W_write8(address,value) \ + outb((value), DC390W_ioport + (address))) + +#define DC390W_write16(address,value) \ + outw((value), DC390W_ioport + (address))) + +#define DC390W_write32(address,value) \ + outl((value), DC390W_ioport + (address))) + +#endif /* TMSCSIW_H */ diff --git a/linux/src/drivers/scsi/u14-34f.c b/linux/src/drivers/scsi/u14-34f.c new file mode 100644 index 00000000..cad88bc2 --- /dev/null +++ b/linux/src/drivers/scsi/u14-34f.c @@ -0,0 +1,1995 @@ +/* + * u14-34f.c - Low-level driver for UltraStor 14F/34F SCSI host adapters. + * + * 26 Jul 1998 Rev. 4.33 for linux 2.0.35 and 2.1.111 + * Added command line option (et:[y|n]) to use the existing + * translation (returned by scsicam_bios_param) as disk geometry. + * The default is et:n, which uses the disk geometry jumpered + * on the board. + * The default value et:n is compatible with all previous revisions + * of this driver. + * + * 28 May 1998 Rev. 4.32 for linux 2.0.33 and 2.1.104 + * Increased busy timeout from 10 msec. to 200 msec. while + * processing interrupts. + * + * 18 May 1998 Rev. 4.31 for linux 2.0.33 and 2.1.102 + * Improved abort handling during the eh recovery process. + * + * 13 May 1998 Rev. 4.30 for linux 2.0.33 and 2.1.101 + * The driver is now fully SMP safe, including the + * abort and reset routines. + * Added command line options (eh:[y|n]) to choose between + * new_eh_code and the old scsi code. + * If linux version >= 2.1.101 the default is eh:y, while the eh + * option is ignored for previous releases and the old scsi code + * is used. + * + * 18 Apr 1998 Rev. 4.20 for linux 2.0.33 and 2.1.97 + * Reworked interrupt handler. + * + * 11 Apr 1998 rev. 4.05 for linux 2.0.33 and 2.1.95 + * Major reliability improvement: when a batch with overlapping + * requests is detected, requests are queued one at a time + * eliminating any possible board or drive reordering. + * + * 10 Apr 1998 rev. 4.04 for linux 2.0.33 and 2.1.95 + * Improved SMP support (if linux version >= 2.1.95). + * + * 9 Apr 1998 rev. 4.03 for linux 2.0.33 and 2.1.94 + * Performance improvement: when sequential i/o is detected, + * always use direct sort instead of reverse sort. + * + * 4 Apr 1998 rev. 4.02 for linux 2.0.33 and 2.1.92 + * io_port is now unsigned long. + * + * 17 Mar 1998 rev. 4.01 for linux 2.0.33 and 2.1.88 + * Use new scsi error handling code (if linux version >= 2.1.88). + * Use new interrupt code. + * + * 12 Sep 1997 rev. 3.11 for linux 2.0.30 and 2.1.55 + * Use of udelay inside the wait loops to avoid timeout + * problems with fast cpus. + * Removed check about useless calls to the interrupt service + * routine (reported on SMP systems only). + * At initialization time "sorted/unsorted" is displayed instead + * of "linked/unlinked" to reinforce the fact that "linking" is + * nothing but "elevator sorting" in the actual implementation. + * + * 17 May 1997 rev. 3.10 for linux 2.0.30 and 2.1.38 + * Use of serial_number_at_timeout in abort and reset processing. + * Use of the __initfunc and __initdata macro in setup code. + * Minor cleanups in the list_statistics code. + * + * 24 Feb 1997 rev. 3.00 for linux 2.0.29 and 2.1.26 + * When loading as a module, parameter passing is now supported + * both in 2.0 and in 2.1 style. + * Fixed data transfer direction for some SCSI opcodes. + * Immediate acknowledge to request sense commands. + * Linked commands to each disk device are now reordered by elevator + * sorting. Rare cases in which reordering of write requests could + * cause wrong results are managed. + * + * 18 Jan 1997 rev. 2.60 for linux 2.1.21 and 2.0.28 + * Added command line options to enable/disable linked commands + * (lc:[y|n]), old firmware support (of:[y|n]) and to set the max + * queue depth (mq:xx). Default is "u14-34f=lc:n,of:n,mq:8". + * Improved command linking. + * + * 8 Jan 1997 rev. 2.50 for linux 2.1.20 and 2.0.27 + * Added linked command support. + * + * 3 Dec 1996 rev. 2.40 for linux 2.1.14 and 2.0.27 + * Added queue depth adjustment. + * + * 22 Nov 1996 rev. 2.30 for linux 2.1.12 and 2.0.26 + * The list of i/o ports to be probed can be overwritten by the + * "u14-34f=port0,port1,...." boot command line option. + * Scatter/gather lists are now allocated by a number of kmalloc + * calls, in order to avoid the previous size limit of 64Kb. + * + * 16 Nov 1996 rev. 2.20 for linux 2.1.10 and 2.0.25 + * Added multichannel support. + * + * 27 Sep 1996 rev. 2.12 for linux 2.1.0 + * Portability cleanups (virtual/bus addressing, little/big endian + * support). + * + * 09 Jul 1996 rev. 2.11 for linux 2.0.4 + * "Data over/under-run" no longer implies a redo on all targets. + * Number of internal retries is now limited. + * + * 16 Apr 1996 rev. 2.10 for linux 1.3.90 + * New argument "reset_flags" to the reset routine. + * + * 21 Jul 1995 rev. 2.02 for linux 1.3.11 + * Fixed Data Transfer Direction for some SCSI commands. + * + * 13 Jun 1995 rev. 2.01 for linux 1.2.10 + * HAVE_OLD_UX4F_FIRMWARE should be defined for U34F boards when + * the firmware prom is not the latest one (28008-006). + * + * 11 Mar 1995 rev. 2.00 for linux 1.2.0 + * Fixed a bug which prevented media change detection for removable + * disk drives. + * + * 23 Feb 1995 rev. 1.18 for linux 1.1.94 + * Added a check for scsi_register returning NULL. + * + * 11 Feb 1995 rev. 1.17 for linux 1.1.91 + * U14F qualified to run with 32 sglists. + * Now DEBUG_RESET is disabled by default. + * + * 9 Feb 1995 rev. 1.16 for linux 1.1.90 + * Use host->wish_block instead of host->block. + * + * 8 Feb 1995 rev. 1.15 for linux 1.1.89 + * Cleared target_time_out counter while performing a reset. + * + * 28 Jan 1995 rev. 1.14 for linux 1.1.86 + * Added module support. + * Log and do a retry when a disk drive returns a target status + * different from zero on a recovered error. + * Auto detects if U14F boards have an old firmware revision. + * Max number of scatter/gather lists set to 16 for all boards + * (most installation run fine using 33 sglists, while other + * has problems when using more then 16). + * + * 16 Jan 1995 rev. 1.13 for linux 1.1.81 + * Display a message if check_region detects a port address + * already in use. + * + * 15 Dec 1994 rev. 1.12 for linux 1.1.74 + * The host->block flag is set for all the detected ISA boards. + * + * 30 Nov 1994 rev. 1.11 for linux 1.1.68 + * Redo i/o on target status CHECK_CONDITION for TYPE_DISK only. + * Added optional support for using a single board at a time. + * + * 14 Nov 1994 rev. 1.10 for linux 1.1.63 + * + * 28 Oct 1994 rev. 1.09 for linux 1.1.58 Final BETA release. + * 16 Jul 1994 rev. 1.00 for linux 1.1.29 Initial ALPHA release. + * + * This driver is a total replacement of the original UltraStor + * scsi driver, but it supports ONLY the 14F and 34F boards. + * It can be configured in the same kernel in which the original + * ultrastor driver is configured to allow the original U24F + * support. + * + * Multiple U14F and/or U34F host adapters are supported. + * + * Copyright (C) 1994-1998 Dario Ballabio (dario@milano.europe.dg.com) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that redistributions of source + * code retain the above copyright notice and this comment without + * modification. + * + * WARNING: if your 14/34F board has an old firmware revision (see below) + * you must change "#undef" into "#define" in the following + * statement. + */ +#undef HAVE_OLD_UX4F_FIRMWARE +/* + * The UltraStor 14F, 24F, and 34F are a family of intelligent, high + * performance SCSI-2 host adapters. + * Here is the scoop on the various models: + * + * 14F - ISA first-party DMA HA with floppy support and WD1003 emulation. + * 24F - EISA Bus Master HA with floppy support and WD1003 emulation. + * 34F - VESA Local-Bus Bus Master HA (no WD1003 emulation). + * + * This code has been tested with up to two U14F boards, using both + * firmware 28004-005/38004-004 (BIOS rev. 2.00) and the latest firmware + * 28004-006/38004-005 (BIOS rev. 2.01). + * + * The latest firmware is required in order to get reliable operations when + * clustering is enabled. ENABLE_CLUSTERING provides a performance increase + * up to 50% on sequential access. + * + * Since the Scsi_Host_Template structure is shared among all 14F and 34F, + * the last setting of use_clustering is in effect for all of these boards. + * + * Here a sample configuration using two U14F boards: + * + U14F0: ISA 0x330, BIOS 0xc8000, IRQ 11, DMA 5, SG 32, MB 16, of:n, lc:y, mq:8. + U14F1: ISA 0x340, BIOS 0x00000, IRQ 10, DMA 6, SG 32, MB 16, of:n, lc:y, mq:8. + * + * The boot controller must have its BIOS enabled, while other boards can + * have their BIOS disabled, or enabled to an higher address. + * Boards are named Ux4F0, Ux4F1..., according to the port address order in + * the io_port[] array. + * + * The following facts are based on real testing results (not on + * documentation) on the above U14F board. + * + * - The U14F board should be jumpered for bus on time less or equal to 7 + * microseconds, while the default is 11 microseconds. This is order to + * get acceptable performance while using floppy drive and hard disk + * together. The jumpering for 7 microseconds is: JP13 pin 15-16, + * JP14 pin 7-8 and pin 9-10. + * The reduction has a little impact on scsi performance. + * + * - If scsi bus length exceeds 3m., the scsi bus speed needs to be reduced + * from 10Mhz to 5Mhz (do this by inserting a jumper on JP13 pin 7-8). + * + * - If U14F on board firmware is older than 28004-006/38004-005, + * the U14F board is unable to provide reliable operations if the scsi + * request length exceeds 16Kbyte. When this length is exceeded the + * behavior is: + * - adapter_status equal 0x96 or 0xa3 or 0x93 or 0x94; + * - adapter_status equal 0 and target_status equal 2 on for all targets + * in the next operation following the reset. + * This sequence takes a long time (>3 seconds), so in the meantime + * the SD_TIMEOUT in sd.c could expire giving rise to scsi aborts + * (SD_TIMEOUT has been increased from 3 to 6 seconds in 1.1.31). + * Because of this I had to DISABLE_CLUSTERING and to work around the + * bus reset in the interrupt service routine, returning DID_BUS_BUSY + * so that the operations are retried without complains from the scsi.c + * code. + * Any reset of the scsi bus is going to kill tape operations, since + * no retry is allowed for tapes. Bus resets are more likely when the + * scsi bus is under heavy load. + * Requests using scatter/gather have a maximum length of 16 x 1024 bytes + * when DISABLE_CLUSTERING is in effect, but unscattered requests could be + * larger than 16Kbyte. + * + * The new firmware has fixed all the above problems. + * + * For U34F boards the latest bios prom is 38008-002 (BIOS rev. 2.01), + * the latest firmware prom is 28008-006. Older firmware 28008-005 has + * problems when using more then 16 scatter/gather lists. + * + * The list of i/o ports to be probed can be totally replaced by the + * boot command line option: "u14-34f=port0,port1,port2,...", where the + * port0, port1... arguments are ISA/VESA addresses to be probed. + * For example using "u14-34f=0x230,0x340", the driver probes only the two + * addresses 0x230 and 0x340 in this order; "u14-34f=0" totally disables + * this driver. + * + * After the optional list of detection probes, other possible command line + * options are: + * + * eh:y use new scsi code (linux 2.2 only); + * eh:n use old scsi code; + * et:y use disk geometry returned by scsicam_bios_param; + * et:n use disk geometry jumpered on the board; + * lc:y enables linked commands; + * lc:n disables linked commands; + * of:y enables old firmware support; + * of:n disables old firmware support; + * mq:xx set the max queue depth to the value xx (2 <= xx <= 8). + * + * The default value is: "u14-34f=lc:n,of:n,mq:8,et:n". + * An example using the list of detection probes could be: + * "u14-34f=0x230,0x340,lc:y,of:n,mq:4,eh:n,et:n". + * + * When loading as a module, parameters can be specified as well. + * The above example would be (use 1 in place of y and 0 in place of n): + * + * modprobe u14-34f io_port=0x230,0x340 linked_comm=1 have_old_firmware=0 \ + * max_queue_depth=4 use_new_eh_code=0 ext_tran=0 + * + * ---------------------------------------------------------------------------- + * In this implementation, linked commands are designed to work with any DISK + * or CD-ROM, since this linking has only the intent of clustering (time-wise) + * and reordering by elevator sorting commands directed to each device, + * without any relation with the actual SCSI protocol between the controller + * and the device. + * If Q is the queue depth reported at boot time for each device (also named + * cmds/lun) and Q > 2, whenever there is already an active command to the + * device all other commands to the same device (up to Q-1) are kept waiting + * in the elevator sorting queue. When the active command completes, the + * commands in this queue are sorted by sector address. The sort is chosen + * between increasing or decreasing by minimizing the seek distance between + * the sector of the commands just completed and the sector of the first + * command in the list to be sorted. + * Trivial math assures that the unsorted average seek distance when doing + * random seeks over S sectors is S/3. + * When (Q-1) requests are uniformly distributed over S sectors, the average + * distance between two adjacent requests is S/((Q-1) + 1), so the sorted + * average seek distance for (Q-1) random requests over S sectors is S/Q. + * The elevator sorting hence divides the seek distance by a factor Q/3. + * The above pure geometric remarks are valid in all cases and the + * driver effectively reduces the seek distance by the predicted factor + * when there are Q concurrent read i/o operations on the device, but this + * does not necessarily results in a noticeable performance improvement: + * your mileage may vary.... + * + * Note: command reordering inside a batch of queued commands could cause + * wrong results only if there is at least one write request and the + * intersection (sector-wise) of all requests is not empty. + * When the driver detects a batch including overlapping requests + * (a really rare event) strict serial (pid) order is enforced. + * ---------------------------------------------------------------------------- + * + * The boards are named Ux4F0, Ux4F1,... according to the detection order. + * + * In order to support multiple ISA boards in a reliable way, + * the driver sets host->wish_block = TRUE for all ISA boards. + */ + +#include <linux/version.h> + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) +#define MAX_INT_PARAM 10 + +#if defined(MODULE) +#include <linux/module.h> + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,26) +MODULE_PARM(io_port, "1-" __MODULE_STRING(MAX_INT_PARAM) "i"); +MODULE_PARM(linked_comm, "i"); +MODULE_PARM(have_old_firmware, "i"); +MODULE_PARM(link_statistics, "i"); +MODULE_PARM(max_queue_depth, "i"); +MODULE_PARM(use_new_eh_code, "i"); +MODULE_PARM(ext_tran, "i"); +MODULE_AUTHOR("Dario Ballabio"); +#endif + +#endif + +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include <linux/proc_fs.h> +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" +#include <asm/dma.h> +#include <asm/irq.h> +#include "u14-34f.h" +#include <linux/stat.h> +#include <linux/config.h> + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,36) +#include <linux/init.h> +#else +#define __initfunc(A) A +#define __initdata +#define __init +#endif + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) +#include <asm/spinlock.h> +#define IRQ_FLAGS +#define IRQ_LOCK +#define IRQ_LOCK_SAVE +#define IRQ_UNLOCK +#define IRQ_UNLOCK_RESTORE +#define SPIN_FLAGS unsigned long spin_flags; +#define SPIN_LOCK spin_lock_irq(&io_request_lock); +#define SPIN_LOCK_SAVE spin_lock_irqsave(&io_request_lock, spin_flags); +#define SPIN_UNLOCK spin_unlock_irq(&io_request_lock); +#define SPIN_UNLOCK_RESTORE \ + spin_unlock_irqrestore(&io_request_lock, spin_flags); +static int use_new_eh_code = TRUE; +#else +#define IRQ_FLAGS unsigned long irq_flags; +#define IRQ_LOCK cli(); +#define IRQ_LOCK_SAVE do {save_flags(irq_flags); cli();} while (0); +#define IRQ_UNLOCK sti(); +#define IRQ_UNLOCK_RESTORE do {restore_flags(irq_flags);} while (0); +#define SPIN_FLAGS +#define SPIN_LOCK +#define SPIN_LOCK_SAVE +#define SPIN_UNLOCK +#define SPIN_UNLOCK_RESTORE +static int use_new_eh_code = FALSE; +#endif + +struct proc_dir_entry proc_scsi_u14_34f = { + PROC_SCSI_U14_34F, 6, "u14_34f", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +/* Values for the PRODUCT_ID ports for the 14/34F */ +#define PRODUCT_ID1 0x56 +#define PRODUCT_ID2 0x40 /* NOTE: Only upper nibble is used */ + +/* Subversion values */ +#define ISA 0 +#define ESA 1 + +#define OP_HOST_ADAPTER 0x1 +#define OP_SCSI 0x2 +#define OP_RESET 0x4 +#define DTD_SCSI 0x0 +#define DTD_IN 0x1 +#define DTD_OUT 0x2 +#define DTD_NONE 0x3 +#define HA_CMD_INQUIRY 0x1 +#define HA_CMD_SELF_DIAG 0x2 +#define HA_CMD_READ_BUFF 0x3 +#define HA_CMD_WRITE_BUFF 0x4 + +#undef DEBUG_LINKED_COMMANDS +#undef DEBUG_DETECT +#undef DEBUG_INTERRUPT +#undef DEBUG_RESET +#undef DEBUG_GENERATE_ERRORS +#undef DEBUG_GENERATE_ABORTS +#undef DEBUG_GEOMETRY + +#define MAX_ISA 3 +#define MAX_VESA 1 +#define MAX_EISA 0 +#define MAX_PCI 0 +#define MAX_BOARDS (MAX_ISA + MAX_VESA + MAX_EISA + MAX_PCI) +#define MAX_CHANNEL 1 +#define MAX_LUN 8 +#define MAX_TARGET 8 +#define MAX_MAILBOXES 16 +#define MAX_SGLIST 32 +#define MAX_SAFE_SGLIST 16 +#define MAX_INTERNAL_RETRIES 64 +#define MAX_CMD_PER_LUN 2 +#define MAX_TAGGED_CMD_PER_LUN (MAX_MAILBOXES - MAX_CMD_PER_LUN) + +#define SKIP ULONG_MAX +#define FALSE 0 +#define TRUE 1 +#define FREE 0 +#define IN_USE 1 +#define LOCKED 2 +#define IN_RESET 3 +#define IGNORE 4 +#define READY 5 +#define ABORTING 6 +#define NO_DMA 0xff +#define MAXLOOP 10000 + +#define REG_LCL_MASK 0 +#define REG_LCL_INTR 1 +#define REG_SYS_MASK 2 +#define REG_SYS_INTR 3 +#define REG_PRODUCT_ID1 4 +#define REG_PRODUCT_ID2 5 +#define REG_CONFIG1 6 +#define REG_CONFIG2 7 +#define REG_OGM 8 +#define REG_ICM 12 +#define REGION_SIZE 13 +#define BSY_ASSERTED 0x01 +#define IRQ_ASSERTED 0x01 +#define CMD_RESET 0xc0 +#define CMD_OGM_INTR 0x01 +#define CMD_CLR_INTR 0x01 +#define CMD_ENA_INTR 0x81 +#define ASOK 0x00 +#define ASST 0x91 + +#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr)[0]) +#define YESNO(a) ((a) ? 'y' : 'n') +#define TLDEV(type) ((type) == TYPE_DISK || (type) == TYPE_ROM) + +#define PACKED __attribute__((packed)) + +struct sg_list { + unsigned int address; /* Segment Address */ + unsigned int num_bytes; /* Segment Length */ + }; + +/* MailBox SCSI Command Packet */ +struct mscp { + unsigned char opcode: 3; /* type of command */ + unsigned char xdir: 2; /* data transfer direction */ + unsigned char dcn: 1; /* disable disconnect */ + unsigned char ca: 1; /* use cache (if available) */ + unsigned char sg: 1; /* scatter/gather operation */ + unsigned char target: 3; /* SCSI target id */ + unsigned char channel: 2; /* SCSI channel number */ + unsigned char lun: 3; /* SCSI logical unit number */ + unsigned int data_address PACKED; /* transfer data pointer */ + unsigned int data_len PACKED; /* length in bytes */ + unsigned int link_address PACKED; /* for linking command chains */ + unsigned char clink_id; /* identifies command in chain */ + unsigned char use_sg; /* (if sg is set) 8 bytes per list */ + unsigned char sense_len; + unsigned char scsi_cdbs_len; /* 6, 10, or 12 */ + unsigned char scsi_cdbs[12]; /* SCSI commands */ + unsigned char adapter_status; /* non-zero indicates HA error */ + unsigned char target_status; /* non-zero indicates target error */ + unsigned int sense_addr PACKED; + Scsi_Cmnd *SCpnt; + unsigned int index; /* cp index */ + struct sg_list *sglist; + }; + +struct hostdata { + struct mscp cp[MAX_MAILBOXES]; /* Mailboxes for this board */ + unsigned int cp_stat[MAX_MAILBOXES]; /* FREE, IN_USE, LOCKED, IN_RESET */ + unsigned int last_cp_used; /* Index of last mailbox used */ + unsigned int iocount; /* Total i/o done for this board */ + int board_number; /* Number of this board */ + char board_name[16]; /* Name of this board */ + char board_id[256]; /* data from INQUIRY on this board */ + int in_reset; /* True if board is doing a reset */ + int target_to[MAX_TARGET][MAX_CHANNEL]; /* N. of timeout errors on target */ + int target_redo[MAX_TARGET][MAX_CHANNEL]; /* If TRUE redo i/o on target */ + unsigned int retries; /* Number of internal retries */ + unsigned long last_retried_pid; /* Pid of last retried command */ + unsigned char subversion; /* Bus type, either ISA or ESA */ + unsigned char heads; + unsigned char sectors; + + /* slot != 0 for the U24F, slot == 0 for both the U14F and U34F */ + unsigned char slot; + }; + +static struct Scsi_Host *sh[MAX_BOARDS + 1]; +static const char *driver_name = "Ux4F"; +static char sha[MAX_BOARDS]; + +/* Initialize num_boards so that ihdlr can work while detect is in progress */ +static unsigned int num_boards = MAX_BOARDS; + +static unsigned long io_port[] __initdata = { + + /* Space for MAX_INT_PARAM ports usable while loading as a module */ + SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, SKIP, + SKIP, SKIP, + + /* Possible ISA/VESA ports */ + 0x330, 0x340, 0x230, 0x240, 0x210, 0x130, 0x140, + + /* End of list */ + 0x0 + }; + +#define HD(board) ((struct hostdata *) &sh[board]->hostdata) +#define BN(board) (HD(board)->board_name) + +#define SWAP_BYTE(x) ((unsigned long)( \ + (((unsigned long)(x) & 0x000000ffU) << 24) | \ + (((unsigned long)(x) & 0x0000ff00U) << 8) | \ + (((unsigned long)(x) & 0x00ff0000U) >> 8) | \ + (((unsigned long)(x) & 0xff000000U) >> 24))) + +#if defined(__BIG_ENDIAN) +#define H2DEV(x) SWAP_BYTE(x) +#else +#define H2DEV(x) (x) +#endif + +#define DEV2H(x) H2DEV(x) +#define V2DEV(addr) ((addr) ? H2DEV(virt_to_bus((void *)addr)) : 0) +#define DEV2V(addr) ((addr) ? DEV2H(bus_to_virt((unsigned long)addr)) : 0) + +static void do_interrupt_handler(int, void *, struct pt_regs *); +static void flush_dev(Scsi_Device *, unsigned long, unsigned int, unsigned int); +static int do_trace = FALSE; +static int setup_done = FALSE; +static int link_statistics = 0; +static int ext_tran = FALSE; + +#if defined(HAVE_OLD_UX4F_FIRMWARE) +static int have_old_firmware = TRUE; +#else +static int have_old_firmware = FALSE; +#endif + +#if defined(CONFIG_SCSI_U14_34F_LINKED_COMMANDS) +static int linked_comm = TRUE; +#else +static int linked_comm = FALSE; +#endif + +#if defined(CONFIG_SCSI_U14_34F_MAX_TAGS) +static int max_queue_depth = CONFIG_SCSI_U14_34F_MAX_TAGS; +#else +static int max_queue_depth = MAX_CMD_PER_LUN; +#endif + +static void select_queue_depths(struct Scsi_Host *host, Scsi_Device *devlist) { + Scsi_Device *dev; + int j, ntag = 0, nuntag = 0, tqd, utqd; + IRQ_FLAGS + + IRQ_LOCK_SAVE + j = ((struct hostdata *) host->hostdata)->board_number; + + for(dev = devlist; dev; dev = dev->next) { + + if (dev->host != host) continue; + + if (TLDEV(dev->type) && (dev->tagged_supported || linked_comm)) + ntag++; + else + nuntag++; + } + + utqd = MAX_CMD_PER_LUN; + + tqd = (host->can_queue - utqd * nuntag) / (ntag ? ntag : 1); + + if (tqd > max_queue_depth) tqd = max_queue_depth; + + if (tqd < MAX_CMD_PER_LUN) tqd = MAX_CMD_PER_LUN; + + for(dev = devlist; dev; dev = dev->next) { + char *tag_suffix = "", *link_suffix = ""; + + if (dev->host != host) continue; + + if (TLDEV(dev->type) && (dev->tagged_supported || linked_comm)) + dev->queue_depth = tqd; + else + dev->queue_depth = utqd; + + if (TLDEV(dev->type)) { + if (linked_comm && dev->queue_depth > 2) + link_suffix = ", sorted"; + else + link_suffix = ", unsorted"; + } + + if (dev->tagged_supported && TLDEV(dev->type) && dev->tagged_queue) + tag_suffix = ", tagged"; + else if (dev->tagged_supported && TLDEV(dev->type)) + tag_suffix = ", untagged"; + + printk("%s: scsi%d, channel %d, id %d, lun %d, cmds/lun %d%s%s.\n", + BN(j), host->host_no, dev->channel, dev->id, dev->lun, + dev->queue_depth, link_suffix, tag_suffix); + } + + IRQ_UNLOCK_RESTORE + return; +} + +static inline int wait_on_busy(unsigned long iobase, unsigned int loop) { + + while (inb(iobase + REG_LCL_INTR) & BSY_ASSERTED) { + udelay(1L); + if (--loop == 0) return TRUE; + } + + return FALSE; +} + +static int board_inquiry(unsigned int j) { + struct mscp *cpp; + unsigned int time, limit = 0; + + cpp = &HD(j)->cp[0]; + memset(cpp, 0, sizeof(struct mscp)); + cpp->opcode = OP_HOST_ADAPTER; + cpp->xdir = DTD_IN; + cpp->data_address = V2DEV(HD(j)->board_id); + cpp->data_len = H2DEV(sizeof(HD(j)->board_id)); + cpp->scsi_cdbs_len = 6; + cpp->scsi_cdbs[0] = HA_CMD_INQUIRY; + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: board_inquiry, adapter busy.\n", BN(j)); + return TRUE; + } + + HD(j)->cp_stat[0] = IGNORE; + + /* Clear the interrupt indication */ + outb(CMD_CLR_INTR, sh[j]->io_port + REG_SYS_INTR); + + /* Store pointer in OGM address bytes */ + outl(V2DEV(cpp), sh[j]->io_port + REG_OGM); + + /* Issue OGM interrupt */ + outb(CMD_OGM_INTR, sh[j]->io_port + REG_LCL_INTR); + + SPIN_UNLOCK + IRQ_UNLOCK + time = jiffies; + while ((jiffies - time) < HZ && limit++ < 20000) udelay(100L); + IRQ_LOCK + SPIN_LOCK + + if (cpp->adapter_status || HD(j)->cp_stat[0] != FREE) { + HD(j)->cp_stat[0] = FREE; + printk("%s: board_inquiry, err 0x%x.\n", BN(j), cpp->adapter_status); + return TRUE; + } + + return FALSE; +} + +__initfunc (static inline int port_detect \ + (unsigned long port_base, unsigned int j, Scsi_Host_Template *tpnt)) { + unsigned char irq, dma_channel, subversion, i; + unsigned char in_byte; + char *bus_type, dma_name[16]; + + /* Allowed BIOS base addresses (NULL indicates reserved) */ + void *bios_segment_table[8] = { + NULL, + (void *) 0xc4000, (void *) 0xc8000, (void *) 0xcc000, (void *) 0xd0000, + (void *) 0xd4000, (void *) 0xd8000, (void *) 0xdc000 + }; + + /* Allowed IRQs */ + unsigned char interrupt_table[4] = { 15, 14, 11, 10 }; + + /* Allowed DMA channels for ISA (0 indicates reserved) */ + unsigned char dma_channel_table[4] = { 5, 6, 7, 0 }; + + /* Head/sector mappings */ + struct { + unsigned char heads; + unsigned char sectors; + } mapping_table[4] = { + { 16, 63 }, { 64, 32 }, { 64, 63 }, { 64, 32 } + }; + + struct config_1 { + unsigned char bios_segment: 3; + unsigned char removable_disks_as_fixed: 1; + unsigned char interrupt: 2; + unsigned char dma_channel: 2; + } config_1; + + struct config_2 { + unsigned char ha_scsi_id: 3; + unsigned char mapping_mode: 2; + unsigned char bios_drive_number: 1; + unsigned char tfr_port: 2; + } config_2; + + char name[16]; + + sprintf(name, "%s%d", driver_name, j); + + if(check_region(port_base, REGION_SIZE)) { + printk("%s: address 0x%03lx in use, skipping probe.\n", name, port_base); + return FALSE; + } + + if (inb(port_base + REG_PRODUCT_ID1) != PRODUCT_ID1) return FALSE; + + in_byte = inb(port_base + REG_PRODUCT_ID2); + + if ((in_byte & 0xf0) != PRODUCT_ID2) return FALSE; + + *(char *)&config_1 = inb(port_base + REG_CONFIG1); + *(char *)&config_2 = inb(port_base + REG_CONFIG2); + + irq = interrupt_table[config_1.interrupt]; + dma_channel = dma_channel_table[config_1.dma_channel]; + subversion = (in_byte & 0x0f); + + /* Board detected, allocate its IRQ */ + if (request_irq(irq, do_interrupt_handler, + SA_INTERRUPT | ((subversion == ESA) ? SA_SHIRQ : 0), + driver_name, (void *) &sha[j])) { + printk("%s: unable to allocate IRQ %u, detaching.\n", name, irq); + return FALSE; + } + + if (subversion == ISA && request_dma(dma_channel, driver_name)) { + printk("%s: unable to allocate DMA channel %u, detaching.\n", + name, dma_channel); + free_irq(irq, &sha[j]); + return FALSE; + } + + if (have_old_firmware) tpnt->use_clustering = DISABLE_CLUSTERING; + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + tpnt->use_new_eh_code = use_new_eh_code; +#else + use_new_eh_code = FALSE; +#endif + + sh[j] = scsi_register(tpnt, sizeof(struct hostdata)); + + if (sh[j] == NULL) { + printk("%s: unable to register host, detaching.\n", name); + + free_irq(irq, &sha[j]); + + if (subversion == ISA) free_dma(dma_channel); + + return FALSE; + } + + sh[j]->io_port = port_base; + sh[j]->unique_id = port_base; + sh[j]->n_io_port = REGION_SIZE; + sh[j]->base = bios_segment_table[config_1.bios_segment]; + sh[j]->irq = irq; + sh[j]->sg_tablesize = MAX_SGLIST; + sh[j]->this_id = config_2.ha_scsi_id; + sh[j]->can_queue = MAX_MAILBOXES; + sh[j]->cmd_per_lun = MAX_CMD_PER_LUN; + sh[j]->select_queue_depths = select_queue_depths; + +#if defined(DEBUG_DETECT) + { + unsigned char sys_mask, lcl_mask; + + sys_mask = inb(sh[j]->io_port + REG_SYS_MASK); + lcl_mask = inb(sh[j]->io_port + REG_LCL_MASK); + printk("SYS_MASK 0x%x, LCL_MASK 0x%x.\n", sys_mask, lcl_mask); + } +#endif + + /* Probably a bogus host scsi id, set it to the dummy value */ + if (sh[j]->this_id == 0) sh[j]->this_id = -1; + + /* If BIOS is disabled, force enable interrupts */ + if (sh[j]->base == 0) outb(CMD_ENA_INTR, sh[j]->io_port + REG_SYS_MASK); + + /* Register the I/O space that we use */ + request_region(sh[j]->io_port, sh[j]->n_io_port, driver_name); + + memset(HD(j), 0, sizeof(struct hostdata)); + HD(j)->heads = mapping_table[config_2.mapping_mode].heads; + HD(j)->sectors = mapping_table[config_2.mapping_mode].sectors; + HD(j)->subversion = subversion; + HD(j)->board_number = j; + + if (have_old_firmware) sh[j]->sg_tablesize = MAX_SAFE_SGLIST; + + if (HD(j)->subversion == ESA) { + sh[j]->unchecked_isa_dma = FALSE; + sh[j]->dma_channel = NO_DMA; + sprintf(BN(j), "U34F%d", j); + bus_type = "VESA"; + } + else { + sh[j]->wish_block = TRUE; + sh[j]->unchecked_isa_dma = TRUE; + disable_dma(dma_channel); + clear_dma_ff(dma_channel); + set_dma_mode(dma_channel, DMA_MODE_CASCADE); + enable_dma(dma_channel); + sh[j]->dma_channel = dma_channel; + sprintf(BN(j), "U14F%d", j); + bus_type = "ISA"; + } + + sh[j]->max_channel = MAX_CHANNEL - 1; + sh[j]->max_id = MAX_TARGET; + sh[j]->max_lun = MAX_LUN; + + if (HD(j)->subversion == ISA && !board_inquiry(j)) { + HD(j)->board_id[40] = 0; + + if (strcmp(&HD(j)->board_id[32], "06000600")) { + printk("%s: %s.\n", BN(j), &HD(j)->board_id[8]); + printk("%s: firmware %s is outdated, FW PROM should be 28004-006.\n", + BN(j), &HD(j)->board_id[32]); + sh[j]->hostt->use_clustering = DISABLE_CLUSTERING; + sh[j]->sg_tablesize = MAX_SAFE_SGLIST; + } + } + + if (dma_channel == NO_DMA) sprintf(dma_name, "%s", "BMST"); + else sprintf(dma_name, "DMA %u", dma_channel); + + for (i = 0; i < sh[j]->can_queue; i++) + if (! ((&HD(j)->cp[i])->sglist = kmalloc( + sh[j]->sg_tablesize * sizeof(struct sg_list), + (sh[j]->unchecked_isa_dma ? GFP_DMA : 0) | GFP_ATOMIC))) { + printk("%s: kmalloc SGlist failed, mbox %d, detaching.\n", BN(j), i); + u14_34f_release(sh[j]); + return FALSE; + } + + if (max_queue_depth > MAX_TAGGED_CMD_PER_LUN) + max_queue_depth = MAX_TAGGED_CMD_PER_LUN; + + if (max_queue_depth < MAX_CMD_PER_LUN) max_queue_depth = MAX_CMD_PER_LUN; + + if (j == 0) { + printk("UltraStor 14F/34F: Copyright (C) 1994-1998 Dario Ballabio.\n"); + printk("%s config options -> of:%c, lc:%c, mq:%d, eh:%c, et:%c.\n", + driver_name, YESNO(have_old_firmware), YESNO(linked_comm), + max_queue_depth, YESNO(use_new_eh_code), YESNO(ext_tran)); + } + + printk("%s: %s 0x%03lx, BIOS 0x%05x, IRQ %u, %s, SG %d, MB %d.\n", + BN(j), bus_type, (unsigned long)sh[j]->io_port, (int)sh[j]->base, + sh[j]->irq, dma_name, sh[j]->sg_tablesize, sh[j]->can_queue); + + if (sh[j]->max_id > 8 || sh[j]->max_lun > 8) + printk("%s: wide SCSI support enabled, max_id %u, max_lun %u.\n", + BN(j), sh[j]->max_id, sh[j]->max_lun); + + for (i = 0; i <= sh[j]->max_channel; i++) + printk("%s: SCSI channel %u enabled, host target ID %d.\n", + BN(j), i, sh[j]->this_id); + + return TRUE; +} + +__initfunc (void u14_34f_setup(char *str, int *ints)) { + int i, argc = ints[0]; + char *cur = str, *pc; + + if (argc > 0) { + + if (argc > MAX_INT_PARAM) argc = MAX_INT_PARAM; + + for (i = 0; i < argc; i++) io_port[i] = ints[i + 1]; + + io_port[i] = 0; + setup_done = TRUE; + } + + while (cur && (pc = strchr(cur, ':'))) { + int val = 0, c = *++pc; + + if (c == 'n' || c == 'N') val = FALSE; + else if (c == 'y' || c == 'Y') val = TRUE; + else val = (int) simple_strtoul(pc, NULL, 0); + + if (!strncmp(cur, "lc:", 3)) linked_comm = val; + else if (!strncmp(cur, "of:", 3)) have_old_firmware = val; + else if (!strncmp(cur, "mq:", 3)) max_queue_depth = val; + else if (!strncmp(cur, "ls:", 3)) link_statistics = val; + else if (!strncmp(cur, "eh:", 3)) use_new_eh_code = val; + else if (!strncmp(cur, "et:", 3)) ext_tran = val; + + if ((cur = strchr(cur, ','))) ++cur; + } + + return; +} + +__initfunc (int u14_34f_detect(Scsi_Host_Template *tpnt)) { + unsigned int j = 0, k; + IRQ_FLAGS + + IRQ_LOCK_SAVE + tpnt->proc_dir = &proc_scsi_u14_34f; + +#if defined(MODULE) + /* io_port could have been modified when loading as a module */ + if(io_port[0] != SKIP) { + setup_done = TRUE; + io_port[MAX_INT_PARAM] = 0; + } +#endif + + for (k = 0; k < MAX_BOARDS + 1; k++) sh[k] = NULL; + + for (k = 0; io_port[k]; k++) { + + if (io_port[k] == SKIP) continue; + + if (j < MAX_BOARDS && port_detect(io_port[k], j, tpnt)) j++; + } + + num_boards = j; + IRQ_UNLOCK_RESTORE + return j; +} + +static inline void build_sg_list(struct mscp *cpp, Scsi_Cmnd *SCpnt) { + unsigned int k, data_len = 0; + struct scatterlist *sgpnt; + + sgpnt = (struct scatterlist *) SCpnt->request_buffer; + + for (k = 0; k < SCpnt->use_sg; k++) { + cpp->sglist[k].address = V2DEV(sgpnt[k].address); + cpp->sglist[k].num_bytes = H2DEV(sgpnt[k].length); + data_len += sgpnt[k].length; + } + + cpp->use_sg = SCpnt->use_sg; + cpp->data_address = V2DEV(cpp->sglist); + cpp->data_len = H2DEV(data_len); +} + +static inline int do_qcomm(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) { + unsigned int i, j, k; + struct mscp *cpp; + + static const unsigned char data_out_cmds[] = { + 0x0a, 0x2a, 0x15, 0x55, 0x04, 0x07, 0x18, 0x1d, 0x24, 0x2e, + 0x30, 0x31, 0x32, 0x38, 0x39, 0x3a, 0x3b, 0x3d, 0x3f, 0x40, + 0x41, 0x4c, 0xaa, 0xae, 0xb0, 0xb1, 0xb2, 0xb6, 0xea, 0x1b + }; + + static const unsigned char data_none_cmds[] = { + 0x01, 0x0b, 0x10, 0x11, 0x13, 0x16, 0x17, 0x19, 0x2b, 0x1e, + 0x2c, 0xac, 0x2f, 0xaf, 0x33, 0xb3, 0x35, 0x36, 0x45, 0x47, + 0x48, 0x49, 0xa9, 0x4b, 0xa5, 0xa6, 0xb5 + }; + + /* j is the board number */ + j = ((struct hostdata *) SCpnt->host->hostdata)->board_number; + + if (SCpnt->host_scribble) + panic("%s: qcomm, pid %ld, SCpnt %p already active.\n", + BN(j), SCpnt->pid, SCpnt); + + /* i is the mailbox number, look for the first free mailbox + starting from last_cp_used */ + i = HD(j)->last_cp_used + 1; + + for (k = 0; k < sh[j]->can_queue; k++, i++) { + + if (i >= sh[j]->can_queue) i = 0; + + if (HD(j)->cp_stat[i] == FREE) { + HD(j)->last_cp_used = i; + break; + } + } + + if (k == sh[j]->can_queue) { + printk("%s: qcomm, no free mailbox.\n", BN(j)); + return 1; + } + + /* Set pointer to control packet structure */ + cpp = &HD(j)->cp[i]; + + memset(cpp, 0, sizeof(struct mscp) - sizeof(struct sg_list *)); + SCpnt->scsi_done = done; + cpp->index = i; + SCpnt->host_scribble = (unsigned char *) &cpp->index; + + if (do_trace) printk("%s: qcomm, mbox %d, target %d.%d:%d, pid %ld.\n", + BN(j), i, SCpnt->channel, SCpnt->target, + SCpnt->lun, SCpnt->pid); + + cpp->xdir = DTD_IN; + + for (k = 0; k < ARRAY_SIZE(data_out_cmds); k++) + if (SCpnt->cmnd[0] == data_out_cmds[k]) { + cpp->xdir = DTD_OUT; + break; + } + + if (cpp->xdir == DTD_IN) + for (k = 0; k < ARRAY_SIZE(data_none_cmds); k++) + if (SCpnt->cmnd[0] == data_none_cmds[k]) { + cpp->xdir = DTD_NONE; + break; + } + + cpp->opcode = OP_SCSI; + cpp->channel = SCpnt->channel; + cpp->target = SCpnt->target; + cpp->lun = SCpnt->lun; + cpp->SCpnt = SCpnt; + cpp->sense_addr = V2DEV(SCpnt->sense_buffer); + cpp->sense_len = sizeof SCpnt->sense_buffer; + + if (SCpnt->use_sg) { + cpp->sg = TRUE; + build_sg_list(cpp, SCpnt); + } + else { + cpp->data_address = V2DEV(SCpnt->request_buffer); + cpp->data_len = H2DEV(SCpnt->request_bufflen); + } + + cpp->scsi_cdbs_len = SCpnt->cmd_len; + memcpy(cpp->scsi_cdbs, SCpnt->cmnd, cpp->scsi_cdbs_len); + + if (linked_comm && SCpnt->device->queue_depth > 2 + && TLDEV(SCpnt->device->type)) { + HD(j)->cp_stat[i] = READY; + flush_dev(SCpnt->device, SCpnt->request.sector, j, FALSE); + return 0; + } + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + SCpnt->host_scribble = NULL; + printk("%s: qcomm, target %d.%d:%d, pid %ld, adapter busy.\n", + BN(j), SCpnt->channel, SCpnt->target, SCpnt->lun, SCpnt->pid); + return 1; + } + + /* Store pointer in OGM address bytes */ + outl(V2DEV(cpp), sh[j]->io_port + REG_OGM); + + /* Issue OGM interrupt */ + outb(CMD_OGM_INTR, sh[j]->io_port + REG_LCL_INTR); + + HD(j)->cp_stat[i] = IN_USE; + return 0; +} + +int u14_34f_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) { + int rtn; + IRQ_FLAGS + + IRQ_LOCK_SAVE + rtn = do_qcomm(SCpnt, done); + IRQ_UNLOCK_RESTORE + return rtn; +} + +static inline int do_old_abort(Scsi_Cmnd *SCarg) { + unsigned int i, j; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + + if (SCarg->host_scribble == NULL || + (SCarg->serial_number_at_timeout && + (SCarg->serial_number != SCarg->serial_number_at_timeout))) { + printk("%s: abort, target %d.%d:%d, pid %ld inactive.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + return SCSI_ABORT_NOT_RUNNING; + } + + i = *(unsigned int *)SCarg->host_scribble; + printk("%s: abort, mbox %d, target %d.%d:%d, pid %ld.\n", + BN(j), i, SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (i >= sh[j]->can_queue) + panic("%s: abort, invalid SCarg->host_scribble.\n", BN(j)); + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: abort, timeout error.\n", BN(j)); + return SCSI_ABORT_ERROR; + } + + if (HD(j)->cp_stat[i] == FREE) { + printk("%s: abort, mbox %d is free.\n", BN(j), i); + return SCSI_ABORT_NOT_RUNNING; + } + + if (HD(j)->cp_stat[i] == IN_USE) { + printk("%s: abort, mbox %d is in use.\n", BN(j), i); + + if (SCarg != HD(j)->cp[i].SCpnt) + panic("%s: abort, mbox %d, SCarg %p, cp SCpnt %p.\n", + BN(j), i, SCarg, HD(j)->cp[i].SCpnt); + + if (inb(sh[j]->io_port + REG_SYS_INTR) & IRQ_ASSERTED) + printk("%s: abort, mbox %d, interrupt pending.\n", BN(j), i); + + return SCSI_ABORT_SNOOZE; + } + + if (HD(j)->cp_stat[i] == IN_RESET) { + printk("%s: abort, mbox %d is in reset.\n", BN(j), i); + return SCSI_ABORT_ERROR; + } + + if (HD(j)->cp_stat[i] == LOCKED) { + printk("%s: abort, mbox %d is locked.\n", BN(j), i); + return SCSI_ABORT_NOT_RUNNING; + } + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + SCarg->result = DID_ABORT << 16; + SCarg->host_scribble = NULL; + HD(j)->cp_stat[i] = FREE; + printk("%s, abort, mbox %d ready, DID_ABORT, pid %ld done.\n", + BN(j), i, SCarg->pid); + SCarg->scsi_done(SCarg); + return SCSI_ABORT_SUCCESS; + } + + panic("%s: abort, mbox %d, invalid cp_stat.\n", BN(j), i); +} + +int u14_34f_old_abort(Scsi_Cmnd *SCarg) { + int rtn; + IRQ_FLAGS + + IRQ_LOCK_SAVE + rtn = do_old_abort(SCarg); + IRQ_UNLOCK_RESTORE + return rtn; +} + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + +static inline int do_abort(Scsi_Cmnd *SCarg) { + unsigned int i, j; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + + if (SCarg->host_scribble == NULL) { + printk("%s: abort, target %d.%d:%d, pid %ld inactive.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + return SUCCESS; + } + + i = *(unsigned int *)SCarg->host_scribble; + printk("%s: abort, mbox %d, target %d.%d:%d, pid %ld.\n", + BN(j), i, SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (i >= sh[j]->can_queue) + panic("%s: abort, invalid SCarg->host_scribble.\n", BN(j)); + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: abort, timeout error.\n", BN(j)); + return FAILED; + } + + if (HD(j)->cp_stat[i] == FREE) { + printk("%s: abort, mbox %d is free.\n", BN(j), i); + return SUCCESS; + } + + if (HD(j)->cp_stat[i] == IN_USE) { + printk("%s: abort, mbox %d is in use.\n", BN(j), i); + + if (SCarg != HD(j)->cp[i].SCpnt) + panic("%s: abort, mbox %d, SCarg %p, cp SCpnt %p.\n", + BN(j), i, SCarg, HD(j)->cp[i].SCpnt); + + if (inb(sh[j]->io_port + REG_SYS_INTR) & IRQ_ASSERTED) + printk("%s: abort, mbox %d, interrupt pending.\n", BN(j), i); + + if (SCarg->eh_state == SCSI_STATE_TIMEOUT) { + SCarg->host_scribble = NULL; + HD(j)->cp_stat[i] = FREE; + printk("%s, abort, mbox %d, eh_state timeout, pid %ld.\n", + BN(j), i, SCarg->pid); + return SUCCESS; + } + + return FAILED; + } + + if (HD(j)->cp_stat[i] == IN_RESET) { + printk("%s: abort, mbox %d is in reset.\n", BN(j), i); + return FAILED; + } + + if (HD(j)->cp_stat[i] == LOCKED) { + printk("%s: abort, mbox %d is locked.\n", BN(j), i); + return SUCCESS; + } + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + SCarg->result = DID_ABORT << 16; + SCarg->host_scribble = NULL; + HD(j)->cp_stat[i] = FREE; + printk("%s, abort, mbox %d ready, DID_ABORT, pid %ld done.\n", + BN(j), i, SCarg->pid); + SCarg->scsi_done(SCarg); + return SUCCESS; + } + + panic("%s: abort, mbox %d, invalid cp_stat.\n", BN(j), i); +} + +int u14_34f_abort(Scsi_Cmnd *SCarg) { + + return do_abort(SCarg); +} + +#endif /* new_eh_code */ + +static inline int do_old_reset(Scsi_Cmnd *SCarg) { + unsigned int i, j, time, k, c, limit = 0; + int arg_done = FALSE; + Scsi_Cmnd *SCpnt; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + printk("%s: reset, enter, target %d.%d:%d, pid %ld.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (SCarg->host_scribble == NULL) + printk("%s: reset, pid %ld inactive.\n", BN(j), SCarg->pid); + + if (SCarg->serial_number_at_timeout && + (SCarg->serial_number != SCarg->serial_number_at_timeout)) { + printk("%s: reset, pid %ld, reset not running.\n", BN(j), SCarg->pid); + return SCSI_RESET_NOT_RUNNING; + } + + if (HD(j)->in_reset) { + printk("%s: reset, exit, already in reset.\n", BN(j)); + return SCSI_RESET_ERROR; + } + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: reset, exit, timeout error.\n", BN(j)); + return SCSI_RESET_ERROR; + } + + HD(j)->retries = 0; + + for (c = 0; c <= sh[j]->max_channel; c++) + for (k = 0; k < sh[j]->max_id; k++) { + HD(j)->target_redo[k][c] = TRUE; + HD(j)->target_to[k][c] = 0; + } + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == FREE) continue; + + if (HD(j)->cp_stat[i] == LOCKED) { + HD(j)->cp_stat[i] = FREE; + printk("%s: reset, locked mbox %d forced free.\n", BN(j), i); + continue; + } + + if (!(SCpnt = HD(j)->cp[i].SCpnt)) + panic("%s: reset, mbox %d, SCpnt == NULL.\n", BN(j), i); + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + HD(j)->cp_stat[i] = ABORTING; + printk("%s: reset, mbox %d aborting, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + else { + HD(j)->cp_stat[i] = IN_RESET; + printk("%s: reset, mbox %d in reset, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + if (SCpnt->host_scribble == NULL) + panic("%s: reset, mbox %d, garbled SCpnt.\n", BN(j), i); + + if (*(unsigned int *)SCpnt->host_scribble != i) + panic("%s: reset, mbox %d, index mismatch.\n", BN(j), i); + + if (SCpnt->scsi_done == NULL) + panic("%s: reset, mbox %d, SCpnt->scsi_done == NULL.\n", BN(j), i); + + if (SCpnt == SCarg) arg_done = TRUE; + } + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: reset, cannot reset, timeout error.\n", BN(j)); + return SCSI_RESET_ERROR; + } + + outb(CMD_RESET, sh[j]->io_port + REG_LCL_INTR); + printk("%s: reset, board reset done, enabling interrupts.\n", BN(j)); + +#if defined(DEBUG_RESET) + do_trace = TRUE; +#endif + + HD(j)->in_reset = TRUE; + SPIN_UNLOCK + IRQ_UNLOCK + time = jiffies; + while ((jiffies - time) < (10 * HZ) && limit++ < 200000) udelay(100L); + IRQ_LOCK + SPIN_LOCK + printk("%s: reset, interrupts disabled, loops %d.\n", BN(j), limit); + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == IN_RESET) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox is still waiting for its interrupt */ + HD(j)->cp_stat[i] = LOCKED; + + printk("%s, reset, mbox %d locked, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else if (HD(j)->cp_stat[i] == ABORTING) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox was never queued to the adapter */ + HD(j)->cp_stat[i] = FREE; + + printk("%s, reset, mbox %d aborting, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else + + /* Any other mailbox has already been set free by interrupt */ + continue; + + SCpnt->scsi_done(SCpnt); + IRQ_LOCK + } + + HD(j)->in_reset = FALSE; + do_trace = FALSE; + + if (arg_done) { + printk("%s: reset, exit, success.\n", BN(j)); + return SCSI_RESET_SUCCESS; + } + else { + printk("%s: reset, exit, wakeup.\n", BN(j)); + return SCSI_RESET_PUNT; + } +} + +int u14_34f_old_reset(Scsi_Cmnd *SCarg, unsigned int reset_flags) { + int rtn; + IRQ_FLAGS + + IRQ_LOCK_SAVE + rtn = do_old_reset(SCarg); + IRQ_UNLOCK_RESTORE + return rtn; +} + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + +static inline int do_reset(Scsi_Cmnd *SCarg) { + unsigned int i, j, time, k, c, limit = 0; + int arg_done = FALSE; + Scsi_Cmnd *SCpnt; + + j = ((struct hostdata *) SCarg->host->hostdata)->board_number; + printk("%s: reset, enter, target %d.%d:%d, pid %ld.\n", + BN(j), SCarg->channel, SCarg->target, SCarg->lun, SCarg->pid); + + if (SCarg->host_scribble == NULL) + printk("%s: reset, pid %ld inactive.\n", BN(j), SCarg->pid); + + if (HD(j)->in_reset) { + printk("%s: reset, exit, already in reset.\n", BN(j)); + return FAILED; + } + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: reset, exit, timeout error.\n", BN(j)); + return FAILED; + } + + HD(j)->retries = 0; + + for (c = 0; c <= sh[j]->max_channel; c++) + for (k = 0; k < sh[j]->max_id; k++) { + HD(j)->target_redo[k][c] = TRUE; + HD(j)->target_to[k][c] = 0; + } + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == FREE) continue; + + if (HD(j)->cp_stat[i] == LOCKED) { + HD(j)->cp_stat[i] = FREE; + printk("%s: reset, locked mbox %d forced free.\n", BN(j), i); + continue; + } + + if (!(SCpnt = HD(j)->cp[i].SCpnt)) + panic("%s: reset, mbox %d, SCpnt == NULL.\n", BN(j), i); + + if (HD(j)->cp_stat[i] == READY || HD(j)->cp_stat[i] == ABORTING) { + HD(j)->cp_stat[i] = ABORTING; + printk("%s: reset, mbox %d aborting, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + else { + HD(j)->cp_stat[i] = IN_RESET; + printk("%s: reset, mbox %d in reset, pid %ld.\n", + BN(j), i, SCpnt->pid); + } + + if (SCpnt->host_scribble == NULL) + panic("%s: reset, mbox %d, garbled SCpnt.\n", BN(j), i); + + if (*(unsigned int *)SCpnt->host_scribble != i) + panic("%s: reset, mbox %d, index mismatch.\n", BN(j), i); + + if (SCpnt->scsi_done == NULL) + panic("%s: reset, mbox %d, SCpnt->scsi_done == NULL.\n", BN(j), i); + + if (SCpnt == SCarg) arg_done = TRUE; + } + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: reset, cannot reset, timeout error.\n", BN(j)); + return FAILED; + } + + outb(CMD_RESET, sh[j]->io_port + REG_LCL_INTR); + printk("%s: reset, board reset done, enabling interrupts.\n", BN(j)); + +#if defined(DEBUG_RESET) + do_trace = TRUE; +#endif + + HD(j)->in_reset = TRUE; + SPIN_UNLOCK + IRQ_UNLOCK + time = jiffies; + while ((jiffies - time) < (10 * HZ) && limit++ < 200000) udelay(100L); + IRQ_LOCK + SPIN_LOCK + printk("%s: reset, interrupts disabled, loops %d.\n", BN(j), limit); + + for (i = 0; i < sh[j]->can_queue; i++) { + + if (HD(j)->cp_stat[i] == IN_RESET) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox is still waiting for its interrupt */ + HD(j)->cp_stat[i] = LOCKED; + + printk("%s, reset, mbox %d locked, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else if (HD(j)->cp_stat[i] == ABORTING) { + SCpnt = HD(j)->cp[i].SCpnt; + SCpnt->result = DID_RESET << 16; + SCpnt->host_scribble = NULL; + + /* This mailbox was never queued to the adapter */ + HD(j)->cp_stat[i] = FREE; + + printk("%s, reset, mbox %d aborting, DID_RESET, pid %ld done.\n", + BN(j), i, SCpnt->pid); + } + + else + + /* Any other mailbox has already been set free by interrupt */ + continue; + + SCpnt->scsi_done(SCpnt); + IRQ_LOCK + } + + HD(j)->in_reset = FALSE; + do_trace = FALSE; + + if (arg_done) printk("%s: reset, exit, pid %ld done.\n", BN(j), SCarg->pid); + else printk("%s: reset, exit.\n", BN(j)); + + return SUCCESS; +} + +int u14_34f_reset(Scsi_Cmnd *SCarg) { + + return do_reset(SCarg); +} + +#endif /* new_eh_code */ + +int u14_34f_biosparam(Disk *disk, kdev_t dev, int *dkinfo) { + unsigned int j = 0; + int size = disk->capacity; + + dkinfo[0] = HD(j)->heads; + dkinfo[1] = HD(j)->sectors; + dkinfo[2] = size / (HD(j)->heads * HD(j)->sectors); + + if (ext_tran && (scsicam_bios_param(disk, dev, dkinfo) < 0)) { + dkinfo[0] = 255; + dkinfo[1] = 63; + dkinfo[2] = size / (dkinfo[0] * dkinfo[1]); + } + +#if defined (DEBUG_GEOMETRY) + printk ("%s: biosparam, head=%d, sec=%d, cyl=%d.\n", driver_name, + dkinfo[0], dkinfo[1], dkinfo[2]); +#endif + + return FALSE; +} + +static void sort(unsigned long sk[], unsigned int da[], unsigned int n, + unsigned int rev) { + unsigned int i, j, k, y; + unsigned long x; + + for (i = 0; i < n - 1; i++) { + k = i; + + for (j = k + 1; j < n; j++) + if (rev) { + if (sk[j] > sk[k]) k = j; + } + else { + if (sk[j] < sk[k]) k = j; + } + + if (k != i) { + x = sk[k]; sk[k] = sk[i]; sk[i] = x; + y = da[k]; da[k] = da[i]; da[i] = y; + } + } + + return; + } + +static inline int reorder(unsigned int j, unsigned long cursec, + unsigned int ihdlr, unsigned int il[], unsigned int n_ready) { + Scsi_Cmnd *SCpnt; + struct mscp *cpp; + unsigned int k, n; + unsigned int rev = FALSE, s = TRUE, r = TRUE; + unsigned int input_only = TRUE, overlap = FALSE; + unsigned long sl[n_ready], pl[n_ready], ll[n_ready]; + unsigned long maxsec = 0, minsec = ULONG_MAX, seek = 0, iseek = 0; + unsigned long ioseek = 0; + + static unsigned int flushcount = 0, batchcount = 0, sortcount = 0; + static unsigned int readycount = 0, ovlcount = 0, inputcount = 0; + static unsigned int readysorted = 0, revcount = 0; + static unsigned long seeksorted = 0, seeknosort = 0; + + if (link_statistics && !(++flushcount % link_statistics)) + printk("fc %d bc %d ic %d oc %d rc %d rs %d sc %d re %d"\ + " av %ldK as %ldK.\n", flushcount, batchcount, inputcount, + ovlcount, readycount, readysorted, sortcount, revcount, + seeknosort / (readycount + 1), + seeksorted / (readycount + 1)); + + if (n_ready <= 1) return FALSE; + + for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + + if (!(cpp->xdir == DTD_IN)) input_only = FALSE; + + if (SCpnt->request.sector < minsec) minsec = SCpnt->request.sector; + if (SCpnt->request.sector > maxsec) maxsec = SCpnt->request.sector; + + sl[n] = SCpnt->request.sector; + ioseek += SCpnt->request.nr_sectors; + + if (!n) continue; + + if (sl[n] < sl[n - 1]) s = FALSE; + if (sl[n] > sl[n - 1]) r = FALSE; + + if (link_statistics) { + if (sl[n] > sl[n - 1]) + seek += sl[n] - sl[n - 1]; + else + seek += sl[n - 1] - sl[n]; + } + + } + + if (link_statistics) { + if (cursec > sl[0]) seek += cursec - sl[0]; else seek += sl[0] - cursec; + } + + if (cursec > ((maxsec + minsec) / 2)) rev = TRUE; + + if (ioseek > ((maxsec - minsec) / 2)) rev = FALSE; + + if (!((rev && r) || (!rev && s))) sort(sl, il, n_ready, rev); + + if (!input_only) for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + ll[n] = SCpnt->request.nr_sectors; pl[n] = SCpnt->pid; + + if (!n) continue; + + if ((sl[n] == sl[n - 1]) || (!rev && ((sl[n - 1] + ll[n - 1]) > sl[n])) + || (rev && ((sl[n] + ll[n]) > sl[n - 1]))) overlap = TRUE; + } + + if (overlap) sort(pl, il, n_ready, FALSE); + + if (link_statistics) { + if (cursec > sl[0]) iseek = cursec - sl[0]; else iseek = sl[0] - cursec; + batchcount++; readycount += n_ready, seeknosort += seek / 1024; + if (input_only) inputcount++; + if (overlap) { ovlcount++; seeksorted += iseek / 1024; } + else seeksorted += (iseek + maxsec - minsec) / 1024; + if (rev && !r) { revcount++; readysorted += n_ready; } + if (!rev && !s) { sortcount++; readysorted += n_ready; } + } + +#if defined(DEBUG_LINKED_COMMANDS) + if (link_statistics && (overlap || !(flushcount % link_statistics))) + for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + printk("%s %d.%d:%d pid %ld mb %d fc %d nr %d sec %ld ns %ld"\ + " cur %ld s:%c r:%c rev:%c in:%c ov:%c xd %d.\n", + (ihdlr ? "ihdlr" : "qcomm"), SCpnt->channel, SCpnt->target, + SCpnt->lun, SCpnt->pid, k, flushcount, n_ready, + SCpnt->request.sector, SCpnt->request.nr_sectors, cursec, + YESNO(s), YESNO(r), YESNO(rev), YESNO(input_only), + YESNO(overlap), cpp->xdir); + } +#endif + return overlap; +} + +static void flush_dev(Scsi_Device *dev, unsigned long cursec, unsigned int j, + unsigned int ihdlr) { + Scsi_Cmnd *SCpnt; + struct mscp *cpp; + unsigned int k, n, n_ready = 0, il[MAX_MAILBOXES]; + + for (k = 0; k < sh[j]->can_queue; k++) { + + if (HD(j)->cp_stat[k] != READY && HD(j)->cp_stat[k] != IN_USE) continue; + + cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + + if (SCpnt->device != dev) continue; + + if (HD(j)->cp_stat[k] == IN_USE) return; + + il[n_ready++] = k; + } + + if (reorder(j, cursec, ihdlr, il, n_ready)) n_ready = 1; + + for (n = 0; n < n_ready; n++) { + k = il[n]; cpp = &HD(j)->cp[k]; SCpnt = cpp->SCpnt; + + if (wait_on_busy(sh[j]->io_port, MAXLOOP)) { + printk("%s: %s, target %d.%d:%d, pid %ld, mbox %d, adapter"\ + " busy, will abort.\n", BN(j), (ihdlr ? "ihdlr" : "qcomm"), + SCpnt->channel, SCpnt->target, SCpnt->lun, SCpnt->pid, k); + HD(j)->cp_stat[k] = ABORTING; + continue; + } + + outl(V2DEV(cpp), sh[j]->io_port + REG_OGM); + outb(CMD_OGM_INTR, sh[j]->io_port + REG_LCL_INTR); + HD(j)->cp_stat[k] = IN_USE; + } + +} + +static inline void ihdlr(int irq, unsigned int j) { + Scsi_Cmnd *SCpnt; + unsigned int i, k, c, status, tstatus, reg, ret; + struct mscp *spp, *cpp; + + if (sh[j]->irq != irq) + panic("%s: ihdlr, irq %d, sh[j]->irq %d.\n", BN(j), irq, sh[j]->irq); + + /* Check if this board need to be serviced */ + if (!((reg = inb(sh[j]->io_port + REG_SYS_INTR)) & IRQ_ASSERTED)) return; + + HD(j)->iocount++; + + if (do_trace) printk("%s: ihdlr, enter, irq %d, count %d.\n", BN(j), irq, + HD(j)->iocount); + + /* Check if this board is still busy */ + if (wait_on_busy(sh[j]->io_port, 20 * MAXLOOP)) { + outb(CMD_CLR_INTR, sh[j]->io_port + REG_SYS_INTR); + printk("%s: ihdlr, busy timeout error, irq %d, reg 0x%x, count %d.\n", + BN(j), irq, reg, HD(j)->iocount); + return; + } + + spp = (struct mscp *)DEV2V(ret = inl(sh[j]->io_port + REG_ICM)); + cpp = spp; + + /* Clear interrupt pending flag */ + outb(CMD_CLR_INTR, sh[j]->io_port + REG_SYS_INTR); + +#if defined(DEBUG_GENERATE_ABORTS) + if ((HD(j)->iocount > 500) && ((HD(j)->iocount % 500) < 3)) return; +#endif + + /* Find the mailbox to be serviced on this board */ + i = cpp - HD(j)->cp; + + if (cpp < HD(j)->cp || cpp >= HD(j)->cp + sh[j]->can_queue + || i >= sh[j]->can_queue) + panic("%s: ihdlr, invalid mscp bus address %p, cp0 %p.\n", BN(j), + (void *)ret, HD(j)->cp); + + if (HD(j)->cp_stat[i] == IGNORE) { + HD(j)->cp_stat[i] = FREE; + return; + } + else if (HD(j)->cp_stat[i] == LOCKED) { + HD(j)->cp_stat[i] = FREE; + printk("%s: ihdlr, mbox %d unlocked, count %d.\n", BN(j), i, + HD(j)->iocount); + return; + } + else if (HD(j)->cp_stat[i] == FREE) { + printk("%s: ihdlr, mbox %d is free, count %d.\n", BN(j), i, + HD(j)->iocount); + return; + } + else if (HD(j)->cp_stat[i] == IN_RESET) + printk("%s: ihdlr, mbox %d is in reset.\n", BN(j), i); + else if (HD(j)->cp_stat[i] != IN_USE) + panic("%s: ihdlr, mbox %d, invalid cp_stat: %d.\n", + BN(j), i, HD(j)->cp_stat[i]); + + HD(j)->cp_stat[i] = FREE; + SCpnt = cpp->SCpnt; + + if (SCpnt == NULL) panic("%s: ihdlr, mbox %d, SCpnt == NULL.\n", BN(j), i); + + if (SCpnt->host_scribble == NULL) + panic("%s: ihdlr, mbox %d, pid %ld, SCpnt %p garbled.\n", BN(j), i, + SCpnt->pid, SCpnt); + + if (*(unsigned int *)SCpnt->host_scribble != i) + panic("%s: ihdlr, mbox %d, pid %ld, index mismatch %d.\n", + BN(j), i, SCpnt->pid, *(unsigned int *)SCpnt->host_scribble); + + if (linked_comm && SCpnt->device->queue_depth > 2 + && TLDEV(SCpnt->device->type)) + flush_dev(SCpnt->device, SCpnt->request.sector, j, TRUE); + + tstatus = status_byte(spp->target_status); + +#if defined(DEBUG_GENERATE_ERRORS) + if ((HD(j)->iocount > 500) && ((HD(j)->iocount % 200) < 2)) + spp->adapter_status = 0x01; +#endif + + switch (spp->adapter_status) { + case ASOK: /* status OK */ + + /* Forces a reset if a disk drive keeps returning BUSY */ + if (tstatus == BUSY && SCpnt->device->type != TYPE_TAPE) + status = DID_ERROR << 16; + + /* If there was a bus reset, redo operation on each target */ + else if (tstatus != GOOD && SCpnt->device->type == TYPE_DISK + && HD(j)->target_redo[SCpnt->target][SCpnt->channel]) + status = DID_BUS_BUSY << 16; + + /* Works around a flaw in scsi.c */ + else if (tstatus == CHECK_CONDITION + && SCpnt->device->type == TYPE_DISK + && (SCpnt->sense_buffer[2] & 0xf) == RECOVERED_ERROR) + status = DID_BUS_BUSY << 16; + + else + status = DID_OK << 16; + + if (tstatus == GOOD) + HD(j)->target_redo[SCpnt->target][SCpnt->channel] = FALSE; + + if (spp->target_status && SCpnt->device->type == TYPE_DISK) + printk("%s: ihdlr, target %d.%d:%d, pid %ld, "\ + "target_status 0x%x, sense key 0x%x.\n", BN(j), + SCpnt->channel, SCpnt->target, SCpnt->lun, + SCpnt->pid, spp->target_status, + SCpnt->sense_buffer[2]); + + HD(j)->target_to[SCpnt->target][SCpnt->channel] = 0; + + if (HD(j)->last_retried_pid == SCpnt->pid) HD(j)->retries = 0; + + break; + case ASST: /* Selection Time Out */ + + if (HD(j)->target_to[SCpnt->target][SCpnt->channel] > 1) + status = DID_ERROR << 16; + else { + status = DID_TIME_OUT << 16; + HD(j)->target_to[SCpnt->target][SCpnt->channel]++; + } + + break; + + /* Perform a limited number of internal retries */ + case 0x93: /* Unexpected bus free */ + case 0x94: /* Target bus phase sequence failure */ + case 0x96: /* Illegal SCSI command */ + case 0xa3: /* SCSI bus reset error */ + + for (c = 0; c <= sh[j]->max_channel; c++) + for (k = 0; k < sh[j]->max_id; k++) + HD(j)->target_redo[k][c] = TRUE; + + + case 0x92: /* Data over/under-run */ + + if (SCpnt->device->type != TYPE_TAPE + && HD(j)->retries < MAX_INTERNAL_RETRIES) { + +#if defined(DID_SOFT_ERROR) + status = DID_SOFT_ERROR << 16; +#else + status = DID_BUS_BUSY << 16; +#endif + + HD(j)->retries++; + HD(j)->last_retried_pid = SCpnt->pid; + } + else + status = DID_ERROR << 16; + + break; + case 0x01: /* Invalid command */ + case 0x02: /* Invalid parameters */ + case 0x03: /* Invalid data list */ + case 0x84: /* SCSI bus abort error */ + case 0x9b: /* Auto request sense error */ + case 0x9f: /* Unexpected command complete message error */ + case 0xff: /* Invalid parameter in the S/G list */ + default: + status = DID_ERROR << 16; + break; + } + + SCpnt->result = status | spp->target_status; + +#if defined(DEBUG_INTERRUPT) + if (SCpnt->result || do_trace) +#else + if ((spp->adapter_status != ASOK && HD(j)->iocount > 1000) || + (spp->adapter_status != ASOK && + spp->adapter_status != ASST && HD(j)->iocount <= 1000) || + do_trace || msg_byte(spp->target_status)) +#endif + printk("%s: ihdlr, mbox %2d, err 0x%x:%x,"\ + " target %d.%d:%d, pid %ld, reg 0x%x, count %d.\n", + BN(j), i, spp->adapter_status, spp->target_status, + SCpnt->channel, SCpnt->target, SCpnt->lun, SCpnt->pid, + reg, HD(j)->iocount); + + /* Set the command state to inactive */ + SCpnt->host_scribble = NULL; + + SCpnt->scsi_done(SCpnt); + + if (do_trace) printk("%s: ihdlr, exit, irq %d, count %d.\n", BN(j), irq, + HD(j)->iocount); + + return; +} + +static void do_interrupt_handler(int irq, void *shap, struct pt_regs *regs) { + unsigned int j; + IRQ_FLAGS + SPIN_FLAGS + + /* Check if the interrupt must be processed by this handler */ + if ((j = (unsigned int)((char *)shap - sha)) >= num_boards) return; + + SPIN_LOCK_SAVE + IRQ_LOCK_SAVE + ihdlr(irq, j); + IRQ_UNLOCK_RESTORE + SPIN_UNLOCK_RESTORE +} + +int u14_34f_release(struct Scsi_Host *shpnt) { + unsigned int i, j; + IRQ_FLAGS + + IRQ_LOCK_SAVE + + for (j = 0; sh[j] != NULL && sh[j] != shpnt; j++); + + if (sh[j] == NULL) panic("%s: release, invalid Scsi_Host pointer.\n", + driver_name); + + for (i = 0; i < sh[j]->can_queue; i++) + if ((&HD(j)->cp[i])->sglist) kfree((&HD(j)->cp[i])->sglist); + + free_irq(sh[j]->irq, &sha[j]); + + if (sh[j]->dma_channel != NO_DMA) free_dma(sh[j]->dma_channel); + + release_region(sh[j]->io_port, sh[j]->n_io_port); + scsi_unregister(sh[j]); + IRQ_UNLOCK_RESTORE + return FALSE; +} + +#if defined(MODULE) +Scsi_Host_Template driver_template = ULTRASTOR_14_34F; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/u14-34f.h b/linux/src/drivers/scsi/u14-34f.h new file mode 100644 index 00000000..943b8cbb --- /dev/null +++ b/linux/src/drivers/scsi/u14-34f.h @@ -0,0 +1,60 @@ +/* + * u14-34f.h - used by the low-level driver for UltraStor 14F/34F + */ +#ifndef _U14_34F_H +#define _U14_34F_H + +#include <scsi/scsicam.h> +#include <linux/version.h> + +int u14_34f_detect(Scsi_Host_Template *); +int u14_34f_release(struct Scsi_Host *); +int u14_34f_queuecommand(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int u14_34f_abort(Scsi_Cmnd *); +int u14_34f_old_abort(Scsi_Cmnd *); +int u14_34f_reset(Scsi_Cmnd *); +int u14_34f_old_reset(Scsi_Cmnd *, unsigned int); +int u14_34f_biosparam(Disk *, kdev_t, int *); + +#define U14_34F_VERSION "4.33.00" + +#define LinuxVersionCode(v, p, s) (((v)<<16)+((p)<<8)+(s)) + +#if LINUX_VERSION_CODE >= LinuxVersionCode(2,1,101) + +#define ULTRASTOR_14_34F { \ + name: "UltraStor 14F/34F rev. " U14_34F_VERSION " ", \ + detect: u14_34f_detect, \ + release: u14_34f_release, \ + queuecommand: u14_34f_queuecommand, \ + abort: u14_34f_old_abort, \ + reset: u14_34f_old_reset, \ + eh_abort_handler: u14_34f_abort, \ + eh_device_reset_handler: NULL, \ + eh_bus_reset_handler: NULL, \ + eh_host_reset_handler: u14_34f_reset, \ + bios_param: u14_34f_biosparam, \ + this_id: 7, \ + unchecked_isa_dma: 1, \ + use_clustering: ENABLE_CLUSTERING, \ + use_new_eh_code: 1 /* Enable new error code */ \ + } + +#else /* Use old scsi code */ + +#define ULTRASTOR_14_34F { \ + name: "UltraStor 14F/34F rev. " U14_34F_VERSION " ", \ + detect: u14_34f_detect, \ + release: u14_34f_release, \ + queuecommand: u14_34f_queuecommand, \ + abort: u14_34f_old_abort, \ + reset: u14_34f_old_reset, \ + bios_param: u14_34f_biosparam, \ + this_id: 7, \ + unchecked_isa_dma: 1, \ + use_clustering: ENABLE_CLUSTERING \ + } + +#endif + +#endif diff --git a/linux/src/drivers/scsi/ultrastor.c b/linux/src/drivers/scsi/ultrastor.c new file mode 100644 index 00000000..3d9b4118 --- /dev/null +++ b/linux/src/drivers/scsi/ultrastor.c @@ -0,0 +1,1165 @@ +/* + * ultrastor.c Copyright (C) 1992 David B. Gentzel + * Low-level SCSI driver for UltraStor 14F, 24F, and 34F + * by David B. Gentzel, Whitfield Software Services, Carnegie, PA + * (gentzel@nova.enet.dec.com) + * scatter/gather added by Scott Taylor (n217cg@tamuts.tamu.edu) + * 24F and multiple command support by John F. Carr (jfc@athena.mit.edu) + * John's work modified by Caleb Epstein (cae@jpmorgan.com) and + * Eric Youngdale (ericy@cais.com). + * Thanks to UltraStor for providing the necessary documentation + */ + +/* + * TODO: + * 1. Find out why scatter/gather is limited to 16 requests per command. + * This is fixed, at least on the 24F, as of version 1.12 - CAE. + * 2. Look at command linking (mscp.command_link and + * mscp.command_link_id). (Does not work with many disks, + * and no performance increase. ERY). + * 3. Allow multiple adapters. + */ + +/* + * NOTES: + * The UltraStor 14F, 24F, and 34F are a family of intelligent, high + * performance SCSI-2 host adapters. They all support command queueing + * and scatter/gather I/O. Some of them can also emulate the standard + * WD1003 interface for use with OS's which don't support SCSI. Here + * is the scoop on the various models: + * 14F - ISA first-party DMA HA with floppy support and WD1003 emulation. + * 14N - ISA HA with floppy support. I think that this is a non-DMA + * HA. Nothing further known. + * 24F - EISA Bus Master HA with floppy support and WD1003 emulation. + * 34F - VL-Bus Bus Master HA with floppy support (no WD1003 emulation). + * + * The 14F, 24F, and 34F are supported by this driver. + * + * Places flagged with a triple question-mark are things which are either + * unfinished, questionable, or wrong. + */ + +/* Changes from version 1.11 alpha to 1.12 + * + * Increased the size of the scatter-gather list to 33 entries for + * the 24F adapter (it was 16). I don't have the specs for the 14F + * or the 34F, so they may support larger s-g lists as well. + * + * Caleb Epstein <cae@jpmorgan.com> + */ + +/* Changes from version 1.9 to 1.11 + * + * Patches to bring this driver up to speed with the default kernel + * driver which supports only the 14F and 34F adapters. This version + * should compile cleanly into 0.99.13, 0.99.12 and probably 0.99.11. + * + * Fixes from Eric Youngdale to fix a few possible race conditions and + * several problems with bit testing operations (insufficient + * parentheses). + * + * Removed the ultrastor_abort() and ultrastor_reset() functions + * (enclosed them in #if 0 / #endif). These functions, at least on + * the 24F, cause the SCSI bus to do odd things and generally lead to + * kernel panics and machine hangs. This is like the Adaptec code. + * + * Use check/snarf_region for 14f, 34f to avoid I/O space address conflicts. + */ + +/* Changes from version 1.8 to version 1.9 + * + * 0.99.11 patches (cae@jpmorgan.com) */ + +/* Changes from version 1.7 to version 1.8 + * + * Better error reporting. + */ + +/* Changes from version 1.6 to version 1.7 + * + * Removed CSIR command code. + * + * Better race condition avoidance (xchgb function added). + * + * Set ICM and OGM status to zero at probe (24F) + * + * reset sends soft reset to UltraStor adapter + * + * reset adapter if adapter interrupts with an invalid MSCP address + * + * handle aborted command interrupt (24F) + * + */ + +/* Changes from version 1.5 to version 1.6: + * + * Read MSCP address from ICM _before_ clearing the interrupt flag. + * This fixes a race condition. + */ + +/* Changes from version 1.4 to version 1.5: + * + * Abort now calls done when multiple commands are enabled. + * + * Clear busy when aborted command finishes, not when abort is called. + * + * More debugging messages for aborts. + */ + +/* Changes from version 1.3 to version 1.4: + * + * Enable automatic request of sense data on error (requires newer version + * of scsi.c to be useful). + * + * Fix PORT_OVERRIDE for 14F. + * + * Fix abort and reset to work properly (config.aborted wasn't cleared + * after it was tested, so after a command abort no further commands would + * work). + * + * Boot time test to enable SCSI bus reset (defaults to not allowing reset). + * + * Fix test for OGM busy -- the busy bit is in different places on the 24F. + * + * Release ICM slot by clearing first byte on 24F. + */ + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <linux/stddef.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <asm/io.h> +#include <asm/bitops.h> +#include <asm/system.h> +#include <asm/dma.h> + +#define ULTRASTOR_PRIVATE /* Get the private stuff from ultrastor.h */ +#include <linux/blk.h> +#include "scsi.h" +#include "hosts.h" +#include "ultrastor.h" +#include "sd.h" +#include<linux/stat.h> + +struct proc_dir_entry proc_scsi_ultrastor = { + PROC_SCSI_ULTRASTOR, 9, "ultrastor", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#define FALSE 0 +#define TRUE 1 + +#ifndef ULTRASTOR_DEBUG +#define ULTRASTOR_DEBUG (UD_ABORT|UD_CSIR|UD_RESET) +#endif + +#define VERSION "1.12" + +#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr)[0]) + +#define PACKED __attribute__((packed)) +#define ALIGNED(x) __attribute__((aligned(x))) + + +/* The 14F uses an array of 4-byte ints for its scatter/gather list. + The data can be unaligned, but need not be. It's easier to give + the list normal alignment since it doesn't need to fit into a + packed structure. */ + +typedef struct { + unsigned int address; + unsigned int num_bytes; +} ultrastor_sg_list; + + +/* MailBox SCSI Command Packet. Basic command structure for communicating + with controller. */ +struct mscp { + unsigned char opcode: 3; /* type of command */ + unsigned char xdir: 2; /* data transfer direction */ + unsigned char dcn: 1; /* disable disconnect */ + unsigned char ca: 1; /* use cache (if available) */ + unsigned char sg: 1; /* scatter/gather operation */ + unsigned char target_id: 3; /* target SCSI id */ + unsigned char ch_no: 2; /* SCSI channel (always 0 for 14f) */ + unsigned char lun: 3; /* logical unit number */ + unsigned int transfer_data PACKED; /* transfer data pointer */ + unsigned int transfer_data_length PACKED; /* length in bytes */ + unsigned int command_link PACKED; /* for linking command chains */ + unsigned char scsi_command_link_id; /* identifies command in chain */ + unsigned char number_of_sg_list; /* (if sg is set) 8 bytes per list */ + unsigned char length_of_sense_byte; + unsigned char length_of_scsi_cdbs; /* 6, 10, or 12 */ + unsigned char scsi_cdbs[12]; /* SCSI commands */ + unsigned char adapter_status; /* non-zero indicates HA error */ + unsigned char target_status; /* non-zero indicates target error */ + unsigned int sense_data PACKED; + /* The following fields are for software only. They are included in + the MSCP structure because they are associated with SCSI requests. */ + void (*done)(Scsi_Cmnd *); + Scsi_Cmnd *SCint; + ultrastor_sg_list sglist[ULTRASTOR_24F_MAX_SG]; /* use larger size for 24F */ +}; + + +/* Port addresses (relative to the base address) */ +#define U14F_PRODUCT_ID(port) ((port) + 0x4) +#define CONFIG(port) ((port) + 0x6) + +/* Port addresses relative to the doorbell base address. */ +#define LCL_DOORBELL_MASK(port) ((port) + 0x0) +#define LCL_DOORBELL_INTR(port) ((port) + 0x1) +#define SYS_DOORBELL_MASK(port) ((port) + 0x2) +#define SYS_DOORBELL_INTR(port) ((port) + 0x3) + + +/* Used to store configuration info read from config i/o registers. Most of + this is not used yet, but might as well save it. + + This structure also holds port addresses that are not at the same offset + on the 14F and 24F. + + This structure holds all data that must be duplicated to support multiple + adapters. */ + +static struct ultrastor_config +{ + unsigned short port_address; /* base address of card */ + unsigned short doorbell_address; /* base address of doorbell CSRs */ + unsigned short ogm_address; /* base address of OGM */ + unsigned short icm_address; /* base address of ICM */ + const void *bios_segment; + unsigned char interrupt: 4; + unsigned char dma_channel: 3; + unsigned char bios_drive_number: 1; + unsigned char heads; + unsigned char sectors; + unsigned char ha_scsi_id: 3; + unsigned char subversion: 4; + unsigned char revision; + /* The slot number is used to distinguish the 24F (slot != 0) from + the 14F and 34F (slot == 0). */ + unsigned char slot; + +#ifdef PRINT_U24F_VERSION + volatile int csir_done; +#endif + + /* A pool of MSCP structures for this adapter, and a bitmask of + busy structures. (If ULTRASTOR_14F_MAX_CMDS == 1, a 1 byte + busy flag is used instead.) */ + +#if ULTRASTOR_MAX_CMDS == 1 + unsigned char mscp_busy; +#else + unsigned short mscp_free; +#endif + volatile unsigned char aborted[ULTRASTOR_MAX_CMDS]; + struct mscp mscp[ULTRASTOR_MAX_CMDS]; +} config = {0}; + +/* Set this to 1 to reset the SCSI bus on error. */ +int ultrastor_bus_reset = 0; + + +/* Allowed BIOS base addresses (NULL indicates reserved) */ +static const void *const bios_segment_table[8] = { + NULL, (void *)0xC4000, (void *)0xC8000, (void *)0xCC000, + (void *)0xD0000, (void *)0xD4000, (void *)0xD8000, (void *)0xDC000, +}; + +/* Allowed IRQs for 14f */ +static const unsigned char interrupt_table_14f[4] = { 15, 14, 11, 10 }; + +/* Allowed DMA channels for 14f (0 indicates reserved) */ +static const unsigned char dma_channel_table_14f[4] = { 5, 6, 7, 0 }; + +/* Head/sector mappings allowed by 14f */ +static const struct { + unsigned char heads; + unsigned char sectors; +} mapping_table[4] = { { 16, 63 }, { 64, 32 }, { 64, 63 }, { 64, 32 } }; + +#ifndef PORT_OVERRIDE +/* ??? A probe of address 0x310 screws up NE2000 cards */ +static const unsigned short ultrastor_ports_14f[] = { + 0x330, 0x340, /*0x310,*/ 0x230, 0x240, 0x210, 0x130, 0x140, +}; +#endif + +static void ultrastor_interrupt(int, void *, struct pt_regs *); +static inline void build_sg_list(struct mscp *, Scsi_Cmnd *SCpnt); + + +static inline int find_and_clear_bit_16(unsigned short *field) +{ + int rv; + unsigned long flags; + + save_flags(flags); + cli(); + if (*field == 0) panic("No free mscp"); + asm("xorl %0,%0\n0:\tbsfw %1,%w0\n\tbtr %0,%1\n\tjnc 0b" + : "=&r" (rv), "=m" (*field) : "1" (*field)); + restore_flags(flags); + return rv; +} + +/* This has been re-implemented with the help of Richard Earnshaw, + <rwe@pegasus.esprit.ec.org> and works with gcc-2.5.8 and gcc-2.6.0. + The instability noted by jfc below appears to be a bug in + gcc-2.5.x when compiling w/o optimization. --Caleb + + This asm is fragile: it doesn't work without the casts and it may + not work without optimization. Maybe I should add a swap builtin + to gcc. --jfc */ +static inline unsigned char xchgb(unsigned char reg, + volatile unsigned char *mem) +{ + __asm__ ("xchgb %0,%1" : "=q" (reg), "=m" (*mem) : "0" (reg)); + return reg; +} + +#if ULTRASTOR_DEBUG & (UD_COMMAND | UD_ABORT) + +static void log_ultrastor_abort(register struct ultrastor_config *config, + int command) +{ + static char fmt[80] = "abort %d (%x); MSCP free pool: %x;"; + register int i; + int flags; + save_flags(flags); + cli(); + + for (i = 0; i < ULTRASTOR_MAX_CMDS; i++) + { + fmt[20 + i*2] = ' '; + if (! (config->mscp_free & (1 << i))) + fmt[21 + i*2] = '0' + config->mscp[i].target_id; + else + fmt[21 + i*2] = '-'; + } + fmt[20 + ULTRASTOR_MAX_CMDS * 2] = '\n'; + fmt[21 + ULTRASTOR_MAX_CMDS * 2] = 0; + printk(fmt, command, &config->mscp[command], config->mscp_free); + restore_flags(flags); +} +#endif + +static int ultrastor_14f_detect(Scsi_Host_Template * tpnt) +{ + size_t i; + unsigned char in_byte, version_byte = 0; + struct config_1 { + unsigned char bios_segment: 3; + unsigned char removable_disks_as_fixed: 1; + unsigned char interrupt: 2; + unsigned char dma_channel: 2; + } config_1; + struct config_2 { + unsigned char ha_scsi_id: 3; + unsigned char mapping_mode: 2; + unsigned char bios_drive_number: 1; + unsigned char tfr_port: 2; + } config_2; + +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US14F: detect: called\n"); +#endif + + /* If a 24F has already been configured, don't look for a 14F. */ + if (config.bios_segment) + return FALSE; + +#ifdef PORT_OVERRIDE + if(check_region(PORT_OVERRIDE, 0xc)) { + printk("Ultrastor I/O space already in use\n"); + return FALSE; + }; + config.port_address = PORT_OVERRIDE; +#else + for (i = 0; i < ARRAY_SIZE(ultrastor_ports_14f); i++) { + if(check_region(ultrastor_ports_14f[i], 0x0c)) continue; + config.port_address = ultrastor_ports_14f[i]; +#endif + +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US14F: detect: testing port address %03X\n", config.port_address); +#endif + + in_byte = inb(U14F_PRODUCT_ID(config.port_address)); + if (in_byte != US14F_PRODUCT_ID_0) { +#if (ULTRASTOR_DEBUG & UD_DETECT) +# ifdef PORT_OVERRIDE + printk("US14F: detect: wrong product ID 0 - %02X\n", in_byte); +# else + printk("US14F: detect: no adapter at port %03X\n", config.port_address); +# endif +#endif +#ifdef PORT_OVERRIDE + return FALSE; +#else + continue; +#endif + } + in_byte = inb(U14F_PRODUCT_ID(config.port_address) + 1); + /* Only upper nibble is significant for Product ID 1 */ + if ((in_byte & 0xF0) != US14F_PRODUCT_ID_1) { +#if (ULTRASTOR_DEBUG & UD_DETECT) +# ifdef PORT_OVERRIDE + printk("US14F: detect: wrong product ID 1 - %02X\n", in_byte); +# else + printk("US14F: detect: no adapter at port %03X\n", config.port_address); +# endif +#endif +#ifdef PORT_OVERRIDE + return FALSE; +#else + continue; +#endif + } + version_byte = in_byte; +#ifndef PORT_OVERRIDE + break; + } + if (i == ARRAY_SIZE(ultrastor_ports_14f)) { +# if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US14F: detect: no port address found!\n"); +# endif + return FALSE; + } +#endif + +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US14F: detect: adapter found at port address %03X\n", + config.port_address); +#endif + + /* Set local doorbell mask to disallow bus reset unless + ultrastor_bus_reset is true. */ + outb(ultrastor_bus_reset ? 0xc2 : 0x82, LCL_DOORBELL_MASK(config.port_address)); + + /* All above tests passed, must be the right thing. Get some useful + info. */ + + request_region(config.port_address, 0x0c,"ultrastor"); + /* Register the I/O space that we use */ + + *(char *)&config_1 = inb(CONFIG(config.port_address + 0)); + *(char *)&config_2 = inb(CONFIG(config.port_address + 1)); + config.bios_segment = bios_segment_table[config_1.bios_segment]; + config.doorbell_address = config.port_address; + config.ogm_address = config.port_address + 0x8; + config.icm_address = config.port_address + 0xC; + config.interrupt = interrupt_table_14f[config_1.interrupt]; + config.ha_scsi_id = config_2.ha_scsi_id; + config.heads = mapping_table[config_2.mapping_mode].heads; + config.sectors = mapping_table[config_2.mapping_mode].sectors; + config.bios_drive_number = config_2.bios_drive_number; + config.subversion = (version_byte & 0x0F); + if (config.subversion == U34F) + config.dma_channel = 0; + else + config.dma_channel = dma_channel_table_14f[config_1.dma_channel]; + + if (!config.bios_segment) { +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US14F: detect: not detected.\n"); +#endif + return FALSE; + } + + /* Final consistency check, verify previous info. */ + if (config.subversion != U34F) + if (!config.dma_channel || !(config_2.tfr_port & 0x2)) { +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US14F: detect: consistency check failed\n"); +#endif + return FALSE; + } + + /* If we were TRULY paranoid, we could issue a host adapter inquiry + command here and verify the data returned. But frankly, I'm + exhausted! */ + + /* Finally! Now I'm satisfied... */ +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US14F: detect: detect succeeded\n" + " Port address: %03X\n" + " BIOS segment: %05X\n" + " Interrupt: %u\n" + " DMA channel: %u\n" + " H/A SCSI ID: %u\n" + " Subversion: %u\n", + config.port_address, config.bios_segment, config.interrupt, + config.dma_channel, config.ha_scsi_id, config.subversion); +#endif + tpnt->this_id = config.ha_scsi_id; + tpnt->unchecked_isa_dma = (config.subversion != U34F); + +#if ULTRASTOR_MAX_CMDS > 1 + config.mscp_free = ~0; +#endif + + if (request_irq(config.interrupt, ultrastor_interrupt, 0, "Ultrastor", NULL)) { + printk("Unable to allocate IRQ%u for UltraStor controller.\n", + config.interrupt); + return FALSE; + } + if (config.dma_channel && request_dma(config.dma_channel,"Ultrastor")) { + printk("Unable to allocate DMA channel %u for UltraStor controller.\n", + config.dma_channel); + free_irq(config.interrupt, NULL); + return FALSE; + } + tpnt->sg_tablesize = ULTRASTOR_14F_MAX_SG; + printk("UltraStor driver version" VERSION ". Using %d SG lists.\n", + ULTRASTOR_14F_MAX_SG); + + return TRUE; +} + +static int ultrastor_24f_detect(Scsi_Host_Template * tpnt) +{ + register int i; + struct Scsi_Host * shpnt = NULL; + +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US24F: detect"); +#endif + + /* probe each EISA slot at slot address C80 */ + for (i = 1; i < 15; i++) + { + unsigned char config_1, config_2; + unsigned short addr = (i << 12) | ULTRASTOR_24F_PORT; + + if (inb(addr) != US24F_PRODUCT_ID_0 && + inb(addr+1) != US24F_PRODUCT_ID_1 && + inb(addr+2) != US24F_PRODUCT_ID_2) + continue; + + config.revision = inb(addr+3); + config.slot = i; + if (! (inb(addr+4) & 1)) + { +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("U24F: found disabled card in slot %u\n", i); +#endif + continue; + } +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("U24F: found card in slot %u\n", i); +#endif + config_1 = inb(addr + 5); + config.bios_segment = bios_segment_table[config_1 & 7]; + switch(config_1 >> 4) + { + case 1: + config.interrupt = 15; + break; + case 2: + config.interrupt = 14; + break; + case 4: + config.interrupt = 11; + break; + case 8: + config.interrupt = 10; + break; + default: + printk("U24F: invalid IRQ\n"); + return FALSE; + } + if (request_irq(config.interrupt, ultrastor_interrupt, 0, "Ultrastor", NULL)) + { + printk("Unable to allocate IRQ%u for UltraStor controller.\n", + config.interrupt); + return FALSE; + } + /* BIOS addr set */ + /* base port set */ + config.port_address = addr; + config.doorbell_address = addr + 12; + config.ogm_address = addr + 0x17; + config.icm_address = addr + 0x1C; + config_2 = inb(addr + 7); + config.ha_scsi_id = config_2 & 7; + config.heads = mapping_table[(config_2 >> 3) & 3].heads; + config.sectors = mapping_table[(config_2 >> 3) & 3].sectors; +#if (ULTRASTOR_DEBUG & UD_DETECT) + printk("US24F: detect: detect succeeded\n" + " Port address: %03X\n" + " BIOS segment: %05X\n" + " Interrupt: %u\n" + " H/A SCSI ID: %u\n", + config.port_address, config.bios_segment, + config.interrupt, config.ha_scsi_id); +#endif + tpnt->this_id = config.ha_scsi_id; + tpnt->unchecked_isa_dma = 0; + tpnt->sg_tablesize = ULTRASTOR_24F_MAX_SG; + + shpnt = scsi_register(tpnt, 0); + shpnt->irq = config.interrupt; + shpnt->dma_channel = config.dma_channel; + shpnt->io_port = config.port_address; + +#if ULTRASTOR_MAX_CMDS > 1 + config.mscp_free = ~0; +#endif + /* Mark ICM and OGM free */ + outb(0, addr + 0x16); + outb(0, addr + 0x1B); + + /* Set local doorbell mask to disallow bus reset unless + ultrastor_bus_reset is true. */ + outb(ultrastor_bus_reset ? 0xc2 : 0x82, LCL_DOORBELL_MASK(addr+12)); + outb(0x02, SYS_DOORBELL_MASK(addr+12)); + printk("UltraStor driver version " VERSION ". Using %d SG lists.\n", + tpnt->sg_tablesize); + return TRUE; + } + return FALSE; +} + +int ultrastor_detect(Scsi_Host_Template * tpnt) +{ + tpnt->proc_dir = &proc_scsi_ultrastor; + return ultrastor_14f_detect(tpnt) || ultrastor_24f_detect(tpnt); +} + +const char *ultrastor_info(struct Scsi_Host * shpnt) +{ + static char buf[64]; + + if (config.slot) + sprintf(buf, "UltraStor 24F SCSI @ Slot %u IRQ%u", + config.slot, config.interrupt); + else if (config.subversion) + sprintf(buf, "UltraStor 34F SCSI @ Port %03X BIOS %05X IRQ%u", + config.port_address, (int)config.bios_segment, + config.interrupt); + else + sprintf(buf, "UltraStor 14F SCSI @ Port %03X BIOS %05X IRQ%u DMA%u", + config.port_address, (int)config.bios_segment, + config.interrupt, config.dma_channel); + return buf; +} + +static inline void build_sg_list(register struct mscp *mscp, Scsi_Cmnd *SCpnt) +{ + struct scatterlist *sl; + long transfer_length = 0; + int i, max; + + sl = (struct scatterlist *) SCpnt->request_buffer; + max = SCpnt->use_sg; + for (i = 0; i < max; i++) { + mscp->sglist[i].address = (unsigned int)sl[i].address; + mscp->sglist[i].num_bytes = sl[i].length; + transfer_length += sl[i].length; + } + mscp->number_of_sg_list = max; + mscp->transfer_data = (unsigned int)mscp->sglist; + /* ??? May not be necessary. Docs are unclear as to whether transfer + length field is ignored or whether it should be set to the total + number of bytes of the transfer. */ + mscp->transfer_data_length = transfer_length; +} + +int ultrastor_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *)) +{ + register struct mscp *my_mscp; +#if ULTRASTOR_MAX_CMDS > 1 + int mscp_index; +#endif + unsigned int status; + int flags; + + /* Next test is for debugging; "can't happen" */ + if ((config.mscp_free & ((1U << ULTRASTOR_MAX_CMDS) - 1)) == 0) + panic("ultrastor_queuecommand: no free MSCP\n"); + mscp_index = find_and_clear_bit_16(&config.mscp_free); + + /* Has the command been aborted? */ + if (xchgb(0xff, &config.aborted[mscp_index]) != 0) + { + status = DID_ABORT << 16; + goto aborted; + } + + my_mscp = &config.mscp[mscp_index]; + +#if 1 + /* This way is faster. */ + *(unsigned char *)my_mscp = OP_SCSI | (DTD_SCSI << 3); +#else + my_mscp->opcode = OP_SCSI; + my_mscp->xdir = DTD_SCSI; + my_mscp->dcn = FALSE; +#endif + /* Tape drives don't work properly if the cache is used. The SCSI + READ command for a tape doesn't have a block offset, and the adapter + incorrectly assumes that all reads from the tape read the same + blocks. Results will depend on read buffer size and other disk + activity. + + ??? Which other device types should never use the cache? */ + my_mscp->ca = SCpnt->device->type != TYPE_TAPE; + my_mscp->target_id = SCpnt->target; + my_mscp->ch_no = 0; + my_mscp->lun = SCpnt->lun; + if (SCpnt->use_sg) { + /* Set scatter/gather flag in SCSI command packet */ + my_mscp->sg = TRUE; + build_sg_list(my_mscp, SCpnt); + } else { + /* Unset scatter/gather flag in SCSI command packet */ + my_mscp->sg = FALSE; + my_mscp->transfer_data = (unsigned int)SCpnt->request_buffer; + my_mscp->transfer_data_length = SCpnt->request_bufflen; + } + my_mscp->command_link = 0; /*???*/ + my_mscp->scsi_command_link_id = 0; /*???*/ + my_mscp->length_of_sense_byte = sizeof SCpnt->sense_buffer; + my_mscp->length_of_scsi_cdbs = SCpnt->cmd_len; + memcpy(my_mscp->scsi_cdbs, SCpnt->cmnd, my_mscp->length_of_scsi_cdbs); + my_mscp->adapter_status = 0; + my_mscp->target_status = 0; + my_mscp->sense_data = (unsigned int)&SCpnt->sense_buffer; + my_mscp->done = done; + my_mscp->SCint = SCpnt; + SCpnt->host_scribble = (unsigned char *)my_mscp; + + /* Find free OGM slot. On 24F, look for OGM status byte == 0. + On 14F and 34F, wait for local interrupt pending flag to clear. */ + + retry: + if (config.slot) + while (inb(config.ogm_address - 1) != 0 && + config.aborted[mscp_index] == 0xff) barrier(); + + /* else??? */ + + while ((inb(LCL_DOORBELL_INTR(config.doorbell_address)) & + (config.slot ? 2 : 1)) + && config.aborted[mscp_index] == 0xff) barrier(); + + /* To avoid race conditions, make the code to write to the adapter + atomic. This simplifies the abort code. */ + + save_flags(flags); + cli(); + + if (inb(LCL_DOORBELL_INTR(config.doorbell_address)) & + (config.slot ? 2 : 1)) + { + restore_flags(flags); + goto retry; + } + + status = xchgb(0, &config.aborted[mscp_index]); + if (status != 0xff) { + restore_flags(flags); + +#if ULTRASTOR_DEBUG & (UD_COMMAND | UD_ABORT) + printk("USx4F: queuecommand: aborted\n"); +#if ULTRASTOR_MAX_CMDS > 1 + log_ultrastor_abort(&config, mscp_index); +#endif +#endif + status <<= 16; + + aborted: + set_bit(mscp_index, &config.mscp_free); + /* If the driver queues commands, call the done proc here. Otherwise + return an error. */ +#if ULTRASTOR_MAX_CMDS > 1 + SCpnt->result = status; + done(SCpnt); + return 0; +#else + return status; +#endif + } + + /* Store pointer in OGM address bytes */ + outl((unsigned int)my_mscp, config.ogm_address); + + /* Issue OGM interrupt */ + if (config.slot) { + /* Write OGM command register on 24F */ + outb(1, config.ogm_address - 1); + outb(0x2, LCL_DOORBELL_INTR(config.doorbell_address)); + } else { + outb(0x1, LCL_DOORBELL_INTR(config.doorbell_address)); + } + + restore_flags(flags); + +#if (ULTRASTOR_DEBUG & UD_COMMAND) + printk("USx4F: queuecommand: returning\n"); +#endif + + return 0; +} + +/* This code must deal with 2 cases: + + 1. The command has not been written to the OGM. In this case, set + the abort flag and return. + + 2. The command has been written to the OGM and is stuck somewhere in + the adapter. + + 2a. On a 24F, ask the adapter to abort the command. It will interrupt + when it does. + + 2b. Call the command's done procedure. + + */ + +int ultrastor_abort(Scsi_Cmnd *SCpnt) +{ +#if ULTRASTOR_DEBUG & UD_ABORT + char out[108]; + unsigned char icm_status = 0, ogm_status = 0; + unsigned int icm_addr = 0, ogm_addr = 0; +#endif + unsigned int mscp_index; + unsigned char old_aborted; + void (*done)(Scsi_Cmnd *); + + if(config.slot) + return SCSI_ABORT_SNOOZE; /* Do not attempt an abort for the 24f */ + + /* Simple consistency checking */ + if(!SCpnt->host_scribble) + return SCSI_ABORT_NOT_RUNNING; + + mscp_index = ((struct mscp *)SCpnt->host_scribble) - config.mscp; + if (mscp_index >= ULTRASTOR_MAX_CMDS) + panic("Ux4F aborting invalid MSCP"); + +#if ULTRASTOR_DEBUG & UD_ABORT + if (config.slot) + { + int port0 = (config.slot << 12) | 0xc80; + int i; + int flags; + save_flags(flags); + cli(); + strcpy(out, "OGM %d:%x ICM %d:%x ports: "); + for (i = 0; i < 16; i++) + { + unsigned char p = inb(port0 + i); + out[28 + i * 3] = "0123456789abcdef"[p >> 4]; + out[29 + i * 3] = "0123456789abcdef"[p & 15]; + out[30 + i * 3] = ' '; + } + out[28 + i * 3] = '\n'; + out[29 + i * 3] = 0; + ogm_status = inb(port0 + 22); + ogm_addr = inl(port0 + 23); + icm_status = inb(port0 + 27); + icm_addr = inl(port0 + 28); + restore_flags(flags); + } + + /* First check to see if an interrupt is pending. I suspect the SiS + chipset loses interrupts. (I also suspect is mangles data, but + one bug at a time... */ + if (config.slot ? inb(config.icm_address - 1) == 2 : + (inb(SYS_DOORBELL_INTR(config.doorbell_address)) & 1)) + { + int flags; + save_flags(flags); + printk("Ux4F: abort while completed command pending\n"); + restore_flags(flags); + cli(); + ultrastor_interrupt(0, NULL, NULL); + restore_flags(flags); + return SCSI_ABORT_SUCCESS; /* FIXME - is this correct? -ERY */ + } +#endif + + old_aborted = xchgb(DID_ABORT, &config.aborted[mscp_index]); + + /* aborted == 0xff is the signal that queuecommand has not yet sent + the command. It will notice the new abort flag and fail. */ + if (old_aborted == 0xff) + return SCSI_ABORT_SUCCESS; + + /* On 24F, send an abort MSCP request. The adapter will interrupt + and the interrupt handler will call done. */ + if (config.slot && inb(config.ogm_address - 1) == 0) + { + int flags; + + save_flags(flags); + cli(); + outl((int)&config.mscp[mscp_index], config.ogm_address); + inb(0xc80); /* delay */ + outb(0x80, config.ogm_address - 1); + outb(0x2, LCL_DOORBELL_INTR(config.doorbell_address)); +#if ULTRASTOR_DEBUG & UD_ABORT + log_ultrastor_abort(&config, mscp_index); + printk(out, ogm_status, ogm_addr, icm_status, icm_addr); +#endif + restore_flags(flags); + return SCSI_ABORT_PENDING; + } + +#if ULTRASTOR_DEBUG & UD_ABORT + log_ultrastor_abort(&config, mscp_index); +#endif + + /* Can't request a graceful abort. Either this is not a 24F or + the OGM is busy. Don't free the command -- the adapter might + still be using it. Setting SCint = 0 causes the interrupt + handler to ignore the command. */ + + /* FIXME - devices that implement soft resets will still be running + the command after a bus reset. We would probably rather leave + the command in the queue. The upper level code will automatically + leave the command in the active state instead of requeueing it. ERY */ + +#if ULTRASTOR_DEBUG & UD_ABORT + if (config.mscp[mscp_index].SCint != SCpnt) + printk("abort: command mismatch, %p != %p\n", + config.mscp[mscp_index].SCint, SCpnt); +#endif + if (config.mscp[mscp_index].SCint == 0) + return SCSI_ABORT_NOT_RUNNING; + + if (config.mscp[mscp_index].SCint != SCpnt) panic("Bad abort"); + config.mscp[mscp_index].SCint = 0; + done = config.mscp[mscp_index].done; + config.mscp[mscp_index].done = 0; + SCpnt->result = DID_ABORT << 16; + /* I worry about reentrancy in scsi.c */ + done(SCpnt); + + /* Need to set a timeout here in case command never completes. */ + return SCSI_ABORT_SUCCESS; +} + +int ultrastor_reset(Scsi_Cmnd * SCpnt, unsigned int reset_flags) +{ + int flags; + register int i; +#if (ULTRASTOR_DEBUG & UD_RESET) + printk("US14F: reset: called\n"); +#endif + + if(config.slot) + return SCSI_RESET_PUNT; /* Do not attempt a reset for the 24f */ + + save_flags(flags); + cli(); + + /* Reset the adapter and SCSI bus. The SCSI bus reset can be + inhibited by clearing ultrastor_bus_reset before probe. */ + outb(0xc0, LCL_DOORBELL_INTR(config.doorbell_address)); + if (config.slot) + { + outb(0, config.ogm_address - 1); + outb(0, config.icm_address - 1); + } + +#if ULTRASTOR_MAX_CMDS == 1 + if (config.mscp_busy && config.mscp->done && config.mscp->SCint) + { + config.mscp->SCint->result = DID_RESET << 16; + config.mscp->done(config.mscp->SCint); + } + config.mscp->SCint = 0; +#else + for (i = 0; i < ULTRASTOR_MAX_CMDS; i++) + { + if (! (config.mscp_free & (1 << i)) && + config.mscp[i].done && config.mscp[i].SCint) + { + config.mscp[i].SCint->result = DID_RESET << 16; + config.mscp[i].done(config.mscp[i].SCint); + config.mscp[i].done = 0; + } + config.mscp[i].SCint = 0; + } +#endif + + /* FIXME - if the device implements soft resets, then the command + will still be running. ERY */ + + memset((unsigned char *)config.aborted, 0, sizeof config.aborted); +#if ULTRASTOR_MAX_CMDS == 1 + config.mscp_busy = 0; +#else + config.mscp_free = ~0; +#endif + + restore_flags(flags); + return SCSI_RESET_SUCCESS; + +} + +int ultrastor_biosparam(Disk * disk, kdev_t dev, int * dkinfo) +{ + int size = disk->capacity; + unsigned int s = config.heads * config.sectors; + + dkinfo[0] = config.heads; + dkinfo[1] = config.sectors; + dkinfo[2] = size / s; /* Ignore partial cylinders */ +#if 0 + if (dkinfo[2] > 1024) + dkinfo[2] = 1024; +#endif + return 0; +} + +static void ultrastor_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned int status; +#if ULTRASTOR_MAX_CMDS > 1 + unsigned int mscp_index; +#endif + register struct mscp *mscp; + void (*done)(Scsi_Cmnd *); + Scsi_Cmnd *SCtmp; + +#if ULTRASTOR_MAX_CMDS == 1 + mscp = &config.mscp[0]; +#else + mscp = (struct mscp *)inl(config.icm_address); + mscp_index = mscp - config.mscp; + if (mscp_index >= ULTRASTOR_MAX_CMDS) { + printk("Ux4F interrupt: bad MSCP address %x\n", (unsigned int) mscp); + /* A command has been lost. Reset and report an error + for all commands. */ + ultrastor_reset(NULL, 0); + return; + } +#endif + + /* Clean ICM slot (set ICMINT bit to 0) */ + if (config.slot) { + unsigned char icm_status = inb(config.icm_address - 1); +#if ULTRASTOR_DEBUG & (UD_INTERRUPT|UD_ERROR|UD_ABORT) + if (icm_status != 1 && icm_status != 2) + printk("US24F: ICM status %x for MSCP %d (%x)\n", icm_status, + mscp_index, (unsigned int) mscp); +#endif + /* The manual says clear interrupt then write 0 to ICM status. + This seems backwards, but I'll do it anyway. --jfc */ + outb(2, SYS_DOORBELL_INTR(config.doorbell_address)); + outb(0, config.icm_address - 1); + if (icm_status == 4) { + printk("UltraStor abort command failed\n"); + return; + } + if (icm_status == 3) { + void (*done)(Scsi_Cmnd *) = mscp->done; + if (done) { + mscp->done = 0; + mscp->SCint->result = DID_ABORT << 16; + done(mscp->SCint); + } + return; + } + } else { + outb(1, SYS_DOORBELL_INTR(config.doorbell_address)); + } + + SCtmp = mscp->SCint; + mscp->SCint = NULL; + + if (SCtmp == 0) + { +#if ULTRASTOR_DEBUG & (UD_ABORT|UD_INTERRUPT) + printk("MSCP %d (%x): no command\n", mscp_index, (unsigned int) mscp); +#endif +#if ULTRASTOR_MAX_CMDS == 1 + config.mscp_busy = FALSE; +#else + set_bit(mscp_index, &config.mscp_free); +#endif + config.aborted[mscp_index] = 0; + return; + } + + /* Save done locally and zero before calling. This is needed as + once we call done, we may get another command queued before this + interrupt service routine can return. */ + done = mscp->done; + mscp->done = 0; + + /* Let the higher levels know that we're done */ + switch (mscp->adapter_status) + { + case 0: + status = DID_OK << 16; + break; + case 0x01: /* invalid command */ + case 0x02: /* invalid parameters */ + case 0x03: /* invalid data list */ + default: + status = DID_ERROR << 16; + break; + case 0x84: /* SCSI bus abort */ + status = DID_ABORT << 16; + break; + case 0x91: + status = DID_TIME_OUT << 16; + break; + } + + SCtmp->result = status | mscp->target_status; + + SCtmp->host_scribble = 0; + + /* Free up mscp block for next command */ +#if ULTRASTOR_MAX_CMDS == 1 + config.mscp_busy = FALSE; +#else + set_bit(mscp_index, &config.mscp_free); +#endif + +#if ULTRASTOR_DEBUG & (UD_ABORT|UD_INTERRUPT) + if (config.aborted[mscp_index]) + printk("Ux4 interrupt: MSCP %d (%x) aborted = %d\n", + mscp_index, (unsigned int) mscp, config.aborted[mscp_index]); +#endif + config.aborted[mscp_index] = 0; + + if (done) + done(SCtmp); + else + printk("US14F: interrupt: unexpected interrupt\n"); + + if (config.slot ? inb(config.icm_address - 1) : + (inb(SYS_DOORBELL_INTR(config.doorbell_address)) & 1)) +#if (ULTRASTOR_DEBUG & UD_MULTI_CMD) + printk("Ux4F: multiple commands completed\n"); +#else + ; +#endif + +#if (ULTRASTOR_DEBUG & UD_INTERRUPT) + printk("USx4F: interrupt: returning\n"); +#endif +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = ULTRASTOR_14F; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/ultrastor.h b/linux/src/drivers/scsi/ultrastor.h new file mode 100644 index 00000000..7a40acc5 --- /dev/null +++ b/linux/src/drivers/scsi/ultrastor.h @@ -0,0 +1,102 @@ +/* + * ultrastor.c (C) 1991 David B. Gentzel + * Low-level scsi driver for UltraStor 14F + * by David B. Gentzel, Whitfield Software Services, Carnegie, PA + * (gentzel@nova.enet.dec.com) + * scatter/gather added by Scott Taylor (n217cg@tamuts.tamu.edu) + * 24F support by John F. Carr (jfc@athena.mit.edu) + * John's work modified by Caleb Epstein (cae@jpmorgan.com) and + * Eric Youngdale (eric@tantalus.nrl.navy.mil). + * Thanks to UltraStor for providing the necessary documentation + */ + +#ifndef _ULTRASTOR_H +#define _ULTRASTOR_H +#include <linux/kdev_t.h> + +int ultrastor_detect(Scsi_Host_Template *); +const char *ultrastor_info(struct Scsi_Host * shpnt); +int ultrastor_queuecommand(Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int ultrastor_abort(Scsi_Cmnd *); +int ultrastor_reset(Scsi_Cmnd *, unsigned int); +int ultrastor_biosparam(Disk *, kdev_t, int *); + + +#define ULTRASTOR_14F_MAX_SG 16 +#define ULTRASTOR_24F_MAX_SG 33 + +#define ULTRASTOR_MAX_CMDS_PER_LUN 5 +#define ULTRASTOR_MAX_CMDS 16 + +#define ULTRASTOR_24F_PORT 0xC80 + + +#define ULTRASTOR_14F { NULL, NULL, /* Ptr for modules*/ \ + NULL, \ + NULL, \ + "UltraStor 14F/24F/34F", \ + ultrastor_detect, \ + NULL, /* Release */ \ + ultrastor_info, \ + 0, \ + ultrastor_queuecommand, \ + ultrastor_abort, \ + ultrastor_reset, \ + 0, \ + ultrastor_biosparam, \ + ULTRASTOR_MAX_CMDS, \ + 0, \ + ULTRASTOR_14F_MAX_SG, \ + ULTRASTOR_MAX_CMDS_PER_LUN, \ + 0, \ + 1, \ + ENABLE_CLUSTERING } + + +#ifdef ULTRASTOR_PRIVATE + +#define UD_ABORT 0x0001 +#define UD_COMMAND 0x0002 +#define UD_DETECT 0x0004 +#define UD_INTERRUPT 0x0008 +#define UD_RESET 0x0010 +#define UD_MULTI_CMD 0x0020 +#define UD_CSIR 0x0040 +#define UD_ERROR 0x0080 + +/* #define PORT_OVERRIDE 0x330 */ + +/* Values for the PRODUCT_ID ports for the 14F */ +#define US14F_PRODUCT_ID_0 0x56 +#define US14F_PRODUCT_ID_1 0x40 /* NOTE: Only upper nibble is used */ + +#define US24F_PRODUCT_ID_0 0x56 +#define US24F_PRODUCT_ID_1 0x63 +#define US24F_PRODUCT_ID_2 0x02 + +/* Subversion values */ +#define U14F 0 +#define U34F 1 + +/* MSCP field values */ + +/* Opcode */ +#define OP_HOST_ADAPTER 0x1 +#define OP_SCSI 0x2 +#define OP_RESET 0x4 + +/* Date Transfer Direction */ +#define DTD_SCSI 0x0 +#define DTD_IN 0x1 +#define DTD_OUT 0x2 +#define DTD_NONE 0x3 + +/* Host Adapter command subcodes */ +#define HA_CMD_INQUIRY 0x1 +#define HA_CMD_SELF_DIAG 0x2 +#define HA_CMD_READ_BUFF 0x3 +#define HA_CMD_WRITE_BUFF 0x4 + +#endif + +#endif diff --git a/linux/src/drivers/scsi/wd7000.c b/linux/src/drivers/scsi/wd7000.c new file mode 100644 index 00000000..d910e27b --- /dev/null +++ b/linux/src/drivers/scsi/wd7000.c @@ -0,0 +1,1452 @@ +/* $Id: wd7000.c,v 1.1 1999/04/26 05:55:18 tb Exp $ + * linux/drivers/scsi/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 + * accommodate Eric Youngdale's modifications to scsi.c. Nov 1992. + * + * Additional changes to support scatter/gather. Dec. 1992. tw/jb + * + * No longer tries to reset SCSI bus at boot (it wasn't working anyway). + * Rewritten to support multiple host adapters. + * Miscellaneous cleanup. + * So far, still doesn't do reset or abort correctly, since I have no idea + * how to do them with this board (8^(. Jan 1994 jb + * + * This driver now supports both of the two standard configurations (per + * the 3.36 Owner's Manual, my latest reference) by the same method as + * before; namely, by looking for a BIOS signature. Thus, the location of + * the BIOS signature determines the board configuration. Until I have + * time to do something more flexible, users should stick to one of the + * following: + * + * Standard configuration for single-adapter systems: + * - BIOS at CE00h + * - I/O base address 350h + * - IRQ level 15 + * - DMA channel 6 + * Standard configuration for a second adapter in a system: + * - BIOS at C800h + * - I/O base address 330h + * - IRQ level 11 + * - DMA channel 5 + * + * Anyone who can recompile the kernel is welcome to add others as need + * arises, but unpredictable results may occur if there are conflicts. + * In any event, if there are multiple adapters in a system, they MUST + * use different I/O bases, IRQ levels, and DMA channels, since they will be + * indistinguishable (and in direct conflict) otherwise. + * + * As a point of information, the NO_OP command toggles the CMD_RDY bit + * of the status port, and this fact could be used as a test for the I/O + * base address (or more generally, board detection). There is an interrupt + * status port, so IRQ probing could also be done. I suppose the full + * DMA diagnostic could be used to detect the DMA channel being used. I + * haven't done any of this, though, because I think there's too much of + * a chance that such explorations could be destructive, if some other + * board's resources are used inadvertently. So, call me a wimp, but I + * don't want to try it. The only kind of exploration I trust is memory + * exploration, since it's more certain that reading memory won't be + * destructive. + * + * More to my liking would be a LILO boot command line specification, such + * as is used by the aha152x driver (and possibly others). I'll look into + * it, as I have time... + * + * I get mail occasionally from people who either are using or are + * considering using a WD7000 with Linux. There is a variety of + * nomenclature describing WD7000's. To the best of my knowledge, the + * following is a brief summary (from an old WD doc - I don't work for + * them or anything like that): + * + * WD7000-FASST2: This is a WD7000 board with the real-mode SST ROM BIOS + * installed. Last I heard, the BIOS was actually done by Columbia + * Data Products. The BIOS is only used by this driver (and thus + * by Linux) to identify the board; none of it can be executed under + * Linux. + * + * WD7000-ASC: This is the original adapter board, with or without BIOS. + * The board uses a WD33C93 or WD33C93A SBIC, which in turn is + * controlled by an onboard Z80 processor. The board interface + * visible to the host CPU is defined effectively by the Z80's + * firmware, and it is this firmware's revision level that is + * determined and reported by this driver. (The version of the + * on-board BIOS is of no interest whatsoever.) The host CPU has + * no access to the SBIC; hence the fact that it is a WD33C93 is + * also of no interest to this driver. + * + * WD7000-AX: + * WD7000-MX: + * WD7000-EX: These are newer versions of the WD7000-ASC. The -ASC is + * largely built from discrete components; these boards use more + * integration. The -AX is an ISA bus board (like the -ASC), + * the -MX is an MCA (i.e., PS/2) bus board), and the -EX is an + * EISA bus board. + * + * At the time of my documentation, the -?X boards were "future" products, + * and were not yet available. However, I vaguely recall that Thomas + * Wuensche had an -AX, so I believe at least it is supported by this + * driver. I have no personal knowledge of either -MX or -EX boards. + * + * P.S. Just recently, I've discovered (directly from WD and Future + * Domain) that all but the WD7000-EX have been out of production for + * two years now. FD has production rights to the 7000-EX, and are + * producing it under a new name, and with a new BIOS. If anyone has + * one of the FD boards, it would be nice to come up with a signature + * for it. + * J.B. Jan 1994. + * + * + * Revisions by Miroslav Zagorac <zaga@fly.cc.fer.hr> + * + * -- 08/24/1996. -------------------------------------------------------------- + * Enhancement for wd7000_detect function has been made, so you don't have + * to enter BIOS ROM address in initialisation data (see struct Config). + * We cannot detect IRQ, DMA and I/O base address for now, so we have to + * enter them as arguments while wd_7000 is detected. If someone has IRQ, + * DMA or an I/O base address set to some other value, he can enter them in + * a configuration without any problem. + * Also I wrote a function wd7000_setup, so now you can enter WD-7000 + * definition as kernel arguments, as in lilo.conf: + * + * append="wd7000=IRQ,DMA,IO" + * + * PS: If card BIOS ROM is disabled, function wd7000_detect now will recognize + * adapter, unlike the old one. Anyway, BIOS ROM from WD7000 adapter is + * useless for Linux. B^) + * + * -- 09/06/1996. -------------------------------------------------------------- + * Auto detecting of an I/O base address from wd7000_detect function is + * removed, some little bugs too... + * + * Thanks to Roger Scott for driver debugging. + * + * -- 06/07/1997. -------------------------------------------------------------- + * Added support for /proc file system (/proc/scsi/wd7000/[0...] files). + * Now, the driver can handle hard disks with capacity >1GB. + * + * -- 01/15/1998. -------------------------------------------------------------- + * Added support for BUS_ON and BUS_OFF parameters in config line. + * Miscellaneous cleanups. Syntax of the append line is changed to: + * + * append="wd7000=IRQ,DMA,IO[,BUS_ON[,BUS_OFF]]" + * + * , where BUS_ON and BUS_OFF are time in nanoseconds. + * + * -- 03/01/1998. -------------------------------------------------------------- + * The WD7000 driver now works on kernels' >= 2.1.x + * + * -- 06/11/1998. -------------------------------------------------------------- + * Ugly init_scbs, alloc_scbs and free_scb functions are changed with + * scbs_init, scb_alloc and scb_free. Now, source code is identical on + * 2.0.xx and 2.1.xx kernels. + * WD7000 specific definitions are moved from this file to wd7000.h. + * + */ +#ifdef MODULE +# include <linux/module.h> +#endif + +#if (LINUX_VERSION_CODE >= 0x020100) +# include <asm/spinlock.h> +#endif + +#include <stdarg.h> +#include <linux/kernel.h> +#include <linux/head.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <asm/system.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <linux/blk.h> +#include <linux/version.h> +#include <linux/stat.h> +#include "scsi.h" +#include "hosts.h" +#include "sd.h" +#include <scsi/scsicam.h> + +#undef WD7000_DEBUG /* general debug */ +#define WD7000_DEFINES /* This must be defined! */ + +#include "wd7000.h" + + +struct proc_dir_entry proc_scsi_wd7000 = +{ + PROC_SCSI_7000FASST, + 6, + "wd7000", + S_IFDIR | S_IRUGO | S_IXUGO, + 2 +}; + +/* + * (linear) base address for ROM BIOS + */ +static const long wd7000_biosaddr[] = { + 0xc0000, 0xc2000, 0xc4000, 0xc6000, 0xc8000, 0xca000, 0xcc000, 0xce000, + 0xd0000, 0xd2000, 0xd4000, 0xd6000, 0xd8000, 0xda000, 0xdc000, 0xde000 +}; +#define NUM_ADDRS (sizeof (wd7000_biosaddr) / sizeof (long)) + +static const ushort wd7000_iobase[] = { + 0x0300, 0x0308, 0x0310, 0x0318, 0x0320, 0x0328, 0x0330, 0x0338, + 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370, 0x0378, + 0x0380, 0x0388, 0x0390, 0x0398, 0x03a0, 0x03a8, 0x03b0, 0x03b8, + 0x03c0, 0x03c8, 0x03d0, 0x03d8, 0x03e0, 0x03e8, 0x03f0, 0x03f8 +}; +#define NUM_IOPORTS (sizeof (wd7000_iobase) / sizeof (ushort)) + +static const short wd7000_irq[] = { 3, 4, 5, 7, 9, 10, 11, 12, 14, 15 }; +#define NUM_IRQS (sizeof (wd7000_irq) / sizeof (short)) + +static const short wd7000_dma[] = { 5, 6, 7 }; +#define NUM_DMAS (sizeof (wd7000_dma) / sizeof (short)) + +/* + * The following is set up by wd7000_detect, and used thereafter by + * wd7000_intr_handle to map the irq level to the corresponding Adapter. + * Note that if SA_INTERRUPT is not used, wd7000_intr_handle must be + * changed to pick up the IRQ level correctly. + */ +static struct Scsi_Host *wd7000_host[IRQS]; + +/* + * Add here your configuration... + */ +static Config configs[] = +{ + { 15, 6, 0x350, BUS_ON, BUS_OFF }, /* defaults for single adapter */ + { 11, 5, 0x320, BUS_ON, BUS_OFF }, /* defaults for second adapter */ + { 7, 6, 0x350, BUS_ON, BUS_OFF }, /* My configuration (Zaga) */ + { -1, -1, 0x0, BUS_ON, BUS_OFF } /* Empty slot */ +}; +#define NUM_CONFIGS (sizeof(configs)/sizeof(Config)) + +static const Signature signatures[] = +{ + {"SSTBIOS", 0x0000d, 7} /* "SSTBIOS" @ offset 0x0000d */ +}; +#define NUM_SIGNATURES (sizeof(signatures)/sizeof(Signature)) + +/* + * 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]; + + +/* + * END of data/declarations - code follows. + */ +static void setup_error (char *mesg, int *ints) +{ + if (ints[0] == 3) + printk ("wd7000_setup: \"wd7000=%d,%d,0x%x\" -> %s\n", + ints[1], ints[2], ints[3], mesg); + else if (ints[0] == 4) + printk ("wd7000_setup: \"wd7000=%d,%d,0x%x,%d\" -> %s\n", + ints[1], ints[2], ints[3], ints[4], mesg); + else + printk ("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. + */ +void wd7000_setup (char *str, int *ints) +{ + static short wd7000_card_num = 0; + short i, j; + + if (wd7000_card_num >= NUM_CONFIGS) { + printk ("%s: Too many \"wd7000=\" configurations in " + "command line!\n", __FUNCTION__); + return; + } + + if ((ints[0] < 3) || (ints[0] > 5)) + printk ("%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; + } + 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; + } + 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; + } + 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++) + for (j = i + 1; j < wd7000_card_num; j++) + if (configs[i].irq == configs[j].irq) { + setup_error ("duplicated IRQ!", ints); + return; + } + else if (configs[i].dma == configs[j].dma) { + setup_error ("duplicated DMA channel!", ints); + return; + } + else if (configs[i].iobase == configs[j].iobase) { + setup_error ("duplicated I/O base address!", ints); + return; + } + } + +#ifdef WD7000_DEBUG + printk ("%s: IRQ=%d, DMA=%d, I/O=0x%x, BUS_ON=%dns, BUS_OFF=%dns\n", + __FUNCTION__, + 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); +#endif + + wd7000_card_num++; + } +} + + +/* + * Since they're used a lot, I've redone the following from the macros + * formerly in wd7000.h, hopefully to speed them up by getting rid of + * all the shifting (it may not matter; GCC might have done as well anyway). + * + * xany2scsi and xscsi2int were not being used, and are no longer defined. + * (They were simply 4-byte versions of these routines). + */ +static inline void any2scsi (unchar *scsi, int any) +{ + *scsi++ = ((i_u) any).u[2]; + *scsi++ = ((i_u) any).u[1]; + *scsi = ((i_u) any).u[0]; +} + + +static inline int scsi2int (unchar *scsi) +{ + i_u result; + + result.i = 0; /* clears unused bytes */ + result.u[2] = *scsi++; + result.u[1] = *scsi++; + result.u[0] = *scsi; + + return (result.i); +} + + +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) +{ + host->control |= DMA_EN; + outb (host->control, host->iobase + ASC_CONTROL); + set_dma_mode (host->dma, DMA_MODE_CASCADE); + enable_dma (host->dma); +} + + +static inline short WAIT (uint port, uint mask, uint allof, uint noneof) +{ + register uint WAITbits; + register ulong WAITtimeout = jiffies + WAITnexttimeout; + + while (jiffies <= WAITtimeout) { + WAITbits = inb (port) & mask; + + if (((WAITbits & allof) == allof) && ((WAITbits & noneof) == 0)) + return (0); + } + + return (1); +} + + +static inline void delay (uint how_long) +{ + register ulong time = jiffies + how_long; + + while (jiffies < time); +} + + +static inline int wd7000_command_out (Adapter *host, unchar *cmd, int len) +{ + if (! WAIT (host->iobase + ASC_STAT, ASC_STATMASK, CMD_RDY, 0)) { + for ( ; len--; cmd++) + 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); + + return (1); + } + + printk ("%s: WAIT failed (%d)\n", __FUNCTION__, len + 1); + + return (0); +} + + +static inline void scbs_init (void) +{ + short i; + + for (i = 0; i < MAX_SCBS; i++) + memset ((void *) &(scbs[i]), 0, sizeof (Scb)); +} + + +static inline Scb *scb_alloc (void) +{ + Scb *scb = NULL; + ulong flags; + short i; +#ifdef WD7000_DEBUG + short free_scbs = 0; +#endif + + save_flags (flags); + cli (); + + for (i = 0; i < MAX_SCBS; i++) + if (! scbs[i].used) { + scbs[i].used = 1; + scb = &(scbs[i]); + + break; + } + +#ifdef WD7000_DEBUG + for (i = 0; i < MAX_SCBS; i++) + free_scbs += scbs[i].used ? 0 : 1; + + printk ("wd7000_%s: allocating scb (0x%08x), %d scbs free\n", + __FUNCTION__, (int) scb, free_scbs); +#endif + + restore_flags (flags); + + return (scb); +} + + +static inline void scb_free (Scb *scb) +{ + short i; + ulong flags; + + save_flags (flags); + cli (); + + for (i = 0; i < MAX_SCBS; i++) + if (&(scbs[i]) == scb) { + memset ((void *) &(scbs[i]), 0, sizeof (Scb)); + + break; + } + + if (i == MAX_SCBS) + printk ("wd7000_%s: trying to free alien scb (0x%08x)...\n", + __FUNCTION__, (int) scb); +#ifdef WD7000_DEBUG + else + printk ("wd7000_%s: freeing scb (0x%08x)\n", __FUNCTION__, (int) scb); +#endif + + restore_flags (flags); +} + + +static int mail_out (Adapter *host, Scb *scbptr) +/* + * Note: this can also be used for ICBs; just cast to the parm type. + */ +{ + register int i, ogmb; + ulong flags; + unchar start_ogmb; + Mailbox *ogmbs = host->mb.ogmb; + int *next_ogmb = &(host->next_ogmb); + +#ifdef WD7000_DEBUG + printk ("wd7000_%s: 0x%08x", __FUNCTION__, (int) scbptr); +#endif + + /* We first look for a free outgoing mailbox */ + save_flags (flags); + cli (); + + ogmb = *next_ogmb; + for (i = 0; i < OGMB_CNT; i++) { + if (ogmbs[ogmb].status == 0) { +#ifdef WD7000_DEBUG + printk (" using OGMB 0x%x", ogmb); +#endif + ogmbs[ogmb].status = 1; + any2scsi ((unchar *) ogmbs[ogmb].scbptr, (int) scbptr); + + *next_ogmb = (ogmb + 1) % OGMB_CNT; + break; + } + else + ogmb = (++ogmb) % OGMB_CNT; + } + + restore_flags (flags); + +#ifdef WD7000_DEBUG + printk (", scb is 0x%08x", (int) scbptr); +#endif + + if (i >= OGMB_CNT) { + /* + * Alternatively, we might issue the "interrupt on free OGMB", + * and sleep, but it must be ensured that it isn't the init + * task running. Instead, this version assumes that the caller + * will be persistent, and try again. Since it's the adapter + * that marks OGMB's free, waiting even with interrupts off + * should work, since they are freed very quickly in most cases. + */ +#ifdef WD7000_DEBUG + printk (", no free OGMBs.\n"); +#endif + return (0); + } + + wd7000_enable_intr (host); + + start_ogmb = START_OGMB | ogmb; + wd7000_command_out (host, &start_ogmb, 1); + +#ifdef WD7000_DEBUG + printk (", awaiting interrupt.\n"); +#endif + + return (1); +} + + +int make_code (uint hosterr, uint scsierr) +{ +#ifdef WD7000_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; + } + +#ifdef WD7000_DEBUG + if (scsierr || hosterr) + printk ("\nSCSI command error: SCSI 0x%02x host 0x%04x return %d\n", + scsierr, in_error, hosterr); +#endif + + return (scsierr | (hosterr << 16)); +} + + +static void wd7000_scsi_done (Scsi_Cmnd *SCpnt) +{ +#ifdef WD7000_DEBUG + printk ("%s: 0x%08x\n", __FUNCTION__, (int) SCpnt); +#endif + + SCpnt->SCp.phase = 0; +} + + +static inline void wd7000_intr_ack (Adapter *host) +{ + outb (0, host->iobase + ASC_INTR_ACK); +} + + +void wd7000_intr_handle (int irq, void *dev_id, struct pt_regs *regs) +{ + register int flag, icmb, errstatus, icmb_status; + register int host_error, scsi_error; + register Scb *scb; /* for SCSI commands */ + register IcbAny *icb; /* for host commands */ + register Scsi_Cmnd *SCpnt; + Adapter *host = (Adapter *) wd7000_host[irq - IRQ_MIN]->hostdata; /* This MUST be set!!! */ + Mailbox *icmbs = host->mb.icmb; + + host->int_counter++; + +#ifdef WD7000_DEBUG + printk ("%s: irq = %d, host = 0x%08x\n", __FUNCTION__, irq, (int) host); +#endif + + flag = inb (host->iobase + ASC_INTR_STAT); + +#ifdef WD7000_DEBUG + printk ("%s: intr stat = 0x%02x\n", __FUNCTION__, flag); +#endif + + if (! (inb (host->iobase + ASC_STAT) & INT_IM)) { + /* NB: these are _very_ possible if IRQ 15 is being used, since + * it's the "garbage collector" on the 2nd 8259 PIC. Specifically, + * any interrupt signal into the 8259 which can't be identified + * comes out as 7 from the 8259, which is 15 to the host. Thus, it + * is a good thing the WD7000 has an interrupt status port, so we + * can sort these out. Otherwise, electrical noise and other such + * problems would be indistinguishable from valid interrupts... + */ +#ifdef WD7000_DEBUG + printk ("%s: phantom interrupt...\n", __FUNCTION__); +#endif + wd7000_intr_ack (host); + return; + } + + if (flag & MB_INTR) { + /* The interrupt is for a mailbox */ + if (! (flag & IMB_INTR)) { +#ifdef WD7000_DEBUG + printk ("%s: free outgoing mailbox\n", __FUNCTION__); +#endif + /* + * If sleep_on() and the "interrupt on free OGMB" command are + * used in mail_out(), wake_up() should correspondingly be called + * here. For now, we don't need to do anything special. + */ + wd7000_intr_ack (host); + return; + } + else { + /* The interrupt is for an incoming mailbox */ + icmb = flag & MB_MASK; + icmb_status = icmbs[icmb].status; + + if (icmb_status & 0x80) { /* unsolicited - result in ICMB */ +#ifdef WD7000_DEBUG + printk ("%s: unsolicited interrupt 0x%02x\n", + __FUNCTION__, icmb_status); +#endif + wd7000_intr_ack (host); + return; + } + + /* Aaaargh! (Zaga) */ + scb = (Scb *) bus_to_virt (scsi2int ((unchar *) icmbs[icmb].scbptr)); + + icmbs[icmb].status = 0; + if (!(scb->op & ICB_OP_MASK)) { /* an SCB is done */ + SCpnt = scb->SCpnt; + if (--(SCpnt->SCp.phase) <= 0) { /* all scbs are done */ + host_error = scb->vue | (icmb_status << 8); + scsi_error = scb->status; + errstatus = make_code (host_error, scsi_error); + SCpnt->result = errstatus; + + scb_free (scb); + + SCpnt->scsi_done (SCpnt); + } + } + else { /* an ICB is done */ + icb = (IcbAny *) scb; + icb->status = icmb_status; + icb->phase = 0; + } + } /* incoming mailbox */ + } + + wd7000_intr_ack (host); + +#ifdef WD7000_DEBUG + printk ("%s: return from interrupt handler\n", __FUNCTION__); +#endif +} + + +void do_wd7000_intr_handle (int irq, void *dev_id, struct pt_regs *regs) +{ +#if (LINUX_VERSION_CODE >= 0x020100) + ulong flags; + + spin_lock_irqsave (&io_request_lock, flags); +#endif + + wd7000_intr_handle (irq, dev_id, regs); + +#if (LINUX_VERSION_CODE >= 0x020100) + spin_unlock_irqrestore (&io_request_lock, flags); +#endif +} + + +int wd7000_queuecommand (Scsi_Cmnd *SCpnt, void (*done) (Scsi_Cmnd *)) +{ + register Scb *scb; + register Sgb *sgb; + register Adapter *host = (Adapter *) SCpnt->host->hostdata; + + if ((scb = scb_alloc ()) == NULL) { + printk ("%s: Cannot allocate SCB!\n", __FUNCTION__); + return (0); + } + + SCpnt->scsi_done = done; + SCpnt->SCp.phase = 1; + SCpnt->host_scribble = (unchar *) scb; + scb->idlun = ((SCpnt->target << 5) & 0xe0) | (SCpnt->lun & 7); + scb->direc = 0x40; /* Disable direction check */ + scb->SCpnt = SCpnt; /* so we can find stuff later */ + scb->host = host; + memcpy (scb->cdb, SCpnt->cmnd, SCpnt->cmd_len); + + if (SCpnt->use_sg) { + struct scatterlist *sg = (struct scatterlist *) SCpnt->request_buffer; + uint i; + + if (SCpnt->host->sg_tablesize == SG_NONE) + panic ("%s: scatter/gather not supported.\n", __FUNCTION__); +#ifdef WD7000_DEBUG + else + printk ("Using scatter/gather with %d elements.\n", SCpnt->use_sg); +#endif + + sgb = scb->sgb; + scb->op = 1; + any2scsi (scb->dataptr, (int) sgb); + any2scsi (scb->maxlen, SCpnt->use_sg * sizeof (Sgb)); + + for (i = 0; i < SCpnt->use_sg; i++) { + any2scsi (sgb[i].ptr, (int) sg[i].address); + any2scsi (sgb[i].len, sg[i].length); + } + } + else { + scb->op = 0; + any2scsi (scb->dataptr, (int) SCpnt->request_buffer); + any2scsi (scb->maxlen, SCpnt->request_bufflen); + } + + while (! mail_out (host, scb)); /* keep trying */ + + return (1); +} + + +int wd7000_command (Scsi_Cmnd *SCpnt) +{ + if (! wd7000_queuecommand (SCpnt, wd7000_scsi_done)) + return (-1); + + while (SCpnt->SCp.phase > 0) + barrier (); /* phase counts scbs down to 0 */ + + return (SCpnt->result); +} + + +int wd7000_diagnostics (Adapter *host, int code) +{ + static IcbDiag icb = { ICB_OP_DIAGNOSTICS }; + static unchar buf[256]; + ulong timeout; + + /* + * This routine is only called at init, so there should be OGMBs + * available. I'm assuming so here. If this is going to + * fail, I can just let the timeout catch the failure. + */ + icb.type = code; + any2scsi (icb.len, sizeof (buf)); + any2scsi (icb.ptr, (int) &buf); + icb.phase = 1; + + mail_out (host, (Scb *) &icb); + + /* + * Wait up to 2 seconds for completion. + */ + for (timeout = jiffies + WAITnexttimeout; icb.phase && (jiffies < timeout); ) + barrier (); + + if (icb.phase) { + printk ("%s: timed out.\n", __FUNCTION__); + return (0); + } + + if (make_code (icb.vue | (icb.status << 8), 0)) { + printk ("%s: failed (0x%02x,0x%02x)\n", __FUNCTION__, icb.vue, icb.status); + return (0); + } + + return (1); +} + + +int wd7000_init (Adapter *host) +{ + InitCmd init_cmd = + { + INITIALIZATION, + 7, + host->bus_on, + host->bus_off, + 0, + { 0, 0, 0 }, + OGMB_CNT, + ICMB_CNT + }; + int diag; + + /* + * Reset the adapter - only. The SCSI bus was initialized at power-up, + * and we need to do this just so we control the mailboxes, etc. + */ + outb (ASC_RES, host->iobase + ASC_CONTROL); + delay (1); /* reset pulse: this is 10ms, only need 25us */ + outb (0, host->iobase + ASC_CONTROL); + host->control = 0; /* this must always shadow ASC_CONTROL */ + + if (WAIT (host->iobase + ASC_STAT, ASC_STATMASK, CMD_RDY, 0)) { + printk ("%s: WAIT timed out.\n", __FUNCTION__); + return (0); /* 0 = not ok */ + } + + if ((diag = inb (host->iobase + ASC_INTR_STAT)) != 1) { + printk ("%s: ", __FUNCTION__); + + switch (diag) { + case 2: printk ("RAM failure.\n"); + break; + + case 3: printk ("FIFO R/W failed\n"); + break; + + case 4: printk ("SBIC register R/W failed\n"); + break; + + case 5: printk ("Initialization D-FF failed.\n"); + break; + + case 6: printk ("Host IRQ D-FF failed.\n"); + break; + + case 7: printk ("ROM checksum error.\n"); + break; + + default: printk ("diagnostic code 0x%02Xh received.\n", diag); + } + + return (0); + } + + /* Clear mailboxes */ + memset (&(host->mb), 0, sizeof (host->mb)); + + /* Execute init command */ + any2scsi ((unchar *) &(init_cmd.mailboxes), (int) &(host->mb)); + + if (! wd7000_command_out (host, (unchar *) &init_cmd, sizeof (init_cmd))) { + printk ("%s: adapter initialization failed.\n", __FUNCTION__); + return (0); + } + + if (WAIT (host->iobase + ASC_STAT, ASC_STATMASK, ASC_INIT, 0)) { + printk ("%s: WAIT timed out.\n", __FUNCTION__); + return (0); + } + + if (request_irq (host->irq, do_wd7000_intr_handle, SA_INTERRUPT, "wd7000", NULL)) { + printk ("%s: can't get IRQ %d.\n", __FUNCTION__, host->irq); + return (0); + } + + if (request_dma (host->dma, "wd7000")) { + printk ("%s: can't get DMA channel %d.\n", __FUNCTION__, host->dma); + free_irq (host->irq, NULL); + return (0); + } + + wd7000_enable_dma (host); + wd7000_enable_intr (host); + + if (! wd7000_diagnostics (host, ICB_DIAG_FULL)) { + free_dma (host->dma); + free_irq (host->irq, NULL); + return (0); + } + + return (1); +} + + +void wd7000_revision (Adapter *host) +{ + static IcbRevLvl icb = { ICB_OP_GET_REVISION }; + + /* + * Like diagnostics, this is only done at init time, in fact, from + * wd7000_detect, so there should be OGMBs available. If it fails, + * the only damage will be that the revision will show up as 0.0, + * which in turn means that scatter/gather will be disabled. + */ + icb.phase = 1; + mail_out (host, (Scb *) &icb); + + while (icb.phase) + barrier (); /* wait for completion */ + + host->rev1 = icb.primary; + host->rev2 = icb.secondary; +} + + +#undef SPRINTF +#define SPRINTF(args...) { if (pos < (buffer + length)) pos += sprintf (pos, ## args); } + +int wd7000_set_info (char *buffer, int length, struct Scsi_Host *host) +{ + ulong flags; + + save_flags (flags); + cli (); + +#ifdef WD7000_DEBUG + printk ("Buffer = <%.*s>, length = %d\n", length, buffer, length); +#endif + + /* + * Currently this is a no-op + */ + printk ("Sorry, this function is currently out of order...\n"); + + restore_flags (flags); + + return (length); +} + + +int wd7000_proc_info (char *buffer, char **start, off_t offset, int length, int hostno, int inout) +{ + struct Scsi_Host *host = NULL; + Scsi_Device *scd; + Adapter *adapter; + ulong flags; + char *pos = buffer; + short i; + +#ifdef WD7000_DEBUG + Mailbox *ogmbs, *icmbs; + short count; +#endif + + /* + * Find the specified host board. + */ + for (i = 0; i < IRQS; i++) + if (wd7000_host[i] && (wd7000_host[i]->host_no == hostno)) { + host = wd7000_host[i]; + + break; + } + + /* + * Host not found! + */ + if (! host) + return (-ESRCH); + + /* + * Has data been written to the file ? + */ + if (inout) + return (wd7000_set_info (buffer, length, host)); + + adapter = (Adapter *) host->hostdata; + + save_flags (flags); + cli (); + + SPRINTF ("Host scsi%d: Western Digital WD-7000 (rev %d.%d)\n", hostno, adapter->rev1, adapter->rev2); + SPRINTF (" IO base: 0x%x\n", adapter->iobase); + SPRINTF (" IRQ: %d\n", adapter->irq); + SPRINTF (" DMA channel: %d\n", adapter->dma); + SPRINTF (" Interrupts: %d\n", adapter->int_counter); + SPRINTF (" BUS_ON time: %d nanoseconds\n", adapter->bus_on * 125); + SPRINTF (" BUS_OFF time: %d nanoseconds\n", adapter->bus_off * 125); + +#ifdef WD7000_DEBUG + ogmbs = adapter->mb.ogmb; + icmbs = adapter->mb.icmb; + + SPRINTF ("\nControl port value: 0x%x\n", adapter->control); + SPRINTF ("Incoming mailbox:\n"); + SPRINTF (" size: %d\n", ICMB_CNT); + SPRINTF (" queued messages: "); + + for (i = count = 0; i < ICMB_CNT; i++) + if (icmbs[i].status) { + count++; + SPRINTF ("0x%x ", i); + } + + SPRINTF (count ? "\n" : "none\n"); + + SPRINTF ("Outgoing mailbox:\n"); + SPRINTF (" size: %d\n", OGMB_CNT); + SPRINTF (" next message: 0x%x\n", adapter->next_ogmb); + SPRINTF (" queued messages: "); + + for (i = count = 0; i < OGMB_CNT; i++) + if (ogmbs[i].status) { + count++; + SPRINTF ("0x%x ", i); + } + + SPRINTF (count ? "\n" : "none\n"); +#endif + + /* + * Display driver information for each device attached to the board. + */ +#if (LINUX_VERSION_CODE >= 0x020100) + scd = host->host_queue; +#else + scd = scsi_devices; +#endif + + SPRINTF ("\nAttached devices: %s\n", scd ? "" : "none"); + + for ( ; scd; scd = scd->next) + if (scd->host->host_no == hostno) { + SPRINTF (" [Channel: %02d, Id: %02d, Lun: %02d] ", + scd->channel, scd->id, scd->lun); + SPRINTF ("%s ", (scd->type < MAX_SCSI_DEVICE_CODE) ? + scsi_device_types[(short) scd->type] : "Unknown device"); + + for (i = 0; (i < 8) && (scd->vendor[i] >= 0x20); i++) + SPRINTF ("%c", scd->vendor[i]); + SPRINTF (" "); + + for (i = 0; (i < 16) && (scd->model[i] >= 0x20); i++) + SPRINTF ("%c", scd->model[i]); + SPRINTF ("\n"); + } + + SPRINTF ("\n"); + + restore_flags (flags); + + /* + * Calculate start of next buffer, and return value. + */ + *start = buffer + offset; + + if ((pos - buffer) < offset) + return (0); + else if ((pos - buffer - offset) < length) + return (pos - buffer - offset); + else + return (length); +} + + +/* + * Returns the number of adapters this driver is supporting. + * + * The source for hosts.c says to wait to call scsi_register until 100% + * sure about an adapter. We need to do it a little sooner here; we + * need the storage set up by scsi_register before wd7000_init, and + * changing the location of an Adapter structure is more trouble than + * calling scsi_unregister. + * + */ +int wd7000_detect (Scsi_Host_Template *tpnt) +{ + short present = 0, biosaddr_ptr, sig_ptr, i, pass; + short biosptr[NUM_CONFIGS]; + uint iobase; + Adapter *host = NULL; + struct Scsi_Host *sh; + +#ifdef WD7000_DEBUG + printk ("%s: started\n", __FUNCTION__); +#endif + + /* + * Set up SCB free list, which is shared by all adapters + */ + scbs_init (); + + for (i = 0; i < IRQS; wd7000_host[i++] = NULL); + for (i = 0; i < NUM_CONFIGS; biosptr[i++] = -1); + + tpnt->proc_dir = &proc_scsi_wd7000; + tpnt->proc_info = &wd7000_proc_info; + + for (pass = 0; pass < NUM_CONFIGS; pass++) { + short bios_match = 1; + +#ifdef WD7000_DEBUG + printk ("%s: pass %d\n", __FUNCTION__, pass + 1); +#endif + + /* + * First, search for BIOS SIGNATURE... + */ + for (biosaddr_ptr = 0; bios_match && (biosaddr_ptr < NUM_ADDRS); biosaddr_ptr++) + for (sig_ptr = 0; bios_match && (sig_ptr < NUM_SIGNATURES); sig_ptr++) { + for (i = 0; i < pass; i++) + if (biosptr[i] == biosaddr_ptr) + break; + + if (i == pass) { +#if (LINUX_VERSION_CODE >= 0x020100) + char *biosaddr = (char *) ioremap (wd7000_biosaddr[biosaddr_ptr] + + signatures[sig_ptr].ofs, + signatures[sig_ptr].len); +#else + char *biosaddr = (char *) (wd7000_biosaddr[biosaddr_ptr] + + signatures[sig_ptr].ofs); +#endif + bios_match = memcmp (biosaddr, signatures[sig_ptr].sig, + signatures[sig_ptr].len); + +#if (LINUX_VERSION_CODE >= 0x020100) + iounmap (biosaddr); +#else +#endif + if (! bios_match) { + /* + * BIOS SIGNATURE has been found. + */ + biosptr[pass] = biosaddr_ptr; +#ifdef WD7000_DEBUG + printk ("WD-7000 SST BIOS detected at 0x%lx: checking...\n", + wd7000_biosaddr[biosaddr_ptr]); +#endif + } + } + } + +#ifdef WD7000_DEBUG + if (bios_match) + printk ("WD-7000 SST BIOS not detected...\n"); +#endif + + if (configs[pass].irq < 0) + continue; + + iobase = configs[pass].iobase; + +#ifdef WD7000_DEBUG + printk ("%s: check IO 0x%x region...\n", __FUNCTION__, iobase); +#endif + + if (! check_region (iobase, 4)) { +#ifdef WD7000_DEBUG + printk ("%s: ASC reset (IO 0x%x) ...", __FUNCTION__, iobase); +#endif + /* + * ASC reset... + */ + outb (ASC_RES, iobase + ASC_CONTROL); + delay (1); + outb (0, iobase + ASC_CONTROL); + + if (WAIT (iobase + ASC_STAT, ASC_STATMASK, CMD_RDY, 0)) +#ifdef WD7000_DEBUG + { + printk ("failed!\n"); + continue; + } + else + printk ("ok!\n"); +#else + continue; +#endif + + if (inb (iobase + ASC_INTR_STAT) == 1) { + /* + * We register here, to get a pointer to the extra space, + * which we'll use as the Adapter structure (host) for + * this adapter. It is located just after the registered + * Scsi_Host structure (sh), and is located by the empty + * array hostdata. + */ + sh = scsi_register (tpnt, sizeof (Adapter)); + host = (Adapter *) sh->hostdata; + +#ifdef WD7000_DEBUG + printk ("%s: adapter allocated at 0x%x\n", __FUNCTION__, (int) host); +#endif + + memset (host, 0, sizeof (Adapter)); + + host->irq = configs[pass].irq; + host->dma = configs[pass].dma; + host->iobase = iobase; + host->int_counter = 0; + host->bus_on = configs[pass].bus_on; + host->bus_off = configs[pass].bus_off; + host->sh = wd7000_host[host->irq - IRQ_MIN] = sh; + +#ifdef WD7000_DEBUG + printk ("%s: Trying to init WD-7000 card at IO 0x%x, IRQ %d, DMA %d...\n", + __FUNCTION__, host->iobase, host->irq, host->dma); +#endif + + if (! wd7000_init (host)) { /* Initialization failed */ + scsi_unregister (sh); + continue; + } + + /* + * OK from here - we'll use this adapter/configuration. + */ + wd7000_revision (host); /* important for scatter/gather */ + + /* + * Register our ports. + */ + request_region (host->iobase, 4, "wd7000"); + + /* + * For boards before rev 6.0, scatter/gather isn't supported. + */ + if (host->rev1 < 6) + sh->sg_tablesize = SG_NONE; + + present++; /* count it */ + + printk ("Western Digital WD-7000 (rev %d.%d) ", + host->rev1, host->rev2); + printk ("using IO 0x%x, IRQ %d, DMA %d.\n", + host->iobase, host->irq, host->dma); + printk (" BUS_ON time: %dns, BUS_OFF time: %dns\n", + host->bus_on * 125, host->bus_off * 125); + } + } + +#ifdef WD7000_DEBUG + else + printk ("%s: IO 0x%x region is already allocated!\n", __FUNCTION__, iobase); +#endif + + } + + if (! present) + printk ("Failed initialization of WD-7000 SCSI card!\n"); + + return (present); +} + + +/* + * I have absolutely NO idea how to do an abort with the WD7000... + */ +int wd7000_abort (Scsi_Cmnd *SCpnt) +{ + Adapter *host = (Adapter *) SCpnt->host->hostdata; + + if (inb (host->iobase + ASC_STAT) & INT_IM) { + printk ("%s: lost interrupt\n", __FUNCTION__); + wd7000_intr_handle (host->irq, NULL, NULL); + + return (SCSI_ABORT_SUCCESS); + } + + return (SCSI_ABORT_SNOOZE); +} + + +/* + * I also have no idea how to do a reset... + */ +int wd7000_reset (Scsi_Cmnd *SCpnt, uint flags) +{ + return (SCSI_RESET_PUNT); +} + + +/* + * This was borrowed directly from aha1542.c. (Zaga) + */ +int wd7000_biosparam (Disk *disk, kdev_t dev, int *ip) +{ +#ifdef WD7000_DEBUG + printk ("%s: dev=%s, size=%d, ", __FUNCTION__, kdevname (dev), disk->capacity); +#endif + + /* + * try default translation + */ + ip[0] = 64; + ip[1] = 32; + ip[2] = disk->capacity / (64 * 32); + + /* + * for disks >1GB do some guessing + */ + if (ip[2] >= 1024) { + int info[3]; + + /* + * try to figure out the geometry from the partition table + */ + if ((scsicam_bios_param (disk, dev, info) < 0) || + !(((info[0] == 64) && (info[1] == 32)) || + ((info[0] == 255) && (info[1] == 63)))) { + printk ("%s: unable to verify geometry for disk with >1GB.\n" + " using extended translation.\n", + __FUNCTION__); + + ip[0] = 255; + ip[1] = 63; + ip[2] = disk->capacity / (255 * 63); + } + else { + ip[0] = info[0]; + ip[1] = info[1]; + ip[2] = info[2]; + + if (info[0] == 255) + printk ("%s: current partition table is using extended translation.\n", + __FUNCTION__); + } + } + +#ifdef WD7000_DEBUG + printk ("bios geometry: head=%d, sec=%d, cyl=%d\n", ip[0], ip[1], ip[2]); + printk ("WARNING: check, if the bios geometry is correct.\n"); +#endif + + return (0); +} + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = WD7000; + +#include "scsi_module.c" +#endif diff --git a/linux/src/drivers/scsi/wd7000.h b/linux/src/drivers/scsi/wd7000.h new file mode 100644 index 00000000..e17a69b1 --- /dev/null +++ b/linux/src/drivers/scsi/wd7000.h @@ -0,0 +1,446 @@ +/* $Id: wd7000.h,v 1.1 1999/04/26 05:55:19 tb Exp $ + * + * Header file for the WD-7000 driver for Linux + * + * John Boyd <boyd@cis.ohio-state.edu> Jan 1994: + * This file has been reduced to only the definitions needed for the + * WD7000 host structure. + * + * Revision by Miroslav Zagorac <zaga@fly.cc.fer.hr> Jun 1997. + */ +#ifndef _WD7000_H + +#include <linux/version.h> +#include <linux/types.h> +#include <linux/kdev_t.h> + +#ifndef NULL +#define NULL 0L +#endif + +/* + * In this version, sg_tablesize now defaults to WD7000_SG, and will + * be set to SG_NONE for older boards. This is the reverse of the + * previous default, and was changed so that the driver-level + * Scsi_Host_Template would reflect the driver's support for scatter/ + * gather. + * + * Also, it has been reported that boards at Revision 6 support scatter/ + * gather, so the new definition of an "older" board has been changed + * accordingly. + */ +#define WD7000_Q 16 +#define WD7000_SG 16 + +#ifdef WD7000_DEFINES +/* + * Mailbox structure sizes. + * I prefer to keep the number of ICMBs much larger than the number of + * OGMBs. OGMBs are used very quickly by the driver to start one or + * more commands, while ICMBs are used by the host adapter per command. + */ +#define OGMB_CNT 16 +#define ICMB_CNT 32 + +/* + * Scb's are shared by all active adapters. If you'd rather conserve + * memory, use a smaller number (> 0, of course) - things will should + * still work OK. + */ +#define MAX_SCBS (4 * WD7000_Q) + +/* + * WD7000-specific mailbox structure + */ +typedef volatile struct { + unchar status; + unchar scbptr[3]; /* SCSI-style - MSB first (big endian) */ +} Mailbox; + +/* + * This structure should contain all per-adapter global data. I.e., any + * new global per-adapter data should put in here. + */ +typedef struct { + struct Scsi_Host *sh; /* Pointer to Scsi_Host structure */ + int iobase; /* This adapter's I/O base address */ + int irq; /* This adapter's IRQ level */ + int dma; /* This adapter's DMA channel */ + int int_counter; /* This adapter's interrupt counter */ + int bus_on; /* This adapter's BUS_ON time */ + int bus_off; /* This adapter's BUS_OFF time */ + struct { /* This adapter's mailboxes */ + Mailbox ogmb[OGMB_CNT]; /* Outgoing mailboxes */ + Mailbox icmb[ICMB_CNT]; /* Incoming mailboxes */ + } mb; + int next_ogmb; /* to reduce contention at mailboxes */ + unchar control; /* shadows CONTROL port value */ + unchar rev1; /* filled in by wd7000_revision */ + unchar rev2; +} Adapter; + + +/* + * possible irq range + */ +#define IRQ_MIN 3 +#define IRQ_MAX 15 +#define IRQS (IRQ_MAX - IRQ_MIN + 1) + +#define BUS_ON 64 /* x 125ns = 8000ns (BIOS default) */ +#define BUS_OFF 15 /* x 125ns = 1875ns (BIOS default) */ + +/* + * Standard Adapter Configurations - used by wd7000_detect + */ +typedef struct { + short irq; /* IRQ level */ + short dma; /* DMA channel */ + uint iobase; /* I/O base address */ + short bus_on; /* Time that WD7000 spends on the AT-bus when */ + /* transferring data. BIOS default is 8000ns. */ + short bus_off; /* Time that WD7000 spends OFF THE BUS after */ + /* while it is transferring data. */ + /* BIOS default is 1875ns */ +} Config; + + +/* + * The following list defines strings to look for in the BIOS that identify + * it as the WD7000-FASST2 SST BIOS. I suspect that something should be + * added for the Future Domain version. + */ +typedef struct { + const char *sig; /* String to look for */ + ulong ofs; /* offset from BIOS base address */ + uint len; /* length of string */ +} Signature; + +/* + * I/O Port Offsets and Bit Definitions + * 4 addresses are used. Those not defined here are reserved. + */ +#define ASC_STAT 0 /* Status, Read */ +#define ASC_COMMAND 0 /* Command, Write */ +#define ASC_INTR_STAT 1 /* Interrupt Status, Read */ +#define ASC_INTR_ACK 1 /* Acknowledge, Write */ +#define ASC_CONTROL 2 /* Control, Write */ + +/* + * ASC Status Port + */ +#define INT_IM 0x80 /* Interrupt Image Flag */ +#define CMD_RDY 0x40 /* Command Port Ready */ +#define CMD_REJ 0x20 /* Command Port Byte Rejected */ +#define ASC_INIT 0x10 /* ASC Initialized Flag */ +#define ASC_STATMASK 0xf0 /* The lower 4 Bytes are reserved */ + +/* + * COMMAND opcodes + * + * Unfortunately, I have no idea how to properly use some of these commands, + * as the OEM manual does not make it clear. I have not been able to use + * enable/disable unsolicited interrupts or the reset commands with any + * discernible effect whatsoever. I think they may be related to certain + * ICB commands, but again, the OEM manual doesn't make that clear. + */ +#define NO_OP 0 /* NO-OP toggles CMD_RDY bit in ASC_STAT */ +#define INITIALIZATION 1 /* initialization (10 bytes) */ +#define DISABLE_UNS_INTR 2 /* disable unsolicited interrupts */ +#define ENABLE_UNS_INTR 3 /* enable unsolicited interrupts */ +#define INTR_ON_FREE_OGMB 4 /* interrupt on free OGMB */ +#define SOFT_RESET 5 /* SCSI bus soft reset */ +#define HARD_RESET_ACK 6 /* SCSI bus hard reset acknowledge */ +#define START_OGMB 0x80 /* start command in OGMB (n) */ +#define SCAN_OGMBS 0xc0 /* start multiple commands, signature (n) */ + /* where (n) = lower 6 bits */ +/* + * For INITIALIZATION: + */ +typedef struct { + unchar op; /* command opcode (= 1) */ + unchar ID; /* Adapter's SCSI ID */ + unchar bus_on; /* Bus on time, x 125ns (see below) */ + unchar bus_off; /* Bus off time, "" "" */ + unchar rsvd; /* Reserved */ + unchar mailboxes[3]; /* Address of Mailboxes, MSB first */ + unchar ogmbs; /* Number of outgoing MBs, max 64, 0,1 = 1 */ + unchar icmbs; /* Number of incoming MBs, "" "" */ +} InitCmd; + +/* + * Interrupt Status Port - also returns diagnostic codes at ASC reset + * + * if msb is zero, the lower bits are diagnostic status + * Diagnostics: + * 01 No diagnostic error occurred + * 02 RAM failure + * 03 FIFO R/W failed + * 04 SBIC register read/write failed + * 05 Initialization D-FF failed + * 06 Host IRQ D-FF failed + * 07 ROM checksum error + * Interrupt status (bitwise): + * 10NNNNNN outgoing mailbox NNNNNN is free + * 11NNNNNN incoming mailbox NNNNNN needs service + */ +#define MB_INTR 0xC0 /* Mailbox Service possible/required */ +#define IMB_INTR 0x40 /* 1 Incoming / 0 Outgoing */ +#define MB_MASK 0x3f /* mask for mailbox number */ + +/* + * CONTROL port bits + */ +#define INT_EN 0x08 /* Interrupt Enable */ +#define DMA_EN 0x04 /* DMA Enable */ +#define SCSI_RES 0x02 /* SCSI Reset */ +#define ASC_RES 0x01 /* ASC Reset */ + +/* + * 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 { + unchar len[3]; + unchar ptr[3]; /* Also SCSI-style - MSB first */ +} Sgb; + +typedef struct { /* 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 */ + Scsi_Cmnd *SCpnt; /* Scsi_Cmnd using this SCB */ + Sgb sgb[WD7000_SG]; /* Scatter/gather list for this SCB */ + Adapter *host; /* host adapter */ + unchar used; /* flag */ +} 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 { + 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 { + 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 { + 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 { /* 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 { + 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 { + 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 { + 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 { + 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; + +#define WAITnexttimeout 200 /* 2 seconds */ + +typedef union { /* let's cheat... */ + int i; + unchar u[sizeof (int)]; /* the sizeof(int) makes it more portable */ +} i_u; + +#endif /* WD7000_DEFINES */ + + +#if (LINUX_VERSION_CODE >= 0x020100) + +#define WD7000 { \ + proc_dir: &proc_scsi_wd7000, \ + proc_info: wd7000_proc_info, \ + name: "Western Digital WD-7000", \ + detect: wd7000_detect, \ + command: wd7000_command, \ + queuecommand: wd7000_queuecommand, \ + abort: wd7000_abort, \ + reset: wd7000_reset, \ + bios_param: wd7000_biosparam, \ + can_queue: WD7000_Q, \ + this_id: 7, \ + sg_tablesize: WD7000_SG, \ + cmd_per_lun: 1, \ + unchecked_isa_dma: 1, \ + use_clustering: ENABLE_CLUSTERING, \ + use_new_eh_code: 0 \ +} + +#else /* Use old scsi code */ + +#define WD7000 { \ + proc_dir: &proc_scsi_wd7000, \ + proc_info: wd7000_proc_info, \ + name: "Western Digital WD-7000", \ + detect: wd7000_detect, \ + command: wd7000_command, \ + queuecommand: wd7000_queuecommand, \ + abort: wd7000_abort, \ + reset: wd7000_reset, \ + bios_param: wd7000_biosparam, \ + can_queue: WD7000_Q, \ + this_id: 7, \ + sg_tablesize: WD7000_SG, \ + cmd_per_lun: 1, \ + unchecked_isa_dma: 1, \ + use_clustering: ENABLE_CLUSTERING, \ +} + +#endif /* LINUX_VERSION_CODE */ + + +extern struct proc_dir_entry proc_scsi_wd7000; + + +#ifdef WD7000_DEFINES +int wd7000_diagnostics (Adapter *, int); +int wd7000_init (Adapter *); +void wd7000_revision (Adapter *); +#endif /* WD7000_DEFINES */ + +void wd7000_setup (char *, int *); +int make_code (uint, uint); +void wd7000_intr_handle (int, void *, struct pt_regs *); +void do_wd7000_intr_handle (int, void *, struct pt_regs *); +int wd7000_queuecommand (Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +int wd7000_command (Scsi_Cmnd *); +int wd7000_set_info (char *, int, struct Scsi_Host *); +int wd7000_proc_info (char *, char **, off_t, int, int, int); +int wd7000_detect (Scsi_Host_Template *); +int wd7000_abort (Scsi_Cmnd *); +int wd7000_reset (Scsi_Cmnd *, uint); +int wd7000_biosparam (Disk *, kdev_t, int *); + +#endif /* _WD7000_H */ |