summaryrefslogtreecommitdiffstats
path: root/drivers/scsi/ibmmca.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/ibmmca.c
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/scsi/ibmmca.c')
-rw-r--r--drivers/scsi/ibmmca.c2491
1 files changed, 2491 insertions, 0 deletions
diff --git a/drivers/scsi/ibmmca.c b/drivers/scsi/ibmmca.c
new file mode 100644
index 000000000000..a3fdead9bce9
--- /dev/null
+++ b/drivers/scsi/ibmmca.c
@@ -0,0 +1,2491 @@
+/*
+ Low Level Linux Driver for the IBM Microchannel SCSI Subsystem for
+ Linux Kernel >= 2.4.0.
+ Copyright (c) 1995 Strom Systems, Inc. under the terms of the GNU
+ General Public License. Written by Martin Kolinek, December 1995.
+ Further development by: Chris Beauregard, Klaus Kudielka, Michael Lang
+ See the file Documentation/scsi/ibmmca.txt for a detailed description
+ of this driver, the commandline arguments and the history of its
+ development.
+ See the WWW-page: http://www.uni-mainz.de/~langm000/linux.html for latest
+ updates, info and ADF-files for adapters supported by this driver.
+
+ Alan Cox <alan@redhat.com>
+ Updated for Linux 2.5.45 to use the new error handler, cleaned up the
+ lock macros and did a few unavoidable locking tweaks, plus one locking
+ fix in the irq and completion path.
+
+ */
+
+#include <linux/config.h>
+#ifndef LINUX_VERSION_CODE
+#include <linux/version.h>
+#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,45)
+#error "This driver works only with kernel 2.5.45 or higher!"
+#endif
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/blkdev.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/mca.h>
+#include <linux/string.h>
+#include <linux/spinlock.h>
+#include <linux/init.h>
+#include <linux/mca-legacy.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+
+#include "scsi.h"
+#include <scsi/scsi_host.h>
+#include "ibmmca.h"
+
+/* current version of this driver-source: */
+#define IBMMCA_SCSI_DRIVER_VERSION "4.0b-ac"
+
+/* driver configuration */
+#define IM_MAX_HOSTS 8 /* maximum number of host adapters */
+#define IM_RESET_DELAY 60 /* seconds allowed for a reset */
+
+/* driver debugging - #undef all for normal operation */
+/* if defined: count interrupts and ignore this special one: */
+#undef IM_DEBUG_TIMEOUT //50
+#define TIMEOUT_PUN 0
+#define TIMEOUT_LUN 0
+/* verbose interrupt: */
+#undef IM_DEBUG_INT
+/* verbose queuecommand: */
+#undef IM_DEBUG_CMD
+/* verbose queucommand for specific SCSI-device type: */
+#undef IM_DEBUG_CMD_SPEC_DEV
+/* verbose device probing */
+#undef IM_DEBUG_PROBE
+
+/* device type that shall be displayed on syslog (only during debugging): */
+#define IM_DEBUG_CMD_DEVICE TYPE_TAPE
+
+/* relative addresses of hardware registers on a subsystem */
+#define IM_CMD_REG(hi) (hosts[(hi)]->io_port) /*Command Interface, (4 bytes long) */
+#define IM_ATTN_REG(hi) (hosts[(hi)]->io_port+4) /*Attention (1 byte) */
+#define IM_CTR_REG(hi) (hosts[(hi)]->io_port+5) /*Basic Control (1 byte) */
+#define IM_INTR_REG(hi) (hosts[(hi)]->io_port+6) /*Interrupt Status (1 byte, r/o) */
+#define IM_STAT_REG(hi) (hosts[(hi)]->io_port+7) /*Basic Status (1 byte, read only) */
+
+/* basic I/O-port of first adapter */
+#define IM_IO_PORT 0x3540
+/* maximum number of hosts that can be found */
+#define IM_N_IO_PORT 8
+
+/*requests going into the upper nibble of the Attention register */
+/*note: the lower nibble specifies the device(0-14), or subsystem(15) */
+#define IM_IMM_CMD 0x10 /*immediate command */
+#define IM_SCB 0x30 /*Subsystem Control Block command */
+#define IM_LONG_SCB 0x40 /*long Subsystem Control Block command */
+#define IM_EOI 0xe0 /*end-of-interrupt request */
+
+/*values for bits 7,1,0 of Basic Control reg. (bits 6-2 reserved) */
+#define IM_HW_RESET 0x80 /*hardware reset */
+#define IM_ENABLE_DMA 0x02 /*enable subsystem's busmaster DMA */
+#define IM_ENABLE_INTR 0x01 /*enable interrupts to the system */
+
+/*to interpret the upper nibble of Interrupt Status register */
+/*note: the lower nibble specifies the device(0-14), or subsystem(15) */
+#define IM_SCB_CMD_COMPLETED 0x10
+#define IM_SCB_CMD_COMPLETED_WITH_RETRIES 0x50
+#define IM_LOOP_SCATTER_BUFFER_FULL 0x60
+#define IM_ADAPTER_HW_FAILURE 0x70
+#define IM_IMMEDIATE_CMD_COMPLETED 0xa0
+#define IM_CMD_COMPLETED_WITH_FAILURE 0xc0
+#define IM_CMD_ERROR 0xe0
+#define IM_SOFTWARE_SEQUENCING_ERROR 0xf0
+
+/*to interpret bits 3-0 of Basic Status register (bits 7-4 reserved) */
+#define IM_CMD_REG_FULL 0x08
+#define IM_CMD_REG_EMPTY 0x04
+#define IM_INTR_REQUEST 0x02
+#define IM_BUSY 0x01
+
+/*immediate commands (word written into low 2 bytes of command reg) */
+#define IM_RESET_IMM_CMD 0x0400
+#define IM_FEATURE_CTR_IMM_CMD 0x040c
+#define IM_DMA_PACING_IMM_CMD 0x040d
+#define IM_ASSIGN_IMM_CMD 0x040e
+#define IM_ABORT_IMM_CMD 0x040f
+#define IM_FORMAT_PREP_IMM_CMD 0x0417
+
+/*SCB (Subsystem Control Block) structure */
+struct im_scb {
+ unsigned short command; /*command word (read, etc.) */
+ unsigned short enable; /*enable word, modifies cmd */
+ union {
+ unsigned long log_blk_adr; /*block address on SCSI device */
+ unsigned char scsi_cmd_length; /*6,10,12, for other scsi cmd */
+ } u1;
+ unsigned long sys_buf_adr; /*physical system memory adr */
+ unsigned long sys_buf_length; /*size of sys mem buffer */
+ unsigned long tsb_adr; /*Termination Status Block adr */
+ unsigned long scb_chain_adr; /*optional SCB chain address */
+ union {
+ struct {
+ unsigned short count; /*block count, on SCSI device */
+ unsigned short length; /*block length, on SCSI device */
+ } blk;
+ unsigned char scsi_command[12]; /*other scsi command */
+ } u2;
+};
+
+/*structure scatter-gather element (for list of system memory areas) */
+struct im_sge {
+ void *address;
+ unsigned long byte_length;
+};
+
+/*structure returned by a get_pos_info command: */
+struct im_pos_info {
+ unsigned short pos_id; /* adapter id */
+ unsigned char pos_3a; /* pos 3 (if pos 6 = 0) */
+ unsigned char pos_2; /* pos 2 */
+ unsigned char int_level; /* interrupt level IRQ 11 or 14 */
+ unsigned char pos_4a; /* pos 4 (if pos 6 = 0) */
+ unsigned short connector_size; /* MCA connector size: 16 or 32 Bit */
+ unsigned char num_luns; /* number of supported luns per device */
+ unsigned char num_puns; /* number of supported puns */
+ unsigned char pacing_factor; /* pacing factor */
+ unsigned char num_ldns; /* number of ldns available */
+ unsigned char eoi_off; /* time EOI and interrupt inactive */
+ unsigned char max_busy; /* time between reset and busy on */
+ unsigned short cache_stat; /* ldn cachestat. Bit=1 = not cached */
+ unsigned short retry_stat; /* retry status of ldns. Bit=1=disabled */
+ unsigned char pos_4b; /* pos 4 (if pos 6 = 1) */
+ unsigned char pos_3b; /* pos 3 (if pos 6 = 1) */
+ unsigned char pos_6; /* pos 6 */
+ unsigned char pos_5; /* pos 5 */
+ unsigned short max_overlap; /* maximum overlapping requests */
+ unsigned short num_bus; /* number of SCSI-busses */
+};
+
+/*values for SCB command word */
+#define IM_NO_SYNCHRONOUS 0x0040 /*flag for any command */
+#define IM_NO_DISCONNECT 0x0080 /*flag for any command */
+#define IM_READ_DATA_CMD 0x1c01
+#define IM_WRITE_DATA_CMD 0x1c02
+#define IM_READ_VERIFY_CMD 0x1c03
+#define IM_WRITE_VERIFY_CMD 0x1c04
+#define IM_REQUEST_SENSE_CMD 0x1c08
+#define IM_READ_CAPACITY_CMD 0x1c09
+#define IM_DEVICE_INQUIRY_CMD 0x1c0b
+#define IM_READ_LOGICAL_CMD 0x1c2a
+#define IM_OTHER_SCSI_CMD_CMD 0x241f
+
+/* unused, but supported, SCB commands */
+#define IM_GET_COMMAND_COMPLETE_STATUS_CMD 0x1c07 /* command status */
+#define IM_GET_POS_INFO_CMD 0x1c0a /* returns neat stuff */
+#define IM_READ_PREFETCH_CMD 0x1c31 /* caching controller only */
+#define IM_FOMAT_UNIT_CMD 0x1c16 /* format unit */
+#define IM_REASSIGN_BLOCK_CMD 0x1c18 /* in case of error */
+
+/*values to set bits in the enable word of SCB */
+#define IM_READ_CONTROL 0x8000
+#define IM_REPORT_TSB_ONLY_ON_ERROR 0x4000
+#define IM_RETRY_ENABLE 0x2000
+#define IM_POINTER_TO_LIST 0x1000
+#define IM_SUPRESS_EXCEPTION_SHORT 0x0400
+#define IM_BYPASS_BUFFER 0x0200
+#define IM_CHAIN_ON_NO_ERROR 0x0001
+
+/*TSB (Termination Status Block) structure */
+struct im_tsb {
+ unsigned short end_status;
+ unsigned short reserved1;
+ unsigned long residual_byte_count;
+ unsigned long sg_list_element_adr;
+ unsigned short status_length;
+ unsigned char dev_status;
+ unsigned char cmd_status;
+ unsigned char dev_error;
+ unsigned char cmd_error;
+ unsigned short reserved2;
+ unsigned short reserved3;
+ unsigned short low_of_last_scb_adr;
+ unsigned short high_of_last_scb_adr;
+};
+
+/*subsystem uses interrupt request level 14 */
+#define IM_IRQ 14
+/*SCSI-2 F/W may evade to interrupt 11 */
+#define IM_IRQ_FW 11
+
+/* Model 95 has an additional alphanumeric display, which can be used
+ to display SCSI-activities. 8595 models do not have any disk led, which
+ makes this feature quite useful.
+ The regular PS/2 disk led is turned on/off by bits 6,7 of system
+ control port. */
+
+/* LED display-port (actually, last LED on display) */
+#define MOD95_LED_PORT 0x108
+/* system-control-register of PS/2s with diskindicator */
+#define PS2_SYS_CTR 0x92
+/* activity displaying methods */
+#define LED_DISP 1
+#define LED_ADISP 2
+#define LED_ACTIVITY 4
+/* failed intr */
+#define CMD_FAIL 255
+
+/* The SCSI-ID(!) of the accessed SCSI-device is shown on PS/2-95 machines' LED
+ displays. ldn is no longer displayed here, because the ldn mapping is now
+ done dynamically and the ldn <-> pun,lun maps can be looked-up at boottime
+ or during uptime in /proc/scsi/ibmmca/<host_no> in case of trouble,
+ interest, debugging or just for having fun. The left number gives the
+ host-adapter number and the right shows the accessed SCSI-ID. */
+
+/* display_mode is set by the ibmmcascsi= command line arg */
+static int display_mode = 0;
+/* set default adapter timeout */
+static unsigned int adapter_timeout = 45;
+/* for probing on feature-command: */
+static unsigned int global_command_error_excuse = 0;
+/* global setting by command line for adapter_speed */
+static int global_adapter_speed = 0; /* full speed by default */
+
+/* Panel / LED on, do it right for F/W addressin, too. adisplay will
+ * just ignore ids>7, as the panel has only 7 digits available */
+#define PS2_DISK_LED_ON(ad,id) { if (display_mode & LED_DISP) { if (id>9) \
+ outw((ad+48)|((id+55)<<8), MOD95_LED_PORT ); else \
+ outw((ad+48)|((id+48)<<8), MOD95_LED_PORT ); } else \
+ if (display_mode & LED_ADISP) { if (id<7) outb((char)(id+48),MOD95_LED_PORT+1+id); \
+ outb((char)(ad+48), MOD95_LED_PORT); } \
+ if ((display_mode & LED_ACTIVITY)||(!display_mode)) \
+ outb(inb(PS2_SYS_CTR) | 0xc0, PS2_SYS_CTR); }
+
+/* Panel / LED off */
+/* bug fixed, Dec 15, 1997, where | was replaced by & here */
+#define PS2_DISK_LED_OFF() { if (display_mode & LED_DISP) \
+ outw(0x2020, MOD95_LED_PORT ); else if (display_mode & LED_ADISP) { \
+ outl(0x20202020,MOD95_LED_PORT); outl(0x20202020,MOD95_LED_PORT+4); } \
+ if ((display_mode & LED_ACTIVITY)||(!display_mode)) \
+ outb(inb(PS2_SYS_CTR) & 0x3f, PS2_SYS_CTR); }
+
+/*list of supported subsystems */
+struct subsys_list_struct {
+ unsigned short mca_id;
+ char *description;
+};
+
+/* types of different supported hardware that goes to hostdata special */
+#define IBM_SCSI2_FW 0
+#define IBM_7568_WCACHE 1
+#define IBM_EXP_UNIT 2
+#define IBM_SCSI_WCACHE 3
+#define IBM_SCSI 4
+
+/* other special flags for hostdata structure */
+#define FORCED_DETECTION 100
+#define INTEGRATED_SCSI 101
+
+/* List of possible IBM-SCSI-adapters */
+static struct subsys_list_struct subsys_list[] = {
+ {0x8efc, "IBM SCSI-2 F/W Adapter"}, /* special = 0 */
+ {0x8efd, "IBM 7568 Industrial Computer SCSI Adapter w/Cache"}, /* special = 1 */
+ {0x8ef8, "IBM Expansion Unit SCSI Controller"}, /* special = 2 */
+ {0x8eff, "IBM SCSI Adapter w/Cache"}, /* special = 3 */
+ {0x8efe, "IBM SCSI Adapter"}, /* special = 4 */
+};
+
+/* Max number of logical devices (can be up from 0 to 14). 15 is the address
+of the adapter itself. */
+#define MAX_LOG_DEV 15
+
+/*local data for a logical device */
+struct logical_device {
+ struct im_scb scb; /* SCSI-subsystem-control-block structure */
+ struct im_tsb tsb; /* SCSI command complete status block structure */
+ struct im_sge sge[16]; /* scatter gather list structure */
+ unsigned char buf[256]; /* SCSI command return data buffer */
+ Scsi_Cmnd *cmd; /* SCSI-command that is currently in progress */
+ int device_type; /* type of the SCSI-device. See include/scsi/scsi.h
+ for interpretation of the possible values */
+ int block_length; /* blocksize of a particular logical SCSI-device */
+ int cache_flag; /* 1 if this is uncached, 0 if cache is present for ldn */
+ int retry_flag; /* 1 if adapter retry is disabled, 0 if enabled */
+};
+
+/* statistics of the driver during operations (for proc_info) */
+struct Driver_Statistics {
+ /* SCSI statistics on the adapter */
+ int ldn_access[MAX_LOG_DEV + 1]; /* total accesses on a ldn */
+ int ldn_read_access[MAX_LOG_DEV + 1]; /* total read-access on a ldn */
+ int ldn_write_access[MAX_LOG_DEV + 1]; /* total write-access on a ldn */
+ int ldn_inquiry_access[MAX_LOG_DEV + 1]; /* total inquiries on a ldn */
+ int ldn_modeselect_access[MAX_LOG_DEV + 1]; /* total mode selects on ldn */
+ int scbs; /* short SCBs queued */
+ int long_scbs; /* long SCBs queued */
+ int total_accesses; /* total accesses on all ldns */
+ int total_interrupts; /* total interrupts (should be
+ same as total_accesses) */
+ int total_errors; /* command completed with error */
+ /* dynamical assignment statistics */
+ int total_scsi_devices; /* number of physical pun,lun */
+ int dyn_flag; /* flag showing dynamical mode */
+ int dynamical_assignments; /* number of remappings of ldns */
+ int ldn_assignments[MAX_LOG_DEV + 1]; /* number of remappings of each
+ ldn */
+};
+
+/* data structure for each host adapter */
+struct ibmmca_hostdata {
+ /* array of logical devices: */
+ struct logical_device _ld[MAX_LOG_DEV + 1];
+ /* array to convert (pun, lun) into logical device number: */
+ unsigned char _get_ldn[16][8];
+ /*array that contains the information about the physical SCSI-devices
+ attached to this host adapter: */
+ unsigned char _get_scsi[16][8];
+ /* used only when checking logical devices: */
+ int _local_checking_phase_flag;
+ /* report received interrupt: */
+ int _got_interrupt;
+ /* report termination-status of SCSI-command: */
+ int _stat_result;
+ /* reset status (used only when doing reset): */
+ int _reset_status;
+ /* code of the last SCSI command (needed for panic info): */
+ int _last_scsi_command[MAX_LOG_DEV + 1];
+ /* identifier of the last SCSI-command type */
+ int _last_scsi_type[MAX_LOG_DEV + 1];
+ /* last blockcount */
+ int _last_scsi_blockcount[MAX_LOG_DEV + 1];
+ /* last locgical block address */
+ unsigned long _last_scsi_logical_block[MAX_LOG_DEV + 1];
+ /* Counter that points on the next reassignable ldn for dynamical
+ remapping. The default value is 7, that is the first reassignable
+ number in the list at boottime: */
+ int _next_ldn;
+ /* Statistics-structure for this IBM-SCSI-host: */
+ struct Driver_Statistics _IBM_DS;
+ /* This hostadapters pos-registers pos2 until pos6 */
+ unsigned int _pos[8];
+ /* assign a special variable, that contains dedicated info about the
+ adaptertype */
+ int _special;
+ /* connector size on the MCA bus */
+ int _connector_size;
+ /* synchronous SCSI transfer rate bitpattern */
+ int _adapter_speed;
+};
+
+/* macros to access host data structure */
+#define subsystem_pun(hi) (hosts[(hi)]->this_id)
+#define subsystem_maxid(hi) (hosts[(hi)]->max_id)
+#define ld(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_ld)
+#define get_ldn(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_get_ldn)
+#define get_scsi(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_get_scsi)
+#define local_checking_phase_flag(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_local_checking_phase_flag)
+#define got_interrupt(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_got_interrupt)
+#define stat_result(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_stat_result)
+#define reset_status(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_reset_status)
+#define last_scsi_command(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_last_scsi_command)
+#define last_scsi_type(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_last_scsi_type)
+#define last_scsi_blockcount(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_last_scsi_blockcount)
+#define last_scsi_logical_block(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_last_scsi_logical_block)
+#define last_scsi_type(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_last_scsi_type)
+#define next_ldn(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_next_ldn)
+#define IBM_DS(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_IBM_DS)
+#define special(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_special)
+#define subsystem_connector_size(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_connector_size)
+#define adapter_speed(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_adapter_speed)
+#define pos2(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_pos[2])
+#define pos3(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_pos[3])
+#define pos4(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_pos[4])
+#define pos5(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_pos[5])
+#define pos6(hi) (((struct ibmmca_hostdata *) hosts[(hi)]->hostdata)->_pos[6])
+
+/* Define a arbitrary number as subsystem-marker-type. This number is, as
+ described in the ANSI-SCSI-standard, not occupied by other device-types. */
+#define TYPE_IBM_SCSI_ADAPTER 0x2F
+
+/* Define 0xFF for no device type, because this type is not defined within
+ the ANSI-SCSI-standard, therefore, it can be used and should not cause any
+ harm. */
+#define TYPE_NO_DEVICE 0xFF
+
+/* define medium-changer. If this is not defined previously, e.g. Linux
+ 2.0.x, define this type here. */
+#ifndef TYPE_MEDIUM_CHANGER
+#define TYPE_MEDIUM_CHANGER 0x08
+#endif
+
+/* define possible operations for the immediate_assign command */
+#define SET_LDN 0
+#define REMOVE_LDN 1
+
+/* ldn which is used to probe the SCSI devices */
+#define PROBE_LDN 0
+
+/* reset status flag contents */
+#define IM_RESET_NOT_IN_PROGRESS 0
+#define IM_RESET_IN_PROGRESS 1
+#define IM_RESET_FINISHED_OK 2
+#define IM_RESET_FINISHED_FAIL 3
+#define IM_RESET_NOT_IN_PROGRESS_NO_INT 4
+#define IM_RESET_FINISHED_OK_NO_INT 5
+
+/* define undefined SCSI-command */
+#define NO_SCSI 0xffff
+
+/*-----------------------------------------------------------------------*/
+
+/* if this is nonzero, ibmmcascsi option has been passed to the kernel */
+static int io_port[IM_MAX_HOSTS] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+static int scsi_id[IM_MAX_HOSTS] = { 7, 7, 7, 7, 7, 7, 7, 7 };
+
+/* fill module-parameters only, when this define is present.
+ (that is kernel version 2.1.x) */
+#if defined(MODULE)
+static char *boot_options = NULL;
+module_param(boot_options, charp, 0);
+module_param_array(io_port, int, NULL, 0);
+module_param_array(scsi_id, int, NULL, 0);
+
+#if 0 /* FIXME: No longer exist? --RR */
+MODULE_PARM(display, "1i");
+MODULE_PARM(adisplay, "1i");
+MODULE_PARM(normal, "1i");
+MODULE_PARM(ansi, "1i");
+#endif
+#endif
+/*counter of concurrent disk read/writes, to turn on/off disk led */
+static int disk_rw_in_progress = 0;
+
+/* host information */
+static int found = 0;
+static struct Scsi_Host *hosts[IM_MAX_HOSTS + 1] = {
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+static unsigned int pos[8]; /* whole pos register-line for diagnosis */
+/* Taking into account the additions, made by ZP Gu.
+ * This selects now the preset value from the configfile and
+ * offers the 'normal' commandline option to be accepted */
+#ifdef CONFIG_IBMMCA_SCSI_ORDER_STANDARD
+static char ibm_ansi_order = 1;
+#else
+static char ibm_ansi_order = 0;
+#endif
+
+static void issue_cmd(int, unsigned long, unsigned char);
+static void internal_done(Scsi_Cmnd * cmd);
+static void check_devices(int, int);
+static int immediate_assign(int, unsigned int, unsigned int, unsigned int, unsigned int);
+static int immediate_feature(int, unsigned int, unsigned int);
+#ifdef CONFIG_IBMMCA_SCSI_DEV_RESET
+static int immediate_reset(int, unsigned int);
+#endif
+static int device_inquiry(int, int);
+static int read_capacity(int, int);
+static int get_pos_info(int);
+static char *ti_p(int);
+static char *ti_l(int);
+static char *ibmrate(unsigned int, int);
+static int probe_display(int);
+static int probe_bus_mode(int);
+static int device_exists(int, int, int *, int *);
+static struct Scsi_Host *ibmmca_register(Scsi_Host_Template *, int, int, int, char *);
+static int option_setup(char *);
+/* local functions needed for proc_info */
+static int ldn_access_load(int, int);
+static int ldn_access_total_read_write(int);
+
+static irqreturn_t interrupt_handler(int irq, void *dev_id,
+ struct pt_regs *regs)
+{
+ int host_index, ihost_index;
+ unsigned int intr_reg;
+ unsigned int cmd_result;
+ unsigned int ldn;
+ Scsi_Cmnd *cmd;
+ int lastSCSI;
+ struct Scsi_Host *dev = dev_id;
+
+ spin_lock(dev->host_lock);
+ /* search for one adapter-response on shared interrupt */
+ for (host_index = 0; hosts[host_index] && !(inb(IM_STAT_REG(host_index)) & IM_INTR_REQUEST); host_index++);
+ /* return if some other device on this IRQ caused the interrupt */
+ if (!hosts[host_index]) {
+ spin_unlock(dev->host_lock);
+ return IRQ_NONE;
+ }
+
+ /* the reset-function already did all the job, even ints got
+ renabled on the subsystem, so just return */
+ if ((reset_status(host_index) == IM_RESET_NOT_IN_PROGRESS_NO_INT) || (reset_status(host_index) == IM_RESET_FINISHED_OK_NO_INT)) {
+ reset_status(host_index) = IM_RESET_NOT_IN_PROGRESS;
+ spin_unlock(dev->host_lock);
+ return IRQ_HANDLED;
+ }
+
+ /*must wait for attention reg not busy, then send EOI to subsystem */
+ while (1) {
+ if (!(inb(IM_STAT_REG(host_index)) & IM_BUSY))
+ break;
+ cpu_relax();
+ }
+ ihost_index = host_index;
+ /*get command result and logical device */
+ intr_reg = (unsigned char) (inb(IM_INTR_REG(ihost_index)));
+ cmd_result = intr_reg & 0xf0;
+ ldn = intr_reg & 0x0f;
+ /* get the last_scsi_command here */
+ lastSCSI = last_scsi_command(ihost_index)[ldn];
+ outb(IM_EOI | ldn, IM_ATTN_REG(ihost_index));
+
+ /*these should never happen (hw fails, or a local programming bug) */
+ if (!global_command_error_excuse) {
+ switch (cmd_result) {
+ /* Prevent from Ooopsing on error to show the real reason */
+ case IM_ADAPTER_HW_FAILURE:
+ case IM_SOFTWARE_SEQUENCING_ERROR:
+ case IM_CMD_ERROR:
+ printk(KERN_ERR "IBM MCA SCSI: Fatal Subsystem ERROR!\n");
+ printk(KERN_ERR " Last cmd=0x%x, ena=%x, len=", lastSCSI, ld(ihost_index)[ldn].scb.enable);
+ if (ld(ihost_index)[ldn].cmd)
+ printk("%ld/%ld,", (long) (ld(ihost_index)[ldn].cmd->request_bufflen), (long) (ld(ihost_index)[ldn].scb.sys_buf_length));
+ else
+ printk("none,");
+ if (ld(ihost_index)[ldn].cmd)
+ printk("Blocksize=%d", ld(ihost_index)[ldn].scb.u2.blk.length);
+ else
+ printk("Blocksize=none");
+ printk(", host=0x%x, ldn=0x%x\n", ihost_index, ldn);
+ if (ld(ihost_index)[ldn].cmd) {
+ printk(KERN_ERR "Blockcount=%d/%d\n", last_scsi_blockcount(ihost_index)[ldn], ld(ihost_index)[ldn].scb.u2.blk.count);
+ printk(KERN_ERR "Logical block=%lx/%lx\n", last_scsi_logical_block(ihost_index)[ldn], ld(ihost_index)[ldn].scb.u1.log_blk_adr);
+ }
+ printk(KERN_ERR "Reason given: %s\n", (cmd_result == IM_ADAPTER_HW_FAILURE) ? "HARDWARE FAILURE" : (cmd_result == IM_SOFTWARE_SEQUENCING_ERROR) ? "SOFTWARE SEQUENCING ERROR" : (cmd_result == IM_CMD_ERROR) ? "COMMAND ERROR" : "UNKNOWN");
+ /* if errors appear, enter this section to give detailed info */
+ printk(KERN_ERR "IBM MCA SCSI: Subsystem Error-Status follows:\n");
+ printk(KERN_ERR " Command Type................: %x\n", last_scsi_type(ihost_index)[ldn]);
+ printk(KERN_ERR " Attention Register..........: %x\n", inb(IM_ATTN_REG(ihost_index)));
+ printk(KERN_ERR " Basic Control Register......: %x\n", inb(IM_CTR_REG(ihost_index)));
+ printk(KERN_ERR " Interrupt Status Register...: %x\n", intr_reg);
+ printk(KERN_ERR " Basic Status Register.......: %x\n", inb(IM_STAT_REG(ihost_index)));
+ if ((last_scsi_type(ihost_index)[ldn] == IM_SCB) || (last_scsi_type(ihost_index)[ldn] == IM_LONG_SCB)) {
+ printk(KERN_ERR " SCB-Command.................: %x\n", ld(ihost_index)[ldn].scb.command);
+ printk(KERN_ERR " SCB-Enable..................: %x\n", ld(ihost_index)[ldn].scb.enable);
+ printk(KERN_ERR " SCB-logical block address...: %lx\n", ld(ihost_index)[ldn].scb.u1.log_blk_adr);
+ printk(KERN_ERR " SCB-system buffer address...: %lx\n", ld(ihost_index)[ldn].scb.sys_buf_adr);
+ printk(KERN_ERR " SCB-system buffer length....: %lx\n", ld(ihost_index)[ldn].scb.sys_buf_length);
+ printk(KERN_ERR " SCB-tsb address.............: %lx\n", ld(ihost_index)[ldn].scb.tsb_adr);
+ printk(KERN_ERR " SCB-Chain address...........: %lx\n", ld(ihost_index)[ldn].scb.scb_chain_adr);
+ printk(KERN_ERR " SCB-block count.............: %x\n", ld(ihost_index)[ldn].scb.u2.blk.count);
+ printk(KERN_ERR " SCB-block length............: %x\n", ld(ihost_index)[ldn].scb.u2.blk.length);
+ }
+ printk(KERN_ERR " Send this report to the maintainer.\n");
+ panic("IBM MCA SCSI: Fatal error message from the subsystem (0x%X,0x%X)!\n", lastSCSI, cmd_result);
+ break;
+ }
+ } else {
+ /* The command error handling is made silent, but we tell the
+ * calling function, that there is a reported error from the
+ * adapter. */
+ switch (cmd_result) {
+ case IM_ADAPTER_HW_FAILURE:
+ case IM_SOFTWARE_SEQUENCING_ERROR:
+ case IM_CMD_ERROR:
+ global_command_error_excuse = CMD_FAIL;
+ break;
+ default:
+ global_command_error_excuse = 0;
+ break;
+ }
+ }
+ /* if no panic appeared, increase the interrupt-counter */
+ IBM_DS(ihost_index).total_interrupts++;
+ /*only for local checking phase */
+ if (local_checking_phase_flag(ihost_index)) {
+ stat_result(ihost_index) = cmd_result;
+ got_interrupt(ihost_index) = 1;
+ reset_status(ihost_index) = IM_RESET_FINISHED_OK;
+ last_scsi_command(ihost_index)[ldn] = NO_SCSI;
+ spin_unlock(dev->host_lock);
+ return IRQ_HANDLED;
+ }
+ /* handling of commands coming from upper level of scsi driver */
+ if (last_scsi_type(ihost_index)[ldn] == IM_IMM_CMD) {
+ /* verify ldn, and may handle rare reset immediate command */
+ if ((reset_status(ihost_index) == IM_RESET_IN_PROGRESS) && (last_scsi_command(ihost_index)[ldn] == IM_RESET_IMM_CMD)) {
+ if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE) {
+ disk_rw_in_progress = 0;
+ PS2_DISK_LED_OFF();
+ reset_status(ihost_index) = IM_RESET_FINISHED_FAIL;
+ } else {
+ /*reset disk led counter, turn off disk led */
+ disk_rw_in_progress = 0;
+ PS2_DISK_LED_OFF();
+ reset_status(ihost_index) = IM_RESET_FINISHED_OK;
+ }
+ stat_result(ihost_index) = cmd_result;
+ last_scsi_command(ihost_index)[ldn] = NO_SCSI;
+ last_scsi_type(ihost_index)[ldn] = 0;
+ spin_unlock(dev->host_lock);
+ return IRQ_HANDLED;
+ } else if (last_scsi_command(ihost_index)[ldn] == IM_ABORT_IMM_CMD) {
+ /* react on SCSI abort command */
+#ifdef IM_DEBUG_PROBE
+ printk("IBM MCA SCSI: Interrupt from SCSI-abort.\n");
+#endif
+ disk_rw_in_progress = 0;
+ PS2_DISK_LED_OFF();
+ cmd = ld(ihost_index)[ldn].cmd;
+ ld(ihost_index)[ldn].cmd = NULL;
+ if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE)
+ cmd->result = DID_NO_CONNECT << 16;
+ else
+ cmd->result = DID_ABORT << 16;
+ stat_result(ihost_index) = cmd_result;
+ last_scsi_command(ihost_index)[ldn] = NO_SCSI;
+ last_scsi_type(ihost_index)[ldn] = 0;
+ if (cmd->scsi_done)
+ (cmd->scsi_done) (cmd); /* should be the internal_done */
+ spin_unlock(dev->host_lock);
+ return IRQ_HANDLED;
+ } else {
+ disk_rw_in_progress = 0;
+ PS2_DISK_LED_OFF();
+ reset_status(ihost_index) = IM_RESET_FINISHED_OK;
+ stat_result(ihost_index) = cmd_result;
+ last_scsi_command(ihost_index)[ldn] = NO_SCSI;
+ spin_unlock(dev->host_lock);
+ return IRQ_HANDLED;
+ }
+ }
+ last_scsi_command(ihost_index)[ldn] = NO_SCSI;
+ last_scsi_type(ihost_index)[ldn] = 0;
+ cmd = ld(ihost_index)[ldn].cmd;
+ ld(ihost_index)[ldn].cmd = NULL;
+#ifdef IM_DEBUG_TIMEOUT
+ if (cmd) {
+ if ((cmd->target == TIMEOUT_PUN) && (cmd->device->lun == TIMEOUT_LUN)) {
+ printk("IBM MCA SCSI: Ignoring interrupt from pun=%x, lun=%x.\n", cmd->target, cmd->device->lun);
+ return IRQ_HANDLED;
+ }
+ }
+#endif
+ /*if no command structure, just return, else clear cmd */
+ if (!cmd)
+ {
+ spin_unlock(dev->host_lock);
+ return IRQ_HANDLED;
+ }
+
+#ifdef IM_DEBUG_INT
+ printk("cmd=%02x ireg=%02x ds=%02x cs=%02x de=%02x ce=%02x\n", cmd->cmnd[0], intr_reg, ld(ihost_index)[ldn].tsb.dev_status, ld(ihost_index)[ldn].tsb.cmd_status, ld(ihost_index)[ldn].tsb.dev_error, ld(ihost_index)[ldn].tsb.cmd_error);
+#endif
+ /*if this is end of media read/write, may turn off PS/2 disk led */
+ if ((ld(ihost_index)[ldn].device_type != TYPE_NO_LUN) && (ld(ihost_index)[ldn].device_type != TYPE_NO_DEVICE)) {
+ /* only access this, if there was a valid device addressed */
+ if (--disk_rw_in_progress == 0)
+ PS2_DISK_LED_OFF();
+ }
+
+ /* IBM describes the status-mask to be 0x1e, but this is not conform
+ * with SCSI-definition, I suppose, the reason for it is that IBM
+ * adapters do not support CMD_TERMINATED, TASK_SET_FULL and
+ * ACA_ACTIVE as returning statusbyte information. (ML) */
+ if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE) {
+ cmd->result = (unsigned char) (ld(ihost_index)[ldn].tsb.dev_status & 0x1e);
+ IBM_DS(ihost_index).total_errors++;
+ } else
+ cmd->result = 0;
+ /* write device status into cmd->result, and call done function */
+ if (lastSCSI == NO_SCSI) { /* unexpected interrupt :-( */
+ cmd->result |= DID_BAD_INTR << 16;
+ printk("IBM MCA SCSI: WARNING - Interrupt from non-pending SCSI-command!\n");
+ } else /* things went right :-) */
+ cmd->result |= DID_OK << 16;
+ if (cmd->scsi_done)
+ (cmd->scsi_done) (cmd);
+ spin_unlock(dev->host_lock);
+ return IRQ_HANDLED;
+}
+
+static void issue_cmd(int host_index, unsigned long cmd_reg, unsigned char attn_reg)
+{
+ unsigned long flags;
+ /* must wait for attention reg not busy */
+ while (1) {
+ spin_lock_irqsave(hosts[host_index]->host_lock, flags);
+ if (!(inb(IM_STAT_REG(host_index)) & IM_BUSY))
+ break;
+ spin_unlock_irqrestore(hosts[host_index]->host_lock, flags);
+ }
+ /* write registers and enable system interrupts */
+ outl(cmd_reg, IM_CMD_REG(host_index));
+ outb(attn_reg, IM_ATTN_REG(host_index));
+ spin_unlock_irqrestore(hosts[host_index]->host_lock, flags);
+}
+
+static void internal_done(Scsi_Cmnd * cmd)
+{
+ cmd->SCp.Status++;
+ return;
+}
+
+/* SCSI-SCB-command for device_inquiry */
+static int device_inquiry(int host_index, int ldn)
+{
+ int retr;
+ struct im_scb *scb;
+ struct im_tsb *tsb;
+ unsigned char *buf;
+
+ scb = &(ld(host_index)[ldn].scb);
+ tsb = &(ld(host_index)[ldn].tsb);
+ buf = (unsigned char *) (&(ld(host_index)[ldn].buf));
+ ld(host_index)[ldn].tsb.dev_status = 0; /* prepare statusblock */
+ for (retr = 0; retr < 3; retr++) {
+ /* fill scb with inquiry command */
+ scb->command = IM_DEVICE_INQUIRY_CMD | IM_NO_DISCONNECT;
+ scb->enable = IM_REPORT_TSB_ONLY_ON_ERROR | IM_READ_CONTROL | IM_SUPRESS_EXCEPTION_SHORT | IM_RETRY_ENABLE | IM_BYPASS_BUFFER;
+ last_scsi_command(host_index)[ldn] = IM_DEVICE_INQUIRY_CMD;
+ last_scsi_type(host_index)[ldn] = IM_SCB;
+ scb->sys_buf_adr = isa_virt_to_bus(buf);
+ scb->sys_buf_length = 255; /* maximum bufferlength gives max info */
+ scb->tsb_adr = isa_virt_to_bus(tsb);
+ /* issue scb to passed ldn, and busy wait for interrupt */
+ got_interrupt(host_index) = 0;
+ issue_cmd(host_index, isa_virt_to_bus(scb), IM_SCB | ldn);
+ while (!got_interrupt(host_index))
+ barrier();
+
+ /*if command succesful, break */
+ if ((stat_result(host_index) == IM_SCB_CMD_COMPLETED) || (stat_result(host_index) == IM_SCB_CMD_COMPLETED_WITH_RETRIES))
+ return 1;
+ }
+ /*if all three retries failed, return "no device at this ldn" */
+ if (retr >= 3)
+ return 0;
+ else
+ return 1;
+}
+
+static int read_capacity(int host_index, int ldn)
+{
+ int retr;
+ struct im_scb *scb;
+ struct im_tsb *tsb;
+ unsigned char *buf;
+
+ scb = &(ld(host_index)[ldn].scb);
+ tsb = &(ld(host_index)[ldn].tsb);
+ buf = (unsigned char *) (&(ld(host_index)[ldn].buf));
+ ld(host_index)[ldn].tsb.dev_status = 0;
+ for (retr = 0; retr < 3; retr++) {
+ /*fill scb with read capacity command */
+ scb->command = IM_READ_CAPACITY_CMD;
+ scb->enable = IM_REPORT_TSB_ONLY_ON_ERROR | IM_READ_CONTROL | IM_RETRY_ENABLE | IM_BYPASS_BUFFER;
+ last_scsi_command(host_index)[ldn] = IM_READ_CAPACITY_CMD;
+ last_scsi_type(host_index)[ldn] = IM_SCB;
+ scb->sys_buf_adr = isa_virt_to_bus(buf);
+ scb->sys_buf_length = 8;
+ scb->tsb_adr = isa_virt_to_bus(tsb);
+ /*issue scb to passed ldn, and busy wait for interrupt */
+ got_interrupt(host_index) = 0;
+ issue_cmd(host_index, isa_virt_to_bus(scb), IM_SCB | ldn);
+ while (!got_interrupt(host_index))
+ barrier();
+
+ /*if got capacity, get block length and return one device found */
+ if ((stat_result(host_index) == IM_SCB_CMD_COMPLETED) || (stat_result(host_index) == IM_SCB_CMD_COMPLETED_WITH_RETRIES))
+ return 1;
+ }
+ /*if all three retries failed, return "no device at this ldn" */
+ if (retr >= 3)
+ return 0;
+ else
+ return 1;
+}
+
+static int get_pos_info(int host_index)
+{
+ int retr;
+ struct im_scb *scb;
+ struct im_tsb *tsb;
+ unsigned char *buf;
+
+ scb = &(ld(host_index)[MAX_LOG_DEV].scb);
+ tsb = &(ld(host_index)[MAX_LOG_DEV].tsb);
+ buf = (unsigned char *) (&(ld(host_index)[MAX_LOG_DEV].buf));
+ ld(host_index)[MAX_LOG_DEV].tsb.dev_status = 0;
+ for (retr = 0; retr < 3; retr++) {
+ /*fill scb with get_pos_info command */
+ scb->command = IM_GET_POS_INFO_CMD;
+ scb->enable = IM_READ_CONTROL | IM_REPORT_TSB_ONLY_ON_ERROR | IM_RETRY_ENABLE | IM_BYPASS_BUFFER;
+ last_scsi_command(host_index)[MAX_LOG_DEV] = IM_GET_POS_INFO_CMD;
+ last_scsi_type(host_index)[MAX_LOG_DEV] = IM_SCB;
+ scb->sys_buf_adr = isa_virt_to_bus(buf);
+ if (special(host_index) == IBM_SCSI2_FW)
+ scb->sys_buf_length = 256; /* get all info from F/W adapter */
+ else
+ scb->sys_buf_length = 18; /* get exactly 18 bytes for other SCSI */
+ scb->tsb_adr = isa_virt_to_bus(tsb);
+ /*issue scb to ldn=15, and busy wait for interrupt */
+ got_interrupt(host_index) = 0;
+ issue_cmd(host_index, isa_virt_to_bus(scb), IM_SCB | MAX_LOG_DEV);
+
+ /* FIXME: timeout */
+ while (!got_interrupt(host_index))
+ barrier();
+
+ /*if got POS-stuff, get block length and return one device found */
+ if ((stat_result(host_index) == IM_SCB_CMD_COMPLETED) || (stat_result(host_index) == IM_SCB_CMD_COMPLETED_WITH_RETRIES))
+ return 1;
+ }
+ /* if all three retries failed, return "no device at this ldn" */
+ if (retr >= 3)
+ return 0;
+ else
+ return 1;
+}
+
+/* SCSI-immediate-command for assign. This functions maps/unmaps specific
+ ldn-numbers on SCSI (PUN,LUN). It is needed for presetting of the
+ subsystem and for dynamical remapping od ldns. */
+static int immediate_assign(int host_index, unsigned int pun, unsigned int lun, unsigned int ldn, unsigned int operation)
+{
+ int retr;
+ unsigned long imm_cmd;
+
+ for (retr = 0; retr < 3; retr++) {
+ /* select mutation level of the SCSI-adapter */
+ switch (special(host_index)) {
+ case IBM_SCSI2_FW:
+ imm_cmd = (unsigned long) (IM_ASSIGN_IMM_CMD);
+ imm_cmd |= (unsigned long) ((lun & 7) << 24);
+ imm_cmd |= (unsigned long) ((operation & 1) << 23);
+ imm_cmd |= (unsigned long) ((pun & 7) << 20) | ((pun & 8) << 24);
+ imm_cmd |= (unsigned long) ((ldn & 15) << 16);
+ break;
+ default:
+ imm_cmd = inl(IM_CMD_REG(host_index));
+ imm_cmd &= (unsigned long) (0xF8000000); /* keep reserved bits */
+ imm_cmd |= (unsigned long) (IM_ASSIGN_IMM_CMD);
+ imm_cmd |= (unsigned long) ((lun & 7) << 24);
+ imm_cmd |= (unsigned long) ((operation & 1) << 23);
+ imm_cmd |= (unsigned long) ((pun & 7) << 20);
+ imm_cmd |= (unsigned long) ((ldn & 15) << 16);
+ break;
+ }
+ last_scsi_command(host_index)[MAX_LOG_DEV] = IM_ASSIGN_IMM_CMD;
+ last_scsi_type(host_index)[MAX_LOG_DEV] = IM_IMM_CMD;
+ got_interrupt(host_index) = 0;
+ issue_cmd(host_index, (unsigned long) (imm_cmd), IM_IMM_CMD | MAX_LOG_DEV);
+ while (!got_interrupt(host_index))
+ barrier();
+
+ /*if command succesful, break */
+ if (stat_result(host_index) == IM_IMMEDIATE_CMD_COMPLETED)
+ return 1;
+ }
+ if (retr >= 3)
+ return 0;
+ else
+ return 1;
+}
+
+static int immediate_feature(int host_index, unsigned int speed, unsigned int timeout)
+{
+ int retr;
+ unsigned long imm_cmd;
+
+ for (retr = 0; retr < 3; retr++) {
+ /* select mutation level of the SCSI-adapter */
+ imm_cmd = IM_FEATURE_CTR_IMM_CMD;
+ imm_cmd |= (unsigned long) ((speed & 0x7) << 29);
+ imm_cmd |= (unsigned long) ((timeout & 0x1fff) << 16);
+ last_scsi_command(host_index)[MAX_LOG_DEV] = IM_FEATURE_CTR_IMM_CMD;