| /*+M************************************************************************* |
| * Adaptec AIC7xxx device driver for Linux. |
| * |
| * 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. |
| * |
| * Parts of this driver were also based on the FreeBSD driver by |
| * Justin T. Gibbs. His copyright follows: |
| * |
| * -------------------------------------------------------------------------- |
| * Copyright (c) 1994-1997 Justin Gibbs. |
| * 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, immediately at the beginning of the file. |
| * 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. |
| * |
| * Where this Software is combined with software released under the terms of |
| * the GNU General Public License ("GPL") and the terms of the GPL would require the |
| * combined work to also be released under the terms of the GPL, the terms |
| * and conditions of this License will apply in addition to those of the |
| * GPL with the exception of any terms or conditions of this License that |
| * conflict with, or are expressly prohibited by, the GPL. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. |
| * |
| * $Id: aic7xxx.c,v 1.119 1997/06/27 19:39:18 gibbs Exp $ |
| *--------------------------------------------------------------------------- |
| * |
| * 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=ultra |
| * aic7xxx=irq_trigger:[0,1] # 0 edge, 1 level |
| * 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 $ |
| *-M*************************************************************************/ |
| |
| /*+M************************************************************************** |
| * |
| * 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 |
| * |
| * Overall, this driver represents a significant departure from the official |
| * aic7xxx driver released by Dan Eischen in two ways. First, in the code |
| * itself. A diff between the two version of the driver is now a several |
| * thousand line diff. Second, in approach to solving the same problem. The |
| * problem is importing the FreeBSD aic7xxx driver code to linux can be a |
| * difficult and time consuming process, that also can be error prone. Dan |
| * Eischen's official driver uses the approach that the linux and FreeBSD |
| * drivers should be as identical as possible. To that end, his next version |
| * of this driver will be using a mid-layer code library that he is developing |
| * to moderate communications between the linux mid-level SCSI code and the |
| * low level FreeBSD driver. He intends to be able to essentially drop the |
| * FreeBSD driver into the linux kernel with only a few minor tweaks to some |
| * include files and the like and get things working, making for fast easy |
| * imports of the FreeBSD code into linux. |
| * |
| * I disagree with Dan's approach. Not that I don't think his way of doing |
| * things would be nice, easy to maintain, and create a more uniform driver |
| * between FreeBSD and Linux. I have no objection to those issues. My |
| * disagreement is on the needed functionality. There simply are certain |
| * things that are done differently in FreeBSD than linux that will cause |
| * problems for this driver regardless of any middle ware Dan implements. |
| * The biggest example of this at the moment is interrupt semantics. Linux |
| * doesn't provide the same protection techniques as FreeBSD does, nor can |
| * they be easily implemented in any middle ware code since they would truly |
| * belong in the kernel proper and would effect all drivers. For the time |
| * being, I see issues such as these as major stumbling blocks to the |
| * reliability of code based upon such middle ware. Therefore, I choose to |
| * use a different approach to importing the FreeBSD code that doesn't |
| * involve any middle ware type code. My approach is to import the sequencer |
| * code from FreeBSD wholesale. Then, to only make changes in the kernel |
| * portion of the driver as they are needed for the new sequencer semantics. |
| * In this way, the portion of the driver that speaks to the rest of the |
| * linux kernel is fairly static and can be changed/modified to solve |
| * any problems one might encounter without concern for the FreeBSD driver. |
| * |
| * Note: If time and experience should prove me wrong that the middle ware |
| * code Dan writes is reliable in its operation, then I'll retract my above |
| * statements. But, for those that don't know, I'm from Missouri (in the US) |
| * and our state motto is "The Show-Me State". Well, before I will put |
| * faith into it, you'll have to show me that it works :) |
| * |
| *_M*************************************************************************/ |
| |
| /* |
| * The next three defines are user configurable. These should be the only |
| * defines a user might need to get in here and change. There are other |
| * defines buried deeper in the code, but those really shouldn't need touched |
| * under normal conditions. |
| */ |
| |
| /* |
| * AIC7XXX_STRICT_PCI_SETUP |
| * Should we assume the PCI config options on our controllers are set with |
| * sane and proper values, or should we be anal about our PCI config |
| * registers and force them to what we want? The main advantage to |
| * defining this option is on non-Intel hardware where the BIOS may not |
| * have been run to set things up, or if you have one of the BIOSless |
| * Adaptec controllers, such as a 2910, that don't get set up by the |
| * BIOS. However, keep in mind that we really do set the most important |
| * items in the driver regardless of this setting, this only controls some |
| * of the more esoteric PCI options on these cards. In that sense, I |
| * would default to leaving this off. However, if people wish to try |
| * things both ways, that would also help me to know if there are some |
| * machines where it works one way but not another. |
| * |
| * -- July 7, 17:09 |
| * OK...I need this on my machine for testing, so the default is to |
| * leave it defined. |
| * |
| * -- July 7, 18:49 |
| * I needed it for testing, but it didn't make any difference, so back |
| * off she goes. |
| * |
| * -- July 16, 23:04 |
| * I turned it back on to try and compensate for the 2.1.x PCI code |
| * which no longer relies solely on the BIOS and now tries to set |
| * things itself. |
| */ |
| |
| #define AIC7XXX_STRICT_PCI_SETUP |
| |
| /* |
| * AIC7XXX_VERBOSE_DEBUGGING |
| * This option enables a lot of extra printk();s in the code, surrounded |
| * by if (aic7xxx_verbose ...) statements. Executing all of those if |
| * statements and the extra checks can get to where it actually does have |
| * an impact on CPU usage and such, as well as code size. Disabling this |
| * define will keep some of those from becoming part of the code. |
| * |
| * NOTE: Currently, this option has no real effect, I will be adding the |
| * various #ifdef's in the code later when I've decided a section is |
| * complete and no longer needs debugging. OK...a lot of things are now |
| * surrounded by this define, so turning this off does have an impact. |
| */ |
| |
| /* |
| * #define AIC7XXX_VERBOSE_DEBUGGING |
| */ |
| |
| #include <linux/module.h> |
| #include <stdarg.h> |
| #include <asm/io.h> |
| #include <asm/irq.h> |
| #include <asm/byteorder.h> |
| #include <linux/string.h> |
| #include <linux/errno.h> |
| #include <linux/kernel.h> |
| #include <linux/ioport.h> |
| #include <linux/delay.h> |
| #include <linux/pci.h> |
| #include <linux/proc_fs.h> |
| #include <linux/blkdev.h> |
| #include <linux/init.h> |
| #include <linux/spinlock.h> |
| #include <linux/smp.h> |
| #include <linux/interrupt.h> |
| #include "scsi.h" |
| #include <scsi/scsi_host.h> |
| #include "aic7xxx_old/aic7xxx.h" |
| |
| #include "aic7xxx_old/sequencer.h" |
| #include "aic7xxx_old/scsi_message.h" |
| #include "aic7xxx_old/aic7xxx_reg.h" |
| #include <scsi/scsicam.h> |
| |
| #include <linux/stat.h> |
| #include <linux/slab.h> /* for kmalloc() */ |
| |
| #define AIC7XXX_C_VERSION "5.2.6" |
| |
| #define ALL_TARGETS -1 |
| #define ALL_CHANNELS -1 |
| #define ALL_LUNS -1 |
| #define MAX_TARGETS 16 |
| #define MAX_LUNS 8 |
| #ifndef TRUE |
| # define TRUE 1 |
| #endif |
| #ifndef FALSE |
| # define FALSE 0 |
| #endif |
| |
| #if defined(__powerpc__) || defined(__i386__) || defined(__x86_64__) |
| # define MMAPIO |
| #endif |
| |
| /* |
| * You can try raising me for better performance or lowering me if you have |
| * flaky devices that go off the scsi bus when hit with too many tagged |
| * commands (like some IBM SCSI-3 LVD drives). |
| */ |
| #define AIC7XXX_CMDS_PER_DEVICE 32 |
| |
| typedef struct |
| { |
| unsigned char tag_commands[16]; /* Allow for wide/twin adapters. */ |
| } adapter_tag_info_t; |
| |
| /* |
| * Make a define that will tell the driver not to the default tag depth |
| * everywhere. |
| */ |
| #define DEFAULT_TAG_COMMANDS {0, 0, 0, 0, 0, 0, 0, 0,\ |
| 0, 0, 0, 0, 0, 0, 0, 0} |
| |
| /* |
| * Modify this as you see fit for your system. By setting tag_commands |
| * to 0, the driver will use it's own algorithm for determining the |
| * number of commands to use (see above). When 255, the driver will |
| * not enable tagged queueing for that particular device. When positive |
| * (> 0) and (< 255) the values in the array are used for the queue_depth. |
| * Note that the maximum value for an entry is 254, but you're insane if |
| * you try to use that many commands on one device. |
| * |
| * 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 |
| * (1, 2-11, 13-15), disables tagged queueing for ID 12, and tells the |
| * driver to use its own algorithm 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 found after this fake one. |
| * |
| adapter_tag_info_t aic7xxx_tag_info[] = |
| { |
| {DEFAULT_TAG_COMMANDS}, |
| {{4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 255, 4, 4, 4}}, |
| {DEFAULT_TAG_COMMANDS}, |
| {{255, 16, 4, 255, 16, 4, 4, 4, 127, 4, 4, 4, 4, 4, 4, 4}} |
| }; |
| */ |
| |
| static adapter_tag_info_t aic7xxx_tag_info[] = |
| { |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS}, |
| {DEFAULT_TAG_COMMANDS} |
| }; |
| |
| |
| /* |
| * Define an array of board names that can be indexed by aha_type. |
| * Don't forget to change this when changing the types! |
| */ |
| static const char *board_names[] = { |
| "AIC-7xxx Unknown", /* AIC_NONE */ |
| "Adaptec AIC-7810 Hardware RAID Controller", /* AIC_7810 */ |
| "Adaptec AIC-7770 SCSI host adapter", /* AIC_7770 */ |
| "Adaptec AHA-274X SCSI host adapter", /* AIC_7771 */ |
| "Adaptec AHA-284X SCSI host adapter", /* AIC_284x */ |
| "Adaptec AIC-7850 SCSI host adapter", /* AIC_7850 */ |
| "Adaptec AIC-7855 SCSI host adapter", /* AIC_7855 */ |
| "Adaptec AIC-7860 Ultra SCSI host adapter", /* AIC_7860 */ |
| "Adaptec AHA-2940A Ultra SCSI host adapter", /* AIC_7861 */ |
| "Adaptec AIC-7870 SCSI host adapter", /* AIC_7870 */ |
| "Adaptec AHA-294X SCSI host adapter", /* AIC_7871 */ |
| "Adaptec AHA-394X SCSI host adapter", /* AIC_7872 */ |
| "Adaptec AHA-398X SCSI host adapter", /* AIC_7873 */ |
| "Adaptec AHA-2944 SCSI host adapter", /* AIC_7874 */ |
| "Adaptec AIC-7880 Ultra SCSI host adapter", /* AIC_7880 */ |
| "Adaptec AHA-294X Ultra SCSI host adapter", /* AIC_7881 */ |
| "Adaptec AHA-394X Ultra SCSI host adapter", /* AIC_7882 */ |
| "Adaptec AHA-398X Ultra SCSI host adapter", /* AIC_7883 */ |
| "Adaptec AHA-2944 Ultra SCSI host adapter", /* AIC_7884 */ |
| "Adaptec AHA-2940UW Pro Ultra SCSI host adapter", /* AIC_7887 */ |
| "Adaptec AIC-7895 Ultra SCSI host adapter", /* AIC_7895 */ |
| "Adaptec AIC-7890/1 Ultra2 SCSI host adapter", /* AIC_7890 */ |
| "Adaptec AHA-293X Ultra2 SCSI host adapter", /* AIC_7890 */ |
| "Adaptec AHA-294X Ultra2 SCSI host adapter", /* AIC_7890 */ |
| "Adaptec AIC-7896/7 Ultra2 SCSI host adapter", /* AIC_7896 */ |
| "Adaptec AHA-394X Ultra2 SCSI host adapter", /* AIC_7897 */ |
| "Adaptec AHA-395X Ultra2 SCSI host adapter", /* AIC_7897 */ |
| "Adaptec PCMCIA SCSI controller", /* card bus stuff */ |
| "Adaptec AIC-7892 Ultra 160/m SCSI host adapter", /* AIC_7892 */ |
| "Adaptec AIC-7899 Ultra 160/m SCSI host adapter", /* AIC_7899 */ |
| }; |
| |
| /* |
| * 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 |
| |
| /* |
| * What we want to do is have the higher level scsi driver requeue |
| * the command to us. There is no specific driver status for this |
| * condition, but the higher level scsi driver will requeue the |
| * command on a DID_BUS_BUSY error. |
| * |
| * Upon further inspection and testing, it seems that DID_BUS_BUSY |
| * will *always* retry the command. We can get into an infinite loop |
| * if this happens when we really want some sort of counter that |
| * will automatically abort/reset the command after so many retries. |
| * Using DID_ERROR will do just that. (Made by a suggestion by |
| * Doug Ledford 8/1/96) |
| */ |
| #define DID_RETRY_COMMAND DID_ERROR |
| |
| #define HSCSIID 0x07 |
| #define SCSI_RESET 0x040 |
| |
| /* |
| * EISA/VL-bus stuff |
| */ |
| #define MINSLOT 1 |
| #define MAXSLOT 15 |
| #define SLOTBASE(x) ((x) << 12) |
| #define BASE_TO_SLOT(x) ((x) >> 12) |
| |
| /* |
| * Standard EISA Host ID regs (Offset from slot base) |
| */ |
| #define AHC_HID0 0x80 /* 0,1: msb of ID2, 2-7: ID1 */ |
| #define AHC_HID1 0x81 /* 0-4: ID3, 5-7: LSB ID2 */ |
| #define AHC_HID2 0x82 /* product */ |
| #define AHC_HID3 0x83 /* firmware revision */ |
| |
| /* |
| * AIC-7770 I/O range to reserve for a card |
| */ |
| #define MINREG 0xC00 |
| #define MAXREG 0xCFF |
| |
| #define INTDEF 0x5C /* Interrupt Definition Register */ |
| |
| /* |
| * AIC-78X0 PCI registers |
| */ |
| #define CLASS_PROGIF_REVID 0x08 |
| #define DEVREVID 0x000000FFul |
| #define PROGINFC 0x0000FF00ul |
| #define SUBCLASS 0x00FF0000ul |
| #define BASECLASS 0xFF000000ul |
| |
| #define CSIZE_LATTIME 0x0C |
| #define CACHESIZE 0x0000003Ful /* only 5 bits */ |
| #define LATTIME 0x0000FF00ul |
| |
| #define DEVCONFIG 0x40 |
| #define SCBSIZE32 0x00010000ul /* aic789X only */ |
| #define MPORTMODE 0x00000400ul /* aic7870 only */ |
| #define RAMPSM 0x00000200ul /* aic7870 only */ |
| #define RAMPSM_ULTRA2 0x00000004 |
| #define VOLSENSE 0x00000100ul |
| #define SCBRAMSEL 0x00000080ul |
| #define SCBRAMSEL_ULTRA2 0x00000008 |
| #define MRDCEN 0x00000040ul |
| #define EXTSCBTIME 0x00000020ul /* aic7870 only */ |
| #define EXTSCBPEN 0x00000010ul /* aic7870 only */ |
| #define BERREN 0x00000008ul |
| #define DACEN 0x00000004ul |
| #define STPWLEVEL 0x00000002ul |
| #define DIFACTNEGEN 0x00000001ul /* aic7870 only */ |
| |
| #define SCAMCTL 0x1a /* Ultra2 only */ |
| #define CCSCBBADDR 0xf0 /* aic7895/6/7 */ |
| |
| /* |
| * Define the different types of SEEPROMs on aic7xxx adapters |
| * and make it also represent the address size used in accessing |
| * its registers. The 93C46 chips have 1024 bits organized into |
| * 64 16-bit words, while the 93C56 chips have 2048 bits organized |
| * into 128 16-bit words. The C46 chips use 6 bits to address |
| * each word, while the C56 and C66 (4096 bits) use 8 bits to |
| * address each word. |
| */ |
| typedef enum {C46 = 6, C56_66 = 8} seeprom_chip_type; |
| |
| /* |
| * |
| * Define the format of the SEEPROM registers (16 bits). |
| * |
| */ |
| struct seeprom_config { |
| |
| /* |
| * SCSI ID Configuration Flags |
| */ |
| #define CFXFER 0x0007 /* synchronous transfer rate */ |
| #define CFSYNCH 0x0008 /* enable synchronous transfer */ |
| #define CFDISC 0x0010 /* enable disconnection */ |
| #define CFWIDEB 0x0020 /* wide bus device (wide card) */ |
| #define CFSYNCHISULTRA 0x0040 /* CFSYNC is an ultra offset */ |
| #define CFNEWULTRAFORMAT 0x0080 /* Use the Ultra2 SEEPROM format */ |
| #define CFSTART 0x0100 /* send start unit SCSI command */ |
| #define CFINCBIOS 0x0200 /* include in BIOS scan */ |
| #define CFRNFOUND 0x0400 /* report even if not found */ |
| #define CFMULTILUN 0x0800 /* probe mult luns in BIOS scan */ |
| #define CFWBCACHEYES 0x4000 /* Enable W-Behind Cache on drive */ |
| #define CFWBCACHENC 0xc000 /* Don't change W-Behind Cache */ |
| /* UNUSED 0x3000 */ |
| unsigned short device_flags[16]; /* words 0-15 */ |
| |
| /* |
| * BIOS Control Bits |
| */ |
| #define CFSUPREM 0x0001 /* support all removable drives */ |
| #define CFSUPREMB 0x0002 /* support removable drives for boot only */ |
| #define CFBIOSEN 0x0004 /* BIOS enabled */ |
| /* UNUSED 0x0008 */ |
| #define CFSM2DRV 0x0010 /* support more than two drives */ |
| #define CF284XEXTEND 0x0020 /* extended translation (284x cards) */ |
| /* UNUSED 0x0040 */ |
| #define CFEXTEND 0x0080 /* extended translation enabled */ |
| /* UNUSED 0xFF00 */ |
| unsigned short bios_control; /* word 16 */ |
| |
| /* |
| * Host Adapter Control Bits |
| */ |
| #define CFAUTOTERM 0x0001 /* Perform Auto termination */ |
| #define CFULTRAEN 0x0002 /* Ultra SCSI speed enable (Ultra cards) */ |
| #define CF284XSELTO 0x0003 /* Selection timeout (284x cards) */ |
| #define CF284XFIFO 0x000C /* FIFO Threshold (284x cards) */ |
| #define CFSTERM 0x0004 /* SCSI low byte termination */ |
| #define CFWSTERM 0x0008 /* SCSI high byte termination (wide card) */ |
| #define CFSPARITY 0x0010 /* SCSI parity */ |
| #define CF284XSTERM 0x0020 /* SCSI low byte termination (284x cards) */ |
| #define CFRESETB 0x0040 /* reset SCSI bus at boot */ |
| #define CFBPRIMARY 0x0100 /* Channel B primary on 7895 chipsets */ |
| #define CFSEAUTOTERM 0x0400 /* aic7890 Perform SE Auto Term */ |
| #define CFLVDSTERM 0x0800 /* aic7890 LVD Termination */ |
| /* UNUSED 0xF280 */ |
| unsigned short adapter_control; /* word 17 */ |
| |
| /* |
| * Bus Release, Host Adapter ID |
| */ |
| #define CFSCSIID 0x000F /* host adapter SCSI ID */ |
| /* UNUSED 0x00F0 */ |
| #define CFBRTIME 0xFF00 /* bus release time */ |
| unsigned short brtime_id; /* word 18 */ |
| |
| /* |
| * Maximum targets |
| */ |
| #define CFMAXTARG 0x00FF /* maximum targets */ |
| /* UNUSED 0xFF00 */ |
| unsigned short max_targets; /* word 19 */ |
| |
| unsigned short res_1[11]; /* words 20-30 */ |
| unsigned short checksum; /* word 31 */ |
| }; |
| |
| #define SELBUS_MASK 0x0a |
| #define SELNARROW 0x00 |
| #define SELBUSB 0x08 |
| #define SINGLE_BUS 0x00 |
| |
| #define SCB_TARGET(scb) \ |
| (((scb)->hscb->target_channel_lun & TID) >> 4) |
| #define SCB_LUN(scb) \ |
| ((scb)->hscb->target_channel_lun & LID) |
| #define SCB_IS_SCSIBUS_B(scb) \ |
| (((scb)->hscb->target_channel_lun & SELBUSB) != 0) |
| |
| /* |
| * If an error occurs during a data transfer phase, run the command |
| * to completion - it's easier that way - making a note of the error |
| * condition in this location. This then will modify a DID_OK status |
| * into an appropriate error for the higher-level SCSI code. |
| */ |
| #define aic7xxx_error(cmd) ((cmd)->SCp.Status) |
| |
| /* |
| * Keep track of the targets returned status. |
| */ |
| #define aic7xxx_status(cmd) ((cmd)->SCp.sent_command) |
| |
| /* |
| * The position of the SCSI commands scb within the scb array. |
| */ |
| #define aic7xxx_position(cmd) ((cmd)->SCp.have_data_in) |
| |
| /* |
| * The stored DMA mapping for single-buffer data transfers. |
| */ |
| #define aic7xxx_mapping(cmd) ((cmd)->SCp.phase) |
| |
| /* |
| * Get out private data area from a scsi cmd pointer |
| */ |
| #define AIC_DEV(cmd) ((struct aic_dev_data *)(cmd)->device->hostdata) |
| |
| /* |
| * So we can keep track of our host structs |
| */ |
| static struct aic7xxx_host *first_aic7xxx = NULL; |
| |
| /* |
| * As of Linux 2.1, the mid-level SCSI code uses virtual addresses |
| * in the scatter-gather lists. We need to convert the virtual |
| * addresses to physical addresses. |
| */ |
| struct hw_scatterlist { |
| unsigned int address; |
| unsigned int length; |
| }; |
| |
| /* |
| * Maximum number of SG segments these cards can support. |
| */ |
| #define AIC7XXX_MAX_SG 128 |
| |
| /* |
| * The maximum number of SCBs we could have for ANY type |
| * of card. DON'T FORGET TO CHANGE THE SCB MASK IN THE |
| * SEQUENCER CODE IF THIS IS MODIFIED! |
| */ |
| #define AIC7XXX_MAXSCB 255 |
| |
| |
| struct aic7xxx_hwscb { |
| /* ------------ Begin hardware supported fields ---------------- */ |
| /* 0*/ unsigned char control; |
| /* 1*/ unsigned char target_channel_lun; /* 4/1/3 bits */ |
| /* 2*/ unsigned char target_status; |
| /* 3*/ unsigned char SG_segment_count; |
| /* 4*/ unsigned int SG_list_pointer; |
| /* 8*/ unsigned char residual_SG_segment_count; |
| /* 9*/ unsigned char residual_data_count[3]; |
| /*12*/ unsigned int data_pointer; |
| /*16*/ unsigned int data_count; |
| /*20*/ unsigned int SCSI_cmd_pointer; |
| /*24*/ unsigned char SCSI_cmd_length; |
| /*25*/ unsigned char tag; /* Index into our kernel SCB array. |
| * Also used as the tag for tagged I/O |
| */ |
| #define SCB_PIO_TRANSFER_SIZE 26 /* amount we need to upload/download |
| * via PIO to initialize a transaction. |
| */ |
| /*26*/ unsigned char next; /* Used to thread SCBs awaiting selection |
| * or disconnected down in the sequencer. |
| */ |
| /*27*/ unsigned char prev; |
| /*28*/ unsigned int pad; /* |
| * Unused by the kernel, but we require |
| * the padding so that the array of |
| * hardware SCBs is aligned on 32 byte |
| * boundaries so the sequencer can index |
| */ |
| }; |
| |
| typedef enum { |
| SCB_FREE = 0x0000, |
| SCB_DTR_SCB = 0x0001, |
| SCB_WAITINGQ = 0x0002, |
| SCB_ACTIVE = 0x0004, |
| SCB_SENSE = 0x0008, |
| SCB_ABORT = 0x0010, |
| SCB_DEVICE_RESET = 0x0020, |
| SCB_RESET = 0x0040, |
| SCB_RECOVERY_SCB = 0x0080, |
| SCB_MSGOUT_PPR = 0x0100, |
| SCB_MSGOUT_SENT = 0x0200, |
| SCB_MSGOUT_SDTR = 0x0400, |
| SCB_MSGOUT_WDTR = 0x0800, |
| SCB_MSGOUT_BITS = SCB_MSGOUT_PPR | |
| SCB_MSGOUT_SENT | |
| SCB_MSGOUT_SDTR | |
| SCB_MSGOUT_WDTR, |
| SCB_QUEUED_ABORT = 0x1000, |
| SCB_QUEUED_FOR_DONE = 0x2000, |
| SCB_WAS_BUSY = 0x4000, |
| SCB_QUEUE_FULL = 0x8000 |
| } scb_flag_type; |
| |
| typedef enum { |
| AHC_FNONE = 0x00000000, |
| AHC_PAGESCBS = 0x00000001, |
| AHC_CHANNEL_B_PRIMARY = 0x00000002, |
| AHC_USEDEFAULTS = 0x00000004, |
| AHC_INDIRECT_PAGING = 0x00000008, |
| AHC_CHNLB = 0x00000020, |
| AHC_CHNLC = 0x00000040, |
| AHC_EXTEND_TRANS_A = 0x00000100, |
| AHC_EXTEND_TRANS_B = 0x00000200, |
| AHC_TERM_ENB_A = 0x00000400, |
| AHC_TERM_ENB_SE_LOW = 0x00000400, |
| AHC_TERM_ENB_B = 0x00000800, |
| AHC_TERM_ENB_SE_HIGH = 0x00000800, |
| AHC_HANDLING_REQINITS = 0x00001000, |
| AHC_TARGETMODE = 0x00002000, |
| AHC_NEWEEPROM_FMT = 0x00004000, |
| /* |
| * Here ends the FreeBSD defined flags and here begins the linux defined |
| * flags. NOTE: I did not preserve the old flag name during this change |
| * specifically to force me to evaluate what flags were being used properly |
| * and what flags weren't. This way, I could clean up the flag usage on |
| * a use by use basis. Doug Ledford |
| */ |
| AHC_MOTHERBOARD = 0x00020000, |
| AHC_NO_STPWEN = 0x00040000, |
| AHC_RESET_DELAY = 0x00080000, |
| AHC_A_SCANNED = 0x00100000, |
| AHC_B_SCANNED = 0x00200000, |
| AHC_MULTI_CHANNEL = 0x00400000, |
| AHC_BIOS_ENABLED = 0x00800000, |
| AHC_SEEPROM_FOUND = 0x01000000, |
| AHC_TERM_ENB_LVD = 0x02000000, |
| AHC_ABORT_PENDING = 0x04000000, |
| AHC_RESET_PENDING = 0x08000000, |
| #define AHC_IN_ISR_BIT 28 |
| AHC_IN_ISR = 0x10000000, |
| AHC_IN_ABORT = 0x20000000, |
| AHC_IN_RESET = 0x40000000, |
| AHC_EXTERNAL_SRAM = 0x80000000 |
| } ahc_flag_type; |
| |
| typedef enum { |
| AHC_NONE = 0x0000, |
| AHC_CHIPID_MASK = 0x00ff, |
| AHC_AIC7770 = 0x0001, |
| AHC_AIC7850 = 0x0002, |
| AHC_AIC7860 = 0x0003, |
| AHC_AIC7870 = 0x0004, |
| AHC_AIC7880 = 0x0005, |
| AHC_AIC7890 = 0x0006, |
| AHC_AIC7895 = 0x0007, |
| AHC_AIC7896 = 0x0008, |
| AHC_AIC7892 = 0x0009, |
| AHC_AIC7899 = 0x000a, |
| AHC_VL = 0x0100, |
| AHC_EISA = 0x0200, |
| AHC_PCI = 0x0400, |
| } ahc_chip; |
| |
| typedef enum { |
| AHC_FENONE = 0x0000, |
| AHC_ULTRA = 0x0001, |
| AHC_ULTRA2 = 0x0002, |
| AHC_WIDE = 0x0004, |
| AHC_TWIN = 0x0008, |
| AHC_MORE_SRAM = 0x0010, |
| AHC_CMD_CHAN = 0x0020, |
| AHC_QUEUE_REGS = 0x0040, |
| AHC_SG_PRELOAD = 0x0080, |
| AHC_SPIOCAP = 0x0100, |
| AHC_ULTRA3 = 0x0200, |
| AHC_NEW_AUTOTERM = 0x0400, |
| AHC_AIC7770_FE = AHC_FENONE, |
| AHC_AIC7850_FE = AHC_SPIOCAP, |
| AHC_AIC7860_FE = AHC_ULTRA|AHC_SPIOCAP, |
| AHC_AIC7870_FE = AHC_FENONE, |
| AHC_AIC7880_FE = AHC_ULTRA, |
| AHC_AIC7890_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA2| |
| AHC_QUEUE_REGS|AHC_SG_PRELOAD|AHC_NEW_AUTOTERM, |
| AHC_AIC7895_FE = AHC_MORE_SRAM|AHC_CMD_CHAN|AHC_ULTRA, |
| AHC_AIC7896_FE = AHC_AIC7890_FE, |
| AHC_AIC7892_FE = AHC_AIC7890_FE|AHC_ULTRA3, |
| AHC_AIC7899_FE = AHC_AIC7890_FE|AHC_ULTRA3, |
| } ahc_feature; |
| |
| #define SCB_DMA_ADDR(scb, addr) ((unsigned long)(addr) + (scb)->scb_dma->dma_offset) |
| |
| struct aic7xxx_scb_dma { |
| unsigned long dma_offset; /* Correction you have to add |
| * to virtual address to get |
| * dma handle in this region */ |
| dma_addr_t dma_address; /* DMA handle of the start, |
| * for unmap */ |
| unsigned int dma_len; /* DMA length */ |
| }; |
| |
| typedef enum { |
| AHC_BUG_NONE = 0x0000, |
| AHC_BUG_TMODE_WIDEODD = 0x0001, |
| AHC_BUG_AUTOFLUSH = 0x0002, |
| AHC_BUG_CACHETHEN = 0x0004, |
| AHC_BUG_CACHETHEN_DIS = 0x0008, |
| AHC_BUG_PCI_2_1_RETRY = 0x0010, |
| AHC_BUG_PCI_MWI = 0x0020, |
| AHC_BUG_SCBCHAN_UPLOAD = 0x0040, |
| } ahc_bugs; |
| |
| struct aic7xxx_scb { |
| struct aic7xxx_hwscb *hscb; /* corresponding hardware scb */ |
| struct scsi_cmnd *cmd; /* scsi_cmnd for this scb */ |
| struct aic7xxx_scb *q_next; /* next scb in queue */ |
| volatile scb_flag_type flags; /* current state of scb */ |
| struct hw_scatterlist *sg_list; /* SG list in adapter format */ |
| unsigned char tag_action; |
| unsigned char sg_count; |
| unsigned char *sense_cmd; /* |
| * Allocate 6 characters for |
| * sense command. |
| */ |
| unsigned char *cmnd; |
| unsigned int sg_length; /* |
| * We init this during |
| * buildscb so we don't have |
| * to calculate anything during |
| * underflow/overflow/stat code |
| */ |
| void *kmalloc_ptr; |
| struct aic7xxx_scb_dma *scb_dma; |
| }; |
| |
| /* |
| * Define a linked list of SCBs. |
| */ |
| typedef struct { |
| struct aic7xxx_scb *head; |
| struct aic7xxx_scb *tail; |
| } scb_queue_type; |
| |
| static struct { |
| unsigned char errno; |
| const char *errmesg; |
| } hard_error[] = { |
| { ILLHADDR, "Illegal Host Access" }, |
| { ILLSADDR, "Illegal Sequencer Address referenced" }, |
| { ILLOPCODE, "Illegal Opcode in sequencer program" }, |
| { SQPARERR, "Sequencer Ram Parity Error" }, |
| { DPARERR, "Data-Path Ram Parity Error" }, |
| { MPARERR, "Scratch Ram/SCB Array Ram Parity Error" }, |
| { PCIERRSTAT,"PCI Error detected" }, |
| { CIOPARERR, "CIOBUS Parity Error" } |
| }; |
| |
| static unsigned char |
| generic_sense[] = { REQUEST_SENSE, 0, 0, 0, 255, 0 }; |
| |
| typedef struct { |
| scb_queue_type free_scbs; /* |
| * SCBs assigned to free slot on |
| * card (no paging required) |
| */ |
| struct aic7xxx_scb *scb_array[AIC7XXX_MAXSCB]; |
| struct aic7xxx_hwscb *hscbs; |
| unsigned char numscbs; /* current number of scbs */ |
| unsigned char maxhscbs; /* hardware scbs */ |
| unsigned char maxscbs; /* max scbs including pageable scbs */ |
| dma_addr_t hscbs_dma; /* DMA handle to hscbs */ |
| unsigned int hscbs_dma_len; /* length of the above DMA area */ |
| void *hscb_kmalloc_ptr; |
| } scb_data_type; |
| |
| struct target_cmd { |
| unsigned char mesg_bytes[4]; |
| unsigned char command[28]; |
| }; |
| |
| #define AHC_TRANS_CUR 0x0001 |
| #define AHC_TRANS_ACTIVE 0x0002 |
| #define AHC_TRANS_GOAL 0x0004 |
| #define AHC_TRANS_USER 0x0008 |
| #define AHC_TRANS_QUITE 0x0010 |
| typedef struct { |
| unsigned char width; |
| unsigned char period; |
| unsigned char offset; |
| unsigned char options; |
| } transinfo_type; |
| |
| struct aic_dev_data { |
| volatile scb_queue_type delayed_scbs; |
| volatile unsigned short temp_q_depth; |
| unsigned short max_q_depth; |
| volatile unsigned char active_cmds; |
| /* |
| * Statistics Kept: |
| * |
| * Total Xfers (count for each command that has a data xfer), |
| * broken down by reads && writes. |
| * |
| * Further sorted into a few bins for keeping tabs on how many commands |
| * we get of various sizes. |
| * |
| */ |
| long w_total; /* total writes */ |
| long r_total; /* total reads */ |
| long barrier_total; /* total num of REQ_BARRIER commands */ |
| long ordered_total; /* How many REQ_BARRIER commands we |
| used ordered tags to satisfy */ |
| long w_bins[6]; /* binned write */ |
| long r_bins[6]; /* binned reads */ |
| transinfo_type cur; |
| transinfo_type goal; |
| #define BUS_DEVICE_RESET_PENDING 0x01 |
| #define DEVICE_RESET_DELAY 0x02 |
| #define DEVICE_PRINT_DTR 0x04 |
| #define DEVICE_WAS_BUSY 0x08 |
| #define DEVICE_DTR_SCANNED 0x10 |
| #define DEVICE_SCSI_3 0x20 |
| volatile unsigned char flags; |
| unsigned needppr:1; |
| unsigned needppr_copy:1; |
| unsigned needsdtr:1; |
| unsigned needsdtr_copy:1; |
| unsigned needwdtr:1; |
| unsigned needwdtr_copy:1; |
| unsigned dtr_pending:1; |
| struct scsi_device *SDptr; |
| struct list_head list; |
| }; |
| |
| /* |
| * Define a structure used for each host adapter. Note, in order to avoid |
| * problems with architectures I can't test on (because I don't have one, |
| * such as the Alpha based systems) which happen to give faults for |
| * non-aligned memory accesses, care was taken to align this structure |
| * in a way that gauranteed all accesses larger than 8 bits were aligned |
| * on the appropriate boundary. It's also organized to try and be more |
| * cache line efficient. Be careful when changing this lest you might hurt |
| * overall performance and bring down the wrath of the masses. |
| */ |
| struct aic7xxx_host { |
| /* |
| * This is the first 64 bytes in the host struct |
| */ |
| |
| /* |
| * We are grouping things here....first, items that get either read or |
| * written with nearly every interrupt |
| */ |
| volatile long flags; |
| ahc_feature features; /* chip features */ |
| unsigned long base; /* card base address */ |
| volatile unsigned char __iomem *maddr; /* memory mapped address */ |
| unsigned long isr_count; /* Interrupt count */ |
| unsigned long spurious_int; |
| scb_data_type *scb_data; |
| struct aic7xxx_cmd_queue { |
| struct scsi_cmnd *head; |
| struct scsi_cmnd *tail; |
| } completeq; |
| |
| /* |
| * Things read/written on nearly every entry into aic7xxx_queue() |
| */ |
| volatile scb_queue_type waiting_scbs; |
| unsigned char unpause; /* unpause value for HCNTRL */ |
| unsigned char pause; /* pause value for HCNTRL */ |
| volatile unsigned char qoutfifonext; |
| volatile unsigned char activescbs; /* active scbs */ |
| volatile unsigned char max_activescbs; |
| volatile unsigned char qinfifonext; |
| volatile unsigned char *untagged_scbs; |
| volatile unsigned char *qoutfifo; |
| volatile unsigned char *qinfifo; |
| |
| unsigned char dev_last_queue_full[MAX_TARGETS]; |
| unsigned char dev_last_queue_full_count[MAX_TARGETS]; |
| unsigned short ultraenb; /* Gets downloaded to card as a bitmap */ |
| unsigned short discenable; /* Gets downloaded to card as a bitmap */ |
| transinfo_type user[MAX_TARGETS]; |
| |
| unsigned char msg_buf[13]; /* The message for the target */ |
| unsigned char msg_type; |
| #define MSG_TYPE_NONE 0x00 |
| #define MSG_TYPE_INITIATOR_MSGOUT 0x01 |
| #define MSG_TYPE_INITIATOR_MSGIN 0x02 |
| unsigned char msg_len; /* Length of message */ |
| unsigned char msg_index; /* Index into msg_buf array */ |
| |
| |
| /* |
| * We put the less frequently used host structure items |
| * after the more frequently used items to try and ease |
| * the burden on the cache subsystem. |
| * These entries are not *commonly* accessed, whereas |
| * the preceding entries are accessed very often. |
| */ |
| |
| unsigned int irq; /* IRQ for this adapter */ |
| int instance; /* aic7xxx instance number */ |
| int scsi_id; /* host adapter SCSI ID */ |
| int scsi_id_b; /* channel B for twin adapters */ |
| unsigned int bios_address; |
| int board_name_index; |
| unsigned short bios_control; /* bios control - SEEPROM */ |
| unsigned short adapter_control; /* adapter control - SEEPROM */ |
| struct pci_dev *pdev; |
| unsigned char pci_bus; |
| unsigned char pci_device_fn; |
| struct seeprom_config sc; |
| unsigned short sc_type; |
| unsigned short sc_size; |
| struct aic7xxx_host *next; /* allow for multiple IRQs */ |
| struct Scsi_Host *host; /* pointer to scsi host */ |
| struct list_head aic_devs; /* all aic_dev structs on host */ |
| int host_no; /* SCSI host number */ |
| unsigned long mbase; /* I/O memory address */ |
| ahc_chip chip; /* chip type */ |
| ahc_bugs bugs; |
| dma_addr_t fifo_dma; /* DMA handle for fifo arrays */ |
| }; |
| |
| /* |
| * Valid SCSIRATE values. (p. 3-17) |
| * Provides a mapping of transfer periods in ns/4 to the proper value to |
| * stick in the SCSIRATE reg to use that transfer rate. |
| */ |
| #define AHC_SYNCRATE_ULTRA3 0 |
| #define AHC_SYNCRATE_ULTRA2 1 |
| #define AHC_SYNCRATE_ULTRA 3 |
| #define AHC_SYNCRATE_FAST 6 |
| #define AHC_SYNCRATE_CRC 0x40 |
| #define AHC_SYNCRATE_SE 0x10 |
| static struct aic7xxx_syncrate { |
| /* Rates in Ultra mode have bit 8 of sxfr set */ |
| #define ULTRA_SXFR 0x100 |
| int sxfr_ultra2; |
| int sxfr; |
| unsigned char period; |
| const char *rate[2]; |
| } aic7xxx_syncrates[] = { |
| { 0x42, 0x000, 9, {"80.0", "160.0"} }, |
| { 0x13, 0x000, 10, {"40.0", "80.0"} }, |
| { 0x14, 0x000, 11, {"33.0", "66.6"} }, |
| { 0x15, 0x100, 12, {"20.0", "40.0"} }, |
| { 0x16, 0x110, 15, {"16.0", "32.0"} }, |
| { 0x17, 0x120, 18, {"13.4", "26.8"} }, |
| { 0x18, 0x000, 25, {"10.0", "20.0"} }, |
| { 0x19, 0x010, 31, {"8.0", "16.0"} }, |
| { 0x1a, 0x020, 37, {"6.67", "13.3"} }, |
| { 0x1b, 0x030, 43, {"5.7", "11.4"} }, |
| { 0x10, 0x040, 50, {"5.0", "10.0"} }, |
| { 0x00, 0x050, 56, {"4.4", "8.8" } }, |
| { 0x00, 0x060, 62, {"4.0", "8.0" } }, |
| { 0x00, 0x070, 68, {"3.6", "7.2" } }, |
| { 0x00, 0x000, 0, {NULL, NULL} }, |
| }; |
| |
| #define CTL_OF_SCB(scb) (((scb->hscb)->target_channel_lun >> 3) & 0x1), \ |
| (((scb->hscb)->target_channel_lun >> 4) & 0xf), \ |
| ((scb->hscb)->target_channel_lun & 0x07) |
| |
| #define CTL_OF_CMD(cmd) ((cmd->device->channel) & 0x01), \ |
| ((cmd->device->id) & 0x0f), \ |
| ((cmd->device->lun) & 0x07) |
| |
| #define TARGET_INDEX(cmd) ((cmd)->device->id | ((cmd)->device->channel << 3)) |
| |
| /* |
| * A nice little define to make doing our printks a little easier |
| */ |
| |
| #define WARN_LEAD KERN_WARNING "(scsi%d:%d:%d:%d) " |
| #define INFO_LEAD KERN_INFO "(scsi%d:%d:%d:%d) " |
| |
| /* |
| * 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. |
| */ |
| |
| /* |
| * Use this as the default queue depth when setting tagged queueing on. |
| */ |
| static unsigned int aic7xxx_default_queue_depth = AIC7XXX_CMDS_PER_DEVICE; |
| |
| /* |
| * 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 unsigned int aic7xxx_no_reset = 0; |
| /* |
| * 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 int aic7xxx_reverse_scan = 0; |
| /* |
| * 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 unsigned int aic7xxx_extended = 0; |
| /* |
| * The IRQ trigger method used on EISA controllers. Does not effect PCI cards. |
| * -1 = Use detected settings. |
| * 0 = Force Edge triggered mode. |
| * 1 = Force Level triggered mode. |
| */ |
| static int aic7xxx_irq_trigger = -1; |
| /* |
| * This variable is used to override the termination settings on a controller. |
| * This should not be used under normal conditions. However, in the case |
| * that a controller does not have a readable SEEPROM (so that we can't |
| * read the SEEPROM settings directly) and that a controller has a buggered |
| * version of the cable detection logic, this can be used to force the |
| * correct termination. It is preferable to use the manual termination |
| * settings in the BIOS if possible, but some motherboard controllers store |
| * those settings in a format we can't read. In other cases, auto term |
| * should also work, but the chipset was put together with no auto term |
| * logic (common on motherboard controllers). In those cases, we have |
| * 32 bits here to work with. That's good for 8 controllers/channels. The |
| * bits are organized as 4 bits per channel, with scsi0 getting the lowest |
| * 4 bits in the int. A 1 in a bit position indicates the termination setting |
| * that corresponds to that bit should be enabled, a 0 is disabled. |
| * It looks something like this: |
| * |
| * 0x0f = 1111-Single Ended Low Byte Termination on/off |
| * ||\-Single Ended High Byte Termination on/off |
| * |\-LVD Low Byte Termination on/off |
| * \-LVD High Byte Termination on/off |
| * |
| * For non-Ultra2 controllers, the upper 2 bits are not important. So, to |
| * enable both high byte and low byte termination on scsi0, I would need to |
| * make sure that the override_term variable was set to 0x03 (bits 0011). |
| * To make sure that all termination is enabled on an Ultra2 controller at |
| * scsi2 and only high byte termination on scsi1 and high and low byte |
| * termination on scsi0, I would set override_term=0xf23 (bits 1111 0010 0011) |
| * |
| * For the most part, users should never have to use this, that's why I |
| * left it fairly cryptic instead of easy to understand. If you need it, |
| * most likely someone will be telling you what your's needs to be set to. |
| */ |
| static int aic7xxx_override_term = -1; |
| /* |
| * Certain motherboard chipset controllers tend to screw |
| * up the polarity of the term enable output pin. Use this variable |
| * to force the correct polarity for your system. This is a bitfield variable |
| * similar to the previous one, but this one has one bit per channel instead |
| * of four. |
| * 0 = Force the setting to active low. |
| * 1 = Force setting to active high. |
| * Most Adaptec cards are active high, several motherboards are active low. |
| * To force a 2940 card at SCSI 0 to active high and a motherboard 7895 |
| * controller at scsi1 and scsi2 to active low, and a 2910 card at scsi3 |
| * to active high, you would need to set stpwlev=0x9 (bits 1001). |
| * |
| * People shouldn't need to use this, but if you are experiencing lots of |
| * SCSI timeout problems, this may help. There is one sure way to test what |
| * this option needs to be. Using a boot floppy to boot the system, configure |
| * your system to enable all SCSI termination (in the Adaptec SCSI BIOS) and |
| * if needed then also pass a value to override_term to make sure that the |
| * driver is enabling SCSI termination, then set this variable to either 0 |
| * or 1. When the driver boots, make sure there are *NO* SCSI cables |
| * connected to your controller. If it finds and inits the controller |
| * without problem, then the setting you passed to stpwlev was correct. If |
| * the driver goes into a reset loop and hangs the system, then you need the |
| * other setting for this variable. If neither setting lets the machine |
| * boot then you have definite termination problems that may not be fixable. |
| */ |
| static int aic7xxx_stpwlev = -1; |
| /* |
| * Set this to non-0 in order to force the driver to panic the kernel |
| * and print out debugging info on a SCSI abort or reset cycle. |
| */ |
| static int aic7xxx_panic_on_abort = 0; |
| /* |
| * 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, it can generate tons of false error messages. |
| * It's included in the driver for completeness. |
| * 0 = Shut off PCI parity check |
| * -1 = Normal polarity pci parity checking |
| * 1 = reverse polarity pci parity checking |
| * |
| * NOTE: you can't actually pass -1 on the lilo prompt. So, to set this |
| * variable to -1 you would actually want to simply pass the variable |
| * name without a number. That will invert the 0 which will result in |
| * -1. |
| */ |
| static int aic7xxx_pci_parity = 0; |
| /* |
| * Set this to any non-0 value to cause us to dump the contents of all |
| * the card's registers in a hex dump format tailored to each model of |
| * controller. |
| * |
| * NOTE: THE CONTROLLER IS LEFT IN AN UNUSEABLE STATE BY THIS OPTION. |
| * YOU CANNOT BOOT UP WITH THIS OPTION, IT IS FOR DEBUGGING PURPOSES |
| * ONLY |
| */ |
| static int aic7xxx_dump_card = 0; |
| /* |
| * Set this to a non-0 value to make us dump out the 32 bit instruction |
| * registers on the card after completing the sequencer download. This |
| * allows the actual sequencer download to be verified. It is possible |
| * to use this option and still boot up and run your system. This is |
| * only intended for debugging purposes. |
| */ |
| static int aic7xxx_dump_sequencer = 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 :) |
| */ |
| static int aic7xxx_no_probe = 0; |
| /* |
| * On some machines, enabling the external SCB RAM isn't reliable yet. I |
| * haven't had time to make test patches for things like changing the |
| * timing mode on that external RAM either. Some of those changes may |
| * fix the problem. Until then though, we default to external SCB RAM |
| * off and give a command line option to enable it. |
| */ |
| static int aic7xxx_scbram = 0; |
| /* |
| * 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 64ms because it's fast. Some old SCSI-I devices need a |
| * longer time. The final value has to be left shifted by 3, hence 0x10 |
| * is the final value. |
| */ |
| static int aic7xxx_seltime = 0x10; |
| /* |
| * So that insmod can find the variable and make it point to something |
| */ |
| #ifdef MODULE |
| static char * aic7xxx = NULL; |
| module_param(aic7xxx, charp, 0); |
| #endif |
| |
| #define VERBOSE_NORMAL 0x0000 |
| #define VERBOSE_NEGOTIATION 0x0001 |
| #define VERBOSE_SEQINT 0x0002 |
| #define VERBOSE_SCSIINT 0x0004 |
| #define VERBOSE_PROBE 0x0008 |
| #define VERBOSE_PROBE2 0x0010 |
| #define VERBOSE_NEGOTIATION2 0x0020 |
| #define VERBOSE_MINOR_ERROR 0x0040 |
| #define VERBOSE_TRACING 0x0080 |
| #define VERBOSE_ABORT 0x0f00 |
| #define VERBOSE_ABORT_MID 0x0100 |
| #define VERBOSE_ABORT_FIND 0x0200 |
| #define VERBOSE_ABORT_PROCESS 0x0400 |
| #define VERBOSE_ABORT_RETURN 0x0800 |
| #define VERBOSE_RESET 0xf000 |
| #define VERBOSE_RESET_MID 0x1000 |
| #define VERBOSE_RESET_FIND 0x2000 |
| #define VERBOSE_RESET_PROCESS 0x4000 |
| #define VERBOSE_RESET_RETURN 0x8000 |
| static int aic7xxx_verbose = VERBOSE_NORMAL | VERBOSE_NEGOTIATION | |
| VERBOSE_PROBE; /* verbose messages */ |
| |
| |
| /**************************************************************************** |
| * |
| * We're going to start putting in function declarations so that order of |
| * functions is no longer important. As needed, they are added here. |
| * |
| ***************************************************************************/ |
| |
| static int aic7xxx_release(struct Scsi_Host *host); |
| static void aic7xxx_set_syncrate(struct aic7xxx_host *p, |
| struct aic7xxx_syncrate *syncrate, int target, int channel, |
| unsigned int period, unsigned int offset, unsigned char options, |
| unsigned int type, struct aic_dev_data *aic_dev); |
| static void aic7xxx_set_width(struct aic7xxx_host *p, int target, int channel, |
| int lun, unsigned int width, unsigned int type, |
| struct aic_dev_data *aic_dev); |
| static void aic7xxx_panic_abort(struct aic7xxx_host *p, struct scsi_cmnd *cmd); |
| static void aic7xxx_print_card(struct aic7xxx_host *p); |
| static void aic7xxx_print_scratch_ram(struct aic7xxx_host *p); |
| static void aic7xxx_print_sequencer(struct aic7xxx_host *p, int downloaded); |
| #ifdef AIC7XXX_VERBOSE_DEBUGGING |
| static void aic7xxx_check_scbs(struct aic7xxx_host *p, char *buffer); |
| #endif |
| |
| /**************************************************************************** |
| * |
| * These functions are now used. They happen to be wrapped in useless |
| * inb/outb port read/writes around the real reads and writes because it |
| * seems that certain very fast CPUs have a problem dealing with us when |
| * going at full speed. |
| * |
| ***************************************************************************/ |
| |
| static unsigned char |
| aic_inb(struct aic7xxx_host *p, long port) |
| { |
| #ifdef MMAPIO |
| unsigned char x; |
| if(p->maddr) |
| { |
| x = readb(p->maddr + port); |
| } |
| else |
| { |
| x = inb(p->base + port); |
| } |
| return(x); |
| #else |
| return(inb(p->base + port)); |
| #endif |
| } |
| |
| static void |
| aic_outb(struct aic7xxx_host *p, unsigned char val, long port) |
| { |
| #ifdef MMAPIO |
| if(p->maddr) |
| { |
| writeb(val, p->maddr + port); |
| mb(); /* locked operation in order to force CPU ordering */ |
| readb(p->maddr + HCNTRL); /* dummy read to flush the PCI write */ |
| } |
| else |
| { |
| outb(val, p->base + port); |
| mb(); /* locked operation in order to force CPU ordering */ |
| } |
| #else |
| outb(val, p->base + port); |
| mb(); /* locked operation in order to force CPU ordering */ |
| #endif |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_setup |
| * |
| * Description: |
| * Handle Linux boot parameters. This routine allows for assigning a value |
| * to a parameter with a ':' between the parameter and the value. |
| * ie. aic7xxx=unpause:0x0A,extended |
| *-F*************************************************************************/ |
| static int |
| aic7xxx_setup(char *s) |
| { |
| int i, n; |
| char *p; |
| char *end; |
| |
| static struct { |
| const char *name; |
| unsigned int *flag; |
| } options[] = { |
| { "extended", &aic7xxx_extended }, |
| { "no_reset", &aic7xxx_no_reset }, |
| { "irq_trigger", &aic7xxx_irq_trigger }, |
| { "verbose", &aic7xxx_verbose }, |
| { "reverse_scan",&aic7xxx_reverse_scan }, |
| { "override_term", &aic7xxx_override_term }, |
| { "stpwlev", &aic7xxx_stpwlev }, |
| { "no_probe", &aic7xxx_no_probe }, |
| { "panic_on_abort", &aic7xxx_panic_on_abort }, |
| { "pci_parity", &aic7xxx_pci_parity }, |
| { "dump_card", &aic7xxx_dump_card }, |
| { "dump_sequencer", &aic7xxx_dump_sequencer }, |
| { "default_queue_depth", &aic7xxx_default_queue_depth }, |
| { "scbram", &aic7xxx_scbram }, |
| { "seltime", &aic7xxx_seltime }, |
| { "tag_info", NULL } |
| }; |
| |
| end = strchr(s, '\0'); |
| |
| while ((p = strsep(&s, ",.")) != NULL) |
| { |
| for (i = 0; i < ARRAY_SIZE(options); i++) |
| { |
| n = strlen(options[i].name); |
| if (!strncmp(options[i].name, p, n)) |
| { |
| if (!strncmp(p, "tag_info", n)) |
| { |
| if (p[n] == ':') |
| { |
| char *base; |
| char *tok, *tok_end, *tok_end2; |
| char tok_list[] = { '.', ',', '{', '}', '\0' }; |
| int i, instance = -1, device = -1; |
| unsigned char done = FALSE; |
| |
| base = p; |
| tok = base + n + 1; /* Forward us just past the ':' */ |
| tok_end = strchr(tok, '\0'); |
| if (tok_end < end) |
| *tok_end = ','; |
| while(!done) |
| { |
| switch(*tok) |
| { |
| case '{': |
| if (instance == -1) |
| instance = 0; |
| else if (device == -1) |
| device = 0; |
| tok++; |
| break; |
| case '}': |
| if (device != -1) |
| device = -1; |
| else if (instance != -1) |
| instance = -1; |
| tok++; |
| break; |
| case ',': |
| case '.': |
| if (instance == -1) |
| done = TRUE; |
| else if (device >= 0) |
| device++; |
| else if (instance >= 0) |
| instance++; |
| if ( (device >= MAX_TARGETS) || |
| (instance >= ARRAY_SIZE(aic7xxx_tag_info)) ) |
| done = TRUE; |
| tok++; |
| if (!done) |
| { |
| base = tok; |
| } |
| break; |
| case '\0': |
| done = TRUE; |
| break; |
| default: |
| done = TRUE; |
| tok_end = strchr(tok, '\0'); |
| for(i=0; tok_list[i]; i++) |
| { |
| tok_end2 = strchr(tok, tok_list[i]); |
| if ( (tok_end2) && (tok_end2 < tok_end) ) |
| { |
| tok_end = tok_end2; |
| done = FALSE; |
| } |
| } |
| if ( (instance >= 0) && (device >= 0) && |
| (instance < ARRAY_SIZE(aic7xxx_tag_info)) && |
| (device < MAX_TARGETS) ) |
| aic7xxx_tag_info[instance].tag_commands[device] = |
| simple_strtoul(tok, NULL, 0) & 0xff; |
| tok = tok_end; |
| break; |
| } |
| } |
| while((p != base) && (p != NULL)) |
| p = strsep(&s, ",."); |
| } |
| } |
| else if (p[n] == ':') |
| { |
| *(options[i].flag) = simple_strtoul(p + n + 1, NULL, 0); |
| if(!strncmp(p, "seltime", n)) |
| { |
| *(options[i].flag) = (*(options[i].flag) % 4) << 3; |
| } |
| } |
| else if (!strncmp(p, "verbose", n)) |
| { |
| *(options[i].flag) = 0xff29; |
| } |
| else |
| { |
| *(options[i].flag) = ~(*(options[i].flag)); |
| if(!strncmp(p, "seltime", n)) |
| { |
| *(options[i].flag) = (*(options[i].flag) % 4) << 3; |
| } |
| } |
| } |
| } |
| } |
| return 1; |
| } |
| |
| __setup("aic7xxx=", aic7xxx_setup); |
| |
| /*+F************************************************************************* |
| * Function: |
| * pause_sequencer |
| * |
| * Description: |
| * Pause the sequencer and wait for it to actually stop - this |
| * is important since the sequencer can disable pausing for critical |
| * sections. |
| *-F*************************************************************************/ |
| static void |
| pause_sequencer(struct aic7xxx_host *p) |
| { |
| aic_outb(p, p->pause, HCNTRL); |
| while ((aic_inb(p, HCNTRL) & PAUSE) == 0) |
| { |
| ; |
| } |
| if(p->features & AHC_ULTRA2) |
| { |
| aic_inb(p, CCSCBCTL); |
| } |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * unpause_sequencer |
| * |
| * Description: |
| * Unpause the sequencer. Unremarkable, yet done often enough to |
| * warrant an easy way to do it. |
| *-F*************************************************************************/ |
| static void |
| unpause_sequencer(struct aic7xxx_host *p, int unpause_always) |
| { |
| if (unpause_always || |
| ( !(aic_inb(p, INTSTAT) & (SCSIINT | SEQINT | BRKADRINT)) && |
| !(p->flags & AHC_HANDLING_REQINITS) ) ) |
| { |
| aic_outb(p, p->unpause, HCNTRL); |
| } |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * restart_sequencer |
| * |
| * Description: |
| * Restart the sequencer program from address zero. This assumes |
| * that the sequencer is already paused. |
| *-F*************************************************************************/ |
| static void |
| restart_sequencer(struct aic7xxx_host *p) |
| { |
| aic_outb(p, 0, SEQADDR0); |
| aic_outb(p, 0, SEQADDR1); |
| aic_outb(p, FASTMODE, SEQCTL); |
| } |
| |
| /* |
| * We include the aic7xxx_seq.c file here so that the other defines have |
| * already been made, and so that it comes before the code that actually |
| * downloads the instructions (since we don't typically use function |
| * prototype, our code has to be ordered that way, it's a left-over from |
| * the original driver days.....I should fix it some time DL). |
| */ |
| #include "aic7xxx_old/aic7xxx_seq.c" |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_check_patch |
| * |
| * Description: |
| * See if the next patch to download should be downloaded. |
| *-F*************************************************************************/ |
| static int |
| aic7xxx_check_patch(struct aic7xxx_host *p, |
| struct sequencer_patch **start_patch, int start_instr, int *skip_addr) |
| { |
| struct sequencer_patch *cur_patch; |
| struct sequencer_patch *last_patch; |
| int num_patches; |
| |
| num_patches = ARRAY_SIZE(sequencer_patches); |
| last_patch = &sequencer_patches[num_patches]; |
| cur_patch = *start_patch; |
| |
| while ((cur_patch < last_patch) && (start_instr == cur_patch->begin)) |
| { |
| if (cur_patch->patch_func(p) == 0) |
| { |
| /* |
| * Start rejecting code. |
| */ |
| *skip_addr = start_instr + cur_patch->skip_instr; |
| cur_patch += cur_patch->skip_patch; |
| } |
| else |
| { |
| /* |
| * Found an OK patch. Advance the patch pointer to the next patch |
| * and wait for our instruction pointer to get here. |
| */ |
| cur_patch++; |
| } |
| } |
| |
| *start_patch = cur_patch; |
| if (start_instr < *skip_addr) |
| /* |
| * Still skipping |
| */ |
| return (0); |
| return(1); |
| } |
| |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_download_instr |
| * |
| * Description: |
| * Find the next patch to download. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_download_instr(struct aic7xxx_host *p, int instrptr, |
| unsigned char *dconsts) |
| { |
| union ins_formats instr; |
| struct ins_format1 *fmt1_ins; |
| struct ins_format3 *fmt3_ins; |
| unsigned char opcode; |
| |
| instr = *(union ins_formats*) &seqprog[instrptr * 4]; |
| |
| instr.integer = le32_to_cpu(instr.integer); |
| |
| fmt1_ins = &instr.format1; |
| fmt3_ins = NULL; |
| |
| /* Pull the opcode */ |
| opcode = instr.format1.opcode; |
| switch (opcode) |
| { |
| case AIC_OP_JMP: |
| case AIC_OP_JC: |
| case AIC_OP_JNC: |
| case AIC_OP_CALL: |
| case AIC_OP_JNE: |
| case AIC_OP_JNZ: |
| case AIC_OP_JE: |
| case AIC_OP_JZ: |
| { |
| struct sequencer_patch *cur_patch; |
| int address_offset; |
| unsigned int address; |
| int skip_addr; |
| int i; |
| |
| fmt3_ins = &instr.format3; |
| address_offset = 0; |
| address = fmt3_ins->address; |
| cur_patch = sequencer_patches; |
| skip_addr = 0; |
| |
| for (i = 0; i < address;) |
| { |
| aic7xxx_check_patch(p, &cur_patch, i, &skip_addr); |
| if (skip_addr > i) |
| { |
| int end_addr; |
| |
| end_addr = min_t(int, address, skip_addr); |
| address_offset += end_addr - i; |
| i = skip_addr; |
| } |
| else |
| { |
| i++; |
| } |
| } |
| address -= address_offset; |
| fmt3_ins->address = address; |
| /* Fall Through to the next code section */ |
| } |
| case AIC_OP_OR: |
| case AIC_OP_AND: |
| case AIC_OP_XOR: |
| case AIC_OP_ADD: |
| case AIC_OP_ADC: |
| case AIC_OP_BMOV: |
| if (fmt1_ins->parity != 0) |
| { |
| fmt1_ins->immediate = dconsts[fmt1_ins->immediate]; |
| } |
| fmt1_ins->parity = 0; |
| /* Fall Through to the next code section */ |
| case AIC_OP_ROL: |
| if ((p->features & AHC_ULTRA2) != 0) |
| { |
| int i, count; |
| |
| /* Calculate odd parity for the instruction */ |
| for ( i=0, count=0; i < 31; i++) |
| { |
| unsigned int mask; |
| |
| mask = 0x01 << i; |
| if ((instr.integer & mask) != 0) |
| count++; |
| } |
| if (!(count & 0x01)) |
| instr.format1.parity = 1; |
| } |
| else |
| { |
| if (fmt3_ins != NULL) |
| { |
| instr.integer = fmt3_ins->immediate | |
| (fmt3_ins->source << 8) | |
| (fmt3_ins->address << 16) | |
| (fmt3_ins->opcode << 25); |
| } |
| else |
| { |
| instr.integer = fmt1_ins->immediate | |
| (fmt1_ins->source << 8) | |
| (fmt1_ins->destination << 16) | |
| (fmt1_ins->ret << 24) | |
| (fmt1_ins->opcode << 25); |
| } |
| } |
| aic_outb(p, (instr.integer & 0xff), SEQRAM); |
| aic_outb(p, ((instr.integer >> 8) & 0xff), SEQRAM); |
| aic_outb(p, ((instr.integer >> 16) & 0xff), SEQRAM); |
| aic_outb(p, ((instr.integer >> 24) & 0xff), SEQRAM); |
| udelay(10); |
| break; |
| |
| default: |
| panic("aic7xxx: Unknown opcode encountered in sequencer program."); |
| break; |
| } |
| } |
| |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_loadseq |
| * |
| * Description: |
| * Load the sequencer code into the controller memory. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_loadseq(struct aic7xxx_host *p) |
| { |
| struct sequencer_patch *cur_patch; |
| int i; |
| int downloaded; |
| int skip_addr; |
| unsigned char download_consts[4] = {0, 0, 0, 0}; |
| |
| if (aic7xxx_verbose & VERBOSE_PROBE) |
| { |
| printk(KERN_INFO "(scsi%d) Downloading sequencer code...", p->host_no); |
| } |
| #if 0 |
| download_consts[TMODE_NUMCMDS] = p->num_targetcmds; |
| #endif |
| download_consts[TMODE_NUMCMDS] = 0; |
| cur_patch = &sequencer_patches[0]; |
| downloaded = 0; |
| skip_addr = 0; |
| |
| aic_outb(p, PERRORDIS|LOADRAM|FAILDIS|FASTMODE, SEQCTL); |
| aic_outb(p, 0, SEQADDR0); |
| aic_outb(p, 0, SEQADDR1); |
| |
| for (i = 0; i < sizeof(seqprog) / 4; i++) |
| { |
| if (aic7xxx_check_patch(p, &cur_patch, i, &skip_addr) == 0) |
| { |
| /* Skip this instruction for this configuration. */ |
| continue; |
| } |
| aic7xxx_download_instr(p, i, &download_consts[0]); |
| downloaded++; |
| } |
| |
| aic_outb(p, 0, SEQADDR0); |
| aic_outb(p, 0, SEQADDR1); |
| aic_outb(p, FASTMODE | FAILDIS, SEQCTL); |
| unpause_sequencer(p, TRUE); |
| mdelay(1); |
| pause_sequencer(p); |
| aic_outb(p, FASTMODE, SEQCTL); |
| if (aic7xxx_verbose & VERBOSE_PROBE) |
| { |
| printk(" %d instructions downloaded\n", downloaded); |
| } |
| if (aic7xxx_dump_sequencer) |
| aic7xxx_print_sequencer(p, downloaded); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_print_sequencer |
| * |
| * Description: |
| * Print the contents of the sequencer memory to the screen. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_print_sequencer(struct aic7xxx_host *p, int downloaded) |
| { |
| int i, k, temp; |
| |
| aic_outb(p, PERRORDIS|LOADRAM|FAILDIS|FASTMODE, SEQCTL); |
| aic_outb(p, 0, SEQADDR0); |
| aic_outb(p, 0, SEQADDR1); |
| |
| k = 0; |
| for (i=0; i < downloaded; i++) |
| { |
| if ( k == 0 ) |
| printk("%03x: ", i); |
| temp = aic_inb(p, SEQRAM); |
| temp |= (aic_inb(p, SEQRAM) << 8); |
| temp |= (aic_inb(p, SEQRAM) << 16); |
| temp |= (aic_inb(p, SEQRAM) << 24); |
| printk("%08x", temp); |
| if ( ++k == 8 ) |
| { |
| printk("\n"); |
| k = 0; |
| } |
| else |
| printk(" "); |
| } |
| aic_outb(p, 0, SEQADDR0); |
| aic_outb(p, 0, SEQADDR1); |
| aic_outb(p, FASTMODE | FAILDIS, SEQCTL); |
| unpause_sequencer(p, TRUE); |
| mdelay(1); |
| pause_sequencer(p); |
| aic_outb(p, FASTMODE, SEQCTL); |
| printk("\n"); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_info |
| * |
| * Description: |
| * Return a string describing the driver. |
| *-F*************************************************************************/ |
| static const char * |
| aic7xxx_info(struct Scsi_Host *dooh) |
| { |
| static char buffer[256]; |
| char *bp; |
| struct aic7xxx_host *p; |
| |
| bp = &buffer[0]; |
| p = (struct aic7xxx_host *)dooh->hostdata; |
| memset(bp, 0, sizeof(buffer)); |
| strcpy(bp, "Adaptec AHA274x/284x/294x (EISA/VLB/PCI-Fast SCSI) "); |
| strcat(bp, AIC7XXX_C_VERSION); |
| strcat(bp, "/"); |
| strcat(bp, AIC7XXX_H_VERSION); |
| strcat(bp, "\n"); |
| strcat(bp, " <"); |
| strcat(bp, board_names[p->board_name_index]); |
| strcat(bp, ">"); |
| |
| return(bp); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_find_syncrate |
| * |
| * Description: |
| * Look up the valid period to SCSIRATE conversion in our table |
| *-F*************************************************************************/ |
| static struct aic7xxx_syncrate * |
| aic7xxx_find_syncrate(struct aic7xxx_host *p, unsigned int *period, |
| unsigned int maxsync, unsigned char *options) |
| { |
| struct aic7xxx_syncrate *syncrate; |
| int done = FALSE; |
| |
| switch(*options) |
| { |
| case MSG_EXT_PPR_OPTION_DT_CRC: |
| case MSG_EXT_PPR_OPTION_DT_UNITS: |
| if(!(p->features & AHC_ULTRA3)) |
| { |
| *options = 0; |
| maxsync = max_t(unsigned int, maxsync, AHC_SYNCRATE_ULTRA2); |
| } |
| break; |
| case MSG_EXT_PPR_OPTION_DT_CRC_QUICK: |
| case MSG_EXT_PPR_OPTION_DT_UNITS_QUICK: |
| if(!(p->features & AHC_ULTRA3)) |
| { |
| *options = 0; |
| maxsync = max_t(unsigned int, maxsync, AHC_SYNCRATE_ULTRA2); |
| } |
| else |
| { |
| /* |
| * we don't support the Quick Arbitration variants of dual edge |
| * clocking. As it turns out, we want to send back the |
| * same basic option, but without the QA attribute. |
| * We know that we are responding because we would never set |
| * these options ourself, we would only respond to them. |
| */ |
| switch(*options) |
| { |
| case MSG_EXT_PPR_OPTION_DT_CRC_QUICK: |
| *options = MSG_EXT_PPR_OPTION_DT_CRC; |
| break; |
| case MSG_EXT_PPR_OPTION_DT_UNITS_QUICK: |
| *options = MSG_EXT_PPR_OPTION_DT_UNITS; |
| break; |
| } |
| } |
| break; |
| default: |
| *options = 0; |
| maxsync = max_t(unsigned int, maxsync, AHC_SYNCRATE_ULTRA2); |
| break; |
| } |
| syncrate = &aic7xxx_syncrates[maxsync]; |
| while ( (syncrate->rate[0] != NULL) && |
| (!(p->features & AHC_ULTRA2) || syncrate->sxfr_ultra2) ) |
| { |
| if (*period <= syncrate->period) |
| { |
| switch(*options) |
| { |
| case MSG_EXT_PPR_OPTION_DT_CRC: |
| case MSG_EXT_PPR_OPTION_DT_UNITS: |
| if(!(syncrate->sxfr_ultra2 & AHC_SYNCRATE_CRC)) |
| { |
| done = TRUE; |
| /* |
| * oops, we went too low for the CRC/DualEdge signalling, so |
| * clear the options byte |
| */ |
| *options = 0; |
| /* |
| * We'll be sending a reply to this packet to set the options |
| * properly, so unilaterally set the period as well. |
| */ |
| *period = syncrate->period; |
| } |
| else |
| { |
| done = TRUE; |
| if(syncrate == &aic7xxx_syncrates[maxsync]) |
| { |
| *period = syncrate->period; |
| } |
| } |
| break; |
| default: |
| if(!(syncrate->sxfr_ultra2 & AHC_SYNCRATE_CRC)) |
| { |
| done = TRUE; |
| if(syncrate == &aic7xxx_syncrates[maxsync]) |
| { |
| *period = syncrate->period; |
| } |
| } |
| break; |
| } |
| if(done) |
| { |
| break; |
| } |
| } |
| syncrate++; |
| } |
| if ( (*period == 0) || (syncrate->rate[0] == NULL) || |
| ((p->features & AHC_ULTRA2) && (syncrate->sxfr_ultra2 == 0)) ) |
| { |
| /* |
| * Use async transfers for this target |
| */ |
| *options = 0; |
| *period = 255; |
| syncrate = NULL; |
| } |
| return (syncrate); |
| } |
| |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_find_period |
| * |
| * Description: |
| * Look up the valid SCSIRATE to period conversion in our table |
| *-F*************************************************************************/ |
| static unsigned int |
| aic7xxx_find_period(struct aic7xxx_host *p, unsigned int scsirate, |
| unsigned int maxsync) |
| { |
| struct aic7xxx_syncrate *syncrate; |
| |
| if (p->features & AHC_ULTRA2) |
| { |
| scsirate &= SXFR_ULTRA2; |
| } |
| else |
| { |
| scsirate &= SXFR; |
| } |
| |
| syncrate = &aic7xxx_syncrates[maxsync]; |
| while (syncrate->rate[0] != NULL) |
| { |
| if (p->features & AHC_ULTRA2) |
| { |
| if (syncrate->sxfr_ultra2 == 0) |
| break; |
| else if (scsirate == syncrate->sxfr_ultra2) |
| return (syncrate->period); |
| else if (scsirate == (syncrate->sxfr_ultra2 & ~AHC_SYNCRATE_CRC)) |
| return (syncrate->period); |
| } |
| else if (scsirate == (syncrate->sxfr & ~ULTRA_SXFR)) |
| { |
| return (syncrate->period); |
| } |
| syncrate++; |
| } |
| return (0); /* async */ |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_validate_offset |
| * |
| * Description: |
| * Set a valid offset value for a particular card in use and transfer |
| * settings in use. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_validate_offset(struct aic7xxx_host *p, |
| struct aic7xxx_syncrate *syncrate, unsigned int *offset, int wide) |
| { |
| unsigned int maxoffset; |
| |
| /* Limit offset to what the card (and device) can do */ |
| if (syncrate == NULL) |
| { |
| maxoffset = 0; |
| } |
| else if (p->features & AHC_ULTRA2) |
| { |
| maxoffset = MAX_OFFSET_ULTRA2; |
| } |
| else |
| { |
| if (wide) |
| maxoffset = MAX_OFFSET_16BIT; |
| else |
| maxoffset = MAX_OFFSET_8BIT; |
| } |
| *offset = min(*offset, maxoffset); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_set_syncrate |
| * |
| * Description: |
| * Set the actual syncrate down in the card and in our host structs |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_set_syncrate(struct aic7xxx_host *p, struct aic7xxx_syncrate *syncrate, |
| int target, int channel, unsigned int period, unsigned int offset, |
| unsigned char options, unsigned int type, struct aic_dev_data *aic_dev) |
| { |
| unsigned char tindex; |
| unsigned short target_mask; |
| unsigned char lun, old_options; |
| unsigned int old_period, old_offset; |
| |
| tindex = target | (channel << 3); |
| target_mask = 0x01 << tindex; |
| lun = aic_inb(p, SCB_TCL) & 0x07; |
| |
| if (syncrate == NULL) |
| { |
| period = 0; |
| offset = 0; |
| } |
| |
| old_period = aic_dev->cur.period; |
| old_offset = aic_dev->cur.offset; |
| old_options = aic_dev->cur.options; |
| |
| |
| if (type & AHC_TRANS_CUR) |
| { |
| unsigned int scsirate; |
| |
| scsirate = aic_inb(p, TARG_SCSIRATE + tindex); |
| if (p->features & AHC_ULTRA2) |
| { |
| scsirate &= ~SXFR_ULTRA2; |
| if (syncrate != NULL) |
| { |
| switch(options) |
| { |
| case MSG_EXT_PPR_OPTION_DT_UNITS: |
| /* |
| * mask off the CRC bit in the xfer settings |
| */ |
| scsirate |= (syncrate->sxfr_ultra2 & ~AHC_SYNCRATE_CRC); |
| break; |
| default: |
| scsirate |= syncrate->sxfr_ultra2; |
| break; |
| } |
| } |
| if (type & AHC_TRANS_ACTIVE) |
| { |
| aic_outb(p, offset, SCSIOFFSET); |
| } |
| aic_outb(p, offset, TARG_OFFSET + tindex); |
| } |
| else /* Not an Ultra2 controller */ |
| { |
| scsirate &= ~(SXFR|SOFS); |
| p->ultraenb &= ~target_mask; |
| if (syncrate != NULL) |
| { |
| if (syncrate->sxfr & ULTRA_SXFR) |
| { |
| p->ultraenb |= target_mask; |
| } |
| scsirate |= (syncrate->sxfr & SXFR); |
| scsirate |= (offset & SOFS); |
| } |
| if (type & AHC_TRANS_ACTIVE) |
| { |
| unsigned char sxfrctl0; |
| |
| sxfrctl0 = aic_inb(p, SXFRCTL0); |
| sxfrctl0 &= ~FAST20; |
| if (p->ultraenb & target_mask) |
| sxfrctl0 |= FAST20; |
| aic_outb(p, sxfrctl0, SXFRCTL0); |
| } |
| aic_outb(p, p->ultraenb & 0xff, ULTRA_ENB); |
| aic_outb(p, (p->ultraenb >> 8) & 0xff, ULTRA_ENB + 1 ); |
| } |
| if (type & AHC_TRANS_ACTIVE) |
| { |
| aic_outb(p, scsirate, SCSIRATE); |
| } |
| aic_outb(p, scsirate, TARG_SCSIRATE + tindex); |
| aic_dev->cur.period = period; |
| aic_dev->cur.offset = offset; |
| aic_dev->cur.options = options; |
| if ( !(type & AHC_TRANS_QUITE) && |
| (aic7xxx_verbose & VERBOSE_NEGOTIATION) && |
| (aic_dev->flags & DEVICE_PRINT_DTR) ) |
| { |
| if (offset) |
| { |
| int rate_mod = (scsirate & WIDEXFER) ? 1 : 0; |
| |
| printk(INFO_LEAD "Synchronous at %s Mbyte/sec, " |
| "offset %d.\n", p->host_no, channel, target, lun, |
| syncrate->rate[rate_mod], offset); |
| } |
| else |
| { |
| printk(INFO_LEAD "Using asynchronous transfers.\n", |
| p->host_no, channel, target, lun); |
| } |
| aic_dev->flags &= ~DEVICE_PRINT_DTR; |
| } |
| } |
| |
| if (type & AHC_TRANS_GOAL) |
| { |
| aic_dev->goal.period = period; |
| aic_dev->goal.offset = offset; |
| aic_dev->goal.options = options; |
| } |
| |
| if (type & AHC_TRANS_USER) |
| { |
| p->user[tindex].period = period; |
| p->user[tindex].offset = offset; |
| p->user[tindex].options = options; |
| } |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_set_width |
| * |
| * Description: |
| * Set the actual width down in the card and in our host structs |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_set_width(struct aic7xxx_host *p, int target, int channel, int lun, |
| unsigned int width, unsigned int type, struct aic_dev_data *aic_dev) |
| { |
| unsigned char tindex; |
| unsigned short target_mask; |
| unsigned int old_width; |
| |
| tindex = target | (channel << 3); |
| target_mask = 1 << tindex; |
| |
| old_width = aic_dev->cur.width; |
| |
| if (type & AHC_TRANS_CUR) |
| { |
| unsigned char scsirate; |
| |
| scsirate = aic_inb(p, TARG_SCSIRATE + tindex); |
| |
| scsirate &= ~WIDEXFER; |
| if (width == MSG_EXT_WDTR_BUS_16_BIT) |
| scsirate |= WIDEXFER; |
| |
| aic_outb(p, scsirate, TARG_SCSIRATE + tindex); |
| |
| if (type & AHC_TRANS_ACTIVE) |
| aic_outb(p, scsirate, SCSIRATE); |
| |
| aic_dev->cur.width = width; |
| |
| if ( !(type & AHC_TRANS_QUITE) && |
| (aic7xxx_verbose & VERBOSE_NEGOTIATION2) && |
| (aic_dev->flags & DEVICE_PRINT_DTR) ) |
| { |
| printk(INFO_LEAD "Using %s transfers\n", p->host_no, channel, target, |
| lun, (scsirate & WIDEXFER) ? "Wide(16bit)" : "Narrow(8bit)" ); |
| } |
| } |
| |
| if (type & AHC_TRANS_GOAL) |
| aic_dev->goal.width = width; |
| if (type & AHC_TRANS_USER) |
| p->user[tindex].width = width; |
| |
| if (aic_dev->goal.offset) |
| { |
| if (p->features & AHC_ULTRA2) |
| { |
| aic_dev->goal.offset = MAX_OFFSET_ULTRA2; |
| } |
| else if (width == MSG_EXT_WDTR_BUS_16_BIT) |
| { |
| aic_dev->goal.offset = MAX_OFFSET_16BIT; |
| } |
| else |
| { |
| aic_dev->goal.offset = MAX_OFFSET_8BIT; |
| } |
| } |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * scbq_init |
| * |
| * Description: |
| * SCB queue initialization. |
| * |
| *-F*************************************************************************/ |
| static void |
| scbq_init(volatile scb_queue_type *queue) |
| { |
| queue->head = NULL; |
| queue->tail = NULL; |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * scbq_insert_head |
| * |
| * Description: |
| * Add an SCB to the head of the list. |
| * |
| *-F*************************************************************************/ |
| static inline void |
| scbq_insert_head(volatile scb_queue_type *queue, struct aic7xxx_scb *scb) |
| { |
| scb->q_next = queue->head; |
| queue->head = scb; |
| if (queue->tail == NULL) /* If list was empty, update tail. */ |
| queue->tail = queue->head; |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * scbq_remove_head |
| * |
| * Description: |
| * Remove an SCB from the head of the list. |
| * |
| *-F*************************************************************************/ |
| static inline struct aic7xxx_scb * |
| scbq_remove_head(volatile scb_queue_type *queue) |
| { |
| struct aic7xxx_scb * scbp; |
| |
| scbp = queue->head; |
| if (queue->head != NULL) |
| queue->head = queue->head->q_next; |
| if (queue->head == NULL) /* If list is now empty, update tail. */ |
| queue->tail = NULL; |
| return(scbp); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * scbq_remove |
| * |
| * Description: |
| * Removes an SCB from the list. |
| * |
| *-F*************************************************************************/ |
| static inline void |
| scbq_remove(volatile scb_queue_type *queue, struct aic7xxx_scb *scb) |
| { |
| if (queue->head == scb) |
| { |
| /* At beginning of queue, remove from head. */ |
| scbq_remove_head(queue); |
| } |
| else |
| { |
| struct aic7xxx_scb *curscb = queue->head; |
| |
| /* |
| * Search until the next scb is the one we're looking for, or |
| * we run out of queue. |
| */ |
| while ((curscb != NULL) && (curscb->q_next != scb)) |
| { |
| curscb = curscb->q_next; |
| } |
| if (curscb != NULL) |
| { |
| /* Found it. */ |
| curscb->q_next = scb->q_next; |
| if (scb->q_next == NULL) |
| { |
| /* Update the tail when removing the tail. */ |
| queue->tail = curscb; |
| } |
| } |
| } |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * scbq_insert_tail |
| * |
| * Description: |
| * Add an SCB at the tail of the list. |
| * |
| *-F*************************************************************************/ |
| static inline void |
| scbq_insert_tail(volatile scb_queue_type *queue, struct aic7xxx_scb *scb) |
| { |
| scb->q_next = NULL; |
| if (queue->tail != NULL) /* Add the scb at the end of the list. */ |
| queue->tail->q_next = scb; |
| queue->tail = scb; /* Update the tail. */ |
| if (queue->head == NULL) /* If list was empty, update head. */ |
| queue->head = queue->tail; |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_match_scb |
| * |
| * Description: |
| * Checks to see if an scb matches the target/channel as specified. |
| * If target is ALL_TARGETS (-1), then we're looking for any device |
| * on the specified channel; this happens when a channel is going |
| * to be reset and all devices on that channel must be aborted. |
| *-F*************************************************************************/ |
| static int |
| aic7xxx_match_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, |
| int target, int channel, int lun, unsigned char tag) |
| { |
| int targ = (scb->hscb->target_channel_lun >> 4) & 0x0F; |
| int chan = (scb->hscb->target_channel_lun >> 3) & 0x01; |
| int slun = scb->hscb->target_channel_lun & 0x07; |
| int match; |
| |
| match = ((chan == channel) || (channel == ALL_CHANNELS)); |
| if (match != 0) |
| match = ((targ == target) || (target == ALL_TARGETS)); |
| if (match != 0) |
| match = ((lun == slun) || (lun == ALL_LUNS)); |
| if (match != 0) |
| match = ((tag == scb->hscb->tag) || (tag == SCB_LIST_NULL)); |
| |
| return (match); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_add_curscb_to_free_list |
| * |
| * Description: |
| * Adds the current scb (in SCBPTR) to the list of free SCBs. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_add_curscb_to_free_list(struct aic7xxx_host *p) |
| { |
| /* |
| * Invalidate the tag so that aic7xxx_find_scb doesn't think |
| * it's active |
| */ |
| aic_outb(p, SCB_LIST_NULL, SCB_TAG); |
| aic_outb(p, 0, SCB_CONTROL); |
| |
| aic_outb(p, aic_inb(p, FREE_SCBH), SCB_NEXT); |
| aic_outb(p, aic_inb(p, SCBPTR), FREE_SCBH); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_rem_scb_from_disc_list |
| * |
| * Description: |
| * Removes the current SCB from the disconnected list and adds it |
| * to the free list. |
| *-F*************************************************************************/ |
| static unsigned char |
| aic7xxx_rem_scb_from_disc_list(struct aic7xxx_host *p, unsigned char scbptr, |
| unsigned char prev) |
| { |
| unsigned char next; |
| |
| aic_outb(p, scbptr, SCBPTR); |
| next = aic_inb(p, SCB_NEXT); |
| aic7xxx_add_curscb_to_free_list(p); |
| |
| if (prev != SCB_LIST_NULL) |
| { |
| aic_outb(p, prev, SCBPTR); |
| aic_outb(p, next, SCB_NEXT); |
| } |
| else |
| { |
| aic_outb(p, next, DISCONNECTED_SCBH); |
| } |
| |
| return next; |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_busy_target |
| * |
| * Description: |
| * Set the specified target busy. |
| *-F*************************************************************************/ |
| static inline void |
| aic7xxx_busy_target(struct aic7xxx_host *p, struct aic7xxx_scb *scb) |
| { |
| p->untagged_scbs[scb->hscb->target_channel_lun] = scb->hscb->tag; |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_index_busy_target |
| * |
| * Description: |
| * Returns the index of the busy target, and optionally sets the |
| * target inactive. |
| *-F*************************************************************************/ |
| static inline unsigned char |
| aic7xxx_index_busy_target(struct aic7xxx_host *p, unsigned char tcl, |
| int unbusy) |
| { |
| unsigned char busy_scbid; |
| |
| busy_scbid = p->untagged_scbs[tcl]; |
| if (unbusy) |
| { |
| p->untagged_scbs[tcl] = SCB_LIST_NULL; |
| } |
| return (busy_scbid); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_find_scb |
| * |
| * Description: |
| * Look through the SCB array of the card and attempt to find the |
| * hardware SCB that corresponds to the passed in SCB. Return |
| * SCB_LIST_NULL if unsuccessful. This routine assumes that the |
| * card is already paused. |
| *-F*************************************************************************/ |
| static unsigned char |
| aic7xxx_find_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) |
| { |
| unsigned char saved_scbptr; |
| unsigned char curindex; |
| |
| saved_scbptr = aic_inb(p, SCBPTR); |
| curindex = 0; |
| for (curindex = 0; curindex < p->scb_data->maxhscbs; curindex++) |
| { |
| aic_outb(p, curindex, SCBPTR); |
| if (aic_inb(p, SCB_TAG) == scb->hscb->tag) |
| { |
| break; |
| } |
| } |
| aic_outb(p, saved_scbptr, SCBPTR); |
| if (curindex >= p->scb_data->maxhscbs) |
| { |
| curindex = SCB_LIST_NULL; |
| } |
| |
| return (curindex); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_allocate_scb |
| * |
| * Description: |
| * Get an SCB from the free list or by allocating a new one. |
| *-F*************************************************************************/ |
| static int |
| aic7xxx_allocate_scb(struct aic7xxx_host *p) |
| { |
| struct aic7xxx_scb *scbp = NULL; |
| int scb_size = (sizeof (struct hw_scatterlist) * AIC7XXX_MAX_SG) + 12 + 6; |
| int i; |
| int step = PAGE_SIZE / 1024; |
| unsigned long scb_count = 0; |
| struct hw_scatterlist *hsgp; |
| struct aic7xxx_scb *scb_ap; |
| struct aic7xxx_scb_dma *scb_dma; |
| unsigned char *bufs; |
| |
| if (p->scb_data->numscbs < p->scb_data->maxscbs) |
| { |
| /* |
| * Calculate the optimal number of SCBs to allocate. |
| * |
| * NOTE: This formula works because the sizeof(sg_array) is always |
| * 1024. Therefore, scb_size * i would always be > PAGE_SIZE * |
| * (i/step). The (i-1) allows the left hand side of the equation |
| * to grow into the right hand side to a point of near perfect |
| * efficiency since scb_size * (i -1) is growing slightly faster |
| * than the right hand side. If the number of SG array elements |
| * is changed, this function may not be near so efficient any more. |
| * |
| * Since the DMA'able buffers are now allocated in a separate |
| * chunk this algorithm has been modified to match. The '12' |
| * and '6' factors in scb_size are for the DMA'able command byte |
| * and sensebuffers respectively. -DaveM |
| */ |
| for ( i=step;; i *= 2 ) |
| { |
| if ( (scb_size * (i-1)) >= ( (PAGE_SIZE * (i/step)) - 64 ) ) |
| { |
| i /= 2; |
| break; |
| } |
| } |
| scb_count = min( (i-1), p->scb_data->maxscbs - p->scb_data->numscbs); |
| scb_ap = kmalloc(sizeof (struct aic7xxx_scb) * scb_count |
| + sizeof(struct aic7xxx_scb_dma), GFP_ATOMIC); |
| if (scb_ap == NULL) |
| return(0); |
| scb_dma = (struct aic7xxx_scb_dma *)&scb_ap[scb_count]; |
| hsgp = (struct hw_scatterlist *) |
| pci_alloc_consistent(p->pdev, scb_size * scb_count, |
| &scb_dma->dma_address); |
| if (hsgp == NULL) |
| { |
| kfree(scb_ap); |
| return(0); |
| } |
| bufs = (unsigned char *)&hsgp[scb_count * AIC7XXX_MAX_SG]; |
| #ifdef AIC7XXX_VERBOSE_DEBUGGING |
| if (aic7xxx_verbose > 0xffff) |
| { |
| if (p->scb_data->numscbs == 0) |
| printk(INFO_LEAD "Allocating initial %ld SCB structures.\n", |
| p->host_no, -1, -1, -1, scb_count); |
| else |
| printk(INFO_LEAD "Allocating %ld additional SCB structures.\n", |
| p->host_no, -1, -1, -1, scb_count); |
| } |
| #endif |
| memset(scb_ap, 0, sizeof (struct aic7xxx_scb) * scb_count); |
| scb_dma->dma_offset = (unsigned long)scb_dma->dma_address |
| - (unsigned long)hsgp; |
| scb_dma->dma_len = scb_size * scb_count; |
| for (i=0; i < scb_count; i++) |
| { |
| scbp = &scb_ap[i]; |
| scbp->hscb = &p->scb_data->hscbs[p->scb_data->numscbs]; |
| scbp->sg_list = &hsgp[i * AIC7XXX_MAX_SG]; |
| scbp->sense_cmd = bufs; |
| scbp->cmnd = bufs + 6; |
| bufs += 12 + 6; |
| scbp->scb_dma = scb_dma; |
| memset(scbp->hscb, 0, sizeof(struct aic7xxx_hwscb)); |
| scbp->hscb->tag = p->scb_data->numscbs; |
| /* |
| * Place in the scb array; never is removed |
| */ |
| p->scb_data->scb_array[p->scb_data->numscbs++] = scbp; |
| scbq_insert_tail(&p->scb_data->free_scbs, scbp); |
| } |
| scbp->kmalloc_ptr = scb_ap; |
| } |
| return(scb_count); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_queue_cmd_complete |
| * |
| * Description: |
| * Due to race conditions present in the SCSI subsystem, it is easier |
| * to queue completed commands, then call scsi_done() on them when |
| * we're finished. This function queues the completed commands. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_queue_cmd_complete(struct aic7xxx_host *p, struct scsi_cmnd *cmd) |
| { |
| aic7xxx_position(cmd) = SCB_LIST_NULL; |
| cmd->host_scribble = (char *)p->completeq.head; |
| p->completeq.head = cmd; |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_done_cmds_complete |
| * |
| * Description: |
| * Process the completed command queue. |
| *-F*************************************************************************/ |
| static void aic7xxx_done_cmds_complete(struct aic7xxx_host *p) |
| { |
| struct scsi_cmnd *cmd; |
| |
| while (p->completeq.head != NULL) { |
| cmd = p->completeq.head; |
| p->completeq.head = (struct scsi_cmnd *) cmd->host_scribble; |
| cmd->host_scribble = NULL; |
| cmd->scsi_done(cmd); |
| } |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_free_scb |
| * |
| * Description: |
| * Free the scb and insert into the free scb list. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_free_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) |
| { |
| |
| scb->flags = SCB_FREE; |
| scb->cmd = NULL; |
| scb->sg_count = 0; |
| scb->sg_length = 0; |
| scb->tag_action = 0; |
| scb->hscb->control = 0; |
| scb->hscb->target_status = 0; |
| scb->hscb->target_channel_lun = SCB_LIST_NULL; |
| |
| scbq_insert_head(&p->scb_data->free_scbs, scb); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_done |
| * |
| * Description: |
| * Calls the higher level scsi done function and frees the scb. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_done(struct aic7xxx_host *p, struct aic7xxx_scb *scb) |
| { |
| struct scsi_cmnd *cmd = scb->cmd; |
| struct aic_dev_data *aic_dev = cmd->device->hostdata; |
| int tindex = TARGET_INDEX(cmd); |
| struct aic7xxx_scb *scbp; |
| unsigned char queue_depth; |
| |
| scsi_dma_unmap(cmd); |
| |
| if (scb->flags & SCB_SENSE) |
| { |
| pci_unmap_single(p->pdev, |
| le32_to_cpu(scb->sg_list[0].address), |
| SCSI_SENSE_BUFFERSIZE, |
| PCI_DMA_FROMDEVICE); |
| } |
| if (scb->flags & SCB_RECOVERY_SCB) |
| { |
| p->flags &= ~AHC_ABORT_PENDING; |
| } |
| if (scb->flags & (SCB_RESET|SCB_ABORT)) |
| { |
| cmd->result |= (DID_RESET << 16); |
| } |
| |
| if ((scb->flags & SCB_MSGOUT_BITS) != 0) |
| { |
| unsigned short mask; |
| int message_error = FALSE; |
| |
| mask = 0x01 << tindex; |
| |
| /* |
| * Check to see if we get an invalid message or a message error |
| * after failing to negotiate a wide or sync transfer message. |
| */ |
| if ((scb->flags & SCB_SENSE) && |
| ((scb->cmd->sense_buffer[12] == 0x43) || /* INVALID_MESSAGE */ |
| (scb->cmd->sense_buffer[12] == 0x49))) /* MESSAGE_ERROR */ |
| { |
| message_error = TRUE; |
| } |
| |
| if (scb->flags & SCB_MSGOUT_WDTR) |
| { |
| if (message_error) |
| { |
| if ( (aic7xxx_verbose & VERBOSE_NEGOTIATION2) && |
| (aic_dev->flags & DEVICE_PRINT_DTR) ) |
| { |
| printk(INFO_LEAD "Device failed to complete Wide Negotiation " |
| "processing and\n", p->host_no, CTL_OF_SCB(scb)); |
| printk(INFO_LEAD "returned a sense error code for invalid message, " |
| "disabling future\n", p->host_no, CTL_OF_SCB(scb)); |
| printk(INFO_LEAD "Wide negotiation to this device.\n", p->host_no, |
| CTL_OF_SCB(scb)); |
| } |
| aic_dev->needwdtr = aic_dev->needwdtr_copy = 0; |
| } |
| } |
| if (scb->flags & SCB_MSGOUT_SDTR) |
| { |
| if (message_error) |
| { |
| if ( (aic7xxx_verbose & VERBOSE_NEGOTIATION2) && |
| (aic_dev->flags & DEVICE_PRINT_DTR) ) |
| { |
| printk(INFO_LEAD "Device failed to complete Sync Negotiation " |
| "processing and\n", p->host_no, CTL_OF_SCB(scb)); |
| printk(INFO_LEAD "returned a sense error code for invalid message, " |
| "disabling future\n", p->host_no, CTL_OF_SCB(scb)); |
| printk(INFO_LEAD "Sync negotiation to this device.\n", p->host_no, |
| CTL_OF_SCB(scb)); |
| aic_dev->flags &= ~DEVICE_PRINT_DTR; |
| } |
| aic_dev->needsdtr = aic_dev->needsdtr_copy = 0; |
| } |
| } |
| if (scb->flags & SCB_MSGOUT_PPR) |
| { |
| if(message_error) |
| { |
| if ( (aic7xxx_verbose & VERBOSE_NEGOTIATION2) && |
| (aic_dev->flags & DEVICE_PRINT_DTR) ) |
| { |
| printk(INFO_LEAD "Device failed to complete Parallel Protocol " |
| "Request processing and\n", p->host_no, CTL_OF_SCB(scb)); |
| printk(INFO_LEAD "returned a sense error code for invalid message, " |
| "disabling future\n", p->host_no, CTL_OF_SCB(scb)); |
| printk(INFO_LEAD "Parallel Protocol Request negotiation to this " |
| "device.\n", p->host_no, CTL_OF_SCB(scb)); |
| } |
| /* |
| * Disable PPR negotiation and revert back to WDTR and SDTR setup |
| */ |
| aic_dev->needppr = aic_dev->needppr_copy = 0; |
| aic_dev->needsdtr = aic_dev->needsdtr_copy = 1; |
| aic_dev->needwdtr = aic_dev->needwdtr_copy = 1; |
| } |
| } |
| } |
| |
| queue_depth = aic_dev->temp_q_depth; |
| if (queue_depth >= aic_dev->active_cmds) |
| { |
| scbp = scbq_remove_head(&aic_dev->delayed_scbs); |
| if (scbp) |
| { |
| if (queue_depth == 1) |
| { |
| /* |
| * Give extra preference to untagged devices, such as CD-R devices |
| * This makes it more likely that a drive *won't* stuff up while |
| * waiting on data at a critical time, such as CD-R writing and |
| * audio CD ripping operations. Should also benefit tape drives. |
| */ |
| scbq_insert_head(&p->waiting_scbs, scbp); |
| } |
| else |
| { |
| scbq_insert_tail(&p->waiting_scbs, scbp); |
| } |
| #ifdef AIC7XXX_VERBOSE_DEBUGGING |
| if (aic7xxx_verbose > 0xffff) |
| printk(INFO_LEAD "Moving SCB from delayed to waiting queue.\n", |
| p->host_no, CTL_OF_SCB(scbp)); |
| #endif |
| if (queue_depth > aic_dev->active_cmds) |
| { |
| scbp = scbq_remove_head(&aic_dev->delayed_scbs); |
| if (scbp) |
| scbq_insert_tail(&p->waiting_scbs, scbp); |
| } |
| } |
| } |
| if (!(scb->tag_action)) |
| { |
| aic7xxx_index_busy_target(p, scb->hscb->target_channel_lun, |
| /* unbusy */ TRUE); |
| if (cmd->device->simple_tags) |
| { |
| aic_dev->temp_q_depth = aic_dev->max_q_depth; |
| } |
| } |
| if(scb->flags & SCB_DTR_SCB) |
| { |
| aic_dev->dtr_pending = 0; |
| } |
| aic_dev->active_cmds--; |
| p->activescbs--; |
| |
| if ((scb->sg_length >= 512) && (((cmd->result >> 16) & 0xf) == DID_OK)) |
| { |
| long *ptr; |
| int x, i; |
| |
| |
| if (rq_data_dir(cmd->request) == WRITE) |
| { |
| aic_dev->w_total++; |
| ptr = aic_dev->w_bins; |
| } |
| else |
| { |
| aic_dev->r_total++; |
| ptr = aic_dev->r_bins; |
| } |
| if(cmd->device->simple_tags && cmd->request->cmd_flags & REQ_HARDBARRIER) |
| { |
| aic_dev->barrier_total++; |
| if(scb->tag_action == MSG_ORDERED_Q_TAG) |
| aic_dev->ordered_total++; |
| } |
| x = scb->sg_length; |
| x >>= 10; |
| for(i=0; i<6; i++) |
| { |
| x >>= 2; |
| if(!x) { |
| ptr[i]++; |
| break; |
| } |
| } |
| if(i == 6 && x) |
| ptr[5]++; |
| } |
| aic7xxx_free_scb(p, scb); |
| aic7xxx_queue_cmd_complete(p, cmd); |
| |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_run_done_queue |
| * |
| * Description: |
| * Calls the aic7xxx_done() for the scsi_cmnd of each scb in the |
| * aborted list, and adds each scb to the free list. If complete |
| * is TRUE, we also process the commands complete list. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_run_done_queue(struct aic7xxx_host *p, /*complete*/ int complete) |
| { |
| struct aic7xxx_scb *scb; |
| int i, found = 0; |
| |
| for (i = 0; i < p->scb_data->numscbs; i++) |
| { |
| scb = p->scb_data->scb_array[i]; |
| if (scb->flags & SCB_QUEUED_FOR_DONE) |
| { |
| if (scb->flags & SCB_QUEUE_FULL) |
| { |
| scb->cmd->result = QUEUE_FULL << 1; |
| } |
| else |
| { |
| if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) |
| printk(INFO_LEAD "Aborting scb %d\n", |
| p->host_no, CTL_OF_SCB(scb), scb->hscb->tag); |
| /* |
| * Clear any residual information since the normal aic7xxx_done() path |
| * doesn't touch the residuals. |
| */ |
| scb->hscb->residual_SG_segment_count = 0; |
| scb->hscb->residual_data_count[0] = 0; |
| scb->hscb->residual_data_count[1] = 0; |
| scb->hscb->residual_data_count[2] = 0; |
| } |
| found++; |
| aic7xxx_done(p, scb); |
| } |
| } |
| if (aic7xxx_verbose & (VERBOSE_ABORT_RETURN | VERBOSE_RESET_RETURN)) |
| { |
| printk(INFO_LEAD "%d commands found and queued for " |
| "completion.\n", p->host_no, -1, -1, -1, found); |
| } |
| if (complete) |
| { |
| aic7xxx_done_cmds_complete(p); |
| } |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_abort_waiting_scb |
| * |
| * Description: |
| * Manipulate the waiting for selection list and return the |
| * scb that follows the one that we remove. |
| *-F*************************************************************************/ |
| static unsigned char |
| aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, |
| unsigned char scbpos, unsigned char prev) |
| { |
| unsigned char curscb, next; |
| |
| /* |
| * Select the SCB we want to abort and pull the next pointer out of it. |
| */ |
| curscb = aic_inb(p, SCBPTR); |
| aic_outb(p, scbpos, SCBPTR); |
| next = aic_inb(p, SCB_NEXT); |
| |
| aic7xxx_add_curscb_to_free_list(p); |
| |
| /* |
| * Update the waiting list |
| */ |
| if (prev == SCB_LIST_NULL) |
| { |
| /* |
| * First in the list |
| */ |
| aic_outb(p, next, WAITING_SCBH); |
| } |
| else |
| { |
| /* |
| * Select the scb that pointed to us and update its next pointer. |
| */ |
| aic_outb(p, prev, SCBPTR); |
| aic_outb(p, next, SCB_NEXT); |
| } |
| /* |
| * Point us back at the original scb position and inform the SCSI |
| * system that the command has been aborted. |
| */ |
| aic_outb(p, curscb, SCBPTR); |
| return (next); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_search_qinfifo |
| * |
| * Description: |
| * Search the queue-in FIFO for matching SCBs and conditionally |
| * requeue. Returns the number of matching SCBs. |
| *-F*************************************************************************/ |
| static int |
| aic7xxx_search_qinfifo(struct aic7xxx_host *p, int target, int channel, |
| int lun, unsigned char tag, int flags, int requeue, |
| volatile scb_queue_type *queue) |
| { |
| int found; |
| unsigned char qinpos, qintail; |
| struct aic7xxx_scb *scbp; |
| |
| found = 0; |
| qinpos = aic_inb(p, QINPOS); |
| qintail = p->qinfifonext; |
| |
| p->qinfifonext = qinpos; |
| |
| while (qinpos != qintail) |
| { |
| scbp = p->scb_data->scb_array[p->qinfifo[qinpos++]]; |
| if (aic7xxx_match_scb(p, scbp, target, channel, lun, tag)) |
| { |
| /* |
| * We found an scb that needs to be removed. |
| */ |
| if (requeue && (queue != NULL)) |
| { |
| if (scbp->flags & SCB_WAITINGQ) |
| { |
| scbq_remove(queue, scbp); |
| scbq_remove(&p->waiting_scbs, scbp); |
| scbq_remove(&AIC_DEV(scbp->cmd)->delayed_scbs, scbp); |
| AIC_DEV(scbp->cmd)->active_cmds++; |
| p->activescbs++; |
| } |
| scbq_insert_tail(queue, scbp); |
| AIC_DEV(scbp->cmd)->active_cmds--; |
| p->activescbs--; |
| scbp->flags |= SCB_WAITINGQ; |
| if ( !(scbp->tag_action & TAG_ENB) ) |
| { |
| aic7xxx_index_busy_target(p, scbp->hscb->target_channel_lun, |
| TRUE); |
| } |
| } |
| else if (requeue) |
| { |
| p->qinfifo[p->qinfifonext++] = scbp->hscb->tag; |
| } |
| else |
| { |
| /* |
| * Preserve any SCB_RECOVERY_SCB flags on this scb then set the |
| * flags we were called with, presumeably so aic7xxx_run_done_queue |
| * can find this scb |
| */ |
| scbp->flags = flags | (scbp->flags & SCB_RECOVERY_SCB); |
| if (aic7xxx_index_busy_target(p, scbp->hscb->target_channel_lun, |
| FALSE) == scbp->hscb->tag) |
| { |
| aic7xxx_index_busy_target(p, scbp->hscb->target_channel_lun, |
| TRUE); |
| } |
| } |
| found++; |
| } |
| else |
| { |
| p->qinfifo[p->qinfifonext++] = scbp->hscb->tag; |
| } |
| } |
| /* |
| * Now that we've done the work, clear out any left over commands in the |
| * qinfifo and update the KERNEL_QINPOS down on the card. |
| * |
| * NOTE: This routine expect the sequencer to already be paused when |
| * it is run....make sure it's that way! |
| */ |
| qinpos = p->qinfifonext; |
| while(qinpos != qintail) |
| { |
| p->qinfifo[qinpos++] = SCB_LIST_NULL; |
| } |
| if (p->features & AHC_QUEUE_REGS) |
| aic_outb(p, p->qinfifonext, HNSCB_QOFF); |
| else |
| aic_outb(p, p->qinfifonext, KERNEL_QINPOS); |
| |
| return (found); |
| } |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_scb_on_qoutfifo |
| * |
| * Description: |
| * Is the scb that was passed to us currently on the qoutfifo? |
| *-F*************************************************************************/ |
| static int |
| aic7xxx_scb_on_qoutfifo(struct aic7xxx_host *p, struct aic7xxx_scb *scb) |
| { |
| int i=0; |
| |
| while(p->qoutfifo[(p->qoutfifonext + i) & 0xff ] != SCB_LIST_NULL) |
| { |
| if(p->qoutfifo[(p->qoutfifonext + i) & 0xff ] == scb->hscb->tag) |
| return TRUE; |
| else |
| i++; |
| } |
| return FALSE; |
| } |
| |
| |
| /*+F************************************************************************* |
| * Function: |
| * aic7xxx_reset_device |
| * |
| * Description: |
| * The device at the given target/channel has been reset. Abort |
| * all active and queued scbs for that target/channel. This function |
| * need not worry about linked next pointers because if was a MSG_ABORT_TAG |
| * then we had a tagged command (no linked next), if it was MSG_ABORT or |
| * MSG_BUS_DEV_RESET then the device won't know about any commands any more |
| * and no busy commands will exist, and if it was a bus reset, then nothing |
| * knows about any linked next commands any more. In all cases, we don't |
| * need to worry about the linked next or busy scb, we just need to clear |
| * them. |
| *-F*************************************************************************/ |
| static void |
| aic7xxx_reset_device(struct aic7xxx_host *p, int target, int channel, |
| int lun, unsigned char tag) |
| { |
| struct aic7xxx_scb *scbp, *prev_scbp; |
| struct scsi_device *sd; |
| unsigned char active_scb, tcl, scb_tag; |
| int i = 0, init_lists = FALSE; |
| struct aic_dev_data *aic_dev; |
| |
| /* |
| * Restore this when we're done |
| */ |
| active_scb = aic_inb(p, SCBPTR); |
| scb_tag = aic_inb(p, SCB_TAG); |
| |
| if (aic7xxx_verbose & (VERBOSE_RESET_PROCESS | VERBOSE_ABORT_PROCESS)) |
| { |
| printk(INFO_LEAD "Reset device, hardware_scb %d,\n", |
| p->host_no, channel, target, lun, active_scb); |
| printk(INFO_LEAD "Current scb %d, SEQADDR 0x%x, LASTPHASE " |
| "0x%x\n", |
| p->host_no, channel, target, lun, scb_tag, |
| aic_inb(p, SEQADDR0) | (aic_inb(p, SEQADDR1) << 8), |
| aic_inb(p, LASTPHASE)); |
| printk(INFO_LEAD "SG_CACHEPTR 0x%x, SG_COUNT %d, SCSISIGI 0x%x\n", |
| p->host_no, channel, target, lun, |
| (p->features & AHC_ULTRA2) ? aic_inb(p, SG_CACHEPTR) : 0, |
| aic_inb(p, SG_COUNT), aic_inb(p, SCSISIGI)); |
| printk(INFO_LEAD "SSTAT0 0x%x, SSTAT1 0x%x, SSTAT2 0x%x\n", |
| p->host_no, channel, target, lun, aic_inb(p, SSTAT0), |
| aic_inb(p, SSTAT1), aic_inb(p, SSTAT2)); |
| } |
| |
| /* |
| * Deal with the busy target and linked next issues. |
| */ |
| list_for_each_entry(aic_dev, &p->aic_devs, list) |
| { |
| if (aic7xxx_verbose & (VERBOSE_RESET_PROCESS | VERBOSE_ABORT_PROCESS)) |
| printk(INFO_LEAD "processing aic_dev %p\n", p->host_no, channel, target, |
| lun, aic_dev); |
| sd = aic_dev->SDptr; |
| |
| if((target != ALL_TARGETS && target != sd->id) || |
| (channel != ALL_CHANNELS && channel != sd->channel)) |
| continue; |
| if (aic7xxx_verbose & (VERBOSE_ABORT_PROCESS | VERBOSE_RESET_PROCESS)) |
| printk(INFO_LEAD "Cleaning up status information " |
| "and delayed_scbs.\n", p->host_no, sd->channel, sd->id, sd->lun); |
| aic_dev->flags &= ~BUS_DEVICE_RESET_PENDING; |
| if ( tag == SCB_LIST_NULL ) |
| { |
| aic_dev->dtr_pending = 0; |
| aic_dev->needppr = aic_dev->needppr_copy; |
| aic_dev->needsdtr = aic_dev->needsdtr_copy; |
| aic_dev->needwdtr = aic_dev->needwdtr_copy; |
| aic_dev->flags = DEVICE_PRINT_DTR; |
| aic_dev->temp_q_depth = aic_dev->max_q_depth; |
| } |
| tcl = (sd->id << 4) | (sd->channel << 3) | sd->lun; |
| if ( (aic7xxx_index_busy_target(p, tcl, FALSE) == tag) || |
| (tag == SCB_LIST_NULL) ) |
| aic7xxx_index_busy_target(p, tcl, /* unbusy */ TRUE); |
| prev_scbp = NULL; |
| scbp = aic_dev->delayed_scbs.head; |
| while (scbp != NULL) |
| { |
| prev_scbp = scbp; |
| scbp = scbp->q_next; |
| if (aic7xxx_match_scb(p, prev_scbp, target, channel, lun, tag)) |
| { |
| scbq_remove(&aic_dev->delayed_scbs, prev_scbp); |
| if (prev_scbp->flags & SCB_WAITINGQ) |
| { |
| aic_dev->active_cmds++; |
| p->activescbs++; |
| } |
| prev_scbp->flags &= ~(SCB_ACTIVE | SCB_WAITINGQ); |
| prev_scbp->flags |= SCB_RESET | SCB_QUEUED_FOR_DONE; |
| } |
|