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/dc395x.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/dc395x.c')
-rw-r--r-- | drivers/scsi/dc395x.c | 4942 |
1 files changed, 4942 insertions, 0 deletions
diff --git a/drivers/scsi/dc395x.c b/drivers/scsi/dc395x.c new file mode 100644 index 00000000000..cca41cf8d3e --- /dev/null +++ b/drivers/scsi/dc395x.c @@ -0,0 +1,4942 @@ +/* + * dc395x.c + * + * Device Driver for Tekram DC395(U/UW/F), DC315(U) + * PCI SCSI Bus Master Host Adapter + * (SCSI chip set used Tekram ASIC TRM-S1040) + * + * Authors: + * C.L. Huang <ching@tekram.com.tw> + * Erich Chen <erich@tekram.com.tw> + * (C) Copyright 1995-1999 Tekram Technology Co., Ltd. + * + * Kurt Garloff <garloff@suse.de> + * (C) 1999-2000 Kurt Garloff + * + * Oliver Neukum <oliver@neukum.name> + * Ali Akcaagac <aliakc@web.de> + * Jamie Lenehan <lenehan@twibble.org> + * (C) 2003 + * + * License: GNU GPL + * + ************************************************************************* + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ************************************************************************ + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/blkdev.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/pci.h> +#include <linux/list.h> +#include <linux/vmalloc.h> +#include <asm/io.h> + +#include <scsi/scsi.h> +#include <scsi/scsicam.h> /* needed for scsicam_bios_param */ +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> + +#include "dc395x.h" + +#define DC395X_NAME "dc395x" +#define DC395X_BANNER "Tekram DC395(U/UW/F), DC315(U) - ASIC TRM-S1040" +#define DC395X_VERSION "v2.05, 2004/03/08" + +/*--------------------------------------------------------------------------- + Features + ---------------------------------------------------------------------------*/ +/* + * Set to disable parts of the driver + */ +/*#define DC395x_NO_DISCONNECT*/ +/*#define DC395x_NO_TAGQ*/ +/*#define DC395x_NO_SYNC*/ +/*#define DC395x_NO_WIDE*/ + +/*--------------------------------------------------------------------------- + Debugging + ---------------------------------------------------------------------------*/ +/* + * Types of debugging that can be enabled and disabled + */ +#define DBG_KG 0x0001 +#define DBG_0 0x0002 +#define DBG_1 0x0004 +#define DBG_SG 0x0020 +#define DBG_FIFO 0x0040 +#define DBG_PIO 0x0080 + + +/* + * Set set of things to output debugging for. + * Undefine to remove all debugging + */ +/*#define DEBUG_MASK (DBG_0|DBG_1|DBG_SG|DBG_FIFO|DBG_PIO)*/ +/*#define DEBUG_MASK DBG_0*/ + + +/* + * Output a kernel mesage at the specified level and append the + * driver name and a ": " to the start of the message + */ +#define dprintkl(level, format, arg...) \ + printk(level DC395X_NAME ": " format , ## arg) + + +#ifdef DEBUG_MASK +/* + * print a debug message - this is formated with KERN_DEBUG, then the + * driver name followed by a ": " and then the message is output. + * This also checks that the specified debug level is enabled before + * outputing the message + */ +#define dprintkdbg(type, format, arg...) \ + do { \ + if ((type) & (DEBUG_MASK)) \ + dprintkl(KERN_DEBUG , format , ## arg); \ + } while (0) + +/* + * Check if the specified type of debugging is enabled + */ +#define debug_enabled(type) ((DEBUG_MASK) & (type)) + +#else +/* + * No debugging. Do nothing + */ +#define dprintkdbg(type, format, arg...) \ + do {} while (0) +#define debug_enabled(type) (0) + +#endif + + +#ifndef PCI_VENDOR_ID_TEKRAM +#define PCI_VENDOR_ID_TEKRAM 0x1DE1 /* Vendor ID */ +#endif +#ifndef PCI_DEVICE_ID_TEKRAM_TRMS1040 +#define PCI_DEVICE_ID_TEKRAM_TRMS1040 0x0391 /* Device ID */ +#endif + + +#define DC395x_LOCK_IO(dev,flags) spin_lock_irqsave(((struct Scsi_Host *)dev)->host_lock, flags) +#define DC395x_UNLOCK_IO(dev,flags) spin_unlock_irqrestore(((struct Scsi_Host *)dev)->host_lock, flags) + +#define DC395x_read8(acb,address) (u8)(inb(acb->io_port_base + (address))) +#define DC395x_read16(acb,address) (u16)(inw(acb->io_port_base + (address))) +#define DC395x_read32(acb,address) (u32)(inl(acb->io_port_base + (address))) +#define DC395x_write8(acb,address,value) outb((value), acb->io_port_base + (address)) +#define DC395x_write16(acb,address,value) outw((value), acb->io_port_base + (address)) +#define DC395x_write32(acb,address,value) outl((value), acb->io_port_base + (address)) + +/* cmd->result */ +#define RES_TARGET 0x000000FF /* Target State */ +#define RES_TARGET_LNX STATUS_MASK /* Only official ... */ +#define RES_ENDMSG 0x0000FF00 /* End Message */ +#define RES_DID 0x00FF0000 /* DID_ codes */ +#define RES_DRV 0xFF000000 /* DRIVER_ codes */ + +#define MK_RES(drv,did,msg,tgt) ((int)(drv)<<24 | (int)(did)<<16 | (int)(msg)<<8 | (int)(tgt)) +#define MK_RES_LNX(drv,did,msg,tgt) ((int)(drv)<<24 | (int)(did)<<16 | (int)(msg)<<8 | (int)(tgt)<<1) + +#define SET_RES_TARGET(who,tgt) { who &= ~RES_TARGET; who |= (int)(tgt); } +#define SET_RES_TARGET_LNX(who,tgt) { who &= ~RES_TARGET_LNX; who |= (int)(tgt) << 1; } +#define SET_RES_MSG(who,msg) { who &= ~RES_ENDMSG; who |= (int)(msg) << 8; } +#define SET_RES_DID(who,did) { who &= ~RES_DID; who |= (int)(did) << 16; } +#define SET_RES_DRV(who,drv) { who &= ~RES_DRV; who |= (int)(drv) << 24; } + +#define TAG_NONE 255 + +/* + * srb->segement_x is the hw sg list. It is always allocated as a + * DC395x_MAX_SG_LISTENTRY entries in a linear block which does not + * cross a page boundy. + */ +#define SEGMENTX_LEN (sizeof(struct SGentry)*DC395x_MAX_SG_LISTENTRY) +#define VIRTX_LEN (sizeof(void *) * DC395x_MAX_SG_LISTENTRY) + +struct SGentry { + u32 address; /* bus! address */ + u32 length; +}; + +/* The SEEPROM structure for TRM_S1040 */ +struct NVRamTarget { + u8 cfg0; /* Target configuration byte 0 */ + u8 period; /* Target period */ + u8 cfg2; /* Target configuration byte 2 */ + u8 cfg3; /* Target configuration byte 3 */ +}; + +struct NvRamType { + u8 sub_vendor_id[2]; /* 0,1 Sub Vendor ID */ + u8 sub_sys_id[2]; /* 2,3 Sub System ID */ + u8 sub_class; /* 4 Sub Class */ + u8 vendor_id[2]; /* 5,6 Vendor ID */ + u8 device_id[2]; /* 7,8 Device ID */ + u8 reserved; /* 9 Reserved */ + struct NVRamTarget target[DC395x_MAX_SCSI_ID]; + /** 10,11,12,13 + ** 14,15,16,17 + ** .... + ** .... + ** 70,71,72,73 + */ + u8 scsi_id; /* 74 Host Adapter SCSI ID */ + u8 channel_cfg; /* 75 Channel configuration */ + u8 delay_time; /* 76 Power on delay time */ + u8 max_tag; /* 77 Maximum tags */ + u8 reserved0; /* 78 */ + u8 boot_target; /* 79 */ + u8 boot_lun; /* 80 */ + u8 reserved1; /* 81 */ + u16 reserved2[22]; /* 82,..125 */ + u16 cksum; /* 126,127 */ +}; + +struct ScsiReqBlk { + struct list_head list; /* next/prev ptrs for srb lists */ + struct DeviceCtlBlk *dcb; + struct scsi_cmnd *cmd; + + struct SGentry *segment_x; /* Linear array of hw sg entries (up to 64 entries) */ + u32 sg_bus_addr; /* Bus address of sg list (ie, of segment_x) */ + + u8 sg_count; /* No of HW sg entries for this request */ + u8 sg_index; /* Index of HW sg entry for this request */ + u32 total_xfer_length; /* Total number of bytes remaining to be transfered */ + void **virt_map; + unsigned char *virt_addr; /* Virtual address of current transfer position */ + + /* + * The sense buffer handling function, request_sense, uses + * the first hw sg entry (segment_x[0]) and the transfer + * length (total_xfer_length). While doing this it stores the + * original values into the last sg hw list + * (srb->segment_x[DC395x_MAX_SG_LISTENTRY - 1] and the + * total_xfer_length in xferred. These values are restored in + * pci_unmap_srb_sense. This is the only place xferred is used. + */ + u32 xferred; /* Saved copy of total_xfer_length */ + + u16 state; + + u8 msgin_buf[6]; + u8 msgout_buf[6]; + + u8 adapter_status; + u8 target_status; + u8 msg_count; + u8 end_message; + + u8 tag_number; + u8 status; + u8 retry_count; + u8 flag; + + u8 scsi_phase; +}; + +struct DeviceCtlBlk { + struct list_head list; /* next/prev ptrs for the dcb list */ + struct AdapterCtlBlk *acb; + struct list_head srb_going_list; /* head of going srb list */ + struct list_head srb_waiting_list; /* head of waiting srb list */ + + struct ScsiReqBlk *active_srb; + u32 tag_mask; + + u16 max_command; + + u8 target_id; /* SCSI Target ID (SCSI Only) */ + u8 target_lun; /* SCSI Log. Unit (SCSI Only) */ + u8 identify_msg; + u8 dev_mode; + + u8 inquiry7; /* To store Inquiry flags */ + u8 sync_mode; /* 0:async mode */ + u8 min_nego_period; /* for nego. */ + u8 sync_period; /* for reg. */ + + u8 sync_offset; /* for reg. and nego.(low nibble) */ + u8 flag; + u8 dev_type; + u8 init_tcq_flag; +}; + +struct AdapterCtlBlk { + struct Scsi_Host *scsi_host; + + unsigned long io_port_base; + unsigned long io_port_len; + + struct list_head dcb_list; /* head of going dcb list */ + struct DeviceCtlBlk *dcb_run_robin; + struct DeviceCtlBlk *active_dcb; + + struct list_head srb_free_list; /* head of free srb list */ + struct ScsiReqBlk *tmp_srb; + struct timer_list waiting_timer; + struct timer_list selto_timer; + + u16 srb_count; + + u8 sel_timeout; + + unsigned int irq_level; + u8 tag_max_num; + u8 acb_flag; + u8 gmode2; + + u8 config; + u8 lun_chk; + u8 scan_devices; + u8 hostid_bit; + + u8 dcb_map[DC395x_MAX_SCSI_ID]; + struct DeviceCtlBlk *children[DC395x_MAX_SCSI_ID][32]; + + struct pci_dev *dev; + + u8 msg_len; + + struct ScsiReqBlk srb_array[DC395x_MAX_SRB_CNT]; + struct ScsiReqBlk srb; + + struct NvRamType eeprom; /* eeprom settings for this adapter */ +}; + + +/*--------------------------------------------------------------------------- + Forward declarations + ---------------------------------------------------------------------------*/ +static void data_out_phase0(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void data_in_phase0(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void command_phase0(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void status_phase0(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void msgout_phase0(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void msgin_phase0(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void data_out_phase1(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void data_in_phase1(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void command_phase1(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void status_phase1(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void msgout_phase1(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void msgin_phase1(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void nop0(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void nop1(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb, + u16 *pscsi_status); +static void set_basic_config(struct AdapterCtlBlk *acb); +static void cleanup_after_transfer(struct AdapterCtlBlk *acb, + struct ScsiReqBlk *srb); +static void reset_scsi_bus(struct AdapterCtlBlk *acb); +static void data_io_transfer(struct AdapterCtlBlk *acb, + struct ScsiReqBlk *srb, u16 io_dir); +static void disconnect(struct AdapterCtlBlk *acb); +static void reselect(struct AdapterCtlBlk *acb); +static u8 start_scsi(struct AdapterCtlBlk *acb, struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb); +static inline void enable_msgout_abort(struct AdapterCtlBlk *acb, + struct ScsiReqBlk *srb); +static void build_srb(struct scsi_cmnd *cmd, struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb); +static void doing_srb_done(struct AdapterCtlBlk *acb, u8 did_code, + struct scsi_cmnd *cmd, u8 force); +static void scsi_reset_detect(struct AdapterCtlBlk *acb); +static void pci_unmap_srb(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb); +static void pci_unmap_srb_sense(struct AdapterCtlBlk *acb, + struct ScsiReqBlk *srb); +static void srb_done(struct AdapterCtlBlk *acb, struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb); +static void request_sense(struct AdapterCtlBlk *acb, struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb); +static void set_xfer_rate(struct AdapterCtlBlk *acb, + struct DeviceCtlBlk *dcb); +static void waiting_timeout(unsigned long ptr); + + +/*--------------------------------------------------------------------------- + Static Data + ---------------------------------------------------------------------------*/ +static u16 current_sync_offset = 0; + +static void *dc395x_scsi_phase0[] = { + data_out_phase0,/* phase:0 */ + data_in_phase0, /* phase:1 */ + command_phase0, /* phase:2 */ + status_phase0, /* phase:3 */ + nop0, /* phase:4 PH_BUS_FREE .. initial phase */ + nop0, /* phase:5 PH_BUS_FREE .. initial phase */ + msgout_phase0, /* phase:6 */ + msgin_phase0, /* phase:7 */ +}; + +static void *dc395x_scsi_phase1[] = { + data_out_phase1,/* phase:0 */ + data_in_phase1, /* phase:1 */ + command_phase1, /* phase:2 */ + status_phase1, /* phase:3 */ + nop1, /* phase:4 PH_BUS_FREE .. initial phase */ + nop1, /* phase:5 PH_BUS_FREE .. initial phase */ + msgout_phase1, /* phase:6 */ + msgin_phase1, /* phase:7 */ +}; + +/* + *Fast20: 000 50ns, 20.0 MHz + * 001 75ns, 13.3 MHz + * 010 100ns, 10.0 MHz + * 011 125ns, 8.0 MHz + * 100 150ns, 6.6 MHz + * 101 175ns, 5.7 MHz + * 110 200ns, 5.0 MHz + * 111 250ns, 4.0 MHz + * + *Fast40(LVDS): 000 25ns, 40.0 MHz + * 001 50ns, 20.0 MHz + * 010 75ns, 13.3 MHz + * 011 100ns, 10.0 MHz + * 100 125ns, 8.0 MHz + * 101 150ns, 6.6 MHz + * 110 175ns, 5.7 MHz + * 111 200ns, 5.0 MHz + */ +/*static u8 clock_period[] = {12,19,25,31,37,44,50,62};*/ + +/* real period:48ns,76ns,100ns,124ns,148ns,176ns,200ns,248ns */ +static u8 clock_period[] = { 12, 18, 25, 31, 37, 43, 50, 62 }; +static u16 clock_speed[] = { 200, 133, 100, 80, 67, 58, 50, 40 }; + + +/*--------------------------------------------------------------------------- + Configuration + ---------------------------------------------------------------------------*/ +/* + * Module/boot parameters currently effect *all* instances of the + * card in the system. + */ + +/* + * Command line parameters are stored in a structure below. + * These are the index's into the structure for the various + * command line options. + */ +#define CFG_ADAPTER_ID 0 +#define CFG_MAX_SPEED 1 +#define CFG_DEV_MODE 2 +#define CFG_ADAPTER_MODE 3 +#define CFG_TAGS 4 +#define CFG_RESET_DELAY 5 + +#define CFG_NUM 6 /* number of configuration items */ + + +/* + * Value used to indicate that a command line override + * hasn't been used to modify the value. + */ +#define CFG_PARAM_UNSET -1 + + +/* + * Hold command line parameters. + */ +struct ParameterData { + int value; /* value of this setting */ + int min; /* minimum value */ + int max; /* maximum value */ + int def; /* default value */ + int safe; /* safe value */ +}; +static struct ParameterData __devinitdata cfg_data[] = { + { /* adapter id */ + CFG_PARAM_UNSET, + 0, + 15, + 7, + 7 + }, + { /* max speed */ + CFG_PARAM_UNSET, + 0, + 7, + 1, /* 13.3Mhz */ + 4, /* 6.7Hmz */ + }, + { /* dev mode */ + CFG_PARAM_UNSET, + 0, + 0x3f, + NTC_DO_PARITY_CHK | NTC_DO_DISCONNECT | NTC_DO_SYNC_NEGO | + NTC_DO_WIDE_NEGO | NTC_DO_TAG_QUEUEING | + NTC_DO_SEND_START, + NTC_DO_PARITY_CHK | NTC_DO_SEND_START + }, + { /* adapter mode */ + CFG_PARAM_UNSET, + 0, + 0x2f, +#ifdef CONFIG_SCSI_MULTI_LUN + NAC_SCANLUN | +#endif + NAC_GT2DRIVES | NAC_GREATER_1G | NAC_POWERON_SCSI_RESET + /*| NAC_ACTIVE_NEG*/, + NAC_GT2DRIVES | NAC_GREATER_1G | NAC_POWERON_SCSI_RESET | 0x08 + }, + { /* tags */ + CFG_PARAM_UNSET, + 0, + 5, + 3, /* 16 tags (??) */ + 2, + }, + { /* reset delay */ + CFG_PARAM_UNSET, + 0, + 180, + 1, /* 1 second */ + 10, /* 10 seconds */ + } +}; + + +/* + * Safe settings. If set to zero the the BIOS/default values with + * command line overrides will be used. If set to 1 then safe and + * slow settings will be used. + */ +static int use_safe_settings = 0; +module_param_named(safe, use_safe_settings, bool, 0); +MODULE_PARM_DESC(safe, "Use safe and slow settings only. Default: false"); + + +module_param_named(adapter_id, cfg_data[CFG_ADAPTER_ID].value, int, 0); +MODULE_PARM_DESC(adapter_id, "Adapter SCSI ID. Default 7 (0-15)"); + +module_param_named(max_speed, cfg_data[CFG_MAX_SPEED].value, int, 0); +MODULE_PARM_DESC(max_speed, "Maximum bus speed. Default 1 (0-7) Speeds: 0=20, 1=13.3, 2=10, 3=8, 4=6.7, 5=5.8, 6=5, 7=4 Mhz"); + +module_param_named(dev_mode, cfg_data[CFG_DEV_MODE].value, int, 0); +MODULE_PARM_DESC(dev_mode, "Device mode."); + +module_param_named(adapter_mode, cfg_data[CFG_ADAPTER_MODE].value, int, 0); +MODULE_PARM_DESC(adapter_mode, "Adapter mode."); + +module_param_named(tags, cfg_data[CFG_TAGS].value, int, 0); +MODULE_PARM_DESC(tags, "Number of tags (1<<x). Default 3 (0-5)"); + +module_param_named(reset_delay, cfg_data[CFG_RESET_DELAY].value, int, 0); +MODULE_PARM_DESC(reset_delay, "Reset delay in seconds. Default 1 (0-180)"); + + +/** + * set_safe_settings - if the use_safe_settings option is set then + * set all values to the safe and slow values. + **/ +static void __devinit set_safe_settings(void) +{ + if (use_safe_settings) + { + int i; + + dprintkl(KERN_INFO, "Using safe settings.\n"); + for (i = 0; i < CFG_NUM; i++) + { + cfg_data[i].value = cfg_data[i].safe; + } + } +} + + +/** + * fix_settings - reset any boot parameters which are out of range + * back to the default values. + **/ +static void __devinit fix_settings(void) +{ + int i; + + dprintkdbg(DBG_1, + "setup: AdapterId=%08x MaxSpeed=%08x DevMode=%08x " + "AdapterMode=%08x Tags=%08x ResetDelay=%08x\n", + cfg_data[CFG_ADAPTER_ID].value, + cfg_data[CFG_MAX_SPEED].value, + cfg_data[CFG_DEV_MODE].value, + cfg_data[CFG_ADAPTER_MODE].value, + cfg_data[CFG_TAGS].value, + cfg_data[CFG_RESET_DELAY].value); + for (i = 0; i < CFG_NUM; i++) + { + if (cfg_data[i].value < cfg_data[i].min + || cfg_data[i].value > cfg_data[i].max) + cfg_data[i].value = cfg_data[i].def; + } +} + + + +/* + * Mapping from the eeprom delay index value (index into this array) + * to the the number of actual seconds that the delay should be for. + */ +static char __devinitdata eeprom_index_to_delay_map[] = + { 1, 3, 5, 10, 16, 30, 60, 120 }; + + +/** + * eeprom_index_to_delay - Take the eeprom delay setting and convert it + * into a number of seconds. + * + * @eeprom: The eeprom structure in which we find the delay index to map. + **/ +static void __devinit eeprom_index_to_delay(struct NvRamType *eeprom) +{ + eeprom->delay_time = eeprom_index_to_delay_map[eeprom->delay_time]; +} + + +/** + * delay_to_eeprom_index - Take a delay in seconds and return the + * closest eeprom index which will delay for at least that amount of + * seconds. + * + * @delay: The delay, in seconds, to find the eeprom index for. + **/ +static int __devinit delay_to_eeprom_index(int delay) +{ + u8 idx = 0; + while (idx < 7 && eeprom_index_to_delay_map[idx] < delay) + idx++; + return idx; +} + + +/** + * eeprom_override - Override the eeprom settings, in the provided + * eeprom structure, with values that have been set on the command + * line. + * + * @eeprom: The eeprom data to override with command line options. + **/ +static void __devinit eeprom_override(struct NvRamType *eeprom) +{ + u8 id; + + /* Adapter Settings */ + if (cfg_data[CFG_ADAPTER_ID].value != CFG_PARAM_UNSET) + eeprom->scsi_id = (u8)cfg_data[CFG_ADAPTER_ID].value; + + if (cfg_data[CFG_ADAPTER_MODE].value != CFG_PARAM_UNSET) + eeprom->channel_cfg = (u8)cfg_data[CFG_ADAPTER_MODE].value; + + if (cfg_data[CFG_RESET_DELAY].value != CFG_PARAM_UNSET) + eeprom->delay_time = delay_to_eeprom_index( + cfg_data[CFG_RESET_DELAY].value); + + if (cfg_data[CFG_TAGS].value != CFG_PARAM_UNSET) + eeprom->max_tag = (u8)cfg_data[CFG_TAGS].value; + + /* Device Settings */ + for (id = 0; id < DC395x_MAX_SCSI_ID; id++) { + if (cfg_data[CFG_DEV_MODE].value != CFG_PARAM_UNSET) + eeprom->target[id].cfg0 = + (u8)cfg_data[CFG_DEV_MODE].value; + + if (cfg_data[CFG_MAX_SPEED].value != CFG_PARAM_UNSET) + eeprom->target[id].period = + (u8)cfg_data[CFG_MAX_SPEED].value; + + } +} + + +/*--------------------------------------------------------------------------- + ---------------------------------------------------------------------------*/ + +static unsigned int list_size(struct list_head *head) +{ + unsigned int count = 0; + struct list_head *pos; + list_for_each(pos, head) + count++; + return count; +} + + +static struct DeviceCtlBlk *dcb_get_next(struct list_head *head, + struct DeviceCtlBlk *pos) +{ + int use_next = 0; + struct DeviceCtlBlk* next = NULL; + struct DeviceCtlBlk* i; + + if (list_empty(head)) + return NULL; + + /* find supplied dcb and then select the next one */ + list_for_each_entry(i, head, list) + if (use_next) { + next = i; + break; + } else if (i == pos) { + use_next = 1; + } + /* if no next one take the head one (ie, wraparound) */ + if (!next) + list_for_each_entry(i, head, list) { + next = i; + break; + } + + return next; +} + + +static void free_tag(struct DeviceCtlBlk *dcb, struct ScsiReqBlk *srb) +{ + if (srb->tag_number < 255) { + dcb->tag_mask &= ~(1 << srb->tag_number); /* free tag mask */ + srb->tag_number = 255; + } +} + + +/* Find cmd in SRB list */ +inline static struct ScsiReqBlk *find_cmd(struct scsi_cmnd *cmd, + struct list_head *head) +{ + struct ScsiReqBlk *i; + list_for_each_entry(i, head, list) + if (i->cmd == cmd) + return i; + return NULL; +} + + +static struct ScsiReqBlk *srb_get_free(struct AdapterCtlBlk *acb) +{ + struct list_head *head = &acb->srb_free_list; + struct ScsiReqBlk *srb = NULL; + + if (!list_empty(head)) { + srb = list_entry(head->next, struct ScsiReqBlk, list); + list_del(head->next); + dprintkdbg(DBG_0, "srb_get_free: srb=%p\n", srb); + } + return srb; +} + + +static void srb_free_insert(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb) +{ + dprintkdbg(DBG_0, "srb_free_insert: srb=%p\n", srb); + list_add_tail(&srb->list, &acb->srb_free_list); +} + + +static void srb_waiting_insert(struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb) +{ + dprintkdbg(DBG_0, "srb_waiting_insert: (pid#%li) <%02i-%i> srb=%p\n", + srb->cmd->pid, dcb->target_id, dcb->target_lun, srb); + list_add(&srb->list, &dcb->srb_waiting_list); +} + + +static void srb_waiting_append(struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb) +{ + dprintkdbg(DBG_0, "srb_waiting_append: (pid#%li) <%02i-%i> srb=%p\n", + srb->cmd->pid, dcb->target_id, dcb->target_lun, srb); + list_add_tail(&srb->list, &dcb->srb_waiting_list); +} + + +static void srb_going_append(struct DeviceCtlBlk *dcb, struct ScsiReqBlk *srb) +{ + dprintkdbg(DBG_0, "srb_going_append: (pid#%li) <%02i-%i> srb=%p\n", + srb->cmd->pid, dcb->target_id, dcb->target_lun, srb); + list_add_tail(&srb->list, &dcb->srb_going_list); +} + + +static void srb_going_remove(struct DeviceCtlBlk *dcb, struct ScsiReqBlk *srb) +{ + struct ScsiReqBlk *i; + struct ScsiReqBlk *tmp; + dprintkdbg(DBG_0, "srb_going_remove: (pid#%li) <%02i-%i> srb=%p\n", + srb->cmd->pid, dcb->target_id, dcb->target_lun, srb); + + list_for_each_entry_safe(i, tmp, &dcb->srb_going_list, list) + if (i == srb) { + list_del(&srb->list); + break; + } +} + + +static void srb_waiting_remove(struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb) +{ + struct ScsiReqBlk *i; + struct ScsiReqBlk *tmp; + dprintkdbg(DBG_0, "srb_waiting_remove: (pid#%li) <%02i-%i> srb=%p\n", + srb->cmd->pid, dcb->target_id, dcb->target_lun, srb); + + list_for_each_entry_safe(i, tmp, &dcb->srb_waiting_list, list) + if (i == srb) { + list_del(&srb->list); + break; + } +} + + +static void srb_going_to_waiting_move(struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb) +{ + dprintkdbg(DBG_0, + "srb_going_to_waiting_move: (pid#%li) <%02i-%i> srb=%p\n", + srb->cmd->pid, dcb->target_id, dcb->target_lun, srb); + list_move(&srb->list, &dcb->srb_waiting_list); +} + + +static void srb_waiting_to_going_move(struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb) +{ + dprintkdbg(DBG_0, + "srb_waiting_to_going_move: (pid#%li) <%02i-%i> srb=%p\n", + srb->cmd->pid, dcb->target_id, dcb->target_lun, srb); + list_move(&srb->list, &dcb->srb_going_list); +} + + +/* Sets the timer to wake us up */ +static void waiting_set_timer(struct AdapterCtlBlk *acb, unsigned long to) +{ + if (timer_pending(&acb->waiting_timer)) + return; + init_timer(&acb->waiting_timer); + acb->waiting_timer.function = waiting_timeout; + acb->waiting_timer.data = (unsigned long) acb; + if (time_before(jiffies + to, acb->scsi_host->last_reset - HZ / 2)) + acb->waiting_timer.expires = + acb->scsi_host->last_reset - HZ / 2 + 1; + else + acb->waiting_timer.expires = jiffies + to + 1; + add_timer(&acb->waiting_timer); +} + + +/* Send the next command from the waiting list to the bus */ +static void waiting_process_next(struct AdapterCtlBlk *acb) +{ + struct DeviceCtlBlk *start = NULL; + struct DeviceCtlBlk *pos; + struct DeviceCtlBlk *dcb; + struct ScsiReqBlk *srb; + struct list_head *dcb_list_head = &acb->dcb_list; + + if (acb->active_dcb + || (acb->acb_flag & (RESET_DETECT + RESET_DONE + RESET_DEV))) + return; + + if (timer_pending(&acb->waiting_timer)) + del_timer(&acb->waiting_timer); + + if (list_empty(dcb_list_head)) + return; + + /* + * Find the starting dcb. Need to find it again in the list + * since the list may have changed since we set the ptr to it + */ + list_for_each_entry(dcb, dcb_list_head, list) + if (dcb == acb->dcb_run_robin) { + start = dcb; + break; + } + if (!start) { + /* This can happen! */ + start = list_entry(dcb_list_head->next, typeof(*start), list); + acb->dcb_run_robin = start; + } + + + /* + * Loop over the dcb, but we start somewhere (potentially) in + * the middle of the loop so we need to manully do this. + */ + pos = start; + do { + struct list_head *waiting_list_head = &pos->srb_waiting_list; + + /* Make sure, the next another device gets scheduled ... */ + acb->dcb_run_robin = dcb_get_next(dcb_list_head, + acb->dcb_run_robin); + + if (list_empty(waiting_list_head) || + pos->max_command <= list_size(&pos->srb_going_list)) { + /* move to next dcb */ + pos = dcb_get_next(dcb_list_head, pos); + } else { + srb = list_entry(waiting_list_head->next, + struct ScsiReqBlk, list); + + /* Try to send to the bus */ + if (!start_scsi(acb, pos, srb)) + srb_waiting_to_going_move(pos, srb); + else + waiting_set_timer(acb, HZ/50); + break; + } + } while (pos != start); +} + + +/* Wake up waiting queue */ +static void waiting_timeout(unsigned long ptr) +{ + unsigned long flags; + struct AdapterCtlBlk *acb = (struct AdapterCtlBlk *)ptr; + dprintkdbg(DBG_1, + "waiting_timeout: Queue woken up by timer. acb=%p\n", acb); + DC395x_LOCK_IO(acb->scsi_host, flags); + waiting_process_next(acb); + DC395x_UNLOCK_IO(acb->scsi_host, flags); +} + + +/* Get the DCB for a given ID/LUN combination */ +static struct DeviceCtlBlk *find_dcb(struct AdapterCtlBlk *acb, u8 id, u8 lun) +{ + return acb->children[id][lun]; +} + + +/* Send SCSI Request Block (srb) to adapter (acb) */ +static void send_srb(struct AdapterCtlBlk *acb, struct ScsiReqBlk *srb) +{ + struct DeviceCtlBlk *dcb = srb->dcb; + + if (dcb->max_command <= list_size(&dcb->srb_going_list) || + acb->active_dcb || + (acb->acb_flag & (RESET_DETECT + RESET_DONE + RESET_DEV))) { + srb_waiting_append(dcb, srb); + waiting_process_next(acb); + return; + } + + if (!start_scsi(acb, dcb, srb)) + srb_going_append(dcb, srb); + else { + srb_waiting_insert(dcb, srb); + waiting_set_timer(acb, HZ / 50); + } +} + + +/* Prepare SRB for being sent to Device DCB w/ command *cmd */ +static void build_srb(struct scsi_cmnd *cmd, struct DeviceCtlBlk *dcb, + struct ScsiReqBlk *srb) +{ + enum dma_data_direction dir = cmd->sc_data_direction; + dprintkdbg(DBG_0, "build_srb: (pid#%li) <%02i-%i>\n", + cmd->pid, dcb->target_id, dcb->target_lun); + + srb->dcb = dcb; + srb->cmd = cmd; + srb->sg_count = 0; + srb->total_xfer_length = 0; + srb->sg_bus_addr = 0; + srb->virt_addr = NULL; + srb->sg_index = 0; + srb->adapter_status = 0; + srb->target_status = 0; + srb->msg_count = 0; + srb->status = 0; + srb->flag = 0; + srb->state = 0; + srb->retry_count = 0; + srb->tag_number = TAG_NONE; + srb->scsi_phase = PH_BUS_FREE; /* initial phase */ + srb->end_message = 0; + + if (dir == PCI_DMA_NONE || !cmd->request_buffer) { + dprintkdbg(DBG_0, + "build_srb: [0] len=%d buf=%p use_sg=%d !MAP=%08x\n", + cmd->bufflen, cmd->request_buffer, + cmd->use_sg, srb->segment_x[0].address); + } else if (cmd->use_sg) { + int i; + u32 reqlen = cmd->request_bufflen; + struct scatterlist *sl = (struct scatterlist *) + cmd->request_buffer; + struct SGentry *sgp = srb->segment_x; + srb->sg_count = pci_map_sg(dcb->acb->dev, sl, cmd->use_sg, + dir); + dprintkdbg(DBG_0, + "build_srb: [n] len=%d buf=%p use_sg=%d segs=%d\n", + reqlen, cmd->request_buffer, cmd->use_sg, + srb->sg_count); + + for (i = 0; i < srb->sg_count; i++) { + u32 seglen = (u32)sg_dma_len(sl + i); + sgp[i].address = (u32)sg_dma_address(sl + i); + sgp[i].length = seglen; + srb->total_xfer_length += seglen; + srb->virt_map[i] = kmap(sl[i].page); + } + srb->virt_addr = srb->virt_map[0]; + sgp += srb->sg_count - 1; + + /* + * adjust last page if too big as it is allocated + * on even page boundaries + */ + if (srb->total_xfer_length > reqlen) { + sgp->length -= (srb->total_xfer_length - reqlen); + srb->total_xfer_length = reqlen; + } + + /* Fixup for WIDE padding - make sure length is even */ + if (dcb->sync_period & WIDE_SYNC && + srb->total_xfer_length % 2) { + srb->total_xfer_length++; + sgp->length++; + } + + srb->sg_bus_addr = pci_map_single(dcb->acb->dev, + srb->segment_x, + SEGMENTX_LEN, + PCI_DMA_TODEVICE); + + dprintkdbg(DBG_SG, "build_srb: [n] map sg %p->%08x(%05x)\n", + srb->segment_x, srb->sg_bus_addr, SEGMENTX_LEN); + } else { + srb->total_xfer_length = cmd->request_bufflen; + srb->sg_count = 1; + srb->segment_x[0].address = + pci_map_single(dcb->acb->dev, cmd->request_buffer, + srb->total_xfer_length, dir); + + /* Fixup for WIDE padding - make sure length is even */ + if (dcb->sync_period & WIDE_SYNC && srb->total_xfer_length % 2) + srb->total_xfer_length++; + + srb->segment_x[0].length = srb->total_xfer_length; + srb->virt_addr = cmd->request_buffer; + dprintkdbg(DBG_0, + "build_srb: [1] len=%d buf=%p use_sg=%d map=%08x\n", + srb->total_xfer_length, cmd->request_buffer, + cmd->use_sg, srb->segment_x[0].address); + } +} + + +/** + * dc395x_queue_command - queue scsi command passed from the mid + * layer, invoke 'done' on completion + * + * @cmd: pointer to scsi command object + * @done: function pointer to be invoked on completion + * + * Returns 1 if the adapter (host) is busy, else returns 0. One + * reason for an adapter to be busy is that the number + * of outstanding queued commands is already equal to + * struct Scsi_Host::can_queue . + * + * Required: if struct Scsi_Host::can_queue is ever non-zero + * then this function is required. + * + * Locks: struct Scsi_Host::host_lock held on entry (with "irqsave") + * and is expected to be held on return. + * + **/ +static int dc395x_queue_command(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)) +{ + struct DeviceCtlBlk *dcb; |