aboutsummaryrefslogtreecommitdiff
path: root/drivers/scsi/aic7xxx/aic7xxx_osm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/aic7xxx/aic7xxx_osm.c')
-rw-r--r--drivers/scsi/aic7xxx/aic7xxx_osm.c5043
1 files changed, 5043 insertions, 0 deletions
diff --git a/drivers/scsi/aic7xxx/aic7xxx_osm.c b/drivers/scsi/aic7xxx/aic7xxx_osm.c
new file mode 100644
index 00000000000..031c6aaa5ca
--- /dev/null
+++ b/drivers/scsi/aic7xxx/aic7xxx_osm.c
@@ -0,0 +1,5043 @@
+/*
+ * Adaptec AIC7xxx device driver for Linux.
+ *
+ * $Id: //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic7xxx_osm.c#235 $
+ *
+ * Copyright (c) 1994 John Aycock
+ * The University of Calgary Department of Computer Science.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Sources include the Adaptec 1740 driver (aha1740.c), the Ultrastor 24F
+ * driver (ultrastor.c), various Linux kernel source, the Adaptec EISA
+ * config file (!adp7771.cfg), the Adaptec AHA-2740A Series User's Guide,
+ * the Linux Kernel Hacker's Guide, Writing a SCSI Device Driver for Linux,
+ * the Adaptec 1542 driver (aha1542.c), the Adaptec EISA overlay file
+ * (adp7770.ovl), the Adaptec AHA-2740 Series Technical Reference Manual,
+ * the Adaptec AIC-7770 Data Book, the ANSI SCSI specification, the
+ * ANSI SCSI-2 specification (draft 10c), ...
+ *
+ * --------------------------------------------------------------------------
+ *
+ * Modifications by Daniel M. Eischen (deischen@iworks.InterWorks.org):
+ *
+ * Substantially modified to include support for wide and twin bus
+ * adapters, DMAing of SCBs, tagged queueing, IRQ sharing, bug fixes,
+ * SCB paging, and other rework of the code.
+ *
+ * --------------------------------------------------------------------------
+ * Copyright (c) 1994-2000 Justin T. Gibbs.
+ * Copyright (c) 2000-2001 Adaptec Inc.
+ * All rights reserved.
+ *
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ *---------------------------------------------------------------------------
+ *
+ * Thanks also go to (in alphabetical order) the following:
+ *
+ * Rory Bolt - Sequencer bug fixes
+ * Jay Estabrook - Initial DEC Alpha support
+ * Doug Ledford - Much needed abort/reset bug fixes
+ * Kai Makisara - DMAing of SCBs
+ *
+ * A Boot time option was also added for not resetting the scsi bus.
+ *
+ * Form: aic7xxx=extended
+ * aic7xxx=no_reset
+ * aic7xxx=verbose
+ *
+ * Daniel M. Eischen, deischen@iworks.InterWorks.org, 1/23/97
+ *
+ * Id: aic7xxx.c,v 4.1 1997/06/12 08:23:42 deang Exp
+ */
+
+/*
+ * Further driver modifications made by Doug Ledford <dledford@redhat.com>
+ *
+ * Copyright (c) 1997-1999 Doug Ledford
+ *
+ * These changes are released under the same licensing terms as the FreeBSD
+ * driver written by Justin Gibbs. Please see his Copyright notice above
+ * for the exact terms and conditions covering my changes as well as the
+ * warranty statement.
+ *
+ * Modifications made to the aic7xxx.c,v 4.1 driver from Dan Eischen include
+ * but are not limited to:
+ *
+ * 1: Import of the latest FreeBSD sequencer code for this driver
+ * 2: Modification of kernel code to accommodate different sequencer semantics
+ * 3: Extensive changes throughout kernel portion of driver to improve
+ * abort/reset processing and error hanndling
+ * 4: Other work contributed by various people on the Internet
+ * 5: Changes to printk information and verbosity selection code
+ * 6: General reliability related changes, especially in IRQ management
+ * 7: Modifications to the default probe/attach order for supported cards
+ * 8: SMP friendliness has been improved
+ *
+ */
+
+#include "aic7xxx_osm.h"
+#include "aic7xxx_inline.h"
+#include <scsi/scsicam.h>
+
+/*
+ * Include aiclib.c as part of our
+ * "module dependencies are hard" work around.
+ */
+#include "aiclib.c"
+
+#include <linux/init.h> /* __setup */
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+#include "sd.h" /* For geometry detection */
+#endif
+
+#include <linux/mm.h> /* For fetching system memory size */
+#include <linux/blkdev.h> /* For block_size() */
+#include <linux/delay.h> /* For ssleep/msleep */
+
+/*
+ * Lock protecting manipulation of the ahc softc list.
+ */
+spinlock_t ahc_list_spinlock;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+/* For dynamic sglist size calculation. */
+u_int ahc_linux_nseg;
+#endif
+
+/*
+ * Set this to the delay in seconds after SCSI bus reset.
+ * Note, we honor this only for the initial bus reset.
+ * The scsi error recovery code performs its own bus settle
+ * delay handling for error recovery actions.
+ */
+#ifdef CONFIG_AIC7XXX_RESET_DELAY_MS
+#define AIC7XXX_RESET_DELAY CONFIG_AIC7XXX_RESET_DELAY_MS
+#else
+#define AIC7XXX_RESET_DELAY 5000
+#endif
+
+/*
+ * Control collection of SCSI transfer statistics for the /proc filesystem.
+ *
+ * NOTE: Do NOT enable this when running on kernels version 1.2.x and below.
+ * NOTE: This does affect performance since it has to maintain statistics.
+ */
+#ifdef CONFIG_AIC7XXX_PROC_STATS
+#define AIC7XXX_PROC_STATS
+#endif
+
+/*
+ * To change the default number of tagged transactions allowed per-device,
+ * add a line to the lilo.conf file like:
+ * append="aic7xxx=verbose,tag_info:{{32,32,32,32},{32,32,32,32}}"
+ * which will result in the first four devices on the first two
+ * controllers being set to a tagged queue depth of 32.
+ *
+ * The tag_commands is an array of 16 to allow for wide and twin adapters.
+ * Twin adapters will use indexes 0-7 for channel 0, and indexes 8-15
+ * for channel 1.
+ */
+typedef struct {
+ uint8_t tag_commands[16]; /* Allow for wide/twin adapters. */
+} adapter_tag_info_t;
+
+/*
+ * Modify this as you see fit for your system.
+ *
+ * 0 tagged queuing disabled
+ * 1 <= n <= 253 n == max tags ever dispatched.
+ *
+ * The driver will throttle the number of commands dispatched to a
+ * device if it returns queue full. For devices with a fixed maximum
+ * queue depth, the driver will eventually determine this depth and
+ * lock it in (a console message is printed to indicate that a lock
+ * has occurred). On some devices, queue full is returned for a temporary
+ * resource shortage. These devices will return queue full at varying
+ * depths. The driver will throttle back when the queue fulls occur and
+ * attempt to slowly increase the depth over time as the device recovers
+ * from the resource shortage.
+ *
+ * In this example, the first line will disable tagged queueing for all
+ * the devices on the first probed aic7xxx adapter.
+ *
+ * The second line enables tagged queueing with 4 commands/LUN for IDs
+ * (0, 2-11, 13-15), disables tagged queueing for ID 12, and tells the
+ * driver to attempt to use up to 64 tags for ID 1.
+ *
+ * The third line is the same as the first line.
+ *
+ * The fourth line disables tagged queueing for devices 0 and 3. It
+ * enables tagged queueing for the other IDs, with 16 commands/LUN
+ * for IDs 1 and 4, 127 commands/LUN for ID 8, and 4 commands/LUN for
+ * IDs 2, 5-7, and 9-15.
+ */
+
+/*
+ * NOTE: The below structure is for reference only, the actual structure
+ * to modify in order to change things is just below this comment block.
+adapter_tag_info_t aic7xxx_tag_info[] =
+{
+ {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
+ {{4, 64, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 4, 4}},
+ {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
+ {{0, 16, 4, 0, 16, 4, 4, 4, 127, 4, 4, 4, 4, 4, 4, 4}}
+};
+*/
+
+#ifdef CONFIG_AIC7XXX_CMDS_PER_DEVICE
+#define AIC7XXX_CMDS_PER_DEVICE CONFIG_AIC7XXX_CMDS_PER_DEVICE
+#else
+#define AIC7XXX_CMDS_PER_DEVICE AHC_MAX_QUEUE
+#endif
+
+#define AIC7XXX_CONFIGED_TAG_COMMANDS { \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE, \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE, \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE, \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE, \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE, \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE, \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE, \
+ AIC7XXX_CMDS_PER_DEVICE, AIC7XXX_CMDS_PER_DEVICE \
+}
+
+/*
+ * By default, use the number of commands specified by
+ * the users kernel configuration.
+ */
+static adapter_tag_info_t aic7xxx_tag_info[] =
+{
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS},
+ {AIC7XXX_CONFIGED_TAG_COMMANDS}
+};
+
+/*
+ * DV option:
+ *
+ * positive value = DV Enabled
+ * zero = DV Disabled
+ * negative value = DV Default for adapter type/seeprom
+ */
+#ifdef CONFIG_AIC7XXX_DV_SETTING
+#define AIC7XXX_CONFIGED_DV CONFIG_AIC7XXX_DV_SETTING
+#else
+#define AIC7XXX_CONFIGED_DV -1
+#endif
+
+static int8_t aic7xxx_dv_settings[] =
+{
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV,
+ AIC7XXX_CONFIGED_DV
+};
+
+/*
+ * There should be a specific return value for this in scsi.h, but
+ * it seems that most drivers ignore it.
+ */
+#define DID_UNDERFLOW DID_ERROR
+
+void
+ahc_print_path(struct ahc_softc *ahc, struct scb *scb)
+{
+ printk("(scsi%d:%c:%d:%d): ",
+ ahc->platform_data->host->host_no,
+ scb != NULL ? SCB_GET_CHANNEL(ahc, scb) : 'X',
+ scb != NULL ? SCB_GET_TARGET(ahc, scb) : -1,
+ scb != NULL ? SCB_GET_LUN(scb) : -1);
+}
+
+/*
+ * XXX - these options apply unilaterally to _all_ 274x/284x/294x
+ * cards in the system. This should be fixed. Exceptions to this
+ * rule are noted in the comments.
+ */
+
+/*
+ * Skip the scsi bus reset. Non 0 make us skip the reset at startup. This
+ * has no effect on any later resets that might occur due to things like
+ * SCSI bus timeouts.
+ */
+static uint32_t aic7xxx_no_reset;
+
+/*
+ * Certain PCI motherboards will scan PCI devices from highest to lowest,
+ * others scan from lowest to highest, and they tend to do all kinds of
+ * strange things when they come into contact with PCI bridge chips. The
+ * net result of all this is that the PCI card that is actually used to boot
+ * the machine is very hard to detect. Most motherboards go from lowest
+ * PCI slot number to highest, and the first SCSI controller found is the
+ * one you boot from. The only exceptions to this are when a controller
+ * has its BIOS disabled. So, we by default sort all of our SCSI controllers
+ * from lowest PCI slot number to highest PCI slot number. We also force
+ * all controllers with their BIOS disabled to the end of the list. This
+ * works on *almost* all computers. Where it doesn't work, we have this
+ * option. Setting this option to non-0 will reverse the order of the sort
+ * to highest first, then lowest, but will still leave cards with their BIOS
+ * disabled at the very end. That should fix everyone up unless there are
+ * really strange cirumstances.
+ */
+static uint32_t aic7xxx_reverse_scan;
+
+/*
+ * Should we force EXTENDED translation on a controller.
+ * 0 == Use whatever is in the SEEPROM or default to off
+ * 1 == Use whatever is in the SEEPROM or default to on
+ */
+static uint32_t aic7xxx_extended;
+
+/*
+ * PCI bus parity checking of the Adaptec controllers. This is somewhat
+ * dubious at best. To my knowledge, this option has never actually
+ * solved a PCI parity problem, but on certain machines with broken PCI
+ * chipset configurations where stray PCI transactions with bad parity are
+ * the norm rather than the exception, the error messages can be overwelming.
+ * It's included in the driver for completeness.
+ * 0 = Shut off PCI parity check
+ * non-0 = reverse polarity pci parity checking
+ */
+static uint32_t aic7xxx_pci_parity = ~0;
+
+/*
+ * Certain newer motherboards have put new PCI based devices into the
+ * IO spaces that used to typically be occupied by VLB or EISA cards.
+ * This overlap can cause these newer motherboards to lock up when scanned
+ * for older EISA and VLB devices. Setting this option to non-0 will
+ * cause the driver to skip scanning for any VLB or EISA controllers and
+ * only support the PCI controllers. NOTE: this means that if the kernel
+ * os compiled with PCI support disabled, then setting this to non-0
+ * would result in never finding any devices :)
+ */
+#ifndef CONFIG_AIC7XXX_PROBE_EISA_VL
+uint32_t aic7xxx_probe_eisa_vl;
+#else
+uint32_t aic7xxx_probe_eisa_vl = ~0;
+#endif
+
+/*
+ * There are lots of broken chipsets in the world. Some of them will
+ * violate the PCI spec when we issue byte sized memory writes to our
+ * controller. I/O mapped register access, if allowed by the given
+ * platform, will work in almost all cases.
+ */
+uint32_t aic7xxx_allow_memio = ~0;
+
+/*
+ * aic7xxx_detect() has been run, so register all device arrivals
+ * immediately with the system rather than deferring to the sorted
+ * attachment performed by aic7xxx_detect().
+ */
+int aic7xxx_detect_complete;
+
+/*
+ * So that we can set how long each device is given as a selection timeout.
+ * The table of values goes like this:
+ * 0 - 256ms
+ * 1 - 128ms
+ * 2 - 64ms
+ * 3 - 32ms
+ * We default to 256ms because some older devices need a longer time
+ * to respond to initial selection.
+ */
+static uint32_t aic7xxx_seltime;
+
+/*
+ * Certain devices do not perform any aging on commands. Should the
+ * device be saturated by commands in one portion of the disk, it is
+ * possible for transactions on far away sectors to never be serviced.
+ * To handle these devices, we can periodically send an ordered tag to
+ * force all outstanding transactions to be serviced prior to a new
+ * transaction.
+ */
+uint32_t aic7xxx_periodic_otag;
+
+/*
+ * Module information and settable options.
+ */
+static char *aic7xxx = NULL;
+
+MODULE_AUTHOR("Maintainer: Justin T. Gibbs <gibbs@scsiguy.com>");
+MODULE_DESCRIPTION("Adaptec Aic77XX/78XX SCSI Host Bus Adapter driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_VERSION(AIC7XXX_DRIVER_VERSION);
+module_param(aic7xxx, charp, 0444);
+MODULE_PARM_DESC(aic7xxx,
+"period delimited, options string.\n"
+" verbose Enable verbose/diagnostic logging\n"
+" allow_memio Allow device registers to be memory mapped\n"
+" debug Bitmask of debug values to enable\n"
+" no_probe Toggle EISA/VLB controller probing\n"
+" probe_eisa_vl Toggle EISA/VLB controller probing\n"
+" no_reset Supress initial bus resets\n"
+" extended Enable extended geometry on all controllers\n"
+" periodic_otag Send an ordered tagged transaction\n"
+" periodically to prevent tag starvation.\n"
+" This may be required by some older disk\n"
+" drives or RAID arrays.\n"
+" reverse_scan Sort PCI devices highest Bus/Slot to lowest\n"
+" tag_info:<tag_str> Set per-target tag depth\n"
+" global_tag_depth:<int> Global tag depth for every target\n"
+" on every bus\n"
+" dv:<dv_settings> Set per-controller Domain Validation Setting.\n"
+" seltime:<int> Selection Timeout\n"
+" (0/256ms,1/128ms,2/64ms,3/32ms)\n"
+"\n"
+" Sample /etc/modprobe.conf line:\n"
+" Toggle EISA/VLB probing\n"
+" Set tag depth on Controller 1/Target 1 to 10 tags\n"
+" Shorten the selection timeout to 128ms\n"
+"\n"
+" options aic7xxx 'aic7xxx=probe_eisa_vl.tag_info:{{}.{.10}}.seltime:1'\n"
+);
+
+static void ahc_linux_handle_scsi_status(struct ahc_softc *,
+ struct ahc_linux_device *,
+ struct scb *);
+static void ahc_linux_queue_cmd_complete(struct ahc_softc *ahc,
+ Scsi_Cmnd *cmd);
+static void ahc_linux_filter_inquiry(struct ahc_softc*, struct ahc_devinfo*);
+static void ahc_linux_sem_timeout(u_long arg);
+static void ahc_linux_freeze_simq(struct ahc_softc *ahc);
+static void ahc_linux_release_simq(u_long arg);
+static void ahc_linux_dev_timed_unfreeze(u_long arg);
+static int ahc_linux_queue_recovery_cmd(Scsi_Cmnd *cmd, scb_flag flag);
+static void ahc_linux_initialize_scsi_bus(struct ahc_softc *ahc);
+static void ahc_linux_size_nseg(void);
+static void ahc_linux_thread_run_complete_queue(struct ahc_softc *ahc);
+static void ahc_linux_start_dv(struct ahc_softc *ahc);
+static void ahc_linux_dv_timeout(struct scsi_cmnd *cmd);
+static int ahc_linux_dv_thread(void *data);
+static void ahc_linux_kill_dv_thread(struct ahc_softc *ahc);
+static void ahc_linux_dv_target(struct ahc_softc *ahc, u_int target);
+static void ahc_linux_dv_transition(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo,
+ struct ahc_linux_target *targ);
+static void ahc_linux_dv_fill_cmd(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo);
+static void ahc_linux_dv_inq(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo,
+ struct ahc_linux_target *targ,
+ u_int request_length);
+static void ahc_linux_dv_tur(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo);
+static void ahc_linux_dv_rebd(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo,
+ struct ahc_linux_target *targ);
+static void ahc_linux_dv_web(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo,
+ struct ahc_linux_target *targ);
+static void ahc_linux_dv_reb(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo,
+ struct ahc_linux_target *targ);
+static void ahc_linux_dv_su(struct ahc_softc *ahc,
+ struct scsi_cmnd *cmd,
+ struct ahc_devinfo *devinfo,
+ struct ahc_linux_target *targ);
+static int ahc_linux_fallback(struct ahc_softc *ahc,
+ struct ahc_devinfo *devinfo);
+static void ahc_linux_dv_complete(Scsi_Cmnd *cmd);
+static void ahc_linux_generate_dv_pattern(struct ahc_linux_target *targ);
+static u_int ahc_linux_user_tagdepth(struct ahc_softc *ahc,
+ struct ahc_devinfo *devinfo);
+static u_int ahc_linux_user_dv_setting(struct ahc_softc *ahc);
+static void ahc_linux_device_queue_depth(struct ahc_softc *ahc,
+ struct ahc_linux_device *dev);
+static struct ahc_linux_target* ahc_linux_alloc_target(struct ahc_softc*,
+ u_int, u_int);
+static void ahc_linux_free_target(struct ahc_softc*,
+ struct ahc_linux_target*);
+static struct ahc_linux_device* ahc_linux_alloc_device(struct ahc_softc*,
+ struct ahc_linux_target*,
+ u_int);
+static void ahc_linux_free_device(struct ahc_softc*,
+ struct ahc_linux_device*);
+static void ahc_linux_run_device_queue(struct ahc_softc*,
+ struct ahc_linux_device*);
+static void ahc_linux_setup_tag_info_global(char *p);
+static aic_option_callback_t ahc_linux_setup_tag_info;
+static aic_option_callback_t ahc_linux_setup_dv;
+static int aic7xxx_setup(char *s);
+static int ahc_linux_next_unit(void);
+static void ahc_runq_tasklet(unsigned long data);
+static struct ahc_cmd *ahc_linux_run_complete_queue(struct ahc_softc *ahc);
+
+/********************************* Inlines ************************************/
+static __inline void ahc_schedule_runq(struct ahc_softc *ahc);
+static __inline struct ahc_linux_device*
+ ahc_linux_get_device(struct ahc_softc *ahc, u_int channel,
+ u_int target, u_int lun, int alloc);
+static __inline void ahc_schedule_completeq(struct ahc_softc *ahc);
+static __inline void ahc_linux_check_device_queue(struct ahc_softc *ahc,
+ struct ahc_linux_device *dev);
+static __inline struct ahc_linux_device *
+ ahc_linux_next_device_to_run(struct ahc_softc *ahc);
+static __inline void ahc_linux_run_device_queues(struct ahc_softc *ahc);
+static __inline void ahc_linux_unmap_scb(struct ahc_softc*, struct scb*);
+
+static __inline int ahc_linux_map_seg(struct ahc_softc *ahc, struct scb *scb,
+ struct ahc_dma_seg *sg,
+ dma_addr_t addr, bus_size_t len);
+
+static __inline void
+ahc_schedule_completeq(struct ahc_softc *ahc)
+{
+ if ((ahc->platform_data->flags & AHC_RUN_CMPLT_Q_TIMER) == 0) {
+ ahc->platform_data->flags |= AHC_RUN_CMPLT_Q_TIMER;
+ ahc->platform_data->completeq_timer.expires = jiffies;
+ add_timer(&ahc->platform_data->completeq_timer);
+ }
+}
+
+/*
+ * Must be called with our lock held.
+ */
+static __inline void
+ahc_schedule_runq(struct ahc_softc *ahc)
+{
+ tasklet_schedule(&ahc->platform_data->runq_tasklet);
+}
+
+static __inline struct ahc_linux_device*
+ahc_linux_get_device(struct ahc_softc *ahc, u_int channel, u_int target,
+ u_int lun, int alloc)
+{
+ struct ahc_linux_target *targ;
+ struct ahc_linux_device *dev;
+ u_int target_offset;
+
+ target_offset = target;
+ if (channel != 0)
+ target_offset += 8;
+ targ = ahc->platform_data->targets[target_offset];
+ if (targ == NULL) {
+ if (alloc != 0) {
+ targ = ahc_linux_alloc_target(ahc, channel, target);
+ if (targ == NULL)
+ return (NULL);
+ } else
+ return (NULL);
+ }
+ dev = targ->devices[lun];
+ if (dev == NULL && alloc != 0)
+ dev = ahc_linux_alloc_device(ahc, targ, lun);
+ return (dev);
+}
+
+#define AHC_LINUX_MAX_RETURNED_ERRORS 4
+static struct ahc_cmd *
+ahc_linux_run_complete_queue(struct ahc_softc *ahc)
+{
+ struct ahc_cmd *acmd;
+ u_long done_flags;
+ int with_errors;
+
+ with_errors = 0;
+ ahc_done_lock(ahc, &done_flags);
+ while ((acmd = TAILQ_FIRST(&ahc->platform_data->completeq)) != NULL) {
+ Scsi_Cmnd *cmd;
+
+ if (with_errors > AHC_LINUX_MAX_RETURNED_ERRORS) {
+ /*
+ * Linux uses stack recursion to requeue
+ * commands that need to be retried. Avoid
+ * blowing out the stack by "spoon feeding"
+ * commands that completed with error back
+ * the operating system in case they are going
+ * to be retried. "ick"
+ */
+ ahc_schedule_completeq(ahc);
+ break;
+ }
+ TAILQ_REMOVE(&ahc->platform_data->completeq,
+ acmd, acmd_links.tqe);
+ cmd = &acmd_scsi_cmd(acmd);
+ cmd->host_scribble = NULL;
+ if (ahc_cmd_get_transaction_status(cmd) != DID_OK
+ || (cmd->result & 0xFF) != SCSI_STATUS_OK)
+ with_errors++;
+
+ cmd->scsi_done(cmd);
+ }
+ ahc_done_unlock(ahc, &done_flags);
+ return (acmd);
+}
+
+static __inline void
+ahc_linux_check_device_queue(struct ahc_softc *ahc,
+ struct ahc_linux_device *dev)
+{
+ if ((dev->flags & AHC_DEV_FREEZE_TIL_EMPTY) != 0
+ && dev->active == 0) {
+ dev->flags &= ~AHC_DEV_FREEZE_TIL_EMPTY;
+ dev->qfrozen--;
+ }
+
+ if (TAILQ_FIRST(&dev->busyq) == NULL
+ || dev->openings == 0 || dev->qfrozen != 0)
+ return;
+
+ ahc_linux_run_device_queue(ahc, dev);
+}
+
+static __inline struct ahc_linux_device *
+ahc_linux_next_device_to_run(struct ahc_softc *ahc)
+{
+
+ if ((ahc->flags & AHC_RESOURCE_SHORTAGE) != 0
+ || (ahc->platform_data->qfrozen != 0
+ && AHC_DV_SIMQ_FROZEN(ahc) == 0))
+ return (NULL);
+ return (TAILQ_FIRST(&ahc->platform_data->device_runq));
+}
+
+static __inline void
+ahc_linux_run_device_queues(struct ahc_softc *ahc)
+{
+ struct ahc_linux_device *dev;
+
+ while ((dev = ahc_linux_next_device_to_run(ahc)) != NULL) {
+ TAILQ_REMOVE(&ahc->platform_data->device_runq, dev, links);
+ dev->flags &= ~AHC_DEV_ON_RUN_LIST;
+ ahc_linux_check_device_queue(ahc, dev);
+ }
+}
+
+static __inline void
+ahc_linux_unmap_scb(struct ahc_softc *ahc, struct scb *scb)
+{
+ Scsi_Cmnd *cmd;
+
+ cmd = scb->io_ctx;
+ ahc_sync_sglist(ahc, scb, BUS_DMASYNC_POSTWRITE);
+ if (cmd->use_sg != 0) {
+ struct scatterlist *sg;
+
+ sg = (struct scatterlist *)cmd->request_buffer;
+ pci_unmap_sg(ahc->dev_softc, sg, cmd->use_sg,
+ scsi_to_pci_dma_dir(cmd->sc_data_direction));
+ } else if (cmd->request_bufflen != 0) {
+ pci_unmap_single(ahc->dev_softc,
+ scb->platform_data->buf_busaddr,
+ cmd->request_bufflen,
+ scsi_to_pci_dma_dir(cmd->sc_data_direction));
+ }
+}
+
+static __inline int
+ahc_linux_map_seg(struct ahc_softc *ahc, struct scb *scb,
+ struct ahc_dma_seg *sg, dma_addr_t addr, bus_size_t len)
+{
+ int consumed;
+
+ if ((scb->sg_count + 1) > AHC_NSEG)
+ panic("Too few segs for dma mapping. "
+ "Increase AHC_NSEG\n");
+
+ consumed = 1;
+ sg->addr = ahc_htole32(addr & 0xFFFFFFFF);
+ scb->platform_data->xfer_len += len;
+
+ if (sizeof(dma_addr_t) > 4
+ && (ahc->flags & AHC_39BIT_ADDRESSING) != 0)
+ len |= (addr >> 8) & AHC_SG_HIGH_ADDR_MASK;
+
+ sg->len = ahc_htole32(len);
+ return (consumed);
+}
+
+/************************ Host template entry points *************************/
+static int ahc_linux_detect(Scsi_Host_Template *);
+static int ahc_linux_queue(Scsi_Cmnd *, void (*)(Scsi_Cmnd *));
+static const char *ahc_linux_info(struct Scsi_Host *);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+static int ahc_linux_slave_alloc(Scsi_Device *);
+static int ahc_linux_slave_configure(Scsi_Device *);
+static void ahc_linux_slave_destroy(Scsi_Device *);
+#if defined(__i386__)
+static int ahc_linux_biosparam(struct scsi_device*,
+ struct block_device*,
+ sector_t, int[]);
+#endif
+#else
+static int ahc_linux_release(struct Scsi_Host *);
+static void ahc_linux_select_queue_depth(struct Scsi_Host *host,
+ Scsi_Device *scsi_devs);
+#if defined(__i386__)
+static int ahc_linux_biosparam(Disk *, kdev_t, int[]);
+#endif
+#endif
+static int ahc_linux_bus_reset(Scsi_Cmnd *);
+static int ahc_linux_dev_reset(Scsi_Cmnd *);
+static int ahc_linux_abort(Scsi_Cmnd *);
+
+/*
+ * Calculate a safe value for AHC_NSEG (as expressed through ahc_linux_nseg).
+ *
+ * In pre-2.5.X...
+ * The midlayer allocates an S/G array dynamically when a command is issued
+ * using SCSI malloc. This array, which is in an OS dependent format that
+ * must later be copied to our private S/G list, is sized to house just the
+ * number of segments needed for the current transfer. Since the code that
+ * sizes the SCSI malloc pool does not take into consideration fragmentation
+ * of the pool, executing transactions numbering just a fraction of our
+ * concurrent transaction limit with list lengths aproaching AHC_NSEG will
+ * quickly depleat the SCSI malloc pool of usable space. Unfortunately, the
+ * mid-layer does not properly handle this scsi malloc failures for the S/G
+ * array and the result can be a lockup of the I/O subsystem. We try to size
+ * our S/G list so that it satisfies our drivers allocation requirements in
+ * addition to avoiding fragmentation of the SCSI malloc pool.
+ */
+static void
+ahc_linux_size_nseg(void)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ u_int cur_size;
+ u_int best_size;
+
+ /*
+ * The SCSI allocator rounds to the nearest 512 bytes
+ * an cannot allocate across a page boundary. Our algorithm
+ * is to start at 1K of scsi malloc space per-command and
+ * loop through all factors of the PAGE_SIZE and pick the best.
+ */
+ best_size = 0;
+ for (cur_size = 1024; cur_size <= PAGE_SIZE; cur_size *= 2) {
+ u_int nseg;
+
+ nseg = cur_size / sizeof(struct scatterlist);
+ if (nseg < AHC_LINUX_MIN_NSEG)
+ continue;
+
+ if (best_size == 0) {
+ best_size = cur_size;
+ ahc_linux_nseg = nseg;
+ } else {
+ u_int best_rem;
+ u_int cur_rem;
+
+ /*
+ * Compare the traits of the current "best_size"
+ * with the current size to determine if the
+ * current size is a better size.
+ */
+ best_rem = best_size % sizeof(struct scatterlist);
+ cur_rem = cur_size % sizeof(struct scatterlist);
+ if (cur_rem < best_rem) {
+ best_size = cur_size;
+ ahc_linux_nseg = nseg;
+ }
+ }
+ }
+#endif
+}
+
+/*
+ * Try to detect an Adaptec 7XXX controller.
+ */
+static int
+ahc_linux_detect(Scsi_Host_Template *template)
+{
+ struct ahc_softc *ahc;
+ int found = 0;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ /*
+ * It is a bug that the upper layer takes
+ * this lock just prior to calling us.
+ */
+ spin_unlock_irq(&io_request_lock);
+#endif
+
+ /*
+ * Sanity checking of Linux SCSI data structures so
+ * that some of our hacks^H^H^H^H^Hassumptions aren't
+ * violated.
+ */
+ if (offsetof(struct ahc_cmd_internal, end)
+ > offsetof(struct scsi_cmnd, host_scribble)) {
+ printf("ahc_linux_detect: SCSI data structures changed.\n");
+ printf("ahc_linux_detect: Unable to attach\n");
+ return (0);
+ }
+ ahc_linux_size_nseg();
+ /*
+ * If we've been passed any parameters, process them now.
+ */
+ if (aic7xxx)
+ aic7xxx_setup(aic7xxx);
+
+ template->proc_name = "aic7xxx";
+
+ /*
+ * Initialize our softc list lock prior to
+ * probing for any adapters.
+ */
+ ahc_list_lockinit();
+
+ found = ahc_linux_pci_init();
+ if (!ahc_linux_eisa_init())
+ found++;
+
+ /*
+ * Register with the SCSI layer all
+ * controllers we've found.
+ */
+ TAILQ_FOREACH(ahc, &ahc_tailq, links) {
+
+ if (ahc_linux_register_host(ahc, template) == 0)
+ found++;
+ }
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ spin_lock_irq(&io_request_lock);
+#endif
+ aic7xxx_detect_complete++;
+
+ return (found);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+/*
+ * Free the passed in Scsi_Host memory structures prior to unloading the
+ * module.
+ */
+int
+ahc_linux_release(struct Scsi_Host * host)
+{
+ struct ahc_softc *ahc;
+ u_long l;
+
+ ahc_list_lock(&l);
+ if (host != NULL) {
+
+ /*
+ * We should be able to just perform
+ * the free directly, but check our
+ * list for extra sanity.
+ */
+ ahc = ahc_find_softc(*(struct ahc_softc **)host->hostdata);
+ if (ahc != NULL) {
+ u_long s;
+
+ ahc_lock(ahc, &s);
+ ahc_intr_enable(ahc, FALSE);
+ ahc_unlock(ahc, &s);
+ ahc_free(ahc);
+ }
+ }
+ ahc_list_unlock(&l);
+ return (0);
+}
+#endif
+
+/*
+ * Return a string describing the driver.
+ */
+static const char *
+ahc_linux_info(struct Scsi_Host *host)
+{
+ static char buffer[512];
+ char ahc_info[256];
+ char *bp;
+ struct ahc_softc *ahc;
+
+ bp = &buffer[0];
+ ahc = *(struct ahc_softc **)host->hostdata;
+ memset(bp, 0, sizeof(buffer));
+ strcpy(bp, "Adaptec AIC7XXX EISA/VLB/PCI SCSI HBA DRIVER, Rev ");
+ strcat(bp, AIC7XXX_DRIVER_VERSION);
+ strcat(bp, "\n");
+ strcat(bp, " <");
+ strcat(bp, ahc->description);
+ strcat(bp, ">\n");
+ strcat(bp, " ");
+ ahc_controller_info(ahc, ahc_info);
+ strcat(bp, ahc_info);
+ strcat(bp, "\n");
+
+ return (bp);
+}
+
+/*
+ * Queue an SCB to the controller.
+ */
+static int
+ahc_linux_queue(Scsi_Cmnd * cmd, void (*scsi_done) (Scsi_Cmnd *))
+{
+ struct ahc_softc *ahc;
+ struct ahc_linux_device *dev;
+ u_long flags;
+
+ ahc = *(struct ahc_softc **)cmd->device->host->hostdata;
+
+ /*
+ * Save the callback on completion function.
+ */
+ cmd->scsi_done = scsi_done;
+
+ ahc_midlayer_entrypoint_lock(ahc, &flags);
+
+ /*
+ * Close the race of a command that was in the process of
+ * being queued to us just as our simq was frozen. Let
+ * DV commands through so long as we are only frozen to
+ * perform DV.
+ */
+ if (ahc->platform_data->qfrozen != 0
+ && AHC_DV_CMD(cmd) == 0) {
+
+ ahc_cmd_set_transaction_status(cmd, CAM_REQUEUE_REQ);
+ ahc_linux_queue_cmd_complete(ahc, cmd);
+ ahc_schedule_completeq(ahc);
+ ahc_midlayer_entrypoint_unlock(ahc, &flags);
+ return (0);
+ }
+ dev = ahc_linux_get_device(ahc, cmd->device->channel, cmd->device->id,
+ cmd->device->lun, /*alloc*/TRUE);
+ if (dev == NULL) {
+ ahc_cmd_set_transaction_status(cmd, CAM_RESRC_UNAVAIL);
+ ahc_linux_queue_cmd_complete(ahc, cmd);
+ ahc_schedule_completeq(ahc);
+ ahc_midlayer_entrypoint_unlock(ahc, &flags);
+ printf("%s: aic7xxx_linux_queue - Unable to allocate device!\n",
+ ahc_name(ahc));
+ return (0);
+ }
+ cmd->result = CAM_REQ_INPROG << 16;
+ TAILQ_INSERT_TAIL(&dev->busyq, (struct ahc_cmd *)cmd, acmd_links.tqe);
+ if ((dev->flags & AHC_DEV_ON_RUN_LIST) == 0) {
+ TAILQ_INSERT_TAIL(&ahc->platform_data->device_runq, dev, links);
+ dev->flags |= AHC_DEV_ON_RUN_LIST;
+ ahc_linux_run_device_queues(ahc);
+ }
+ ahc_midlayer_entrypoint_unlock(ahc, &flags);
+ return (0);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+static int
+ahc_linux_slave_alloc(Scsi_Device *device)
+{
+ struct ahc_softc *ahc;
+
+ ahc = *((struct ahc_softc **)device->host->hostdata);
+ if (bootverbose)
+ printf("%s: Slave Alloc %d\n", ahc_name(ahc), device->id);
+ return (0);
+}
+
+static int
+ahc_linux_slave_configure(Scsi_Device *device)
+{
+ struct ahc_softc *ahc;
+ struct ahc_linux_device *dev;
+ u_long flags;
+
+ ahc = *((struct ahc_softc **)device->host->hostdata);
+ if (bootverbose)
+ printf("%s: Slave Configure %d\n", ahc_name(ahc), device->id);
+ ahc_midlayer_entrypoint_lock(ahc, &flags);
+ /*
+ * Since Linux has attached to the device, configure
+ * it so we don't free and allocate the device
+ * structure on every command.
+ */
+ dev = ahc_linux_get_device(ahc, device->channel,
+ device->id, device->lun,
+ /*alloc*/TRUE);
+ if (dev != NULL) {
+ dev->flags &= ~AHC_DEV_UNCONFIGURED;
+ dev->scsi_device = device;
+ ahc_linux_device_queue_depth(ahc, dev);
+ }
+ ahc_midlayer_entrypoint_unlock(ahc, &flags);
+ return (0);
+}
+
+static void
+ahc_linux_slave_destroy(Scsi_Device *device)
+{
+ struct ahc_softc *ahc;
+ struct ahc_linux_device *dev;
+ u_long flags;
+
+ ahc = *((struct ahc_softc **)device->host->hostdata);
+ if (bootverbose)
+ printf("%s: Slave Destroy %d\n", ahc_name(ahc), device->id);
+ ahc_midlayer_entrypoint_lock(ahc, &flags);
+ dev = ahc_linux_get_device(ahc, device->channel,
+ device->id, device->lun,
+ /*alloc*/FALSE);
+ /*
+ * Filter out "silly" deletion