patch-2.1.15 linux/drivers/scsi/ibmmca.c
Next file: linux/drivers/scsi/ibmmca.h
Previous file: linux/drivers/scsi/hosts.c
Back to the patch index
Back to the overall index
- Lines: 950
- Date:
Thu Dec 12 16:54:19 1996
- Orig file:
v2.1.14/linux/drivers/scsi/ibmmca.c
- Orig date:
Thu Jan 1 02:00:00 1970
diff -u --recursive --new-file v2.1.14/linux/drivers/scsi/ibmmca.c linux/drivers/scsi/ibmmca.c
@@ -0,0 +1,949 @@
+/*
+ * Low Level Driver for the IBM Microchannel SCSI Subsystem
+ *
+ * Copyright (c) 1995 Strom Systems, Inc. under the terms of the GNU
+ * General Public License. Written by Martin Kolinek, December 1995.
+ */
+
+/* Update history:
+ Jan 15 1996: First public release.
+
+ Jan 23 1996: Scrapped code which reassigned scsi devices to logical
+ device numbers. Instead, the existing assignment (created
+ when the machine is powered-up or rebooted) is used.
+ A side effect is that the upper layer of Linux SCSI
+ device driver gets bogus scsi ids (this is benign),
+ and also the hard disks are ordered under Linux the
+ same way as they are under dos (i.e., C: disk is sda,
+ D: disk is sdb, etc.).
+
+ I think that the CD-ROM is now detected only if a CD is
+ inside CD_ROM while Linux boots. This can be fixed later,
+ once the driver works on all types of PS/2's.
+
+ Feb 7 1996: Modified biosparam function. Fixed the CD-ROM detection.
+ For now, devices other than harddisk and CD_ROM are
+ ignored. Temporarily modified abort() function
+ to behave like reset().
+
+ Mar 31 1996: The integrated scsi subsystem is correctly found
+ in PS/2 models 56,57, but not in model 76. Therefore
+ the ibmmca_scsi_setup() function has been added today.
+ This function allows the user to force detection of
+ scsi subsystem. The kernel option has format
+ ibmmcascsi=n
+ where n is the scsi_id (pun) of the subsystem. Most
+ likely, n is 7.
+
+ Aug 21 1996: Modified the code which maps ldns to (pun,0). It was
+ insufficient for those of us with CD-ROM changers.
+ - Chris Beauregard
+ */
+
+#include <linux/kernel.h>
+#include <linux/head.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/blk.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/mca.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include "sd.h"
+#include "scsi.h"
+#include "hosts.h"
+#include "ibmmca.h"
+
+/*--------------------------------------------------------------------*/
+
+/*
+ Driver Description
+
+ (A) Subsystem Detection
+ This is done in find_adapter() function and is easy, since
+ the information about MCA integrated subsystems and plug-in
+ adapters is readily available in structure *mca_info.
+
+ In case you have more than one SCSI subsystem, only one can be used,
+ and its I/O port addresses should be standard 0x3540-7. To use more
+ than one adapter, the sharing of interrupt 14 would have to be
+ supported in Linux (it is not at present, but could be, since MCA
+ interrupts are level-triggered).
+
+ (B) Physical Units, Logical Units, and Logical Devices
+ There can be up to 56 devices on SCSI bus (besides the adapter):
+ there are up to 7 "physical units" (each identified by physical unit
+ number or pun, also called the scsi id, this is the number you select
+ with hardware jumpers), and each physical unit can have up to 8
+ "logical units" (each identified by logical unit number, or lun,
+ between 0 and 7).
+
+ Typically the adapter has pun=7, so puns of other physical units
+ are between 0 and 6. Almost all physical units have only one
+ logical unit, with lun=0. A CD-ROM jukebox would be an example of
+ a physical unit with more than one logical unit.
+
+ The embedded microprocessor of IBM SCSI subsystem hides the complex
+ two-dimensional (pun,lun) organization from the operating system.
+ When the machine is powered-up (or rebooted, I am not sure), the
+ embedded microprocessor checks, on it own, all 56 possible (pun,lun)
+ combinations, and first 15 devices found are assigned into a
+ one-dimensional array of so-called "logical devices", identified by
+ "logical device numbers" or ldn. The last ldn=15 is reserved for
+ the subsystem itself.
+
+ One consequence of information hiding is that the real (pun,lun)
+ numbers are also hidden. Therefore this driver takes the following
+ approach: It checks the ldn's (0 to 6) to find out which ldn's
+ have devices assigned. This is done by function check_devices() and
+ device_exists(). The interrupt handler has a special paragraph of code
+ (see local_checking_phase_flag) to assist in the checking. Assume, for
+ example, that three logical devices were found assigned at ldn 0, 1, 2.
+ These are presented to the upper layer of Linux SCSI driver
+ as devices with bogus (pun, lun) equal to (0,0), (1,0), (2,0).
+ On the other hand, if the upper layer issues a command to device
+ say (4,0), this driver returns DID_NO_CONNECT error.
+
+ That last paragraph is no longer correct, but is left for
+ historical purposes. It limited the number of devices to 7, far
+ fewer than the 15 that it could use. Now it just maps
+ ldn -> (ldn/8,ldn%8). We end up with a real mishmash of puns
+ and luns, but it all seems to work. - Chris Beaurgard
+
+ (C) Regular Processing
+ Only three functions get involved: ibmmca_queuecommand(), issue_cmd(),
+ and interrupt_handler().
+
+ The upper layer issues a scsi command by calling function
+ ibmmca_queuecommand(). This function fills a "subsystem control block"
+ (scb) and calls a local function issue_cmd(), which writes a scb
+ command into subsystem I/O ports. Once the scb command is carried out,
+ interrupt_handler() is invoked.
+
+ (D) Abort, Reset.
+ These are implemented with busy waiting for interrupt to arrive.
+ The abort does not worked well for me, so I instead call the
+ ibmmca_reset() from the ibmmca_abort() function.
+
+ (E) Disk Geometry
+ The ibmmca_biosparams() function should return same disk geometry
+ as bios. This is needed for fdisk, etc. The returned geometry is
+ certainly correct for disk smaller than 1 gigabyte, but I am not
+ 100% sure that it is correct for larger disks.
+
+ (F) Kernel Boot Option
+ The function ibmmca_scsi_setup() is called if option ibmmcascsi=n
+ is passed to the kernel. See file linux/init/main.c for details.
+ */
+
+/*--------------------------------------------------------------------*/
+
+/* Here are the values and structures specific for the subsystem.
+ * The source of information is "Update for the PS/2 Hardware
+ * Interface Technical Reference, Common Interfaces", September 1991,
+ * part number 04G3281, available in the U.S. for $21.75 at
+ * 1-800-IBM-PCTB, elsewhere call your local friendly IBM
+ * representative.
+ * In addition to SCSI subsystem, this update contains fairly detailed
+ * (at hardware register level) sections on diskette controller,
+ * keyboard controller, serial port controller, VGA, and XGA.
+ */
+
+/*addresses of hardware registers on the subsystem */
+#define IM_CMD_REG 0x3540 /*Command Interface, (4 bytes long) */
+#define IM_ATTN_REG 0x3544 /*Attention (1 byte) */
+#define IM_CTR_REG 0x3545 /*Basic Control (1 byte) */
+#define IM_INTR_REG 0x3546 /*Interrupt Status (1 byte, read only) */
+#define IM_STAT_REG 0x3547 /*Basic Status (1 byte, read only) */
+
+/*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_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_FORMAT_PREP_IMM_CMD 0x0417
+#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
+
+/*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;
+ };
+
+/*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_OTHER_SCSI_CMD_CMD 0x241f
+
+/*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_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
+
+/*PS2 disk led is turned on/off by bits 6,7 of system control port */
+#define PS2_SYS_CTR 0x92
+#define PS2_DISK_LED_ON() outb(inb(PS2_SYS_CTR) | 0xc0, PS2_SYS_CTR)
+#define PS2_DISK_LED_OFF() outb(inb(PS2_SYS_CTR) & 0x3f, PS2_SYS_CTR)
+
+/*--------------------------------------------------------------------*/
+
+/*list of supported subsystems */
+struct subsys_list_struct
+ {
+ unsigned short mca_id;
+ char *description;
+ };
+struct subsys_list_struct subsys_list[] =
+{
+ {0x8efc, "IBM Fast SCSI-2 Adapter"},
+ {0x8efd, "IBM 7568 Industrial Computer SCSI Adapter w/cache"},
+ {0x8ef8, "IBM Expansion Unit SCSI Controller"},
+ {0x8eff, "IBM SCSI Adapter w/Cache"},
+ {0x8efe, "IBM SCSI Adapter"},
+};
+
+/*for /proc filesystem */
+struct proc_dir_entry proc_scsi_ibmmca =
+{
+ PROC_SCSI_IBMMCA, 6, "ibmmca",
+ S_IFDIR | S_IRUGO | S_IXUGO, 2
+};
+
+/*max number of logical devices (can be up to 15) */
+#define MAX_LOG_DEV 15
+
+/*local data for a logical device */
+struct logical_device
+ {
+ struct im_scb scb;
+ struct im_tsb tsb;
+ struct im_sge sge[16];
+ Scsi_Cmnd *cmd;
+ int is_disk;
+ int block_length;
+ };
+static struct logical_device ld[MAX_LOG_DEV];
+
+/*if this is nonzero, ibmmcascsi option has been passed to the kernel */
+static int setup_called = 0;
+
+/*scsi id (physical unit number) of subsystem (set during detect) */
+static int subsystem_pun;
+
+/*array to convert (pun, lun) into logical device number */
+static unsigned char get_ldn[8][8];
+
+/*counter of concurrent disk read/writes, to turn on/off disk led */
+static int disk_rw_in_progress;
+
+/*used only when checking logical devices */
+static int local_checking_phase_flag;
+static int got_interrupt;
+static int stat_result;
+
+/*reset status and its values (used only when doing reset) */
+static int reset_status;
+#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
+
+/*local functions */
+static void interrupt_handler (int irq, void *dev_id,
+ struct pt_regs *regs);
+static void issue_cmd (unsigned long cmd_reg, unsigned char attn_reg);
+static void internal_done (Scsi_Cmnd * cmd);
+static int find_subsystem (void);
+static void check_devices (void);
+static int device_exists (int ldn, int *is_disk, int *block_length);
+
+/*--------------------------------------------------------------------*/
+
+static void
+interrupt_handler (int irq, void *dev_id,
+ struct pt_regs *regs)
+{
+ /*get command result and logical device */
+ unsigned int intr_reg = inb (IM_INTR_REG);
+ unsigned int cmd_result = intr_reg & 0xf0;
+ unsigned int ldn = intr_reg & 0x0f;
+
+ if (!inb (IM_STAT_REG) & IM_INTR_REQUEST)
+ {
+ printk ("ibmmca SCSI: spurious/shared interrupt?\n");
+ return;
+ }
+
+ /*must wait for attention reg not busy, then send EOI to subsystem */
+ while (1)
+ {
+ cli ();
+ if (!(inb (IM_STAT_REG) & IM_BUSY))
+ break;
+ sti ();
+ }
+ outb (IM_EOI | ldn, IM_ATTN_REG);
+ sti ();
+
+ /*these should never happen (hw fails, or a local programming bug) */
+ if (cmd_result == IM_ADAPTER_HW_FAILURE)
+ panic ("IBM MCA SCSI: subsystem hardware failure.\n");
+ if (cmd_result == IM_CMD_ERROR)
+ panic ("IBM MCA SCSI: command error.\n");
+ if (cmd_result == IM_SOFTWARE_SEQUENCING_ERROR)
+ panic ("IBM MCA SCSI: software sequencing error.\n");
+
+ /*only for local checking phase */
+ if (local_checking_phase_flag)
+ {
+ stat_result = cmd_result;
+ got_interrupt = 1;
+ return;
+ }
+
+ /*handling of commands coming from upper level of scsi driver */
+ else
+ {
+ Scsi_Cmnd *cmd;
+
+ /*verify ldn, and may handle rare reset immediate command */
+ if (ldn >= MAX_LOG_DEV)
+ {
+ if (ldn == 0xf && reset_status == IM_RESET_IN_PROGRESS)
+ {
+ if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE)
+ {
+ reset_status = IM_RESET_FINISHED_FAIL;
+ }
+ else
+ {
+ /*reset disk led counter, turn of disk led */
+ disk_rw_in_progress = 0;
+ PS2_DISK_LED_OFF ();
+ reset_status = IM_RESET_FINISHED_OK;
+ }
+ return;
+ }
+ else
+ panic ("IBM MCA SCSI: invalid logical device number.\n");
+ }
+
+ /*if no command structure, just return, else clear cmd */
+ cmd = ld[ldn].cmd;
+ if (!cmd)
+ return;
+ ld[ldn].cmd = 0;
+
+ /* printk("cmd=%02x ireg=%02x ds=%02x cs=%02x de=%02x ce=%02x\n",
+ cmd->cmnd[0], intr_reg,
+ ld[ldn].tsb.dev_status, ld[ldn].tsb.cmd_status,
+ ld[ldn].tsb.dev_error, ld[ldn].tsb.cmd_error); */
+
+ /*if this is end of disk read/write, may turn off PS/2 disk led */
+ if (ld[ldn].is_disk)
+ {
+ switch (cmd->cmnd[0])
+ {
+ case READ_6:
+ case WRITE_6:
+ case READ_10:
+ case WRITE_10:
+ if (--disk_rw_in_progress == 0)
+ PS2_DISK_LED_OFF ();
+ }
+ }
+
+ /*write device status into cmd->result, and call done function */
+ if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE)
+ cmd->result = ld[ldn].tsb.dev_status & 0x1e;
+ else
+ cmd->result = 0;
+ (cmd->scsi_done) (cmd);
+ }
+}
+
+/*--------------------------------------------------------------------*/
+
+static void
+issue_cmd (unsigned long cmd_reg, unsigned char attn_reg)
+{
+ /*must wait for attention reg not busy */
+ while (1)
+ {
+ cli ();
+ if (!(inb (IM_STAT_REG) & IM_BUSY))
+ break;
+ sti ();
+ }
+
+ /*write registers and enable system interrupts */
+ outl (cmd_reg, IM_CMD_REG);
+ outb (attn_reg, IM_ATTN_REG);
+ sti ();
+}
+
+/*--------------------------------------------------------------------*/
+
+static void
+internal_done (Scsi_Cmnd * cmd)
+{
+ cmd->SCp.Status++;
+}
+
+/*--------------------------------------------------------------------*/
+
+static int
+ibmmca_getinfo (char *buf, int slot, void *dev)
+{
+ int len = 0;
+
+ len += sprintf (buf + len, "Subsystem PUN: %d\n", subsystem_pun);
+ len += sprintf (buf + len, "Detected at boot: %s\n",
+ setup_called ? "No" : "Yes");
+ return len;
+}
+
+static int
+find_subsystem (void)
+{
+ int j, list_size, slot;
+ unsigned char pos2, pos3;
+
+ /*if this is not MCA machine, return "nothing found" */
+ if (!MCA_bus)
+ return 0;
+
+ /*if ibmmcascsi setup option was passed to kernel, return "found" */
+ if (setup_called)
+ {
+ printk ("IBM MCA SCSI: forced detection, scsi id=%d.\n",
+ subsystem_pun);
+ return 1;
+ }
+
+ /*first look for the SCSI integrated on the motherboard */
+ pos2 = mca_read_stored_pos (MCA_INTEGSCSI, 2);
+ if ((pos2 & 1) == 0)
+ {
+ pos3 = mca_read_stored_pos (MCA_INTEGSCSI, 3);
+ subsystem_pun = (pos3 & 0xe0) >> 5;
+ printk ("IBM MCA SCSI: integrated SCSI found, scsi id=%d.\n",
+ subsystem_pun);
+ mca_set_adapter_name (MCA_INTEGSCSI, "PS/2 Integrated SCSI");
+ mca_set_adapter_procfn (MCA_INTEGSCSI, (MCA_ProcFn) ibmmca_getinfo,
+ NULL);
+ return 1;
+ }
+
+ list_size = sizeof (subsys_list) / sizeof (struct subsys_list_struct);
+ for (j = 0; j < list_size; j += 1)
+ {
+ if ((slot = mca_find_adapter (subsys_list[j].mca_id, 0)) != MCA_NOTFOUND)
+ {
+ pos3 = mca_read_stored_pos (slot, 3);
+ subsystem_pun = (pos3 & 0xe0) >> 5;
+ printk ("IBM MCA SCSI: %s found in slot %d, scsi id=%d.\n",
+ subsys_list[j].description, slot + 1, subsystem_pun);
+
+ mca_set_adapter_name (slot, subsys_list[j].description);
+ mca_set_adapter_procfn (slot, (MCA_ProcFn) ibmmca_getinfo,
+ NULL);
+
+ return 1;
+ }
+ }
+
+ /*return "nothing found" */
+ return 0;
+}
+
+/*--------------------------------------------------------------------*/
+
+static void
+check_devices (void)
+{
+ int is_disk, block_length;
+ int ldn;
+ int num_ldn = 0;
+
+ /*check ldn's from 0 to MAX_LOG_DEV to find which devices exist */
+ for (ldn = 0; ldn < MAX_LOG_DEV; ldn++)
+ {
+ if (device_exists (ldn, &is_disk, &block_length))
+ {
+ printk ("IBM MCA SCSI: logical device found at ldn=%d.\n", ldn);
+ ld[ldn].is_disk = is_disk;
+ ld[ldn].block_length = block_length;
+ get_ldn[num_ldn / 8][num_ldn % 8] = ldn;
+ num_ldn++;
+ }
+ }
+
+ return;
+}
+
+/*--------------------------------------------------------------------*/
+
+static int
+device_exists (int ldn, int *is_disk, int *block_length)
+{
+ struct im_scb scb;
+ struct im_tsb tsb;
+ unsigned char buf[256];
+ int retries;
+
+ for (retries = 0; retries < 3; retries++)
+ {
+ /*fill scb with inquiry command */
+ scb.command = IM_DEVICE_INQUIRY_CMD;
+ scb.enable = IM_READ_CONTROL | IM_SUPRESS_EXCEPTION_SHORT;
+ /* I think this virt_to_bus is needed.. ??? AC */
+ scb.sys_buf_adr = virt_to_bus((unsigned long) buf);
+ scb.sys_buf_length = 255;
+ scb.tsb_adr = virt_to_bus((unsigned long) &tsb);
+
+ /*issue scb to passed ldn, and busy wait for interrupt */
+ got_interrupt = 0;
+ issue_cmd (virt_to_bus((unsigned long)) &scb, IM_SCB | ldn);
+ while (!got_interrupt)
+ barrier ();
+
+ /*if command succesful, break */
+ if (stat_result == IM_SCB_CMD_COMPLETED)
+ break;
+ }
+
+ /*if all three retries failed, return "no device at this ldn" */
+ if (retries >= 3)
+ return 0;
+
+ /*if device is CD_ROM, assume block size 2048 and return */
+ if (buf[0] == TYPE_ROM)
+ {
+ *is_disk = 0;
+ *block_length = 2048;
+ return 1;
+ }
+
+ /*if device is disk, use "read capacity" to find its block size */
+ if (buf[0] == TYPE_DISK)
+ {
+ *is_disk = 1;
+
+ for (retries = 0; retries < 3; retries++)
+ {
+ /*fill scb with read capacity command */
+ scb.command = IM_READ_CAPACITY_CMD;
+ scb.enable = IM_READ_CONTROL;
+ scb.sys_buf_adr = virt_to_bus((unsigned long) buf);
+ scb.sys_buf_length = 8;
+ scb.tsb_adr = virt_to_bus((unsigned long) &tsb);
+
+ /*issue scb to passed ldn, and busy wait for interrupt */
+ got_interrupt = 0;
+ issue_cmd (virt_to_bus((unsigned long) &scb), IM_SCB | ldn);
+ while (!got_interrupt)
+ barrier ();
+
+ /*if got capacity, get block length and return one device found */
+ if (stat_result == IM_SCB_CMD_COMPLETED)
+ {
+ *block_length = buf[7] + (buf[6] << 8) + (buf[5] << 16) + (buf[4] << 24);
+ return 1;
+ }
+ }
+
+ /*if all three retries failed, return "no device at this ldn" */
+ if (retries >= 3)
+ return 0;
+ }
+
+ /*for now, ignore tape and other devices - return 0 */
+ return 0;
+}
+
+/*--------------------------------------------------------------------*/
+
+void
+ibmmca_scsi_setup (char *str, int *ints)
+{
+ /*verify that one value (between 0 and 7) was specified */
+ if (setup_called++ || ints[0] != 1 || ints[1] < 0 || ints[1] >= 8)
+ {
+ printk ("IBM MCA SCSI: usage: ibmmcascsi=<ADAPTER_SCSI_ID>\n");
+ return;
+ }
+ subsystem_pun = ints[1];
+}
+
+/*--------------------------------------------------------------------*/
+
+int
+ibmmca_detect (Scsi_Host_Template * template)
+{
+ /*initialize local data */
+ memset (ld, 0, sizeof ld);
+ memset (get_ldn, 0xff, sizeof get_ldn);
+ disk_rw_in_progress = 0;
+ reset_status = IM_RESET_NOT_IN_PROGRESS;
+
+ /*search for the subsystem, return 0 if not found */
+ if (!find_subsystem ())
+ return 0;
+
+ /*get interrupt request level */
+ if (request_irq (IM_IRQ, interrupt_handler, SA_SHIRQ, "ibmmca", 0))
+ {
+ printk ("IBM MCA SCSI: Unable to get IRQ %d.\n", IM_IRQ);
+ return 0;
+ }
+
+ /*check which logical devices exist */
+ local_checking_phase_flag = 1;
+ check_devices ();
+ local_checking_phase_flag = 0;
+
+ /*if got here, one ibm mca subsystem has been detected */
+ return 1;
+}
+
+/*--------------------------------------------------------------------*/
+
+int
+ibmmca_command (Scsi_Cmnd * cmd)
+{
+ ibmmca_queuecommand (cmd, internal_done);
+ cmd->SCp.Status = 0;
+ while (!cmd->SCp.Status)
+ barrier ();
+ return cmd->result;
+}
+
+/*--------------------------------------------------------------------*/
+
+int
+ibmmca_queuecommand (Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *))
+{
+ unsigned int ldn;
+ unsigned int scsi_cmd;
+ struct im_scb *scb;
+
+ /*if (target,lun) unassigned, return error */
+ ldn = get_ldn[cmd->target][cmd->lun];
+ if (ldn >= MAX_LOG_DEV)
+ {
+ cmd->result = DID_NO_CONNECT << 16;
+ done (cmd);
+ return 0;
+ }
+
+ /*verify there is no command already in progress for this log dev */
+ if (ld[ldn].cmd)
+ panic ("IBM MCA SCSI: cmd already in progress for this ldn.\n");
+
+ /*save done in cmd, and save cmd for the interrupt handler */
+ cmd->scsi_done = done;
+ ld[ldn].cmd = cmd;
+
+ /*fill scb information independent of the scsi command */
+ scb = &(ld[ldn].scb);
+ scb->enable = IM_REPORT_TSB_ONLY_ON_ERROR;
+ scb->tsb_adr = virt_to_bus((unsigned long) &(ld[ldn].tsb));
+ if (cmd->use_sg)
+ {
+ int i = cmd->use_sg;
+ struct scatterlist *sl = (struct scatterlist *) cmd->request_buffer;
+ if (i > 16)
+ panic ("IBM MCA SCSI: scatter-gather list too long.\n");
+ while (--i >= 0)
+ {
+ ld[ldn].sge[i].address = virt_to_bus(sl[i].address);
+ ld[ldn].sge[i].byte_length = sl[i].length;
+ }
+ scb->enable |= IM_POINTER_TO_LIST;
+ scb->sys_buf_adr = virt_to_bus((unsigned long) &(ld[ldn].sge[0]));
+ scb->sys_buf_length = cmd->use_sg * sizeof (struct im_sge);
+ }
+ else
+ {
+ scb->sys_buf_adr = virt_to_bus((unsigned long) cmd->request_buffer);
+ scb->sys_buf_length = cmd->request_bufflen;
+ }
+
+ /*fill scb information dependent on scsi command */
+ scsi_cmd = cmd->cmnd[0];
+ /* printk("issue scsi cmd=%02x to ldn=%d\n", scsi_cmd, ldn); */
+ switch (scsi_cmd)
+ {
+ case READ_6:
+ case WRITE_6:
+ case READ_10:
+ case WRITE_10:
+ if (scsi_cmd == READ_6 || scsi_cmd == READ_10)
+ {
+ scb->command = IM_READ_DATA_CMD;
+ scb->enable |= IM_READ_CONTROL;
+ }
+ else
+ {
+ scb->command = IM_WRITE_DATA_CMD;
+ }
+ if (scsi_cmd == READ_6 || scsi_cmd == WRITE_6)
+ {
+ scb->u1.log_blk_adr = (((unsigned) cmd->cmnd[3]) << 0) |
+ (((unsigned) cmd->cmnd[2]) << 8) |
+ ((((unsigned) cmd->cmnd[1]) & 0x1f) << 16);
+ scb->u2.blk.count = (unsigned) cmd->cmnd[4];
+ }
+ else
+ {
+ scb->u1.log_blk_adr = (((unsigned) cmd->cmnd[5]) << 0) |
+ (((unsigned) cmd->cmnd[4]) << 8) |
+ (((unsigned) cmd->cmnd[3]) << 16) |
+ (((unsigned) cmd->cmnd[2]) << 24);
+ scb->u2.blk.count = (((unsigned) cmd->cmnd[8]) << 0) |
+ (((unsigned) cmd->cmnd[7]) << 8);
+ }
+ scb->u2.blk.length = ld[ldn].block_length;
+ if (ld[ldn].is_disk)
+ {
+ if (++disk_rw_in_progress == 1)
+ PS2_DISK_LED_ON ();
+ }
+ break;
+
+ case INQUIRY:
+ scb->command = IM_DEVICE_INQUIRY_CMD;
+ scb->enable |= IM_READ_CONTROL |
+ IM_SUPRESS_EXCEPTION_SHORT;
+ break;
+
+ case READ_CAPACITY:
+ scb->command = IM_READ_CAPACITY_CMD;
+ scb->enable |= IM_READ_CONTROL;
+ /*the length of system memory buffer must be exactly 8 bytes */
+ if (scb->sys_buf_length >= 8)
+ scb->sys_buf_length = 8;
+ break;
+
+ case REQUEST_SENSE:
+ scb->command = IM_REQUEST_SENSE_CMD;
+ scb->enable |= IM_READ_CONTROL;
+ break;
+
+ default:
+ scb->command = IM_OTHER_SCSI_CMD_CMD;
+ scb->enable |= IM_READ_CONTROL |
+ IM_SUPRESS_EXCEPTION_SHORT;
+ scb->u1.scsi_cmd_length = cmd->cmd_len;
+ memcpy (scb->u2.scsi_command, cmd->cmnd, cmd->cmd_len);
+ break;
+ }
+
+ /*issue scb command, and return */
+ issue_cmd (virt_to_bus((unsigned long) scb), IM_SCB | ldn);
+ return 0;
+}
+
+/*--------------------------------------------------------------------*/
+
+int
+ibmmca_abort (Scsi_Cmnd * cmd)
+{
+/*do a reset instead, since abort does not work well for me at present */
+ return ibmmca_reset (cmd);
+
+#if 0
+ unsigned int ldn;
+ void (*saved_done) (Scsi_Cmnd *);
+
+ /*get logical device number, and disable system interrupts */
+ printk ("IBM MCA SCSI: sending abort to device id=%d lun=%d.\n",
+ cmd->target, cmd->lun);
+ ldn = get_ldn[cmd->target][cmd->lun];
+ cli ();
+
+ /*if cmd for this ldn has already finished, no need to abort */
+ if (!ld[ldn].cmd)
+ {
+ sti ();
+ return SCSI_ABORT_NOT_RUNNING;
+ }
+
+ /* Clear ld.cmd, save done function, install internal done,
+ * send abort immediate command (this enables sys. interrupts),
+ * and wait until the interrupt arrives.
+ */
+ ld[ldn].cmd = 0;
+ saved_done = cmd->scsi_done;
+ cmd->scsi_done = internal_done;
+ cmd->SCp.Status = 0;
+ issue_cmd (IM_ABORT_IMM_CMD, IM_IMM_CMD | ldn);
+ while (!cmd->SCp.Status)
+ barrier ();
+
+ /*if abort went well, call saved done, then return success or error */
+ if (cmd->result == 0)
+ {
+ cmd->result |= DID_ABORT << 16;
+ saved_done (cmd);
+ return SCSI_ABORT_SUCCESS;
+ }
+ else
+ return SCSI_ABORT_ERROR;
+#endif
+}
+
+/*--------------------------------------------------------------------*/
+
+int
+ibmmca_reset (Scsi_Cmnd * cmd)
+{
+ /*issue reset immediate command to subsystem, and wait for interrupt */
+ printk ("IBM MCA SCSI: resetting all devices.\n");
+ cli ();
+ reset_status = IM_RESET_IN_PROGRESS;
+ issue_cmd (IM_RESET_IMM_CMD, IM_IMM_CMD | 0xf);
+ while (reset_status == IM_RESET_IN_PROGRESS)
+ barrier ();
+
+ /*if reset failed, just return error */
+ if (reset_status == IM_RESET_FINISHED_FAIL)
+ return SCSI_RESET_ERROR;
+
+ /*so reset finished ok - call outstanding done's, and return success */
+ printk ("IBM MCA SCSI: reset finished well.\n");
+ {
+ int i;
+ for (i = 0; i < MAX_LOG_DEV; i++)
+ {
+ Scsi_Cmnd *cmd = ld[i].cmd;
+ if (cmd && cmd->scsi_done)
+ {
+ ld[i].cmd = 0;
+ cmd->result = DID_RESET;
+ (cmd->scsi_done) (cmd);
+ }
+ }
+ }
+ return SCSI_RESET_SUCCESS;
+}
+
+/*--------------------------------------------------------------------*/
+
+int
+ibmmca_biosparam (Disk * disk, kdev_t dev, int *info)
+{
+ info[0] = 64;
+ info[1] = 32;
+ info[2] = disk->capacity / (info[0] * info[1]);
+ if (info[2] >= 1024)
+ {
+ info[0] = 128;
+ info[1] = 63;
+ info[2] = disk->capacity / (info[0] * info[1]);
+ if (info[2] >= 1024)
+ {
+ info[0] = 255;
+ info[1] = 63;
+ info[2] = disk->capacity / (info[0] * info[1]);
+ if (info[2] >= 1024)
+ info[2] = 1023;
+ }
+ }
+ return 0;
+}
+
+/*--------------------------------------------------------------------*/
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov