diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/ibmmca.c |
Linux-2.6.12-rc2v2.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.c | 2491 |
1 files changed, 2491 insertions, 0 deletions
diff --git a/drivers/scsi/ibmmca.c b/drivers/scsi/ibmmca.c new file mode 100644 index 00000000000..a3fdead9bce --- /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 lon |