diff options
Diffstat (limited to 'drivers/scsi/fd_mcs.c')
-rw-r--r-- | drivers/scsi/fd_mcs.c | 1369 |
1 files changed, 1369 insertions, 0 deletions
diff --git a/drivers/scsi/fd_mcs.c b/drivers/scsi/fd_mcs.c new file mode 100644 index 000000000000..770930e2aec3 --- /dev/null +++ b/drivers/scsi/fd_mcs.c @@ -0,0 +1,1369 @@ +/* fd_mcs.c -- Future Domain MCS 600/700 (or IBM OEM) driver + * + * FutureDomain MCS-600/700 v0.2 03/11/1998 by ZP Gu (zpg@castle.net) + * + * This driver is cloned from fdomain.* to specifically support + * the Future Domain MCS 600/700 MCA SCSI adapters. Some PS/2s + * also equipped with IBM Fast SCSI Adapter/A which is an OEM + * of MCS 700. + * + * This driver also supports Reply SB16/SCSI card (the SCSI part). + * + * What makes this driver different is that this driver is MCA only + * and it supports multiple adapters in the same system, IRQ + * sharing, some driver statistics, and maps highest SCSI id to sda. + * All cards are auto-detected. + * + * Assumptions: TMC-1800/18C50/18C30, BIOS >= 3.4 + * + * LILO command-line options: + * fd_mcs=<FIFO_COUNT>[,<FIFO_SIZE>] + * + * ******************************************************** + * Please see Copyrights/Comments in fdomain.* for credits. + * Following is from fdomain.c for acknowledgement: + * + * Created: Sun May 3 18:53:19 1992 by faith@cs.unc.edu + * Revised: Wed Oct 2 11:10:55 1996 by r.faith@ieee.org + * Author: Rickard E. Faith, faith@cs.unc.edu + * Copyright 1992, 1993, 1994, 1995, 1996 Rickard E. Faith + * + * $Id: fdomain.c,v 5.45 1996/10/02 15:13:06 root 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. + + ************************************************************************** + + 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. + [*****Now a runtime option*****] + + 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. + + **************************************************************************/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/mca.h> +#include <linux/spinlock.h> +#include <scsi/scsicam.h> +#include <linux/mca-legacy.h> + +#include <asm/io.h> +#include <asm/system.h> + +#include "scsi.h" +#include <scsi/scsi_host.h> + +#define DRIVER_VERSION "v0.2 by ZP Gu<zpg@castle.net>" + +/* START OF USER DEFINABLE OPTIONS */ + +#define DEBUG 0 /* Enable debugging output */ +#define ENABLE_PARITY 1 /* Enable SCSI Parity */ + +/* 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_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_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 +}; + +struct fd_hostdata { + unsigned long _bios_base; + int _bios_major; + int _bios_minor; + volatile int _in_command; + Scsi_Cmnd *_current_SC; + enum chip_type _chip; + int _adapter_mask; + int _fifo_count; /* Number of 512 byte blocks before INTR */ + + char _adapter_name[64]; +#if DEBUG_RACE + volatile int _in_interrupt_flag; +#endif + + int _SCSI_Mode_Cntl_port; + int _FIFO_Data_Count_port; + int _Interrupt_Cntl_port; + int _Interrupt_Status_port; + int _Interrupt_Cond_port; + int _Read_FIFO_port; + int _Read_SCSI_Data_port; + int _SCSI_Cntl_port; + int _SCSI_Data_NoACK_port; + int _SCSI_Status_port; + int _TMC_Cntl_port; + int _TMC_Status_port; + int _Write_FIFO_port; + int _Write_SCSI_Data_port; + + int _FIFO_Size; /* = 0x2000; 8k FIFO for + pre-tmc18c30 chips */ + /* simple stats */ + int _Bytes_Read; + int _Bytes_Written; + int _INTR_Processed; +}; + +#define FD_MAX_HOSTS 3 /* enough? */ + +#define HOSTDATA(shpnt) ((struct fd_hostdata *) shpnt->hostdata) +#define bios_base (HOSTDATA(shpnt)->_bios_base) +#define bios_major (HOSTDATA(shpnt)->_bios_major) +#define bios_minor (HOSTDATA(shpnt)->_bios_minor) +#define in_command (HOSTDATA(shpnt)->_in_command) +#define current_SC (HOSTDATA(shpnt)->_current_SC) +#define chip (HOSTDATA(shpnt)->_chip) +#define adapter_mask (HOSTDATA(shpnt)->_adapter_mask) +#define FIFO_COUNT (HOSTDATA(shpnt)->_fifo_count) +#define adapter_name (HOSTDATA(shpnt)->_adapter_name) +#if DEBUG_RACE +#define in_interrupt_flag (HOSTDATA(shpnt)->_in_interrupt_flag) +#endif +#define SCSI_Mode_Cntl_port (HOSTDATA(shpnt)->_SCSI_Mode_Cntl_port) +#define FIFO_Data_Count_port (HOSTDATA(shpnt)->_FIFO_Data_Count_port) +#define Interrupt_Cntl_port (HOSTDATA(shpnt)->_Interrupt_Cntl_port) +#define Interrupt_Status_port (HOSTDATA(shpnt)->_Interrupt_Status_port) +#define Interrupt_Cond_port (HOSTDATA(shpnt)->_Interrupt_Cond_port) +#define Read_FIFO_port (HOSTDATA(shpnt)->_Read_FIFO_port) +#define Read_SCSI_Data_port (HOSTDATA(shpnt)->_Read_SCSI_Data_port) +#define SCSI_Cntl_port (HOSTDATA(shpnt)->_SCSI_Cntl_port) +#define SCSI_Data_NoACK_port (HOSTDATA(shpnt)->_SCSI_Data_NoACK_port) +#define SCSI_Status_port (HOSTDATA(shpnt)->_SCSI_Status_port) +#define TMC_Cntl_port (HOSTDATA(shpnt)->_TMC_Cntl_port) +#define TMC_Status_port (HOSTDATA(shpnt)->_TMC_Status_port) +#define Write_FIFO_port (HOSTDATA(shpnt)->_Write_FIFO_port) +#define Write_SCSI_Data_port (HOSTDATA(shpnt)->_Write_SCSI_Data_port) +#define FIFO_Size (HOSTDATA(shpnt)->_FIFO_Size) +#define Bytes_Read (HOSTDATA(shpnt)->_Bytes_Read) +#define Bytes_Written (HOSTDATA(shpnt)->_Bytes_Written) +#define INTR_Processed (HOSTDATA(shpnt)->_INTR_Processed) + +struct fd_mcs_adapters_struct { + char *name; + int id; + enum chip_type fd_chip; + int fifo_size; + int fifo_count; +}; + +#define REPLY_ID 0x5137 + +static struct fd_mcs_adapters_struct fd_mcs_adapters[] = { + {"Future Domain SCSI Adapter MCS-700(18C50)", + 0x60e9, + tmc18c50, + 0x2000, + 4}, + {"Future Domain SCSI Adapter MCS-600/700(TMC-1800)", + 0x6127, + tmc1800, + 0x2000, + 4}, + {"Reply Sound Blaster/SCSI Adapter", + REPLY_ID, + tmc18c30, + 0x800, + 2}, +}; + +#define FD_BRDS sizeof(fd_mcs_adapters)/sizeof(struct fd_mcs_adapters_struct) + +static irqreturn_t fd_mcs_intr(int irq, void *dev_id, struct pt_regs *regs); + +static unsigned long addresses[] = { 0xc8000, 0xca000, 0xce000, 0xde000 }; +static unsigned short ports[] = { 0x140, 0x150, 0x160, 0x170 }; +static unsigned short interrupts[] = { 3, 5, 10, 11, 12, 14, 15, 0 }; + +/* host information */ +static int found = 0; +static struct Scsi_Host *hosts[FD_MAX_HOSTS + 1] = { NULL }; + +static int user_fifo_count = 0; +static int user_fifo_size = 0; + +static int __init fd_mcs_setup(char *str) +{ + static int done_setup = 0; + int ints[3]; + + get_options(str, 3, ints); + if (done_setup++ || ints[0] < 1 || ints[0] > 2 || ints[1] < 1 || ints[1] > 16) { + printk("fd_mcs: usage: fd_mcs=FIFO_COUNT, FIFO_SIZE\n"); + return 0; + } + + user_fifo_count = ints[0] >= 1 ? ints[1] : 0; + user_fifo_size = ints[0] >= 2 ? ints[2] : 0; + return 1; +} + +__setup("fd_mcs=", fd_mcs_setup); + +static void print_banner(struct Scsi_Host *shpnt) +{ + printk("scsi%d <fd_mcs>: ", shpnt->host_no); + + if (bios_base) { + printk("BIOS at 0x%lX", bios_base); + } else { + printk("No BIOS"); + } + + printk(", HostID %d, %s Chip, IRQ %d, IO 0x%lX\n", shpnt->this_id, chip == tmc18c50 ? "TMC-18C50" : (chip == tmc18c30 ? "TMC-18C30" : (chip == tmc1800 ? "TMC-1800" : "Unknown")), shpnt->irq, shpnt->io_port); +} + + +static void do_pause(unsigned amount) +{ /* Pause for amount*10 milliseconds */ + do { + mdelay(10); + } while (--amount); +} + +static void fd_mcs_make_bus_idle(struct Scsi_Host *shpnt) +{ + 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 fd_mcs_detect(Scsi_Host_Template * tpnt) +{ + int loop; + struct Scsi_Host *shpnt; + + /* get id, port, bios, irq */ + int slot; + u_char pos2, pos3, pos4; + int id, port, irq; + unsigned long bios; + + /* if not MCA machine, return */ + if (!MCA_bus) + return 0; + + /* changeable? */ + id = 7; + + for (loop = 0; loop < FD_BRDS; loop++) { + slot = 0; + while (MCA_NOTFOUND != (slot = mca_find_adapter(fd_mcs_adapters[loop].id, slot))) { + + /* if we get this far, an adapter has been detected and is + enabled */ + + printk(KERN_INFO "scsi <fd_mcs>: %s at slot %d\n", fd_mcs_adapters[loop].name, slot + 1); + + pos2 = mca_read_stored_pos(slot, 2); + pos3 = mca_read_stored_pos(slot, 3); + pos4 = mca_read_stored_pos(slot, 4); + + /* ready for next probe */ + slot++; + + if (fd_mcs_adapters[loop].id == REPLY_ID) { /* reply card */ + static int reply_irq[] = { 10, 11, 14, 15 }; + + bios = 0; /* no bios */ + + if (pos2 & 0x2) + port = ports[pos4 & 0x3]; + else + continue; + + /* can't really disable it, same as irq=10 */ + irq = reply_irq[((pos4 >> 2) & 0x1) + 2 * ((pos4 >> 4) & 0x1)]; + } else { + bios = addresses[pos2 >> 6]; + port = ports[(pos2 >> 4) & 0x03]; + irq = interrupts[(pos2 >> 1) & 0x07]; + } + + if (irq) { + /* claim the slot */ + mca_set_adapter_name(slot - 1, fd_mcs_adapters[loop].name); + + /* check irq/region */ + if (request_irq(irq, fd_mcs_intr, SA_SHIRQ, "fd_mcs", hosts)) { + printk(KERN_ERR "fd_mcs: interrupt is not available, skipping...\n"); + continue; + } + + /* request I/O region */ + if (request_region(port, 0x10, "fd_mcs")) { + printk(KERN_ERR "fd_mcs: I/O region is already in use, skipping...\n"); + continue; + } + /* register */ + if (!(shpnt = scsi_register(tpnt, sizeof(struct fd_hostdata)))) { + printk(KERN_ERR "fd_mcs: scsi_register() failed\n"); + release_region(port, 0x10); + free_irq(irq, hosts); + continue; + } + + + /* save name */ + strcpy(adapter_name, fd_mcs_adapters[loop].name); + + /* chip/fifo */ + chip = fd_mcs_adapters[loop].fd_chip; + /* use boot time value if available */ + FIFO_COUNT = user_fifo_count ? user_fifo_count : fd_mcs_adapters[loop].fifo_count; + FIFO_Size = user_fifo_size ? user_fifo_size : fd_mcs_adapters[loop].fifo_size; + +/* FIXME: Do we need to keep this bit of code inside NOT_USED around at all? */ +#ifdef NOT_USED + /* *************************************************** */ + /* 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 */ + + printk("FIRST: chip=%s, fifo_size=0x%x\n", (chip == tmc18c30) ? "tmc18c30" : "tmc18c50", FIFO_Size); + } + } + + /* 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 */ + + printk("SECOND: chip=%s, fifo_size=0x%x\n", (chip == tmc18c30) ? "tmc18c30" : "tmc18c50", FIFO_Size); + } + /* *************************************************** */ +#endif + + /* IBM/ANSI scsi scan ordering */ + /* Stick this back in when the scsi.c changes are there */ + shpnt->reverse_ordering = 1; + + + /* saving info */ + hosts[found++] = shpnt; + + shpnt->this_id = id; + shpnt->irq = irq; + shpnt->io_port = port; + shpnt->n_io_port = 0x10; + + /* save */ + bios_base = bios; + adapter_mask = (1 << id); + + /* save more */ + SCSI_Mode_Cntl_port = port + SCSI_Mode_Cntl; + FIFO_Data_Count_port = port + FIFO_Data_Count; + Interrupt_Cntl_port = port + Interrupt_Cntl; + Interrupt_Status_port = port + Interrupt_Status; + Interrupt_Cond_port = port + Interrupt_Cond; + Read_FIFO_port = port + Read_FIFO; + Read_SCSI_Data_port = port + Read_SCSI_Data; + SCSI_Cntl_port = port + SCSI_Cntl; + SCSI_Data_NoACK_port = port + SCSI_Data_NoACK; + SCSI_Status_port = port + SCSI_Status; + TMC_Cntl_port = port + TMC_Cntl; + TMC_Status_port = port + TMC_Status; + Write_FIFO_port = port + Write_FIFO; + Write_SCSI_Data_port = port + Write_SCSI_Data; + + Bytes_Read = 0; + Bytes_Written = 0; + INTR_Processed = 0; + + /* say something */ + print_banner(shpnt); + + /* reset */ + 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); + /* done reset */ + } + } + + if (found == FD_MAX_HOSTS) { + printk("fd_mcs: detecting reached max=%d host adapters.\n", FD_MAX_HOSTS); + break; + } + } + + return found; +} + +static const char *fd_mcs_info(struct Scsi_Host *shpnt) +{ + return adapter_name; +} + +static int TOTAL_INTR = 0; + +/* + * 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 + */ +static int fd_mcs_proc_info(struct Scsi_Host *shpnt, char *buffer, char **start, off_t offset, int length, int inout) +{ + int len = 0; + + if (inout) + return (-ENOSYS); + + *start = buffer + offset; + + len += sprintf(buffer + len, "Future Domain MCS-600/700 Driver %s\n", DRIVER_VERSION); + len += sprintf(buffer + len, "HOST #%d: %s\n", shpnt->host_no, adapter_name); + len += sprintf(buffer + len, "FIFO Size=0x%x, FIFO Count=%d\n", FIFO_Size, FIFO_COUNT); + len += sprintf(buffer + len, "DriverCalls=%d, Interrupts=%d, BytesRead=%d, BytesWrite=%d\n\n", TOTAL_INTR, INTR_Processed, Bytes_Read, Bytes_Written); + + if ((len -= offset) <= 0) + return 0; + if (len > length) + len = length; + return len; +} + +static int fd_mcs_select(struct Scsi_Host *shpnt, int target) +{ + int status; + unsigned long timeout; + + 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 = 350; /* 350mS -- because of timeouts + (was 250mS) */ + + do { + 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; + } + udelay(1000); /* wait one msec */ + } while (--timeout); + + /* Make bus idle */ + fd_mcs_make_bus_idle(shpnt); +#if EVERY_ACCESS + if (!target) + printk("Selection failed\n"); +#endif +#if ERRORS_ONLY + if (!target) { + static int flag = 0; + + if (!flag) /* Skip first failure for all chips. */ + ++flag; + else + printk("fd_mcs: Selection failed\n"); + } +#endif + return 1; +} + +static void my_done(struct Scsi_Host *shpnt, int error) +{ + if (in_command) { + in_command = 0; + outb(0x00, Interrupt_Cntl_port); + fd_mcs_make_bus_idle(shpnt); + current_SC->result = error; + current_SC->scsi_done(current_SC); + } else { + panic("fd_mcs: my_done() called outside of command\n"); + } +#if DEBUG_RACE + in_interrupt_flag = 0; +#endif +} + +/* only my_done needs to be protected */ +static irqreturn_t fd_mcs_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long flags; + int status; + int done = 0; + unsigned data_count, tmp_count; + + int i = 0; + struct Scsi_Host *shpnt; + + TOTAL_INTR++; + + /* search for one adapter-response on shared interrupt */ + while ((shpnt = hosts[i++])) { + if ((inb(TMC_Status_port)) & 1) + break; + } + + /* return if some other device on this IRQ caused the interrupt */ + if (!shpnt) { + return IRQ_NONE; + } + + INTR_Processed++; + + outb(0x00, Interrupt_Cntl_port); + + /* 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 IRQ_HANDLED; */ + } +#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 + spin_lock_irqsave(shpnt->host_lock, flags); + my_done(shpnt, DID_BUS_BUSY << 16); + spin_unlock_irqrestore(shpnt->host_lock, flags); + return IRQ_HANDLED; + } + 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->device->id), 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 IRQ_HANDLED; + } else if (current_SC->SCp.phase & in_selection) { + status = inb(SCSI_Status_port); + if (!(status & 0x01)) { + /* Try again, for slow devices */ + if (fd_mcs_select(shpnt, current_SC->device->id)) { +#if EVERY_ACCESS + printk(" SFAIL "); +#endif + spin_lock_irqsave(shpnt->host_lock, flags); + my_done(shpnt, DID_NO_CONNECT << 16); + spin_unlock_irqrestore(shpnt->host_lock, flags); + return IRQ_HANDLED; + } 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 IRQ_HANDLED; + } + + /* 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("ERROR fd_mcs: target = %d, command = %x, status = %x\n", current_SC->device->id, 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("fd_mcs: 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) { + Bytes_Written++; + + outb(*current_SC->SCp.ptr++, Write_FIFO_port); + --current_SC->SCp.this_residual; + } else { + data_count >>= 1; + tmp_count = data_count << 1; + outsw(Write_FIFO_port, current_SC->SCp.ptr, data_count); + current_SC->SCp.ptr += tmp_count; + Bytes_Written += tmp_count; + current_SC->SCp.this_residual -= tmp_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 = page_address(current_SC->SCp.buffer->page) + current_SC->SCp.buffer->offset; + current_SC->SCp.this_residual = current_SC->SCp.buffer->length; + } else + break; + } + } + } else 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) { + Bytes_Read++; + *current_SC->SCp.ptr++ = inb(Read_FIFO_port); + --current_SC->SCp.this_residual; + } else { + data_count >>= 1; /* Number of words */ + tmp_count = data_count << 1; + insw(Read_FIFO_port, current_SC->SCp.ptr, data_count); + current_SC->SCp.ptr += tmp_count; + Bytes_Read += tmp_count; + current_SC->SCp.this_residual -= tmp_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 = page_address(current_SC->SCp.buffer->page) + current_SC->SCp.buffer->offset; + 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("fd_mcs: REQUEST SENSE " "Key = %x, Code = %x, Qualifier = %x\n", key, code, qualifier); + } + } +#endif +#if EVERY_ACCESS + printk("BEFORE MY_DONE. . ."); +#endif + spin_lock_irqsave(shpnt->host_lock, flags); + my_done(shpnt, (current_SC->SCp.Status & 0xff) + | ((current_SC->SCp.Message & 0xff) << 8) | (DID_OK << 16)); + spin_unlock_irqrestore(shpnt->host_lock, flags); +#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 IRQ_HANDLED; +} + +static int fd_mcs_release(struct Scsi_Host *shpnt) +{ + int i, this_host, irq_usage; + + release_region(shpnt->io_port, shpnt->n_io_port); + + this_host = -1; + irq_usage = 0; + for (i = 0; i < found; i++) { + if (shpnt == hosts[i]) + this_host = i; + if (shpnt->irq == hosts[i]->irq) + irq_usage++; + } + + /* only for the last one */ + if (1 == irq_usage) + free_irq(shpnt->irq, hosts); + + found--; + + for (i = this_host; i < found; i++) + hosts[i] = hosts[i + 1]; + + hosts[found] = NULL; + + return 0; +} + +static int fd_mcs_queue(Scsi_Cmnd * SCpnt, void (*done) (Scsi_Cmnd *)) +{ + struct Scsi_Host *shpnt = SCpnt->device->host; + + if (in_command) { + panic("fd_mcs: fd_mcs_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 + + fd_mcs_make_bus_idle(shpnt); + + SCpnt->scsi_done = done; /* Save this for the done function */ + current_SC = SCpnt; + + /* Initialize static data */ + + if (current_SC->use_sg) { + current_SC->SCp.buffer = (struct scatterlist *) current_SC->request_buffer; + current_SC->SCp.ptr = page_address(current_SC->SCp.buffer->page) + current_SC->SCp.buffer->offset; + 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 = 1; + outb(0x20, Interrupt_Cntl_port); + outb(0x14 | PARITY_MASK, TMC_Cntl_port); /* Start arbitration */ + + return 0; +} + +#if DEBUG_ABORT || DEBUG_RESET +static void fd_mcs_print_info(Scsi_Cmnd * SCpnt) +{ + unsigned int imr; + unsigned int irr; + unsigned int isr; + struct Scsi_Host *shpnt = SCpnt->host; + + if (!SCpnt || !SCpnt->host) { + printk("fd_mcs: cannot provide detailed information\n"); + } + + printk("%s\n", fd_mcs_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->device->id, *(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 << shpnt->irq)) + 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(shpnt->io_port + FIFO_Status)); + printk("Int. Condition = 0x%02x\n", inb(shpnt->io_port + Interrupt_Cond)); + } + printk("Configuration 1 = 0x%02x\n", inb(shpnt->io_port + Configuration1)); + if (chip == tmc18c50 || chip == tmc18c30) + printk("Configuration 2 = 0x%02x\n", inb(shpnt->io_port + Configuration2)); +} +#endif + +static int fd_mcs_abort(Scsi_Cmnd * SCpnt) +{ + struct Scsi_Host *shpnt = SCpnt->device->host; + + unsigned long flags; +#if EVERY_ACCESS || ERRORS_ONLY || DEBUG_ABORT + printk("fd_mcs: abort "); +#endif + + spin_lock_irqsave(shpnt->host_lock, flags); + if (!in_command) { +#if EVERY_ACCESS || ERRORS_ONLY + printk(" (not in command)\n"); +#endif + spin_unlock_irqrestore(shpnt->host_lock, flags); + return FAILED; + } else + printk("\n"); + +#if DEBUG_ABORT + fd_mcs_print_info(SCpnt); +#endif + + fd_mcs_make_bus_idle(shpnt); + + current_SC->SCp.phase |= aborted; + + current_SC->result = DID_ABORT << 16; + + /* Aborts are not done well. . . */ + my_done(shpnt, DID_ABORT << 16); + + spin_unlock_irqrestore(shpnt->host_lock, flags); + return SUCCESS; +} + +static int fd_mcs_host_reset(Scsi_Cmnd * SCpnt) +{ + return FAILED; +} + +static int fd_mcs_device_reset(Scsi_Cmnd * SCpnt) +{ + return FAILED; +} + +static int fd_mcs_bus_reset(Scsi_Cmnd * SCpnt) { + struct Scsi_Host *shpnt = SCpnt->device->host; + +#if DEBUG_RESET + static int called_once = 0; +#endif + +#if ERRORS_ONLY + if (SCpnt) + printk("fd_mcs: SCSI Bus Reset\n"); +#endif + +#if DEBUG_RESET + if (called_once) + fd_mcs_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 SUCCESS; +} + +#include <scsi/scsi_ioctl.h> + +static int fd_mcs_biosparam(struct scsi_device * disk, struct block_device *bdev, + sector_t capacity, int *info_array) +{ + unsigned char *p = scsi_bios_ptable(bdev); + int size = capacity; + + /* BIOS >= 3.4 for MCA cards */ + /* This algorithm was provided by Future Domain (much thanks!). */ + + if (p && p[65] == 0xaa && p[64] == 0x55 /* Partition table valid */ + && p[4]) { /* 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] = p[5] + 1; /* heads */ + info_array[1] = p[6] & 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]); + kfree(p); + return 0; +} + +static Scsi_Host_Template driver_template = { + .proc_name = "fd_mcs", + .proc_info = fd_mcs_proc_info, + .detect = fd_mcs_detect, + .release = fd_mcs_release, + .info = fd_mcs_info, + .queuecommand = fd_mcs_queue, + .eh_abort_handler = fd_mcs_abort, + .eh_bus_reset_handler = fd_mcs_bus_reset, + .eh_host_reset_handler = fd_mcs_host_reset, + .eh_device_reset_handler = fd_mcs_device_reset, + .bios_param = fd_mcs_biosparam, + .can_queue = 1, + .this_id = 7, + .sg_tablesize = 64, + .cmd_per_lun = 1, + .use_clustering = DISABLE_CLUSTERING, +}; +#include "scsi_module.c" |