diff -u --recursive --new-file v2.0.30/linux/CREDITS linux/CREDITS --- v2.0.30/linux/CREDITS Tue Apr 8 09:04:28 1997 +++ linux/CREDITS Mon Aug 4 12:10:37 1997 @@ -466,7 +466,7 @@ S: USA N: Philip Gladstone -E: philipg@onsett.com +E: philip@raptor.com D: Kernel / timekeeping stuff N: Michael A. Griffith @@ -1125,9 +1125,9 @@ S: Germany N: William E. Roadcap -E: roadcapw@cfw.com +E: roadcapw@titus.org W: http://www.cfw.com/~roadcapw -D: Author of menu based configuration tool, Menuconfig. +D: Author of ncurses based configuration tool, Menuconfig. S: 1407 Broad Street S: Waynesboro, Virginia 22980 S: USA diff -u --recursive --new-file v2.0.30/linux/Documentation/Configure.help linux/Documentation/Configure.help --- v2.0.30/linux/Documentation/Configure.help Tue Apr 8 08:47:45 1997 +++ linux/Documentation/Configure.help Mon Aug 4 11:45:55 1997 @@ -124,18 +124,18 @@ nothing to do with the loopback device used for network connections from the machine to itself. Most users will answer N here. -Enhanced IDE/MFM/RLL disk/cdrom/tape support +Enhanced IDE/MFM/RLL disk/cdrom/tape/floppy support CONFIG_BLK_DEV_IDE - This will use the full-featured IDE driver to control up to four IDE - interfaces, for a combination of up to eight IDE disk/cdrom/tape - drives. Useful information about large (>540MB) IDE disks, - soundcard IDE ports, and other topics, is all contained in - Documentation/ide.txt. If you have one or more IDE drives, say Y - here. If your system has no IDE drives, or if memory requirements - are really tight, you could say N here, and select the Old harddisk - driver instead to save about 13kB of memory in the kernel. To - fine-tune IDE drive/interface parameters for improved performance, - look for the hdparm package at + This will use the full-featured IDE driver to control up to four + IDE interfaces, for a combination of up to eight IDE + disk/cdrom/tape/floppy drives. Useful information about large + (>540MB) IDE disks, soundcard IDE ports, and other topics, is all + contained in Documentation/ide.txt. If you have one or more IDE + drives, say Y here. If your system has no IDE drives, or if memory + requirements are really tight, you could say N here, and select + the Old harddisk driver instead to save about 13kB of memory in + the kernel. To fine-tune IDE drive/interface parameters for + improved performance, look for the hdparm package at sunsite.unc.edu:/pub/Linux/kernel/patches/diskdrives/ Old harddisk (MFM/RLL/IDE) driver @@ -190,9 +190,32 @@ ATAPI is a new protocol used by IDE TAPE and ATAPI drives, similar to the SCSI protocol. At boot time, the TAPE drive will be identified along with other IDE devices, as "hdb" or "hdc", - or something similar. Be sure to consult the drivers/block/ide-tape.c + or something similar, and will be mapped to a character device + such as "ht0". Be sure to consult the drivers/block/ide-tape.c and Documentation/ide.txt files for usage information. +Include IDE/ATAPI FLOPPY support (new) +CONFIG_BLK_DEV_IDEFLOPPY + If you have an IDE floppy which uses the ATAPI protocol, say Y. + ATAPI is a new protocol used by IDE cdrom/tape/floppy drives, + similar to the SCSI protocol. IDE floppy drives include the + LS-120 and the ATAPI ZIP (ATAPI PD-CD drives are not supported + by this driver; support for PD-CD drives is available through + the SCSI emulation). At boot time, the FLOPPY drive will be + identified along with other IDE devices, as "hdb" or "hdc", or + something similar. + +SCSI emulation support +CONFIG_BLK_DEV_IDESCSI + This will provide SCSI host adapter emulation for IDE ATAPI devices, + and will allow you to use a SCSI device driver instead of a native + ATAPI driver. This is useful if you have an ATAPI device for which + no native driver has been written (for example, an ATAPI PD-CD + drive); you can then use this emulation together with an appropriate + SCSI device driver. If both this SCSI emulation and native ATAPI + support are compiled into the kernel, the native support will be + used. Normally, say N. + Support removable IDE interfaces (PCMCIA) CONFIG_BLK_DEV_IDE_PCMCIA This option adds code to the IDE driver to handle hot insertion @@ -589,10 +612,11 @@ Intel 82371 PIIX (Triton I/II) DMA support CONFIG_BLK_DEV_TRITON If your PCI system uses an IDE harddrive (as opposed to SCSI, say) - and includes the Intel 430FX PCI Triton chipset, you will want to - enable this option to allow use of bus-mastering DMA data transfers. - Read the comments at the beginning of drivers/block/triton.c. Check - the file Documentation/Changes for location and latest version of + and includes the Intel Triton I/II IDE interface chipset (i82371FB, + i82371SB or i82371AB), you will want to enable this option to allow + use of bus-mastering DMA data transfers. Read the comments at the + beginning of drivers/block/triton.c and Documentation/ide.txt. + Check the file Documentation/Changes for location and latest version of the hdparm utility. It is safe to say Y to this question. System V IPC @@ -2417,19 +2441,18 @@ AT&T WaveLAN & DEC RoamAbout DS support CONFIG_WAVELAN - These are cards for wireless ethernet-like networking. Supported are - AT&T GIS and NCR WaveLAN cards. If you want to use a card of this - type under Linux, say Y and read the Ethernet-HOWTO, available via - ftp (user: anonymous) in sunsite.unc.edu:/pub/Linux/docs/HOWTO. Some - more specific information is contained in - drivers/net/README.wavelan. This driver is also available as a - module ( = code which can be inserted in and removed from the - running kernel whenever you want). If you want to compile it as a - module, say M here and read Documentation/modules.txt as well as - Documentation/networking/net-modules.txt. If you plan to use more - than one network card under linux, read the - Multiple-Ethernet-mini-HOWTO, available from - sunsite.unc.edu:/pub/Linux/docs/HOWTO/mini. + The Lucent Wavelan (formerly NCR and AT&T ; or DEC RoamAbout DS) + is a Radio LAN (wireless ethernet-like) at 900 MHz and 2.4 GHz. + This driver support the ISA version of the Wavelan. A driver for + the pcmcia hardware is available in David Hinds's pcmcia package. + This driver is fairly stable and may be compiled as a module + (wavelan.o). It implements many nice feature and the Wireless + Extensions (you must get the Wireless tools from the net). + For documentation, refer to : + o the wavelan man page, wireless tools man pages + o wavelan.p.h and the source code + o Ethernet-HOWTO, Multiple-Ethernet-mini-HOWTO, Module-HOWTO + o More documentation to come when I will have the time :-) HP PCLAN+ (27247B and 27252A) support CONFIG_HPLAN_PLUS diff -u --recursive --new-file v2.0.30/linux/Documentation/ide.txt linux/Documentation/ide.txt --- v2.0.30/linux/Documentation/ide.txt Wed Sep 25 01:12:11 1996 +++ linux/Documentation/ide.txt Mon Aug 4 11:45:55 1997 @@ -14,6 +14,7 @@ Major features of ide.c & ide-cd.c ("NEW!" marks changes since 1.2.13): +NEW! - support for IDE ATAPI *floppy* drives NEW! - support for IDE ATAPI *tape* drives, courtesy of Gadi Oxman (re-run MAKEDEV.ide to create the tape device entries in /dev/) NEW! - support for up to *four* IDE interfaces on one or more IRQs @@ -111,12 +112,12 @@ Apparently many releases of Slackware 2.2/2.3 have incorrect entries in /dev for hdc* and hdd* -- this can also be corrected by running MAKEDEV.ide -ide.c automatically probes for the primary and secondary interfaces, +ide.c automatically probes for the standard four IDE interfaces, for the drives/geometries attached to those interfaces, and for the -IRQ numbers being used by the interfaces (normally IRQ14 & IRQ15). +IRQ numbers being used by the interfaces (normally 14, 15, 11 and 10). -Interfaces beyond the first two are not normally probed for, but may be -specified using kernel "command line" options. For example, +For special cases, interfaces may be specified using kernel "command line" +options. For example, ide3=0x168,0x36e,10 /* ioports 0x168-0x16f,0x36e, irq 10 */ diff -u --recursive --new-file v2.0.30/linux/Documentation/locks.txt linux/Documentation/locks.txt --- v2.0.30/linux/Documentation/locks.txt Wed May 15 01:22:04 1996 +++ linux/Documentation/locks.txt Sun Aug 3 13:59:07 1997 @@ -2,37 +2,30 @@ Andy Walker - 15 May 1996 + 12 May 1997 -What's New? ------------ +1. What's New? +-------------- -Flock Emulation Warnings ------------------------- -Many people will have noticed the ugly messages that the file locking -code started generating with the release of kernel version 1.3.95. The -messages look something like this: +1.1 Broken Flock Emulation +-------------------------- - fcntl_setlk() called by process XX with broken flock() emulation +The old flock(2) emulation in the kernel was swapped for proper BSD +compatible flock(2) support in the 1.3.x series of kernels. With the +release of the 2.1.x kernel series, support for the old emulation has +been totally removed, so that we don't need to carry this baggage +forever. -This is a warning for people using older C libraries that those libraries -are still calling the pre 1.3.x flock() emulation routines, instead of -the real flock() system call. The old routines are quite badly broken, -especially with respect to parent-child lock sharing, and can give bad -results if, for example, sendmail attempts to use them. +This should not cause problems for anybody, since everybody using a +2.1.x kernel should have updated their C library to a suitable version +anyway (see the file "linux/Documentation/Changes".) -Fixed versions of the C libraries have been on public release for many -months. The latest versions are 5.2.18 or 5.3.12 for ELF, and I believe -somebody made a 4.7.6 release for people using a.out systems. +1.2 Allow Mixed Locks Again +--------------------------- -In 1.3.96 Linus decided to be lenient on the stragglers and changed the -warning message so that the kernel will only complain five times and -then shut up. That should make life more bearable even for people who, -for some reason, don't want to upgrade. - -Sendmail Problems ------------------ +1.2.1 Typical Problems - Sendmail +--------------------------------- Because sendmail was unable to use the old flock() emulation, many sendmail installations use fcntl() instead of flock(). This is true of Slackware 3.0 for example. This gave rise to some other subtle problems if sendmail was @@ -42,23 +35,50 @@ over time, or under a very heavy mail load, would eventually cause the kernel to lock solid with deadlocked processes. -Disallow Mixed Locks --------------------- -I have chosen the rather cruel solution of disallowing mixed locking styles -on a given file at a given time. Attempts to lock a file with flock() when -fcntl() locks exist, or vice versa, return with an error status of EBUSY. -This seemed to be the only way to avoid all possible deadlock conditions, -as flock() locks do not strictly have one owner process and so can't be -checked for deadlocking in the usual manner. - -The process that created a lock with flock() might have forked multiple -children and exited. Previously the parent process would have been marked -as the owner of the lock, but deadlocks could just have easily occurred in -one or more of the children, which we would not have been able to identify -and avoid. - -Some programs may break (again, groan). In particular the aforementioned -sendmail may have problems running in 'newaliases' mode. It will no longer -deadlock though. Recompile sendmail to use flock() and your troubles will -be over. +1.2.2 The Solution +------------------ +The solution I have chosen, after much experimentation and discussion, +is to make flock() and fcntl() locks oblivious to each other. Both can +exists, and neither will have any effect on the other. + +I wanted the two lock styles to be cooperative, but there were so many +race and deadlock conditions that the current solution was the only +practical one. It puts us in the same position as, for example, SunOS +4.1.x and serveral other commercial Unices. The only OS's that support +cooperative flock()/fcntl() are those that emulate flock() using +fcntl(), with all the problems that implies. + + +1.3 Mandatory Locking As A Mount Option +--------------------------------------- + +Mandatory locking, as described in 'Documentation/mandatory.txt' was prior +to this release a general configuration option that was valid for all +mounted filesystems. This had a number of inherent dangers, not the least +of which was the ability to freeze an NFS server by asking it to read a +file for which a mandatory lock existed. + +From this release of the kernel, mandatory locking can be turned on and off +on a per-filesystem basis, using the mount options 'mand' and 'nomand'. +The default is to disallow mandatory locking. The intention is that +mandatory locking only be enabled on a local filesystem as the specific need +arises. + +Until an updated version of mount(8) becomes available you may have to apply +this patch to the mount sources (based on the version distributed with Rick +Faiths util-linux-2.5 package): + +*** mount.c.orig Sat Jun 8 09:14:31 1996 +--- mount.c Sat Jun 8 09:13:02 1996 +*************** +*** 100,105 **** +--- 100,107 ---- + { "noauto", 0, MS_NOAUTO }, /* Can only be mounted explicitly */ + { "user", 0, MS_USER }, /* Allow ordinary user to mount */ + { "nouser", 1, MS_USER }, /* Forbid ordinary user to mount */ ++ { "mand", 0, MS_MANDLOCK }, /* Allow mandatory locks on this FS */ ++ { "nomand", 1, MS_MANDLOCK }, /* Forbid mandatory locks on this FS */ + /* add new options here */ + #ifdef MS_NOSUB + { "sub", 1, MS_NOSUB }, /* allow submounts */ diff -u --recursive --new-file v2.0.30/linux/Documentation/mandatory.txt linux/Documentation/mandatory.txt --- v2.0.30/linux/Documentation/mandatory.txt Tue Apr 16 00:27:10 1996 +++ linux/Documentation/mandatory.txt Sun Aug 3 13:59:07 1997 @@ -5,8 +5,8 @@ 15 April 1996 -What is mandatory locking? ---------------------------- +1. What is mandatory locking? +------------------------------ Mandatory locking is kernel enforced file locking, as opposed to the more usual cooperative file locking used to guarantee sequential access to files among @@ -44,8 +44,8 @@ borrowing the fcntl() locking scheme from System V. The mandatory locking scheme is defined by the System V Interface Definition (SVID) Version 3. -Marking a file for mandatory locking ------------------------------------- +2. Marking a file for mandatory locking +--------------------------------------- A file is marked as a candidate for mandatory by setting the group-id bit in its file mode but removing the group-execute bit. This is an otherwise @@ -58,8 +58,8 @@ refrain from clearing this bit. Similarly the kernel has been modified not to run mandatory lock candidates with setgid privileges. -Available implementations -------------------------- +3. Available implementations +---------------------------- I have considered the implementations of mandatory locking available with SunOS 4.1.x, Solaris 2.x and HP-UX 9.x. @@ -93,8 +93,8 @@ below are just as valid as any others, so long as the main points seem to agree. -Semantics ---------- +4. Semantics +------------ 1. Mandatory locks can only be applied via the fcntl()/lockf() locking interface - in other words the System V/POSIX interface. BSD style @@ -124,8 +124,8 @@ that has any mandatory locks in effect will be rejected with the error status EAGAIN. -Which system calls are affected? --------------------------------- +5. Which system calls are affected? +----------------------------------- Those which modify a file's contents, not just the inode. That gives read(), write(), readv(), writev(), open(), creat(), mmap(), truncate() and @@ -142,8 +142,8 @@ checking in my eagerness to get this code out the door. Please let me know, or better still fix the system calls yourself and submit a patch to me or Linus. -Warning! --------- +6. Warning! +----------- Not even root can override a mandatory lock, so runaway process can wreak havoc if they lock crucial files. The way around it is to change the file diff -u --recursive --new-file v2.0.30/linux/MAINTAINERS linux/MAINTAINERS --- v2.0.30/linux/MAINTAINERS Tue Apr 8 08:47:45 1997 +++ linux/MAINTAINERS Mon Aug 4 11:45:55 1997 @@ -164,6 +164,12 @@ L: linux-eata@i-connect.net, linux-scsi@vger.rutgers.edu S: Maintained +FILE LOCKING (flock() and fcntl()/lockf()) +P: Andy Walker +M: andy@lysaker.kvaerner.no +L: linux-kernel@vger.rutgers.edu +S: Maintained + FRAME RELAY DLCI/FRAD (Sangoma drivers too) P: Mike McLagan M: mike.mclagan@linux.org @@ -206,6 +212,12 @@ L: linux-kernel@vger.rutgers.edu S: Maintained +IDE/ATAPI TAPE/FLOPPY DRIVERS +P: Gadi Oxman +M: Gadi Oxman +L: linux-kernel@vger.rutgers.edu +S: Maintained + ISDN SUBSYSTEM P: Fritz Elfert M: fritz@wuemaus.franken.de @@ -318,7 +330,7 @@ MENUCONFIG: P: William Roadcap -M: roadcapw@cfw.com +M: roadcapw@titus.org L: linux-kernel@vger.rutgers.edu S: Maintained diff -u --recursive --new-file v2.0.30/linux/Makefile linux/Makefile --- v2.0.30/linux/Makefile Mon Mar 17 14:58:22 1997 +++ linux/Makefile Mon Aug 4 15:23:02 1997 @@ -1,6 +1,6 @@ VERSION = 2 PATCHLEVEL = 0 -SUBLEVEL = 30 +SUBLEVEL = 31 ARCH = i386 @@ -319,7 +319,6 @@ mrproper: clean rm -f include/linux/autoconf.h include/linux/version.h rm -f drivers/sound/local.h drivers/sound/.defines - rm -f drivers/scsi/aic7xxx_asm drivers/scsi/aic7xxx_seq.h rm -f drivers/char/uni_hash.tbl drivers/char/conmakehash rm -f .version .config* config.in config.old rm -f scripts/tkparse scripts/kconfig.tk scripts/kconfig.tmp diff -u --recursive --new-file v2.0.30/linux/arch/alpha/defconfig linux/arch/alpha/defconfig --- v2.0.30/linux/arch/alpha/defconfig Tue Oct 29 17:42:40 1996 +++ linux/arch/alpha/defconfig Sun Aug 3 13:59:07 1997 @@ -179,7 +179,6 @@ # Filesystems # # CONFIG_QUOTA is not set -# CONFIG_LOCK_MANDATORY is not set # CONFIG_MINIX_FS is not set # CONFIG_EXT_FS is not set CONFIG_EXT2_FS=y diff -u --recursive --new-file v2.0.30/linux/arch/alpha/kernel/osf_sys.c linux/arch/alpha/kernel/osf_sys.c --- v2.0.30/linux/arch/alpha/kernel/osf_sys.c Tue Aug 20 23:18:07 1996 +++ linux/arch/alpha/kernel/osf_sys.c Sun Aug 3 10:58:38 1997 @@ -714,29 +714,25 @@ osf_getsysinfo (unsigned long op, void * buffer, unsigned long nbytes, int * start, void *arg) { - extern unsigned long rdfpcr (void); - unsigned long fpcw; - - switch (op) { - case 45: /* GSI_IEEE_FP_CONTROL */ - /* build and return current fp control word: */ - fpcw = current->tss.flags & IEEE_TRAP_ENABLE_MASK; - fpcw |= ((rdfpcr() >> 52) << 17) & IEEE_STATUS_MASK; - put_user(fpcw, (unsigned long *) buffer); - return 0; - - case 46: /* GSI_IEEE_STATE_AT_SIGNAL */ - /* - * Not sure anybody will ever use this weird stuff. These - * ops can be used (under OSF/1) to set the fpcr that should - * be used when a signal handler starts executing. - */ - break; - - default: - break; - } - return -EOPNOTSUPP; + switch (op) { + case 45: /* GSI_IEEE_FP_CONTROL */ + /* Return current sw control & status bits. */ + put_user(current->tss.flags & IEEE_SW_MASK, + (unsigned long *)buffer); + return 0; + + case 46: /* GSI_IEEE_STATE_AT_SIGNAL */ + /* + * Not sure anybody will ever use this weird stuff. These + * ops can be used (under OSF/1) to set the fpcr that should + * be used when a signal handler starts executing. + */ + break; + + default: + break; + } + return -EOPNOTSUPP; } @@ -744,25 +740,43 @@ osf_setsysinfo (unsigned long op, void * buffer, unsigned long nbytes, int * start, void *arg) { - unsigned long fpcw; - - switch (op) { - case 14: /* SSI_IEEE_FP_CONTROL */ - /* update trap enable bits: */ - fpcw = get_user((unsigned long *) buffer); - current->tss.flags &= ~IEEE_TRAP_ENABLE_MASK; - current->tss.flags |= (fpcw & IEEE_TRAP_ENABLE_MASK); - return 0; - - case 15: /* SSI_IEEE_STATE_AT_SIGNAL */ - case 16: /* SSI_IEEE_IGNORE_STATE_AT_SIGNAL */ - /* - * Not sure anybody will ever use this weird stuff. These - * ops can be used (under OSF/1) to set the fpcr that should - * be used when a signal handler starts executing. - */ - default: - break; - } - return -EOPNOTSUPP; + switch (op) { + case 14: { /* SSI_IEEE_FP_CONTROL */ + unsigned long sw, fpcw; + + /* + * Alpha Architecture Handbook 4.7.7.3: + * To be fully IEEE compiant, we must track the current IEEE + * exception state in software, because spurrious bits can be + * set in the trap shadow of a software-complete insn. + */ + + /* Update software trap enable bits. */ + sw = get_user((unsigned long *) buffer) & IEEE_SW_MASK; + current->tss.flags &= ~IEEE_SW_MASK; + current->tss.flags |= sw & IEEE_SW_MASK; + + /* Update the real fpcr. For exceptions that are disabled, + but that we have seen, turn off exceptions in h/w. + Otherwise leave them enabled so that we can update our + software status mask. */ + fpcw = rdfpcr() & (~FPCR_MASK | FPCR_DYN_MASK); + fpcw |= ieee_sw_to_fpcr(sw | ((~sw & IEEE_STATUS_MASK) >> 16)); + wrfpcr(fpcw); + return 0; + } + + case 15: /* SSI_IEEE_STATE_AT_SIGNAL */ + case 16: /* SSI_IEEE_IGNORE_STATE_AT_SIGNAL */ + /* + * Not sure anybody will ever use this weird stuff. These + * ops can be used (under OSF/1) to set the fpcr that should + * be used when a signal handler starts executing. + */ + break; + + default: + break; + } + return -EOPNOTSUPP; } diff -u --recursive --new-file v2.0.30/linux/arch/alpha/kernel/ptrace.c linux/arch/alpha/kernel/ptrace.c --- v2.0.30/linux/arch/alpha/kernel/ptrace.c Wed Sep 11 07:57:13 1996 +++ linux/arch/alpha/kernel/ptrace.c Mon Aug 4 15:41:59 1997 @@ -641,7 +641,7 @@ return; current->exit_code = SIGTRAP; current->state = TASK_STOPPED; - notify_parent(current); + notify_parent(current, SIGCHLD); schedule(); /* * this isn't the same as continuing with a signal, but it will do diff -u --recursive --new-file v2.0.30/linux/arch/alpha/kernel/signal.c linux/arch/alpha/kernel/signal.c --- v2.0.30/linux/arch/alpha/kernel/signal.c Tue Aug 20 23:18:07 1996 +++ linux/arch/alpha/kernel/signal.c Mon Aug 4 15:41:48 1997 @@ -291,7 +291,7 @@ if ((current->flags & PF_PTRACED) && signr != SIGKILL) { current->exit_code = signr; current->state = TASK_STOPPED; - notify_parent(current); + notify_parent(current, SIGCHLD); schedule(); single_stepping |= ptrace_cancel_bpt(current); if (!(signr = current->exit_code)) @@ -330,7 +330,7 @@ current->exit_code = signr; if (!(current->p_pptr->sig->action[SIGCHLD-1].sa_flags & SA_NOCLDSTOP)) - notify_parent(current); + notify_parent(current, SIGCHLD); schedule(); single_stepping |= ptrace_cancel_bpt(current); continue; diff -u --recursive --new-file v2.0.30/linux/arch/alpha/math-emu/fp-emul.c linux/arch/alpha/math-emu/fp-emul.c --- v2.0.30/linux/arch/alpha/math-emu/fp-emul.c Thu Apr 11 23:49:30 1996 +++ linux/arch/alpha/math-emu/fp-emul.c Sun Aug 3 10:58:38 1997 @@ -298,19 +298,29 @@ * * - Set the appropriate bits in the FPCR * - If the specified exception is enabled in the FPCR, - * return. The caller (mxr_signal_handler) will dispatch + * return. The caller (entArith) will dispatch * the appropriate signal to the translated program. + * + * In addition, properly track the exception state in software + * as described in Alpha Architecture Handbook 4.7.7.3. */ if (res) { - fpcr |= FPCR_SUM | res; - wrfpcr(fpcr); - if (((res & FPCR_INV) && (fpcw & IEEE_TRAP_ENABLE_INV)) || - ((res & FPCR_DZE) && (fpcw & IEEE_TRAP_ENABLE_DZE)) || - ((res & FPCR_OVF) && (fpcw & IEEE_TRAP_ENABLE_OVF)) || - ((res & FPCR_UNF) && (fpcw & IEEE_TRAP_ENABLE_UNF)) || - ((res & FPCR_INE) && (fpcw & IEEE_TRAP_ENABLE_INE))) + /* record exceptions in software control word. */ + fpcw |= res >> 35; + current->tss.flags = fpcw; + + /* update hardware control register, disabling hardware + exceptions for disabled software exceptions for which + we have a status. (no, really.) */ + fpcr &= (~FPCR_MASK | FPCR_DYN_MASK); + fpcr |= ieee_sw_to_fpcr(fpcw | (~fpcw & IEEE_STATUS_MASK)>>16); + wrfpcr(fpcr); + + /* Do we generate a signal? */ + if (res >> 51 & fpcw & IEEE_TRAP_ENABLE_MASK) return 0; } + /* * Whoo-kay... we got this far, and we're not generating a signal * to the translated program. All that remains is to write the diff -u --recursive --new-file v2.0.30/linux/arch/i386/defconfig linux/arch/i386/defconfig --- v2.0.30/linux/arch/i386/defconfig Tue Oct 29 17:42:40 1996 +++ linux/arch/i386/defconfig Mon Aug 4 14:59:17 1997 @@ -42,11 +42,13 @@ # CONFIG_BLK_DEV_HD_IDE is not set CONFIG_BLK_DEV_IDECD=y # CONFIG_BLK_DEV_IDETAPE is not set +# CONFIG_BLK_DEV_IDEFLOPPY is not set +# CONFIG_BLK_DEV_IDESCSI is not set # CONFIG_BLK_DEV_IDE_PCMCIA is not set CONFIG_BLK_DEV_CMD640=y # CONFIG_BLK_DEV_CMD640_ENHANCED is not set CONFIG_BLK_DEV_RZ1000=y -# CONFIG_BLK_DEV_TRITON is not set +CONFIG_BLK_DEV_TRITON=y # CONFIG_IDE_CHIPSETS is not set # @@ -66,7 +68,10 @@ CONFIG_INET=y # CONFIG_IP_FORWARD is not set # CONFIG_IP_MULTICAST is not set +# CONFIG_SYN_COOKIES is not set # CONFIG_IP_ACCT is not set +# CONFIG_IP_ROUTER is not set +# CONFIG_NET_IPIP is not set # # (it is safe to leave these untouched) @@ -129,7 +134,6 @@ # Filesystems # # CONFIG_QUOTA is not set -# CONFIG_LOCK_MANDATORY is not set CONFIG_MINIX_FS=y # CONFIG_EXT_FS is not set CONFIG_EXT2_FS=y diff -u --recursive --new-file v2.0.30/linux/arch/i386/kernel/bios32.c linux/arch/i386/kernel/bios32.c --- v2.0.30/linux/arch/i386/kernel/bios32.c Tue Apr 8 08:47:45 1997 +++ linux/arch/i386/kernel/bios32.c Mon Aug 4 08:38:56 1997 @@ -1,6 +1,8 @@ /* * bios32.c - BIOS32, PCI BIOS functions. * + * $Id: bios32.c,v 1.3.2.4 1997/08/02 22:24:23 mj Exp $ + * * Sponsored by * iX Multiuser Multitasking Magazine * Hannover, Germany @@ -52,6 +54,13 @@ * Feb 3, 1997 : Set internal functions to static, save/restore flags * avoid dead locks reading broken PCI BIOS, werner@suse.de * + * Apr 26, 1997 : Fixed case when there is BIOS32, but not PCI BIOS + * (mj@atrey.karlin.mff.cuni.cz) + * + * May 7, 1997 : Added some missing cli()'s. [mj] + * + * Jun 20, 1997 : Corrected problems in "conf1" type accesses. + * (paubert@iram.es) */ #include @@ -156,7 +165,7 @@ unsigned long entry; /* %edx */ unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%edi)" : "=a" (return_code), "=b" (address), @@ -171,10 +180,10 @@ case 0: return address + entry; case 0x80: /* Not present */ - printk("bios32_service(%ld) : not present\n", service); + printk("bios32_service(0x%lx) : not present\n", service); return 0; default: /* Shouldn't happen */ - printk("bios32_service(%ld) : returned 0x%x, mail drew@colorado.edu\n", + printk("bios32_service(0x%lx) : returned 0x%x, mail drew@colorado.edu\n", service, return_code); return 0; } @@ -187,7 +196,7 @@ } pci_indirect = { 0, KERNEL_CS }; -extern unsigned long check_pcibios(unsigned long memory_start, unsigned long memory_end) +static int check_pcibios(void) { unsigned long signature; unsigned char present_status; @@ -199,7 +208,7 @@ if ((pcibios_entry = bios32_service(PCI_SERVICE))) { pci_indirect.address = pcibios_entry; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%edi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -230,9 +239,10 @@ if (pcibios_entry) { printk ("pcibios_init : PCI BIOS revision %x.%02x entry at 0x%lx\n", major_revision, minor_revision, pcibios_entry); + return 1; } } - return memory_start; + return 0; } @@ -243,7 +253,7 @@ unsigned long ret; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__ ("lcall (%%edi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -268,7 +278,7 @@ unsigned short ret; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%edi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -293,7 +303,7 @@ unsigned long bx = (bus << 8) | device_fn; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%esi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -315,7 +325,7 @@ unsigned long bx = (bus << 8) | device_fn; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%esi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -337,7 +347,7 @@ unsigned long bx = (bus << 8) | device_fn; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%esi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -359,7 +369,7 @@ unsigned long bx = (bus << 8) | device_fn; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%esi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -381,7 +391,7 @@ unsigned long bx = (bus << 8) | device_fn; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%esi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -403,7 +413,7 @@ unsigned long bx = (bus << 8) | device_fn; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); __asm__("lcall (%%esi)\n\t" "jc 1f\n\t" "xor %%ah, %%ah\n" @@ -444,21 +454,17 @@ { unsigned int curr = 0; struct pci_dev *dev; - unsigned long flags; - save_flags(flags); for (dev = pci_devices; dev; dev = dev->next) { if (dev->vendor == vendor && dev->device == device_id) { if (curr == index) { *devfn = dev->devfn; *bus = dev->bus->number; - restore_flags(flags); return PCIBIOS_SUCCESSFUL; } ++curr; } } - restore_flags(flags); return PCIBIOS_DEVICE_NOT_FOUND; } @@ -472,21 +478,17 @@ { unsigned int curr = 0; struct pci_dev *dev; - unsigned long flags; - save_flags(flags); for (dev = pci_devices; dev; dev = dev->next) { if (dev->class == class_code) { if (curr == index) { *devfn = dev->devfn; *bus = dev->bus->number; - restore_flags(flags); return PCIBIOS_SUCCESSFUL; } ++curr; } } - restore_flags(flags); return PCIBIOS_DEVICE_NOT_FOUND; } @@ -500,18 +502,9 @@ { unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); - switch (where & 3) { - case 0: *value = inb(0xCFC); - break; - case 1: *value = inb(0xCFD); - break; - case 2: *value = inb(0xCFE); - break; - case 3: *value = inb(0xCFF); - break; - } + *value = inb(0xCFC + (where&3)); restore_flags(flags); return PCIBIOS_SUCCESSFUL; } @@ -521,12 +514,10 @@ { unsigned long flags; - save_flags(flags); + if (where&1) return PCIBIOS_BAD_REGISTER_NUMBER; + save_flags(flags); cli(); outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); - if (where & 2) - *value = inw(0xCFE); - else - *value = inw(0xCFC); + *value = inw(0xCFC + (where&2)); restore_flags(flags); return PCIBIOS_SUCCESSFUL; } @@ -536,7 +527,8 @@ { unsigned long flags; - save_flags(flags); + if (where&3) return PCIBIOS_BAD_REGISTER_NUMBER; + save_flags(flags); cli(); outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); *value = inl(0xCFC); restore_flags(flags); @@ -548,9 +540,9 @@ { unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); - outb(value, 0xCFC); + outb(value, 0xCFC + (where&3)); restore_flags(flags); return PCIBIOS_SUCCESSFUL; } @@ -560,9 +552,10 @@ { unsigned long flags; - save_flags(flags); + if (where&1) return PCIBIOS_BAD_REGISTER_NUMBER; + save_flags(flags); cli(); outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); - outw(value, 0xCFC); + outw(value, 0xCFC + (where&2)); restore_flags(flags); return PCIBIOS_SUCCESSFUL; } @@ -572,7 +565,8 @@ { unsigned long flags; - save_flags(flags); + if (where&3) return PCIBIOS_BAD_REGISTER_NUMBER; + save_flags(flags); cli(); outl(CONFIG_CMD(bus,device_fn,where), 0xCF8); outl(value, 0xCFC); restore_flags(flags); @@ -608,7 +602,7 @@ if (device_fn & 0x80) return PCIBIOS_DEVICE_NOT_FOUND; - save_flags(flags); + save_flags(flags); cli(); outb (FUNC(device_fn), 0xCF8); outb (bus, 0xCFA); *value = inb(IOADDR(device_fn,where)); @@ -624,7 +618,7 @@ if (device_fn & 0x80) return PCIBIOS_DEVICE_NOT_FOUND; - save_flags(flags); + save_flags(flags); cli(); outb (FUNC(device_fn), 0xCF8); outb (bus, 0xCFA); *value = inw(IOADDR(device_fn,where)); @@ -640,7 +634,7 @@ if (device_fn & 0x80) return PCIBIOS_DEVICE_NOT_FOUND; - save_flags(flags); + save_flags(flags); cli(); outb (FUNC(device_fn), 0xCF8); outb (bus, 0xCFA); *value = inl (IOADDR(device_fn,where)); @@ -654,7 +648,7 @@ { unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); outb (FUNC(device_fn), 0xCF8); outb (bus, 0xCFA); outb (value, IOADDR(device_fn,where)); @@ -668,7 +662,7 @@ { unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); outb (FUNC(device_fn), 0xCF8); outb (bus, 0xCFA); outw (value, IOADDR(device_fn,where)); @@ -682,7 +676,7 @@ { unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); outb (FUNC(device_fn), 0xCF8); outb (bus, 0xCFA); outl (value, IOADDR(device_fn,where)); @@ -714,7 +708,7 @@ unsigned int tmp; unsigned long flags; - save_flags(flags); + save_flags(flags); cli(); /* * check if configuration type 1 works @@ -736,7 +730,7 @@ outb (0x00, 0xCFB); outb (0x00, 0xCF8); outb (0x00, 0xCFA); - if (inb (0xCF8) == 0x00 && inb (0xCFC) == 0x00) { + if (inb (0xCF8) == 0x00 && inb (0xCFB) == 0x00) { restore_flags(flags); printk("pcibios_init: Using configuration type 2\n"); return &pci_direct_conf2; @@ -883,7 +877,9 @@ * */ - for (check = (union bios32 *) 0xe0000; check <= (union bios32 *) 0xffff0; ++check) { + for (check = (union bios32 *) 0xe0000; + check <= (union bios32 *) 0xffff0; + ++check) { if (check->fields.signature != BIOS32_SIGNATURE) continue; length = check->fields.length * 16; @@ -908,13 +904,11 @@ bios32_entry = check->fields.entry; printk ("pcibios_init : BIOS32 Service Directory entry at 0x%lx\n", bios32_entry); bios32_indirect.address = bios32_entry; - access_pci = &pci_bios_access; } } } - if (bios32_entry) { - memory_start = check_pcibios (memory_start, memory_end); - } + if (bios32_entry && check_pcibios()) + access_pci = &pci_bios_access; #endif return memory_start; } diff -u --recursive --new-file v2.0.30/linux/arch/i386/kernel/ptrace.c linux/arch/i386/kernel/ptrace.c --- v2.0.30/linux/arch/i386/kernel/ptrace.c Wed Sep 11 07:57:13 1996 +++ linux/arch/i386/kernel/ptrace.c Mon Aug 4 12:12:22 1997 @@ -535,7 +535,7 @@ return; current->exit_code = SIGTRAP; current->state = TASK_STOPPED; - notify_parent(current); + notify_parent(current, SIGCHLD); schedule(); /* * this isn't the same as continuing with a signal, but it will do diff -u --recursive --new-file v2.0.30/linux/arch/i386/kernel/signal.c linux/arch/i386/kernel/signal.c --- v2.0.30/linux/arch/i386/kernel/signal.c Wed Dec 11 06:41:01 1996 +++ linux/arch/i386/kernel/signal.c Mon Aug 4 12:12:51 1997 @@ -286,7 +286,7 @@ if ((current->flags & PF_PTRACED) && signr != SIGKILL) { current->exit_code = signr; current->state = TASK_STOPPED; - notify_parent(current); + notify_parent(current, SIGCHLD); schedule(); if (!(signr = current->exit_code)) continue; @@ -324,7 +324,7 @@ current->exit_code = signr; if (!(current->p_pptr->sig->action[SIGCHLD-1].sa_flags & SA_NOCLDSTOP)) - notify_parent(current); + notify_parent(current, SIGCHLD); schedule(); continue; diff -u --recursive --new-file v2.0.30/linux/drivers/block/Config.in linux/drivers/block/Config.in --- v2.0.30/linux/drivers/block/Config.in Mon Aug 5 00:13:50 1996 +++ linux/drivers/block/Config.in Mon Aug 4 11:45:55 1997 @@ -5,7 +5,7 @@ comment 'Floppy, IDE, and other block devices' tristate 'Normal floppy disk support' CONFIG_BLK_DEV_FD -bool 'Enhanced IDE/MFM/RLL disk/cdrom/tape support' CONFIG_BLK_DEV_IDE +bool 'Enhanced IDE/MFM/RLL disk/cdrom/tape/floppy support' CONFIG_BLK_DEV_IDE comment 'Please see Documentation/ide.txt for help/info on IDE drives' if [ "$CONFIG_BLK_DEV_IDE" = "n" ]; then bool 'Old harddisk (MFM/RLL/IDE) driver' CONFIG_BLK_DEV_HD_ONLY @@ -13,6 +13,8 @@ bool ' Use old disk-only driver on primary interface' CONFIG_BLK_DEV_HD_IDE bool ' Include IDE/ATAPI CDROM support' CONFIG_BLK_DEV_IDECD bool ' Include IDE/ATAPI TAPE support' CONFIG_BLK_DEV_IDETAPE + bool ' Include IDE/ATAPI FLOPPY support (new)' CONFIG_BLK_DEV_IDEFLOPPY + bool ' SCSI emulation support' CONFIG_BLK_DEV_IDESCSI bool ' Support removable IDE interfaces (PCMCIA)' CONFIG_BLK_DEV_IDE_PCMCIA bool ' CMD640 chipset bugfix/support' CONFIG_BLK_DEV_CMD640 if [ "$CONFIG_BLK_DEV_CMD640" = "y" ]; then diff -u --recursive --new-file v2.0.30/linux/drivers/block/Makefile linux/drivers/block/Makefile --- v2.0.30/linux/drivers/block/Makefile Sat Aug 10 00:03:14 1996 +++ linux/drivers/block/Makefile Mon Aug 4 11:45:55 1997 @@ -97,6 +97,10 @@ L_OBJS += ide-tape.o endif +ifeq ($(CONFIG_BLK_DEV_IDEFLOPPY),y) +L_OBJS += ide-floppy.o +endif + ifeq ($(CONFIG_BLK_DEV_XD),y) L_OBJS += xd.o else diff -u --recursive --new-file v2.0.30/linux/drivers/block/genhd.c linux/drivers/block/genhd.c --- v2.0.30/linux/drivers/block/genhd.c Tue Aug 20 23:18:07 1996 +++ linux/drivers/block/genhd.c Mon Aug 4 11:45:55 1997 @@ -331,7 +331,7 @@ && (q->sector & 63) == 1 && (q->end_sector & 63) == 63) { unsigned int heads = q->end_head + 1; - if (heads == 32 || heads == 64 || heads == 128) { + if (heads == 32 || heads == 64 || heads == 128 || heads == 255) { (void) ide_xlate_1024(dev, heads, " [PTBL]"); break; diff -u --recursive --new-file v2.0.30/linux/drivers/block/ide-cd.c linux/drivers/block/ide-cd.c --- v2.0.30/linux/drivers/block/ide-cd.c Tue Mar 11 13:28:36 1997 +++ linux/drivers/block/ide-cd.c Mon Aug 4 11:45:55 1997 @@ -125,7 +125,8 @@ * you must define IHAVEADOLPHIN) * Added identifier so new Sanyo CD-changer works * Better detection if door locking isn't supported - * + * 3.21 Jun 16,1997 -- Add work-around for GCD-R580B + * * NOTE: Direct audio reads will only work on some types of drive. * So far, i've received reports of success for Sony and Toshiba drives. * @@ -1141,6 +1142,11 @@ /* Number of sectors to transfer. */ nsect = rq->nr_sectors; +#if !STANDARD_ATAPI + if (nsect > drive->cdrom_info.max_sectors) + nsect = drive->cdrom_info.max_sectors; +#endif /* not STANDARD_ATAPI */ + /* Starting sector. */ sector = rq->sector; @@ -2674,6 +2680,8 @@ CDROM_CONFIG_FLAGS (drive)->drq_interrupt = 0; #if ! STANDARD_ATAPI + drive->cdrom_info.max_sectors = 252; + CDROM_CONFIG_FLAGS (drive)->old_readcd = 0; CDROM_CONFIG_FLAGS (drive)->toctracks_as_bcd = 0; CDROM_CONFIG_FLAGS (drive)->tocaddr_as_bcd = 0; @@ -2698,6 +2706,9 @@ /* Vertos 600 ESD. */ CDROM_CONFIG_FLAGS (drive)->toctracks_as_bcd = 1; } + + else if (strcmp (drive->id->model, "GCD-R580B") == 0) + drive->cdrom_info.max_sectors = 124; else if (strcmp (drive->id->model, "NEC CD-ROM DRIVE:260") == 0 && diff -u --recursive --new-file v2.0.30/linux/drivers/block/ide-floppy.c linux/drivers/block/ide-floppy.c --- v2.0.30/linux/drivers/block/ide-floppy.c Wed Dec 31 16:00:00 1969 +++ linux/drivers/block/ide-floppy.c Mon Aug 4 11:45:55 1997 @@ -0,0 +1,1388 @@ +/* + * linux/drivers/block/ide-floppy.c Version 0.7 - ALPHA Aug 4, 1997 + * + * Copyright (C) 1996, 1997 Gadi Oxman + */ + +/* + * IDE ATAPI floppy driver. + * + * The driver currently doesn't have any fancy features, just the bare + * minimum read/write support. + * + * Many thanks to Lode Leroy , who tested so many + * ALPHA patches to this driver on an EASYSTOR LS-120 ATAPI floppy drive. + * + * Ver 0.1 Oct 17 96 Initial test version, mostly based on ide-tape.c. + * Ver 0.2 Oct 31 96 Minor changes. + * Ver 0.3 Dec 2 96 Fixed error recovery bug. + * Ver 0.4 Jan 26 97 Add support for the HDIO_GETGEO ioctl. + * Ver 0.5 Feb 21 97 Add partitions support. + * Use the minimum of the LBA and CHS capacities. + * Avoid hwgroup->rq == NULL on the last irq. + * Fix potential null dereferencing with DEBUG_LOG. + * Ver 0.6 Jul 3 97 Limit max sectors per read/write command to 64. + * Ver 0.7 Aug 4 97 Increase irq timeout from 10 to 100 seconds. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * Main Linux ide driver include file + */ +#include "ide.h" + +/* + * The following are used to debug the driver. + */ +#define IDEFLOPPY_DEBUG_LOG 0 +#define IDEFLOPPY_DEBUG_INFO 0 +#define IDEFLOPPY_DEBUG_BUGS 1 + +/* + * After each failed packet command we issue a request sense command + * and retry the packet command IDEFLOPPY_MAX_PC_RETRIES times. + */ +#define IDEFLOPPY_MAX_PC_RETRIES 3 + +/* + * With each packet command, we allocate a buffer of + * IDEFLOPPY_PC_BUFFER_SIZE bytes. + */ +#define IDEFLOPPY_PC_BUFFER_SIZE 256 + +/* + * In various places in the driver, we need to allocate storage + * for packet commands and requests, which will remain valid while + * we leave the driver to wait for an interrupt or a timeout event. + */ +#define IDEFLOPPY_PC_STACK (10 + IDEFLOPPY_MAX_PC_RETRIES) + +/* + * Some drives fail read/write requests with 64 or more sectors. + */ +#define IDEFLOPPY_MAX_SECTORS 64 + +/* + * Some drives require a longer irq timeout. + */ +#define IDEFLOPPY_WAIT_CMD (10 * WAIT_CMD) + +/* + * Our view of a packet command. + */ +typedef struct idefloppy_packet_command_s { + u8 c[12]; /* Actual packet bytes */ + int retries; /* On each retry, we increment retries */ + int error; /* Error code */ + int request_transfer; /* Bytes to transfer */ + int actually_transferred; /* Bytes actually transferred */ + int buffer_size; /* Size of our data buffer */ + char *b_data; /* Pointer which runs on the buffers */ + int b_count; /* Missing/Available data on the current buffer */ + struct request *rq; /* The corresponding request */ + byte *buffer; /* Data buffer */ + byte *current_position; /* Pointer into the above buffer */ + void (*callback) (ide_drive_t *); /* Called when this packet command is completed */ + byte pc_buffer[IDEFLOPPY_PC_BUFFER_SIZE]; /* Temporary buffer */ + unsigned int flags; /* Status/Action bit flags */ +} idefloppy_pc_t; + +/* + * Packet command flag bits. + */ +#define PC_ABORT 0 /* Set when an error is considered normal - We won't retry */ +#define PC_DMA_RECOMMENDED 2 /* 1 when we prefer to use DMA if possible */ +#define PC_DMA_IN_PROGRESS 3 /* 1 while DMA in progress */ +#define PC_DMA_ERROR 4 /* 1 when encountered problem during DMA */ +#define PC_WRITING 5 /* Data direction */ + +/* + * Removable Block Access Capabilities Page + */ +typedef struct { + unsigned page_code :6; /* Page code - Should be 0x1b */ + unsigned reserved1_6 :1; /* Reserved */ + unsigned ps :1; /* Should be 0 */ + u8 page_length; /* Page Length - Should be 0xa */ + unsigned reserved2 :6; + unsigned srfp :1; /* Supports reporting progress of format */ + unsigned sflp :1; /* System floppy type device */ + unsigned tlun :3; /* Total logical units supported by the device */ + unsigned reserved3 :3; + unsigned sml :1; /* Single / Multiple lun supported */ + unsigned ncd :1; /* Non cd optical device */ + u8 reserved[8]; +} idefloppy_capabilities_page_t; + +/* + * Flexible disk page. + */ +typedef struct { + unsigned page_code :6; /* Page code - Should be 0x5 */ + unsigned reserved1_6 :1; /* Reserved */ + unsigned ps :1; /* The device is capable of saving the page */ + u8 page_length; /* Page Length - Should be 0x1e */ + u16 transfer_rate; /* In kilobits per second */ + u8 heads, sectors; /* Number of heads, Number of sectors per track */ + u16 sector_size; /* Byes per sector */ + u16 cyls; /* Number of cylinders */ + u8 reserved10[10]; + u8 motor_delay; /* Motor off delay */ + u8 reserved21[7]; + u16 rpm; /* Rotations per minute */ + u8 reserved30[2]; +} idefloppy_flexible_disk_page_t; + +/* + * Format capacity + */ +typedef struct { + u8 reserved[3]; + u8 length; /* Length of the following descriptors in bytes */ +} idefloppy_capacity_header_t; + +typedef struct { + u32 blocks; /* Number of blocks */ + unsigned dc :2; /* Descriptor Code */ + unsigned reserved :6; + u8 length_msb; /* Block Length (MSB)*/ + u16 length; /* Block Length */ +} idefloppy_capacity_descriptor_t; + +#define CAPACITY_INVALID 0x00 +#define CAPACITY_UNFORMATTED 0x01 +#define CAPACITY_CURRENT 0x02 +#define CAPACITY_NO_CARTRIDGE 0x03 + +/* + * Most of our global data which we need to save even as we leave the + * driver due to an interrupt or a timer event is stored in a variable + * of type idefloppy_floppy_t, defined below. + */ +typedef struct { + ide_drive_t *drive; + + idefloppy_pc_t *pc; /* Current packet command */ + idefloppy_pc_t *failed_pc; /* Last failed packet command */ + idefloppy_pc_t pc_stack[IDEFLOPPY_PC_STACK];/* Packet command stack */ + int pc_stack_index; /* Next free packet command storage space */ + struct request rq_stack[IDEFLOPPY_PC_STACK]; + int rq_stack_index; /* We implement a circular array */ + + /* + * Last error information + */ + byte sense_key, asc, ascq; + + /* + * Device information + */ + int blocks, block_size, bs_factor; /* Current format */ + idefloppy_capacity_descriptor_t capacity; /* Last format capacity */ + idefloppy_flexible_disk_page_t flexible_disk_page; /* Copy of the flexible disk page */ + + unsigned int flags; /* Status/Action flags */ +} idefloppy_floppy_t; + +/* + * Floppy flag bits values. + */ +#define IDEFLOPPY_DRQ_INTERRUPT 0 /* DRQ interrupt device */ +#define IDEFLOPPY_MEDIA_CHANGED 1 /* Media may have changed */ +#define IDEFLOPPY_USE_READ12 2 /* Use READ12/WRITE12 or READ10/WRITE10 */ + +/* + * ATAPI floppy drive packet commands + */ +#define IDEFLOPPY_FORMAT_UNIT_CMD 0x04 +#define IDEFLOPPY_INQUIRY_CMD 0x12 +#define IDEFLOPPY_MODE_SELECT_CMD 0x55 +#define IDEFLOPPY_MODE_SENSE_CMD 0x5a +#define IDEFLOPPY_READ10_CMD 0x28 +#define IDEFLOPPY_READ12_CMD 0xa8 +#define IDEFLOPPY_READ_CAPACITY_CMD 0x23 +#define IDEFLOPPY_REQUEST_SENSE_CMD 0x03 +#define IDEFLOPPY_PREVENT_REMOVAL_CMD 0x1e +#define IDEFLOPPY_SEEK_CMD 0x2b +#define IDEFLOPPY_START_STOP_CMD 0x1b +#define IDEFLOPPY_TEST_UNIT_READY_CMD 0x00 +#define IDEFLOPPY_VERIFY_CMD 0x2f +#define IDEFLOPPY_WRITE10_CMD 0x2a +#define IDEFLOPPY_WRITE12_CMD 0xaa +#define IDEFLOPPY_WRITE_VERIFY_CMD 0x2e + +/* + * Defines for the mode sense command + */ +#define MODE_SENSE_CURRENT 0x00 +#define MODE_SENSE_CHANGEABLE 0x01 +#define MODE_SENSE_DEFAULT 0x02 +#define MODE_SENSE_SAVED 0x03 + +/* + * Special requests for our block device strategy routine. + */ +#define IDEFLOPPY_FIRST_RQ 90 + +/* + * IDEFLOPPY_PC_RQ is used to queue a packet command in the request queue. + */ +#define IDEFLOPPY_PC_RQ 90 + +#define IDEFLOPPY_LAST_RQ 90 + +/* + * A macro which can be used to check if a given request command + * originated in the driver or in the buffer cache layer. + */ +#define IDEFLOPPY_RQ_CMD(cmd) ((cmd >= IDEFLOPPY_FIRST_RQ) && (cmd <= IDEFLOPPY_LAST_RQ)) + +/* + * Error codes which are returned in rq->errors to the higher part + * of the driver. + */ +#define IDEFLOPPY_ERROR_GENERAL 101 + +/* + * The ATAPI Status Register. + */ +typedef union { + unsigned all :8; + struct { + unsigned check :1; /* Error occurred */ + unsigned idx :1; /* Reserved */ + unsigned corr :1; /* Correctable error occurred */ + unsigned drq :1; /* Data is request by the device */ + unsigned dsc :1; /* Media access command finished */ + unsigned reserved5 :1; /* Reserved */ + unsigned drdy :1; /* Ignored for ATAPI commands (ready to accept ATA command) */ + unsigned bsy :1; /* The device has access to the command block */ + } b; +} idefloppy_status_reg_t; + +/* + * The ATAPI error register. + */ +typedef union { + unsigned all :8; + struct { + unsigned ili :1; /* Illegal Length Indication */ + unsigned eom :1; /* End Of Media Detected */ + unsigned abrt :1; /* Aborted command - As defined by ATA */ + unsigned mcr :1; /* Media Change Requested - As defined by ATA */ + unsigned sense_key :4; /* Sense key of the last failed packet command */ + } b; +} idefloppy_error_reg_t; + +/* + * ATAPI Feature Register + */ +typedef union { + unsigned all :8; + struct { + unsigned dma :1; /* Using DMA or PIO */ + unsigned reserved321 :3; /* Reserved */ + unsigned reserved654 :3; /* Reserved (Tag Type) */ + unsigned reserved7 :1; /* Reserved */ + } b; +} idefloppy_feature_reg_t; + +/* + * ATAPI Byte Count Register. + */ +typedef union { + unsigned all :16; + struct { + unsigned low :8; /* LSB */ + unsigned high :8; /* MSB */ + } b; +} idefloppy_bcount_reg_t; + +/* + * ATAPI Interrupt Reason Register. + */ +typedef union { + unsigned all :8; + struct { + unsigned cod :1; /* Information transferred is command (1) or data (0) */ + unsigned io :1; /* The device requests us to read (1) or write (0) */ + unsigned reserved :6; /* Reserved */ + } b; +} idefloppy_ireason_reg_t; + +/* + * ATAPI floppy Drive Select Register + */ +typedef union { + unsigned all :8; + struct { + unsigned sam_lun :3; /* Logical unit number */ + unsigned reserved3 :1; /* Reserved */ + unsigned drv :1; /* The responding drive will be drive 0 (0) or drive 1 (1) */ + unsigned one5 :1; /* Should be set to 1 */ + unsigned reserved6 :1; /* Reserved */ + unsigned one7 :1; /* Should be set to 1 */ + } b; +} idefloppy_drivesel_reg_t; + +/* + * ATAPI Device Control Register + */ +typedef union { + unsigned all :8; + struct { + unsigned zero0 :1; /* Should be set to zero */ + unsigned nien :1; /* Device interrupt is disabled (1) or enabled (0) */ + unsigned srst :1; /* ATA software reset. ATAPI devices should use the new ATAPI srst. */ + unsigned one3 :1; /* Should be set to 1 */ + unsigned reserved4567 :4; /* Reserved */ + } b; +} idefloppy_control_reg_t; + +/* + * The following is used to format the general configuration word of + * the ATAPI IDENTIFY DEVICE command. + */ +struct idefloppy_id_gcw { + unsigned packet_size :2; /* Packet Size */ + unsigned reserved234 :3; /* Reserved */ + unsigned drq_type :2; /* Command packet DRQ type */ + unsigned removable :1; /* Removable media */ + unsigned device_type :5; /* Device type */ + unsigned reserved13 :1; /* Reserved */ + unsigned protocol :2; /* Protocol type */ +}; + +/* + * INQUIRY packet command - Data Format + */ +typedef struct { + unsigned device_type :5; /* Peripheral Device Type */ + unsigned reserved0_765 :3; /* Peripheral Qualifier - Reserved */ + unsigned reserved1_6t0 :7; /* Reserved */ + unsigned rmb :1; /* Removable Medium Bit */ + unsigned ansi_version :3; /* ANSI Version */ + unsigned ecma_version :3; /* ECMA Version */ + unsigned iso_version :2; /* ISO Version */ + unsigned response_format :4; /* Response Data Format */ + unsigned reserved3_45 :2; /* Reserved */ + unsigned reserved3_6 :1; /* TrmIOP - Reserved */ + unsigned reserved3_7 :1; /* AENC - Reserved */ + u8 additional_length; /* Additional Length (total_length-4) */ + u8 rsv5, rsv6, rsv7; /* Reserved */ + u8 vendor_id[8]; /* Vendor Identification */ + u8 product_id[16]; /* Product Identification */ + u8 revision_level[4]; /* Revision Level */ + u8 vendor_specific[20]; /* Vendor Specific - Optional */ + u8 reserved56t95[40]; /* Reserved - Optional */ + /* Additional information may be returned */ +} idefloppy_inquiry_result_t; + +/* + * REQUEST SENSE packet command result - Data Format. + */ +typedef struct { + unsigned error_code :7; /* Current error (0x70) */ + unsigned valid :1; /* The information field conforms to SFF-8070i */ + u8 reserved1 :8; /* Reserved */ + unsigned sense_key :4; /* Sense Key */ + unsigned reserved2_4 :1; /* Reserved */ + unsigned ili :1; /* Incorrect Length Indicator */ + unsigned reserved2_67 :2; + u32 information __attribute__ ((packed)); + u8 asl; /* Additional sense length (n-7) */ + u32 command_specific; /* Additional command specific information */ + u8 asc; /* Additional Sense Code */ + u8 ascq; /* Additional Sense Code Qualifier */ + u8 replaceable_unit_code; /* Field Replaceable Unit Code */ + u8 reserved[3]; + u8 pad[2]; /* Padding to 20 bytes */ +} idefloppy_request_sense_result_t; + +/* + * Pages of the SELECT SENSE / MODE SENSE packet commands. + */ +#define IDEFLOPPY_CAPABILITIES_PAGE 0x1b +#define IDEFLOPPY_FLEXIBLE_DISK_PAGE 0x05 + +/* + * Mode Parameter Header for the MODE SENSE packet command + */ +typedef struct { + u16 mode_data_length; /* Length of the following data transfer */ + u8 medium_type; /* Medium Type */ + unsigned reserved3 :7; + unsigned wp :1; /* Write protect */ + u8 reserved[4]; +} idefloppy_mode_parameter_header_t; + +#define IDEFLOPPY_MIN(a,b) ((a)<(b) ? (a):(b)) +#define IDEFLOPPY_MAX(a,b) ((a)>(b) ? (a):(b)) + +/* + * Too bad. The drive wants to send us data which we are not ready to accept. + * Just throw it away. + */ +static void idefloppy_discard_data (ide_drive_t *drive, unsigned int bcount) +{ + while (bcount--) + IN_BYTE (IDE_DATA_REG); +} + +#if IDEFLOPPY_DEBUG_BUGS +static void idefloppy_write_zeros (ide_drive_t *drive, unsigned int bcount) +{ + while (bcount--) + OUT_BYTE (0, IDE_DATA_REG); +} +#endif /* IDEFLOPPY_DEBUG_BUGS */ + +/* + * idefloppy_end_request is used to finish servicing a request. + * + * For read/write requests, we will call ide_end_request to pass to the + * next buffer. + */ +void idefloppy_end_request (byte uptodate, ide_hwgroup_t *hwgroup) +{ + ide_drive_t *drive = hwgroup->drive; + idefloppy_floppy_t *floppy = drive->floppy; + struct request *rq = hwgroup->rq; + int error; + +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "Reached idefloppy_end_request\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + switch (uptodate) { + case 0: error = IDEFLOPPY_ERROR_GENERAL; break; + case 1: error = 0; break; + default: error = uptodate; + } + if (error) + floppy->failed_pc = NULL; + if (!IDEFLOPPY_RQ_CMD (rq->cmd)) { + ide_end_request (uptodate, hwgroup); + return; + } + rq->errors = error; + ide_end_drive_cmd (drive, 0, 0); +} + +static void idefloppy_input_buffers (ide_drive_t *drive, idefloppy_pc_t *pc, unsigned int bcount) +{ + struct request *rq = pc->rq; + struct buffer_head *bh = rq->bh; + int count; + + while (bcount) { + if (pc->b_count == bh->b_size) { + idefloppy_end_request (1, HWGROUP(drive)); + if ((bh = rq->bh) != NULL) + pc->b_count = 0; + } + if (bh == NULL) { + printk (KERN_ERR "%s: bh == NULL in idefloppy_input_buffers, bcount == %d\n", drive->name, bcount); + idefloppy_discard_data (drive, bcount); + return; + } + count = IDEFLOPPY_MIN (bh->b_size - pc->b_count, bcount); + atapi_input_bytes (drive, bh->b_data + pc->b_count, count); + bcount -= count; pc->b_count += count; + if (pc->b_count == bh->b_size) { + rq->sector += rq->current_nr_sectors; + rq->nr_sectors -= rq->current_nr_sectors; + } + } +} + +static void idefloppy_output_buffers (ide_drive_t *drive, idefloppy_pc_t *pc, unsigned int bcount) +{ + struct request *rq = pc->rq; + struct buffer_head *bh = rq->bh; + int count; + + while (bcount) { + if (!pc->b_count) { + idefloppy_end_request (1, HWGROUP(drive)); + if ((bh = rq->bh) != NULL) { + pc->b_data = bh->b_data; + pc->b_count = bh->b_size; + } + } + if (bh == NULL) { + printk (KERN_ERR "%s: bh == NULL in idefloppy_output_buffers, bcount == %d\n", drive->name, bcount); + idefloppy_write_zeros (drive, bcount); + return; + } + count = IDEFLOPPY_MIN (pc->b_count, bcount); + atapi_output_bytes (drive, pc->b_data, count); + bcount -= count; pc->b_data += count; pc->b_count -= count; + if (!pc->b_count) { + rq->sector += rq->current_nr_sectors; + rq->nr_sectors -= rq->current_nr_sectors; + } + } +} + +#ifdef CONFIG_BLK_DEV_TRITON +static void idefloppy_update_buffers (ide_drive_t *drive, idefloppy_pc_t *pc) +{ + struct request *rq = pc->rq; + struct buffer_head *bh = rq->bh; + + while ((bh = rq->bh) != NULL) + idefloppy_end_request (1, HWGROUP(drive)); +} +#endif /* CONFIG_BLK_DEV_TRITON */ + +/* + * idefloppy_queue_pc_head generates a new packet command request in front + * of the request queue, before the current request, so that it will be + * processed immediately, on the next pass through the driver. + */ +static void idefloppy_queue_pc_head (ide_drive_t *drive,idefloppy_pc_t *pc,struct request *rq) +{ + ide_init_drive_cmd (rq); + rq->buffer = (char *) pc; + rq->cmd = IDEFLOPPY_PC_RQ; + (void) ide_do_drive_cmd (drive, rq, ide_preempt); +} + +static idefloppy_pc_t *idefloppy_next_pc_storage (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + + if (floppy->pc_stack_index==IDEFLOPPY_PC_STACK) + floppy->pc_stack_index=0; + return (&floppy->pc_stack[floppy->pc_stack_index++]); +} + +static struct request *idefloppy_next_rq_storage (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + + if (floppy->rq_stack_index==IDEFLOPPY_PC_STACK) + floppy->rq_stack_index=0; + return (&floppy->rq_stack[floppy->rq_stack_index++]); +} + +/* + * idefloppy_analyze_error is called on each failed packet command retry + * to analyze the request sense. + */ +static void idefloppy_analyze_error (ide_drive_t *drive,idefloppy_request_sense_result_t *result) +{ + idefloppy_floppy_t *floppy = drive->floppy; + + floppy->sense_key = result->sense_key; floppy->asc = result->asc; floppy->ascq = result->ascq; +#if IDEFLOPPY_DEBUG_LOG + if (floppy->failed_pc) + printk (KERN_INFO "ide-floppy: pc = %x, sense key = %x, asc = %x, ascq = %x\n",floppy->failed_pc->c[0],result->sense_key,result->asc,result->ascq); + else + printk (KERN_INFO "ide-floppy: sense key = %x, asc = %x, ascq = %x\n",result->sense_key,result->asc,result->ascq); +#endif /* IDEFLOPPY_DEBUG_LOG */ +} + +static void idefloppy_request_sense_callback (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "ide-floppy: Reached idefloppy_request_sense_callback\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + if (!floppy->pc->error) { + idefloppy_analyze_error (drive,(idefloppy_request_sense_result_t *) floppy->pc->buffer); + idefloppy_end_request (1,HWGROUP (drive)); + } else { + printk (KERN_ERR "Error in REQUEST SENSE itself - Aborting request!\n"); + idefloppy_end_request (0,HWGROUP (drive)); + } +} + +/* + * General packet command callback function. + */ +static void idefloppy_pc_callback (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "ide-floppy: Reached idefloppy_pc_callback\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + idefloppy_end_request (floppy->pc->error ? 0:1, HWGROUP(drive)); +} + +/* + * idefloppy_init_pc initializes a packet command. + */ +static void idefloppy_init_pc (idefloppy_pc_t *pc) +{ + memset (pc->c, 0, 12); + pc->retries = 0; + pc->flags = 0; + pc->request_transfer = 0; + pc->buffer = pc->pc_buffer; + pc->buffer_size = IDEFLOPPY_PC_BUFFER_SIZE; + pc->b_data = NULL; + pc->callback = &idefloppy_pc_callback; +} + +static void idefloppy_create_request_sense_cmd (idefloppy_pc_t *pc) +{ + idefloppy_init_pc (pc); + pc->c[0] = IDEFLOPPY_REQUEST_SENSE_CMD; + pc->c[4] = 255; + pc->request_transfer = 18; + pc->callback = &idefloppy_request_sense_callback; +} + +/* + * idefloppy_retry_pc is called when an error was detected during the + * last packet command. We queue a request sense packet command in + * the head of the request list. + */ +static void idefloppy_retry_pc (ide_drive_t *drive) +{ + idefloppy_pc_t *pc; + struct request *rq; + idefloppy_error_reg_t error; + + error.all = IN_BYTE (IDE_ERROR_REG); + pc = idefloppy_next_pc_storage (drive); + rq = idefloppy_next_rq_storage (drive); + idefloppy_create_request_sense_cmd (pc); + idefloppy_queue_pc_head (drive, pc, rq); +} + +/* + * idefloppy_pc_intr is the usual interrupt handler which will be called + * during a packet command. + */ +static void idefloppy_pc_intr (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + idefloppy_status_reg_t status; + idefloppy_bcount_reg_t bcount; + idefloppy_ireason_reg_t ireason; + idefloppy_pc_t *pc=floppy->pc; + struct request *rq = pc->rq; + unsigned int temp; + +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "ide-floppy: Reached idefloppy_pc_intr interrupt handler\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + +#ifdef CONFIG_BLK_DEV_TRITON + if (test_bit (PC_DMA_IN_PROGRESS, &pc->flags)) { + if (HWIF(drive)->dmaproc(ide_dma_status_bad, drive)) { + set_bit (PC_DMA_ERROR, &pc->flags); + } else { + pc->actually_transferred=pc->request_transfer; + idefloppy_update_buffers (drive, pc); + } + (void) (HWIF(drive)->dmaproc(ide_dma_abort, drive)); /* End DMA */ +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "ide-floppy: DMA finished\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + } +#endif /* CONFIG_BLK_DEV_TRITON */ + + status.all = GET_STAT(); /* Clear the interrupt */ + + if (!status.b.drq) { /* No more interrupts */ +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "Packet command completed, %d bytes transferred\n", pc->actually_transferred); +#endif /* IDEFLOPPY_DEBUG_LOG */ + clear_bit (PC_DMA_IN_PROGRESS, &pc->flags); + + sti(); + + if (status.b.check || test_bit (PC_DMA_ERROR, &pc->flags)) { /* Error detected */ +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "ide-floppy: %s: I/O error, ",drive->name); +#endif /* IDEFLOPPY_DEBUG_LOG */ + rq->errors++; + if (pc->c[0] == IDEFLOPPY_REQUEST_SENSE_CMD) { + printk (KERN_ERR "ide-floppy: I/O error in request sense command\n"); + ide_do_reset (drive); + return; + } + idefloppy_retry_pc (drive); /* Retry operation */ + return; + } + pc->error = 0; + if (floppy->failed_pc == pc) + floppy->failed_pc=NULL; + pc->callback(drive); /* Command finished - Call the callback function */ + return; + } +#ifdef CONFIG_BLK_DEV_TRITON + if (clear_bit (PC_DMA_IN_PROGRESS, &pc->flags)) { + printk (KERN_ERR "ide-floppy: The floppy wants to issue more interrupts in DMA mode\n"); + printk (KERN_ERR "ide-floppy: DMA disabled, reverting to PIO\n"); + drive->using_dma=0; + ide_do_reset (drive); + return; + } +#endif /* CONFIG_BLK_DEV_TRITON */ + bcount.b.high=IN_BYTE (IDE_BCOUNTH_REG); /* Get the number of bytes to transfer */ + bcount.b.low=IN_BYTE (IDE_BCOUNTL_REG); /* on this interrupt */ + ireason.all=IN_BYTE (IDE_IREASON_REG); + + if (ireason.b.cod) { + printk (KERN_ERR "ide-floppy: CoD != 0 in idefloppy_pc_intr\n"); + ide_do_reset (drive); + return; + } + if (ireason.b.io == test_bit (PC_WRITING, &pc->flags)) { /* Hopefully, we will never get here */ + printk (KERN_ERR "ide-floppy: We wanted to %s, ", ireason.b.io ? "Write":"Read"); + printk (KERN_ERR "but the floppy wants us to %s !\n",ireason.b.io ? "Read":"Write"); + ide_do_reset (drive); + return; + } + if (!test_bit (PC_WRITING, &pc->flags)) { /* Reading - Check that we have enough space */ + temp = pc->actually_transferred + bcount.all; + if ( temp > pc->request_transfer) { + if (temp > pc->buffer_size) { + printk (KERN_ERR "ide-floppy: The floppy wants to send us more data than expected - discarding data\n"); + idefloppy_discard_data (drive,bcount.all); + ide_set_handler (drive,&idefloppy_pc_intr,IDEFLOPPY_WAIT_CMD); + return; + } +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_NOTICE "ide-floppy: The floppy wants to send us more data than expected - allowing transfer\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + } + } + if (test_bit (PC_WRITING, &pc->flags)) { + if (pc->buffer != NULL) + atapi_output_bytes (drive,pc->current_position,bcount.all); /* Write the current buffer */ + else + idefloppy_output_buffers (drive, pc, bcount.all); + } else { + if (pc->buffer != NULL) + atapi_input_bytes (drive,pc->current_position,bcount.all); /* Read the current buffer */ + else + idefloppy_input_buffers (drive, pc, bcount.all); + } + pc->actually_transferred+=bcount.all; /* Update the current position */ + pc->current_position+=bcount.all; + + ide_set_handler (drive,&idefloppy_pc_intr,IDEFLOPPY_WAIT_CMD); /* And set the interrupt handler again */ +} + +static void idefloppy_transfer_pc (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + idefloppy_ireason_reg_t ireason; + + if (ide_wait_stat (drive,DRQ_STAT,BUSY_STAT,WAIT_READY)) { + printk (KERN_ERR "ide-floppy: Strange, packet command initiated yet DRQ isn't asserted\n"); + return; + } + ireason.all=IN_BYTE (IDE_IREASON_REG); + if (!ireason.b.cod || ireason.b.io) { + printk (KERN_ERR "ide-floppy: (IO,CoD) != (0,1) while issuing a packet command\n"); + ide_do_reset (drive); + return; + } + ide_set_handler (drive, &idefloppy_pc_intr, IDEFLOPPY_WAIT_CMD); /* Set the interrupt routine */ + atapi_output_bytes (drive, floppy->pc->c, 12); /* Send the actual packet */ +} + +/* + * Issue a packet command + */ +static void idefloppy_issue_pc (ide_drive_t *drive, idefloppy_pc_t *pc) +{ + idefloppy_floppy_t *floppy = drive->floppy; + idefloppy_bcount_reg_t bcount; + int dma_ok = 0; + +#if IDEFLOPPY_DEBUG_BUGS + if (floppy->pc->c[0] == IDEFLOPPY_REQUEST_SENSE_CMD && pc->c[0] == IDEFLOPPY_REQUEST_SENSE_CMD) { + printk (KERN_ERR "ide-floppy: possible ide-floppy.c bug - Two request sense in serial were issued\n"); + } +#endif /* IDEFLOPPY_DEBUG_BUGS */ + + if (floppy->failed_pc == NULL && pc->c[0] != IDEFLOPPY_REQUEST_SENSE_CMD) + floppy->failed_pc=pc; + floppy->pc=pc; /* Set the current packet command */ + + if (pc->retries > IDEFLOPPY_MAX_PC_RETRIES || test_bit (PC_ABORT, &pc->flags)) { + /* + * We will "abort" retrying a packet command in case + * a legitimate error code was received. + */ + if (!test_bit (PC_ABORT, &pc->flags)) { + printk (KERN_ERR "ide-floppy: %s: I/O error, pc = %2x, key = %2x, asc = %2x, ascq = %2x\n", + drive->name, pc->c[0], floppy->sense_key, floppy->asc, floppy->ascq); + pc->error = IDEFLOPPY_ERROR_GENERAL; /* Giving up */ + } + floppy->failed_pc=NULL; + pc->callback(drive); + return; + } +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "Retry number - %d\n",pc->retries); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + pc->retries++; + pc->actually_transferred=0; /* We haven't transferred any data yet */ + pc->current_position=pc->buffer; + bcount.all=pc->request_transfer; /* Request to transfer the entire buffer at once */ + +#ifdef CONFIG_BLK_DEV_TRITON + if (clear_bit (PC_DMA_ERROR, &pc->flags)) { + printk (KERN_WARNING "ide-floppy: DMA disabled, reverting to PIO\n"); + drive->using_dma=0; + } + if (test_bit (PC_DMA_RECOMMENDED, &pc->flags) && drive->using_dma) + dma_ok=!HWIF(drive)->dmaproc(test_bit (PC_WRITING, &pc->flags) ? ide_dma_write : ide_dma_read, drive); +#endif /* CONFIG_BLK_DEV_TRITON */ + + OUT_BYTE (drive->ctl,IDE_CONTROL_REG); + OUT_BYTE (dma_ok ? 1:0,IDE_FEATURE_REG); /* Use PIO/DMA */ + OUT_BYTE (bcount.b.high,IDE_BCOUNTH_REG); + OUT_BYTE (bcount.b.low,IDE_BCOUNTL_REG); + OUT_BYTE (drive->select.all,IDE_SELECT_REG); + +#ifdef CONFIG_BLK_DEV_TRITON + if (dma_ok) { /* Begin DMA, if necessary */ + set_bit (PC_DMA_IN_PROGRESS, &pc->flags); + (void) (HWIF(drive)->dmaproc(ide_dma_begin, drive)); + } +#endif /* CONFIG_BLK_DEV_TRITON */ + + if (test_bit (IDEFLOPPY_DRQ_INTERRUPT, &floppy->flags)) { + ide_set_handler (drive, &idefloppy_transfer_pc, IDEFLOPPY_WAIT_CMD); + OUT_BYTE (WIN_PACKETCMD, IDE_COMMAND_REG); /* Issue the packet command */ + } else { + OUT_BYTE (WIN_PACKETCMD, IDE_COMMAND_REG); + idefloppy_transfer_pc (drive); + } +} + +static void idefloppy_rw_callback (ide_drive_t *drive) +{ +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "ide-floppy: Reached idefloppy_rw_callback\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + idefloppy_end_request(1, HWGROUP(drive)); + return; +} + +static void idefloppy_create_prevent_cmd (idefloppy_pc_t *pc, int prevent) +{ +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "ide-floppy: creating prevent removal command, prevent = %d\n", prevent); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + idefloppy_init_pc (pc); + pc->c[0] = IDEFLOPPY_PREVENT_REMOVAL_CMD; + pc->c[4] = prevent; +} + +static void idefloppy_create_read_capacity_cmd (idefloppy_pc_t *pc) +{ + idefloppy_init_pc (pc); + pc->c[0] = IDEFLOPPY_READ_CAPACITY_CMD; + pc->c[7] = 255; + pc->c[8] = 255; +} + +/* + * A mode sense command is used to "sense" floppy parameters. + */ +static void idefloppy_create_mode_sense_cmd (idefloppy_pc_t *pc, byte page_code, byte type) +{ + unsigned short length = sizeof (idefloppy_mode_parameter_header_t); + + idefloppy_init_pc (pc); + pc->c[0] = IDEFLOPPY_MODE_SENSE_CMD; + pc->c[1] = 0; + pc->c[2] = page_code + (type << 6); + + switch (page_code) { + case IDEFLOPPY_CAPABILITIES_PAGE: + length += 12; + break; + case IDEFLOPPY_FLEXIBLE_DISK_PAGE: + length += 32; + break; + default: + printk (KERN_ERR "ide-floppy: unsupported page code in create_mode_sense_cmd\n"); + } + put_unaligned (htons (length), (unsigned short *) &pc->c[7]); + pc->request_transfer = length; +} + +static void idefloppy_create_start_stop_cmd (idefloppy_pc_t *pc, int start) +{ + idefloppy_init_pc (pc); + pc->c[0] = IDEFLOPPY_START_STOP_CMD; + pc->c[4] = start; +} + +static void idefloppy_create_rw_cmd (idefloppy_floppy_t *floppy, idefloppy_pc_t *pc, struct request *rq, unsigned long sector) +{ + int block = sector / floppy->bs_factor; + int blocks = IDEFLOPPY_MIN(rq->nr_sectors / floppy->bs_factor, IDEFLOPPY_MAX_SECTORS); + +#if IDEFLOPPY_DEBUG_LOG + printk ("create_rw1%d_cmd: block == %d, blocks == %d\n", + 2 * test_bit (IDEFLOPPY_USE_READ12, &floppy->flags), block, blocks); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + idefloppy_init_pc (pc); + if (test_bit (IDEFLOPPY_USE_READ12, &floppy->flags)) { + pc->c[0] = rq->cmd == READ ? IDEFLOPPY_READ12_CMD : IDEFLOPPY_WRITE12_CMD; + put_unaligned (htonl (blocks), (unsigned int *) &pc->c[6]); + } else { + pc->c[0] = rq->cmd == READ ? IDEFLOPPY_READ10_CMD : IDEFLOPPY_WRITE10_CMD; + put_unaligned (htons (blocks), (unsigned short *) &pc->c[7]); + } + put_unaligned (htonl (block), (unsigned int *) &pc->c[2]); + pc->callback = &idefloppy_rw_callback; + pc->rq = rq; + pc->b_data = rq->buffer; + pc->b_count = rq->cmd == READ ? 0 : rq->bh->b_size; + if (rq->cmd == WRITE) + set_bit (PC_WRITING, &pc->flags); + pc->buffer = NULL; + pc->request_transfer = pc->buffer_size = blocks * floppy->block_size; + set_bit (PC_DMA_RECOMMENDED, &pc->flags); +} + +/* + * idefloppy_do_request is our request handling function. + */ +void idefloppy_do_request (ide_drive_t *drive, struct request *rq, unsigned long block) +{ + idefloppy_floppy_t *floppy = drive->floppy; + idefloppy_pc_t *pc; + +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "rq_status: %d, rq_dev: %u, cmd: %d, errors: %d\n",rq->rq_status,(unsigned int) rq->rq_dev,rq->cmd,rq->errors); + printk (KERN_INFO "sector: %ld, nr_sectors: %ld, current_nr_sectors: %ld\n",rq->sector,rq->nr_sectors,rq->current_nr_sectors); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + if (rq->errors >= ERROR_MAX) { + if (floppy->failed_pc != NULL) + printk (KERN_ERR "ide-floppy: %s: I/O error, pc = %2x, key = %2x, asc = %2x, ascq = %2x\n", + drive->name, floppy->failed_pc->c[0], floppy->sense_key, floppy->asc, floppy->ascq); + else + printk (KERN_ERR "ide-floppy: %s: I/O error\n", drive->name); + idefloppy_end_request (0, HWGROUP(drive)); + return; + } + switch (rq->cmd) { + case READ: + case WRITE: + if (rq->sector % floppy->bs_factor || rq->nr_sectors % floppy->bs_factor) { + printk ("%s: unsupported r/w request size\n", drive->name); + idefloppy_end_request (0, HWGROUP(drive)); + return; + } + pc = idefloppy_next_pc_storage (drive); + idefloppy_create_rw_cmd (floppy, pc, rq, block); + break; + case IDEFLOPPY_PC_RQ: + pc = (idefloppy_pc_t *) rq->buffer; + break; + default: + printk (KERN_ERR "ide-floppy: unsupported command %x in request queue\n", rq->cmd); + idefloppy_end_request (0,HWGROUP (drive)); + return; + } + pc->rq = rq; + idefloppy_issue_pc (drive, pc); +} + +/* + * idefloppy_queue_pc_tail adds a special packet command request to the + * tail of the request queue, and waits for it to be serviced. + */ +static int idefloppy_queue_pc_tail (ide_drive_t *drive,idefloppy_pc_t *pc) +{ + struct request rq; + + ide_init_drive_cmd (&rq); + rq.buffer = (char *) pc; + rq.cmd = IDEFLOPPY_PC_RQ; + return ide_do_drive_cmd (drive, &rq, ide_wait); +} + +/* + * Look at the flexible disk page parameters. We will ignore the CHS + * capacity parameters and use the LBA parameters instead. + */ +static int idefloppy_get_flexible_disk_page (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + idefloppy_pc_t pc; + idefloppy_mode_parameter_header_t *header; + idefloppy_flexible_disk_page_t *page; + int capacity, lba_capacity; + + idefloppy_create_mode_sense_cmd (&pc, IDEFLOPPY_FLEXIBLE_DISK_PAGE, MODE_SENSE_CURRENT); + if (idefloppy_queue_pc_tail (drive,&pc)) { + printk (KERN_ERR "ide-floppy: Can't get flexible disk page parameters\n"); + return 1; + } + header = (idefloppy_mode_parameter_header_t *) pc.buffer; + page = (idefloppy_flexible_disk_page_t *) (header + 1); + + page->transfer_rate = ntohs (page->transfer_rate); + page->sector_size = ntohs (page->sector_size); + page->cyls = ntohs (page->cyls); + page->rpm = ntohs (page->rpm); + capacity = page->cyls * page->heads * page->sectors * page->sector_size; + if (memcmp (page, &floppy->flexible_disk_page, sizeof (idefloppy_flexible_disk_page_t))) + printk (KERN_INFO "%s: %dkB, %d/%d/%d CHS, %d kBps, %d sector size, %d rpm\n", + drive->name, capacity / 1024, page->cyls, page->heads, page->sectors, + page->transfer_rate / 8, page->sector_size, page->rpm); + + floppy->flexible_disk_page = *page; + drive->bios_cyl = page->cyls; + drive->bios_head = page->heads; + drive->bios_sect = page->sectors; + lba_capacity = floppy->blocks * floppy->block_size; + if (capacity != lba_capacity) { + printk (KERN_NOTICE "%s: The drive reports both %d and %d bytes as its capacity\n", + drive->name, capacity, lba_capacity); + capacity = IDEFLOPPY_MIN(capacity, lba_capacity); + floppy->blocks = floppy->block_size ? capacity / floppy->block_size : 0; + } + return 0; +} + +/* + * Determine if a media is present in the floppy drive, and if so, + * its LBA capacity. + */ +static int idefloppy_get_capacity (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + idefloppy_pc_t pc; + idefloppy_capacity_header_t *header; + idefloppy_capacity_descriptor_t *descriptor; + int i, descriptors, rc = 1, blocks, length; + + drive->bios_cyl = 0; + drive->bios_head = drive->bios_sect = 0; + floppy->blocks = floppy->bs_factor = 0; + drive->part[0].nr_sects = 0; + + idefloppy_create_read_capacity_cmd (&pc); + if (idefloppy_queue_pc_tail (drive, &pc)) { + printk (KERN_ERR "ide-floppy: Can't get floppy parameters\n"); + return 1; + } + header = (idefloppy_capacity_header_t *) pc.buffer; + descriptors = header->length / sizeof (idefloppy_capacity_descriptor_t); + descriptor = (idefloppy_capacity_descriptor_t *) (header + 1); + for (i = 0; i < descriptors; i++, descriptor++) { + blocks = descriptor->blocks = ntohl (descriptor->blocks); + length = descriptor->length = ntohs (descriptor->length); + if (!i && descriptor->dc == CAPACITY_CURRENT) { + if (memcmp (descriptor, &floppy->capacity, sizeof (idefloppy_capacity_descriptor_t))) + printk (KERN_INFO "%s: %dkB, %d blocks, %d sector size\n", drive->name, blocks * length / 1024, blocks, length); + floppy->capacity = *descriptor; + if (!length || length % 512) + printk (KERN_ERR "%s: %d bytes block size not supported\n", drive->name, length); + else { + floppy->blocks = blocks; + floppy->block_size = length; + if ((floppy->bs_factor = length / 512) != 1) + printk (KERN_NOTICE "%s: warning: non 512 bytes block size not fully supported\n", drive->name); + rc = 0; + } + } +#if IDEFLOPPY_DEBUG_INFO + if (!i) printk (KERN_INFO "Descriptor 0 Code: %d\n", descriptor->dc); + printk (KERN_INFO "Descriptor %d: %dkB, %d blocks, %d sector size\n", i, blocks * length / 1024, blocks, length); +#endif /* IDEFLOPPY_DEBUG_INFO */ + } + (void) idefloppy_get_flexible_disk_page (drive); + drive->part[0].nr_sects = floppy->blocks * floppy->bs_factor; + return rc; +} + +/* + * Our special ide-floppy ioctl's. + * + * Currently there aren't any ioctl's. + */ +int idefloppy_ioctl (ide_drive_t *drive, struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return -EIO; +} + +/* + * Our open/release functions + */ +int idefloppy_open (struct inode *inode, struct file *filp, ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + idefloppy_pc_t pc; + +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "Reached idefloppy_open\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + MOD_INC_USE_COUNT; + if (drive->usage == 1) { + idefloppy_create_start_stop_cmd (&pc, 1); + (void) idefloppy_queue_pc_tail (drive, &pc); + if (idefloppy_get_capacity (drive)) { + drive->usage--; + MOD_DEC_USE_COUNT; + return -EIO; + } + set_bit (IDEFLOPPY_MEDIA_CHANGED, &floppy->flags); + idefloppy_create_prevent_cmd (&pc, 1); + (void) idefloppy_queue_pc_tail (drive, &pc); + check_disk_change(inode->i_rdev); + } + return 0; +} + +void idefloppy_release (struct inode *inode, struct file *filp, ide_drive_t *drive) +{ + idefloppy_pc_t pc; + +#if IDEFLOPPY_DEBUG_LOG + printk (KERN_INFO "Reached idefloppy_release\n"); +#endif /* IDEFLOPPY_DEBUG_LOG */ + + if (!drive->usage) { + invalidate_buffers (inode->i_rdev); + idefloppy_create_prevent_cmd (&pc, 0); + (void) idefloppy_queue_pc_tail (drive, &pc); + } + MOD_DEC_USE_COUNT; +} + +/* + * Check media change. Use a simple algorithm for now. + */ +int idefloppy_media_change (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + + return clear_bit (IDEFLOPPY_MEDIA_CHANGED, &floppy->flags); +} + +/* + * Return the current floppy capacity to ide.c. + */ +unsigned long idefloppy_capacity (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + unsigned long capacity = floppy->blocks * floppy->bs_factor; + + return capacity; +} + +/* + * idefloppy_identify_device checks if we can support a drive, + * based on the ATAPI IDENTIFY command results. + */ +int idefloppy_identify_device (ide_drive_t *drive,struct hd_driveid *id) +{ + struct idefloppy_id_gcw gcw; + idefloppy_floppy_t *floppy; +#if IDEFLOPPY_DEBUG_INFO + unsigned short mask,i; + char buffer[80]; +#endif /* IDEFLOPPY_DEBUG_INFO */ + + *((unsigned short *) &gcw) = id->config; + +#if IDEFLOPPY_DEBUG_INFO + printk (KERN_INFO "Dumping ATAPI Identify Device floppy parameters\n"); + switch (gcw.protocol) { + case 0: case 1: sprintf (buffer, "ATA");break; + case 2: sprintf (buffer, "ATAPI");break; + case 3: sprintf (buffer, "Reserved (Unknown to ide-floppy)");break; + } + printk (KERN_INFO "Protocol Type: %s\n", buffer); + switch (gcw.device_type) { + case 0: sprintf (buffer, "Direct-access Device");break; + case 1: sprintf (buffer, "Streaming Tape Device");break; + case 2: case 3: case 4: sprintf (buffer, "Reserved");break; + case 5: sprintf (buffer, "CD-ROM Device");break; + case 6: sprintf (buffer, "Reserved"); + case 7: sprintf (buffer, "Optical memory Device");break; + case 0x1f: sprintf (buffer, "Unknown or no Device type");break; + default: sprintf (buffer, "Reserved"); + } + printk (KERN_INFO "Device Type: %x - %s\n", gcw.device_type, buffer); + printk (KERN_INFO "Removable: %s\n",gcw.removable ? "Yes":"No"); + switch (gcw.drq_type) { + case 0: sprintf (buffer, "Microprocessor DRQ");break; + case 1: sprintf (buffer, "Interrupt DRQ");break; + case 2: sprintf (buffer, "Accelerated DRQ");break; + case 3: sprintf (buffer, "Reserved");break; + } + printk (KERN_INFO "Command Packet DRQ Type: %s\n", buffer); + switch (gcw.packet_size) { + case 0: sprintf (buffer, "12 bytes");break; + case 1: sprintf (buffer, "16 bytes");break; + default: sprintf (buffer, "Reserved");break; + } + printk (KERN_INFO "Command Packet Size: %s\n", buffer); + printk (KERN_INFO "Model: %s\n",id->model); + printk (KERN_INFO "Firmware Revision: %s\n",id->fw_rev); + printk (KERN_INFO "Serial Number: %s\n",id->serial_no); + printk (KERN_INFO "Write buffer size(?): %d bytes\n",id->buf_size*512); + printk (KERN_INFO "DMA: %s",id->capability & 0x01 ? "Yes\n":"No\n"); + printk (KERN_INFO "LBA: %s",id->capability & 0x02 ? "Yes\n":"No\n"); + printk (KERN_INFO "IORDY can be disabled: %s",id->capability & 0x04 ? "Yes\n":"No\n"); + printk (KERN_INFO "IORDY supported: %s",id->capability & 0x08 ? "Yes\n":"Unknown\n"); + printk (KERN_INFO "ATAPI overlap supported: %s",id->capability & 0x20 ? "Yes\n":"No\n"); + printk (KERN_INFO "PIO Cycle Timing Category: %d\n",id->tPIO); + printk (KERN_INFO "DMA Cycle Timing Category: %d\n",id->tDMA); + printk (KERN_INFO "Single Word DMA supported modes:\n"); + for (i=0,mask=1;i<8;i++,mask=mask << 1) { + if (id->dma_1word & mask) + printk (KERN_INFO " Mode %d%s\n", i, (id->dma_1word & (mask << 8)) ? " (active)" : ""); + } + printk (KERN_INFO "Multi Word DMA supported modes:\n"); + for (i=0,mask=1;i<8;i++,mask=mask << 1) { + if (id->dma_mword & mask) + printk (KERN_INFO " Mode %d%s\n", i, (id->dma_mword & (mask << 8)) ? " (active)" : ""); + } + if (id->field_valid & 0x0002) { + printk (KERN_INFO "Enhanced PIO Modes: %s\n",id->eide_pio_modes & 1 ? "Mode 3":"None"); + if (id->eide_dma_min == 0) + sprintf (buffer, "Not supported"); + else + sprintf (buffer, "%d ns",id->eide_dma_min); + printk (KERN_INFO "Minimum Multi-word DMA cycle per word: %s\n", buffer); + if (id->eide_dma_time == 0) + sprintf (buffer, "Not supported"); + else + sprintf (buffer, "%d ns",id->eide_dma_time); + printk (KERN_INFO "Manufacturer\'s Recommended Multi-word cycle: %s\n", buffer); + if (id->eide_pio == 0) + sprintf (buffer, "Not supported"); + else + sprintf (buffer, "%d ns",id->eide_pio); + printk (KERN_INFO "Minimum PIO cycle without IORDY: %s\n", buffer); + if (id->eide_pio_iordy == 0) + sprintf (buffer, "Not supported"); + else + sprintf (buffer, "%d ns",id->eide_pio_iordy); + printk (KERN_INFO "Minimum PIO cycle with IORDY: %s\n", buffer); + } else + printk (KERN_INFO "According to the device, fields 64-70 are not valid.\n"); +#endif /* IDEFLOPPY_DEBUG_INFO */ + + if (gcw.protocol != 2) + printk (KERN_ERR "ide-floppy: Protocol is not ATAPI\n"); + else if (gcw.device_type != 0) + printk (KERN_ERR "ide-floppy: Device type is not set to floppy\n"); + else if (!gcw.removable) + printk (KERN_ERR "ide-floppy: The removable flag is not set\n"); + else if (gcw.drq_type == 3) + printk (KERN_ERR "ide-floppy: Sorry, DRQ type %d not supported\n", gcw.drq_type); + else if (gcw.packet_size != 0) + printk (KERN_ERR "ide-floppy: Packet size is not 12 bytes long\n"); + else if ((floppy = (idefloppy_floppy_t *) kmalloc (sizeof (idefloppy_floppy_t), GFP_KERNEL)) == NULL) + printk (KERN_ERR "ide-floppy: %s: Can't allocate a floppy structure\n", drive->name); + else { + drive->floppy = floppy; + return 1; + } + printk (KERN_ERR "ide-floppy: %s: not supported by this version of ide-floppy\n", drive->name); + return 0; +} + +/* + * idefloppy_get_capabilities asks the floppy about its various + * parameters. + */ +static void idefloppy_get_capabilities (ide_drive_t *drive) +{ + idefloppy_pc_t pc; + idefloppy_mode_parameter_header_t *header; + idefloppy_capabilities_page_t *capabilities; + + idefloppy_create_mode_sense_cmd (&pc, IDEFLOPPY_CAPABILITIES_PAGE, MODE_SENSE_CURRENT); + if (idefloppy_queue_pc_tail (drive,&pc)) { + printk (KERN_ERR "ide-floppy: Can't get drive capabilities\n"); + return; + } + header = (idefloppy_mode_parameter_header_t *) pc.buffer; + capabilities = (idefloppy_capabilities_page_t *) (header + 1); + + if (!capabilities->sflp) + printk (KERN_INFO "%s: Warning - system floppy device bit is not set\n", drive->name); + +#if IDEFLOPPY_DEBUG_INFO + printk (KERN_INFO "Dumping the results of the MODE SENSE packet command\n"); + printk (KERN_INFO "Mode Parameter Header:\n"); + printk (KERN_INFO "Mode Data Length - %d\n",header->mode_data_length); + printk (KERN_INFO "Medium Type - %d\n",header->medium_type); + printk (KERN_INFO "WP - %d\n",header->wp); + + printk (KERN_INFO "Capabilities Page:\n"); + printk (KERN_INFO "Page code - %d\n",capabilities->page_code); + printk (KERN_INFO "Page length - %d\n",capabilities->page_length); + printk (KERN_INFO "PS - %d\n",capabilities->ps); + printk (KERN_INFO "System Floppy Type device - %s\n",capabilities->sflp ? "Yes":"No"); + printk (KERN_INFO "Supports Reporting progress of Format - %s\n",capabilities->srfp ? "Yes":"No"); + printk (KERN_INFO "Non CD Optical device - %s\n",capabilities->ncd ? "Yes":"No"); + printk (KERN_INFO "Multiple LUN support - %s\n",capabilities->sml ? "Yes":"No"); + printk (KERN_INFO "Total LUN supported - %s\n",capabilities->tlun ? "Yes":"No"); +#endif /* IDEFLOPPY_DEBUG_INFO */ +} + +/* + * Driver initialization. + */ +void idefloppy_setup (ide_drive_t *drive) +{ + idefloppy_floppy_t *floppy = drive->floppy; + struct idefloppy_id_gcw gcw; + + *((unsigned short *) &gcw) = drive->id->config; + drive->ready_stat = 0; + memset (floppy, 0, sizeof (idefloppy_floppy_t)); + floppy->drive = drive; + floppy->pc = floppy->pc_stack; + if (gcw.drq_type == 1) + set_bit (IDEFLOPPY_DRQ_INTERRUPT, &floppy->flags); + + idefloppy_get_capabilities (drive); + (void) idefloppy_get_capacity (drive); +} diff -u --recursive --new-file v2.0.30/linux/drivers/block/ide.c linux/drivers/block/ide.c --- v2.0.30/linux/drivers/block/ide.c Tue Nov 19 06:21:06 1996 +++ linux/drivers/block/ide.c Mon Aug 4 11:45:55 1997 @@ -1,5 +1,5 @@ /* - * linux/drivers/block/ide.c Version 5.52 Sep 24, 1996 + * linux/drivers/block/ide.c Version 5.53 Jun 24, 1997 * * Copyright (C) 1994-1996 Linus Torvalds & authors (see below) */ @@ -261,6 +261,17 @@ * change delay_10ms() to delay_50ms() to fix problems * Version 5.52 fix incorrect invalidation of removable devices * add "hdx=slow" command line option + * Version 5.53 add ATAPI floppy drive support + * change default media for type 0 to floppy + * add support for Exabyte Nest + * add missing set_blocksize() in revalidate_disk() + * handle bad status bit sequencing in ide_wait_stat() + * support partition table translations with 255 heads + * probe all interfaces by default + * add probe for the i82371AB chipset + * acknowledge media change on removable drives + * add work-around for BMI drives + * remove "LBA" from boot messages * * Some additional driver compile-time options are in ide.h * @@ -367,7 +378,6 @@ /* fill in any non-zero initial values */ hwif->index = index; - hwif->noprobe = (index > 1); hwif->io_base = default_io_base[index]; hwif->ctl_port = hwif->io_base ? hwif->io_base+0x206 : 0x000; #ifdef CONFIG_BLK_DEV_HD @@ -536,6 +546,29 @@ } /* + * The following routines are mainly used by the ATAPI drivers. + * + * These routines will round up any request for an odd number of bytes, + * so if an odd bytecount is specified, be sure that there's at least one + * extra byte allocated for the buffer. + */ +void atapi_input_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount) +{ + ++bytecount; + ide_input_data (drive, buffer, bytecount / 4); + if ((bytecount & 0x03) >= 2) + insw (IDE_DATA_REG, ((byte *)buffer) + (bytecount & ~0x03), 1); +} + +void atapi_output_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount) +{ + ++bytecount; + ide_output_data (drive, buffer, bytecount / 4); + if ((bytecount & 0x03) >= 2) + outsw (IDE_DATA_REG, ((byte *)buffer) + (bytecount & ~0x03), 1); +} + +/* * This should get invoked any time we exit the driver to * wait for an interrupt response from a drive. handler() points * at the appropriate code to handle the next interrupt, and a @@ -593,6 +626,10 @@ if (!drive->present) return 0; +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + if (drive->media == ide_floppy) + return idefloppy_capacity(drive); +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ if (drive->media != ide_disk) return 0x7fffffff; /* cdrom or tape */ drive->select.b.lba = 0; @@ -625,8 +662,13 @@ if (drive->present && drive->media == ide_tape) idetape_setup(drive); #endif /* CONFIG_BLK_DEV_IDETAPE */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + if (drive->present && drive->media == ide_floppy) + idefloppy_setup(drive); +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ drive->part[0].nr_sects = current_capacity(drive); - if (!drive->present || drive->media != ide_disk) { + if (!drive->present || (drive->media != ide_disk && drive->media != ide_floppy) || + !drive->part[0].nr_sects) { drive->part[0].start_sect = -1; /* skip partition check */ } } @@ -1005,6 +1047,8 @@ rq->errors = ERROR_MAX; else if (err & TRK0_ERR) /* help it find track zero */ rq->errors |= ERROR_RECAL; + else if (err & MC_ERR) + drive->special.b.mc = 1; } if ((stat & DRQ_STAT) && rq->cmd != WRITE) try_to_flush_leftover_data(drive); @@ -1017,9 +1061,20 @@ if (drive->media == ide_tape) { rq->errors = 0; idetape_end_request(0, HWGROUP(drive)); - } - else + } else #endif /* CONFIG_BLK_DEV_IDETAPE */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + if (drive->media == ide_floppy) { + rq->errors = 0; + idefloppy_end_request(0, HWGROUP(drive)); + } else +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ +#ifdef CONFIG_BLK_DEV_IDESCSI + if (drive->media == ide_scsi) { + rq->errors = 0; + idescsi_end_request(0, HWGROUP(drive)); + } else +#endif /* CONFIG_BLK_DEV_IDESCSI */ ide_end_request(0, HWGROUP(drive)); } else { @@ -1230,6 +1285,19 @@ } /* + * mc_intr() is invoked on completion of a WIN_ACKMC cmd. + */ +static void mc_intr (ide_drive_t *drive) +{ + byte stat = GET_STAT(); + + sti(); + if (!OK_STAT(stat,READY_STAT,BAD_STAT)) + ide_error(drive, "mc_intr", stat); + drive->special.b.mc = 0; +} + +/* * drive_cmd_intr() is invoked on completion of a special DRIVE_CMD. */ static void drive_cmd_intr (ide_drive_t *drive) @@ -1265,7 +1333,7 @@ #endif if (s->b.set_geometry) { s->b.set_geometry = 0; - if (drive->media == ide_disk) { + if (drive->media == ide_disk && !drive->no_geom) { OUT_BYTE(drive->sect,IDE_SECTOR_REG); OUT_BYTE(drive->cyl,IDE_LCYL_REG); OUT_BYTE(drive->cyl>>8,IDE_HCYL_REG); @@ -1291,6 +1359,10 @@ ide_cmd(drive, WIN_SETMULT, drive->mult_req, &set_multmode_intr); } else drive->mult_req = 0; + } else if (s->b.mc) { + s->b.mc = 0; + if (drive->media == ide_disk && !IS_PROMISE_DRIVE) + ide_cmd(drive, WIN_ACKMC, drive->sect, &mc_intr); } else if (s->all) { int special = s->all; s->all = 0; @@ -1314,27 +1386,24 @@ byte stat; unsigned long flags; -test: - udelay(1); /* spec allows drive 400ns to change "BUSY" */ - if (OK_STAT((stat = GET_STAT()), good, bad)) - return 0; /* fast exit for most frequent case */ - if (!(stat & BUSY_STAT)) { - ide_error(drive, "status error", stat); - return 1; - } - - save_flags(flags); - sti(); - timeout += jiffies; - do { - if (!((stat = GET_STAT()) & BUSY_STAT)) { - restore_flags(flags); - goto test; + udelay(1); /* spec allows drive 400ns to assert "BUSY" */ + if ((stat = GET_STAT()) & BUSY_STAT) { + save_flags(flags); + sti(); + timeout += jiffies; + while ((stat = GET_STAT()) & BUSY_STAT) { + if (jiffies > timeout) { + restore_flags(flags); + ide_error(drive, "status timeout", stat); + return 1; + } } - } while (jiffies <= timeout); - - restore_flags(flags); - ide_error(drive, "status timeout", GET_STAT()); + restore_flags(flags); + } + udelay(1); /* allow status to settle, then read it again */ + if (OK_STAT((stat = GET_STAT()), good, bad)) + return 0; + ide_error(drive, "status error", stat); return 1; } @@ -1532,6 +1601,16 @@ idetape_do_request (drive, rq, block); return; #endif /* CONFIG_BLK_DEV_IDETAPE */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + case ide_floppy: + idefloppy_do_request (drive, rq, block); + return; +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ +#ifdef CONFIG_BLK_DEV_IDESCSI + case ide_scsi: + idescsi_do_request (drive, rq, block); + return; +#endif /* CONFIG_BLK_DEV_IDESCSI */ default: printk("%s: media type %d not supported\n", @@ -1588,6 +1667,7 @@ if (rq != NULL && rq->rq_status != RQ_INACTIVE) goto got_rq; } while ((hwif = hwif->next) != hwgroup->next_hwif); + hwgroup->active = 0; return; /* no work left for this hwgroup */ } got_rq: @@ -1612,6 +1692,7 @@ if (hwgroup->handler == NULL) { ide_hwif_t *hgif = hwgroup->hwif; ide_hwif_t *hwif = hgif; + hwgroup->active = 1; do { disable_irq(hwif->irq); } while ((hwif = hwif->next) != hgif); @@ -1859,13 +1940,8 @@ if (cur_rq == NULL || action == ide_preempt) { rq->next = cur_rq; bdev->current_request = rq; - if (action == ide_preempt) { + if (action == ide_preempt) HWGROUP(drive)->rq = NULL; - } else - if (HWGROUP(drive)->rq == NULL) { /* is this necessary (?) */ - bdev->request_fn(); - cli(); - } } else { if (action == ide_wait || action == ide_end) { while (cur_rq->next != NULL) /* find end of list */ @@ -1874,6 +1950,10 @@ rq->next = cur_rq->next; cur_rq->next = rq; } + if (!HWGROUP(drive)->active) { + do_hwgroup_request(HWGROUP(drive)); + cli(); + } if (action == ide_wait && rq->rq_status != RQ_INACTIVE) down(&sem); /* wait for it to be serviced */ restore_flags(flags); @@ -1901,6 +1981,14 @@ if (drive->media == ide_tape) return idetape_blkdev_open (inode, filp, drive); #endif /* CONFIG_BLK_DEV_IDETAPE */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + if (drive->media == ide_floppy) + return idefloppy_open (inode, filp, drive); +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ +#ifdef CONFIG_BLK_DEV_IDESCSI + if (drive->media == ide_scsi) + return idescsi_open (inode, filp, drive); +#endif /* CONFIG_BLK_DEV_IDESCSI */ if (drive->removable && drive->usage == 1) { byte door_lock[] = {WIN_DOORLOCK,0,0,0}; struct request rq; @@ -1940,6 +2028,18 @@ return; } #endif /* CONFIG_BLK_DEV_IDETAPE */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + if (drive->media == ide_floppy) { + idefloppy_release (inode, file, drive); + return; + } +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ +#ifdef CONFIG_BLK_DEV_IDESCSI + if (drive->media == ide_scsi) { + idescsi_ide_release (inode, file, drive); + return; + } +#endif /* CONFIG_BLK_DEV_IDESCSI */ if (drive->removable && !drive->usage) { byte door_unlock[] = {WIN_DOORUNLOCK,0,0,0}; struct request rq; @@ -1985,13 +2085,14 @@ fsync_dev (devp); invalidate_inodes (devp); invalidate_buffers (devp); + set_blocksize(devp, 1024); } drive->part[p].start_sect = 0; drive->part[p].nr_sects = 0; }; drive->part[0].nr_sects = current_capacity(drive); - if (drive->media != ide_disk) + if ((drive->media != ide_disk && drive->media != ide_floppy) || !drive->part[0].nr_sects) drive->part[0].start_sect = -1; resetup_one_dev(HWIF(drive)->gd, drive->select.b.unit); @@ -2029,7 +2130,7 @@ case HDIO_GETGEO: { struct hd_geometry *loc = (struct hd_geometry *) arg; - if (!loc || drive->media != ide_disk) return -EINVAL; + if (!loc || (drive->media != ide_disk && drive->media != ide_floppy)) return -EINVAL; err = verify_area(VERIFY_WRITE, loc, sizeof(*loc)); if (err) return err; put_user(drive->bios_head, (byte *) &loc->heads); @@ -2223,6 +2324,14 @@ if (drive->media == ide_tape) return idetape_blkdev_ioctl(drive, inode, file, cmd, arg); #endif /* CONFIG_BLK_DEV_IDETAPE */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + if (drive->media == ide_floppy) + return idefloppy_ioctl(drive, inode, file, cmd, arg); +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ +#ifdef CONFIG_BLK_DEV_IDESCSI + if (drive->media == ide_scsi) + return idescsi_ioctl(drive, inode, file, cmd, arg); +#endif /* CONFIG_BLK_DEV_IDESCSI */ return -EPERM; } } @@ -2237,6 +2346,10 @@ if (drive->media == ide_cdrom) return ide_cdrom_check_media_change (drive); #endif /* CONFIG_BLK_DEV_IDECD */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + if (drive->media == ide_floppy) + return idefloppy_media_change (drive); +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ if (drive->removable) /* for disks */ return 1; /* always assume it was changed */ return 0; @@ -2307,6 +2420,9 @@ ide_fixstring (id->fw_rev, sizeof(id->fw_rev), bswap); ide_fixstring (id->serial_no, sizeof(id->serial_no), bswap); + if (strstr(id->model, "E X A B Y T E N E S T")) + return; + #ifdef CONFIG_BLK_DEV_IDEATAPI /* * Check for an ATAPI device @@ -2322,7 +2438,22 @@ } #endif /* CONFIG_BLK_DEV_PROMISE */ switch (type) { - case 0: /* Early cdrom models used zero */ + case 0: + if (!strstr(id->model, "oppy") && !strstr(id->model, "poyp") && !strstr(id->model, "ZIP")) + printk("cdrom or floppy?, assuming "); + if (drive->media != ide_cdrom) { +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + printk("FLOPPY drive\n"); + drive->media = ide_floppy; + if (idefloppy_identify_device(drive, id)) + drive->present = 1; + return; +#else + printk("FLOPPY "); + break; +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ + } + /* Early cdrom models used zero */ case 5: #ifdef CONFIG_BLK_DEV_IDECD printk ("CDROM drive\n"); @@ -2361,8 +2492,15 @@ printk("Type %d - Unknown device\n", type); return; } +#ifdef CONFIG_BLK_DEV_IDESCSI + printk("drive - enabling SCSI emulation\n"); + drive->media = ide_scsi; + drive->present = 1; + idescsi_setup(drive); +#else drive->present = 0; printk("- not supported by this kernel\n"); +#endif /* CONFIG_BLK_DEV_IDESCSI */ return; } #endif /* CONFIG_BLK_DEV_IDEATAPI */ @@ -2381,7 +2519,7 @@ return; } } - + drive->media = ide_disk; /* Extract geometry if we did not already have one for the drive */ if (!drive->present) { @@ -2431,9 +2569,13 @@ (void) current_capacity (drive); /* initialize LBA selection */ - printk ("%s: %.40s, %ldMB w/%dkB Cache, %sCHS=%d/%d/%d", + if (!strncmp(id->model, "BMI ", 4) && + strstr(id->model, " ENHANCED IDE ") && + drive->select.b.lba) + drive->no_geom = 1; + + printk ("%s: %.40s, %ldMB w/%dkB Cache, CHS=%d/%d/%d", drive->name, id->model, current_capacity(drive)/2048L, id->buf_size/2, - drive->select.b.lba ? "LBA, " : "", drive->bios_cyl, drive->bios_head, drive->bios_sect); drive->mult_count = 0; @@ -2607,6 +2749,34 @@ return rc; } +static void enable_nest (ide_drive_t *drive) +{ + unsigned long timeout; + + printk("%s: enabling %s -- ", HWIF(drive)->name, drive->id->model); + SELECT_DRIVE(HWIF(drive), drive); + delay_50ms(); + OUT_BYTE(EXABYTE_ENABLE_NEST, IDE_COMMAND_REG); + timeout = jiffies + WAIT_WORSTCASE; + do { + if (jiffies > timeout) { + printk("failed (timeout)\n"); + return; + } + delay_50ms(); + } while (GET_STAT() & BUSY_STAT); + delay_50ms(); + if (!OK_STAT(GET_STAT(), 0, BAD_STAT)) + printk("failed (status = 0x%02x)\n", GET_STAT()); + else + printk("success\n"); + if (do_probe(drive, WIN_IDENTIFY) >= 2) { /* if !(success||timed-out) */ +#ifdef CONFIG_BLK_DEV_IDEATAPI + (void) do_probe(drive, WIN_PIDENTIFY); /* look for ATAPI device */ +#endif /* CONFIG_BLK_DEV_IDEATAPI */ + } +} + /* * probe_for_drive() tests for existence of a given drive using do_probe(). * @@ -2622,6 +2792,8 @@ (void) do_probe(drive, WIN_PIDENTIFY); /* look for ATAPI device */ #endif /* CONFIG_BLK_DEV_IDEATAPI */ } + if (drive->id && strstr(drive->id->model, "E X A B Y T E N E S T")) + enable_nest(drive); if (!drive->present) return 0; /* drive not found */ if (drive->id == NULL) { /* identification failed? */ @@ -3250,6 +3422,7 @@ else hwgroup->drive = &hwif->drives[1]; hwgroup->poll_timeout = 0; + hwgroup->active = 0; init_timer(&hwgroup->timer); hwgroup->timer.function = &timer_expiry; hwgroup->timer.data = (unsigned long) hwgroup; @@ -3353,6 +3526,7 @@ */ ide_probe_pci (PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371_0, &ide_init_triton, 1); ide_probe_pci (PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371SB_1, &ide_init_triton, 0); + ide_probe_pci (PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB, &ide_init_triton, 0); #endif /* CONFIG_BLK_DEV_TRITON */ } #endif /* CONFIG_PCI */ diff -u --recursive --new-file v2.0.30/linux/drivers/block/ide.h linux/drivers/block/ide.h --- v2.0.30/linux/drivers/block/ide.h Tue Mar 11 15:39:13 1997 +++ linux/drivers/block/ide.h Mon Aug 4 15:03:06 1997 @@ -51,7 +51,8 @@ #endif #endif /* CONFIG_BLK_DEV_CMD640 */ -#if defined(CONFIG_BLK_DEV_IDECD) || defined(CONFIG_BLK_DEV_IDETAPE) +#if defined(CONFIG_BLK_DEV_IDECD) || defined(CONFIG_BLK_DEV_IDETAPE) || \ + defined(CONFIG_BLK_DEV_IDEFLOPPY) || defined(CONFIG_BLK_DEV_IDESCSI) #define CONFIG_BLK_DEV_IDEATAPI 1 #endif @@ -110,6 +111,9 @@ #define IDE_FEATURE_REG IDE_ERROR_REG #define IDE_COMMAND_REG IDE_STATUS_REG #define IDE_ALTSTATUS_REG IDE_CONTROL_REG +#define IDE_IREASON_REG IDE_NSECTOR_REG +#define IDE_BCOUNTL_REG IDE_LCYL_REG +#define IDE_BCOUNTH_REG IDE_HCYL_REG #ifdef REALLY_FAST_IO #define OUT_BYTE(b,p) outb((b),(p)) @@ -126,7 +130,7 @@ #define BAD_W_STAT (BAD_R_STAT | WRERR_STAT) #define BAD_STAT (BAD_R_STAT | DRQ_STAT) #define DRIVE_READY (READY_STAT | SEEK_STAT) -#define DATA_READY (DRIVE_READY | DRQ_STAT) +#define DATA_READY (DRQ_STAT) /* * Some more useful definitions @@ -285,6 +289,8 @@ /* The result of the last successful request sense command on this device. */ struct atapi_request_sense sense_data; + + int max_sectors; }; #endif /* CONFIG_BLK_DEV_IDECD */ @@ -293,7 +299,7 @@ * Now for the data we need to maintain per-drive: ide_drive_t */ -typedef enum {ide_disk, ide_cdrom, ide_tape} ide_media_t; +typedef enum {ide_disk, ide_cdrom, ide_tape, ide_floppy, ide_scsi} ide_media_t; typedef union { unsigned all : 8; /* all of the bits together */ @@ -302,7 +308,8 @@ unsigned recalibrate : 1; /* seek to cyl 0 */ unsigned set_multmode : 1; /* set multmode count */ unsigned set_tune : 1; /* tune interface for drive */ - unsigned reserved : 4; /* unused */ + unsigned mc : 1; /* acknowledge media change */ + unsigned reserved : 3; /* unused */ } b; } special_t; @@ -335,7 +342,8 @@ #if FAKE_FDISK_FOR_EZDRIVE unsigned remap_0_to_1 : 1; /* flag: partitioned with ezdrive */ #endif /* FAKE_FDISK_FOR_EZDRIVE */ - ide_media_t media; /* disk, cdrom, tape */ + unsigned no_geom : 1; /* flag: do not set geometry */ + ide_media_t media; /* disk, cdrom, tape, floppy */ select_t select; /* basic drive/head select reg value */ byte ctl; /* "normal" value for IDE_CONTROL_REG */ byte ready_stat; /* min status value for drive ready */ @@ -363,6 +371,12 @@ #ifdef CONFIG_BLK_DEV_IDETAPE idetape_tape_t tape; /* for ide-tape.c */ #endif /* CONFIG_BLK_DEV_IDETAPE */ +#ifdef CONFIG_BLK_DEV_IDEFLOPPY + void *floppy; /* for ide-floppy.c */ +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ +#ifdef CONFIG_BLK_DEV_IDESCSI + void *scsi; /* for ide-scsi.c */ +#endif /* CONFIG_BLK_DEV_IDESCSI */ } ide_drive_t; /* @@ -465,6 +479,7 @@ struct timer_list timer; /* failsafe timer */ struct request wrq; /* local copy of current write rq */ unsigned long poll_timeout; /* timeout value during long polls */ + int active; /* set when servicing requests */ } ide_hwgroup_t; /* @@ -503,6 +518,12 @@ void ide_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount); /* + * This is used for (nearly) all ATAPI data transfers from/to the IDE interface + */ +void atapi_input_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount); +void atapi_output_bytes (ide_drive_t *drive, void *buffer, unsigned int bytecount); + +/* * This is used on exit from the driver, to designate the next irq handler * and also to start the safety timer. */ @@ -692,6 +713,28 @@ void idetape_register_chrdev (void); #endif /* CONFIG_BLK_DEV_IDETAPE */ + +#ifdef CONFIG_BLK_DEV_IDEFLOPPY +int idefloppy_identify_device (ide_drive_t *drive,struct hd_driveid *id); +void idefloppy_setup (ide_drive_t *drive); +void idefloppy_do_request (ide_drive_t *drive, struct request *rq, unsigned long block); +void idefloppy_end_request (byte uptodate, ide_hwgroup_t *hwgroup); +int idefloppy_ioctl (ide_drive_t *drive, struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +int idefloppy_open (struct inode *inode, struct file *filp, ide_drive_t *drive); +void idefloppy_release (struct inode *inode, struct file *filp, ide_drive_t *drive); +int idefloppy_media_change (ide_drive_t *drive); +unsigned long idefloppy_capacity (ide_drive_t *drive); +#endif /* CONFIG_BLK_DEV_IDEFLOPPY */ + +#ifdef CONFIG_BLK_DEV_IDESCSI +void idescsi_setup (ide_drive_t *drive); +void idescsi_do_request (ide_drive_t *drive, struct request *rq, unsigned long block); +void idescsi_end_request (byte uptodate, ide_hwgroup_t *hwgroup); +int idescsi_ioctl (ide_drive_t *drive, struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg); +int idescsi_open (struct inode *inode, struct file *filp, ide_drive_t *drive); +void idescsi_ide_release (struct inode *inode, struct file *filp, ide_drive_t *drive); +#endif /* CONFIG_BLK_DEV_IDESCSI */ #ifdef CONFIG_BLK_DEV_TRITON void ide_init_triton (byte, byte); diff -u --recursive --new-file v2.0.30/linux/drivers/isdn/isdn_common.c linux/drivers/isdn/isdn_common.c --- v2.0.30/linux/drivers/isdn/isdn_common.c Tue Nov 12 22:36:19 1996 +++ linux/drivers/isdn/isdn_common.c Sun Aug 3 15:22:52 1997 @@ -1694,6 +1694,7 @@ q->next = p->next; else dev->infochain = (infostruct *) (p->next); + kfree(p); return; } q = p; diff -u --recursive --new-file v2.0.30/linux/drivers/net/auto_irq.c linux/drivers/net/auto_irq.c --- v2.0.30/linux/drivers/net/auto_irq.c Thu Feb 29 21:50:43 1996 +++ linux/drivers/net/auto_irq.c Mon Aug 4 12:10:18 1997 @@ -54,17 +54,23 @@ { irq_number = irq; set_bit(irq, (void *)&irq_bitmap); /* irq_bitmap |= 1 << irq; */ - disable_irq(irq); + /* This code used to disable the irq. However, the interrupt stub + * would then re-enable the interrupt with (potentially) disastrous + * consequences + */ + free_irq(irq, dev_id); return; } int autoirq_setup(int waittime) { - int i, mask; + int i; int timeout = jiffies + waittime; int boguscount = (waittime*loops_per_sec) / 100; irq_handled = 0; + irq_bitmap = 0; + for (i = 0; i < 16; i++) { if (test_bit(i, &irqs_busy) == 0 && request_irq(i, autoirq_probe, SA_INTERRUPT, "irq probe", NULL) == 0) @@ -72,22 +78,15 @@ } /* Update our USED lists. */ irqs_used |= ~irq_handled; - irq_number = 0; - irq_bitmap = 0; /* Hang out at least jiffies waiting for bogus IRQ hits. */ while (timeout > jiffies && --boguscount > 0) ; - for (i = 0, mask = 0x01; i < 16; i++, mask <<= 1) { - if (irq_bitmap & irq_handled & mask) { - irq_handled &= ~mask; -#ifdef notdef - printk(" Spurious interrupt on IRQ %d\n", i); -#endif - free_irq(i, NULL); - } - } + irq_handled &= ~irq_bitmap; + + irq_number = 0; /* We are interested in new interrupts from now on */ + return irq_handled; } @@ -102,6 +101,8 @@ while (timeout > jiffies && --boguscount > 0) if (irq_number) break; + + irq_handled &= ~irq_bitmap; /* This eliminates the already reset handlers */ /* Retract the irq handlers that we installed. */ for (i = 0; i < 16; i++) { diff -u --recursive --new-file v2.0.30/linux/drivers/net/lance32.c linux/drivers/net/lance32.c --- v2.0.30/linux/drivers/net/lance32.c Tue Apr 8 08:47:46 1997 +++ linux/drivers/net/lance32.c Mon Aug 4 12:11:05 1997 @@ -273,6 +273,9 @@ outw(0x0002, ioaddr+LANCE_ADDR); outw(0x0002, ioaddr+LANCE_BUS_IF); + /* Reset the LANCE - this should prevent any more interrupts from arriving */ + inw(ioaddr+LANCE_RESET); + if (lance32_debug > 0) printk(version); @@ -451,7 +454,7 @@ /* Transmitter timeout, serious problems. */ if (dev->tbusy) { int tickssofar = jiffies - dev->trans_start; - if (tickssofar < 20) + if (tickssofar < 60) /* It can take this long to run through the 16 retries */ return 1; outw(0, ioaddr+LANCE_ADDR); printk("%s: transmit timed out, status %4.4x, resetting.\n", @@ -469,8 +472,9 @@ lp->rx_ring[i].base, -lp->rx_ring[i].buf_length, lp->rx_ring[i].msg_length); for (i = 0 ; i < TX_RING_SIZE; i++) - printk("%s %08x %04x %04x", i & 0x3 ? "" : "\n ", + printk("%s %08x %04x %04x %08x ", i & 0x1 ? "" : "\n ", lp->tx_ring[i].base, -lp->tx_ring[i].length, + lp->tx_ring[i].status, lp->tx_ring[i].misc); printk("\n"); } @@ -633,6 +637,9 @@ dirty_tx += TX_RING_SIZE; } #endif + + if (dev->tbusy) + dev->trans_start = jiffies; /* We are just starting the next transmit */ if (lp->tx_full && dev->tbusy && dirty_tx > lp->cur_tx - TX_RING_SIZE + 2) { diff -u --recursive --new-file v2.0.30/linux/drivers/net/new_tunnel.c linux/drivers/net/new_tunnel.c --- v2.0.30/linux/drivers/net/new_tunnel.c Tue Apr 8 08:47:46 1997 +++ linux/drivers/net/new_tunnel.c Sun Aug 3 14:02:34 1997 @@ -267,6 +267,7 @@ /* Tack on our header */ new_skb->h.iph = (struct iphdr *) skb_push(new_skb, tunnel_hlen); + new_skb->mac.raw = new_skb->ip_hdr; /* Free the old packet, we no longer need it */ dev_kfree_skb(skb, FREE_WRITE); diff -u --recursive --new-file v2.0.30/linux/drivers/net/tulip.c linux/drivers/net/tulip.c --- v2.0.30/linux/drivers/net/tulip.c Tue Apr 8 08:47:46 1997 +++ linux/drivers/net/tulip.c Mon Aug 4 14:51:40 1997 @@ -1,7 +1,6 @@ -/* tulip.c: A DEC 21040 ethernet driver for linux. */ +/* tulip.c: A DEC 21040-family ethernet driver for linux. */ /* - NOTICE: this version works with kernels 1.1.82 and later only! - Written 1994,1995 by Donald Becker. + Written 1994-1997 by Donald Becker. This software may be used and distributed according to the terms of the GNU Public License, incorporated herein by reference. @@ -13,44 +12,60 @@ Center of Excellence in Space Data and Information Sciences Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 - Subscribe to linux-tulip@cesdis.gsfc.nasa.gov and linux-tulip-bugs@cesdis.gsfc.nasa.gov - for late breaking news and exciting develovements. + Support and updates available at + http://cesdis.gsfc.nasa.gov/linux/drivers/tulip.html */ -static char *version = -"tulip.c:v0.10 8/11/95 becker@cesdis.gsfc.nasa.gov\n" -" +0.72 4/17/96 " -"http://www.dsl.tutics.tut.ac.jp/~linux/tulip\n" -" +0.02 12/15/96 mjacob@feral.com (2.0.27)\n"; +static const char *version = "tulip.c:v0.78 7/25/97 becker@cesdis.gsfc.nasa.gov\n"; /* A few user-configurable values. */ -/* Default to using 10baseT (i.e. AUI/10base2/100baseT port) port. */ -#define TULIP_10TP_PORT 0 -#define TULIP_100TP_PORT 1 -#define TULIP_AUI_PORT 1 -#define TULIP_BNC_PORT 2 -#define TULIP_MAX_PORT 3 -#define TULIP_AUTO_PORT -1 +/* Set if the PCI BIOS detects the chips on a multiport board backwards. */ +#ifdef REVERSE_PROBE_ORDER +static int reverse_probe = 1; +#else +static int reverse_probe = 0; +#endif -#ifndef TULIP_PORT -#define TULIP_PORT TULIP_10TP_PORT +/* Keep the ring sizes a power of two for efficiency. + Making the Tx ring too large decreases the effectiveness of channel + bonding and packet priority. + There are no ill effects from too-large receive rings. */ +#define TX_RING_SIZE 16 +#define RX_RING_SIZE 16 + +/* Set the copy breakpoint for the copy-only-tiny-buffer Rx structure. */ +#define SKBUFF_RX_COPYBREAK 200 + +/* The following example shows how to always use the 10base2 port. */ +#ifdef notdef +#define TULIP_DEFAULT_MEDIA 1 /* 1 == 10base2 */ +#define TULIP_NO_MEDIA_SWITCH /* Don't switch from this port */ #endif /* Define to force full-duplex operation on all Tulip interfaces. */ /* #define TULIP_FULL_DUPLEX 1 */ -/* Define to fix port. */ -/* #define TULIP_FIX_PORT 1 */ - -/* Define to probe only first detected device */ -/*#define TULIP_MAX_CARDS 1*/ +/* Operational parameters that usually are not changed. */ +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((2000*HZ)/1000) +#include +#ifdef MODULE +#ifdef MODVERSIONS +#include +#endif #include +#include +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif #include #include #include +#include #include #include #include @@ -58,8 +73,7 @@ #include #include #include -#include -#include +#include /* Processor type for cache alignment. */ #include #include #include @@ -68,35 +82,116 @@ #include #include +/* Kernel compatibility defines, common to David Hind's PCMCIA package. + This is only in the support-all-kernels source code. */ +#include /* Evil, but neccessary */ + +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10300 +#define RUN_AT(x) (x) /* What to put in timer->expires. */ +#define DEV_ALLOC_SKB(len) alloc_skb(len, GFP_ATOMIC) +#define virt_to_bus(addr) ((unsigned long)addr) +#define bus_to_virt(addr) ((void*)addr) + +#else /* 1.3.0 and later */ +#define RUN_AT(x) (jiffies + (x)) +#define DEV_ALLOC_SKB(len) dev_alloc_skb(len + 2) +#endif + +#if defined (LINUX_VERSION_CODE) && LINUX_VERSION_CODE < 0x10338 +#ifdef MODULE +#if !defined(CONFIG_MODVERSIONS) && !defined(__NO_VERSION__) +char kernel_version[] = UTS_RELEASE; +#endif +#else +#undef MOD_INC_USE_COUNT +#define MOD_INC_USE_COUNT +#undef MOD_DEC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif +#endif /* 1.3.38 */ + +#if (LINUX_VERSION_CODE >= 0x10344) +#define NEW_MULTICAST +#include +#endif +#if (LINUX_VERSION_CODE >= 0x20100) +char kernel_version[] = UTS_RELEASE; +#endif +#ifdef SA_SHIRQ +#define IRQ(irq, dev_id, pt_regs) (irq, dev_id, pt_regs) +#else +#define IRQ(irq, dev_id, pt_regs) (irq, pt_regs) +#endif + +#if (LINUX_VERSION_CODE < 0x20123) +#define test_and_set_bit(val, addr) set_bit(val, addr) +#endif + +/* This my implementation of shared IRQs, now only used for 1.2.13. */ +#ifdef HAVE_SHARED_IRQ +#define USE_SHARED_IRQ +#include +#endif + /* The total size is unusually large: The 21040 aligns each of its 16 longword-wide registers on a quadword boundary. */ #define TULIP_TOTAL_SIZE 0x80 +#ifdef HAVE_DEVLIST +struct netdev_entry tulip_drv = +{"Tulip", tulip_pci_probe, TULIP_TOTAL_SIZE, NULL}; +#endif + +#ifdef TULIP_DEBUG +int tulip_debug = TULIP_DEBUG; +#else +int tulip_debug = 1; +#endif + /* Theory of Operation I. Board Compatibility -This device driver is designed for the DECchip 21040 "Tulip", Digital's -single-chip ethernet controller for PCI, as used on the SMC EtherPower -ethernet adapter. It also works with boards based the 21041 (new/experimental) -and 21140 (10/100mbps). +This device driver is designed for the DECchip "Tulip", Digital's +single-chip ethernet controllers for PCI. Supported members of the family +are the 21040, 21041, 21140, 21140A and 21142. These chips are used on +many PCI boards including the SMC EtherPower series. II. Board-specific settings PCI bus devices are configured by the system at boot time, so no jumpers -need to be set on the board. The system BIOS should be set to assign the -PCI INTA signal to an otherwise unused system IRQ line. While it's -physically possible to shared PCI interrupt lines, the kernel doesn't -support it. +need to be set on the board. The system BIOS preferably should assign the +PCI INTA signal to an otherwise unused system IRQ line. +Note: Kernel versions earlier than 1.3.73 do not support shared PCI +interrupt lines. III. Driver operation IIIa. Ring buffers + The Tulip can use either ring buffers or lists of Tx and Rx descriptors. -The current driver uses a statically allocated Rx ring of descriptors and -buffers, and a list of the Tx buffers. +This driver uses statically allocated rings of Rx and Tx descriptors, set at +compile time by RX/TX_RING_SIZE. This version of the driver allocates skbuffs +for the Rx ring buffers at open() time and passes the skb->data field to the +Tulip as receive data buffers. When an incoming frame is less than +SKBUFF_RX_COPYBREAK bytes long, a fresh skbuff is allocated and the frame is +copied to the new skbuff. When the incoming frame is larger, the skbuff is +passed directly up the protocol stack and replaced by a newly allocated +skbuff. + +The SKBUFF_RX_COPYBREAK value is chosen to trade-off the memory wasted by +using a full-sized skbuff for small frames vs. the copying costs of larger +frames. For small frames the copying cost is negligible (esp. considering +that we are pre-loading the cache with immediately useful header +information). For large frames the copying cost is non-trivial, and the +larger copy might flush the cache of useful data. A subtle aspect of this +choice is that the Tulip only receives into longword aligned buffers, thus +the IP header at offset 14 isn't longword aligned for further processing. +Copied frames are put into the new skbuff at an offset of "+2", thus copying +has the beneficial effect of aligning the IP header and preloading the +cache. IIIC. Synchronization The driver runs as two independent, single-threaded flows of control. One @@ -120,592 +215,807 @@ Thanks to Duke Kamstra of SMC for providing an EtherPower board. +IVb. References + +http://cesdis.gsfc.nasa.gov/linux/misc/NWay.html +http://www.digital.com (search for current 21*4* datasheets and "21X4 SROM") +http://www.national.com/pf/DP/DP83840.html + +IVc. Errata + The DEC databook doesn't document which Rx filter settings accept broadcast packets. Nor does it document how to configure the part to configure the serial subsystem for normal (vs. loopback) operation or how to have it autoswitch between internal 10baseT, SIA and AUI transceivers. -The databook claims that CSR13, CSR14, and CSR15 should each be the last -register of the set CSR12-15 written. Hmmm, now how is that possible? -*/ +The 21040 databook claims that CSR13, CSR14, and CSR15 should each be the last +register of the set CSR12-15 written. Hmmm, now how is that possible? */ + /* A few values that may be tweaked. */ -/* Keep the ring sizes a power of two for efficiency. */ -#define TX_RING_SIZE 4 -#define RX_RING_SIZE 4 #define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/ -/* This is a mysterious value that can be written to CSR11 in the 21040 - to detect a full-duplex frame. No one knows what it should be, but if - left at its default value some 10base2(!) packets trigger a - full-duplex-request interrupt. */ +/* This is a mysterious value that can be written to CSR11 in the 21040 (only) + to support a pre-NWay full-duplex signaling mechanism using short frames. + No one knows what it should be, but if left at its default value some + 10base2(!) packets trigger a full-duplex-request interrupt. */ #define FULL_DUPLEX_MAGIC 0x6969 +#ifndef PCI_VENDOR_ID_DEC /* Now defined in linux/pci.h */ +#define PCI_VENDOR_ID_DEC 0x1011 +#define PCI_DEVICE_ID_TULIP 0x0002 /* 21040. */ +#define PCI_DEVICE_ID_TULIP_FAST 0x0009 /* 21140. */ +#endif + +#ifndef PCI_DEVICE_ID_DEC_TULIP_PLUS +#define PCI_DEVICE_ID_DEC_TULIP_PLUS 0x0014 /* 21041. */ +#endif + +#ifndef PCI_DEVICE_ID_DEC_TULIP_21142 +#define PCI_DEVICE_ID_DEC_TULIP_21142 0x0019 +#endif + /* The rest of these values should never change. */ -#define PCI_DEVICE_ID_NONE 0xFFFF -#define ETHNAMSIZ 8 -#define ROUND_UP(size, n) ((size + n - 1) & ~(n - 1)) +static void tulip_timer(unsigned long data); + +/* A table describing the chip types. */ +static struct tulip_chip_table { + int device_id; + char *chip_name; + int flags; + void (*media_timer)(unsigned long data); +} tulip_tbl[] = { + { PCI_DEVICE_ID_DEC_TULIP, "DS21040 Tulip", 0, tulip_timer }, + { PCI_DEVICE_ID_DEC_TULIP_PLUS, "DS21041 Tulip", 0, tulip_timer }, + { PCI_DEVICE_ID_DEC_TULIP_FAST, "DS21140 Tulip", 0, tulip_timer }, /* + 21140A*/ + { PCI_DEVICE_ID_DEC_TULIP_21142, "DS21142 Tulip", 0, tulip_timer }, /* + 21143 */ + {0, 0, 0, 0}, +}; +/* This matches the table above. */ +enum chips { DC21040=0, DC21041=1, DC21140=2, DC21142=3, }; + +static const char * const medianame[] = { + "10baseT", "10base2", "AUI", "100baseTx", + "10baseT-FD", "100baseTx-FD", "100baseT4", "100baseFx", + "100baseFx-FD", "MII 10baseT", "MII 10baseT-FD", "MII", + "", "MII 100baseTx", "MII 100baseTx-FD", "MII 100baseT4", +}; +/* A full-duplex map for above. */ +static const char media_fd[] = +{0,0,0,0, 0xff,0xff,0,0, 0xff,0,0xff,0x01, 0,0,0xff,0 }; +/* 21041 transceiver register settings: 10-T, 10-2, AUI, 10-T, 10T-FD*/ +static u16 t21041_csr13[] = { 0xEF05, 0xEF09, 0xEF09, 0xEF01, 0xEF09, }; +static u16 t21041_csr14[] = { 0x7F3F, 0xF7FD, 0xF7FD, 0x7F3F, 0x7F3D, }; +static u16 t21041_csr15[] = { 0x0008, 0x0006, 0x000E, 0x0008, 0x0008, }; + +static u16 t21142_csr13[] = { 0x0001, 0x0009, 0x0009, 0x0001, 0x0001, }; +static u16 t21142_csr14[] = { 0xFFFF, 0x0705, 0x0705, 0x7F3F, 0x7F3D, }; +static u16 t21142_csr15[] = { 0x0008, 0x0006, 0x000E, 0x0008, 0x0008, }; /* Offsets to the Command and Status Registers, "CSRs". All accesses must be longword instructions and quadword aligned. */ enum tulip_offsets { - /* 21040 21041 21140 */ - CSR0=0, /* BUS mode */ - CSR1=0x08, /* TX poll demand */ - CSR2=0x10, /* RX poll demand */ - CSR3=0x18, /* RX ring base addr */ - CSR4=0x20, /* TX ring base addr */ - CSR5=0x28, /* Status */ - CSR6=0x30, /* Command mode */ - CSR7=0x38, /* Interrupt Mask */ - CSR8=0x40, /* Missed frame counter */ - CSR9=0x48, /* Eth.addrROM SROM mii SROM mii */ - CSR10=0x50, /* Diagn. boot ROM - */ - CSR11=0x58, /* Full duplex G.P. timer G.P. timer */ - CSR12=0x60, /* SIA status G.P. */ - CSR13=0x68, /* SIA connectivity - */ - CSR14=0x70, /* SIA TX/RX - */ - CSR15=0x78 /* SIA general watchdog */ -}; + CSR0=0, CSR1=0x08, CSR2=0x10, CSR3=0x18, CSR4=0x20, CSR5=0x28, + CSR6=0x30, CSR7=0x38, CSR8=0x40, CSR9=0x48, CSR10=0x50, CSR11=0x58, + CSR12=0x60, CSR13=0x68, CSR14=0x70, CSR15=0x78 }; -/* description of CSR0 bus mode register */ -#define TBMOD_RESERVED 0xfff80000 /* I don't know */ -#define TBMOD_RESET 0x00000001 -#define TBMOD_BIGENDIAN 0x00000080 -/* - Cache alignment bits 15:14 Burst length 13:8 - 0000 No alignment 0x00000000 unlimited 0800 8 longwords - 4000 8 longwords 0100 1 longword 1000 16 longwords - 8000 16 longwords 0200 2 longwords 2000 32 longwords - C000 32 longwords 0400 4 longwords -*/ -#define TBMOD_ALIGN0 0x00000000 /* no cache alignment */ -#define TBMOD_ALIGN8 0x00004000 /* 8 longwords */ -#define TBMOD_ALIGN16 0x00008000 -#define TBMOD_ALIGN32 (TBMOD_ALIGN8|TBMOD_ALIGN16) -#define TBMOD_BURST0 0x00000000 /* unlimited=rx buffer size */ -#define TBMOD_BURST1 0x00000100 /* 1 longwords */ -#define TBMOD_BURST2 0x00000200 -#define TBMOD_BURST4 0x00000400 -#define TBMOD_BURST8 0x00000800 -#define TBMOD_BURST16 0x00001000 -#define TBMOD_BURST32 0x00002000 - -/* description of CSR1 Tx poll demand register */ -/* description of CSR2 Rx poll demand register */ -#define TPOLL_START 0x00000001 /* ? */ -#define TPOLL_TRIGGER 0x00000000 /* ? */ - -/* description of CSR5 status register from de4x5.h */ -#define TSTAT_BUSERROR 0x03800000 -#define TSTAT_SYSERROR 0x00002000 -#define TSTAT_TxSTAT 0x00700000 -#define TSTAT_RxSTAT 0x000e0000 -#define TSTAT_LKFAIL 0x00001000 -#define TSTAT_NORINTR 0x00010000 /* Normal interrupt */ -#define TSTAT_ABNINTR 0x00008000 /* Abnormal interrupt */ -#define TSTAT_RxMISSED 0x00000100 /* Rx frame missed */ -#define TSTAT_RxUNABL 0x00000080 -#define TSTAT_RxINTR 0x00000040 -#define TSTAT_LKPASS 0x00000010 -#define TSTAT_TEXPIRED 0x00000800 /* Timer Expired */ -#define TSTAT_TxTOUT 0x00000008 -#define TSTAT_TxUNABL 0x00000004 -#define TSTAT_TxINTR 0x00000001 -#define TSTAT_CLEARINTR 0x0001ffff /* clear all interrupt sources */ - -/* description of CSR6 command mode register */ -#define TCMOD_SCRM 0x01000000 /* scrambler mode */ -#define TCMOD_PCS 0x00800000 /* PCS function */ -#define TCMOD_TxTHMODE 0x00400000 /* Tx threshold mode */ -#define TCMOD_SW100TP 0x00040000 /* 21140: 100MB */ -#define TCMOD_CAPTURE 0x00020000 /* capture effect */ -#define TCMOD_FULLDUPLEX 0x00000200 -#define TCMOD_TH128 0x00008000 /* 10 - 128 bytes threshold */ -#define TCMOD_TxSTART 0x00002000 -#define TCMOD_RxSTART 0x00000002 -#define TCMOD_ALLMCAST 0x00000080 /* pass all multicast */ -#define TCMOD_PROMISC 0x00000040 /* promisc */ -#define TCMOD_BOFFCOUNTER 0x00000020 /* backoff counter */ -#define TCMOD_INVFILTER 0x00000010 /* invert filtering */ -#define TCMOD_HONLYFILTER 0x00000004 /* hash only filtering */ -#define TCMOD_HPFILTER 0x00000001 /* hash/perfect Rx filtering */ -#define TCMOD_MODEMASK (TCMOD_ALLMCAST|TCMOD_PROMISC) -#define TCMOD_FILTERMASK (TCMOD_HONLYFILTER|TCMOD_HPFILTER|TCMOD_INVFILTER) -#define TCMOD_TRxSTART (TCMOD_TxSTART|TCMOD_RxSTART) -#define TCMOD_BASE (TCMOD_CAPTURE|TCMOD_BOFFCOUNTER) -#define TCMOD_10TP (TCMOD_TxTHMODE|TCMOD_BASE) -#define TCMOD_100TP (TCMOD_SCRM|TCMOD_PCS|TCMOD_SW100TP|TCMOD_BASE) -#define TCMOD_AUTO (TCMOD_SW100TP|TCMOD_TH128|TCMOD_10TP) - -/* description of CSR7 interrupt mask register */ -#define TINTR_ENABLE 0xFFFFFFFF -#define TINTR_DISABLE 0x00000000 - -/* description of CSR11 G.P. timer (21041/21140) register */ -#define TGEPT_COUNT 0x0001FFFF - -/* description of CSR12 SIA status(2104x)/GP(21140) register */ -#define TSIAS_CONERROR 0x00000002 /* connection error */ -#define TSIAS_LNKERROR 0x00000004 /* link error */ -#define TSIAS_ACTERROR 0x00000200 /* port Rx activity */ -#define TSIAS_RxACTIVE 0x00000100 /* port Rx activity */ - -#define TGEPR_LK10NG 0x00000080 /* 10Mbps N.G. (R) */ -#define TGEPR_LK100NG 0x00000040 /* 100Mbps N.G. (R) */ -#define TGEPR_DETECT 0x00000020 /* detect signal (R) */ -#define TGEPR_HALFDUPLEX 0x00000008 /* half duplex (W) */ -#define TGEPR_PHYLOOPBACK 0x00000004 /* PHY loopback (W) */ -#define TGEPR_FORCEALED 0x00000002 /* force activity LED on (W) */ -#define TGEPR_FORCE100 0x00000001 /* force 100Mbps mode */ - -/* description of CSR13 SIA connectivity register */ -#define TSIAC_OUTEN 0x0000e000 /* 21041: Output enable */ -#define TSIAC_SELED 0x00000f00 /* 21041: AUI or TP with LEDs */ -#define TSIAC_INEN 0x00001000 /* 21041: Input enable */ -#define TSIAC_NO10TP 0x00000008 /* 10baseT(0) or not(1) */ -#define TSIAC_CONFIG 0x00000004 /* Configuration */ -#define TSIAC_SWRESET 0x00000001 /* 21041: software reset */ -#define TSIAC_RESET 0x00000000 /* reset */ -#define TSIAC_C21041 (TSIAC_OUTEN|TSIAC_SELED|TSIAC_SWRESET) -#define TSIAC_C21040 TSIAC_CONFIG - -/* description of CSR14 SIA TX/RX register */ -#define TSIAX_NO10TP 0x0000f73d -#define TSIAX_10TP 0x0000ff3f - -/* description of CSR15 SIA general register */ -#define TSIAG_SWBNCAUI 0x00000008 /* BNC(0) or AUI(1) */ -#define TSIAG_BNC 0x00000006 -#define TSIAG_AUI (TSIAG_BNC|TSIAG_SWBNCAUI) -#define TSIAG_10TP 0x00000000 - -/* description of rx_ring.status */ -#define TRING_OWN 0x80000000 /* Owned by chip */ -#define TRING_CLEAR 0x00000000 /* clear */ -#define TRING_ERROR 0x00008000 /* error summary */ -#define TRING_ETxTO 0x00004000 /* Tx time out */ -#define TRING_ELCOLL 0x00000200 /* late collision */ -#define TRING_EFCOLL 0x00000100 /* fatal collision */ -#define TRING_ELCARR 0x00000800 /* carrier lost */ -#define TRING_ENCARR 0x00000400 /* no carrier */ -#define TRING_ENOHB 0x00000080 /* heartbeat fail */ -#define TRING_ELINK 0x00000004 /* link fail */ -#define TRING_EUFLOW 0x00000002 /* underflow */ - -#define TRING_ELEN 0x00004000 /* length error */ -#define TRING_FDESC 0x00000200 /* first descriptor */ -#define TRING_LDESC 0x00000100 /* last descriptor */ -#define TRING_ERUNT 0x00000800 /* runt frame */ -#define TRING_ELONG 0x00000080 /* frame too long */ -#define TRING_EWATCHDOG 0x00000010 /* receive watchdog */ -#define TRING_EDRBIT 0x00000004 /* dribble bit */ -#define TRING_ECRC 0x00000002 /* CRC error */ -#define TRING_EOVERFLOW 0x00000001 /* overflow */ - -#define TRING_RxDESCMASK (TRING_FDESC|TRING_LDESC) -#define TRING_RxLENGTH (TRING_ERUNT|TRING_ELONG|TRING_EWATCHDOG) -#define TRING_RxFRAME (TRING_EDRBIT) -#define TRING_RxCRC (TRING_ECRC) -#define TRING_RxFIFO (TRING_EOVERFLOW) -#define TRING_TxABORT (TRING_ETxTO|TRING_EFCOLL|TRING_ELINK) -#define TRING_TxCARR (TRING_ELCARR|TRING_ENCARR) -#define TRING_TxWINDOW (TRING_ELCOLL) -#define TRING_TxFIFO (TRING_EUFLOW) -#define TRING_TxHEARTBEAT (TRING_ENOHB) /* The Tulip Rx and Tx buffer descriptors. */ struct tulip_rx_desc { s32 status; s32 length; - u32 buffer1, buffer2; /* We use only buffer 1. */ + u32 buffer1, buffer2; }; struct tulip_tx_desc { s32 status; s32 length; - u32 buffer1, buffer2; /* We use only buffer 1. */ + u32 buffer1, buffer2; /* We use only buffer 1. */ +}; + +struct medialeaf { + u8 type; + u8 media; + unsigned char *leafdata; +}; + +struct mediatable { + u16 defaultmedia; + u8 leafcount, csr12dir; /* General purpose pin directions. */ + unsigned has_mii:1; + struct medialeaf mleaf[0]; +}; + +struct mediainfo { + struct mediainfo *next; + int info_type; + int index; + struct non_mii { char media; unsigned char csr12val; char bitnum, flags;} non_mii; + unsigned char *info; }; struct tulip_private { + char devname[8]; /* Used only for kernel debugging. */ + const char *product_name; + struct device *next_module; struct tulip_rx_desc rx_ring[RX_RING_SIZE]; struct tulip_tx_desc tx_ring[TX_RING_SIZE]; /* The saved address of a sent-in-place packet/buffer, for skfree(). */ struct sk_buff* tx_skbuff[TX_RING_SIZE]; - char rx_buffs[RX_RING_SIZE][PKT_BUF_SZ]; - /* temporary Rx buffers. */ + /* The addresses of receive-in-place skbuffs. */ + struct sk_buff* rx_skbuff[RX_RING_SIZE]; + char *rx_buffs; /* Address of temporary Rx buffers. */ + int setup_frame[48]; /* Pseudo-Tx frame to init address table. */ + int chip_id; + int revision; struct enet_statistics stats; - int setup_frame[48]; /* Pseudo-Tx frame to init address table. */ - void (*port_select)(struct device *dev); - int (*port_fail)(struct device *dev); - struct device *next_module; - char *signature; + struct timer_list timer; /* Media selection timer. */ unsigned int cur_rx, cur_tx; /* The next free ring entry */ unsigned int dirty_rx, dirty_tx; /* The ring entries to be free()ed. */ unsigned int tx_full:1; /* The Tx queue is full. */ unsigned int full_duplex:1; /* Full-duplex operation requested. */ - unsigned int port_fix:1; /* Fix if_port to specified port. */ + unsigned int default_port:4; /* Last dev->if_port value. */ + unsigned int media2:4; /* Secondary monitored media port. */ + unsigned int medialock:1; /* Don't sense media type. */ + unsigned int mediasense:1; /* Media sensing in progress. */ + unsigned int csr6; /* Current CSR6 control settings. */ + unsigned char eeprom[128]; /* Serial EEPROM contents. */ + struct mediatable *mtable; + int cur_index; /* Current media index. */ + int pad0, pad1; /* Used for 8-byte alignment */ }; -struct eeprom { - union { - struct { /* broken EEPROM structure */ - u_char addr[ETH_ALEN]; - } ng; - struct { /* DEC EtherWorks - and other cards which have correct eeprom structure */ - u_char dum1[20]; - u_char addr[ETH_ALEN]; - } ok; - } hw; -#define ng_addr hw.ng.addr -#define ok_addr hw.ok.addr -#define EE_SIGNLEN 14 /* should be 102 ? */ - u_char sign[EE_SIGNLEN]; -}; +#ifdef MODULE +/* Used to pass the full-duplex flag, etc. */ +static int options[] = {-1, -1, -1, -1, -1, -1, -1, -1}; +#endif -static int read_eeprom(int ioaddr, struct eeprom *eepp); +static struct device *tulip_probe1(struct device *dev, int ioaddr, int irq, + int chip_id, int options); +static void parse_eeprom(struct device *dev); +static int read_eeprom(int ioaddr, int location); +static int mdio_read(int ioaddr, int phy_id, int location); +static void select_media(struct device *dev, int startup); static int tulip_open(struct device *dev); +static void tulip_timer(unsigned long data); +static void tulip_tx_timeout(struct device *dev); static void tulip_init_ring(struct device *dev); static int tulip_start_xmit(struct sk_buff *skb, struct device *dev); static int tulip_rx(struct device *dev); -static void tulip_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void tulip_interrupt IRQ(int irq, void *dev_instance, struct pt_regs *regs); static int tulip_close(struct device *dev); static struct enet_statistics *tulip_get_stats(struct device *dev); +#ifdef NEW_MULTICAST static void set_multicast_list(struct device *dev); +#else +static void set_multicast_list(struct device *dev, int num_addrs, void *addrs); +#endif -#define generic21140_fail NULL -static void generic21040_select(struct device *dev); -static void generic21140_select(struct device *dev); -static void generic21041_select(struct device *dev); -static void auto21140_select(struct device *dev); -static void cogent21140_select(struct device *dev); -static int generic21040_fail(struct device *dev); -static int generic21041_fail(struct device *dev); + #ifdef MODULE /* A list of all installed Tulip devices, for removing the driver module. */ static struct device *root_tulip_dev = NULL; #endif -static struct { - void (*port_select)(struct device *dev); - int (*port_fail)(struct device *dev); - unsigned int vendor_id, device_id; - char *signature; - unsigned int array:1; -} cardVendor[] = { - {generic21140_select, generic21140_fail, - 0x0000c000, PCI_DEVICE_ID_DEC_TULIP_FAST, "smc9332", 0}, - {generic21041_select, generic21041_fail, - 0x0000c000, PCI_DEVICE_ID_DEC_TULIP_PLUS, "smc8432", 0}, - {generic21040_select, generic21040_fail, - 0x0000c000, PCI_DEVICE_ID_DEC_TULIP, "old smc8432", 0}, - {auto21140_select, generic21140_fail, - 0x0000f400, PCI_DEVICE_ID_DEC_TULIP_FAST, "LA100PCI", 0}, - {cogent21140_select, generic21140_fail, - 0x00009200, PCI_DEVICE_ID_DEC_TULIP_FAST, "cogent_em110", 0}, - {generic21040_select, generic21040_fail, - 0x00009200, PCI_DEVICE_ID_DEC_TULIP, "cogent_em96x", 1}, - {generic21140_select, generic21140_fail, - 0x0000f800, PCI_DEVICE_ID_DEC_TULIP_FAST, "DE500", 0}, - {generic21041_select, generic21041_fail, - 0x0000f800, PCI_DEVICE_ID_DEC_TULIP_PLUS, "DE450", 0}, - {generic21040_select, generic21040_fail, - 0x0000f800, PCI_DEVICE_ID_DEC_TULIP, "DE43x", 0}, - {generic21040_select, generic21040_fail, - 0x0040c700, PCI_DEVICE_ID_DEC_TULIP, "EN9400", 0}, - {generic21040_select, generic21040_fail, - 0x00c09500, PCI_DEVICE_ID_DEC_TULIP, "ZNYX312", 1}, - {generic21040_select, generic21040_fail, - 0x08002b00, PCI_DEVICE_ID_DEC_TULIP, "QSILVER's", 0}, - {generic21040_select, generic21040_fail, - 0, PCI_DEVICE_ID_DEC_TULIP, "21040", 0}, - {generic21140_select, generic21140_fail, - 0, PCI_DEVICE_ID_DEC_TULIP_FAST, "21140", 0}, - {generic21041_select, generic21041_fail, - 0, PCI_DEVICE_ID_DEC_TULIP_PLUS, "21041", 0}, - {NULL, NULL, 0, 0, "Unknown", 0} -}; +/* This 21040 probe no longer uses a large fixed contiguous Rx buffer region, + but now receives directly into full-sized skbuffs that are allocated + at open() time. + This allows the probe routine to use the old driver initialization + interface. */ - -/* Serial EEPROM section. - A "bit" grungy, but we work our way through bit-by-bit :->. */ -/* EEPROM_Ctrl bits. */ -#define EE_SHIFT_CLK 0x02 /* EEPROM shift clock. */ -#define EE_CS 0x01 /* EEPROM chip select. */ -#define EE_DATA_WRITE 0x04 /* EEPROM chip data in. */ -#define EE_WRITE_0 0x01 -#define EE_WRITE_1 0x05 -#define EE_DATA_READ 0x08 /* EEPROM chip data out. */ -#define EE_ENB (0x4800 | EE_CS) +int tulip_probe(struct device *dev) +{ + int cards_found = 0; + static int pci_index = 0; /* Static, for multiple probe calls. */ -/* The EEPROM commands include the alway-set leading bit. */ -#define EE_WRITE_CMD (5 << 6) -#define EE_READ_CMD (6 << 6) -#define EE_ERASE_CMD (7 << 6) + /* Ideally we would detect all network cards in slot order. That would + be best done a central PCI probe dispatch, which wouldn't work + well with the current structure. So instead we detect just the + Tulip cards in slot order. */ + + if (pcibios_present()) { + unsigned char pci_bus, pci_device_fn; + + for (;pci_index < 0xff; pci_index++) { + unsigned char pci_irq_line, pci_latency; + unsigned short pci_command, vendor, device; + unsigned int pci_ioaddr, chip_idx = 0; + + if (pcibios_find_class + (PCI_CLASS_NETWORK_ETHERNET << 8, + reverse_probe ? 0xfe - pci_index : pci_index, + &pci_bus, &pci_device_fn) != PCIBIOS_SUCCESSFUL) + if (reverse_probe) + continue; + else + break; + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_DEVICE_ID, &device); + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq_line); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + pci_ioaddr &= ~3; + + if (vendor != PCI_VENDOR_ID_DEC) + continue; + + for (chip_idx = 0; tulip_tbl[chip_idx].chip_name; chip_idx++) + if (device == tulip_tbl[chip_idx].device_id) + break; + if (tulip_tbl[chip_idx].chip_name == 0) { + printk("Unknown Digital PCI ethernet chip type %4.4x detected:" + " not configured.\n", device); + continue; + } + if (tulip_debug > 2) + printk("Found DEC PCI Tulip at I/O %#x, IRQ %d.\n", + pci_ioaddr, pci_irq_line); + + if (check_region(pci_ioaddr, TULIP_TOTAL_SIZE)) + continue; #ifdef MODULE -static int if_port=TULIP_AUTO_PORT; -#ifdef TULIP_FULL_DUPLEX -static int full_duplex=1; + dev = tulip_probe1(dev, pci_ioaddr, pci_irq_line, chip_idx, + options[cards_found]); #else -static int full_duplex=0; -#endif + dev = tulip_probe1(dev, pci_ioaddr, pci_irq_line, chip_idx, + dev ? dev->mem_start : 0); #endif -#define tio_write(val, port) outl(val, ioaddr + port) -#define tio_read(port) inl(ioaddr + port) + if (dev) { + /* Get and check the bus-master and latency values. */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + if ( ! (pci_command & PCI_COMMAND_MASTER)) { + printk(" PCI Master Bit has not been set! Setting...\n"); + pci_command |= PCI_COMMAND_MASTER; + pcibios_write_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, pci_command); + } + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < 10) { + printk(" PCI latency timer (CFLT) is unreasonably low at %d." + " Setting to 64 clocks.\n", pci_latency); + pcibios_write_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, 64); + } else if (tulip_debug > 1) + printk(" PCI latency timer (CFLT) is %#x.\n", pci_latency); + /* Bring the 21143 out power-down mode. */ + if (device == PCI_DEVICE_ID_DEC_TULIP_21142) + pcibios_write_config_dword(pci_bus, pci_device_fn, + 0x40, 0x40000000); + dev = 0; + cards_found++; + } + } + } -static void inline -tio_sia_write(u32 ioaddr, u32 val13, u32 val14, u32 val15) -{ - tio_write(0,CSR13); - tio_write(val15,CSR15); - tio_write(val14,CSR14); - tio_write(val13,CSR13); +#if defined (MODULE) + return cards_found; +#else + return 0; +#endif } -/* - card_type returns 1 if the card is 'etherarray' -*/ - -static int -card_type(struct tulip_private *tp, int device_id, int vendor_id) +static struct device *tulip_probe1(struct device *dev, int ioaddr, int irq, + int chip_id, int options) { - int n; + static int did_version = 0; /* Already printed version info. */ + struct tulip_private *tp; + /* See note below on the multiport cards. */ + static unsigned char last_phys_addr[6] = {0x00, 'L', 'i', 'n', 'u', 'x'}; + static int last_irq = 0; + int i; + unsigned short sum; - for (n = 0; cardVendor[n].device_id; n ++) - if (cardVendor[n].device_id == device_id - && (cardVendor[n].vendor_id == vendor_id - || cardVendor[n].vendor_id == 0)) break; - tp->port_select = cardVendor[n].port_select; - tp->port_fail = cardVendor[n].port_fail; - tp->signature = cardVendor[n].signature; - return(cardVendor[n].array ? 1: 0); -} + if (tulip_debug > 0 && did_version++ == 0) + printk(version); -static int -read_eeprom(int ioaddr, struct eeprom *eepp) -{ - int i, n; - unsigned short val = 0; - int read_cmd = EE_READ_CMD; - u_char *p=(u_char *)eepp; - - for (n = 0; n < sizeof(struct eeprom) / 2; n ++, read_cmd ++) { - tio_write(EE_ENB & ~EE_CS, CSR9); - tio_write(EE_ENB, CSR9); - - /* Shift the read command bits out. */ - for (i = 10; i >= 0; i--) { - short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0; - tio_write(EE_ENB | dataval, CSR9); - udelay(100); - tio_write(EE_ENB | dataval | EE_SHIFT_CLK, CSR9); - udelay(150); - tio_write(EE_ENB | dataval, CSR9); - udelay(250); - } - tio_write(EE_ENB, CSR9); - - for (i = 16; i > 0; i--) { - tio_write(EE_ENB | EE_SHIFT_CLK, CSR9); - udelay(100); - val = (val << 1) - | ((tio_read(CSR9) & EE_DATA_READ) ? 1 : 0); - tio_write(EE_ENB, CSR9); - udelay(100); - } - - /* Terminate the EEPROM access. */ - tio_write(EE_ENB & ~EE_CS, CSR9); - *p ++ = val; - *p ++ = val >> 8; - } - /* broken eeprom ? */ - p = (u_char *)eepp; - for (i = 0; i < 8; i ++) - if (p[i] != p[15 - i] || p[i] != p[16 + i]) return(0); - return(-1); /* broken */ -} + dev = init_etherdev(dev, 0); -/* Is this required ? */ -static int -generic21040_fail(struct device *dev) -{ - int ioaddr = dev->base_addr; + printk("%s: DEC %s at %#3x,", + dev->name, tulip_tbl[chip_id].chip_name, ioaddr); - return(tio_read(CSR12) & TSIAS_CONERROR); -} + /* Stop the chip's Tx and Rx processes. */ + outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6); + /* Clear the missed-packet counter. */ + (volatile)inl(ioaddr + CSR8); -static int -generic21041_fail(struct device *dev) -{ - int ioaddr = dev->base_addr; - u32 csr12 = tio_read(CSR12); + if (chip_id == DC21041) { + if (inl(ioaddr + CSR9) & 0x8000) { + printk(" 21040 compatible mode,"); + chip_id = DC21040; + } else { + printk(" 21041 mode,"); + } + } - return((!(csr12 & TSIAS_CONERROR) - || !(csr12 & TSIAS_LNKERROR)) ? 0: 1); -} + /* The station address ROM is read byte serially. The register must + be polled, waiting for the value to be read bit serially from the + EEPROM. + */ + sum = 0; + if (chip_id == DC21040) { + outl(0, ioaddr + CSR9); /* Reset the pointer with a dummy write. */ + for (i = 0; i < 6; i++) { + int value, boguscnt = 100000; + do + value = inl(ioaddr + CSR9); + while (value < 0 && --boguscnt > 0); + dev->dev_addr[i] = value; + sum += value & 0xff; + } + } else { /* Must be a new chip, with a serial EEPROM interface. */ + /* We read the whole EEPROM, and sort it out later. DEC has a + specification _Digital Semiconductor 21X4 Serial ROM Format_ + but early vendor boards just put the address in the first six + EEPROM locations. */ + unsigned char ee_data[128]; + int sa_offset = 0; + + for (i = 0; i < sizeof(ee_data)/2; i++) + ((u16 *)ee_data)[i] = read_eeprom(ioaddr, i); + + /* Detect the simple EEPROM format by the duplicated station addr. */ + for (i = 0; i < 8; i ++) + if (ee_data[i] != ee_data[16+i]) + sa_offset = 20; + for (i = 0; i < 6; i ++) { + dev->dev_addr[i] = ee_data[i + sa_offset]; + sum += ee_data[i + sa_offset]; + } + } + /* On the Zynx 315 Etherarray and other multiport boards only the + first Tulip has an EEPROM. + The addresses of the subsequent ports are derived from the first. + Many PCI BIOSes also incorrectly report the IRQ line, so we correct + that here as well. */ + if (sum == 0 || sum == 6*0xff) { + printk(" EEPROM not present,"); + for (i = 0; i < 5; i++) + dev->dev_addr[i] = last_phys_addr[i]; + dev->dev_addr[i] = last_phys_addr[i] + 1; + irq = last_irq; + } + for (i = 0; i < 6; i++) + printk(" %2.2x", last_phys_addr[i] = dev->dev_addr[i]); + printk(", IRQ %d\n", irq); + last_irq = irq; -static void -generic21040_select(struct device *dev) -{ - int ioaddr = dev->base_addr; - const char *media; + /* We do a request_region() only to register /proc/ioports info. */ + request_region(ioaddr, TULIP_TOTAL_SIZE, tulip_tbl[chip_id].chip_name); - dev->if_port &= 3; - switch (dev->if_port) - { - case TULIP_10TP_PORT: - media = "10baseT"; - break; - case TULIP_AUI_PORT: - media = "AUI"; - break; - case TULIP_BNC_PORT: - media = "BNC"; - break; - default: - media = "unknown type"; - break; - } - printk("%s: enabling %s port.\n", dev->name, media); - /* Set the full duplex match frame. */ - tio_write(FULL_DUPLEX_MAGIC, CSR11); - tio_write(TSIAC_RESET, CSR13); - /* Reset the serial interface */ - tio_write((dev->if_port ? TSIAC_NO10TP: 0) | TSIAC_C21040, CSR13); -} + dev->base_addr = ioaddr; + dev->irq = irq; -#if 0 -static void -generic_timer(struct device *dev, u32 count) -{ - int ioaddr = dev->base_addr; + /* Make certain the data structures are quadword aligned. */ + tp = (void *)(((long)kmalloc(sizeof(*tp), GFP_KERNEL | GFP_DMA) + 7) & ~7); + memset(tp, 0, sizeof(*tp)); + dev->priv = tp; - tio_write(count, CSR11); - while (tio_read(CSR11) & TGEPT_COUNT); -} +#ifdef MODULE + tp->next_module = root_tulip_dev; + root_tulip_dev = dev; #endif -static void -generic21041_select(struct device *dev) -{ - int ioaddr = dev->base_addr; - u32 tsiac = TSIAC_C21041; - u32 tsiax = TSIAX_10TP; - u32 tsiag = TSIAG_10TP; - - switch(dev->if_port) { - case TULIP_AUI_PORT: - tsiac |= TSIAC_NO10TP; - tsiax = TSIAX_NO10TP; - tsiag = TSIAG_AUI; + tp->chip_id = chip_id; + +#ifdef TULIP_FULL_DUPLEX + tp->full_duplex = 1; +#endif +#ifdef TULIP_DEFAULT_MEDIA + tp->default_port = TULIP_DEFAULT_MEDIA; +#endif +#ifdef TULIP_NO_MEDIA_SWITCH + tp->medialock = 1; +#endif + + /* The lower four bits are the media type. */ + if (options > 0) { + tp->full_duplex = (options & 16) ? 1 : 0; + tp->default_port = options & 15; + if (tp->default_port) + tp->medialock = 1; + } + + /* The Tulip-specific entries in the device structure. */ + dev->open = &tulip_open; + dev->hard_start_xmit = &tulip_start_xmit; + dev->stop = &tulip_close; + dev->get_stats = &tulip_get_stats; +#ifdef HAVE_MULTICAST + dev->set_multicast_list = &set_multicast_list; +#endif + + /* This is logically part of probe1(), but too complex to write inline. */ + if (chip_id != DC21040) + parse_eeprom(dev); + + /* Reset the xcvr interface and turn on heartbeat. */ + switch (chip_id) { + case DC21041: + outl(0x00000000, ioaddr + CSR13); + outl(0xFFFFFFFF, ioaddr + CSR14); + outl(0x00000008, ioaddr + CSR15); /* Listen on AUI also. */ + outl(inl(ioaddr + CSR6) | 0x200, ioaddr + CSR6); + outl(0x0000EF05, ioaddr + CSR13); break; - case TULIP_BNC_PORT: - tsiac |= TSIAC_NO10TP; - tsiax = TSIAX_NO10TP; - tsiag = TSIAG_BNC; + case DC21140: case DC21142: + if (tp->mtable) + outl(tp->mtable->csr12dir | 0x100, ioaddr + CSR12); break; - default: - dev->if_port = TULIP_10TP_PORT; + case DC21040: + outl(0x00000000, ioaddr + CSR13); + outl(0x00000004, ioaddr + CSR13); break; } - tio_sia_write(ioaddr, tsiac, tsiax, tsiag); - if (dev->start) - printk("%s: enabling %s port.\n", dev->name, - (dev->if_port == TULIP_AUI_PORT) ? "AUI": - (dev->if_port == TULIP_BNC_PORT) ? "BNC": "10TP"); -} -static void -auto21140_select(struct device *dev) -{ - int i, ioaddr = dev->base_addr; + return dev; +} + +/* Serial EEPROM section. */ +/* The main routine to parse the very complicated SROM structure. + Search www.digital.com for "21X4 SROM" to get details. + This code is very complex, and will require changes to support + additional cards, so I'll be verbose about what is going on. + */ + +/* Known cards that have old-style EEPROMs. */ +static struct fixups { + char *name; + unsigned char addr0, addr1, addr2; + u16 newtable[32]; /* Max length below. */ +} eeprom_fixups[] = { + {"Asante", 0, 0, 0x94, {0x1e00, 0x0000, 0x0800, 0x0100, 0x018c, + 0x0000, 0x0000, 0xe078, 0x0001, 0x0050, 0x0018 }}, + {"SMC9332DST", 0, 0, 0xC0, { 0x1e00, 0x0000, 0x0800, 0x021f, + 0x0000, 0x009E, /* 10baseT */ + 0x0903, 0x006D, /* 100baseTx */ }}, + {"Cogent EM100", 0, 0, 0x92, { 0x1e00, 0x0000, 0x0800, 0x013f, + 0x0103, 0x006D, /* 100baseTx */ }}, + {"Maxtech NX-110", 0, 0, 0xE8, { 0x1e00, 0x0000, 0x0800, 0x0313, + 0x1001, 0x009E, /* 10base2, CSR12 0x10*/ + 0x0000, 0x009E, /* 10baseT */ + 0x0303, 0x006D, /* 100baseTx, CSR12 0x03 */ }}, + {"Accton EN1207", 0, 0, 0xE8, { 0x1e00, 0x0000, 0x0800, 0x031F, + 0x1B01, 0x0000, /* 10base2, CSR12 0x1B */ + 0x1B03, 0x006D, /* 100baseTx, CSR12 0x1B */ + 0x0B00, 0x009E, /* 10baseT, CSR12 0x0B */ + }}, + {0, 0, 0, 0, {}}}; + +static const char * block_name[] = {"21140 non-MII", "21140 MII PHY", + "21142 non-MII PHY", "21142 MII PHY", }; + +#define EEPROM_SIZE 128 +static void parse_eeprom(struct device *dev) +{ + /* The last media info list parsed, for multiport boards. */ + static struct mediatable *last_mediatable = NULL; + static unsigned char *last_ee_data = NULL; + static controller_index = 0; struct tulip_private *tp = (struct tulip_private *)dev->priv; + int ioaddr = dev->base_addr; + unsigned char *ee_data = tp->eeprom; + int i; - /* kick port */ - tio_write(TPOLL_TRIGGER, CSR1); - tio_write(TINTR_ENABLE, CSR7); - tio_write(TCMOD_AUTO|TCMOD_TRxSTART, CSR6); - dev->if_port = !(tio_read(CSR12) & TGEPR_FORCEALED); - printk("%s: probed %s port.\n", - dev->name, dev->if_port ? "100TX" : "10TP"); - tio_write((dev->if_port ? TGEPR_FORCE100: 0) - | (tp->full_duplex ? 0:TGEPR_HALFDUPLEX), CSR12); - tio_write(TINTR_DISABLE, CSR7); - i = tio_read(CSR8) & 0xffff; - tio_write(TCMOD_AUTO, CSR6); -} + { + static int done_did_that = 0; + if (done_did_that++ == 0) + printk("\n THIS IS AN ALPHA TEST DRIVER.\n" + " The following verbose information is emitted for\n" + " bug reports on media selection.\n"); + } + tp->mtable = 0; + for (i = 0; i < EEPROM_SIZE/2; i++) + ((u16 *)ee_data)[i] = read_eeprom(ioaddr, i); -static void -cogent21140_select(struct device *dev) -{ - int ioaddr = dev->base_addr, csr6; - struct tulip_private *tp = (struct tulip_private *)dev->priv; - dev->if_port &= 1; - csr6 = tio_read(CSR6) & - ~(TCMOD_10TP|TCMOD_100TP|TCMOD_TRxSTART|TCMOD_SCRM); - /* Stop the transmit process. */ - tio_write(csr6 | TCMOD_RxSTART, CSR6); - printk("%s: enabling %s port.\n", - dev->name, dev->if_port ? "100baseTx" : "10baseT"); - /* Turn on the output drivers */ - tio_write(0x0000013F, CSR12); - tio_write((dev->if_port ? TGEPR_FORCE100: 0) - | (tp->full_duplex ? 0:TGEPR_HALFDUPLEX), CSR12); - tio_write((dev->if_port ? TCMOD_100TP: TCMOD_10TP) - | TCMOD_TRxSTART | TCMOD_TH128 | csr6, CSR6); + /* Detect an old-style (SA only) EEPROM layout: + memcmp(eedata, eedata+16, 8). */ + for (i = 0; i < 8; i ++) + if (ee_data[i] != ee_data[16+i]) + break; + if (i >= 8) { + if (ee_data[0] == 0xff) { + if (last_mediatable) { + controller_index++; + printk("%s: Controller %d of multiport board.\n", + dev->name, controller_index); + tp->mtable = last_mediatable; + ee_data = last_ee_data; + goto subsequent_board; + } else + printk("%s: Missing EEPROM, this device may not work correctly!\n", + dev->name); + return; + } + /* Do a fix-up based on the vendor half of the station address prefix. */ + for (i = 0; eeprom_fixups[i].name; i++) { + if (dev->dev_addr[0] == eeprom_fixups[i].addr0 + && dev->dev_addr[1] == eeprom_fixups[i].addr1 + && dev->dev_addr[2] == eeprom_fixups[i].addr2) { + if (dev->dev_addr[2] == 0xE8 && ee_data[0x1a] == 0x55) + i++; /* An Accton EN1207, not an outlaw Maxtech. */ + memcpy(ee_data + 26, eeprom_fixups[i].newtable, + sizeof(eeprom_fixups[i].newtable)); + printk("\n%s: Old format EEPROM on '%s' board. Using substitute" + " media control info.\n", + dev->name, eeprom_fixups[i].name); + break; + } + } + if (eeprom_fixups[i].name == NULL) { /* No fixup found. */ + printk("\n %s: Old style EEPROM -- no media selection information.\n", + dev->name); + return; + } + } + if (tulip_debug > 1) { + printk("\nread_eeprom:"); + for (i = 0; i < 64; i++) { + printk("%c%4.4x", (i & 7) == 0 ? '\n':' ', + read_eeprom(ioaddr, i)); + } + printk("\n"); + } + + controller_index = 0; + if (ee_data[19] > 1) { /* Multiport board. */ + last_ee_data = ee_data; + } +subsequent_board: + + if (tp->chip_id == DC21041) { + unsigned char *p = (void *)ee_data + ee_data[27 + controller_index*3]; + short media = *(u16 *)p; + int count = p[2]; + + printk("%s:21041 Media information at %d, default media %4.4x" + " (%s).\n", dev->name, ee_data[27], media, + media & 0x0800 ? "Autosense" : medianame[media & 15]); + for (i = 0; i < count; i++) { + unsigned char media_code = p[3 + i*7]; + unsigned short *csrvals = (unsigned short *)&p[3 + i*7 + 1]; + printk("%s: 21041 media %2.2x (%s)," + " csr13 %4.4x csr14 %4.4x csr15 %4.4x.\n", + dev->name, media_code & 15, medianame[media_code & 15], + csrvals[0], csrvals[1], csrvals[2]); + } + } else { + unsigned char *p = (void *)ee_data + ee_data[27]; + unsigned char csr12dir = 0; + int count; + struct mediatable *mtable; + short media = *((u16 *)p)++; + + if (tp->chip_id == DC21140) + csr12dir = *p++; + count = *p++; + mtable = (struct mediatable *) + kmalloc(sizeof(struct mediatable) + count*sizeof(struct medialeaf), + GFP_KERNEL); + if (mtable == NULL) + return; /* Horrible, impossible failure. */ + last_mediatable = tp->mtable = mtable; + mtable->defaultmedia = media; + mtable->leafcount = count; + mtable->csr12dir = csr12dir; + mtable->has_mii = 0; + + printk("%s: EEPROM default media type %s.\n", dev->name, + media & 0x0800 ? "Autosense" : medianame[media & 15]); + for (i = 0; i < count; i++) { + struct medialeaf *leaf = &mtable->mleaf[i]; + + if ((p[0] & 0x80) == 0) { /* 21140 Compact block. */ + leaf->type = 0; + leaf->media = p[0] & 0x3f; + leaf->leafdata = p; + p += 4; + } else { + leaf->type = p[1]; + if (p[1] & 1) { + mtable->has_mii = 1; + leaf->media = 11; + } else + leaf->media = p[2] & 0x0f; + leaf->leafdata = p + 2; + p += (p[0] & 0x3f) + 1; + } + if (tulip_debug > 1 && leaf->media == 11) { + unsigned char *bp = leaf->leafdata; + printk("%s: MII interface PHY %d, setup/reset sequences" + " %d/%d long, capabilities %2.2x %2.2x.\n", + dev->name, bp[0], bp[1], bp[1 + bp[1]*2], + bp[5 + bp[2 + bp[1]*2]*2], bp[4 + bp[2 + bp[1]*2]*2]); + if (tulip_debug > 2) { /* DEBUG only, should be > 3 */ + int mii_reg; + printk("%s: MII xcvr control registers:", dev->name); + for (mii_reg = 0; mii_reg < 32; mii_reg++) + printk(" %4.4x", mdio_read(ioaddr,bp[0], mii_reg)); + printk(".\n"); + } + } + + printk("%s: Index #%d - Media %s (#%d) described by a %s (%d) block.\n", + dev->name, i, medianame[leaf->media], leaf->media, + block_name[leaf->type], leaf->type); + } + } } +/* Reading a serial EEPROM is a "bit" grungy, but we work our way through:->.*/ -static void -generic21140_select(struct device *dev) -{ - int ioaddr = dev->base_addr, csr6; - struct tulip_private *tp = (struct tulip_private *)dev->priv; +/* EEPROM_Ctrl bits. */ +#define EE_SHIFT_CLK 0x02 /* EEPROM shift clock. */ +#define EE_CS 0x01 /* EEPROM chip select. */ +#define EE_DATA_WRITE 0x04 /* EEPROM chip data in. */ +#define EE_WRITE_0 0x01 +#define EE_WRITE_1 0x05 +#define EE_DATA_READ 0x08 /* EEPROM chip data out. */ +#define EE_ENB (0x4800 | EE_CS) + +/* Delay between EEPROM clock transitions. + The 1.2 code is a "nasty" timing loop, but PC compatible machines are + *supposed* to delay an ISA-compatible period for the SLOW_DOWN_IO macro. */ +#ifdef _LINUX_DELAY_H +#define eeprom_delay(nanosec) udelay((nanosec + 999)/1000) +#else +#define eeprom_delay(nanosec) do { int _i = 3; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0) +#endif - dev->if_port &= 1; - csr6 = tio_read(CSR6) & - ~(TCMOD_10TP|TCMOD_100TP|TCMOD_TRxSTART|TCMOD_SCRM); +/* The EEPROM commands include the alway-set leading bit. */ +#define EE_WRITE_CMD (5 << 6) +#define EE_READ_CMD (6 << 6) +#define EE_ERASE_CMD (7 << 6) - /* Stop the transmit process. */ - tio_write(csr6 | TCMOD_RxSTART, CSR6); - if (dev->start) - printk("%s: enabling %s port.\n", - dev->name, dev->if_port ? "100TX" : "10TP"); - tio_write((dev->if_port ? TCMOD_100TP: TCMOD_10TP) - | TCMOD_TRxSTART | TCMOD_TH128 | csr6, CSR6); - tio_write((dev->if_port ? TGEPR_FORCE100: 0) - | (tp->full_duplex ? 0:TGEPR_HALFDUPLEX), CSR12); +static int read_eeprom(int ioaddr, int location) +{ + int i; + unsigned short retval = 0; + int ee_addr = ioaddr + CSR9; + int read_cmd = location | EE_READ_CMD; + + outl(EE_ENB & ~EE_CS, ee_addr); + outl(EE_ENB, ee_addr); + + /* Shift the read command bits out. */ + for (i = 10; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0; + outl(EE_ENB | dataval, ee_addr); + eeprom_delay(100); + outl(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr); + eeprom_delay(150); + outl(EE_ENB | dataval, ee_addr); /* Finish EEPROM a clock tick. */ + eeprom_delay(250); + } + outl(EE_ENB, ee_addr); + + for (i = 16; i > 0; i--) { + outl(EE_ENB | EE_SHIFT_CLK, ee_addr); + eeprom_delay(100); + retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0); + outl(EE_ENB, ee_addr); + eeprom_delay(100); + } + + /* Terminate the EEPROM access. */ + outl(EE_ENB & ~EE_CS, ee_addr); + return retval; +} + +/* Read and write the MII registers using software-generated serial + MDIO protocol. It is just different enough from the EEPROM protocol + to not share code. The maxium data clock rate is 2.5 Mhz. */ +#define MDIO_SHIFT_CLK 0x10000 +#define MDIO_DATA_WRITE 0x20000 +#define MDIO_ENB 0x40000 +#define MDIO_DATA_READ 0x80000 +static int mdio_read(int ioaddr, int phy_id, int location) +{ + int i; + int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; + unsigned short retval = 0; + int ee_addr = ioaddr + CSR9; + + /* Shift the read command bits out. */ + for (i = 18; i >= 0; i--) { + int dataval = + (read_cmd & (1 << i)) ? MDIO_DATA_WRITE : 0; + + outl(MDIO_ENB | dataval, ee_addr); + eeprom_delay(100); + outl(MDIO_ENB | dataval | MDIO_SHIFT_CLK, ee_addr); + eeprom_delay(250); + outl(MDIO_ENB | dataval, ee_addr); + eeprom_delay(150); + } + + for (i = 16; i > 0; i--) { + outl(MDIO_SHIFT_CLK, ee_addr); + eeprom_delay(250); + retval = (retval << 1) | ((inl(ee_addr) & MDIO_DATA_READ) ? 1 : 0); + outl(0, ee_addr); + eeprom_delay(250); + } + return retval; } + static int tulip_open(struct device *dev) { struct tulip_private *tp = (struct tulip_private *)dev->priv; int ioaddr = dev->base_addr; - int i; + int i = 0; - /* Reset the chip, holding bit 0 set at least 10 PCI cycles. */ - tio_write(tio_read(CSR0)|TBMOD_RESET, CSR0); - udelay(1000); - /* Deassert reset. Set 8 longword cache alignment, 8 longword burst. - -> Set 32 longword cache alignment, unlimited longword burst ? + /* On some chip revs we must set the MII/SYM port before the reset!? */ + if (tp->mtable && tp->mtable->has_mii) + outl(0x00040000, ioaddr + CSR6); + + /* Reset the chip, holding bit 0 set at least 50 PCI cycles. */ + outl(0x00000001, ioaddr + CSR0); +#ifdef _LINUX_DELAY_H + udelay(2); +#else + SLOW_DOWN_IO; +#endif + /* Deassert reset. + 486: Set 8 longword cache alignment, 8 longword burst. + 586: Set 16 longword cache alignment, no burst limit. + Cache alignment bits 15:14 Burst length 13:8 + 0000 No alignment 0x00000000 unlimited 0800 8 longwords + 4000 8 longwords 0100 1 longword 1000 16 longwords + 8000 16 longwords 0200 2 longwords 2000 32 longwords + C000 32 longwords 0400 4 longwords Wait the specified 50 PCI cycles after a reset by initializing Tx and Rx queues and the address filter list. */ - tio_write(tio_read(CSR0)|TBMOD_ALIGN32|TBMOD_BURST0, CSR0); +#if defined(__alpha) + /* ToDo: Alpha setting could be better. */ + outl(0x00200000 | 0xE000, ioaddr + CSR0); +#else +#if defined(MODULE) + /* When a module we don't have 'x86' to check. */ + outl(0x00200000 | 0x4800, ioaddr + CSR0); +#else + outl(0x00200000 | (x86 <= 4 ? 0x4800 : 0x8000), ioaddr + CSR0); + if (x86 <= 4) + printk("This is a 386/486 PCI system, setting cache alignment to %x.\n", + 0x00200000 | (x86 <= 4 ? 0x4800 : 0x8000)); +#endif +#endif - if (request_irq(dev->irq, (void *)&tulip_interrupt, SA_SHIRQ, - tp->signature, dev)) +#ifdef SA_SHIRQ + if (request_irq(dev->irq, &tulip_interrupt, SA_SHIRQ, + tulip_tbl[tp->chip_id].chip_name, dev)) { return -EAGAIN; + } +#else + if (irq2dev_map[dev->irq] != NULL + || (irq2dev_map[dev->irq] = dev) == NULL + || dev->irq == 0 + || request_irq(dev->irq, &tulip_interrupt, 0, + tulip_tbl[tp->chip_id].chip_name)) { + return -EAGAIN; + } +#endif + + if (tulip_debug > 1) + printk("%s: tulip_open() irq %d.\n", dev->name, dev->irq); + + MOD_INC_USE_COUNT; tulip_init_ring(dev); + /* This is set_rx_mode(), but without starting the transmitter. */ /* Fill the whole address filter table with our physical address. */ - { - unsigned short *eaddrs = (unsigned short *)dev->dev_addr; + { + u16 *eaddrs = (u16 *)dev->dev_addr; int *setup_frm = tp->setup_frame, i; /* You must add the broadcast address when doing perfect filtering! */ @@ -721,57 +1031,463 @@ /* Put the setup frame on the Tx list. */ tp->tx_ring[0].length = 0x08000000 | 192; tp->tx_ring[0].buffer1 = virt_to_bus(tp->setup_frame); - tp->tx_ring[0].buffer2 = 0; - tp->tx_ring[0].status = TRING_OWN; - barrier(); - tp->cur_tx++, tp->dirty_tx++; + tp->tx_ring[0].status = 0x80000000; + + tp->cur_tx++; } - tio_write(virt_to_bus(tp->rx_ring), CSR3); - tio_write(virt_to_bus(tp->tx_ring), CSR4); + outl(virt_to_bus(tp->rx_ring), ioaddr + CSR3); + outl(virt_to_bus(tp->tx_ring), ioaddr + CSR4); + + if (dev->if_port == 0) + dev->if_port = tp->default_port; + if (tp->chip_id == DC21041 && dev->if_port > 4) + /* Invalid: Select initial TP, autosense, autonegotiate. */ + dev->if_port = 4; + + /* Allow selecting a default media. */ + if (tp->mtable == NULL) + goto media_picked; + if (dev->if_port) + for (i = 0; i < tp->mtable->leafcount; i++) + if (tp->mtable->mleaf[i].media == + (dev->if_port == 12 ? 0 : dev->if_port)) { + printk("%s: Using user-specified media %s.\n", + dev->name, medianame[dev->if_port]); + goto media_picked; + } + if ((tp->mtable->defaultmedia & 0x0800) == 0) + for (i = 0; i < tp->mtable->leafcount; i++) + if (tp->mtable->mleaf[i].media == (tp->mtable->defaultmedia & 15)) { + printk("%s: Using EEPROM-set media %s.\n", + dev->name, medianame[tp->mtable->mleaf[i].media]); + goto media_picked; + } + for (i = tp->mtable->leafcount - 1; + (media_fd[tp->mtable->mleaf[i].media] & 2) && i > 0; i--) + ; +media_picked: + + tp->cur_index = i; + tp->csr6 = 0; + select_media(dev, 1); + + /* Start the chip's Tx to process setup frame. */ + outl(tp->csr6, ioaddr + CSR6); + outl(tp->csr6 | 0x2000, ioaddr + CSR6); dev->tbusy = 0; dev->interrupt = 0; dev->start = 1; - /* - * process setup frame completely prior to fiddling with media. - */ - tio_write((tio_read(CSR6) & ~TCMOD_PROMISC) | TCMOD_TxSTART, CSR6); - tio_write(TPOLL_TRIGGER, CSR1); - sti(); - for (i = 0; i < 1000; i++) { - if (tp->tx_ring[0].status >= 0) { + + + /* Enable interrupts by setting the interrupt mask. */ + outl(0x0001fbff, ioaddr + CSR7); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + outl(0, ioaddr + CSR2); /* Rx poll demand */ + + if (tulip_debug > 2) { + printk("%s: Done tulip_open(), CSR0 %8.8x, CSR5 %8.8x CSR13 %8.8x.\n", + dev->name, inl(ioaddr + CSR0), inl(ioaddr + CSR5), + inl(ioaddr + CSR13)); + } + /* Set the timer to switch to check for link beat and perhaps switch + to an alternate media type. */ + init_timer(&tp->timer); + tp->timer.expires = RUN_AT((24*HZ)/10); /* 2.4 sec. */ + tp->timer.data = (unsigned long)dev; + tp->timer.function = &tulip_timer; /* timer handler */ + add_timer(&tp->timer); + + return 0; +} + +/* Set up the transceiver control registers for the selected media type. */ +static void select_media(struct device *dev, int startup) +{ + int ioaddr = dev->base_addr; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + struct mediatable *mtable = tp->mtable; + u32 new_csr6; + int i; + + if (mtable) { + struct medialeaf *mleaf = &mtable->mleaf[tp->cur_index]; + unsigned char *p = mleaf->leafdata; + switch (mleaf->type) { + case 0: /* 21140 non-MII xcvr. */ + if (tulip_debug > 1) + printk("%s: Using a 21140 non-MII transceiver with control" + " setting %2.2x.\n", + dev->name, p[1]); + dev->if_port = p[0]; + if (startup) + outl(mtable->csr12dir | 0x100, ioaddr + CSR12); + outl(p[1], ioaddr + CSR12); + new_csr6 = 0x02000000 | ((p[2] & 0x71) << 18); + break; + case 1: + if (startup) { + outl(mtable->csr12dir | 0x100, ioaddr + CSR12); + dev->if_port = 11; + if (tulip_debug > 2) + printk("%s: Doing a reset sequence of length %d.\n", + dev->name, p[2 + p[1]]); + for (i = 0; i < p[2 + p[1]]; i++) + outl(p[3 + p[1] + i], ioaddr + CSR12); + if (tulip_debug > 2) + printk("%s Doing a transceiver setup sequence of length %d.\n", + dev->name, p[1]); + for (i = 0; i < p[1]; i++) + outl(p[2 + i], ioaddr + CSR12); + } + new_csr6 = 0x020C0000; break; + case 2: case 4: { + u16 *setup = (u16*)&p[1]; + dev->if_port = p[0] & 15; + if (tulip_debug > 1) + printk("%s: 21142 non-MII %s transceiver control %4.4x/%4.4x.\n", + dev->name, medianame[dev->if_port], setup[0], setup[1]); + if (p[0] & 0x40) { /* SIA (CSR13-15) setup values are provided. */ + outl(0, ioaddr + CSR13); + outl(setup[1], ioaddr + CSR14); + outl(setup[2], ioaddr + CSR15); + outl(setup[0], ioaddr + CSR13); + setup += 3; + } else { + outl(0, ioaddr + CSR13); + outl(t21142_csr14[dev->if_port], ioaddr + CSR14); + outl(t21142_csr15[dev->if_port], ioaddr + CSR15); + outl(t21142_csr13[dev->if_port], ioaddr + CSR13); + } + outl(setup[0]<<16, ioaddr + CSR15); /* Direction */ + outl(setup[1]<<16, ioaddr + CSR15); /* Data */ + new_csr6 = 0x02000000; + break; + } + case 3: { + int init_length = p[1]; + u16 * init_sequence = (u16*)(p + 2); + int reset_length = p[2 + init_length*2]; + u16 * reset_sequence = (u16*)&p[3 + init_length*2]; + + dev->if_port = 11; + if (startup) { + if (tulip_debug > 2) + printk("%s: Doing a 21142 reset sequence of length %d.\n", + dev->name, reset_length); + for (i = 0; i < reset_length; i++) + outl(reset_sequence[i] << 16, ioaddr + CSR15); + } + if (tulip_debug > 2) + printk("%s: Doing a 21142 xcvr setup sequence of length %d.\n", + dev->name, init_length); + for (i = 0; i < init_length; i++) + outl(init_sequence[i] << 16, ioaddr + CSR15); + new_csr6 = 0x020C0000 | (tp->full_duplex ? 0x0200 : 0); + break; + } + default: + new_csr6 = 0x020C0000; + } + if (tulip_debug > 1) + printk("%s: Using media type %s, CSR12 is %2.2x.\n", + dev->name, medianame[dev->if_port], + inl(ioaddr + CSR12) & 0xff); + } else if (tp->chip_id == DC21140) { + /* Set media type to MII @ 100mbps: 0x020C0000 */ + new_csr6 = 0x020C0000; + dev->if_port = 11; + if (tulip_debug > 1) { + printk("%s: Unknown media control, assuming MII, CSR12 %2.2x.\n", + dev->name, inl(ioaddr + CSR12) & 0xff); } - udelay(1000); + } else if (tp->chip_id == DC21041) { + if (tulip_debug > 1) + printk("%s: 21041 using media %s, CSR12 is %4.4x.\n", + dev->name, medianame[dev->if_port & 15], + inl(ioaddr + CSR12) & 0xffff); + outl(0x00000000, ioaddr + CSR13); /* Reset the serial interface */ + outl(t21041_csr14[dev->if_port], ioaddr + CSR14); + outl(t21041_csr15[dev->if_port], ioaddr + CSR15); + outl(t21041_csr13[dev->if_port], ioaddr + CSR13); + new_csr6 = 0x80020000; + } else { /* 21040 */ + /* Turn on the xcvr interface. */ + int csr12 = inl(ioaddr + CSR12); + if (tulip_debug > 1) + printk("%s: 21040 media type is %s, CSR12 is %2.2x.\n", + dev->name, dev->if_port ? "AUI" : "10baseT", csr12); + new_csr6 = (dev->if_port ? 0x01860000 : 0x00420000); + /* Set the full duplux match frame. */ + outl(FULL_DUPLEX_MAGIC, ioaddr + CSR11); + outl(0x00000000, ioaddr + CSR13); /* Reset the serial interface */ + outl(dev->if_port ? 0x0000000C : 0x00000004, ioaddr + CSR13); } - if (i == 500) { - printk("%s: initial setup frame didn't complete.\n", dev->name); - dev->start = 0; - dev->tbusy = 1; - tio_write(TINTR_DISABLE, CSR7); - tio_write(tio_read(CSR6) & ~(TCMOD_TRxSTART), CSR6); - tio_write(TSIAC_CONFIG, CSR13); - tio_write(0, CSR13); - free_irq(dev->irq, dev); - return (-EIO); - } - /* - * Whack the chip to stop it and *then* do initial media setup. - */ - tio_write((tio_read(CSR6) & ~(TCMOD_PROMISC|TCMOD_TxSTART)), CSR6); - if (tp->port_select) - tp->port_select(dev); - /* Start the chip's Tx and Rx processes. */ - tio_write(tio_read(CSR6) | TCMOD_TRxSTART - | (tp->full_duplex ? TCMOD_FULLDUPLEX:0), CSR6); - /* Enable interrupts by setting the interrupt mask. */ - tio_write(TINTR_ENABLE, CSR7); - MOD_INC_USE_COUNT; - return 0; + tp->csr6 = new_csr6 | (tp->csr6 & 0xfdff) | (tp->full_duplex ? 0x0200 : 0); + return; } +static void tulip_timer(unsigned long data) +{ + struct device *dev = (struct device *)data; + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int ioaddr = dev->base_addr; + u32 csr12 = inl(ioaddr + CSR12); + int next_tick = 0; + + if (tulip_debug > 3) { + printk("%s: Media selection tick, status %8.8x mode %8.8x " + "SIA %8.8x %8.8x %8.8x %8.8x.\n", + dev->name, inl(ioaddr + CSR5), inl(ioaddr + CSR6), + csr12, inl(ioaddr + CSR13), + inl(ioaddr + CSR14), inl(ioaddr + CSR15)); + } + switch (tp->chip_id) { + case DC21040: + if (csr12 & 0x0002) { /* Network error */ + printk("%s: No 10baseT link beat found, switching to %s media.\n", + dev->name, dev->if_port ? "10baseT" : "AUI"); + dev->if_port ^= 1; + outl(dev->if_port ? 0x0000000C : 0x00000004, ioaddr + CSR13); + dev->trans_start = jiffies; + } + break; + case DC21041: + if (tulip_debug > 2) + printk("%s: 21041 media tick CSR12 %8.8x.\n", + dev->name, csr12); + switch (dev->if_port) { + case 0: case 3: case 4: + if (csr12 & 0x0004) { /*LnkFail */ + /* 10baseT is dead. Check for activity on alternate port. */ + tp->mediasense = 1; + if (csr12 & 0x0200) + dev->if_port = 2; + else + dev->if_port = 1; + printk("%s: No 21041 10baseT link beat, Media switched to %s.\n", + dev->name, medianame[dev->if_port]); + outl(0, ioaddr + CSR13); /* Reset */ + outl(t21041_csr14[dev->if_port], ioaddr + CSR14); + outl(t21041_csr15[dev->if_port], ioaddr + CSR15); + outl(t21041_csr13[dev->if_port], ioaddr + CSR13); + next_tick = 10*HZ; /* 2.4 sec. */ + } else + next_tick = 30*HZ; + break; + case 1: /* 10base2 */ + case 2: /* AUI */ + if (csr12 & 0x0100) { + next_tick = (30*HZ); /* 30 sec. */ + tp->mediasense = 0; + } else if ((csr12 & 0x0004) == 0) { + printk("%s: 21041 media switched to 10baseT.\n", dev->name); + dev->if_port = 0; + select_media(dev, 0); + next_tick = (24*HZ)/10; /* 2.4 sec. */ + } else if (tp->mediasense || (csr12 & 0x0002)) { + dev->if_port = 3 - dev->if_port; /* Swap ports. */ + select_media(dev, 0); + next_tick = 20*HZ; + } else { + next_tick = 20*HZ; + } + break; + } + break; + case DC21140: case DC21142: { + struct medialeaf *mleaf; + unsigned char *p; + if (tp->mtable == NULL) { /* No EEPROM info, use generic code. */ + /* Assume this is like a SMC card, and check its link beat bit. */ + if ((dev->if_port == 0 && (csr12 & 0x0080)) || + (dev->if_port == 1 && (csr12 & 0x0040) == 0)) { + dev->if_port ^= 1; + /* Stop the transmit process. */ + tp->csr6 = (dev->if_port ? 0x03860000 : 0x02420000); + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + printk("%s: link beat timed out, CSR12 is 0x%2.2x, switching to" + " %s media.\n", dev->name, + csr12 & 0xff, + dev->if_port ? "100baseTx" : "10baseT"); + outl(tp->csr6 | 0xA002, ioaddr + CSR6); + dev->trans_start = jiffies; + next_tick = (24*HZ)/10; + } else { + next_tick = 10*HZ; + if (tulip_debug > 2) + printk("%s: network media monitor 0x%2.2x, link" + " beat detected as %s.\n", dev->name, + csr12 & 0xff, + dev->if_port ? "100baseTx" : "10baseT"); + } + break; + } + mleaf = &tp->mtable->mleaf[tp->cur_index]; + p = mleaf->leafdata; + switch (mleaf->type) { + case 0: case 4: { + /* Type 0 non-MII or #4 SYM transceiver. Check the link beat bit. */ + s8 bitnum = p[mleaf->type == 4 ? 5 : 2]; + if (tulip_debug > 2) + printk("%s: Transceiver monitor tick: CSR12=%#2.2x bit %d is" + " %d, expecting %d.\n", + dev->name, csr12, (bitnum >> 1) & 7, + (csr12 & (1 << ((bitnum >> 1) & 7))) != 0, + (bitnum >= 0)); + /* Check that the specified bit has the proper value. */ + if ((bitnum < 0) != + ((csr12 & (1 << ((bitnum >> 1) & 7))) != 0)) { + if (tulip_debug > 1) + printk("%s: Link beat detected for %s.\n", dev->name, + medianame[mleaf->media]); + break; + } + if (tp->medialock) + break; + select_next_media: + if (--tp->cur_index < 0) { + /* We start again, but should instead look for default. */ + tp->cur_index = tp->mtable->leafcount - 1; + } + dev->if_port = tp->mtable->mleaf[tp->cur_index].media; + if (media_fd[dev->if_port]) + goto select_next_media; /* Skip FD entries. */ + if (tulip_debug > 1) + printk("%s: No link beat on media %s," + " trying transceiver type %s.\n", + dev->name, medianame[mleaf->media & 15], + medianame[tp->mtable->mleaf[tp->cur_index].media]); + select_media(dev, 0); + /* Restart the transmit process. */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + next_tick = (24*HZ)/10; + break; + } + case 1: + printk(" %s: MII monitoring tick: CSR12 status %2.2x.\n", + dev->name, csr12); + /* Hack for D-Link: Full duplex indication is on bit 3. */ + if (dev->dev_addr[0] == 0 && dev->dev_addr[1] == 0x80 + && dev->dev_addr[2] == 0xC8) { + /* The first message is for information only. */ + if (tp->full_duplex) { + printk("%s: D-Link card in full-duplex mode, csr6 setting" + " %8.8x.\n", dev->name, tp->csr6); + } else if (csr12 & 0x08) { + tp->full_duplex = 0; + tp->csr6 &= ~0x0200; + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + } else { + tp->full_duplex = 1; + tp->csr6 |= 0x0200; + printk("%s: Switching D-Link card to full-duplex.\n", dev->name); + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + } + } + break; + case 2: /* 21142 non-MII */ + case 3: /* 21142 MII */ + next_tick = (24*HZ)/10; + break; + default: + break; + } + } + default: /* Invalid chip type. */ + break; + } + if (next_tick) { + tp->timer.expires = RUN_AT(next_tick); + add_timer(&tp->timer); + } +} + +static void tulip_tx_timeout(struct device *dev) +{ + struct tulip_private *tp = (struct tulip_private *)dev->priv; + int ioaddr = dev->base_addr; + int i; + + if (tp->mtable && tp->mtable->has_mii) { + /* Do nothing -- the media monitor should handle this. */ + if (tulip_debug > 1) + printk("%s: Transmit timeout using MII device.\n", dev->name); + } else if (tp->chip_id == DC21040) { + if (inl(ioaddr + CSR12) & 0x0002) { + printk("%s: transmit timed out, switching to %s media.\n", + dev->name, dev->if_port ? "10baseT" : "AUI"); + dev->if_port ^= 1; + outl(dev->if_port ? 0x0000000C : 0x00000004, ioaddr + CSR13); + } + dev->trans_start = jiffies; + return; + } else if (tp->chip_id == DC21140 || tp->chip_id == DC21142) { + /* Stop the transmit process. */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + dev->if_port ^= 1; + printk("%s: 21140 transmit timed out, status %8.8x, SIA %8.8x %8.8x %8.8x %8.8x, resetting...\n", + dev->name, inl(ioaddr + CSR5), inl(ioaddr + CSR12), + inl(ioaddr + CSR13), inl(ioaddr + CSR14), inl(ioaddr + CSR15)); + printk("%s: transmit timed out, switching to %s media.\n", + dev->name, dev->if_port ? "100baseTx" : "10baseT"); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + tp->stats.tx_errors++; + dev->trans_start = jiffies; + return; + } else if (tp->chip_id == DC21041) { + u32 csr12 = inl(ioaddr + CSR12); + + printk("%s: 21041 transmit timed out, status %8.8x, CSR12 %8.8x," + " CSR13 %8.8x, CSR14 %8.8x, resetting...\n", + dev->name, inl(ioaddr + CSR5), csr12, + inl(ioaddr + CSR13), inl(ioaddr + CSR14)); + tp->mediasense = 1; + if (dev->if_port == 1 || dev->if_port == 2) + if (csr12 & 0x0004) { + dev->if_port = 2 - dev->if_port; + } else + dev->if_port = 0; + else + dev->if_port = 1; + select_media(dev, 0); + tp->stats.tx_errors++; + dev->trans_start = jiffies; + return; + } else + printk("%s: transmit timed out, status %8.8x, CSR12 %8.8x," + " resetting...\n", + dev->name, inl(ioaddr + CSR5), inl(ioaddr + CSR12)); +#ifndef __alpha__ + printk(" Rx ring %8.8x: ", (int)tp->rx_ring); + for (i = 0; i < RX_RING_SIZE; i++) + printk(" %8.8x", (unsigned int)tp->rx_ring[i].status); + printk("\n Tx ring %8.8x: ", (int)tp->tx_ring); + for (i = 0; i < TX_RING_SIZE; i++) + printk(" %8.8x", (unsigned int)tp->tx_ring[i].status); + printk("\n"); +#endif + + /* Perhaps we should reinitialize the hardware here. */ + dev->if_port = 0; + /* Stop and restart the chip's Tx processes . */ + outl(tp->csr6 | 0x0002, ioaddr + CSR6); + outl(tp->csr6 | 0x2002, ioaddr + CSR6); + /* Trigger an immediate transmit demand. */ + outl(0, ioaddr + CSR1); + + dev->trans_start = jiffies; + tp->stats.tx_errors++; + return; +} + + /* Initialize the Rx and Tx rings, along with various 'dev' bits. */ static void tulip_init_ring(struct device *dev) @@ -784,85 +1500,62 @@ tp->dirty_rx = tp->dirty_tx = 0; for (i = 0; i < RX_RING_SIZE; i++) { - tp->rx_ring[i].status = TRING_OWN; + tp->rx_ring[i].status = 0x80000000; /* Owned by Tulip chip */ tp->rx_ring[i].length = PKT_BUF_SZ; - tp->rx_ring[i].buffer1 = virt_to_bus(tp->rx_buffs[i]); + { + /* Note the receive buffer must be longword aligned. + dev_alloc_skb() provides 16 byte alignment. But do *not* + use skb_reserve() to align the IP header! */ + struct sk_buff *skb; + skb = DEV_ALLOC_SKB(PKT_BUF_SZ); + tp->rx_skbuff[i] = skb; + if (skb == NULL) + break; /* Bad news! */ + skb->dev = dev; /* Mark as being used by this device. */ +#if LINUX_VERSION_CODE > 0x10300 + tp->rx_ring[i].buffer1 = virt_to_bus(skb->tail); +#else + tp->rx_ring[i].buffer1 = virt_to_bus(skb->data); +#endif + } tp->rx_ring[i].buffer2 = virt_to_bus(&tp->rx_ring[i+1]); } - /* Mark the last entry as wrapping the ring. */ + /* Mark the last entry as wrapping the ring. */ tp->rx_ring[i-1].length = PKT_BUF_SZ | 0x02000000; tp->rx_ring[i-1].buffer2 = virt_to_bus(&tp->rx_ring[0]); /* The Tx buffer descriptor is filled in as needed, but we do need to clear the ownership bit. */ for (i = 0; i < TX_RING_SIZE; i++) { + tp->tx_skbuff[i] = 0; tp->tx_ring[i].status = 0x00000000; + tp->tx_ring[i].buffer2 = virt_to_bus(&tp->tx_ring[i+1]); } + tp->tx_ring[i-1].buffer2 = virt_to_bus(&tp->tx_ring[0]); } static int tulip_start_xmit(struct sk_buff *skb, struct device *dev) { struct tulip_private *tp = (struct tulip_private *)dev->priv; - int ioaddr = dev->base_addr; - int entry, len; - unsigned long daddr; + int entry; + u32 flag; - /* Transmitter timeout, serious problems. */ - if (dev->tbusy || (tp->port_fail && tp->port_fail(dev))) { - int tickssofar = jiffies - dev->trans_start; - int i; - if (tickssofar < 40) return(1); - if (tp->port_select) { - if (!tp->port_fix) dev->if_port ++; - tp->port_select(dev); - dev->trans_start = jiffies; - dev_kfree_skb(skb, FREE_WRITE); - return(0); - } - printk("%s: transmit timed out, status %8.8x," - "SIA %8.8x %8.8x %8.8x %8.8x, resetting...\n", - dev->name, tio_read(CSR5), tio_read(CSR12), - tio_read(CSR13), tio_read(CSR14), tio_read(CSR15)); -#ifndef __alpha__ - printk(" Rx ring %8.8x: ", (int)tp->rx_ring); -#endif - for (i = 0; i < RX_RING_SIZE; i++) - printk(" %8.8x", (unsigned int)tp->rx_ring[i].status); -#ifndef __alpha__ - printk("\n Tx ring %8.8x: ", (int)tp->tx_ring); -#endif - for (i = 0; i < TX_RING_SIZE; i++) - printk(" %8.8x", (unsigned int)tp->tx_ring[i].status); - printk("\n"); - - tp->stats.tx_errors++; - /* Perhaps we should reinitialize the hardware here. */ - dev->if_port = 0; - tio_write(TSIAC_CONFIG, CSR13); - /* Start the chip's Tx and Rx processes . */ - tio_write(TCMOD_10TP | TCMOD_TRxSTART, CSR6); - /* Trigger an immediate transmit demand. */ - tio_write(TPOLL_TRIGGER, CSR1); - - dev->tbusy=0; - dev->trans_start = jiffies; - dev_kfree_skb(skb, FREE_WRITE); - return(0); - } - - if (skb == NULL || (skb != (struct sk_buff *) -1 && skb->len <= 0)) { +#ifndef final_version + if (skb == NULL || skb->len <= 0) { printk("%s: Obsolete driver layer request made: skbuff==NULL.\n", dev->name); dev_tint(dev); - return(0); + return 0; } +#endif /* Block a timer-based transmit from overlapping. This could better be - done with atomic_swap(1, dev->tbusy), but set_bit() works as well. - If this ever occurs the queue layer is doing something evil! */ - if (set_bit(0, (void*)&dev->tbusy) != 0) { - printk("%s: Transmitter access conflict.\n", dev->name); + done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ + if (test_and_set_bit(0, (void*)&dev->tbusy) != 0) { + if (jiffies - dev->trans_start < TX_TIMEOUT) + return 1; + tulip_tx_timeout(dev); return 1; } @@ -872,43 +1565,49 @@ /* Calculate the next Tx descriptor entry. */ entry = tp->cur_tx % TX_RING_SIZE; - tp->tx_full = 1; - /* - * If skb is == -1, then this is a funky setup_frame redo. - */ - if (skb == (struct sk_buff *) -1) { - daddr = virt_to_bus((char *)tp->setup_frame); - len = 192; - skb = NULL; + tp->tx_skbuff[entry] = skb; + tp->tx_ring[entry].buffer1 = virt_to_bus(skb->data); + + if (tp->cur_tx - tp->dirty_tx < TX_RING_SIZE/2) {/* Typical path */ + flag = 0x60000000; /* No interrupt */ + dev->tbusy = 0; + } else if (tp->cur_tx - tp->dirty_tx == TX_RING_SIZE/2) { + flag = 0xe0000000; /* Tx-done intr. */ + dev->tbusy = 0; + } else if (tp->cur_tx - tp->dirty_tx < TX_RING_SIZE - 2) { + flag = 0x60000000; /* No Tx-done intr. */ + dev->tbusy = 0; } else { - daddr = virt_to_bus(skb->data); - len = skb->len; + /* Leave room for set_rx_mode() to fill entries. */ + flag = 0xe0000000; /* Tx-done intr. */ + tp->tx_full = 1; } - tp->tx_skbuff[entry] = skb; - tp->tx_ring[entry].length = len | - (entry == TX_RING_SIZE-1 ? 0xe2000000 : 0xe0000000); - tp->tx_ring[entry].buffer1 = daddr; - tp->tx_ring[entry].buffer2 = 0; - tp->tx_ring[entry].status = TRING_OWN; /* Pass ownership to the chip. */ - barrier(); + if (entry == TX_RING_SIZE-1) + flag |= 0xe2000000; + tp->tx_ring[entry].length = skb->len | flag; + tp->tx_ring[entry].status = 0x80000000; /* Pass ownership to the chip. */ tp->cur_tx++; - /* Trigger an immediate transmit demand. */ - tio_write(TPOLL_TRIGGER, CSR1); + outl(0, dev->base_addr + CSR1); dev->trans_start = jiffies; - return(0); + return 0; } /* The interrupt handler does all of the Rx thread work and cleans up after the Tx thread. */ -static void tulip_interrupt(int irq, void *dev_id, struct pt_regs *regs) +static void tulip_interrupt IRQ(int irq, void *dev_instance, struct pt_regs *regs) { - struct device *dev = (struct device *)dev_id; +#ifdef SA_SHIRQ /* Use the now-standard shared IRQ implementation. */ + struct device *dev = (struct device *)dev_instance; +#else + struct device *dev = (struct device *)(irq2dev_map[irq]); +#endif + struct tulip_private *lp; - int csr5, ioaddr, boguscnt=10; + int csr5, ioaddr, boguscnt = 10; if (dev == NULL) { printk ("tulip_interrupt(): irq %d for unknown device.\n", irq); @@ -923,33 +1622,46 @@ dev->interrupt = 1; do { - csr5 = tio_read(CSR5); + csr5 = inl(ioaddr + CSR5); /* Acknowledge all of the current interrupt sources ASAP. */ - tio_write(csr5 & TSTAT_CLEARINTR, CSR5); - /* check interrupt ? */ - if ((csr5 & (TSTAT_NORINTR|TSTAT_ABNINTR)) == 0) break; + outl(csr5 & 0x0001ffff, ioaddr + CSR5); - if (csr5 & TSTAT_RxINTR) /* Rx interrupt */ + if (tulip_debug > 4) + printk("%s: interrupt csr5=%#8.8x new csr5=%#8.8x.\n", + dev->name, csr5, inl(dev->base_addr + CSR5)); + + if ((csr5 & 0x00018000) == 0) + break; + + if (csr5 & 0x0040) /* Rx interrupt */ tulip_rx(dev); - if (csr5 & TSTAT_TxINTR) { /* Tx-done interrupt */ - int dirty_tx = lp->dirty_tx; + if (csr5 & 0x0007) { /* Tx-done interrupt */ + int dirty_tx; - while (dirty_tx < lp->cur_tx) { + for (dirty_tx = lp->dirty_tx; dirty_tx < lp->cur_tx; dirty_tx++) { int entry = dirty_tx % TX_RING_SIZE; int status = lp->tx_ring[entry].status; if (status < 0) break; /* It still hasn't been Txed */ + /* Check for Rx filter setup frames. */ + if (lp->tx_skbuff[entry] == NULL) + continue; - if (status & TRING_ERROR) { + if (status & 0x8000) { /* There was an major error, log it. */ +#ifndef final_version + if (tulip_debug > 1) + printk("%s: Transmit error, Tx status %8.8x.\n", + dev->name, status); +#endif lp->stats.tx_errors++; - if (status & TRING_TxABORT) lp->stats.tx_aborted_errors++; - if (status & TRING_TxCARR) lp->stats.tx_carrier_errors++; - if (status & TRING_TxWINDOW) lp->stats.tx_window_errors++; - if (status & TRING_TxFIFO) lp->stats.tx_fifo_errors++; - if ((status & TRING_TxHEARTBEAT) && !lp->full_duplex) + if (status & 0x4104) lp->stats.tx_aborted_errors++; + if (status & 0x0C00) lp->stats.tx_carrier_errors++; + if (status & 0x0200) lp->stats.tx_window_errors++; + if (status & 0x0002) lp->stats.tx_fifo_errors++; + if ((status & 0x0080) && lp->full_duplex == 0) lp->stats.tx_heartbeat_errors++; #ifdef ETHER_STATS if (status & 0x0100) lp->stats.collisions16++; @@ -963,15 +1675,14 @@ } /* Free the original skb. */ - if (lp->tx_skbuff[entry] != NULL) - dev_kfree_skb(lp->tx_skbuff[entry], FREE_WRITE); - dirty_tx++; + dev_kfree_skb(lp->tx_skbuff[entry], FREE_WRITE); + lp->tx_skbuff[entry] = 0; } #ifndef final_version - if (lp->cur_tx - dirty_tx >= TX_RING_SIZE) { - printk("out-of-sync dirty pointer, %d vs. %d, full=%d.\n", - dirty_tx, lp->cur_tx, lp->tx_full); + if (lp->cur_tx - dirty_tx > TX_RING_SIZE) { + printk("%s: Out-of-sync dirty pointer, %d vs. %d, full=%d.\n", + dev->name, dirty_tx, lp->cur_tx, lp->tx_full); dirty_tx += TX_RING_SIZE; } #endif @@ -988,34 +1699,50 @@ } /* Log errors. */ - if (csr5 & TSTAT_ABNINTR) { /* Abnormal error summary bit. */ - if (csr5 & TSTAT_TxTOUT) lp->stats.tx_errors++; /* Tx babble. */ - if (csr5 & TSTAT_RxMISSED) { /* Missed a Rx frame. */ + if (csr5 & 0x8000) { /* Abnormal error summary bit. */ + if (csr5 & 0x0008) lp->stats.tx_errors++; /* Tx babble. */ + if (csr5 & 0x0020) { /* Tx FIFO underflow. */ + lp->csr6 |= 0x00200000; /* Reconfigure to store-n-forward. */ + /* Restart the transmit process. */ + outl(lp->csr6 | 0x0002, ioaddr + CSR6); + outl(lp->csr6 | 0x2002, ioaddr + CSR6); + } + if (csr5 & 0x0100) { /* Missed a Rx frame. */ lp->stats.rx_errors++; - lp->stats.rx_missed_errors += tio_read(CSR8) & 0xffff; + lp->stats.rx_missed_errors += inl(ioaddr + CSR8) & 0xffff; } - if (csr5 & TSTAT_TEXPIRED) { + if (csr5 & 0x0800) { printk("%s: Something Wicked happened! %8.8x.\n", dev->name, csr5); /* Hmmmmm, it's not clear what to do here. */ } + /* Clear all error sources, included undocumented ones! */ + outl(0x000f7ba, ioaddr + CSR5); } if (--boguscnt < 0) { printk("%s: Too much work at interrupt, csr5=0x%8.8x.\n", dev->name, csr5); /* Clear all interrupt sources. */ - tio_write(TSTAT_CLEARINTR, CSR5); + outl(0x0001ffff, ioaddr + CSR5); break; } } while (1); - /* Special code for testing *only*. */ + if (tulip_debug > 3) + printk("%s: exiting interrupt, csr5=%#4.4x.\n", + dev->name, inl(ioaddr + CSR5)); + + /* Code that should never be run! Perhaps remove after testing.. */ { static int stopit = 10; if (dev->start == 0 && --stopit < 0) { printk("%s: Emergency stop, looping startup interrupt.\n", dev->name); +#ifdef SA_SHIRQ free_irq(irq, dev); +#else + free_irq(irq); +#endif } } @@ -1028,59 +1755,100 @@ { struct tulip_private *lp = (struct tulip_private *)dev->priv; int entry = lp->cur_rx % RX_RING_SIZE; - int i; + if (tulip_debug > 4) + printk(" In tulip_rx(), entry %d %8.8x.\n", entry, + lp->rx_ring[entry].status); /* If we own the next entry, it's a new packet. Send it up. */ while (lp->rx_ring[entry].status >= 0) { int status = lp->rx_ring[entry].status; - if ((status & TRING_RxDESCMASK) != TRING_RxDESCMASK) { - printk("%s: Ethernet frame spanned multiple buffers," - "status %8.8x!\n", dev->name, status); - } else if (status & TRING_ERROR) { + if (tulip_debug > 4) + printk(" tulip_rx() status was %8.8x.\n", status); + if ((status & 0x0300) != 0x0300) { + if ((status & 0xffff) != 0x7fff) { /* Ingore earlier buffers. */ + printk("%s: Oversized Ethernet frame spanned multiple buffers," + " status %8.8x!\n", dev->name, status); + lp->stats.rx_length_errors++; + } + } else if (status & 0x8000) { /* There was a fatal error. */ lp->stats.rx_errors++; /* end of a packet.*/ - if (status & TRING_RxLENGTH) lp->stats.rx_length_errors++; - if (status & TRING_RxFRAME) lp->stats.rx_frame_errors++; - if (status & TRING_RxCRC) lp->stats.rx_crc_errors++; - if (status & TRING_RxFIFO) lp->stats.rx_fifo_errors++; + if (status & 0x0890) lp->stats.rx_length_errors++; + if (status & 0x0004) lp->stats.rx_frame_errors++; + if (status & 0x0002) lp->stats.rx_crc_errors++; + if (status & 0x0001) lp->stats.rx_fifo_errors++; } else { /* Malloc up new buffer, compatible with net-2e. */ /* Omit the four octet CRC from the length. */ short pkt_len = (lp->rx_ring[entry].status >> 16) - 4; struct sk_buff *skb; + int rx_in_place = 0; - skb = dev_alloc_skb(pkt_len + 2); + /* Check if the packet is long enough to just accept without + copying to a properly sized skbuff. */ + if (pkt_len > SKBUFF_RX_COPYBREAK) { + struct sk_buff *newskb; + char *temp; + + /* Get a fresh skbuff to replace the filled one. */ + newskb = DEV_ALLOC_SKB(PKT_BUF_SZ); + if (newskb == NULL) { + skb = 0; /* No memory, drop the packet. */ + goto memory_squeeze; + } + /* Pass up the skb already on the Rx ring. */ + skb = lp->rx_skbuff[entry]; + temp = skb_put(skb, pkt_len); + if (bus_to_virt(lp->rx_ring[entry].buffer1) != temp) + printk("%s: Warning -- the skbuff addresses do not match" + " in tulip_rx: %p vs. %p / %p.\n", dev->name, + bus_to_virt(lp->rx_ring[entry].buffer1), + skb->head, temp); + rx_in_place = 1; + lp->rx_skbuff[entry] = newskb; + newskb->dev = dev; + /* Longword alignment required: do not skb_reserve(2)! */ + lp->rx_ring[entry].buffer1 = virt_to_bus(newskb->tail); + } else + skb = DEV_ALLOC_SKB(pkt_len + 2); + memory_squeeze: if (skb == NULL) { - printk("%s: Memory squeeze, deferring packet.\n", - dev->name); + int i; + printk("%s: Memory squeeze, deferring packet.\n", dev->name); /* Check that at least two ring entries are free. If not, free one and mark stats->rx_dropped++. */ - for (i=0; i < RX_RING_SIZE; i++) + for (i = 0; i < RX_RING_SIZE; i++) if (lp->rx_ring[(entry+i) % RX_RING_SIZE].status < 0) break; if (i > RX_RING_SIZE -2) { lp->stats.rx_dropped++; - lp->rx_ring[entry].status = TRING_OWN; + lp->rx_ring[entry].status = 0x80000000; lp->cur_rx++; } break; } skb->dev = dev; - skb_reserve(skb, 2); - memcpy(skb_put(skb, pkt_len), - bus_to_virt(lp->rx_ring[entry].buffer1), pkt_len); - /* Needed for 1.3.x */ - skb->protocol = eth_type_trans(skb,dev); + if (! rx_in_place) { + skb_reserve(skb, 2); /* 16 byte align the data fields */ + memcpy(skb_put(skb, pkt_len), + bus_to_virt(lp->rx_ring[entry].buffer1), pkt_len); + } +#if LINUX_VERSION_CODE > 0x10300 + skb->protocol = eth_type_trans(skb, dev); +#else + skb->len = pkt_len; +#endif netif_rx(skb); lp->stats.rx_packets++; } - lp->rx_ring[entry].status = TRING_OWN; + lp->rx_ring[entry].status = 0x80000000; entry = (++lp->cur_rx) % RX_RING_SIZE; } - return(0); + + return 0; } static int @@ -1088,342 +1856,223 @@ { int ioaddr = dev->base_addr; struct tulip_private *tp = (struct tulip_private *)dev->priv; + int i; dev->start = 0; dev->tbusy = 1; + if (tulip_debug > 1) + printk("%s: Shutting down ethercard, status was %2.2x.\n", + dev->name, inl(ioaddr + CSR5)); + /* Disable interrupts by clearing the interrupt mask. */ - tio_write(TINTR_DISABLE, CSR7); + outl(0x00000000, ioaddr + CSR7); /* Stop the chip's Tx and Rx processes. */ - tio_write(tio_read(CSR6) & ~(TCMOD_TRxSTART), CSR6); - /* Leave the card in 10baseT state. */ - tio_write(TSIAC_CONFIG, CSR13); + outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6); + /* 21040 -- Leave the card in 10baseT state. */ + if (tp->chip_id == DC21040) + outl(0x00000004, ioaddr + CSR13); - tp->stats.rx_missed_errors += tio_read(CSR8) & 0xffff; + tp->stats.rx_missed_errors += inl(ioaddr + CSR8) & 0xffff; - tio_write(0, CSR13); -/* tio_write(0, CSR8); wake up chip ? */ + del_timer(&tp->timer); +#ifdef SA_SHIRQ free_irq(dev->irq, dev); +#else + free_irq(dev->irq); + irq2dev_map[dev->irq] = 0; +#endif - MOD_DEC_USE_COUNT; - return(0); -} + /* Free all the skbuffs in the Rx queue. */ + for (i = 0; i < RX_RING_SIZE; i++) { + struct sk_buff *skb = tp->rx_skbuff[i]; + tp->rx_skbuff[i] = 0; + tp->rx_ring[i].status = 0; /* Not owned by Tulip chip. */ + tp->rx_ring[i].length = 0; + tp->rx_ring[i].buffer1 = 0xBADF00D0; /* An invalid address. */ + if (skb) { +#if LINUX_VERSION_CODE < 0x20100 + skb->free = 1; +#endif + dev_kfree_skb(skb, FREE_WRITE); + } + } + for (i = 0; i < TX_RING_SIZE; i++) { + if (tp->tx_skbuff[i]) + dev_kfree_skb(tp->tx_skbuff[i], FREE_WRITE); + tp->tx_skbuff[i] = 0; + } -static int -tulip_config(struct device *dev, struct ifmap *map) -{ - struct tulip_private *tp = (struct tulip_private *)dev->priv; - if (map->port == 0xff) return(-EINVAL); - dev->if_port = map->port; - tp->port_fix = 1; - if (tp->port_select) tp->port_select(dev); - return(0); + MOD_DEC_USE_COUNT; + + return 0; } static struct enet_statistics * tulip_get_stats(struct device *dev) { struct tulip_private *tp = (struct tulip_private *)dev->priv; - /* short ioaddr = dev->base_addr;*/ + int ioaddr = dev->base_addr; + + if (dev->start) + tp->stats.rx_missed_errors += inl(ioaddr + CSR8) & 0xffff; - return(&tp->stats); + return &tp->stats; +} + +/* Set or clear the multicast filter for this adaptor. + Note that we only use exclusion around actually queueing the + new frame, not around filling tp->setup_frame. This is non-deterministic + when re-entered but still correct. */ + +/* The little-endian AUTODIN32 ethernet CRC calculation. + N.B. Do not use for bulk data, use a table-based routine instead. + This is common code and should be moved to net/core/crc.c */ +static unsigned const ethernet_polynomial_le = 0xedb88320U; +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; } -/* - * Set or clear the multicast filter for this adaptor. - */ -static void set_multicast_list(struct device *dev) +static void +#ifdef NEW_MULTICAST +set_multicast_list(struct device *dev) +#else +static void set_multicast_list(struct device *dev, int num_addrs, void *addrs); +#endif { - short ioaddr = dev->base_addr; - int csr6 = tio_read(CSR6) & ~(TCMOD_MODEMASK|TCMOD_FILTERMASK); + int ioaddr = dev->base_addr; + int csr6 = inl(ioaddr + CSR6) & ~0x00D5; + struct tulip_private *tp = (struct tulip_private *)dev->priv; - if (dev->flags&IFF_PROMISC) - { /* Set promiscuous. why ALLMULTI ? */ - tio_write(csr6 | TCMOD_PROMISC | TCMOD_ALLMCAST, CSR6); - /* Log any net taps. */ + tp->csr6 &= ~0x00D5; + if (dev->flags & IFF_PROMISC) { /* Set promiscuous. */ + outl(csr6 | 0x00C0, ioaddr + CSR6); + /* Unconditionally log net taps. */ printk("%s: Promiscuous mode enabled.\n", dev->name); - } - else if (dev->mc_count > 14 || (dev->flags&IFF_ALLMULTI)) - { + tp->csr6 |= 0xC0; + } else if ((dev->mc_count > 1000) || (dev->flags & IFF_ALLMULTI)) { /* Too many to filter perfectly -- accept all multicasts. */ - tio_write(csr6 | TCMOD_ALLMCAST, CSR6); - } - else - { - struct tulip_private *tp = (struct tulip_private *)dev->priv; - struct dev_mc_list *dmi=dev->mc_list; - int *setup_frm = tp->setup_frame; - unsigned short *eaddrs; + outl(csr6 | 0x0080, ioaddr + CSR6); + tp->csr6 |= 0x80; + } else { + u32 *setup_frm = tp->setup_frame; + struct dev_mc_list *mclist; + u16 *eaddrs; + u32 tx_flags; int i; - /* We have < 15 addresses that we can use the wonderful - 16 address perfect filtering of the Tulip. Note that only - the low shortword of setup_frame[] is valid. */ - tio_write(csr6 | 0x0000, CSR6); - for (i = 0; i < dev->mc_count; i ++) { - eaddrs=(unsigned short *)dmi->dmi_addr; - dmi=dmi->next; + if (dev->mc_count > 14) { /* Must use a multicast hash table. */ + u16 hash_table[32]; + memset(hash_table, 0, sizeof(hash_table)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + set_bit(ether_crc_le(ETH_ALEN, mclist->dmi_addr) & 0x1ff, + hash_table); + /* Copy the hash table to the setup frame. + NOTE that only the LOW SHORTWORD of setup_frame[] is valid! + This code may require tweaking for non-x86 architectures! */ + for (i = 0; i < 32; i++) + *setup_frm++ = hash_table[i]; + setup_frm += 7; + tx_flags = 0x08400000 | 192; + /* Too clever: i > 15 for fall-though. */ + } else { + /* We have <= 15 addresses so we can use the wonderful + 16 address perfect filtering of the Tulip. */ + for (i = 0, mclist = dev->mc_list; i < dev->mc_count; + i++, mclist = mclist->next) { + /* Note that only the low shortword of setup_frame[] is valid! + This code may require tweaking for non-x86 architectures! */ + eaddrs = (u16 *)mclist->dmi_addr; *setup_frm++ = *eaddrs++; *setup_frm++ = *eaddrs++; *setup_frm++ = *eaddrs++; + } + /* Fill the rest of the table with our physical address. + Once again, only the low shortword or setup_frame[] is valid! */ + *setup_frm++ = 0xffff; + *setup_frm++ = 0xffff; + *setup_frm++ = 0xffff; + tx_flags = 0x08000000 | 192; } - /* Fill the rest of the table with our physical address. */ - eaddrs = (unsigned short *)dev->dev_addr; - /* Always accept broadcast packets */ - *setup_frm++ = 0xffff; - *setup_frm++ = 0xffff; - *setup_frm++ = 0xffff; + eaddrs = (u16 *)dev->dev_addr; do { - *setup_frm++ = eaddrs[0]; - *setup_frm++ = eaddrs[1]; - *setup_frm++ = eaddrs[2]; + *setup_frm++ = eaddrs[0]; + *setup_frm++ = eaddrs[1]; + *setup_frm++ = eaddrs[2]; } while (++i < 15); - /* Now add this frame to the Tx list. */ - tulip_start_xmit((struct sk_buff *) -1, dev); - } -} - -int -tulip_hwinit(struct device *dev, int ioaddr, - int irq, int device_id) -{ - /* See note below on the Znyx 315 etherarray. */ - static unsigned char last_phys_addr[6] = {0x00, 'L', 'i', 'n', 'u', 'x'}; - static int last_irq; - char detect_mesg[80], *mesgp=detect_mesg; - struct tulip_private *tp = (struct tulip_private *)dev->priv; - int i; - unsigned short sum, bitsum; - - if (check_region(ioaddr, TULIP_TOTAL_SIZE) != 0) { - printk("tulip_hwinit: region already allocated at %#3x.\n", - ioaddr); - return(-1); - } - - mesgp += sprintf(mesgp, "(DEC 21%d4%d Tulip", - device_id == PCI_DEVICE_ID_DEC_TULIP_FAST, - device_id == PCI_DEVICE_ID_DEC_TULIP_PLUS); - - /* Stop the chip's Tx and Rx processes. */ - tio_write(tio_read(CSR6) & ~TCMOD_TRxSTART, CSR6); - /* Clear the missed-packet counter. */ - i = tio_read(CSR8) & 0xffff; - - if (device_id == PCI_DEVICE_ID_DEC_TULIP_PLUS - && (tio_read(CSR9) & 0x8000)) { - mesgp += sprintf(mesgp, " treat as 21040"); - device_id = PCI_DEVICE_ID_DEC_TULIP; - } - - /* The station address ROM is read byte serially. The register must - be polled, waiting for the value to be read bit serially from the - EEPROM. - */ - sum = 0; - if (device_id == PCI_DEVICE_ID_DEC_TULIP) { - tio_write(0, CSR9); - /* Reset the pointer with a dummy write. */ - bitsum = 0xff; - for (i = 0; i < 6; i++) { - int value, boguscnt = 100000; - do - value = tio_read(CSR9); - while (value < 0 && --boguscnt > 0); - dev->dev_addr[i] = value; - sum += value & 0xFF; - bitsum &= value; - } - } else { - /* Must be a 21140/21041, with a serial EEPROM interface. */ - struct eeprom eep; - u_char *addr; - - if (read_eeprom(ioaddr, &eep) < 0) { - addr = eep.ng_addr;/* broken EEPROM structure */ - } else { - addr = eep.ok_addr;/* DEC EtherWorks */ - } - for (i = 0; i < ETH_ALEN; i++) { - sum += addr[i]; - dev->dev_addr[i] = addr[i]; - } - } - /* Make certain the data structures are quadword aligned. */ - - mesgp += sprintf(mesgp, ") at %#3x, ", ioaddr); - - /* On the Zynx 315 etherarray boards only the first Tulip has an EEPROM. - The addresses of the subsequent ports are derived from the first. */ - if (sum == 0) { - for (i = 0; i < ETH_ALEN - 1; i++) - dev->dev_addr[i] = last_phys_addr[i]; - dev->dev_addr[i] = last_phys_addr[i] + 1; - irq = last_irq; - } - for (i = 0; i < ETH_ALEN - 1; i++) - mesgp += sprintf(mesgp, "%2.2x:", dev->dev_addr[i]); - mesgp += sprintf(mesgp, "%2.2x, IRQ %d\n", - last_phys_addr[i] = dev->dev_addr[i], irq); - last_irq = irq; - - /* copy ethernet address */ - if (card_type(tp, device_id, - htonl((*(int*)dev->dev_addr) & 0xFFFFFF))) - for (i = 0; i < ETH_ALEN - 1; i++) - last_phys_addr[i] = dev->dev_addr[i]; - /* We do a request_region() only to register /proc/ioports info. */ - request_region(ioaddr, TULIP_TOTAL_SIZE, tp->signature); - - dev->base_addr = ioaddr; - dev->irq = irq; - - /* The Tulip-specific entries in the device structure. */ - dev->open = &tulip_open; - dev->hard_start_xmit = &tulip_start_xmit; - dev->stop = &tulip_close; - dev->get_stats = &tulip_get_stats; - dev->set_config = &tulip_config; - dev->set_multicast_list = &set_multicast_list; - -#ifdef MODULE - if (if_port == TULIP_AUTO_PORT) - if_port = TULIP_PORT; - else - tp->port_fix = 1; - dev->if_port = if_port; - tp->full_duplex = full_duplex; - tp->next_module = root_tulip_dev; - root_tulip_dev = dev; -#else -#ifdef TULIP_FULL_DUPLEX - tp->full_duplex = 1; -#endif - dev->if_port = TULIP_PORT; -#endif -#ifdef TULIP_FIX_PORT - tp->port_fix = 1; -#endif - - printk("%s: %s %s", dev->name, tp->signature, detect_mesg); - - /* Reset the xcvr interface and turn on heartbeat. */ - tio_write(TSIAC_RESET, CSR13); - tio_write(TSIAC_CONFIG, CSR13); - - return(0); -} - -int tulip_probe(struct device *dev) -{ - static struct device *tulip_head=NULL; - u_char pci_bus, pci_device_fn, pci_latency, pci_irq; - u_int pci_ioaddr; - u_short pci_command, vendor_id, device_id; - u_int pci_chips[] = { - PCI_DEVICE_ID_DEC_TULIP, - PCI_DEVICE_ID_DEC_TULIP_FAST, - PCI_DEVICE_ID_DEC_TULIP_PLUS, - PCI_DEVICE_ID_NONE - }; - int num=0, cno; - static int pci_index = 0; - - if (!pcibios_present()) return(-ENODEV); - - for (; pci_index < 0xff; pci_index++) { - if (pcibios_find_class(PCI_CLASS_NETWORK_ETHERNET << 8, pci_index, - &pci_bus, &pci_device_fn) != PCIBIOS_SUCCESSFUL) - break; - - /* get vendor id */ - pcibios_read_config_word(pci_bus, pci_device_fn, PCI_VENDOR_ID, - &vendor_id); - /* get IRQ */ - pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_INTERRUPT_LINE, - &pci_irq); - - /* get device id */ - pcibios_read_config_word(pci_bus, pci_device_fn, PCI_DEVICE_ID, - &device_id); - - /* get IO address */ - pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_0, - &pci_ioaddr); - - /* Remove I/O space marker in bit 0. */ - pci_ioaddr &= ~3; - if (vendor_id != PCI_VENDOR_ID_DEC) - continue; - - for (cno = 0; pci_chips[cno] != PCI_DEVICE_ID_NONE; cno++) - if (device_id == pci_chips[cno]) - break; - if (pci_chips[cno] == PCI_DEVICE_ID_NONE) { - printk("Unknown Digital PCI ethernet chip type %4.4x detected:" - " not configured.\n", device_id); - continue; - } - dev = init_etherdev(NULL, ROUND_UP(sizeof(struct device) + - sizeof (struct tulip_private) + - ETHNAMSIZ, 8)); - if (dev == NULL) - break; + if (tp->cur_tx - tp->dirty_tx > TX_RING_SIZE - 2) { + /* Same setup recently queued, we need not add it. */ + } else { + unsigned long flags; + unsigned int entry; + + save_flags(flags); cli(); + entry = tp->cur_tx++ % TX_RING_SIZE; + + if (entry != 0) { + /* Avoid a chip errata by prefixing a dummy entry. */ + tp->tx_skbuff[entry] = 0; + tp->tx_ring[entry].length = + (entry == TX_RING_SIZE-1) ? 0x02000000 : 0; + tp->tx_ring[entry].buffer1 = 0; + tp->tx_ring[entry].status = 0x80000000; + entry = tp->cur_tx++ % TX_RING_SIZE; + } - if (!tulip_head) { - printk(version); - tulip_head = dev; - } - - /* Get and check the bus-master and latency values. */ - pcibios_read_config_word(pci_bus, pci_device_fn, PCI_COMMAND, - &pci_command); - if ((pci_command & PCI_COMMAND_MASTER) == 0) { - printk(" PCI Master Bit has not been set!" - " Setting...\n"); - pci_command |= PCI_COMMAND_MASTER; - pcibios_write_config_word(pci_bus, pci_device_fn, PCI_COMMAND, - pci_command); - } - - pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_LATENCY_TIMER, - &pci_latency); - - if (pci_latency < 10) { - printk(" PCI latency timer (CFLT) is" - " unreasonably low at %d." - " Setting to 100 clocks.\n", pci_latency); - pcibios_write_config_byte(pci_bus, pci_device_fn, - PCI_LATENCY_TIMER, 100); - } - - if (tulip_hwinit(dev, pci_ioaddr, pci_irq, pci_chips[cno]) < 0) { - continue; - } - num++; -#ifdef TULIP_MAX_CARDS - if (num >= TULIP_MAX_CARDS) return(0); -#endif + tp->tx_skbuff[entry] = 0; + /* Put the setup frame on the Tx list. */ + if (entry == TX_RING_SIZE-1) + tx_flags |= 0x02000000; /* Wrap ring. */ + tp->tx_ring[entry].length = tx_flags; + tp->tx_ring[entry].buffer1 = virt_to_bus(tp->setup_frame); + tp->tx_ring[entry].status = 0x80000000; + if (tp->cur_tx - tp->dirty_tx >= TX_RING_SIZE - 2) { + dev->tbusy = 1; + tp->tx_full = 1; + } + restore_flags(flags); + /* Trigger an immediate transmit demand. */ + outl(0, ioaddr + CSR1); + } + outl(csr6 | 0x0000, ioaddr + CSR6); } - return(num > 0 ? 0: -ENODEV); } - + #ifdef MODULE -/* The parameters that may be passed in... */ -/* This driver does nothing with options yet. It will later be used to - pass the full-duplex flag, etc. */ -int debug = -1; +/* An additional parameter that may be passed in... */ +static int debug = -1; int init_module(void) { + int cards_found; + + if (debug >= 0) + tulip_debug = debug; + root_tulip_dev = NULL; - return tulip_probe(NULL); + cards_found = tulip_probe(0); + + return cards_found ? 0 : -ENODEV; } void @@ -1433,21 +2082,21 @@ /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ while (root_tulip_dev) { - next_dev = - ((struct tulip_private *) root_tulip_dev->priv)->next_module; + next_dev = ((struct tulip_private *)root_tulip_dev->priv)->next_module; unregister_netdev(root_tulip_dev); release_region(root_tulip_dev->base_addr, TULIP_TOTAL_SIZE); kfree(root_tulip_dev); root_tulip_dev = next_dev; } } -#endif /* MODULE */ +#endif /* MODULE */ /* * Local variables: - * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c tulip.c" + * compile-command: "gcc -DMODULE -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -c tulip.c" * c-indent-level: 4 + * c-basic-offset: 4 * tab-width: 4 * End: */ diff -u --recursive --new-file v2.0.30/linux/drivers/net/wavelan.c linux/drivers/net/wavelan.c --- v2.0.30/linux/drivers/net/wavelan.c Thu Mar 6 10:03:51 1997 +++ linux/drivers/net/wavelan.c Sun Aug 3 15:17:45 1997 @@ -112,7 +112,7 @@ * Read from card's Host Adaptor Status Register. */ static inline u_short -hasr_read(u_short ioaddr) +hasr_read(u_long ioaddr) { return(inw(HASR(ioaddr))); } /* hasr_read */ @@ -122,7 +122,7 @@ * Write to card's Host Adapter Command Register. */ static inline void -hacr_write(u_short ioaddr, +hacr_write(u_long ioaddr, u_short hacr) { outw(hacr, HACR(ioaddr)); @@ -134,7 +134,7 @@ * those times when it is needed. */ static inline void -hacr_write_slow(u_short ioaddr, +hacr_write_slow(u_long ioaddr, u_short hacr) { hacr_write(ioaddr, hacr); @@ -147,7 +147,7 @@ * Set the channel attention bit. */ static inline void -set_chan_attn(u_short ioaddr, +set_chan_attn(u_long ioaddr, u_short hacr) { hacr_write(ioaddr, hacr | HACR_CA); @@ -158,7 +158,7 @@ * Reset, and then set host adaptor into default mode. */ static inline void -wv_hacr_reset(u_short ioaddr) +wv_hacr_reset(u_long ioaddr) { hacr_write_slow(ioaddr, HACR_RESET); hacr_write(ioaddr, HACR_DEFAULT); @@ -169,7 +169,7 @@ * Set the i/o transfer over the ISA bus to 8 bits mode */ static inline void -wv_16_off(u_short ioaddr, +wv_16_off(u_long ioaddr, u_short hacr) { hacr &= ~HACR_16BITS; @@ -181,7 +181,7 @@ * Set the i/o transfer over the ISA bus to 8 bits mode */ static inline void -wv_16_on(u_short ioaddr, +wv_16_on(u_long ioaddr, u_short hacr) { hacr |= HACR_16BITS; @@ -196,7 +196,7 @@ wv_ints_off(device * dev) { net_local * lp = (net_local *)dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; u_long x; x = wv_splhi(); @@ -215,7 +215,7 @@ wv_ints_on(device * dev) { net_local * lp = (net_local *)dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; u_long x; x = wv_splhi(); @@ -239,7 +239,7 @@ * Read bytes from the PSA. */ static void -psa_read(u_short ioaddr, +psa_read(u_long ioaddr, u_short hacr, int o, /* offset in PSA */ u_char * b, /* buffer to fill */ @@ -262,7 +262,7 @@ * Write the Paramter Storage Area to the WaveLAN card's memory */ static void -psa_write(u_short ioaddr, +psa_write(u_long ioaddr, u_short hacr, int o, /* Offset in psa */ u_char * b, /* Buffer in memory */ @@ -329,7 +329,7 @@ * Write 1 byte to the MMC. */ static inline void -mmc_out(u_short ioaddr, +mmc_out(u_long ioaddr, u_short o, u_char d) { @@ -347,7 +347,7 @@ * We start by the end because it is the way it should be ! */ static inline void -mmc_write(u_short ioaddr, +mmc_write(u_long ioaddr, u_char o, u_char * b, int n) @@ -365,7 +365,7 @@ * Optimised version for 1 byte, avoid using memory... */ static inline u_char -mmc_in(u_short ioaddr, +mmc_in(u_long ioaddr, u_short o) { while(inw(HASR(ioaddr)) & HASR_MMC_BUSY) @@ -386,7 +386,7 @@ * We start by the end because it is the way it should be ! */ static inline void -mmc_read(u_short ioaddr, +mmc_read(u_long ioaddr, u_char o, u_char * b, int n) @@ -400,11 +400,27 @@ /*------------------------------------------------------------------*/ /* + * Get the type of encryption available... + */ +static inline int +mmc_encr(u_long ioaddr) /* i/o port of the card */ +{ + int temp; + + temp = mmc_in(ioaddr, mmroff(0, mmr_des_avail)); + if((temp != MMR_DES_AVAIL_DES) && (temp != MMR_DES_AVAIL_AES)) + return 0; + else + return temp; +} + +/*------------------------------------------------------------------*/ +/* * Wait for the frequency EEprom to complete a command... * I hope this one will be optimally inlined... */ static inline void -fee_wait(u_short ioaddr, /* i/o port of the card */ +fee_wait(u_long ioaddr, /* i/o port of the card */ int delay, /* Base delay to wait for */ int number) /* Number of time to wait */ { @@ -420,7 +436,7 @@ * Read bytes from the Frequency EEprom (frequency select cards). */ static void -fee_read(u_short ioaddr, /* i/o port of the card */ +fee_read(u_long ioaddr, /* i/o port of the card */ u_short o, /* destination offset */ u_short * b, /* data buffer */ int n) /* number of registers */ @@ -445,6 +461,8 @@ } } +#ifdef WIRELESS_EXT /* If wireless extension exist in the kernel */ + /*------------------------------------------------------------------*/ /* * Write bytes from the Frequency EEprom (frequency select cards). @@ -453,7 +471,7 @@ * Jean II */ static void -fee_write(u_short ioaddr, /* i/o port of the card */ +fee_write(u_long ioaddr, /* i/o port of the card */ u_short o, /* destination offset */ u_short * b, /* data buffer */ int n) /* number of registers */ @@ -528,6 +546,7 @@ fee_wait(ioaddr, 10, 100); #endif /* EEPROM_IS_PROTECTED */ } +#endif /* WIRELESS_EXT */ /************************ I82586 SUBROUTINES *************************/ /* @@ -540,7 +559,7 @@ * Why inlining this function make it fail ??? */ static /*inline*/ void -obram_read(u_short ioaddr, +obram_read(u_long ioaddr, u_short o, u_char * b, int n) @@ -554,7 +573,7 @@ * Write bytes to the on-board RAM. */ static inline void -obram_write(u_short ioaddr, +obram_write(u_long ioaddr, u_short o, u_char * b, int n) @@ -571,7 +590,7 @@ wv_ack(device * dev) { net_local * lp = (net_local *)dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; u_short scb_cs; int i; @@ -614,7 +633,7 @@ const char * str) { net_local * lp = (net_local *)dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; u_short scb_cmd; ach_t cb; int i; @@ -660,7 +679,7 @@ */ static inline int wv_config_complete(device * dev, - u_short ioaddr, + u_long ioaddr, net_local * lp) { unsigned short mcs_addr; @@ -722,7 +741,7 @@ */ static int wv_complete(device * dev, - u_short ioaddr, + u_long ioaddr, net_local * lp) { int nreaped = 0; @@ -992,7 +1011,7 @@ static void wv_mmc_show(device * dev) { - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; net_local * lp = (net_local *)dev->priv; mmr_t m; @@ -1077,7 +1096,7 @@ * Print the last block of the i82586 memory */ static void -wv_scb_show(unsigned short ioaddr) +wv_scb_show(u_long ioaddr) { scb_t scb; @@ -1162,7 +1181,7 @@ int i, u_short p) { - unsigned short ioaddr; + u_long ioaddr; ac_tx_t actx; ioaddr = dev->base_addr; @@ -1529,7 +1548,7 @@ * (called in wavelan_ioctl) */ static inline int -wv_set_frequency(u_short ioaddr, /* i/o port of the card */ +wv_set_frequency(u_long ioaddr, /* i/o port of the card */ iw_freq * frequency) { const int BAND_NUM = 10; /* Number of bands */ @@ -1550,7 +1569,7 @@ } /* Setting by channel (same as wfreqsel) */ - /* Warning : each channel is 11MHz wide, so some of the channels + /* Warning : each channel is 22MHz wide, so some of the channels * will interfere... */ if((frequency->e == 0) && (frequency->m >= 0) && (frequency->m < BAND_NUM)) @@ -1729,7 +1748,7 @@ * Give the list of available frequencies */ static inline int -wv_frequency_list(u_short ioaddr, /* i/o port of the card */ +wv_frequency_list(u_long ioaddr, /* i/o port of the card */ iw_freq * list, /* List of frequency to fill */ int max) /* Maximum number of frequencies */ { @@ -1826,7 +1845,7 @@ struct ifreq * rq, /* Data passed */ int cmd) /* Ioctl number */ { - unsigned short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; net_local * lp = (net_local *)dev->priv; /* lp is not unused */ struct iwreq * wrq = (struct iwreq *) rq; psa_t psa; @@ -1866,9 +1885,7 @@ m.w.mmw_netw_id_h = (wrq->u.nwid.nwid & 0xFF00) >> 8; mmc_write(ioaddr, (char *)&m.w.mmw_netw_id_l - (char *)&m, (unsigned char *)&m.w.mmw_netw_id_l, 2); - m.w.mmw_loopt_sel = 0x00; - mmc_write(ioaddr, (char *)&m.w.mmw_loopt_sel - (char *)&m, - (unsigned char *)&m.w.mmw_loopt_sel, 1); + mmc_out(ioaddr, mmwoff(0, mmw_loopt_sel), 0x00); } else { @@ -1878,10 +1895,8 @@ (char *)&psa.psa_nwid_select - (char *)&psa, (unsigned char *)&psa.psa_nwid_select, 1); - /* Disable nwid in the mmc (no check) */ - m.w.mmw_loopt_sel = MMW_LOOPT_SEL_DIS_NWID; - mmc_write(ioaddr, (char *)&m.w.mmw_loopt_sel - (char *)&m, - (unsigned char *)&m.w.mmw_loopt_sel, 1); + /* Disable nwid in the mmc (no filtering) */ + mmc_out(ioaddr, mmwoff(0, mmw_loopt_sel), MMW_LOOPT_SEL_DIS_NWID); } break; @@ -1950,6 +1965,82 @@ wrq->u.sensitivity = psa.psa_thr_pre_set & 0x3F; break; + case SIOCSIWENCODE: + /* Set encryption key */ + if(!mmc_encr(ioaddr)) + { + ret = -EOPNOTSUPP; + break; + } + + if(wrq->u.encoding.method) + { /* enable encryption */ + int i; + long long key = wrq->u.encoding.code; + + for(i = 7; i >= 0; i--) + { + psa.psa_encryption_key[i] = key & 0xFF; + key >>= 8; + } + psa.psa_encryption_select = 1; + psa_write(ioaddr, lp->hacr, + (char *) &psa.psa_encryption_select - (char *) &psa, + (unsigned char *) &psa.psa_encryption_select, 8+1); + + mmc_out(ioaddr, mmwoff(0, mmw_encr_enable), + MMW_ENCR_ENABLE_EN | MMW_ENCR_ENABLE_MODE); + mmc_write(ioaddr, mmwoff(0, mmw_encr_key), + (unsigned char *) &psa.psa_encryption_key, 8); + } + else + { /* disable encryption */ + psa.psa_encryption_select = 0; + psa_write(ioaddr, lp->hacr, + (char *) &psa.psa_encryption_select - (char *) &psa, + (unsigned char *) &psa.psa_encryption_select, 1); + + mmc_out(ioaddr, mmwoff(0, mmw_encr_enable), 0); + } + break; + + case SIOCGIWENCODE: + /* Read the encryption key */ + if(!mmc_encr(ioaddr)) + { + ret = -EOPNOTSUPP; + break; + } + + /* only super-user can see encryption key */ + if(!suser()) + { + ret = -EPERM; + break; + } + else + { + int i; + long long key = 0; + + psa_read(ioaddr, lp->hacr, + (char *) &psa.psa_encryption_select - (char *) &psa, + (unsigned char *) &psa.psa_encryption_select, 1+8); + for(i = 0; i < 8; i++) + { + key <<= 8; + key += psa.psa_encryption_key[i]; + } + wrq->u.encoding.code = key; + + /* encryption is enabled */ + if(psa.psa_encryption_select) + wrq->u.encoding.method = mmc_encr(ioaddr); + else + wrq->u.encoding.method = 0; + } + break; + case SIOCGIWRANGE: /* Basic checking... */ if(wrq->u.data.pointer != (caddr_t) 0) @@ -2207,7 +2298,7 @@ static iw_stats * wavelan_get_wireless_stats(device * dev) { - unsigned short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; net_local * lp = (net_local *) dev->priv; mmr_t m; iw_stats * wstats; @@ -2281,7 +2372,7 @@ int sksize) { net_local * lp = (net_local *) dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; struct sk_buff * skb; #ifdef DEBUG_RX_TRACE @@ -2369,7 +2460,7 @@ static inline void wv_receive(device * dev) { - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; net_local * lp = (net_local *)dev->priv; int nreaped = 0; @@ -2555,7 +2646,7 @@ short length) { net_local * lp = (net_local *) dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; unsigned short txblock; unsigned short txpred; unsigned short tx_addr; @@ -2760,7 +2851,7 @@ static inline int wv_mmc_init(device * dev) { - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; net_local * lp = (net_local *)dev->priv; psa_t psa; mmw_t m; @@ -2789,6 +2880,9 @@ /* As NWID is not set : no NWID checking */ psa.psa_nwid_select = 0; + /* Disable encryption */ + psa.psa_encryption_select = 0; + /* Set to standard values * 0x04 for AT, * 0x01 for MCA, @@ -2806,7 +2900,7 @@ #ifdef USE_PSA_CONFIG /* Write the psa */ psa_write(ioaddr, lp->hacr, (char *)psa.psa_nwid - (char *)&psa, - (unsigned char *)psa.psa_nwid, 3); + (unsigned char *)psa.psa_nwid, 4); psa_write(ioaddr, lp->hacr, (char *)&psa.psa_thr_pre_set - (char *)&psa, (unsigned char *)&psa.psa_thr_pre_set, 1); psa_write(ioaddr, lp->hacr, (char *)&psa.psa_quality_thr - (char *)&psa, @@ -2828,6 +2922,14 @@ else m.mmw_loopt_sel = MMW_LOOPT_SEL_DIS_NWID; + memcpy(&m.mmw_encr_key, &psa.psa_encryption_key, + sizeof(m.mmw_encr_key)); + + if(psa.psa_encryption_select) + m.mmw_encr_enable = MMW_ENCR_ENABLE_EN | MMW_ENCR_ENABLE_MODE; + else + m.mmw_encr_enable = 0; + m.mmw_thr_pre_set = psa.psa_thr_pre_set & 0x3F; m.mmw_quality_thr = psa.psa_quality_thr & 0x0F; @@ -2919,7 +3021,7 @@ wv_ru_start(device * dev) { net_local * lp = (net_local *) dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; u_short scb_cs; fd_t fd; rbd_t rbd; @@ -3013,7 +3115,7 @@ wv_cu_start(device * dev) { net_local * lp = (net_local *) dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; int i; u_short txblock; u_short first_nop; @@ -3114,7 +3216,7 @@ wv_82586_start(device * dev) { net_local * lp = (net_local *) dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; scp_t scp; /* system configuration pointer */ iscp_t iscp; /* intermediate scp */ scb_t scb; /* system control block */ @@ -3244,7 +3346,7 @@ wv_82586_config(device * dev) { net_local * lp = (net_local *) dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; unsigned short txblock; unsigned short txpred; unsigned short tx_addr; @@ -3440,7 +3542,7 @@ wv_82586_stop(device * dev) { net_local * lp = (net_local *) dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; u_short scb_cmd; #ifdef DEBUG_CONFIG_TRACE @@ -3475,7 +3577,7 @@ wv_hw_reset(device * dev) { net_local * lp = (net_local *)dev->priv; - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; #ifdef DEBUG_CONFIG_TRACE printk(KERN_DEBUG "%s: ->wv_hw_reset(dev=0x%x)\n", dev->name, @@ -3520,7 +3622,7 @@ * (called in wavelan_probe() and init_module()) */ static int -wv_check_ioaddr(u_short ioaddr, +wv_check_ioaddr(u_long ioaddr, u_char * mac) { int i; /* Loop counter */ @@ -3567,7 +3669,7 @@ struct pt_regs * regs) { device * dev; - u_short ioaddr; + u_long ioaddr; net_local * lp; u_short hasr; u_short status; @@ -3714,7 +3816,7 @@ { device * dev; net_local * lp; - unsigned short ioaddr; + u_long ioaddr; unsigned long x; unsigned int nreaped; @@ -3909,7 +4011,7 @@ static int wavelan_config(device * dev) { - u_short ioaddr = dev->base_addr; + u_long ioaddr = dev->base_addr; u_char irq_mask; int irq; net_local * lp; diff -u --recursive --new-file v2.0.30/linux/drivers/net/wavelan.p.h linux/drivers/net/wavelan.p.h --- v2.0.30/linux/drivers/net/wavelan.p.h Thu Mar 6 10:03:51 1997 +++ linux/drivers/net/wavelan.p.h Sun Aug 3 15:17:45 1997 @@ -35,6 +35,12 @@ /* ------------------------ SPECIFIC NOTES ------------------------ */ /* + * wavelan.o is darn too big + * ------------------------- + * That's true ! There is a very simple way to reduce the driver + * object by 33% (yes !). Comment out the following line : + * #include + * * MAC address and hardware detection : * ---------------------------------- * The detection code of the wavelan chech that the first 3 @@ -80,14 +86,12 @@ * caracteristics of the hardware in a standard way and support for * applications for taking advantage of it (like Mobile IP). * - * By default, these wireless extensions are disabled, because they - * need a patch to the Linux Kernel. This simple patch may be found - * with the driver + some utilities to access those wireless - * extensions (iwconfig...). Hopefully, those wireless extensions will - * make their way in the kernel someday. + * You will need to enable the CONFIG_NET_RADIO define in the kernel + * configuration to enable the wireless extensions (this is the one + * giving access to the radio network device choice). * - * You also will need to enable the CONFIG_NET_RADIO in the kernel - * configuration to enable the wireless extensions. + * It might also be a good idea as well to fetch the wireless tools to + * configure the device and play a bit. */ /* ---------------------------- FILES ---------------------------- */ @@ -161,6 +165,7 @@ * Ajay Bakre (bakre@paul.rutgers.edu), * Donald Becker (becker@cesdis.gsfc.nasa.gov), * Loeke Brederveld (Loeke.Brederveld@Utrecht.NCR.com), + * Brent Elphick , * Anders Klemets (klemets@it.kth.se), * Vladimir V. Kolpakov (w@stier.koenig.ru), * Marc Meertens (Marc.Meertens@Utrecht.NCR.com), @@ -185,6 +190,7 @@ * John Rosenberg (johnr@cs.usyd.edu.au), * George Rossi (george@phm.gov.au), * Arthur Scott (arthur@cs.usyd.edu.au), + * Stanislav Sinyagin * Peter Storey, * for their assistance and advice. * @@ -260,9 +266,13 @@ * - Remove extern kerword for wavelan_probe() * - Level threshold is now a standard wireless extension (version 4 !) * + * Changes made for release in 2.1.36 : + * ---------------------------------- + * - Encryption setting from Brent Elphick (thanks a lot !) + * - 'ioaddr' to 'u_long' for the Alpha (thanks to Stanislav Sinyagin) + * * Wishes & dreams : * --------------- - * - Encryption stuff * - Roaming */ @@ -345,7 +355,7 @@ /************************ CONSTANTS & MACROS ************************/ #ifdef DEBUG_VERSION_SHOW -static const char *version = "wavelan.c : v15 (wireless extensions) 12/2/97\n"; +static const char *version = "wavelan.c : v16 (wireless extensions) 17/4/97\n"; #endif /* Watchdog temporisation */ @@ -442,69 +452,65 @@ wv_psa_to_irq(u_char); /* ------------------- HOST ADAPTER SUBROUTINES ------------------- */ static inline u_short /* data */ - hasr_read(u_short); /* Read the host interface : base address */ + hasr_read(u_long); /* Read the host interface : base address */ static inline void - hacr_write(u_short, /* Write to host interface : base address */ + hacr_write(u_long, /* Write to host interface : base address */ u_short), /* data */ - hacr_write_slow(u_short, + hacr_write_slow(u_long, u_short), - set_chan_attn(u_short, /* ioaddr */ + set_chan_attn(u_long, /* ioaddr */ u_short), /* hacr */ - wv_hacr_reset(u_short), /* ioaddr */ - wv_16_off(u_short, /* ioaddr */ + wv_hacr_reset(u_long), /* ioaddr */ + wv_16_off(u_long, /* ioaddr */ u_short), /* hacr */ - wv_16_on(u_short, /* ioaddr */ + wv_16_on(u_long, /* ioaddr */ u_short), /* hacr */ wv_ints_off(device *), wv_ints_on(device *); /* ----------------- MODEM MANAGEMENT SUBROUTINES ----------------- */ static void - psa_read(u_short, /* Read the Parameter Storage Area */ + psa_read(u_long, /* Read the Parameter Storage Area */ u_short, /* hacr */ int, /* offset in PSA */ u_char *, /* buffer to fill */ int), /* size to read */ - psa_write(u_short, /* Write to the PSA */ + psa_write(u_long, /* Write to the PSA */ u_short, /* hacr */ int, /* Offset in psa */ u_char *, /* Buffer in memory */ int); /* Length of buffer */ static inline void - mmc_out(u_short, /* Write 1 byte to the Modem Manag Control */ + mmc_out(u_long, /* Write 1 byte to the Modem Manag Control */ u_short, u_char), - mmc_write(u_short, /* Write n bytes to the MMC */ + mmc_write(u_long, /* Write n bytes to the MMC */ u_char, u_char *, int); static inline u_char /* Read 1 byte from the MMC */ - mmc_in(u_short, + mmc_in(u_long, u_short); static inline void - mmc_read(u_short, /* Read n bytes from the MMC */ + mmc_read(u_long, /* Read n bytes from the MMC */ u_char, u_char *, int), - fee_wait(u_short, /* Wait for frequency EEprom : base address */ + fee_wait(u_long, /* Wait for frequency EEprom : base address */ int, /* Base delay to wait for */ int); /* Number of time to wait */ static void - fee_read(u_short, /* Read the frequency EEprom : base address */ + fee_read(u_long, /* Read the frequency EEprom : base address */ u_short, /* destination offset */ u_short *, /* data buffer */ - int), /* number of registers */ - fee_write(u_short, /* Write to frequency EEprom : base address */ - u_short, /* destination offset */ - u_short *, /* data buffer */ - int); /* number of registers */ + int); /* number of registers */ /* ---------------------- I82586 SUBROUTINES ----------------------- */ static /*inline*/ void - obram_read(u_short, /* ioaddr */ + obram_read(u_long, /* ioaddr */ u_short, /* o */ u_char *, /* b */ int); /* n */ static inline void - obram_write(u_short, /* ioaddr */ + obram_write(u_long, /* ioaddr */ u_short, /* o */ u_char *, /* b */ int); /* n */ @@ -514,11 +520,11 @@ wv_synchronous_cmd(device *, const char *), wv_config_complete(device *, - u_short, + u_long, net_local *); static int wv_complete(device *, - u_short, + u_long, net_local *); static inline void wv_82586_reconfig(device *); @@ -560,7 +566,7 @@ wv_82586_stop(device *); static int wv_hw_reset(device *), /* Reset the wavelan hardware */ - wv_check_ioaddr(u_short, /* ioaddr */ + wv_check_ioaddr(u_long, /* ioaddr */ u_char *); /* mac address (read) */ /* ---------------------- INTERRUPT HANDLING ---------------------- */ static void diff -u --recursive --new-file v2.0.30/linux/drivers/pci/pci.c linux/drivers/pci/pci.c --- v2.0.30/linux/drivers/pci/pci.c Mon Mar 31 13:23:23 1997 +++ linux/drivers/pci/pci.c Mon Aug 4 11:45:55 1997 @@ -241,6 +241,7 @@ DEVICE( INTEL, INTEL_82371SB_1,"82371SB Natoma/Triton II PIIX3"), DEVICE( INTEL, INTEL_82371SB_2,"82371SB Natoma/Triton II PIIX3"), DEVICE( INTEL, INTEL_82437VX, "82437VX Triton II"), + DEVICE( INTEL, INTEL_82371AB, "82371AB 430TX PIIX4"), DEVICE( INTEL, INTEL_P6, "Orion P6"), DEVICE( ADAPTEC, ADAPTEC_7850, "AIC-7850"), DEVICE( ADAPTEC, ADAPTEC_7855, "AIC-7855"), diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/Config.in linux/drivers/scsi/Config.in --- v2.0.30/linux/drivers/scsi/Config.in Fri Feb 28 15:14:18 1997 +++ linux/drivers/scsi/Config.in Mon Aug 4 14:57:13 1997 @@ -18,7 +18,14 @@ dep_tristate 'Adaptec AHA152X/2825 support' CONFIG_SCSI_AHA152X $CONFIG_SCSI dep_tristate 'Adaptec AHA1542 support' CONFIG_SCSI_AHA1542 $CONFIG_SCSI dep_tristate 'Adaptec AHA1740 support' CONFIG_SCSI_AHA1740 $CONFIG_SCSI -dep_tristate 'Adaptec AHA274X/284X/294X support' CONFIG_SCSI_AIC7XXX $CONFIG_SCSI +dep_tristate 'Adaptec AIC7xxx support' CONFIG_SCSI_AIC7XXX $CONFIG_SCSI +if [ "$CONFIG_SCSI_AIC7XXX" != "n" ]; then + bool ' Enable tagged command queueing' CONFIG_AIC7XXX_TAGGED_QUEUEING Y + int ' Maximum number of commands per LUN' CONFIG_AIC7XXX_CMDS_PER_LUN 8 + bool ' Enable SCB paging' CONFIG_AIC7XXX_PAGE_ENABLE N + bool ' Collect statistics to report in /proc' CONFIG_AIC7XXX_PROC_STATS N + int ' delay in seconds after SCSI bus reset' CONFIG_AIC7XXX_RESET_DELAY 15 +fi dep_tristate 'AdvanSys SCSI support' CONFIG_SCSI_ADVANSYS $CONFIG_SCSI dep_tristate 'Always IN2000 SCSI support' CONFIG_SCSI_IN2000 $CONFIG_SCSI dep_tristate 'AM53/79C974 PCI SCSI support' CONFIG_SCSI_AM53C974 $CONFIG_SCSI diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/Makefile linux/drivers/scsi/Makefile --- v2.0.30/linux/drivers/scsi/Makefile Fri Feb 28 15:14:18 1997 +++ linux/drivers/scsi/Makefile Mon Aug 4 14:56:15 1997 @@ -348,6 +348,11 @@ endif endif + +ifeq ($(CONFIG_BLK_DEV_IDESCSI),y) +L_OBJS += ide-scsi.o +endif + include $(TOPDIR)/Rules.make BusLogic.o: BusLogic.c FlashPoint.c @@ -359,12 +364,8 @@ aha152x.o: aha152x.c $(CC) $(CFLAGS) $(AHA152X) -c aha152x.c -aic7xxx_asm: aic7xxx_asm.c - $(HOSTCC) -o $@ aic7xxx_asm.c - -aic7xxx.c: aic7xxx_seq.h -aic7xxx_seq.h: aic7xxx_asm aic7xxx.seq - ./aic7xxx_asm -o $@ aic7xxx.seq +aic7xxx.o: aic7xxx.c aic7xxx_seq.h aic7xxx_reg.h + $(CC) $(CFLAGS) -c -o $@ aic7xxx.c seagate.o: seagate.c $(CC) $(CFLAGS) -DARBITRATE -DSLOW_HANDSHAKE -DFAST32 -c seagate.c diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx/aic7xxx.reg linux/drivers/scsi/aic7xxx/aic7xxx.reg --- v2.0.30/linux/drivers/scsi/aic7xxx/aic7xxx.reg Wed Dec 31 16:00:00 1969 +++ linux/drivers/scsi/aic7xxx/aic7xxx.reg Thu Jul 31 12:37:17 1997 @@ -0,0 +1,1120 @@ +/* + * Aic7xxx register and scratch ram definitions. + * + * 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 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.reg,v 1.4 1997/06/27 19:38:39 gibbs Exp $ + */ + +/* + * This file is processed by the aic7xxx_asm utility for use in assembling + * firmware for the aic7xxx family of SCSI host adapters as well as to generate + * a C header file for use in the kernel portion of the Aic7xxx driver. + * + * All page numbers refer to the Adaptec AIC-7770 Data Book available from + * Adaptec's Technical Documents Department 1-800-934-2766 + */ + +/* + * SCSI Sequence Control (p. 3-11). + * Each bit, when set starts a specific SCSI sequence on the bus + */ +register SCSISEQ { + address 0x000 + access_mode RW + bit TEMODE 0x80 + bit ENSELO 0x40 + bit ENSELI 0x20 + bit ENRSELI 0x10 + bit ENAUTOATNO 0x08 + bit ENAUTOATNI 0x04 + bit ENAUTOATNP 0x02 + bit SCSIRSTO 0x01 +} + +/* + * SCSI Transfer Control 0 Register (pp. 3-13). + * Controls the SCSI module data path. + */ +register SXFRCTL0 { + address 0x001 + access_mode RW + bit DFON 0x80 + bit DFPEXP 0x40 + bit FAST20 0x20 + bit CLRSTCNT 0x10 + bit SPIOEN 0x08 + bit SCAMEN 0x04 + bit CLRCHN 0x02 +} + +/* + * SCSI Transfer Control 1 Register (pp. 3-14,15). + * Controls the SCSI module data path. + */ +register SXFRCTL1 { + address 0x002 + access_mode RW + bit BITBUCKET 0x80 + bit SWRAPEN 0x40 + bit ENSPCHK 0x20 + mask STIMESEL 0x18 + bit ENSTIMER 0x04 + bit ACTNEGEN 0x02 + bit STPWEN 0x01 /* Powered Termination */ +} + +/* + * SCSI Control Signal Read Register (p. 3-15). + * Reads the actual state of the SCSI bus pins + */ +register SCSISIGI { + address 0x003 + access_mode RO + bit CDI 0x80 + bit IOI 0x40 + bit MSGI 0x20 + bit ATNI 0x10 + bit SELI 0x08 + bit BSYI 0x04 + bit REQI 0x02 + bit ACKI 0x01 +/* + * Possible phases in SCSISIGI + */ + mask PHASE_MASK CDI|IOI|MSGI + mask P_DATAOUT 0x00 + mask P_DATAIN IOI + mask P_COMMAND CDI + mask P_MESGOUT CDI|MSGI + mask P_STATUS CDI|IOI + mask P_MESGIN CDI|IOI|MSGI +} + +/* + * SCSI Control Signal Write Register (p. 3-16). + * Writing to this register modifies the control signals on the bus. Only + * those signals that are allowed in the current mode (Initiator/Target) are + * asserted. + */ +register SCSISIGO { + address 0x003 + access_mode WO + bit CDO 0x80 + bit IOO 0x40 + bit MSGO 0x20 + bit ATNO 0x10 + bit SELO 0x08 + bit BSYO 0x04 + bit REQO 0x02 + bit ACKO 0x01 +/* + * Possible phases to write into SCSISIG0 + */ + mask PHASE_MASK CDI|IOI|MSGI + mask P_DATAOUT 0x00 + mask P_DATAIN IOI + mask P_COMMAND CDI + mask P_MESGOUT CDI|MSGI + mask P_STATUS CDI|IOI + mask P_MESGIN CDI|IOI|MSGI +} + +/* + * SCSI Rate Control (p. 3-17). + * Contents of this register determine the Synchronous SCSI data transfer + * rate and the maximum synchronous Req/Ack offset. An offset of 0 in the + * SOFS (3:0) bits disables synchronous data transfers. Any offset value + * greater than 0 enables synchronous transfers. + */ +register SCSIRATE { + address 0x004 + access_mode RW + bit WIDEXFER 0x80 /* Wide transfer control */ + mask SXFR 0x70 /* Sync transfer rate */ + mask SOFS 0x0f /* Sync offset */ +} + +/* + * SCSI ID (p. 3-18). + * Contains the ID of the board and the current target on the + * selected channel. + */ +register SCSIID { + address 0x005 + access_mode RW + mask TID 0xf0 /* Target ID mask */ + mask OID 0x0f /* Our ID mask */ +} + +/* + * SCSI Latched Data (p. 3-19). + * Read/Write latches used to transfer data on the SCSI bus during + * Automatic or Manual PIO mode. SCSIDATH can be used for the + * upper byte of a 16bit wide asynchronouse data phase transfer. + */ +register SCSIDATL { + address 0x006 + access_mode RW +} + +register SCSIDATH { + address 0x007 + access_mode RW +} + +/* + * SCSI Transfer Count (pp. 3-19,20) + * These registers count down the number of bytes transferred + * across the SCSI bus. The counter is decremented only once + * the data has been safely transferred. SDONE in SSTAT0 is + * set when STCNT goes to 0 + */ +register STCNT { + address 0x008 + size 3 + access_mode RW +} + +/* + * Clear SCSI Interrupt 0 (p. 3-20) + * Writing a 1 to a bit clears the associated SCSI Interrupt in SSTAT0. + */ +register CLRSINT0 { + address 0x00b + access_mode WO + bit CLRSELDO 0x40 + bit CLRSELDI 0x20 + bit CLRSELINGO 0x10 + bit CLRSWRAP 0x08 + bit CLRSPIORDY 0x02 +} + +/* + * SCSI Status 0 (p. 3-21) + * Contains one set of SCSI Interrupt codes + * These are most likely of interest to the sequencer + */ +register SSTAT0 { + address 0x00b + access_mode RO + bit TARGET 0x80 /* Board acting as target */ + bit SELDO 0x40 /* Selection Done */ + bit SELDI 0x20 /* Board has been selected */ + bit SELINGO 0x10 /* Selection In Progress */ + bit SWRAP 0x08 /* 24bit counter wrap */ + bit SDONE 0x04 /* STCNT = 0x000000 */ + bit SPIORDY 0x02 /* SCSI PIO Ready */ + bit DMADONE 0x01 /* DMA transfer completed */ +} + +/* + * Clear SCSI Interrupt 1 (p. 3-23) + * Writing a 1 to a bit clears the associated SCSI Interrupt in SSTAT1. + */ +register CLRSINT1 { + address 0x00c + access_mode WO + bit CLRSELTIMEO 0x80 + bit CLRATNO 0x40 + bit CLRSCSIRSTI 0x20 + bit CLRBUSFREE 0x08 + bit CLRSCSIPERR 0x04 + bit CLRPHASECHG 0x02 + bit CLRREQINIT 0x01 +} + +/* + * SCSI Status 1 (p. 3-24) + */ +register SSTAT1 { + address 0x00c + access_mode RO + bit SELTO 0x80 + bit ATNTARG 0x40 + bit SCSIRSTI 0x20 + bit PHASEMIS 0x10 + bit BUSFREE 0x08 + bit SCSIPERR 0x04 + bit PHASECHG 0x02 + bit REQINIT 0x01 +} + +/* + * SCSI Status 2 (pp. 3-25,26) + */ +register SSTAT2 { + address 0x00d + access_mode RO + bit OVERRUN 0x80 + mask SFCNT 0x1f +} + +/* + * SCSI Status 3 (p. 3-26) + */ +register SSTAT3 { + address 0x00e + access_mode RO + mask SCSICNT 0xf0 + mask OFFCNT 0x0f +} + +/* + * SCSI Test Control (p. 3-27) + */ +register SCSITEST { + address 0x00f + access_mode RW + bit RQAKCNT 0x04 + bit CNTRTEST 0x02 + bit CMODE 0x01 +} + +/* + * SCSI Interrupt Mode 1 (p. 3-28) + * Setting any bit will enable the corresponding function + * in SIMODE0 to interrupt via the IRQ pin. + */ +register SIMODE0 { + address 0x010 + access_mode RW + bit ENSELDO 0x40 + bit ENSELDI 0x20 + bit ENSELINGO 0x10 + bit ENSWRAP 0x08 + bit ENSDONE 0x04 + bit ENSPIORDY 0x02 + bit ENDMADONE 0x01 +} + +/* + * SCSI Interrupt Mode 1 (pp. 3-28,29) + * Setting any bit will enable the corresponding function + * in SIMODE1 to interrupt via the IRQ pin. + */ +register SIMODE1 { + address 0x011 + access_mode RW + bit ENSELTIMO 0x80 + bit ENATNTARG 0x40 + bit ENSCSIRST 0x20 + bit ENPHASEMIS 0x10 + bit ENBUSFREE 0x08 + bit ENSCSIPERR 0x04 + bit ENPHASECHG 0x02 + bit ENREQINIT 0x01 +} + +/* + * SCSI Data Bus (High) (p. 3-29) + * This register reads data on the SCSI Data bus directly. + */ +register SCSIBUSL { + address 0x012 + access_mode RO +} + +register SCSIBUSH { + address 0x013 + access_mode RO +} + +/* + * SCSI/Host Address (p. 3-30) + * These registers hold the host address for the byte about to be + * transferred on the SCSI bus. They are counted up in the same + * manner as STCNT is counted down. SHADDR should always be used + * to determine the address of the last byte transferred since HADDR + * can be skewed by write ahead. + */ +register SHADDR { + address 0x014 + size 4 + access_mode RO +} + +/* + * Selection Timeout Timer (p. 3-30) + */ +register SELTIMER { + address 0x018 + access_mode RW + bit STAGE6 0x20 + bit STAGE5 0x10 + bit STAGE4 0x08 + bit STAGE3 0x04 + bit STAGE2 0x02 + bit STAGE1 0x01 +} + +/* + * Selection/Reselection ID (p. 3-31) + * Upper four bits are the device id. The ONEBIT is set when the re/selecting + * device did not set its own ID. + */ +register SELID { + address 0x019 + access_mode RW + mask SELID_MASK 0xf0 + bit ONEBIT 0x08 +} + +/* + * SCSI Block Control (p. 3-32) + * Controls Bus type and channel selection. In a twin channel configuration + * addresses 0x00-0x1e are gated to the appropriate channel based on this + * register. SELWIDE allows for the coexistence of 8bit and 16bit devices + * on a wide bus. + */ +register SBLKCTL { + address 0x01f + access_mode RW + bit DIAGLEDEN 0x80 /* Aic78X0 only */ + bit DIAGLEDON 0x40 /* Aic78X0 only */ + bit AUTOFLUSHDIS 0x20 + bit SELBUSB 0x08 + bit SELWIDE 0x02 +} + +/* + * Sequencer Control (p. 3-33) + * Error detection mode and speed configuration + */ +register SEQCTL { + address 0x060 + access_mode RW + bit PERRORDIS 0x80 + bit PAUSEDIS 0x40 + bit FAILDIS 0x20 + bit FASTMODE 0x10 + bit BRKADRINTEN 0x08 + bit STEP 0x04 + bit SEQRESET 0x02 + bit LOADRAM 0x01 +} + +/* + * Sequencer RAM Data (p. 3-34) + * Single byte window into the Scratch Ram area starting at the address + * specified by SEQADDR0 and SEQADDR1. To write a full word, simply write + * four bytes in sucessesion. The SEQADDRs will increment after the most + * significant byte is written + */ +register SEQRAM { + address 0x061 + access_mode RW +} + +/* + * Sequencer Address Registers (p. 3-35) + * Only the first bit of SEQADDR1 holds addressing information + */ +register SEQADDR0 { + address 0x062 + access_mode RW +} + +register SEQADDR1 { + address 0x063 + access_mode RW + mask SEQADDR1_MASK 0x01 +} + +/* + * Accumulator + * We cheat by passing arguments in the Accumulator up to the kernel driver + */ +register ACCUM { + address 0x064 + access_mode RW + accumulator +} + +register SINDEX { + address 0x065 + access_mode RW + sindex +} + +register DINDEX { + address 0x066 + access_mode RW +} + +register ALLONES { + address 0x069 + access_mode RO + allones +} + +register ALLZEROS { + address 0x06a + access_mode RO + allzeros +} + +register NONE { + address 0x06a + access_mode WO + none +} + +register FLAGS { + address 0x06b + access_mode RO + bit ZERO 0x02 + bit CARRY 0x01 +} + +register SINDIR { + address 0x06c + access_mode RO +} + +register DINDIR { + address 0x06d + access_mode WO +} + +register FUNCTION1 { + address 0x06e + access_mode RW +} + +register STACK { + address 0x06f + access_mode RO +} + +/* + * Board Control (p. 3-43) + */ +register BCTL { + address 0x084 + access_mode RW + bit ACE 0x08 + bit ENABLE 0x01 +} + +/* + * On the aic78X0 chips, Board Control is replaced by the DSCommand + * register (p. 4-64) + */ +register DSCOMMAND { + address 0x084 + access_mode RW + bit CACHETHEN 0x80 /* Cache Threshold enable */ + bit DPARCKEN 0x40 /* Data Parity Check Enable */ + bit MPARCKEN 0x20 /* Memory Parity Check Enable */ + bit EXTREQLCK 0x10 /* External Request Lock */ +} + +/* + * Bus On/Off Time (p. 3-44) + */ +register BUSTIME { + address 0x085 + access_mode RW + mask BOFF 0xf0 + mask BON 0x0f +} + +/* + * Bus Speed (p. 3-45) + */ +register BUSSPD { + address 0x086 + access_mode RW + mask DFTHRSH 0xc0 + mask STBOFF 0x38 + mask STBON 0x07 + mask DFTHRSH_100 0xc0 +} + +/* + * Host Control (p. 3-47) R/W + * Overall host control of the device. + */ +register HCNTRL { + address 0x087 + access_mode RW + bit POWRDN 0x40 + bit SWINT 0x10 + bit IRQMS 0x08 + bit PAUSE 0x04 + bit INTEN 0x02 + bit CHIPRST 0x01 + bit CHIPRSTACK 0x01 +} + +/* + * Host Address (p. 3-48) + * This register contains the address of the byte about + * to be transferred across the host bus. + */ +register HADDR { + address 0x088 + size 4 + access_mode RW +} + +register HCNT { + address 0x08c + size 3 + access_mode RW +} + +/* + * SCB Pointer (p. 3-49) + * Gate one of the four SCBs into the SCBARRAY window. + */ +register SCBPTR { + address 0x090 + access_mode RW +} + +/* + * Interrupt Status (p. 3-50) + * Status for system interrupts + */ +register INTSTAT { + address 0x091 + access_mode RW + bit BRKADRINT 0x08 + bit SCSIINT 0x04 + bit CMDCMPLT 0x02 + bit SEQINT 0x01 + mask BAD_PHASE SEQINT /* unknown scsi bus phase */ + mask SEND_REJECT 0x10|SEQINT /* sending a message reject */ + mask NO_IDENT 0x20|SEQINT /* no IDENTIFY after reconnect*/ + mask NO_MATCH 0x30|SEQINT /* no cmd match for reconnect */ + mask EXTENDED_MSG 0x40|SEQINT /* Extended message received */ + mask NO_MATCH_BUSY 0x50|SEQINT /* Couldn't find BUSY SCB */ + mask REJECT_MSG 0x60|SEQINT /* Reject message received */ + mask BAD_STATUS 0x70|SEQINT /* Bad status from target */ + mask RESIDUAL 0x80|SEQINT /* Residual byte count != 0 */ + mask ABORT_CMDCMPLT 0x91 /* + * Command tagged for abort + * completed successfully. + */ + mask AWAITING_MSG 0xa0|SEQINT /* + * Kernel requested to specify + * a message to this target + * (command was null), so tell + * it that it can fill the + * message buffer. + */ + mask MSG_BUFFER_BUSY 0xc0|SEQINT /* + * Sequencer wants to use the + * message buffer, but it + * already contains a message + */ + mask MSGIN_PHASEMIS 0xd0|SEQINT /* + * Target changed phase on us + * when we were expecting + * another msgin byte. + */ + mask DATA_OVERRUN 0xe0|SEQINT /* + * Target attempted to write + * beyond the bounds of its + * command. + */ + + mask SEQINT_MASK 0xf0|SEQINT /* SEQINT Status Codes */ + mask INT_PEND (BRKADRINT|SEQINT|SCSIINT|CMDCMPLT) +} + +/* + * Hard Error (p. 3-53) + * Reporting of catastrophic errors. You usually cannot recover from + * these without a full board reset. + */ +register ERROR { + address 0x092 + access_mode RO + bit PARERR 0x08 + bit ILLOPCODE 0x04 + bit ILLSADDR 0x02 + bit ILLHADDR 0x01 +} + +/* + * Clear Interrupt Status (p. 3-52) + */ +register CLRINT { + address 0x092 + access_mode WO + bit CLRBRKADRINT 0x08 + bit CLRSCSIINT 0x04 + bit CLRCMDINT 0x02 + bit CLRSEQINT 0x01 +} + +register DFCNTRL { + address 0x093 + access_mode RW + bit WIDEODD 0x40 + bit SCSIEN 0x20 + bit SDMAEN 0x10 + bit SDMAENACK 0x10 + bit HDMAEN 0x08 + bit HDMAENACK 0x08 + bit DIRECTION 0x04 + bit FIFOFLUSH 0x02 + bit FIFORESET 0x01 +} + +register DFSTATUS { + address 0x094 + access_mode RO + bit DWORDEMP 0x20 + bit MREQPEND 0x10 + bit HDONE 0x08 + bit DFTHRESH 0x04 + bit FIFOFULL 0x02 + bit FIFOEMP 0x01 +} + +register DFDAT { + address 0x099 + access_mode RW +} + +/* + * SCB Auto Increment (p. 3-59) + * Byte offset into the SCB Array and an optional bit to allow auto + * incrementing of the address during download and upload operations + */ +register SCBCNT { + address 0x09a + access_mode RW + bit SCBAUTO 0x80 + mask SCBCNT_MASK 0x1f +} + +/* + * Queue In FIFO (p. 3-60) + * Input queue for queued SCBs (commands that the seqencer has yet to start) + */ +register QINFIFO { + address 0x09b + access_mode RW +} + +/* + * Queue In Count (p. 3-60) + * Number of queued SCBs + */ +register QINCNT { + address 0x09c + access_mode RO +} + +/* + * Queue Out FIFO (p. 3-61) + * Queue of SCBs that have completed and await the host + */ +register QOUTFIFO { + address 0x09d + access_mode WO +} + +/* + * Queue Out Count (p. 3-61) + * Number of queued SCBs in the Out FIFO + */ +register QOUTCNT { + address 0x09e + access_mode RO +} + +/* + * SCB Definition (p. 5-4) + */ +scb { + address 0x0a0 + SCB_CONTROL { + size 1 + bit MK_MESSAGE 0x80 + bit DISCENB 0x40 + bit TAG_ENB 0x20 + bit MUST_DMAUP_SCB 0x10 + bit ABORT_SCB 0x08 + bit DISCONNECTED 0x04 + mask SCB_TAG_TYPE 0x03 + } + SCB_TCL { + size 1 + bit SELBUSB 0x08 + mask TID 0xf0 + mask LID 0x07 + } + SCB_TARGET_STATUS { + size 1 + } + SCB_SGCOUNT { + size 1 + } + SCB_SGPTR { + size 4 + } + SCB_RESID_SGCNT { + size 1 + } + SCB_RESID_DCNT { + size 3 + } + SCB_DATAPTR { + size 4 + } + SCB_DATACNT { + size 3 + } + SCB_LINKED_NEXT { + size 1 + } + SCB_CMDPTR { + size 4 + } + SCB_CMDLEN { + size 1 + } + SCB_TAG { + size 1 + } + SCB_NEXT { + size 1 + } + SCB_PREV { + size 1 + } + SCB_BUSYTARGETS { + size 4 + } +} + +const SG_SIZEOF 0x08 /* sizeof(struct ahc_dma) */ + +/* --------------------- AHA-2840-only definitions -------------------- */ + +register SEECTL_2840 { + address 0x0c0 + access_mode RW + bit CS_2840 0x04 + bit CK_2840 0x02 + bit DO_2840 0x01 +} + +register STATUS_2840 { + address 0x0c1 + access_mode RW + bit EEPROM_TF 0x80 + mask BIOS_SEL 0x60 + mask ADSEL 0x1e + bit DI_2840 0x01 +} + +/* --------------------- AIC-7870-only definitions -------------------- */ + +register DSPCISTATUS { + address 0x086 +} + +register BRDCTL { + address 0x01d + bit BRDDAT7 0x80 + bit BRDDAT6 0x40 + bit BRDDAT5 0x20 + bit BRDSTB 0x10 + bit BRDCS 0x08 + bit BRDRW 0x04 + bit BRDCTL1 0x02 + bit BRDCTL0 0x01 +} + +/* + * Serial EEPROM Control (p. 4-92 in 7870 Databook) + * Controls the reading and writing of an external serial 1-bit + * EEPROM Device. In order to access the serial EEPROM, you must + * first set the SEEMS bit that generates a request to the memory + * port for access to the serial EEPROM device. When the memory + * port is not busy servicing another request, it reconfigures + * to allow access to the serial EEPROM. When this happens, SEERDY + * gets set high to verify that the memory port access has been + * granted. + * + * After successful arbitration for the memory port, the SEECS bit of + * the SEECTL register is connected to the chip select. The SEECK, + * SEEDO, and SEEDI are connected to the clock, data out, and data in + * lines respectively. The SEERDY bit of SEECTL is useful in that it + * gives us an 800 nsec timer. After a write to the SEECTL register, + * the SEERDY goes high 800 nsec later. The one exception to this is + * when we first request access to the memory port. The SEERDY goes + * high to signify that access has been granted and, for this case, has + * no implied timing. + * + * See 93cx6.c for detailed information on the protocol necessary to + * read the serial EEPROM. + */ +register SEECTL { + address 0x01e + bit EXTARBACK 0x80 + bit EXTARBREQ 0x40 + bit SEEMS 0x20 + bit SEERDY 0x10 + bit SEECS 0x08 + bit SEECK 0x04 + bit SEEDO 0x02 + bit SEEDI 0x01 +} +/* ---------------------- Scratch RAM Offsets ------------------------- */ +/* These offsets are either to values that are initialized by the board's + * BIOS or are specified by the sequencer code. + * + * The host adapter card (at least the BIOS) uses 20-2f for SCSI + * device information, 32-33 and 5a-5f as well. As it turns out, the + * BIOS trashes 20-2f, writing the synchronous negotiation results + * on top of the BIOS values, so we re-use those for our per-target + * scratchspace (actually a value that can be copied directly into + * SCSIRATE). The kernel driver will enable synchronous negotiation + * for all targets that have a value other than 0 in the lower four + * bits of the target scratch space. This should work regardless of + * whether the bios has been installed. + */ + +scratch_ram { + address 0x020 + + /* + * 1 byte per target starting at this address for configuration values + */ + TARG_SCRATCH { + size 16 + } + ULTRA_ENB { + size 2 + } + /* + * Bit vector of targets that have disconnection disabled. + */ + DISC_DSB { + size 2 + } + /* + * Length of pending message + */ + MSG_LEN { + size 1 + } + /* We reserve 8bytes to store outgoing messages */ + MSG_OUT { + size 8 + } + /* Parameters for DMA Logic */ + DMAPARAMS { + size 1 + bit WIDEODD 0x40 + bit SCSIEN 0x20 + bit SDMAEN 0x10 + bit SDMAENACK 0x10 + bit HDMAEN 0x08 + bit HDMAENACK 0x08 + bit DIRECTION 0x04 + bit FIFOFLUSH 0x02 + bit FIFORESET 0x01 + } + /* + * Number of SCBs supported by + * this card. + */ + SCBCOUNT { + size 1 + } + /* + * Two's complement of SCBCOUNT + */ + COMP_SCBCOUNT { + size 1 + } + /* + * Mask of bits to test against + * when looking at the Queue Count + * registers. Works around a bug + * on aic7850 chips. + */ + QCNTMASK { + size 1 + } + SEQ_FLAGS { + size 1 + bit RESELECTED 0x80 + bit IDENTIFY_SEEN 0x40 + bit TAGGED_SCB 0x20 + bit DPHASE 0x10 + bit PAGESCBS 0x04 + bit WIDE_BUS 0x02 + bit TWIN_BUS 0x01 + } + /* + * Temporary storage for the + * target/channel/lun of a + * reconnecting target + */ + SAVED_TCL { + size 1 + } + SG_COUNT { + size 1 + } + /* working value of SG pointer */ + SG_NEXT { + size 4 + } + /* + * head of list of SCBs awaiting + * selection + */ + WAITING_SCBH { + size 1 + } + SAVED_LINKPTR { + size 1 + } + SAVED_SCBPTR { + size 1 + } + /* + * The sequencer will stick the frist byte of any rejected message here + * so we can see what is getting thrown away. + */ + REJBYTE { + size 1 + } + /* + * The last bus phase as seen by the sequencer. + */ + LASTPHASE { + size 1 + bit CDI 0x80 + bit IOI 0x40 + bit MSGI 0x20 + mask PHASE_MASK CDI|IOI|MSGI + mask P_DATAOUT 0x00 + mask P_DATAIN IOI + mask P_COMMAND CDI + mask P_MESGOUT CDI|MSGI + mask P_STATUS CDI|IOI + mask P_MESGIN CDI|IOI|MSGI + mask P_BUSFREE 0x01 + } + MSGIN_EXT_LEN { + size 1 + } + MSGIN_EXT_OPCODE { + size 1 + } + /* + * location 3, stores the last + * byte of an extended message if + * it passes the two bytes of space + * we allow now. This byte isn't + * used for anything, it just makes + * the code shorter for tossing + * extra bytes. + */ + MSGIN_EXT_BYTES { + size 3 + } + /* + * head of list of SCBs that are + * disconnected. Used for SCB + * paging. + */ + DISCONNECTED_SCBH { + size 1 + } + /* + * head of list of SCBs that are + * not in use. Used for SCB paging. + */ + FREE_SCBH { + size 1 + } + HSCB_ADDR { + size 4 + } + CUR_SCBID { + size 1 + } + ARG_1 { + size 1 + mask SEND_MSG 0x80 + mask SEND_SENSE 0x40 + mask SEND_REJ 0x20 + alias RETURN_1 + } + /* + * These are reserved registers in the card's scratch ram. Some of + * the values are specified in the AHA2742 technical reference manual + * and are initialized by the BIOS at boot time. + */ + SCSICONF { + address 0x05a + size 1 + bit RESET_SCSI 0x40 + } + HOSTCONF { + address 0x05d + size 1 + } + HA_274_BIOSCTRL { + address 0x05f + size 1 + mask BIOSMODE 0x30 + mask BIOSDISABLED 0x30 + bit CHANNEL_B_PRIMARY 0x08 + } +} + +const SCB_LIST_NULL 0xff + + +/* WDTR Message values */ +const BUS_8_BIT 0x00 +const BUS_16_BIT 0x01 +const BUS_32_BIT 0x02 +const MAX_OFFSET_8BIT 0x0f +const MAX_OFFSET_16BIT 0x08 diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx/aic7xxx.seq linux/drivers/scsi/aic7xxx/aic7xxx.seq --- v2.0.30/linux/drivers/scsi/aic7xxx/aic7xxx.seq Wed Dec 31 16:00:00 1969 +++ linux/drivers/scsi/aic7xxx/aic7xxx.seq Thu Jul 31 12:37:17 1997 @@ -0,0 +1,1147 @@ +/* + * Adaptec 274x/284x/294x device driver firmware for Linux and FreeBSD. + * + * 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 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.seq,v 1.74 1997/06/27 19:38:42 gibbs Exp $ + */ + +#include +#include + +/* + * A few words on the waiting SCB list: + * After starting the selection hardware, we check for reconnecting targets + * as well as for our selection to complete just in case the reselection wins + * bus arbitration. The problem with this is that we must keep track of the + * SCB that we've already pulled from the QINFIFO and started the selection + * on just in case the reselection wins so that we can retry the selection at + * a later time. This problem cannot be resolved by holding a single entry + * in scratch ram since a reconnecting target can request sense and this will + * create yet another SCB waiting for selection. The solution used here is to + * use byte 27 of the SCB as a psuedo-next pointer and to thread a list + * of SCBs that are awaiting selection. Since 0-0xfe are valid SCB indexes, + * SCB_LIST_NULL is 0xff which is out of range. An entry is also added to + * this list everytime a request sense occurs or after completing a non-tagged + * command for which a second SCB has been queued. The sequencer will + * automatically consume the entries. + */ + +/* + * We assume that the kernel driver may reset us at any time, even in the + * middle of a DMA, so clear DFCNTRL too. + */ +reset: + clr SCSISIGO; /* De-assert BSY */ + /* Always allow reselection */ + mvi SCSISEQ, ENRSELI|ENAUTOATNP; + call clear_target_state; +poll_for_work: + test SSTAT0,SELDO jnz select; + test SSTAT0,SELDI jnz reselect; + test SCSISEQ, ENSELO jnz poll_for_work; +.if ( TWIN_CHANNEL ) + /* + * Twin channel devices cannot handle things like SELTO + * interrupts on the "background" channel. So, if we + * are selecting, keep polling the current channel util + * either a selection or reselection occurs. + */ + xor SBLKCTL,SELBUSB; /* Toggle to the other bus */ + test SSTAT0,SELDO jnz select; + test SSTAT0,SELDI jnz reselect; + test SCSISEQ, ENSELO jnz poll_for_work; + xor SBLKCTL,SELBUSB; /* Toggle back */ +.endif + cmp WAITING_SCBH,SCB_LIST_NULL jne start_waiting; +test_queue: + /* Has the driver posted any work for us? */ + mov A, QCNTMASK; + test QINCNT,A jz poll_for_work; + +/* + * We have at least one queued SCB now and we don't have any + * SCBs in the list of SCBs awaiting selection. If we have + * any SCBs available for use, pull the tag from the QINFIFO + * and get to work on it. + */ +.if ( SCB_PAGING ) + mov ALLZEROS call get_free_or_disc_scb; + cmp SINDEX, SCB_LIST_NULL je poll_for_work; +.endif +dequeue_scb: + mov CUR_SCBID,QINFIFO; +.if !( SCB_PAGING ) + /* In the non-paging case, the SCBID == hardware SCB index */ + mov SCBPTR, CUR_SCBID; +.endif +dma_queued_scb: +/* + * DMA the SCB from host ram into the current SCB location. + */ + mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; + mov CUR_SCBID call dma_scb; + +/* + * See if there is not already an active SCB for this target. This code + * locks out on a per target basis instead of target/lun. Although this + * is not ideal for devices that have multiple luns active at the same + * time, it is faster than looping through all SCB's looking for active + * commands. We also don't have enough spare SCB space for us to store the + * SCBID of the currently busy transaction for each target/lun making it + * impossible to link up the SCBs. + */ +test_busy: + test SCB_CONTROL, TAG_ENB|ABORT_SCB jnz start_scb; + mvi SEQCTL, PAUSEDIS|FASTMODE; + mov SAVED_SCBPTR, SCBPTR; + mov SCB_TCL call index_untagged_scb; + mov ARG_1, SINDIR; /* + * ARG_1 should + * now have the SCB ID of + * any active, non-tagged, + * command for this target. + */ + cmp ARG_1, SCB_LIST_NULL je make_busy; +.if ( SCB_PAGING ) + /* + * Put this SCB back onto the free list. It + * may be necessary to satisfy the search for + * the active SCB. + */ + mov SCBPTR, SAVED_SCBPTR; + call add_scb_to_free_list; + /* Find the active SCB */ + mov ALLZEROS call findSCB; + /* + * If we couldn't find it, tell the kernel. This should + * never happen. + */ + cmp SINDEX, SCB_LIST_NULL jne paged_busy_link; + mvi INTSTAT, NO_MATCH_BUSY; +paged_busy_link: + /* Link us in */ + mov SCB_LINKED_NEXT, CUR_SCBID; + /* Put it back on the disconnected list */ + call add_scb_to_disc_list; + mvi SEQCTL, FASTMODE; + jmp poll_for_work; +.else +simple_busy_link: + mov SCBPTR, ARG_1; + mov SCB_LINKED_NEXT, CUR_SCBID; + mvi SEQCTL, FASTMODE; + jmp poll_for_work; +.endif +make_busy: + mov DINDIR, CUR_SCBID; + mov SCBPTR, SAVED_SCBPTR; + mvi SEQCTL, FASTMODE; + +start_scb: + /* + * Place us on the waiting list in case our selection + * doesn't win during bus arbitration. + */ + mov SCB_NEXT,WAITING_SCBH; + mov WAITING_SCBH, SCBPTR; +start_waiting: + /* + * Pull the first entry off of the waiting SCB list + * We don't have to "test_busy" because only transactions that + * have passed that test can be in the WAITING_SCB list. + */ + mov SCBPTR, WAITING_SCBH; + call start_selection; + jmp poll_for_work; + +start_selection: +.if ( TWIN_CHANNEL ) + and SINDEX,~SELBUSB,SBLKCTL;/* Clear the channel select bit */ + and A,SELBUSB,SCB_TCL; /* Get new channel bit */ + or SINDEX,A; + mov SBLKCTL,SINDEX; /* select channel */ +.endif +initialize_scsiid: + and A, TID, SCB_TCL; /* Get target ID */ + and SCSIID, OID; /* Clear old target */ + or SCSIID, A; + mvi SCSISEQ, ENSELO|ENAUTOATNO|ENRSELI|ENAUTOATNP ret; +/* + * Reselection has been initiated by a target. Make a note that we've been + * reselected, but haven't seen an IDENTIFY message from the target yet. + */ +reselect: + clr MSG_LEN; /* Don't have anything in the mesg buffer */ + mvi CLRSINT0, CLRSELDI; + /* XXX test for and handle ONE BIT condition */ + and SAVED_TCL, SELID_MASK, SELID; + or SEQ_FLAGS,RESELECTED; + jmp select2; + +/* + * After the selection, remove this SCB from the "waiting SCB" + * list. This is achieved by simply moving our "next" pointer into + * WAITING_SCBH. Our next pointer will be set to null the next time this + * SCB is used, so don't bother with it now. + */ +select: + /* Turn off the selection hardware */ + mvi SCSISEQ, ENRSELI|ENAUTOATNP; /* + * ATN on parity errors + * for "in" phases + */ + mvi CLRSINT0, CLRSELDO; + mov SCBPTR, WAITING_SCBH; + mov WAITING_SCBH,SCB_NEXT; + mov SAVED_TCL, SCB_TCL; +/* + * As soon as we get a successful selection, the target should go + * into the message out phase since we have ATN asserted. Prepare + * the message to send. + * + * Messages are stored in scratch RAM starting with a length byte + * followed by the message itself. + */ + +mk_identify: + and MSG_OUT,0x7,SCB_TCL; /* lun */ + and A,DISCENB,SCB_CONTROL; /* mask off disconnect privledge */ + or MSG_OUT,A; /* or in disconnect privledge */ + or MSG_OUT,MSG_IDENTIFYFLAG; + mvi MSG_LEN, 1; + +/* + * Send a tag message if TAG_ENB is set in the SCB control block. + * Use SCB_TAG (the position in the kernel's SCB array) as the tag value. + */ +mk_tag: + test SCB_CONTROL,TAG_ENB jz mk_message; + and MSG_OUT[1],TAG_ENB|SCB_TAG_TYPE,SCB_CONTROL; + mov MSG_OUT[2],SCB_TAG; + add MSG_LEN,2; /* update message length */ + +/* + * Interrupt the driver, and allow it to tweak the message buffer + * if it asks. + */ +mk_message: + test SCB_CONTROL,MK_MESSAGE jz select2; + mvi INTSTAT,AWAITING_MSG; + +select2: + mvi CLRSINT1,CLRBUSFREE; + or SIMODE1, ENBUSFREE; /* + * We aren't expecting a + * bus free, so interrupt + * the kernel driver if it + * happens. + */ +/* + * Initialize Ultra mode setting and clear the SCSI channel. + */ + or SXFRCTL0, CLRSTCNT|SPIOEN|CLRCHN; +.if ( ULTRA ) +ultra: + mvi SINDEX, ULTRA_ENB+1; + test SAVED_TCL, 0x80 jnz ultra_2; /* Target ID > 7 */ + dec SINDEX; +ultra_2: + mov FUNCTION1,SAVED_TCL; + mov A,FUNCTION1; + test SINDIR, A jz ndx_dtr; + or SXFRCTL0, FAST20; +.endif + +/* + * Initialize SCSIRATE with the appropriate value for this target. + * The SCSIRATE settings for each target are stored in an array + * based at TARG_SCRATCH. + */ +ndx_dtr: + shr A,4,SAVED_TCL; + test SBLKCTL,SELBUSB jz ndx_dtr_2; + or SAVED_TCL, SELBUSB; /* Add the channel bit while we're here */ + or A,0x08; /* Channel B entries add 8 */ +ndx_dtr_2: + add SINDEX,TARG_SCRATCH,A; + mov SCSIRATE,SINDIR; + + +/* + * Main loop for information transfer phases. If BSY is false, then + * we have a bus free condition, expected or not. Otherwise, wait + * for the target to assert REQ before checking MSG, C/D and I/O + * for the bus phase. + * + */ +ITloop: + test SSTAT1,REQINIT jz ITloop; + test SSTAT1, SCSIPERR jnz ITloop; + + and A,PHASE_MASK,SCSISIGI; + mov LASTPHASE,A; + mov SCSISIGO,A; + + cmp ALLZEROS,A je p_dataout; + cmp A,P_DATAIN je p_datain; + cmp A,P_COMMAND je p_command; + cmp A,P_MESGOUT je p_mesgout; + cmp A,P_STATUS je p_status; + cmp A,P_MESGIN je p_mesgin; + + mvi INTSTAT,BAD_PHASE; /* unknown phase - signal driver */ + jmp ITloop; /* Try reading the bus again. */ + +await_busfree: + and SIMODE1, ~ENBUSFREE; + call clear_target_state; + mov NONE, SCSIDATL; /* Ack the last byte */ + test SSTAT1,REQINIT|BUSFREE jz .; + test SSTAT1, BUSFREE jnz poll_for_work; + mvi INTSTAT, BAD_PHASE; + +clear_target_state: + clr DFCNTRL; + clr SCSIRATE; /* + * We don't know the target we will + * connect to, so default to narrow + * transfers to avoid parity problems. + */ + and SXFRCTL0, ~FAST20; + mvi LASTPHASE, P_BUSFREE; + /* clear target specific flags */ + and SEQ_FLAGS,~(RESELECTED|IDENTIFY_SEEN|TAGGED_SCB|DPHASE) ret; + +p_dataout: + mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET; + jmp data_phase_init; + +/* + * If we re-enter the data phase after going through another phase, the + * STCNT may have been cleared, so restore it from the residual field. + */ +data_phase_reinit: + mvi DINDEX, STCNT; + mvi SCB_RESID_DCNT call bcopy_3; + jmp data_phase_loop; + +p_datain: + mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|FIFORESET; +data_phase_init: + call assert; /* + * Ensure entering a data + * phase is okay - seen identify, etc. + */ + + test SEQ_FLAGS, DPHASE jnz data_phase_reinit; + + /* + * Initialize the DMA address and counter from the SCB. + * Also set SG_COUNT and SG_NEXT in memory since we cannot + * modify the values in the SCB itself until we see a + * save data pointers message. + */ + mvi DINDEX, HADDR; + mvi SCB_DATAPTR call bcopy_7; + + call set_stcnt_from_hcnt; + + mov SG_COUNT,SCB_SGCOUNT; + + mvi DINDEX, SG_NEXT; + mvi SCB_SGPTR call bcopy_4; + +data_phase_loop: +/* Guard against overruns */ + test SG_COUNT, 0xff jnz data_phase_inbounds; +/* + * Turn on 'Bit Bucket' mode, set the transfer count to + * 16meg and let the target run until it changes phase. + * When the transfer completes, notify the host that we + * had an overrun. + */ + or SXFRCTL1,BITBUCKET; + mvi HCNT[0], 0xff; + mvi HCNT[1], 0xff; + mvi HCNT[2], 0xff; + call set_stcnt_from_hcnt; + +data_phase_inbounds: +/* If we are the last SG block, ensure wideodd is off. */ + cmp SG_COUNT,0x01 jne data_phase_wideodd; + and DMAPARAMS, ~WIDEODD; +data_phase_wideodd: + mov DMAPARAMS call dma; + +/* Go tell the host about any overruns */ + test SXFRCTL1,BITBUCKET jnz data_phase_overrun; + +/* Exit if we had an underrun. dma clears SINDEX in this case. */ + test SINDEX,0xff jz data_phase_finish; + +/* + * Advance the scatter-gather pointers if needed + */ +sg_advance: + dec SG_COUNT; /* one less segment to go */ + + test SG_COUNT, 0xff jz data_phase_finish; /* Are we done? */ + + clr A; /* add sizeof(struct scatter) */ + add SG_NEXT[0],SG_SIZEOF; + adc SG_NEXT[1],A; + +/* + * Load a struct scatter and set up the data address and length. + * If the working value of the SG count is nonzero, then + * we need to load a new set of values. + * + * This, like all DMA's, assumes little-endian host data storage. + */ +sg_load: + mvi DINDEX, HADDR; + mvi SG_NEXT call bcopy_4; + + mvi HCNT[0],SG_SIZEOF; + clr HCNT[1]; + clr HCNT[2]; + + or DFCNTRL, HDMAEN|DIRECTION|FIFORESET; + + call dma_finish; + +/* + * Copy data from FIFO into SCB data pointer and data count. This assumes + * that the SG segments are of the form: + * + * struct ahc_dma_seg { + * u_int32_t addr; four bytes, little-endian order + * u_int32_t len; four bytes, little endian order + * }; + */ + mvi HADDR call dfdat_in_7; + +/* Load STCNT as well. It is a mirror of HCNT */ + call set_stcnt_from_hcnt; + test SSTAT1,PHASEMIS jz data_phase_loop; + +data_phase_finish: +/* + * After a DMA finishes, save the SG and STCNT residuals back into the SCB + * We use STCNT instead of HCNT, since it's a reflection of how many bytes + * were transferred on the SCSI (as opposed to the host) bus. + */ + mov SCB_RESID_DCNT[0],STCNT[0]; + mov SCB_RESID_DCNT[1],STCNT[1]; + mov SCB_RESID_DCNT[2],STCNT[2]; + mov SCB_RESID_SGCNT, SG_COUNT; + + /* We have seen a data phase */ + or SEQ_FLAGS, DPHASE; + + jmp ITloop; + +data_phase_overrun: +/* + * Turn off BITBUCKET mode and notify the host + */ + and SXFRCTL1, ~BITBUCKET; + mvi INTSTAT,DATA_OVERRUN; + jmp ITloop; + +/* + * Command phase. Set up the DMA registers and let 'er rip. + */ +p_command: + call assert; + +/* + * Load HADDR and HCNT. + */ + mvi DINDEX, HADDR; + mvi SCB_CMDPTR call bcopy_5; + clr HCNT[1]; + clr HCNT[2]; + + call set_stcnt_from_hcnt; + + mvi (SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET) call dma; + jmp ITloop; + +/* + * Status phase. Wait for the data byte to appear, then read it + * and store it into the SCB. + */ +p_status: + call assert; + + mov SCB_TARGET_STATUS, SCSIDATL; + jmp ITloop; + +/* + * Message out phase. If there is not an active message, but the target + * took us into this phase anyway, build a no-op message and send it. + */ +p_mesgout: + test MSG_LEN, 0xff jnz p_mesgout_start; + mvi MSG_NOOP call mk_mesg; /* build NOP message */ +p_mesgout_start: +/* + * Set up automatic PIO transfer from MSG_OUT. Bit 3 in + * SXFRCTL0 (SPIOEN) is already on. + */ + mvi SINDEX,MSG_OUT; + mov DINDEX,MSG_LEN; + +/* + * When target asks for a byte, drop ATN if it's the last one in + * the message. Otherwise, keep going until the message is exhausted. + * ATN must be dropped *at least* 90ns before we ack the last byte, so + * the code is aranged to execute two instructions before the byte is + * transferred to give a good margin of safety + * + * Keep an eye out for a phase change, in case the target issues + * a MESSAGE REJECT. + */ +p_mesgout_loop: + test SSTAT1, REQINIT jz p_mesgout_loop; + test SSTAT1, SCSIPERR jnz p_mesgout_loop; + and LASTPHASE, PHASE_MASK, SCSISIGI; + cmp LASTPHASE, P_MESGOUT jne p_mesgout_done; +p_mesgout_testretry: + test DINDEX,0xff jnz p_mesgout_dropatn; + or SCSISIGO,ATNO,LASTPHASE;/* turn on ATN for the retry */ + jmp p_mesgout_start; +/* + * If the next bus phase after ATN drops is a message out, it means + * that the target is requesting that the last message(s) be resent. + */ +p_mesgout_dropatn: + cmp DINDEX,1 jne p_mesgout_outb; /* last byte? */ + mvi CLRSINT1,CLRATNO; /* drop ATN */ +p_mesgout_outb: + dec DINDEX; + mov SCSIDATL,SINDIR; + jmp p_mesgout_loop; + +p_mesgout_done: + mvi CLRSINT1,CLRATNO; /* Be sure to turn ATNO off */ + clr MSG_LEN; /* no active msg */ + jmp ITloop; + +/* + * Message in phase. Bytes are read using Automatic PIO mode. + */ +p_mesgin: + mvi ACCUM call inb_first; /* read the 1st message byte */ + mov REJBYTE,A; /* save it for the driver */ + + test A,MSG_IDENTIFYFLAG jnz mesgin_identify; + cmp A,MSG_DISCONNECT je mesgin_disconnect; + cmp A,MSG_SAVEDATAPOINTER je mesgin_sdptrs; + cmp ALLZEROS,A je mesgin_complete; + cmp A,MSG_RESTOREPOINTERS je mesgin_rdptrs; + cmp A,MSG_EXTENDED je mesgin_extended; + cmp A,MSG_MESSAGE_REJECT je mesgin_reject; + cmp A,MSG_NOOP je mesgin_done; + +rej_mesgin: +/* + * We have no idea what this message in is, so we issue a message reject + * and hope for the best. In any case, rejection should be a rare + * occurrence - signal the driver when it happens. + */ + mvi INTSTAT,SEND_REJECT; /* let driver know */ + + mvi MSG_MESSAGE_REJECT call mk_mesg; + +mesgin_done: + mov NONE,SCSIDATL; /*dummy read from latch to ACK*/ + jmp ITloop; + + +mesgin_complete: +/* + * We got a "command complete" message, so put the SCB_TAG into the QOUTFIFO, + * and trigger a completion interrupt. Before doing so, check to see if there + * is a residual or the status byte is something other than NO_ERROR (0). In + * either of these conditions, we upload the SCB back to the host so it can + * process this information. In the case of a non zero status byte, we + * additionally interrupt the kernel driver synchronously, allowing it to + * decide if sense should be retrieved. If the kernel driver wishes to request + * sense, it will fill the kernel SCB with a request sense command and set + * RETURN_1 to SEND_SENSE. If RETURN_1 is set to SEND_SENSE we redownload + * the SCB, and process it as the next command by adding it to the waiting list. + * If the kernel driver does not wish to request sense, it need only clear + * RETURN_1, and the command is allowed to complete normally. We don't bother + * to post to the QOUTFIFO in the error cases since it would require extra + * work in the kernel driver to ensure that the entry was removed before the + * command complete code tried processing it. + */ + +/* + * First check for residuals + */ + test SCB_RESID_SGCNT,0xff jnz upload_scb; + test SCB_TARGET_STATUS,0xff jz status_ok; /* Good Status? */ +upload_scb: + mvi DMAPARAMS, FIFORESET; + mov SCB_TAG call dma_scb; +check_status: + test SCB_TARGET_STATUS,0xff jz status_ok; /* Just a residual? */ + mvi INTSTAT,BAD_STATUS; /* let driver know */ + cmp RETURN_1, SEND_SENSE jne status_ok; + /* This SCB becomes the next to execute as it will retrieve sense */ + mov SCB_LINKED_NEXT, SCB_TAG; + jmp dma_next_scb; + +status_ok: +/* First, mark this target as free. */ + test SCB_CONTROL,TAG_ENB jnz complete; /* + * Tagged commands + * don't busy the + * target. + */ + mov SAVED_SCBPTR, SCBPTR; + mov SAVED_LINKPTR, SCB_LINKED_NEXT; + mov SCB_TCL call index_untagged_scb; + mov DINDIR, SAVED_LINKPTR; + mov SCBPTR, SAVED_SCBPTR; + +complete: + /* Post the SCB and issue an interrupt */ + mov QOUTFIFO,SCB_TAG; + mvi INTSTAT,CMDCMPLT; + test SCB_CONTROL, ABORT_SCB jz dma_next_scb; + mvi INTSTAT, ABORT_CMDCMPLT; + +dma_next_scb: + cmp SCB_LINKED_NEXT, SCB_LIST_NULL je add_to_free_list; +.if !( SCB_PAGING ) + /* Only DMA on top of ourselves if we are the SCB to download */ + mov A, SCB_LINKED_NEXT; + cmp SCB_TAG, A je dma_next_scb2; + call add_scb_to_free_list; + mov SCBPTR, A; + jmp add_to_waiting_list; +.endif +dma_next_scb2: + mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; + mov SCB_LINKED_NEXT call dma_scb; +add_to_waiting_list: + mov SCB_NEXT,WAITING_SCBH; + mov WAITING_SCBH, SCBPTR; + /* + * Prepare our selection hardware before the busfree so we have a + * high probability of winning arbitration. + */ + call start_selection; + jmp await_busfree; +add_to_free_list: + call add_scb_to_free_list; + jmp await_busfree; + +/* + * Is it an extended message? Copy the message to our message buffer and + * notify the host. The host will tell us whether to reject this message, + * respond to it with the message that the host placed in our message buffer, + * or simply to do nothing. + */ +mesgin_extended: + mvi MSGIN_EXT_LEN call inb_next; + mov A, MSGIN_EXT_LEN; +mesgin_extended_loop: + mov DINDEX call inb_next; + dec A; + cmp DINDEX, MSGIN_EXT_BYTES+3 jne mesgin_extended_loop_test; + dec DINDEX; /* dump by repeatedly filling the last byte */ +mesgin_extended_loop_test: + test A, 0xFF jnz mesgin_extended_loop; +mesgin_extended_intr: + mvi INTSTAT,EXTENDED_MSG; /* let driver know */ + cmp RETURN_1,SEND_REJ je rej_mesgin; + cmp RETURN_1,SEND_MSG jne mesgin_done; +/* The kernel has setup a message to be sent */ + or SCSISIGO,ATNO,LASTPHASE; /* turn on ATNO */ + jmp mesgin_done; + +/* + * Is it a disconnect message? Set a flag in the SCB to remind us + * and await the bus going free. + */ +mesgin_disconnect: + or SCB_CONTROL,DISCONNECTED; +.if ( SCB_PAGING ) + call add_scb_to_disc_list; +.endif + jmp await_busfree; + +/* + * Save data pointers message: + * Copying RAM values back to SCB, for Save Data Pointers message, but + * only if we've actually been into a data phase to change them. This + * protects against bogus data in scratch ram and the residual counts + * since they are only initialized when we go into data_in or data_out. + */ +mesgin_sdptrs: + test SEQ_FLAGS, DPHASE jz mesgin_done; + mov SCB_SGCOUNT,SG_COUNT; + + /* The SCB SGPTR becomes the next one we'll download */ + mvi DINDEX, SCB_SGPTR; + mvi SG_NEXT call bcopy_4; + + /* The SCB DATAPTR0 becomes the current SHADDR */ + mvi DINDEX, SCB_DATAPTR; + mvi SHADDR call bcopy_4; + +/* + * Use the residual number since STCNT is corrupted by any message transfer. + */ + mvi SCB_RESID_DCNT call bcopy_3; + + jmp mesgin_done; + +/* + * Restore pointers message? Data pointers are recopied from the + * SCB anytime we enter a data phase for the first time, so all + * we need to do is clear the DPHASE flag and let the data phase + * code do the rest. + */ +mesgin_rdptrs: + and SEQ_FLAGS, ~DPHASE; /* + * We'll reload them + * the next time through + * the dataphase. + */ + jmp mesgin_done; + +/* + * Identify message? For a reconnecting target, this tells us the lun + * that the reconnection is for - find the correct SCB and switch to it, + * clearing the "disconnected" bit so we don't "find" it by accident later. + */ +mesgin_identify: + test A,0x78 jnz rej_mesgin; /*!DiscPriv|!LUNTAR|!Reserved*/ + and A,0x07; /* lun in lower three bits */ + or SAVED_TCL,A; /* SAVED_TCL should be complete now */ + mov SAVED_TCL call index_untagged_scb; + mov ARG_1, SINDIR; +.if ( SCB_PAGING ) + cmp ARG_1,SCB_LIST_NULL jne use_findSCB; +.else + cmp ARG_1,SCB_LIST_NULL je snoop_tag; + /* Directly index the SCB */ + mov SCBPTR,ARG_1; + test SCB_CONTROL,DISCONNECTED jz not_found; + jmp setup_SCB; +.endif +/* + * Here we "snoop" the bus looking for a SIMPLE QUEUE TAG message. + * If we get one, we use the tag returned to find the proper + * SCB. With SCB paging, this requires using findSCB for both tagged + * and non-tagged transactions since the SCB may exist in any slot. + * If we're not using SCB paging, we can use the tag as the direct + * index to the SCB. + */ +snoop_tag: + mov NONE,SCSIDATL; /* ACK Identify MSG */ +snoop_tag_loop: + test SSTAT1,REQINIT jz snoop_tag_loop; + test SSTAT1, SCSIPERR jnz snoop_tag_loop; + and LASTPHASE, PHASE_MASK, SCSISIGI; + cmp LASTPHASE, P_MESGIN jne not_found; + cmp SCSIBUSL,MSG_SIMPLE_Q_TAG jne not_found; +get_tag: + or SEQ_FLAGS, TAGGED_SCB; + mvi ARG_1 call inb_next; /* tag value */ +/* + * See if the tag is in range. The tag is < SCBCOUNT if we add + * the complement of SCBCOUNT to the incomming tag and there is + * no carry. + */ + mov A,COMP_SCBCOUNT; + add SINDEX,A,ARG_1; + jc not_found; + +.if ! ( SCB_PAGING ) +index_by_tag: + mov SCBPTR,ARG_1; + mov A, SAVED_TCL; + cmp SCB_TCL,A jne not_found; + test SCB_CONTROL,TAG_ENB jz not_found; + test SCB_CONTROL,DISCONNECTED jz not_found; +.else +/* + * Ensure that the SCB the tag points to is for an SCB transaction + * to the reconnecting target. + */ +use_findSCB: + mov ALLZEROS call findSCB; /* Have to search */ + cmp SINDEX, SCB_LIST_NULL je not_found; +.endif +setup_SCB: + and SCB_CONTROL,~DISCONNECTED; + or SEQ_FLAGS,IDENTIFY_SEEN; /* make note of IDENTIFY */ + jmp mesgin_done; + +not_found: + mvi INTSTAT, NO_MATCH; + mvi MSG_BUS_DEV_RESET call mk_mesg; + jmp mesgin_done; + +/* + * Message reject? Let the kernel driver handle this. If we have an + * outstanding WDTR or SDTR negotiation, assume that it's a response from + * the target selecting 8bit or asynchronous transfer, otherwise just ignore + * it since we have no clue what it pertains to. + */ +mesgin_reject: + mvi INTSTAT, REJECT_MSG; + jmp mesgin_done; + +/* + * [ ADD MORE MESSAGE HANDLING HERE ] + */ + +/* + * Locking the driver out, build a one-byte message passed in SINDEX + * if there is no active message already. SINDEX is returned intact. + */ +mk_mesg: + mvi SEQCTL, PAUSEDIS|FASTMODE; + test MSG_LEN,0xff jz mk_mesg1; /* Should always succeed */ + + /* + * Hmmm. For some reason the mesg buffer is in use. + * Tell the driver. It should look at SINDEX to find + * out what we wanted to use the buffer for and resolve + * the conflict. + */ + mvi SEQCTL,FASTMODE; + mvi INTSTAT,MSG_BUFFER_BUSY; + +mk_mesg1: + or SCSISIGO,ATNO,LASTPHASE;/* turn on ATNO */ + mvi MSG_LEN,1; /* length = 1 */ + mov MSG_OUT,SINDEX; /* 1-byte message */ + mvi SEQCTL,FASTMODE ret; + +/* + * Functions to read data in Automatic PIO mode. + * + * According to Adaptec's documentation, an ACK is not sent on input from + * the target until SCSIDATL is read from. So we wait until SCSIDATL is + * latched (the usual way), then read the data byte directly off the bus + * using SCSIBUSL. When we have pulled the ATN line, or we just want to + * acknowledge the byte, then we do a dummy read from SCISDATL. The SCSI + * spec guarantees that the target will hold the data byte on the bus until + * we send our ACK. + * + * The assumption here is that these are called in a particular sequence, + * and that REQ is already set when inb_first is called. inb_{first,next} + * use the same calling convention as inb. + */ + +inb_next: + mov NONE,SCSIDATL; /*dummy read from latch to ACK*/ +inb_next_wait: + /* + * If there is a parity error, wait for the kernel to + * see the interrupt and prepare our message response + * before continuing. + */ + test SSTAT1, REQINIT jz inb_next_wait; + test SSTAT1, SCSIPERR jnz inb_next_wait; + and LASTPHASE, PHASE_MASK, SCSISIGI; + cmp LASTPHASE, P_MESGIN jne mesgin_phasemis; +inb_first: + mov DINDEX,SINDEX; + mov DINDIR,SCSIBUSL ret; /*read byte directly from bus*/ +inb_last: + mov NONE,SCSIDATL ret; /*dummy read from latch to ACK*/ + +mesgin_phasemis: +/* + * We expected to receive another byte, but the target changed phase + */ + mvi INTSTAT, MSGIN_PHASEMIS; + jmp ITloop; + +/* + * DMA data transfer. HADDR and HCNT must be loaded first, and + * SINDEX should contain the value to load DFCNTRL with - 0x3d for + * host->scsi, or 0x39 for scsi->host. The SCSI channel is cleared + * during initialization. + */ +dma: + mov DFCNTRL,SINDEX; +dma_loop: + test SSTAT0,DMADONE jnz dma_dmadone; + test SSTAT1,PHASEMIS jz dma_loop; /* ie. underrun */ +dma_phasemis: + test SSTAT0,SDONE jnz dma_checkfifo; + mov SINDEX,ALLZEROS; /* Notify caller of phasemiss */ + +/* + * We will be "done" DMAing when the transfer count goes to zero, or + * the target changes the phase (in light of this, it makes sense that + * the DMA circuitry doesn't ACK when PHASEMIS is active). If we are + * doing a SCSI->Host transfer, the data FIFO should be flushed auto- + * magically on STCNT=0 or a phase change, so just wait for FIFO empty + * status. + */ +dma_checkfifo: + test DFCNTRL,DIRECTION jnz dma_fifoempty; +dma_fifoflush: + test DFSTATUS,FIFOEMP jz dma_fifoflush; + +dma_fifoempty: + /* Don't clobber an inprogress host data transfer */ + test DFSTATUS, MREQPEND jnz dma_fifoempty; +/* + * Now shut the DMA enables off and make sure that the DMA enables are + * actually off first lest we get an ILLSADDR. + */ +dma_dmadone: + and DFCNTRL, ~(SCSIEN|SDMAEN|HDMAEN); +dma_halt: + test DFCNTRL, (SCSIEN|SDMAEN|HDMAEN) jnz dma_halt; +return: + ret; + +/* + * Assert that if we've been reselected, then we've seen an IDENTIFY + * message. + */ +assert: + test SEQ_FLAGS,RESELECTED jz return; /* reselected? */ + test SEQ_FLAGS,IDENTIFY_SEEN jnz return; /* seen IDENTIFY? */ + + mvi INTSTAT,NO_IDENT ret; /* no - tell the kernel */ + +.if ( SCB_PAGING ) +/* + * Locate a disconnected SCB either by SAVED_TCL (ARG_1 is SCB_LIST_NULL) + * or by the SCBIDn ARG_1. The search begins at the SCB index passed in + * via SINDEX. If the SCB cannot be found, SINDEX will be SCB_LIST_NULL, + * otherwise, SCBPTR is set to the proper SCB. + */ +findSCB: + mov SCBPTR,SINDEX; /* switch to next SCB */ + mov A, ARG_1; /* Tag passed in ARG_1 */ + cmp SCB_TAG,A jne findSCB_loop; + test SCB_CONTROL,DISCONNECTED jnz foundSCB;/*should be disconnected*/ +findSCB_loop: + inc SINDEX; + mov A,SCBCOUNT; + cmp SINDEX,A jne findSCB; +/* + * We didn't find it. If we're paging, pull an SCB and DMA down the + * one we want. If we aren't paging or the SCB we dma down has the + * abort flag set, return not found. + */ + mov ALLZEROS call get_free_or_disc_scb; + mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; + mov ARG_1 call dma_scb; + test SCB_RESID_SGCNT, 0xff jz . + 2; + or SCB_CONTROL, MUST_DMAUP_SCB; + test SCB_CONTROL, ABORT_SCB jz return; +find_error: + mvi SINDEX, SCB_LIST_NULL ret; +foundSCB: + test SCB_CONTROL, ABORT_SCB jnz find_error; +rem_scb_from_disc_list: +/* Remove this SCB from the disconnection list */ + cmp SCB_NEXT,SCB_LIST_NULL je unlink_prev; + mov SAVED_LINKPTR, SCB_PREV; + mov SCBPTR, SCB_NEXT; + mov SCB_PREV, SAVED_LINKPTR; + mov SCBPTR, SINDEX; +unlink_prev: + cmp SCB_PREV,SCB_LIST_NULL je rHead;/* At the head of the list */ + mov SAVED_LINKPTR, SCB_NEXT; + mov SCBPTR, SCB_PREV; + mov SCB_NEXT, SAVED_LINKPTR; + mov SCBPTR, SINDEX ret; +rHead: + mov DISCONNECTED_SCBH,SCB_NEXT ret; +.else + ret; +.endif + +set_stcnt_from_hcnt: + mov STCNT[0], HCNT[0]; + mov STCNT[1], HCNT[1]; + mov STCNT[2], HCNT[2] ret; + +bcopy_7: + mov DINDIR, SINDIR; + mov DINDIR, SINDIR; +bcopy_5: + mov DINDIR, SINDIR; +bcopy_4: + mov DINDIR, SINDIR; +bcopy_3: + mov DINDIR, SINDIR; + mov DINDIR, SINDIR; + mov DINDIR, SINDIR ret; + +dma_scb: + /* + * SCB index is in SINDEX. Determine the physical address in + * the host where this SCB is located and load HADDR with it. + */ + shr DINDEX, 3, SINDEX; + shl A, 5, SINDEX; + add HADDR[0], A, HSCB_ADDR[0]; + mov A, DINDEX; + adc HADDR[1], A, HSCB_ADDR[1]; + clr A; + adc HADDR[2], A, HSCB_ADDR[2]; + adc HADDR[3], A, HSCB_ADDR[3]; + /* Setup Count */ + mvi HCNT[0], 28; + clr HCNT[1]; + clr HCNT[2]; + mov DFCNTRL, DMAPARAMS; + test DMAPARAMS, DIRECTION jnz dma_scb_fromhost; + /* Fill it with the SCB data */ +copy_scb_tofifo: + mvi SINDEX, SCB_CONTROL; + add A, 28, SINDEX; +copy_scb_tofifo_loop: + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + mov DFDAT,SINDIR; + cmp SINDEX, A jne copy_scb_tofifo_loop; + or DFCNTRL, HDMAEN|FIFOFLUSH; +dma_scb_fromhost: + call dma_finish; + /* If we were putting the SCB, we are done */ + test DMAPARAMS, DIRECTION jz return; + mvi SCB_CONTROL call dfdat_in_7; + call dfdat_in_7_continued; + call dfdat_in_7_continued; + jmp dfdat_in_7_continued; +dfdat_in_7: + mov DINDEX,SINDEX; +dfdat_in_7_continued: + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT ret; + +/* + * Wait for DMA from host memory to data FIFO to complete, then disable + * DMA and wait for it to acknowledge that it's off. + */ +dma_finish: + test DFSTATUS,HDONE jz dma_finish; + /* Turn off DMA */ + and DFCNTRL, ~HDMAEN; + test DFCNTRL, HDMAEN jnz .; + ret; + +index_untagged_scb: + mov DINDEX, SINDEX; + shr DINDEX, 4; + and DINDEX, 0x03; /* Bottom two bits of tid */ + add DINDEX, SCB_BUSYTARGETS; + shr A, 6, SINDEX; /* Target ID divided by 4 */ + test SINDEX, SELBUSB jz index_untagged_scb2; + add A, 2; /* Add 2 positions */ +index_untagged_scb2: + mov SCBPTR, A; /* + * Select the SCB with this + * target's information. + */ + mov SINDEX, DINDEX ret; + +add_scb_to_free_list: + mov SCB_NEXT, FREE_SCBH; + mvi SCB_TAG, SCB_LIST_NULL; + mov FREE_SCBH, SCBPTR ret; + +.if ( SCB_PAGING ) +get_free_or_disc_scb: + cmp FREE_SCBH, SCB_LIST_NULL jne dequeue_free_scb; + cmp DISCONNECTED_SCBH, SCB_LIST_NULL jne dequeue_disc_scb; +return_error: + mvi SINDEX, SCB_LIST_NULL ret; +dequeue_disc_scb: + mov SCBPTR, DISCONNECTED_SCBH; +/* + * If we have a residual, then we are in the middle of some I/O + * and we have to send this SCB back up to the kernel so that the + * saved data pointers and residual information isn't lost. + */ + test SCB_CONTROL, MUST_DMAUP_SCB jz . + 3; + and SCB_CONTROL, ~MUST_DMAUP_SCB; + jmp dma_up_scb; + test SCB_RESID_SGCNT,0xff jnz dma_up_scb; + cmp SCB_LINKED_NEXT, SCB_LIST_NULL je unlink_disc_scb; +dma_up_scb: + mvi DMAPARAMS, FIFORESET; + mov SCB_TAG call dma_scb; +unlink_disc_scb: + /* jmp instead of call since we want to return anyway */ + mov SCBPTR jmp rem_scb_from_disc_list; +dequeue_free_scb: + mov SCBPTR, FREE_SCBH; + mov FREE_SCBH, SCB_NEXT ret; + +add_scb_to_disc_list: +/* + * Link this SCB into the DISCONNECTED list. This list holds the + * candidates for paging out an SCB if one is needed for a new command. + * Modifying the disconnected list is a critical(pause dissabled) section. + */ + mvi SCB_PREV, SCB_LIST_NULL; + mov SCB_NEXT, DISCONNECTED_SCBH; + mov DISCONNECTED_SCBH, SCBPTR; + cmp SCB_NEXT,SCB_LIST_NULL je return; + mov SCBPTR,SCB_NEXT; + mov SCB_PREV,DISCONNECTED_SCBH; + mov SCBPTR,DISCONNECTED_SCBH ret; +.endif diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx/scsi_message.h linux/drivers/scsi/aic7xxx/scsi_message.h --- v2.0.30/linux/drivers/scsi/aic7xxx/scsi_message.h Wed Dec 31 16:00:00 1969 +++ linux/drivers/scsi/aic7xxx/scsi_message.h Thu Jul 31 12:37:17 1997 @@ -0,0 +1,41 @@ +/* + * SCSI messages definitions. + */ + +/* Messages (1 byte) */ /* I/T (M)andatory or (O)ptional */ +#define MSG_CMDCOMPLETE 0x00 /* M/M */ +#define MSG_EXTENDED 0x01 /* O/O */ +#define MSG_SAVEDATAPOINTER 0x02 /* O/O */ +#define MSG_RESTOREPOINTERS 0x03 /* O/O */ +#define MSG_DISCONNECT 0x04 /* O/O */ +#define MSG_INITIATOR_DET_ERR 0x05 /* M/M */ +#define MSG_ABORT 0x06 /* O/M */ +#define MSG_MESSAGE_REJECT 0x07 /* M/M */ +#define MSG_NOOP 0x08 /* M/M */ +#define MSG_PARITY_ERROR 0x09 /* M/M */ +#define MSG_LINK_CMD_COMPLETE 0x0a /* O/O */ +#define MSG_LINK_CMD_COMPLETEF 0x0b /* O/O */ +#define MSG_BUS_DEV_RESET 0x0c /* O/M */ +#define MSG_ABORT_TAG 0x0d /* O/O */ +#define MSG_CLEAR_QUEUE 0x0e /* O/O */ +#define MSG_INIT_RECOVERY 0x0f /* O/O */ +#define MSG_REL_RECOVERY 0x10 /* O/O */ +#define MSG_TERM_IO_PROC 0x11 /* O/O */ + +/* Messages (2 byte) */ +#define MSG_SIMPLE_Q_TAG 0x20 /* O/O */ +#define MSG_HEAD_OF_Q_TAG 0x21 /* O/O */ +#define MSG_ORDERED_Q_TAG 0x22 /* O/O */ +#define MSG_IGN_WIDE_RESIDUE 0x23 /* O/O */ + +/* Identify message */ /* M/M */ +#define MSG_IDENTIFYFLAG 0x80 +#define MSG_IDENTIFY(lun, disc) (((disc) ? 0xc0 : MSG_IDENTIFYFLAG) | (lun)) +#define MSG_ISIDENTIFY(m) ((m) & MSG_IDENTIFYFLAG) + +/* Extended messages (opcode and length) */ +#define MSG_EXT_SDTR 0x01 +#define MSG_EXT_SDTR_LEN 0x03 + +#define MSG_EXT_WDTR 0x03 +#define MSG_EXT_WDTR_LEN 0x02 diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx/sequencer.h linux/drivers/scsi/aic7xxx/sequencer.h --- v2.0.30/linux/drivers/scsi/aic7xxx/sequencer.h Wed Dec 31 16:00:00 1969 +++ linux/drivers/scsi/aic7xxx/sequencer.h Thu Jul 31 12:37:17 1997 @@ -0,0 +1,102 @@ +/* + * Instruction formats for the sequencer program downloaded to + * Aic7xxx SCSI host adapters + * + * Copyright (c) 1997 Justin T. 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 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: sequencer.h,v 1.2 1997/06/27 19:38:52 gibbs Exp $ + */ + +#if defined(__KERNEL__) +typedef unsigned char u_int8_t; +#endif + +struct ins_format1 { + u_int8_t immediate; + u_int8_t source; + u_int8_t destination; + u_int8_t opcode_ret; +}; + +struct ins_format2 { + u_int8_t shift_control; + u_int8_t source; + u_int8_t destination; + u_int8_t opcode_ret; +#define RETURN_BIT 0x01 +}; + +struct ins_format3 { + u_int8_t immediate; + u_int8_t source; + u_int8_t address; + u_int8_t opcode_addr; +#define ADDR_HIGH_BIT 0x01 +}; + +struct instruction { + union { + struct ins_format1 format1; + struct ins_format2 format2; + struct ins_format3 format3; + u_int8_t bytes[4]; + } format; + u_int srcline; + struct symbol *patch_label; + struct { + struct instruction *stqe_next; /* next element */ + } links; +}; + +#define AIC_OP_OR 0x0 +#define AIC_OP_AND 0x1 +#define AIC_OP_XOR 0x2 +#define AIC_OP_ADD 0x3 +#define AIC_OP_ADC 0x4 +#define AIC_OP_ROL 0x5 + +#define AIC_OP_JMP 0x8 +#define AIC_OP_JC 0x9 +#define AIC_OP_JNC 0xa +#define AIC_OP_CALL 0xb +#define AIC_OP_JNE 0xc +#define AIC_OP_JNZ 0xd +#define AIC_OP_JE 0xe +#define AIC_OP_JZ 0xf + +/* Pseudo Ops */ +#define AIC_OP_SHL 0x10 +#define AIC_OP_SHR 0x20 +#define AIC_OP_ROR 0x30 diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx.c linux/drivers/scsi/aic7xxx.c --- v2.0.30/linux/drivers/scsi/aic7xxx.c Tue Apr 8 08:47:46 1997 +++ linux/drivers/scsi/aic7xxx.c Thu Jul 31 12:37:17 1997 @@ -1,5 +1,3 @@ -#define EXPERIMENTAL_FLAGS 0 - /*+M************************************************************************* * Adaptec AIC7xxx device driver for Linux. * @@ -29,21 +27,73 @@ * the Adaptec AIC-7770 Data Book, the ANSI SCSI specification, the * ANSI SCSI-2 specification (draft 10c), ... * - * ---------------------------------------------------------------- - * Modified to include support for wide and twin bus adapters, - * DMAing of SCBs, tagged queueing, IRQ sharing, bug fixes, + * -------------------------------------------------------------------------- + * + * 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 are based on the FreeBSD driver by Justin - * T. Gibbs. + * 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 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,no_reset + * 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, 07/07/96 + * Daniel M. Eischen, deischen@iworks.InterWorks.org, 1/23/97 * - * $Id: aic7xxx.c,v 4.0 1996/10/13 08:23:42 deang Exp $ + * $Id: aic7xxx.c,v 4.1 1997/06/12 08:23:42 deang Exp $ *-M*************************************************************************/ #ifdef MODULE @@ -67,7 +117,11 @@ #include "scsi.h" #include "hosts.h" #include "aic7xxx.h" + +#include "aic7xxx/sequencer.h" +#include "aic7xxx/scsi_message.h" #include "aic7xxx_reg.h" +#include "aic7xxx_seq.h" #include #include /* for kmalloc() */ @@ -81,14 +135,18 @@ struct proc_dir_entry proc_scsi_aic7xxx = { PROC_SCSI_AIC7XXX, 7, "aic7xxx", - S_IFDIR | S_IRUGO | S_IXUGO, 2 + S_IFDIR | S_IRUGO | S_IXUGO, 2, + 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; -#define AIC7XXX_C_VERSION "$Revision: 4.0 $" +#define AIC7XXX_C_VERSION "$Revision: 4.1 $" #define NUMBER(arr) (sizeof(arr) / sizeof(arr[0])) -#define MIN(a,b) ((a < b) ? a : b) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) #define ALL_TARGETS -1 +#define ALL_CHANNELS '\0' +#define ALL_LUNS -1 #ifndef TRUE # define TRUE 1 #endif @@ -107,11 +165,8 @@ * support because all PCI dependent code is bracketed with * "#ifdef CONFIG_PCI ... #endif CONFIG_PCI". * - * o Twin bus support - this has been tested and does work. - * - * o DMAing of SCBs - thanks to Kai Makisara, this now works. - * This define is now taken out and DMAing of SCBs is always - * performed (8/12/95 - DE). + * o Twin bus support - this has been tested and does work. It is + * not an option anymore. * * o Tagged queueing - this driver is capable of tagged queueing * but I am unsure as to how well the higher level driver implements @@ -140,16 +195,16 @@ * LUN using its own heuristic based on the number of available * SCBs. * - * o 3985 support - The 3985 adapter is much like the 3940, but - * has three 7870 controllers as opposed to two for the 3940. - * It will get probed and recognized as three different adapters, - * but all three controllers can share the same external bank of - * 255 SCBs. If you enable AIC7XXX_SHARE_SCBS, then the driver - * will attempt to share the common bank of SCBs between the three - * controllers of the 3985. This is experimental and hasn't - * been tested. By default, we do not share the bank of SCBs, - * and force the controllers to use their own internal bank of - * 16 SCBs. Please let us know if sharing the SCB array works. + * o 3985 support - The 3985 adapter is much like the 3940, but has + * three 7870 controllers as opposed to two for the 3940. It will + * be probed and recognized as three different adapters, but all + * three controllers can share the same external bank of 255 SCBs. + * If you enable AIC7XXX_USE_EXT_SCBRAM, then the driver will attempt + * to use and share the common bank of SCBs between the three + * controllers of the 3985. This is experimental and hasn't been + * been tested. By default, we do not use external SCB RAM, and + * force the controllers to use their own internal bank of 16 SCBs. + * Please let us know if using the external SCB array works. * * o SCB paging support - SCB paging is enabled by defining * AIC7XXX_PAGE_ENABLE. Support for this was taken from the @@ -162,43 +217,54 @@ * Note that sharing of IRQs is not an option any longer. Linux supports * it so we support it. * - * Daniel M. Eischen, deischen@iworks.InterWorks.org, 06/30/96 + * Daniel M. Eischen, deischen@iworks.InterWorks.org, 01/26/96 */ -/* Uncomment this for testing twin bus support. */ -#define AIC7XXX_TWIN_SUPPORT - /* Uncomment this for tagged queueing. */ -/* #define AIC7XXX_TAGGED_QUEUEING */ +#ifdef CONFIG_AIC7XXX_TAGGED_QUEUEING +#define AIC7XXX_TAGGED_QUEUEING +#endif /* * You can try raising me if tagged queueing is enabled, or lowering * me if you only have 4 SCBs. */ -/* #define AIC7XXX_CMDS_PER_LUN 8 */ +#ifdef CONFIG_AIC7XXX_CMDS_PER_LUN +#define AIC7XXX_CMDS_PER_LUN CONFIG_AIC7XXX_CMDS_PER_LUN +#endif /* Set this to the delay in seconds after SCSI bus reset. */ +#ifdef CONFIG_AIC7XXX_RESET_DELAY +#define AIC7XXX_RESET_DELAY CONFIG_AIC7XXX_RESET_DELAY +#else #define AIC7XXX_RESET_DELAY 15 +#endif /* - * Uncomment the following define for collection of SCSI transfer statistics - * for the /proc filesystem. + * Control collection of SCSI transfer statistics for the /proc filesystem. * * NOTE: Do NOT enable this when running on kernels version 1.2.x and below. * NOTE: This does affect performance since it has to maintain statistics. */ -/* #define AIC7XXX_PROC_STATS */ +#ifdef CONFIG_AIC7XXX_PROC_STATS +#define AIC7XXX_PROC_STATS +#endif /* - * Uncomment the following to enable SCB paging. + * Enable SCB paging. */ -/* #define AIC7XXX_PAGE_ENABLE */ +#ifdef CONFIG_AIC7XXX_PAGE_ENABLE +#define AIC7XXX_PAGE_ENABLE +#endif /* - * Uncomment the following to enable sharing of the external bank - * of 255 SCBs for the 3985. + * Uncomment the following to enable use of the external bank + * of 255 SCBs. For 3985 adapters, this will also enable sharing + * of the SCB array across all three controllers. */ -#define AIC7XXX_SHARE_SCBS +#ifdef CONFIG_AIC7XXX_USE_EXT_SCBRAM +#define AIC7XXX_USE_EXT_SCBRAM +#endif /* * For debugging the abort/reset code. @@ -211,6 +277,87 @@ #define AIC7XXX_DEBUG /* + * Set this for defining the number of tagged commands on a device + * by device, and controller by controller basis. The first set + * of tagged commands will be used for the first detected aic7xxx + * controller, the second set will be used for the second detected + * aic7xxx controller, and so on. These values will *only* be used + * for targets that are tagged queueing capable; these values will + * be ignored in all other cases. The tag_commands is an array of + * 16 to allow for wide and twin adapters. Twin adapters will use + * indexes 0-7 for channel 0, and indexes 8-15 for channel 1. + * + * *** Determining commands per LUN *** + * + * When AIC7XXX_CMDS_PER_LUN is not defined, the driver will use its + * own algorithm to determine the commands/LUN. If SCB paging is + * enabled, the commands/LUN is 8. When SCB paging is not enabled, + * then commands/LUN is 8 for adapters with 16 or more hardware SCBs + * and 4 commands/LUN for adapters with 3 or 4 SCBs. + * + */ +/* #define AIC7XXX_TAGGED_QUEUEING_BY_DEVICE */ + +#ifdef AIC7XXX_TAGGED_QUEUEING_BY_DEVICE +typedef struct +{ + char tag_commands[16]; /* Allow for wide/twin channel adapters. */ +} adapter_tag_info_t; + +/* + * Make a define that will tell the driver to use it's own algorithm + * for determining commands/LUN (see Determining commands per LUN + * above). + */ +#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 -1, the driver will + * not enable tagged queueing for that particular device. When positive + * (> 0) the values in the array are used for the queue_depth. Note + * that the maximum value for an entry is 127. + * + * In this example, the first line will enable tagged queueing for all + * the devices on the first probed aic7xxx adapter and tells the driver + * to use it's own algorithm for determining commands/LUN. + * + * 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. + */ +adapter_tag_info_t aic7xxx_tag_info[] = +{ + {DEFAULT_TAG_COMMANDS}, + {{4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, -1, 4, 4, 4}}, + {DEFAULT_TAG_COMMANDS}, + {{-1, 16, 4, -1, 16, 4, 4, 4, 127, 4, 4, 4, 4, 4, 4, 4}} +}; +#endif + +/* + * Don't define this unless you have problems with the driver + * interrupt handler. The old method would register the drivers + * interrupt handler as a "fast" type interrupt handler that would + * lock out other interrupts. Since this driver can spend a lot + * of time in the interrupt handler, this is _not_ a good idea. + * It also conflicts with some of the more common ethernet drivers + * that don't use fast interrupts. Currently, Linux does not allow + * IRQ sharing unless both drivers can agree on the type of interrupt + * handler. + */ +/* #define AIC7XXX_OLD_ISR_TYPE */ + + +/* * Controller type and options */ typedef enum { @@ -232,14 +379,15 @@ AIC_7882, /* PCI aic7882 on 3940 Ultra */ AIC_7883, /* PCI aic7883 on 3985 Ultra */ AIC_7884 /* PCI aic7884 on 294x Ultra Differential */ -} aha_type; +} aha_chip_type; typedef enum { AIC_777x, /* AIC-7770 based */ - AIC_785x, /* AIC-7850 based */ + AIC_785x, /* AIC-7850 based (3 SCBs)*/ + AIC_786x, /* AIC-7860 based (7850 ultra) */ AIC_787x, /* AIC-7870 based */ - AIC_788x /* AIC-7880 based */ -} aha_chip_type; + AIC_788x /* AIC-7880 based (ultra) */ +} aha_chip_class_type; typedef enum { AIC_SINGLE, /* Single Channel */ @@ -269,24 +417,24 @@ * Don't forget to change this when changing the types! */ static const char *board_names[] = { - "", /* AIC_NONE */ - "AIC-7770", /* AIC_7770 */ - "AHA-2740", /* AIC_7771 */ - "AHA-2840", /* AIC_284x */ - "AIC-7850", /* AIC_7850 */ - "AIC-7855", /* AIC_7855 */ - "AIC-7850 Ultra", /* AIC_7860 */ - "AHA-2940A Ultra", /* AIC_7861 */ - "AIC-7870", /* AIC_7870 */ - "AHA-2940", /* AIC_7871 */ - "AHA-3940", /* AIC_7872 */ - "AHA-3985", /* AIC_7873 */ - "AHA-2940 Differential", /* AIC_7874 */ - "AIC-7880 Ultra", /* AIC_7880 */ - "AHA-2940 Ultra", /* AIC_7881 */ - "AHA-3940 Ultra", /* AIC_7882 */ - "AHA-3985 Ultra", /* AIC_7883 */ - "AHA-2940 Ultra Differential" /* AIC_7884 */ + "AIC-7xxx Unknown", /* AIC_NONE */ + "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 */ }; /* @@ -310,12 +458,17 @@ */ #define DID_RETRY_COMMAND DID_ERROR +#define HSCSIID 0x07 +#define HWSCSIID 0x0F +#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) @@ -334,14 +487,6 @@ #define INTDEF 0x5C /* Interrupt Definition Register */ /* - * Some defines for the HCNTRL register. - */ -#define REQ_PAUSE IRQMS | INTEN | PAUSE -#define UNPAUSE_274X IRQMS | INTEN -#define UNPAUSE_284X INTEN -#define UNPAUSE_294X IRQMS | INTEN - -/* * AIC-78X0 PCI registers */ #define CLASS_PROGIF_REVID 0x08 @@ -377,7 +522,7 @@ * 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; +typedef enum {C46 = 6, C56_66 = 8} seeprom_chip_type; /* * @@ -417,15 +562,15 @@ /* * Host Adapter Control Bits */ -/* UNUSED 0x0001 */ +#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 (non-wide 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 IC initialization */ +#define CFRESETB 0x0040 /* reset SCSI bus at boot */ /* UNUSED 0xFF80 */ unsigned short adapter_control; /* word 17 */ @@ -448,35 +593,17 @@ unsigned short checksum; /* word 31 */ }; - -#define SCSI_RESET 0x040 - -/* - * Pause the sequencer and wait for it to actually stop - this - * is important since the sequencer can disable pausing for critical - * sections. - */ -#define PAUSE_SEQUENCER(p) \ - outb(p->pause, HCNTRL + p->base); \ - while ((inb(HCNTRL + p->base) & PAUSE) == 0) \ - ; \ - -/* - * Unpause the sequencer. Unremarkable, yet done often enough to - * warrant an easy way to do it. - */ -#define UNPAUSE_SEQUENCER(p) \ - outb(p->unpause, HCNTRL + p->base) - -/* - * Restart the sequencer program from address zero - */ -#define RESTART_SEQUENCER(p) \ - do { \ - outb(SEQRESET | FASTMODE, SEQCTL + p->base); \ - } while (inb(SEQADDR0 + p->base) != 0 && \ - inb(SEQADDR1 + p->base) != 0); \ - UNPAUSE_SEQUENCER(p); +#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 @@ -540,12 +667,6 @@ static int aic7xxx_spurious_count; /* - * The driver keeps up to four scb structures per card in memory. Only the - * first 25 bytes of the structure are valid for the hardware, the rest used - * for driver level bookkeeping. - */ - -/* * 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. @@ -558,20 +679,28 @@ /* * Maximum number of SG segments these cards can support. */ -#define MAX_SG 256 +#define AIC7XXX_MAX_SG 27 -struct aic7xxx_scb { +/* + * 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 char SG_list_pointer[4] __attribute__ ((packed)); +/* 4*/ unsigned int SG_list_pointer; /* 8*/ unsigned char residual_SG_segment_count; -/* 9*/ unsigned char residual_data_count[3] __attribute__ ((packed)); -/*12*/ unsigned char data_pointer[4] __attribute__ ((packed)); -/*16*/ unsigned int data_count __attribute__ ((packed)); /* must be 32 bits */ -/*20*/ unsigned char SCSI_cmd_pointer[4] __attribute__ ((packed)); +/* 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*/ u_char tag; /* Index into our kernel SCB array. * Also used as the tag for tagged I/O @@ -579,29 +708,47 @@ #define SCB_PIO_TRANSFER_SIZE 26 /* amount we need to upload/download * via PIO to initialize a transaction. */ -/*26*/ u_char next; /* Used to thread SCBs awaiting selection +/*26*/ unsigned char next; /* Used to thread SCBs awaiting selection * or disconnected down in the sequencer. */ - /*-----------------end of hardware supported fields----------------*/ - Scsi_Cmnd *cmd; /* Scsi_Cmnd for this scb */ - struct aic7xxx_scb *q_next; /* next scb in queue */ -#define SCB_FREE 0x00 -#define SCB_ACTIVE 0x01 -#define SCB_ABORTED 0x02 -#define SCB_DEVICE_RESET 0x04 -#define SCB_IMMED 0x08 -#define SCB_SENSE 0x10 -#define SCB_QUEUED_FOR_DONE 0x40 -#define SCB_PAGED_OUT 0x80 -#define SCB_WAITINGQ 0x100 -#define SCB_ASSIGNEDQ 0x200 -#define SCB_SENTORDEREDTAG 0x400 -#define SCB_IN_PROGRESS (SCB_ACTIVE | SCB_PAGED_OUT | \ - SCB_WAITINGQ | SCB_ASSIGNEDQ) - int state; /* current state of scb */ - unsigned int position; /* Position in scb array */ - struct hw_scatterlist sg_list[MAX_SG]; /* SG list in adapter format */ - unsigned char sense_cmd[6]; /* Allocate 6 characters for sense command */ +/*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 alligned on 32 byte + * boundaries so the sequencer can index + */ +}; + +typedef enum { + SCB_FREE = 0x0000, + SCB_ACTIVE = 0x0001, + SCB_ABORTED = 0x0002, + SCB_DEVICE_RESET = 0x0004, + SCB_SENSE = 0x0008, + SCB_TIMEDOUT = 0x0010, + SCB_QUEUED_FOR_DONE = 0x0020, + SCB_RECOVERY_SCB = 0x0040, + SCB_WAITINGQ = 0x0080, + SCB_ASSIGNEDQ = 0x0100, + SCB_SENTORDEREDTAG = 0x0200, + SCB_MSGOUT_SDTR = 0x0400, + SCB_MSGOUT_WDTR = 0x0800, + SCB_ABORT = 0x1000, + SCB_QUEUED_ABORT = 0x2000 +} scb_flag_type; + +struct aic7xxx_scb { + struct aic7xxx_hwscb *hscb; /* corresponding hardware scb */ + Scsi_Cmnd *cmd; /* Scsi_Cmnd for this scb */ + struct aic7xxx_scb *q_next; /* next scb in queue */ + scb_flag_type flags; /* current state of scb */ + struct hw_scatterlist *sg_list; /* SG list in adapter format */ + unsigned char sg_count; + unsigned char sense_cmd[6]; /* + * Allocate 6 characters for + * sense command. + */ }; /* @@ -626,20 +773,17 @@ generic_sense[] = { REQUEST_SENSE, 0, 0, 0, 255, 0 }; typedef struct { + struct aic7xxx_hwscb *hscbs; scb_queue_type free_scbs; /* * SCBs assigned to free slot on * card (no paging required) */ - int numscbs; /* current number of scbs */ - int activescbs; /* active scbs */ -} scb_usage_type; - -/* - * 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 + unsigned char numscbs; /* current number of scbs */ + unsigned char maxhscbs; /* hardware scbs */ + unsigned char maxscbs; /* max scbs including pageable scbs */ + struct aic7xxx_scb *scb_array[AIC7XXX_MAXSCB]; + unsigned int reserve[100]; +} scb_data_type; /* * Define a structure used for each host adapter, only one per IRQ. @@ -647,17 +791,26 @@ struct aic7xxx_host { struct Scsi_Host *host; /* pointer to scsi host */ int host_no; /* SCSI host number */ + int instance; /* aic7xxx instance number */ + int scsi_id; /* host adapter SCSI ID */ + int scsi_id_b; /* channel B for twin adapters */ + int irq; /* IRQ for this adapter */ int base; /* card base address */ - int maxhscbs; /* hardware SCBs */ - int maxscbs; /* max SCBs (including pageable) */ -#define A_SCANNED 0x0001 -#define B_SCANNED 0x0002 -#define EXTENDED_TRANSLATION 0x0004 -#define HAVE_SEEPROM 0x0008 -#define ULTRA_ENABLED 0x0010 -#define PAGE_ENABLED 0x0020 -#define IN_ISR 0x0040 -#define USE_DEFAULTS 0x0080 + unsigned int mbase; /* I/O memory address */ + volatile unsigned char *maddr; /* memory mapped address */ +#define A_SCANNED 0x0001 +#define B_SCANNED 0x0002 +#define EXTENDED_TRANSLATION 0x0004 +#define FLAGS_CHANNEL_B_PRIMARY 0x0008 +#define MULTI_CHANNEL 0x0010 +#define ULTRA_ENABLED 0x0020 +#define PAGE_ENABLED 0x0040 +#define USE_DEFAULTS 0x0080 +#define BIOS_ENABLED 0x0100 +#define IN_ISR 0x0200 +#define IN_TIMEOUT 0x0400 +#define SHARED_SCBDATA 0x0800 +#define HAVE_SEEPROM 0x1000 unsigned int flags; unsigned int isr_count; /* Interrupt count */ unsigned short needsdtr_copy; /* default config */ @@ -668,36 +821,22 @@ unsigned short wdtr_pending; unsigned short orderedtag; unsigned short discenable; /* Targets allowed to disconnect */ - aha_type type; /* card type */ - aha_chip_type chip_type; /* chip base type */ + aha_chip_type chip_type; /* card type */ + aha_chip_class_type chip_class; aha_bus_type bus_type; /* normal/twin/wide bus */ - char * mbase; /* I/O memory address */ - unsigned char chan_num; /* for 3940/3985, channel number */ + unsigned char chan_num; /* for 39xx, channel number */ unsigned char unpause; /* unpause value for HCNTRL */ unsigned char pause; /* pause value for HCNTRL */ unsigned char qcntmask; - struct seeprom_config seeprom; + unsigned char qfullcount; + unsigned char curqincnt; struct Scsi_Host *next; /* allow for multiple IRQs */ - struct aic7xxx_scb *scb_array[AIC7XXX_MAXSCB]; /* active commands */ - struct aic7xxx_scb *pagedout_ntscbs[16]; /* - * paged-out, non-tagged scbs - * indexed by target. - */ - scb_queue_type page_scbs; /* - * SCBs that will require paging - * before use (no assigned slot) - */ + unsigned char activescbs; /* active scbs */ scb_queue_type waiting_scbs; /* - * SCBs waiting to be paged and - * started. - */ - scb_queue_type assigned_scbs; /* - * SCBs that were waiting but have - * have now been assigned a slot - * by aic7xxx_free_scb + * SCBs waiting for space in + * the QINFIFO. */ - scb_usage_type scb_usage; - scb_usage_type *scb_link; + scb_data_type *scb_data; struct aic7xxx_cmd_queue { Scsi_Cmnd *head; @@ -709,6 +848,7 @@ #define BUS_DEVICE_RESET_PENDING 0x02 int flags; int commands_sent; + int active_cmds; } device_status[16]; #ifdef AIC7XXX_PROC_STATS /* @@ -734,34 +874,10 @@ #endif /* AIC7XXX_PROC_STATS */ }; -struct aic7xxx_host_config { - int irq; /* IRQ number */ - int mbase; /* memory base address*/ - int base; /* I/O base address*/ - int maxhscbs; /* hardware SCBs */ - int maxscbs; /* max SCBs (including pageable) */ - int unpause; /* unpause value for HCNTRL */ - int pause; /* pause value for HCNTRL */ - int scsi_id; /* host SCSI ID */ - int scsi_id_b; /* host SCSI ID B channel for twin cards */ - unsigned int flags; /* used the same as struct aic7xxx_host flags */ - int chan_num; /* for 3940/3985, channel number */ - unsigned char busrtime; /* bus release time */ - unsigned char bus_speed; /* bus speed */ - unsigned char qcntmask; - aha_type type; /* card type */ - aha_chip_type chip_type; /* chip base type */ - aha_bus_type bus_type; /* normal/twin/wide bus */ - aha_status_type bios; /* BIOS is enabled/disabled */ - aha_status_type parity; /* bus parity enabled/disabled */ - aha_status_type low_term; /* bus termination low byte */ - aha_status_type high_term; /* bus termination high byte (wide cards only) */ -}; - /* * Valid SCSIRATE values. (p. 3-17) - * Provides a mapping of transfer periods in ns to the proper value to - * stick in the scsiscfr reg to use that transfer rate. + * Provides a mapping of transfer periods in ns/4 to the proper value to + * stick in the SCSIRATE reg to use that transfer rate. */ static struct { short period; @@ -770,17 +886,17 @@ short rate; const char *english; } aic7xxx_syncrates[] = { - { 50, 0x100, "20.0" }, - { 62, 0x110, "16.0" }, - { 75, 0x120, "13.4" }, - { 100, 0x000, "10.0" }, - { 125, 0x010, "8.0" }, - { 150, 0x020, "6.67" }, - { 175, 0x030, "5.7" }, - { 200, 0x040, "5.0" }, - { 225, 0x050, "4.4" }, - { 250, 0x060, "4.0" }, - { 275, 0x070, "3.6" } + { 12, 0x100, "20.0" }, + { 15, 0x110, "16.0" }, + { 18, 0x120, "13.4" }, + { 25, 0x000, "10.0" }, + { 31, 0x010, "8.0" }, + { 37, 0x020, "6.67" }, + { 43, 0x030, "5.7" }, + { 50, 0x040, "5.0" }, + { 56, 0x050, "4.4" }, + { 62, 0x060, "4.0" }, + { 68, 0x070, "3.6" } }; static int num_aic7xxx_syncrates = @@ -789,166 +905,51 @@ #ifdef CONFIG_PCI static int number_of_3940s = 0; static int number_of_3985s = 0; -#ifdef AIC7XXX_SHARE_SCBS -static scb_usage_type *shared_3985_scbs = NULL; -#endif -#endif CONFIG_PCI +#endif /* CONFIG_PCI */ #ifdef AIC7XXX_DEBUG -static void -debug_config(struct aic7xxx_host_config *p) -{ - int scsi_conf; - unsigned char brelease; - unsigned char dfthresh; - - static int DFT[] = { 0, 50, 75, 100 }; - static int SST[] = { 256, 128, 64, 32 }; - static const char *BUSW[] = { "", "-TWIN", "-WIDE" }; - - scsi_conf = inb(SCSICONF + p->base); - - /* - * Scale the Data FIFO Threshhold and the Bus Release Time; they are - * stored in formats compatible for writing to sequencer registers. - */ - dfthresh = p->bus_speed >> 6; - - if (p->chip_type == AIC_777x) - { - brelease = p->busrtime >> 2; - } - else - { - brelease = p->busrtime; - } - if (brelease == 0) - { - brelease = 2; - } - - switch (p->type) - { - case AIC_7770: - case AIC_7771: - printk("%s%s AT EISA SLOT %d:\n", board_names[p->type], BUSW[p->bus_type], - p->base >> 12); - break; - - case AIC_284x: - printk("%s%s AT VLB SLOT %d:\n", board_names[p->type], BUSW[p->bus_type], - p->base >> 12); - break; - - case AIC_7850: - case AIC_7855: - case AIC_7860: - case AIC_7861: - case AIC_7870: - case AIC_7871: - case AIC_7872: - case AIC_7873: - case AIC_7874: - case AIC_7880: - case AIC_7881: - case AIC_7882: - case AIC_7883: - case AIC_7884: - printk("%s%s (PCI-bus), I/O 0x%x, Mem 0x%x:\n", board_names[p->type], - BUSW[p->bus_type], p->base, p->mbase); - break; - - default: - panic("aic7xxx: (debug_config) internal error.\n"); - } - - printk(" irq %d\n" - " bus release time %d bclks\n" - " data fifo threshold %d%%\n", - p->irq, - brelease, - DFT[dfthresh]); - - printk(" SCSI CHANNEL A:\n" - " scsi id %d\n" - " scsi selection timeout %d ms\n" - " scsi bus reset at power-on %sabled\n", - (p->bus_type & AIC_WIDE) ? (scsi_conf & 0x0f) : (scsi_conf & 0x07), - SST[(scsi_conf >> 3) & 0x03], - (scsi_conf & 0x40) ? "en" : "dis"); - - if ((p->chip_type == AIC_777x) && (p->parity == AIC_UNKNOWN)) - { - /* - * Set the parity for 7770 based cards. - */ - p->parity = (scsi_conf & 0x20) ? AIC_ENABLED : AIC_DISABLED; - } - if (p->parity != AIC_UNKNOWN) - { - printk(" scsi bus parity %sabled\n", - (p->parity == AIC_ENABLED) ? "en" : "dis"); - } - - if ((p->type == AIC_7770) || (p->type == AIC_7771)) - { - p->low_term = (scsi_conf & 0x80) ? AIC_ENABLED : AIC_DISABLED; - } - if (p->low_term != AIC_UNKNOWN) - { - printk(" scsi bus termination (low byte) %sabled\n", - (p->low_term == AIC_ENABLED) ? "en" : "dis"); - } - if ((p->bus_type == AIC_WIDE) && (p->high_term != AIC_UNKNOWN)) - { - printk(" scsi bus termination (high byte) %sabled\n", - (p->high_term == AIC_ENABLED) ? "en" : "dis"); - } -} - #if 0 static void debug_scb(struct aic7xxx_scb *scb) { - printk("control 0x%x, tcl 0x%x, sg_count %d, sg_ptr 0x%x, cmdp 0x%x, cmdlen %d\n", - scb->control, scb->target_channel_lun, scb->SG_segment_count, - (scb->SG_list_pointer[3] << 24) | (scb->SG_list_pointer[2] << 16) | - (scb->SG_list_pointer[1] << 8) | scb->SG_list_pointer[0], - (scb->SCSI_cmd_pointer[3] << 24) | (scb->SCSI_cmd_pointer[2] << 16) | - (scb->SCSI_cmd_pointer[1] << 8) | scb->SCSI_cmd_pointer[0], - scb->SCSI_cmd_length); - printk("reserved 0x%x, target status 0x%x, resid SG count %d, resid data count %d\n", - (scb->RESERVED[1] << 8) | scb->RESERVED[0], scb->target_status, - scb->residual_SG_segment_count, - ((scb->residual_data_count[2] << 16) | - (scb->residual_data_count[1] << 8) | - (scb->residual_data_count[0])); - printk("data ptr 0x%x, data count %d, next waiting %d\n", - (scb->data_pointer[3] << 24) | (scb->data_pointer[2] << 16) | - (scb->data_pointer[1] << 8) | scb->data_pointer[0], - scb->data_count, scb->next_waiting); - printk("next ptr 0x%lx, Scsi Cmnd 0x%lx, state 0x%x, position %d\n", - (unsigned long) scb->next, (unsigned long) scb->cmd, scb->state, - scb->position); + struct aic7xxx_hwscb *hscb = scb->hscb; + + printk("scb:%p control:0x%x tcl:0x%x cmdlen:%d cmdpointer:0x%lx\n", + scb, + hscb->control, + hscb->target_channel_lun, + hscb->SCSI_cmd_length, + hscb->SCSI_cmd_pointer ); + printk(" datlen:%d data:0x%lx segs:0x%x segp:0x%lx\n", + hscb->data_count, + hscb->data_pointer, + hscb->SG_segment_count, + hscb->SG_list_pointer); + printk(" sg_addr:%lx sg_len:%ld\n", + hscb->sg_list[0].address, + hscb->sg_list[0].length); } #endif #else -# define debug_config(x) # define debug_scb(x) #endif AIC7XXX_DEBUG -#define TCL_OF_SCB(x) (((x)->target_channel_lun >> 4) & 0xf), \ - (((x)->target_channel_lun >> 3) & 0x01), \ - ((x)->target_channel_lun & 0x07) +#define TCL_OF_SCB(scb) (((scb->hscb)->target_channel_lun >> 4) & 0xf), \ + (((scb->hscb)->target_channel_lun >> 3) & 0x01), \ + ((scb->hscb)->target_channel_lun & 0x07) -#define TARGET_INDEX(x) ((x)->target | ((x)->channel << 3)) +#define TC_OF_SCB(scb) (((scb->hscb)->target_channel_lun >> 4) & 0xf), \ + (((scb->hscb)->target_channel_lun >> 3) & 0x01) + +#define CHAN_TO_INT(chan) ((chan) == 'A' ? 0 : 1) + +#define TARGET_INDEX(cmd) ((cmd)->target | ((cmd)->channel << 3)) /* * XXX - these options apply unilaterally to _all_ 274x/284x/294x - * cards in the system. This should be fixed, but then, - * does anyone really have more than one in a machine? + * cards in the system. This should be fixed. */ static unsigned int aic7xxx_extended = 0; /* extended translation on? */ static unsigned int aic7xxx_no_reset = 0; /* no resetting of SCSI bus */ @@ -958,6 +959,53 @@ * 1 use level triggered */ static int aic7xxx_enable_ultra = 0; /* enable ultra SCSI speeds */ +static int aic7xxx_verbose = 0; /* verbose messages */ + + +/**************************************************************************** + * + * These functions are not used yet, but when we do memory mapped + * IO, we'll use them then. + * + ***************************************************************************/ +static inline unsigned char +aic_inb(struct aic7xxx_host *p, long port) +{ + if (p->maddr != NULL) + return (p->maddr[port]); + else + return (inb(p->base + port)); +} + +static inline void +aic_outb(struct aic7xxx_host *p, unsigned char val, long port) +{ + if (p->maddr != NULL) + p->maddr[port] = val; + else + outb(val, p->base + port); +} + +static inline void +aic_outsb(struct aic7xxx_host *p, long port, unsigned char *valp, size_t size) +{ + if (p->maddr != NULL) + { + __asm __volatile(" + cld; + 1: lodsb; + movb %%al,(%0); + loop 1b" : + : + "r" ((p)->maddr + (port)), + "S" ((valp)), "c" ((size)) : + "%esi", "%ecx", "%eax"); + } + else + { + outsb(p->base + port, valp, size); + } +} /*+F************************************************************************* * Function: @@ -982,6 +1030,7 @@ { "no_reset", &aic7xxx_no_reset }, { "irq_trigger", &aic7xxx_irq_trigger }, { "ultra", &aic7xxx_enable_ultra }, + { "verbose", &aic7xxx_verbose }, { NULL, NULL } }; @@ -1007,162 +1056,343 @@ /*+F************************************************************************* * Function: - * aic7xxx_loadseq + * pause_sequencer * * Description: - * Load the sequencer code into the controller memory. + * 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 -aic7xxx_loadseq(int base) +static inline void +pause_sequencer(struct aic7xxx_host *p) { - static unsigned char seqprog[] = { - /* - * Each sequencer instruction is 29 bits - * long (fill in the excess with zeroes) - * and has to be loaded from least -> most - * significant byte, so this table has the - * byte ordering reversed. - */ -# include "aic7xxx_seq.h" - }; - - /* - * When the AIC-7770 is paused (as on chip reset), the - * sequencer address can be altered and a sequencer - * program can be loaded by writing it, byte by byte, to - * the sequencer RAM port - the Adaptec documentation - * recommends using REP OUTSB to do this, hence the inline - * assembly. Since the address autoincrements as we load - * the program, reset it back to zero afterward. Disable - * sequencer RAM parity error detection while loading, and - * make sure the LOADRAM bit is enabled for loading. - */ - outb(PERRORDIS | SEQRESET | LOADRAM, SEQCTL + base); - - outsb(SEQRAM + base, seqprog, sizeof(seqprog)); - - /* - * WARNING! This is a magic sequence! After extensive - * experimentation, it seems that you MUST turn off the - * LOADRAM bit before you play with SEQADDR again, else - * you will end up with parity errors being flagged on - * your sequencer program. (You would also think that - * turning off LOADRAM and setting SEQRESET to reset the - * address to zero would work, but you need to do it twice - * for it to take effect on the address. Timing problem?) - */ - do { - /* - * Actually, reset it until - * the address shows up as - * zero just to be safe.. - */ - outb(SEQRESET | FASTMODE, SEQCTL + base); - } while ((inb(SEQADDR0 + base) != 0) && (inb(SEQADDR1 + base) != 0)); + outb(p->pause, p->base + HCNTRL); + while ((inb(p->base + HCNTRL) & PAUSE) == 0) + { + ; + } } /*+F************************************************************************* * Function: - * aic7xxx_delay + * unpause_sequencer * * Description: - * Delay for specified amount of time. + * Unpause the sequencer. Unremarkable, yet done often enough to + * warrant an easy way to do it. *-F*************************************************************************/ -static void -aic7xxx_delay(int seconds) +static inline void +unpause_sequencer(struct aic7xxx_host *p, int unpause_always) { - unsigned long i; - - i = jiffies + (seconds * HZ); /* compute time to stop */ - - while (jiffies < i) + if (unpause_always || + ((inb(p->base + INTSTAT) & (SCSIINT | SEQINT | BRKADRINT)) == 0)) { - ; /* Do nothing! */ + outb(p->unpause, p->base + HCNTRL); } } /*+F************************************************************************* * Function: - * rcs_version + * restart_sequencer * * Description: - * Return a string containing just the RCS version number from either - * an Id or Revision RCS clause. + * Restart the sequencer program from address zero. This assumes + * that the sequencer is already paused. *-F*************************************************************************/ -const char * -rcs_version(const char *version_info) +static inline void +restart_sequencer(struct aic7xxx_host *p) { - static char buf[10]; - char *bp, *ep; + /* Set the sequencer address to 0. */ + outb(0, p->base + SEQADDR0); + outb(0, p->base + SEQADDR1); - bp = NULL; - strcpy(buf, "????"); - if (!strncmp(version_info, "$Id: ", 5)) - { - if ((bp = strchr(version_info, ' ')) != NULL) - { - bp++; - if ((bp = strchr(bp, ' ')) != NULL) - { - bp++; - } - } - } - else + /* + * Reset and unpause the sequencer. The reset is suppose to + * start the sequencer running, but we do an unpause to make + * sure. + */ + outb(SEQRESET | FASTMODE, p->base + SEQCTL); + + unpause_sequencer(p, /*unpause_always*/ TRUE); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_next_patch + * + * Description: + * Find the next patch to download. + *-F*************************************************************************/ +static struct patch * +aic7xxx_next_patch(struct patch *cur_patch, int options, int instrptr) +{ + while (cur_patch != NULL) { - if (!strncmp(version_info, "$Revision: ", 11)) + if ((((cur_patch->options & options) != 0) && (cur_patch->negative == FALSE)) + || (((cur_patch->options & options) == 0) && (cur_patch->negative == TRUE)) + || (instrptr >= cur_patch->end)) { - if ((bp = strchr(version_info, ' ')) != NULL) + /* + * Either we want to keep this section of code, or we have consumed + * this patch. Skip to the next patch. + */ + cur_patch++; + if (cur_patch->options == 0) { - bp++; + /* Out of patches. */ + cur_patch = NULL; } } - } - - if (bp != NULL) - { - if ((ep = strchr(bp, ' ')) != NULL) + else { - register int len = ep - bp; - - strncpy(buf, bp, len); - buf[len] = '\0'; + /* Found an OK patch. */ + break; } } - - return buf; + return (cur_patch); } + /*+F************************************************************************* * Function: - * aic7xxx_info + * aic7xxx_download_instr * * Description: - * Return a string describing the driver. + * Find the next patch to download. *-F*************************************************************************/ -const char * -aic7xxx_info(struct Scsi_Host *notused) +static void +aic7xxx_download_instr(struct aic7xxx_host *p, int options, int instrptr) { - static char buffer[128]; + unsigned char opcode; + struct ins_format3 *instr; - strcpy(buffer, "Adaptec AHA274x/284x/294x (EISA/VLB/PCI-Fast SCSI) "); - strcat(buffer, rcs_version(AIC7XXX_C_VERSION)); - strcat(buffer, "/"); - strcat(buffer, rcs_version(AIC7XXX_H_VERSION)); - strcat(buffer, "/"); - strcat(buffer, rcs_version(AIC7XXX_SEQ_VER)); + instr = (struct ins_format3 *) &seqprog[instrptr * 4]; + /* Pull the opcode */ + opcode = instr->opcode_addr >> 1; + 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: + { + int address_offset; + struct ins_format3 new_instr; + unsigned int address; + struct patch *patch; + int i; + + address_offset = 0; + new_instr = *instr; /* Strucure copy */ + address = new_instr.address; + address |= (new_instr.opcode_addr & ADDR_HIGH_BIT) << 8; + for (i = 0; i < NUMBER(patches); i++) + { + patch = &patches[i]; + if ((((patch->options & options) == 0) && (patch->negative == FALSE)) || + (((patch->options & options) != 0) && (patch->negative == TRUE))) + { + if (address >= patch->end) + { + address_offset += patch->end - patch->begin; + } + } + } + address -= address_offset; + new_instr.address = address &0xFF; + new_instr.opcode_addr &= ~ADDR_HIGH_BIT; + new_instr.opcode_addr |= (address >> 8) & ADDR_HIGH_BIT; + outsb(p->base + SEQRAM, &new_instr.immediate, 4); + break; + } - return buffer; -} + case AIC_OP_OR: + case AIC_OP_AND: + case AIC_OP_XOR: + case AIC_OP_ADD: + case AIC_OP_ADC: + case AIC_OP_ROL: + outsb(p->base + SEQRAM, &instr->immediate, 4); + 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) +{ + int options; + struct patch *cur_patch; + int i; + int downloaded; + + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Downloading sequencer code..."); + } + options = 1; /* Code for all options. */ + downloaded = 0; + if ((p->flags & ULTRA_ENABLED) != 0) + options |= ULTRA; + if (p->bus_type == AIC_TWIN) + options |= TWIN_CHANNEL; + if (p->scb_data->maxscbs > p->scb_data->maxhscbs) + options |= SCB_PAGING; + + cur_patch = patches; + outb(PERRORDIS | LOADRAM, p->base + SEQCTL); + outb(0, p->base + SEQADDR0); + outb(0, p->base + SEQADDR1); + + for (i = 0; i < sizeof(seqprog) / 4; i++) + { + cur_patch = aic7xxx_next_patch(cur_patch, options, i); + if (cur_patch && (cur_patch->begin <= i) && (cur_patch->end > i)) + { + /* Skip this instruction for this configuration. */ + continue; + } + aic7xxx_download_instr(p, options, i); + downloaded++; + } + + outb(FASTMODE, p->base + SEQCTL); + outb(0, p->base + SEQADDR0); + outb(0, p->base + SEQADDR1); + + if (aic7xxx_verbose) + { + printk(" %d instructions downloaded\n", downloaded); + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_delay + * + * Description: + * Delay for specified amount of time. We use udelay because the timer + * interrupt is not guaranteed to be enabled. This will cause an + * infinite loop since jiffies (clock ticks) is not updated. + *-F*************************************************************************/ +static void +aic7xxx_delay(int seconds) +{ + int i; + + /* + * Call udelay() for 1 millisecond inside a loop for + * the requested amount of seconds. + */ + for (i=0; i < seconds*1000; i++) + { + udelay(1000); /* Delay for 1 millisecond. */ + } +} + +/*+F************************************************************************* + * Function: + * rcs_version + * + * Description: + * Return a string containing just the RCS version number from either + * an Id or Revision RCS clause. + *-F*************************************************************************/ +const char * +rcs_version(const char *version_info) +{ + static char buf[10]; + char *bp, *ep; + + bp = NULL; + strcpy(buf, "????"); + if (!strncmp(version_info, "$Id: ", 5)) + { + if ((bp = strchr(version_info, ' ')) != NULL) + { + bp++; + if ((bp = strchr(bp, ' ')) != NULL) + { + bp++; + } + } + } + else + { + if (!strncmp(version_info, "$Revision: ", 11)) + { + if ((bp = strchr(version_info, ' ')) != NULL) + { + bp++; + } + } + } + + if (bp != NULL) + { + if ((ep = strchr(bp, ' ')) != NULL) + { + register int len = ep - bp; + + strncpy(buf, bp, len); + buf[len] = '\0'; + } + } + + return buf; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_info + * + * Description: + * Return a string describing the driver. + *-F*************************************************************************/ +const char * +aic7xxx_info(struct Scsi_Host *notused) +{ + static char buffer[128]; + + strcpy(buffer, "Adaptec AHA274x/284x/294x (EISA/VLB/PCI-Fast SCSI) "); + strcat(buffer, rcs_version(AIC7XXX_C_VERSION)); + strcat(buffer, "/"); + strcat(buffer, rcs_version(AIC7XXX_H_VERSION)); +#if 0 + strcat(buffer, "/"); + strcat(buffer, rcs_version(AIC7XXX_SEQ_VER)); +#endif + + return buffer; +} /*+F************************************************************************* * Function: * aic7xxx_length * * Description: - * How much data should be transferred for this SCSI command? Stop - * at segment sg_last if it's a scatter-gather command so we can - * compute underflow easily. + * How much data should be transferred for this SCSI command? Assume + * all segments are to be transferred except for the last sg_last + * segments. This will allow us to compute underflow easily. To + * calculate the total length of the command, use sg_last = 0. To + * calculate the length of all but the last 2 SG segments, use + * sg_last = 2. *-F*************************************************************************/ static unsigned aic7xxx_length(Scsi_Cmnd *cmd, int sg_last) @@ -1176,7 +1406,7 @@ if (cmd->use_sg) { - for (i = length = 0; (i < cmd->use_sg) && (i < segments); i++) + for (i = length = 0; i < segments; i++) { length += sg[i].length; } @@ -1198,9 +1428,9 @@ *-F*************************************************************************/ static void aic7xxx_scsirate(struct aic7xxx_host *p, unsigned char *scsirate, - short period, unsigned char offset, int target, char channel) + unsigned char *period, unsigned char *offset, int target, char channel) { - int i; + int i = num_aic7xxx_syncrates; unsigned long ultra_enb_addr; unsigned char ultra_enb, sxfrctl0; @@ -1208,11 +1438,11 @@ * If the offset is 0, then the device is requesting asynchronous * transfers. */ - if (offset != 0) + if ((*period >= aic7xxx_syncrates[i].period) && *offset != 0) { for (i = 0; i < num_aic7xxx_syncrates; i++) { - if ((aic7xxx_syncrates[i].period - period) >= 0) + if (*period <= aic7xxx_syncrates[i].period) { /* * Watch out for Ultra speeds when ultra is not enabled and @@ -1228,99 +1458,57 @@ */ continue; } - *scsirate = (aic7xxx_syncrates[i].rate) | (offset & 0x0F); + *scsirate = (aic7xxx_syncrates[i].rate & 0xF0) | (*offset & 0x0F); + *period = aic7xxx_syncrates[i].period; - /* - * Ensure Ultra mode is set properly for this target. - */ - ultra_enb_addr = ULTRA_ENB; - if ((channel == 'B') || (target > 7)) + if (aic7xxx_verbose) { - ultra_enb_addr++; + printk("scsi%d: Target %d, channel %c, now synchronous at %sMHz, " + "offset %d.\n", p->host_no, target, channel, + aic7xxx_syncrates[i].english, *offset); } - ultra_enb = inb(p->base + ultra_enb_addr); - sxfrctl0 = inb(p->base + SXFRCTL0); - if (aic7xxx_syncrates[i].rate & ULTRA_SXFR) - { - ultra_enb |= 0x01 << (target & 0x07); - sxfrctl0 |= ULTRAEN; - } - else - { - ultra_enb &= ~(0x01 << (target & 0x07)); - sxfrctl0 &= ~ULTRAEN; - } - outb(ultra_enb, p->base + ultra_enb_addr); - outb(sxfrctl0, p->base + SXFRCTL0); - - printk("scsi%d: Target %d, channel %c, now synchronous at %sMHz, " - "offset %d.\n", p->host_no, target, channel, - aic7xxx_syncrates[i].english, offset); - return; + break; } } } - /* - * Default to asynchronous transfer - */ - *scsirate = 0; - printk("scsi%d: Target %d, channel %c, using asynchronous transfers.\n", - p->host_no, target, channel); -} - -/*+F************************************************************************* - * Function: - * aic7xxx_putscb - * - * Description: - * Transfer a SCB to the controller. - *-F*************************************************************************/ -static inline void -aic7xxx_putscb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) -{ - int base = p->base; - - outb(SCBAUTO, SCBCNT + base); + if (i >= num_aic7xxx_syncrates) + { + /* + * Use asynchronous transfers. + */ + *scsirate = 0; + *period = 0; + *offset = 0; + if (aic7xxx_verbose) + { + printk("scsi%d: Target %d, channel %c, using asynchronous transfers.\n", + p->host_no, target, channel); + } + } /* - * By turning on the SCB auto increment, any reference - * to the SCB I/O space postincrements the SCB address - * we're looking at. So turn this on and dump the relevant - * portion of the SCB to the card. - * - * We can do 16bit transfers on all but 284x. + * Ensure Ultra mode is set properly for this target. */ - if (p->type == AIC_284x) + ultra_enb_addr = ULTRA_ENB; + if ((channel == 'B') || (target > 7)) + { + ultra_enb_addr++; + } + ultra_enb = inb(p->base + ultra_enb_addr); + sxfrctl0 = inb(p->base + SXFRCTL0); + if ((*scsirate != 0) && (aic7xxx_syncrates[i].rate & ULTRA_SXFR)) { - outsb(SCBARRAY + base, scb, SCB_PIO_TRANSFER_SIZE); + ultra_enb |= 0x01 << (target & 0x07); + sxfrctl0 |= FAST20; } else { - outsl(SCBARRAY + base, scb, (SCB_PIO_TRANSFER_SIZE + 3) / 4); + ultra_enb &= ~(0x01 << (target & 0x07)); + sxfrctl0 &= ~FAST20; } - - outb(0, SCBCNT + base); -} - -/*+F************************************************************************* - * Function: - * aic7xxx_getscb - * - * Description: - * Get a SCB from the controller. - *-F*************************************************************************/ -static inline void -aic7xxx_getscb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) -{ - int base = p->base; - - /* - * This is almost identical to aic7xxx_putscb(). - */ - outb(SCBAUTO, SCBCNT + base); - insb(SCBARRAY + base, scb, SCB_PIO_TRANSFER_SIZE); - outb(0, SCBCNT + base); + outb(ultra_enb, p->base + ultra_enb_addr); + outb(sxfrctl0, p->base + SXFRCTL0); } /*+F************************************************************************* @@ -1374,6 +1562,47 @@ /*+F************************************************************************* * Function: + * scbq_remove + * + * Description: + * Removes an SCB from the list. + * + *-F*************************************************************************/ +static inline void +scbq_remove(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: @@ -1403,23 +1632,87 @@ * to be reset and all devices on that channel must be aborted. *-F*************************************************************************/ static int -aic7xxx_match_scb(struct aic7xxx_scb *scb, int target, char channel) +aic7xxx_match_scb(struct aic7xxx_scb *scb, int target, char channel, + int lun, unsigned char tag) { - int targ = (scb->target_channel_lun >> 4) & 0x0F; - char chan = (scb->target_channel_lun & SELBUSB) ? 'B' : 'A'; + int targ = (scb->hscb->target_channel_lun >> 4) & 0x0F; + char chan = (scb->hscb->target_channel_lun & SELBUSB) ? 'B' : 'A'; + int slun = scb->hscb->target_channel_lun & 0x07; + int match; #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (match_scb) comparing target/channel %d/%c to scb %d/%c\n", - target, channel, targ, chan); + printk("scsi%d: (targ %d/chan %c) matching scb to (targ %d/chan %c)\n", + scb->cmd->device->host->host_no, target, channel, targ, chan); #endif - if (target == ALL_TARGETS) + 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 + */ + outb(SCB_LIST_NULL, p->base + SCB_TAG); + + outb(inb(p->base + FREE_SCBH), p->base + SCB_NEXT); + outb(inb(p->base + SCBPTR), p->base + 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 next; + unsigned char prev; + + outb(scbptr, p->base + SCBPTR); + next = inb(p->base + SCB_NEXT); + prev = inb(p->base + SCB_PREV); + + outb(0, p->base + SCB_CONTROL); + + aic7xxx_add_curscb_to_free_list(p); + + if (prev != SCB_LIST_NULL) { - return (chan == channel); + outb(prev, p->base + SCBPTR); + outb(next, p->base + SCB_NEXT); } else { - return ((chan == channel) && (targ == target)); + outb(next, p->base + DISCONNECTED_SCBH); } + + if (next != SCB_LIST_NULL) + { + outb(next, p->base + SCBPTR); + outb(prev, p->base + SCB_PREV); + } + return next; } /*+F************************************************************************* @@ -1427,51 +1720,93 @@ * aic7xxx_busy_target * * Description: - * Set the specified target active. + * Set the specified target busy. *-F*************************************************************************/ static void -aic7xxx_busy_target(unsigned char target, char channel, int base) +aic7xxx_busy_target(struct aic7xxx_host *p, unsigned char target, + char channel, unsigned char scbid) +{ + unsigned char active_scb; + unsigned char info_scb; + unsigned int scb_offset; + + info_scb = target / 4; + if (channel == 'B') + info_scb = info_scb + 2; + + active_scb = inb(p->base + SCBPTR); + outb(info_scb, p->base + SCBPTR); + scb_offset = SCB_BUSYTARGETS + (target & 0x03); + outb(scbid, p->base + scb_offset); + outb(active_scb, p->base + SCBPTR); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_index_busy_target + * + * Description: + * Returns the index of the busy target, and optionally sets the + * target inactive. + *-F*************************************************************************/ +static unsigned char +aic7xxx_index_busy_target(struct aic7xxx_host *p, unsigned char target, + char channel, int unbusy) { - unsigned char active; - unsigned long active_port = ACTIVE_A + base; + unsigned char active_scb; + unsigned char info_scb; + unsigned char busy_scbid; + unsigned int scb_offset; + + info_scb = target / 4; + if (channel == 'B') + info_scb = info_scb + 2; - if ((target > 0x07) || (channel == 'B')) + active_scb = inb(p->base + SCBPTR); + outb(info_scb, p->base + SCBPTR); + scb_offset = SCB_BUSYTARGETS + (target & 0x03); + busy_scbid = inb(p->base + scb_offset); + if (unbusy) { - /* - * targets on the Second channel or above id 7 store info in byte two - * of ACTIVE - */ - active_port++; + outb(SCB_LIST_NULL, p->base + scb_offset); } - active = inb(active_port); - active |= (0x01 << (target & 0x07)); - outb(active, active_port); + outb(active_scb, p->base + SCBPTR); + return (busy_scbid); } /*+F************************************************************************* * Function: - * aic7xxx_unbusy_target + * aic7xxx_find_scb * * Description: - * Set the specified target inactive. + * 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 void -aic7xxx_unbusy_target(unsigned char target, char channel, int base) +static unsigned char +aic7xxx_find_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) { - unsigned char active; - unsigned long active_port = ACTIVE_A + base; + unsigned char saved_scbptr; + unsigned char curindex; - if ((target > 0x07) || (channel == 'B')) + saved_scbptr = inb(p->base + SCBPTR); + curindex = 0; + for (curindex = 0; curindex < p->scb_data->maxhscbs; curindex++) { - /* - * targets on the Second channel or above id 7 store info in byte two - * of ACTIVE - */ - active_port++; + outb(curindex, p->base + SCBPTR); + if (inb(p->base + SCB_TAG) == scb->hscb->tag) + { + break; + } } - active = inb(active_port); - active &= ~(0x01 << (target & 0x07)); - outb(active, active_port); + outb(saved_scbptr, p->base + SCBPTR); + if (curindex >= p->scb_data->maxhscbs) + { + curindex = SCB_LIST_NULL; + } + + return (curindex); } /*+F************************************************************************* @@ -1479,68 +1814,60 @@ * aic7xxx_allocate_scb * * Description: - * Get a free SCB either from one already assigned to a hardware - * slot, or one that will require an SCB to be paged out before - * use. If there are none, attempt to allocate a new one. + * Get an SCB from the free list or by allocating a new one. *-F*************************************************************************/ static struct aic7xxx_scb * aic7xxx_allocate_scb(struct aic7xxx_host *p) { - struct aic7xxx_scb *scbp = NULL; - int maxscbs; + struct aic7xxx_scb *scbp = NULL; + struct aic7xxx_hwscb *hscbp = NULL; +#ifdef AGRESSIVE + long processor_flags; + + save_flags(processor_flags); + cli(); +#endif - scbp = p->scb_link->free_scbs.head; + scbp = p->scb_data->free_scbs.head; if (scbp != NULL) { - scbq_remove_head(&p->scb_link->free_scbs); + scbq_remove_head(&p->scb_data->free_scbs); } else { - /* - * This should always be NULL if paging is not enabled. - */ - scbp = p->page_scbs.head; - if (scbp != NULL) - { - scbq_remove_head(&p->page_scbs); - } - else + if (p->scb_data->numscbs < p->scb_data->maxscbs) { - /* - * Set limit the SCB allocation to the maximum number of - * hardware SCBs if paging is not enabled; otherwise use - * the maximum (255). - */ - if (p->flags & PAGE_ENABLED) - maxscbs = p->maxscbs; - else - maxscbs = p->maxhscbs; - if (p->scb_link->numscbs < maxscbs) - { - int scb_index = p->scb_link->numscbs; - int scb_size = sizeof(struct aic7xxx_scb); - - p->scb_array[scb_index] = kmalloc(scb_size, GFP_ATOMIC | GFP_DMA); - scbp = (p->scb_array[scb_index]); - if (scbp != NULL) - { - memset(scbp, 0, sizeof(*scbp)); - scbp->tag = scb_index; - if (scb_index < p->maxhscbs) - scbp->position = scb_index; - else - scbp->position = SCB_LIST_NULL; - p->scb_link->numscbs++; - } + int scb_index = p->scb_data->numscbs; + int scb_size = sizeof(struct aic7xxx_scb) + + sizeof (struct hw_scatterlist) * AIC7XXX_MAX_SG; + + scbp = kmalloc(scb_size, GFP_ATOMIC); + if (scbp != NULL) + { + memset(scbp, 0, sizeof(struct aic7xxx_scb)); + hscbp = &p->scb_data->hscbs[scb_index]; + scbp->hscb = hscbp; + scbp->sg_list = (struct hw_scatterlist *) &scbp[1]; + memset(hscbp, 0, sizeof(struct aic7xxx_hwscb)); + hscbp->tag = scb_index; + p->scb_data->numscbs++; + /* + * Place in the scb array; never is removed + */ + p->scb_data->scb_array[scb_index] = scbp; } } } +#ifdef AIC7XXX_DEBUG if (scbp != NULL) { -#ifdef AIC7XXX_DEBUG - p->scb_link->activescbs++; -#endif + p->activescbs++; } +#endif + +#ifdef AGRESSIVE + restore_flags(processor_flags); +#endif return (scbp); } @@ -1580,6 +1907,7 @@ cmd = p->completeq.head; p->completeq.head = (Scsi_Cmnd *)cmd->host_scribble; cmd->host_scribble = NULL; + p->device_status[TARGET_INDEX(cmd)].active_cmds--; cmd->scsi_done(cmd); } p->completeq.tail = NULL; @@ -1590,53 +1918,29 @@ * aic7xxx_free_scb * * Description: - * Free the scb and update the page, waiting, free scb lists. + * Free the scb and insert into the free scb list. *-F*************************************************************************/ static void aic7xxx_free_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) { - struct aic7xxx_scb *wscb; + struct aic7xxx_hwscb *hscb; + long flags; + + hscb = scb->hscb; + save_flags(flags); + cli(); - scb->state = SCB_FREE; + scb->flags = SCB_FREE; scb->cmd = NULL; - scb->control = 0; - scb->state = 0; + hscb->control = 0; + hscb->target_status = 0; - if (scb->position == SCB_LIST_NULL) - { - scbq_insert_head(&p->page_scbs, scb); - } - else - { - /* - * If there are any SCBS on the waiting queue, assign the slot of this - * "freed" SCB to the first one. We'll run the waiting queues after - * all command completes for a particular interrupt are completed or - * when we start another command. - */ - wscb = p->waiting_scbs.head; - if (wscb != NULL) - { - scbq_remove_head(&p->waiting_scbs); - wscb->position = scb->position; - scbq_insert_tail(&p->assigned_scbs, wscb); - wscb->state = (wscb->state & ~SCB_WAITINGQ) | SCB_ASSIGNEDQ; - - /* - * The "freed" SCB will need to be assigned a slot before being - * used, so put it in the page_scbs queue. - */ - scb->position = SCB_LIST_NULL; - scbq_insert_head(&p->page_scbs, scb); - } - else - { - scbq_insert_head(&p->scb_link->free_scbs, scb); - } + scbq_insert_head(&p->scb_data->free_scbs, scb); #ifdef AIC7XXX_DEBUG - p->scb_link->activescbs--; /* For debugging purposes. */ + p->activescbs--; /* For debugging purposes. */ #endif - } + + restore_flags(flags); } /*+F************************************************************************* @@ -1651,68 +1955,113 @@ { Scsi_Cmnd *cmd = scb->cmd; + if (scb->flags & SCB_RECOVERY_SCB) + { + p->flags &= ~IN_TIMEOUT; + } + if (cmd->result == DID_OK) + { + if (scb->flags & SCB_ABORTED) + { + cmd->result = (DID_RESET << 16); + } + } + if ((scb->flags & (SCB_MSGOUT_WDTR | SCB_MSGOUT_SDTR)) != 0) + { + unsigned short mask; + + mask = 0x01 << TARGET_INDEX(scb->cmd); + if (scb->flags & SCB_MSGOUT_WDTR) + { + p->wdtr_pending &= ~mask; + } + if (scb->flags & SCB_MSGOUT_SDTR) + { + p->sdtr_pending &= ~mask; + } + } aic7xxx_free_scb(p, scb); aic7xxx_queue_cmd_complete(p, cmd); +#ifdef AIC7XXX_PROC_STATS + { + int actual; + + /* + * XXX: we should actually know how much actually transferred + * XXX: for each command, but apparently that's too difficult. + */ + actual = aic7xxx_length(cmd, 0); + if (!(scb->flags & (SCB_ABORTED | SCB_SENSE)) && (actual > 0) + && (aic7xxx_error(cmd) == 0)) + { + struct aic7xxx_xferstats *sp; + long *ptr; + int x; + + sp = &p->stats[cmd->channel & 0x01][cmd->target & 0x0F][cmd->lun & 0x07]; + sp->xfers++; + + if (cmd->request.cmd == WRITE) + { + sp->w_total++; + sp->w_total512 += (actual >> 9); + ptr = sp->w_bins; + } + else + { + sp->r_total++; + sp->r_total512 += (actual >> 9); + ptr = sp->r_bins; + } + for (x = 9; x <= 17; x++) + { + if (actual < (1 << x)) + { + ptr[x - 9]++; + break; + } + } + if (x > 17) + { + ptr[x - 9]++; + } + } + } +#endif /* AIC7XXX_PROC_STATS */ } /*+F************************************************************************* * Function: - * aic7xxx_done_aborted_scbs + * aic7xxx_run_done_queue * * Description: - * Calls the scsi_done() for the Scsi_Cmnd of each scb in the - * aborted list, and adds each scb to the free list. + * 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_done_aborted_scbs(struct aic7xxx_host *p) +aic7xxx_run_done_queue(struct aic7xxx_host *p, /*complete*/ int complete) { - Scsi_Cmnd *cmd; struct aic7xxx_scb *scb; int i; - for (i = 0; i < p->scb_link->numscbs; i++) + for (i = 0; i < p->scb_data->numscbs; i++) { - scb = (p->scb_array[i]); - if (scb->state & SCB_QUEUED_FOR_DONE) + scb = p->scb_data->scb_array[i]; + if (scb->flags & SCB_QUEUED_FOR_DONE) { #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (done_aborted_scbs) Aborting scb %d, TCL=%d/%d/%d\n", - scb->position, TCL_OF_SCB(scb)); + printk("(scsi%d:%d:%d) Aborting scb %d\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag); #endif - /* - * Process the command after marking the scb as free - * and adding it to the free list. - */ - cmd = scb->cmd; - p->device_status[TARGET_INDEX(cmd)].flags = 0; - aic7xxx_free_scb(p, scb); - cmd->scsi_done(cmd); /* call the done function */ + aic7xxx_done(p, scb); } } -} - -/*+F************************************************************************* - * Function: - * aic7xxx_add_waiting_scb - * - * Description: - * Add this SCB to the head of the "waiting for selection" list. - *-F*************************************************************************/ -static void -aic7xxx_add_waiting_scb(u_long base, struct aic7xxx_scb *scb) -{ - unsigned char next; - unsigned char curscb; - - curscb = inb(SCBPTR + base); - next = inb(WAITING_SCBH + base); - - outb(scb->position, SCBPTR + base); - outb(next, SCB_NEXT + base); - outb(scb->position, WAITING_SCBH + base); - - outb(curscb, SCBPTR + base); + if (complete) + { + aic7xxx_done_cmds_complete(p); + } } /*+F************************************************************************* @@ -1725,26 +2074,23 @@ *-F*************************************************************************/ static unsigned char aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, - unsigned char prev) + unsigned char scbpos, unsigned char prev) { unsigned char curscb, next; - int target = (scb->target_channel_lun >> 4) & 0x0F; - char channel = (scb->target_channel_lun & SELBUSB) ? 'B' : 'A'; - int base = p->base; /* * Select the SCB we want to abort and pull the next pointer out of it. */ - curscb = inb(SCBPTR + base); - outb(scb->position, SCBPTR + base); - next = inb(SCB_NEXT + base); + curscb = inb(p->base + SCBPTR); + outb(scbpos, p->base + SCBPTR); + next = inb(p->base + SCB_NEXT); /* * Clear the necessary fields */ - outb(0, SCB_CONTROL + base); - outb(SCB_LIST_NULL, SCB_NEXT + base); - aic7xxx_unbusy_target(target, channel, base); + outb(0, p->base + SCB_CONTROL); + + aic7xxx_add_curscb_to_free_list(p); /* * Update the waiting list @@ -1754,22 +2100,23 @@ /* * First in the list */ - outb(next, WAITING_SCBH + base); + outb(next, p->base + WAITING_SCBH); } else { /* * Select the scb that pointed to us and update its next pointer. */ - outb(prev, SCBPTR + base); - outb(next, SCB_NEXT + base); + outb(prev, p->base + SCBPTR); + outb(next, p->base + SCB_NEXT); } /* * Point us back at the original scb position and inform the SCSI * system that the command has been aborted. */ - outb(curscb, SCBPTR + base); - scb->state |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; + outb(curscb, p->base + SCBPTR); + scb->flags |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; + scb->flags &= ~SCB_ACTIVE; scb->cmd->result = (DID_RESET << 16); return (next); @@ -1777,6 +2124,75 @@ /*+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, char channel, + int lun, unsigned char tag, int flags, int requeue) +{ + unsigned char saved_queue[AIC7XXX_MAXSCB]; + int queued = inb(p->base + QINCNT) & p->qcntmask; + int i; + int found; + struct aic7xxx_scb *scbp; + scb_queue_type removed_scbs; + + found = 0; + scbq_init (&removed_scbs); + for (i = 0; i < (queued - found); i++) + { + saved_queue[i] = inb(p->base + QINFIFO); + scbp = p->scb_data->scb_array[saved_queue[i]]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) + { + /* + * We found an scb that needs to be removed. + */ + if (requeue) + { + scbq_insert_head(&removed_scbs, scbp); + } + else + { + scbp->flags = flags; + scbp->flags &= ~SCB_ACTIVE; + /* + * XXX - Don't know what error to use here. + */ + aic7xxx_error(scbp->cmd) = DID_RESET; + } + i--; + found++; + } + } + /* Now put the saved scbs back. */ + for (queued = 0; queued < i; queued++) + outb(saved_queue[queued], p->base + QINFIFO); + + if (requeue) + { + scbp = removed_scbs.head; + while (scbp != NULL) + { + scbq_remove_head(&removed_scbs); + /* + * XXX - Shouldn't we be adding this to the free list? + */ + scbq_insert_head(&p->waiting_scbs, scbp); + scbp->flags |= SCB_WAITINGQ; + scbp = removed_scbs.head; + } + } + + return (found); +} + +/*+F************************************************************************* + * Function: * aic7xxx_reset_device * * Description: @@ -1784,131 +2200,280 @@ * all active and queued scbs for that target/channel. *-F*************************************************************************/ static int -aic7xxx_reset_device(struct aic7xxx_host *p, int target, char channel) +aic7xxx_reset_device(struct aic7xxx_host *p, int target, char channel, + int lun, unsigned char tag) { - int base = p->base; - struct aic7xxx_scb *scb; + struct aic7xxx_scb *scbp; unsigned char active_scb; int i = 0; - int found = 0; + int found; /* * Restore this when we're done */ - active_scb = inb(SCBPTR + base); + active_scb = inb(p->base + SCBPTR); #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_device) target/channel %d/%c, active_scb %d\n", - target, channel, active_scb); + printk("(scsi%d:%d:%d) Reset device, active_scb %d\n", + p->host_no, target, CHAN_TO_INT(channel), active_scb); #endif + /* - * Search the QINFIFO. + * Deal with the busy target and linked next issues. */ { - int saved_queue[AIC7XXX_MAXSCB]; - int queued = inb(QINCNT + base) & p->qcntmask; + int min_target, max_target; + unsigned char busy_scbid; - for (i = 0; i < (queued - found); i++) + /* Make all targets 'relative' to bus A. */ + if (target == ALL_TARGETS) { - saved_queue[i] = inb(QINFIFO + base); - outb(saved_queue[i], SCBPTR + base); - scb = (p->scb_array[inb(SCB_TAG + base)]); - if (aic7xxx_match_scb(scb, target, channel)) + switch (channel) { - /* - * We found an scb that needs to be aborted. - */ -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_device) aborting SCB %d, TCL=%d/%d/%d\n", - saved_queue[i], TCL_OF_SCB(scb)); -#endif - scb->state |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; - scb->cmd->result = (DID_RESET << 16); - outb(0, SCB_CONTROL + base); - i--; - found++; + case 'A': + min_target = 0; + max_target = (p->bus_type == AIC_SINGLE) ? 7 : 15; + break; + case 'B': + min_target = 8; + max_target = 15; + break; + case ALL_CHANNELS: + default: + min_target = 0; + max_target = (p->bus_type == AIC_SINGLE) ? 7 : 15; + break; } } - /* - * Now put the saved scbs back. - */ - for (queued = 0; queued < i; queued++) + else + { + min_target = target + channel == 'B' ? 8 : 0; + max_target = min_target; + } + + for (i = min_target; i <= max_target; i++) { - outb(saved_queue[queued], QINFIFO + base); + busy_scbid = aic7xxx_index_busy_target(p, i, 'A', /*unbusy*/FALSE); + if (busy_scbid < p->scb_data->numscbs) + { + struct aic7xxx_scb *busy_scb; + struct aic7xxx_scb *next_scb; + unsigned char next_scbid; + + busy_scb = p->scb_data->scb_array[busy_scbid]; + + next_scbid = busy_scb->hscb->data_count >> 24; + + if (next_scbid == SCB_LIST_NULL) + { + busy_scbid = aic7xxx_find_scb(p, busy_scb); + + if (busy_scbid != SCB_LIST_NULL) + { + outb(busy_scbid, p->base + SCBPTR); + next_scbid = inb(p->base + SCB_LINKED_NEXT); + } + } + + if (aic7xxx_match_scb(busy_scb, target, channel, lun, tag)) + { + aic7xxx_index_busy_target(p, i, 'A', /*unbusy*/TRUE); + } + + if (next_scbid != SCB_LIST_NULL) + { + next_scb = p->scb_data->scb_array[next_scbid]; + if (aic7xxx_match_scb(next_scb, target, channel, lun, tag)) + { + continue; + } + /* Requeue for later processing */ + scbq_insert_head(&p->waiting_scbs, next_scb); + next_scb->flags |= SCB_WAITINGQ; + } + } } } + found = aic7xxx_search_qinfifo(p, target, channel, lun, tag, + SCB_ABORTED | SCB_QUEUED_FOR_DONE, /* requeue */ FALSE); + /* * Search waiting for selection list. */ { - unsigned char next, prev; + unsigned char next, prev, scb_index; - next = inb(WAITING_SCBH + base); /* Start at head of list. */ + next = inb(p->base + WAITING_SCBH); /* Start at head of list. */ prev = SCB_LIST_NULL; while (next != SCB_LIST_NULL) { - outb(next, SCBPTR + base); - scb = (p->scb_array[inb(SCB_TAG + base)]); - /* - * Select the SCB. - */ - if (aic7xxx_match_scb(scb, target, channel)) + outb(next, p->base + SCBPTR); + scb_index = inb(p->base + SCB_TAG); + if (scb_index >= p->scb_data->numscbs) + { + panic("aic7xxx: Waiting List inconsistency; SCB index=%d, numscbs=%d\n", + scb_index, p->scb_data->numscbs); + } + scbp = p->scb_data->scb_array[scb_index]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) { - next = aic7xxx_abort_waiting_scb(p, scb, prev); + unsigned char linked_next; + + next = aic7xxx_abort_waiting_scb(p, scbp, next, prev); + linked_next = inb(p->base + SCB_LINKED_NEXT); + if (linked_next != SCB_LIST_NULL) + { + struct aic7xxx_scb *next_scb; + /* + * Requeue the waiting SCB via the waiting list. + */ + next_scb = p->scb_data->scb_array[linked_next]; + if (! aic7xxx_match_scb(next_scb, target, channel, lun, tag)) + { + scbq_insert_head(&p->waiting_scbs, next_scb); + next_scb->flags |= SCB_WAITINGQ; + } + } found++; } else { prev = next; - next = inb(SCB_NEXT + base); + next = inb(p->base + SCB_NEXT); } } } /* - * Go through the entire SCB array now and look for commands for - * for this target that are active. These are other (most likely - * tagged) commands that were disconnected when the reset occurred. + * Go through disconnected list and remove any entries we have queued + * for completion, zeroing their control byte too. */ - for (i = 0; i < p->scb_link->numscbs; i++) { - scb = (p->scb_array[i]); - if ((scb->state & SCB_ACTIVE) && aic7xxx_match_scb(scb, target, channel)) + unsigned char next, prev, scb_index; + + next = inb(p->base + DISCONNECTED_SCBH); + prev = SCB_LIST_NULL; + + while (next != SCB_LIST_NULL) { - /* - * Ensure the target is "free" - */ - aic7xxx_unbusy_target(target, channel, base); - if (! (scb->state & SCB_PAGED_OUT)) + outb(next, p->base + SCBPTR); + scb_index = inb(p->base + SCB_TAG); + if (scb_index > p->scb_data->numscbs) { - outb(scb->position, SCBPTR + base); - outb(0, SCB_CONTROL + base); + panic("aic7xxx: Disconnected List inconsistency, SCB index = %d, " + "num scbs = %d.\n", scb_index, p->scb_data->numscbs); } - scb->state |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; - scb->cmd->result = (DID_RESET << 16); + scbp = p->scb_data->scb_array[scb_index]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) + { + next = aic7xxx_rem_scb_from_disc_list(p, next); + } + else + { + prev = next; + next = inb(p->base + SCB_NEXT); + } + } + } + + /* + * Go through the hardware SCB array looking for commands that + * were active but not on any list. + */ + for (i = 0; i < p->scb_data->maxhscbs; i++) + { + unsigned char scbid; + + outb(i, p->base + SCBPTR); + scbid = inb(p->base + SCB_TAG); + if (scbid < p->scb_data->numscbs) + { + scbp = p->scb_data->scb_array[scbid]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) + { + aic7xxx_add_curscb_to_free_list(p); + } + } + } + + /* + * Go through the entire SCB array now and look for commands for + * for this target that are stillactive. These are other (most likely + * tagged) commands that were disconnected when the reset occurred. + */ + for (i = 0; i < p->scb_data->numscbs; i++) + { + scbp = p->scb_data->scb_array[i]; + if (((scbp->flags & SCB_ACTIVE) != 0) && + aic7xxx_match_scb(scbp, target, channel, lun, tag)) + { + scbp->flags |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; + scbp->flags &= ~SCB_ACTIVE; + aic7xxx_error(scbp->cmd) = DID_RESET; + found++; + + if ((scbp->flags & SCB_WAITINGQ) != 0) + { + scbq_remove(&p->waiting_scbs, scbp); + scbp->flags &= ~SCB_WAITINGQ; + } } } - outb(active_scb, SCBPTR + base); + outb(active_scb, p->base + SCBPTR); return (found); } /*+F************************************************************************* * Function: + * aic7xxx_clear_intstat + * + * Description: + * Clears the interrupt status. + *-F*************************************************************************/ +static void +aic7xxx_clear_intstat(struct aic7xxx_host *p) +{ + /* Clear any interrupt conditions this may have caused. */ + outb(CLRSELDO | CLRSELDI | CLRSELINGO, p->base + CLRSINT0); + outb(CLRSELTIMEO | CLRATNO | CLRSCSIRSTI | CLRBUSFREE | CLRSCSIPERR | + CLRPHASECHG | CLRREQINIT, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); +} + +/*+F************************************************************************* + * Function: * aic7xxx_reset_current_bus * * Description: * Reset the current SCSI bus. *-F*************************************************************************/ static void -aic7xxx_reset_current_bus(int base) +aic7xxx_reset_current_bus(struct aic7xxx_host *p) { - outb(SCSIRSTO, SCSISEQ + base); + unsigned char scsiseq; + + /* Disable reset interrupts. */ + outb(inb(p->base + SIMODE1) & ~ENSCSIRST, p->base + SIMODE1); + + /* Turn on the bus reset. */ + scsiseq = inb(p->base + SCSISEQ); + outb(scsiseq | SCSIRSTO, p->base + SCSISEQ); + + udelay(1000); + + /* Turn off the bus reset. */ + outb(scsiseq & ~SCSIRSTO, p->base + SCSISEQ); + + aic7xxx_clear_intstat(p); + + /* Re-enable reset interrupts. */ + outb(inb(p->base + SIMODE1) | ENSCSIRST, p->base + SIMODE1); + udelay(1000); - outb(0, SCSISEQ + base); } /*+F************************************************************************* @@ -1921,25 +2486,24 @@ static int aic7xxx_reset_channel(struct aic7xxx_host *p, char channel, int initiate_reset) { - int base = p->base; - unsigned char sblkctl; - char cur_channel; unsigned long offset, offset_max; int found; + unsigned char sblkctl; + char cur_channel; + pause_sequencer(p); /* - * Clean up all the state information for the - * pending transactions on this bus. + * Clean up all the state information for the pending transactions + * on this bus. */ - found = aic7xxx_reset_device(p, ALL_TARGETS, channel); + found = aic7xxx_reset_device(p, ALL_TARGETS, channel, ALL_LUNS, SCB_LIST_NULL); if (channel == 'B') { p->needsdtr |= (p->needsdtr_copy & 0xFF00); p->sdtr_pending &= 0x00FF; - outb(0, ACTIVE_B + base); - offset = TARG_SCRATCH + base + 8; - offset_max = TARG_SCRATCH + base + 16; + offset = TARG_SCRATCH + 8; + offset_max = TARG_SCRATCH + 16; } else { @@ -1949,132 +2513,100 @@ p->needwdtr = p->needwdtr_copy; p->sdtr_pending = 0x0; p->wdtr_pending = 0x0; - outb(0, ACTIVE_A + base); - outb(0, ACTIVE_B + base); - offset = TARG_SCRATCH + base; - offset_max = TARG_SCRATCH + base + 16; + offset = TARG_SCRATCH; + offset_max = TARG_SCRATCH + 16; } else { + /* Channel A */ p->needsdtr |= (p->needsdtr_copy & 0x00FF); p->sdtr_pending &= 0xFF00; - outb(0, ACTIVE_A + base); - offset = TARG_SCRATCH + base; - offset_max = TARG_SCRATCH + base + 8; + offset = TARG_SCRATCH; + offset_max = TARG_SCRATCH + 8; } } + while (offset < offset_max) { /* - * Revert to async/narrow transfers - * until we renegotiate. + * Revert to async/narrow transfers until we renegotiate. */ u_char targ_scratch; - targ_scratch = inb(offset); + + targ_scratch = inb(p->base + offset); targ_scratch &= SXFR; - outb(targ_scratch, offset); + outb(targ_scratch, p->base + offset); offset++; } /* * Reset the bus and unpause/restart the controller */ - - /* - * Case 1: Command for another bus is active - */ - sblkctl = inb(SBLKCTL + base); + sblkctl = inb(p->base + SBLKCTL); cur_channel = (sblkctl & SELBUSB) ? 'B' : 'A'; if (cur_channel != channel) { + /* + * Case 1: Command for another bus is active + */ #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_channel) Stealthily resetting channel %c\n", - channel); + printk("scsi%d: Stealthily resetting channel %c\n", + p->host_no, channel); #endif /* - * Stealthily reset the other bus without upsetting the current bus + * Stealthily reset the other bus without upsetting the current bus. */ - outb(sblkctl ^ SELBUSB, SBLKCTL + base); + outb(sblkctl ^ SELBUSB, p->base + SBLKCTL); + outb(inb(p->base + SIMODE1) & ~ENBUSFREE, p->base + SIMODE1); if (initiate_reset) { - aic7xxx_reset_current_bus(base); + aic7xxx_reset_current_bus(p); + /* + * Cause the mid-level SCSI code to delay any further + * queueing by the bus settle time for us. + */ + p->host->last_reset = (jiffies + (AIC7XXX_RESET_DELAY * HZ)); } - outb(CLRSCSIRSTI | CLRSELTIMEO, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); - outb(sblkctl, SBLKCTL + base); - - UNPAUSE_SEQUENCER(p); + outb(0, p->base + SCSISEQ); + aic7xxx_clear_intstat(p); + outb(sblkctl, p->base + SBLKCTL); + unpause_sequencer(p, /* unpause_always */ FALSE); } - /* - * Case 2: A command from this bus is active or we're idle - */ else { + /* + * Case 2: A command from this bus is active or we're idle. + */ #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_channel) Resetting current channel %c\n", - channel); + printk("scsi%d: Resetting current channel %c\n", + p->host_no, channel); #endif + outb(inb(p->base + SIMODE1) & ~ENBUSFREE, p->base + SIMODE1); if (initiate_reset) { - aic7xxx_reset_current_bus(base); + aic7xxx_reset_current_bus(p); + /* + * Cause the mid-level SCSI code to delay any further + * queueing by the bus settle time for us. + */ +#if 0 + p->host->last_reset = (jiffies + (AIC7XXX_RESET_DELAY * HZ)); +#endif } - outb(CLRSCSIRSTI | CLRSELTIMEO, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); - RESTART_SEQUENCER(p); + outb(0, p->base + SCSISEQ); + aic7xxx_clear_intstat(p); + restart_sequencer(p); #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_channel) Channel reset, sequencer restarted\n"); + printk("scsi%d: Channel reset, sequencer restarted\n", p->host_no); #endif } /* - * Cause the mid-level SCSI code to delay any further - * queueing by the bus settle time for us. - */ - p->host->last_reset = (jiffies + (AIC7XXX_RESET_DELAY * HZ)); - - /* * Now loop through all the SCBs that have been marked for abortion, * and call the scsi_done routines. */ - aic7xxx_done_aborted_scbs(p); - return found; -} - -/*+F************************************************************************* - * Function: - * aic7xxx_page_scb - * - * Description: - * Swap in_scbp for out_scbp down in the cards SCB array. - * We assume that the SCB for out_scbp is already selected in SCBPTR. - * - *-F*************************************************************************/ -static inline void -aic7xxx_page_scb(struct aic7xxx_host *p, struct aic7xxx_scb *out_scbp, - struct aic7xxx_scb *in_scbp) -{ - int index; - - /* Page-out */ -#if 0 -printk("aic7xxx: Paging out target %d SCB and paging in target %d SCB\n", - out_scbp->cmd->target, in_scbp->cmd->target); -#endif - aic7xxx_getscb(p, out_scbp); - out_scbp->state |= SCB_PAGED_OUT; - if (!(out_scbp->control & TAG_ENB)) - { - /* Stick in non-tagged array */ - index = (out_scbp->target_channel_lun >> 4) | - (out_scbp->target_channel_lun & SELBUSB); - p->pagedout_ntscbs[index] = out_scbp; - } - - /* Page-in */ - in_scbp->position = out_scbp->position; - out_scbp->position = SCB_LIST_NULL; - aic7xxx_putscb(p, in_scbp); - in_scbp->state &= ~SCB_PAGED_OUT; + aic7xxx_run_done_queue(p, /*complete*/ TRUE); + return (found); } /*+F************************************************************************* @@ -2082,1159 +2614,1326 @@ * aic7xxx_run_waiting_queues * * Description: - * Scan the assigned_scbs and waiting_scbs queues. For scbs in the - * assigned_scbs queue, we download and start them. For scbs in the - * waiting_scbs queue, we page in as many as we can being careful - * not to cause a deadlock for a reconnecting target. - * + * Scan the awaiting_scbs queue downloading and starting as many + * scbs as we can. *-F*************************************************************************/ static inline void aic7xxx_run_waiting_queues(struct aic7xxx_host *p) { struct aic7xxx_scb *scb; - u_char cur_scb, intstat; - u_long base = p->base; - long flags; - if ((p->assigned_scbs.head == NULL) && (p->waiting_scbs.head == NULL)) + if (p->waiting_scbs.head == NULL) return; - save_flags(flags); - cli(); - - PAUSE_SEQUENCER(p); - cur_scb = inb(SCBPTR + base); - intstat = inb(INTSTAT + base); - + pause_sequencer(p); /* * First handle SCBs that are waiting but have been assigned a slot. */ - scb = p->assigned_scbs.head; - while (scb != NULL) - { - scbq_remove_head(&(p->assigned_scbs)); - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - /* Mark this as an active command. */ - scb->state = (scb->state & ~SCB_ASSIGNEDQ) | SCB_ACTIVE; - outb(scb->position, QINFIFO + base); - scb = p->assigned_scbs.head; - } - - /* Now deal with SCBs that require paging. */ scb = p->waiting_scbs.head; - if (scb != NULL) + while (scb != NULL) { - u_char disc_scb = inb(DISCONNECTED_SCBH + base); - u_char active = inb(FLAGS + base) & (SELECTED | IDENTIFY_SEEN); - int count = 0; - u_char next_scb; - - while (scb != NULL) + if (p->curqincnt >= p->qfullcount) { - /* Attempt to page this SCB in */ - if (disc_scb == SCB_LIST_NULL) - break; - - /* - * Advance disc_scb to the next one in the list. - */ - outb(disc_scb, SCBPTR + base); - next_scb = inb(SCB_NEXT + base); - - /* - * We have to be careful about when we allow an SCB to be paged out. - * There must always be at least one slot availible for a reconnecting - * target in case it references an SCB that has been paged out. Our - * heuristic is that either the disconnected list has at least two - * entries in it or there is one entry and the sequencer is activily - * working on an SCB which implies that it will either complete or - * disconnect before another reconnection can occur. - */ - if ((next_scb != SCB_LIST_NULL) || active) + p->curqincnt = inb(p->base + QINCNT) & p->qcntmask; + if (p->curqincnt >= p->qfullcount) { - u_char out_scbi; - struct aic7xxx_scb *out_scbp; - - scbq_remove_head(&(p->waiting_scbs)); - - /* - * Find the in-core SCB for the one we're paging out. - */ - out_scbi = inb(SCB_TAG + base); - out_scbp = (p->scb_array[out_scbi]); - - /* Do the page out and mark the paged in SCB as active. */ - aic7xxx_page_scb(p, out_scbp, scb); - - /* Mark this as an active command. */ - scb->state = (scb->state & ~SCB_WAITINGQ) | SCB_ACTIVE; - - /* Queue the command */ - outb(scb->position, QINFIFO + base); - count++; - - /* Advance to the next disconnected SCB */ - disc_scb = next_scb; - scb = p->waiting_scbs.head; + break; } - else - scb = NULL; } - if (count) + /* + * We have some space. + */ + scbq_remove_head(&(p->waiting_scbs)); + scb->flags &= ~SCB_WAITINGQ; + + outb(scb->hscb->tag, p->base + QINFIFO); + + if ((p->flags & PAGE_ENABLED) != 0) { - /* - * Update the head of the disconnected list. + /* + * We only care about this statistic when paging + * since it's impossible to overflow the qinfifo + * in the non-paging case. */ - outb(disc_scb, DISCONNECTED_SCBH + base); - if (disc_scb != SCB_LIST_NULL) - { - outb(disc_scb, SCBPTR + base); - outb(SCB_LIST_NULL, SCB_PREV + base); - } + p->curqincnt++; } + scb = p->waiting_scbs.head; } - /* Restore old position */ - outb(cur_scb, SCBPTR + base); - /* - * Guard against unpausing the sequencer if there is an interrupt - * waiting to happen. - */ - if (!(intstat & (BRKADRINT | SEQINT | SCSIINT))) - { - UNPAUSE_SEQUENCER(p); - } + unpause_sequencer(p, FALSE); +} - restore_flags(flags); +/*+F************************************************************************* + * Function: + * aic7xxx_construct_sdtr + * + * Description: + * Constucts a synchronous data transfer message in the message + * buffer on the sequencer. + *-F*************************************************************************/ +static void +aic7xxx_construct_sdtr(struct aic7xxx_host *p, int start_byte, + unsigned char period, unsigned char offset) +{ + outb(MSG_EXTENDED, p->base + MSG_OUT + start_byte); + outb(MSG_EXT_SDTR_LEN, p->base + MSG_OUT + 1 + start_byte); + outb(MSG_EXT_SDTR, p->base + MSG_OUT + 2 + start_byte); + outb(period, p->base + MSG_OUT + 3 + start_byte); + outb(offset, p->base + MSG_OUT + 4 + start_byte); + outb(start_byte + 5, p->base + MSG_LEN); } /*+F************************************************************************* * Function: - * aic7xxx_isr + * aic7xxx_construct_wdtr * * Description: - * SCSI controller interrupt handler. + * Constucts a wide data transfer message in the message buffer + * on the sequencer. + *-F*************************************************************************/ +static void +aic7xxx_construct_wdtr(struct aic7xxx_host *p, int start_byte, + unsigned char bus_width) +{ + outb(MSG_EXTENDED, p->base + MSG_OUT + start_byte); + outb(MSG_EXT_WDTR_LEN, p->base + MSG_OUT + 1 + start_byte); + outb(MSG_EXT_WDTR, p->base + MSG_OUT + 2 + start_byte); + outb(bus_width, p->base + MSG_OUT + 3 + start_byte); + outb(start_byte + 4, p->base + MSG_LEN); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_calc_residual * - * NOTE: Since we declared this using SA_INTERRUPT, interrupts should - * be disabled all through this function unless we say otherwise. + * Description: + * Calculate the residual data not yet transferred. *-F*************************************************************************/ static void -aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) +aic7xxx_calculate_residual (struct aic7xxx_host *p, struct aic7xxx_scb *scb) { - int base, intstat, actual, scb_index, run_aborted_queue = FALSE; - struct aic7xxx_host *p; - struct aic7xxx_scb *scb = NULL; - short transfer; - unsigned char ha_flags, scsi_id, bus_width; - unsigned char offset, rate, scratch, scratch_offset; - unsigned char max_offset, rej_byte; - unsigned short target_mask; - char channel; - unsigned int addr; /* must be 32 bits */ + struct aic7xxx_hwscb *hscb; Scsi_Cmnd *cmd; + int actual; - p = (struct aic7xxx_host *) aic7xxx_boards[irq]->hostdata; + cmd = scb->cmd; + hscb = scb->hscb; /* - * Search for the host with a pending interrupt. If we can't find - * one, then we've encountered a spurious interrupt. + * Don't destroy valid residual information with + * residual coming from a check sense operation. */ - while ((p != NULL) && !(inb(INTSTAT + p->base) & INT_PEND)) + if (((scb->hscb->control & DISCONNECTED) == 0) && + (scb->flags & SCB_SENSE) == 0) { - if (p->next == NULL) - { - p = NULL; - } - else + /* + * We had an underflow. At this time, there's only + * one other driver that bothers to check for this, + * and cmd->underflow seems to be set rather half- + * heartedly in the higher-level SCSI code. + */ + actual = aic7xxx_length(cmd, hscb->residual_SG_segment_count); + + actual -= (hscb->residual_data_count[2] << 16) | + (hscb->residual_data_count[1] << 8) | + hscb->residual_data_count[0]; + + if (actual < cmd->underflow) { - p = (struct aic7xxx_host *) p->next->hostdata; + printk(KERN_WARNING "(scsi%d:%d:%d) Underflow - " + "Wanted at least %u, got %u, residual SG count %d.\n", + p->host_no, TC_OF_SCB(scb), cmd->underflow, actual, + hscb->residual_SG_segment_count); + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + aic7xxx_status(cmd) = hscb->target_status; } } - if (p == NULL) - return; - /* - * Keep track of interrupts for /proc/scsi + * Clean out the residual information in the SCB for the + * next consumer. */ - p->isr_count++; + hscb->residual_data_count[2] = 0; + hscb->residual_data_count[1] = 0; + hscb->residual_data_count[0] = 0; + hscb->residual_SG_segment_count = 0; +} - if (!(p->flags & A_SCANNED) && (p->isr_count == 1)) +/*+F************************************************************************* + * Function: + * aic7xxx_handle_device_reset + * + * Description: + * Interrupt handler for sequencer interrupts (SEQINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_device_reset(struct aic7xxx_host *p, int target, char channel) +{ + unsigned short targ_mask; + unsigned char targ_scratch; + int scratch_offset = target; + int found; + + if (channel == 'B') { - /* - * We must only have one card at this IRQ and it must have been - * added to the board data before the spurious interrupt occurred. - * It is sufficient that we check isr_count and not the spurious - * interrupt count. - */ - printk("aic7xxx: (aic7xxx_isr) Encountered spurious interrupt.\n"); - return; + scratch_offset += 8; } - - base = p->base; + targ_mask = (0x01 << scratch_offset); /* - * Handle all the interrupt sources - especially for SCSI - * interrupts, we won't get a second chance at them. + * Go back to async/narrow transfers and renegotiate. */ - intstat = inb(INTSTAT + base); + p->needsdtr |= p->needsdtr_copy & targ_mask; + p->needwdtr |= p->needwdtr_copy & targ_mask; + p->sdtr_pending &= ~targ_mask; + p->wdtr_pending &= ~targ_mask; + targ_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + targ_scratch &= SXFR; + outb(targ_scratch, p->base + TARG_SCRATCH + scratch_offset); + found = aic7xxx_reset_device(p, target, channel, ALL_LUNS, SCB_LIST_NULL); + printk(KERN_WARNING "(scsi%d:%d:%d) Bus Device Reset delivered, " + "%d SCBs aborted.\n", p->host_no, target, CHAN_TO_INT(channel), found); + aic7xxx_run_done_queue(p, /*complete*/ TRUE); +} - /* - * Indicate that we're in the interrupt handler. - */ - p->flags |= IN_ISR; +/*+F************************************************************************* + * Function: + * aic7xxx_handle_seqint + * + * Description: + * Interrupt handler for sequencer interrupts (SEQINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_seqint(struct aic7xxx_host *p, unsigned char intstat) +{ + struct aic7xxx_scb *scb; + unsigned short target_mask; + unsigned char target, scratch_offset; + char channel; - if (intstat & BRKADRINT) + if ((inb(p->base + SEQ_FLAGS) & RESELECTED) != 0) { - int i; - unsigned char errno = inb(ERROR + base); - - printk(KERN_ERR "scsi%d: BRKADRINT error(0x%x):\n", p->host_no, errno); - for (i = 0; i < NUMBER(hard_error); i++) - { - if (errno & hard_error[i].errno) - { - printk(KERN_ERR " %s\n", hard_error[i].errmesg); - } - } - panic("scsi%d: BRKADRINT, error 0x%x, seqaddr 0x%x.\n", p->host_no, - inb(ERROR + base), (inb(SEQADDR1 + base) << 8) | inb(SEQADDR0 + base)); + target = (inb(p->base + SELID) >> 4) & 0x0F; } - - if (intstat & SEQINT) + else { - /* - * Although the sequencer is paused immediately on - * a SEQINT, an interrupt for a SCSIINT condition will - * unpaused the sequencer before this point. - */ - PAUSE_SEQUENCER(p); + target = (inb(p->base + SCSIID) >> 4) & 0x0F; + } + scratch_offset = target; + channel = 'A'; + if (inb(p->base + SBLKCTL) & SELBUSB) + { + channel = 'B'; + scratch_offset += 8; + } + target_mask = (0x01 << scratch_offset); - scsi_id = (inb(SCSIID + base) >> 4) & 0x0F; - scratch_offset = scsi_id; - channel = 'A'; - if (inb(SBLKCTL + base) & SELBUSB) - { - channel = 'B'; - scratch_offset += 8; - } - target_mask = (0x01 << scratch_offset); + switch (intstat & SEQINT_MASK) + { + case NO_MATCH: + { + /* + * This could be for a normal abort request. Figure out + * which SCB we were trying to find and only give an error + * if we didn't ask for this to happen. + */ + unsigned char scb_index; + unsigned char busy_scbid; + unsigned char arg1; + + busy_scbid = aic7xxx_index_busy_target(p, target, channel, + /*unbusy*/ FALSE); + arg1 = inb(p->base + ARG_1); - switch (intstat & SEQINT_MASK) - { - case NO_MATCH: - if (p->flags & PAGE_ENABLED) + if (arg1 == SCB_LIST_NULL) { - /* SCB Page-in request */ - struct aic7xxx_scb *outscb; - u_char arg_1 = inb(ARG_1 + base); - int use_disconnected = FALSE; - - /* - * The sequencer expects this value upon return. Assume - * we will find the paged out SCB and set the value now. - * If we don't, and one of the methods used to acquire an - * SCB calls aic7xxx_done(), we will end up in our queue - * routine and unpause the sequencer without giving it the - * correct return value, which causes a hang. - */ - outb(SCB_PAGEDIN, RETURN_1 + base); - if (arg_1 == SCB_LIST_NULL) - { - /* Non-tagged command */ - int index = scsi_id; - if (channel == 'B') - { - index |= SELBUSB; - } - scb = p->pagedout_ntscbs[index]; - } - else - scb = (p->scb_array[arg_1]); + /* untagged request */ + scb_index = busy_scbid; + } + else + { + scb_index = arg1; + } - if (!(scb->state & SCB_PAGED_OUT)) + if (scb_index < p->scb_data->numscbs) + { + scb = p->scb_data->scb_array[scb_index]; + if (scb->hscb->control & ABORT_SCB) { - printk(KERN_WARNING "scsi%d: No active paged-out SCB for reconnecting " - "target %d, channel %c - Issuing ABORT. SAVED_TCL(0x%x).\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - aic7xxx_unbusy_target(scsi_id, channel, base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(0, RETURN_1 + base); + /* + * We expected this. Let the busfree handler take care + * of this when we the abort is finially sent. Set + * IDENTIFY_SEEN so that the busfree handler knows that + * there is an SCB to cleanup. + */ + outb(inb(p->base + SEQ_FLAGS) | IDENTIFY_SEEN, p->base + SEQ_FLAGS); + printk(KERN_INFO "(scsi%d:%d:%d) reconnect SCB abort successful\n", + p->host_no, TC_OF_SCB(scb)); break; } + } + printk(KERN_WARNING "(scsi%d:%d:%d) No active SCB for reconnecting " + "target - Issuing BUS DEVICE RESET.\n", + p->host_no, target, CHAN_TO_INT(channel)); + + printk(KERN_WARNING " SAVED_TCL=0x%x, ARG_1=0x%x, SEQADDR=0x%x\n", + inb(p->base + SAVED_TCL), arg1, + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + aic7xxx_handle_device_reset(p, target, channel); + } + break; - /* - * Now to pick the SCB to page out. Either take a free SCB, an - * assigned SCB, an SCB that just completed, or the first one - * on the disconnected SCB list. - */ - if (p->scb_link->free_scbs.head != NULL) - { - outscb = p->scb_link->free_scbs.head; - scbq_remove_head(&p->scb_link->free_scbs); - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - scbq_insert_head(&p->page_scbs, outscb); - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; - } - else if (p->assigned_scbs.head != NULL) - { - outscb = p->assigned_scbs.head; - scbq_remove_head(&p->assigned_scbs); - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - scbq_insert_head(&p->waiting_scbs, outscb); - outscb->state = (outscb->state & ~SCB_ASSIGNEDQ) | SCB_WAITINGQ; - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; - } - else if (intstat & CMDCMPLT) + case NO_MATCH_BUSY: + { + /* + * XXX - Leave this as a panic for the time being since it + * indicates a bug in the timeout code for this to happen. + */ + unsigned char scb_index; + + scb_index = inb(p->base + CUR_SCBID); + scb = p->scb_data->scb_array[scb_index]; + + panic("scsi%d: Target %d, channel %c, Target busy link failure, " + "but busy SCB exists!\n", + p->host_no, target, channel); + } + break; + + case SEND_REJECT: + { + unsigned char rej_byte; + + rej_byte = inb(p->base + REJBYTE); + printk(KERN_WARNING "(scsi%d:%d:%d) Rejecting unknown message (0x%x) " + "received from target, SEQ_FLAGS=0x%x\n", + p->host_no, target, CHAN_TO_INT(channel), rej_byte, + inb(p->base + SEQ_FLAGS)); + } + break; + + case NO_IDENT: + { + /* + * The reconnecting target either did not send an identify + * message, or did, but we didn't find and SCB to match and + * before it could respond to our ATN/abort, it hit a dataphase. + * The only safe thing to do is to blow it away with a bus + * reset. + */ + int found; + + printk(KERN_WARNING "(scsi%d:%d:%d): Target did not send an IDENTIFY " + "message; LASTPHASE 0x%x, SAVED_TCL 0x%x\n", + p->host_no, target, CHAN_TO_INT(channel), + inb(p->base + LASTPHASE), inb(p->base + SAVED_TCL)); + + found = aic7xxx_reset_channel(p, channel, /*initiate reset*/ TRUE); + + printk(KERN_WARNING "scsi%d: Issued channel %c bus reset; " + "%d SCBs aborted\n", p->host_no, channel, found); + } + break; + + case BAD_PHASE: + if (inb(p->base + LASTPHASE) == P_BUSFREE) + { + printk(KERN_WARNING "(scsi%d:%d:%d): Missed busfree.\n", + p->host_no, CHAN_TO_INT(channel), target); + restart_sequencer(p); + } + else + { + printk(KERN_WARNING "(scsi%d:%d:%d): Unknown scsi bus phase, attempting " + "to continue\n", p->host_no, CHAN_TO_INT(channel), target); + } + break; + + case EXTENDED_MSG: + { + unsigned char message_length; + unsigned char message_code; + unsigned char scb_index; + + message_length = inb(p->base + MSGIN_EXT_LEN); + message_code = inb(p->base + MSGIN_EXT_OPCODE); + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; + + switch (message_code) + { + case MSG_EXT_SDTR: { - int scb_index; + unsigned char period; + unsigned char offset; + unsigned char saved_offset; + unsigned char targ_scratch; + unsigned char max_offset; + unsigned char rate; - outb(CLRCMDINT, CLRINT + base); - scb_index = inb(QOUTFIFO + base); - if (!(inb(QOUTCNT + base) & p->qcntmask)) + if (message_length != MSG_EXT_SDTR_LEN) { - intstat &= ~CMDCMPLT; - } - outscb = (p->scb_array[scb_index]); - if (!(outscb->state & SCB_ACTIVE)) - { - printk(KERN_WARNING "scsi%d: No command for completed SCB %d " - "during NO_MATCH interrupt\n", scb_index, p->host_no); - use_disconnected = TRUE; + outb(SEND_REJ, p->base + RETURN_1); + break; } + + period = inb(p->base + MSGIN_EXT_BYTES); + saved_offset = inb(p->base + MSGIN_EXT_BYTES + 1); + targ_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + + if (targ_scratch & WIDEXFER) + max_offset = MAX_OFFSET_16BIT; else + max_offset = MAX_OFFSET_8BIT; + offset = MIN(saved_offset, max_offset); + + aic7xxx_scsirate(p, &rate, &period, &offset, target, channel); + + /* + * Preserve the WideXfer flag. + */ + targ_scratch = rate | (targ_scratch & WIDEXFER); + + /* + * Update both the target scratch area and current SCSIRATE. + */ + outb(targ_scratch, p->base + TARG_SCRATCH + scratch_offset); + outb(targ_scratch, p->base + SCSIRATE); + + /* + * See if we initiated Sync Negotiation and didn't have + * have to fall down to async transfers. + */ + if ((scb->flags & SCB_MSGOUT_SDTR) != 0) { - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; - outscb->cmd->result |= (aic7xxx_error(outscb->cmd) << 16); - if ((outscb->cmd->flags & WAS_SENSE) && - !(outscb->cmd->flags & ASKED_FOR_SENSE)) + /* We started it. */ + if (saved_offset == offset) { - /* - * Got sense information. - */ - outscb->cmd->flags &= ASKED_FOR_SENSE; + /* + * Don't send an SDTR back to the target. + */ + outb(0, p->base + RETURN_1); + } + else + { + /* We went too low - force async. */ + outb(SEND_REJ, p->base + RETURN_1); } - p->device_status[TARGET_INDEX(outscb->cmd)].flags - |= DEVICE_SUCCESS; - aic7xxx_done(p, outscb); } + else + { + /* + * Send our own SDTR in reply. + * + * We want to see this message as we don't expect a target + * to send us a SDTR request first. + */ + printk(KERN_WARNING "scsi%d: Sending SDTR!!\n", p->host_no); + aic7xxx_construct_sdtr(p, /* start byte */ 0, period, offset); + outb(SEND_MSG, p->base + RETURN_1); + } + /* + * Clear the flags. + */ + p->needsdtr &= ~target_mask; + break; } - else - { - use_disconnected = TRUE; - } - if (use_disconnected) + + case MSG_EXT_WDTR: { - u_char tag; - u_char next; - u_char disc_scb = inb(DISCONNECTED_SCBH + base); - if (disc_scb != SCB_LIST_NULL) + unsigned char scratch, bus_width; + + if (message_length != MSG_EXT_WDTR_LEN) { - outb(disc_scb, SCBPTR + base); - tag = inb(SCB_TAG + base); - outscb = (p->scb_array[tag]); - next = inb(SCB_NEXT + base); - if (next != SCB_LIST_NULL) - { - outb(next, SCBPTR + base); - outb(SCB_LIST_NULL, SCB_PREV + base); - outb(disc_scb, SCBPTR + base); - } - outb(next, DISCONNECTED_SCBH + base); - aic7xxx_page_scb(p, outscb, scb); - } - else if (inb(QINCNT + base) & p->qcntmask) + outb(SEND_REJ, p->base + RETURN_1); + break; + } + + bus_width = inb(p->base + MSGIN_EXT_BYTES); + scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + + if ((scb->flags & SCB_MSGOUT_WDTR) != 0) { - /* Pull one of our queued commands as a last resort. */ - disc_scb = inb(QINFIFO + base); - outb(disc_scb, SCBPTR + base); - tag = inb(SCB_TAG + base); - outscb = (p->scb_array[tag]); - if ((outscb->control & 0x23) != TAG_ENB) + /* + * Don't send an WDTR back to the target, since we asked first. + */ + outb(0, p->base + RETURN_1); + switch (bus_width) { - /* - * This is not a simple tagged command so its position - * in the queue matters. Take the command at the end of - * the queue instead. - */ - int i; - int saved_queue[AIC7XXX_MAXSCB]; - int queued = inb(QINCNT + base) & p->qcntmask; - - /* Count the command we removed already */ - saved_queue[0] = disc_scb; - queued++; - - /* Empty the input queue. */ - for (i = 1; i < queued; i++) - { - saved_queue[i] = inb(QINFIFO + base); - } - - /* Put everyone back but the last entry. */ - queued--; - for (i = 0; i < queued; i++) - { - outb(saved_queue[i], QINFIFO + base); - } - - outb(saved_queue[queued], SCBPTR + base); - tag = inb(SCB_TAG + base); - outscb = (p->scb_array[tag]); + case BUS_8_BIT: + scratch &= 0x7F; + break; + + case BUS_16_BIT: + if (aic7xxx_verbose) + { + printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 " + "bit transfers.\n", p->host_no, target, channel); + } + scratch |= WIDEXFER; + break; + + case BUS_32_BIT: + outb(SEND_REJ, p->base + RETURN_1); + /* No verbose here! We want to see this condition. */ + printk(KERN_WARNING "scsi%d: Target %d, channel %c, " + "requesting 32 bit transfers, rejecting...\n", + p->host_no, target, channel); + break; + + default: + break; } - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - scbq_insert_head(&p->waiting_scbs, outscb); - outscb->state |= SCB_WAITINGQ; - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; } else { - printk(KERN_WARNING "scsi%d: Page-in request with no candidates " - "target %d, channel %c - Issuing ABORT. SAVED_TCL(0x%x).\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - aic7xxx_unbusy_target(scsi_id, channel, base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(0, RETURN_1 + base); + /* + * Send our own WDTR in reply. + */ + switch (bus_width) + { + case BUS_8_BIT: + scratch &= 0x7F; + break; + + case BUS_32_BIT: + case BUS_16_BIT: + if (p->bus_type == AIC_WIDE) + { + printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 " + "bit transfers.\n", p->host_no, target, channel); + bus_width = BUS_16_BIT; + scratch |= WIDEXFER; + } + else + { + bus_width = BUS_8_BIT; + scratch &= 0x7F; /* XXX - FreeBSD doesn't do this. */ + } + break; + + default: + break; + } + aic7xxx_construct_wdtr(p, /* start byte */ 0, bus_width); + outb(SEND_MSG, p->base + RETURN_1); } - } - } - else - { - printk(KERN_WARNING "scsi%d: No active SCB for reconnecting " - "target %d, channel %c - Issuing ABORT. SAVED_TCL(0x%x).\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - aic7xxx_unbusy_target(scsi_id, channel, base); - outb(0, SCB_CONTROL + base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(0, RETURN_1 + base); - } - break; - - case BAD_PHASE: - panic("scsi%d: Unknown scsi bus phase.\n", p->host_no); - break; - - case SEND_REJECT: - rej_byte = inb(REJBYTE + base); - if ((rej_byte & 0xF0) == 0x20) - { - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - printk(KERN_WARNING "scsi%d: Tagged message received without identify." - "Disabling tagged commands for target %d channel %c.\n", - p->host_no, scsi_id, channel); - scb->cmd->device->tagged_supported = 0; - scb->cmd->device->tagged_queue = 0; - } - else - { - printk(KERN_WARNING "scsi%d: Rejecting unknown message (0x%x) received " - "from target %d channel %c.\n", - p->host_no, rej_byte, scsi_id, channel); - } - break; - - case NO_IDENT: - panic("scsi%d: Target %d, channel %c, did not send an IDENTIFY " - "message. SAVED_TCL 0x%x.\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - break; + p->needwdtr &= ~target_mask; + outb(scratch, p->base + TARG_SCRATCH + scratch_offset); + outb(scratch, p->base + SCSIRATE); + break; + } /* case MSG_EXT_WDTR */ - case SDTR_MSG: - /* - * Help the sequencer to translate the negotiated - * transfer rate. Transfer is 1/4 the period - * in ns as is returned by the sync negotiation - * message. So, we must multiply by four. - */ - transfer = (inb(ARG_1 + base) << 2); - offset = inb(ACCUM + base); - scratch = inb(TARG_SCRATCH + base + scratch_offset); + default: + /* + * Unknown extended message - reject it. + */ + outb(SEND_REJ, p->base + RETURN_1); + break; + } /* switch (message_code) */ + } /* case EXTENDED_MSG */ + break; + + case REJECT_MSG: + { /* - * The maximum offset for a wide device is 0x08; for a - * 8-bit bus device the maximum offset is 0x0F. + * What we care about here is if we had an outstanding SDTR + * or WDTR message for this target. If we did, this is a + * signal that the target is refusing negotiation. */ - if (scratch & WIDEXFER) + unsigned char targ_scratch; + unsigned char scb_index; + + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; + targ_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + + if ((scb->flags & SCB_MSGOUT_WDTR) != 0) { - max_offset = 0x08; + /* + * note 8bit xfers and clear flag + */ + targ_scratch &= 0x7F; + p->needwdtr &= ~target_mask; + printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing WIDE " + "negotiation; using 8 bit transfers.\n", + p->host_no, target, channel); } else { - max_offset = 0x0F; - } - aic7xxx_scsirate(p, &rate, transfer, MIN(offset, max_offset), - scsi_id, channel); - /* - * Preserve the wide transfer flag. - */ - scratch = rate | (scratch & WIDEXFER); - outb(scratch, TARG_SCRATCH + base + scratch_offset); - outb(scratch, SCSIRATE + base); - if ((scratch & 0x0F) == 0) - { - /* - * One of two things happened. Either the device requested - * asynchronous data transfers, or it requested a synchronous - * data transfer rate that was so low that asynchronous - * transfers are faster (not to mention the controller won't - * support them). In both cases the synchronous data transfer - * rate and the offset are set to 0 indicating asynchronous - * transfers. - * - * If the device requested an asynchronous transfer, then - * accept the request. If the device is being forced to - * asynchronous data transfers and this is the first time - * we've seen the request, accept the request. If we've - * already seen the request, then attempt to force - * asynchronous data transfers by rejecting the message. - */ - if ((offset == 0) || (p->sdtr_pending & target_mask)) + if ((scb->flags & SCB_MSGOUT_SDTR) != 0) { /* - * Device requested asynchronous transfers or we're - * forcing asynchronous transfers for the first time. + * note asynch xfers and clear flag */ - outb(0, RETURN_1 + base); - } - else - { - /* - * The first time in forcing asynchronous transfers - * failed, so we try sending a reject message. - */ - outb(SEND_REJ, RETURN_1 + base); + targ_scratch &= 0xF0; + p->needsdtr &= ~target_mask; + printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing " + "synchronous negotiation; using asynchronous transfers.\n", + p->host_no, target, channel); } + /* + * Otherwise, we ignore it. + */ } - else - { - /* - * See if we initiated Sync Negotiation - */ - if (p->sdtr_pending & target_mask) - { - /* - * Don't send an SDTR back to the target. - */ - outb(0, RETURN_1 + base); - } - else - { - /* - * Send our own SDTR in reply. - */ - printk("aic7xxx: Sending SDTR!!\n"); - outb(SEND_SDTR, RETURN_1 + base); - } - } - /* - * Clear the flags. - */ - p->needsdtr &= ~target_mask; - p->sdtr_pending &= ~target_mask; - break; - - case WDTR_MSG: - { - bus_width = inb(ARG_1 + base); - printk(KERN_INFO "scsi%d: Received MSG_WDTR, Target %d, channel %c " - "needwdtr(0x%x).\n", p->host_no, scsi_id, channel, p->needwdtr); - scratch = inb(TARG_SCRATCH + base + scratch_offset); - - if (p->wdtr_pending & target_mask) - { - /* - * Don't send an WDTR back to the target, since we asked first. - */ - outb(0, RETURN_1 + base); - switch (bus_width) - { - case BUS_8_BIT: - scratch &= 0x7F; - break; - - case BUS_16_BIT: - printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 bit " - "transfers.\n", p->host_no, scsi_id, channel); - scratch |= 0x80; - break; - - case BUS_32_BIT: - outb(SEND_REJ, RETURN_1 + base); - printk(KERN_INFO "scsi%d: Target %d, channel %c, requesting 32 bit " - "transfers, rejecting...\n", p->host_no, scsi_id, channel); - break; - } - } - else - { - /* - * Send our own WDTR in reply. - */ - printk(KERN_INFO "scsi%d: Will send WDTR!!\n", p->host_no); - switch (bus_width) - { - case BUS_8_BIT: - scratch &= 0x7F; - break; - - case BUS_32_BIT: - /* - * Negotiate 16 bits. - */ - bus_width = BUS_16_BIT; - /* Yes, we mean to fall thru here. */ - - case BUS_16_BIT: - printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 bit " - "transfers.\n", p->host_no, scsi_id, channel); - scratch |= 0x80; - break; - } - outb(bus_width | SEND_WDTR, RETURN_1 + base); - } - p->needwdtr &= ~target_mask; - p->wdtr_pending &= ~target_mask; - outb(scratch, TARG_SCRATCH + base + scratch_offset); - outb(scratch, SCSIRATE + base); - break; + outb(targ_scratch, p->base + TARG_SCRATCH + scratch_offset); + outb(targ_scratch, p->base + SCSIRATE); } + break; - case REJECT_MSG: + case BAD_STATUS: { - /* - * What we care about here is if we had an - * outstanding SDTR or WDTR message for this - * target. If we did, this is a signal that - * the target is refusing negotiation. + unsigned char scb_index; + struct aic7xxx_hwscb *hscb; + Scsi_Cmnd *cmd; + + /* The sequencer will notify us when a command has an error that + * would be of interest to the kernel. This allows us to leave + * the sequencer running in the common case of command completes + * without error. The sequencer will have DMA'd the SCB back + * up to us, so we can reference the drivers SCB array. */ + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; + hscb = scb->hscb; - scratch = inb(TARG_SCRATCH + base + scratch_offset); - - if (p->wdtr_pending & target_mask) - { - /* - * note 8bit xfers and clear flag - */ - scratch &= 0x7F; - p->needwdtr &= ~target_mask; - p->wdtr_pending &= ~target_mask; - printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing WIDE " - "negotiation; using 8 bit transfers.\n", - p->host_no, scsi_id, channel); - } - else - { - if (p->sdtr_pending & target_mask) - { - /* - * note asynch xfers and clear flag - */ - scratch &= 0xF0; - p->needsdtr &= ~target_mask; - p->sdtr_pending &= ~target_mask; - printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing " - "synchronous negotiation; using asynchronous transfers.\n", - p->host_no, scsi_id, channel); - } - /* - * Otherwise, we ignore it. - */ - } - outb(scratch, TARG_SCRATCH + base + scratch_offset); - outb(scratch, SCSIRATE + base); - break; - } - - case BAD_STATUS: - /* The sequencer will notify us when a command has an error that - * would be of interest to the kernel. This allows us to leave - * the sequencerrunning in the common case of command completes - * without error. - */ - - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - outb(0, RETURN_1 + base); /* CHECK_CONDITION may change this */ - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + /* + * Set the default return value to 0 indicating not to send + * sense. The sense code will change this if needed and this + * reduces code duplication. + */ + outb(0, p->base + RETURN_1); + if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL)) { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx.\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); + printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " + "SEQINT 0x%x, scb %d, flags 0x%x, cmd 0x%x.\n", p->host_no, + intstat, scb_index, scb->flags, (unsigned int) scb->cmd); } else { - cmd = scb->cmd; - scb->target_status = inb(SCB_TARGET_STATUS + base); - aic7xxx_status(cmd) = scb->target_status; + cmd = scb->cmd; + hscb->target_status = inb(p->base + SCB_TARGET_STATUS); + aic7xxx_status(cmd) = hscb->target_status; - cmd->result |= scb->target_status; + cmd->result |= hscb->target_status; - switch (status_byte(scb->target_status)) - { - case GOOD: - printk(KERN_WARNING "aic7xxx: Interrupted for status of GOOD???\n"); - break; - - case CHECK_CONDITION: - if ((aic7xxx_error(cmd) == 0) && !(cmd->flags & WAS_SENSE)) - { - unsigned char tcl; - unsigned int req_buf; /* must be 32 bits */ + switch (status_byte(hscb->target_status)) + { + case GOOD: + printk(KERN_WARNING "(scsi%d:%d:%d) Interrupted for status of " + "GOOD???\n", p->host_no, TC_OF_SCB(scb)); + break; - tcl = scb->target_channel_lun; + case CHECK_CONDITION: + if ((aic7xxx_error(cmd) == 0) && !(scb->flags & SCB_SENSE)) + { + unsigned int addr; /* must be 32 bits */ + /* + * XXX - How do we save the residual (if there is one). + */ + aic7xxx_calculate_residual(p, scb); + + /* + * Send a sense command to the requesting target. + * XXX - revisit this and get rid of the memcopys. + */ + memcpy((void *) scb->sense_cmd, (void *) generic_sense, + sizeof(generic_sense)); + + scb->sense_cmd[1] = (cmd->lun << 5); + scb->sense_cmd[4] = sizeof(cmd->sense_buffer); + + scb->sg_list[0].address = VIRT_TO_BUS(&cmd->sense_buffer); + scb->sg_list[0].length = sizeof(cmd->sense_buffer); + cmd->cmd_len = COMMAND_SIZE(cmd->cmnd[0]); - /* - * Send a sense command to the requesting target. + /* + * XXX - We should allow disconnection, but can't as it + * might allow overlapped tagged commands. */ - cmd->flags |= WAS_SENSE; - memcpy((void *) scb->sense_cmd, (void *) generic_sense, - sizeof(generic_sense)); - - scb->sense_cmd[1] = (cmd->lun << 5); - scb->sense_cmd[4] = sizeof(cmd->sense_buffer); - - scb->sg_list[0].address = VIRT_TO_BUS(&cmd->sense_buffer); - scb->sg_list[0].length = sizeof(cmd->sense_buffer); - req_buf = VIRT_TO_BUS(&scb->sg_list[0]); - cmd->cmd_len = COMMAND_SIZE(cmd->cmnd[0]); - - scb->control = scb->control & DISCENB; - scb->target_channel_lun = tcl; - addr = VIRT_TO_BUS(scb->sense_cmd); - scb->SCSI_cmd_length = COMMAND_SIZE(scb->sense_cmd[0]); - memcpy(scb->SCSI_cmd_pointer, &addr, - sizeof(scb->SCSI_cmd_pointer)); - scb->SG_segment_count = 1; - memcpy(scb->SG_list_pointer, &req_buf, - sizeof(scb->SG_list_pointer)); - scb->data_count = scb->sg_list[0].length; - memcpy(scb->data_pointer, &(scb->sg_list[0].address), - sizeof(scb->data_pointer)); + /* hscb->control &= DISCENB; */ + hscb->control = 0; + hscb->target_status = 0; + hscb->SG_segment_count = 1; + + addr = VIRT_TO_BUS(&scb->sg_list[0]); + memcpy(&hscb->SG_list_pointer, &addr, + sizeof(hscb->SG_list_pointer)); + + memcpy(&hscb->data_pointer, &(scb->sg_list[0].address), + sizeof(hscb->data_pointer)); + /* Maintain SCB_LINKED_NEXT */ + hscb->data_count &= 0xFF000000; + hscb->data_count |= scb->sg_list[0].length; + + addr = VIRT_TO_BUS(scb->sense_cmd); + memcpy(&hscb->SCSI_cmd_pointer, &addr, + sizeof(hscb->SCSI_cmd_pointer)); + hscb->SCSI_cmd_length = COMMAND_SIZE(scb->sense_cmd[0]); - aic7xxx_putscb(p, scb); + scb->sg_count = hscb->SG_segment_count; + scb->flags |= SCB_SENSE; /* - * Ensure that the target is "BUSY" so we don't get overlapping - * commands if we happen to be doing tagged I/O. + * Ensure the target is busy since this will be an + * an untagged request. */ - aic7xxx_busy_target(scsi_id, channel, base); + aic7xxx_busy_target(p, target, channel, hscb->tag); + outb(SEND_SENSE, p->base + RETURN_1); + } /* first time sense, no errors */ + else + { + if (aic7xxx_error(cmd) == 0) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + } + break; - aic7xxx_add_waiting_scb(base, scb); - outb(SEND_SENSE, RETURN_1 + base); - } /* first time sense, no errors */ - else + case QUEUE_FULL: +#ifdef NOT_YET + if (scb->hscb->control & TAG_ENB) + { + if (cmd->device->queue_depth > 2) + { + cmd->device->queue_depth--; /* Not correct */ + printk(KERN_WARNING "(scsi%d:%d:%d) Tagged queue depth " + "reduced to %d\n", p->host_no, + TC_OF_SCB(scb), cmd->device->queue_depth); + } + /* + * XXX - Requeue this unconditionally? + */ + + /* + * We'd like to be able to give the SCB some more time + * (untimeout, then timeout). + */ + break; + } +#endif + printk(KERN_WARNING "(scsi%d:%d:%d) Queue full received; " + "queue depth %d, active %d\n", p->host_no, + TC_OF_SCB(scb), cmd->device->queue_depth, + p->device_status[TARGET_INDEX(cmd)].active_cmds); + + /* Else treat this as if it was a BUSY condition. */ + scb->hscb->target_status = (BUSY << 1) | + (scb->hscb->target_status & 0x01); + /* Fall through to the BUSY case. */ + + case BUSY: + printk(KERN_WARNING "(scsi%d:%d:%d) Target busy\n", + p->host_no, TC_OF_SCB(scb)); + if (!aic7xxx_error(cmd)) { - cmd->flags &= ~ASKED_FOR_SENSE; - if (aic7xxx_error(cmd) == 0) - { - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - } + /* + * The mid-level SCSI code should be fixed to + * retry the command at a later time instead of + * trying right away. + */ + aic7xxx_error(cmd) = DID_BUS_BUSY | (SUGGEST_RETRY << 8); } - break; + udelay(1000); /* A small pause (1ms) to help the drive */ + break; - case BUSY: - printk(KERN_WARNING "scsi%d: Target busy, TCL=0x%x.\n", - p->host_no, scb->target_channel_lun); - if (!aic7xxx_error(cmd)) - { - /* The error code here used to be DID_BUS_BUSY, - * but after extensive testing, it has been determined - * that a DID_BUS_BUSY return is a waste of time. If - * the problem is something that will go away, then it - * will, if it isn't, then you don't want the endless - * looping that you get with a DID_BUS_BUSY. Better - * to be on the safe side and specify an error condition - * that will eventually lead to a reset or abort of some - * sort instead of an endless loop. - */ - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - } - break; - - case QUEUE_FULL: - printk(KERN_WARNING "scsi%d: Queue full.\n", p->host_no); - scb->state |= SCB_ASSIGNEDQ; - scbq_insert_tail(&p->assigned_scbs, scb); - break; - - default: - printk(KERN_WARNING "scsi%d: Unexpected target status 0x%x.\n", - p->host_no, scb->target_status); - if (!aic7xxx_error(cmd)) - { - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - } - break; - } /* end switch */ + default: + printk(KERN_WARNING "(scsi%d:%d:%d) Unexpected target " + "status 0x%x.\n", p->host_no, + TC_OF_SCB(scb), scb->hscb->target_status); + if (!aic7xxx_error(cmd)) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + break; + } /* end switch */ } /* end else of */ - break; - - case RESIDUAL: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) - { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx.\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); - } - else - { - cmd = scb->cmd; - /* - * Don't destroy valid residual information with - * residual coming from a check sense operation. - */ - if (!(cmd->flags & WAS_SENSE)) - { - /* - * We had an underflow. At this time, there's only - * one other driver that bothers to check for this, - * and cmd->underflow seems to be set rather half- - * heartedly in the higher-level SCSI code. - */ - actual = aic7xxx_length(cmd, scb->residual_SG_segment_count); - - actual -= (inb(SCB_RESID_DCNT2 + base) << 16) | - (inb(SCB_RESID_DCNT1 + base) << 8) | - inb(SCB_RESID_DCNT0 + base); - - if (actual < cmd->underflow) - { - printk(KERN_WARNING "scsi%d: Target %d underflow - " - "Wanted at least %u, got %u, residual SG count %d.\n", - p->host_no, cmd->target, cmd->underflow, actual, - inb(SCB_RESID_SGCNT + base)); - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - aic7xxx_status(cmd) = scb->target_status; - } - } - } - break; + } + break; - case ABORT_TAG: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) - { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); - } - else - { - cmd = scb->cmd; - /* - * We didn't receive a valid tag back from the target - * on a reconnect. - */ - printk("scsi%d: Invalid tag received on target %d, channel %c, " - "lun %d - Sending ABORT_TAG.\n", p->host_no, - scsi_id, channel, cmd->lun & 0x07); + case AWAITING_MSG: + { + unsigned char scb_index; + unsigned char message_offset; - cmd->result = (DID_RETRY_COMMAND << 16); - aic7xxx_done(p, scb); - } - break; + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; - case AWAITING_MSG: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + /* + * This SCB had a MK_MESSAGE set in its control byte informing + * the sequencer that we wanted to send a special message to + * this target. + */ + message_offset = inb(p->base + MSG_LEN); + if (scb->flags & SCB_DEVICE_RESET) { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx.\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); + outb(MSG_BUS_DEV_RESET, p->base + MSG_OUT); + outb(1, p->base + MSG_LEN); + printk(KERN_INFO "(scsi%d:%d:%d) Bus device reset sent\n", + p->host_no, TC_OF_SCB(scb)); } - else + else if (scb->flags & SCB_ABORT) + { + if ((scb->hscb->control & TAG_ENB) != 0) + { + outb(MSG_ABORT_TAG, p->base + MSG_OUT + message_offset); + } + else + { + outb(MSG_ABORT, p->base + MSG_OUT + message_offset); + } + outb(message_offset + 1, p->base + MSG_LEN); + printk(KERN_WARNING "(scsi%d:%d:%d): Abort message sent.\n", + p->host_no, TC_OF_SCB(scb)); + } + else if (scb->flags & SCB_MSGOUT_WDTR) { - /* - * This SCB had a zero length command, informing the sequencer - * that we wanted to send a special message to this target. - * We only do this for BUS_DEVICE_RESET messages currently. - */ - if (scb->state & SCB_DEVICE_RESET) - { -#ifdef AIC7XXX_DEBUG_ABORT - printk ("aic7xxx: (isr) sending bus device reset to target %d\n", - scsi_id); -#endif - outb(MSG_BUS_DEVICE_RESET, MSG0 + base); - outb(1, MSG_LEN + base); - } - else - { - panic("scsi%d: AWAITING_SCB for an SCB that does " - "not have a waiting message.\n", p->host_no); - } - } - break; - - case IMMEDDONE: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: received IMMEDDONE for target %d, scb %d, state %d\n", - scsi_id, scb_index, scb->state); -#endif - if (scb->state & SCB_DEVICE_RESET) + aic7xxx_construct_wdtr(p, message_offset, BUS_16_BIT); + } + else if (scb->flags & SCB_MSGOUT_SDTR) { - int found; + unsigned char target_scratch; + unsigned short ultra_enable; + int i, sxfr; /* - * Go back to async/narrow transfers and renegotiate. + * Pull the user defined setting from scratch RAM. */ - aic7xxx_unbusy_target(scsi_id, channel, base); - p->needsdtr |= (p->needsdtr_copy & target_mask); - p->needwdtr |= (p->needwdtr_copy & target_mask); - p->sdtr_pending &= ~target_mask; - p->wdtr_pending &= ~target_mask; - scratch = inb(TARG_SCRATCH + base + scratch_offset); - scratch &= SXFR; - outb(scratch, TARG_SCRATCH + base + scratch_offset); - found = aic7xxx_reset_device(p, (int) scsi_id, channel); - printk(KERN_INFO "scsi%d: Bus Device Reset delivered, %d SCBs " - "aborted.\n", p->host_no, found); - /* Indicate that we want to call aic7xxx_done_aborted_scbs() */ - run_aborted_queue = TRUE; + target_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + sxfr = target_scratch & SXFR; + ultra_enable = inb(p->base + ULTRA_ENB) | + (inb(p->base + ULTRA_ENB + 1) << 8); + if (ultra_enable & target_mask) + { + sxfr |= 0x100; + } + for (i = 0; i < num_aic7xxx_syncrates; i++) + { + if (sxfr == aic7xxx_syncrates[i].rate) + break; + } + aic7xxx_construct_sdtr(p, message_offset, + aic7xxx_syncrates[i].period, + target_scratch & WIDEXFER ? + MAX_OFFSET_16BIT : MAX_OFFSET_8BIT); } - else + else { - panic("scsi%d: Immediate complete for unknown operation.\n", - p->host_no); - } - break; + panic("aic7xxx: AWAITING_MSG for an SCB that does " + "not have a waiting message."); + } + } + break; - case DATA_OVERRUN: + case DATA_OVERRUN: { - unsigned int overrun; - - scb = (p->scb_array[inb(base + SCB_TAG)]); - overrun = inb(base + STCNT0) | (inb(base + STCNT1) << 8) | - (inb(base + STCNT2) << 16); - overrun =0x00FFFFFF - overrun; - printk(KERN_WARNING "scsi%d: data overrun of %d bytes detected; forcing " - "a retry.\n", p->host_no, overrun); - aic7xxx_error(scb->cmd) = DID_RETRY_COMMAND; - break; + unsigned char scb_index = inb(p->base + SCB_TAG); + unsigned char lastphase = inb(p->base + LASTPHASE); + unsigned int i, overrun; + + scb = (p->scb_data->scb_array[scb_index]); + overrun = inb(p->base + STCNT) | (inb(p->base + STCNT + 1) << 8) | + (inb(p->base + STCNT + 2) << 16); + overrun = 0x00FFFFFF - overrun; + printk(KERN_WARNING "(scsi%d:%d:%d) Data overrun of %d bytes detected " + "in %s phase, tag %d; forcing a retry.\n", + p->host_no, TC_OF_SCB(scb), overrun, + lastphase == P_DATAIN ? "Data-In" : "Data-Out", + scb->hscb->tag); + printk(KERN_WARNING "%s seen Data Phase. Length = %d, NumSGs = %d.\n", + inb(p->base + SEQ_FLAGS) & DPHASE ? "Have" : "Haven't", + aic7xxx_length(scb->cmd, 0), scb->sg_count); + for (i = 0; i < scb->sg_count; i++) + { + printk(KERN_INFO " sg[%d] - Addr 0x%x : Length %d\n", + i, scb->sg_list[i].address, scb->sg_list[i].length); + } + /* + * XXX - What do we really want to do on an overrun? The + * mid-level SCSI code should handle this, but for now, + * we'll just indicate that the command should retried. + */ + aic7xxx_error(scb->cmd) = DID_RETRY_COMMAND; } + break; -#if AIC7XXX_NOT_YET - /* XXX Fill these in later */ - case MESG_BUFFER_BUSY: - break; - case MSGIN_PHASEMIS: - break; -#endif +/* #if AIC7XXX_NOT_YET */ + /* XXX Fill these in later */ + case MSG_BUFFER_BUSY: + printk("aic7xxx: Message buffer busy.\n"); + break; + case MSGIN_PHASEMIS: + printk("aic7xxx: Message-in phasemis.\n"); + break; +/*#endif */ + + case ABORT_CMDCMPLT: + /* This interrupt serves to pause the sequencer until we can clean + * up the QOUTFIFO allowing us to handle any abort SCBs that may + * completed yet still have an SCB in the QINFIFO or waiting for + * selection queue. By the time we get here, we should have + * already cleaned up the queues, so all we need to do is unpause + * the sequencer. + */ + break; + + default: /* unknown */ + printk(KERN_WARNING "scsi%d: SEQINT, INTSTAT 0x%x, SCSISIGI 0x%x.\n", + p->host_no, intstat, inb(p->base + SCSISIGI)); + break; + } + + /* + * Clear the sequencer interrupt and unpause the sequencer. + */ + outb(CLRSEQINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_handle_scsiint + * + * Description: + * Interrupt handler for SCSI interrupts (SCSIINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_scsiint(struct aic7xxx_host *p, unsigned char intstat) +{ + unsigned char scb_index; + unsigned char status; + struct aic7xxx_scb *scb; + + scb_index = inb(p->base + SCB_TAG); + status = inb(p->base + SSTAT1); - default: /* unknown */ - printk(KERN_WARNING "scsi%d: SEQINT, INTSTAT 0x%x, SCSISIGI 0x%x.\n", - p->host_no, intstat, inb(SCSISIGI + base)); - break; + if (scb_index < p->scb_data->numscbs) + { + scb = p->scb_data->scb_array[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + { + scb = NULL; } + } + else + { + scb = NULL; + } + if ((status & SCSIRSTI) != 0) + { + char channel; + + channel = (inb(p->base + SBLKCTL) & SELBUSB) ? 'B' : 'A'; + + printk(KERN_WARNING "scsi%d: SCSIINT - Someone reset channel %c.\n", + p->host_no, channel); /* - * Clear the sequencer interrupt and unpause the sequencer. + * Go through and abort all commands for the channel, but do not + * reset the channel again. */ - outb(CLRSEQINT, CLRINT + base); - UNPAUSE_SEQUENCER(p); + aic7xxx_reset_channel(p, channel, /* Initiate Reset */ FALSE); + scb = NULL; } - - if (intstat & SCSIINT) + else if ( ((status & BUSFREE) != 0) && ((status & SELTO) == 0) ) { - int status = inb(SSTAT1 + base); - scsi_id = (inb(SCSIID + base) >> 4) & 0x0F; - channel = 'A'; - if (inb(SBLKCTL + base) & SELBUSB) - { - channel = 'B'; + /* + * First look at what phase we were last in. If it's message-out, + * chances are pretty good that the bus free was in response to + * one of our abort requests. + */ + unsigned char lastphase = inb(p->base + LASTPHASE); + unsigned char target = (inb(p->base + SAVED_TCL) >> 4) & 0x0F; + char channel = (inb(p->base + SBLKCTL) & SELBUSB) ? 'B' : 'A'; + int printerror = TRUE; + + outb(0, p->base + SCSISEQ); + if (lastphase == P_MESGOUT) + { + unsigned char sindex; + unsigned char message; + + sindex = inb(p->base + SINDEX); + message = inb(p->base + sindex - 1); + + if (message == MSG_ABORT) + { + printk(KERN_WARNING "(scsi%d:%d:%d) SCB %d abort completed.\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag); + aic7xxx_reset_device(p, target, channel, SCB_LUN(scb), SCB_LIST_NULL); + aic7xxx_run_done_queue(p, /* complete */ TRUE); + scb = NULL; + printerror = 0; + } + else if (message == MSG_ABORT_TAG) + { + printk(KERN_WARNING "(scsi%d:%d:%d) SCB %d abort Tag completed.\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag); + aic7xxx_reset_device(p, target, channel, SCB_LUN(scb), scb->hscb->tag); + aic7xxx_run_done_queue(p, /* complete */ TRUE); + scb = NULL; + printerror = 0; + } + else if (message == MSG_BUS_DEV_RESET) + { + aic7xxx_handle_device_reset(p, target, channel); + scb = NULL; + printerror = 0; + } } + if (printerror != 0) + { + if (scb != NULL) + { + unsigned char tag; - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (status & SCSIRSTI) + if ((scb->hscb->control & TAG_ENB) != 0) + { + tag = scb->hscb->tag; + } + else + { + tag = SCB_LIST_NULL; + } + aic7xxx_reset_device(p, target, channel, SCB_LUN(scb), tag); + } + else + { + aic7xxx_reset_device(p, target, channel, ALL_LUNS, SCB_LIST_NULL); + } + printk(KERN_WARNING "scsi%d: Unexpected busfree, LASTPHASE = 0x%x, " + "SEQADDR = 0x%x\n", p->host_no, lastphase, + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + } + outb(inb(p->base + SIMODE1) & ~ENBUSFREE, p->base + SIMODE1); + outb(CLRBUSFREE, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + restart_sequencer(p); + } + else if ((status & SELTO) != 0) + { + unsigned char scbptr; + unsigned char nextscb; + Scsi_Cmnd *cmd; + + scbptr = inb(p->base + WAITING_SCBH); + outb(scbptr, p->base + SCBPTR); + scb_index = inb(p->base + SCB_TAG); + + scb = NULL; + if (scb_index < p->scb_data->numscbs) { - PAUSE_SEQUENCER(p); - printk(KERN_WARNING "scsi%d: SCSIINT - Someone reset channel %c.\n", - p->host_no, channel); - /* - * Go through and abort all commands for the channel, but do not - * reset the channel again. - */ - aic7xxx_reset_channel(p, channel, FALSE); - run_aborted_queue = TRUE; + scb = p->scb_data->scb_array[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + { + scb = NULL; + } + } + if (scb == NULL) + { + printk(KERN_WARNING "scsi%d: Referenced SCB %d not valid during SELTO.\n", + p->host_no, scb_index); + printk(KERN_WARNING " SCSISEQ = 0x%x SEQADDR = 0x%x SSTAT0 = 0x%x " + "SSTAT1 = 0x%x\n", inb(p->base + SCSISEQ), + inb(p->base + SEQADDR0) | (inb(p->base + SEQADDR1) << 8), + inb(p->base + SSTAT0), inb(p->base + SSTAT1)); } - else if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + else { - printk(KERN_WARNING "scsi%d: SCSIINT - No command for SCB.\n", p->host_no); /* - * Turn off the interrupt and set status to zero, so that it - * falls through the rest of the SCSIINT code. + * XXX - If we queued an abort tag, go clean up the disconnected list. + */ + cmd = scb->cmd; + cmd->result = (DID_TIME_OUT << 16); + + /* + * Clear an pending messages for the timed out + * target and mark the target as free. + */ + outb(0, p->base + MSG_LEN); + aic7xxx_index_busy_target(p, cmd->target, + cmd->channel ? 'B': 'A', /*unbusy*/ TRUE); + outb(0, p->base + SCB_CONTROL); + + /* + * Shift the waiting for selection queue forward + */ + nextscb = inb(p->base + SCB_NEXT); + outb(nextscb, p->base + WAITING_SCBH); + + /* + * Put this SCB back on the free list. */ - outb(status, CLRSINT1 + base); - UNPAUSE_SEQUENCER(p); - outb(CLRSCSIINT, CLRINT + base); + aic7xxx_add_curscb_to_free_list(p); + } + /* + * Stop the selection. + */ + outb(0, p->base + SCSISEQ); + outb(CLRSELTIMEO | CLRBUSFREE, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + restart_sequencer(p); + } + else if (scb == NULL) + { + printk(KERN_WARNING "scsi%d: aic7xxx_isr - referenced scb not valid " + "during scsiint 0x%x scb(%d)\n" + " SIMODE0 0x%x, SIMODE1 0x%x, SSTAT0 0x%x, SEQADDR 0x%x\n", + p->host_no, status, scb_index, inb(p->base + SIMODE0), + inb(p->base + SIMODE1), inb(p->base + SSTAT0), + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + /* + * Turn off the interrupt and set status to zero, so that it + * falls through the rest of the SCSIINT code. + */ + outb(status, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); + scb = NULL; + } + else if (status & SCSIPERR) + { + /* + * Determine the bus phase and queue an appropriate message. + */ + char *phase; + Scsi_Cmnd *cmd; + unsigned char mesg_out = MSG_NOOP; + unsigned char lastphase = inb(p->base + LASTPHASE); + + cmd = scb->cmd; + switch (lastphase) + { + case P_DATAOUT: + phase = "Data-Out"; + break; + case P_DATAIN: + phase = "Data-In"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_COMMAND: + phase = "Command"; + break; + case P_MESGOUT: + phase = "Message-Out"; + break; + case P_STATUS: + phase = "Status"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_MESGIN: + phase = "Message-In"; + mesg_out = MSG_PARITY_ERROR; + break; + default: + phase = "unknown"; + break; + } + + /* + * A parity error has occurred during a data + * transfer phase. Flag it and continue. + */ + printk(KERN_WARNING "(scsi%d:%d:%d) Parity error during phase %s.\n", + p->host_no, TC_OF_SCB(scb), phase); + + /* + * We've set the hardware to assert ATN if we get a parity + * error on "in" phases, so all we need to do is stuff the + * message buffer with the appropriate message. "In" phases + * have set mesg_out to something other than MSG_NOP. + */ + if (mesg_out != MSG_NOOP) + { + outb(mesg_out, p->base + MSG_OUT); + outb(1, p->base + MSG_LEN); scb = NULL; } - else if (status & SCSIPERR) + else { - char *phase; - unsigned char mesg_out = MSG_NOP; - unsigned char lastphase = inb(LASTPHASE + base); + /* + * Should we allow the target to make this decision for us? + */ + cmd->result = DID_RETRY_COMMAND << 16; + } + outb(CLRSCSIPERR, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause_always */ TRUE); + } + else + { + /* + * We don't know what's going on. Turn off the + * interrupt source and try to continue. + */ + printk(KERN_WARNING "aic7xxx: SSTAT1(0x%x).\n", status); + outb(status, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); + scb = NULL; + } + if (scb != NULL) + { + aic7xxx_done(p, scb); + aic7xxx_done_cmds_complete(p); + } +} - cmd = scb->cmd; - switch (lastphase) - { - case P_DATAOUT: - phase = "Data-Out"; - break; - case P_DATAIN: - phase = "Data-In"; - mesg_out = MSG_INITIATOR_DET_ERROR; - break; - case P_COMMAND: - phase = "Command"; - break; - case P_MESGOUT: - phase = "Message-Out"; - break; - case P_STATUS: - phase = "Status"; - mesg_out = MSG_INITIATOR_DET_ERROR; - break; - case P_MESGIN: - phase = "Message-In"; - mesg_out = MSG_MSG_PARITY_ERROR; - break; - default: - phase = "unknown"; - break; - } +/*+F************************************************************************* + * Function: + * aic7xxx_isr + * + * Description: + * SCSI controller interrupt handler. + * + * NOTE: Since we declared this using SA_INTERRUPT, interrupts should + * be disabled all through this function unless we say otherwise. + *-F*************************************************************************/ +static void +aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct aic7xxx_host *p; + unsigned char intstat; + unsigned long flags; - /* - * A parity error has occurred during a data - * transfer phase. Flag it and continue. - */ - printk(KERN_WARNING "scsi%d: Parity error during phase %s on target %d, " - "channel %d, lun %d.\n", p->host_no, phase, - cmd->target, cmd->channel & 0x01, cmd->lun & 0x07); + p = (struct aic7xxx_host *) aic7xxx_boards[irq]->hostdata; - /* - * We've set the hardware to assert ATN if we get a parity - * error on "in" phases, so all we need to do is stuff the - * message buffer with the appropriate message. In phases - * have set mesg_out to something other than MSG_NOP. - */ - if (mesg_out != MSG_NOP) - { - outb(mesg_out, MSG0 + base); - outb(1, MSG_LEN + base); - cmd->result = DID_PARITY << 16; - } - else - { - /* - * Should we allow the target to make this decision for us? - */ - cmd->result = DID_RETRY_COMMAND << 16; - } - aic7xxx_done(p, scb); + /* + * Search for the host with a pending interrupt. If we can't find + * one, then we've encountered a spurious interrupt. + */ + while ((p != NULL) && !(inb(p->base + INTSTAT) & INT_PEND)) + { + if (p->next == NULL) + { + p = NULL; } - else if (status & SELTO) + else { - unsigned char waiting; + p = (struct aic7xxx_host *) p->next->hostdata; + } + } - cmd = scb->cmd; + if (p == NULL) + return; - cmd->result = (DID_TIME_OUT << 16); - /* - * Clear an pending messages for the timed out - * target and mark the target as free. - */ - ha_flags = inb(FLAGS + base); - outb(0, MSG_LEN + base); - aic7xxx_unbusy_target(scsi_id, channel, base); - /* - * Stop the selection. - */ - outb(0, SCSISEQ + base); - outb(0, SCB_CONTROL + base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); + /* + * Handle all the interrupt sources - especially for SCSI + * interrupts, we won't get a second chance at them. + */ + intstat = inb(p->base + INTSTAT); - /* - * Shift the waiting for selection queue forward - */ - waiting = inb(WAITING_SCBH + base); - outb(waiting, SCBPTR + base); - waiting = inb(SCB_NEXT + base); - outb(waiting, WAITING_SCBH + base); + /* + * Keep track of interrupts for /proc/scsi + */ + p->isr_count++; - RESTART_SEQUENCER(p); - aic7xxx_done(p, scb); - } - else if (!(status & BUSFREE)) + if (!(p->flags & A_SCANNED) && (p->isr_count == 1)) + { + /* + * We must only have one card at this IRQ and it must have been + * added to the board data before the spurious interrupt occurred. + * It is sufficient that we check isr_count and not the spurious + * interrupt count. + */ + printk("scsi%d: Encountered spurious interrupt.\n", p->host_no); + if (intstat) { - /* - * We don't know what's going on. Turn off the - * interrupt source and try to continue. - */ - printk(KERN_WARNING "aic7xxx: SSTAT1(0x%x).\n", status); - outb(status, CLRSINT1 + base); - UNPAUSE_SEQUENCER(p); - outb(CLRSCSIINT, CLRINT + base); + /* Try clearing all interrupts. */ + outb(CLRBRKADRINT | CLRSCSIINT | CLRCMDINT | CLRSEQINT, p->base + CLRINT); } + return; + } + + if (p->flags & IN_ISR) + { + printk(KERN_WARNING "scsi%d: Warning!! Interrupt routine called reentrantly!\n", + p->host_no); + return; } - if (run_aborted_queue) - aic7xxx_done_aborted_scbs(p); + /* + * Indicate that we're in the interrupt handler. + */ + save_flags(flags); + cli(); + p->flags |= IN_ISR; if (intstat & CMDCMPLT) { - int complete; + struct aic7xxx_scb *scb = NULL; + Scsi_Cmnd *cmd; + unsigned char qoutcnt; + unsigned char scb_index; + int i, interrupts_cleared = 0; /* * The sequencer will continue running when it * issues this interrupt. There may be >1 commands * finished, so loop until we've processed them all. */ - do { - complete = inb(QOUTFIFO + base); + qoutcnt = inb(p->base + QOUTCNT) & p->qcntmask; - scb = (p->scb_array[complete]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) - { - printk(KERN_WARNING "scsi%d: CMDCMPLT without command for SCB %d.\n" - " QOUTCNT %d, QINCNT %d, SCB state 0x%x, cmd 0x%lx, " - "pos(%d).\n", p->host_no, complete, inb(QOUTCNT + base), - inb(QINCNT + base), scb->state, (unsigned long) scb->cmd, - scb->position); - outb(CLRCMDINT, CLRINT + base); - continue; - } - cmd = scb->cmd; - cmd->result |= (aic7xxx_error(cmd) << 16); - if ((cmd->flags & WAS_SENSE) && !(cmd->flags & ASKED_FOR_SENSE)) +#if 1 + if (qoutcnt >= p->qfullcount - 1) + printk(KERN_WARNING "aic7xxx: Command complete near Qfull count, " + "qoutcnt = %d.\n", qoutcnt); +#endif + while (qoutcnt > 0) + { + for (i = 0; i < qoutcnt; i++) { - /* - * Got sense information. - */ - cmd->flags &= ASKED_FOR_SENSE; + scb_index = inb(p->base + QOUTFIFO); + scb = p->scb_data->scb_array[scb_index]; + if (scb == NULL) + { + printk(KERN_WARNING "scsi%d: CMDCMPLT with invalid SCB index %d, " + "QOUTCNT %d, QINCNT %d\n", p->host_no, scb_index, + inb(p->base + QOUTCNT), inb(p->base + QINCNT)); + continue; + } + else if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk(KERN_WARNING "scsi%d: CMDCMPLT without command for SCB %d, " + "QOUTCNT %d, QINCNT %d, SCB flags 0x%x, cmd 0x%lx\n", + p->host_no, scb_index, inb(p->base + QOUTCNT), + inb(p->base + QINCNT), scb->flags, (unsigned long) scb->cmd); + continue; + } + cmd = scb->cmd; + if (scb->hscb->residual_SG_segment_count != 0) + { + aic7xxx_calculate_residual(p, scb); + } + if ((scb->flags & SCB_QUEUED_ABORT) != 0) + { + /* + * Have to clean up any possible entries in the + * waiting queue and the QINFIFO. + */ + int target; + char channel; + int lun; + unsigned char tag; + + tag = SCB_LIST_NULL; + target = cmd->target; + lun = cmd->lun; + channel = (scb->hscb->target_channel_lun & SELBUSB) ? 'B' : 'A'; + if (scb->hscb->control & TAG_ENB) + { + tag = scb->hscb->tag; + } + aic7xxx_reset_device(p, target, channel, lun, tag); + /* + * Run the done queue, but don't complete the commands; we + * do this once at the end of the loop. + */ + aic7xxx_run_done_queue(p, /*complete*/ FALSE); + } + cmd->result |= (aic7xxx_error(cmd) << 16); + p->device_status[TARGET_INDEX(cmd)].flags |= DEVICE_SUCCESS; + aic7xxx_done(p, scb); } - p->device_status[TARGET_INDEX(cmd)].flags |= DEVICE_SUCCESS; - /* * Clear interrupt status before checking the output queue again. * This eliminates a race condition whereby a command could @@ -3242,56 +3941,152 @@ * so notification of the command being complete never made it * back up to the kernel. */ - outb(CLRCMDINT, CLRINT + base); - aic7xxx_done(p, scb); + outb(CLRCMDINT, p->base + CLRINT); + interrupts_cleared++; + qoutcnt = inb(p->base + QOUTCNT) & p->qcntmask; + } -#ifdef AIC7XXX_PROC_STATS - /* - * XXX: we should actually know how much actually transferred - * XXX: for each command, but apparently that's too difficult. - */ - actual = aic7xxx_length(cmd, 0); - if (!(cmd->flags & WAS_SENSE) && (actual > 0)) + if (interrupts_cleared == 0) + { + outb(CLRCMDINT, p->base + CLRINT); + } + + aic7xxx_done_cmds_complete(p); + } + + if (intstat & BRKADRINT) + { + int i; + unsigned char errno = inb(p->base + ERROR); + + printk(KERN_ERR "scsi%d: BRKADRINT error(0x%x):\n", p->host_no, errno); + for (i = 0; i < NUMBER(hard_error); i++) + { + if (errno & hard_error[i].errno) { - struct aic7xxx_xferstats *sp; - long *ptr; - int x; + printk(KERN_ERR " %s\n", hard_error[i].errmesg); + } + } + printk("scsi%d: BRKADRINT, error 0x%x, seqaddr 0x%x.\n", p->host_no, + inb(p->base + ERROR), + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + aic7xxx_reset_device(p, ALL_TARGETS, ALL_CHANNELS, ALL_LUNS, SCB_LIST_NULL); + aic7xxx_run_done_queue(p, /*complete*/ TRUE); + } + + if (intstat & SEQINT) + { + aic7xxx_handle_seqint(p, intstat); + } + + if (intstat & SCSIINT) + { + aic7xxx_handle_scsiint(p, intstat); + } + + if (p->waiting_scbs.head != NULL) + { + aic7xxx_run_waiting_queues(p); + } + + p->flags &= ~IN_ISR; + restore_flags(flags); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_device_queue_depth + * + * Description: + * Determines the queue depth for a given device. There are two ways + * a queue depth can be obtained for a tagged queueing device. One + * way is the default queue depth which is determined by whether + * AIC7XXX_CMDS_PER_LUN is defined. If it is defined, then it is used + * as the default queue depth. Otherwise, we use either 4 or 8 as the + * default queue depth (dependent on the number of hardware SCBs). + * The other way we determine queue depth is through the use of the + * aic7xxx_tag_info array which is enabled by defining + * AIC7XXX_TAGGED_QUEUEING_BY_DEVICE. This array can be initialized + * with queue depths for individual devices. It also allows tagged + * queueing to be [en|dis]abled for a specific adapter. + *-F*************************************************************************/ +static void +aic7xxx_device_queue_depth(struct aic7xxx_host *p, Scsi_Device *device) +{ + int default_depth = 2; + + device->queue_depth = default_depth; +#ifdef AIC7XXX_TAGGED_QUEUEING + if (device->tagged_supported) + { + unsigned short target_mask; + int tag_enabled = TRUE; - sp = &p->stats[cmd->channel & 0x01][cmd->target & 0x0F][cmd->lun & 0x07]; - sp->xfers++; + target_mask = (1 << (device->id | (device->channel << 3))); + +#ifdef AIC7XXX_CMDS_PER_LUN + default_depth = AIC7XXX_CMDS_PER_LUN; +#else + if (p->scb_data->maxhscbs <= 4) + { + default_depth = 4; /* Not many SCBs to work with. */ + } + else + { + default_depth = 8; + } +#endif + + if (!(p->discenable & target_mask)) + { + printk(KERN_INFO "(scsi%d:%d:%d) Disconnection disabled, unable to " + "enable tagged queueing.\n", + p->host_no, device->id, device->channel); + } + else + { +#ifndef AIC7XXX_TAGGED_QUEUEING_BY_DEVICE + device->queue_depth = default_depth; +#else + if (p->instance > NUMBER(aic7xxx_tag_info)) + { + device->queue_depth = default_depth; + } + else + { + unsigned char tindex; - if (cmd->request.cmd == WRITE) + tindex = device->id | (device->channel << 3); + if (aic7xxx_tag_info[p->instance].tag_commands[tindex] < 0) { - sp->w_total++; - sp->w_total512 += (actual >> 9); - ptr = sp->w_bins; + tag_enabled = FALSE; + device->queue_depth = 2; /* Tagged queueing is disabled. */ } - else + else if (aic7xxx_tag_info[p->instance].tag_commands[tindex] == 0) { - sp->r_total++; - sp->r_total512 += (actual >> 9); - ptr = sp->r_bins; + device->queue_depth = default_depth; } - for (x = 9; x <= 17; x++) + else { - if (actual < (1 << x)) - { - ptr[x - 9]++; - break; - } + device->queue_depth = + aic7xxx_tag_info[p->instance].tag_commands[tindex]; } - if (x > 17) + } +#endif + if ((device->tagged_queue == 0) && tag_enabled) + { + if (aic7xxx_verbose) { - ptr[x - 9]++; + printk(KERN_INFO "(scsi%d:%d:%d) Enabled tagged queuing, " + "queue depth %d.\n", p->host_no, + device->id, device->channel, device->queue_depth); } + device->tagged_queue = 1; + device->current_tag = SCB_LIST_NULL; } -#endif /* AIC7XXX_PROC_STATS */ - - } while (inb(QOUTCNT + base) & p->qcntmask); + } } - aic7xxx_done_cmds_complete(p); - p->flags &= ~IN_ISR; - aic7xxx_run_waiting_queues(p); +#endif } /*+F************************************************************************* @@ -3306,59 +4101,18 @@ * algorithm for determining the queue depth based on the maximum * SCBs for the controller. *-F*************************************************************************/ -static void aic7xxx_select_queue_depth(struct Scsi_Host *host, +static void +aic7xxx_select_queue_depth(struct Scsi_Host *host, Scsi_Device *scsi_devs) { - Scsi_Device *device = scsi_devs; - int tq_depth = 2; + Scsi_Device *device; struct aic7xxx_host *p = (struct aic7xxx_host *) host->hostdata; -#ifdef AIC7XXX_CMDS_PER_LUN - tq_depth = AIC7XXX_CMDS_PER_LUN; -#else - { - if (p->maxhscbs <= 4) - { - tq_depth = 4; /* Not many SCBs to work with. */ - } - else - { - tq_depth = 8; - } - } -#endif - for (device = scsi_devs; device != NULL; device = device->next) { if (device->host == host) { - device->queue_depth = 2; -#ifdef AIC7XXX_TAGGED_QUEUEING - if (device->tagged_supported) - { - unsigned short target_mask = (1 << device->id) | device->channel; - - if (!(p->discenable & target_mask)) - { - printk(KERN_INFO "scsi%d: Disconnection disabled, unable to enable " - "tagged queueing for target %d, channel %d, LUN %d.\n", - host->host_no, device->id, device->channel, device->lun); - } - else - { - device->queue_depth = tq_depth; - if (device->tagged_queue == 0) - { - printk(KERN_INFO "scsi%d: Enabled tagged queuing for target %d, " - "channel %d, LUN %d, queue depth %d.\n", host->host_no, - device->id, device->channel, device->lun, - device->queue_depth); - device->tagged_queue = 1; - device->current_tag = SCB_LIST_NULL; - } - } - } -#endif + aic7xxx_device_queue_depth(p, device); } } } @@ -3385,7 +4139,7 @@ * The fourth byte's lowest bit seems to be an enabled/disabled * flag (rest of the bits are reserved?). *-F*************************************************************************/ -static aha_type +static aha_chip_type aic7xxx_probe(int slot, int base, aha_status_type *bios) { int i; @@ -3394,7 +4148,7 @@ static struct { int n; unsigned char signature[sizeof(buf)]; - aha_type type; + aha_chip_type type; int bios_disabled; } AIC7xxx[] = { { 4, { 0x04, 0x90, 0x77, 0x71 }, AIC_7771, FALSE }, /* host adapter 274x */ @@ -3433,7 +4187,8 @@ return (AIC7xxx[i].type); } - printk("aic7xxx: Disabled at slot %d, ignored.\n", slot); + printk("aic7xxx: " + "disabled at slot %d, ignored.\n", slot); } } @@ -3460,10 +4215,9 @@ * useful in that it gives us an 800 nsec timer. After a read from the * SEECTL_2840 register the timing flag is cleared and goes high 800 nsec * later. - * *-F*************************************************************************/ static int -read_2840_seeprom(int base, struct seeprom_config *sc) +read_284x_seeprom(struct aic7xxx_host *p, struct seeprom_config *sc) { int i = 0, k = 0; unsigned char temp; @@ -3476,11 +4230,11 @@ struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; #define CLOCK_PULSE(p) \ - while ((inb(STATUS_2840 + base) & EEPROM_TF) == 0) \ + while ((inb(p->base + STATUS_2840) & EEPROM_TF) == 0) \ { \ ; /* Do nothing */ \ } \ - (void) inb(SEECTL_2840 + base); + (void) inb(p->base + SEECTL_2840); /* * Read the first 32 registers of the seeprom. For the 2840, @@ -3493,8 +4247,8 @@ /* * Send chip select for one clock cycle. */ - outb(CK_2840 | CS_2840, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(CK_2840 | CS_2840, p->base + SEECTL_2840); + CLOCK_PULSE(p); /* * Now we're ready to send the read command followed by the @@ -3503,11 +4257,11 @@ for (i = 0; i < seeprom_read.len; i++) { temp = CS_2840 | seeprom_read.bits[i]; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); temp = temp ^ CK_2840; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); } /* * Send the 6 bit address (MSB first, LSB last). @@ -3517,11 +4271,11 @@ temp = k; temp = (temp >> i) & 1; /* Mask out all but lower bit. */ temp = CS_2840 | temp; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); temp = temp ^ CK_2840; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); } /* @@ -3533,12 +4287,12 @@ for (i = 0; i <= 16; i++) { temp = CS_2840; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); temp = temp ^ CK_2840; - seeprom[k] = (seeprom[k] << 1) | (inb(STATUS_2840 + base) & DI_2840); - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + seeprom[k] = (seeprom[k] << 1) | (inb(p->base + STATUS_2840) & DI_2840); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); } /* * The serial EEPROM has a checksum in the last word. Keep a @@ -3554,12 +4308,12 @@ /* * Reset the chip select for the next command cycle. */ - outb(0, SEECTL_2840 + base); - CLOCK_PULSE(base); - outb(CK_2840, SEECTL_2840 + base); - CLOCK_PULSE(base); - outb(0, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(0, p->base + SEECTL_2840); + CLOCK_PULSE(p); + outb(CK_2840, p->base + SEECTL_2840); + CLOCK_PULSE(p); + outb(0, p->base + SEECTL_2840); + CLOCK_PULSE(p); } #if 0 @@ -3588,6 +4342,53 @@ /*+F************************************************************************* * Function: + * acquire_seeprom + * + * Description: + * Acquires access to the memory port on PCI controllers. + *-F*************************************************************************/ +static inline int +acquire_seeprom(struct aic7xxx_host *p) +{ + int wait; + + /* + * Request access of the memory port. When access is + * granted, SEERDY will go high. We use a 1 second + * timeout which should be near 1 second more than + * is needed. Reason: after the 7870 chip reset, there + * should be no contention. + */ + outb(SEEMS, p->base + SEECTL); + wait = 1000; /* 1000 msec = 1 second */ + while ((wait > 0) && ((inb(p->base + SEECTL) & SEERDY) == 0)) + { + wait--; + udelay(1000); /* 1 msec */ + } + if ((inb(p->base + SEECTL) & SEERDY) == 0) + { + outb(0, p->base + SEECTL); + return (0); + } + return (1); +} + +/*+F************************************************************************* + * Function: + * release_seeprom + * + * Description: + * Releases access to the memory port on PCI controllers. + *-F*************************************************************************/ +static inline void +release_seeprom(struct aic7xxx_host *p) +{ + outb(0, p->base + SEECTL); +} + +/*+F************************************************************************* + * Function: * read_seeprom * * Description: @@ -3625,7 +4426,7 @@ * first). The clock cycling from low to high initiates the next data * bit to be sent from the chip. * - * The 7870 interface to the 93C46 serial EEPROM is through the SEECTL + * The 78xx interface to the 93C46 serial EEPROM is through the SEECTL * register. After successful arbitration for the memory port, the * SEECS bit of the SEECTL register is connected to the chip select. * The SEECK, SEEDO, and SEEDI are connected to the clock, data out, @@ -3635,17 +4436,14 @@ * to this is when we first request access to the memory port. The * SEERDY goes high to signify that access has been granted and, for * this case, has no implied timing. - * *-F*************************************************************************/ static int -read_seeprom(int base, int offset, struct seeprom_config *sc, - seeprom_chip_type chip) +read_seeprom(struct aic7xxx_host *p, int offset, unsigned short *scarray, + unsigned int len, seeprom_chip_type chip) { int i = 0, k; - unsigned long timeout; unsigned char temp; unsigned short checksum = 0; - unsigned short *seeprom = (unsigned short *) sc; struct seeprom_cmd { unsigned char len; unsigned char bits[3]; @@ -3653,43 +4451,33 @@ struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; #define CLOCK_PULSE(p) \ - while ((inb(SEECTL + base) & SEERDY) == 0) \ + while ((inb(p->base + SEECTL) & SEERDY) == 0) \ { \ ; /* Do nothing */ \ } /* - * Request access of the memory port. When access is - * granted, SEERDY will go high. We use a 1 second - * timeout which should be near 1 second more than - * is needed. Reason: after the 7870 chip reset, there - * should be no contention. + * Request access of the memory port. */ - outb(SEEMS, SEECTL + base); - timeout = jiffies + 100; /* 1 second timeout */ - while ((jiffies < timeout) && ((inb(SEECTL + base) & SEERDY) == 0)) + if (acquire_seeprom(p) == 0) { - ; /* Do nothing! Wait for access to be granted. */ - } - if ((inb(SEECTL + base) & SEERDY) == 0) - { - outb(0, SEECTL + base); return (0); } /* - * Read the first 32 registers of the seeprom. For the 7870, - * the 93C46 SEEPROM is a 1024-bit device with 64 16-bit registers - * but only the first 32 are used by Adaptec BIOS. The loop - * will range from 0 to 31. + * Read 'len' registers of the seeprom. For the 7870, the 93C46 + * SEEPROM is a 1024-bit device with 64 16-bit registers but only + * the first 32 are used by Adaptec BIOS. Some adapters use the + * 93C56 SEEPROM which is a 2048-bit device. The loop will range + * from 0 to 'len' - 1. */ - for (k = 0; k < (sizeof(*sc) / 2); k++) + for (k = 0; k < len; k++) { /* * Send chip select for one clock cycle. */ - outb(SEEMS | SEECK | SEECS, SEECTL + base); - CLOCK_PULSE(base); + outb(SEEMS | SEECK | SEECS, p->base + SEECTL); + CLOCK_PULSE(p); /* * Now we're ready to send the read command followed by the @@ -3698,25 +4486,25 @@ for (i = 0; i < seeprom_read.len; i++) { temp = SEEMS | SEECS | (seeprom_read.bits[i] << 1); - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); temp = temp ^ SEECK; - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); } /* - * Send the 6 bit address (MSB first, LSB last). + * Send the 6 or 8 bit address (MSB first, LSB last). */ for (i = ((int) chip - 1); i >= 0; i--) { temp = k + offset; temp = (temp >> i) & 1; /* Mask out all but lower bit. */ temp = SEEMS | SEECS | (temp << 1); - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); temp = temp ^ SEECK; - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); } /* @@ -3728,56 +4516,57 @@ for (i = 0; i <= 16; i++) { temp = SEEMS | SEECS; - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); temp = temp ^ SEECK; - seeprom[k] = (seeprom[k] << 1) | (inb(SEECTL + base) & SEEDI); - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + scarray[k] = (scarray[k] << 1) | (inb(p->base + SEECTL) & SEEDI); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); } /* - * The serial EEPROM has a checksum in the last word. Keep a - * running checksum for all words read except for the last - * word. We'll verify the checksum after all words have been - * read. + * The serial EEPROM should have a checksum in the last word. + * Keep a running checksum for all words read except for the + * last word. We'll verify the checksum after all words have + * been read. */ - if (k < (sizeof(*sc) / 2) - 1) + if (k < (len - 1)) { - checksum = checksum + seeprom[k]; + checksum = checksum + scarray[k]; } /* * Reset the chip select for the next command cycle. */ - outb(SEEMS, SEECTL + base); - CLOCK_PULSE(base); - outb(SEEMS | SEECK, SEECTL + base); - CLOCK_PULSE(base); - outb(SEEMS, SEECTL + base); - CLOCK_PULSE(base); + outb(SEEMS, p->base + SEECTL); + CLOCK_PULSE(p); + outb(SEEMS | SEECK, p->base + SEECTL); + CLOCK_PULSE(p); + outb(SEEMS, p->base + SEECTL); + CLOCK_PULSE(p); } /* * Release access to the memory port and the serial EEPROM. */ - outb(0, SEECTL + base); + release_seeprom(p); #if 0 - printk("Computed checksum 0x%x, checksum read 0x%x\n", checksum, sc->checksum); + printk("Computed checksum 0x%x, checksum read 0x%x\n", + checksum, scarray[len - 1]); printk("Serial EEPROM:"); - for (k = 0; k < (sizeof(*sc) / 2); k++) + for (k = 0; k < len; k++) { if (((k % 8) == 0) && (k != 0)) { printk("\n "); } - printk(" 0x%x", seeprom[k]); + printk(" 0x%x", scarray[k]); } printk("\n"); #endif - if (checksum != sc->checksum) + if (checksum != scarray[len - 1]) { return (0); } @@ -3788,563 +4577,452 @@ /*+F************************************************************************* * Function: - * detect_maxscb + * write_brdctl * * Description: - * Detects the maximum number of SCBs for the controller and returns - * the count and a mask in config (config->maxscbs, config->qcntmask). + * Writes a value to the BRDCTL register. *-F*************************************************************************/ -static void -detect_maxscb(struct aic7xxx_host_config *config) +static inline void +write_brdctl(struct aic7xxx_host *p, unsigned char value) { - unsigned char sblkctl_reg; - int base, i; - -#ifdef AIC7XXX_PAGE_ENABLE - config->flags |= PAGE_ENABLED; -#endif - base = config->base; - switch (config->type) - { - case AIC_7770: - case AIC_7771: - case AIC_284x: - /* - * Check for Rev C or E boards. Rev E boards can supposedly have - * more than 4 SCBs, while the Rev C boards are limited to 4 SCBs. - * It's still not clear extactly what is different about the Rev E - * boards, but we think it allows 8 bit entries in the QOUTFIFO to - * support "paging" SCBs (more than 4 commands can be active at once). - * - * The Rev E boards have a read/write autoflush bit in the - * SBLKCTL register, while in the Rev C boards it is read only. - */ - sblkctl_reg = inb(SBLKCTL + base) ^ AUTOFLUSHDIS; - outb(sblkctl_reg, SBLKCTL + base); - if (inb(SBLKCTL + base) == sblkctl_reg) - { - /* - * We detected a Rev E board, we allow paging on this board. - */ - printk(KERN_INFO "aic7xxx: %s Rev E and subsequent.\n", - board_names[config->type]); - outb(sblkctl_reg ^ AUTOFLUSHDIS, SBLKCTL + base); - } - else - { - /* Do not allow paging. */ - config->flags &= ~PAGE_ENABLED; - printk(KERN_INFO "aic7xxx: %s Rev C and previous.\n", - board_names[config->type]); - } - break; - - default: - break; - } - - /* - * Walk the SCBs to determine how many there are. - */ - i = 1; - outb(0, SCBPTR + base); - outb(0, SCBARRAY + base); - - while (i < AIC7XXX_MAXSCB) - { - outb(i, SCBPTR + base); - outb(i, SCBARRAY + base); - if (inb(SCBARRAY + base) != i) - break; - outb(0, SCBPTR + base); - if (inb(SCBARRAY + base) != 0) - break; - - outb(i, SCBPTR + base); /* Clear the control byte. */ - outb(0, SCBARRAY + base); - - config->qcntmask |= i; /* Update the count mask. */ - i++; - } - outb(i, SCBPTR + base); /* Ensure we clear the control bytes. */ - outb(0, SCBARRAY + base); - outb(0, SCBPTR + base); - outb(0, SCBARRAY + base); - - config->maxhscbs = i; - config->qcntmask |= i; - if ((config->flags & PAGE_ENABLED) && (config->maxhscbs < AIC7XXX_MAXSCB)) - { - config->maxscbs = AIC7XXX_MAXSCB; - } - else - { - config->flags &= ~PAGE_ENABLED; /* Disable paging if we have 255 SCBs!. */ - config->maxscbs = config->maxhscbs; - } - - printk(KERN_INFO "aic7xxx: Memory check yields %d SCBs", config->maxhscbs); - if (config->flags & PAGE_ENABLED) - printk(", %d page-enabled SCBs.\n", config->maxscbs); - else - printk(", paging not enabled.\n"); + unsigned char brdctl; + brdctl = BRDCS | BRDSTB; + outb(brdctl, p->base + BRDCTL); + brdctl |= value; + outb(brdctl, p->base + BRDCTL); + brdctl &= ~BRDSTB; + outb(brdctl, p->base + BRDCTL); + brdctl &= ~BRDCS; + outb(brdctl, p->base + BRDCTL); } /*+F************************************************************************* * Function: - * aic7xxx_register + * read_brdctl * * Description: - * Register a Adaptec aic7xxx chip SCSI controller with the kernel. + * Reads the BRDCTL register. *-F*************************************************************************/ -static int -aic7xxx_register(Scsi_Host_Template *template, - struct aic7xxx_host_config *config) +static inline unsigned char +read_brdctl(struct aic7xxx_host *p) { - int i; - unsigned char sblkctl, flags = 0; - int max_targets; - int found = 1; - unsigned int sram, base; - unsigned char target_settings; - unsigned char scsi_conf, host_conf; - unsigned short ultraenable = 0; - int have_seeprom = FALSE; - struct Scsi_Host *host; - struct aic7xxx_host *p; - struct seeprom_config sc; - - base = config->base; + outb(BRDRW | BRDCS, p->base + BRDCTL); + return (inb(p->base + BRDCTL)); +} - /* - * Lock out other contenders for our i/o space. - */ - request_region(base, MAXREG - MINREG, "aic7xxx"); +/*+F************************************************************************* + * Function: + * configure_termination + * + * Description: + * Configures the termination settings on PCI adapters that have + * SEEPROMs available. + *-F*************************************************************************/ +static void +configure_termination(struct aic7xxx_host *p, unsigned char *sxfrctl1, + unsigned short adapter_control, unsigned char max_targ) +{ + unsigned char brdctl_int, brdctl_ext; + int internal50_present; + int internal68_present = 0; + int external_present = 0; + int eprom_present; + int high_on; + int low_on; + int old_verbose; + + if (acquire_seeprom(p)) + { + if (adapter_control & CFAUTOTERM) + { + old_verbose = aic7xxx_verbose; + printk(KERN_INFO "aic7xxx: Warning - detected auto-termination. Please " + "verify driver"); + printk(KERN_INFO " detected settings and use manual termination " + "if necessary."); - switch (config->type) - { - case AIC_7770: - case AIC_7771: - /* - * Use the boot-time option for the interrupt trigger type. If not - * supplied (-1), then we use BIOS settings to determine the interrupt - * trigger type (level or edge) and use this value for pausing and - * unpausing the sequencer. - */ - switch (aic7xxx_irq_trigger) - { - case 0: config->unpause = INTEN; /* Edge */ - break; - case 1: config->unpause = IRQMS | INTEN; /* Level */ - break; - case -1: - default: config->unpause = (inb(HCNTRL + base) & IRQMS) | INTEN; - break; - } - config->pause = config->unpause | PAUSE; + /* Configure auto termination. */ + outb(SEECS | SEEMS, p->base + SEECTL); /* - * For some 274x boards, we must clear the CHIPRST bit and pause - * the sequencer. For some reason, this makes the driver work. - * For 284x boards, we give it a CHIPRST just like the 294x boards. + * First read the status of our cables. Set the rom bank to + * 0 since the bank setting serves as a multiplexor for the + * cable detection logic. BRDDAT5 controls the bank switch. */ - outb(config->pause | CHIPRST, HCNTRL + base); - aic7xxx_delay(1); - if (inb(HCNTRL + base) & CHIPRST) - { - printk(KERN_INFO "aic7xxx: Chip reset not cleared; clearing manually.\n"); - } - outb(config->pause, HCNTRL + base); + write_brdctl(p, 0); /* - * Just to be on the safe side with the 274x, we will re-read the irq - * since there was some issue about resetting the board. + * Now read the state of the internal connectors. The + * bits BRDDAT6 and BRDDAT7 are 0 when cables are present + * set when cables are not present (BRDDAT6 is INT50 and + * BRDDAT7 is INT68). */ - config->irq = inb(INTDEF + base) & 0x0F; - if ((config->type == AIC_7771) && - (inb(HA_274_BIOSCTRL + base) & BIOSMODE) == BIOSDISABLED) - { - config->bios = AIC_DISABLED; - config->flags |= USE_DEFAULTS; - } - else + brdctl_int = read_brdctl(p); + internal50_present = (brdctl_int & BRDDAT6) ? 0 : 1; + if (max_targ > 8) { - host_conf = inb(HOSTCONF + base); - config->bus_speed = host_conf & DFTHRSH; - config->busrtime = (host_conf << 2) & BOFF; + internal68_present = (brdctl_int & BRDDAT7) ? 0 : 1; } /* - * Setup the FIFO threshold and the bus off time + * Set the rom bank to 1 and determine + * the other signals. */ - outb(config->bus_speed & DFTHRSH, BUSSPD + base); - outb(config->busrtime, BUSTIME + base); + write_brdctl(p, BRDDAT5); /* - * A reminder until this can be detected automatically. + * Now read the state of the external connectors. BRDDAT6 is + * 0 when an external cable is present, and BRDDAT7 (EPROMPS) is + * set when the eprom is present. */ - printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", - (config->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); - break; - - case AIC_284x: - outb(CHIPRST, HCNTRL + base); - config->unpause = UNPAUSE_284X; - config->pause = REQ_PAUSE; /* DWG would like to be like the rest */ - aic7xxx_delay(1); - outb(config->pause, HCNTRL + base); - - config->parity = AIC_ENABLED; - config->irq = inb(INTDEF + base) & 0x0F; - host_conf = inb(HOSTCONF + base); - - printk(KERN_INFO "aic7xxx: Reading SEEPROM..."); - have_seeprom = read_2840_seeprom(base, &sc); - if (!have_seeprom) - { - printk("aic7xxx: Unable to read SEEPROM.\n"); - } - else + brdctl_ext = read_brdctl(p); + external_present = (brdctl_ext & BRDDAT6) ? 0 : 1; + eprom_present = brdctl_ext & BRDDAT7; + if (aic7xxx_verbose) { - printk("done.\n"); - config->flags |= HAVE_SEEPROM; - if (sc.bios_control & CF284XEXTEND) - config->flags |= EXTENDED_TRANSLATION; - if (!(sc.bios_control & CFBIOSEN)) + if (max_targ > 8) { - /* - * The BIOS is disabled; the values left over in scratch - * RAM are still valid. Do not use defaults as in the - * AIC-7770 case. - */ - config->bios = AIC_DISABLED; + printk(KERN_INFO "aic7xxx: Cables present (Int-50 %s, Int-68 %s, " + "Ext-68 %s)\n", + internal50_present ? "YES" : "NO", + internal68_present ? "YES" : "NO", + external_present ? "YES" : "NO"); } else { - config->parity = (sc.adapter_control & CFSPARITY) ? - AIC_ENABLED : AIC_DISABLED; - config->low_term = (sc.adapter_control & CF284XSTERM) ? - AIC_ENABLED : AIC_DISABLED; - /* - * XXX - Adaptec *does* make 284x wide controllers, but the - * documents do not say where the high byte termination - * enable bit is located. - */ + printk(KERN_INFO "aic7xxx: Cables present (Int-50 %s, Ext-50 %s)\n", + internal50_present ? "YES" : "NO", + external_present ? "YES" : "NO"); } + printk(KERN_INFO "aic7xxx: eprom %s present, brdctl_int=0x%x, " + "brdctl_ext=0x%x\n", + eprom_present ? "is" : "not", brdctl_int, brdctl_ext); } - host_conf = inb(HOSTCONF + base); - config->bus_speed = host_conf & DFTHRSH; - config->busrtime = (host_conf << 2) & BOFF; - - /* - * Setup the FIFO threshold and the bus off time - */ - outb(config->bus_speed & DFTHRSH, BUSSPD + base); - outb(config->busrtime, BUSTIME + base); - - printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", - (config->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); - break; - - case AIC_7860: - case AIC_7861: - case AIC_7880: - case AIC_7881: - case AIC_7882: - case AIC_7883: - case AIC_7884: - /* - * Remember if Ultra was enabled in case there is no SEEPROM. - * Fall through to the rest of the AIC_78xx code. - */ - if ((inb(SXFRCTL0 + base) & ULTRAEN) || aic7xxx_enable_ultra) - config->flags |= ULTRA_ENABLED; - - case AIC_7850: - case AIC_7855: - case AIC_7870: - case AIC_7871: - case AIC_7872: - case AIC_7873: - case AIC_7874: /* - * Grab the SCSI ID before chip reset in case there is no SEEPROM. + * Now set the termination based on what we found. BRDDAT6 + * controls wide termination enable. */ - config->scsi_id = inb(SCSIID + base) & OID; - outb(CHIPRST, HCNTRL + base); - config->unpause = UNPAUSE_294X; - config->pause = config->unpause | PAUSE; - aic7xxx_delay(1); - outb(config->pause, HCNTRL + base); - - config->parity = AIC_ENABLED; + high_on = FALSE; + low_on = FALSE; + if ((max_targ > 8) && + ((external_present == 0) || (internal68_present == 0))) + { + high_on = TRUE; + } - printk(KERN_INFO "aic7xxx: Reading SEEPROM..."); - if ((config->type == AIC_7873) || (config->type == AIC_7883)) + if ((internal50_present + internal68_present + external_present) <= 1) { - have_seeprom = read_seeprom(base, config->chan_num * (sizeof(sc) / 2), - &sc, c56_66); + low_on = TRUE; } - else + + if (internal50_present && internal68_present && external_present) { - have_seeprom = read_seeprom(base, config->chan_num * (sizeof(sc) / 2), - &sc, c46); + printk(KERN_WARNING "aic7xxx: Illegal cable configuration!!\n" + " Only two connectors on the adapter may be " + "used at a time!\n"); } - if (!have_seeprom) + + if (high_on == TRUE) + write_brdctl(p, BRDDAT6); + else + write_brdctl(p, 0); + + if (low_on == TRUE) + *sxfrctl1 |= STPWEN; + + if (aic7xxx_verbose) { - for (sram = base + TARG_SCRATCH; sram < base + 0x60; sram++) - { - if (inb(sram) != 0x00) - break; - } - if (sram == base + TARG_SCRATCH) - { - for (sram = base + TARG_SCRATCH; sram < base + 0x60; sram++) - { - if (inb(sram) != 0xFF) - break; - } - } - if ((sram != base + 0x60) && (config->scsi_id != 0)) + if (max_targ > 8) { - config->flags &= ~USE_DEFAULTS; - printk("\naic7xxx: Unable to read SEEPROM; " - "using leftover BIOS values.\n"); + printk(KERN_INFO "aic7xxx: Termination (Low %s, High %s)\n", + low_on ? "ON" : "OFF", + high_on ? "ON" : "OFF"); } else { - printk("\n"); - printk(KERN_INFO "aic7xxx: Unable to read SEEPROM; using default " - "settings.\n"); - config->flags |= USE_DEFAULTS; - config->flags &= ~ULTRA_ENABLED; - config->scsi_id = 7; + printk(KERN_INFO "aic7xxx: Termination %s\n", low_on ? "ON" : "OFF"); } - scsi_conf = ENSPCHK | RESET_SCSI; + } + aic7xxx_verbose = old_verbose; + } + else + { + if (adapter_control & CFSTERM) + { + *sxfrctl1 |= STPWEN; + } + outb(SEEMS | SEECS, p->base + SEECTL); + /* + * Configure high byte termination. + */ + if (adapter_control & CFWSTERM) + { + write_brdctl(p, BRDDAT6); } else { - printk("done.\n"); - config->flags |= HAVE_SEEPROM; - if (!(sc.bios_control & CFBIOSEN)) - { - /* - * The BIOS is disabled; the values left over in scratch - * RAM are still valid. Do not use defaults as in the - * AIC-7770 case. - */ - config->bios = AIC_DISABLED; - scsi_conf = ENSPCHK | RESET_SCSI; - } - else - { - scsi_conf = 0; - if (sc.adapter_control & CFRESETB) - scsi_conf |= RESET_SCSI; - if (sc.adapter_control & CFSPARITY) - scsi_conf |= ENSPCHK; - if (sc.bios_control & CFEXTEND) - config->flags |= EXTENDED_TRANSLATION; - config->scsi_id = (sc.brtime_id & CFSCSIID); - config->parity = (sc.adapter_control & CFSPARITY) ? - AIC_ENABLED : AIC_DISABLED; - config->low_term = (sc.adapter_control & CFSTERM) ? - AIC_ENABLED : AIC_DISABLED; - config->high_term = (sc.adapter_control & CFWSTERM) ? - AIC_ENABLED : AIC_DISABLED; - config->busrtime = ((sc.brtime_id & CFBRTIME) >> 8); - if (((config->type == AIC_7880) || (config->type == AIC_7881) || - (config->type == AIC_7882) || (config->type == AIC_7883) || - (config->type == AIC_7884)) && (sc.adapter_control & CFULTRAEN)) - { - printk(KERN_INFO "aic7xxx: Enabling support for Ultra SCSI " - "speed.\n"); - config->flags |= ULTRA_ENABLED; - } - } + write_brdctl(p, 0); + } + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Termination (Low %s, High %s)\n", + (adapter_control & CFSTERM) ? "ON" : "OFF", + (adapter_control & CFWSTERM) ? "ON" : "OFF"); } + } + release_seeprom(p); + } +} + +/*+F************************************************************************* + * Function: + * detect_maxscb + * + * Description: + * Detects the maximum number of SCBs for the controller and returns + * the count and a mask in p (p->maxscbs, p->qcntmask). + *-F*************************************************************************/ +static void +detect_maxscb(struct aic7xxx_host *p) +{ + int i; + unsigned char max_scbid = 255; - outb(scsi_conf | (config->scsi_id & 0x07), SCSICONF + base); - config->bus_speed = DFTHRSH_100; - outb(config->bus_speed, DSPCISTATUS + base); + /* + * It's possible that we've already done this for multichannel + * adapters. + */ + if (p->scb_data->maxhscbs == 0) + { + /* + * We haven't initialized the SCB settings yet. Walk the SCBs to + * determince how many there are. + */ + outb(0, p->base + FREE_SCBH); - /* - * In case we are a wide card... - */ - outb(config->scsi_id, SCSICONF + base + 1); + for (i = 0; i < AIC7XXX_MAXSCB; i++) + { + outb(i, p->base + SCBPTR); + outb(i, p->base + SCB_CONTROL); + if (inb(p->base + SCB_CONTROL) != i) + break; + outb(0, p->base + SCBPTR); + if (inb(p->base + SCB_CONTROL) != 0) + break; - printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", - (config->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); - break; + outb(i, p->base + SCBPTR); + outb(0, p->base + SCB_CONTROL); /* Clear the control byte. */ + outb(i + 1, p->base + SCB_NEXT); /* Set the next pointer. */ + outb(SCB_LIST_NULL, p->base + SCB_TAG); /* Make the tag invalid. */ - default: - panic(KERN_WARNING "aic7xxx: (aic7xxx_register) Internal error.\n"); + /* Make the non-tagged targets not busy. */ + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS); + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS + 1); + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS + 2); + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS + 3); + } + + /* Make sure the last SCB terminates the free list. */ + outb(i - 1, p->base + SCBPTR); + outb(SCB_LIST_NULL, p->base + SCB_NEXT); + + /* Ensure we clear the first (0) SCBs control byte. */ + outb(0, p->base + SCBPTR); + outb(0, p->base + SCB_CONTROL); + + p->scb_data->maxhscbs = i; } - detect_maxscb(config); + if ((p->flags & PAGE_ENABLED) && (p->scb_data->maxhscbs < AIC7XXX_MAXSCB)) + { + /* Determine the number of valid bits in the FIFOs. */ + outb(max_scbid, p->base + QINFIFO); + max_scbid = inb(p->base + QINFIFO); + p->scb_data->maxscbs = MIN(AIC7XXX_MAXSCB, max_scbid + 1); + } + else + { + p->scb_data->maxscbs = p->scb_data->maxhscbs; + } + if (p->scb_data->maxscbs == p->scb_data->maxhscbs) + { + /* + * Disable paging if the QINFIFO doesn't allow more SCBs than + * we have in hardware. + */ + p->flags &= ~PAGE_ENABLED; + } - if (config->chip_type == AIC_777x) + /* + * Set the Queue Full Count. Some cards have more queue space than + * SCBs. + */ + switch (p->chip_class) { - if (config->pause & IRQMS) - { - printk(KERN_INFO "aic7xxx: Using level sensitive interrupts.\n"); - } - else - { - printk(KERN_INFO "aic7xxx: Using edge triggered interrupts.\n"); - } + case AIC_777x: + p->qfullcount = 4; + p->qcntmask = 0x07; + break; + case AIC_785x: + case AIC_786x: + p->qfullcount = 8; + p->qcntmask = 0x0f; + break; + case AIC_787x: + case AIC_788x: + if (p->scb_data->maxhscbs == AIC7XXX_MAXSCB) + { + p->qfullcount = AIC7XXX_MAXSCB; + p->qcntmask = 0xFF; + } + else + { + p->qfullcount = 16; + p->qcntmask = 0x1F; + } + break; } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_register + * + * Description: + * Register a Adaptec aic7xxx chip SCSI controller with the kernel. + *-F*************************************************************************/ +static int +aic7xxx_register(Scsi_Host_Template *template, struct aic7xxx_host *p) +{ + int i; + unsigned char sblkctl, flags = 0; + int max_targets; + int found = 1; + char channel_ids[] = {'A', 'B', 'C'}; + unsigned char target_settings; + unsigned char scsi_conf, sxfrctl1; + unsigned short ultraenable = 0; + struct Scsi_Host *host; + + /* + * Lock out other contenders for our i/o space. + */ + request_region(p->base, MAXREG - MINREG, "aic7xxx"); /* * Read the bus type from the SBLKCTL register. Set the FLAGS * register in the sequencer for twin and wide bus cards. */ - sblkctl = inb(SBLKCTL + base); - if (config->flags & PAGE_ENABLED) + sblkctl = inb(p->base + SBLKCTL); + if (p->flags & PAGE_ENABLED) flags = PAGESCBS; switch (sblkctl & SELBUS_MASK) { case SELNARROW: /* narrow/normal bus */ - config->scsi_id = inb(SCSICONF + base) & 0x07; - config->bus_type = AIC_SINGLE; - outb(flags | SINGLE_BUS, FLAGS + base); + p->scsi_id = inb(p->base + SCSICONF) & 0x07; + p->bus_type = AIC_SINGLE; + p->flags &= ~FLAGS_CHANNEL_B_PRIMARY; + if (p->flags & MULTI_CHANNEL) + { + printk(KERN_INFO "aic7xxx: Channel %c, SCSI ID %d, ", + channel_ids[p->chan_num], p->scsi_id); + } + else + { + printk (KERN_INFO "aic7xxx: Single Channel, SCSI ID %d, ", + p->scsi_id); + } + outb(flags | SINGLE_BUS, p->base + SEQ_FLAGS); break; case SELWIDE: /* Wide bus */ - config->scsi_id = inb(SCSICONF + base + 1) & 0x0F; - config->bus_type = AIC_WIDE; - printk("aic7xxx: Enabling wide channel of %s-Wide.\n", - board_names[config->type]); - outb(flags | WIDE_BUS, FLAGS + base); - break; - - case SELBUSB: /* Twin bus */ - config->scsi_id = inb(SCSICONF + base) & 0x07; -#ifdef AIC7XXX_TWIN_SUPPORT - config->scsi_id_b = inb(SCSICONF + base + 1) & 0x07; - config->bus_type = AIC_TWIN; - printk(KERN_INFO "aic7xxx: Enabled channel B of %s-Twin.\n", - board_names[config->type]); - outb(flags | TWIN_BUS, FLAGS + base); -#else - config->bus_type = AIC_SINGLE; - printk(KERN_INFO "aic7xxx: Channel B of %s-Twin will be ignored.\n", - board_names[config->type]); - outb(flags, FLAGS + base); -#endif + p->scsi_id = inb(p->base + SCSICONF + 1) & HWSCSIID; + p->bus_type = AIC_WIDE; + p->flags &= ~FLAGS_CHANNEL_B_PRIMARY; + if (p->flags & MULTI_CHANNEL) + { + printk(KERN_INFO "aic7xxx: Wide Channel %c, SCSI ID %d, ", + channel_ids[p->chan_num], p->scsi_id); + } + else + { + printk (KERN_INFO "aic7xxx: Wide Channel, SCSI ID %d, ", + p->scsi_id); + } + outb(flags | WIDE_BUS, p->base + SEQ_FLAGS); break; - default: - printk(KERN_WARNING "aic7xxx: Unsupported type 0x%x, please " - "mail deang@teleport.com\n", inb(SBLKCTL + base)); - outb(0, FLAGS + base); - return (0); - } - - /* - * For the 294x cards, clearing DIAGLEDEN and DIAGLEDON, will - * take the card out of diagnostic mode and make the host adapter - * LED follow bus activity (will not always be on). - */ - outb(sblkctl & ~(DIAGLEDEN | DIAGLEDON), SBLKCTL + base); - - /* - * The IRQ level in i/o port 4 maps directly onto the real - * IRQ number. If it's ok, register it with the kernel. - * - * NB. the Adaptec documentation says the IRQ number is only - * in the lower four bits; the ECU information shows the - * high bit being used as well. Which is correct? - * - * The PCI cards get their interrupt from PCI BIOS. - */ - if ((config->chip_type == AIC_777x) && ((config->irq < 9) || (config->irq > 15))) - { - printk(KERN_WARNING "aic7xxx: Host adapter uses unsupported IRQ level, " - "ignoring.\n"); - return (0); + case SELBUSB: /* Twin bus */ + p->scsi_id = inb(p->base + SCSICONF) & HSCSIID; + p->scsi_id_b = inb(p->base + SCSICONF + 1) & HSCSIID; + p->bus_type = AIC_TWIN; + printk(KERN_INFO "aic7xxx: Twin Channel, A SCSI ID %d, B SCSI ID %d, ", + p->scsi_id, p->scsi_id_b); + outb(flags | TWIN_BUS, p->base + SEQ_FLAGS); + break; + + default: + printk(KERN_WARNING "aic7xxx: Unsupported type 0x%x, please " + "mail deang@teleport.com\n", inb(p->base + SBLKCTL)); + outb(0, p->base + SEQ_FLAGS); + return (0); } /* - * Print out debugging information before re-enabling - * the card - a lot of registers on it can't be read - * when the sequencer is active. - */ - debug_config(config); - - /* - * Register each "host" and fill in the returned Scsi_Host - * structure as best we can. Some of the parameters aren't - * really relevant for bus types beyond ISA, and none of the - * high-level SCSI code looks at it anyway. Why are the fields - * there? Also save the pointer so that we can find the - * information when an IRQ is triggered. + * Detect SCB parameters and initialize the SCB array. */ - host = scsi_register(template, sizeof(struct aic7xxx_host)); - host->can_queue = config->maxscbs; + detect_maxscb(p); + printk("%d/%d SCBs, QFull %d, QMask 0x%x\n", + p->scb_data->maxhscbs, p->scb_data->maxscbs, + p->qfullcount, p->qcntmask); + + host = p->host; + + host->can_queue = p->scb_data->maxscbs; host->cmd_per_lun = 2; + host->sg_tablesize = AIC7XXX_MAX_SG; host->select_queue_depths = aic7xxx_select_queue_depth; - host->this_id = config->scsi_id; - host->io_port = config->base; + host->this_id = p->scsi_id; + host->io_port = p->base; host->n_io_port = 0xFF; - host->base = (unsigned char *)config->mbase; - host->irq = config->irq; - if (config->bus_type == AIC_WIDE) + host->base = (unsigned char *) p->mbase; + host->irq = p->irq; + if (p->bus_type == AIC_WIDE) { host->max_id = 16; } - if (config->bus_type == AIC_TWIN) + if (p->bus_type == AIC_TWIN) { host->max_channel = 1; } - p = (struct aic7xxx_host *) host->hostdata; - p->host = host; - p->host_no = (int)host->host_no; + p->host_no = host->host_no; p->isr_count = 0; - p->base = base; - p->maxscbs = config->maxscbs; - p->maxhscbs = config->maxhscbs; - p->qcntmask = config->qcntmask; - p->mbase = (char *)config->mbase; - p->type = config->type; - p->chip_type = config->chip_type; - p->flags = config->flags; - p->chan_num = config->chan_num; - p->scb_link = &(p->scb_usage); -#if defined(CONFIG_PCI) && defined(AIC7XXX_SHARE_SCBS) - if ((p->chan_num == 0) && ((p->type == AIC_7873) | (p->type == AIC_7883))) - { - shared_3985_scbs = &(p->scb_usage); - p->scb_link = &(p->scb_usage); - } -#endif - p->scb_link->numscbs = 0; - p->bus_type = config->bus_type; - p->seeprom = sc; p->next = NULL; p->completeq.head = NULL; p->completeq.tail = NULL; - scbq_init(&p->scb_link->free_scbs); - scbq_init(&p->page_scbs); + scbq_init(&p->scb_data->free_scbs); scbq_init(&p->waiting_scbs); - scbq_init(&p->assigned_scbs); - p->unpause = config->unpause; - p->pause = config->pause; - - for (i = 0; i <= 15; i++) + for (i = 0; i <= NUMBER(p->device_status); i++) { p->device_status[i].commands_sent = 0; p->device_status[i].flags = 0; + p->device_status[i].active_cmds = 0; p->device_status[i].last_reset = 0; } - if (aic7xxx_boards[config->irq] == NULL) + if (aic7xxx_boards[p->irq] == NULL) { + int result; + int irq_flags = 0; + +#ifdef AIC7XXX_OLD_ISR_TYPE + irg_flags = SA_INTERRUPT; +#endif /* * Warning! This must be done before requesting the irq. It is * possible for some boards to raise an interrupt as soon as @@ -4352,17 +5030,26 @@ * kernel, an interrupt is triggered immediately. Therefore, we * must ensure the board data is correctly set before the request. */ - aic7xxx_boards[config->irq] = host; + aic7xxx_boards[p->irq] = host; /* - * Register IRQ with the kernel. + * Register IRQ with the kernel. Only allow sharing IRQs with + * PCI devices. */ - if (request_irq(config->irq, aic7xxx_isr, SA_INTERRUPT | SA_SHIRQ, - "aic7xxx", NULL)) + if (p->chip_class == AIC_777x) + { + result = (request_irq(p->irq, aic7xxx_isr, irq_flags, "aic7xxx", NULL)); + } + else + { + result = (request_irq(p->irq, aic7xxx_isr, irq_flags | SA_SHIRQ, + "aic7xxx", NULL)); + } + if (result < 0) { printk(KERN_WARNING "aic7xxx: Couldn't register IRQ %d, ignoring.\n", - config->irq); - aic7xxx_boards[config->irq] = NULL; + p->irq); + aic7xxx_boards[p->irq] = NULL; return (0); } } @@ -4373,79 +5060,74 @@ * registered host adapter. Add this host adapter's Scsi_Host * to the beginning of the linked list of hosts at the same IRQ. */ - p->next = aic7xxx_boards[config->irq]; - aic7xxx_boards[config->irq] = host; - } - - /* - * Load the sequencer program, then re-enable the board - - * resetting the AIC-7770 disables it, leaving the lights - * on with nobody home. On the PCI bus you *may* be home, - * but then your mailing address is dynamically assigned - * so no one can find you anyway :-) - */ - printk(KERN_INFO "aic7xxx: Downloading sequencer code..."); - aic7xxx_loadseq(base); - - /* - * Set Fast Mode and Enable the board - */ - outb(FASTMODE, SEQCTL + base); - - if (p->chip_type == AIC_777x) - { - outb(ENABLE, BCTL + base); + p->next = aic7xxx_boards[p->irq]; + aic7xxx_boards[p->irq] = host; } - printk("done.\n"); - /* * Set the SCSI Id, SXFRCTL0, SXFRCTL1, and SIMODE1, for both channels */ if (p->bus_type == AIC_TWIN) { /* - * Select Channel B. + * The controller is gated to channel B after a chip reset; set + * bus B values first. */ - outb((sblkctl & ~SELBUS_MASK) | SELBUSB, SBLKCTL + base); - - outb(config->scsi_id_b, SCSIID + base); - scsi_conf = inb(SCSICONF + base + 1) & (ENSPCHK | STIMESEL); - outb(scsi_conf | ENSTIMER | ACTNEGEN | STPWEN, SXFRCTL1 + base); -#if EXPERIMENTAL_FLAGS - outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, SIMODE1 + base); -#else - outb(ENSELTIMO, SIMODE1 + base); -#endif + outb(p->scsi_id_b, p->base + SCSIID); + scsi_conf = inb(p->base + SCSICONF + 1); + sxfrctl1 = inb(p->base + SXFRCTL1); + outb((scsi_conf & (ENSPCHK | STIMESEL)) | (sxfrctl1 & STPWEN) | + ENSTIMER | ACTNEGEN, p->base + SXFRCTL1); + outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, p->base + SIMODE1); if (p->flags & ULTRA_ENABLED) { - outb(DFON | SPIOEN | ULTRAEN, SXFRCTL0 + base); + outb(DFON | SPIOEN | FAST20, p->base + SXFRCTL0); } else { - outb(DFON | SPIOEN, SXFRCTL0 + base); + outb(DFON | SPIOEN, p->base + SXFRCTL0); } - /* - * Select Channel A - */ - outb((sblkctl & ~SELBUS_MASK) | SELNARROW, SBLKCTL + base); + if ((scsi_conf & RESET_SCSI) && (aic7xxx_no_reset == 0)) + { + /* Reset SCSI bus B. */ + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Resetting channel B\n"); + + aic7xxx_reset_current_bus(p); + } + + /* Select channel A */ + outb(SELNARROW, p->base + SBLKCTL); } - outb(config->scsi_id, SCSIID + base); - scsi_conf = inb(SCSICONF + base) & (ENSPCHK | STIMESEL); - outb(scsi_conf | ENSTIMER | ACTNEGEN | STPWEN, SXFRCTL1 + base); -#if EXPERIMENTAL_FLAGS - outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, SIMODE1 + base); -#else - outb(ENSELTIMO, SIMODE1 + base); -#endif + + outb(p->scsi_id, p->base + SCSIID); + scsi_conf = inb(p->base + SCSICONF); + sxfrctl1 = inb(p->base + SXFRCTL1); + outb((scsi_conf & (ENSPCHK | STIMESEL)) | (sxfrctl1 & STPWEN) | + ENSTIMER | ACTNEGEN, p->base + SXFRCTL1); + outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, p->base + SIMODE1); if (p->flags & ULTRA_ENABLED) { - outb(DFON | SPIOEN | ULTRAEN, SXFRCTL0 + base); + outb(DFON | SPIOEN | FAST20, p->base + SXFRCTL0); } else { - outb(DFON | SPIOEN, SXFRCTL0 + base); + outb(DFON | SPIOEN, p->base + SXFRCTL0); + } + + if ((scsi_conf & RESET_SCSI) && (aic7xxx_no_reset == 0)) + { + /* Reset SCSI bus A. */ + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Resetting channel A\n"); + + aic7xxx_reset_current_bus(p); + + /* + * Delay for the reset delay. + */ + aic7xxx_delay(AIC7XXX_RESET_DELAY); } /* @@ -4472,67 +5154,47 @@ /* * Grab the disconnection disable table and invert it for our needs */ - if (have_seeprom) + if (p->flags & USE_DEFAULTS) { - p->discenable = 0x0; + printk(KERN_INFO "aic7xxx: Host adapter BIOS disabled. Using default SCSI " + "device parameters.\n"); + p->discenable = 0xFFFF; } else { - if (config->bios == AIC_DISABLED) - { - printk(KERN_INFO "aic7xxx : Host adapter BIOS disabled. Using default SCSI " - "device parameters.\n"); - p->discenable = 0xFFFF; - } - else - { - p->discenable = ~((inb(DISC_DSB + base + 1) << 8) | - inb(DISC_DSB + base)); - } + p->discenable = ~((inb(p->base + DISC_DSB + 1) << 8) | + inb(p->base + DISC_DSB)); } for (i = 0; i < max_targets; i++) { - if (config->flags & USE_DEFAULTS) + if (p->flags & USE_DEFAULTS) { - target_settings = 0; /* 10 MHz */ + target_settings = 0; /* 10 or 20 MHz depending on Ultra enable */ p->needsdtr_copy |= (0x01 << i); p->needwdtr_copy |= (0x01 << i); + if ((p->chip_class == AIC_786x) || (p->chip_class == AIC_788x)) + ultraenable |= (0x01 << i); } else { - if (have_seeprom) + target_settings = inb(p->base + TARG_SCRATCH + i); + if (target_settings & 0x0F) { - target_settings = ((sc.device_flags[i] & CFXFER) << 4); - if (sc.device_flags[i] & CFSYNCH) - { - p->needsdtr_copy |= (0x01 << i); - } - if (sc.device_flags[i] & CFWIDEB) - { - p->needwdtr_copy |= (0x01 << i); - } - if (sc.device_flags[i] & CFDISC) - { - p->discenable |= (0x01 << i); - } + p->needsdtr_copy |= (0x01 << i); + /* + * Default to asynchronous transfers (0 offset) + */ + target_settings &= 0xF0; } - else + if (target_settings & 0x80) { - target_settings = inb(TARG_SCRATCH + base + i); - if (target_settings & 0x0F) - { - p->needsdtr_copy |= (0x01 << i); - /* - * Default to asynchronous transfers (0 offset) - */ - target_settings &= 0xF0; - } - if (target_settings & 0x80) - { - p->needwdtr_copy |= (0x01 << i); - target_settings &= 0x7F; - } + p->needwdtr_copy |= (0x01 << i); + /* + * Clear the wide flag. When wide negotiation is successful, + * we'll enable it. + */ + target_settings &= 0x7F; } if (p->flags & ULTRA_ENABLED) { @@ -4543,126 +5205,471 @@ case 0x20: ultraenable |= (0x01 << i); break; - case 0x40: + case 0x40: /* treat 10MHz as 10MHz without Ultra enabled */ target_settings &= ~(0x70); break; default: break; } } - } - outb(target_settings, (TARG_SCRATCH + base + i)); + } + outb(target_settings, p->base + TARG_SCRATCH + i); + } + + /* + * If we are not wide, forget WDTR. This makes the driver + * work on some cards that don't leave these fields cleared + * when BIOS is not installed. + */ + if (p->bus_type != AIC_WIDE) + { + p->needwdtr_copy = 0; + } + p->needsdtr = p->needsdtr_copy; + p->needwdtr = p->needwdtr_copy; + p->orderedtag = 0; + outb(ultraenable & 0xFF, p->base + ULTRA_ENB); + outb((ultraenable >> 8) & 0xFF, p->base + ULTRA_ENB + 1); + + /* + * Set the number of available hardware SCBs. + */ + outb(p->scb_data->maxhscbs, p->base + SCBCOUNT); + + /* + * 2s compliment of maximum tag value. + */ + i = p->scb_data->maxscbs; + outb(-i & 0xFF, p->base + COMP_SCBCOUNT); + + /* + * Allocate enough hardware scbs to handle the maximum number of + * concurrent transactions we can have. We have to make sure that + * the allocated memory is contiguous memory. The Linux kmalloc + * routine should only allocate contiguous memory, but note that + * this could be a problem if kmalloc() is changed. + */ + if (p->scb_data->hscbs == NULL) + { + size_t array_size; + unsigned int hscb_physaddr; + + array_size = p->scb_data->maxscbs * sizeof(struct aic7xxx_hwscb); + p->scb_data->hscbs = kmalloc(array_size, GFP_ATOMIC); + if (p->scb_data->hscbs == NULL) + { + printk("aic7xxx: Unable to allocate hardware SCB array; " + "failing detection.\n"); + release_region(p->base, MAXREG - MINREG); + /* + * Ensure that we only free the IRQ when there is _not_ another + * aic7xxx adapter sharing this IRQ. The adapters are always + * added to the beginning of the list, so we can grab the next + * pointer and place it back in the board array. + */ + if (p->next == NULL) + { + free_irq(p->irq, aic7xxx_isr); + } + aic7xxx_boards[p->irq] = p->next; + return(0); + } + /* At least the control byte of each SCB needs to be 0. */ + memset(p->scb_data->hscbs, 0, array_size); + + /* Tell the sequencer where it can find the hardware SCB array. */ + hscb_physaddr = VIRT_TO_BUS(p->scb_data->hscbs); + outb(hscb_physaddr & 0xFF, p->base + HSCB_ADDR); + outb((hscb_physaddr >> 8) & 0xFF, p->base + HSCB_ADDR + 1); + outb((hscb_physaddr >> 16) & 0xFF, p->base + HSCB_ADDR + 2); + outb((hscb_physaddr >> 24) & 0xFF, p->base + HSCB_ADDR + 3); + } + + /* + * QCount mask to deal with broken aic7850s that sporadically get + * garbage in the upper bits of their QCNT registers. + */ + outb(p->qcntmask, p->base + QCNTMASK); + + /* + * We don't have any waiting selections or disconnected SCBs. + */ + outb(SCB_LIST_NULL, p->base + WAITING_SCBH); + outb(SCB_LIST_NULL, p->base + DISCONNECTED_SCBH); + + /* + * Message out buffer starts empty + */ + outb(0, p->base + MSG_LEN); + + /* + * Load the sequencer program, then re-enable the board - + * resetting the AIC-7770 disables it, leaving the lights + * on with nobody home. On the PCI bus you *may* be home, + * but then your mailing address is dynamically assigned + * so no one can find you anyway :-) + */ + aic7xxx_loadseq(p); + + if (p->chip_class == AIC_777x) + { + outb(ENABLE, p->base + BCTL); /* Enable the boards BUS drivers. */ + } + + /* + * Unpause the sequencer before returning and enable + * interrupts - we shouldn't get any until the first + * command is sent to us by the high-level SCSI code. + */ + unpause_sequencer(p, /* unpause_always */ TRUE); + + return (found); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_chip_reset + * + * Description: + * Perform a chip reset on the aic7xxx SCSI controller. The controller + * is paused upon return. + *-F*************************************************************************/ +static void +aic7xxx_chip_reset(struct aic7xxx_host *p) +{ + unsigned char hcntrl; + int wait; + + /* Retain the IRQ type across the chip reset. */ + hcntrl = (inb(p->base + HCNTRL) & IRQMS) | INTEN; + + /* + * For some 274x boards, we must clear the CHIPRST bit and pause + * the sequencer. For some reason, this makes the driver work. + */ + outb(PAUSE | CHIPRST, p->base + HCNTRL); + + /* + * In the future, we may call this function as a last resort for + * error handling. Let's be nice and not do any unecessary delays. + */ + wait = 1000; /* 1 second (1000 * 1000 usec) */ + while ((wait > 0) && ((inb(p->base + HCNTRL) & CHIPRSTACK) == 0)) + { + udelay(1000); /* 1 msec = 1000 usec */ + wait = wait - 1; + } + + if ((inb(p->base + HCNTRL) & CHIPRSTACK) == 0) + { + printk(KERN_INFO "aic7xxx: Chip reset not cleared; clearing manually.\n"); + } + + outb(hcntrl | PAUSE, p->base + HCNTRL); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_alloc + * + * Description: + * Allocate and initialize a host structure. Returns NULL upon error + * and a pointer to a aic7xxx_host struct upon success. + *-F*************************************************************************/ +static struct aic7xxx_host * +aic7xxx_alloc(Scsi_Host_Template *sht, unsigned int base, unsigned int mbase, + aha_chip_type chip_type, int flags, scb_data_type *scb_data) +{ + struct aic7xxx_host *p = NULL; + struct Scsi_Host *host; + + /* + * Allocate a storage area by registering us with the mid-level + * SCSI layer. + */ + host = scsi_register(sht, sizeof(struct aic7xxx_host)); + + if (host != NULL) + { + p = (struct aic7xxx_host *) host->hostdata; + memset(p, 0, sizeof(struct aic7xxx_host)); + p->host = host; + + if (scb_data != NULL) + { + /* + * We are sharing SCB data areas; use the SCB data pointer + * provided. + */ + p->scb_data = scb_data; + p->flags |= SHARED_SCBDATA; + } + else + { + /* + * We are not sharing SCB data; allocate one. + */ + p->scb_data = kmalloc(sizeof(scb_data_type), GFP_ATOMIC); + if (p->scb_data != NULL) + { + memset(p->scb_data, 0, sizeof(scb_data_type)); + scbq_init (&p->scb_data->free_scbs); + } + else + { + /* + * For some reason we don't have enough memory. Free the + * allocated memory for the aic7xxx_host struct, and return NULL. + */ + scsi_unregister(host); + p = NULL; + } + } + if (p != NULL) + { + p->host_no = host->host_no; + p->base = base; + p->mbase = mbase; + p->maddr = NULL; + p->flags = flags; + p->chip_type = chip_type; + p->unpause = (inb(p->base + HCNTRL) & IRQMS) | INTEN; + p->pause = p->unpause | PAUSE; + } + } + return (p); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_free + * + * Description: + * Frees and releases all resources associated with an instance of + * the driver (struct aic7xxx_host *). + *-F*************************************************************************/ +static void +aic7xxx_free (struct aic7xxx_host *p) +{ + int i; + + /* + * We should be careful in freeing the scb_data area. For those + * adapters sharing external SCB RAM(398x), there will be only one + * scb_data area allocated. The flag SHARED_SCBDATA indicates if + * one adapter is sharing anothers SCB RAM. + */ + if (!(p->flags & SHARED_SCBDATA)) + { + /* + * Free the allocated hardware SCB space. + */ + if (p->scb_data->hscbs != NULL) + { + kfree(p->scb_data->hscbs); + } + /* + * Free the driver SCBs. These were allocated on an as-need + * basis. + */ + for (i = 0; i < p->scb_data->numscbs; i++) + { + kfree(p->scb_data->scb_array[i]); + } + /* + * Free the hardware SCBs. + */ + if (p->scb_data->hscbs != NULL) + { + kfree(p->scb_data->hscbs); + } + + /* + * Free the SCB data area. + */ + kfree(p->scb_data); + } + /* + * Free the instance of the device structure. + */ + scsi_unregister(p->host); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_load_seeprom + * + * Description: + * Load the seeprom and configure adapter and target settings. + * Returns 1 if the load was successful and 0 otherwise. + *-F*************************************************************************/ +static int +load_seeprom (struct aic7xxx_host *p, unsigned char *sxfrctl1) +{ + int have_seeprom = 0; + int i, max_targets; + unsigned char target_settings, scsi_conf; + unsigned short scarray[128]; + struct seeprom_config *sc = (struct seeprom_config *) scarray; + + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Loading serial EEPROM..."); + } + switch (p->chip_type) + { + case AIC_7770: /* None of these adapters have seeproms. */ + case AIC_7771: + case AIC_7850: + case AIC_7855: + break; + + case AIC_284x: + have_seeprom = read_284x_seeprom(p, (struct seeprom_config *) scarray); + break; + + case AIC_7861: + case AIC_7870: + case AIC_7871: + case AIC_7872: + case AIC_7874: + case AIC_7881: + case AIC_7882: + case AIC_7884: + have_seeprom = read_seeprom(p, p->chan_num * (sizeof(*sc)/2), + scarray, sizeof(*sc)/2, C46); + break; + + case AIC_7860: /* Motherboard Ultra controllers might have RAID port. */ + case AIC_7880: + have_seeprom = read_seeprom(p, 0, scarray, sizeof(*sc)/2, C46); + if (!have_seeprom) + { + have_seeprom = read_seeprom(p, 0, scarray, sizeof(scarray)/2, C56_66); + } + break; + + case AIC_7873: /* The 3985 adapters use the 93c56 serial EEPROM. */ + case AIC_7883: + have_seeprom = read_seeprom(p, p->chan_num * (sizeof(*sc)/2), + scarray, sizeof(scarray)/2, C56_66); + break; + + default: + break; } - /* - * If we are not wide, forget WDTR. This makes the driver - * work on some cards that don't leave these fields cleared - * when BIOS is not installed. - */ - if (p->bus_type != AIC_WIDE) + if (!have_seeprom) { - p->needwdtr_copy = 0; + if (aic7xxx_verbose) + { + printk("\naic7xxx: No SEEPROM available; using defaults.\n"); + } + p->flags |= USE_DEFAULTS; } - p->needsdtr = p->needsdtr_copy; - p->needwdtr = p->needwdtr_copy; - p->orderedtag = 0; -#if 0 - printk("NeedSdtr = 0x%x, 0x%x\n", p->needsdtr_copy, p->needsdtr); - printk("NeedWdtr = 0x%x, 0x%x\n", p->needwdtr_copy, p->needwdtr); -#endif - outb(ultraenable & 0xFF, ULTRA_ENB + base); - outb((ultraenable >> 8) & 0xFF, ULTRA_ENB + base + 1); + else + { + if (aic7xxx_verbose) + { + printk("done\n"); + } + p->flags |= HAVE_SEEPROM; - /* - * Set the number of available SCBs. - */ - outb(config->maxhscbs, SCBCOUNT + base); + /* + * Update the settings in sxfrctl1 to match the termination settings. + */ + *sxfrctl1 = 0; - /* - * 2s compliment of maximum tag value. - */ - i = p->maxscbs; - outb(-i & 0xFF, COMP_SCBCOUNT + base); + /* + * First process the settings that are different between the VLB + * and PCI adapter seeproms. + */ + if (p->chip_class == AIC_777x) + { + /* VLB adapter seeproms */ + if (sc->bios_control & CF284XEXTEND) + p->flags |= EXTENDED_TRANSLATION; - /* - * Set the QCNT (queue count) mask to deal with broken aic7850s that - * sporatically get garbage in the upper bits of their QCNT registers. - */ - outb(config->qcntmask, QCNTMASK + base); + if (sc->adapter_control & CF284XSTERM) + *sxfrctl1 |= STPWEN; + /* + * The 284x SEEPROM doesn't have a max targets field. We + * set it to 16 to make sure we take care of the 284x-wide + * adapters. For narrow adapters, going through the extra + * 8 target entries will not cause any harm since they will + * will not be used. + * + * XXX - We should probably break out the bus detection + * from the register function so we can use it here + * to tell us how many targets there really are. + */ + max_targets = 16; + } + else + { + /* PCI adapter seeproms */ + if (sc->bios_control & CFEXTEND) + p->flags |= EXTENDED_TRANSLATION; - /* - * Clear the active flags - no targets are busy. - */ - outb(0, ACTIVE_A + base); - outb(0, ACTIVE_B + base); + if (sc->adapter_control & CFSTERM) + *sxfrctl1 |= STPWEN; - /* - * We don't have any waiting selections or disconnected SCBs. - */ - outb(SCB_LIST_NULL, WAITING_SCBH + base); - outb(SCB_LIST_NULL, DISCONNECTED_SCBH + base); + /* Limit to 16 targets just in case. */ + max_targets = MIN(sc->max_targets & CFMAXTARG, 16); + } - /* - * Message out buffer starts empty - */ - outb(0, MSG_LEN + base); + for (i = 0; i < max_targets; i++) + { + target_settings = (sc->device_flags[i] & CFXFER) << 4; + if (sc->device_flags[i] & CFSYNCH) + target_settings |= SOFS; + if (sc->device_flags[i] & CFWIDEB) + target_settings |= WIDEXFER; + if (sc->device_flags[i] & CFDISC) + p->discenable |= (0x01 << i); + outb(target_settings, p->base + TARG_SCRATCH + i); + } + outb(~(p->discenable & 0xFF), p->base + DISC_DSB); + outb(~((p->discenable >> 8) & 0xFF), p->base + DISC_DSB + 1); - /* - * Reset the SCSI bus. Is this necessary? - * There may be problems for a warm boot without resetting - * the SCSI bus. Either BIOS settings in scratch RAM - * will not get reinitialized, or devices may stay at - * previous negotiated settings (SDTR and WDTR) while - * the driver will think that no negotiations have been - * performed. - * - * Some devices need a long time to "settle" after a SCSI - * bus reset. - */ - if (!aic7xxx_no_reset) - { - printk("aic7xxx: Resetting the SCSI bus..."); - if (p->bus_type == AIC_TWIN) + p->scsi_id = sc->brtime_id & CFSCSIID; + + scsi_conf = (p->scsi_id & 0x7); + if (sc->adapter_control & CFSPARITY) + scsi_conf |= ENSPCHK; + if (sc->adapter_control & CFRESETB) + scsi_conf |= RESET_SCSI; + + if ((p->chip_class == AIC_786x) || (p->chip_class == AIC_788x)) { /* - * Select Channel B. + * We allow the operator to override ultra enable through + * the boot prompt. */ - outb((sblkctl & ~SELBUS_MASK) | SELBUSB, SBLKCTL + base); + if (!(sc->adapter_control & CFULTRAEN) && (aic7xxx_enable_ultra == 0)) + { + /* Treat us as a non-ultra card */ + p->flags &= ~ULTRA_ENABLED; + } + } - outb(SCSIRSTO, SCSISEQ + base); - udelay(1000); - outb(0, SCSISEQ + base); - - /* Ensure we don't get a RSTI interrupt from this. */ - outb(CLRSCSIRSTI, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); + /* Set the host ID */ + outb(scsi_conf, p->base + SCSICONF); + /* In case we are a wide card */ + outb(p->scsi_id, p->base + SCSICONF + 1); - /* - * Select Channel A. + if (p->chip_class != AIC_777x) + { + /* + * Update the settings in sxfrctl1 to match the termination + * settings. */ - outb((sblkctl & ~SELBUS_MASK) | SELNARROW, SBLKCTL + base); + *sxfrctl1 = 0; + configure_termination(p, sxfrctl1, sc->adapter_control, + (unsigned char) sc->max_targets & CFMAXTARG); } - - outb(SCSIRSTO, SCSISEQ + base); - udelay(1000); - outb(0, SCSISEQ + base); - - /* Ensure we don't get a RSTI interrupt from this. */ - outb(CLRSCSIRSTI, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); - - aic7xxx_delay(AIC7XXX_RESET_DELAY); - - printk("done.\n"); } - - /* - * Unpause the sequencer before returning and enable - * interrupts - we shouldn't get any until the first - * command is sent to us by the high-level SCSI code. - */ - UNPAUSE_SEQUENCER(p); - return (found); + return (have_seeprom); } /*+F************************************************************************* @@ -4671,17 +5678,24 @@ * * Description: * Try to detect and register an Adaptec 7770 or 7870 SCSI controller. + * + * XXX - This should really be called aic7xxx_probe(). A sequence of + * probe(), attach()/detach(), and init() makes more sense than + * one do-it-all function. This may be useful when (and if) the + * mid-level SCSI code is overhauled. *-F*************************************************************************/ int aic7xxx_detect(Scsi_Host_Template *template) { - int found = 0, slot, base; - unsigned char irq = 0; + int found = 0; + aha_status_type adapter_bios; + aha_chip_class_type chip_class; + aha_chip_type chip_type; + int slot, base; + int chan_num = 0; + unsigned char hcntrl, sxfrctl1, sblkctl, hostconf, irq = 0; int i; - struct aic7xxx_host_config config; - - template->proc_dir = &proc_scsi_aic7xxx; - config.chan_num = 0; + struct aic7xxx_host *p; /* * Since we may allow sharing of IRQs, it is imperative @@ -4695,6 +5709,10 @@ aic7xxx_boards[i] = NULL; } + template->proc_dir = &proc_scsi_aic7xxx; + template->name = aic7xxx_info(NULL); + template->sg_tablesize = AIC7XXX_MAX_SG; + /* * Initialize the spurious count to 0. */ @@ -4716,33 +5734,174 @@ continue; } - config.type = aic7xxx_probe(slot, HID0 + base, &(config.bios)); - if (config.type != AIC_NONE) + chip_type = aic7xxx_probe(slot, base + HID0, &(adapter_bios)); + if (chip_type != AIC_NONE) { + + switch (chip_type) + { + case AIC_7770: + case AIC_7771: + printk("aic7xxx: <%s> at EISA %d\n", + board_names[chip_type], slot); + break; + case AIC_284x: + printk("aic7xxx: <%s> at VLB %d\n", + board_names[chip_type], slot); + break; + default: + break; + } + /* * We found a card, allow 1 spurious interrupt. */ aic7xxx_spurious_count = 1; /* - * We "find" a AIC-7770 if we locate the card - * signature and we can set it up and register - * it with the kernel without incident. + * Pause the card preserving the IRQ type. Allow the operator + * to override the IRQ trigger. */ - config.chip_type = AIC_777x; - config.base = base; - config.mbase = 0; - config.irq = irq; - config.parity = AIC_ENABLED; - config.low_term = AIC_UNKNOWN; - config.high_term = AIC_UNKNOWN; - config.flags = 0; - if (aic7xxx_extended) - config.flags |= EXTENDED_TRANSLATION; - config.bus_speed = DFTHRSH_100; - config.busrtime = BOFF; - found += aic7xxx_register(template, &config); + if (aic7xxx_irq_trigger == 1) + hcntrl = IRQMS; /* Level */ + else if (aic7xxx_irq_trigger == 0) + hcntrl = 0; /* Edge */ + else + hcntrl = inb(base + HCNTRL) & IRQMS; /* Default */ + outb(hcntrl | PAUSE, base + HCNTRL); + + irq = inb(INTDEF + base) & 0x0F; + switch (irq) + { + case 9: + case 10: + case 11: + case 12: + case 14: + case 15: + break; + + default: + printk(KERN_WARNING "aic7xxx: Host adapter uses unsupported IRQ " + "level, ignoring.\n"); + irq = 0; + break; + } + + if (irq != 0) + { + p = aic7xxx_alloc(template, base, 0, chip_type, 0, NULL); + if (p == NULL) + { + printk(KERN_WARNING "aic7xxx: Unable to allocate device space.\n"); + continue; + } + p->irq = irq & 0x0F; + p->chip_class = AIC_777x; +#ifdef AIC7XXX_PAGE_ENABLE + p->flags |= PAGE_ENABLED; +#endif + p->instance = found; + if (aic7xxx_extended) + { + p->flags |= EXTENDED_TRANSLATION; + } + aic7xxx_chip_reset(p); + + switch (p->chip_type) + { + case AIC_7770: + case AIC_7771: + { + unsigned char biosctrl = inb(p->base + HA_274_BIOSCTRL); + + /* + * Get the primary channel information. Right now we don't + * do anything with this, but someday we will be able to inform + * the mid-level SCSI code which channel is primary. + */ + if (biosctrl & CHANNEL_B_PRIMARY) + { + p->flags |= FLAGS_CHANNEL_B_PRIMARY; + } + + if ((biosctrl & BIOSMODE) == BIOSDISABLED) + { + p->flags |= USE_DEFAULTS; + } + break; + } + case AIC_284x: + if (!load_seeprom(p, &sxfrctl1)) + { + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: SEEPROM not available.\n"); + } + break; + + default: /* Won't get here. */ + break; + } + printk(KERN_INFO "aic7xxx: BIOS %sabled, IO Port 0x%x, IRQ %d (%s), ", + (p->flags & USE_DEFAULTS) ? "dis" : "en", p->base, p->irq, + (p->pause & IRQMS) ? "level sensitive" : "edge triggered"); + /* + * Check for Rev C or E boards. Rev E boards can supposedly have + * more than 4 SCBs, while the Rev C boards are limited to 4 SCBs. + * It's still not clear extactly what is different about the Rev E + * boards, but we think it allows 8 bit entries in the QOUTFIFO to + * support "paging" SCBs (more than 4 commands can be active at once). + * + * The Rev E boards have a read/write autoflush bit in the + * SBLKCTL register, while in the Rev C boards it is read only. + */ + sblkctl = inb(p->base + SBLKCTL) ^ AUTOFLUSHDIS; + outb(sblkctl, p->base + SBLKCTL); + if (inb(p->base + SBLKCTL) == sblkctl) + { + /* + * We detected a Rev E board, we allow paging on this board. + */ + printk("Revision >= E\n"); + outb(sblkctl & ~AUTOFLUSHDIS, base + SBLKCTL); + } + else + { + /* Do not allow paging. */ + p->flags &= ~PAGE_ENABLED; + printk("Revision <= C\n"); + } + + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", + (p->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); + + /* + * Set the FIFO threshold and the bus off time. + */ + hostconf = inb(p->base + HOSTCONF); + outb(hostconf & DFTHRSH, p->base + BUSSPD); + outb((hostconf << 2) & BOFF, p->base + BUSTIME); + + /* + * Try to initialize the card and register it with the kernel. + */ + if (aic7xxx_register(template, p)) + { + /* + * We successfully found a board and registered it. + */ + found = found + 1; + } + else + { + /* + * Something went wrong; release and free all resources. + */ + aic7xxx_free(p); + } + } /* * Disallow spurious interrupts. */ @@ -4758,15 +5917,15 @@ { struct { - unsigned short vendor_id; - unsigned short device_id; - aha_type card_type; - aha_chip_type chip_type; + unsigned short vendor_id; + unsigned short device_id; + aha_chip_type chip_type; + aha_chip_class_type chip_class; } const aic7xxx_pci_devices[] = { {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7850, AIC_7850, AIC_785x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7855, AIC_7855, AIC_785x}, - {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7860, AIC_7860, AIC_785x}, - {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7861, AIC_7861, AIC_785x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7860, AIC_7860, AIC_786x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7861, AIC_7861, AIC_786x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7870, AIC_7870, AIC_787x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7871, AIC_7871, AIC_787x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7872, AIC_7872, AIC_787x}, @@ -4779,14 +5938,14 @@ {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7884, AIC_7884, AIC_788x} }; - int error; + int error, flags; int done = 0; unsigned int iobase, mbase; unsigned short index = 0; unsigned char pci_bus, pci_device_fn; - unsigned int csize_lattime; - unsigned int class_revid; - unsigned int devconfig; + unsigned char ultra_enb = 0; + unsigned int devconfig, class_revid; + scb_data_type *shared_scb_data = NULL; char rev_id[] = {'B', 'C', 'D'}; for (i = 0; i < NUMBER(aic7xxx_pci_devices); i++) @@ -4803,36 +5962,33 @@ } else /* Found an Adaptec PCI device. */ { - config.type = aic7xxx_pci_devices[i].card_type; - config.chip_type = aic7xxx_pci_devices[i].chip_type; - config.chan_num = 0; - config.bios = AIC_ENABLED; /* Assume bios is enabled. */ - config.flags = 0; - config.busrtime = 40; - switch (config.type) + chip_class = aic7xxx_pci_devices[i].chip_class; + chip_type = aic7xxx_pci_devices[i].chip_type; + chan_num = 0; + flags = 0; + switch (aic7xxx_pci_devices[i].chip_type) { case AIC_7850: case AIC_7855: - case AIC_7860: - case AIC_7861: - config.bios = AIC_DISABLED; - config.flags |= USE_DEFAULTS; - config.bus_speed = DFTHRSH_100; + flags |= USE_DEFAULTS; break; case AIC_7872: /* 3940 */ case AIC_7882: /* 3940-Ultra */ - config.chan_num = number_of_3940s & 0x1; /* Has 2 controllers */ + flags |= MULTI_CHANNEL; + chan_num = number_of_3940s & 0x1; /* Has 2 controllers */ number_of_3940s++; break; case AIC_7873: /* 3985 */ case AIC_7883: /* 3985-Ultra */ - config.chan_num = number_of_3985s; /* Has 3 controllers */ + chan_num = number_of_3985s; /* Has 3 controllers */ + flags |= MULTI_CHANNEL; number_of_3985s++; if (number_of_3985s == 3) { number_of_3985s = 0; + shared_scb_data = NULL; } break; @@ -4849,39 +6005,165 @@ PCI_INTERRUPT_LINE, &irq); error += pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_1, &mbase); + error += pcibios_read_config_dword(pci_bus, pci_device_fn, + DEVCONFIG, &devconfig); + error += pcibios_read_config_dword(pci_bus, pci_device_fn, + CLASS_PROGIF_REVID, &class_revid); + + printk("aic7xxx: <%s> at PCI %d\n", + board_names[chip_type], PCI_SLOT(pci_device_fn)); /* - * The first bit of PCI_BASE_ADDRESS_0 is always set, so + * The first bit (LSB) of PCI_BASE_ADDRESS_0 is always set, so * we mask it off. */ iobase &= PCI_BASE_ADDRESS_IO_MASK; + p = aic7xxx_alloc(template, iobase, mbase, chip_type, flags, + shared_scb_data); + + if (p == NULL) + { + printk(KERN_WARNING "aic7xxx: Unable to allocate device space.\n"); + continue; + } + + /* Remember to set the channel number, irq, and chip class. */ + p->chan_num = chan_num; + p->irq = irq; + p->chip_class = chip_class; +#ifdef AIC7XXX_PAGE_ENABLE + p->flags |= PAGE_ENABLED; +#endif + p->instance = found; + /* - * Read the PCI burst size and latency timer. + * Remember how the card was setup in case there is no seeprom. */ - error += pcibios_read_config_dword(pci_bus, pci_device_fn, - CSIZE_LATTIME, &csize_lattime); - printk(KERN_INFO "aic7xxx: BurstLen = %d DWDs, Latency Timer = %d " - "PCLKS\n", (int) (csize_lattime & CACHESIZE), - (csize_lattime >> 8) & 0x000000ff); + p->scsi_id = inb(p->base + SCSIID) & OID; + if ((p->chip_class == AIC_786x) || (p->chip_class == AIC_788x)) + { + p->flags |= ULTRA_ENABLED; + ultra_enb = inb(p->base + SXFRCTL1) & FAST20; + } + sxfrctl1 = inb(p->base + SXFRCTL1) & STPWEN; - error += pcibios_read_config_dword(pci_bus, pci_device_fn, - CLASS_PROGIF_REVID, &class_revid); - if ((class_revid & DEVREVID) < 3) + aic7xxx_chip_reset(p); + +#ifdef AIC7XXX_USE_EXT_SCBRAM + if (devconfig & RAMPSM) + { + printk(KERN_INFO "aic7xxx: External RAM detected; enabling RAM " + "access.\n"); + /* + * XXX - Assume 9 bit SRAM and enable parity checking. + */ + devconfig |= EXTSCBPEN; + + /* + * XXX - Assume fast SRAM and only enable 2 cycle access if we + * are sharing the SRAM across multiple adapters (398x). + */ + if ((devconfig & MPORTMODE) == 0) + { + devconfig |= EXTSCBTIME; + } + devconfig &= ~SCBRAMSEL; + pcibios_write_config_dword(pci_bus, pci_device_fn, + DEVCONFIG, devconfig); + } +#endif + + if ((p->flags & USE_DEFAULTS) == 0) { - printk(KERN_INFO "aic7xxx: %s Rev %c.\n", board_names[config.type], - rev_id[class_revid & DEVREVID]); + load_seeprom(p, &sxfrctl1); } - error += pcibios_read_config_dword(pci_bus, pci_device_fn, - DEVCONFIG, &devconfig); - if (error) + /* + * Take the LED out of diagnostic mode + */ + sblkctl = inb(p->base + SBLKCTL); + outb((sblkctl & ~(DIAGLEDEN | DIAGLEDON)), p->base + SBLKCTL); + + /* + * We don't know where this is set in the SEEPROM or by the + * BIOS, so we default to 100%. + */ + outb(DFTHRSH_100, p->base + DSPCISTATUS); + + if (p->flags & USE_DEFAULTS) { - panic("aic7xxx: (aic7xxx_detect) Error %d reading PCI registers.\n", - error); + int j; + /* + * Default setup; should only be used if the adapter does + * not have a SEEPROM. + */ + /* + * Check the target scratch area to see if someone set us + * up already. We are previously set up if the scratch + * area contains something other than all zeroes and ones. + */ + for (j = TARG_SCRATCH; j < 0x60; j++) + { + if (inb(p->base + j) != 0x00) /* Check for all zeroes. */ + break; + } + if (j == TARG_SCRATCH) + { + for (j = TARG_SCRATCH; j < 0x60; j++) + { + if (inb(p->base + 1) != 0xFF) /* Check for all ones. */ + break; + } + } + if ((j != 0x60) && (p->scsi_id != 0)) + { + p->flags &= ~USE_DEFAULTS; + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Using leftover BIOS values.\n"); + } + } + else + { + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: No BIOS found; using default " + "settings.\n"); + } + /* + * Assume only one connector and always turn on + * termination. + */ + sxfrctl1 = STPWEN; + p->scsi_id = 7; + } + outb((p->scsi_id & HSCSIID) | ENSPCHK | RESET_SCSI, + p->base + SCSICONF); + /* In case we are a wide card. */ + outb(p->scsi_id, p->base + SCSICONF + 1); + if ((ultra_enb == 0) && ((p->flags & USE_DEFAULTS) == 0)) + { + /* + * If there wasn't a BIOS or the board wasn't in this mode + * to begin with, turn off Ultra. + */ + p->flags &= ~ULTRA_ENABLED; + } } - printk(KERN_INFO "aic7xxx: devconfig = 0x%x.\n", devconfig); + /* + * Print some additional information about the adapter. + */ + printk(KERN_INFO "aic7xxx: BIOS %sabled, IO Port 0x%x, " + "IO Mem 0x%x, IRQ %d", + (p->flags & USE_DEFAULTS) ? "dis" : "en", + p->base, p->mbase, p->irq); + if ((class_revid & DEVREVID) < 3) + { + printk(", Revision %c", rev_id[class_revid & DEVREVID]); + } + printk("\n"); /* * I don't think we need to bother with allowing @@ -4890,58 +6172,57 @@ */ aic7xxx_spurious_count = 1; - config.base = iobase; - config.mbase = mbase; - config.irq = irq; - config.parity = AIC_ENABLED; - config.low_term = AIC_UNKNOWN; - config.high_term = AIC_UNKNOWN; if (aic7xxx_extended) - config.flags |= EXTENDED_TRANSLATION; -#ifdef AIC7XXX_SHARE_SCBs - if (devconfig & RAMPSM) -#else - if ((devconfig & RAMPSM) && (config.type != AIC_7873) && - (config.type != AIC_7883)) -#endif + p->flags |= EXTENDED_TRANSLATION; + + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", + (p->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); + + /* + * Put our termination setting into sxfrctl1 now that the + * generic initialization is complete. + */ + sxfrctl1 |= inb(p->base + SXFRCTL1); + outb(sxfrctl1, p->base + SXFRCTL1); + + if (aic7xxx_register(template, p) == 0) + { + aic7xxx_free(p); + } + else { + found = found + 1; + +#ifdef AIC7XXX_USE_EXT_SCBRAM /* - * External SRAM present. The probe will walk the SCBs to see - * how much SRAM we have and set the number of SCBs accordingly. - * We have to turn off SCBRAMSEL to access the external SCB - * SRAM. - * - * It seems that early versions of the aic7870 didn't use these - * bits, hence the hack for the 3940 above. I would guess that - * recent 3940s using later aic7870 or aic7880 chips do actually - * set RAMPSM. + * Set the shared SCB data once we've successfully probed a + * 398x adapter. * - * The documentation isn't clear, but it sounds like the value - * written to devconfig must not have RAMPSM set. The second - * sixteen bits of the register are R/O anyway, so it shouldn't - * affect RAMPSM either way. + * Note that we can only do this if the use of external + * SCB RAM is enabled. */ - printk(KERN_INFO "aic7xxx: External RAM detected; enabling RAM " - "access.\n"); - devconfig &= ~(RAMPSM | SCBRAMSEL); - pcibios_write_config_dword(pci_bus, pci_device_fn, - DEVCONFIG, devconfig); + if ((p->chip_type == AIC_7873) || (p->chip_type == AIC_7883)) + { + if (shared_scb_data == NULL) + { + shared_scb_data = p->scb_data; + } + } +#endif } - found += aic7xxx_register(template, &config); + index++; /* * Disable spurious interrupts. */ aic7xxx_spurious_count = 0; - - index++; } /* Found an Adaptec PCI device. */ } } } #endif CONFIG_PCI - template->name = aic7xxx_info(NULL); return (found); } @@ -4957,45 +6238,45 @@ aic7xxx_buildscb(struct aic7xxx_host *p, Scsi_Cmnd *cmd, struct aic7xxx_scb *scb) { - unsigned int addr; /* must be 32 bits */ unsigned short mask; + struct aic7xxx_hwscb *hscb; mask = (0x01 << TARGET_INDEX(cmd)); + hscb = scb->hscb; + /* * Setup the control byte if we need negotiation and have not * already requested it. */ -#ifdef AIC7XXX_TAGGED_QUEUEING - if (cmd->device->tagged_queue) + if (p->discenable & mask) { - cmd->tag = scb->tag; - cmd->device->current_tag = scb->tag; - scb->control |= TAG_ENB; - p->device_status[TARGET_INDEX(cmd)].commands_sent++; - if (p->device_status[TARGET_INDEX(cmd)].commands_sent == 200) - { - scb->control |= 0x02; - p->device_status[TARGET_INDEX(cmd)].commands_sent = 0; - } -#if 0 - if (p->orderedtag & mask) + hscb->control |= DISCENB; +#ifdef AIC7XXX_TAGGED_QUEUEING + if (cmd->device->tagged_queue) { - scb->control |= 0x02; - p->orderedtag = p->orderedtag & ~mask; + cmd->tag = hscb->tag; + p->device_status[TARGET_INDEX(cmd)].commands_sent++; + if (p->device_status[TARGET_INDEX(cmd)].commands_sent < 75) + { + hscb->control |= MSG_SIMPLE_Q_TAG; + } + else + { + hscb->control |= MSG_ORDERED_Q_TAG; + p->device_status[TARGET_INDEX(cmd)].commands_sent = 0; + } } -#endif - } -#endif - if (p->discenable & mask) - { - scb->control |= DISCENB; +#endif /* Tagged queueing */ } + if ((p->needwdtr & mask) && !(p->wdtr_pending & mask)) { p->wdtr_pending |= mask; - scb->control |= NEEDWDTR; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_WDTR; #if 0 - printk("aic7xxx: Sending WDTR request to target %d.\n", cmd->target); + printk("scsi%d: Sending WDTR request to target %d.\n", + p->host_no, cmd->target); #endif } else @@ -5003,19 +6284,20 @@ if ((p->needsdtr & mask) && !(p->sdtr_pending & mask)) { p->sdtr_pending |= mask; - scb->control |= NEEDSDTR; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_SDTR; #if 0 - printk("aic7xxx: Sending SDTR request to target %d.\n", cmd->target); + printk("scsi%d: Sending SDTR request to target %d.\n", + p->host_no, cmd->target); #endif } } - #if 0 printk("aic7xxx: (build_scb) Target %d, cmd(0x%x) size(%u) wdtr(0x%x) " "mask(0x%x).\n", cmd->target, cmd->cmnd[0], cmd->cmd_len, p->needwdtr, mask); #endif - scb->target_channel_lun = ((cmd->target << 4) & 0xF0) | + hscb->target_channel_lun = ((cmd->target << 4) & 0xF0) | ((cmd->channel & 0x01) << 3) | (cmd->lun & 0x07); /* @@ -5029,9 +6311,8 @@ * XXX - this relies on the host data being stored in a * little-endian format. */ - addr = VIRT_TO_BUS(cmd->cmnd); - scb->SCSI_cmd_length = cmd->cmd_len; - memcpy(scb->SCSI_cmd_pointer, &addr, sizeof(scb->SCSI_cmd_pointer)); + hscb->SCSI_cmd_length = cmd->cmd_len; + hscb->SCSI_cmd_pointer = VIRT_TO_BUS(cmd->cmnd); if (cmd->use_sg) { @@ -5051,15 +6332,16 @@ scb->sg_list[i].address = VIRT_TO_BUS(sg[i].address); scb->sg_list[i].length = (unsigned int) sg[i].length; } - scb->SG_segment_count = cmd->use_sg; - addr = VIRT_TO_BUS(scb->sg_list); - memcpy(scb->SG_list_pointer, &addr, sizeof(scb->SG_list_pointer)); - memcpy(scb->data_pointer, &(scb->sg_list[0].address), - sizeof(scb->data_pointer)); - scb->data_count = scb->sg_list[0].length; + hscb->SG_list_pointer = VIRT_TO_BUS(scb->sg_list); + hscb->SG_segment_count = cmd->use_sg; + scb->sg_count = hscb->SG_segment_count; + + /* Copy the first SG into the data pointer area. */ + hscb->data_pointer = scb->sg_list[0].address; + hscb->data_count = scb->sg_list[0].length | (SCB_LIST_NULL << 24); #if 0 printk("aic7xxx: (build_scb) SG segs(%d), length(%u), sg[0].length(%d).\n", - cmd->use_sg, aic7xxx_length(cmd, 0), scb->data_count); + cmd->use_sg, aic7xxx_length(cmd, 0), hscb->data_count); #endif } else @@ -5068,28 +6350,23 @@ printk("aic7xxx: (build_scb) Creating scatterlist, addr(0x%lx) length(%d).\n", (unsigned long) cmd->request_buffer, cmd->request_bufflen); #endif - if (cmd->request_bufflen == 0) + if (cmd->request_bufflen) { - /* - * In case the higher level SCSI code ever tries to send a zero - * length command, ensure the SCB indicates no data. The driver - * will interpret a zero length command as a Bus Device Reset. - */ - scb->SG_segment_count = 0; - memset(scb->SG_list_pointer, 0, sizeof(scb->SG_list_pointer)); - memset(scb->data_pointer, 0, sizeof(scb->data_pointer)); - scb->data_count = 0; + hscb->SG_segment_count = 1; + scb->sg_count = 1; + scb->sg_list[0].address = VIRT_TO_BUS(cmd->request_buffer); + scb->sg_list[0].length = cmd->request_bufflen; + hscb->SG_list_pointer = VIRT_TO_BUS(&scb->sg_list[0]); + hscb->data_count = scb->sg_list[0].length | (SCB_LIST_NULL << 24); + hscb->data_pointer = VIRT_TO_BUS(cmd->request_buffer); } else { - scb->SG_segment_count = 1; - scb->sg_list[0].address = VIRT_TO_BUS(cmd->request_buffer); - scb->sg_list[0].length = cmd->request_bufflen; - addr = VIRT_TO_BUS(&scb->sg_list[0]); - memcpy(scb->SG_list_pointer, &addr, sizeof(scb->SG_list_pointer)); - scb->data_count = scb->sg_list[0].length; - addr = VIRT_TO_BUS(cmd->request_buffer); - memcpy(scb->data_pointer, &addr, sizeof(scb->data_pointer)); + hscb->SG_segment_count = 0; + scb->sg_count = 0; + hscb->SG_list_pointer = 0; + hscb->data_pointer = 0; + hscb->data_count = SCB_LIST_NULL << 24; } } } @@ -5107,7 +6384,6 @@ long processor_flags; struct aic7xxx_host *p; struct aic7xxx_scb *scb; - u_char curscb, intstat; p = (struct aic7xxx_host *) cmd->host->hostdata; if (p->host != cmd->host) @@ -5139,34 +6415,21 @@ cmd->lun & 0x07); #endif - /* - * This is a critical section, since we don't want the interrupt - * routine mucking with the host data or the card. For this reason - * it is nice to know that this function can only be called in one - * of two ways from scsi.c First, as part of a routine queue command, - * in which case, the irq for our card is disabled before this - * function is called. This doesn't help us if there is more than - * one card using more than one IRQ in our system, therefore, we - * should disable all interrupts on these grounds alone. Second, - * this can be called as part of the scsi_done routine, in which case - * we are in the aic7xxx_isr routine already and interrupts are - * disabled, therefore we should saveflags first, then disable the - * interrupts, do our work, then restore the CPU flags. If it weren't - * for the possibility of more than one card using more than one IRQ - * in our system, we wouldn't have to touch the interrupt flags at all. - */ - save_flags(processor_flags); - cli(); - + if (p->device_status[TARGET_INDEX(cmd)].active_cmds + > cmd->device->queue_depth) + { + printk(KERN_WARNING "(scsi%d:%d:%d) Commands queued exceeds queue depth\n", + p->host_no, cmd->target, cmd->channel); + } scb = aic7xxx_allocate_scb(p); if (scb == NULL) { - panic("aic7xxx: (aic7xxx_free) Couldn't find a free SCB.\n"); + panic("aic7xxx: (aic7xxx_queue) Couldn't find a free SCB.\n"); } else { scb->cmd = cmd; - aic7xxx_position(cmd) = scb->tag; + aic7xxx_position(cmd) = scb->hscb->tag; #if 0 debug_scb(scb); #endif; @@ -5178,14 +6441,14 @@ aic7xxx_buildscb(p, cmd, scb); #if 0 - if (scb != (p->scb_array[scb->position])) + if (scb != (p->scb_data->scb_array[scb->hscb->tag])) { printk("aic7xxx: (queue) Address of SCB by position does not match SCB " "address.\n"); } printk("aic7xxx: (queue) SCB pos(%d) cmdptr(0x%x) state(%d) freescb(0x%x)\n", - scb->position, (unsigned int) scb->cmd, - scb->state, (unsigned int) p->free_scb); + scb->hscb->tag, (unsigned int) scb->cmd, + scb->flags, (unsigned int) p->free_scb); #endif /* @@ -5200,70 +6463,28 @@ cmd->host_scribble = NULL; memset(&cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); - if (scb->position != SCB_LIST_NULL) - { - /* We've got a valid slot, yeah! */ - if (p->flags & IN_ISR) - { - scbq_insert_tail(&p->assigned_scbs, scb); - scb->state |= SCB_ASSIGNEDQ; - } - else - { - /* - * Pause the sequencer so we can play with its registers - - * wait for it to acknowledge the pause. - * - * XXX - should the interrupts be left on while doing this? - */ - PAUSE_SEQUENCER(p); - intstat = inb(INTSTAT + p->base); - - /* - * Save the SCB pointer and put our own pointer in - this - * selects one of the four banks of SCB registers. Load - * the SCB, then write its pointer into the queue in FIFO - * and restore the saved SCB pointer. - */ - curscb = inb(SCBPTR + p->base); - outb(scb->position, SCBPTR + p->base); - aic7xxx_putscb(p, scb); - outb(curscb, SCBPTR + p->base); - outb(scb->position, QINFIFO + p->base); - scb->state |= SCB_ACTIVE; + scb->flags |= SCB_ACTIVE | SCB_WAITINGQ; - /* - * Guard against unpausing the sequencer if there is an interrupt - * waiting to happen. - */ - if (!(intstat & (BRKADRINT | SEQINT | SCSIINT))) - { - UNPAUSE_SEQUENCER(p); - } - } - } - else + save_flags(processor_flags); + cli(); + scbq_insert_tail(&p->waiting_scbs, scb); + if ((p->flags & (IN_ISR | IN_TIMEOUT)) == 0) { - scb->state |= SCB_WAITINGQ; - scbq_insert_tail(&p->waiting_scbs, scb); - if (!(p->flags & IN_ISR)) - { - aic7xxx_run_waiting_queues(p); - } + aic7xxx_run_waiting_queues(p); } + restore_flags(processor_flags); #if 0 printk("aic7xxx: (queue) After - cmd(0x%lx) scb->cmd(0x%lx) pos(%d).\n", - (long) cmd, (long) scb->cmd, scb->position); + (long) cmd, (long) scb->cmd, scb->hscb->tag); #endif; - restore_flags(processor_flags); } return (0); } /*+F************************************************************************* * Function: - * aic7xxx_abort_reset + * aic7xxx_bus_device_reset * * Description: * Abort or reset the current SCSI command(s). If the scb has not @@ -5275,204 +6496,257 @@ static int aic7xxx_bus_device_reset(struct aic7xxx_host *p, Scsi_Cmnd *cmd) { - struct aic7xxx_scb *scb; + struct aic7xxx_scb *scb; + struct aic7xxx_hwscb *hscb; unsigned char bus_state; - int base, result = -1; + int result = -1; char channel; - scb = (p->scb_array[aic7xxx_position(cmd)]); - base = p->base; + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); + hscb = scb->hscb; + + /* + * Ensure that the card doesn't do anything behind our back. + * Also make sure that we didn't just miss an interrupt that + * could affect this abort/reset. + */ + pause_sequencer(p); + while (inb(p->base + INTSTAT) & INT_PEND); + { + aic7xxx_isr(p->irq, (void *) NULL, (void *) NULL); + pause_sequencer(p); + } + if ((cmd != scb->cmd) || ((scb->flags & SCB_ACTIVE) == 0)) + { + result = SCSI_RESET_NOT_RUNNING; + unpause_sequencer(p, /* unpause_always */ TRUE); + return(result); + } + + + printk(KERN_WARNING "(scsi%d:%d:%d) Abort_reset, scb flags 0x%x, ", + p->host_no, TC_OF_SCB(scb), scb->flags); + bus_state = inb(p->base + LASTPHASE); - channel = scb->target_channel_lun & SELBUSB ? 'B': 'A'; - if ((cmd == scb->cmd) && (scb->state & SCB_IN_PROGRESS)) + switch (bus_state) { + case P_DATAOUT: + printk("Data-Out phase, "); + break; + case P_DATAIN: + printk("Data-In phase, "); + break; + case P_COMMAND: + printk("Command phase, "); + break; + case P_MESGOUT: + printk("Message-Out phase, "); + break; + case P_STATUS: + printk("Status phase, "); + break; + case P_MESGIN: + printk("Message-In phase, "); + break; + default: + /* + * We're not in a valid phase, so assume we're idle. + */ + printk("while idle, LASTPHASE = 0x%x, ", bus_state); + break; + } + printk("SCSISIGI 0x%x, SEQADDR 0x%x, SSTAT0 0x%x, SSTAT1 0x%x\n", + inb(p->base + SCSISIGI), + inb(p->base + SEQADDR0) | (inb(p->base + SEQADDR1) << 8), + inb(p->base + SSTAT0), inb(p->base + SSTAT1)); - if (scb->state & SCB_IN_PROGRESS) + channel = hscb->target_channel_lun & SELBUSB ? 'B': 'A'; + /* + * Determine our course of action. + */ + if (scb->flags & SCB_ABORT) + { + /* + * Been down this road before; do a full bus reset. + */ + scb->flags |= SCB_RECOVERY_SCB; + unpause_sequencer(p, /* unpause_always */ TRUE); + result = -1; + } +#if 0 + else if (hscb->control & TAG_ENB) { /* - * Ensure that the card doesn't do anything - * behind our back. + * We could be starving this command; try sending and ordered tag + * command to the target we come from. */ - PAUSE_SEQUENCER(p); + scb->flags |= SCB_SENTORDEREDTAG | SCB_RECOVERY_SCB; + p->orderedtag = p->orderedtag | 0xFF; + result = SCSI_RESET_PENDING; + unpause_sequencer(p, /* unpause_always */ TRUE); + printk(KERN_WARNING "scsi%d: Abort_reset, odered tag queued.\n", + p->host_no); + } +#endif + else + { + unsigned char active_scb_index, saved_scbptr; + struct aic7xxx_scb *active_scb; - printk(KERN_WARNING "aic7xxx: (abort_reset) scb state 0x%x, ", scb->state); - bus_state = inb(LASTPHASE + p->base); + /* + * Send an Abort Message: + * The target that is holding up the bus may not be the same as + * the one that triggered this timeout (different commands have + * different timeout lengths). Our strategy here is to queue an + * abort message to the timed out target if it is disconnected. + * Otherwise, if we have an active target we stuff the message buffer + * with an abort message and assert ATN in the hopes that the target + * will let go of the bus and go to the mesgout phase. If this + * fails, we'll get another timeout a few seconds later which will + * attempt a bus reset. + */ + saved_scbptr = inb(p->base + SCBPTR); + active_scb_index = inb(p->base + SCB_TAG); + active_scb = p->scb_data->scb_array[active_scb_index]; - switch (bus_state) + if (bus_state != P_BUSFREE) + { + if (active_scb_index >= p->scb_data->numscbs) { - case P_DATAOUT: - printk("Data-Out phase, "); - break; - case P_DATAIN: - printk("Data-In phase, "); - break; - case P_COMMAND: - printk("Command phase, "); - break; - case P_MESGOUT: - printk("Message-Out phase, "); - break; - case P_STATUS: - printk("Status phase, "); - break; - case P_MESGIN: - printk("Message-In phase, "); - break; - default: - printk("while idle, LASTPHASE = 0x%x, ", bus_state); - /* - * We're not in a valid phase, so assume we're idle. - */ - bus_state = 0; - break; + /* + * Perform a bus reset. + * + * XXX - We want to queue an abort for the timedout SCB + * instead. + */ + result = -1; + printk(KERN_WARNING "scsi%d: Invalid SCB ID %d is active, " + "SCB flags = 0x%x.\n", p->host_no, scb->hscb->tag, scb->flags); } - printk("SCSISIGI = 0x%x\n", inb(p->base + SCSISIGI)); - - /* - * First, determine if we want to do a bus reset or simply a bus device - * reset. If this is the first time that a transaction has timed out - * and the SCB is not paged out, just schedule a bus device reset. - * Otherwise, we reset the bus and abort all pending I/Os on that bus. - */ - if (!(scb->state & (SCB_ABORTED | SCB_PAGED_OUT))) + else { -#if 0 - if (scb->control & TAG_ENB) - { + /* Send the abort message to the active SCB. */ + outb(1, p->base + MSG_LEN); + if (active_scb->hscb->control & TAG_ENB) + { + outb(MSG_ABORT_TAG, p->base + MSG_OUT); + } + else + { + outb(MSG_ABORT, p->base + MSG_OUT); + } + outb(bus_state | ATNO, p->base + SCSISIGO); + printk(KERN_WARNING "scsi%d: abort message in message buffer\n", + p->host_no); + active_scb->flags |= SCB_ABORT | SCB_RECOVERY_SCB; + if (active_scb != scb) + { /* - * We could be starving this command; try sending and ordered tag - * command to the target we come from. + * XXX - We would like to increment the timeout on scb, but + * access to that routine is denied because it is hidden + * in scsi.c. If we were able to do this, it would give + * scb a new lease on life. */ - scb->state = scb->state | SCB_ABORTED | SCB_SENTORDEREDTAG; - p->orderedtag = p->orderedtag | 0xFF; result = SCSI_RESET_PENDING; - UNPAUSE_SEQUENCER(p); - printk(KERN_WARNING "aic7xxx: (abort_reset) Ordered tag queued.\n"); - } -#endif - unsigned char active_scb, control; - struct aic7xxx_scb *active_scbp; + aic7xxx_error(active_scb->cmd) = DID_RESET; + } + else + { + aic7xxx_error(scb->cmd) = DID_RESET; + result = SCSI_RESET_PENDING; + } + unpause_sequencer(p, /* unpause_always */ TRUE); + } + } + else + { + unsigned char hscb_index, linked_next; + int disconnected; - /* - * Send a Bus Device Reset Message: - * The target we select to send the message to may be entirely - * different than the target pointed to by the scb that timed - * out. If the command is in the QINFIFO or the waiting for - * selection list, its not tying up the bus and isn't responsible - * for the delay so we pick off the active command which should - * be the SCB selected by SCBPTR. If its disconnected or active, - * we device reset the target scbp points to. Although it may - * be that this target is not responsible for the delay, it may - * may also be that we're timing out on a command that just takes - * too much time, so we try the bus device reset there first. - */ - active_scb = inb(SCBPTR + base); - active_scbp = (p->scb_array[inb(SCB_TAG + base)]); - control = inb(SCB_CONTROL + base); + disconnected = FALSE; + hscb_index = aic7xxx_find_scb(p, scb); + if (hscb_index == SCB_LIST_NULL) + { + disconnected = TRUE; + linked_next = (scb->hscb->data_count >> 24) & 0xFF; + } + else + { + outb(hscb_index, p->base + SCBPTR); + if (inb(p->base + SCB_CONTROL) & DISCONNECTED) + { + disconnected = TRUE; + } + linked_next = inb(p->base + SCB_LINKED_NEXT); + } + if (disconnected) + { + /* + * Simply set the ABORT_SCB control bit and preserve the + * linked next pointer. + */ + scb->hscb->control |= ABORT_SCB | MK_MESSAGE; + scb->hscb->data_count &= ~0xFF000000; + scb->hscb->data_count |= linked_next << 24; + if ((p->flags & PAGE_ENABLED) == 0) + { + scb->hscb->control &= ~DISCONNECTED; + } + scb->flags |= SCB_QUEUED_ABORT | SCB_ABORT | SCB_RECOVERY_SCB; + if (hscb_index != SCB_LIST_NULL) + { + unsigned char scb_control; - /* - * Test to see if scbp is disconnected - */ - outb(scb->position, SCBPTR + base); - if (inb(SCB_CONTROL + base) & DISCONNECTED) - { -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (abort_scb) scb %d is disconnected; " - "bus device reset message queued.\n", scb->position); -#endif - if (p->flags & PAGE_ENABLED) - { - /* Pull this SCB out of the disconnected list. */ - u_char prev = inb(SCB_PREV + base); - u_char next = inb(SCB_NEXT + base); - if (prev == SCB_LIST_NULL) - { - /* Head of list */ - outb(next, DISCONNECTED_SCBH + base); - } - else - { - outb(prev, SCBPTR + base); - outb(next, SCB_NEXT + base); - if (next != SCB_LIST_NULL) - { - outb(next, SCBPTR + base); - outb(prev, SCB_PREV + base); - } - outb(scb->position, SCBPTR + base); - } - } - scb->state |= (SCB_DEVICE_RESET | SCB_ABORTED); - scb->control = scb->control & DISCENB; - scb->SCSI_cmd_length = 0; - scb->SG_segment_count = 0; - memset(scb->SG_list_pointer, 0, sizeof(scb->SG_list_pointer)); - memset(scb->data_pointer, 0, sizeof(scb->data_pointer)); - scb->data_count = 0; - aic7xxx_putscb(p, scb); - aic7xxx_add_waiting_scb(base, scb); - outb(active_scb, SCBPTR + base); - result = SCSI_RESET_PENDING; - UNPAUSE_SEQUENCER(p); - } - else - { - /* - * Is the active SCB really active? - */ - if ((active_scbp->state & SCB_ACTIVE) && bus_state) - { - /* - * Load the message buffer and assert attention. - */ - active_scbp->state |= (SCB_DEVICE_RESET | SCB_ABORTED); - outb(1, MSG_LEN + base); - outb(MSG_BUS_DEVICE_RESET, MSG0 + base); - outb(bus_state | ATNO, SCSISIGO + base); -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (abort_scb) asserted ATN - " - "bus device reset in message buffer.\n"); -#endif - if (active_scbp != scb) - { - /* - * XXX - We would like to increment the timeout on scb, but - * access to that routine is denied because it is hidden - * in scsi.c. If we were able to do this, it would give - * scb a new lease on life. - */ - ; - } - aic7xxx_error(scb->cmd) = DID_RESET; - /* - * Restore the active SCB and unpause the sequencer. - */ - outb(active_scb, SCBPTR + base); - if (active_scbp != scb) - { - /* - * The mid-level SCSI code requested us to reset a command - * different from the one that we actually reset. Return - * a "not running" indication and hope that the SCSI code - * will Do the Right Thing (tm). - */ - result = SCSI_RESET_NOT_RUNNING; - } - else - { - result = SCSI_RESET_PENDING; - } - UNPAUSE_SEQUENCER(p); - } - } + scb_control = inb(p->base + SCB_CONTROL); + outb(scb_control | MK_MESSAGE| ABORT_SCB, p->base + SCB_CONTROL); + } + /* + * Actually requeue this SCB in case we can select the + * device before it reconnects. If the transaction we + * want to abort is not tagged, unbusy it first so that + * we don't get held back from sending the command. + */ + if ((scb->hscb->control & TAG_ENB) == 0) + { + unsigned char target; + int lun; + + target = scb->cmd->target; + lun = scb->cmd->lun; + aic7xxx_search_qinfifo(p, target, channel, lun, SCB_LIST_NULL, + 0, /* requeue */ TRUE); + } + printk(KERN_WARNING "(scsi%d:%d:%d) Queueing an Abort SCB.\n", + p->host_no, TC_OF_SCB(scb)); + scbq_insert_head(&p->waiting_scbs, scb); + scb->flags |= SCB_WAITINGQ; + outb(saved_scbptr, p->base + SCBPTR); + if ((p->flags & IN_ISR) == 0) + { + /* + * Processing the waiting queue may unpause us. + */ + aic7xxx_run_waiting_queues(p); + /* + * If we are using AAP, aic7xxx_run_waiting_queues() will not + * unpause us, so ensure we are unpaused. + */ + unpause_sequencer(p, /*unpause_always*/ FALSE); + } + else + { + unpause_sequencer(p, /*unpause_always*/ TRUE); + } + result = SCSI_RESET_PENDING; + } + else + { + scb->flags |= SCB_RECOVERY_SCB; + unpause_sequencer(p, /* unpause_always */ TRUE); + result = -1; } } } - /* Make sure the sequencer is unpaused upon return. */ - if (result == -1) - { - UNPAUSE_SEQUENCER(p); - } return (result); } @@ -5490,16 +6764,48 @@ struct aic7xxx_scb *scb = NULL; struct aic7xxx_host *p; int base, result; + unsigned long processor_flags; p = (struct aic7xxx_host *) cmd->host->hostdata; - scb = (p->scb_array[aic7xxx_position(cmd)]); + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); base = p->base; + save_flags(processor_flags); + cli(); + #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (abort) Aborting scb %d, TCL %d/%d/%d\n", - scb->position, TCL_OF_SCB(scb)); + if (scb != NULL) + { + printk("(scsi%d:%d:%d) Aborting scb %d, flags 0x%x\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag, scb->flags); + } + else + { + printk("aic7xxx: Abort called with no SCB for cmd.\n"); + } #endif + if (p->flags & IN_TIMEOUT) + { + /* + * We've already started a recovery operation. + */ + if ((scb->flags & SCB_RECOVERY_SCB) == 0) + { + restore_flags(processor_flags); + return (SCSI_ABORT_PENDING); + } + else + { + /* + * This is the second time we've tried to abort the recovery + * SCB. We want the mid-level SCSI code to call the reset + * function to reset the SCSI bus. + */ + restore_flags(processor_flags); + return (SCSI_ABORT_NOT_RUNNING); + } + } if (cmd->serial_number != cmd->serial_number_at_timeout) { result = SCSI_ABORT_NOT_RUNNING; @@ -5508,14 +6814,34 @@ { result = SCSI_ABORT_NOT_RUNNING; } - else if ((scb->cmd != cmd) || (!(scb->state & SCB_IN_PROGRESS))) + else if ((scb->cmd != cmd) || (!(scb->flags & SCB_ACTIVE))) { result = SCSI_ABORT_NOT_RUNNING; } else { - result = SCSI_ABORT_SNOOZE; + /* + * XXX - Check use of IN_TIMEOUT to see if we're Doing the + * Right Thing with it. + */ + p->flags |= IN_TIMEOUT; + result = aic7xxx_bus_device_reset(p, scb->cmd); + switch (result) + { + case SCSI_RESET_NOT_RUNNING: + p->flags &= ~IN_TIMEOUT; + result = SCSI_ABORT_NOT_RUNNING; + break; + case SCSI_RESET_PENDING: + result = SCSI_ABORT_PENDING; + break; + default: + p->flags &= ~IN_TIMEOUT; + result = SCSI_ABORT_SNOOZE; + break; + } } + restore_flags(processor_flags); return (result); } @@ -5535,18 +6861,27 @@ { struct aic7xxx_scb *scb = NULL; struct aic7xxx_host *p; - int base, found, tindex, min_target, max_target, result = -1; + int base, found, tindex, min_target, max_target; + int result = -1; char channel = 'A'; unsigned long processor_flags; p = (struct aic7xxx_host *) cmd->host->hostdata; - scb = (p->scb_array[aic7xxx_position(cmd)]); + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); base = p->base; channel = cmd->channel ? 'B': 'A'; tindex = (cmd->channel << 4) | cmd->target; -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset) target/channel %d/%d\n", cmd->target, cmd->channel); +#ifdef 0 /* AIC7XXX_DEBUG_ABORT */ + if (scb != NULL) + { + printk("(scsi%d:%d:%d) Reset called, scb %d, flags 0x%x\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag, scb->flags); + } + else + { + printk("aic7xxx: Reset called with no SCB for cmd.\n"); + } #endif /* @@ -5561,34 +6896,45 @@ if (scb->cmd != cmd) scb = NULL; - if (!(flags & (SCSI_RESET_SUGGEST_HOST_RESET | SCSI_RESET_SUGGEST_BUS_RESET)) - && (scb != NULL)) + if (p->flags & IN_TIMEOUT) { /* - * Attempt a bus device reset if commands have completed successfully - * since the last bus device reset, or it has been less than 100ms - * since the last reset. + * We've already started a recovery operation. */ - if ((p->flags & DEVICE_SUCCESS) || - ((jiffies - p->device_status[tindex].last_reset) < HZ/10)) + if ((scb->flags & SCB_RECOVERY_SCB) == 0) { - if (cmd->serial_number != cmd->serial_number_at_timeout) - { - result = SCSI_RESET_NOT_RUNNING; - } - else + restore_flags(processor_flags); + return (SCSI_RESET_PENDING); + } + } + else + { + if (!(flags & (SCSI_RESET_SUGGEST_HOST_RESET | SCSI_RESET_SUGGEST_BUS_RESET)) + && (scb != NULL)) + { + /* + * Attempt a bus device reset if commands have completed successfully + * since the last bus device reset, or it has been less than 100ms + * since the last reset. + */ + if ((p->flags & DEVICE_SUCCESS) || + ((jiffies - p->device_status[tindex].last_reset) < HZ/10)) { - if (scb == NULL) + if (cmd->serial_number != cmd->serial_number_at_timeout) + { + result = SCSI_RESET_NOT_RUNNING; + } + else if (scb == NULL) { result = SCSI_RESET_NOT_RUNNING; } else if (flags & SCSI_RESET_ASYNCHRONOUS) { - if (scb->state & SCB_ABORTED) + if (scb->flags & SCB_ABORTED) { result = SCSI_RESET_PENDING; } - else if (!(scb->state & SCB_IN_PROGRESS)) + else if (!(scb->flags & SCB_ACTIVE)) { result = SCSI_RESET_NOT_RUNNING; } @@ -5599,20 +6945,23 @@ if ((flags & SCSI_RESET_SYNCHRONOUS) && (p->device_status[tindex].flags & BUS_DEVICE_RESET_PENDING)) { - scb->state |= SCB_ABORTED; + scb->flags |= SCB_ABORTED; result = SCSI_RESET_PENDING; } else { + p->flags |= IN_TIMEOUT; result = aic7xxx_bus_device_reset(p, cmd); if (result == 0) + { + p->flags &= ~IN_TIMEOUT; result = SCSI_RESET_PENDING; + } } - } + } } } } - if (result == -1) { /* @@ -5625,11 +6974,11 @@ { result = SCSI_RESET_NOT_RUNNING; } - else if (!(scb->state & SCB_IN_PROGRESS)) + else if (!(scb->flags & SCB_ACTIVE)) { result = SCSI_RESET_NOT_RUNNING; } - else if ((scb->state & SCB_ABORTED) && + else if ((scb->flags & SCB_ABORTED) && (!(p->device_status[tindex].flags & BUS_DEVICE_RESET_PENDING))) { result = SCSI_RESET_PENDING; @@ -5641,8 +6990,9 @@ /* * The reset channel function assumes that the sequencer is paused. */ - PAUSE_SEQUENCER(p); + pause_sequencer(p); found = aic7xxx_reset_channel(p, channel, TRUE); + p->flags = p->flags & ~IN_TIMEOUT; /* * If this is a synchronous reset and there is no SCB for this @@ -5688,8 +7038,10 @@ } result = SCSI_RESET_SUCCESS | SCSI_RESET_HOST_RESET; + p->flags &= ~IN_TIMEOUT; } } + aic7xxx_run_waiting_queues(p); restore_flags(processor_flags); return (result); } diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx.h linux/drivers/scsi/aic7xxx.h --- v2.0.30/linux/drivers/scsi/aic7xxx.h Sat Aug 17 11:19:27 1996 +++ linux/drivers/scsi/aic7xxx.h Thu Jul 31 12:37:17 1997 @@ -40,13 +40,13 @@ aic7xxx_info, \ NULL, \ aic7xxx_queue, \ - aic7xxx_abort, \ + NULL, \ aic7xxx_reset, \ NULL, \ aic7xxx_biosparam, \ -1, /* max simultaneous cmds */\ -1, /* scsi id of host adapter */\ - SG_ALL, /* max scatter-gather cmds */\ + 0, /* max scatter-gather cmds */\ 2, /* cmds per lun (linked cmds) */\ 0, /* number of 7xxx's present */\ 0, /* no memory DMA restrictions */\ @@ -57,7 +57,6 @@ extern int aic7xxx_biosparam(Disk *, kdev_t, int[]); extern int aic7xxx_detect(Scsi_Host_Template *); extern int aic7xxx_command(Scsi_Cmnd *); -extern int aic7xxx_abort(Scsi_Cmnd *); extern int aic7xxx_reset(Scsi_Cmnd *, unsigned int); extern const char *aic7xxx_info(struct Scsi_Host *); diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx.seq linux/drivers/scsi/aic7xxx.seq --- v2.0.30/linux/drivers/scsi/aic7xxx.seq Sun Oct 13 03:42:28 1996 +++ linux/drivers/scsi/aic7xxx.seq Wed Dec 31 16:00:00 1969 @@ -1,1127 +0,0 @@ -/*+M************************************************************************* - * Adaptec AIC7xxx device driver for Linux and FreeBSD. - * - * Copyright (c) 1994 John Aycock - * The University of Calgary Department of Computer Science. - * - *Modifications/enhancements: - * Copyright (c) 1994, 1995, 1996 Justin Gibbs. All rights reserved. - * - * 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. - * - * FreeBSD, Twin, Wide, 2 command per target support, tagged queuing and other - * optimizations provided by Justin T. Gibbs (gibbs@FreeBSD.org) - * - * This version corresponds to version 1.42 of FreeBSDs aic7xxx.seq. - * - *-M*************************************************************************/ - -VERSION AIC7XXX_SEQ_VER "$Id: aic7xxx.seq,v 4.0 1996/10/13 08:23:42 deang Exp $" - -#ifdef linux -#include "aic7xxx_reg.h" -#else -#if defined(__NetBSD__) -#include "../../../../dev/ic/aic7xxxreg.h" -#elif defined(__FreeBSD__) -#include "../../dev/aic7xxx/aic7xxx_reg.h" -#endif -#endif - -/* - * We can't just use ACCUM in the sequencer code because it - * must be treated specially by the assembler, and it currently - * looks for the symbol 'A'. This is the only register defined in - * the assembler's symbol space. - */ -A = ACCUM - -/* After starting the selection hardware, we check for reconnecting targets - * as well as for our selection to complete just in case the reselection wins - * bus arbitration. The problem with this is that we must keep track of the - * SCB that we've already pulled from the QINFIFO and started the selection - * on just in case the reselection wins so that we can retry the selection at - * a later time. This problem cannot be resolved by holding a single entry - * in scratch ram since a reconnecting target can request sense and this will - * create yet another SCB waiting for selection. The solution used here is to - * use byte 27 of the SCB as a pseudo-next pointer and to thread a list - * of SCBs that are awaiting selection. Since 0-0xfe are valid SCB offsets, - * SCB_LIST_NULL is 0xff which is out of range. The kernel driver must - * add an entry to this list every time a request sense occurs. The sequencer - * will automatically consume the entries. - */ - -/* - * We assume that the kernel driver may reset us at any time, even in the - * middle of a DMA, so clear DFCNTRL too. - */ -reset: - clr DFCNTRL - clr SCSISIGO /* De-assert BSY */ -/* - * We jump to start after every bus free. - */ -start: - and FLAGS,0x0f /* clear target specific flags */ - mvi SCSISEQ,ENRSELI /* Always allow reselection */ - clr SCSIRATE /* - * We don't know the target we will - * connect to, so default to narrow - * transfers to avoid parity problems. - */ -poll_for_work: - /* - * Are we a twin channel device? - * For fairness, we check the other bus first, - * since we just finished a transaction on the - * current channel. - */ - test FLAGS,TWIN_BUS jz start2 - xor SBLKCTL,SELBUSB /* Toggle to the other bus */ - test SSTAT0,SELDI jnz reselect - xor SBLKCTL,SELBUSB /* Toggle to the original bus */ -start2: - test SSTAT0,SELDI jnz reselect - cmp WAITING_SCBH,SCB_LIST_NULL jne start_waiting - mov A, QCNTMASK - test QINCNT,A jz poll_for_work - -/* - * We have at least one queued SCB now and we don't have any - * SCBs in the list of SCBs awaiting selection. Set the SCB - * pointer from the FIFO so we see the right bank of SCB - * registers. - */ - mov SCBPTR,QINFIFO - -/* - * See if there is not already an active SCB for this target. This code - * locks out on a per target basis instead of target/lun. Although this - * is not ideal for devices that have multiple luns active at the same - * time, it is faster than looping through all SCB's looking for active - * commands. It may be beneficial to make findscb a more general procedure - * to see if the added cost of the search is negligible. This code also - * assumes that the kernel driver will clear the active flags on board - * initialization, board reset, and a target SELTO. Tagged commands - * don't set the active bits since you can queue more than one command - * at a time. We do, however, look to see if there are any non-tagged - * I/Os in progress, and requeue the command if there are. Tagged and - * non-tagged commands cannot be mixed to a single target. - */ - -test_busy: - mov FUNCTION1,SCB_TCL - mov A,FUNCTION1 - test SCB_TCL,0x88 jz test_a /* Id < 8 && A channel */ - - test ACTIVE_B,A jnz requeue - test SCB_CONTROL,TAG_ENB jnz start_scb - /* Mark the current target as busy */ - or ACTIVE_B,A - jmp start_scb - -/* Place the currently active SCB back on the queue for later processing */ -requeue: - mov QINFIFO, SCBPTR - jmp poll_for_work - -/* - * Pull the first entry off of the waiting for selection list - * We don't have to "test_busy" because only transactions that - * have passed that test can be in the waiting_scb list. - */ -start_waiting: - mov SCBPTR,WAITING_SCBH - jmp start_scb2 - -test_a: - test ACTIVE_A,A jnz requeue - test SCB_CONTROL,TAG_ENB jnz start_scb - /* Mark the current target as busy */ - or ACTIVE_A,A - -start_scb: - mov SCB_NEXT,WAITING_SCBH - mov WAITING_SCBH, SCBPTR -start_scb2: - and SINDEX,0xf7,SBLKCTL /* Clear the channel select bit */ - and A,0x08,SCB_TCL /* Get new channel bit */ - or SINDEX,A - mov SBLKCTL,SINDEX /* select channel */ - mov SCB_TCL call initialize_scsiid - -/* - * Enable selection phase as an initiator, and do automatic ATN - * after the selection. We do this now so that we can overlap the - * rest of our work to set up this target with the arbitration and - * selection bus phases. - */ -start_selection: - mvi SCSISEQ,0x58 /* ENSELO|ENAUTOATNO|ENRSELI */ - -/* - * As soon as we get a successful selection, the target should go - * into the message out phase since we have ATN asserted. Prepare - * the message to send. - * - * Messages are stored in scratch RAM starting with a length byte - * followed by the message itself. - */ - test SCB_CMDLEN,0xff jnz mk_identify /* 0 Length Command? */ - -/* - * The kernel has sent us an SCB with no command attached. This implies - * that the kernel wants to send a message of some sort to this target, - * so we interrupt the driver, allow it to fill the message buffer, and - * then go back into the arbitration loop - */ - mvi INTSTAT,AWAITING_MSG - jmp wait_for_selection - -mk_identify: - and A,DISCENB,SCB_CONTROL /* mask off disconnect privledge */ - - and MSG0,0x7,SCB_TCL /* lun */ - or MSG0,A /* or in disconnect privledge */ - or MSG0,MSG_IDENTIFY - mvi MSG_LEN, 1 - - test SCB_CONTROL,0xb0 jz !message /* WDTR, SDTR or TAG?? */ -/* - * Send a tag message if TAG_ENB is set in the SCB control block. - * Use SCB_TAG (the position in the kernel's SCB array) as the tag value. - */ - -mk_tag: - mvi DINDEX, MSG1 - test SCB_CONTROL,TAG_ENB jz mk_tag_done - and DINDIR,0x23,SCB_CONTROL - mov DINDIR,SCB_TAG - - add MSG_LEN,COMP_MSG0,DINDEX /* update message length */ - -mk_tag_done: - - test SCB_CONTROL,0x90 jz !message /* NEEDWDTR|NEEDSDTR */ - mov DINDEX call mk_dtr /* build DTR message if needed */ - -!message: -wait_for_selection: - test SSTAT0,SELDO jnz select - test SSTAT0,SELDI jz wait_for_selection - -/* - * Reselection has been initiated by a target. Make a note that we've been - * reselected, but haven't seen an IDENTIFY message from the target - * yet. - */ -reselect: - clr MSG_LEN /* Don't have anything in the mesg buffer */ - mov SELID call initialize_scsiid - or FLAGS,RESELECTED - jmp select2 - -/* - * After the selection, remove this SCB from the "waiting for selection" - * list. This is achieved by simply moving our "next" pointer into - * WAITING_SCBH. Our next pointer will be set to null the next time this - * SCB is used, so don't bother with it now. - */ -select: - mov WAITING_SCBH,SCB_NEXT - or FLAGS,SELECTED -select2: -/* - * Set CLRCHN here before the target has entered a data transfer mode - - * with synchronous SCSI, if you do it later, you blow away some - * data in the SCSI FIFO that the target has already sent to you. - */ - or SXFRCTL0,CLRCHN -/* - * Initialize SCSIRATE with the appropriate value for this target. - */ - call ndx_dtr - mov SCSIRATE,SINDIR - -/* - * Initialize Ultra mode setting. - */ - mov FUNCTION1,SCSIID - mov A,FUNCTION1 - and SINDEX,0xdf,SXFRCTL0 /* default to Ultra disabled */ - test SCSIID, 0x80 jnz ultra_b /* Target ID > 7 */ - test SBLKCTL, SELBUSB jnz ultra_b /* Second channel device */ - test ULTRA_ENB,A jz set_sxfrctl0 - or SINDEX, ULTRAEN jmp set_sxfrctl0 -ultra_b: - test ULTRA_ENB_B,A jz set_sxfrctl0 - or SINDEX, ULTRAEN - -set_sxfrctl0: - mov SXFRCTL0,SINDEX - - mvi SCSISEQ,ENAUTOATNP /* - * ATN on parity errors - * for "in" phases - */ - mvi CLRSINT1,CLRBUSFREE - mvi CLRSINT0,0x60 /* CLRSELDI|CLRSELDO */ -/* - * Main loop for information transfer phases. If BSY is false, then - * we have a bus free condition, expected or not. Otherwise, wait - * for the target to assert REQ before checking MSG, C/D and I/O - * for the bus phase. - * - */ -ITloop: - test SSTAT1,BUSFREE jnz p_busfree - test SSTAT1,REQINIT jz ITloop - - and A,PHASE_MASK,SCSISIGI - mov LASTPHASE,A - mov SCSISIGO,A - - cmp ALLZEROS,A je p_dataout - cmp A,P_DATAIN je p_datain - cmp A,P_COMMAND je p_command - cmp A,P_MESGOUT je p_mesgout - cmp A,P_STATUS je p_status - cmp A,P_MESGIN je p_mesgin - - mvi INTSTAT,BAD_PHASE /* unknown phase - signal driver */ - jmp ITloop /* Try reading the bus again. */ - -p_dataout: - mvi DMAPARAMS,0x7d /* - * WIDEODD|SCSIEN|SDMAEN|HDMAEN| - * DIRECTION|FIFORESET - */ - jmp data_phase_init - -/* - * If we re-enter the data phase after going through another phase, the - * STCNT may have been cleared, so restore it from the residual field. - */ -data_phase_reinit: - mov STCNT0,SCB_RESID_DCNT0 - mov STCNT1,SCB_RESID_DCNT1 - mov STCNT2,SCB_RESID_DCNT2 - jmp data_phase_loop - -p_datain: - mvi DMAPARAMS,0x79 /* - * WIDEODD|SCSIEN|SDMAEN|HDMAEN| - * !DIRECTION|FIFORESET - */ -data_phase_init: - call assert - - test FLAGS, DPHASE jnz data_phase_reinit - call sg_scb2ram - or FLAGS, DPHASE /* We have seen a data phase */ - -data_phase_loop: -/* Guard against overruns */ - test SG_COUNT, 0xff jnz data_phase_inbounds -/* - * Turn on 'Bit Bucket' mode, set the transfer count to - * 16meg and let the target run until it changes phase. - * When the transfer completes, notify the host that we - * had an overrun. - */ - or SXFRCTL1,BITBUCKET - mvi STCNT0,0xff - mvi STCNT1,0xff - mvi STCNT2,0xff - -data_phase_inbounds: -/* If we are the last SG block, don't set wideodd. */ - cmp SG_COUNT,0x01 jne data_phase_wideodd - and DMAPARAMS, 0xbf /* Turn off WIDEODD */ -data_phase_wideodd: - mov DMAPARAMS call dma - -/* Go tell the host about any overruns */ - test SXFRCTL1,BITBUCKET jnz data_phase_overrun - -/* Exit if we had an underrun */ - test SSTAT0,SDONE jz data_phase_finish /* underrun STCNT != 0 */ - -/* - * Advance the scatter-gather pointers if needed - */ -sg_advance: - dec SG_COUNT /* one less segment to go */ - - test SG_COUNT, 0xff jz data_phase_finish /* Are we done? */ - - clr A /* add sizeof(struct scatter) */ - add SG_NEXT0,SG_SIZEOF,SG_NEXT0 - adc SG_NEXT1,A,SG_NEXT1 - -/* - * Load a struct scatter and set up the data address and length. - * If the working value of the SG count is nonzero, then - * we need to load a new set of values. - * - * This, like all DMA's, assumes a little-endian host data storage. - */ -sg_load: - clr HCNT2 - clr HCNT1 - mvi HCNT0,SG_SIZEOF - - mov HADDR0,SG_NEXT0 - mov HADDR1,SG_NEXT1 - mov HADDR2,SG_NEXT2 - mov HADDR3,SG_NEXT3 - - or DFCNTRL,0xd /* HDMAEN|DIRECTION|FIFORESET */ - -/* - * Wait for DMA from host memory to data FIFO to complete, then disable - * DMA and wait for it to acknowledge that it's off. - */ -dma_finish: - test DFSTATUS,HDONE jz dma_finish - /* Turn off DMA preserving WIDEODD */ - and DFCNTRL,WIDEODD -dma_finish2: - test DFCNTRL,HDMAENACK jnz dma_finish2 - -/* - * Copy data from FIFO into SCB data pointer and data count. In - * both FreeBSD and Linux, the scatter list entry is 8 bytes. - * - * struct ahc_dma_seg { - * physaddr addr; four bytes, little-endian order - * long len; four bytes, little endian order - * }; - */ - - mov HADDR0,DFDAT - mov HADDR1,DFDAT - mov HADDR2,DFDAT - mov HADDR3,DFDAT - mov HCNT0,DFDAT - mov HCNT1,DFDAT - mov HCNT2,DFDAT - -/* Load STCNT as well. It is a mirror of HCNT */ - mov STCNT0,HCNT0 - mov STCNT1,HCNT1 - mov STCNT2,HCNT2 - test SSTAT1,PHASEMIS jz data_phase_loop - -data_phase_finish: -/* - * After a DMA finishes, save the SG and STCNT residuals back into the SCB - * We use STCNT instead of HCNT, since it's a reflection of how many bytes - * were transferred on the SCSI (as opposed to the host) bus. - */ - mov SCB_RESID_DCNT0,STCNT0 - mov SCB_RESID_DCNT1,STCNT1 - mov SCB_RESID_DCNT2,STCNT2 - mov SCB_RESID_SGCNT, SG_COUNT - jmp ITloop - -data_phase_overrun: -/* - * Turn off BITBUCKET mode and notify the host - */ - and SXFRCTL1,0x7f /* ~BITBUCKET */ - mvi INTSTAT,DATA_OVERRUN - jmp ITloop - -/* - * Command phase. Set up the DMA registers and let 'er rip. - */ -p_command: - call assert - -/* - * Load HADDR and HCNT. - */ - mov HADDR0, SCB_CMDPTR0 - mov HADDR1, SCB_CMDPTR1 - mov HADDR2, SCB_CMDPTR2 - mov HADDR3, SCB_CMDPTR3 - mov HCNT0, SCB_CMDLEN - clr HCNT1 - clr HCNT2 - - mov STCNT0, HCNT0 - mov STCNT1, HCNT1 - mov STCNT2, HCNT2 - - mvi 0x3d call dma # SCSIEN|SDMAEN|HDMAEN| - # DIRECTION|FIFORESET - jmp ITloop - -/* - * Status phase. Wait for the data byte to appear, then read it - * and store it into the SCB. - */ -p_status: - mvi SCB_TARGET_STATUS call inb_first - jmp mesgin_done - -/* - * Message out phase. If there is not an active message, but the target - * took us into this phase anyway, build a no-op message and send it. - */ -p_mesgout: - test MSG_LEN, 0xff jnz p_mesgout_start - mvi MSG_NOP call mk_mesg /* build NOP message */ - -p_mesgout_start: -/* - * Set up automatic PIO transfer from MSG0. Bit 3 in - * SXFRCTL0 (SPIOEN) is already on. - */ - mvi SINDEX,MSG0 - mov DINDEX,MSG_LEN - -/* - * When target asks for a byte, drop ATN if it's the last one in - * the message. Otherwise, keep going until the message is exhausted. - * - * Keep an eye out for a phase change, in case the target issues - * a MESSAGE REJECT. - */ -p_mesgout_loop: - test SSTAT1,PHASEMIS jnz p_mesgout_phasemis - test SSTAT0,SPIORDY jz p_mesgout_loop - test SSTAT1,PHASEMIS jnz p_mesgout_phasemis - cmp DINDEX,1 jne p_mesgout_outb /* last byte? */ - mvi CLRSINT1,CLRATNO /* drop ATN */ -p_mesgout_outb: - dec DINDEX - or CLRSINT0, CLRSPIORDY - mov SCSIDATL,SINDIR - -p_mesgout4: - test DINDEX,0xff jnz p_mesgout_loop - -/* - * If the next bus phase after ATN drops is a message out, it means - * that the target is requesting that the last message(s) be resent. - */ -p_mesgout_snoop: - test SSTAT1,BUSFREE jnz p_mesgout_done - test SSTAT1,REQINIT jz p_mesgout_snoop - - test SSTAT1,PHASEMIS jnz p_mesgout_done - - or SCSISIGO,ATNO /* turn on ATNO */ - - jmp ITloop - -p_mesgout_phasemis: - mvi CLRSINT1,CLRATNO /* Be sure to turn ATNO off */ -p_mesgout_done: - clr MSG_LEN /* no active msg */ - jmp ITloop - -/* - * Message in phase. Bytes are read using Automatic PIO mode. - */ -p_mesgin: - mvi A call inb_first /* read the 1st message byte */ - mov REJBYTE,A /* save it for the driver */ - - test A,MSG_IDENTIFY jnz mesgin_identify - cmp A,MSG_DISCONNECT je mesgin_disconnect - cmp A,MSG_SDPTRS je mesgin_sdptrs - cmp ALLZEROS,A je mesgin_complete - cmp A,MSG_RDPTRS je mesgin_rdptrs - cmp A,MSG_EXTENDED je mesgin_extended - cmp A,MSG_REJECT je mesgin_reject - -rej_mesgin: -/* - * We have no idea what this message in is, and there's no way - * to pass it up to the kernel, so we issue a message reject and - * hope for the best. Since we're now using manual PIO mode to - * read in the message, there should no longer be a race condition - * present when we assert ATN. In any case, rejection should be a - * rare occurrence - signal the driver when it happens. - */ - or SCSISIGO,ATNO /* turn on ATNO */ - mvi INTSTAT,SEND_REJECT /* let driver know */ - - mvi MSG_REJECT call mk_mesg - -mesgin_done: - call inb_last /*ack & turn auto PIO back on*/ - jmp ITloop - - -mesgin_complete: -/* - * We got a "command complete" message, so put the SCB_TAG into QUEUEOUT, - * and trigger a completion interrupt. Check status for non zero return - * and interrupt driver if needed. This allows the driver to interpret - * errors only when they occur instead of always uploading the scb. If - * the status is SCSI_CHECK, the driver will download a new scb requesting - * sense to replace the old one, modify the "waiting for selection" SCB list - * and set RETURN_1 to SEND_SENSE. If RETURN_1 is set to SEND_SENSE the - * sequencer imediately jumps to main loop where it will run down the waiting - * SCB list and process the sense request. If the kernel driver does not - * wish to request sense, it need only clear RETURN_1, and the command is - * allowed to complete. We don't bother to post to the QOUTFIFO in the - * error case since it would require extra work in the kernel driver to - * ensure that the entry was removed before the command complete code tried - * processing it. - * - * First check for residuals - */ - test SCB_RESID_SGCNT,0xff jz check_status -/* - * If we have a residual count, interrupt and tell the host. Other - * alternatives are to pause the sequencer on all command completes (yuck), - * dma the resid directly to the host (slick, we may have space to do it now) - * or have the sequencer pause itself when it encounters a non-zero resid - * (unnecessary pause just to flag the command -yuck-, but takes one instruction - * and since it shouldn't happen that often is good enough for our purposes). - */ -resid: - mvi INTSTAT,RESIDUAL - -check_status: - test SCB_TARGET_STATUS,0xff jz status_ok /* Good Status? */ - mvi INTSTAT,BAD_STATUS /* let driver know */ - cmp RETURN_1, SEND_SENSE jne status_ok - jmp mesgin_done - -status_ok: -/* First, mark this target as free. */ - test SCB_CONTROL,TAG_ENB jnz test_immediate /* - * Tagged commands - * don't busy the - * target. - */ - mov FUNCTION1,SCB_TCL - mov A,FUNCTION1 - test SCB_TCL,0x88 jz clear_a - xor ACTIVE_B,A - jmp test_immediate - -clear_a: - xor ACTIVE_A,A - -test_immediate: - test SCB_CMDLEN,0xff jnz complete /* Immediate message complete */ -/* - * Pause the sequencer until the driver gets around to handling the command - * complete. This is so that any action that might require careful timing - * with the completion of this command can occur. - */ - mvi INTSTAT,IMMEDDONE - jmp start -complete: - mov QOUTFIFO,SCB_TAG - mvi INTSTAT,CMDCMPLT - jmp mesgin_done - - -/* - * Is it an extended message? We only support the synchronous and wide data - * transfer request messages, which will probably be in response to - * WDTR or SDTR message outs from us. If it's not SDTR or WDTR, reject it - - * apparently this can be done after any message in byte, according - * to the SCSI-2 spec. - */ -mesgin_extended: - mvi ARG_1 call inb_next /* extended message length */ - mvi REJBYTE_EXT call inb_next /* extended message code */ - - cmp REJBYTE_EXT,MSG_SDTR je p_mesginSDTR - cmp REJBYTE_EXT,MSG_WDTR je p_mesginWDTR - jmp rej_mesgin - -p_mesginWDTR: - cmp ARG_1,2 jne rej_mesgin /* extended mesg length=2 */ - mvi ARG_1 call inb_next /* Width of bus */ - mvi INTSTAT,WDTR_MSG /* let driver know */ - test RETURN_1,0xff jz mesgin_done /* Do we need to send WDTR? */ - cmp RETURN_1,SEND_REJ je rej_mesgin /* - * Bus width was too large - * Reject it. - */ - -/* We didn't initiate the wide negotiation, so we must respond to the request */ - and RETURN_1,0x7f /* Clear the SEND_WDTR Flag */ - mvi DINDEX,MSG0 - mvi MSG0 call mk_wdtr /* build WDTR message */ - or SCSISIGO,ATNO /* turn on ATNO */ - jmp mesgin_done - -p_mesginSDTR: - cmp ARG_1,3 jne rej_mesgin /* extended mesg length=3 */ - mvi ARG_1 call inb_next /* xfer period */ - mvi A call inb_next /* REQ/ACK offset */ - mvi INTSTAT,SDTR_MSG /* call driver to convert */ - - test RETURN_1,0xff jz mesgin_done /* Do we need to mk_sdtr/rej */ - cmp RETURN_1,SEND_REJ je rej_mesgin /* - * Requested SDTR too small - * Reject it. - */ - clr ARG_1 /* Use the scratch ram rate */ - mvi DINDEX, MSG0 - mvi MSG0 call mk_sdtr - or SCSISIGO,ATNO /* turn on ATNO */ - jmp mesgin_done - -/* - * Is it a disconnect message? Set a flag in the SCB to remind us - * and await the bus going free. - */ -mesgin_disconnect: - or SCB_CONTROL,DISCONNECTED - test FLAGS, PAGESCBS jz mesgin_done -/* - * Link this SCB into the DISCONNECTED list. This list holds the - * candidates for paging out an SCB if one is needed for a new command. - * Modifying the disconnected list is a critical(pause dissabled) section. - */ - mvi SCB_PREV, SCB_LIST_NULL - mvi SEQCTL,0x50 /* PAUSEDIS|FASTMODE */ - mov SCB_NEXT, DISCONNECTED_SCBH - mov DISCONNECTED_SCBH, SCBPTR - cmp SCB_NEXT,SCB_LIST_NULL je linkdone - mov SCBPTR,SCB_NEXT - mov SCB_PREV,DISCONNECTED_SCBH - mov SCBPTR,DISCONNECTED_SCBH -linkdone: - mvi SEQCTL,0x10 /* !PAUSEDIS|FASTMODE */ - jmp mesgin_done - -/* - * Save data pointers message? Copy working values into the SCB, - * usually in preparation for a disconnect. - */ -mesgin_sdptrs: - call sg_ram2scb - jmp mesgin_done - -/* - * Restore pointers message? Data pointers are recopied from the - * SCB anytime we enter a data phase for the first time, so all - * we need to do is clear the DPHASE flag and let the data phase - * code do the rest. - */ -mesgin_rdptrs: - and FLAGS,0xef /* - * !DPHASE we'll reload them - * the next time through - */ - jmp mesgin_done - -/* - * Identify message? For a reconnecting target, this tells us the lun - * that the reconnection is for - find the correct SCB and switch to it, - * clearing the "disconnected" bit so we don't "find" it by accident later. - */ -mesgin_identify: - test A,0x78 jnz rej_mesgin /*!DiscPriv|!LUNTAR|!Reserved*/ - - and A,0x07 /* lun in lower three bits */ - or SAVED_TCL,A,SELID - and SAVED_TCL,0xf7 - and A,SELBUSB,SBLKCTL /* B Channel?? */ - or SAVED_TCL,A - call inb_last /* ACK */ - -/* - * Here we "snoop" the bus looking for a SIMPLE QUEUE TAG message. - * If we get one, we use the tag returned to switch to find the proper - * SCB. With SCB paging, this requires using findSCB for both tagged - * and non-tagged transactions since the SCB may exist in any slot. - * If we're not using SCB paging, we can use the tag as the direct - * index to the SCB. - */ - mvi ARG_1,SCB_LIST_NULL /* Default to no-tag */ -snoop_tag_loop: - test SSTAT1,BUSFREE jnz use_findSCB - test SSTAT1,REQINIT jz snoop_tag_loop - test SSTAT1,PHASEMIS jnz use_findSCB - mvi A call inb_first - cmp A,MSG_SIMPLE_TAG jne use_findSCB -get_tag: - mvi ARG_1 call inb_next /* tag value */ -/* - * See if the tag is in range. The tag is < SCBCOUNT if we add - * the complement of SCBCOUNT to the incoming tag and there is - * no carry. - */ - mov A,COMP_SCBCOUNT - add SINDEX,A,ARG_1 - jc abort_tag - -/* - * Ensure that the SCB the tag points to is for a SCB transaction - * to the reconnecting target. - */ - test FLAGS, PAGESCBS jz index_by_tag - call inb_last /* Ack Tag */ -use_findSCB: - mov ALLZEROS call findSCB /* Have to search */ -setup_SCB: - and SCB_CONTROL,0xfb /* clear disconnect bit in SCB */ - or FLAGS,IDENTIFY_SEEN /* make note of IDENTIFY */ - jmp ITloop -index_by_tag: - mov SCBPTR,ARG_1 - mov A,SAVED_TCL - cmp SCB_TCL,A jne abort_tag - test SCB_CONTROL,TAG_ENB jz abort_tag - call inb_last /* Ack Successful tag */ - jmp setup_SCB - -abort_tag: - or SCSISIGO,ATNO /* turn on ATNO */ - mvi INTSTAT,ABORT_TAG /* let driver know */ - mvi MSG_ABORT_TAG call mk_mesg /* ABORT TAG message */ - jmp mesgin_done - -/* - * Message reject? Let the kernel driver handle this. If we have an - * outstanding WDTR or SDTR negotiation, assume that it's a response from - * the target selecting 8bit or asynchronous transfer, otherwise just ignore - * it since we have no clue what it pertains to. - */ -mesgin_reject: - mvi INTSTAT, REJECT_MSG - jmp mesgin_done - -/* - * [ ADD MORE MESSAGE HANDLING HERE ] - */ - -/* - * Bus free phase. It might be useful to interrupt the device - * driver if we aren't expecting this. For now, make sure that - * ATN isn't being asserted and look for a new command. - */ -p_busfree: - mvi CLRSINT1,CLRATNO - clr LASTPHASE - -/* - * if this is an immediate command, perform a pseudo command complete to - * notify the driver. - */ - test SCB_CMDLEN,0xff jz status_ok - jmp start - -/* - * Locking the driver out, build a one-byte message passed in SINDEX - * if there is no active message already. SINDEX is returned intact. - */ -mk_mesg: - mvi SEQCTL,0x50 /* PAUSEDIS|FASTMODE */ - test MSG_LEN,0xff jz mk_mesg1 /* Should always succeed */ - - /* - * Hmmm. For some reason the mesg buffer is in use. - * Tell the driver. It should look at SINDEX to find - * out what we wanted to use the buffer for and resolve - * the conflict. - */ - mvi SEQCTL,0x10 /* !PAUSEDIS|FASTMODE */ - mvi INTSTAT,MSG_BUFFER_BUSY - -mk_mesg1: - mvi MSG_LEN,1 /* length = 1 */ - mov MSG0,SINDEX /* 1-byte message */ - mvi SEQCTL,0x10 ret /* !PAUSEDIS|FASTMODE */ - -/* - * Functions to read data in Automatic PIO mode. - * - * According to Adaptec's documentation, an ACK is not sent on input from - * the target until SCSIDATL is read from. So we wait until SCSIDATL is - * latched (the usual way), then read the data byte directly off the bus - * using SCSIBUSL. When we have pulled the ATN line, or we just want to - * acknowledge the byte, then we do a dummy read from SCISDATL. The SCSI - * spec guarantees that the target will hold the data byte on the bus until - * we send our ACK. - * - * The assumption here is that these are called in a particular sequence, - * and that REQ is already set when inb_first is called. inb_{first,next} - * use the same calling convention as inb. - */ - -inb_next: - or CLRSINT0, CLRSPIORDY - mov NONE,SCSIDATL /*dummy read from latch to ACK*/ -inb_next_wait: - test SSTAT1,PHASEMIS jnz mesgin_phasemis - test SSTAT0,SPIORDY jz inb_next_wait /* wait for next byte */ -inb_first: - mov DINDEX,SINDEX - test SSTAT1,PHASEMIS jnz mesgin_phasemis - mov DINDIR,SCSIBUSL ret /*read byte directly from bus*/ -inb_last: - mov NONE,SCSIDATL ret /*dummy read from latch to ACK*/ - -mesgin_phasemis: -/* - * We expected to receive another byte, but the target changed phase - */ - mvi INTSTAT, MSGIN_PHASEMIS - jmp ITloop - -/* - * DMA data transfer. HADDR and HCNT must be loaded first, and - * SINDEX should contain the value to load DFCNTRL with - 0x3d for - * host->scsi, or 0x39 for scsi->host. The SCSI channel is cleared - * during initialization. - */ -dma: - mov DFCNTRL,SINDEX -dma1: - test SSTAT0,DMADONE jnz dma3 - test SSTAT1,PHASEMIS jz dma1 /* ie. underrun */ - -/* - * We will be "done" DMAing when the transfer count goes to zero, or - * the target changes the phase (in light of this, it makes sense that - * the DMA circuitry doesn't ACK when PHASEMIS is active). If we are - * doing a SCSI->Host transfer, the data FIFO should be flushed auto- - * magically on STCNT=0 or a phase change, so just wait for FIFO empty - * status. - */ -dma3: - test SINDEX,DIRECTION jnz dma5 -dma4: - test DFSTATUS,FIFOEMP jz dma4 - -/* - * Now shut the DMA enables off and make sure that the DMA enables are - * actually off first lest we get an ILLSADDR. - */ -dma5: - /* disable DMA, but maintain WIDEODD */ - and DFCNTRL,WIDEODD -dma6: - test DFCNTRL,0x38 jnz dma6 /* SCSIENACK|SDMAENACK|HDMAENACK */ - - ret - -/* - * Common SCSI initialization for selection and reselection. Expects - * the target SCSI ID to be in the upper four bits of SINDEX, and A's - * contents are stomped on return. - */ -initialize_scsiid: - and SINDEX,0xf0 /* Get target ID */ - and A,0x0f,SCSIID - or SINDEX,A - mov SCSIID,SINDEX ret - -/* - * Assert that if we've been reselected, then we've seen an IDENTIFY - * message. - */ -assert: - test FLAGS,RESELECTED jz return /* reselected? */ - test FLAGS,IDENTIFY_SEEN jnz return /* seen IDENTIFY? */ - - mvi INTSTAT,NO_IDENT ret /* no - cause a kernel panic */ - -/* - * Locate the SCB matching the target ID/channel/lun in SAVED_TCL, and the tag - * value in ARG_1. If ARG_1 == SCB_LIST_NULL, we're looking for a non-tagged - * SCB. Have the kernel print a warning message if it can't be found, and - * generate an ABORT/ABORT_TAG message to the target. SINDEX should be - * cleared on call. - */ -findSCB: - mov A,SAVED_TCL - mov SCBPTR,SINDEX /* switch to next SCB */ - mvi SEQCTL,0x50 /* PAUSEDIS|FASTMODE */ - cmp SCB_TCL,A jne findSCB1 /* target ID/channel/lun match? */ - test SCB_CONTROL,DISCONNECTED jz findSCB1 /*should be disconnected*/ - test SCB_CONTROL,TAG_ENB jnz findTaggedSCB - cmp ARG_1,SCB_LIST_NULL je foundSCB - jmp findSCB1 -findTaggedSCB: - mov A, ARG_1 /* Tag passed in ARG_1 */ - cmp SCB_TAG,A jne findSCB1 /* Found it? */ -foundSCB: - test FLAGS,PAGESCBS jz foundSCB_ret -/* Remove this SCB from the disconnection list */ - cmp SCB_NEXT,SCB_LIST_NULL je unlink_prev - mov SAVED_LINKPTR, SCB_PREV - mov SCBPTR, SCB_NEXT - mov SCB_PREV, SAVED_LINKPTR - mov SCBPTR, SINDEX -unlink_prev: - cmp SCB_PREV,SCB_LIST_NULL je rHead/* At the head of the list */ - mov SAVED_LINKPTR, SCB_NEXT - mov SCBPTR, SCB_PREV - mov SCB_NEXT, SAVED_LINKPTR - mov SCBPTR, SINDEX - mvi SEQCTL,0x10 ret /* !PAUSEDIS|FASTMODE */ -rHead: - mov DISCONNECTED_SCBH,SCB_NEXT -foundSCB_ret: - mvi SEQCTL,0x10 ret /* !PAUSEDIS|FASTMODE */ - -findSCB1: - mvi SEQCTL,0x10 /* !PAUSEDIS|FASTMODE */ - inc SINDEX - mov A,SCBCOUNT - cmp SINDEX,A jne findSCB - - mvi INTSTAT,NO_MATCH /* not found - signal kernel */ - cmp RETURN_1,SCB_PAGEDIN je return - or SCSISIGO,ATNO /* assert ATNO */ - cmp ARG_1,SCB_LIST_NULL jne find_abort_tag - mvi MSG_ABORT call mk_mesg - jmp ITloop -find_abort_tag: - mvi MSG_ABORT_TAG call mk_mesg - jmp ITloop - -/* - * Make a working copy of the scatter-gather parameters from the SCB. - */ -sg_scb2ram: - mov HADDR0, SCB_DATAPTR0 - mov HADDR1, SCB_DATAPTR1 - mov HADDR2, SCB_DATAPTR2 - mov HADDR3, SCB_DATAPTR3 - mov HCNT0, SCB_DATACNT0 - mov HCNT1, SCB_DATACNT1 - mov HCNT2, SCB_DATACNT2 - - mov STCNT0, HCNT0 - mov STCNT1, HCNT1 - mov STCNT2, HCNT2 - - mov SG_COUNT,SCB_SGCOUNT - - mov SG_NEXT0, SCB_SGPTR0 - mov SG_NEXT1, SCB_SGPTR1 - mov SG_NEXT2, SCB_SGPTR2 - mov SG_NEXT3, SCB_SGPTR3 ret - -/* - * Copying RAM values back to SCB, for Save Data Pointers message, but - * only if we've actually been into a data phase to change them. This - * protects against bogus data in scratch ram and the residual counts - * since they are only initialized when we go into data_in or data_out. - */ -sg_ram2scb: - test FLAGS, DPHASE jz return - mov SCB_SGCOUNT,SG_COUNT - - mov SCB_SGPTR0,SG_NEXT0 - mov SCB_SGPTR1,SG_NEXT1 - mov SCB_SGPTR2,SG_NEXT2 - mov SCB_SGPTR3,SG_NEXT3 - - mov SCB_DATAPTR0,SHADDR0 - mov SCB_DATAPTR1,SHADDR1 - mov SCB_DATAPTR2,SHADDR2 - mov SCB_DATAPTR3,SHADDR3 - -/* - * Use the residual number since STCNT is corrupted by any message transfer - */ - mov SCB_DATACNT0,SCB_RESID_DCNT0 - mov SCB_DATACNT1,SCB_RESID_DCNT1 - mov SCB_DATACNT2,SCB_RESID_DCNT2 ret - -/* - * Add the array base TARG_SCRATCH to the target offset (the target address - * is in SCSIID), and return the result in SINDEX. The accumulator - * contains the 3->8 decoding of the target ID on return. - */ -ndx_dtr: - shr A,SCSIID,4 - test SBLKCTL,SELBUSB jz ndx_dtr_2 - or A,0x08 /* Channel B entries add 8 */ -ndx_dtr_2: - add SINDEX,TARG_SCRATCH,A ret - -/* - * If we need to negotiate transfer parameters, build the WDTR or SDTR message - * starting at the address passed in SINDEX. DINDEX is modified on return. - * The SCSI-II spec requires that Wide negotiation occur first and you can - * only negotiate one or the other at a time otherwise in the event of a message - * reject, you wouldn't be able to tell which message was the culprit. - */ -mk_dtr: - test SCB_CONTROL,NEEDWDTR jnz mk_wdtr_16bit - mvi ARG_1, MAXOFFSET /* Force an offset of 15 or 8 if WIDE */ - -mk_sdtr: - mvi DINDIR,1 /* extended message */ - mvi DINDIR,3 /* extended message length = 3 */ - mvi DINDIR,1 /* SDTR code */ - call sdtr_to_rate - mov DINDIR,RETURN_1 /* REQ/ACK transfer period */ - cmp ARG_1, MAXOFFSET je mk_sdtr_max_offset - and DINDIR,0x0f,SINDIR /* Sync Offset */ - -mk_sdtr_done: - add MSG_LEN,COMP_MSG0,DINDEX ret /* update message length */ - -mk_sdtr_max_offset: -/* - * We're initiating sync negotiation, so request the max offset we can (15 or 8) - */ - /* Talking to a WIDE device? */ - test SCSIRATE, WIDEXFER jnz wmax_offset - mvi DINDIR, MAX_OFFSET_8BIT - jmp mk_sdtr_done - -wmax_offset: - mvi DINDIR, MAX_OFFSET_16BIT - jmp mk_sdtr_done - -mk_wdtr_16bit: - mvi ARG_1,BUS_16_BIT -mk_wdtr: - mvi DINDIR,1 /* extended message */ - mvi DINDIR,2 /* extended message length = 2 */ - mvi DINDIR,3 /* WDTR code */ - mov DINDIR,ARG_1 /* bus width */ - - add MSG_LEN,COMP_MSG0,DINDEX ret /* update message length */ - -sdtr_to_rate: - call ndx_dtr /* index scratch space for target */ - shr A,SINDIR,0x4 - dec SINDEX /* Preserve SINDEX */ - and A,0x7 - clr RETURN_1 -sdtr_to_rate_loop: - test A,0x0f jz sdtr_to_rate_done - add RETURN_1,0x19 - dec A - jmp sdtr_to_rate_loop -sdtr_to_rate_done: - shr RETURN_1,0x2 - add RETURN_1,0x19 - test SXFRCTL0,ULTRAEN jz return - shr RETURN_1,0x1 -return: - ret diff -u --recursive --new-file v2.0.30/linux/drivers/scsi/aic7xxx_asm.c linux/drivers/scsi/aic7xxx_asm.c --- v2.0.30/linux/drivers/scsi/aic7xxx_asm.c Sat Apr 20 10:59:10 1996 +++ linux/drivers/scsi/aic7xxx_asm.c Wed Dec 31 16:00:00 1969 @@ -1,734 +0,0 @@ -/*+M************************************************************************* - * Adaptec AIC7xxx sequencer code assembler. - * - * 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. - * - * Comments are started by `#' and continue to the end of the line; lines - * may be of the form: - *